diff options
Diffstat (limited to 'kolourpaint')
305 files changed, 54081 insertions, 0 deletions
diff --git a/kolourpaint/AUTHORS b/kolourpaint/AUTHORS new file mode 100644 index 00000000..c4befef2 --- /dev/null +++ b/kolourpaint/AUTHORS @@ -0,0 +1,112 @@ + +Authors +======= + +Clarence Dang <dang@kde.org> +Maintainer + +Thurston Dang <thurston_dang@users.sourceforge.net> +Chief Investigator + +Kristof Borrey <borrey@kde.org> +Icons + +Kazuki Ohta <mover@hct.zaq.ne.jp> +InputMethod Support + +Nuno Pinheiro <nf.pinheiro@gmail.com> +Icons + +Danny Allen <dannya40uk@yahoo.co.uk> +Icons + +Martin Koller <m.koller@surfeu.at> +Scanning Support + + +Thanks To +========= + +Rashid N. Achilov +Toyohiro Asukai +Bela-Andreas Bargel +Waldo Bastian +Ismail Belhachmi +Sashmit Bhaduri +Antonio Bianco +Stephan Binner +Markus Brueffer +Rob Buis +Lucijan Busch +Mikhail Capone +Enrico Ceppi +Tom Chance +Albert Astals Cid +Jennifer Dang +Lawrence Dang +Christoph Eckert +David Faure +P. Fisher +Nicolas Goutte +Herbert Graeber +Brad Grant +David Greenaway +Wilco Greven +Hubert Grininger +Adriaan de Groot +Esben Mose Hansen +Nadeem Hasan +Simon Hausmann +Michael Hoehne +Andrew J +Werner Joss +Derek Kite +Tobias Koenig +Dmitry Kolesnikov +Stephan Kulow +Eric Laffoon +Michael Lake +Sebastien Laout +David Ling +Volker Lochte +Anders Lund +Jacek Masiulaniec +Benjamin Meyer +Amir Michail +Robert Moszczynski +Dirk Mueller +Ruivaldo Neto +Ralf Nolden +Steven Pasternak +Cedric Pasteur +Erik K. Pedersen +Dennis Pennekamp +Jos Poortvliet +Boudewijn Rempt +Marcos Rodriguez +Matt Rogers +Francisco Jose Canizares Santofimia +Bram Schoenmakers +Dirk Schonberger +Lutz Schweizer +Emmeran Seehuber +Peter Simonsson +Andrew Simpson +A T Somers +Igor Stepin +Stephen Sweeney +Bart Symons +Stefan Taferner +Hogne Titlestad +Brandon Mark Turner +Jonathan Turner +Stephan Unknown +Dries Verachtert +Simon Vermeersch +Lauri Watts +Mark Wege +Christoph Wiesen +Andre Wobbeking +Luke-Jr +Maxim_86ualb2 +Michele diff --git a/kolourpaint/BUGS b/kolourpaint/BUGS new file mode 100644 index 00000000..84f3391f --- /dev/null +++ b/kolourpaint/BUGS @@ -0,0 +1,154 @@ + +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: + + +1. Flicker when zooming in/out. + +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. + +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 text and 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) e.g. an ellipse of height 1 always has a width 1 + pixel less than it should be. 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. + +13. Due to a workaround for a Qt bug, writing text with the foreground + colour set to transparent is incredibly slow. Write your text in + another colour and then set the foreground colour to transparent after + you've finished typing to avoid this issue. + +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. + + b) KolourPaint is screen depth dependent. Opening an image with a + an alpha channel and/or a depth higher than the screen and then + saving it will likely result in loss of colour information. Also, + 8-bit screens are not supported at all. To reduce data loss, run + your screen at 24-bit. This bug will be addressed in a future + version of KolourPaint. + +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(). + +22. The Emboss, Blur and Sharpen effects give different results depending + on _both_: + + a) The KDE version KolourPaint was compiled with + (due to KImageEffect not supporting strength settings for these + effects in KDE 3.0, KolourPaint repeats these effects in order to + simulate strength) + + b) The KDE version KolourPaint is running under + (e.g. for the same function calls, KDE 3.2's effects are slower but + give better results than those in KDE 3.0) + +23. Changing tool options while in the middle of a drawing option may + confuse KolourPaint. 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 has not been tested at zoom levels other than 100%. + +28. KolourPaint has not been tested against invalid or malicious clipboard + data. + + +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/kolourpaint/COPYING b/kolourpaint/COPYING new file mode 100644 index 00000000..df47eb9b --- /dev/null +++ b/kolourpaint/COPYING @@ -0,0 +1,23 @@ +Copyright (c) 2003,2004,2005,2006 Clarence Dang <dang@kde.org> +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. diff --git a/kolourpaint/ChangeLog b/kolourpaint/ChangeLog new file mode 100644 index 00000000..9acd0397 --- /dev/null +++ b/kolourpaint/ChangeLog @@ -0,0 +1,15 @@ + +For logs of _every_ single change made to KolourPaint between any date or +revision, visit: + + http://websvn.kde.org/trunk/KDE/kdegraphics/kolourpaint + + http://websvn.kde.org/branches/KDE/<version>/kdegraphics/kolourpaint + http://websvn.kde.org/tags/KDE/<version>/kdegraphics/kolourpaint + + http://websvn.kde.org/branches/kolourpaint + http://websvn.kde.org/tags/kolourpaint + + +For a summary of user-visible changes between each release, read NEWS. + diff --git a/kolourpaint/Makefile.am b/kolourpaint/Makefile.am new file mode 100644 index 00000000..35da2859 --- /dev/null +++ b/kolourpaint/Makefile.am @@ -0,0 +1,76 @@ +SUBDIRS = cursors pics pixmapfx tools views widgets + +bin_PROGRAMS = kolourpaint + + +kolourpaint.o: kolourpaintlicense.h kolourpaintversion.h + +kolourpaintlicense.h : $(srcdir)/COPYING + echo "static const char * const kpLicenseText =" > kolourpaintlicense.h + cat $(srcdir)/COPYING | sed -e 's/"/\\"/g' -e 's/$$/\\n"/g' -e 's/^/ "/g' >> kolourpaintlicense.h + echo ";" >> kolourpaintlicense.h + +kolourpaintversion.h : $(srcdir)/VERSION + echo "static const char * const kpVersionText =" > kolourpaintversion.h + cat $(srcdir)/VERSION | sed -e 's/"/\\"/g' -e 's/$$/"/g' -e 's/^/ "/g' >> kolourpaintversion.h + echo ";" >> kolourpaintversion.h + +CLEANFILES = kolourpaintlicense.h kolourpaintversion.h + + +kolourpaint_SOURCES = kolourpaint.cpp \ + kpdocument.cpp \ + kpdocumentmetainfo.cpp \ + kpdocumentsaveoptions.cpp \ + kpdocumentsaveoptionswidget.cpp \ + kpview.cpp \ + kpcolor.cpp kpcommandhistory.cpp \ + kpmainwindow.cpp \ + kpmainwindow_edit.cpp kpmainwindow_help.cpp \ + kpmainwindow_image.cpp kpmainwindow_tools.cpp \ + kpmainwindow_file.cpp kpmainwindow_settings.cpp kpmainwindow_statusbar.cpp \ + kpmainwindow_text.cpp \ + kpmainwindow_view.cpp \ + kpselection.cpp kpselectiondrag.cpp kpselectiontransparency.cpp \ + kpsinglekeytriggersaction.cpp \ + kptemppixmap.cpp kptextstyle.cpp \ + kpthumbnail.cpp \ + kptool.cpp \ + kpviewmanager.cpp \ + kpviewscrollablecontainer.cpp \ + kpwidgetmapper.cpp +kolourpaint_LDFLAGS = $(all_libraries) $(KDE_RPATH) +kolourpaint_LDADD = $(LIB_KDEPRINT) \ + cursors/libkolourpaintcursors.la \ + pixmapfx/libkolourpaintpixmapfx.la \ + tools/libkolourpainttools.la \ + views/libkolourpaintviews.la \ + widgets/libkolourpaintwidgets.la + +AM_CPPFLAGS = -I$(srcdir)/cursors -I$(srcdir)/interfaces \ + -I$(srcdir)/pixmapfx \ + -I$(srcdir)/tools \ + -I$(srcdir)/views \ + -I$(srcdir)/widgets $(all_includes) + +METASOURCES = AUTO + +rcdir = $(kde_datadir)/kolourpaint +rc_DATA = kolourpaintui.rc + +xdg_apps_DATA = kolourpaint.desktop + +messages: rc.cpp + $(EXTRACTRC) *.rc *.ui \ + cursors/*.rc cursors/*.ui \ + pixmapfx/*.rc pixmapfx/*.ui \ + tools/*.rc tools/*.ui \ + widgets/*.rc widgets/*.ui \ + >> rc.cpp + $(XGETTEXT) *.cpp *.h \ + cursors/*.cpp cursors/*.h \ + pixmapfx/*.cpp pixmapfx/*.h \ + tools/*.cpp tools/*.h \ + widgets/*.cpp widgets/*.h \ + -o $(podir)/kolourpaint.pot + diff --git a/kolourpaint/NEWS b/kolourpaint/NEWS new file mode 100644 index 00000000..43828069 --- /dev/null +++ b/kolourpaint/NEWS @@ -0,0 +1,349 @@ + +KolourPaint 1.4_relight Series (branches/KDE/3.5/) +=============================== + +KolourPaint 1.4.9_relight (Frozen ???) + + * Ensure selection operations always repaint correctly + [the effects of this change are unlikely to be functionality visible] + +KolourPaint 1.4.8_relight (Frozen 2007-10-08) + + * Always enable the paste actions to guarantee that pasting from + non-Qt applications is always allowed (non-Qt applications do not + notify KolourPaint when they place objects into the clipboard) + + * Paste transparent pixels as white instead of uninitialized colors, + when the app does not support pasting transparent pixels (such as + OpenOffice.org) + + * 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) + + * Saving, exporting and printing a document with an active text box, + that has opaque text and a transparent background, antialiases the + text with the document below + + * "Edit / Paste From File..." respects the "Transparent" selection mode + + * Focus an input field when the "Skew", "Rotate" and "Resize / Scale" + dialogs are displayed -- this allows the user to edit values without + an extra mouse click + + * Add error dialogs for: + - if scanning support is unavailable + - running out of graphics memory during a scan + + * Other minor changes -- some of these are: + - Finish the current shape in more cases of menu item accesses + - [internal] kpDocument::selectionCopyOntoDocument() marks the document + as modified + - More comments + +KolourPaint 1.4.7_relight (Frozen 2007-05-14) + + * Save local files atomically - KolourPaint will no longer truncate + an existing file if the KImageIO library for the file format is + missing or if you run out of disk space. + + * Add "File / Scan..." feature (Martin Koller) + + * Add global session save/restore (Bug #94651) + + * Make "File / Open Recent" consistently work when multiple windows are + open + + * 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 + + * Change minimum allowed zoom level for the grid from 600% to 400% + +KolourPaint 1.4.6_relight (Frozen 2007-01-13) + + * Fix crash triggered by rapidly deselecting the selection after + drag-scaling it (Bug #117866) + [also in branches/KDE/3.[34]/, branches/kolourpaint/1.2_kde3/] + +KolourPaint 1.4.5_relight (Frozen 2006-09-19) + + * Translation updates + +KolourPaint 1.4.4_relight (Frozen 2006-07-12) + + * Minor code cleanups and corrections + +KolourPaint 1.4.3_relight (Frozen 2006-05-02) + + * Probably translation updates + +KolourPaint 1.4.2_relight (Frozen 2006-03-12) + + * Printing improvements (Bug #108976) + - Respect image DPI + - Fit image to page if image is too big + - Centre image on page + [also in branches/KDE/3.[34]/, branches/kolourpaint/1.2_kde3/] + +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+<key>) + * 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/kolourpaint/README b/kolourpaint/README new file mode 100644 index 00000000..b045e3b9 --- /dev/null +++ b/kolourpaint/README @@ -0,0 +1,102 @@ + +KolourPaint Version 1.4.9_relight (KDE 3.5.9 Release Frozen ???) +http://www.kolourpaint.org/ + +Copyright (c) 2003,2004,2005,2006 Clarence Dang <dang@kde.org> + + +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): + + +1. What is KolourPaint? +======================= + +KolourPaint is a free, easy-to-use paint program for 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. + + +2. 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 + +* Selections (fully undo- and redo-able) + - Rectangular, Elliptical, Free-Form shapes + - Choice between Opaque and Transparent selections + - Full Clipboard/Edit Menu support + - Freehand resizeable + +* Colour 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 Colour" + +* Image Effects + - Autocrop / Remove Internal Border + - Balance (Brightness, Contrast, Gamma) + - Clear, Emboss, Flatten, Flip, Invert (with choice of channels) + - Reduce Colours, Reduce to Greyscale, 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 + + +3. Updates & More Information +============================= + +Visit: http://www.kolourpaint.org/ + + +4. 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. + + +5. 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/kolourpaint/VERSION b/kolourpaint/VERSION new file mode 100644 index 00000000..8b2f0f9b --- /dev/null +++ b/kolourpaint/VERSION @@ -0,0 +1 @@ +1.4.8_relight-post diff --git a/kolourpaint/cursors/Makefile.am b/kolourpaint/cursors/Makefile.am new file mode 100644 index 00000000..5ae0504a --- /dev/null +++ b/kolourpaint/cursors/Makefile.am @@ -0,0 +1,11 @@ +INCLUDES = -I$(srcdir)/.. -I$(srcdir)/../cursors -I$(srcdir)/../interfaces \ + -I$(srcdir)/../pixmapfx \ + -I$(srcdir)/../tools \ + -I$(srcdir)/../views \ + -I$(srcdir)/../widgets $(all_includes) + +noinst_LTLIBRARIES = libkolourpaintcursors.la +libkolourpaintcursors_la_SOURCES = kpcursorlightcross.cpp kpcursorprovider.cpp + +METASOURCES = AUTO + diff --git a/kolourpaint/cursors/kpcursorlightcross.cpp b/kolourpaint/cursors/kpcursorlightcross.cpp new file mode 100644 index 00000000..0595d320 --- /dev/null +++ b/kolourpaint/cursors/kpcursorlightcross.cpp @@ -0,0 +1,128 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <qbitmap.h> +#include <qcursor.h> + +#include <kdebug.h> + + +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: + default: + 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 *kpMakeCursorLightCross () +{ +#if DEBUG_KP_CURSOR_LIGHT_CROSS + kdDebug () << "kpMakeCursorLightCross() " << endl; +#endif + + const int side = 24; + const int byteSize = (side * side) / 8; + unsigned char *colorBitmap = new unsigned char [byteSize]; + unsigned char *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_ + } + + QCursor *cursor = new QCursor (QBitmap (side, side, colorBitmap, true/*little endian bit order*/), + QBitmap (side, side, maskBitmap, true/*little endian bit order*/)); + + delete [] maskBitmap; + delete [] colorBitmap; + + return cursor; +} + diff --git a/kolourpaint/cursors/kpcursorlightcross.h b/kolourpaint/cursors/kpcursorlightcross.h new file mode 100644 index 00000000..d2bf83e1 --- /dev/null +++ b/kolourpaint/cursors/kpcursorlightcross.h @@ -0,0 +1,38 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 *kpMakeCursorLightCross (); + + +#endif // __kp_cursor_light_cross_h__ diff --git a/kolourpaint/cursors/kpcursorprovider.cpp b/kolourpaint/cursors/kpcursorprovider.cpp new file mode 100644 index 00000000..45d43801 --- /dev/null +++ b/kolourpaint/cursors/kpcursorprovider.cpp @@ -0,0 +1,49 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <qcursor.h> + +#include <kstaticdeleter.h> + +#include <kpcursorlightcross.h> + + +static const QCursor *theLightCursor = 0; + + +// public static +QCursor kpCursorProvider::lightCross () +{ + // TODO: don't leak (although it's cleaned up on exit by OS anyway) + if (!theLightCursor) + theLightCursor = kpMakeCursorLightCross (); + + return *theLightCursor; +} diff --git a/kolourpaint/cursors/kpcursorprovider.h b/kolourpaint/cursors/kpcursorprovider.h new file mode 100644 index 00000000..191b4f78 --- /dev/null +++ b/kolourpaint/cursors/kpcursorprovider.h @@ -0,0 +1,43 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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/kolourpaint/kolourpaint.cpp b/kolourpaint/kolourpaint.cpp new file mode 100644 index 00000000..b9ba0f0c --- /dev/null +++ b/kolourpaint/kolourpaint.cpp @@ -0,0 +1,229 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <qfile.h> + +#include <dcopclient.h> +#include <kaboutdata.h> +#include <kapplication.h> +#include <kcmdlineargs.h> +#include <kdebug.h> +#include <kimageio.h> +#include <klocale.h> + +// for srand +#include <stdlib.h> +#include <sys/types.h> +#include <unistd.h> + +#include <kpdefs.h> +#include <kpmainwindow.h> + +#include <kolourpaintlicense.h> +#include <kolourpaintversion.h> + + +static const KCmdLineOptions cmdLineOptions [] = +{ + {"+[file]", I18N_NOOP ("Image file to open"), 0}, + KCmdLineLastOption +}; + + +int main (int argc, char *argv []) +{ + KAboutData aboutData + ( + "kolourpaint", + I18N_NOOP ("KolourPaint"), + kpVersionText, + I18N_NOOP ("Paint Program for KDE"), + KAboutData::License_Custom, + 0/*copyright statement - see licence instead*/, + 0/*no free text*/, + "http://www.kolourpaint.org/" + ); + + + // this is _not_ the same as KAboutData::License_BSD + aboutData.setLicenseText (kpLicenseText); + + + // SYNC: with AUTHORS + + aboutData.addAuthor ("Clarence Dang", I18N_NOOP ("Maintainer"), "dang@kde.org"); + aboutData.addAuthor ("Thurston Dang", I18N_NOOP ("Chief Investigator"), + "thurston_dang@users.sourceforge.net"); + aboutData.addAuthor ("Kristof Borrey", I18N_NOOP ("Icons"), "borrey@kde.org"); + aboutData.addAuthor ("Kazuki Ohta", I18N_NOOP ("InputMethod Support"), "mover@hct.zaq.ne.jp"); + aboutData.addAuthor ("Nuno Pinheiro", I18N_NOOP ("Icons"), "nf.pinheiro@gmail.com"); + aboutData.addAuthor ("Danny Allen", I18N_NOOP ("Icons"), "dannya40uk@yahoo.co.uk"); + aboutData.addAuthor ("Martin Koller", + 0/*STRING: string freeze prevents us from writing: Scanning Support*/, + "m.koller@surfeu.at"); + + + aboutData.addCredit ("Rashid N. Achilov"); + aboutData.addCredit ("Toyohiro Asukai"); + aboutData.addCredit ("Bela-Andreas Bargel"); + aboutData.addCredit ("Waldo Bastian"); + aboutData.addCredit ("Ismail Belhachmi"); + aboutData.addCredit ("Sashmit Bhaduri"); + aboutData.addCredit ("Antonio Bianco"); + aboutData.addCredit ("Stephan Binner"); + aboutData.addCredit ("Markus Brueffer"); + aboutData.addCredit ("Rob Buis"); + aboutData.addCredit ("Lucijan Busch"); + aboutData.addCredit ("Mikhail Capone"); + aboutData.addCredit ("Enrico Ceppi"); + aboutData.addCredit ("Tom Chance"); + aboutData.addCredit ("Albert Astals Cid"); + aboutData.addCredit ("Jennifer Dang"); + aboutData.addCredit ("Lawrence Dang"); + aboutData.addCredit ("Christoph Eckert"); + aboutData.addCredit ("David Faure"); + aboutData.addCredit ("P. Fisher"); + aboutData.addCredit ("Nicolas Goutte"); + aboutData.addCredit ("Herbert Graeber"); + aboutData.addCredit ("Brad Grant"); + aboutData.addCredit ("David Greenaway"); + aboutData.addCredit ("Wilco Greven"); + aboutData.addCredit ("Hubert Grininger"); + aboutData.addCredit ("Adriaan de Groot"); + aboutData.addCredit ("Esben Mose Hansen"); + aboutData.addCredit ("Nadeem Hasan"); + aboutData.addCredit ("Simon Hausmann"); + aboutData.addCredit ("Michael Hoehne"); + aboutData.addCredit ("Andrew J"); + aboutData.addCredit ("Werner Joss"); + aboutData.addCredit ("Derek Kite"); + aboutData.addCredit ("Tobias Koenig"); + aboutData.addCredit ("Dmitry Kolesnikov"); + aboutData.addCredit ("Stephan Kulow"); + aboutData.addCredit ("Eric Laffoon"); + aboutData.addCredit ("Michael Lake"); + aboutData.addCredit ("Sebastien Laout"); + aboutData.addCredit ("David Ling"); + aboutData.addCredit ("Volker Lochte"); + aboutData.addCredit ("Anders Lund"); + aboutData.addCredit ("Jacek Masiulaniec"); + aboutData.addCredit ("Benjamin Meyer"); + aboutData.addCredit ("Amir Michail"); + aboutData.addCredit ("Robert Moszczynski"); + aboutData.addCredit ("Dirk Mueller"); + aboutData.addCredit ("Ruivaldo Neto"); + aboutData.addCredit ("Ralf Nolden"); + aboutData.addCredit ("Steven Pasternak"); + aboutData.addCredit ("Cédric Pasteur"); + aboutData.addCredit ("Erik K. Pedersen"); + aboutData.addCredit ("Dennis Pennekamp"); + aboutData.addCredit ("Jos Poortvliet"); + aboutData.addCredit ("Boudewijn Rempt"); + aboutData.addCredit ("Marcos Rodriguez"); + aboutData.addCredit ("Matt Rogers"); + aboutData.addCredit ("Francisco Jose Canizares Santofimia"); + aboutData.addCredit ("Bram Schoenmakers"); + aboutData.addCredit ("Dirk Schönberger"); + aboutData.addCredit ("Lutz Schweizer"); + aboutData.addCredit ("Emmeran Seehuber"); + aboutData.addCredit ("Peter Simonsson"); + aboutData.addCredit ("Andrew Simpson"); + aboutData.addCredit ("A T Somers"); + aboutData.addCredit ("Igor Stepin"); + aboutData.addCredit ("Stephen Sweeney"); + aboutData.addCredit ("Bart Symons"); + aboutData.addCredit ("Stefan Taferner"); + aboutData.addCredit ("Hogne Titlestad"); + aboutData.addCredit ("Brandon Mark Turner"); + aboutData.addCredit ("Jonathan Turner"); + aboutData.addCredit ("Stephan Unknown"); + aboutData.addCredit ("Dries Verachtert"); + aboutData.addCredit ("Simon Vermeersch"); + aboutData.addCredit ("Lauri Watts"); + aboutData.addCredit ("Mark Wege"); + aboutData.addCredit ("Christoph Wiesen"); + aboutData.addCredit ("Andre Wobbeking"); + aboutData.addCredit ("Luke-Jr"); + aboutData.addCredit ("Maxim_86ualb2"); + aboutData.addCredit ("Michele"); + + + KCmdLineArgs::init (argc, argv, &aboutData); + KCmdLineArgs::addCmdLineOptions (cmdLineOptions); + + KApplication app; + + + // mainly for changing wallpaper :) + DCOPClient *client = app.dcopClient (); + if (!client->attach ()) + kdError () << "Could not contact DCOP server" << endl; + + // mainly for the Spraycan Tool + srand ((unsigned int) (getpid () + getppid ())); + + // access more formats + KImageIO::registerFormats (); + + + // Qt says this is necessary but I don't think it is... + QObject::connect (&app, SIGNAL (lastWindowClosed ()), + &app, SLOT (quit ())); + + + if (app.isRestored ()) + { + // Creates a kpMainWindow using the default constructor and then + // calls kpMainWindow::readProperties(). + RESTORE (kpMainWindow) + } + else + { + kpMainWindow *mainWindow; + KCmdLineArgs *args = KCmdLineArgs::parsedArgs (); + + if (args->count () >= 1) + { + for (int i = 0; i < args->count (); i++) + { + mainWindow = new kpMainWindow (args->url (i)); + mainWindow->show (); + } + } + else + { + mainWindow = new kpMainWindow (); + mainWindow->show (); + } + + args->clear (); + } + + + return app.exec (); +} diff --git a/kolourpaint/kolourpaint.desktop b/kolourpaint/kolourpaint.desktop new file mode 100644 index 00000000..8a586219 --- /dev/null +++ b/kolourpaint/kolourpaint.desktop @@ -0,0 +1,92 @@ +[Desktop Entry] + +Name=KolourPaint +Name[nb]=KPaint +Name[ne]=रङ पेन्ट +Name[pa]=ਕੇ-ਰੰਗ-ਪੇਂਟ +Name[sv]=Kolourpaint +Name[ta]=நிற பெயின்ட் +Name[zh_TW]=KolourPaint 小畫家 +GenericName=Paint Program +GenericName[af]=Verf Program +GenericName[ar]=برنامج تلوين +GenericName[bg]=Графичен редактор +GenericName[br]=Goulev tresañ +GenericName[bs]=Jednostavni program za crtanje +GenericName[ca]=Programa de pintura +GenericName[cs]=Kreslící program +GenericName[cy]=Rhaglen Peintio +GenericName[da]=Maleprogram +GenericName[de]=Mal- und Zeichenprogramm +GenericName[el]=Πρόγραμμα ζωγραφικής +GenericName[eo]=Pentrilo +GenericName[es]=Programa de pintura +GenericName[et]=Joonistusprogramm +GenericName[eu]=Marrazteko programa +GenericName[fa]=برنامۀ رنگ +GenericName[fi]=Piirto-ohjelma +GenericName[fr]=Petit programme de dessin +GenericName[ga]=Clár Péinteála +GenericName[gl]=Programa de debuxo +GenericName[he]=תוכנית ציור +GenericName[hi]=छवि बनाने का प्रोग्राम +GenericName[hr]=Program za slikanje +GenericName[hu]=Rajzolóprogram +GenericName[is]=Teikniforrit +GenericName[it]=Programma di disegno +GenericName[ja]=ペイントプログラム +GenericName[kk]=Сурет салу бағдарламасы +GenericName[km]=កម្មវិធីគូរ +GenericName[lt]=Piešimo programa +GenericName[lv]=Krāsošanas Programma +GenericName[ms]=Program Mewarna +GenericName[mt]=Programm sempliċi tat-tpinġija +GenericName[nb]=Maleprogram +GenericName[nds]=Maalprogramm +GenericName[ne]=पेन्ट कार्यक्रम +GenericName[nl]=Tekenprogramma +GenericName[nn]=Måleprogram +GenericName[nso]=Lenaneo la Paint +GenericName[pa]=ਰੰਗ ਕਾਰਜ +GenericName[pl]=Program Paint +GenericName[pt]=Programa de Pintura +GenericName[pt_BR]=Programa de Pintura +GenericName[ro]=Program de desenare +GenericName[ru]=Графический редактор +GenericName[rw]=Porogaramu Gusiga irangi +GenericName[se]=Málenprográmma +GenericName[sk]=Kreslenie +GenericName[sl]=Slikarski program +GenericName[sr]=Програм за сликање +GenericName[sr@Latn]=Program za slikanje +GenericName[sv]=Ritprogram +GenericName[ta]=பெயிண்ட் நிரலி +GenericName[tg]=Муҳаррири графикӣ +GenericName[th]=โปรแกรมวาดภาพธรรมดาๆ +GenericName[tr]=Boyama Programı +GenericName[uk]=Програма для малювання +GenericName[uz]=Chizish dasturi +GenericName[uz@cyrillic]=Чизиш дастури +GenericName[ven]=Mbekanyamushumo ya Pennde +GenericName[wa]=Program di dessinaedje +GenericName[xh]=Udweliso lwenkqubo lwepeyinti +GenericName[zh_CN]=绘图程序 +GenericName[zh_HK]=繪圖程式 +GenericName[zh_TW]=繪圖程式 +GenericName[zu]=Elila Iprogremu Kapende +Icon=kolourpaint + +Type=Application +Exec=kolourpaint %u +DocPath=kolourpaint/index.html + +# SYNC: Run branches/kolourpaint/control/scripts/gen_mimetype_line.sh in +# the version of kdelibs/kimgio/ (e.g. KDE 3.5) KolourPaint is +# shipped with. +MimeType=image/fax-g3;image/gif;image/jp2;image/jpeg;image/png;image/tiff;image/x-bmp;image/x-dds;image/x-eps;image/x-exr;image/x-hdr;image/x-ico;image/x-pcx;image/x-portable-bitmap;image/x-portable-greymap;image/x-portable-pixmap;image/x-rgb;image/x-targa;image/x-vnd.adobe.photoshop;image/x-xbm;image/x-xcf-gimp;image/x-xpm;video/x-mng; + +Categories=Qt;KDE;Graphics; +Terminal=false +X-KDE-StartupNotify=true +X-DCOP-ServiceType=Multi + diff --git a/kolourpaint/kolourpaintui.rc b/kolourpaint/kolourpaintui.rc new file mode 100644 index 00000000..876c3e38 --- /dev/null +++ b/kolourpaint/kolourpaintui.rc @@ -0,0 +1,169 @@ +<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd"> +<!-- +SYNC: Do not change the number of quotes before the version number + - it is parsed by the KolourPaint wrapper shell script (in standalone + backport releases of KolourPaint) +--> +<gui name="kolourpaint" version="25"> + +<!-- +SYNC: Check for duplicate actions in menus caused by some of our actions + being added to ui_standards.rc. Makes me wonder why we are using + merging in the first place. +--> + +<MenuBar> + <Menu name="file"> + <!-- <Action name="file_new_window" append="new_merge" /> --> + + <Action name="file_scan" append="open_merge" /> + + <Action name="file_export" append="save_merge" /> + <Separator append="save_merge" /> + + <Action name="file_set_as_wallpaper_centered" /> + <Action name="file_set_as_wallpaper_tiled" /> + </Menu> + + <Menu name="edit"> + <Action name="edit_paste_in_new_window" append="edit_paste_merge" /> + + <Action name="edit_copy_to_file" /> + <Action name="edit_paste_from_file" /> + </Menu> + + <!-- SRC: ui_standards.rc v10 (KDE 3.3) --> + <Menu name="view" noMerge="1"><text>&View</text> + <Action name="view_actual_size"/> + <Action name="view_fit_to_page"/> + <Action name="view_fit_to_width"/> + <Action name="view_fit_to_height"/> + + <Separator /> + + <!-- <MergeLocal name="view_zoom_merge"/> --> + <Action name="view_zoom_in"/> + + <!-- Changed from "view_zoom" to allow custom ordering of zoom + actions in "mainToolBar" (which has a hardcoded position for + "view_zoom"). + --> + <Action name="view_zoom_to"/> + + <Action name="view_zoom_out"/> + + <WeakSeparator/> + + <Action name="view_redisplay"/> + + <Separator/> + + <!-- <MergeLocal/> --> + <Action name="view_show_grid" /> + <Action name="view_show_thumbnail" /> + + <Separator/> + + <Action name="view_zoomed_thumbnail" /> + <Action name="view_show_thumbnail_rectangle" /> + </Menu> + + <Menu name="image"><text>&Image</text> + <Action name="image_crop" /> + <Action name="image_auto_crop" /> + <Separator /> + <Action name="image_resize_scale" /> + <Action name="image_flip" /> + <Action name="image_rotate" /> + <Action name="image_skew" /> + <Separator /> + <Action name="image_convert_to_black_and_white" /> + <Action name="image_convert_to_grayscale" /> + <Action name="image_more_effects" /> + <Separator /> + <Action name="image_invert_colors" /> + <Action name="image_clear" /> + </Menu> + + <Menu name="settings"> + <Action name="settings_show_path" append="show_merge" /> + </Menu> + + <Menu name="help"> + <Action name="help_taking_screenshots" /> + </Menu> + +</MenuBar> + +<ToolBar name="mainToolBar"> + <Action name="view_zoom_in" /> + <Action name="view_zoom_out" /> + <Action name="view_zoom_to" /> +</ToolBar> + +<ToolBar name="textToolBar" fullWidth="false" position="top" hidden="true"><text>Text Toolbar</text> + <Action name="text_font_family" /> + <Action name="text_font_size" /> + <Separator /> + <Action name="text_bold" /> + <Action name="text_italic" /> + <Action name="text_underline" /> + <Action name="text_strike_thru" /> +</ToolBar> + +<Menu name="selectionToolRMBMenu"><text>Selection Tool RMB Menu</text> + <!-- SRC: ui_standards.rc v10 (KDE 3.3) --> + <!-- <Menu name="edit"><text>&Edit</text> --> + + <!-- <Action name="edit_undo"/> + <Action name="edit_redo"/> + <MergeLocal name="edit_undo_merge"/> + <Separator/> --> + <Action name="edit_cut"/> + <Action name="edit_copy"/> + <Action name="edit_paste"/> + <!-- CUSTOM --> <!-- <Action name="edit_paste_in_new_window" /> --> + <MergeLocal name="edit_paste_merge"/> + <Action name="edit_clear"/> + <!-- <Separator/> --> + <Action name="edit_select_all"/> + <!-- <Action name="edit_deselect"/> --> + <MergeLocal name="edit_select_merge"/> + <Separator/> + <Action name="edit_find"/> + <Action name="edit_find_next"/> + <Action name="edit_find_last"/> + <Action name="edit_replace"/> + <MergeLocal name="edit_find_merge"/> + <Separator/> + <!-- CUSTOM --> <Action name="edit_copy_to_file" /> + <!-- CUSTOM --> <Action name="edit_paste_from_file" /> + <MergeLocal/> + + <!-- </Menu> --> + + + <Separator/> + + + <!-- <Menu name="image"><text>&Image</text> --> + + <Action name="image_crop" /> + <!-- <Action name="image_auto_crop" /> --> + <Separator /> + <Action name="image_resize_scale" /> + <Action name="image_flip" /> + <Action name="image_rotate" /> + <Action name="image_skew" /> + <Separator /> + <!-- <Action name="image_convert_to_black_and_white" /> --> + <!-- <Action name="image_convert_to_grayscale" /> --> + <!-- <Action name="image_more_effects" /> --> + <!-- <Separator /> --> + <Action name="image_invert_colors" /> + <!-- <Action name="image_clear" /> --> + + <!-- </Menu> --> +</Menu> + +</gui> diff --git a/kolourpaint/kpcolor.cpp b/kolourpaint/kpcolor.cpp new file mode 100644 index 00000000..a9dc000b --- /dev/null +++ b/kolourpaint/kpcolor.cpp @@ -0,0 +1,360 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <qdatastream.h> + +#include <kdebug.h> + + +// public static +const int kpColor::Exact = 0; + +// public static +const kpColor kpColor::invalid; // TODO: what's wrong with explicitly specifying () constructor? +const kpColor kpColor::transparent (0, 0, 0, true/*isTransparent*/); + + +kpColor::kpColor () + : m_rgbaIsValid (false), + m_colorCacheIsValid (false) +{ +} + +kpColor::kpColor (int red, int green, int blue, bool isTransparent) + : m_colorCacheIsValid (false) +{ + if (red < 0 || red > 255 || + green < 0 || green > 255 || + blue < 0 || blue > 255) + { + kdError () << "kpColor::<ctor>(r=" << red + << ",g=" << green + << ",b=" << blue + << ",t=" << isTransparent + << ") passed out of range values" << endl; + 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 (qAlpha (rgba) > 0 && qAlpha (rgba) < 255) + { + kdError () << "kpColor::<ctor>(QRgb) passed translucent alpha " + << qAlpha (rgba) + << " - trying to recover" + << endl; + + // Forget the alpha channel - make it opaque + m_rgba = qRgb (qRed (m_rgba), qGreen (m_rgba), qBlue (m_rgba)); + m_rgbaIsValid = true; + } + else + { + 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) +{ +} + +// 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 = 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 <class dtype> +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 (isTransparent () != rhs.isTransparent ()) + return false; + + // Are both of us transparent? + if (isTransparent ()) + return true; + + // --- both are now valid and opaque --- + + + if (m_rgba == rhs.m_rgba) + return true; + + + if (processedSimilarity == kpColor::Exact) + return false; + else + { + 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); + } +} + +kpColor::~kpColor () +{ +} + + +// public +bool kpColor::isValid () const +{ + return m_rgbaIsValid; +} + + +// public +int kpColor::red () const +{ + if (!m_rgbaIsValid) + { + kdError () << "kpColor::red() called with invalid kpColor" << endl; + return 0; + } + + if (isTransparent ()) + { + kdError () << "kpColor::red() called with transparent kpColor" << endl; + return 0; + } + + return qRed (m_rgba); +} + +// public +int kpColor::green () const +{ + if (!m_rgbaIsValid) + { + kdError () << "kpColor::green() called with invalid kpColor" << endl; + return 0; + } + + if (isTransparent ()) + { + kdError () << "kpColor::green() called with transparent kpColor" << endl; + return 0; + } + + return qGreen (m_rgba); +} + +// public +int kpColor::blue () const +{ + if (!m_rgbaIsValid) + { + kdError () << "kpColor::blue() called with invalid kpColor" << endl; + return 0; + } + + if (isTransparent ()) + { + kdError () << "kpColor::blue() called with transparent kpColor" << endl; + return 0; + } + + return qBlue (m_rgba); +} + +// public +int kpColor::alpha () const +{ + if (!m_rgbaIsValid) + { + kdError () << "kpColor::alpha() called with invalid kpColor" << endl; + return 0; + } + + const int alpha = qAlpha (m_rgba); + + if (alpha > 0 && alpha < 255) + { + kdError () << "kpColor::alpha() called with translucent kpColor alpha=" << alpha << endl; + + // no translucency + return alpha ? 255 : 0; + } + else + { + return alpha; + } +} + +// public +bool kpColor::isTransparent () const +{ + return (alpha () == 0); +} + +// public +bool kpColor::isOpaque () const +{ + return (alpha () == 255); +} + + +// public +QRgb kpColor::toQRgb () const +{ + if (!m_rgbaIsValid) + { + kdError () << "kpColor::toQRgb() called with invalid kpColor" << endl; + return 0; + } + + return m_rgba; +} + +// public +const QColor &kpColor::toQColor () const +{ + if (!m_rgbaIsValid) + { + kdError () << "kpColor::toQColor() called with invalid kpColor" << endl; + return Qt::black; + } + + if (m_colorCacheIsValid) + return m_colorCache; + + if (qAlpha (m_rgba) < 255) + { + kdError () << "kpColor::toQColor() called with not fully opaque kpColor alpha=" + << qAlpha (m_rgba) + << endl; + return Qt::black; + } + + m_colorCache = QColor (m_rgba); + if (!m_colorCache.isValid ()) + { + kdError () << "kpColor::toQColor () internal error - could not return valid QColor" + << endl; + return Qt::black; + } + + m_colorCacheIsValid = true; + + return m_colorCache; +} + +// public +QColor kpColor::maskColor () const +{ + return isTransparent () ? Qt::color0 : Qt::color1; +} diff --git a/kolourpaint/kpcolor.h b/kolourpaint/kpcolor.h new file mode 100644 index 00000000..131d4b61 --- /dev/null +++ b/kolourpaint/kpcolor.h @@ -0,0 +1,101 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <qcolor.h> + +class QDataStream; + + +// +// kpColor is an object-oriented abstraction of QRgb, with the additional +// restriction of following the KolourPaint convention of only supporting +// totally transparent and totally opaque colors. It also provides better +// error handling, reporting (noisy kdError()'s) and recovery. +// +// 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 onscreen. Constructing a kpColor object +// from QColor is probably wrong since onscreen representations of color +// are not guaranteed to be faithful (due to QColor color allocation). +// +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; + + static int processSimilarity (double colorSimilarity); + static const int Exact; // "isSimilarTo (rhs, kpColor::Exact)" == "== rhs" + // Usage: isSimilarTo (rhs, kpColor::processSimilarity (.1)) checks for + // Color Similarity within 10% + bool isSimilarTo (const kpColor &rhs, int processedSimilarity) const; + ~kpColor (); + + static const kpColor invalid; + static const kpColor transparent; + + bool isValid () const; + + int red () const; + int green () const; + int blue () const; + int alpha () const; + bool isTransparent () const; + bool isOpaque () const; + + // Cast operators will most likely result in careless conversions so + // use explicit functions instead: + QRgb toQRgb () const; + + // (only valid if isOpaque()) + // (const QColor & return results in fewer color reallocations) + const QColor &toQColor () const; + + QColor maskColor () const; + +private: + bool m_rgbaIsValid; + QRgb m_rgba; + + mutable bool m_colorCacheIsValid; + mutable QColor m_colorCache; +}; + + +#endif // __kp_color_h__ diff --git a/kolourpaint/kpcommandhistory.cpp b/kolourpaint/kpcommandhistory.cpp new file mode 100644 index 00000000..33010918 --- /dev/null +++ b/kolourpaint/kpcommandhistory.cpp @@ -0,0 +1,939 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <limits.h> + +#include <qdatetime.h> + +#include <kactionclasses.h> +#include <kapplication.h> +#include <kconfig.h> +#include <kdebug.h> +#include <klocale.h> +#include <kpopupmenu.h> +#include <kstdaccel.h> +#include <kstdaction.h> + +#include <kpdefs.h> +#include <kpdocument.h> +#include <kpmainwindow.h> +#include <kptool.h> + + +//template <typename T> +static void clearPointerList (QValueList <kpCommand *> *listPtr) +{ + if (!listPtr) + return; + + for (QValueList <kpCommand *>::iterator it = listPtr->begin (); + it != listPtr->end (); + it++) + { + delete (*it); + } + + listPtr->clear (); +} + + +// +// kpCommand +// + +kpCommand::kpCommand (kpMainWindow *mainWindow) + : m_mainWindow (mainWindow) +{ + if (!mainWindow) + kdError () << "kpCommand::kpCommand() passed 0 mainWindow" << endl; +} + +kpCommand::~kpCommand () +{ +} + + +// protected +kpMainWindow *kpCommand::mainWindow () const +{ + return m_mainWindow; +} + + +// protected +kpDocument *kpCommand::document () const +{ + return m_mainWindow ? m_mainWindow->document () : 0; +} + +// protected +kpSelection *kpCommand::selection () const +{ + kpDocument *doc = document (); + if (!doc) + return 0; + + return doc->selection (); +} + + +// protected +kpViewManager *kpCommand::viewManager () const +{ + return m_mainWindow ? m_mainWindow->viewManager () : 0; +} + + +// +// kpNamedCommand +// + +kpNamedCommand::kpNamedCommand (const QString &name, kpMainWindow *mainWindow) + : kpCommand (mainWindow), + m_name (name) +{ +} + +kpNamedCommand::~kpNamedCommand () +{ +} + + +// public virtual [base kpCommand] +QString kpNamedCommand::name () const +{ + return m_name; +} + + +// +// kpMacroCommand +// + +struct kpMacroCommandPrivate +{ +}; + +kpMacroCommand::kpMacroCommand (const QString &name, kpMainWindow *mainWindow) + : kpNamedCommand (name, mainWindow), + d (new kpMacroCommandPrivate ()) +{ +} + +kpMacroCommand::~kpMacroCommand () +{ + clearPointerList (&m_commandList); + delete d; +} + + +// public virtual [base kpCommand] +int kpMacroCommand::size () const +{ +#if DEBUG_KP_COMMAND_HISTORY && 0 + kdDebug () << "kpMacroCommand::size()" << endl; +#endif + int s = 0; + +#if DEBUG_KP_COMMAND_HISTORY && 0 + kdDebug () << "\tcalculating:" << endl; +#endif + for (QValueList <kpCommand *>::const_iterator it = m_commandList.begin (); + it != m_commandList.end (); + it++) + { + #if DEBUG_KP_COMMAND_HISTORY && 0 + kdDebug () << "\t\tcurrentSize=" << s << " + " + << (*it)->name () << ".size=" << (*it)->size () + << endl; + #endif + if (s > INT_MAX - (*it)->size ()) + { + #if DEBUG_KP_COMMAND_HISTORY && 0 + kdDebug () << "\t\t\toverflow" << endl; + #endif + s = INT_MAX; + break; + } + else + { + s += (*it)->size (); + } + } + +#if DEBUG_KP_COMMAND_HISTORY && 0 + kdDebug () << "\treturning " << s << endl; +#endif + return s; +} + + +// public virtual [base kpCommand] +void kpMacroCommand::execute () +{ +#if DEBUG_KP_COMMAND_HISTORY + kdDebug () << "kpMacroCommand::execute()" << endl; +#endif + for (QValueList <kpCommand *>::const_iterator it = m_commandList.begin (); + it != m_commandList.end (); + it++) + { + #if DEBUG_KP_COMMAND_HISTORY + kdDebug () << "\texecuting " << (*it)->name () << endl; + #endif + (*it)->execute (); + } +} + +// public virtual [base kpCommand] +void kpMacroCommand::unexecute () +{ +#if DEBUG_KP_COMMAND_HISTORY + kdDebug () << "kpMacroCommand::unexecute()" << endl; +#endif + QValueList <kpCommand *>::const_iterator it = m_commandList.end (); + it--; + + while (it != m_commandList.end ()) + { + #if DEBUG_KP_COMMAND_HISTORY + kdDebug () << "\tunexecuting " << (*it)->name () << endl; + #endif + (*it)->unexecute (); + + it--; + } +} + + +// public +void kpMacroCommand::addCommand (kpCommand *command) +{ + m_commandList.push_back (command); +} + + +// +// kpCommandHistoryBase +// + +struct kpCommandHistoryBasePrivate +{ +}; + + +kpCommandHistoryBase::kpCommandHistoryBase (bool doReadConfig, + KActionCollection *ac) + : d (new kpCommandHistoryBasePrivate ()) +{ + m_actionUndo = new KToolBarPopupAction (undoActionText (), + QString::fromLatin1 ("undo"), + KStdAccel::shortcut (KStdAccel::Undo), + this, SLOT (undo ()), + ac, KStdAction::name (KStdAction::Undo)); + + m_actionRedo = new KToolBarPopupAction (redoActionText (), + QString::fromLatin1 ("redo"), + KStdAccel::shortcut (KStdAccel::Redo), + this, SLOT (redo ()), + ac, KStdAction::name (KStdAction::Redo)); + + + m_actionUndo->setEnabled (false); + m_actionRedo->setEnabled (false); + + + connect (m_actionUndo->popupMenu (), SIGNAL (activated (int)), + this, SLOT (undoUpToNumber (int))); + connect (m_actionRedo->popupMenu (), SIGNAL (activated (int)), + this, SLOT (redoUpToNumber (int))); + + + m_undoMinLimit = 10; + m_undoMaxLimit = 500; + m_undoMaxLimitSizeLimit = 16 * 1048576; + + + m_documentRestoredPosition = 0; + + + if (doReadConfig) + readConfig (); +} + +kpCommandHistoryBase::~kpCommandHistoryBase () +{ + clearPointerList (&m_undoCommandList); + clearPointerList (&m_redoCommandList); + + delete d; +} + + +// 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 + kdDebug () << "kpCommandHistoryBase::setUndoMinLimit(" + << limit << ")" + << endl; +#endif + + if (limit < 1 || limit > 5000/*"ought to be enough for anybody"*/) + { + kdError () << "kpCommandHistoryBase::setUndoMinLimit(" + << limit << ")" + << endl; + 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 + kdDebug () << "kpCommandHistoryBase::setUndoMaxLimit(" + << limit << ")" + << endl; +#endif + + if (limit < 1 || limit > 5000/*"ought to be enough for anybody"*/) + { + kdError () << "kpCommandHistoryBase::setUndoMaxLimit(" + << limit << ")" + << endl; + return; + } + + if (limit == m_undoMaxLimit) + return; + + m_undoMaxLimit = limit; + trimCommandListsUpdateActions (); +} + + +// public +int kpCommandHistoryBase::undoMaxLimitSizeLimit () const +{ + return m_undoMaxLimitSizeLimit; +} + +// public +void kpCommandHistoryBase::setUndoMaxLimitSizeLimit (int sizeLimit) +{ +#if DEBUG_KP_COMMAND_HISTORY + kdDebug () << "kpCommandHistoryBase::setUndoMaxLimitSizeLimit(" + << sizeLimit << ")" + << endl; +#endif + + if (sizeLimit < 0 || + sizeLimit > (500 * 1048576)/*"ought to be enough for anybody"*/) + { + kdError () << "kpCommandHistoryBase::setUndoMaxLimitSizeLimit(" + << sizeLimit << ")" + << endl; + return; + } + + if (sizeLimit == m_undoMaxLimitSizeLimit) + return; + + m_undoMaxLimitSizeLimit = sizeLimit; + trimCommandListsUpdateActions (); +} + + +// public +void kpCommandHistoryBase::readConfig () +{ +#if DEBUG_KP_COMMAND_HISTORY + kdDebug () << "kpCommandHistoryBase::readConfig()" << endl; +#endif + KConfigGroupSaver cfgGroupSaver (kapp->config (), kpSettingsGroupUndoRedo); + KConfigBase *cfg = cfgGroupSaver.config (); + + setUndoMinLimit (cfg->readNumEntry (kpSettingUndoMinLimit, undoMinLimit ())); + setUndoMaxLimit (cfg->readNumEntry (kpSettingUndoMaxLimit, undoMaxLimit ())); + setUndoMaxLimitSizeLimit (cfg->readNumEntry (kpSettingUndoMaxLimitSizeLimit, + undoMaxLimitSizeLimit ())); + + trimCommandListsUpdateActions (); +} + +// public +void kpCommandHistoryBase::writeConfig () +{ +#if DEBUG_KP_COMMAND_HISTORY + kdDebug () << "kpCommandHistoryBase::writeConfig()" << endl; +#endif + KConfigGroupSaver cfgGroupSaver (kapp->config (), kpSettingsGroupUndoRedo); + KConfigBase *cfg = cfgGroupSaver.config (); + + 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 + kdDebug () << "kpCommandHistoryBase::addCommand(" + << command + << ",execute=" << execute << ")" + << endl; +#endif + + if (execute) + command->execute (); + + m_undoCommandList.push_front (command); + clearPointerList (&m_redoCommandList); + +#if DEBUG_KP_COMMAND_HISTORY + kdDebug () << "\tdocumentRestoredPosition=" << m_documentRestoredPosition + << endl; +#endif + if (m_documentRestoredPosition != INT_MAX) + { + if (m_documentRestoredPosition > 0) + m_documentRestoredPosition = INT_MAX; + else + m_documentRestoredPosition--; + #if DEBUG_KP_COMMAND_HISTORY + kdDebug () << "\t\tdocumentRestoredPosition=" << m_documentRestoredPosition + << endl; + #endif + } + + trimCommandListsUpdateActions (); +} + +// public +void kpCommandHistoryBase::clear () +{ +#if DEBUG_KP_COMMAND_HISTORY + kdDebug () << "kpCommandHistoryBase::clear()" << endl; +#endif + + clearPointerList (&m_undoCommandList); + clearPointerList (&m_redoCommandList); + + m_documentRestoredPosition = 0; + + updateActions (); +} + + +// protected slot +void kpCommandHistoryBase::undoInternal () +{ +#if DEBUG_KP_COMMAND_HISTORY + kdDebug () << "kpCommandHistoryBase::undoInternal()" << endl; +#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 + kdDebug () << "\tdocumentRestoredPosition=" << m_documentRestoredPosition + << endl; +#endif + if (m_documentRestoredPosition != INT_MAX) + { + m_documentRestoredPosition++; + if (m_documentRestoredPosition == 0) + emit documentRestored (); + #if DEBUG_KP_COMMAND_HISTORY + kdDebug () << "\t\tdocumentRestoredPosition=" << m_documentRestoredPosition + << endl; + #endif + } +} + +// protected slot +void kpCommandHistoryBase::redoInternal () +{ +#if DEBUG_KP_COMMAND_HISTORY + kdDebug () << "kpCommandHistoryBase::redoInternal()" << endl; +#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 + kdDebug () << "\tdocumentRestoredPosition=" << m_documentRestoredPosition + << endl; +#endif + if (m_documentRestoredPosition != INT_MAX) + { + m_documentRestoredPosition--; + if (m_documentRestoredPosition == 0) + emit documentRestored (); + #if DEBUG_KP_COMMAND_HISTORY + kdDebug () << "\t\tdocumentRestoredPosition=" << m_documentRestoredPosition + << endl; + #endif + } +} + + +// public slot virtual +void kpCommandHistoryBase::undo () +{ +#if DEBUG_KP_COMMAND_HISTORY + kdDebug () << "kpCommandHistoryBase::undo()" << endl; +#endif + + undoInternal (); + trimCommandListsUpdateActions (); +} + +// public slot virtual +void kpCommandHistoryBase::redo () +{ +#if DEBUG_KP_COMMAND_HISTORY + kdDebug () << "kpCommandHistoryBase::redo()" << endl; +#endif + + redoInternal (); + trimCommandListsUpdateActions (); +} + + +// public slot virtual +void kpCommandHistoryBase::undoUpToNumber (int which) +{ +#if DEBUG_KP_COMMAND_HISTORY + kdDebug () << "kpCommandHistoryBase::undoUpToNumber(" << which << ")" << endl; +#endif + + for (int i = 0; + i <= which && !m_undoCommandList.isEmpty (); + i++) + { + undoInternal (); + } + + trimCommandListsUpdateActions (); +} + +// public slot virtual +void kpCommandHistoryBase::redoUpToNumber (int which) +{ +#if DEBUG_KP_COMMAND_HISTORY + kdDebug () << "kpCommandHistoryBase::redoUpToNumber(" << which << ")" << endl; +#endif + + for (int i = 0; + i <= which && !m_redoCommandList.isEmpty (); + i++) + { + redoInternal (); + } + + trimCommandListsUpdateActions (); +} + + +// protected +QString kpCommandHistoryBase::undoActionText () const +{ + kpCommand *undoCommand = nextUndoCommand (); + + if (undoCommand) + return i18n ("&Undo: %1").arg (undoCommand->name ()); + else + return i18n ("&Undo"); +} + +// protected +QString kpCommandHistoryBase::redoActionText () const +{ + kpCommand *redoCommand = nextRedoCommand (); + + if (redoCommand) + return i18n ("&Redo: %1").arg (redoCommand->name ()); + else + return i18n ("&Redo"); +} + + +// protected +void kpCommandHistoryBase::trimCommandListsUpdateActions () +{ +#if DEBUG_KP_COMMAND_HISTORY + kdDebug () << "kpCommandHistoryBase::trimCommandListsUpdateActions()" << endl; +#endif + + trimCommandLists (); + updateActions (); +} + +// protected +void kpCommandHistoryBase::trimCommandList (QValueList <kpCommand *> *commandList) +{ +#if DEBUG_KP_COMMAND_HISTORY + kdDebug () << "kpCommandHistoryBase::trimCommandList()" << endl; + QTime timer; timer.start (); +#endif + + if (!commandList) + { + kdError () << "kpCommandHistoryBase::trimCommandList() passed 0 commandList" + << endl; + return; + } + + +#if DEBUG_KP_COMMAND_HISTORY + kdDebug () << "\tsize=" << commandList->size () + << " undoMinLimit=" << m_undoMinLimit + << " undoMaxLimit=" << m_undoMaxLimit + << " undoMaxLimitSizeLimit=" << m_undoMaxLimitSizeLimit + << endl; +#endif + if ((int) commandList->size () <= m_undoMinLimit) + { + #if DEBUG_KP_COMMAND_HISTORY + kdDebug () << "\t\tsize under undoMinLimit - done" << endl; + #endif + return; + } + + +#if DEBUG_KP_COMMAND_HISTORY && 0 + kdDebug () << "\tsize over undoMinLimit - iterating thru cmds:" << endl; +#endif + + QValueList <kpCommand *>::iterator it = commandList->begin (); + int upto = 0; + + int sizeSoFar = 0; + + while (it != commandList->end ()) + { + bool advanceIt = true; + + if (sizeSoFar <= m_undoMaxLimitSizeLimit) + { + if (sizeSoFar > INT_MAX - (*it)->size ()) + sizeSoFar = INT_MAX; + else + sizeSoFar += (*it)->size (); + } + + #if DEBUG_KP_COMMAND_HISTORY && 0 + kdDebug () << "\t\t" << upto << ":" + << " name='" << (*it)->name () + << "' size=" << (*it)->size () + << " sizeSoFar=" << sizeSoFar + << endl; + #endif + + if (upto >= m_undoMinLimit) + { + if (upto >= m_undoMaxLimit || + sizeSoFar > m_undoMaxLimitSizeLimit) + { + #if DEBUG_KP_COMMAND_HISTORY && 0 + kdDebug () << "\t\t\tkill" << endl; + #endif + delete (*it); + it = m_undoCommandList.erase (it); + advanceIt = false; + } + } + + if (advanceIt) + it++; + upto++; + } + +#if DEBUG_KP_COMMAND_HISTORY + kdDebug () << "\ttook " << timer.elapsed () << "ms" << endl; +#endif +} + +// protected +void kpCommandHistoryBase::trimCommandLists () +{ +#if DEBUG_KP_COMMAND_HISTORY + kdDebug () << "kpCommandHistoryBase::trimCommandLists()" << endl; +#endif + + trimCommandList (&m_undoCommandList); + trimCommandList (&m_redoCommandList); + +#if DEBUG_KP_COMMAND_HISTORY + kdDebug () << "\tdocumentRestoredPosition=" << m_documentRestoredPosition + << endl; +#endif + if (m_documentRestoredPosition != INT_MAX) + { + #if DEBUG_KP_COMMAND_HISTORY + kdDebug () << "\t\tundoCmdList.size=" << m_undoCommandList.size () + << " redoCmdList.size=" << m_redoCommandList.size () + << endl; + #endif + if (m_documentRestoredPosition > (int) m_redoCommandList.size () || + -m_documentRestoredPosition > (int) m_undoCommandList.size ()) + { + #if DEBUG_KP_COMMAND_HISTORY + kdDebug () << "\t\t\tinvalidate documentRestoredPosition" << endl; + #endif + m_documentRestoredPosition = INT_MAX; + } + } +} + + +static void populatePopupMenu (KPopupMenu *popupMenu, + const QString &undoOrRedo, + const QValueList <kpCommand *> &commandList) +{ + if (!popupMenu) + return; + + popupMenu->clear (); + + QValueList <kpCommand *>::const_iterator it = commandList.begin (); + int i = 0; + while (i < 10 && it != commandList.end ()) + { + popupMenu->insertItem (i18n ("%1: %2").arg (undoOrRedo).arg ((*it)->name ()), i/*id*/); + i++, it++; + } + + if (it != commandList.end ()) + { + // TODO: maybe have a scrollview show all the items instead + KPopupTitle *title = new KPopupTitle (popupMenu); + title->setTitle (i18n ("%n more item", "%n more items", + commandList.size () - i)); + + popupMenu->insertItem (title); + } +} + + +// protected +void kpCommandHistoryBase::updateActions () +{ +#if DEBUG_KP_COMMAND_HISTORY + kdDebug () << "kpCommandHistoryBase::updateActions()" << endl; +#endif + + m_actionUndo->setEnabled ((bool) nextUndoCommand ()); + m_actionUndo->setText (undoActionText ()); +#if DEBUG_KP_COMMAND_HISTORY + QTime timer; timer.start (); +#endif + populatePopupMenu (m_actionUndo->popupMenu (), + i18n ("Undo"), + m_undoCommandList); +#if DEBUG_KP_COMMAND_HISTORY + kdDebug () << "\tpopuplatePopupMenu undo=" << timer.elapsed () + << "ms" << endl;; +#endif + + m_actionRedo->setEnabled ((bool) nextRedoCommand ()); + m_actionRedo->setText (redoActionText ()); +#if DEBUG_KP_COMMAND_HISTORY + timer.restart (); +#endif + populatePopupMenu (m_actionRedo->popupMenu (), + i18n ("Redo"), + m_redoCommandList); +#if DEBUG_KP_COMMAND_HISTORY + kdDebug () << "\tpopuplatePopupMenu redo=" << timer.elapsed () + << "ms" << endl; +#endif +} + + +// public +kpCommand *kpCommandHistoryBase::nextUndoCommand () const +{ + if (m_undoCommandList.isEmpty ()) + return 0; + + return m_undoCommandList.first (); +} + +// public +kpCommand *kpCommandHistoryBase::nextRedoCommand () const +{ + if (m_redoCommandList.isEmpty ()) + return 0; + + return m_redoCommandList.first (); +} + + +// public +void kpCommandHistoryBase::setNextUndoCommand (kpCommand *command) +{ +#if DEBUG_KP_COMMAND_HISTORY + kdDebug () << "kpCommandHistoryBase::setNextUndoCommand(" + << command + << ")" + << endl; +#endif + + if (m_undoCommandList.isEmpty ()) + return; + + + delete m_undoCommandList [0]; + m_undoCommandList [0] = command; + + + trimCommandListsUpdateActions (); +} + + +// public slot virtual +void kpCommandHistoryBase::documentSaved () +{ +#if DEBUG_KP_COMMAND_HISTORY + kdDebug () << "kpCommandHistoryBase::documentSaved()" << endl; +#endif + + m_documentRestoredPosition = 0; +} + + +// +// kpCommandHistory +// + +kpCommandHistory::kpCommandHistory (bool doReadConfig, kpMainWindow *mainWindow) + : kpCommandHistoryBase (doReadConfig, mainWindow->actionCollection ()), + m_mainWindow (mainWindow) +{ +} + +kpCommandHistory::~kpCommandHistory () +{ +} + + +// public slot virtual [base KCommandHistory] +void kpCommandHistory::undo () +{ +#if DEBUG_KP_COMMAND_HISTORY + kdDebug () << "kpCommandHistory::undo() CALLED!" << endl; +#endif + if (m_mainWindow && m_mainWindow->toolHasBegunShape ()) + { + #if DEBUG_KP_COMMAND_HISTORY + kdDebug () << "\thas begun shape - cancel draw" << endl; + #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 (); +} + +#include <kpcommandhistory.moc> diff --git a/kolourpaint/kpcommandhistory.h b/kolourpaint/kpcommandhistory.h new file mode 100644 index 00000000..a1541512 --- /dev/null +++ b/kolourpaint/kpcommandhistory.h @@ -0,0 +1,255 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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_COMMAND_HISTORY_H +#define KP_COMMAND_HISTORY_H + +#include <qobject.h> +#include <qstring.h> +#include <qvaluelist.h> + + +class KActionCollection; +class KToolBarPopupAction; + +class kpDocument; +class kpMainWindow; +class kpSelection; +class kpViewManager; + + +class kpCommand +{ +public: + kpCommand (kpMainWindow *mainWindow); + 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. + virtual int size () const = 0; + + virtual void execute () = 0; + virtual void unexecute () = 0; + +protected: + kpMainWindow *mainWindow () const; + + kpDocument *document () const; + kpSelection *selection () const; + + kpViewManager *viewManager () const; + +protected: + kpMainWindow *m_mainWindow; +}; + + +class kpNamedCommand : public kpCommand +{ +public: + kpNamedCommand (const QString &name, kpMainWindow *mainWindow); + virtual ~kpNamedCommand (); + + virtual QString name () const; + +protected: + QString m_name; +}; + + +class kpMacroCommand : public kpNamedCommand +{ +public: + kpMacroCommand (const QString &name, kpMainWindow *mainWindow); + virtual ~kpMacroCommand (); + + + // + // kpCommand Interface + // + + virtual int size () const; + + virtual void execute (); + virtual void unexecute (); + + + // + // Interface + // + + void addCommand (kpCommand *command); + +protected: + QValueList <kpCommand *> m_commandList; + +private: + class kpMacroCommandPrivate *d; +}; + + +// Clone of KCommandHistory with features required by KolourPaint: +// - 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); + virtual ~kpCommandHistoryBase (); + +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); + + int undoMaxLimitSizeLimit () const; + void setUndoMaxLimitSizeLimit (int 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 (int which); + virtual void redoUpToNumber (int which); + +protected: + QString undoActionText () const; + QString redoActionText () const; + + void trimCommandListsUpdateActions (); + void trimCommandList (QValueList <kpCommand *> *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) + QValueList <kpCommand *> m_undoCommandList; + QValueList <kpCommand *> m_redoCommandList; + + int m_undoMinLimit, m_undoMaxLimit, 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; + +private: + class kpCommandHistoryBasePrivate *d; +}; + + +// 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. +// +// 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); + virtual ~kpCommandHistory (); + +public slots: + virtual void undo (); + virtual void redo (); + +protected: + kpMainWindow *m_mainWindow; +}; + + +#endif // KP_COMMAND_HISTORY_H diff --git a/kolourpaint/kpdefs.h b/kolourpaint/kpdefs.h new file mode 100644 index 00000000..15faaee0 --- /dev/null +++ b/kolourpaint/kpdefs.h @@ -0,0 +1,151 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <limits.h> + +#include <qglobal.h> +#include <qpoint.h> +#include <qsize.h> +#include <qstring.h> + +#include <kdeversion.h> + + +#define KP_IS_QT_3_3 (QT_VERSION >= 0x030300 && 1) +#define KP_IS_KDE_3_3 ((KDE_VERSION_MAJOR >= 3 && KDE_VERSION_MINOR >= 3) && 1) + + +// approx. 2896x2896x32bpp or 3344x3344x24bpp (TODO: 24==32?) or 4096*4096x16bpp +#define KP_BIG_IMAGE_SIZE (32 * 1048576) + + +#define KP_PI 3.141592653589793238462 + + +#define KP_DEGREES_TO_RADIANS(deg) ((deg) * KP_PI / 180.0) +#define KP_RADIANS_TO_DEGREES(rad) ((rad) * 180.0 / KP_PI) + + +#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) + + +// +// Settings +// + +#define kpSettingsGroupGeneral QString::fromLatin1 ("General Settings") +#define kpSettingFirstTime QString::fromLatin1 ("First Time") +#define kpSettingShowGrid QString::fromLatin1 ("Show Grid") +#define kpSettingShowPath QString::fromLatin1 ("Show Path") +#define kpSettingColorSimilarity QString::fromLatin1 ("Color Similarity") +#define kpSettingDitherOnOpen QString::fromLatin1 ("Dither on Open if Screen is 15/16bpp and Image Num Colors More Than") +#define kpSettingPrintImageCenteredOnPage QString::fromLatin1 ("Print Image Centered On Page") + +#define kpSettingsGroupFileSaveAs QString::fromLatin1 ("File/Save As") +#define kpSettingsGroupFileExport QString::fromLatin1 ("File/Export") +#define kpSettingsGroupEditCopyTo QString::fromLatin1 ("Edit/Copy To") + +#define kpSettingForcedMimeType QString::fromLatin1 ("Forced MimeType") +#define kpSettingForcedColorDepth QString::fromLatin1 ("Forced Color Depth") +#define kpSettingForcedDither QString::fromLatin1 ("Forced Dither") +#define kpSettingForcedQuality QString::fromLatin1 ("Forced Quality") + +#define kpSettingLastDocSize QString::fromLatin1 ("Last Document Size") + +#define kpSettingMoreEffectsLastEffect QString::fromLatin1 ("More Effects - Last Effect") + +#define kpSettingResizeScaleLastKeepAspect QString::fromLatin1 ("Resize Scale - Last Keep Aspect") + + +#define kpSettingsGroupMimeTypeProperties QString::fromLatin1 ("MimeType Properties Version 1.2-2") +#define kpSettingMimeTypeMaximumColorDepth QString::fromLatin1 ("Maximum Color Depth") +#define kpSettingMimeTypeHasConfigurableColorDepth QString::fromLatin1 ("Configurable Color Depth") +#define kpSettingMimeTypeHasConfigurableQuality QString::fromLatin1 ("Configurable Quality Setting") + + +#define kpSettingsGroupUndoRedo QString::fromLatin1 ("Undo/Redo Settings") +#define kpSettingUndoMinLimit QString::fromLatin1 ("Min Limit") +#define kpSettingUndoMaxLimit QString::fromLatin1 ("Max Limit") +#define kpSettingUndoMaxLimitSizeLimit QString::fromLatin1 ("Max Limit Size Limit") + + +#define kpSettingsGroupThumbnail QString::fromLatin1 ("Thumbnail Settings") +#define kpSettingThumbnailShown QString::fromLatin1 ("Shown") +#define kpSettingThumbnailGeometry QString::fromLatin1 ("Geometry") +#define kpSettingThumbnailZoomed QString::fromLatin1 ("Zoomed") +#define kpSettingThumbnailShowRectangle QString::fromLatin1 ("ShowRectangle") + + +#define kpSettingsGroupPreviewSave QString::fromLatin1 ("Save Preview Settings") +#define kpSettingPreviewSaveGeometry QString::fromLatin1 ("Geometry") +#define kpSettingPreviewSaveUpdateDelay QString::fromLatin1 ("Update Delay") + + +#define kpSettingsGroupTools QString::fromLatin1 ("Tool Settings") +#define kpSettingLastTool QString::fromLatin1 ("Last Used Tool") +#define kpSettingToolBoxIconSize QString::fromLatin1 ("Tool Box Icon Size") + + +#define kpSettingsGroupText QString::fromLatin1 ("Text Settings") +#define kpSettingFontFamily QString::fromLatin1 ("Font Family") +#define kpSettingFontSize QString::fromLatin1 ("Font Size") +#define kpSettingBold QString::fromLatin1 ("Bold") +#define kpSettingItalic QString::fromLatin1 ("Italic") +#define kpSettingUnderline QString::fromLatin1 ("Underline") +#define kpSettingStrikeThru QString::fromLatin1 ("Strike Thru") + + +#define kpSettingsGroupFlattenEffect QString::fromLatin1 ("Flatten Effect Settings") +#define kpSettingFlattenEffectColor1 QString::fromLatin1 ("Color1") +#define kpSettingFlattenEffectColor2 QString::fromLatin1 ("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/kolourpaint/kpdocument.cpp b/kolourpaint/kpdocument.cpp new file mode 100644 index 00000000..801b922e --- /dev/null +++ b/kolourpaint/kpdocument.cpp @@ -0,0 +1,1539 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <math.h> + +#include <qcolor.h> +#include <qbitmap.h> +#include <qbrush.h> +#include <qfile.h> +#include <qimage.h> +#include <qpixmap.h> +#include <qpainter.h> +#include <qrect.h> +#include <qsize.h> +#include <qvaluelist.h> +#include <qwmatrix.h> + +#include <kdebug.h> +#include <kglobal.h> +#include <kimageio.h> +#include <kio/netaccess.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <kmimetype.h> +#include <ksavefile.h> +#include <ktempfile.h> + +#include <kpcolor.h> +#include <kpcolortoolbar.h> +#include <kpdefs.h> +#include <kpdocumentsaveoptions.h> +#include <kpdocumentmetainfo.h> +#include <kpeffectreducecolors.h> +#include <kpmainwindow.h> +#include <kppixmapfx.h> +#include <kpselection.h> +#include <kptool.h> +#include <kptooltoolbar.h> +#include <kpviewmanager.h> + + +struct kpDocumentPrivate +{ + kpDocumentPrivate () + { + } +}; + + +kpDocument::kpDocument (int w, int h, kpMainWindow *mainWindow) + : m_constructorWidth (w), m_constructorHeight (h), + m_mainWindow (mainWindow), + m_isFromURL (false), + m_savedAtLeastOnceBefore (false), + m_saveOptions (new kpDocumentSaveOptions ()), + m_metaInfo (new kpDocumentMetaInfo ()), + m_modified (false), + m_selection (0), + m_oldWidth (-1), m_oldHeight (-1), + d (new kpDocumentPrivate ()) +{ +#if DEBUG_KP_DOCUMENT && 0 + kdDebug () << "kpDocument::kpDocument (" << w << "," << h << ")" << endl; +#endif + + m_pixmap = new QPixmap (w, h); + m_pixmap->fill (Qt::white); +} + +kpDocument::~kpDocument () +{ + delete d; + + delete m_pixmap; + + delete m_saveOptions; + delete m_metaInfo; + + delete m_selection; +} + + +kpMainWindow *kpDocument::mainWindow () const +{ + return m_mainWindow; +} + +void kpDocument::setMainWindow (kpMainWindow *mainWindow) +{ + m_mainWindow = mainWindow; +} + + +/* + * File I/O + */ + +// public static +QPixmap kpDocument::convertToPixmapAsLosslessAsPossible ( + const QImage &image, + const kpPixmapFX::WarnAboutLossInfo &wali, + + kpDocumentSaveOptions *saveOptions, + kpDocumentMetaInfo *metaInfo) +{ + if (image.isNull ()) + return QPixmap (); + + +#if DEBUG_KP_DOCUMENT + kdDebug () << "\timage: depth=" << image.depth () + << " (X display=" << QColor::numBitPlanes () << ")" + << " hasAlphaBuffer=" << image.hasAlphaBuffer () + << endl; +#endif + + if (saveOptions) + { + saveOptions->setColorDepth (image.depth ()); + saveOptions->setDither (false); // avoid double dithering when saving + } + + if (metaInfo) + { + metaInfo->setDotsPerMeterX (image.dotsPerMeterX ()); + metaInfo->setDotsPerMeterY (image.dotsPerMeterY ()); + metaInfo->setOffset (image.offset ()); + + QValueList <QImageTextKeyLang> keyList = image.textList (); + for (QValueList <QImageTextKeyLang>::const_iterator it = keyList.begin (); + it != keyList.end (); + it++) + { + metaInfo->setText (*it, image.text (*it)); + } + + #if DEBUG_KP_DOCUMENT + metaInfo->printDebug ("\tmetaInfo"); + #endif + } + +#if DEBUG_KP_DOCUMENT && 1 +{ + if (image.width () <= 16 && image.height () <= 16) + { + kdDebug () << "Image dump:" << endl; + + for (int y = 0; y < image.height (); y++) + { + for (int x = 0; x < image.width (); x++) + { + const QRgb rgb = image.pixel (x, y); + fprintf (stderr, " %08X", rgb); + } + fprintf (stderr, "\n"); + } + } +} +#endif + + + QPixmap newPixmap = kpPixmapFX::convertToPixmapAsLosslessAsPossible (image, wali); + + +#if DEBUG_KP_DOCUMENT && 1 +{ + const QImage image2 = kpPixmapFX::convertToImage (newPixmap); + kdDebug () << "(Converted to pixmap) Image dump:" << endl; + + bool differsFromOrgImage = false; + unsigned long hash = 0; + int numDiff = 0; + for (int y = 0; y < image2.height (); y++) + { + for (int x = 0; x < image2.width (); x++) + { + const QRgb rgb = image2.pixel (x, y); + hash += ((x % 2) + 1) * rgb; + if (rgb != image.pixel (x, y)) + { + differsFromOrgImage = true; + numDiff++; + } + if (image2.width () <= 16 && image2.height () <= 16) + fprintf (stderr, " %08X", rgb); + } + if (image2.width () <= 16 && image2.height () <= 16) + fprintf (stderr, "\n"); + } + + kdDebug () << "\tdiffersFromOrgImage=" + << differsFromOrgImage + << " numDiff=" + << numDiff + << " hash=" << hash << endl; +} +#endif + + return newPixmap; +} + +// public static +QPixmap kpDocument::getPixmapFromFile (const KURL &url, bool suppressDoesntExistDialog, + QWidget *parent, + kpDocumentSaveOptions *saveOptions, + kpDocumentMetaInfo *metaInfo) +{ +#if DEBUG_KP_DOCUMENT + kdDebug () << "kpDocument::getPixmapFromFile(" << url << "," << parent << ")" << endl; +#endif + + if (saveOptions) + *saveOptions = kpDocumentSaveOptions (); + + if (metaInfo) + *metaInfo = kpDocumentMetaInfo (); + + + QString tempFile; + if (url.isEmpty () || !KIO::NetAccess::download (url, tempFile, parent)) + { + if (!suppressDoesntExistDialog) + { + KMessageBox::sorry (parent, + i18n ("Could not open \"%1\".") + .arg (kpDocument::prettyFilenameForURL (url))); + } + + return QPixmap (); + } + + + QImage image; + + // sync: remember to "KIO::NetAccess::removeTempFile (tempFile)" in all exit paths + { + QString detectedMimeType = KImageIO::mimeType (tempFile); + if (saveOptions) + saveOptions->setMimeType (detectedMimeType); + + #if DEBUG_KP_DOCUMENT + kdDebug () << "\ttempFile=" << tempFile << endl; + kdDebug () << "\tmimetype=" << detectedMimeType << endl; + kdDebug () << "\tsrc=" << url.path () << endl; + kdDebug () << "\tmimetype of src=" << KImageIO::mimeType (url.path ()) << endl; + #endif + + if (detectedMimeType.isEmpty ()) + { + KMessageBox::sorry (parent, + i18n ("Could not open \"%1\" - unknown mimetype.") + .arg (kpDocument::prettyFilenameForURL (url))); + KIO::NetAccess::removeTempFile (tempFile); + return QPixmap (); + } + + + image = QImage (tempFile); + KIO::NetAccess::removeTempFile (tempFile); + } + + + if (image.isNull ()) + { + KMessageBox::sorry (parent, + i18n ("Could not open \"%1\" - unsupported image format.\n" + "The file may be corrupt.") + .arg (kpDocument::prettyFilenameForURL (url))); + return QPixmap (); + } + + const QPixmap newPixmap = kpDocument::convertToPixmapAsLosslessAsPossible (image, + kpPixmapFX::WarnAboutLossInfo ( + i18n ("The image \"%1\"" + " may have more colors than the current screen mode." + " In order to display it, some colors may be changed." + " Try increasing your screen depth to at least %2bpp." + + "\nIt also" + + " contains translucency which is not fully" + " supported. The translucency data will be" + " approximated with a 1-bit transparency mask.") + .arg (prettyFilenameForURL (url)), + i18n ("The image \"%1\"" + " may have more colors than the current screen mode." + " In order to display it, some colors may be changed." + " Try increasing your screen depth to at least %2bpp.") + .arg (prettyFilenameForURL (url)), + i18n ("The image \"%1\"" + " contains translucency which is not fully" + " supported. The translucency data will be" + " approximated with a 1-bit transparency mask.") + .arg (prettyFilenameForURL (url)), + "docOpen", + parent), + saveOptions, + metaInfo); + + if (newPixmap.isNull ()) + { + KMessageBox::sorry (parent, + i18n ("Could not open \"%1\" - out of graphics memory.") + .arg (kpDocument::prettyFilenameForURL (url))); + return QPixmap (); + } + +#if DEBUG_KP_DOCUMENT + kdDebug () << "\tpixmap: depth=" << newPixmap.depth () + << " hasAlphaChannelOrMask=" << newPixmap.hasAlpha () + << " hasAlphaChannel=" << newPixmap.hasAlphaChannel () + << endl; +#endif + + + return newPixmap; +} + +void kpDocument::openNew (const KURL &url) +{ +#if DEBUG_KP_DOCUMENT + kdDebug () << "KpDocument::openNew (" << url << ")" << endl; +#endif + + m_pixmap->fill (Qt::white); + + setURL (url, false/*not from url*/); + *m_saveOptions = kpDocumentSaveOptions (); + *m_metaInfo = kpDocumentMetaInfo (); + m_modified = false; + + emit documentOpened (); +} + +bool kpDocument::open (const KURL &url, bool newDocSameNameIfNotExist) +{ +#if DEBUG_KP_DOCUMENT + kdDebug () << "kpDocument::open (" << url << ")" << endl; +#endif + + kpDocumentSaveOptions newSaveOptions; + kpDocumentMetaInfo newMetaInfo; + QPixmap newPixmap = kpDocument::getPixmapFromFile (url, + newDocSameNameIfNotExist/*suppress "doesn't exist" dialog*/, + m_mainWindow, + &newSaveOptions, + &newMetaInfo); + + if (!newPixmap.isNull ()) + { + delete m_pixmap; + m_pixmap = new QPixmap (newPixmap); + + setURL (url, true/*is from url*/); + *m_saveOptions = newSaveOptions; + *m_metaInfo = newMetaInfo; + m_modified = false; + + emit documentOpened (); + return true; + } + + if (newDocSameNameIfNotExist) + { + if (!url.isEmpty () && + // not just a permission error? + !KIO::NetAccess::exists (url, true/*open*/, m_mainWindow)) + { + openNew (url); + } + else + { + openNew (KURL ()); + } + + return true; + } + else + { + return false; + } +} + +bool kpDocument::save (bool overwritePrompt, bool lossyPrompt) +{ +#if DEBUG_KP_DOCUMENT + kdDebug () << "kpDocument::save(" + << "overwritePrompt=" << overwritePrompt + << ",lossyPrompt=" << lossyPrompt + << ") url=" << m_url + << " savedAtLeastOnceBefore=" << savedAtLeastOnceBefore () + << endl; +#endif + + // TODO: check feels weak + if (m_url.isEmpty () || m_saveOptions->mimeType ().isEmpty ()) + { + KMessageBox::detailedError (m_mainWindow, + i18n ("Could not save image - insufficient information."), + i18n ("URL: %1\n" + "Mimetype: %2") + .arg (prettyURL ()) + .arg (m_saveOptions->mimeType ().isEmpty () ? + i18n ("<empty>") : + m_saveOptions->mimeType ()), + i18n ("Internal Error")); + return false; + } + + return saveAs (m_url, *m_saveOptions, + overwritePrompt, + lossyPrompt); +} + + +// public static +bool kpDocument::lossyPromptContinue (const QPixmap &pixmap, + const kpDocumentSaveOptions &saveOptions, + QWidget *parent) +{ +#if DEBUG_KP_DOCUMENT + kdDebug () << "kpDocument::lossyPromptContinue()" << endl; +#endif + +#define QUIT_IF_CANCEL(messageBoxCommand) \ +{ \ + if (messageBoxCommand != KMessageBox::Continue) \ + { \ + return false; \ + } \ +} + + const int lossyType = saveOptions.isLossyForSaving (pixmap); + if (lossyType & (kpDocumentSaveOptions::MimeTypeMaximumColorDepthLow | + kpDocumentSaveOptions::Quality)) + { + QUIT_IF_CANCEL ( + KMessageBox::warningContinueCancel (parent, + i18n ("<qt><p>The <b>%1</b> format may not be able" + " to preserve all of the image's color information.</p>" + + "<p>Are you sure you want to save in this format?</p></qt>") + .arg (KMimeType::mimeType (saveOptions.mimeType ())->comment ()), + // TODO: caption misleading for lossless formats that have + // low maximum colour depth + i18n ("Lossy File Format"), + KStdGuiItem::save (), + QString::fromLatin1 ("SaveInLossyMimeTypeDontAskAgain"))); + } + else if (lossyType & kpDocumentSaveOptions::ColorDepthLow) + { + QUIT_IF_CANCEL ( + KMessageBox::warningContinueCancel (parent, + i18n ("<qt><p>Saving the image at the low color depth of %1-bit" + " may result in the loss of color information." + + " Any transparency will also be removed.</p>" + + "<p>Are you sure you want to save at this color depth?</p></qt>") + .arg (saveOptions.colorDepth ()), + i18n ("Low Color Depth"), + KStdGuiItem::save (), + QString::fromLatin1 ("SaveAtLowColorDepthDontAskAgain"))); + } +#undef QUIT_IF_CANCEL + + return true; +} + +// public static +bool kpDocument::savePixmapToDevice (const QPixmap &pixmap, + QIODevice *device, + const kpDocumentSaveOptions &saveOptions, + const kpDocumentMetaInfo &metaInfo, + bool lossyPrompt, + QWidget *parent, + bool *userCancelled) +{ + if (userCancelled) + *userCancelled = false; + + QString type = KImageIO::typeForMime (saveOptions.mimeType ()); +#if DEBUG_KP_DOCUMENT + kdDebug () << "\tmimeType=" << saveOptions.mimeType () + << " type=" << type << endl; +#endif + + if (lossyPrompt && !lossyPromptContinue (pixmap, saveOptions, parent)) + { + if (userCancelled) + *userCancelled = true; + + #if DEBUG_KP_DOCUMENT + kdDebug () << "\treturning false because of lossyPrompt" << endl; + #endif + return false; + } + + + QPixmap pixmapToSave = + kpPixmapFX::pixmapWithDefinedTransparentPixels (pixmap, + Qt::white); // CONFIG + QImage imageToSave = kpPixmapFX::convertToImage (pixmapToSave); + + + // 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 (useSaveOptionsColorDepth && + imageToSave.depth () != saveOptions.colorDepth ()) + { + imageToSave = ::convertImageDepth (imageToSave, + saveOptions.colorDepth (), + saveOptions.dither ()); + } + + + // + // Write Meta Info + // + + imageToSave.setDotsPerMeterX (metaInfo.dotsPerMeterX ()); + imageToSave.setDotsPerMeterY (metaInfo.dotsPerMeterY ()); + imageToSave.setOffset (metaInfo.offset ()); + + QValueList <QImageTextKeyLang> keyList = metaInfo.textList (); + for (QValueList <QImageTextKeyLang>::const_iterator it = keyList.begin (); + it != keyList.end (); + it++) + { + imageToSave.setText ((*it).key, (*it).lang, metaInfo.text (*it)); + } + + + // + // Save at required quality + // + + int quality = -1; // default + + if (useSaveOptionsQuality) + quality = saveOptions.quality (); + + if (!imageToSave.save (device, type.latin1 (), quality)) + { + #if DEBUG_KP_DOCUMENT + kdDebug () << "\tQImage::save() returned false" << endl; + #endif + return false; + } + + +#if DEBUG_KP_DOCUMENT + kdDebug () << "\tsave OK" << endl; +#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 KURL &url, QWidget *parent) +{ + // TODO: use file.errorString() + KMessageBox::error (parent, + i18n ("Could not save as \"%1\".") + .arg (kpDocument::prettyFilenameForURL (url))); +} + +// public static +bool kpDocument::savePixmapToFile (const QPixmap &pixmap, + const KURL &url, + const kpDocumentSaveOptions &saveOptions, + const kpDocumentMetaInfo &metaInfo, + bool overwritePrompt, + 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 + kdDebug () << "kpDocument::savePixmapToFile (" + << url + << ",overwritePrompt=" << overwritePrompt + << ",lossyPrompt=" << lossyPrompt + << ")" << endl; + saveOptions.printDebug (QString::fromLatin1 ("\tsaveOptions")); + metaInfo.printDebug (QString::fromLatin1 ("\tmetaInfo")); +#endif + + if (overwritePrompt && KIO::NetAccess::exists (url, false/*write*/, parent)) + { + int result = KMessageBox::warningContinueCancel (parent, + i18n ("A document called \"%1\" already exists.\n" + "Do you want to overwrite it?") + .arg (prettyFilenameForURL (url)), + QString::null, + i18n ("Overwrite")); + + if (result != KMessageBox::Continue) + { + #if DEBUG_KP_DOCUMENT + kdDebug () << "\tuser doesn't want to overwrite" << endl; + #endif + + return false; + } + } + + + if (lossyPrompt && !lossyPromptContinue (pixmap, saveOptions, parent)) + { + #if DEBUG_KP_DOCUMENT + kdDebug () << "\treturning false because of lossyPrompt" << endl; + #endif + return false; + } + + + // Local file? + if (url.isLocalFile ()) + { + const QString filename = url.path (); + + // sync: All failure exit paths _must_ call KSaveFile::abort() or + // else, the KSaveFile destructor will overwrite the file, + // <filename>, despite the failure. + KSaveFile atomicFileWriter (filename); + { + if (atomicFileWriter.status () != 0) + { + // We probably don't need this as <filename> has not been + // opened. + atomicFileWriter.abort (); + + #if DEBUG_KP_DOCUMENT + kdDebug () << "\treturning false because could not open KSaveFile" + << " status=" << atomicFileWriter.status () << endl; + #endif + ::CouldNotCreateTemporaryFileDialog (parent); + return false; + } + + // Write to local temporary file. + if (!savePixmapToDevice (pixmap, atomicFileWriter.file (), + saveOptions, metaInfo, + false/*no lossy prompt*/, + parent)) + { + atomicFileWriter.abort (); + + #if DEBUG_KP_DOCUMENT + kdDebug () << "\treturning false because could not save pixmap to device" + << endl; + #endif + ::CouldNotSaveDialog (url, parent); + return false; + } + + // Atomically overwrite local file with the temporary file + // we saved to. + if (!atomicFileWriter.close ()) + { + atomicFileWriter.abort (); + + #if DEBUG_KP_DOCUMENT + kdDebug () << "\tcould not close KSaveFile" << endl; + #endif + ::CouldNotSaveDialog (url, parent); + return false; + } + } // sync KSaveFile.abort() + } + // Remote file? + else + { + // Create temporary file that is deleted when the variable goes + // out of scope. + KTempFile tempFile; + tempFile.setAutoDelete (true); + + QString filename = tempFile.name (); + if (filename.isEmpty ()) + { + #if DEBUG_KP_DOCUMENT + kdDebug () << "\treturning false because tempFile empty" << endl; + #endif + ::CouldNotCreateTemporaryFileDialog (parent); + return false; + } + + // Write to local temporary file. + QFile file (filename); + { + if (!file.open (IO_WriteOnly)) + { + #if DEBUG_KP_DOCUMENT + kdDebug () << "\treturning false because can't open file" + << " errorString=" << file.errorString () << endl; + #endif + ::CouldNotCreateTemporaryFileDialog (parent); + return false; + } + + if (!savePixmapToDevice (pixmap, &file, + saveOptions, metaInfo, + false/*no lossy prompt*/, + parent)) + { + #if DEBUG_KP_DOCUMENT + kdDebug () << "\treturning false because could not save pixmap to device" + << endl; + #endif + ::CouldNotSaveDialog (url, parent); + return false; + } + } + file.close (); + if (file.status () != IO_Ok) + { + #if DEBUG_KP_DOCUMENT + kdDebug () << "\treturning false because could not close" << endl; + #endif + ::CouldNotSaveDialog (url, parent); + return false; + } + + // Copy local temporary file to overwrite remote. + // TODO: No one seems to know how to do this atomically + // [http://lists.kde.org/?l=kde-core-devel&m=117845162728484&w=2]. + // At least, fish:// (ssh) is definitely not atomic. + if (!KIO::NetAccess::upload (filename, url, parent)) + { + #if DEBUG_KP_DOCUMENT + kdDebug () << "\treturning false because could not upload" << endl; + #endif + KMessageBox::error (parent, + i18n ("Could not save image - failed to upload.")); + return false; + } + } + + + return true; +} + +bool kpDocument::saveAs (const KURL &url, + const kpDocumentSaveOptions &saveOptions, + bool overwritePrompt, + bool lossyPrompt) +{ +#if DEBUG_KP_DOCUMENT + kdDebug () << "kpDocument::saveAs (" << url << "," + << saveOptions.mimeType () << ")" << endl; +#endif + + if (kpDocument::savePixmapToFile (pixmapWithSelection (), + url, + saveOptions, *metaInfo (), + overwritePrompt, + lossyPrompt, + m_mainWindow)) + { + setURL (url, true/*is from url*/); + *m_saveOptions = saveOptions; + m_modified = false; + + m_savedAtLeastOnceBefore = true; + + emit documentSaved (); + return true; + } + else + { + return false; + } +} + +// public +bool kpDocument::savedAtLeastOnceBefore () const +{ + return m_savedAtLeastOnceBefore; +} + +// public +KURL kpDocument::url () const +{ + return m_url; +} + +// public +void kpDocument::setURL (const KURL &url, bool isFromURL) +{ + m_url = url; + m_isFromURL = isFromURL; +} + +// public +bool kpDocument::isFromURL (bool checkURLStillExists) const +{ + if (!m_isFromURL) + return false; + + if (!checkURLStillExists) + return true; + + return (!m_url.isEmpty () && + KIO::NetAccess::exists (m_url, true/*open*/, m_mainWindow)); +} + + +// static +QString kpDocument::prettyURLForURL (const KURL &url) +{ + if (url.isEmpty ()) + return i18n ("Untitled"); + else + return url.prettyURL (0, KURL::StripFileProtocol); +} + +QString kpDocument::prettyURL () const +{ + return prettyURLForURL (m_url); +} + + +// static +QString kpDocument::prettyFilenameForURL (const KURL &url) +{ + if (url.isEmpty ()) + return i18n ("Untitled"); + else if (url.fileName ().isEmpty ()) + return prettyURLForURL (url); // better than the name "" + else + return url.fileName (); +} + +QString kpDocument::prettyFilename () const +{ + return prettyFilenameForURL (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 +{ + if (ofSelection && m_selection) + return m_selection->width (); + else + return m_pixmap->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 +{ + if (ofSelection && m_selection) + return m_selection->height (); + else + return m_pixmap->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 +{ + if (ofSelection && m_selection) + return m_selection->boundingRect (); + else + return m_pixmap->rect (); +} + + +/* + * Pixmap access + */ + +// public +QPixmap kpDocument::getPixmapAt (const QRect &rect) const +{ + return kpPixmapFX::getPixmapAt (*m_pixmap, rect); +} + +// public +void kpDocument::setPixmapAt (const QPixmap &pixmap, const QPoint &at) +{ +#if DEBUG_KP_DOCUMENT && 0 + kdDebug () << "kpDocument::setPixmapAt (pixmap (w=" + << pixmap.width () + << ",h=" << pixmap.height () + << "), x=" << at.x () + << ",y=" << at.y () + << endl; +#endif + + kpPixmapFX::setPixmapAt (m_pixmap, at, pixmap); + slotContentsChanged (QRect (at.x (), at.y (), pixmap.width (), pixmap.height ())); +} + +// public +void kpDocument::paintPixmapAt (const QPixmap &pixmap, const QPoint &at) +{ + kpPixmapFX::paintPixmapAt (m_pixmap, at, pixmap); + slotContentsChanged (QRect (at.x (), at.y (), pixmap.width (), pixmap.height ())); +} + + +// public +QPixmap *kpDocument::pixmap (bool ofSelection) const +{ + if (ofSelection) + { + if (m_selection && m_selection->pixmap ()) + return m_selection->pixmap (); + else + return 0; + } + else + return m_pixmap; +} + +// public +void kpDocument::setPixmap (const QPixmap &pixmap) +{ + m_oldWidth = width (), m_oldHeight = height (); + + *m_pixmap = pixmap; + + if (m_oldWidth == width () && m_oldHeight == height ()) + slotContentsChanged (pixmap.rect ()); + else + slotSizeChanged (width (), height ()); +} + +// public +void kpDocument::setPixmap (bool ofSelection, const QPixmap &pixmap) +{ + if (ofSelection) + { + if (!m_selection) + { + kdError () << "kpDocument::setPixmap(ofSelection=true) without sel" << endl; + return; + } + + m_selection->setPixmap (pixmap); + } + else + setPixmap (pixmap); +} + + +// private +void kpDocument::updateToolsSingleKeyTriggersEnabled () +{ + if (m_mainWindow) + { + // Disable single key shortcuts when the user is editing text + m_mainWindow->enableActionsSingleKeyTriggers (!m_selection || !m_selection->isText ()); + } +} + + +// public +kpSelection *kpDocument::selection () const +{ + return m_selection; +} + +// public +void kpDocument::setSelection (const kpSelection &selection) +{ +#if DEBUG_KP_DOCUMENT && 0 + kdDebug () << "kpDocument::setSelection() sel boundingRect=" + << selection.boundingRect () + << endl; +#endif + + kpViewManager *vm = m_mainWindow ? m_mainWindow->viewManager () : 0; + if (vm) + vm->setQueueUpdates (); + + bool hadSelection = (bool) m_selection; + + + const bool isTextChanged = (m_mainWindow->toolIsTextTool () != + (selection.type () == kpSelection::Text)); + + // 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 (m_mainWindow && + (!m_mainWindow->toolIsASelectionTool () || isTextChanged)) + { + // Switch to the appropriately shaped selection tool + // _before_ we change the selection + // (all selection tool's ::end() functions nuke the current selection) + switch (selection.type ()) + { + case kpSelection::Rectangle: + m_mainWindow->slotToolRectSelection (); + break; + case kpSelection::Ellipse: + m_mainWindow->slotToolEllipticalSelection (); + break; + case kpSelection::Points: + m_mainWindow->slotToolFreeFormSelection (); + break; + case kpSelection::Text: + m_mainWindow->slotToolText (); + break; + default: + break; + } + } + + + if (m_selection) + { + // TODO: Emitting this, before setting the new selection, is bogus + // since it would redraw the old selection. + // + // Luckily, this doesn't matter thanks to the + // kpViewManager::setQueueUpdates() call above. + if (m_selection->pixmap ()) + slotContentsChanged (m_selection->boundingRect ()); + else + // TODO: Should emit contentsChanged() instead? + // I don't think it matters since contentsChanged() is + // connected to updateViews() anyway (see + // kpMainWindow::setDocument ()). + vm->updateViews (m_selection->boundingRect ()); + + delete m_selection; + } + + m_selection = new kpSelection (selection); + + // TODO: this coupling is bad, careless and lazy + if (m_mainWindow) + { + if (!m_selection->isText ()) + { + if (m_selection->transparency () != m_mainWindow->selectionTransparency ()) + { + kdDebug () << "kpDocument::setSelection() sel's transparency differs " + "from mainWindow's transparency - setting mainWindow's transparency " + "to sel" + << endl; + kdDebug () << "\tisOpaque: sel=" << m_selection->transparency ().isOpaque () + << " mainWindow=" << m_mainWindow->selectionTransparency ().isOpaque () + << endl; + m_mainWindow->setSelectionTransparency (m_selection->transparency ()); + } + } + else + { + if (m_selection->textStyle () != m_mainWindow->textStyle ()) + { + kdDebug () << "kpDocument::setSelection() sel's textStyle differs " + "from mainWindow's textStyle - setting mainWindow's textStyle " + "to sel" + << endl; + m_mainWindow->setTextStyle (m_selection->textStyle ()); + } + } + } + + updateToolsSingleKeyTriggersEnabled (); + +#if DEBUG_KP_DOCUMENT && 0 + kdDebug () << "\tcheck sel " << (int *) m_selection + << " boundingRect=" << m_selection->boundingRect () + << endl; +#endif + if (m_selection->pixmap ()) + slotContentsChanged (m_selection->boundingRect ()); + else + // TODO: Should emit contentsChanged() instead? + // I don't think it matters since contentsChanged() is + // connected to updateViews() anyway (see + // kpMainWindow::setDocument ()). + vm->updateViews (m_selection->boundingRect ()); + + // There's no need to disconnect() the old selection since we: + // + // 1. Connect our _copy_ of the given selection. + // 2. We delete our copy when setSelection() is called again. + // + // See code above for both. + connect (m_selection, SIGNAL (changed (const QRect &)), + this, SLOT (slotContentsChanged (const QRect &))); + + + if (!hadSelection) + emit selectionEnabled (true); + + if (isTextChanged) + emit selectionIsTextChanged (selection.type () == kpSelection::Text); + + if (vm) + vm->restoreQueueUpdates (); +} + +// public +QPixmap kpDocument::getSelectedPixmap (const QBitmap &maskBitmap_) const +{ + kpSelection *sel = selection (); + + // must have a selection region + if (!sel) + { + kdError () << "kpDocument::getSelectedPixmap() no sel region" << endl; + return QPixmap (); + } + + // easy if we already have it :) + if (sel->pixmap ()) + return *sel->pixmap (); + + + const QRect boundingRect = sel->boundingRect (); + if (!boundingRect.isValid ()) + { + kdError () << "kpDocument::getSelectedPixmap() boundingRect invalid" << endl; + return QPixmap (); + } + + + QBitmap maskBitmap = maskBitmap_; + if (maskBitmap.isNull () && + !sel->isRectangular ()) + { + maskBitmap = sel->maskForOwnType (); + + if (maskBitmap.isNull ()) + { + kdError () << "kpDocument::getSelectedPixmap() could not get mask" << endl; + return QPixmap (); + } + } + + + QPixmap selPixmap = getPixmapAt (boundingRect); + + if (!maskBitmap.isNull ()) + { + // Src Dest = Result + // ----------------- + // 0 0 0 + // 0 1 0 + // 1 0 0 + // 1 1 1 + QBitmap selMaskBitmap = kpPixmapFX::getNonNullMask (selPixmap); + bitBlt (&selMaskBitmap, + QPoint (0, 0), + &maskBitmap, + QRect (0, 0, maskBitmap.width (), maskBitmap.height ()), + Qt::AndROP); + selPixmap.setMask (selMaskBitmap); + } + + return selPixmap; +} + +// public +bool kpDocument::selectionPullFromDocument (const kpColor &backgroundColor) +{ + kpViewManager *vm = m_mainWindow ? m_mainWindow->viewManager () : 0; + + kpSelection *sel = selection (); + + // must have a selection region + if (!sel) + { + kdError () << "kpDocument::selectionPullFromDocument() no sel region" << endl; + return false; + } + + // should not already have a pixmap + if (sel->pixmap ()) + { + kdError () << "kpDocument::selectionPullFromDocument() already has pixmap" << endl; + return false; + } + + const QRect boundingRect = sel->boundingRect (); + if (!boundingRect.isValid ()) + { + kdError () << "kpDocument::selectionPullFromDocument() boundingRect invalid" << endl; + return false; + } + + + // + // Figure out mask for non-rectangular selections + // + + QBitmap maskBitmap = sel->maskForOwnType (true/*return null bitmap for rectangular*/); + + + // + // Get selection pixmap from document + // + + QPixmap selPixmap = getSelectedPixmap (maskBitmap); + + if (vm) + vm->setQueueUpdates (); + + sel->setPixmap (selPixmap); + + + // + // Fill opaque bits of the hole in the document + // + + // TODO: this assumes backgroundColor == sel->transparency ().transparentColor() + const QPixmap selTransparentPixmap = sel->transparentPixmap (); + + if (backgroundColor.isOpaque ()) + { + QPixmap erasePixmap (boundingRect.width (), boundingRect.height ()); + erasePixmap.fill (backgroundColor.toQColor ()); + + if (selTransparentPixmap.mask ()) + erasePixmap.setMask (*selTransparentPixmap.mask ()); + + paintPixmapAt (erasePixmap, boundingRect.topLeft ()); + } + else + { + kpPixmapFX::paintMaskTransparentWithBrush (m_pixmap, + boundingRect.topLeft (), + kpPixmapFX::getNonNullMask (selTransparentPixmap)); + slotContentsChanged (boundingRect); + } + + if (vm) + vm->restoreQueueUpdates (); + + return true; +} + +// public +bool kpDocument::selectionDelete () +{ + kpSelection *sel = selection (); + + if (!sel) + return false; + + const QRect boundingRect = sel->boundingRect (); + if (!boundingRect.isValid ()) + return false; + + bool selectionHadPixmap = m_selection ? (bool) m_selection->pixmap () : false; + + delete m_selection; + m_selection = 0; + + + // HACK to prevent document from being modified when + // user cancels dragging out a new selection + if (selectionHadPixmap) + slotContentsChanged (boundingRect); + else + emit contentsChanged (boundingRect); + + emit selectionEnabled (false); + + + updateToolsSingleKeyTriggersEnabled (); + + return true; +} + +// public +bool kpDocument::selectionCopyOntoDocument (bool useTransparentPixmap) +{ + kpSelection *sel = selection (); + + // must have a pixmap already + if (!sel) + return false; + + // hasn't actually been lifted yet + if (!sel->pixmap ()) + return true; + + const QRect boundingRect = sel->boundingRect (); + if (!boundingRect.isValid ()) + return false; + + if (!sel->isText ()) + { + // We can't use kpSelection::paint() since that always uses the + // transparent pixmap. + paintPixmapAt (useTransparentPixmap ? sel->transparentPixmap () : sel->opaquePixmap (), + boundingRect.topLeft ()); + } + else + { + // (for antialiasing with background) + sel->paint (m_pixmap, rect ()); + } + + slotContentsChanged (boundingRect); + + return true; +} + +// public +bool kpDocument::selectionPushOntoDocument (bool useTransparentPixmap) +{ + return (selectionCopyOntoDocument (useTransparentPixmap) && selectionDelete ()); +} + +// public +QPixmap kpDocument::pixmapWithSelection () const +{ +#if DEBUG_KP_DOCUMENT && 1 + kdDebug () << "kpDocument::pixmapWithSelection()" << endl; +#endif + + // Have floating selection? + if (m_selection && m_selection->pixmap ()) + { + #if DEBUG_KP_DOCUMENT && 1 + kdDebug () << "\tselection @ " << m_selection->boundingRect () << endl; + #endif + QPixmap output = *m_pixmap; + + m_selection->paint (&output, rect ()); + + return output; + } + else + { + #if DEBUG_KP_DOCUMENT && 1 + kdDebug () << "\tno selection" << endl; + #endif + return *m_pixmap; + } +} + + +/* + * Transformations + */ + +void kpDocument::fill (const kpColor &color) +{ +#if DEBUG_KP_DOCUMENT + kdDebug () << "kpDocument::fill ()" << endl; +#endif + + kpPixmapFX::fill (m_pixmap, color); + slotContentsChanged (m_pixmap->rect ()); +} + +void kpDocument::resize (int w, int h, const kpColor &backgroundColor, bool fillNewAreas) +{ +#if DEBUG_KP_DOCUMENT + kdDebug () << "kpDocument::resize (" << w << "," << h << "," << fillNewAreas << ")" << endl; +#endif + + m_oldWidth = width (), m_oldHeight = height (); + +#if DEBUG_KP_DOCUMENT && 1 + kdDebug () << "\toldWidth=" << m_oldWidth + << " oldHeight=" << m_oldHeight + << endl; +#endif + + if (w == m_oldWidth && h == m_oldHeight) + return; + + kpPixmapFX::resize (m_pixmap, w, h, backgroundColor, fillNewAreas); + + slotSizeChanged (width (), height ()); +} + + +/* + * Slots + */ + +void kpDocument::slotContentsChanged (const QRect &rect) +{ + setModified (); + emit contentsChanged (rect); +} + +void kpDocument::slotSizeChanged (int newWidth, int newHeight) +{ + setModified (); + emit sizeChanged (newWidth, newHeight); + emit sizeChanged (QSize (newWidth, newHeight)); +} + +void kpDocument::slotSizeChanged (const QSize &newSize) +{ + slotSizeChanged (newSize.width (), newSize.height ()); +} + +#include <kpdocument.moc> diff --git a/kolourpaint/kpdocument.h b/kolourpaint/kpdocument.h new file mode 100644 index 00000000..d75e36ff --- /dev/null +++ b/kolourpaint/kpdocument.h @@ -0,0 +1,260 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <qbitmap.h> +#include <qobject.h> +#include <qstring.h> + +#include <kurl.h> + +#include <kppixmapfx.h> + + +class QImage; +class QIODevice; +class QPixmap; +class QPoint; +class QRect; +class QSize; + +class kpColor; +class kpDocumentSaveOptions; +class kpDocumentMetaInfo; +class kpMainWindow; +class kpSelection; + + +class kpDocument : public QObject +{ +Q_OBJECT + +public: + kpDocument (int w, int h, kpMainWindow *mainWindow); + ~kpDocument (); + + kpMainWindow *mainWindow () const; + void setMainWindow (kpMainWindow *mainWindow); + + + /* + * File I/O + */ + + // Wraps kpPixmapFX::convertToPixmapAsLosslessAsPossible() but also + // returns document meta information. + static QPixmap convertToPixmapAsLosslessAsPossible ( + const QImage &image, + const kpPixmapFX::WarnAboutLossInfo &wali = kpPixmapFX::WarnAboutLossInfo (), + kpDocumentSaveOptions *saveOptions = 0, + kpDocumentMetaInfo *metaInfo = 0); + + static QPixmap getPixmapFromFile (const KURL &url, bool suppressDoesntExistDialog, + QWidget *parent, + kpDocumentSaveOptions *saveOptions = 0, + kpDocumentMetaInfo *metaInfo = 0); + // TODO: fix: open*() should only be called once. + // Create a new kpDocument() if you want to open again. + void openNew (const KURL &url); + bool open (const KURL &url, bool newDocSameNameIfNotExist = false); + + static bool lossyPromptContinue (const QPixmap &pixmap, + const kpDocumentSaveOptions &saveOptions, + QWidget *parent); + static bool savePixmapToDevice (const QPixmap &pixmap, + QIODevice *device, + const kpDocumentSaveOptions &saveOptions, + const kpDocumentMetaInfo &metaInfo, + bool lossyPrompt, + QWidget *parent, + bool *userCancelled = 0); + static bool savePixmapToFile (const QPixmap &pixmap, + const KURL &url, + const kpDocumentSaveOptions &saveOptions, + const kpDocumentMetaInfo &metaInfo, + bool overwritePrompt, + bool lossyPrompt, + QWidget *parent); + bool save (bool overwritePrompt = false, bool lossyPrompt = false); + bool saveAs (const KURL &url, + const kpDocumentSaveOptions &saveOptions, + bool overwritePrompt = true, + bool lossyPrompt = true); + + // Returns whether save() or saveAs() have ever been called and returned true + bool savedAtLeastOnceBefore () const; + + KURL url () const; + void setURL (const KURL &url, bool isFromURL); + + // Returns whether the document's pixmap 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 pixmap stored at url() + // is equal to pixmap() (unless the something has happened to that url + // outside of KolourPaint). + bool isFromURL (bool checkURLStillExists = true) const; + + // (will convert: empty URL --> "Untitled") + static QString prettyURLForURL (const KURL &url); + QString prettyURL () const; + + // (will convert: empty URL --> "Untitled") + static QString prettyFilenameForURL (const KURL &url); + 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; + + 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); + + 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; + + + /* + * Pixmap access + */ + + // get a copy of a bit of the doc's pixmap + // (not including the selection) + QPixmap getPixmapAt (const QRect &rect) const; + + void setPixmapAt (const QPixmap &pixmap, const QPoint &at); + + void paintPixmapAt (const QPixmap &pixmap, const QPoint &at); + + // (not including the selection) + QPixmap *pixmap (bool ofSelection = false) const; + void setPixmap (const QPixmap &pixmap); + void setPixmap (bool ofSelection, const QPixmap &pixmap); + +private: + void updateToolsSingleKeyTriggersEnabled (); + +public: + kpSelection *selection () const; + void setSelection (const kpSelection &selection); + + // TODO: this always returns opaque pixmap - need transparent ver + QPixmap getSelectedPixmap (const QBitmap &maskBitmap = QBitmap ()) const; + + bool selectionPullFromDocument (const kpColor &backgroundColor); + bool selectionDelete (); + bool selectionCopyOntoDocument (bool useTransparentPixmap = true); + bool selectionPushOntoDocument (bool useTransparentPixmap = true); + + // same as pixmap() but returns a _copy_ of the current pixmap + // + any selection pasted on top + QPixmap pixmapWithSelection () 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, bool fillNewAreas = true); + + +public slots: + // these will emit signals! + void slotContentsChanged (const QRect &rect); + void slotSizeChanged (int newWidth, int newHeight); + 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); + + // HACK: until we support Text Selection -> Rectangular Selection for Image ops + void selectionIsTextChanged (bool isText); + +private: + int m_constructorWidth, m_constructorHeight; + kpMainWindow *m_mainWindow; + QPixmap *m_pixmap; + + KURL m_url; + bool m_isFromURL; + bool m_savedAtLeastOnceBefore; + + kpDocumentSaveOptions *m_saveOptions; + kpDocumentMetaInfo *m_metaInfo; + + bool m_modified; + + kpSelection *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. + class kpDocumentPrivate *d; +}; + +#endif // KP_DOCUMENT_H diff --git a/kolourpaint/kpdocumentmetainfo.cpp b/kolourpaint/kpdocumentmetainfo.cpp new file mode 100644 index 00000000..5e5fc6ae --- /dev/null +++ b/kolourpaint/kpdocumentmetainfo.cpp @@ -0,0 +1,186 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <qpoint.h> + +#include <kdebug.h> + + +struct kpDocumentMetaInfoPrivate +{ + int m_dotsPerMeterX, m_dotsPerMeterY; + QPoint m_offset; + + QMap <QImageTextKeyLang, QString> 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 +kpDocumentMetaInfo &kpDocumentMetaInfo::operator= (const kpDocumentMetaInfo &rhs) +{ + 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 () ? + prefix + QString::fromLatin1 (":") : + QString::null; + + kdDebug () << usedPrefix << endl; + + kdDebug () << "dotsPerMeter X=" << dotsPerMeterX () + << " Y=" << dotsPerMeterY () + << " offset=" << offset () << endl; + + QValueList <QImageTextKeyLang> keyList = textList (); + for (QValueList <QImageTextKeyLang>::const_iterator it = keyList.begin (); + it != keyList.end (); + it++) + { + kdDebug () << "key=" << (*it).key + << " lang=" << (*it).lang + << " text=" << text (*it) + << endl; + } + + kdDebug () << usedPrefix << "ENDS" << endl; +} + + +// public +int kpDocumentMetaInfo::dotsPerMeterX () const +{ + return d->m_dotsPerMeterX; +} + +// public +void kpDocumentMetaInfo::setDotsPerMeterX (int val) +{ + d->m_dotsPerMeterX = val; +} + + +// public +int kpDocumentMetaInfo::dotsPerMeterY () const +{ + return d->m_dotsPerMeterY; +} + +// public +void kpDocumentMetaInfo::setDotsPerMeterY (int val) +{ + d->m_dotsPerMeterY = val; +} + + +// public +QPoint kpDocumentMetaInfo::offset () const +{ + return d->m_offset; +} + +// public +void kpDocumentMetaInfo::setOffset (const QPoint &point) +{ + d->m_offset = point; +} + + +// public +QMap <QImageTextKeyLang, QString> kpDocumentMetaInfo::textMap () const +{ + return d->m_textMap; +} + +// public +QValueList <QImageTextKeyLang> kpDocumentMetaInfo::textList () const +{ + return d->m_textMap.keys (); +} + + +// public +QString kpDocumentMetaInfo::text (const QImageTextKeyLang &itkl) const +{ + return d->m_textMap [itkl]; +} + +// public +QString kpDocumentMetaInfo::text (const char *key, const char *lang) const +{ + return text (QImageTextKeyLang (key, lang)); +} + + +// public +void kpDocumentMetaInfo::setText (const QImageTextKeyLang &itkl, + const QString &string) +{ + d->m_textMap [itkl] = string; +} + +// public +void kpDocumentMetaInfo::setText (const char *key, const char *lang, + const QString &string) +{ + setText (QImageTextKeyLang (key, lang), string); +} diff --git a/kolourpaint/kpdocumentmetainfo.h b/kolourpaint/kpdocumentmetainfo.h new file mode 100644 index 00000000..15e1408f --- /dev/null +++ b/kolourpaint/kpdocumentmetainfo.h @@ -0,0 +1,90 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 +#define KP_DOCUMENT_META_INFO + + +#include <qimage.h> +#include <qmap.h> +#include <qstring.h> +#include <qvaluelist.h> + + +class QPoint; + + +class kpDocumentMetaInfo +{ +public: + kpDocumentMetaInfo (); + kpDocumentMetaInfo (const kpDocumentMetaInfo &rhs); + virtual ~kpDocumentMetaInfo (); + +private: + bool operator== (const kpDocumentMetaInfo &rhs) const; + bool operator!= (const kpDocumentMetaInfo &rhs) const; + +public: + kpDocumentMetaInfo &operator= (const kpDocumentMetaInfo &rhs); + + + void printDebug (const QString &prefix) const; + + + // See QImage documentation + + int dotsPerMeterX () const; + void setDotsPerMeterX (int val); + + int dotsPerMeterY () const; + void setDotsPerMeterY (int val); + + + QPoint offset () const; + void setOffset (const QPoint &point); + + + QMap <QImageTextKeyLang, QString> textMap () const; + QValueList <QImageTextKeyLang> textList () const; + + QString text (const QImageTextKeyLang &itkl) const; + QString text (const char *key, const char *lang) const; + void setText (const QImageTextKeyLang &itkl, const QString &string); + void setText (const char *key, const char *lang, const QString &string); + + +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 kpDocumentMetaInfoPrivate *d; +}; + + +#endif // KP_DOCUMENT_META_INFO diff --git a/kolourpaint/kpdocumentsaveoptions.cpp b/kolourpaint/kpdocumentsaveoptions.cpp new file mode 100644 index 00000000..701b6b51 --- /dev/null +++ b/kolourpaint/kpdocumentsaveoptions.cpp @@ -0,0 +1,561 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <qpixmap.h> +#include <qstring.h> + +#include <kconfig.h> +#include <kdebug.h> +#include <kglobal.h> + +#include <kpdefs.h> + + +struct kpDocumentSaveOptionsPrivate +{ + 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 (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 + QString::fromLatin1 (": ") : + QString::null; + + kdDebug () << usedPrefix + << "mimeType=" << mimeType () + << " colorDepth=" << colorDepth () + << " dither=" << dither () + << " quality=" << quality () + << endl; +} + + +// public +QString kpDocumentSaveOptions::mimeType () const +{ + return d->m_mimeType; +} + +// public +void kpDocumentSaveOptions::setMimeType (const QString &mimeType) +{ + d->m_mimeType = mimeType; +} + + +// public static +QString kpDocumentSaveOptions::invalidMimeType () +{ + return QString::null; +} + +// 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 (KConfigBase *config) +{ + return config->readEntry (kpSettingForcedMimeType, + QString::fromLatin1 ("image/png")); +} + +// public static +void kpDocumentSaveOptions::saveDefaultMimeType (KConfigBase *config, + const QString &mimeType) +{ + config->writeEntry (kpSettingForcedMimeType, mimeType); +} + + +// public static +int kpDocumentSaveOptions::defaultColorDepth (KConfigBase *config) +{ + int colorDepth = + config->readNumEntry (kpSettingForcedColorDepth, -1); + + if (colorDepthIsInvalid (colorDepth)) + { + // (not screen depth, in case of transparency) + colorDepth = 32; + } + + return colorDepth; +} + +// public static +void kpDocumentSaveOptions::saveDefaultColorDepth (KConfigBase *config, int colorDepth) +{ + config->writeEntry (kpSettingForcedColorDepth, colorDepth); +} + + +// public static +int kpDocumentSaveOptions::defaultDither (KConfigBase *config) +{ + return config->readBoolEntry (kpSettingForcedDither, initialDither ()); +} + +// public static +void kpDocumentSaveOptions::saveDefaultDither (KConfigBase *config, bool dither) +{ + config->writeEntry (kpSettingForcedDither, dither); +} + + +// public static +int kpDocumentSaveOptions::defaultQuality (KConfigBase *config) +{ + int val = config->readNumEntry (kpSettingForcedQuality, -1); + if (qualityIsInvalid (val)) + val = -1; + + return val; +} + +// public static +void kpDocumentSaveOptions::saveDefaultQuality (KConfigBase *config, int quality) +{ + config->writeEntry (kpSettingForcedQuality, quality); +} + + +// public static +kpDocumentSaveOptions kpDocumentSaveOptions::defaultDocumentSaveOptions (KConfigBase *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 (KConfigBase *config, + const kpDocumentSaveOptions &oldDocInfo, + const kpDocumentSaveOptions &newDocInfo) +{ + bool savedSomething = false; + +#if DEBUG_KP_DOCUMENT_SAVE_OPTIONS + kdDebug () << "kpDocumentSaveOptions::saveDefaultDifferences()" << endl; + 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; +} + + +static QStringList mimeTypesSupportingProperty (const QString &property, + const QStringList &defaultMimeTypesWithPropertyList) +{ + QStringList mimeTypeList; + + KConfigGroupSaver cfgGroupSaver (KGlobal::config (), + kpSettingsGroupMimeTypeProperties); + KConfigBase *cfg = cfgGroupSaver.config (); + + if (cfg->hasKey (property)) + { + mimeTypeList = cfg->readListEntry (property); + } + else + { + mimeTypeList = defaultMimeTypesWithPropertyList; + + cfg->writeEntry (property, mimeTypeList); + cfg->sync (); + } + + return mimeTypeList; +} + +static bool mimeTypeSupportsProperty (const QString &mimeType, + const QString &property, const QStringList &defaultMimeTypesWithPropertyList) +{ + const QStringList mimeTypeList = mimeTypesSupportingProperty ( + property, defaultMimeTypesWithPropertyList); + + return mimeTypeList.contains (mimeType); +} + + +// SYNC: update mime info +// +// Only care about writable mimetypes. +// +// Run "branches/kolourpaint/control/scripts/gen_mimetype_line.sh Write" in +// the version of kdelibs/kimgio/ (e.g. KDE 3.5) KolourPaint is shipped with, +// to check for any new mimetypes to add info for. In the methods below, +// you can specify this info (maximum color depth, whether it's lossy etc.). +// +// Update the below list also and bump up "kpSettingsGroupMimeTypeProperties" +// in kpdefs.h. +// +// 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). +// +// Mimetypes done: +// image/jp2 [UNTESTED] +// image/jpeg +// image/png +// image/x-bmp +// image/x-eps +// image/x-pcx +// image/x-portable-bitmap +// image/x-portable-greymap +// image/x-portable-pixmap +// image/x-rgb +// image/x-targa +// image/x-xbm +// image/x-xpm +// +// 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) +{ + QStringList defaultList; + + // SYNC: update mime info here + + // Greyscale actually (unenforced since depth not set to configurable) + defaultList << QString::fromLatin1 ("image/x-eps:32"); + + defaultList << QString::fromLatin1 ("image/x-portable-bitmap:1"); + + // Greyscale actually (unenforced since depth not set to configurable) + defaultList << QString::fromLatin1 ("image/x-portable-greymap:8"); + + defaultList << QString::fromLatin1 ("image/x-xbm:1"); + + const QStringList mimeTypeList = mimeTypesSupportingProperty ( + kpSettingMimeTypeMaximumColorDepth, defaultList); + + const QString mimeTypeColon = mimeType + QString::fromLatin1 (":"); + for (QStringList::const_iterator it = mimeTypeList.begin (); + it != mimeTypeList.end (); + it++) + { + if ((*it).startsWith (mimeTypeColon)) + { + int number = (*it).mid (mimeTypeColon.length ()).toInt (); + if (!colorDepthIsInvalid (number)) + { + return number; + } + } + } + + 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 << QString::fromLatin1 ("image/png"); + defaultMimeTypes << QString::fromLatin1 ("image/x-bmp"); + defaultMimeTypes << QString::fromLatin1 ("image/x-pcx"); + + // TODO: Only 1, 24 not 8; Qt only sees 32 but "file" cmd realises + // it's either 1 or 24. + defaultMimeTypes << QString::fromLatin1 ("image/x-rgb"); + + // TODO: Only 8 and 24 - no 1. + defaultMimeTypes << QString::fromLatin1 ("image/x-xpm"); + + return mimeTypeSupportsProperty (mimeType, + kpSettingMimeTypeHasConfigurableColorDepth, + defaultMimeTypes); +} + +// public +bool kpDocumentSaveOptions::mimeTypeHasConfigurableColorDepth () const +{ + return mimeTypeHasConfigurableColorDepth (mimeType ()); +} + + +// public static +bool kpDocumentSaveOptions::mimeTypeHasConfigurableQuality (const QString &mimeType) +{ + QStringList defaultMimeTypes; + + // SYNC: update mime info here + defaultMimeTypes << QString::fromLatin1 ("image/jp2"); + defaultMimeTypes << QString::fromLatin1 ("image/jpeg"); + + return mimeTypeSupportsProperty (mimeType, + kpSettingMimeTypeHasConfigurableQuality, + defaultMimeTypes); +} + +// public +bool kpDocumentSaveOptions::mimeTypeHasConfigurableQuality () const +{ + return mimeTypeHasConfigurableQuality (mimeType ()); +} + + +// public +int kpDocumentSaveOptions::isLossyForSaving (const QPixmap &pixmap) const +{ + int ret = 0; + + if (mimeTypeMaximumColorDepth () < pixmap.depth ()) + { + ret |= MimeTypeMaximumColorDepthLow; + } + + if (mimeTypeHasConfigurableColorDepth () && + !colorDepthIsInvalid () /*TODO: prevent*/ && + (colorDepth () < pixmap.depth () || + colorDepth () < 32 && pixmap.mask ())) + { + ret |= ColorDepthLow; + } + + if (mimeTypeHasConfigurableQuality () && + !qualityIsInvalid ()) + { + ret |= Quality; + } + + return ret; +} + diff --git a/kolourpaint/kpdocumentsaveoptions.h b/kolourpaint/kpdocumentsaveoptions.h new file mode 100644 index 00000000..0d77ec2c --- /dev/null +++ b/kolourpaint/kpdocumentsaveoptions.h @@ -0,0 +1,150 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 QPixmap; +class QString; + +class KConfigBase; + + +class kpDocumentSaveOptions +{ +public: + kpDocumentSaveOptions (); + kpDocumentSaveOptions (const kpDocumentSaveOptions &rhs); + kpDocumentSaveOptions (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 <config>'s group has been set) + // (None of them call KConfigBase::reparseConfig() nor KConfigBase::sync()) + + static QString defaultMimeType (KConfigBase *config); + static void saveDefaultMimeType (KConfigBase *config, const QString &mimeType); + + static int defaultColorDepth (KConfigBase *config); + static void saveDefaultColorDepth (KConfigBase *config, int colorDepth); + + static int defaultDither (KConfigBase *config); + static void saveDefaultDither (KConfigBase *config, bool dither); + + static int defaultQuality (KConfigBase *config); + static void saveDefaultQuality (KConfigBase *config, int quality); + + + static kpDocumentSaveOptions defaultDocumentSaveOptions (KConfigBase *config); + // (returns true if it encountered a difference (and saved it to <config>)) + static bool saveDefaultDifferences (KConfigBase *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() < <pixmap>.depth() + MimeTypeMaximumColorDepthLow = 1, + // i.e. colorDepth() < <pixmap>.depth() || + // colorDepth() < 32 && <pixmap>.mask() + ColorDepthLow = 2, + // i.e. mimeTypeHasConfigurableQuality() + Quality = 4 + }; + + // Returns whether saving <pixmap> with these options will result in + // loss of information. Returned value is the bitwise OR of + // LossType enum possiblities. + int isLossyForSaving (const QPixmap &pixmap) 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/kolourpaint/kpdocumentsaveoptionswidget.cpp b/kolourpaint/kpdocumentsaveoptionswidget.cpp new file mode 100644 index 00000000..39edf5b8 --- /dev/null +++ b/kolourpaint/kpdocumentsaveoptionswidget.cpp @@ -0,0 +1,951 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <kpdocumentsaveoptionswidget.h> + +#include <qapplication.h> +#include <qbuffer.h> +#include <qimage.h> +#include <qlabel.h> +#include <qlayout.h> +#include <qtimer.h> + +#include <kcombobox.h> +#include <kconfig.h> +#include <kdebug.h> +#include <kdialog.h> +#include <kdialogbase.h> +#include <kglobal.h> +#include <kimageio.h> +#include <klocale.h> +#include <knuminput.h> +#include <kpushbutton.h> + +#include <kpdefs.h> +#include <kpdocument.h> +#include <kppixmapfx.h> +#include <kpresizesignallinglabel.h> +#include <kpselection.h> +#include <kptoolpreviewdialog.h> +#include <kpwidgetmapper.h> + + +// protected static +const QSize kpDocumentSaveOptionsPreviewDialog::s_pixmapLabelMinimumSize (25, 25); + + +kpDocumentSaveOptionsPreviewDialog::kpDocumentSaveOptionsPreviewDialog ( + QWidget *parent, + const char *name) + : QWidget (parent, name, + Qt::WType_TopLevel | + Qt::WStyle_Customize | + Qt::WStyle_DialogBorder | + Qt::WStyle_Title), +#if 0 +KDialogBase (parent, name, false/*non-modal*/, + i18n ("Save Preview"), + 0/*no buttons*/), +#endif + m_filePixmap (0), + m_fileSize (0) +{ + setCaption (i18n ("Save Preview")); + + QWidget *baseWidget = this;//new QWidget (this); + //setMainWidget (baseWidget); + + + QGridLayout *lay = new QGridLayout (baseWidget, 2, 1, + KDialog::marginHint (), KDialog::spacingHint ()); + + 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, SIGNAL (resized ()), + this, SLOT (updatePixmapPreview ())); +} + +kpDocumentSaveOptionsPreviewDialog::~kpDocumentSaveOptionsPreviewDialog () +{ + delete m_filePixmap; +} + + +// public +QSize kpDocumentSaveOptionsPreviewDialog::preferredMinimumSize () const +{ + const int contentsWidth = 180; + const int totalMarginsWidth = 2 * KDialog::marginHint (); + + return QSize (contentsWidth + totalMarginsWidth, + contentsWidth * 3 / 4 + totalMarginsWidth); +} + + +// public slot +void kpDocumentSaveOptionsPreviewDialog::setFilePixmapAndSize (const QPixmap &pixmap, + int fileSize) +{ + delete m_filePixmap; + m_filePixmap = new QPixmap (pixmap); + + updatePixmapPreview (); + + m_fileSize = fileSize; + + const int pixmapSize = kpPixmapFX::pixmapSize (pixmap); + const int percent = pixmapSize ? + QMAX (1, fileSize * 100 / pixmapSize) : + 0; +#if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + kdDebug () << "kpDocumentSaveOptionsPreviewDialog::setFilePixmapAndSize()" + << " pixmapSize=" << pixmapSize + << " fileSize=" << fileSize + << " raw fileSize/pixmapSize%=" + << (pixmapSize ? fileSize * 100 / pixmapSize : 0) + << endl; +#endif + + // HACK: I don't know if the percentage thing will work well and we're + // really close to the message freeze so provide alt. texts to choose + // from during the message freeze :) + const QString alternateText0 = i18n ("%1 bytes"); + const QString alternateText1 = i18n ("%1 bytes (%2%)"); + const QString alternateText2 = i18n ("%1 B"); + const QString alternateText3 = i18n ("%1 B (%2%)"); + const QString alternateText4 = i18n ("%1 B (approx. %2%)"); + const QString alternateText5 = i18n ("%1B"); + const QString alternateText6 = i18n ("%1B (%2%)"); + const QString alternateText7 = i18n ("%1B (approx. %2%)"); + m_fileSizeLabel->setText (i18n ("%1 bytes (approx. %2%)") + .arg (KGlobal::locale ()->formatLong (m_fileSize)) + .arg (percent)); +} + +// public slot +void kpDocumentSaveOptionsPreviewDialog::updatePixmapPreview () +{ +#if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + kdDebug () << "kpDocumentSaveOptionsPreviewDialog::updatePreviewPixmap()" + << " filePixmapLabel.size=" << m_filePixmapLabel->size () + << " filePixmap.size=" << m_filePixmap->size () + << endl; +#endif + + if (m_filePixmap) + { + int maxNewWidth = QMIN (m_filePixmap->width (), + m_filePixmapLabel->width ()), + maxNewHeight = QMIN (m_filePixmap->height (), + m_filePixmapLabel->height ()); + + double keepsAspect = kpToolPreviewDialog::aspectScale ( + maxNewWidth, maxNewHeight, + m_filePixmap->width (), m_filePixmap->height ()); + #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + kdDebug () << "\tmaxNewWidth=" << maxNewWidth + << " maxNewHeight=" << maxNewHeight + << " keepsAspect=" << keepsAspect + << endl; + #endif + + + const int newWidth = kpToolPreviewDialog::scaleDimension ( + m_filePixmap->width (), + keepsAspect, + 1, + maxNewWidth); + const int newHeight = kpToolPreviewDialog::scaleDimension ( + m_filePixmap->height (), + keepsAspect, + 1, + maxNewHeight); + #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + kdDebug () << "\tnewWidth=" << newWidth + << " newHeight=" << newHeight + << endl; + #endif + + + QPixmap transformedPixmap = + kpPixmapFX::scale (*m_filePixmap, + newWidth, newHeight); + + + QPixmap labelPixmap (m_filePixmapLabel->width (), + m_filePixmapLabel->height ()); + kpPixmapFX::fill (&labelPixmap, kpColor::transparent); + kpPixmapFX::setPixmapAt (&labelPixmap, + (labelPixmap.width () - transformedPixmap.width ()) / 2, + (labelPixmap.height () - transformedPixmap.height ()) / 2, + transformedPixmap); + + + m_filePixmapLabel->setPixmap (labelPixmap); + } + else + { + m_filePixmapLabel->setPixmap (QPixmap ()); + } +} + + +// protected virtual [base QWidget] +void kpDocumentSaveOptionsPreviewDialog::closeEvent (QCloseEvent *e) +{ +#if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + kdDebug () << "kpDocumentSaveOptionsPreviewDialog::closeEvent()" << endl; +#endif + + QWidget::closeEvent (e); + + emit finished (); +} + +// protected virtual [base QWidget] +void kpDocumentSaveOptionsPreviewDialog::moveEvent (QMoveEvent *e) +{ +#if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + kdDebug () << "kpDocumentSaveOptionsPreviewDialog::moveEvent()" << endl; +#endif + + QWidget::moveEvent (e); + + emit moved (); +} + +// protected virtual [base QWidget] +void kpDocumentSaveOptionsPreviewDialog::resizeEvent (QResizeEvent *e) +{ +#if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + kdDebug () << "kpDocumentSaveOptionsPreviewDialog::resizeEvent()" << endl; +#endif + + QWidget::resizeEvent (e); + + emit resized (); +} + + +kpDocumentSaveOptionsWidget::kpDocumentSaveOptionsWidget ( + const QPixmap &docPixmap, + const kpDocumentSaveOptions &saveOptions, + const kpDocumentMetaInfo &metaInfo, + QWidget *parent, const char *name) + : QWidget (parent, name), + m_visualParent (parent) +{ + init (); + setDocumentSaveOptions (saveOptions); + setDocumentPixmap (docPixmap); + setDocumentMetaInfo (metaInfo); +} + +kpDocumentSaveOptionsWidget::kpDocumentSaveOptionsWidget ( + QWidget *parent, const char *name) + : QWidget (parent, name), + m_visualParent (parent) +{ + init (); +} + +// private +void kpDocumentSaveOptionsWidget::init () +{ + m_documentPixmap = 0; + m_previewDialog = 0; + m_visualParent = 0; + + + m_colorDepthLabel = new QLabel (i18n ("Convert &to:"), this); + m_colorDepthCombo = new KComboBox (this); + + m_colorDepthSpaceWidget = new QWidget (this); + + m_qualityLabel = new QLabel (i18n ("Quali&ty:"), this); + m_qualityInput = new KIntNumInput (this); + // Note that we set min to 1 not 0 since "0 Quality" is a bit misleading + // and 101 quality settings would be weird. So we lose 1 quality setting + // according to QImage::save(). + // TODO: 100 quality is also misleading since that implies perfect quality. + m_qualityInput->setRange (1, 100, 1/*step*/, true/*slider*/); + + m_previewButton = new KPushButton (i18n ("&Preview"), this); + m_previewButton->setToggleButton (true); + + + m_colorDepthLabel->setBuddy (m_colorDepthCombo); + + m_qualityLabel->setBuddy (m_qualityInput); + + + QHBoxLayout *lay = new QHBoxLayout (this, 0/*margin*/, KDialog::spacingHint ()); + + lay->addWidget (m_colorDepthLabel, 0/*stretch*/, Qt::AlignLeft); + lay->addWidget (m_colorDepthCombo, 0/*stretch*/); + + lay->addWidget (m_colorDepthSpaceWidget, 1/*stretch*/); + + lay->addWidget (m_qualityLabel, 0/*stretch*/, Qt::AlignLeft); + lay->addWidget (m_qualityInput, 2/*stretch*/); + + lay->addWidget (m_previewButton, 0/*stretch*/, Qt::AlignRight); + + + connect (m_colorDepthCombo, SIGNAL (activated (int)), + this, SLOT (slotColorDepthSelected ())); + connect (m_colorDepthCombo, SIGNAL (activated (int)), + this, SLOT (updatePreview ())); + + connect (m_qualityInput, SIGNAL (valueChanged (int)), + this, SLOT (updatePreviewDelayed ())); + + connect (m_previewButton, SIGNAL (toggled (bool)), + this, SLOT (showPreview (bool))); + + + m_updatePreviewDelay = 200/*ms*/; + + m_updatePreviewTimer = new QTimer (this); + connect (m_updatePreviewTimer, SIGNAL (timeout ()), + this, SLOT (updatePreview ())); + + m_updatePreviewDialogLastRelativeGeometryTimer = new QTimer (this); + connect (m_updatePreviewDialogLastRelativeGeometryTimer, SIGNAL (timeout ()), + this, SLOT (updatePreviewDialogLastRelativeGeometry ())); + + + setMode (None); + + slotColorDepthSelected (); +} + +kpDocumentSaveOptionsWidget::~kpDocumentSaveOptionsWidget () +{ +#if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + kdDebug () << "kpDocumentSaveOptionsWidget::<dtor>()" << endl; +#endif + hidePreview (); + + delete m_documentPixmap; +} + + +// public +void kpDocumentSaveOptionsWidget::setVisualParent (QWidget *visualParent) +{ +#if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + kdDebug () << "kpDocumentSaveOptionsWidget::setVisualParent(" + << visualParent << ")" << endl; +#endif + + m_visualParent = visualParent; +} + + +// protected +bool kpDocumentSaveOptionsWidget::mimeTypeHasConfigurableColorDepth () const +{ + return kpDocumentSaveOptions::mimeTypeHasConfigurableColorDepth (mimeType ()); +} + +// protected +bool kpDocumentSaveOptionsWidget::mimeTypeHasConfigurableQuality () const +{ + return kpDocumentSaveOptions::mimeTypeHasConfigurableQuality (mimeType ()); +} + + +// public +QString kpDocumentSaveOptionsWidget::mimeType () const +{ + return m_baseDocumentSaveOptions.mimeType (); +} + +// public slots +void kpDocumentSaveOptionsWidget::setMimeType (const QString &string) +{ +#if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + kdDebug () << "kpDocumentSaveOptionsWidget::setMimeType(" << string + << ") maxColorDepth=" + << kpDocumentSaveOptions::mimeTypeMaximumColorDepth (string) + << endl; +#endif + + const int newMimeTypeMaxDepth = + kpDocumentSaveOptions::mimeTypeMaximumColorDepth (string); + +#if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + kdDebug () << "\toldMimeType=" << mimeType () + << " maxColorDepth=" + << kpDocumentSaveOptions::mimeTypeMaximumColorDepth ( + mimeType ()) + << endl; +#endif + + if (mimeType ().isEmpty () || + kpDocumentSaveOptions::mimeTypeMaximumColorDepth (mimeType ()) != + newMimeTypeMaxDepth) + { + m_colorDepthCombo->clear (); + + m_colorDepthCombo->insertItem (i18n ("Monochrome"), 0); + m_colorDepthCombo->insertItem (i18n ("Monochrome (Dithered)"), 1); + + if (newMimeTypeMaxDepth >= 8) + { + m_colorDepthCombo->insertItem (i18n ("256 Color"), 2); + m_colorDepthCombo->insertItem (i18n ("256 Color (Dithered)"), 3); + } + + if (newMimeTypeMaxDepth >= 24) + { + m_colorDepthCombo->insertItem (i18n ("24-bit Color"), 4); + } + + if (m_colorDepthComboLastSelectedItem >= 0 && + m_colorDepthComboLastSelectedItem < m_colorDepthCombo->count ()) + { + #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + kdDebug () << "\tsetting colorDepthCombo to " + << m_colorDepthComboLastSelectedItem << endl; + #endif + + m_colorDepthCombo->setCurrentItem (m_colorDepthComboLastSelectedItem); + } + else + { + #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + kdDebug () << "\tsetting colorDepthCombo to max item since" + << " m_colorDepthComboLastSelectedItem=" + << m_colorDepthComboLastSelectedItem + << " out of range" << endl; + #endif + + m_colorDepthCombo->setCurrentItem (m_colorDepthCombo->count () - 1); + } + } + + + m_baseDocumentSaveOptions.setMimeType (string); + + if (mimeTypeHasConfigurableColorDepth ()) + setMode (ColorDepth); + else if (mimeTypeHasConfigurableQuality ()) + setMode (Quality); + else + setMode (None); + + updatePreview (); +} + + +// public +int kpDocumentSaveOptionsWidget::colorDepth () const +{ + if (mode () & ColorDepth) + { + switch (m_colorDepthCombo->currentItem ()) + { + case 0: + case 1: + return 1; + + case 2: + case 3: + return 8; + + case 4: + return 32; + + default: + return kpDocumentSaveOptions::invalidColorDepth (); + } + } + else + { + return m_baseDocumentSaveOptions.colorDepth (); + } +} + +// public +bool kpDocumentSaveOptionsWidget::dither () const +{ + if (mode () & ColorDepth) + { + return (m_colorDepthCombo->currentItem () == 1 || + m_colorDepthCombo->currentItem () == 3); + } + else + { + return m_baseDocumentSaveOptions.dither (); + } +} + +// protected static +int kpDocumentSaveOptionsWidget::colorDepthComboItemFromColorDepthAndDither ( + int depth, bool dither) +{ + if (depth == 1) + { + if (!dither) + { + return 0; + } + else + { + return 1; + } + } + else if (depth == 8) + { + if (!dither) + { + return 2; + } + else + { + return 3; + } + } + else if (depth == 32) + { + return 4; + } + else + { + return -1; + } +} + +// public slots +void kpDocumentSaveOptionsWidget::setColorDepthDither (int newDepth, bool newDither) +{ +#if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + kdDebug () << "kpDocumentSaveOptionsWidget::setColorDepthDither(" + << "depth=" << newDepth + << ",dither=" << newDither + << ")" << endl; +#endif + + m_baseDocumentSaveOptions.setColorDepth (newDepth); + m_baseDocumentSaveOptions.setDither (newDither); + + + const int comboItem = colorDepthComboItemFromColorDepthAndDither ( + newDepth, newDither); + // TODO: Ignoring when comboItem >= m_colorDepthCombo->count() is wrong. + // This happens if this mimeType has configurable colour depth + // and an incorrect maximum colour depth (less than a QImage of + // this mimeType, opened by kpDocument). + if (comboItem >= 0 && comboItem < m_colorDepthCombo->count ()) + m_colorDepthCombo->setCurrentItem (comboItem); + + + slotColorDepthSelected (); +} + + +// protected slot +void kpDocumentSaveOptionsWidget::slotColorDepthSelected () +{ + if (mode () & ColorDepth) + { + m_colorDepthComboLastSelectedItem = m_colorDepthCombo->currentItem (); + } + else + { + m_colorDepthComboLastSelectedItem = + colorDepthComboItemFromColorDepthAndDither ( + m_baseDocumentSaveOptions.colorDepth (), + m_baseDocumentSaveOptions.dither ()); + } + +#if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + kdDebug () << "kpDocumentSaveOptionsWidget::slotColorDepthSelected()" + << " mode&ColorDepth=" << (mode () & ColorDepth) + << " colorDepthComboLastSelectedItem=" + << m_colorDepthComboLastSelectedItem + << endl; +#endif +} + + +// public +int kpDocumentSaveOptionsWidget::quality () const +{ + if (mode () & Quality) + { + return m_qualityInput->value (); + } + else + { + return m_baseDocumentSaveOptions.quality (); + } +} + +// public +void kpDocumentSaveOptionsWidget::setQuality (int newQuality) +{ +#if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + kdDebug () << "kpDocumentSaveOptionsWidget::setQuality(" + << newQuality << ")" << endl; +#endif + + m_baseDocumentSaveOptions.setQuality (newQuality); + m_qualityInput->setValue (newQuality == -1/*QImage::save() default*/ ? + 75 : + newQuality); +} + + +// public +kpDocumentSaveOptions kpDocumentSaveOptionsWidget::documentSaveOptions () const +{ + return kpDocumentSaveOptions (mimeType (), colorDepth (), dither (), quality ()); +} + +// public +void kpDocumentSaveOptionsWidget::setDocumentSaveOptions ( + const kpDocumentSaveOptions &saveOptions) +{ + setMimeType (saveOptions.mimeType ()); + setColorDepthDither (saveOptions.colorDepth (), saveOptions.dither ()); + setQuality (saveOptions.quality ()); +} + + +// public +void kpDocumentSaveOptionsWidget::setDocumentPixmap (const QPixmap &documentPixmap) +{ + delete m_documentPixmap; + m_documentPixmap = new QPixmap (documentPixmap); + + updatePreview (); +} + +// public +void kpDocumentSaveOptionsWidget::setDocumentMetaInfo ( + const kpDocumentMetaInfo &metaInfo) +{ + m_documentMetaInfo = metaInfo; + + updatePreview (); +} + + +// public +kpDocumentSaveOptionsWidget::Mode kpDocumentSaveOptionsWidget::mode () const +{ + return m_mode; +} + +// public +void kpDocumentSaveOptionsWidget::setMode (Mode mode) +{ + m_mode = mode; + + + // If mode == None, we show still show the Color Depth widgets but disabled + m_colorDepthLabel->setShown (mode != Quality); + m_colorDepthCombo->setShown (mode != Quality); + m_colorDepthSpaceWidget->setShown (mode != Quality); + + m_qualityLabel->setShown (mode == Quality); + m_qualityInput->setShown (mode == Quality); + + + m_colorDepthLabel->setEnabled (mode == ColorDepth); + m_colorDepthCombo->setEnabled (mode == ColorDepth); + + m_qualityLabel->setEnabled (mode == Quality); + m_qualityInput->setEnabled (mode == Quality); + + + // SYNC: HACK: When changing between color depth and quality widgets, + // we change the height of "this", causing the text on the labels + // to move but the first instance of the text doesn't get erased. + // Qt bug. + QTimer::singleShot (0, this, SLOT (repaintLabels ())); +} + +// protected slot +void kpDocumentSaveOptionsWidget::repaintLabels () +{ + if (mode () != Quality) + m_colorDepthLabel->repaint (); + if (mode () == Quality) + m_qualityLabel->repaint (); +} + + +// protected slot +void kpDocumentSaveOptionsWidget::showPreview (bool yes) +{ +#if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + kdDebug () << "kpDocumentSaveOptionsWidget::showPreview(" << yes << ")" + << " m_previewDialog=" << bool (m_previewDialog) + << endl; +#endif + + if (yes == bool (m_previewDialog)) + return; + + if (!m_visualParent) + return; + + if (yes) + { + m_previewDialog = new kpDocumentSaveOptionsPreviewDialog (m_visualParent, "previewSaveDialog"); + updatePreview (); + + connect (m_previewDialog, SIGNAL (finished ()), + this, SLOT (hidePreview ())); + + + KConfigGroupSaver cfgGroupSaver (KGlobal::config (), kpSettingsGroupPreviewSave); + KConfigBase *cfg = cfgGroupSaver.config (); + + if (cfg->hasKey (kpSettingPreviewSaveUpdateDelay)) + { + m_updatePreviewDelay = cfg->readNumEntry (kpSettingPreviewSaveUpdateDelay); + } + else + { + cfg->writeEntry (kpSettingPreviewSaveUpdateDelay, m_updatePreviewDelay); + cfg->sync (); + } + + if (m_updatePreviewDelay < 0) + m_updatePreviewDelay = 0; + #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + kdDebug () << "\tread cfg preview dialog update delay=" + << m_updatePreviewDelay + << endl; + #endif + + + if (m_previewDialogLastRelativeGeometry.isEmpty ()) + { + #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + kdDebug () << "\tread cfg preview dialog last rel geometry" << endl; + #endif + KConfigGroupSaver cfgGroupSaver (KGlobal::config (), kpSettingsGroupPreviewSave); + KConfigBase *cfg = cfgGroupSaver.config (); + + m_previewDialogLastRelativeGeometry = cfg->readRectEntry ( + kpSettingPreviewSaveGeometry); + } + + #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + kdDebug () << "\tpreviewDialogLastRelativeGeometry=" + << m_previewDialogLastRelativeGeometry + << " visualParent->rect()=" << m_visualParent->rect () + << endl; + #endif + + QRect relativeGeometry; + if (!m_previewDialogLastRelativeGeometry.isEmpty () && + m_visualParent->rect ().intersects (m_previewDialogLastRelativeGeometry)) + { + #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + kdDebug () << "\tok" << endl; + #endif + relativeGeometry = m_previewDialogLastRelativeGeometry; + } + else + { + #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + kdDebug () << "\t\tinvalid" << endl; + #endif + const int margin = 20; + + relativeGeometry = + QRect (m_visualParent->width () - + m_previewDialog->preferredMinimumSize ().width () - + margin, + margin * 2, // Avoid folder combo + m_previewDialog->preferredMinimumSize ().width (), + m_previewDialog->preferredMinimumSize ().height ()); + } + + + const QRect globalGeometry = + kpWidgetMapper::toGlobal (m_visualParent, + relativeGeometry); + #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + kdDebug () << "\trelativeGeometry=" << relativeGeometry + << " globalGeometry=" << globalGeometry + << endl; + #endif + + m_previewDialog->resize (globalGeometry.size ()); + m_previewDialog->move (globalGeometry.topLeft ()); + + + m_previewDialog->show (); + + + #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + kdDebug () << "\tgeometry after show=" + << QRect (m_previewDialog->x (), m_previewDialog->y (), + m_previewDialog->width (), m_previewDialog->height ()) + << endl; + #endif + + updatePreviewDialogLastRelativeGeometry (); + + connect (m_previewDialog, SIGNAL (moved ()), + this, SLOT (updatePreviewDialogLastRelativeGeometry ())); + connect (m_previewDialog, SIGNAL (resized ()), + this, SLOT (updatePreviewDialogLastRelativeGeometry ())); + + m_updatePreviewDialogLastRelativeGeometryTimer->start (200/*ms*/); + } + else + { + m_updatePreviewDialogLastRelativeGeometryTimer->stop (); + + KConfigGroupSaver cfgGroupSaver (KGlobal::config (), kpSettingsGroupPreviewSave); + KConfigBase *cfg = cfgGroupSaver.config (); + + cfg->writeEntry (kpSettingPreviewSaveGeometry, m_previewDialogLastRelativeGeometry); + cfg->sync (); + + #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + kdDebug () << "\tsaving preview geometry " + << m_previewDialogLastRelativeGeometry + << " (Qt would have us believe " + << kpWidgetMapper::fromGlobal (m_visualParent, + QRect (m_previewDialog->x (), m_previewDialog->y (), + m_previewDialog->width (), m_previewDialog->height ())) + << ")" + << endl; + #endif + + m_previewDialog->deleteLater (); + m_previewDialog = 0; + } +} + +// protected slot +void kpDocumentSaveOptionsWidget::hidePreview () +{ + if (m_previewButton->isOn ()) + m_previewButton->toggle (); +} + + +// protected slot +void kpDocumentSaveOptionsWidget::updatePreviewDelayed () +{ + m_updatePreviewTimer->start (m_updatePreviewDelay, true/*single shot*/); +} + +// protected slot +void kpDocumentSaveOptionsWidget::updatePreview () +{ + if (!m_previewDialog || !m_documentPixmap) + return; + + + m_updatePreviewTimer->stop (); + + + QApplication::setOverrideCursor (Qt::waitCursor); + + QByteArray data; + + QBuffer buffer (data); + buffer.open (IO_WriteOnly); + kpDocument::savePixmapToDevice (*m_documentPixmap, + &buffer, + documentSaveOptions (), + m_documentMetaInfo, + false/*no lossy prompt*/, + this); + buffer.close (); + + + QImage image; + image.loadFromData (data, + KImageIO::typeForMime (mimeType ()).latin1 ()); + + // TODO: merge with kpDocument::getPixmapFromFile() + m_previewDialog->setFilePixmapAndSize ( + kpPixmapFX::convertToPixmapAsLosslessAsPossible (image), + data.size ()); + + QApplication::restoreOverrideCursor (); +} + +// protected slot +void kpDocumentSaveOptionsWidget::updatePreviewDialogLastRelativeGeometry () +{ +#if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + kdDebug () << "kpDocumentSaveOptionsWidget::" + << "updatePreviewDialogLastRelativeGeometry()" + << endl; +#endif + + if (m_previewDialog && m_previewDialog->isVisible ()) + { + m_previewDialogLastRelativeGeometry = + kpWidgetMapper::fromGlobal (m_visualParent, + QRect (m_previewDialog->x (), m_previewDialog->y (), + m_previewDialog->width (), m_previewDialog->height ())); + #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + kdDebug () << "\tcaching pos = " + << m_previewDialogLastRelativeGeometry + << endl; + #endif + } + else + { + #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + kdDebug () << "\tnot visible - ignoring geometry" << endl; + #endif + } +} + + +#include <kpdocumentsaveoptionswidget.moc> diff --git a/kolourpaint/kpdocumentsaveoptionswidget.h b/kolourpaint/kpdocumentsaveoptionswidget.h new file mode 100644 index 00000000..50bd35aa --- /dev/null +++ b/kolourpaint/kpdocumentsaveoptionswidget.h @@ -0,0 +1,200 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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_WIDGET_H +#define KP_DOCUMENT_SAVE_OPTIONS_WIDGET_H + + +#include <qsize.h> + +#include <qwidget.h> + + +class QPixmap; +class QLabel; + +class kpResizeSignallingLabel; + + +class kpDocumentSaveOptionsPreviewDialog : public QWidget +{ +Q_OBJECT + +public: + kpDocumentSaveOptionsPreviewDialog (QWidget *parent, const char *name = 0); + virtual ~kpDocumentSaveOptionsPreviewDialog (); + + QSize preferredMinimumSize () const; + +protected: + static const QSize s_pixmapLabelMinimumSize; + +signals: + void moved (); + void resized (); + void finished (); + +public slots: + void setFilePixmapAndSize (const QPixmap &filePixmap, int fileSize); + void updatePixmapPreview (); + +protected: + virtual void closeEvent (QCloseEvent *e); + virtual void moveEvent (QMoveEvent *e); + virtual void resizeEvent (QResizeEvent *e); + +protected: + QPixmap *m_filePixmap; + int m_fileSize; + + kpResizeSignallingLabel *m_filePixmapLabel; + QLabel *m_fileSizeLabel; +}; + + +#include <qrect.h> +#include <qwidget.h> + +#include <kpdocumentmetainfo.h> +#include <kpdocumentsaveoptions.h> + + +class QLabel; +class QTimer; + +class KComboBox; +class KIntNumInput; +class KPushButton; + + +class kpDocumentSaveOptionsWidget : public QWidget +{ +Q_OBJECT + +public: + kpDocumentSaveOptionsWidget (const QPixmap &docPixmap, + const kpDocumentSaveOptions &saveOptions, + const kpDocumentMetaInfo &metaInfo, + QWidget *parent, const char *name = 0); + kpDocumentSaveOptionsWidget (QWidget *parent, const char *name = 0); +private: + void init (); +public: + virtual ~kpDocumentSaveOptionsWidget (); + + + // <visualParent> is usually the filedialog + void setVisualParent (QWidget *visualParent); + + +protected: + bool mimeTypeHasConfigurableColorDepth () const; + bool mimeTypeHasConfigurableQuality () const; + +public: + QString mimeType () const; +public slots: + void setMimeType (const QString &string); + +public: + int colorDepth () const; + bool dither () const; +protected: + static int colorDepthComboItemFromColorDepthAndDither (int depth, bool dither); +public slots: + void setColorDepthDither (int depth, + bool dither = kpDocumentSaveOptions::initialDither ()); +protected slots: + void slotColorDepthSelected (); + +public: + int quality () const; +public slots: + void setQuality (int newQuality); + +public: + kpDocumentSaveOptions documentSaveOptions () const; +public slots: + void setDocumentSaveOptions (const kpDocumentSaveOptions &saveOptions); + + +public: + void setDocumentPixmap (const QPixmap &documentPixmap); + void setDocumentMetaInfo (const kpDocumentMetaInfo &metaInfo); + + +protected: + enum Mode + { + // (mutually exclusive) + None, ColorDepth, Quality + }; + + Mode mode () const; + void setMode (Mode mode); + +protected slots: + void repaintLabels (); + + +protected slots: + void showPreview (bool yes = true); + void hidePreview (); + void updatePreviewDelayed (); + void updatePreview (); + void updatePreviewDialogLastRelativeGeometry (); + + +protected: + QWidget *m_visualParent; + + Mode m_mode; + + QPixmap *m_documentPixmap; + + kpDocumentSaveOptions m_baseDocumentSaveOptions; + kpDocumentMetaInfo m_documentMetaInfo; + + QLabel *m_colorDepthLabel; + KComboBox *m_colorDepthCombo; + int m_colorDepthComboLastSelectedItem; + QWidget *m_colorDepthSpaceWidget; + + QLabel *m_qualityLabel; + KIntNumInput *m_qualityInput; + + KPushButton *m_previewButton; + kpDocumentSaveOptionsPreviewDialog *m_previewDialog; + QRect m_previewDialogLastRelativeGeometry; + QTimer *m_updatePreviewTimer; + int m_updatePreviewDelay; + QTimer *m_updatePreviewDialogLastRelativeGeometryTimer; +}; + + +#endif // KP_DOCUMENT_SAVE_OPTIONS_WIDGET_H diff --git a/kolourpaint/kpmainwindow.cpp b/kolourpaint/kpmainwindow.cpp new file mode 100644 index 00000000..9af3177b --- /dev/null +++ b/kolourpaint/kpmainwindow.cpp @@ -0,0 +1,1061 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <kpmainwindow_p.h> + +#include <qdragobject.h> +#include <qpainter.h> +#include <qtimer.h> + +#include <kactionclasses.h> +#include <kapplication.h> +#include <kconfig.h> +#include <kdebug.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <kurldrag.h> + +#include <kpcolortoolbar.h> +#include <kpcommandhistory.h> +#include <kpdefs.h> +#include <kpdocument.h> +#include <kppixmapfx.h> +#include <kpselection.h> +#include <kpselectiondrag.h> +#include <kpsinglekeytriggersaction.h> +#include <kpthumbnail.h> +#include <kptool.h> +#include <kptooltoolbar.h> +#include <kpviewmanager.h> +#include <kpviewscrollablecontainer.h> +#include <kpwidgetmapper.h> +#include <kpzoomedthumbnailview.h> +#include <kpzoomedview.h> + +#if DEBUG_KP_MAIN_WINDOW + #include <qdatetime.h> +#endif + + +kpMainWindow::kpMainWindow () + : KMainWindow (0/*parent*/, "mainWindow"), + m_isFullyConstructed (false) +{ + init (); + open (KURL (), true/*create an empty doc*/); + + m_isFullyConstructed = true; +} + +kpMainWindow::kpMainWindow (const KURL &url) + : KMainWindow (0/*parent*/, "mainWindow"), + m_isFullyConstructed (false) +{ + init (); + open (url, true/*create an empty doc with the same url if url !exist*/); + + m_isFullyConstructed = true; +} + +kpMainWindow::kpMainWindow (kpDocument *newDoc) + : KMainWindow (0/*parent*/, "mainWindow"), + m_isFullyConstructed (false) +{ + init (); + setDocument (newDoc); + + m_isFullyConstructed = true; +} + + +// public +double kpMainWindow::configColorSimilarity () const +{ + return m_configColorSimilarity; +} + +// public +void kpMainWindow::configSetColorSimilarity (double val) +{ + KConfigGroupSaver cfgGroupSaver (kapp->config (), kpSettingsGroupGeneral); + KConfigBase *cfg = cfgGroupSaver.config (); + + cfg->writeEntry (kpSettingColorSimilarity, m_configColorSimilarity = val); + cfg->sync (); +} + + +// private +void kpMainWindow::readGeneralSettings () +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\tkpMainWindow(" << name () << ")::readGeneralSettings()" << endl; +#endif + + KConfigGroupSaver cfgGroupSaver (kapp->config (), kpSettingsGroupGeneral); + KConfigBase *cfg = cfgGroupSaver.config (); + + m_configFirstTime = cfg->readBoolEntry (kpSettingFirstTime, true); + m_configShowGrid = cfg->readBoolEntry (kpSettingShowGrid, false); + m_configShowPath = cfg->readBoolEntry (kpSettingShowPath, false); + m_configColorSimilarity = cfg->readDoubleNumEntry (kpSettingColorSimilarity, 0); + d->m_moreEffectsDialogLastEffect = cfg->readNumEntry (kpSettingMoreEffectsLastEffect); + d->m_resizeScaleDialogLastKeepAspect = cfg->readBoolEntry (kpSettingResizeScaleLastKeepAspect, false); + + +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\t\tGeneral Settings: firstTime=" << m_configFirstTime + << " showGrid=" << m_configShowGrid + << " showPath=" << m_configShowPath + << " colorSimilarity=" << m_configColorSimilarity + << " moreEffectsDialogLastEffect=" << d->m_moreEffectsDialogLastEffect + << " resizeScaleDialogLastKeepAspect=" << d->m_resizeScaleDialogLastKeepAspect + << endl; +#endif +} + +// private +void kpMainWindow::readThumbnailSettings () +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\tkpMainWindow(" << name () << ")::readThumbnailSettings()" << endl; +#endif + + KConfigGroupSaver cfgGroupSaver (kapp->config (), kpSettingsGroupThumbnail); + KConfigBase *cfg = cfgGroupSaver.config (); + + m_configThumbnailShown = cfg->readBoolEntry (kpSettingThumbnailShown, false); + m_configThumbnailGeometry = cfg->readRectEntry (kpSettingThumbnailGeometry); + m_configZoomedThumbnail = cfg->readBoolEntry (kpSettingThumbnailZoomed, true); + d->m_configThumbnailShowRectangle = cfg->readBoolEntry (kpSettingThumbnailShowRectangle, true); + +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\t\tThumbnail Settings: shown=" << m_configThumbnailShown + << " geometry=" << m_configThumbnailGeometry + << " zoomed=" << m_configZoomedThumbnail + << " showRectangle=" << d->m_configThumbnailShowRectangle + << endl; +#endif +} + +// private +void kpMainWindow::init () +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow(" << name () << ")::init()" << endl; + QTime totalTime; totalTime.start (); + QTime time; time.start (); +#endif + + d = new kpMainWindowPrivate; + + m_scrollView = 0; + m_mainView = 0; + m_thumbnail = 0; + m_thumbnailView = 0; + m_document = 0; + m_viewManager = 0; + m_colorToolBar = 0; + m_toolToolBar = 0; + m_commandHistory = 0; + m_statusBarCreated = false; + m_settingSelectionTransparency = 0; + m_settingTextStyle = 0; + + m_docResizeToBeCompleted = false; + + + // + // set mainwindow properties + // + + setMinimumSize (320, 260); + setAcceptDrops (true); +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\tTIME: little init = " << time.restart () << "msec" << endl; +#endif + + + // + // read config + // + + // KConfig::readEntry() does not actually reread from disk, hence doesn't + // realise what other processes have done e.g. Settings / Show Path + kapp->config ()->reparseConfiguration (); +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\tTIME: reparseConfig = " << time.restart () << "msec" << endl; +#endif + + readGeneralSettings (); +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\tTIME: readGeneralSettings = " << time.restart () << "msec" << endl; +#endif + + readThumbnailSettings (); +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\tTIME: readThumbnailSettings = " << time.restart () << "msec" << endl; +#endif + + + // + // create GUI + // + + setupActions (); +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\tTIME: setupActions = " << time.restart () << "msec" << endl; +#endif + + createStatusBar (); +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\tTIME: createStatusBar = " << time.restart () << "msec" << endl; +#endif + + createGUI (); +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\tTIME: createGUI = " << time.restart () << "msec" << endl; +#endif + + + // + // create more GUI + // + + m_colorToolBar = new kpColorToolBar (i18n ("Color Box"), this, "Color Box"); +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\tTIME: new kpColorToolBar = " << time.restart () << "msec" << endl; +#endif + + createToolBox (); +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\tTIME: createToolBox = " << time.restart () << "msec" << endl; +#endif + + m_scrollView = new kpViewScrollableContainer (this, "scrollView"); + connect (m_scrollView, SIGNAL (beganDocResize ()), + this, SLOT (slotBeganDocResize ())); + connect (m_scrollView, SIGNAL (continuedDocResize (const QSize &)), + this, SLOT (slotContinuedDocResize (const QSize &))); + connect (m_scrollView, SIGNAL (cancelledDocResize ()), + this, SLOT (slotCancelledDocResize ())); + connect (m_scrollView, SIGNAL (endedDocResize (const QSize &)), + this, SLOT (slotEndedDocResize (const QSize &))); + + connect (m_scrollView, SIGNAL (statusMessageChanged (const QString &)), + this, SLOT (slotDocResizeMessageChanged (const QString &))); + + connect (m_scrollView, SIGNAL (contentsMoving (int, int)), + this, SLOT (slotScrollViewAboutToScroll ())); + setCentralWidget (m_scrollView); + m_scrollView->show (); +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\tTIME: m_scrollView = " << time.restart () << "msec" << endl; +#endif + + + // + // set initial pos/size of GUI + // + + setAutoSaveSettings (); + + // Put our non-XMLGUI toolbars in a sane place, the first time around + // (have to do this _after_ setAutoSaveSettings as that applies default + // (i.e. random) settings to the toolbars) + if (m_configFirstTime) + { + #if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\tfirstTime: positioning toolbars" << endl; + #endif + + m_toolToolBar->setBarPos (KToolBar::Left); + m_colorToolBar->setBarPos (KToolBar::Bottom); + + KConfigGroupSaver cfgGroupSaver (kapp->config (), kpSettingsGroupGeneral); + KConfigBase *cfg = cfgGroupSaver.config (); + + cfg->writeEntry (kpSettingFirstTime, m_configFirstTime = false); + cfg->sync (); + } + +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\tall done in " << totalTime.elapsed () << "msec" << endl; +#endif +} + + +// private virtual [base KMainWindow] +void kpMainWindow::readProperties (KConfig *cfg) +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow<" << this << ">::readProperties()" << endl; +#endif + + // No document at all? + if (!cfg->hasKey (kpSessionSettingDocumentUrl)) + { + #if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\tno url - no document" << endl; + #endif + setDocument (0); + } + // Have a document. + else + { + const KURL url (cfg->readEntry (kpSessionSettingDocumentUrl)); + #if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\turl=" << url << endl; + #endif + + const QSize notFromURLDocSize = + cfg->readSizeEntry (kpSessionSettingNotFromUrlDocumentSize); + + // 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 + kdDebug () << "\tnot from url; doc size=" << notFromURLDocSize << endl; + #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 (KConfig *cfg) +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow<" << this << ">::saveProperties()" << endl; +#endif + + // No document at all? + if (!m_document) + { + #if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\tno url - no document" << endl; + #endif + } + // Have a document. + else + { + // Save URL in all cases: + // + // a) m_document->isFromURL() + // b) !m_document->isFromURL() [save size in this case] + // i) No URL + // ii) URL (from "kolourpaint doesnotexist.png") + + const KURL url = m_document->url (); + #if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\turl=" << url << endl; + #endif + cfg->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 (hence the + // "false" arg to isFromURL()). 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 (!m_document->isFromURL (false/*don't bother checking exists*/)) + { + // 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 (m_document->constructorWidth (), + m_document->constructorHeight ()); + #if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\tnot from url; doc size=" << docSize << endl; + #endif + cfg->writeEntry (kpSessionSettingNotFromUrlDocumentSize, docSize); + } + + + // Local session save i.e. queryClose() was not called beforehand + // (see QApplication::saveState())? + #if 0 + if (m_document->isModified ()) + { + // TODO: Implement by saving the current image to a persistent file. + // We do this instead of saving/mutating the backing image file + // as no one expects a file save on a session save without a + // "do you want to save" dialog first. + // + // I don't think any KDE application implements local session saving. + // + // --- The below code does not compile but shows you want to do --- + + // Create unique name for the document in this main window. + const KURL tempURL = homeDir + + "kolourpaint session " + sessionID + + mainWindowPtrToString + ".png"; + // TODO: Use lossless PNG saving options. + kpDocumentSaveOptions pngSaveOptions; + + if (kpDocument::savePixmapToFile (m_document->pixmapWithSelection (), + tempURL, + pngSaveOptions, *m_document->metaInfo (), + false/*no overwrite prompt*/, + false/*no lossy prompt*/, + this)) + { + // readProperties() will still open kpSessionSettingDocumentUrl + // (as that's the expected URL) and will then add commands to: + // + // 1. Resize the document to the size of image at + // kpSessionSettingDocumentUnsavedContentsUrl, if the sizes + // differ. + // 2. Paste the kpSessionSettingDocumentUnsavedContentsUrl image + // (setting the main window's selection mode to opaque beforehand). + // + // It will then delete the file at + // kpSessionSettingDocumentUnsavedContentsUrl. + cfg->writeEntry (kpSessionSettingDocumentUnsavedContentsUrl, + tempURL.url ()); + } + else + { + // Not much we can do - we aren't allowed to throw up a dialog. + } + } + #endif + } +} + + +kpMainWindow::~kpMainWindow () +{ + m_isFullyConstructed = false; + + // delete document & views + setDocument (0); + + delete m_commandHistory; m_commandHistory = 0; + delete m_scrollView; m_scrollView = 0; + + delete d; d = 0; +} + + +// public +kpDocument *kpMainWindow::document () const +{ + return m_document; +} + +// public +kpViewManager *kpMainWindow::viewManager () const +{ + return m_viewManager; +} + +// public +kpColorToolBar *kpMainWindow::colorToolBar () const +{ + return m_colorToolBar; +} + +// public +kpToolToolBar *kpMainWindow::toolToolBar () const +{ + return m_toolToolBar; +} + +// public +kpCommandHistory *kpMainWindow::commandHistory () const +{ + return m_commandHistory; +} + + +// private +void kpMainWindow::setupActions () +{ + setupFileMenuActions (); + setupEditMenuActions (); + setupViewMenuActions (); + setupImageMenuActions (); + setupSettingsMenuActions (); + setupHelpMenuActions (); + + setupTextToolBarActions (); + setupToolActions (); +} + +// private +void kpMainWindow::enableDocumentActions (bool enable) +{ + enableFileMenuDocumentActions (enable); + enableEditMenuDocumentActions (enable); + enableViewMenuDocumentActions (enable); + enableImageMenuDocumentActions (enable); + enableSettingsMenuDocumentActions (enable); + enableHelpMenuDocumentActions (enable); +} + + +// public +bool kpMainWindow::actionsSingleKeyTriggersEnabled () const +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::actionsSingleKeyTriggersEnabled()" << endl; + QTime timer; timer.start (); +#endif + + if (m_toolToolBar) + { + #if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\ttime=" << timer.restart () << endl; + #endif + return m_toolToolBar->toolsSingleKeyTriggersEnabled (); + } + + return (m_actionPrevToolOptionGroup1->singleKeyTriggersEnabled () || + m_actionNextToolOptionGroup1->singleKeyTriggersEnabled () || + m_actionPrevToolOptionGroup2->singleKeyTriggersEnabled () || + m_actionNextToolOptionGroup2->singleKeyTriggersEnabled ()); +} + +// public +void kpMainWindow::enableActionsSingleKeyTriggers (bool enable) +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::enableActionsSingleKeyTriggers(" + << enable << ")" << endl; + QTime timer; timer.start (); +#endif + + if (m_toolToolBar) + m_toolToolBar->enableToolsSingleKeyTriggers (enable); + + m_actionPrevToolOptionGroup1->enableSingleKeyTriggers (enable); + m_actionNextToolOptionGroup1->enableSingleKeyTriggers (enable); + m_actionPrevToolOptionGroup2->enableSingleKeyTriggers (enable); + m_actionNextToolOptionGroup2->enableSingleKeyTriggers (enable); + +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\ttime=" << timer.restart () << endl; +#endif +} + + +// private +void kpMainWindow::setDocument (kpDocument *newDoc) +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::setDocument (" << newDoc << ")" << endl; +#endif + + // is it a close operation? + if (!newDoc) + { + #if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\tdisabling actions" << endl; + #endif + + // sync with the bit marked "sync" below + + if (m_colorToolBar) + m_colorToolBar->setEnabled (false); + else + { + kdError () << "kpMainWindow::setDocument() without colorToolBar" + << endl; + } + + 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); + } + +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\tdestroying views" << endl; +#endif + + delete m_mainView; m_mainView = 0; + slotDestroyThumbnail (); + +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\tdestroying viewManager" << endl; +#endif + + // viewManager will die and so will the selection + m_actionCopy->setEnabled (false); + m_actionCut->setEnabled (false); + m_actionDelete->setEnabled (false); + m_actionDeselect->setEnabled (false); + m_actionCopyToFile->setEnabled (false); + + delete m_viewManager; m_viewManager = 0; + +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\tdestroying document" << endl; + kdDebug () << "\t\tm_document=" << m_document << endl; +#endif + // destroy current document + delete m_document; + m_document = newDoc; + + + if (!m_lastCopyToURL.isEmpty ()) + m_lastCopyToURL.setFileName (QString::null); + m_copyToFirstTime = true; + + if (!m_lastExportURL.isEmpty ()) + m_lastExportURL.setFileName (QString::null); + m_exportFirstTime = true; + + + // not a close operation? + if (m_document) + { + if (m_document->mainWindow () != this) + { + #if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\tchanging doc's mainWindow from " + << m_document->mainWindow () + << " to this=" + << this + << endl; + #endif + m_document->setMainWindow (this); + } + + #if DEBUG_KP_MAIN_WINDOW + kdDebug () <<"\tcreating viewManager" << endl; + #endif + m_viewManager = new kpViewManager (this); + + #if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\tcreating views" << endl; + #endif + m_mainView = new kpZoomedView (m_document, m_toolToolBar, m_viewManager, + 0/*buddyView*/, + m_scrollView, + m_scrollView->viewport (), "mainView"); + if (m_scrollView) + { + m_scrollView->addChild (m_mainView); + } + else + kdError () << "kpMainWindow::setDocument() without scrollView" << endl; + m_viewManager->registerView (m_mainView); + m_mainView->show (); + + #if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\thooking up document signals" << endl; + #endif + + // Copy/Cut/Deselect/Delete + connect (m_document, SIGNAL (selectionEnabled (bool)), + m_actionCut, SLOT (setEnabled (bool))); + connect (m_document, SIGNAL (selectionEnabled (bool)), + m_actionCopy, SLOT (setEnabled (bool))); + connect (m_document, SIGNAL (selectionEnabled (bool)), + m_actionDelete, SLOT (setEnabled (bool))); + connect (m_document, SIGNAL (selectionEnabled (bool)), + m_actionDeselect, SLOT (setEnabled (bool))); + connect (m_document, SIGNAL (selectionEnabled (bool)), + m_actionCopyToFile, SLOT (setEnabled (bool))); + + // this code won't actually enable any actions at this stage + // (fresh document) but better safe than sorry + m_actionCopy->setEnabled (m_document->selection ()); + m_actionCut->setEnabled (m_document->selection ()); + m_actionDeselect->setEnabled (m_document->selection ()); + m_actionDelete->setEnabled (m_document->selection ()); + m_actionCopyToFile->setEnabled (m_document->selection ()); + + connect (m_document, SIGNAL (selectionEnabled (bool)), + this, SLOT (slotImageMenuUpdateDueToSelection ())); + connect (m_document, SIGNAL (selectionIsTextChanged (bool)), + this, SLOT (slotImageMenuUpdateDueToSelection ())); + + // Status bar + connect (m_document, SIGNAL (documentOpened ()), + this, SLOT (recalculateStatusBar ())); + + connect (m_document, SIGNAL (sizeChanged (const QSize &)), + this, SLOT (setStatusBarDocSize (const QSize &))); + + // Caption (url, modified) + connect (m_document, SIGNAL (documentModified ()), + this, SLOT (slotUpdateCaption ())); + connect (m_document, SIGNAL (documentOpened ()), + this, SLOT (slotUpdateCaption ())); + connect (m_document, SIGNAL (documentSaved ()), + this, SLOT (slotUpdateCaption ())); + + // File/Reload action only available with non-empty URL + connect (m_document, SIGNAL (documentSaved ()), + this, SLOT (slotEnableReload ())); + + connect (m_document, SIGNAL (documentSaved ()), + this, SLOT (slotEnableSettingsShowPath ())); + + // Command history + if (m_commandHistory) + { + connect (m_commandHistory, SIGNAL (documentRestored ()), + this, SLOT (slotDocumentRestored ())); // caption "!modified" + connect (m_document, SIGNAL (documentSaved ()), + m_commandHistory, SLOT (documentSaved ())); + } + else + { + kdError () << "kpMainWindow::setDocument() without commandHistory" + << endl; + } + + // Sync document -> views + connect (m_document, SIGNAL (contentsChanged (const QRect &)), + m_viewManager, SLOT (updateViews (const QRect &))); + connect (m_document, SIGNAL (sizeChanged (int, int)), + m_viewManager, SLOT (adjustViewsToEnvironment ())); + + #if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\tenabling actions" << endl; + #endif + + // sync with the bit marked "sync" above + + if (m_colorToolBar) + m_colorToolBar->setEnabled (true); + else + { + kdError () << "kpMainWindow::setDocument() without colorToolBar" + << endl; + } + + + // Hide the text toolbar - it will be shown by kpToolText::begin() + enableTextToolBarActions (false); + + enableToolsDocumentActions (true); + + enableDocumentActions (true); + + // TODO: The thumbnail auto zoom doesn't work because it thinks its + // width == 1 when !this->isShown(). So for consistency, + // never create the thumbnail. + #if 0 + if (m_configThumbnailShown) + { + if (isShown ()) + { + #if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\tcreating thumbnail immediately" << endl; + #endif + slotCreateThumbnail (); + } + // this' geometry is weird ATM + else + { + #if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\tcreating thumbnail LATER" << endl; + #endif + QTimer::singleShot (0, this, SLOT (slotCreateThumbnail ())); + } + } + #endif + } + +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\tupdating mainWindow elements" << endl; +#endif + + slotImageMenuUpdateDueToSelection (); + recalculateStatusBar (); + slotUpdateCaption (); // Untitled to start with + slotEnableReload (); + slotEnableSettingsShowPath (); + + if (m_commandHistory) + m_commandHistory->clear (); + +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\tdocument and views ready to go!" << endl; +#endif +} + + +// private virtual [base KMainWindow] +bool kpMainWindow::queryClose () +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::queryClose()" << endl; +#endif + if (toolHasBegunShape ()) + tool ()->endShapeInternal (); + + if (!m_document || !m_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?") + .arg (m_document->prettyFilename ()), + QString::null/*caption*/, + KStdGuiItem::save (), KStdGuiItem::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 QWidget] +void kpMainWindow::dragEnterEvent (QDragEnterEvent *e) +{ + e->accept (kpSelectionDrag::canDecode (e) || + KURLDrag::canDecode (e) || + QTextDrag::canDecode (e)); +} + +// private virtual [base QWidget] +void kpMainWindow::dropEvent (QDropEvent *e) +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::dropEvent" << e->pos () << endl; +#endif + + kpSelection sel; + KURL::List urls; + QString text; + + if (kpSelectionDrag::decode (e, sel/*ref*/, pasteWarnAboutLossInfo ())) + { + sel.setTransparency (selectionTransparency ()); + // TODO: drop at point like with QTextDrag below? + paste (sel); + } + else if (KURLDrag::decode (e, urls/*ref*/)) + { + for (KURL::List::ConstIterator it = urls.begin (); it != urls.end (); it++) + { + open (*it); + } + } + else if (QTextDrag::decode (e, text/*ref*/)) + { + QPoint selTopLeft = KP_INVALID_POINT; + const QPoint globalPos = QWidget::mapToGlobal (e->pos ()); + #if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\tpos toGlobal=" << globalPos << endl; + #endif + + kpView *view = 0; + + if (m_viewManager) + { + view = m_viewManager->viewUnderCursor (); + #if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\t\tviewUnderCursor=" << view << endl; + #endif + if (!view) + { + // HACK: see kpViewManager::setViewUnderCursor() to see why + // it's not reliable + #if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\t\tattempting to discover view" << endl; + + if (m_mainView && m_scrollView) + { + kdDebug () << "\t\t\tmainView->globalRect=" + << kpWidgetMapper::toGlobal (m_mainView, m_mainView->rect ()) + << " scrollView->globalRect=" + << kpWidgetMapper::toGlobal (m_scrollView, + QRect (0, 0, + m_scrollView->visibleWidth (), + m_scrollView->visibleHeight ())) + << endl; + } + #endif + if (m_thumbnailView && + kpWidgetMapper::toGlobal (m_thumbnailView, m_thumbnailView->rect ()) + .contains (globalPos)) + { + // TODO: Code will never get executed. + // Thumbnail doesn't accept drops. + view = m_thumbnailView; + } + else if (m_mainView && + kpWidgetMapper::toGlobal (m_mainView, m_mainView->rect ()) + .contains (globalPos) && + m_scrollView && + kpWidgetMapper::toGlobal (m_scrollView, + QRect (0, 0, + m_scrollView->visibleWidth (), + m_scrollView->visibleHeight ())) + .contains (globalPos)) + { + view = m_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 (m_document && m_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::slotScrollViewAboutToScroll () +{ +#if DEBUG_KP_MAIN_WINDOW && 0 + kdDebug () << "kpMainWindow::slotScrollViewAboutToScroll() tool=" + << tool () << " viewManager=" << viewManager () << endl; + if (viewManager ()) + { + kdDebug () << "\tfastUpdates=" << viewManager ()->fastUpdates () + << " queueUpdates=" << viewManager ()->queueUpdates () + << endl; + } + else + { + // We're getting a late signal from the scrollview (thanks to + // a timer inside the QScrollView). By now, setDocument() has + // already killed the document(), tool() and viewManager(). + } +#endif + + QTimer::singleShot (0, this, SLOT (slotScrollViewAfterScroll ())); +} + +// private slot +void kpMainWindow::slotScrollViewAfterScroll () +{ +#if DEBUG_KP_MAIN_WINDOW && 0 + kdDebug () << "kpMainWindow::slotScrollViewAfterScroll() tool=" + << tool () << endl; +#endif + + if (tool ()) + { + tool ()->somethingBelowTheCursorChanged (); + } +} + + +// private virtual [base QWidget] +void kpMainWindow::moveEvent (QMoveEvent * /*e*/) +{ + if (m_thumbnail) + { + // Disabled because it lags too far behind the mainWindow + // m_thumbnail->move (m_thumbnail->pos () + (e->pos () - e->oldPos ())); + + notifyThumbnailGeometryChanged (); + } +} + + +// private slot +void kpMainWindow::slotUpdateCaption () +{ + if (m_document) + { + setCaption (m_configShowPath ? m_document->prettyURL () + : m_document->prettyFilename (), + m_document->isModified ()); + } + else + { + setCaption (QString::null, false); + } +} + +// private slot +void kpMainWindow::slotDocumentRestored () +{ + if (m_document) + m_document->setModified (false); + slotUpdateCaption (); +} + + +#include <kpmainwindow.moc> diff --git a/kolourpaint/kpmainwindow.h b/kolourpaint/kpmainwindow.h new file mode 100644 index 00000000..f5514848 --- /dev/null +++ b/kolourpaint/kpmainwindow.h @@ -0,0 +1,739 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 + + +#define DEBUG_KP_MAIN_WINDOW 0 + +#include <qpoint.h> +#include <qptrlist.h> +#include <qsize.h> +#include <qvaluevector.h> + +#include <kmainwindow.h> +#include <kurl.h> + +#include <kpdefs.h> +#include <kpdocumentsaveoptions.h> +#include <kppixmapfx.h> + + +class QPainter; +class QPoint; +class QPopupMenu; +class QRect; +class QSize; +class QStringList; + +class KAction; +class KFontAction; +class KFontSizeAction; +class KSelectAction; +class KToggleAction; +class KToolBar; +class KPrinter; +class KRecentFilesAction; +class KScanDialog; +class KToggleFullScreenAction; + +class kpColor; +class kpColorToolBar; +class kpCommand; +class kpCommandHistory; +class kpDocument; +class kpDocumentMetaInfo; +class kpDocumentSaveOptions; +class kpViewManager; +class kpViewScrollableContainer; +class kpSelection; +class kpSelectionTransparency; +class kpSingleKeyTriggersAction; +class kpSqueezedTextLabel; +class kpTextStyle; +class kpThumbnail; +class kpThumbnailView; +class kpTool; +class kpToolText; +class kpToolToolBar; +class kpZoomedView; + + +class kpMainWindow : public KMainWindow +{ +Q_OBJECT + +public: + // Opens a new window with a blank document. + kpMainWindow (); + + // Opens a new window with the document specified by <url> + // or creates a blank document if <url> could not be opened. + kpMainWindow (const KURL &url); + + // Opens a new window with the document <newDoc> + // (<newDoc> can be 0 although this would result in a new + // window without a document at all). + kpMainWindow (kpDocument *newDoc); + +public: + double configColorSimilarity () const; + void configSetColorSimilarity (double val); + +private: + bool m_configFirstTime; + bool m_configShowGrid; + bool m_configShowPath; + double m_configColorSimilarity; + + bool m_configThumbnailShown; + QRect m_configThumbnailGeometry; + bool m_configZoomedThumbnail; + + 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) + virtual void readProperties (KConfig *cfg); + // (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) + virtual void saveProperties (KConfig *cfg); + +public: + ~kpMainWindow (); + +private: + bool m_isFullyConstructed; + +public: + kpDocument *document () const; + kpViewManager *viewManager () const; + kpColorToolBar *colorToolBar () const; + kpToolToolBar *toolToolBar () const; + kpCommandHistory *commandHistory () const; + +private: + kpViewScrollableContainer *m_scrollView; + kpZoomedView *m_mainView; + kpThumbnail *m_thumbnail; + kpThumbnailView *m_thumbnailView; + kpDocument *m_document; + kpViewManager *m_viewManager; + kpColorToolBar *m_colorToolBar; + kpToolToolBar *m_toolToolBar; + kpCommandHistory *m_commandHistory; + +private: + void setupActions (); + void enableDocumentActions (bool enable = true); + +public: + bool actionsSingleKeyTriggersEnabled () const; + void enableActionsSingleKeyTriggers (bool enable = true); + +private: + void setDocument (kpDocument *newDoc); + + virtual bool queryClose (); + + virtual void dragEnterEvent (QDragEnterEvent *e); + virtual void dropEvent (QDropEvent *e); + +private slots: + void slotScrollViewAboutToScroll (); + void slotScrollViewAfterScroll (); + +private: + virtual void moveEvent (QMoveEvent *e); + +private slots: + void slotUpdateCaption (); + void slotDocumentRestored (); + + + /* + * Tools + */ + +private: + void setupToolActions (); + void createToolBox (); + void enableToolsDocumentActions (bool enable = true); + +private slots: + void updateToolOptionPrevNextActionsEnabled (); + +private: + kpTool *m_toolAirSpray, *m_toolBrush, *m_toolColorPicker, + *m_toolColorWasher, *m_toolCurve, *m_toolEllipse, + *m_toolEllipticalSelection, *m_toolEraser, + *m_toolFloodFill, *m_toolFreeFormSelection, + *m_toolLine, *m_toolPen, *m_toolPolygon, + *m_toolPolyline, *m_toolRectangle, *m_toolRectSelection, + *m_toolRoundedRectangle; + kpToolText *m_toolText; + + QPtrList <kpTool> m_tools; + int m_lastToolNumber; + + bool m_toolActionsEnabled; + kpSingleKeyTriggersAction *m_actionPrevToolOptionGroup1, + *m_actionNextToolOptionGroup1, + *m_actionPrevToolOptionGroup2, + *m_actionNextToolOptionGroup2; + + int m_settingSelectionTransparency; + + int m_docResizeWidth, m_docResizeHeight; + bool m_docResizeToBeCompleted; + +public: + kpTool *tool () const; + bool toolHasBegunShape () const; + bool toolIsASelectionTool (bool includingTextTool = true) const; + bool toolIsTextTool () const; + + kpSelectionTransparency selectionTransparency () const; + // The drawing background color is set to <transparency>.transparentColor() + // if the <transparency> is in Transparent mode or if <forceColorChange> + // is true (not the default). [x] + // + // If <transparency> is in Opaque mode and <forceColorChange> 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 <forceColorChange> to true is in + // kpToolSelectionTransparencyCommand 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 kpToolSelectionTransparencyCommand that switches to + // Transparent mode will automatically set the background + // color due to the first part of [x] anyway. + // + // The other fields of <transparency> are copied into the main window + // as expected. + void setSelectionTransparency (const kpSelectionTransparency &transparency, + bool forceColorChange = false); + int settingSelectionTransparency () 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 (); + +public slots: + void slotToolAirSpray (); + void slotToolBrush (); + void slotToolColorPicker (); + void slotToolColorWasher (); + void slotToolCurve (); + void slotToolEllipse (); + void slotToolEllipticalSelection (); + void slotToolEraser (); + void slotToolFloodFill (); + void slotToolFreeFormSelection (); + void slotToolLine (); + void slotToolPen (); + void slotToolPolygon (); + void slotToolPolyline (); + void slotToolRectangle (); + void slotToolRectSelection (); + void slotToolRoundedRectangle (); + void slotToolText (); + + + /* + * File Menu + */ + +private: + void setupFileMenuActions (); + void enableFileMenuDocumentActions (bool enable = true); + + KAction *m_actionNew, *m_actionOpen; + KRecentFilesAction *m_actionOpenRecent; + KAction *m_actionScan, *m_actionSave, *m_actionSaveAs, *m_actionExport, + *m_actionReload, + *m_actionPrint, *m_actionPrintPreview, + *m_actionMail, + *m_actionSetAsWallpaperTiled, *m_actionSetAsWallpaperCentered, + *m_actionClose, *m_actionQuit; + + KScanDialog *m_scanDialog; + + KURL m_lastExportURL; + kpDocumentSaveOptions m_lastExportSaveOptions; + bool m_exportFirstTime; + +private: + void addRecentURL (const KURL &url); + +private slots: + void slotNew (); + +private: + QSize defaultDocSize () const; + void saveDefaultDocSize (const QSize &size); + +private: + bool shouldOpenInNewWindow () const; + void setDocumentChoosingWindow (kpDocument *doc); + +private: + kpDocument *openInternal (const KURL &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 KURL &url, bool newDocSameNameIfNotExist = false); + + KURL::List askForOpenURLs (const QString &caption, + const QString &startURL, + bool allowMultipleURLs = true); + +private slots: + void slotOpen (); + void slotOpenRecent (const KURL &url); + + void slotScan (); + void slotScanned (const QImage &image, int); + + bool save (bool localOnly = false); + bool slotSave (); + +private: + KURL askForSaveURL (const QString &caption, + const QString &startURL, + const QPixmap &pixmapToBeSaved, + const kpDocumentSaveOptions &startSaveOptions, + const kpDocumentMetaInfo &docMetaInfo, + const QString &forcedSaveOptionsGroup, + bool localOnly, + kpDocumentSaveOptions *chosenSaveOptions, + bool isSavingForFirstTime, + bool *allowOverwritePrompt, + bool *allowLossyPrompt); + +private slots: + bool saveAs (bool localOnly = false); + bool slotSaveAs (); + + bool slotExport (); + + void slotEnableReload (); + bool slotReload (); + +private: + void sendFilenameToPrinter (KPrinter *printer); + void sendPixmapToPrinter (KPrinter *printer, bool showPrinterSetupDialog); + +private slots: + void slotPrint (); + void slotPrintPreview (); + + void slotMail (); + +private: + void setAsWallpaper (bool centered); +private slots: + void slotSetAsWallpaperCentered (); + void slotSetAsWallpaperTiled (); + + void slotClose (); + void slotQuit (); + + + /* + * Edit Menu + */ + +private: + kpPixmapFX::WarnAboutLossInfo pasteWarnAboutLossInfo (); + void setupEditMenuActions (); + void enableEditMenuDocumentActions (bool enable = true); + + bool m_editMenuDocumentActionsEnabled; + + KAction *m_actionUndo, *m_actionRedo, + *m_actionCut, *m_actionCopy, + *m_actionPaste, *m_actionPasteInNewWindow, + *m_actionDelete, + *m_actionSelectAll, *m_actionDeselect, + *m_actionCopyToFile, *m_actionPasteFromFile; + + KURL m_lastPasteFromURL; + + KURL m_lastCopyToURL; + kpDocumentSaveOptions m_lastCopyToSaveOptions; + bool m_copyToFirstTime; + +public: + QPopupMenu *selectionToolRMBMenu (); + +private slots: + void slotCut (); + void slotCopy (); + void slotEnablePaste (); +private: + QRect calcUsefulPasteRect (int pixmapWidth, int pixmapHeight); + void paste (const kpSelection &sel, + bool forceTopLeft = false); +public: + // (<forceNewTextSelection> is ignored if <text> 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 <point> 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: + bool m_viewMenuDocumentActionsEnabled; + + void setupViewMenuActions (); + bool viewMenuDocumentActionsEnabled () const; + void enableViewMenuDocumentActions (bool enable = true); + void actionShowGridUpdate (); + + KAction *m_actionFullScreenBIC, + *m_actionActualSize, + *m_actionFitToPage, *m_actionFitToWidth, *m_actionFitToHeight, + *m_actionZoomIn, *m_actionZoomOut; + KSelectAction *m_actionZoom; + KToggleAction *m_actionShowGrid, + *m_actionShowThumbnail, *m_actionZoomedThumbnail; + + QValueVector <int> m_zoomList; + +private: + void sendZoomListToActionZoom (); + int zoomLevelFromString (const QString &string); + QString zoomLevelToString (int zoomLevel); + void zoomTo (int zoomLevel, bool centerUnderCursor = false); + +private slots: + void finishZoomTo (); + +private 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 (); + + void slotShowGridToggled (); +private: + void updateMainViewGrid (); + +private: + QRect mapToGlobal (const QRect &rect) const; + QRect mapFromGlobal (const QRect &rect) const; + +private slots: + void slotDestroyThumbnailIfNotVisible (bool tnIsVisible); + void slotDestroyThumbnail (); + void slotDestroyThumbnailInitatedByUser (); + void slotCreateThumbnail (); + +private: + QTimer *m_thumbnailSaveConfigTimer; + +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: + bool isSelectionActive () const; + bool isTextSelection () const; + + QString autoCropText () const; + + void setupImageMenuActions (); + void enableImageMenuDocumentActions (bool enable = true); + + bool m_imageMenuDocumentActionsEnabled; + + KAction *m_actionResizeScale, + *m_actionCrop, *m_actionAutoCrop, + *m_actionFlip, *m_actionRotate, *m_actionSkew, + *m_actionConvertToBlackAndWhite, *m_actionConvertToGrayscale, + *m_actionMoreEffects, + *m_actionInvertColors, *m_actionClear; + +private slots: + void slotImageMenuUpdateDueToSelection (); + +public: + kpColor backgroundColor (bool ofSelection = false) const; + void addImageOrSelectionCommand (kpCommand *cmd, + bool addSelCreateCmdIfSelAvail = true, + bool addSelPullCmdIfSelAvail = true); + +private slots: + void slotResizeScale (); +public slots: + void slotCrop (); +private slots: + void slotAutoCrop (); + void slotFlip (); + void slotRotate (); + void slotSkew (); + void slotConvertToBlackAndWhite (); + void slotConvertToGrayscale (); + void slotInvertColors (); + void slotClear (); + void slotMoreEffects (); + + + /* + * Settings Menu + */ + +private: + void setupSettingsMenuActions (); + void enableSettingsMenuDocumentActions (bool enable = true); + + KToggleAction *m_actionShowPath; + KAction *m_actionKeyBindings, *m_actionConfigureToolbars, *m_actionConfigure; + KToggleFullScreenAction *m_actionFullScreen; + +private slots: + void slotFullScreen (); + + void slotEnableSettingsShowPath (); + void slotShowPathToggled (); + + void slotKeyBindings (); + + void slotConfigureToolBars (); + void slotNewToolBarConfig (); + + void slotConfigure (); + + + /* + * Status Bar + */ + +private: + bool m_statusBarCreated; + kpSqueezedTextLabel *m_statusBarMessageLabel; + + bool m_statusBarShapeLastPointsInitialised; + QPoint m_statusBarShapeLastStartPoint, m_statusBarShapeLastEndPoint; + bool m_statusBarShapeLastSizeInitialised; + QSize m_statusBarShapeLastSize; + + enum + { + StatusBarItemMessage, + StatusBarItemShapePoints, + StatusBarItemShapeSize, + StatusBarItemDocSize, + StatusBarItemDocDepth, + StatusBarItemZoom + }; + + void addPermanentStatusBarItem (int id, int maxTextLen); + void createStatusBar (); + +private slots: + void setStatusBarMessage (const QString &message = QString::null); + 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 setStatusBarDocDepth (int depth = 0); + 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: + KFontAction *m_actionTextFontFamily; + KFontSizeAction *m_actionTextFontSize; + KToggleAction *m_actionTextBold, *m_actionTextItalic, + *m_actionTextUnderline, *m_actionTextStrikeThru; + + int m_settingTextStyle; + QString m_textOldFontFamily; + int m_textOldFontSize; + + + /* + * Help Menu + */ +private: + void setupHelpMenuActions (); + void enableHelpMenuDocumentActions (bool enable = true); + +private slots: + void slotHelpTakingScreenshots (); + void slotHelpTakingScreenshotsFollowLink (const QString &link); + + +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 kpMainWindowPrivate *d; +}; + + +#endif // KP_MAIN_WINDOW_H diff --git a/kolourpaint/kpmainwindow_edit.cpp b/kolourpaint/kpmainwindow_edit.cpp new file mode 100644 index 00000000..3cf9b4f6 --- /dev/null +++ b/kolourpaint/kpmainwindow_edit.cpp @@ -0,0 +1,1069 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <qapplication.h> +#include <qclipboard.h> +#include <qdatetime.h> +#include <qfontmetrics.h> +#include <qimage.h> +#include <qpixmap.h> +#include <qvaluevector.h> + +#include <kaction.h> +#include <kdebug.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <kstdaction.h> + +#include <kpcommandhistory.h> +#include <kpdocument.h> +#include <kpdocumentmetainfo.h> +#include <kpdocumentsaveoptions.h> +#include <kppixmapfx.h> +#include <kpselection.h> +#include <kpselectiondrag.h> +#include <kpselectiontransparency.h> +#include <kptool.h> +#include <kptoolcrop.h> +#include <kptoolresizescale.h> +#include <kptoolselection.h> +#include <kptooltext.h> +#include <kpviewmanager.h> +#include <kpviewscrollablecontainer.h> +#include <kpzoomedview.h> + + +// private +kpPixmapFX::WarnAboutLossInfo kpMainWindow::pasteWarnAboutLossInfo () +{ + return kpPixmapFX::WarnAboutLossInfo ( + i18n ("The image to be pasted" + " may have more colors than the current screen mode." + " In order to display it, some colors may be changed." + " Try increasing your screen depth to at least %1bpp." + + "\nIt also" + + " contains translucency which is not fully" + " supported. The translucency data will be" + " approximated with a 1-bit transparency mask."), + i18n ("The image to be pasted" + " may have more colors than the current screen mode." + " In order to display it, some colors may be changed." + " Try increasing your screen depth to at least %1bpp."), + i18n ("The image to be pasted" + " contains translucency which is not fully" + " supported. The translucency data will be" + " approximated with a 1-bit transparency mask."), + "paste", + this); +} + + +// private +void kpMainWindow::setupEditMenuActions () +{ + KActionCollection *ac = actionCollection (); + + + // Undo/Redo + // CONFIG: need GUI + m_commandHistory = new kpCommandHistory (true/*read config*/, this); + + if (m_configFirstTime) + { + // (so that cfg-file-editing user can modify in the meantime) + m_commandHistory->writeConfig (); + } + + + m_actionCut = KStdAction::cut (this, SLOT (slotCut ()), ac); + m_actionCopy = KStdAction::copy (this, SLOT (slotCopy ()), ac); + m_actionPaste = KStdAction::paste (this, SLOT (slotPaste ()), ac); + m_actionPasteInNewWindow = new KAction (i18n ("Paste in &New Window"), + Qt::CTRL + Qt::SHIFT + Qt::Key_V, + this, SLOT (slotPasteInNewWindow ()), ac, "edit_paste_in_new_window"); + + //m_actionDelete = KStdAction::clear (this, SLOT (slotDelete ()), ac); + m_actionDelete = new KAction (i18n ("&Delete Selection"), 0, + this, SLOT (slotDelete ()), ac, "edit_clear"); + + m_actionSelectAll = KStdAction::selectAll (this, SLOT (slotSelectAll ()), ac); + m_actionDeselect = KStdAction::deselect (this, SLOT (slotDeselect ()), ac); + + + m_actionCopyToFile = new KAction (i18n ("C&opy to File..."), 0, + this, SLOT (slotCopyToFile ()), ac, "edit_copy_to_file"); + m_actionPasteFromFile = new KAction (i18n ("Paste &From File..."), 0, + this, SLOT (slotPasteFromFile ()), ac, "edit_paste_from_file"); + + + m_editMenuDocumentActionsEnabled = false; + enableEditMenuDocumentActions (false); + + // Paste should always be enabled, as long as there is something paste + // (independent of whether we have a document or not) + connect (QApplication::clipboard (), SIGNAL (dataChanged ()), + this, SLOT (slotEnablePaste ())); + slotEnablePaste (); +} + +// private +void kpMainWindow::enableEditMenuDocumentActions (bool enable) +{ + // m_actionCut + // m_actionCopy + // m_actionPaste + // m_actionPasteInNewWindow + + // m_actionDelete + + m_actionSelectAll->setEnabled (enable); + // m_actionDeselect + + m_editMenuDocumentActionsEnabled = enable; + + // m_actionCopyToFile + // Unlike m_actionPaste, we disable this if there is no document. + // This is because "File / Open" would do the same thing, if there is + // no document. + m_actionPasteFromFile->setEnabled (enable); +} + + +// public +QPopupMenu *kpMainWindow::selectionToolRMBMenu () +{ + return (QPopupMenu *) guiFactory ()->container ("selectionToolRMBMenu", this); +} + + +// private slot +void kpMainWindow::slotCut () +{ +#if DEBUG_KP_MAIN_WINDOW && 1 + kdDebug () << "kpMainWindow::slotCut() CALLED" << endl; +#endif + + if (!m_document || !m_document->selection ()) + { + kdError () << "kpMainWindow::slotCut () doc=" << m_document + << " sel=" << (m_document ? m_document->selection () : 0) + << endl; + return; + } + + + QApplication::setOverrideCursor (Qt::waitCursor); + + if (toolHasBegunShape ()) + tool ()->endShapeInternal (); + + slotCopy (); + slotDelete (); + + QApplication::restoreOverrideCursor (); + +} + +// private slot +void kpMainWindow::slotCopy () +{ +#if DEBUG_KP_MAIN_WINDOW && 1 + kdDebug () << "kpMainWindow::slotCopy() CALLED" << endl; +#endif + + if (!m_document || !m_document->selection ()) + { + kdError () << "kpMainWindow::slotCopy () doc=" << m_document + << " sel=" << (m_document ? m_document->selection () : 0) + << endl; + return; + } + + + QApplication::setOverrideCursor (Qt::waitCursor); + + if (toolHasBegunShape ()) + tool ()->endShapeInternal (); + + kpSelection sel = *m_document->selection (); + // 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.setPixmap(). + sel.setTransparency (kpSelectionTransparency ()); + + if (sel.isText ()) + { + if (!sel.text ().isEmpty ()) + { + QApplication::clipboard ()->setData (new QTextDrag (sel.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 QTextDrag 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 ()->setData (new QTextDrag (sel.text ()), + QClipboard::Selection); + } + } + else + { + QPixmap rawPixmap; + + if (sel.pixmap ()) + rawPixmap = *sel.pixmap (); + else + rawPixmap = m_document->getSelectedPixmap (); + + // Some apps, such as OpenOffice.org 2.0.4, ignore the image mask + // when pasting. For transparent pixels, the uninitialized RGB + // values are used. Fix this by initializing those values to a + // neutral color -- white. + // + // Strangely enough, OpenOffice.org respects the mask when inserting + // an image from a file, as opposed to pasting one from the clipboard. + sel.setPixmap ( + kpPixmapFX::pixmapWithDefinedTransparentPixels ( + rawPixmap, + Qt::white)); // CONFIG + + QApplication::clipboard ()->setData (new kpSelectionDrag (sel), + QClipboard::Clipboard); + } + + QApplication::restoreOverrideCursor (); +} + + +static bool HasSomethingToPaste (kpMainWindow *mw) +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow(" << mw->name () << "):HasSomethingToPaste()" << endl; + QTime timer; + timer.start (); +#else + (void) mw; +#endif + + bool hasSomething = false; + + QMimeSource *ms = QApplication::clipboard ()->data (QClipboard::Clipboard); + if (ms) + { + // It's faster to test for QTextDrag::canDecode() first due to the + // lazy evaluation of the '||' operator. + hasSomething = (QTextDrag::canDecode (ms) || + kpSelectionDrag::canDecode (ms)); + #if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\t" << mw->name () << "***canDecode=" << timer.restart () << endl; + for (int i = 0; ; i++) + { + const char *fmt = ms->format (i); + if (!fmt) + break; + + kdDebug () << "\t'" << fmt << "'" << endl; + } + #endif + } + + return hasSomething; +} + +// HACK: SYNC: Non-Qt apps do not cause QApplication::clipboard() to +// emit dataChanged(). We don't want to have our paste +// action disabled when we can actually paste something. +// +// So we make sure the paste action is always enabled and +// before any paste, we check if there's actually something +// to paste (HasSomethingToPasteWithDialogIfNot ()). + +#if 1 // Hack code path + + +// Call before any paste only. +static bool HasSomethingToPasteWithDialogIfNot (kpMainWindow *mw) +{ + if (::HasSomethingToPaste (mw)) + return true; + + // STRING: Unfortunately, we are in a string freeze. +#if 1 + (void) mw; +#else + QApplication::setOverrideCursor (Qt::arrowCursor); + + KMessageBox::sorry (mw, + STRING_FREEZE_i18n ("<qt><p>There is nothing in the clipboard to paste.</p></qt>"), + STRING_FREEZE_i18n ("Cannot Paste")); + + QApplication::restoreOverrideCursor (); +#endif + + return false; +} + +// private slot +void kpMainWindow::slotEnablePaste () +{ + const bool shouldEnable = true; + m_actionPasteInNewWindow->setEnabled (shouldEnable); + m_actionPaste->setEnabled (shouldEnable); +} + + +#else // No hack + + +// Call before any paste only. +static bool HasSomethingToPasteWithDialogIfNot (kpMainWindow *) +{ + // We will not be called if there's nothing to paste, as the paste + // action would have been disabled. But do _not_ assert that + // (see the slotPaste() "data unexpectedly disappeared" KMessageBox). + return true; +} + +// private slot +void kpMainWindow::slotEnablePaste () +{ + const bool shouldEnable = ::HasSomethingToPaste (this); + m_actionPasteInNewWindow->setEnabled (shouldEnable); + m_actionPaste->setEnabled (shouldEnable); +} + + +#endif + + +// private +QRect kpMainWindow::calcUsefulPasteRect (int pixmapWidth, int pixmapHeight) +{ +#if DEBUG_KP_MAIN_WINDOW && 1 + kdDebug () << "kpMainWindow::calcUsefulPasteRect(" + << pixmapWidth << "," << pixmapHeight + << ")" + << endl; +#endif + if (!m_document) + { + kdError () << "kpMainWindow::calcUsefulPasteRect() without doc" << endl; + return QRect (); + } + + // TODO: 1st choice is to paste sel near but not overlapping last deselect point + + if (m_mainView && m_scrollView) + { + const QPoint viewTopLeft (m_scrollView->contentsX (), + m_scrollView->contentsY ()); + + const QPoint docTopLeft = m_mainView->transformViewToDoc (viewTopLeft); + + if ((docTopLeft.x () + pixmapWidth <= m_document->width () && + docTopLeft.y () + pixmapHeight <= m_document->height ()) || + pixmapWidth <= docTopLeft.x () || + pixmapHeight <= docTopLeft.y ()) + { + return QRect (docTopLeft.x (), docTopLeft.y (), + pixmapWidth, pixmapHeight); + } + } + + return QRect (0, 0, pixmapWidth, pixmapHeight); +} + +// private +void kpMainWindow::paste (const kpSelection &sel, bool forceTopLeft) +{ +#if DEBUG_KP_MAIN_WINDOW && 1 + kdDebug () << "kpMainWindow::paste(forceTopLeft=" << forceTopLeft << ")" + << endl; +#endif + + if (!sel.pixmap ()) + { + kdError () << "kpMainWindow::paste() with sel without pixmap" << endl; + return; + } + + QApplication::setOverrideCursor (Qt::waitCursor); + + if (toolHasBegunShape ()) + tool ()->endShapeInternal (); + + + // + // Make sure we've got a document (esp. with File/Close) + // + + if (!m_document) + { + kpDocument *newDoc = new kpDocument ( + sel.width (), sel.height (), this); + + // will also create viewManager + setDocument (newDoc); + } + + + // + // Paste as new selection + // + + kpSelection selInUsefulPos = sel; + if (!forceTopLeft) + selInUsefulPos.moveTo (calcUsefulPasteRect (sel.width (), sel.height ()).topLeft ()); + addDeselectFirstCommand (new kpToolSelectionCreateCommand ( + selInUsefulPos.isText () ? + i18n ("Text: Create Box") : + i18n ("Selection: Create"), + selInUsefulPos, + this)); + + +#if DEBUG_KP_MAIN_WINDOW && 1 + kdDebug () << "sel.size=" << QSize (sel.width (), sel.height ()) + << " document.size=" + << QSize (m_document->width (), m_document->height ()) + << endl; +#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 () > m_document->width () || + sel.height () > m_document->height ()) + { + m_commandHistory->addCommand ( + new kpToolResizeScaleCommand ( + false/*act on doc, not sel*/, + QMAX (sel.width (), m_document->width ()), + QMAX (sel.height (), m_document->height ()), + kpToolResizeScaleCommand::Resize, + this)); + } + + + QApplication::restoreOverrideCursor (); +} + +// public +void kpMainWindow::pasteText (const QString &text, + bool forceNewTextSelection, + const QPoint &newTextSelectionTopLeft) +{ +#if DEBUG_KP_MAIN_WINDOW && 1 + kdDebug () << "kpMainWindow::pasteText(" << text + << ",forceNewTextSelection=" << forceNewTextSelection + << ",newTextSelectionTopLeft=" << newTextSelectionTopLeft + << ")" << endl; +#endif + + if (text.isEmpty ()) + return; + + + // sync: restoreOverrideCursor() in all exit paths + QApplication::setOverrideCursor (Qt::waitCursor); + + if (toolHasBegunShape ()) + tool ()->endShapeInternal (); + + + QValueVector <QString> textLines (1, QString::null); + + for (int i = 0; i < (int) text.length (); i++) + { + if (text [i] == '\n') + textLines.push_back (QString::null); + else + textLines [textLines.size () - 1].append (text [i]); + } + + + if (!forceNewTextSelection && + m_document && m_document->selection () && + m_document->selection ()->isText () && + m_commandHistory && m_viewManager) + { + #if DEBUG_KP_MAIN_WINDOW && 1 + kdDebug () << "\treusing existing Text Selection" << endl; + #endif + + kpMacroCommand *macroCmd = new kpMacroCommand (i18n ("Text: Paste"), + this); + + for (int i = 0; i < (int) textLines.size (); i++) + { + if (i > 0) + { + macroCmd->addCommand ( + new kpToolTextEnterCommand ( + QString::null/*uninteresting child of macroCmd*/, + m_viewManager->textCursorRow (), + m_viewManager->textCursorCol (), + this)); + } + + macroCmd->addCommand ( + new kpToolTextInsertCommand ( + QString::null/*uninteresting child of macroCmd*/, + m_viewManager->textCursorRow (), + m_viewManager->textCursorCol (), + textLines [i], + this)); + } + + m_commandHistory->addCommand (macroCmd, false/*no exec*/); + } + else + { + #if DEBUG_KP_MAIN_WINDOW && 1 + kdDebug () << "\tcreating Text Selection" << endl; + #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; + for (QValueVector <QString>::const_iterator it = textLines.begin (); + it != textLines.end (); + it++) + { + const int w = fontMetrics.width (*it); + if (w > width) + width = w; + } + + + const int selWidth = QMAX (kpSelection::minimumWidthForTextStyle (ts), + width + kpSelection::textBorderSize () * 2); + const int selHeight = QMAX (kpSelection::minimumHeightForTextStyle (ts), + height + kpSelection::textBorderSize () * 2); + kpSelection sel (QRect (0, 0, selWidth, selHeight), + textLines, + ts); + + if (newTextSelectionTopLeft != KP_INVALID_POINT) + { + sel.moveTo (newTextSelectionTopLeft); + paste (sel, true/*force topLeft*/); + } + else + { + paste (sel); + } + } + + + QApplication::restoreOverrideCursor (); +} + +// public +void kpMainWindow::pasteTextAt (const QString &text, const QPoint &point, + bool allowNewTextSelectionPointShift) +{ +#if DEBUG_KP_MAIN_WINDOW && 1 + kdDebug () << "kpMainWindow::pasteTextAt(" << text + << ",point=" << point + << ",allowNewTextSelectionPointShift=" + << allowNewTextSelectionPointShift + << ")" << endl; +#endif + + QApplication::setOverrideCursor (Qt::waitCursor); + + if (toolHasBegunShape ()) + tool ()->endShapeInternal (); + + + if (m_document && + m_document->selection () && + m_document->selection ()->isText () && + m_document->selection ()->pointIsInTextArea (point)) + { + kpSelection *sel = m_document->selection (); + + const int row = sel->textRowForPoint (point); + const int col = sel->textColForPoint (point); + + m_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); + } + + QApplication::restoreOverrideCursor (); +} + +// public slot +void kpMainWindow::slotPaste () +{ +#if DEBUG_KP_MAIN_WINDOW && 1 + kdDebug () << "kpMainWindow::slotPaste() CALLED" << endl; +#endif + + // sync: restoreOverrideCursor() in all exit paths + QApplication::setOverrideCursor (Qt::waitCursor); + + if (toolHasBegunShape ()) + tool ()->endShapeInternal (); + + + if (!::HasSomethingToPasteWithDialogIfNot (this)) + { + QApplication::restoreOverrideCursor (); + return; + } + + + // + // Acquire the pixmap + // + + QMimeSource *ms = QApplication::clipboard ()->data (QClipboard::Clipboard); + if (!ms) + { + kdError () << "kpMainWindow::slotPaste() without mimeSource" << endl; + QApplication::restoreOverrideCursor (); + return; + } + + kpSelection sel; + QString text; + if (kpSelectionDrag::decode (ms, sel/*ref*/, pasteWarnAboutLossInfo ())) + { + sel.setTransparency (selectionTransparency ()); + paste (sel); + } + else if (QTextDrag::decode (ms, text/*ref*/)) + { + pasteText (text); + } + else + { + QApplication::restoreOverrideCursor (); + + kdDebug () << "kpMainWindow::slotPaste() could not decode selection" << endl; + kdDebug () << "\tFormats supported:" << endl; + for (int i = 0; ms->format (i); i++) + { + kdDebug () << "\t\t" << i << ":" << ms->format (i) << endl; + } + + // TODO: fix Klipper + KMessageBox::sorry (this, + i18n ("<qt><p>KolourPaint cannot paste the contents of" + " the clipboard as the data unexpectedly disappeared.</p>" + + "<p>This usually occurs if the application which was" + " responsible" + " for the clipboard contents has been closed.</p></qt>"), + i18n ("Cannot Paste")); + + // TODO: PROPAGATE: interprocess + if (KMainWindow::memberList) + { + #if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\thave memberList" << endl; + #endif + + for (QPtrList <KMainWindow>::const_iterator it = KMainWindow::memberList->begin (); + it != KMainWindow::memberList->end (); + it++) + { + kpMainWindow *mw = dynamic_cast <kpMainWindow *> (*it); + + if (!mw) + { + kdError () << "kpMainWindow::slotPaste() given fake kpMainWindow: " << (*it) << endl; + continue; + } + #if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\t\tmw=" << mw << endl; + #endif + + mw->slotEnablePaste (); + } + } + + return; + } + + QApplication::restoreOverrideCursor (); +} + +// private slot +void kpMainWindow::slotPasteInNewWindow () +{ +#if DEBUG_KP_MAIN_WINDOW && 1 + kdDebug () << "kpMainWindow::slotPasteInNewWindow() CALLED" << endl; +#endif + + // sync: restoreOverrideCursor() in all exit paths + QApplication::setOverrideCursor (Qt::waitCursor); + + if (toolHasBegunShape ()) + tool ()->endShapeInternal (); + + + if (!::HasSomethingToPasteWithDialogIfNot (this)) + { + QApplication::restoreOverrideCursor (); + return; + } + + + // + // 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. + // + + kpMainWindow *win = new kpMainWindow (0/*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. + kpSelectionTransparency transparency = win->selectionTransparency (); + if (transparency.isTransparent ()) + { + #if DEBUG_KP_MAIN_WINDOW && 1 + kdDebug () << "\tchanging selection transparency to opaque" << endl; + #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->setSelectionTransparency (transparency); + } + + // (this handles Requirement 1. above) + win->slotPaste (); + + // (this handles Requirement 2. above; + // slotDeselect() is not enough unless the document is filled with the + // transparent color in advance) + win->slotCrop (); + + + QApplication::restoreOverrideCursor (); +} + +// public slot +void kpMainWindow::slotDelete () +{ +#if DEBUG_KP_MAIN_WINDOW && 1 + kdDebug () << "kpMainWindow::slotDelete() CALLED" << endl; +#endif + if (!m_actionDelete->isEnabled ()) + { + #if DEBUG_KP_MAIN_WINDOW && 1 + kdDebug () << "\taction not enabled - was probably called from kpTool::keyPressEvent()" << endl; + #endif + return; + } + + if (!m_document || !m_document->selection ()) + { + kdError () << "kpMainWindow::slotDelete () doc=" << m_document + << " sel=" << (m_document ? m_document->selection () : 0) + << endl; + return; + } + + if (toolHasBegunShape ()) + tool ()->endShapeInternal (); + + addImageOrSelectionCommand (new kpToolSelectionDestroyCommand ( + m_document->selection ()->isText () ? + i18n ("Text: Delete Box") : // not to be confused with i18n ("Text: Delete") + i18n ("Selection: Delete"), + false/*no push onto doc*/, + this)); +} + + +// private slot +void kpMainWindow::slotSelectAll () +{ +#if DEBUG_KP_MAIN_WINDOW && 1 + kdDebug () << "kpMainWindow::slotSelectAll() CALLED" << endl; +#endif + if (!m_document) + { + kdError () << "kpMainWindow::slotSelectAll() without doc" << endl; + return; + } + + if (toolHasBegunShape ()) + tool ()->endShapeInternal (); + + if (m_document->selection ()) + slotDeselect (); + + // just the border - don't actually pull pixmap from doc yet + m_document->setSelection (kpSelection (kpSelection::Rectangle, m_document->rect (), selectionTransparency ())); + + if (tool ()) + tool ()->somethingBelowTheCursorChanged (); +} + + +// private +void kpMainWindow::addDeselectFirstCommand (kpCommand *cmd) +{ +#if DEBUG_KP_MAIN_WINDOW && 1 + kdDebug () << "kpMainWindow::addDeselectFirstCommand(" + << cmd + << ")" + << endl; +#endif + + + kpSelection *sel = m_document->selection (); + +#if DEBUG_KP_MAIN_WINDOW && 1 + kdDebug () << "\tsel=" << sel << endl; +#endif + + if (sel) + { + // if you just dragged out something with no action then + // forget the drag + if (!sel->pixmap ()) + { + #if DEBUG_KP_MAIN_WINDOW && 1 + kdDebug () << "\tjust a fresh border - was nop - delete" << endl; + #endif + m_document->selectionDelete (); + if (tool ()) + tool ()->somethingBelowTheCursorChanged (); + + if (cmd) + m_commandHistory->addCommand (cmd); + } + else + { + #if DEBUG_KP_MAIN_WINDOW && 1 + kdDebug () << "\treal selection with pixmap - push onto doc cmd" << endl; + #endif + kpCommand *deselectCommand = new kpToolSelectionDestroyCommand ( + sel->isText () ? + i18n ("Text: Finish") : + i18n ("Selection: Deselect"), + true/*push onto document*/, + this); + + if (cmd) + { + kpMacroCommand *macroCmd = new kpMacroCommand (cmd->name (), this); + macroCmd->addCommand (deselectCommand); + macroCmd->addCommand (cmd); + m_commandHistory->addCommand (macroCmd); + } + else + m_commandHistory->addCommand (deselectCommand); + } + } + else + { + if (cmd) + m_commandHistory->addCommand (cmd); + } +} + + +// public slot +void kpMainWindow::slotDeselect () +{ +#if DEBUG_KP_MAIN_WINDOW && 1 + kdDebug () << "kpMainWindow::slotDeselect() CALLED" << endl; +#endif + if (!m_document || !m_document->selection ()) + { + kdError () << "kpMainWindow::slotDeselect() doc=" << m_document + << " sel=" << (m_document ? m_document->selection () : 0) + << endl; + return; + } + + if (toolHasBegunShape ()) + tool ()->endShapeInternal (); + + addDeselectFirstCommand (0); +} + + +// private slot +void kpMainWindow::slotCopyToFile () +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::slotCopyToFile()" << endl; +#endif + + if (toolHasBegunShape ()) + tool ()->endShapeInternal (); + + + if (!m_document->selection ()) + return; + + kpSelection sel = *m_document->selection (); + + QPixmap pixmapToSave; + + if (!sel.pixmap ()) + { + // Not a floating selection - user has just selected a region; + // haven't pulled it off yet so probably don't expect and can't + // visualise selection transparency so give opaque, not transparent + // pixmap. + pixmapToSave = m_document->getSelectedPixmap (); + } + else + pixmapToSave = sel.transparentPixmap (); + + + kpDocumentSaveOptions chosenSaveOptions; + bool allowOverwritePrompt, allowLossyPrompt; + KURL chosenURL = askForSaveURL (i18n ("Copy to File"), + m_lastCopyToURL.url (), + pixmapToSave, + m_lastCopyToSaveOptions, + kpDocumentMetaInfo (), + kpSettingsGroupEditCopyTo, + false/*allow remote files*/, + &chosenSaveOptions, + m_copyToFirstTime, + &allowOverwritePrompt, + &allowLossyPrompt); + + if (chosenURL.isEmpty ()) + return; + + + if (!kpDocument::savePixmapToFile (pixmapToSave, + chosenURL, + chosenSaveOptions, kpDocumentMetaInfo (), + allowOverwritePrompt, + allowLossyPrompt, + this)) + { + return; + } + + + addRecentURL (chosenURL); + + + m_lastCopyToURL = chosenURL; + m_lastCopyToSaveOptions = chosenSaveOptions; + + m_copyToFirstTime = false; +} + +// private slot +void kpMainWindow::slotPasteFromFile () +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::slotPasteFromFile()" << endl; +#endif + + if (toolHasBegunShape ()) + tool ()->endShapeInternal (); + + + KURL::List urls = askForOpenURLs (i18n ("Paste From File"), + m_lastPasteFromURL.url (), + false/*only 1 URL*/); + + if (urls.count () != 1) + return; + + KURL url = urls.first (); + m_lastPasteFromURL = url; + + + QPixmap pixmap = kpDocument::getPixmapFromFile (url, + false/*show error message if doesn't exist*/, + this); + + + if (pixmap.isNull ()) + return; + + + addRecentURL (url); + + paste (kpSelection (kpSelection::Rectangle, + QRect (0, 0, pixmap.width (), pixmap.height ()), + pixmap, + selectionTransparency ())); +} + diff --git a/kolourpaint/kpmainwindow_file.cpp b/kolourpaint/kpmainwindow_file.cpp new file mode 100644 index 00000000..b30b323e --- /dev/null +++ b/kolourpaint/kpmainwindow_file.cpp @@ -0,0 +1,1409 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <qcstring.h> +#include <qdatastream.h> +#include <qpaintdevicemetrics.h> +#include <qpainter.h> +#include <qsize.h> + +#include <dcopclient.h> +#include <kapplication.h> +#include <kaction.h> +#include <kconfig.h> +#include <kdebug.h> +#include <kfiledialog.h> +#include <kiconloader.h> +#include <kimagefilepreview.h> +#include <kimageio.h> +#include <kio/netaccess.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <kprinter.h> +#include <kstdaccel.h> +#include <kstdaction.h> +#include <kscan.h> + +#include <kpdefs.h> +#include <kpdocument.h> +#include <kpdocumentsaveoptionswidget.h> +#include <kptool.h> +#include <kpview.h> +#include <kpviewmanager.h> + + +// private +void kpMainWindow::setupFileMenuActions () +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::setupFileMenuActions()" << endl; +#endif + KActionCollection *ac = actionCollection (); + + m_actionNew = KStdAction::openNew (this, SLOT (slotNew ()), ac); + m_actionOpen = KStdAction::open (this, SLOT (slotOpen ()), ac); + + m_actionOpenRecent = KStdAction::openRecent (this, SLOT (slotOpenRecent (const KURL &)), ac); + m_actionOpenRecent->loadEntries (kapp->config ()); +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\trecent URLs=" << m_actionOpenRecent->items () << endl; +#endif + + m_actionSave = KStdAction::save (this, SLOT (slotSave ()), ac); + m_actionSaveAs = KStdAction::saveAs (this, SLOT (slotSaveAs ()), ac); + + m_actionExport = new KAction (i18n ("E&xport..."), 0, + this, SLOT (slotExport ()), ac, "file_export"); + + m_actionScan = new KAction (i18n ("Scan..."), SmallIcon ("scanner"), 0, + this, SLOT (slotScan ()), ac, "file_scan"); + + //m_actionRevert = KStdAction::revert (this, SLOT (slotRevert ()), ac); + m_actionReload = new KAction (i18n ("Reloa&d"), KStdAccel::reload (), + this, SLOT (slotReload ()), ac, "file_revert"); + slotEnableReload (); + + m_actionPrint = KStdAction::print (this, SLOT (slotPrint ()), ac); + m_actionPrintPreview = KStdAction::printPreview (this, SLOT (slotPrintPreview ()), ac); + + m_actionMail = KStdAction::mail (this, SLOT (slotMail ()), ac); + + m_actionSetAsWallpaperCentered = new KAction (i18n ("Set as Wa&llpaper (Centered)"), 0, + this, SLOT (slotSetAsWallpaperCentered ()), ac, "file_set_as_wallpaper_centered"); + m_actionSetAsWallpaperTiled = new KAction (i18n ("Set as Wallpaper (&Tiled)"), 0, + this, SLOT (slotSetAsWallpaperTiled ()), ac, "file_set_as_wallpaper_tiled"); + + m_actionClose = KStdAction::close (this, SLOT (slotClose ()), ac); + m_actionQuit = KStdAction::quit (this, SLOT (slotQuit ()), ac); + + m_scanDialog = 0; + + enableFileMenuDocumentActions (false); +} + +// private +void kpMainWindow::enableFileMenuDocumentActions (bool enable) +{ + // m_actionNew + // m_actionOpen + + // m_actionOpenRecent + + m_actionSave->setEnabled (enable); + m_actionSaveAs->setEnabled (enable); + + m_actionExport->setEnabled (enable); + + // m_actionReload + + m_actionPrint->setEnabled (enable); + m_actionPrintPreview->setEnabled (enable); + + m_actionMail->setEnabled (enable); + + m_actionSetAsWallpaperCentered->setEnabled (enable); + m_actionSetAsWallpaperTiled->setEnabled (enable); + + m_actionClose->setEnabled (enable); + // m_actionQuit->setEnabled (enable); +} + + +// private +void kpMainWindow::addRecentURL (const KURL &url) +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::addRecentURL(" << url << ")" << endl; +#endif + if (url.isEmpty ()) + return; + + + KConfig *cfg = kapp->config (); + + // KConfig::readEntry() does not actually reread from disk, hence doesn't + // realise what other processes have done e.g. Settings / Show Path + cfg->reparseConfiguration (); + + // HACK: Something might have changed interprocess. + // If we could PROPAGATE: interprocess, then this wouldn't be required. + m_actionOpenRecent->loadEntries (cfg); + + m_actionOpenRecent->addURL (url); + + m_actionOpenRecent->saveEntries (cfg); + cfg->sync (); + +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\tnew recent URLs=" << m_actionOpenRecent->items () << endl; +#endif + + + // TODO: PROPAGATE: interprocess + if (KMainWindow::memberList) + { + #if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\thave memberList" << endl; + #endif + + for (QPtrList <KMainWindow>::const_iterator it = KMainWindow::memberList->begin (); + it != KMainWindow::memberList->end (); + it++) + { + kpMainWindow *mw = dynamic_cast <kpMainWindow *> (*it); + + if (!mw) + { + kdError () << "kpMainWindow::addRecentURL() given fake kpMainWindow: " << (*it) << endl; + continue; + } + #if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\t\tmw=" << mw << endl; + #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->m_actionOpenRecent->clearURLList (); + + mw->m_actionOpenRecent->loadEntries (cfg); + #if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\t\t\tcheck recent URLs=" + << mw->m_actionOpenRecent->items () << endl; + #endif + } + } + } +} + + +// private slot +void kpMainWindow::slotNew () +{ + if (toolHasBegunShape ()) + tool ()->endShapeInternal (); + + if (m_document) + { + kpMainWindow *win = new kpMainWindow (); + win->show (); + } + else + { + open (KURL (), true/*create an empty doc*/); + } +} + + +// private +QSize kpMainWindow::defaultDocSize () const +{ + // KConfig::readEntry() does not actually reread from disk, hence doesn't + // realise what other processes have done e.g. Settings / Show Path + kapp->config ()->reparseConfiguration (); + + KConfigGroupSaver cfgGroupSaver (kapp->config (), kpSettingsGroupGeneral); + KConfigBase *cfg = cfgGroupSaver.config (); + + QSize docSize = cfg->readSizeEntry (kpSettingLastDocSize); + + 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 + kdDebug () << "\tCONFIG: saving Last Doc Size = " << size << endl; +#endif + + KConfigGroupSaver cfgGroupSaver (kapp->config (), kpSettingsGroupGeneral); + KConfigBase *cfg = cfgGroupSaver.config (); + + cfg->writeEntry (kpSettingLastDocSize, size); + cfg->sync (); +} + + +// private +bool kpMainWindow::shouldOpenInNewWindow () const +{ + return (m_document && !m_document->isEmpty ()); +} + +// private +void kpMainWindow::setDocumentChoosingWindow (kpDocument *doc) +{ + // need new window? + if (shouldOpenInNewWindow ()) + { + // send doc to new window + kpMainWindow *win = new kpMainWindow (doc); + win->show (); + } + else + { + // set up views, doc signals + setDocument (doc); + } +} + + +// private +kpDocument *kpMainWindow::openInternal (const KURL &url, + const QSize &fallbackDocSize, + bool newDocSameNameIfNotExist) +{ + // create doc + kpDocument *newDoc = new kpDocument (fallbackDocSize.width (), + fallbackDocSize.height (), + this); + if (!newDoc->open (url, newDocSameNameIfNotExist)) + { + delete newDoc; + return 0; + } + + // Send document to current or new window. + setDocumentChoosingWindow (newDoc); + + return newDoc; +} + +// private +bool kpMainWindow::open (const KURL &url, bool newDocSameNameIfNotExist) +{ + kpDocument *newDoc = openInternal (url, + defaultDocSize (), + newDocSameNameIfNotExist); + if (newDoc) + { + if (newDoc->isFromURL (false/*don't bother checking exists*/)) + addRecentURL (url); + return true; + } + else + { + return false; + } +} + + +// private +KURL::List kpMainWindow::askForOpenURLs (const QString &caption, const QString &startURL, + bool allowMultipleURLs) +{ + QStringList mimeTypes = KImageIO::mimeTypes (KImageIO::Reading); +#if DEBUG_KP_MAIN_WINDOW + QStringList sortedMimeTypes = mimeTypes; + sortedMimeTypes.sort (); + kdDebug () << "kpMainWindow::askForURLs(allowMultiple=" + << allowMultipleURLs + << ")" << endl + << "\tmimeTypes=" << mimeTypes << endl + << "\tsortedMimeTypes=" << sortedMimeTypes << endl; +#endif + QString filter = mimeTypes.join (" "); + + KFileDialog fd (startURL, filter, this, "fd", true/*modal*/); + fd.setCaption (caption); + fd.setOperationMode (KFileDialog::Opening); + if (allowMultipleURLs) + fd.setMode (KFile::Files); + fd.setPreviewWidget (new KImageFilePreview (&fd)); + + if (fd.exec ()) + return fd.selectedURLs (); + else + return KURL::List (); +} + +// private slot +void kpMainWindow::slotOpen () +{ + if (toolHasBegunShape ()) + tool ()->endShapeInternal (); + + + const KURL::List urls = askForOpenURLs (i18n ("Open Image"), + m_document ? m_document->url ().url () : QString::null); + + for (KURL::List::const_iterator it = urls.begin (); + it != urls.end (); + it++) + { + open (*it); + } +} + +// private slot +void kpMainWindow::slotOpenRecent (const KURL &url) +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::slotOpenRecent(" << url << ")" << endl; + kdDebug () << "\titems=" << m_actionOpenRecent->items () << endl; +#endif + + if (toolHasBegunShape ()) + tool ()->endShapeInternal (); + + 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. + // + m_actionOpenRecent->setCurrentItem (-1); +} + + +// private slot +void kpMainWindow::slotScan () +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::slotScan() scanDialog=" << m_scanDialog << endl; +#endif + + if (toolHasBegunShape ()) + tool ()->endShapeInternal (); + + + if (!m_scanDialog) + { + // Create scan dialog by looking for plugin. + // [takes about 500ms on 350Mhz] + m_scanDialog = KScanDialog::getScanDialog (this, "scandialog", true/*modal*/); + + // No scanning support (kdegraphics/libkscan) installed? + // [Remove $KDEDIR/share/servicetypes/kscan.desktop and + // $KDEDIR/share/services/scanservice.desktop to simulate this] + if (!m_scanDialog) + { + #if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\tcould not create scan dialog" << endl; + #endif + // Instead, we could try to create the scan dialog in the ctor + // and just disable the action in the first place, removing + // the need for this dialog. + // + // But this increases startup time and is a bit risky e.g. if + // the scan support hangs, KolourPaint would not be able to be + // started at all. + // + // Also, disabling the action is bad because the scan support + // can be installed while KolourPaint is still running. + KMessageBox::sorry (this, + i18n ("Scanning support is not installed."), + i18n ("No Scanning Support")); + return; + } + + #if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\tcreated scanDialog=" << m_scanDialog << endl; + #endif + connect (m_scanDialog, SIGNAL (finalImage (const QImage &, int)), + SLOT (slotScanned (const QImage &, int))); + } + + +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\tcalling setup" << endl; +#endif + // Bring up dialog to select scan device. + if (m_scanDialog->setup ()) + { + #if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\t\tOK - showing dialog" << endl; + #endif + // Called only if scanner configured/available. + // + // In reality, this seems to be called even if you press "Cancel" in + // the KScanDialog::setup() dialog! + m_scanDialog->show (); + } + else + { + // Have never seen this code path execute even if "Cancel" is pressed. + #if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\t\tFAIL" << endl; + #endif + } +} + +// private slot +void kpMainWindow::slotScanned (const QImage &image, int) +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::slotScanned() image.rect=" << image.rect () << endl; +#endif + +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\thiding dialog" << endl; +#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. + m_scanDialog->hide (); + + // (just in case there's some drawing between slotScan() exiting and + // us being called) + if (toolHasBegunShape ()) + tool ()->endShapeInternal (); + + + // TODO: Maybe this code should be moved into kpdocument.cpp - + // since it resembles the responsibilities of kpDocument::open(). + + // Convert QImage to kpDocument's image format, gathering meta info + // from QImage. + kpDocumentSaveOptions saveOptions; + kpDocumentMetaInfo metaInfo; + const QPixmap pixmap = kpDocument::convertToPixmapAsLosslessAsPossible ( + image, + kpMainWindow::pasteWarnAboutLossInfo (), + &saveOptions, + &metaInfo); + + if (pixmap.isNull ()) + { + #if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\tcould not convert to pixmap" << endl; + #endif + KMessageBox::sorry (this, + i18n ("Cannot scan - out of graphics memory."), + i18n ("Cannot Scan")); + return; + } + + + // Create document from image and meta info. + kpDocument *doc = new kpDocument (pixmap.width (), pixmap.height (), this); + doc->setPixmap (pixmap); + doc->setSaveOptions (saveOptions); + doc->setMetaInfo (metaInfo); + + + // Send document to current or new window. + setDocumentChoosingWindow (doc); +} + + +// private slot +bool kpMainWindow::save (bool localOnly) +{ + if (m_document->url ().isEmpty () || + KImageIO::mimeTypes (KImageIO::Writing) + .findIndex (m_document->saveOptions ()->mimeType ()) < 0 || + // SYNC: kpDocument::getPixmapFromFile() can't determine quality + // from file so it has been set initially to an invalid value. + (m_document->saveOptions ()->mimeTypeHasConfigurableQuality () && + m_document->saveOptions ()->qualityIsInvalid ()) || + (localOnly && !m_document->url ().isLocalFile ())) + { + return saveAs (localOnly); + } + else + { + if (m_document->save (false/*no overwrite prompt*/, + !m_document->savedAtLeastOnceBefore ()/*lossy prompt*/)) + { + addRecentURL (m_document->url ()); + return true; + } + else + return false; + } +} + +// private slot +bool kpMainWindow::slotSave () +{ + if (toolHasBegunShape ()) + tool ()->endShapeInternal (); + + return save (); +} + +// private +KURL kpMainWindow::askForSaveURL (const QString &caption, + const QString &startURL, + const QPixmap &pixmapToBeSaved, + const kpDocumentSaveOptions &startSaveOptions, + const kpDocumentMetaInfo &docMetaInfo, + const QString &forcedSaveOptionsGroup, + bool localOnly, + kpDocumentSaveOptions *chosenSaveOptions, + bool isSavingForFirstTime, + bool *allowOverwritePrompt, + bool *allowLossyPrompt) +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::askForURL() startURL=" << startURL << endl; + startSaveOptions.printDebug ("\tstartSaveOptions"); +#endif + + bool reparsedConfiguration = false; + + // KConfig::readEntry() does not actually reread from disk, hence doesn't + // realise what other processes have done e.g. Settings / Show Path + // so reparseConfiguration() must be called +#define SETUP_READ_CFG() \ + if (!reparsedConfiguration) \ + { \ + kapp->config ()->reparseConfiguration (); \ + reparsedConfiguration = true; \ + } \ + \ + KConfigGroupSaver cfgGroupSaver (kapp->config (), forcedSaveOptionsGroup); \ + KConfigBase *cfg = cfgGroupSaver.config (); + + + if (chosenSaveOptions) + *chosenSaveOptions = kpDocumentSaveOptions (); + + if (allowOverwritePrompt) + *allowOverwritePrompt = true; // play it safe for now + + if (allowLossyPrompt) + *allowLossyPrompt = true; // play it safe for now + + + kpDocumentSaveOptions fdSaveOptions = startSaveOptions; + + QStringList mimeTypes = KImageIO::mimeTypes (KImageIO::Writing); +#if DEBUG_KP_MAIN_WINDOW + QStringList sortedMimeTypes = mimeTypes; + sortedMimeTypes.sort (); + kdDebug () << "\tmimeTypes=" << mimeTypes << endl + << "\tsortedMimeTypes=" << sortedMimeTypes << endl; +#endif + if (mimeTypes.isEmpty ()) + { + kdError () << "No KImageIO output mimetypes!" << endl; + return KURL (); + } + +#define MIME_TYPE_IS_VALID() (!fdSaveOptions.mimeTypeIsInvalid () && \ + mimeTypes.findIndex (fdSaveOptions.mimeType ()) >= 0) + if (!MIME_TYPE_IS_VALID ()) + { + #if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\tmimeType=" << fdSaveOptions.mimeType () + << " not valid, get default" << endl; + #endif + + SETUP_READ_CFG (); + + fdSaveOptions.setMimeType (kpDocumentSaveOptions::defaultMimeType (cfg)); + + + if (!MIME_TYPE_IS_VALID ()) + { + #if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\tmimeType=" << fdSaveOptions.mimeType () + << " not valid, get hardcoded" << endl; + #endif + if (mimeTypes.findIndex ("image/png") > -1) + fdSaveOptions.setMimeType ("image/png"); + else if (mimeTypes.findIndex ("image/x-bmp") > -1) + fdSaveOptions.setMimeType ("image/x-bmp"); + else + fdSaveOptions.setMimeType (mimeTypes.first ()); + } + } +#undef MIME_TYPE_IN_LIST + + 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 + + kpDocumentSaveOptionsWidget *saveOptionsWidget = + new kpDocumentSaveOptionsWidget (pixmapToBeSaved, + fdSaveOptions, + docMetaInfo, + this); + + KFileDialog fd (startURL, QString::null, this, "fd", true/*modal*/, + saveOptionsWidget); + saveOptionsWidget->setVisualParent (&fd); + fd.setCaption (caption); + fd.setOperationMode (KFileDialog::Saving); +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\tmimeTypes=" << mimeTypes << endl; +#endif + fd.setMimeFilter (mimeTypes, fdSaveOptions.mimeType ()); + if (localOnly) + fd.setMode (KFile::File | KFile::LocalOnly); + + connect (&fd, SIGNAL (filterChanged (const QString &)), + saveOptionsWidget, SLOT (setMimeType (const QString &))); + + + if (fd.exec ()) + { + kpDocumentSaveOptions newSaveOptions = saveOptionsWidget->documentSaveOptions (); + #if DEBUG_KP_MAIN_WINDOW + newSaveOptions.printDebug ("\tnewSaveOptions"); + #endif + + KConfigGroupSaver cfgGroupSaver (kapp->config (), forcedSaveOptionsGroup); + KConfigBase *cfg = cfgGroupSaver.config (); + + // Save options user forced - probably want to use them in future + kpDocumentSaveOptions::saveDefaultDifferences (cfg, + fdSaveOptions, newSaveOptions); + cfg->sync (); + + + if (chosenSaveOptions) + *chosenSaveOptions = newSaveOptions; + + + bool shouldAllowOverwritePrompt = + (fd.selectedURL () != startURL || + newSaveOptions.mimeType () != startSaveOptions.mimeType ()); + if (allowOverwritePrompt) + { + *allowOverwritePrompt = shouldAllowOverwritePrompt; + #if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\tallowOverwritePrompt=" << *allowOverwritePrompt << endl; + #endif + } + + 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 || + shouldAllowOverwritePrompt || + newSaveOptions.mimeType () != startSaveOptions.mimeType () || + newSaveOptions.colorDepth () != startSaveOptions.colorDepth () || + newSaveOptions.dither () != startSaveOptions.dither ()); + #if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\tallowLossyPrompt=" << *allowLossyPrompt << endl; + #endif + } + + + #if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\tselectedURL=" << fd.selectedURL () << endl; + #endif + return fd.selectedURL (); + } + else + return KURL (); +#undef SETUP_READ_CFG +} + + +// private slot +bool kpMainWindow::saveAs (bool localOnly) +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::saveAs URL=" << m_document->url () << endl; +#endif + + kpDocumentSaveOptions chosenSaveOptions; + bool allowOverwritePrompt, allowLossyPrompt; + KURL chosenURL = askForSaveURL (i18n ("Save Image As"), + m_document->url ().url (), + m_document->pixmapWithSelection (), + *m_document->saveOptions (), + *m_document->metaInfo (), + kpSettingsGroupFileSaveAs, + localOnly, + &chosenSaveOptions, + !m_document->savedAtLeastOnceBefore (), + &allowOverwritePrompt, + &allowLossyPrompt); + + + if (chosenURL.isEmpty ()) + return false; + + + if (!m_document->saveAs (chosenURL, chosenSaveOptions, + allowOverwritePrompt, + allowLossyPrompt)) + { + return false; + } + + + addRecentURL (chosenURL); + + return true; +} + +// private slot +bool kpMainWindow::slotSaveAs () +{ + if (toolHasBegunShape ()) + tool ()->endShapeInternal (); + + return saveAs (); +} + +// private slot +bool kpMainWindow::slotExport () +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::slotExport()" << endl; +#endif + + if (toolHasBegunShape ()) + tool ()->endShapeInternal (); + + + kpDocumentSaveOptions chosenSaveOptions; + bool allowOverwritePrompt, allowLossyPrompt; + KURL chosenURL = askForSaveURL (i18n ("Export"), + m_lastExportURL.url (), + m_document->pixmapWithSelection (), + m_lastExportSaveOptions, + *m_document->metaInfo (), + kpSettingsGroupFileExport, + false/*allow remote files*/, + &chosenSaveOptions, + m_exportFirstTime, + &allowOverwritePrompt, + &allowLossyPrompt); + + + if (chosenURL.isEmpty ()) + return false; + + + if (!kpDocument::savePixmapToFile (m_document->pixmapWithSelection (), + chosenURL, + chosenSaveOptions, *m_document->metaInfo (), + allowOverwritePrompt, + allowLossyPrompt, + this)) + { + return false; + } + + + addRecentURL (chosenURL); + + + m_lastExportURL = chosenURL; + m_lastExportSaveOptions = chosenSaveOptions; + + m_exportFirstTime = false; + + return true; +} + + +// private slot +void kpMainWindow::slotEnableReload () +{ + m_actionReload->setEnabled (m_document); +} + +// private slot +bool kpMainWindow::slotReload () +{ + if (toolHasBegunShape ()) + tool ()->endShapeInternal (); + + if (!m_document) + return false; + + + KURL oldURL = m_document->url (); + + + if (m_document->isModified ()) + { + int result = KMessageBox::Cancel; + + if (m_document->isFromURL (false/*don't bother checking exists*/) && !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?") + .arg (m_document->prettyFilename ()), + QString::null/*caption*/, + i18n ("&Reload")); + } + else + { + result = KMessageBox::warningContinueCancel (this, + i18n ("The document \"%1\" has been modified.\n" + "Reloading will lose all changes.\n" + "Are you sure?") + .arg (m_document->prettyFilename ()), + QString::null/*caption*/, + i18n ("&Reload")); + } + + if (result != KMessageBox::Continue) + return false; + } + + + kpDocument *doc = 0; + + // If it's _supposed to_ come from a URL or it exists + if (m_document->isFromURL (false/*don't bother checking exists*/) || + (!oldURL.isEmpty () && KIO::NetAccess::exists (oldURL, true/*open*/, this))) + { + #if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::slotReload() reloading from disk!" << endl; + #endif + + doc = new kpDocument (1, 1, this); + if (!doc->open (oldURL)) + { + delete doc; doc = 0; + return false; + } + + addRecentURL (oldURL); + } + else + { + #if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::slotReload() create doc" << endl; + #endif + + doc = new kpDocument (m_document->constructorWidth (), + m_document->constructorHeight (), + this); + doc->setURL (oldURL, false/*not from URL*/); + } + + + setDocument (doc); + + return true; +} + + +// private +void kpMainWindow::sendFilenameToPrinter (KPrinter *printer) +{ + KURL url = m_document->url (); + if (!url.isEmpty ()) + { + int dot; + + QString fileName = url.fileName (); + dot = fileName.findRev ('.'); + + // file.ext but not .hidden-file? + if (dot > 0) + fileName.truncate (dot); + + #if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::sendFilenameToPrinter() fileName=" + << fileName + << " dir=" + << url.directory () + << endl; + #endif + printer->setDocName (fileName); + printer->setDocFileName (fileName); + printer->setDocDirectory (url.directory ()); + } +} + + +static const double InchesPerMeter = 100 / 2.54; + + +// TODO: GUI should allow viewing & changing of DPI. + + +static bool shouldPrintImageCenteredOnPage () +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpmainwindow_file.cpp:shouldPrintImageCenteredOnPage()" << endl; +#endif + bool ret; + + KConfigGroupSaver cfgGroupSaver (KGlobal::config (), + kpSettingsGroupGeneral); + KConfigBase *cfg = cfgGroupSaver.config (); + + if (cfg->hasKey (kpSettingPrintImageCenteredOnPage)) + { + ret = cfg->readBoolEntry (kpSettingPrintImageCenteredOnPage); + #if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\tread: " << ret << endl; + #endif + } + else + { + ret = true; +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\tfirst time - writing default: " << ret << endl; +#endif + cfg->writeEntry (kpSettingPrintImageCenteredOnPage, ret); + cfg->sync (); + } + + return ret; +} + + +// private +void kpMainWindow::sendPixmapToPrinter (KPrinter *printer, + bool showPrinterSetupDialog) +{ + // Get image to be printed. + QPixmap pixmap = m_document->pixmapWithSelection (); + + + // Get image DPI. + double pixmapDotsPerMeterX = + double (m_document->metaInfo ()->dotsPerMeterX ()); + double pixmapDotsPerMeterY = + double (m_document->metaInfo ()->dotsPerMeterY ()); +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::sendPixmapToPrinter() pixmap:" + << " width=" << pixmap.width () + << " height=" << pixmap.height () + << " dotsPerMeterX=" << pixmapDotsPerMeterX + << " dotsPerMeterY=" << pixmapDotsPerMeterY + << endl; +#endif + + // Image DPI invalid (e.g. new image, could not read from file + // or Qt3 doesn't implement DPI for JPEG)? + if (pixmapDotsPerMeterX < 1 || pixmapDotsPerMeterY < 1) + { + // 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. + QPaintDeviceMetrics screenMetrics (&pixmap/*screen element*/); + const int dpiX = screenMetrics.logicalDpiX (), + dpiY = screenMetrics.logicalDpiY (); + #if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\tusing screen dpi: x=" << dpiX << " y=" << dpiY << endl; + #endif + + pixmapDotsPerMeterX = dpiX * InchesPerMeter; + pixmapDotsPerMeterY = dpiY * InchesPerMeter; + } + + + // Get page size (excluding margins). + // Coordinate (0,0) is the X here: + // mmmmm + // mX m + // m m m = margin + // m m + // mmmmm + QPaintDeviceMetrics printerMetrics (printer); + const int printerWidthMM = printerMetrics.widthMM (); + const int printerHeightMM = printerMetrics.heightMM (); +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\tprinter: widthMM=" << printerWidthMM + << " heightMM=" << printerHeightMM + << endl; +#endif + + + double dpiX = pixmapDotsPerMeterX / InchesPerMeter; + double dpiY = pixmapDotsPerMeterY / InchesPerMeter; +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\tpixmap: dpiX=" << dpiX << " dpiY=" << dpiY << endl; +#endif + + + // + // If image doesn't fit on page at intended DPI, change the DPI. + // + + const double scaleDpiX = (pixmap.width () / (printerWidthMM / 25.4)) + / dpiX; + const double scaleDpiY = (pixmap.height () / (printerHeightMM / 25.4)) + / dpiY; + const double scaleDpi = QMAX (scaleDpiX, scaleDpiY); +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\t\tscaleDpi: x=" << scaleDpiX << " y=" << scaleDpiY + << " --> scale at " << scaleDpi << " to fit?" + << endl; +#endif + + // Need to increase resolution to fit page? + if (scaleDpi > 1.0) + { + dpiX *= scaleDpi; + dpiY *= scaleDpi; + #if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\t\t\tto fit page, scaled to:" + << " dpiX=" << dpiX << " dpiY=" << dpiY << endl; + #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 + kdDebug () << "\tdpiX > dpiY; stretching pixmap height to equalise DPIs to dpiX=" + << dpiX << endl; + #endif + kpPixmapFX::scale (&pixmap, + pixmap.width (), + QMAX (1, qRound (pixmap.height () * dpiX / dpiY)), + false/*don't antialias*/); + + dpiY = dpiX; + } + else if (dpiY > dpiX) + { + #if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\tdpiY > dpiX; stretching pixmap width to equalise DPIs to dpiY=" + << dpiY << endl; + #endif + kpPixmapFX::scale (&pixmap, + QMAX (1, qRound (pixmap.width () * dpiY / dpiX)), + pixmap.height (), + false/*don't antialias*/); + + dpiX = dpiY; + } + + + // ASSERT: dpiX == dpiY + // QPrinter::setResolution() has to be called before QPrinter::setup(). + printer->setResolution (QMAX (1, qRound (dpiX))); + + + double originX = 0, originY = 0; + + // Centre image on page? + if (shouldPrintImageCenteredOnPage ()) + { + originX = (printerWidthMM * dpiX / 25.4 - pixmap.width ()) / 2; + originY = (printerHeightMM * dpiY / 25.4 - pixmap.height ()) / 2; + } + +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\torigin: x=" << originX << " y=" << originY << endl; +#endif + + + sendFilenameToPrinter (printer); + + if (showPrinterSetupDialog) + { + // The user can mutate margins at their own risk in this dialog. + // It doesn't seem to affect the size of the page as reported + // by QPaintDeviceMetrics::{width,height}MM(). + if (!printer->setup (this)) + return; + } + + + // Send pixmap to printer. + QPainter painter; + painter.begin (printer); + painter.drawPixmap (qRound (originX), qRound (originY), pixmap); + painter.end (); +} + + +// private slot +void kpMainWindow::slotPrint () +{ + if (toolHasBegunShape ()) + tool ()->endShapeInternal (); + + KPrinter printer; + + sendPixmapToPrinter (&printer, true/*showPrinterSetupDialog*/); +} + +// private slot +void kpMainWindow::slotPrintPreview () +{ + if (toolHasBegunShape ()) + tool ()->endShapeInternal (); + + // TODO: get it to reflect default printer's settings + KPrinter printer (false/*separate settings from ordinary printer*/); + + // TODO: pass "this" as parent + printer.setPreviewOnly (true); + + sendPixmapToPrinter (&printer, false/*don't showPrinterSetupDialog*/); +} + + +// private slot +void kpMainWindow::slotMail () +{ + if (toolHasBegunShape ()) + tool ()->endShapeInternal (); + + if (m_document->url ().isEmpty ()/*no name*/ || + !m_document->isFromURL () || + m_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::null, + KStdGuiItem::save (), KStdGuiItem::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; + } + } + + kapp->invokeMailer ( + QString::null/*to*/, + QString::null/*cc*/, + QString::null/*bcc*/, + m_document->prettyFilename()/*subject*/, + QString::null/*body*/, + QString::null/*messageFile*/, + QStringList (m_document->url ().url ())/*attachments*/); +} + + +// private +void kpMainWindow::setAsWallpaper (bool centered) +{ + if (m_document->url ().isEmpty ()/*no name*/ || + !m_document->url ().isLocalFile ()/*remote file*/ || + !m_document->isFromURL () || + m_document->isModified ()/*needs to be saved*/) + { + QString question; + + if (!m_document->url ().isLocalFile ()) + { + question = i18n ("Before this image can be set as the wallpaper, " + "you must save it as a local file.\n" + "Do you want to save it?"); + } + else + { + question = i18n ("Before this image can be set as the wallpaper, " + "you must save it.\n" + "Do you want to save it?"); + } + + int result = KMessageBox::questionYesNo (this, + question, QString::null, + KStdGuiItem::save (), KStdGuiItem::cancel ()); + + if (result == KMessageBox::Yes) + { + // save() is smart enough to pop up a filedialog if it's a + // remote file that should be saved locally + if (!save (true/*localOnly*/)) + { + // save failed or aborted - don't set the wallpaper + return; + } + } + else + { + // don't want to save - don't set wallpaper + return; + } + } + + + QByteArray data; + QDataStream dataStream (data, IO_WriteOnly); + + // write path +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::setAsWallpaper() path=" + << m_document->url ().path () << endl; +#endif + dataStream << QString (m_document->url ().path ()); + + // write position: + // + // SYNC: kdebase/kcontrol/background/bgsettings.h: + // 1 = Centered + // 2 = Tiled + // 6 = Scaled + // 9 = lastWallpaperMode + // + // Why restrict the user to Centered & Tiled? + // Why don't we let the user choose if it should be common to all desktops? + // Why don't we rewrite the Background control page? + // + // Answer: This is supposed to be a quick & convenient feature. + // + // If you want more options, go to kcontrol for that kind of + // flexiblity. We don't want to slow down average users, who see way too + // many dialogs already and probably haven't even heard of "Centered Maxpect"... + // + dataStream << int (centered ? 1 : 2); + + + // I'm going to all this trouble because the user might not have kdebase + // installed so kdebase/kdesktop/KBackgroundIface.h might not be around + // to be compiled in (where user == developer :)) + if (!KApplication::dcopClient ()->send ("kdesktop", "KBackgroundIface", + "setWallpaper(QString,int)", data)) + { + KMessageBox::sorry (this, i18n ("Could not change wallpaper.")); + } +} + +// private slot +void kpMainWindow::slotSetAsWallpaperCentered () +{ + if (toolHasBegunShape ()) + tool ()->endShapeInternal (); + + setAsWallpaper (true/*centered*/); +} + +// private slot +void kpMainWindow::slotSetAsWallpaperTiled () +{ + if (toolHasBegunShape ()) + tool ()->endShapeInternal (); + + setAsWallpaper (false/*tiled*/); +} + + +// private slot +void kpMainWindow::slotClose () +{ + if (toolHasBegunShape ()) + tool ()->endShapeInternal (); + +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::slotClose()" << endl; +#endif + + if (!queryClose ()) + return; + + setDocument (0); +} + +// private slot +void kpMainWindow::slotQuit () +{ + if (toolHasBegunShape ()) + tool ()->endShapeInternal (); + +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::slotQuit()" << endl; +#endif + + close (); // will call queryClose() +} + diff --git a/kolourpaint/kpmainwindow_help.cpp b/kolourpaint/kpmainwindow_help.cpp new file mode 100644 index 00000000..fb1fc790 --- /dev/null +++ b/kolourpaint/kpmainwindow_help.cpp @@ -0,0 +1,219 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <kpmainwindow_p.h> + +#include <dcopclient.h> +#include <kaction.h> +#include <kactivelabel.h> +#include <kapplication.h> +#include <kconfig.h> +#include <kdebug.h> +#include <kdialogbase.h> +#include <krun.h> +#include <klocale.h> +#include <kshortcut.h> + +#include <kptool.h> + + +// private +void kpMainWindow::setupHelpMenuActions () +{ + KActionCollection *ac = actionCollection (); + + + // Explanation for action name: + // "Taking" is like a digital camera when you record the image and is + // analogous to pressing PrintScreen. However, "Acquiring" is when + // the image is brought into KolourPaint, just as you would acquire + // from a digital camera in future versions of KolourPaint. Hence + // "Acquiring" is more appropriate. + // -- Thurston + d->m_actionHelpTakingScreenshots = new KAction ( + i18n ("Acquiring &Screenshots"), 0, + this, SLOT (slotHelpTakingScreenshots ()), + ac, "help_taking_screenshots"); + + + enableHelpMenuDocumentActions (false); +} + +// private +void kpMainWindow::enableHelpMenuDocumentActions (bool /*enable*/) +{ +} + + +// SYNC: kdebase/kwin/kwinbindings.cpp +static QString printScreenShortcutString () +{ + KConfigGroupSaver cfgGroupSaver (KGlobal::config (), "Global Shortcuts"); + KConfigBase *cfg = cfgGroupSaver.config (); + + // TODO: i18n() entry name? kwinbindings.cpp seems to but it doesn't + // make sense. + const QString cfgEntryString = cfg->readEntry ("Desktop Screenshot"); + + + // (only use 1st key sequence, if it exists) + const QString humanReadableShortcut = + KShortcut (cfgEntryString).seq (0).toString (); + + if (!humanReadableShortcut.isEmpty ()) + { + return humanReadableShortcut; + } + else + { + // (localised) + return KKey (Qt::CTRL + Qt::Key_Print).toString (); + } +} + + +// private slot +void kpMainWindow::slotHelpTakingScreenshots () +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::slotHelpTakingScreenshots()" << endl; +#endif + + if (toolHasBegunShape ()) + tool ()->endShapeInternal (); + + + // TODO: Totally bogus logic if kwin not running under same user as KolourPaint. + // SYNC: KWin contains PrintScreen key logic + QCStringList dcopApps = KApplication::dcopClient ()->registeredApplications (); +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\tdcopApps=" << dcopApps << endl; +#endif + bool isRunningKDE = (dcopApps.findIndex ("kwin") >= 0); + +#if 0 +{ + int i = 0; + FILE *fp = fopen ("/home/kdevel/kolourpaint.tmp", "rt"); + if (fp && fscanf (fp, "Hello: %d", &i) == 1) + isRunningKDE = i, fclose (fp); +} +#endif + + QString message; + if (isRunningKDE) + { + message = i18n + ( + "<p>" + "To acquire a screenshot, press <b>%1</b>." + " The screenshot will be placed into the clipboard" + " and you will be able to paste it in KolourPaint." + "</p>" + + "<p>" + "You may configure the <b>Desktop Screenshot</b> shortcut" + " in the KDE Control Center" + " module <a href=\"configure kde shortcuts\">Keyboard Shortcuts</a>." + "</p>" + + "<p>Alternatively, you may try the application" + " <a href=\"run ksnapshot\">KSnapshot</a>." + "</p>" + ); + } + else + { + message = i18n + ( + "<p>" + "You do not appear to be running KDE." + "</p>" + + // We tell them this much even though they aren't running KDE + // to entice them to use KDE since it's so easy. + "<p>" + "Once you have loaded KDE:<br>" + "<blockquote>" + "To acquire a screenshot, press <b>%1</b>." + " The screenshot will be placed into the clipboard" + " and you will be able to paste it in KolourPaint." + "</blockquote>" + "</p>" + + "<p>Alternatively, you may try the application" + " <a href=\"run ksnapshot\">KSnapshot</a>." + "</p>" + ); + } + + // TODO: Totally bogus logic if kwin not running under same user as KolourPaint. + message = message.arg (::printScreenShortcutString ()); + + // Add extra vertical space + message += "<p> </p>"; + + + KDialogBase dlg (this, "helpTakingScreenshotsDialog", true/*modal*/, + i18n ("Acquiring Screenshots"), + KDialogBase::Close, KDialogBase::Close/*default btn*/, + true/*separator line*/); + + KActiveLabel *messageLabel = new KActiveLabel (message, &dlg); + disconnect (messageLabel, SIGNAL (linkClicked (const QString &)), + messageLabel, SLOT (openLink (const QString &))); + connect (messageLabel, SIGNAL (linkClicked (const QString &)), + this, SLOT (slotHelpTakingScreenshotsFollowLink (const QString &))); + + dlg.setMainWidget (messageLabel); + + dlg.exec (); +} + +// private +void kpMainWindow::slotHelpTakingScreenshotsFollowLink (const QString &link) +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::slotHelpTakingScreenshotsFollowLink(" + << link << ")" << endl; +#endif + + if (link == "configure kde shortcuts") + { + KRun::runCommand ("kcmshell keys"); + } + else if (link == "run ksnapshot") + { + KRun::runCommand ("ksnapshot"); + } + else + { + kdError () << "kpMainWindow::slotHelpTakingScreenshotsFollowLink(" + << link << ")" << endl; + } +} diff --git a/kolourpaint/kpmainwindow_image.cpp b/kolourpaint/kpmainwindow_image.cpp new file mode 100644 index 00000000..7f662af7 --- /dev/null +++ b/kolourpaint/kpmainwindow_image.cpp @@ -0,0 +1,474 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <kpmainwindow_p.h> + +#include <qcolor.h> +#include <qsize.h> + +#include <kaction.h> +#include <kapplication.h> +#include <kconfig.h> +#include <kdebug.h> +#include <klocale.h> +#include <kmenubar.h> + +#include <kpcolor.h> +#include <kpdefs.h> +#include <kpcoloreffect.h> +#include <kpcolortoolbar.h> +#include <kpcommandhistory.h> +#include <kpdocument.h> +#include <kpeffectinvert.h> +#include <kpeffectreducecolors.h> +#include <kpeffectsdialog.h> +#include <kpselection.h> +#include <kptool.h> +#include <kptoolautocrop.h> +#include <kptoolclear.h> +#include <kptoolconverttograyscale.h> +#include <kptoolcrop.h> +#include <kptoolflip.h> +#include <kptoolresizescale.h> +#include <kptoolrotate.h> +#include <kptoolselection.h> +#include <kptoolskew.h> +#include <kpviewmanager.h> + + +// private +bool kpMainWindow::isSelectionActive () const +{ + return (m_document ? bool (m_document->selection ()) : false); +} + +// private +bool kpMainWindow::isTextSelection () const +{ + return (m_document && m_document->selection () && + m_document->selection ()->isText ()); +} + + +// private +QString kpMainWindow::autoCropText () const +{ + return kpToolAutoCropCommand::name (isSelectionActive (), + kpToolAutoCropCommand::ShowAccel); +} + + +// private +void kpMainWindow::setupImageMenuActions () +{ + KActionCollection *ac = actionCollection (); + + m_actionResizeScale = new KAction (i18n ("R&esize / Scale..."), Qt::CTRL + Qt::Key_E, + this, SLOT (slotResizeScale ()), ac, "image_resize_scale"); + + m_actionCrop = new KAction (i18n ("Se&t as Image (Crop)"), Qt::CTRL + Qt::Key_T, + this, SLOT (slotCrop ()), ac, "image_crop"); + + m_actionAutoCrop = new KAction (autoCropText (), Qt::CTRL + Qt::Key_U, + this, SLOT (slotAutoCrop ()), ac, "image_auto_crop"); + + m_actionFlip = new KAction (i18n ("&Flip..."), Qt::CTRL + Qt::Key_F, + this, SLOT (slotFlip ()), ac, "image_flip"); + + m_actionRotate = new KAction (i18n ("&Rotate..."), Qt::CTRL + Qt::Key_R, + this, SLOT (slotRotate ()), ac, "image_rotate"); + + m_actionSkew = new KAction (i18n ("S&kew..."), Qt::CTRL + Qt::Key_K, + this, SLOT (slotSkew ()), ac, "image_skew"); + + m_actionConvertToBlackAndWhite = new KAction (i18n ("Reduce to Mo&nochrome (Dithered)"), 0, + this, SLOT (slotConvertToBlackAndWhite ()), ac, "image_convert_to_black_and_white"); + + m_actionConvertToGrayscale = new KAction (i18n ("Reduce to &Grayscale"), 0, + this, SLOT (slotConvertToGrayscale ()), ac, "image_convert_to_grayscale"); + + m_actionInvertColors = new KAction (i18n ("&Invert Colors"), Qt::CTRL + Qt::Key_I, + this, SLOT (slotInvertColors ()), ac, "image_invert_colors"); + + m_actionClear = new KAction (i18n ("C&lear"), Qt::CTRL + Qt::SHIFT + Qt::Key_N, + this, SLOT (slotClear ()), ac, "image_clear"); + + m_actionMoreEffects = new KAction (i18n ("&More Effects..."), Qt::CTRL + Qt::Key_M, + this, SLOT (slotMoreEffects ()), ac, "image_more_effects"); + + enableImageMenuDocumentActions (false); +} + +// private +void kpMainWindow::enableImageMenuDocumentActions (bool enable) +{ + m_actionResizeScale->setEnabled (enable); + m_actionCrop->setEnabled (enable); + m_actionAutoCrop->setEnabled (enable); + m_actionFlip->setEnabled (enable); + m_actionRotate->setEnabled (enable); + m_actionSkew->setEnabled (enable); + m_actionConvertToBlackAndWhite->setEnabled (enable); + m_actionConvertToGrayscale->setEnabled (enable); + m_actionInvertColors->setEnabled (enable); + m_actionClear->setEnabled (enable); + m_actionMoreEffects->setEnabled (enable); + + m_imageMenuDocumentActionsEnabled = enable; +} + + +// private slot +void kpMainWindow::slotImageMenuUpdateDueToSelection () +{ + KMenuBar *mBar = menuBar (); + if (!mBar) // just in case + return; + + int mBarNumItems = (int) mBar->count (); + for (int index = 0; index < mBarNumItems; index++) + { + int id = mBar->idAt (index); + + // SYNC: kolourpaintui.rc + QString menuBarItemTextImage = i18n ("&Image"); + QString menuBarItemTextSelection = i18n ("Select&ion"); + + const QString menuBarItemText = mBar->text (id); + if (menuBarItemText == menuBarItemTextImage || + menuBarItemText == menuBarItemTextSelection) + { + if (isSelectionActive ()) + mBar->changeItem (id, menuBarItemTextSelection); + else + mBar->changeItem (id, menuBarItemTextImage); + + break; + } + } + + + m_actionResizeScale->setEnabled (m_imageMenuDocumentActionsEnabled); + m_actionCrop->setEnabled (m_imageMenuDocumentActionsEnabled && + isSelectionActive ()); + + const bool enable = (m_imageMenuDocumentActionsEnabled && !isTextSelection ()); + m_actionAutoCrop->setText (autoCropText ()); + m_actionAutoCrop->setEnabled (enable); + m_actionFlip->setEnabled (enable); + m_actionRotate->setEnabled (enable); + m_actionSkew->setEnabled (enable); + m_actionConvertToBlackAndWhite->setEnabled (enable); + m_actionConvertToGrayscale->setEnabled (enable); + m_actionInvertColors->setEnabled (enable); + m_actionClear->setEnabled (enable); + m_actionMoreEffects->setEnabled (enable); +} + + +// public +kpColor kpMainWindow::backgroundColor (bool ofSelection) const +{ + if (ofSelection) + return kpColor::transparent; + else + { + if (m_colorToolBar) + return m_colorToolBar->backgroundColor (); + else + { + kdError () << "kpMainWindow::backgroundColor() without colorToolBar" << endl; + return kpColor::invalid; + } + } +} + + +// public +void kpMainWindow::addImageOrSelectionCommand (kpCommand *cmd, + bool addSelCreateCmdIfSelAvail, + bool addSelPullCmdIfSelAvail) +{ +#if DEBUG_KP_MAIN_WINDOW && 1 + kdDebug () << "kpMainWindow::addImageOrSelectionCommand()" + << " addSelCreateCmdIfSelAvail=" << addSelCreateCmdIfSelAvail + << " addSelPullCmdIfSelAvail=" << addSelPullCmdIfSelAvail + << endl; +#endif + + if (!m_document) + { + kdError () << "kpMainWindow::addImageOrSelectionCommand() without doc" << endl; + return; + } + + + if (m_viewManager) + m_viewManager->setQueueUpdates (); + + + kpSelection *sel = m_document->selection (); +#if DEBUG_KP_MAIN_WINDOW && 1 + kdDebug () << "\tsel=" << sel + << " sel->pixmap=" << (sel ? sel->pixmap () : 0) + << endl; +#endif + if (addSelCreateCmdIfSelAvail && sel && !sel->pixmap ()) + { + // create selection region + kpCommand *createCommand = new kpToolSelectionCreateCommand ( + i18n ("Selection: Create"), + *sel, + this); + + if (kpToolSelectionCreateCommand::nextUndoCommandIsCreateBorder (commandHistory ())) + commandHistory ()->setNextUndoCommand (createCommand); + else + commandHistory ()->addCommand (createCommand, + false/*no exec - user already dragged out sel*/); + } + + + if (addSelPullCmdIfSelAvail && sel && !sel->pixmap ()) + { + kpMacroCommand *macroCmd = new kpMacroCommand (cmd->name (), this); + + macroCmd->addCommand (new kpToolSelectionPullFromDocumentCommand ( + QString::null/*uninteresting child of macro cmd*/, + this)); + + macroCmd->addCommand (cmd); + + m_commandHistory->addCommand (macroCmd); + } + else + { + m_commandHistory->addCommand (cmd); + } + + + if (m_viewManager) + m_viewManager->restoreQueueUpdates (); +} + +// private slot +void kpMainWindow::slotResizeScale () +{ + if (toolHasBegunShape ()) + tool ()->endShapeInternal (); + + kpToolResizeScaleDialog dialog (this); + dialog.setKeepAspectRatio (d->m_resizeScaleDialogLastKeepAspect); + + if (dialog.exec () && !dialog.isNoOp ()) + { + kpToolResizeScaleCommand *cmd = new kpToolResizeScaleCommand ( + dialog.actOnSelection (), + dialog.imageWidth (), dialog.imageHeight (), + dialog.type (), + this); + + bool addSelCreateCommand = (dialog.actOnSelection () || + cmd->scaleSelectionWithImage ()); + bool addSelPullCommand = dialog.actOnSelection (); + + addImageOrSelectionCommand ( + cmd, + addSelCreateCommand, + addSelPullCommand); + + // Resized document? + if (!dialog.actOnSelection () && + dialog.type () == kpToolResizeScaleCommand::Resize) + { + // TODO: this should be the responsibility of kpDocument + saveDefaultDocSize (QSize (dialog.imageWidth (), dialog.imageHeight ())); + } + } + + + if (d->m_resizeScaleDialogLastKeepAspect != dialog.keepAspectRatio ()) + { + d->m_resizeScaleDialogLastKeepAspect = dialog.keepAspectRatio (); + + KConfigGroupSaver cfgGroupSaver (kapp->config (), kpSettingsGroupGeneral); + KConfigBase *cfg = cfgGroupSaver.config (); + + cfg->writeEntry (kpSettingResizeScaleLastKeepAspect, + d->m_resizeScaleDialogLastKeepAspect); + cfg->sync (); + } +} + +// public slot +void kpMainWindow::slotCrop () +{ + if (toolHasBegunShape ()) + tool ()->endShapeInternal (); + + if (!m_document || !m_document->selection ()) + { + kdError () << "kpMainWindow::slotCrop() doc=" << m_document + << " sel=" << (m_document ? m_document->selection () : 0) + << endl; + return; + } + + + ::kpToolCrop (this); +} + +// private slot +void kpMainWindow::slotAutoCrop () +{ + if (toolHasBegunShape ()) + tool ()->endShapeInternal (); + + ::kpToolAutoCrop (this); +} + +// private slot +void kpMainWindow::slotFlip () +{ + if (toolHasBegunShape ()) + tool ()->endShapeInternal (); + + kpToolFlipDialog dialog ((bool) m_document->selection (), this); + + if (dialog.exec () && !dialog.isNoOp ()) + { + addImageOrSelectionCommand ( + new kpToolFlipCommand (m_document->selection (), + dialog.getHorizontalFlip (), dialog.getVerticalFlip (), + this)); + } +} + +// private slot +void kpMainWindow::slotRotate () +{ + if (toolHasBegunShape ()) + tool ()->endShapeInternal (); + + kpToolRotateDialog dialog ((bool) m_document->selection (), this); + + if (dialog.exec () && !dialog.isNoOp ()) + { + addImageOrSelectionCommand ( + new kpToolRotateCommand (m_document->selection (), + dialog.angle (), + this)); + } +} + +// private slot +void kpMainWindow::slotSkew () +{ + if (toolHasBegunShape ()) + tool ()->endShapeInternal (); + + kpToolSkewDialog dialog ((bool) m_document->selection (), this); + + if (dialog.exec () && !dialog.isNoOp ()) + { + addImageOrSelectionCommand ( + new kpToolSkewCommand (m_document->selection (), + dialog.horizontalAngle (), dialog.verticalAngle (), + this)); + } +} + +// private slot +void kpMainWindow::slotConvertToBlackAndWhite () +{ + if (toolHasBegunShape ()) + tool ()->endShapeInternal (); + + addImageOrSelectionCommand ( + new kpEffectReduceColorsCommand (1/*depth*/, true/*dither*/, + m_document->selection (), this)); +} + +// private slot +void kpMainWindow::slotConvertToGrayscale () +{ + if (toolHasBegunShape ()) + tool ()->endShapeInternal (); + + addImageOrSelectionCommand ( + new kpToolConvertToGrayscaleCommand (m_document->selection (), this)); +} + +// private slot +void kpMainWindow::slotInvertColors () +{ + if (toolHasBegunShape ()) + tool ()->endShapeInternal (); + + addImageOrSelectionCommand ( + new kpEffectInvertCommand (m_document->selection (), this)); +} + +// private slot +void kpMainWindow::slotClear () +{ + if (toolHasBegunShape ()) + tool ()->endShapeInternal (); + + addImageOrSelectionCommand ( + new kpToolClearCommand (m_document->selection (), this)); +} + +// private slot +void kpMainWindow::slotMoreEffects () +{ + if (toolHasBegunShape ()) + tool ()->endShapeInternal (); + + kpEffectsDialog dialog ((bool) m_document->selection (), this); + dialog.selectEffect (d->m_moreEffectsDialogLastEffect); + + if (dialog.exec () && !dialog.isNoOp ()) + { + addImageOrSelectionCommand (dialog.createCommand ()); + } + + + if (d->m_moreEffectsDialogLastEffect != dialog.selectedEffect ()) + { + d->m_moreEffectsDialogLastEffect = dialog.selectedEffect (); + + KConfigGroupSaver cfgGroupSaver (kapp->config (), kpSettingsGroupGeneral); + KConfigBase *cfg = cfgGroupSaver.config (); + + cfg->writeEntry (kpSettingMoreEffectsLastEffect, + d->m_moreEffectsDialogLastEffect); + cfg->sync (); + } +} diff --git a/kolourpaint/kpmainwindow_p.h b/kolourpaint/kpmainwindow_p.h new file mode 100644 index 00000000..9ec94eaa --- /dev/null +++ b/kolourpaint/kpmainwindow_p.h @@ -0,0 +1,49 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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_P_H +#define KP_MAIN_WINDOW_P_H + + +class KAction; +class KToggleAction; + + +struct kpMainWindowPrivate +{ + bool m_configThumbnailShowRectangle; + KToggleAction *m_actionShowThumbnailRectangle; + + KAction *m_actionHelpTakingScreenshots; + + int m_moreEffectsDialogLastEffect; + bool m_resizeScaleDialogLastKeepAspect; +}; + + +#endif // KP_MAIN_WINDOW_P_H diff --git a/kolourpaint/kpmainwindow_settings.cpp b/kolourpaint/kpmainwindow_settings.cpp new file mode 100644 index 00000000..609f7dfe --- /dev/null +++ b/kolourpaint/kpmainwindow_settings.cpp @@ -0,0 +1,209 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <kactionclasses.h> +#include <kapplication.h> +#include <kconfig.h> +#include <kdebug.h> +#include <kedittoolbar.h> +#include <kkeydialog.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <kstdaction.h> + +#include <kpdefs.h> +#include <kpdocument.h> +#include <kptool.h> +#include <kptooltoolbar.h> + + +// private +void kpMainWindow::setupSettingsMenuActions () +{ + KActionCollection *ac = actionCollection (); + + + // Settings/Toolbars |> %s + setStandardToolBarMenuEnabled (true); + + // Settings/Show Statusbar + createStandardStatusBarAction (); + + + m_actionFullScreen = KStdAction::fullScreen (this, SLOT (slotFullScreen ()), ac, + this/*window*/); + + + m_actionShowPath = new KToggleAction (i18n ("Show &Path"), 0, + this, SLOT (slotShowPathToggled ()), ac, "settings_show_path"); + m_actionShowPath->setCheckedState (i18n ("Hide &Path")); + slotEnableSettingsShowPath (); + + + m_actionKeyBindings = KStdAction::keyBindings (this, SLOT (slotKeyBindings ()), ac); + m_actionConfigureToolbars = KStdAction::configureToolbars (this, SLOT (slotConfigureToolBars ()), ac); + // m_actionConfigure = KStdAction::preferences (this, SLOT (slotConfigure ()), ac); + + + enableSettingsMenuDocumentActions (false); +} + +// private +void kpMainWindow::enableSettingsMenuDocumentActions (bool /*enable*/) +{ +} + + +// private slot +void kpMainWindow::slotFullScreen () +{ + if (m_actionFullScreen->isChecked ()) + showFullScreen (); + else + showNormal (); +} + + +// private slot +void kpMainWindow::slotEnableSettingsShowPath () +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::slotEnableSettingsShowPath()" << endl; +#endif + + const bool enable = (m_document && !m_document->url ().isEmpty ()); + + m_actionShowPath->setEnabled (enable); + m_actionShowPath->setChecked (enable && m_configShowPath); +} + +// private slot +void kpMainWindow::slotShowPathToggled () +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::slotShowPathToggled()" << endl; +#endif + + m_configShowPath = m_actionShowPath->isChecked (); + + slotUpdateCaption (); + + + KConfigGroupSaver cfgGroupSaver (kapp->config (), kpSettingsGroupGeneral); + KConfigBase *cfg = cfgGroupSaver.config (); + + cfg->writeEntry (kpSettingShowPath, m_configShowPath); + cfg->sync (); +} + + +// private slot +void kpMainWindow::slotKeyBindings () +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::slotKeyBindings()" << endl; +#endif + + if (toolHasBegunShape ()) + tool ()->endShapeInternal (); + + + bool singleKeyTriggersDisabled = !actionsSingleKeyTriggersEnabled (); + + if (singleKeyTriggersDisabled) + enableActionsSingleKeyTriggers (true); + + + if (KKeyDialog::configure (actionCollection (), this)) + { + #if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\tdialog accepted" << endl; + #endif + // TODO: PROPAGATE: thru mainWindow's and interprocess + + if (singleKeyTriggersDisabled) + enableActionsSingleKeyTriggers (false); + } +} + + +// private slot +void kpMainWindow::slotConfigureToolBars () +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::slotConfigureToolBars()" << endl; +#endif + + if (toolHasBegunShape ()) + tool ()->endShapeInternal (); + + + //saveMainWindowSettings (kapp->config (), autoSaveGroup ()); + + KEditToolbar dialog (actionCollection (), + QString::null/*default ui.rc file*/, + true/*global resource*/, + this/*parent*/); + // Clicking on OK after Apply brings up the dialog (below) again. + // Bug with KEditToolBar. + dialog.showButtonApply (false); + connect (&dialog, SIGNAL (newToolbarConfig ()), + this, SLOT (slotNewToolBarConfig ())); + + dialog.exec (); +} + +// private slot +void kpMainWindow::slotNewToolBarConfig () +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::slotNewToolBarConfig()" << endl; +#endif + + // Wouldn't it be nice if createGUI () didn't nuke all the KToolBar's? + // (including my non-XMLGUI ones whose states take a _lot_ of effort to + // restore). + // TODO: this message is probably unacceptable - so restore the state of + // my toolbars instead. + KMessageBox::information (this, + i18n ("You have to restart KolourPaint for these changes to take effect."), + i18n ("Toolbar Settings Changed"), + QString::fromLatin1 ("ToolBarSettingsChanged")); + + //createGUI(); + //applyMainWindowSettings (kapp->config (), autoSaveGroup ()); +} + + +// private slot +void kpMainWindow::slotConfigure () +{ + // TODO +} diff --git a/kolourpaint/kpmainwindow_statusbar.cpp b/kolourpaint/kpmainwindow_statusbar.cpp new file mode 100644 index 00000000..ed854604 --- /dev/null +++ b/kolourpaint/kpmainwindow_statusbar.cpp @@ -0,0 +1,417 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <kpmainwindow.h> + +#include <qlabel.h> +#include <qstring.h> + +#include <kdebug.h> +#include <klocale.h> +#include <kstatusbar.h> + +#include <kpdefs.h> +#include <kpdocument.h> +#include <kpsqueezedtextlabel.h> +#include <kptool.h> +#include <kpviewmanager.h> +#include <kpviewscrollablecontainer.h> +#include <kpzoomedview.h> + + +// private +void kpMainWindow::addPermanentStatusBarItem (int id, int maxTextLen) +{ + KStatusBar *sb = statusBar (); + + QString textWithMaxLen; + textWithMaxLen.fill (QString::number (8/*big fat*/).at (0), + maxTextLen); //+ 2/*spaces on either side*/); + + sb->insertFixedItem (textWithMaxLen, id, true/*permanent, place on the right*/); + sb->changeItem (QString::null, id); +} + +// private +void kpMainWindow::createStatusBar () +{ + KStatusBar *sb = statusBar (); + + // 9999 pixels "ought to be enough for anybody" + const int maxDimenLength = 4; + + //sb->insertItem (QString::null, StatusBarItemMessage, 1/*stretch*/); + //sb->setItemAlignment (StatusBarItemMessage, Qt::AlignLeft | Qt::AlignVCenter); + + m_statusBarMessageLabel = new kpSqueezedTextLabel (sb); + //m_statusBarMessageLabel->setShowEllipsis (false); + sb->addWidget (m_statusBarMessageLabel, 1/*stretch*/); + + addPermanentStatusBarItem (StatusBarItemShapePoints, + (maxDimenLength + 1/*,*/ + maxDimenLength) * 2 + 3/* - */); + addPermanentStatusBarItem (StatusBarItemShapeSize, + (1/*+/-*/ + maxDimenLength) * 2 + 1/*x*/); + + addPermanentStatusBarItem (StatusBarItemDocSize, + maxDimenLength + 1/*x*/ + maxDimenLength); + addPermanentStatusBarItem (StatusBarItemDocDepth, + 5/*XXbpp*/); + + addPermanentStatusBarItem (StatusBarItemZoom, + 5/*1600%*/); + + m_statusBarShapeLastPointsInitialised = false; + m_statusBarShapeLastSizeInitialised = false; + m_statusBarCreated = true; +} + + + +// private slot +void kpMainWindow::setStatusBarMessage (const QString &message) +{ +#if DEBUG_STATUS_BAR && 1 + kdDebug () << "kpMainWindow::setStatusBarMessage(" + << message + << ") ok=" << m_statusBarCreated + << endl; +#endif + + if (!m_statusBarCreated) + return; + + //statusBar ()->changeItem (message, StatusBarItemMessage); + m_statusBarMessageLabel->setText (message); +} + +// private slot +void kpMainWindow::setStatusBarShapePoints (const QPoint &startPoint, + const QPoint &endPoint) +{ +#if DEBUG_STATUS_BAR && 0 + kdDebug () << "kpMainWindow::setStatusBarShapePoints(" + << startPoint << "," << endPoint + << ") ok=" << m_statusBarCreated + << endl; +#endif + + if (!m_statusBarCreated) + return; + + if (m_statusBarShapeLastPointsInitialised && + startPoint == m_statusBarShapeLastStartPoint && + endPoint == m_statusBarShapeLastEndPoint) + { + #if DEBUG_STATUS_BAR && 0 + kdDebug () << "\tNOP" << endl; + #endif + return; + } + + if (startPoint == KP_INVALID_POINT) + { + statusBar ()->changeItem (QString::null, StatusBarItemShapePoints); + } + else if (endPoint == KP_INVALID_POINT) + { + statusBar ()->changeItem (i18n ("%1,%2") + .arg (startPoint.x ()) + .arg (startPoint.y ()), + StatusBarItemShapePoints); + } + else + { + statusBar ()->changeItem (i18n ("%1,%2 - %3,%4") + .arg (startPoint.x ()) + .arg (startPoint.y ()) + .arg (endPoint.x ()) + .arg (endPoint.y ()), + StatusBarItemShapePoints); + } + + m_statusBarShapeLastStartPoint = startPoint; + m_statusBarShapeLastEndPoint = endPoint; + m_statusBarShapeLastPointsInitialised = true; +} + +// private slot +void kpMainWindow::setStatusBarShapeSize (const QSize &size) +{ +#if DEBUG_STATUS_BAR && 0 + kdDebug () << "kpMainWindow::setStatusBarShapeSize(" + << size + << ") ok=" << m_statusBarCreated + << endl; +#endif + + if (!m_statusBarCreated) + return; + + if (m_statusBarShapeLastSizeInitialised && + size == m_statusBarShapeLastSize) + { + #if DEBUG_STATUS_BAR && 0 + kdDebug () << "\tNOP" << endl; + #endif + return; + } + + if (size == KP_INVALID_SIZE) + { + statusBar ()->changeItem (QString::null, StatusBarItemShapeSize); + } + else + { + statusBar ()->changeItem (i18n ("%1x%2") + .arg (size.width ()) + .arg (size.height ()), + StatusBarItemShapeSize); + } + + m_statusBarShapeLastSize = size; + m_statusBarShapeLastSizeInitialised = true; +} + +// private slot +void kpMainWindow::setStatusBarDocSize (const QSize &size) +{ +#if DEBUG_STATUS_BAR && 0 + kdDebug () << "kpMainWindow::setStatusBarDocSize(" + << size + << ") ok=" << m_statusBarCreated + << endl; +#endif + + if (!m_statusBarCreated) + return; + + if (size == KP_INVALID_SIZE) + { + statusBar ()->changeItem (QString::null, StatusBarItemDocSize); + } + else + { + statusBar ()->changeItem (i18n ("%1x%2") + .arg (size.width ()) + .arg (size.height ()), + StatusBarItemDocSize); + } +} + +// private slot +void kpMainWindow::setStatusBarDocDepth (int depth) +{ +#if DEBUG_STATUS_BAR && 0 + kdDebug () << "kpMainWindow::setStatusBarDocDepth(" + << depth + << ") ok=" << m_statusBarCreated + << endl; +#endif + + if (!m_statusBarCreated) + return; + + if (depth <= 0) + { + statusBar ()->changeItem (QString::null, StatusBarItemDocDepth); + } + else + { + statusBar ()->changeItem (i18n ("%1bpp").arg (depth), + StatusBarItemDocDepth); + } +} + +// private slot +void kpMainWindow::setStatusBarZoom (int zoom) +{ +#if DEBUG_STATUS_BAR && 0 + kdDebug () << "kpMainWindow::setStatusBarZoom(" + << zoom + << ") ok=" << m_statusBarCreated + << endl; +#endif + + if (!m_statusBarCreated) + return; + + if (zoom <= 0) + { + statusBar ()->changeItem (QString::null, StatusBarItemZoom); + } + else + { + statusBar ()->changeItem (i18n ("%1%").arg (zoom), + StatusBarItemZoom); + } +} + +void kpMainWindow::recalculateStatusBarMessage () +{ +#if DEBUG_STATUS_BAR && 1 + kdDebug () << "kpMainWindow::recalculateStatusBarMessage()" << endl; +#endif + QString scrollViewMessage = m_scrollView->statusMessage (); +#if DEBUG_STATUS_BAR && 1 + kdDebug () << "\tscrollViewMessage=" << scrollViewMessage << endl; + kdDebug () << "\tresizing doc? " << !m_scrollView->newDocSize ().isEmpty () + << endl; + kdDebug () << "\tviewUnderCursor? " + << (m_viewManager && m_viewManager->viewUnderCursor ()) + << endl; +#endif + + // HACK: To work around kpViewScrollableContainer's unreliable + // status messages (which in turn is due to Qt not updating + // QWidget::hasMouse() on drags and we needing to hack around it) + if (!scrollViewMessage.isEmpty () && + m_scrollView->newDocSize ().isEmpty () && + m_viewManager && m_viewManager->viewUnderCursor ()) + { + #if DEBUG_STATUS_BAR && 1 + kdDebug () << "\t\tnot resizing & viewUnderCursor - message is wrong - clearing" + << endl; + #endif + m_scrollView->blockSignals (true); + m_scrollView->clearStatusMessage (); + m_scrollView->blockSignals (false); + + scrollViewMessage = QString::null; + #if DEBUG_STATUS_BAR && 1 + kdDebug () << "\t\t\tdone" << endl; + #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 + kdDebug () << "kpMainWindow::recalculateStatusBarShape()" << endl; +#endif + + QSize docResizeTo = m_scrollView->newDocSize (); +#if DEBUG_STATUS_BAR && 0 + kdDebug () << "\tdocResizeTo=" << docResizeTo << endl; +#endif + if (docResizeTo.isValid ()) + { + const QPoint startPoint (m_document->width (), m_document->height ()); + #if DEBUG_STATUS_BAR && 0 + kdDebug () << "\thavedMovedFromOrgSize=" + << m_scrollView->haveMovedFromOriginalDocSize () << endl; + #endif + if (!m_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 + kdDebug () << "\ttool=" << t << endl; + #endif + if (t) + { + setStatusBarShapePoints (t->userShapeStartPoint (), + t->userShapeEndPoint ()); + setStatusBarShapeSize (t->userShapeSize ()); + } + else + { + setStatusBarShapePoints (); + setStatusBarShapeSize (); + } + } +} + +// private slot +void kpMainWindow::recalculateStatusBar () +{ +#if DEBUG_STATUS_BAR && 1 + kdDebug () << "kpMainWindow::recalculateStatusBar() ok=" + << m_statusBarCreated + << endl; +#endif + + if (!m_statusBarCreated) + return; + + recalculateStatusBarMessage (); + recalculateStatusBarShape (); + + if (m_document) + { + setStatusBarDocSize (QSize (m_document->width (), m_document->height ())); + setStatusBarDocDepth (m_document->pixmap ()->depth ()); + } + else + { + setStatusBarDocSize (); + setStatusBarDocDepth (); + } + + if (m_mainView) + { + setStatusBarZoom (m_mainView->zoomLevelX ()); + } + else + { + setStatusBarZoom (); + } +} diff --git a/kolourpaint/kpmainwindow_text.cpp b/kolourpaint/kpmainwindow_text.cpp new file mode 100644 index 00000000..d5694dea --- /dev/null +++ b/kolourpaint/kpmainwindow_text.cpp @@ -0,0 +1,395 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <kactionclasses.h> +#include <kapplication.h> + +#include <kconfig.h> +#include <kdebug.h> +#include <klocale.h> + +#include <kpcolortoolbar.h> +#include <kpdefs.h> +#include <kptextstyle.h> +#include <kptooltext.h> +#include <kptooltoolbar.h> +#include <kptoolwidgetopaqueortransparent.h> +#include <kpzoomedview.h> + + +// private +void kpMainWindow::setupTextToolBarActions () +{ + KActionCollection *ac = actionCollection (); + + m_actionTextFontFamily = new KFontAction (i18n ("Font Family"), 0/*shortcut*/, + this, SLOT (slotTextFontFamilyChanged ()), ac, "text_font_family"); + m_actionTextFontSize = new KFontSizeAction (i18n ("Font Size"), 0/*shortcut*/, + this, SLOT (slotTextFontSizeChanged ()), ac, "text_font_size"); + + m_actionTextBold = new KToggleAction (i18n ("Bold"), + "text_bold"/*icon*/, 0/*shortcut*/, + this, SLOT (slotTextBoldChanged ()), ac, "text_bold"); + m_actionTextItalic = new KToggleAction (i18n ("Italic"), + "text_italic"/*icon*/, 0/*shortcut*/, + this, SLOT (slotTextItalicChanged ()), ac, "text_italic"); + m_actionTextUnderline = new KToggleAction (i18n ("Underline"), + "text_under"/*icon*/, 0/*shortcut*/, + this, SLOT (slotTextUnderlineChanged ()), ac, "text_underline"); + m_actionTextStrikeThru = new KToggleAction (i18n ("Strike Through"), + "text_strike"/*icon*/, 0/*shortcut*/, + this, SLOT (slotTextStrikeThruChanged ()), ac, "text_strike_thru"); + + + readAndApplyTextSettings (); + + + enableTextToolBarActions (false); +} + +// private +void kpMainWindow::readAndApplyTextSettings () +{ + KConfigGroupSaver cfgGroupSaver (kapp->config (), kpSettingsGroupText); + KConfigBase *cfg = cfgGroupSaver.config (); + + m_actionTextFontFamily->setFont (cfg->readEntry (kpSettingFontFamily, QString::fromLatin1 ("Times"))); + m_actionTextFontSize->setFontSize (cfg->readNumEntry (kpSettingFontSize, 14)); + m_actionTextBold->setChecked (cfg->readBoolEntry (kpSettingBold, false)); + m_actionTextItalic->setChecked (cfg->readBoolEntry (kpSettingItalic, false)); + m_actionTextUnderline->setChecked (cfg->readBoolEntry (kpSettingUnderline, false)); + m_actionTextStrikeThru->setChecked (cfg->readBoolEntry (kpSettingStrikeThru, false)); + + m_textOldFontFamily = m_actionTextFontFamily->font (); + m_textOldFontSize = m_actionTextFontSize->fontSize (); +} + + +// public +void kpMainWindow::enableTextToolBarActions (bool enable) +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::enableTextToolBarActions(" << enable << ")" << endl; +#endif + + m_actionTextFontFamily->setEnabled (enable); + m_actionTextFontSize->setEnabled (enable); + m_actionTextBold->setEnabled (enable); + m_actionTextItalic->setEnabled (enable); + m_actionTextUnderline->setEnabled (enable); + m_actionTextStrikeThru->setEnabled (enable); + + if (textToolBar ()) + { + #if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\thave toolbar - setShown" << endl; + #endif + textToolBar ()->setShown (enable); + } +} + + +// private slot +void kpMainWindow::slotTextFontFamilyChanged () +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::slotTextFontFamilyChanged() alive=" + << m_isFullyConstructed + << " fontFamily=" + << m_actionTextFontFamily->font () + << endl; +#endif + + if (!m_isFullyConstructed) + return; + + if (m_toolText && m_toolText->hasBegun ()) + { + m_toolText->slotFontFamilyChanged (m_actionTextFontFamily->font (), + m_textOldFontFamily); + } + + // Since editable KSelectAction's steal focus from view, switch back to mainView + // TODO: back to the last view + if (m_mainView) + m_mainView->setFocus (); + + KConfigGroupSaver cfgGroupSaver (kapp->config (), kpSettingsGroupText); + KConfigBase *cfg = cfgGroupSaver.config (); + cfg->writeEntry (kpSettingFontFamily, m_actionTextFontFamily->font ()); + cfg->sync (); + + m_textOldFontFamily = m_actionTextFontFamily->font (); +} + +// private slot +void kpMainWindow::slotTextFontSizeChanged () +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::slotTextFontSizeChanged() alive=" + << m_isFullyConstructed + << " fontSize=" + << m_actionTextFontSize->fontSize () + << endl; +#endif + + if (!m_isFullyConstructed) + return; + + if (m_toolText && m_toolText->hasBegun ()) + { + m_toolText->slotFontSizeChanged (m_actionTextFontSize->fontSize (), + m_textOldFontSize); + } + + // Since editable KSelectAction's steal focus from view, switch back to mainView + // TODO: back to the last view + if (m_mainView) + m_mainView->setFocus (); + + KConfigGroupSaver cfgGroupSaver (kapp->config (), kpSettingsGroupText); + KConfigBase *cfg = cfgGroupSaver.config (); + cfg->writeEntry (kpSettingFontSize, m_actionTextFontSize->fontSize ()); + cfg->sync (); + + m_textOldFontSize = m_actionTextFontSize->fontSize (); +} + +// private slot +void kpMainWindow::slotTextBoldChanged () +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::slotTextFontBoldChanged() alive=" + << m_isFullyConstructed + << " bold=" + << m_actionTextBold->isChecked () + << endl; +#endif + + if (!m_isFullyConstructed) + return; + + if (m_toolText && m_toolText->hasBegun ()) + m_toolText->slotBoldChanged (m_actionTextBold->isChecked ()); + + KConfigGroupSaver cfgGroupSaver (kapp->config (), kpSettingsGroupText); + KConfigBase *cfg = cfgGroupSaver.config (); + cfg->writeEntry (kpSettingBold, m_actionTextBold->isChecked ()); + cfg->sync (); +} + +// private slot +void kpMainWindow::slotTextItalicChanged () +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::slotTextFontItalicChanged() alive=" + << m_isFullyConstructed + << " bold=" + << m_actionTextItalic->isChecked () + << endl; +#endif + + if (!m_isFullyConstructed) + return; + + if (m_toolText && m_toolText->hasBegun ()) + m_toolText->slotItalicChanged (m_actionTextItalic->isChecked ()); + + KConfigGroupSaver cfgGroupSaver (kapp->config (), kpSettingsGroupText); + KConfigBase *cfg = cfgGroupSaver.config (); + cfg->writeEntry (kpSettingItalic, m_actionTextItalic->isChecked ()); + cfg->sync (); +} + +// private slot +void kpMainWindow::slotTextUnderlineChanged () +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::slotTextFontUnderlineChanged() alive=" + << m_isFullyConstructed + << " underline=" + << m_actionTextUnderline->isChecked () + << endl; +#endif + + if (!m_isFullyConstructed) + return; + + if (m_toolText && m_toolText->hasBegun ()) + m_toolText->slotUnderlineChanged (m_actionTextUnderline->isChecked ()); + + KConfigGroupSaver cfgGroupSaver (kapp->config (), kpSettingsGroupText); + KConfigBase *cfg = cfgGroupSaver.config (); + cfg->writeEntry (kpSettingUnderline, m_actionTextUnderline->isChecked ()); + cfg->sync (); +} + +// private slot +void kpMainWindow::slotTextStrikeThruChanged () +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::slotTextStrikeThruChanged() alive=" + << m_isFullyConstructed + << " strikeThru=" + << m_actionTextStrikeThru->isChecked () + << endl; +#endif + + if (!m_isFullyConstructed) + return; + + if (m_toolText && m_toolText->hasBegun ()) + m_toolText->slotStrikeThruChanged (m_actionTextStrikeThru->isChecked ()); + + KConfigGroupSaver cfgGroupSaver (kapp->config (), kpSettingsGroupText); + KConfigBase *cfg = cfgGroupSaver.config (); + cfg->writeEntry (kpSettingStrikeThru, m_actionTextStrikeThru->isChecked ()); + cfg->sync (); +} + + +// public +KToolBar *kpMainWindow::textToolBar () +{ + return toolBar ("textToolBar"); +} + +bool kpMainWindow::isTextStyleBackgroundOpaque () const +{ + if (m_toolToolBar) + { + kpToolWidgetOpaqueOrTransparent *oot = + m_toolToolBar->toolWidgetOpaqueOrTransparent (); + + if (oot) + { + return oot->isOpaque (); + } + } + + return true; +} + +// public +kpTextStyle kpMainWindow::textStyle () const +{ + return kpTextStyle (m_actionTextFontFamily->font (), + m_actionTextFontSize->fontSize (), + m_actionTextBold->isChecked (), + m_actionTextItalic->isChecked (), + m_actionTextUnderline->isChecked (), + m_actionTextStrikeThru->isChecked (), + m_colorToolBar ? m_colorToolBar->foregroundColor () : kpColor::invalid, + m_colorToolBar ? m_colorToolBar->backgroundColor () : kpColor::invalid, + isTextStyleBackgroundOpaque ()); +} + +// public +void kpMainWindow::setTextStyle (const kpTextStyle &textStyle_) +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::setTextStyle()" << endl; +#endif + + m_settingTextStyle++; + + + if (textStyle_.fontFamily () != m_actionTextFontFamily->font ()) + { + m_actionTextFontFamily->setFont (textStyle_.fontFamily ()); + slotTextFontFamilyChanged (); + } + + if (textStyle_.fontSize () != m_actionTextFontSize->fontSize ()) + { + m_actionTextFontSize->setFontSize (textStyle_.fontSize ()); + slotTextFontSizeChanged (); + } + + if (textStyle_.isBold () != m_actionTextBold->isChecked ()) + { + m_actionTextBold->setChecked (textStyle_.isBold ()); + slotTextBoldChanged (); + } + + if (textStyle_.isItalic () != m_actionTextItalic->isChecked ()) + { + m_actionTextItalic->setChecked (textStyle_.isItalic ()); + slotTextItalicChanged (); + } + + if (textStyle_.isUnderline () != m_actionTextUnderline->isChecked ()) + { + m_actionTextUnderline->setChecked (textStyle_.isUnderline ()); + slotTextUnderlineChanged (); + } + + if (textStyle_.isStrikeThru () != m_actionTextStrikeThru->isChecked ()) + { + m_actionTextStrikeThru->setChecked (textStyle_.isStrikeThru ()); + slotTextStrikeThruChanged (); + } + + + if (textStyle_.foregroundColor () != m_colorToolBar->foregroundColor ()) + { + m_colorToolBar->setForegroundColor (textStyle_.foregroundColor ()); + } + + if (textStyle_.backgroundColor () != m_colorToolBar->backgroundColor ()) + { + m_colorToolBar->setBackgroundColor (textStyle_.backgroundColor ()); + } + + + if (textStyle_.isBackgroundOpaque () != isTextStyleBackgroundOpaque ()) + { + if (m_toolToolBar) + { + kpToolWidgetOpaqueOrTransparent *oot = + m_toolToolBar->toolWidgetOpaqueOrTransparent (); + + if (oot) + { + oot->setOpaque (textStyle_.isBackgroundOpaque ()); + } + } + } + + + m_settingTextStyle--; +} + +// public +int kpMainWindow::settingTextStyle () const +{ + return m_settingTextStyle; +} + diff --git a/kolourpaint/kpmainwindow_tools.cpp b/kolourpaint/kpmainwindow_tools.cpp new file mode 100644 index 00000000..fb86f91f --- /dev/null +++ b/kolourpaint/kpmainwindow_tools.cpp @@ -0,0 +1,646 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <kapplication.h> +#include <kconfig.h> +#include <kdebug.h> +#include <klocale.h> + +#include <kpcolortoolbar.h> +#include <kpcommandhistory.h> +#include <kpdocument.h> +#include <kpselectiontransparency.h> +#include <kpsinglekeytriggersaction.h> +#include <kptool.h> +#include <kptoolaction.h> +#include <kptoolairspray.h> +#include <kptoolbrush.h> +#include <kptoolcolorpicker.h> +#include <kptoolcolorwasher.h> +#include <kptoolcurve.h> +#include <kptoolellipticalselection.h> +#include <kptoolellipse.h> +#include <kptooleraser.h> +#include <kptoolfloodfill.h> +#include <kptoolfreeformselection.h> +#include <kptoolline.h> +#include <kptoolpen.h> +#include <kptoolpolygon.h> +#include <kptoolpolyline.h> +#include <kptoolrectangle.h> +#include <kptoolrectselection.h> +#include <kptoolresizescale.h> +#include <kptoolroundedrectangle.h> +#include <kptooltext.h> +#include <kptooltoolbar.h> +#include <kptoolwidgetopaqueortransparent.h> +#include <kpviewscrollablecontainer.h> +#include <kpzoomedview.h> + + +// private +void kpMainWindow::setupToolActions () +{ + m_tools.setAutoDelete (true); + + m_tools.append (m_toolFreeFormSelection = new kpToolFreeFormSelection (this)); + m_tools.append (m_toolRectSelection = new kpToolRectSelection (this)); + + m_tools.append (m_toolEllipticalSelection = new kpToolEllipticalSelection (this)); + m_tools.append (m_toolText = new kpToolText (this)); + + m_tools.append (m_toolLine = new kpToolLine (this)); + m_tools.append (m_toolPen = new kpToolPen (this)); + + m_tools.append (m_toolEraser = new kpToolEraser (this)); + m_tools.append (m_toolBrush = new kpToolBrush (this)); + + m_tools.append (m_toolFloodFill = new kpToolFloodFill (this)); + m_tools.append (m_toolColorPicker = new kpToolColorPicker (this)); + + m_tools.append (m_toolColorWasher = new kpToolColorWasher (this)); + m_tools.append (m_toolAirSpray = new kpToolAirSpray (this)); + + m_tools.append (m_toolRoundedRectangle = new kpToolRoundedRectangle (this)); + m_tools.append (m_toolRectangle = new kpToolRectangle (this)); + + m_tools.append (m_toolPolygon = new kpToolPolygon (this)); + m_tools.append (m_toolEllipse = new kpToolEllipse (this)); + + m_tools.append (m_toolPolyline = new kpToolPolyline (this)); + m_tools.append (m_toolCurve = new kpToolCurve (this)); + + + KActionCollection *ac = actionCollection (); + + m_actionPrevToolOptionGroup1 = new kpSingleKeyTriggersAction ( + i18n ("Previous Tool Option (Group #1)"), + kpTool::shortcutForKey (Qt::Key_1), + this, SLOT (slotActionPrevToolOptionGroup1 ()), + ac, "prev_tool_option_group_1"); + m_actionNextToolOptionGroup1 = new kpSingleKeyTriggersAction ( + i18n ("Next Tool Option (Group #1)"), + kpTool::shortcutForKey (Qt::Key_2), + this, SLOT (slotActionNextToolOptionGroup1 ()), + ac, "next_tool_option_group_1"); + + m_actionPrevToolOptionGroup2 = new kpSingleKeyTriggersAction ( + i18n ("Previous Tool Option (Group #2)"), + kpTool::shortcutForKey (Qt::Key_3), + this, SLOT (slotActionPrevToolOptionGroup2 ()), + ac, "prev_tool_option_group_2"); + m_actionNextToolOptionGroup2 = new kpSingleKeyTriggersAction ( + i18n ("Next Tool Option (Group #2)"), + kpTool::shortcutForKey (Qt::Key_4), + this, SLOT (slotActionNextToolOptionGroup2 ()), + ac, "next_tool_option_group_2"); +} + +// private +void kpMainWindow::createToolBox () +{ + m_toolToolBar = new kpToolToolBar (i18n ("Tool Box"), this, 2/*columns/rows*/, "Tool Box"); + connect (m_toolToolBar, SIGNAL (sigToolSelected (kpTool *)), + this, SLOT (slotToolSelected (kpTool *))); + connect (m_toolToolBar, SIGNAL (toolWidgetOptionSelected ()), + this, SLOT (updateToolOptionPrevNextActionsEnabled ())); + + for (QPtrList <kpTool>::const_iterator it = m_tools.begin (); + it != m_tools.end (); + it++) + { + m_toolToolBar->registerTool (*it); + } + + + // (from config file) + readLastTool (); + + + enableToolsDocumentActions (false); +} + +// private +void kpMainWindow::enableToolsDocumentActions (bool enable) +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::enableToolsDocumentsAction(" << enable << ")" << endl; +#endif + + m_toolActionsEnabled = enable; + + + if (enable && !m_toolToolBar->isEnabled ()) + { + kpTool *previousTool = m_toolToolBar->previousTool (); + + // select tool for enabled Tool Box + + if (previousTool) + m_toolToolBar->selectPreviousTool (); + else + { + if (m_lastToolNumber >= 0 && m_lastToolNumber < (int) m_tools.count ()) + m_toolToolBar->selectTool (m_tools.at (m_lastToolNumber)); + else + m_toolToolBar->selectTool (m_toolPen); + } + } + else if (!enable && m_toolToolBar->isEnabled ()) + { + // don't have a disabled Tool Box with an enabled Tool + m_toolToolBar->selectTool (0); + } + + + m_toolToolBar->setEnabled (enable); + + + for (QPtrList <kpTool>::const_iterator it = m_tools.begin (); + it != m_tools.end (); + it++) + { + kpToolAction *action = (*it)->action (); + if (action) + { + #if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\tchanging enabled state of " << (*it)->name () << endl; + #endif + + if (!enable && action->isChecked ()) + action->setChecked (false); + + action->setEnabled (enable); + } + else + { + #if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\tno action for " << (*it)->name () << endl; + #endif + } + } + + + updateToolOptionPrevNextActionsEnabled (); +} + +// private slot +void kpMainWindow::updateToolOptionPrevNextActionsEnabled () +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::updateToolOptionPrevNextActionsEnabled()" + << " numShownToolWidgets=" + << m_toolToolBar->numShownToolWidgets () + << endl; +#endif + + const bool enable = m_toolActionsEnabled; + + + m_actionPrevToolOptionGroup1->setEnabled (enable && + m_toolToolBar->shownToolWidget (0) && + m_toolToolBar->shownToolWidget (0)->hasPreviousOption ()); + m_actionNextToolOptionGroup1->setEnabled (enable && + m_toolToolBar->shownToolWidget (0) && + m_toolToolBar->shownToolWidget (0)->hasNextOption ()); + + m_actionPrevToolOptionGroup2->setEnabled (enable && + m_toolToolBar->shownToolWidget (1) && + m_toolToolBar->shownToolWidget (1)->hasPreviousOption ()); + m_actionNextToolOptionGroup2->setEnabled (enable && + m_toolToolBar->shownToolWidget (1) && + m_toolToolBar->shownToolWidget (1)->hasNextOption ()); +} + + +// public +kpTool *kpMainWindow::tool () const +{ + return m_toolToolBar ? m_toolToolBar->tool () : 0; +} + +// public +bool kpMainWindow::toolHasBegunShape () const +{ + kpTool *currentTool = tool (); + return (currentTool && currentTool->hasBegunShape ()); +} + +// public +bool kpMainWindow::toolIsASelectionTool (bool includingTextTool) const +{ + kpTool *currentTool = tool (); + + return ((currentTool == m_toolFreeFormSelection) || + (currentTool == m_toolRectSelection) || + (currentTool == m_toolEllipticalSelection) || + (currentTool == m_toolText && includingTextTool)); +} + +// public +bool kpMainWindow::toolIsTextTool () const +{ + return (tool () == m_toolText); +} + + +// public +kpSelectionTransparency kpMainWindow::selectionTransparency () const +{ + kpToolWidgetOpaqueOrTransparent *oot = m_toolToolBar->toolWidgetOpaqueOrTransparent (); + if (!oot) + { + kdError () << "kpMainWindow::selectionTransparency() without opaqueOrTransparent widget" << endl; + return kpSelectionTransparency (); + } + + return kpSelectionTransparency (oot->isOpaque (), backgroundColor (), m_colorToolBar->colorSimilarity ()); +} + +// public +void kpMainWindow::setSelectionTransparency (const kpSelectionTransparency &transparency, bool forceColorChange) +{ +#if DEBUG_KP_MAIN_WINDOW && 1 + kdDebug () << "kpMainWindow::setSelectionTransparency() isOpaque=" << transparency.isOpaque () + << " color=" << (transparency.transparentColor ().isValid () ? (int *) transparency.transparentColor ().toQRgb () : 0) + << " forceColorChange=" << forceColorChange + << endl; +#endif + + kpToolWidgetOpaqueOrTransparent *oot = m_toolToolBar->toolWidgetOpaqueOrTransparent (); + if (!oot) + { + kdError () << "kpMainWindow::setSelectionTransparency() without opaqueOrTransparent widget" << endl; + return; + } + + m_settingSelectionTransparency++; + + oot->setOpaque (transparency.isOpaque ()); + if (transparency.isTransparent () || forceColorChange) + { + m_colorToolBar->setColor (1, transparency.transparentColor ()); + m_colorToolBar->setColorSimilarity (transparency.colorSimilarity ()); + } + + m_settingSelectionTransparency--; +} + +// public +int kpMainWindow::settingSelectionTransparency () const +{ + return m_settingSelectionTransparency; +} + + +// private slot +void kpMainWindow::slotToolSelected (kpTool *tool) +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::slotToolSelected (" << tool << ")" << endl; +#endif + + kpTool *previousTool = m_toolToolBar ? m_toolToolBar->previousTool () : 0; + + if (previousTool) + { + disconnect (previousTool, SIGNAL (movedAndAboutToDraw (const QPoint &, const QPoint &, int, bool *)), + this, SLOT (slotDragScroll (const QPoint &, const QPoint &, int, bool *))); + disconnect (previousTool, SIGNAL (endedDraw (const QPoint &)), + this, SLOT (slotEndDragScroll ())); + disconnect (previousTool, SIGNAL (cancelledShape (const QPoint &)), + this, SLOT (slotEndDragScroll ())); + + disconnect (previousTool, SIGNAL (userMessageChanged (const QString &)), + this, SLOT (recalculateStatusBarMessage ())); + disconnect (previousTool, SIGNAL (userShapePointsChanged (const QPoint &, const QPoint &)), + this, SLOT (recalculateStatusBarShape ())); + disconnect (previousTool, SIGNAL (userShapeSizeChanged (const QSize &)), + this, SLOT (recalculateStatusBarShape ())); + + disconnect (m_colorToolBar, SIGNAL (colorsSwapped (const kpColor &, const kpColor &)), + previousTool, SLOT (slotColorsSwappedInternal (const kpColor &, const kpColor &))); + disconnect (m_colorToolBar, SIGNAL (foregroundColorChanged (const kpColor &)), + previousTool, SLOT (slotForegroundColorChangedInternal (const kpColor &))); + disconnect (m_colorToolBar, SIGNAL (backgroundColorChanged (const kpColor &)), + previousTool, SLOT (slotBackgroundColorChangedInternal (const kpColor &))); + disconnect (m_colorToolBar, SIGNAL (colorSimilarityChanged (double, int)), + previousTool, SLOT (slotColorSimilarityChangedInternal (double, int))); + } + + if (tool) + { + connect (tool, SIGNAL (movedAndAboutToDraw (const QPoint &, const QPoint &, int, bool *)), + this, SLOT (slotDragScroll (const QPoint &, const QPoint &, int, bool *))); + connect (tool, SIGNAL (endedDraw (const QPoint &)), + this, SLOT (slotEndDragScroll ())); + connect (tool, SIGNAL (cancelledShape (const QPoint &)), + this, SLOT (slotEndDragScroll ())); + + connect (tool, SIGNAL (userMessageChanged (const QString &)), + this, SLOT (recalculateStatusBarMessage ())); + connect (tool, SIGNAL (userShapePointsChanged (const QPoint &, const QPoint &)), + this, SLOT (recalculateStatusBarShape ())); + connect (tool, SIGNAL (userShapeSizeChanged (const QSize &)), + this, SLOT (recalculateStatusBarShape ())); + recalculateStatusBar (); + + connect (m_colorToolBar, SIGNAL (colorsSwapped (const kpColor &, const kpColor &)), + tool, SLOT (slotColorsSwappedInternal (const kpColor &, const kpColor &))); + connect (m_colorToolBar, SIGNAL (foregroundColorChanged (const kpColor &)), + tool, SLOT (slotForegroundColorChangedInternal (const kpColor &))); + connect (m_colorToolBar, SIGNAL (backgroundColorChanged (const kpColor &)), + tool, SLOT (slotBackgroundColorChangedInternal (const kpColor &))); + connect (m_colorToolBar, SIGNAL (colorSimilarityChanged (double, int)), + tool, SLOT (slotColorSimilarityChangedInternal (double, int))); + + + saveLastTool (); + } + + updateToolOptionPrevNextActionsEnabled (); +} + + +// private +void kpMainWindow::readLastTool () +{ + KConfigGroupSaver cfgGroupSaver (kapp->config (), kpSettingsGroupTools); + KConfigBase *cfg = cfgGroupSaver.config (); + + m_lastToolNumber = cfg->readNumEntry (kpSettingLastTool, -1); +} + + +// private +int kpMainWindow::toolNumber () const +{ + int number = 0; + for (QPtrList <kpTool>::const_iterator it = m_tools.begin (); + it != m_tools.end (); + it++) + { + if (*it == tool ()) + return number; + + number++; + } + + return -1; +} + +// private +void kpMainWindow::saveLastTool () +{ + int number = toolNumber (); + if (number < 0 || number >= (int) m_tools.count ()) + return; + + + KConfigGroupSaver cfgGroupSaver (kapp->config (), kpSettingsGroupTools); + KConfigBase *cfg = cfgGroupSaver.config (); + + cfg->writeEntry (kpSettingLastTool, number); + cfg->sync (); +} + + +// private +bool kpMainWindow::maybeDragScrollingMainView () const +{ + return (tool () && m_mainView && + tool ()->viewUnderStartPoint () == m_mainView); +} + +// private slot +bool kpMainWindow::slotDragScroll (const QPoint &docPoint, + const QPoint &docLastPoint, + int zoomLevel, + bool *scrolled) +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::slotDragScroll() maybeDragScrolling=" + << maybeDragScrollingMainView () + << endl; +#endif + + if (maybeDragScrollingMainView ()) + { + return m_scrollView->beginDragScroll (docPoint, docLastPoint, zoomLevel, scrolled); + } + else + { + return false; + } +} + +// private slot +bool kpMainWindow::slotEndDragScroll () +{ + // (harmless if haven't started drag scroll) + return m_scrollView->endDragScroll (); +} + + +// private slot +void kpMainWindow::slotBeganDocResize () +{ + if (toolHasBegunShape ()) + tool ()->endShapeInternal (); + + 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() \ +{ \ + m_docResizeToBeCompleted = false; \ + recalculateStatusBar (); \ +} + + // Prevent statusbar updates + m_docResizeToBeCompleted = true; + + m_docResizeWidth = (size.width () > 0 ? size.width () : 1), + m_docResizeHeight = (size.height () > 0 ? size.height () : 1); + + if (m_docResizeWidth == m_document->width () && + m_docResizeHeight == m_document->height ()) + { + DOC_RESIZE_COMPLETED (); + return; + } + + + // Blank status to avoid confusion if dialog comes up + setStatusBarMessage (); + setStatusBarShapePoints (); + setStatusBarShapeSize (); + + + if (kpTool::warnIfBigImageSize (m_document->width (), + m_document->height (), + m_docResizeWidth, m_docResizeHeight, + i18n ("<qt><p>Resizing the image to" + " %1x%2 may take a substantial amount of memory." + " This can reduce system" + " responsiveness and cause other application resource" + " problems.</p>" + + "<p>Are you sure want to resize the" + " image?</p></qt>") + .arg (m_docResizeWidth) + .arg (m_docResizeHeight), + i18n ("Resize Image?"), + i18n ("R&esize Image"), + this)) + { + m_commandHistory->addCommand ( + new kpToolResizeScaleCommand ( + false/*doc, not sel*/, + m_docResizeWidth, m_docResizeHeight, + kpToolResizeScaleCommand::Resize, + this)); + + saveDefaultDocSize (QSize (m_docResizeWidth, m_docResizeHeight)); + } + + + DOC_RESIZE_COMPLETED (); + +#undef DOC_RESIZE_COMPLETED +} + +// private slot +void kpMainWindow::slotDocResizeMessageChanged (const QString &string) +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::slotDocResizeMessageChanged(" << string + << ") docResizeToBeCompleted=" << m_docResizeToBeCompleted + << endl; +#else + (void) string; +#endif + + if (m_docResizeToBeCompleted) + return; + + recalculateStatusBarMessage (); +} + + +// private slot +void kpMainWindow::slotActionPrevToolOptionGroup1 () +{ + if (!m_toolToolBar->shownToolWidget (0)) + return; + + m_toolToolBar->shownToolWidget (0)->selectPreviousOption (); + updateToolOptionPrevNextActionsEnabled (); +} + +// private slot +void kpMainWindow::slotActionNextToolOptionGroup1 () +{ + if (!m_toolToolBar->shownToolWidget (0)) + return; + + m_toolToolBar->shownToolWidget (0)->selectNextOption (); + updateToolOptionPrevNextActionsEnabled (); +} + + +// private slot +void kpMainWindow::slotActionPrevToolOptionGroup2 () +{ + if (!m_toolToolBar->shownToolWidget (1)) + return; + + m_toolToolBar->shownToolWidget (1)->selectPreviousOption (); + updateToolOptionPrevNextActionsEnabled (); +} + +// private slot +void kpMainWindow::slotActionNextToolOptionGroup2 () +{ + if (!m_toolToolBar->shownToolWidget (1)) + return; + + m_toolToolBar->shownToolWidget (1)->selectNextOption (); + updateToolOptionPrevNextActionsEnabled (); +} + + +// public slots + +#define SLOT_TOOL(toolName) \ +void kpMainWindow::slotTool##toolName () \ +{ \ + if (!m_toolToolBar) \ + return; \ + \ + if (tool () == m_tool##toolName) \ + return; \ + \ + m_toolToolBar->selectTool (m_tool##toolName); \ +} + +SLOT_TOOL (AirSpray) +SLOT_TOOL (Brush) +SLOT_TOOL (ColorPicker) +SLOT_TOOL (ColorWasher) +SLOT_TOOL (Curve) +SLOT_TOOL (Ellipse) +SLOT_TOOL (EllipticalSelection) +SLOT_TOOL (Eraser) +SLOT_TOOL (FloodFill) +SLOT_TOOL (FreeFormSelection) +SLOT_TOOL (Line) +SLOT_TOOL (Pen) +SLOT_TOOL (Polygon) +SLOT_TOOL (Polyline) +SLOT_TOOL (Rectangle) +SLOT_TOOL (RectSelection) +SLOT_TOOL (RoundedRectangle) +SLOT_TOOL (Text) diff --git a/kolourpaint/kpmainwindow_view.cpp b/kolourpaint/kpmainwindow_view.cpp new file mode 100644 index 00000000..1aa9b5dc --- /dev/null +++ b/kolourpaint/kpmainwindow_view.cpp @@ -0,0 +1,1151 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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_ZOOM_FLICKER 0 + +#include <kpmainwindow.h> +#include <kpmainwindow_p.h> + +#include <qdatetime.h> +#include <qpainter.h> +#include <qtimer.h> + +#include <kactionclasses.h> +#include <kapplication.h> +#include <kconfig.h> +#include <kdebug.h> +#include <klocale.h> +#include <kstdaction.h> + +#include <kpdefs.h> +#include <kpdocument.h> +#include <kpthumbnail.h> +#include <kptool.h> +#include <kptooltoolbar.h> +#include <kpunzoomedthumbnailview.h> +#include <kpviewmanager.h> +#include <kpviewscrollablecontainer.h> +#include <kpwidgetmapper.h> +#include <kpzoomedview.h> +#include <kpzoomedthumbnailview.h> + + +// private +void kpMainWindow::setupViewMenuActions () +{ + m_viewMenuDocumentActionsEnabled = false; + m_thumbnailSaveConfigTimer = 0; + + + KActionCollection *ac = actionCollection (); + + /*m_actionFullScreen = KStdAction::fullScreen (0, 0, ac); + m_actionFullScreen->setEnabled (false);*/ + + + m_actionActualSize = KStdAction::actualSize (this, SLOT (slotActualSize ()), ac); + /*m_actionFitToPage = KStdAction::fitToPage (this, SLOT (slotFitToPage ()), ac); + m_actionFitToWidth = KStdAction::fitToWidth (this, SLOT (slotFitToWidth ()), ac); + m_actionFitToHeight = KStdAction::fitToHeight (this, SLOT (slotFitToHeight ()), ac);*/ + + + m_actionZoomIn = KStdAction::zoomIn (this, SLOT (slotZoomIn ()), ac); + m_actionZoomOut = KStdAction::zoomOut (this, SLOT (slotZoomOut ()), ac); + + + m_actionZoom = new KSelectAction (i18n ("&Zoom"), 0, + this, SLOT (slotZoom ()), actionCollection (), "view_zoom_to"); + m_actionZoom->setEditable (true); + + // create the zoom list for the 1st call to zoomTo() below + m_zoomList.append (10); m_zoomList.append (25); m_zoomList.append (33); + m_zoomList.append (50); m_zoomList.append (67); m_zoomList.append (75); + m_zoomList.append (100); + m_zoomList.append (200); m_zoomList.append (300); + m_zoomList.append (400); m_zoomList.append (600); m_zoomList.append (800); + m_zoomList.append (1000); m_zoomList.append (1200); m_zoomList.append (1600); + + + m_actionShowGrid = new KToggleAction (i18n ("Show &Grid"), CTRL + Key_G, + this, SLOT (slotShowGridToggled ()), actionCollection (), "view_show_grid"); + m_actionShowGrid->setCheckedState (i18n ("Hide &Grid")); + + + // 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. + m_actionShowThumbnail = new KToggleAction (i18n ("Show T&humbnail"), CTRL + Key_H, + this, SLOT (slotShowThumbnailToggled ()), actionCollection (), "view_show_thumbnail"); + m_actionShowThumbnail->setCheckedState (i18n ("Hide T&humbnail")); + + // Please do not use setCheckedState() here - it wouldn't make sense + m_actionZoomedThumbnail = new KToggleAction (i18n ("Zoo&med Thumbnail Mode"), 0, + this, SLOT (slotZoomedThumbnailToggled ()), actionCollection (), "view_zoomed_thumbnail"); + + // 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->m_actionShowThumbnailRectangle = new KToggleAction ( + i18n ("Enable Thumbnail &Rectangle"), + 0, + this, SLOT (slotThumbnailShowRectangleToggled ()), + actionCollection (), "view_show_thumbnail_rectangle"); + + + enableViewMenuDocumentActions (false); +} + +// private +bool kpMainWindow::viewMenuDocumentActionsEnabled () const +{ + return m_viewMenuDocumentActionsEnabled; +} + +// private +void kpMainWindow::enableViewMenuDocumentActions (bool enable) +{ + m_viewMenuDocumentActionsEnabled = enable; + + + m_actionActualSize->setEnabled (enable); + /*m_actionFitToPage->setEnabled (enable); + m_actionFitToWidth->setEnabled (enable); + m_actionFitToHeight->setEnabled (enable);*/ + + m_actionZoomIn->setEnabled (enable); + m_actionZoomOut->setEnabled (enable); + + m_actionZoom->setEnabled (enable); + + actionShowGridUpdate (); + + m_actionShowThumbnail->setEnabled (enable); + enableThumbnailOptionActions (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::actionShowGridUpdate () +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::actionShowGridUpdate()" << endl; +#endif + const bool enable = (viewMenuDocumentActionsEnabled () && + m_mainView && m_mainView->canShowGrid ()); + + m_actionShowGrid->setEnabled (enable); + m_actionShowGrid->setChecked (enable && m_configShowGrid); +} + + +// private +void kpMainWindow::sendZoomListToActionZoom () +{ + QStringList items; + + const QValueVector <int>::ConstIterator zoomListEnd (m_zoomList.end ()); + for (QValueVector <int>::ConstIterator it = m_zoomList.begin (); + 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 = m_actionZoom->isEnabled (); + m_actionZoom->setItems (items); + if (e != m_actionZoom->isEnabled ()) + m_actionZoom->setEnabled (e); +} + +// private +int kpMainWindow::zoomLevelFromString (const QString &string) +{ + // loop until not digit + int i; + for (i = 0; i < (int) string.length () && string.at (i).isDigit (); i++) + ; + + // convert zoom level to number + bool ok = false; + int zoomLevel = string.left (i).toInt (&ok); + + if (!ok || zoomLevel <= 0 || zoomLevel > 3200) + return 0; // error + else + return zoomLevel; +} + +// private +QString kpMainWindow::zoomLevelToString (int zoomLevel) +{ + return i18n ("%1%").arg (zoomLevel); +} + +// private +void kpMainWindow::zoomTo (int zoomLevel, bool centerUnderCursor) +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::zoomTo (" << zoomLevel << ")" << endl; +#endif + + if (zoomLevel <= 0) + zoomLevel = m_mainView ? m_mainView->zoomLevelX () : 100; + +// mute point since the thumbnail suffers from this too +#if 0 + else if (m_mainView && m_mainView->zoomLevelX () % 100 == 0 && zoomLevel % 100) + { + if (KMessageBox::warningContinueCancel (this, + i18n ("Setting the zoom level to a value that is not a multiple of 100% " + "results in imprecise editing and redraw glitches.\n" + "Do you really want to set to zoom level to %1%?") + .arg (zoomLevel), + QString::null/*caption*/, + i18n ("Set Zoom Level to %1%").arg (zoomLevel), + "DoNotAskAgain_ZoomLevelNotMultipleOf100") != KMessageBox::Continue) + { + zoomLevel = m_mainView->zoomLevelX (); + } + } +#endif + + int index = 0; + QValueVector <int>::Iterator it = m_zoomList.begin (); + + while (index < (int) m_zoomList.count () && zoomLevel > *it) + it++, index++; + + if (zoomLevel != *it) + m_zoomList.insert (it, zoomLevel); + + sendZoomListToActionZoom (); + m_actionZoom->setCurrentItem (index); + + + if (viewMenuDocumentActionsEnabled ()) + { + m_actionActualSize->setEnabled (zoomLevel != 100); + + m_actionZoomIn->setEnabled (m_actionZoom->currentItem () < (int) m_zoomList.count () - 1); + m_actionZoomOut->setEnabled (m_actionZoom->currentItem () > 0); + } + + + if (m_viewManager) + m_viewManager->setQueueUpdates (); + + + if (m_scrollView) + { + m_scrollView->setUpdatesEnabled (false); + if (m_scrollView->viewport ()) + m_scrollView->viewport ()->setUpdatesEnabled (false); + } + + if (m_mainView) + { + m_mainView->setUpdatesEnabled (false); + + if (m_scrollView && m_scrollView->viewport ()) + { + // Ordinary flicker is better than the whole view moving + QPainter p (m_mainView); + p.fillRect (m_mainView->rect (), + m_scrollView->viewport ()->colorGroup ().background ()); + } + } + + + if (m_scrollView && m_mainView) + { + #if DEBUG_KP_MAIN_WINDOW && 1 + kdDebug () << "\tscrollView contentsX=" << m_scrollView->contentsX () + << " contentsY=" << m_scrollView->contentsY () + << " contentsWidth=" << m_scrollView->contentsWidth () + << " contentsHeight=" << m_scrollView->contentsHeight () + << " visibleWidth=" << m_scrollView->visibleWidth () + << " visibleHeight=" << m_scrollView->visibleHeight () + << " oldZoomX=" << m_mainView->zoomLevelX () + << " oldZoomY=" << m_mainView->zoomLevelY () + << " newZoom=" << zoomLevel + << " mainViewX=" << m_scrollView->childX (m_mainView) + << " mainViewY=" << m_scrollView->childY (m_mainView) + << endl; + #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 centring is off by about 5-10 pixels. + + // TODO: use visibleRect() for greater accuracy? + + int viewX, viewY; + + bool targetDocAvail = false; + double targetDocX, targetDocY; + + if (centerUnderCursor && + m_viewManager && m_viewManager->viewUnderCursor ()) + { + kpView *const vuc = m_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 != m_mainView) + viewPoint = vuc->transformViewToOtherView (viewPoint, m_mainView); + + viewX = viewPoint.x (); + viewY = viewPoint.y (); + } + else + { + viewX = m_scrollView->contentsX () + + QMIN (m_mainView->width (), + m_scrollView->visibleWidth ()) / 2; + viewY = m_scrollView->contentsY () + + QMIN (m_mainView->height (), + m_scrollView->visibleHeight ()) / 2; + } + + int newCenterX = viewX * zoomLevel / m_mainView->zoomLevelX (); + int newCenterY = viewY * zoomLevel / m_mainView->zoomLevelY (); + + m_mainView->setZoomLevel (zoomLevel, zoomLevel); + #if DEBUG_ZOOM_FLICKER + { + kdDebug () << "FLICKER: just setZoomLevel" << endl; + QTime timer; timer.start (); + while (timer.elapsed () < 1000) + ; + } + #endif + + #if DEBUG_KP_MAIN_WINDOW && 1 + kdDebug () << "\tvisibleWidth=" << m_scrollView->visibleWidth () + << " visibleHeight=" << m_scrollView->visibleHeight () + << endl; + kdDebug () << "\tnewCenterX=" << newCenterX + << " newCenterY=" << newCenterY << endl; + #endif + + m_scrollView->center (newCenterX, newCenterY); + #if DEBUG_ZOOM_FLICKER + { + kdDebug () << "FLICKER: just centred" << endl; + QTime timer; timer.start (); + while (timer.elapsed () < 2000) + ; + } + #endif + + if (centerUnderCursor && + targetDocAvail && + m_viewManager && m_viewManager->viewUnderCursor ()) + { + kpView *const vuc = m_viewManager->viewUnderCursor (); + + #if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\tcenterUnderCursor: reposition cursor; viewUnderCursor=" + << vuc->name () << endl; + #endif + + const double viewX = vuc->transformDocToViewX (targetDocX); + const double 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 ((int) viewX, (int) viewY); + #if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\t\tdoc: (" << targetDocX << "," << targetDocY << ")" + << " viewUnderCursor: (" << viewX << "," << viewY << ")" + << endl; + #endif + + if (vuc->clipRegion ().contains (viewPoint)) + { + const QPoint globalPoint = + kpWidgetMapper::toGlobal (vuc, viewPoint); + #if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\t\tglobalPoint=" << globalPoint << endl; + #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 + kdDebug () << "\t\twon't move cursor - would get outside view" + << endl; + #endif + + // TODO: Sane cursor flashing indication that indicates + // that the normal cursor movement didn't happen. + } + } + + #if DEBUG_KP_MAIN_WINDOW && 1 + kdDebug () << "\t\tcheck (contentsX=" << m_scrollView->contentsX () + << ",contentsY=" << m_scrollView->contentsY () + << ")" << endl; + #endif + } + + if (m_mainView) + { + actionShowGridUpdate (); + #if DEBUG_ZOOM_FLICKER + { + kdDebug () << "FLICKER: updated grid action" << endl; + QTime timer; timer.start (); + while (timer.elapsed () < 1000) + ; + } + #endif + + + updateMainViewGrid (); + #if DEBUG_ZOOM_FLICKER + { + kdDebug () << "FLICKER: just updated grid" << endl; + QTime timer; timer.start (); + while (timer.elapsed () < 1000) + ; + } + #endif + + + // Since Zoom Level KSelectAction on ToolBar grabs focus after changing + // Zoom, switch back to the Main View. + // TODO: back to the last view + m_mainView->setFocus (); + #if DEBUG_ZOOM_FLICKER + { + kdDebug () << "FLICKER: just set focus to mainview" << endl; + QTime timer; timer.start (); + while (timer.elapsed () < 1000) + ; + } + #endif + + } +#if 1 + // The view magnified and moved beneath the cursor + if (tool ()) + tool ()->somethingBelowTheCursorChanged (); + #if DEBUG_ZOOM_FLICKER + { + kdDebug () << "FLICKER: signalled something below cursor" << endl; + QTime timer; timer.start (); + while (timer.elapsed () < 1000) + ; + } + #endif +#endif + + // HACK: make sure all of Qt's update() calls trigger + // kpView::paintEvent() _now_ so that they can be queued by us + // (until kpViewManager::restoreQueueUpdates()) to reduce flicker + // caused mainly by m_scrollView->center() + // + // TODO: remove flicker completely + //QTimer::singleShot (0, this, SLOT (finishZoomTo ())); + + // Later: I don't think there is an update() that needs to be queued + // - let's reduce latency instead. + finishZoomTo (); +} + +// private slot +void kpMainWindow::finishZoomTo () +{ +#if DEBUG_KP_MAIN_WINDOW && 1 + kdDebug () << "\tkpMainWindow::finishZoomTo enter" << endl; +#endif + +#if DEBUG_ZOOM_FLICKER +{ + kdDebug () << "FLICKER: inside finishZoomTo" << endl; + QTime timer; timer.start (); + while (timer.elapsed () < 2000) + ; +} +#endif + + // TODO: setUpdatesEnabled() should really return to old value + // - not neccessarily "true" + + if (m_mainView) + { + m_mainView->setUpdatesEnabled (true); + m_mainView->update (); + } + +#if DEBUG_ZOOM_FLICKER +{ + kdDebug () << "FLICKER: just updated mainView" << endl; + QTime timer; timer.start (); + while (timer.elapsed () < 1000) + ; +} +#endif + + if (m_scrollView) + { + if (m_scrollView->viewport ()) + { + m_scrollView->viewport ()->setUpdatesEnabled (true); + m_scrollView->viewport ()->update (); + } + #if DEBUG_ZOOM_FLICKER + { + kdDebug () << "FLICKER: just updated scrollView::viewport()" << endl; + QTime timer; timer.start (); + while (timer.elapsed () < 1000) + ; + } + #endif + + m_scrollView->setUpdatesEnabled (true); + m_scrollView->update (); + #if DEBUG_ZOOM_FLICKER + { + kdDebug () << "FLICKER: just updated scrollView" << endl; + QTime timer; timer.start (); + while (timer.elapsed () < 1000) + ; + } + #endif + + } + + + if (m_viewManager && m_viewManager->queueUpdates ()/*just in case*/) + m_viewManager->restoreQueueUpdates (); +#if DEBUG_ZOOM_FLICKER +{ + kdDebug () << "FLICKER: restored vm queued updates" << endl; + QTime timer; timer.start (); + while (timer.elapsed () < 1000) + ; +} +#endif + + setStatusBarZoom (m_mainView ? m_mainView->zoomLevelX () : 0); + +#if DEBUG_KP_MAIN_WINDOW && 1 + kdDebug () << "\tkpMainWindow::finishZoomTo done" << endl; +#endif + +#if DEBUG_ZOOM_FLICKER +{ + kdDebug () << "FLICKER: finishZoomTo done" << endl; + QTime timer; timer.start (); + while (timer.elapsed () < 1000) + ; +} +#endif +} + + +// private slot +void kpMainWindow::slotActualSize () +{ + zoomTo (100); +} + +// private slot +void kpMainWindow::slotFitToPage () +{ + if (!m_scrollView || !m_document) + return; + + // doc_width * zoom / 100 <= view_width && + // doc_height * zoom / 100 <= view_height && + // 1 <= zoom <= 3200 + + zoomTo (QMIN (3200, QMAX (1, QMIN (m_scrollView->visibleWidth () * 100 / m_document->width (), + m_scrollView->visibleHeight () * 100 / m_document->height ())))); +} + +// private slot +void kpMainWindow::slotFitToWidth () +{ + if (!m_scrollView || !m_document) + return; + + // doc_width * zoom / 100 <= view_width && + // 1 <= zoom <= 3200 + + zoomTo (QMIN (3200, QMAX (1, m_scrollView->visibleWidth () * 100 / m_document->width ()))); +} + +// private slot +void kpMainWindow::slotFitToHeight () +{ + if (!m_scrollView || !m_document) + return; + + // doc_height * zoom / 100 <= view_height && + // 1 <= zoom <= 3200 + + zoomTo (QMIN (3200, QMAX (1, m_scrollView->visibleHeight () * 100 / m_document->height ()))); +} + + +// public +void kpMainWindow::zoomIn (bool centerUnderCursor) +{ + const int targetItem = m_actionZoom->currentItem () + 1; + + if (targetItem >= (int) m_zoomList.count ()) + return; + + m_actionZoom->setCurrentItem (targetItem); + zoomAccordingToZoomAction (centerUnderCursor); +} + +// public +void kpMainWindow::zoomOut (bool centerUnderCursor) +{ + const int targetItem = m_actionZoom->currentItem () - 1; + + if (targetItem < 0) + return; + + m_actionZoom->setCurrentItem (targetItem); + zoomAccordingToZoomAction (centerUnderCursor); +} + + +// public slot +void kpMainWindow::slotZoomIn () +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::slotZoomIn ()" << endl; +#endif + + zoomIn (false/*don't center under cursor*/); +} + +// public slot +void kpMainWindow::slotZoomOut () +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::slotZoomOut ()" << endl; +#endif + + zoomOut (false/*don't center under cursor*/); +} + + +// public +void kpMainWindow::zoomAccordingToZoomAction (bool centerUnderCursor) +{ + zoomTo (zoomLevelFromString (m_actionZoom->currentText ()), + centerUnderCursor); +} + +// private slot +void kpMainWindow::slotZoom () +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::slotZoom () index=" << m_actionZoom->currentItem () + << " text='" << m_actionZoom->currentText () << "'" << endl; +#endif + zoomAccordingToZoomAction (false/*don't center under cursor*/); +} + + +// private slot +void kpMainWindow::slotShowGridToggled () +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::slotActionShowGridToggled()" << endl; +#endif + + updateMainViewGrid (); + + + KConfigGroupSaver cfgGroupSaver (kapp->config (), kpSettingsGroupGeneral); + KConfigBase *cfg = cfgGroupSaver.config (); + + cfg->writeEntry (kpSettingShowGrid, m_configShowGrid = m_actionShowGrid->isChecked ()); + cfg->sync (); +} + +// private +void kpMainWindow::updateMainViewGrid () +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::updateMainViewGrid ()" << endl; +#endif + + if (m_mainView) + m_mainView->showGrid (m_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); +} + + +// public slot +void kpMainWindow::slotDestroyThumbnailIfNotVisible (bool tnIsVisible) +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::slotDestroyThumbnailIfNotVisible(isVisible=" + << tnIsVisible + << ")" + << endl; +#endif + + if (!tnIsVisible) + { + slotDestroyThumbnailInitatedByUser (); + } +} + +// private slot +void kpMainWindow::slotDestroyThumbnail () +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::slotDestroyThumbnail()" << endl; +#endif + + m_actionShowThumbnail->setChecked (false); + enableThumbnailOptionActions (false); + updateThumbnail (); +} + +// private slot +void kpMainWindow::slotDestroyThumbnailInitatedByUser () +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::slotDestroyThumbnailInitiatedByUser()" << endl; +#endif + + m_actionShowThumbnail->setChecked (false); + slotShowThumbnailToggled (); +} + +// private slot +void kpMainWindow::slotCreateThumbnail () +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::slotCreateThumbnail()" << endl; +#endif + + m_actionShowThumbnail->setChecked (true); + enableThumbnailOptionActions (true); + updateThumbnail (); +} + +// public +void kpMainWindow::notifyThumbnailGeometryChanged () +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::notifyThumbnailGeometryChanged()" << endl; +#endif + + if (!m_thumbnailSaveConfigTimer) + { + m_thumbnailSaveConfigTimer = new QTimer (this); + connect (m_thumbnailSaveConfigTimer, SIGNAL (timeout ()), + this, SLOT (slotSaveThumbnailGeometry ())); + } + + m_thumbnailSaveConfigTimer->start (500/*msec*/, true/*single shot*/); +} + +// private slot +void kpMainWindow::slotSaveThumbnailGeometry () +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::saveThumbnailGeometry()" << endl; +#endif + + if (!m_thumbnail) + return; + + QRect rect (m_thumbnail->x (), m_thumbnail->y (), + m_thumbnail->width (), m_thumbnail->height ()); +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\tthumbnail relative geometry=" << rect << endl; +#endif + + m_configThumbnailGeometry = mapFromGlobal (rect); + +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\tCONFIG: saving thumbnail geometry " + << m_configThumbnailGeometry + << endl; +#endif + + KConfigGroupSaver cfgGroupSaver (kapp->config (), kpSettingsGroupThumbnail); + KConfigBase *cfg = cfgGroupSaver.config (); + + cfg->writeEntry (kpSettingThumbnailGeometry, m_configThumbnailGeometry); + cfg->sync (); +} + +// private slot +void kpMainWindow::slotShowThumbnailToggled () +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::slotShowThumbnailToggled()" << endl; +#endif + + m_configThumbnailShown = m_actionShowThumbnail->isChecked (); + + KConfigGroupSaver cfgGroupSaver (kapp->config (), kpSettingsGroupThumbnail); + KConfigBase *cfg = cfgGroupSaver.config (); + + cfg->writeEntry (kpSettingThumbnailShown, m_configThumbnailShown); + cfg->sync (); + + + enableThumbnailOptionActions (m_actionShowThumbnail->isChecked ()); + updateThumbnail (); +} + +// private slot +void kpMainWindow::updateThumbnailZoomed () +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::updateThumbnailZoomed() zoomed=" + << m_actionZoomedThumbnail->isChecked () << endl; +#endif + + if (!m_thumbnailView) + return; + + destroyThumbnailView (); + createThumbnailView (); +} + +// private slot +void kpMainWindow::slotZoomedThumbnailToggled () +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::slotZoomedThumbnailToggled()" << endl; +#endif + + m_configZoomedThumbnail = m_actionZoomedThumbnail->isChecked (); + + KConfigGroupSaver cfgGroupSaver (kapp->config (), kpSettingsGroupThumbnail); + KConfigBase *cfg = cfgGroupSaver.config (); + + cfg->writeEntry (kpSettingThumbnailZoomed, m_configZoomedThumbnail); + cfg->sync (); + + + updateThumbnailZoomed (); +} + +// private slot +void kpMainWindow::slotThumbnailShowRectangleToggled () +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::slotThumbnailShowRectangleToggled()" << endl; +#endif + + d->m_configThumbnailShowRectangle = d->m_actionShowThumbnailRectangle->isChecked (); + + KConfigGroupSaver cfgGroupSaver (kapp->config (), kpSettingsGroupThumbnail); + KConfigBase *cfg = cfgGroupSaver.config (); + + cfg->writeEntry (kpSettingThumbnailShowRectangle, d->m_configThumbnailShowRectangle); + cfg->sync (); + + + if (m_thumbnailView) + { + m_thumbnailView->showBuddyViewScrollableContainerRectangle ( + d->m_actionShowThumbnailRectangle->isChecked ()); + } +} + +// private +void kpMainWindow::enableViewZoomedThumbnail (bool enable) +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::enableSettingsViewZoomedThumbnail()" << endl; +#endif + + m_actionZoomedThumbnail->setEnabled (enable && + m_actionShowThumbnail->isChecked ()); + + // Note: Don't uncheck if disabled - being able to see the zoomed state + // before turning on the thumbnail can be useful. + m_actionZoomedThumbnail->setChecked (m_configZoomedThumbnail); +} + +// private +void kpMainWindow::enableViewShowThumbnailRectangle (bool enable) +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::enableViewShowThumbnailRectangle()" << endl; +#endif + + d->m_actionShowThumbnailRectangle->setEnabled (enable && + m_actionShowThumbnail->isChecked ()); + + // Note: Don't uncheck if disabled for consistency with + // enableViewZoomedThumbnail() + d->m_actionShowThumbnailRectangle->setChecked ( + d->m_configThumbnailShowRectangle); +} + +// private +void kpMainWindow::enableThumbnailOptionActions (bool enable) +{ + enableViewZoomedThumbnail (enable); + enableViewShowThumbnailRectangle (enable); +} + + +// private +void kpMainWindow::createThumbnailView () +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\t\tcreating new kpView:" << endl; +#endif + + if (m_thumbnailView) + { + kdDebug () << "kpMainWindow::createThumbnailView() had to destroy view" << endl; + destroyThumbnailView (); + } + + if (m_actionZoomedThumbnail->isChecked ()) + { + m_thumbnailView = new kpZoomedThumbnailView ( + m_document, m_toolToolBar, m_viewManager, + m_mainView, + 0/*scrollableContainer*/, + m_thumbnail, "thumbnailView"); + } + else + { + m_thumbnailView = new kpUnzoomedThumbnailView ( + m_document, m_toolToolBar, m_viewManager, + m_mainView, + 0/*scrollableContainer*/, + m_thumbnail, "thumbnailView"); + + } + + m_thumbnailView->showBuddyViewScrollableContainerRectangle ( + d->m_actionShowThumbnailRectangle->isChecked ()); + + +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\t\tgive kpThumbnail the kpView:" << endl; +#endif + if (m_thumbnail) + m_thumbnail->setView (m_thumbnailView); + else + kdError () << "kpMainWindow::createThumbnailView() no thumbnail" << endl; + +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\t\tregistering the kpView:" << endl; +#endif + if (m_viewManager) + m_viewManager->registerView (m_thumbnailView); +} + +// private +void kpMainWindow::destroyThumbnailView () +{ + if (!m_thumbnailView) + return; + + if (m_viewManager) + m_viewManager->unregisterView (m_thumbnailView); + + if (m_thumbnail) + m_thumbnail->setView (0); + + m_thumbnailView->deleteLater (); m_thumbnailView = 0; +} + + +// private +void kpMainWindow::updateThumbnail () +{ +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "kpMainWindow::updateThumbnail()" << endl; +#endif + bool enable = m_actionShowThumbnail->isChecked (); + +#if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\tthumbnail=" + << bool (m_thumbnail) + << " action_isChecked=" + << enable + << endl; +#endif + + if (bool (m_thumbnail) == enable) + return; + + if (!m_thumbnail) + { + #if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\tcreating thumbnail" << endl; + #endif + + // Read last saved geometry before creating thumbnail & friends + // in case they call notifyThumbnailGeometryChanged() + QRect thumbnailGeometry = m_configThumbnailGeometry; + #if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\t\tlast used geometry=" << thumbnailGeometry << endl; + #endif + + m_thumbnail = new kpThumbnail (this, "thumbnail"); + + createThumbnailView (); + + #if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\t\tmoving thumbnail to right place" << endl; + #endif + if (!thumbnailGeometry.isEmpty () && + QRect (0, 0, width (), height ()).intersects (thumbnailGeometry)) + { + const QRect geometry = mapToGlobal (thumbnailGeometry); + m_thumbnail->resize (geometry.size ()); + m_thumbnail->move (geometry.topLeft ()); + } + else + { + if (m_scrollView) + { + const int margin = 20; + const int initialWidth = 160, initialHeight = 120; + + QRect geometryRect (width () - initialWidth - margin * 2, + m_scrollView->y () + margin, + initialWidth, + initialHeight); + + #if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\t\tcreating geometry=" << geometryRect << endl; + #endif + + geometryRect = mapToGlobal (geometryRect); + #if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\t\tmap to global=" << geometryRect << endl; + #endif + m_thumbnail->resize (geometryRect.size ()); + m_thumbnail->move (geometryRect.topLeft ()); + } + } + + #if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\t\tshowing thumbnail" << endl; + #endif + m_thumbnail->show (); + + #if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\t\tconnecting thumbnail::visibilityChange to destroy slot" << endl; + #endif + connect (m_thumbnail, SIGNAL (visibilityChanged (bool)), + this, SLOT (slotDestroyThumbnailIfNotVisible (bool))); + #if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\t\tDONE" << endl; + #endif + } + else + { + #if DEBUG_KP_MAIN_WINDOW + kdDebug () << "\tdestroying thumbnail" << endl; + #endif + + if (m_thumbnailSaveConfigTimer && m_thumbnailSaveConfigTimer->isActive ()) + { + m_thumbnailSaveConfigTimer->stop (); + slotSaveThumbnailGeometry (); + } + + + destroyThumbnailView (); + + + disconnect (m_thumbnail, SIGNAL (visibilityChanged (bool)), + this, SLOT (slotDestroyThumbnailIfNotVisible (bool))); + + m_thumbnail->deleteLater (); m_thumbnail = 0; + } +} diff --git a/kolourpaint/kpselection.cpp b/kolourpaint/kpselection.cpp new file mode 100644 index 00000000..eb5924cf --- /dev/null +++ b/kolourpaint/kpselection.cpp @@ -0,0 +1,1446 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <kpselection.h> + +#include <qfont.h> +#include <qimage.h> +#include <qpainter.h> +#include <qwmatrix.h> + +#include <kdebug.h> +#include <klocale.h> + +#include <kpcolorsimilaritydialog.h> +#include <kpdefs.h> +#include <kppixmapfx.h> +#include <kptool.h> + + +kpSelection::kpSelection (const kpSelectionTransparency &transparency) + : QObject (), + m_type (kpSelection::Rectangle), + m_pixmap (0) +{ + setTransparency (transparency); +} + +kpSelection::kpSelection (Type type, const QRect &rect, const QPixmap &pixmap, + const kpSelectionTransparency &transparency) + : QObject (), + m_type (type), + m_rect (rect) +{ + calculatePoints (); + m_pixmap = pixmap.isNull () ? 0 : new QPixmap (pixmap); + + setTransparency (transparency); +} + +kpSelection::kpSelection (Type type, const QRect &rect, const kpSelectionTransparency &transparency) + : QObject (), + m_type (type), + m_rect (rect), + m_pixmap (0) +{ + calculatePoints (); + + setTransparency (transparency); +} + +kpSelection::kpSelection (const QRect &rect, + const QValueVector <QString> &textLines_, + const kpTextStyle &textStyle_) + : QObject (), + m_type (Text), + m_rect (rect), + m_pixmap (0), + m_textStyle (textStyle_) +{ + calculatePoints (); + + setTextLines (textLines_); +} + +kpSelection::kpSelection (const QPointArray &points, const QPixmap &pixmap, + const kpSelectionTransparency &transparency) + : QObject (), + m_type (Points), + m_rect (points.boundingRect ()), + m_points (points) +{ + m_pixmap = pixmap.isNull () ? 0 : new QPixmap (pixmap); + m_points.detach (); + + setTransparency (transparency); +} + +kpSelection::kpSelection (const QPointArray &points, const kpSelectionTransparency &transparency) + : QObject (), + m_type (Points), + m_rect (points.boundingRect ()), + m_points (points), + m_pixmap (0) +{ + m_points.detach (); + + setTransparency (transparency); +} + +kpSelection::kpSelection (const kpSelection &rhs) + : QObject (), + m_type (rhs.m_type), + m_rect (rhs.m_rect), + m_points (rhs.m_points), + m_pixmap (rhs.m_pixmap ? new QPixmap (*rhs.m_pixmap) : 0), + m_textLines (rhs.m_textLines), + m_textStyle (rhs.m_textStyle), + m_transparency (rhs.m_transparency), + m_transparencyMask (rhs.m_transparencyMask) +{ + m_points.detach (); +} + +kpSelection &kpSelection::operator= (const kpSelection &rhs) +{ + if (this == &rhs) + return *this; + + m_type = rhs.m_type; + m_rect = rhs.m_rect; + m_points = rhs.m_points; + m_points.detach (); + + delete m_pixmap; + m_pixmap = rhs.m_pixmap ? new QPixmap (*rhs.m_pixmap) : 0; + + m_textLines = rhs.m_textLines; + m_textStyle = rhs.m_textStyle; + + m_transparency = rhs.m_transparency; + m_transparencyMask = rhs.m_transparencyMask; + + return *this; +} + + +// friend +QDataStream &operator<< (QDataStream &stream, const kpSelection &selection) +{ +#if DEBUG_KP_SELECTION && 1 + kdDebug () << "kpSelection::operator<<(sel: rect=" << selection.boundingRect () + << " pixmap rect=" << (selection.pixmap () ? selection.pixmap ()->rect () : QRect ()) + << endl; +#endif + stream << int (selection.m_type); + stream << selection.m_rect; + stream << selection.m_points; +#if DEBUG_KP_SELECTION && 1 + kdDebug () << "\twrote type=" << int (selection.m_type) << " rect=" << selection.m_rect + << " and points" << endl; +#endif + + // TODO: need for text? + // For now we just use QTextDrag for Text Selections so this point is mute. + if (selection.m_pixmap) + { + const QImage image = kpPixmapFX::convertToImage (*selection.m_pixmap); + #if DEBUG_KP_SELECTION && 1 + kdDebug () << "\twrote image rect=" << image.rect () << endl; + #endif + stream << image; + } + else + { + #if DEBUG_KP_SELECTION && 1 + kdDebug () << "\twrote no image because no pixmap" << endl; + #endif + stream << QImage (); + } + + //stream << selection.m_textLines; + //stream << selection.m_textStyle; + + return stream; +} + +// friend +QDataStream &operator>> (QDataStream &stream, kpSelection &selection) +{ + selection.readFromStream (stream); + return stream; +} + +// public +// TODO: KolourPaint has not been tested against invalid or malicious +// clipboard data [Bug #28]. +void kpSelection::readFromStream (QDataStream &stream, + const kpPixmapFX::WarnAboutLossInfo &wali) +{ +#if DEBUG_KP_SELECTION && 1 + kdDebug () << "kpSelection::readFromStream()" << endl; +#endif + int typeAsInt; + stream >> typeAsInt; + m_type = kpSelection::Type (typeAsInt); +#if DEBUG_KP_SELECTION && 1 + kdDebug () << "\ttype=" << typeAsInt << endl; +#endif + + stream >> m_rect; + stream >> m_points; + m_points.detach (); +#if DEBUG_KP_SELECTION && 1 + kdDebug () << "\trect=" << m_rect << endl; + //kdDebug () << "\tpoints=" << m_points << endl; +#endif + + QImage image; + stream >> image; + delete m_pixmap; + if (!image.isNull ()) + m_pixmap = new QPixmap (kpPixmapFX::convertToPixmap (image, false/*no dither*/, wali)); + else + m_pixmap = 0; +#if DEBUG_KP_SELECTION && 1 + kdDebug () << "\timage: w=" << image.width () << " h=" << image.height () + << " depth=" << image.depth () << endl; + if (m_pixmap) + { + kdDebug () << "\tpixmap: w=" << m_pixmap->width () << " h=" << m_pixmap->height () + << endl; + } + else + { + kdDebug () << "\tpixmap: none" << endl; + } +#endif + + //stream >> m_textLines; + //stream >> m_textStyle; +} + +kpSelection::~kpSelection () +{ + delete m_pixmap; m_pixmap = 0; +} + + +// private +void kpSelection::calculatePoints () +{ + if (m_type == kpSelection::Points) + return; + + if (m_type == kpSelection::Ellipse) + { + m_points.makeEllipse (m_rect.x (), m_rect.y (), + m_rect.width (), m_rect.height ()); + return; + } + + if (m_type == kpSelection::Rectangle || m_type == kpSelection::Text) + { + // OPT: not space optimal - redoes corners + m_points.resize (m_rect.width () * 2 + m_rect.height () * 2); + + int pointsUpto = 0; + + // top + for (int x = 0; x < m_rect.width (); x++) + m_points [pointsUpto++] = QPoint (m_rect.x () + x, m_rect.top ()); + + // right + for (int y = 0; y < m_rect.height (); y++) + m_points [pointsUpto++] = QPoint (m_rect.right (), m_rect.y () + y); + + // bottom + for (int x = m_rect.width () - 1; x >= 0; x--) + m_points [pointsUpto++] = QPoint (m_rect.x () + x, m_rect.bottom ()); + + // left + for (int y = m_rect.height () - 1; y >= 0; y--) + m_points [pointsUpto++] = QPoint (m_rect.left (), m_rect.y () + y); + + return; + } + + kdError () << "kpSelection::calculatePoints() with unknown type" << endl; + return; +} + + +// public +kpSelection::Type kpSelection::type () const +{ + return m_type; +} + +// public +bool kpSelection::isRectangular () const +{ + return (m_type == Rectangle || m_type == Text); +} + +// public +bool kpSelection::isText () const +{ + return (m_type == Text); +} + +// public +QString kpSelection::name () const +{ + if (m_type == Text) + return i18n ("Text"); + + return i18n ("Selection"); +} + + +// public +int kpSelection::size () const +{ + return kpPixmapFX::pointArraySize (m_points) + + kpPixmapFX::pixmapSize (m_pixmap) + + kpPixmapFX::stringSize (text ()) + + kpPixmapFX::pixmapSize (m_transparencyMask); +} + + +// public +QBitmap kpSelection::maskForOwnType (bool nullForRectangular) const +{ + if (!m_rect.isValid ()) + { + kdError () << "kpSelection::maskForOwnType() boundingRect invalid" << endl; + return QBitmap (); + } + + + if (isRectangular ()) + { + if (nullForRectangular) + return QBitmap (); + + QBitmap maskBitmap (m_rect.width (), m_rect.height ()); + maskBitmap.fill (Qt::color1/*opaque*/); + return maskBitmap; + } + + + QBitmap maskBitmap (m_rect.width (), m_rect.height ()); + maskBitmap.fill (Qt::color0/*transparent*/); + + QPainter painter; + painter.begin (&maskBitmap); + painter.setPen (Qt::color1)/*opaque*/; + painter.setBrush (Qt::color1/*opaque*/); + + if (m_type == kpSelection::Ellipse) + painter.drawEllipse (0, 0, m_rect.width (), m_rect.height ()); + else if (m_type == kpSelection::Points) + { + QPointArray points = m_points; + points.detach (); + points.translate (-m_rect.x (), -m_rect.y ()); + + painter.drawPolygon (points, false/*even-odd algo*/); + } + + painter.end (); + + + return maskBitmap; +} + + +// public +QPoint kpSelection::topLeft () const +{ + return m_rect.topLeft (); +} + +// public +QPoint kpSelection::point () const +{ + return m_rect.topLeft (); +} + + +// public +int kpSelection::x () const +{ + return m_rect.x (); +} + +// public +int kpSelection::y () const +{ + return m_rect.y (); +} + + +// public +void kpSelection::moveBy (int dx, int dy) +{ +#if DEBUG_KP_SELECTION && 1 + kdDebug () << "kpSelection::moveBy(" << dx << "," << dy << ")" << endl; +#endif + + if (dx == 0 && dy == 0) + return; + + QRect oldRect = boundingRect (); + +#if DEBUG_KP_SELECTION && 1 + kdDebug () << "\toldRect=" << oldRect << endl; +#endif + + m_rect.moveBy (dx, dy); + m_points.translate (dx, dy); +#if DEBUG_KP_SELECTION && 1 + kdDebug () << "\tnewRect=" << m_rect << endl; +#endif + + emit changed (oldRect); + emit changed (boundingRect ()); +} + +// public +void kpSelection::moveTo (int dx, int dy) +{ + moveTo (QPoint (dx, dy)); +} + +// public +void kpSelection::moveTo (const QPoint &topLeftPoint) +{ +#if DEBUG_KP_SELECTION && 1 + kdDebug () << "kpSelection::moveTo(" << topLeftPoint << ")" << endl; +#endif + QRect oldBoundingRect = boundingRect (); +#if DEBUG_KP_SELECTION && 1 + kdDebug () << "\toldBoundingRect=" << oldBoundingRect << endl; +#endif + if (topLeftPoint == oldBoundingRect.topLeft ()) + return; + + QPoint delta (topLeftPoint - oldBoundingRect.topLeft ()); + moveBy (delta.x (), delta.y ()); +} + + +// public +QPointArray kpSelection::points () const +{ + return m_points; +} + +// public +QPointArray kpSelection::pointArray () const +{ + return m_points; +} + +// public +QRect kpSelection::boundingRect () const +{ + return m_rect; +} + +// public +int kpSelection::width () const +{ + return boundingRect ().width (); +} + +// public +int kpSelection::height () const +{ + return boundingRect ().height (); +} + +// public +bool kpSelection::contains (const QPoint &point) const +{ + QRect rect = boundingRect (); + +#if DEBUG_KP_SELECTION && 1 + kdDebug () << "kpSelection::contains(" << point + << ") rect==" << rect + << " #points=" << m_points.size () + << endl; +#endif + + if (!rect.contains (point)) + return false; + + // OPT: QRegion is probably incredibly slow - cache + // We can't use the m_pixmap (if avail) and get the transparency of + // the pixel at that point as it may be transparent but still within the + // border + switch (m_type) + { + case kpSelection::Rectangle: + case kpSelection::Text: + return true; + case kpSelection::Ellipse: + return QRegion (m_rect, QRegion::Ellipse).contains (point); + case kpSelection::Points: + // TODO: make this always include the border + // (draw up a rect sel in this mode to see what I mean) + return QRegion (m_points, false/*even-odd algo*/).contains (point); + default: + return false; + } +} + +// public +bool kpSelection::contains (int x, int y) +{ + return contains (QPoint (x, y)); +} + + +// public +QPixmap *kpSelection::pixmap () const +{ + return m_pixmap; +} + +// public +void kpSelection::setPixmap (const QPixmap &pixmap) +{ + delete m_pixmap; + // TODO: If isText(), setting pixmap() to 0 is unexpected (implies + // it's a border, not a text box) but saves memory when using + // that kpSelection::setPixmap (QPixmap ()) hack. + m_pixmap = pixmap.isNull () ? 0 : new QPixmap (pixmap); + QRect changedRect = boundingRect (); + + if (m_pixmap) + { + const bool changedSize = (m_pixmap->width () != m_rect.width () || + m_pixmap->height () != m_rect.height ()); + const bool changedFromText = (m_type == Text); + if (changedSize || changedFromText) + { + if (changedSize) + { + kdError () << "kpSelection::setPixmap() changes the size of the selection!" + << " old:" + << " w=" << m_rect.width () + << " h=" << m_rect.height () + << " new:" + << " w=" << m_pixmap->width () + << " h=" << m_pixmap->height () + << endl; + } + + if (changedFromText) + { + kdError () << "kpSelection::setPixmap() changed from text" << endl; + } + + m_type = kpSelection::Rectangle; + m_rect = QRect (m_rect.x (), m_rect.y (), + m_pixmap->width (), m_pixmap->height ()); + calculatePoints (); + + m_textLines = QValueVector <QString> (); + + changedRect = changedRect.unite (boundingRect ()); + } + } + + calculateTransparencyMask (); + + emit changed (changedRect); +} + + +// public +bool kpSelection::usesBackgroundPixmapToPaint () const +{ + // Opaque text with transparent background needs to antialias with + // doc pixmap below it. + return (isText () && + m_textStyle.foregroundColor ().isOpaque () && + m_textStyle.effectiveBackgroundColor ().isTransparent ()); +} + +static int mostContrastingValue (int val) +{ + if (val <= 127) + return 255; + else + return 0; +} + +static QRgb mostContrastingRGB (QRgb val) +{ + return qRgba (mostContrastingValue (qRed (val)), + mostContrastingValue (qGreen (val)), + mostContrastingValue (qBlue (val)), + mostContrastingValue (qAlpha (val))); +} + +// private +static void drawTextLines (QPainter *painter, QPainter *maskPainter, + const QRect &rect, + const QValueVector <QString> &textLines) +{ + if (!painter->clipRegion ().isEmpty () || !maskPainter->clipRegion ().isEmpty ()) + { + // TODO: fix esp. before making method public + kdError () << "kpselection.cpp:drawTextLines() can't deal with existing painter clip regions" << endl; + return; + } + + +#define PAINTER_CALL(cmd) \ +{ \ + if (painter->isActive ()) \ + painter->cmd; \ + \ + if (maskPainter->isActive ()) \ + maskPainter->cmd; \ +} + + + // Can't do this because the line heights become + // >QFontMetrics::height() if you type Chinese characters (!) and then + // the cursor gets out of sync. + // PAINTER_CALL (drawText (rect, 0/*flags*/, text ())); + + +#if 0 + const QFontMetrics fontMetrics (painter->fontMetrics ()); + + kdDebug () << "height=" << fontMetrics.height () + << " leading=" << fontMetrics.leading () + << " ascent=" << fontMetrics.ascent () + << " descent=" << fontMetrics.descent () + << " lineSpacing=" << fontMetrics.lineSpacing () + << endl; +#endif + + + PAINTER_CALL (setClipRect (rect, QPainter::CoordPainter/*transform*/)); + + int baseLine = rect.y () + painter->fontMetrics ().ascent (); + for (QValueVector <QString>::const_iterator it = textLines.begin (); + it != textLines.end (); + it++) + { + PAINTER_CALL (drawText (rect.x (), baseLine, *it)); + baseLine += painter->fontMetrics ().lineSpacing (); + } + + +#undef PAINTER_CALL +} + +// private +void kpSelection::paintOpaqueText (QPixmap *destPixmap, const QRect &docRect) const +{ + if (!isText () || !m_textStyle.foregroundColor ().isOpaque ()) + return; + + + const QRect modifyingRect = docRect.intersect (boundingRect ()); + if (modifyingRect.isEmpty ()) + return; + + + QBitmap destPixmapMask; + QPainter destPixmapPainter, destPixmapMaskPainter; + + if (destPixmap->mask ()) + { + if (m_textStyle.effectiveBackgroundColor ().isTransparent ()) + { + QRect modifyingRectRelPixmap = modifyingRect; + modifyingRectRelPixmap.moveBy (-docRect.x (), -docRect.y ()); + + // Set the RGB of transparent pixels to foreground colour to avoid + // anti-aliasing the foreground coloured text with undefined RGBs. + kpPixmapFX::setPixmapAt (destPixmap, + modifyingRectRelPixmap, + kpPixmapFX::pixmapWithDefinedTransparentPixels ( + kpPixmapFX::getPixmapAt (*destPixmap, modifyingRectRelPixmap), + m_textStyle.foregroundColor ().toQColor ())); + } + + destPixmapMask = *destPixmap->mask (); + destPixmapMaskPainter.begin (&destPixmapMask); + destPixmapMaskPainter.translate (-docRect.x (), -docRect.y ()); + destPixmapMaskPainter.setPen (Qt::color1/*opaque*/); + destPixmapMaskPainter.setFont (m_textStyle.font ()); + } + + destPixmapPainter.begin (destPixmap); + destPixmapPainter.translate (-docRect.x (), -docRect.y ()); + destPixmapPainter.setPen (m_textStyle.foregroundColor ().toQColor ()); + destPixmapPainter.setFont (m_textStyle.font ()); + + + if (m_textStyle.effectiveBackgroundColor ().isOpaque ()) + { + destPixmapPainter.fillRect ( + boundingRect (), + m_textStyle.effectiveBackgroundColor ().toQColor ()); + + if (destPixmapMaskPainter.isActive ()) + { + destPixmapMaskPainter.fillRect ( + boundingRect (), + Qt::color1/*opaque*/); + } + } + + + ::drawTextLines (&destPixmapPainter, &destPixmapMaskPainter, + textAreaRect (), + textLines ()); + + + if (destPixmapPainter.isActive ()) + destPixmapPainter.end (); + + if (destPixmapMaskPainter.isActive ()) + destPixmapMaskPainter.end (); + + + if (!destPixmapMask.isNull ()) + destPixmap->setMask (destPixmapMask); +} + +// private +QPixmap kpSelection::transparentForegroundTextPixmap () const +{ + if (!isText () || !m_textStyle.foregroundColor ().isTransparent ()) + return QPixmap (); + + + QPixmap pixmap (m_rect.width (), m_rect.height ()); + QBitmap pixmapMask (m_rect.width (), m_rect.height ()); + + + // Iron out stupid case first + if (m_textStyle.effectiveBackgroundColor ().isTransparent ()) + { + pixmapMask.fill (Qt::color0/*transparent*/); + pixmap.setMask (pixmapMask); + return pixmap; + } + + + // -- Foreground transparent, background opaque -- + + + QFont font = m_textStyle.font (); + // TODO: why doesn't "font.setStyleStrategy (QFont::NoAntialias);" + // let us avoid the hack below? + + + QPainter pixmapPainter, pixmapMaskPainter; + + pixmap.fill (m_textStyle.effectiveBackgroundColor ().toQColor ()); + pixmapPainter.begin (&pixmap); + // HACK: Transparent foreground colour + antialiased fonts don't + // work - they don't seem to be able to draw in + // Qt::color0/*transparent*/ (but Qt::color1 seems Ok). + // So we draw in a contrasting color to the background so that + // we can identify the transparent pixels for manually creating + // the mask. + pixmapPainter.setPen ( + QColor (mostContrastingRGB (m_textStyle.effectiveBackgroundColor ().toQRgb () & RGB_MASK))); + pixmapPainter.setFont (font); + + + pixmapMask.fill (Qt::color1/*opaque*/); + pixmapMaskPainter.begin (&pixmapMask); + pixmapMaskPainter.setPen (Qt::color0/*transparent*/); + pixmapMaskPainter.setFont (font); + + + QRect rect (textAreaRect ()); + rect.moveBy (-m_rect.x (), -m_rect.y ()); + ::drawTextLines (&pixmapPainter, &pixmapMaskPainter, + rect, + textLines ()); + + + if (pixmapPainter.isActive ()) + pixmapPainter.end (); + + if (pixmapMaskPainter.isActive ()) + pixmapMaskPainter.end (); + + +#if DEBUG_KP_SELECTION + kdDebug () << "\tinvoking foreground transparency hack" << endl; +#endif + QImage image = kpPixmapFX::convertToImage (pixmap); + QRgb backgroundRGB = image.pixel (0, 0); // on textBorderSize() + + pixmapMaskPainter.begin (&pixmapMask); + for (int y = 0; y < image.height (); y++) + { + for (int x = 0; x < image.width (); x++) + { + if (image.pixel (x, y) == backgroundRGB) + pixmapMaskPainter.setPen (Qt::color1/*opaque*/); + else + pixmapMaskPainter.setPen (Qt::color0/*transparent*/); + + pixmapMaskPainter.drawPoint (x, y); + } + } + pixmapMaskPainter.end (); + + + if (!pixmapMask.isNull ()) + pixmap.setMask (pixmapMask); + + + return pixmap; +} + +// public +void kpSelection::paint (QPixmap *destPixmap, const QRect &docRect) const +{ + if (!isText ()) + { + if (pixmap () && !pixmap ()->isNull ()) + { + kpPixmapFX::paintPixmapAt (destPixmap, + topLeft () - docRect.topLeft (), + transparentPixmap ()); + } + } + else + { + #if DEBUG_KP_SELECTION + kdDebug () << "kpSelection::paint() textStyle: fcol=" + << (int *) m_textStyle.foregroundColor ().toQRgb () + << " bcol=" + << (int *) m_textStyle.effectiveBackgroundColor ().toQRgb () + << endl; + #endif + + if (m_textStyle.foregroundColor ().isOpaque ()) + { + // (may have to antialias with background so don't use m_pixmap) + paintOpaqueText (destPixmap, docRect); + } + else + { + if (!m_pixmap) + { + kdError () << "kpSelection::paint() without m_pixmap?" << endl; + return; + } + + // (transparent foreground slow to render, no antialiasing + // so use m_pixmap cache) + kpPixmapFX::paintPixmapAt (destPixmap, + topLeft () - docRect.topLeft (), + *m_pixmap); + } + } +} + +// private +void kpSelection::calculateTextPixmap () +{ + if (!isText ()) + { + kdError () << "kpSelection::calculateTextPixmap() not text sel" + << endl; + return; + } + + delete m_pixmap; + + if (m_textStyle.foregroundColor().isOpaque ()) + { + m_pixmap = new QPixmap (m_rect.width (), m_rect.height ()); + + if (usesBackgroundPixmapToPaint ()) + kpPixmapFX::fill (m_pixmap, kpColor::transparent); + + paintOpaqueText (m_pixmap, m_rect); + } + else + { + m_pixmap = new QPixmap (transparentForegroundTextPixmap ()); + } +} + + +// public static +QString kpSelection::textForTextLines (const QValueVector <QString> &textLines_) +{ + if (textLines_.isEmpty ()) + return QString::null; + + QString bigString = textLines_ [0]; + + for (QValueVector <QString>::const_iterator it = textLines_.begin () + 1; + it != textLines_.end (); + it++) + { + bigString += QString::fromLatin1 ("\n"); + bigString += (*it); + } + + return bigString; +} + +// public +QString kpSelection::text () const +{ + if (!isText ()) + { + return QString::null; + } + + return textForTextLines (m_textLines); +} + +// public +QValueVector <QString> kpSelection::textLines () const +{ + if (!isText ()) + { + kdError () << "kpSelection::textLines() not a text selection" << endl; + return QValueVector <QString> (); + } + + return m_textLines; +} + +// public +void kpSelection::setTextLines (const QValueVector <QString> &textLines_) +{ + if (!isText ()) + { + kdError () << "kpSelection::setTextLines() not a text selection" << endl; + return; + } + + m_textLines = textLines_; + if (m_textLines.isEmpty ()) + { + kdError () << "kpSelection::setTextLines() passed no lines" << endl; + m_textLines.push_back (QString::null); + } + calculateTextPixmap (); + emit changed (boundingRect ()); +} + +// public static +int kpSelection::textBorderSize () +{ + return 1; +} + +// public +QRect kpSelection::textAreaRect () const +{ + if (!isText ()) + { + kdError () << "kpSelection::textAreaRect() not a text selection" << endl; + return QRect (); + } + + return QRect (m_rect.x () + textBorderSize (), + m_rect.y () + textBorderSize (), + m_rect.width () - textBorderSize () * 2, + m_rect.height () - textBorderSize () * 2); +} + +// public +bool kpSelection::pointIsInTextBorderArea (const QPoint &globalPoint) const +{ + if (!isText ()) + { + kdError () << "kpSelection::pointIsInTextBorderArea() not a text selection" << endl; + return false; + } + + return (m_rect.contains (globalPoint) && !pointIsInTextArea (globalPoint)); +} + +// public +bool kpSelection::pointIsInTextArea (const QPoint &globalPoint) const +{ + if (!isText ()) + { + kdError () << "kpSelection::pointIsInTextArea() not a text selection" << endl; + return false; + } + + return textAreaRect ().contains (globalPoint); +} + + +// public +void kpSelection::textResize (int width, int height) +{ + if (!isText ()) + { + kdError () << "kpSelection::textResize() not a text selection" << endl; + return; + } + + QRect oldRect = m_rect; + + m_rect = QRect (oldRect.x (), oldRect.y (), width, height); + + calculatePoints (); + calculateTextPixmap (); + + emit changed (m_rect.unite (oldRect)); +} + + +// public static +int kpSelection::minimumWidthForTextStyle (const kpTextStyle &) +{ + return (kpSelection::textBorderSize () * 2 + 5); +} + +// public static +int kpSelection::minimumHeightForTextStyle (const kpTextStyle &) +{ + return (kpSelection::textBorderSize () * 2 + 5); +} + +// public static +QSize kpSelection::minimumSizeForTextStyle (const kpTextStyle &textStyle) +{ + return QSize (minimumWidthForTextStyle (textStyle), + minimumHeightForTextStyle (textStyle)); +} + + +// public static +int kpSelection::preferredMinimumWidthForTextStyle (const kpTextStyle &textStyle) +{ + const int about15CharsWidth = + textStyle.fontMetrics ().width ( + QString::fromLatin1 ("1234567890abcde")); + + const int preferredMinWidth = + QMAX (150, + textBorderSize () * 2 + about15CharsWidth); + + return QMAX (minimumWidthForTextStyle (textStyle), + QMIN (250, preferredMinWidth)); +} + +// public static +int kpSelection::preferredMinimumHeightForTextStyle (const kpTextStyle &textStyle) +{ + const int preferredMinHeight = + textBorderSize () * 2 + textStyle.fontMetrics ().height (); + + return QMAX (minimumHeightForTextStyle (textStyle), + QMIN (150, preferredMinHeight)); +} + +// public static +QSize kpSelection::preferredMinimumSizeForTextStyle (const kpTextStyle &textStyle) +{ + return QSize (preferredMinimumWidthForTextStyle (textStyle), + preferredMinimumHeightForTextStyle (textStyle)); +} + + +// public +int kpSelection::minimumWidth () const +{ + if (isText ()) + return minimumWidthForTextStyle (textStyle ()); + else + return 1; +} + +// public +int kpSelection::minimumHeight () const +{ + if (isText ()) + return minimumHeightForTextStyle (textStyle ()); + else + return 1; +} + +// public +QSize kpSelection::minimumSize () const +{ + return QSize (minimumWidth (), minimumHeight ()); +} + + +// public +int kpSelection::textRowForPoint (const QPoint &globalPoint) const +{ + if (!isText ()) + { + kdError () << "kpSelection::textRowForPoint() not a text selection" << endl; + return -1; + } + + if (!pointIsInTextArea (globalPoint)) + return -1; + + const QFontMetrics fontMetrics (m_textStyle.fontMetrics ()); + + int row = (globalPoint.y () - textAreaRect ().y ()) / + fontMetrics.lineSpacing (); + if (row >= (int) m_textLines.size ()) + row = m_textLines.size () - 1; + + return row; +} + +// public +int kpSelection::textColForPoint (const QPoint &globalPoint) const +{ + if (!isText ()) + { + kdError () << "kpSelection::textColForPoint() not a text selection" << endl; + return -1; + } + + int row = textRowForPoint (globalPoint); + if (row < 0 || row >= (int) m_textLines.size ()) + return -1; + + const int localX = globalPoint.x () - textAreaRect ().x (); + + const QFontMetrics fontMetrics (m_textStyle.fontMetrics ()); + + // (should be 0 but call just in case) + int charLocalLeft = fontMetrics.width (m_textLines [row], 0); + + // OPT: binary search or guess location then move + for (int col = 0; col < (int) m_textLines [row].length (); col++) + { + // OPT: fontMetrics::charWidth() might be faster + const int nextCharLocalLeft = fontMetrics.width (m_textLines [row], col + 1); + if (localX <= (charLocalLeft + nextCharLocalLeft) / 2) + return col; + + charLocalLeft = nextCharLocalLeft; + } + + return m_textLines [row].length ()/*past end of line*/; +} + +// public +QPoint kpSelection::pointForTextRowCol (int row, int col) +{ + if (!isText ()) + { + kdError () << "kpSelection::pointForTextRowCol() not a text selection" << endl; + return KP_INVALID_POINT; + } + + if (row < 0 || row >= (int) m_textLines.size () || + col < 0 || col > (int) m_textLines [row].length ()) + { + #if DEBUG_KP_SELECTION && 1 + kdDebug () << "kpSelection::pointForTextRowCol(" + << row << "," + << col << ") out of range" + << " textLines='" + << text () + << "'" + << endl; + #endif + return KP_INVALID_POINT; + } + + const QFontMetrics fontMetrics (m_textStyle.fontMetrics ()); + + const int x = fontMetrics.width (m_textLines [row], col); + const int y = row * fontMetrics.height () + + (row >= 1 ? row * fontMetrics.leading () : 0); + + return textAreaRect ().topLeft () + QPoint (x, y); +} + +// public +kpTextStyle kpSelection::textStyle () const +{ + if (!isText ()) + { + kdError () << "kpSelection::textStyle() not a text selection" << endl; + } + + return m_textStyle; +} + +// public +void kpSelection::setTextStyle (const kpTextStyle &textStyle_) +{ + if (!isText ()) + { + kdError () << "kpSelection::setTextStyle() not a text selection" << endl; + return; + } + + m_textStyle = textStyle_; + calculateTextPixmap (); + emit changed (boundingRect ()); +} + +// public +QPixmap kpSelection::opaquePixmap () const +{ + QPixmap *p = pixmap (); + if (p) + { + return *p; + } + else + { + return QPixmap (); + } +} + +// private +void kpSelection::calculateTransparencyMask () +{ +#if DEBUG_KP_SELECTION + kdDebug () << "kpSelection::calculateTransparencyMask()" << endl; +#endif + + if (isText ()) + { + #if DEBUG_KP_SELECTION + kdDebug () << "\ttext - no need for transparency mask" << endl; + #endif + m_transparencyMask.resize (0, 0); + return; + } + + if (!m_pixmap) + { + #if DEBUG_KP_SELECTION + kdDebug () << "\tno pixmap - no need for transparency mask" << endl; + #endif + m_transparencyMask.resize (0, 0); + return; + } + + if (m_transparency.isOpaque ()) + { + #if DEBUG_KP_SELECTION + kdDebug () << "\topaque - no need for transparency mask" << endl; + #endif + m_transparencyMask.resize (0, 0); + return; + } + + m_transparencyMask.resize (m_pixmap->width (), m_pixmap->height ()); + + QImage image = kpPixmapFX::convertToImage (*m_pixmap); + QPainter transparencyMaskPainter (&m_transparencyMask); + + bool hasTransparent = false; + for (int y = 0; y < m_pixmap->height (); y++) + { + for (int x = 0; x < m_pixmap->width (); x++) + { + if (kpPixmapFX::getColorAtPixel (image, x, y).isSimilarTo (m_transparency.transparentColor (), + m_transparency.processedColorSimilarity ())) + { + transparencyMaskPainter.setPen (Qt::color1/*make it transparent*/); + hasTransparent = true; + } + else + { + transparencyMaskPainter.setPen (Qt::color0/*keep pixel as is*/); + } + + transparencyMaskPainter.drawPoint (x, y); + } + } + + transparencyMaskPainter.end (); + + if (!hasTransparent) + { + #if DEBUG_KP_SELECTION + kdDebug () << "\tcolour useless - completely opaque" << endl; + #endif + m_transparencyMask.resize (0, 0); + return; + } +} + +// public +QPixmap kpSelection::transparentPixmap () const +{ + QPixmap pixmap = opaquePixmap (); + + if (!m_transparencyMask.isNull ()) + { + kpPixmapFX::paintMaskTransparentWithBrush (&pixmap, QPoint (0, 0), + m_transparencyMask); + } + + return pixmap; +} + +// public +kpSelectionTransparency kpSelection::transparency () const +{ + return m_transparency; +} + +// public +bool kpSelection::setTransparency (const kpSelectionTransparency &transparency, + bool checkTransparentPixmapChanged) +{ + if (m_transparency == transparency) + return false; + + m_transparency = transparency; + + bool haveChanged = true; + + QBitmap oldTransparencyMask = m_transparencyMask; + calculateTransparencyMask (); + + + if (oldTransparencyMask.width () == m_transparencyMask.width () && + oldTransparencyMask.height () == m_transparencyMask.height ()) + { + if (m_transparencyMask.isNull ()) + { + #if DEBUG_KP_SELECTION + kdDebug () << "\tboth old and new pixmaps are null - nothing changed" << endl; + #endif + haveChanged = false; + } + else if (checkTransparentPixmapChanged) + { + QImage oldTransparencyMaskImage = kpPixmapFX::convertToImage (oldTransparencyMask); + QImage newTransparencyMaskImage = kpPixmapFX::convertToImage (m_transparencyMask); + + 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 + kdDebug () << "\tdiffer at " << QPoint (x, y) + << " old=" << (int *) kpPixmapFX::getColorAtPixel (oldTransparencyMaskImage, x, y).toQRgb () + << " new=" << (int *) kpPixmapFX::getColorAtPixel (newTransparencyMaskImage, x, y).toQRgb () + << endl; + #endif + changed = true; + break; + } + } + } + + if (!changed) + haveChanged = false; + } + } + + + if (haveChanged) + emit changed (boundingRect ()); + + return haveChanged; +} + + +// private +void kpSelection::flipPoints (bool horiz, bool vert) +{ + QRect oldRect = boundingRect (); + + m_points.translate (-oldRect.x (), -oldRect.y ()); + + const QWMatrix matrix = kpPixmapFX::flipMatrix (oldRect.width (), oldRect.height (), + horiz, vert); + m_points = matrix.map (m_points); + + m_points.translate (oldRect.x (), oldRect.y ()); +} + + +// public +void kpSelection::flip (bool horiz, bool vert) +{ +#if DEBUG_KP_SELECTION && 1 + kdDebug () << "kpSelection::flip(horiz=" << horiz + << ",vert=" << vert << ")" << endl; +#endif + + flipPoints (horiz, vert); + + + if (m_pixmap) + { + #if DEBUG_KP_SELECTION && 1 + kdDebug () << "\thave pixmap - flipping that" << endl; + #endif + kpPixmapFX::flip (m_pixmap, horiz, vert); + } + + if (!m_transparencyMask.isNull ()) + { + #if DEBUG_KP_SELECTION && 1 + kdDebug () << "\thave transparency mask - flipping that" << endl; + #endif + kpPixmapFX::flip (&m_transparencyMask, horiz, vert); + } + + + emit changed (boundingRect ()); +} + +#include <kpselection.moc> + diff --git a/kolourpaint/kpselection.h b/kolourpaint/kpselection.h new file mode 100644 index 00000000..69b836b9 --- /dev/null +++ b/kolourpaint/kpselection.h @@ -0,0 +1,237 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 __kpselection_h__ +#define __kpselection_h__ + +#include <qbitmap.h> +#include <qdatastream.h> +#include <qobject.h> +#include <qpixmap.h> +#include <qpoint.h> +#include <qpointarray.h> +#include <qvaluevector.h> +#include <qrect.h> +#include <qstring.h> + +#include <kpcolor.h> +#include <kppixmapfx.h> +#include <kpselectiontransparency.h> +#include <kptextstyle.h> + + +class QSize; + + +/* + * Holds a selection - will also be used for the clipboard + * so that we can retain the border. + */ +class kpSelection : public QObject +{ +Q_OBJECT + +public: + enum Type + { + Rectangle, + Ellipse, + Points, + Text + }; + + // (for any) + kpSelection (const kpSelectionTransparency &transparency = kpSelectionTransparency ()); + + // (for Rectangle & Ellipse) + kpSelection (Type type, const QRect &rect, const QPixmap &pixmap = QPixmap (), + const kpSelectionTransparency &transparency = kpSelectionTransparency ()); + kpSelection (Type type, const QRect &rect, const kpSelectionTransparency &transparency); + + // (for Text) + kpSelection (const QRect &rect, const QValueVector <QString> &textLines_, const kpTextStyle &textStyle_); + + // (for Points) + kpSelection (const QPointArray &points, const QPixmap &pixmap = QPixmap (), + const kpSelectionTransparency &transparency = kpSelectionTransparency ()); + kpSelection (const QPointArray &points, const kpSelectionTransparency &transparency); + + kpSelection (const kpSelection &rhs); + kpSelection &operator= (const kpSelection &rhs); + friend QDataStream &operator<< (QDataStream &stream, const kpSelection &selection); + friend QDataStream &operator>> (QDataStream &stream, kpSelection &selection); + void readFromStream (QDataStream &stream, + const kpPixmapFX::WarnAboutLossInfo &wali = + kpPixmapFX::WarnAboutLossInfo ()); + ~kpSelection (); + +private: + void calculatePoints (); + +public: + Type type () const; + bool isRectangular () const; + bool isText () const; + // returns either i18n ("Selection") or i18n ("Text") + QString name () const; + + int size () const; + + QBitmap maskForOwnType (bool nullForRectangular = false) const; + + // synonyms + QPoint topLeft () const; + QPoint point () const; + + int x () const; + int y () const; + + void moveBy (int dx, int dy); + void moveTo (int dx, int dy); + void moveTo (const QPoint &topLeftPoint); + + // synonyms + QPointArray points () const; + QPointArray pointArray () const; + + QRect boundingRect () const; + int width () const; + int height () const; + + + // (for non-rectangular selections, may return false even if + // kpView::onSelectionResizeHandle()) + bool contains (const QPoint &point) const; + bool contains (int x, int y); + + + // (Avoid using for text selections since text selection may + // require a background for antialiasing purposes - use paint() + // instead, else no antialising) + QPixmap *pixmap () const; + void setPixmap (const QPixmap &pixmap); + + bool usesBackgroundPixmapToPaint () const; + +private: + void paintOpaqueText (QPixmap *destPixmap, const QRect &docRect) const; + QPixmap transparentForegroundTextPixmap () const; + +public: + // (<docRect> is the document rectangle that <*destPixmap> represents) + void paint (QPixmap *destPixmap, const QRect &docRect) const; + +private: + void calculateTextPixmap (); + +public: + static QString textForTextLines (const QValueVector <QString> &textLines_); + QString text () const; // textLines() as one long string + QValueVector <QString> textLines () const; + void setTextLines (const QValueVector <QString> &textLines_); + + static int textBorderSize (); + QRect textAreaRect () const; + bool pointIsInTextBorderArea (const QPoint &globalPoint) const; + bool pointIsInTextArea (const QPoint &globalPoint) const; + + + void textResize (int width, int height); + + // TODO: Enforce in kpSelection, not just in kpToolSelection & when pasting + // (in kpMainWindow). + // Be more robust when external enforcement fails. + static int minimumWidthForTextStyle (const kpTextStyle &); + static int minimumHeightForTextStyle (const kpTextStyle &); + static QSize minimumSizeForTextStyle (const kpTextStyle &); + + static int preferredMinimumWidthForTextStyle (const kpTextStyle &textStyle); + static int preferredMinimumHeightForTextStyle (const kpTextStyle &textStyle); + static QSize preferredMinimumSizeForTextStyle (const kpTextStyle &textStyle); + + int minimumWidth () const; + int minimumHeight () const; + QSize minimumSize () const; + + int textRowForPoint (const QPoint &globalPoint) const; + int textColForPoint (const QPoint &globalPoint) const; + QPoint pointForTextRowCol (int row, int col); + + kpTextStyle textStyle () const; + void setTextStyle (const kpTextStyle &textStyle); + + // TODO: ret val inconstent with pixmap() + // - fix when merge with kpTempPixmap + QPixmap opaquePixmap () const; // same as pixmap() + +private: + void calculateTransparencyMask (); + +public: + // Returns opaquePixmap() after applying kpSelectionTransparency + QPixmap transparentPixmap () const; + + kpSelectionTransparency transparency () const; + // Returns whether or not the selection changed due to setting the + // transparency info. If <checkTransparentPixmapChanged> is set, + // it will try harder to return false (although the check is + // expensive). + bool setTransparency (const kpSelectionTransparency &transparency, + bool checkTransparentPixmapChanged = false); + +private: + void flipPoints (bool horiz, bool vert); + +public: + void flip (bool horiz, bool vert); + +signals: + void changed (const QRect &docRect); + +private: + // OPT: use implicit sharing + + Type m_type; + QRect m_rect; + QPointArray m_points; + QPixmap *m_pixmap; + + QValueVector <QString> m_textLines; + kpTextStyle m_textStyle; + + kpSelectionTransparency m_transparency; + QBitmap m_transparencyMask; + +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 kpSelectionPrivate *d; +}; + +#endif // __kpselection_h__ diff --git a/kolourpaint/kpselectiondrag.cpp b/kolourpaint/kpselectiondrag.cpp new file mode 100644 index 00000000..23ab9a4e --- /dev/null +++ b/kolourpaint/kpselectiondrag.cpp @@ -0,0 +1,294 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <qdatastream.h> + +#include <kdebug.h> + +#include <kppixmapfx.h> +#include <kpselection.h> + + +// public static +const char * const kpSelectionDrag::selectionMimeType = "application/x-kolourpaint-selection"; + + +kpSelectionDrag::kpSelectionDrag (QWidget *dragSource, const char *name) + : QImageDrag (dragSource, name) +{ +} + +kpSelectionDrag::kpSelectionDrag (const QImage &image, QWidget *dragSource, const char *name) + : QImageDrag (image, dragSource, name) +{ +} + +kpSelectionDrag::kpSelectionDrag (const kpSelection &sel, QWidget *dragSource, const char *name) + : QImageDrag (dragSource, name) +{ + setSelection (sel); +} + +kpSelectionDrag::~kpSelectionDrag () +{ +} + + +// public +void kpSelectionDrag::setSelection (const kpSelection &sel) +{ + if (!sel.pixmap ()) + { + kdError () << "kpSelectionDrag::setSelection() without pixmap" << endl; + return; + } + + m_selection = sel; + + // OPT: an awful waste of memory storing image in both selection and QImage + + // HACK: need to set image else QImageDrag::format() lies + setImage (kpPixmapFX::convertToImage (*m_selection.pixmap ())); +} + +// protected +bool kpSelectionDrag::holdsSelection () const +{ + return bool (m_selection.pixmap ()); +} + + +// public virtual [base QMimeSource] +const char *kpSelectionDrag::format (int which) const +{ +#if DEBUG_KP_SELECTION_DRAG && 0 + kdDebug () << "kpSelectionDrag::format(" << which << ")" << endl; +#endif + const char *ret = QImageDrag::format (which); + if (ret) + { + #if DEBUG_KP_SELECTION_DRAG && 0 + kdDebug () << "\tQImageDrag reports " << ret << endl; + #endif + return ret; + } + + int i; + for (i = 0; QImageDrag::format (i); i++) + ; + +#if DEBUG_KP_SELECTION_DRAG && 0 + kdDebug () << "\tend of QImageDrag format list at " << i << endl; +#endif + + if (i == which) + { + #if DEBUG_KP_SELECTION_DRAG && 0 + kdDebug () << "\treturning own mimetype" << endl; + #endif + return kpSelectionDrag::selectionMimeType; + } + else + { + #if DEBUG_KP_SELECTION_DRAG && 0 + kdDebug () << "\treturning non-existent" << endl; + #endif + return 0; + } +} + +// public virtual [base QMimeSource] +// Don't need to override in Qt 3.2 (the QMimeSource base will work) +// but we do so just in case QImageDrag later decides to override it. +bool kpSelectionDrag::provides (const char *mimeType) const +{ +#if DEBUG_KP_SELECTION_DRAG + kdDebug () << "kpSelectionDrag::provides(" << mimeType << ")" << endl; +#endif + + if (!mimeType) + return false; + + return (!strcmp (mimeType, kpSelectionDrag::selectionMimeType) || + QImageDrag::provides (mimeType)); +} + +// public virtual [base QMimeSource] +QByteArray kpSelectionDrag::encodedData (const char *mimeType) const +{ +#if DEBUG_KP_SELECTION_DRAG + kdDebug () << "kpSelectionDrag::encodedData(" << mimeType << ")" << endl; +#endif + + if (!mimeType) + return QByteArray (); + + if (!strcmp (mimeType, kpSelectionDrag::selectionMimeType)) + { + QByteArray ba; + QDataStream stream (ba, IO_WriteOnly); + + #if DEBUG_KP_SELECTION_DRAG + kdDebug () << "\twant it as kpSelection in QByteArray" << endl; + #endif + + if (holdsSelection ()) + { + #if DEBUG_KP_SELECTION_DRAG + kdDebug () << "\t\thave selection - return it" << endl; + #endif + stream << m_selection; + } + else + { + #if DEBUG_KP_SELECTION_DRAG + kdDebug () << "\t\thave image - call kpSelectionDrag::decode(QImage)" << endl; + #endif + QImage image; + if (kpSelectionDrag::decode (this, image/*ref*/)) + { + #if DEBUG_KP_SELECTION_DRAG + kdDebug () << "\t\t\tok - returning sel with image w=" + << image.width () + << " h=" + << image.height () + << endl; + #endif + + QPixmap pixmap = kpPixmapFX::convertToPixmapAsLosslessAsPossible (image); + + stream << kpSelection (kpSelection::Rectangle, + QRect (0, 0, pixmap.width (), pixmap.height ()), + pixmap); + } + else + { + kdError () << "kpSelectionDrag::encodedData(" << mimeType << ")" + << " kpSelectionDrag(QImage) could not decode data into QImage" + << endl; + stream << kpSelection (); + } + } + + return ba; + } + else + { + #if DEBUG_KP_SELECTION_DRAG + kdDebug () << "\twant it as QImage in QByteArray" << endl; + #endif + + return QImageDrag::encodedData (mimeType); + } +} + + +// public static +bool kpSelectionDrag::canDecode (const QMimeSource *e) +{ +#if DEBUG_KP_SELECTION_DRAG + kdDebug () << "kpSelectionDrag::canDecode()" << endl; +#endif + + if (!e) + return false; + + return (e->provides (kpSelectionDrag::selectionMimeType) || + QImageDrag::canDecode (e)); +} + + +// public static +bool kpSelectionDrag::decode (const QMimeSource *e, QImage &img) +{ +#if DEBUG_KP_SELECTION_DRAG + kdDebug () << "kpSelectionDrag::decode(QImage)" << endl; +#endif + if (!e) + return false; + + return (QImageDrag::canDecode (e) && // prevents X errors, jumps based on unitialised values... + QImageDrag::decode (e, img/*ref*/)); +} + +// public static +bool kpSelectionDrag::decode (const QMimeSource *e, kpSelection &sel, + const kpPixmapFX::WarnAboutLossInfo &wali) +{ +#if DEBUG_KP_SELECTION_DRAG + kdDebug () << "kpSelectionDrag::decode(kpSelection)" << endl; +#endif + if (!e) + return false; + + if (e->provides (kpSelectionDrag::selectionMimeType)) + { + #if DEBUG_KP_SELECTION_DRAG + kdDebug () << "\tmimeSource provides selection - just return it in QByteArray" << endl; + #endif + QByteArray data = e->encodedData (kpSelectionDrag::selectionMimeType); + QDataStream stream (data, IO_ReadOnly); + + // (no need for wali as kpSelection's by definition only support QPixmap's) + stream >> sel; + } + else + { + #if DEBUG_KP_SELECTION_DRAG + kdDebug () << "\tmimeSource doesn't provide selection - try image" << endl; + #endif + + QImage image; + if (kpSelectionDrag::decode (e, image/*ref*/)) + { + #if DEBUG_KP_SELECTION_DRAG + kdDebug () << "\tok w=" << image.width () << " h=" << image.height () << endl; + #endif + + sel = kpSelection (kpSelection::Rectangle, + QRect (0, 0, image.width (), image.height ()), + kpPixmapFX::convertToPixmapAsLosslessAsPossible (image, wali)); + } + else + { + #if DEBUG_KP_SELECTION_DRAG + kdDebug () << "kpSelectionDrag::decode(kpSelection) mimeSource had no sel " + "and could not decode to image" << endl; + #endif + return false; + } + } + + return true; +} + +#include <kpselectiondrag.moc> diff --git a/kolourpaint/kpselectiondrag.h b/kolourpaint/kpselectiondrag.h new file mode 100644 index 00000000..5aae82d9 --- /dev/null +++ b/kolourpaint/kpselectiondrag.h @@ -0,0 +1,71 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <qdragobject.h> + +#include <kppixmapfx.h> +#include <kpselection.h> + + +class kpSelectionDrag : public QImageDrag +{ +Q_OBJECT + +public: + kpSelectionDrag (QWidget *dragSource = 0, const char *name = 0); + kpSelectionDrag (const QImage &image, QWidget *dragSource = 0, const char *name = 0); + kpSelectionDrag (const kpSelection &sel, QWidget *dragSource = 0, const char *name = 0); + virtual ~kpSelectionDrag (); + + static const char * const selectionMimeType; + + void setSelection (const kpSelection &sel); + +protected: + bool holdsSelection () const; + +public: + virtual const char *format (int which = 0) const; + virtual bool provides (const char *mimeType) const; + virtual QByteArray encodedData (const char *mimeType) const; + + static bool canDecode (const QMimeSource *e); + static bool decode (const QMimeSource *e, QImage &img); + static bool decode (const QMimeSource *e, kpSelection &sel, + const kpPixmapFX::WarnAboutLossInfo &wali = + kpPixmapFX::WarnAboutLossInfo ()); + +protected: + kpSelection m_selection; +}; + + +#endif // __kp_selection_drag_h__ diff --git a/kolourpaint/kpselectiontransparency.cpp b/kolourpaint/kpselectiontransparency.cpp new file mode 100644 index 00000000..62919be4 --- /dev/null +++ b/kolourpaint/kpselectiontransparency.cpp @@ -0,0 +1,178 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <kpselectiontransparency.h> + +#include <kdebug.h> + +#include <kpcolor.h> +#include <kpcolorsimilaritydialog.h> + + +kpSelectionTransparency::kpSelectionTransparency () + : m_isOpaque (true) +{ + setColorSimilarity (0); +} + +kpSelectionTransparency::kpSelectionTransparency (const kpColor &transparentColor, double colorSimilarity) + : m_isOpaque (false), + m_transparentColor (transparentColor) +{ + setColorSimilarity (colorSimilarity); +} + +kpSelectionTransparency::kpSelectionTransparency (bool isOpaque, const kpColor &transparentColor, + double colorSimilarity) + : m_isOpaque (isOpaque), + m_transparentColor (transparentColor) +{ + setColorSimilarity (colorSimilarity); +} + +bool kpSelectionTransparency::operator== (const kpSelectionTransparency &rhs) const +{ +#if DEBUG_KP_SELECTION_TRANSPARENCY && 0 + kdDebug () << "kpSelectionTransparency::operator==()" << endl; +#endif + + if (m_isOpaque != rhs.m_isOpaque) + { + #if DEBUG_KP_SELECTION_TRANSPARENCY && 0 + kdDebug () << "\tdifferent opacity: lhs=" << m_isOpaque + << " rhs=" << rhs.m_isOpaque + << endl; + #endif + return false; + } + + if (m_isOpaque) + { + #if DEBUG_KP_SELECTION_TRANSPARENCY && 0 + kdDebug () << "\tboth opaque - eq" << endl; + #endif + return true; + } + +#if DEBUG_KP_SELECTION_TRANSPARENCY && 0 + kdDebug () << "\tcolours: lhs=" << (int *) m_transparentColor.toQRgb () + << " rhs=" << (int *) rhs.m_transparentColor.toQRgb () + << endl; + kdDebug () << "\tcolour similarity: lhs=" << m_colorSimilarity + << " rhs=" << rhs.m_colorSimilarity + << endl; +#endif + + return (m_transparentColor == rhs.m_transparentColor && + m_colorSimilarity == rhs.m_colorSimilarity); +} + +bool kpSelectionTransparency::operator!= (const kpSelectionTransparency &rhs) const +{ + return !(*this == rhs); +} + +kpSelectionTransparency::~kpSelectionTransparency () +{ +} + + +// public +bool kpSelectionTransparency::isOpaque () const +{ + return m_isOpaque; +} + +// public +bool kpSelectionTransparency::isTransparent () const +{ + return !isOpaque (); +} + +// public +void kpSelectionTransparency::setOpaque (bool yes) +{ + m_isOpaque = yes; +} + +// public +void kpSelectionTransparency::setTransparent (bool yes) +{ + setOpaque (!yes); +} + + +// public +kpColor kpSelectionTransparency::transparentColor () const +{ + if (m_isOpaque) + { + // There are legitimate uses for this so no kdError() + kdDebug () << "kpSelectionTransparency::transparentColor() " + "getting transparent color even though opaque" << endl; + } + + return m_transparentColor; +} + +// public +void kpSelectionTransparency::setTransparentColor (const kpColor &transparentColor) +{ + m_transparentColor = transparentColor; +} + + +// public +double kpSelectionTransparency::colorSimilarity () const +{ + if (m_colorSimilarity < 0 || + m_colorSimilarity > kpColorSimilarityDialog::maximumColorSimilarity) + { + kdError () << "kpSelectionTransparency::colorSimilarity() invalid colorSimilarity" << endl; + return 0; + } + + return m_colorSimilarity; +} + +// pubulic +void kpSelectionTransparency::setColorSimilarity (double colorSimilarity) +{ + m_colorSimilarity = colorSimilarity; + m_processedColorSimilarity = kpColor::processSimilarity (colorSimilarity); +} + +// public +int kpSelectionTransparency::processedColorSimilarity () const +{ + return m_processedColorSimilarity; +} + diff --git a/kolourpaint/kpselectiontransparency.h b/kolourpaint/kpselectiontransparency.h new file mode 100644 index 00000000..690c4c1e --- /dev/null +++ b/kolourpaint/kpselectiontransparency.h @@ -0,0 +1,80 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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_transparency_h__ +#define __kp_selection_transparency_h__ + +#include <kpcolor.h> + + +// This does not apply to the Text Tool. Use kpTextStyle for that. +class kpSelectionTransparency +{ +public: + // Opaque selection + kpSelectionTransparency (); + // Selection that's transparent at pixels with <color> + kpSelectionTransparency (const kpColor &transparentColor, double colorSimilarity); + // If <isOpaque>, <transparentColor> 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 !<isOpaque>, + // because setTransparent(true) might be called later, after which + // the <transparentColor> would suddenly become important. + // + // It is a similar case with <colorSimilarity>, although <colorSimilarity> + // must be in-range (see kpColorSimilarityDialog). + kpSelectionTransparency (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 kpSelectionTransparency &rhs) const; + bool operator!= (const kpSelectionTransparency &rhs) const; + ~kpSelectionTransparency (); + + 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_selection_transparency_h__ diff --git a/kolourpaint/kpsinglekeytriggersaction.cpp b/kolourpaint/kpsinglekeytriggersaction.cpp new file mode 100644 index 00000000..d3b64002 --- /dev/null +++ b/kolourpaint/kpsinglekeytriggersaction.cpp @@ -0,0 +1,155 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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_SINGLE_KEY_TRIGGERS_ACTION 0 + + +// +// kpSingleKeyTriggersActionInterface +// + + +#include <kpsinglekeytriggersaction.h> + +#include <kdebug.h> + +#include <kptool.h> + + +kpSingleKeyTriggersActionInterface::kpSingleKeyTriggersActionInterface () +{ + m_singleKeyTriggersEnabled = true; +} + +kpSingleKeyTriggersActionInterface::~kpSingleKeyTriggersActionInterface () +{ +} + + +// public +bool kpSingleKeyTriggersActionInterface::singleKeyTriggersEnabled () const +{ + return m_singleKeyTriggersEnabled; +} + +// public +void kpSingleKeyTriggersActionInterface::enableSingleKeyTriggers (bool enable) +{ +#if DEBUG_KP_SINGLE_KEY_TRIGGERS_ACTION + kdDebug () << "kpSingleKeyTriggersActionInterface(" << /*virtual*/actionName () + << ")::enableSingleKeyTriggers(" << enable << ")" + << endl; +#endif + + if (enable == m_singleKeyTriggersEnabled) + { + #if DEBUG_KP_SINGLE_KEY_TRIGGERS_ACTION + kdDebug () << "\tnop" << endl; + #endif + return; + } + + m_singleKeyTriggersEnabled = enable; + + + if (enable) + { + #if DEBUG_KP_SINGLE_KEY_TRIGGERS_ACTION + kdDebug () << "\tre-enabling to " << m_fullShortcut.toString () << endl; + #endif + /*pure virtual*/actionSetShortcut (m_fullShortcut); + } + else // disable single key triggers + { + m_fullShortcut = /*pure virtual*/actionShortcut (); + + KShortcut newShortcut; + if (kpTool::containsSingleKeyTrigger (m_fullShortcut, &newShortcut)) + { + #if DEBUG_KP_SINGLE_KEY_TRIGGERS_ACTION + kdDebug () << "\tchange shortcuts: old=" + << m_fullShortcut.toString () + << " new=" + << newShortcut.toString () + << endl; + #endif + /*pure virtual*/actionSetShortcut (newShortcut); + } + else + { + #if DEBUG_KP_SINGLE_KEY_TRIGGERS_ACTION + kdDebug () << "\tshortcut is untouched " + << m_fullShortcut.toString () + << endl; + #endif + } + } +} + + +// +// kpSingleKeyTriggersAction +// + + +kpSingleKeyTriggersAction::kpSingleKeyTriggersAction (const QString &text, + const KShortcut &shortcut, + const QObject *receiver, const char *slot, + KActionCollection *parent, const char *name) + : KAction (text, shortcut, receiver, slot, parent, name) +{ +} + +kpSingleKeyTriggersAction::~kpSingleKeyTriggersAction () +{ +} + + +// +// kpSingleKeyTriggersAction implements kpSingleKeyTriggersActionInterface +// + +// public virtual [base kpSingleKeyTriggersActionInterface] +const char *kpSingleKeyTriggersAction::actionName () const +{ + return name (); +} + +// public virtual [base kpSingleKeyTriggersActionInterface] +KShortcut kpSingleKeyTriggersAction::actionShortcut () const +{ + return shortcut (); +} + +// public virtual [base kpSingleKeyTriggersActionInterface] +void kpSingleKeyTriggersAction::actionSetShortcut (const KShortcut &shortcut) +{ + setShortcut (shortcut); +} + + +#include <kpsinglekeytriggersaction.moc> diff --git a/kolourpaint/kpsinglekeytriggersaction.h b/kolourpaint/kpsinglekeytriggersaction.h new file mode 100644 index 00000000..fc404bd5 --- /dev/null +++ b/kolourpaint/kpsinglekeytriggersaction.h @@ -0,0 +1,82 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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_SINGLE_KEY_TRIGGERS_ACTION_H +#define KP_SINGLE_KEY_TRIGGERS_ACTION_H + + +#include <kshortcut.h> + + +class kpSingleKeyTriggersActionInterface +{ +public: + kpSingleKeyTriggersActionInterface (); + virtual ~kpSingleKeyTriggersActionInterface (); + + bool singleKeyTriggersEnabled () const; + void enableSingleKeyTriggers (bool enable = true); + + // Avoid inheritance diamond by not deriving from KAction + // so you'll have to implement these by talking to your base KAction. + virtual const char *actionName () const { return 0; } + virtual KShortcut actionShortcut () const = 0; + virtual void actionSetShortcut (const KShortcut &shortcut) = 0; + +protected: + bool m_singleKeyTriggersEnabled; + KShortcut m_fullShortcut; +}; + + +#include <kaction.h> + + +class kpSingleKeyTriggersAction : public KAction, + public kpSingleKeyTriggersActionInterface +{ +Q_OBJECT + +public: + kpSingleKeyTriggersAction (const QString &text, + const KShortcut &shortcut, + const QObject *receiver, const char *slot, + KActionCollection *parent, const char *name); + virtual ~kpSingleKeyTriggersAction (); + + + // + // kpSingleKeyTriggersActionInterface + // + + virtual const char *actionName () const; + virtual KShortcut actionShortcut () const; + virtual void actionSetShortcut (const KShortcut &shortcut); +}; + + +#endif // KP_SINGLE_KEY_TRIGGERS_ACTION_H diff --git a/kolourpaint/kptemppixmap.cpp b/kolourpaint/kptemppixmap.cpp new file mode 100644 index 00000000..55f669ee --- /dev/null +++ b/kolourpaint/kptemppixmap.cpp @@ -0,0 +1,148 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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_TEMP_PIXMAP 0 + + +#include <kptemppixmap.h> + +#include <kppixmapfx.h> +#include <kpviewmanager.h> + + +kpTempPixmap::kpTempPixmap (bool isBrush, RenderMode renderMode, + const QPoint &topLeft, const QPixmap &pixmap) + : m_isBrush (isBrush), + m_renderMode (renderMode), + m_topLeft (topLeft), + m_pixmap (pixmap) +{ +} + +kpTempPixmap::kpTempPixmap (const kpTempPixmap &rhs) + : m_isBrush (rhs.m_isBrush), + m_renderMode (rhs.m_renderMode), + m_topLeft (rhs.m_topLeft), + m_pixmap (rhs.m_pixmap) +{ +} + +kpTempPixmap &kpTempPixmap::operator= (const kpTempPixmap &rhs) +{ + if (this == &rhs) + return *this; + + m_isBrush = rhs.m_isBrush; + m_renderMode = rhs.m_renderMode; + m_topLeft = rhs.m_topLeft; + m_pixmap = rhs.m_pixmap; + + return *this; +} + +kpTempPixmap::~kpTempPixmap () +{ +} + + +// public +bool kpTempPixmap::isBrush () const +{ + return m_isBrush; +} + +// public +kpTempPixmap::RenderMode kpTempPixmap::renderMode () const +{ + return m_renderMode; +} + +// public +QPoint kpTempPixmap::topLeft () const +{ + return m_topLeft; +} + +// public +QPixmap kpTempPixmap::pixmap () const +{ + return m_pixmap; +} + + +// public +bool kpTempPixmap::isVisible (const kpViewManager *vm) const +{ + return m_isBrush ? (bool) vm->viewUnderCursor () : true; +} + +// public +QRect kpTempPixmap::rect () const +{ + return QRect (m_topLeft.x (), m_topLeft.y (), + m_pixmap.width (), m_pixmap.height ()); +} + +// public +int kpTempPixmap::width () const +{ + return m_pixmap.width (); +} + +// public +int kpTempPixmap::height () const +{ + return m_pixmap.height (); +} + + +// public +bool kpTempPixmap::mayChangeDocumentMask () const +{ + return (m_renderMode == SetPixmap || + m_renderMode == PaintMaskTransparentWithBrush); +} + +// public +void kpTempPixmap::paint (QPixmap *destPixmap, const QRect &docRect) const +{ +#define PARAMS destPixmap, m_topLeft - docRect.topLeft (), m_pixmap + switch (m_renderMode) + { + case SetPixmap: + kpPixmapFX::setPixmapAt (PARAMS); + break; + case PaintPixmap: + kpPixmapFX::paintPixmapAt (PARAMS); + break; + case PaintMaskTransparentWithBrush: + kpPixmapFX::paintMaskTransparentWithBrush (PARAMS); + break; + } +#undef PARAMS +} diff --git a/kolourpaint/kptemppixmap.h b/kolourpaint/kptemppixmap.h new file mode 100644 index 00000000..6ddb9413 --- /dev/null +++ b/kolourpaint/kptemppixmap.h @@ -0,0 +1,90 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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: maybe merge with kpSelection? + + +#ifndef __kp_temp_pixmap_h__ +#define __kp_temp_pixmap_h__ + + +#include <qpoint.h> +#include <qpixmap.h> + +class kpViewManager; + + +class kpTempPixmap +{ +public: + enum RenderMode + { + SetPixmap, + PaintPixmap, + PaintMaskTransparentWithBrush + }; + + /* + * <isBrush> Specifies that its visibility is dependent on whether or + * not the mouse cursor is inside a view. If false, the + * pixmap is always displayed. + */ + kpTempPixmap (bool isBrush, RenderMode renderMode, const QPoint &topLeft, const QPixmap &pixmap); + kpTempPixmap (const kpTempPixmap &rhs); + kpTempPixmap &operator= (const kpTempPixmap &rhs); + ~kpTempPixmap (); + + bool isBrush () const; + RenderMode renderMode () const; + QPoint topLeft () const; + QPixmap pixmap () const; + + bool isVisible (const kpViewManager *vm) const; + QRect rect () const; + int width () const; + int height () const; + + + // Returns whether a call to paint() may change <*destPixmap>'s mask + bool mayChangeDocumentMask () const; + + /* + * Draws itself onto <*destPixmap>, given that <*destPixmap> represents + * the unzoomed <docRect> of the kpDocument. You should check for + * visibility before calling this function. + */ + void paint (QPixmap *destPixmap, const QRect &docRect) const; + +private: + bool m_isBrush; + RenderMode m_renderMode; + QPoint m_topLeft; + QPixmap m_pixmap; +}; + + +#endif // __kp_temp_pixmap_h__ diff --git a/kolourpaint/kptextstyle.cpp b/kolourpaint/kptextstyle.cpp new file mode 100644 index 00000000..9f4d8fce --- /dev/null +++ b/kolourpaint/kptextstyle.cpp @@ -0,0 +1,279 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <kptextstyle.h> + +#include <qdatastream.h> +#include <qfont.h> +#include <qfontmetrics.h> + + +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 () +{ +} + + +// 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 +kpColor kpTextStyle::effectiveBackgroundColor () const +{ + if (isBackgroundOpaque ()) + return backgroundColor (); + else + return kpColor::transparent; +} + + +// 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/kolourpaint/kptextstyle.h b/kolourpaint/kptextstyle.h new file mode 100644 index 00000000..9356ce2c --- /dev/null +++ b/kolourpaint/kptextstyle.h @@ -0,0 +1,108 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <qstring.h> + +#include <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 without applying any + // isBackground(Opaque|Transparent)() transformation. + // See effectiveBackgroundColor(). + kpColor backgroundColor () const; + void setBackgroundColor (const kpColor &bcolor); + + bool isBackgroundOpaque () const; + void setBackgroundOpaque (bool yes = true); + + bool isBackgroundTransparent () const; + void setBackgroundTransparent (bool yes = true); + + + // If isBackgroundOpaque(), returns backgroundColor(). + // Else, returns kpColor::transparent. + kpColor effectiveBackgroundColor () const; + + 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/kolourpaint/kpthumbnail.cpp b/kolourpaint/kpthumbnail.cpp new file mode 100644 index 00000000..a45164ac --- /dev/null +++ b/kolourpaint/kpthumbnail.cpp @@ -0,0 +1,213 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <qdockarea.h> +#include <qdockwindow.h> +#include <qtimer.h> + +#include <kdebug.h> +#include <klocale.h> + +#include <kpdefs.h> +#include <kpdocument.h> +#include <kpmainwindow.h> +#include <kpthumbnailview.h> +#include <kptool.h> + + +// TODO: get out of the Alt+Tab list +kpThumbnail::kpThumbnail (kpMainWindow *parent, const char *name) +#if KP_IS_QT_3_3 + : QDockWindow (QDockWindow::OutsideDock, parent, name), +#else // With Qt <3.3, OutsideDock requires parent = 0, + // sync: make sure outside of dock + : QDockWindow (QDockWindow::InDock, parent, name), + #warning "Using Qt <3.3: the thumbnail will flicker more when appearing" +#endif + m_mainWindow (parent), + m_view (0) +{ + if (!parent) + kdError () << "kpThumbnail::kpThumbnail() requires parent" << endl; + + +#if !KP_IS_QT_3_3 + if (parent) + { + hide (); + + // sync: make sure outside of dock + parent->moveDockWindow (this, Qt::DockTornOff); + } +#endif + + + if (parent) + { + // Prevent thumbnail from docking - it's _really_ irritating otherwise + parent->leftDock ()->setAcceptDockWindow (this, false); + parent->rightDock ()->setAcceptDockWindow (this, false); + parent->topDock ()->setAcceptDockWindow (this, false); + parent->bottomDock ()->setAcceptDockWindow (this, false); + } + + + QSize layoutMinimumSize = layout () ? layout ()->minimumSize () : QSize (); +#if DEBUG_KP_THUMBNAIL + kdDebug () << "\tlayout=" << layout () + << " minSize=" << (layout () ? layout ()->minimumSize () : QSize ()) << endl; + kdDebug () << "\tboxLayout=" << boxLayout () + << " minSize=" << (boxLayout () ? boxLayout ()->minimumSize () : QSize ()) + << endl; +#endif + if (layout ()) + layout ()->setResizeMode (QLayout::FreeResize); + setMinimumSize (QMAX (layoutMinimumSize.width (), 64), + QMAX (layoutMinimumSize.height (), 64)); + + + // Enable "X" Close Button + setCloseMode (QDockWindow::Always); + + setResizeEnabled (true); + + updateCaption (); +} + +kpThumbnail::~kpThumbnail () +{ +} + + +// public +kpThumbnailView *kpThumbnail::view () const +{ + return m_view; +} + +// public +void kpThumbnail::setView (kpThumbnailView *view) +{ +#if DEBUG_KP_THUMBNAIL + kdDebug () << "kpThumbnail::setView(" << view << ")" << endl; +#endif + + if (m_view == view) + return; + + + if (m_view) + { + disconnect (m_view, SIGNAL (destroyed ()), + this, SLOT (slotViewDestroyed ())); + disconnect (m_view, SIGNAL (zoomLevelChanged (int, int)), + this, SLOT (updateCaption ())); + + boxLayout ()->remove (m_view); + } + + m_view = view; + + if (m_view) + { + connect (m_view, SIGNAL (destroyed ()), + this, SLOT (slotViewDestroyed ())); + connect (m_view, SIGNAL (zoomLevelChanged (int, int)), + this, SLOT (updateCaption ())); + updateCaption (); + + boxLayout ()->addWidget (m_view); + m_view->show (); + } +} + + +// public slot +void kpThumbnail::updateCaption () +{ + setCaption (view () ? view ()->caption () : i18n ("Thumbnail")); +} + + +// public slot virtual [base QDockWindow] +void kpThumbnail::dock () +{ +#if DEBUG_KP_THUMBNAIL + kdDebug () << "kpThumbnail::dock() CALLED - ignoring request" << endl; +#endif + + // --- ignore all requests to dock --- +} + + +// protected slot +void kpThumbnail::slotViewDestroyed () +{ +#if DEBUG_KP_THUMBNAIL + kdDebug () << "kpThumbnail::slotViewDestroyed()" << endl; +#endif + + m_view = 0; + updateCaption (); +} + + +// protected virtual [base QWidget] +void kpThumbnail::resizeEvent (QResizeEvent *e) +{ +#if DEBUG_KP_THUMBNAIL + kdDebug () << "kpThumbnail::resizeEvent(" << width () + << "," << height () << ")" << endl; +#endif + + QDockWindow::resizeEvent (e); + + // updateVariableZoom (); TODO: is below a good idea since this commented out + + if (m_mainWindow) + { + m_mainWindow->notifyThumbnailGeometryChanged (); + + if (m_mainWindow->tool ()) + m_mainWindow->tool ()->somethingBelowTheCursorChanged (); + } +} + +// protected virtual [base QWidget] +void kpThumbnail::moveEvent (QMoveEvent * /*e*/) +{ + if (m_mainWindow) + m_mainWindow->notifyThumbnailGeometryChanged (); +} + + +#include <kpthumbnail.moc> diff --git a/kolourpaint/kpthumbnail.h b/kolourpaint/kpthumbnail.h new file mode 100644 index 00000000..183dc2f1 --- /dev/null +++ b/kolourpaint/kpthumbnail.h @@ -0,0 +1,68 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <qdockwindow.h> + +class kpMainWindow; +class kpThumbnailView; + + +class kpThumbnail : public QDockWindow +{ +Q_OBJECT + +public: + kpThumbnail (kpMainWindow *parent, const char *name = 0); + virtual ~kpThumbnail (); + +public: + kpThumbnailView *view () const; + void setView (kpThumbnailView *view); + +public slots: + void updateCaption (); + + virtual void dock (); + +protected slots: + void slotViewDestroyed (); + +protected: + virtual void resizeEvent (QResizeEvent *e); + virtual void moveEvent (QMoveEvent *e); + +private: + kpMainWindow *m_mainWindow; + kpThumbnailView *m_view; +}; + + +#endif // __kp_thumbnail_h__ diff --git a/kolourpaint/kptool.cpp b/kolourpaint/kptool.cpp new file mode 100644 index 00000000..8d337c5b --- /dev/null +++ b/kolourpaint/kptool.cpp @@ -0,0 +1,1666 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 0 + + +#include <kptool.h> + +#include <limits.h> + +#include <qapplication.h> +#include <qclipboard.h> +#include <qcursor.h> +#include <qevent.h> +#include <qlayout.h> +#include <qpainter.h> +#include <qpixmap.h> + +#include <kapplication.h> +#include <kdebug.h> +#include <kiconloader.h> +#include <klocale.h> +#include <kmessagebox.h> + +#include <kpcolor.h> +#include <kpcolortoolbar.h> +#include <kpdefs.h> +#include <kpmainwindow.h> +#include <kppixmapfx.h> +#include <kpsinglekeytriggersaction.h> +#include <kptoolaction.h> +#include <kptooltoolbar.h> +#include <kpview.h> +#include <kpviewmanager.h> + + +// +// kpTool +// + +struct kpToolPrivate +{ +}; + + +kpTool::kpTool (const QString &text, const QString &description, + int key, + kpMainWindow *mainWindow, const char *name) +{ + init (text, description, key, mainWindow, name); +} + +kpTool::~kpTool () +{ + // before destructing, stop using the tool + if (m_began) + endInternal (); + + if (m_action) + { + if (m_mainWindow && m_mainWindow->actionCollection ()) + m_mainWindow->actionCollection ()->remove (m_action); + else + delete m_action; + } + + delete d; +} + + +// private +void kpTool::init (const QString &text, const QString &description, + int key, + kpMainWindow *mainWindow, const char *name) +{ + d = new kpToolPrivate (); + + m_key = key; + m_action = 0; + m_ignoreColorSignals = 0; + m_shiftPressed = false, m_controlPressed = false, m_altPressed = false; // set in beginInternal() + m_beganDraw = false; + m_text = text, m_description = description, m_name = name; + m_mainWindow = mainWindow; + m_began = false; + m_viewUnderStartPoint = 0; + m_userShapeStartPoint = KP_INVALID_POINT; + m_userShapeEndPoint = KP_INVALID_POINT; + m_userShapeSize = KP_INVALID_SIZE; + + createAction (); +} + + +// private +void kpTool::createAction () +{ +#if DEBUG_KP_TOOL && 0 + kdDebug () << "kpTool(" << name () << "::createAction()" << endl; +#endif + + if (!m_mainWindow) + { + kdError () << "kpTool::createAction() without mw" << endl; + return; + } + + KActionCollection *ac = m_mainWindow->actionCollection (); + if (!ac) + { + kdError () << "kpTool::createAction() without ac" << endl; + return; + } + + + if (m_action) + { + // TODO: I don't think this will ever be executed as we are not called + // outside of the constructor. + #if DEBUG_KP_TOOL + kdDebug () << "\tdeleting existing" << endl; + #endif + ac->remove (m_action); + m_action = 0; + } + + + m_action = new kpToolAction (text (), iconName (), shortcutForKey (m_key), + this, SLOT (slotActionActivated ()), + m_mainWindow->actionCollection (), name ()); + m_action->setExclusiveGroup (QString::fromLatin1 ("Tool Box Actions")); + m_action->setWhatsThis (description ()); + + connect (m_action, SIGNAL (toolTipChanged (const QString &)), + this, SLOT (slotActionToolTipChanged (const QString &))); +} + + +// protected slot +void kpTool::slotActionToolTipChanged (const QString &string) +{ + emit actionToolTipChanged (string); +} + + +// public +QString kpTool::text () const +{ + return m_text; +} + +// public +void kpTool::setText (const QString &text) +{ + m_text = text; + + if (m_action) + m_action->setText (m_text); + else + createAction (); +} + + +// public static +QString kpTool::toolTipForTextAndShortcut (const QString &text, + const KShortcut &shortcut) +{ + for (int i = 0; i < (int) shortcut.count (); i++) + { + const KKeySequence seq = shortcut.seq (i); + if (seq.count () == 1 && containsSingleKeyTrigger (seq)) + { + return i18n ("<Tool Name> (<Single Accel Key>)", + "%1 (%2)") + .arg (text, seq.toString ()); + } + } + + return text; +} + +// public static +QString kpTool::toolTip () const +{ + return toolTipForTextAndShortcut (text (), shortcut ()); +} + + +// public +int kpTool::key () const +{ + return m_key; +} + +// public +void kpTool::setKey (int key) +{ + m_key = key; + + if (m_action) + // TODO: this probably not wise since it nukes the user's settings + m_action->setShortcut (shortcutForKey (m_key)); + else + createAction (); +} + +// public static +KShortcut kpTool::shortcutForKey (int key) +{ + KShortcut shortcut; + + if (key) + { + shortcut.append (KKeySequence (KKey (key))); + // (CTRL+<key>, ALT+<key>, CTRL+ALT+<key>, CTRL+SHIFT+<key> + // all clash with global KDE shortcuts) + shortcut.append (KKeySequence (KKey (Qt::ALT + Qt::SHIFT + key))); + } + + return shortcut; +} + +// public +KShortcut kpTool::shortcut () const +{ + return m_action ? m_action->shortcut () : KShortcut (); +} + + +// public static +bool kpTool::keyIsText (int key) +{ + // TODO: should work like !QKeyEvent::text().isEmpty() + return !(key & (Qt::MODIFIER_MASK ^ Qt::SHIFT)); +} + +// public static +bool kpTool::containsSingleKeyTrigger (const KKeySequence &seq) +{ + for (int i = 0; i < (int) seq.count (); i++) + { + const KKey key = seq.key (i); + if (keyIsText (key.keyCodeQt ())) + return true; + } + + return false; +} + +// public static +bool kpTool::containsSingleKeyTrigger (const KShortcut &shortcut, + KShortcut *shortcutWithoutSingleKeyTriggers) +{ + if (shortcutWithoutSingleKeyTriggers) + *shortcutWithoutSingleKeyTriggers = shortcut; + + + KShortcut newShortcut; + bool needNewShortcut = false; + + for (int i = 0; i < (int) shortcut.count (); i++) + { + const KKeySequence seq = shortcut.seq (i); + + if (containsSingleKeyTrigger (seq)) + { + needNewShortcut = true; + } + else + { + newShortcut.append (seq); + } + } + + + if (needNewShortcut && shortcutWithoutSingleKeyTriggers) + *shortcutWithoutSingleKeyTriggers = newShortcut; + + return needNewShortcut; +} + + +// public +bool kpTool::singleKeyTriggersEnabled () const +{ + return (m_action ? m_action->singleKeyTriggersEnabled () : true); +} + +// public +void kpTool::enableSingleKeyTriggers (bool enable) +{ +#if DEBUG_KP_TOOL && 0 + kdDebug () << "kpTool(" << name () << ")::enableSingleKeyTriggers(" + << enable << ")" << endl; +#endif + + if (!m_action) + { + #if DEBUG_KP_TOOL && 0 + kdDebug () << "\tno action" << endl; + #endif + return; + } + + m_action->enableSingleKeyTriggers (enable); +} + + +// public +QString kpTool::description () const +{ + return m_description; +} + +// public +void kpTool::setDescription (const QString &description) +{ + m_description = description; + + if (m_action) + m_action->setWhatsThis (m_description); + else + createAction (); +} + + +// public +const char *kpTool::name () const +{ + return m_name; +} + + +// static +QRect kpTool::neededRect (const QRect &rect, int lineWidth) +{ + int x1, y1, x2, y2; + rect.coords (&x1, &y1, &x2, &y2); + + if (lineWidth < 1) + lineWidth = 1; + + return QRect (QPoint (x1 - lineWidth + 1, y1 - lineWidth + 1), + QPoint (x2 + lineWidth - 1, y2 + lineWidth - 1)); +} + +// static +QPixmap kpTool::neededPixmap (const QPixmap &pixmap, const QRect &boundingRect) +{ + return kpPixmapFX::getPixmapAt (pixmap, boundingRect); +} + + +// public +bool kpTool::hasCurrentPoint () const +{ + return (viewUnderStartPoint () || viewUnderCursor ()); +} + +// public +QPoint kpTool::currentPoint (bool zoomToDoc) const +{ +#if DEBUG_KP_TOOL && 0 + kdDebug () << "kpTool::currentPoint(zoomToDoc=" << zoomToDoc << ")" << endl; + kdDebug () << "\tviewUnderStartPoint=" + << (viewUnderStartPoint () ? viewUnderStartPoint ()->name () : "(none)") + << " viewUnderCursor=" + << (viewUnderCursor () ? viewUnderCursor ()->name () : "(none)") + << endl; +#endif + + kpView *v = viewUnderStartPoint (); + if (!v) + { + v = viewUnderCursor (); + if (!v) + { + #if DEBUG_KP_TOOL && 0 + kdDebug () << "\tno view - returning sentinel" << endl; + #endif + return KP_INVALID_POINT; + } + } + + + const QPoint globalPos = QCursor::pos (); + const QPoint viewPos = v->mapFromGlobal (globalPos); +#if DEBUG_KP_TOOL && 0 + kdDebug () << "\tglobalPos=" << globalPos + << " viewPos=" << viewPos + << endl; +#endif + if (!zoomToDoc) + return viewPos; + + + const QPoint docPos = v->transformViewToDoc (viewPos); +#if DEBUG_KP_TOOL && 0 + kdDebug () << "\tdocPos=" << docPos << endl; +#endif + return docPos; +} + + +// public slot +void kpTool::somethingBelowTheCursorChanged () +{ + somethingBelowTheCursorChanged (currentPoint (), + currentPoint (false/*view point*/)); +} + +// private +// TODO: don't dup code from mouseMoveEvent() +void kpTool::somethingBelowTheCursorChanged (const QPoint ¤tPoint_, + const QPoint ¤tViewPoint_) +{ +#if DEBUG_KP_TOOL && 0 + kdDebug () << "kpTool::somethingBelowTheCursorChanged(docPoint=" + << currentPoint_ + << " viewPoint=" + << currentViewPoint_ + << ")" << endl; + kdDebug () << "\tviewUnderStartPoint=" + << (viewUnderStartPoint () ? viewUnderStartPoint ()->name () : "(none)") + << " viewUnderCursor=" + << (viewUnderCursor () ? viewUnderCursor ()->name () : "(none)") + << endl; + kdDebug () << "\tbegan draw=" << m_beganDraw << endl; +#endif + + m_currentPoint = currentPoint_; + m_currentViewPoint = currentViewPoint_; + + if (m_beganDraw) + { + if (m_currentPoint != KP_INVALID_POINT) + { + draw (m_currentPoint, m_lastPoint, QRect (m_startPoint, m_currentPoint).normalize ()); + m_lastPoint = m_currentPoint; + } + } + else + { + hover (m_currentPoint); + } +} + + +void kpTool::beginInternal () +{ +#if DEBUG_KP_TOOL + kdDebug () << "kpTool::beginInternal()" << endl; +#endif + + if (!m_began) + { + // clear leftover statusbar messages + setUserMessage (); + m_currentPoint = currentPoint (); + m_currentViewPoint = currentPoint (false/*view point*/); + setUserShapePoints (m_currentPoint); + + // TODO: Audit all the code in this file - states like "m_began" & + // "m_beganDraw" should be set before calling user func. + // Also, m_currentPoint should be more frequently initialised. + + // call user virtual func + begin (); + + // we've starting using the tool... + m_began = true; + + // but we haven't started drawing with it + m_beganDraw = false; + + + uint keyState = KApplication::keyboardModifiers (); + + m_shiftPressed = (keyState & KApplication::ShiftModifier); + m_controlPressed = (keyState & KApplication::ControlModifier); + + // TODO: Can't do much about ALT - unless it's always KApplication::Modifier1? + // Ditto for everywhere else where I set SHIFT & CTRL but not alt. + m_altPressed = false; + } +} + +void kpTool::endInternal () +{ + if (m_began) + { + // before we can stop using the tool, we must stop the current drawing operation (if any) + if (hasBegunShape ()) + endShapeInternal (m_currentPoint, QRect (m_startPoint, m_currentPoint).normalize ()); + + // call user virtual func + end (); + + // clear leftover statusbar messages + setUserMessage (); + setUserShapePoints (currentPoint ()); + + // we've stopped using the tool... + m_began = false; + + // and so we can't be drawing with it + m_beganDraw = false; + + if (m_mainWindow) + { + kpToolToolBar *tb = m_mainWindow->toolToolBar (); + if (tb) + { + tb->hideAllToolWidgets (); + } + } + + } +} + +// virtual +void kpTool::begin () +{ +#if DEBUG_KP_TOOL + kdDebug () << "kpTool::begin() base implementation" << endl; +#endif +} + +// virtual +void kpTool::end () +{ +#if DEBUG_KP_TOOL + kdDebug () << "kpTool::end() base implementation" << endl; +#endif +} + +void kpTool::beginDrawInternal () +{ + if (!m_beganDraw) + { + beginDraw (); + + m_beganDraw = true; + emit beganDraw (m_currentPoint); + } +} + +// virtual +void kpTool::beginDraw () +{ +} + +// virtual +void kpTool::hover (const QPoint &point) +{ +#if DEBUG_KP_TOOL + kdDebug () << "kpTool::hover" << point + << " base implementation" + << endl; +#endif + + setUserShapePoints (point); +} + +// virtual +void kpTool::globalDraw () +{ +} + +// virtual +void kpTool::reselect () +{ +#if DEBUG_KP_TOOL + kdDebug () << "kpTool::reselect() base implementation" << endl; +#endif +} + + +// public +QIconSet kpTool::iconSet (int forceSize) const +{ +#if DEBUG_KP_TOOL && 0 + kdDebug () << "kpTool(" << name () << ")::iconSet(forceSize=" << forceSize << ")" << endl; +#endif + // (robust in case BarIcon() default arg changes) + if (forceSize > 0) + return BarIconSet (name (), forceSize); + else + return BarIconSet (name ()); +} + +// public +QString kpTool::iconName () const +{ + return name (); +} + +// public +kpToolAction *kpTool::action () +{ + if (!m_action) + createAction (); + + return m_action; +} + + +// protected slots +void kpTool::slotActionActivated () +{ + emit actionActivated (); +} + + +// virtual +void kpTool::draw (const QPoint &, const QPoint &, const QRect &) +{ +} + +// also called by kpView +void kpTool::cancelShapeInternal () +{ + if (hasBegunShape ()) + { + m_beganDraw = false; + cancelShape (); + m_viewUnderStartPoint = 0; + + emit cancelledShape (viewUnderCursor () ? m_currentPoint : KP_INVALID_POINT); + + if (viewUnderCursor ()) + hover (m_currentPoint); + else + { + m_currentPoint = KP_INVALID_POINT; + m_currentViewPoint = KP_INVALID_POINT; + hover (m_currentPoint); + } + + if (returnToPreviousToolAfterEndDraw ()) + { + kpToolToolBar *tb = mainWindow ()->toolToolBar (); + + // (don't end up with no tool selected) + if (tb->previousTool ()) + { + // endInternal() will be called by kpMainWindow (thanks to this line) + // so we won't have the view anymore + tb->selectPreviousTool (); + } + } + } +} + +// virtual +void kpTool::cancelShape () +{ + kdWarning () << "Tool cannot cancel operation!" << endl; +} + +void kpTool::releasedAllButtons () +{ +} + +void kpTool::endDrawInternal (const QPoint &thisPoint, const QRect &normalizedRect, + bool wantEndShape) +{ +#if DEBUG_KP_TOOL && 1 + kdDebug () << "kpTool::endDrawInternal() wantEndShape=" << wantEndShape << endl; +#endif + + if (wantEndShape && !hasBegunShape ()) + return; + else if (!wantEndShape && !hasBegunDraw ()) + return; + + m_beganDraw = false; + + if (wantEndShape) + { + #if DEBUG_KP_TOOL && 0 + kdDebug () << "\tcalling endShape()" << endl; + #endif + endShape (thisPoint, normalizedRect); + } + else + { + #if DEBUG_KP_TOOL && 0 + kdDebug () << "\tcalling endDraw()" << endl; + #endif + endDraw (thisPoint, normalizedRect); + } + m_viewUnderStartPoint = 0; + + emit endedDraw (m_currentPoint); + if (viewUnderCursor ()) + hover (m_currentPoint); + else + { + m_currentPoint = KP_INVALID_POINT; + m_currentViewPoint = KP_INVALID_POINT; + hover (m_currentPoint); + } + + if (returnToPreviousToolAfterEndDraw ()) + { + kpToolToolBar *tb = mainWindow ()->toolToolBar (); + + // (don't end up with no tool selected) + if (tb->previousTool ()) + { + // endInternal() will be called by kpMainWindow (thanks to this line) + // so we won't have the view anymore + tb->selectPreviousTool (); + } + } +} + +// private +void kpTool::endShapeInternal (const QPoint &thisPoint, const QRect &normalizedRect) +{ + endDrawInternal (thisPoint, normalizedRect, true/*end shape*/); +} + +// virtual +void kpTool::endDraw (const QPoint &, const QRect &) +{ +} + +kpMainWindow *kpTool::mainWindow () const +{ + return m_mainWindow; +} + +kpDocument *kpTool::document () const +{ + return m_mainWindow ? m_mainWindow->document () : 0; +} + +kpView *kpTool::viewUnderCursor () const +{ + kpViewManager *vm = viewManager (); + return vm ? vm->viewUnderCursor () : 0; +} + +kpViewManager *kpTool::viewManager () const +{ + return m_mainWindow ? m_mainWindow->viewManager () : 0; +} + +kpToolToolBar *kpTool::toolToolBar () const +{ + return m_mainWindow ? m_mainWindow->toolToolBar () : 0; +} + +kpColor kpTool::color (int which) const +{ + if (m_mainWindow) + return m_mainWindow->colorToolBar ()->color (which); + else + { + kdError () << "kpTool::color () without mainWindow" << endl; + return kpColor::invalid; + } +} + +kpColor kpTool::foregroundColor () const +{ + return color (0); +} + +kpColor kpTool::backgroundColor () const +{ + return color (1); +} + + +double kpTool::colorSimilarity () const +{ + if (m_mainWindow) + return m_mainWindow->colorToolBar ()->colorSimilarity (); + else + { + kdError () << "kpTool::colorSimilarity() without mainWindow" << endl; + return 0; + } +} + +int kpTool::processedColorSimilarity () const +{ + if (m_mainWindow) + return m_mainWindow->colorToolBar ()->processedColorSimilarity (); + else + { + kdError () << "kpTool::processedColorSimilarity() without mainWindow" << endl; + return kpColor::Exact; + } +} + + +kpColor kpTool::oldForegroundColor () const +{ + if (m_mainWindow) + return m_mainWindow->colorToolBar ()->oldForegroundColor (); + else + { + kdError () << "kpTool::oldForegroundColor() without mainWindow" << endl; + return kpColor::invalid; + } +} + +kpColor kpTool::oldBackgroundColor () const +{ + if (m_mainWindow) + return m_mainWindow->colorToolBar ()->oldBackgroundColor (); + else + { + kdError () << "kpTool::oldBackgroundColor() without mainWindow" << endl; + return kpColor::invalid; + } +} + +double kpTool::oldColorSimilarity () const +{ + if (m_mainWindow) + return m_mainWindow->colorToolBar ()->oldColorSimilarity (); + else + { + kdError () << "kpTool::oldColorSimilarity() without mainWindow" << endl; + return 0; + } +} + + +void kpTool::slotColorsSwappedInternal (const kpColor &newForegroundColor, + const kpColor &newBackgroundColor) +{ + if (careAboutColorsSwapped ()) + { + slotColorsSwapped (newForegroundColor, newBackgroundColor); + m_ignoreColorSignals = 2; + } + else + m_ignoreColorSignals = 0; +} + +void kpTool::slotForegroundColorChangedInternal (const kpColor &color) +{ + if (m_ignoreColorSignals > 0) + { + #if DEBUG_KP_TOOL && 1 + kdDebug () << "kpTool::slotForegroundColorChangedInternal() ignoreColorSignals=" << m_ignoreColorSignals << endl; + #endif + m_ignoreColorSignals--; + return; + } + + slotForegroundColorChanged (color); +} + +void kpTool::slotBackgroundColorChangedInternal (const kpColor &color) +{ + if (m_ignoreColorSignals > 0) + { + #if DEBUG_KP_TOOL && 1 + kdDebug () << "kpTool::slotBackgroundColorChangedInternal() ignoreColorSignals=" << m_ignoreColorSignals << endl; + #endif + m_ignoreColorSignals--; + return; + } + + slotBackgroundColorChanged (color); +} + +void kpTool::slotColorSimilarityChangedInternal (double similarity, int processedSimilarity) +{ + slotColorSimilarityChanged (similarity, processedSimilarity); +} + +bool kpTool::currentPointNextToLast () const +{ + if (m_lastPoint == QPoint (-1, -1)) + return true; + + int dx = kAbs (m_currentPoint.x () - m_lastPoint.x ()); + int dy = kAbs (m_currentPoint.y () - m_lastPoint.y ()); + + return (dx <= 1 && dy <= 1); +} + +bool kpTool::currentPointCardinallyNextToLast () const +{ + if (m_lastPoint == QPoint (-1, -1)) + return true; + + int dx = kAbs (m_currentPoint.x () - m_lastPoint.x ()); + int dy = kAbs (m_currentPoint.y () - m_lastPoint.y ()); + + return (dx + dy <= 1); +} + +kpCommandHistory *kpTool::commandHistory () const +{ + return m_mainWindow ? m_mainWindow->commandHistory () : 0; +} + +void kpTool::mousePressEvent (QMouseEvent *e) +{ +#if DEBUG_KP_TOOL && 1 + kdDebug () << "kpTool::mousePressEvent pos=" << e->pos () + << " btnStateBefore=" << (int) e->state () + << " btnStateAfter=" << (int) e->stateAfter () + << " button=" << (int) e->button () + << " beganDraw=" << m_beganDraw << endl; +#endif + + // state of all the buttons - not just the one that triggered the event (button()) + Qt::ButtonState buttonState = e->stateAfter (); + + if (m_mainWindow && e->button () == Qt::MidButton) + { + const QString text = QApplication::clipboard ()->text (QClipboard::Selection); + #if DEBUG_KP_TOOL && 1 + kdDebug () << "\tMMB pasteText='" << text << "'" << endl; + #endif + if (!text.isEmpty ()) + { + if (hasBegunShape ()) + { + #if DEBUG_KP_TOOL && 1 + kdDebug () << "\t\thasBegunShape - end" << endl; + #endif + endShapeInternal (m_currentPoint, + QRect (m_startPoint, m_currentPoint).normalize ()); + } + + if (viewUnderCursor ()) + { + m_mainWindow->pasteTextAt (text, + viewUnderCursor ()->transformViewToDoc (e->pos ()), + true/*adjust topLeft so that cursor isn't + on top of resize handle*/); + } + + return; + } + } + + int mb = mouseButton (buttonState); +#if DEBUG_KP_TOOL && 1 + kdDebug () << "\tmb=" << mb << " m_beganDraw=" << m_beganDraw << endl; +#endif + + if (mb == -1 && !m_beganDraw) return; // ignore + + if (m_beganDraw) + { + if (mb == -1 || mb != m_mouseButton) + { + #if DEBUG_KP_TOOL && 1 + kdDebug () << "\tCancelling operation as " << mb << " == -1 or != " << m_mouseButton << endl; + #endif + + kpView *view = viewUnderStartPoint (); + if (!view) + { + kdError () << "kpTool::mousePressEvent() cancel without a view under the start point!" << endl; + } + + // if we get a mousePressEvent when we're drawing, then the other + // mouse button must have been pressed + m_currentPoint = view ? view->transformViewToDoc (e->pos ()) : QPoint (-1, -1); + m_currentViewPoint = view ? e->pos () : QPoint (-1, -1); + cancelShapeInternal (); + } + + return; + } + + kpView *view = viewUnderCursor (); + if (!view) + { + kdError () << "kpTool::mousePressEvent() without a view under the cursor!" << endl; + } + +#if DEBUG_KP_TOOL && 1 + if (view) + kdDebug () << "\tview=" << view->name () << endl; +#endif + + + // let user know what mouse button is being used for entire draw + m_mouseButton = mouseButton (buttonState); + m_shiftPressed = (buttonState & Qt::ShiftButton); + m_controlPressed = (buttonState & Qt::ControlButton); + m_altPressed = (buttonState & Qt::AltButton); + m_startPoint = m_currentPoint = view ? view->transformViewToDoc (e->pos ()) : QPoint (-1, -1); + m_currentViewPoint = view ? e->pos () : QPoint (-1, -1); + m_viewUnderStartPoint = view; + m_lastPoint = QPoint (-1, -1); + +#if DEBUG_KP_TOOL && 1 + kdDebug () << "\tBeginning draw @ " << m_currentPoint << endl; +#endif + + beginDrawInternal (); + + draw (m_currentPoint, m_lastPoint, QRect (m_currentPoint, m_currentPoint)); + m_lastPoint = m_currentPoint; +} + +void kpTool::mouseMoveEvent (QMouseEvent *e) +{ +#if DEBUG_KP_TOOL && 0 + kdDebug () << "kpTool::mouseMoveEvent pos=" << e->pos () + << " btnStateAfter=" << (int) e->stateAfter () << endl; + kpView *v0 = viewUnderCursor (), + *v1 = viewManager ()->viewUnderCursor (true/*use Qt*/), + *v2 = viewUnderStartPoint (); + kdDebug () << "\tviewUnderCursor=" << (v0 ? v0->name () : "(none)") + << " viewUnderCursorQt=" << (v1 ? v1->name () : "(none)") + << " viewUnderStartPoint=" << (v2 ? v2->name () : "(none)") + << endl; + kdDebug () << "\tfocusWidget=" << kapp->focusWidget () << endl; +#endif + + Qt::ButtonState buttonState = e->stateAfter (); + m_shiftPressed = (buttonState & Qt::ShiftButton); + m_controlPressed = (buttonState & Qt::ControlButton); + m_altPressed = (buttonState & Qt::AltButton); + + if (m_beganDraw) + { + kpView *view = viewUnderStartPoint (); + if (!view) + { + kdError () << "kpTool::mouseMoveEvent() without a view under the start point!" << endl; + return; + } + + m_currentPoint = view->transformViewToDoc (e->pos ()); + m_currentViewPoint = e->pos (); + + #if DEBUG_KP_TOOL && 0 + kdDebug () << "\tDraw!" << endl; + #endif + + bool dragScrolled = false; + movedAndAboutToDraw (m_currentPoint, m_lastPoint, view->zoomLevelX (), &dragScrolled); + + if (dragScrolled) + { + m_currentPoint = currentPoint (); + m_currentViewPoint = currentPoint (false/*view point*/); + + // Scrollview has scrolled contents and has scheduled an update + // for the newly exposed region. If draw() schedules an update + // as well (instead of immediately updating), the scrollview's + // update will be executed first and it'll only update part of + // the screen resulting in ugly tearing of the viewManager's + // tempPixmap. + viewManager ()->setFastUpdates (); + } + + draw (m_currentPoint, m_lastPoint, QRect (m_startPoint, m_currentPoint).normalize ()); + + if (dragScrolled) + viewManager ()->restoreFastUpdates (); + + m_lastPoint = m_currentPoint; + } + else + { + kpView *view = viewUnderCursor (); + if (!view) // possible if cancelShape()'ed but still holding down initial mousebtn + { + m_currentPoint = KP_INVALID_POINT; + m_currentViewPoint = KP_INVALID_POINT; + return; + } + + m_currentPoint = view->transformViewToDoc (e->pos ()); + m_currentViewPoint = e->pos (); + hover (m_currentPoint); + } +} + +void kpTool::mouseReleaseEvent (QMouseEvent *e) +{ +#if DEBUG_KP_TOOL && 1 + kdDebug () << "kpTool::mouseReleaseEvent pos=" << e->pos () + << " btnStateBefore=" << (int) e->state () + << " btnStateAfter=" << (int) e->stateAfter () + << " button=" << (int) e->button () << endl; +#endif + + if (m_beganDraw) // didn't cancelShape() + { + kpView *view = viewUnderStartPoint (); + if (!view) + { + kdError () << "kpTool::mouseReleaseEvent() without a view under the start point!" << endl; + return; + } + + m_currentPoint = view ? view->transformViewToDoc (e->pos ()) : QPoint (-1, -1); + m_currentViewPoint = view ? e->pos () : QPoint (-1, -1); + endDrawInternal (m_currentPoint, QRect (m_startPoint, m_currentPoint).normalize ()); + } + + if ((e->stateAfter () & Qt::MouseButtonMask) == 0) + { + releasedAllButtons (); + } +} + +void kpTool::wheelEvent (QWheelEvent *e) +{ +#if DEBUG_KP_TOOL + kdDebug () << "kpTool::wheelEvent() state=" << e->state () + << " hasBegunDraw=" << hasBegunDraw () + << " delta=" << e->delta () + << endl; +#endif + + e->ignore (); + + // If CTRL not pressed, bye. + if ((e->state () & Qt::ControlButton) == 0) + return; + + // If drawing, bye; don't care if a shape in progress though. + if (hasBegunDraw ()) + return; + + + // Zoom in/out depending on wheel direction. + + // Moved wheel away from user? + if (e->delta () > 0) + { + m_mainWindow->zoomIn (true/*center under cursor*/); + e->accept (); + } + // Moved wheel towards user? + else if (e->delta () < 0) + { + #if 1 + m_mainWindow->zoomOut (true/*center under cursor - make zoom in/out + stay under same doc pos*/); + #else + m_mainWindow->zoomOut (false/*don't center under cursor - as is + confusing behaviour when zooming + out*/); + #endif + e->accept (); + } +} + + +void kpTool::keyPressEvent (QKeyEvent *e) +{ +#if DEBUG_KP_TOOL && 0 + kdDebug () << "kpTool::keyPressEvent() e->key=" << e->key () << endl; +#endif + + int dx = 0, dy = 0; + + e->ignore (); + + switch (e->key ()) + { + case 0: + case Qt::Key_unknown: + #if DEBUG_KP_TOOL && 0 + kdDebug () << "kpTool::keyPressEvent() picked up unknown key!" << endl; + #endif + // --- fall thru and update all modifiers --- + case Qt::Key_Alt: + case Qt::Key_Shift: + case Qt::Key_Control: + keyUpdateModifierState (e); + + e->accept (); + break; + + case Qt::Key_Delete: + m_mainWindow->slotDelete (); + break; + + /* + * QCursor::setPos conveniently causes mouseMoveEvents :) + */ + + case Qt::Key_Home: dx = -1, dy = -1; break; + case Qt::Key_Up: dy = -1; break; + case Qt::Key_PageUp: dx = +1, dy = -1; break; + + case Qt::Key_Left: dx = -1; break; + case Qt::Key_Right: dx = +1; break; + + case Qt::Key_End: dx = -1, dy = +1; break; + case Qt::Key_Down: dy = +1; break; + case Qt::Key_PageDown: dx = +1, dy = +1; break; + + case Qt::Key_Enter: + case Qt::Key_Return: + case Qt::Key_Insert: + case Qt::Key_Clear/*Numpad 5 Key*/: + { + kpView *view = viewUnderCursor (); // TODO: wrong for dragging lines outside of view (for e.g.) + if (view) + { + // TODO: what about the modifiers + QMouseEvent me (QEvent::MouseButtonPress, + view->mapFromGlobal (QCursor::pos ()), + Qt::LeftButton, + 0); + mousePressEvent (&me); + e->accept (); + } + + break; + }} + + kpView *view = viewUnderCursor (); + if (view && (dx || dy)) + { + QPoint oldPoint = view->mapFromGlobal (QCursor::pos ()); + #if DEBUG_KP_TOOL && 0 + kdDebug () << "\toldPoint=" << oldPoint + << " dx=" << dx << " dy=" << dy << endl; + #endif + + + const int viewIncX = (dx ? QMAX (1, view->zoomLevelX () / 100) * dx : 0); + const int viewIncY = (dy ? QMAX (1, view->zoomLevelY () / 100) * dy : 0); + + int newViewX = oldPoint.x () + viewIncX; + int newViewY = oldPoint.y () + viewIncY; + + + #if DEBUG_KP_TOOL && 0 + kdDebug () << "\tnewPoint=" << QPoint (newViewX, newViewY) << endl; + #endif + + if (view->transformViewToDoc (QPoint (newViewX, newViewY)) == + view->transformViewToDoc (oldPoint)) + { + newViewX += viewIncX, newViewY += viewIncY; + + #if DEBUG_KP_TOOL && 0 + kdDebug () << "\tneed adjust for doc - newPoint=" + << QPoint (newViewX, newViewY) << endl; + #endif + } + + + // TODO: visible width/height (e.g. with scrollbars) + int x = QMIN (QMAX (newViewX, 0), view->width () - 1); + int y = QMIN (QMAX (newViewY, 0), view->height () - 1); + + QCursor::setPos (view->mapToGlobal (QPoint (x, y))); + e->accept (); + } +} + +void kpTool::keyReleaseEvent (QKeyEvent *e) +{ +#if DEBUG_KP_TOOL && 0 + kdDebug () << "kpTool::keyReleaseEvent() e->key=" << e->key () << endl; +#endif + + e->ignore (); + + switch (e->key ()) + { + case 0: + case Qt::Key_unknown: + #if DEBUG_KP_TOOL + kdDebug () << "kpTool::keyReleaseEvent() picked up unknown key!" << endl; + #endif + // HACK: around Qt bug: if you hold a modifier before you start the + // program and then release it over the view, + // Qt reports it as the release of an unknown key + // --- fall thru and update all modifiers --- + case Qt::Key_Alt: + case Qt::Key_Shift: + case Qt::Key_Control: + keyUpdateModifierState (e); + + e->accept (); + break; + + case Qt::Key_Escape: + if (hasBegunDraw ()) + { + cancelShapeInternal (); + e->accept (); + } + + break; + + case Qt::Key_Enter: + case Qt::Key_Return: + case Qt::Key_Insert: + case Qt::Key_Clear/*Numpad 5 Key*/: + { + kpView *view = viewUnderCursor (); + if (view) + { + QMouseEvent me (QEvent::MouseButtonRelease, + view->mapFromGlobal (QCursor::pos ()), + Qt::LeftButton, + Qt::LeftButton); + mouseReleaseEvent (&me); + e->accept (); + } + break; + }} +} + +// private +void kpTool::keyUpdateModifierState (QKeyEvent *e) +{ +#if DEBUG_KP_TOOL && 0 + kdDebug () << "kpTool::updateModifierState() e->key=" << e->key () << endl; + kdDebug () << "\tshift=" + << (e->stateAfter () & Qt::ShiftButton) + << " control=" + << (e->stateAfter () & Qt::ControlButton) + << " alt=" + << (e->stateAfter () & Qt::AltButton) + << endl; +#endif + if (e->key () & (Qt::Key_Alt | Qt::Key_Shift | Qt::Key_Control)) + { + #if DEBUG_KP_TOOL && 0 + kdDebug () << "\t\tmodifier changed - use e's claims" << endl; + #endif + setShiftPressed (e->stateAfter () & Qt::ShiftButton); + setControlPressed (e->stateAfter () & Qt::ControlButton); + setAltPressed (e->stateAfter () & Qt::AltButton); + } + else + { + #if DEBUG_KP_TOOL && 0 + kdDebug () << "\t\tmodifiers not changed - figure out the truth" << endl; + #endif + uint keyState = KApplication::keyboardModifiers (); + + setShiftPressed (keyState & KApplication::ShiftModifier); + setControlPressed (keyState & KApplication::ControlModifier); + + // TODO: Can't do much about ALT - unless it's always KApplication::Modifier1? + // Ditto for everywhere else where I set SHIFT & CTRL but not alt. + setAltPressed (e->stateAfter () & Qt::AltButton); + } +} + + +void kpTool::notifyModifierStateChanged () +{ + if (careAboutModifierState ()) + { + if (m_beganDraw) + draw (m_currentPoint, m_lastPoint, QRect (m_startPoint, m_currentPoint).normalize ()); + else + { + m_currentPoint = currentPoint (); + m_currentViewPoint = currentPoint (false/*view point*/); + hover (m_currentPoint); + } + } +} + +void kpTool::setShiftPressed (bool pressed) +{ + if (pressed == m_shiftPressed) + return; + + m_shiftPressed = pressed; + + notifyModifierStateChanged (); +} + +void kpTool::setControlPressed (bool pressed) +{ + if (pressed == m_controlPressed) + return; + + m_controlPressed = pressed; + + notifyModifierStateChanged (); +} + +void kpTool::setAltPressed (bool pressed) +{ + if (pressed = m_altPressed) + return; + + m_altPressed = pressed; + + notifyModifierStateChanged (); +} + +void kpTool::focusInEvent (QFocusEvent *) +{ +} + +void kpTool::focusOutEvent (QFocusEvent *) +{ +#if DEBUG_KP_TOOL && 0 + kdDebug () << "kpTool::focusOutEvent() beganDraw=" << m_beganDraw << endl; +#endif + + if (m_beganDraw) + endDrawInternal (m_currentPoint, QRect (m_startPoint, m_currentPoint).normalize ()); +} + +void kpTool::enterEvent (QEvent *) +{ +#if DEBUG_KP_TOOL && 1 + kdDebug () << "kpTool::enterEvent() beganDraw=" << m_beganDraw << endl; +#endif +} + +void kpTool::leaveEvent (QEvent *) +{ +#if DEBUG_KP_TOOL && 1 + kdDebug () << "kpTool::leaveEvent() beganDraw=" << m_beganDraw << endl; +#endif + + // if we haven't started drawing (e.g. dragging a rectangle)... + if (!m_beganDraw) + { + m_currentPoint = KP_INVALID_POINT; + m_currentViewPoint = KP_INVALID_POINT; + hover (m_currentPoint); + } +} + +// static +int kpTool::mouseButton (const Qt::ButtonState &buttonState) +{ + // we have nothing to do with mid-buttons + if (buttonState & Qt::MidButton) + return -1; + + // both left & right together is quite meaningless... + Qt::ButtonState bothButtons = (Qt::ButtonState) (Qt::LeftButton | Qt::RightButton); + if ((buttonState & bothButtons) == bothButtons) + return -1; + + if (buttonState & Qt::LeftButton) + return 0; + else if (buttonState & Qt::RightButton) + return 1; + else + return -1; +} + + +/* + * User Notifications + */ + + +// public static +QString kpTool::cancelUserMessage (int mouseButton) +{ + if (mouseButton == 0) + return i18n ("Right click to cancel."); + else + return i18n ("Left click to cancel."); +} + +// public +QString kpTool::cancelUserMessage () const +{ + return cancelUserMessage (m_mouseButton); +} + + +// public +QString kpTool::userMessage () const +{ + return m_userMessage; +} + +// public +void kpTool::setUserMessage (const QString &userMessage) +{ + m_userMessage = userMessage; + + if (m_userMessage.isEmpty ()) + m_userMessage = text (); + else + m_userMessage.prepend (i18n ("%1: ").arg (text ())); + + emit userMessageChanged (m_userMessage); +} + + +// public +QPoint kpTool::userShapeStartPoint () const +{ + return m_userShapeStartPoint; +} + +// public +QPoint kpTool::userShapeEndPoint () const +{ + return m_userShapeEndPoint; +} + +// public static +int kpTool::calculateLength (int start, int end) +{ + if (start <= end) + { + return end - start + 1; + } + else + { + return end - start - 1; + } +} + +// public +void kpTool::setUserShapePoints (const QPoint &startPoint, + const QPoint &endPoint, + bool setSize) +{ + m_userShapeStartPoint = startPoint; + m_userShapeEndPoint = endPoint; + emit userShapePointsChanged (m_userShapeStartPoint, m_userShapeEndPoint); + + if (setSize) + { + if (startPoint != KP_INVALID_POINT && + endPoint != KP_INVALID_POINT) + { + setUserShapeSize (calculateLength (startPoint.x (), endPoint.x ()), + calculateLength (startPoint.y (), endPoint.y ())); + } + else + { + setUserShapeSize (); + } + } +} + + +// public +QSize kpTool::userShapeSize () const +{ + return m_userShapeSize; +} + +// public +int kpTool::userShapeWidth () const +{ + return m_userShapeSize.width (); +} + +// public +int kpTool::userShapeHeight () const +{ + return m_userShapeSize.height (); +} + +// public +void kpTool::setUserShapeSize (const QSize &size) +{ + m_userShapeSize = size; + + emit userShapeSizeChanged (m_userShapeSize); + emit userShapeSizeChanged (m_userShapeSize.width (), + m_userShapeSize.height ()); +} + +// public +void kpTool::setUserShapeSize (int width, int height) +{ + setUserShapeSize (QSize (width, height)); +} + + +// public static +bool kpTool::warnIfBigImageSize (int oldWidth, int oldHeight, + int newWidth, int newHeight, + const QString &text, + const QString &caption, + const QString &continueButtonText, + QWidget *parent) +{ +#if DEBUG_KP_TOOL + kdDebug () << "kpTool::warnIfBigImageSize()" + << " old: w=" << oldWidth << " h=" << oldWidth + << " new: w=" << newWidth << " h=" << newHeight + << " pixmapSize=" + << kpPixmapFX::pixmapSize (newWidth, + newHeight, + QPixmap::defaultDepth ()) + << " vs BigImageSize=" << KP_BIG_IMAGE_SIZE + << endl; +#endif + + // Only got smaller or unchanged - don't complain + if (!(newWidth > oldWidth || newHeight > oldHeight)) + { + return true; + } + + // Was already large - user was warned before, don't annoy him/her again + if (kpPixmapFX::pixmapSize (oldWidth, oldHeight, QPixmap::defaultDepth ()) >= + KP_BIG_IMAGE_SIZE) + { + return true; + } + + if (kpPixmapFX::pixmapSize (newWidth, newHeight, QPixmap::defaultDepth ()) >= + KP_BIG_IMAGE_SIZE) + { + int accept = KMessageBox::warningContinueCancel (parent, + text, + caption, + continueButtonText, + QString::fromLatin1 ("BigImageDontAskAgain")); + + return (accept == KMessageBox::Continue); + } + else + { + return true; + } +} + + +#include <kptool.moc> diff --git a/kolourpaint/kptool.h b/kolourpaint/kptool.h new file mode 100644 index 00000000..ba7ee75e --- /dev/null +++ b/kolourpaint/kptool.h @@ -0,0 +1,422 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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_h__ +#define __kp_tool_h__ + +#include <qobject.h> +#include <qpoint.h> +#include <qrect.h> +#include <qsize.h> +#include <qstring.h> + +#include <kpdefs.h> + + +class QIconSet; +class QPixmap; + +class KKeySequence; +class KShortcut; + +class kpColor; +class kpColorToolBar; +class kpCommandHistory; +class kpDocument; +class kpView; +class kpViewManager; +class kpMainWindow; +class kpToolAction; +class kpToolToolBar; + + +// Base class for all tools +class kpTool : public QObject +{ +Q_OBJECT + +public: + kpTool (const QString &text, const QString &description, + int key, + kpMainWindow *mainWindow, const char *name); + virtual ~kpTool (); + +private: + void init (const QString &text, const QString &description, + int key, + kpMainWindow *mainWindow, const char *name); + + +protected: + void createAction (); + + int m_key; + kpToolAction *m_action; + +signals: + void actionToolTipChanged (const QString &string); + +protected slots: + void slotActionToolTipChanged (const QString &string); + +public: + QString text () const; + void setText (const QString &text); + + static QString toolTipForTextAndShortcut (const QString &text, + const KShortcut &shortcut); + QString toolTip () const; + + QString description () const; + void setDescription (const QString &description); + + int key () const; + void setKey (int key); + + // Given a single <key>, returns a shortcut with <key> + // (disabled when the user is editing text) and as an alternate, + // <some modifiers>+<key>. + static KShortcut shortcutForKey (int key); + KShortcut shortcut () const; + + static bool keyIsText (int key); + static bool containsSingleKeyTrigger (const KKeySequence &seq); + static bool containsSingleKeyTrigger (const KShortcut &shortcut, + KShortcut *shortcutWithoutSingleKeyTriggers); + + bool singleKeyTriggersEnabled () const; + void enableSingleKeyTriggers (bool enable = true); + + const char *name () const; + + + static QRect neededRect (const QRect &rect, int lineWidth); + static QPixmap neededPixmap (const QPixmap &pixmap, const QRect &boundingRect); + + bool hasCurrentPoint () const; + // Returns the position of the cursor relative to the topleft point of + // the current view (viewUnderStartPoint() or viewUnderCursor() otherwise). + // + // If neither viewUnderStartPoint() nor viewUnderCursor() + // (i.e. !hasCurrentPoint()), then it returns KP_INVALID_POINT. + // + // If <zoomToDoc> is set (default), then it returns the position in the + // document. This theoretically == m_currentPoint (when m_currentPoint + // is defined) but I wouldn't bet on it. This function is useful when + // m_currentPoint isn't necessarily defined (outside of beginDraw(),draw() + // and hover()). + // + // If <zoomToDoc> is not set, then it returns an unzoomed view coordinate. + // + // Keep in mind that if viewUnderStartPoint(), this can return coordinates + // outside of the document/view. + QPoint currentPoint (bool zoomToDoc = true) const; + +public slots: + // Call this when something below the mouse cursor may have changed + // and/or if the view has moved relative to the cursor (as opposed to + // the cursor moving relative to the view, which would trigger a + // mouseMoveEvent and all would be well without such hacks) + // e.g. when zooming or scrolling the view or when deleting a selection. + // + // This calls hover() or draw() to let the tool know. The Brush Tool + // can then update the position of the Brush Cursor. The Selection + // Tool can update the real cursor. The Line Tool can update the current + // line. The statubar gets correct coordinates. etc. etc. + void somethingBelowTheCursorChanged (); + +private: + // Same as above except that you claim you know better than currentPoint() + void somethingBelowTheCursorChanged (const QPoint ¤tPoint_, + const QPoint ¤tViewPoint_); + +public: + // called when the tool is selected + virtual void begin (); + + // called when the tool is deselected + virtual void end (); + + // set after begin() has been called, unset after end() has been called + bool hasBegun () const { return m_began; } + + bool hasBegunDraw () const { return m_beganDraw; } + + virtual bool hasBegunShape () const { return hasBegunDraw (); } + + // called when user double-left-clicks on Tool Icon (not view) + virtual void globalDraw (); + + // called when the user clicks on the Tool Icon even though it's already + // the current tool (used by the selection tools to deselect) + virtual void reselect (); + +signals: + // emitted after beginDraw() has been called + void beganDraw (const QPoint &point); + + // Emitted just before draw() is called in mouseMoveEvent(). Slots + // connected to this signal should return in <scrolled> whether the + // mouse pos may have changed. Used by drag scrolling. + void movedAndAboutToDraw (const QPoint ¤tPoint, const QPoint &lastPoint, + int zoomLevel, + bool *scrolled); + + // emitted after endDraw() has been called + void endedDraw (const QPoint &point); + + // emitted after cancelShape() has been called + void cancelledShape (const QPoint &point); + + +public: + QIconSet iconSet (int forceSize = 0) const; + QString iconName () const; + kpToolAction *action (); + +signals: + // User clicked on the tool's action - i.e. select this tool + void actionActivated (); + +protected slots: + void slotActionActivated (); + + +protected: + virtual bool returnToPreviousToolAfterEndDraw () const { return false; } + virtual bool careAboutModifierState () const { return false; } + virtual bool careAboutColorsSwapped () const { return false; } + + virtual void beginDraw (); + + // mouse move without button pressed + // (only m_currentPoint & m_currentViewPoint is defined) + virtual void hover (const QPoint &point); + + // this is useful for "instant" tools like the Pen & Eraser + virtual void draw (const QPoint &thisPoint, const QPoint &lastPoint, + const QRect &normalizedRect); + + // (m_mouseButton will not change from beginDraw()) + virtual void cancelShape (); + virtual void releasedAllButtons (); + + virtual void endDraw (const QPoint &thisPoint, const QRect &normalizedRect); + + virtual void endShape (const QPoint &thisPoint = QPoint (), + const QRect &normalizedRect = QRect ()) + { + endDraw (thisPoint, normalizedRect); + } + + kpMainWindow *mainWindow () const; + kpDocument *document () const; + kpViewManager *viewManager () const; + kpToolToolBar *toolToolBar () const; + kpView *viewUnderStartPoint () const { return m_viewUnderStartPoint; } + kpView *viewUnderCursor () const; + kpCommandHistory *commandHistory () const; + + kpColor color (int which) const; + + kpColor foregroundColor () const; + kpColor backgroundColor () const; + + double colorSimilarity () const; + int processedColorSimilarity () const; + +protected: + int m_ignoreColorSignals; + +protected slots: + void slotColorsSwappedInternal (const kpColor &newForegroundColor, + const kpColor &newBackgroundColor); + void slotForegroundColorChangedInternal (const kpColor &color); + void slotBackgroundColorChangedInternal (const kpColor &color); + void slotColorSimilarityChangedInternal (double similarity, int processedSimilarity); + +protected slots: // TODO: there is no reason why these should be slots + virtual void slotColorsSwapped (const kpColor & /*newForegroundColor*/, const kpColor & /*newBackgroundColor*/) {} + virtual void slotForegroundColorChanged (const kpColor & /*color*/) {} + virtual void slotBackgroundColorChanged (const kpColor & /*color*/) {} + virtual void slotColorSimilarityChanged (double /*similarity*/, int /*processedSimilarity*/) {}; + +protected: + // (only valid in slots connected to the respective signals above) + kpColor oldForegroundColor () const; + kpColor oldBackgroundColor () const; + double oldColorSimilarity () const; + +protected: + // returns true if m_currentPoint <= 1 pixel away from m_lastPoint + // or if there was no lastPoint + bool currentPointNextToLast () const; // (includes diagonal adjacency) + bool currentPointCardinallyNextToLast () const; // (only cardinally adjacent i.e. horiz & vert; no diag) + + int m_mouseButton; /* 0 = left, 1 = right */ + bool m_shiftPressed, m_controlPressed, m_altPressed; // m_altPressed is unreliable + bool m_beganDraw; // set after beginDraw() is called, unset before endDraw() is called + QPoint m_startPoint, + m_currentPoint, m_currentViewPoint, + m_lastPoint; + +protected: + friend class kpCommandHistory; + friend class kpMainWindow; + friend class kpToolToolBar; + void beginInternal (); + void endInternal (); + + void beginDrawInternal (); + void endDrawInternal (const QPoint &thisPoint, const QRect &normalizedRect, + bool wantEndShape = false); + void cancelShapeInternal (); + void endShapeInternal (const QPoint &thisPoint = QPoint (), + const QRect &normalizedRect = QRect ()); + + friend class kpView; + + // If you're reimplementing any of these, you probably don't know what + // you're doing - reimplement begin(),beginDraw(),draw(),cancelShape(), + // endDraw() etc. instead. + virtual void mousePressEvent (QMouseEvent *e); + virtual void mouseMoveEvent (QMouseEvent *e); + virtual void mouseReleaseEvent (QMouseEvent *e); + virtual void wheelEvent (QWheelEvent *e); + + virtual void keyPressEvent (QKeyEvent *e); + virtual void keyReleaseEvent (QKeyEvent *e); + + virtual void imStartEvent(QIMEvent *){} + virtual void imComposeEvent(QIMEvent *){} + virtual void imEndEvent(QIMEvent *){} + +private: + void keyUpdateModifierState (QKeyEvent *e); + void notifyModifierStateChanged (); +protected: + virtual void setShiftPressed (bool pressed); + virtual void setControlPressed (bool pressed); + virtual void setAltPressed (bool pressed); + virtual void focusInEvent (QFocusEvent *e); + virtual void focusOutEvent (QFocusEvent *e); + virtual void enterEvent (QEvent *e); + virtual void leaveEvent (QEvent *e); + + // 0 = left, 1 = right, -1 = other (none, left+right, mid) + static int mouseButton (const Qt::ButtonState &buttonState); + + QString m_text, m_description; + const char *m_name; + + kpMainWindow *m_mainWindow; + bool m_began; + + kpView *m_viewUnderStartPoint; + + + /* + * User Notifications (Status Bar) + */ + +public: + // Returns "(Left|Right) click to cancel." where Left or Right is chosen + // depending on which one is the _opposite_ of <mouseButton> + static QString cancelUserMessage (int mouseButton); + QString cancelUserMessage () const; + + QString userMessage () const; + void setUserMessage (const QString &userMessage = QString::null); + + QPoint userShapeStartPoint () const; + QPoint userShapeEndPoint () const; + static int calculateLength (int start, int end); + void setUserShapePoints (const QPoint &startPoint = KP_INVALID_POINT, + const QPoint &endPoint = KP_INVALID_POINT, + bool setSize = true); + + QSize userShapeSize () const; + int userShapeWidth () const; + int userShapeHeight () const; + void setUserShapeSize (const QSize &size = KP_INVALID_SIZE); + void setUserShapeSize (int width, int height); + +signals: + void userMessageChanged (const QString &userMessage); + void userShapePointsChanged (const QPoint &startPoint = KP_INVALID_POINT, + const QPoint &endPoint = KP_INVALID_POINT); + void userShapeSizeChanged (const QSize &size); + void userShapeSizeChanged (int width, int height); + +protected: + QString m_userMessage; + QPoint m_userShapeStartPoint, m_userShapeEndPoint; + QSize m_userShapeSize; + + +public: + // Call this before the user tries to cause the document or selection + // to resize from <oldWidth>x<oldHeight> to <newWidth>x<newHeight>. + // If at least one dimension increases, the new dimensions will take a + // large amount of memory (which causes thrashing, instability) and + // the old dimensions did not take a large amount of memory, ask the + // user if s/he really wants to perform the operation. + // + // Returns true if the operation should proceed, false otherwise. + // + // In order to make the translators' lives possible, this function cannot + // generate the <text>,<caption> nor <continueButtonText> (without + // concantenating sentences and words with tense). However, it is + // recommended that you give them the following values: + // + // e.g.: + // text = i18n ("<qt><p>(Rotating|Skewing) the (image|selection) to" + // " %1x%2 may take a substantial amount of memory." + // " This can reduce system" + // " responsiveness and cause other application resource" + // " problems.</p>").arg (newWidth, newHeight) + // + // "<p>Are you sure want to (rotate|skew) the" + // " (image|selection)?</p></qt>"); + // caption = i18n ("Rotate (Image|Selection)?"); + // continueButtonText = i18n ("Rotat&e (Image|Selection)"); + static bool warnIfBigImageSize (int oldWidth, int oldHeight, + int newWidth, int newHeight, + const QString &text, + const QString &caption, + const QString &continueButtonText, + QWidget *parent); + + +protected: + // 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 kpToolPrivate *d; +}; + +#endif // __kp_tool_h__ diff --git a/kolourpaint/kpview.cpp b/kolourpaint/kpview.cpp new file mode 100644 index 00000000..1f18c659 --- /dev/null +++ b/kolourpaint/kpview.cpp @@ -0,0 +1,1910 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 0 +#define DEBUG_KP_VIEW_RENDERER ((DEBUG_KP_VIEW && 0) || 0) + + +#include <kpview.h> + +#include <math.h> +#include <stdlib.h> + +#include <qbitmap.h> +#include <qcursor.h> +#include <qdragobject.h> +#include <qguardedptr.h> +#include <qimage.h> +#include <qpainter.h> +#include <qpixmap.h> +#include <qpoint.h> +#include <qrect.h> +#include <qregion.h> +#include <qmemarray.h> + +#if DEBUG_KP_VIEW || DEBUG_KP_VIEW_RENDERER + #include <qdatetime.h> +#endif + +#include <kdebug.h> + +#include <kpdefs.h> +#include <kpdocument.h> +#include <kppixmapfx.h> +#include <kpselection.h> +#include <kptemppixmap.h> +#include <kptool.h> +#include <kptooltoolbar.h> +#include <kpviewmanager.h> +#include <kpviewscrollablecontainer.h> + + +struct kpViewPrivate +{ + // sync: kpView::paintEvent() + // + // Normally, these pointers must be valid while the kpView is alive. + // Generally, the objects they point to are deleted only after kpView + // is deleted. + // + // However, sometimes we use deleteLater() for the kpView. + // Before the delayed deletion is executed, those objects are deleted + // and then our paintEvent() is called. paintEvent() must therefore + // have some way of realising that those objects have been deleted so + // we use guardded pointers. + // + // For more details, see SVN commit: + // "r385274 | dang | 2005-02-02 22:08:27 +1100 (Wed, 02 Feb 2005) | 21 lines". + QGuardedPtr <kpDocument> m_document; + QGuardedPtr <kpToolToolBar> m_toolToolBar; + QGuardedPtr <kpViewManager> m_viewManager; + QGuardedPtr <kpView> m_buddyView; + QGuardedPtr <kpViewScrollableContainer> m_scrollableContainer; + + int m_hzoom, m_vzoom; + QPoint m_origin; + bool m_showGrid; + bool m_isBuddyViewScrollableContainerRectangleShown; + QRect m_buddyViewScrollableContainerRectangle; + + QRegion m_queuedUpdateArea; + QPixmap *m_backBuffer; +}; + + +kpView::kpView (kpDocument *document, + kpToolToolBar *toolToolBar, + kpViewManager *viewManager, + kpView *buddyView, + kpViewScrollableContainer *scrollableContainer, + QWidget *parent, const char *name) + + : QWidget (parent, name, Qt::WNoAutoErase/*no flicker*/), + d (new kpViewPrivate ()) +{ + d->m_document = document; + d->m_toolToolBar = toolToolBar; + d->m_viewManager = viewManager; + d->m_buddyView = buddyView; + d->m_scrollableContainer = scrollableContainer; + + d->m_hzoom = 100, d->m_vzoom = 100; + d->m_origin = QPoint (0, 0); + d->m_showGrid = false; + d->m_isBuddyViewScrollableContainerRectangleShown = false; + + d->m_backBuffer = 0; + + + setBackgroundMode (Qt::NoBackground); // no flicker + setFocusPolicy (QWidget::WheelFocus); + setMouseTracking (true); // mouseMoveEvent's even when no mousebtn down + setKeyCompression (true); + setInputMethodEnabled (true); // ensure using InputMethod +} + +kpView::~kpView () +{ + setHasMouse (false); + + delete d->m_backBuffer; + delete d; +} + + +// public +kpDocument *kpView::document () const +{ + return d->m_document; +} + +// protected +kpSelection *kpView::selection () const +{ + return document () ? document ()->selection () : 0; +} + +// public +kpToolToolBar *kpView::toolToolBar () const +{ + return d->m_toolToolBar; +} + +// protected +kpTool *kpView::tool () const +{ + return toolToolBar () ? toolToolBar ()->tool () : 0; +} + +// public +kpViewManager *kpView::viewManager () const +{ + return d->m_viewManager; +} + +// public +kpView *kpView::buddyView () const +{ + return d->m_buddyView; +} + +// public +kpViewScrollableContainer *kpView::buddyViewScrollableContainer () const +{ + return (buddyView () ? buddyView ()->scrollableContainer () : 0); +} + +// public +kpViewScrollableContainer *kpView::scrollableContainer () const +{ + return d->m_scrollableContainer; +} + + +// public +int kpView::zoomLevelX (void) const +{ + return d->m_hzoom; +} + +// public +int kpView::zoomLevelY (void) const +{ + return d->m_vzoom; +} + +// public virtual +void kpView::setZoomLevel (int hzoom, int vzoom) +{ + if (hzoom == d->m_hzoom && vzoom == d->m_vzoom) + return; + + if (hzoom <= 0 || vzoom <= 0) + return; + + d->m_hzoom = hzoom; + d->m_vzoom = vzoom; + + if (viewManager ()) + viewManager ()->updateView (this); + + emit zoomLevelChanged (hzoom, vzoom); +} + + +// public +QPoint kpView::origin () const +{ + return d->m_origin; +} + +// public virtual +void kpView::setOrigin (const QPoint &origin) +{ +#if DEBUG_KP_VIEW + kdDebug () << "kpView(" << name () << ")::setOrigin" << origin << endl; +#endif + + if (origin == d->m_origin) + { + #if DEBUG_KP_VIEW + kdDebug () << "\tNOP" << endl; + #endif + return; + } + + d->m_origin = origin; + + if (viewManager ()) + viewManager ()->updateView (this); + + emit originChanged (origin); +} + + +// public +bool kpView::canShowGrid () const +{ + // (minimum zoom level < 400% would probably be reported as a bug by + // users who thought that the grid was a part of the image!) + return ((zoomLevelX () >= 400 && zoomLevelX () % 100 == 0) && + (zoomLevelY () >= 400 && zoomLevelY () % 100 == 0)); +} + +// public +bool kpView::isGridShown () const +{ + return d->m_showGrid; +} + +// public +void kpView::showGrid (bool yes) +{ + if (d->m_showGrid == yes) + return; + + if (yes && !canShowGrid ()) + return; + + d->m_showGrid = yes; + + if (viewManager ()) + viewManager ()->updateView (this); +} + + +// public +bool kpView::isBuddyViewScrollableContainerRectangleShown () const +{ + return d->m_isBuddyViewScrollableContainerRectangleShown; +} + +// public +void kpView::showBuddyViewScrollableContainerRectangle (bool yes) +{ + if (yes == d->m_isBuddyViewScrollableContainerRectangleShown) + return; + + d->m_isBuddyViewScrollableContainerRectangleShown = yes; + + if (d->m_isBuddyViewScrollableContainerRectangleShown) + { + // Got these connect statements by analysing deps of + // updateBuddyViewScrollableContainerRectangle() rect update code. + + connect (this, SIGNAL (zoomLevelChanged (int, int)), + this, SLOT (updateBuddyViewScrollableContainerRectangle ())); + connect (this, SIGNAL (originChanged (const QPoint &)), + this, SLOT (updateBuddyViewScrollableContainerRectangle ())); + + if (buddyViewScrollableContainer ()) + { + connect (buddyViewScrollableContainer (), SIGNAL (contentsMovingSoon (int, int)), + this, SLOT (updateBuddyViewScrollableContainerRectangle ())); + connect (buddyViewScrollableContainer (), SIGNAL (resized ()), + this, SLOT (updateBuddyViewScrollableContainerRectangle ())); + } + + if (buddyView ()) + { + connect (buddyView (), SIGNAL (zoomLevelChanged (int, int)), + this, SLOT (updateBuddyViewScrollableContainerRectangle ())); + connect (buddyView (), SIGNAL (originChanged (const QPoint &)), + this, SLOT (updateBuddyViewScrollableContainerRectangle ())); + + connect (buddyView (), SIGNAL (sizeChanged (int, int)), + this, SLOT (updateBuddyViewScrollableContainerRectangle ())); + } + + } + else + { + disconnect (this, SIGNAL (zoomLevelChanged (int, int)), + this, SLOT (updateBuddyViewScrollableContainerRectangle ())); + disconnect (this, SIGNAL (originChanged (const QPoint &)), + this, SLOT (updateBuddyViewScrollableContainerRectangle ())); + + if (buddyViewScrollableContainer ()) + { + disconnect (buddyViewScrollableContainer (), SIGNAL (contentsMovingSoon (int, int)), + this, SLOT (updateBuddyViewScrollableContainerRectangle ())); + disconnect (buddyViewScrollableContainer (), SIGNAL (resized ()), + this, SLOT (updateBuddyViewScrollableContainerRectangle ())); + } + + if (buddyView ()) + { + disconnect (buddyView (), SIGNAL (zoomLevelChanged (int, int)), + this, SLOT (updateBuddyViewScrollableContainerRectangle ())); + disconnect (buddyView (), SIGNAL (originChanged (const QPoint &)), + this, SLOT (updateBuddyViewScrollableContainerRectangle ())); + + disconnect (buddyView (), SIGNAL (sizeChanged (int, int)), + this, SLOT (updateBuddyViewScrollableContainerRectangle ())); + } + + } + + updateBuddyViewScrollableContainerRectangle (); +} + + +// protected +QRect kpView::buddyViewScrollableContainerRectangle () const +{ + return d->m_buddyViewScrollableContainerRectangle; +} + +// protected slot +void kpView::updateBuddyViewScrollableContainerRectangle () +{ + if (viewManager ()) + viewManager ()->setQueueUpdates (); + + { + if (d->m_buddyViewScrollableContainerRectangle.isValid ()) + { + if (viewManager ()) + { + // Erase last + viewManager ()->updateViewRectangleEdges (this, + d->m_buddyViewScrollableContainerRectangle); + } + } + + + QRect newRect; + if (isBuddyViewScrollableContainerRectangleShown () && + buddyViewScrollableContainer () && buddyView ()) + { + QRect docRect = buddyView ()->transformViewToDoc ( + QRect (buddyViewScrollableContainer ()->contentsXSoon (), + buddyViewScrollableContainer ()->contentsYSoon (), + QMIN (buddyView ()->width (), + buddyViewScrollableContainer ()->visibleWidth ()), + QMIN (buddyView ()->height (), + buddyViewScrollableContainer ()->visibleHeight ()))); + + + QRect viewRect = this->transformDocToView (docRect); + + + // (Surround the area of interest by moving outwards by 1 pixel in each + // direction - don't overlap area) + newRect = QRect (viewRect.x () - 1, + viewRect.y () - 1, + viewRect.width () + 2, + viewRect.height () + 2); + } + else + { + newRect = QRect (); + } + + if (newRect != d->m_buddyViewScrollableContainerRectangle) + { + // (must set before updateView() for paintEvent() to see new + // rect) + d->m_buddyViewScrollableContainerRectangle = newRect; + + if (newRect.isValid ()) + { + if (viewManager ()) + { + viewManager ()->updateViewRectangleEdges (this, + d->m_buddyViewScrollableContainerRectangle); + } + } + } + } + + if (viewManager ()) + viewManager ()->restoreQueueUpdates (); +} + + +// public +double kpView::transformViewToDocX (double viewX) const +{ + return (viewX - origin ().x ()) * 100.0 / zoomLevelX (); +} + +// public +double kpView::transformViewToDocY (double viewY) const +{ + return (viewY - origin ().y ()) * 100.0 / zoomLevelY (); +} + +// public +QPoint kpView::transformViewToDoc (const QPoint &viewPoint) const +{ + return QPoint ((int) transformViewToDocX (viewPoint.x ()), + (int) transformViewToDocY (viewPoint.y ())); +} + +// public +QRect kpView::transformViewToDoc (const QRect &viewRect) const +{ + if (zoomLevelX () == 100 && zoomLevelY () == 100) + { + return QRect (viewRect.x () - origin ().x (), + viewRect.y () - origin ().y (), + viewRect.width (), + viewRect.height ()); + } + else + { + const QPoint docTopLeft = transformViewToDoc (viewRect.topLeft ()); + + // (don't call transformViewToDoc[XY]() - need to round up dimensions) + const int docWidth = qRound (double (viewRect.width ()) * 100.0 / double (zoomLevelX ())); + const int docHeight = qRound (double (viewRect.height ()) * 100.0 / double (zoomLevelY ())); + + // (like QWMatrix::Areas) + return QRect (docTopLeft.x (), docTopLeft.y (), docWidth, docHeight); + } +} + + +// public +double kpView::transformDocToViewX (double docX) const +{ + return (docX * zoomLevelX () / 100.0) + origin ().x (); +} + +// public +double kpView::transformDocToViewY (double docY) const +{ + return (docY * zoomLevelY () / 100.0) + origin ().y (); +} + +// public +QPoint kpView::transformDocToView (const QPoint &docPoint) const +{ + return QPoint ((int) transformDocToViewX (docPoint.x ()), + (int) transformDocToViewY (docPoint.y ())); +} + +// public +QRect kpView::transformDocToView (const QRect &docRect) const +{ + if (zoomLevelX () == 100 && zoomLevelY () == 100) + { + return QRect (docRect.x () + origin ().x (), + docRect.y () + origin ().y (), + docRect.width (), + docRect.height ()); + } + else + { + const QPoint viewTopLeft = transformDocToView (docRect.topLeft ()); + + // (don't call transformDocToView[XY]() - need to round up dimensions) + const int viewWidth = qRound (double (docRect.width ()) * double (zoomLevelX ()) / 100.0); + const int viewHeight = qRound (double (docRect.height ()) * double (zoomLevelY ()) / 100.0); + + // (like QWMatrix::Areas) + return QRect (viewTopLeft.x (), viewTopLeft.y (), viewWidth, viewHeight); + } +} + + +// public +QPoint kpView::transformViewToOtherView (const QPoint &viewPoint, + const kpView *otherView) +{ + if (this == otherView) + return viewPoint; + + const double docX = transformViewToDocX (viewPoint.x ()); + const double docY = transformViewToDocY (viewPoint.y ()); + + const double otherViewX = otherView->transformDocToViewX (docX); + const double otherViewY = otherView->transformDocToViewY (docY); + + return QPoint ((int) otherViewX, (int) otherViewY); +} + + +// public +int kpView::zoomedDocWidth () const +{ + return document () ? document ()->width () * zoomLevelX () / 100 : 0; +} + +// public +int kpView::zoomedDocHeight () const +{ + return document () ? document ()->height () * zoomLevelY () / 100 : 0; +} + + +// public +void kpView::setHasMouse (bool yes) +{ + kpViewManager *vm = viewManager (); + if (!vm) + return; + +#if DEBUG_KP_VIEW && 0 + kdDebug () << "kpView(" << name () + << ")::setHasMouse(" << yes + << ") existing viewUnderCursor=" + << (vm->viewUnderCursor () ? vm->viewUnderCursor ()->name () : "(none)") + << endl; +#endif + if (yes && vm->viewUnderCursor () != this) + vm->setViewUnderCursor (this); + else if (!yes && vm->viewUnderCursor () == this) + vm->setViewUnderCursor (0); +} + + +// public +void kpView::addToQueuedArea (const QRegion ®ion) +{ +#if DEBUG_KP_VIEW && 0 + kdDebug () << "kpView(" << name () + << ")::addToQueuedArea() already=" << d->m_queuedUpdateArea + << " - plus - " << region + << endl; +#endif + d->m_queuedUpdateArea += region; +} + +// public +void kpView::addToQueuedArea (const QRect &rect) +{ +#if DEBUG_KP_VIEW && 0 + kdDebug () << "kpView(" << name () + << ")::addToQueuedArea() already=" << d->m_queuedUpdateArea + << " - plus - " << rect + << endl; +#endif + d->m_queuedUpdateArea += rect; +} + +// public +void kpView::invalidateQueuedArea () +{ +#if DEBUG_KP_VIEW && 0 + kdDebug () << "kpView::invalidateQueuedArea()" << endl; +#endif + + d->m_queuedUpdateArea = QRegion (); +} + +// public +void kpView::updateQueuedArea () +{ + kpViewManager *vm = viewManager (); +#if DEBUG_KP_VIEW && 0 + kdDebug () << "kpView(" << name () + << ")::updateQueuedArea() vm=" << (bool) vm + << " queueUpdates=" << (vm && vm->queueUpdates ()) + << " fastUpdates=" << (vm && vm->fastUpdates ()) + << " area=" << d->m_queuedUpdateArea + << endl; +#endif + + if (!vm) + return; + + if (vm->queueUpdates ()) + return; + + if (!d->m_queuedUpdateArea.isNull ()) + vm->updateView (this, d->m_queuedUpdateArea); + + invalidateQueuedArea (); +} + +// public +void kpView::updateMicroFocusHint (const QRect µFocusHint) +{ + int x = microFocusHint.topLeft().x(); + int y = microFocusHint.topLeft().y(); + int width = microFocusHint.width(); + int height = microFocusHint.height(); + setMicroFocusHint (x, y, width, height); +} + +// public +QRect kpView::selectionViewRect () const +{ + return selection () ? + transformDocToView (selection ()->boundingRect ()) : + QRect (); + +} + +// public +QPoint kpView::mouseViewPoint (const QPoint &returnViewPoint) const +{ + if (returnViewPoint != KP_INVALID_POINT) + return returnViewPoint; + else + return mapFromGlobal (QCursor::pos ()); +} + +// public +QPoint kpView::mouseViewPointRelativeToSelection (const QPoint &viewPoint) const +{ + if (!selection ()) + return KP_INVALID_POINT; + + return mouseViewPoint (viewPoint) - transformDocToView (selection ()->topLeft ()); +} + +// public +bool kpView::mouseOnSelection (const QPoint &viewPoint) const +{ + const QRect selViewRect = selectionViewRect (); + if (!selViewRect.isValid ()) + return false; + + return selViewRect.contains (mouseViewPoint (viewPoint)); +} + + +// public +int kpView::textSelectionMoveBorderAtomicSize () const +{ + if (!selection () || !selection ()->isText ()) + return 0; + + return QMAX (4, zoomLevelX () / 100); +} + +// public +bool kpView::mouseOnSelectionToMove (const QPoint &viewPoint) const +{ + if (!mouseOnSelection (viewPoint)) + return false; + + if (!selection ()->isText ()) + return true; + + if (mouseOnSelectionResizeHandle (viewPoint)) + return false; + + + const QPoint viewPointRelSel = mouseViewPointRelativeToSelection (viewPoint); + + // Middle point should always be selectable + const QPoint selCenterDocPoint = selection ()->boundingRect ().center (); + if (tool () && + tool ()->currentPoint () == selCenterDocPoint) + { + return false; + } + + + const int atomicSize = textSelectionMoveBorderAtomicSize (); + const QRect selViewRect = selectionViewRect (); + + return (viewPointRelSel.x () < atomicSize || + viewPointRelSel.x () >= selViewRect.width () - atomicSize || + viewPointRelSel.y () < atomicSize || + viewPointRelSel.y () >= selViewRect.height () - atomicSize); +} + + +// protected +bool kpView::selectionLargeEnoughToHaveResizeHandlesIfAtomicSize (int atomicSize) const +{ + if (!selection ()) + return false; + + const QRect selViewRect = selectionViewRect (); + + return (selViewRect.width () >= atomicSize * 5 || + selViewRect.height () >= atomicSize * 5); +} + +// public +int kpView::selectionResizeHandleAtomicSize () const +{ + int atomicSize = QMIN (7, QMAX (4, zoomLevelX () / 100)); + while (atomicSize > 0 && + !selectionLargeEnoughToHaveResizeHandlesIfAtomicSize (atomicSize)) + { + atomicSize--; + } + + return atomicSize; +} + +// public +bool kpView::selectionLargeEnoughToHaveResizeHandles () const +{ + return (selectionResizeHandleAtomicSize () > 0); +} + +// public +QRegion kpView::selectionResizeHandlesViewRegion (bool forRenderer) const +{ + QRegion ret; + + const int atomicLength = selectionResizeHandleAtomicSize (); + if (atomicLength <= 0) + return QRegion (); + + + // HACK: At low zoom (e.g. 100%), resize handles will probably be too + // big and overlap text / cursor / too much of selection. + // + // So limit the _visual_ size of handles at low zoom. The + // handles' grab area remains the same for usability; so yes, + // there are a few pixels that don't look grabable but they are. + // + // The real solution is to be able to partially render the + // handles outside of the selection view rect. If not possible, + // at least for text boxes, render text on top of handles. + int normalAtomicLength = atomicLength; + int vertEdgeAtomicLength = atomicLength; + if (forRenderer && selection ()) + { + if (zoomLevelX () <= 150) + { + if (normalAtomicLength > 1) + normalAtomicLength--; + + if (vertEdgeAtomicLength > 1) + vertEdgeAtomicLength--; + } + + // 1 line of text? + if (selection ()->isText () && selection ()->textLines ().size () == 1) + { + if (zoomLevelX () <= 150) + vertEdgeAtomicLength = QMIN (vertEdgeAtomicLength, QMAX (2, zoomLevelX () / 100)); + else if (zoomLevelX () <= 250) + vertEdgeAtomicLength = QMIN (vertEdgeAtomicLength, QMAX (3, zoomLevelX () / 100)); + } + } + + + const QRect selViewRect = selectionViewRect (); + +#define ADD_BOX_RELATIVE_TO_SELECTION(type,x,y) \ + ret += QRect ((x), (y), type##AtomicLength, type##AtomicLength) + + ADD_BOX_RELATIVE_TO_SELECTION (normal, + selViewRect.width () - normalAtomicLength, + selViewRect.height () - normalAtomicLength); + ADD_BOX_RELATIVE_TO_SELECTION (normal, + selViewRect.width () - normalAtomicLength, + 0); + ADD_BOX_RELATIVE_TO_SELECTION (normal, + 0, + selViewRect.height () - normalAtomicLength); + ADD_BOX_RELATIVE_TO_SELECTION (normal, + 0, + 0); + + ADD_BOX_RELATIVE_TO_SELECTION (vertEdge, + selViewRect.width () - vertEdgeAtomicLength, + (selViewRect.height () - vertEdgeAtomicLength) / 2); + ADD_BOX_RELATIVE_TO_SELECTION (normal, + (selViewRect.width () - normalAtomicLength) / 2, + selViewRect.height () - normalAtomicLength); + ADD_BOX_RELATIVE_TO_SELECTION (normal, + (selViewRect.width () - normalAtomicLength) / 2, + 0); + ADD_BOX_RELATIVE_TO_SELECTION (vertEdge, + 0, + (selViewRect.height () - vertEdgeAtomicLength) / 2); + +#undef ADD_BOX_RELATIVE_TO_SELECTION + + ret.translate (selViewRect.x (), selViewRect.y ()); + ret = ret.intersect (selViewRect); + + return ret; +} + +// public +int kpView::mouseOnSelectionResizeHandle (const QPoint &viewPoint) const +{ +#if DEBUG_KP_VIEW + kdDebug () << "kpView::mouseOnSelectionResizeHandle(viewPoint=" + << viewPoint << ")" << endl; +#endif + + if (!mouseOnSelection (viewPoint)) + { + #if DEBUG_KP_VIEW + kdDebug () << "\tmouse not on sel" << endl; + #endif + return 0; + } + + + const QRect selViewRect = selectionViewRect (); +#if DEBUG_KP_VIEW + kdDebug () << "\tselViewRect=" << selViewRect << endl; +#endif + + + const int atomicLength = selectionResizeHandleAtomicSize (); +#if DEBUG_KP_VIEW + kdDebug () << "\tatomicLength=" << atomicLength << endl; +#endif + + if (atomicLength <= 0) + { + #if DEBUG_KP_VIEW + kdDebug () << "\tsel not large enough to have resize handles" << endl; + #endif + // Want to make it possible to move a small selection + return 0; + } + + + const QPoint viewPointRelSel = mouseViewPointRelativeToSelection (viewPoint); +#if DEBUG_KP_VIEW + kdDebug () << "\tviewPointRelSel=" << viewPointRelSel << endl; +#endif + + +#define LOCAL_POINT_IN_BOX_AT(x,y) \ + QRect ((x), (y), atomicLength, atomicLength).contains (viewPointRelSel) + + // Favour the bottom & right and the corners. + if (LOCAL_POINT_IN_BOX_AT (selViewRect.width () - atomicLength, + selViewRect.height () - atomicLength)) + { + return Bottom | Right; + } + else if (LOCAL_POINT_IN_BOX_AT (selViewRect.width () - atomicLength, 0)) + { + return Top | Right; + } + else if (LOCAL_POINT_IN_BOX_AT (0, selViewRect.height () - atomicLength)) + { + return Bottom | Left; + } + else if (LOCAL_POINT_IN_BOX_AT (0, 0)) + { + return Top | Left; + } + else if (LOCAL_POINT_IN_BOX_AT (selViewRect.width () - atomicLength, + (selViewRect.height () - atomicLength) / 2)) + { + return Right; + } + else if (LOCAL_POINT_IN_BOX_AT ((selViewRect.width () - atomicLength) / 2, + selViewRect.height () - atomicLength)) + { + return Bottom; + } + else if (LOCAL_POINT_IN_BOX_AT ((selViewRect.width () - atomicLength) / 2, 0)) + { + return Top; + } + else if (LOCAL_POINT_IN_BOX_AT (0, (selViewRect.height () - atomicLength) / 2)) + { + return Left; + } + else + { + #if DEBUG_KP_VIEW + kdDebug () << "\tnot on sel resize handle" << endl; + #endif + return 0; + } +#undef LOCAL_POINT_IN_BOX_AT +} + +// public +bool kpView::mouseOnSelectionToSelectText (const QPoint &viewPoint) const +{ +#if DEBUG_KP_VIEW + kdDebug () << "kpView::mouseOnSelectionToSelectText(viewPoint=" + << viewPoint << ")" << endl; +#endif + + if (!mouseOnSelection (viewPoint)) + { + #if DEBUG_KP_VIEW + kdDebug () << "\tmouse non on sel" << endl; + #endif + return false; + } + + if (!selection ()->isText ()) + { + #if DEBUG_KP_VIEW + kdDebug () << "\tsel not text" << endl; + #endif + return false; + } + +#if DEBUG_KP_VIEW + kdDebug () << "\tmouse on sel: to move=" << mouseOnSelectionToMove () + << " to resize=" << mouseOnSelectionResizeHandle () + << endl; +#endif + + return (!mouseOnSelectionToMove (viewPoint) && + !mouseOnSelectionResizeHandle (viewPoint)); +} + + +// protected virtual [base QWidget] +void kpView::mouseMoveEvent (QMouseEvent *e) +{ +#if DEBUG_KP_VIEW && 0 + kdDebug () << "kpView(" << name () << ")::mouseMoveEvent (" + << e->x () << "," << e->y () << ")" + << endl; +#endif + + // TODO: This is wrong if you leaveEvent the mainView by mouseMoving on the + // mainView, landing on top of the thumbnailView cleverly put on top + // of the mainView. + setHasMouse (rect ().contains (e->pos ())); + + if (tool ()) + tool ()->mouseMoveEvent (e); + + e->accept (); +} + +// protected virtual [base QWidget] +void kpView::mousePressEvent (QMouseEvent *e) +{ +#if DEBUG_KP_VIEW && 0 + kdDebug () << "kpView(" << name () << ")::mousePressEvent (" + << e->x () << "," << e->y () << ")" + << endl; +#endif + + setHasMouse (true); + + if (tool ()) + tool ()->mousePressEvent (e); + + e->accept (); +} + +// protected virtual [base QWidget] +void kpView::mouseReleaseEvent (QMouseEvent *e) +{ +#if DEBUG_KP_VIEW && 0 + kdDebug () << "kpView(" << name () << ")::mouseReleaseEvent (" + << e->x () << "," << e->y () << ")" + << endl; +#endif + + setHasMouse (rect ().contains (e->pos ())); + + if (tool ()) + tool ()->mouseReleaseEvent (e); + + e->accept (); +} + +// public virtual [base QWidget] +void kpView::wheelEvent (QWheelEvent *e) +{ + if (tool ()) + tool ()->wheelEvent (e); +} + + +// protected virtual [base QWidget] +void kpView::keyPressEvent (QKeyEvent *e) +{ +#if DEBUG_KP_VIEW && 0 + kdDebug () << "kpView(" << name () << ")::keyPressEvent()" << endl; +#endif + + if (tool ()) + tool ()->keyPressEvent (e); + + e->accept (); +} + +// protected virtual [base QWidget] +void kpView::keyReleaseEvent (QKeyEvent *e) +{ +#if DEBUG_KP_VIEW && 0 + kdDebug () << "kpView(" << name () << ")::keyReleaseEvent()" << endl; +#endif + + if (tool ()) + tool ()->keyReleaseEvent (e); + + e->accept (); +} + + +// protected virtual [base QWidget] +void kpView::focusInEvent (QFocusEvent *e) +{ +#if DEBUG_KP_VIEW && 0 + kdDebug () << "kpView(" << name () << ")::focusInEvent()" << endl; +#endif + if (tool ()) + tool ()->focusInEvent (e); +} + +// protected virtual [base QWidget] +void kpView::focusOutEvent (QFocusEvent *e) +{ +#if DEBUG_KP_VIEW && 0 + kdDebug () << "kpView(" << name () << ")::focusOutEvent()" << endl; +#endif + if (tool ()) + tool ()->focusOutEvent (e); +} + + +// protected virtual [base QWidget] +void kpView::enterEvent (QEvent *e) +{ +#if DEBUG_KP_VIEW && 0 + kdDebug () << "kpView(" << name () << ")::enterEvent()" << endl; +#endif + + // Don't call setHasMouse(true) as it displays the brush cursor (if + // active) when dragging open a menu and then dragging + // past the extents of the menu due to Qt sending us an EnterEvent. + // We're already covered by MouseMoveEvent anyway. + // + // But disabling this causes a more serious problem: RMB on a text + // box and Esc. We have no other reliable way to determine if the + // mouse is still above the view (user could have moved mouse out + // while RMB menu was up) and hence the cursor is not updated. + setHasMouse (true); + + if (tool ()) + tool ()->enterEvent (e); +} + +// protected virtual [base QWidget] +void kpView::leaveEvent (QEvent *e) +{ +#if DEBUG_KP_VIEW && 0 + kdDebug () << "kpView(" << name () << ")::leaveEvent()" << endl; +#endif + + setHasMouse (false); + if (tool ()) + tool ()->leaveEvent (e); +} + + +// protected virtual [base QWidget] +void kpView::dragEnterEvent (QDragEnterEvent *) +{ +#if DEBUG_KP_VIEW && 1 + kdDebug () << "kpView(" << name () << ")::dragEnterEvent()" << endl; +#endif + + setHasMouse (true); +} + +// protected virtual [base QWidget] +void kpView::dragLeaveEvent (QDragLeaveEvent *) +{ +#if DEBUG_KP_VIEW && 1 + kdDebug () << "kpView(" << name () << ")::dragLeaveEvent" << endl; +#endif + + setHasMouse (false); +} + + +// public virtual [base QWidget] +void kpView::resize (int w, int h) +{ +#if DEBUG_KP_VIEW && 1 + kdDebug () << "kpView(" << name () + << ")::resize(" << w << "," << h << ")" + << endl; +#endif + + QWidget::resize (w, h); +} + +// protected virtual [base QWidget] +void kpView::resizeEvent (QResizeEvent *e) +{ +#if DEBUG_KP_VIEW && 1 + kdDebug () << "kpView(" << name () << ")::resizeEvent(" + << e->size () + << " vs actual=" << size () + << ") old=" << e->oldSize () << endl; +#endif + + QWidget::resizeEvent (e); + + emit sizeChanged (width (), height ()); + emit sizeChanged (size ()); +} + + +// private virtual +void kpView::imStartEvent (QIMEvent *e) +{ +#if DEBUG_KP_VIEW && 1 + kdDebug () << "kpView(" << name () << ")::imStartEvent" << endl; +#endif + + if (tool ()) + tool ()->imStartEvent (e); + e->accept(); +} + +// private virtual +void kpView::imComposeEvent (QIMEvent *e) +{ +#if DEBUG_KP_VIEW && 1 + kdDebug () << "kpView(" << name () << ")::imComposeEvent" << endl; +#endif + + if (tool ()) + tool ()->imComposeEvent (e); + e->accept(); +} + +// private virtual +void kpView::imEndEvent (QIMEvent *e) +{ +#if DEBUG_KP_VIEW && 1 + kdDebug () << "kpView(" << name () << ")::imEndEvent" << endl; +#endif + + if (tool ()) + tool ()->imEndEvent (e); + e->accept(); +} + + +// +// Renderer +// + +// protected +QRect kpView::paintEventGetDocRect (const QRect &viewRect) const +{ +#if DEBUG_KP_VIEW_RENDERER && 1 + kdDebug () << "kpView::paintEventGetDocRect(" << viewRect << ")" << endl; +#endif + + QRect docRect; + + // From the "we aren't sure whether to round up or round down" department: + + if (zoomLevelX () < 100 || zoomLevelY () < 100) + docRect = transformViewToDoc (viewRect); + else + { + // think of a grid - you need to fully cover the zoomed-in pixels + // when docRect is zoomed back to the view later + docRect = QRect (transformViewToDoc (viewRect.topLeft ()), // round down + transformViewToDoc (viewRect.bottomRight ())); // round down + } + + if (zoomLevelX () % 100 || zoomLevelY () % 100) + { + // at least round up the bottom-right point and deal with matrix weirdness: + // - helpful because it ensures we at least cover the required area + // at e.g. 67% or 573% + // - harmless since paintEventDrawRect() clips for us anyway + docRect.setBottomRight (docRect.bottomRight () + QPoint (2, 2)); + } + +#if DEBUG_KP_VIEW_RENDERER && 1 + kdDebug () << "\tdocRect=" << docRect << endl; +#endif + kpDocument *doc = document (); + if (doc) + { + docRect = docRect.intersect (doc->rect ()); + #if DEBUG_KP_VIEW_RENDERER && 1 + kdDebug () << "\tintersect with doc=" << docRect << endl; + #endif + } + + return docRect; +} + +// public +void kpView::drawTransparentBackground (QPainter *painter, + int /*viewWidth*/, int /*viewHeight*/, + const QRect &rect, + bool isPreview) +{ + const int cellSize = !isPreview ? 16 : 10; + + int starty = rect.y (); + if (starty % cellSize) + starty -= (starty % cellSize); + + int startx = rect.x (); + if (startx % cellSize) + startx -= (startx % cellSize); + + painter->save (); + for (int y = starty; y <= rect.bottom (); y += cellSize) + { + for (int x = startx; x <= rect.right (); x += cellSize) + { + bool parity = (x / cellSize + y / cellSize) % 2; + QColor col; + + if (parity) + { + if (!isPreview) + col = QColor (213, 213, 213); + else + col = QColor (224, 224, 224); + } + else + col = Qt::white; + + painter->fillRect (x - rect.x (), y - rect.y (), cellSize, cellSize, + col); + } + } + painter->restore (); +} + +// protected +void kpView::paintEventDrawCheckerBoard (QPainter *painter, const QRect &viewRect) +{ + kpDocument *doc = document (); + if (!doc) + return; + + drawTransparentBackground (painter, + doc->width () * zoomLevelX () / 100, + doc->height () * zoomLevelY () / 100, + viewRect); +} + +// protected +void kpView::paintEventDrawSelection (QPixmap *destPixmap, const QRect &docRect) +{ +#if DEBUG_KP_VIEW_RENDERER && 1 + kdDebug () << "kpView::paintEventDrawSelection() docRect=" << docRect << endl; +#endif + + kpDocument *doc = document (); + if (!doc) + { + #if DEBUG_KP_VIEW_RENDERER && 1 + kdDebug () << "\tno doc - abort" << endl; + #endif + return; + } + + kpSelection *sel = doc->selection (); + if (!sel) + { + #if DEBUG_KP_VIEW_RENDERER && 1 + kdDebug () << "\tno sel - abort" << endl; + #endif + return; + } + + + // + // Draw selection pixmap (if there is one) + // +#if DEBUG_KP_VIEW_RENDERER && 1 + kdDebug () << "\tdraw sel pixmap @ " << sel->topLeft () << endl; +#endif + sel->paint (destPixmap, docRect); + + + // + // Draw selection border + // + + kpViewManager *vm = viewManager (); +#if DEBUG_KP_VIEW_RENDERER && 1 + kdDebug () << "\tsel border visible=" + << vm->selectionBorderVisible () + << endl; +#endif + if (vm->selectionBorderVisible ()) + { + QPainter destPixmapPainter (destPixmap); + destPixmapPainter.setRasterOp (Qt::XorROP); + destPixmapPainter.setPen (QPen (Qt::white, 1, Qt::DotLine)); + + destPixmapPainter.setBackgroundMode (QPainter::OpaqueMode); + destPixmapPainter.setBackgroundColor (Qt::blue); + + QBitmap maskBitmap; + QPainter maskBitmapPainter; + if (destPixmap->mask ()) + { + maskBitmap = *destPixmap->mask (); + maskBitmapPainter.begin (&maskBitmap); + maskBitmapPainter.setPen (Qt::color1/*opaque*/); + } + + + #define PAINTER_CMD(cmd) \ + { \ + destPixmapPainter . cmd; \ + if (maskBitmapPainter.isActive ()) \ + maskBitmapPainter . cmd; \ + } + + QRect boundingRect = sel->boundingRect (); + #if DEBUG_KP_VIEW_RENDERER && 1 + kdDebug () << "\tsel boundingRect=" + << boundingRect + << endl; + #endif + + if (boundingRect.topLeft () != boundingRect.bottomRight ()) + { + switch (sel->type ()) + { + case kpSelection::Rectangle: + case kpSelection::Text: + #if DEBUG_KP_VIEW_RENDERER && 1 + kdDebug () << "\tselection border = rectangle" << endl; + kdDebug () << "\t\tx=" << boundingRect.x () - docRect.x () + << " y=" << boundingRect.y () - docRect.y () + << " w=" << boundingRect.width () + << " h=" << boundingRect.height () + << endl; + #endif + PAINTER_CMD (drawRect (boundingRect.x () - docRect.x (), + boundingRect.y () - docRect.y (), + boundingRect.width (), + boundingRect.height ())); + break; + + case kpSelection::Ellipse: + #if DEBUG_KP_VIEW_RENDERER && 1 + kdDebug () << "\tselection border = ellipse" << endl; + #endif + PAINTER_CMD (drawEllipse (boundingRect.x () - docRect.x (), + boundingRect.y () - docRect.y (), + boundingRect.width (), + boundingRect.height ())); + break; + + case kpSelection::Points: + { + #if DEBUG_KP_VIEW_RENDERER + kdDebug () << "\tselection border = freeForm" << endl; + #endif + QPointArray points = sel->points (); + points.detach (); + points.translate (-docRect.x (), -docRect.y ()); + if (vm->selectionBorderFinished ()) + { + PAINTER_CMD (drawPolygon (points)); + } + else + { + PAINTER_CMD (drawPolyline (points)); + } + + break; + } + + default: + kdError () << "kpView::paintEventDrawSelection() unknown sel border type" << endl; + break; + } + + + if (vm->selectionBorderFinished () && + (sel->type () == kpSelection::Ellipse || + sel->type () == kpSelection::Points)) + { + destPixmapPainter.save (); + + destPixmapPainter.setRasterOp (Qt::NotROP); + PAINTER_CMD (drawRect (boundingRect.x () - docRect.x (), + boundingRect.y () - docRect.y (), + boundingRect.width (), + boundingRect.height ())); + + destPixmapPainter.restore (); + } + } + else + { + // SYNC: Work around Qt bug: can't draw 1x1 rectangle + PAINTER_CMD (drawPoint (boundingRect.topLeft () - docRect.topLeft ())); + } + + #undef PAINTER_CMD + + destPixmapPainter.end (); + if (maskBitmapPainter.isActive ()) + maskBitmapPainter.end (); + + destPixmap->setMask (maskBitmap); + } + + + // + // Draw text cursor + // + + if (sel->isText () && + vm->textCursorEnabled () && + (vm->textCursorBlinkState () || + // For the current main window: + // As long as _any_ view has focus, blink _all_ views not just the + // one with focus // !this->isActiveWindow () + !vm->activeView ())) // sync: call will break when vm is not held by 1 mainWindow + { + // TODO: Fix code duplication: kpViewManager::{setTextCursorPosition,updateTextCursor}() & kpView::paintEventDrawSelection() + QPoint topLeft = sel->pointForTextRowCol (vm->textCursorRow (), vm->textCursorCol ()); + if (topLeft != KP_INVALID_POINT) + { + QRect rect = QRect (topLeft.x (), topLeft.y (), + 1, sel->textStyle ().fontMetrics ().height ()); + rect = rect.intersect (sel->textAreaRect ()); + if (!rect.isEmpty ()) + { + rect.moveBy (-docRect.x (), -docRect.y ()); + + QBitmap maskBitmap; + QPainter destPixmapPainter, maskBitmapPainter; + + if (destPixmap->mask ()) + { + maskBitmap = *destPixmap->mask (); + maskBitmapPainter.begin (&maskBitmap); + maskBitmapPainter.fillRect (rect, Qt::color1/*opaque*/); + maskBitmapPainter.end (); + } + + destPixmapPainter.begin (destPixmap); + destPixmapPainter.setRasterOp (Qt::XorROP); + destPixmapPainter.fillRect (rect, Qt::white); + destPixmapPainter.end (); + + if (!maskBitmap.isNull ()) + destPixmap->setMask (maskBitmap); + } + } + } +} + +// protected +bool kpView::selectionResizeHandleAtomicSizeCloseToZoomLevel () const +{ + return (abs (selectionResizeHandleAtomicSize () - zoomLevelX () / 100) < 3); +} + +// protected +void kpView::paintEventDrawSelectionResizeHandles (QPainter *painter, const QRect &viewRect) +{ +#if DEBUG_KP_VIEW_RENDERER && 1 + kdDebug () << "kpView::paintEventDrawSelectionResizeHandles(" + << viewRect << ")" << endl; +#endif + + if (!selectionLargeEnoughToHaveResizeHandles ()) + { + #if DEBUG_KP_VIEW_RENDERER && 1 + kdDebug () << "\tsel not large enough to have resize handles" << endl; + #endif + return; + } + + kpViewManager *vm = viewManager (); + if (!vm || !vm->selectionBorderVisible () || !vm->selectionBorderFinished ()) + { + #if DEBUG_KP_VIEW_RENDERER && 1 + kdDebug () << "\tsel border not visible or not finished" << endl; + #endif + + return; + } + + const QRect selViewRect = selectionViewRect (); +#if DEBUG_KP_VIEW_RENDERER && 1 + kdDebug () << "\tselViewRect=" << selViewRect << endl; +#endif + if (!selViewRect.intersects (viewRect)) + { + #if DEBUG_KP_VIEW_RENDERER && 1 + kdDebug () << "\tdoesn't intersect viewRect" << endl; + #endif + return; + } + + QRegion selResizeHandlesRegion = selectionResizeHandlesViewRegion (true/*for renderer*/); +#if DEBUG_KP_VIEW_RENDERER && 1 + kdDebug () << "\tsel resize handles view region=" + << selResizeHandlesRegion << endl; +#endif + selResizeHandlesRegion.translate (-viewRect.x (), -viewRect.y ()); + + painter->save (); + + QColor fillColor; + if (selectionResizeHandleAtomicSizeCloseToZoomLevel ()) + { + fillColor = Qt::blue; + painter->setRasterOp (Qt::CopyROP); + } + else + { + fillColor = Qt::white; + painter->setRasterOp (Qt::XorROP); + } + + QMemArray <QRect> rects = selResizeHandlesRegion.rects (); + for (QMemArray <QRect>::ConstIterator it = rects.begin (); + it != rects.end (); + it++) + { + painter->fillRect (*it, fillColor); + } + + painter->restore (); +} + +// protected +void kpView::paintEventDrawTempPixmap (QPixmap *destPixmap, const QRect &docRect) +{ + kpViewManager *vm = viewManager (); + if (!vm) + return; + + const kpTempPixmap *tpm = vm->tempPixmap (); +#if DEBUG_KP_VIEW_RENDERER && 1 + kdDebug () << "kpView::paintEventDrawTempPixmap() tempPixmap=" + << tpm + << " isVisible=" + << (tpm ? tpm->isVisible (vm) : false) + << endl; +#endif + + if (!tpm || !tpm->isVisible (vm)) + return; + + tpm->paint (destPixmap, docRect); +} + +// protected +void kpView::paintEventDrawGridLines (QPainter *painter, const QRect &viewRect) +{ + int hzoomMultiple = zoomLevelX () / 100; + int vzoomMultiple = zoomLevelY () / 100; + + QPen ordinaryPen (Qt::gray); + QPen tileBoundaryPen (Qt::lightGray); + + painter->setPen (ordinaryPen); + + // horizontal lines + int starty = viewRect.top (); + if (starty % vzoomMultiple) + starty = (starty + vzoomMultiple) / vzoomMultiple * vzoomMultiple; + int tileHeight = 16 * vzoomMultiple; // CONFIG + for (int y = starty - viewRect.y (); y <= viewRect.bottom () - viewRect.y (); y += vzoomMultiple) + { + if (0 && tileHeight > 0 && y % tileHeight == 0) + { + painter->setPen (tileBoundaryPen); + //painter.setRasterOp (Qt::XorROP); + } + + painter->drawLine (0, y, viewRect.right () - viewRect.left (), y); + + if (0 && tileHeight > 0 && y % tileHeight == 0) + { + painter->setPen (ordinaryPen); + //painter.setRasterOp (Qt::CopyROP); + } + } + + // vertical lines + int startx = viewRect.left (); + if (startx % hzoomMultiple) + startx = (startx + hzoomMultiple) / hzoomMultiple * hzoomMultiple; + int tileWidth = 16 * hzoomMultiple; // CONFIG + for (int x = startx - viewRect.x (); x <= viewRect.right () - viewRect.x (); x += hzoomMultiple) + { + if (0 && tileWidth > 0 && x % tileWidth == 0) + { + painter->setPen (tileBoundaryPen); + //painter.setRasterOp (Qt::XorROP); + } + + painter->drawLine (x, 0, x, viewRect.bottom () - viewRect.top ()); + + if (0 && tileWidth > 0 && x % tileWidth == 0) + { + painter->setPen (ordinaryPen); + //painter.setRasterOp (Qt::CopyROP); + } + } +} + + +void kpView::paintEventDrawRect (const QRect &viewRect) +{ +#if DEBUG_KP_VIEW_RENDERER + kdDebug () << "\tkpView::paintEventDrawRect(viewRect=" << viewRect + << ")" << endl; +#endif + + kpViewManager *vm = viewManager (); + const kpDocument *doc = document (); + + if (!vm || !doc) + return; + + + if (viewRect.isEmpty ()) + return; + + + QRect docRect = paintEventGetDocRect (viewRect); + +#if DEBUG_KP_VIEW_RENDERER && 1 + kdDebug () << "\tdocRect=" << docRect << endl; +#endif + +// uncomment to cause deliberate flicker (identifies needless updates) +#if DEBUG_KP_VIEW_RENDERER && 0 + QPainter flickerPainter (this); + flickerPainter.fillRect (viewRect, Qt::red); + flickerPainter.end (); +#endif + + + // + // Prepare Back Buffer + // + + if (!d->m_backBuffer || + d->m_backBuffer->width () < viewRect.width () || + d->m_backBuffer->height () < viewRect.height () || + d->m_backBuffer->width () > width () || + d->m_backBuffer->height () > height ()) + { + // don't use QPixmap::resize() as that wastes time copying pixels + // that will be overwritten anyway + // + // OPT: Should use doubling trick or at least go up in multiples + // to reduce X server pressure. + delete d->m_backBuffer; + d->m_backBuffer = new QPixmap (viewRect.width (), viewRect.height ()); + } + +// uncomment to catch bits of the view that the renderer forgot to update +#if 0 + d->m_backBuffer->fill (Qt::green); +#endif + + QPainter backBufferPainter; + backBufferPainter.begin (d->m_backBuffer); + + + // + // Draw checkboard for transparent images and/or views with borders + // + + QPixmap docPixmap; + + bool tempPixmapWillBeRendered = false; + + if (!docRect.isEmpty ()) + { + docPixmap = doc->getPixmapAt (docRect); + + tempPixmapWillBeRendered = + (!doc->selection () && + vm->tempPixmap () && + vm->tempPixmap ()->isVisible (vm) && + docRect.intersects (vm->tempPixmap ()->rect ())); + + #if DEBUG_KP_VIEW_RENDERER && 1 + kdDebug () << "\ttempPixmapWillBeRendered=" << tempPixmapWillBeRendered + << " (sel=" << doc->selection () + << " tempPixmap=" << vm->tempPixmap () + << " tempPixmap.isVisible=" << (vm->tempPixmap () ? vm->tempPixmap ()->isVisible (vm) : false) + << " docRect.intersects(tempPixmap.rect)=" << (vm->tempPixmap () ? docRect.intersects (vm->tempPixmap ()->rect ()) : false) + << ")" + << endl; + #endif + } + + if (docPixmap.mask () || + (tempPixmapWillBeRendered && vm->tempPixmap ()->mayChangeDocumentMask ())) + { + #if DEBUG_KP_VIEW_RENDERER && 1 + kdDebug () << "\tmask=" << (bool) docPixmap.mask () + << endl; + #endif + paintEventDrawCheckerBoard (&backBufferPainter, viewRect); + } + else + { + #if DEBUG_KP_VIEW_RENDERER && 1 + kdDebug () << "\tno mask" << endl; + #endif + } + + + if (!docRect.isEmpty ()) + { + // + // Draw docPixmap + tempPixmap + // + + if (doc->selection ()) + { + paintEventDrawSelection (&docPixmap, docRect); + } + else if (tempPixmapWillBeRendered) + { + paintEventDrawTempPixmap (&docPixmap, docRect); + } + + #if DEBUG_KP_VIEW_RENDERER && 1 + kdDebug () << "\torigin=" << origin () << endl; + #endif + // blit scaled version of docPixmap + tempPixmap onto Back Buffer + #if DEBUG_KP_VIEW_RENDERER && 1 + QTime scaleTimer; scaleTimer.start (); + #endif + backBufferPainter.translate (origin ().x () - viewRect.x (), + origin ().y () - viewRect.y ()); + backBufferPainter.scale (double (zoomLevelX ()) / 100.0, + double (zoomLevelY ()) / 100.0); + backBufferPainter.drawPixmap (docRect, docPixmap); + backBufferPainter.resetXForm (); // back to 1-1 scaling + #if DEBUG_KP_VIEW_RENDERER && 1 + kdDebug () << "\tscale time=" << scaleTimer.elapsed () << endl; + #endif + + } // if (!docRect.isEmpty ()) { + + + // + // Draw Grid Lines + // + + if (isGridShown ()) + { + #if DEBUG_KP_VIEW_RENDERER && 1 + QTime gridTimer; gridTimer.start (); + #endif + paintEventDrawGridLines (&backBufferPainter, viewRect); + #if DEBUG_KP_VIEW_RENDERER && 1 + kdDebug () << "\tgrid time=" << gridTimer.elapsed () << endl; + #endif + } + + + const QRect bvsvRect = buddyViewScrollableContainerRectangle (); + if (!bvsvRect.isEmpty ()) + { + backBufferPainter.save (); + + backBufferPainter.setRasterOp (Qt::XorROP); + backBufferPainter.setPen (Qt::white); + backBufferPainter.translate (-viewRect.x (), -viewRect.y ()); + backBufferPainter.drawRect (bvsvRect); + + backBufferPainter.restore (); + } + + + if (!docRect.isEmpty ()) + { + if (doc->selection ()) + { + // Draw resize handles on top of possible grid lines + paintEventDrawSelectionResizeHandles (&backBufferPainter, viewRect); + } + } + + + // + // Blit Back Buffer to View + // + + backBufferPainter.end (); + + bitBlt (this, viewRect.topLeft (), + d->m_backBuffer, QRect (0, 0, viewRect.width (), viewRect.height ())); +} + + +// protected virtual [base QWidget] +void kpView::paintEvent (QPaintEvent *e) +{ + // sync: kpViewPrivate + // WARNING: document(), viewManager() and friends might be 0 in this method. + // TODO: I'm not 100% convinced that we always check if their friends are 0. + +#if DEBUG_KP_VIEW_RENDERER && 1 + QTime timer; + timer.start (); +#endif + + kpViewManager *vm = viewManager (); + +#if DEBUG_KP_VIEW_RENDERER && 1 + kdDebug () << "kpView(" << name () << ")::paintEvent() vm=" << (bool) vm + << " queueUpdates=" << (vm && vm->queueUpdates ()) + << " fastUpdates=" << (vm && vm->fastUpdates ()) + << " viewRect=" << e->rect () + << " erased=" << e->erased () + << " topLeft=" << QPoint (x (), y ()) + << endl; +#endif + + if (!vm) + return; + + if (vm->queueUpdates ()) + { + // OPT: if this update was due to the document, + // use document coordinates (in case of a zoom change in + // which view coordinates become out of date) + addToQueuedArea (e->region ()); + return; + } + + + QRegion viewRegion = clipRegion ().intersect (e->region ()); + QMemArray <QRect> rects = viewRegion.rects (); +#if DEBUG_KP_VIEW_RENDERER && 1 + kdDebug () << "\t#rects = " << rects.count () << endl; +#endif + + for (QMemArray <QRect>::ConstIterator it = rects.begin (); + it != rects.end (); + it++) + { + paintEventDrawRect (*it); + } + + +#if DEBUG_KP_VIEW_RENDERER && 1 + kdDebug () << "\tall done in: " << timer.restart () << "ms" << endl; +#endif +} + + +#include <kpview.moc> diff --git a/kolourpaint/kpview.h b/kolourpaint/kpview.h new file mode 100644 index 00000000..0bf8c495 --- /dev/null +++ b/kolourpaint/kpview.h @@ -0,0 +1,535 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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_H +#define KP_VIEW_H + + +#include <qwidget.h> + +#include <kpdefs.h> + + +class kpDocument; +class kpSelection; +class kpTool; +class kpToolToolBar; +class kpViewManager; +class kpViewScrollableContainer; + + +/** + * @short Abstract base class for all views. + * + * This is the abstract base class for all views. A view is a widget that + * renders a possibly zoomed onscreen representation of a document. + * + * 1 view corresponds to 1 document. + * 1 document corresponds to 0 or more views. + * + * @see kpViewManager + * + * @author Clarence Dang <dang@kde.org> + */ +class kpView : public QWidget +{ +Q_OBJECT + +public: + /** + * Constructs a view. + * + * @param document The document this view is representing. + * @param toolToolBar The tool tool bar. + * @param viewManager The view manager. + * @param buddyView The view this view watches over (e.g. a thumbnail + * view would watch over the main view). May be 0. + * See for example, highlightBuddyViewRectangle(). + * @param scrollableContainer This view's scrollable container. + * May be 0. + * + * You must call adjustEnvironment() at the end of your constructor. + */ + kpView (kpDocument *document, + kpToolToolBar *toolToolBar, + kpViewManager *viewManager, + kpView *buddyView, + kpViewScrollableContainer *scrollableContainer, + QWidget *parent, const char *name); + + /** + * Destructs this view. Informs the viewManager() that the mouse + * cursor is no longer above this view. + */ + virtual ~kpView (); + + + /** + * @returns the document. + */ + kpDocument *document () const; + +protected: + /** + * @returns the document's selection. + */ + kpSelection *selection () const; + +public: + /** + * @returns the tool tool bar. + */ + kpToolToolBar *toolToolBar () const; + +protected: + /** + * @returns the currently selected tool. + */ + kpTool *tool () const; + +public: + /** + * @returns the view manager. + */ + kpViewManager *viewManager () const; + + /** + * @returns the buddy view. + */ + kpView *buddyView () const; + + /** + * @returns the buddyView()'s scrollable container. + */ + kpViewScrollableContainer *buddyViewScrollableContainer () const; + + /** + * @returns this view's scrollable container. + */ + kpViewScrollableContainer *scrollableContainer () const; + + + /** + * @returns the horizontal zoom level (100 is unzoomed). + */ + int zoomLevelX (void) const; + + /** + * @returns the vertical zoom level (100 is unzoomed). + */ + int zoomLevelY (void) const; + + /** + * Sets the horizontal and vertical zoom levels. + * + * @param hzoom Horizontal zoom level. + * @param vzoom Vertical zoom level. + * + * If reimplementing, you must call this base implementation. + */ + virtual void setZoomLevel (int hzoom, int vzoom); + + + /** + * @returns in views coordinates, where the top-left document() pixel + * will be rendered (default: (0,0)). + */ + QPoint origin () const; + + /** + * Sets the origin. + * + * @param origin New origin. + * + * If reimplementing, you must call this base implementation. + */ + virtual void setOrigin (const QPoint &origin); + + + /** + * @returns whether at this zoom level, the grid can be enabled. + * This is based on whether the grid can be sensibly rendered. + */ + bool canShowGrid () const; + + /** + * @returns whether the grid is currently shown. + */ + bool isGridShown () const; + + /** + * Turns on/off the grid. + * + * @param yes Whether to enable the grid. + */ + void showGrid (bool yes = true); + + + /** + * @returns whether to draw a rectangle highlighting the area of + * buddyView() visible through buddyViewScrollableContainer(). + */ + bool isBuddyViewScrollableContainerRectangleShown () const; + + /** + * Turns on/off the rectangle highlighting the area of buddyView() + * visible through buddyViewScrollableContainer() and redraws. + * + * @param yes Whether to turn on the rectangle. + */ + void showBuddyViewScrollableContainerRectangle (bool yes = true); + +protected: + /** + * @returns the current rectangle highlighting the area of buddyView() + * visible through buddyViewScrollableContainer(), that is being + * rendered by this view. + */ + QRect buddyViewScrollableContainerRectangle () const; + +protected slots: + /** + * Updates the buddyViewScrollableContainerRectangle() and redraws + * appropriately. + * + * This is already connected to zoomLevelChanged() and originChanged(); + * buddyView() and buddyViewScrollableContainer() signals. There is probably no + * need to call this function directly. + */ + void updateBuddyViewScrollableContainerRectangle (); + + +public: + + /** + * @param viewX Horizontal position in view coordinates. + * + * @returns viewX transformed to document coordinates, based on the + * origin() and zoomLevelX(). + */ + double transformViewToDocX (double viewX) const; + + /** + * @param viewY Vertical position in view coordinates. + * + * @returns viewY transformed to document coordinates, based on the + * origin() and zoomLevelY(). + */ + double transformViewToDocY (double viewY) const; + + /** + * @param viewPoint Position in view coordinates. + * + * @returns viewPoint transformed to document coordinates, based on the + * origin(), zoomLevelX() and zoomLevelY(). + */ + QPoint transformViewToDoc (const QPoint &viewPoint) const; + + /** + * @param viewRect Rectangle in view coordinates. + * + * @returns viewRect transformed to document coordinates based on the + * origin(), zoomLevelX() and zoomLevelY(). + * + * For bounding rectangles, you should use this function instead of + * transformViewToDocX(), transformViewToDocY() or + * transformViewToDoc(const QPoint &) which act on coordinates only. + */ + QRect transformViewToDoc (const QRect &viewRect) const; + + + /** + * @param docX Horizontal position in document coordinates. + * + * @returns docX transformed to view coordinates, based on the origin() + * and zoomLevelX(). + */ + double transformDocToViewX (double docX) const; + + /** + * @param docY Vertical position in document coordinates. + * + * @returns docY transformed to view coordinates, based on the origin() + * and zoomLevelY(). + */ + double transformDocToViewY (double docY) const; + + /** + * @param docPoint Position in document coordinates. + * + * @returns docPoint transformed to view coordinates, based on the + * origin(), zoomLevelX(), zoomLevelY(). + */ + QPoint transformDocToView (const QPoint &docPoint) const; + + /** + * @param docRect Rectangle in document coordinates. + * + * @return docRect transformed to view coordinates, based on the + * origin(), zoomLevelX() and zoomLevelY(). + * + * For bounding rectangles, you should use this function instead of + * transformDocToViewX(), transformDocToViewY() or + * transformDocToView(const QPoint &) which act on coordinates only. + */ + QRect transformDocToView (const QRect &docRect) const; + + + /** + * @param viewPoint Position in view coordinates. + * @param otherView View whose coordinate system the return value will + * be in. + * + * @returns viewPoint transformed to the coordinate system of + * @param otherView based on this and otherView's origin(), + * zoomLevelX() and zoomLevelY(). This has less rounding + * error than otherView->transformDocToView (transformViewToDoc (viewPoint)). + */ + QPoint transformViewToOtherView (const QPoint &viewPoint, + const kpView *otherView); + + + /** + * @returns the approximate view width required to display the entire + * document(), based on the zoom level only. + */ + int zoomedDocWidth () const; + + /** + * @returns the approximate view height required to display the entire + * document(), based on the zoom level only. + */ + int zoomedDocHeight () const; + + +protected: + /** + * Updates the viewManager() on whether or not the mouse is directly + * above this view. Among other things, this ensures the brush cursor + * is updated. + * + * This should be called in event handlers. + * + * @param yes Whether the mouse is directly above this view. + */ + void setHasMouse (bool yes = true); + + +public: + /** + * Adds a region (in view coordinates) to the dirty area that is + * repainted when the parent @ref kpViewManager is set not to queue + * updates. + * + * @param region Region (in view coordinates) that needs repainting. + */ + void addToQueuedArea (const QRegion ®ion); + + /** + * Convenience function. Same as above. + * + * Adds a rectangle (in view coordinates) to the dirty area that is + * repainted when the parent @ref kpViewManager is set not to queue + * updates. + * + * @param rect Rectangle (in view coordinates) that needs repainting. + */ + void addToQueuedArea (const QRect &rect); + + /** + * Removes the dirty region that has been queued for updating. + * Does not update the view. + */ + void invalidateQueuedArea (); + + /** + * Updates the part of the view described by dirty region and then + * calls invalidateQueuedArea(). Does nothing if @ref kpViewManager + * is set to queue updates. + */ + void updateQueuedArea (); + + void updateMicroFocusHint (const QRect µFocusHint); + + +public slots: + /** + * Call this when the "environment" (e.g. document size) changes. The + * environment is defined by the caller and should be based on the type + * of view. For instance, an unzoomed thumbnail view would also + * include in its environment the contents position of an associated + * scrollable container. + * + * This is never called by the kpView base class. + * + * Implementors should change whatever state is neccessary for their + * type of view. For instance, an unzoomed view would resize itself; + * a zoomed thumbnail would change the zoom level. + */ + virtual void adjustToEnvironment () = 0; + + +public: + QRect selectionViewRect () const; + + // (if <viewPoint> is KP_INVALID_POINT, it uses QCursor::pos()) + + QPoint mouseViewPoint (const QPoint &returnViewPoint = KP_INVALID_POINT) const; + QPoint mouseViewPointRelativeToSelection (const QPoint &viewPoint = KP_INVALID_POINT) const; + bool mouseOnSelection (const QPoint &viewPoint = KP_INVALID_POINT) const; + + int textSelectionMoveBorderAtomicSize () const; + bool mouseOnSelectionToMove (const QPoint &viewPoint = KP_INVALID_POINT) const; + +protected: + bool selectionLargeEnoughToHaveResizeHandlesIfAtomicSize (int atomicSize) const; +public: + int selectionResizeHandleAtomicSize () const; + bool selectionLargeEnoughToHaveResizeHandles () const; + + QRegion selectionResizeHandlesViewRegion (bool forRenderer = false) const; + + enum SelectionResizeType + { + None = 0, + Left = 1, + Right = 2, + Top = 4, + Bottom = 8 + }; + + // Returns a bitwise OR of the SelectionResizeType's + int mouseOnSelectionResizeHandle (const QPoint &viewPoint = KP_INVALID_POINT) const; + + bool mouseOnSelectionToSelectText (const QPoint &viewPoint = KP_INVALID_POINT) const; + + +signals: + /** + * Emitted after all zooming code has been executed. + * + * @param zoomLevelX New zoomLevelX() + * @param zoomLevelY New zoomLevelY() + */ + void zoomLevelChanged (int zoomLevelX, int zoomLevelY); + + /** + * Emitted after all resizing code has been executed. + * + * @param size New view size. + */ + void sizeChanged (const QSize &size); + + /** + * Convenience signal - same as above. + * + * Emitted after all resizing code has been executed. + * + * @param width New view width. + * @param height New view height. + */ + void sizeChanged (int width, int height); + + /** + * Emitted after all origin changing code has been executed. + * + * @param origin The new origin. + */ + void originChanged (const QPoint &origin); + +protected: + virtual void mouseMoveEvent (QMouseEvent *e); + virtual void mousePressEvent (QMouseEvent *e); + virtual void mouseReleaseEvent (QMouseEvent *e); +public: + // (needs to be public as it may also get event from + // QScrollView::contentsWheelEvent()) + virtual void wheelEvent (QWheelEvent *e); + +protected: + virtual void keyPressEvent (QKeyEvent *e); + virtual void keyReleaseEvent (QKeyEvent *e); + + virtual void focusInEvent (QFocusEvent *e); + virtual void focusOutEvent (QFocusEvent *e); + + virtual void enterEvent (QEvent *e); + virtual void leaveEvent (QEvent *e); + + virtual void dragEnterEvent (QDragEnterEvent *); + virtual void dragLeaveEvent (QDragLeaveEvent *); + + virtual void imStartEvent (QIMEvent *e); + virtual void imComposeEvent (QIMEvent *e); + virtual void imEndEvent (QIMEvent *e); + +public: + virtual void resize (int w, int h); +protected: + virtual void resizeEvent (QResizeEvent *e); + + +protected: + QRect paintEventGetDocRect (const QRect &viewRect) const; +public: + /** + * Draws an opaque background representing transparency. Currently, + * it draws a checkerboard. + * + * @param painter Painter. + * @param viewWidth Total width of the view visible to the user. + * @param viewHeight Total height of the view visible to the user. + * @param rect Rectangle to paint in relative to the painter. Note + * that this function does not clip and may draw slightly + * more than the requested rectangle. TODO: why not? + * @param isPreview Whether the view is for a preview as opposed to + * e.g. editing. If set, this function may render + * slightly differently. + */ + static void drawTransparentBackground (QPainter *painter, + int viewWidth, int viewHeight, + const QRect &rect, + bool isPreview = false); +protected: + void paintEventDrawCheckerBoard (QPainter *painter, const QRect &viewRect); + void paintEventDrawSelection (QPixmap *destPixmap, const QRect &docRect); + bool selectionResizeHandleAtomicSizeCloseToZoomLevel () const; + void paintEventDrawSelectionResizeHandles (QPainter *painter, const QRect &viewRect); + void paintEventDrawTempPixmap (QPixmap *destPixmap, const QRect &docRect); + void paintEventDrawGridLines (QPainter *painter, const QRect &viewRect); + + void paintEventDrawRect (const QRect &viewRect); + virtual void paintEvent (QPaintEvent *e); + + +private: + struct kpViewPrivate *d; +}; + + +#endif // KP_VIEW_H diff --git a/kolourpaint/kpviewmanager.cpp b/kolourpaint/kpviewmanager.cpp new file mode 100644 index 00000000..d649f359 --- /dev/null +++ b/kolourpaint/kpviewmanager.cpp @@ -0,0 +1,766 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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_MANAGER 0 + + +#include <kpviewmanager.h> + +#include <qapplication.h> +#include <qtimer.h> + +#include <kdebug.h> + +#include <kpdefs.h> +#include <kpdocument.h> +#include <kpmainwindow.h> +#include <kpselection.h> +#include <kptemppixmap.h> +#include <kptool.h> +#include <kpview.h> + + +kpViewManager::kpViewManager (kpMainWindow *mainWindow) + : m_textCursorBlinkTimer (0), + m_textCursorRow (-1), + m_textCursorCol (-1), + m_textCursorBlinkState (true), + m_mainWindow (mainWindow), + m_tempPixmap (0), + m_viewUnderCursor (0), + m_selectionBorderVisible (false), + m_selectionBorderFinished (false) +{ + m_queueUpdatesCounter = m_fastUpdatesCounter = 0; +} + +// private +kpDocument *kpViewManager::document () const +{ + return m_mainWindow ? m_mainWindow->document () : 0; +} + +kpViewManager::~kpViewManager () +{ + unregisterAllViews (); + + delete m_tempPixmap; m_tempPixmap = 0; +} + + +void kpViewManager::registerView (kpView *view) +{ +#if DEBUG_KP_VIEW_MANAGER && 1 + kdDebug () << "kpViewManager::registerView (" << view << ")" << endl; +#endif + if (view && m_views.findRef (view) < 0) + { + #if DEBUG_KP_VIEW_MANAGER && 1 + kdDebug () << "\tadded view" << endl; + #endif + view->setCursor (m_cursor); + m_views.append (view); + } + else + { + #if DEBUG_KP_VIEW_MANAGER && 1 + kdDebug () << "\tignored register view attempt" << endl; + #endif + } +} + +void kpViewManager::unregisterView (kpView *view) +{ + if (view) + { + if (view == m_viewUnderCursor) + m_viewUnderCursor = 0; + + view->unsetCursor (); + m_views.removeRef (view); + } +} + +void kpViewManager::unregisterAllViews () +{ + // no autoDelete + m_views.clear (); +} + + +// public +const kpTempPixmap *kpViewManager::tempPixmap () const +{ + return m_tempPixmap; +} + +// public +void kpViewManager::setTempPixmap (const kpTempPixmap &tempPixmap) +{ +#if DEBUG_KP_VIEW_MANAGER + kdDebug () << "kpViewManager::setTempPixmap(isBrush=" + << tempPixmap.isBrush () + << ",topLeft=" << tempPixmap.topLeft () + << ",pixmap.rect=" << tempPixmap.pixmap ().rect () + << ")" << endl; +#endif + + QRect oldRect; + + if (m_tempPixmap) + { + oldRect = m_tempPixmap->rect (); + delete m_tempPixmap; + m_tempPixmap = 0; + } + + m_tempPixmap = new kpTempPixmap (tempPixmap); + + + setQueueUpdates (); + + if (oldRect.isValid ()) + updateViews (oldRect); + updateViews (m_tempPixmap->rect ()); + + restoreQueueUpdates (); +} + +// public +void kpViewManager::invalidateTempPixmap () +{ + if (!m_tempPixmap) + return; + + QRect oldRect = m_tempPixmap->rect (); + + delete m_tempPixmap; + m_tempPixmap = 0; + + updateViews (oldRect); +} + + +// public +bool kpViewManager::selectionBorderVisible () const +{ + return m_selectionBorderVisible; +} + +// public +void kpViewManager::setSelectionBorderVisible (bool yes) +{ + if (m_selectionBorderVisible == yes) + return; + + m_selectionBorderVisible = yes; + + if (document () && document ()->selection ()) + updateViews (document ()->selection ()->boundingRect ()); +} + + +// public +bool kpViewManager::selectionBorderFinished () const +{ + return m_selectionBorderFinished; +} + +// public +void kpViewManager::setSelectionBorderFinished (bool yes) +{ + if (m_selectionBorderFinished == yes) + return; + + m_selectionBorderFinished = yes; + + if (document () && document ()->selection ()) + updateViews (document ()->selection ()->boundingRect ()); +} + + +bool kpViewManager::textCursorEnabled () const +{ + return (bool) m_textCursorBlinkTimer; +} + +void kpViewManager::setTextCursorEnabled (bool yes) +{ +#if DEBUG_KP_VIEW_MANAGER && 1 + kdDebug () << "kpViewManager::setTextCursorEnabled(" << yes << ")" << endl; +#endif + + if (yes == textCursorEnabled ()) + return; + + delete m_textCursorBlinkTimer; + m_textCursorBlinkTimer = 0; + + setFastUpdates (); + setQueueUpdates (); + + m_textCursorBlinkState = true; + + if (yes) + { + m_textCursorBlinkTimer = new QTimer (this); + connect (m_textCursorBlinkTimer, SIGNAL (timeout ()), + this, SLOT (slotTextCursorBlink ())); + slotTextCursorBlink (); + } + // TODO: What if !yes - shouldn't it clear the cursor? + + restoreQueueUpdates (); + restoreFastUpdates (); +} + + +int kpViewManager::textCursorRow () const +{ + bool handledErrors = false; + if (m_mainWindow) + { + kpDocument *doc = m_mainWindow->document (); + if (doc) + { + kpSelection *sel = doc->selection (); + if (sel && sel->isText ()) + { + if (m_textCursorRow >= (int) sel->textLines ().size ()) + { + #if DEBUG_KP_VIEW_MANAGER && 1 + kdDebug () << "kpViewManager::textCursorRow() row=" + << m_textCursorRow + << endl; + #endif + (const_cast <kpViewManager *> (this))->m_textCursorRow = + sel->textLines ().size () - 1; + } + + handledErrors = true; + } + } + } + + if (!handledErrors) + { + #if DEBUG_KP_VIEW_MANAGER && 1 + kdDebug () << "kpViewManager::textCursorRow() no mw, doc or text sel" << endl; + #endif + (const_cast <kpViewManager *> (this))->m_textCursorRow = -1; + } + + return m_textCursorRow; +} + +int kpViewManager::textCursorCol () const +{ + int row = textCursorRow (); + if (row < 0) + return -1; + + bool handledErrors = false; + if (m_mainWindow) + { + kpDocument *doc = m_mainWindow->document (); + if (doc) + { + kpSelection *sel = doc->selection (); + if (sel && sel->isText ()) + { + if (m_textCursorCol > (int) sel->textLines () [row].length ()) + { + #if DEBUG_KP_VIEW_MANAGER && 1 + kdDebug () << "kpViewManager::textCursorRow() col=" + << m_textCursorCol + << endl; + #endif + (const_cast <kpViewManager *> (this))->m_textCursorCol = + sel->textLines () [row].length (); + } + + handledErrors = true; + } + } + } + + if (!handledErrors) + { + #if DEBUG_KP_VIEW_MANAGER && 1 + kdDebug () << "kpViewManager::textCursorCol() no mw, doc or text sel" << endl; + #endif + (const_cast <kpViewManager *> (this))->m_textCursorCol = -1; + } + + return m_textCursorCol; +} + +void kpViewManager::setTextCursorPosition (int row, int col, bool isUpdateMicroFocusHint) +{ + if (row == m_textCursorRow && col == m_textCursorCol) + return; + + setFastUpdates (); + setQueueUpdates (); + + m_textCursorBlinkState = false; + updateTextCursor (); + + m_textCursorRow = row; + m_textCursorCol = col; + + m_textCursorBlinkState = true; + updateTextCursor (); + + restoreQueueUpdates (); + restoreFastUpdates (); + + if (isUpdateMicroFocusHint) + { + kpDocument *doc = m_mainWindow->document (); + if (!doc) + return; + + kpSelection *sel = doc->selection (); + if (!sel || !sel->isText ()) + return; + + if (m_viewUnderCursor) + { + // TODO: Fix code duplication: kpViewManager::{setTextCursorPosition,updateTextCursor}() & kpView::paintEventDrawSelection() + QPoint topLeft = sel->pointForTextRowCol (m_textCursorRow, m_textCursorCol); + if (topLeft != KP_INVALID_POINT) + { + // TODO: I think you need to consider zooming e.g. try editing + // text at 800% or with focus set to the thumbnail. + // kpSelection/kpDocument works fully in unzoomed + // coordinates unlike the view (which is zoomed and can + // change size). + // + // To fix it here, I think you should call + // m_viewUnderCursor->transformDocToView(QRect). However, + // the rest of the InputMethod support still needs to + // audited for this. + // + // [Bug #27] + m_viewUnderCursor->updateMicroFocusHint(QRect (topLeft.x (), topLeft.y (), 1, sel->textStyle ().fontMetrics ().height ())); + } + } + } +} + + +bool kpViewManager::textCursorBlinkState () const +{ + return m_textCursorBlinkState; +} + +void kpViewManager::setTextCursorBlinkState (bool on) +{ + if (on == m_textCursorBlinkState) + return; + + m_textCursorBlinkState = on; + + updateTextCursor (); +} + + +// protected +void kpViewManager::updateTextCursor () +{ +#if DEBUG_KP_VIEW_MANAGER && 0 + kdDebug () << "kpViewManager::updateTextCursor()" << endl; +#endif + + if (!m_mainWindow) + return; + + kpDocument *doc = m_mainWindow->document (); + if (!doc) + return; + + kpSelection *sel = doc->selection (); + if (!sel || !sel->isText ()) + return; + + // TODO: Fix code duplication: kpViewManager::{setTextCursorPosition,updateTextCursor}() & kpView::paintEventDrawSelection() + QPoint topLeft = sel->pointForTextRowCol (m_textCursorRow, m_textCursorCol); + if (topLeft != KP_INVALID_POINT) + { + setFastUpdates (); + updateViews (QRect (topLeft.x (), topLeft.y (), 1, sel->textStyle ().fontMetrics ().height ())); + restoreFastUpdates (); + } +} + +// protected slot +void kpViewManager::slotTextCursorBlink () +{ +#if DEBUG_KP_VIEW_MANAGER && 0 + kdDebug () << "kpViewManager::slotTextCursorBlink() cursorBlinkState=" + << m_textCursorBlinkState << endl; +#endif + + if (m_textCursorBlinkTimer) + { + m_textCursorBlinkTimer->start (QApplication::cursorFlashTime () / 2, + true/*single shot*/); + } + + updateTextCursor (); + m_textCursorBlinkState = !m_textCursorBlinkState; +} + + +void kpViewManager::setCursor (const QCursor &cursor) +{ + for (QPtrList <kpView>::const_iterator it = m_views.begin (); + it != m_views.end (); + it++) + { + (*it)->setCursor (cursor); + } + + m_cursor = cursor; +} + +void kpViewManager::unsetCursor () +{ + for (QPtrList <kpView>::const_iterator it = m_views.begin (); + it != m_views.end (); + it++) + { + (*it)->unsetCursor (); + } + + m_cursor = QCursor (); +} + + +kpView *kpViewManager::viewUnderCursor (bool usingQt) const +{ + if (!usingQt) + { + kpViewManager *nonConstThis = const_cast <kpViewManager *> (this); + + if (m_viewUnderCursor && nonConstThis->m_views.findRef (m_viewUnderCursor) < 0) + { + kdError () << "kpViewManager::viewUnderCursor(): invalid view" << endl; + nonConstThis->m_viewUnderCursor = 0; + } + + + return m_viewUnderCursor; + } + else + { + for (QPtrList <kpView>::const_iterator it = m_views.begin (); + it != m_views.end (); + it++) + { + if ((*it)->hasMouse ()) + return (*it); + } + + return 0; + } +} + +void kpViewManager::setViewUnderCursor (kpView *view) +{ +#if DEBUG_KP_VIEW_MANAGER && 1 + kdDebug () << "kpViewManager::setViewUnderCursor (" + << (view ? view->name () : "(none)") << ")" + << " old=" << (m_viewUnderCursor ? m_viewUnderCursor->name () : "(none)") + << endl; +#endif + if (view == m_viewUnderCursor) + return; + + m_viewUnderCursor = view; + + if (!m_viewUnderCursor) + { + // Hide the brush if the mouse cursor just left the view + if (m_tempPixmap && m_tempPixmap->isBrush ()) + { + #if DEBUG_KP_VIEW_MANAGER && 1 + kdDebug () << "\thiding brush pixmap since cursor left view" << endl; + #endif + updateViews (m_tempPixmap->rect ()); + } + } + else + { + if (m_mainWindow && m_mainWindow->tool ()) + { + #if DEBUG_KP_VIEW_MANAGER && 1 + kdDebug () << "\tnotify tool that something changed below cursor" << endl; + #endif + m_mainWindow->tool ()->somethingBelowTheCursorChanged (); + } + } +} + + +// public +kpView *kpViewManager::activeView () const +{ + for (QPtrList <kpView>::const_iterator it = m_views.begin (); + it != m_views.end (); + it++) + { + if ((*it)->isActiveWindow ()) + return (*it); + } + + return 0; +} + + +// public +bool kpViewManager::queueUpdates () const +{ + return (m_queueUpdatesCounter > 0); +} + +// public +void kpViewManager::setQueueUpdates () +{ + m_queueUpdatesCounter++; +#if DEBUG_KP_VIEW_MANAGER && 1 + kdDebug () << "kpViewManager::setQueueUpdates() counter=" + << m_queueUpdatesCounter << endl; +#endif +} + +// public +void kpViewManager::restoreQueueUpdates () +{ + m_queueUpdatesCounter--; +#if DEBUG_KP_VIEW_MANAGER && 1 + kdDebug () << "kpViewManager::restoreQueueUpdates() counter=" + << m_queueUpdatesCounter << endl; +#endif + if (m_queueUpdatesCounter < 0) + { + kdError () << "kpViewManager::restoreQueueUpdates() counter=" + << m_queueUpdatesCounter; + } + + if (m_queueUpdatesCounter <= 0) + { + for (QPtrList <kpView>::const_iterator it = m_views.begin (); + it != m_views.end (); + it++) + { + (*it)->updateQueuedArea (); + } + } +} + + +// public +bool kpViewManager::fastUpdates () const +{ + return (m_fastUpdatesCounter > 0); +} + +// public +void kpViewManager::setFastUpdates () +{ + m_fastUpdatesCounter++; +#if DEBUG_KP_VIEW_MANAGER && 0 + kdDebug () << "kpViewManager::setFastUpdates() counter=" + << m_fastUpdatesCounter << endl; +#endif +} + +// public +void kpViewManager::restoreFastUpdates () +{ + m_fastUpdatesCounter--; +#if DEBUG_KP_VIEW_MANAGER && 0 + kdDebug () << "kpViewManager::restoreFastUpdates() counter=" + << m_fastUpdatesCounter << endl; +#endif + if (m_fastUpdatesCounter < 0) + { + kdError () << "kpViewManager::restoreFastUpdates() counter=" + << m_fastUpdatesCounter; + } +} + + +void kpViewManager::updateView (kpView *v) +{ + updateView (v, QRect (0, 0, v->width (), v->height ())); +} + +void kpViewManager::updateView (kpView *v, const QRect &viewRect) +{ + if (!queueUpdates ()) + { + if (fastUpdates ()) + v->repaint (viewRect, false/*no erase*/); + else + v->update (viewRect); + } + else + v->addToQueuedArea (viewRect); +} + +void kpViewManager::updateView (kpView *v, int x, int y, int w, int h) +{ + updateView (v, QRect (x, y, w, h)); +} + +void kpViewManager::updateView (kpView *v, const QRegion &viewRegion) +{ + if (!queueUpdates ()) + { + if (fastUpdates ()) + v->repaint (viewRegion, false/*no erase*/); + else + v->update (viewRegion.boundingRect ()); + } + else + v->addToQueuedArea (viewRegion); +} + +void kpViewManager::updateViewRectangleEdges (kpView *v, const QRect &viewRect) +{ + if (viewRect.height () <= 0 || viewRect.width () <= 0) + return; + + // Top line + updateView (v, QRect (viewRect.x (), viewRect.y (), + viewRect.width (), 1)); + + if (viewRect.height () >= 2) + { + // Bottom line + updateView (v, QRect (viewRect.x (), viewRect.bottom (), + viewRect.width (), 1)); + + if (viewRect.height () > 2) + { + // Left line + updateView (v, QRect (viewRect.x (), viewRect.y () + 1, + 1, viewRect.height () - 2)); + + if (viewRect.width () >= 2) + { + // Right line + updateView (v, QRect (viewRect.right (), viewRect.y () + 1, + 1, viewRect.height () - 2)); + } + } + } +} + + +void kpViewManager::updateViews () +{ + kpDocument *doc = document (); + if (doc) + updateViews (QRect (0, 0, doc->width (), doc->height ())); +} + +void kpViewManager::updateViews (const QRect &docRect) +{ +#if DEBUG_KP_VIEW_MANAGER && 0 + kdDebug () << "kpViewManager::updateViews (" << docRect << ")" << endl; +#endif + + for (QPtrList <kpView>::const_iterator it = m_views.begin (); + it != m_views.end (); + it++) + { + kpView *view = *it; + + #if DEBUG_KP_VIEW_MANAGER && 0 + kdDebug () << "\tupdating view " << view->name () << endl; + #endif + if (view->zoomLevelX () % 100 == 0 && view->zoomLevelY () % 100 == 0) + { + #if DEBUG_KP_VIEW_MANAGER && 0 + kdDebug () << "\t\tviewRect=" << view->transformDocToView (docRect) << endl; + #endif + updateView (view, view->transformDocToView (docRect)); + } + else + { + QRect viewRect = view->transformDocToView (docRect); + + int diff = qRound (double (QMAX (view->zoomLevelX (), view->zoomLevelY ())) / 100.0) + 1; + + QRect newRect = QRect (viewRect.x () - diff, + viewRect.y () - diff, + viewRect.width () + 2 * diff, + viewRect.height () + 2 * diff) + .intersect (QRect (0, 0, view->width (), view->height ())); + + #if DEBUG_KP_VIEW_MANAGER && 0 + kdDebug () << "\t\tviewRect (+compensate)=" << newRect << endl; + #endif + updateView (view, newRect); + } + } +} + +void kpViewManager::updateViews (int x, int y, int w, int h) +{ + updateViews (QRect (x, y, w, h)); +} + + +void kpViewManager::adjustViewsToEnvironment () +{ +#if DEBUG_KP_VIEW_MANAGER && 1 + kdDebug () << "kpViewManager::adjustViewsToEnvironment()" + << " numViews=" << m_views.count () + << endl; +#endif + for (QPtrList <kpView>::const_iterator it = m_views.begin (); + it != m_views.end (); + it++) + { + kpView *view = *it; + + #if DEBUG_KP_VIEW_MANAGER && 1 + kdDebug () << "\tview: " << view->name () + << endl; + #endif + view->adjustToEnvironment (); + } +} + +#include <kpviewmanager.moc> + diff --git a/kolourpaint/kpviewmanager.h b/kolourpaint/kpviewmanager.h new file mode 100644 index 00000000..c6ea1ef0 --- /dev/null +++ b/kolourpaint/kpviewmanager.h @@ -0,0 +1,220 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 __kpviewmanager_h__ +#define __kpviewmanager_h__ + +#include <qcursor.h> +#include <qobject.h> +#include <qptrlist.h> +#include <qrect.h> + + +class QPixmap; +class QRect; +class QTimer; + +class kpDocument; +class kpView; +class kpMainWindow; +class kpTempPixmap; + +class kpViewManager : public QObject +{ +Q_OBJECT + +public: + kpViewManager (kpMainWindow *mainWindow); + ~kpViewManager (); + + + // + // Registering views + // + + void registerView (kpView *view); + void unregisterView (kpView *view); + void unregisterAllViews (); + + + // + // Temp Pixmap + // + + const kpTempPixmap *tempPixmap () const; + void setTempPixmap (const kpTempPixmap &tempPixmap); + void invalidateTempPixmap (); + + + // + // Selections + // + + bool selectionBorderVisible () const; + void setSelectionBorderVisible (bool yes = true); + + bool selectionBorderFinished () const; + void setSelectionBorderFinished (bool yes = true); + + + // + // Text Cursor + // + + bool textCursorEnabled () const; + void setTextCursorEnabled (bool yes = true); + + int textCursorRow () const; + int textCursorCol () const; + void setTextCursorPosition (int row, int col, bool isUpdateMicroFocusHint = false); + + bool textCursorBlinkState () const; + void setTextCursorBlinkState (bool on = true); + +protected: + void updateTextCursor (); + + QTimer *m_textCursorBlinkTimer; + int m_textCursorRow, m_textCursorCol; + bool m_textCursorBlinkState; + +protected slots: + void slotTextCursorBlink (); + +public: + + // + // Cursors + // + + void setCursor (const QCursor &cursor); + void unsetCursor (); + + + // + // View + // + + kpView *viewUnderCursor (bool usingQt = false) const; + + // + // QWidget::hasMouse() is unreliable: + // + // "bool QWidget::hasMouse () const + // ... See the "underMouse" property for details. + // . + // . + // . + // bool underMouse + // ... This value is not updated properly during drag and drop operations." + // + // i.e. it's possible that hasMouse() returns false in a mousePressEvent()! + // + // This hack needs to be called from kpView so that viewUnderCursor() works + // as a reasonable replacement (although there is at least one case where + // it still won't work - just after a fake drag onto the view). + // + void setViewUnderCursor (kpView *view); + + + // Returns a pointer to the view that has keyboard focus or else, 0 + // TODO: rename to "anActiveView()" or "aViewIsActive()" as more than + // 1 view can be active at the same time? + kpView *activeView () const; + + + // Specifies whether KolourPaint will queue _all_ paint events + // (generated by you or the window system), until the + // corresponding call to restoreQueueUpdates(). Use this + // before multiple, big, non-interactive changes to the + // document to eliminate virtually all flicker. + // + // This is better than QWidget::setUpdatesEnabled() because + // restoreQueueUpdates() automatically restores only the regions + // of the views that need to be repainted, per view. + bool queueUpdates () const; + void setQueueUpdates (); + void restoreQueueUpdates (); + + // Controls behaviour of updateViews(): + // + // Slow: Let Qt buffer paint events via QWidget::update(). + // Results in less flicker. Paint events are probably merged + // so long-term efficiency is increased at the expense of + // reduced responsiveness (default). + // Fast: Force Qt to redraw immediately. No paint events + // are merged so there is great potential for flicker, + // if used inappropriately. Use this when the redraw + // area is small and KolourPaint's responsiveness is + // critical. Continual use of this mode can result in + // unnecessary redraws and incredibly slugish performance. + bool fastUpdates () const; + void setFastUpdates (); + void restoreFastUpdates (); + +private: + int m_queueUpdatesCounter, m_fastUpdatesCounter; + +public slots: + // updating views + void updateView (kpView *v); + void updateView (kpView *v, const QRect &viewRect); + void updateView (kpView *v, int x, int y, int w, int h); + void updateView (kpView *v, const QRegion &viewRegion); + void updateViewRectangleEdges (kpView *v, const QRect &viewRect); + + void updateViews (); + void updateViews (const QRect &docRect); + void updateViews (int x, int y, int w, int h); + + void adjustViewsToEnvironment (); + +private: + // don't use + kpViewManager (const kpViewManager &); + bool operator= (const kpViewManager &); + + kpDocument *document () const; + + kpMainWindow *m_mainWindow; + QPtrList <kpView> m_views; + QCursor m_cursor; + + kpTempPixmap *m_tempPixmap; + kpView *m_viewUnderCursor; + + bool m_selectionBorderVisible; + bool m_selectionBorderFinished; + + // 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 kpViewManagerPrivate *d; +}; + +#endif // __kpviewmanager_h__ diff --git a/kolourpaint/kpviewscrollablecontainer.cpp b/kolourpaint/kpviewscrollablecontainer.cpp new file mode 100644 index 00000000..637f71b7 --- /dev/null +++ b/kolourpaint/kpviewscrollablecontainer.cpp @@ -0,0 +1,1390 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <qcursor.h> +#include <qpainter.h> +#include <qpen.h> +#include <qpixmap.h> +#include <qtimer.h> + +#include <kdebug.h> +#include <klocale.h> + +#include <kpdefs.h> +#include <kppixmapfx.h> +#include <kpview.h> +#include <kpwidgetmapper.h> + + +// (Pulled from out of Thurston's hat) +static const int DragScrollLeftTopMargin = 0; +static const int DragScrollRightBottomMargin = 16; // scrollbarish +static const int DragScrollInterval = 1000 / 10; +static const int DragScrollInitialInterval = DragScrollInterval * 2; +static const int DragScrollNumPixels = 5; +static const int DragDistanceFromRectMaxFor1stMultiplier = 50; +static const int DragDistanceFromRectMaxFor2ndMultiplier = 100; + +static const int GripSize = 7; +static const int GripHandleSize = 7; + + +kpGrip::kpGrip (GripType type, + QWidget *parent, const char *name) + : QWidget (parent, name), + 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 + + updatePixmap (); +} + +kpGrip::~kpGrip () +{ +} + + +// public +kpGrip::GripType kpGrip::type () const +{ + return m_type; +} + + +// public static +const QCursor &kpGrip::cursorForType (GripType type) +{ + switch (type) + { + case Bottom: + return Qt::sizeVerCursor; + break; // one day you'll forget + + case Right: + return Qt::sizeHorCursor; + break; // one day you'll forget + + case BottomRight: + return Qt::sizeFDiagCursor; + break; // one day you'll forget + } + + return Qt::arrowCursor; +} + + +// public +QRect kpGrip::hotRect (bool toGlobal) const +{ + QRect ret; + + switch (m_type) + { + case Bottom: + { + const int handleX = (width () - GripHandleSize) / 2; + ret = QRect (handleX, 0, + GripHandleSize, height ()); + break; + } + case Right: + { + const int handleY = (height () - GripHandleSize) / 2; + ret = QRect (0, handleY, + width (), GripHandleSize); + break; + } + case BottomRight: + // pixmap all opaque + ret = rect (); + break; + + default: + return QRect (); + } + + return (toGlobal ? QRect (mapToGlobal (ret.topLeft ()), + mapToGlobal (ret.bottomRight ())) + : ret); +} + + +// 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::updatePixmap () +{ +#if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER + kdDebug () << "kpGrip::updatePixmap() rect=" << rect () << endl; +#endif + if (width () <= 0 || height () <= 0) + return; + + QPixmap pixmap (width (), height ()); + pixmap.fill (colorGroup ().highlight ()); + kpPixmapFX::ensureTransparentAt (&pixmap, pixmap.rect ()); + const QRect hr = hotRect (); +#if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER + kdDebug () << "\thotRect=" << hr << endl; +#endif + if (hr.isValid ()) + kpPixmapFX::ensureOpaqueAt (&pixmap, hr); + + setBackgroundPixmap (pixmap); + if (pixmap.mask ()) + setMask (*pixmap.mask ()); +} + + +// protected +void kpGrip::cancel () +{ +#if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER + kdDebug () << "kpGrip::cancel()" << endl; +#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; + + releaseKeyboard (); + 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->stateAfter () & Qt::MouseButtonMask) == Qt::LeftButton) + { + m_startPoint = e->pos (); + m_currentPoint = e->pos (); + emit beganDraw (); + grabKeyboard (); + + 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 QPoint (((m_type & Right) ? point.x () - m_startPoint.x () : 0), + ((m_type & 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 & Right) ? point.x () - m_startPoint.x () : 0), + ((m_type & Bottom) ? point.y () - m_startPoint.y () : 0), + dueToDragScroll); +} + +// protected virtual [base QWidget] +void kpGrip::mouseMoveEvent (QMouseEvent *e) +{ +#if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER + kdDebug () << "kpGrip::mouseMoveEvent() m_startPoint=" << m_startPoint + << " stateAfter=" << e->stateAfter () + << endl; +#endif + + if (m_startPoint == KP_INVALID_POINT) + { + if ((e->stateAfter () & 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 + kdDebug () << "kpGrip::mouseReleaseEvent() m_startPoint=" << m_startPoint + << " stateAfter=" << e->stateAfter () + << endl; +#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; + + releaseKeyboard (); + emit endedDraw ((m_type & Right) ? dx : 0, + (m_type & Bottom) ? dy : 0); + } + + if ((e->stateAfter () & Qt::MouseButtonMask) == 0) + { + m_shouldReleaseMouseButtons = false; + setUserMessage (QString::null); + setCursor (cursorForType (m_type)); + + releaseKeyboard (); + emit releasedAllButtons (); + } +} + + +// protected virtual [base QWidget] +void kpGrip::resizeEvent (QResizeEvent *) +{ +#if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER + kdDebug () << "kpGrip::resizeEvent()" << endl; +#endif + updatePixmap (); +} + + +// protected virtual [base QWidget] +void kpGrip::enterEvent (QEvent * /*e*/) +{ +#if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER + kdDebug () << "kpGrip::enterEvent()" + << " m_startPoint=" << m_startPoint + << " shouldReleaseMouseButtons=" + << m_shouldReleaseMouseButtons << endl; +#endif + + if (m_startPoint == KP_INVALID_POINT && + !m_shouldReleaseMouseButtons) + { + #if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER + kdDebug () << "\tsending message" << endl; + #endif + setUserMessage (haventBegunDrawUserMessage ()); + } +} + +// protected virtual [base QWidget] +void kpGrip::leaveEvent (QEvent * /*e*/) +{ +#if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER + kdDebug () << "kpGrip::leaveEvent()" + << " m_startPoint=" << m_startPoint + << " shouldReleaseMouseButtons=" + << m_shouldReleaseMouseButtons << endl; +#endif + if (m_startPoint == KP_INVALID_POINT && + !m_shouldReleaseMouseButtons) + { + setUserMessage (QString::null); + } +} + + +// protected virtual [base QWidget] +void kpGrip::paintEvent (QPaintEvent *e) +{ +#if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER && 0 + kdDebug () << "kpGrip::paintEvent(" << e->rect () << ")" << endl; +#endif + QWidget::paintEvent (e); +} + + +// TODO: Are we checking for m_view == 0 often enough? +kpViewScrollableContainer::kpViewScrollableContainer (kpMainWindow *parent, + const char *name) + : QScrollView ((QWidget *) parent, name, Qt::WStaticContents | Qt::WNoAutoErase), + m_mainWindow (parent), + m_contentsXSoon (-1), m_contentsYSoon (-1), + m_view (0), + m_bottomGrip (new kpGrip (kpGrip::Bottom, viewport (), "Bottom Grip")), + m_rightGrip (new kpGrip (kpGrip::Right, viewport (), "Right Grip")), + m_bottomRightGrip (new kpGrip (kpGrip::BottomRight, viewport (), "BottomRight Grip")), + m_docResizingGrip (0), + 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) + +{ + m_bottomGrip->setFixedHeight (GripSize); + m_bottomGrip->hide (); + addChild (m_bottomGrip); + connectGripSignals (m_bottomGrip); + + m_rightGrip->setFixedWidth (GripSize); + m_rightGrip->hide (); + addChild (m_rightGrip); + connectGripSignals (m_rightGrip); + + m_bottomRightGrip->setFixedSize (GripSize, GripSize); + m_bottomRightGrip->hide (); + addChild (m_bottomRightGrip); + connectGripSignals (m_bottomRightGrip); + + + connect (this, SIGNAL (contentsMoving (int, int)), + this, SLOT (slotContentsMoving (int, int))); + + connect (m_dragScrollTimer, SIGNAL (timeout ()), + this, SLOT (slotDragScroll ())); +} + +kpViewScrollableContainer::~kpViewScrollableContainer () +{ +} + + +// public +int kpViewScrollableContainer::contentsXSoon () +{ + if (m_contentsXSoon < 0) + return contentsX (); + else + return m_contentsXSoon; +} + +// public +int kpViewScrollableContainer::contentsYSoon () +{ + if (m_contentsYSoon < 0) + return contentsY (); + else + return m_contentsYSoon; +} + + +// protected +void kpViewScrollableContainer::connectGripSignals (kpGrip *grip) +{ + connect (grip, SIGNAL (beganDraw ()), + this, SLOT (slotGripBeganDraw ())); + connect (grip, SIGNAL (continuedDraw (int, int, bool)), + this, SLOT (slotGripContinuedDraw (int, int, bool))); + connect (grip, SIGNAL (cancelledDraw ()), + this, SLOT (slotGripCancelledDraw ())); + connect (grip, SIGNAL (endedDraw (int, int)), + this, SLOT (slotGripEndedDraw (int, int))); + + connect (grip, SIGNAL (statusMessageChanged (const QString &)), + this, SLOT (slotGripStatusMessageChanged (const QString &))); + + connect (grip, SIGNAL (releasedAllButtons ()), + this, SLOT (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 + kdDebug () << "kpViewScrollableContainer::clearStatusMessage()" << endl; +#endif + m_bottomRightGrip->setUserMessage (QString::null); + m_bottomGrip->setUserMessage (QString::null); + m_rightGrip->setUserMessage (QString::null); +} + + +// protected +QSize kpViewScrollableContainer::newDocSize (int viewDX, int viewDY) const +{ + if (!m_view) + return QSize (); + + if (!docResizingGrip ()) + return QSize (); + + const int docX = (int) m_view->transformViewToDocX (m_view->width () + viewDX); + const int docY = (int) m_view->transformViewToDocY (m_view->height () + viewDY); + + return QSize (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 = 0; +} + +// 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); + else + 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); + else + return 1; +} + + +// protected +QRect kpViewScrollableContainer::bottomResizeLineRect () const +{ + if (m_resizeRoundedLastViewX < 0 || m_resizeRoundedLastViewY < 0) + return QRect (); + + return QRect (QPoint (0, + m_resizeRoundedLastViewY), + QPoint (m_resizeRoundedLastViewX - 1, + m_resizeRoundedLastViewY + bottomResizeLineWidth () - 1)); +} + +// protected +QRect kpViewScrollableContainer::rightResizeLineRect () const +{ + if (m_resizeRoundedLastViewX < 0 || m_resizeRoundedLastViewY < 0) + return QRect (); + + return QRect (QPoint (m_resizeRoundedLastViewX, + 0), + QPoint (m_resizeRoundedLastViewX + rightResizeLineWidth () - 1, + m_resizeRoundedLastViewY - 1)); +} + +// protected +QRect kpViewScrollableContainer::bottomRightResizeLineRect () const +{ + if (m_resizeRoundedLastViewX < 0 || m_resizeRoundedLastViewY < 0) + return QRect (); + + return QRect (QPoint (m_resizeRoundedLastViewX, + m_resizeRoundedLastViewY), + QPoint (m_resizeRoundedLastViewX + rightResizeLineWidth () - 1, + m_resizeRoundedLastViewY + bottomResizeLineWidth () - 1)); +} + + +// TODO: are these 2 correct? Remember that viewport()->x() == 1, viewport()->y() == 1 + +// protected +QPoint kpViewScrollableContainer::mapViewToViewport (const QPoint &viewPoint) +{ + return viewPoint - QPoint (contentsX (), contentsY ()); +} + +// protected +QRect kpViewScrollableContainer::mapViewToViewport (const QRect &viewRect) +{ + if (!viewRect.isValid ()) + return QRect (); + + QRect ret = viewRect; + ret.moveBy (-contentsX (), -contentsY ()); + return ret; +} + + +// protected +QRect kpViewScrollableContainer::mapViewportToGlobal (const QRect &viewportRect) +{ + return kpWidgetMapper::toGlobal (viewport (), viewportRect); +} + +// protected +QRect kpViewScrollableContainer::mapViewToGlobal (const QRect &viewRect) +{ + return mapViewportToGlobal (mapViewToViewport (viewRect)); +} + + +// protected +void kpViewScrollableContainer::repaintWidgetAtResizeLineViewRect ( + QWidget *widget, const QRect &resizeLineViewRect) +{ + const QRect resizeLineGlobalRect = mapViewToGlobal (resizeLineViewRect); + const QRect widgetGlobalRect = kpWidgetMapper::toGlobal (widget, + widget->rect ()); + + const QRect redrawGlobalRect = + resizeLineGlobalRect.intersect (widgetGlobalRect); + + const QRect redrawWidgetRect = + kpWidgetMapper::fromGlobal (widget, redrawGlobalRect); + + + if (redrawWidgetRect.isValid ()) + { + // TODO: should be "!widget->testWFlags (Qt::WRepaintNoErase)" + // but for some reason, doesn't work for viewport(). + const bool erase = !dynamic_cast <kpView *> (widget); + widget->repaint (redrawWidgetRect, erase); + } +} + +// protected +void kpViewScrollableContainer::repaintWidgetAtResizeLines (QWidget *widget) +{ + repaintWidgetAtResizeLineViewRect (widget, rightResizeLineRect ()); + repaintWidgetAtResizeLineViewRect (widget, bottomResizeLineRect ()); + repaintWidgetAtResizeLineViewRect (widget, bottomRightResizeLineRect ()); +} + +// protected +void kpViewScrollableContainer::eraseResizeLines () +{ + if (m_resizeRoundedLastViewX >= 0 && m_resizeRoundedLastViewY >= 0) + { + repaintWidgetAtResizeLines (viewport ()); + repaintWidgetAtResizeLines (m_view); + + repaintWidgetAtResizeLines (m_bottomGrip); + repaintWidgetAtResizeLines (m_rightGrip); + repaintWidgetAtResizeLines (m_bottomRightGrip); + } +} + + +// protected +void kpViewScrollableContainer::drawResizeLines () +{ +#if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER && 0 + kdDebug () << "kpViewScrollableContainer::drawResizeLines()" + << " lastViewX=" << m_resizeRoundedLastViewX + << " lastViewY=" << m_resizeRoundedLastViewY + << endl; +#endif + + + QPainter p (viewport (), true/*unclipped*/); + p.setRasterOp (Qt::NotROP); + + const QRect rightRect = rightResizeLineRect (); + if (rightRect.isValid ()) + p.fillRect (mapViewToViewport (rightRect), Qt::white); + + const QRect bottomRect = bottomResizeLineRect (); + if (bottomRect.isValid ()) + p.fillRect (mapViewToViewport (bottomRect), Qt::white); + + const QRect bottomRightRect = bottomRightResizeLineRect (); + if (bottomRightRect.isValid ()) + p.fillRect (mapViewToViewport (bottomRightRect), Qt::white); + + p.end (); +} + + +// protected +void kpViewScrollableContainer::updateResizeLines (int viewX, int viewY, + int viewDX, int viewDY) +{ +#if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER && 0 + kdDebug () << "kpViewScrollableContainer::updateResizeLines(" + << viewX << "," << viewY << ")" + << " oldViewX=" << m_resizeRoundedLastViewX + << " oldViewY=" << m_resizeRoundedLastViewY + << " viewDX=" << viewDX + << " viewDY=" << viewDY + << endl; +#endif + + eraseResizeLines (); + + + if (viewX >= 0 && viewY >= 0) + { + m_resizeRoundedLastViewX = (int) m_view->transformDocToViewX ((int) m_view->transformViewToDocX (viewX)); + m_resizeRoundedLastViewY = (int) m_view->transformDocToViewY ((int) m_view->transformViewToDocY (viewY)); + + m_resizeRoundedLastViewDX = viewDX; + m_resizeRoundedLastViewDY = viewDY; + } + else + { + m_resizeRoundedLastViewX = -1; + m_resizeRoundedLastViewY = -1; + + m_resizeRoundedLastViewDX = 0; + m_resizeRoundedLastViewDY = 0; + } + + // TODO: This is suboptimal since if another window pops up on top of + // KolourPaint then disappears, the lines are not redrawn + // (although this doesn't happen very frequently since we grab the + // keyboard and mouse when resizing): + // + // e.g. sleep 5 && gedit & sleep 10 && killall gedit + // + // Should be done in the paintEvent's of every child of the + // scrollview. + drawResizeLines (); +} + + +// protected slot +void kpViewScrollableContainer::slotGripBeganDraw () +{ + if (!m_view) + return; + + 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 + kdDebug () << "kpViewScrollableContainer::slotGripContinuedDraw(" + << viewDX << "," << viewDY << ") size=" + << newDocSize (viewDX, viewDY) + << " dueToDragScroll=" << dueToDragScroll + << endl; +#endif + + if (!m_view) + return; + + if (!dueToDragScroll && + beginDragScroll (QPoint (), QPoint (), m_view->zoomLevelX ())) + { + const QPoint newViewDeltaPoint = docResizingGrip ()->viewDeltaPoint (); + viewDX = newViewDeltaPoint.x (); + viewDY = newViewDeltaPoint.y (); + #if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER + kdDebug () << "\tdrag scrolled - new view delta point=" + << newViewDeltaPoint + << endl; + #endif + } + + m_haveMovedFromOriginalDocSize = true; + + updateResizeLines (QMAX (1, QMAX (m_view->width () + viewDX, (int) m_view->transformDocToViewX (1))), + QMAX (1, QMAX (m_view->height () + viewDY, (int) 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 (); +} + +// protected slot +void kpViewScrollableContainer::slotGripEndedDraw (int viewDX, int viewDY) +{ +#if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER + kdDebug () << "kpViewScrollableContainer::slotGripEndedDraw(" + << viewDX << "," << viewDY << ") size=" + << newDocSize (viewDX, viewDY) + << endl; +#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 (); +} + + +// 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 + kdDebug () << "kpViewScrollabelContainer::recalculateStatusMessage()" << endl; + kdDebug () << "\tQCursor::pos=" << QCursor::pos () + << " global visibleRect=" + << kpWidgetMapper::toGlobal (this, + QRect (0, 0, visibleWidth (), visibleHeight ())) + << " brGrip.hotRect=" << m_bottomRightGrip->hotRect (true) + << " bGrip.hotRect=" << m_bottomGrip->hotRect (true) + << " rGrip.hotRect=" << m_rightGrip->hotRect (true) + << endl; +#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 cancelling over any + // grip. + // + if (kpWidgetMapper::toGlobal (this, + QRect (0, 0, visibleWidth (), visibleHeight ())) + .contains (QCursor::pos ())) + { + if (m_bottomRightGrip->isShown () && + m_bottomRightGrip->hotRect (true/*to global*/) + .contains (QCursor::pos ())) + { + m_bottomRightGrip->setUserMessage (i18n ("Left drag the handle to resize the image.")); + } + else if (m_bottomGrip->isShown () && + m_bottomGrip->hotRect (true/*to global*/) + .contains (QCursor::pos ())) + { + m_bottomGrip->setUserMessage (i18n ("Left drag the handle to resize the image.")); + } + else if (m_rightGrip->isShown () && + m_rightGrip->hotRect (true/*to global*/) + .contains (QCursor::pos ())) + { + m_rightGrip->setUserMessage (i18n ("Left drag the handle to resize the image.")); + } + else + { + clearStatusMessage (); + } + } + else + { + clearStatusMessage (); + } +} + + +// protected slot +void kpViewScrollableContainer::slotContentsMoving (int x, int y) +{ +#if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER + kdDebug () << "kpViewScrollableContainer::slotContentsMoving(" + << x << "," << y << ")" + << " contentsX=" << contentsX () + << " contentsY=" << contentsY () << endl; +#endif + + m_contentsXSoon = x, m_contentsYSoon = y; + emit contentsMovingSoon (m_contentsXSoon, m_contentsYSoon); + + // Reduce flicker - don't let QScrollView scroll to-be-erased lines + eraseResizeLines (); + + QTimer::singleShot (0, this, SLOT (slotContentsMoved ())); +} + +// protected slot +void kpViewScrollableContainer::slotContentsMoved () +{ + m_contentsXSoon = m_contentsYSoon = -1; + + kpGrip *grip = docResizingGrip (); +#if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER + kdDebug () << "kpViewScrollableContainer::slotContentsMoved()" + << " grip=" << grip + << " contentsX=" << contentsX () + << " contentsY=" << contentsY () << endl; +#endif + if (!grip) + return; + + grip->mouseMovedTo (grip->mapFromGlobal (QCursor::pos ()), + true/*moved due to drag scroll*/); +} + + +// protected +void kpViewScrollableContainer::disconnectViewSignals () +{ + disconnect (m_view, SIGNAL (sizeChanged (const QSize &)), + this, SLOT (updateGrips ())); + disconnect (m_view, SIGNAL (destroyed ()), + this, SLOT (slotViewDestroyed ())); +} + +// protected +void kpViewScrollableContainer::connectViewSignals () +{ + connect (m_view, SIGNAL (sizeChanged (const QSize &)), + this, SLOT (updateGrips ())); + connect (m_view, SIGNAL (destroyed ()), + this, SLOT (slotViewDestroyed ())); +} + + +// public virtual [base QScrollView] +void kpViewScrollableContainer::addChild (QWidget *widget, int x, int y) +{ +#if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER + kdDebug () << "kpViewScrollableContainer::addChild(" << widget + << "," << x << "," << y << endl; +#endif + + QScrollView::addChild (widget, x, y); + + kpView *view = dynamic_cast <kpView *> (widget); +#if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER + kdDebug () << "\tcast to kpView: " << view << endl; +#endif + if (view) + { + setView (view); + } +} + + +// public +kpView *kpViewScrollableContainer::view () const +{ + return m_view; +} + +// public +void kpViewScrollableContainer::setView (kpView *view) +{ +#if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER + kdDebug () << "kpViewScrollableContainer::setView(" << view << ")" << endl; +#endif + + if (m_view == view) + return; + + if (m_view) + { + disconnectViewSignals (); + } + + m_view = view; + + updateGrips (); + + if (m_view) + { + connectViewSignals (); + } +} + + +// public slot +void kpViewScrollableContainer::updateGrips () +{ +#if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER + kdDebug () << "kpViewScrollableContainer::updateGrips() m_view=" + << m_view << endl; +#endif + + if (m_view) + { + m_bottomGrip->setFixedWidth (m_view->width ()); + moveChild (m_bottomGrip, 0, m_view->height ()); + + m_rightGrip->setFixedHeight (m_view->height ()); + moveChild (m_rightGrip, m_view->width (), 0); + + moveChild (m_bottomRightGrip, m_view->width (), m_view->height ()); + } + + m_bottomGrip->setShown (bool (m_view)); + m_rightGrip->setShown (bool (m_view)); + m_bottomRightGrip->setShown (bool (m_view)); + +#if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER + kdDebug () << "\tcontentsRect=" << contentsRect () + << " visibleRect=" << visibleRect () + << " viewportRect=" << viewport ()->rect () + << endl; +#endif + + if (m_view) + { + resizeContents (m_view->width () + m_rightGrip->width (), + m_view->height () + m_bottomGrip->height ()); + } + else + { + resizeContents (0, 0); + } + + recalculateStatusMessage (); +} + +// protected slot +void kpViewScrollableContainer::slotViewDestroyed () +{ +#if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER + kdDebug () << "kpViewScrollableContainer::slotViewDestroyed() m_view=" + << m_view << endl; +#endif + + m_view = 0; + updateGrips (); +} + + +// public slot +bool kpViewScrollableContainer::beginDragScroll (const QPoint &/*docPoint*/, + const QPoint &/*lastDocPoint*/, + int zoomLevel, + bool *didSomething) +{ + if (didSomething) + *didSomething = false; + + m_zoomLevel = zoomLevel; + + const QPoint p = mapFromGlobal (QCursor::pos ()); + +#if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER + kdDebug () << "kpViewScrollableContainer::beginDragScroll() p=" << p + << " dragScrollTimerRunOnce=" << m_scrollTimerRunOnce + << endl; +#endif + + 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 (const QPoint &docPoint, + const QPoint &lastDocPoint, + int zoomLevel) +{ + return beginDragScroll (docPoint, lastDocPoint, zoomLevel, + 0/*don't want scrolled notification*/); +} + + +// public slot +bool kpViewScrollableContainer::endDragScroll () +{ +#if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER + kdDebug () << "kpViewScrollableContainer::endDragScroll()" << endl; +#endif + + if (m_dragScrollTimer->isActive ()) + { + m_dragScrollTimer->stop (); + return true; + } + else + { + return false; + } +} + + +static const int distanceFromRectToMultiplier (int dist) +{ + if (dist < 0) + return 0; + else if (dist < DragDistanceFromRectMaxFor1stMultiplier) + return 1; + else if (dist < DragDistanceFromRectMaxFor2ndMultiplier) + return 2; + else + 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 ()); + +#if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER + kdDebug () << "kpViewScrollableContainer::slotDragScroll()" + << " noDragScrollRect=" << rect + << " pos=" << pos + << " contentsX=" << contentsX () + << " contentsY=" << contentsY () << endl; +#endif + + 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 ()); + } + +#if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER && 0 + kdDebug () << "kpViewScrollableContainer::slotDragScroll()" + << " dx=" << dx << " * " << dxMultiplier + << " dy=" << dy << " * " << dyMultiplier + << " zoomLevel=" << m_zoomLevel + << endl; +#endif + + dx *= dxMultiplier;// * QMAX (1, m_zoomLevel / 100); + dy *= dyMultiplier;// * QMAX (1, m_zoomLevel / 100); + + if (dx || dy) + { + const int oldContentsX = contentsX (), + oldContentsY = contentsY (); + + scrollBy (dx, dy); + + #if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER && 1 + kdDebug () << "\tafter scrollBy():" + << " contentsX=" << contentsX () + << " contentsY=" << contentsY () << endl; + #endif + + scrolled = (oldContentsX != contentsX () || + oldContentsY != contentsY ()); + + if (scrolled) + { + QRegion region = QRect (contentsX (), contentsY (), + visibleWidth (), visibleHeight ()); + region -= QRect (oldContentsX, oldContentsY, + visibleWidth (), visibleHeight ()); + + // Repaint newly exposed region immediately to reduce tearing + // of scrollView. + m_view->repaint (region, false/*no erase*/); + } + } + + + m_dragScrollTimer->changeInterval (DragScrollInterval); + m_scrollTimerRunOnce = true; + + + if (didSomething) + *didSomething = scrolled; + + return scrolled; +} + +// protected virtual [base QScrollView] +void kpViewScrollableContainer::contentsDragMoveEvent (QDragMoveEvent *e) +{ +#if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER + kdDebug () << "kpViewScrollableContainer::contentsDragMoveEvent" + << e->pos () + << endl; +#endif + + QScrollView::contentsDragMoveEvent (e); +} + +// protected slot +bool kpViewScrollableContainer::slotDragScroll () +{ + return slotDragScroll (0/*don't want scrolled notification*/); +} + + +// protected virtual [base QScrollView] +void kpViewScrollableContainer::contentsMouseMoveEvent (QMouseEvent *e) +{ +#if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER + kdDebug () << "kpViewScrollableContainer::contentsMouseMoveEvent" + << e->pos () + << endl; +#endif + + QScrollView::contentsMouseMoveEvent (e); +} + +// protected virtual [base QScrollView] +void kpViewScrollableContainer::mouseMoveEvent (QMouseEvent *e) +{ +#if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER + kdDebug () << "kpViewScrollableContainer::mouseMoveEvent" + << e->pos () + << endl; +#endif + + QScrollView::mouseMoveEvent (e); +} + + +// protected virtual [base QScrollView] +void kpViewScrollableContainer::contentsWheelEvent (QWheelEvent *e) +{ + e->ignore (); + + if (m_view) + m_view->wheelEvent (e); + + if (!e->isAccepted ()) + QScrollView::contentsWheelEvent (e); +} + + +QRect kpViewScrollableContainer::noDragScrollRect () const +{ + return QRect (DragScrollLeftTopMargin, DragScrollLeftTopMargin, + width () - DragScrollLeftTopMargin - DragScrollRightBottomMargin, + height () - DragScrollLeftTopMargin - DragScrollRightBottomMargin); +} + +// protected virtual [base QScrollView] +bool kpViewScrollableContainer::eventFilter (QObject *watchedObject, QEvent *event) +{ + return QScrollView::eventFilter (watchedObject, event); +} + +// protected virtual [base QScrollView] +void kpViewScrollableContainer::viewportPaintEvent (QPaintEvent *e) +{ +#if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER + kdDebug () << "kpViewScrollableContainer::viewportPaintEvent(" + << e->rect () + << ")" << endl; +#endif + + QScrollView::viewportPaintEvent (e); +} + +// protected virtual [base QFrame] +void kpViewScrollableContainer::paintEvent (QPaintEvent *e) +{ +#if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER && 0 + kdDebug () << "kpViewScrollableContainer::paintEvent(" + << e->rect () + << ")" << endl; +#endif + + QScrollView::paintEvent (e); +} + +// protected virtual [base QScrollView] +void kpViewScrollableContainer::resizeEvent (QResizeEvent *e) +{ + QScrollView::resizeEvent (e); + + emit resized (); +} + + +#include <kpviewscrollablecontainer.moc> diff --git a/kolourpaint/kpviewscrollablecontainer.h b/kolourpaint/kpviewscrollablecontainer.h new file mode 100644 index 00000000..203bbd1f --- /dev/null +++ b/kolourpaint/kpviewscrollablecontainer.h @@ -0,0 +1,255 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <qpoint.h> +#include <qscrollview.h> +#include <qsize.h> + + +class QCursor; +class QRect; +class QTimer; + +class kpGrip; +class kpView; +class kpMainWindow; + + +// TODO: 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, const char *name = 0); + virtual ~kpGrip (); + + GripType type () const; + + static const QCursor &cursorForType (GripType type); + + QRect hotRect (bool toGlobal = false) const; + + bool isDrawing () const; + +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 updatePixmap (); + void cancel (); + +protected: + virtual void keyReleaseEvent (QKeyEvent *e); + virtual void mousePressEvent (QMouseEvent *e); +public: + QPoint viewDeltaPoint () const; + void mouseMovedTo (const QPoint &point, bool dueToDragScroll); +protected: + virtual void mouseMoveEvent (QMouseEvent *e); + virtual void mouseReleaseEvent (QMouseEvent *e); + virtual void resizeEvent (QResizeEvent *e); + + virtual void enterEvent (QEvent *e); + virtual void leaveEvent (QEvent *e); + + virtual void paintEvent (QPaintEvent *e); + +protected: + GripType m_type; + QPoint m_startPoint, m_currentPoint; + QString m_userMessage; + bool m_shouldReleaseMouseButtons; +}; + + +class kpViewScrollableContainer : public QScrollView +{ +Q_OBJECT + +public: + kpViewScrollableContainer (kpMainWindow *parent, const char *name = 0); + virtual ~kpViewScrollableContainer (); + + // Same as contentsX() and contentsY() except that after + // contentsMovingSoon() is emitted and before the scrollview actually + // scrolls, they return the would be values of contentsX() and + // contentsY() after scrolling. + int contentsXSoon (); + int contentsYSoon (); + +signals: + // connect to this instead of contentsMoving(int,int) so that + // contentsXSoon() and contentsYSoon() work + void contentsMovingSoon (int contentsX, int contentsY); + + 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: + QSize newDocSize () const; + bool haveMovedFromOriginalDocSize () const; + QString statusMessage () const; + void clearStatusMessage (); + +protected: + 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; + + QPoint mapViewToViewport (const QPoint &viewPoint); + QRect mapViewToViewport (const QRect &viewRect); + + QRect mapViewportToGlobal (const QRect &viewportRect); + QRect mapViewToGlobal (const QRect &viewRect); + + void repaintWidgetAtResizeLineViewRect (QWidget *widget, + const QRect &resizeLineViewRect); + void repaintWidgetAtResizeLines (QWidget *widget); + void eraseResizeLines (); + + void drawResizeLines (); + + void updateResizeLines (int viewX, int viewY, + int viewDX, int viewDY); + +protected slots: + void slotGripBeganDraw (); + void slotGripContinuedDraw (int viewDX, int viewDY, bool dueToScrollView); + void slotGripCancelledDraw (); + void slotGripEndedDraw (int viewDX, int viewDY); + + void slotGripStatusMessageChanged (const QString &string); + +public slots: + void recalculateStatusMessage (); + +protected slots: + void slotContentsMoving (int x, int y); + void slotContentsMoved (); + +protected: + void disconnectViewSignals (); + void connectViewSignals (); + +public: + // Calls setView(<widget>) after adding <widget> if it's a kpView. + virtual void addChild (QWidget *widget, int x = 0, int y = 0); + + kpView *view () const; + void setView (kpView *view); + +public slots: + void updateGrips (); +protected slots: + void slotViewDestroyed (); + +public slots: + // TODO: Why the QPoint's? + // Why the need for view's zoomLevel? We have the view() anyway. + bool beginDragScroll (const QPoint &, const QPoint &, + int zoomLevel, + bool *didSomething); + bool beginDragScroll (const QPoint &, const QPoint &, + int zoomLevel); + bool endDragScroll (); + +protected slots: + bool slotDragScroll (bool *didSomething); + bool slotDragScroll (); + +protected: + QRect noDragScrollRect () const; + + virtual void contentsDragMoveEvent (QDragMoveEvent *e); + virtual void contentsMouseMoveEvent (QMouseEvent *e); + virtual void contentsWheelEvent (QWheelEvent *e); + virtual void mouseMoveEvent (QMouseEvent *e); + virtual bool eventFilter (QObject *watchedObject, QEvent *e); + virtual void viewportPaintEvent (QPaintEvent *e); + virtual void paintEvent (QPaintEvent *e); + virtual void resizeEvent (QResizeEvent *e); + +protected: + kpMainWindow *m_mainWindow; + int m_contentsXSoon, m_contentsYSoon; + kpView *m_view; + 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/kolourpaint/kpwidgetmapper.cpp b/kolourpaint/kpwidgetmapper.cpp new file mode 100644 index 00000000..beb2624c --- /dev/null +++ b/kolourpaint/kpwidgetmapper.cpp @@ -0,0 +1,76 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <qpoint.h> +#include <qrect.h> +#include <qwidget.h> + + +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; + + QPoint topLeft = fromGlobal (widget, rect.topLeft ()); + return QRect (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; + + QPoint topLeft = toGlobal (widget, rect.topLeft ()); + return QRect (topLeft.x (), topLeft.y (), rect.width (), rect.height ()); +} + + +} // namespace kpWidgetMapper { diff --git a/kolourpaint/kpwidgetmapper.h b/kolourpaint/kpwidgetmapper.h new file mode 100644 index 00000000..b5c4c412 --- /dev/null +++ b/kolourpaint/kpwidgetmapper.h @@ -0,0 +1,47 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 +#define KP_WIDGET_MAPPER + + +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 diff --git a/kolourpaint/patches/checkerboard-faster-render.diff b/kolourpaint/patches/checkerboard-faster-render.diff new file mode 100644 index 00000000..8c9c6402 --- /dev/null +++ b/kolourpaint/patches/checkerboard-faster-render.diff @@ -0,0 +1,141 @@ +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. + +--- 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 ++ kdDebug () << "\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 (); ++ kdDebug () << "\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 ++ kdDebug () << "\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 (); ++ kdDebug () << "\t\ttotal=" << totalTimerElapsed << endl; ++} ++#endif ++} ++#endif + + // private slot + void kpMainWindow::slotUpdateCaption () diff --git a/kolourpaint/patches/color_eraser_speedup.diff b/kolourpaint/patches/color_eraser_speedup.diff new file mode 100644 index 00000000..5e1ff7b7 --- /dev/null +++ b/kolourpaint/patches/color_eraser_speedup.diff @@ -0,0 +1,264 @@ +[probably no longer applies without modification] + +Attempts to improve the performance of the Color Eraser & Eraser +by drawing only _unique_ rectangles across interpolation lines +and by not drawing pixmaps when the user has a solid rectangular +brush. + +- appears to decrease the performance of the Eraser (QRegion + overhead?). +- reduces code clarity +- unsure of whether it increases performance of Color Eraser + (sometimes it seems faster, sometimes not) + +Index: tools/kptoolpen.cpp +=================================================================== +RCS file: /home/kde/kdenonbeta/kolourpaint/tools/kptoolpen.cpp,v +retrieving revision 1.9 +diff -u -p -r1.9 kptoolpen.cpp +--- tools/kptoolpen.cpp 6 Dec 2003 06:53:36 -0000 1.9 ++++ tools/kptoolpen.cpp 6 Dec 2003 06:55:46 -0000 +@@ -34,12 +34,13 @@ + #include <qapplication.h> + #include <qbitmap.h> + #include <qcursor.h> +-#include <qimage.h> +-#include <qpainter.h> +-#include <qpixmap.h> + #if DEBUG_KP_TOOL_PEN + #include <qdatetime.h> + #endif ++#include <qimage.h> ++#include <qpainter.h> ++#include <qpixmap.h> ++#include <qregion.h> + + #include <kdebug.h> + #include <klocale.h> +@@ -416,31 +417,28 @@ void kpToolPen::draw (const QPoint &this + rect = neededRect (rect, m_brushPixmap [m_mouseButton].width ()); + + #if DEBUG_KP_TOOL_PEN +- if (m_mode & WashesPixmaps) +- { +- kdDebug () << "Washing pixmap (w=" << rect.width () +- << ",h=" << rect.height () << ")" << endl; +- } ++ kdDebug () << "kpToolPen::draw() interpolate: area (w=" << rect.width () ++ << ",h=" << rect.height () << ")" << endl; + QTime timer; + int convAndWashTime; + #endif + +- QPixmap pixmap = document ()->getPixmapAt (rect); +- QPainter painter (&pixmap); ++ // optimsation - only render intersections of rectangles once ++ bool delayedDraw = ((m_mode & SquareBrushes) && ++ ((m_mode & WashesPixmaps) || ++ (m_mode == Eraser)/*solid rectangular brush*/)); ++ ++ ++ QPixmap pixmap; ++ QPainter painter; ++ pixmap = document ()->getPixmapAt (rect); ++ painter.begin (&pixmap); + painter.setPen (color (m_mouseButton)); + +- QImage image; +- if (m_mode & WashesPixmaps) +- { + #if DEBUG_KP_TOOL_PEN ++ if (m_mode & WashesPixmaps) + timer.start (); + #endif +- image = pixmap.convertToImage (); +- #if DEBUG_KP_TOOL_PEN +- convAndWashTime = timer.restart (); +- kdDebug () << "\tconvert to image: " << convAndWashTime << " ms" << endl; +- #endif +- } + + bool didSomething = false; + +@@ -453,10 +451,21 @@ void kpToolPen::draw (const QPoint &this + else if (m_mode & (DrawsPixmaps | WashesPixmaps)) + { + QRgb colorToReplace; ++ QImage image; ++ QRegion region; + + if (m_mode & WashesPixmaps) ++ { + colorToReplace = color (1 - m_mouseButton).rgb (); + ++ image = pixmap.convertToImage (); ++ ++ #if DEBUG_KP_TOOL_PEN ++ convAndWashTime = timer.restart (); ++ kdDebug () << "\tconvert to image: " << convAndWashTime << " ms" << endl; ++ #endif ++ } ++ + // Sweeps a pixmap along a line (modified Bresenham's line algorithm, + // see MODIFIED comment below). + // +@@ -485,19 +494,27 @@ void kpToolPen::draw (const QPoint &this + int x = 0; + int y = 0; + +- if (m_mode & WashesPixmaps) ++ if (delayedDraw) + { +- if (wash (&painter, image, +- colorToReplace, +- rect, plotx + rect.left (), ploty + rect.top ())) +- { +- didSomething = true; +- } ++ region = region.unite (hotRect (plotx + rect.left (), ploty + rect.top ())); + } + else + { +- painter.drawPixmap (hotPoint (plotx, ploty), m_brushPixmap [m_mouseButton]); +- didSomething = true; ++ if (m_mode & WashesPixmaps) ++ { ++ if (wash (&painter, image, ++ colorToReplace, ++ rect, plotx + rect.left (), ploty + rect.top ())) ++ { ++ didSomething = true; ++ } ++ } ++ else ++ { ++ painter.drawPixmap (hotPoint (plotx, ploty), ++ m_brushPixmap [m_mouseButton]); ++ didSomething = true; ++ } + } + + for (int i = 0; i <= inc; i++) +@@ -541,39 +558,115 @@ void kpToolPen::draw (const QPoint &this + // is more than 1 point, of course). This is in contrast to the + // ordinary line algorithm which can create diagonal adjacencies. + ++ if (delayedDraw) ++ { ++ region = region.unite (hotRect (plotx + rect.left (), oldploty + rect.top ())); ++ } ++ else ++ { ++ if (m_mode & WashesPixmaps) ++ { ++ if (wash (&painter, image, ++ colorToReplace, ++ rect, plotx + rect.left (), oldploty + rect.top ())) ++ { ++ didSomething = true; ++ } ++ } ++ else ++ { ++ painter.drawPixmap (hotPoint (plotx, oldploty), ++ m_brushPixmap [m_mouseButton]); ++ didSomething = true; ++ } ++ } ++ } ++ ++ if (delayedDraw) ++ { ++ region = region.unite (hotRect (plotx + rect.left (), ploty + rect.top ())); ++ } ++ else ++ { + if (m_mode & WashesPixmaps) + { + if (wash (&painter, image, + colorToReplace, +- rect, plotx + rect.left (), oldploty + rect.top ())) ++ rect, plotx + rect.left (), ploty + rect.top ())) + { + didSomething = true; + } + } + else + { +- painter.drawPixmap (hotPoint (plotx, oldploty), m_brushPixmap [m_mouseButton]); ++ painter.drawPixmap (hotPoint (plotx, ploty), ++ m_brushPixmap [m_mouseButton]); + didSomething = true; + } + } +- ++ } ++ } ++ ++ if (delayedDraw) ++ { ++ QMemArray <QRect> rects = region.rects (); ++ ++ int numRects = rects.count (); ++ #if DEBUG_KP_TOOL_PEN ++ kdDebug () << "\tdelayed draw now happening: numRects=" ++ << numRects << endl; ++ int convImageMS = 0; ++ int washMS = 0; ++ int setDocMS = 0; ++ QTime timer; ++ #endif ++ for (int i = 0; i < numRects; i++) ++ { ++ QRect r = rects [i]; ++ QPixmap pm = document ()->getPixmapAt (r); ++ #if DEBUG_KP_TOOL_PEN && 0 ++ kdDebug () << "\tr=" << r << endl; ++ #endif ++ ++ bool drew = false; ++ + if (m_mode & WashesPixmaps) + { ++ timer.start (); ++ + if (wash (&painter, image, + colorToReplace, +- rect, plotx + rect.left (), ploty + rect.top ())) ++ rect, r)) + { +- didSomething = true; ++ drew = true; + } ++ washMS += timer.restart (); + } + else + { +- painter.drawPixmap (hotPoint (plotx, ploty), m_brushPixmap [m_mouseButton]); ++ painter.setBrush (color (m_mouseButton)); ++ painter.drawRect (r.x () - rect.x (), ++ r.y () - rect.y (), ++ r.width (), ++ r.height ()); ++ drew = true; ++ } ++ ++ if (drew) ++ { ++ m_currentCommand->updateBoundingRect (r); + didSomething = true; ++ setDocMS += timer.restart (); + } + } ++ ++ #if DEBUG_KP_TOOL_PEN ++ kdDebug () << "convImageMS=" << convImageMS ++ << " washMS=" << washMS ++ << " setDocMS=" << setDocMS ++ << endl; ++ #endif + } +- + } + + painter.end (); diff --git a/kolourpaint/patches/doc_resize_no_flicker.diff b/kolourpaint/patches/doc_resize_no_flicker.diff new file mode 100644 index 00000000..ae5f9aba --- /dev/null +++ b/kolourpaint/patches/doc_resize_no_flicker.diff @@ -0,0 +1,614 @@ +Eliminates flicker when moving document resize lines / dragging resize +handles by: + +1. Not erasing areas that will be subsequently painted over with resize + lines. +2. Erasing the old areas and painting the new ones atomicly by using + clever NOT'ing of pixels. + +Additionally, recover the resize lines after a window pops up momentarily +over KolourPaint (kpViewScrollableContainer::windowActivationChange()). + +Critical bugs with this code and scrollbars: + +1. Drag scrolling leaves trails of resize lines. +2. Moving the mouse cursor above the start of the document does not result + in a resize line at document y = 1. + +Because I'm still debugging, there are a few hacks in the code such as +"m_resizeLinesDontPaintClever". + +Index: kpviewscrollablecontainer.cpp +=================================================================== +RCS file: /home/kde/kdegraphics/kolourpaint/kpviewscrollablecontainer.cpp,v +retrieving revision 1.7 +diff -u -p -r1.7 kpviewscrollablecontainer.cpp +--- kpviewscrollablecontainer.cpp 29 Jul 2004 12:47:15 -0000 1.7 ++++ kpviewscrollablecontainer.cpp 30 Jul 2004 11:37:20 -0000 +@@ -1,4 +1,4 @@ +- ++static bool inScroll = false; + /* + Copyright (c) 2003-2004 Clarence Dang <dang@kde.org> + All rights reserved. +@@ -33,6 +33,7 @@ + #include <qpainter.h> + #include <qpen.h> + #include <qpixmap.h> ++#include <qregion.h> + #include <qtimer.h> + + #include <kdebug.h> +@@ -240,7 +241,7 @@ void kpGrip::mousePressEvent (QMouseEven + m_startPoint = e->pos (); + m_currentPoint = e->pos (); + emit beganDraw (); +- grabKeyboard (); ++ //grabKeyboard (); HACK + + setUserMessage (i18n ("Resize Image: Right click to cancel.")); + setCursor (cursorForType (m_type)); +@@ -387,6 +388,7 @@ kpViewScrollableContainer::kpViewScrolla + m_scrollTimerRunOnce (false), + m_resizeRoundedLastViewX (-1), m_resizeRoundedLastViewY (-1), + m_resizeRoundedLastViewDX (0), m_resizeRoundedLastViewDY (0), ++ m_resizeLinesDontPaintClever (0), + m_haveMovedFromOriginalDocSize (false) + + { +@@ -561,6 +563,18 @@ QRect kpViewScrollableContainer::bottomR + m_resizeRoundedLastViewY + bottomResizeLineWidth () - 1)); + } + ++// protected ++QRegion kpViewScrollableContainer::resizeLinesRegion () const ++{ ++ QRegion ret; ++ ++ ret += rightResizeLineRect (); ++ ret += bottomResizeLineRect (); ++ ret += bottomRightResizeLineRect (); ++ ++ return ret; ++} ++ + + // TODO: are these 2 correct? Remember that viewport()->x() == 1, viewport()->y() == 1 + +@@ -581,6 +595,17 @@ QRect kpViewScrollableContainer::mapView + return ret; + } + ++// protected ++QRegion kpViewScrollableContainer::mapViewToViewport (const QRegion &viewRegion) ++{ ++ if (viewRegion.isEmpty ()) ++ return viewRegion; ++ ++ QRegion ret = viewRegion; ++ ret.translate (-contentsX (), -contentsY ()); ++ return ret; ++} ++ + + // protected + QRect kpViewScrollableContainer::mapViewportToGlobal (const QRect &viewportRect) +@@ -589,89 +614,108 @@ QRect kpViewScrollableContainer::mapView + } + + // protected ++QRegion kpViewScrollableContainer::mapViewportToGlobal (const QRegion &viewportRegion) ++{ ++ return kpWidgetMapper::toGlobal (viewport (), viewportRegion); ++} ++ ++ ++// protected + QRect kpViewScrollableContainer::mapViewToGlobal (const QRect &viewRect) + { + return mapViewportToGlobal (mapViewToViewport (viewRect)); + } + ++// protected ++QRegion kpViewScrollableContainer::mapViewToGlobal (const QRegion &viewRegion) ++{ ++ return mapViewportToGlobal (mapViewToViewport (viewRegion)); ++} ++ + + // protected +-void kpViewScrollableContainer::repaintWidgetAtResizeLineViewRect ( +- QWidget *widget, const QRect &resizeLineViewRect) ++void kpViewScrollableContainer::repaintWidgetRegion ( ++ QWidget *widget, ++ const QRegion &viewRegion) + { +- const QRect resizeLineGlobalRect = mapViewToGlobal (resizeLineViewRect); ++ const QRegion globalRegion = mapViewToGlobal (viewRegion); ++ + const QRect widgetGlobalRect = kpWidgetMapper::toGlobal (widget, + widget->rect ()); + +- const QRect redrawGlobalRect = +- resizeLineGlobalRect.intersect (widgetGlobalRect); + +- const QRect redrawWidgetRect = +- kpWidgetMapper::fromGlobal (widget, redrawGlobalRect); ++ const QRegion redrawGlobalRegion = ++ globalRegion.intersect (widgetGlobalRect); + ++ const QRegion redrawWidgetRegion = ++ kpWidgetMapper::fromGlobal (widget, redrawGlobalRegion); + +- if (redrawWidgetRect.isValid ()) ++ ++ if (!redrawWidgetRegion.isEmpty ()) + { + // TODO: should be "!widget->testWFlags (Qt::WRepaintNoErase)" + // but for some reason, doesn't work for viewport(). + const bool erase = !dynamic_cast <kpView *> (widget); +- widget->repaint (redrawWidgetRect, erase); ++ widget->repaint (redrawWidgetRegion, erase); + } + } + + // protected +-void kpViewScrollableContainer::repaintWidgetAtResizeLines (QWidget *widget) ++void kpViewScrollableContainer::eraseResizeLines (const QRegion &viewRegion) + { +- repaintWidgetAtResizeLineViewRect (widget, rightResizeLineRect ()); +- repaintWidgetAtResizeLineViewRect (widget, bottomResizeLineRect ()); +- repaintWidgetAtResizeLineViewRect (widget, bottomRightResizeLineRect ()); +-} ++ if (viewRegion.isEmpty ()) ++ return; + +-// protected +-void kpViewScrollableContainer::eraseResizeLines () +-{ +- if (m_resizeRoundedLastViewX >= 0 && m_resizeRoundedLastViewY >= 0) +- { +- repaintWidgetAtResizeLines (viewport ()); +- repaintWidgetAtResizeLines (m_view); + +- repaintWidgetAtResizeLines (m_bottomGrip); +- repaintWidgetAtResizeLines (m_rightGrip); +- repaintWidgetAtResizeLines (m_bottomRightGrip); +- } ++ repaintWidgetRegion (viewport (), viewRegion); ++ repaintWidgetRegion (m_view, viewRegion); ++ ++ repaintWidgetRegion (m_bottomGrip, viewRegion); ++ repaintWidgetRegion (m_rightGrip, viewRegion); ++ repaintWidgetRegion (m_bottomRightGrip, viewRegion); + } + + + // protected +-void kpViewScrollableContainer::drawResizeLines () ++void kpViewScrollableContainer::drawResizeLines (const QRegion &viewRegion) + { + #if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER +- kdDebug () << "kpViewScrollableContainer::drawResizeLines()" ++ kdDebug () << "kpViewScrollableContainer::drawResizeLines(" ++ << viewRegion <<")" + << " lastViewX=" << m_resizeRoundedLastViewX + << " lastViewY=" << m_resizeRoundedLastViewY + << endl; + #endif + ++ if (viewRegion.isEmpty ()) ++ return; ++ + + QPainter p (viewport (), true/*unclipped*/); + p.setRasterOp (Qt::NotROP); + +- const QRect rightRect = rightResizeLineRect (); +- if (rightRect.isValid ()) +- p.fillRect (mapViewToViewport (rightRect), Qt::white); +- +- const QRect bottomRect = bottomResizeLineRect (); +- if (bottomRect.isValid ()) +- p.fillRect (mapViewToViewport (bottomRect), Qt::white); +- +- const QRect bottomRightRect = bottomRightResizeLineRect (); +- if (bottomRightRect.isValid ()) +- p.fillRect (mapViewToViewport (bottomRightRect), Qt::white); ++ const QMemArray <QRect> rects = mapViewToViewport (viewRegion).rects (); ++ for (QMemArray <QRect>::ConstIterator it = rects.begin (); ++ it != rects.end (); ++ it++) ++ { ++ p.fillRect (*it, Qt::white); ++ } + + p.end (); + } + + ++template <typename T> ++static inline void swap (T &a, T &b) ++{ ++ T temp = a; ++ ++ a = b; ++ b = temp; ++} ++ ++ + // protected + void kpViewScrollableContainer::updateResizeLines (int viewX, int viewY, + int viewDX, int viewDY) +@@ -686,36 +730,71 @@ void kpViewScrollableContainer::updateRe + << endl; + #endif + +- eraseResizeLines (); +- ++ int newResizeRoundedLastViewX = -1, ++ newResizeRoundedLastViewY = -1; ++ int newResizeRoundedLastViewDX = 0, ++ newResizeRoundedLastViewDY = 0; + + if (viewX >= 0 && viewY >= 0) + { +- m_resizeRoundedLastViewX = m_view->zoomDocToViewX (m_view->zoomViewToDocX (viewX)); +- m_resizeRoundedLastViewY = m_view->zoomDocToViewY (m_view->zoomViewToDocY (viewY)); ++ newResizeRoundedLastViewX = m_view->zoomDocToViewX (m_view->zoomViewToDocX (viewX)); ++ newResizeRoundedLastViewY = m_view->zoomDocToViewY (m_view->zoomViewToDocY (viewY)); + +- m_resizeRoundedLastViewDX = viewDX; +- m_resizeRoundedLastViewDY = viewDY; ++ newResizeRoundedLastViewDX = viewDX; ++ newResizeRoundedLastViewDY = viewDY; + } +- else +- { +- m_resizeRoundedLastViewX = -1; +- m_resizeRoundedLastViewY = -1; + +- m_resizeRoundedLastViewDX = 0; +- m_resizeRoundedLastViewDY = 0; +- } + +- // TODO: This is suboptimal since if another window pops up on top of +- // KolourPaint then disappears, the lines are not redrawn +- // (although this doesn't happen very frequently since we grab the +- // keyboard and mouse when resizing): +- // +- // e.g. sleep 5 && gedit & sleep 10 && killall gedit ++ QRegion oldLinesRegion = resizeLinesRegion (); ++#if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER ++ kdDebug () << "\toldLinesRegion=" << oldLinesRegion << endl; ++#endif ++ ++ ++// (macro instead of writing out code to permit experimentation) ++#define SWAP_LAST_VIEW_STATS() \ ++{ \ ++ swap (m_resizeRoundedLastViewX, newResizeRoundedLastViewX); \ ++ swap (m_resizeRoundedLastViewY, newResizeRoundedLastViewY); \ ++ \ ++ swap (m_resizeRoundedLastViewDX, newResizeRoundedLastViewDX); \ ++ swap (m_resizeRoundedLastViewDY, newResizeRoundedLastViewDY); \ ++} ++ SWAP_LAST_VIEW_STATS (); ++#undef SWAP_LAST_VIEW_STATS ++ ++ ++ QRegion newLinesRegion = resizeLinesRegion (); ++#if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER ++ kdDebug () << "\tnewLinesRegion=" << newLinesRegion << endl; ++#endif ++ ++ ++ // TODO: This is suboptimal - we will get redraw errors sooner or later. ++ // But I've tried hard to avoid them (e.g. windowActivationChange()). + // + // Should be done in the paintEvent's of every child of the + // scrollview. +- drawResizeLines (); ++ ++ if (m_resizeLinesDontPaintClever) ++ { ++ // (drawResizeLines() NOT's the pixels - so we can erase old and draw ++ // new at the same time) ++ drawResizeLines (newLinesRegion.eor (oldLinesRegion)); ++ #if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER ++ kdDebug () << "\tNOTRregion=" ++ << newLinesRegion.eor (oldLinesRegion) << endl; ++ #endif ++ } ++ else ++ { ++ eraseResizeLines (oldLinesRegion); ++ drawResizeLines (newLinesRegion); ++ #if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER ++ kdDebug () << "\tnot erasing old lines; NOTRregion=" ++ << newLinesRegion << endl; ++ #endif ++ } + } + + +@@ -729,6 +808,8 @@ void kpViewScrollableContainer::slotGrip + + m_haveMovedFromOriginalDocSize = false; + ++ m_resizeLinesDontPaintClever = true; ++ + updateResizeLines (m_view->width (), m_view->height (), + 0/*viewDX*/, 0/*viewDY*/); + +@@ -750,12 +831,28 @@ void kpViewScrollableContainer::slotGrip + + m_haveMovedFromOriginalDocSize = true; + ++#if 0 ++ if (inScroll != !m_resizeLinesNeedErase) ++ { ++ kdError () << "slotGripContDraw EXCEPTION 0: inScroll=" << inScroll << endl; ++ memset (0, 42, 1048576); ++ } ++#endif ++ + updateResizeLines (QMAX (1, QMAX (m_view->width () + viewDX, m_view->zoomDocToViewX (1))), + QMAX (1, QMAX (m_view->height () + viewDY, m_view->zoomDocToViewY (1))), + viewDX, viewDY); + + emit continuedDocResize (newDocSize ()); + ++#if 0 ++ if (!m_resizeLinesNeedErase) ++ { ++ kdError () << "slotGripContDraw EXCEPTION 1" << endl; ++ memset (0, 42, 1048576); ++ } ++#endif ++ + beginDragScroll (QPoint (), QPoint (), m_view->zoomLevelX ()); + } + +@@ -859,8 +956,19 @@ void kpViewScrollableContainer::slotCont + << x << "," << y << ")" << endl; + #endif + ++ m_resizeLinesDontPaintClever++; ++ ++ if (inScroll && 0) ++ { ++ kdError () << "slotContentsMovING EXCEPTION" << endl; ++ memset (0, 42, 1048576); ++ } ++ ++ inScroll = true; + // Reduce flicker - don't let QScrollView scroll to-be-erased lines +- eraseResizeLines (); ++ //eraseResizeLines (resizeLinesRegion ()); ++ //m_resizeLinesNeedErase = false; ++ + + QTimer::singleShot (0, this, SLOT (slotContentsMoved ())); + } +@@ -874,9 +982,27 @@ void kpViewScrollableContainer::slotCont + << " grip=" << grip << endl; + #endif + if (!grip) ++ { ++ inScroll = false; + return; ++ } + ++ if (!inScroll && 0) ++ { ++ kdError () << "slotContentsMoved EXCEPTION" << endl; ++ memset (0, 42, 1048576); ++ } + grip->mouseMovedTo (grip->mapFromGlobal (QCursor::pos ())); ++#if 0 ++ if (!m_resizeLinesNeedErase) ++ { ++ kdError () << "slotContentsMoved EXCEPTION 2" << endl; ++ memset (0, 42, 1048576); ++ } ++#endif ++ inScroll = false; ++ ++ m_resizeLinesDontPaintClever--; + } + + +@@ -1191,7 +1317,7 @@ bool kpViewScrollableContainer::eventFil + // protected virtual [base QScrollView] + void kpViewScrollableContainer::viewportPaintEvent (QPaintEvent *e) + { +-#if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER ++#if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER && 0 + kdDebug () << "kpViewScrollableContainer::viewportPaintEvent(" + << e->rect () + << ")" << endl; +@@ -1213,4 +1339,42 @@ void kpViewScrollableContainer::paintEve + } + + ++// protected slot ++void kpViewScrollableContainer::windowActivationChanged () ++{ ++ if (isActiveWindow () && ++ m_resizeRoundedLastViewX >= 0 && m_resizeRoundedLastViewY >= 0) ++ { ++ // We were obscured by a window that popped up monmentarily and ++ // this clobbered the resize lines (since the scrollView's child ++ // widgets don't draw them). This doesn't happen very frequently ++ // since we grab the keyboard and mouse when resizing but: ++ // ++ // e.g. sleep 5 && gedit & sleep 10 && killall gedit ++ // ++ ++ // Repaint child widgets at the resize lines to make sure any ++ // remains of the resize lines are gone. ++ eraseResizeLines (resizeLinesRegion ()); ++ ++ // Draw the resize lines by NOT-ing the child widget pixels. ++ drawResizeLines (resizeLinesRegion ()); ++ } ++} ++ ++// protected virtual [base QWidget] ++void kpViewScrollableContainer::windowActivationChange (bool wasActive) ++{ ++#if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER && 1 ++ kdDebug () << "kpViewScrollableContainer::windowActivationChange(" ++ << wasActive << ")" << endl; ++#endif ++ ++ QScrollView::windowActivationChange (wasActive); ++ ++ // Wait for m_view to update ++ QTimer::singleShot (0, this, SLOT (windowActivationChanged ())); ++} ++ ++ + #include <kpviewscrollablecontainer.moc> +Index: kpviewscrollablecontainer.h +=================================================================== +RCS file: /home/kde/kdegraphics/kolourpaint/kpviewscrollablecontainer.h,v +retrieving revision 1.3 +diff -u -p -r1.3 kpviewscrollablecontainer.h +--- kpviewscrollablecontainer.h 19 Jul 2004 05:00:47 -0000 1.3 ++++ kpviewscrollablecontainer.h 30 Jul 2004 11:37:21 -0000 +@@ -31,6 +31,7 @@ + + + #include <qpoint.h> ++#include <qregion.h> + #include <qscrollview.h> + #include <qsize.h> + +@@ -147,19 +148,23 @@ protected: + QRect bottomResizeLineRect () const; + QRect rightResizeLineRect () const; + QRect bottomRightResizeLineRect () const; ++ QRegion resizeLinesRegion () const; + + QPoint mapViewToViewport (const QPoint &viewPoint); + QRect mapViewToViewport (const QRect &viewRect); ++ QRegion mapViewToViewport (const QRegion &viewRegion); + + QRect mapViewportToGlobal (const QRect &viewportRect); ++ QRegion mapViewportToGlobal (const QRegion &viewportRegion); ++ + QRect mapViewToGlobal (const QRect &viewRect); ++ QRegion mapViewToGlobal (const QRegion &viewRegion); + +- void repaintWidgetAtResizeLineViewRect (QWidget *widget, +- const QRect &resizeLineViewRect); +- void repaintWidgetAtResizeLines (QWidget *widget); +- void eraseResizeLines (); ++ void repaintWidgetRegion (QWidget *widget, ++ const QRegion &viewRegion); ++ void eraseResizeLines (const QRegion &viewRegion); + +- void drawResizeLines (); ++ void drawResizeLines (const QRegion &viewRegion); + + void updateResizeLines (int viewX, int viewY, + int viewDX, int viewDY); +@@ -213,6 +218,12 @@ protected: + virtual void viewportPaintEvent (QPaintEvent *e); + virtual void paintEvent (QPaintEvent *e); + ++protected slots: ++ void windowActivationChanged (); ++protected: ++ virtual void windowActivationChange (bool wasActive); ++ ++ + protected: + kpMainWindow *m_mainWindow; + kpView *m_view; +@@ -223,6 +234,7 @@ protected: + bool m_scrollTimerRunOnce; + int m_resizeRoundedLastViewX, m_resizeRoundedLastViewY; + int m_resizeRoundedLastViewDX, m_resizeRoundedLastViewDY; ++ int m_resizeLinesDontPaintClever; + bool m_haveMovedFromOriginalDocSize; + QString m_gripStatusMessage; + }; +Index: kpwidgetmapper.cpp +=================================================================== +RCS file: /home/kde/kdegraphics/kolourpaint/kpwidgetmapper.cpp,v +retrieving revision 1.1 +diff -u -p -r1.1 kpwidgetmapper.cpp +--- kpwidgetmapper.cpp 10 Jul 2004 11:38:09 -0000 1.1 ++++ kpwidgetmapper.cpp 30 Jul 2004 11:37:21 -0000 +@@ -30,6 +30,7 @@ + + #include <qpoint.h> + #include <qrect.h> ++#include <qregion.h> + #include <qwidget.h> + + +@@ -54,6 +55,17 @@ QRect fromGlobal (const QWidget *widget, + return QRect (topLeft.x (), topLeft.y (), rect.width (), rect.height ()); + } + ++QRegion fromGlobal (const QWidget *widget, const QRegion ®ion) ++{ ++ if (!widget || region.isEmpty ()) ++ return region; ++ ++ const QPoint widgetGlobalTopLeft = toGlobal (widget, QPoint (0, 0)); ++ QRegion ret = region; ++ ret.translate (-widgetGlobalTopLeft.x (), -widgetGlobalTopLeft.y ()); ++ return ret; ++} ++ + + QPoint toGlobal (const QWidget *widget, const QPoint &point) + { +@@ -72,5 +84,16 @@ QRect toGlobal (const QWidget *widget, c + return QRect (topLeft.x (), topLeft.y (), rect.width (), rect.height ()); + } + ++QRegion toGlobal (const QWidget *widget, const QRegion ®ion) ++{ ++ if (!widget || region.isEmpty ()) ++ return region; ++ ++ const QPoint widgetGlobalTopLeft = toGlobal (widget, QPoint (0, 0)); ++ QRegion ret = region; ++ ret.translate (widgetGlobalTopLeft.x (), widgetGlobalTopLeft.y ()); ++ return ret; ++} ++ + + } // namespace kpWidgetMapper { +Index: kpwidgetmapper.h +=================================================================== +RCS file: /home/kde/kdegraphics/kolourpaint/kpwidgetmapper.h,v +retrieving revision 1.1 +diff -u -p -r1.1 kpwidgetmapper.h +--- kpwidgetmapper.h 10 Jul 2004 11:38:09 -0000 1.1 ++++ kpwidgetmapper.h 30 Jul 2004 11:37:21 -0000 +@@ -32,15 +32,18 @@ + class QWidget; + class QPoint; + class QRect; ++class QRegion; + + + namespace kpWidgetMapper + { + QPoint fromGlobal (const QWidget *widget, const QPoint &point); + QRect fromGlobal (const QWidget *widget, const QRect &rect); ++ QRegion fromGlobal (const QWidget *widget, const QRegion ®ion); + + QPoint toGlobal (const QWidget *widget, const QPoint &point); + QRect toGlobal (const QWidget *widget, const QRect &rect); ++ QRegion toGlobal (const QWidget *widget, const QRegion ®ion); + } + + diff --git a/kolourpaint/pics/Makefile.am b/kolourpaint/pics/Makefile.am new file mode 100644 index 00000000..2e4aed53 --- /dev/null +++ b/kolourpaint/pics/Makefile.am @@ -0,0 +1,13 @@ +SUBDIRS = custom + +KDE_ICON = kolourpaint + +actionicondir = $(kde_datadir)/kolourpaint/icons +actionicon_ICON = AUTO + +# Change the following line every time you add an icon +# to force Makefile regeneration: +# +# 4 +# + diff --git a/kolourpaint/pics/cr16-action-tool_brush.png b/kolourpaint/pics/cr16-action-tool_brush.png Binary files differnew file mode 100644 index 00000000..32a23881 --- /dev/null +++ b/kolourpaint/pics/cr16-action-tool_brush.png diff --git a/kolourpaint/pics/cr16-action-tool_color_picker.png b/kolourpaint/pics/cr16-action-tool_color_picker.png Binary files differnew file mode 100644 index 00000000..569171e6 --- /dev/null +++ b/kolourpaint/pics/cr16-action-tool_color_picker.png diff --git a/kolourpaint/pics/cr16-action-tool_color_washer.png b/kolourpaint/pics/cr16-action-tool_color_washer.png Binary files differnew file mode 100644 index 00000000..97193458 --- /dev/null +++ b/kolourpaint/pics/cr16-action-tool_color_washer.png diff --git a/kolourpaint/pics/cr16-action-tool_curve.png b/kolourpaint/pics/cr16-action-tool_curve.png Binary files differnew file mode 100644 index 00000000..b86c96fb --- /dev/null +++ b/kolourpaint/pics/cr16-action-tool_curve.png diff --git a/kolourpaint/pics/cr16-action-tool_ellipse.png b/kolourpaint/pics/cr16-action-tool_ellipse.png Binary files differnew file mode 100644 index 00000000..608d40b7 --- /dev/null +++ b/kolourpaint/pics/cr16-action-tool_ellipse.png diff --git a/kolourpaint/pics/cr16-action-tool_elliptical_selection.png b/kolourpaint/pics/cr16-action-tool_elliptical_selection.png Binary files differnew file mode 100644 index 00000000..70edc438 --- /dev/null +++ b/kolourpaint/pics/cr16-action-tool_elliptical_selection.png diff --git a/kolourpaint/pics/cr16-action-tool_eraser.png b/kolourpaint/pics/cr16-action-tool_eraser.png Binary files differnew file mode 100644 index 00000000..459d28a2 --- /dev/null +++ b/kolourpaint/pics/cr16-action-tool_eraser.png diff --git a/kolourpaint/pics/cr16-action-tool_flood_fill.png b/kolourpaint/pics/cr16-action-tool_flood_fill.png Binary files differnew file mode 100644 index 00000000..746ede5b --- /dev/null +++ b/kolourpaint/pics/cr16-action-tool_flood_fill.png diff --git a/kolourpaint/pics/cr16-action-tool_free_form_selection.png b/kolourpaint/pics/cr16-action-tool_free_form_selection.png Binary files differnew file mode 100644 index 00000000..ed03ba39 --- /dev/null +++ b/kolourpaint/pics/cr16-action-tool_free_form_selection.png diff --git a/kolourpaint/pics/cr16-action-tool_line.png b/kolourpaint/pics/cr16-action-tool_line.png Binary files differnew file mode 100644 index 00000000..ce282923 --- /dev/null +++ b/kolourpaint/pics/cr16-action-tool_line.png diff --git a/kolourpaint/pics/cr16-action-tool_pen.png b/kolourpaint/pics/cr16-action-tool_pen.png Binary files differnew file mode 100644 index 00000000..ae64f5aa --- /dev/null +++ b/kolourpaint/pics/cr16-action-tool_pen.png diff --git a/kolourpaint/pics/cr16-action-tool_polygon.png b/kolourpaint/pics/cr16-action-tool_polygon.png Binary files differnew file mode 100644 index 00000000..a5500d94 --- /dev/null +++ b/kolourpaint/pics/cr16-action-tool_polygon.png diff --git a/kolourpaint/pics/cr16-action-tool_polyline.png b/kolourpaint/pics/cr16-action-tool_polyline.png Binary files differnew file mode 100644 index 00000000..1e23ccd9 --- /dev/null +++ b/kolourpaint/pics/cr16-action-tool_polyline.png diff --git a/kolourpaint/pics/cr16-action-tool_rect_selection.png b/kolourpaint/pics/cr16-action-tool_rect_selection.png Binary files differnew file mode 100644 index 00000000..a85ef3f8 --- /dev/null +++ b/kolourpaint/pics/cr16-action-tool_rect_selection.png diff --git a/kolourpaint/pics/cr16-action-tool_rectangle.png b/kolourpaint/pics/cr16-action-tool_rectangle.png Binary files differnew file mode 100644 index 00000000..a8455de0 --- /dev/null +++ b/kolourpaint/pics/cr16-action-tool_rectangle.png diff --git a/kolourpaint/pics/cr16-action-tool_rounded_rectangle.png b/kolourpaint/pics/cr16-action-tool_rounded_rectangle.png Binary files differnew file mode 100644 index 00000000..4b5a0617 --- /dev/null +++ b/kolourpaint/pics/cr16-action-tool_rounded_rectangle.png diff --git a/kolourpaint/pics/cr16-action-tool_spraycan.png b/kolourpaint/pics/cr16-action-tool_spraycan.png Binary files differnew file mode 100644 index 00000000..75b7f748 --- /dev/null +++ b/kolourpaint/pics/cr16-action-tool_spraycan.png diff --git a/kolourpaint/pics/cr16-action-tool_text.png b/kolourpaint/pics/cr16-action-tool_text.png Binary files differnew file mode 100644 index 00000000..ffaab637 --- /dev/null +++ b/kolourpaint/pics/cr16-action-tool_text.png diff --git a/kolourpaint/pics/cr22-action-tool_brush.png b/kolourpaint/pics/cr22-action-tool_brush.png Binary files differnew file mode 100644 index 00000000..f2ad76cf --- /dev/null +++ b/kolourpaint/pics/cr22-action-tool_brush.png diff --git a/kolourpaint/pics/cr22-action-tool_color_picker.png b/kolourpaint/pics/cr22-action-tool_color_picker.png Binary files differnew file mode 100644 index 00000000..3b7ed752 --- /dev/null +++ b/kolourpaint/pics/cr22-action-tool_color_picker.png diff --git a/kolourpaint/pics/cr22-action-tool_color_washer.png b/kolourpaint/pics/cr22-action-tool_color_washer.png Binary files differnew file mode 100644 index 00000000..35fcbac1 --- /dev/null +++ b/kolourpaint/pics/cr22-action-tool_color_washer.png diff --git a/kolourpaint/pics/cr22-action-tool_curve.png b/kolourpaint/pics/cr22-action-tool_curve.png Binary files differnew file mode 100644 index 00000000..9723e209 --- /dev/null +++ b/kolourpaint/pics/cr22-action-tool_curve.png diff --git a/kolourpaint/pics/cr22-action-tool_ellipse.png b/kolourpaint/pics/cr22-action-tool_ellipse.png Binary files differnew file mode 100644 index 00000000..cc57b2f8 --- /dev/null +++ b/kolourpaint/pics/cr22-action-tool_ellipse.png diff --git a/kolourpaint/pics/cr22-action-tool_elliptical_selection.png b/kolourpaint/pics/cr22-action-tool_elliptical_selection.png Binary files differnew file mode 100644 index 00000000..a6b23e11 --- /dev/null +++ b/kolourpaint/pics/cr22-action-tool_elliptical_selection.png diff --git a/kolourpaint/pics/cr22-action-tool_eraser.png b/kolourpaint/pics/cr22-action-tool_eraser.png Binary files differnew file mode 100644 index 00000000..78632a55 --- /dev/null +++ b/kolourpaint/pics/cr22-action-tool_eraser.png diff --git a/kolourpaint/pics/cr22-action-tool_flood_fill.png b/kolourpaint/pics/cr22-action-tool_flood_fill.png Binary files differnew file mode 100644 index 00000000..17cfefba --- /dev/null +++ b/kolourpaint/pics/cr22-action-tool_flood_fill.png diff --git a/kolourpaint/pics/cr22-action-tool_free_form_selection.png b/kolourpaint/pics/cr22-action-tool_free_form_selection.png Binary files differnew file mode 100644 index 00000000..cc397f09 --- /dev/null +++ b/kolourpaint/pics/cr22-action-tool_free_form_selection.png diff --git a/kolourpaint/pics/cr22-action-tool_line.png b/kolourpaint/pics/cr22-action-tool_line.png Binary files differnew file mode 100644 index 00000000..7ab3c3d9 --- /dev/null +++ b/kolourpaint/pics/cr22-action-tool_line.png diff --git a/kolourpaint/pics/cr22-action-tool_pen.png b/kolourpaint/pics/cr22-action-tool_pen.png Binary files differnew file mode 100644 index 00000000..f80fa363 --- /dev/null +++ b/kolourpaint/pics/cr22-action-tool_pen.png diff --git a/kolourpaint/pics/cr22-action-tool_polygon.png b/kolourpaint/pics/cr22-action-tool_polygon.png Binary files differnew file mode 100644 index 00000000..1d05ec57 --- /dev/null +++ b/kolourpaint/pics/cr22-action-tool_polygon.png diff --git a/kolourpaint/pics/cr22-action-tool_polyline.png b/kolourpaint/pics/cr22-action-tool_polyline.png Binary files differnew file mode 100644 index 00000000..36c089c9 --- /dev/null +++ b/kolourpaint/pics/cr22-action-tool_polyline.png diff --git a/kolourpaint/pics/cr22-action-tool_rect_selection.png b/kolourpaint/pics/cr22-action-tool_rect_selection.png Binary files differnew file mode 100644 index 00000000..3dc8c75a --- /dev/null +++ b/kolourpaint/pics/cr22-action-tool_rect_selection.png diff --git a/kolourpaint/pics/cr22-action-tool_rectangle.png b/kolourpaint/pics/cr22-action-tool_rectangle.png Binary files differnew file mode 100644 index 00000000..9954c17e --- /dev/null +++ b/kolourpaint/pics/cr22-action-tool_rectangle.png diff --git a/kolourpaint/pics/cr22-action-tool_rounded_rectangle.png b/kolourpaint/pics/cr22-action-tool_rounded_rectangle.png Binary files differnew file mode 100644 index 00000000..b736a6cc --- /dev/null +++ b/kolourpaint/pics/cr22-action-tool_rounded_rectangle.png diff --git a/kolourpaint/pics/cr22-action-tool_spraycan.png b/kolourpaint/pics/cr22-action-tool_spraycan.png Binary files differnew file mode 100644 index 00000000..bbd35078 --- /dev/null +++ b/kolourpaint/pics/cr22-action-tool_spraycan.png diff --git a/kolourpaint/pics/cr22-action-tool_text.png b/kolourpaint/pics/cr22-action-tool_text.png Binary files differnew file mode 100644 index 00000000..52b3ccf7 --- /dev/null +++ b/kolourpaint/pics/cr22-action-tool_text.png diff --git a/kolourpaint/pics/cr32-action-tool_brush.png b/kolourpaint/pics/cr32-action-tool_brush.png Binary files differnew file mode 100644 index 00000000..18bcab66 --- /dev/null +++ b/kolourpaint/pics/cr32-action-tool_brush.png diff --git a/kolourpaint/pics/cr32-action-tool_color_picker.png b/kolourpaint/pics/cr32-action-tool_color_picker.png Binary files differnew file mode 100644 index 00000000..38197e44 --- /dev/null +++ b/kolourpaint/pics/cr32-action-tool_color_picker.png diff --git a/kolourpaint/pics/cr32-action-tool_color_washer.png b/kolourpaint/pics/cr32-action-tool_color_washer.png Binary files differnew file mode 100644 index 00000000..b49c03ca --- /dev/null +++ b/kolourpaint/pics/cr32-action-tool_color_washer.png diff --git a/kolourpaint/pics/cr32-action-tool_curve.png b/kolourpaint/pics/cr32-action-tool_curve.png Binary files differnew file mode 100644 index 00000000..db900434 --- /dev/null +++ b/kolourpaint/pics/cr32-action-tool_curve.png diff --git a/kolourpaint/pics/cr32-action-tool_ellipse.png b/kolourpaint/pics/cr32-action-tool_ellipse.png Binary files differnew file mode 100644 index 00000000..f6315fdc --- /dev/null +++ b/kolourpaint/pics/cr32-action-tool_ellipse.png diff --git a/kolourpaint/pics/cr32-action-tool_elliptical_selection.png b/kolourpaint/pics/cr32-action-tool_elliptical_selection.png Binary files differnew file mode 100644 index 00000000..72c5fc7c --- /dev/null +++ b/kolourpaint/pics/cr32-action-tool_elliptical_selection.png diff --git a/kolourpaint/pics/cr32-action-tool_eraser.png b/kolourpaint/pics/cr32-action-tool_eraser.png Binary files differnew file mode 100644 index 00000000..92efe970 --- /dev/null +++ b/kolourpaint/pics/cr32-action-tool_eraser.png diff --git a/kolourpaint/pics/cr32-action-tool_flood_fill.png b/kolourpaint/pics/cr32-action-tool_flood_fill.png Binary files differnew file mode 100644 index 00000000..6c116a71 --- /dev/null +++ b/kolourpaint/pics/cr32-action-tool_flood_fill.png diff --git a/kolourpaint/pics/cr32-action-tool_free_form_selection.png b/kolourpaint/pics/cr32-action-tool_free_form_selection.png Binary files differnew file mode 100644 index 00000000..b27f17d2 --- /dev/null +++ b/kolourpaint/pics/cr32-action-tool_free_form_selection.png diff --git a/kolourpaint/pics/cr32-action-tool_line.png b/kolourpaint/pics/cr32-action-tool_line.png Binary files differnew file mode 100644 index 00000000..b42db17f --- /dev/null +++ b/kolourpaint/pics/cr32-action-tool_line.png diff --git a/kolourpaint/pics/cr32-action-tool_pen.png b/kolourpaint/pics/cr32-action-tool_pen.png Binary files differnew file mode 100644 index 00000000..a5881690 --- /dev/null +++ b/kolourpaint/pics/cr32-action-tool_pen.png diff --git a/kolourpaint/pics/cr32-action-tool_polygon.png b/kolourpaint/pics/cr32-action-tool_polygon.png Binary files differnew file mode 100644 index 00000000..5f643e3c --- /dev/null +++ b/kolourpaint/pics/cr32-action-tool_polygon.png diff --git a/kolourpaint/pics/cr32-action-tool_polyline.png b/kolourpaint/pics/cr32-action-tool_polyline.png Binary files differnew file mode 100644 index 00000000..fabc5a48 --- /dev/null +++ b/kolourpaint/pics/cr32-action-tool_polyline.png diff --git a/kolourpaint/pics/cr32-action-tool_rect_selection.png b/kolourpaint/pics/cr32-action-tool_rect_selection.png Binary files differnew file mode 100644 index 00000000..c13c43b9 --- /dev/null +++ b/kolourpaint/pics/cr32-action-tool_rect_selection.png diff --git a/kolourpaint/pics/cr32-action-tool_rectangle.png b/kolourpaint/pics/cr32-action-tool_rectangle.png Binary files differnew file mode 100644 index 00000000..a271b7e5 --- /dev/null +++ b/kolourpaint/pics/cr32-action-tool_rectangle.png diff --git a/kolourpaint/pics/cr32-action-tool_rounded_rectangle.png b/kolourpaint/pics/cr32-action-tool_rounded_rectangle.png Binary files differnew file mode 100644 index 00000000..ed572dda --- /dev/null +++ b/kolourpaint/pics/cr32-action-tool_rounded_rectangle.png diff --git a/kolourpaint/pics/cr32-action-tool_spraycan.png b/kolourpaint/pics/cr32-action-tool_spraycan.png Binary files differnew file mode 100644 index 00000000..b908810c --- /dev/null +++ b/kolourpaint/pics/cr32-action-tool_spraycan.png diff --git a/kolourpaint/pics/cr32-action-tool_text.png b/kolourpaint/pics/cr32-action-tool_text.png Binary files differnew file mode 100644 index 00000000..a80b07a6 --- /dev/null +++ b/kolourpaint/pics/cr32-action-tool_text.png diff --git a/kolourpaint/pics/cr48-action-tool_brush.png b/kolourpaint/pics/cr48-action-tool_brush.png Binary files differnew file mode 100644 index 00000000..55758f0d --- /dev/null +++ b/kolourpaint/pics/cr48-action-tool_brush.png diff --git a/kolourpaint/pics/cr48-action-tool_color_picker.png b/kolourpaint/pics/cr48-action-tool_color_picker.png Binary files differnew file mode 100644 index 00000000..e5414d6e --- /dev/null +++ b/kolourpaint/pics/cr48-action-tool_color_picker.png diff --git a/kolourpaint/pics/cr48-action-tool_color_washer.png b/kolourpaint/pics/cr48-action-tool_color_washer.png Binary files differnew file mode 100644 index 00000000..2966d680 --- /dev/null +++ b/kolourpaint/pics/cr48-action-tool_color_washer.png diff --git a/kolourpaint/pics/cr48-action-tool_curve.png b/kolourpaint/pics/cr48-action-tool_curve.png Binary files differnew file mode 100644 index 00000000..c046a3ab --- /dev/null +++ b/kolourpaint/pics/cr48-action-tool_curve.png diff --git a/kolourpaint/pics/cr48-action-tool_ellipse.png b/kolourpaint/pics/cr48-action-tool_ellipse.png Binary files differnew file mode 100644 index 00000000..a17095b1 --- /dev/null +++ b/kolourpaint/pics/cr48-action-tool_ellipse.png diff --git a/kolourpaint/pics/cr48-action-tool_elliptical_selection.png b/kolourpaint/pics/cr48-action-tool_elliptical_selection.png Binary files differnew file mode 100644 index 00000000..637b9603 --- /dev/null +++ b/kolourpaint/pics/cr48-action-tool_elliptical_selection.png diff --git a/kolourpaint/pics/cr48-action-tool_eraser.png b/kolourpaint/pics/cr48-action-tool_eraser.png Binary files differnew file mode 100644 index 00000000..69e5b77a --- /dev/null +++ b/kolourpaint/pics/cr48-action-tool_eraser.png diff --git a/kolourpaint/pics/cr48-action-tool_flood_fill.png b/kolourpaint/pics/cr48-action-tool_flood_fill.png Binary files differnew file mode 100644 index 00000000..a2937a95 --- /dev/null +++ b/kolourpaint/pics/cr48-action-tool_flood_fill.png diff --git a/kolourpaint/pics/cr48-action-tool_free_form_selection.png b/kolourpaint/pics/cr48-action-tool_free_form_selection.png Binary files differnew file mode 100644 index 00000000..b0dd0ae9 --- /dev/null +++ b/kolourpaint/pics/cr48-action-tool_free_form_selection.png diff --git a/kolourpaint/pics/cr48-action-tool_line.png b/kolourpaint/pics/cr48-action-tool_line.png Binary files differnew file mode 100644 index 00000000..6d28915b --- /dev/null +++ b/kolourpaint/pics/cr48-action-tool_line.png diff --git a/kolourpaint/pics/cr48-action-tool_pen.png b/kolourpaint/pics/cr48-action-tool_pen.png Binary files differnew file mode 100644 index 00000000..16d0f2f3 --- /dev/null +++ b/kolourpaint/pics/cr48-action-tool_pen.png diff --git a/kolourpaint/pics/cr48-action-tool_polygon.png b/kolourpaint/pics/cr48-action-tool_polygon.png Binary files differnew file mode 100644 index 00000000..d06b5b64 --- /dev/null +++ b/kolourpaint/pics/cr48-action-tool_polygon.png diff --git a/kolourpaint/pics/cr48-action-tool_polyline.png b/kolourpaint/pics/cr48-action-tool_polyline.png Binary files differnew file mode 100644 index 00000000..2d859d96 --- /dev/null +++ b/kolourpaint/pics/cr48-action-tool_polyline.png diff --git a/kolourpaint/pics/cr48-action-tool_rect_selection.png b/kolourpaint/pics/cr48-action-tool_rect_selection.png Binary files differnew file mode 100644 index 00000000..71b59563 --- /dev/null +++ b/kolourpaint/pics/cr48-action-tool_rect_selection.png diff --git a/kolourpaint/pics/cr48-action-tool_rectangle.png b/kolourpaint/pics/cr48-action-tool_rectangle.png Binary files differnew file mode 100644 index 00000000..9320c2d4 --- /dev/null +++ b/kolourpaint/pics/cr48-action-tool_rectangle.png diff --git a/kolourpaint/pics/cr48-action-tool_rounded_rectangle.png b/kolourpaint/pics/cr48-action-tool_rounded_rectangle.png Binary files differnew file mode 100644 index 00000000..91b8a185 --- /dev/null +++ b/kolourpaint/pics/cr48-action-tool_rounded_rectangle.png diff --git a/kolourpaint/pics/cr48-action-tool_spraycan.png b/kolourpaint/pics/cr48-action-tool_spraycan.png Binary files differnew file mode 100644 index 00000000..cb653e54 --- /dev/null +++ b/kolourpaint/pics/cr48-action-tool_spraycan.png diff --git a/kolourpaint/pics/cr48-action-tool_text.png b/kolourpaint/pics/cr48-action-tool_text.png Binary files differnew file mode 100644 index 00000000..1d007c02 --- /dev/null +++ b/kolourpaint/pics/cr48-action-tool_text.png diff --git a/kolourpaint/pics/crsc-action-tool_brush.svgz b/kolourpaint/pics/crsc-action-tool_brush.svgz Binary files differnew file mode 100644 index 00000000..5559a91b --- /dev/null +++ b/kolourpaint/pics/crsc-action-tool_brush.svgz diff --git a/kolourpaint/pics/crsc-action-tool_color_picker.svgz b/kolourpaint/pics/crsc-action-tool_color_picker.svgz Binary files differnew file mode 100644 index 00000000..810cfe6b --- /dev/null +++ b/kolourpaint/pics/crsc-action-tool_color_picker.svgz diff --git a/kolourpaint/pics/crsc-action-tool_color_washer.svgz b/kolourpaint/pics/crsc-action-tool_color_washer.svgz Binary files differnew file mode 100644 index 00000000..00f72a44 --- /dev/null +++ b/kolourpaint/pics/crsc-action-tool_color_washer.svgz diff --git a/kolourpaint/pics/crsc-action-tool_curve.svgz b/kolourpaint/pics/crsc-action-tool_curve.svgz Binary files differnew file mode 100644 index 00000000..fa995555 --- /dev/null +++ b/kolourpaint/pics/crsc-action-tool_curve.svgz diff --git a/kolourpaint/pics/crsc-action-tool_ellipse.svgz b/kolourpaint/pics/crsc-action-tool_ellipse.svgz Binary files differnew file mode 100644 index 00000000..42ca1349 --- /dev/null +++ b/kolourpaint/pics/crsc-action-tool_ellipse.svgz diff --git a/kolourpaint/pics/crsc-action-tool_elliptical_selection.svgz b/kolourpaint/pics/crsc-action-tool_elliptical_selection.svgz Binary files differnew file mode 100644 index 00000000..6b7c23b2 --- /dev/null +++ b/kolourpaint/pics/crsc-action-tool_elliptical_selection.svgz diff --git a/kolourpaint/pics/crsc-action-tool_eraser.svgz b/kolourpaint/pics/crsc-action-tool_eraser.svgz Binary files differnew file mode 100644 index 00000000..1a0278cc --- /dev/null +++ b/kolourpaint/pics/crsc-action-tool_eraser.svgz diff --git a/kolourpaint/pics/crsc-action-tool_flood_fill.svgz b/kolourpaint/pics/crsc-action-tool_flood_fill.svgz Binary files differnew file mode 100644 index 00000000..24725599 --- /dev/null +++ b/kolourpaint/pics/crsc-action-tool_flood_fill.svgz diff --git a/kolourpaint/pics/crsc-action-tool_free_form_selection.svgz b/kolourpaint/pics/crsc-action-tool_free_form_selection.svgz Binary files differnew file mode 100644 index 00000000..2f304923 --- /dev/null +++ b/kolourpaint/pics/crsc-action-tool_free_form_selection.svgz diff --git a/kolourpaint/pics/crsc-action-tool_line.svgz b/kolourpaint/pics/crsc-action-tool_line.svgz Binary files differnew file mode 100644 index 00000000..f2cb1822 --- /dev/null +++ b/kolourpaint/pics/crsc-action-tool_line.svgz diff --git a/kolourpaint/pics/crsc-action-tool_pen.svgz b/kolourpaint/pics/crsc-action-tool_pen.svgz Binary files differnew file mode 100644 index 00000000..da783fda --- /dev/null +++ b/kolourpaint/pics/crsc-action-tool_pen.svgz diff --git a/kolourpaint/pics/crsc-action-tool_polygon.svgz b/kolourpaint/pics/crsc-action-tool_polygon.svgz Binary files differnew file mode 100644 index 00000000..2e8e9066 --- /dev/null +++ b/kolourpaint/pics/crsc-action-tool_polygon.svgz diff --git a/kolourpaint/pics/crsc-action-tool_polyline.svgz b/kolourpaint/pics/crsc-action-tool_polyline.svgz Binary files differnew file mode 100644 index 00000000..f5f2e881 --- /dev/null +++ b/kolourpaint/pics/crsc-action-tool_polyline.svgz diff --git a/kolourpaint/pics/crsc-action-tool_rect_selection.svgz b/kolourpaint/pics/crsc-action-tool_rect_selection.svgz Binary files differnew file mode 100644 index 00000000..ac7ae6cf --- /dev/null +++ b/kolourpaint/pics/crsc-action-tool_rect_selection.svgz diff --git a/kolourpaint/pics/crsc-action-tool_rectangle.svgz b/kolourpaint/pics/crsc-action-tool_rectangle.svgz Binary files differnew file mode 100644 index 00000000..c12ebf7c --- /dev/null +++ b/kolourpaint/pics/crsc-action-tool_rectangle.svgz diff --git a/kolourpaint/pics/crsc-action-tool_rounded_rectangle.svgz b/kolourpaint/pics/crsc-action-tool_rounded_rectangle.svgz Binary files differnew file mode 100644 index 00000000..37625f3f --- /dev/null +++ b/kolourpaint/pics/crsc-action-tool_rounded_rectangle.svgz diff --git a/kolourpaint/pics/crsc-action-tool_spraycan.svgz b/kolourpaint/pics/crsc-action-tool_spraycan.svgz Binary files differnew file mode 100644 index 00000000..6095c388 --- /dev/null +++ b/kolourpaint/pics/crsc-action-tool_spraycan.svgz diff --git a/kolourpaint/pics/crsc-action-tool_text.svgz b/kolourpaint/pics/crsc-action-tool_text.svgz Binary files differnew file mode 100644 index 00000000..c6be2b8f --- /dev/null +++ b/kolourpaint/pics/crsc-action-tool_text.svgz diff --git a/kolourpaint/pics/custom/Makefile.am b/kolourpaint/pics/custom/Makefile.am new file mode 100644 index 00000000..e796e21a --- /dev/null +++ b/kolourpaint/pics/custom/Makefile.am @@ -0,0 +1,10 @@ +customicondir = $(kde_datadir)/kolourpaint/pics +customicon_DATA = tool_spraycan_9x9.png tool_spraycan_17x17.png tool_spraycan_29x29.png \ + color_transparent_26x26.png colorbutton_swap_16x16.png \ + option_opaque.png option_transparent.png \ + resize.png scale.png smooth_scale.png \ + image_skew_horizontal.png image_skew_vertical.png \ + image_rotate_anticlockwise.png image_rotate_clockwise.png + +EXTRA_DIST = $(customicon_DATA) + diff --git a/kolourpaint/pics/custom/color_transparent_26x26.png b/kolourpaint/pics/custom/color_transparent_26x26.png Binary files differnew file mode 100644 index 00000000..3ba3d39e --- /dev/null +++ b/kolourpaint/pics/custom/color_transparent_26x26.png diff --git a/kolourpaint/pics/custom/colorbutton_swap_16x16.png b/kolourpaint/pics/custom/colorbutton_swap_16x16.png Binary files differnew file mode 100644 index 00000000..cebe9ed5 --- /dev/null +++ b/kolourpaint/pics/custom/colorbutton_swap_16x16.png diff --git a/kolourpaint/pics/custom/image_rotate_anticlockwise.png b/kolourpaint/pics/custom/image_rotate_anticlockwise.png Binary files differnew file mode 100644 index 00000000..8ef21bf2 --- /dev/null +++ b/kolourpaint/pics/custom/image_rotate_anticlockwise.png diff --git a/kolourpaint/pics/custom/image_rotate_clockwise.png b/kolourpaint/pics/custom/image_rotate_clockwise.png Binary files differnew file mode 100644 index 00000000..dee00f4a --- /dev/null +++ b/kolourpaint/pics/custom/image_rotate_clockwise.png diff --git a/kolourpaint/pics/custom/image_skew_horizontal.png b/kolourpaint/pics/custom/image_skew_horizontal.png Binary files differnew file mode 100644 index 00000000..c27c8223 --- /dev/null +++ b/kolourpaint/pics/custom/image_skew_horizontal.png diff --git a/kolourpaint/pics/custom/image_skew_vertical.png b/kolourpaint/pics/custom/image_skew_vertical.png Binary files differnew file mode 100644 index 00000000..e84ac257 --- /dev/null +++ b/kolourpaint/pics/custom/image_skew_vertical.png diff --git a/kolourpaint/pics/custom/option_opaque.png b/kolourpaint/pics/custom/option_opaque.png Binary files differnew file mode 100644 index 00000000..ab442ecb --- /dev/null +++ b/kolourpaint/pics/custom/option_opaque.png diff --git a/kolourpaint/pics/custom/option_transparent.png b/kolourpaint/pics/custom/option_transparent.png Binary files differnew file mode 100644 index 00000000..e751d7e9 --- /dev/null +++ b/kolourpaint/pics/custom/option_transparent.png diff --git a/kolourpaint/pics/custom/resize.png b/kolourpaint/pics/custom/resize.png Binary files differnew file mode 100644 index 00000000..0046cbcf --- /dev/null +++ b/kolourpaint/pics/custom/resize.png diff --git a/kolourpaint/pics/custom/scale.png b/kolourpaint/pics/custom/scale.png Binary files differnew file mode 100644 index 00000000..9f0904dd --- /dev/null +++ b/kolourpaint/pics/custom/scale.png diff --git a/kolourpaint/pics/custom/smooth_scale.png b/kolourpaint/pics/custom/smooth_scale.png Binary files differnew file mode 100644 index 00000000..5c48a6e2 --- /dev/null +++ b/kolourpaint/pics/custom/smooth_scale.png diff --git a/kolourpaint/pics/custom/tool_spraycan_17x17.png b/kolourpaint/pics/custom/tool_spraycan_17x17.png Binary files differnew file mode 100644 index 00000000..c5d228b0 --- /dev/null +++ b/kolourpaint/pics/custom/tool_spraycan_17x17.png diff --git a/kolourpaint/pics/custom/tool_spraycan_29x29.png b/kolourpaint/pics/custom/tool_spraycan_29x29.png Binary files differnew file mode 100644 index 00000000..47cfce16 --- /dev/null +++ b/kolourpaint/pics/custom/tool_spraycan_29x29.png diff --git a/kolourpaint/pics/custom/tool_spraycan_9x9.png b/kolourpaint/pics/custom/tool_spraycan_9x9.png Binary files differnew file mode 100644 index 00000000..85dde5a8 --- /dev/null +++ b/kolourpaint/pics/custom/tool_spraycan_9x9.png diff --git a/kolourpaint/pics/hi16-app-kolourpaint.png b/kolourpaint/pics/hi16-app-kolourpaint.png Binary files differnew file mode 100644 index 00000000..7802218f --- /dev/null +++ b/kolourpaint/pics/hi16-app-kolourpaint.png diff --git a/kolourpaint/pics/hi22-app-kolourpaint.png b/kolourpaint/pics/hi22-app-kolourpaint.png Binary files differnew file mode 100644 index 00000000..9f411e5c --- /dev/null +++ b/kolourpaint/pics/hi22-app-kolourpaint.png diff --git a/kolourpaint/pics/hi32-app-kolourpaint.png b/kolourpaint/pics/hi32-app-kolourpaint.png Binary files differnew file mode 100644 index 00000000..33e3e6f2 --- /dev/null +++ b/kolourpaint/pics/hi32-app-kolourpaint.png diff --git a/kolourpaint/pics/hi48-app-kolourpaint.png b/kolourpaint/pics/hi48-app-kolourpaint.png Binary files differnew file mode 100644 index 00000000..6878b110 --- /dev/null +++ b/kolourpaint/pics/hi48-app-kolourpaint.png diff --git a/kolourpaint/pics/hisc-app-kolourpaint.svgz b/kolourpaint/pics/hisc-app-kolourpaint.svgz Binary files differnew file mode 100644 index 00000000..9823cf97 --- /dev/null +++ b/kolourpaint/pics/hisc-app-kolourpaint.svgz diff --git a/kolourpaint/pixmapfx/Makefile.am b/kolourpaint/pixmapfx/Makefile.am new file mode 100644 index 00000000..dfa1d697 --- /dev/null +++ b/kolourpaint/pixmapfx/Makefile.am @@ -0,0 +1,19 @@ +INCLUDES = -I$(srcdir)/.. -I$(srcdir)/../cursors -I$(srcdir)/../interfaces \ + -I$(srcdir)/../pixmapfx \ + -I$(srcdir)/../tools \ + -I$(srcdir)/../views \ + -I$(srcdir)/../widgets $(all_includes) + +noinst_LTLIBRARIES = libkolourpaintpixmapfx.la +libkolourpaintpixmapfx_la_SOURCES = kpcoloreffect.cpp \ + kpeffectbalance.cpp \ + kpeffectblursharpen.cpp \ + kpeffectemboss.cpp \ + kpeffectflatten.cpp \ + kpeffectinvert.cpp \ + kpeffectreducecolors.cpp \ + kpeffectsdialog.cpp \ + kpfloodfill.cpp \ + kppixmapfx.cpp + +METASOURCES = AUTO diff --git a/kolourpaint/pixmapfx/kpcoloreffect.cpp b/kolourpaint/pixmapfx/kpcoloreffect.cpp new file mode 100644 index 00000000..1660c1fa --- /dev/null +++ b/kolourpaint/pixmapfx/kpcoloreffect.cpp @@ -0,0 +1,168 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <kpcoloreffect.h> + +#include <qapplication.h> +#include <qpixmap.h> + +#include <kdialog.h> +#include <klocale.h> + +#include <kpdefs.h> +#include <kpdocument.h> +#include <kpmainwindow.h> +#include <kpselection.h> + + +kpColorEffectCommand::kpColorEffectCommand (const QString &name, + bool actOnSelection, + kpMainWindow *mainWindow) + : kpCommand (mainWindow), + m_name (name), + m_actOnSelection (actOnSelection), + m_oldPixmapPtr (0) +{ +} + +kpColorEffectCommand::~kpColorEffectCommand () +{ + delete m_oldPixmapPtr; m_oldPixmapPtr = 0; +} + + +// public virtual [base kpCommand] +QString kpColorEffectCommand::name () const +{ + if (m_actOnSelection) + return i18n ("Selection: %1").arg (m_name); + else + return m_name; +} + + +// public virtual [base kpCommand] +int kpColorEffectCommand::size () const +{ + return kpPixmapFX::pixmapSize (m_oldPixmapPtr); +} + + +// public virtual [base kpCommand] +void kpColorEffectCommand::execute () +{ + kpDocument *doc = document (); + if (!doc) + return; + + QApplication::setOverrideCursor (Qt::waitCursor); + + + const QPixmap oldPixmap = *doc->pixmap (m_actOnSelection); + + if (!isInvertible ()) + { + m_oldPixmapPtr = new QPixmap (); + *m_oldPixmapPtr = oldPixmap; + } + + + QPixmap newPixmap = /*pure virtual*/applyColorEffect (oldPixmap); + + doc->setPixmap (m_actOnSelection, newPixmap); + + + QApplication::restoreOverrideCursor (); +} + +// public virtual [base kpCommand] +void kpColorEffectCommand::unexecute () +{ + kpDocument *doc = document (); + if (!doc) + return; + + QApplication::setOverrideCursor (Qt::waitCursor); + + + QPixmap newPixmap; + + if (!isInvertible ()) + { + newPixmap = *m_oldPixmapPtr; + } + else + { + newPixmap = /*pure virtual*/applyColorEffect (*doc->pixmap (m_actOnSelection)); + } + + doc->setPixmap (m_actOnSelection, newPixmap); + + + delete m_oldPixmapPtr; m_oldPixmapPtr = 0; + + + QApplication::restoreOverrideCursor (); +} + + +kpColorEffectWidget::kpColorEffectWidget (bool actOnSelection, + kpMainWindow *mainWindow, + QWidget *parent, const char *name) + : QWidget (parent, name), + m_actOnSelection (actOnSelection), + m_mainWindow (mainWindow) +{ +} + +kpColorEffectWidget::~kpColorEffectWidget () +{ +} + + +// public +QString kpColorEffectWidget::caption () const +{ + return QString::null; +} + + +// protected +int kpColorEffectWidget::marginHint () const +{ + return 0; +} + +// protected +int kpColorEffectWidget::spacingHint () const +{ + return KDialog::spacingHint (); +} + + +#include <kpcoloreffect.moc> diff --git a/kolourpaint/pixmapfx/kpcoloreffect.h b/kolourpaint/pixmapfx/kpcoloreffect.h new file mode 100644 index 00000000..8b3dfd09 --- /dev/null +++ b/kolourpaint/pixmapfx/kpcoloreffect.h @@ -0,0 +1,111 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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_EFFECT_H +#define KP_COLOR_EFFECT_H + +#include <qstring.h> +#include <qwidget.h> + +#include <kpcommandhistory.h> + +class QPixmap; + +class kpDocument; +class kpMainWindow; + + +class kpColorEffectCommand : public kpCommand +{ +public: + kpColorEffectCommand (const QString &name, + bool actOnSelection, + kpMainWindow *mainWindow); + virtual ~kpColorEffectCommand (); + + virtual QString name () const; + virtual int size () const; + +public: + virtual void execute (); + virtual void unexecute (); + +public: + // Return true if applyColorEffect(applyColorEffect(pixmap)) == pixmap + // to avoid storing the old pixmap, saving memory. + virtual bool isInvertible () const { return false; } + +protected: + virtual QPixmap applyColorEffect (const QPixmap &pixmap) = 0; + +private: + QString m_name; + bool m_actOnSelection; + + QPixmap *m_oldPixmapPtr; +}; + + +class kpColorEffectWidget : public QWidget +{ +Q_OBJECT + +public: + kpColorEffectWidget (bool actOnSelection, + kpMainWindow *mainWindow, + QWidget *parent, const char *name = 0); + virtual ~kpColorEffectWidget (); + +signals: + void settingsChangedNoWaitCursor (); + + void settingsChanged (); + + // (same as settingsChanged() but preview doesn't update until there + // has been no activity for a while - used for sliders in slow effects) + void settingsChangedDelayed (); + +public: + virtual QString caption () const; + + virtual bool isNoOp () const = 0; + virtual QPixmap applyColorEffect (const QPixmap &pixmap) = 0; + + virtual kpColorEffectCommand *createCommand () const = 0; + +protected: + int marginHint () const; + int spacingHint () const; + +protected: + bool m_actOnSelection; + kpMainWindow *m_mainWindow; +}; + + +#endif // KP_COLOR_EFFECT_H diff --git a/kolourpaint/pixmapfx/kpeffectbalance.cpp b/kolourpaint/pixmapfx/kpeffectbalance.cpp new file mode 100644 index 00000000..f4494d29 --- /dev/null +++ b/kolourpaint/pixmapfx/kpeffectbalance.cpp @@ -0,0 +1,517 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <math.h> + +#include <qfontmetrics.h> +#include <qimage.h> +#include <qlabel.h> +#include <qlayout.h> +#include <qpixmap.h> +#include <qpushbutton.h> + +#include <kcombobox.h> +#include <kdebug.h> +#include <kimageeffect.h> +#include <klocale.h> +#include <knuminput.h> + +#include <kppixmapfx.h> + + +#if DEBUG_KP_EFFECT_BALANCE + #include <qdatetime.h> +#endif + + +kpEffectBalanceCommand::kpEffectBalanceCommand (int channels, + int brightness, int contrast, int gamma, + bool actOnSelection, + kpMainWindow *mainWindow) + : kpColorEffectCommand (i18n ("Balance"), actOnSelection, mainWindow), + m_channels (channels), + m_brightness (brightness), m_contrast (contrast), m_gamma (gamma) +{ +} + +kpEffectBalanceCommand::~kpEffectBalanceCommand () +{ +} + + +static inline int between0And255 (int val) +{ + if (val < 0) + return 0; + else if (val > 255) + return 255; + else + 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 * pow (base / 255.0, 1.0 / 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); +} + +static inline QRgb brightnessContrastGammaForRGB (QRgb rgb, + int channels, + int brightness, int contrast, int gamma) +{ + int red = qRed (rgb); + int green = qGreen (rgb); + int blue = qBlue (rgb); + + + if (channels & kpEffectBalanceCommand::Red) + red = brightnessContrastGamma (red, brightness, contrast, gamma); + if (channels & kpEffectBalanceCommand::Green) + green = brightnessContrastGamma (green, brightness, contrast, gamma); + if (channels & kpEffectBalanceCommand::Blue) + blue = brightnessContrastGamma (blue, brightness, contrast, gamma); + + + return qRgba (red, green, blue, qAlpha (rgb)); +} + + +// public static +QPixmap kpEffectBalanceCommand::applyColorEffect (const QPixmap &pixmap, + int channels, + int brightness, int contrast, int gamma) +{ +#if DEBUG_KP_EFFECT_BALANCE + kdDebug () << "kpEffectBalanceCommand::applyColorEffect(" + << "channels=" << channels + << ",brightness=" << brightness + << ",contrast=" << contrast + << ",gamma=" << gamma + << ")" << endl; + QTime timer; timer.start (); +#endif + + QImage image = kpPixmapFX::convertToImage (pixmap); +#if DEBUG_KP_EFFECT_BALANCE + kdDebug () << "\tconvertToImage=" << timer.restart () << endl; +#endif + + + Q_UINT8 transformRed [256], + transformGreen [256], + transformBlue [256]; + + for (int i = 0; i < 256; i++) + { + Q_UINT8 applied = (Q_UINT8) brightnessContrastGamma (i, brightness, contrast, gamma); + + if (channels & kpEffectBalanceCommand::Red) + transformRed [i] = applied; + else + transformRed [i] = i; + + if (channels & kpEffectBalanceCommand::Green) + transformGreen [i] = applied; + else + transformGreen [i] = i; + + if (channels & kpEffectBalanceCommand::Blue) + transformBlue [i] = applied; + else + transformBlue [i] = i; + } + +#if DEBUG_KP_EFFECT_BALANCE + kdDebug () << "\tbuild lookup=" << timer.restart () << endl; +#endif + + + if (image.depth () > 8) + { + for (int y = 0; y < image.height (); y++) + { + for (int x = 0; x < image.width (); x++) + { + const QRgb rgb = image.pixel (x, y); + + const Q_UINT8 red = (Q_UINT8) qRed (rgb); + const Q_UINT8 green = (Q_UINT8) qGreen (rgb); + const Q_UINT8 blue = (Q_UINT8) qBlue (rgb); + const Q_UINT8 alpha = (Q_UINT8) qAlpha (rgb); + + image.setPixel (x, y, + qRgba (transformRed [red], + transformGreen [green], + transformBlue [blue], + alpha)); + + #if 0 + image.setPixel (x, y, + brightnessContrastGammaForRGB (image.pixel (x, y), + channels, + brightness, contrast, gamma)); + #endif + } + } + } + else + { + for (int i = 0; i < image.numColors (); i++) + { + const QRgb rgb = image.color (i); + + const Q_UINT8 red = (Q_UINT8) qRed (rgb); + const Q_UINT8 green = (Q_UINT8) qGreen (rgb); + const Q_UINT8 blue = (Q_UINT8) qBlue (rgb); + const Q_UINT8 alpha = (Q_UINT8) qAlpha (rgb); + + image.setColor (i, + qRgba (transformRed [red], + transformGreen [green], + transformBlue [blue], + alpha)); + + #if 0 + image.setColor (i, + brightnessContrastGammaForRGB (image.color (i), + channels, + brightness, contrast, gamma)); + #endif + } + + } +#if DEBUG_KP_EFFECT_BALANCE + kdDebug () << "\teffect=" << timer.restart () << endl; +#endif + + const QPixmap retPixmap = kpPixmapFX::convertToPixmap (image); +#if DEBUG_KP_EFFECT_BALANCE + kdDebug () << "\tconvertToPixmap=" << timer.restart () << endl; +#endif + + return retPixmap; +} + +// protected virtual [base kpColorEffectCommand] +QPixmap kpEffectBalanceCommand::applyColorEffect (const QPixmap &pixmap) +{ + return applyColorEffect (pixmap, m_channels, + m_brightness, m_contrast, m_gamma); +} + + + +kpEffectBalanceWidget::kpEffectBalanceWidget (bool actOnSelection, + kpMainWindow *mainWindow, + QWidget *parent, const char *name) + : kpColorEffectWidget (actOnSelection, mainWindow, parent, name) +{ + QGridLayout *lay = new QGridLayout (this, 5, 5, marginHint (), spacingHint ()); + + + QLabel *brightnessLabel = new QLabel (i18n ("&Brightness:"), this); + m_brightnessInput = new KIntNumInput (0/*value*/, this); + m_brightnessInput->setRange (-50, 50, 1/*step*/, true/*slider*/); + QPushButton *brightnessResetPushButton = new QPushButton (i18n ("Re&set"), this); + + QLabel *contrastLabel = new QLabel (i18n ("Co&ntrast:"), this); + m_contrastInput = new KIntNumInput (0/*value*/, this); + m_contrastInput->setRange (-50, 50, 1/*step*/, true/*slider*/); + QPushButton *contrastResetPushButton = new QPushButton (i18n ("&Reset"), this); + + QLabel *gammaLabel = new QLabel (i18n ("&Gamma:"), this); + m_gammaInput = new KIntNumInput (0/*value*/, this); + m_gammaInput->setRange (-50, 50, 1/*step*/, true/*slider*/); + // TODO: This is what should be shown in the m_gammaInput spinbox + m_gammaLabel = new QLabel (this); + // TODO: This doesn't seem to be wide enough with some fonts so the + // whole layout moves when we drag the gamma slider. + m_gammaLabel->setMinimumWidth (m_gammaLabel->fontMetrics ().width (" 10.00 ")); + m_gammaLabel->setAlignment (m_gammaLabel->alignment () | Qt::AlignRight); + QPushButton *gammaResetPushButton = new QPushButton (i18n ("Rese&t"), this); + + + QWidget *spaceWidget = new QLabel (this); + spaceWidget->setFixedSize (1, spacingHint ()); + + + QLabel *channelLabel = new QLabel (i18n ("C&hannels:"), this); + m_channelsComboBox = new KComboBox (this); + m_channelsComboBox->insertItem (i18n ("All")); + m_channelsComboBox->insertItem (i18n ("Red")); + m_channelsComboBox->insertItem (i18n ("Green")); + m_channelsComboBox->insertItem (i18n ("Blue")); + + + QPushButton *resetPushButton = new QPushButton (i18n ("Reset &All Values"), this); + + + brightnessLabel->setBuddy (m_brightnessInput); + contrastLabel->setBuddy (m_contrastInput); + gammaLabel->setBuddy (m_gammaInput); + + channelLabel->setBuddy (m_channelsComboBox); + + + lay->addWidget (brightnessLabel, 0, 0); + lay->addMultiCellWidget (m_brightnessInput, 0, 0, 1, 2); + lay->addWidget (brightnessResetPushButton, 0, 4); + + lay->addWidget (contrastLabel, 1, 0); + lay->addMultiCellWidget (m_contrastInput, 1, 1, 1, 2); + lay->addWidget (contrastResetPushButton, 1, 4); + + lay->addWidget (gammaLabel, 2, 0); + lay->addMultiCellWidget (m_gammaInput, 2, 2, 1, 2); + lay->addWidget (m_gammaLabel, 2, 3); + lay->addWidget (gammaResetPushButton, 2, 4); + + lay->addMultiCellWidget (spaceWidget, 3, 3, 0, 4); + lay->addMultiCellWidget (resetPushButton, 4, 4, 2, 4, Qt::AlignRight); + + lay->addWidget (channelLabel, 4, 0); + lay->addWidget (m_channelsComboBox, 4, 1, Qt::AlignLeft); + //lay->addWidget (resetPushButton, 4, 2, Qt::AlignRight); + + lay->setColStretch (1, 1); + + + // (no need for settingsChangedDelayed() since BCG effect is so fast :)) + connect (m_brightnessInput, SIGNAL (valueChanged (int)), + this, SIGNAL (settingsChangedNoWaitCursor ())); + connect (m_contrastInput, SIGNAL (valueChanged (int)), + this, SIGNAL (settingsChangedNoWaitCursor ())); + + connect (m_gammaInput, SIGNAL (valueChanged (int)), + this, SLOT (recalculateGammaLabel ())); + connect (m_gammaInput, SIGNAL (valueChanged (int)), + this, SIGNAL (settingsChangedNoWaitCursor ())); + + connect (m_channelsComboBox, SIGNAL (activated (int)), + this, SIGNAL (settingsChanged ())); + + connect (brightnessResetPushButton, SIGNAL (clicked ()), + this, SLOT (resetBrightness ())); + connect (contrastResetPushButton, SIGNAL (clicked ()), + this, SLOT (resetContrast ())); + connect (gammaResetPushButton, SIGNAL (clicked ()), + this, SLOT (resetGamma ())); + + connect (resetPushButton, SIGNAL (clicked ()), + this, SLOT (resetAll ())); + + + recalculateGammaLabel (); +} + +kpEffectBalanceWidget::~kpEffectBalanceWidget () +{ +} + + +// public virtual [base kpColorEffectWidget] +QString kpEffectBalanceWidget::caption () const +{ + return i18n ("Settings"); +} + + +// public virtual [base kpColorEffectWidget] +bool kpEffectBalanceWidget::isNoOp () const +{ + return (brightness () == 0 && contrast () == 0 && gamma () == 0); +} + +// public virtual [base kpColorEffectWidget] +QPixmap kpEffectBalanceWidget::applyColorEffect (const QPixmap &pixmap) +{ + return kpEffectBalanceCommand::applyColorEffect (pixmap, + channels (), brightness (), contrast (), gamma ()); +} + +// public virtual [base kpColorEffectWidget] +kpColorEffectCommand *kpEffectBalanceWidget::createCommand () const +{ + return new kpEffectBalanceCommand (channels (), + brightness (), contrast (), gamma (), + m_actOnSelection, + m_mainWindow); +} + + +// protected +int kpEffectBalanceWidget::channels () const +{ + switch (m_channelsComboBox->currentItem ()) + { + default: + case 0: + return kpEffectBalanceCommand::RGB; + + case 1: + return kpEffectBalanceCommand::Red; + + case 2: + return kpEffectBalanceCommand::Green; + + case 3: + return kpEffectBalanceCommand::Blue; + } +} + + +// protected +int kpEffectBalanceWidget::brightness () const +{ + return m_brightnessInput->value (); +} + +// protected +int kpEffectBalanceWidget::contrast () const +{ + return m_contrastInput->value (); +} + +// protected +int kpEffectBalanceWidget::gamma () const +{ + return m_gammaInput->value (); +} + + +// protected slot +void kpEffectBalanceWidget::recalculateGammaLabel () +{ + m_gammaLabel->setText ( + " " + + QString::number (pow (10, gamma () / 50.0), + 'f'/*[-]9.9*/, + 2/*precision*/) + + " "); + m_gammaLabel->repaint (); +} + + +// protected slot +void kpEffectBalanceWidget::resetBrightness () +{ + if (brightness () == 0) + return; + + bool sb = signalsBlocked (); + + if (!sb) blockSignals (true); + m_brightnessInput->setValue (0); + if (!sb) blockSignals (false); + + // Immediate update (if signals aren't blocked) + emit settingsChanged (); +} + +// protected slot +void kpEffectBalanceWidget::resetContrast () +{ + if (contrast () == 0) + return; + + bool sb = signalsBlocked (); + + if (!sb) blockSignals (true); + m_contrastInput->setValue (0); + if (!sb) blockSignals (false); + + // Immediate update (if signals aren't blocked) + emit settingsChanged (); +} + +// protected slot +void kpEffectBalanceWidget::resetGamma () +{ + if (gamma () == 0) + return; + + bool sb = signalsBlocked (); + + if (!sb) blockSignals (true); + m_gammaInput->setValue (0); + recalculateGammaLabel (); + if (!sb) blockSignals (false); + + // Immediate update (if signals aren't blocked) + emit settingsChanged (); +} + + +// protected slot +void kpEffectBalanceWidget::resetAll () +{ + if (isNoOp ()) + return; + + // Prevent multiple settingsChanged() which would normally result in + // redundant, expensive preview repaints + blockSignals (true); + + resetBrightness (); + resetContrast (); + resetGamma (); + + recalculateGammaLabel (); + + blockSignals (false); + + emit settingsChanged (); +} + + +#include <kpeffectbalance.moc> diff --git a/kolourpaint/pixmapfx/kpeffectbalance.h b/kolourpaint/pixmapfx/kpeffectbalance.h new file mode 100644 index 00000000..b045159f --- /dev/null +++ b/kolourpaint/pixmapfx/kpeffectbalance.h @@ -0,0 +1,116 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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_EFFECT_BALANCE_H +#define KP_EFFECT_BALANCE_H + +#include <kpcoloreffect.h> + + +class QLabel; + +class KComboBox; +class KIntNumInput; + +class kpMainWindowe; + + +class kpEffectBalanceCommand : public kpColorEffectCommand +{ +public: + enum Channel + { + None = 0, + Red = 1, Green = 2, Blue = 4, + RGB = Red | Green | Blue + }; + + // <brightness>, <contrast> & <gamma> are from -50 to 50 + + kpEffectBalanceCommand (int channels, + int brightness, int contrast, int gamma, + bool actOnSelection, + kpMainWindow *mainWindow); + virtual ~kpEffectBalanceCommand (); + + static QPixmap applyColorEffect (const QPixmap &pixmap, + int channels, + int brightness, int contrast, int gamma); + +protected: + virtual QPixmap applyColorEffect (const QPixmap &pixmap); + +protected: + int m_channels; + int m_brightness, m_contrast, m_gamma; +}; + + +class kpEffectBalanceWidget : public kpColorEffectWidget +{ +Q_OBJECT + +public: + kpEffectBalanceWidget (bool actOnSelection, + kpMainWindow *mainWindow, + QWidget *parent, const char *name = 0); + virtual ~kpEffectBalanceWidget (); + + virtual QString caption () const; + + virtual bool isNoOp () const; + virtual QPixmap applyColorEffect (const QPixmap &pixmap); + + virtual kpColorEffectCommand *createCommand () const; + +protected: + int channels () const; + + int brightness () const; + int contrast () const; + int gamma () const; + +protected slots: + void recalculateGammaLabel (); + + void resetBrightness (); + void resetContrast (); + void resetGamma (); + + void resetAll (); + +protected: + KIntNumInput *m_brightnessInput, + *m_contrastInput, + *m_gammaInput; + QLabel *m_gammaLabel; + KComboBox *m_channelsComboBox; +}; + + +#endif // KP_EFFECT_BALANCE_H diff --git a/kolourpaint/pixmapfx/kpeffectblursharpen.cpp b/kolourpaint/pixmapfx/kpeffectblursharpen.cpp new file mode 100644 index 00000000..50c0b27d --- /dev/null +++ b/kolourpaint/pixmapfx/kpeffectblursharpen.cpp @@ -0,0 +1,291 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <qimage.h> +#include <qlabel.h> +#include <qlayout.h> +#include <qpixmap.h> +#include <qpushbutton.h> + +#include <kdebug.h> +#include <kimageeffect.h> +#include <klocale.h> +#include <knuminput.h> + +#include <kpmainwindow.h> +#include <kppixmapfx.h> + + +static QString nameForType (kpEffectBlurSharpenCommand::Type type) +{ + if (type == kpEffectBlurSharpenCommand::Blur) + return i18n ("Soften"); + else if (type == kpEffectBlurSharpenCommand::Sharpen) + return i18n ("Sharpen"); + else + return QString::null; +} + + +kpEffectBlurSharpenCommand::kpEffectBlurSharpenCommand (Type type, + double radius, double sigma, + int repeat, + bool actOnSelection, + kpMainWindow *mainWindow) + : kpColorEffectCommand (::nameForType (type), actOnSelection, mainWindow), + m_type (type), + m_radius (radius), m_sigma (sigma), + m_repeat (repeat) +{ +} + +kpEffectBlurSharpenCommand::~kpEffectBlurSharpenCommand () +{ +} + + +// public static +QPixmap kpEffectBlurSharpenCommand::apply (const QPixmap &pixmap, + Type type, double radius, double sigma, + int repeat) +{ +#if DEBUG_KP_EFFECT_BLUR_SHARPEN + kdDebug () << "kpEffectBlurSharpenCommand::apply(type=" + << int (type) + << " radius=" << radius + << " sigma=" << sigma + << " repeat=" << repeat + << ")" + << endl; +#endif + + // (KImageEffect::(blur|sharpen)() ignores mask) + QPixmap usePixmap = kpPixmapFX::pixmapWithDefinedTransparentPixels ( + pixmap, + Qt::white/*arbitrarily chosen*/); + + + QImage image = kpPixmapFX::convertToImage (usePixmap); + + for (int i = 0; i < repeat; i++) + { + if (type == Blur) + image = KImageEffect::blur (image, radius, sigma); + else if (type == Sharpen) + image = KImageEffect::sharpen (image, radius, sigma); + } + + QPixmap retPixmap = kpPixmapFX::convertToPixmap (image); + + + // KImageEffect::(blur|sharpen)() nukes mask - restore it + if (usePixmap.mask ()) + retPixmap.setMask (*usePixmap.mask ()); + + + return retPixmap; +} + +// protected virtual [base kpColorEffectCommand] +QPixmap kpEffectBlurSharpenCommand::applyColorEffect (const QPixmap &pixmap) +{ + return apply (pixmap, m_type, m_radius, m_sigma, m_repeat); +} + + + +kpEffectBlurSharpenWidget::kpEffectBlurSharpenWidget (bool actOnSelection, + kpMainWindow *mainWindow, + QWidget *parent, const char *name) + : kpColorEffectWidget (actOnSelection, mainWindow, parent, name) +{ + QGridLayout *lay = new QGridLayout (this, 4, 2, marginHint (), spacingHint ()); + + + QLabel *amountLabel = new QLabel (i18n ("&Amount:"), this); + m_amountInput = new KIntNumInput (this); + m_amountInput->setRange (-10, 10, 1/*step*/, true/*slider*/); + + m_typeLabel = new QLabel (this); + + + amountLabel->setBuddy (m_amountInput); + + + lay->addWidget (amountLabel, 0, 0); + lay->addWidget (m_amountInput, 0, 1); + + lay->addMultiCellWidget (m_typeLabel, 1, 1, 0, 1, Qt::AlignCenter); + + lay->setColStretch (1, 1); + + + connect (m_amountInput, SIGNAL (valueChanged (int)), + this, SIGNAL (settingsChangedDelayed ())); + + connect (m_amountInput, SIGNAL (valueChanged (int)), + this, SLOT (slotUpdateTypeLabel ())); +} + +kpEffectBlurSharpenWidget::~kpEffectBlurSharpenWidget () +{ +} + + +// public virtual [base kpColorEffectWidget] +QString kpEffectBlurSharpenWidget::caption () const +{ + return QString::null; +} + + +// public virtual [base kpColorEffectWidget] +bool kpEffectBlurSharpenWidget::isNoOp () const +{ + return (type () == kpEffectBlurSharpenCommand::None); +} + +// public virtual [base kpColorEffectWidget] +QPixmap kpEffectBlurSharpenWidget::applyColorEffect (const QPixmap &pixmap) +{ + return kpEffectBlurSharpenCommand::apply (pixmap, + type (), radius (), sigma (), repeat ()); +} + +// public virtual [base kpColorEffectWidget] +kpColorEffectCommand *kpEffectBlurSharpenWidget::createCommand () const +{ + return new kpEffectBlurSharpenCommand (type (), radius (), sigma (), repeat (), + m_actOnSelection, + m_mainWindow); +} + + +// protected slot +void kpEffectBlurSharpenWidget::slotUpdateTypeLabel () +{ + QString text = ::nameForType (type ()); + +#if DEBUG_KP_EFFECT_BLUR_SHARPEN + kdDebug () << "kpEffectBlurSharpenWidget::slotUpdateTypeLabel() text=" + << text << endl; +#endif + m_typeLabel->setText (text); +} + + +// protected +kpEffectBlurSharpenCommand::Type kpEffectBlurSharpenWidget::type () const +{ + if (m_amountInput->value () == 0) + return kpEffectBlurSharpenCommand::None; + else if (m_amountInput->value () < 0) + return kpEffectBlurSharpenCommand::Blur; + else + return kpEffectBlurSharpenCommand::Sharpen; +} + +// The numbers that follow were picked by experimentation. +// I still have no idea what "radius" and "sigma" mean +// (even after reading the API). + +// protected +double kpEffectBlurSharpenWidget::radius () const +{ + if (m_amountInput->value () == 0) + return 0; + + if (m_amountInput->value () < 0) + { + return 8; + } + else + { + const double SharpenMin = .1; + const double SharpenMax = 2.5; + + return SharpenMin + + (m_amountInput->value () - 1) * + (SharpenMax - SharpenMin) / + (m_amountInput->maxValue () - 1); + } +} + +// protected +double kpEffectBlurSharpenWidget::sigma () const +{ + if (m_amountInput->value () == 0) + return 0; + + if (m_amountInput->value () < 0) + { + const double BlurMin = .5; + const double BlurMax = 4; + + return BlurMin + + (-m_amountInput->value () - 1) * + (BlurMax - BlurMin) / + (-m_amountInput->minValue () - 1); + } + else + { + const double SharpenMin = .5; + const double SharpenMax = 3.0; + + return SharpenMin + + (m_amountInput->value () - 1) * + (SharpenMax - SharpenMin) / + (m_amountInput->maxValue () - 1); + } +} + +// protected +int kpEffectBlurSharpenWidget::repeat () const +{ + if (m_amountInput->value () == 0) + return 0; + + if (m_amountInput->value () < 0) + return 1; + else + { + const double SharpenMin = 1; + const double SharpenMax = 2; + + return qRound (SharpenMin + + (m_amountInput->value () - 1) * + (SharpenMax - SharpenMin) / + (m_amountInput->maxValue () - 1)); + } +} + +#include <kpeffectblursharpen.moc> diff --git a/kolourpaint/pixmapfx/kpeffectblursharpen.h b/kolourpaint/pixmapfx/kpeffectblursharpen.h new file mode 100644 index 00000000..3b12def1 --- /dev/null +++ b/kolourpaint/pixmapfx/kpeffectblursharpen.h @@ -0,0 +1,105 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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_EFFECT_BLUR_SHARPEN_H +#define KP_EFFECT_BLUR_SHARPEN_H + + +#include <kpcolor.h> + +#include <kpcoloreffect.h> + + +class QLabel; + +class KIntNumInput; + +class kpMainWindow; + + +class kpEffectBlurSharpenCommand : public kpColorEffectCommand +{ +public: + enum Type + { + None = 0, Blur, Sharpen + }; + + kpEffectBlurSharpenCommand (Type type, + double radius, double sigma, + int repeat, + bool actOnSelection, + kpMainWindow *mainWindow); + virtual ~kpEffectBlurSharpenCommand (); + + static QPixmap apply (const QPixmap &pixmap, + Type type, double radius, double sigma, + int repeat); + +protected: + virtual QPixmap applyColorEffect (const QPixmap &pixmap); + +protected: + Type m_type; + double m_radius, m_sigma; + int m_repeat; +}; + + +class kpEffectBlurSharpenWidget : public kpColorEffectWidget +{ +Q_OBJECT + +public: + kpEffectBlurSharpenWidget (bool actOnSelection, + kpMainWindow *mainWindow, + QWidget *parent, const char *name = 0); + virtual ~kpEffectBlurSharpenWidget (); + + virtual QString caption () const; + + virtual bool isNoOp () const; + virtual QPixmap applyColorEffect (const QPixmap &pixmap); + + virtual kpColorEffectCommand *createCommand () const; + +protected slots: + void slotUpdateTypeLabel (); + +protected: + kpEffectBlurSharpenCommand::Type type () const; + double radius () const; + double sigma () const; + int repeat () const; + + KIntNumInput *m_amountInput; + QLabel *m_typeLabel; +}; + + +#endif // KP_EFFECT_BLUR_SHARPEN_H diff --git a/kolourpaint/pixmapfx/kpeffectemboss.cpp b/kolourpaint/pixmapfx/kpeffectemboss.cpp new file mode 100644 index 00000000..e33f3a42 --- /dev/null +++ b/kolourpaint/pixmapfx/kpeffectemboss.cpp @@ -0,0 +1,228 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <qcheckbox.h> +#include <qimage.h> +#include <qlabel.h> +#include <qlayout.h> +#include <qpixmap.h> +#include <qpushbutton.h> + +#include <kdebug.h> +#include <kimageeffect.h> +#include <klocale.h> +#include <knuminput.h> + +#include <kpmainwindow.h> +#include <kppixmapfx.h> + + +kpEffectEmbossCommand::kpEffectEmbossCommand (double radius, double sigma, + int repeat, + bool actOnSelection, + kpMainWindow *mainWindow) + : kpColorEffectCommand (i18n ("Emboss"), actOnSelection, mainWindow), + m_radius (radius), m_sigma (sigma), + m_repeat (repeat) +{ +} + +kpEffectEmbossCommand::~kpEffectEmbossCommand () +{ +} + + +// public static +QPixmap kpEffectEmbossCommand::apply (const QPixmap &pixmap, + double radius, double sigma, + int repeat) +{ +#if DEBUG_KP_EFFECT_EMBOSS + kdDebug () << "kpEffectEmbossCommand::apply()" + << " radius=" << radius + << " sigma=" << sigma + << " repeat=" << repeat + << ")" + << endl; +#endif + + // (KImageEffect::emboss() ignores mask) + QPixmap usePixmap = kpPixmapFX::pixmapWithDefinedTransparentPixels ( + pixmap, + Qt::white/*arbitrarily chosen*/); + + + QImage image = kpPixmapFX::convertToImage (usePixmap); + + for (int i = 0; i < repeat; i++) + { + image = KImageEffect::emboss (image, radius, sigma); + } + + QPixmap retPixmap = kpPixmapFX::convertToPixmap (image); + + + // KImageEffect::emboss() nukes mask - restore it + if (usePixmap.mask ()) + retPixmap.setMask (*usePixmap.mask ()); + + + return retPixmap; +} + +// protected virtual [base kpColorEffectCommand] +QPixmap kpEffectEmbossCommand::applyColorEffect (const QPixmap &pixmap) +{ + return apply (pixmap, m_radius, m_sigma, m_repeat); +} + + + +kpEffectEmbossWidget::kpEffectEmbossWidget (bool actOnSelection, + kpMainWindow *mainWindow, + QWidget *parent, const char *name) + : kpColorEffectWidget (actOnSelection, mainWindow, parent, name) +{ + QGridLayout *lay = new QGridLayout (this, 4, 2, marginHint (), spacingHint ()); + + +#if 0 + QLabel *amountLabel = new QLabel (i18n ("&Amount:"), this); + m_amountInput = new KIntNumInput (this); + m_amountInput->setRange (0, 10, 1/*step*/, true/*slider*/); + m_amountInput->setSpecialValueText (i18n ("None")); + + + amountLabel->setBuddy (m_amountInput); + + + lay->addWidget (amountLabel, 0, 0); + lay->addWidget (m_amountInput, 0, 1); + + lay->setColStretch (1, 1); + + + connect (m_amountInput, SIGNAL (valueChanged (int)), + this, SIGNAL (settingsChanged ())); +#endif + + m_enableCheckBox = new QCheckBox (i18n ("E&nable"), this); + + + lay->addMultiCellWidget (m_enableCheckBox, 0, 0, 0, 1, Qt::AlignCenter); + + + // (settingsChangedDelayed() instead of settingsChanged() so that the + // user can quickly press OK to apply effect to document directly and + // not have to wait for the also slow preview) + connect (m_enableCheckBox, SIGNAL (toggled (bool)), + this, SIGNAL (settingsChangedDelayed ())); +} + +kpEffectEmbossWidget::~kpEffectEmbossWidget () +{ +} + + +// public virtual [base kpColorEffectWidget] +QString kpEffectEmbossWidget::caption () const +{ + return QString::null; +} + + +// public virtual [base kpColorEffectWidget] +bool kpEffectEmbossWidget::isNoOp () const +{ + //return (m_amountInput->value () == 0); + return !m_enableCheckBox->isChecked (); +} + +// public virtual [base kpColorEffectWidget] +QPixmap kpEffectEmbossWidget::applyColorEffect (const QPixmap &pixmap) +{ + if (isNoOp ()) + return pixmap; + + return kpEffectEmbossCommand::apply (pixmap, radius (), sigma (), repeat ()); +} + +// public virtual [base kpColorEffectWidget] +kpColorEffectCommand *kpEffectEmbossWidget::createCommand () const +{ + return new kpEffectEmbossCommand (radius (), sigma (), repeat (), + m_actOnSelection, + m_mainWindow); +} + + +// The numbers that follow were picked by experimentation. +// I still have no idea what "radius" and "sigma" mean +// (even after reading the API). + +// protected +double kpEffectEmbossWidget::radius () const +{ + //if (m_amountInput->value () == 0) + // return 0; + + return 0; +} + + +// protected +double kpEffectEmbossWidget::sigma () const +{ +#if 0 + if (m_amountInput->value () == 0) + return 0; + + const double Min = 1; + const double Max = 1.2; + + return Min + + (m_amountInput->maxValue () - m_amountInput->value ()) * + (Max - Min) / + (m_amountInput->maxValue () - 1); +#endif + + return 1; +} + +// protected +int kpEffectEmbossWidget::repeat () const +{ + return 1; +} + + +#include <kpeffectemboss.moc> diff --git a/kolourpaint/pixmapfx/kpeffectemboss.h b/kolourpaint/pixmapfx/kpeffectemboss.h new file mode 100644 index 00000000..0234627f --- /dev/null +++ b/kolourpaint/pixmapfx/kpeffectemboss.h @@ -0,0 +1,93 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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_EFFECT_EMBOSS_H +#define KP_EFFECT_EMBOSS_H + + +#include <kpcolor.h> + +#include <kpcoloreffect.h> + + +class QCheckBox; +class KIntNumInput; + +class kpMainWindow; + + +class kpEffectEmbossCommand : public kpColorEffectCommand +{ +public: + kpEffectEmbossCommand (double radius, double sigma, + int repeat, + bool actOnSelection, + kpMainWindow *mainWindow); + virtual ~kpEffectEmbossCommand (); + + static QPixmap apply (const QPixmap &pixmap, + double radius, double sigma, + int repeat); + +protected: + virtual QPixmap applyColorEffect (const QPixmap &pixmap); + +protected: + double m_radius, m_sigma; + int m_repeat; +}; + + +class kpEffectEmbossWidget : public kpColorEffectWidget +{ +Q_OBJECT + +public: + kpEffectEmbossWidget (bool actOnSelection, + kpMainWindow *mainWindow, + QWidget *parent, const char *name = 0); + virtual ~kpEffectEmbossWidget (); + + virtual QString caption () const; + + virtual bool isNoOp () const; + virtual QPixmap applyColorEffect (const QPixmap &pixmap); + + virtual kpColorEffectCommand *createCommand () const; + +protected: + double radius () const; + double sigma () const; + int repeat () const; + + //KIntNumInput *m_amountInput; + QCheckBox *m_enableCheckBox; +}; + + +#endif // KP_EFFECT_EMBOSS_H diff --git a/kolourpaint/pixmapfx/kpeffectflatten.cpp b/kolourpaint/pixmapfx/kpeffectflatten.cpp new file mode 100644 index 00000000..6a81bca0 --- /dev/null +++ b/kolourpaint/pixmapfx/kpeffectflatten.cpp @@ -0,0 +1,266 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <kpeffectflatten.h> + +#include <qcheckbox.h> +#include <qimage.h> +#include <qlayout.h> +#include <qpixmap.h> +#include <qvbox.h> + +#include <kcolorbutton.h> +#include <kconfig.h> +#include <kdialog.h> +#include <kdebug.h> +#include <kglobal.h> +#include <kimageeffect.h> +#include <klocale.h> + +#include <kpdefs.h> +#include <kppixmapfx.h> + + +// +// kpEffectFlattenCommand +// + +kpEffectFlattenCommand::kpEffectFlattenCommand (const QColor &color1, + const QColor &color2, + bool actOnSelection, + kpMainWindow *mainWindow) + : kpColorEffectCommand (i18n ("Flatten"), actOnSelection, mainWindow), + m_color1 (color1), m_color2 (color2) +{ +} + +kpEffectFlattenCommand::~kpEffectFlattenCommand () +{ +} + + +// public static +void kpEffectFlattenCommand::apply (QPixmap *destPixmapPtr, + const QColor &color1, const QColor &color2) +{ + if (!destPixmapPtr) + return; + + QImage image = kpPixmapFX::convertToImage (*destPixmapPtr); + apply (&image, color1, color2); + *destPixmapPtr = kpPixmapFX::convertToPixmap (image); +} + +// public static +QPixmap kpEffectFlattenCommand::apply (const QPixmap &pm, + const QColor &color1, const QColor &color2) +{ + QImage image = kpPixmapFX::convertToImage (pm); + apply (&image, color1, color2); + return kpPixmapFX::convertToPixmap (image); +} + +// public static +void kpEffectFlattenCommand::apply (QImage *destImagePtr, + const QColor &color1, const QColor &color2) +{ + if (!destImagePtr) + return; + + KImageEffect::flatten (*destImagePtr/*ref*/, color1, color2); +} + +// public static +QImage kpEffectFlattenCommand::apply (const QImage &img, + const QColor &color1, const QColor &color2) +{ + QImage retImage = img; + apply (&retImage, color1, color2); + return retImage; +} + + +// +// kpEffectFlattenCommand implements kpColorEffectCommand interface +// + +// protected virtual [base kpColorEffectCommand] +QPixmap kpEffectFlattenCommand::applyColorEffect (const QPixmap &pixmap) +{ + return apply (pixmap, m_color1, m_color2); +} + + +// +// kpEffectFlattenWidget +// + +// public static +// Don't initialise globally when we probably don't have a colour +// allocation context. This way, the colours aren't sometimes invalid +// (e.g. at 8-bit). +QColor kpEffectFlattenWidget::s_lastColor1; +QColor kpEffectFlattenWidget::s_lastColor2; + +kpEffectFlattenWidget::kpEffectFlattenWidget (bool actOnSelection, + kpMainWindow *mainWindow, + QWidget *parent, + const char *name) + : kpColorEffectWidget (actOnSelection, mainWindow, parent, name) +{ + if (!s_lastColor1.isValid () || !s_lastColor2.isValid ()) + { + KConfigGroupSaver cfgGroupSaver (KGlobal::config (), kpSettingsGroupFlattenEffect); + KConfigBase *cfg = cfgGroupSaver.config (); + + s_lastColor1 = cfg->readColorEntry (kpSettingFlattenEffectColor1); + if (!s_lastColor1.isValid ()) + s_lastColor1 = Qt::red; + + s_lastColor2 = cfg->readColorEntry (kpSettingFlattenEffectColor2); + if (!s_lastColor2.isValid ()) + s_lastColor2 = Qt::blue; + } + + + m_enableCheckBox = new QCheckBox (i18n ("E&nable"), this); + + QVBox *colorButtonContainer = new QVBox (this); + colorButtonContainer->setMargin (KDialog::marginHint () / 2); + colorButtonContainer->setSpacing (spacingHint ()); + m_color1Button = new KColorButton (s_lastColor1, colorButtonContainer); + m_color2Button = new KColorButton (s_lastColor2, colorButtonContainer); + + + m_color1Button->setEnabled (false); + m_color2Button->setEnabled (false); + + + QVBoxLayout *lay = new QVBoxLayout (this, marginHint (), spacingHint ()); + lay->addWidget (m_enableCheckBox); + lay->addWidget (colorButtonContainer); + + + connect (m_enableCheckBox, SIGNAL (toggled (bool)), + this, SLOT (slotEnableChanged (bool))); + + connect (m_color1Button, SIGNAL (changed (const QColor &)), + this, SIGNAL (settingsChanged ())); + connect (m_color2Button, SIGNAL (changed (const QColor &)), + this, SIGNAL (settingsChanged ())); +} + +kpEffectFlattenWidget::~kpEffectFlattenWidget () +{ + s_lastColor1 = color1 (); + s_lastColor2 = color2 (); + + + KConfigGroupSaver cfgGroupSaver (KGlobal::config (), kpSettingsGroupFlattenEffect); + KConfigBase *cfg = cfgGroupSaver.config (); + + cfg->writeEntry (kpSettingFlattenEffectColor1, s_lastColor1); + cfg->writeEntry (kpSettingFlattenEffectColor2, s_lastColor2); + cfg->sync (); +} + + +// public +QColor kpEffectFlattenWidget::color1 () const +{ + return m_color1Button->color (); +} + +// public +QColor kpEffectFlattenWidget::color2 () const +{ + return m_color2Button->color (); +} + + +// +// kpEffectFlattenWidget implements kpColorEffectWidget interface +// + +// public virtual [base kpColorEffectWidget] +QString kpEffectFlattenWidget::caption () const +{ + return i18n ("Colors"); +} + + +// public virtual [base kpColorEffectWidget] +bool kpEffectFlattenWidget::isNoOp () const +{ + return !m_enableCheckBox->isChecked (); +} + +// public virtual [base kpColorEffectWidget] +QPixmap kpEffectFlattenWidget::applyColorEffect (const QPixmap &pixmap) +{ +#if DEBUG_KP_EFFECT_FLATTEN + kdDebug () << "kpEffectFlattenWidget::applyColorEffect() nop=" + << isNoOp () << endl; +#endif + + if (isNoOp ()) + return pixmap; + + return kpEffectFlattenCommand::apply (pixmap, color1 (), color2 ()); +} + + +// public virtual [base kpColorEffectWidget] +kpColorEffectCommand *kpEffectFlattenWidget::createCommand () const +{ + return new kpEffectFlattenCommand (color1 (), color2 (), + m_actOnSelection, + m_mainWindow); +} + + +// protected slot: +void kpEffectFlattenWidget::slotEnableChanged (bool enable) +{ +#if DEBUG_KP_EFFECT_FLATTEN + kdDebug () << "kpEffectFlattenWidget::slotEnableChanged(" << enable + << ") enableButton=" << m_enableCheckBox->isChecked () + << endl; +#endif + + m_color1Button->setEnabled (enable); + m_color2Button->setEnabled (enable); + + emit settingsChanged (); +} + + +#include <kpeffectflatten.moc> + diff --git a/kolourpaint/pixmapfx/kpeffectflatten.h b/kolourpaint/pixmapfx/kpeffectflatten.h new file mode 100644 index 00000000..79c9bbaf --- /dev/null +++ b/kolourpaint/pixmapfx/kpeffectflatten.h @@ -0,0 +1,115 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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_EFFECT_FLATTEN_H +#define KP_EFFECT_FLATTEN_H + + +#include <qcolor.h> + +#include <kpcoloreffect.h> + + +class QCheckBox; +class QImage; + +class KColorButton; + +class kpMainWindow; + + +class kpEffectFlattenCommand : public kpColorEffectCommand +{ +public: + kpEffectFlattenCommand (const QColor &color1, const QColor &color2, + bool actOnSelection, + kpMainWindow *mainWindow); + virtual ~kpEffectFlattenCommand (); + + + static void apply (QPixmap *destPixmapPtr, + const QColor &color1, const QColor &color2); + static QPixmap apply (const QPixmap &pm, + const QColor &color1, const QColor &color2); + static void apply (QImage *destImagePtr, + const QColor &color1, const QColor &color2); + static QImage apply (const QImage &img, + const QColor &color1, const QColor &color2); + + + // + // kpColorEffectCommand interface + // + +protected: + virtual QPixmap applyColorEffect (const QPixmap &pixmap); + + QColor m_color1, m_color2; +}; + + +class kpEffectFlattenWidget : public kpColorEffectWidget +{ +Q_OBJECT + +public: + kpEffectFlattenWidget (bool actOnSelection, + kpMainWindow *mainWindow, + QWidget *parent, const char *name = 0); + virtual ~kpEffectFlattenWidget (); + + + static QColor s_lastColor1, s_lastColor2; + + + QColor color1 () const; + QColor color2 () const; + + + // + // kpColorEffectWidget interface + // + + virtual QString caption () const; + + virtual bool isNoOp () const; + virtual QPixmap applyColorEffect (const QPixmap &pixmap); + + virtual kpColorEffectCommand *createCommand () const; + +protected slots: + void slotEnableChanged (bool enable); + +protected: + QCheckBox *m_enableCheckBox; + KColorButton *m_color1Button, *m_color2Button; +}; + + + +#endif // KP_EFFECT_FLATTEN_H diff --git a/kolourpaint/pixmapfx/kpeffectinvert.cpp b/kolourpaint/pixmapfx/kpeffectinvert.cpp new file mode 100644 index 00000000..b9bb00a8 --- /dev/null +++ b/kolourpaint/pixmapfx/kpeffectinvert.cpp @@ -0,0 +1,315 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <qcheckbox.h> +#include <qimage.h> +#include <qlayout.h> +#include <qpixmap.h> + +#include <kdebug.h> +#include <klocale.h> + +#include <kppixmapfx.h> + + +// +// kpEffectInvertCommand +// + +kpEffectInvertCommand::kpEffectInvertCommand (int channels, + bool actOnSelection, + kpMainWindow *mainWindow) + : kpColorEffectCommand (channels == RGB ? + i18n ("Invert Colors") : i18n ("Invert"), + actOnSelection, mainWindow), + m_channels (channels) +{ +} + +kpEffectInvertCommand::kpEffectInvertCommand (bool actOnSelection, + kpMainWindow *mainWindow) + : kpColorEffectCommand (i18n ("Invert Colors"), actOnSelection, mainWindow), + m_channels (RGB) +{ +} + +kpEffectInvertCommand::~kpEffectInvertCommand () +{ +} + + +// public static +void kpEffectInvertCommand::apply (QPixmap *destPixmapPtr, int channels) +{ + QImage image = kpPixmapFX::convertToImage (*destPixmapPtr); + apply (&image, channels); + *destPixmapPtr = kpPixmapFX::convertToPixmap (image); +} + +// public static +QPixmap kpEffectInvertCommand::apply (const QPixmap &pm, int channels) +{ + QImage image = kpPixmapFX::convertToImage (pm); + apply (&image, channels); + return kpPixmapFX::convertToPixmap (image); +} + +// public static +void kpEffectInvertCommand::apply (QImage *destImagePtr, int channels) +{ + 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 + kdDebug () << "kpEffectInvertCommand::apply(channels=" << channels + << ") mask=" << (int *) mask + << endl; +#endif + + if (destImagePtr->depth () > 8) + { + #if 0 + // SYNC: TODO: Qt BUG - invertAlpha argument is inverted!!! + destImagePtr->invertPixels (true/*no invert alpha (Qt 3.2)*/); + #else + // 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); + } + } + #endif + } + else + { + for (int i = 0; i < destImagePtr->numColors (); i++) + { + destImagePtr->setColor (i, destImagePtr->color (i) ^ mask); + } + } +} + +// public static +QImage kpEffectInvertCommand::apply (const QImage &img, int channels) +{ + QImage retImage = img; + apply (&retImage, channels); + return retImage; +} + + +// +// kpEffectInvertCommand implements kpColorEffectCommand interface +// + +// protected virtual [base kpColorEffectCommand] +QPixmap kpEffectInvertCommand::applyColorEffect (const QPixmap &pixmap) +{ + return apply (pixmap, m_channels); +} + + +// +// kpEffectInvertWidget +// + +kpEffectInvertWidget::kpEffectInvertWidget (bool actOnSelection, + kpMainWindow *mainWindow, + QWidget *parent, + const char *name) + : kpColorEffectWidget (actOnSelection, mainWindow, parent, name) +{ + QVBoxLayout *topLevelLay = new QVBoxLayout (this, marginHint (), spacingHint ()); + + + QWidget *centerWidget = new QWidget (this); + topLevelLay->addWidget (centerWidget, 0/*stretch*/, Qt::AlignCenter); + + + QVBoxLayout *centerWidgetLay = new QVBoxLayout (centerWidget, + 0/*margin*/, + spacingHint ()); + + + m_redCheckBox = new QCheckBox (i18n ("&Red"), centerWidget); + m_greenCheckBox = new QCheckBox (i18n ("&Green"), centerWidget); + m_blueCheckBox = new QCheckBox (i18n ("&Blue"), centerWidget); + + QWidget *spaceWidget = new QWidget (centerWidget); + spaceWidget->setFixedSize (1, spacingHint ()); + + m_allCheckBox = new QCheckBox (i18n ("&All"), centerWidget); + + + m_redCheckBox->setChecked (false); + m_greenCheckBox->setChecked (false); + m_blueCheckBox->setChecked (false); + + m_allCheckBox->setChecked (false); + + + centerWidgetLay->addWidget (m_redCheckBox); + centerWidgetLay->addWidget (m_greenCheckBox); + centerWidgetLay->addWidget (m_blueCheckBox); + + centerWidgetLay->addWidget (spaceWidget); + + centerWidgetLay->addWidget (m_allCheckBox); + + + m_inSignalHandler = false; + connect (m_redCheckBox, SIGNAL (toggled (bool)), + this, SLOT (slotRGBCheckBoxToggled ())); + connect (m_greenCheckBox, SIGNAL (toggled (bool)), + this, SLOT (slotRGBCheckBoxToggled ())); + connect (m_blueCheckBox, SIGNAL (toggled (bool)), + this, SLOT (slotRGBCheckBoxToggled ())); + + connect (m_allCheckBox, SIGNAL (toggled (bool)), + this, SLOT (slotAllCheckBoxToggled ())); +} + +kpEffectInvertWidget::~kpEffectInvertWidget () +{ +} + + +// public +int kpEffectInvertWidget::channels () const +{ +#if DEBUG_KP_EFFECT_INVERT + kdDebug () << "kpEffectInvertWidget::channels()" + << " isChecked: r=" << m_redCheckBox->isChecked () + << " g=" << m_greenCheckBox->isChecked () + << " b=" << m_blueCheckBox->isChecked () + << endl; +#endif + + int channels = 0; + + + if (m_redCheckBox->isChecked ()) + channels |= kpEffectInvertCommand::Red; + + if (m_greenCheckBox->isChecked ()) + channels |= kpEffectInvertCommand::Green; + + if (m_blueCheckBox->isChecked ()) + channels |= kpEffectInvertCommand::Blue; + + +#if DEBUG_KP_EFFECT_INVERT + kdDebug () << "\treturning channels=" << (int *) channels << endl; +#endif + return channels; +} + + +// +// kpEffectInvertWidget implements kpColorEffectWidget interface +// + +// public virtual [base kpColorEffectWidget] +QString kpEffectInvertWidget::caption () const +{ + return i18n ("Channels"); +} + + +// public virtual [base kpColorEffectWidget] +bool kpEffectInvertWidget::isNoOp () const +{ + return (channels () == kpEffectInvertCommand::None); +} + +// public virtual [base kpColorEffectWidget] +QPixmap kpEffectInvertWidget::applyColorEffect (const QPixmap &pixmap) +{ + return kpEffectInvertCommand::apply (pixmap, channels ()); +} + + +// public virtual [base kpColorEffectWidget] +kpColorEffectCommand *kpEffectInvertWidget::createCommand () const +{ + return new kpEffectInvertCommand (channels (), + m_actOnSelection, + m_mainWindow); +} + + +// protected slots +void kpEffectInvertWidget::slotRGBCheckBoxToggled () +{ + if (m_inSignalHandler) + return; + + m_inSignalHandler = true; + + //blockSignals (true); + m_allCheckBox->setChecked (m_redCheckBox->isChecked () && + m_blueCheckBox->isChecked () && + m_greenCheckBox->isChecked ()); + //blockSignals (false); + + emit settingsChanged (); + + m_inSignalHandler = false; +} + +// protected slot +void kpEffectInvertWidget::slotAllCheckBoxToggled () +{ + if (m_inSignalHandler) + return; + + m_inSignalHandler = true; + + //blockSignals (true); + m_redCheckBox->setChecked (m_allCheckBox->isChecked ()); + m_greenCheckBox->setChecked (m_allCheckBox->isChecked ()); + m_blueCheckBox->setChecked (m_allCheckBox->isChecked ()); + //blockSignals (false); + + emit settingsChanged (); + + m_inSignalHandler = false; +} + + +#include <kpeffectinvert.moc> + diff --git a/kolourpaint/pixmapfx/kpeffectinvert.h b/kolourpaint/pixmapfx/kpeffectinvert.h new file mode 100644 index 00000000..61d6cfda --- /dev/null +++ b/kolourpaint/pixmapfx/kpeffectinvert.h @@ -0,0 +1,130 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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_EFFECT_INVERT_H +#define KP_EFFECT_INVERT_H + + +#include <kpcoloreffect.h> + + +class QCheckBox; +class QImage; + +class kpMainWindow; + + +class kpEffectInvertCommand : public kpColorEffectCommand +{ +public: + enum Channel + { + None = 0, + Red = 1, Green = 2, Blue = 4, + RGB = Red | Green | Blue + }; + + kpEffectInvertCommand (int channels, + bool actOnSelection, + kpMainWindow *mainWindow); + kpEffectInvertCommand (bool actOnSelection, + kpMainWindow *mainWindow); + virtual ~kpEffectInvertCommand (); + + + // + // 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 apply (QPixmap *destPixmapPtr, int channels = RGB); + static QPixmap apply (const QPixmap &pm, int channels = RGB); + static void apply (QImage *destImagePtr, int channels = RGB); + static QImage apply (const QImage &img, int channels = RGB); + + + // + // kpColorEffectCommand interface + // + +public: + virtual bool isInvertible () const { return true; } + +protected: + virtual QPixmap applyColorEffect (const QPixmap &pixmap); + + int m_channels; +}; + + +class kpEffectInvertWidget : public kpColorEffectWidget +{ +Q_OBJECT + +public: + kpEffectInvertWidget (bool actOnSelection, + kpMainWindow *mainWindow, + QWidget *parent, const char *name = 0); + virtual ~kpEffectInvertWidget (); + + + int channels () const; + + + // + // kpColorEffectWidget interface + // + + virtual QString caption () const; + + virtual bool isNoOp () const; + virtual QPixmap applyColorEffect (const QPixmap &pixmap); + + virtual kpColorEffectCommand *createCommand () const; + +protected slots: + void slotRGBCheckBoxToggled (); + void slotAllCheckBoxToggled (); + +protected: + QCheckBox *m_redCheckBox, *m_greenCheckBox, *m_blueCheckBox, + *m_allCheckBox; + + // blockSignals() didn't seem to work + bool m_inSignalHandler; +}; + + + +#endif // KP_EFFECT_INVERT_H diff --git a/kolourpaint/pixmapfx/kpeffectreducecolors.cpp b/kolourpaint/pixmapfx/kpeffectreducecolors.cpp new file mode 100644 index 00000000..b6eb7a42 --- /dev/null +++ b/kolourpaint/pixmapfx/kpeffectreducecolors.cpp @@ -0,0 +1,446 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <kpeffectreducecolors.h> + +#include <qbuttongroup.h> +#include <qcheckbox.h> +#include <qimage.h> +#include <qlayout.h> +#include <qpixmap.h> +#include <qradiobutton.h> + +#include <kdebug.h> +#include <klocale.h> + +#include <kppixmapfx.h> + + +QImage convertImageDepth (const QImage &image, int depth, bool dither) +{ +#if DEBUG_KP_EFFECT_REDUCE_COLORS + kdDebug () << "::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::convertDepth(1, ...) (with + // dithering off) which produces pathetic results with an image that + // only has 2 colours - sometimes it just gives a completely black + // result. Instead, we simply preserve the 2 colours. One use case + // is resaving a "colour monochrome" image (<= 2 colours but not + // necessarily black & white). + if (depth == 1 && !dither) + { + #if DEBUG_KP_EFFECT_REDUCE_COLORS + kdDebug () << "\tinvoking convert-to-depth 1 hack" << endl; + #endif + QRgb color0, color1; + bool color0Valid = false, color1Valid = false; + + bool moreThan2Colors = false; + + QImage monoImage (image.width (), image.height (), + 1/*depth*/, 2/*numColors*/, QImage::LittleEndian); + #if DEBUG_KP_EFFECT_REDUCE_COLORS + kdDebug () << "\t\tinitialising output image w=" << monoImage.width () + << ",h=" << monoImage.height () + << ",d=" << monoImage.depth () + << endl; + #endif + for (int y = 0; y < image.height (); y++) + { + for (int x = 0; x < image.width (); x++) + { + 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 + kdDebug () << "\t\t\tcolor0=" << (int *) color0 + << " at x=" << x << ",y=" << y << endl; + #endif + } + else if (!color1Valid) + { + color1 = imagePixel; + color1Valid = true; + monoImage.setPixel (x, y, 1); + #if DEBUG_KP_EFFECT_REDUCE_COLORS + kdDebug () << "\t\t\tcolor1=" << (int *) color1 + << " at x=" << x << ",y=" << y << endl; + #endif + } + else + { + #if DEBUG_KP_EFFECT_REDUCE_COLORS + kdDebug () << "\t\t\timagePixel=" << (int *) imagePixel + << " at x=" << x << ",y=" << y + << " moreThan2Colors - abort hack" << endl; + #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.convertDepth (depth, + Qt::AutoColor | + (dither ? Qt::DiffuseDither : Qt::ThresholdDither) | + Qt::ThresholdAlphaDither | + (dither ? Qt::PreferDither : Qt::AvoidDither)); + +#if DEBUG_KP_EFFECT_REDUCE_COLORS && 0 + kdDebug () << "After colour reduction:" << endl; + 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; +} + + +// +// kpEffectReduceColorsCommand +// + +kpEffectReduceColorsCommand::kpEffectReduceColorsCommand (int depth, bool dither, + bool actOnSelection, + kpMainWindow *mainWindow) + : kpColorEffectCommand (commandName (depth, dither), actOnSelection, mainWindow), + m_depth (depth), m_dither (dither) +{ +} + +kpEffectReduceColorsCommand::~kpEffectReduceColorsCommand () +{ +} + + +// public +QString kpEffectReduceColorsCommand::commandName (int depth, int dither) const +{ + if (depth == 1) + { + if (dither) + return i18n ("Reduce to Monochrome (Dithered)"); + else + return i18n ("Reduce to Monochrome"); + } + else if (depth == 8) + { + if (dither) + return i18n ("Reduce to 256 Color (Dithered)"); + else + return i18n ("Reduce to 256 Color"); + } + else + { + return QString::null; + } +} + + +// public static +void kpEffectReduceColorsCommand::apply (QPixmap *destPixmapPtr, int depth, bool dither) +{ + if (!destPixmapPtr) + return; + + if (depth != 1 && depth != 8) + return; + + + QImage image = kpPixmapFX::convertToImage (*destPixmapPtr); + + + image = ::convertImageDepth (image, depth, dither); + + if (image.isNull ()) + return; + + + QPixmap pixmap = kpPixmapFX::convertToPixmap (image, false/*no dither*/); + + + // HACK: The above "image.convertDepth()" erases the Alpha Channel + // (at least for monochrome). + // qpixmap.html says "alpha masks on monochrome images are ignored." + // + // Put the mask back. + // + if (destPixmapPtr->mask ()) + pixmap.setMask (*destPixmapPtr->mask ()); + + *destPixmapPtr = pixmap; +} + +// public static +QPixmap kpEffectReduceColorsCommand::apply (const QPixmap &pm, int depth, bool dither) +{ + QPixmap ret = pm; + apply (&ret, depth, dither); + return ret; +} + + +// +// kpEffectReduceColorsCommand implements kpColorEffectCommand interface +// + +// protected virtual [base kpColorEffectCommand] +QPixmap kpEffectReduceColorsCommand::applyColorEffect (const QPixmap &pixmap) +{ + return apply (pixmap, m_depth, m_dither); +} + + +// +// kpEffectReduceColorsWidget +// + +kpEffectReduceColorsWidget::kpEffectReduceColorsWidget (bool actOnSelection, + kpMainWindow *mainWindow, + QWidget *parent, + const char *name) + : kpColorEffectWidget (actOnSelection, mainWindow, parent, name) +{ + QVBoxLayout *lay = new QVBoxLayout (this, marginHint (), spacingHint ()); + + + m_blackAndWhiteRadioButton = + new QRadioButton (i18n ("&Monochrome"), this); + + m_blackAndWhiteDitheredRadioButton = + new QRadioButton (i18n ("Mo&nochrome (dithered)"), this); + + m_8BitRadioButton = new QRadioButton (i18n ("256 co&lor"), this); + + m_8BitDitheredRadioButton = new QRadioButton (i18n ("256 colo&r (dithered)"), this); + + m_24BitRadioButton = new QRadioButton (i18n ("24-&bit color"), this); + + + QButtonGroup *buttonGroup = new QButtonGroup (this); + buttonGroup->hide (); + + buttonGroup->insert (m_blackAndWhiteRadioButton); + buttonGroup->insert (m_blackAndWhiteDitheredRadioButton); + buttonGroup->insert (m_8BitRadioButton); + buttonGroup->insert (m_8BitDitheredRadioButton); + buttonGroup->insert (m_24BitRadioButton); + + + const int screenDepth = QPixmap::defaultDepth (); +#if DEBUG_KP_EFFECT_REDUCE_COLORS + kdDebug () << "kpEffectReduceColorsWidget::<ctor> screenDepth=" + << screenDepth + << endl; +#endif + + // Note that everything is disabled for a 1-bit screen since there + // would be no effect. I won't support 2-bit or 4-bit screens either :) + m_blackAndWhiteRadioButton->setEnabled (screenDepth >= 8); + m_blackAndWhiteDitheredRadioButton->setEnabled (screenDepth >= 8); + m_8BitRadioButton->setEnabled (screenDepth >= 8); + // (not enabled if screenDepth==8 as m_8BitRadioButton already serves + // as NOP default) + m_8BitDitheredRadioButton->setEnabled (screenDepth > 8); + // (not "screenDepth >= 24" as we need a NOP default for 15/16-bit + // screens) + m_24BitRadioButton->setEnabled (screenDepth > 8); + + + m_defaultRadioButton = 0; + + if (m_24BitRadioButton->isEnabled ()) + { + #if DEBUG_KP_EFFECT_REDUCE_COLORS + kdDebug () << "\tdefault is 24-bit button" << endl; + #endif + m_defaultRadioButton = m_24BitRadioButton; + } + else if (m_8BitRadioButton->isEnabled ()) + { + #if DEBUG_KP_EFFECT_REDUCE_COLORS + kdDebug () << "\tdefault is 8-bit button" << endl; + #endif + m_defaultRadioButton = m_8BitRadioButton; + } + else + { + #if DEBUG_KP_EFFECT_REDUCE_COLORS + kdDebug () << "\tuser must have a 1-bit screen - no default" << endl; + #endif + } + + + if (m_defaultRadioButton) + m_defaultRadioButton->setChecked (true); + + + lay->addWidget (m_blackAndWhiteRadioButton); + lay->addWidget (m_blackAndWhiteDitheredRadioButton); + lay->addWidget (m_8BitRadioButton); + lay->addWidget (m_8BitDitheredRadioButton); + lay->addWidget (m_24BitRadioButton); + + + connect (m_blackAndWhiteRadioButton, SIGNAL (toggled (bool)), + this, SIGNAL (settingsChanged ())); + connect (m_blackAndWhiteDitheredRadioButton, SIGNAL (toggled (bool)), + this, SIGNAL (settingsChanged ())); + connect (m_8BitRadioButton, SIGNAL (toggled (bool)), + this, SIGNAL (settingsChanged ())); + connect (m_8BitDitheredRadioButton, SIGNAL (toggled (bool)), + this, SIGNAL (settingsChanged ())); + connect (m_24BitRadioButton, SIGNAL (toggled (bool)), + this, SIGNAL (settingsChanged ())); +} + +kpEffectReduceColorsWidget::~kpEffectReduceColorsWidget () +{ +} + + +// public +int kpEffectReduceColorsWidget::depth () const +{ + if (m_blackAndWhiteRadioButton->isChecked () || + m_blackAndWhiteDitheredRadioButton->isChecked ()) + { + return 1; + } + else if (m_8BitRadioButton->isChecked () || + m_8BitDitheredRadioButton->isChecked ()) + { + return 8; + } + else if (m_24BitRadioButton->isChecked ()) + { + return 24; + } + else + { + return 0; + } +} + +// public +bool kpEffectReduceColorsWidget::dither () const +{ + return (m_blackAndWhiteDitheredRadioButton->isChecked () || + m_8BitDitheredRadioButton->isChecked ()); +} + + +// +// kpEffectReduceColorsWidget implements kpColorEffectWidget interface +// + +// public virtual [base kpColorEffectWidget] +QString kpEffectReduceColorsWidget::caption () const +{ + return i18n ("Reduce To"); +} + + +// public virtual [base kpColorEffectWidget] +bool kpEffectReduceColorsWidget::isNoOp () const +{ + return (!m_defaultRadioButton || m_defaultRadioButton->isChecked ()); +} + +// public virtual [base kpColorEffectWidget] +QPixmap kpEffectReduceColorsWidget::applyColorEffect (const QPixmap &pixmap) +{ + return kpEffectReduceColorsCommand::apply (pixmap, depth (), dither ()); +} + + +// public virtual [base kpColorEffectWidget] +kpColorEffectCommand *kpEffectReduceColorsWidget::createCommand () const +{ + return new kpEffectReduceColorsCommand (depth (), dither (), + m_actOnSelection, + m_mainWindow); +} + + +#include <kpeffectreducecolors.moc> + diff --git a/kolourpaint/pixmapfx/kpeffectreducecolors.h b/kolourpaint/pixmapfx/kpeffectreducecolors.h new file mode 100644 index 00000000..a14cffc7 --- /dev/null +++ b/kolourpaint/pixmapfx/kpeffectreducecolors.h @@ -0,0 +1,110 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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_EFFECT_REDUCE_COLORS_H +#define KP_EFFECT_REDUCE_COLORS_H + + +#include <kpcoloreffect.h> + + +class QRadioButton; +class QImage; + +class kpMainWindow; + + +QImage convertImageDepth (const QImage &image, int depth, bool dither); + + +class kpEffectReduceColorsCommand : public kpColorEffectCommand +{ +public: + // depth must be 1 or 8 + kpEffectReduceColorsCommand (int depth, bool dither, + bool actOnSelection, + kpMainWindow *mainWindow); + virtual ~kpEffectReduceColorsCommand (); + + QString commandName (int depth, int dither) const; + + // (always preserves mask) + static void apply (QPixmap *destPixmapPtr, int depth, bool dither); + static QPixmap apply (const QPixmap &pm, int depth, bool dither); + + + // + // kpColorEffectCommand interface + // + +protected: + virtual QPixmap applyColorEffect (const QPixmap &pixmap); + + int m_depth; + bool m_dither; +}; + + +class kpEffectReduceColorsWidget : public kpColorEffectWidget +{ +Q_OBJECT + +public: + kpEffectReduceColorsWidget (bool actOnSelection, + kpMainWindow *mainWindow, + QWidget *parent, const char *name = 0); + virtual ~kpEffectReduceColorsWidget (); + + + int depth () const; + bool dither () const; + + + // + // kpColorEffectWidget interface + // + + virtual QString caption () const; + + virtual bool isNoOp () const; + virtual QPixmap applyColorEffect (const QPixmap &pixmap); + + virtual kpColorEffectCommand *createCommand () const; + +protected: + QRadioButton *m_blackAndWhiteRadioButton, + *m_blackAndWhiteDitheredRadioButton, + *m_8BitRadioButton, + *m_8BitDitheredRadioButton, + *m_24BitRadioButton; + QRadioButton *m_defaultRadioButton; +}; + + + +#endif // KP_EFFECT_REDUCE_COLORS_H diff --git a/kolourpaint/pixmapfx/kpeffectsdialog.cpp b/kolourpaint/pixmapfx/kpeffectsdialog.cpp new file mode 100644 index 00000000..666f81cf --- /dev/null +++ b/kolourpaint/pixmapfx/kpeffectsdialog.cpp @@ -0,0 +1,369 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <qgroupbox.h> +#include <qhbox.h> +#include <qlabel.h> +#include <qlayout.h> +#include <qtimer.h> + +#include <kapplication.h> +#include <kcombobox.h> +#include <kconfig.h> +#include <kdebug.h> +#include <klocale.h> + +#include <kpdefs.h> +#include <kpdocument.h> +#include <kpeffectbalance.h> +#include <kpeffectblursharpen.h> +#include <kpeffectemboss.h> +#include <kpeffectflatten.h> +#include <kpeffectinvert.h> +#include <kpeffectreducecolors.h> +#include <kppixmapfx.h> + + +// protected static +int kpEffectsDialog::s_lastWidth = 640; +int kpEffectsDialog::s_lastHeight = 620; + + +kpEffectsDialog::kpEffectsDialog (bool actOnSelection, + kpMainWindow *parent, + const char *name) + : kpToolPreviewDialog (kpToolPreviewDialog::Preview, + true/*reserve top row*/, + QString::null/*caption*/, + QString::null/*afterActionText (no Dimensions Group Box)*/, + actOnSelection, + parent, + name), + m_delayedUpdateTimer (new QTimer (this)), + m_effectsComboBox (0), + m_settingsGroupBox (0), + m_settingsLayout (0), + m_colorEffectWidget (0) +{ +#if DEBUG_KP_EFFECTS_DIALOG + kdDebug () << "kpEffectsDialog::kpEffectsDialog()" << endl; +#endif + + if (actOnSelection) + setCaption (i18n ("More Image Effects (Selection)")); + else + setCaption (i18n ("More Image Effects")); + + + connect (m_delayedUpdateTimer, SIGNAL (timeout ()), + this, SLOT (slotUpdateWithWaitCursor ())); + + + QHBox *effectContainer = new QHBox (mainWidget ()); + effectContainer->setSpacing (spacingHint () * 4 + /*need more space for QGroupBox titles*/); + effectContainer->setMargin (0); + + QLabel *label = new QLabel (i18n ("&Effect:"), effectContainer); + + m_effectsComboBox = new KComboBox (effectContainer); + m_effectsComboBox->insertItem (i18n ("Balance")); + m_effectsComboBox->insertItem (i18n ("Emboss")); + m_effectsComboBox->insertItem (i18n ("Flatten")); + m_effectsComboBox->insertItem (i18n ("Invert")); + m_effectsComboBox->insertItem (i18n ("Reduce Colors")); + m_effectsComboBox->insertItem (i18n ("Soften & Sharpen")); + + label->setBuddy (m_effectsComboBox); + effectContainer->setStretchFactor (m_effectsComboBox, 1); + + addCustomWidgetToFront (effectContainer); + + + m_settingsGroupBox = new QGroupBox (mainWidget ()); + m_settingsLayout = new QVBoxLayout (m_settingsGroupBox, + marginHint () * 2, + spacingHint ()); + addCustomWidgetToBack (m_settingsGroupBox); + + + connect (m_effectsComboBox, SIGNAL (activated (int)), + this, SLOT (selectEffect (int))); + selectEffect (0); + + + resize (s_lastWidth, s_lastHeight); + + +#if DEBUG_KP_EFFECTS_DIALOG + kdDebug () << "\tabout to slotUpdate()" << endl; +#endif + slotUpdate (); +} + +kpEffectsDialog::~kpEffectsDialog () +{ + s_lastWidth = width (); + s_lastHeight = height (); +} + + +// public virtual [base kpToolPreviewDialog] +bool kpEffectsDialog::isNoOp () const +{ + if (!m_colorEffectWidget) + return true; + + return m_colorEffectWidget->isNoOp (); +} + +// public +kpColorEffectCommand *kpEffectsDialog::createCommand () const +{ + if (!m_colorEffectWidget) + return 0; + + return m_colorEffectWidget->createCommand (); +} + + +// protected virtual [base kpToolPreviewDialog] +QSize kpEffectsDialog::newDimensions () const +{ + kpDocument *doc = document (); + if (!doc) + return QSize (); + + return QSize (doc->width (m_actOnSelection), + doc->height (m_actOnSelection)); +} + +// protected virtual [base kpToolPreviewDialog] +QPixmap kpEffectsDialog::transformPixmap (const QPixmap &pixmap, + int targetWidth, int targetHeight) const +{ + QPixmap pixmapWithEffect; + + if (m_colorEffectWidget) + pixmapWithEffect = m_colorEffectWidget->applyColorEffect (pixmap); + else + pixmapWithEffect = pixmap; + + return kpPixmapFX::scale (pixmapWithEffect, targetWidth, targetHeight); +} + + +// public +int kpEffectsDialog::selectedEffect () const +{ + return m_effectsComboBox->currentItem (); +} + +// public slot +void kpEffectsDialog::selectEffect (int which) +{ +#if DEBUG_KP_EFFECTS_DIALOG + kdDebug () << "kpEffectsDialog::selectEffect(" << which << ")" << endl; +#endif + + if (which < 0 || + which >= m_effectsComboBox->count ()) + { + return; + } + + if (which != m_effectsComboBox->currentItem ()) + m_effectsComboBox->setCurrentItem (which); + + + delete m_colorEffectWidget; + m_colorEffectWidget = 0; + + + m_settingsGroupBox->setCaption (QString::null); + +#define CREATE_EFFECT_WIDGET(name) \ + m_colorEffectWidget = new name (m_actOnSelection, \ + m_mainWindow, \ + m_settingsGroupBox) + 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 (kpEffectInvertWidget); + break; + + case 4: + CREATE_EFFECT_WIDGET (kpEffectReduceColorsWidget); + break; + + case 5: + CREATE_EFFECT_WIDGET (kpEffectBlurSharpenWidget); + break; + } +#undef CREATE_EFFECT_WIDGET + + + if (m_colorEffectWidget) + { + #if DEBUG_KP_EFFECTS_DIALOG + kdDebug () << "\twidget exists for effect #" << endl; + #endif + m_settingsGroupBox->setTitle (m_colorEffectWidget->caption ()); + + + // Don't resize the preview when showing the widget: + // TODO: actually work + + QSize previewGroupBoxMinSize = m_previewGroupBox->minimumSize (); + QSize previewGroupBoxMaxSize = m_previewGroupBox->maximumSize (); + QLayout::ResizeMode previewGroupBoxResizeMode = + m_previewGroupBox->layout () ? + m_previewGroupBox->layout ()->resizeMode () : + QLayout::Auto; + #if DEBUG_KP_EFFECTS_DIALOG + kdDebug () << "\tpreviewGroupBox: minSize=" << previewGroupBoxMinSize + << " maxSize=" << previewGroupBoxMaxSize + << " size=" << m_previewGroupBox->size () + << " layout=" << m_previewGroupBox->layout () + << " resizeMode=" << previewGroupBoxResizeMode + << endl; + #endif + + if (m_previewGroupBox->layout ()) + m_previewGroupBox->layout ()->setResizeMode (QLayout::FreeResize); + #if DEBUG_KP_EFFECTS_DIALOG + kdDebug () << "\tafter set resizeMode, previewGroupBox.size=" + << m_previewGroupBox->size () << endl; + #endif + m_previewGroupBox->setFixedSize (m_previewGroupBox->size ()); + #if DEBUG_KP_EFFECTS_DIALOG + kdDebug () << "\tafter set fixedSize, previewGroupBox.size=" + << m_previewGroupBox->size () << endl; + #endif + + // Show widget + m_settingsLayout->addWidget (m_colorEffectWidget); + #if DEBUG_KP_EFFECTS_DIALOG + kdDebug () << "\tafter addWidget, previewGroupBox.size=" + << m_previewGroupBox->size () << endl; + #endif + m_colorEffectWidget->show (); + #if DEBUG_KP_EFFECTS_DIALOG + kdDebug () << "\tafter addWidget show, previewGroupBox.size=" + << m_previewGroupBox->size () << endl; + #endif + + m_previewGroupBox->setMinimumSize (previewGroupBoxMinSize); + m_previewGroupBox->setMaximumSize (previewGroupBoxMaxSize); + #if DEBUG_KP_EFFECTS_DIALOG + kdDebug () << "\tafter set fixedSize, previewGroupBox.size=" + << m_previewGroupBox->size () << endl; + #endif + if (m_previewGroupBox->layout ()) + m_previewGroupBox->layout ()->setResizeMode (previewGroupBoxResizeMode); + #if DEBUG_KP_EFFECTS_DIALOG + kdDebug () << "\tafter restore resizeMode, previewGroupBox.size=" + << m_previewGroupBox->size () << endl; + #endif + + + connect (m_colorEffectWidget, SIGNAL (settingsChangedNoWaitCursor ()), + this, SLOT (slotUpdate ())); + connect (m_colorEffectWidget, SIGNAL (settingsChanged ()), + this, SLOT (slotUpdateWithWaitCursor ())); + connect (m_colorEffectWidget, SIGNAL (settingsChangedDelayed ()), + this, SLOT (slotDelayedUpdate ())); + slotUpdateWithWaitCursor (); + #if DEBUG_KP_EFFECTS_DIALOG + kdDebug () << "\tafter slotUpdateWithWaitCursor, previewGroupBox.size=" + << m_previewGroupBox->size () << endl; + #endif + } +} + + +// protected slot virtual [base kpToolPreviewDialog] +void kpEffectsDialog::slotUpdate () +{ +#if DEBUG_KP_EFFECTS_DIALOG + kdDebug () << "kpEffectsDialog::slotUpdate()" + << " timerActive=" << m_delayedUpdateTimer->isActive () + << endl; +#endif + + m_delayedUpdateTimer->stop (); + + kpToolPreviewDialog::slotUpdate (); +} + +// protected slot virtual [base kpToolPreviewDialog] +void kpEffectsDialog::slotUpdateWithWaitCursor () +{ +#if DEBUG_KP_EFFECTS_DIALOG + kdDebug () << "kpEffectsDialog::slotUpdateWithWaitCursor()" + << " timerActive=" << m_delayedUpdateTimer->isActive () + << endl; +#endif + + m_delayedUpdateTimer->stop (); + + kpToolPreviewDialog::slotUpdateWithWaitCursor (); +} + + +// protected slot +void kpEffectsDialog::slotDelayedUpdate () +{ +#if DEBUG_KP_EFFECTS_DIALOG + kdDebug () << "kpEffectsDialog::slotDelayedUpdate()" + << " timerActive=" << m_delayedUpdateTimer->isActive () + << endl; +#endif + m_delayedUpdateTimer->stop (); + + m_delayedUpdateTimer->start (400/*ms*/, true/*single shot*/); +} + + +#include <kpeffectsdialog.moc> diff --git a/kolourpaint/pixmapfx/kpeffectsdialog.h b/kolourpaint/pixmapfx/kpeffectsdialog.h new file mode 100644 index 00000000..fe7265cc --- /dev/null +++ b/kolourpaint/pixmapfx/kpeffectsdialog.h @@ -0,0 +1,90 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <kptoolpreviewdialog.h> + + +class QGroupBox; +class QStringList; +class QTimer; +class QVBoxLayout; + +class KComboBox; + +class kpColorEffectCommand; +class kpColorEffectWidget; +class kpMainWindow; + + +class kpEffectsDialog : public kpToolPreviewDialog +{ +Q_OBJECT + +public: + kpEffectsDialog (bool actOnSelection, + kpMainWindow *parent, + const char *name = 0); + virtual ~kpEffectsDialog (); + + virtual bool isNoOp () const; + kpColorEffectCommand *createCommand () const; + +protected: + virtual QSize newDimensions () const; + virtual QPixmap transformPixmap (const QPixmap &pixmap, + int targetWidth, int targetHeight) const; + +public: + int selectedEffect () const; +public slots: + void selectEffect (int which); + +protected slots: + virtual void slotUpdate (); + virtual void slotUpdateWithWaitCursor (); + + void slotDelayedUpdate (); + +protected: + static int s_lastWidth, s_lastHeight; + + QTimer *m_delayedUpdateTimer; + + KComboBox *m_effectsComboBox; + QGroupBox *m_settingsGroupBox; + QVBoxLayout *m_settingsLayout; + + kpColorEffectWidget *m_colorEffectWidget; +}; + + +#endif // KP_EFFECTS_DIALOG_H diff --git a/kolourpaint/pixmapfx/kpfloodfill.cpp b/kolourpaint/pixmapfx/kpfloodfill.cpp new file mode 100644 index 00000000..602e8acf --- /dev/null +++ b/kolourpaint/pixmapfx/kpfloodfill.cpp @@ -0,0 +1,362 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <qapplication.h> +#include <qbitmap.h> +#include <qpainter.h> +#include <qpixmap.h> + +#include <kdebug.h> +#include <kpdefs.h> + +#include <kppixmapfx.h> +#include <kptool.h> + + +kpFloodFill::kpFloodFill (QPixmap *pixmap, int x, int y, + const kpColor &color, int processedColorSimilarity) + : m_pixmapPtr (pixmap), m_x (x), m_y (y), + m_color (color), m_processedColorSimilarity (processedColorSimilarity), + m_initState (0) +{ +} + +kpFloodFill::~kpFloodFill () +{ +} + + +// private +int kpFloodFill::fillLinesListSize (const QValueList <kpFloodFill::FillLine> &fillLines) const +{ + return (fillLines.size () * kpFloodFill::FillLine::size ()); +} + +// public +int kpFloodFill::size () const +{ + int fillLinesCacheSize = 0; + for (QValueVector < QValueList <kpFloodFill::FillLine > >::const_iterator it = m_fillLinesCache.begin (); + it != m_fillLinesCache.end (); + it++) + { + fillLinesCacheSize += fillLinesListSize (*it); + } + + return fillLinesListSize (m_fillLines) + + kpPixmapFX::imageSize (m_image) + + fillLinesCacheSize; +} + + +QRect kpFloodFill::boundingRect () const +{ + return m_boundingRect; +} + +bool kpFloodFill::fill () +{ + if (m_initState < 2 && !prepare ()) + { + kdError () << "kpFloodFill:fill() could not prepare()!" << endl; + return false; + } + + // not trying to do a NOP fill + if (m_boundingRect.isValid ()) + { + QApplication::setOverrideCursor (Qt::waitCursor); + + QPainter painter, maskPainter; + QBitmap maskBitmap; + + if (m_pixmapPtr->mask () || m_color.isTransparent ()) + { + maskBitmap = kpPixmapFX::getNonNullMask (*m_pixmapPtr); + maskPainter.begin (&maskBitmap); + maskPainter.setPen (m_color.maskColor ()); + } + + if (m_color.isOpaque ()) + { + painter.begin (m_pixmapPtr); + painter.setPen (m_color.toQColor ()); + } + + const QValueList <FillLine>::ConstIterator fillLinesEnd = m_fillLines.end (); + for (QValueList <FillLine>::ConstIterator it = m_fillLines.begin (); + it != fillLinesEnd; + it++) + { + QPoint p1 = QPoint ((*it).m_x1, (*it).m_y); + QPoint p2 = QPoint ((*it).m_x2, (*it).m_y); + + if (painter.isActive ()) + painter.drawLine (p1, p2); + + if (maskPainter.isActive ()) + maskPainter.drawLine (p1, p2); + } + + if (painter.isActive ()) + painter.end (); + + if (maskPainter.isActive ()) + maskPainter.end (); + + if (!maskBitmap.isNull ()) + m_pixmapPtr->setMask (maskBitmap); + + QApplication::restoreOverrideCursor (); + } + else + { + #if DEBUG_KP_FLOOD_FILL && 1 + kdDebug () << "kpFloodFill::fill() performing NOP fill" << endl; + #endif + } + + return true; +} + +bool kpFloodFill::prepareColorToChange () +{ +#if DEBUG_KP_FLOOD_FILL && 1 + kdDebug () << "kpFloodFill::prepareColorToChange" << endl; +#endif + + m_colorToChange = kpPixmapFX::getColorAtPixel (*m_pixmapPtr, QPoint (m_x, m_y)); + + if (m_colorToChange.isOpaque ()) + { + #if DEBUG_KP_FLOOD_FILL && 1 + kdDebug () << "\tcolorToChange: r=" << m_colorToChange.red () + << ", b=" << m_colorToChange.blue () + << ", g=" << m_colorToChange.green () + << endl; + #endif + } + else + { + #if DEBUG_KP_FLOOD_FILL && 1 + kdDebug () << "\tcolorToChange: transparent" << endl; + #endif + } + + m_initState = 1; + return true; +} + +// Derived from the zSprite2 Graphics Engine + +bool kpFloodFill::prepare () +{ +#if DEBUG_KP_FLOOD_FILL && 1 + kdDebug () << "kpFloodFill::prepare()" << endl; +#endif + m_boundingRect = QRect (); + + if (m_initState < 1 && !prepareColorToChange ()) + { + kdError () << "kpFloodFill:prepare() could not prepareColorToChange()!" << endl; + return false; + } + +#if DEBUG_KP_FLOOD_FILL && 1 + kdDebug () << "\tperforming NOP check" << endl; +#endif + + // get the color we need to replace + if (m_processedColorSimilarity == 0 && m_color == m_colorToChange) + { + // need to do absolutely nothing (this is a significant optimisation + // for people who randomly click a lot over already-filled areas) + m_initState = 2; // sync with all "return true"'s + return true; + } + +#if DEBUG_KP_FLOOD_FILL && 1 + kdDebug () << "\tconverting to image" << endl; +#endif + + // is this the only way to read pixels? + m_image = kpPixmapFX::convertToImage (*m_pixmapPtr); + if (m_image.isNull ()) + { + kdError () << "kpFloodFill::prepare() could not convert to QImage" << endl; + return false; + } + +#if DEBUG_KP_FLOOD_FILL && 1 + kdDebug () << "\tcreating fillLinesCache" << endl; +#endif + + // ready cache + m_fillLinesCache.resize (m_pixmapPtr->height ()); + +#if DEBUG_KP_FLOOD_FILL && 1 + kdDebug () << "\tcreating fill lines" << endl; +#endif + + // draw initial line + addLine (m_y, findMinX (m_y, m_x), findMaxX (m_y, m_x)); + + for (QValueList <FillLine>::ConstIterator it = m_fillLines.begin (); + it != m_fillLines.end (); + it++) + { + #if DEBUG_KP_FLOOD_FILL && 0 + kdDebug () << "Expanding from y=" << (*it).m_y + << " x1=" << (*it).m_x1 + << " x2=" << (*it).m_x2 + << endl; + #endif + + // make more lines above and below current line + findAndAddLines (*it, -1); + findAndAddLines (*it, +1); + } + +#if DEBUG_KP_FLOOD_FILL && 1 + kdDebug () << "\tfinalising memory usage" << endl; +#endif + + // finalize memory usage + m_image.reset (); + m_fillLinesCache.clear (); + + m_initState = 2; // sync with all "return true"'s + return true; +} + +void kpFloodFill::addLine (int y, int x1, int x2) +{ +#if DEBUG_KP_FLOOD_FILL && 0 + kdDebug () << "kpFillCommand::fillAddLine (" << y << "," << x1 << "," << x2 << ")" << endl; +#endif + + m_fillLines.append (FillLine (y, x1, x2)); + m_fillLinesCache [y].append (FillLine (y /* OPT */, x1, x2)); + m_boundingRect = m_boundingRect.unite (QRect (QPoint (x1, y), QPoint (x2, y))); +} + +kpColor kpFloodFill::pixelColor (int x, int y, bool *beenHere) const +{ + if (beenHere) + *beenHere = false; + + if (y >= (int) m_fillLinesCache.count ()) + { + kdError () << "kpFloodFill::pixelColor(" + << x << "," + << y << ") y out of range=" << m_pixmapPtr->height () << endl; + return kpColor::invalid; + } + + const QValueList <FillLine>::ConstIterator theEnd = m_fillLinesCache [y].end (); + for (QValueList <FillLine>::ConstIterator it = m_fillLinesCache [y].begin (); + it != theEnd; + it++) + { + if (x >= (*it).m_x1 && x <= (*it).m_x2) + { + if (beenHere) + *beenHere = true; + return m_color; + } + } + + return kpPixmapFX::getColorAtPixel (m_image, QPoint (x, y)); +} + +bool kpFloodFill::shouldGoTo (int x, int y) const +{ + bool beenThere; + const kpColor col = pixelColor (x, y, &beenThere); + + return (!beenThere && col.isSimilarTo (m_colorToChange, m_processedColorSimilarity)); +} + +void kpFloodFill::findAndAddLines (const FillLine &fillLine, int dy) +{ + // out of bounds? + if (fillLine.m_y + dy < 0 || fillLine.m_y + dy >= m_pixmapPtr->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; + } + } +} + +// finds the minimum x value at a certain line to be filled +int kpFloodFill::findMinX (int y, int x) const +{ + for (;;) + { + if (x < 0) + return 0; + + if (shouldGoTo (x, y)) + x--; + else + return x + 1; + } +} + +// finds the maximum x value at a certain line to be filled +int kpFloodFill::findMaxX (int y, int x) const +{ + for (;;) + { + if (x > m_pixmapPtr->width () - 1) + return m_pixmapPtr->width () - 1; + + if (shouldGoTo (x, y)) + x++; + else + return x - 1; + } +} diff --git a/kolourpaint/pixmapfx/kpfloodfill.h b/kolourpaint/pixmapfx/kpfloodfill.h new file mode 100644 index 00000000..5c0d8001 --- /dev/null +++ b/kolourpaint/pixmapfx/kpfloodfill.h @@ -0,0 +1,106 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 __kpfloodfill_h__ +#define __kpfloodfill_h__ + +#include <qimage.h> +#include <qvaluelist.h> +#include <qvaluevector.h> + +#include <kpcolor.h> + +class QPixmap; + +class kpFloodFill +{ +public: + kpFloodFill (QPixmap *pixmap, int x, int y, + const kpColor &color, + int processedColorSimilarity); + ~kpFloodFill (); + + int size () const; + + kpColor color () const { return m_color; } + int processedColorSimilarity () const { return m_processedColorSimilarity; } + + // you should call [prepareColorToChange(),[prepare(),[fill()]]] + bool prepareColorToChange (); + + // (only valid after prepareColorToChange()) + kpColor colorToChange () const { return m_colorToChange; }; + + bool prepare (); + QRect boundingRect () const; // only valid after prepare() + + bool fill (); + +private: + QPixmap *m_pixmapPtr; + int m_x, m_y; + kpColor m_color; + int m_processedColorSimilarity; + + int m_initState; + + QRect m_boundingRect; + + struct FillLine + { + FillLine (int y = -1, int x1 = -1, int x2 = -1) + : m_y (y), m_x1 (x1), m_x2 (x2) + { + } + + static int size () + { + return sizeof (FillLine); + } + + int m_y, m_x1, m_x2; + }; + + int fillLinesListSize (const QValueList <kpFloodFill::FillLine> &fillLines) const; + + void addLine (int y, int x1, int x2); + kpColor pixelColor (int x, int y, bool *beenHere = 0) const; + bool shouldGoTo (int x, int y) const; + void findAndAddLines (const FillLine &fillLine, int dy); + int findMinX (int y, int x) const; + int findMaxX (int y, int x) const; + + QValueList <FillLine> m_fillLines; + + // Init info + QImage m_image; + QValueVector < QValueList <FillLine> > m_fillLinesCache; + kpColor m_colorToChange; +}; + +#endif // __kpfloodfill_h__ diff --git a/kolourpaint/pixmapfx/kppixmapfx.cpp b/kolourpaint/pixmapfx/kppixmapfx.cpp new file mode 100644 index 00000000..1bd0b173 --- /dev/null +++ b/kolourpaint/pixmapfx/kppixmapfx.cpp @@ -0,0 +1,1677 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <math.h> + +#include <qapplication.h> +#include <qbitmap.h> +#include <qdatetime.h> +#include <qimage.h> +#include <qpainter.h> +#include <qpixmap.h> +#include <qpoint.h> +#include <qpointarray.h> +#include <qrect.h> + +#include <kconfig.h> +#include <kdebug.h> +#include <klocale.h> +#include <kmessagebox.h> + +#include <kpcolor.h> +#include <kpdefs.h> +#include <kpselection.h> +#include <kptool.h> + + +// +// Overflow Resistant Arithmetic: +// + +// public static +int kpPixmapFX::addDimensions (int lhs, int rhs) +{ + if (lhs < 0 || rhs < 0 || + lhs > INT_MAX - rhs) + { + return INT_MAX; + } + + return lhs + rhs; +} + +// public static +int kpPixmapFX::multiplyDimensions (int lhs, int rhs) +{ + if (rhs == 0) + return 0; + + if (lhs < 0 || rhs < 0 || + lhs > INT_MAX / rhs) + { + return INT_MAX; + } + + return lhs * rhs; +} + + +// +// QPixmap Statistics +// + +// public static +int kpPixmapFX::pixmapArea (const QPixmap &pixmap) +{ + return kpPixmapFX::pixmapArea (pixmap.width (), pixmap.height ()); +} + +// public static +int kpPixmapFX::pixmapArea (const QPixmap *pixmap) +{ + return (pixmap ? kpPixmapFX::pixmapArea (*pixmap) : 0); +} + +// public static +int kpPixmapFX::pixmapArea (int width, int height) +{ + return multiplyDimensions (width, height); +} + + +// public static +int kpPixmapFX::pixmapSize (const QPixmap &pixmap) +{ + return kpPixmapFX::pixmapSize (pixmap.width (), pixmap.height (), + pixmap.depth ()); +} + +// public static +int kpPixmapFX::pixmapSize (const QPixmap *pixmap) +{ + return (pixmap ? kpPixmapFX::pixmapSize (*pixmap) : 0); +} + +// public static +int kpPixmapFX::pixmapSize (int width, int height, int depth) +{ + // handle 15bpp + int roundedDepth = (depth > 8 ? (depth + 7) / 8 * 8 : depth); + +#if DEBUG_KP_PIXMAP_FX && 0 + kdDebug () << "kpPixmapFX::pixmapSize() w=" << width + << " h=" << height + << " d=" << depth + << " roundedDepth=" << roundedDepth + << " ret=" + << multiplyDimensions (kpPixmapFX::pixmapArea (width, height), roundedDepth) / 8 + << endl; +#endif + return multiplyDimensions (kpPixmapFX::pixmapArea (width, height), roundedDepth) / 8; +} + + +// public static +int kpPixmapFX::imageSize (const QImage &image) +{ + return kpPixmapFX::imageSize (image.width (), image.height (), image.depth ()); +} + +// public static +int kpPixmapFX::imageSize (const QImage *image) +{ + return (image ? kpPixmapFX::imageSize (*image) : 0); +} + +// public static +int kpPixmapFX::imageSize (int width, int height, int depth) +{ + // handle 15bpp + int roundedDepth = (depth > 8 ? (depth + 7) / 8 * 8 : depth); + +#if DEBUG_KP_PIXMAP_FX && 0 + kdDebug () << "kpPixmapFX::imageSize() w=" << width + << " h=" << height + << " d=" << depth + << " roundedDepth=" << roundedDepth + << " ret=" + << multiplyDimensions (multiplyDimensions (width, height), roundedDepth) / 8 + << endl; +#endif + + return multiplyDimensions (multiplyDimensions (width, height), roundedDepth) / 8; +} + + +// public static +int kpPixmapFX::selectionSize (const kpSelection &sel) +{ + return sel.size (); +} + +// public static +int kpPixmapFX::selectionSize (const kpSelection *sel) +{ + return (sel ? sel->size () : 0); +} + + +// public static +int kpPixmapFX::stringSize (const QString &string) +{ +#if DEBUG_KP_PIXMAP_FX && 1 + kdDebug () << "kpPixmapFX::stringSize(" << string << ")" + << " len=" << string.length () + << " sizeof(QChar)=" << sizeof (QChar) + << endl; +#endif + return string.length () * sizeof (QChar); +} + + +// public static +int kpPixmapFX::pointArraySize (const QPointArray &points) +{ +#if DEBUG_KP_PIXMAP_FX && 1 + kdDebug () << "kpPixmapFX::pointArraySize() points.size=" + << points.size () + << " sizeof(QPoint)=" << sizeof (QPoint) + << endl; +#endif + + return (points.size () * sizeof (QPoint)); +} + + +// +// QPixmap/QImage Conversion Functions +// + +// public static +QImage kpPixmapFX::convertToImage (const QPixmap &pixmap) +{ + if (pixmap.isNull ()) + return QImage (); + + return pixmap.convertToImage (); +} + + +// Returns true if <image> contains translucency (rather than just transparency) +// QPixmap::hasAlphaChannel() appears to give incorrect results +static bool imageHasAlphaChannel (const QImage &image) +{ + if (image.depth () < 32) + return false; + + for (int y = 0; y < image.height (); y++) + { + for (int x = 0; x < image.width (); x++) + { + const QRgb rgb = image.pixel (x, y); + + if (qAlpha (rgb) > 0 && qAlpha (rgb) < 255) + return true; + } + } + + return false; +} + +static int imageNumColorsUpTo (const QImage &image, int max) +{ + QMap <QRgb, bool> rgbMap; + + if (image.depth () <= 8) + { + for (int i = 0; i < image.numColors () && (int) rgbMap.size () < max; i++) + { + rgbMap.insert (image.color (i), true); + } + } + else + { + for (int y = 0; y < image.height () && (int) rgbMap.size () < max; y++) + { + for (int x = 0; x < image.width () && (int) rgbMap.size () < max; x++) + { + rgbMap.insert (image.pixel (x, y), true); + } + } + } + + return rgbMap.size (); +} + +static void convertToPixmapWarnAboutLoss (const QImage &image, + const kpPixmapFX::WarnAboutLossInfo &wali) +{ + if (!wali.isValid ()) + return; + + + const QString colorDepthTranslucencyDontAskAgain = + wali.m_dontAskAgainPrefix + "_ColorDepthTranslucency"; + const QString colorDepthDontAskAgain = + wali.m_dontAskAgainPrefix + "_ColorDepth"; + const QString translucencyDontAskAgain = + wali.m_dontAskAgainPrefix + "_Translucency"; + +#if DEBUG_KP_PIXMAP_FX && 1 + QTime timer; + timer.start (); +#endif + + bool hasAlphaChannel = + (KMessageBox::shouldBeShownContinue (translucencyDontAskAgain) && + imageHasAlphaChannel (image)); + +#if DEBUG_KP_PIXMAP_FX && 1 + kdDebug () << "\twarnAboutLoss - check hasAlphaChannel took " + << timer.restart () << "msec" << endl; +#endif + + bool moreColorsThanDisplay = + (KMessageBox::shouldBeShownContinue (colorDepthDontAskAgain) && + image.depth () > QColor::numBitPlanes () && + QColor::numBitPlanes () < 24); // 32 indicates alpha channel + + int screenDepthNeeded = 0; + + if (moreColorsThanDisplay) + screenDepthNeeded = QMIN (24, image.depth ()); + +#if DEBUG_KP_PIXMAP_FX && 1 + kdDebug () << "\ttranslucencyShouldBeShown=" + << KMessageBox::shouldBeShownContinue (translucencyDontAskAgain) + << endl + << "\thasAlphaChannel=" << hasAlphaChannel + << endl + << "\tcolorDepthShownBeShown=" + << KMessageBox::shouldBeShownContinue (colorDepthDontAskAgain) + << endl + << "\timage.depth()=" << image.depth () + << endl + << "\tscreenDepth=" << QColor::numBitPlanes () + << endl + << "\tmoreColorsThanDisplay=" << moreColorsThanDisplay + << endl + << "\tneedDepth=" << screenDepthNeeded + << endl; +#endif + + + QApplication::setOverrideCursor (Qt::arrowCursor); + + if (moreColorsThanDisplay && hasAlphaChannel) + { + KMessageBox::information (wali.m_parent, + wali.m_moreColorsThanDisplayAndHasAlphaChannelMessage + .arg (screenDepthNeeded), + QString::null, // or would you prefer "Low Screen Depth and Image Contains Transparency"? :) + colorDepthTranslucencyDontAskAgain); + + if (!KMessageBox::shouldBeShownContinue (colorDepthTranslucencyDontAskAgain)) + { + KMessageBox::saveDontShowAgainContinue (colorDepthDontAskAgain); + KMessageBox::saveDontShowAgainContinue (translucencyDontAskAgain); + } + } + else if (moreColorsThanDisplay) + { + KMessageBox::information (wali.m_parent, + wali.m_moreColorsThanDisplayMessage + .arg (screenDepthNeeded), + i18n ("Low Screen Depth"), + colorDepthDontAskAgain); + } + else if (hasAlphaChannel) + { + KMessageBox::information (wali.m_parent, + wali.m_hasAlphaChannelMessage, + i18n ("Image Contains Translucency"), + translucencyDontAskAgain); + } + + QApplication::restoreOverrideCursor (); +} + +// public static +QPixmap kpPixmapFX::convertToPixmap (const QImage &image, bool pretty, + const WarnAboutLossInfo &wali) +{ +#if DEBUG_KP_PIXMAP_FX && 1 + kdDebug () << "kpPixmapFX::convertToPixmap(image,pretty=" << pretty + << ",warnAboutLossInfo.isValid=" << wali.isValid () + << ")" << endl; + QTime timer; + timer.start (); +#endif + + if (image.isNull ()) + return QPixmap (); + + + QPixmap destPixmap; + + if (!pretty) + { + destPixmap.convertFromImage (image, + Qt::ColorOnly/*always display depth*/ | + Qt::ThresholdDither/*no dither*/ | + Qt::ThresholdAlphaDither/*no dither alpha*/| + Qt::AvoidDither); + } + else + { + destPixmap.convertFromImage (image, + Qt::ColorOnly/*always display depth*/ | + Qt::DiffuseDither/*hi quality dither*/ | + Qt::ThresholdAlphaDither/*no dither alpha*/ | + Qt::PreferDither/*(dither even if <256 colours)*/); + } + +#if DEBUG_KP_PIXMAP_FX && 1 + kdDebug () << "\tconversion took " << timer.elapsed () << "msec" << endl; +#endif + + kpPixmapFX::ensureNoAlphaChannel (&destPixmap); + + + if (wali.isValid ()) + convertToPixmapWarnAboutLoss (image, wali); + + + return destPixmap; +} + +// TODO: don't dup convertToPixmap() code +// public static +QPixmap kpPixmapFX::convertToPixmapAsLosslessAsPossible (const QImage &image, + const WarnAboutLossInfo &wali) +{ +#if DEBUG_KP_PIXMAP_FX && 1 + kdDebug () << "kpPixmapFX::convertToPixmapAsLosslessAsPossible(image depth=" + << image.depth () + << ",warnAboutLossInfo.isValid=" << wali.isValid () + << ") screenDepth=" << QPixmap::defaultDepth () + << " imageNumColorsUpTo257=" << imageNumColorsUpTo (image, 257) + << endl; + QTime timer; + timer.start (); +#endif + + if (image.isNull ()) + return QPixmap (); + + + const int screenDepth = (QPixmap::defaultDepth () >= 24 ? + 32 : + QPixmap::defaultDepth ()); + + QPixmap destPixmap; + int ditherFlags = 0; + + if (image.depth () <= screenDepth) + { + #if DEBUG_KP_PIXMAP_FX && 1 + kdDebug () << "\timage depth <= screen depth - don't dither" + << " (AvoidDither | ThresholdDither)" << endl; + #endif + + ditherFlags = (Qt::AvoidDither | Qt::ThresholdDither); + } + // PRE: image.depth() > screenDepth + // ASSERT: screenDepth < 32 + else if (screenDepth <= 8) + { + const int screenNumColors = (1 << screenDepth); + + #if DEBUG_KP_PIXMAP_FX && 1 + kdDebug () << "\tscreen depth <= 8; imageNumColorsUpTo" + << (screenNumColors + 1) + << "=" << imageNumColorsUpTo (image, screenNumColors + 1) + << endl; + #endif + + if (imageNumColorsUpTo (image, screenNumColors + 1) <= screenNumColors) + { + #if DEBUG_KP_PIXMAP_FX && 1 + kdDebug () << "\t\tcolors fit on screen - don't dither" + << " (AvoidDither | ThresholdDither)" << endl; + #endif + ditherFlags = (Qt::AvoidDither | Qt::ThresholdDither); + } + else + { + #if DEBUG_KP_PIXMAP_FX && 1 + kdDebug () << "\t\tcolors don't fit on screen - dither" + << " (PreferDither | DiffuseDither)" << endl; + #endif + ditherFlags = (Qt::PreferDither | Qt::DiffuseDither); + } + } + // PRE: image.depth() > screenDepth && + // screenDepth > 8 + // ASSERT: screenDepth < 32 + else + { + #if DEBUG_KP_PIXMAP_FX && 1 + kdDebug () << "\tscreen depth > 8 - read config" << endl; + #endif + + int configDitherIfNumColorsGreaterThan = 323; + + KConfigGroupSaver cfgGroupSaver (KGlobal::config (), + kpSettingsGroupGeneral); + KConfigBase *cfg = cfgGroupSaver.config (); + + if (cfg->hasKey (kpSettingDitherOnOpen)) + { + configDitherIfNumColorsGreaterThan = cfg->readNumEntry (kpSettingDitherOnOpen); + } + else + { + cfg->writeEntry (kpSettingDitherOnOpen, configDitherIfNumColorsGreaterThan); + cfg->sync (); + } + + #if DEBUG_KP_PIXMAP_FX && 1 + kdDebug () << "\t\tcfg=" << configDitherIfNumColorsGreaterThan + << " image=" << imageNumColorsUpTo (image, configDitherIfNumColorsGreaterThan + 1) + << endl; + #endif + + if (imageNumColorsUpTo (image, configDitherIfNumColorsGreaterThan + 1) > + configDitherIfNumColorsGreaterThan) + { + #if DEBUG_KP_PIXMAP_FX && 1 + kdDebug () << "\t\t\talways dither (PreferDither | DiffuseDither)" + << endl; + #endif + ditherFlags = (Qt::PreferDither | Qt::DiffuseDither); + } + else + { + #if DEBUG_KP_PIXMAP_FX && 1 + kdDebug () << "\t\t\tdon't dither (AvoidDither | ThresholdDither)" + << endl; + #endif + ditherFlags = (Qt::AvoidDither | Qt::ThresholdDither); + } + } + + + destPixmap.convertFromImage (image, + Qt::ColorOnly/*always display depth*/ | + Qt::ThresholdAlphaDither/*no dither alpha*/ | + ditherFlags); + +#if DEBUG_KP_PIXMAP_FX && 1 + kdDebug () << "\tconversion took " << timer.elapsed () << "msec" << endl; +#endif + + kpPixmapFX::ensureNoAlphaChannel (&destPixmap); + + + if (wali.isValid ()) + convertToPixmapWarnAboutLoss (image, wali); + + + return destPixmap; +} + + +// public static +QPixmap kpPixmapFX::pixmapWithDefinedTransparentPixels (const QPixmap &pixmap, + const QColor &transparentColor) +{ + if (!pixmap.mask ()) + return pixmap; + + QPixmap retPixmap (pixmap.width (), pixmap.height ()); + retPixmap.fill (transparentColor); + + QPainter p (&retPixmap); + p.drawPixmap (QPoint (0, 0), pixmap); + p.end (); + + retPixmap.setMask (*pixmap.mask ()); + return retPixmap; +} + + +// +// Get/Set Parts of Pixmap +// + + +// public static +QPixmap kpPixmapFX::getPixmapAt (const QPixmap &pm, const QRect &rect) +{ + QPixmap retPixmap (rect.width (), rect.height ()); + +#if DEBUG_KP_PIXMAP_FX && 0 + kdDebug () << "kpPixmapFX::getPixmapAt(pm.hasMask=" + << (pm.mask () ? 1 : 0) + << ",rect=" + << rect + << ")" + << endl; +#endif + + const QRect validSrcRect = pm.rect ().intersect (rect); + const bool wouldHaveUndefinedPixels = (validSrcRect != rect); + + if (wouldHaveUndefinedPixels) + { + #if DEBUG_KP_PIXMAP_FX && 0 + kdDebug () << "\tret would contain undefined pixels - setting them to transparent" << endl; + #endif + QBitmap transparentMask (rect.width (), rect.height ()); + transparentMask.fill (Qt::color0/*transparent*/); + retPixmap.setMask (transparentMask); + } + + if (validSrcRect.isEmpty ()) + { + #if DEBUG_KP_PIXMAP_FX && 0 + kdDebug () << "\tsilly case - completely invalid rect - ret transparent pixmap" << endl; + #endif + return retPixmap; + } + + + const QPoint destTopLeft = validSrcRect.topLeft () - rect.topLeft (); + + // copy data _and_ mask (if avail) + copyBlt (&retPixmap, /* dest */ + destTopLeft.x (), destTopLeft.y (), /* dest pt */ + &pm, /* src */ + validSrcRect.x (), validSrcRect.y (), /* src pt */ + validSrcRect.width (), validSrcRect.height ()); + + if (wouldHaveUndefinedPixels && retPixmap.mask () && !pm.mask ()) + { + #if DEBUG_KP_PIXMAP_FX && 0 + kdDebug () << "\tensure opaque in valid region" << endl; + #endif + kpPixmapFX::ensureOpaqueAt (&retPixmap, + QRect (destTopLeft.x (), destTopLeft.y (), + validSrcRect.width (), validSrcRect.height ())); + } + +#if DEBUG_KP_PIXMAP_FX && 0 + kdDebug () << "\tretPixmap.hasMask=" + << (retPixmap.mask () ? 1 : 0) + << endl; +#endif + + return retPixmap; +} + + +// public static +void kpPixmapFX::setPixmapAt (QPixmap *destPixmapPtr, const QRect &destRect, + const QPixmap &srcPixmap) +{ + if (!destPixmapPtr) + return; + +#if DEBUG_KP_PIXMAP_FX && 0 + kdDebug () << "kpPixmapFX::setPixmapAt(destPixmap->rect=" + << destPixmapPtr->rect () + << ",destPixmap->hasMask=" + << (destPixmapPtr->mask () ? 1 : 0) + << ",destRect=" + << destRect + << ",srcPixmap.rect=" + << srcPixmap.rect () + << ",srcPixmap.hasMask=" + << (srcPixmap.mask () ? 1 : 0) + << ")" + << endl; +#endif + +#if DEBUG_KP_PIXMAP_FX && 0 + if (destPixmapPtr->mask ()) + { + QImage image = kpPixmapFX::convertToImage (*destPixmapPtr); + int numTrans = 0; + + for (int y = 0; y < image.height (); y++) + { + for (int x = 0; x < image.width (); x++) + { + if (qAlpha (image.pixel (x, y)) == 0) + numTrans++; + } + } + + kdDebug () << "\tdestPixmapPtr numTrans=" << numTrans << endl; + } +#endif + +#if 0 + // TODO: why does undo'ing a single pen dot on a transparent pixel, + // result in a opaque image, except for that single transparent pixel??? + // Qt bug on boundary case? + + // copy data _and_ mask + copyBlt (destPixmapPtr, + destAt.x (), destAt.y (), + &srcPixmap, + 0, 0, + destRect.width (), destRect.height ()); +#else + bitBlt (destPixmapPtr, + destRect.x (), destRect.y (), + &srcPixmap, + 0, 0, + destRect.width (), destRect.height (), + Qt::CopyROP, + true/*ignore mask*/); + + if (srcPixmap.mask ()) + { + QBitmap mask = getNonNullMask (*destPixmapPtr); + bitBlt (&mask, + destRect.x (), destRect.y (), + srcPixmap.mask (), + 0, 0, + destRect.width (), destRect.height (), + Qt::CopyROP, + true/*ignore mask*/); + destPixmapPtr->setMask (mask); + } +#endif + + if (destPixmapPtr->mask () && !srcPixmap.mask ()) + { + #if DEBUG_KP_PIXMAP_FX && 0 + kdDebug () << "\t\topaque'ing dest rect" << endl; + #endif + kpPixmapFX::ensureOpaqueAt (destPixmapPtr, destRect); + } + +#if DEBUG_KP_PIXMAP_FX && 0 + kdDebug () << "\tdestPixmap->hasMask=" + << (destPixmapPtr->mask () ? 1 : 0) + << endl; + if (destPixmapPtr->mask ()) + { + QImage image = kpPixmapFX::convertToImage (*destPixmapPtr); + int numTrans = 0; + + for (int y = 0; y < image.height (); y++) + { + for (int x = 0; x < image.width (); x++) + { + if (qAlpha (image.pixel (x, y)) == 0) + numTrans++; + } + } + + kdDebug () << "\tdestPixmapPtr numTrans=" << numTrans << endl; + } +#endif +} + +// public static +void kpPixmapFX::setPixmapAt (QPixmap *destPixmapPtr, const QPoint &destAt, + const QPixmap &srcPixmap) +{ + kpPixmapFX::setPixmapAt (destPixmapPtr, + QRect (destAt.x (), destAt.y (), + srcPixmap.width (), srcPixmap.height ()), + srcPixmap); +} + +// public static +void kpPixmapFX::setPixmapAt (QPixmap *destPixmapPtr, int destX, int destY, + const QPixmap &srcPixmap) +{ + kpPixmapFX::setPixmapAt (destPixmapPtr, QPoint (destX, destY), srcPixmap); +} + + +// public static +void kpPixmapFX::paintPixmapAt (QPixmap *destPixmapPtr, const QPoint &destAt, + const QPixmap &srcPixmap) +{ + if (!destPixmapPtr) + return; + + // Copy src (masked by src's mask) on top of dest. + bitBlt (destPixmapPtr, /* dest */ + destAt.x (), destAt.y (), /* dest pt */ + &srcPixmap, /* src */ + 0, 0 /* src pt */); + + kpPixmapFX::ensureOpaqueAt (destPixmapPtr, destAt, srcPixmap); +} + +// public static +void kpPixmapFX::paintPixmapAt (QPixmap *destPixmapPtr, int destX, int destY, + const QPixmap &srcPixmap) +{ + kpPixmapFX::paintPixmapAt (destPixmapPtr, QPoint (destX, destY), srcPixmap); +} + + +// public static +kpColor kpPixmapFX::getColorAtPixel (const QPixmap &pm, const QPoint &at) +{ +#if DEBUG_KP_PIXMAP_FX && 0 + kdDebug () << "kpToolColorPicker::colorAtPixel" << p << endl; +#endif + + if (at.x () < 0 || at.x () >= pm.width () || + at.y () < 0 || at.y () >= pm.height ()) + { + return kpColor::invalid; + } + + QPixmap pixmap = getPixmapAt (pm, QRect (at, at)); + QImage image = kpPixmapFX::convertToImage (pixmap); + if (image.isNull ()) + { + kdError () << "kpPixmapFX::getColorAtPixel(QPixmap) could not convert to QImage" << endl; + return kpColor::invalid; + } + + return getColorAtPixel (image, QPoint (0, 0)); +} + +// public static +kpColor kpPixmapFX::getColorAtPixel (const QPixmap &pm, int x, int y) +{ + return kpPixmapFX::getColorAtPixel (pm, QPoint (x, y)); +} + +// 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.x (), at.y ()); + return kpColor (rgba); +} + +// public static +kpColor kpPixmapFX::getColorAtPixel (const QImage &img, int x, int y) +{ + return kpPixmapFX::getColorAtPixel (img, QPoint (x, y)); +} + + +// +// Mask Operations +// + + +// public static +void kpPixmapFX::ensureNoAlphaChannel (QPixmap *destPixmapPtr) +{ + if (destPixmapPtr->hasAlphaChannel ()) + destPixmapPtr->setMask (kpPixmapFX::getNonNullMask/*just in case*/ (*destPixmapPtr)); +} + + +// public static +QBitmap kpPixmapFX::getNonNullMask (const QPixmap &pm) +{ + if (pm.mask ()) + return *pm.mask (); + else + { + QBitmap maskBitmap (pm.width (), pm.height ()); + maskBitmap.fill (Qt::color1/*opaque*/); + + return maskBitmap; + } +} + + +// public static +void kpPixmapFX::ensureTransparentAt (QPixmap *destPixmapPtr, const QRect &destRect) +{ + if (!destPixmapPtr) + return; + + QBitmap maskBitmap = getNonNullMask (*destPixmapPtr); + + QPainter p (&maskBitmap); + + p.setPen (Qt::color0/*transparent*/); + p.setBrush (Qt::color0/*transparent*/); + + p.drawRect (destRect); + + p.end (); + + destPixmapPtr->setMask (maskBitmap); +} + + +// public static +void kpPixmapFX::paintMaskTransparentWithBrush (QPixmap *destPixmapPtr, const QPoint &destAt, + const QPixmap &brushBitmap) +{ + if (!destPixmapPtr) + return; + + if (brushBitmap.depth () > 1) + { + kdError () << "kpPixmapFX::paintMaskTransparentWidthBrush() passed brushPixmap with depth > 1" << endl; + return; + } + + QBitmap destMaskBitmap = kpPixmapFX::getNonNullMask (*destPixmapPtr); + + // Src + // Dest Mask Brush Bitmap = Result + // ------------------------------------- + // 0 0 0 + // 0 1 0 + // 1 0 1 + // 1 1 0 + // + // Brush Bitmap value of 1 means "make transparent" + // 0 means "leave it as it is" + + bitBlt (&destMaskBitmap, + destAt.x (), destAt.y (), + &brushBitmap, + 0, 0, + brushBitmap.width (), brushBitmap.height (), + Qt::NotAndROP); + + destPixmapPtr->setMask (destMaskBitmap); +} + +// public static +void kpPixmapFX::paintMaskTransparentWithBrush (QPixmap *destPixmapPtr, int destX, int destY, + const QPixmap &brushBitmap) +{ + kpPixmapFX::paintMaskTransparentWithBrush (destPixmapPtr, + QPoint (destX, destY), + brushBitmap); +} + + +// public static +void kpPixmapFX::ensureOpaqueAt (QPixmap *destPixmapPtr, const QRect &destRect) +{ + if (!destPixmapPtr || !destPixmapPtr->mask ()/*already opaque*/) + return; + + QBitmap maskBitmap = *destPixmapPtr->mask (); + + QPainter p (&maskBitmap); + + p.setPen (Qt::color1/*opaque*/); + p.setBrush (Qt::color1/*opaque*/); + + p.drawRect (destRect); + + p.end (); + + destPixmapPtr->setMask (maskBitmap); +} + +// public static +void kpPixmapFX::ensureOpaqueAt (QPixmap *destPixmapPtr, const QPoint &destAt, + const QPixmap &srcPixmap) +{ + if (!destPixmapPtr || !destPixmapPtr->mask ()/*already opaque*/) + return; + + QBitmap destMask = *destPixmapPtr->mask (); + + if (srcPixmap.mask ()) + { + bitBlt (&destMask, /* dest */ + destAt, /* dest pt */ + srcPixmap.mask (), /* src */ + QRect (0, 0, srcPixmap.width (), srcPixmap.height ()), /* src rect */ + Qt::OrROP/*if either is opaque, it's opaque*/); + } + else + { + QPainter p (&destMask); + + p.setPen (Qt::color1/*opaque*/); + p.setBrush (Qt::color1/*opaque*/); + + p.drawRect (destAt.x (), destAt.y (), + srcPixmap.width (), srcPixmap.height ()); + + p.end (); + } + + destPixmapPtr->setMask (destMask); +} + +// public static +void kpPixmapFX::ensureOpaqueAt (QPixmap *destPixmapPtr, int destX, int destY, + const QPixmap &srcPixmap) +{ + kpPixmapFX::ensureOpaqueAt (destPixmapPtr, QPoint (destX, destY), srcPixmap); +} + + +// +// Effects +// + +// public static +void kpPixmapFX::convertToGrayscale (QPixmap *destPixmapPtr) +{ + QImage image = kpPixmapFX::convertToImage (*destPixmapPtr); + kpPixmapFX::convertToGrayscale (&image); + *destPixmapPtr = kpPixmapFX::convertToPixmap (image); +} + +// public static +QPixmap kpPixmapFX::convertToGrayscale (const QPixmap &pm) +{ + QImage image = kpPixmapFX::convertToImage (pm); + kpPixmapFX::convertToGrayscale (&image); + return kpPixmapFX::convertToPixmap (image); +} + +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 +void kpPixmapFX::convertToGrayscale (QImage *destImagePtr) +{ + if (destImagePtr->depth () > 8) + { + // hmm, why not just write to the pixmap directly??? + + for (int y = 0; y < destImagePtr->height (); y++) + { + for (int x = 0; x < destImagePtr->width (); x++) + { + destImagePtr->setPixel (x, y, toGray (destImagePtr->pixel (x, y))); + } + } + } + else + { + // 1- & 8- bit images use a color table + for (int i = 0; i < destImagePtr->numColors (); i++) + destImagePtr->setColor (i, toGray (destImagePtr->color (i))); + } +} + +// public static +QImage kpPixmapFX::convertToGrayscale (const QImage &img) +{ + QImage retImage = img; + kpPixmapFX::convertToGrayscale (&retImage); + return retImage; +} + + +// public static +void kpPixmapFX::fill (QPixmap *destPixmapPtr, const kpColor &color) +{ + if (!destPixmapPtr) + return; + + if (color.isOpaque ()) + { + destPixmapPtr->setMask (QBitmap ()); // no mask = opaque + destPixmapPtr->fill (color.toQColor ()); + } + else + { + kpPixmapFX::ensureTransparentAt (destPixmapPtr, destPixmapPtr->rect ()); + } +} + +// public static +QPixmap kpPixmapFX::fill (const QPixmap &pm, const kpColor &color) +{ + QPixmap ret = pm; + kpPixmapFX::fill (&ret, color); + return ret; +} + + +// public static +void kpPixmapFX::resize (QPixmap *destPixmapPtr, int w, int h, + const kpColor &backgroundColor, bool fillNewAreas) +{ +#if DEBUG_KP_PIXMAP_FX && 1 + kdDebug () << "kpPixmapFX::resize()" << endl; +#endif + + if (!destPixmapPtr) + return; + + int oldWidth = destPixmapPtr->width (); + int oldHeight = destPixmapPtr->height (); + + if (w == oldWidth && h == oldHeight) + return; + + + destPixmapPtr->resize (w, h); + + if (fillNewAreas && (w > oldWidth || h > oldHeight)) + { + #if DEBUG_KP_PIXMAP_FX && 1 + kdDebug () << "\tfilling in new areas" << endl; + #endif + QBitmap maskBitmap; + QPainter painter, maskPainter; + + if (backgroundColor.isOpaque ()) + { + painter.begin (destPixmapPtr); + painter.setPen (backgroundColor.toQColor ()); + painter.setBrush (backgroundColor.toQColor ()); + } + + if (backgroundColor.isTransparent () || destPixmapPtr->mask ()) + { + maskBitmap = kpPixmapFX::getNonNullMask (*destPixmapPtr); + maskPainter.begin (&maskBitmap); + maskPainter.setPen (backgroundColor.maskColor ()); + maskPainter.setBrush (backgroundColor.maskColor ()); + } + + #define PAINTER_CALL(cmd) \ + { \ + if (painter.isActive ()) \ + painter . cmd ; \ + \ + if (maskPainter.isActive ()) \ + maskPainter . cmd ; \ + } + if (w > oldWidth) + PAINTER_CALL (drawRect (oldWidth, 0, w - oldWidth, oldHeight)); + + if (h > oldHeight) + PAINTER_CALL (drawRect (0, oldHeight, w, h - oldHeight)); + #undef PAINTER_CALL + + if (maskPainter.isActive ()) + maskPainter.end (); + + if (painter.isActive ()) + painter.end (); + + if (!maskBitmap.isNull ()) + destPixmapPtr->setMask (maskBitmap); + } +} + +// public static +QPixmap kpPixmapFX::resize (const QPixmap &pm, int w, int h, + const kpColor &backgroundColor, bool fillNewAreas) +{ + QPixmap ret = pm; + kpPixmapFX::resize (&ret, w, h, backgroundColor, fillNewAreas); + return ret; +} + + +// public static +void kpPixmapFX::scale (QPixmap *destPixmapPtr, int w, int h, bool pretty) +{ + if (!destPixmapPtr) + return; + + *destPixmapPtr = kpPixmapFX::scale (*destPixmapPtr, w, h, pretty); +} + +// public static +QPixmap kpPixmapFX::scale (const QPixmap &pm, int w, int h, bool pretty) +{ +#if DEBUG_KP_PIXMAP_FX && 0 + kdDebug () << "kpPixmapFX::scale(oldRect=" << pm.rect () + << ",w=" << w + << ",h=" << h + << ",pretty=" << pretty + << ")" + << endl; +#endif + + if (w == pm.width () && h == pm.height ()) + return pm; + + if (pretty) + { + QImage image = kpPixmapFX::convertToImage (pm); + + #if DEBUG_KP_PIXMAP_FX && 0 + kdDebug () << "\tBefore smooth scale:" << endl; + 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 + + image = image.smoothScale (w, h); + + #if DEBUG_KP_PIXMAP_FX && 0 + kdDebug () << "\tAfter smooth scale:" << endl; + 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 kpPixmapFX::convertToPixmap (image, false/*let's not smooth it again*/); + } + else + { + QWMatrix matrix; + + matrix.scale (double (w) / double (pm.width ()), + double (h) / double (pm.height ())); + + return pm.xForm (matrix); + } +} + + +// public static +double kpPixmapFX::AngleInDegreesEpsilon = + KP_RADIANS_TO_DEGREES (atan (1.0 / 10000.0)) + / (2.0/*max error allowed*/ * 2.0/*for good measure*/); + + +static QWMatrix matrixWithZeroOrigin (const QWMatrix &matrix, int width, int height) +{ +#if DEBUG_KP_PIXMAP_FX + kdDebug () << "matrixWithZeroOrigin(w=" << width << ",h=" << height << ")" << endl; + kdDebug () << "\tmatrix: m11=" << matrix.m11 () + << " m12=" << matrix.m12 () + << " m21=" << matrix.m21 () + << " m22=" << matrix.m22 () + << " dx=" << matrix.dx () + << " dy=" << matrix.dy () + << endl; +#endif + // TODO: Should we be using QWMatrix::Areas? + QRect newRect = matrix.mapRect (QRect (0, 0, width, height)); +#if DEBUG_KP_PIXMAP_FX + kdDebug () << "\tnewRect=" << newRect << endl; +#endif + + QWMatrix translatedMatrix (matrix.m11 (), matrix.m12 (), matrix.m21 (), matrix.m22 (), + matrix.dx () - newRect.left (), matrix.dy () - newRect.top ()); + + return translatedMatrix; +} + +static QPixmap xForm (const QPixmap &pm, const QWMatrix &transformMatrix_, + const kpColor &backgroundColor, + int targetWidth, int targetHeight) +{ + QWMatrix transformMatrix = transformMatrix_; + +#if DEBUG_KP_PIXMAP_FX && 1 + kdDebug () << "kppixmapfx.cpp: xForm(pm.size=" << pm.size () + << ",targetWidth=" << targetWidth + << ",targetHeight=" << targetHeight + << ")" + << endl; +#endif + // TODO: Should we be using QWMatrix::Areas? + QRect newRect = transformMatrix.map (pm.rect ()); +#if DEBUG_KP_PIXMAP_FX && 1 + kdDebug () << "\tmappedRect=" << newRect << endl; + +#endif + + QWMatrix scaleMatrix; + if (targetWidth > 0 && targetWidth != newRect.width ()) + { + #if DEBUG_KP_PIXMAP_FX && 1 + kdDebug () << "\tadjusting for targetWidth" << endl; + #endif + scaleMatrix.scale (double (targetWidth) / double (newRect.width ()), 1); + } + + if (targetHeight > 0 && targetHeight != newRect.height ()) + { + #if DEBUG_KP_PIXMAP_FX && 1 + kdDebug () << "\tadjusting for targetHeight" << endl; + #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? + QWMatrix wrongMatrix = transformMatrix * scaleMatrix; + QWMatrix 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 ())); + QWMatrix 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); + QWMatrix correctMatrix = scaleMatrix * transformMatrix; + + kdDebug () << "\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.map (pm.rect ()) + << endl + << "\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.map (pm.rect ()) + << endl + << "\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.map (pm.rect ()) + << endl + << "\tsupposedlyCorrectMatrix: m11=" << correctMatrix.m11 () + << " m12=" << correctMatrix.m12 () + << " m21=" << correctMatrix.m21 () + << " m22=" << correctMatrix.m22 () + << " dx=" << correctMatrix.dx () + << " dy=" << correctMatrix.dy () + << " rect=" << correctMatrix.map (pm.rect ()) + << endl; + #endif + + transformMatrix = transformMatrix * scaleMatrix; + + // TODO: Should we be using QWMatrix::Areas? + newRect = transformMatrix.map (pm.rect ()); + #if DEBUG_KP_PIXMAP_FX && 1 + kdDebug () << "\tnewRect after targetWidth,targetHeight adjust=" << newRect << endl; + #endif + } + + + QPixmap newPixmap (targetWidth > 0 ? targetWidth : newRect.width (), + targetHeight > 0 ? targetHeight : newRect.height ()); + if ((targetWidth > 0 && targetWidth != newRect.width ()) || + (targetHeight > 0 && targetHeight != newRect.height ())) + { + #if DEBUG_KP_PIXMAP_FX && 1 + kdDebug () << "kppixmapfx.cpp: xForm(pm.size=" << pm.size () + << ",targetWidth=" << targetWidth + << ",targetHeight=" << targetHeight + << ") newRect=" << newRect + << " (you are a victim of rounding error)" + << endl; + #endif + } + + QBitmap newBitmapMask; + + if (backgroundColor.isOpaque ()) + newPixmap.fill (backgroundColor.toQColor ()); + + if (backgroundColor.isTransparent () || pm.mask ()) + { + newBitmapMask.resize (newPixmap.width (), newPixmap.height ()); + newBitmapMask.fill (backgroundColor.maskColor ()); + } + + QPainter painter (&newPixmap); +#if DEBUG_KP_PIXMAP_FX && 1 + kdDebug () << "\tmatrix: m11=" << transformMatrix.m11 () + << " m12=" << transformMatrix.m12 () + << " m21=" << transformMatrix.m21 () + << " m22=" << transformMatrix.m22 () + << " dx=" << transformMatrix.dx () + << " dy=" << transformMatrix.dy () + << endl; + const QWMatrix trueMatrix = QPixmap::trueMatrix (transformMatrix, + pm.width (), pm.height ()); + kdDebug () << "\ttrue matrix: m11=" << trueMatrix.m11 () + << " m12=" << trueMatrix.m12 () + << " m21=" << trueMatrix.m21 () + << " m22=" << trueMatrix.m22 () + << " dx=" << trueMatrix.dx () + << " dy=" << trueMatrix.dy () + << endl; +#endif + painter.setWorldMatrix (transformMatrix); +#if DEBUG_KP_PIXMAP_FX && 0 + kdDebug () << "\ttranslate top=" << painter.xForm (QPoint (0, 0)) << endl; + kdDebug () << "\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 + painter.drawPixmap (QPoint (0, 0), pm); + painter.end (); + + if (!newBitmapMask.isNull ()) + { + QPainter maskPainter (&newBitmapMask); + maskPainter.setWorldMatrix (transformMatrix); + maskPainter.drawPixmap (QPoint (0, 0), kpPixmapFX::getNonNullMask (pm)); + maskPainter.end (); + newPixmap.setMask (newBitmapMask); + } + + return newPixmap; +} + +// public static +QWMatrix kpPixmapFX::skewMatrix (int width, int height, double hangle, double vangle) +{ + if (fabs (hangle - 0) < kpPixmapFX::AngleInDegreesEpsilon && + fabs (vangle - 0) < kpPixmapFX::AngleInDegreesEpsilon) + { + return QWMatrix (); + } + + + /* 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. + * + */ + + //QWMatrix matrix (1, tan (KP_DEGREES_TO_RADIANS (vangle)), tan (KP_DEGREES_TO_RADIANS (hangle)), 1, 0, 0); + // I think this is clearer than above :) + QWMatrix matrix; + matrix.shear (tan (KP_DEGREES_TO_RADIANS (hangle)), + tan (KP_DEGREES_TO_RADIANS (vangle))); + + return matrixWithZeroOrigin (matrix, width, height); +} + +// public static +QWMatrix kpPixmapFX::skewMatrix (const QPixmap &pixmap, double hangle, double vangle) +{ + return kpPixmapFX::skewMatrix (pixmap.width (), pixmap.height (), hangle, vangle); +} + + +// public static +void kpPixmapFX::skew (QPixmap *destPixmapPtr, double hangle, double vangle, + const kpColor &backgroundColor, + int targetWidth, int targetHeight) +{ + if (!destPixmapPtr) + return; + + *destPixmapPtr = kpPixmapFX::skew (*destPixmapPtr, hangle, vangle, + backgroundColor, + targetWidth, targetHeight); +} + +// public static +QPixmap kpPixmapFX::skew (const QPixmap &pm, double hangle, double vangle, + const kpColor &backgroundColor, + int targetWidth, int targetHeight) +{ +#if DEBUG_KP_PIXMAP_FX + kdDebug () << "kpPixmapFX::skew() pm.width=" << pm.width () + << " pm.height=" << pm.height () + << " hangle=" << hangle + << " vangle=" << vangle + << " targetWidth=" << targetWidth + << " targetHeight=" << targetHeight + << endl; +#endif + + if (fabs (hangle - 0) < kpPixmapFX::AngleInDegreesEpsilon && + fabs (vangle - 0) < kpPixmapFX::AngleInDegreesEpsilon && + (targetWidth <= 0 && targetHeight <= 0)/*don't want to scale?*/) + { + return pm; + } + + if (fabs (hangle) > 90 - kpPixmapFX::AngleInDegreesEpsilon || + fabs (vangle) > 90 - kpPixmapFX::AngleInDegreesEpsilon) + { + kdError () << "kpPixmapFX::skew() passed hangle and/or vangle out of range (-90 < x < 90)" << endl; + return pm; + } + + + QWMatrix matrix = skewMatrix (pm, hangle, vangle); + + return ::xForm (pm, matrix, backgroundColor, targetWidth, targetHeight); +} + + +// public static +QWMatrix kpPixmapFX::rotateMatrix (int width, int height, double angle) +{ + if (fabs (angle - 0) < kpPixmapFX::AngleInDegreesEpsilon) + { + return QWMatrix (); + } + + QWMatrix matrix; + matrix.translate (width / 2, height / 2); + matrix.rotate (angle); + + return matrixWithZeroOrigin (matrix, width, height); +} + +// public static +QWMatrix kpPixmapFX::rotateMatrix (const QPixmap &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 -= ((int) angle) / 90 * 90; + + // "Impossible" situation? + if (angle < 0 || angle > 90) + { + kdError () << "kpPixmapFX::isLosslessRotation(" << angleIn + << ") result=" << angle + << endl; + return false; // better safe than sorry + } + + const bool ret = (angle < kpPixmapFX::AngleInDegreesEpsilon || + 90 - angle < kpPixmapFX::AngleInDegreesEpsilon); +#if DEBUG_KP_PIXMAP_FX + kdDebug () << "kpPixmapFX::isLosslessRotation(" << angleIn << ")" + << " residual angle=" << angle + << " returning " << ret + << endl; +#endif + return ret; +} + + +// public static +void kpPixmapFX::rotate (QPixmap *destPixmapPtr, double angle, + const kpColor &backgroundColor, + int targetWidth, int targetHeight) +{ + if (!destPixmapPtr) + return; + + *destPixmapPtr = kpPixmapFX::rotate (*destPixmapPtr, angle, + backgroundColor, + targetWidth, targetHeight); +} + +// public static +QPixmap kpPixmapFX::rotate (const QPixmap &pm, double angle, + const kpColor &backgroundColor, + int targetWidth, int targetHeight) +{ + if (fabs (angle - 0) < kpPixmapFX::AngleInDegreesEpsilon && + (targetWidth <= 0 && targetHeight <= 0)/*don't want to scale?*/) + { + return pm; + } + + + QWMatrix matrix = rotateMatrix (pm, angle); + + return ::xForm (pm, matrix, backgroundColor, targetWidth, targetHeight); +} + + +// public static +QWMatrix kpPixmapFX::flipMatrix (int width, int height, bool horz, bool vert) +{ + if (width <= 0 || height <= 0) + { + kdError () << "kpPixmapFX::flipMatrix() passed invalid dimensions" << endl; + return QWMatrix (); + } + + return QWMatrix (horz ? -1 : +1, // m11 + 0, // m12 + 0, // m21 + vert ? -1 : +1, // m22 + horz ? (width - 1) : 0, // dx + vert ? (height - 1) : 0); // dy +} + +// public static +QWMatrix kpPixmapFX::flipMatrix (const QPixmap &pixmap, bool horz, bool vert) +{ + return kpPixmapFX::flipMatrix (pixmap.width (), pixmap.height (), + horz, vert); +} + + +// public static +void kpPixmapFX::flip (QPixmap *destPixmapPtr, bool horz, bool vert) +{ + if (!horz && !vert) + return; + + *destPixmapPtr = kpPixmapFX::flip (*destPixmapPtr, horz, vert); +} + +// public static +QPixmap kpPixmapFX::flip (const QPixmap &pm, bool horz, bool vert) +{ + if (!horz && !vert) + return pm; + + return pm.xForm (flipMatrix (pm, horz, vert)); +} + +// public static +void kpPixmapFX::flip (QImage *destImagePtr, bool horz, bool vert) +{ + if (!horz && !vert) + return; + + *destImagePtr = kpPixmapFX::flip (*destImagePtr, horz, vert); +} + +// public static +QImage kpPixmapFX::flip (const QImage &img, bool horz, bool vert) +{ + if (!horz && !vert) + return img; + + return img.mirror (horz, vert); +} diff --git a/kolourpaint/pixmapfx/kppixmapfx.h b/kolourpaint/pixmapfx/kppixmapfx.h new file mode 100644 index 00000000..c083ee43 --- /dev/null +++ b/kolourpaint/pixmapfx/kppixmapfx.h @@ -0,0 +1,450 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <qstring.h> + + +class QBitmap; +class QColor; +class QImage; +class QPointArray; +class QPixmap; +class QPoint; +class QRect; +class QString; +class QWidget; +class QWMatrix; + +class kpColor; +class kpSelection; + + +class kpPixmapFX +{ +public: + + // + // Overflow Resistant Arithmetic: + // + // Returns INT_MAX if <lhs> or <rhs> < 0 or if would overflow. + static int addDimensions (int lhs, int rhs); + static int multiplyDimensions (int lhs, int rhs); + + + // + // QPixmap Statistics + // + + // Returns the width * height. + static int pixmapArea (const QPixmap &pixmap); + static int pixmapArea (const QPixmap *pixmap); + static int pixmapArea (int width, int height); + + // Returns the estimated size of <pixmap> in pixmap memory. + static int pixmapSize (const QPixmap &pixmap); + static int pixmapSize (const QPixmap *pixmap); + static int pixmapSize (int width, int height, int depth); + + static int imageSize (const QImage &image); + static int imageSize (const QImage *image); + static int imageSize (int width, int height, int depth); + + static int selectionSize (const kpSelection &sel); + static int selectionSize (const kpSelection *sel); + + static int stringSize (const QString &string); + + static int pointArraySize (const QPointArray &points); + + + // + // QPixmap/QImage Conversion Functions + // + + // + // Converts <pixmap> to a QImage and returns it. + // + // WARNING: On an 8-bit screen: + // + // QPixmap result = convertToPixmap (convertToImage (pixmap)); + // + // <result> is slightly differently colored to <pixmap>. + // + // KolourPaint needs to convert to QImage occasionally as + // QImage allows KolourPaint to read pixels and because the QImage + // methods give reliable results and pixel-identical results on + // all platforms. The QPixmap paint engine has no such guarantee + // and even depends on the quality of the video driver. + // + // As a result, KolourPaint should not be used on an 8-bit screen. + // HITODO: Add warning on startup, like in KolourPaint/KDE4. + // + // This bug will be fixed when KolourPaint gets a proper image library, + // where QPixmap -> QImage -> QPixmap transitions will be not be needed. + static QImage convertToImage (const QPixmap &pixmap); + + // + // Dialog info for warning about data loss with convertToPixmap(). + // + struct WarnAboutLossInfo + { + // <moreColorsThanDisplayAndHasAlphaChannelMessage>: + // + // i18n ("The (image \"example.jpg\"|image from the clipboard)" + // " may have more colors than the current screen mode." + // " In order to display it, some colors may be changed." + // " Try increasing your screen depth to at least %1bpp." + // + // "\nIt also" + // + // " contains translucency which is not fully" + // " supported. The translucency data will be" + // " approximated with a 1-bit transparency mask.") + // + // <moreColorsThanDisplayMessage>: + // i18n ("The (image \"example.jpg\"|image from the clipboard)" + // " may have more colors than the current screen mode." + // " In order to display it, some colors may be changed." + // " Try increasing your screen depth to at least %1bpp.") + // + // <hasAlphaChannelMessage>: + // i18n ("The (image \"example.jpg\"|image from the clipboard)" + // " contains translucency which is not fully" + // " supported. The translucency data will be" + // " approximated with a 1-bit transparency mask.") + // + // <dontAskAgainPrefix>: + // + // Don'tAskAgain ID for dialog. + // + // <parent>: + // + // Dialog parent + // + WarnAboutLossInfo (const QString &moreColorsThanDisplayAndHasAlphaChannelMessage, + const QString &moreColorsThanDisplayMessage, + const QString &hasAlphaChannelMessage, + const QString &dontAskAgainPrefix, + QWidget *parent) + : + m_moreColorsThanDisplayAndHasAlphaChannelMessage ( + moreColorsThanDisplayAndHasAlphaChannelMessage), + m_moreColorsThanDisplayMessage ( + moreColorsThanDisplayMessage), + m_hasAlphaChannelMessage ( + hasAlphaChannelMessage), + m_dontAskAgainPrefix ( + dontAskAgainPrefix), + m_parent (parent), + m_isValid (true) + { + } + + WarnAboutLossInfo () + : m_parent (0), + m_isValid (false) + { + } + + ~WarnAboutLossInfo () + { + } + + + bool isValid () const { return m_isValid; } + + + QString m_moreColorsThanDisplayAndHasAlphaChannelMessage, + m_moreColorsThanDisplayMessage, + m_hasAlphaChannelMessage; + QString m_dontAskAgainPrefix; + QWidget *m_parent; + bool m_isValid; + }; + + // + // Converts <image> to a QPixmap of the current display's depth and + // returns it. + // + // If the flag <pretty> is set, it will dither the image making the + // returned pixmap look better but if the image has few colours + // (less than the screen can handle), this will be at the expense of + // exactness of conversion. + // + // This will automatically call ensureNoAlphaChannel(). + // + // Never use a foreign QPixmap that is offered to you - always get the + // foreign QImage and use this function to convert it to a sane QPixmap. + // + // <wali>, if specified, describes parameters for the dialog that comes + // up warning the user of data loss if the <image> contains translucency + // and/or more colors than the current display. + // + static QPixmap convertToPixmap (const QImage &image, bool pretty = false, + const WarnAboutLossInfo &wali = WarnAboutLossInfo ()); + + // Same as convertToPixmap() but tries as hard as possible to make the + // pixmap look like the original <image> - when in doubt, reads the + // config to see whether or not to dither (default: on). + // + // If you know for sure that <image> can be displayed losslessly on + // the screen, you should call convertToPixmap() with <pretty> = false + // instead. If you know for sure that <image> cannot be displayed + // losslessly, then call convertToPixmap() with <pretty> = true. + // + static QPixmap convertToPixmapAsLosslessAsPossible (const QImage &image, + const WarnAboutLossInfo &wali = WarnAboutLossInfo ()); + + + // Sets the RGB values of the pixels where <pixmap> is transparent to + // <transparentColor>. This has visually no effect on the <pixmap> + // unless the mask is lost. + static QPixmap pixmapWithDefinedTransparentPixels (const QPixmap &pixmap, + const QColor &transparentColor); + + + // + // Get/Set Parts of Pixmap + // + + + // + // Returns the pixel and mask data found at the <rect> in <pm>. + // + static QPixmap getPixmapAt (const QPixmap &pm, const QRect &rect); + + // + // Sets the pixel and mask data at <destRect> in <*destPixmapPtr> + // to <srcPixmap>. + // + static void setPixmapAt (QPixmap *destPixmapPtr, const QRect &destRect, + const QPixmap &srcPixmap); + + // + // Sets the pixel and mask data at the rectangle in <*destPixmapPtr>, + // with the top-left <destAt> and dimensions <srcPixmap.rect()>, + // to <srcPixmap>. + // + static void setPixmapAt (QPixmap *destPixmapPtr, const QPoint &destAt, + const QPixmap &srcPixmap); + static void setPixmapAt (QPixmap *destPixmapPtr, int destX, int destY, + const QPixmap &srcPixmap); + + // + // Draws <srcPixmap> on top of <*destPixmapPtr> at <destAt>. + // The mask of <*destPixmapPtr> is adjusted so that all opaque + // pixels in <srcPixmap> will be opaque in <*destPixmapPtr>. + // + static void paintPixmapAt (QPixmap *destPixmapPtr, const QPoint &destAt, + const QPixmap &srcPixmap); + static void paintPixmapAt (QPixmap *destPixmapPtr, int destX, int destY, + const QPixmap &srcPixmap); + + // + // Returns the colour of the pixel at <at> in <pm>. + // If the pixel is transparent, a value is returned such that + // kpTool::isColorTransparent(<return_value>) will return true. + // + static kpColor getColorAtPixel (const QPixmap &pm, const QPoint &at); + static kpColor getColorAtPixel (const QPixmap &pm, int x, int y); + + // + // Returns the color of the pixel at <at> in <img>. + // If the pixel is transparent, a value is returned such that + // kpTool::isColorTransparent(<return_value>) will return true. + // + static kpColor getColorAtPixel (const QImage &img, const QPoint &at); + static kpColor getColorAtPixel (const QImage &img, int x, int y); + + + // + // Mask Operations + // + + + // + // Removes <*destPixmapPtr>'s Alpha Channel and attempts to convert it + // to a mask. KolourPaint - and QPixmap to a great extent - does not + // support Alpha Channels - only masks. Call this whenever you get + // a pixmap from a foreign source; else all KolourPaint code will + // exhibit "undefined behaviour". + // + static void ensureNoAlphaChannel (QPixmap *destPixmapPtr); + + // + // Returns <pm>'s mask or a fully opaque mask (with <pm>'s dimensions) + // if <pm> does not have a mask. + // + static QBitmap getNonNullMask (const QPixmap &pm); + + // + // Ensures that <*destPixmapPtr> is transparent at <rect>. + // + static void ensureTransparentAt (QPixmap *destPixmapPtr, const QRect &destRect); + + // + // Sets the mask of <*destPixmapPtr> at the rectangle, with the + // top-left <destAt> and dimensions <srcMaskBitmap.rect()>, + // to transparent where <brushBitmap> is opaque. + // + // <brushPixmap> must be a QPixmap of depth 1 (or a QBitmap). + // + static void paintMaskTransparentWithBrush (QPixmap *destPixmapPtr, const QPoint &destAt, + const QPixmap &brushBitmap); + static void paintMaskTransparentWithBrush (QPixmap *destPixmapPtr, int destX, int destY, + const QPixmap &brushBitmap); + + // + // Ensures that <*destPixmapPtr> is opaque at <rect>. + // + static void ensureOpaqueAt (QPixmap *destPixmapPtr, const QRect &destRect); + + // + // Ensures that <srcPixmap>'s opaque pixels will be opaque if + // painted onto <*destPixmapPtr> at <destAt>. + // + static void ensureOpaqueAt (QPixmap *destPixmapPtr, const QPoint &destAt, + const QPixmap &srcPixmap); + static void ensureOpaqueAt (QPixmap *destPixmapPtr, int destX, int destY, + const QPixmap &srcPixmap); + + + // + // Effects + // + + + // + // Converts the image to grayscale. + // + static void convertToGrayscale (QPixmap *destPixmapPtr); + static QPixmap convertToGrayscale (const QPixmap &pm); + static void convertToGrayscale (QImage *destImagePtr); + static QImage convertToGrayscale (const QImage &img); + + // + // Fills an image in the given color. + // + static void fill (QPixmap *destPixmapPtr, const kpColor &color); + static QPixmap fill (const QPixmap &pm, const kpColor &color); + + // + // Resizes an image to the given width and height, + // filling any new areas with <backgroundColor> if <fillNewAreas> is set. + // + static void resize (QPixmap *destPixmapPtr, int w, int h, + const kpColor &backgroundColor, bool fillNewAreas = true); + static QPixmap resize (const QPixmap &pm, int w, int h, + const kpColor &backgroundColor, bool fillNewAreas = true); + + // + // Scales an image to the given width and height. + // If <pretty> is true, a smooth scale will be used. + // + static void scale (QPixmap *destPixmapPtr, int w, int h, bool pretty = false); + static QPixmap scale (const QPixmap &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 double AngleInDegreesEpsilon; + + + // + // Skews an image. + // + // <hangle> horizontal angle clockwise (-90 < x < 90) + // <vangle> vertical angle clockwise (-90 < x < 90) + // <backgroundColor> color to fill new areas with + // <targetWidth> if > 0, the desired width of the resultant pixmap + // <targetHeight> if > 0, the desired height of the resultant pixmap + // + // Using <targetWidth> & <targetHeight> to generate preview pixmaps is + // significantly more efficient than skewing and then scaling yourself. + // + static QWMatrix skewMatrix (int width, int height, double hangle, double vangle); + static QWMatrix skewMatrix (const QPixmap &pixmap, double hangle, double vangle); + + static void skew (QPixmap *destPixmapPtr, double hangle, double vangle, + const kpColor &backgroundColor, + int targetWidth = -1, int targetHeight = -1); + static QPixmap skew (const QPixmap &pm, double hangle, double vangle, + const kpColor &backgroundColor, + int targetWidth = -1, int targetHeight = -1); + + // + // Rotates an image. + // + // <angle> clockwise angle to rotate by + // <backgroundColor> color to fill new areas with + // <targetWidth> if > 0, the desired width of the resultant pixmap + // <targetHeight> if > 0, the desired height of the resultant pixmap + // + // Using <targetWidth> & <targetHeight> to generate preview pixmaps is + // significantly more efficient than rotating and then scaling yourself. + // + static QWMatrix rotateMatrix (int width, int height, double angle); + static QWMatrix rotateMatrix (const QPixmap &pixmap, double angle); + + static bool isLosslessRotation (double angle); + + static void rotate (QPixmap *destPixmapPtr, double angle, + const kpColor &backgroundColor, + int targetWidth = -1, int targetHeight = -1); + static QPixmap rotate (const QPixmap &pm, double angle, + const kpColor &backgroundColor, + int targetWidth = -1, int targetHeight = -1); + + + // + // Flips an image in the given directions. + // + static QWMatrix flipMatrix (int width, int height, bool horz, bool vert); + static QWMatrix flipMatrix (const QPixmap &pixmap, bool horz, bool vert); + + // TODO: this kind of overloading is error prone + // e.g. QPixmap pixmap; + // kpPixmapFX::flip (pixmap, false, true); + // looks like it will flip vertically but does absolutely nothing! + // (should be &pixmap) + static void flip (QPixmap *destPixmapPtr, bool horz, bool vert); + static QPixmap flip (const QPixmap &pm, bool horz, bool vert); + static void flip (QImage *destImagePtr, bool horz, bool vert); + static QImage flip (const QImage &img, bool horz, bool vert); +}; + + +#endif // KP_PIXMAP_FX_H diff --git a/kolourpaint/tests/45deg_line.png b/kolourpaint/tests/45deg_line.png Binary files differnew file mode 100644 index 00000000..5af95109 --- /dev/null +++ b/kolourpaint/tests/45deg_line.png diff --git a/kolourpaint/tests/4x4-transparent.png b/kolourpaint/tests/4x4-transparent.png Binary files differnew file mode 100644 index 00000000..58b0668e --- /dev/null +++ b/kolourpaint/tests/4x4-transparent.png diff --git a/kolourpaint/tests/5x5.png b/kolourpaint/tests/5x5.png Binary files differnew file mode 100644 index 00000000..850766c7 --- /dev/null +++ b/kolourpaint/tests/5x5.png diff --git a/kolourpaint/tests/depth1.bmp b/kolourpaint/tests/depth1.bmp Binary files differnew file mode 100644 index 00000000..326c665a --- /dev/null +++ b/kolourpaint/tests/depth1.bmp diff --git a/kolourpaint/tests/dither.png b/kolourpaint/tests/dither.png Binary files differnew file mode 100644 index 00000000..443ed07c --- /dev/null +++ b/kolourpaint/tests/dither.png diff --git a/kolourpaint/tests/rotate.png b/kolourpaint/tests/rotate.png Binary files differnew file mode 100644 index 00000000..f6f028b4 --- /dev/null +++ b/kolourpaint/tests/rotate.png diff --git a/kolourpaint/tests/small16x16.png b/kolourpaint/tests/small16x16.png Binary files differnew file mode 100644 index 00000000..ed61d4d6 --- /dev/null +++ b/kolourpaint/tests/small16x16.png diff --git a/kolourpaint/tests/tool_fill_xlimit.png b/kolourpaint/tests/tool_fill_xlimit.png Binary files differnew file mode 100644 index 00000000..b499879a --- /dev/null +++ b/kolourpaint/tests/tool_fill_xlimit.png diff --git a/kolourpaint/tests/transparent.png b/kolourpaint/tests/transparent.png Binary files differnew file mode 100644 index 00000000..c05a92ef --- /dev/null +++ b/kolourpaint/tests/transparent.png diff --git a/kolourpaint/tests/transparent_selection.png b/kolourpaint/tests/transparent_selection.png Binary files differnew file mode 100644 index 00000000..8db8b7e5 --- /dev/null +++ b/kolourpaint/tests/transparent_selection.png diff --git a/kolourpaint/tools/Makefile.am b/kolourpaint/tools/Makefile.am new file mode 100644 index 00000000..9c665cb1 --- /dev/null +++ b/kolourpaint/tools/Makefile.am @@ -0,0 +1,53 @@ +INCLUDES = -I$(srcdir)/.. -I$(srcdir)/../cursors -I$(srcdir)/../interfaces \ + -I$(srcdir)/../pixmapfx \ + -I$(srcdir)/../tools \ + -I$(srcdir)/../views \ + -I$(srcdir)/../widgets $(all_includes) + +noinst_LTLIBRARIES = libkolourpainttools.la +libkolourpainttools_la_SOURCES = kptoolaction.cpp \ + kptoolairspray.cpp \ + kptoolautocrop.cpp \ + kptoolbrush.cpp kptoolclear.cpp \ + kptoolcolorpicker.cpp kptoolcolorwasher.cpp \ + kptoolconverttograyscale.cpp \ + kptoolcrop.cpp \ + kptoolcurve.cpp \ + kptoolellipse.cpp \ + kptoolellipticalselection.cpp kptooleraser.cpp \ + kptoolflip.cpp kptoolfloodfill.cpp \ + kptoolfreeformselection.cpp \ + kptoolline.cpp kptoolpen.cpp \ + kptoolpolygon.cpp kptoolpolyline.cpp \ + kptoolpreviewdialog.cpp \ + kptoolrectangle.cpp kptoolrectselection.cpp \ + kptoolresizescale.cpp kptoolrotate.cpp \ + kptoolroundedrectangle.cpp kptoolselection.cpp \ + kptoolskew.cpp kptooltext.cpp + +# TODO: Why is this needed? Isn't linking at the toplevel enough? +libkolourpainttools_la_LIBADD = ../pixmapfx/libkolourpaintpixmapfx.la ../cursors/libkolourpaintcursors.la + +METASOURCES = kptoolaction.moc \ + kptoolairspray.moc \ + kptoolbrush.moc \ + kptoolcolorpicker.moc \ + kptoolcolorwasher.moc \ + kptoolcurve.moc \ + kptoolellipse.moc \ + kptooleraser.moc \ + kptoolflip.moc \ + kptoolfloodfill.moc \ + kptoolline.moc \ + kptoolpen.moc \ + kptoolpolygon.moc \ + kptoolpolyline.moc \ + kptoolpreviewdialog.moc \ + kptoolrectangle.moc \ + kptoolresizescale.moc \ + kptoolrotate.moc \ + kptoolroundedrectangle.moc \ + kptoolselection.moc \ + kptoolskew.moc \ + kptooltext.moc + diff --git a/kolourpaint/tools/kptoolaction.cpp b/kolourpaint/tools/kptoolaction.cpp new file mode 100644 index 00000000..ef5c8510 --- /dev/null +++ b/kolourpaint/tools/kptoolaction.cpp @@ -0,0 +1,107 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <kptoolaction.h> + +#include <kptool.h> + + +kpToolAction::kpToolAction (const QString &text, + const QString &pic, const KShortcut &shortcut, + const QObject *receiver, const char *slot, + QObject *parent, const char *name) + : KToggleAction (text, + pic, shortcut, + receiver, slot, + parent, name) +{ + updateToolTip (); +} + +kpToolAction::~kpToolAction () +{ +} + + +// protected +void kpToolAction::updateToolTip () +{ + const QString newToolTip = + kpTool::toolTipForTextAndShortcut (text (), shortcut ()); + if (newToolTip == toolTip ()) + return; + + setToolTip (newToolTip); + emit toolTipChanged (newToolTip); +} + + +// +// KToggleAction interface +// + +// public slot virtual [base KAction] +void kpToolAction::setText (const QString &text) +{ + KToggleAction::setText (text); + updateToolTip (); +} + +// public slot virtual [base KAction] +bool kpToolAction::setShortcut (const KShortcut &shortcut) +{ + bool ret = KToggleAction::setShortcut (shortcut); + updateToolTip (); + return ret; +} + + +// +// KToggleAction implements kpSingleKeyTriggersActionInterface +// + +// public virtual [base kpSingleKeyTriggersActionInterface] +const char *kpToolAction::actionName () const +{ + return name (); +} + +// public virtual [base kpSingleKeyTriggersActionInterface] +KShortcut kpToolAction::actionShortcut () const +{ + return shortcut (); +} + +// public virtual [base kpSingleKeyTriggersActionInterface] +void kpToolAction::actionSetShortcut (const KShortcut &shortcut) +{ + setShortcut (shortcut); +} + + +#include <kptoolaction.moc> diff --git a/kolourpaint/tools/kptoolaction.h b/kolourpaint/tools/kptoolaction.h new file mode 100644 index 00000000..df4e407e --- /dev/null +++ b/kolourpaint/tools/kptoolaction.h @@ -0,0 +1,78 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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_ACTION_H +#define KP_TOOL_ACTION_H + +#include <kactionclasses.h> + +#include <kpsinglekeytriggersaction.h> + + +// Same as KToggleAction but shows the first single key trigger in the tooltip. +class kpToolAction : public KToggleAction, + public kpSingleKeyTriggersActionInterface +{ +Q_OBJECT + +public: + kpToolAction (const QString &text, + const QString &pic, const KShortcut &shortcut, + const QObject *receiver, const char *slot, + QObject *parent, const char *name); + virtual ~kpToolAction (); + + +signals: + // Not emitted when toolTip is manually overriden by setToolTip() + void toolTipChanged (const QString &string); + +protected: + void updateToolTip (); + + + // + // KToggleAction interface + // + +public slots: + virtual void setText (const QString &text); + virtual bool setShortcut (const KShortcut &shortcut); + + + // + // kpSingleKeyTriggersActionInterface + // + +public: + virtual const char *actionName () const; + virtual KShortcut actionShortcut () const; + virtual void actionSetShortcut (const KShortcut &shortcut); +}; + + +#endif // KP_TOOL_ACTION_H diff --git a/kolourpaint/tools/kptoolairspray.cpp b/kolourpaint/tools/kptoolairspray.cpp new file mode 100644 index 00000000..43f8bef3 --- /dev/null +++ b/kolourpaint/tools/kptoolairspray.cpp @@ -0,0 +1,376 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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_SPRAYCAN 0 + +#include <stdlib.h> + +#include <qbitmap.h> +#include <qpainter.h> +#include <qpen.h> +#include <qpixmap.h> +#include <qpoint.h> +#include <qpointarray.h> +#include <qrect.h> +#include <qtimer.h> + +#include <kdebug.h> +#include <klocale.h> + +#include <kpcommandhistory.h> +#include <kpdefs.h> +#include <kpdocument.h> +#include <kpmainwindow.h> +#include <kppixmapfx.h> +#include <kptoolairspray.h> +#include <kptooltoolbar.h> +#include <kptoolwidgetspraycansize.h> +#include <kpview.h> +#include <kpviewmanager.h> + + +/* + * kpToolAirSpray + */ + +kpToolAirSpray::kpToolAirSpray (kpMainWindow *mainWindow) + : kpTool (i18n ("Spraycan"), i18n ("Sprays graffiti"), + Qt::Key_Y, + mainWindow, "tool_spraycan"), + m_currentCommand (0) +{ + m_timer = new QTimer (this); + connect (m_timer, SIGNAL (timeout ()), this, SLOT (actuallyDraw ())); +} + +kpToolAirSpray::~kpToolAirSpray () +{ + delete m_currentCommand; +} + + +// private +QString kpToolAirSpray::haventBegunDrawUserMessage () const +{ + return i18n ("Click or drag to spray graffiti."); +} + +// public virtual +void kpToolAirSpray::begin () +{ + kpToolToolBar *tb = toolToolBar (); + + m_toolWidgetSpraycanSize = 0; + m_size = 10; + + if (tb) + { + m_toolWidgetSpraycanSize = tb->toolWidgetSpraycanSize (); + + if (m_toolWidgetSpraycanSize) + { + m_size = m_toolWidgetSpraycanSize->spraycanSize (); + connect (m_toolWidgetSpraycanSize, SIGNAL (spraycanSizeChanged (int)), + this, SLOT (slotSpraycanSizeChanged (int))); + + m_toolWidgetSpraycanSize->show (); + } + } + + setUserMessage (haventBegunDrawUserMessage ()); +} + +// public virtual +void kpToolAirSpray::end () +{ + if (m_toolWidgetSpraycanSize) + { + disconnect (m_toolWidgetSpraycanSize, SIGNAL (spraycanSizeChanged (int)), + this, SLOT (slotSpraycanSizeChanged (int))); + m_toolWidgetSpraycanSize = 0; + } + + setUserMessage (haventBegunDrawUserMessage ()); +} + +// private slot +void kpToolAirSpray::slotSpraycanSizeChanged (int size) +{ + m_size = size; +} + + +void kpToolAirSpray::beginDraw () +{ + m_currentCommand = new kpToolAirSprayCommand ( + color (m_mouseButton), + m_size, + mainWindow ()); + + // without delay + actuallyDraw (); + + // use a timer instead of reimplementing draw() (we don't draw all the time) + m_timer->start (25); + + setUserMessage (cancelUserMessage ()); +} + +void kpToolAirSpray::draw (const QPoint &thisPoint, const QPoint &, const QRect &) +{ + // if the user is moving the spray, make the spray line continuous + if (thisPoint != m_lastPoint) + { + // without delay + actuallyDraw (); + } + + setUserShapePoints (thisPoint); +} + +void kpToolAirSpray::actuallyDraw () +{ + QPointArray pArray (10); + int numPoints = 0; + + QPoint p = m_currentPoint; + +#if DEBUG_KP_TOOL_SPRAYCAN + kdDebug () << "kpToolAirSpray::actuallyDraw() currentPoint=" << p + << " size=" << m_size + << endl; +#endif + + int radius = m_size / 2; + + for (int i = 0; i < 10; i++) + { + int dx, dy; + + dx = (rand () % m_size) - radius; + dy = (rand () % m_size) - radius; + + // make it look circular + // OPT: can be done better + if (dx * dx + dy * dy <= radius * radius) + pArray [numPoints++] = QPoint (p.x () + dx, p.y () + dy); + } + + pArray.resize (numPoints); + + if (numPoints > 0) + { + // leave the command to draw + m_currentCommand->addPoints (pArray); + } +} + +// virtual +void kpToolAirSpray::cancelShape () +{ +#if 0 + endDraw (QPoint (), QRect ()); + mainWindow ()->commandHistory ()->undo (); +#else + m_timer->stop (); + + m_currentCommand->finalize (); + m_currentCommand->cancel (); + + delete m_currentCommand; + m_currentCommand = 0; +#endif + + setUserMessage (i18n ("Let go of all the mouse buttons.")); +} + +void kpToolAirSpray::releasedAllButtons () +{ + setUserMessage (haventBegunDrawUserMessage ()); +} + +// virtual +void kpToolAirSpray::endDraw (const QPoint &, const QRect &) +{ + m_timer->stop (); + + m_currentCommand->finalize (); + mainWindow ()->commandHistory ()->addCommand (m_currentCommand, false /* don't exec */); + + // don't delete - it's up to the commandHistory + m_currentCommand = 0; + + setUserMessage (haventBegunDrawUserMessage ()); +} + + +/* + * kpToolAirSprayCommand + */ + +kpToolAirSprayCommand::kpToolAirSprayCommand (const kpColor &color, int size, + kpMainWindow *mainWindow) + : kpCommand (mainWindow), + m_color (color), + m_size (size), + m_newPixmapPtr (0) +{ + m_oldPixmap = *document ()->pixmap (); +} + +kpToolAirSprayCommand::~kpToolAirSprayCommand () +{ + delete m_newPixmapPtr; +} + + +// public virtual [base kpCommand] +QString kpToolAirSprayCommand::name () const +{ + return i18n ("Spraycan"); +} + + +// public virtual [base kpCommand] +int kpToolAirSprayCommand::size () const +{ + return kpPixmapFX::pixmapSize (m_newPixmapPtr) + + kpPixmapFX::pixmapSize (m_oldPixmap); +} + + +// Redo: +// +// must not call before unexecute() as m_newPixmapPtr is null +// (one reason why we told addCommand() not to execute, +// the other being that the dots have already been draw onto the doc) +void kpToolAirSprayCommand::execute () +{ + if (m_newPixmapPtr) + { + document ()->setPixmapAt (*m_newPixmapPtr, m_boundingRect.topLeft ()); + + // (will be regenerated in unexecute() if required) + delete m_newPixmapPtr; + m_newPixmapPtr = 0; + } + else + kdError () << "kpToolAirSprayCommand::execute() has null m_newPixmapPtr" << endl; +} + +// Undo: +void kpToolAirSprayCommand::unexecute () +{ + if (!m_newPixmapPtr) + { + // the ultimate in laziness - figure out Redo info only if we Undo + m_newPixmapPtr = new QPixmap (m_boundingRect.width (), m_boundingRect.height ()); + *m_newPixmapPtr = document ()->getPixmapAt (m_boundingRect); + } + else + kdError () << "kpToolAirSprayCommand::unexecute() has non-null newPixmapPtr" << endl; + + document ()->setPixmapAt (m_oldPixmap, m_boundingRect.topLeft ()); +} + + +// public +void kpToolAirSprayCommand::addPoints (const QPointArray &points) +{ + QRect docRect = points.boundingRect (); + +#if DEBUG_KP_TOOL_SPRAYCAN + kdDebug () << "kpToolAirSprayCommand::addPoints() docRect=" << docRect + << " numPoints=" << points.count () << endl; + for (int i = 0; i < (int) points.count (); i++) + kdDebug () << "\t" << i << ": " << points [i] << endl; +#endif + + QPixmap pixmap = document ()->getPixmapAt (docRect); + QBitmap mask; + + QPainter painter, maskPainter; + + if (m_color.isOpaque ()) + { + painter.begin (&pixmap); + painter.setPen (m_color.toQColor ()); + } + + if (pixmap.mask () || m_color.isTransparent ()) + { + mask = kpPixmapFX::getNonNullMask (pixmap); + maskPainter.begin (&mask); + maskPainter.setPen (m_color.maskColor ()); + } + + for (int i = 0; i < (int) points.count (); i++) + { + QPoint pt (points [i].x () - docRect.x (), + points [i].y () - docRect.y ()); + + if (painter.isActive ()) + painter.drawPoint (pt); + + if (maskPainter.isActive ()) + maskPainter.drawPoint (pt); + } + + if (maskPainter.isActive ()) + maskPainter.end (); + + if (painter.isActive ()) + painter.end (); + + if (!mask.isNull ()) + pixmap.setMask (mask); + + viewManager ()->setFastUpdates (); + document ()->setPixmapAt (pixmap, docRect.topLeft ()); + viewManager ()->restoreFastUpdates (); + + m_boundingRect = m_boundingRect.unite (docRect); +} + +void kpToolAirSprayCommand::finalize () +{ + // store only needed part of doc pixmap + m_oldPixmap = kpTool::neededPixmap (m_oldPixmap, m_boundingRect); +} + +void kpToolAirSprayCommand::cancel () +{ + if (m_boundingRect.isValid ()) + { + viewManager ()->setFastUpdates (); + document ()->setPixmapAt (m_oldPixmap, m_boundingRect.topLeft ()); + viewManager ()->restoreFastUpdates (); + } +} + +#include <kptoolairspray.moc> diff --git a/kolourpaint/tools/kptoolairspray.h b/kolourpaint/tools/kptoolairspray.h new file mode 100644 index 00000000..24f02787 --- /dev/null +++ b/kolourpaint/tools/kptoolairspray.h @@ -0,0 +1,110 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 __kptoolairspray_h__ +#define __kptoolairspray_h__ + +#include <kpcommandhistory.h> +#include <kpcolor.h> +#include <kptool.h> + +class QPixmap; +class QPoint; +class QRect; +class QString; +class QTimer; + +class kpMainWindow; +class kpToolAirSprayCommand; +class kpToolWidgetSpraycanSize; +class kpViewManager; + +class kpToolAirSpray : public kpTool +{ +Q_OBJECT + +public: + kpToolAirSpray (kpMainWindow *); + virtual ~kpToolAirSpray (); + +private: + QString haventBegunDrawUserMessage () const; + +public: + virtual void begin (); + virtual void end (); + +private slots: + void slotSpraycanSizeChanged (int size); + +public: + virtual void beginDraw (); + virtual void draw (const QPoint &thisPoint, const QPoint &, const QRect &); + virtual void cancelShape (); + virtual void releasedAllButtons (); + virtual void endDraw (const QPoint &, const QRect &); + +public slots: + void actuallyDraw (); + +private: + kpToolWidgetSpraycanSize *m_toolWidgetSpraycanSize; + kpToolAirSprayCommand *m_currentCommand; + QTimer *m_timer; + int m_size; +}; + +class kpToolAirSprayCommand : public kpCommand +{ +public: + kpToolAirSprayCommand (const kpColor &color, int size, + kpMainWindow *mainWindow); + virtual ~kpToolAirSprayCommand (); + + virtual QString name () const; + + virtual int size () const; + + virtual void execute (); + virtual void unexecute (); + + // interface for KToolAirSpray + void addPoints (const QPointArray &points); + void finalize (); + void cancel (); + +private: + kpColor m_color; + int m_size; + + QPixmap *m_newPixmapPtr; + QPixmap m_oldPixmap; + QRect m_boundingRect; +}; + +#endif // __kptoolairspray_h__ diff --git a/kolourpaint/tools/kptoolautocrop.cpp b/kolourpaint/tools/kptoolautocrop.cpp new file mode 100644 index 00000000..244c192d --- /dev/null +++ b/kolourpaint/tools/kptoolautocrop.cpp @@ -0,0 +1,780 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <kptoolautocrop.h> + +#include <qapplication.h> +#include <qbitmap.h> +#include <qimage.h> +#include <qpainter.h> + +#include <kdebug.h> +#include <klocale.h> +#include <kmessagebox.h> + +#include <kpcolortoolbar.h> +#include <kpcommandhistory.h> +#include <kpdocument.h> +#include <kpmainwindow.h> +#include <kppixmapfx.h> +#include <kpselection.h> +#include <kptool.h> +#include <kpviewmanager.h> + + +kpToolAutoCropBorder::kpToolAutoCropBorder (const QPixmap *pixmapPtr, + int processedColorSimilarity) + : m_pixmapPtr (pixmapPtr), + m_processedColorSimilarity (processedColorSimilarity) +{ + invalidate (); +} + + +// public +int kpToolAutoCropBorder::size () const +{ + return sizeof (kpToolAutoCropBorder); +} + + +// public +const QPixmap *kpToolAutoCropBorder::pixmap () const +{ + return m_pixmapPtr; +} + +// public +int kpToolAutoCropBorder::processedColorSimilarity () const +{ + return m_processedColorSimilarity; +} + +// public +QRect kpToolAutoCropBorder::rect () const +{ + return m_rect; +} + +// public +int kpToolAutoCropBorder::left () const +{ + return m_rect.left (); +} + +// public +int kpToolAutoCropBorder::right () const +{ + return m_rect.right (); +} + +// public +int kpToolAutoCropBorder::top () const +{ + return m_rect.top (); +} + +// public +int kpToolAutoCropBorder::bottom () const +{ + return m_rect.bottom (); +} + +// public +kpColor kpToolAutoCropBorder::referenceColor () const +{ + return m_referenceColor; +} + +// public +kpColor kpToolAutoCropBorder::averageColor () const +{ + if (!m_rect.isValid ()) + return kpColor::invalid; + + if (m_referenceColor.isTransparent ()) + return kpColor::transparent; + else if (m_processedColorSimilarity == 0) + return m_referenceColor; + else + { + int numPixels = (m_rect.width () * m_rect.height ()); + if (numPixels <= 0) + { + kdError () << "kpToolAutoCropBorder::averageColor() rect=" << m_rect << endl; + return kpColor::invalid; + } + + return kpColor (m_redSum / numPixels, + m_greenSum / numPixels, + m_blueSum / numPixels); + } +} + +bool kpToolAutoCropBorder::isSingleColor () const +{ + return m_isSingleColor; +} + + +// public +bool kpToolAutoCropBorder::calculate (int isX, int dir) +{ +#if DEBUG_KP_TOOL_AUTO_CROP && 1 + kdDebug () << "kpToolAutoCropBorder::calculate() CALLED!" << endl; +#endif + int maxX = m_pixmapPtr->width () - 1; + int maxY = m_pixmapPtr->height () - 1; + + QImage image = kpPixmapFX::convertToImage (*m_pixmapPtr); + if (image.isNull ()) + { + kdError () << "Border::calculate() could not convert to QImage" << endl; + return false; + } + + // (sync both branches) + if (isX) + { + int numCols = 0; + int startX = (dir > 0) ? 0 : maxX; + + kpColor col = kpPixmapFX::getColorAtPixel (image, startX, 0); + for (int x = startX; + x >= 0 && x <= maxX; + x += dir) + { + int y; + for (y = 0; y <= maxY; y++) + { + if (!kpPixmapFX::getColorAtPixel (image, x, y).isSimilarTo (col, m_processedColorSimilarity)) + break; + } + + if (y <= maxY) + break; + else + numCols++; + } + + if (numCols) + { + m_rect = QRect (QPoint (startX, 0), + QPoint (startX + (numCols - 1) * dir, maxY)).normalize (); + m_referenceColor = col; + } + } + else + { + int numRows = 0; + int startY = (dir > 0) ? 0 : maxY; + + kpColor col = kpPixmapFX::getColorAtPixel (image, 0, startY); + for (int y = startY; + y >= 0 && y <= maxY; + y += dir) + { + int x; + for (x = 0; x <= maxX; x++) + { + if (!kpPixmapFX::getColorAtPixel (image, x, y).isSimilarTo (col, m_processedColorSimilarity)) + break; + } + + if (x <= maxX) + break; + else + numRows++; + } + + if (numRows) + { + m_rect = QRect (QPoint (0, startY), + QPoint (maxX, startY + (numRows - 1) * dir)).normalize (); + m_referenceColor = col; + } + } + + + if (m_rect.isValid ()) + { + m_isSingleColor = true; + + if (m_referenceColor.isOpaque () && 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 (image, 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 kpToolAutoCropBorder::fillsEntirePixmap () const +{ + return (m_rect == m_pixmapPtr->rect ()); +} + +// public +bool kpToolAutoCropBorder::exists () const +{ + // (will use in an addition so make sure returns 1 or 0) + return (m_rect.isValid () ? 1 : 0); +} + +// public +void kpToolAutoCropBorder::invalidate () +{ + m_rect = QRect (); + m_referenceColor = kpColor::invalid; + m_redSum = m_greenSum = m_blueSum = 0; + m_isSingleColor = false; +} + + +class kpSetOverrideCursorSaver +{ +public: + kpSetOverrideCursorSaver (const QCursor &cursor) + { + QApplication::setOverrideCursor (cursor); + } + + ~kpSetOverrideCursorSaver () + { + QApplication::restoreOverrideCursor (); + } +}; + + +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."), + i18n ("Cannot Remove Internal Border"), + "NothingToAutoCrop"); + } + else + { + KMessageBox::information (mainWindow, + i18n ("KolourPaint cannot automatically crop the image as its" + " border could not be located."), + i18n ("Cannot Autocrop"), + "NothingToAutoCrop"); + } +} + +bool kpToolAutoCrop (kpMainWindow *mainWindow) +{ +#if DEBUG_KP_TOOL_AUTO_CROP + kdDebug () << "kpToolAutoCrop() CALLED!" << endl; +#endif + + if (!mainWindow) + { + kdError () << "kpToolAutoCrop() passed NULL mainWindow" << endl; + return false; + } + + kpDocument *doc = mainWindow->document (); + if (!doc) + { + kdError () << "kpToolAutoCrop() passed NULL document" << endl; + return false; + } + + // OPT: if already pulled selection pixmap, no need to do it again here + QPixmap pixmap = doc->selection () ? doc->getSelectedPixmap () : *doc->pixmap (); + if (pixmap.isNull ()) + { + kdError () << "kptoolAutoCrop() pased NULL pixmap" << endl; + return false; + } + + kpViewManager *vm = mainWindow->viewManager (); + if (!vm) + { + kdError () << "kpToolAutoCrop() passed NULL vm" << endl; + return false; + } + + int processedColorSimilarity = mainWindow->colorToolBar ()->processedColorSimilarity (); + kpToolAutoCropBorder leftBorder (&pixmap, processedColorSimilarity), + rightBorder (&pixmap, processedColorSimilarity), + topBorder (&pixmap, processedColorSimilarity), + botBorder (&pixmap, processedColorSimilarity); + + + kpSetOverrideCursorSaver cursorSaver (Qt::waitCursor); + + // 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.fillsEntirePixmap () || + !rightBorder.calculate (true/*x*/, -1/*going left*/) || + rightBorder.fillsEntirePixmap () || + !topBorder.calculate (false/*y*/, +1/*going down*/) || + topBorder.fillsEntirePixmap () || + !botBorder.calculate (false/*y*/, -1/*going up*/) || + botBorder.fillsEntirePixmap () || + ((numRegions = leftBorder.exists () + + rightBorder.exists () + + topBorder.exists () + + botBorder.exists ()) == 0)) + { + #if DEBUG_KP_TOOL_AUTO_CROP + kdDebug () << "\tcan't find border; leftBorder.rect=" << leftBorder.rect () + << " rightBorder.rect=" << rightBorder.rect () + << " topBorder.rect=" << topBorder.rect () + << " botBorder.rect=" << botBorder.rect () + << endl; + #endif + ::showNothingToAutocropMessage (mainWindow, (bool) doc->selection ()); + return false; + } + +#if DEBUG_KP_TOOL_AUTO_CROP + kdDebug () << "\tnumRegions=" << numRegions << endl; + kdDebug () << "\t\tleft=" << leftBorder.rect () + << " refCol=" << (leftBorder.exists () ? (int *) leftBorder.referenceColor ().toQRgb () : 0) + << " avgCol=" << (leftBorder.exists () ? (int *) leftBorder.averageColor ().toQRgb () : 0) + << endl; + kdDebug () << "\t\tright=" << rightBorder.rect () + << " refCol=" << (rightBorder.exists () ? (int *) rightBorder.referenceColor ().toQRgb () : 0) + << " avgCol=" << (rightBorder.exists () ? (int *) rightBorder.averageColor ().toQRgb () : 0) + << endl; + kdDebug () << "\t\ttop=" << topBorder.rect () + << " refCol=" << (topBorder.exists () ? (int *) topBorder.referenceColor ().toQRgb () : 0) + << " avgCol=" << (topBorder.exists () ? (int *) topBorder.averageColor ().toQRgb () : 0) + << endl; + kdDebug () << "\t\tbot=" << botBorder.rect () + << " refCol=" << (botBorder.exists () ? (int *) botBorder.referenceColor ().toQRgb () : 0) + << " avgCol=" << (botBorder.exists () ? (int *) botBorder.averageColor ().toQRgb () : 0) + << endl; +#endif + + + // In case e.g. the user pastes a solid, coloured-in rectangle, + // we favour 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 + kdDebug () << "\tignoring left border" << endl; + #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 + kdDebug () << "\tignoring top border" << endl; + #endif + topBorder.invalidate (); + } + } + + + mainWindow->addImageOrSelectionCommand ( + new kpToolAutoCropCommand ( + (bool) doc->selection (), + leftBorder, rightBorder, + topBorder, botBorder, + mainWindow)); + + + return true; +} + + +kpToolAutoCropCommand::kpToolAutoCropCommand (bool actOnSelection, + const kpToolAutoCropBorder &leftBorder, + const kpToolAutoCropBorder &rightBorder, + const kpToolAutoCropBorder &topBorder, + const kpToolAutoCropBorder &botBorder, + kpMainWindow *mainWindow) + : kpNamedCommand (name (actOnSelection, DontShowAccel), mainWindow), + m_actOnSelection (actOnSelection), + m_leftBorder (leftBorder), + m_rightBorder (rightBorder), + m_topBorder (topBorder), + m_botBorder (botBorder), + m_leftPixmap (0), + m_rightPixmap (0), + m_topPixmap (0), + m_botPixmap (0) +{ + kpDocument *doc = document (); + if (!doc) + { + kdError () << "kpToolAutoCropCommand::<ctor>() without doc" << endl; + m_oldWidth = 0; + m_oldHeight = 0; + return; + } + + m_oldWidth = doc->width (m_actOnSelection); + m_oldHeight = doc->height (m_actOnSelection); +} + +kpToolAutoCropCommand::~kpToolAutoCropCommand () +{ + deleteUndoPixmaps (); +} + + +// public static +QString kpToolAutoCropCommand::name (bool actOnSelection, int options) +{ + if (actOnSelection) + { + if (options & ShowAccel) + return i18n ("Remove Internal B&order"); + else + return i18n ("Remove Internal Border"); + } + else + { + if (options & ShowAccel) + return i18n ("Autocr&op"); + else + return i18n ("Autocrop"); + } +} + + +// public virtual [base kpCommand] +int kpToolAutoCropCommand::size () const +{ + return m_leftBorder.size () + + m_rightBorder.size () + + m_topBorder.size () + + m_botBorder.size () + + kpPixmapFX::pixmapSize (m_leftPixmap) + + kpPixmapFX::pixmapSize (m_rightPixmap) + + kpPixmapFX::pixmapSize (m_topPixmap) + + kpPixmapFX::pixmapSize (m_botPixmap) + + m_oldSelection.size (); +} + + +// private +void kpToolAutoCropCommand::getUndoPixmap (const kpToolAutoCropBorder &border, QPixmap **pixmap) +{ + kpDocument *doc = document (); + +#if DEBUG_KP_TOOL_AUTO_CROP && 1 + kdDebug () << "kpToolAutoCropCommand::getUndoPixmap()" << endl; + kdDebug () << "\tpixmap=" << pixmap + << " border: rect=" << border.rect () + << " isSingleColor=" << border.isSingleColor () + << endl; +#endif + + if (!doc) + return; + + if (pixmap && border.exists () && !border.isSingleColor ()) + { + if (*pixmap) + { + #if DEBUG_KP_TOOL_AUTO_CROP && 1 + kdDebug () << "\talready have *pixmap - delete it" << endl; + #endif + delete *pixmap; + } + + *pixmap = new QPixmap ( + kpPixmapFX::getPixmapAt (*doc->pixmap (m_actOnSelection), + border.rect ())); + } +} + + +// private +void kpToolAutoCropCommand::getUndoPixmaps () +{ + getUndoPixmap (m_leftBorder, &m_leftPixmap); + getUndoPixmap (m_rightBorder, &m_rightPixmap); + getUndoPixmap (m_topBorder, &m_topPixmap); + getUndoPixmap (m_botBorder, &m_botPixmap); +} + +// private +void kpToolAutoCropCommand::deleteUndoPixmaps () +{ +#if DEBUG_KP_TOOL_AUTO_CROP && 1 + kdDebug () << "kpToolAutoCropCommand::deleteUndoPixmaps()" << endl; +#endif + + delete m_leftPixmap; m_leftPixmap = 0; + delete m_rightPixmap; m_rightPixmap = 0; + delete m_topPixmap; m_topPixmap = 0; + delete m_botPixmap; m_botPixmap = 0; +} + + +// public virtual [base kpCommand] +void kpToolAutoCropCommand::execute () +{ + if (!m_contentsRect.isValid ()) + m_contentsRect = contentsRect (); + + + getUndoPixmaps (); + + + kpDocument *doc = document (); + if (!doc) + return; + + + QPixmap pixmapWithoutBorder = + kpTool::neededPixmap (*doc->pixmap (m_actOnSelection), + m_contentsRect); + + + if (!m_actOnSelection) + doc->setPixmap (pixmapWithoutBorder); + else + { + m_oldSelection = *doc->selection (); + m_oldSelection.setPixmap (QPixmap ()); + + // m_contentsRect is relative to the top of the sel + // while sel is relative to the top of the doc + QRect rect = m_contentsRect; + rect.moveBy (m_oldSelection.x (), m_oldSelection.y ()); + + kpSelection sel (kpSelection::Rectangle, + rect, + pixmapWithoutBorder, + m_oldSelection.transparency ()); + + doc->setSelection (sel); + + if (m_mainWindow->tool ()) + m_mainWindow->tool ()->somethingBelowTheCursorChanged (); + } +} + +// public virtual [base kpCommand] +void kpToolAutoCropCommand::unexecute () +{ +#if DEBUG_KP_TOOL_AUTO_CROP && 1 + kdDebug () << "kpToolAutoCropCommand::unexecute()" << endl; +#endif + + kpDocument *doc = document (); + if (!doc) + return; + + QPixmap pixmap (m_oldWidth, m_oldHeight); + QBitmap maskBitmap; + + // restore the position of the centre image + kpPixmapFX::setPixmapAt (&pixmap, m_contentsRect, + *doc->pixmap (m_actOnSelection)); + + // draw the borders + + QPainter painter (&pixmap); + QPainter maskPainter; + + const kpToolAutoCropBorder *borders [] = + { + &m_leftBorder, &m_rightBorder, + &m_topBorder, &m_botBorder, + 0 + }; + + const QPixmap *pixmaps [] = + { + m_leftPixmap, m_rightPixmap, + m_topPixmap, m_botPixmap, + 0 + }; + + const QPixmap **p = pixmaps; + for (const kpToolAutoCropBorder **b = borders; *b; b++, p++) + { + if (!(*b)->exists ()) + continue; + + if ((*b)->isSingleColor ()) + { + kpColor col = (*b)->referenceColor (); + #if DEBUG_KP_TOOL_AUTO_CROP && 1 + kdDebug () << "\tdrawing border " << (*b)->rect () + << " rgb=" << (int *) col.toQRgb () /* %X hack */ << endl; + #endif + + if (col.isOpaque ()) + { + painter.fillRect ((*b)->rect (), col.toQColor ()); + } + else + { + if (maskBitmap.isNull ()) + { + // TODO: dangerous when a painter is active on pixmap? + maskBitmap = kpPixmapFX::getNonNullMask (pixmap); + maskPainter.begin (&maskBitmap); + } + + maskPainter.fillRect ((*b)->rect (), Qt::color0/*transparent*/); + } + } + else + { + #if DEBUG_KP_TOOL_AUTO_CROP && 1 + kdDebug () << "\trestoring border pixmap " << (*b)->rect () << endl; + #endif + // **p cannot contain a single transparent pixel because + // if it did, all other pixels must be transparent (only + // transparent pixels are similar to transparent pixels) + // and the other branch would execute. + if (*p) + { + // TODO: We should really edit the mask here. Due to good + // luck (if "maskBitmap" is initialized above, this region + // will be marked as opaque in the mask; if it's not + // initialized, we will be opaque by default), we + // don't actually have to edit the mask but this is + // highly error-prone. + painter.drawPixmap ((*b)->rect (), **p); + } + } + } + + if (maskPainter.isActive ()) + maskPainter.end (); + + painter.end (); + + if (!maskBitmap.isNull ()) + pixmap.setMask (maskBitmap); + + + if (!m_actOnSelection) + doc->setPixmap (pixmap); + else + { + kpSelection sel = m_oldSelection; + sel.setPixmap (pixmap); + + doc->setSelection (sel); + + if (m_mainWindow->tool ()) + m_mainWindow->tool ()->somethingBelowTheCursorChanged (); + } + + + deleteUndoPixmaps (); +} + + +// private +QRect kpToolAutoCropCommand::contentsRect () const +{ + const QPixmap *pixmap = document ()->pixmap (m_actOnSelection); + + QPoint topLeft (m_leftBorder.exists () ? + m_leftBorder.rect ().right () + 1 : + 0, + m_topBorder.exists () ? + m_topBorder.rect ().bottom () + 1 : + 0); + QPoint botRight (m_rightBorder.exists () ? + m_rightBorder.rect ().left () - 1 : + pixmap->width () - 1, + m_botBorder.exists () ? + m_botBorder.rect ().top () - 1 : + pixmap->height () - 1); + + return QRect (topLeft, botRight); +} diff --git a/kolourpaint/tools/kptoolautocrop.h b/kolourpaint/tools/kptoolautocrop.h new file mode 100644 index 00000000..4d016a1d --- /dev/null +++ b/kolourpaint/tools/kptoolautocrop.h @@ -0,0 +1,127 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 __kptoolautocrop_h__ +#define __kptoolautocrop_h__ + +#include <qrect.h> + +#include <kpcommandhistory.h> + +#include <kpcolor.h> +#include <kpselection.h> + +class QPixmap; +class kpDocument; +class kpMainWindow; +class kpViewManager; + + +// (returns true on success (even if it did nothing) or false on error) +bool kpToolAutoCrop (kpMainWindow *mainWindow); + + +class kpToolAutoCropBorder +{ +public: + kpToolAutoCropBorder (const QPixmap *pixmapPtr, int processedColorSimilarity); + + int size () const; + + const QPixmap *pixmap () 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 fillsEntirePixmap () const; + bool exists () const; + void invalidate (); + +private: + const QPixmap *m_pixmapPtr; + int m_processedColorSimilarity; + + QRect m_rect; + kpColor m_referenceColor; + int m_redSum, m_greenSum, m_blueSum; + bool m_isSingleColor; +}; + + +class kpToolAutoCropCommand : public kpNamedCommand +{ +public: + kpToolAutoCropCommand (bool actOnSelection, + const kpToolAutoCropBorder &leftBorder, + const kpToolAutoCropBorder &rightBorder, + const kpToolAutoCropBorder &topBorder, + const kpToolAutoCropBorder &botBorder, + kpMainWindow *mainWindow); + virtual ~kpToolAutoCropCommand (); + + enum NameOptions + { + DontShowAccel = 0, + ShowAccel = 1 + }; + + static QString name (bool actOnSelection, int options); + + virtual int size () const; + +private: + void getUndoPixmap (const kpToolAutoCropBorder &border, QPixmap **pixmap); + void getUndoPixmaps (); + void deleteUndoPixmaps (); + +public: + virtual void execute (); + virtual void unexecute (); + +private: + QRect contentsRect () const; + + bool m_actOnSelection; + kpToolAutoCropBorder m_leftBorder, m_rightBorder, m_topBorder, m_botBorder; + QPixmap *m_leftPixmap, *m_rightPixmap, *m_topPixmap, *m_botPixmap; + + QRect m_contentsRect; + int m_oldWidth, m_oldHeight; + kpSelection m_oldSelection; +}; + +#endif // __kptoolautocrop_h__ diff --git a/kolourpaint/tools/kptoolbrush.cpp b/kolourpaint/tools/kptoolbrush.cpp new file mode 100644 index 00000000..6e684ed9 --- /dev/null +++ b/kolourpaint/tools/kptoolbrush.cpp @@ -0,0 +1,45 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <klocale.h> +#include <kptoolbrush.h> + +kpToolBrush::kpToolBrush (kpMainWindow *mainWindow) + : kpToolPen (kpToolPen::Brush, + i18n ("Brush"), + i18n ("Draw using brushes of different shapes and sizes"), + Qt::Key_B, + mainWindow, "tool_brush") +{ +} + +kpToolBrush::~kpToolBrush () +{ +} + +#include <kptoolbrush.moc> diff --git a/kolourpaint/tools/kptoolbrush.h b/kolourpaint/tools/kptoolbrush.h new file mode 100644 index 00000000..69498495 --- /dev/null +++ b/kolourpaint/tools/kptoolbrush.h @@ -0,0 +1,43 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 __kptoolbrush_h__ +#define __kptoolbrush_h__ + +#include <kptoolpen.h> + +class kpToolBrush : public kpToolPen +{ +Q_OBJECT + +public: + kpToolBrush (kpMainWindow *mainWindow); + virtual ~kpToolBrush (); +}; + +#endif // __kptoolbrush_h__ diff --git a/kolourpaint/tools/kptoolclear.cpp b/kolourpaint/tools/kptoolclear.cpp new file mode 100644 index 00000000..230e54a3 --- /dev/null +++ b/kolourpaint/tools/kptoolclear.cpp @@ -0,0 +1,135 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <kptoolclear.h> + +#include <qpixmap.h> + +#include <kdebug.h> +#include <klocale.h> + +#include <kpdefs.h> +#include <kpdocument.h> +#include <kpmainwindow.h> +#include <kppixmapfx.h> +#include <kpselection.h> + + +kpToolClearCommand::kpToolClearCommand (bool actOnSelection, + const kpColor &newColor, + kpMainWindow *mainWindow) + : kpCommand (mainWindow), + m_actOnSelection (actOnSelection), + m_newColor (newColor), + m_oldPixmapPtr (0) +{ +} + +kpToolClearCommand::kpToolClearCommand (bool actOnSelection, + kpMainWindow *mainWindow) + : kpCommand (mainWindow), + m_actOnSelection (actOnSelection), + m_newColor (mainWindow ? mainWindow->backgroundColor () : kpColor::invalid), + m_oldPixmapPtr (0) +{ +} + +kpToolClearCommand::~kpToolClearCommand () +{ + delete m_oldPixmapPtr; +} + + +// public virtual [base kpCommand] +QString kpToolClearCommand::name () const +{ + QString opName = i18n ("Clear"); + + if (m_actOnSelection) + return i18n ("Selection: %1").arg (opName); + else + return opName; +} + + +// public virtual [base kpCommand] +int kpToolClearCommand::size () const +{ + return kpPixmapFX::pixmapSize (m_oldPixmapPtr); +} + + +// public virtual [base kpCommand] +void kpToolClearCommand::execute () +{ + kpDocument *doc = document (); + if (!doc) + { + kdError () << "kpToolClearCommand::execute() without doc" << endl; + return; + } + + + m_oldPixmapPtr = new QPixmap (); + *m_oldPixmapPtr = *doc->pixmap (m_actOnSelection); + + + if (m_actOnSelection) + { + // OPT: could just edit pixmap directly and signal change + kpSelection *sel = doc->selection (); + + QPixmap newPixmap (sel->width (), sel->height ()); + kpPixmapFX::fill (&newPixmap, m_newColor); + // TODO: maybe disable Image/Clear if transparent colour + if (m_newColor.isOpaque ()) + newPixmap.setMask (sel->maskForOwnType ()); + + sel->setPixmap (newPixmap); + } + else + doc->fill (m_newColor); +} + +// public virtual [base kpCommand] +void kpToolClearCommand::unexecute () +{ + kpDocument *doc = document (); + if (!doc) + { + kdError () << "kpToolClearCommand::execute() without doc" << endl; + return; + } + + + doc->setPixmap (m_actOnSelection, *m_oldPixmapPtr); + + + delete m_oldPixmapPtr; + m_oldPixmapPtr = 0; +} diff --git a/kolourpaint/tools/kptoolclear.h b/kolourpaint/tools/kptoolclear.h new file mode 100644 index 00000000..ccf3697f --- /dev/null +++ b/kolourpaint/tools/kptoolclear.h @@ -0,0 +1,68 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 __kptoolclear_h__ +#define __kptoolclear_h__ + +#include <kpcommandhistory.h> + +#include <kpcolor.h> + +class QPixmap; +class QString; + +class kpDocument; +class kpMainWindow; + + +class kpToolClearCommand : public kpCommand +{ +public: + kpToolClearCommand (bool actOnSelection, + const kpColor &newColor, + kpMainWindow *mainWindow); + kpToolClearCommand (bool actOnSelection, + kpMainWindow *mainWindow); + virtual ~kpToolClearCommand (); + + virtual QString name () const; + + virtual int size () const; + + virtual void execute (); + virtual void unexecute (); + +private: + bool m_actOnSelection; + + kpColor m_newColor; + QPixmap *m_oldPixmapPtr; +}; + + +#endif // __kptoolclear_h__ diff --git a/kolourpaint/tools/kptoolcolorpicker.cpp b/kolourpaint/tools/kptoolcolorpicker.cpp new file mode 100644 index 00000000..1050b1cf --- /dev/null +++ b/kolourpaint/tools/kptoolcolorpicker.cpp @@ -0,0 +1,197 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <kptoolcolorpicker.h> + +#include <qimage.h> +#include <qpixmap.h> +#include <qpoint.h> + +#include <kdebug.h> +#include <klocale.h> + +#include <kpcolortoolbar.h> +#include <kpcommandhistory.h> +#include <kpdefs.h> +#include <kpdocument.h> +#include <kpmainwindow.h> +#include <kppixmapfx.h> + + +/* + * kpToolColorPicker + */ + +kpToolColorPicker::kpToolColorPicker (kpMainWindow *mainWindow) + : kpTool (i18n ("Color Picker"), i18n ("Lets you select a color from the image"), + Qt::Key_C, + mainWindow, "tool_color_picker") +{ +} + +kpToolColorPicker::~kpToolColorPicker () +{ +} + +kpColor kpToolColorPicker::colorAtPixel (const QPoint &p) +{ +#if DEBUG_KP_TOOL_COLOR_PICKER && 0 + kdDebug () << "kpToolColorPicker::colorAtPixel" << p << endl; +#endif + + return kpPixmapFX::getColorAtPixel (*document ()->pixmap (), p); +} + + +QString kpToolColorPicker::haventBegunDrawUserMessage () const +{ + return i18n ("Click to select a color."); +} + +void kpToolColorPicker::begin () +{ + setUserMessage (haventBegunDrawUserMessage ()); +} + +// virtual +void kpToolColorPicker::beginDraw () +{ + m_oldColor = color (m_mouseButton); + + setUserMessage (cancelUserMessage ()); +} + +// virtual +void kpToolColorPicker::draw (const QPoint &thisPoint, const QPoint &, const QRect &) +{ + const kpColor color = colorAtPixel (thisPoint); + + if (color.isValid ()) + { + mainWindow ()->colorToolBar ()->setColor (m_mouseButton, color); + setUserShapePoints (thisPoint); + } + else + { + mainWindow ()->colorToolBar ()->setColor (m_mouseButton, m_oldColor); + setUserShapePoints (); + } +} + +// virtual +void kpToolColorPicker::cancelShape () +{ + mainWindow ()->colorToolBar ()->setColor (m_mouseButton, m_oldColor); + + setUserMessage (i18n ("Let go of all the mouse buttons.")); +} + +void kpToolColorPicker::releasedAllButtons () +{ + setUserMessage (haventBegunDrawUserMessage ()); + +} + +// virtual +void kpToolColorPicker::endDraw (const QPoint &thisPoint, const QRect &) +{ + const kpColor color = colorAtPixel (thisPoint); + + if (color.isValid ()) + { + kpToolColorPickerCommand *cmd = new kpToolColorPickerCommand ( + m_mouseButton, + color, m_oldColor, + mainWindow ()); + + mainWindow ()->commandHistory ()->addCommand (cmd, false /* no exec */); + setUserMessage (haventBegunDrawUserMessage ()); + } + else + { + cancelShape (); + } +} + +/* + * kpToolColorPickerCommand + */ + +kpToolColorPickerCommand::kpToolColorPickerCommand (int mouseButton, + const kpColor &newColor, + const kpColor &oldColor, + kpMainWindow *mainWindow) + : kpCommand (mainWindow), + m_mouseButton (mouseButton), + m_newColor (newColor), + m_oldColor (oldColor) +{ +} + +kpToolColorPickerCommand::~kpToolColorPickerCommand () +{ +} + + +// public virtual [base kpCommand] +QString kpToolColorPickerCommand::name () const +{ + return i18n ("Color Picker"); +} + + +// public virtual [base kpCommand] +int kpToolColorPickerCommand::size () const +{ + return 0; +} + + +// public virtual [base kpCommand] +void kpToolColorPickerCommand::execute () +{ + colorToolBar ()->setColor (m_mouseButton, m_newColor); +} + +// public virtual [base kpCommand] +void kpToolColorPickerCommand::unexecute () +{ + colorToolBar ()->setColor (m_mouseButton, m_oldColor); +} + + +// private +kpColorToolBar *kpToolColorPickerCommand::colorToolBar () const +{ + return m_mainWindow ? m_mainWindow->colorToolBar () : 0; +} + +#include <kptoolcolorpicker.moc> diff --git a/kolourpaint/tools/kptoolcolorpicker.h b/kolourpaint/tools/kptoolcolorpicker.h new file mode 100644 index 00000000..46fc94be --- /dev/null +++ b/kolourpaint/tools/kptoolcolorpicker.h @@ -0,0 +1,95 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 __kptoolcolorpicker_h__ +#define __kptoolcolorpicker_h__ + +#include <kpcommandhistory.h> + +#include <kpcolor.h> +#include <kptool.h> + +class QPoint; +class QRect; + +class kpColorToolBar; + +class kpToolColorPicker : public kpTool +{ +Q_OBJECT + +public: + kpToolColorPicker (kpMainWindow *); + virtual ~kpToolColorPicker (); + + // generally the user goes to pick a color but wants to return to using + // his/her previous drawing tool + virtual bool returnToPreviousToolAfterEndDraw () const { return true; } + +private: + QString haventBegunDrawUserMessage () const; + +public: + virtual void begin (); + virtual void beginDraw (); + virtual void draw (const QPoint &thisPoint, const QPoint &, const QRect &); + virtual void cancelShape (); + virtual void releasedAllButtons (); + virtual void endDraw (const QPoint &thisPoint, const QRect &); + +private: + kpColor colorAtPixel (const QPoint &p); + + kpColor m_oldColor; +}; + +class kpToolColorPickerCommand : public kpCommand +{ +public: + kpToolColorPickerCommand (int mouseButton, + const kpColor &newColor, const kpColor &oldColor, + kpMainWindow *mainWindow); + virtual ~kpToolColorPickerCommand (); + + virtual QString name () const; + + virtual int size () const; + + virtual void execute (); + virtual void unexecute (); + +private: + kpColorToolBar *colorToolBar () const; + +private: + int m_mouseButton; + kpColor m_newColor; + kpColor m_oldColor; +}; + +#endif // __kptoolcolorpicker_h__ diff --git a/kolourpaint/tools/kptoolcolorwasher.cpp b/kolourpaint/tools/kptoolcolorwasher.cpp new file mode 100644 index 00000000..6c2d091f --- /dev/null +++ b/kolourpaint/tools/kptoolcolorwasher.cpp @@ -0,0 +1,45 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <klocale.h> +#include <kptoolcolorwasher.h> + +kpToolColorWasher::kpToolColorWasher (kpMainWindow *mainWindow) + : kpToolPen (kpToolPen::ColorWasher, + i18n ("Color Eraser"), + i18n ("Replaces pixels of the foreground color with the background color"), + Qt::Key_O, + mainWindow, "tool_color_washer") +{ +} + +kpToolColorWasher::~kpToolColorWasher () +{ +} + +#include <kptoolcolorwasher.moc> diff --git a/kolourpaint/tools/kptoolcolorwasher.h b/kolourpaint/tools/kptoolcolorwasher.h new file mode 100644 index 00000000..1a707c3e --- /dev/null +++ b/kolourpaint/tools/kptoolcolorwasher.h @@ -0,0 +1,43 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 __kptoolcolorwasher_h__ +#define __kptoolcolorwasher_h__ + +#include <kptoolpen.h> + +class kpToolColorWasher : public kpToolPen +{ +Q_OBJECT + +public: + kpToolColorWasher (kpMainWindow *mainWindow); + virtual ~kpToolColorWasher (); +}; + +#endif // __kptoolcolorwasher_h__ diff --git a/kolourpaint/tools/kptoolconverttograyscale.cpp b/kolourpaint/tools/kptoolconverttograyscale.cpp new file mode 100644 index 00000000..a80ef8fa --- /dev/null +++ b/kolourpaint/tools/kptoolconverttograyscale.cpp @@ -0,0 +1,106 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <qapplication.h> +#include <qpixmap.h> + +#include <klocale.h> + +#include <kpdefs.h> +#include <kpdocument.h> +#include <kpmainwindow.h> +#include <kppixmapfx.h> +#include <kpselection.h> +#include <kptoolconverttograyscale.h> + + +kpToolConvertToGrayscaleCommand::kpToolConvertToGrayscaleCommand (bool actOnSelection, + kpMainWindow *mainWindow) + : kpCommand (mainWindow), + m_actOnSelection (actOnSelection), + m_oldPixmapPtr (0) +{ +} + +kpToolConvertToGrayscaleCommand::~kpToolConvertToGrayscaleCommand () +{ + delete m_oldPixmapPtr; +} + + +// public virtual [base kpCommand] +QString kpToolConvertToGrayscaleCommand::name () const +{ + QString opName = i18n ("Reduce to Grayscale"); + + if (m_actOnSelection) + return i18n ("Selection: %1").arg (opName); + else + return opName; +} + + +// public virtual [base kpCommand] +int kpToolConvertToGrayscaleCommand::size () const +{ + return kpPixmapFX::pixmapSize (m_oldPixmapPtr); +} + + +// public virtual [base kpCommand] +void kpToolConvertToGrayscaleCommand::execute () +{ + kpDocument *doc = document (); + if (!doc) + return; + + QApplication::setOverrideCursor (Qt::waitCursor); + + m_oldPixmapPtr = new QPixmap (); + *m_oldPixmapPtr = *doc->pixmap (m_actOnSelection); + + QPixmap newPixmap = kpPixmapFX::convertToGrayscale (*doc->pixmap (m_actOnSelection)); + + doc->setPixmap (m_actOnSelection, newPixmap); + + QApplication::restoreOverrideCursor (); +} + +// public virtual [base kpCommand] +void kpToolConvertToGrayscaleCommand::unexecute () +{ + kpDocument *doc = document (); + if (!doc) + return; + + doc->setPixmap (m_actOnSelection, *m_oldPixmapPtr); + + delete m_oldPixmapPtr; + m_oldPixmapPtr = 0; +} + diff --git a/kolourpaint/tools/kptoolconverttograyscale.h b/kolourpaint/tools/kptoolconverttograyscale.h new file mode 100644 index 00000000..6ea5e515 --- /dev/null +++ b/kolourpaint/tools/kptoolconverttograyscale.h @@ -0,0 +1,57 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 __kptoolconverttograyscale_h__ +#define __kptoolconverttograyscale_h__ + +#include <kpcommandhistory.h> + +class QPixmap; +class QString; + +class kpMainWindow; + +class kpToolConvertToGrayscaleCommand : public kpCommand +{ +public: + kpToolConvertToGrayscaleCommand (bool actOnSelection, + kpMainWindow *mainWindow); + virtual ~kpToolConvertToGrayscaleCommand (); + + virtual QString name () const; + virtual int size () const; + + virtual void execute (); + virtual void unexecute (); + +private: + bool m_actOnSelection; + QPixmap *m_oldPixmapPtr; +}; + +#endif // __kptoolconverttograyscale_h__ diff --git a/kolourpaint/tools/kptoolcrop.cpp b/kolourpaint/tools/kptoolcrop.cpp new file mode 100644 index 00000000..8cc6e880 --- /dev/null +++ b/kolourpaint/tools/kptoolcrop.cpp @@ -0,0 +1,335 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <kptoolcrop.h> + +#include <qpixmap.h> + +#include <kdebug.h> +#include <klocale.h> + +#include <kpcolor.h> +#include <kpcommandhistory.h> +#include <kpdocument.h> +#include <kpmainwindow.h> +#include <kpselection.h> +#include <kptoolclear.h> +#include <kptoolresizescale.h> +#include <kptoolselection.h> +#include <kpviewmanager.h> + + +kpSelection selectionBorderAndMovedTo0_0 (const kpSelection &sel) +{ + kpSelection borderSel = sel; + + borderSel.setPixmap (QPixmap ()); // only interested in border + borderSel.moveTo (QPoint (0, 0)); + + return borderSel; +} + + +// +// kpToolCropSetImageCommand +// + +class kpToolCropSetImageCommand : public kpCommand +{ +public: + kpToolCropSetImageCommand (kpMainWindow *mainWindow); + virtual ~kpToolCropSetImageCommand (); + + /* (uninteresting child of macro cmd) */ + virtual QString name () const { return QString::null; } + + virtual int size () const + { + return kpPixmapFX::pixmapSize (m_oldPixmap) + + kpPixmapFX::selectionSize (m_fromSelection) + + kpPixmapFX::pixmapSize (m_pixmapIfFromSelectionDoesntHaveOne); + } + + virtual void execute (); + virtual void unexecute (); + +protected: + kpColor m_backgroundColor; + QPixmap m_oldPixmap; + kpSelection m_fromSelection; + QPixmap m_pixmapIfFromSelectionDoesntHaveOne; +}; + + +kpToolCropSetImageCommand::kpToolCropSetImageCommand (kpMainWindow *mainWindow) + : kpCommand (mainWindow), + m_backgroundColor (mainWindow ? mainWindow->backgroundColor () : kpColor::invalid), + m_fromSelection (*mainWindow->document ()->selection ()), + m_pixmapIfFromSelectionDoesntHaveOne ( + m_fromSelection.pixmap () ? + QPixmap () : + mainWindow->document ()->getSelectedPixmap ()) +{ +} + +kpToolCropSetImageCommand::~kpToolCropSetImageCommand () +{ +} + + +// public virtual [base kpCommand] +void kpToolCropSetImageCommand::execute () +{ +#if DEBUG_KP_TOOL_CROP + kdDebug () << "kpToolCropSetImageCommand::execute()" << endl; +#endif + + viewManager ()->setQueueUpdates (); + { + m_oldPixmap = kpPixmapFX::getPixmapAt (*document ()->pixmap (), + QRect (0, 0, m_fromSelection.width (), m_fromSelection.height ())); + + + // + // 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. + // + + QPixmap newDocPixmap (m_fromSelection.width (), m_fromSelection.height ()); + kpPixmapFX::fill (&newDocPixmap, m_backgroundColor); + + #if DEBUG_KP_TOOL_CROP + kdDebug () << "\tsel: rect=" << m_fromSelection.boundingRect () + << " pm=" << m_fromSelection.pixmap () + << endl; + #endif + QPixmap selTransparentPixmap; + + if (m_fromSelection.pixmap ()) + { + selTransparentPixmap = m_fromSelection.transparentPixmap (); + #if DEBUG_KP_TOOL_CROP + kdDebug () << "\thave pixmap; rect=" + << selTransparentPixmap.rect () + << endl; + #endif + } + else + { + selTransparentPixmap = m_pixmapIfFromSelectionDoesntHaveOne; + #if DEBUG_KP_TOOL_CROP + kdDebug () << "\tno pixmap in sel - get it; rect=" + << selTransparentPixmap.rect () + << endl; + #endif + } + + kpPixmapFX::paintMaskTransparentWithBrush (&newDocPixmap, + QPoint (0, 0), + m_fromSelection.maskForOwnType ()); + + kpPixmapFX::paintPixmapAt (&newDocPixmap, + QPoint (0, 0), + selTransparentPixmap); + + + document ()->setPixmapAt (newDocPixmap, QPoint (0, 0)); + document ()->selectionDelete (); + + + if (mainWindow ()->tool ()) + m_mainWindow->tool ()->somethingBelowTheCursorChanged (); + } + viewManager ()->restoreQueueUpdates (); +} + +// public virtual [base kpCommand] +void kpToolCropSetImageCommand::unexecute () +{ +#if DEBUG_KP_TOOL_CROP + kdDebug () << "kpToolCropSetImageCommand::unexecute()" << endl; +#endif + + viewManager ()->setQueueUpdates (); + { + document ()->setPixmapAt (m_oldPixmap, QPoint (0, 0)); + m_oldPixmap.resize (0, 0); + + #if DEBUG_KP_TOOL_CROP + kdDebug () << "\tsel: rect=" << m_fromSelection.boundingRect () + << " pm=" << m_fromSelection.pixmap () + << endl; + #endif + document ()->setSelection (m_fromSelection); + + if (mainWindow ()->tool ()) + m_mainWindow->tool ()->somethingBelowTheCursorChanged (); + } + viewManager ()->restoreQueueUpdates (); +} + + +// +// kpToolCropCommand +// + + +class kpToolCropCommand : public kpMacroCommand +{ +public: + kpToolCropCommand (kpMainWindow *mainWindow); + virtual ~kpToolCropCommand (); +}; + + +kpToolCropCommand::kpToolCropCommand (kpMainWindow *mainWindow) + : kpMacroCommand (i18n ("Set as Image"), mainWindow) +{ +#if DEBUG_KP_TOOL_CROP + kdDebug () << "kpToolCropCommand::<ctor>()" << endl; +#endif + + if (!mainWindow || + !mainWindow->document () || + !mainWindow->document ()->selection ()) + { + kdError () << "kpToolCropCommand::kpToolCropCommand() without sel" << endl; + return; + } + + kpSelection *sel = mainWindow->document ()->selection (); + + +#if DEBUG_KP_TOOL_CROP + kdDebug () << "\tsel: w=" << sel->width () + << " h=" << sel->height () + << " <- resizing doc to these dimen" << endl; +#endif + + // (must resize doc _before_ kpToolCropSetImageCommand in case doc + // needs to gets bigger - else pasted down pixmap may not fit) + addCommand ( + new kpToolResizeScaleCommand ( + false/*act on doc, not sel*/, + sel->width (), sel->height (), + kpToolResizeScaleCommand::Resize, + mainWindow)); + + + if (sel->isText ()) + { + #if DEBUG_KP_TOOL_CROP + kdDebug () << "\tisText" << endl; + kdDebug () << "\tclearing doc with trans cmd" << endl; + #endif + addCommand ( + new kpToolClearCommand ( + false/*act on doc*/, + kpColor::transparent, + mainWindow)); + + #if DEBUG_KP_TOOL_CROP + kdDebug () << "\tmoving sel to (0,0) cmd" << endl; + #endif + kpToolSelectionMoveCommand *moveCmd = + new kpToolSelectionMoveCommand ( + QString::null/*uninteresting child of macro cmd*/, + mainWindow); + moveCmd->moveTo (QPoint (0, 0), true/*move on exec, not now*/); + moveCmd->finalize (); + addCommand (moveCmd); + } + else + { + #if DEBUG_KP_TOOL_CROP + kdDebug () << "\tis pixmap sel" << endl; + kdDebug () << "\tcreating SetImage cmd" << endl; + #endif + addCommand (new kpToolCropSetImageCommand (mainWindow)); + + #if 0 + addCommand ( + new kpToolSelectionCreateCommand ( + QString::null/*uninteresting child of macro cmd*/, + selectionBorderAndMovedTo0_0 (*sel), + mainWindow)); + #endif + } +} + +kpToolCropCommand::~kpToolCropCommand () +{ +} + + +void kpToolCrop (kpMainWindow *mainWindow) +{ + kpDocument *doc = mainWindow->document (); + if (!doc) + return; + + kpSelection *sel = doc ? doc->selection () : 0; + if (!sel) + return; + + + bool selWasText = sel->isText (); + kpSelection borderSel = selectionBorderAndMovedTo0_0 (*sel); + + + mainWindow->addImageOrSelectionCommand ( + new kpToolCropCommand (mainWindow), + true/*add create cmd*/, + false/*don't add pull cmd*/); + + + if (!selWasText) + { + mainWindow->commandHistory ()->addCommand ( + new kpToolSelectionCreateCommand ( + i18n ("Selection: Create"), + borderSel, + mainWindow)); + } +} diff --git a/kolourpaint/tools/kptoolcrop.h b/kolourpaint/tools/kptoolcrop.h new file mode 100644 index 00000000..c710a041 --- /dev/null +++ b/kolourpaint/tools/kptoolcrop.h @@ -0,0 +1,39 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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_CROP_H +#define KP_TOOL_CROP_H + + +class kpMainWindow; + + +void kpToolCrop (kpMainWindow *mainWindow); + + +#endif // KP_TOOL_CROP_H diff --git a/kolourpaint/tools/kptoolcurve.cpp b/kolourpaint/tools/kptoolcurve.cpp new file mode 100644 index 00000000..f889c1ba --- /dev/null +++ b/kolourpaint/tools/kptoolcurve.cpp @@ -0,0 +1,47 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <kptoolcurve.h> + +#include <klocale.h> + + +kpToolCurve::kpToolCurve (kpMainWindow *mainWindow) + : kpToolPolygon (Curve, + i18n ("Curve"), + i18n ("Draws curves"), + Qt::Key_V, + mainWindow, "tool_curve") +{ +} + +kpToolCurve::~kpToolCurve () +{ +} + +#include <kptoolcurve.moc> diff --git a/kolourpaint/tools/kptoolcurve.h b/kolourpaint/tools/kptoolcurve.h new file mode 100644 index 00000000..489ce1fb --- /dev/null +++ b/kolourpaint/tools/kptoolcurve.h @@ -0,0 +1,45 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 __kptoolcurve_h__ +#define __kptoolcurve_h__ + +#include <kptoolpolygon.h> + +class kpMainWindow; + +class kpToolCurve : public kpToolPolygon +{ +Q_OBJECT + +public: + kpToolCurve (kpMainWindow *mainWindow); + virtual ~kpToolCurve (); +}; + +#endif // __kptoolcurve_h__ diff --git a/kolourpaint/tools/kptoolellipse.cpp b/kolourpaint/tools/kptoolellipse.cpp new file mode 100644 index 00000000..f3b31dbb --- /dev/null +++ b/kolourpaint/tools/kptoolellipse.cpp @@ -0,0 +1,45 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <klocale.h> +#include <kptoolellipse.h> + +kpToolEllipse::kpToolEllipse (kpMainWindow *mainWindow) + : kpToolRectangle (Ellipse, + i18n ("Ellipse"), + i18n ("Draws ellipses and circles"), + Qt::Key_E, + mainWindow, "tool_ellipse") +{ +} + +kpToolEllipse::~kpToolEllipse () +{ +} + +#include <kptoolellipse.moc> diff --git a/kolourpaint/tools/kptoolellipse.h b/kolourpaint/tools/kptoolellipse.h new file mode 100644 index 00000000..fc9bf798 --- /dev/null +++ b/kolourpaint/tools/kptoolellipse.h @@ -0,0 +1,45 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 __kptoolellipse_h__ +#define __kptoolellipse_h__ + +#include <kptoolrectangle.h> + +class kpMainWindow; + +class kpToolEllipse : public kpToolRectangle +{ +Q_OBJECT + +public: + kpToolEllipse (kpMainWindow *); + virtual ~kpToolEllipse (); +}; + +#endif // __kptoolellipse_h__ diff --git a/kolourpaint/tools/kptoolellipticalselection.cpp b/kolourpaint/tools/kptoolellipticalselection.cpp new file mode 100644 index 00000000..13daf799 --- /dev/null +++ b/kolourpaint/tools/kptoolellipticalselection.cpp @@ -0,0 +1,46 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <kptoolellipticalselection.h> + +#include <klocale.h> + + +kpToolEllipticalSelection::kpToolEllipticalSelection (kpMainWindow *mainWindow) + : kpToolSelection (Ellipse, + i18n ("Selection (Elliptical)"), + i18n ("Makes an elliptical or circular selection"), + Qt::Key_I, + mainWindow, "tool_elliptical_selection") +{ +} + +kpToolEllipticalSelection::~kpToolEllipticalSelection () +{ +} + diff --git a/kolourpaint/tools/kptoolellipticalselection.h b/kolourpaint/tools/kptoolellipticalselection.h new file mode 100644 index 00000000..9dbd643e --- /dev/null +++ b/kolourpaint/tools/kptoolellipticalselection.h @@ -0,0 +1,43 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 __kptoolellipticalselection_h__ +#define __kptoolellipticalselection_h__ + +#include <kptoolselection.h> + +class kpMainWindow; + +class kpToolEllipticalSelection : public kpToolSelection +{ +public: + kpToolEllipticalSelection (kpMainWindow *); + virtual ~kpToolEllipticalSelection (); +}; + +#endif // __kptoolellipticalselection_h__ diff --git a/kolourpaint/tools/kptooleraser.cpp b/kolourpaint/tools/kptooleraser.cpp new file mode 100644 index 00000000..1acbf66e --- /dev/null +++ b/kolourpaint/tools/kptooleraser.cpp @@ -0,0 +1,44 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <klocale.h> +#include <kptooleraser.h> + +kpToolEraser::kpToolEraser (kpMainWindow *mainWindow) + : kpToolPen (kpToolPen::Eraser, + i18n ("Eraser"), i18n ("Lets you rub out mistakes"), + Qt::Key_A, + mainWindow, "tool_eraser") +{ +} + +kpToolEraser::~kpToolEraser () +{ +} + +#include <kptooleraser.moc> diff --git a/kolourpaint/tools/kptooleraser.h b/kolourpaint/tools/kptooleraser.h new file mode 100644 index 00000000..4dd7704a --- /dev/null +++ b/kolourpaint/tools/kptooleraser.h @@ -0,0 +1,43 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 __kptooleraser_h__ +#define __kptooleraser_h__ + +#include <kptoolpen.h> + +class kpToolEraser : public kpToolPen +{ +Q_OBJECT + +public: + kpToolEraser (kpMainWindow *mainWindow); + virtual ~kpToolEraser (); +}; + +#endif // __kptooleraser_h__ diff --git a/kolourpaint/tools/kptoolflip.cpp b/kolourpaint/tools/kptoolflip.cpp new file mode 100644 index 00000000..58eeb66d --- /dev/null +++ b/kolourpaint/tools/kptoolflip.cpp @@ -0,0 +1,213 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <kptoolflip.h> + +#include <qapplication.h> +#include <qradiobutton.h> +#include <qvbox.h> +#include <qvbuttongroup.h> + +#include <kdebug.h> +#include <klocale.h> + +#include <kpdefs.h> +#include <kpdocument.h> +#include <kppixmapfx.h> +#include <kpselection.h> +#include <kptool.h> +#include <kpmainwindow.h> + + +/* + * kpToolFlipCommand + */ + +kpToolFlipCommand::kpToolFlipCommand (bool actOnSelection, + bool horiz, bool vert, + kpMainWindow *mainWindow) + : kpCommand (mainWindow), + m_actOnSelection (actOnSelection), + m_horiz (horiz), m_vert (vert) +{ +} + +kpToolFlipCommand::~kpToolFlipCommand () +{ +} + + +// public virtual [base kpCommand] +QString kpToolFlipCommand::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 + { + kdError () << "kpToolFlipCommand::name() not asked to flip" << endl; + return QString::null; + } +#endif + + + if (m_actOnSelection) + return i18n ("Selection: %1").arg (opName); + else + return opName; +} + + +// public virtual [base kpCommand] +int kpToolFlipCommand::size () const +{ + return 0; +} + + +// public virtual [base kpCommand] +void kpToolFlipCommand::execute () +{ + flip (); +} + +// public virtual [base kpCommand] +void kpToolFlipCommand::unexecute () +{ + flip (); +} + + +// private +void kpToolFlipCommand::flip () +{ + kpDocument *doc = document (); + if (!doc) + return; + + + QApplication::setOverrideCursor (Qt::waitCursor); + + + if (m_actOnSelection) + { + doc->selection ()->flip (m_horiz, m_vert); + if (m_mainWindow->tool ()) + m_mainWindow->tool ()->somethingBelowTheCursorChanged (); + } + else + { + QPixmap newPixmap = kpPixmapFX::flip (*doc->pixmap (), m_horiz, m_vert); + + doc->setPixmap (newPixmap); + } + + + QApplication::restoreOverrideCursor (); +} + + +/* + * kpToolFlipDialog + */ + +// private static +bool kpToolFlipDialog::s_lastIsVerticalFlip = true; + + +kpToolFlipDialog::kpToolFlipDialog (bool actOnSelection, QWidget *parent) + : KDialogBase (parent, 0/*name*/, true/*modal*/, + actOnSelection ? i18n ("Flip Selection") : i18n ("Flip Image"), + KDialogBase::Ok | KDialogBase::Cancel) +{ + QVBox *vbox = makeVBoxMainWidget (); + + if (!vbox) + { + kdError () << "kpToolFlipDialog::kpToolFlipDialog() received NULL vbox" << endl; + } + else + { + QVButtonGroup *buttonGroup = new QVButtonGroup (i18n ("Direction"), vbox); + + // I'm sure vert flipping is much more common than horiz flipping so make it come first + m_verticalFlipRadioButton = new QRadioButton (i18n ("&Vertical (upside-down)"), buttonGroup); + m_horizontalFlipRadioButton = new QRadioButton (i18n ("&Horizontal"), buttonGroup); + + m_verticalFlipRadioButton->setChecked (s_lastIsVerticalFlip); + m_horizontalFlipRadioButton->setChecked (!s_lastIsVerticalFlip); + + connect (m_verticalFlipRadioButton, SIGNAL (toggled (bool)), + this, SLOT (slotIsVerticalFlipChanged ())); + connect (m_horizontalFlipRadioButton, SIGNAL (toggled (bool)), + this, SLOT (slotIsVerticalFlipChanged ())); + } +} + +kpToolFlipDialog::~kpToolFlipDialog () +{ +} + + +// public slot +void kpToolFlipDialog::slotIsVerticalFlipChanged () +{ + s_lastIsVerticalFlip = m_verticalFlipRadioButton->isChecked (); +} + + +// public +bool kpToolFlipDialog::getHorizontalFlip () const +{ + return m_horizontalFlipRadioButton->isChecked (); +} + +// public +bool kpToolFlipDialog::getVerticalFlip () const +{ + return m_verticalFlipRadioButton->isChecked (); +} + +// public +bool kpToolFlipDialog::isNoOp () const +{ + return !getHorizontalFlip () && !getVerticalFlip (); +} + + +#include <kptoolflip.moc> + diff --git a/kolourpaint/tools/kptoolflip.h b/kolourpaint/tools/kptoolflip.h new file mode 100644 index 00000000..c287c320 --- /dev/null +++ b/kolourpaint/tools/kptoolflip.h @@ -0,0 +1,88 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 __kptoolflip_h__ +#define __kptoolflip_h__ + +#include <kpcommandhistory.h> +#include <kdialogbase.h> + +class QRadioButton; +class QString; + +class kpDocument; +class kpMainWindow; + + +class kpToolFlipCommand : public kpCommand +{ +public: + kpToolFlipCommand (bool actOnSelection, + bool horiz, bool vert, + kpMainWindow *mainWindow); + virtual ~kpToolFlipCommand (); + + virtual QString name () const; + + virtual int size () const; + + virtual void execute (); + virtual void unexecute (); + +private: + void flip (); + + bool m_actOnSelection; + bool m_horiz, m_vert; +}; + + +class kpToolFlipDialog : public KDialogBase +{ +Q_OBJECT + +public: + kpToolFlipDialog (bool actOnSelection, QWidget *parent); + ~kpToolFlipDialog (); + +private: + static bool s_lastIsVerticalFlip; + +public slots: + void slotIsVerticalFlipChanged (); + +public: + bool getHorizontalFlip () const; + bool getVerticalFlip () const; + bool isNoOp () const; + +private: + QRadioButton *m_horizontalFlipRadioButton, *m_verticalFlipRadioButton; +}; + +#endif // __kptoolflip_h__ diff --git a/kolourpaint/tools/kptoolfloodfill.cpp b/kolourpaint/tools/kptoolfloodfill.cpp new file mode 100644 index 00000000..bb17d701 --- /dev/null +++ b/kolourpaint/tools/kptoolfloodfill.cpp @@ -0,0 +1,261 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <kptoolfloodfill.h> + +#include <qapplication.h> +#include <qcursor.h> +#include <qpainter.h> +#include <qpixmap.h> + +#include <kdebug.h> +#include <klocale.h> + +#include <kpcommandhistory.h> +#include <kpdefs.h> +#include <kpdocument.h> +#include <kpmainwindow.h> +#include <kpview.h> +#include <kpviewmanager.h> + + +/* + * kpToolFloodFill + */ + +kpToolFloodFill::kpToolFloodFill (kpMainWindow *mainWindow) + : kpTool (i18n ("Flood Fill"), i18n ("Fills regions in the image"), + Qt::Key_F, + mainWindow, "tool_flood_fill"), + m_currentCommand (0) +{ +} + +kpToolFloodFill::~kpToolFloodFill () +{ +} + +QString kpToolFloodFill::haventBegunDrawUserMessage () const +{ + return i18n ("Click to fill a region."); +} + +void kpToolFloodFill::begin () +{ + setUserMessage (haventBegunDrawUserMessage ()); +} + +// virtual +void kpToolFloodFill::beginDraw () +{ +#if DEBUG_KP_TOOL_FLOOD_FILL && 1 + kdDebug () << "kpToolFloodFill::beginDraw()" << endl; +#endif + + QApplication::setOverrideCursor (Qt::waitCursor); + + // Flood Fill is an expensive CPU operation so we only fill at a + // mouse click (beginDraw ()), not on mouse move (virtually draw()) + m_currentCommand = new kpToolFloodFillCommand (m_currentPoint.x (), m_currentPoint.y (), + color (m_mouseButton), processedColorSimilarity (), + mainWindow ()); + + if (m_currentCommand->prepareColorToChange ()) + { + #if DEBUG_KP_TOOL_FLOOD_FILL && 1 + kdDebug () << "\tperforming new-doc-corner-case check" << endl; + #endif + if (document ()->url ().isEmpty () && !document ()->isModified ()) + { + m_currentCommand->setFillEntirePixmap (); + m_currentCommand->execute (); + } + else if (m_currentCommand->prepare ()) + { + m_currentCommand->execute (); + } + else + { + kdError () << "kpToolFloodFill::beginDraw() could not fill!" << endl; + } + } + else + { + kdError () << "kpToolFloodFill::beginDraw() could not prepareColorToChange!" << endl; + } + + QApplication::restoreOverrideCursor (); + + setUserMessage (cancelUserMessage ()); +} + +// virtual +void kpToolFloodFill::draw (const QPoint &thisPoint, const QPoint &, const QRect &) +{ + setUserShapePoints (thisPoint); +} + +// virtual +void kpToolFloodFill::cancelShape () +{ +#if 0 + endDraw (QPoint (), QRect ()); + mainWindow ()->commandHistory ()->undo (); +#else + m_currentCommand->unexecute (); + + delete m_currentCommand; + m_currentCommand = 0; +#endif + + setUserMessage (i18n ("Let go of all the mouse buttons.")); +} + +void kpToolFloodFill::releasedAllButtons () +{ + setUserMessage (haventBegunDrawUserMessage ()); +} + +// virtual +void kpToolFloodFill::endDraw (const QPoint &, const QRect &) +{ + mainWindow ()->commandHistory ()->addCommand (m_currentCommand, + false /* no exec - we already did it up there */); + + // don't delete + m_currentCommand = 0; + setUserMessage (haventBegunDrawUserMessage ()); +} + + +/* + * kpToolFloodFillCommand + */ + +kpToolFloodFillCommand::kpToolFloodFillCommand (int x, int y, + const kpColor &color, int processedColorSimilarity, + kpMainWindow *mainWindow) + : kpCommand (mainWindow), + kpFloodFill (document ()->pixmap (), x, y, color, processedColorSimilarity), + m_fillEntirePixmap (false) +{ +} + +kpToolFloodFillCommand::~kpToolFloodFillCommand () +{ +} + + +// public virtual [base kpCommand] +QString kpToolFloodFillCommand::name () const +{ + return i18n ("Flood Fill"); +} + +// public virtual [base kpCommand] +int kpToolFloodFillCommand::size () const +{ + return kpFloodFill::size () + kpPixmapFX::pixmapSize (m_oldPixmap); +} + + +void kpToolFloodFillCommand::setFillEntirePixmap (bool yes) +{ + m_fillEntirePixmap = yes; +} + + +// virtual +void kpToolFloodFillCommand::execute () +{ +#if DEBUG_KP_TOOL_FLOOD_FILL && 1 + kdDebug () << "kpToolFloodFillCommand::execute() m_fillEntirePixmap=" << m_fillEntirePixmap << endl; +#endif + + kpDocument *doc = document (); + if (!doc) + return; + + + if (m_fillEntirePixmap) + { + doc->fill (kpFloodFill::color ()); + } + else + { + QRect rect = kpFloodFill::boundingRect (); + if (rect.isValid ()) + { + QApplication::setOverrideCursor (QCursor::waitCursor); + + m_oldPixmap = doc->getPixmapAt (rect); + + kpFloodFill::fill (); + doc->slotContentsChanged (rect); + + QApplication::restoreOverrideCursor (); + } + else + { + #if DEBUG_KP_TOOL_FLOOD_FILL && 1 + kdDebug () << "\tinvalid boundingRect - must be NOP case" << endl; + #endif + } + } +} + +// virtual +void kpToolFloodFillCommand::unexecute () +{ + kpDocument *doc = document (); + if (!doc) + return; + + + if (m_fillEntirePixmap) + { + doc->fill (kpFloodFill::colorToChange ()); + } + else + { + QRect rect = kpFloodFill::boundingRect (); + if (rect.isValid ()) + { + doc->setPixmapAt (m_oldPixmap, rect.topLeft ()); + + m_oldPixmap.resize (0, 0); + + doc->slotContentsChanged (rect); + } + } +} + +#include <kptoolfloodfill.moc> diff --git a/kolourpaint/tools/kptoolfloodfill.h b/kolourpaint/tools/kptoolfloodfill.h new file mode 100644 index 00000000..a2eeaa5a --- /dev/null +++ b/kolourpaint/tools/kptoolfloodfill.h @@ -0,0 +1,94 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 __kptoolfloodfill_h__ +#define __kptoolfloodfill_h__ + +#include <qpixmap.h> + +#include <kpcommandhistory.h> + +#include <kpfloodfill.h> +#include <kptool.h> + + +class QString; + +class kpColor; + +class kpMainWindow; +class kpToolFloodFillCommand; + + +class kpToolFloodFill : public kpTool +{ +Q_OBJECT + +public: + kpToolFloodFill (kpMainWindow *); + virtual ~kpToolFloodFill (); + +private: + QString haventBegunDrawUserMessage () const; + +public: + virtual void begin (); + virtual void beginDraw (); + virtual void draw (const QPoint &thisPoint, const QPoint &, const QRect &); + virtual void cancelShape (); + virtual void releasedAllButtons (); + virtual void endDraw (const QPoint &, const QRect &); + +private: + kpToolFloodFillCommand *m_currentCommand; +}; + + +class kpToolFloodFillCommand : public kpCommand, public kpFloodFill +{ +public: + kpToolFloodFillCommand (int x, int y, + const kpColor &color, int processedColorSimilarity, + kpMainWindow *mainWindow); + virtual ~kpToolFloodFillCommand (); + + virtual QString name () const; + + virtual int size () const; + + void setFillEntirePixmap (bool yes = true); + + virtual void execute (); + virtual void unexecute (); + +private: + QPixmap m_oldPixmap; + bool m_fillEntirePixmap; +}; + +#endif // __kptoolfloodfill_h__ diff --git a/kolourpaint/tools/kptoolfreeformselection.cpp b/kolourpaint/tools/kptoolfreeformselection.cpp new file mode 100644 index 00000000..7c736728 --- /dev/null +++ b/kolourpaint/tools/kptoolfreeformselection.cpp @@ -0,0 +1,46 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <kptoolfreeformselection.h> + +#include <klocale.h> + + +kpToolFreeFormSelection::kpToolFreeFormSelection (kpMainWindow *mainWindow) + : kpToolSelection (kpToolSelection::FreeForm, + i18n ("Selection (Free-Form)"), + i18n ("Makes a free-form selection"), + Qt::Key_M, + mainWindow, "tool_free_form_selection") +{ +} + +kpToolFreeFormSelection::~kpToolFreeFormSelection () +{ +} + diff --git a/kolourpaint/tools/kptoolfreeformselection.h b/kolourpaint/tools/kptoolfreeformselection.h new file mode 100644 index 00000000..28f1e5ec --- /dev/null +++ b/kolourpaint/tools/kptoolfreeformselection.h @@ -0,0 +1,43 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 __kptoolfreeformselection_h__ +#define __kptoolfreeformselection_h__ + +#include <kptoolselection.h> + +class kpMainWindow; + +class kpToolFreeFormSelection : public kpToolSelection +{ +public: + kpToolFreeFormSelection (kpMainWindow *); + virtual ~kpToolFreeFormSelection (); +}; + +#endif // __kptoolfreeformselection_h__ diff --git a/kolourpaint/tools/kptoolline.cpp b/kolourpaint/tools/kptoolline.cpp new file mode 100644 index 00000000..809824d9 --- /dev/null +++ b/kolourpaint/tools/kptoolline.cpp @@ -0,0 +1,47 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <kptoolline.h> + +#include <klocale.h> + + +kpToolLine::kpToolLine (kpMainWindow *mainWindow) + : kpToolPolygon (Line, + i18n ("Line"), + i18n ("Draws lines"), + Qt::Key_L, + mainWindow, "tool_line") +{ +} + +kpToolLine::~kpToolLine () +{ +} + +#include <kptoolline.moc> diff --git a/kolourpaint/tools/kptoolline.h b/kolourpaint/tools/kptoolline.h new file mode 100644 index 00000000..7a956245 --- /dev/null +++ b/kolourpaint/tools/kptoolline.h @@ -0,0 +1,45 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 __kptoolline_h__ +#define __kptoolline_h__ + +#include <kptoolpolygon.h> + +class kpMainWindow; + +class kpToolLine : public kpToolPolygon +{ +Q_OBJECT + +public: + kpToolLine (kpMainWindow *); + virtual ~kpToolLine (); +}; + +#endif // __kptoolline_h__ diff --git a/kolourpaint/tools/kptoolpen.cpp b/kolourpaint/tools/kptoolpen.cpp new file mode 100644 index 00000000..eb731ceb --- /dev/null +++ b/kolourpaint/tools/kptoolpen.cpp @@ -0,0 +1,1145 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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_PEN 0 + +#include <qapplication.h> +#include <qbitmap.h> +#include <qcursor.h> +#include <qimage.h> +#include <qpainter.h> +#if DEBUG_KP_TOOL_PEN + #include <qdatetime.h> +#endif + +#include <kdebug.h> +#include <klocale.h> + +#include <kpcolor.h> +#include <kpcommandhistory.h> +#include <kpcursorprovider.h> +#include <kptoolpen.h> +#include <kpdefs.h> +#include <kpdocument.h> +#include <kpmainwindow.h> +#include <kppixmapfx.h> +#include <kptemppixmap.h> +#include <kptoolclear.h> +#include <kptooltoolbar.h> +#include <kptoolwidgetbrush.h> +#include <kptoolwidgeterasersize.h> +#include <kpviewmanager.h> + +/* + * kpToolPen + */ + +kpToolPen::kpToolPen (Mode mode, + const QString &text, const QString &description, + int key, + kpMainWindow *mainWindow, const char *name) + : kpTool (text, description, key, mainWindow, name), + m_mode (mode), + m_toolWidgetBrush (0), + m_toolWidgetEraserSize (0), + m_currentCommand (0) +{ +} + +kpToolPen::kpToolPen (kpMainWindow *mainWindow) + : kpTool (i18n ("Pen"), i18n ("Draws dots and freehand strokes"), + Qt::Key_P, + mainWindow, "tool_pen"), + m_mode (Pen), + m_toolWidgetBrush (0), + m_toolWidgetEraserSize (0), + m_currentCommand (0) +{ +} + +void kpToolPen::setMode (Mode mode) +{ + int usesPixmaps = (mode & (DrawsPixmaps | WashesPixmaps)); + int usesBrushes = (mode & (SquareBrushes | DiverseBrushes)); + + if ((usesPixmaps && !usesBrushes) || + (usesBrushes && !usesPixmaps)) + { + kdError () << "kpToolPen::setMode() passed invalid mode" << endl; + return; + } + + m_mode = mode; +} + +kpToolPen::~kpToolPen () +{ +} + + +// private +QString kpToolPen::haventBegunDrawUserMessage () const +{ + switch (m_mode) + { + case Pen: + case Brush: + return i18n ("Click to draw dots or drag to draw strokes."); + return i18n ("Click to draw dots or drag to draw strokes."); + case Eraser: + return i18n ("Click or drag to erase."); + case ColorWasher: + return i18n ("Click or drag to erase pixels of the foreground color."); + default: + return QString::null; + } +} + +// virtual +void kpToolPen::begin () +{ + m_toolWidgetBrush = 0; + m_brushIsDiagonalLine = false; + + kpToolToolBar *tb = toolToolBar (); + if (!tb) + return; + + if (m_mode & SquareBrushes) + { + m_toolWidgetEraserSize = tb->toolWidgetEraserSize (); + connect (m_toolWidgetEraserSize, SIGNAL (eraserSizeChanged (int)), + this, SLOT (slotEraserSizeChanged (int))); + m_toolWidgetEraserSize->show (); + + slotEraserSizeChanged (m_toolWidgetEraserSize->eraserSize ()); + + viewManager ()->setCursor (kpCursorProvider::lightCross ()); + } + + if (m_mode & DiverseBrushes) + { + m_toolWidgetBrush = tb->toolWidgetBrush (); + connect (m_toolWidgetBrush, SIGNAL (brushChanged (const QPixmap &, bool)), + this, SLOT (slotBrushChanged (const QPixmap &, bool))); + m_toolWidgetBrush->show (); + + slotBrushChanged (m_toolWidgetBrush->brush (), + m_toolWidgetBrush->brushIsDiagonalLine ()); + + viewManager ()->setCursor (kpCursorProvider::lightCross ()); + } + + setUserMessage (haventBegunDrawUserMessage ()); +} + +// virtual +void kpToolPen::end () +{ + if (m_toolWidgetEraserSize) + { + disconnect (m_toolWidgetEraserSize, SIGNAL (eraserSizeChanged (int)), + this, SLOT (slotEraserSizeChanged (int))); + m_toolWidgetEraserSize = 0; + } + + if (m_toolWidgetBrush) + { + disconnect (m_toolWidgetBrush, SIGNAL (brushChanged (const QPixmap &, bool)), + this, SLOT (slotBrushChanged (const QPixmap &, bool))); + m_toolWidgetBrush = 0; + } + + kpViewManager *vm = viewManager (); + if (vm) + { + if (vm->tempPixmap () && vm->tempPixmap ()->isBrush ()) + vm->invalidateTempPixmap (); + + if (m_mode & (SquareBrushes | DiverseBrushes)) + vm->unsetCursor (); + } + + // save memory + for (int i = 0; i < 2; i++) + m_brushPixmap [i].resize (0, 0); + m_cursorPixmap.resize (0, 0); +} + +// virtual +void kpToolPen::beginDraw () +{ + switch (m_mode) + { + case Pen: + m_currentCommand = new kpToolPenCommand (i18n ("Pen"), mainWindow ()); + break; + case Brush: + m_currentCommand = new kpToolPenCommand (i18n ("Brush"), mainWindow ()); + break; + case Eraser: + m_currentCommand = new kpToolPenCommand (i18n ("Eraser"), mainWindow ()); + break; + case ColorWasher: + m_currentCommand = new kpToolPenCommand (i18n ("Color Eraser"), mainWindow ()); + break; + + default: + m_currentCommand = new kpToolPenCommand (i18n ("Custom Pen or Brush"), mainWindow ()); + break; + } + + // we normally show the Brush pix in the foreground colour but if the + // user starts drawing in the background color, we don't want to leave + // the cursor in the foreground colour -- just hide it in all cases + // to avoid confusion + viewManager ()->invalidateTempPixmap (); + + setUserMessage (cancelUserMessage ()); +} + +// virtual +void kpToolPen::hover (const QPoint &point) +{ +#if DEBUG_KP_TOOL_PEN && 0 + kdDebug () << "kpToolPen::hover(" << point << ")" + << " hasBegun=" << hasBegun () + << " hasBegunDraw=" << hasBegunDraw () + << " cursorPixmap.isNull=" << m_cursorPixmap.isNull () + << endl; +#endif + if (point != KP_INVALID_POINT && !m_cursorPixmap.isNull ()) + { + // (for hotPoint() as m_mouseButton is not normally defined in hover()) + m_mouseButton = 0; + + kpTempPixmap::RenderMode renderMode; + QPixmap cursorPixmapForTempPixmap = m_cursorPixmap; + + if (m_mode & SquareBrushes) + renderMode = kpTempPixmap::SetPixmap; + else if (m_mode & DiverseBrushes) + { + if (color (0).isOpaque ()) + renderMode = kpTempPixmap::PaintPixmap; + else + { + renderMode = kpTempPixmap::PaintMaskTransparentWithBrush; + cursorPixmapForTempPixmap = kpPixmapFX::getNonNullMask (m_cursorPixmap); + } + } + + viewManager ()->setFastUpdates (); + + viewManager ()->setTempPixmap ( + kpTempPixmap (true/*brush*/, + renderMode, + hotPoint (), + cursorPixmapForTempPixmap)); + + viewManager ()->restoreFastUpdates (); + } + +#if DEBUG_KP_TOOL_PEN && 0 + if (document ()->rect ().contains (point)) + { + QImage image = kpPixmapFX::convertToImage (*document ()->pixmap ()); + + QRgb v = image.pixel (point.x (), point.y ()); + kdDebug () << "(" << point << "): r=" << qRed (v) + << " g=" << qGreen (v) + << " b=" << qBlue (v) + << " a=" << qAlpha (v) + << endl; + } +#endif + + setUserShapePoints (point); +} + +bool kpToolPen::wash (QPainter *painter, QPainter *maskPainter, + const QImage &image, + const kpColor &colorToReplace, + const QRect &imageRect, int plotx, int ploty) +{ + return wash (painter, maskPainter, image, colorToReplace, imageRect, hotRect (plotx, ploty)); +} + +bool kpToolPen::wash (QPainter *painter, QPainter *maskPainter, + const QImage &image, + const kpColor &colorToReplace, + const QRect &imageRect, const QRect &drawRect) +{ + bool didSomething = false; + +#if DEBUG_KP_TOOL_PEN && 0 + kdDebug () << "kpToolPen::wash(imageRect=" << imageRect + << ",drawRect=" << drawRect + << ")" << endl; +#endif + +// make use of scanline coherence +#define FLUSH_LINE() \ +{ \ + if (painter && painter->isActive ()) \ + painter->drawLine (startDrawX, y, x - 1, y); \ + if (maskPainter && maskPainter->isActive ()) \ + maskPainter->drawLine (startDrawX, y, x - 1, 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_TOOL_PEN && 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_TOOL_PEN && 0 + fprintf (stderr, "similar\n"); + #endif + if (startDrawX < 0) + startDrawX = x; + } + else + { + #if DEBUG_KP_TOOL_PEN && 0 + fprintf (stderr, "different\n"); + #endif + if (startDrawX >= 0) + FLUSH_LINE (); + } + } + + if (startDrawX >= 0) + FLUSH_LINE (); + } + +#undef FLUSH_LINE + + return didSomething; +} + +// virtual +void kpToolPen::globalDraw () +{ + // it's easiest to reimplement globalDraw() here rather than in + // all the relevant subclasses + + if (m_mode == Eraser) + { + #if DEBUG_KP_TOOL_PEN + kdDebug () << "kpToolPen::globalDraw() eraser" << endl; + #endif + mainWindow ()->commandHistory ()->addCommand ( + new kpToolClearCommand (false/*act on doc, not sel*/, mainWindow ())); + } + else if (m_mode == ColorWasher) + { + #if DEBUG_KP_TOOL_PEN + kdDebug () << "kpToolPen::globalDraw() colour eraser" << endl; + #endif + if (foregroundColor () == backgroundColor () && processedColorSimilarity () == 0) + return; + + QApplication::setOverrideCursor (Qt::waitCursor); + + kpToolPenCommand *cmd = new kpToolPenCommand ( + i18n ("Color Eraser"), mainWindow ()); + + QPainter painter, maskPainter; + QBitmap maskBitmap; + + if (backgroundColor ().isOpaque ()) + { + painter.begin (document ()->pixmap ()); + painter.setPen (backgroundColor ().toQColor ()); + } + + if (backgroundColor ().isTransparent () || + document ()->pixmap ()->mask ()) + { + maskBitmap = kpPixmapFX::getNonNullMask (*document ()->pixmap ()); + maskPainter.begin (&maskBitmap); + + maskPainter.setPen (backgroundColor ().maskColor ()); + } + + const QImage image = kpPixmapFX::convertToImage (*document ()->pixmap ()); + QRect rect = document ()->rect (); + + const bool didSomething = wash (&painter, &maskPainter, image, + foregroundColor ()/*replace foreground*/, + rect, rect); + + // flush + if (painter.isActive ()) + painter.end (); + + if (maskPainter.isActive ()) + maskPainter.end (); + + if (didSomething) + { + if (!maskBitmap.isNull ()) + document ()->pixmap ()->setMask (maskBitmap); + + + document ()->slotContentsChanged (rect); + + + cmd->updateBoundingRect (rect); + cmd->finalize (); + + mainWindow ()->commandHistory ()->addCommand (cmd, false /* don't exec */); + + // don't delete - it's up to the commandHistory + cmd = 0; + } + else + { + #if DEBUG_KP_TOOL_PEN + kdDebug () << "\tisNOP" << endl; + #endif + delete cmd; + cmd = 0; + } + + QApplication::restoreOverrideCursor (); + } +} + +// virtual +// TODO: refactor! +void kpToolPen::draw (const QPoint &thisPoint, const QPoint &lastPoint, const QRect &) +{ + if ((m_mode & WashesPixmaps) && (foregroundColor () == backgroundColor ()) && processedColorSimilarity () == 0) + return; + + // sync: remember to restoreFastUpdates() in all exit paths + viewManager ()->setFastUpdates (); + + if (m_brushIsDiagonalLine ? currentPointCardinallyNextToLast () : currentPointNextToLast ()) + { + if (m_mode & DrawsPixels) + { + QPixmap pixmap (1, 1); + + const kpColor c = color (m_mouseButton); + + // OPT: this seems hopelessly inefficient + if (c.isOpaque ()) + { + pixmap.fill (c.toQColor ()); + } + else + { + QBitmap mask (1, 1); + mask.fill (Qt::color0/*transparent*/); + + pixmap.setMask (mask); + } + + // draw onto doc + document ()->setPixmapAt (pixmap, thisPoint); + + m_currentCommand->updateBoundingRect (thisPoint); + } + // Brush & Eraser + else if (m_mode & DrawsPixmaps) + { + if (color (m_mouseButton).isOpaque ()) + document ()->paintPixmapAt (m_brushPixmap [m_mouseButton], hotPoint ()); + else + { + kpPixmapFX::paintMaskTransparentWithBrush (document ()->pixmap (), + hotPoint (), + kpPixmapFX::getNonNullMask (m_brushPixmap [m_mouseButton])); + document ()->slotContentsChanged (hotRect ()); + } + + m_currentCommand->updateBoundingRect (hotRect ()); + } + else if (m_mode & WashesPixmaps) + { + #if DEBUG_KP_TOOL_PEN + kdDebug () << "Washing pixmap (immediate)" << endl; + QTime timer; + #endif + QRect rect = hotRect (); + #if DEBUG_KP_TOOL_PEN + timer.start (); + #endif + QPixmap pixmap = document ()->getPixmapAt (rect); + #if DEBUG_KP_TOOL_PEN + kdDebug () << "\tget from doc: " << timer.restart () << "ms" << endl; + #endif + const QImage image = kpPixmapFX::convertToImage (pixmap); + #if DEBUG_KP_TOOL_PEN + kdDebug () << "\tconvert to image: " << timer.restart () << "ms" << endl; + #endif + QPainter painter, maskPainter; + QBitmap maskBitmap; + + if (color (m_mouseButton).isOpaque ()) + { + painter.begin (&pixmap); + painter.setPen (color (m_mouseButton).toQColor ()); + } + + if (color (m_mouseButton).isTransparent () || + pixmap.mask ()) + { + maskBitmap = kpPixmapFX::getNonNullMask (pixmap); + maskPainter.begin (&maskBitmap); + maskPainter.setPen (color (m_mouseButton).maskColor ()); + } + + bool didSomething = wash (&painter, &maskPainter, + image, + color (1 - m_mouseButton)/*color to replace*/, + rect, rect); + + if (painter.isActive ()) + painter.end (); + + if (maskPainter.isActive ()) + maskPainter.end (); + + if (didSomething) + { + if (!maskBitmap.isNull ()) + pixmap.setMask (maskBitmap); + + #if DEBUG_KP_TOOL_PEN + kdDebug () << "\twashed: " << timer.restart () << "ms" << endl; + #endif + document ()->setPixmapAt (pixmap, hotPoint ()); + #if DEBUG_KP_TOOL_PEN + kdDebug () << "\tset doc: " << timer.restart () << "ms" << endl; + #endif + m_currentCommand->updateBoundingRect (hotRect ()); + #if DEBUG_KP_TOOL_PEN + kdDebug () << "\tupdate boundingRect: " << timer.restart () << "ms" << endl; + kdDebug () << "\tdone" << endl; + #endif + } + + #if DEBUG_KP_TOOL_PEN && 1 + kdDebug () << endl; + #endif + } + } + // in reality, the system is too slow to give us all the MouseMove events + // so we "interpolate" the missing points :) + else + { + // find bounding rectangle + QRect rect = QRect (thisPoint, lastPoint).normalize (); + if (m_mode != DrawsPixels) + rect = neededRect (rect, m_brushPixmap [m_mouseButton].width ()); + + #if DEBUG_KP_TOOL_PEN + if (m_mode & WashesPixmaps) + { + kdDebug () << "Washing pixmap (w=" << rect.width () + << ",h=" << rect.height () << ")" << endl; + } + QTime timer; + int convAndWashTime; + #endif + + const kpColor c = color (m_mouseButton); + bool transparent = c.isTransparent (); + + QPixmap pixmap = document ()->getPixmapAt (rect); + QBitmap maskBitmap; + + QPainter painter, maskPainter; + + if (m_mode & (DrawsPixels | WashesPixmaps)) + { + if (!transparent) + { + painter.begin (&pixmap); + painter.setPen (c.toQColor ()); + } + + if (transparent || pixmap.mask ()) + { + maskBitmap = kpPixmapFX::getNonNullMask (pixmap); + maskPainter.begin (&maskBitmap); + maskPainter.setPen (c.maskColor ()); + } + } + + QImage image; + if (m_mode & WashesPixmaps) + { + #if DEBUG_KP_TOOL_PEN + timer.start (); + #endif + image = kpPixmapFX::convertToImage (pixmap); + #if DEBUG_KP_TOOL_PEN + convAndWashTime = timer.restart (); + kdDebug () << "\tconvert to image: " << convAndWashTime << " ms" << endl; + #endif + } + + bool didSomething = false; + + if (m_mode & DrawsPixels) + { + QPoint sp = lastPoint - rect.topLeft (), ep = thisPoint - rect.topLeft (); + if (painter.isActive ()) + painter.drawLine (sp, ep); + + if (maskPainter.isActive ()) + maskPainter.drawLine (sp, ep); + + didSomething = true; + } + // Brush & Eraser + else if (m_mode & (DrawsPixmaps | WashesPixmaps)) + { + kpColor colorToReplace; + + if (m_mode & WashesPixmaps) + colorToReplace = color (1 - m_mouseButton); + + // Sweeps a pixmap along a line (modified Bresenham's line algorithm, + // see MODIFIED comment below). + // + // Derived from the zSprite2 Graphics Engine + + const int x1 = (thisPoint - rect.topLeft ()).x (), + y1 = (thisPoint - rect.topLeft ()).y (), + x2 = (lastPoint - rect.topLeft ()).x (), + y2 = (lastPoint - rect.topLeft ()).y (); + + // Difference of x and y values + int dx = x2 - x1; + int dy = y2 - y1; + + // Absolute values of differences + int ix = kAbs (dx); + int iy = kAbs (dy); + + // Larger of the x and y differences + int inc = ix > iy ? ix : iy; + + // Plot location + int plotx = x1; + int ploty = y1; + + int x = 0; + int y = 0; + + if (m_mode & WashesPixmaps) + { + if (wash (&painter, &maskPainter, image, + colorToReplace, + rect, plotx + rect.left (), ploty + rect.top ())) + { + didSomething = true; + } + } + else + { + if (!transparent) + { + kpPixmapFX::paintPixmapAt (&pixmap, + hotPoint (plotx, ploty), + m_brushPixmap [m_mouseButton]); + } + else + { + kpPixmapFX::paintMaskTransparentWithBrush (&pixmap, + hotPoint (plotx, ploty), + kpPixmapFX::getNonNullMask (m_brushPixmap [m_mouseButton])); + } + + didSomething = true; + } + + for (int i = 0; i <= inc; i++) + { + // oldplotx is equally as valid but would look different + // (but nobody will notice which one it is) + 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 (m_brushIsDiagonalLine && 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 (m_mode & WashesPixmaps) + { + if (wash (&painter, &maskPainter, image, + colorToReplace, + rect, plotx + rect.left (), oldploty + rect.top ())) + { + didSomething = true; + } + } + else + { + if (!transparent) + { + kpPixmapFX::paintPixmapAt (&pixmap, + hotPoint (plotx, oldploty), + m_brushPixmap [m_mouseButton]); + } + else + { + kpPixmapFX::paintMaskTransparentWithBrush (&pixmap, + hotPoint (plotx, oldploty), + kpPixmapFX::getNonNullMask (m_brushPixmap [m_mouseButton])); + } + + didSomething = true; + } + } + + if (m_mode & WashesPixmaps) + { + if (wash (&painter, &maskPainter, image, + colorToReplace, + rect, plotx + rect.left (), ploty + rect.top ())) + { + didSomething = true; + } + } + else + { + if (!transparent) + { + kpPixmapFX::paintPixmapAt (&pixmap, + hotPoint (plotx, ploty), + m_brushPixmap [m_mouseButton]); + } + else + { + kpPixmapFX::paintMaskTransparentWithBrush (&pixmap, + hotPoint (plotx, ploty), + kpPixmapFX::getNonNullMask (m_brushPixmap [m_mouseButton])); + } + + didSomething = true; + } + } + } + + } + + if (painter.isActive ()) + painter.end (); + + if (maskPainter.isActive ()) + maskPainter.end (); + + #if DEBUG_KP_TOOL_PEN + if (m_mode & WashesPixmaps) + { + int ms = timer.restart (); + kdDebug () << "\ttried to wash: " << ms << "ms" + << " (" << (ms ? (rect.width () * rect.height () / ms) : -1234) + << " pixels/ms)" + << endl; + convAndWashTime += ms; + } + #endif + + + if (didSomething) + { + if (!maskBitmap.isNull ()) + pixmap.setMask (maskBitmap); + + // draw onto doc + document ()->setPixmapAt (pixmap, rect.topLeft ()); + + #if DEBUG_KP_TOOL_PEN + if (m_mode & WashesPixmaps) + { + int ms = timer.restart (); + kdDebug () << "\tset doc: " << ms << "ms" << endl; + convAndWashTime += ms; + } + #endif + + m_currentCommand->updateBoundingRect (rect); + + #if DEBUG_KP_TOOL_PEN + if (m_mode & WashesPixmaps) + { + int ms = timer.restart (); + kdDebug () << "\tupdate boundingRect: " << ms << "ms" << endl; + convAndWashTime += ms; + kdDebug () << "\tdone (" << (convAndWashTime ? (rect.width () * rect.height () / convAndWashTime) : -1234) + << " pixels/ms)" + << endl; + } + #endif + } + + #if DEBUG_KP_TOOL_PEN + if (m_mode & WashesPixmaps) + kdDebug () << endl; + #endif + } + + viewManager ()->restoreFastUpdates (); + setUserShapePoints (thisPoint); +} + +// virtual +void kpToolPen::cancelShape () +{ + m_currentCommand->finalize (); + m_currentCommand->cancel (); + + delete m_currentCommand; + m_currentCommand = 0; + + updateBrushCursor (false/*no recalc*/); + + setUserMessage (i18n ("Let go of all the mouse buttons.")); +} + +void kpToolPen::releasedAllButtons () +{ + setUserMessage (haventBegunDrawUserMessage ()); +} + +// virtual +void kpToolPen::endDraw (const QPoint &, const QRect &) +{ + m_currentCommand->finalize (); + mainWindow ()->commandHistory ()->addCommand (m_currentCommand, false /* don't exec */); + + // don't delete - it's up to the commandHistory + m_currentCommand = 0; + + updateBrushCursor (false/*no recalc*/); + + setUserMessage (haventBegunDrawUserMessage ()); +} + + +// TODO: maybe the base should be virtual? +kpColor kpToolPen::color (int which) +{ +#if DEBUG_KP_TOOL_PEN && 0 + kdDebug () << "kpToolPen::color (" << which << ")" << endl; +#endif + + // Pen & Brush + if ((m_mode & SwappedColors) == 0) + return kpTool::color (which); + // only the (Color) Eraser uses the opposite color + else + return kpTool::color (which ? 0 : 1); // don't trust !0 == 1 +} + +// virtual private slot +void kpToolPen::slotForegroundColorChanged (const kpColor &col) +{ +#if DEBUG_KP_TOOL_PEN + kdDebug () << "kpToolPen::slotForegroundColorChanged()" << endl; +#endif + if (col.isOpaque ()) + m_brushPixmap [(m_mode & SwappedColors) ? 1 : 0].fill (col.toQColor ()); + + updateBrushCursor (); +} + +// virtual private slot +void kpToolPen::slotBackgroundColorChanged (const kpColor &col) +{ +#if DEBUG_KP_TOOL_PEN + kdDebug () << "kpToolPen::slotBackgroundColorChanged()" << endl; +#endif + + if (col.isOpaque ()) + m_brushPixmap [(m_mode & SwappedColors) ? 0 : 1].fill (col.toQColor ()); + + updateBrushCursor (); +} + +// private slot +void kpToolPen::slotBrushChanged (const QPixmap &pixmap, bool isDiagonalLine) +{ +#if DEBUG_KP_TOOL_PEN + kdDebug () << "kpToolPen::slotBrushChanged()" << endl; +#endif + for (int i = 0; i < 2; i++) + { + m_brushPixmap [i] = pixmap; + if (color (i).isOpaque ()) + m_brushPixmap [i].fill (color (i).toQColor ()); + } + + m_brushIsDiagonalLine = isDiagonalLine; + + updateBrushCursor (); +} + +// private slot +void kpToolPen::slotEraserSizeChanged (int size) +{ +#if DEBUG_KP_TOOL_PEN + kdDebug () << "KpToolPen::slotEraserSizeChanged(size=" << size << ")" << endl; +#endif + + for (int i = 0; i < 2; i++) + { + // Note: No matter what, the eraser's brush pixmap is never given + // a mask. + // + // With a transparent color, since we don't fill anything, the + // resize by itself will leave us with garbage pixels. This + // doesn't matter because: + // + // 1. The hover cursor will ask kpToolWidgetEraserSize for a proper + // cursor pixmap. + // 2. We will draw using kpPixmapFX::paintMaskTransparentWithBrush() + // which only cares about the opaqueness. + m_brushPixmap [i].resize (size, size); + if (color (i).isOpaque ()) + m_brushPixmap [i].fill (color (i).toQColor ()); + } + + updateBrushCursor (); +} + +QPoint kpToolPen::hotPoint () const +{ + return hotPoint (m_currentPoint); +} + +QPoint kpToolPen::hotPoint (int x, int y) const +{ + return hotPoint (QPoint (x, y)); +} + +QPoint kpToolPen::hotPoint (const QPoint &point) const +{ + /* + * e.g. + * Width 5: + * 0 1 2 3 4 + * ^ + * | + * Center + */ + return point - + QPoint (m_brushPixmap [m_mouseButton].width () / 2, + m_brushPixmap [m_mouseButton].height () / 2); +} + +QRect kpToolPen::hotRect () const +{ + return hotRect (m_currentPoint); +} + +QRect kpToolPen::hotRect (int x, int y) const +{ + return hotRect (QPoint (x, y)); +} + +QRect kpToolPen::hotRect (const QPoint &point) const +{ + QPoint topLeft = hotPoint (point); + return QRect (topLeft.x (), + topLeft.y (), + m_brushPixmap [m_mouseButton].width (), + m_brushPixmap [m_mouseButton].height ()); +} + +// private +void kpToolPen::updateBrushCursor (bool recalc) +{ +#if DEBUG_KP_TOOL_PEN && 1 + kdDebug () << "kpToolPen::updateBrushCursor(recalc=" << recalc << ")" << endl; +#endif + + if (recalc) + { + if (m_mode & SquareBrushes) + m_cursorPixmap = m_toolWidgetEraserSize->cursorPixmap (color (0)); + else if (m_mode & DiverseBrushes) + m_cursorPixmap = m_brushPixmap [0]; + } + + hover (hasBegun () ? m_currentPoint : currentPoint ()); +} + + +/* + * kpToolPenCommand + */ + +kpToolPenCommand::kpToolPenCommand (const QString &name, kpMainWindow *mainWindow) + : kpNamedCommand (name, mainWindow), + m_pixmap (*document ()->pixmap ()) +{ +} + +kpToolPenCommand::~kpToolPenCommand () +{ +} + + +// public virtual [base kpCommand] +int kpToolPenCommand::size () const +{ + return kpPixmapFX::pixmapSize (m_pixmap); +} + + +// public virtual [base kpCommand] +void kpToolPenCommand::execute () +{ + swapOldAndNew (); +} + +// public virtual [base kpCommand] +void kpToolPenCommand::unexecute () +{ + swapOldAndNew (); +} + + +// private +void kpToolPenCommand::swapOldAndNew () +{ + if (m_boundingRect.isValid ()) + { + QPixmap oldPixmap = document ()->getPixmapAt (m_boundingRect); + + document ()->setPixmapAt (m_pixmap, m_boundingRect.topLeft ()); + + m_pixmap = oldPixmap; + } +} + +// public +void kpToolPenCommand::updateBoundingRect (const QPoint &point) +{ + updateBoundingRect (QRect (point, point)); +} + +// public +void kpToolPenCommand::updateBoundingRect (const QRect &rect) +{ +#if DEBUG_KP_TOOL_PEN & 0 + kdDebug () << "kpToolPenCommand::updateBoundingRect() existing=" + << m_boundingRect + << " plus=" + << rect + << endl; +#endif + m_boundingRect = m_boundingRect.unite (rect); +#if DEBUG_KP_TOOL_PEN & 0 + kdDebug () << "\tresult=" << m_boundingRect << endl; +#endif +} + +// public +void kpToolPenCommand::finalize () +{ + if (m_boundingRect.isValid ()) + { + // store only needed part of doc pixmap + m_pixmap = kpTool::neededPixmap (m_pixmap, m_boundingRect); + } + else + { + m_pixmap.resize (0, 0); + } +} + +// public +void kpToolPenCommand::cancel () +{ + if (m_boundingRect.isValid ()) + { + viewManager ()->setFastUpdates (); + document ()->setPixmapAt (m_pixmap, m_boundingRect.topLeft ()); + viewManager ()->restoreFastUpdates (); + } +} + +#include <kptoolpen.moc> diff --git a/kolourpaint/tools/kptoolpen.h b/kolourpaint/tools/kptoolpen.h new file mode 100644 index 00000000..f57eb367 --- /dev/null +++ b/kolourpaint/tools/kptoolpen.h @@ -0,0 +1,160 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 __kptoolpen_h__ +#define __kptoolpen_h__ + +#include <qpixmap.h> +#include <qrect.h> + +#include <kpcommandhistory.h> +#include <kptool.h> + +class QPoint; +class QString; + +class kpColor; +class kpMainWindow; +class kpToolPenCommand; +class kpToolWidgetBrush; +class kpToolWidgetEraserSize; +class kpViewManager; + +class kpToolPen : public kpTool +{ +Q_OBJECT + +public: + enum Mode + { + // tool properties + DrawsPixels = (1 << 0), DrawsPixmaps = (1 << 1), WashesPixmaps = (1 << 2), + NoBrushes = 0, SquareBrushes = (1 << 3), DiverseBrushes = (1 << 4), + NormalColors = 0, SwappedColors = (1 << 5), + + // tools: + // + // Pen = draws pixels, "interpolates" by "sweeping" pixels along a line (no brushes) + // Brush = draws pixmaps, "interpolates" by "sweeping" pixmaps along a line (interesting brushes) + // Eraser = Brush but with foreground & background colors swapped (a few square brushes) + // Color Washer = Brush that replaces/washes the foreground color with the background color + // + // (note the capitalization of "brush" here :)) + Pen = DrawsPixels | NoBrushes | NormalColors, + Brush = DrawsPixmaps | DiverseBrushes | NormalColors, + Eraser = DrawsPixmaps | SquareBrushes | SwappedColors, + ColorWasher = WashesPixmaps | SquareBrushes | SwappedColors + }; + + kpToolPen (Mode mode, const QString &text, const QString &description, + int key, + kpMainWindow *mainWindow, const char *name); + kpToolPen (kpMainWindow *mainWindow); + virtual ~kpToolPen (); + + void setMode (Mode mode); + +private: + QString haventBegunDrawUserMessage () const; + +public: + virtual void begin (); + virtual void end (); + + virtual void beginDraw (); + virtual void hover (const QPoint &point); + virtual void globalDraw (); + virtual void draw (const QPoint &thisPoint, const QPoint &lastPoint, const QRect &); + virtual void cancelShape (); + virtual void releasedAllButtons (); + virtual void endDraw (const QPoint &, const QRect &); + +private slots: + virtual void slotForegroundColorChanged (const kpColor &col); + virtual void slotBackgroundColorChanged (const kpColor &col); + + void slotBrushChanged (const QPixmap &pixmap, bool isDiagonalLine); + void slotEraserSizeChanged (int size); + +private: + bool wash (QPainter *painter, QPainter *maskPainter, + const QImage &image, + const kpColor &colorToReplace, + const QRect &imageRect, int plotx, int ploty); + bool wash (QPainter *painter, QPainter *maskPainter, + const QImage &image, + const kpColor &colorToReplace, + const QRect &imageRect, const QRect &drawRect); + + kpColor color (int which); + + QPoint hotPoint () const; + QPoint hotPoint (int x, int y) const; + QPoint hotPoint (const QPoint &point) const; + QRect hotRect () const; + QRect hotRect (int x, int y) const; + QRect hotRect (const QPoint &point) const; + + Mode m_mode; + + void updateBrushCursor (bool recalc = true); + + kpToolWidgetBrush *m_toolWidgetBrush; + kpToolWidgetEraserSize *m_toolWidgetEraserSize; + QPixmap m_brushPixmap [2]; + QPixmap m_cursorPixmap; + bool m_brushIsDiagonalLine; + + kpToolPenCommand *m_currentCommand; +}; + +class kpToolPenCommand : public kpNamedCommand +{ +public: + kpToolPenCommand (const QString &name, kpMainWindow *mainWindow); + virtual ~kpToolPenCommand (); + + virtual int size () const; + + virtual void execute (); + virtual void unexecute (); + + // interface for KToolPen + void updateBoundingRect (const QPoint &point); + void updateBoundingRect (const QRect &rect); + void finalize (); + void cancel (); + +private: + void swapOldAndNew (); + + QPixmap m_pixmap; + QRect m_boundingRect; +}; + +#endif // __kptoolpen_h__ diff --git a/kolourpaint/tools/kptoolpolygon.cpp b/kolourpaint/tools/kptoolpolygon.cpp new file mode 100644 index 00000000..fb68745c --- /dev/null +++ b/kolourpaint/tools/kptoolpolygon.cpp @@ -0,0 +1,895 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <kptoolpolygon.h> + +#include <float.h> +#include <math.h> + +#include <qbitmap.h> +#include <qcursor.h> +#include <qlayout.h> +#include <qpainter.h> +#include <qpoint.h> +#include <qpushbutton.h> +#include <qrect.h> +#include <qtooltip.h> +#include <qvbuttongroup.h> + +#include <kdebug.h> +#include <klocale.h> + +#include <kpcommandhistory.h> +#include <kpdocument.h> +#include <kpdefs.h> +#include <kpmainwindow.h> +#include <kppixmapfx.h> +#include <kptemppixmap.h> +#include <kptooltoolbar.h> +#include <kptoolwidgetlinewidth.h> +#include <kpviewmanager.h> + + +#if DEBUG_KP_TOOL_POLYGON +static const char *pointArrayToString (const QPointArray &pointArray) +{ + static char string [1000]; + string [0] = '\0'; + + for (QPointArray::ConstIterator it = pointArray.begin (); + it != pointArray.end (); + it++) + { + QString ps = QString (" (%1, %2)").arg ((*it).x ()).arg ((*it).y ()); + const char *pss = ps.latin1 (); + if (strlen (string) + strlen (pss) + 1 > sizeof (string) / sizeof (string [0])) + break; + strcat (string, pss); + } + + return string; +} +#endif + + +static QPen makeMaskPen (const kpColor &color, int lineWidth, Qt::PenStyle lineStyle) +{ + return QPen (color.maskColor (), + lineWidth == 1 ? 0/*closer to looking width 1*/ : lineWidth, lineStyle, + Qt::RoundCap, Qt::RoundJoin); +} + +static QPen makePen (const kpColor &color, int lineWidth, Qt::PenStyle lineStyle) +{ + if (color.isOpaque ()) + { + return QPen (color.toQColor (), + lineWidth == 1 ? 0/*closer to looking width 1*/ : lineWidth, lineStyle, + Qt::RoundCap, Qt::RoundJoin); + } + else + return Qt::NoPen; +} + +static QBrush makeMaskBrush (const kpColor &foregroundColor, + const kpColor &backgroundColor, + kpToolWidgetFillStyle *toolWidgetFillStyle) +{ + if (toolWidgetFillStyle) + return toolWidgetFillStyle->maskBrush (foregroundColor, backgroundColor); + else + return Qt::NoBrush; +} + +static QBrush makeBrush (const kpColor &foregroundColor, + const kpColor &backgroundColor, + kpToolWidgetFillStyle *toolWidgetFillStyle) +{ + if (toolWidgetFillStyle) + return toolWidgetFillStyle->brush (foregroundColor, backgroundColor); + else + return Qt::NoBrush; +} + +static bool only1PixelInPointArray (const QPointArray &points) +{ + if (points.count () == 0) + return false; + + for (int i = 1; i < (int) points.count (); i++) + { + if (points [i] != points [0]) + return false; + } + + return true; +} + +static QPixmap pixmap (const QPixmap &oldPixmap, + const QPointArray &points, const QRect &rect, + const kpColor &foregroundColor, kpColor backgroundColor, + int lineWidth, Qt::PenStyle lineStyle, + kpToolWidgetFillStyle *toolWidgetFillStyle, + enum kpToolPolygon::Mode mode, bool final = true) +{ + // + // figure out points to draw relative to topLeft of oldPixmap + + QPointArray pointsInRect = points; + pointsInRect.detach (); + pointsInRect.translate (-rect.x (), -rect.y ()); + +#if DEBUG_KP_TOOL_POLYGON && 0 + kdDebug () << "kptoolpolygon.cpp: pixmap(): points=" << pointArrayToString (points) << endl; +#endif + + + // + // draw + + QPen pen = makePen (foregroundColor, lineWidth, lineStyle), + maskPen = makeMaskPen (foregroundColor, lineWidth, lineStyle); + QBrush brush = makeBrush (foregroundColor, backgroundColor, toolWidgetFillStyle), + maskBrush = makeMaskBrush (foregroundColor, backgroundColor, toolWidgetFillStyle); + + QPixmap pixmap = oldPixmap; + QBitmap maskBitmap; + + QPainter painter, maskPainter; + + if (pixmap.mask () || + (maskPen.style () != Qt::NoPen && + maskPen.color () == Qt::color0/*transparent*/) || + (maskBrush.style () != Qt::NoBrush && + maskBrush.color () == Qt::color0/*transparent*/)) + { + maskBitmap = kpPixmapFX::getNonNullMask (pixmap); + maskPainter.begin (&maskBitmap); + maskPainter.setPen (maskPen); + maskPainter.setBrush (maskBrush); + + #if DEBUG_KP_TOOL_POLYGON && 0 + kdDebug () << "\tmaskPainter begin because:" << endl + << "\t\tpixmap.mask=" << pixmap.mask () << endl + << "\t\t(maskPenStyle!=NoPen)=" << (maskPen.style () != Qt::NoPen) << endl + << "\t\t(maskPenColor==trans)=" << (maskPen.color () == Qt::color0) << endl + << "\t\t(maskBrushStyle!=NoBrush)=" << (maskBrush.style () != Qt::NoBrush) << endl + << "\t\t(maskBrushColor==trans)=" << (maskBrush.color () == Qt::color0) << endl; + #endif + } + + if (pen.style () != Qt::NoPen || + brush.style () != Qt::NoBrush) + { + painter.begin (&pixmap); + painter.setPen (pen); + painter.setBrush (brush); + + #if DEBUG_KP_TOOL_POLYGON && 0 + kdDebug () << "\tpainter begin pen.rgb=" + << (int *) painter.pen ().color ().rgb () + << endl; + #endif + } + +#define PAINTER_CALL(cmd) \ +{ \ + if (painter.isActive ()) \ + painter . cmd ; \ + \ + if (maskPainter.isActive ()) \ + maskPainter . cmd ; \ +} + + // SYNC: Qt bug + if (only1PixelInPointArray (pointsInRect)) + { + PAINTER_CALL (drawPoint (pointsInRect [0])); + } + else + { + switch (mode) + { + case kpToolPolygon::Line: + case kpToolPolygon::Polyline: + PAINTER_CALL (drawPolyline (pointsInRect)); + break; + case kpToolPolygon::Polygon: + // TODO: why aren't the ends rounded? + PAINTER_CALL (drawPolygon (pointsInRect)); + + if (!final && 0/*HACK for TODO*/) + { + int count = pointsInRect.count (); + + if (count > 2) + { + if (painter.isActive ()) + { + QPen XORpen = painter.pen (); + XORpen.setColor (Qt::white); + + painter.setPen (XORpen); + painter.setRasterOp (Qt::XorROP); + } + + if (maskPainter.isActive ()) + { + QPen XORpen = maskPainter.pen (); + + // TODO??? + #if 0 + if (kpTool::isColorTransparent (foregroundColor)) + XORpen.setColor (Qt::color1/*opaque*/); + else + XORpen.setColor (Qt::color0/*transparent*/); + #endif + + maskPainter.setPen (XORpen); + } + + PAINTER_CALL (drawLine (pointsInRect [0], pointsInRect [count - 1])); + } + } + break; + case kpToolPolygon::Curve: + int numPoints = pointsInRect.count (); + QPointArray pa (4); + + pa [0] = pointsInRect [0]; + pa [3] = pointsInRect [1]; + + switch (numPoints) + { + case 2: + pa [1] = pointsInRect [0]; + pa [2] = pointsInRect [1]; + break; + case 3: + pa [1] = pa [2] = pointsInRect [2]; + break; + case 4: + pa [1] = pointsInRect [2]; + pa [2] = pointsInRect [3]; + } + + PAINTER_CALL (drawCubicBezier (pa)); + } + } +#undef PAINTER_CALL + + if (painter.isActive ()) + painter.end (); + + if (maskPainter.isActive ()) + maskPainter.end (); + + if (!maskBitmap.isNull ()) + pixmap.setMask (maskBitmap); + + return pixmap; +} + + +/* + * kpToolPolygon + */ + +kpToolPolygon::kpToolPolygon (Mode mode, + const QString &text, const QString &description, + int key, + kpMainWindow *mainWindow, const char *name) + : kpTool (text, description, key, mainWindow, name), + m_mode (mode), + m_toolWidgetFillStyle (0), + m_toolWidgetLineWidth (0) +{ +} + +kpToolPolygon::kpToolPolygon (kpMainWindow *mainWindow) + : kpTool (i18n ("Polygon"), i18n ("Draws polygons"), + Qt::Key_G, + mainWindow, "tool_polygon"), + m_mode (Polygon), + m_toolWidgetFillStyle (0), + m_toolWidgetLineWidth (0) +{ +} + +kpToolPolygon::~kpToolPolygon () +{ +} + +void kpToolPolygon::setMode (Mode m) +{ + m_mode = m; +} + + +// private +QString kpToolPolygon::haventBegunShapeUserMessage () const +{ + switch (m_mode) + { + case Line: + return i18n ("Drag to draw."); + case Polygon: + case Polyline: + return i18n ("Drag to draw the first line."); + case Curve: + return i18n ("Drag out the start and end points."); + default: + return QString::null; + } +} + +// virtual +void kpToolPolygon::begin () +{ + kpToolToolBar *tb = toolToolBar (); + +#if DEBUG_KP_TOOL_POLYGON + kdDebug () << "kpToolPolygon::begin() tb=" << tb << endl; +#endif + + if (tb) + { + if (m_mode == Polygon) + m_toolWidgetFillStyle = tb->toolWidgetFillStyle (); + else + m_toolWidgetFillStyle = 0; + + m_toolWidgetLineWidth = tb->toolWidgetLineWidth (); + + if (m_toolWidgetFillStyle) + { + connect (m_toolWidgetFillStyle, SIGNAL (fillStyleChanged (kpToolWidgetFillStyle::FillStyle)), + this, SLOT (slotFillStyleChanged (kpToolWidgetFillStyle::FillStyle))); + } + connect (m_toolWidgetLineWidth, SIGNAL (lineWidthChanged (int)), + this, SLOT (slotLineWidthChanged (int))); + + if (m_toolWidgetFillStyle) + m_toolWidgetFillStyle->show (); + m_toolWidgetLineWidth->show (); + + m_lineWidth = m_toolWidgetLineWidth->lineWidth (); + } + else + { + m_toolWidgetFillStyle = 0; + m_toolWidgetLineWidth = 0; + + m_lineWidth = 1; + } + + viewManager ()->setCursor (QCursor (CrossCursor)); + + m_originatingMouseButton = -1; + + setUserMessage (haventBegunShapeUserMessage ()); +} + +// virtual +void kpToolPolygon::end () +{ + endShape (); + + if (m_toolWidgetFillStyle) + { + disconnect (m_toolWidgetFillStyle, SIGNAL (fillStyleChanged (kpToolWidgetFillStyle::FillStyle)), + this, SLOT (slotFillStyleChanged (kpToolWidgetFillStyle::FillStyle))); + m_toolWidgetFillStyle = 0; + } + + if (m_toolWidgetLineWidth) + { + disconnect (m_toolWidgetLineWidth, SIGNAL (lineWidthChanged (int)), + this, SLOT (slotLineWidthChanged (int))); + m_toolWidgetLineWidth = 0; + } + + viewManager ()->unsetCursor (); +} + + +void kpToolPolygon::beginDraw () +{ +#if DEBUG_KP_TOOL_POLYGON + kdDebug () << "kpToolPolygon::beginDraw() m_points=" << pointArrayToString (m_points) + << ", startPoint=" << m_startPoint << endl; +#endif + + bool endedShape = false; + + // starting with a line... + if (m_points.count () == 0) + { + m_originatingMouseButton = m_mouseButton; + m_points.putPoints (m_points.count (), 2, + m_startPoint.x (), m_startPoint.y (), + m_startPoint.x (), m_startPoint.y ()); + } + // continuing poly* + else + { + if (m_mouseButton != m_originatingMouseButton) + { + m_mouseButton = m_originatingMouseButton; + endShape (); + endedShape = true; + } + else + { + int count = m_points.count (); + m_points.putPoints (count, 1, + m_startPoint.x (), m_startPoint.y ()); + + // start point = last end point; + // _not_ the new/current start point + // (which is disregarded in a poly* as only the end points count + // after the initial line) + // + // Curve Tool ignores m_startPoint (doesn't call applyModifiers()) + // after the initial has been defined. + m_startPoint = m_points [count - 1]; + } + } + +#if DEBUG_KP_TOOL_POLYGON + kdDebug () << "\tafterwards, m_points=" << pointArrayToString (m_points) << endl; +#endif + + if (!endedShape) + { + switch (m_mode) + { + case Line: + case Curve: + case Polygon: + case Polyline: + setUserMessage (cancelUserMessage ()); + break; + + default: + kdError () << "kpToolPolygon::beginDraw() shape" << endl; + break; + } + } +} + +// private +void kpToolPolygon::applyModifiers () +{ + int count = m_points.count (); + + m_toolLineStartPoint = m_startPoint; /* also correct for poly* tool (see beginDraw()) */ + m_toolLineEndPoint = m_currentPoint; + +#if DEBUG_KP_TOOL_POLYGON && 1 + kdDebug () << "kpToolPolygon::applyModifiers() #pts=" << count + << " line: startPt=" << m_toolLineStartPoint + << " endPt=" << m_toolLineEndPoint + << " modifiers: shift=" << m_shiftPressed + << " alt=" << m_altPressed + << " ctrl=" << m_controlPressed + << endl; +#endif + + // angles + if (m_shiftPressed || m_controlPressed) + { + int diffx = m_toolLineEndPoint.x () - m_toolLineStartPoint.x (); + int diffy = m_toolLineEndPoint.y () - m_toolLineStartPoint.y (); + + double ratio; + if (diffx == 0) + ratio = DBL_MAX; + else + ratio = fabs (double (diffy) / double (diffx)); + #if DEBUG_KP_TOOL_POLYGON && 1 + kdDebug () << "\tdiffx=" << diffx << " diffy=" << diffy + << " ratio=" << ratio + << endl; + #endif + + // Shift = 0, 45, 90 + // Alt = 0, 30, 60, 90 + // Shift + Alt = 0, 30, 45, 60, 90 + double angles [10]; // "ought to be enough for anybody" + int numAngles = 0; + angles [numAngles++] = 0; + if (m_controlPressed) + angles [numAngles++] = KP_PI / 6; + if (m_shiftPressed) + angles [numAngles++] = KP_PI / 4; + if (m_controlPressed) + angles [numAngles++] = KP_PI / 3; + angles [numAngles++] = KP_PI / 2; + + double angle = angles [numAngles - 1]; + for (int i = 0; i < numAngles - 1; i++) + { + double acceptingRatio = tan ((angles [i] + angles [i + 1]) / 2.0); + if (ratio < acceptingRatio) + { + angle = angles [i]; + break; + } + } + + // horizontal (dist from start !maintained) + if (fabs (KP_RADIANS_TO_DEGREES (angle) - 0) + < kpPixmapFX::AngleInDegreesEpsilon) + { + m_toolLineEndPoint = QPoint (m_toolLineEndPoint.x (), m_toolLineStartPoint.y ()); + } + // vertical (dist from start !maintained) + else if (fabs (KP_RADIANS_TO_DEGREES (angle) - 90) + < kpPixmapFX::AngleInDegreesEpsilon) + { + m_toolLineEndPoint = QPoint (m_toolLineStartPoint.x (), m_toolLineEndPoint.y ()); + } + // diagonal (dist from start maintained) + else + { + const double dist = sqrt (diffx * diffx + diffy * diffy); + + #define sgn(a) ((a)<0?-1:1) + // Round distances _before_ adding to any coordinate + // (ensures consistent rounding behaviour in x & y directions) + const int newdx = qRound (dist * cos (angle) * sgn (diffx)); + const int newdy = qRound (dist * sin (angle) * sgn (diffy)); + #undef sgn + + m_toolLineEndPoint = QPoint (m_toolLineStartPoint.x () + newdx, + m_toolLineStartPoint.y () + newdy); + + #if DEBUG_KP_TOOL_POLYGON && 1 + kdDebug () << "\t\tdiagonal line: dist=" << dist + << " angle=" << (angle * 180 / KP_PI) + << " endPoint=" << m_toolLineEndPoint + << endl; + #endif + } + } // if (m_shiftPressed || m_controlPressed) { + + // centring + if (m_altPressed && 0/*ALT is unreliable*/) + { + // start = start - diff + // = start - (end - start) + // = start - end + start + // = 2 * start - end + if (count == 2) + m_toolLineStartPoint += (m_toolLineStartPoint - m_toolLineEndPoint); + else + m_toolLineEndPoint += (m_toolLineEndPoint - m_toolLineStartPoint); + } // if (m_altPressed) { + + m_points [count - 2] = m_toolLineStartPoint; + m_points [count - 1] = m_toolLineEndPoint; + + m_toolLineRect = kpTool::neededRect (QRect (m_toolLineStartPoint, m_toolLineEndPoint).normalize (), + m_lineWidth); +} + +// virtual +void kpToolPolygon::draw (const QPoint &, const QPoint &, const QRect &) +{ + if (m_points.count () == 0) + return; + +#if DEBUG_KP_TOOL_POLYGON + kdDebug () << "kpToolPolygon::draw() m_points=" << pointArrayToString (m_points) + << ", endPoint=" << m_currentPoint << endl; +#endif + + bool drawingALine = (m_mode != Curve) || + (m_mode == Curve && m_points.count () == 2); + + if (drawingALine) + applyModifiers (); + else + m_points [m_points.count () - 1] = m_currentPoint; + +#if DEBUG_KP_TOOL_POLYGON + kdDebug () << "\tafterwards, m_points=" << pointArrayToString (m_points) << endl; +#endif + + updateShape (); + + if (drawingALine) + setUserShapePoints (m_toolLineStartPoint, m_toolLineEndPoint); + else + setUserShapePoints (m_currentPoint); +} + +// private slot +void kpToolPolygon::updateShape () +{ + if (m_points.count () == 0) + return; + + QRect boundingRect = kpTool::neededRect (m_points.boundingRect (), m_lineWidth); + +#if DEBUG_KP_TOOL_POLYGON + kdDebug () << "kpToolPolygon::updateShape() boundingRect=" + << boundingRect + << " lineWidth=" + << m_lineWidth + << endl; +#endif + + QPixmap oldPixmap = document ()->getPixmapAt (boundingRect); + QPixmap newPixmap = pixmap (oldPixmap, + m_points, boundingRect, + color (m_mouseButton), color (1 - m_mouseButton), + m_lineWidth, Qt::SolidLine, + m_toolWidgetFillStyle, + m_mode, false/*not final*/); + + viewManager ()->setFastUpdates (); + viewManager ()->setTempPixmap (kpTempPixmap (false/*always display*/, + kpTempPixmap::SetPixmap/*render mode*/, + boundingRect.topLeft (), + newPixmap)); + viewManager ()->restoreFastUpdates (); +} + +// virtual +void kpToolPolygon::cancelShape () +{ +#if 0 + endDraw (QPoint (), QRect ()); + commandHistory ()->undo (); +#else + viewManager ()->invalidateTempPixmap (); +#endif + m_points.resize (0); + + setUserMessage (i18n ("Let go of all the mouse buttons.")); +} + +void kpToolPolygon::releasedAllButtons () +{ + if (!hasBegunShape ()) + setUserMessage (haventBegunShapeUserMessage ()); + + // --- else case already handled by endDraw() --- +} + +// virtual +void kpToolPolygon::endDraw (const QPoint &, const QRect &) +{ +#if DEBUG_KP_TOOL_POLYGON + kdDebug () << "kpToolPolygon::endDraw() m_points=" << pointArrayToString (m_points) << endl; +#endif + + if (m_points.count () == 0) + return; + + if (m_mode == Line || + (m_mode == Curve && m_points.count () >= 4) || + m_points.count () >= 50) + { + endShape (); + } + else + { + switch (m_mode) + { + case Line: + kdError () << "kpToolPolygon::endDraw() - line not ended" << endl; + setUserMessage (); + break; + + case Polygon: + case Polyline: + if (m_points.isEmpty ()) + { + kdError () << "kpToolPolygon::endDraw() exception - poly without points" << endl; + setUserMessage (); + } + else + { + if (m_mouseButton == 0) + { + setUserMessage (i18n ("Left drag another line or right click to finish.")); + } + else + { + setUserMessage (i18n ("Right drag another line or left click to finish.")); + } + } + + break; + + case Curve: + if (m_points.size () == 2) + { + if (m_mouseButton == 0) + { + setUserMessage (i18n ("Left drag to set the first control point or right click to finish.")); + } + else + { + setUserMessage (i18n ("Right drag to set the first control point or left click to finish.")); + } + } + else if (m_points.size () == 3) + { + if (m_mouseButton == 0) + { + setUserMessage (i18n ("Left drag to set the last control point or right click to finish.")); + } + else + { + setUserMessage (i18n ("Right drag to set the last control point or left click to finish.")); + } + } + else + { + kdError () << "kpToolPolygon::endDraw() exception - points" << endl; + setUserMessage (); + } + + break; + + default: + kdError () << "kpToolPolygon::endDraw() - clueless" << endl; + setUserMessage (); + break; + } + } +} + +// public virtual +void kpToolPolygon::endShape (const QPoint &, const QRect &) +{ +#if DEBUG_KP_TOOL_POLYGON + kdDebug () << "kpToolPolygon::endShape() m_points=" << pointArrayToString (m_points) << endl; +#endif + + if (!hasBegunShape ()) + return; + + viewManager ()->invalidateTempPixmap (); + + QRect boundingRect = kpTool::neededRect (m_points.boundingRect (), m_lineWidth); + + kpToolPolygonCommand *lineCommand = + new kpToolPolygonCommand + (text (), + m_points, boundingRect, + color (m_mouseButton), color (1 - m_mouseButton), + m_lineWidth, Qt::SolidLine, + m_toolWidgetFillStyle, + document ()->getPixmapAt (boundingRect), + m_mode, + mainWindow ()); + + commandHistory ()->addCommand (lineCommand); + + m_points.resize (0); + setUserMessage (haventBegunShapeUserMessage ()); + +} + +// public virtual +bool kpToolPolygon::hasBegunShape () const +{ + return (m_points.count () > 0); +} + + +// public slot +void kpToolPolygon::slotLineWidthChanged (int width) +{ + m_lineWidth = width; + updateShape (); +} + +// public slot +void kpToolPolygon::slotFillStyleChanged (kpToolWidgetFillStyle::FillStyle /*fillStyle*/) +{ + updateShape (); +} + +// virtual protected slot +void kpToolPolygon::slotForegroundColorChanged (const kpColor &) +{ + updateShape (); +} + +// virtual protected slot +void kpToolPolygon::slotBackgroundColorChanged (const kpColor &) +{ + updateShape (); +} + + +/* + * kpToolPolygonCommand + */ + +kpToolPolygonCommand::kpToolPolygonCommand (const QString &name, + const QPointArray &points, + const QRect &normalizedRect, + const kpColor &foregroundColor, const kpColor &backgroundColor, + int lineWidth, Qt::PenStyle lineStyle, + kpToolWidgetFillStyle *toolWidgetFillStyle, + const QPixmap &originalArea, + enum kpToolPolygon::Mode mode, + kpMainWindow *mainWindow) + : kpNamedCommand (name, mainWindow), + m_points (points), + m_normalizedRect (normalizedRect), + m_foregroundColor (foregroundColor), m_backgroundColor (backgroundColor), + m_lineWidth (lineWidth), m_lineStyle (lineStyle), + m_toolWidgetFillStyle (toolWidgetFillStyle), + m_originalArea (originalArea), + m_mode (mode) +{ + m_points.detach (); +} + +kpToolPolygonCommand::~kpToolPolygonCommand () +{ +} + + +// public virtual [base kpCommand] +int kpToolPolygonCommand::size () const +{ + return kpPixmapFX::pointArraySize (m_points) + + kpPixmapFX::pixmapSize (m_originalArea); +} + + +// public virtual [base kpCommand] +void kpToolPolygonCommand::execute () +{ + QPixmap p = pixmap (m_originalArea, + m_points, m_normalizedRect, + m_foregroundColor, m_backgroundColor, + m_lineWidth, m_lineStyle, + m_toolWidgetFillStyle, + m_mode); + document ()->setPixmapAt (p, m_normalizedRect.topLeft ()); +} + +// public virtual [base kpCommand] +void kpToolPolygonCommand::unexecute () +{ + document ()->setPixmapAt (m_originalArea, m_normalizedRect.topLeft ()); +} + +#include <kptoolpolygon.moc> diff --git a/kolourpaint/tools/kptoolpolygon.h b/kolourpaint/tools/kptoolpolygon.h new file mode 100644 index 00000000..456dc4c0 --- /dev/null +++ b/kolourpaint/tools/kptoolpolygon.h @@ -0,0 +1,157 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 __kptoolpolygon_h__ +#define __kptoolpolygon_h__ + +#include <qbrush.h> +#include <qpen.h> +#include <qobject.h> +#include <qpixmap.h> +#include <qpoint.h> +#include <qpointarray.h> +#include <qrect.h> + +#include <kpcommandhistory.h> + +#include <kpcolor.h> +#include <kptool.h> +#include <kptoolwidgetfillstyle.h> + +class QMouseEvent; +class QPen; +class QPoint; +class QRect; +class QString; + +class kpView; +class kpDocument; +class kpMainWindow; + +class kpToolWidgetFillStyle; +class kpToolWidgetLineWidth; +class kpViewManager; + +class kpToolPolygon : public kpTool +{ +Q_OBJECT + +public: + enum Mode + { + Polygon, Polyline, Line, Curve + }; + + kpToolPolygon (Mode mode, const QString &text, const QString &description, + int key, + kpMainWindow *mainWindow, const char *name); + kpToolPolygon (kpMainWindow *mainWindow); + virtual ~kpToolPolygon (); + + void setMode (Mode mode); + + virtual bool careAboutModifierState () const { return true; } + +private: + QString haventBegunShapeUserMessage () const; + +public: + virtual void begin (); + virtual void end (); + + virtual void beginDraw (); + virtual void draw (const QPoint &, const QPoint &, const QRect &); + virtual void cancelShape (); + virtual void releasedAllButtons (); + virtual void endDraw (const QPoint &, const QRect &); + virtual void endShape (const QPoint & = QPoint (), const QRect & = QRect ()); + + virtual bool hasBegunShape () const; + +public slots: + void slotLineWidthChanged (int width); + void slotFillStyleChanged (kpToolWidgetFillStyle::FillStyle fillStyle); + +protected slots: + virtual void slotForegroundColorChanged (const kpColor &); + virtual void slotBackgroundColorChanged (const kpColor &); + +private slots: + void updateShape (); + +private: + Mode m_mode; + + kpToolWidgetFillStyle *m_toolWidgetFillStyle; + + int m_lineWidth; + kpToolWidgetLineWidth *m_toolWidgetLineWidth; + + int m_originatingMouseButton; + + void applyModifiers (); + + QPoint m_toolLineStartPoint, m_toolLineEndPoint; + QRect m_toolLineRect; + + QPointArray m_points; +}; + +class kpToolPolygonCommand : public kpNamedCommand +{ +public: + kpToolPolygonCommand (const QString &name, + const QPointArray &points, + const QRect &normalizedRect, + const kpColor &foregroundColor, const kpColor &backgroundColor, + int lineWidth, Qt::PenStyle lineStyle, + kpToolWidgetFillStyle *toolWidgetFillStyle, + const QPixmap &originalArea, + kpToolPolygon::Mode mode, + kpMainWindow *mainWindow); + virtual ~kpToolPolygonCommand (); + + virtual int size () const; + + virtual void execute (); + virtual void unexecute (); + +private: + QPointArray m_points; + QRect m_normalizedRect; + + kpColor m_foregroundColor, m_backgroundColor; + int m_lineWidth; + Qt::PenStyle m_lineStyle; + kpToolWidgetFillStyle *m_toolWidgetFillStyle; + + QPixmap m_originalArea; + kpToolPolygon::Mode m_mode; +}; + +#endif // __kptoolpolygon_h__ diff --git a/kolourpaint/tools/kptoolpolyline.cpp b/kolourpaint/tools/kptoolpolyline.cpp new file mode 100644 index 00000000..6299b5b7 --- /dev/null +++ b/kolourpaint/tools/kptoolpolyline.cpp @@ -0,0 +1,47 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <kptoolpolyline.h> + +#include <klocale.h> + + +kpToolPolyline::kpToolPolyline (kpMainWindow *mainWindow) + : kpToolPolygon (Polyline, + i18n ("Connected Lines"), + i18n ("Draws connected lines"), + Qt::Key_N, + mainWindow, "tool_polyline") +{ +} + +kpToolPolyline::~kpToolPolyline () +{ +} + +#include <kptoolpolyline.moc> diff --git a/kolourpaint/tools/kptoolpolyline.h b/kolourpaint/tools/kptoolpolyline.h new file mode 100644 index 00000000..f76a3959 --- /dev/null +++ b/kolourpaint/tools/kptoolpolyline.h @@ -0,0 +1,46 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 __kptoolpolyline_h__ +#define __kptoolpolyline_h__ + +#include <kptoolpolygon.h> + +class kpMainWindow; + +class kpToolPolyline : public kpToolPolygon +{ +Q_OBJECT + +public: + kpToolPolyline (kpMainWindow *); + virtual ~kpToolPolyline (); +}; + +#endif // __kptoolpolyline_h__ + diff --git a/kolourpaint/tools/kptoolpreviewdialog.cpp b/kolourpaint/tools/kptoolpreviewdialog.cpp new file mode 100644 index 00000000..23149232 --- /dev/null +++ b/kolourpaint/tools/kptoolpreviewdialog.cpp @@ -0,0 +1,431 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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_PREVIEW_DIALOG 0 + +#include <kptoolpreviewdialog.h> + +#include <qapplication.h> +#include <qlayout.h> +#include <qgroupbox.h> +#include <qlabel.h> +#include <qpushbutton.h> + +#include <kdebug.h> +#include <klocale.h> + +#include <kpcolor.h> +#include <kpdocument.h> +#include <kpmainwindow.h> +#include <kppixmapfx.h> +#include <kpresizesignallinglabel.h> +#include <kpselection.h> + + +kpToolPreviewDialog::kpToolPreviewDialog (Features features, + bool reserveTopRow, + const QString &caption, + const QString &afterActionText, + bool actOnSelection, + kpMainWindow *parent, + const char *name) + : KDialogBase (parent, name, true/*modal*/, + caption, + KDialogBase::Ok | KDialogBase::Cancel), + m_afterActionText (afterActionText), + m_actOnSelection (actOnSelection), + m_mainWindow (parent), + m_dimensionsGroupBox (0), + m_afterTransformDimensionsLabel (0), + m_previewGroupBox (0), + m_previewPixmapLabel (0), + m_gridLayout (0) +{ + QWidget *baseWidget = new QWidget (this); + setMainWidget (baseWidget); + + + 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, 4, 2, + 0/*margin*/, spacingHint ()); + 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->setColStretch (1, 1); + } + else if (m_dimensionsGroupBox) + { + m_gridLayout->addMultiCellWidget (m_dimensionsGroupBox, + m_gridNumRows, m_gridNumRows, 0, 1); + } + else if (m_previewGroupBox) + { + m_gridLayout->addMultiCellWidget (m_previewGroupBox, + m_gridNumRows, m_gridNumRows, 0, 1); + } + + m_gridLayout->setRowStretch (m_gridNumRows, 1); + m_gridNumRows++;; + } +} + +kpToolPreviewDialog::~kpToolPreviewDialog () +{ +} + + +// private +void kpToolPreviewDialog::createDimensionsGroupBox () +{ + m_dimensionsGroupBox = new QGroupBox (i18n ("Dimensions"), mainWidget ()); + + QLabel *originalLabel = new QLabel (i18n ("Original:"), m_dimensionsGroupBox); + QString originalDimensions; + if (document ()) + { + originalDimensions = i18n ("%1 x %2") + .arg (m_oldWidth) + .arg (m_oldHeight); + + // Stop the Dimensions Group Box from resizing so often + const QString minimumLengthString ("100000 x 100000"); + const int padLength = minimumLengthString.length (); + for (int i = originalDimensions.length (); i < padLength; i++) + originalDimensions += " "; + } + QLabel *originalDimensionsLabel = new QLabel (originalDimensions, m_dimensionsGroupBox); + + QLabel *afterTransformLabel = new QLabel (m_afterActionText, m_dimensionsGroupBox); + m_afterTransformDimensionsLabel = new QLabel (m_dimensionsGroupBox); + + + QGridLayout *dimensionsLayout = new QGridLayout (m_dimensionsGroupBox, + 2, 2, + marginHint () * 2, spacingHint ()); + + 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 kpToolPreviewDialog::createPreviewGroupBox () +{ + m_previewGroupBox = new QGroupBox (i18n ("Preview"), mainWidget ()); + + m_previewPixmapLabel = new kpResizeSignallingLabel (m_previewGroupBox); + m_previewPixmapLabel->setMinimumSize (150, 110); + connect (m_previewPixmapLabel, SIGNAL (resized ()), + this, SLOT (updatePreview ())); + + QPushButton *updatePushButton = new QPushButton (i18n ("&Update"), + m_previewGroupBox); + connect (updatePushButton, SIGNAL (clicked ()), + this, SLOT (slotUpdateWithWaitCursor ())); + + + QVBoxLayout *previewLayout = new QVBoxLayout (m_previewGroupBox, + marginHint () * 2, + QMAX (1, spacingHint () / 2)); + + previewLayout->addWidget (m_previewPixmapLabel, 1/*stretch*/); + previewLayout->addWidget (updatePushButton, 0/*stretch*/, Qt::AlignHCenter); +} + + +// protected +kpDocument *kpToolPreviewDialog::document () const +{ + return m_mainWindow ? m_mainWindow->document () : 0; +} + + +// protected +void kpToolPreviewDialog::addCustomWidgetToFront (QWidget *w) +{ + m_gridLayout->addMultiCellWidget (w, 0, 0, 0, 1); +} + +// protected +void kpToolPreviewDialog::addCustomWidget (QWidget *w) +{ + m_gridLayout->addMultiCellWidget (w, m_gridNumRows, m_gridNumRows, 0, 1); + m_gridNumRows++; +} + + +// private +void kpToolPreviewDialog::updateDimensions () +{ + if (!m_dimensionsGroupBox) + return; + + kpDocument *doc = document (); + if (!doc) + return; + + QSize newDim = newDimensions (); +#if DEBUG_KP_TOOL_PREVIEW_DIALOG + kdDebug () << "kpToolPreviewDialog::updateDimensions(): newDim=" << newDim << endl; +#endif + + QString newDimString = i18n ("%1 x %2") + .arg (newDim.width ()) + .arg (newDim.height ()); + m_afterTransformDimensionsLabel->setText (newDimString); +} + + +// public static +double kpToolPreviewDialog::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 kpToolPreviewDialog::scaleDimension (int dimension, double scale, int min, int max) +{ + return QMAX (min, + QMIN (max, + qRound (dimension * scale))); +} + + +// private +void kpToolPreviewDialog::updateShrukenDocumentPixmap () +{ +#if DEBUG_KP_TOOL_PREVIEW_DIALOG + kdDebug () << "kpToolPreviewDialog::updateShrukenDocumentPixmap()" + << " shrunkenDocPixmap.size=" + << m_shrunkenDocumentPixmap.size () + << " previewPixmapLabelSizeWhenUpdatedPixmap=" + << m_previewPixmapLabelSizeWhenUpdatedPixmap + << " previewPixmapLabel.size=" + << m_previewPixmapLabel->size () + << endl; +#endif + + if (!m_previewGroupBox) + return; + + + kpDocument *doc = document (); + if (!doc || !doc->pixmap ()) + { + kdError () << "kpToolPreviewDialog::updateShrunkenDocumentPixmap() doc=" + << doc << endl; + return; + } + + if (m_shrunkenDocumentPixmap.isNull () || + m_previewPixmapLabel->size () != m_previewPixmapLabelSizeWhenUpdatedPixmap) + { + #if DEBUG_KP_TOOL_PREVIEW_DIALOG + kdDebug () << "\tupdating shrunkenDocPixmap" << endl; + #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); + + QPixmap pixmap; + + if (m_actOnSelection) + { + kpSelection sel = *doc->selection (); + if (!sel.pixmap ()) + sel.setPixmap (doc->getSelectedPixmap ()); + + pixmap = sel.transparentPixmap (); + } + else + { + pixmap = *doc->pixmap (); + } + + m_shrunkenDocumentPixmap = kpPixmapFX::scale ( + pixmap, + scaleDimension (m_oldWidth, + keepsAspectScale, + 1, m_previewPixmapLabel->width ()), + scaleDimension (m_oldHeight, + keepsAspectScale, + 1, m_previewPixmapLabel->height ())); + #if 0 + m_shrunkenDocumentPixmap = kpPixmapFX::scale ( + m_actOnSelection ? doc->getSelectedPixmap () : *doc->pixmap (), + m_previewPixmapLabel->width (), + m_previewPixmapLabel->height ()); + #endif + + m_previewPixmapLabelSizeWhenUpdatedPixmap = m_previewPixmapLabel->size (); + } +} + + +// private +void kpToolPreviewDialog::updatePreview () +{ +#if DEBUG_KP_TOOL_PREVIEW_DIALOG + kdDebug () << "kpToolPreviewDialog::updatePreview()" << endl; +#endif + + if (!m_previewGroupBox) + return; + + + kpDocument *doc = document (); + if (!doc) + return; + + updateShrukenDocumentPixmap (); + + 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 + QPixmap transformedShrunkenDocumentPixmap = + transformPixmap (m_shrunkenDocumentPixmap, targetWidth, targetHeight); + + QPixmap previewPixmap (m_previewPixmapLabel->width (), + m_previewPixmapLabel->height ()); + kpPixmapFX::fill (&previewPixmap, kpColor::transparent); + kpPixmapFX::setPixmapAt (&previewPixmap, + (previewPixmap.width () - transformedShrunkenDocumentPixmap.width ()) / 2, + (previewPixmap.height () - transformedShrunkenDocumentPixmap.height ()) / 2, + transformedShrunkenDocumentPixmap); + +#if DEBUG_KP_TOOL_PREVIEW_DIALOG + kdDebug () << "kpToolPreviewDialog::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 (previewPixmap); + + // immediate update esp. for expensive previews + m_previewPixmapLabel->repaint (false/*no erase*/); + +#if DEBUG_KP_TOOL_PREVIEW_DIALOG + kdDebug () << "\tafter QLabel::setPixmap() previewPixmapLabel: w=" + << m_previewPixmapLabel->width () + << " h=" + << m_previewPixmapLabel->height () + << endl; +#endif + } +} + + +// protected slot virtual +void kpToolPreviewDialog::slotUpdate () +{ +#if DEBUG_KP_TOOL_PREVIEW_DIALOG + kdDebug () << "kpToolPreviewDialog::slotUpdate()" << endl; +#endif + updateDimensions (); + updatePreview (); +} + +// protected slot virtual +void kpToolPreviewDialog::slotUpdateWithWaitCursor () +{ +#if DEBUG_KP_TOOL_PREVIEW_DIALOG + kdDebug () << "kpToolPreviewDialog::slotUpdateWithWaitCursor()" + << endl; +#endif + + QApplication::setOverrideCursor (Qt::waitCursor); + + slotUpdate (); + + QApplication::restoreOverrideCursor (); +} + + +#include <kptoolpreviewdialog.moc> diff --git a/kolourpaint/tools/kptoolpreviewdialog.h b/kolourpaint/tools/kptoolpreviewdialog.h new file mode 100644 index 00000000..35efdc38 --- /dev/null +++ b/kolourpaint/tools/kptoolpreviewdialog.h @@ -0,0 +1,131 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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_preview_dialog_h__ +#define __kp_tool_preview_dialog_h__ + + +#include <qpixmap.h> + +#include <kdialogbase.h> + + +class QLabel; +class QGridLayout; +class QGroupBox; + +class kpDocument; +class kpMainWindow; +class kpResizeSignallingLabel; + + +class kpToolPreviewDialog : public KDialogBase +{ +Q_OBJECT + +public: + enum Features + { + Dimensions = 1, Preview = 2, + AllFeatures = Dimensions | Preview + }; + + // You must call slotUpdate() in your constructor + kpToolPreviewDialog (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, + kpMainWindow *parent, + const char *name = 0); + virtual ~kpToolPreviewDialog (); + +private: + void createDimensionsGroupBox (); + void createPreviewGroupBox (); + +public: + virtual bool isNoOp () const = 0; + +protected: + kpDocument *document () const; + + // All widgets must have mainWidget() as their parent + void addCustomWidgetToFront (QWidget *w); // see <reserveTopRow> in ctor + void addCustomWidget (QWidget *w); + void addCustomWidgetToBack (QWidget *w) + { + addCustomWidget (w); + } + + virtual QSize newDimensions () const = 0; + virtual QPixmap transformPixmap (const QPixmap &pixmap, + int targetWidth, int targetHeight) const = 0; + +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 updateShrukenDocumentPixmap (); + +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: + QString m_afterActionText; + bool m_actOnSelection; + kpMainWindow *m_mainWindow; + + int m_oldWidth, m_oldHeight; + + QGroupBox *m_dimensionsGroupBox; + QLabel *m_afterTransformDimensionsLabel; + + QGroupBox *m_previewGroupBox; + kpResizeSignallingLabel *m_previewPixmapLabel; + QSize m_previewPixmapLabelSizeWhenUpdatedPixmap; + QPixmap m_shrunkenDocumentPixmap; + + QGridLayout *m_gridLayout; + int m_gridNumRows; +}; + + +#endif // __kp_tool_preview_dialog_h__ diff --git a/kolourpaint/tools/kptoolrectangle.cpp b/kolourpaint/tools/kptoolrectangle.cpp new file mode 100644 index 00000000..275db667 --- /dev/null +++ b/kolourpaint/tools/kptoolrectangle.cpp @@ -0,0 +1,638 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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_RECTANGLE 0 + +#include <qbitmap.h> +#include <qcursor.h> +#include <qevent.h> +#include <qpainter.h> + +#include <kdebug.h> +#include <klocale.h> + +#include <kpcolor.h> +#include <kpcommandhistory.h> +#include <kpdefs.h> +#include <kpdocument.h> +#include <kpmainwindow.h> +#include <kppixmapfx.h> +#include <kptemppixmap.h> +#include <kptoolrectangle.h> +#include <kptooltoolbar.h> +#include <kptoolwidgetfillstyle.h> +#include <kptoolwidgetlinewidth.h> +#include <kpview.h> +#include <kpviewmanager.h> + +static QPixmap pixmap (const kpToolRectangle::Mode mode, + kpDocument *document, const QRect &rect, + const QPoint &startPoint, const QPoint &endPoint, + const QPen &pen, const QPen &maskPen, + const QBrush &brush, const QBrush &maskBrush) +{ + QPixmap pixmap = document->getPixmapAt (rect); + QBitmap maskBitmap; + + QPainter painter, maskPainter; + +#if DEBUG_KP_TOOL_RECTANGLE && 1 + kdDebug () << "pixmap: rect=" << rect + << " startPoint=" << startPoint + << " endPoint=" << endPoint + << endl; + kdDebug () << "\tm: p=" << (maskPen.style () != Qt::NoPen) + << " b=" << (maskBrush.style () != Qt::NoBrush) + << " o: p=" << (pen.style () != Qt::NoPen) + << " b=" << (brush.style () != Qt::NoBrush) + << endl; + kdDebug () << "\tmaskPen.color()=" << (int *) maskPen.color ().rgb () + << " transparent=" << (int *) Qt::color0.rgb ()/*transparent*/ + << endl; +#endif + + if (pixmap.mask () || + (maskPen.style () != Qt::NoPen && + maskPen.color () == Qt::color0/*transparent*/) || + (maskBrush.style () != Qt::NoBrush && + maskBrush.color () == Qt::color0/*transparent*/)) + { + maskBitmap = kpPixmapFX::getNonNullMask (pixmap); + maskPainter.begin (&maskBitmap); + maskPainter.setPen (maskPen); + maskPainter.setBrush (maskBrush); + } + + if (pen.style () != Qt::NoPen || + brush.style () != Qt::NoBrush) + { + painter.begin (&pixmap); + painter.setPen (pen); + painter.setBrush (brush); + } + +#define PAINTER_CALL(cmd) \ +{ \ + if (painter.isActive ()) \ + painter . cmd ; \ + \ + if (maskPainter.isActive ()) \ + maskPainter . cmd ; \ +} + + if (startPoint != endPoint) + { + #if DEBUG_KP_TOOL_RECTANGLE && 1 + kdDebug () << "\tdraw shape" << endl; + #endif + + // TODO: Rectangle of pen width 1, height 1 and width X is rendered + // as width X - 1. + switch (mode) + { + case kpToolRectangle::Rectangle: + PAINTER_CALL (drawRect (QRect (startPoint - rect.topLeft (), endPoint - rect.topLeft ()))); + break; + case kpToolRectangle::RoundedRectangle: + PAINTER_CALL (drawRoundRect (QRect (startPoint - rect.topLeft (), endPoint - rect.topLeft ()))); + break; + case kpToolRectangle::Ellipse: + PAINTER_CALL (drawEllipse (QRect (startPoint - rect.topLeft (), endPoint - rect.topLeft ()))); + break; + default: + kdError () << "kptoolrectangle.cpp::pixmap() passed unknown mode: " << int (mode) << endl; + break; + } + } + else + { + #if DEBUG_KP_TOOL_RECTANGLE && 1 + kdDebug () << "\tstartPoint == endPoint" << endl; + #endif + // SYNC: Work around Qt bug: can't draw 1x1 rectangle + // Not strictly correct for border width > 1 + // but better than not drawing at all + PAINTER_CALL (drawPoint (startPoint - rect.topLeft ())); + } +#undef PAINTER_CALL + + if (painter.isActive ()) + painter.end (); + + if (maskPainter.isActive ()) + maskPainter.end (); + + if (!maskBitmap.isNull ()) + pixmap.setMask (maskBitmap); + + return pixmap; +} + + +/* + * kpToolRectangle + */ + +kpToolRectangle::kpToolRectangle (Mode mode, + const QString &text, + const QString &description, + int key, + kpMainWindow *mainWindow, + const char *name) + : kpTool (text, description, key, mainWindow, name), + m_mode (mode), + m_toolWidgetLineWidth (0), + m_toolWidgetFillStyle (0) +{ +} + +kpToolRectangle::kpToolRectangle (kpMainWindow *mainWindow) + : kpTool (i18n ("Rectangle"), i18n ("Draws rectangles and squares"), + Qt::Key_R, + mainWindow, "tool_rectangle"), + m_mode (Rectangle), + m_toolWidgetLineWidth (0), + m_toolWidgetFillStyle (0) +{ +} + +kpToolRectangle::~kpToolRectangle () +{ +} + +void kpToolRectangle::setMode (Mode mode) +{ + m_mode = mode; +} + + +// private +void kpToolRectangle::updatePens () +{ + for (int i = 0; i < 2; i++) + updatePen (i); +} + +// private +void kpToolRectangle::updateBrushes () +{ + for (int i = 0; i < 2; i++) + updateBrush (i); +} + +// virtual private slot +void kpToolRectangle::slotForegroundColorChanged (const kpColor &) +{ +#if DEBUG_KP_TOOL_RECTANGLE + kdDebug () << "kpToolRectangle::slotForegroundColorChanged()" << endl; +#endif + updatePen (0); + updateBrush (0); // brush may be in foreground color + updateBrush (1); +} + +// virtual private slot +void kpToolRectangle::slotBackgroundColorChanged (const kpColor &) +{ +#if DEBUG_KP_TOOL_RECTANGLE + kdDebug () << "kpToolRectangle::slotBackgroundColorChanged()" << endl; + kdDebug () << "\tm_toolWidgetFillStyle=" << m_toolWidgetFillStyle << endl; +#endif + updatePen (1); + updateBrush (0); + updateBrush (1); // brush may be in background color +} + +// private +void kpToolRectangle::updatePen (int mouseButton) +{ + QColor maskPenColor = color (mouseButton).maskColor (); + + if (!m_toolWidgetLineWidth) + { + if (color (mouseButton).isOpaque ()) + m_pen [mouseButton] = QPen (color (mouseButton).toQColor ()); + else + m_pen [mouseButton] = Qt::NoPen; + m_maskPen [mouseButton] = QPen (maskPenColor); + } + else + { + if (color (mouseButton).isOpaque ()) + { + m_pen [mouseButton] = QPen (color (mouseButton).toQColor (), + m_toolWidgetLineWidth->lineWidth (), + Qt::SolidLine); + } + else + m_pen [mouseButton] = Qt::NoPen; + m_maskPen [mouseButton] = QPen (maskPenColor, + m_toolWidgetLineWidth->lineWidth (), + Qt::SolidLine); + } +} + +void kpToolRectangle::updateBrush (int mouseButton) +{ +#if DEBUG_KP_TOOL_RECTANGLE + kdDebug () << "kpToolRectangle::brush () mouseButton=" << mouseButton + << " m_toolWidgetFillStyle=" << m_toolWidgetFillStyle + << endl; +#endif + if (m_toolWidgetFillStyle) + { + m_brush [mouseButton] = m_toolWidgetFillStyle->brush ( + color (mouseButton)/*foreground colour*/, + color (1 - mouseButton)/*background colour*/); + + m_maskBrush [mouseButton] = m_toolWidgetFillStyle->maskBrush ( + color (mouseButton)/*foreground colour*/, + color (1 - mouseButton)/*background colour*/); + } + else + { + m_brush [mouseButton] = Qt::NoBrush; + m_maskBrush [mouseButton] = Qt::NoBrush; + } +} + + +// private slot virtual +void kpToolRectangle::slotLineWidthChanged () +{ + updatePens (); + + if (hasBegunDraw ()) + updateShape (); +} + +// private slot virtual +void kpToolRectangle::slotFillStyleChanged () +{ + updateBrushes (); + + if (hasBegunDraw ()) + updateShape (); +} + + +// private +QString kpToolRectangle::haventBegunDrawUserMessage () const +{ + return i18n ("Drag to draw."); +} + +// virtual +void kpToolRectangle::begin () +{ +#if DEBUG_KP_TOOL_RECTANGLE + kdDebug () << "kpToolRectangle::begin ()" << endl; +#endif + + kpToolToolBar *tb = toolToolBar (); + +#if DEBUG_KP_TOOL_RECTANGLE + kdDebug () << "\ttoolToolBar=" << tb << endl; +#endif + + if (tb) + { + m_toolWidgetLineWidth = tb->toolWidgetLineWidth (); + connect (m_toolWidgetLineWidth, SIGNAL (lineWidthChanged (int)), + this, SLOT (slotLineWidthChanged ())); + m_toolWidgetLineWidth->show (); + + updatePens (); + + + m_toolWidgetFillStyle = tb->toolWidgetFillStyle (); + connect (m_toolWidgetFillStyle, SIGNAL (fillStyleChanged (kpToolWidgetFillStyle::FillStyle)), + this, SLOT (slotFillStyleChanged ())); + m_toolWidgetFillStyle->show (); + + updateBrushes (); + } + +#if DEBUG_KP_TOOL_RECTANGLE + kdDebug () << "\t\tm_toolWidgetFillStyle=" << m_toolWidgetFillStyle << endl; +#endif + + viewManager ()->setCursor (QCursor (CrossCursor)); + + setUserMessage (haventBegunDrawUserMessage ()); +} + +// virtual +void kpToolRectangle::end () +{ +#if DEBUG_KP_TOOL_RECTANGLE + kdDebug () << "kpToolRectangle::end ()" << endl; +#endif + + if (m_toolWidgetLineWidth) + { + disconnect (m_toolWidgetLineWidth, SIGNAL (lineWidthChanged (int)), + this, SLOT (slotLineWidthChanged ())); + m_toolWidgetLineWidth = 0; + } + + if (m_toolWidgetFillStyle) + { + disconnect (m_toolWidgetFillStyle, SIGNAL (fillStyleChanged (kpToolWidgetFillStyle::FillStyle)), + this, SLOT (slotFillStyleChanged ())); + m_toolWidgetFillStyle = 0; + } + + viewManager ()->unsetCursor (); +} + +void kpToolRectangle::applyModifiers () +{ + QRect rect = QRect (m_startPoint, m_currentPoint).normalize (); + +#if DEBUG_KP_TOOL_RECTANGLE + kdDebug () << "kpToolRectangle::applyModifiers(" << rect + << ") shift=" << m_shiftPressed + << " ctrl=" << m_controlPressed + << endl; +#endif + + // user wants to m_startPoint == centre + if (m_controlPressed) + { + int xdiff = kAbs (m_startPoint.x () - m_currentPoint.x ()); + int ydiff = kAbs (m_startPoint.y () - m_currentPoint.y ()); + rect = QRect (m_startPoint.x () - xdiff, m_startPoint.y () - ydiff, + xdiff * 2 + 1, ydiff * 2 + 1); + } + + // user wants major axis == minor axis: + // rectangle --> square + // rounded rectangle --> rounded square + // ellipse --> circle + if (m_shiftPressed) + { + if (!m_controlPressed) + { + if (rect.width () < rect.height ()) + { + if (m_startPoint.y () == rect.y ()) + rect.setHeight (rect.width ()); + else + rect.setY (rect.bottom () - rect.width () + 1); + } + else + { + if (m_startPoint.x () == rect.x ()) + rect.setWidth (rect.height ()); + else + rect.setX (rect.right () - rect.height () + 1); + } + } + // have to maintain the centre + else + { + if (rect.width () < rect.height ()) + { + QPoint center = rect.center (); + rect.setHeight (rect.width ()); + rect.moveCenter (center); + } + else + { + QPoint center = rect.center (); + rect.setWidth (rect.height ()); + rect.moveCenter (center); + } + } + } + + m_toolRectangleStartPoint = rect.topLeft (); + m_toolRectangleEndPoint = rect.bottomRight (); + + m_toolRectangleRectWithoutLineWidth = rect; + m_toolRectangleRect = kpTool::neededRect (rect, QMAX (m_pen [m_mouseButton].width (), + m_maskPen [m_mouseButton].width ())); +} + +void kpToolRectangle::beginDraw () +{ + setUserMessage (cancelUserMessage ()); +} + +void kpToolRectangle::updateShape () +{ + viewManager ()->setFastUpdates (); + + QPixmap newPixmap = pixmap (m_mode, document (), m_toolRectangleRect, + m_toolRectangleStartPoint, m_toolRectangleEndPoint, + m_pen [m_mouseButton], m_maskPen [m_mouseButton], + m_brush [m_mouseButton], m_maskBrush [m_mouseButton]); + kpTempPixmap newTempPixmap (false/*always display*/, + kpTempPixmap::SetPixmap/*render mode*/, + m_toolRectangleRect.topLeft (), + newPixmap); + viewManager ()->setTempPixmap (newTempPixmap); + + viewManager ()->restoreFastUpdates (); +} + +void kpToolRectangle::draw (const QPoint &, const QPoint &, const QRect &) +{ + applyModifiers (); + + + updateShape (); + + + // Recover the start and end points from the transformed & normalized m_toolRectangleRect + + // S. or S or SC or S == C + // .C C + if (m_currentPoint.x () >= m_startPoint.x () && + m_currentPoint.y () >= m_startPoint.y ()) + { + setUserShapePoints (m_toolRectangleRectWithoutLineWidth.topLeft (), + m_toolRectangleRectWithoutLineWidth.bottomRight ()); + } + // .C or C + // S. S + else if (m_currentPoint.x () >= m_startPoint.x () && + m_currentPoint.y () < m_startPoint.y ()) + { + setUserShapePoints (m_toolRectangleRectWithoutLineWidth.bottomLeft (), + m_toolRectangleRectWithoutLineWidth.topRight ()); + } + // .S or CS + // C. + else if (m_currentPoint.x () < m_startPoint.x () && + m_currentPoint.y () >= m_startPoint.y ()) + { + setUserShapePoints (m_toolRectangleRectWithoutLineWidth.topRight (), + m_toolRectangleRectWithoutLineWidth.bottomLeft ()); + } + // C. + // .S + else + { + setUserShapePoints (m_toolRectangleRectWithoutLineWidth.bottomRight (), + m_toolRectangleRectWithoutLineWidth.topLeft ()); + } +} + +void kpToolRectangle::cancelShape () +{ +#if 0 + endDraw (m_currentPoint, QRect (m_startPoint, m_currentPoint).normalize ()); + mainWindow ()->commandHistory ()->undo (); +#else + viewManager ()->invalidateTempPixmap (); +#endif + + setUserMessage (i18n ("Let go of all the mouse buttons.")); +} + +void kpToolRectangle::releasedAllButtons () +{ + setUserMessage (haventBegunDrawUserMessage ()); +} + +void kpToolRectangle::endDraw (const QPoint &, const QRect &) +{ + applyModifiers (); + + // TODO: flicker + viewManager ()->invalidateTempPixmap (); + + mainWindow ()->commandHistory ()->addCommand ( + new kpToolRectangleCommand + (m_mode, + m_pen [m_mouseButton], m_maskPen [m_mouseButton], + m_brush [m_mouseButton], m_maskBrush [m_mouseButton], + m_toolRectangleRect, m_toolRectangleStartPoint, m_toolRectangleEndPoint, + mainWindow ())); + + setUserMessage (haventBegunDrawUserMessage ()); +} + + +/* + * kpToolRectangleCommand + */ + +kpToolRectangleCommand::kpToolRectangleCommand (kpToolRectangle::Mode mode, + const QPen &pen, const QPen &maskPen, + const QBrush &brush, const QBrush &maskBrush, + const QRect &rect, + const QPoint &startPoint, const QPoint &endPoint, + kpMainWindow *mainWindow) + : kpCommand (mainWindow), + m_mode (mode), + m_pen (pen), m_maskPen (maskPen), + m_brush (brush), m_maskBrush (maskBrush), + m_rect (rect), + m_startPoint (startPoint), + m_endPoint (endPoint), + m_oldPixmapPtr (0) +{ +} + +kpToolRectangleCommand::~kpToolRectangleCommand () +{ + delete m_oldPixmapPtr; +} + + +// public virtual [base kpCommand] +QString kpToolRectangleCommand::name () const +{ + switch (m_mode) + { + case kpToolRectangle::Rectangle: + return i18n ("Rectangle"); + case kpToolRectangle::RoundedRectangle: + return i18n ("Rounded Rectangle"); + case kpToolRectangle::Ellipse: + return i18n ("Ellipse"); + default: + kdError () << "kpToolRectangleCommand::name() passed unknown mode: " << int (m_mode) << endl; + return QString::null; + } +} + + +// public virtual [base kpCommand] +int kpToolRectangleCommand::size () const +{ + return kpPixmapFX::pixmapSize (m_oldPixmapPtr); +} + + +// public virtual [base kpCommand] +void kpToolRectangleCommand::execute () +{ + kpDocument *doc = document (); + if (!doc) + return; + + // store Undo info + if (!m_oldPixmapPtr) + { + // OPT: I can do better with no brush + m_oldPixmapPtr = new QPixmap (); + *m_oldPixmapPtr = doc->getPixmapAt (m_rect); + } + else + kdError () << "kpToolRectangleCommand::execute() m_oldPixmapPtr not null" << endl; + + doc->setPixmapAt (pixmap (m_mode, doc, + m_rect, m_startPoint, m_endPoint, + m_pen, m_maskPen, + m_brush, m_maskBrush), + m_rect.topLeft ()); +} + +// public virtual [base kpCommand] +void kpToolRectangleCommand::unexecute () +{ + kpDocument *doc = document (); + if (!doc) + return; + + if (m_oldPixmapPtr) + { + doc->setPixmapAt (*m_oldPixmapPtr, m_rect.topLeft ()); + + delete m_oldPixmapPtr; + m_oldPixmapPtr = 0; + } + else + kdError () << "kpToolRectangleCommand::unexecute() m_oldPixmapPtr null" << endl; +} + +#include <kptoolrectangle.moc> diff --git a/kolourpaint/tools/kptoolrectangle.h b/kolourpaint/tools/kptoolrectangle.h new file mode 100644 index 00000000..0fcf5ff4 --- /dev/null +++ b/kolourpaint/tools/kptoolrectangle.h @@ -0,0 +1,142 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 __kptoolrectangle_h__ +#define __kptoolrectangle_h__ + +#include <qbrush.h> +#include <qpen.h> +#include <qpixmap.h> +#include <qpoint.h> +#include <qrect.h> + +#include <kpcommandhistory.h> + +#include <kptool.h> + +class QString; + +class kpColor; +class kpMainWindow; +class kpToolWidgetFillStyle; +class kpToolWidgetLineWidth; +class kpViewManager; + +class kpToolRectangle : public kpTool +{ +Q_OBJECT + +public: + // it turns out that these shapes are all really the same thing + // (same options, same feel) - the only real difference is the + // drawing functions (a one line change) + enum Mode {Rectangle, RoundedRectangle, Ellipse}; + + kpToolRectangle (Mode mode, + const QString &text, const QString &description, + int key, + kpMainWindow *mainWindow, + const char *name); + kpToolRectangle (kpMainWindow *); + virtual ~kpToolRectangle (); + + void setMode (Mode mode); + + virtual bool careAboutModifierState () const { return true; } + +private: + QString haventBegunDrawUserMessage () const; + +public: + virtual void begin (); + virtual void end (); + + virtual void beginDraw (); +private: + void updateShape (); +public: + virtual void draw (const QPoint &, const QPoint &, const QRect &); + virtual void cancelShape (); + virtual void releasedAllButtons (); + virtual void endDraw (const QPoint &, const QRect &); + +private slots: + void updatePens (); + void updateBrushes (); + + virtual void slotForegroundColorChanged (const kpColor &); + virtual void slotBackgroundColorChanged (const kpColor &); + + virtual void slotLineWidthChanged (); + virtual void slotFillStyleChanged (); + +private: + Mode m_mode; + + kpToolWidgetLineWidth *m_toolWidgetLineWidth; + kpToolWidgetFillStyle *m_toolWidgetFillStyle; + + void updatePen (int mouseButton); + QPen m_pen [2], m_maskPen [2]; + + void updateBrush (int mouseButton); + QBrush m_brush [2], m_maskBrush [2]; + + void applyModifiers (); + QPoint m_toolRectangleStartPoint, m_toolRectangleEndPoint; + QRect m_toolRectangleRectWithoutLineWidth, m_toolRectangleRect; +}; + +class kpToolRectangleCommand : public kpCommand +{ +public: + kpToolRectangleCommand (kpToolRectangle::Mode mode, + const QPen &pen, const QPen &maskPen, + const QBrush &brush, const QBrush &maskBrush, + const QRect &rect, + const QPoint &startPoint, const QPoint &endPoint, + kpMainWindow *mainWindow); + virtual ~kpToolRectangleCommand (); + + virtual QString name () const; + + virtual int size () const; + + virtual void execute (); + virtual void unexecute (); + +private: + kpToolRectangle::Mode m_mode; + QPen m_pen, m_maskPen; + QBrush m_brush, m_maskBrush; + QRect m_rect; + QPoint m_startPoint, m_endPoint; + QPixmap *m_oldPixmapPtr; +}; + +#endif // __kptoolrectangle_h__ diff --git a/kolourpaint/tools/kptoolrectselection.cpp b/kolourpaint/tools/kptoolrectselection.cpp new file mode 100644 index 00000000..3726cbfe --- /dev/null +++ b/kolourpaint/tools/kptoolrectselection.cpp @@ -0,0 +1,46 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <kptoolrectselection.h> + +#include <klocale.h> + + +kpToolRectSelection::kpToolRectSelection (kpMainWindow *mainWindow) + : kpToolSelection (Rectangle, + i18n ("Selection (Rectangular)"), + i18n ("Makes a rectangular selection"), + Qt::Key_S, + mainWindow, "tool_rect_selection") +{ +} + +kpToolRectSelection::~kpToolRectSelection () +{ +} + diff --git a/kolourpaint/tools/kptoolrectselection.h b/kolourpaint/tools/kptoolrectselection.h new file mode 100644 index 00000000..0d66b7a5 --- /dev/null +++ b/kolourpaint/tools/kptoolrectselection.h @@ -0,0 +1,43 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 __kptoolrectselection_h__ +#define __kptoolrectselection_h__ + +#include <kptoolselection.h> + +class kpMainWindow; + +class kpToolRectSelection : public kpToolSelection +{ +public: + kpToolRectSelection (kpMainWindow *); + virtual ~kpToolRectSelection (); +}; + +#endif // __kptoolrectselection_h__ diff --git a/kolourpaint/tools/kptoolresizescale.cpp b/kolourpaint/tools/kptoolresizescale.cpp new file mode 100644 index 00000000..ce9c9059 --- /dev/null +++ b/kolourpaint/tools/kptoolresizescale.cpp @@ -0,0 +1,1222 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <kptoolresizescale.h> + +#include <math.h> + +#include <qaccel.h> +#include <qapplication.h> +#include <qbuttongroup.h> +#include <qcheckbox.h> +#include <qgroupbox.h> +#include <qhbox.h> +#include <qlabel.h> +#include <qlayout.h> +#include <qpoint.h> +#include <qpointarray.h> +#include <qpushbutton.h> +#include <qrect.h> +#include <qsize.h> +#include <qtoolbutton.h> +#include <qwhatsthis.h> +#include <qwmatrix.h> + +#include <kapplication.h> +#include <kcombobox.h> +#include <kconfig.h> +#include <kdebug.h> +#include <kglobal.h> +#include <kiconeffect.h> +#include <kiconloader.h> +#include <klocale.h> +#include <knuminput.h> + +#include <kpdefs.h> +#include <kpdocument.h> +#include <kpmainwindow.h> +#include <kppixmapfx.h> +#include <kpselection.h> +#include <kptool.h> + + +/* + * kpToolResizeScaleCommand + */ + +kpToolResizeScaleCommand::kpToolResizeScaleCommand (bool actOnSelection, + int newWidth, int newHeight, + Type type, + kpMainWindow *mainWindow) + : kpCommand (mainWindow), + m_actOnSelection (actOnSelection), + m_type (type), + m_backgroundColor (mainWindow ? mainWindow->backgroundColor () : kpColor::invalid), + m_oldSelection (0) +{ + kpDocument *doc = document (); + + m_oldWidth = doc->width (m_actOnSelection); + m_oldHeight = doc->height (m_actOnSelection); + + m_actOnTextSelection = (m_actOnSelection && + doc && doc->selection () && + doc->selection ()->isText ()); + + 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 ()->pixmap ()); +} + +kpToolResizeScaleCommand::~kpToolResizeScaleCommand () +{ + delete m_oldSelection; +} + + +// public virtual [base kpCommand] +QString kpToolResizeScaleCommand::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"); + else 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 QString::null; +} + +// public virtual [base kpCommand] +int kpToolResizeScaleCommand::size () const +{ + return kpPixmapFX::pixmapSize (m_oldPixmap) + + kpPixmapFX::pixmapSize (m_oldRightPixmap) + + kpPixmapFX::pixmapSize (m_oldBottomPixmap) + + (m_oldSelection ? m_oldSelection->size () : 0); +} + + +// public +int kpToolResizeScaleCommand::newWidth () const +{ + return m_newWidth; +} + +// public +void kpToolResizeScaleCommand::setNewWidth (int width) +{ + resize (width, newHeight ()); +} + + +// public +int kpToolResizeScaleCommand::newHeight () const +{ + return m_newHeight; +} + +// public +void kpToolResizeScaleCommand::setNewHeight (int height) +{ + resize (newWidth (), height); +} + + +// public +QSize kpToolResizeScaleCommand::newSize () const +{ + return QSize (newWidth (), newHeight ()); +} + +// public virtual +void kpToolResizeScaleCommand::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 kpToolResizeScaleCommand::scaleSelectionWithImage () const +{ + return m_scaleSelectionWithImage; +} + + +// private +void kpToolResizeScaleCommand::scaleSelectionRegionWithDocument () +{ +#if DEBUG_KP_TOOL_RESIZE_SCALE_COMMAND + kdDebug () << "kpToolResizeScaleCommand::scaleSelectionRegionWithDocument" + << endl; +#endif + + if (!m_oldSelection) + { + kdError () << "kpToolResizeScaleCommand::scaleSelectionRegionWithDocument()" + << " without old sel" << endl; + return; + } + + if (m_oldSelection->pixmap ()) + { + kdError () << "kpToolResizeScaleCommand::scaleSelectionRegionWithDocument()" + << " old sel has pixmap" << endl; + return; + } + + + const double horizScale = double (m_newWidth) / double (m_oldWidth); + const double vertScale = double (m_newHeight) / double (m_oldHeight); + + const int newX = (int) (m_oldSelection->x () * horizScale); + const int newY = (int) (m_oldSelection->y () * vertScale); + + + QPointArray currentPoints = m_oldSelection->points (); + currentPoints.detach (); + + currentPoints.translate (-currentPoints.boundingRect ().x (), + -currentPoints.boundingRect ().y ()); + + // TODO: refactor into kpPixmapFX + QWMatrix scaleMatrix; + scaleMatrix.scale (horizScale, vertScale); + currentPoints = scaleMatrix.map (currentPoints); + + currentPoints.translate ( + -currentPoints.boundingRect ().x () + newX, + -currentPoints.boundingRect ().y () + newY); + + document ()->setSelection (kpSelection (currentPoints, QPixmap (), + m_oldSelection->transparency ())); + + + if (m_mainWindow->tool ()) + m_mainWindow->tool ()->somethingBelowTheCursorChanged (); +} + + +// public virtual [base kpCommand] +void kpToolResizeScaleCommand::execute () +{ +#if DEBUG_KP_TOOL_RESIZE_SCALE_COMMAND + kdDebug () << "kpToolResizeScaleCommand::execute() type=" + << (int) m_type + << " oldWidth=" << m_oldWidth + << " oldHeight=" << m_oldHeight + << " newWidth=" << m_newWidth + << " newHeight=" << m_newHeight + << endl; +#endif + + if (m_oldWidth == m_newWidth && m_oldHeight == m_newHeight) + return; + + if (m_type == Resize) + { + if (m_actOnSelection) + { + if (!m_actOnTextSelection) + { + kdError () << "kpToolResizeScaleCommand::execute() resizing sel doesn't make sense" << endl; + return; + } + else + { + QApplication::setOverrideCursor (Qt::waitCursor); + document ()->selection ()->textResize (m_newWidth, m_newHeight); + + if (m_mainWindow->tool ()) + m_mainWindow->tool ()->somethingBelowTheCursorChanged (); + + QApplication::restoreOverrideCursor (); + } + } + else + { + QApplication::setOverrideCursor (Qt::waitCursor); + + + if (m_newWidth < m_oldWidth) + { + m_oldRightPixmap = kpPixmapFX::getPixmapAt ( + *document ()->pixmap (), + QRect (m_newWidth, 0, + m_oldWidth - m_newWidth, m_oldHeight)); + } + + if (m_newHeight < m_oldHeight) + { + m_oldBottomPixmap = kpPixmapFX::getPixmapAt ( + *document ()->pixmap (), + QRect (0, m_newHeight, + m_newWidth, m_oldHeight - m_newHeight)); + } + + document ()->resize (m_newWidth, m_newHeight, m_backgroundColor); + + + QApplication::restoreOverrideCursor (); + } + } + else + { + QApplication::setOverrideCursor (Qt::waitCursor); + + + QPixmap oldPixmap = *document ()->pixmap (m_actOnSelection); + + if (!m_isLosslessScale) + m_oldPixmap = oldPixmap; + + QPixmap newPixmap = kpPixmapFX::scale (oldPixmap, m_newWidth, m_newHeight, + m_type == SmoothScale); + + + if (!m_oldSelection && document ()->selection ()) + { + // Save sel border + m_oldSelection = new kpSelection (*document ()->selection ()); + m_oldSelection->setPixmap (QPixmap ()); + } + + if (m_actOnSelection) + { + QRect newRect = QRect (m_oldSelection->x (), m_oldSelection->y (), + newPixmap.width (), newPixmap.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) + document ()->setSelection ( + kpSelection (kpSelection::Rectangle, newRect, newPixmap, + m_oldSelection->transparency ())); + + if (m_mainWindow->tool ()) + m_mainWindow->tool ()->somethingBelowTheCursorChanged (); + } + else + { + document ()->setPixmap (newPixmap); + + if (m_scaleSelectionWithImage) + { + scaleSelectionRegionWithDocument (); + } + } + + + QApplication::restoreOverrideCursor (); + } +} + +// public virtual [base kpCommand] +void kpToolResizeScaleCommand::unexecute () +{ +#if DEBUG_KP_TOOL_RESIZE_SCALE_COMMAND + kdDebug () << "kpToolResizeScaleCommand::unexecute() type=" + << m_type << endl; +#endif + + if (m_oldWidth == m_newWidth && m_oldHeight == m_newHeight) + return; + + kpDocument *doc = document (); + if (!doc) + return; + + if (m_type == Resize) + { + if (m_actOnSelection) + { + if (!m_actOnTextSelection) + { + kdError () << "kpToolResizeScaleCommand::unexecute() resizing sel doesn't make sense" << endl; + return; + } + else + { + QApplication::setOverrideCursor (Qt::waitCursor); + document ()->selection ()->textResize (m_oldWidth, m_oldHeight); + + if (m_mainWindow->tool ()) + m_mainWindow->tool ()->somethingBelowTheCursorChanged (); + + QApplication::restoreOverrideCursor (); + } + } + else + { + QApplication::setOverrideCursor (Qt::waitCursor); + + + QPixmap newPixmap (m_oldWidth, m_oldHeight); + + kpPixmapFX::setPixmapAt (&newPixmap, QPoint (0, 0), + *doc->pixmap ()); + + if (m_newWidth < m_oldWidth) + { + kpPixmapFX::setPixmapAt (&newPixmap, + QPoint (m_newWidth, 0), + m_oldRightPixmap); + } + + if (m_newHeight < m_oldHeight) + { + kpPixmapFX::setPixmapAt (&newPixmap, + QPoint (0, m_newHeight), + m_oldBottomPixmap); + } + + doc->setPixmap (newPixmap); + + + QApplication::restoreOverrideCursor (); + } + } + else + { + QApplication::setOverrideCursor (Qt::waitCursor); + + + QPixmap oldPixmap; + + if (!m_isLosslessScale) + oldPixmap = m_oldPixmap; + else + oldPixmap = kpPixmapFX::scale (*doc->pixmap (m_actOnSelection), + m_oldWidth, m_oldHeight); + + + if (m_actOnSelection) + { + kpSelection oldSelection = *m_oldSelection; + oldSelection.setPixmap (oldPixmap); + doc->setSelection (oldSelection); + + if (m_mainWindow->tool ()) + m_mainWindow->tool ()->somethingBelowTheCursorChanged (); + } + else + { + doc->setPixmap (oldPixmap); + + if (m_scaleSelectionWithImage) + { + doc->setSelection (*m_oldSelection); + + if (m_mainWindow->tool ()) + m_mainWindow->tool ()->somethingBelowTheCursorChanged (); + } + } + + + QApplication::restoreOverrideCursor (); + } +} + + +/* + * kpToolResizeScaleDialog + */ + +#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--; \ +} + + +// private static +kpToolResizeScaleCommand::Type kpToolResizeScaleDialog::s_lastType = + kpToolResizeScaleCommand::Resize; + +// private static +double kpToolResizeScaleDialog::s_lastPercentWidth = 100, + kpToolResizeScaleDialog::s_lastPercentHeight = 100; + + +kpToolResizeScaleDialog::kpToolResizeScaleDialog (kpMainWindow *mainWindow) + : KDialogBase ((QWidget *) mainWindow, + 0/*name*/, + true/*modal*/, + i18n ("Resize / Scale")/*caption*/, + KDialogBase::Ok | KDialogBase::Cancel), + m_mainWindow (mainWindow), + m_ignoreKeepAspectRatio (0) +{ + // Using the percentage from last time become too confusing so disable for now + s_lastPercentWidth = 100, s_lastPercentHeight = 100; + + + QWidget *baseWidget = new QWidget (this); + setMainWidget (baseWidget); + + + createActOnBox (baseWidget); + createOperationGroupBox (baseWidget); + createDimensionsGroupBox (baseWidget); + + + QVBoxLayout *baseLayout = new QVBoxLayout (baseWidget, 0/*margin*/, spacingHint ()); + baseLayout->addWidget (m_actOnBox); + baseLayout->addWidget (m_operationGroupBox); + baseLayout->addWidget (m_dimensionsGroupBox); + + + slotActOnChanged (); + + m_newWidthInput->setEditFocus (); + + //enableButtonOK (!isNoOp ()); +} + +kpToolResizeScaleDialog::~kpToolResizeScaleDialog () +{ +} + + +// private +kpDocument *kpToolResizeScaleDialog::document () const +{ + return m_mainWindow ? m_mainWindow->document () : 0; +} + +// private +kpSelection *kpToolResizeScaleDialog::selection () const +{ + return document () ? document ()->selection () : 0; +} + + +// private +void kpToolResizeScaleDialog::createActOnBox (QWidget *baseWidget) +{ + m_actOnBox = new QHBox (baseWidget); + m_actOnBox->setSpacing (spacingHint () * 2); + + + m_actOnLabel = new QLabel (i18n ("Ac&t on:"), m_actOnBox); + m_actOnCombo = new KComboBox (m_actOnBox); + + + m_actOnLabel->setBuddy (m_actOnCombo); + + m_actOnCombo->insertItem (i18n ("Entire Image"), Image); + if (selection ()) + { + QString selName = i18n ("Selection"); + + if (selection ()->isText ()) + selName = i18n ("Text Box"); + + m_actOnCombo->insertItem (selName, Selection); + m_actOnCombo->setCurrentItem (Selection); + } + else + { + m_actOnLabel->setEnabled (false); + m_actOnCombo->setEnabled (false); + } + + + m_actOnBox->setStretchFactor (m_actOnCombo, 1); + + + connect (m_actOnCombo, SIGNAL (activated (int)), + this, SLOT (slotActOnChanged ())); +} + + +static QIconSet toolButtonIconSet (const QString &iconName) +{ + QIconSet iconSet = UserIconSet (iconName); + + + // No "disabled" pixmap is generated by UserIconSet() so generate it + // ourselves: + + QPixmap disabledIcon = KGlobal::iconLoader ()->iconEffect ()->apply ( + UserIcon (iconName), + KIcon::Toolbar, KIcon::DisabledState); + + const QPixmap iconSetNormalIcon = iconSet.pixmap (QIconSet::Small, + QIconSet::Normal); + + // I bet past or future versions of KIconEffect::apply() resize the + // disabled icon if we claim it's in group KIcon::Toolbar. So resize + // it to match the QIconSet::Normal icon, just in case. + disabledIcon = kpPixmapFX::scale (disabledIcon, + iconSetNormalIcon.width (), + iconSetNormalIcon.height (), + true/*smooth scale*/); + + + iconSet.setPixmap (disabledIcon, + QIconSet::Small, QIconSet::Disabled); + + return iconSet; +} + +static void toolButtonSetLook (QToolButton *button, + const QString &iconName, + const QString &name) +{ + button->setIconSet (toolButtonIconSet (iconName)); + button->setUsesTextLabel (true); + button->setTextLabel (name, false/*no tooltip*/); + button->setAccel (QAccel::shortcutKey (name)); + button->setFocusPolicy (QWidget::StrongFocus); + button->setToggleButton (true); +} + + +// private +void kpToolResizeScaleDialog::createOperationGroupBox (QWidget *baseWidget) +{ + m_operationGroupBox = new QGroupBox (i18n ("Operation"), baseWidget); + QWhatsThis::add (m_operationGroupBox, + i18n ("<qt>" + "<ul>" + "<li><b>Resize</b>: 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.</li>" + + "<li><b>Scale</b>: The picture will be expanded" + " by duplicating pixels or squashed by dropping pixels.</li>" + + "<li><b>Smooth Scale</b>: This is the same as" + " <i>Scale</i> except that it blends neighboring" + " pixels to produce a smoother looking picture.</li>" + "</ul>" + "</qt>")); + + // TODO: ALT+R doesn't select the button. + m_resizeButton = new QToolButton (m_operationGroupBox); + toolButtonSetLook (m_resizeButton, + QString::fromLatin1 ("resize"), + i18n ("&Resize")); + + m_scaleButton = new QToolButton (m_operationGroupBox); + toolButtonSetLook (m_scaleButton, + QString::fromLatin1 ("scale"), + i18n ("&Scale")); + + m_smoothScaleButton = new QToolButton (m_operationGroupBox); + toolButtonSetLook (m_smoothScaleButton, + QString::fromLatin1 ("smooth_scale"), + i18n ("S&mooth Scale")); + + + //m_resizeLabel = new QLabel (i18n ("&Resize"), m_operationGroupBox); + //m_scaleLabel = new QLabel (i18n ("&Scale"), m_operationGroupBox); + //m_smoothScaleLabel = new QLabel (i18n ("S&mooth scale"), m_operationGroupBox); + + + //m_resizeLabel->setAlignment (m_resizeLabel->alignment () | Qt::ShowPrefix); + //m_scaleLabel->setAlignment (m_scaleLabel->alignment () | Qt::ShowPrefix); + //m_smoothScaleLabel->setAlignment (m_smoothScaleLabel->alignment () | Qt::ShowPrefix); + + + QButtonGroup *resizeScaleButtonGroup = new QButtonGroup (baseWidget); + resizeScaleButtonGroup->setExclusive (true); + resizeScaleButtonGroup->hide (); + + resizeScaleButtonGroup->insert (m_resizeButton); + resizeScaleButtonGroup->insert (m_scaleButton); + resizeScaleButtonGroup->insert (m_smoothScaleButton); + + + QGridLayout *operationLayout = new QGridLayout (m_operationGroupBox, + 1, 2, + marginHint () * 2/*don't overlap groupbox title*/, + spacingHint ()); + + operationLayout->addWidget (m_resizeButton, 0, 0, Qt::AlignCenter); + //operationLayout->addWidget (m_resizeLabel, 1, 0, Qt::AlignCenter); + + operationLayout->addWidget (m_scaleButton, 0, 1, Qt::AlignCenter); + //operationLayout->addWidget (m_scaleLabel, 1, 1, Qt::AlignCenter); + + operationLayout->addWidget (m_smoothScaleButton, 0, 2, Qt::AlignCenter); + //operationLayout->addWidget (m_smoothScaleLabel, 1, 2, Qt::AlignCenter); + + + connect (m_resizeButton, SIGNAL (toggled (bool)), + this, SLOT (slotTypeChanged ())); + connect (m_scaleButton, SIGNAL (toggled (bool)), + this, SLOT (slotTypeChanged ())); + connect (m_smoothScaleButton, SIGNAL (toggled (bool)), + this, SLOT (slotTypeChanged ())); +} + +// private +void kpToolResizeScaleDialog::createDimensionsGroupBox (QWidget *baseWidget) +{ + m_dimensionsGroupBox = new QGroupBox (i18n ("Dimensions"), baseWidget); + + QLabel *widthLabel = new QLabel (i18n ("Width:"), m_dimensionsGroupBox); + widthLabel->setAlignment (widthLabel->alignment () | Qt::AlignHCenter); + QLabel *heightLabel = new QLabel (i18n ("Height:"), m_dimensionsGroupBox); + heightLabel->setAlignment (heightLabel->alignment () | Qt::AlignHCenter); + + QLabel *originalLabel = new QLabel (i18n ("Original:"), m_dimensionsGroupBox); + m_originalWidthInput = new KIntNumInput ( + document ()->width ((bool) selection ()), + m_dimensionsGroupBox); + QLabel *xLabel0 = new QLabel (i18n ("x"), m_dimensionsGroupBox); + m_originalHeightInput = new KIntNumInput ( + document ()->height ((bool) selection ()), + m_dimensionsGroupBox); + + QLabel *newLabel = new QLabel (i18n ("&New:"), m_dimensionsGroupBox); + m_newWidthInput = new KIntNumInput (m_dimensionsGroupBox); + QLabel *xLabel1 = new QLabel (i18n ("x"), m_dimensionsGroupBox); + m_newHeightInput = new KIntNumInput (m_dimensionsGroupBox); + + QLabel *percentLabel = new QLabel (i18n ("&Percent:"), m_dimensionsGroupBox); + m_percentWidthInput = new KDoubleNumInput (0.01/*lower*/, 1000000/*upper*/, + 100/*value*/, 1/*step*/, + 2/*precision*/, + m_dimensionsGroupBox); + m_percentWidthInput->setSuffix (i18n ("%")); + QLabel *xLabel2 = new QLabel (i18n ("x"), m_dimensionsGroupBox); + m_percentHeightInput = new KDoubleNumInput (0.01/*lower*/, 1000000/*upper*/, + 100/*value*/, 1/*step*/, + 2/*precision*/, + m_dimensionsGroupBox); + m_percentHeightInput->setSuffix (i18n ("%")); + + m_keepAspectRatioCheckBox = new QCheckBox (i18n ("Keep &aspect ratio"), + m_dimensionsGroupBox); + + + m_originalWidthInput->setEnabled (false); + m_originalHeightInput->setEnabled (false); + originalLabel->setBuddy (m_originalWidthInput); + newLabel->setBuddy (m_newWidthInput); + m_percentWidthInput->setValue (s_lastPercentWidth); + m_percentHeightInput->setValue (s_lastPercentHeight); + percentLabel->setBuddy (m_percentWidthInput); + + + QGridLayout *dimensionsLayout = new QGridLayout (m_dimensionsGroupBox, + 5, 4, marginHint () * 2, spacingHint ()); + dimensionsLayout->setColStretch (1/*column*/, 1); + dimensionsLayout->setColStretch (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->addMultiCellWidget (m_keepAspectRatioCheckBox, 4, 4, 0, 3); + dimensionsLayout->setRowStretch (4/*row*/, 1); + dimensionsLayout->setRowSpacing (4/*row*/, dimensionsLayout->rowSpacing (4) * 2); + + + connect (m_newWidthInput, SIGNAL (valueChanged (int)), + this, SLOT (slotWidthChanged (int))); + connect (m_newHeightInput, SIGNAL (valueChanged (int)), + this, SLOT (slotHeightChanged (int))); + + connect (m_percentWidthInput, SIGNAL (valueChanged (double)), + this, SLOT (slotPercentWidthChanged (double))); + connect (m_percentHeightInput, SIGNAL (valueChanged (double)), + this, SLOT (slotPercentHeightChanged (double))); + + connect (m_keepAspectRatioCheckBox, SIGNAL (toggled (bool)), + this, SLOT (setKeepAspectRatio (bool))); +} + + +// private +void kpToolResizeScaleDialog::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 kpToolResizeScaleDialog::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 kpToolResizeScaleDialog::resizeEnabled () const +{ + return (!actOnSelection () || + (actOnSelection () && selection ()->isText ())); +} + +// private +bool kpToolResizeScaleDialog::scaleEnabled () const +{ + return (!(actOnSelection () && selection ()->isText ())); +} + +// private +bool kpToolResizeScaleDialog::smoothScaleEnabled () const +{ + return scaleEnabled (); +} + + +// public slot +void kpToolResizeScaleDialog::slotActOnChanged () +{ +#if DEBUG_KP_TOOL_RESIZE_SCALE_DIALOG && 1 + kdDebug () << "kpToolResizeScaleDialog::slotActOnChanged()" << endl; +#endif + + m_resizeButton->setEnabled (resizeEnabled ()); + //m_resizeLabel->setEnabled (resizeEnabled ()); + + m_scaleButton->setEnabled (scaleEnabled ()); + //m_scaleLabel->setEnabled (scaleEnabled ()); + + m_smoothScaleButton->setEnabled (smoothScaleEnabled ()); + //m_smoothScaleLabel->setEnabled (smoothScaleEnabled ()); + + + // TODO: somehow share logic with (resize|*scale)Enabled() + if (actOnSelection ()) + { + if (selection ()->isText ()) + { + m_resizeButton->setOn (true); + } + else + { + if (s_lastType == kpToolResizeScaleCommand::Scale) + m_scaleButton->setOn (true); + else + m_smoothScaleButton->setOn (true); + } + } + else + { + if (s_lastType == kpToolResizeScaleCommand::Resize) + m_resizeButton->setOn (true); + else if (s_lastType == kpToolResizeScaleCommand::Scale) + m_scaleButton->setOn (true); + else + m_smoothScaleButton->setOn (true); + } + + + m_originalWidthInput->setValue (originalWidth ()); + m_originalHeightInput->setValue (originalHeight ()); + + + m_newWidthInput->blockSignals (true); + m_newHeightInput->blockSignals (true); + + m_newWidthInput->setMinValue (actOnSelection () ? + selection ()->minimumWidth () : + 1); + m_newHeightInput->setMinValue (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 kpToolResizeScaleDialog::slotTypeChanged () +{ + s_lastType = type (); +} + +// public slot +void kpToolResizeScaleDialog::slotWidthChanged (int width) +{ +#if DEBUG_KP_TOOL_RESIZE_SCALE_DIALOG && 1 + kdDebug () << "kpToolResizeScaleDialog::slotWidthChanged(" + << width << ")" << endl; +#endif + const double newPercentWidth = double (width) * 100 / double (originalWidth ()); + + SET_VALUE_WITHOUT_SIGNAL_EMISSION (m_percentWidthInput, newPercentWidth); + + widthFitHeightToAspectRatio (); + + //enableButtonOK (!isNoOp ()); + s_lastPercentWidth = newPercentWidth; +} + +// public slot +void kpToolResizeScaleDialog::slotHeightChanged (int height) +{ +#if DEBUG_KP_TOOL_RESIZE_SCALE_DIALOG && 1 + kdDebug () << "kpToolResizeScaleDialog::slotHeightChanged(" + << height << ")" << endl; +#endif + const double newPercentHeight = double (height) * 100 / double (originalHeight ()); + + SET_VALUE_WITHOUT_SIGNAL_EMISSION (m_percentHeightInput, newPercentHeight); + + heightFitWidthToAspectRatio (); + + //enableButtonOK (!isNoOp ()); + s_lastPercentHeight = newPercentHeight; +} + +// public slot +void kpToolResizeScaleDialog::slotPercentWidthChanged (double percentWidth) +{ +#if DEBUG_KP_TOOL_RESIZE_SCALE_DIALOG && 1 + kdDebug () << "kpToolResizeScaleDialog::slotPercentWidthChanged(" + << percentWidth << ")" << endl; +#endif + + SET_VALUE_WITHOUT_SIGNAL_EMISSION (m_newWidthInput, + qRound (percentWidth * originalWidth () / 100.0)); + + widthFitHeightToAspectRatio (); + + //enableButtonOK (!isNoOp ()); + s_lastPercentWidth = percentWidth; +} + +// public slot +void kpToolResizeScaleDialog::slotPercentHeightChanged (double percentHeight) +{ +#if DEBUG_KP_TOOL_RESIZE_SCALE_DIALOG && 1 + kdDebug () << "kpToolResizeScaleDialog::slotPercentHeightChanged(" + << percentHeight << ")" << endl; +#endif + + SET_VALUE_WITHOUT_SIGNAL_EMISSION (m_newHeightInput, + qRound (percentHeight * originalHeight () / 100.0)); + + heightFitWidthToAspectRatio (); + + //enableButtonOK (!isNoOp ()); + s_lastPercentHeight = percentHeight; +} + +// public +bool kpToolResizeScaleDialog::keepAspectRatio () const +{ + return m_keepAspectRatioCheckBox->isChecked (); +} + +// public slot +void kpToolResizeScaleDialog::setKeepAspectRatio (bool on) +{ +#if DEBUG_KP_TOOL_RESIZE_SCALE_DIALOG && 1 + kdDebug () << "kpToolResizeScaleDialog::setKeepAspectRatio(" + << on << ")" << endl; +#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 kpToolResizeScaleDialog::originalWidth () const +{ + return document ()->width (actOnSelection ()); +} + +// private +int kpToolResizeScaleDialog::originalHeight () const +{ + return document ()->height (actOnSelection ()); +} + + +// public +int kpToolResizeScaleDialog::imageWidth () const +{ + return m_newWidthInput->value (); +} + +// public +int kpToolResizeScaleDialog::imageHeight () const +{ + return m_newHeightInput->value (); +} + +// public +bool kpToolResizeScaleDialog::actOnSelection () const +{ + return (m_actOnCombo->currentItem () == Selection); +} + +// public +kpToolResizeScaleCommand::Type kpToolResizeScaleDialog::type () const +{ + if (m_resizeButton->isOn ()) + return kpToolResizeScaleCommand::Resize; + else if (m_scaleButton->isOn ()) + return kpToolResizeScaleCommand::Scale; + else + return kpToolResizeScaleCommand::SmoothScale; +} + +// public +bool kpToolResizeScaleDialog::isNoOp () const +{ + return (imageWidth () == originalWidth () && + imageHeight () == originalHeight ()); +} + + +// private slot virtual [base KDialogBase] +void kpToolResizeScaleDialog::slotOk () +{ + enum { eText, eSelection, eImage } actionTarget = eText; + + if (actOnSelection ()) + { + if (selection ()->isText ()) + { + actionTarget = eText; + } + else + { + actionTarget = eSelection; + } + } + else + { + actionTarget = eImage; + } + + + QString message, caption, continueButtonText; + + // Note: If eText, can't Scale nor SmoothScale. + // If eSelection, can't Resize. + + switch (type ()) + { + default: + case kpToolResizeScaleCommand::Resize: + if (actionTarget == eText) + { + message = + i18n ("<qt><p>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.</p>" + + "<p>Are you sure you want to resize the text box?</p></qt>"); + + caption = i18n ("Resize Text Box?"); + continueButtonText = i18n ("R&esize Text Box"); + } + else if (actionTarget == eImage) + { + message = + i18n ("<qt><p>Resizing the image to %1x%2" + " may take a substantial amount of memory." + " This can reduce system" + " responsiveness and cause other application resource" + " problems.</p>" + + "<p>Are you sure you want to resize the image?</p></qt>"); + + caption = i18n ("Resize Image?"); + continueButtonText = i18n ("R&esize Image"); + } + + break; + + case kpToolResizeScaleCommand::Scale: + if (actionTarget == eImage) + { + message = + i18n ("<qt><p>Scaling the image to %1x%2" + " may take a substantial amount of memory." + " This can reduce system" + " responsiveness and cause other application resource" + " problems.</p>" + + "<p>Are you sure you want to scale the image?</p></qt>"); + + caption = i18n ("Scale Image?"); + continueButtonText = i18n ("Scal&e Image"); + } + else if (actionTarget == eSelection) + { + message = + i18n ("<qt><p>Scaling the selection to %1x%2" + " may take a substantial amount of memory." + " This can reduce system" + " responsiveness and cause other application resource" + " problems.</p>" + + "<p>Are you sure you want to scale the selection?</p></qt>"); + + caption = i18n ("Scale Selection?"); + continueButtonText = i18n ("Scal&e Selection"); + } + + break; + + case kpToolResizeScaleCommand::SmoothScale: + if (actionTarget == eImage) + { + message = + i18n ("<qt><p>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.</p>" + + "<p>Are you sure you want to smooth scale the image?</p></qt>"); + + caption = i18n ("Smooth Scale Image?"); + continueButtonText = i18n ("Smooth Scal&e Image"); + } + else if (actionTarget == eSelection) + { + message = + i18n ("<qt><p>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.</p>" + + "<p>Are you sure you want to smooth scale the selection?</p></qt>"); + + caption = i18n ("Smooth Scale Selection?"); + continueButtonText = i18n ("Smooth Scal&e Selection"); + } + + break; + } + + + if (kpTool::warnIfBigImageSize (originalWidth (), + originalHeight (), + imageWidth (), imageHeight (), + message.arg (imageWidth ()).arg (imageHeight ()), + caption, + continueButtonText, + this)) + { + KDialogBase::slotOk (); + } +} + + +#include <kptoolresizescale.moc> diff --git a/kolourpaint/tools/kptoolresizescale.h b/kolourpaint/tools/kptoolresizescale.h new file mode 100644 index 00000000..e23b040e --- /dev/null +++ b/kolourpaint/tools/kptoolresizescale.h @@ -0,0 +1,196 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 __kptoolresizescale_h__ +#define __kptoolresizescale_h__ + +#include <qpixmap.h> + +#include <kpcommandhistory.h> +#include <kdialogbase.h> + +#include <kpcolor.h> +#include <kpselection.h> + +class QCheckBox; +class QGroupBox; +class QHBox; +class QRadioButton; +class QSize; +class QString; +class QToolButton; + +class KComboBox; +class KDoubleNumInput; +class KIntNumInput; + +class kpDocument; +class kpMainWindow; +class kpViewManager; + +class kpToolResizeScaleCommand : public kpCommand +{ +public: + enum Type + { + Resize, Scale, SmoothScale + }; + + kpToolResizeScaleCommand (bool actOnSelection, + int newWidth, int newHeight, + Type type, + kpMainWindow *mainWindow); + virtual ~kpToolResizeScaleCommand (); + + virtual QString name () const; + virtual int size () const; + +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: + virtual void execute (); + virtual void unexecute (); + +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; + QPixmap m_oldPixmap, m_oldRightPixmap, m_oldBottomPixmap; + kpSelection *m_oldSelection; +}; + +class kpToolResizeScaleDialog : public KDialogBase +{ +Q_OBJECT + +public: + kpToolResizeScaleDialog (kpMainWindow *mainWindow); + virtual ~kpToolResizeScaleDialog (); + + enum ActOn + { + Image, Selection + }; + +private: + static kpToolResizeScaleCommand::Type s_lastType; + static double s_lastPercentWidth, s_lastPercentHeight; + +private: + kpDocument *document () const; + kpSelection *selection () const; + + void createActOnBox (QWidget *baseWidget); + void createOperationGroupBox (QWidget *baseWidget); + void createDimensionsGroupBox (QWidget *baseWidget); + + void widthFitHeightToAspectRatio (); + void heightFitWidthToAspectRatio (); + +private: + bool resizeEnabled () const; + bool scaleEnabled () const; + bool smoothScaleEnabled () const; + +public slots: + void slotActOnChanged (); + void slotTypeChanged (); + + void slotWidthChanged (int width); + void slotHeightChanged (int height); + + void slotPercentWidthChanged (double percentWidth); + void slotPercentHeightChanged (double percentHeight); + +public: + // (refers only to the state of the checkbox - user of dialog does + // not have to do extra calculations) + bool keepAspectRatio () const; +public slots: + void setKeepAspectRatio (bool on); + +private: + int originalWidth () const; + int originalHeight () const; + +public: + int imageWidth () const; + int imageHeight () const; + bool actOnSelection () const; + kpToolResizeScaleCommand::Type type () const; + + bool isNoOp () const; + +private slots: + virtual void slotOk (); + +private: + kpMainWindow *m_mainWindow; + + QHBox *m_actOnBox; + QLabel *m_actOnLabel; + KComboBox *m_actOnCombo; + + QGroupBox *m_operationGroupBox; + QToolButton *m_resizeButton, + *m_scaleButton, + *m_smoothScaleButton; + QLabel *m_resizeLabel, + *m_scaleLabel, + *m_smoothScaleLabel; + + QGroupBox *m_dimensionsGroupBox; + KIntNumInput *m_originalWidthInput, *m_originalHeightInput, + *m_newWidthInput, *m_newHeightInput; + KDoubleNumInput *m_percentWidthInput, *m_percentHeightInput; + QCheckBox *m_keepAspectRatioCheckBox; + + int m_ignoreKeepAspectRatio; +}; + +#endif // __kptoolresizescale_h__ diff --git a/kolourpaint/tools/kptoolrotate.cpp b/kolourpaint/tools/kptoolrotate.cpp new file mode 100644 index 00000000..8a37b673 --- /dev/null +++ b/kolourpaint/tools/kptoolrotate.cpp @@ -0,0 +1,500 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <kptoolrotate.h> + +#include <qapplication.h> +#include <qbuttongroup.h> +#include <qgroupbox.h> +#include <qlabel.h> +#include <qlayout.h> +#include <qpushbutton.h> +#include <qradiobutton.h> +#include <qwmatrix.h> + +#include <kdebug.h> +#include <kiconloader.h> +#include <knuminput.h> +#include <klocale.h> + +#include <kpdefs.h> +#include <kpdocument.h> +#include <kpmainwindow.h> +#include <kppixmapfx.h> +#include <kpselection.h> +#include <kptool.h> +#include <kpviewmanager.h> + + +kpToolRotateCommand::kpToolRotateCommand (bool actOnSelection, + double angle, + kpMainWindow *mainWindow) + : kpCommand (mainWindow), + m_actOnSelection (actOnSelection), + m_angle (angle), + m_backgroundColor (mainWindow ? mainWindow->backgroundColor (actOnSelection) : kpColor::invalid), + m_losslessRotation (kpPixmapFX::isLosslessRotation (angle)) +{ +} + +kpToolRotateCommand::~kpToolRotateCommand () +{ +} + + +// public virtual [base kpCommand] +QString kpToolRotateCommand::name () const +{ + QString opName = i18n ("Rotate"); + + if (m_actOnSelection) + return i18n ("Selection: %1").arg (opName); + else + return opName; +} + + +// public virtual [base kpCommand] +int kpToolRotateCommand::size () const +{ + return kpPixmapFX::pixmapSize (m_oldPixmap) + + m_oldSelection.size (); +} + + +// public virtual [base kpCommand] +void kpToolRotateCommand::execute () +{ + kpDocument *doc = document (); + if (!doc) + return; + + + QApplication::setOverrideCursor (Qt::waitCursor); + + + if (!m_losslessRotation) + m_oldPixmap = *doc->pixmap (m_actOnSelection); + + + QPixmap newPixmap = kpPixmapFX::rotate (*doc->pixmap (m_actOnSelection), + m_angle, + m_backgroundColor); + + + if (m_actOnSelection) + { + kpSelection *sel = doc->selection (); + + // Save old selection + m_oldSelection = *sel; + m_oldSelection.setPixmap (QPixmap ()); + + + // Calculate new top left (so selection rotates about centre) + // (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 (newPixmap.width (), newPixmap.height ())); + QPoint newTopLeft (newTopLeftTimes2.x () / 2, newTopLeftTimes2.y () / 2); + + + // Calculate rotated points + QPointArray currentPoints = sel->points (); + currentPoints.translate (-currentPoints.boundingRect ().x (), + -currentPoints.boundingRect ().y ()); + QWMatrix rotateMatrix = kpPixmapFX::rotateMatrix (*doc->pixmap (m_actOnSelection), m_angle); + currentPoints = rotateMatrix.map (currentPoints); + currentPoints.translate (-currentPoints.boundingRect ().x () + newTopLeft.x (), + -currentPoints.boundingRect ().y () + newTopLeft.y ()); + + + if (currentPoints.boundingRect ().width () == newPixmap.width () && + currentPoints.boundingRect ().height () == newPixmap.height ()) + { + doc->setSelection (kpSelection (currentPoints, newPixmap, + m_oldSelection.transparency ())); + } + else + { + // TODO: fix the latter "victim of" problem in kpSelection by + // allowing the border width & height != pixmap width & height + // Or maybe autocrop? + #if DEBUG_KP_TOOL_ROTATE + kdDebug () << "kpToolRotateCommand::execute() currentPoints.boundingRect=" + << currentPoints.boundingRect () + << " newPixmap: w=" << newPixmap.width () + << " h=" << newPixmap.height () + << " (victim of rounding error and/or rotated-a-(rectangular)-pixmap-that-was-transparent-in-the-corners-making-sel-uselessly-bigger-than-needs-be)" + << endl; + #endif + doc->setSelection (kpSelection (kpSelection::Rectangle, + QRect (newTopLeft.x (), newTopLeft.y (), + newPixmap.width (), newPixmap.height ()), + newPixmap, + m_oldSelection.transparency ())); + } + + if (m_mainWindow->tool ()) + m_mainWindow->tool ()->somethingBelowTheCursorChanged (); + } + else + doc->setPixmap (newPixmap); + + + QApplication::restoreOverrideCursor (); +} + +// public virtual [base kpCommand] +void kpToolRotateCommand::unexecute () +{ + kpDocument *doc = document (); + if (!doc) + return; + + + QApplication::setOverrideCursor (Qt::waitCursor); + + + QPixmap oldPixmap; + + if (!m_losslessRotation) + { + oldPixmap = m_oldPixmap; + m_oldPixmap.resize (0, 0); + } + else + { + oldPixmap = kpPixmapFX::rotate (*doc->pixmap (m_actOnSelection), + 360 - m_angle, + m_backgroundColor); + } + + + if (!m_actOnSelection) + doc->setPixmap (oldPixmap); + else + { + kpSelection oldSelection = m_oldSelection; + oldSelection.setPixmap (oldPixmap); + doc->setSelection (oldSelection); + + if (m_mainWindow->tool ()) + m_mainWindow->tool ()->somethingBelowTheCursorChanged (); + } + + + QApplication::restoreOverrideCursor (); +} + + +/* + * kpToolRotateDialog + */ + + +// private static +int kpToolRotateDialog::s_lastWidth = -1, + kpToolRotateDialog::s_lastHeight = -1; + +// private static +bool kpToolRotateDialog::s_lastIsClockwise = true; +int kpToolRotateDialog::s_lastAngleRadioButtonID = 3; +int kpToolRotateDialog::s_lastAngleCustom = 0; + + +kpToolRotateDialog::kpToolRotateDialog (bool actOnSelection, + kpMainWindow *mainWindow, + const char *name) + : kpToolPreviewDialog (kpToolPreviewDialog::AllFeatures, + false/*don't reserve top row*/, + actOnSelection ? i18n ("Rotate Selection") : i18n ("Rotate Image"), + i18n ("After Rotate:"), + actOnSelection, mainWindow, name) +{ + // Too confusing - disable for now + s_lastAngleRadioButtonID = 3; + s_lastAngleCustom = 0; + + + createDirectionGroupBox (); + createAngleGroupBox (); + + + if (s_lastWidth > 0 && s_lastHeight > 0) + resize (s_lastWidth, s_lastHeight); + + + slotAngleCustomRadioButtonToggled (m_angleCustomRadioButton->isChecked ()); + slotUpdate (); +} + +kpToolRotateDialog::~kpToolRotateDialog () +{ + s_lastWidth = width (), s_lastHeight = height (); +} + + +// private +void kpToolRotateDialog::createDirectionGroupBox () +{ + QGroupBox *directionGroupBox = new QGroupBox (i18n ("Direction"), mainWidget ()); + addCustomWidget (directionGroupBox); + + + QLabel *antiClockwisePixmapLabel = new QLabel (directionGroupBox); + antiClockwisePixmapLabel->setPixmap (UserIcon ("image_rotate_anticlockwise")); + + QLabel *clockwisePixmapLabel = new QLabel (directionGroupBox); + clockwisePixmapLabel->setPixmap (UserIcon ("image_rotate_clockwise")); + + + 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); + + + QButtonGroup *buttonGroup = new QButtonGroup (directionGroupBox); + buttonGroup->hide (); + + buttonGroup->insert (m_antiClockwiseRadioButton); + buttonGroup->insert (m_clockwiseRadioButton); + + + QGridLayout *directionLayout = new QGridLayout (directionGroupBox, + 2, 2, marginHint () * 2, spacingHint ()); + 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, SIGNAL (toggled (bool)), + this, SLOT (slotUpdate ())); + connect (m_clockwiseRadioButton, SIGNAL (toggled (bool)), + this, SLOT (slotUpdate ())); +} + +// private +void kpToolRotateDialog::createAngleGroupBox () +{ + QGroupBox *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 KIntNumInput (s_lastAngleCustom, angleGroupBox); + m_angleCustomInput->setMinValue (-359); + m_angleCustomInput->setMaxValue (+359); + QLabel *degreesLabel = new QLabel (i18n ("degrees"), angleGroupBox); + + + m_angleButtonGroup = new QButtonGroup (angleGroupBox); + m_angleButtonGroup->hide (); + + m_angleButtonGroup->insert (m_angle90RadioButton); + m_angleButtonGroup->insert (m_angle180RadioButton); + m_angleButtonGroup->insert (m_angle270RadioButton); + + m_angleButtonGroup->insert (m_angleCustomRadioButton); + + m_angleButtonGroup->setButton (s_lastAngleRadioButtonID); + + + QGridLayout *angleLayout = new QGridLayout (angleGroupBox, + 6, 3, + marginHint () * 2, spacingHint ()); + + angleLayout->addMultiCellWidget (m_angle90RadioButton, 0, 0, 0, 2); + angleLayout->addMultiCellWidget (m_angle180RadioButton, 1, 1, 0, 2); + angleLayout->addMultiCellWidget (m_angle270RadioButton, 2, 2, 0, 2); + + angleLayout->addWidget (m_angleCustomRadioButton, 3, 0); + angleLayout->addWidget (m_angleCustomInput, 3, 1); + angleLayout->addWidget (degreesLabel, 3, 2); + + angleLayout->setColStretch (1, 2); // Stretch Custom Angle Input + + + connect (m_angle90RadioButton, SIGNAL (toggled (bool)), + this, SLOT (slotUpdate ())); + connect (m_angle180RadioButton, SIGNAL (toggled (bool)), + this, SLOT (slotUpdate ())); + connect (m_angle270RadioButton, SIGNAL (toggled (bool)), + this, SLOT (slotUpdate ())); + + connect (m_angleCustomRadioButton, SIGNAL (toggled (bool)), + this, SLOT (slotAngleCustomRadioButtonToggled (bool))); + connect (m_angleCustomRadioButton, SIGNAL (toggled (bool)), + this, SLOT (slotUpdate ())); + + connect (m_angleCustomInput, SIGNAL (valueChanged (int)), + this, SLOT (slotUpdate ())); +} + + +// public virtual [base kpToolPreviewDialog] +bool kpToolRotateDialog::isNoOp () const +{ + return (angle () == 0); +} + +// public +int kpToolRotateDialog::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 kpToolPreviewDialog] +QSize kpToolRotateDialog::newDimensions () const +{ + QWMatrix matrix = kpPixmapFX::rotateMatrix (m_oldWidth, m_oldHeight, angle ()); + // TODO: Should we be using QWMatrix::Areas? + QRect rect = matrix.map (QRect (0, 0, m_oldWidth, m_oldHeight)); + return rect.size (); +} + +// private virtual [base kpToolPreviewDialog] +QPixmap kpToolRotateDialog::transformPixmap (const QPixmap &pixmap, + int targetWidth, int targetHeight) const +{ + return kpPixmapFX::rotate (pixmap, angle (), + m_mainWindow ? m_mainWindow->backgroundColor (m_actOnSelection) : kpColor::invalid, + targetWidth, targetHeight); +} + + +// private slot +void kpToolRotateDialog::slotAngleCustomRadioButtonToggled (bool isChecked) +{ + m_angleCustomInput->setEnabled (isChecked); + + if (isChecked) + m_angleCustomInput->setEditFocus (); +} + +// private slot virtual [base kpToolPreviewDialog] +void kpToolRotateDialog::slotUpdate () +{ + s_lastIsClockwise = m_clockwiseRadioButton->isChecked (); + s_lastAngleRadioButtonID = m_angleButtonGroup->selectedId (); + s_lastAngleCustom = m_angleCustomInput->value (); + + kpToolPreviewDialog::slotUpdate (); +} + + +// private slot virtual [base KDialogBase] +void kpToolRotateDialog::slotOk () +{ + QString message, caption, continueButtonText; + + if (document ()->selection ()) + { + if (!document ()->selection ()->isText ()) + { + message = + i18n ("<qt><p>Rotating the selection to %1x%2" + " may take a substantial amount of memory." + " This can reduce system" + " responsiveness and cause other application resource" + " problems.</p>" + + "<p>Are you sure want to rotate the selection?</p></qt>"); + + caption = i18n ("Rotate Selection?"); + continueButtonText = i18n ("Rotat&e Selection"); + } + } + else + { + message = + i18n ("<qt><p>Rotating the image to %1x%2" + " may take a substantial amount of memory." + " This can reduce system" + " responsiveness and cause other application resource" + " problems.</p>" + + "<p>Are you sure want to rotate the image?</p></qt>"); + + caption = i18n ("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.arg (newWidth).arg (newHeight), + caption, + continueButtonText, + this)) + { + KDialogBase::slotOk (); + } +} + +#include <kptoolrotate.moc> diff --git a/kolourpaint/tools/kptoolrotate.h b/kolourpaint/tools/kptoolrotate.h new file mode 100644 index 00000000..887473dc --- /dev/null +++ b/kolourpaint/tools/kptoolrotate.h @@ -0,0 +1,129 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 __kptoolrotate_h__ +#define __kptoolrotate_h__ + +#include <qpixmap.h> +#include <qpoint.h> + +#include <kdialogbase.h> + +#include <kpcolor.h> +#include <kpcommandhistory.h> +#include <kpselection.h> +#include <kptoolpreviewdialog.h> + + +class QButtonGroup; +class QRadioButton; +class QString; + +class KIntNumInput; + +class kpDocument; +class kpViewManager; +class kpMainWindow; + + +class kpToolRotateCommand : public kpCommand +{ +public: + kpToolRotateCommand (bool actOnSelection, + double angle, // 0 <= angle < 360 (clockwise) + kpMainWindow *mainWindow); + virtual ~kpToolRotateCommand (); + + virtual QString name () const; + + virtual int size () const; + + virtual void execute (); + virtual void unexecute (); + +private: + bool m_actOnSelection; + double m_angle; + + kpColor m_backgroundColor; + + bool m_losslessRotation; + QPixmap m_oldPixmap; + kpSelection m_oldSelection; +}; + + +class kpToolRotateDialog : public kpToolPreviewDialog +{ +Q_OBJECT + +public: + kpToolRotateDialog (bool actOnSelection, + kpMainWindow *parent, + const char *name = 0); + virtual ~kpToolRotateDialog (); + +private: + static int s_lastWidth, s_lastHeight; + static bool s_lastIsClockwise; + static int s_lastAngleRadioButtonID; + static int s_lastAngleCustom; + + void createDirectionGroupBox (); + void createAngleGroupBox (); + +public: + virtual bool isNoOp () const; + int angle () const; // 0 <= angle < 360 (clockwise); + +private: + virtual QSize newDimensions () const; + virtual QPixmap transformPixmap (const QPixmap &pixmap, + int targetWidth, int targetHeight) const; + +private slots: + void slotAngleCustomRadioButtonToggled (bool isChecked); + virtual void slotUpdate (); + +private slots: + virtual void slotOk (); + +private: + QRadioButton *m_antiClockwiseRadioButton, + *m_clockwiseRadioButton; + + QButtonGroup *m_angleButtonGroup; + QRadioButton *m_angle90RadioButton, + *m_angle180RadioButton, + *m_angle270RadioButton, + *m_angleCustomRadioButton; + KIntNumInput *m_angleCustomInput; +}; + + +#endif // __kptoolrotate_h__ diff --git a/kolourpaint/tools/kptoolroundedrectangle.cpp b/kolourpaint/tools/kptoolroundedrectangle.cpp new file mode 100644 index 00000000..b0f4ba05 --- /dev/null +++ b/kolourpaint/tools/kptoolroundedrectangle.cpp @@ -0,0 +1,45 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <klocale.h> +#include <kptoolroundedrectangle.h> + +kpToolRoundedRectangle::kpToolRoundedRectangle (kpMainWindow *mainWindow) + : kpToolRectangle (RoundedRectangle, + i18n ("Rounded Rectangle"), + i18n ("Draws rectangles and squares with rounded corners"), + Qt::Key_U, + mainWindow, "tool_rounded_rectangle") +{ +} + +kpToolRoundedRectangle::~kpToolRoundedRectangle () +{ +} + +#include <kptoolroundedrectangle.moc> diff --git a/kolourpaint/tools/kptoolroundedrectangle.h b/kolourpaint/tools/kptoolroundedrectangle.h new file mode 100644 index 00000000..924c1b34 --- /dev/null +++ b/kolourpaint/tools/kptoolroundedrectangle.h @@ -0,0 +1,45 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 __kptoolroundedrectangle_h__ +#define __kptoolroundedrectangle_h__ + +#include <kptoolrectangle.h> + +class kpMainWindow; + +class kpToolRoundedRectangle : public kpToolRectangle +{ +Q_OBJECT + +public: + kpToolRoundedRectangle (kpMainWindow *); + virtual ~kpToolRoundedRectangle (); +}; + +#endif // __kptoolroundedrectangle_h__ diff --git a/kolourpaint/tools/kptoolselection.cpp b/kolourpaint/tools/kptoolselection.cpp new file mode 100644 index 00000000..f664f01b --- /dev/null +++ b/kolourpaint/tools/kptoolselection.cpp @@ -0,0 +1,2371 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <kptoolselection.h> + +#include <qapplication.h> +#include <qbitmap.h> +#include <qcursor.h> +#include <qpainter.h> +#include <qpopupmenu.h> +#include <qtimer.h> + +#include <kdebug.h> +#include <klocale.h> + +#include <kpcommandhistory.h> +#include <kpdefs.h> +#include <kpdocument.h> +#include <kpmainwindow.h> +#include <kpselection.h> +#include <kptooltoolbar.h> +#include <kptoolwidgetopaqueortransparent.h> +#include <kpview.h> +#include <kpviewmanager.h> + + +kpToolSelection::kpToolSelection (Mode mode, + const QString &text, + const QString &description, + int key, + kpMainWindow *mainWindow, + const char *name) + : kpTool (text, description, key, mainWindow, name), + m_mode (mode), + m_currentPullFromDocumentCommand (0), + m_currentMoveCommand (0), + m_currentResizeScaleCommand (0), + m_toolWidgetOpaqueOrTransparent (0), + m_currentCreateTextCommand (0), + m_createNOPTimer (new QTimer (this)), + m_RMBMoveUpdateGUITimer (new QTimer (this)) +{ + connect (m_createNOPTimer, SIGNAL (timeout ()), + this, SLOT (delayedDraw ())); + connect (m_RMBMoveUpdateGUITimer, SIGNAL (timeout ()), + this, SLOT (slotRMBMoveUpdateGUI ())); +} + +kpToolSelection::~kpToolSelection () +{ +} + + +// private +void kpToolSelection::pushOntoDocument () +{ +#if DEBUG_KP_TOOL_SELECTION && 1 + kdDebug () << "kpToolSelection::pushOntoDocument() CALLED" << endl; +#endif + mainWindow ()->slotDeselect (); +} + + +// protected +bool kpToolSelection::onSelectionToMove () const +{ + kpView *v = viewManager ()->viewUnderCursor (); + if (!v) + return 0; + + return v->mouseOnSelectionToMove (m_currentViewPoint); +} + +// protected +int kpToolSelection::onSelectionResizeHandle () const +{ + kpView *v = viewManager ()->viewUnderCursor (); + if (!v) + return 0; + + return v->mouseOnSelectionResizeHandle (m_currentViewPoint); +} + +// protected +bool kpToolSelection::onSelectionToSelectText () const +{ + kpView *v = viewManager ()->viewUnderCursor (); + if (!v) + return 0; + + return v->mouseOnSelectionToSelectText (m_currentViewPoint); +} + + +// public +QString kpToolSelection::haventBegunDrawUserMessage () const +{ +#if DEBUG_KP_TOOL_SELECTION && 0 + kdDebug () << "kpToolSelection::haventBegunDrawUserMessage()" + " cancelledShapeButStillHoldingButtons=" + << m_cancelledShapeButStillHoldingButtons + << endl; +#endif + + if (m_cancelledShapeButStillHoldingButtons) + return i18n ("Let go of all the mouse buttons."); + + kpSelection *sel = document ()->selection (); + if (sel && onSelectionResizeHandle () && !controlOrShiftPressed ()) + { + if (m_mode == Text) + return i18n ("Left drag to resize text box."); + else + return i18n ("Left drag to scale selection."); + } + else if (sel && sel->contains (m_currentPoint)) + { + if (m_mode == Text) + { + if (onSelectionToSelectText () && !controlOrShiftPressed ()) + return i18n ("Left click to change cursor position."); + else + return i18n ("Left drag to move text box."); + } + else + { + return i18n ("Left drag to move selection."); + } + } + else + { + if (m_mode == Text) + return i18n ("Left drag to create text box."); + else + return i18n ("Left drag to create selection."); + } +} + + +// virtual +void kpToolSelection::begin () +{ +#if DEBUG_KP_TOOL_SELECTION + kdDebug () << "kpToolSelection::begin()" << endl; +#endif + + kpToolToolBar *tb = toolToolBar (); + + if (tb) + { + m_toolWidgetOpaqueOrTransparent = tb->toolWidgetOpaqueOrTransparent (); + + if (m_toolWidgetOpaqueOrTransparent) + { + connect (m_toolWidgetOpaqueOrTransparent, SIGNAL (isOpaqueChanged (bool)), + this, SLOT (slotIsOpaqueChanged ())); + m_toolWidgetOpaqueOrTransparent->show (); + } + } + else + { + m_toolWidgetOpaqueOrTransparent = 0; + } + + viewManager ()->setQueueUpdates (); + { + viewManager ()->setSelectionBorderVisible (true); + viewManager ()->setSelectionBorderFinished (true); + } + viewManager ()->restoreQueueUpdates (); + + m_startDragFromSelectionTopLeft = QPoint (); + m_dragType = Unknown; + m_dragHasBegun = false; + m_hadSelectionBeforeDrag = false; // arbitrary + m_resizeScaleType = 0; + + m_currentPullFromDocumentCommand = 0; + m_currentMoveCommand = 0; + m_currentResizeScaleCommand = 0; + m_currentCreateTextCommand = 0; + + m_cancelledShapeButStillHoldingButtons = false; + + setUserMessage (haventBegunDrawUserMessage ()); +} + +// virtual +void kpToolSelection::end () +{ +#if DEBUG_KP_TOOL_SELECTION + kdDebug () << "kpToolSelection::end()" << endl; +#endif + + if (document ()->selection ()) + pushOntoDocument (); + + if (m_toolWidgetOpaqueOrTransparent) + { + disconnect (m_toolWidgetOpaqueOrTransparent, SIGNAL (isOpaqueChanged (bool)), + this, SLOT (slotIsOpaqueChanged ())); + m_toolWidgetOpaqueOrTransparent = 0; + } + + viewManager ()->unsetCursor (); +} + +// virtual +void kpToolSelection::reselect () +{ +#if DEBUG_KP_TOOL_SELECTION + kdDebug () << "kpToolSelection::reselect()" << endl; +#endif + + if (document ()->selection ()) + pushOntoDocument (); +} + + +// virtual +void kpToolSelection::beginDraw () +{ +#if DEBUG_KP_TOOL_SELECTION + kdDebug () << "kpToolSelection::beginDraw() m_startPoint=" + << m_startPoint + << " QCursor::pos() view startPoint=" + << m_viewUnderStartPoint->mapFromGlobal (QCursor::pos ()) + << endl; +#endif + + m_createNOPTimer->stop (); + m_RMBMoveUpdateGUITimer->stop (); + + + // In case the cursor was wrong to start with + // (forgot to call kpTool::somethingBelowTheCursorChanged()), + // make sure it is correct during this operation. + hover (m_currentPoint); + + // Currently used only to end the current text + if (hasBegunShape ()) + endShape (m_currentPoint, QRect (m_startPoint/* TODO: wrong */, m_currentPoint).normalize ()); + + m_dragType = Create; + m_dragHasBegun = false; + + kpSelection *sel = document ()->selection (); + m_hadSelectionBeforeDrag = bool (sel); + + if (sel) + { + #if DEBUG_KP_TOOL_SELECTION + kdDebug () << "\thas sel region rect=" << sel->boundingRect () << endl; + #endif + QRect selectionRect = sel->boundingRect (); + + if (onSelectionResizeHandle () && !controlOrShiftPressed ()) + { + #if DEBUG_KP_TOOL_SELECTION + kdDebug () << "\t\tis resize/scale" << endl; + #endif + + m_startDragFromSelectionTopLeft = m_currentPoint - selectionRect.topLeft (); + m_dragType = ResizeScale; + m_resizeScaleType = onSelectionResizeHandle (); + + viewManager ()->setQueueUpdates (); + { + viewManager ()->setSelectionBorderVisible (true); + viewManager ()->setSelectionBorderFinished (true); + viewManager ()->setTextCursorEnabled (false); + } + viewManager ()->restoreQueueUpdates (); + } + else if (sel->contains (m_currentPoint)) + { + if (m_mode == Text && onSelectionToSelectText () && !controlOrShiftPressed ()) + { + #if DEBUG_KP_TOOL_SELECTION + kdDebug () << "\t\tis select cursor pos" << endl; + #endif + + m_dragType = SelectText; + + viewManager ()->setTextCursorPosition (sel->textRowForPoint (m_currentPoint), + sel->textColForPoint (m_currentPoint)); + } + else + { + #if DEBUG_KP_TOOL_SELECTION + kdDebug () << "\t\tis move" << endl; + #endif + + m_startDragFromSelectionTopLeft = m_currentPoint - selectionRect.topLeft (); + m_dragType = Move; + + if (m_mouseButton == 0) + { + setSelectionBorderForMove (); + } + else + { + // Don't hide sel border momentarily if user is just + // right _clicking_ selection + m_RMBMoveUpdateGUITimer->start (100, true/*single shot*/); + } + } + } + else + { + #if DEBUG_KP_TOOL_SELECTION + kdDebug () << "\t\tis new sel" << endl; + #endif + + pushOntoDocument (); + } + } + + // creating new selection? + if (m_dragType == Create) + { + viewManager ()->setQueueUpdates (); + { + viewManager ()->setSelectionBorderVisible (true); + viewManager ()->setSelectionBorderFinished (false); + viewManager ()->setTextCursorEnabled (false); + } + viewManager ()->restoreQueueUpdates (); + + m_createNOPTimer->start (200, true/*single shot*/); + } + + if (m_dragType != SelectText) + { + setUserMessage (cancelUserMessage ()); + } +} + + +// protected +const QCursor &kpToolSelection::cursor () const +{ +#if DEBUG_KP_TOOL_SELECTION && 1 + kdDebug () << "kpToolSelection::cursor()" + << " m_currentPoint=" << m_currentPoint + << " QCursor::pos() view under cursor=" + << (viewUnderCursor () ? + viewUnderCursor ()->mapFromGlobal (QCursor::pos ()) : + KP_INVALID_POINT) + << " controlOrShiftPressed=" << controlOrShiftPressed () + << endl; +#endif + + kpSelection *sel = document () ? document ()->selection () : 0; + + if (sel && onSelectionResizeHandle () && !controlOrShiftPressed ()) + { + #if DEBUG_KP_TOOL_SELECTION && 1 + kdDebug () << "\tonSelectionResizeHandle=" + << onSelectionResizeHandle () << endl; + #endif + switch (onSelectionResizeHandle ()) + { + case (kpView::Top | kpView::Left): + case (kpView::Bottom | kpView::Right): + return Qt::sizeFDiagCursor; + + case (kpView::Bottom | kpView::Left): + case (kpView::Top | kpView::Right): + return Qt::sizeBDiagCursor; + + case kpView::Top: + case kpView::Bottom: + return Qt::sizeVerCursor; + + case kpView::Left: + case kpView::Right: + return Qt::sizeHorCursor; + } + + return Qt::arrowCursor; + } + else if (sel && sel->contains (m_currentPoint)) + { + #if DEBUG_KP_TOOL_SELECTION && 1 + kdDebug () << "\tsel contains currentPoint; selecting text? " + << onSelectionToSelectText () << endl; + #endif + + if (m_mode == Text && onSelectionToSelectText () && !controlOrShiftPressed ()) + return Qt::ibeamCursor; + else + return Qt::sizeAllCursor; + } + else + { + #if DEBUG_KP_TOOL_SELECTION && 1 + kdDebug () << "\tnot on sel" << endl; + #endif + return Qt::crossCursor; + } +} + +// virtual +void kpToolSelection::hover (const QPoint &point) +{ +#if DEBUG_KP_TOOL_SELECTION && 1 + kdDebug () << "kpToolSelection::hover" << point << endl; +#endif + + viewManager ()->setCursor (cursor ()); + + setUserShapePoints (point, KP_INVALID_POINT, false/*don't set size*/); + if (document () && document ()->selection ()) + { + setUserShapeSize (document ()->selection ()->width (), + document ()->selection ()->height ()); + } + else + { + setUserShapeSize (KP_INVALID_SIZE); + } + + QString mess = haventBegunDrawUserMessage (); + if (mess != userMessage ()) + setUserMessage (mess); +} + +// protected +void kpToolSelection::popupRMBMenu () +{ + QPopupMenu *pop = mainWindow () ? mainWindow ()->selectionToolRMBMenu () : 0; + if (!pop) + return; + + // WARNING: enters event loop - may re-enter view/tool event handlers + pop->exec (QCursor::pos ()); + + // Cursor may have moved while menu up, triggering mouseMoveEvents + // for the menu - not the view. Update cursor position now. + somethingBelowTheCursorChanged (); +} + +// protected +void kpToolSelection::setSelectionBorderForMove () +{ + // don't show border while moving + viewManager ()->setQueueUpdates (); + { + viewManager ()->setSelectionBorderVisible (false); + viewManager ()->setSelectionBorderFinished (true); + viewManager ()->setTextCursorEnabled (false); + } + viewManager ()->restoreQueueUpdates (); +} + +// protected slot +void kpToolSelection::slotRMBMoveUpdateGUI () +{ + // (just in case not called from single shot) + m_RMBMoveUpdateGUITimer->stop (); + + setSelectionBorderForMove (); + + kpSelection * const sel = document () ? document ()->selection () : 0; + if (sel) + setUserShapePoints (sel->topLeft ()); +} + +// protected slot +void kpToolSelection::delayedDraw () +{ +#if DEBUG_KP_TOOL_SELECTION && 1 + kdDebug () << "kpToolSelection::delayedDraw() hasBegunDraw=" + << hasBegunDraw () + << " currentPoint=" << m_currentPoint + << " lastPoint=" << m_lastPoint + << " startPoint=" << m_startPoint + << endl; +#endif + + // (just in case not called from single shot) + m_createNOPTimer->stop (); + + if (hasBegunDraw ()) + { + draw (m_currentPoint, m_lastPoint, + QRect (m_startPoint, m_currentPoint).normalize ()); + } +} + +// virtual +void kpToolSelection::draw (const QPoint &inThisPoint, const QPoint & /*lastPoint*/, + const QRect &inNormalizedRect) +{ + QPoint thisPoint = inThisPoint; + QRect normalizedRect = inNormalizedRect; + +#if DEBUG_KP_TOOL_SELECTION && 1 + kdDebug () << "kpToolSelection::draw" << thisPoint + << " startPoint=" << m_startPoint + << " normalizedRect=" << normalizedRect << endl; +#endif + + + // OPT: return when thisPoint == m_lastPoint so that e.g. when creating + // Points sel, press modifiers doesn't add multiple points in same + // place + + + bool nextDragHasBegun = true; + + + if (m_dragType == Create) + { + #if DEBUG_KP_TOOL_SELECTION && 1 + kdDebug () << "\tnot moving - resizing rect to" << normalizedRect + << endl; + kdDebug () << "\t\tcreateNOPTimer->isActive()=" + << m_createNOPTimer->isActive () + << " viewManhattanLength from startPoint=" + << m_viewUnderStartPoint->transformDocToViewX ((thisPoint - m_startPoint).manhattanLength ()) + << endl; + #endif + + if (m_createNOPTimer->isActive ()) + { + if (m_viewUnderStartPoint->transformDocToViewX ((thisPoint - m_startPoint).manhattanLength ()) <= 6) + { + #if DEBUG_KP_TOOL_SELECTION && 1 + kdDebug () << "\t\tsuppress accidental movement" << endl; + #endif + thisPoint = m_startPoint; + } + else + { + #if DEBUG_KP_TOOL_SELECTION && 1 + kdDebug () << "\t\tit's a \"big\" intended move - stop timer" << endl; + #endif + m_createNOPTimer->stop (); + } + } + + + // Prevent unintentional 1-pixel selections + if (!m_dragHasBegun && thisPoint == m_startPoint) + { + if (m_mode != kpToolSelection::Text) + { + #if DEBUG_KP_TOOL_SELECTION && 1 + kdDebug () << "\tnon-text NOP - return" << endl; + #endif + setUserShapePoints (thisPoint); + return; + } + else // m_mode == kpToolSelection::Text + { + // Attempt to deselect text box by clicking? + if (m_hadSelectionBeforeDrag) + { + #if DEBUG_KP_TOOL_SELECTION && 1 + kdDebug () << "\ttext box deselect - NOP - return" << endl; + #endif + setUserShapePoints (thisPoint); + return; + } + + // Drag-wise, this is a NOP so we'd normally return (hence + // m_dragHasBegun would not change). However, as a special + // case, allow user to create a text box using a single + // click. But don't set m_dragHasBegun for next iteration + // since it would be untrue. + // + // This makes sure that a single click creation of text box + // works even if draw() is invoked more than once at the + // same position (esp. with accidental drag suppression + // (above)). + nextDragHasBegun = false; + } + } + + + switch (m_mode) + { + case kpToolSelection::Rectangle: + { + const QRect usefulRect = normalizedRect.intersect (document ()->rect ()); + document ()->setSelection (kpSelection (kpSelection::Rectangle, usefulRect, + mainWindow ()->selectionTransparency ())); + + setUserShapePoints (m_startPoint, + QPoint (QMAX (0, QMIN (m_currentPoint.x (), document ()->width () - 1)), + QMAX (0, QMIN (m_currentPoint.y (), document ()->height () - 1)))); + break; + } + case kpToolSelection::Text: + { + const kpTextStyle textStyle = mainWindow ()->textStyle (); + + int minimumWidth, minimumHeight; + + // Just a click? + if (!m_dragHasBegun && thisPoint == m_startPoint) + { + #if DEBUG_KP_TOOL_SELECTION && 1 + kdDebug () << "\tclick creating text box" << endl; + #endif + + // (Click creating text box with RMB would not be obvious + // since RMB menu most likely hides text box immediately + // afterwards) + if (m_mouseButton == 1) + break; + + + minimumWidth = kpSelection::preferredMinimumWidthForTextStyle (textStyle); + if (thisPoint.x () >= m_startPoint.x ()) + { + if (m_startPoint.x () + minimumWidth - 1 >= document ()->width ()) + { + minimumWidth = QMAX (kpSelection::minimumWidthForTextStyle (textStyle), + document ()->width () - m_startPoint.x ()); + } + } + else + { + if (m_startPoint.x () - minimumWidth + 1 < 0) + { + minimumWidth = QMAX (kpSelection::minimumWidthForTextStyle (textStyle), + m_startPoint.x () + 1); + } + } + + minimumHeight = kpSelection::preferredMinimumHeightForTextStyle (textStyle); + if (thisPoint.y () >= m_startPoint.y ()) + { + if (m_startPoint.y () + minimumHeight - 1 >= document ()->height ()) + { + minimumHeight = QMAX (kpSelection::minimumHeightForTextStyle (textStyle), + document ()->height () - m_startPoint.y ()); + } + } + else + { + if (m_startPoint.y () - minimumHeight + 1 < 0) + { + minimumHeight = QMAX (kpSelection::minimumHeightForTextStyle (textStyle), + m_startPoint.y () + 1); + } + } + } + else + { + #if DEBUG_KP_TOOL_SELECTION && 1 + kdDebug () << "\tdrag creating text box" << endl; + #endif + minimumWidth = kpSelection::minimumWidthForTextStyle (textStyle); + minimumHeight = kpSelection::minimumHeightForTextStyle (textStyle); + } + + + if (normalizedRect.width () < minimumWidth) + { + if (thisPoint.x () >= m_startPoint.x ()) + normalizedRect.setWidth (minimumWidth); + else + normalizedRect.setX (normalizedRect.right () - minimumWidth + 1); + } + + if (normalizedRect.height () < minimumHeight) + { + if (thisPoint.y () >= m_startPoint.y ()) + normalizedRect.setHeight (minimumHeight); + else + normalizedRect.setY (normalizedRect.bottom () - minimumHeight + 1); + } + #if DEBUG_KP_TOOL_SELECTION && 1 + kdDebug () << "\t\tnormalizedRect=" << normalizedRect + << " kpSelection::preferredMinimumSize=" + << QSize (minimumWidth, minimumHeight) + << endl; + #endif + + QValueVector <QString> textLines (1, QString ()); + kpSelection sel (normalizedRect, textLines, textStyle); + + if (!m_currentCreateTextCommand) + { + m_currentCreateTextCommand = new kpToolSelectionCreateCommand ( + i18n ("Text: Create Box"), + sel, + mainWindow ()); + } + else + m_currentCreateTextCommand->setFromSelection (sel); + + viewManager ()->setTextCursorPosition (0, 0); + document ()->setSelection (sel); + + QPoint actualEndPoint = KP_INVALID_POINT; + if (m_startPoint == normalizedRect.topLeft ()) + actualEndPoint = normalizedRect.bottomRight (); + else if (m_startPoint == normalizedRect.bottomRight ()) + actualEndPoint = normalizedRect.topLeft (); + else if (m_startPoint == normalizedRect.topRight ()) + actualEndPoint = normalizedRect.bottomLeft (); + else if (m_startPoint == normalizedRect.bottomLeft ()) + actualEndPoint = normalizedRect.topRight (); + + setUserShapePoints (m_startPoint, actualEndPoint); + break; + } + case kpToolSelection::Ellipse: + document ()->setSelection (kpSelection (kpSelection::Ellipse, normalizedRect, + mainWindow ()->selectionTransparency ())); + setUserShapePoints (m_startPoint, m_currentPoint); + break; + case kpToolSelection::FreeForm: + QPointArray points; + + if (document ()->selection ()) + points = document ()->selection ()->points (); + + + // (not detached so will modify "points" directly but + // still need to call kpDocument::setSelection() to + // update screen) + + if (!m_dragHasBegun) + { + // We thought the drag at startPoint was a NOP + // but it turns out that it wasn't... + points.putPoints (points.count (), 1, m_startPoint.x (), m_startPoint.y ()); + } + + // TODO: there should be an upper limit on this before drawing the + // polygon becomes too slow + points.putPoints (points.count (), 1, thisPoint.x (), thisPoint.y ()); + + + document ()->setSelection (kpSelection (points, mainWindow ()->selectionTransparency ())); + #if DEBUG_KP_TOOL_SELECTION && 1 + kdDebug () << "\t\tfreeform; #points=" << document ()->selection ()->points ().count () << endl; + #endif + + setUserShapePoints (m_currentPoint); + break; + } + + viewManager ()->setSelectionBorderVisible (true); + } + else if (m_dragType == Move) + { + #if DEBUG_KP_TOOL_SELECTION && 1 + kdDebug () << "\tmoving selection" << endl; + #endif + + kpSelection *sel = document ()->selection (); + + QRect targetSelRect = QRect (thisPoint.x () - m_startDragFromSelectionTopLeft.x (), + thisPoint.y () - m_startDragFromSelectionTopLeft.y (), + sel->width (), + sel->height ()); + + #if DEBUG_KP_TOOL_SELECTION && 1 + kdDebug () << "\t\tstartPoint=" << m_startPoint + << " thisPoint=" << thisPoint + << " startDragFromSel=" << m_startDragFromSelectionTopLeft + << " targetSelRect=" << targetSelRect + << endl; + #endif + + // Try to make sure selection still intersects document so that it's + // reachable. + + if (targetSelRect.right () < 0) + targetSelRect.moveBy (-targetSelRect.right (), 0); + else if (targetSelRect.left () >= document ()->width ()) + targetSelRect.moveBy (document ()->width () - targetSelRect.left () - 1, 0); + + if (targetSelRect.bottom () < 0) + targetSelRect.moveBy (0, -targetSelRect.bottom ()); + else if (targetSelRect.top () >= document ()->height ()) + targetSelRect.moveBy (0, document ()->height () - targetSelRect.top () - 1); + + #if DEBUG_KP_TOOL_SELECTION && 1 + kdDebug () << "\t\t\tafter ensure sel rect clickable=" << targetSelRect << endl; + #endif + + + if (!m_dragHasBegun && + targetSelRect.topLeft () + m_startDragFromSelectionTopLeft == m_startPoint) + { + #if DEBUG_KP_TOOL_SELECTION && 1 + kdDebug () << "\t\t\t\tnop" << endl; + #endif + + + if (!m_RMBMoveUpdateGUITimer->isActive ()) + { + // (slotRMBMoveUpdateGUI() calls similar line) + setUserShapePoints (sel->topLeft ()); + } + + // Prevent both NOP drag-moves + return; + } + + + if (m_RMBMoveUpdateGUITimer->isActive ()) + { + m_RMBMoveUpdateGUITimer->stop (); + slotRMBMoveUpdateGUI (); + } + + + if (!sel->pixmap () && !m_currentPullFromDocumentCommand) + { + m_currentPullFromDocumentCommand = new kpToolSelectionPullFromDocumentCommand ( + QString::null/*uninteresting child of macro cmd*/, + mainWindow ()); + m_currentPullFromDocumentCommand->execute (); + } + + if (!m_currentMoveCommand) + { + m_currentMoveCommand = new kpToolSelectionMoveCommand ( + QString::null/*uninteresting child of macro cmd*/, + mainWindow ()); + m_currentMoveCommandIsSmear = false; + } + + + //viewManager ()->setQueueUpdates (); + //viewManager ()->setFastUpdates (); + + if (m_shiftPressed) + m_currentMoveCommandIsSmear = true; + + if (!m_dragHasBegun && (m_controlPressed || m_shiftPressed)) + m_currentMoveCommand->copyOntoDocument (); + + m_currentMoveCommand->moveTo (targetSelRect.topLeft ()); + + if (m_shiftPressed) + m_currentMoveCommand->copyOntoDocument (); + + //viewManager ()->restoreFastUpdates (); + //viewManager ()->restoreQueueUpdates (); + + QPoint start = m_currentMoveCommand->originalSelection ().topLeft (); + QPoint end = targetSelRect.topLeft (); + setUserShapePoints (start, end, false/*don't set size*/); + setUserShapeSize (end.x () - start.x (), end.y () - start.y ()); + } + else if (m_dragType == ResizeScale) + { + #if DEBUG_KP_TOOL_SELECTION && 1 + kdDebug () << "\tresize/scale" << endl; + #endif + + kpSelection *sel = document ()->selection (); + + if (!m_dragHasBegun && thisPoint == m_startPoint) + { + #if DEBUG_KP_TOOL_SELECTION && 1 + kdDebug () << "\t\tnop" << endl; + #endif + + setUserShapePoints (QPoint (sel->width (), sel->height ())); + return; + } + + + if (!sel->pixmap () && !m_currentPullFromDocumentCommand) + { + m_currentPullFromDocumentCommand = new kpToolSelectionPullFromDocumentCommand ( + QString::null/*uninteresting child of macro cmd*/, + mainWindow ()); + m_currentPullFromDocumentCommand->execute (); + } + + if (!m_currentResizeScaleCommand) + { + m_currentResizeScaleCommand = new kpToolSelectionResizeScaleCommand (mainWindow ()); + } + + + kpSelection originalSelection = m_currentResizeScaleCommand->originalSelection (); + const int oldWidth = originalSelection.width (); + const int oldHeight = originalSelection.height (); + + + // Determine new width. + + int userXSign = 0; + if (m_resizeScaleType & kpView::Left) + userXSign = -1; + else if (m_resizeScaleType & kpView::Right) + userXSign = +1; + + int newWidth = oldWidth + userXSign * (thisPoint.x () - m_startPoint.x ()); + + newWidth = QMAX (originalSelection.minimumWidth (), newWidth); + + + // Determine new height. + + int userYSign = 0; + if (m_resizeScaleType & kpView::Top) + userYSign = -1; + else if (m_resizeScaleType & kpView::Bottom) + userYSign = +1; + + int newHeight = oldHeight + userYSign * (thisPoint.y () - m_startPoint.y ()); + + newHeight = QMAX (originalSelection.minimumHeight (), newHeight); + + + // Keep aspect ratio? + if (m_shiftPressed && !sel->isText ()) + { + // Width changed more than height? At equality, favour width. + // Fix width, change height. + if ((userXSign ? double (newWidth) / oldWidth : 0) >= + (userYSign ? double (newHeight) / oldHeight : 0)) + { + newHeight = newWidth * oldHeight / oldWidth; + newHeight = QMAX (originalSelection.minimumHeight (), + newHeight); + } + // Height changed more than width? + // Fix height, change width. + else + { + newWidth = newHeight * oldWidth / oldHeight; + newWidth = QMAX (originalSelection.minimumWidth (), newWidth); + } + } + + + // Adjust x/y to new width/height for left/top resizes. + + int newX = originalSelection.x (); + int newY = originalSelection.y (); + + if (m_resizeScaleType & kpView::Left) + { + newX -= (newWidth - originalSelection.width ()); + } + + if (m_resizeScaleType & kpView::Top) + { + newY -= (newHeight - originalSelection.height ()); + } + + #if DEBUG_KP_TOOL_SELECTION && 1 + kdDebug () << "\t\tnewX=" << newX + << " newY=" << newY + << " newWidth=" << newWidth + << " newHeight=" << newHeight + << endl; + #endif + + + viewManager ()->setFastUpdates (); + m_currentResizeScaleCommand->resizeAndMoveTo (newWidth, newHeight, + QPoint (newX, newY), + true/*smooth scale delayed*/); + viewManager ()->restoreFastUpdates (); + + setUserShapePoints (QPoint (originalSelection.width (), + originalSelection.height ()), + QPoint (newWidth, + newHeight), + false/*don't set size*/); + setUserShapeSize (newWidth - originalSelection.width (), + newHeight - originalSelection.height ()); + } + + + m_dragHasBegun = nextDragHasBegun; +} + +// virtual +void kpToolSelection::cancelShape () +{ +#if DEBUG_KP_TOOL_SELECTION + kdDebug () << "kpToolSelection::cancelShape() mouseButton=" << m_mouseButton << endl; +#endif + + m_createNOPTimer->stop (); + m_RMBMoveUpdateGUITimer->stop (); + + + viewManager ()->setQueueUpdates (); + { + if (m_dragType == Move) + { + #if DEBUG_KP_TOOL_SELECTION + kdDebug () << "\twas drag moving - undo drag and undo acquire" << endl; + #endif + + if (m_currentMoveCommand) + { + #if DEBUG_KP_TOOL_SELECTION + kdDebug () << "\t\tundo currentMoveCommand" << endl; + #endif + m_currentMoveCommand->finalize (); + m_currentMoveCommand->unexecute (); + delete m_currentMoveCommand; + m_currentMoveCommand = 0; + + if (document ()->selection ()->isText ()) + viewManager ()->setTextCursorBlinkState (true); + } + } + else if (m_dragType == Create) + { + #if DEBUG_KP_TOOL_SELECTION + kdDebug () << "\twas creating sel - kill" << endl; + #endif + + // TODO: should we give the user back the selection s/he had before (if any)? + document ()->selectionDelete (); + + if (m_currentCreateTextCommand) + { + delete m_currentCreateTextCommand; + m_currentCreateTextCommand = 0; + } + } + else if (m_dragType == ResizeScale) + { + #if DEBUG_KP_TOOL_SELECTION + kdDebug () << "\twas resize/scale sel - kill" << endl; + #endif + + if (m_currentResizeScaleCommand) + { + #if DEBUG_KP_TOOL_SELECTION + kdDebug () << "\t\tundo currentResizeScaleCommand" << endl; + #endif + m_currentResizeScaleCommand->finalize (); // (unneeded but let's be safe) + m_currentResizeScaleCommand->unexecute (); + delete m_currentResizeScaleCommand; + m_currentResizeScaleCommand = 0; + + if (document ()->selection ()->isText ()) + viewManager ()->setTextCursorBlinkState (true); + } + } + + + if (m_currentPullFromDocumentCommand) + { + #if DEBUG_KP_TOOL_SELECTION + kdDebug () << "\t\tundo pullFromDocumentCommand" << endl; + #endif + m_currentPullFromDocumentCommand->unexecute (); + delete m_currentPullFromDocumentCommand; + m_currentPullFromDocumentCommand = 0; + } + + + viewManager ()->setSelectionBorderVisible (true); + viewManager ()->setSelectionBorderFinished (true); + viewManager ()->setTextCursorEnabled (m_mode == Text && true); + } + viewManager ()->restoreQueueUpdates (); + + + m_dragType = Unknown; + m_cancelledShapeButStillHoldingButtons = true; + setUserMessage (i18n ("Let go of all the mouse buttons.")); +} + +// virtual +void kpToolSelection::releasedAllButtons () +{ + m_cancelledShapeButStillHoldingButtons = false; + setUserMessage (haventBegunDrawUserMessage ()); +} + +// virtual +void kpToolSelection::endDraw (const QPoint & /*thisPoint*/, const QRect & /*normalizedRect*/) +{ + m_createNOPTimer->stop (); + m_RMBMoveUpdateGUITimer->stop (); + + + viewManager ()->setQueueUpdates (); + { + if (m_currentCreateTextCommand) + { + commandHistory ()->addCommand (m_currentCreateTextCommand, false/*no exec*/); + m_currentCreateTextCommand = 0; + } + + kpMacroCommand *cmd = 0; + if (m_currentMoveCommand) + { + if (m_currentMoveCommandIsSmear) + { + cmd = new kpMacroCommand (i18n ("%1: Smear") + .arg (document ()->selection ()->name ()), + mainWindow ()); + } + else + { + cmd = new kpMacroCommand ((document ()->selection ()->isText () ? + i18n ("Text: Move Box") : + i18n ("Selection: Move")), + mainWindow ()); + } + + if (document ()->selection ()->isText ()) + viewManager ()->setTextCursorBlinkState (true); + } + else if (m_currentResizeScaleCommand) + { + cmd = new kpMacroCommand (m_currentResizeScaleCommand->kpNamedCommand::name (), + mainWindow ()); + + if (document ()->selection ()->isText ()) + viewManager ()->setTextCursorBlinkState (true); + } + + if (m_currentPullFromDocumentCommand) + { + if (!m_currentMoveCommand && !m_currentResizeScaleCommand) + { + kdError () << "kpToolSelection::endDraw() pull without move nor resize/scale" << endl; + delete m_currentPullFromDocumentCommand; + m_currentPullFromDocumentCommand = 0; + } + else + { + kpSelection selection; + + if (m_currentMoveCommand) + selection = m_currentMoveCommand->originalSelection (); + else if (m_currentResizeScaleCommand) + selection = m_currentResizeScaleCommand->originalSelection (); + + // just the border + selection.setPixmap (QPixmap ()); + + kpCommand *createCommand = new kpToolSelectionCreateCommand ( + i18n ("Selection: Create"), + selection, + mainWindow ()); + + if (kpToolSelectionCreateCommand::nextUndoCommandIsCreateBorder (commandHistory ())) + commandHistory ()->setNextUndoCommand (createCommand); + else + commandHistory ()->addCommand (createCommand, + false/*no exec - user already dragged out sel*/); + + + cmd->addCommand (m_currentPullFromDocumentCommand); + m_currentPullFromDocumentCommand = 0; + } + } + + if (m_currentMoveCommand) + { + m_currentMoveCommand->finalize (); + cmd->addCommand (m_currentMoveCommand); + m_currentMoveCommand = 0; + + if (document ()->selection ()->isText ()) + viewManager ()->setTextCursorBlinkState (true); + } + + if (m_currentResizeScaleCommand) + { + m_currentResizeScaleCommand->finalize (); + cmd->addCommand (m_currentResizeScaleCommand); + m_currentResizeScaleCommand = 0; + + if (document ()->selection ()->isText ()) + viewManager ()->setTextCursorBlinkState (true); + } + + if (cmd) + commandHistory ()->addCommand (cmd, false/*no exec*/); + + viewManager ()->setSelectionBorderVisible (true); + viewManager ()->setSelectionBorderFinished (true); + viewManager ()->setTextCursorEnabled (m_mode == Text && true); + } + viewManager ()->restoreQueueUpdates (); + + + m_dragType = Unknown; + setUserMessage (haventBegunDrawUserMessage ()); + + + if (m_mouseButton == 1/*right*/) + popupRMBMenu (); +} + + +// protected virtual [base kpTool] +void kpToolSelection::keyPressEvent (QKeyEvent *e) +{ +#if DEBUG_KP_TOOL_SELECTION && 0 + kdDebug () << "kpToolSelection::keyPressEvent(e->text='" << e->text () << "')" << endl; +#endif + + + e->ignore (); + + + if (document ()->selection () && + !hasBegunDraw () && + e->key () == Qt::Key_Escape) + { + #if DEBUG_KP_TOOL_SELECTION && 0 + kdDebug () << "\tescape pressed with sel when not begun draw - deselecting" << endl; + #endif + + pushOntoDocument (); + e->accept (); + } + + + if (!e->isAccepted ()) + { + #if DEBUG_KP_TOOL_SELECTION && 0 + kdDebug () << "\tkey processing did not accept (text was '" + << e->text () + << "') - passing on event to kpTool" + << endl; + #endif + + kpTool::keyPressEvent (e); + return; + } +} + + +// private slot +void kpToolSelection::selectionTransparencyChanged (const QString & /*name*/) +{ +#if 0 +#if DEBUG_KP_TOOL_SELECTION + kdDebug () << "kpToolSelection::selectionTransparencyChanged(" << name << ")" << endl; +#endif + + if (mainWindow ()->settingSelectionTransparency ()) + { + #if DEBUG_KP_TOOL_SELECTION + kdDebug () << "\trecursion - abort setting selection transparency: " + << mainWindow ()->settingSelectionTransparency () << endl; + #endif + return; + } + + if (document ()->selection ()) + { + #if DEBUG_KP_TOOL_SELECTION + kdDebug () << "\thave sel - set transparency" << endl; + #endif + + kpSelectionTransparency oldST = document ()->selection ()->transparency (); + kpSelectionTransparency st = mainWindow ()->selectionTransparency (); + + // TODO: This "NOP" check causes us a great deal of trouble e.g.: + // + // Select a solid red rectangle. + // Switch to transparent and set red as the background colour. + // (the selection is now invisible) + // Invert Colours. + // (the selection is now cyan) + // Change the background colour to green. + // (no command is added to undo this as the selection does not change) + // Undo. + // The rectangle is no longer invisible. + // + //if (document ()->selection ()->setTransparency (st, true/*check harder for no change in mask*/)) + + document ()->selection ()->setTransparency (st); + if (true) + { + #if DEBUG_KP_TOOL_SELECTION + kdDebug () << "\t\twhich changed the pixmap" << endl; + #endif + + commandHistory ()->addCommand (new kpToolSelectionTransparencyCommand ( + i18n ("Selection: Transparency"), // name, + st, oldST, + mainWindow ()), + false/* no exec*/); + } + } +#endif + + // TODO: I've duplicated the code (see below 3x) to make sure + // kpSelectionTransparency(oldST)::transparentColor() is defined + // and not taken from kpDocument (where it may not be defined because + // the transparency may be opaque). + // + // That way kpToolSelectionTransparencyCommand can force set colours. +} + + +// protected slot virtual +void kpToolSelection::slotIsOpaqueChanged () +{ +#if DEBUG_KP_TOOL_SELECTION + kdDebug () << "kpToolSelection::slotIsOpaqueChanged()" << endl; +#endif + + if (mainWindow ()->settingSelectionTransparency ()) + { + #if DEBUG_KP_TOOL_SELECTION + kdDebug () << "\trecursion - abort setting selection transparency: " + << mainWindow ()->settingSelectionTransparency () << endl; + #endif + return; + } + + if (document ()->selection ()) + { + #if DEBUG_KP_TOOL_SELECTION + kdDebug () << "\thave sel - set transparency" << endl; + #endif + + QApplication::setOverrideCursor (Qt::waitCursor); + + if (hasBegunShape ()) + endShapeInternal (); + + kpSelectionTransparency st = mainWindow ()->selectionTransparency (); + kpSelectionTransparency oldST = st; + oldST.setOpaque (!oldST.isOpaque ()); + + document ()->selection ()->setTransparency (st); + commandHistory ()->addCommand (new kpToolSelectionTransparencyCommand ( + st.isOpaque () ? + i18n ("Selection: Opaque") : + i18n ("Selection: Transparent"), + st, oldST, + mainWindow ()), + false/* no exec*/); + + QApplication::restoreOverrideCursor (); + } +} + +// protected slot virtual [base kpTool] +void kpToolSelection::slotBackgroundColorChanged (const kpColor &) +{ +#if DEBUG_KP_TOOL_SELECTION + kdDebug () << "kpToolSelection::slotBackgroundColorChanged()" << endl; +#endif + + if (mainWindow ()->settingSelectionTransparency ()) + { + #if DEBUG_KP_TOOL_SELECTION + kdDebug () << "\trecursion - abort setting selection transparency: " + << mainWindow ()->settingSelectionTransparency () << endl; + #endif + return; + } + + if (document ()->selection ()) + { + #if DEBUG_KP_TOOL_SELECTION + kdDebug () << "\thave sel - set transparency" << endl; + #endif + + QApplication::setOverrideCursor (Qt::waitCursor); + + kpSelectionTransparency st = mainWindow ()->selectionTransparency (); + kpSelectionTransparency oldST = st; + oldST.setTransparentColor (oldBackgroundColor ()); + + document ()->selection ()->setTransparency (st); + commandHistory ()->addCommand (new kpToolSelectionTransparencyCommand ( + i18n ("Selection: Transparency Color"), + st, oldST, + mainWindow ()), + false/* no exec*/); + + QApplication::restoreOverrideCursor (); + } +} + +// protected slot virtual [base kpTool] +void kpToolSelection::slotColorSimilarityChanged (double, int) +{ +#if DEBUG_KP_TOOL_SELECTION + kdDebug () << "kpToolSelection::slotColorSimilarityChanged()" << endl; +#endif + + if (mainWindow ()->settingSelectionTransparency ()) + { + #if DEBUG_KP_TOOL_SELECTION + kdDebug () << "\trecursion - abort setting selection transparency: " + << mainWindow ()->settingSelectionTransparency () << endl; + #endif + return; + } + + if (document ()->selection ()) + { + #if DEBUG_KP_TOOL_SELECTION + kdDebug () << "\thave sel - set transparency" << endl; + #endif + + QApplication::setOverrideCursor (Qt::waitCursor); + + kpSelectionTransparency st = mainWindow ()->selectionTransparency (); + kpSelectionTransparency oldST = st; + oldST.setColorSimilarity (oldColorSimilarity ()); + + document ()->selection ()->setTransparency (st); + commandHistory ()->addCommand (new kpToolSelectionTransparencyCommand ( + i18n ("Selection: Transparency Color Similarity"), + st, oldST, + mainWindow ()), + false/* no exec*/); + + QApplication::restoreOverrideCursor (); + } +} + + +/* + * kpToolSelectionCreateCommand + */ + +kpToolSelectionCreateCommand::kpToolSelectionCreateCommand (const QString &name, + const kpSelection &fromSelection, + kpMainWindow *mainWindow) + : kpNamedCommand (name, mainWindow), + m_fromSelection (0), + m_textRow (0), m_textCol (0) +{ + setFromSelection (fromSelection); +} + +kpToolSelectionCreateCommand::~kpToolSelectionCreateCommand () +{ + delete m_fromSelection; +} + + +// public virtual [base kpCommand] +int kpToolSelectionCreateCommand::size () const +{ + return kpPixmapFX::selectionSize (m_fromSelection); +} + + +// public static +bool kpToolSelectionCreateCommand::nextUndoCommandIsCreateBorder ( + kpCommandHistory *commandHistory) +{ + if (!commandHistory) + return false; + + kpCommand *cmd = commandHistory->nextUndoCommand (); + if (!cmd) + return false; + + kpToolSelectionCreateCommand *c = dynamic_cast <kpToolSelectionCreateCommand *> (cmd); + if (!c) + return false; + + const kpSelection *sel = c->fromSelection (); + if (!sel) + return false; + + return (!sel->pixmap ()); +} + + +// public +const kpSelection *kpToolSelectionCreateCommand::fromSelection () const +{ + return m_fromSelection; +} + +// public +void kpToolSelectionCreateCommand::setFromSelection (const kpSelection &fromSelection) +{ + delete m_fromSelection; + m_fromSelection = new kpSelection (fromSelection); +} + +// public virtual [base kpCommand] +void kpToolSelectionCreateCommand::execute () +{ +#if DEBUG_KP_TOOL_SELECTION + kdDebug () << "kpToolSelectionCreateCommand::execute()" << endl; +#endif + + kpDocument *doc = document (); + if (!doc) + { + kdError () << "kpToolSelectionCreateCommand::execute() without doc" << endl; + return; + } + + if (m_fromSelection) + { + #if DEBUG_KP_TOOL_SELECTION + kdDebug () << "\tusing fromSelection" << endl; + kdDebug () << "\t\thave sel=" << doc->selection () + << " pixmap=" << (doc->selection () ? doc->selection ()->pixmap () : 0) + << endl; + #endif + if (!m_fromSelection->isText ()) + { + if (m_fromSelection->transparency () != m_mainWindow->selectionTransparency ()) + m_mainWindow->setSelectionTransparency (m_fromSelection->transparency ()); + } + else + { + if (m_fromSelection->textStyle () != m_mainWindow->textStyle ()) + m_mainWindow->setTextStyle (m_fromSelection->textStyle ()); + } + + m_mainWindow->viewManager ()->setTextCursorPosition (m_textRow, m_textCol); + doc->setSelection (*m_fromSelection); + + if (m_mainWindow->tool ()) + m_mainWindow->tool ()->somethingBelowTheCursorChanged (); + } +} + +// public virtual [base kpCommand] +void kpToolSelectionCreateCommand::unexecute () +{ + kpDocument *doc = document (); + if (!doc) + { + kdError () << "kpToolSelectionCreateCommand::unexecute() without doc" << endl; + return; + } + + if (!doc->selection ()) + { + // Was just a border that got deselected? + if (m_fromSelection && !m_fromSelection->pixmap ()) + return; + + kdError () << "kpToolSelectionCreateCommand::unexecute() without sel region" << endl; + return; + } + + m_textRow = m_mainWindow->viewManager ()->textCursorRow (); + m_textCol = m_mainWindow->viewManager ()->textCursorCol (); + + doc->selectionDelete (); + + if (m_mainWindow->tool ()) + m_mainWindow->tool ()->somethingBelowTheCursorChanged (); +} + + +/* + * kpToolSelectionPullFromDocumentCommand + */ + +kpToolSelectionPullFromDocumentCommand::kpToolSelectionPullFromDocumentCommand (const QString &name, + kpMainWindow *mainWindow) + : kpNamedCommand (name, mainWindow), + m_backgroundColor (mainWindow ? mainWindow->backgroundColor () : kpColor::invalid), + m_originalSelectionRegion (0) +{ +#if DEBUG_KP_TOOL_SELECTION && 1 + kdDebug () << "kpToolSelectionPullFromDocumentCommand::<ctor>() mainWindow=" + << m_mainWindow + << endl; +#endif +} + +kpToolSelectionPullFromDocumentCommand::~kpToolSelectionPullFromDocumentCommand () +{ + delete m_originalSelectionRegion; +} + + +// public virtual [base kpCommand] +int kpToolSelectionPullFromDocumentCommand::size () const +{ + return kpPixmapFX::selectionSize (m_originalSelectionRegion); +} + + +// public virtual [base kpCommand] +void kpToolSelectionPullFromDocumentCommand::execute () +{ +#if DEBUG_KP_TOOL_SELECTION && 1 + kdDebug () << "kpToolSelectionPullFromDocumentCommand::execute()" << endl; +#endif + + kpDocument *doc = document (); + + if (!doc) + { + kdError () << "kpToolSelectionPullFromDocumentCommand::execute() without doc" << endl; + return; + } + + kpViewManager *vm = m_mainWindow ? m_mainWindow->viewManager () : 0; + if (vm) + vm->setQueueUpdates (); + + // In case the user CTRL+Z'ed, selected a random region to throw us off + // and then CTRL+Shift+Z'ed putting us here. Make sure we pull from the + // originally requested region - not the random one. + if (m_originalSelectionRegion) + { + if (m_originalSelectionRegion->transparency () != m_mainWindow->selectionTransparency ()) + m_mainWindow->setSelectionTransparency (m_originalSelectionRegion->transparency ()); + + doc->setSelection (*m_originalSelectionRegion); + } + else + { + // must have selection region but not pixmap + if (!doc->selection () || doc->selection ()->pixmap ()) + { + kdError () << "kpToolSelectionPullFromDocumentCommand::execute() sel=" + << doc->selection () + << " pixmap=" + << (doc->selection () ? doc->selection ()->pixmap () : 0) + << endl; + if (vm) + vm->restoreQueueUpdates (); + return; + } + } + + doc->selectionPullFromDocument (m_backgroundColor); + + if (vm) + vm->restoreQueueUpdates (); +} + +// public virtual [base kpCommand] +void kpToolSelectionPullFromDocumentCommand::unexecute () +{ +#if DEBUG_KP_TOOL_SELECTION && 1 + kdDebug () << "kpToolSelectionPullFromDocumentCommand::unexecute()" << endl; +#endif + + kpDocument *doc = document (); + + if (!doc) + { + kdError () << "kpToolSelectionPullFromDocumentCommand::unexecute() without doc" << endl; + return; + } + + // must have selection pixmap + if (!doc->selection () || !doc->selection ()->pixmap ()) + { + kdError () << "kpToolSelectionPullFromDocumentCommand::unexecute() sel=" + << doc->selection () + << " pixmap=" + << (doc->selection () ? doc->selection ()->pixmap () : 0) + << endl; + return; + } + + + // 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->selection ()->setPixmap (QPixmap ()); + + delete m_originalSelectionRegion; + m_originalSelectionRegion = new kpSelection (*doc->selection ()); +} + + +/* + * kpToolSelectionTransparencyCommand + */ + +kpToolSelectionTransparencyCommand::kpToolSelectionTransparencyCommand (const QString &name, + const kpSelectionTransparency &st, + const kpSelectionTransparency &oldST, + kpMainWindow *mainWindow) + : kpNamedCommand (name, mainWindow), + m_st (st), + m_oldST (oldST) +{ +} + +kpToolSelectionTransparencyCommand::~kpToolSelectionTransparencyCommand () +{ +} + + +// public virtual [base kpCommand] +int kpToolSelectionTransparencyCommand::size () const +{ + return 0; +} + + +// public virtual [base kpCommand] +void kpToolSelectionTransparencyCommand::execute () +{ +#if DEBUG_KP_TOOL_SELECTION && 1 + kdDebug () << "kpToolSelectionTransparencyCommand::execute()" << endl; +#endif + kpDocument *doc = document (); + if (!doc) + return; + + QApplication::setOverrideCursor (Qt::waitCursor); + + m_mainWindow->setSelectionTransparency (m_st, true/*force colour change*/); + + if (doc->selection ()) + doc->selection ()->setTransparency (m_st); + + QApplication::restoreOverrideCursor (); +} + +// public virtual [base kpCommand] +void kpToolSelectionTransparencyCommand::unexecute () +{ +#if DEBUG_KP_TOOL_SELECTION && 1 + kdDebug () << "kpToolSelectionTransparencyCommand::unexecute()" << endl; +#endif + + kpDocument *doc = document (); + if (!doc) + return; + + QApplication::setOverrideCursor (Qt::waitCursor); + + m_mainWindow->setSelectionTransparency (m_oldST, true/*force colour change*/); + + if (doc->selection ()) + doc->selection ()->setTransparency (m_oldST); + + QApplication::restoreOverrideCursor (); +} + + +/* + * kpToolSelectionMoveCommand + */ + +kpToolSelectionMoveCommand::kpToolSelectionMoveCommand (const QString &name, + kpMainWindow *mainWindow) + : kpNamedCommand (name, mainWindow) +{ + kpDocument *doc = document (); + if (doc && doc->selection ()) + { + m_startPoint = m_endPoint = doc->selection ()->topLeft (); + } +} + +kpToolSelectionMoveCommand::~kpToolSelectionMoveCommand () +{ +} + + +// public +kpSelection kpToolSelectionMoveCommand::originalSelection () const +{ + kpDocument *doc = document (); + if (!doc || !doc->selection ()) + { + kdError () << "kpToolSelectionMoveCommand::originalSelection() doc=" + << doc + << " sel=" + << (doc ? doc->selection () : 0) + << endl; + return kpSelection (kpSelection::Rectangle, QRect ()); + } + + kpSelection selection = *doc->selection(); + selection.moveTo (m_startPoint); + + return selection; +} + + +// public virtual [base kpComand] +int kpToolSelectionMoveCommand::size () const +{ + return kpPixmapFX::pixmapSize (m_oldDocumentPixmap) + + kpPixmapFX::pointArraySize (m_copyOntoDocumentPoints); +} + + +// public virtual [base kpCommand] +void kpToolSelectionMoveCommand::execute () +{ +#if DEBUG_KP_TOOL_SELECTION && 1 + kdDebug () << "kpToolSelectionMoveCommand::execute()" << endl; +#endif + + kpDocument *doc = document (); + if (!doc) + { + kdError () << "kpToolSelectionMoveCommand::execute() no doc" << endl; + return; + } + + kpSelection *sel = doc->selection (); + + // have to have pulled pixmap by now + if (!sel || !sel->pixmap ()) + { + kdError () << "kpToolSelectionMoveCommand::execute() but haven't pulled pixmap yet: " + << "sel=" << sel << " sel->pixmap=" << (sel ? sel->pixmap () : 0) + << endl; + return; + } + + kpViewManager *vm = m_mainWindow ? m_mainWindow->viewManager () : 0; + + if (vm) + vm->setQueueUpdates (); + + QPointArray::ConstIterator copyOntoDocumentPointsEnd = m_copyOntoDocumentPoints.end (); + for (QPointArray::ConstIterator it = m_copyOntoDocumentPoints.begin (); + it != copyOntoDocumentPointsEnd; + it++) + { + sel->moveTo (*it); + doc->selectionCopyOntoDocument (); + } + + sel->moveTo (m_endPoint); + + if (m_mainWindow->tool ()) + m_mainWindow->tool ()->somethingBelowTheCursorChanged (); + + if (vm) + vm->restoreQueueUpdates (); +} + +// public virtual [base kpCommand] +void kpToolSelectionMoveCommand::unexecute () +{ +#if DEBUG_KP_TOOL_SELECTION && 1 + kdDebug () << "kpToolSelectionMoveCommand::unexecute()" << endl; +#endif + + kpDocument *doc = document (); + if (!doc) + { + kdError () << "kpToolSelectionMoveCommand::unexecute() no doc" << endl; + return; + } + + kpSelection *sel = doc->selection (); + + // have to have pulled pixmap by now + if (!sel || !sel->pixmap ()) + { + kdError () << "kpToolSelectionMoveCommand::unexecute() but haven't pulled pixmap yet: " + << "sel=" << sel << " sel->pixmap=" << (sel ? sel->pixmap () : 0) + << endl; + return; + } + + kpViewManager *vm = m_mainWindow ? m_mainWindow->viewManager () : 0; + + if (vm) + vm->setQueueUpdates (); + + if (!m_oldDocumentPixmap.isNull ()) + doc->setPixmapAt (m_oldDocumentPixmap, m_documentBoundingRect.topLeft ()); +#if DEBUG_KP_TOOL_SELECTION && 1 + kdDebug () << "\tmove to startPoint=" << m_startPoint << endl; +#endif + sel->moveTo (m_startPoint); + + if (m_mainWindow->tool ()) + m_mainWindow->tool ()->somethingBelowTheCursorChanged (); + + if (vm) + vm->restoreQueueUpdates (); +} + +// public +void kpToolSelectionMoveCommand::moveTo (const QPoint &point, bool moveLater) +{ +#if DEBUG_KP_TOOL_SELECTION && 0 + kdDebug () << "kpToolSelectionMoveCommand::moveTo" << point + << " moveLater=" << moveLater + <<endl; +#endif + + if (!moveLater) + { + kpDocument *doc = document (); + if (!doc) + { + kdError () << "kpToolSelectionMoveCommand::moveTo() without doc" << endl; + return; + } + + kpSelection *sel = doc->selection (); + + // have to have pulled pixmap by now + if (!sel) + { + kdError () << "kpToolSelectionMoveCommand::moveTo() no sel region" << endl; + return; + } + + if (!sel->pixmap ()) + { + kdError () << "kpToolSelectionMoveCommand::moveTo() no sel pixmap" << endl; + return; + } + + 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 + kdDebug () << "kpToolSelectionMoveCommand::copyOntoDocument()" << endl; +#endif + + kpDocument *doc = document (); + if (!doc) + return; + + kpSelection *sel = doc->selection (); + + // have to have pulled pixmap by now + if (!sel) + { + kdError () << "\tkpToolSelectionMoveCommand::copyOntoDocument() without sel region" << endl; + return; + } + + if (!sel->pixmap ()) + { + kdError () << "kpToolSelectionMoveCommand::moveTo() no sel pixmap" << endl; + return; + } + + if (m_oldDocumentPixmap.isNull ()) + m_oldDocumentPixmap = *doc->pixmap (); + + QRect selBoundingRect = sel->boundingRect (); + m_documentBoundingRect.unite (selBoundingRect); + + doc->selectionCopyOntoDocument (); + + m_copyOntoDocumentPoints.putPoints (m_copyOntoDocumentPoints.count (), + 1, + selBoundingRect.x (), + selBoundingRect.y ()); +} + +// public +void kpToolSelectionMoveCommand::finalize () +{ + if (!m_oldDocumentPixmap.isNull () && !m_documentBoundingRect.isNull ()) + { + m_oldDocumentPixmap = kpTool::neededPixmap (m_oldDocumentPixmap, + m_documentBoundingRect); + } +} + + +/* + * kpToolSelectionResizeScaleCommand + */ + +kpToolSelectionResizeScaleCommand::kpToolSelectionResizeScaleCommand ( + kpMainWindow *mainWindow) + : kpNamedCommand (mainWindow->document ()->selection ()->isText () ? + i18n ("Text: Resize Box") : + i18n ("Selection: Smooth Scale"), + mainWindow), + m_smoothScaleTimer (new QTimer (this)) +{ + m_originalSelection = *selection (); + + m_newTopLeft = selection ()->topLeft (); + m_newWidth = selection ()->width (); + m_newHeight = selection ()->height (); + + connect (m_smoothScaleTimer, SIGNAL (timeout ()), + this, SLOT (resizeScaleAndMove ())); +} + +kpToolSelectionResizeScaleCommand::~kpToolSelectionResizeScaleCommand () +{ +} + + +// public virtual +int kpToolSelectionResizeScaleCommand::size () const +{ + return m_originalSelection.size (); +} + + +// public +kpSelection kpToolSelectionResizeScaleCommand::originalSelection () const +{ + return m_originalSelection; +} + + +// 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 + kdDebug () << "kpToolSelectionResizeScaleCommand::resizeScaleAndMove(delayed=" + << delayed << ")" << endl; +#endif + + killSmoothScaleTimer (); + + kpSelection newSel; + + if (selection ()->isText ()) + { + newSel = m_originalSelection; + newSel.textResize (m_newWidth, m_newHeight); + } + else + { + newSel = kpSelection (kpSelection::Rectangle, + QRect (m_originalSelection.x (), + m_originalSelection.y (), + m_newWidth, + m_newHeight), + kpPixmapFX::scale (*m_originalSelection.pixmap (), + m_newWidth, m_newHeight, + !delayed/*if not delayed, smooth*/), + m_originalSelection.transparency ()); + + if (delayed) + { + // Call self with delayed==false in 200ms + m_smoothScaleTimer->start (200/*ms*/, true/*single shot*/); + } + } + + newSel.moveTo (m_newTopLeft); + + m_mainWindow->document ()->setSelection (newSel); +} + +// protected slots +void kpToolSelectionResizeScaleCommand::resizeScaleAndMove () +{ +#if DEBUG_KP_TOOL_SELECTION + kdDebug () << "kpToolSelectionResizeScaleCommand::resizeScaleAndMove()" << endl; +#endif + resizeScaleAndMove (false/*no delay*/); +} + + +// public +void kpToolSelectionResizeScaleCommand::finalize () +{ +#if DEBUG_KP_TOOL_SELECTION + kdDebug () << "kpToolSelectionResizeScaleCommand::finalize()" + << " smoothScaleTimer->isActive=" + << m_smoothScaleTimer->isActive () + << endl; +#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 (); + + if (m_mainWindow->tool ()) + m_mainWindow->tool ()->somethingBelowTheCursorChanged (); + + QApplication::restoreOverrideCursor (); +} + +// public virtual [base kpToolResizeScaleCommand] +void kpToolSelectionResizeScaleCommand::unexecute () +{ + QApplication::setOverrideCursor (Qt::waitCursor); + + killSmoothScaleTimer (); + + m_mainWindow->document ()->setSelection (m_originalSelection); + + if (m_mainWindow->tool ()) + m_mainWindow->tool ()->somethingBelowTheCursorChanged (); + + QApplication::restoreOverrideCursor (); +} + + +/* + * kpToolSelectionDestroyCommand + */ + +kpToolSelectionDestroyCommand::kpToolSelectionDestroyCommand (const QString &name, + bool pushOntoDocument, + kpMainWindow *mainWindow) + : kpNamedCommand (name, mainWindow), + m_pushOntoDocument (pushOntoDocument), + m_oldSelection (0) +{ +} + +kpToolSelectionDestroyCommand::~kpToolSelectionDestroyCommand () +{ + delete m_oldSelection; +} + + +// public virtual [base kpCommand] +int kpToolSelectionDestroyCommand::size () const +{ + return kpPixmapFX::pixmapSize (m_oldDocPixmap) + + kpPixmapFX::selectionSize (m_oldSelection); +} + + +// public virtual [base kpCommand] +void kpToolSelectionDestroyCommand::execute () +{ +#if DEBUG_KP_TOOL_SELECTION + kdDebug () << "kpToolSelectionDestroyCommand::execute () CALLED" << endl; +#endif + + kpDocument *doc = document (); + if (!doc) + { + kdError () << "kpToolSelectionDestroyCommand::execute() without doc" << endl; + return; + } + + if (!doc->selection ()) + { + kdError () << "kpToolSelectionDestroyCommand::execute() without sel region" << endl; + return; + } + + m_textRow = m_mainWindow->viewManager ()->textCursorRow (); + m_textCol = m_mainWindow->viewManager ()->textCursorCol (); + + m_oldSelection = new kpSelection (*doc->selection ()); + if (m_pushOntoDocument) + { + m_oldDocPixmap = doc->getPixmapAt (doc->selection ()->boundingRect ()); + doc->selectionPushOntoDocument (); + } + else + doc->selectionDelete (); + + if (m_mainWindow->tool ()) + m_mainWindow->tool ()->somethingBelowTheCursorChanged (); +} + +// public virtual [base kpCommand] +void kpToolSelectionDestroyCommand::unexecute () +{ +#if DEBUG_KP_TOOL_SELECTION + kdDebug () << "kpToolSelectionDestroyCommand::unexecute () CALLED" << endl; +#endif + + kpDocument *doc = document (); + if (!doc) + { + kdError () << "kpToolSelectionDestroyCommand::unexecute() without doc" << endl; + return; + } + + if (doc->selection ()) + { + // not error because it's possible that the user dragged out a new + // region (without pulling pixmap), and then CTRL+Z + #if DEBUG_KP_TOOL_SELECTION + kdDebug () << "kpToolSelectionDestroyCommand::unexecute() already has sel region" << endl; + #endif + + if (doc->selection ()->pixmap ()) + { + kdError () << "kpToolSelectionDestroyCommand::unexecute() already has sel pixmap" << endl; + return; + } + } + + if (!m_oldSelection) + { + kdError () << "kpToolSelectionDestroyCommand::unexecute() without old sel" << endl; + return; + } + + if (m_pushOntoDocument) + { + #if DEBUG_KP_TOOL_SELECTION + kdDebug () << "\tunpush oldDocPixmap onto doc first" << endl; + #endif + doc->setPixmapAt (m_oldDocPixmap, m_oldSelection->topLeft ()); + } + +#if DEBUG_KP_TOOL_SELECTION + kdDebug () << "\tsetting selection to: rect=" << m_oldSelection->boundingRect () + << " pixmap=" << m_oldSelection->pixmap () + << " pixmap.isNull()=" << (m_oldSelection->pixmap () + ? + m_oldSelection->pixmap ()->isNull () + : + true) + << endl; +#endif + if (!m_oldSelection->isText ()) + { + if (m_oldSelection->transparency () != m_mainWindow->selectionTransparency ()) + m_mainWindow->setSelectionTransparency (m_oldSelection->transparency ()); + } + else + { + if (m_oldSelection->textStyle () != m_mainWindow->textStyle ()) + m_mainWindow->setTextStyle (m_oldSelection->textStyle ()); + } + + m_mainWindow->viewManager ()->setTextCursorPosition (m_textRow, m_textCol); + doc->setSelection (*m_oldSelection); + + if (m_mainWindow->tool ()) + m_mainWindow->tool ()->somethingBelowTheCursorChanged (); + + delete m_oldSelection; + m_oldSelection = 0; +} + +#include <kptoolselection.moc> diff --git a/kolourpaint/tools/kptoolselection.h b/kolourpaint/tools/kptoolselection.h new file mode 100644 index 00000000..ee978a15 --- /dev/null +++ b/kolourpaint/tools/kptoolselection.h @@ -0,0 +1,313 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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_selection_h__ +#define __kp_tool_selection_h__ + + +#include <qpixmap.h> +#include <qpoint.h> +#include <qpointarray.h> +#include <qrect.h> + +#include <kpcolor.h> +#include <kpcommandhistory.h> +#include <kpselection.h> +#include <kpselectiontransparency.h> +#include <kptool.h> + + +class QPoint; +class QRect; +class QTimer; + +class kpMainWindow; +class kpSelection; + +class kpToolSelectionCreateCommand; +class kpToolSelectionMoveCommand; +class kpToolSelectionPullFromDocumentCommand; +class kpToolSelectionResizeScaleCommand; +class kpToolWidgetOpaqueOrTransparent; + + +class kpToolSelection : public kpTool +{ +Q_OBJECT + +public: + enum Mode {Rectangle, Ellipse, FreeForm, Text}; + + kpToolSelection (Mode mode, + const QString &text, const QString &description, + int key, + kpMainWindow *mainWindow, const char *name); + virtual ~kpToolSelection (); + + void setMode (Mode mode) { m_mode = mode; } + +private: + void pushOntoDocument (); + +protected: + bool onSelectionToMove () const; + int onSelectionResizeHandle () const; + bool onSelectionToSelectText () const; + +public: + QString haventBegunDrawUserMessage () const; + + virtual void begin (); + virtual void end (); + virtual void reselect (); + + virtual bool careAboutModifierState () const { return true; } + bool controlOrShiftPressed () const { return (m_controlPressed || m_shiftPressed); } + + virtual void beginDraw (); +protected: + const QCursor &cursor () const; +public: + virtual void hover (const QPoint &point); +protected: + void popupRMBMenu (); + void setSelectionBorderForMove (); +protected slots: + void slotRMBMoveUpdateGUI (); + void delayedDraw (); +public: + virtual void draw (const QPoint &thisPoint, const QPoint &lastPoint, + const QRect &normalizedRect); + virtual void cancelShape (); + virtual void releasedAllButtons (); + virtual void endDraw (const QPoint &thisPoint, const QRect &normalizedRect); + +protected: + virtual void keyPressEvent (QKeyEvent *e); + +protected: + void selectionTransparencyChanged (const QString &name); + +protected slots: + virtual void slotIsOpaqueChanged (); + virtual void slotBackgroundColorChanged (const kpColor &color); + virtual void slotColorSimilarityChanged (double similarity, int); + +protected: + Mode m_mode; + + QPoint m_startDragFromSelectionTopLeft; + enum DragType + { + Unknown, Create, Move, SelectText, ResizeScale + }; + DragType m_dragType; + bool m_dragHasBegun; + bool m_hadSelectionBeforeDrag; + int m_resizeScaleType; + + kpToolSelectionPullFromDocumentCommand *m_currentPullFromDocumentCommand; + kpToolSelectionMoveCommand *m_currentMoveCommand; + bool m_currentMoveCommandIsSmear; + kpToolSelectionResizeScaleCommand *m_currentResizeScaleCommand; + kpToolWidgetOpaqueOrTransparent *m_toolWidgetOpaqueOrTransparent; + + kpToolSelectionCreateCommand *m_currentCreateTextCommand; + bool m_cancelledShapeButStillHoldingButtons; + + QTimer *m_createNOPTimer, *m_RMBMoveUpdateGUITimer; +}; + +class kpToolSelectionCreateCommand : public kpNamedCommand +{ +public: + // (if fromSelection doesn't have a pixmap, it will only recreate the region) + kpToolSelectionCreateCommand (const QString &name, const kpSelection &fromSelection, + kpMainWindow *mainWindow); + virtual ~kpToolSelectionCreateCommand (); + + virtual int size () const; + + static bool nextUndoCommandIsCreateBorder (kpCommandHistory *commandHistory); + + const kpSelection *fromSelection () const; + void setFromSelection (const kpSelection &fromSelection); + + virtual void execute (); + virtual void unexecute (); + +private: + kpSelection *m_fromSelection; + + int m_textRow, m_textCol; +}; + +class kpToolSelectionPullFromDocumentCommand : public kpNamedCommand +{ +public: + kpToolSelectionPullFromDocumentCommand (const QString &name, kpMainWindow *mainWindow); + virtual ~kpToolSelectionPullFromDocumentCommand (); + + virtual int size () const; + + virtual void execute (); + virtual void unexecute (); + +private: + kpColor m_backgroundColor; + kpSelection *m_originalSelectionRegion; +}; + +class kpToolSelectionTransparencyCommand : public kpNamedCommand +{ +public: + kpToolSelectionTransparencyCommand (const QString &name, + const kpSelectionTransparency &st, + const kpSelectionTransparency &oldST, + kpMainWindow *mainWindow); + virtual ~kpToolSelectionTransparencyCommand (); + + virtual int size () const; + + virtual void execute (); + virtual void unexecute (); + +private: + kpSelectionTransparency m_st, m_oldST; +}; + +class kpToolSelectionMoveCommand : public kpNamedCommand +{ +public: + kpToolSelectionMoveCommand (const QString &name, kpMainWindow *mainWindow); + virtual ~kpToolSelectionMoveCommand (); + + kpSelection originalSelection () const; + + virtual int size () const; + + virtual void execute (); + virtual void unexecute (); + + 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; + + QPixmap m_oldDocumentPixmap; + + // area of document affected (not the bounding rect of the sel) + QRect m_documentBoundingRect; + + QPointArray m_copyOntoDocumentPoints; +}; + +// 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(). +// +class kpToolSelectionResizeScaleCommand : public QObject, + public kpNamedCommand +{ +Q_OBJECT + +public: + kpToolSelectionResizeScaleCommand (kpMainWindow *mainWindow); + virtual ~kpToolSelectionResizeScaleCommand (); + + virtual int size () const; + +public: + kpSelection 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 <delayed>, does a fast, low-quality scale and then calls itself + // with <delayed> unset for a smooth scale, a short time later. + // If acting on a text box, <delayed> is ignored. + void resizeScaleAndMove (bool delayed); + +protected slots: + void resizeScaleAndMove (/*delayed = false*/); + +public: + void finalize (); + +public: + virtual void execute (); + virtual void unexecute (); + +protected: + kpSelection m_originalSelection; + + QPoint m_newTopLeft; + int m_newWidth, m_newHeight; + + QTimer *m_smoothScaleTimer; +}; + +class kpToolSelectionDestroyCommand : public kpNamedCommand +{ +public: + kpToolSelectionDestroyCommand (const QString &name, bool pushOntoDocument, + kpMainWindow *mainWindow); + virtual ~kpToolSelectionDestroyCommand (); + + virtual int size () const; + + virtual void execute (); + virtual void unexecute (); + +private: + bool m_pushOntoDocument; + QPixmap m_oldDocPixmap; + kpSelection *m_oldSelection; + + int m_textRow, m_textCol; +}; + +#endif // __kp_tool_selection_h__ diff --git a/kolourpaint/tools/kptoolskew.cpp b/kolourpaint/tools/kptoolskew.cpp new file mode 100644 index 00000000..f1e446be --- /dev/null +++ b/kolourpaint/tools/kptoolskew.cpp @@ -0,0 +1,449 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <kptoolskew.h> + +#include <qapplication.h> +#include <qgroupbox.h> +#include <qlabel.h> +#include <qlayout.h> +#include <qpushbutton.h> +#include <qwmatrix.h> + +#include <kdebug.h> +#include <kiconloader.h> +#include <klocale.h> +#include <knuminput.h> + +#include <kpdefs.h> +#include <kpdocument.h> +#include <kpmainwindow.h> +#include <kppixmapfx.h> +#include <kpselection.h> +#include <kptool.h> + + +/* + * kpToolSkewCommand + */ + +kpToolSkewCommand::kpToolSkewCommand (bool actOnSelection, + int hangle, int vangle, + kpMainWindow *mainWindow) + : kpCommand (mainWindow), + m_actOnSelection (actOnSelection), + m_hangle (hangle), m_vangle (vangle), + m_backgroundColor (mainWindow ? mainWindow->backgroundColor (actOnSelection) : kpColor::invalid), + m_oldPixmapPtr (0) +{ +} + +kpToolSkewCommand::~kpToolSkewCommand () +{ + delete m_oldPixmapPtr; +} + + +// public virtual [base kpCommand] +QString kpToolSkewCommand::name () const +{ + QString opName = i18n ("Skew"); + + if (m_actOnSelection) + return i18n ("Selection: %1").arg (opName); + else + return opName; +} + + +// public virtual [base kpCommand] +int kpToolSkewCommand::size () const +{ + return kpPixmapFX::pixmapSize (m_oldPixmapPtr) + + m_oldSelection.size (); +} + + +// public virtual [base kpCommand] +void kpToolSkewCommand::execute () +{ + kpDocument *doc = document (); + if (!doc) + return; + + + QApplication::setOverrideCursor (Qt::waitCursor); + + + m_oldPixmapPtr = new QPixmap (); + *m_oldPixmapPtr = *doc->pixmap (m_actOnSelection); + + + QPixmap newPixmap = kpPixmapFX::skew (*doc->pixmap (m_actOnSelection), + kpToolSkewDialog::horizontalAngleForPixmapFX (m_hangle), + kpToolSkewDialog::verticalAngleForPixmapFX (m_vangle), + m_backgroundColor); + + if (m_actOnSelection) + { + kpSelection *sel = doc->selection (); + + // Save old selection + m_oldSelection = *sel; + + + // Calculate skewed points + QPointArray currentPoints = sel->points (); + currentPoints.translate (-currentPoints.boundingRect ().x (), + -currentPoints.boundingRect ().y ()); + QWMatrix skewMatrix = kpPixmapFX::skewMatrix ( + *doc->pixmap (m_actOnSelection), + kpToolSkewDialog::horizontalAngleForPixmapFX (m_hangle), + kpToolSkewDialog::verticalAngleForPixmapFX (m_vangle)); + currentPoints = skewMatrix.map (currentPoints); + currentPoints.translate (-currentPoints.boundingRect ().x () + m_oldSelection.x (), + -currentPoints.boundingRect ().y () + m_oldSelection.y ()); + + + if (currentPoints.boundingRect ().width () == newPixmap.width () && + currentPoints.boundingRect ().height () == newPixmap.height ()) + { + doc->setSelection (kpSelection (currentPoints, newPixmap, + m_oldSelection.transparency ())); + } + else + { + // TODO: fix the latter "victim of" problem in kpSelection by + // allowing the border width & height != pixmap width & height + // Or maybe autocrop? + #if DEBUG_KP_TOOL_SKEW + kdDebug () << "kpToolSkewCommand::execute() currentPoints.boundingRect=" + << currentPoints.boundingRect () + << " newPixmap: w=" << newPixmap.width () + << " h=" << newPixmap.height () + << " (victim of rounding error and/or skewed-a-(rectangular)-pixmap-that-was-transparent-in-the-corners-making-sel-uselessly-bigger-than-needs-be))" + << endl; + #endif + doc->setSelection (kpSelection (kpSelection::Rectangle, + QRect (currentPoints.boundingRect ().x (), + currentPoints.boundingRect ().y (), + newPixmap.width (), + newPixmap.height ()), + newPixmap, + m_oldSelection.transparency ())); + } + + if (m_mainWindow->tool ()) + m_mainWindow->tool ()->somethingBelowTheCursorChanged (); + } + else + { + doc->setPixmap (newPixmap); + } + + + QApplication::restoreOverrideCursor (); +} + +// public virtual [base kpCommand] +void kpToolSkewCommand::unexecute () +{ + kpDocument *doc = document (); + if (!doc) + return; + + + QApplication::setOverrideCursor (Qt::waitCursor); + + + QPixmap oldPixmap = *m_oldPixmapPtr; + delete m_oldPixmapPtr; m_oldPixmapPtr = 0; + + + if (!m_actOnSelection) + doc->setPixmap (oldPixmap); + else + { + kpSelection oldSelection = m_oldSelection; + doc->setSelection (oldSelection); + + if (m_mainWindow->tool ()) + m_mainWindow->tool ()->somethingBelowTheCursorChanged (); + } + + + QApplication::restoreOverrideCursor (); +} + + +/* + * kpToolSkewDialog + */ + + +// private static +int kpToolSkewDialog::s_lastWidth = -1, + kpToolSkewDialog::s_lastHeight = -1; + +// private static +int kpToolSkewDialog::s_lastHorizontalAngle = 0, + kpToolSkewDialog::s_lastVerticalAngle = 0; + + +kpToolSkewDialog::kpToolSkewDialog (bool actOnSelection, kpMainWindow *parent, + const char *name) + : kpToolPreviewDialog (kpToolPreviewDialog::AllFeatures, + false/*don't reserve top row*/, + actOnSelection ? i18n ("Skew Selection") : i18n ("Skew Image"), + i18n ("After Skew:"), + actOnSelection, parent, name) +{ + // 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->setEditFocus (); +} + +kpToolSkewDialog::~kpToolSkewDialog () +{ + s_lastWidth = width (), s_lastHeight = height (); +} + + +// private +void kpToolSkewDialog::createAngleGroupBox () +{ + QGroupBox *angleGroupBox = new QGroupBox (i18n ("Angle"), mainWidget ()); + addCustomWidget (angleGroupBox); + + + QLabel *horizontalSkewPixmapLabel = new QLabel (angleGroupBox); + horizontalSkewPixmapLabel->setPixmap (UserIcon ("image_skew_horizontal")); + + QLabel *horizontalSkewLabel = new QLabel (i18n ("&Horizontal:"), angleGroupBox); + m_horizontalSkewInput = new KIntNumInput (s_lastHorizontalAngle, angleGroupBox); + m_horizontalSkewInput->setMinValue (-89); + m_horizontalSkewInput->setMaxValue (+89); + + QLabel *horizontalSkewDegreesLabel = new QLabel (i18n ("degrees"), angleGroupBox); + + + QLabel *verticalSkewPixmapLabel = new QLabel (angleGroupBox); + verticalSkewPixmapLabel->setPixmap (UserIcon ("image_skew_vertical")); + + QLabel *verticalSkewLabel = new QLabel (i18n ("&Vertical:"), angleGroupBox); + m_verticalSkewInput = new KIntNumInput (s_lastVerticalAngle, angleGroupBox); + m_verticalSkewInput->setMinValue (-89); + m_verticalSkewInput->setMaxValue (+89); + + QLabel *verticalSkewDegreesLabel = new QLabel (i18n ("degrees"), angleGroupBox); + + + horizontalSkewLabel->setBuddy (m_horizontalSkewInput); + verticalSkewLabel->setBuddy (m_verticalSkewInput); + + + QGridLayout *angleLayout = new QGridLayout (angleGroupBox, 4, 4, + marginHint () * 2, spacingHint ()); + + angleLayout->addWidget (horizontalSkewPixmapLabel, 0, 0); + angleLayout->addWidget (horizontalSkewLabel, 0, 1); + angleLayout->addWidget (m_horizontalSkewInput, 0, 2); + angleLayout->addWidget (horizontalSkewDegreesLabel, 0, 3); + + angleLayout->addWidget (verticalSkewPixmapLabel, 1, 0); + angleLayout->addWidget (verticalSkewLabel, 1, 1); + angleLayout->addWidget (m_verticalSkewInput, 1, 2); + angleLayout->addWidget (verticalSkewDegreesLabel, 1, 3); + + + connect (m_horizontalSkewInput, SIGNAL (valueChanged (int)), + this, SLOT (slotUpdate ())); + connect (m_verticalSkewInput, SIGNAL (valueChanged (int)), + this, SLOT (slotUpdate ())); +} + + +// private virtual [base kpToolPreviewDialog] +QSize kpToolSkewDialog::newDimensions () const +{ + kpDocument *doc = document (); + if (!doc) + return QSize (); + + QWMatrix skewMatrix = kpPixmapFX::skewMatrix (*doc->pixmap (), + horizontalAngleForPixmapFX (), + verticalAngleForPixmapFX ()); + // TODO: Should we be using QWMatrix::Areas? + QRect skewRect = skewMatrix.mapRect (doc->rect (m_actOnSelection)); + + return QSize (skewRect.width (), skewRect.height ()); +} + +// private virtual [base kpToolPreviewDialog] +QPixmap kpToolSkewDialog::transformPixmap (const QPixmap &pixmap, + int targetWidth, int targetHeight) const +{ + return kpPixmapFX::skew (pixmap, + horizontalAngleForPixmapFX (), + verticalAngleForPixmapFX (), + m_mainWindow ? m_mainWindow->backgroundColor (m_actOnSelection) : kpColor::invalid, + targetWidth, + targetHeight); +} + + +// private +void kpToolSkewDialog::updateLastAngles () +{ + s_lastHorizontalAngle = horizontalAngle (); + s_lastVerticalAngle = verticalAngle (); +} + +// private slot virtual [base kpToolPreviewDialog] +void kpToolSkewDialog::slotUpdate () +{ + updateLastAngles (); + kpToolPreviewDialog::slotUpdate (); +} + + +// public +int kpToolSkewDialog::horizontalAngle () const +{ + return m_horizontalSkewInput->value (); +} + +// public +int kpToolSkewDialog::verticalAngle () const +{ + return m_verticalSkewInput->value (); +} + + +// public static +int kpToolSkewDialog::horizontalAngleForPixmapFX (int hangle) +{ + return -hangle; +} + +// public static +int kpToolSkewDialog::verticalAngleForPixmapFX (int vangle) +{ + return -vangle; +} + + +// public +int kpToolSkewDialog::horizontalAngleForPixmapFX () const +{ + return kpToolSkewDialog::horizontalAngleForPixmapFX (horizontalAngle ()); +} + +// public +int kpToolSkewDialog::verticalAngleForPixmapFX () const +{ + return kpToolSkewDialog::verticalAngleForPixmapFX (verticalAngle ()); +} + + +// public virtual [base kpToolPreviewDialog] +bool kpToolSkewDialog::isNoOp () const +{ + return (horizontalAngle () == 0) && (verticalAngle () == 0); +} + + +// private slot virtual [base KDialogBase] +void kpToolSkewDialog::slotOk () +{ + QString message, caption, continueButtonText; + + if (document ()->selection ()) + { + if (!document ()->selection ()->isText ()) + { + message = + i18n ("<qt><p>Skewing the selection to %1x%2" + " may take a substantial amount of memory." + " This can reduce system" + " responsiveness and cause other application resource" + " problems.</p>" + + "<p>Are you sure want to skew the selection?</p></qt>"); + + caption = i18n ("Skew Selection?"); + continueButtonText = i18n ("Sk&ew Selection"); + } + } + else + { + message = + i18n ("<qt><p>Skewing the image to %1x%2" + " may take a substantial amount of memory." + " This can reduce system" + " responsiveness and cause other application resource" + " problems.</p>" + + "<p>Are you sure want to skew the image?</p></qt>"); + + caption = i18n ("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.arg (newWidth).arg (newHeight), + caption, + continueButtonText, + this)) + { + KDialogBase::slotOk (); + } +} + +#include <kptoolskew.moc> diff --git a/kolourpaint/tools/kptoolskew.h b/kolourpaint/tools/kptoolskew.h new file mode 100644 index 00000000..570909c2 --- /dev/null +++ b/kolourpaint/tools/kptoolskew.h @@ -0,0 +1,121 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 __kptool_skew_h__ +#define __kptool_skew_h__ + +#include <qpixmap.h> + +#include <kpcommandhistory.h> +#include <kdialogbase.h> + +#include <kpcolor.h> +#include <kpselection.h> +#include <kptoolpreviewdialog.h> + +class QGroupBox; +class QLabel; +class QPixmap; + +class KIntNumInput; + +class kpDocument; +class kpMainWindow; + + +class kpToolSkewCommand : public kpCommand +{ +public: + kpToolSkewCommand (bool actOnSelection, + int hangle, int vangle, + kpMainWindow *mainWindow); + virtual ~kpToolSkewCommand (); + + virtual QString name () const; + + virtual int size () const; + + virtual void execute (); + virtual void unexecute (); + +private: + bool m_actOnSelection; + int m_hangle, m_vangle; + + kpColor m_backgroundColor; + QPixmap *m_oldPixmapPtr; + kpSelection m_oldSelection; +}; + + +class kpToolSkewDialog : public kpToolPreviewDialog +{ +Q_OBJECT + +public: + kpToolSkewDialog (bool actOnSelection, kpMainWindow *parent, + const char *name = 0); + virtual ~kpToolSkewDialog (); + +private: + static int s_lastWidth, s_lastHeight; + static int s_lastHorizontalAngle, s_lastVerticalAngle; + + void createAngleGroupBox (); + + virtual QSize newDimensions () const; + virtual QPixmap transformPixmap (const QPixmap &pixmap, + int targetWidth, int targetHeight) const; + + void updateLastAngles (); + +private slots: + virtual void slotUpdate (); + +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; + + virtual bool isNoOp () const; + +private slots: + virtual void slotOk (); + +private: + KIntNumInput *m_horizontalSkewInput, *m_verticalSkewInput; +}; + +#endif // __kptool_skew_h__ diff --git a/kolourpaint/tools/kptooltext.cpp b/kolourpaint/tools/kptooltext.cpp new file mode 100644 index 00000000..73a60e66 --- /dev/null +++ b/kolourpaint/tools/kptooltext.cpp @@ -0,0 +1,1394 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <kptooltext.h> + +#include <qvaluevector.h> + +#include <kdebug.h> +#include <klocale.h> + +#include <kpcommandhistory.h> +#include <kpdocument.h> +#include <kpmainwindow.h> +#include <kpselection.h> +#include <kptoolwidgetopaqueortransparent.h> +#include <kpviewmanager.h> + + +kpToolText::kpToolText (kpMainWindow *mainWindow) + : kpToolSelection (Text, + i18n ("Text"), i18n ("Writes text"), + Qt::Key_T, + mainWindow, "tool_text"), + m_isIMStarted (false), + m_IMStartCursorRow (0), + m_IMStartCursorCol (0), + m_IMPreeditStr (0) +{ +} + +kpToolText::~kpToolText () +{ +} + + +// public virtual [base kpToolSelection] +void kpToolText::begin () +{ +#if DEBUG_KP_TOOL_TEXT && 1 + kdDebug () << "kpToolText::begin()" << endl; +#endif + + mainWindow ()->enableTextToolBarActions (true); + viewManager ()->setTextCursorEnabled (true); + + m_insertCommand = 0; + m_enterCommand = 0; + m_backspaceCommand = 0; + m_deleteCommand = 0; + + kpToolSelection::begin (); +} + +// public virtual [base kpToolSelection] +void kpToolText::end () +{ +#if DEBUG_KP_TOOL_TEXT && 1 + kdDebug () << "kpToolText::end()" << endl; +#endif + + kpToolSelection::end (); + + viewManager ()->setTextCursorEnabled (false); + mainWindow ()->enableTextToolBarActions (false); +} + + +// public +bool kpToolText::hasBegunText () const +{ + return (m_insertCommand || + m_enterCommand || + m_backspaceCommand || + m_deleteCommand); +} + +// public virtual [base kpTool] +bool kpToolText::hasBegunShape () const +{ + return (hasBegunDraw () || hasBegunText ()); +} + + +// public virtual [base kpToolSelection] +void kpToolText::cancelShape () +{ +#if DEBUG_KP_TOOL_TEXT + kdDebug () << "kpToolText::cancelShape()" << endl; +#endif + + if (m_dragType != Unknown) + kpToolSelection::cancelShape (); + else if (hasBegunText ()) + { + m_insertCommand = 0; + m_enterCommand = 0; + m_backspaceCommand = 0; + m_deleteCommand = 0; + + commandHistory ()->undo (); + } + else + kpToolSelection::cancelShape (); +} + +// public virtual [base kpTool] +void kpToolText::endShape (const QPoint &thisPoint, const QRect &normalizedRect) +{ +#if DEBUG_KP_TOOL_TEXT + kdDebug () << "kpToolText::endShape()" << endl; +#endif + + if (m_dragType != Unknown) + kpToolSelection::endDraw (thisPoint, normalizedRect); + else if (hasBegunText ()) + { + m_insertCommand = 0; + m_enterCommand = 0; + m_backspaceCommand = 0; + m_deleteCommand = 0; + } + else + kpToolSelection::endDraw (thisPoint, normalizedRect); +} + + +// protected virtual [base kpTool] +void kpToolText::keyPressEvent (QKeyEvent *e) +{ +#if DEBUG_KP_TOOL_TEXT + kdDebug () << "kpToolText::keyPressEvent(e->text='" << e->text () << "')" << endl; +#endif + + + e->ignore (); + + + if (hasBegunDraw ()) + { + #if DEBUG_KP_TOOL_TEXT + kdDebug () << "\talready began draw with mouse - passing on event to kpTool" << endl; + #endif + kpToolSelection::keyPressEvent (e); + return; + } + + + kpSelection *sel = document ()->selection (); + + if (!sel || !sel->isText ()) + { + #if DEBUG_KP_TOOL_TEXT + kdDebug () << "\tno text sel - passing on event to kpTool" << endl; + #endif + //if (hasBegunShape ()) + // endShape (m_currentPoint, QRect (m_startPoint, m_currentPoint).normalize ()); + + kpToolSelection::keyPressEvent (e); + return; + } + + + const QValueVector <QString> textLines = sel->textLines (); + int cursorRow = viewManager ()->textCursorRow (); + int cursorCol = viewManager ()->textCursorCol (); + + +#define IS_SPACE(c) ((c).isSpace () || (c).isNull ()) + if (e->key () == Qt::Key_Enter || e->key () == Qt::Key_Return) + { + #if DEBUG_KP_TOOL_TEXT + kdDebug () << "\tenter pressed" << endl; + #endif + if (!m_enterCommand) + { + // TODO: why not endShapeInternal(); ditto for everywhere else in this file? + if (hasBegunShape ()) + endShape (m_currentPoint, QRect (m_startPoint, m_currentPoint).normalize ()); + + m_enterCommand = new kpToolTextEnterCommand (i18n ("Text: New Line"), + viewManager ()->textCursorRow (), viewManager ()->textCursorCol (), + mainWindow ()); + commandHistory ()->addCommand (m_enterCommand, false/*no exec*/); + } + else + m_enterCommand->addEnter (); + + e->accept (); + } + else if (e->key () == Qt::Key_Backspace) + { + #if DEBUG_KP_TOOL_TEXT + kdDebug () << "\tbackspace pressed" << endl; + #endif + + if (!m_backspaceCommand) + { + if (hasBegunShape ()) + endShape (m_currentPoint, QRect (m_startPoint, m_currentPoint).normalize ()); + + m_backspaceCommand = new kpToolTextBackspaceCommand (i18n ("Text: Backspace"), + viewManager ()->textCursorRow (), viewManager ()->textCursorCol (), + mainWindow ()); + commandHistory ()->addCommand (m_backspaceCommand, false/*no exec*/); + } + else + m_backspaceCommand->addBackspace (); + + e->accept (); + } + else if (e->key () == Qt::Key_Delete) + { + #if DEBUG_KP_TOOL_TEXT + kdDebug () << "\tdelete pressed" << endl; + #endif + + if (!m_deleteCommand) + { + if (hasBegunShape ()) + endShape (m_currentPoint, QRect (m_startPoint, m_currentPoint).normalize ()); + + m_deleteCommand = new kpToolTextDeleteCommand (i18n ("Text: Delete"), + viewManager ()->textCursorRow (), viewManager ()->textCursorCol (), + mainWindow ()); + commandHistory ()->addCommand (m_deleteCommand, false/*no exec*/); + } + else + m_deleteCommand->addDelete (); + + e->accept (); + } + else if (e->key () == Qt::Key_Up) + { + #if DEBUG_KP_TOOL_TEXT + kdDebug () << "\tup pressed" << endl; + #endif + + if (hasBegunShape ()) + endShape (m_currentPoint, QRect (m_startPoint, m_currentPoint).normalize ()); + + if (cursorRow > 0) + { + cursorRow--; + cursorCol = QMIN (cursorCol, (int) textLines [cursorRow].length ()); + viewManager ()->setTextCursorPosition (cursorRow, cursorCol); + } + + e->accept (); + } + else if (e->key () == Qt::Key_Down) + { + #if DEBUG_KP_TOOL_TEXT + kdDebug () << "\tdown pressed" << endl; + #endif + + if (hasBegunShape ()) + endShape (m_currentPoint, QRect (m_startPoint, m_currentPoint).normalize ()); + + if (cursorRow < (int) textLines.size () - 1) + { + cursorRow++; + cursorCol = QMIN (cursorCol, (int) textLines [cursorRow].length ()); + viewManager ()->setTextCursorPosition (cursorRow, cursorCol); + } + + e->accept (); + } + else if (e->key () == Qt::Key_Left) + { + #if DEBUG_KP_TOOL_TEXT + kdDebug () << "\tleft pressed" << endl; + #endif + + #define MOVE_CURSOR_LEFT() \ + { \ + cursorCol--; \ + \ + if (cursorCol < 0) \ + { \ + cursorRow--; \ + if (cursorRow < 0) \ + { \ + cursorRow = 0; \ + cursorCol = 0; \ + } \ + else \ + cursorCol = textLines [cursorRow].length (); \ + } \ + } + + if (hasBegunShape ()) + endShape (m_currentPoint, QRect (m_startPoint, m_currentPoint).normalize ()); + + if ((e->state () & Qt::ControlButton) == 0) + { + #if DEBUG_KP_TOOL_TEXT + kdDebug () << "\tmove single char" << endl; + #endif + + MOVE_CURSOR_LEFT (); + viewManager ()->setTextCursorPosition (cursorRow, cursorCol); + } + else + { + #if DEBUG_KP_TOOL_TEXT + kdDebug () << "\tmove to start of word" << endl; + #endif + + // (these comments will exclude the row=0,col=0 boundary case) + + #define IS_ON_ANCHOR() (!IS_SPACE (textLines [cursorRow][cursorCol]) && \ + (cursorCol == 0 || IS_SPACE (textLines [cursorRow][cursorCol - 1]))) + if (IS_ON_ANCHOR ()) + MOVE_CURSOR_LEFT (); + + // --- now we're not on an anchor point (start of word) --- + + // End up on a letter... + while (!(cursorRow == 0 && cursorCol == 0) && + (IS_SPACE (textLines [cursorRow][cursorCol]))) + { + MOVE_CURSOR_LEFT (); + } + + // --- now we're on a letter --- + + // Find anchor point + while (!(cursorRow == 0 && cursorCol == 0) && !IS_ON_ANCHOR ()) + { + MOVE_CURSOR_LEFT (); + } + + #undef IS_ON_ANCHOR + + viewManager ()->setTextCursorPosition (cursorRow, cursorCol); + } + + #undef MOVE_CURSOR_LEFT + + e->accept (); + + } + else if (e->key () == Qt::Key_Right) + { + #if DEBUG_KP_TOOL_TEXT + kdDebug () << "\tright pressed" << endl; + #endif + + #define MOVE_CURSOR_RIGHT() \ + { \ + cursorCol++; \ + \ + if (cursorCol > (int) textLines [cursorRow].length ()) \ + { \ + cursorRow++; \ + if (cursorRow > (int) textLines.size () - 1) \ + { \ + cursorRow = textLines.size () - 1; \ + cursorCol = textLines [cursorRow].length (); \ + } \ + else \ + cursorCol = 0; \ + } \ + } + + if (hasBegunShape ()) + endShape (m_currentPoint, QRect (m_startPoint, m_currentPoint).normalize ()); + + if ((e->state () & Qt::ControlButton) == 0) + { + #if DEBUG_KP_TOOL_TEXT + kdDebug () << "\tmove single char" << endl; + #endif + + MOVE_CURSOR_RIGHT (); + viewManager ()->setTextCursorPosition (cursorRow, cursorCol); + } + else + { + #if DEBUG_KP_TOOL_TEXT + kdDebug () << "\tmove to start of word" << endl; + #endif + + // (these comments will exclude the last row,end col boundary case) + + #define IS_AT_END() (cursorRow == (int) textLines.size () - 1 && \ + cursorCol == (int) textLines [cursorRow].length ()) + + // Find space + while (!IS_AT_END () && !IS_SPACE (textLines [cursorRow][cursorCol])) + { + MOVE_CURSOR_RIGHT (); + } + + // --- now we're on a space --- + + // Find letter + while (!IS_AT_END () && IS_SPACE (textLines [cursorRow][cursorCol])) + { + MOVE_CURSOR_RIGHT (); + } + + // --- now we're on a letter --- + + viewManager ()->setTextCursorPosition (cursorRow, cursorCol); + + #undef IS_AT_END + } + + #undef MOVE_CURSOR_RIGHT + + e->accept (); + } + else if (e->key () == Qt::Key_Home) + { + #if DEBUG_KP_TOOL_TEXT + kdDebug () << "\thome pressed" << endl; + #endif + + if (hasBegunShape ()) + endShape (m_currentPoint, QRect (m_startPoint, m_currentPoint).normalize ()); + + if (e->state () & Qt::ControlButton) + cursorRow = 0; + + cursorCol = 0; + + viewManager ()->setTextCursorPosition (cursorRow, cursorCol); + + e->accept (); + } + else if (e->key () == Qt::Key_End) + { + #if DEBUG_KP_TOOL_TEXT + kdDebug () << "\tend pressed" << endl; + #endif + + if (hasBegunShape ()) + endShape (m_currentPoint, QRect (m_startPoint, m_currentPoint).normalize ()); + + if (e->state () & Qt::ControlButton) + cursorRow = textLines.size () - 1; + + cursorCol = textLines [cursorRow].length (); + + viewManager ()->setTextCursorPosition (cursorRow, cursorCol); + + e->accept (); + } + else + { + #if DEBUG_KP_TOOL_TEXT + kdDebug () << "\ttext='" << e->text () << "'" << endl; + #endif + QString usableText; + for (int i = 0; i < (int) e->text ().length (); i++) + { + if (e->text ().at (i).isPrint ()) + usableText += e->text ().at (i); + } + #if DEBUG_KP_TOOL_TEXT + kdDebug () << "\tusableText='" << usableText << "'" << endl; + #endif + + if (usableText.length () > 0) + { + if (!m_insertCommand) + { + if (hasBegunShape ()) + endShape (m_currentPoint, QRect (m_startPoint, m_currentPoint).normalize ()); + + m_insertCommand = new kpToolTextInsertCommand (i18n ("Text: Write"), + viewManager ()->textCursorRow (), viewManager ()->textCursorCol (), + usableText, + mainWindow ()); + commandHistory ()->addCommand (m_insertCommand, false/*no exec*/); + } + else + m_insertCommand->addText (usableText); + + e->accept (); + } + } +#undef IS_SPACE + + + if (!e->isAccepted ()) + { + #if DEBUG_KP_TOOL_TEXT + kdDebug () << "\tkey processing did not accept (text was '" + << e->text () + << "') - passing on event to kpToolSelection" + << endl; + #endif + //if (hasBegunShape ()) + // endShape (m_currentPoint, QRect (m_startPoint, m_currentPoint).normalize ()); + + kpToolSelection::keyPressEvent (e); + return; + } +} + +void kpToolText::imStartEvent (QIMEvent *e) +{ +#if DEBUG_KP_TOOL_TEXT && 1 + kdDebug () << "kpToolText::imStartEvent() text='" << e->text () + << " cursorPos=" << e->cursorPos () + << " selectionLength=" << e->selectionLength () + << endl; +#endif + + kpSelection *sel = document ()->selection (); + if (hasBegunDraw() || !sel || !sel->isText ()) + { + e->ignore(); + return; + } + + m_IMStartCursorRow = viewManager ()->textCursorRow (); + m_IMStartCursorCol = viewManager ()->textCursorCol (); + m_IMPreeditStr = QString::null; +} + +void kpToolText::imComposeEvent (QIMEvent *e) +{ +#if DEBUG_KP_TOOL_TEXT && 1 + kdDebug () << "kpToolText::imComposeEvent() text='" << e->text () + << " cursorPos=" << e->cursorPos () + << " selectionLength=" << e->selectionLength () + << endl; +#endif + + kpSelection *sel = document ()->selection (); + if (hasBegunDraw() || !sel || !sel->isText ()) + { + e->ignore(); + return; + } + + // remove old preedit + if (m_IMPreeditStr.length() > 0 ) + { + // set cursor at the start input point + viewManager ()->setTextCursorPosition (m_IMStartCursorRow, m_IMStartCursorCol); + for (unsigned int i = 0; i < m_IMPreeditStr.length(); i++) + { + if (!m_deleteCommand) + { + if (hasBegunShape ()) + endShape (m_currentPoint, QRect (m_startPoint, m_currentPoint).normalize ()); + + m_deleteCommand = new kpToolTextDeleteCommand (i18n ("Text: Delete"), + viewManager ()->textCursorRow (), viewManager ()->textCursorCol (), + mainWindow ()); + commandHistory ()->addCommand (m_deleteCommand, false/*no exec*/); + } + else + m_deleteCommand->addDelete (); + } + } + + // insert new preedit + m_IMPreeditStr = e->text(); + if (m_IMPreeditStr.length() > 0) + { + if (!m_insertCommand) + { + if (hasBegunShape ()) + endShape (m_currentPoint, QRect (m_startPoint, m_currentPoint).normalize ()); + + m_insertCommand = new kpToolTextInsertCommand (i18n ("Text: Write"), + viewManager ()->textCursorRow (), viewManager ()->textCursorCol (), + m_IMPreeditStr, + mainWindow ()); + commandHistory ()->addCommand (m_insertCommand, false/*no exec*/); + } + else + m_insertCommand->addText (m_IMPreeditStr); + } + + // set cursor pos + if (m_IMStartCursorRow >= 0) + { + int row = m_IMStartCursorRow; + int col = m_IMStartCursorCol + e->cursorPos () /* + e->selectionLength()*/; + viewManager ()->setTextCursorPosition (row, col, true /* update MicroFocusHint */); + } +} + +void kpToolText::imEndEvent (QIMEvent *e) +{ +#if DEBUG_KP_TOOL_TEXT && 1 + kdDebug () << "kpToolText::imEndEvent() text='" << e->text () + << " cursorPos=" << e->cursorPos () + << " selectionLength=" << e->selectionLength () + << endl; +#endif + + kpSelection *sel = document ()->selection (); + if (hasBegunDraw() || !sel || !sel->isText ()) + { + e->ignore(); + return; + } + + // remove old preedit + if (m_IMPreeditStr.length() > 0 ) + { + // set cursor at the start input point + viewManager ()->setTextCursorPosition (m_IMStartCursorRow, m_IMStartCursorCol); + for (unsigned int i = 0; i < m_IMPreeditStr.length(); i++) + { + if (!m_deleteCommand) + { + if (hasBegunShape ()) + endShape (m_currentPoint, QRect (m_startPoint, m_currentPoint).normalize ()); + + m_deleteCommand = new kpToolTextDeleteCommand (i18n ("Text: Delete"), + viewManager ()->textCursorRow (), viewManager ()->textCursorCol (), + mainWindow ()); + commandHistory ()->addCommand (m_deleteCommand, false/*no exec*/); + } + else + m_deleteCommand->addDelete (); + } + } + m_IMPreeditStr = QString::null; + + // commit string + QString inputStr = e->text(); + if (inputStr.length() > 0) + { + if (!m_insertCommand) + { + if (hasBegunShape ()) + endShape (m_currentPoint, QRect (m_startPoint, m_currentPoint).normalize ()); + + m_insertCommand = new kpToolTextInsertCommand (i18n ("Text: Write"), + viewManager ()->textCursorRow (), viewManager ()->textCursorCol (), + inputStr, + mainWindow ()); + commandHistory ()->addCommand (m_insertCommand, false/*no exec*/); + } + else + m_insertCommand->addText (inputStr); + } +} + + +// protected +bool kpToolText::shouldChangeTextStyle () const +{ + if (mainWindow ()->settingTextStyle ()) + { + #if DEBUG_KP_TOOL_TEXT + kdDebug () << "\trecursion - abort setting text style: " + << mainWindow ()->settingTextStyle () + << endl; + #endif + return false; + } + + if (!document ()->selection () || + !document ()->selection ()->isText ()) + { + #if DEBUG_KP_TOOL_TEXT + kdDebug () << "\tno text selection - abort setting text style" << endl; + #endif + return false; + } + + return true; +} + +// protected +void kpToolText::changeTextStyle (const QString &name, + const kpTextStyle &newTextStyle, + const kpTextStyle &oldTextStyle) +{ +#if DEBUG_KP_TOOL_TEXT + kdDebug () << "kpToolText::changeTextStyle(" << name << ")" << endl; +#endif + + if (hasBegunShape ()) + endShape (m_currentPoint, QRect (m_startPoint, m_currentPoint).normalize ()); + + commandHistory ()->addCommand ( + new kpToolTextChangeStyleCommand ( + name, + newTextStyle, + oldTextStyle, + mainWindow ())); +} + + +// protected slot virtual [base kpToolSelection] +void kpToolText::slotIsOpaqueChanged () +{ +#if DEBUG_KP_TOOL_TEXT + kdDebug () << "kpToolText::slotIsOpaqueChanged()" << endl; +#endif + + if (!shouldChangeTextStyle ()) + return; + + kpTextStyle newTextStyle = mainWindow ()->textStyle (); + kpTextStyle oldTextStyle = newTextStyle; + oldTextStyle.setBackgroundOpaque (!m_toolWidgetOpaqueOrTransparent->isOpaque ()); + + changeTextStyle (newTextStyle.isBackgroundOpaque () ? + i18n ("Text: Opaque Background") : + i18n ("Text: Transparent Background"), + newTextStyle, + oldTextStyle); +} + +// protected slot virtual [base kpTool] +void kpToolText::slotColorsSwapped (const kpColor &newForegroundColor, + const kpColor &newBackgroundColor) +{ +#if DEBUG_KP_TOOL_TEXT + kdDebug () << "kpToolText::slotColorsSwapped()" << endl; +#endif + + if (!shouldChangeTextStyle ()) + return; + + kpTextStyle newTextStyle = mainWindow ()->textStyle (); + kpTextStyle oldTextStyle = newTextStyle; + oldTextStyle.setForegroundColor (newBackgroundColor); + oldTextStyle.setBackgroundColor (newForegroundColor); + + changeTextStyle (i18n ("Text: Swap Colors"), + newTextStyle, + oldTextStyle); +} + +// protected slot virtual [base kpTool] +void kpToolText::slotForegroundColorChanged (const kpColor & /*color*/) +{ +#if DEBUG_KP_TOOL_TEXT + kdDebug () << "kpToolText::slotForegroundColorChanged()" << endl; +#endif + + if (!shouldChangeTextStyle ()) + return; + + kpTextStyle newTextStyle = mainWindow ()->textStyle (); + kpTextStyle oldTextStyle = newTextStyle; + oldTextStyle.setForegroundColor (oldForegroundColor ()); + + changeTextStyle (i18n ("Text: Foreground Color"), + newTextStyle, + oldTextStyle); +} + +// protected slot virtual [base kpToolSelection] +void kpToolText::slotBackgroundColorChanged (const kpColor & /*color*/) +{ +#if DEBUG_KP_TOOL_TEXT + kdDebug () << "kpToolText::slotBackgroundColorChanged()" << endl; +#endif + + if (!shouldChangeTextStyle ()) + return; + + kpTextStyle newTextStyle = mainWindow ()->textStyle (); + kpTextStyle oldTextStyle = newTextStyle; + oldTextStyle.setBackgroundColor (oldBackgroundColor ()); + + changeTextStyle (i18n ("Text: Background Color"), + newTextStyle, + oldTextStyle); +} + +// protected slot virtual [base kpToolSelection] +void kpToolText::slotColorSimilarityChanged (double, int) +{ + // --- don't pass on event to kpToolSelection which would have set the + // SelectionTransparency - not relevant to the Text Tool --- +} + + +// public slot +void kpToolText::slotFontFamilyChanged (const QString &fontFamily, + const QString &oldFontFamily) +{ +#if DEBUG_KP_TOOL_TEXT + kdDebug () << "kpToolText::slotFontFamilyChanged() new=" + << fontFamily + << " old=" + << oldFontFamily + << endl; +#else + (void) fontFamily; +#endif + + if (!shouldChangeTextStyle ()) + return; + + kpTextStyle newTextStyle = mainWindow ()->textStyle (); + kpTextStyle oldTextStyle = newTextStyle; + oldTextStyle.setFontFamily (oldFontFamily); + + changeTextStyle (i18n ("Text: Font"), + newTextStyle, + oldTextStyle); +} + +// public slot +void kpToolText::slotFontSizeChanged (int fontSize, int oldFontSize) +{ +#if DEBUG_KP_TOOL_TEXT + kdDebug () << "kpToolText::slotFontSizeChanged() new=" + << fontSize + << " old=" + << oldFontSize + << endl; +#else + (void) fontSize; +#endif + + if (!shouldChangeTextStyle ()) + return; + + kpTextStyle newTextStyle = mainWindow ()->textStyle (); + kpTextStyle oldTextStyle = newTextStyle; + oldTextStyle.setFontSize (oldFontSize); + + changeTextStyle (i18n ("Text: Font Size"), + newTextStyle, + oldTextStyle); +} + + +// public slot +void kpToolText::slotBoldChanged (bool isBold) +{ +#if DEBUG_KP_TOOL_TEXT + kdDebug () << "kpToolText::slotBoldChanged(" << isBold << ")" << endl; +#endif + + if (!shouldChangeTextStyle ()) + return; + + kpTextStyle newTextStyle = mainWindow ()->textStyle (); + kpTextStyle oldTextStyle = newTextStyle; + oldTextStyle.setBold (!isBold); + + changeTextStyle (i18n ("Text: Bold"), + newTextStyle, + oldTextStyle); +} + +// public slot +void kpToolText::slotItalicChanged (bool isItalic) +{ +#if DEBUG_KP_TOOL_TEXT + kdDebug () << "kpToolText::slotItalicChanged(" << isItalic << ")" << endl; +#endif + + if (!shouldChangeTextStyle ()) + return; + + kpTextStyle newTextStyle = mainWindow ()->textStyle (); + kpTextStyle oldTextStyle = newTextStyle; + oldTextStyle.setItalic (!isItalic); + + changeTextStyle (i18n ("Text: Italic"), + newTextStyle, + oldTextStyle); +} + +// public slot +void kpToolText::slotUnderlineChanged (bool isUnderline) +{ +#if DEBUG_KP_TOOL_TEXT + kdDebug () << "kpToolText::slotUnderlineChanged(" << isUnderline << ")" << endl; +#endif + + if (!shouldChangeTextStyle ()) + return; + + kpTextStyle newTextStyle = mainWindow ()->textStyle (); + kpTextStyle oldTextStyle = newTextStyle; + oldTextStyle.setUnderline (!isUnderline); + + changeTextStyle (i18n ("Text: Underline"), + newTextStyle, + oldTextStyle); +} + +// public slot +void kpToolText::slotStrikeThruChanged (bool isStrikeThru) +{ +#if DEBUG_KP_TOOL_TEXT + kdDebug () << "kpToolText::slotStrikeThruChanged(" << isStrikeThru << ")" << endl; +#endif + + if (!shouldChangeTextStyle ()) + return; + + kpTextStyle newTextStyle = mainWindow ()->textStyle (); + kpTextStyle oldTextStyle = newTextStyle; + oldTextStyle.setStrikeThru (!isStrikeThru); + + changeTextStyle (i18n ("Text: Strike Through"), + newTextStyle, + oldTextStyle); +} + + +/* + * kpToolTextChangeStyleCommand + */ + +kpToolTextChangeStyleCommand::kpToolTextChangeStyleCommand (const QString &name, + const kpTextStyle &newTextStyle, const kpTextStyle &oldTextStyle, + kpMainWindow *mainWindow) + : kpNamedCommand (name, mainWindow), + m_newTextStyle (newTextStyle), + m_oldTextStyle (oldTextStyle) +{ +} + +kpToolTextChangeStyleCommand::~kpToolTextChangeStyleCommand () +{ +} + + +// public virtual [base kpCommand] +int kpToolTextChangeStyleCommand::size () const +{ + return 0; +} + + +// public virtual [base kpCommand] +void kpToolTextChangeStyleCommand::execute () +{ +#if DEBUG_KP_TOOL_TEXT && 1 + kdDebug () << "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 () + << endl; +#endif + + m_mainWindow->setTextStyle (m_newTextStyle); + if (selection ()) + selection ()->setTextStyle (m_newTextStyle); + else + kdError () << "kpToolTextChangeStyleCommand::execute() without sel" << endl; +} + +// public virtual [base kpCommand] +void kpToolTextChangeStyleCommand::unexecute () +{ +#if DEBUG_KP_TOOL_TEXT && 1 + kdDebug () << "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 () + << endl; +#endif + + m_mainWindow->setTextStyle (m_oldTextStyle); + if (selection ()) + selection ()->setTextStyle (m_oldTextStyle); + else + kdError () << "kpToolTextChangeStyleCommand::unexecute() without sel" << endl; +} + + +/* + * kpToolTextInsertCommand + */ + +kpToolTextInsertCommand::kpToolTextInsertCommand (const QString &name, + int row, int col, QString newText, + kpMainWindow *mainWindow) + : kpNamedCommand (name, mainWindow), + m_row (row), m_col (col) +{ + viewManager ()->setTextCursorPosition (m_row, m_col); + addText (newText); +} + +kpToolTextInsertCommand::~kpToolTextInsertCommand () +{ +} + + +// public +void kpToolTextInsertCommand::addText (const QString &moreText) +{ + if (moreText.isEmpty ()) + return; + + QValueVector <QString> textLines = selection ()->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; + selection ()->setTextLines (textLines); + + m_newText += moreText; + m_col += moreText.length (); + + viewManager ()->setTextCursorPosition (m_row, m_col); +} + + +// public virtual [base kpCommand] +int kpToolTextInsertCommand::size () const +{ + return m_newText.length () * sizeof (QChar); +} + + +// public virtual [base kpCommand] +void kpToolTextInsertCommand::execute () +{ + viewManager ()->setTextCursorPosition (m_row, m_col); + + QString text = m_newText; + m_newText = QString::null; + addText (text); +} + +// public virtual [base kpCommand] +void kpToolTextInsertCommand::unexecute () +{ + viewManager ()->setTextCursorPosition (m_row, m_col); + + QValueVector <QString> textLines = selection ()->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; + selection ()->setTextLines (textLines); + + m_col -= m_newText.length (); + + viewManager ()->setTextCursorPosition (m_row, m_col); +} + + +/* + * kpToolTextEnterCommand + */ + +kpToolTextEnterCommand::kpToolTextEnterCommand (const QString &name, + int row, int col, + kpMainWindow *mainWindow) + : kpNamedCommand (name, mainWindow), + m_row (row), m_col (col), + m_numEnters (0) +{ + viewManager ()->setTextCursorPosition (m_row, m_col); + addEnter (); +} + +kpToolTextEnterCommand::~kpToolTextEnterCommand () +{ +} + + +// public +void kpToolTextEnterCommand::addEnter () +{ + QValueVector <QString> textLines = selection ()->textLines (); + + const QString rightHalf = textLines [m_row].mid (m_col); + + textLines [m_row].truncate (m_col); + textLines.insert (textLines.begin () + m_row + 1, rightHalf); + + selection ()->setTextLines (textLines); + + m_row++; + m_col = 0; + + viewManager ()->setTextCursorPosition (m_row, m_col); + + m_numEnters++; +} + + +// public virtual [base kpCommand] +int 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); + + QValueVector <QString> textLines = selection ()->textLines (); + + for (int i = 0; i < m_numEnters; i++) + { + if (m_col != 0) + { + kdError () << "kpToolTextEnterCommand::unexecute() col=" << m_col << endl; + break; + } + + 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; + } + + selection ()->setTextLines (textLines); + + viewManager ()->setTextCursorPosition (m_row, m_col); +} + + +/* + * kpToolTextBackspaceCommand + */ + +kpToolTextBackspaceCommand::kpToolTextBackspaceCommand (const QString &name, + int row, int col, + kpMainWindow *mainWindow) + : kpNamedCommand (name, mainWindow), + m_row (row), m_col (col), + m_numBackspaces (0) +{ + viewManager ()->setTextCursorPosition (m_row, m_col); + addBackspace (); +} + +kpToolTextBackspaceCommand::~kpToolTextBackspaceCommand () +{ +} + + +// public +void kpToolTextBackspaceCommand::addBackspace () +{ + QValueVector <QString> textLines = selection ()->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; + } + } + + selection ()->setTextLines (textLines); + + viewManager ()->setTextCursorPosition (m_row, m_col); + + m_numBackspaces++; +} + + +// public virtual [base kpCommand] +int kpToolTextBackspaceCommand::size () const +{ + return m_deletedText.length () * sizeof (QChar); +} + + +// public virtual [base kpCommand] +void kpToolTextBackspaceCommand::execute () +{ + viewManager ()->setTextCursorPosition (m_row, m_col); + + m_deletedText = QString::null; + 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); + + QValueVector <QString> textLines = selection ()->textLines (); + + for (int i = 0; i < (int) m_deletedText.length (); i++) + { + if (m_deletedText [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 + m_deletedText [i] + rightHalf; + m_col++; + } + } + + m_deletedText = QString::null; + + selection ()->setTextLines (textLines); + + viewManager ()->setTextCursorPosition (m_row, m_col); +} + + +/* + * kpToolTextDeleteCommand + */ + +kpToolTextDeleteCommand::kpToolTextDeleteCommand (const QString &name, + int row, int col, + kpMainWindow *mainWindow) + : kpNamedCommand (name, mainWindow), + m_row (row), m_col (col), + m_numDeletes (0) +{ + viewManager ()->setTextCursorPosition (m_row, m_col); + addDelete (); +} + +kpToolTextDeleteCommand::~kpToolTextDeleteCommand () +{ +} + + +// public +void kpToolTextDeleteCommand::addDelete () +{ + QValueVector <QString> textLines = selection ()->textLines (); + + if (m_col < (int) 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 < (int) textLines.size () - 1) + { + m_deletedText.prepend ('\n'); + + textLines [m_row] += textLines [m_row + 1]; + textLines.erase (textLines.begin () + m_row + 1); + } + } + + selection ()->setTextLines (textLines); + + viewManager ()->setTextCursorPosition (m_row, m_col); + + m_numDeletes++; +} + + +// public virtual [base kpCommand] +int kpToolTextDeleteCommand::size () const +{ + return m_deletedText.length () * sizeof (QChar); +} + + +// public virtual [base kpCommand] +void kpToolTextDeleteCommand::execute () +{ + viewManager ()->setTextCursorPosition (m_row, m_col); + + m_deletedText = QString::null; + 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); + + QValueVector <QString> textLines = selection ()->textLines (); + + for (int i = 0; i < (int) m_deletedText.length (); i++) + { + if (m_deletedText [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 + m_deletedText [i] + rightHalf; + } + } + + m_deletedText = QString::null; + + selection ()->setTextLines (textLines); + + viewManager ()->setTextCursorPosition (m_row, m_col); +} + + +#include <kptooltext.moc> diff --git a/kolourpaint/tools/kptooltext.h b/kolourpaint/tools/kptooltext.h new file mode 100644 index 00000000..a99654b7 --- /dev/null +++ b/kolourpaint/tools/kptooltext.h @@ -0,0 +1,203 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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_h__ +#define __kp_tool_text_h__ + +#include <qstring.h> + +#include <kpcommandhistory.h> + +#include <kptextstyle.h> +#include <kptoolselection.h> + +class kpColor; +class kpMainWindow; +class kpSelection; +class kpViewManager; + +class kpToolText : public kpToolSelection +{ +Q_OBJECT + +public: + kpToolText (kpMainWindow *mainWindow); + virtual ~kpToolText (); + + virtual bool careAboutColorsSwapped () const { return true; } + + virtual void begin (); + virtual void end (); + + bool hasBegunText () const; + virtual bool hasBegunShape () const; + virtual void cancelShape (); + virtual void endShape (const QPoint &thisPoint, const QRect &normalizedRect); + +protected: + virtual void keyPressEvent (QKeyEvent *e); + virtual void imStartEvent (QIMEvent *e); + virtual void imComposeEvent (QIMEvent *e); + virtual void imEndEvent (QIMEvent *e); + +protected: + bool shouldChangeTextStyle () const; + void changeTextStyle (const QString &name, + const kpTextStyle &newTextStyle, + const kpTextStyle &oldTextStyle); + +protected slots: + virtual void slotIsOpaqueChanged (); + virtual void slotColorsSwapped (const kpColor &newForegroundColor, + const kpColor &newBackgroundColor); + virtual void slotForegroundColorChanged (const kpColor &color); + virtual void slotBackgroundColorChanged (const kpColor &color); + virtual void slotColorSimilarityChanged (double, int); + +public slots: + void slotFontFamilyChanged (const QString &fontFamily, const QString &oldFontFamily); + void slotFontSizeChanged (int fontSize, int oldFontSize); + void slotBoldChanged (bool isBold); + void slotItalicChanged (bool isItalic); + void slotUnderlineChanged (bool isUnderline); + void slotStrikeThruChanged (bool isStrikeThru); + +protected: + class kpToolTextInsertCommand *m_insertCommand; + class kpToolTextEnterCommand *m_enterCommand; + class kpToolTextBackspaceCommand *m_backspaceCommand; + class kpToolTextDeleteCommand *m_deleteCommand; + + bool m_isIMStarted; + int m_IMStartCursorRow; + int m_IMStartCursorCol; + QString m_IMPreeditStr; +}; + + +class kpToolTextChangeStyleCommand : public kpNamedCommand +{ +public: + kpToolTextChangeStyleCommand (const QString &name, + const kpTextStyle &newTextStyle, const kpTextStyle &oldTextStyle, + kpMainWindow *mainWindow); + virtual ~kpToolTextChangeStyleCommand (); + + virtual int size () const; + + virtual void execute (); + virtual void unexecute (); + +protected: + kpTextStyle m_newTextStyle, m_oldTextStyle; +}; + +class kpToolTextInsertCommand : public kpNamedCommand +{ +public: + kpToolTextInsertCommand (const QString &name, + int row, int col, QString newText, + kpMainWindow *mainWindow); + virtual ~kpToolTextInsertCommand (); + + void addText (const QString &moreText); + + virtual int size () const; + + virtual void execute (); + virtual void unexecute (); + +protected: + int m_row, m_col; + QString m_newText; +}; + +class kpToolTextEnterCommand : public kpNamedCommand +{ +public: + kpToolTextEnterCommand (const QString &name, + int row, int col, + kpMainWindow *mainWindow); + virtual ~kpToolTextEnterCommand (); + + void addEnter (); + + virtual int size () const; + + virtual void execute (); + virtual void unexecute (); + +protected: + int m_row, m_col; + int m_numEnters; +}; + +class kpToolTextBackspaceCommand : public kpNamedCommand +{ +public: + kpToolTextBackspaceCommand (const QString &name, + int row, int col, + kpMainWindow *mainWindow); + virtual ~kpToolTextBackspaceCommand (); + + void addBackspace (); + + virtual int size () const; + + virtual void execute (); + virtual void unexecute (); + +protected: + int m_row, m_col; + int m_numBackspaces; + QString m_deletedText; +}; + +class kpToolTextDeleteCommand : public kpNamedCommand +{ +public: + kpToolTextDeleteCommand (const QString &name, + int row, int col, + kpMainWindow *mainWindow); + virtual ~kpToolTextDeleteCommand (); + + void addDelete (); + + virtual int size () const; + + virtual void execute (); + virtual void unexecute (); + +protected: + int m_row, m_col; + int m_numDeletes; + QString m_deletedText; +}; + +#endif // __kp_tool_text_h__ + diff --git a/kolourpaint/views/Makefile.am b/kolourpaint/views/Makefile.am new file mode 100644 index 00000000..2d771cfc --- /dev/null +++ b/kolourpaint/views/Makefile.am @@ -0,0 +1,14 @@ +INCLUDES = -I$(srcdir)/.. -I$(srcdir)/../cursors -I$(srcdir)/../interfaces \ + -I$(srcdir)/../pixmapfx \ + -I$(srcdir)/../tools \ + -I$(srcdir)/../views \ + -I$(srcdir)/../widgets $(all_includes) + +noinst_LTLIBRARIES = libkolourpaintviews.la +libkolourpaintviews_la_SOURCES = kpthumbnailview.cpp \ + kpunzoomedthumbnailview.cpp \ + kpzoomedthumbnailview.cpp \ + kpzoomedview.cpp + +METASOURCES = AUTO + diff --git a/kolourpaint/views/kpthumbnailview.cpp b/kolourpaint/views/kpthumbnailview.cpp new file mode 100644 index 00000000..32c54376 --- /dev/null +++ b/kolourpaint/views/kpthumbnailview.cpp @@ -0,0 +1,98 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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_VIEW 0 + + +#include <kpthumbnailview.h> + +#include <kdebug.h> + + +kpThumbnailView::kpThumbnailView (kpDocument *document, + kpToolToolBar *toolToolBar, + kpViewManager *viewManager, + kpView *buddyView, + kpViewScrollableContainer *scrollableContainer, + QWidget *parent, const char *name) + + : kpView (document, toolToolBar, viewManager, + buddyView, + scrollableContainer, + parent, name) +{ +} + +kpThumbnailView::~kpThumbnailView () +{ +} + + +// protected +void kpThumbnailView::setMaskToCoverDocument () +{ +#if DEBUG_KP_THUMBNAIL_VIEW + kdDebug () << "kpThumbnailView::setMaskToCoverDocument()" + << " origin=" << origin () + << " zoomedDoc: width=" << zoomedDocWidth () + << " height=" << zoomedDocHeight () + << endl; +#endif + + setMask (QRegion (QRect (origin ().x (), origin ().y (), + zoomedDocWidth (), zoomedDocHeight ()))); +} + + +// protected virtual [base kpView] +void kpThumbnailView::resizeEvent (QResizeEvent *e) +{ +#if DEBUG_KP_THUMBNAIL_VIEW + kdDebug () << "kpThumbnailView(" << name () << ")::resizeEvent()" + << endl; +#endif + + // For QResizeEvent's, Qt already throws an entire widget repaint into + // the event loop. So eat useless update() calls that can only slow + // things down. + // TODO: this doesn't seem to work. + const bool oldIsUpdatesEnabled = isUpdatesEnabled (); + setUpdatesEnabled (false); + + { + kpView::resizeEvent (e); + + adjustToEnvironment (); + } + + setUpdatesEnabled (oldIsUpdatesEnabled); +} + + +#include <kpthumbnailview.moc> + diff --git a/kolourpaint/views/kpthumbnailview.h b/kolourpaint/views/kpthumbnailview.h new file mode 100644 index 00000000..c3420833 --- /dev/null +++ b/kolourpaint/views/kpthumbnailview.h @@ -0,0 +1,90 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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_VIEW +#define KP_THUMBNAIL_VIEW + + +#include <kpview.h> + + +/** + * @short Abstract base class for all thumbnail views. + * + * @author Clarence Dang <dang@kde.org> + */ +class kpThumbnailView : public kpView +{ +Q_OBJECT + +public: + /** + * Constructs a thumbnail view. + * + * You must call adjustEnvironment() at the end of your constructor. + */ + kpThumbnailView (kpDocument *document, + kpToolToolBar *toolToolBar, + kpViewManager *viewManager, + kpView *buddyView, + kpViewScrollableContainer *scrollableContainer, + QWidget *parent, const char *name); + + /** + * Destructs this thumbnail view. + */ + virtual ~kpThumbnailView (); + + + /** + * @returns the caption to display in an enclosing thumbnail window. + */ + virtual QString caption () const = 0; + + +protected: + /** + * Sets the mask to cover the rectangle with top-left, origin() and + * dimensions equal to or slightly less than (in case of rounding + * error) the size of the document in view coordinates. This ensures + * that all pixels are initialised with either document pixels or the + * standard widget background. + */ + void setMaskToCoverDocument (); + + + /** + * Calls adjustToEnvironment() in response to a resize event. + * + * Extends @ref kpView. + */ + virtual void resizeEvent (QResizeEvent *e); +}; + + +#endif // KP_THUMBNAIL_VIEW diff --git a/kolourpaint/views/kpunzoomedthumbnailview.cpp b/kolourpaint/views/kpunzoomedthumbnailview.cpp new file mode 100644 index 00000000..09d5aed1 --- /dev/null +++ b/kolourpaint/views/kpunzoomedthumbnailview.cpp @@ -0,0 +1,212 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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_UNZOOMED_THUMBNAIL_VIEW 0 + + +#include <kpunzoomedthumbnailview.h> + +#include <kdebug.h> +#include <klocale.h> + +#include <kpdocument.h> +#include <kpviewmanager.h> +#include <kpviewscrollablecontainer.h> + + +struct kpUnzoomedThumbnailViewPrivate +{ +}; + + +kpUnzoomedThumbnailView::kpUnzoomedThumbnailView ( + kpDocument *document, + kpToolToolBar *toolToolBar, + kpViewManager *viewManager, + kpView *buddyView, + kpViewScrollableContainer *scrollableContainer, + QWidget *parent, const char *name) + + : kpThumbnailView (document, toolToolBar, viewManager, + buddyView, + scrollableContainer, + parent, name), + d (new kpUnzoomedThumbnailViewPrivate ()) +{ + if (buddyViewScrollableContainer ()) + { + connect (buddyViewScrollableContainer (), + SIGNAL (contentsMovingSoon (int, int)), + this, + SLOT (adjustToEnvironment ())); + } + + // Call to virtual function - this is why the class is sealed + adjustToEnvironment (); +} + + +kpUnzoomedThumbnailView::~kpUnzoomedThumbnailView () +{ + delete d; +} + + +// public virtual [base kpThumbnailView] +QString kpUnzoomedThumbnailView::caption () const +{ + return i18n ("Unzoomed Mode - Thumbnail"); +} + + +// public slot virtual [base kpView] +void kpUnzoomedThumbnailView::adjustToEnvironment () +{ + if (!buddyView () || !buddyViewScrollableContainer () || !document ()) + return; + + const int scrollViewContentsX = + buddyViewScrollableContainer ()->contentsXSoon (); + const int scrollViewContentsY = + buddyViewScrollableContainer ()->contentsYSoon (); + +#if DEBUG_KP_UNZOOMED_THUMBNAIL_VIEW + kdDebug () << "kpUnzoomedThumbnailView(" << name () + << ")::adjustToEnvironment(" + << scrollViewContentsX + << "," + << scrollViewContentsY + << ") width=" << width () + << " height=" << height () + << endl; +#endif + + +#if 1 + int x; + if (document ()->width () > width ()) + { + x = (int) buddyView ()->transformViewToDocX (scrollViewContentsX); + const int rightMostAllowedX = QMAX (0, document ()->width () - width ()); + #if DEBUG_KP_UNZOOMED_THUMBNAIL_VIEW + kdDebug () << "\tdocX=" << x + << " docWidth=" << document ()->width () + << " rightMostAllowedX=" << rightMostAllowedX + << endl; + #endif + if (x > rightMostAllowedX) + x = rightMostAllowedX; + } + // Thumbnail width <= doc width + else + { + // Centre X (rather than flush left to be consistent with + // kpZoomedThumbnailView) + x = -(width () - document ()->width ()) / 2; + } + + + int y; + if (document ()->height () > height ()) + { + y = (int) buddyView ()->transformViewToDocY (scrollViewContentsY); + const int bottomMostAllowedY = QMAX (0, document ()->height () - height ()); + #if DEBUG_KP_UNZOOMED_THUMBNAIL_VIEW + kdDebug () << "\tdocY=" << y + << " docHeight=" << document ()->height () + << " bottomMostAllowedY=" << bottomMostAllowedY + << endl; + #endif + if (y > bottomMostAllowedY) + y = bottomMostAllowedY; + } + // Thumbnail height <= doc height + else + { + // Centre Y (rather than flush top to be consistent with + // kpZoomedThumbnailView) + y = -(height () - document ()->height ()) / 2; + } +// Prefer to keep visible area centred in thumbnail instead of flushed left. +// Gives more editing context to the left and top. +// But feels awkward for left-to-right users. So disabled for now. +// Not totally tested. +#else + if (!buddyViewScrollableContainer ()) + return; + + QRect docRect = buddyView ()->transformViewToDoc ( + QRect (buddyViewScrollableContainer ()->contentsXSoon (), + buddyViewScrollableContainer ()->contentsYSoon (), + QMIN (buddyView ()->width (), buddyViewScrollableContainer ()->visibleWidth ()), + QMIN (buddyView ()->height (), buddyViewScrollableContainer ()->visibleHeight ()))); + + x = docRect.x () - (width () - docRect.width ()) / 2; + kdDebug () << "\tnew suggest x=" << x << endl; + const int rightMostAllowedX = QMAX (0, document ()->width () - width ()); + if (x < 0) + x = 0; + if (x > rightMostAllowedX) + x = rightMostAllowedX; + + y = docRect.y () - (height () - docRect.height ()) / 2; + kdDebug () << "\tnew suggest y=" << y << endl; + const int bottomMostAllowedY = QMAX (0, document ()->height () - height ()); + if (y < 0) + y = 0; + if (y > bottomMostAllowedY) + y = bottomMostAllowedY; +#endif + + + if (viewManager ()) + { + viewManager ()->setFastUpdates (); + viewManager ()->setQueueUpdates (); + } + + { + // OPT: scrollView impl would be much, much faster + setOrigin (QPoint (-x, -y)); + setMaskToCoverDocument (); + + // Above might be a NOP even if e.g. doc size changed so force + // update + if (viewManager ()) + viewManager ()->updateView (this); + } + + if (viewManager ()) + { + viewManager ()->restoreQueueUpdates (); + viewManager ()->restoreFastUpdates (); + } +} + + +#include <kpunzoomedthumbnailview.moc> diff --git a/kolourpaint/views/kpunzoomedthumbnailview.h b/kolourpaint/views/kpunzoomedthumbnailview.h new file mode 100644 index 00000000..0f7ccf53 --- /dev/null +++ b/kolourpaint/views/kpunzoomedthumbnailview.h @@ -0,0 +1,106 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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_UNZOOMED_THUMBNAIL_VIEW_H +#define KP_UNZOOMED_THUMBNAIL_VIEW_H + + +#include <kpthumbnailview.h> + + +class kpViewScrollableContainer; + + +/** + * @short Unzoomed thumbnail view of a document. + * + * This is an unzoomed thumbnail view of a document. Unlike + * @ref kpZoomedThumbnailView, it never changes the zoom level. And unlike + * @ref kpZoomedView, it never resizes itself. Instead, it changes its + * origin according to the main view's scrollable container so that the + * top-left most document pixel displayed in the scrollable container will + * be visible. + * + * Do not call setZoomLevel() nor setOrigin(). + * + * This class is sealed. Do not derive from it. + * + * @author Clarence Dang <dang@kde.org> + */ +/*sealed*/ class kpUnzoomedThumbnailView : public kpThumbnailView +{ +Q_OBJECT + +public: + /** + * Constructs an unzoomed thumbnail view. + */ + kpUnzoomedThumbnailView (kpDocument *document, + kpToolToolBar *toolToolBar, + kpViewManager *viewManager, + kpView *buddyView, + kpViewScrollableContainer *scrollableContainer, + QWidget *parent, const char *name); + + /** + * Destructs an unzoomed thumbnail view. + */ + virtual ~kpUnzoomedThumbnailView (); + + + /** + * Implements @ref kpThumbnailView. + */ + QString caption () const; + + +public slots: + /** + * Changes its origin according to the main view's scrollable container + * so that the top-left most document pixel displayed in the scrollable + * container will be visible. + * + * It tries to maximise the used area of this view. Unused areas will + * be set to the widget background thanks to the mask. + * + * Call this if the size of the document changes. + * Already connected to buddyViewScrollableContainer()'s + * contentsMovingSoon(int,int) signal. + * Already called by @ref kpThumbnailView resizeEvent(). + * + * Implements @ref kpView. + */ + virtual void adjustToEnvironment (); + + +private: + struct kpUnzoomedThumbnailViewPrivate *d; +}; + + +#endif // KP_UNZOOMED_THUMBNAIL_VIEW_H diff --git a/kolourpaint/views/kpzoomedthumbnailview.cpp b/kolourpaint/views/kpzoomedthumbnailview.cpp new file mode 100644 index 00000000..ecbfd317 --- /dev/null +++ b/kolourpaint/views/kpzoomedthumbnailview.cpp @@ -0,0 +1,140 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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_ZOOMED_THUMBNAIL_VIEW 0 + + +#include <kpzoomedthumbnailview.h> + +#include <kdebug.h> +#include <klocale.h> + +#include <kpdocument.h> +#include <kpviewmanager.h> + + +kpZoomedThumbnailView::kpZoomedThumbnailView (kpDocument *document, + kpToolToolBar *toolToolBar, + kpViewManager *viewManager, + kpView *buddyView, + kpViewScrollableContainer *scrollableContainer, + QWidget *parent, const char *name) + + : kpThumbnailView (document, toolToolBar, viewManager, + buddyView, + scrollableContainer, + parent, name) +{ + // Call to virtual function - this is why the class is sealed + adjustToEnvironment (); +} + + +kpZoomedThumbnailView::~kpZoomedThumbnailView () +{ +} + + +// public virtual [base kpThumbnailView] +QString kpZoomedThumbnailView::caption () const +{ + return i18n ("%1% - Thumbnail").arg (zoomLevelX ()); +} + + +// public slot virtual [base kpView] +void kpZoomedThumbnailView::adjustToEnvironment () +{ +#if DEBUG_KP_ZOOMED_THUMBNAIL_VIEW + kdDebug () << "kpZoomedThumbnailView(" << name () + << ")::adjustToEnvironment()" + << " width=" << width () + << " height=" << height () + << endl; +#endif + + if (!document ()) + return; + +#if DEBUG_KP_ZOOMED_THUMBNAIL_VIEW + kdDebug () << "\tdoc: width=" << document ()->width () + << " height=" << document ()->height () + << endl; +#endif + + if (document ()->width () <= 0 || document ()->height () <= 0) + { + kdError () << "kpZoomedThumbnailView::adjustToEnvironment() doc:" + << " width=" << document ()->width () + << " height=" << document ()->height () + << endl; + return; + } + + + int hzoom = QMAX (1, width () * 100 / document ()->width ()); + int vzoom = QMAX (1, height () * 100 / document ()->height ()); + + // keep aspect ratio + if (hzoom < vzoom) + vzoom = hzoom; + else + hzoom = vzoom; + +#if DEBUG_KP_ZOOMED_THUMBNAIL_VIEW && 1 + kdDebug () << "\tproposed zoom=" << hzoom << endl; +#endif + if (hzoom > 100 || vzoom > 100) + { + #if DEBUG_KP_ZOOMED_THUMBNAIL_VIEW && 1 + kdDebug () << "\twon't magnify - setting zoom to 100%" << endl; + #endif + hzoom = 100, vzoom = 100; + } + + + if (viewManager ()) + viewManager ()->setQueueUpdates (); + + { + setZoomLevel (hzoom, vzoom); + + setOrigin (QPoint ((width () - zoomedDocWidth ()) / 2, + (height () - zoomedDocHeight ()) / 2)); + setMaskToCoverDocument (); + + if (viewManager ()) + viewManager ()->updateView (this); + } + + if (viewManager ()) + viewManager ()->restoreQueueUpdates (); +} + + +#include <kpzoomedthumbnailview.moc> diff --git a/kolourpaint/views/kpzoomedthumbnailview.h b/kolourpaint/views/kpzoomedthumbnailview.h new file mode 100644 index 00000000..0bcb367c --- /dev/null +++ b/kolourpaint/views/kpzoomedthumbnailview.h @@ -0,0 +1,95 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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_ZOOMED_THUMBNAIL_VIEW_H +#define KP_ZOOMED_THUMBNAIL_VIEW_H + + +#include <kpthumbnailview.h> + + +/** + * @short Zoomed thumbnail view of a document. + * + * This is a zoomed thumbnail view of a document. Unlike @ref kpZoomedView, + * it never resizes itself. Instead, it changes its zoom level to + * accommodate the display of entire document in the view, while + * maintaining aspect. + * + * Do not call setZoomLevel() nor setOrigin(). + * + * This class is sealed. Do not derive from it. + * + * @author Clarence Dang <dang@kde.org> + */ +/*sealed*/ class kpZoomedThumbnailView : public kpThumbnailView +{ +Q_OBJECT + +public: + /** + * Constructs a zoomed thumbnail view. + */ + kpZoomedThumbnailView (kpDocument *document, + kpToolToolBar *toolToolBar, + kpViewManager *viewManager, + kpView *buddyView, + kpViewScrollableContainer *scrollableContainer, + QWidget *parent, const char *name); + + /** + * Destructs a zoomed thumbnail view. + */ + virtual ~kpZoomedThumbnailView (); + + + /** + * Implements @ref kpThumbnailView. + */ + QString caption () const; + + +public slots: + /** + * Changes its zoom level to accommodate the display of entire document + * in the view. It maintains aspect by changing the origin and mask. + * + * Call this if the size of the document changes. + * Already called by @ref kpThumbnailView resizeEvent(). + * + * Implements @ref kpView. + */ + virtual void adjustToEnvironment (); + + +private: + struct kpZoomedThumbnailViewPrivate *d; +}; + + +#endif // KP_ZOOMED_THUMBNAIL_VIEW_H diff --git a/kolourpaint/views/kpzoomedview.cpp b/kolourpaint/views/kpzoomedview.cpp new file mode 100644 index 00000000..ef1d6981 --- /dev/null +++ b/kolourpaint/views/kpzoomedview.cpp @@ -0,0 +1,103 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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_ZOOMED_VIEW 0 + + +#include <kpzoomedview.h> + +#include <kdebug.h> + +#include <kpdocument.h> +#include <kpview.h> +#include <kpviewmanager.h> + + +kpZoomedView::kpZoomedView (kpDocument *document, + kpToolToolBar *toolToolBar, + kpViewManager *viewManager, + kpView *buddyView, + kpViewScrollableContainer *scrollableContainer, + QWidget *parent, const char *name) + + : kpView (document, toolToolBar, viewManager, + buddyView, + scrollableContainer, + parent, name) +{ + // Call to virtual function - this is why the class is sealed + adjustToEnvironment (); +} + +kpZoomedView::~kpZoomedView () +{ +} + + +// public virtual [base kpView] +void kpZoomedView::setZoomLevel (int hzoom, int vzoom) +{ +#if DEBUG_KP_ZOOMED_VIEW + kdDebug () << "kpZoomedView(" << name () << ")::setZoomLevel(" + << hzoom << "," << vzoom << ")" << endl; +#endif + + if (viewManager ()) + viewManager ()->setQueueUpdates (); + + { + kpView::setZoomLevel (hzoom, vzoom); + + adjustToEnvironment (); + } + + if (viewManager ()) + viewManager ()->restoreQueueUpdates (); +} + + +// public slot virtual [base kpView] +void kpZoomedView::adjustToEnvironment () +{ +#if DEBUG_KP_ZOOMED_VIEW + kdDebug () << "kpZoomedView(" << name () << ")::adjustToEnvironment()" + << " doc: width=" << document ()->width () + << " height=" << document ()->height () + << endl; +#endif + + if (document ()) + { + // TODO: use zoomedDocWidth() & zoomedDocHeight()? + resize ((int) transformDocToViewX (document ()->width ()), + (int) transformDocToViewY (document ()->height ())); + } +} + + +#include <kpzoomedview.moc> diff --git a/kolourpaint/views/kpzoomedview.h b/kolourpaint/views/kpzoomedview.h new file mode 100644 index 00000000..c3729282 --- /dev/null +++ b/kolourpaint/views/kpzoomedview.h @@ -0,0 +1,96 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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_ZOOMED_VIEW_H +#define KP_ZOOMED_VIEW_H + + +#include <kpview.h> + + +/** + * @short Zoomed view of a document. Suitable as an ordinary editing view. + * + * This is a zoomed view of a document. It resizes according to the size + * of the document and the zoom level. Do not manually call resize() for + * this reason. + * + * It is suitable as an ordinary editing view. + * + * Do not call setOrigin(). + * + * This class is sealed. Do not derive from it. + * + * @author Clarence Dang <dang@kde.org> + */ +/*sealed*/ class kpZoomedView : public kpView +{ +Q_OBJECT + +public: + /** + * Constructs a zoomed view. + */ + kpZoomedView (kpDocument *document, + kpToolToolBar *toolToolBar, + kpViewManager *viewManager, + kpView *buddyView, + kpViewScrollableContainer *scrollableContainer, + QWidget *parent, const char *name); + + /** + * Destructs an unzoomed view. + */ + virtual ~kpZoomedView (); + + + /** + * Extends @kpView. Calls adjustToEnvironment(). + */ + virtual void setZoomLevel (int hzoom, int vzoom); + + +public slots: + /** + * Resizes itself so that the entire document in the zoom level fits + * almost perfectly. + * + * Call this if the size of the document changes. + * Already called by setZoomLevel(). + * + * Implements @ref kpView. + */ + virtual void adjustToEnvironment (); + + +private: + struct kpZoomedViewPrivate *d; +}; + + +#endif // KP_ZOOMED_VIEW_H diff --git a/kolourpaint/widgets/Makefile.am b/kolourpaint/widgets/Makefile.am new file mode 100644 index 00000000..f6501fac --- /dev/null +++ b/kolourpaint/widgets/Makefile.am @@ -0,0 +1,21 @@ +INCLUDES = -I$(srcdir)/.. -I$(srcdir)/../cursors -I$(srcdir)/../interfaces \ + -I$(srcdir)/../pixmapfx \ + -I$(srcdir)/../tools \ + -I$(srcdir)/../views \ + -I$(srcdir)/../widgets $(all_includes) + +noinst_LTLIBRARIES = libkolourpaintwidgets.la +libkolourpaintwidgets_la_SOURCES = kpcolorsimilaritycube.cpp \ + kpcolorsimilaritydialog.cpp \ + kpcolortoolbar.cpp \ + kpresizesignallinglabel.cpp \ + kpsqueezedtextlabel.cpp \ + kptooltoolbar.cpp \ + kptoolwidgetbase.cpp kptoolwidgetbrush.cpp \ + kptoolwidgeterasersize.cpp kptoolwidgetfillstyle.cpp \ + kptoolwidgetlinewidth.cpp \ + kptoolwidgetopaqueortransparent.cpp \ + kptoolwidgetspraycansize.cpp + +METASOURCES = AUTO + diff --git a/kolourpaint/widgets/kpcolorsimilaritycube.cpp b/kolourpaint/widgets/kpcolorsimilaritycube.cpp new file mode 100644 index 00000000..9fe3f29b --- /dev/null +++ b/kolourpaint/widgets/kpcolorsimilaritycube.cpp @@ -0,0 +1,348 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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_SIMILARITY_CUBE 0 + + +#include <kpcolorsimilaritycube.h> + +#include <math.h> + +#include <qpainter.h> +#include <qpixmap.h> +#include <qwhatsthis.h> + +#include <kdebug.h> +#include <klocale.h> + +#include <kpcolor.h> +#include <kpcolorsimilaritydialog.h> +#include <kpdefs.h> + + +const double kpColorSimilarityCube::colorCubeDiagonalDistance = + sqrt (255 * 255 * 3); + +kpColorSimilarityCube::kpColorSimilarityCube (int look, + kpMainWindow *mainWindow, + QWidget *parent, + const char *name) + : QFrame (parent, name, Qt::WNoAutoErase/*no flicker*/), + m_mainWindow (mainWindow), + m_colorSimilarity (-1) +{ + if (look & Depressed) + setFrameStyle (QFrame::Panel | QFrame::Sunken); + + setColorSimilarity (0); + + + // Don't cause the translators grief by appending strings + // - duplicate text with 2 cases + + if (look & DoubleClickInstructions) + { + QWhatsThis::add (this, + i18n ("<qt><p><b>Color Similarity</b> is how close " + "colors must be in the RGB Color Cube " + "to be considered the same.</p>" + + "<p>If you set it to something " + "other than <b>Exact</b>, " + "you can work more effectively with dithered " + "images and photos.</p>" + + "<p>This feature applies to transparent selections, as well as " + "the Flood Fill, Color Eraser and Autocrop " + "tools.</p>" + + // sync: different to else case + "<p>To configure it, double click on the cube.</p>" + + "</qt>")); + } + else + { + QWhatsThis::add (this, + i18n ("<qt><p><b>Color Similarity</b> is how close " + "colors must be in the RGB Color Cube " + "to be considered the same.</p>" + + "<p>If you set it to something " + "other than <b>Exact</b>, " + "you can work more effectively with dithered " + "images and photos.</p>" + + "<p>This feature applies to transparent selections, as well as " + "the Flood Fill, Color Eraser and Autocrop " + "tools.</p>" + + "</qt>")); + } +} + +kpColorSimilarityCube::~kpColorSimilarityCube () +{ +} + + +// public +double kpColorSimilarityCube::colorSimilarity () const +{ + return m_colorSimilarity; +} + +// public +void kpColorSimilarityCube::setColorSimilarity (double similarity) +{ +#if DEBUG_KP_COLOR_SIMILARITY_CUBE + kdDebug () << "kpColorSimilarityCube::setColorSimilarity(" << similarity << ")" << endl; +#endif + + if (m_colorSimilarity == similarity) + return; + + if (similarity < 0) + similarity = 0; + else if (similarity > kpColorSimilarityDialog::maximumColorSimilarity) + similarity = kpColorSimilarityDialog::maximumColorSimilarity; + + m_colorSimilarity = similarity; + + repaint (false/*no erase*/); +} + + +// protected virtual [base QWidget] +QSize kpColorSimilarityCube::sizeHint () const +{ + return QSize (52, 52); +} + + +// protected +QColor kpColorSimilarityCube::color (int redOrGreenOrBlue, + int baseBrightness, + int similarityDirection) const +{ + int brightness = int (baseBrightness + + similarityDirection * + .5 * m_colorSimilarity * kpColorSimilarityCube::colorCubeDiagonalDistance); + + if (brightness < 0) + brightness = 0; + else if (brightness > 255) + brightness = 255; + + switch (redOrGreenOrBlue) + { + default: + case 0: return QColor (brightness, 0, 0); + case 1: return QColor (0, brightness, 0); + case 2: return QColor (0, 0, brightness); + } +} + +static QPoint pointBetween (const QPoint &p, const QPoint &q) +{ + return QPoint ((p.x () + q.x ()) / 2, (p.y () + q.y ()) / 2); +} + +static void drawQuadrant (QPainter *p, + const QColor &col, + const QPoint &p1, const QPoint &p2, const QPoint &p3, + const QPoint pointNotOnOutline) +{ + p->save (); + + + QPointArray points (4); + points [0] = p1; + points [1] = p2; + points [2] = p3; + points [3] = pointNotOnOutline; + + p->setPen (col); + p->setBrush (col); + p->drawPolygon (points); + + + points.resize (3); + + p->setPen (Qt::black); + p->setBrush (Qt::NoBrush); + p->drawPolyline (points); + + + p->restore (); +} + +// protected +void kpColorSimilarityCube::drawFace (QPainter *p, + int redOrGreenOrBlue, + const QPoint &tl, const QPoint &tr, + const QPoint &bl, const QPoint &br) +{ +#if DEBUG_KP_COLOR_SIMILARITY_CUBE + kdDebug () << "kpColorSimilarityCube(RorGorB=" << redOrGreenOrBlue + << ",tl=" << tl + << ",tr=" << tr + << ",bl=" << bl + << ",br=" << br + << ")" + << endl; +#endif + + // tl --- tm --- tr + // | | | + // | | | + // ml --- mm --- mr + // | | | + // | | | + // bl --- bm --- br + + const QPoint tm (::pointBetween (tl, tr)); + const QPoint bm (::pointBetween (bl, br)); + + const QPoint ml (::pointBetween (tl, bl)); + const QPoint mr (::pointBetween (tr, br)); + const QPoint mm (::pointBetween (ml, mr)); + + + const int baseBrightness = QMAX (127, + 255 - int (kpColorSimilarityDialog::maximumColorSimilarity * + kpColorSimilarityCube::colorCubeDiagonalDistance / 2)); + QColor colors [2] = + { + color (redOrGreenOrBlue, baseBrightness, -1), + color (redOrGreenOrBlue, baseBrightness, +1) + }; + + if (!isEnabled ()) + { + #if DEBUG_KP_COLOR_SIMILARITY_CUBE + kdDebug () << "\tnot enabled - making us grey" << endl; + #endif + colors [0] = colorGroup ().background (); + colors [1] = colorGroup ().background (); + } + +#if DEBUG_KP_COLOR_SIMILARITY_CUBE + kdDebug () << "\tmaxColorSimilarity=" << kpColorSimilarityDialog::maximumColorSimilarity + << " colorCubeDiagDist=" << kpColorSimilarityCube::colorCubeDiagonalDistance + << endl + << "\tbaseBrightness=" << baseBrightness + << " color[0]=" << ((colors [0].rgb () & RGB_MASK) >> ((2 - redOrGreenOrBlue) * 8)) + << " color[1]=" << ((colors [1].rgb () & RGB_MASK) >> ((2 - redOrGreenOrBlue) * 8)) + << endl; +#endif + + + ::drawQuadrant (p, colors [0], tm, tl, ml, mm); + ::drawQuadrant (p, colors [1], tm, tr, mr, mm); + ::drawQuadrant (p, colors [1], ml, bl, bm, mm); + ::drawQuadrant (p, colors [0], bm, br, mr, mm); +} + +// protected virtual [base QFrame] +void kpColorSimilarityCube::drawContents (QPainter *p) +{ + QRect cr (contentsRect ()); + + QPixmap backBuffer (cr.width (), cr.height ()); + backBuffer.fill (colorGroup ().background ()); + + QPainter backBufferPainter (&backBuffer); + + int cubeRectSize = QMIN (cr.width () * 6 / 8, cr.height () * 6 / 8); + int dx = (cr.width () - cubeRectSize) / 2, + dy = (cr.height () - cubeRectSize) / 2; + backBufferPainter.translate (dx, dy); + + // + // P------- Q --- --- + // / / | | | + // /A / | side | + // R-------S T --- cubeRectSize + // | | / / | + // S | | / side | + // U-------V --- --- + // |-------| + // side + // |-----------| + // cubeRectSize + // + // + + const double angle = KP_DEGREES_TO_RADIANS (45); + // S + S sin A = cubeRectSize + // (1 + sin A) x S = cubeRectSize + const double side = double (cubeRectSize) / (1 + sin (angle)); + + + const QPoint pointP ((int) (side * cos (angle)), 0); + const QPoint pointQ ((int) (side * cos (angle) + side), 0); + const QPoint pointR (0, (int) (side * sin (angle))); + const QPoint pointS ((int) (side), (int) (side * sin (angle))); + const QPoint pointU (0, (int) (side * sin (angle) + side)); + const QPoint pointT ((int) (side + side * cos (angle)), (int) (side)); + const QPoint pointV ((int) (side), (int) (side * sin (angle) + side)); + + + // Top Face + drawFace (&backBufferPainter, + 0/*red*/, + pointP, pointQ, + pointR, pointS); + + + // Bottom Face + drawFace (&backBufferPainter, + 1/*green*/, + pointR, pointS, + pointU, pointV); + + + // Right Face + drawFace (&backBufferPainter, + 2/*blue*/, + pointS, pointQ, + pointV, pointT); + + +#if 0 + backBufferPainter.save (); + backBufferPainter.setPen (Qt::cyan); + backBufferPainter.drawRect (0, 0, cubeRectSize, cubeRectSize); + backBufferPainter.restore (); +#endif + + + backBufferPainter.end (); + + p->drawPixmap (cr, backBuffer); +} diff --git a/kolourpaint/widgets/kpcolorsimilaritycube.h b/kolourpaint/widgets/kpcolorsimilaritycube.h new file mode 100644 index 00000000..358d4b3a --- /dev/null +++ b/kolourpaint/widgets/kpcolorsimilaritycube.h @@ -0,0 +1,72 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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_cube_h__ +#define __kp_color_similarity_cube_h__ + +#include <qframe.h> + +class kpColor; +class kpMainWindow; + +class kpColorSimilarityCube : public QFrame +{ +public: + enum Look + { + Plain = 0, + Depressed = 1, + DoubleClickInstructions = 2 + }; + + kpColorSimilarityCube (int look, + kpMainWindow *mainWindow, + QWidget *parent, + const char *name = 0); + virtual ~kpColorSimilarityCube (); + + static const double colorCubeDiagonalDistance; + + double colorSimilarity () const; + void setColorSimilarity (double similarity); + + virtual QSize sizeHint () const; + +protected: + QColor color (int redOrGreenOrBlue, int baseBrightness, int similarityDirection) const; + void drawFace (QPainter *p, + int redOrGreenOrBlue, + const QPoint &tl, const QPoint &tr, + const QPoint &bl, const QPoint &br); + virtual void drawContents (QPainter *p); + + kpMainWindow *m_mainWindow; + double m_colorSimilarity; +}; + +#endif // __kp_color_similarity_cube_h__ diff --git a/kolourpaint/widgets/kpcolorsimilaritydialog.cpp b/kolourpaint/widgets/kpcolorsimilaritydialog.cpp new file mode 100644 index 00000000..d2766568 --- /dev/null +++ b/kolourpaint/widgets/kpcolorsimilaritydialog.cpp @@ -0,0 +1,123 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <qgroupbox.h> +#include <qlabel.h> +#include <qlayout.h> +#include <qpushbutton.h> + +#include <klocale.h> +#include <knuminput.h> + +#include <kpcolorsimilaritycube.h> + + +// public static +const double kpColorSimilarityDialog::maximumColorSimilarity = .30; + + +kpColorSimilarityDialog::kpColorSimilarityDialog (kpMainWindow *mainWindow, + QWidget *parent, + const char *name) + : KDialogBase (parent, name, true/*modal*/, + i18n ("Color Similarity"), + KDialogBase::Ok | KDialogBase::Cancel), + m_mainWindow (mainWindow) +{ + QWidget *baseWidget = new QWidget (this); + setMainWidget (baseWidget); + + + QGroupBox *cubeGroupBox = new QGroupBox (i18n ("Preview"), baseWidget); + + m_colorSimilarityCube = new kpColorSimilarityCube (kpColorSimilarityCube::Plain, + mainWindow, cubeGroupBox); + m_colorSimilarityCube->setMinimumSize (240, 180); + + QPushButton *updatePushButton = new QPushButton (i18n ("&Update"), cubeGroupBox); + + + QVBoxLayout *cubeLayout = new QVBoxLayout (cubeGroupBox, marginHint () * 2, spacingHint ()); + cubeLayout->addWidget (m_colorSimilarityCube, 1/*stretch*/); + cubeLayout->addWidget (updatePushButton, 0/*stretch*/, Qt::AlignHCenter); + + + connect (updatePushButton, SIGNAL (clicked ()), + this, SLOT (slotColorSimilarityValueChanged ())); + + + QGroupBox *inputGroupBox = new QGroupBox (i18n ("RGB Color Cube Distance"), baseWidget); + + m_colorSimilarityInput = new KIntNumInput (inputGroupBox); + m_colorSimilarityInput->setRange (0, int (kpColorSimilarityDialog::maximumColorSimilarity * 100 + .1/*don't floor below target int*/), + 5/*step*/, true/*slider*/); + m_colorSimilarityInput->setSuffix (i18n ("%")); + m_colorSimilarityInput->setSpecialValueText (i18n ("Exact Match")); + + + QVBoxLayout *inputLayout = new QVBoxLayout (inputGroupBox, marginHint () * 2, spacingHint ()); + inputLayout->addWidget (m_colorSimilarityInput); + + + connect (m_colorSimilarityInput, SIGNAL (valueChanged (int)), + this, SLOT (slotColorSimilarityValueChanged ())); + + + QVBoxLayout *baseLayout = new QVBoxLayout (baseWidget, 0/*margin*/, spacingHint () * 2); + baseLayout->addWidget (cubeGroupBox, 1/*stretch*/); + baseLayout->addWidget (inputGroupBox); +} + +kpColorSimilarityDialog::~kpColorSimilarityDialog () +{ +} + + +// public +double kpColorSimilarityDialog::colorSimilarity () const +{ + return m_colorSimilarityCube->colorSimilarity (); +} + +// public +void kpColorSimilarityDialog::setColorSimilarity (double similarity) +{ + m_colorSimilarityInput->setValue (qRound (similarity * 100)); +} + + +// private slot +void kpColorSimilarityDialog::slotColorSimilarityValueChanged () +{ + m_colorSimilarityCube->setColorSimilarity (double (m_colorSimilarityInput->value ()) / 100); +} + + +#include <kpcolorsimilaritydialog.moc> diff --git a/kolourpaint/widgets/kpcolorsimilaritydialog.h b/kolourpaint/widgets/kpcolorsimilaritydialog.h new file mode 100644 index 00000000..fd70ecd0 --- /dev/null +++ b/kolourpaint/widgets/kpcolorsimilaritydialog.h @@ -0,0 +1,62 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <kdialogbase.h> + +class KIntNumInput; + +class kpColorSimilarityCube; +class kpMainWindow; + +class kpColorSimilarityDialog : public KDialogBase +{ +Q_OBJECT + +public: + kpColorSimilarityDialog (kpMainWindow *mainWindow, + QWidget *parent, + const char *name = 0); + virtual ~kpColorSimilarityDialog (); + + double colorSimilarity () const; + void setColorSimilarity (double similarity); + + static const double maximumColorSimilarity; + +private slots: + void slotColorSimilarityValueChanged (); + +private: + kpMainWindow *m_mainWindow; + kpColorSimilarityCube *m_colorSimilarityCube; + KIntNumInput *m_colorSimilarityInput; +}; + +#endif // __kp_color_similarity_dialog_h__ diff --git a/kolourpaint/widgets/kpcolortoolbar.cpp b/kolourpaint/widgets/kpcolortoolbar.cpp new file mode 100644 index 00000000..cba73b4f --- /dev/null +++ b/kolourpaint/widgets/kpcolortoolbar.cpp @@ -0,0 +1,1112 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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_TOOL_BAR 0 + + +#include <kpcolortoolbar.h> + +#include <qbitmap.h> +#include <qdrawutil.h> +#include <qframe.h> +#include <qlayout.h> +#include <qpainter.h> +#include <qsize.h> +#include <qtooltip.h> +#include <qwidget.h> + +#include <kapplication.h> +#include <kcolordialog.h> +#include <kcolordrag.h> +#include <kconfig.h> +#include <kdebug.h> +#include <kiconloader.h> +#include <klocale.h> + +#include <kpcolorsimilaritydialog.h> +#include <kpdefs.h> +#include <kpmainwindow.h> +#include <kppixmapfx.h> +#include <kptool.h> +#include <kpview.h> + + +/* + * kpDualColorButton + */ + +kpDualColorButton::kpDualColorButton (kpMainWindow *mainWindow, + QWidget *parent, const char *name) + : QFrame (parent, name, Qt::WNoAutoErase/*no flicker*/), + m_mainWindow (mainWindow), + m_backBuffer (0) +{ + setFrameStyle (QFrame::Panel | QFrame::Sunken); + + m_color [0] = kpColor (0, 0, 0); // black + m_color [1] = kpColor (255, 255, 255); // white + + setAcceptDrops (true); +} + +kpDualColorButton::~kpDualColorButton () +{ + delete m_backBuffer; m_backBuffer = 0; +} + + +kpColor kpDualColorButton::color (int which) const +{ + if (which < 0 || which > 1) + { + kdWarning () << "kpDualColorButton::color (" << which + << ") - out of range" << endl; + which = 0; + } + + return m_color [which]; +} + +kpColor kpDualColorButton::foregroundColor () const +{ + return color (0); +} + +kpColor kpDualColorButton::backgroundColor () const +{ + return color (1); +} + + +void kpDualColorButton::setColor (int which, const kpColor &color) +{ + if (which < 0 || which > 1) + { + kdWarning () << "kpDualColorButton::setColor (" << which + << ") - out of range" << endl; + which = 0; + } + + if (m_color [which] == color) + return; + + m_oldColor [which] = m_color [which]; + m_color [which] = color; + update (); + + if (which == 0) + emit foregroundColorChanged (color); + else + emit backgroundColorChanged (color); +} + +void kpDualColorButton::setForegroundColor (const kpColor &color) +{ + setColor (0, color); +} + +void kpDualColorButton::setBackgroundColor (const kpColor &color) +{ + setColor (1, color); +} + + +// public +kpColor kpDualColorButton::oldForegroundColor () const +{ + return m_oldColor [0]; +} + +// public +kpColor kpDualColorButton::oldBackgroundColor () const +{ + return m_oldColor [1]; +} + + +// public virtual [base QWidget] +QSize kpDualColorButton::sizeHint () const +{ + return QSize (52, 52); +} + + +// protected +QRect kpDualColorButton::swapPixmapRect () const +{ + QPixmap swapPixmap = UserIcon ("colorbutton_swap_16x16"); + + return QRect (contentsRect ().width () - swapPixmap.width (), + 0, + swapPixmap.width (), + swapPixmap.height ()); +} + +// protected +QRect kpDualColorButton::foregroundBackgroundRect () const +{ + QRect cr (contentsRect ()); + return QRect (cr.width () / 8, + cr.height () / 8, + cr.width () * 6 / 8, + cr.height () * 6 / 8); +} + +// protected +QRect kpDualColorButton::foregroundRect () const +{ + QRect fbr (foregroundBackgroundRect ()); + return QRect (fbr.x (), + fbr.y (), + fbr.width () * 3 / 4, + fbr.height () * 3 / 4); +} + +// protected +QRect kpDualColorButton::backgroundRect () const +{ + QRect fbr (foregroundBackgroundRect ()); + return QRect (fbr.x () + fbr.width () / 4, + fbr.y () + fbr.height () / 4, + fbr.width () * 3 / 4, + fbr.height () * 3 / 4); +} + + +// TODO: drag a colour from this widget + +// protected virtual [base QWidget] +void kpDualColorButton::dragMoveEvent (QDragMoveEvent *e) +{ + e->accept ((foregroundRect ().contains (e->pos ()) || + backgroundRect ().contains (e->pos ())) && + KColorDrag::canDecode (e)); +} + +// protected virtual [base QWidget] +void kpDualColorButton::dropEvent (QDropEvent *e) +{ + QColor col; + KColorDrag::decode (e, col/*ref*/); + + if (col.isValid ()) + { + if (foregroundRect ().contains (e->pos ())) + setForegroundColor (kpColor (col.rgb ())); + else if (backgroundRect ().contains (e->pos ())) + setBackgroundColor (kpColor (col.rgb ())); + } +} + + +// protected virtual [base QWidget] +void kpDualColorButton::mousePressEvent (QMouseEvent * /*e*/) +{ + // eat right-mouse click to prevent it from getting to the toolbar +} + +// protected virtual [base QWidget] +void kpDualColorButton::mouseDoubleClickEvent (QMouseEvent *e) +{ + int whichColor = -1; + + if (foregroundRect ().contains (e->pos ())) + whichColor = 0; + else if (backgroundRect ().contains (e->pos ())) + whichColor = 1; + + if (whichColor == 0 || whichColor == 1) + { + QColor col = Qt::black; + if (color (whichColor).isOpaque ()) + col = color (whichColor).toQColor (); + else + { + // TODO: If you double-click on a transparent color and press OK, you get + // black, instead of the color staying as transparent. + // + // We should modify or fork KColorDialog to allow us to fix this. + // + // It would be wrong to stop the user from double-clicking on a + // transparent color as that would make the UI inconsistent, compared + // to opaque colors. + } + + // TODO: parent + if (KColorDialog::getColor (col/*ref*/)) + setColor (whichColor, kpColor (col.rgb ())); + } +} + +// protected virtual [base QWidget] +void kpDualColorButton::mouseReleaseEvent (QMouseEvent *e) +{ + if (swapPixmapRect ().contains (e->pos ()) && + m_color [0] != m_color [1]) + { + #if DEBUG_KP_COLOR_TOOL_BAR && 1 + kdDebug () << "kpDualColorButton::mouseReleaseEvent() swap colors:" << endl; + #endif + m_oldColor [0] = m_color [0]; + m_oldColor [1] = m_color [1]; + + kpColor temp = m_color [0]; + m_color [0] = m_color [1]; + m_color [1] = temp; + + update (); + + emit colorsSwapped (m_color [0], m_color [1]); + emit foregroundColorChanged (m_color [0]); + emit backgroundColorChanged (m_color [1]); + } +} + + +// protected virtual [base QFrame] +void kpDualColorButton::drawContents (QPainter *p) +{ +#if DEBUG_KP_COLOR_TOOL_BAR && 1 + kdDebug () << "kpDualColorButton::draw() rect=" << rect () + << " contentsRect=" << contentsRect () + << endl; +#endif + + if (!m_backBuffer || + m_backBuffer->width () != contentsRect ().width () || + m_backBuffer->height () != contentsRect ().height ()) + { + delete m_backBuffer; + m_backBuffer = new QPixmap (contentsRect ().width (), contentsRect ().height ()); + } + + + QPainter backBufferPainter (m_backBuffer); + + if (isEnabled () && m_mainWindow) + { + kpView::drawTransparentBackground (&backBufferPainter, + m_backBuffer->width (), m_backBuffer->height (), + m_backBuffer->rect (), + true/*preview*/); + } + else + { + backBufferPainter.fillRect (m_backBuffer->rect (), + colorGroup ().color (QColorGroup::Background)); + } + + QPixmap swapPixmap = UserIcon ("colorbutton_swap_16x16"); + if (!isEnabled ()) + { + // swapPixmap has a mask after all + swapPixmap.fill (colorGroup ().color (QColorGroup::Dark)); + } + backBufferPainter.drawPixmap (swapPixmapRect ().topLeft (), swapPixmap); + + // foreground patch must be drawn after background patch + // as it overlaps on top of background patch + QRect bgRect = backgroundRect (); + QRect bgRectInside = QRect (bgRect.x () + 2, bgRect.y () + 2, + bgRect.width () - 4, bgRect.height () - 4); + if (isEnabled ()) + { + #if DEBUG_KP_COLOR_TOOL_BAR && 1 + kdDebug () << "\tbackgroundColor=" << (int *) m_color [1].toQRgb () + << endl; + #endif + if (m_color [1].isOpaque ()) + backBufferPainter.fillRect (bgRectInside, m_color [1].toQColor ()); + else + backBufferPainter.drawPixmap (bgRectInside, UserIcon ("color_transparent_26x26")); + } + else + backBufferPainter.fillRect (bgRectInside, colorGroup ().color (QColorGroup::Button)); + qDrawShadePanel (&backBufferPainter, bgRect, colorGroup (), + false/*not sunken*/, 2/*lineWidth*/, + 0/*never fill*/); + + QRect fgRect = foregroundRect (); + QRect fgRectInside = QRect (fgRect.x () + 2, fgRect.y () + 2, + fgRect.width () - 4, fgRect.height () - 4); + if (isEnabled ()) + { + #if DEBUG_KP_COLOR_TOOL_BAR && 1 + kdDebug () << "\tforegroundColor=" << (int *) m_color [0].toQRgb () + << endl; + #endif + if (m_color [0].isOpaque ()) + backBufferPainter.fillRect (fgRectInside, m_color [0].toQColor ()); + else + backBufferPainter.drawPixmap (fgRectInside, UserIcon ("color_transparent_26x26")); + } + else + backBufferPainter.fillRect (fgRectInside, colorGroup ().color (QColorGroup::Button)); + qDrawShadePanel (&backBufferPainter, fgRect, colorGroup (), + false/*not sunken*/, 2/*lineWidth*/, + 0/*never fill*/); + + backBufferPainter.end (); + + p->drawPixmap (contentsRect (), *m_backBuffer); +} + + +/* + * kpColorCells + */ + +static inline int roundUp2 (int val) +{ + return val % 2 ? val + 1 : val; +} + +static inline int btwn0_255 (int val) +{ + if (val < 0) + return 0; + else if (val > 255) + return 255; + else + return val; +} + +enum +{ + blendDark = 25, + blendNormal = 50, + blendLight = 75, + blendAdd = 100 +}; + +static QColor blend (const QColor &a, const QColor &b, int percent = blendNormal) +{ + return QColor (btwn0_255 (roundUp2 (a.red () + b.red ()) * percent / 100), + btwn0_255 (roundUp2 (a.green () + b.green ()) * percent / 100), + btwn0_255 (roundUp2 (a.blue () + b.blue ()) * percent / 100)); +} + +static QColor add (const QColor &a, const QColor &b) +{ + return blend (a, b, blendAdd); +} + + + + + +// +// make our own colors in case weird ones like "Qt::cyan" +// (turquoise) get changed by Qt +// + +// primary colors + B&W +static QColor kpRed; +static QColor kpGreen; +static QColor kpBlue; +static QColor kpBlack; +static QColor kpWhite; + +// intentionally _not_ an HSV darkener +static QColor dark (const QColor &color) +{ + return blend (color, kpBlack); +} + +// full-brightness colors +static QColor kpYellow; +static QColor kpPurple; +static QColor kpAqua; + +// mixed colors +static QColor kpGrey; +static QColor kpLightGrey; +static QColor kpOrange; + +// pastel colors +static QColor kpPink; +static QColor kpLightGreen; +static QColor kpLightBlue; +static QColor kpTan; + +static bool ownColorsInitialised = false; + +/* TODO: clean up this code!!! + * (probably when adding palette load/save) + */ +#define rows 2 +#define cols 11 +kpColorCells::kpColorCells (QWidget *parent, + Qt::Orientation o, + const char *name) + : KColorCells (parent, rows, cols), + m_mouseButton (-1) +{ + setName (name); + + setShading (false); // no 3D look + + // Trap KColorDrag so that kpMainWindow does not trap it. + // See our impl of dropEvent(). + setAcceptDrops (true); + setAcceptDrags (true); + + connect (this, SIGNAL (colorDoubleClicked (int)), + SLOT (slotColorDoubleClicked (int))); + + if (!ownColorsInitialised) + { + // Don't initialise globally when we probably don't have a colour + // allocation context. This way, the colours aren't sometimes + // invalid (e.g. at 8-bit). + + kpRed = QColor (255, 0, 0); + kpGreen = QColor (0, 255, 0); + kpBlue = QColor (0, 0, 255); + kpBlack = QColor (0, 0, 0); + kpWhite = QColor (255, 255, 255); + + kpYellow = add (kpRed, kpGreen); + kpPurple = add (kpRed, kpBlue); + kpAqua = add (kpGreen, kpBlue); + + kpGrey = blend (kpBlack, kpWhite); + kpLightGrey = blend (kpGrey, kpWhite); + kpOrange = blend (kpRed, kpYellow); + + kpPink = blend (kpRed, kpWhite); + kpLightGreen = blend (kpGreen, kpWhite); + kpLightBlue = blend (kpBlue, kpWhite); + kpTan = blend (kpYellow, kpWhite); + + ownColorsInitialised = true; + } + + setOrientation (o); +} + +kpColorCells::~kpColorCells () +{ +} + +Qt::Orientation kpColorCells::orientation () const +{ + return m_orientation; +} + +void kpColorCells::setOrientation (Qt::Orientation o) +{ + int c, r; + + if (o == Qt::Horizontal) + { + c = cols; + r = rows; + } + else + { + c = rows; + r = cols; + } + +#if DEBUG_KP_COLOR_TOOL_BAR + kdDebug () << "kpColorCells::setOrientation(): r=" << r << " c=" << c << endl; +#endif + + setNumRows (r); + setNumCols (c); + + setCellWidth (26); + setCellHeight (26); + + setFixedSize (numCols () * cellWidth () + frameWidth () * 2, + numRows () * cellHeight () + frameWidth () * 2); + +/* + kdDebug () << "\tlimits: array=" << sizeof (colors) / sizeof (colors [0]) + << " r*c=" << r * c << endl; + kdDebug () << "\tsizeof (colors)=" << sizeof (colors) + << " sizeof (colors [0])=" << sizeof (colors [0]) + << endl;*/ + QColor colors [] = + { + kpBlack, + kpGrey, + kpRed, + kpOrange, + kpYellow, + kpGreen, + kpAqua, + kpBlue, + kpPurple, + kpPink, + kpLightGreen, + + kpWhite, + kpLightGrey, + dark (kpRed), + dark (kpOrange)/*brown*/, + dark (kpYellow), + dark (kpGreen), + dark (kpAqua), + dark (kpBlue), + dark (kpPurple), + kpLightBlue, + kpTan + }; + + for (int i = 0; + /*i < int (sizeof (colors) / sizeof (colors [0])) &&*/ + i < r * c; + i++) + { + int y, x; + int pos; + + if (o == Qt::Horizontal) + { + y = i / cols; + x = i % cols; + pos = i; + } + else + { + y = i % cols; + x = i / cols; + // int x = rows - 1 - i / cols; + pos = y * rows + x; + } + + KColorCells::setColor (pos, colors [i]); + //QToolTip::add (this, cellGeometry (y, x), colors [i].name ()); + } + + m_orientation = o; +} + +// virtual protected [base KColorCells] +void kpColorCells::dropEvent (QDropEvent *e) +{ + // Eat event so that: + // + // 1. User doesn't clobber the palette (until we support reconfigurable + // palettes) + // 2. kpMainWindow::dropEvent() doesn't try to paste colour code as text + // (when the user slips and drags colour cell a little instead of clicking) + e->accept (); +} + +// virtual protected +void kpColorCells::paintCell (QPainter *painter, int row, int col) +{ + QColor oldColor; + int cellNo; + + if (!isEnabled ()) + { + cellNo = row * numCols () + col; + + // make all cells 3D (so that disabled palette doesn't look flat) + setShading (true); + + oldColor = KColorCells::color (cellNo); + KColorCells::colors [cellNo] = backgroundColor (); + } + + + // no focus rect as it doesn't make sense + // since 2 colors (foreground & background) can be selected + KColorCells::selected = -1; + KColorCells::paintCell (painter, row, col); + + + if (!isEnabled ()) + { + KColorCells::colors [cellNo] = oldColor; + setShading (false); + } +} + +// virtual protected +void kpColorCells::mouseReleaseEvent (QMouseEvent *e) +{ + m_mouseButton = -1; + + Qt::ButtonState button = e->button (); +#if DEBUG_KP_COLOR_TOOL_BAR + kdDebug () << "kpColorCells::mouseReleaseEvent(left=" + << (button & Qt::LeftButton) + << ",right=" + << (button & Qt::RightButton) + << ")" + << endl; +#endif + if (!((button & Qt::LeftButton) && (button & Qt::RightButton))) + { + if (button & Qt::LeftButton) + m_mouseButton = 0; + else if (button & Qt::RightButton) + m_mouseButton = 1; + } + + connect (this, SIGNAL (colorSelected (int)), this, SLOT (slotColorSelected (int))); + KColorCells::mouseReleaseEvent (e); + disconnect (this, SIGNAL (colorSelected (int)), this, SLOT (slotColorSelected (int))); + +#if DEBUG_KP_COLOR_TOOL_BAR + kdDebug () << "kpColorCells::mouseReleaseEvent() setting m_mouseButton back to -1" << endl; +#endif + m_mouseButton = -1; +} + +// protected virtual [base KColorCells] +void kpColorCells::resizeEvent (QResizeEvent *e) +{ + // KColorCells::resizeEvent() tries to adjust the cellWidth and cellHeight + // to the current dimensions but doesn't take into account + // frame{Width,Height}(). + // + // In any case, we already set the cell{Width,Height} and a fixed + // widget size and don't want any of it changed. Eat the resize event. + (void) e; +} + +// protected slot +void kpColorCells::slotColorSelected (int cell) +{ +#if DEBUG_KP_COLOR_TOOL_BAR + kdDebug () << "kpColorCells::slotColorSelected(cell=" << cell + << ") mouseButton = " << m_mouseButton << endl; +#endif + QColor c = KColorCells::color (cell); + + if (m_mouseButton == 0) + { + emit foregroundColorChanged (c); + emit foregroundColorChanged (kpColor (c.rgb ())); + } + else if (m_mouseButton == 1) + { + emit backgroundColorChanged (c); + emit backgroundColorChanged (kpColor (c.rgb ())); + } + + m_mouseButton = -1; // just in case +} + +// protected slot +void kpColorCells::slotColorDoubleClicked (int cell) +{ +#if DEBUG_KP_COLOR_TOOL_BAR + kdDebug () << "kpColorCells::slotColorDoubleClicked(cell=" + << cell << ")" << endl; +#endif + + QColor color = KColorCells::color (cell); + + // TODO: parent + if (KColorDialog::getColor (color/*ref*/)) + KColorCells::setColor (cell, color); +} + + +/* + * kpTransparentColorCell + */ + +kpTransparentColorCell::kpTransparentColorCell (QWidget *parent, const char *name) + : QFrame (parent, name) +{ +#if DEBUG_KP_COLOR_TOOL_BAR + kdDebug () << "kpTransparentColorCell::kpTransparentColorCell()" << endl; +#endif + + setFrameStyle (QFrame::Panel | QFrame::Sunken); +#if DEBUG_KP_COLOR_TOOL_BAR && 0 + kdDebug () << "\tdefault line width=" << lineWidth () + << " frame width=" << frameWidth () << endl; +#endif + //setLineWidth (2); +#if DEBUG_KP_COLOR_TOOL_BAR && 0 + kdDebug () << "\tline width=" << lineWidth () + << " frame width=" << frameWidth () << endl; +#endif + + m_pixmap = UserIcon ("color_transparent_26x26"); + + QToolTip::add (this, i18n ("Transparent")); +} + +kpTransparentColorCell::~kpTransparentColorCell () +{ +} + + +// public virtual [base QWidget] +QSize kpTransparentColorCell::sizeHint () const +{ + return QSize (m_pixmap.width () + frameWidth () * 2, + m_pixmap.height () + frameWidth () * 2); +} + +// protected virtual [base QWidget] +void kpTransparentColorCell::mousePressEvent (QMouseEvent * /*e*/) +{ + // eat right-mouse click to prevent it from getting to the toolbar +} + +// protected virtual [base QWidget] +void kpTransparentColorCell::mouseReleaseEvent (QMouseEvent *e) +{ + if (rect ().contains (e->pos ())) + { + if (e->button () == Qt::LeftButton) + { + emit transparentColorSelected (0); + emit foregroundColorChanged (kpColor::transparent); + } + else if (e->button () == Qt::RightButton) + { + emit transparentColorSelected (1); + emit backgroundColorChanged (kpColor::transparent); + } + } +} + +// protected virtual [base QFrame] +void kpTransparentColorCell::drawContents (QPainter *p) +{ + QFrame::drawContents (p); + if (isEnabled ()) + { + #if DEBUG_KP_COLOR_TOOL_BAR + kdDebug () << "kpTransparentColorCell::drawContents() contentsRect=" + << contentsRect () + << endl; + #endif + p->drawPixmap (contentsRect (), m_pixmap); + } +} + + +/* + * kpColorPalette + */ + +kpColorPalette::kpColorPalette (QWidget *parent, + Qt::Orientation o, + const char *name) + : QWidget (parent, name), + m_boxLayout (0) +{ +#if DEBUG_KP_COLOR_TOOL_BAR + kdDebug () << "kpColorPalette::kpColorPalette()" << endl; +#endif + + m_transparentColorCell = new kpTransparentColorCell (this); + m_transparentColorCell->setSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed); + connect (m_transparentColorCell, SIGNAL (foregroundColorChanged (const kpColor &)), + this, SIGNAL (foregroundColorChanged (const kpColor &))); + connect (m_transparentColorCell, SIGNAL (backgroundColorChanged (const kpColor &)), + this, SIGNAL (backgroundColorChanged (const kpColor &))); + + m_colorCells = new kpColorCells (this); + connect (m_colorCells, SIGNAL (foregroundColorChanged (const kpColor &)), + this, SIGNAL (foregroundColorChanged (const kpColor &))); + connect (m_colorCells, SIGNAL (backgroundColorChanged (const kpColor &)), + this, SIGNAL (backgroundColorChanged (const kpColor &))); + + setOrientation (o); +} + +kpColorPalette::~kpColorPalette () +{ +} + +// public +Qt::Orientation kpColorPalette::orientation () const +{ + return m_orientation; +} + +void kpColorPalette::setOrientation (Qt::Orientation o) +{ + m_colorCells->setOrientation (o); + + delete m_boxLayout; + + if (o == Qt::Horizontal) + { + m_boxLayout = new QBoxLayout (this, QBoxLayout::LeftToRight, 0/*margin*/, 5/*spacing*/); + m_boxLayout->addWidget (m_transparentColorCell, 0/*stretch*/, Qt::AlignVCenter); + m_boxLayout->addWidget (m_colorCells); + } + else + { + m_boxLayout = new QBoxLayout (this, QBoxLayout::TopToBottom, 0/*margin*/, 5/*spacing*/); + m_boxLayout->addWidget (m_transparentColorCell, 0/*stretch*/, Qt::AlignHCenter); + m_boxLayout->addWidget (m_colorCells); + } + + m_orientation = o; +} + + +/* + * kpColorSimilarityToolBarItem + */ + +kpColorSimilarityToolBarItem::kpColorSimilarityToolBarItem (kpMainWindow *mainWindow, + QWidget *parent, + const char *name) + : kpColorSimilarityCube (kpColorSimilarityCube::Depressed | + kpColorSimilarityCube::DoubleClickInstructions, + mainWindow, parent, name), + m_mainWindow (mainWindow), + m_processedColorSimilarity (kpColor::Exact) +{ + setColorSimilarity (mainWindow->configColorSimilarity ()); +} + +kpColorSimilarityToolBarItem::~kpColorSimilarityToolBarItem () +{ +} + + +// public +int kpColorSimilarityToolBarItem::processedColorSimilarity () const +{ + return m_processedColorSimilarity; +} + + +// public slot +void kpColorSimilarityToolBarItem::setColorSimilarity (double similarity) +{ + m_oldColorSimilarity = colorSimilarity (); + + kpColorSimilarityCube::setColorSimilarity (similarity); + if (similarity > 0) + QToolTip::add (this, i18n ("Color similarity: %1%").arg (qRound (similarity * 100))); + else + QToolTip::add (this, i18n ("Color similarity: Exact")); + + m_processedColorSimilarity = kpColor::processSimilarity (colorSimilarity ()); + + m_mainWindow->configSetColorSimilarity (colorSimilarity ()); + + emit colorSimilarityChanged (colorSimilarity (), m_processedColorSimilarity); +} + +// public +double kpColorSimilarityToolBarItem::oldColorSimilarity () const +{ + return m_oldColorSimilarity; +} + + +// private virtual [base QWidget] +void kpColorSimilarityToolBarItem::mousePressEvent (QMouseEvent * /*e*/) +{ + // eat right-mouse click to prevent it from getting to the toolbar +} + +// private virtual [base QWidget] +void kpColorSimilarityToolBarItem::mouseDoubleClickEvent (QMouseEvent * /*e*/) +{ + kpColorSimilarityDialog dialog (m_mainWindow, this); + dialog.setColorSimilarity (colorSimilarity ()); + if (dialog.exec ()) + { + setColorSimilarity (dialog.colorSimilarity ()); + } +} + + +/* + * kpColorToolBar + */ + +kpColorToolBar::kpColorToolBar (const QString &label, kpMainWindow *mainWindow, const char *name) + : KToolBar (mainWindow, name), + m_mainWindow (mainWindow) +{ + setText (label); + + + QWidget *base = new QWidget (this); + m_boxLayout = new QBoxLayout (base, QBoxLayout::LeftToRight, + 5/*margin*/, (10 * 4)/*spacing*/); + + m_dualColorButton = new kpDualColorButton (mainWindow, base); + m_dualColorButton->setSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed); + connect (m_dualColorButton, SIGNAL (colorsSwapped (const kpColor &, const kpColor &)), + this, SIGNAL (colorsSwapped (const kpColor &, const kpColor &))); + connect (m_dualColorButton, SIGNAL (foregroundColorChanged (const kpColor &)), + this, SIGNAL (foregroundColorChanged (const kpColor &))); + connect (m_dualColorButton, SIGNAL (backgroundColorChanged (const kpColor &)), + this, SIGNAL (backgroundColorChanged (const kpColor &))); + m_boxLayout->addWidget (m_dualColorButton, 0/*stretch*/); + + m_colorPalette = new kpColorPalette (base); + connect (m_colorPalette, SIGNAL (foregroundColorChanged (const kpColor &)), + m_dualColorButton, SLOT (setForegroundColor (const kpColor &))); + connect (m_colorPalette, SIGNAL (backgroundColorChanged (const kpColor &)), + m_dualColorButton, SLOT (setBackgroundColor (const kpColor &))); + m_boxLayout->addWidget (m_colorPalette, 0/*stretch*/); + + m_colorSimilarityToolBarItem = new kpColorSimilarityToolBarItem (mainWindow, base); + m_colorSimilarityToolBarItem->setSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed); + connect (m_colorSimilarityToolBarItem, SIGNAL (colorSimilarityChanged (double, int)), + this, SIGNAL (colorSimilarityChanged (double, int))); + m_boxLayout->addWidget (m_colorSimilarityToolBarItem, 0/*stretch*/); + + // HACK: couldn't get QSpacerItem to work + QWidget *fakeSpacer = new QWidget (base); + m_boxLayout->addWidget (fakeSpacer, 1/*stretch*/); + + m_lastDockedOrientationSet = false; + setOrientation (orientation ()); + + KToolBar::insertWidget (0, base->width (), base); +} + +// virtual +void kpColorToolBar::setOrientation (Qt::Orientation o) +{ + // (QDockWindow::undock() calls us) + bool isOutsideDock = (place () == QDockWindow::OutsideDock); + + if (!m_lastDockedOrientationSet || !isOutsideDock) + { + m_lastDockedOrientation = o; + m_lastDockedOrientationSet = true; + } + + if (isOutsideDock) + { + //kdDebug () << "\toutside dock, forcing orientation to last" << endl; + o = m_lastDockedOrientation; + } + + if (o == Qt::Horizontal) + { + m_boxLayout->setDirection (QBoxLayout::LeftToRight); + } + else + { + m_boxLayout->setDirection (QBoxLayout::TopToBottom); + } + + m_colorPalette->setOrientation (o); + + KToolBar::setOrientation (o); +} + +kpColorToolBar::~kpColorToolBar () +{ +} + +kpColor kpColorToolBar::color (int which) const +{ + if (which < 0 || which > 1) + { + kdWarning () << "kpColorToolBar::color (" << which + << ") - out of range" << endl; + which = 0; + } + + return m_dualColorButton->color (which); +} + +void kpColorToolBar::setColor (int which, const kpColor &color) +{ + if (which < 0 || which > 1) + { + kdWarning () << "kpColorToolBar::setColor (" << which + << ") - out of range" << endl; + which = 0; + } + + m_dualColorButton->setColor (which, color); +} + +kpColor kpColorToolBar::foregroundColor () const +{ + return m_dualColorButton->foregroundColor (); +} + +void kpColorToolBar::setForegroundColor (const kpColor &color) +{ + m_dualColorButton->setForegroundColor (color); +} + +kpColor kpColorToolBar::backgroundColor () const +{ + return m_dualColorButton->backgroundColor (); +} + +void kpColorToolBar::setBackgroundColor (const kpColor &color) +{ + m_dualColorButton->setBackgroundColor (color); +} + + +kpColor kpColorToolBar::oldForegroundColor () const +{ + return m_dualColorButton->oldForegroundColor (); +} + +kpColor kpColorToolBar::oldBackgroundColor () const +{ + return m_dualColorButton->oldBackgroundColor (); +} + +double kpColorToolBar::oldColorSimilarity () const +{ + return m_colorSimilarityToolBarItem->oldColorSimilarity (); +} + + +double kpColorToolBar::colorSimilarity () const +{ + return m_colorSimilarityToolBarItem->colorSimilarity (); +} + +void kpColorToolBar::setColorSimilarity (double similarity) +{ + m_colorSimilarityToolBarItem->setColorSimilarity (similarity); +} + +int kpColorToolBar::processedColorSimilarity () const +{ + return m_colorSimilarityToolBarItem->processedColorSimilarity (); +} + + +#include <kpcolortoolbar.moc> diff --git a/kolourpaint/widgets/kpcolortoolbar.h b/kolourpaint/widgets/kpcolortoolbar.h new file mode 100644 index 00000000..b4a77bfb --- /dev/null +++ b/kolourpaint/widgets/kpcolortoolbar.h @@ -0,0 +1,297 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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_toolbar_h__ +#define __kp_color_toolbar_h__ + + +#include <qframe.h> +#include <qwidget.h> + +#include <kcolordialog.h> +#include <ktoolbar.h> + +#include <kpcolor.h> +#include <kpcolorsimilaritycube.h> + + +class QGridLayout; +class KColorButton; + +class kpColorSimilarityCube; +class kpMainWindow; + + +// +// Widget similar to KDualColorButton. +// Main differences: +// - more consistent feel with other KolourPaint widgets +// (esp. kpColorPalette) +// - displays the transparent colour using the special pixmap +// used by kpTransparentColorCell +// - no obscure "current" colour +// +class kpDualColorButton : public QFrame +{ +Q_OBJECT + +public: + kpDualColorButton (kpMainWindow *mainWindow, + QWidget *parent, const char *name = 0); + virtual ~kpDualColorButton (); + + kpColor color (int which) const; + kpColor foregroundColor () const; + kpColor backgroundColor () const; + +public slots: + void setColor (int which, const kpColor &color); + void setForegroundColor (const kpColor &color); + void setBackgroundColor (const kpColor &color); + +signals: + // If you connect to this signal, ignore the following + // foregroundColorChanged() and backgroundColorChanged() signals + void colorsSwapped (const kpColor &newForegroundColor, + const kpColor &newBackgroundColor); + + void foregroundColorChanged (const kpColor &color); + void backgroundColorChanged (const kpColor &color); + +public: + // (only valid in slots connected to foregroundColorChanged()) + kpColor oldForegroundColor () const; + // (only valid in slots connected to backgroundColorChanged()) + kpColor oldBackgroundColor () const; + +public: + virtual QSize sizeHint () const; + +protected: + QRect swapPixmapRect () const; + QRect foregroundBackgroundRect () const; + QRect foregroundRect () const; + QRect backgroundRect () const; + + //virtual void dragEnterEvent (QDragEnterEvent *e); + virtual void dragMoveEvent (QDragMoveEvent *e); + virtual void dropEvent (QDropEvent *e); + + virtual void mousePressEvent (QMouseEvent *e); + virtual void mouseDoubleClickEvent (QMouseEvent *e); + virtual void mouseReleaseEvent (QMouseEvent *e); + + virtual void drawContents (QPainter *p); + + kpMainWindow *m_mainWindow; + kpColor m_color [2]; + kpColor m_oldColor [2]; + QPixmap *m_backBuffer; +}; + + +class kpColorCells : public KColorCells +{ +Q_OBJECT + +public: + kpColorCells (QWidget *parent, + Qt::Orientation o = Qt::Horizontal, + const char *name = 0); + virtual ~kpColorCells (); + + Qt::Orientation orientation () const; + void setOrientation (Qt::Orientation o); + +signals: + void foregroundColorChanged (const QColor &color); + void backgroundColorChanged (const QColor &color); + + // lazy + void foregroundColorChanged (const kpColor &color); + void backgroundColorChanged (const kpColor &color); + +protected: + Qt::Orientation m_orientation; + + virtual void dropEvent (QDropEvent *e); + virtual void paintCell (QPainter *painter, int row, int col); + virtual void mouseReleaseEvent (QMouseEvent *e); + virtual void resizeEvent (QResizeEvent *e); + + int m_mouseButton; + +protected slots: + void slotColorSelected (int cell); + void slotColorDoubleClicked (int cell); +}; + + +class kpTransparentColorCell : public QFrame +{ +Q_OBJECT + +public: + kpTransparentColorCell (QWidget *parent, const char *name = 0); + virtual ~kpTransparentColorCell (); + + virtual QSize sizeHint () const; + +signals: + void transparentColorSelected (int mouseButton); + + // lazy + void foregroundColorChanged (const kpColor &color); + void backgroundColorChanged (const kpColor &color); + +protected: + virtual void mousePressEvent (QMouseEvent *e); + virtual void mouseReleaseEvent (QMouseEvent *e); + + virtual void drawContents (QPainter *p); + + QPixmap m_pixmap; +}; + + +class kpColorPalette : public QWidget +{ +Q_OBJECT + +public: + kpColorPalette (QWidget *parent, + Qt::Orientation o = Qt::Horizontal, + const char *name = 0); + virtual ~kpColorPalette (); + + Qt::Orientation orientation () const; + void setOrientation (Qt::Orientation o); + +signals: + void foregroundColorChanged (const kpColor &color); + void backgroundColorChanged (const kpColor &color); + +protected: + Qt::Orientation m_orientation; + + QBoxLayout *m_boxLayout; + kpTransparentColorCell *m_transparentColorCell; + kpColorCells *m_colorCells; +}; + + +class kpColorSimilarityToolBarItem : public kpColorSimilarityCube +{ +Q_OBJECT + +public: + kpColorSimilarityToolBarItem (kpMainWindow *mainWindow, + QWidget *parent, + const char *name = 0); + virtual ~kpColorSimilarityToolBarItem (); + +public: + int processedColorSimilarity () const; + +public slots: + void setColorSimilarity (double similarity); + +signals: + void colorSimilarityChanged (double similarity, int processedSimilarity); + +public: + // (only valid in slots connected to colorSimilarityChanged()); + double oldColorSimilarity () const; + +private: + virtual void mousePressEvent (QMouseEvent *e); + virtual void mouseDoubleClickEvent (QMouseEvent *e); + +private: + kpMainWindow *m_mainWindow; + + double m_oldColorSimilarity; + int m_processedColorSimilarity; +}; + + +class kpColorToolBar : public KToolBar +{ +Q_OBJECT + +public: + kpColorToolBar (const QString &label, kpMainWindow *mainWindow, const char *name = 0); + virtual ~kpColorToolBar (); + + kpColor color (int which) const; + void setColor (int which, const kpColor &color); + + kpColor foregroundColor () const; + kpColor backgroundColor () const; + + double colorSimilarity () const; + void setColorSimilarity (double similarity); + int processedColorSimilarity () const; + +signals: + // If you connect to this signal, ignore the following + // foregroundColorChanged() and backgroundColorChanged() signals + void colorsSwapped (const kpColor &newForegroundColor, + const kpColor &newBackgroundColor); + + void foregroundColorChanged (const kpColor &color); + void backgroundColorChanged (const kpColor &color); + void colorSimilarityChanged (double similarity, int processedSimilarity); + +public: + // (only valid in slots connected to foregroundColorChanged()) + kpColor oldForegroundColor () const; + // (only valid in slots connected to backgroundColorChanged()) + kpColor oldBackgroundColor () const; + + // (only valid in slots connected to colorSimilarityChanged()) + double oldColorSimilarity () const; + +public slots: + void setForegroundColor (const kpColor &color); + void setBackgroundColor (const kpColor &color); + +private: + kpMainWindow *m_mainWindow; + + Qt::Orientation m_lastDockedOrientation; + bool m_lastDockedOrientationSet; + virtual void setOrientation (Qt::Orientation o); + + QBoxLayout *m_boxLayout; + kpDualColorButton *m_dualColorButton; + kpColorPalette *m_colorPalette; + kpColorSimilarityToolBarItem *m_colorSimilarityToolBarItem; +}; + +#endif // __kp_color_toolbar_h__ diff --git a/kolourpaint/widgets/kpresizesignallinglabel.cpp b/kolourpaint/widgets/kpresizesignallinglabel.cpp new file mode 100644 index 00000000..77d0ad2b --- /dev/null +++ b/kolourpaint/widgets/kpresizesignallinglabel.cpp @@ -0,0 +1,67 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <kpresizesignallinglabel.h> + +#include <kdebug.h> + + +kpResizeSignallingLabel::kpResizeSignallingLabel (const QString &string, + QWidget *parent, + const char *name) + : QLabel (string, parent, name) +{ +} + +kpResizeSignallingLabel::kpResizeSignallingLabel (QWidget *parent, + const char *name) + : QLabel (parent, name) +{ +} + +kpResizeSignallingLabel::~kpResizeSignallingLabel () +{ +} + + +// protected virtual [base QLabel] +void kpResizeSignallingLabel::resizeEvent (QResizeEvent *e) +{ +#if DEBUG_KP_RESIZE_SIGNALLING_LABEL + kdDebug () << "kpResizeSignallingLabel::resizeEvent() newSize=" << e->size () + << " oldSize=" << e->oldSize () << endl; +#endif + QLabel::resizeEvent (e); + + emit resized (); +} + + +#include <kpresizesignallinglabel.moc> diff --git a/kolourpaint/widgets/kpresizesignallinglabel.h b/kolourpaint/widgets/kpresizesignallinglabel.h new file mode 100644 index 00000000..6cd3beba --- /dev/null +++ b/kolourpaint/widgets/kpresizesignallinglabel.h @@ -0,0 +1,52 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 +#define KP_RESIZE_SIGNALLING_LABEL + + +#include <qlabel.h> + + +class kpResizeSignallingLabel : public QLabel +{ +Q_OBJECT + +public: + kpResizeSignallingLabel (const QString &string, QWidget *parent, const char *name = 0); + kpResizeSignallingLabel (QWidget *parent, const char *name = 0); + virtual ~kpResizeSignallingLabel (); + +signals: + void resized (); + +protected: + virtual void resizeEvent (QResizeEvent *e); +}; + + +#endif // KP_RESIZE_SIGNALLING_LABEL diff --git a/kolourpaint/widgets/kpsqueezedtextlabel.cpp b/kolourpaint/widgets/kpsqueezedtextlabel.cpp new file mode 100644 index 00000000..53fd85c9 --- /dev/null +++ b/kolourpaint/widgets/kpsqueezedtextlabel.cpp @@ -0,0 +1,215 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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_SQUEEZED_TEXT_LABEL 0 + + +#include <kpsqueezedtextlabel.h> + +#include <qfont.h> +#include <qfontmetrics.h> +#include <qstring.h> + +#include <kdebug.h> +#include <klocale.h> + + +kpSqueezedTextLabel::kpSqueezedTextLabel (QWidget *parent, const char *name) + : QLabel (parent, name), + m_showEllipsis (true) +{ +} + +kpSqueezedTextLabel::kpSqueezedTextLabel (const QString &text, QWidget *parent, const char *name) + : QLabel (parent, name), + m_showEllipsis (true) +{ + setText (text); +} + + +// public virtual +QSize kpSqueezedTextLabel::minimumSizeHint () const +{ +#if DEBUG_KP_SQUEEZED_TEXT_LABEL && 1 + kdDebug () << "kpSqueezedTextLabel::minimumSizeHint() qLabel prefers" + << QLabel::minimumSizeHint () << endl; +#endif + return QSize (-1/*no minimum width*/, QLabel::minimumHeight ()); +} + + +// public +QString kpSqueezedTextLabel::fullText () const +{ + return m_fullText; +} + + +// public +bool kpSqueezedTextLabel::showEllipsis () const +{ + return m_showEllipsis; +} + +// public +void kpSqueezedTextLabel::setShowEllipsis (bool yes) +{ + if (m_showEllipsis == yes) + return; + + m_showEllipsis = yes; + + squeezeText (); +} + + +// public slots virtual [base QLabel] +void kpSqueezedTextLabel::setText (const QString &text) +{ + m_fullText = text; + squeezeText (); +} + + +// protected virtual [base QWidget] +void kpSqueezedTextLabel::resizeEvent (QResizeEvent *e) +{ +#if DEBUG_KP_SQUEEZED_TEXT_LABEL && 1 + kdDebug () << "kpSqueezedTextLabeL::resizeEvent() size=" << e->size () + << " oldSize=" << e->oldSize () + << endl; +#endif + squeezeText (); +} + + +// protected +QString kpSqueezedTextLabel::ellipsisText () const +{ + return m_showEllipsis ? i18n ("...") : QString::null; +} + +// protected +void kpSqueezedTextLabel::squeezeText () +{ +#if DEBUG_KP_SQUEEZED_TEXT_LABEL && 1 + kdDebug () << "kpSqueezedTextLabeL::squeezeText" << endl; +#endif + + QFontMetrics fontMetrics (font ()); + int fullTextWidth = fontMetrics.width (m_fullText); +#if DEBUG_KP_SQUEEZED_TEXT_LABEL && 1 + kdDebug () << "\tfullText=" << m_fullText + << " fullTextWidth=" << fullTextWidth + << " labelWidth=" << width () + << endl; +#endif + + if (fullTextWidth <= width ()) + { + #if DEBUG_KP_SQUEEZED_TEXT_LABEL && 1 + kdDebug () << "\tfullText will fit - display" << endl; + #endif + QLabel::setText (m_fullText); + } + else + { + #if DEBUG_KP_SQUEEZED_TEXT_LABEL && 1 + kdDebug () << "\tfullText won't fit :( - squeeze" << endl; + kdDebug () << "\t\twidth of \"...\"=" + << fontMetrics.width (ellipsisText ()) + << endl; + + #endif + if (fontMetrics.width (ellipsisText ()) > width ()) + { + #if DEBUG_KP_SQUEEZED_TEXT_LABEL && 1 + kdDebug () << "\t\t\tcan't even fit \"...\" - forget it" << endl; + #endif + QLabel::setText (QString::null); + return; + } + + // Binary search our way to fit squeezed text + int numLettersToUseLo = 0; + int numLettersToUseHi = m_fullText.length (); + int numLettersToUse = 0; + + while (numLettersToUseLo <= numLettersToUseHi) + { + int numLettersToUseMid = (numLettersToUseLo + numLettersToUseHi) / 2; + int squeezedWidth = fontMetrics.width (m_fullText.left (numLettersToUseMid) + ellipsisText ()); + #if DEBUG_KP_SQUEEZED_TEXT_LABEL && 1 + kdDebug () << "\tbsearch: lo=" << numLettersToUseLo + << " hi=" << numLettersToUseHi + << " mid=" << numLettersToUseMid + << " acceptable=" << numLettersToUse + << " squeezedWidth=" << squeezedWidth + << endl; + #endif + + if (squeezedWidth == width ()) + { + #if DEBUG_KP_SQUEEZED_TEXT_LABEL && 1 + kdDebug () << "\t\tperfect match!" << endl; + #endif + numLettersToUse = numLettersToUseMid; + break; + } + else if (squeezedWidth < width ()) + { + #if DEBUG_KP_SQUEEZED_TEXT_LABEL && 1 + kdDebug () << "\t\tsmall enough - numLettersToUse=" + << numLettersToUse << endl; + #endif + if (numLettersToUseMid > numLettersToUse) + { + numLettersToUse = numLettersToUseMid; + #if DEBUG_KP_SQUEEZED_TEXT_LABEL && 1 + kdDebug () << "\t\t\tset numLettersToUse=" + << numLettersToUse + << endl; + #endif + } + + numLettersToUseLo = numLettersToUseMid + 1; + } + else + { + #if DEBUG_KP_SQUEEZED_TEXT_LABEL && 1 + kdDebug () << "\t\ttoo big" << endl; + #endif + numLettersToUseHi = numLettersToUseMid - 1; + } + } + + QLabel::setText (m_fullText.left (numLettersToUse) + ellipsisText ()); + } +} + +#include <kpsqueezedtextlabel.moc> diff --git a/kolourpaint/widgets/kpsqueezedtextlabel.h b/kolourpaint/widgets/kpsqueezedtextlabel.h new file mode 100644 index 00000000..57aa7b2f --- /dev/null +++ b/kolourpaint/widgets/kpsqueezedtextlabel.h @@ -0,0 +1,65 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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_squeezed_text_label_h__ +#define __kp_squeezed_text_label_h__ + +#include <qlabel.h> +#include <qstring.h> + + +// KSqueezedTextLabel done properly - squeeze at the end of the string, +// not the middle. +class kpSqueezedTextLabel : public QLabel +{ +Q_OBJECT + +public: + kpSqueezedTextLabel (QWidget *parent, const char *name = 0); + kpSqueezedTextLabel (const QString &text, QWidget *parent, const char *name = 0); + + virtual QSize minimumSizeHint () const; + + // TODO: maybe text() should return the full text? + QString fullText () const; + + bool showEllipsis () const; + void setShowEllipsis (bool yes = true); + +public slots: + virtual void setText (const QString &text); + +protected: + virtual void resizeEvent (QResizeEvent *); + QString ellipsisText () const; + void squeezeText (); + + QString m_fullText; + bool m_showEllipsis; +}; + +#endif // __kp_squeezed_text_label_h__ diff --git a/kolourpaint/widgets/kptooltoolbar.cpp b/kolourpaint/widgets/kptooltoolbar.cpp new file mode 100644 index 00000000..b8d1985c --- /dev/null +++ b/kolourpaint/widgets/kptooltoolbar.cpp @@ -0,0 +1,640 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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_TOOL_BAR 0 + + +#include <kptooltoolbar.h> + +#include <qbuttongroup.h> +#include <qlayout.h> +#include <qdatetime.h> +#include <qtoolbutton.h> +#include <qtooltip.h> +#include <qwidget.h> +#include <qwhatsthis.h> + +#include <kconfig.h> +#include <kdebug.h> +#include <kglobalsettings.h> +#include <kicontheme.h> + +#include <kpdefs.h> +#include <kptool.h> +#include <kptoolaction.h> +#include <kptoolwidgetbrush.h> +#include <kptoolwidgeterasersize.h> +#include <kptoolwidgetfillstyle.h> +#include <kptoolwidgetlinewidth.h> +#include <kptoolwidgetopaqueortransparent.h> +#include <kptoolwidgetspraycansize.h> + + +class kpToolButton : public QToolButton +{ +public: + kpToolButton (kpTool *tool, QWidget *parent) + : QToolButton (parent), + m_tool (tool) + { + } + + virtual ~kpToolButton () + { + } + +protected: + // virtual [base QWidget] + void mouseDoubleClickEvent (QMouseEvent *e) + { + if (e->button () == Qt::LeftButton && m_tool) + m_tool->globalDraw (); + } + + kpTool *m_tool; +}; + + +kpToolToolBar::kpToolToolBar (const QString &label, kpMainWindow *mainWindow, int colsOrRows, const char *name) + : KToolBar ((QWidget *) mainWindow, name, false/*don't use global toolBar settings*/, true/*readConfig*/), + m_vertCols (colsOrRows), + m_buttonGroup (0), + m_baseWidget (0), + m_baseLayout (0), + m_toolLayout (0), + m_previousTool (0), m_currentTool (0), + m_defaultIconSize (0) +{ + setText (label); + + + // With these lines enabled, mousePressEvent's weren't being generated + // when right clicking in empty part of the toolbar (each call affects + // the toolbar in its respective orientation). They don't seem to be + // needed anyway since !isResizeEnabled(). + + //setHorizontallyStretchable (false); + //setVerticallyStretchable (false); + + + m_baseWidget = new QWidget (this); + +#if DEBUG_KP_TOOL_TOOL_BAR + QTime timer; + timer.start (); +#endif + + m_toolWidgets.append (m_toolWidgetBrush = + new kpToolWidgetBrush (m_baseWidget, "Tool Widget Brush")); + m_toolWidgets.append (m_toolWidgetEraserSize = + new kpToolWidgetEraserSize (m_baseWidget, "Tool Widget Eraser Size")); + m_toolWidgets.append (m_toolWidgetFillStyle = + new kpToolWidgetFillStyle (m_baseWidget, "Tool Widget Fill Style")); + m_toolWidgets.append (m_toolWidgetLineWidth = + new kpToolWidgetLineWidth (m_baseWidget, "Tool Widget Line Width")); + m_toolWidgets.append (m_toolWidgetOpaqueOrTransparent = + new kpToolWidgetOpaqueOrTransparent (m_baseWidget, "Tool Widget Opaque/Transparent")); + m_toolWidgets.append (m_toolWidgetSpraycanSize = + new kpToolWidgetSpraycanSize (m_baseWidget, "Tool Widget Spraycan Size")); + +#if DEBUG_KP_TOOL_TOOL_BAR + kdDebug () << "kpToolToolBar::<ctor> create tool widgets msec=" + << timer.restart () << endl; +#endif + + for (QValueVector <kpToolWidgetBase *>::const_iterator it = m_toolWidgets.begin (); + it != m_toolWidgets.end (); + it++) + { + connect (*it, SIGNAL (optionSelected (int, int)), + this, SIGNAL (toolWidgetOptionSelected ())); + } + +#if DEBUG_KP_TOOL_TOOL_BAR + kdDebug () << "kpToolToolBar::<ctor> connect widgets msec=" + << timer.restart () << endl; +#endif + + m_lastDockedOrientationSet = false; + setOrientation (orientation ()); + +#if DEBUG_KP_TOOL_TOOL_BAR + kdDebug () << "kpToolToolBar::<ctor> layout tool widgets msec=" + << timer.elapsed () << endl; +#endif + + m_buttonGroup = new QButtonGroup (); // invisible + m_buttonGroup->setExclusive (true); + + connect (m_buttonGroup, SIGNAL (clicked (int)), SLOT (slotToolButtonClicked ())); + + hideAllToolWidgets (); +} + +kpToolToolBar::~kpToolToolBar () +{ + unregisterAllTools (); + delete m_buttonGroup; +} + + +// private +int kpToolToolBar::defaultIconSize () +{ + // Cached? + if (m_defaultIconSize > 0) + return m_defaultIconSize; + +#if DEBUG_KP_TOOL_TOOL_BAR + kdDebug () << "kpToolToolBar::defaultIconSize()" << endl; +#endif + + + KConfigGroupSaver cfgGroupSaver (KGlobal::config (), + kpSettingsGroupTools); + KConfigBase *cfg = cfgGroupSaver.config (); + + if (cfg->hasKey (kpSettingToolBoxIconSize)) + { + m_defaultIconSize = cfg->readNumEntry (kpSettingToolBoxIconSize); + #if DEBUG_KP_TOOL_TOOL_BAR + kdDebug () << "\tread: " << m_defaultIconSize << endl; + #endif + } + else + { + m_defaultIconSize = -1; +#if DEBUG_KP_TOOL_TOOL_BAR + kdDebug () << "\tfirst time - writing default: " << m_defaultIconSize << endl; +#endif + cfg->writeEntry (kpSettingToolBoxIconSize, m_defaultIconSize); + cfg->sync (); + } + + + if (m_defaultIconSize <= 0) + { + // Adapt according to screen geometry + const QRect desktopSize = KGlobalSettings::desktopGeometry (this); + #if DEBUG_KP_TOOL_TOOL_BAR + kdDebug () << "\tadapting to screen size=" << desktopSize << endl; + #endif + + if (desktopSize.width () >= 1024 && desktopSize.height () >= 768) + m_defaultIconSize = KIcon::SizeSmallMedium/*22x22*/; + else + m_defaultIconSize = KIcon::SizeSmall/*16x16*/; + } + + +#if DEBUG_KP_TOOL_TOOL_BAR + kdDebug () << "\treturning " << m_defaultIconSize << endl; +#endif + return m_defaultIconSize; +} + +// public +void kpToolToolBar::registerTool (kpTool *tool) +{ + for (QValueVector <kpButtonToolPair>::const_iterator it = m_buttonToolPairs.begin (); + it != m_buttonToolPairs.end (); + it++) + { + if ((*it).m_tool == tool) + return; + } + int num = m_buttonToolPairs.count (); + + QToolButton *b = new kpToolButton (tool, m_baseWidget); + b->setAutoRaise (true); + b->setUsesBigPixmap (false); + b->setUsesTextLabel (false); + b->setToggleButton (true); + + b->setText (tool->text ()); + b->setIconSet (tool->iconSet (defaultIconSize ())); + QToolTip::add (b, tool->toolTip ()); + QWhatsThis::add (b, tool->description ()); + + m_buttonGroup->insert (b); + addButton (b, orientation (), num); + + m_buttonToolPairs.append (kpButtonToolPair (b, tool)); + + + connect (tool, SIGNAL (actionActivated ()), + this, SLOT (slotToolActionActivated ())); + connect (tool, SIGNAL (actionToolTipChanged (const QString &)), + this, SLOT (slotToolActionToolTipChanged ())); +} + +// public +void kpToolToolBar::unregisterTool (kpTool *tool) +{ + for (QValueVector <kpButtonToolPair>::iterator it = m_buttonToolPairs.begin (); + it != m_buttonToolPairs.end (); + it++) + { + if ((*it).m_tool == tool) + { + delete ((*it).m_button); + m_buttonToolPairs.erase (it); + + disconnect (tool, SIGNAL (actionActivated ()), + this, SLOT (slotToolActionActivated ())); + disconnect (tool, SIGNAL (actionToolTipChanged (const QString &)), + this, SLOT (slotToolActionToolTipChanged ())); + break; + } + } +} + +// public +void kpToolToolBar::unregisterAllTools () +{ + for (QValueVector <kpButtonToolPair>::iterator it = m_buttonToolPairs.begin (); + it != m_buttonToolPairs.end (); + it++) + { + delete ((*it).m_button); + } + + m_buttonToolPairs.clear (); +} + + +// public +kpTool *kpToolToolBar::tool () const +{ + return m_currentTool; +} + +// public +void kpToolToolBar::selectTool (const kpTool *tool, bool reselectIfSameTool) +{ +#if DEBUG_KP_TOOL_TOOL_BAR + kdDebug () << "kpToolToolBar::selectTool (tool=" << tool + << ") currentTool=" << m_currentTool + << endl; +#endif + + if (!reselectIfSameTool && tool == m_currentTool) + return; + + if (tool) + { + for (QValueVector <kpButtonToolPair>::iterator it = m_buttonToolPairs.begin (); + it != m_buttonToolPairs.end (); + it++) + { + if ((*it).m_tool == tool) + { + m_buttonGroup->setButton (m_buttonGroup->id ((*it).m_button)); + slotToolButtonClicked (); + break; + } + } + } + else + { + QButton *b = m_buttonGroup->selected (); + #if DEBUG_KP_TOOL_TOOL_BAR + kdDebug () << "\twant to select no tool - button selected=" << b << endl; + #endif + if (b) + { + b->toggle (); + slotToolButtonClicked (); + } + } +} + + +// public +kpTool *kpToolToolBar::previousTool () const +{ + return m_previousTool; +} + +// public +void kpToolToolBar::selectPreviousTool () +{ + selectTool (m_previousTool); +} + + +// public +void kpToolToolBar::hideAllToolWidgets () +{ + for (QValueVector <kpToolWidgetBase *>::const_iterator it = m_toolWidgets.begin (); + it != m_toolWidgets.end (); + it++) + { + (*it)->hide (); + } +} + +// public +int kpToolToolBar::numShownToolWidgets () const +{ +#if DEBUG_KP_TOOL_TOOL_BAR + kdDebug () << "kpToolToolBar::numShownToolWidgets()" << endl; +#endif + + int ret = 0; + + for (QValueVector <kpToolWidgetBase *>::const_iterator it = m_toolWidgets.begin (); + it != m_toolWidgets.end (); + it++) + { + #if DEBUG_KP_TOOL_TOOL_BAR + kdDebug () << "\t" << (*it)->name () + << " isShown=" << (*it)->isShown () + << endl; + #endif + if ((*it)->isShown ()) + ret++; + } + + return ret; +} + +// public +kpToolWidgetBase *kpToolToolBar::shownToolWidget (int which) const +{ + int uptoVisibleWidget = 0; + + for (QValueVector <kpToolWidgetBase *>::const_iterator it = m_toolWidgets.begin (); + it != m_toolWidgets.end (); + it++) + { + if ((*it)->isShown ()) + { + if (which == uptoVisibleWidget) + return *it; + + uptoVisibleWidget++; + } + } + + return 0; +} + + +// public +bool kpToolToolBar::toolsSingleKeyTriggersEnabled () const +{ + for (QValueVector <kpButtonToolPair>::const_iterator it = m_buttonToolPairs.begin (); + it != m_buttonToolPairs.end (); + it++) + { + if (!(*it).m_tool->singleKeyTriggersEnabled ()) + return false; + } + + return true; +} + +// public +void kpToolToolBar::enableToolsSingleKeyTriggers (bool enable) +{ +#if DEBUG_KP_TOOL_TOOL_BAR + kdDebug () << "kpToolToolBar::enableToolsSingleKeyTriggers(" << enable << ")" << endl; +#endif + + for (QValueVector <kpButtonToolPair>::const_iterator it = m_buttonToolPairs.begin (); + it != m_buttonToolPairs.end (); + it++) + { + (*it).m_tool->enableSingleKeyTriggers (enable); + } +} + + +// private slot +void kpToolToolBar::slotToolButtonClicked () +{ + QButton *b = m_buttonGroup->selected (); + +#if DEBUG_KP_TOOL_TOOL_BAR + kdDebug () << "kpToolToolBar::slotToolButtonClicked() button=" << b << endl; +#endif + + kpTool *tool = 0; + for (QValueVector <kpButtonToolPair>::iterator it = m_buttonToolPairs.begin (); + it != m_buttonToolPairs.end (); + it++) + { + if ((*it).m_button == b) + { + tool = (*it).m_tool; + break; + } + } + +#if DEBUG_KP_TOOL_TOOL_BAR + kdDebug () << "\ttool=" << tool + << " currentTool=" << m_currentTool + << endl; +#endif + + if (tool == m_currentTool) + { + if (m_currentTool) + m_currentTool->reselect (); + + return; + } + + if (m_currentTool) + m_currentTool->endInternal (); + + m_previousTool = m_currentTool; + m_currentTool = tool; + + if (m_currentTool) + { + kpToolAction *action = m_currentTool->action (); + if (action) + { + action->setChecked (true); + } + + m_currentTool->beginInternal (); + } + + emit sigToolSelected (m_currentTool); +} + + +#define CONST_KP_TOOL_SENDER() (dynamic_cast <const kpTool *> (sender ())) + +// private slot +void kpToolToolBar::slotToolActionActivated () +{ + const kpTool *tool = CONST_KP_TOOL_SENDER (); + +#if DEBUG_KP_TOOL_TOOL_BAR + kdDebug () << "kpToolToolBar::slotToolActionActivated() tool=" + << (tool ? tool->name () : "null") + << endl; +#endif + + if (m_currentTool) + { + // If the user clicks on the same KToggleAction, it unchecks it + // - this is inconsistent with the Tool Box so always make sure it's + // checked. + kpToolAction *action = m_currentTool->action (); + if (action) + { + action->setChecked (true); + } + } + + selectTool (tool, true/*reselect if same tool*/); +} + +// private slot +void kpToolToolBar::slotToolActionToolTipChanged () +{ + const kpTool *tool = CONST_KP_TOOL_SENDER (); + +#if DEBUG_KP_TOOL_TOOL_BAR + kdDebug () << "kpToolToolBar::slotToolActionToolTipChanged() tool=" + << (tool ? tool->name () : "null") + << endl; +#endif + + if (!tool) + return; + + for (QValueVector <kpButtonToolPair>::const_iterator it = m_buttonToolPairs.begin (); + it != m_buttonToolPairs.end (); + it++) + { + if (tool == (*it).m_tool) + { + QToolTip::add ((*it).m_button, tool->toolTip ()); + return; + } + } +} + + +// public slot virtual [base QDockWindow] +void kpToolToolBar::setOrientation (Qt::Orientation o) +{ +#if DEBUG_KP_TOOL_TOOL_BAR + kdDebug () << "kpToolToolBar::setOrientation(" + << (o == Qt::Vertical ? "vertical" : "horizontal") + << ") called!" << endl; +#endif + + // (QDockWindow::undock() calls us) + bool isOutsideDock = (place () == QDockWindow::OutsideDock); + + if (!m_lastDockedOrientationSet || !isOutsideDock) + { + m_lastDockedOrientation = o; + m_lastDockedOrientationSet = true; + } + + if (isOutsideDock) + { + #if DEBUG_KP_TOOL_TOOL_BAR + kdDebug () << "\toutside dock, forcing orientation to last" << endl; + #endif + o = m_lastDockedOrientation; + } + + delete m_toolLayout; + delete m_baseLayout; + if (o == Qt::Vertical) + { + m_baseLayout = new QBoxLayout (m_baseWidget, QBoxLayout::TopToBottom, + 5/*margin*/, + 10/*spacing*/); + m_toolLayout = new QGridLayout (m_baseLayout, + 5/*arbitrary rows since toolBar auto-expands*/, + m_vertCols, + 0/*margin*/, + 0/*spacing*/); + } + else // if (o == Qt::Horizontal) + { + m_baseLayout = new QBoxLayout (m_baseWidget, QBoxLayout::LeftToRight, + 5/*margin*/, + 10/*spacing*/); + m_toolLayout = new QGridLayout (m_baseLayout, + m_vertCols/*rows in this case, since horiz*/, + 5/*arbitrary cols since toolBar auto-expands*/, + 0/*margin*/, + 0/*spacing*/); + } + + int num = 0; + + for (QValueVector <kpButtonToolPair>::iterator it = m_buttonToolPairs.begin (); + it != m_buttonToolPairs.end (); + it++) + { + addButton ((*it).m_button, o, num); + num++; + } + + for (QValueVector <kpToolWidgetBase *>::const_iterator it = m_toolWidgets.begin (); + it != m_toolWidgets.end (); + it++) + { + if (*it) + { + m_baseLayout->addWidget (*it, + 0/*stretch*/, + o == Qt::Vertical ? Qt::AlignHCenter : Qt::AlignVCenter); + } + } + + KToolBar::setOrientation (o); +} + +// private +void kpToolToolBar::addButton (QButton *button, Qt::Orientation o, int num) +{ + if (o == Qt::Vertical) + m_toolLayout->addWidget (button, num / m_vertCols, num % m_vertCols); + else + { + // maps Left (o = vertical) to Bottom (o = horizontal) + int row = (m_vertCols - 1) - (num % m_vertCols); + m_toolLayout->addWidget (button, row, num / m_vertCols); + } +} + + +#include <kptooltoolbar.moc> diff --git a/kolourpaint/widgets/kptooltoolbar.h b/kolourpaint/widgets/kptooltoolbar.h new file mode 100644 index 00000000..c3a7d1b7 --- /dev/null +++ b/kolourpaint/widgets/kptooltoolbar.h @@ -0,0 +1,155 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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_tool_bar_h__ +#define __kp_tool_tool_bar_h__ + +#include <qvaluevector.h> + +#include <ktoolbar.h> + + +class QBoxLayout; +class QButton; +class QButtonGroup; +class QWidget; +class QGridLayout; + +class kpMainWindow; +class kpTool; + +class kpToolWidgetBase; +class kpToolWidgetBrush; +class kpToolWidgetEraserSize; +class kpToolWidgetFillStyle; +class kpToolWidgetLineWidth; +class kpToolWidgetOpaqueOrTransparent; +class kpToolWidgetSpraycanSize; + +class kpToolToolBar : public KToolBar +{ +Q_OBJECT + +public: + kpToolToolBar (const QString &label, kpMainWindow *mainWindow, int colsOrRows = 2, const char *name = 0); + virtual ~kpToolToolBar (); + +private: + int defaultIconSize (); +public: + void registerTool (kpTool *tool); + void unregisterTool (kpTool *tool); + void unregisterAllTools (); + + kpTool *tool () const; + void selectTool (const kpTool *tool, bool reselectIfSameTool = false); + + kpTool *previousTool () const; + void selectPreviousTool (); + + void hideAllToolWidgets (); + // could this be cleaner (the tools have to access them individually somehow)? + kpToolWidgetBrush *toolWidgetBrush () const { return m_toolWidgetBrush; } + kpToolWidgetEraserSize *toolWidgetEraserSize () const { return m_toolWidgetEraserSize; } + kpToolWidgetFillStyle *toolWidgetFillStyle () const { return m_toolWidgetFillStyle; } + kpToolWidgetLineWidth *toolWidgetLineWidth () const { return m_toolWidgetLineWidth; } + kpToolWidgetOpaqueOrTransparent *toolWidgetOpaqueOrTransparent () const { return m_toolWidgetOpaqueOrTransparent; } + kpToolWidgetSpraycanSize *toolWidgetSpraycanSize () const { return m_toolWidgetSpraycanSize; } + +public: + int numShownToolWidgets () const; + kpToolWidgetBase *shownToolWidget (int which) const; + + bool toolsSingleKeyTriggersEnabled () const; + void enableToolsSingleKeyTriggers (bool enable); + +signals: + void sigToolSelected (kpTool *tool); // tool may be 0 + void toolWidgetOptionSelected (); + +private slots: + void slotToolButtonClicked (); + + void slotToolActionActivated (); + void slotToolActionToolTipChanged (); + +public slots: + virtual void setOrientation (Qt::Orientation o); + +private: + void addButton (QButton *button, Qt::Orientation o, int num); + + Qt::Orientation m_lastDockedOrientation; + bool m_lastDockedOrientationSet; + int m_vertCols; + + QButtonGroup *m_buttonGroup; + QWidget *m_baseWidget; + QBoxLayout *m_baseLayout; + QGridLayout *m_toolLayout; + + kpToolWidgetBrush *m_toolWidgetBrush; + kpToolWidgetEraserSize *m_toolWidgetEraserSize; + kpToolWidgetFillStyle *m_toolWidgetFillStyle; + kpToolWidgetLineWidth *m_toolWidgetLineWidth; + kpToolWidgetOpaqueOrTransparent *m_toolWidgetOpaqueOrTransparent; + kpToolWidgetSpraycanSize *m_toolWidgetSpraycanSize; + + QValueVector <kpToolWidgetBase *> m_toolWidgets; + +private: + struct kpButtonToolPair + { + kpButtonToolPair (QButton *button, kpTool *tool) + : m_button (button), m_tool (tool) + { + } + + kpButtonToolPair () + : m_button (0), m_tool (0) + { + } + + QButton *m_button; + kpTool *m_tool; + }; + + QValueVector <kpButtonToolPair> m_buttonToolPairs; + + kpTool *m_previousTool, *m_currentTool; + + int m_defaultIconSize; + +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 kpToolToolBarPrivate *d; +}; + +#endif // __kp_tool_tool_bar_h__ diff --git a/kolourpaint/widgets/kptoolwidgetbase.cpp b/kolourpaint/widgets/kptoolwidgetbase.cpp new file mode 100644 index 00000000..a0042dbc --- /dev/null +++ b/kolourpaint/widgets/kptoolwidgetbase.cpp @@ -0,0 +1,608 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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_WIDGET_BASE 0 + + +#include <kptoolwidgetbase.h> + +#include <qbitmap.h> +#include <qcolor.h> +#include <qimage.h> +#include <qpainter.h> +#include <qtooltip.h> + +#include <kapplication.h> +#include <kconfig.h> +#include <kdebug.h> + +#include <kpdefs.h> +#include <kpeffectinvert.h> + + +kpToolWidgetBase::kpToolWidgetBase (QWidget *parent, const char *name) + : QFrame (parent, name), + m_invertSelectedPixmap (true), + m_selectedRow (-1), m_selectedCol (-1) +{ + if (!name) + kdError () << "kpToolWidgetBase::kpToolWidgetBase() without name" << endl; + + setFrameStyle (QFrame::Panel | QFrame::Sunken); + setFixedSize (44, 66); +} + +kpToolWidgetBase::~kpToolWidgetBase () +{ +} + + +// public +void kpToolWidgetBase::addOption (const QPixmap &pixmap, const QString &toolTip) +{ + if (m_pixmaps.isEmpty ()) + startNewOptionRow (); + + m_pixmaps.last ().append (pixmap); + m_pixmapRects.last ().append (QRect ()); + m_toolTips.last ().append (toolTip); +} + +// public +void kpToolWidgetBase::startNewOptionRow () +{ + m_pixmaps.resize (m_pixmaps.count () + 1); + m_pixmapRects.resize (m_pixmapRects.count () + 1); + m_toolTips.resize (m_toolTips.count () + 1); +} + +// public +void kpToolWidgetBase::finishConstruction (int fallBackRow, int fallBackCol) +{ +#if DEBUG_KP_TOOL_WIDGET_BASE + kdDebug () << "kpToolWidgetBase(" << name () + << ")::kpToolWidgetBase(fallBack:row=" << fallBackRow + << ",col=" << fallBackCol + << ")" + << endl; +#endif + + relayoutOptions (); + + const QPair <int, int> rowColPair = defaultSelectedRowAndCol (); + if (!setSelected (rowColPair.first, rowColPair.second, false/*don't save*/)) + { + if (!setSelected (fallBackRow, fallBackCol)) + { + if (!setSelected (0, 0)) + { + kdError () << "kpToolWidgetBase::finishConstruction() " + "can't even fall back to setSelected(row=0,col=0)" << endl; + } + } + } +} + + +// private +QValueVector <int> kpToolWidgetBase::spreadOutElements (const QValueVector <int> &sizes, int max) +{ + if (sizes.count () == 0) + return QValueVector <int> (); + else if (sizes.count () == 1) + return QValueVector <int> (1, sizes.first () > max ? 0 : 1/*margin*/); + + QValueVector <int> retOffsets (sizes.count ()); + + int totalSize = 0; + for (int i = 0; i < (int) sizes.count (); i++) + totalSize += sizes [i]; + + int margin = 1; + + // if don't fit with margin, then just return elements + // packed right next to each other + if (totalSize + margin * 2 > max) + { + retOffsets [0] = 0; + for (int i = 1; i < (int) sizes.count (); i++) + retOffsets [i] = retOffsets [i - 1] + sizes [i - 1]; + + return retOffsets; + } + + int maxLeftOver = max - (totalSize + margin * 2); + + int startCompensating = -1; + int numCompensate = 0; + + int spacing = 0; + + spacing = maxLeftOver / (sizes.count () - 1); + if (spacing * int (sizes.count () - 1) < maxLeftOver) + { + numCompensate = maxLeftOver - spacing * (sizes.count () - 1); + startCompensating = ((sizes.count () - 1) - numCompensate) / 2; + } + + retOffsets [0] = margin; + for (int i = 1; i < (int) sizes.count (); i++) + { + retOffsets [i] += retOffsets [i - 1] + + sizes [i - 1] + + spacing + + ((numCompensate && + i >= startCompensating && + i < startCompensating + numCompensate) ? 1 : 0); + } + + return retOffsets; +} + + +// public +QPair <int, int> kpToolWidgetBase::defaultSelectedRowAndCol () const +{ + int row = -1, col = -1; + + if (name ()) + { + KConfigGroupSaver cfgGroupSaver (kapp->config (), kpSettingsGroupTools); + KConfigBase *cfg = cfgGroupSaver.config (); + + QString nameString = QString::fromLatin1 (name ()); + + row = cfg->readNumEntry (nameString + QString::fromLatin1 (" Row"), -1); + col = cfg->readNumEntry (nameString + QString::fromLatin1 (" Col"), -1); + } + +#if DEBUG_KP_TOOL_WIDGET_BASE + kdDebug () << "kpToolWidgetBase(" << name () + << ")::defaultSelectedRowAndCol() returning row=" << row + << " col=" << col + << endl; +#endif + + return qMakePair (row, col); +} + +// public +int kpToolWidgetBase::defaultSelectedRow () const +{ + return defaultSelectedRowAndCol ().first; +} + +// public +int kpToolWidgetBase::defaultSelectedCol () const +{ + return defaultSelectedRowAndCol ().second; +} + +// public +void kpToolWidgetBase::saveSelectedAsDefault () const +{ +#if DEBUG_KP_TOOL_WIDGET_BASE + kdDebug () << "kpToolWidgetBase(" << name () + << ")::saveSelectedAsDefault() row=" << m_selectedRow + << " col=" << m_selectedCol << endl; +#endif + + if (!name ()) + return; + + KConfigGroupSaver cfgGroupSaver (kapp->config (), kpSettingsGroupTools); + KConfigBase *cfg = cfgGroupSaver.config (); + + QString nameString = QString::fromLatin1 (name ()); + cfg->writeEntry (nameString + QString::fromLatin1 (" Row"), m_selectedRow); + cfg->writeEntry (nameString + QString::fromLatin1 (" Col"), m_selectedCol); + cfg->sync (); +} + + +// public +void kpToolWidgetBase::relayoutOptions () +{ +#if DEBUG_KP_TOOL_WIDGET_BASE + kdDebug () << "kpToolWidgetBase::relayoutOptions()" << endl; +#endif + + while (!m_pixmaps.isEmpty () && m_pixmaps.last ().count () == 0) + { + #if DEBUG_KP_TOOL_WIDGET_BASE + kdDebug () << "\tkilling #" << m_pixmaps.count () - 1 << endl; + #endif + m_pixmaps.resize (m_pixmaps.count () - 1); + m_pixmapRects.resize (m_pixmapRects.count () - 1); + m_toolTips.resize (m_toolTips.count () - 1); + } + + if (m_pixmaps.isEmpty ()) + return; + +#if DEBUG_KP_TOOL_WIDGET_BASE + kdDebug () << "\tsurvived killing of empty rows" << endl; + kdDebug () << "\tfinding heights of rows:" << endl; +#endif + + QValueVector <int> maxHeightOfRow (m_pixmaps.count ()); + + for (int r = 0; r < (int) m_pixmaps.count (); r++) + { + for (int c = 0; c < (int) m_pixmaps [r].count (); c++) + { + if (c == 0 || m_pixmaps [r][c].height () > maxHeightOfRow [r]) + maxHeightOfRow [r] = m_pixmaps [r][c].height (); + } + #if DEBUG_KP_TOOL_WIDGET_BASE + kdDebug () << "\t\t" << r << ": " << maxHeightOfRow [r] << endl; + #endif + } + + QValueVector <int> rowYOffset = spreadOutElements (maxHeightOfRow, height ()); +#if DEBUG_KP_TOOL_WIDGET_BASE + kdDebug () << "\tspread out offsets of rows:" << endl; + for (int r = 0; r < (int) rowYOffset.count (); r++) + kdDebug () << "\t\t" << r << ": " << rowYOffset [r] << endl; +#endif + + for (int r = 0; r < (int) m_pixmaps.count (); r++) + { + #if DEBUG_KP_TOOL_WIDGET_BASE + kdDebug () << "\tlaying out row " << r << ":" << endl; + #endif + + QValueVector <int> widths (m_pixmaps [r].count ()); + for (int c = 0; c < (int) m_pixmaps [r].count (); c++) + widths [c] = m_pixmaps [r][c].width (); + #if DEBUG_KP_TOOL_WIDGET_BASE + kdDebug () << "\t\twidths of cols:" << endl; + for (int c = 0; c < (int) m_pixmaps [r].count (); c++) + kdDebug () << "\t\t\t" << c << ": " << widths [c] << endl; + #endif + + QValueVector <int> colXOffset = spreadOutElements (widths, width ()); + #if DEBUG_KP_TOOL_WIDGET_BASE + kdDebug () << "\t\tspread out offsets of cols:" << endl; + for (int c = 0; c < (int) colXOffset.count (); c++) + kdDebug () << "\t\t\t" << c << ": " << colXOffset [c] << endl; + #endif + + for (int c = 0; c < (int) colXOffset.count (); c++) + { + int x = colXOffset [c]; + int y = rowYOffset [r]; + int w, h; + + if (c == (int) colXOffset.count () - 1) + { + if (x + m_pixmaps [r][c].width () >= width ()) + w = m_pixmaps [r][c].width (); + else + w = width () - 1 - x; + } + else + w = colXOffset [c + 1] - x; + + if (r == (int) m_pixmaps.count () - 1) + { + if (y + m_pixmaps [r][c].height () >= height ()) + h = m_pixmaps [r][c].height (); + else + h = height () - 1 - y; + } + else + h = rowYOffset [r + 1] - y; + + m_pixmapRects [r][c] = QRect (x, y, w, h); + + if (!m_toolTips [r][c].isEmpty ()) + QToolTip::add (this, m_pixmapRects [r][c], m_toolTips [r][c]); + } + } + + update (); +} + + +// public +int kpToolWidgetBase::selectedRow () const +{ + return m_selectedRow; +} + +// public +int kpToolWidgetBase::selectedCol () const +{ + return m_selectedCol; +} + +// public +int kpToolWidgetBase::selected () const +{ + if (m_selectedRow < 0 || + m_selectedRow >= (int) m_pixmaps.count () || + m_selectedCol < 0) + { + return -1; + } + + int upto = 0; + for (int y = 0; y < m_selectedRow; y++) + upto += m_pixmaps [y].count (); + + if (m_selectedCol >= (int) m_pixmaps [m_selectedRow].count ()) + return -1; + + upto += m_selectedCol; + + return upto; +} + + +// public +bool kpToolWidgetBase::hasPreviousOption (int *row, int *col) const +{ +#if DEBUG_KP_TOOL_WIDGET_BASE + kdDebug () << "kpToolWidgetBase" << name () + << "::hasPreviousOption() current row=" << m_selectedRow + << " col=" << m_selectedCol + << endl; +#endif + if (row) + *row = -1; + if (col) + *col = -1; + + + if (m_selectedRow < 0 || m_selectedCol < 0) + return false; + + int newRow = m_selectedRow, + newCol = m_selectedCol; + + newCol--; + if (newCol < 0) + { + newRow--; + if (newRow < 0) + return false; + + newCol = m_pixmaps [newRow].count () - 1; + if (newCol < 0) + return false; + } + + + if (row) + *row = newRow; + if (col) + *col = newCol; + + return true; +} + +// public +bool kpToolWidgetBase::hasNextOption (int *row, int *col) const +{ +#if DEBUG_KP_TOOL_WIDGET_BASE + kdDebug () << "kpToolWidgetBase" << name () + << "::hasNextOption() current row=" << m_selectedRow + << " col=" << m_selectedCol + << endl; +#endif + + if (row) + *row = -1; + if (col) + *col = -1; + + + if (m_selectedRow < 0 || m_selectedCol < 0) + return false; + + int newRow = m_selectedRow, + newCol = m_selectedCol; + + newCol++; + if (newCol >= (int) m_pixmaps [newRow].count ()) + { + newRow++; + if (newRow >= (int) m_pixmaps.count ()) + return false; + + newCol = 0; + if (newCol >= (int) m_pixmaps [newRow].count ()) + return false; + } + + + if (row) + *row = newRow; + if (col) + *col = newCol; + + return true; +} + + +// public slot virtual +bool kpToolWidgetBase::setSelected (int row, int col, bool saveAsDefault) +{ +#if DEBUG_KP_TOOL_WIDGET_BASE + kdDebug () << "kpToolWidgetBase::setSelected(row=" << row + << ",col=" << col + << ",saveAsDefault=" << saveAsDefault + << ")" + << endl; +#endif + + if (row < 0 || col < 0 || + row >= (int) m_pixmapRects.count () || col >= (int) m_pixmapRects [row].count ()) + { + #if DEBUG_KP_TOOL_WIDGET_BASE + kdDebug () << "\tout of range" << endl; + #endif + return false; + } + + if (row == m_selectedRow && col == m_selectedCol) + { + #if DEBUG_KP_TOOL_WIDGET_BASE + kdDebug () << "\tNOP" << endl; + #endif + + if (saveAsDefault) + saveSelectedAsDefault (); + + return true; + } + + const int wasSelectedRow = m_selectedRow; + const int wasSelectedCol = m_selectedCol; + + m_selectedRow = row, m_selectedCol = col; + + if (wasSelectedRow >= 0 && wasSelectedCol >= 0) + { + // unhighlight old option + update (m_pixmapRects [wasSelectedRow][wasSelectedCol]); + } + + // highlight new option + update (m_pixmapRects [row][col]); + +#if DEBUG_KP_TOOL_WIDGET_BASE + kdDebug () << "\tOK" << endl; +#endif + + if (saveAsDefault) + saveSelectedAsDefault (); + + emit optionSelected (row, col); + return true; +} + +// public slot +bool kpToolWidgetBase::setSelected (int row, int col) +{ + return setSelected (row, col, true/*set as default*/); +} + + +// public slot +bool kpToolWidgetBase::selectPreviousOption () +{ + int newRow, newCol; + if (!hasPreviousOption (&newRow, &newCol)) + return false; + + return setSelected (newRow, newCol); +} + +// public slot +bool kpToolWidgetBase::selectNextOption () +{ + int newRow, newCol; + if (!hasNextOption (&newRow, &newCol)) + return false; + + return setSelected (newRow, newCol); +} + + +// protected virtual [base QWidget] +void kpToolWidgetBase::mousePressEvent (QMouseEvent *e) +{ + e->ignore (); + + if (e->button () != Qt::LeftButton) + return; + + + for (int i = 0; i < (int) m_pixmapRects.count (); i++) + { + for (int j = 0; j < (int) m_pixmapRects [i].count (); j++) + { + if (m_pixmapRects [i][j].contains (e->pos ())) + { + setSelected (i, j); + e->accept (); + return; + } + } + } +} + +// protected virtual [base QFrame] +void kpToolWidgetBase::drawContents (QPainter *painter) +{ +#if DEBUG_KP_TOOL_WIDGET_BASE && 1 + kdDebug () << "kpToolWidgetBase::drawContents(): rect=" << contentsRect () << endl; +#endif + + for (int i = 0; i < (int) m_pixmaps.count (); i++) + { + #if DEBUG_KP_TOOL_WIDGET_BASE && 1 + kdDebug () << "\tRow: " << i << endl; + #endif + + for (int j = 0; j < (int) m_pixmaps [i].count (); j++) + { + QRect rect = m_pixmapRects [i][j]; + QPixmap pixmap = m_pixmaps [i][j]; + + #if DEBUG_KP_TOOL_WIDGET_BASE && 1 + kdDebug () << "\t\tCol: " << j << " rect=" << rect << endl; + #endif + + if (i == m_selectedRow && j == m_selectedCol) + { + painter->fillRect (rect, Qt::blue/*selection color*/); + + if (m_invertSelectedPixmap) + kpEffectInvertCommand::apply (&pixmap); + } + + #if DEBUG_KP_TOOL_WIDGET_BASE && 1 + kdDebug () << "\t\t\tdraw pixmap @ x=" + << rect.x () + (rect.width () - pixmap.width ()) / 2 + << " y=" + << rect.y () + (rect.height () - pixmap.height ()) / 2 + << endl; + + #endif + + painter->drawPixmap (QPoint (rect.x () + (rect.width () - pixmap.width ()) / 2, + rect.y () + (rect.height () - pixmap.height ()) / 2), + pixmap); + } + } +} + +#include <kptoolwidgetbase.moc> diff --git a/kolourpaint/widgets/kptoolwidgetbase.h b/kolourpaint/widgets/kptoolwidgetbase.h new file mode 100644 index 00000000..a23f9a16 --- /dev/null +++ b/kolourpaint/widgets/kptoolwidgetbase.h @@ -0,0 +1,112 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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_widget_base_h__ +#define __kp_tool_widget_base_h__ + +#include <qframe.h> +#include <qpair.h> +#include <qpixmap.h> +#include <qrect.h> +#include <qvaluevector.h> +#include <qwidget.h> + + +class QPainter; + + +// TODO: frame becomes a combobox when its parent kpToolToolBar becomes too small +class kpToolWidgetBase : public QFrame +{ +Q_OBJECT + +public: + kpToolWidgetBase (QWidget *parent, const char *name); // must provide a name for config to work + virtual ~kpToolWidgetBase (); + +public: + void addOption (const QPixmap &pixmap, const QString &toolTip = QString::null); + void startNewOptionRow (); + + // Call this at the end of your constructor. + // If the default row & col could not be read from the config, + // <fallBackRow> & <fallBackCol> are passed to setSelected(). + void finishConstruction (int fallBackRow, int fallBackCol); + +private: + QValueVector <int> spreadOutElements (const QValueVector <int> &sizes, int maxSize); + +public: // (only have to use these if you don't use finishConstruction()) + // (rereads from config file) + QPair <int, int> defaultSelectedRowAndCol () const; + int defaultSelectedRow () const; + int defaultSelectedCol () const; + + void saveSelectedAsDefault () const; + + void relayoutOptions (); + +public: + int selectedRow () const; + int selectedCol () const; + + int selected () const; + + bool hasPreviousOption (int *row = 0, int *col = 0) const; + bool hasNextOption (int *row = 0, int *col = 0) const; + +public slots: + // (returns whether <row> and <col> were in range) + virtual bool setSelected (int row, int col, bool saveAsDefault); + bool setSelected (int row, int col); + + bool selectPreviousOption (); + bool selectNextOption (); + +signals: + void optionSelected (int row, int col); + +protected: + virtual void mousePressEvent (QMouseEvent *e); + virtual void drawContents (QPainter *painter); + + void setInvertSelectedPixmap (bool yes = true) { m_invertSelectedPixmap = yes; } + bool m_invertSelectedPixmap; + + // coulbe be a QFrame or a ComboBox + QWidget *m_baseWidget; + + QValueVector < QValueVector <QPixmap> > m_pixmaps; + QValueVector < QValueVector <QString> > m_toolTips; + + QValueVector < QValueVector <QRect> > m_pixmapRects; + + int m_selectedRow, m_selectedCol; +}; + +#endif // __kp_tool_widget_base_h__ diff --git a/kolourpaint/widgets/kptoolwidgetbrush.cpp b/kolourpaint/widgets/kptoolwidgetbrush.cpp new file mode 100644 index 00000000..046dc8b5 --- /dev/null +++ b/kolourpaint/widgets/kptoolwidgetbrush.cpp @@ -0,0 +1,184 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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_WIDGET_BRUSH 0 + + +#include <kptoolwidgetbrush.h> + +#include <qbitmap.h> +#include <qpainter.h> + +#include <kdebug.h> +#include <klocale.h> + +#include <kpdefs.h> + + +/* sync: <brushes> */ +static int brushSize [][3] = +{ + {8, 4, 1/*like Pen*/}, + {9, 5, 2}, + {9, 5, 2}, + {9, 5, 2} +}; + +#define BRUSH_SIZE_NUM_COLS (int (sizeof (brushSize [0]) / sizeof (brushSize [0][0]))) +#define BRUSH_SIZE_NUM_ROWS (int (sizeof (brushSize) / sizeof (brushSize [0]))) + +kpToolWidgetBrush::kpToolWidgetBrush (QWidget *parent, const char *name) + : kpToolWidgetBase (parent, name) +{ + setInvertSelectedPixmap (); + + QPixmap *pm = m_brushBitmaps; + + for (int shape = 0; shape < BRUSH_SIZE_NUM_ROWS; shape++) + { + for (int i = 0; i < BRUSH_SIZE_NUM_COLS; i++) + { + int w = (width () - 2/*margin*/ - 2/*spacing*/) / BRUSH_SIZE_NUM_COLS; + int h = (height () - 2/*margin*/ - 3/*spacing*/) / BRUSH_SIZE_NUM_ROWS; + pm->resize ((w <= 0 ? width () : w), + (h <= 0 ? height () : h)); + + const int s = brushSize [shape][i]; + QRect rect; + + if (s >= pm->width () || s >= pm->height ()) + rect = QRect (0, 0, pm->width (), pm->height ()); + else + { + rect = QRect ((pm->width () - s) / 2, + (pm->height () - s) / 2, + s, + s); + } + + #if DEBUG_KP_TOOL_WIDGET_BRUSH + kdDebug () << "kpToolWidgetBrush::kpToolWidgetBrush() rect=" << rect << endl; + #endif + + pm->fill (Qt::white); + + QPainter painter (pm); + painter.setPen (Qt::black); + painter.setBrush (Qt::black); + + // sync: <brushes> + switch (shape) + { + case 0: + painter.drawEllipse (rect); + break; + case 1: + painter.drawRect (rect); + break; + case 2: + painter.drawLine (rect.topRight (), rect.bottomLeft ()); + break; + case 3: + painter.drawLine (rect.topLeft (), rect.bottomRight ()); + break; + } + painter.end (); + + pm->setMask (pm->createHeuristicMask ()); + addOption (*pm, brushName (shape, i)/*tooltip*/); + + pm++; + } + + startNewOptionRow (); + } + + finishConstruction (0, 0); +} + +kpToolWidgetBrush::~kpToolWidgetBrush () +{ +} + + +// private +QString kpToolWidgetBrush::brushName (int shape, int whichSize) +{ + int s = brushSize [shape][whichSize]; + + if (s == 1) + return i18n ("1x1"); + + QString shapeName; + + // sync: <brushes> + switch (shape) + { + case 0: + shapeName = i18n ("Circle"); + break; + case 1: + shapeName = i18n ("Square"); + break; + case 2: + // TODO: is this really the name of a shape? :) + shapeName = i18n ("Slash"); + break; + case 3: + // TODO: is this really the name of a shape? :) + shapeName = i18n ("Backslash"); + break; + } + + if (shapeName.isEmpty ()) + return QString::null; + + return i18n ("%1x%2 %3").arg (s).arg (s).arg (shapeName); +} + +QPixmap kpToolWidgetBrush::brush () const +{ + return m_brushBitmaps [selectedRow () * BRUSH_SIZE_NUM_COLS + selectedCol ()]; +} + +bool kpToolWidgetBrush::brushIsDiagonalLine () const +{ + // sync: <brushes> + return (selectedRow () >= 2); +} + +// virtual protected slot [base kpToolWidgetBase] +bool kpToolWidgetBrush::setSelected (int row, int col, bool saveAsDefault) +{ + const bool ret = kpToolWidgetBase::setSelected (row, col, saveAsDefault); + if (ret) + emit brushChanged (brush (), brushIsDiagonalLine ()); + return ret; +} + +#include <kptoolwidgetbrush.moc> diff --git a/kolourpaint/widgets/kptoolwidgetbrush.h b/kolourpaint/widgets/kptoolwidgetbrush.h new file mode 100644 index 00000000..db222e79 --- /dev/null +++ b/kolourpaint/widgets/kptoolwidgetbrush.h @@ -0,0 +1,61 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 __kptoolwidgetbrush_h__ +#define __kptoolwidgetbrush_h__ + +#include <qpixmap.h> + +#include <kptoolwidgetbase.h> + +class kpToolWidgetBrush : public kpToolWidgetBase +{ +Q_OBJECT + +public: + kpToolWidgetBrush (QWidget *parent, const char *name); + virtual ~kpToolWidgetBrush (); + +private: + QString brushName (int shape, int whichSize); + +public: + QPixmap brush () const; + bool brushIsDiagonalLine () const; + +signals: + void brushChanged (const QPixmap &pixmap, bool isDiagonalLine); + +protected slots: + virtual bool setSelected (int row, int col, bool saveAsDefault); + +private: + QPixmap m_brushBitmaps [16]; +}; + +#endif // __kptoolwidgetbrush_h__ diff --git a/kolourpaint/widgets/kptoolwidgeterasersize.cpp b/kolourpaint/widgets/kptoolwidgeterasersize.cpp new file mode 100644 index 00000000..cc58c0d1 --- /dev/null +++ b/kolourpaint/widgets/kptoolwidgeterasersize.cpp @@ -0,0 +1,161 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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_WIDGET_ERASER_SIZE 0 + + +#include <kptoolwidgeterasersize.h> + +#include <qbitmap.h> +#include <qpainter.h> + +#include <kdebug.h> +#include <klocale.h> + +#include <kpcolor.h> +#include <kptool.h> + + +static int eraserSizes [] = {2, 3, 5, 9, 17, 29}; +static const int numEraserSizes = int (sizeof (eraserSizes) / sizeof (eraserSizes [0])); + + +kpToolWidgetEraserSize::kpToolWidgetEraserSize (QWidget *parent, const char *name) + : kpToolWidgetBase (parent, name) +{ + setInvertSelectedPixmap (); + + m_cursorPixmaps = new QPixmap [numEraserSizes]; + QPixmap *cursorPixmap = m_cursorPixmaps; + + for (int i = 0; i < numEraserSizes; i++) + { + if (i == 3 || i == 5) + startNewOptionRow (); + + int s = eraserSizes [i]; + + cursorPixmap->resize (s, s); + cursorPixmap->fill (Qt::black); + + + QPixmap previewPixmap (s, s); + if (i < 3) + { + // HACK: kpToolWidgetBase's layout code sucks and gives uneven spacing + previewPixmap.resize ((width () - 4) / 3, 9); + } + + QPainter painter (&previewPixmap); + QRect rect ((previewPixmap.width () - s) / 2, (previewPixmap.height () - s) / 2, s, s); + painter.fillRect (rect, Qt::black); + painter.end (); + + QBitmap mask (previewPixmap.width (), previewPixmap.height ()); + mask.fill (Qt::color0/*transparent*/); + + QPainter maskPainter (&mask); + maskPainter.fillRect (rect, Qt::color1/*opaque*/); + maskPainter.end (); + + previewPixmap.setMask (mask); + + + addOption (previewPixmap, i18n ("%1x%2").arg (s).arg (s)/*tooltip*/); + + + cursorPixmap++; + } + + finishConstruction (1, 0); +} + +kpToolWidgetEraserSize::~kpToolWidgetEraserSize () +{ + delete [] m_cursorPixmaps; +} + +int kpToolWidgetEraserSize::eraserSize () const +{ + return eraserSizes [selected ()]; +} + +QPixmap kpToolWidgetEraserSize::cursorPixmap (const kpColor &color) const +{ +#if DEBUG_KP_TOOL_WIDGET_ERASER_SIZE + kdDebug () << "kpToolWidgetEraseSize::cursorPixmap() selected=" << selected () + << " numEraserSizes=" << numEraserSizes + << endl; +#endif + + // TODO: why are we even storing m_cursorPixmaps? + QPixmap pixmap = m_cursorPixmaps [selected ()]; + if (color.isOpaque ()) + pixmap.fill (color.toQColor ()); + + + bool showBorder = (pixmap.width () > 2 && pixmap.height () > 2); + + if (showBorder) + { + QPainter painter (&pixmap); + painter.setPen (Qt::black); + painter.drawRect (pixmap.rect ()); + } + + + if (color.isTransparent ()) + { + QBitmap maskBitmap (pixmap.width (), pixmap.height ()); + maskBitmap.fill (Qt::color0/*transparent*/); + + + if (showBorder) + { + QPainter maskBitmapPainter (&maskBitmap); + maskBitmapPainter.setPen (Qt::color1/*opaque*/); + maskBitmapPainter.drawRect (maskBitmap.rect ()); + } + + + pixmap.setMask (maskBitmap); + } + + + return pixmap; +} + +// virtual protected slot [base kpToolWidgetBase] +bool kpToolWidgetEraserSize::setSelected (int row, int col, bool saveAsDefault) +{ + const bool ret = kpToolWidgetBase::setSelected (row, col, saveAsDefault); + if (ret) + emit eraserSizeChanged (eraserSize ()); + return ret; +} + +#include <kptoolwidgeterasersize.moc> diff --git a/kolourpaint/widgets/kptoolwidgeterasersize.h b/kolourpaint/widgets/kptoolwidgeterasersize.h new file mode 100644 index 00000000..71093fd6 --- /dev/null +++ b/kolourpaint/widgets/kptoolwidgeterasersize.h @@ -0,0 +1,59 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 __kptoolwidgeterasersize_h__ +#define __kptoolwidgeterasersize_h__ + +#include <qpixmap.h> +#include <kptoolwidgetbase.h> + + +class kpColor; + +class kpToolWidgetEraserSize : public kpToolWidgetBase +{ +Q_OBJECT + +public: + kpToolWidgetEraserSize (QWidget *parent, const char *name); + virtual ~kpToolWidgetEraserSize (); + + int eraserSize () const; + QPixmap cursorPixmap (const kpColor &color) const; + +signals: + void eraserSizeChanged (int size); + +protected slots: + virtual bool setSelected (int row, int col, bool saveAsDefault); + +private: + QPixmap *m_cursorPixmaps; +}; + +#endif // __kptoolwidgeterasersize_h__ diff --git a/kolourpaint/widgets/kptoolwidgetfillstyle.cpp b/kolourpaint/widgets/kptoolwidgetfillstyle.cpp new file mode 100644 index 00000000..74c174ce --- /dev/null +++ b/kolourpaint/widgets/kptoolwidgetfillstyle.cpp @@ -0,0 +1,222 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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_WIDGET_FILL_STYLE 0 + + +#include <kptoolwidgetfillstyle.h> + +#include <qbitmap.h> +#include <qbrush.h> +#include <qpainter.h> + +#include <kdebug.h> +#include <klocale.h> + +#include <kpcolor.h> +#include <kpdefs.h> +#include <kptool.h> + + +kpToolWidgetFillStyle::kpToolWidgetFillStyle (QWidget *parent, const char *name) + : kpToolWidgetBase (parent, name) +{ + setInvertSelectedPixmap (); + + for (int i = 0; i < (int) FillStyleNum; i++) + { + QPixmap pixmap; + + pixmap = fillStylePixmap ((FillStyle) i, + (width () - 2/*margin*/) * 3 / 4, + (height () - 2/*margin*/ - 2/*spacing*/) * 3 / (3 * 4)); + addOption (pixmap, fillStyleName ((FillStyle) i)/*tooltip*/); + + startNewOptionRow (); + } + + finishConstruction (0, 0); +} + +kpToolWidgetFillStyle::~kpToolWidgetFillStyle () +{ +} + + +// private +QPixmap kpToolWidgetFillStyle::fillStylePixmap (FillStyle fs, int w, int h) +{ + QPixmap pixmap ((w <= 0 ? width () : w), (h <= 0 ? height () : h)); + pixmap.fill (Qt::white); + + QPainter painter (&pixmap); + + painter.setPen (QPen (Qt::black, 2)); + painter.setBrush (brushForFillStyle (fs, + kpColor (Qt::black.rgb ())/*foreground*/, + kpColor (Qt::gray.rgb ())/*background*/)); + + painter.drawRect (2, 2, w - 3, h - 3); + + painter.end (); + + + QBitmap mask (pixmap.width (), pixmap.height ()); + mask.fill (Qt::color0); + + painter.begin (&mask); + painter.setPen (QPen (Qt::color1, 2)); + + if (fs == FillWithBackground || fs == FillWithForeground) + painter.setBrush (Qt::color1); + + painter.drawRect (2, 2, w - 3, h - 3); + + painter.end (); + + pixmap.setMask (mask); + + return pixmap; +} + +// private +QString kpToolWidgetFillStyle::fillStyleName (FillStyle fs) const +{ + // do not complain about the "useless" breaks + // as the return statements might not be return statements one day + + switch (fs) + { + case NoFill: + return i18n ("No Fill"); + break; + case FillWithBackground: + return i18n ("Fill with Background Color"); + break; + case FillWithForeground: + return i18n ("Fill with Foreground Color"); + break; + default: + return QString::null; + break; + } +} + + +// public +kpToolWidgetFillStyle::FillStyle kpToolWidgetFillStyle::fillStyle () const +{ +#if DEBUG_KP_TOOL_WIDGET_FILL_STYLE + kdDebug () << "kpToolWidgetFillStyle::fillStyle() selected=" + << selectedRow () + << endl; +#endif + return (FillStyle) selectedRow (); +} + +// public static +QBrush kpToolWidgetFillStyle::maskBrushForFillStyle (FillStyle fs, + const kpColor &foregroundColor, + const kpColor &backgroundColor) +{ + // do not complain about the "useless" breaks + // as the return statements might not be return statements one day + + switch (fs) + { + case NoFill: + return Qt::NoBrush; + break; + case FillWithBackground: + return QBrush (backgroundColor.maskColor ()); + break; + case FillWithForeground: + return QBrush (foregroundColor.maskColor ()); + break; + default: + return Qt::NoBrush; + break; + } +} + +QBrush kpToolWidgetFillStyle::maskBrush (const kpColor &foregroundColor, + const kpColor &backgroundColor) +{ + return maskBrushForFillStyle (fillStyle (), foregroundColor, backgroundColor); +} + +// public static +QBrush kpToolWidgetFillStyle::brushForFillStyle (FillStyle fs, + const kpColor &foregroundColor, + const kpColor &backgroundColor) +{ + // do not complain about the "useless" breaks + // as the return statements might not be return statements one day + + // sync: kptoolpolygon.cpp pixmap() + + switch (fs) + { + case NoFill: + return Qt::NoBrush; + break; + case FillWithBackground: + if (backgroundColor.isOpaque ()) + return QBrush (backgroundColor.toQColor ()); + else + return Qt::NoBrush; + break; + case FillWithForeground: + if (foregroundColor.isOpaque ()) + return QBrush (foregroundColor.toQColor ()); + else + return Qt::NoBrush; + break; + default: + return Qt::NoBrush; + break; + } +} + +// public +QBrush kpToolWidgetFillStyle::brush (const kpColor &foregroundColor, + const kpColor &backgroundColor) +{ + return brushForFillStyle (fillStyle (), foregroundColor, backgroundColor); +} + + +// virtual protected slot [base kpToolWidgetBase] +bool kpToolWidgetFillStyle::setSelected (int row, int col, bool saveAsDefault) +{ + const bool ret = kpToolWidgetBase::setSelected (row, col, saveAsDefault); + if (ret) + emit fillStyleChanged (fillStyle ()); + return ret; +} + +#include <kptoolwidgetfillstyle.moc> diff --git a/kolourpaint/widgets/kptoolwidgetfillstyle.h b/kolourpaint/widgets/kptoolwidgetfillstyle.h new file mode 100644 index 00000000..219d47f2 --- /dev/null +++ b/kolourpaint/widgets/kptoolwidgetfillstyle.h @@ -0,0 +1,80 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 __kptoolwidgetfillstyle_h__ +#define __kptoolwidgetfillstyle_h__ + +#include <kptoolwidgetbase.h> + +class QBrush; + +class kpColor; + +class kpToolWidgetFillStyle : public kpToolWidgetBase +{ +Q_OBJECT + +public: + kpToolWidgetFillStyle (QWidget *parent, const char *name); + virtual ~kpToolWidgetFillStyle (); + + enum FillStyle + { + NoFill, + FillWithBackground, + FillWithForeground, + FillStyleNum /* not (a valid FillStyle) */ + }; + +private: + QPixmap fillStylePixmap (FillStyle fs, int width, int height); + QString fillStyleName (FillStyle fs) const; + +public: + FillStyle fillStyle () const; + + static QBrush maskBrushForFillStyle (FillStyle fs, + const kpColor &foregroundColor, + const kpColor &backgroundColor); + QBrush maskBrush (const kpColor &foregroundColor, + const kpColor &backgroundColor); + + static QBrush brushForFillStyle (FillStyle fs, + const kpColor &foregroundColor, + const kpColor &backgroundColor); + QBrush brush (const kpColor &foregroundColor, + const kpColor &backgroundColor); + +signals: + void fillStyleChanged (kpToolWidgetFillStyle::FillStyle fillStyle); + +protected slots: + virtual bool setSelected (int row, int col, bool saveAsDefault); +}; + +#endif // __kptoolwidgetfillstyle_h__ diff --git a/kolourpaint/widgets/kptoolwidgetlinewidth.cpp b/kolourpaint/widgets/kptoolwidgetlinewidth.cpp new file mode 100644 index 00000000..27e34ecb --- /dev/null +++ b/kolourpaint/widgets/kptoolwidgetlinewidth.cpp @@ -0,0 +1,97 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 <kptoolwidgetlinewidth.h> + +#include <qbitmap.h> +#include <qpainter.h> + +#include <klocale.h> + + +static int lineWidths [] = {1, 2, 3, 5, 8}; + +kpToolWidgetLineWidth::kpToolWidgetLineWidth (QWidget *parent, const char *name) + : kpToolWidgetBase (parent, name) +{ + setInvertSelectedPixmap (); + + int numLineWidths = sizeof (lineWidths) / sizeof (lineWidths [0]); + + int w = (width () - 2/*margin*/) * 3 / 4; + int h = (height () - 2/*margin*/ - (numLineWidths - 1)/*spacing*/) * 3 / (numLineWidths * 4); + + for (int i = 0; i < numLineWidths; i++) + { + QPixmap pixmap ((w <= 0 ? width () : w), + (h <= 0 ? height () : h)); + pixmap.fill (Qt::white); + + QBitmap maskBitmap (pixmap.width (), pixmap.height ()); + maskBitmap.fill (Qt::color0/*transparent*/); + + + QPainter painter (&pixmap), maskPainter (&maskBitmap); + painter.setPen (Qt::black), maskPainter.setPen (Qt::color1/*opaque*/); + painter.setBrush (Qt::black), maskPainter.setBrush (Qt::color1/*opaque*/); + + QRect rect = QRect (0, (pixmap.height () - lineWidths [i]) / 2, + pixmap.width (), lineWidths [i]); + painter.drawRect (rect), maskPainter.drawRect (rect); + + painter.end (), maskPainter.end (); + + + pixmap.setMask (maskBitmap); + + addOption (pixmap, QString::number (lineWidths [i])); + startNewOptionRow (); + } + + finishConstruction (0, 0); +} + +kpToolWidgetLineWidth::~kpToolWidgetLineWidth () +{ +} + +int kpToolWidgetLineWidth::lineWidth () const +{ + return lineWidths [selectedRow ()]; +} + +// virtual protected slot [base kpToolWidgetBase] +bool kpToolWidgetLineWidth::setSelected (int row, int col, bool saveAsDefault) +{ + const bool ret = kpToolWidgetBase::setSelected (row, col, saveAsDefault); + if (ret) + emit lineWidthChanged (lineWidth ()); + return ret; +} + +#include <kptoolwidgetlinewidth.moc> diff --git a/kolourpaint/widgets/kptoolwidgetlinewidth.h b/kolourpaint/widgets/kptoolwidgetlinewidth.h new file mode 100644 index 00000000..3255e443 --- /dev/null +++ b/kolourpaint/widgets/kptoolwidgetlinewidth.h @@ -0,0 +1,51 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 __kptoolwidgetlinewidth_h__ +#define __kptoolwidgetlinewidth_h__ + +#include <kptoolwidgetbase.h> + +class kpToolWidgetLineWidth : public kpToolWidgetBase +{ +Q_OBJECT + +public: + kpToolWidgetLineWidth (QWidget *parent, const char *name); + virtual ~kpToolWidgetLineWidth (); + + int lineWidth () const; + +signals: + void lineWidthChanged (int width); + +protected slots: + virtual bool setSelected (int row, int col, bool saveAsDefault); +}; + +#endif // __kptoolwidgetlinewidth_h__ diff --git a/kolourpaint/widgets/kptoolwidgetopaqueortransparent.cpp b/kolourpaint/widgets/kptoolwidgetopaqueortransparent.cpp new file mode 100644 index 00000000..41b55d0f --- /dev/null +++ b/kolourpaint/widgets/kptoolwidgetopaqueortransparent.cpp @@ -0,0 +1,100 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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_WIDGET_OPAQUE_OR_TRANSPARENT 0 + + +#include <kptoolwidgetopaqueortransparent.h> + +#include <kdebug.h> +#include <kiconloader.h> +#include <klocale.h> + + +kpToolWidgetOpaqueOrTransparent::kpToolWidgetOpaqueOrTransparent (QWidget *parent, const char *name) + : kpToolWidgetBase (parent, name) +{ + setInvertSelectedPixmap (false); + + addOption (UserIcon ("option_opaque"), i18n ("Opaque")/*tooltip*/); + startNewOptionRow (); + addOption (UserIcon ("option_transparent"), i18n ("Transparent")/*tooltip*/); + + finishConstruction (0, 0); +} + +kpToolWidgetOpaqueOrTransparent::~kpToolWidgetOpaqueOrTransparent () +{ +} + + +// public +bool kpToolWidgetOpaqueOrTransparent::isOpaque () const +{ + return (selected () == 0); +} + +// public +bool kpToolWidgetOpaqueOrTransparent::isTransparent () const +{ + return (!isOpaque ()); +} + +// public +void kpToolWidgetOpaqueOrTransparent::setOpaque (bool yes) +{ +#if DEBUG_KP_TOOL_WIDGET_OPAQUE_OR_TRANSPARENT && 1 + kdDebug () << "kpToolWidgetOpaqueOrTransparent::setOpaque(" << yes << ")" << endl; +#endif + setSelected (yes ? 0 : 1, 0, false/*don't save*/); +} + +// public +void kpToolWidgetOpaqueOrTransparent::setTransparent (bool yes) +{ +#if DEBUG_KP_TOOL_WIDGET_OPAQUE_OR_TRANSPARENT && 1 + kdDebug () << "kpToolWidgetOpaqueOrTransparent::setTransparent(" << yes << ")" << endl; +#endif + setSelected (yes ? 1 : 0, 0, false/*don't save*/); +} + + +// protected slot virtual [base kpToolWidgetBase] +bool kpToolWidgetOpaqueOrTransparent::setSelected (int row, int col, bool saveAsDefault) +{ +#if DEBUG_KP_TOOL_WIDGET_OPAQUE_OR_TRANSPARENT && 1 + kdDebug () << "kpToolWidgetOpaqueOrTransparent::setSelected(" + << row << "," << col << ")" << endl; +#endif + const bool ret = kpToolWidgetBase::setSelected (row, col, saveAsDefault); + if (ret) + emit isOpaqueChanged (isOpaque ()); + return ret; +} + + +#include <kptoolwidgetopaqueortransparent.moc> diff --git a/kolourpaint/widgets/kptoolwidgetopaqueortransparent.h b/kolourpaint/widgets/kptoolwidgetopaqueortransparent.h new file mode 100644 index 00000000..c24cd308 --- /dev/null +++ b/kolourpaint/widgets/kptoolwidgetopaqueortransparent.h @@ -0,0 +1,56 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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_widget_opaque_or_transparent_h__ +#define __kp_tool_widget_opaque_or_transparent_h__ + + +#include <kptoolwidgetbase.h> + +class kpToolWidgetOpaqueOrTransparent : public kpToolWidgetBase +{ +Q_OBJECT + +public: + kpToolWidgetOpaqueOrTransparent (QWidget *parent, const char *name); + virtual ~kpToolWidgetOpaqueOrTransparent (); + + bool isOpaque () const; + bool isTransparent () const; + void setOpaque (bool yes = true); + void setTransparent (bool yes = true); + +signals: + void isOpaqueChanged (bool isOpaque_); + +protected slots: + virtual bool setSelected (int row, int col, bool saveAsDefault); +}; + + +#endif // kp_tool_widget_opaque_or_transparent_h__ diff --git a/kolourpaint/widgets/kptoolwidgetspraycansize.cpp b/kolourpaint/widgets/kptoolwidgetspraycansize.cpp new file mode 100644 index 00000000..161e5015 --- /dev/null +++ b/kolourpaint/widgets/kptoolwidgetspraycansize.cpp @@ -0,0 +1,119 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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_WIDGET_SPRAYCAN_SIZE 0 + + +#include <kptoolwidgetspraycansize.h> + +#include <qbitmap.h> +#include <qimage.h> +#include <qpainter.h> + +#include <kdebug.h> +#include <kiconloader.h> +#include <klocale.h> + +#include <kppixmapfx.h> + + +static int spraycanSizes [] = {9, 17, 29}; + +kpToolWidgetSpraycanSize::kpToolWidgetSpraycanSize (QWidget *parent, const char *name) + : kpToolWidgetBase (parent, name) +{ +#if DEBUG_KP_TOOL_WIDGET_SPRAYCAN_SIZE + kdDebug () << "kpToolWidgetSpraycanSize::kpToolWidgetSpraycanSize() CALLED!" << endl; +#endif + + for (int i = 0; i < int (sizeof (spraycanSizes) / sizeof (spraycanSizes [0])); i++) + { + int s = spraycanSizes [i]; + QString iconName = QString ("tool_spraycan_%1x%1").arg (s).arg(s); + + #if DEBUG_KP_TOOL_WIDGET_SPRAYCAN_SIZE + kdDebug () << "\ticonName=" << iconName << endl; + #endif + + QPixmap pixmap (s, s); + pixmap.fill (Qt::white); + + QPainter painter (&pixmap); + painter.drawPixmap (0, 0, UserIcon (iconName)); + painter.end (); + + QImage image = kpPixmapFX::convertToImage (pixmap); + + QBitmap mask (pixmap.width (), pixmap.height ()); + mask.fill (Qt::color0); + + painter.begin (&mask); + painter.setPen (Qt::color1); + + for (int y = 0; y < image.height (); y++) + { + for (int x = 0; x < image.width (); x++) + { + if ((image.pixel (x, y) & RGB_MASK) == 0/*black*/) + painter.drawPoint (x, y); // mark as opaque + } + } + + painter.end (); + + pixmap.setMask (mask); + + addOption (pixmap, i18n ("%1x%2").arg (s).arg (s)/*tooltip*/); + if (i == 1) + startNewOptionRow (); + } + + finishConstruction (0, 0); +} + +kpToolWidgetSpraycanSize::~kpToolWidgetSpraycanSize () +{ +} + + +// public +int kpToolWidgetSpraycanSize::spraycanSize () const +{ + return spraycanSizes [selected ()]; +} + +// protected slot virtual [base kpToolWidgetBase] +bool kpToolWidgetSpraycanSize::setSelected (int row, int col, bool saveAsDefault) +{ + const bool ret = kpToolWidgetBase::setSelected (row, col, saveAsDefault); + if (ret) + emit spraycanSizeChanged (spraycanSize ()); + return ret; +} + +#include <kptoolwidgetspraycansize.moc> diff --git a/kolourpaint/widgets/kptoolwidgetspraycansize.h b/kolourpaint/widgets/kptoolwidgetspraycansize.h new file mode 100644 index 00000000..b4233a80 --- /dev/null +++ b/kolourpaint/widgets/kptoolwidgetspraycansize.h @@ -0,0 +1,51 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org> + 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 __kptoolwidgetspraycansize_h__ +#define __kptoolwidgetspraycansize_h__ + +#include <kptoolwidgetbase.h> + +class kpToolWidgetSpraycanSize : public kpToolWidgetBase +{ +Q_OBJECT + +public: + kpToolWidgetSpraycanSize (QWidget *parent, const char *name); + virtual ~kpToolWidgetSpraycanSize (); + + int spraycanSize () const; + +signals: + void spraycanSizeChanged (int size); + +protected slots: + virtual bool setSelected (int row, int col, bool saveAsDefault); +}; + +#endif // __kptoolwidgetspraycansize_h__ |