diff --git a/core/libs/database/tags/tagregion.cpp b/core/libs/database/tags/tagregion.cpp index 523ae17471..da67d8ebc9 100644 --- a/core/libs/database/tags/tagregion.cpp +++ b/core/libs/database/tags/tagregion.cpp @@ -1,358 +1,334 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-08-26 * Description : Tag region formatting * * 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 "tagregion.h" // Qt includes #include #include // Local includes #include "dimg.h" #include "metaengine.h" namespace Digikam { TagRegion::TagRegion() : m_type(Invalid) { } TagRegion::TagRegion(const QString& descriptor) : m_value(descriptor), m_type(Invalid) { QString xmlStartDocument = QLatin1String(""); QXmlStreamReader reader(xmlStartDocument + descriptor); if (reader.readNextStartElement()) { if (reader.name() == QLatin1String("rect")) { QRect r(reader.attributes().value(QLatin1String("x")).toString().toInt(), reader.attributes().value(QLatin1String("y")).toString().toInt(), reader.attributes().value(QLatin1String("width")).toString().toInt(), reader.attributes().value(QLatin1String("height")).toString().toInt()); if (r.isValid()) { m_value = r; m_type = Rect; } } } } TagRegion::TagRegion(const QRect& rect) : m_value(rect), m_type(Rect) { } TagRegion::Type TagRegion::type() const { return m_type; } bool TagRegion::isValid() const { return m_type != Invalid; } bool TagRegion::operator==(const TagRegion& other) const { return ((m_type == other.m_type) && (m_value == other.m_value)); } QString TagRegion::toXml() const { if (m_type == Invalid) { return QString(); } QString output; QXmlStreamWriter writer(&output); writer.writeStartDocument(); int lengthOfHeader = output.length(); if (m_type == Rect) { QRect rect = m_value.toRect(); writer.writeStartElement(QLatin1String("rect")); writer.writeAttribute(QLatin1String("x"), QString::number(rect.x())); writer.writeAttribute(QLatin1String("y"), QString::number(rect.y())); writer.writeAttribute(QLatin1String("width"), QString::number(rect.width())); writer.writeAttribute(QLatin1String("height"), QString::number(rect.height())); writer.writeEndElement(); } // cut off the tag at start of document return output.mid(lengthOfHeader); } QRect TagRegion::toRect() const { if (m_type == Rect) { return m_value.toRect(); } return QRect(); } QVariant TagRegion::toVariant() const { return m_value; } TagRegion TagRegion::fromVariant(const QVariant& var) { switch (var.type()) { case QVariant::Rect: return TagRegion(var.toRect()); case QVariant::String: return TagRegion(var.toString()); default: return TagRegion(); } } bool TagRegion::intersects(const TagRegion& other, double fraction) { if (m_type == Invalid || other.m_type == Invalid) { return false; } if (m_type == Rect) { QRect r = toRect(); if (other.m_type == Rect) { QRect r2 = other.toRect(); if (fraction == 0) { return r.intersects(r2); } else if (fraction == 1) { return r.contains(r2); } else { QRect i = r.intersected(r2); return ( (double(i.width() * i.height()) / double(r.width() * r.height())) > fraction ); } } } return false; } QRect TagRegion::mapToOriginalSize(const QSize& fullImageSize, const QSize& reducedImageSize, const QRect& reducedSizeDetail) { if (fullImageSize == reducedImageSize) { return reducedSizeDetail; } double ratioWidth = double(fullImageSize.width()) / double(reducedImageSize.width()); double ratioHeight = double(fullImageSize.height()) / double(reducedImageSize.height()); return QRectF(reducedSizeDetail.x() * ratioWidth, reducedSizeDetail.y() * ratioHeight, reducedSizeDetail.width() * ratioWidth, reducedSizeDetail.height() * ratioHeight).toRect(); } QRect TagRegion::mapFromOriginalSize(const QSize& fullImageSize, const QSize& reducedImageSize, const QRect& fullSizeDetail) { if (fullImageSize == reducedImageSize) { return fullSizeDetail; } double ratioWidth = double(reducedImageSize.width()) / double(fullImageSize.width()); double ratioHeight = double(reducedImageSize.height()) / double(fullImageSize.height()); return QRectF(fullSizeDetail.x() * ratioWidth, fullSizeDetail.y() * ratioHeight, fullSizeDetail.width() * ratioWidth, fullSizeDetail.height() * ratioHeight).toRect(); } QRect TagRegion::mapToOriginalSize(const DImg& reducedSizeImage, const QRect& reducedSizeDetail) { return mapToOriginalSize(reducedSizeImage.originalSize(), reducedSizeImage.size(), reducedSizeDetail); } QRect TagRegion::mapFromOriginalSize(const DImg& reducedSizeImage, const QRect& fullSizeDetail) { return mapFromOriginalSize(reducedSizeImage.originalSize(), reducedSizeImage.size(), fullSizeDetail); } QRect TagRegion::relativeToAbsolute(const QRectF& region, const QSize& fullSize) { return QRectF(region.x() * fullSize.width(), region.y() * fullSize.height(), region.width() * fullSize.width(), region.height() * fullSize.height()).toRect(); } QRect TagRegion::relativeToAbsolute(const QRectF& region, const DImg& reducedSizeImage) { return relativeToAbsolute(region, reducedSizeImage.originalSize()); } QRectF TagRegion::absoluteToRelative(const QRect& region, const QSize& fullSize) { return QRectF((qreal)region.x() / (qreal)fullSize.width(), (qreal)region.y() / (qreal)fullSize.height(), (qreal)region.width() / (qreal)fullSize.width(), (qreal)region.height() / (qreal)fullSize.height()); } -QRect TagRegion::adjustToRotatedImg(const QRect& region, const QSize& fullSize, int rotation) +void TagRegion::adjustToRotatedImg(QRect& region, const QSize& fullSize, int rotation) { - int x, y, w, h; - region.getRect(&x, &y, &w, &h); - int newx, newy, neww, newh; - if (rotation == 0) // Rotate right 90 degrees { - newx = fullSize.height() - y - h; - newy = x; - neww = h; - newh = w; - + region.moveTo(fullSize.height() - region.y() - region.height(), region.x()); + region.setSize(region.size().transposed()); } else // Rotate left 90 degrees { - newx = y; - newy = fullSize.width() - x - w; - neww = h; - newh = w; + region.moveTo(region.y(), fullSize.width() - region.x() - region.width()); + region.setSize(region.size().transposed()); } - - return QRect(newx, newy, neww, newh); } -QRect TagRegion::adjustToFlippedImg(const QRect& region, const QSize& fullSize, int flip) +void TagRegion::adjustToFlippedImg(QRect& region, const QSize& fullSize, int flip) { - int x, y, w, h; - region.getRect(&x, &y, &w, &h); - int newx, newy, neww, newh; - if (flip == 0) // Flip horizontally { - newx = fullSize.width() - x - w; - newy = y; - neww = w; - newh = h; - + region.moveTo(fullSize.width() - region.x() - region.width(), region.y()); } else // Flip vertically { - newx = x; - newy = fullSize.height() - y - h; - neww = w; - newh = h; + region.moveTo(region.x(), fullSize.height() - region.y() - region.height()); } - - return QRect(newx, newy, neww, newh); } void TagRegion::adjustToOrientation(QRect& region, int orientation, const QSize& fullSize) { QSize size = fullSize; switch (orientation) { case MetaEngine::ORIENTATION_HFLIP: - region = TagRegion::adjustToFlippedImg(region, size, 0); + TagRegion::adjustToFlippedImg(region, size, 0); break; case MetaEngine::ORIENTATION_ROT_180: - region = TagRegion::adjustToFlippedImg(region, size, 0); - region = TagRegion::adjustToFlippedImg(region, size, 1); + TagRegion::adjustToFlippedImg(region, size, 0); + TagRegion::adjustToFlippedImg(region, size, 1); break; case MetaEngine::ORIENTATION_VFLIP: - region = TagRegion::adjustToFlippedImg(region, size, 1); + TagRegion::adjustToFlippedImg(region, size, 1); break; case MetaEngine::ORIENTATION_ROT_90_HFLIP: - region = TagRegion::adjustToRotatedImg(region, size, 0); + TagRegion::adjustToRotatedImg(region, size, 0); size.transpose(); - region = TagRegion::adjustToFlippedImg(region, size, 0); + TagRegion::adjustToFlippedImg(region, size, 0); break; case MetaEngine::ORIENTATION_ROT_90: - region = TagRegion::adjustToRotatedImg(region, size, 0); + TagRegion::adjustToRotatedImg(region, size, 0); break; case MetaEngine::ORIENTATION_ROT_90_VFLIP: - region = TagRegion::adjustToRotatedImg(region, size, 0); + TagRegion::adjustToRotatedImg(region, size, 0); size.transpose(); - region = TagRegion::adjustToFlippedImg(region, size, 1); + TagRegion::adjustToFlippedImg(region, size, 1); break; case MetaEngine::ORIENTATION_ROT_270: - region = TagRegion::adjustToRotatedImg(region, size, 1); + TagRegion::adjustToRotatedImg(region, size, 1); break; default: break; } } QDebug operator<<(QDebug dbg, const TagRegion& r) { QVariant var = r.toVariant(); switch (var.type()) { case QVariant::Rect: dbg.nospace() << var.toRect(); break; case QVariant::String: dbg.nospace() << var.toString(); break; default: dbg.nospace() << var; break; } return dbg; } } // namespace Digikam diff --git a/core/libs/database/tags/tagregion.h b/core/libs/database/tags/tagregion.h index b1477dea0a..70661252df 100644 --- a/core/libs/database/tags/tagregion.h +++ b/core/libs/database/tags/tagregion.h @@ -1,153 +1,153 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-08-26 * Description : Tag region formatting * * 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. * * ============================================================ */ #ifndef DIGIKAM_TAG_REGION_H #define DIGIKAM_TAG_REGION_H // Qt includes #include #include #include #include // Local includes #include "digikam_export.h" class QDebug; namespace Digikam { class DImg; class DIGIKAM_EXPORT TagRegion { public: enum Type { Invalid, Rect }; public: /** * Use this small class to convert between the formatted * textual representation of a tag region in the database * and the corresponding object. */ /// Invalid region TagRegion(); /// Construct with the textual descriptor explicit TagRegion(const QString& descriptor); /// Construct with the region explicit TagRegion(const QRect& rect); Type type() const; bool isValid() const; bool operator==(const TagRegion& other) const; bool operator!=(const TagRegion& other) const { return !operator==(other); } /// Returns an XML textual representation of this region QString toXml() const; /// If type is Rect, returns the contained rectangle QRect toRect() const; /// Stores in / loads from a variant. Will only use native QVariant types. QVariant toVariant() const; static TagRegion fromVariant(const QVariant& var); /** * Returns true if this and the other region intersect. * fraction describes the relative overlap area needed to return true: * If fraction is 0, returns true if the regions intersect at all. * If fraction is 1, returns true only if other is completely contained in this region. * If fraction is x, 0 < x < 1, returns true if the area of this region * covered by the other is greater than x. * Invalid areas never intersect. */ bool intersects(const TagRegion& other, double fraction = 0); /** * Converts detail rectangles taken from a reduced size image to the original size, and vice versa */ static QRect mapToOriginalSize(const QSize& fullImageSize, const QSize& reducedImageSize, const QRect& reducedSizeDetail); static QRect mapFromOriginalSize(const QSize& fullImageSize, const QSize& reducedImageSize, const QRect& fullSizeDetail); /// Takes the original and reduced size from the DImg static QRect mapToOriginalSize(const DImg& reducedSizeImage, const QRect& reducedSizeDetail); static QRect mapFromOriginalSize(const DImg& reducedSizeImage, const QRect& fullSizeDetail); /** * Takes a relative region and a full size and returns the absolute region */ static QRect relativeToAbsolute(const QRectF& region, const QSize& fullSize); /** * Takes the original and reduced size from the DImg, maps to original size */ static QRect relativeToAbsolute(const QRectF& region, const DImg& reducedSizeImage); /** * Takes absolute region and full size to return the original relative region * Used to write back rectangles into image's XMP. see MetadataHub::write */ static QRectF absoluteToRelative(const QRect& region, const QSize& fullSize); /** * When images is rotated, rectangles are off-position, adjust them using * image's current size and rotation(left, right supported only) */ - static QRect adjustToRotatedImg(const QRect& region, const QSize& fullSize, int rotation); + static void adjustToRotatedImg(QRect& region, const QSize& fullSize, int rotation); /** * When images is flipped, rectangles are off-position, adjust them using * image's current size and flip(horizon, vertical supported only) */ - static QRect adjustToFlippedImg(const QRect& region, const QSize& fullSize, int flip); + static void adjustToFlippedImg(QRect& region, const QSize& fullSize, int flip); /** * Rotate and flip region to MetaEngine::ImageOrientation */ static void adjustToOrientation(QRect& region, int orientation, const QSize& fullSize); protected: QVariant m_value; Type m_type; }; QDebug DIGIKAM_EXPORT operator<<(QDebug dbg, const TagRegion& r); } // namespace Digikam #endif // DIGIKAM_TAG_REGION_H diff --git a/core/utilities/facemanagement/facegroup.cpp b/core/utilities/facemanagement/facegroup.cpp index f30ae44eee..a2b30b80fb 100644 --- a/core/utilities/facemanagement/facegroup.cpp +++ b/core/utilities/facemanagement/facegroup.cpp @@ -1,978 +1,978 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-09-17 * Description : Managing of face tag region items on a GraphicsDImgView * * Copyright (C) 2010 by Aditya Bhatt * 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 "facegroup.h" // Qt includes #include #include #include // Local includes #include "metaenginesettings.h" #include "addtagscombobox.h" #include "albummodel.h" #include "albumfiltermodel.h" #include "albummanager.h" #include "assignnamewidget.h" #include "clickdragreleaseitem.h" #include "dimgpreviewitem.h" #include "facepipeline.h" #include "facetags.h" #include "facetagseditor.h" #include "graphicsdimgview.h" #include "iteminfo.h" #include "regionframeitem.h" #include "taggingaction.h" #include "itemvisibilitycontroller.h" #include "digikam_debug.h" namespace Digikam { enum FaceGroupState { NoFaces, LoadingFaces, FacesLoaded }; //------------------------------------------------------------------------------- class Q_DECL_HIDDEN FaceItem : public RegionFrameItem { public: explicit FaceItem(QGraphicsItem* const parent = 0); void setFace(const FaceTagsIface& face); FaceTagsIface face() const; void setHudWidget(AssignNameWidget* const widget); AssignNameWidget* widget() const; void switchMode(AssignNameWidget::Mode mode); void setEditable(bool allowEdit); void updateCurrentTag(); protected: FaceTagsIface m_face; AssignNameWidget* m_widget; HidingStateChanger* m_changer; }; //------------------------------------------------------------------------------- FaceItem::FaceItem(QGraphicsItem* const parent) : RegionFrameItem(parent), m_widget(0), m_changer(0) { } void FaceItem::setFace(const FaceTagsIface& face) { m_face = face; updateCurrentTag(); setEditable(!m_face.isConfirmedName()); } FaceTagsIface FaceItem::face() const { return m_face; } void FaceItem::setHudWidget(AssignNameWidget* const widget) { m_widget = widget; updateCurrentTag(); RegionFrameItem::setHudWidget(widget); // Ensure that all HUD widgets are stacked before the frame items hudWidget()->setZValue(1); } AssignNameWidget* FaceItem::widget() const { return m_widget; } void FaceItem::switchMode(AssignNameWidget::Mode mode) { if (!m_widget || m_widget->mode() == mode) { return; } if (!m_changer) { m_changer = new AssignNameWidgetHidingStateChanger(this); } m_changer->changeValue(mode); } void FaceItem::setEditable(bool allowEdit) { changeFlags(ShowResizeHandles | MoveByDrag, allowEdit); } void FaceItem::updateCurrentTag() { if (m_widget) { m_widget->setCurrentFace(m_face); } } //------------------------------------------------------------------------------- AssignNameWidgetHidingStateChanger::AssignNameWidgetHidingStateChanger(FaceItem* const item) : HidingStateChanger(item->widget(), "mode", item) { // The WidgetProxyItem addItem(item->hudWidget()); connect(this, SIGNAL(stateChanged()), this, SLOT(slotStateChanged())); } void AssignNameWidgetHidingStateChanger::slotStateChanged() { FaceItem* const item = static_cast(parent()); // Show resize handles etc. only in edit modes item->setEditable(item->widget()->mode() != AssignNameWidget::ConfirmedMode); } //------------------------------------------------------------------------------- class Q_DECL_HIDDEN FaceGroup::Private { public: explicit Private(FaceGroup* const q) : q(q) { view = 0; autoSuggest = false; showOnHover = false; manuallyAddWrapItem = 0; manuallyAddedItem = 0; visibilityController = 0; state = NoFaces; tagModel = 0; filterModel = 0; filteredModel = 0; } void applyVisible(); FaceItem* createItem(const FaceTagsIface& face); FaceItem* addItem(const FaceTagsIface& face); AssignNameWidget* createAssignNameWidget(const FaceTagsIface& face, const QVariant& identifier); AssignNameWidget::Mode assignWidgetMode(FaceTagsIface::Type type); void checkModels(); QList hotItems(const QPointF& scenePos); public: GraphicsDImgView* view; ItemInfo info; bool autoSuggest; bool showOnHover; QList items; ClickDragReleaseItem* manuallyAddWrapItem; FaceItem* manuallyAddedItem; FaceGroupState state; ItemVisibilityController* visibilityController; TagModel* tagModel; CheckableAlbumFilterModel* filterModel; TagPropertiesFilterModel* filteredModel; FacePipeline editPipeline; FaceGroup* const q; }; FaceGroup::FaceGroup(GraphicsDImgView* const view) : QObject(view), d(new Private(this)) { d->view = view; d->visibilityController = new ItemVisibilityController(this); d->visibilityController->setShallBeShown(false); connect(AlbumManager::instance(), SIGNAL(signalAlbumRenamed(Album*)), this, SLOT(slotAlbumRenamed(Album*))); connect(AlbumManager::instance(), SIGNAL(signalAlbumsUpdated(int)), this, SLOT(slotAlbumsUpdated(int))); connect(view->previewItem(), SIGNAL(stateChanged(int)), this, SLOT(itemStateChanged(int))); d->editPipeline.plugDatabaseEditor(); d->editPipeline.plugTrainer(); d->editPipeline.construct(); } FaceGroup::~FaceGroup() { delete d; } void FaceGroup::itemStateChanged(int itemState) { switch (itemState) { case DImgPreviewItem::NoImage: case DImgPreviewItem::Loading: case DImgPreviewItem::ImageLoadingFailed: d->visibilityController->hide(); break; case DImgPreviewItem::ImageLoaded: if (d->state == FacesLoaded) { d->visibilityController->show(); } break; } } bool FaceGroup::isVisible() const { return d->visibilityController->shallBeShown(); } bool FaceGroup::hasVisibleItems() const { return d->visibilityController->hasVisibleItems(); } ItemInfo FaceGroup::info() const { return d->info; } QList FaceGroup::items() const { QList items; foreach (FaceItem* const item, d->items) { items << item; } return items; } void FaceGroup::setAutoSuggest(bool doAutoSuggest) { if (d->autoSuggest == doAutoSuggest) { return; } d->autoSuggest = doAutoSuggest; } bool FaceGroup::autoSuggest() const { return d->autoSuggest; } void FaceGroup::setShowOnHover(bool show) { d->showOnHover = show; } bool FaceGroup::showOnHover() const { return d->showOnHover; } void FaceGroup::Private::applyVisible() { if (state == NoFaces) { // If not yet loaded, load. load() will transitionToVisible after loading. q->load(); } else if (state == FacesLoaded) { // show existing faces, if we have an image if (view->previewItem()->isLoaded()) { visibilityController->show(); } } } void FaceGroup::setVisible(bool visible) { d->visibilityController->setShallBeShown(visible); d->applyVisible(); } void FaceGroup::setVisibleItem(RegionFrameItem* item) { d->visibilityController->setItemThatShallBeShown(item); d->applyVisible(); } void FaceGroup::setInfo(const ItemInfo& info) { if (d->info == info && d->state != NoFaces) { return; } clear(); d->info = info; if (d->visibilityController->shallBeShown()) { load(); } } void FaceGroup::aboutToSetInfo(const ItemInfo& info) { if (d->info == info) { return; } //applyItemGeometryChanges(); clear(); } void FaceGroup::aboutToSetInfoAfterRotate(const ItemInfo& info) { if (d->info == info) { return; } //applyItemGeometryChanges(); clear(); } static QPointF closestPointOfRect(const QPointF& p, const QRectF& r) { QPointF cp = p; if (p.x() < r.left()) { cp.setX(r.left()); } else if (p.x() > r.right()) { cp.setX(r.right()); } if (p.y() < r.top()) { cp.setY(r.top()); } else if (p.y() > r.bottom()) { cp.setY(r.bottom()); } return cp; } RegionFrameItem* FaceGroup::closestItem(const QPointF& p, qreal* const manhattanLength) const { RegionFrameItem* closestItem = 0; qreal minDistance = 0; qreal minCenterDistance = 0; foreach (RegionFrameItem* const item, d->items) { QRectF r = item->boundingRect().translated(item->pos()); qreal distance = (p - closestPointOfRect(p, r)).manhattanLength(); if (!closestItem || distance < minDistance || (distance == 0 && (p - r.center()).manhattanLength() < minCenterDistance)) { closestItem = item; minDistance = distance; if (distance == 0) { minCenterDistance = (p - r.center()).manhattanLength(); } } } if (manhattanLength) { *manhattanLength = minDistance; } return closestItem; } QList FaceGroup::Private::hotItems(const QPointF& scenePos) { if (!q->hasVisibleItems()) { return QList(); } const int distance = 15; QRectF hotSceneRect = QRectF(scenePos, QSize(0, 0)).adjusted(-distance, -distance, distance, distance); QList closeItems = view->scene()->items(hotSceneRect, Qt::IntersectsItemBoundingRect); closeItems.removeOne(view->previewItem()); return closeItems; /* qreal distance; d->faceGroup->closestItem(mapToScene(e->pos()), &distance); if (distance < 15) return false; */ } bool FaceGroup::acceptsMouseClick(const QPointF& scenePos) { return d->hotItems(scenePos).isEmpty(); } void FaceGroup::itemHoverMoveEvent(QGraphicsSceneHoverEvent* e) { if (d->showOnHover && !isVisible()) { qreal distance; RegionFrameItem* const item = closestItem(e->scenePos(), &distance); // There's a possible nuisance when the direct mouse way from hovering pos to HUD widget // is not part of the condition. Maybe, we should add a exemption for this case. if (distance < 25) { setVisibleItem(item); } else { // get all items close to pos QList hotItems = d->hotItems(e->scenePos()); // this will be the one item shown by mouse over QList visible = d->visibilityController->visibleItems(ItemVisibilityController::ExcludeFadingOut); foreach (QGraphicsItem* const item, hotItems) { foreach (QObject* const parent, visible) { if (static_cast(parent)->isAncestorOf(item)) { return; } } } setVisibleItem(0); } } } void FaceGroup::itemHoverLeaveEvent(QGraphicsSceneHoverEvent*) { } void FaceGroup::itemHoverEnterEvent(QGraphicsSceneHoverEvent*) { } void FaceGroup::leaveEvent(QEvent*) { if (d->showOnHover && !isVisible()) { setVisibleItem(0); } } void FaceGroup::enterEvent(QEvent*) { } FaceItem* FaceGroup::Private::createItem(const FaceTagsIface& face) { FaceItem* const item = new FaceItem(view->previewItem()); item->setFace(face); QRect faceRect = face.region().toRect(); if (MetaEngineSettings::instance()->settings().exifRotate) { TagRegion::adjustToOrientation(faceRect, info.orientation(), info.dimensions()); } item->setOriginalRect(faceRect); item->setVisible(false); return item; } FaceItem* FaceGroup::Private::addItem(const FaceTagsIface& face) { FaceItem* const item = createItem(face); // for identification, use index in our list AssignNameWidget* const assignWidget = createAssignNameWidget(face, items.size()); item->setHudWidget(assignWidget); //new StyleSheetDebugger(assignWidget); visibilityController->addItem(item); items << item; return item; } void FaceGroup::Private::checkModels() { if (!tagModel) { tagModel = new TagModel(AbstractAlbumModel::IgnoreRootAlbum, q); } if (!filterModel) { filterModel = new CheckableAlbumFilterModel(q); } if (!filteredModel) { filteredModel = new TagPropertiesFilterModel(q); } } AssignNameWidget::Mode FaceGroup::Private::assignWidgetMode(FaceTagsIface::Type type) { switch (type) { case FaceTagsIface::UnknownName: case FaceTagsIface::UnconfirmedName: return AssignNameWidget::UnconfirmedEditMode; case FaceTagsIface::ConfirmedName: return AssignNameWidget::ConfirmedMode; default: return AssignNameWidget::InvalidMode; } } AssignNameWidget* FaceGroup::Private::createAssignNameWidget(const FaceTagsIface& face, const QVariant& identifier) { AssignNameWidget* const assignWidget = new AssignNameWidget(view); assignWidget->setMode(assignWidgetMode(face.type())); assignWidget->setTagEntryWidgetMode(AssignNameWidget::AddTagsComboBoxMode); assignWidget->setVisualStyle(AssignNameWidget::TranslucentDarkRound); assignWidget->setLayoutMode(AssignNameWidget::TwoLines); assignWidget->setUserData(info, identifier); checkModels(); assignWidget->setModel(tagModel, filteredModel, filterModel); assignWidget->setParentTag(AlbumManager::instance()->findTAlbum(FaceTags::personParentTag())); q->connect(assignWidget, SIGNAL(assigned(TaggingAction,ItemInfo,QVariant)), q, SLOT(slotAssigned(TaggingAction,ItemInfo,QVariant))); q->connect(assignWidget, SIGNAL(rejected(ItemInfo,QVariant)), q, SLOT(slotRejected(ItemInfo,QVariant))); q->connect(assignWidget, SIGNAL(labelClicked(ItemInfo,QVariant)), q, SLOT(slotLabelClicked(ItemInfo,QVariant))); return assignWidget; } void FaceGroup::load() { if (d->state != NoFaces) { return; } d->state = LoadingFaces; if (d->info.isNull()) { d->state = FacesLoaded; return; } QList faces = FaceTagsEditor().databaseFaces(d->info.id()); d->visibilityController->clear(); foreach (const FaceTagsIface& face, faces) { d->addItem(face); } d->state = FacesLoaded; if (d->view->previewItem()->isLoaded()) { d->visibilityController->show(); } } void FaceGroup::clear() { cancelAddItem(); d->visibilityController->clear(); foreach (RegionFrameItem* const item, d->items) { delete item; } d->items.clear(); d->state = NoFaces; } void FaceGroup::rejectAll() { foreach (FaceItem* const item, d->items) { d->editPipeline.remove(d->info, item->face()); item->setFace(FaceTagsIface()); d->visibilityController->hideAndRemoveItem(item); } clear(); } void FaceGroup::slotAlbumsUpdated(int type) { if (type != Album::TAG) { return; } if (d->items.isEmpty()) { return; } clear(); load(); } void FaceGroup::slotAlbumRenamed(Album* album) { if (!album || album->type() != Album::TAG) { return; } foreach (FaceItem* const item, d->items) { if (!item->face().isNull() && item->face().tagId() == album->id()) { item->updateCurrentTag(); } } } void FaceGroup::slotAssigned(const TaggingAction& action, const ItemInfo&, const QVariant& faceIdentifier) { FaceItem* const item = d->items[faceIdentifier.toInt()]; FaceTagsIface face = item->face(); QRect faceRect = convertItemRectToFaceRect(item->originalRect()); TagRegion currentRegion = TagRegion(faceRect); if (!face.isConfirmedName() || face.region() != currentRegion || action.shallCreateNewTag() || (action.shallAssignTag() && action.tagId() != face.tagId())) { int tagId = 0; if (action.shallAssignTag()) { tagId = action.tagId(); } else if (action.shallCreateNewTag()) { tagId = FaceTags::getOrCreateTagForPerson(action.newTagName(), action.parentTagId()); } if (FaceTags::isTheUnknownPerson(tagId)) { qCDebug(DIGIKAM_GENERAL_LOG) << "Refusing to assign the unknown person to an image"; return; } if (!tagId) { qCDebug(DIGIKAM_GENERAL_LOG) << "Failed to get person tag"; return; } if (tagId) { face = d->editPipeline.confirm(d->info, face, d->view->previewItem()->image(), tagId, currentRegion); } } item->setFace(face); item->switchMode(AssignNameWidget::ConfirmedMode); } void FaceGroup::slotRejected(const ItemInfo&, const QVariant& faceIdentifier) { FaceItem* const item = d->items[faceIdentifier.toInt()]; d->editPipeline.remove(d->info, item->face()); item->setFace(FaceTagsIface()); d->visibilityController->hideAndRemoveItem(item); } void FaceGroup::slotLabelClicked(const ItemInfo&, const QVariant& faceIdentifier) { FaceItem* const item = d->items[faceIdentifier.toInt()]; item->switchMode(AssignNameWidget::ConfirmedEditMode); } void FaceGroup::startAutoSuggest() { if (!d->autoSuggest) { return; } } void FaceGroup::addFace() { if (d->manuallyAddWrapItem) { return; } d->manuallyAddWrapItem = new ClickDragReleaseItem(d->view->previewItem()); d->manuallyAddWrapItem->setFocus(); d->view->setFocus(); connect(d->manuallyAddWrapItem, SIGNAL(started(QPointF)), this, SLOT(slotAddItemStarted(QPointF))); connect(d->manuallyAddWrapItem, SIGNAL(moving(QRectF)), this, SLOT(slotAddItemMoving(QRectF))); connect(d->manuallyAddWrapItem, SIGNAL(finished(QRectF)), this, SLOT(slotAddItemFinished(QRectF))); connect(d->manuallyAddWrapItem, SIGNAL(cancelled()), this, SLOT(cancelAddItem())); } void FaceGroup::slotAddItemStarted(const QPointF& pos) { Q_UNUSED(pos); } void FaceGroup::slotAddItemMoving(const QRectF& rect) { if (!d->manuallyAddedItem) { d->manuallyAddedItem = d->createItem(FaceTagsIface()); d->visibilityController->addItem(d->manuallyAddedItem); d->visibilityController->showItem(d->manuallyAddedItem); } d->manuallyAddedItem->setRectInSceneCoordinatesAdjusted(rect); } void FaceGroup::slotAddItemFinished(const QRectF& rect) { if (d->manuallyAddedItem) { d->manuallyAddedItem->setRectInSceneCoordinatesAdjusted(rect); QRect faceRect = convertItemRectToFaceRect(d->manuallyAddedItem->originalRect()); FaceTagsIface face = d->editPipeline.addManually(d->info, d->view->previewItem()->image(), TagRegion(faceRect)); FaceItem* const item = d->addItem(face); d->visibilityController->setItemDirectlyVisible(item, true); item->switchMode(AssignNameWidget::UnconfirmedEditMode); d->manuallyAddWrapItem->stackBefore(item); } cancelAddItem(); } void FaceGroup::cancelAddItem() { delete d->manuallyAddedItem; d->manuallyAddedItem = 0; if (d->manuallyAddWrapItem) { d->view->scene()->removeItem(d->manuallyAddWrapItem); d->manuallyAddWrapItem->deleteLater(); d->manuallyAddWrapItem = 0; } } void FaceGroup::applyItemGeometryChanges() { foreach (FaceItem* const item, d->items) { if (item->face().isNull()) { continue; } TagRegion currentRegion = TagRegion(item->originalRect()); if (item->face().region() != currentRegion) { d->editPipeline.editRegion(d->info, d->view->previewItem()->image(), item->face(), currentRegion); } } } QRect FaceGroup::convertItemRectToFaceRect(const QRect& rect) const { QRect faceRect = rect; if (MetaEngineSettings::instance()->settings().exifRotate) { QSize imgSize = d->info.dimensions(); switch (d->info.orientation()) { case MetaEngine::ORIENTATION_HFLIP: - faceRect = TagRegion::adjustToFlippedImg(faceRect, imgSize, 0); + TagRegion::adjustToFlippedImg(faceRect, imgSize, 0); break; case MetaEngine::ORIENTATION_ROT_180: - faceRect = TagRegion::adjustToFlippedImg(faceRect, imgSize, 1); - faceRect = TagRegion::adjustToFlippedImg(faceRect, imgSize, 0); + TagRegion::adjustToFlippedImg(faceRect, imgSize, 1); + TagRegion::adjustToFlippedImg(faceRect, imgSize, 0); break; case MetaEngine::ORIENTATION_VFLIP: - faceRect = TagRegion::adjustToFlippedImg(faceRect, imgSize, 1); + TagRegion::adjustToFlippedImg(faceRect, imgSize, 1); break; case MetaEngine::ORIENTATION_ROT_90_HFLIP: imgSize.transpose(); - faceRect = TagRegion::adjustToFlippedImg(faceRect, imgSize, 0); - faceRect = TagRegion::adjustToRotatedImg(faceRect, imgSize, 1); + TagRegion::adjustToFlippedImg(faceRect, imgSize, 0); + TagRegion::adjustToRotatedImg(faceRect, imgSize, 1); break; case MetaEngine::ORIENTATION_ROT_90: imgSize.transpose(); - faceRect = TagRegion::adjustToRotatedImg(faceRect, imgSize, 1); + TagRegion::adjustToRotatedImg(faceRect, imgSize, 1); break; case MetaEngine::ORIENTATION_ROT_90_VFLIP: imgSize.transpose(); - faceRect = TagRegion::adjustToFlippedImg(faceRect, imgSize, 1); - faceRect = TagRegion::adjustToRotatedImg(faceRect, imgSize, 1); + TagRegion::adjustToFlippedImg(faceRect, imgSize, 1); + TagRegion::adjustToRotatedImg(faceRect, imgSize, 1); break; case MetaEngine::ORIENTATION_ROT_270: imgSize.transpose(); - faceRect = TagRegion::adjustToRotatedImg(faceRect, imgSize, 0); + TagRegion::adjustToRotatedImg(faceRect, imgSize, 0); break; default: break; } } return faceRect; } /* void ItemPreviewView::trainFaces() { QList trainList; foreach (Face f, d->currentFaces) { if (f.name() != "" && !d->faceIface->isFaceTrained(getItemInfo().id(), f.toRect(), f.name())) trainList += f; } qCDebug(DIGIKAM_GENERAL_LOG) << "Number of training faces" << trainList.size(); if (trainList.size()!=0) { d->faceIface->trainWithFaces(trainList); d->faceIface->markFacesAsTrained(getItemInfo().id(), trainList); } } void ItemPreviewView::suggestFaces() { // Assign tentative names to the face list QList recogList; foreach (Face f, d->currentFaces) { if (!d->faceIface->isFaceRecognized(getItemInfo().id(), f.toRect(), f.name()) && f.name().isEmpty()) { f.setName(d->faceIface->recognizedName(f)); d->faceIface->markFaceAsRecognized(getItemInfo().id(), f.toRect(), f.name()); // If the face wasn't recognized (too distant) don't suggest anything if (f.name().isEmpty()) continue; else recogList += f; } } qCDebug(DIGIKAM_GENERAL_LOG) << "Number of suggestions = " << recogList.size(); qCDebug(DIGIKAM_GENERAL_LOG) << "Number of faceitems = " << d->faceitems.size(); // Now find the relevant face items and suggest faces for (int i = 0 ; i < recogList.size() ; ++i) { for (int j = 0 ; j < d->faceitems.size() ; ++j) { if (recogList[i].toRect() == d->faceitems[j]->originalRect()) { qCDebug(DIGIKAM_GENERAL_LOG) << "Suggesting a name " << recogList[i].name(); d->faceitems[j]->suggest(recogList[i].name()); break; } } } } */ } // namespace Digikam diff --git a/core/utilities/imageeditor/main/imagewindow.cpp b/core/utilities/imageeditor/main/imagewindow.cpp index a407e9f9fa..e907297090 100644 --- a/core/utilities/imageeditor/main/imagewindow.cpp +++ b/core/utilities/imageeditor/main/imagewindow.cpp @@ -1,1334 +1,1333 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2004-02-12 * Description : digiKam image editor GUI * * Copyright (C) 2004-2005 by Renchi Raju * Copyright (C) 2004-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 "imagewindow.h" #include "imagewindow_p.h" namespace Digikam { ImageWindow* ImageWindow::m_instance = 0; ImageWindow* ImageWindow::imageWindow() { if (!m_instance) { new ImageWindow(); } return m_instance; } bool ImageWindow::imageWindowCreated() { return m_instance; } ImageWindow::ImageWindow() : EditorWindow(QLatin1String("Image Editor")), d(new Private) { setXMLFile(QLatin1String("imageeditorui5.rc")); m_instance = this; // We don't want to be deleted on close setAttribute(Qt::WA_DeleteOnClose, false); setAcceptDrops(true); // -- Build the GUI ------------------------------- setupUserArea(); setupActions(); setupStatusBar(); createGUI(xmlFile()); registerPluginsActions(); cleanupActions(); showMenuBarAction()->setChecked(!menuBar()->isHidden()); // NOTE: workaround for bug #171080 // Create tool selection view setupSelectToolsAction(); // Create context menu. setupContextMenu(); // Make signals/slots connections setupConnections(); // -- Read settings -------------------------------- readSettings(); KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(configGroupName()); applyMainWindowSettings(group); d->thumbBarDock->setShouldBeVisible(group.readEntry(d->configShowThumbbarEntry, false)); setAutoSaveSettings(configGroupName(), true); d->viewContainer->setAutoSaveSettings(QLatin1String("ImageViewer Thumbbar"), true); //------------------------------------------------------------- d->rightSideBar->setConfigGroup(KConfigGroup(&group, QLatin1String("Right Sidebar"))); d->rightSideBar->loadState(); d->rightSideBar->populateTags(); slotSetupChanged(); } ImageWindow::~ImageWindow() { m_instance = 0; delete d->rightSideBar; delete d->thumbBar; delete d; } void ImageWindow::closeEvent(QCloseEvent* e) { if (!queryClose()) { e->ignore(); return; } // put right side bar in a defined state emit signalNoCurrentItem(); m_canvas->resetImage(); // There is one nasty habit with the thumbnail bar if it is floating: it // doesn't close when the parent window does, so it needs to be manually // closed. If the light table is opened again, its original state needs to // be restored. // This only needs to be done when closing a visible window and not when // destroying a closed window, since the latter case will always report that // the thumbnail bar isn't visible. if (isVisible()) { thumbBar()->hide(); } KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(configGroupName()); d->rightSideBar->setConfigGroup(KConfigGroup(&group, "Right Sidebar")); d->rightSideBar->saveState(); saveSettings(); DXmlGuiWindow::closeEvent(e); } void ImageWindow::showEvent(QShowEvent*) { // Restore the visibility of the thumbbar and start autosaving again. thumbBar()->restoreVisibility(); } bool ImageWindow::queryClose() { // Note: we re-implement closeEvent above for this window. // Additionally, queryClose is called from DigikamApp. // wait if a save operation is currently running if (!waitForSavingToComplete()) { return false; } return promptUserSave(d->currentUrl()); } void ImageWindow::loadItemInfos(const ItemInfoList& imageInfoList, const ItemInfo& imageInfoCurrent, const QString& caption) { // Very first thing is to check for changes, user may choose to cancel operation if (!promptUserSave(d->currentUrl(), AskIfNeeded)) { return; } d->currentItemInfo = imageInfoCurrent; // Note: Addition is asynchronous, indexes not yet available // We enable thumbbar as soon as indexes are available // If not, we load imageInfoCurrent, then the index 0, then again imageInfoCurrent d->thumbBar->setEnabled(false); d->imageInfoModel->setItemInfos(imageInfoList); d->setThumbBarToCurrent(); if (!caption.isEmpty()) { setCaption(i18n("Image Editor - %1", caption)); } else { setCaption(i18n("Image Editor")); } // it can slightly improve the responsiveness when inserting an event loop run here QTimer::singleShot(0, this, SLOT(slotLoadItemInfosStage2())); } void ImageWindow::slotLoadItemInfosStage2() { // if window is minimized, show it if (isMinimized()) { KWindowSystem::unminimizeWindow(winId()); } slotLoadCurrent(); } void ImageWindow::slotThumbBarModelReady() { d->thumbBar->setEnabled(true); } void ImageWindow::openImage(const ItemInfo& info) { if (d->currentItemInfo == info) { return; } d->currentItemInfo = info; d->ensureModelContains(d->currentItemInfo); slotLoadCurrent(); } void ImageWindow::slotLoadCurrent() { if (!d->currentIsValid()) { return; } m_canvas->load(d->currentItemInfo.filePath(), m_IOFileSettings); QModelIndex next = d->nextIndex(); if (next.isValid()) { m_canvas->preload(d->imageInfo(next).filePath()); } d->setThumbBarToCurrent(); // Do this _after_ the canvas->load(), so that the main view histogram does not load // a smaller version if a raw image, and after that the EditorCore loads the full version. // So first let EditorCore create its loading task, only then any external objects. setViewToURL(d->currentUrl()); } void ImageWindow::setViewToURL(const QUrl& url) { emit signalURLChanged(url); } void ImageWindow::slotThumbBarImageSelected(const ItemInfo& info) { if (d->currentItemInfo == info || !d->thumbBar->isEnabled()) { return; } if (!promptUserSave(d->currentUrl(), AskIfNeeded, false)) { return; } d->currentItemInfo = info; slotLoadCurrent(); } void ImageWindow::slotDroppedOnThumbbar(const QList& infos) { // Check whether dropped image list is empty if (infos.isEmpty()) { return; } // Create new list and images that are not present currently in the thumbbar QList toAdd; foreach (const ItemInfo& it, infos) { QModelIndex index(d->imageFilterModel->indexForItemInfo(it)); if (!index.isValid()) { toAdd.append(it); } } // Loading images if new images are dropped if (!toAdd.isEmpty()) { loadItemInfos(ItemInfoList(toAdd), toAdd.first(), QString()); } } void ImageWindow::slotFileOriginChanged(const QString& filePath) { // By redo or undo, we have virtually switched to a new image. // So we do _not_ load anything! ItemInfo newCurrent = ItemInfo::fromLocalFile(filePath); if (newCurrent.isNull() || !d->imageInfoModel->hasImage(newCurrent)) { return; } d->currentItemInfo = newCurrent; d->setThumbBarToCurrent(); setViewToURL(d->currentUrl()); } void ImageWindow::loadIndex(const QModelIndex& index) { if (!promptUserSave(d->currentUrl(), AskIfNeeded)) { return; } if (!index.isValid()) { return; } d->currentItemInfo = d->imageFilterModel->imageInfo(index); slotLoadCurrent(); } void ImageWindow::slotForward() { loadIndex(d->nextIndex()); } void ImageWindow::slotBackward() { loadIndex(d->previousIndex()); } void ImageWindow::slotFirst() { loadIndex(d->firstIndex()); } void ImageWindow::slotLast() { loadIndex(d->lastIndex()); } void ImageWindow::slotChanged() { QString mpixels; QSize dims(m_canvas->imageWidth(), m_canvas->imageHeight()); mpixels.setNum(dims.width()*dims.height() / 1000000.0, 'f', 2); QString str = (!dims.isValid()) ? i18n("Unknown") : i18n("%1x%2 (%3Mpx)", dims.width(), dims.height(), mpixels); m_resLabel->setAdjustedText(str); if (!d->currentIsValid()) { return; } DImg* const img = m_canvas->interface()->getImg(); DImageHistory history = m_canvas->interface()->getItemHistory(); DImageHistory redoHistory = m_canvas->interface()->getImageHistoryOfFullRedo(); d->rightSideBar->itemChanged(d->currentItemInfo, m_canvas->getSelectedArea(), img, redoHistory); // Filters for redo will be turn in grey out d->rightSideBar->getFiltersHistoryTab()->setEnabledHistorySteps(history.actionCount()); /* if (!d->currentItemInfo.isNull()) { } else { d->rightSideBar->itemChanged(d->currentUrl(), m_canvas->getSelectedArea(), img); } */ } void ImageWindow::slotToggleTag(const QUrl& url, int tagID) { toggleTag(ItemInfo::fromUrl(url), tagID); } void ImageWindow::toggleTag(int tagID) { toggleTag(d->currentItemInfo, tagID); } void ImageWindow::toggleTag(const ItemInfo& info, int tagID) { if (!info.isNull()) { if (info.tagIds().contains(tagID)) { FileActionMngr::instance()->removeTag(info, tagID); } else { FileActionMngr::instance()->assignTag(info, tagID); } } } void ImageWindow::slotAssignTag(int tagID) { if (!d->currentItemInfo.isNull()) { FileActionMngr::instance()->assignTag(d->currentItemInfo, tagID); } } void ImageWindow::slotRemoveTag(int tagID) { if (!d->currentItemInfo.isNull()) { FileActionMngr::instance()->removeTag(d->currentItemInfo, tagID); } } void ImageWindow::slotAssignPickLabel(int pickId) { assignPickLabel(d->currentItemInfo, pickId); } void ImageWindow::slotAssignColorLabel(int colorId) { assignColorLabel(d->currentItemInfo, colorId); } void ImageWindow::assignPickLabel(const ItemInfo& info, int pickId) { if (!info.isNull()) { FileActionMngr::instance()->assignPickLabel(info, pickId); } } void ImageWindow::assignColorLabel(const ItemInfo& info, int colorId) { if (!info.isNull()) { FileActionMngr::instance()->assignColorLabel(info, colorId); } } void ImageWindow::slotAssignRating(int rating) { assignRating(d->currentItemInfo, rating); } void ImageWindow::assignRating(const ItemInfo& info, int rating) { rating = qMin(RatingMax, qMax(RatingMin, rating)); if (!info.isNull()) { FileActionMngr::instance()->assignRating(info, rating); } } void ImageWindow::slotRatingChanged(const QUrl& url, int rating) { assignRating(ItemInfo::fromUrl(url), rating); } void ImageWindow::slotColorLabelChanged(const QUrl& url, int color) { assignColorLabel(ItemInfo::fromUrl(url), color); } void ImageWindow::slotPickLabelChanged(const QUrl& url, int pick) { assignPickLabel(ItemInfo::fromUrl(url), pick); } void ImageWindow::slotUpdateItemInfo() { QString text = i18nc(" ( of )", "%1 (%2 of %3)", d->currentItemInfo.name(), d->currentIndex().row() + 1, d->imageFilterModel->rowCount()); m_nameLabel->setText(text); if (!m_actionEnabledState) { return; } if (d->imageInfoModel->rowCount() == 1) { m_backwardAction->setEnabled(false); m_forwardAction->setEnabled(false); m_firstAction->setEnabled(false); m_lastAction->setEnabled(false); } else { m_backwardAction->setEnabled(true); m_forwardAction->setEnabled(true); m_firstAction->setEnabled(true); m_lastAction->setEnabled(true); } if (d->currentIndex() == d->firstIndex()) { m_backwardAction->setEnabled(false); m_firstAction->setEnabled(false); } if (d->currentIndex() == d->lastIndex()) { m_forwardAction->setEnabled(false); m_lastAction->setEnabled(false); } /* // Disable some menu actions if the current root image URL // is not include in the digiKam Albums library database. // This is necessary when ImageEditor is opened from cameraclient. QUrl u(d->currentUrl().directory()); PAlbum* palbum = AlbumManager::instance()->findPAlbum(u); if (!palbum) { m_fileDeleteAction->setEnabled(false); } else { m_fileDeleteAction->setEnabled(true); } */ } void ImageWindow::slotToMainWindow() { close(); } void ImageWindow::saveIsComplete() { // With save(), we do not reload the image but just continue using the data. // This means that a saving operation does not lead to quality loss for // subsequent editing operations. // put image in cache, the LoadingCacheInterface cares for the details LoadingCacheInterface::putImage(m_savingContext.destinationURL.toLocalFile(), m_canvas->currentImage()); ItemInfo info = ScanController::instance()->scannedInfo(m_savingContext.destinationURL.toLocalFile()); // Save new face tags to the image saveFaceTagsToImage(info); // reset the orientation flag in the database DMetadata meta(m_canvas->currentImage().getMetadata()); d->currentItemInfo.setOrientation(meta.getItemOrientation()); // Pop-up a message to bring user when save is done. DNotificationWrapper(QLatin1String("editorsavefilecompleted"), i18n("Image saved successfully"), this, windowTitle()); resetOrigin(); QModelIndex next = d->nextIndex(); if (next.isValid()) { m_canvas->preload(d->imageInfo(next).filePath()); } slotUpdateItemInfo(); setViewToURL(d->currentItemInfo.fileUrl()); } void ImageWindow::saveVersionIsComplete() { qCDebug(DIGIKAM_GENERAL_LOG) << "save version done"; saveAsIsComplete(); } void ImageWindow::saveAsIsComplete() { qCDebug(DIGIKAM_GENERAL_LOG) << "Saved" << m_savingContext.srcURL << "to" << m_savingContext.destinationURL; // Nothing to be done if operating without database if (d->currentItemInfo.isNull()) { return; } if (CollectionManager::instance()->albumRootPath(m_savingContext.srcURL).isNull() || CollectionManager::instance()->albumRootPath(m_savingContext.destinationURL).isNull()) { // not in-collection operation - nothing to do return; } // copy the metadata of the original file to the new file qCDebug(DIGIKAM_GENERAL_LOG) << "was versioned" << (m_savingContext.executedOperation == SavingContext::SavingStateVersion) << "current" << d->currentItemInfo.id() << d->currentItemInfo.name() << "destinations" << m_savingContext.versionFileOperation.allFilePaths(); ItemInfo sourceInfo = d->currentItemInfo; // Set new current index. Employ synchronous scanning for this main file. d->currentItemInfo = ScanController::instance()->scannedInfo(m_savingContext.destinationURL.toLocalFile()); if (m_savingContext.destinationExisted) { // reset the orientation flag in the database DMetadata meta(m_canvas->currentImage().getMetadata()); d->currentItemInfo.setOrientation(meta.getItemOrientation()); } QStringList derivedFilePaths; if (m_savingContext.executedOperation == SavingContext::SavingStateVersion) { derivedFilePaths = m_savingContext.versionFileOperation.allFilePaths(); } else { derivedFilePaths << m_savingContext.destinationURL.toLocalFile(); } // Will ensure files are scanned, and then copy attributes in a thread FileActionMngr::instance()->copyAttributes(sourceInfo, derivedFilePaths); // The model updates asynchronously, so we need to force addition of the main entry d->ensureModelContains(d->currentItemInfo); // Save new face tags to the image saveFaceTagsToImage(d->currentItemInfo); // set origin of EditorCore: "As if" the last saved image was loaded directly resetOriginSwitchFile(); // If the DImg is put in the cache under the new name, this means the new file will not be reloaded. // This may irritate users who want to check for quality loss in lossy formats. // In any case, only do that if the format did not change - too many assumptions otherwise (see bug #138949). if (m_savingContext.originalFormat == m_savingContext.format) { LoadingCacheInterface::putImage(m_savingContext.destinationURL.toLocalFile(), m_canvas->currentImage()); } // all that is done in slotLoadCurrent, except for loading d->thumbBar->setCurrentIndex(d->currentIndex()); QModelIndex next = d->nextIndex(); if (next.isValid()) { m_canvas->preload(d->imageInfo(next).filePath()); } setViewToURL(d->currentItemInfo.fileUrl()); slotUpdateItemInfo(); // Pop-up a message to bring user when save is done. DNotificationWrapper(QLatin1String("editorsavefilecompleted"), i18n("Image saved successfully"), this, windowTitle()); } void ImageWindow::prepareImageToSave() { if (!d->currentItemInfo.isNull()) { MetadataHub hub; hub.load(d->currentItemInfo); // Get face tags d->newFaceTags.clear(); QMultiMap faceTags; faceTags = hub.loadIntegerFaceTags(d->currentItemInfo); if (!faceTags.isEmpty()) { - QSize tempS = d->currentItemInfo.dimensions(); + QSize size = d->currentItemInfo.dimensions(); QMap::const_iterator it; for (it = faceTags.constBegin() ; it != faceTags.constEnd() ; ++it) { // Start transform each face rect QRect faceRect = it.value().toRect(); - int tempH = tempS.height(); - int tempW = tempS.width(); + QSize tempSize = size; qCDebug(DIGIKAM_GENERAL_LOG) << ">>>>>>>>>face rect before:" << faceRect.x() << faceRect.y() << faceRect.width() << faceRect.height(); for (int i = 0 ; i < m_transformQue.size() ; ++i) { EditorWindow::TransformType type = m_transformQue[i]; switch (type) { case EditorWindow::TransformType::RotateLeft: - faceRect = TagRegion::adjustToRotatedImg(faceRect, QSize(tempW, tempH), 1); - std::swap(tempH, tempW); + TagRegion::adjustToRotatedImg(faceRect, tempSize, 1); + tempSize.transpose(); break; case EditorWindow::TransformType::RotateRight: - faceRect = TagRegion::adjustToRotatedImg(faceRect, QSize(tempW, tempH), 0); - std::swap(tempH, tempW); + TagRegion::adjustToRotatedImg(faceRect, tempSize, 0); + tempSize.transpose(); break; case EditorWindow::TransformType::FlipHorizontal: - faceRect = TagRegion::adjustToFlippedImg(faceRect, QSize(tempW, tempH), 0); + TagRegion::adjustToFlippedImg(faceRect, tempSize, 0); break; case EditorWindow::TransformType::FlipVertical: - faceRect = TagRegion::adjustToFlippedImg(faceRect, QSize(tempW, tempH), 1); + TagRegion::adjustToFlippedImg(faceRect, tempSize, 1); break; default: break; } qCDebug(DIGIKAM_GENERAL_LOG) << ">>>>>>>>>face rect transform:" << faceRect.x() << faceRect.y() << faceRect.width() << faceRect.height(); } d->newFaceTags.insertMulti(it.key(), QVariant(faceRect)); } } // Ensure there is a UUID for the source image in the database, // even if not in the source image's metadata if (d->currentItemInfo.uuid().isNull()) { QString uuid = m_canvas->interface()->ensureHasCurrentUuid(); d->currentItemInfo.setUuid(uuid); } else { m_canvas->interface()->provideCurrentUuid(d->currentItemInfo.uuid()); } } } void ImageWindow::saveFaceTagsToImage(const ItemInfo& info) { if (!info.isNull() && !d->newFaceTags.isEmpty()) { // Delete all old faces FaceTagsEditor().removeAllFaces(info.id()); QMap::const_iterator it; for (it = d->newFaceTags.constBegin() ; it != d->newFaceTags.constEnd() ; ++it) { int tagId = FaceTags::getOrCreateTagForPerson(it.key()); if (tagId) { TagRegion region(it.value().toRect()); FaceTagsEditor().add(info.id(), tagId, region, false); } else { qCDebug(DIGIKAM_GENERAL_LOG) << "Failed to create a person tag for name" << it.key(); } } MetadataHub hub; hub.load(info); QSize tempS = info.dimensions(); hub.setFaceTags(d->newFaceTags, tempS); hub.write(info.filePath(), MetadataHub::WRITE_ALL); } m_transformQue.clear(); d->newFaceTags.clear(); } VersionManager* ImageWindow::versionManager() const { return &d->versionManager; } bool ImageWindow::save() { prepareImageToSave(); startingSave(d->currentUrl()); return true; } bool ImageWindow::saveAs() { prepareImageToSave(); return startingSaveAs(d->currentUrl()); } bool ImageWindow::saveNewVersion() { prepareImageToSave(); return startingSaveNewVersion(d->currentUrl()); } bool ImageWindow::saveCurrentVersion() { prepareImageToSave(); return startingSaveCurrentVersion(d->currentUrl()); } bool ImageWindow::saveNewVersionAs() { prepareImageToSave(); return startingSaveNewVersionAs(d->currentUrl()); } bool ImageWindow::saveNewVersionInFormat(const QString& format) { prepareImageToSave(); return startingSaveNewVersionInFormat(d->currentUrl(), format); } QUrl ImageWindow::saveDestinationUrl() { return d->currentUrl(); } void ImageWindow::slotDeleteCurrentItem() { deleteCurrentItem(true, false); } void ImageWindow::slotDeleteCurrentItemPermanently() { deleteCurrentItem(true, true); } void ImageWindow::slotDeleteCurrentItemPermanentlyDirectly() { deleteCurrentItem(false, true); } void ImageWindow::slotTrashCurrentItemDirectly() { deleteCurrentItem(false, false); } void ImageWindow::deleteCurrentItem(bool ask, bool permanently) { // This function implements all four of the above slots. // The meaning of permanently differs depending on the value of ask if (d->currentItemInfo.isNull()) { return; } if (!promptUserDelete(d->currentUrl())) { return; } bool useTrash; if (ask) { bool preselectDeletePermanently = permanently; DeleteDialog dialog(this); QList urlList; urlList << d->currentUrl(); if (!dialog.confirmDeleteList(urlList, DeleteDialogMode::Files, preselectDeletePermanently ? DeleteDialogMode::NoChoiceDeletePermanently : DeleteDialogMode::NoChoiceTrash)) { return; } useTrash = !dialog.shouldDelete(); } else { useTrash = !permanently; } DIO::del(d->currentItemInfo, useTrash); // bring all (sidebar) to a defined state without letting them sit on the deleted file emit signalNoCurrentItem(); // We have database information, which means information will get through // everywhere. Just do it asynchronously. removeCurrent(); } void ImageWindow::removeCurrent() { if (!d->currentIsValid()) { return; } if (m_canvas->interface()->undoState().hasChanges) { m_canvas->slotRestore(); } d->imageInfoModel->removeItemInfo(d->currentItemInfo); if (d->imageInfoModel->isEmpty()) { // No image in the current Album -> Quit ImageEditor... QMessageBox::information(this, i18n("No Image in Current Album"), i18n("There is no image to show in the current album.\n" "The image editor will be closed.")); close(); } } void ImageWindow::slotFileMetadataChanged(const QUrl& url) { if (url == d->currentUrl()) { m_canvas->interface()->readMetadataFromFile(url.toLocalFile()); } } /* * Should all be done by ItemViewCategorized * void ImageWindow::slotRowsAboutToBeRemoved(const QModelIndex& parent, int start, int end) { // ignore when closed if (!isVisible() || !d->currentIsValid()) { return; } QModelIndex currentIndex = d->currentIndex(); if (currentIndex.row() >= start && currentIndex.row() <= end) { promptUserSave(d->currentUrl(), AlwaysNewVersion, false); // ensure selection int totalToRemove = end - start + 1; if (d->imageFilterModel->rowCount(parent) > totalToRemove) { if (end == d->imageFilterModel->rowCount(parent) - 1) { loadIndex(d->imageFilterModel->index(start - 1, 0)); // last remaining, no next one left } else { loadIndex(d->imageFilterModel->index(end + 1, 0)); // next remaining } } } }*/ /* void ImageWindow::slotCollectionImageChange(const CollectionImageChangeset& changeset) { bool needLoadCurrent = false; switch (changeset.operation()) { case CollectionImageChangeset::Removed: for (int i=0; iimageInfoList.size(); ++i) { if (changeset.containsImage(d->imageInfoList[i].id())) { if (d->currentItemInfo == d->imageInfoList[i]) { promptUserSave(d->currentUrl(), AlwaysNewVersion, false); if (removeItem(i)) { needLoadCurrent = true; } } else { removeItem(i); } --i; } } break; case CollectionImageChangeset::RemovedAll: for (int i=0; iimageInfoList.size(); ++i) { if (changeset.containsAlbum(d->imageInfoList[i].albumId())) { if (d->currentItemInfo == d->imageInfoList[i]) { promptUserSave(d->currentUrl(), AlwaysNewVersion, false); if (removeItem(i)) { needLoadCurrent = true; } } else { removeItem(i); } --i; } } break; default: break; } if (needLoadCurrent) { QTimer::singleShot(0, this, SLOT(slotLoadCurrent())); } } */ void ImageWindow::dragMoveEvent(QDragMoveEvent* e) { int albumID; QList albumIDs; QList imageIDs; QList urls; if (DItemDrag::decode(e->mimeData(), urls, albumIDs, imageIDs) || DAlbumDrag::decode(e->mimeData(), urls, albumID) || DTagListDrag::canDecode(e->mimeData())) { e->accept(); return; } e->ignore(); } void ImageWindow::dropEvent(QDropEvent* e) { int albumID; QList albumIDs; QList imageIDs; QList urls; if (DItemDrag::decode(e->mimeData(), urls, albumIDs, imageIDs)) { ItemInfoList imageInfoList(imageIDs); if (imageInfoList.isEmpty()) { e->ignore(); return; } QString ATitle; AlbumManager* const man = AlbumManager::instance(); PAlbum* const palbum = man->findPAlbum(albumIDs.first()); if (palbum) { ATitle = palbum->title(); } loadItemInfos(imageInfoList, imageInfoList.first(), i18n("Album \"%1\"", ATitle)); e->accept(); } else if (DAlbumDrag::decode(e->mimeData(), urls, albumID)) { AlbumManager* const man = AlbumManager::instance(); QList itemIDs = CoreDbAccess().db()->getItemIDsInAlbum(albumID); ItemInfoList imageInfoList(itemIDs); if (imageInfoList.isEmpty()) { e->ignore(); return; } QString ATitle; PAlbum* const palbum = man->findPAlbum(albumIDs.first()); if (palbum) { ATitle = palbum->title(); } loadItemInfos(imageInfoList, imageInfoList.first(), i18n("Album \"%1\"", ATitle)); e->accept(); } else if (DTagListDrag::canDecode(e->mimeData())) { QList tagIDs; if (!DTagListDrag::decode(e->mimeData(), tagIDs)) { return; } AlbumManager* const man = AlbumManager::instance(); QList itemIDs = CoreDbAccess().db()->getItemIDsInTag(tagIDs.first(), true); ItemInfoList imageInfoList(itemIDs); if (imageInfoList.isEmpty()) { e->ignore(); return; } QString ATitle; TAlbum* const talbum = man->findTAlbum(tagIDs.first()); if (talbum) { ATitle = talbum->title(); } loadItemInfos(imageInfoList, imageInfoList.first(), i18n("Album \"%1\"", ATitle)); e->accept(); } else { e->ignore(); } } void ImageWindow::slotRevert() { if (!promptUserSave(d->currentUrl(), AskIfNeeded)) { return; } if (m_canvas->interface()->undoState().hasChanges) { m_canvas->slotRestore(); } } void ImageWindow::slotOpenOriginal() { if (!hasOriginalToRestore()) { return; } if (!promptUserSave(d->currentUrl(), AskIfNeeded)) { return; } // this time, with mustBeAvailable = true DImageHistory availableResolved = ItemScanner::resolvedImageHistory(m_canvas->interface()->getItemHistory(), true); QList originals = availableResolved.referredImagesOfType(HistoryImageId::Original); HistoryImageId originalId = m_canvas->interface()->getItemHistory().originalReferredImage(); if (originals.isEmpty()) { //TODO: point to remote collection QMessageBox::warning(this, i18nc("@title", "File Not Available"), i18nc("@info", "The original file (%1) is currently not available", originalId.m_fileName)); return; } QList imageInfos; foreach(const HistoryImageId& id, originals) { QUrl url = QUrl::fromLocalFile(id.m_filePath); url = url.adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + QLatin1Char('/') + (id.m_fileName)); imageInfos << ItemInfo::fromUrl(url); } ItemScanner::sortByProximity(imageInfos, d->currentItemInfo); if (!imageInfos.isEmpty() && !imageInfos.first().isNull()) { openImage(imageInfos.first()); } } bool ImageWindow::hasOriginalToRestore() { // not implemented for db-less situation, so check for ItemInfo return !d->currentItemInfo.isNull() && EditorWindow::hasOriginalToRestore(); } DImageHistory ImageWindow::resolvedImageHistory(const DImageHistory& history) { return ItemScanner::resolvedImageHistory(history); } ThumbBarDock* ImageWindow::thumbBar() const { return d->thumbBarDock; } Sidebar* ImageWindow::rightSideBar() const { return (dynamic_cast(d->rightSideBar)); } void ImageWindow::slotComponentsInfo() { showDigikamComponentsInfo(); } void ImageWindow::slotDBStat() { showDigikamDatabaseStat(); } void ImageWindow::slotAddedDropedItems(QDropEvent* e) { int albumID; QList albumIDs; QList imageIDs; QList urls; ItemInfoList imgList; if (DItemDrag::decode(e->mimeData(), urls, albumIDs, imageIDs)) { imgList = ItemInfoList(imageIDs); } else if (DAlbumDrag::decode(e->mimeData(), urls, albumID)) { QList itemIDs = CoreDbAccess().db()->getItemIDsInAlbum(albumID); imgList = ItemInfoList(itemIDs); } else if (DTagListDrag::canDecode(e->mimeData())) { QList tagIDs; if (!DTagListDrag::decode(e->mimeData(), tagIDs)) { return; } QList itemIDs = CoreDbAccess().db()->getItemIDsInTag(tagIDs.first(), true); imgList = ItemInfoList(itemIDs); } e->accept(); if (!imgList.isEmpty()) { loadItemInfos(imgList, imgList.first(), QString()); } } void ImageWindow::slotFileWithDefaultApplication() { DFileOperations::openFilesWithDefaultApplication(QList() << d->currentUrl()); } void ImageWindow::slotOpenWith(QAction* action) { openWith(d->currentUrl(), action); } void ImageWindow::slotRightSideBarActivateTitles() { d->rightSideBar->setActiveTab(d->rightSideBar->imageDescEditTab()); d->rightSideBar->imageDescEditTab()->setFocusToTitlesEdit(); } void ImageWindow::slotRightSideBarActivateComments() { d->rightSideBar->setActiveTab(d->rightSideBar->imageDescEditTab()); d->rightSideBar->imageDescEditTab()->setFocusToCommentsEdit(); } void ImageWindow::slotRightSideBarActivateAssignedTags() { d->rightSideBar->setActiveTab(d->rightSideBar->imageDescEditTab()); d->rightSideBar->imageDescEditTab()->activateAssignedTagsButton(); } DInfoInterface* ImageWindow::infoIface(DPluginAction* const ac) { ApplicationSettings::OperationType aset = ApplicationSettings::Unspecified; switch (ac->actionCategory()) { case DPluginAction::GenericExport: case DPluginAction::GenericImport: aset = ApplicationSettings::ImportExport; break; case DPluginAction::GenericMetadata: aset = ApplicationSettings::Metadata; break; case DPluginAction::GenericTool: aset = ApplicationSettings::Tools; break; case DPluginAction::GenericView: aset = ApplicationSettings::Slideshow; break; default: break; } DBInfoIface* const iface = new DBInfoIface(this, d->thumbBar->allUrls(), aset); connect(iface, SIGNAL(signalImportedImage(QUrl)), this, SLOT(slotImportedImagefromScanner(QUrl))); return iface; } } // namespace Digikam