You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdegraphics/kolourpaint/kpselection.cpp

1447 lines
37 KiB

/*
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>