summaryrefslogtreecommitdiffstats
path: root/kolourpaint/kpselection.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'kolourpaint/kpselection.cpp')
-rw-r--r--kolourpaint/kpselection.cpp1446
1 files changed, 1446 insertions, 0 deletions
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 <[email protected]>
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (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>
+