diff --git a/core/app/date/ddateedit.cpp b/core/app/date/ddateedit.cpp index efa9c7490d..db34e755ca 100644 --- a/core/app/date/ddateedit.cpp +++ b/core/app/date/ddateedit.cpp @@ -1,510 +1,510 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2002-01-10 * Description : a combo box to list date. * this widget come from libkdepim. * * Copyright (C) 2011-2019 by Gilles Caulier * Copyright (C) 2002 by Cornelius Schumacher * Copyright (C) 2003-2004 by Reinhold Kainhofer * Copyright (C) 2004 by Tobias Koenig * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "ddateedit.h" // Qt includes #include #include #include #include #include #include #include #include #include // KDE includes #include // Local includes #include "ddatepickerpopup.h" namespace Digikam { class Q_DECL_HIDDEN DateValidator : public QValidator { public: DateValidator(const QStringList& keywords, const QString& dateFormat, QWidget* const parent) : QValidator(parent), mKeywords(keywords), mDateFormat(dateFormat) { } virtual State validate(QString& str, int&) const { int length = str.length(); // empty string is intermediate so one can clear the edit line and start from scratch if (length <= 0) { return Intermediate; } if (mKeywords.contains(str.toLower())) { return Acceptable; } bool ok = QDate::fromString(str, mDateFormat).isValid(); if (ok) { return Acceptable; } else { return Intermediate; } } private: QStringList mKeywords; QString mDateFormat; }; // ----------------------------------------------------------------------------------- class Q_DECL_HIDDEN DDateEdit::Private { public: explicit Private() : readOnly(false), textChanged(false), discardNextMousePress(false), popup(0) { } bool readOnly; bool textChanged; bool discardNextMousePress; QDate date; QString dateFormat; QMap keywordMap; DDatePickerPopup* popup; }; DDateEdit::DDateEdit(QWidget* const parent, const QString& name) : QComboBox(parent), d(new Private) { setObjectName(name); // need at least one entry for popup to work setMaxCount(1); setEditable(true); d->date = QDate::currentDate(); d->dateFormat = QLocale().dateFormat(QLocale::ShortFormat); if (!d->dateFormat.contains(QLatin1String("yyyy"))) { d->dateFormat.replace(QLatin1String("yy"), QLatin1String("yyyy")); } QString today = d->date.toString(d->dateFormat); addItem(today); setCurrentIndex(0); setMinimumSize(sizeHint()); setMinimumSize(minimumSizeHint()); connect(lineEdit(), SIGNAL(returnPressed()), this, SLOT(lineEnterPressed())); connect(this, SIGNAL(currentTextChanged(QString)), SLOT(slotTextChanged(QString))); d->popup = new DDatePickerPopup(DDatePickerPopup::DatePicker | DDatePickerPopup::Words); d->popup->hide(); d->popup->installEventFilter(this); connect(d->popup, SIGNAL(dateChanged(QDate)), this, SLOT(dateSelected(QDate))); // handle keyword entry setupKeywords(); lineEdit()->installEventFilter(this); setValidator(new DateValidator(d->keywordMap.keys(), d->dateFormat, this)); d->textChanged = false; } DDateEdit::~DDateEdit() { delete d->popup; d->popup = 0; delete d; } void DDateEdit::setDate(const QDate& date) { assignDate(date); updateView(); } QDate DDateEdit::date() const { return d->date; } void DDateEdit::setReadOnly(bool readOnly) { d->readOnly = readOnly; lineEdit()->setReadOnly(readOnly); } bool DDateEdit::isReadOnly() const { return d->readOnly; } void DDateEdit::showPopup() { if (d->readOnly) { return; } QRect desk = QApplication::desktop()->screenGeometry(this); - QPoint popupPoint = mapToGlobal(QPoint(0,0)); + QPoint popupPoint = mapToGlobal(QPoint(0, 0)); int dateFrameHeight = d->popup->sizeHint().height(); if (popupPoint.y() + height() + dateFrameHeight > desk.bottom()) { popupPoint.setY(popupPoint.y() - dateFrameHeight); } else { popupPoint.setY(popupPoint.y() + height()); } int dateFrameWidth = d->popup->sizeHint().width(); if (popupPoint.x() + dateFrameWidth > desk.right()) { popupPoint.setX(desk.right() - dateFrameWidth); } if (popupPoint.x() < desk.left()) { popupPoint.setX(desk.left()); } if (popupPoint.y() < desk.top()) { popupPoint.setY(desk.top()); } if (d->date.isValid()) { d->popup->setDate(d->date); } else { d->popup->setDate(QDate::currentDate()); } d->popup->popup(popupPoint); // The combo box is now shown pressed. Make it show not pressed again // by causing its (invisible) list box to emit a 'selected' signal. // First, ensure that the list box contains the date currently displayed. QDate date = parseDate(); assignDate(date); updateView(); // Now, simulate an Enter to unpress it QAbstractItemView* const lb = view(); if (lb) { lb->setCurrentIndex(lb->model()->index(0, 0)); QKeyEvent* const keyEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Enter, Qt::NoModifier); QApplication::postEvent(lb, keyEvent); } } void DDateEdit::dateSelected(const QDate& date) { if (assignDate(date)) { updateView(); emit dateChanged(date); if (date.isValid()) { d->popup->hide(); } } } void DDateEdit::dateEntered(const QDate& date) { if (assignDate(date)) { updateView(); emit dateChanged(date); } } void DDateEdit::lineEnterPressed() { bool replaced = false; QDate date = parseDate(&replaced); if (assignDate(date)) { if (replaced) { updateView(); } emit dateChanged(date); } } QDate DDateEdit::parseDate(bool* replaced) const { QString text = currentText(); QDate result; if (replaced) { (*replaced) = false; } if (text.isEmpty()) { result = QDate(); } else if (d->keywordMap.contains(text.toLower())) { QDate today = QDate::currentDate(); int i = d->keywordMap[text.toLower()]; if (i >= 100) { /* A day name has been entered. Convert to offset from today. * This uses some math tricks to figure out the offset in days * to the next date the given day of the week occurs. There * are two cases, that the new day is >= the current day, which means * the new day has not occurred yet or that the new day < the current day, * which means the new day is already passed (so we need to find the * day in the next week). */ i -= 100; int currentDay = today.dayOfWeek(); if (i >= currentDay) { i -= currentDay; } else { i += 7 - currentDay; } } result = today.addDays(i); if (replaced) { (*replaced) = true; } } else { result = QDate::fromString(text, d->dateFormat); } return result; } bool DDateEdit::eventFilter(QObject* object, QEvent* event) { if (object == lineEdit()) { // We only process the focus out event if the text has changed // since we got focus if ((event->type() == QEvent::FocusOut) && d->textChanged) { lineEnterPressed(); d->textChanged = false; } else if (event->type() == QEvent::KeyPress) { // Up and down arrow keys step the date QKeyEvent* const keyEvent = (QKeyEvent*)event; if (keyEvent->key() == Qt::Key_Return) { lineEnterPressed(); return true; } int step = 0; if (keyEvent->key() == Qt::Key_Up) { step = 1; } else if (keyEvent->key() == Qt::Key_Down) { step = -1; } if (step && !d->readOnly) { QDate date = parseDate(); if (date.isValid()) { date = date.addDays(step); if (assignDate(date)) { updateView(); emit dateChanged(date); return true; } } } } } else { // It's a date picker event switch (event->type()) { case QEvent::MouseButtonDblClick: case QEvent::MouseButtonPress: { QMouseEvent* const mouseEvent = (QMouseEvent*)event; if (!d->popup->rect().contains(mouseEvent->pos())) { QPoint globalPos = d->popup->mapToGlobal(mouseEvent->pos()); if (QApplication::widgetAt(globalPos) == this) { // The date picker is being closed by a click on the // DDateEdit widget. Avoid popping it up again immediately. d->discardNextMousePress = true; } } break; } default: break; } } return false; } void DDateEdit::mousePressEvent(QMouseEvent* e) { if (e->button() == Qt::LeftButton && d->discardNextMousePress) { d->discardNextMousePress = false; return; } QComboBox::mousePressEvent(e); } void DDateEdit::slotTextChanged(const QString&) { QDate date = parseDate(); if (assignDate(date)) { emit dateChanged(date); } d->textChanged = true; } void DDateEdit::setupKeywords() { // Create the keyword list. This will be used to match against when the user // enters information. d->keywordMap.insert(i18n("tomorrow"), 1); d->keywordMap.insert(i18n("today"), 0); d->keywordMap.insert(i18n("yesterday"), -1); QString dayName; for (int i = 1; i <= 7; ++i) { dayName = QLocale().standaloneDayName(i, QLocale::LongFormat).toLower(); d->keywordMap.insert(dayName, i + 100); } } bool DDateEdit::assignDate(const QDate& date) { d->date = date; d->textChanged = false; return true; } void DDateEdit::updateView() { QString dateString; if (d->date.isValid()) { dateString = d->date.toString(d->dateFormat); } // We do not want to generate a signal here, // since we explicitly setting the date bool blocked = signalsBlocked(); blockSignals(true); removeItem(0); insertItem(0, dateString); blockSignals(blocked); } } // namespace Digikam diff --git a/core/libs/dimg/dimgscale.cpp b/core/libs/dimg/dimgscale.cpp index 4445b3522d..dd138fe3c1 100644 --- a/core/libs/dimg/dimgscale.cpp +++ b/core/libs/dimg/dimgscale.cpp @@ -1,2453 +1,2453 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2005-06-14 * Description : This is the normal smoothscale method, * based on Imlib2's smoothscale. Added * smoothScaleSection - Scaling only of a * section of a image. Added 16 bits image support * * Copyright (C) 2005 by Renchi Raju * Copyright (C) 2006-2019 by Gilles Caulier * * Ported to C++/QImage by Daniel M. Duley * Following modification are (C) Daniel M. Duley * Changes include formatting, namespaces and other C++'ings, removal of old * #ifdef'ed code, and removal of unneeded border calculation code. * Original implementation: http://trac.enlightenment.org/e/browser/trunk/imlib2/src/lib/scale.c * * Imlib2 is (C) Carsten Haitzler and various contributors. The MMX code * is by Willem Monsuwe . * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ // C ANSI includes extern "C" { #include } // C++ includes #include #include #include // Local includes #include "digikam_debug.h" #include "dimg.h" #include "dimg_p.h" typedef uint64_t ullong; // krazy:exclude=typedefs typedef int64_t llong; // krazy:exclude=typedefs namespace Digikam { namespace DImgScale { class Q_DECL_HIDDEN DImgScaleInfo { public: DImgScaleInfo() { xpoints = 0; ypoints = 0; ypoints16 = 0; xapoints = 0; yapoints = 0; xup_yup = 0; } ~DImgScaleInfo() { delete [] xpoints; delete [] ypoints; delete [] ypoints16; delete [] xapoints; delete [] yapoints; } int* xpoints; uint** ypoints; ullong** ypoints16; int* xapoints; int* yapoints; int xup_yup; }; uint** dimgCalcYPoints(uint* const src, int sw, int sh, int dh); ullong** dimgCalcYPoints16(ullong* const src, int sw, int sh, int dh); int* dimgCalcXPoints(int sw, int dw); int* dimgCalcApoints(int s, int d, int up); DImgScaleInfo* dimgCalcScaleInfo(const DImg& img, int sw, int sh, int dw, int dh, bool sixteenBit, bool aa); // 8 bit, not smoothed void dimgSampleRGBA(DImgScaleInfo* const isi, uint* const dest, int dxx, int dyy, int dw, int dh, int dow); void dimgSampleRGBA(DImgScaleInfo* const isi, uint* const dest, int dxx, int dyy, int dw, int dh, int dow, int clip_dx, int clip_dy, int clip_dw, int clip_dh); // 16 bit, not smoothed void dimgSampleRGBA16(DImgScaleInfo* const isi, ullong* const dest, int dxx, int dyy, int dw, int dh, int dow); void dimgSampleRGBA16(DImgScaleInfo* const isi, ullong* const dest, int dxx, int dyy, int dw, int dh, int dow, int clip_dx, int clip_dy, int clip_dw, int clip_dh); // 8 bit, RGBA void dimgScaleAARGBA(DImgScaleInfo* const isi, uint* const dest, int dxx, int dyy, int dw, int dh, int dow, int sow); void dimgScaleAARGBA(DImgScaleInfo* const isi, uint* const dest, int dxx, int dyy, int dw, int dh, int dow, int sow, int clip_dx, int clip_dy, int clip_dw, int clip_dh); // 8 bit, RGB void dimgScaleAARGB(DImgScaleInfo* const isi, uint* const dest, int dxx, int dyy, int dw, int dh, int dow, int sow); void dimgScaleAARGB(DImgScaleInfo* const isi, uint* const dest, int dxx, int dyy, int dw, int dh, int dow, int sow, int clip_dx, int clip_dy, int clip_dw, int clip_dh); // 16 bit, RGBA void dimgScaleAARGBA16(DImgScaleInfo* const isi, ullong* const dest, int dxx, int dyy, int dw, int dh, int dow, int sow); void dimgScaleAARGBA16(DImgScaleInfo* const isi, ullong* const dest, int dxx, int dyy, int dw, int dh, int dow, int sow, int clip_dx, int clip_dy, int clip_dw, int clip_dh); // 16 bit, RGB void dimgScaleAARGB16(DImgScaleInfo* const isi, ullong* const dest, int dxx, int dyy, int dw, int dh, int dow, int sow); void dimgScaleAARGB16(DImgScaleInfo* const isi, ullong* const dest, int dxx, int dyy, int dw, int dh, int dow, int sow, int clip_dx, int clip_dy, int clip_dw, int clip_dh); } using namespace DImgScale; /* #define CLIP(x, y, w, h, xx, yy, ww, hh) \ if (x < (xx)) {w += (x - (xx)); x = (xx);} \ if (y < (yy)) {h += (y - (yy)); y = (yy);} \ if ((x + w) > ((xx) + (ww))) {w = (ww) - (x - xx);} \ if ((y + h) > ((yy) + (hh))) {h = (hh) - (y - yy);} */ DImg DImg::smoothScale(const QSize& destSize, Qt::AspectRatioMode aspectRatioMode) const { QSize scaleSize = size(); scaleSize.scale(destSize, aspectRatioMode); if (scaleSize.isEmpty()) { return DImg(); } - return smoothScaleClipped(scaleSize, QRect(QPoint(0,0), scaleSize)); + return smoothScaleClipped(scaleSize, QRect(QPoint(0, 0), scaleSize)); } DImg DImg::smoothScale(int dw, int dh, Qt::AspectRatioMode aspectRatioMode) const { return smoothScale(QSize(dw, dh), aspectRatioMode); } DImg DImg::smoothScaleClipped(const QSize& destSize, const QRect& clip) const { return DImg::smoothScaleClipped(destSize.width(), destSize.height(), clip.x(), clip.y(), clip.width(), clip.height()); } DImg DImg::smoothScaleClipped(int dw, int dh, int clipx, int clipy, int clipw, int cliph) const { if (dw <= 0 || dh <= 0 || clipw <= 0 || cliph <= 0 || isNull()) { return DImg(); } uint w = width(); uint h = height(); if (w <= 0 || h <= 0) { return DImg(); } // ensure clip is valid if (!Private::clipped(clipx, clipy, clipw, cliph, dw, dh)) { return DImg(); } // do we actually need to scale? if ((w == (uint)dw) && (h == (uint)dh)) { if (clipw == dw && cliph == dh) { return copy(); } else { return copy(clipx, clipy, clipw, cliph); } } DImgScaleInfo* scaleinfo = dimgCalcScaleInfo(*this, w, h, dw, dh, sixteenBit(), true); DImg buffer(*this, clipw, cliph); if (sixteenBit()) { if (hasAlpha()) { dimgScaleAARGBA16(scaleinfo, reinterpret_cast(buffer.bits()), 0, 0, dw, dh, clipw, w, clipx, clipy, clipw, cliph); } else { dimgScaleAARGB16(scaleinfo, reinterpret_cast(buffer.bits()), 0, 0, dw, dh, clipw, w, clipx, clipy, clipw, cliph); } } else { if (hasAlpha()) { dimgScaleAARGBA(scaleinfo, reinterpret_cast(buffer.bits()), 0, 0, dw, dh, clipw, w, clipx, clipy, clipw, cliph); } else { dimgScaleAARGB(scaleinfo, reinterpret_cast(buffer.bits()), 0, 0, dw, dh, clipw, w, clipx, clipy, clipw, cliph); } } delete scaleinfo; return buffer; } DImg DImg::smoothScaleSection(const QRect& sourceRect, const QSize& destSize) const { return smoothScaleSection(sourceRect.x(), sourceRect.y(), sourceRect.width(), sourceRect.height(), destSize.width(), destSize.height()); } DImg DImg::smoothScaleSection(int sx, int sy, int sw, int sh, int dw, int dh) const { uint w = width(); uint h = height(); // sanity checks if ((dw <= 0) || (dh <= 0)) { return DImg(); } if ((sw <= 0) || (sh <= 0)) { return DImg(); } // clip the source rect to be within the actual image int /*psx, psy,*/ psw, psh; // psx = sx; // psy = sy; psw = sw; psh = sh; if (!Private::clipped(sx, sy, sw, sh, w, h)) { return DImg(); } // clip output coords to clipped input coords if (psw != sw) { dw = (dw * sw) / psw; } if (psh != sh) { dh = (dh * sh) / psh; } // do a second check to see if we now have invalid coords // do not do anything if we have a 0 width or height image to render if ((dw <= 0) || (dh <= 0)) { return DImg(); } // if the input rect size < 0 do not render either if ((sw <= 0) || (sh <= 0)) { return DImg(); } // do we actually need to scale? if ((sw == dw) && (sh == dh)) { return copy(sx, sy, sw, sh); } // calculate scaleinfo DImgScaleInfo* scaleinfo = dimgCalcScaleInfo(*this, sw, sh, dw, dh, sixteenBit(), true); DImg buffer(*this, dw, dh); if (sixteenBit()) { if (hasAlpha()) { dimgScaleAARGBA16(scaleinfo, reinterpret_cast(buffer.bits()), ((sx * dw) / sw), ((sy * dh) / sh), dw, dh, dw, w); } else { dimgScaleAARGB16(scaleinfo, reinterpret_cast(buffer.bits()), ((sx * dw) / sw), ((sy * dh) / sh), dw, dh, dw, w); } } else { if (hasAlpha()) { dimgScaleAARGBA(scaleinfo, reinterpret_cast(buffer.bits()), ((sx * dw) / sw), ((sy * dh) / sh), dw, dh, dw, w); } else { dimgScaleAARGB(scaleinfo, reinterpret_cast(buffer.bits()), ((sx * dw) / sw), ((sy * dh) / sh), dw, dh, dw, w); } } delete scaleinfo; return buffer; } // // Code ported from Imlib2... // // FIXME: replace with mRed, etc... These work on pointers to pixels, not // pixel values #define A_VAL(p) ((unsigned char*)(p))[3] #define R_VAL(p) ((unsigned char*)(p))[2] #define G_VAL(p) ((unsigned char*)(p))[1] #define B_VAL(p) ((unsigned char*)(p))[0] #define INV_XAP (256 - xapoints[x]) #define XAP (xapoints[x]) #define INV_YAP (256 - yapoints[dyy + y]) #define YAP (yapoints[dyy + y]) uint** DImgScale::dimgCalcYPoints(uint* const src, int sw, int sh, int dh) { uint** p = 0; int i, j = 0; ullong val, inc; p = new uint* [dh+1]; val = 0; inc = (((ullong)sh) << 16) / dh; for (i = 0 ; i < dh ; ++i) { p[j++] = src + ((val >> 16) * sw); val += inc; } return(p); } ullong** DImgScale::dimgCalcYPoints16(ullong* const src, int sw, int sh, int dh) { ullong** p = 0; int i, j = 0; ullong val, inc; p = new ullong*[(dh+1)]; val = 0; inc = (((ullong)sh) << 16) / dh; for (i = 0 ; i < dh ; ++i) { p[j++] = src + ((val >> 16) * sw); val += inc; } return p; } int* DImgScale::dimgCalcXPoints(int sw, int dw) { int* p=0, i, j = 0; ullong val, inc; p = new int[dw+1]; val = 0; inc = (((ullong)sw) << 16) / dw; for (i = 0 ; i < dw ; ++i) { p[j++] = (val >> 16); val += inc; } return(p); } int* DImgScale::dimgCalcApoints(int s, int d, int up) { int* p=0, i, j = 0; p = new int[d]; /* scaling up */ if (up) { ullong val, inc; val = 0; inc = (((ullong)s) << 16) / d; for (i = 0 ; i < d ; ++i) { p[j++] = (val >> 8) - ((val >> 8) & 0xffffff00); if ((int)(val >> 16) >= (s - 1)) { p[j - 1] = 0; } val += inc; } } /* scaling down */ else { ullong val, inc; int ap, Cp; val = 0; inc = (((ullong)s) << 16) / d; Cp = ((d << 14) / s) + 1; for (i = 0 ; i < d ; ++i) { ap = ((0x100 - ((val >> 8) & 0xff)) * Cp) >> 8; p[j] = ap | (Cp << 16); ++j; val += inc; } } return(p); } DImgScaleInfo* DImgScale::dimgCalcScaleInfo(const DImg& img, int sw, int sh, int dw, int dh, bool /*sixteenBit*/, bool aa) { DImgScaleInfo* isi = new DImgScaleInfo; int scw, sch; scw = dw * img.width() / sw; sch = dh * img.height() / sh; isi->xup_yup = (abs(dw) >= sw) + ((abs(dh) >= sh) << 1); isi->xpoints = dimgCalcXPoints(img.width(), scw); if (img.sixteenBit()) { isi->ypoints = 0; isi->ypoints16 = dimgCalcYPoints16(reinterpret_cast(img.bits()), img.width(), img.height(), sch); } else { isi->ypoints16 = 0; isi->ypoints = dimgCalcYPoints(reinterpret_cast(img.bits()), img.width(), img.height(), sch); } if (aa) { isi->xapoints = dimgCalcApoints(img.width(), scw, isi->xup_yup & 1); isi->yapoints = dimgCalcApoints(img.height(), sch, isi->xup_yup & 2); } else { isi->xapoints = new int[scw]; for (int i = 0; i < scw; ++i) { isi->xapoints[i] = 0; } isi->yapoints = new int[sch]; for (int i = 0; i < sch; ++i) { isi->yapoints[i] = 0; } } return isi; } /** scale by pixel sampling only */ void DImgScale::dimgSampleRGBA(DImgScaleInfo* const isi, uint* const dest, int dxx, int dyy, int dw, int dh, int dow) { dimgSampleRGBA(isi, dest, dxx, dyy, dw, dh, dow, 0, 0, dw, dh); } void DImgScale::dimgSampleRGBA(DImgScaleInfo* const isi, uint* const dest, int dxx, int dyy, int dw, int dh, int dow, int clip_dx, int clip_dy, int clip_dw, int clip_dh) { Q_UNUSED(dw); Q_UNUSED(dh); uint* sptr=0; uint* dptr=0; int x, y; uint** ypoints = isi->ypoints; int* xpoints = isi->xpoints; const int x_begin = dxx + clip_dx; // no clip set = dxx const int x_end = x_begin + clip_dw; // no clip set = dxx + dw const int y_begin = clip_dy; // no clip set = 0 const int y_end = clip_dy + clip_dh; // no clip set = dh /* go through every scanline in the output buffer */ for (y = y_begin; y < y_end; ++y) { /* get the pointer to the start of the destination scanline */ dptr = dest + (y - y_begin) * dow; /* calculate the source line we'll scan from */ sptr = ypoints[dyy + y]; /* go through the scanline and copy across */ for (x = x_begin; x < x_end; ++x) { *dptr++ = sptr[xpoints[x]]; } } } void DImgScale::dimgSampleRGBA16(DImgScaleInfo* const isi, ullong* const dest, int dxx, int dyy, int dw, int dh, int dow) { dimgSampleRGBA16(isi, dest, dxx, dyy, dw, dh, dow, 0, 0, dw, dh); } void DImgScale::dimgSampleRGBA16(DImgScaleInfo* const isi, ullong* const dest, int dxx, int dyy, int dw, int dh, int dow, int clip_dx, int clip_dy, int clip_dw, int clip_dh) { Q_UNUSED(dw); Q_UNUSED(dh); ullong* sptr=0; ullong* dptr=0; int x, y; ullong** ypoints = isi->ypoints16; int* xpoints = isi->xpoints; const int x_begin = dxx + clip_dx; // no clip set = dxx const int x_end = x_begin + clip_dw; // no clip set = dxx + dw const int y_begin = clip_dy; // no clip set = 0 const int y_end = clip_dy + clip_dh; // no clip set = dh /* go through every scanline in the output buffer */ for (y = y_begin; y < y_end; ++y) { /* get the pointer to the start of the destination scanline */ dptr = dest + (y - y_begin) * dow; /* calculate the source line we'll scan from */ sptr = ypoints[dyy + y]; /* go through the scanline and copy across */ for (x = x_begin; x < x_end; ++x) { *dptr++ = sptr[xpoints[x]]; } } } /* FIXME: NEED to optimize ScaleAARGBA - currently its "ok" but needs work*/ /** dimgScaleAARGBA : scale by area sampling. Arguments: DImgScaleInfo* isi, // scaleinfo uint* dest, // destination img data int dxx, // destination x location corresponding to start x of src section int dyy, // destination y location corresponding to start y of src section int dw, // destination width int dh, // destination height int dow, // destination scanline width int sow); // src scanline width */ void DImgScale::dimgScaleAARGBA(DImgScaleInfo* const isi, uint* const dest, int dxx, int dyy, int dw, int dh, int dow, int sow ) { dimgScaleAARGBA(isi, dest, dxx, dyy, dw, dh, dow, sow, 0, 0, dw, dh); } void DImgScale::dimgScaleAARGBA(DImgScaleInfo* const isi, uint* const dest, int dxx, int dyy, int dw, int dh, int dow, int sow, int clip_dx, int clip_dy, int clip_dw, int clip_dh ) { Q_UNUSED(dw); Q_UNUSED(dh); uint* sptr=0; uint* dptr=0; int x, y; uint** ypoints = isi->ypoints; int* xpoints = isi->xpoints; int* xapoints = isi->xapoints; int* yapoints = isi->yapoints; const int x_begin = dxx + clip_dx; // no clip set = dxx const int x_end = x_begin + clip_dw; // no clip set = dxx + dw const int y_begin = clip_dy; // no clip set = 0 const int y_end = clip_dy + clip_dh; // no clip set = dh /* scaling up both ways */ if (isi->xup_yup == 3) { /* go through every scanline in the output buffer */ for (y = y_begin; y < y_end; ++y) { /* calculate the source line we'll scan from */ dptr = dest + (y - y_begin) * dow; sptr = ypoints[dyy + y]; if (YAP > 0) { for (x = x_begin; x < x_end; ++x) { int r, g, b, a; uint* pix=0; if (XAP > 0) { int rr, gg, bb, aa; pix = ypoints[dyy + y] + xpoints[x]; r = R_VAL(pix) * INV_XAP; g = G_VAL(pix) * INV_XAP; b = B_VAL(pix) * INV_XAP; a = A_VAL(pix) * INV_XAP; ++pix; r += R_VAL(pix) * XAP; g += G_VAL(pix) * XAP; b += B_VAL(pix) * XAP; a += A_VAL(pix) * XAP; pix += sow; rr = R_VAL(pix) * XAP; gg = G_VAL(pix) * XAP; bb = B_VAL(pix) * XAP; aa = A_VAL(pix) * XAP; --pix; rr += R_VAL(pix) * INV_XAP; gg += G_VAL(pix) * INV_XAP; bb += B_VAL(pix) * INV_XAP; aa += A_VAL(pix) * INV_XAP; r = ((rr * YAP) + (r * INV_YAP)) >> 16; g = ((gg * YAP) + (g * INV_YAP)) >> 16; b = ((bb * YAP) + (b * INV_YAP)) >> 16; a = ((aa * YAP) + (a * INV_YAP)) >> 16; A_VAL(dptr) = a; R_VAL(dptr) = r; G_VAL(dptr) = g; B_VAL(dptr) = b; ++dptr; } else { pix = ypoints[dyy + y] + xpoints[x]; r = R_VAL(pix) * INV_YAP; g = G_VAL(pix) * INV_YAP; b = B_VAL(pix) * INV_YAP; a = A_VAL(pix) * INV_YAP; pix += sow; r += R_VAL(pix) * YAP; g += G_VAL(pix) * YAP; b += B_VAL(pix) * YAP; a += A_VAL(pix) * YAP; r >>= 8; g >>= 8; b >>= 8; a >>= 8; A_VAL(dptr) = a; R_VAL(dptr) = r; G_VAL(dptr) = g; B_VAL(dptr) = b; ++dptr; } } } else { for (x = x_begin; x < x_end; ++x) { uint* pix=0; if (XAP > 0) { int r, g, b, a; pix = ypoints[dyy + y] + xpoints[x]; r = R_VAL(pix) * INV_XAP; g = G_VAL(pix) * INV_XAP; b = B_VAL(pix) * INV_XAP; a = A_VAL(pix) * INV_XAP; ++pix; r += R_VAL(pix) * XAP; g += G_VAL(pix) * XAP; b += B_VAL(pix) * XAP; a += A_VAL(pix) * XAP; r >>= 8; g >>= 8; b >>= 8; a >>= 8; A_VAL(dptr) = a; R_VAL(dptr) = r; G_VAL(dptr) = g; B_VAL(dptr) = b; ++dptr; } else { *dptr++ = sptr[xpoints[x] ]; } } } } } /* if we're scaling down vertically */ else if (isi->xup_yup == 1) { /*\ 'Correct' version, with math units prepared for MMXification \*/ int Cy, j; uint* pix=0; int r, g, b, a, rr, gg, bb, aa; int yap; /* go through every scanline in the output buffer */ for (y = y_begin; y < y_end; ++y) { Cy = YAP >> 16; yap = YAP & 0xffff; dptr = dest + (y - y_begin) * dow; for (x = x_begin; x < x_end; ++x) { pix = ypoints[dyy + y] + xpoints[x]; r = (R_VAL(pix) * yap) >> 10; g = (G_VAL(pix) * yap) >> 10; b = (B_VAL(pix) * yap) >> 10; a = (A_VAL(pix) * yap) >> 10; for (j = (1 << 14) - yap; j > Cy; j -= Cy) { pix += sow; r += (R_VAL(pix) * Cy) >> 10; g += (G_VAL(pix) * Cy) >> 10; b += (B_VAL(pix) * Cy) >> 10; a += (A_VAL(pix) * Cy) >> 10; } if (j > 0) { pix += sow; r += (R_VAL(pix) * j) >> 10; g += (G_VAL(pix) * j) >> 10; b += (B_VAL(pix) * j) >> 10; a += (A_VAL(pix) * j) >> 10; } if (XAP > 0) { pix = ypoints[dyy + y] + xpoints[x] + 1; rr = (R_VAL(pix) * yap) >> 10; gg = (G_VAL(pix) * yap) >> 10; bb = (B_VAL(pix) * yap) >> 10; aa = (A_VAL(pix) * yap) >> 10; for (j = (1 << 14) - yap; j > Cy; j -= Cy) { pix += sow; rr += (R_VAL(pix) * Cy) >> 10; gg += (G_VAL(pix) * Cy) >> 10; bb += (B_VAL(pix) * Cy) >> 10; aa += (A_VAL(pix) * Cy) >> 10; } if (j > 0) { pix += sow; rr += (R_VAL(pix) * j) >> 10; gg += (G_VAL(pix) * j) >> 10; bb += (B_VAL(pix) * j) >> 10; aa += (A_VAL(pix) * j) >> 10; } r = r * INV_XAP; g = g * INV_XAP; b = b * INV_XAP; a = a * INV_XAP; r = (r + ((rr * XAP))) >> 12; g = (g + ((gg * XAP))) >> 12; b = (b + ((bb * XAP))) >> 12; a = (a + ((aa * XAP))) >> 12; } else { r >>= 4; g >>= 4; b >>= 4; a >>= 4; } A_VAL(dptr) = a; R_VAL(dptr) = r; G_VAL(dptr) = g; B_VAL(dptr) = b; ++dptr; } } } /* if we're scaling down horizontally */ else if (isi->xup_yup == 2) { /*\ 'Correct' version, with math units prepared for MMXification \*/ int Cx, j; uint* pix=0; int r, g, b, a, rr, gg, bb, aa; int xap; /* go through every scanline in the output buffer */ for (y = y_begin; y < y_end; ++y) { dptr = dest + (y - y_begin) * dow; for (x = x_begin; x < x_end; ++x) { Cx = XAP >> 16; xap = XAP & 0xffff; pix = ypoints[dyy + y] + xpoints[x]; r = (R_VAL(pix) * xap) >> 10; g = (G_VAL(pix) * xap) >> 10; b = (B_VAL(pix) * xap) >> 10; a = (A_VAL(pix) * xap) >> 10; for (j = (1 << 14) - xap; j > Cx; j -= Cx) { ++pix; r += (R_VAL(pix) * Cx) >> 10; g += (G_VAL(pix) * Cx) >> 10; b += (B_VAL(pix) * Cx) >> 10; a += (A_VAL(pix) * Cx) >> 10; } if (j > 0) { ++pix; r += (R_VAL(pix) * j) >> 10; g += (G_VAL(pix) * j) >> 10; b += (B_VAL(pix) * j) >> 10; a += (A_VAL(pix) * j) >> 10; } if (YAP > 0) { pix = ypoints[dyy + y] + xpoints[x] + sow; rr = (R_VAL(pix) * xap) >> 10; gg = (G_VAL(pix) * xap) >> 10; bb = (B_VAL(pix) * xap) >> 10; aa = (A_VAL(pix) * xap) >> 10; for (j = (1 << 14) - xap; j > Cx; j -= Cx) { ++pix; rr += (R_VAL(pix) * Cx) >> 10; gg += (G_VAL(pix) * Cx) >> 10; bb += (B_VAL(pix) * Cx) >> 10; aa += (A_VAL(pix) * Cx) >> 10; } if (j > 0) { ++pix; rr += (R_VAL(pix) * j) >> 10; gg += (G_VAL(pix) * j) >> 10; bb += (B_VAL(pix) * j) >> 10; aa += (A_VAL(pix) * j) >> 10; } r = r * INV_YAP; g = g * INV_YAP; b = b * INV_YAP; a = a * INV_YAP; r = (r + ((rr * YAP))) >> 12; g = (g + ((gg * YAP))) >> 12; b = (b + ((bb * YAP))) >> 12; a = (a + ((aa * YAP))) >> 12; } else { r >>= 4; g >>= 4; b >>= 4; a >>= 4; } A_VAL(dptr) = a; R_VAL(dptr) = r; G_VAL(dptr) = g; B_VAL(dptr) = b; ++dptr; } } } /* if we're scaling down horizontally & vertically */ else { /*\ 'Correct' version, with math units prepared for MMXification: |*| The operation 'b = (b * c) >> 16' translates to pmulhw, |*| so the operation 'b = (b * c) >> d' would translate to |*| psllw (16 - d), %mmb; pmulh %mmc, %mmb \*/ int Cx, Cy, i, j; uint* pix=0; int a, r, g, b, ax, rx, gx, bx; int xap, yap; for (y = y_begin; y < y_end; ++y) { Cy = YAP >> 16; yap = YAP & 0xffff; dptr = dest + (y - y_begin) * dow; for (x = x_begin; x < x_end; ++x) { Cx = XAP >> 16; xap = XAP & 0xffff; sptr = ypoints[dyy + y] + xpoints[x]; pix = sptr; sptr += sow; rx = (R_VAL(pix) * xap) >> 9; gx = (G_VAL(pix) * xap) >> 9; bx = (B_VAL(pix) * xap) >> 9; ax = (A_VAL(pix) * xap) >> 9; ++pix; for (i = (1 << 14) - xap; i > Cx; i -= Cx) { rx += (R_VAL(pix) * Cx) >> 9; gx += (G_VAL(pix) * Cx) >> 9; bx += (B_VAL(pix) * Cx) >> 9; ax += (A_VAL(pix) * Cx) >> 9; ++pix; } if (i > 0) { rx += (R_VAL(pix) * i) >> 9; gx += (G_VAL(pix) * i) >> 9; bx += (B_VAL(pix) * i) >> 9; ax += (A_VAL(pix) * i) >> 9; } r = (rx * yap) >> 14; g = (gx * yap) >> 14; b = (bx * yap) >> 14; a = (ax * yap) >> 14; for (j = (1 << 14) - yap; j > Cy; j -= Cy) { pix = sptr; sptr += sow; rx = (R_VAL(pix) * xap) >> 9; gx = (G_VAL(pix) * xap) >> 9; bx = (B_VAL(pix) * xap) >> 9; ax = (A_VAL(pix) * xap) >> 9; ++pix; for (i = (1 << 14) - xap; i > Cx; i -= Cx) { rx += (R_VAL(pix) * Cx) >> 9; gx += (G_VAL(pix) * Cx) >> 9; bx += (B_VAL(pix) * Cx) >> 9; ax += (A_VAL(pix) * Cx) >> 9; ++pix; } if (i > 0) { rx += (R_VAL(pix) * i) >> 9; gx += (G_VAL(pix) * i) >> 9; bx += (B_VAL(pix) * i) >> 9; ax += (A_VAL(pix) * i) >> 9; } r += (rx * Cy) >> 14; g += (gx * Cy) >> 14; b += (bx * Cy) >> 14; a += (ax * Cy) >> 14; } if (j > 0) { pix = sptr; sptr += sow; (void)sptr; // disable clang warning. rx = (R_VAL(pix) * xap) >> 9; gx = (G_VAL(pix) * xap) >> 9; bx = (B_VAL(pix) * xap) >> 9; ax = (A_VAL(pix) * xap) >> 9; ++pix; for (i = (1 << 14) - xap; i > Cx; i -= Cx) { rx += (R_VAL(pix) * Cx) >> 9; gx += (G_VAL(pix) * Cx) >> 9; bx += (B_VAL(pix) * Cx) >> 9; ax += (A_VAL(pix) * Cx) >> 9; ++pix; } if (i > 0) { rx += (R_VAL(pix) * i) >> 9; gx += (G_VAL(pix) * i) >> 9; bx += (B_VAL(pix) * i) >> 9; ax += (A_VAL(pix) * i) >> 9; } r += (rx * j) >> 14; g += (gx * j) >> 14; b += (bx * j) >> 14; a += (ax * j) >> 14; } R_VAL(dptr) = r >> 5; G_VAL(dptr) = g >> 5; B_VAL(dptr) = b >> 5; A_VAL(dptr) = a >> 5; ++dptr; } } } } void DImgScale::dimgScaleAARGB(DImgScaleInfo* const isi, uint* const dest, int dxx, int dyy, int dw, int dh, int dow, int sow ) { dimgScaleAARGB(isi, dest, dxx, dyy, dw, dh, dow, sow, 0, 0, dw, dh); } /** scale by area sampling - IGNORE the ALPHA byte */ void DImgScale::dimgScaleAARGB(DImgScaleInfo* const isi, uint* const dest, int dxx, int dyy, int dw, int dh, int dow, int sow, int clip_dx, int clip_dy, int clip_dw, int clip_dh) { Q_UNUSED(dw); Q_UNUSED(dh); uint* sptr=0; uint* dptr=0; int x, y; uint** ypoints = isi->ypoints; int* xpoints = isi->xpoints; int* xapoints = isi->xapoints; int* yapoints = isi->yapoints; const int x_begin = dxx + clip_dx; // no clip set = dxx const int x_end = x_begin + clip_dw; // no clip set = dxx + dw const int y_begin = clip_dy; // no clip set = 0 const int y_end = clip_dy + clip_dh; // no clip set = dh /* scaling up both ways */ if (isi->xup_yup == 3) { /* go through every scanline in the output buffer */ for (y = y_begin; y < y_end; ++y) { /* calculate the source line we'll scan from */ dptr = dest + (y - y_begin) * dow; sptr = ypoints[dyy + y]; if (YAP > 0) { for (x = x_begin; x < x_end; ++x) { int r = 0, g = 0, b = 0; uint* pix=0; if (XAP > 0) { int rr = 0, gg = 0, bb = 0; pix = ypoints[dyy + y] + xpoints[x]; r = R_VAL(pix) * INV_XAP; g = G_VAL(pix) * INV_XAP; b = B_VAL(pix) * INV_XAP; ++pix; r += R_VAL(pix) * XAP; g += G_VAL(pix) * XAP; b += B_VAL(pix) * XAP; pix += sow; rr = R_VAL(pix) * XAP; gg = G_VAL(pix) * XAP; bb = B_VAL(pix) * XAP; pix--; rr += R_VAL(pix) * INV_XAP; gg += G_VAL(pix) * INV_XAP; bb += B_VAL(pix) * INV_XAP; r = ((rr * YAP) + (r * INV_YAP)) >> 16; g = ((gg * YAP) + (g * INV_YAP)) >> 16; b = ((bb * YAP) + (b * INV_YAP)) >> 16; R_VAL(dptr) = r; G_VAL(dptr) = g; B_VAL(dptr) = b; A_VAL(dptr) = 0xFF; ++dptr; } else { pix = ypoints[dyy + y] + xpoints[x]; r = R_VAL(pix) * INV_YAP; g = G_VAL(pix) * INV_YAP; b = B_VAL(pix) * INV_YAP; pix += sow; r += R_VAL(pix) * YAP; g += G_VAL(pix) * YAP; b += B_VAL(pix) * YAP; r >>= 8; g >>= 8; b >>= 8; R_VAL(dptr) = r; G_VAL(dptr) = g; B_VAL(dptr) = b; A_VAL(dptr) = 0xFF; ++dptr; } } } else { for (x = x_begin; x < x_end; ++x) { uint* pix=0; if (XAP > 0) { int r = 0, g = 0, b = 0; pix = ypoints[dyy + y] + xpoints[x]; r = R_VAL(pix) * INV_XAP; g = G_VAL(pix) * INV_XAP; b = B_VAL(pix) * INV_XAP; ++pix; r += R_VAL(pix) * XAP; g += G_VAL(pix) * XAP; b += B_VAL(pix) * XAP; r >>= 8; g >>= 8; b >>= 8; R_VAL(dptr) = r; G_VAL(dptr) = g; B_VAL(dptr) = b; A_VAL(dptr) = 0xFF; ++dptr; } else { *dptr++ = sptr[xpoints[x] ]; } } } } } /* if we're scaling down vertically */ else if (isi->xup_yup == 1) { /*\ 'Correct' version, with math units prepared for MMXification \*/ int Cy, j; uint* pix=0; int r, g, b, rr, gg, bb; int yap; /* go through every scanline in the output buffer */ for (y = y_begin; y < y_end; ++y) { Cy = YAP >> 16; yap = YAP & 0xffff; dptr = dest + (y - y_begin) * dow; for (x = x_begin; x < x_end; ++x) { pix = ypoints[dyy + y] + xpoints[x]; r = (R_VAL(pix) * yap) >> 10; g = (G_VAL(pix) * yap) >> 10; b = (B_VAL(pix) * yap) >> 10; pix += sow; for (j = (1 << 14) - yap; j > Cy; j -= Cy) { r += (R_VAL(pix) * Cy) >> 10; g += (G_VAL(pix) * Cy) >> 10; b += (B_VAL(pix) * Cy) >> 10; pix += sow; } if (j > 0) { r += (R_VAL(pix) * j) >> 10; g += (G_VAL(pix) * j) >> 10; b += (B_VAL(pix) * j) >> 10; } if (XAP > 0) { pix = ypoints[dyy + y] + xpoints[x] + 1; rr = (R_VAL(pix) * yap) >> 10; gg = (G_VAL(pix) * yap) >> 10; bb = (B_VAL(pix) * yap) >> 10; pix += sow; for (j = (1 << 14) - yap; j > Cy; j -= Cy) { rr += (R_VAL(pix) * Cy) >> 10; gg += (G_VAL(pix) * Cy) >> 10; bb += (B_VAL(pix) * Cy) >> 10; pix += sow; } if (j > 0) { rr += (R_VAL(pix) * j) >> 10; gg += (G_VAL(pix) * j) >> 10; bb += (B_VAL(pix) * j) >> 10; } r = r * INV_XAP; g = g * INV_XAP; b = b * INV_XAP; r = (r + ((rr * XAP))) >> 12; g = (g + ((gg * XAP))) >> 12; b = (b + ((bb * XAP))) >> 12; } else { r >>= 4; g >>= 4; b >>= 4; } R_VAL(dptr) = r; G_VAL(dptr) = g; B_VAL(dptr) = b; A_VAL(dptr) = 0xFF; ++dptr; } } } /* if we're scaling down horizontally */ else if (isi->xup_yup == 2) { /*\ 'Correct' version, with math units prepared for MMXification \*/ int Cx, j; uint* pix=0; int r, g, b, rr, gg, bb; int xap; /* go through every scanline in the output buffer */ for (y = y_begin; y < y_end; ++y) { dptr = dest + (y - y_begin) * dow; for (x = x_begin; x < x_end; ++x) { Cx = XAP >> 16; xap = XAP & 0xffff; pix = ypoints[dyy + y] + xpoints[x]; r = (R_VAL(pix) * xap) >> 10; g = (G_VAL(pix) * xap) >> 10; b = (B_VAL(pix) * xap) >> 10; ++pix; for (j = (1 << 14) - xap; j > Cx; j -= Cx) { r += (R_VAL(pix) * Cx) >> 10; g += (G_VAL(pix) * Cx) >> 10; b += (B_VAL(pix) * Cx) >> 10; ++pix; } if (j > 0) { r += (R_VAL(pix) * j) >> 10; g += (G_VAL(pix) * j) >> 10; b += (B_VAL(pix) * j) >> 10; } if (YAP > 0) { pix = ypoints[dyy + y] + xpoints[x] + sow; rr = (R_VAL(pix) * xap) >> 10; gg = (G_VAL(pix) * xap) >> 10; bb = (B_VAL(pix) * xap) >> 10; ++pix; for (j = (1 << 14) - xap; j > Cx; j -= Cx) { rr += (R_VAL(pix) * Cx) >> 10; gg += (G_VAL(pix) * Cx) >> 10; bb += (B_VAL(pix) * Cx) >> 10; ++pix; } if (j > 0) { rr += (R_VAL(pix) * j) >> 10; gg += (G_VAL(pix) * j) >> 10; bb += (B_VAL(pix) * j) >> 10; } r = r * INV_YAP; g = g * INV_YAP; b = b * INV_YAP; r = (r + ((rr * YAP))) >> 12; g = (g + ((gg * YAP))) >> 12; b = (b + ((bb * YAP))) >> 12; } else { r >>= 4; g >>= 4; b >>= 4; } R_VAL(dptr) = r; G_VAL(dptr) = g; B_VAL(dptr) = b; A_VAL(dptr) = 0xFF; ++dptr; } } } /* fully optimized (i think) - only change of algorithm can help */ /* if we're scaling down horizontally & vertically */ else { /*\ 'Correct' version, with math units prepared for MMXification \*/ int Cx, Cy, i, j; uint* pix=0; int r, g, b, rx, gx, bx; int xap, yap; for (y = y_begin; y < y_end; ++y) { Cy = YAP >> 16; yap = YAP & 0xffff; dptr = dest + (y - y_begin) * dow; for (x = x_begin; x < x_end; ++x) { Cx = XAP >> 16; xap = XAP & 0xffff; sptr = ypoints[dyy + y] + xpoints[x]; pix = sptr; sptr += sow; rx = (R_VAL(pix) * xap) >> 9; gx = (G_VAL(pix) * xap) >> 9; bx = (B_VAL(pix) * xap) >> 9; ++pix; for (i = (1 << 14) - xap; i > Cx; i -= Cx) { rx += (R_VAL(pix) * Cx) >> 9; gx += (G_VAL(pix) * Cx) >> 9; bx += (B_VAL(pix) * Cx) >> 9; ++pix; } if (i > 0) { rx += (R_VAL(pix) * i) >> 9; gx += (G_VAL(pix) * i) >> 9; bx += (B_VAL(pix) * i) >> 9; } r = (rx * yap) >> 14; g = (gx * yap) >> 14; b = (bx * yap) >> 14; for (j = (1 << 14) - yap; j > Cy; j -= Cy) { pix = sptr; sptr += sow; rx = (R_VAL(pix) * xap) >> 9; gx = (G_VAL(pix) * xap) >> 9; bx = (B_VAL(pix) * xap) >> 9; ++pix; for (i = (1 << 14) - xap; i > Cx; i -= Cx) { rx += (R_VAL(pix) * Cx) >> 9; gx += (G_VAL(pix) * Cx) >> 9; bx += (B_VAL(pix) * Cx) >> 9; ++pix; } if (i > 0) { rx += (R_VAL(pix) * i) >> 9; gx += (G_VAL(pix) * i) >> 9; bx += (B_VAL(pix) * i) >> 9; } r += (rx * Cy) >> 14; g += (gx * Cy) >> 14; b += (bx * Cy) >> 14; } if (j > 0) { pix = sptr; sptr += sow; (void)sptr; // disable clang warning. rx = (R_VAL(pix) * xap) >> 9; gx = (G_VAL(pix) * xap) >> 9; bx = (B_VAL(pix) * xap) >> 9; ++pix; for (i = (1 << 14) - xap; i > Cx; i -= Cx) { rx += (R_VAL(pix) * Cx) >> 9; gx += (G_VAL(pix) * Cx) >> 9; bx += (B_VAL(pix) * Cx) >> 9; ++pix; } if (i > 0) { rx += (R_VAL(pix) * i) >> 9; gx += (G_VAL(pix) * i) >> 9; bx += (B_VAL(pix) * i) >> 9; } r += (rx * j) >> 14; g += (gx * j) >> 14; b += (bx * j) >> 14; } R_VAL(dptr) = r >> 5; G_VAL(dptr) = g >> 5; B_VAL(dptr) = b >> 5; A_VAL(dptr) = 0xFF; ++dptr; } } } } #define A_VAL16(p) ((ushort*)(p))[3] #define R_VAL16(p) ((ushort*)(p))[2] #define G_VAL16(p) ((ushort*)(p))[1] #define B_VAL16(p) ((ushort*)(p))[0] void DImgScale::dimgScaleAARGB16(DImgScaleInfo* const isi, ullong* const dest, int dxx, int dyy, int dw, int dh, int dow, int sow) { dimgScaleAARGB16(isi, dest, dxx, dyy, dw, dh, dow, sow, 0, 0, dw, dh); } /** scale by area sampling - IGNORE the ALPHA byte*/ void DImgScale::dimgScaleAARGB16(DImgScaleInfo* const isi, ullong* const dest, int dxx, int dyy, int dw, int dh, int dow, int sow, int clip_dx, int clip_dy, int clip_dw, int clip_dh) { Q_UNUSED(dw); Q_UNUSED(dh); ullong* sptr=0; ullong* dptr=0; int x, y; ullong** ypoints = isi->ypoints16; int* xpoints = isi->xpoints; int* xapoints = isi->xapoints; int* yapoints = isi->yapoints; const int x_begin = dxx + clip_dx; // no clip set = dxx const int x_end = x_begin + clip_dw; // no clip set = dxx + dw const int y_begin = clip_dy; // no clip set = 0 const int y_end = clip_dy + clip_dh; // no clip set = dh // scaling up both ways if (isi->xup_yup == 3) { // go through every scanline in the output buffer for (y = y_begin; y < y_end; ++y) { // calculate the source line we'll scan from dptr = dest + (y - y_begin) * dow; sptr = ypoints[dyy + y]; if (YAP > 0) { for (x = x_begin; x < x_end; ++x) { llong r = 0, g = 0, b = 0; ullong* pix=0; if (XAP > 0) { llong rr = 0, gg = 0, bb = 0; pix = ypoints[dyy + y] + xpoints[x]; r = R_VAL16(pix) * INV_XAP; g = G_VAL16(pix) * INV_XAP; b = B_VAL16(pix) * INV_XAP; ++pix; r += R_VAL16(pix) * XAP; g += G_VAL16(pix) * XAP; b += B_VAL16(pix) * XAP; pix += sow; rr = R_VAL16(pix) * XAP; gg = G_VAL16(pix) * XAP; bb = B_VAL16(pix) * XAP; pix--; rr += R_VAL16(pix) * INV_XAP; gg += G_VAL16(pix) * INV_XAP; bb += B_VAL16(pix) * INV_XAP; r = ((rr * YAP) + (r * INV_YAP)) >> 16; g = ((gg * YAP) + (g * INV_YAP)) >> 16; b = ((bb * YAP) + (b * INV_YAP)) >> 16; R_VAL16(dptr) = r; G_VAL16(dptr) = g; B_VAL16(dptr) = b; A_VAL16(dptr) = 0xFFFF; ++dptr; } else { pix = ypoints[dyy + y] + xpoints[x]; r = R_VAL16(pix) * INV_YAP; g = G_VAL16(pix) * INV_YAP; b = B_VAL16(pix) * INV_YAP; pix += sow; r += R_VAL16(pix) * YAP; g += G_VAL16(pix) * YAP; b += B_VAL16(pix) * YAP; r >>= 8; g >>= 8; b >>= 8; R_VAL16(dptr) = r; G_VAL16(dptr) = g; B_VAL16(dptr) = b; A_VAL16(dptr) = 0xFFFF; ++dptr; } } } else { for (x = x_begin; x < x_end; ++x) { ullong* pix=0; if (XAP > 0) { llong r = 0, g = 0, b = 0; pix = ypoints[dyy + y] + xpoints[x]; r = R_VAL16(pix) * INV_XAP; g = G_VAL16(pix) * INV_XAP; b = B_VAL16(pix) * INV_XAP; ++pix; r += R_VAL16(pix) * XAP; g += G_VAL16(pix) * XAP; b += B_VAL16(pix) * XAP; r >>= 8; g >>= 8; b >>= 8; R_VAL16(dptr) = r; G_VAL16(dptr) = g; B_VAL16(dptr) = b; A_VAL16(dptr) = 0xFFFF; ++dptr; } else { *dptr++ = sptr[xpoints[x] ]; } } } } } // if we're scaling down vertically else if (isi->xup_yup == 1) { // 'Correct' version, with math units prepared for MMXification int Cy, j; ullong* pix=0; llong r, g, b, rr, gg, bb; int yap; // go through every scanline in the output buffer for (y = y_begin; y < y_end; ++y) { Cy = YAP >> 16; yap = YAP & 0xffff; dptr = dest + (y - y_begin) * dow; for (x = x_begin; x < x_end; ++x) { pix = ypoints[dyy + y] + xpoints[x]; r = (R_VAL16(pix) * yap) >> 10; g = (G_VAL16(pix) * yap) >> 10; b = (B_VAL16(pix) * yap) >> 10; pix += sow; for (j = (1 << 14) - yap; j > Cy; j -= Cy) { r += (R_VAL16(pix) * Cy) >> 10; g += (G_VAL16(pix) * Cy) >> 10; b += (B_VAL16(pix) * Cy) >> 10; pix += sow; } if (j > 0) { r += (R_VAL16(pix) * j) >> 10; g += (G_VAL16(pix) * j) >> 10; b += (B_VAL16(pix) * j) >> 10; } if (XAP > 0) { pix = ypoints[dyy + y] + xpoints[x] + 1; rr = (R_VAL16(pix) * yap) >> 10; gg = (G_VAL16(pix) * yap) >> 10; bb = (B_VAL16(pix) * yap) >> 10; pix += sow; for (j = (1 << 14) - yap; j > Cy; j -= Cy) { rr += (R_VAL16(pix) * Cy) >> 10; gg += (G_VAL16(pix) * Cy) >> 10; bb += (B_VAL16(pix) * Cy) >> 10; pix += sow; } if (j > 0) { rr += (R_VAL16(pix) * j) >> 10; gg += (G_VAL16(pix) * j) >> 10; bb += (B_VAL16(pix) * j) >> 10; } r = r * INV_XAP; g = g * INV_XAP; b = b * INV_XAP; r = (r + ((rr * XAP))) >> 12; g = (g + ((gg * XAP))) >> 12; b = (b + ((bb * XAP))) >> 12; } else { r >>= 4; g >>= 4; b >>= 4; } R_VAL16(dptr) = r; G_VAL16(dptr) = g; B_VAL16(dptr) = b; A_VAL16(dptr) = 0xFFFF; ++dptr; } } } // if we're scaling down horizontally else if (isi->xup_yup == 2) { // 'Correct' version, with math units prepared for MMXification int Cx, j; ullong* pix=0; llong r, g, b, rr, gg, bb; int xap; // go through every scanline in the output buffer for (y = y_begin; y < y_end; ++y) { dptr = dest + (y - y_begin) * dow; for (x = x_begin; x < x_end; ++x) { Cx = XAP >> 16; xap = XAP & 0xffff; pix = ypoints[dyy + y] + xpoints[x]; r = (R_VAL16(pix) * xap) >> 10; g = (G_VAL16(pix) * xap) >> 10; b = (B_VAL16(pix) * xap) >> 10; ++pix; for (j = (1 << 14) - xap; j > Cx; j -= Cx) { r += (R_VAL16(pix) * Cx) >> 10; g += (G_VAL16(pix) * Cx) >> 10; b += (B_VAL16(pix) * Cx) >> 10; ++pix; } if (j > 0) { r += (R_VAL16(pix) * j) >> 10; g += (G_VAL16(pix) * j) >> 10; b += (B_VAL16(pix) * j) >> 10; } if (YAP > 0) { pix = ypoints[dyy + y] + xpoints[x] + sow; rr = (R_VAL16(pix) * xap) >> 10; gg = (G_VAL16(pix) * xap) >> 10; bb = (B_VAL16(pix) * xap) >> 10; ++pix; for (j = (1 << 14) - xap; j > Cx; j -= Cx) { rr += (R_VAL16(pix) * Cx) >> 10; gg += (G_VAL16(pix) * Cx) >> 10; bb += (B_VAL16(pix) * Cx) >> 10; ++pix; } if (j > 0) { rr += (R_VAL16(pix) * j) >> 10; gg += (G_VAL16(pix) * j) >> 10; bb += (B_VAL16(pix) * j) >> 10; } r = r * INV_YAP; g = g * INV_YAP; b = b * INV_YAP; r = (r + ((rr * YAP))) >> 12; g = (g + ((gg * YAP))) >> 12; b = (b + ((bb * YAP))) >> 12; } else { r >>= 4; g >>= 4; b >>= 4; } R_VAL16(dptr) = r; G_VAL16(dptr) = g; B_VAL16(dptr) = b; A_VAL16(dptr) = 0xFFFF; ++dptr; } } } // fully optimized (i think) - only change of algorithm can help // if we're scaling down horizontally & vertically else { // 'Correct' version, with math units prepared for MMXification int Cx, Cy, i, j; ullong* pix=0; llong r, g, b, rx, gx, bx; int xap, yap; for (y = y_begin; y < y_end; ++y) { Cy = YAP >> 16; yap = YAP & 0xffff; dptr = dest + (y - y_begin) * dow; for (x = x_begin; x < x_end; ++x) { Cx = XAP >> 16; xap = XAP & 0xffff; sptr = ypoints[dyy + y] + xpoints[x]; pix = sptr; sptr += sow; rx = (R_VAL16(pix) * xap) >> 9; gx = (G_VAL16(pix) * xap) >> 9; bx = (B_VAL16(pix) * xap) >> 9; ++pix; for (i = (1 << 14) - xap; i > Cx; i -= Cx) { rx += (R_VAL16(pix) * Cx) >> 9; gx += (G_VAL16(pix) * Cx) >> 9; bx += (B_VAL16(pix) * Cx) >> 9; ++pix; } if (i > 0) { rx += (R_VAL16(pix) * i) >> 9; gx += (G_VAL16(pix) * i) >> 9; bx += (B_VAL16(pix) * i) >> 9; } r = (rx * yap) >> 14; g = (gx * yap) >> 14; b = (bx * yap) >> 14; for (j = (1 << 14) - yap; j > Cy; j -= Cy) { pix = sptr; sptr += sow; rx = (R_VAL16(pix) * xap) >> 9; gx = (G_VAL16(pix) * xap) >> 9; bx = (B_VAL16(pix) * xap) >> 9; ++pix; for (i = (1 << 14) - xap; i > Cx; i -= Cx) { rx += (R_VAL16(pix) * Cx) >> 9; gx += (G_VAL16(pix) * Cx) >> 9; bx += (B_VAL16(pix) * Cx) >> 9; ++pix; } if (i > 0) { rx += (R_VAL16(pix) * i) >> 9; gx += (G_VAL16(pix) * i) >> 9; bx += (B_VAL16(pix) * i) >> 9; } r += (rx * Cy) >> 14; g += (gx * Cy) >> 14; b += (bx * Cy) >> 14; } if (j > 0) { pix = sptr; sptr += sow; (void)sptr; // disable clang warning. rx = (R_VAL16(pix) * xap) >> 9; gx = (G_VAL16(pix) * xap) >> 9; bx = (B_VAL16(pix) * xap) >> 9; ++pix; for (i = (1 << 14) - xap; i > Cx; i -= Cx) { rx += (R_VAL16(pix) * Cx) >> 9; gx += (G_VAL16(pix) * Cx) >> 9; bx += (B_VAL16(pix) * Cx) >> 9; ++pix; } if (i > 0) { rx += (R_VAL16(pix) * i) >> 9; gx += (G_VAL16(pix) * i) >> 9; bx += (B_VAL16(pix) * i) >> 9; } r += (rx * j) >> 14; g += (gx * j) >> 14; b += (bx * j) >> 14; } R_VAL16(dptr) = r >> 5; G_VAL16(dptr) = g >> 5; B_VAL16(dptr) = b >> 5; A_VAL16(dptr) = 0xFFFF; ++dptr; } } } } void DImgScale::dimgScaleAARGBA16(DImgScaleInfo* const isi, ullong* const dest, int dxx, int dyy, int dw, int dh, int dow, int sow ) { dimgScaleAARGBA16(isi, dest, dxx, dyy, dw, dh, dow, sow, 0, 0, dw, dh); } /* scale by area sampling */ void DImgScale::dimgScaleAARGBA16(DImgScaleInfo* const isi, ullong* const dest, int dxx, int dyy, int dw, int dh, int dow, int sow, int clip_dx, int clip_dy, int clip_dw, int clip_dh) { Q_UNUSED(dw); Q_UNUSED(dh); ullong* sptr=0; ullong* dptr=0; int x, y; ullong** ypoints = isi->ypoints16; int* xpoints = isi->xpoints; int* xapoints = isi->xapoints; int* yapoints = isi->yapoints; const int x_begin = dxx + clip_dx; // no clip set = dxx const int x_end = x_begin + clip_dw; // no clip set = dxx + dw const int y_begin = clip_dy; // no clip set = 0 const int y_end = clip_dy + clip_dh; // no clip set = dh /* scaling up both ways */ if (isi->xup_yup == 3) { /* go through every scanline in the output buffer */ for (y = y_begin; y < y_end; ++y) { /* calculate the source line we'll scan from */ dptr = dest + (y - y_begin) * dow; sptr = ypoints[dyy + y]; if (YAP > 0) { for (x = x_begin; x < x_end; ++x) { llong r, g, b, a; llong rr, gg, bb, aa; ullong* pix=0; if (XAP > 0) { pix = ypoints[dyy + y] + xpoints[x]; r = R_VAL16(pix) * INV_XAP; g = G_VAL16(pix) * INV_XAP; b = B_VAL16(pix) * INV_XAP; a = A_VAL16(pix) * INV_XAP; ++pix; r += R_VAL16(pix) * XAP; g += G_VAL16(pix) * XAP; b += B_VAL16(pix) * XAP; a += A_VAL16(pix) * XAP; pix += sow; rr = R_VAL16(pix) * XAP; gg = G_VAL16(pix) * XAP; bb = B_VAL16(pix) * XAP; aa = A_VAL16(pix) * XAP; --pix; rr += R_VAL16(pix) * INV_XAP; gg += G_VAL16(pix) * INV_XAP; bb += B_VAL16(pix) * INV_XAP; aa += A_VAL16(pix) * INV_XAP; r = ((rr * YAP) + (r * INV_YAP)) >> 16; g = ((gg * YAP) + (g * INV_YAP)) >> 16; b = ((bb * YAP) + (b * INV_YAP)) >> 16; a = ((aa * YAP) + (a * INV_YAP)) >> 16; R_VAL16(dptr) = r; G_VAL16(dptr) = g; B_VAL16(dptr) = b; A_VAL16(dptr) = a; ++dptr; } else { pix = ypoints[dyy + y] + xpoints[x]; r = R_VAL16(pix) * INV_YAP; g = G_VAL16(pix) * INV_YAP; b = B_VAL16(pix) * INV_YAP; a = A_VAL16(pix) * INV_YAP; pix += sow; r += R_VAL16(pix) * YAP; g += G_VAL16(pix) * YAP; b += B_VAL16(pix) * YAP; a += A_VAL16(pix) * YAP; r >>= 8; g >>= 8; b >>= 8; a >>= 8; R_VAL16(dptr) = r; G_VAL16(dptr) = g; B_VAL16(dptr) = b; A_VAL16(dptr) = a; ++dptr; } } } else { for (x = x_begin; x < x_end; ++x) { llong r, g, b, a; ullong* pix=0; if (XAP > 0) { pix = ypoints[dyy + y] + xpoints[x]; r = R_VAL16(pix) * INV_XAP; g = G_VAL16(pix) * INV_XAP; b = B_VAL16(pix) * INV_XAP; a = A_VAL16(pix) * INV_XAP; ++pix; r += R_VAL16(pix) * XAP; g += G_VAL16(pix) * XAP; b += B_VAL16(pix) * XAP; a += A_VAL16(pix) * XAP; r >>= 8; g >>= 8; b >>= 8; a >>= 8; R_VAL16(dptr) = r; G_VAL16(dptr) = g; B_VAL16(dptr) = b; A_VAL16(dptr) = a; ++dptr; } else { *dptr++ = sptr[xpoints[x] ]; } } } } } /* if we're scaling down vertically */ else if (isi->xup_yup == 1) { /*\ 'Correct' version, with math units prepared for MMXification \*/ int Cy, j; ullong* pix=0; llong r, g, b, a, rr, gg, bb, aa; int yap; /* go through every scanline in the output buffer */ for (y = y_begin; y < y_end; ++y) { Cy = YAP >> 16; yap = YAP & 0xffff; dptr = dest + (y - y_begin) * dow; for (x = x_begin; x < x_end; ++x) { pix = ypoints[dyy + y] + xpoints[x]; r = (R_VAL16(pix) * yap) >> 10; g = (G_VAL16(pix) * yap) >> 10; b = (B_VAL16(pix) * yap) >> 10; a = (A_VAL16(pix) * yap) >> 10; for (j = (1 << 14) - yap; j > Cy; j -= Cy) { pix += sow; r += (R_VAL16(pix) * Cy) >> 10; g += (G_VAL16(pix) * Cy) >> 10; b += (B_VAL16(pix) * Cy) >> 10; a += (A_VAL16(pix) * Cy) >> 10; } if (j > 0) { pix += sow; r += (R_VAL16(pix) * j) >> 10; g += (G_VAL16(pix) * j) >> 10; b += (B_VAL16(pix) * j) >> 10; a += (A_VAL16(pix) * j) >> 10; } if (XAP > 0) { pix = ypoints[dyy + y] + xpoints[x] + 1; rr = (R_VAL16(pix) * yap) >> 10; gg = (G_VAL16(pix) * yap) >> 10; bb = (B_VAL16(pix) * yap) >> 10; aa = (A_VAL16(pix) * yap) >> 10; for (j = (1 << 14) - yap; j > Cy; j -= Cy) { pix += sow; rr += (R_VAL16(pix) * Cy) >> 10; gg += (G_VAL16(pix) * Cy) >> 10; bb += (B_VAL16(pix) * Cy) >> 10; aa += (A_VAL16(pix) * Cy) >> 10; } if (j > 0) { pix += sow; rr += (R_VAL16(pix) * j) >> 10; gg += (G_VAL16(pix) * j) >> 10; bb += (B_VAL16(pix) * j) >> 10; aa += (A_VAL16(pix) * j) >> 10; } r = r * INV_XAP; g = g * INV_XAP; b = b * INV_XAP; a = a * INV_XAP; r = (r + ((rr * XAP))) >> 12; g = (g + ((gg * XAP))) >> 12; b = (b + ((bb * XAP))) >> 12; a = (a + ((aa * XAP))) >> 12; } else { r >>= 4; g >>= 4; b >>= 4; a >>= 4; } R_VAL16(dptr) = r; G_VAL16(dptr) = g; B_VAL16(dptr) = b; A_VAL16(dptr) = a; ++dptr; } } } /* if we're scaling down horizontally */ else if (isi->xup_yup == 2) { /*\ 'Correct' version, with math units prepared for MMXification \*/ int Cx, j; ullong* pix=0; llong r, g, b, a, rr, gg, bb, aa; int xap; /* go through every scanline in the output buffer */ for (y = y_begin; y < y_end; ++y) { dptr = dest + (y - y_begin) * dow; for (x = x_begin; x < x_end; ++x) { Cx = XAP >> 16; xap = XAP & 0xffff; pix = ypoints[dyy + y] + xpoints[x]; r = (R_VAL16(pix) * xap) >> 10; g = (G_VAL16(pix) * xap) >> 10; b = (B_VAL16(pix) * xap) >> 10; a = (A_VAL16(pix) * xap) >> 10; for (j = (1 << 14) - xap; j > Cx; j -= Cx) { ++pix; r += (R_VAL16(pix) * Cx) >> 10; g += (G_VAL16(pix) * Cx) >> 10; b += (B_VAL16(pix) * Cx) >> 10; a += (A_VAL16(pix) * Cx) >> 10; } if (j > 0) { ++pix; r += (R_VAL16(pix) * j) >> 10; g += (G_VAL16(pix) * j) >> 10; b += (B_VAL16(pix) * j) >> 10; a += (A_VAL16(pix) * j) >> 10; } if (YAP > 0) { pix = ypoints[dyy + y] + xpoints[x] + sow; rr = (R_VAL16(pix) * xap) >> 10; gg = (G_VAL16(pix) * xap) >> 10; bb = (B_VAL16(pix) * xap) >> 10; aa = (A_VAL16(pix) * xap) >> 10; for (j = (1 << 14) - xap; j > Cx; j -= Cx) { ++pix; rr += (R_VAL16(pix) * Cx) >> 10; gg += (G_VAL16(pix) * Cx) >> 10; bb += (B_VAL16(pix) * Cx) >> 10; aa += (A_VAL16(pix) * Cx) >> 10; } if (j > 0) { ++pix; rr += (R_VAL16(pix) * j) >> 10; gg += (G_VAL16(pix) * j) >> 10; bb += (B_VAL16(pix) * j) >> 10; aa += (A_VAL16(pix) * j) >> 10; } r = r * INV_YAP; g = g * INV_YAP; b = b * INV_YAP; a = a * INV_YAP; r = (r + ((rr * YAP))) >> 12; g = (g + ((gg * YAP))) >> 12; b = (b + ((bb * YAP))) >> 12; a = (a + ((aa * YAP))) >> 12; } else { r >>= 4; g >>= 4; b >>= 4; a >>= 4; } R_VAL16(dptr) = r; G_VAL16(dptr) = g; B_VAL16(dptr) = b; A_VAL16(dptr) = a; ++dptr; } } } /* if we're scaling down horizontally & vertically */ else { /*\ 'Correct' version, with math units prepared for MMXification: |*| The operation 'b = (b * c) >> 16' translates to pmulhw, |*| so the operation 'b = (b * c) >> d' would translate to |*| psllw (16 - d), %mmb; pmulh %mmc, %mmb \*/ int Cx, Cy, i, j; ullong* pix=0; llong a, r, g, b, ax, rx, gx, bx; int xap, yap; for (y = y_begin; y < y_end; ++y) { Cy = YAP >> 16; yap = YAP & 0xffff; dptr = dest + (y - y_begin) * dow; for (x = x_begin; x < x_end; ++x) { Cx = XAP >> 16; xap = XAP & 0xffff; sptr = ypoints[dyy + y] + xpoints[x]; pix = sptr; sptr += sow; rx = (R_VAL16(pix) * xap) >> 9; gx = (G_VAL16(pix) * xap) >> 9; bx = (B_VAL16(pix) * xap) >> 9; ax = (A_VAL16(pix) * xap) >> 9; ++pix; for (i = (1 << 14) - xap; i > Cx; i -= Cx) { rx += (R_VAL16(pix) * Cx) >> 9; gx += (G_VAL16(pix) * Cx) >> 9; bx += (B_VAL16(pix) * Cx) >> 9; ax += (A_VAL16(pix) * Cx) >> 9; ++pix; } if (i > 0) { rx += (R_VAL16(pix) * i) >> 9; gx += (G_VAL16(pix) * i) >> 9; bx += (B_VAL16(pix) * i) >> 9; ax += (A_VAL16(pix) * i) >> 9; } r = (rx * yap) >> 14; g = (gx * yap) >> 14; b = (bx * yap) >> 14; a = (ax * yap) >> 14; for (j = (1 << 14) - yap; j > Cy; j -= Cy) { pix = sptr; sptr += sow; rx = (R_VAL16(pix) * xap) >> 9; gx = (G_VAL16(pix) * xap) >> 9; bx = (B_VAL16(pix) * xap) >> 9; ax = (A_VAL16(pix) * xap) >> 9; ++pix; for (i = (1 << 14) - xap; i > Cx; i -= Cx) { rx += (R_VAL16(pix) * Cx) >> 9; gx += (G_VAL16(pix) * Cx) >> 9; bx += (B_VAL16(pix) * Cx) >> 9; ax += (A_VAL16(pix) * Cx) >> 9; ++pix; } if (i > 0) { rx += (R_VAL16(pix) * i) >> 9; gx += (G_VAL16(pix) * i) >> 9; bx += (B_VAL16(pix) * i) >> 9; ax += (A_VAL16(pix) * i) >> 9; } r += (rx * Cy) >> 14; g += (gx * Cy) >> 14; b += (bx * Cy) >> 14; a += (ax * Cy) >> 14; } if (j > 0) { pix = sptr; sptr += sow; (void)sptr; // disable clang warning. rx = (R_VAL16(pix) * xap) >> 9; gx = (G_VAL16(pix) * xap) >> 9; bx = (B_VAL16(pix) * xap) >> 9; ax = (A_VAL16(pix) * xap) >> 9; ++pix; for (i = (1 << 14) - xap; i > Cx; i -= Cx) { rx += (R_VAL16(pix) * Cx) >> 9; gx += (G_VAL16(pix) * Cx) >> 9; bx += (B_VAL16(pix) * Cx) >> 9; ax += (A_VAL16(pix) * Cx) >> 9; ++pix; } if (i > 0) { rx += (R_VAL16(pix) * i) >> 9; gx += (G_VAL16(pix) * i) >> 9; bx += (B_VAL16(pix) * i) >> 9; ax += (A_VAL16(pix) * i) >> 9; } r += (rx * j) >> 14; g += (gx * j) >> 14; b += (bx * j) >> 14; a += (ax * j) >> 14; } R_VAL16(dptr) = r >> 5; G_VAL16(dptr) = g >> 5; B_VAL16(dptr) = b >> 5; A_VAL16(dptr) = a >> 5; ++dptr; } } } } } // namespace Digikam diff --git a/core/libs/dplugins/widgets/dpreviewimage.cpp b/core/libs/dplugins/widgets/dpreviewimage.cpp index 648c05795b..62c8bb07a5 100644 --- a/core/libs/dplugins/widgets/dpreviewimage.cpp +++ b/core/libs/dplugins/widgets/dpreviewimage.cpp @@ -1,1254 +1,1254 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2009-12-13 * Description : a widget to preview image effect. * * Copyright (C) 2009-2019 by Gilles Caulier * Copyright (C) 2008 by Kare Sars * Copyright (C) 2012 by Benjamin Girault * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "dpreviewimage.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include // Local includes #include "digikam_debug.h" #include "dimg.h" #include "previewloadthread.h" namespace Digikam { static const qreal selMargin = 8.0; static const QPointF boundMargin(selMargin, selMargin); class Q_DECL_HIDDEN DSelectionItem::Private { public: explicit Private() { selMargin = 0.0; invZoom = 1.0; maxX = 0.0; maxY = 0.0; showAnchors = true; hasMaxX = false; hasMaxY = false; hasMax = false; } QPen penDark; QPen penLight; QPen penAnchors; QRectF rect; qreal maxX; qreal maxY; bool hasMaxX; bool hasMaxY; bool hasMax; qreal invZoom; qreal selMargin; QRectF anchorTopLeft; QRectF anchorTopRight; QRectF anchorBottomLeft; QRectF anchorBottomRight; QLineF anchorTop; QLineF anchorBottom; QLineF anchorLeft; QLineF anchorRight; bool showAnchors; }; DSelectionItem::DSelectionItem(const QRectF& rect) : QGraphicsItem(), d(new Private) { d->selMargin = selMargin; setRect(rect); d->penDark.setColor(Qt::black); d->penDark.setStyle(Qt::SolidLine); d->penLight.setColor(Qt::white); d->penLight.setStyle(Qt::DashLine); d->penAnchors.setColor(Qt::white); d->penAnchors.setStyle(Qt::SolidLine); } DSelectionItem::~DSelectionItem() { delete d; } void DSelectionItem::saveZoom(qreal zoom) { if (zoom < 0.00001) { zoom = 0.00001; } d->invZoom = 1 / zoom; d->selMargin = selMargin * d->invZoom; updateAnchors(); } void DSelectionItem::setMaxRight(qreal maxX) { d->maxX = maxX; d->hasMaxX = true; if (d->hasMaxY) { d->hasMax = true; } } void DSelectionItem::setMaxBottom(qreal maxY) { d->maxY = maxY; d->hasMaxY = true; if (d->hasMaxX) { d->hasMax = true; } } DSelectionItem::Intersects DSelectionItem::intersects(QPointF& point) { if ((point.x() < (d->rect.left() - d->selMargin)) || (point.x() > (d->rect.right() + d->selMargin)) || (point.y() < (d->rect.top() - d->selMargin)) || (point.y() > (d->rect.bottom() + d->selMargin))) { d->showAnchors = false; update(); return None; } d->showAnchors = true; update(); if (point.x() < (d->rect.left() + d->selMargin)) { if (point.y() < (d->rect.top() + d->selMargin)) return TopLeft; if (point.y() > (d->rect.bottom() - d->selMargin)) return BottomLeft; return Left; } if (point.x() > (d->rect.right() - d->selMargin)) { if (point.y() < (d->rect.top() + d->selMargin)) return TopRight; if (point.y() > (d->rect.bottom() - d->selMargin)) return BottomRight; return Right; } if (point.y() < (d->rect.top() + d->selMargin)) { return Top; } if (point.y() > (d->rect.bottom()-d->selMargin)) { return Bottom; } return Move; } void DSelectionItem::setRect(const QRectF& rect) { prepareGeometryChange(); d->rect = rect; d->rect = d->rect.normalized(); if (d->hasMax) { if (d->rect.top() < 0) { d->rect.setTop(0); } if (d->rect.left() < 0) { d->rect.setLeft(0); } if (d->rect.right() > d->maxX) { d->rect.setRight(d->maxX); } if (d->rect.bottom() > d->maxY) { d->rect.setBottom(d->maxY); } } updateAnchors(); } QPointF DSelectionItem::fixTranslation(QPointF dp) const { if ((d->rect.left() + dp.x()) < 0) { dp.setX(-d->rect.left()); } if ((d->rect.top() + dp.y()) < 0) { dp.setY(-d->rect.top()); } if ((d->rect.right() + dp.x()) > d->maxX) { dp.setX(d->maxX - d->rect.right()); } if ((d->rect.bottom() + dp.y()) > d->maxY) { dp.setY(d->maxY - d->rect.bottom()); } return dp; } QRectF DSelectionItem::rect() const { return d->rect; } QRectF DSelectionItem::boundingRect() const { return QRectF(d->rect.topLeft() - boundMargin, d->rect.bottomRight() + boundMargin); } void DSelectionItem::paint(QPainter* painter, const QStyleOptionGraphicsItem*, QWidget*) { painter->setPen(d->penDark); painter->drawRect(d->rect); painter->setPen(d->penLight); painter->drawRect(d->rect); if (d->showAnchors) { painter->setPen(d->penAnchors); painter->setOpacity(0.4); if (!d->anchorTop.isNull()) { painter->drawLine(d->anchorTop); } if (!d->anchorBottom.isNull()) { painter->drawLine(d->anchorBottom); } if (!d->anchorLeft.isNull()) { painter->drawLine(d->anchorLeft); } if (!d->anchorRight.isNull()) { painter->drawLine(d->anchorRight); } painter->setOpacity(0.4); if (!d->anchorTopLeft.isNull()) { painter->fillRect(d->anchorTopLeft, Qt::white); } if (!d->anchorTopRight.isNull()) { painter->fillRect(d->anchorTopRight, Qt::white); } if (!d->anchorBottomLeft.isNull()) { painter->fillRect(d->anchorBottomLeft, Qt::white); } if (!d->anchorBottomRight.isNull()) { painter->fillRect(d->anchorBottomRight, Qt::white); } } } void DSelectionItem::updateAnchors() { QPointF moveDown(0.0, d->selMargin); QPointF moveRight(d->selMargin, 0.0); bool verticalCondition = (d->rect.height() - 3 * d->selMargin) > 0; bool horizontalCondition = (d->rect.width() - 3 * d->selMargin) > 0; if (verticalCondition) { if (horizontalCondition) { d->anchorTop = QLineF(d->rect.topLeft() + moveDown, d->rect.topRight() + moveDown); d->anchorBottom = QLineF(d->rect.bottomLeft() - moveDown, d->rect.bottomRight() - moveDown); d->anchorLeft = QLineF(d->rect.topLeft() + moveRight, d->rect.bottomLeft() + moveRight); d->anchorRight = QLineF(d->rect.topRight() - moveRight, d->rect.bottomRight() - moveRight); d->anchorTopLeft = QRectF(d->rect.topLeft(), d->rect.topLeft() + moveDown + moveRight); d->anchorTopRight = QRectF(d->rect.topRight() - moveRight, d->rect.topRight() + moveDown); d->anchorBottomLeft = QRectF(d->rect.bottomLeft() - moveDown, d->rect.bottomLeft() + moveRight); d->anchorBottomRight = QRectF(d->rect.bottomRight() - moveDown - moveRight, d->rect.bottomRight()); } else { // Only the top & bottom lines & middle line plus two corners are drawn d->anchorTop = QLineF(d->rect.topLeft() + moveDown, d->rect.topRight() + moveDown); d->anchorBottom = QLineF(d->rect.bottomLeft() - moveDown, d->rect.bottomRight() - moveDown); d->anchorLeft = QLineF(d->rect.topLeft() + QPointF(d->rect.width() / 2.0, 0.0), d->rect.bottomLeft() + QPointF(d->rect.width() / 2.0, 0.0)); d->anchorRight = QLineF(); d->anchorTopLeft = QRectF(d->rect.topLeft(), d->rect.topRight() + moveDown); d->anchorTopRight = QRectF(); d->anchorBottomLeft = QRectF(d->rect.bottomLeft() - moveDown, d->rect.bottomRight()); d->anchorBottomRight = QRectF(); } } else { if (horizontalCondition) { // Only the left & right lines & middle line plus two corners are drawn d->anchorTop = QLineF(d->rect.topLeft() + QPointF(0.0, d->rect.height() / 2.0), d->rect.topRight() + QPointF(0.0, d->rect.height() / 2.0)); d->anchorBottom = QLineF(); d->anchorLeft = QLineF(d->rect.topLeft() + moveRight, d->rect.bottomLeft() + moveRight); d->anchorRight = QLineF(d->rect.topRight() - moveRight, d->rect.bottomRight() - moveRight); d->anchorTopLeft = QRectF(d->rect.topLeft(), d->rect.bottomLeft() + moveRight); d->anchorTopRight = QRectF(d->rect.topRight() - moveRight, d->rect.bottomRight()); d->anchorBottomLeft = QRectF(); d->anchorBottomRight = QRectF(); } else { d->anchorTop = QLineF(); d->anchorBottom = QLineF(); d->anchorLeft = QLineF(); d->anchorRight = QLineF(); d->anchorTopLeft = QRectF(); d->anchorTopRight = QRectF(); d->anchorBottomLeft = QRectF(); d->anchorBottomRight = QRectF(); } } } // ------------------------------------------------------------------------------------------------- class Q_DECL_HIDDEN DPreviewImage::Private { public: enum { NONE, LOOKAROUND, DRAWSELECTION, EXPANDORSHRINK, MOVESELECTION } mouseDragAction; public: explicit Private() : mouseDragAction(NONE), lastdx(0), lastdy(0), scene(0), pixmapItem(0), selection(0), enableSelection(false), mouseZone(DSelectionItem::None), zoomInAction(0), zoomOutAction(0), zoom2FitAction(0), toolBar(0), highLightLeft(0), highLightRight(0), highLightTop(0), highLightBottom(0), highLightArea(0) { } int lastdx; int lastdy; QGraphicsScene* scene; QGraphicsPixmapItem* pixmapItem; DSelectionItem* selection; bool enableSelection; DSelectionItem::Intersects mouseZone; QPointF lastMousePoint; QAction* zoomInAction; QAction* zoomOutAction; QAction* zoom2FitAction; QToolBar* toolBar; QGraphicsRectItem* highLightLeft; QGraphicsRectItem* highLightRight; QGraphicsRectItem* highLightTop; QGraphicsRectItem* highLightBottom; QGraphicsRectItem* highLightArea; }; DPreviewImage::DPreviewImage(QWidget* const parent) : QGraphicsView(parent), d(new Private) { setAttribute(Qt::WA_DeleteOnClose); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); setMouseTracking(true); setCacheMode(QGraphicsView::CacheBackground); d->scene = new QGraphicsScene; d->pixmapItem = new QGraphicsPixmapItem; d->selection = new DSelectionItem(QRectF()); d->selection->setZValue(10); d->selection->setVisible(false); d->enableSelection = false; d->scene->addItem(d->pixmapItem); setScene(d->scene); d->highLightTop = new QGraphicsRectItem; d->highLightBottom = new QGraphicsRectItem; d->highLightRight = new QGraphicsRectItem; d->highLightLeft = new QGraphicsRectItem; d->highLightArea = new QGraphicsRectItem; d->highLightTop->setOpacity(0.4); d->highLightBottom->setOpacity(0.4); d->highLightRight->setOpacity(0.4); d->highLightLeft->setOpacity(0.4); d->highLightArea->setOpacity(0.6); d->highLightTop->setPen(Qt::NoPen); d->highLightBottom->setPen(Qt::NoPen); d->highLightRight->setPen(Qt::NoPen); d->highLightLeft->setPen(Qt::NoPen); d->highLightArea->setPen(Qt::NoPen); d->highLightTop->setBrush(QBrush(Qt::black)); d->highLightBottom->setBrush(QBrush(Qt::black)); d->highLightRight->setBrush(QBrush(Qt::black)); d->highLightLeft->setBrush(QBrush(Qt::black)); d->scene->addItem(d->selection); d->scene->addItem(d->highLightTop); d->scene->addItem(d->highLightBottom); d->scene->addItem(d->highLightRight); d->scene->addItem(d->highLightLeft); d->scene->addItem(d->highLightArea); d->mouseZone = DSelectionItem::None; // create context menu d->zoomInAction = new QAction(QIcon::fromTheme(QLatin1String("zoom-in")), i18n("Zoom In"), this); d->zoomInAction->setToolTip(i18n("Zoom In")); d->zoomInAction->setShortcut(Qt::Key_Plus); connect(d->zoomInAction, &QAction::triggered, this, &DPreviewImage::slotZoomIn); d->zoomOutAction = new QAction(QIcon::fromTheme(QLatin1String("zoom-out")), i18n("Zoom Out"), this); d->zoomOutAction->setToolTip(i18n("Zoom Out")); d->zoomOutAction->setShortcut(Qt::Key_Minus); connect(d->zoomOutAction, &QAction::triggered, this, &DPreviewImage::slotZoomOut); d->zoom2FitAction = new QAction(QIcon::fromTheme(QLatin1String("zoom-fit-best")), i18n("Zoom to Fit"), this); d->zoom2FitAction->setToolTip(i18n("Zoom to Fit")); d->zoom2FitAction->setShortcut(Qt::Key_Asterisk); connect(d->zoom2FitAction, &QAction::triggered, this, &DPreviewImage::slotZoom2Fit); addAction(d->zoomInAction); addAction(d->zoomOutAction); addAction(d->zoom2FitAction); setContextMenuPolicy(Qt::ActionsContextMenu); // Create ToolBar d->toolBar = new QToolBar(this); d->toolBar->addAction(d->zoomInAction); d->toolBar->addAction(d->zoomOutAction); d->toolBar->addAction(d->zoom2FitAction); d->toolBar->hide(); d->toolBar->installEventFilter(this); horizontalScrollBar()->installEventFilter(this); verticalScrollBar()->installEventFilter(this); } DPreviewImage::~DPreviewImage() { delete d; } bool DPreviewImage::setImage(const QImage& img) const { if (!img.isNull()) { d->pixmapItem->setPixmap(QPixmap::fromImage(img)); d->pixmapItem->setShapeMode(QGraphicsPixmapItem::BoundingRectShape); d->scene->setSceneRect(0, 0, img.width(), img.height()); return true; } return false; } void DPreviewImage::enableSelectionArea(bool b) { d->enableSelection = b; } QRectF DPreviewImage::getSelectionArea() const { return d->selection->rect(); } void DPreviewImage::setSelectionArea(const QRectF& rectangle) { d->selection->setRect(rectangle); if (!d->selection->isVisible()) d->selection->setVisible(true); } bool DPreviewImage::load(const QUrl& file) const { DImg dimg = PreviewLoadThread::loadHighQualitySynchronously(file.toLocalFile()); bool ret = setImage(dimg.copyQImage()); if (ret && d->enableSelection) { qCDebug(DIGIKAM_GENERAL_LOG) << d->scene->height() << " " << d->scene->width(); d->selection->setMaxRight(d->scene->width()); d->selection->setMaxBottom(d->scene->height()); d->selection->setRect(d->scene->sceneRect()); } return ret; } void DPreviewImage::slotZoomIn() { scale(1.5, 1.5); d->selection->saveZoom(transform().m11()); d->zoom2FitAction->setDisabled(false); } void DPreviewImage::slotZoomOut() { scale(1.0 / 1.5, 1.0 / 1.5); d->selection->saveZoom(transform().m11()); d->zoom2FitAction->setDisabled(false); } void DPreviewImage::slotZoom2Fit() { fitInView(d->pixmapItem->boundingRect(), Qt::KeepAspectRatio); d->selection->saveZoom(transform().m11()); d->zoom2FitAction->setDisabled(true); } void DPreviewImage::slotSetTLX(float ratio) { if (!d->selection->isVisible()) return; // only correct the selection if it is visible QRectF rect = d->selection->rect(); rect.setLeft(ratio * d->scene->width()); d->selection->setRect(rect); updateSelVisibility(); } void DPreviewImage::slotSetTLY(float ratio) { if (!d->selection->isVisible()) return; // only correct the selection if it is visible QRectF rect = d->selection->rect(); rect.setTop(ratio * d->scene->height()); d->selection->setRect(rect); updateSelVisibility(); } void DPreviewImage::slotSetBRX(float ratio) { if (!d->selection->isVisible()) return; // only correct the selection if it is visible QRectF rect = d->selection->rect(); rect.setRight(ratio * d->scene->width()); d->selection->setRect(rect); updateSelVisibility(); } void DPreviewImage::slotSetBRY(float ratio) { if (!d->selection->isVisible()) return; // only correct the selection if it is visible QRectF rect = d->selection->rect(); rect.setBottom(ratio * d->scene->height()); d->selection->setRect(rect); updateSelVisibility(); } void DPreviewImage::slotSetSelection(float tl_x, float tl_y, float br_x, float br_y) { QRectF rect; rect.setCoords(tl_x * d->scene->width(), tl_y * d->scene->height(), br_x * d->scene->width(), br_y * d->scene->height()); d->selection->setRect(rect); updateSelVisibility(); } void DPreviewImage::slotClearActiveSelection() { d->selection->setRect(QRectF(0, 0, 0, 0)); d->selection->setVisible(false); } void DPreviewImage::slotSetHighlightArea(float tl_x, float tl_y, float br_x, float br_y) { QRectF rect; // Left reason for rect: setCoords(x1,y1,x2,y2) != setRect(x1,x2, width, height) rect.setCoords(0, 0, tl_x * d->scene->width(), d->scene->height()); d->highLightLeft->setRect(rect); // Right rect.setCoords(br_x * d->scene->width(), 0, d->scene->width(), d->scene->height()); d->highLightRight->setRect(rect); // Top rect.setCoords(tl_x * d->scene->width(), 0, br_x * d->scene->width(), tl_y * d->scene->height()); d->highLightTop->setRect(rect); // Bottom rect.setCoords(tl_x * d->scene->width(), br_y * d->scene->height(), br_x * d->scene->width(), d->scene->height()); d->highLightBottom->setRect(rect); // Area rect.setCoords(tl_x * d->scene->width(), tl_y* d->scene->height(), br_x * d->scene->width(), br_y* d->scene->height()); d->highLightArea->setRect(rect); d->highLightLeft->show(); d->highLightRight->show(); d->highLightTop->show(); d->highLightBottom->show(); // the highlight area is hidden until setHighlightShown is called. d->highLightArea->hide(); } void DPreviewImage::slotSetHighlightShown(int percentage, QColor highLightColor) { if (percentage >= 100) { d->highLightArea->hide(); return; } d->highLightArea->setBrush(highLightColor); qreal diff = d->highLightBottom->rect().top() - d->highLightTop->rect().bottom(); diff -= (diff * percentage) / 100; QRectF rect = d->highLightArea->rect(); rect.setTop(d->highLightBottom->rect().top() - diff); d->highLightArea->setRect(rect); d->highLightArea->show(); } void DPreviewImage::slotClearHighlight() { d->highLightLeft->hide(); d->highLightRight->hide(); d->highLightTop->hide(); d->highLightBottom->hide(); d->highLightArea->hide(); } void DPreviewImage::wheelEvent(QWheelEvent* e) { if (e->modifiers() == Qt::ControlModifier) { if (e->delta() > 0) { slotZoomIn(); } else { slotZoomOut(); } } else { QGraphicsView::wheelEvent(e); } } void DPreviewImage::mousePressEvent(QMouseEvent* e) { if (e->button() & Qt::LeftButton) { d->lastdx = e->x(); d->lastdy = e->y(); QPointF scenePoint = mapToScene(e->pos()); d->lastMousePoint = scenePoint; if (e->modifiers() != Qt::ControlModifier && d->enableSelection) { if (!d->selection->isVisible() || !d->selection->contains(scenePoint)) { // Beginning of a selection area change d->mouseDragAction = Private::DRAWSELECTION; d->selection->setVisible(true); - d->selection->setRect(QRectF(scenePoint, QSizeF(0,0))); + d->selection->setRect(QRectF(scenePoint, QSizeF(0, 0))); d->mouseZone = DSelectionItem::BottomRight; } else if (d->selection->isVisible() && d->mouseZone != DSelectionItem::None && d->mouseZone != DSelectionItem::Move) { // Modification of the selection area d->mouseDragAction = Private::EXPANDORSHRINK; } else { // Selection movement called by QGraphicsView d->mouseDragAction = Private::MOVESELECTION; } updateHighlight(); } else { // Beginning of moving around the picture d->mouseDragAction = Private::LOOKAROUND; setCursor(Qt::ClosedHandCursor); } } QGraphicsView::mousePressEvent(e); } void DPreviewImage::mouseReleaseEvent(QMouseEvent* e) { if (e->button() & Qt::LeftButton) { if (d->mouseDragAction == Private::DRAWSELECTION) { // Stop and setup the selection area // Only one case: small rectangle that we drop if ((d->selection->rect().width() < 0.001) || (d->selection->rect().height() < 0.001)) { slotClearActiveSelection(); } } if (!d->selection->isVisible() || !d->selection->contains(e->pos())) { setCursor(Qt::CrossCursor); } } d->mouseDragAction = Private::NONE; updateHighlight(); QGraphicsView::mouseReleaseEvent(e); } void DPreviewImage::mouseMoveEvent(QMouseEvent* e) { QPointF scenePoint = mapToScene(e->pos()); if (e->buttons() & Qt::LeftButton) { if (d->mouseDragAction == Private::LOOKAROUND) { int dx = e->x() - d->lastdx; int dy = e->y() - d->lastdy; verticalScrollBar()->setValue(verticalScrollBar()->value() - dy); horizontalScrollBar()->setValue(horizontalScrollBar()->value() - dx); d->lastdx = e->x(); d->lastdy = e->y(); } else if (d->mouseDragAction == Private::DRAWSELECTION || d->mouseDragAction == Private::EXPANDORSHRINK || d->mouseDragAction == Private::MOVESELECTION) { - ensureVisible(QRectF(scenePoint, QSizeF(0,0)), 1, 1); + ensureVisible(QRectF(scenePoint, QSizeF(0, 0)), 1, 1); QRectF rect = d->selection->rect(); switch (d->mouseZone) { case DSelectionItem::None: // should not be here :) break; case DSelectionItem::Top: if (scenePoint.y() < rect.bottom()) { rect.setTop(scenePoint.y()); } else { d->mouseZone = DSelectionItem::Bottom; rect.setTop(rect.bottom()); } break; case DSelectionItem::TopRight: if (scenePoint.x() > rect.left()) { rect.setRight(scenePoint.x()); } else { d->mouseZone = DSelectionItem::TopLeft; setCursor(Qt::SizeFDiagCursor); rect.setRight(rect.left()); } if (scenePoint.y() < rect.bottom()) { rect.setTop(scenePoint.y()); } else { if (d->mouseZone != DSelectionItem::TopLeft) { d->mouseZone = DSelectionItem::BottomRight; setCursor(Qt::SizeFDiagCursor); } else { d->mouseZone = DSelectionItem::BottomLeft; setCursor(Qt::SizeBDiagCursor); } rect.setTop(rect.bottom()); } break; case DSelectionItem::Right: if (scenePoint.x() > rect.left()) { rect.setRight(scenePoint.x()); } else { d->mouseZone = DSelectionItem::Left; rect.setRight(rect.left()); } break; case DSelectionItem::BottomRight: if (scenePoint.x() > rect.left()) { rect.setRight(scenePoint.x()); } else { d->mouseZone = DSelectionItem::BottomLeft; setCursor(Qt::SizeBDiagCursor); rect.setRight(rect.left()); } if (scenePoint.y() > rect.top()) { rect.setBottom(scenePoint.y()); } else { if (d->mouseZone != DSelectionItem::BottomLeft) { d->mouseZone = DSelectionItem::TopRight; setCursor(Qt::SizeBDiagCursor); } else { d->mouseZone = DSelectionItem::TopLeft; setCursor(Qt::SizeFDiagCursor); } rect.setBottom(rect.top()); } break; case DSelectionItem::Bottom: if (scenePoint.y() > rect.top()) { rect.setBottom(scenePoint.y()); } else { d->mouseZone = DSelectionItem::Top; rect.setBottom(rect.top()); } break; case DSelectionItem::BottomLeft: if (scenePoint.x() < rect.right()) { rect.setLeft(scenePoint.x()); } else { d->mouseZone = DSelectionItem::BottomRight; setCursor(Qt::SizeFDiagCursor); rect.setLeft(rect.right()); } if (scenePoint.y() > rect.top()) { rect.setBottom(scenePoint.y()); } else { if (d->mouseZone != DSelectionItem::BottomRight) { d->mouseZone = DSelectionItem::TopLeft; setCursor(Qt::SizeFDiagCursor); } else { d->mouseZone = DSelectionItem::TopRight; setCursor(Qt::SizeBDiagCursor); } rect.setBottom(rect.top()); } break; case DSelectionItem::Left: if (scenePoint.x() < rect.right()) { rect.setLeft(scenePoint.x()); } else { d->mouseZone = DSelectionItem::Right; rect.setLeft(rect.right()); } break; case DSelectionItem::TopLeft: if (scenePoint.x() < rect.right()) { rect.setLeft(scenePoint.x()); } else { d->mouseZone = DSelectionItem::TopRight; setCursor(Qt::SizeBDiagCursor); rect.setLeft(rect.right()); } if (scenePoint.y() < rect.bottom()) { rect.setTop(scenePoint.y()); } else { if (d->mouseZone != DSelectionItem::TopRight) { d->mouseZone = DSelectionItem::BottomLeft; setCursor(Qt::SizeBDiagCursor); } else { d->mouseZone = DSelectionItem::BottomRight; setCursor(Qt::SizeFDiagCursor); } rect.setTop(rect.bottom()); } break; case DSelectionItem::Move: rect.translate(d->selection->fixTranslation(scenePoint - d->lastMousePoint)); break; } d->selection->setRect(rect); } } else if (d->selection->isVisible()) { d->mouseZone = d->selection->intersects(scenePoint); switch (d->mouseZone) { case DSelectionItem::None: setCursor(Qt::CrossCursor); break; case DSelectionItem::Top: setCursor(Qt::SizeVerCursor); break; case DSelectionItem::TopRight: setCursor(Qt::SizeBDiagCursor); break; case DSelectionItem::Right: setCursor(Qt::SizeHorCursor); break; case DSelectionItem::BottomRight: setCursor(Qt::SizeFDiagCursor); break; case DSelectionItem::Bottom: setCursor(Qt::SizeVerCursor); break; case DSelectionItem::BottomLeft: setCursor(Qt::SizeBDiagCursor); break; case DSelectionItem::Left: setCursor(Qt::SizeHorCursor); break; case DSelectionItem::TopLeft: setCursor(Qt::SizeFDiagCursor); break; case DSelectionItem::Move: setCursor(Qt::SizeAllCursor); break; } } else { setCursor(Qt::CrossCursor); } d->lastMousePoint = scenePoint; updateHighlight(); QGraphicsView::mouseMoveEvent(e); } void DPreviewImage::enterEvent(QEvent*) { d->toolBar->show(); } void DPreviewImage::leaveEvent(QEvent*) { d->toolBar->hide(); } bool DPreviewImage::eventFilter(QObject* obj, QEvent* ev) { if ( obj == d->toolBar ) { if ( ev->type() == QEvent::Enter) setCursor(Qt::ArrowCursor); else if ( ev->type() == QEvent::Leave) unsetCursor(); return false; } else if ( obj == verticalScrollBar() && verticalScrollBar()->isVisible()) { if ( ev->type() == QEvent::Enter) setCursor(Qt::ArrowCursor); else if ( ev->type() == QEvent::Leave) unsetCursor(); return false; } else if ( obj == horizontalScrollBar() && horizontalScrollBar()->isVisible()) { if ( ev->type() == QEvent::Enter) setCursor(Qt::ArrowCursor); else if ( ev->type() == QEvent::Leave) unsetCursor(); return false; } return QGraphicsView::eventFilter(obj, ev); } void DPreviewImage::resizeEvent(QResizeEvent* e) { if (!d->zoom2FitAction->isEnabled()) { // Fit the image to the new size... fitInView(d->pixmapItem->boundingRect(), Qt::KeepAspectRatio); d->selection->saveZoom(transform().m11()); } QGraphicsView::resizeEvent(e); } void DPreviewImage::updateSelVisibility() { if ((d->selection->rect().width() > 0.001) && (d->selection->rect().height() > 0.001) && ((d->scene->width() - d->selection->rect().width() > 0.1) || (d->scene->height() - d->selection->rect().height() > 0.1))) { d->selection->setVisible(true); } else { d->selection->setVisible(false); } updateHighlight(); } void DPreviewImage::updateHighlight() { if (d->selection->isVisible()) { QRectF rect; // Left rect.setCoords(0, 0, d->selection->rect().left(), d->scene->height()); d->highLightLeft->setRect(rect); // Right rect.setCoords(d->selection->rect().right(), 0, d->scene->width(), d->scene->height()); d->highLightRight->setRect(rect); // Top rect.setCoords(d->selection->rect().left(), 0, d->selection->rect().right(), d->selection->rect().top()); d->highLightTop->setRect(rect); // Bottom rect.setCoords(d->selection->rect().left(), d->selection->rect().bottom(), d->selection->rect().right(), d->scene->height()); d->highLightBottom->setRect(rect); d->highLightLeft->show(); d->highLightRight->show(); d->highLightTop->show(); d->highLightBottom->show(); d->highLightArea->hide(); } else { d->highLightLeft->hide(); d->highLightRight->hide(); d->highLightTop->hide(); d->highLightBottom->hide(); d->highLightArea->hide(); } } } // namespace Digikam diff --git a/core/libs/widgets/graphicsview/clickdragreleaseitem.cpp b/core/libs/widgets/graphicsview/clickdragreleaseitem.cpp index 8b5f6767ee..8ddbbe5cf6 100644 --- a/core/libs/widgets/graphicsview/clickdragreleaseitem.cpp +++ b/core/libs/widgets/graphicsview/clickdragreleaseitem.cpp @@ -1,204 +1,204 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2011-03-04 * Description : A simple item to click, drag and release * * Copyright (C) 2010-2011 by Marcel Wiesweg * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "clickdragreleaseitem.h" // Qt includes #include #include #include #include #include // Local includes #include "digikam_debug.h" #include "itemvisibilitycontroller.h" namespace Digikam { enum ClickDragState { HoverState, PressedState, PressDragState, ClickedMoveState }; class Q_DECL_HIDDEN ClickDragReleaseItem::Private { public: explicit Private() : state(HoverState) { } template bool isDrag(Event* e) { return (pressPos - e->scenePos()).manhattanLength() > QApplication::startDragDistance(); } template QRectF rect(Event* e) { return QRectF(pressPos, e->scenePos()).normalized(); } public: ClickDragState state; QPointF pressPos; }; ClickDragReleaseItem::ClickDragReleaseItem(QGraphicsItem* const parent) : QGraphicsObject(parent), d(new Private) { setCursor(Qt::CrossCursor); setFlags(ItemIsFocusable | ItemHasNoContents); } ClickDragReleaseItem::~ClickDragReleaseItem() { delete d; } QRectF ClickDragReleaseItem::boundingRect() const { if (parentItem()) { - return QRectF(QPointF(0,0), parentItem()->boundingRect().size()); + return QRectF(QPointF(0, 0), parentItem()->boundingRect().size()); } return QRectF(); } void ClickDragReleaseItem::paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*) { } /** * 1) Press - Drag - Release: * mousePress, PressedState -> mouseMoveEvent over threshold, PressDragState -> mouseReleaseEvent, finished * 2) Click - Move - Click: * mousePressEvent, PressedState -> mouseReleaseEvent, ClickedMoveState -> * hoverMoveEvent -> mouseReleaseEvent, finished */ void ClickDragReleaseItem::mousePressEvent(QGraphicsSceneMouseEvent* e) { if (e->button() != Qt::LeftButton) { emit cancelled(); return; } if (d->state == HoverState) { d->pressPos = e->scenePos(); d->state = PressedState; emit started(e->scenePos()); } } void ClickDragReleaseItem::mouseMoveEvent(QGraphicsSceneMouseEvent* e) { if (d->state == PressedState && d->isDrag(e)) { d->state = PressDragState; setCursor(Qt::SizeFDiagCursor); } if (d->state == PressDragState) { emit moving(d->rect(e)); } } void ClickDragReleaseItem::hoverMoveEvent(QGraphicsSceneHoverEvent* e) { if (d->state == ClickedMoveState) { emit moving(d->rect(e)); } } void ClickDragReleaseItem::mouseReleaseEvent(QGraphicsSceneMouseEvent* e) { if (d->state == PressedState) { // click-move-click mode first click. // It cannot be over the drag threshold in this state, would be caught in moveEvent. d->state = ClickedMoveState; setCursor(Qt::SizeFDiagCursor); setAcceptHoverEvents(true); } else if (d->state == ClickedMoveState) { // click-move-click mode second click d->state = HoverState; setCursor(Qt::CrossCursor); setAcceptHoverEvents(false); emit finished(d->rect(e)); } else if (d->state == PressDragState) { if (d->isDrag(e)) { emit finished(d->rect(e)); } else { emit cancelled(); } d->state = HoverState; setCursor(Qt::CrossCursor); setAcceptHoverEvents(false); } } void ClickDragReleaseItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* e) { e->ignore(); } void ClickDragReleaseItem::keyPressEvent(QKeyEvent* e) { qCDebug(DIGIKAM_WIDGETS_LOG) << e; switch (e->key()) { case Qt::Key_Backspace: case Qt::Key_Escape: emit cancelled(); break; default: e->ignore(); break; } } } // namespace Digikam diff --git a/core/libs/widgets/graphicsview/graphicsdimgitem.cpp b/core/libs/widgets/graphicsview/graphicsdimgitem.cpp index 6b7afff939..656573139c 100644 --- a/core/libs/widgets/graphicsview/graphicsdimgitem.cpp +++ b/core/libs/widgets/graphicsview/graphicsdimgitem.cpp @@ -1,266 +1,266 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-04-30 * Description : Graphics View item for DImg * * Copyright (C) 2010-2012 by Marcel Wiesweg * Copyright (C) 2011-2019 by Gilles Caulier * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "graphicsdimgitem.h" #include "dimgitems_p.h" // Qt includes #include #include #include #include // Local includes #include "digikam_config.h" #include "dimg.h" #include "imagezoomsettings.h" namespace Digikam { CachedPixmaps::CachedPixmaps(int maxCount) : maxCount(maxCount) { } CachedPixmaps::~CachedPixmaps() { clear(); } void CachedPixmaps::setMaxCount(int count) { maxCount = count; } void CachedPixmaps::clear() { foreach(const CachedPixmapKey& key, keys) { QPixmapCache::remove(key.key); } keys.clear(); } bool CachedPixmaps::find(const QRect& region, QPixmap* const pix, QRect* const source) { QQueue::iterator key; for (key = keys.begin(); key != keys.end(); ) { if (!key->region.contains(region)) { ++key; continue; } if (!QPixmapCache::find(key->key, pix)) { key = keys.erase(key); continue; } if (key->region == region) { *source = QRect(); } else { QPoint startPoint = region.topLeft() - key->region.topLeft(); *source = QRect(startPoint, region.size()); } return true; } return false; } void CachedPixmaps::insert(const QRect& region, const QPixmap& pixmap) { if (keys.size() >= maxCount) { CachedPixmapKey key = keys.dequeue(); QPixmapCache::remove(key.key); } CachedPixmapKey key; key.region = region; key.key = QPixmapCache::insert(pixmap); keys.enqueue(key); } // --------------------------------------------------------------------------------------- GraphicsDImgItem::GraphicsDImgItem(QGraphicsItem* const parent) : QGraphicsObject(parent), d_ptr(new GraphicsDImgItemPrivate) { d_ptr->init(this); } GraphicsDImgItem::GraphicsDImgItem(GraphicsDImgItemPrivate& dd, QGraphicsItem* const parent) : QGraphicsObject(parent), d_ptr(&dd) { d_ptr->init(this); } void GraphicsDImgItem::GraphicsDImgItemPrivate::init(GraphicsDImgItem* const q) { // ItemCoordinateCache is very slow, DeviceCoordinateCache makes severe render artifacts q->setCacheMode(QGraphicsItem::NoCache); // This flag is crucial for our performance! Limits redrawing area. q->setFlag(QGraphicsItem::ItemUsesExtendedStyleOption); q->setAcceptedMouseButtons(Qt::NoButton); } GraphicsDImgItem::~GraphicsDImgItem() { Q_D(GraphicsDImgItem); delete d; } void GraphicsDImgItem::setImage(const DImg& img) { Q_D(GraphicsDImgItem); d->image = img; d->zoomSettings.setImageSize(img.size(), img.originalSize()); d->cachedPixmaps.clear(); sizeHasChanged(); emit imageChanged(); } DImg GraphicsDImgItem::image() const { Q_D(const GraphicsDImgItem); return d->image; } void GraphicsDImgItem::sizeHasChanged() { Q_D(GraphicsDImgItem); QGraphicsItem::prepareGeometryChange(); d->cachedPixmaps.clear(); emit imageSizeChanged(d->zoomSettings.zoomedSize()); } void GraphicsDImgItem::clearCache() { Q_D(GraphicsDImgItem); d->cachedPixmaps.clear(); } const ImageZoomSettings* GraphicsDImgItem::zoomSettings() const { Q_D(const GraphicsDImgItem); return &d->zoomSettings; } ImageZoomSettings* GraphicsDImgItem::zoomSettings() { Q_D(GraphicsDImgItem); return &d->zoomSettings; } QRectF GraphicsDImgItem::boundingRect() const { Q_D(const GraphicsDImgItem); // always return full integer sizes, we can only scale to integer - return QRectF(QPointF(0,0), d->zoomSettings.zoomedSize()).toAlignedRect(); + return QRectF(QPointF(0, 0), d->zoomSettings.zoomedSize()).toAlignedRect(); } void GraphicsDImgItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget*) { Q_D(GraphicsDImgItem); QRect drawRect = option->exposedRect.intersected(boundingRect()).toAlignedRect(); QRect pixSourceRect; QPixmap pix; QSize completeSize = boundingRect().size().toSize(); /* For high resolution ("retina") displays, Mac OS X / Qt report only half of the physical resolution in terms of pixels, i.e. every logical pixels corresponds to 2x2 physical pixels. However, UI elements and fonts are nevertheless rendered at full resolution, and pixmaps as well, provided their resolution is high enough (that is, higher than the reported, logical resolution). To work around this, we render the photos not a logical resolution, but with the photo's full resolution, but at the screen's aspect ratio. When we later draw this high resolution bitmap, it is up to Qt to scale the photo to the true physical resolution. The ratio computed below is the ratio between the photo and screen resolutions, or equivalently the factor by which we need to increase the pixel size of the rendered pixmap. */ #ifdef USE_QT_SCALING double xratio = double(d->image.width()) / completeSize.width(); double yratio = double(d->image.height()) / completeSize.height(); double ratio = qMax(qMin(xratio, yratio), 1.0); #else double ratio = 1.0; #endif QRect scaledDrawRect = QRectF(ratio*drawRect.x(), ratio*drawRect.y(), ratio*drawRect.width(), ratio*drawRect.height()).toRect(); if (d->cachedPixmaps.find(scaledDrawRect, &pix, &pixSourceRect)) { if (pixSourceRect.isNull()) { painter->drawPixmap(drawRect, pix); } else { painter->drawPixmap(drawRect, pix, pixSourceRect); } } else { // scale "as if" scaling to whole image, but clip output to our exposed region QSize scaledCompleteSize = QSizeF(ratio*completeSize.width(), ratio*completeSize.height()).toSize(); DImg scaledImage = d->image.smoothScaleClipped(scaledCompleteSize.width(), scaledCompleteSize.height(), scaledDrawRect.x(), scaledDrawRect.y(), scaledDrawRect.width(), scaledDrawRect.height()); pix = scaledImage.convertToPixmap(); d->cachedPixmaps.insert(scaledDrawRect, pix); painter->drawPixmap(drawRect, pix); } } void GraphicsDImgItem::contextMenuEvent(QGraphicsSceneContextMenuEvent* e) { emit showContextMenu(e); } } // namespace Digikam diff --git a/core/libs/widgets/history/filtershistorywidget.cpp b/core/libs/widgets/history/filtershistorywidget.cpp index 0de4a21c0a..b63b4ee989 100644 --- a/core/libs/widgets/history/filtershistorywidget.cpp +++ b/core/libs/widgets/history/filtershistorywidget.cpp @@ -1,170 +1,170 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-08-03 * Description : Widget displaying filters history used on an image * * Copyright (C) 2010 by Martin Klapetek * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "filtershistorywidget.h" // Qt includes #include #include #include #include #include // KDE includes #include // Local includes #include "digikam_debug.h" #include "itemfiltershistorymodel.h" #include "itemfiltershistorytreeitem.h" #include "itemfiltershistoryitemdelegate.h" namespace Digikam { RemoveFilterAction::RemoveFilterAction(const QString& label, const QModelIndex& index, QObject* const parent) : QAction(label, parent) { m_index = index; } // ------------------------------------------------------------------------------------------------------- class Q_DECL_HIDDEN FiltersHistoryWidget::Private { public: explicit Private() { view = 0; model = 0; layout = 0; delegate = 0; headerLabel = 0; } QTreeView* view; ItemFiltersHistoryModel* model; QGridLayout* layout; ItemFiltersHistoryItemDelegate* delegate; QLabel* headerLabel; }; FiltersHistoryWidget::FiltersHistoryWidget(QWidget* const parent) : QWidget(parent), d(new Private) { d->layout = new QGridLayout(this); d->view = new QTreeView(this); d->delegate = new ItemFiltersHistoryItemDelegate(this); d->model = new ItemFiltersHistoryModel(0, QUrl()); d->headerLabel = new QLabel(this); d->headerLabel->setText(i18n("Used filters")); d->layout->addWidget(d->headerLabel); d->layout->addWidget(d->view); d->view->setItemDelegate(d->delegate); d->view->setModel(d->model); d->view->setRootIsDecorated(false); d->view->setContextMenuPolicy(Qt::CustomContextMenu); d->view->setHeaderHidden(true); connect(d->view, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showCustomContextMenu(QPoint))); } FiltersHistoryWidget::~FiltersHistoryWidget() { delete d->model; delete d->delegate; delete d; } void FiltersHistoryWidget::setCurrentURL(const QUrl& url) { d->model->setUrl(url); d->view->setModel(d->model); d->view->update(); } void FiltersHistoryWidget::showCustomContextMenu(const QPoint& position) { QList actions; if (d->view->indexAt(position).isValid()) { QModelIndex index = d->view->indexAt(position); QString s(i18n("Remove filter")); RemoveFilterAction* removeFilterAction = new RemoveFilterAction(s, index, 0); removeFilterAction->setDisabled(true); if (!index.model()->parent(index).isValid()) { actions.append(removeFilterAction); connect(removeFilterAction, SIGNAL(triggered()), removeFilterAction, SLOT(triggerSlot())); connect(removeFilterAction, SIGNAL(actionTriggered(QModelIndex)), d->model, SLOT(removeEntry(QModelIndex))); } } if (actions.count() > 0) { QMenu::exec(actions, d->view->mapToGlobal(position)); } } void FiltersHistoryWidget::setHistory(const DImageHistory& history) { d->model->setupModelData(history.entries()); } void FiltersHistoryWidget::setEnabledEntries(int count) { d->model->setEnabledEntries(count); } void FiltersHistoryWidget::disableEntries(int count) { d->model->disableEntries(count); } void FiltersHistoryWidget::enableEntries(int count) { d->model->enableEntries(count); } void FiltersHistoryWidget::clearData() { - d->model->removeRows(0, d->model->rowCount(), d->model->index(0,0)); + d->model->removeRows(0, d->model->rowCount(), d->model->index(0, 0)); } } // namespace Digikam diff --git a/core/libs/widgets/itemview/itemviewcategorized.cpp b/core/libs/widgets/itemview/itemviewcategorized.cpp index 49371a3420..ce26055d74 100644 --- a/core/libs/widgets/itemview/itemviewcategorized.cpp +++ b/core/libs/widgets/itemview/itemviewcategorized.cpp @@ -1,1057 +1,1057 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-01-16 * Description : Qt item view for images * * Copyright (C) 2009-2012 by Marcel Wiesweg * Copyright (C) 2011-2019 by Gilles Caulier * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "itemviewcategorized.h" // Qt includes #include #include #include #include #include #include // Local includes #include "digikam_debug.h" #include "thememanager.h" #include "ditemdelegate.h" #include "abstractitemdragdrophandler.h" #include "itemviewtooltip.h" namespace Digikam { class Q_DECL_HIDDEN ItemViewCategorized::Private { public: explicit Private(ItemViewCategorized* const q) : delegate(0), toolTip(0), notificationToolTip(0), showToolTip(false), usePointingHand(true), scrollStepFactor(10), currentMouseEvent(0), ensureOneSelectedItem(false), ensureInitialSelectedItem(false), scrollCurrentToCenter(false), mouseButtonPressed(Qt::NoButton), hintAtSelectionRow(-1), q(q) { } QModelIndex scrollPositionHint() const; public: DItemDelegate* delegate; ItemViewToolTip* toolTip; ItemViewToolTip* notificationToolTip; bool showToolTip; bool usePointingHand; int scrollStepFactor; QMouseEvent* currentMouseEvent; bool ensureOneSelectedItem; bool ensureInitialSelectedItem; bool scrollCurrentToCenter; Qt::MouseButton mouseButtonPressed; QPersistentModelIndex hintAtSelectionIndex; int hintAtSelectionRow; QPersistentModelIndex hintAtScrollPosition; ItemViewCategorized* const q; }; QModelIndex ItemViewCategorized::Private::scrollPositionHint() const { if (q->verticalScrollBar()->value() == q->verticalScrollBar()->minimum()) { return QModelIndex(); } QModelIndex hint = q->currentIndex(); // If the user scrolled, do not take current item, but first visible if (!hint.isValid() || !q->viewport()->rect().intersects(q->visualRect(hint))) { QList visibleIndexes = q->categorizedIndexesIn(q->viewport()->rect()); if (!visibleIndexes.isEmpty()) { hint = visibleIndexes.first(); } } return hint; } // ------------------------------------------------------------------------------- ItemViewCategorized::ItemViewCategorized(QWidget* const parent) : DCategorizedView(parent), d(new Private(this)) { setViewMode(QListView::IconMode); setLayoutDirection(Qt::LeftToRight); setFlow(QListView::LeftToRight); setResizeMode(QListView::Adjust); setMovement(QListView::Static); setWrapping(true); // important optimization for layouting setUniformItemSizes(true); // disable "feature" from DCategorizedView setDrawDraggedItems(false); setSelectionMode(QAbstractItemView::ExtendedSelection); setDragEnabled(true); setEditTriggers(QAbstractItemView::NoEditTriggers); viewport()->setAcceptDrops(true); setMouseTracking(true); connect(this, SIGNAL(activated(QModelIndex)), this, SLOT(slotActivated(QModelIndex))); connect(this, SIGNAL(clicked(QModelIndex)), this, SLOT(slotClicked(QModelIndex))); connect(this, SIGNAL(entered(QModelIndex)), this, SLOT(slotEntered(QModelIndex))); connect(ThemeManager::instance(), SIGNAL(signalThemeChanged()), this, SLOT(slotThemeChanged())); } ItemViewCategorized::~ItemViewCategorized() { delete d; } void ItemViewCategorized::setToolTip(ItemViewToolTip* tip) { d->toolTip = tip; } void ItemViewCategorized::setItemDelegate(DItemDelegate* delegate) { if (d->delegate == delegate) { return; } if (d->delegate) { disconnect(d->delegate, SIGNAL(gridSizeChanged(QSize)), this, SLOT(slotGridSizeChanged(QSize))); } d->delegate = delegate; DCategorizedView::setItemDelegate(d->delegate); connect(d->delegate, SIGNAL(gridSizeChanged(QSize)), this, SLOT(slotGridSizeChanged(QSize))); } void ItemViewCategorized::setSpacing(int spacing) { d->delegate->setSpacing(spacing); } void ItemViewCategorized::setUsePointingHandCursor(bool useCursor) { d->usePointingHand = useCursor; } void ItemViewCategorized::setScrollStepGranularity(int factor) { d->scrollStepFactor = qMax(1, factor); } DItemDelegate* ItemViewCategorized::delegate() const { return d->delegate; } int ItemViewCategorized::numberOfSelectedIndexes() const { return selectedIndexes().size(); } void ItemViewCategorized::toFirstIndex() { QModelIndex index = moveCursor(MoveHome, Qt::NoModifier); clearSelection(); setCurrentIndex(index); scrollToTop(); } void ItemViewCategorized::toLastIndex() { QModelIndex index = moveCursor(MoveEnd, Qt::NoModifier); clearSelection(); setCurrentIndex(index); scrollToBottom(); } void ItemViewCategorized::toNextIndex() { toIndex(moveCursor(MoveNext, Qt::NoModifier)); } void ItemViewCategorized::toPreviousIndex() { toIndex(moveCursor(MovePrevious, Qt::NoModifier)); } void ItemViewCategorized::toIndex(const QModelIndex& index) { if (!index.isValid()) { return; } clearSelection(); setCurrentIndex(index); scrollTo(index); } void ItemViewCategorized::awayFromSelection() { QItemSelection selection = selectionModel()->selection(); if (selection.isEmpty()) { return; } const QModelIndex first = model()->index(0, 0); const QModelIndex last = model()->index(model()->rowCount() - 1, 0); if (selection.contains(first) && selection.contains(last)) { QItemSelection remaining(first, last); remaining.merge(selection, QItemSelectionModel::Toggle); QList indexes = remaining.indexes(); if (indexes.isEmpty()) { clearSelection(); setCurrentIndex(QModelIndex()); } else { toIndex(remaining.indexes().first()); } } else if (selection.contains(last)) { setCurrentIndex(selection.indexes().first()); toPreviousIndex(); } else { setCurrentIndex(selection.indexes().last()); toNextIndex(); } } void ItemViewCategorized::scrollToRelaxed(const QModelIndex& index, QAbstractItemView::ScrollHint hint) { if (viewport()->rect().intersects(visualRect(index))) { return; } scrollTo(index, hint); } void ItemViewCategorized::invertSelection() { const QModelIndex topLeft = model()->index(0, 0); const QModelIndex bottomRight = model()->index(model()->rowCount() - 1, 0); const QItemSelection selection(topLeft, bottomRight); selectionModel()->select(selection, QItemSelectionModel::Toggle); } void ItemViewCategorized::setSelectedIndexes(const QList& indexes) { if (selectedIndexes() == indexes) { return; } QItemSelection mySelection; foreach (const QModelIndex& index, indexes) { mySelection.select(index, index); } selectionModel()->select(mySelection, QItemSelectionModel::ClearAndSelect); } void ItemViewCategorized::setToolTipEnabled(bool enable) { d->showToolTip = enable; } bool ItemViewCategorized::isToolTipEnabled() const { return d->showToolTip; } void ItemViewCategorized::slotThemeChanged() { viewport()->update(); } void ItemViewCategorized::slotSetupChanged() { viewport()->update(); } void ItemViewCategorized::slotGridSizeChanged(const QSize& gridSize) { setGridSize(gridSize); if (!gridSize.isNull()) { horizontalScrollBar()->setSingleStep(gridSize.width() / d->scrollStepFactor); verticalScrollBar()->setSingleStep(gridSize.height() / d->scrollStepFactor); } } void ItemViewCategorized::updateDelegateSizes() { QStyleOptionViewItem option = viewOptions(); /* int frameAroundContents = 0; if (style()->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents)) { frameAroundContents = style()->pixelMetric(QStyle::PM_DefaultFrameWidth) * 2; } const int contentWidth = viewport()->width() - 1 - frameAroundContents - style()->pixelMetric(QStyle::PM_ScrollBarExtent, 0, verticalScrollBar()); const int contentHeight = viewport()->height() - 1 - frameAroundContents - style()->pixelMetric(QStyle::PM_ScrollBarExtent, 0, horizontalScrollBar()); option.rect = QRect(0, 0, contentWidth, contentHeight); */ option.rect = QRect(QPoint(0, 0), viewport()->size()); d->delegate->setDefaultViewOptions(option); } void ItemViewCategorized::slotActivated(const QModelIndex& index) { Qt::KeyboardModifiers modifiers = Qt::NoModifier; if (d->currentMouseEvent) { // Ignore activation if Ctrl or Shift is pressed (for selection) modifiers = d->currentMouseEvent->modifiers(); Qt::MouseButton button = d->currentMouseEvent->button(); const bool shiftKeyPressed = modifiers & Qt::ShiftModifier; const bool controlKeyPressed = modifiers & Qt::ControlModifier; const bool rightClickPressed = button & Qt::RightButton; if (shiftKeyPressed || controlKeyPressed || rightClickPressed) { return; } // if the activation is caused by mouse click (not keyboard) // we need to check the hot area if (d->currentMouseEvent->isAccepted() && !d->delegate->acceptsActivation(d->currentMouseEvent->pos(), visualRect(index), index)) { return; } } d->currentMouseEvent = 0; indexActivated(index, modifiers); } void ItemViewCategorized::slotClicked(const QModelIndex& index) { if (d->currentMouseEvent) { emit clicked(d->currentMouseEvent, index); } } void ItemViewCategorized::slotEntered(const QModelIndex& index) { if (d->currentMouseEvent) { emit entered(d->currentMouseEvent, index); } } void ItemViewCategorized::reset() { DCategorizedView::reset(); // FIXME : Emitting this causes a crash importstackedview, because the model is not yet set. // atm there's a check against null models though. emit selectionChanged(); emit selectionCleared(); d->ensureInitialSelectedItem = true; d->hintAtScrollPosition = QModelIndex(); d->hintAtSelectionIndex = QModelIndex(); d->hintAtSelectionRow = -1; verticalScrollBar()->setValue(verticalScrollBar()->minimum()); horizontalScrollBar()->setValue(horizontalScrollBar()->minimum()); } void ItemViewCategorized::selectionChanged(const QItemSelection& selectedItems, const QItemSelection& deselectedItems) { DCategorizedView::selectionChanged(selectedItems, deselectedItems); emit selectionChanged(); if (!selectionModel()->hasSelection()) { emit selectionCleared(); } userInteraction(); } void ItemViewCategorized::rowsInserted(const QModelIndex& parent, int start, int end) { DCategorizedView::rowsInserted(parent, start, end); if (start == 0) { ensureSelectionAfterChanges(); } } void ItemViewCategorized::rowsRemoved(const QModelIndex& parent, int start, int end) { DCategorizedView::rowsRemoved(parent, start, end); if (d->scrollCurrentToCenter) { scrollTo(currentIndex(), QAbstractItemView::PositionAtCenter); } } void ItemViewCategorized::rowsAboutToBeRemoved(const QModelIndex& parent, int start, int end) { DCategorizedView::rowsAboutToBeRemoved(parent, start, end); // Ensure one selected item int totalToRemove = end - start + 1; bool remainingRows = model()->rowCount(parent) > totalToRemove; if (!remainingRows) { return; } QItemSelection removed(model()->index(start, 0), model()->index(end, 0)); if (selectionModel()->hasSelection()) { // find out which selected indexes are left after rows are removed QItemSelection selected = selectionModel()->selection(); QModelIndex current = currentIndex(); QModelIndex indexToAnchor; if (selected.contains(current)) { indexToAnchor = current; } else if (!selected.isEmpty()) { indexToAnchor = selected.first().topLeft(); } selected.merge(removed, QItemSelectionModel::Deselect); if (selected.isEmpty()) { QModelIndex newCurrent = nextIndexHint(indexToAnchor, removed.first() /*a range*/); setCurrentIndex(newCurrent); } } QModelIndex hint = d->scrollPositionHint(); if (removed.contains(hint)) { d->hintAtScrollPosition = nextIndexHint(hint, removed.first() /*a range*/); } } void ItemViewCategorized::layoutAboutToBeChanged() { if (selectionModel()) { d->ensureOneSelectedItem = selectionModel()->hasSelection(); } else { qCWarning(DIGIKAM_GENERAL_LOG) << "Called without selection model, check whether the models are ok.."; } QModelIndex current = currentIndex(); // store some hints so that if all selected items were removed do not need to default to 0,0. if (d->ensureOneSelectedItem) { QItemSelection currentSelection = selectionModel()->selection(); QModelIndex indexToAnchor; if (currentSelection.contains(current)) { indexToAnchor = current; } else if (!currentSelection.isEmpty()) { indexToAnchor = currentSelection.first().topLeft(); } if (indexToAnchor.isValid()) { d->hintAtSelectionRow = indexToAnchor.row(); d->hintAtSelectionIndex = nextIndexHint(indexToAnchor, QItemSelectionRange(indexToAnchor)); } } // some precautions to keep current scroll position d->hintAtScrollPosition = d->scrollPositionHint(); } QModelIndex ItemViewCategorized::nextIndexHint(const QModelIndex& indexToAnchor, const QItemSelectionRange& removed) const { Q_UNUSED(indexToAnchor); if (removed.bottomRight().row() == model()->rowCount() - 1) { if (removed.topLeft().row() == 0) { return QModelIndex(); } return model()->index(removed.topLeft().row() - 1, 0); // last remaining, no next one left } else { return model()->index(removed.bottomRight().row() + 1, 0); // next remaining } } void ItemViewCategorized::layoutWasChanged() { // connected queued to layoutChanged() ensureSelectionAfterChanges(); if (d->hintAtScrollPosition.isValid()) { scrollToRelaxed(d->hintAtScrollPosition); d->hintAtScrollPosition = QModelIndex(); } else { scrollToRelaxed(currentIndex()); } } void ItemViewCategorized::userInteraction() { // as soon as the user did anything affecting selection, we don't interfere anymore d->ensureInitialSelectedItem = false; d->hintAtSelectionIndex = QModelIndex(); } void ItemViewCategorized::ensureSelectionAfterChanges() { if (d->ensureInitialSelectedItem && model()->rowCount()) { // Ensure the item (0,0) is selected, if the model was reset previously // and the user did not change the selection since reset. // Caveat: Item at (0,0) may have changed. bool hadInitial = d->ensureInitialSelectedItem; d->ensureInitialSelectedItem = false; d->ensureOneSelectedItem = false; - QModelIndex index = model()->index(0,0); + QModelIndex index = model()->index(0, 0); if (index.isValid()) { selectionModel()->select(index, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Clear); setCurrentIndex(index); // we want ensureInitial set to false if and only if the selection // is done from any other place than the previous line (i.e., by user action) // Effect: we select whatever is the current index(0,0) if (hadInitial) { d->ensureInitialSelectedItem = true; } } } else if (d->ensureOneSelectedItem) { // ensure we have a selection if there was one before d->ensureOneSelectedItem = false; if (model()->rowCount() && selectionModel()->selection().isEmpty()) { QModelIndex index; if (d->hintAtSelectionIndex.isValid()) { index = d->hintAtSelectionIndex; } else if (d->hintAtSelectionRow != -1) { index = model()->index(qMin(model()->rowCount(), d->hintAtSelectionRow), 0); } else { index = currentIndex(); } if (!index.isValid()) { index = model()->index(0, 0); } d->hintAtSelectionRow = -1; d->hintAtSelectionIndex = QModelIndex(); if (index.isValid()) { setCurrentIndex(index); selectionModel()->select(index, QItemSelectionModel::SelectCurrent); } } } } QModelIndex ItemViewCategorized::indexForCategoryAt(const QPoint& pos) const { return categoryAt(pos); } QModelIndex ItemViewCategorized::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) { QModelIndex current = currentIndex(); if (!current.isValid()) { return DCategorizedView::moveCursor(cursorAction, modifiers); } // We want a simple wrapping navigation. // Default behavior we do not want: right/left does never change row; Next/Previous is equivalent to Down/Up switch (cursorAction) { case MoveNext: case MoveRight: { QModelIndex next = model()->index(current.row() + 1, 0); if (next.isValid()) { return next; } else { return current; } break; } case MovePrevious: case MoveLeft: { QModelIndex previous = model()->index(current.row() - 1, 0); if (previous.isValid()) { return previous; } else { return current; } break; } default: break; } return DCategorizedView::moveCursor(cursorAction, modifiers); } void ItemViewCategorized::showContextMenuOnIndex(QContextMenuEvent*, const QModelIndex&) { // implemented in subclass } void ItemViewCategorized::showContextMenu(QContextMenuEvent*) { // implemented in subclass } void ItemViewCategorized::indexActivated(const QModelIndex&, Qt::KeyboardModifiers) { } bool ItemViewCategorized::showToolTip(const QModelIndex& index, QStyleOptionViewItem& option, QHelpEvent* he) { QRect innerRect; QPoint pos; if (he) { pos = he->pos(); } else { pos = option.rect.center(); } if (d->delegate->acceptsToolTip(he->pos(), option.rect, index, &innerRect)) { if (!innerRect.isNull()) { option.rect = innerRect; } d->toolTip->show(option, index); return true; } return false; } void ItemViewCategorized::contextMenuEvent(QContextMenuEvent* event) { userInteraction(); QModelIndex index = indexAt(event->pos()); if (index.isValid()) { showContextMenuOnIndex(event, index); } else { showContextMenu(event); } } void ItemViewCategorized::leaveEvent(QEvent*) { hideIndexNotification(); if (d->mouseButtonPressed != Qt::RightButton) { d->mouseButtonPressed = Qt::NoButton; } } void ItemViewCategorized::mousePressEvent(QMouseEvent* event) { userInteraction(); const QModelIndex index = indexAt(event->pos()); // Clear selection on click on empty area. Standard behavior, but not done by QAbstractItemView for some reason. Qt::KeyboardModifiers modifiers = event->modifiers(); const Qt::MouseButton button = event->button(); const bool rightButtonPressed = button & Qt::RightButton; const bool shiftKeyPressed = modifiers & Qt::ShiftModifier; const bool controlKeyPressed = modifiers & Qt::ControlModifier; d->mouseButtonPressed = button; if (!index.isValid() && !rightButtonPressed && !shiftKeyPressed && !controlKeyPressed) { clearSelection(); } // store event for entered(), clicked(), activated() signal handlers if (!rightButtonPressed) { d->currentMouseEvent = event; } else { d->currentMouseEvent = 0; } DCategorizedView::mousePressEvent(event); if (!index.isValid()) { emit viewportClicked(event); } } void ItemViewCategorized::mouseReleaseEvent(QMouseEvent* event) { userInteraction(); if (d->scrollCurrentToCenter) { scrollTo(currentIndex(), QAbstractItemView::PositionAtCenter); } DCategorizedView::mouseReleaseEvent(event); } void ItemViewCategorized::mouseMoveEvent(QMouseEvent* event) { QModelIndex index = indexAt(event->pos()); QRect indexVisualRect; if (index.isValid()) { indexVisualRect = visualRect(index); if (d->usePointingHand && d->delegate->acceptsActivation(event->pos(), indexVisualRect, index)) { setCursor(Qt::PointingHandCursor); } else { unsetCursor(); } } else { unsetCursor(); } if (d->notificationToolTip && d->notificationToolTip->isVisible()) { if (!d->notificationToolTip->rect().adjusted(-50, -50, 50, 50).contains(event->pos())) { hideIndexNotification(); } } DCategorizedView::mouseMoveEvent(event); d->delegate->mouseMoved(event, indexVisualRect, index); } void ItemViewCategorized::wheelEvent(QWheelEvent* event) { // DCategorizedView updates the single step at some occasions in a private methody horizontalScrollBar()->setSingleStep(d->delegate->gridSize().height() / d->scrollStepFactor); verticalScrollBar()->setSingleStep(d->delegate->gridSize().width() / d->scrollStepFactor); if (event->modifiers() & Qt::ControlModifier) { const int delta = event->delta(); if (delta > 0) { emit zoomInStep(); } else if (delta < 0) { emit zoomOutStep(); } event->accept(); return; } if (verticalScrollBarPolicy() == Qt::ScrollBarAlwaysOff && event->orientation() == Qt::Vertical) { QWheelEvent n(event->pos(), event->globalPos(), event->delta(), event->buttons(), event->modifiers(), Qt::Horizontal); QApplication::sendEvent(horizontalScrollBar(), &n); event->setAccepted(n.isAccepted()); } else { DCategorizedView::wheelEvent(event); } } void ItemViewCategorized::keyPressEvent(QKeyEvent* event) { userInteraction(); if (event == QKeySequence::Copy) { copy(); event->accept(); return; } else if (event == QKeySequence::Paste) { paste(); event->accept(); return; } /* // from dolphincontroller.cpp const QItemSelectionModel* selModel = m_itemView->selectionModel(); const QModelIndex currentIndex = selModel->currentIndex(); const bool trigger = currentIndex.isValid() && ((event->key() == Qt::Key_Return) || (event->key() == Qt::Key_Enter)) && (selModel->selectedIndexes().count() > 0); if (trigger) { const QModelIndexList indexList = selModel->selectedIndexes(); foreach (const QModelIndex& index, indexList) { emit itemTriggered(itemForIndex(index)); } } */ DCategorizedView::keyPressEvent(event); emit keyPressed(event); } void ItemViewCategorized::resizeEvent(QResizeEvent* e) { QModelIndex oldPosition = d->scrollPositionHint(); DCategorizedView::resizeEvent(e); updateDelegateSizes(); scrollToRelaxed(oldPosition, QAbstractItemView::PositionAtTop); } bool ItemViewCategorized::viewportEvent(QEvent* event) { switch (event->type()) { case QEvent::FontChange: { updateDelegateSizes(); break; } case QEvent::ToolTip: { if (!d->showToolTip) { return true; } QHelpEvent* he = static_cast(event); const QModelIndex index = indexAt(he->pos()); if (!index.isValid()) { break; } QStyleOptionViewItem option = viewOptions(); option.rect = visualRect(index); option.state |= (index == currentIndex() ? QStyle::State_HasFocus : QStyle::State_None); showToolTip(index, option, he); return true; } default: break; } return DCategorizedView::viewportEvent(event); } void ItemViewCategorized::showIndexNotification(const QModelIndex& index, const QString& message) { hideIndexNotification(); if (!index.isValid()) { return; } if (!d->notificationToolTip) { d->notificationToolTip = new ItemViewToolTip(this); } d->notificationToolTip->setTipContents(message); QStyleOptionViewItem option = viewOptions(); option.rect = visualRect(index); option.state |= (index == currentIndex() ? QStyle::State_HasFocus : QStyle::State_None); d->notificationToolTip->show(option, index); } void ItemViewCategorized::hideIndexNotification() { if (d->notificationToolTip) { d->notificationToolTip->hide(); } } /** * cut(), copy(), paste(), dragEnterEvent(), dragMoveEvent(), dropEvent(), startDrag() * are implemented by DragDropViewImplementation */ QModelIndex ItemViewCategorized::mapIndexForDragDrop(const QModelIndex& index) const { return filterModel()->mapToSource(index); } QPixmap ItemViewCategorized::pixmapForDrag(const QList& indexes) const { QStyleOptionViewItem option = viewOptions(); option.rect = viewport()->rect(); return d->delegate->pixmapForDrag(option, indexes); } void ItemViewCategorized::setScrollCurrentToCenter(bool enabled) { d->scrollCurrentToCenter = enabled; } void ItemViewCategorized::scrollTo(const QModelIndex& index, ScrollHint hint) { if (d->scrollCurrentToCenter && d->mouseButtonPressed == Qt::NoButton) { hint = QAbstractItemView::PositionAtCenter; } d->mouseButtonPressed = Qt::NoButton; DCategorizedView::scrollTo(index, hint); } } // namespace Digikam diff --git a/core/tests/geolocation/editor/test_rgtagmodel.cpp b/core/tests/geolocation/editor/test_rgtagmodel.cpp index a4bf1725f1..ac5d553802 100644 --- a/core/tests/geolocation/editor/test_rgtagmodel.cpp +++ b/core/tests/geolocation/editor/test_rgtagmodel.cpp @@ -1,285 +1,285 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-06-21 * Description : Test for RG tag model. * * Copyright (C) 2010 by Michael G. Hansen * Copyright (C) 2010 by Gabriel Voicu * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "test_rgtagmodel.h" // Qt includes #include #include // local includes #include "simpletreemodel.h" #include "rgtagmodel.h" #include "modeltest.h" using namespace Digikam; /** * @brief Dummy test that does nothing */ void TestRGTagModel::testNoOp() { } /** * @brief Create an RGTagModel, but leave it empty */ void TestRGTagModel::testModelEmpty() { SimpleTreeModel* const treeModel = new SimpleTreeModel(1, this); new ModelTest(treeModel, this); RGTagModel* const tagModel = new RGTagModel(treeModel, this); new ModelTest(tagModel, this); } void TestRGTagModel::testModel2() { SimpleTreeModel* const treeModel = new SimpleTreeModel(1, this); new ModelTest(treeModel, this); // add some items before the tagModel is created: SimpleTreeModel::Item* const treeItem1 = treeModel->addItem(); const QPersistentModelIndex treeItem1Index = treeModel->itemToIndex(treeItem1); SimpleTreeModel::Item* const treeItem11 = treeModel->addItem(treeItem1); const QPersistentModelIndex treeItem11Index = treeModel->itemToIndex(treeItem11); SimpleTreeModel::Item* const treeItem2 = treeModel->addItem(); const QPersistentModelIndex treeItem2Index = treeModel->itemToIndex(treeItem2); RGTagModel* const tagModel = new RGTagModel(treeModel, this); // modeltest will be created at the end of this function to make sure it does not influence the result! // first, verify the row and column counts: QCOMPARE(tagModel->rowCount(QModelIndex()), 2); QCOMPARE(tagModel->columnCount(QModelIndex()), 1); // now test toSourceIndex: const QModelIndex tagItem1Index = tagModel->index(0, 0); Q_ASSERT(tagItem1Index.isValid()); QCOMPARE(tagModel->rowCount(tagItem1Index), 1); QCOMPARE(tagModel->columnCount(tagItem1Index), 1); const QModelIndex tagItem1IndexSource = tagModel->toSourceIndex(tagItem1Index); Q_ASSERT(tagItem1IndexSource.isValid()); Q_ASSERT(tagItem1IndexSource==treeItem1Index); const QModelIndex tagItem2Index = tagModel->index(1, 0); Q_ASSERT(tagItem2Index.isValid()); QCOMPARE(tagModel->rowCount(tagItem2Index), 0); QCOMPARE(tagModel->columnCount(tagItem2Index), 1); const QModelIndex tagItem2IndexSource = tagModel->toSourceIndex(tagItem2Index); Q_ASSERT(tagItem2IndexSource.isValid()); Q_ASSERT(tagItem2IndexSource==treeItem2Index); const QModelIndex tagItem11Index = tagModel->index(0, 0, tagItem1Index); Q_ASSERT(tagItem11Index.isValid()); QCOMPARE(tagModel->rowCount(tagItem11Index), 0); QCOMPARE(tagModel->columnCount(tagItem11Index), 1); const QModelIndex tagItem11IndexSource = tagModel->toSourceIndex(tagItem11Index); Q_ASSERT(tagItem11IndexSource.isValid()); Q_ASSERT(tagItem11IndexSource==treeItem11Index); // add modeltest as the last test: new ModelTest(tagModel, this); } void TestRGTagModel::testModel3() { SimpleTreeModel* const treeModel = new SimpleTreeModel(1, this); new ModelTest(treeModel, this); // add some items before the tagModel is created: SimpleTreeModel::Item* const treeItem1 = treeModel->addItem(); const QPersistentModelIndex treeItem1Index = treeModel->itemToIndex(treeItem1); SimpleTreeModel::Item* const treeItem11 = treeModel->addItem(treeItem1); const QPersistentModelIndex treeItem11Index = treeModel->itemToIndex(treeItem11); SimpleTreeModel::Item* const treeItem2 = treeModel->addItem(); const QPersistentModelIndex treeItem2Index = treeModel->itemToIndex(treeItem2); RGTagModel* const tagModel = new RGTagModel(treeModel, this); // modeltest will be created at the end of this function to make sure it does not influence the result! // first, verify the row and column counts: QCOMPARE(tagModel->rowCount(QModelIndex()), 2); QCOMPARE(tagModel->columnCount(QModelIndex()), 1); // now test toSourceIndex: //const QModelIndex tagItem1Index = tagModel->index(0, 0); // now add a new item to the source model, before the existing item: SimpleTreeModel::Item* const treeItem11a = treeModel->addItem(treeItem1, 0); const QPersistentModelIndex treeItem11aIndex = treeModel->itemToIndex(treeItem11a); QCOMPARE(treeItem11Index.row(), 1); QCOMPARE(treeItem11aIndex.row(), 0); // now add a new item to the source model, this time in the middle: SimpleTreeModel::Item* const treeItem11b = treeModel->addItem(treeItem1, 1); const QPersistentModelIndex treeItem11bIndex = treeModel->itemToIndex(treeItem11b); QCOMPARE(treeItem11Index.row(), 2); QCOMPARE(treeItem11aIndex.row(), 0); QCOMPARE(treeItem11bIndex.row(), 1); // add modeltest as the last test: new ModelTest(tagModel, this); } void TestRGTagModel::testModel1() { SimpleTreeModel* const treeModel = new SimpleTreeModel(1, this); new ModelTest(treeModel, this); // add some items before the tagModel is created: SimpleTreeModel::Item* const treeItem1 = treeModel->addItem(); QPersistentModelIndex treeItem1Index = treeModel->itemToIndex(treeItem1); SimpleTreeModel::Item* const treeItem11 = treeModel->addItem(treeItem1); QPersistentModelIndex treeItem11Index = treeModel->itemToIndex(treeItem11); RGTagModel* const tagModel = new RGTagModel(treeModel, this); // TODO: make sure the ModelTest does not find any errors, currently it does find errors ;-) //new ModelTest(tagModel, this); // simple tests Q_ASSERT(tagModel->rowCount()==treeModel->rowCount()); const QPersistentModelIndex tagItem1Index = tagModel->fromSourceIndex(treeItem1Index); Q_ASSERT(tagItem1Index.isValid()); qDebug()<rowCount(tagItem1Index)==treeModel->rowCount(treeItem1Index)); // make sure the tagModel handles items inserted after it was created // - both top level SimpleTreeModel::Item* const treeItem2 = treeModel->addItem(); QPersistentModelIndex treeItem2Index = treeModel->itemToIndex(treeItem2); Q_ASSERT(tagModel->rowCount()==treeModel->rowCount()); const QPersistentModelIndex tagItem2Index = tagModel->fromSourceIndex(treeItem2Index); // - and sub items: SimpleTreeModel::Item* const treeItem21 = treeModel->addItem(treeItem2); Q_ASSERT(tagItem2Index.isValid()); Q_ASSERT(tagModel->rowCount(tagItem2Index)==treeModel->rowCount(treeItem2Index)); const QPersistentModelIndex tagItem11Index = tagModel->fromSourceIndex(treeItem11Index); Q_ASSERT(tagItem11Index.isValid()); QPersistentModelIndex treeItem21Index = treeModel->itemToIndex(treeItem21); const QPersistentModelIndex tagItem21Index = tagModel->fromSourceIndex(treeItem21Index); Q_ASSERT(tagItem21Index.isValid()); // now make sure we can descend: const QModelIndex ti1 = tagModel->index(0, 0); Q_ASSERT(ti1.isValid()); Q_ASSERT(ti1 == tagItem1Index); // descends level 1 row 0 const QModelIndex ti11 = tagModel->index(0, 0, ti1); Q_ASSERT(ti11.isValid()); Q_ASSERT(ti11 == tagItem11Index); qDebug()<<"----------------------_"; // descends level 0 row 1 const QModelIndex ti2 = tagModel->index(1, 0); Q_ASSERT(ti2.isValid()); Q_ASSERT(ti2 == tagItem2Index); // descends level 1 row 0 QModelIndex ti21 = tagModel->index(0, 0, ti2); Q_ASSERT(ti21.isValid()); Q_ASSERT(ti21 == tagItem21Index); //checks invalid index - const QModelIndex ti111 = tagModel->index(0,0, ti11); + const QModelIndex ti111 = tagModel->index(0, 0, ti11); Q_ASSERT(!ti111.isValid()); //checks parent of tagItem1 const QModelIndex parent_ti1 = tagModel->parent(ti1); Q_ASSERT(!parent_ti1.isValid()); //checks parent of tagItem11 const QModelIndex parent_ti11 = tagModel->parent(ti11); Q_ASSERT(parent_ti11 == tagItem1Index); //checks parent of tagItem2 const QModelIndex parent_ti2 = tagModel->parent(ti2); Q_ASSERT(!parent_ti2.isValid()); const QModelIndex parent_ti21 = tagModel->parent(ti21); Q_ASSERT(parent_ti21.isValid()); } void TestRGTagModel::testModelSpacerTags() { SimpleTreeModel* const treeModel = new SimpleTreeModel(1, this); new ModelTest(treeModel, this); // add some items before the tagModel is created: SimpleTreeModel::Item* const treeItem1 = treeModel->addItem(); QPersistentModelIndex treeItem1Index = treeModel->itemToIndex(treeItem1); treeItem1->data = QLatin1String("oldChildren"); SimpleTreeModel::Item* const treeItem11 = treeModel->addItem(treeItem1); QPersistentModelIndex treeItem11Index = treeModel->itemToIndex(treeItem11); RGTagModel* const tagModel = new RGTagModel(treeModel, this); // TODO: make sure the ModelTest does not find any errors, currently it does find errors ;-) new ModelTest(tagModel, this); const QPersistentModelIndex tagItem11Index = tagModel->fromSourceIndex(treeItem11Index); Q_ASSERT(tagItem11Index.isValid()); qDebug()<<"Worked before adding spacers"; //insert spacer below ti21 tagModel->addSpacerTag(QModelIndex(), QLatin1String("{Country}")); tagModel->addNewTag(QModelIndex(), QLatin1String("New Tag")); qDebug()<<"Added the spacers."; - const QModelIndex index11 = tagModel->index(0,0); - const QModelIndex index12 = tagModel->index(1,0); - const QModelIndex index13 = tagModel->index(2,0); + const QModelIndex index11 = tagModel->index(0, 0); + const QModelIndex index12 = tagModel->index(1, 0); + const QModelIndex index13 = tagModel->index(2, 0); qDebug()<data(index11, Qt::DisplayRole); qDebug()<data(index12, Qt::DisplayRole); qDebug()<data(index13, Qt::DisplayRole); -// qDebug()<data(2,0,QModelIndex()); +// qDebug()<data(2, 0, QModelIndex()); /* qDebug()<<"VERIFY IF NEW TAG EXISTS:"; - QModelIndex ti211Spacer = tagModel->index(0,0,ti21); + QModelIndex ti211Spacer = tagModel->index(0, 0, ti21); Q_ASSERT(ti211Spacer.isValid()); */ } QTEST_GUILESS_MAIN(TestRGTagModel) diff --git a/core/utilities/geolocation/geoiface/backends/backendmarble.cpp b/core/utilities/geolocation/geoiface/backends/backendmarble.cpp index 05e4878d2c..53c530259d 100644 --- a/core/utilities/geolocation/geoiface/backends/backendmarble.cpp +++ b/core/utilities/geolocation/geoiface/backends/backendmarble.cpp @@ -1,1916 +1,1916 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2009-12-08 * Description : Marble-backend for geolocation interface * * Copyright (C) 2010-2019 by Gilles Caulier * Copyright (C) 2009-2011 by Michael G. Hansen * Copyright (C) 2014 by Justus Schwartz * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "backendmarble.h" // Qt includes #include #include #include #include // KDE includes #include #include // Marble includes #include #include #include #include #include #include #include #include // Local includes #include "backendmarblelayer.h" #include "abstractmarkertiler.h" #include "mapwidget.h" #include "geomodelhelper.h" #include "trackmanager.h" #include "digikam_debug.h" namespace Digikam { class Q_DECL_HIDDEN BMInternalWidgetInfo { public: explicit BMInternalWidgetInfo() { bmLayer = 0; } BackendMarbleLayer* bmLayer; }; } // namespace Digikam Q_DECLARE_METATYPE(Digikam::BMInternalWidgetInfo) namespace Digikam { class Q_DECL_HIDDEN BackendMarble::Private { public: explicit Private() : marbleWidget(0), actionGroupMapTheme(0), actionGroupProjection(0), actionGroupFloatItems(0), actionShowCompass(0), actionShowScaleBar(0), actionShowNavigation(0), actionShowOverviewMap(0), cacheMapTheme(QLatin1String("atlas")), cacheProjection(QLatin1String("spherical")), cacheShowCompass(false), cacheShowScaleBar(false), cacheShowNavigation(false), cacheShowOverviewMap(false), cacheZoom(900), havePotentiallyMouseMovingObject(false), haveMouseMovingObject(false), mouseMoveClusterIndex(-1), mouseMoveMarkerIndex(), mouseMoveObjectCoordinates(), - mouseMoveCenterOffset(0,0), + mouseMoveCenterOffset(0, 0), dragDropMarkerCount(0), dragDropMarkerPos(), clustersDirtyCacheProjection(), clustersDirtyCacheLat(), clustersDirtyCacheLon(), displayedRectangle(), firstSelectionScreenPoint(), firstSelectionPoint(), activeState(false), widgetIsDocked(false), blockingZoomWhileChangingTheme(false), trackCache(), bmLayer(0) { } QPointer marbleWidget; QActionGroup* actionGroupMapTheme; QActionGroup* actionGroupProjection; QActionGroup* actionGroupFloatItems; QAction* actionShowCompass; QAction* actionShowScaleBar; QAction* actionShowNavigation; QAction* actionShowOverviewMap; QString cacheMapTheme; QString cacheProjection; bool cacheShowCompass; bool cacheShowScaleBar; bool cacheShowNavigation; bool cacheShowOverviewMap; int cacheZoom; bool havePotentiallyMouseMovingObject; bool haveMouseMovingObject; int mouseMoveClusterIndex; QPersistentModelIndex mouseMoveMarkerIndex; GeoCoordinates mouseMoveObjectCoordinates; QPoint mouseMoveCenterOffset; int dragDropMarkerCount; QPoint dragDropMarkerPos; int clustersDirtyCacheProjection; qreal clustersDirtyCacheLat; qreal clustersDirtyCacheLon; GeoCoordinates::Pair displayedRectangle; QPoint firstSelectionScreenPoint; QPoint intermediateSelectionScreenPoint; GeoCoordinates firstSelectionPoint; GeoCoordinates intermediateSelectionPoint; bool activeState; bool widgetIsDocked; bool blockingZoomWhileChangingTheme; QHash trackCache; BackendMarbleLayer* bmLayer; }; BackendMarble::BackendMarble(const QExplicitlySharedDataPointer& sharedData, QObject* const parent) : MapBackend(sharedData, parent), d(new Private()) { createActions(); } BackendMarble::~BackendMarble() { /// @todo Should we leave our widget in this list and not destroy it? GeoIfaceGlobalObject* const go = GeoIfaceGlobalObject::instance(); go->removeMyInternalWidgetFromPool(this); if (d->marbleWidget) { d->marbleWidget->removeLayer(d->bmLayer); delete d->bmLayer; delete d->marbleWidget; } delete d; } QString BackendMarble::backendName() const { return QLatin1String("marble"); } QString BackendMarble::backendHumanName() const { return i18n("Marble Virtual Globe"); } QWidget* BackendMarble::mapWidget() { if (!d->marbleWidget) { GeoIfaceGlobalObject* const go = GeoIfaceGlobalObject::instance(); GeoIfaceInternalWidgetInfo info; if (go->getInternalWidgetFromPool(this, &info)) { d->marbleWidget = qobject_cast(info.widget); const BMInternalWidgetInfo intInfo = info.backendData.value(); d->bmLayer = intInfo.bmLayer; if (d->bmLayer) { d->bmLayer->setBackend(this); } else { qCWarning(DIGIKAM_GEOIFACE_LOG) << "Marble widget instance is null!"; } } else { d->marbleWidget = new Marble::MarbleWidget(); d->bmLayer = new BackendMarbleLayer(this); d->marbleWidget->addLayer(d->bmLayer); } // hide Marble bookmark action in the right button context menu // TODO: check with a new Marble version if the bookmark action position is still valid if (d->marbleWidget->popupMenu()) { QList actions = d->marbleWidget->popupMenu()->findChildren(); if (actions.count() > 4 && actions[4]) actions[4]->setVisible(false); } d->marbleWidget->installEventFilter(this); connect(d->marbleWidget, SIGNAL(zoomChanged(int)), this, SLOT(slotMarbleZoomChanged())); // set a backend first /// @todo Do this only if we are set active! applyCacheToWidget(); emit(signalBackendReadyChanged(backendName())); } return d->marbleWidget; } void BackendMarble::releaseWidget(GeoIfaceInternalWidgetInfo* const info) { info->widget->removeEventFilter(this); BMInternalWidgetInfo intInfo = info->backendData.value(); if (intInfo.bmLayer) { intInfo.bmLayer->setBackend(0); } disconnect(d->marbleWidget, SIGNAL(zoomChanged(int)), this, SLOT(slotMarbleZoomChanged())); info->currentOwner = 0; info->state = GeoIfaceInternalWidgetInfo::InternalWidgetReleased; d->marbleWidget = 0; d->bmLayer = 0; emit(signalBackendReadyChanged(backendName())); } GeoCoordinates BackendMarble::getCenter() const { if (!d->marbleWidget) { return GeoCoordinates(); } return GeoCoordinates(d->marbleWidget->centerLatitude(), d->marbleWidget->centerLongitude()); } void BackendMarble::setCenter(const GeoCoordinates& coordinate) { if (!d->marbleWidget) { return; } d->marbleWidget->setCenterLatitude(coordinate.lat()); d->marbleWidget->setCenterLongitude(coordinate.lon()); } bool BackendMarble::isReady() const { return d->marbleWidget != 0; } void BackendMarble::zoomIn() { if (!d->marbleWidget) { return; } d->marbleWidget->zoomIn(); d->marbleWidget->repaint(); } void BackendMarble::zoomOut() { if (!d->marbleWidget) { return; } d->marbleWidget->zoomOut(); d->marbleWidget->repaint(); } void BackendMarble::createActions() { // map theme: d->actionGroupMapTheme = new QActionGroup(this); d->actionGroupMapTheme->setExclusive(true); connect(d->actionGroupMapTheme, &QActionGroup::triggered, this, &BackendMarble::slotMapThemeActionTriggered); QAction* const actionAtlas = new QAction(d->actionGroupMapTheme); actionAtlas->setCheckable(true); actionAtlas->setText(i18n("Atlas map")); actionAtlas->setData(QLatin1String("atlas")); QAction* const actionOpenStreetmap = new QAction(d->actionGroupMapTheme); actionOpenStreetmap->setCheckable(true); actionOpenStreetmap->setText(i18n("OpenStreetMap")); actionOpenStreetmap->setData(QLatin1String("openstreetmap")); // projection: d->actionGroupProjection = new QActionGroup(this); d->actionGroupProjection->setExclusive(true); connect(d->actionGroupProjection, &QActionGroup::triggered, this, &BackendMarble::slotProjectionActionTriggered); QAction* const actionSpherical = new QAction(d->actionGroupProjection); actionSpherical->setCheckable(true); actionSpherical->setText(i18nc("Spherical projection", "Spherical")); actionSpherical->setData(QLatin1String("spherical")); QAction* const actionMercator = new QAction(d->actionGroupProjection); actionMercator->setCheckable(true); actionMercator->setText(i18n("Mercator")); actionMercator->setData(QLatin1String("mercator")); QAction* const actionEquirectangular = new QAction(d->actionGroupProjection); actionEquirectangular->setCheckable(true); actionEquirectangular->setText(i18n("Equirectangular")); actionEquirectangular->setData(QLatin1String("equirectangular")); // float items: d->actionGroupFloatItems = new QActionGroup(this); d->actionGroupFloatItems->setExclusive(false); connect(d->actionGroupFloatItems, &QActionGroup::triggered, this, &BackendMarble::slotFloatSettingsTriggered); d->actionShowCompass = new QAction(i18n("Show compass"), d->actionGroupFloatItems); d->actionShowCompass->setData(QLatin1String("showcompass")); d->actionShowCompass->setCheckable(true); d->actionGroupFloatItems->addAction(d->actionShowCompass); d->actionShowScaleBar = new QAction(i18n("Show scale bar"), d->actionGroupFloatItems); d->actionShowScaleBar->setData(QLatin1String("showscalebar")); d->actionShowScaleBar->setCheckable(true); d->actionGroupFloatItems->addAction(d->actionShowScaleBar); d->actionShowNavigation = new QAction(i18n("Show navigation"), d->actionGroupFloatItems); d->actionShowNavigation->setData(QLatin1String("shownavigation")); d->actionShowNavigation->setCheckable(true); d->actionGroupFloatItems->addAction(d->actionShowNavigation); d->actionShowOverviewMap = new QAction(i18n("Show overview map"), d->actionGroupFloatItems); d->actionShowOverviewMap->setData(QLatin1String("showoverviewmap")); d->actionShowOverviewMap->setCheckable(true); d->actionGroupFloatItems->addAction(d->actionShowOverviewMap); } void BackendMarble::addActionsToConfigurationMenu(QMenu* const configurationMenu) { GEOIFACE_ASSERT(configurationMenu != 0); configurationMenu->addSeparator(); const QList mapThemeActions = d->actionGroupMapTheme->actions(); for (int i = 0 ; i < mapThemeActions.count() ; ++i) { configurationMenu->addAction(mapThemeActions.at(i)); } configurationMenu->addSeparator(); // TODO: we need a parent for this guy! QMenu* const projectionSubMenu = new QMenu(i18n("Projection"), configurationMenu); configurationMenu->addMenu(projectionSubMenu); const QList projectionActions = d->actionGroupProjection->actions(); for (int i = 0 ; i < projectionActions.count() ; ++i) { projectionSubMenu->addAction(projectionActions.at(i)); } QMenu* const floatItemsSubMenu = new QMenu(i18n("Float items"), configurationMenu); configurationMenu->addMenu(floatItemsSubMenu); const QList floatActions = d->actionGroupFloatItems->actions(); for (int i = 0 ; i < floatActions.count() ; ++i) { floatItemsSubMenu->addAction(floatActions.at(i)); } updateActionAvailability(); } void BackendMarble::slotMapThemeActionTriggered(QAction* action) { setMapTheme(action->data().toString()); } QString BackendMarble::getMapTheme() const { // TODO: read the theme from the marblewidget! return d->cacheMapTheme; } void BackendMarble::setMapTheme(const QString& newMapTheme) { d->cacheMapTheme = newMapTheme; if (!d->marbleWidget) { return; } // Changing the map theme changes the zoom - we want to try to keep the zoom constant d->blockingZoomWhileChangingTheme = true; // Remember the zoom from the cache. The zoom of the widget may not have been set yet! const int oldMarbleZoom = d->cacheZoom; if (newMapTheme == QLatin1String("atlas")) { d->marbleWidget->setMapThemeId(QLatin1String("earth/srtm/srtm.dgml")); } else if (newMapTheme == QLatin1String("openstreetmap")) { d->marbleWidget->setMapThemeId(QLatin1String("earth/openstreetmap/openstreetmap.dgml")); } // the float items are reset when the theme is changed: setShowCompass(d->cacheShowCompass); setShowScaleBar(d->cacheShowScaleBar); setShowNavigation(d->cacheShowNavigation); setShowOverviewMap(d->cacheShowOverviewMap); // make sure the zoom level is within the allowed range int targetZoomLevel = oldMarbleZoom; if (oldMarbleZoom > d->marbleWidget->maximumZoom()) { targetZoomLevel = d->marbleWidget->maximumZoom(); } else if (oldMarbleZoom < d->marbleWidget->minimumZoom()) { targetZoomLevel = d->marbleWidget->minimumZoom(); } if (targetZoomLevel!=oldMarbleZoom) { // our zoom level had to be adjusted, therefore unblock // the signal now to allow the change to propagate d->blockingZoomWhileChangingTheme = false; } d->marbleWidget->zoomView(targetZoomLevel); d->blockingZoomWhileChangingTheme = false; updateActionAvailability(); } void BackendMarble::saveSettingsToGroup(KConfigGroup* const group) { GEOIFACE_ASSERT(group != 0); if (!group) { return; } group->writeEntry("Marble Map Theme", d->cacheMapTheme); group->writeEntry("Marble Projection", d->cacheProjection); group->writeEntry("Marble Show Compass", d->cacheShowCompass); group->writeEntry("Marble Show Scale Bar", d->cacheShowScaleBar); group->writeEntry("Marble Show Navigation", d->cacheShowNavigation); group->writeEntry("Marble Show Overview Map", d->cacheShowOverviewMap); } void BackendMarble::readSettingsFromGroup(const KConfigGroup* const group) { GEOIFACE_ASSERT(group != 0); if (!group) { return; } setMapTheme(group->readEntry("Marble Map Theme", d->cacheMapTheme)); setProjection(group->readEntry("Marble Projection", d->cacheProjection)); setShowCompass(group->readEntry("Marble Show Compass", d->cacheShowCompass)); setShowScaleBar(group->readEntry("Marble Show Scale Bar", d->cacheShowScaleBar)); setShowNavigation(group->readEntry("Marble Show Navigation", d->cacheShowNavigation)); setShowOverviewMap(group->readEntry("Marble Show Overview Map", d->cacheShowOverviewMap)); } void BackendMarble::updateMarkers() { if (!d->marbleWidget) { return; } // just redraw, that's it: d->marbleWidget->update(); } bool BackendMarble::screenCoordinates(const GeoCoordinates& coordinates, QPoint* const point) { if (!d->marbleWidget) { return false; } if (!coordinates.hasCoordinates()) { return false; } qreal x, y; const bool isVisible = d->marbleWidget->screenCoordinates(coordinates.lon(), coordinates.lat(), x, y); if (!isVisible) { return false; } if (point) { *point = QPoint(x, y); } return true; } bool BackendMarble::geoCoordinates(const QPoint& point, GeoCoordinates* const coordinates) const { if (!d->marbleWidget) { return false; } // apparently, MarbleWidget::GeoCoordinates can return true even if the object is not on the screen // check that the point is in the visible range: if (!d->marbleWidget->rect().contains(point)) { return false; } qreal lat, lon; const bool isVisible = d->marbleWidget->geoCoordinates(point.x(), point.y(), lon, lat, Marble::GeoDataCoordinates::Degree); if (!isVisible) { return false; } if (coordinates) { *coordinates = GeoCoordinates(lat, lon); } return true; } /** * @brief Replacement for Marble::GeoPainter::drawPixmap which takes a pixel offset * * @param painter Marble::GeoPainter on which to draw the pixmap * @param pixmap Pixmap to be drawn * @param coordinates GeoCoordinates where the image is to be drawn * @param offsetPoint Point in the @p pixmap which should be at @p coordinates */ void BackendMarble::GeoPainter_drawPixmapAtCoordinates(Marble::GeoPainter* const painter, const QPixmap& pixmap, const GeoCoordinates& coordinates, const QPoint& offsetPoint) { // base point starts at the top left of the pixmap // try to convert the coordinates to pixels QPoint pointOnScreen; if (!screenCoordinates(coordinates, &pointOnScreen)) { return; } // Marble::GeoPainter::drawPixmap(coordinates, pixmap) draws the pixmap centered on coordinates // therefore we calculate the pixel position of the center of the image if its offsetPoint // is to be at pointOnScreen: const QSize pixmapSize = pixmap.size(); const QPoint pixmapHalfSize = QPoint(pixmapSize.width() / 2, pixmapSize.height() / 2); const QPoint drawPoint = pointOnScreen + pixmapHalfSize - offsetPoint; // now re-calculate the coordinates of the new pixel coordinates: GeoCoordinates drawGeoCoordinates; if (!geoCoordinates(drawPoint, &drawGeoCoordinates)) { return; } // convert to Marble datatype and draw: const Marble::GeoDataCoordinates mcoord = drawGeoCoordinates.toMarbleCoordinates(); painter->drawPixmap(mcoord, pixmap); } void BackendMarble::marbleCustomPaint(Marble::GeoPainter* painter) { if (!d->activeState) { return; } // check whether the parameters of the map changed and we may have to update the clusters: if ((d->clustersDirtyCacheLat != d->marbleWidget->centerLatitude()) || (d->clustersDirtyCacheLon != d->marbleWidget->centerLongitude()) || (d->clustersDirtyCacheProjection != d->marbleWidget->projection())) { /* qCDebug(DIGIKAM_GEOIFACE_LOG) << d->marbleWidget->centerLatitude() << d->marbleWidget->centerLongitude() << d->marbleWidget->projection(); */ d->clustersDirtyCacheLat = d->marbleWidget->centerLatitude(); d->clustersDirtyCacheLon = d->marbleWidget->centerLongitude(); d->clustersDirtyCacheProjection = d->marbleWidget->projection(); s->worldMapWidget->markClustersAsDirty(); } painter->save(); if (s->trackManager) { if (s->trackManager->getVisibility()) { TrackManager::Track::List const& tracks = s->trackManager->getTrackList(); for (int trackIdx = 0 ; trackIdx < tracks.count() ; ++trackIdx) { TrackManager::Track const& track = tracks.at(trackIdx); if (track.points.count() < 2) { continue; } Marble::GeoDataLineString lineString; if (d->trackCache.contains(track.id)) { lineString = d->trackCache.value(track.id); } else { for (int coordIdx = 0 ; coordIdx < track.points.count() ; ++coordIdx) { GeoCoordinates const& coordinates = track.points.at(coordIdx).coordinates; const Marble::GeoDataCoordinates marbleCoordinates = coordinates.toMarbleCoordinates(); lineString << marbleCoordinates; } d->trackCache.insert(track.id, lineString); } /// @TODO looks a bit too thick IMHO when you zoom out. /// Maybe adjust to zoom level? QColor trackColor = track.color; trackColor.setAlpha(180); painter->setPen(QPen(QBrush(trackColor),5)); painter->drawPolyline(lineString); } } } for (int i = 0 ; i < s->ungroupedModels.count() ; ++i) { GeoModelHelper* const modelHelper = s->ungroupedModels.at(i); if (!modelHelper->modelFlags().testFlag(GeoModelHelper::FlagVisible)) continue; QAbstractItemModel* const model = modelHelper->model(); // render all visible markers: for (int row = 0 ; row < model->rowCount() ; ++row) { const QModelIndex currentIndex = model->index(row, 0); GeoCoordinates markerCoordinates; if (!modelHelper->itemCoordinates(currentIndex, &markerCoordinates)) continue; // is the marker being moved right now? if (currentIndex == d->mouseMoveMarkerIndex) { markerCoordinates = d->mouseMoveObjectCoordinates; } QPoint markerPoint; if (!screenCoordinates(markerCoordinates, &markerPoint)) { /// @todo This check does not work properly in all cases! // the marker is not visible continue; } QPoint markerOffsetPoint; QPixmap markerPixmap; const bool haveMarkerPixmap = modelHelper->itemIcon(currentIndex, &markerOffsetPoint, 0, &markerPixmap, 0); if (!haveMarkerPixmap || markerPixmap.isNull()) { markerPixmap = GeoIfaceGlobalObject::instance()->getStandardMarkerPixmap(); markerOffsetPoint = QPoint(markerPixmap.width() / 2, markerPixmap.height() - 1); } GeoPainter_drawPixmapAtCoordinates(painter, markerPixmap, markerCoordinates, markerOffsetPoint); } } int markersInMovingCluster = 0; if (s->markerModel) { // now for the clusters: s->worldMapWidget->updateClusters(); for (int i = 0 ; i < s->clusterList.size() ; ++i) { const GeoIfaceCluster& cluster = s->clusterList.at(i); GeoCoordinates clusterCoordinates = cluster.coordinates; int markerCountOverride = cluster.markerCount; GeoGroupState selectionStateOverride = cluster.groupState; if (d->haveMouseMovingObject && (d->mouseMoveClusterIndex >= 0)) { bool movingSelectedMarkers = s->clusterList.at(d->mouseMoveClusterIndex).groupState != SelectedNone; if (movingSelectedMarkers) { markersInMovingCluster += cluster.markerSelectedCount; markerCountOverride -= cluster.markerSelectedCount; selectionStateOverride = SelectedNone; } else if (d->mouseMoveClusterIndex == i) { markerCountOverride = 0; } if (markerCountOverride == 0) continue; } QPoint clusterPoint; if (!screenCoordinates(clusterCoordinates, &clusterPoint)) { /// @todo This check does not work properly in all cases! // cluster is not visible continue; } QPoint clusterOffsetPoint; const QPixmap clusterPixmap = s->worldMapWidget->getDecoratedPixmapForCluster(i, &selectionStateOverride, &markerCountOverride, &clusterOffsetPoint); GeoPainter_drawPixmapAtCoordinates(painter, clusterPixmap, clusterCoordinates, clusterOffsetPoint); } } // now render the mouse-moving cluster, if there is one: if (d->haveMouseMovingObject && (d->mouseMoveClusterIndex >= 0)) { const GeoIfaceCluster& cluster = s->clusterList.at(d->mouseMoveClusterIndex); GeoCoordinates clusterCoordinates = d->mouseMoveObjectCoordinates; int markerCountOverride = (markersInMovingCluster>0)?markersInMovingCluster:cluster.markerCount; GeoGroupState selectionStateOverride = cluster.groupState; QPoint clusterPoint; if (screenCoordinates(clusterCoordinates, &clusterPoint)) { // determine the colors: QColor fillColor; QColor strokeColor; Qt::PenStyle strokeStyle; QColor labelColor; QString labelText; s->worldMapWidget->getColorInfos(d->mouseMoveClusterIndex, &fillColor, &strokeColor, &strokeStyle, &labelText, &labelColor, &selectionStateOverride, &markerCountOverride); QString pixmapName = fillColor.name().mid(1); if (cluster.groupState == SelectedAll) { pixmapName += QLatin1String("-selected"); } if (cluster.groupState == SelectedSome) { pixmapName += QLatin1String("-someselected"); } const QPixmap& markerPixmap = GeoIfaceGlobalObject::instance()->getMarkerPixmap(pixmapName); painter->drawPixmap(clusterPoint.x() - markerPixmap.width() / 2, clusterPoint.y() - markerPixmap.height() - 1, markerPixmap); } } // now render the drag-and-drop marker, if there is one: if (d->dragDropMarkerCount>0) { // determine the colors: QColor fillColor; QColor strokeColor; Qt::PenStyle strokeStyle; QColor labelColor; QString labelText; s->worldMapWidget->getColorInfos(SelectedAll, d->dragDropMarkerCount, &fillColor, &strokeColor, &strokeStyle, &labelText, &labelColor); QString pixmapName = fillColor.name().mid(1); pixmapName += QLatin1String("-selected"); const QPixmap& markerPixmap = GeoIfaceGlobalObject::instance()->getMarkerPixmap(pixmapName); painter->drawPixmap(d->dragDropMarkerPos.x() - markerPixmap.width() / 2, d->dragDropMarkerPos.y() - markerPixmap.height() - 1, markerPixmap); } // here we draw the selection rectangle which is being made by the user right now /// @todo merge drawing of the rectangles into one function if (d->displayedRectangle.first.hasCoordinates()) { drawSearchRectangle(painter, d->displayedRectangle, false); } // draw the current or old search rectangle if (s->selectionRectangle.first.hasCoordinates()) { drawSearchRectangle(painter, s->selectionRectangle, d->intermediateSelectionPoint.hasCoordinates()); } painter->restore(); } QString BackendMarble::getProjection() const { /// @todo Do we actually need to read out the projection from the widget??? if (d->marbleWidget) { const Marble::Projection currentProjection = d->marbleWidget->projection(); switch (currentProjection) { case Marble::Equirectangular: d->cacheProjection = QLatin1String("equirectangular"); break; case Marble::Mercator: d->cacheProjection = QLatin1String("mercator"); break; default: case Marble::Spherical: d->cacheProjection = QLatin1String("spherical"); break; } } return d->cacheProjection; } void BackendMarble::setProjection(const QString& newProjection) { d->cacheProjection = newProjection; if (d->marbleWidget) { if (newProjection == QLatin1String("equirectangular")) { d->marbleWidget->setProjection(Marble::Equirectangular); } else if (newProjection == QLatin1String("mercator")) { d->marbleWidget->setProjection(Marble::Mercator); } else // "spherical" { d->marbleWidget->setProjection(Marble::Spherical); } } updateActionAvailability(); } void BackendMarble::slotProjectionActionTriggered(QAction* action) { setProjection(action->data().toString()); } void BackendMarble::setShowCompass(const bool state) { d->cacheShowCompass = state; updateActionAvailability(); if (d->marbleWidget) { Marble::AbstractFloatItem* const item = d->marbleWidget->floatItem(QLatin1String("compass")); if (item) { item->setVisible(state); } } } void BackendMarble::setShowScaleBar(const bool state) { d->cacheShowScaleBar = state; updateActionAvailability(); if (d->marbleWidget) { Marble::AbstractFloatItem* const item = d->marbleWidget->floatItem(QLatin1String("scalebar")); if (item) { item->setVisible(state); } } } void BackendMarble::setShowNavigation(const bool state) { d->cacheShowNavigation = state; updateActionAvailability(); if (d->marbleWidget) { Marble::AbstractFloatItem* const item = d->marbleWidget->floatItem(QLatin1String("navigation")); if (item) { item->setVisible(state); } } } void BackendMarble::setShowOverviewMap(const bool state) { d->cacheShowOverviewMap = state; updateActionAvailability(); if (d->marbleWidget) { Marble::AbstractFloatItem* const item = d->marbleWidget->floatItem(QLatin1String("overviewmap")); if (item) { item->setVisible(state); } } } void BackendMarble::slotFloatSettingsTriggered(QAction* action) { const QString actionIdString = action->data().toString(); const bool actionState = action->isChecked(); if (actionIdString == QLatin1String("showcompass")) { setShowCompass(actionState); } else if (actionIdString == QLatin1String("showscalebar")) { setShowScaleBar(actionState); } else if (actionIdString == QLatin1String("shownavigation")) { setShowNavigation(actionState); } else if (actionIdString == QLatin1String("showoverviewmap")) { setShowOverviewMap(actionState); } } void BackendMarble::slotClustersNeedUpdating() { if (!d->marbleWidget) { return; } // tell the widget to redraw: d->marbleWidget->update(); } void BackendMarble::updateClusters() { // clusters are only needed during redraw } QSize BackendMarble::mapSize() const { return d->marbleWidget->size(); } void BackendMarble::slotMarbleZoomChanged() { // ignore the zoom change while changing the map theme if (d->blockingZoomWhileChangingTheme) { return; } const QString newZoomString = getZoom(); s->worldMapWidget->markClustersAsDirty(); updateActionAvailability(); emit(signalZoomChanged(newZoomString)); } void BackendMarble::setZoom(const QString& newZoom) { const QString myZoomString = s->worldMapWidget->convertZoomToBackendZoom(newZoom, QLatin1String("marble")); GEOIFACE_ASSERT(myZoomString.startsWith(QLatin1String("marble:"))); const int myZoom = myZoomString.mid(QString::fromLatin1("marble:").length()).toInt(); d->cacheZoom = myZoom; d->marbleWidget->setZoom(myZoom); } QString BackendMarble::getZoom() const { if (d->marbleWidget) { d->cacheZoom = d->marbleWidget->zoom(); } return QString::fromLatin1("marble:%1").arg(d->cacheZoom); } int BackendMarble::getMarkerModelLevel() { // return AbstractMarkerTiler::TileIndex::MaxLevel-1; GEOIFACE_ASSERT(isReady()); if (!isReady()) { return 0; } const int currentZoom = d->marbleWidget->zoom(); int tileLevel = 0; const Marble::Projection currentProjection = d->marbleWidget->projection(); switch (currentProjection) { case Marble::Equirectangular: if (currentZoom < 1000) { tileLevel = 4; } else if (currentZoom < 1400) { tileLevel = 5; } else if (currentZoom < 1900) { tileLevel = 6; } else if (currentZoom < 2300) { tileLevel = 7; } else if (currentZoom < 2800) { tileLevel = 8; } else { tileLevel = 9; } // note: level 9 is not enough starting at zoom level 3200 break; case Marble::Mercator: if (currentZoom < 1000) { tileLevel = 4; } else if (currentZoom < 1500) { tileLevel = 5; } else if (currentZoom < 1900) { tileLevel = 6; } else if (currentZoom < 2300) { tileLevel = 7; } else if (currentZoom < 2800) { tileLevel = 8; } else { tileLevel = 9; } // note: level 9 is not enough starting at zoom level 3200 break; default: case Marble::Spherical: if (currentZoom < 1300) { tileLevel = 5; } else if (currentZoom < 1800) { tileLevel = 6; } else if (currentZoom < 2200) { tileLevel = 7; } else if (currentZoom < 2800) { tileLevel = 8; } else { tileLevel = 9; } // note: level 9 is not enough starting at zoom level 3200 break; } // TODO: verify that this assertion was too strict // GEOIFACE_ASSERT(tileLevel <= AbstractMarkerTiler::TileIndex::MaxLevel-1); return tileLevel; } GeoCoordinates::PairList BackendMarble::getNormalizedBounds() { if (!d->marbleWidget) { return GeoCoordinates::PairList(); } const Marble::GeoDataLatLonAltBox marbleBounds = d->marbleWidget->viewport()->viewLatLonAltBox(); // qCDebug(DIGIKAM_GEOIFACE_LOG) << marbleBounds.toString(GeoDataCoordinates::Degree); const GeoCoordinates::Pair boundsPair = GeoCoordinates::makePair( marbleBounds.south(Marble::GeoDataCoordinates::Degree), marbleBounds.west(Marble::GeoDataCoordinates::Degree), marbleBounds.north(Marble::GeoDataCoordinates::Degree), marbleBounds.east(Marble::GeoDataCoordinates::Degree)); // qCDebug(DIGIKAM_GEOIFACE_LOG) << boundsPair.first<marbleWidget) { // event not filtered, because it is not for our object return QObject::eventFilter(object, event); } // we only handle mouse events: if ((event->type() != QEvent::MouseButtonPress) && (event->type() != QEvent::MouseMove) && (event->type() != QEvent::MouseButtonRelease)) { return QObject::eventFilter(object, event); } // no filtering in pan mode if (s->currentMouseMode == MouseModePan) { return QObject::eventFilter(object, event); } QMouseEvent* const mouseEvent = static_cast(event); bool doFilterEvent = false; if (s->currentMouseMode == MouseModeRegionSelection) { if ((event->type() == QEvent::MouseButtonPress) && (mouseEvent->button() == Qt::LeftButton)) { // we need to filter this event because otherwise Marble displays // a left click context menu doFilterEvent = true; } else if (event->type() == QEvent::MouseMove) { if (d->firstSelectionPoint.hasCoordinates()) { d->intermediateSelectionPoint.clear(); geoCoordinates(mouseEvent->pos(), &d->intermediateSelectionPoint); d->intermediateSelectionScreenPoint = mouseEvent->pos(); qCDebug(DIGIKAM_GEOIFACE_LOG) << d->firstSelectionScreenPoint << " " << d->intermediateSelectionScreenPoint; qreal lonWest, latNorth, lonEast, latSouth; if (d->firstSelectionScreenPoint.x() < d->intermediateSelectionScreenPoint.x()) { lonWest = d->firstSelectionPoint.lon(); lonEast = d->intermediateSelectionPoint.lon(); } else { lonWest = d->intermediateSelectionPoint.lon(); lonEast = d->firstSelectionPoint.lon(); } if (d->firstSelectionScreenPoint.y() < d->intermediateSelectionScreenPoint.y()) { latNorth = d->firstSelectionPoint.lat(); latSouth = d->intermediateSelectionPoint.lat(); } else { latNorth = d->intermediateSelectionPoint.lat(); latSouth = d->firstSelectionPoint.lat(); } const GeoCoordinates::Pair selectionCoordinates( GeoCoordinates(latNorth, lonWest), GeoCoordinates(latSouth, lonEast)); //setSelectionRectangle(selectionCoordinates, SelectionRectangle); d->displayedRectangle = selectionCoordinates; d->marbleWidget->update(); } doFilterEvent = true; } else if ((event->type() == QEvent::MouseButtonRelease) && (mouseEvent->button() == Qt::LeftButton)) { if (!d->firstSelectionPoint.hasCoordinates()) { geoCoordinates(mouseEvent->pos(), &d->firstSelectionPoint); d->firstSelectionScreenPoint = mouseEvent->pos(); } else { d->intermediateSelectionPoint.clear(); GeoCoordinates secondSelectionPoint; geoCoordinates(mouseEvent->pos(), &secondSelectionPoint); QPoint secondSelectionScreenPoint = mouseEvent->pos(); qreal lonWest, latNorth, lonEast, latSouth; if (d->firstSelectionScreenPoint.x() < secondSelectionScreenPoint.x()) { lonWest = d->firstSelectionPoint.lon(); lonEast = secondSelectionPoint.lon(); } else { lonWest = secondSelectionPoint.lon(); lonEast = d->firstSelectionPoint.lon(); } if (d->firstSelectionScreenPoint.y() < secondSelectionScreenPoint.y()) { latNorth = d->firstSelectionPoint.lat(); latSouth = secondSelectionPoint.lat(); } else { latNorth = secondSelectionPoint.lat(); latSouth = d->firstSelectionPoint.lat(); } const GeoCoordinates::Pair selectionCoordinates( GeoCoordinates(latNorth, lonWest), GeoCoordinates(latSouth, lonEast)); d->firstSelectionPoint.clear(); d->displayedRectangle.first.clear(); emit signalSelectionHasBeenMade(selectionCoordinates); } doFilterEvent = true; } } else { if ((event->type() == QEvent::MouseButtonPress) && (mouseEvent->button() == Qt::LeftButton)) { // check whether the user clicked on one of our items: // scan in reverse order, because the user would expect // the topmost marker to be picked up and not the // one below // if (s->specialMarkersModel) // { // for (int row = s->specialMarkersModel->rowCount()-1; row >= 0; --row) // { // const QModelIndex currentIndex = s->specialMarkersModel->index(row, 0); // const GeoCoordinates currentCoordinates = s->specialMarkersModel->data(currentIndex, s->specialMarkersCoordinatesRole).value(); // // QPoint markerPoint; // if (!screenCoordinates(currentCoordinates, &markerPoint)) // { // continue; // } // // const int markerPixmapHeight = s->markerPixmap.height(); // const int markerPixmapWidth = s->markerPixmap.width(); // const QRect markerRect(markerPoint.x()-markerPixmapWidth/2, markerPoint.y()-markerPixmapHeight, markerPixmapWidth, markerPixmapHeight); // if (!markerRect.contains(mouseEvent->pos())) // { // continue; // } // // // the user clicked on a marker: // d->mouseMoveMarkerIndex = QPersistentModelIndex(currentIndex); // d->mouseMoveCenterOffset = mouseEvent->pos() - markerPoint; // d->mouseMoveObjectCoordinates = currentCoordinates; // doFilterEvent = true; // d->havePotentiallyMouseMovingObject = true; // // break; // } // } if (/*s->inEditMode &&*/ !doFilterEvent) { // scan in reverse order of painting! for (int clusterIndex = s->clusterList.count()-1 ; clusterIndex >= 0 ; --clusterIndex) { const GeoIfaceCluster& cluster = s->clusterList.at(clusterIndex); const GeoCoordinates currentCoordinates = cluster.coordinates; QPoint clusterPoint; if (!screenCoordinates(currentCoordinates, &clusterPoint)) { continue; } QRect markerRect; markerRect.setSize(cluster.pixmapSize); markerRect.moveTopLeft(clusterPoint); markerRect.translate(-cluster.pixmapOffset); if (!markerRect.contains(mouseEvent->pos())) { continue; } /// @todo For circles, make sure the mouse is really above the circle and not just in the rectangle! // the user clicked on a cluster: d->mouseMoveClusterIndex = clusterIndex; d->mouseMoveCenterOffset = mouseEvent->pos() - clusterPoint; d->mouseMoveObjectCoordinates = currentCoordinates; doFilterEvent = true; d->havePotentiallyMouseMovingObject = true; s->haveMovingCluster = true; break; } } } else if ((event->type() == QEvent::MouseMove) && (d->havePotentiallyMouseMovingObject || d->haveMouseMovingObject)) { if ((!s->modificationsAllowed) || (!s->markerModel->tilerFlags().testFlag(AbstractMarkerTiler::FlagMovable)) || ((d->mouseMoveClusterIndex >= 0) && s->showThumbnails)) { // clusters only move in edit mode and when edit mode is enabled /// @todo This blocks moving of the map in non-edit mode d->havePotentiallyMouseMovingObject = false; d->mouseMoveClusterIndex = -1; d->mouseMoveMarkerIndex = QPersistentModelIndex(); s->haveMovingCluster = false; } else { // mark the object as really moving: d->havePotentiallyMouseMovingObject = false; d->haveMouseMovingObject = true; // a cluster or marker is being moved. update its position: QPoint newMarkerPoint = mouseEvent->pos() - d->mouseMoveCenterOffset; QPoint snapPoint; if (findSnapPoint(newMarkerPoint, &snapPoint, 0, 0)) { newMarkerPoint = snapPoint; } GeoCoordinates newCoordinates; if (geoCoordinates(newMarkerPoint, &newCoordinates)) { d->mouseMoveObjectCoordinates = newCoordinates; d->marbleWidget->update(); } } } else if ((event->type() == QEvent::MouseButtonRelease) && (d->havePotentiallyMouseMovingObject)) { // the object was not moved, but just clicked once if (d->mouseMoveClusterIndex >= 0) { const int mouseMoveClusterIndex = d->mouseMoveClusterIndex; // we are done with the clicked object // reset these before sending the signal d->havePotentiallyMouseMovingObject = false; d->mouseMoveClusterIndex = -1; d->mouseMoveMarkerIndex = QPersistentModelIndex(); s->haveMovingCluster = false; doFilterEvent = true; emit(signalClustersClicked(QIntList() << mouseMoveClusterIndex)); } else { // we are done with the clicked object: d->havePotentiallyMouseMovingObject = false; d->mouseMoveClusterIndex = -1; d->mouseMoveMarkerIndex = QPersistentModelIndex(); s->haveMovingCluster = false; } } else if ((event->type() == QEvent::MouseButtonRelease) && (d->haveMouseMovingObject)) { // the object was dropped, apply the coordinates if it is on screen: const QPoint dropMarkerPoint = mouseEvent->pos() - d->mouseMoveCenterOffset; QPair snapTargetIndex(-1, QModelIndex()); GeoCoordinates newCoordinates; bool haveValidPoint = findSnapPoint(dropMarkerPoint, 0, &newCoordinates, &snapTargetIndex); if (!haveValidPoint) { haveValidPoint = geoCoordinates(dropMarkerPoint, &newCoordinates); } if (haveValidPoint) { if (d->mouseMoveMarkerIndex.isValid()) { /* // the marker was dropped to valid coordinates s->specialMarkersModel->setData(d->mouseMoveMarkerIndex, QVariant::fromValue(newCoordinates), s->specialMarkersCoordinatesRole); QList markerIndices; markerIndices << d->mouseMoveMarkerIndex; // also emit a signal that the marker was moved: emit(signalSpecialMarkersMoved(markerIndices)); */ } else { // a cluster is being moved s->clusterList[d->mouseMoveClusterIndex].coordinates = newCoordinates; emit(signalClustersMoved(QIntList() << d->mouseMoveClusterIndex, snapTargetIndex)); } } d->haveMouseMovingObject = false; d->mouseMoveClusterIndex = -1; d->mouseMoveMarkerIndex = QPersistentModelIndex(); d->marbleWidget->update(); s->haveMovingCluster = false; } } if (doFilterEvent) { return true; } return QObject::eventFilter(object, event); } /* void BackendMarble::updateDragDropMarker(const QPoint& pos, const GeoIfaceDragData* const dragData) { if (!dragData) { d->dragDropMarkerCount = 0; } else { d->dragDropMarkerPos = pos; d->dragDropMarkerCount = dragData->itemCount; } d->marbleWidget->update(); // TODO: hide dragged markers on the map } void BackendMarble::updateDragDropMarkerPosition(const QPoint& pos) { d->dragDropMarkerPos = pos; d->marbleWidget->update(); } */ void BackendMarble::updateActionAvailability() { if ((!d->activeState) || (!d->marbleWidget)) { return; } qCDebug(DIGIKAM_GEOIFACE_LOG) << d->cacheZoom << d->marbleWidget->maximumZoom() << d->marbleWidget->minimumZoom(); s->worldMapWidget->getControlAction(QLatin1String("zoomin"))->setEnabled(d->cacheZoommarbleWidget->maximumZoom()); s->worldMapWidget->getControlAction(QLatin1String("zoomout"))->setEnabled(d->cacheZoom>d->marbleWidget->minimumZoom()); const QList mapThemeActions = d->actionGroupMapTheme->actions(); for (int i = 0 ; isetChecked(mapThemeActions.at(i)->data().toString() == getMapTheme()); } const QList projectionActions = d->actionGroupProjection->actions(); for (int i = 0 ; isetChecked(projectionActions.at(i)->data().toString() == d->cacheProjection); } d->actionShowCompass->setChecked(d->cacheShowCompass); d->actionShowScaleBar->setChecked(d->cacheShowScaleBar); d->actionShowNavigation->setChecked(d->cacheShowNavigation); d->actionShowOverviewMap->setChecked(d->cacheShowOverviewMap); } void BackendMarble::slotThumbnailAvailableForIndex(const QVariant& index, const QPixmap& pixmap) { if (!d->marbleWidget) { return; } qCDebug(DIGIKAM_GEOIFACE_LOG) << index << pixmap.size(); if (pixmap.isNull() || !s->showThumbnails) { return; } // TODO: properly reject pixmaps with the wrong size const int expectedThumbnailSize = s->worldMapWidget->getUndecoratedThumbnailSize(); if ((pixmap.size().height() > expectedThumbnailSize) || (pixmap.size().width() > expectedThumbnailSize)) { return; } // re-paint the map d->marbleWidget->update(); } void BackendMarble::slotUngroupedModelChanged(const int index) { Q_UNUSED(index) if (!d->marbleWidget) { return; } d->marbleWidget->update(); } void BackendMarble::slotTrackManagerChanged() { d->trackCache.clear(); if (s->trackManager) { connect(s->trackManager, SIGNAL(signalTracksChanged(QList)), this, SLOT(slotTracksChanged(QList))); // when the visibility of the tracks is changed, we simple schedule a redraw connect(s->trackManager, SIGNAL(signalVisibilityChanged(bool)), this, SLOT(slotScheduleUpdate())); } slotScheduleUpdate(); } bool BackendMarble::findSnapPoint(const QPoint& actualPoint, QPoint* const snapPoint, GeoCoordinates* const snapCoordinates, QPair* const snapTargetIndex) { QPoint bestSnapPoint; GeoCoordinates bestSnapCoordinates; int bestSnapDistanceSquared = -1; QModelIndex bestSnapIndex; int bestSnapUngroupedModel = -1; // now handle snapping: is there any object close by? for (int im = 0 ; im < s->ungroupedModels.count() ; ++im) { GeoModelHelper* const modelHelper = s->ungroupedModels.at(im); // TODO: test for active snapping if ((!modelHelper->modelFlags().testFlag(GeoModelHelper::FlagVisible)) || (!modelHelper->modelFlags().testFlag(GeoModelHelper::FlagSnaps))) { continue; } // TODO: configurable snapping radius const int snapRadiusSquared = 10*10; QAbstractItemModel* const itemModel = modelHelper->model(); for (int row = 0 ; row < itemModel->rowCount() ; ++row) { const QModelIndex currentIndex = itemModel->index(row, 0); GeoCoordinates currentCoordinates; if (!modelHelper->itemCoordinates(currentIndex, ¤tCoordinates)) { continue; } QPoint snapMarkerPoint; if (!screenCoordinates(currentCoordinates, &snapMarkerPoint)) { continue; } const QPoint distancePoint = snapMarkerPoint - actualPoint; const int snapDistanceSquared = (distancePoint.x()*distancePoint.x()+distancePoint.y()*distancePoint.y()); if ((snapDistanceSquared <= snapRadiusSquared) && ((bestSnapDistanceSquared == -1) || (bestSnapDistanceSquared > snapDistanceSquared))) { bestSnapDistanceSquared = snapDistanceSquared; bestSnapPoint = snapMarkerPoint; bestSnapCoordinates = currentCoordinates; bestSnapIndex = currentIndex; bestSnapUngroupedModel = im; } } } const bool foundSnapPoint = (bestSnapDistanceSquared >= 0); if (foundSnapPoint) { if (snapPoint) { *snapPoint = bestSnapPoint; } if (snapCoordinates) { *snapCoordinates = bestSnapCoordinates; } if (snapTargetIndex) { *snapTargetIndex = QPair(bestSnapUngroupedModel, bestSnapIndex); } } return foundSnapPoint; } void BackendMarble::regionSelectionChanged() { if (d->marbleWidget && d->activeState) { d->marbleWidget->update(); } } void BackendMarble::mouseModeChanged() { if (s->currentMouseMode != MouseModeRegionSelection) { d->firstSelectionPoint.clear(); d->intermediateSelectionPoint.clear(); if (d->marbleWidget && d->activeState) { d->marbleWidget->update(); } } } void BackendMarble::centerOn(const Marble::GeoDataLatLonBox& box, const bool useSaneZoomLevel) { if (!d->marbleWidget) { return; } /** * @todo Boxes with very small width or height (<1e-6 or so) cause a deadlock in Marble * in spherical projection. * So instead, we just center on the center of the box and go to maximum zoom. * This does not yet handle the case of only width or height being too small though. */ const bool boxTooSmall = qMin(box.width(), box.height()) < 0.000001; if (boxTooSmall) { d->marbleWidget->centerOn(box.center()); d->marbleWidget->zoomView(useSaneZoomLevel ? qMin(3400, d->marbleWidget->maximumZoom()) : d->marbleWidget->maximumZoom()); } else { d->marbleWidget->centerOn(box, false); } // simple check to see whether the zoom level is now too high /// @todo for very small boxes, Marbles zoom becomes -2billion. Catch this case here. int maxZoomLevel = d->marbleWidget->maximumZoom(); if (useSaneZoomLevel) { maxZoomLevel = qMin(maxZoomLevel, 3400); } if ((d->marbleWidget->zoom()>maxZoomLevel) || (d->marbleWidget->zoom()marbleWidget->minimumZoom())) { d->marbleWidget->zoomView(maxZoomLevel); } } void BackendMarble::setActive(const bool state) { const bool oldState = d->activeState; d->activeState = state; if (oldState!=state) { if ((!state) && d->marbleWidget) { // we should share our widget in the list of widgets in the global object GeoIfaceInternalWidgetInfo info; info.deleteFunction = deleteInfoFunction; info.widget = d->marbleWidget; info.currentOwner = this; info.backendName = backendName(); info.state = d->widgetIsDocked ? GeoIfaceInternalWidgetInfo::InternalWidgetStillDocked : GeoIfaceInternalWidgetInfo::InternalWidgetUndocked; BMInternalWidgetInfo intInfo; intInfo.bmLayer = d->bmLayer; info.backendData.setValue(intInfo); GeoIfaceGlobalObject* const go = GeoIfaceGlobalObject::instance(); go->addMyInternalWidgetToPool(info); } if (state && d->marbleWidget) { // we should remove our widget from the list of widgets in the global object GeoIfaceGlobalObject* const go = GeoIfaceGlobalObject::instance(); go->removeMyInternalWidgetFromPool(this); } } } void BackendMarble::mapWidgetDocked(const bool state) { if (d->widgetIsDocked!=state) { GeoIfaceGlobalObject* const go = GeoIfaceGlobalObject::instance(); go->updatePooledWidgetState(d->marbleWidget, state ? GeoIfaceInternalWidgetInfo::InternalWidgetStillDocked : GeoIfaceInternalWidgetInfo::InternalWidgetUndocked); } d->widgetIsDocked = state; } void BackendMarble::drawSearchRectangle(Marble::GeoPainter* const painter, const GeoCoordinates::Pair& searchRectangle, const bool isOldRectangle) { const GeoCoordinates& topLeft = searchRectangle.first; const GeoCoordinates& bottomRight = searchRectangle.second; const qreal lonWest = topLeft.lon(); const qreal latNorth = topLeft.lat(); const qreal lonEast = bottomRight.lon(); const qreal latSouth = bottomRight.lat(); Marble::GeoDataCoordinates coordTopLeft(lonWest, latNorth, 0, Marble::GeoDataCoordinates::Degree); Marble::GeoDataCoordinates coordTopRight(lonEast, latNorth, 0, Marble::GeoDataCoordinates::Degree); Marble::GeoDataCoordinates coordBottomLeft(lonWest, latSouth, 0, Marble::GeoDataCoordinates::Degree); Marble::GeoDataCoordinates coordBottomRight(lonEast, latSouth, 0, Marble::GeoDataCoordinates::Degree); Marble::GeoDataLinearRing polyRing; polyRing << coordTopLeft << coordTopRight << coordBottomRight << coordBottomLeft; QPen selectionPen; if (isOldRectangle) { // there is a new selection in progress, // therefore display the current search rectangle in red selectionPen.setColor(Qt::red); } else { selectionPen.setColor(Qt::blue); } selectionPen.setStyle(Qt::SolidLine); selectionPen.setWidth(1); painter->setPen(selectionPen); painter->setBrush(Qt::NoBrush); painter->drawPolygon(polyRing); } void BackendMarble::deleteInfoFunction(GeoIfaceInternalWidgetInfo* const info) { if (info->currentOwner) { qobject_cast(info->currentOwner.data())->releaseWidget(info); } BMInternalWidgetInfo intInfo = info->backendData.value(); if (intInfo.bmLayer) { delete intInfo.bmLayer; } delete info->widget.data(); } void BackendMarble::applyCacheToWidget() { /// @todo Do this only when the widget is active! if (!d->marbleWidget) { return; } setMapTheme(d->cacheMapTheme); setProjection(d->cacheProjection); setShowCompass(d->cacheShowCompass); setShowScaleBar(d->cacheShowScaleBar); setShowNavigation(d->cacheShowNavigation); setShowOverviewMap(d->cacheShowOverviewMap); } void BackendMarble::slotTracksChanged(const QList trackChanges) { // invalidate the cache for all changed tracks foreach (const TrackManager::TrackChanges& tc, trackChanges) { if (tc.second & (TrackManager::ChangeTrackPoints | TrackManager::ChangeRemoved)) { d->trackCache.remove(tc.first); } } slotScheduleUpdate(); } void BackendMarble::slotScheduleUpdate() { if (d->marbleWidget && d->activeState) { /// @TODO Put this onto the eventloop to collect update calls into one. d->marbleWidget->update(); } } } // namespace Digikam