diff --git a/core/app/date/monthwidget.cpp b/core/app/date/monthwidget.cpp index f95aa68634..0680553e6a 100644 --- a/core/app/date/monthwidget.cpp +++ b/core/app/date/monthwidget.cpp @@ -1,520 +1,520 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2005-05-02 * Description : a widget to perform month selection. * * Copyright (C) 2005 by Renchi Raju * Copyright (C) 2006-2019 by Gilles Caulier * Copyright (C) 2011 by Andi Clemens * * 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 "monthwidget.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include // Local includes #include "itemfiltermodel.h" #include "itemmodel.h" namespace Digikam { class Q_DECL_HIDDEN MonthWidget::Private { public: struct Month { Month() { active = false; selected = false; day = 0; numImages = 0; } bool active; bool selected; int day; int numImages; }; public: explicit Private() : active(true), model(0), timer(0), year(0), month(0), width(0), height(0), currw(0), currh(0) { } bool active; ItemFilterModel* model; QTimer* timer; int year; int month; int width; int height; int currw; int currh; struct Month days[42]; }; MonthWidget::MonthWidget(QWidget* const parent) : QWidget(parent), d(new Private) { init(); QDate date = QDate::currentDate(); setYearMonth(date.year(), date.month()); setActive(false); d->timer = new QTimer(this); d->timer->setSingleShot(true); d->timer->setInterval(150); connect(d->timer, &QTimer::timeout, this, &MonthWidget::updateDays); } MonthWidget::~MonthWidget() { delete d; } void MonthWidget::init() { QFont fn(font()); fn.setBold(true); fn.setPointSize(fn.pointSize()+1); QFontMetrics fm(fn); QRect r(fm.boundingRect(QLatin1String("XX"))); r.setWidth(r.width() + 2); r.setHeight(r.height() + 4); d->width = r.width(); d->height = r.height(); setMinimumWidth(d->width * 8); setMinimumHeight(d->height * 9); } void MonthWidget::setYearMonth(int year, int month) { d->year = year; d->month = month; - for (int i=0; i<42; ++i) + for (int i = 0 ; i < 42 ; ++i) { d->days[i].active = false; d->days[i].selected = false; d->days[i].day = -1; d->days[i].numImages = 0; } QDate date(year, month, 1); int s = date.dayOfWeek(); - for (int i=s; i<(s+date.daysInMonth()); ++i) + for (int i = s ; i < (s+date.daysInMonth()) ; ++i) { d->days[i-1].day = i-s+1; } update(); } QSize MonthWidget::sizeHint() const { return QSize(d->width * 8, d->height * 9); } void MonthWidget::resizeEvent(QResizeEvent* e) { QWidget::resizeEvent(e); d->currw = contentsRect().width()/8; d->currh = contentsRect().height()/9; } void MonthWidget::paintEvent(QPaintEvent*) { QRect cr(contentsRect()); QPixmap pix(cr.width(), cr.height()); QFont fnBold(font()); QFont fnOrig(font()); fnBold.setBold(true); fnOrig.setBold(false); QPainter p(&pix); p.fillRect(0, 0, cr.width(), cr.height(), palette().color(QPalette::Window)); QRect r(0, 0, d->currw, d->currh); QRect rsmall; int sx, sy; int index = 0; bool weekvisible; - for (int j=3; j<9; ++j) + for (int j = 3 ; j < 9 ; ++j) { sy = d->currh * j; weekvisible = false; - for (int i=1; i<8; ++i) + for (int i = 1 ; i < 8 ; ++i) { sx = d->currw * i; r.moveTopLeft(QPoint(sx,sy)); rsmall = QRect(r.x()+1, r.y()+1, r.width()-2, r.height()-2); if (d->days[index].day != -1) { if (d->days[index].selected) { p.fillRect(r, palette().color(QPalette::Highlight)); p.setPen(palette().color(QPalette::HighlightedText)); if (d->days[index].active) { p.setFont(fnBold); } else { p.setFont(fnOrig); } } else { if (d->days[index].active) { p.setPen(palette().color(QPalette::Text)); p.setFont(fnBold); } else { p.setPen(palette().color(QPalette::Mid)); p.setFont(fnOrig); } } p.drawText(rsmall, Qt::AlignVCenter|Qt::AlignHCenter, QString::number(d->days[index].day)); if (!weekvisible) { int weeknr = QDate(d->year, d->month, d->days[index].day).weekNumber(); p.setPen(d->active ? Qt::black : Qt::gray); p.setFont(fnBold); p.fillRect(1, sy, d->currw-1, d->currh-1, QColor(210, 210, 210)); p.drawText(1, sy, d->currw-1, d->currh-1, Qt::AlignVCenter|Qt::AlignHCenter, QString::number(weeknr)); weekvisible = true; } } ++index; } } p.setPen(d->active ? Qt::black : Qt::gray); p.setFont(fnBold); sy = 2*d->currh; - for (int i = 1; i < 8; ++i) + for (int i = 1 ; i < 8 ; ++i) { sx = d->currw * i; r.moveTopLeft(QPoint(sx+1,sy+1)); rsmall = r; rsmall.setWidth(r.width() - 2); rsmall.setHeight(r.height() - 2); p.drawText(rsmall, Qt::AlignVCenter|Qt::AlignHCenter, QLocale().standaloneDayName(i, QLocale::ShortFormat).remove(2, 1)); ++index; } r = QRect(0, 0, cr.width(), 2*d->currh); fnBold.setPointSize(fnBold.pointSize()+2); p.setFont(fnBold); p.drawText(r, Qt::AlignCenter, QString::fromUtf8("%1 %2") .arg(QLocale().standaloneMonthName(d->month, QLocale::LongFormat)) .arg(QDate(d->year, d->month, 1).year())); p.end(); QPainter p2(this); p2.drawPixmap(cr.x(), cr.y(), pix); p2.end(); } void MonthWidget::mousePressEvent(QMouseEvent* e) { int firstSelected = 0, lastSelected = 0; if (e->modifiers() != Qt::ControlModifier) { - for (int i=0; i<42; ++i) + for (int i = 0 ; i < 42 ; ++i) { if (d->days[i].selected) { if (firstSelected==0) { firstSelected = i; } lastSelected =i; } d->days[i].selected = false; } } QRect r1(0, d->currh*3, d->currw, d->currh*6); QRect r2(d->currw, d->currh*3, d->currw*7, d->currh*6); QRect r3(d->currw, d->currh*2, d->currw*7, d->currh); // Click on a weekday - if ( r3.contains(e->pos())) + if (r3.contains(e->pos())) { int j = (e->pos().x() - d->currw)/d->currw; - for (int i=0; i<6; ++i) + for (int i = 0 ; i < 6 ; ++i) { d->days[i*7+j].selected = !d->days[i*7+j].selected; } } // Click on a week else if (r1.contains(e->pos())) { int j = (e->pos().y() - 3*d->currh)/d->currh; - for (int i=0; i<7; ++i) + for (int i = 0 ; i < 7 ; ++i) { d->days[j*7+i].selected = !d->days[j*7+i].selected; } } // Click on a day. else if (r2.contains(e->pos())) { int i, j; i = (e->pos().x() - d->currw)/d->currw; j = (e->pos().y() - 3*d->currh)/d->currh; if (e->modifiers() == Qt::ShiftModifier) { int endSelection = j*7+i; if (endSelection > firstSelected) - for (int i2=firstSelected ; i2 <= endSelection; ++i2) + for (int i2=firstSelected ; i2 <= endSelection ; ++i2) { d->days[i2].selected = true; } else if (endSelection < firstSelected) - for (int i2=lastSelected ; i2 >= endSelection; --i2) + for (int i2=lastSelected ; i2 >= endSelection ; --i2) { d->days[i2].selected = true; } } else { d->days[j*7+i].selected = !d->days[j*7+i].selected; } } QList filterDays; - for (int i=0; i<42; ++i) + for (int i = 0 ; i < 42 ; ++i) { if (d->days[i].selected && d->days[i].day != -1) { filterDays.append(QDateTime(QDate(d->year, d->month, d->days[i].day), QTime())); } } if (d->model) { d->model->setDayFilter(filterDays); } update(); } void MonthWidget::setActive(bool val) { if (d->active == val) { return; } d->active = val; if (d->active) { connectModel(); triggerUpdateDays(); } else { QDate date = QDate::currentDate(); setYearMonth(date.year(), date.month()); if (d->model) { d->model->setDayFilter(QList()); disconnect(d->model, 0, this, 0); } } } void MonthWidget::setItemModel(ItemFilterModel* model) { if (d->model) { disconnect(d->model, 0, this, 0); } d->model = model; connectModel(); triggerUpdateDays(); } void MonthWidget::connectModel() { if (d->model) { connect(d->model, &ItemFilterModel::destroyed, this, &MonthWidget::slotModelDestroyed); connect(d->model, &ItemFilterModel::rowsInserted, this, &MonthWidget::triggerUpdateDays); connect(d->model, &ItemFilterModel::rowsRemoved, this, &MonthWidget::triggerUpdateDays); connect(d->model, &ItemFilterModel::modelReset, this, &MonthWidget::triggerUpdateDays); /* connect(d->model, SIGNAL(triggerUpdateDays()), this, SLOT(triggerUpdateDays())); connect(d->model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(triggerUpdateDays())); */ } } void MonthWidget::triggerUpdateDays() { if (!d->timer->isActive()) { d->timer->start(); } } void MonthWidget::resetDayCounts() { - for (int i=0; i<42; ++i) + for (int i = 0 ; i < 42 ; ++i) { d->days[i].active = false; d->days[i].numImages = 0; } } void MonthWidget::updateDays() { if (!d->active) { return; } resetDayCounts(); if (!d->model) { return; } const int size = d->model->rowCount(); - for (int i=0; imodel->index(i, 0); if (!index.isValid()) { continue; } QDateTime dt = d->model->data(index, ItemModel::CreationDateRole).toDateTime(); if (dt.isNull()) { continue; } - for (int i=0; i<42; ++i) + for (int i = 0 ; i < 42 ; ++i) { if (d->days[i].day == dt.date().day()) { d->days[i].active = true; d->days[i].numImages++; break; } } } update(); } void MonthWidget::slotModelDestroyed() { d->model = 0; resetDayCounts(); update(); } } // namespace Digikam diff --git a/core/app/items/utils/contextmenuhelper.cpp b/core/app/items/utils/contextmenuhelper.cpp index f82d6c050b..801eab966d 100644 --- a/core/app/items/utils/contextmenuhelper.cpp +++ b/core/app/items/utils/contextmenuhelper.cpp @@ -1,1226 +1,1226 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2009-02-15 * Description : contextmenu helper class * * Copyright (C) 2009-2011 by Andi Clemens * Copyright (C) 2010-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 "contextmenuhelper.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include #include #ifdef HAVE_KIO # include #endif // Local includes #include "digikam_debug.h" #include "album.h" #include "coredb.h" #include "albummanager.h" #include "albumpointer.h" #include "albummodificationhelper.h" #include "abstractalbummodel.h" #include "coredbaccess.h" #include "digikamapp.h" #include "dfileoperations.h" #include "iteminfo.h" #include "itemfiltermodel.h" #include "lighttablewindow.h" #include "queuemgrwindow.h" #include "picklabelwidget.h" #include "colorlabelwidget.h" #include "ratingwidget.h" #include "tagmodificationhelper.h" #include "tagspopupmenu.h" #include "fileactionmngr.h" #include "tagscache.h" #include "dimg.h" #include "dxmlguiwindow.h" #ifdef HAVE_AKONADICONTACT # include "akonadiiface.h" #endif #ifdef Q_OS_WIN # include # include #endif namespace Digikam { class Q_DECL_HIDDEN ContextMenuHelper::Private { public: explicit Private(ContextMenuHelper* const q) : gotoAlbumAction(0), gotoDateAction(0), setThumbnailAction(0), imageFilterModel(0), albumModel(0), parent(0), stdActionCollection(0), q(q) { } QAction* gotoAlbumAction; QAction* gotoDateAction; QAction* setThumbnailAction; QList selectedIds; QList selectedItems; QMap queueActions; QMap servicesMap; ItemFilterModel* imageFilterModel; AbstractCheckableAlbumModel* albumModel; QMenu* parent; KActionCollection* stdActionCollection; ContextMenuHelper* q; public: QModelIndex indexForAlbumFromAction(QObject* sender) const { QAction* action = 0; if ((action = qobject_cast(sender))) { Album* const album = action->data().value >(); return albumModel->indexForAlbum(album); } return QModelIndex(); } QAction* copyFromMainCollection(const QString& name) const { QAction* const mainAction = stdActionCollection->action(name); if (!mainAction) { return 0; } QAction* const action = new QAction(mainAction->icon(), mainAction->text(), q); action->setShortcut(mainAction->shortcut()); action->setToolTip(mainAction->toolTip()); return action; } }; ContextMenuHelper::ContextMenuHelper(QMenu* const parent, KActionCollection* const actionCollection) : QObject(parent), d(new Private(this)) { d->parent = parent; if (!actionCollection) { d->stdActionCollection = DigikamApp::instance()->actionCollection(); } else { d->stdActionCollection = actionCollection; } } ContextMenuHelper::~ContextMenuHelper() { delete d; } void ContextMenuHelper::addAction(const QString& name, bool addDisabled) { QAction* const action = d->stdActionCollection->action(name); addAction(action, addDisabled); } void ContextMenuHelper::addAction(QAction* action, bool addDisabled) { if (!action) { return; } if (action->isEnabled() || addDisabled) { d->parent->addAction(action); } } void ContextMenuHelper::addSubMenu(QMenu* subMenu) { d->parent->addMenu(subMenu); } void ContextMenuHelper::addSeparator() { d->parent->addSeparator(); } void ContextMenuHelper::addAction(QAction* action, QObject* recv, const char* slot, bool addDisabled) { if (!action) { return; } connect(action, SIGNAL(triggered()), recv, slot); addAction(action, addDisabled); } void ContextMenuHelper::addStandardActionLightTable() { QAction* action = 0; QStringList ltActionNames; ltActionNames << QLatin1String("image_add_to_lighttable") << QLatin1String("image_lighttable"); if (LightTableWindow::lightTableWindowCreated() && !LightTableWindow::lightTableWindow()->isEmpty()) { action = d->stdActionCollection->action(ltActionNames.at(0)); } else { action = d->stdActionCollection->action(ltActionNames.at(1)); } addAction(action); } void ContextMenuHelper::addStandardActionThumbnail(const imageIds& ids, Album* album) { if (d->setThumbnailAction) { return; } setSelectedIds(ids); if (album && ids.count() == 1) { if (album->type() == Album::PHYSICAL) { d->setThumbnailAction = new QAction(i18n("Set as Album Thumbnail"), this); } else if (album->type() == Album::TAG) { d->setThumbnailAction = new QAction(i18n("Set as Tag Thumbnail"), this); } addAction(d->setThumbnailAction); d->parent->addSeparator(); } } void ContextMenuHelper::addOpenAndNavigateActions(const imageIds &ids) { addAction(QLatin1String("image_edit")); addServicesMenu(ItemInfoList(ids).toImageUrlList()); addAction(QLatin1String("move_selection_to_album")); addGotoMenu(ids); } void ContextMenuHelper::addServicesMenu(const QList& selectedItems) { setSelectedItems(selectedItems); #ifdef Q_OS_WIN if (selectedItems.length() == 1) { QAction* const openWith = new QAction(i18n("Open With"), this); addAction(openWith); connect(openWith, SIGNAL(triggered()), this, SLOT(slotOpenWith())); } #else // Q_OS_WIN KService::List offers = DFileOperations::servicesForOpenWith(selectedItems); if (!offers.isEmpty()) { QMenu* const servicesMenu = new QMenu(d->parent); qDeleteAll(servicesMenu->actions()); QAction* const serviceAction = servicesMenu->menuAction(); serviceAction->setText(i18n("Open With")); foreach (const KService::Ptr& service, offers) { QString name = service->name().replace(QLatin1Char('&'), QLatin1String("&&")); QAction* const action = servicesMenu->addAction(name); action->setIcon(QIcon::fromTheme(service->icon())); action->setData(service->name()); d->servicesMap[name] = service; } # ifdef HAVE_KIO servicesMenu->addSeparator(); servicesMenu->addAction(i18n("Other...")); addAction(serviceAction); connect(servicesMenu, SIGNAL(triggered(QAction*)), this, SLOT(slotOpenWith(QAction*))); } else { QAction* const serviceAction = new QAction(i18n("Open With..."), this); addAction(serviceAction); connect(serviceAction, SIGNAL(triggered()), this, SLOT(slotOpenWith())); # endif // HAVE_KIO } #endif // Q_OS_WIN } void ContextMenuHelper::slotOpenWith() { // call the slot with an "empty" action slotOpenWith(0); } void ContextMenuHelper::slotOpenWith(QAction* action) { #ifdef Q_OS_WIN Q_UNUSED(action); // See Bug #380065 for details. if (d->selectedItems.length() == 1) { SHELLEXECUTEINFO sei = {}; sei.cbSize = sizeof(sei); sei.fMask = SEE_MASK_INVOKEIDLIST | SEE_MASK_NOASYNC; sei.nShow = SW_SHOWNORMAL; sei.lpVerb = (LPCWSTR)QString::fromLatin1("openas").utf16(); sei.lpFile = (LPCWSTR)d->selectedItems.first().toLocalFile().utf16(); ShellExecuteEx(&sei); qCDebug(DIGIKAM_GENERAL_LOG) << "ShellExecuteEx::openas called"; } #else // Q_OS_WIN KService::Ptr service; QList list = d->selectedItems; QString name = action ? action->data().toString() : QString(); # ifdef HAVE_KIO if (name.isEmpty()) { QPointer dlg = new KOpenWithDialog(list); if (dlg->exec() != KOpenWithDialog::Accepted) { delete dlg; return; } service = dlg->service(); if (!service) { // User entered a custom command if (!dlg->text().isEmpty()) { DFileOperations::runFiles(dlg->text(), list); } delete dlg; return; } delete dlg; } else # endif // HAVE_KIO { service = d->servicesMap[name]; } DFileOperations::runFiles(service.data(), list); #endif // Q_OS_WIN } bool ContextMenuHelper::imageIdsHaveSameCategory(const imageIds& ids, DatabaseItem::Category category) { bool sameCategory = true; QVariantList varList; foreach (const qlonglong& id, ids) { varList = CoreDbAccess().db()->getImagesFields(id, DatabaseFields::Category); if (varList.isEmpty() || (DatabaseItem::Category)varList.first().toInt() != category) { sameCategory = false; break; } } return sameCategory; } void ContextMenuHelper::addActionNewTag(TagModificationHelper* helper, TAlbum* tag) { QAction* const newTagAction = new QAction(QIcon::fromTheme(QLatin1String("tag-new")), i18n("New Tag..."), this); addAction(newTagAction); helper->bindTag(newTagAction, tag); connect(newTagAction, SIGNAL(triggered()), helper, SLOT(slotTagNew())); } void ContextMenuHelper::addActionDeleteTag(TagModificationHelper* helper, TAlbum* tag) { QAction* const deleteTagAction = new QAction(QIcon::fromTheme(QLatin1String("edit-delete")), i18n("Delete Tag"), this); addAction(deleteTagAction); helper->bindTag(deleteTagAction, tag); connect(deleteTagAction, SIGNAL(triggered()), helper, SLOT(slotTagDelete())); } void ContextMenuHelper::addActionDeleteTags(Digikam::TagModificationHelper* helper, QList< TAlbum* > tags) { QAction* const deleteTagsAction = new QAction(QIcon::fromTheme(QLatin1String("edit-delete")), i18n("Delete Tags"), this); addAction(deleteTagsAction); helper->bindMultipleTags(deleteTagsAction, tags); connect(deleteTagsAction, SIGNAL(triggered()), helper, SLOT(slotMultipleTagDel())); } void ContextMenuHelper::addActionTagToFaceTag(TagModificationHelper* helper, TAlbum* tag) { QAction* const tagToFaceTagAction = new QAction(QIcon::fromTheme(QLatin1String("tag-properties")), i18n("Mark As Face Tag"), this); addAction(tagToFaceTagAction); helper->bindTag(tagToFaceTagAction, tag); connect(tagToFaceTagAction, SIGNAL(triggered()), helper, SLOT(slotTagToFaceTag())); } void ContextMenuHelper::addActionTagsToFaceTags(TagModificationHelper* helper, QList< TAlbum* > tags) { QAction* const tagToFaceTagsAction = new QAction(QIcon::fromTheme(QLatin1String("tag-properties")), i18n("Mark As Face Tags"), this); addAction(tagToFaceTagsAction); helper->bindMultipleTags(tagToFaceTagsAction, tags); connect(tagToFaceTagsAction, SIGNAL(triggered()), helper, SLOT(slotMultipleTagsToFaceTags())); } void ContextMenuHelper::addActionEditTag(TagModificationHelper* helper, TAlbum* tag) { QAction* const editTagAction = new QAction(QIcon::fromTheme(QLatin1String("tag-properties")), i18nc("Edit Tag Properties", "Properties..."), this); // This is only for the user to give a hint for the shortcut key editTagAction->setShortcut(Qt::ALT + Qt::Key_Return); addAction(editTagAction); helper->bindTag(editTagAction, tag); connect(editTagAction, SIGNAL(triggered()), helper, SLOT(slotTagEdit())); } void ContextMenuHelper::addActionDeleteFaceTag(TagModificationHelper* helper, TAlbum* tag) { QAction* const deleteFaceTagAction = new QAction(QIcon::fromTheme(QLatin1String("user-trash")), i18n("Remove Face Tag"), this); deleteFaceTagAction->setWhatsThis(i18n("Removes the face property from the selected tag and the face region from the contained images. Can also untag the images if wished.")); addAction(deleteFaceTagAction); helper->bindTag(deleteFaceTagAction, tag); connect(deleteFaceTagAction, SIGNAL(triggered()), helper, SLOT(slotFaceTagDelete())); } void ContextMenuHelper::addActionDeleteFaceTags(TagModificationHelper* helper, QList< TAlbum* > tags) { QAction* const deleteFaceTagsAction = new QAction(QIcon::fromTheme(QLatin1String("user-trash")), i18n("Remove Face Tags"), this); deleteFaceTagsAction->setWhatsThis(i18n("Removes the face property from the selected tags and the face region from the contained images. Can also untag the images if wished.")); addAction(deleteFaceTagsAction); helper->bindMultipleTags(deleteFaceTagsAction, tags); connect(deleteFaceTagsAction, SIGNAL(triggered()), helper, SLOT(slotMultipleFaceTagDel())); } void ContextMenuHelper::addActionNewAlbum(AlbumModificationHelper* helper, PAlbum* parentAlbum) { QAction* const action = d->copyFromMainCollection(QLatin1String("album_new")); addAction(action); helper->bindAlbum(action, parentAlbum); connect(action, SIGNAL(triggered()), helper, SLOT(slotAlbumNew())); } void ContextMenuHelper::addActionDeleteAlbum(AlbumModificationHelper* helper, PAlbum* album) { QAction* const action = d->copyFromMainCollection(QLatin1String("album_delete")); addAction(action, !(album->isRoot() || album->isAlbumRoot())); helper->bindAlbum(action, album); connect(action, SIGNAL(triggered()), helper, SLOT(slotAlbumDelete())); } void ContextMenuHelper::addActionEditAlbum(AlbumModificationHelper* helper, PAlbum* album) { QAction* const action = d->copyFromMainCollection(QLatin1String("album_propsEdit")); addAction(action, !album->isRoot()); helper->bindAlbum(action, album); connect(action, SIGNAL(triggered()), helper, SLOT(slotAlbumEdit())); } void ContextMenuHelper::addActionRenameAlbum(AlbumModificationHelper* helper, PAlbum* album) { QAction* const action = d->copyFromMainCollection(QLatin1String("album_rename")); addAction(action, !(album->isRoot() || album->isAlbumRoot())); helper->bindAlbum(action, album); connect(action, SIGNAL(triggered()), helper, SLOT(slotAlbumRename())); } void ContextMenuHelper::addActionResetAlbumIcon(AlbumModificationHelper* helper, PAlbum* album) { QAction* const action = new QAction(QIcon::fromTheme(QLatin1String("view-refresh")), i18n("Reset Album Icon"), this); addAction(action, !album->isRoot()); helper->bindAlbum(action, album); connect(action, SIGNAL(triggered()), helper, SLOT(slotAlbumResetIcon())); } void ContextMenuHelper::addAssignTagsMenu(const imageIds &ids) { setSelectedIds(ids); QMenu* const assignTagsPopup = new TagsPopupMenu(ids, TagsPopupMenu::RECENTLYASSIGNED, d->parent); assignTagsPopup->menuAction()->setText(i18n("A&ssign Tag")); assignTagsPopup->menuAction()->setIcon(QIcon::fromTheme(QLatin1String("tag"))); d->parent->addMenu(assignTagsPopup); connect(assignTagsPopup, SIGNAL(signalTagActivated(int)), this, SIGNAL(signalAssignTag(int))); connect(assignTagsPopup, SIGNAL(signalPopupTagsView()), this, SIGNAL(signalPopupTagsView())); } void ContextMenuHelper::addRemoveTagsMenu(const imageIds &ids) { setSelectedIds(ids); QMenu* const removeTagsPopup = new TagsPopupMenu(ids, TagsPopupMenu::REMOVE, d->parent); removeTagsPopup->menuAction()->setText(i18n("R&emove Tag")); removeTagsPopup->menuAction()->setIcon(QIcon::fromTheme(QLatin1String("tag"))); d->parent->addMenu(removeTagsPopup); // Performance: Only check for tags if there are <250 images selected // Otherwise enable it regardless if there are tags or not if (ids.count() < 250) { QList tagIDs = CoreDbAccess().db()->getItemCommonTagIDs(ids); bool enable = false; foreach (int tag, tagIDs) { if (TagsCache::instance()->colorLabelForTag(tag) == -1 && TagsCache::instance()->pickLabelForTag(tag) == -1 && TagsCache::instance()->isInternalTag(tag) == false) { enable = true; break; } } removeTagsPopup->menuAction()->setEnabled(enable); } connect(removeTagsPopup, SIGNAL(signalTagActivated(int)), this, SIGNAL(signalRemoveTag(int))); } void ContextMenuHelper::addLabelsAction() { QMenu* const menuLabels = new QMenu(i18n("Assign Labe&ls"), d->parent); PickLabelMenuAction* const pmenu = new PickLabelMenuAction(d->parent); ColorLabelMenuAction* const cmenu = new ColorLabelMenuAction(d->parent); RatingMenuAction* const rmenu = new RatingMenuAction(d->parent); menuLabels->addAction(pmenu->menuAction()); menuLabels->addAction(cmenu->menuAction()); menuLabels->addAction(rmenu->menuAction()); addSubMenu(menuLabels); connect(pmenu, SIGNAL(signalPickLabelChanged(int)), this, SIGNAL(signalAssignPickLabel(int))); connect(cmenu, SIGNAL(signalColorLabelChanged(int)), this, SIGNAL(signalAssignColorLabel(int))); connect(rmenu, SIGNAL(signalRatingChanged(int)), this, SIGNAL(signalAssignRating(int))); } void ContextMenuHelper::addCreateTagFromAddressbookMenu() { #ifdef HAVE_AKONADICONTACT AkonadiIface* const abc = new AkonadiIface(d->parent); connect(abc, SIGNAL(signalContactTriggered(QString)), this, SIGNAL(signalAddNewTagFromABCMenu(QString))); // AkonadiIface instance will be deleted with d->parent. #endif } void ContextMenuHelper::slotDeselectAllAlbumItems() { QAction* const selectNoneAction = d->stdActionCollection->action(QLatin1String("selectNone")); QTimer::singleShot(75, selectNoneAction, SIGNAL(triggered())); } void ContextMenuHelper::addImportMenu() { QMenu* const menuImport = new QMenu(i18n("Import"), d->parent); KXMLGUIClient* const client = const_cast(d->stdActionCollection->parentGUIClient()); QList actions = DPluginLoader::instance()->pluginsActions(DPluginAction::GenericImport, dynamic_cast(client)); if (!actions.isEmpty()) { foreach (DPluginAction* const ac, actions) { menuImport->addActions(QList() << ac); } } else { QAction* const notools = new QAction(i18n("No import tool available"), this); notools->setEnabled(false); menuImport->addAction(notools); } d->parent->addMenu(menuImport); } void ContextMenuHelper::addExportMenu() { QMenu* const menuExport = new QMenu(i18n("Export"), d->parent); KXMLGUIClient* const client = const_cast(d->stdActionCollection->parentGUIClient()); QList actions = DPluginLoader::instance()->pluginsActions(DPluginAction::GenericExport, dynamic_cast(client)); #if 0 QAction* selectAllAction = 0; selectAllAction = d->stdActionCollection->action("selectAll"); #endif if (!actions.isEmpty()) { foreach (DPluginAction* const ac, actions) { menuExport->addActions(QList() << ac); } } else { QAction* const notools = new QAction(i18n("No export tool available"), this); notools->setEnabled(false); menuExport->addAction(notools); } d->parent->addMenu(menuExport); } void ContextMenuHelper::addAlbumActions() { QList albumActions; if (!albumActions.isEmpty()) { d->parent->addActions(albumActions); } } void ContextMenuHelper::addGotoMenu(const imageIds &ids) { if (d->gotoAlbumAction && d->gotoDateAction) { return; } setSelectedIds(ids); // the currently selected image is always the first item ItemInfo item; if (!d->selectedIds.isEmpty()) { item = ItemInfo(d->selectedIds.first()); } if (item.isNull()) { return; } // when more then one item is selected, don't add the menu if (d->selectedIds.count() > 1) { return; } d->gotoAlbumAction = new QAction(QIcon::fromTheme(QLatin1String("folder-pictures")), i18n("Album"), this); d->gotoDateAction = new QAction(QIcon::fromTheme(QLatin1String("view-calendar")), i18n("Date"), this); QMenu* const gotoMenu = new QMenu(d->parent); gotoMenu->addAction(d->gotoAlbumAction); gotoMenu->addAction(d->gotoDateAction); TagsPopupMenu* const gotoTagsPopup = new TagsPopupMenu(d->selectedIds, TagsPopupMenu::DISPLAY, gotoMenu); QAction* const gotoTag = gotoMenu->addMenu(gotoTagsPopup); gotoTag->setIcon(QIcon::fromTheme(QLatin1String("tag"))); gotoTag->setText(i18n("Tag")); // Disable the goto Tag popup menu, if there are no tags at all. if (!CoreDbAccess().db()->hasTags(d->selectedIds)) { gotoTag->setEnabled(false); } /** * TODO:tags to be ported to multiple selection */ QList albumList = AlbumManager::instance()->currentAlbums(); Album* currentAlbum = 0; - if(!albumList.isEmpty()) + if (!albumList.isEmpty()) { currentAlbum = albumList.first(); } else { return; } if (currentAlbum->type() == Album::PHYSICAL) { // If the currently selected album is the same as album to // which the image belongs, then disable the "Go To" Album. // (Note that in recursive album view these can be different). if (item.albumId() == currentAlbum->id()) { d->gotoAlbumAction->setEnabled(false); } } else if (currentAlbum->type() == Album::DATE) { d->gotoDateAction->setEnabled(false); } QAction* const gotoMenuAction = gotoMenu->menuAction(); gotoMenuAction->setIcon(QIcon::fromTheme(QLatin1String("go-jump"))); gotoMenuAction->setText(i18n("Go To")); connect(gotoTagsPopup, SIGNAL(signalTagActivated(int)), this, SIGNAL(signalGotoTag(int))); addAction(gotoMenuAction); } void ContextMenuHelper::addQueueManagerMenu() { QMenu* const bqmMenu = new QMenu(i18n("Batch Queue Manager"), d->parent); bqmMenu->menuAction()->setIcon(QIcon::fromTheme(QLatin1String("run-build"))); bqmMenu->addAction(d->stdActionCollection->action(QLatin1String("image_add_to_current_queue"))); bqmMenu->addAction(d->stdActionCollection->action(QLatin1String("image_add_to_new_queue"))); // if queue list is empty, do not display the queue submenu if (QueueMgrWindow::queueManagerWindowCreated() && !QueueMgrWindow::queueManagerWindow()->queuesMap().isEmpty()) { QueueMgrWindow* const qmw = QueueMgrWindow::queueManagerWindow(); QMenu* const queueMenu = new QMenu(i18n("Add to Existing Queue"), bqmMenu); // queueActions is used by the exec() method to emit an appropriate signal. // Reset the map before filling in the actions. if (!d->queueActions.isEmpty()) { d->queueActions.clear(); } QList queueList; // get queue list from BQM window, do not access it directly, it might crash // when the list is changed QMap qmwMap = qmw->queuesMap(); for (QMap::const_iterator it = qmwMap.constBegin(); it != qmwMap.constEnd(); ++it) { QAction* const action = new QAction(it.value(), this); queueList << action; d->queueActions[it.key()] = action; } queueMenu->addActions(queueList); bqmMenu->addMenu(queueMenu); } d->parent->addMenu(bqmMenu); // NOTE: see bug #252130 : we need to disable new items to add on BQM is this one is running. bqmMenu->setDisabled(QueueMgrWindow::queueManagerWindow()->isBusy()); } void ContextMenuHelper::setAlbumModel(AbstractCheckableAlbumModel* model) { d->albumModel = model; } void ContextMenuHelper::addAlbumCheckUncheckActions(Album* album) { bool enabled = false; QString allString = i18n("All Albums"); QVariant albumData; if (album) { enabled = true; albumData = QVariant::fromValue(AlbumPointer<>(album)); if (album->type() == Album::TAG) allString = i18n("All Tags"); } QMenu* const selectTagsMenu = new QMenu(i18nc("select tags menu", "Select")); addSubMenu(selectTagsMenu); selectTagsMenu->addAction(allString, d->albumModel, SLOT(checkAllAlbums())); selectTagsMenu->addSeparator(); QAction* const selectChildrenAction = selectTagsMenu->addAction(i18n("Children"), this, SLOT(slotSelectChildren())); QAction* const selectParentsAction = selectTagsMenu->addAction(i18n("Parents"), this, SLOT(slotSelectParents())); selectChildrenAction->setData(albumData); selectParentsAction->setData(albumData); QMenu* const deselectTagsMenu = new QMenu(i18nc("deselect tags menu", "Deselect")); addSubMenu(deselectTagsMenu); deselectTagsMenu->addAction(allString, d->albumModel, SLOT(resetAllCheckedAlbums())); deselectTagsMenu->addSeparator(); QAction* const deselectChildrenAction = deselectTagsMenu->addAction(i18n("Children"), this, SLOT(slotDeselectChildren())); QAction* const deselectParentsAction = deselectTagsMenu->addAction(i18n("Parents"), this, SLOT(slotDeselectParents())); deselectChildrenAction->setData(albumData); deselectParentsAction->setData(albumData); d->parent->addAction(i18n("Invert Selection"), d->albumModel, SLOT(invertCheckedAlbums())); selectChildrenAction->setEnabled(enabled); selectParentsAction->setEnabled(enabled); deselectChildrenAction->setEnabled(enabled); deselectParentsAction->setEnabled(enabled); } void ContextMenuHelper::slotSelectChildren() { if (!d->albumModel) { return; } d->albumModel->checkAllAlbums(d->indexForAlbumFromAction(sender())); } void ContextMenuHelper::slotDeselectChildren() { if (!d->albumModel) { return; } d->albumModel->resetCheckedAlbums(d->indexForAlbumFromAction(sender())); } void ContextMenuHelper::slotSelectParents() { if (!d->albumModel) { return; } d->albumModel->checkAllParentAlbums(d->indexForAlbumFromAction(sender())); } void ContextMenuHelper::slotDeselectParents() { if (!d->albumModel) { return; } d->albumModel->resetCheckedParentAlbums(d->indexForAlbumFromAction(sender())); } void ContextMenuHelper::addGroupMenu(const imageIds &ids, const QList& extraMenuItems) { QList actions = groupMenuActions(ids); if (actions.isEmpty() && extraMenuItems.isEmpty()) { return; } if (!extraMenuItems.isEmpty()) { if (!actions.isEmpty()) { QAction* separator = new QAction(this); separator->setSeparator(true); actions << separator; } actions << extraMenuItems; } QMenu* const menu = new QMenu(i18n("Group")); foreach (QAction* const action, actions) { menu->addAction(action); } d->parent->addMenu(menu); } void ContextMenuHelper::addGroupActions(const imageIds &ids) { foreach (QAction* const action, groupMenuActions(ids)) { d->parent->addAction(action); } } void ContextMenuHelper::setItemFilterModel(ItemFilterModel* model) { d->imageFilterModel = model; } QList ContextMenuHelper::groupMenuActions(const imageIds &ids) { setSelectedIds(ids); QList actions; if (ids.isEmpty()) { if (d->imageFilterModel) { if (!d->imageFilterModel->isAllGroupsOpen()) { QAction* const openAction = new QAction(i18nc("@action:inmenu", "Open All Groups"), this); connect(openAction, SIGNAL(triggered()), this, SLOT(slotOpenGroups())); actions << openAction; } else { QAction* const closeAction = new QAction(i18nc("@action:inmenu", "Close All Groups"), this); connect(closeAction, SIGNAL(triggered()), this, SLOT(slotCloseGroups())); actions << closeAction; } } return actions; } ItemInfo info(ids.first()); if (ids.size() == 1) { if (info.hasGroupedImages()) { if (d->imageFilterModel) { if (!d->imageFilterModel->isGroupOpen(info.id())) { QAction* const action = new QAction(i18nc("@action:inmenu", "Show Grouped Images"), this); connect(action, SIGNAL(triggered()), this, SLOT(slotOpenGroups())); actions << action; } else { QAction* const action = new QAction(i18nc("@action:inmenu", "Hide Grouped Images"), this); connect(action, SIGNAL(triggered()), this, SLOT(slotCloseGroups())); actions << action; } } QAction* const separator = new QAction(this); separator->setSeparator(true); actions << separator; QAction* const clearAction = new QAction(i18nc("@action:inmenu", "Ungroup"), this); connect(clearAction, SIGNAL(triggered()), this, SIGNAL(signalUngroup())); actions << clearAction; } else if (info.isGrouped()) { QAction* const action = new QAction(i18nc("@action:inmenu", "Remove From Group"), this); connect(action, SIGNAL(triggered()), this, SIGNAL(signalRemoveFromGroup())); actions << action; // TODO: set as group leader / pick image } } else { QAction* const closeAction = new QAction(i18nc("@action:inmenu", "Group Selected Here"), this); connect(closeAction, SIGNAL(triggered()), this, SIGNAL(signalCreateGroup())); actions << closeAction; QAction* const closeActionDate = new QAction(i18nc("@action:inmenu", "Group Selected By Time"), this); connect(closeActionDate, SIGNAL(triggered()), this, SIGNAL(signalCreateGroupByTime())); actions << closeActionDate; QAction* const closeActionType = new QAction(i18nc("@action:inmenu", "Group Selected By Filename"), this); connect(closeActionType, SIGNAL(triggered()), this, SIGNAL(signalCreateGroupByFilename())); actions << closeActionType; QAction* const closeActionTimelapse = new QAction(i18nc("@action:inmenu", "Group Selected By Timelapse / Burst"), this); connect(closeActionTimelapse, SIGNAL(triggered()), this, SIGNAL(signalCreateGroupByTimelapse())); actions << closeActionTimelapse; QAction* const separator = new QAction(this); separator->setSeparator(true); actions << separator; if (d->imageFilterModel) { QAction* const openAction = new QAction(i18nc("@action:inmenu", "Show Grouped Images"), this); connect(openAction, SIGNAL(triggered()), this, SLOT(slotOpenGroups())); actions << openAction; QAction* const closeAction = new QAction(i18nc("@action:inmenu", "Hide Grouped Images"), this); connect(closeAction, SIGNAL(triggered()), this, SLOT(slotCloseGroups())); actions << closeAction; QAction* const separator2 = new QAction(this); separator2->setSeparator(true); actions << separator2; } QAction* const removeAction = new QAction(i18nc("@action:inmenu", "Remove Selected From Groups"), this); connect(removeAction, SIGNAL(triggered()), this, SIGNAL(signalRemoveFromGroup())); actions << removeAction; QAction* const clearAction = new QAction(i18nc("@action:inmenu", "Ungroup Selected"), this); connect(clearAction, SIGNAL(triggered()), this, SIGNAL(signalUngroup())); actions << clearAction; } return actions; } void ContextMenuHelper::setGroupsOpen(bool open) { if (!d->imageFilterModel || d->selectedIds.isEmpty()) { return; } GroupItemFilterSettings settings = d->imageFilterModel->groupItemFilterSettings(); foreach (const qlonglong& id, d->selectedIds) { ItemInfo info(id); if (info.hasGroupedImages()) { settings.setOpen(id, open); } } d->imageFilterModel->setGroupItemFilterSettings(settings); } void ContextMenuHelper::slotOpenGroups() { setGroupsOpen(true); } void ContextMenuHelper::slotCloseGroups() { setGroupsOpen(false); } void ContextMenuHelper::slotOpenAllGroups() { if (!d->imageFilterModel) { return; } d->imageFilterModel->setAllGroupsOpen(true); } void ContextMenuHelper::slotCloseAllGroups() { if (!d->imageFilterModel) { return; } d->imageFilterModel->setAllGroupsOpen(false); } void ContextMenuHelper::addStandardActionCut(QObject* recv, const char* slot) { QAction* const cut = DXmlGuiWindow::buildStdAction(StdCutAction, recv, slot, d->parent); addAction(cut); } void ContextMenuHelper::addStandardActionCopy(QObject* recv, const char* slot) { QAction* const copy = DXmlGuiWindow::buildStdAction(StdCopyAction, recv, slot, d->parent); addAction(copy); } void ContextMenuHelper::addStandardActionPaste(QObject* recv, const char* slot) { QAction* const paste = DXmlGuiWindow::buildStdAction(StdPasteAction, recv, slot, d->parent); const QMimeData* const data = qApp->clipboard()->mimeData(QClipboard::Clipboard); if (!data || !data->hasUrls()) { paste->setEnabled(false); } addAction(paste, true); } void ContextMenuHelper::addStandardActionItemDelete(QObject* recv, const char* slot, int quantity) { QAction* const trashAction = new QAction(QIcon::fromTheme(QLatin1String("user-trash-full")), i18ncp("@action:inmenu Pluralized", "Move to Trash", "Move %1 Files to Trash", quantity), d->parent); connect(trashAction, SIGNAL(triggered()), recv, slot); addAction(trashAction); } QAction* ContextMenuHelper::exec(const QPoint& pos, QAction* at) { QAction* const choice = d->parent->exec(pos, at); if (choice) { if (d->selectedIds.count() == 1) { ItemInfo selectedItem(d->selectedIds.first()); if (choice == d->gotoAlbumAction) { emit signalGotoAlbum(selectedItem); } else if (choice == d->gotoDateAction) { emit signalGotoDate(selectedItem); } else if (choice == d->setThumbnailAction) { emit signalSetThumbnail(selectedItem); } } // check if a BQM action has been triggered for (QMap::const_iterator it = d->queueActions.constBegin(); it != d->queueActions.constEnd(); ++it) { if (choice == it.value()) { emit signalAddToExistingQueue(it.key()); return choice; } } } return choice; } void ContextMenuHelper::setSelectedIds(const imageIds &ids) { if (d->selectedIds.isEmpty()) { d->selectedIds = ids; } } void ContextMenuHelper::setSelectedItems(const QList& urls) { if (d->selectedItems.isEmpty()) { d->selectedItems = urls; } } } // namespace Digikam diff --git a/core/app/items/utils/itemviewutilities.cpp b/core/app/items/utils/itemviewutilities.cpp index 6aa0c0f394..d8ec2f14bd 100644 --- a/core/app/items/utils/itemviewutilities.cpp +++ b/core/app/items/utils/itemviewutilities.cpp @@ -1,593 +1,593 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2009-05-04 * Description : Various operation on items * * Copyright (C) 2002-2005 by Renchi Raju * Copyright (C) 2002-2019 by Gilles Caulier * Copyright (C) 2006-2010 by Marcel Wiesweg * Copyright (C) 2009-2010 by Andi Clemens * * 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 "itemviewutilities.h" // Qt includes #include #include #include // KDE includes #include // Local includes #include "digikam_debug.h" #include "album.h" #include "albummanager.h" #include "albumselectdialog.h" #include "applicationsettings.h" #include "deletedialog.h" #include "dio.h" #include "iteminfo.h" #include "imagewindow.h" #include "lighttablewindow.h" #include "loadingcacheinterface.h" #include "queuemgrwindow.h" #include "thumbnailloadthread.h" #include "fileactionmngr.h" #include "dfileoperations.h" #include "coredb.h" #include "coredbaccess.h" namespace Digikam { ItemViewUtilities::ItemViewUtilities(QWidget* const parentWidget) : QObject(parentWidget) { m_widget = parentWidget; connect(this, SIGNAL(signalImagesDeleted(QList)), AlbumManager::instance(), SLOT(slotImagesDeleted(QList))); } void ItemViewUtilities::setAsAlbumThumbnail(Album* album, const ItemInfo& itemInfo) { if (!album) { return; } if (album->type() == Album::PHYSICAL) { PAlbum* const palbum = static_cast(album); QString err; AlbumManager::instance()->updatePAlbumIcon(palbum, itemInfo.id(), err); } else if (album->type() == Album::TAG) { TAlbum* const talbum = static_cast(album); QString err; AlbumManager::instance()->updateTAlbumIcon(talbum, QString(), itemInfo.id(), err); } } void ItemViewUtilities::rename(const QUrl& imageUrl, const QString& newName, bool overwrite) { if (imageUrl.isEmpty() || !imageUrl.isLocalFile() || newName.isEmpty()) { return; } DIO::rename(imageUrl, newName, overwrite); } bool ItemViewUtilities::deleteImages(const QList& infos, const DeleteMode deleteMode) { if (infos.isEmpty()) { return false; } QList deleteInfos = infos; QList urlList; QList imageIds; // Buffer the urls for deletion and imageids for notification of the AlbumManager foreach (const ItemInfo& info, deleteInfos) { urlList << info.fileUrl(); imageIds << info.id(); } DeleteDialog dialog(m_widget); DeleteDialogMode::DeleteMode deleteDialogMode = DeleteDialogMode::NoChoiceTrash; if (deleteMode == ItemViewUtilities::DeletePermanently) { deleteDialogMode = DeleteDialogMode::NoChoiceDeletePermanently; } if (!dialog.confirmDeleteList(urlList, DeleteDialogMode::Files, deleteDialogMode)) { return false; } const bool useTrash = !dialog.shouldDelete(); DIO::del(deleteInfos, useTrash); // Signal the Albummanager about the ids of the deleted images. emit signalImagesDeleted(imageIds); return true; } void ItemViewUtilities::deleteImagesDirectly(const QList& infos, const DeleteMode deleteMode) { // This method deletes the selected items directly, without confirmation. // It is not used in the default setup. if (infos.isEmpty()) { return; } QList imageIds; foreach (const ItemInfo& info, infos) { imageIds << info.id(); } const bool useTrash = (deleteMode == ItemViewUtilities::DeleteUseTrash); DIO::del(infos, useTrash); // Signal the Albummanager about the ids of the deleted images. emit signalImagesDeleted(imageIds); } void ItemViewUtilities::notifyFileContentChanged(const QList& urls) { foreach (const QUrl& url, urls) { QString path = url.toLocalFile(); ThumbnailLoadThread::deleteThumbnail(path); // clean LoadingCache as well - be pragmatic, do it here. LoadingCacheInterface::fileChanged(path); } } void ItemViewUtilities::createNewAlbumForInfos(const QList& infos, Album* currentAlbum) { if (infos.isEmpty()) { return; } if (currentAlbum && currentAlbum->type() != Album::PHYSICAL) { currentAlbum = 0; } QString header(i18n("

Please select the destination album from the digiKam library to " "move the selected images into.

")); Album* const album = AlbumSelectDialog::selectAlbum(m_widget, static_cast(currentAlbum), header); if (!album) { return; } DIO::move(infos, (PAlbum*)album); } void ItemViewUtilities::insertToLightTableAuto(const QList& all, const QList& selected, const ItemInfo& current) { ItemInfoList list = ItemInfoList(selected); ItemInfo singleInfo = current; if (list.isEmpty() || (list.size() == 1 && LightTableWindow::lightTableWindow()->isEmpty())) { list = ItemInfoList(all); } if (singleInfo.isNull() && !list.isEmpty()) { singleInfo = list.first(); } insertToLightTable(list, current, list.size() <= 1); } void ItemViewUtilities::insertToLightTable(const QList& list, const ItemInfo& current, bool addTo) { LightTableWindow* const ltview = LightTableWindow::lightTableWindow(); // If addTo is false, the light table will be emptied before adding // the images. ltview->loadItemInfos(ItemInfoList(list), current, addTo); ltview->setLeftRightItems(ItemInfoList(list), addTo); if (ltview->isHidden()) { ltview->show(); } if (ltview->isMinimized()) { KWindowSystem::unminimizeWindow(ltview->winId()); } KWindowSystem::activateWindow(ltview->winId()); } void ItemViewUtilities::insertToQueueManager(const QList& list, const ItemInfo& current, bool newQueue) { Q_UNUSED(current); QueueMgrWindow* const bqmview = QueueMgrWindow::queueManagerWindow(); if (bqmview->isHidden()) { bqmview->show(); } if (bqmview->isMinimized()) { KWindowSystem::unminimizeWindow(bqmview->winId()); } KWindowSystem::activateWindow(bqmview->winId()); if (newQueue) { bqmview->loadItemInfosToNewQueue(ItemInfoList(list)); } else { bqmview->loadItemInfosToCurrentQueue(ItemInfoList(list)); } } void ItemViewUtilities::insertSilentToQueueManager(const QList& list, const ItemInfo& /*current*/, int queueid) { QueueMgrWindow* const bqmview = QueueMgrWindow::queueManagerWindow(); bqmview->loadItemInfos(ItemInfoList(list), queueid); } void ItemViewUtilities::openInfos(const ItemInfo& info, const QList& allInfosToOpen, Album* currentAlbum) { if (info.isNull()) { return; } QFileInfo fi(info.filePath()); QString imagefilter = ApplicationSettings::instance()->getImageFileFilter(); imagefilter += ApplicationSettings::instance()->getRawFileFilter(); // If the current item is not an image file. - if ( !imagefilter.contains(fi.suffix().toLower()) ) + if (!imagefilter.contains(fi.suffix().toLower())) { // Openonly the first one from the list. openInfosWithDefaultApplication(QList() << info); return; } // Run digiKam ImageEditor with all image from current Album. ImageWindow* const imview = ImageWindow::imageWindow(); imview->disconnect(this); connect(imview, SIGNAL(signalURLChanged(QUrl)), this, SIGNAL(editorCurrentUrlChanged(QUrl))); imview->loadItemInfos(ItemInfoList(allInfosToOpen), info, currentAlbum ? i18n("Album \"%1\"", currentAlbum->title()) : QString()); if (imview->isHidden()) { imview->show(); } if (imview->isMinimized()) { KWindowSystem::unminimizeWindow(imview->winId()); } KWindowSystem::activateWindow(imview->winId()); } void ItemViewUtilities::openInfosWithDefaultApplication(const QList& infos) { if (infos.isEmpty()) { return; } QList urls; foreach (const ItemInfo& inf, infos) { urls << inf.fileUrl(); } DFileOperations::openFilesWithDefaultApplication(urls); } namespace { bool lessThanByTimeForItemInfo(const ItemInfo& a, const ItemInfo& b) { return a.dateTime() < b.dateTime(); } bool lowerThanByNameForItemInfo(const ItemInfo& a, const ItemInfo& b) { return a.name() < b.name(); } bool lowerThanBySizeForItemInfo(const ItemInfo& a, const ItemInfo& b) { return a.fileSize() < b.fileSize(); } } // namespace void ItemViewUtilities::createGroupByTimeFromInfoList(const ItemInfoList& itemInfoList) { QList groupingList = itemInfoList; // sort by time std::stable_sort(groupingList.begin(), groupingList.end(), lessThanByTimeForItemInfo); QList::iterator it, it2; for (it = groupingList.begin() ; it != groupingList.end() ; ) { const ItemInfo& leader = *it; QList group; QDateTime time = it->dateTime(); if (time.isValid()) { for (it2 = it + 1 ; it2 != groupingList.end() ; ++it2) { if (qAbs(time.secsTo(it2->dateTime())) < 2) { group << *it2; } else { break; } } } else { ++it; continue; } // increment to next item not put in the group it = it2; if (!group.isEmpty()) { FileActionMngr::instance()->addToGroup(leader, group); } } } void ItemViewUtilities::createGroupByFilenameFromInfoList(const ItemInfoList& itemInfoList) { QList groupingList = itemInfoList; // sort by Name std::stable_sort(groupingList.begin(), groupingList.end(), lowerThanByNameForItemInfo); QList::iterator it, it2; for (it = groupingList.begin() ; it != groupingList.end() ; ) { QList group; QString fname = it->name().left(it->name().lastIndexOf(QLatin1Char('.'))); // don't know the leader yet so put first element also in group group << *it; for (it2 = it + 1 ; it2 != groupingList.end() ; ++it2) { QString fname2 = it2->name().left(it2->name().lastIndexOf(QLatin1Char('.'))); if (fname == fname2) { group << *it2; } else { break; } } // increment to next item not put in the group it = it2; if (group.count() > 1) { // sort by filesize and take smallest as leader std::stable_sort(group.begin(), group.end(), lowerThanBySizeForItemInfo); const ItemInfo& leader = group.takeFirst(); FileActionMngr::instance()->addToGroup(leader, group); } } } namespace { struct Q_DECL_HIDDEN NumberInFilenameMatch { NumberInFilenameMatch() : value(0), containsValue(false) { } explicit NumberInFilenameMatch(const QString& filename) : NumberInFilenameMatch() { if (filename.isEmpty()) { return; } auto firstDigit = std::find_if(filename.begin(), filename.end(), [](const QChar& c) { return c.isDigit(); }); prefix = filename.leftRef(std::distance(filename.begin(), firstDigit)); if (firstDigit == filename.end()) { return; } auto lastDigit = std::find_if(firstDigit, filename.end(), [](const QChar& c) { return !c.isDigit(); }); value = filename.midRef(prefix.size(), std::distance(firstDigit, lastDigit)).toULongLong(&containsValue); suffix = filename.midRef(std::distance(lastDigit, filename.end())); } bool directlyPreceeds(NumberInFilenameMatch const& other) const { if (!containsValue || !other.containsValue) { return false; } if (prefix != other.prefix) { return false; } if (suffix != other.suffix) { return false; } return (value+1 == other.value); } qulonglong value; QStringRef prefix; QStringRef suffix; bool containsValue; }; bool imageMatchesTimelapseGroup(const ItemInfoList& group, const ItemInfo& itemInfo) { if (group.size() < 2) { return true; } auto const timeBetweenPhotos = qAbs(group.first().dateTime() .secsTo(group.last() .dateTime())) / (group.size()-1); auto const predictedNextTimestamp = group.last().dateTime() .addSecs(timeBetweenPhotos); return (qAbs(itemInfo.dateTime().secsTo(predictedNextTimestamp)) <= 1); } } // namespace void ItemViewUtilities::createGroupByTimelapseFromInfoList(const ItemInfoList& itemInfoList) { if (itemInfoList.size() < 3) { return; } ItemInfoList groupingList = itemInfoList; std::stable_sort(groupingList.begin(), groupingList.end(), lowerThanByNameForItemInfo); NumberInFilenameMatch previousNumberMatch; ItemInfoList group; for (const auto& itemInfo : groupingList) { NumberInFilenameMatch numberMatch(itemInfo.name()); // if this is an end of currently processed group if (!previousNumberMatch.directlyPreceeds(numberMatch) || !imageMatchesTimelapseGroup(group, itemInfo)) { if (group.size() > 2) { FileActionMngr::instance()->addToGroup(group.takeFirst(), group); } group.clear(); } group.append(itemInfo); previousNumberMatch = std::move(numberMatch); } if (group.size() > 2) { FileActionMngr::instance()->addToGroup(group.takeFirst(), group); } } } // namespace Digikam diff --git a/core/app/main/digikamapp_config.cpp b/core/app/main/digikamapp_config.cpp index 8b67b45c92..68ab379b6d 100644 --- a/core/app/main/digikamapp_config.cpp +++ b/core/app/main/digikamapp_config.cpp @@ -1,97 +1,97 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2002-16-10 * Description : main digiKam interface implementation - Configure * * Copyright (C) 2002-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 "digikamapp.h" #include "digikamapp_p.h" namespace Digikam { bool DigikamApp::setup() { return Setup::execDialog(this, Setup::LastPageUsed); } bool DigikamApp::setupICC() { return Setup::execSinglePage(this, Setup::ICCPage); } void DigikamApp::slotSetup() { setup(); } void DigikamApp::slotSetupChanged() { // raw loading options might have changed LoadingCacheInterface::cleanCache(); // TODO: clear history when location changed - //if(ApplicationSettings::instance()->getAlbumLibraryPath() != AlbumManager::instance()->getLibraryPath()) - // d->view->clearHistory(); + //if (ApplicationSettings::instance()->getAlbumLibraryPath() != AlbumManager::instance()->getLibraryPath()) + // d->view->clearHistory(); const DbEngineParameters prm = ApplicationSettings::instance()->getDbEngineParameters(); if (!AlbumManager::instance()->databaseEqual(prm)) { AlbumManager::instance()->changeDatabase(ApplicationSettings::instance()->getDbEngineParameters()); } if (ApplicationSettings::instance()->getShowFolderTreeViewItemsCount()) { AlbumManager::instance()->prepareItemCounts(); } // Load full-screen options KConfigGroup group = KSharedConfig::openConfig()->group(configGroupName()); readFullScreenSettings(group); d->view->applySettings(); AlbumThumbnailLoader::instance()->setThumbnailSize(ApplicationSettings::instance()->getTreeViewIconSize()); if (LightTableWindow::lightTableWindowCreated()) { LightTableWindow::lightTableWindow()->applySettings(); } if (QueueMgrWindow::queueManagerWindowCreated()) { QueueMgrWindow::queueManagerWindow()->applySettings(); } d->config->sync(); } void DigikamApp::slotEditKeys() { editKeyboardShortcuts(); } void DigikamApp::slotThemeChanged() { ApplicationSettings::instance()->setCurrentTheme(ThemeManager::instance()->currentThemeName()); } } // namespace Digikam diff --git a/core/app/main/digikamapp_solid.cpp b/core/app/main/digikamapp_solid.cpp index b63a52a4ce..4d5dafa0aa 100644 --- a/core/app/main/digikamapp_solid.cpp +++ b/core/app/main/digikamapp_solid.cpp @@ -1,672 +1,672 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2002-16-10 * Description : main digiKam interface implementation - Solid methods * * Copyright (C) 2002-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 "digikamapp.h" #include "digikamapp_p.h" // Solid includes #include #include #include #include #include #include #include namespace Digikam { void DigikamApp::fillSolidMenus() { QHash newAppearanceTimes; d->usbMediaMenu->clear(); d->cardReaderMenu->clear(); // delete the actionGroups to avoid duplicate menu entries delete d->solidUsmActionGroup; delete d->solidCameraActionGroup; d->solidCameraActionGroup = new QActionGroup(this); connect(d->solidCameraActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(slotOpenSolidCamera(QAction*))); d->solidUsmActionGroup = new QActionGroup(this); connect(d->solidUsmActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(slotOpenSolidUsmDevice(QAction*))); // -------------------------------------------------------- QList cameraDevices = Solid::Device::listFromType(Solid::DeviceInterface::Camera); foreach(const Solid::Device& cameraDevice, cameraDevices) { // USM camera: will be handled below if (cameraDevice.is()) { continue; } if (!checkSolidCamera(cameraDevice)) { continue; } // -------------------------------------------------------- QString l = labelForSolidCamera(cameraDevice); QString label = CameraNameHelper::cameraNameAutoDetected(l.trimmed()); // -------------------------------------------------------- QString iconName = cameraDevice.icon(); if (iconName.isEmpty()) { iconName = QLatin1String("camera-photo"); } QAction* const action = new QAction(label, d->solidCameraActionGroup); action->setIcon(QIcon::fromTheme(iconName)); // set data to identify device in action slot slotSolidSetupDevice action->setData(cameraDevice.udi()); newAppearanceTimes[cameraDevice.udi()] = d->cameraAppearanceTimes.contains(cameraDevice.udi()) ? d->cameraAppearanceTimes.value(cameraDevice.udi()) : QDateTime::currentDateTime(); d->cameraMenu->addAction(action); } QList storageDevices = Solid::Device::listFromType(Solid::DeviceInterface::StorageAccess); foreach(const Solid::Device& accessDevice, storageDevices) { // check for StorageAccess if (!accessDevice.is()) { continue; } // check for StorageDrive Solid::Device driveDevice; for (Solid::Device currentDevice = accessDevice ; currentDevice.isValid() ; currentDevice = currentDevice.parent()) { if (currentDevice.is()) { driveDevice = currentDevice; break; } } if (!driveDevice.isValid()) { continue; } const Solid::StorageDrive* const drive = driveDevice.as(); QString driveType; bool isHarddisk = false; switch (drive->driveType()) { // skip these case Solid::StorageDrive::CdromDrive: case Solid::StorageDrive::Floppy: case Solid::StorageDrive::Tape: default: continue; // accept card readers case Solid::StorageDrive::CompactFlash: driveType = i18n("CompactFlash Card Reader"); break; case Solid::StorageDrive::MemoryStick: driveType = i18n("Memory Stick Reader"); break; case Solid::StorageDrive::SmartMedia: driveType = i18n("SmartMedia Card Reader"); break; case Solid::StorageDrive::SdMmc: driveType = i18n("SD / MMC Card Reader"); break; case Solid::StorageDrive::Xd: driveType = i18n("xD Card Reader"); break; case Solid::StorageDrive::HardDisk: // We don't want to list HardDisk partitions, but USB Mass Storage devices. // Don't know what is the exact difference between removable and hotpluggable. if (drive->isRemovable() || drive->isHotpluggable()) { isHarddisk = true; if (drive->bus() == Solid::StorageDrive::Usb) { driveType = i18n("USB Disk"); } else { driveType = i18nc("non-USB removable storage device", "Disk"); } break; } else { continue; } } // check for StorageVolume Solid::Device volumeDevice; for (Solid::Device currentDevice = accessDevice ; currentDevice.isValid() ; currentDevice = currentDevice.parent()) { if (currentDevice.is()) { volumeDevice = currentDevice; break; } } if (!volumeDevice.isValid()) { continue; } bool isCamera = accessDevice.is(); const Solid::StorageAccess* const access = accessDevice.as(); const Solid::StorageVolume* const volume = volumeDevice.as(); if (volume->isIgnored()) { continue; } QString label; if (isCamera) { label = accessDevice.vendor() + QLatin1Char(' ') + accessDevice.product(); } else { QString labelOrProduct; if (!volume->label().isEmpty()) { labelOrProduct = volume->label(); } else if (!volumeDevice.product().isEmpty()) { labelOrProduct = volumeDevice.product(); } else if (!volumeDevice.vendor().isEmpty()) { labelOrProduct = volumeDevice.vendor(); } else if (!driveDevice.product().isEmpty()) { labelOrProduct = driveDevice.product(); } if (!labelOrProduct.isNull()) { if (!access->filePath().isEmpty()) { label += i18nc(" \"\" at ", "%1 \"%2\" at %3", driveType, labelOrProduct, QDir::toNativeSeparators(access->filePath())); } else { label += i18nc(" \"\"", "%1 \"%2\"", driveType, labelOrProduct); } } else { if (!access->filePath().isEmpty()) { label += i18nc(" at ", "%1 at %2", driveType, QDir::toNativeSeparators(access->filePath())); } else { label += driveType; } } if (volume->size()) { label += i18nc("device label etc... ()", " (%1)", ItemPropertiesTab::humanReadableBytesCount(volume->size())); } } QString iconName; if (!driveDevice.icon().isEmpty()) { iconName = driveDevice.icon(); } else if (!accessDevice.icon().isEmpty()) { iconName = accessDevice.icon(); } else if (!volumeDevice.icon().isEmpty()) { iconName = volumeDevice.icon(); } QAction* const action = new QAction(label, d->solidUsmActionGroup); if (!iconName.isEmpty()) { action->setIcon(QIcon::fromTheme(iconName)); } // set data to identify device in action slot slotSolidSetupDevice action->setData(accessDevice.udi()); newAppearanceTimes[accessDevice.udi()] = d->cameraAppearanceTimes.contains(accessDevice.udi()) ? d->cameraAppearanceTimes.value(accessDevice.udi()) : QDateTime::currentDateTime(); if (isCamera) { d->cameraMenu->addAction(action); } if (isHarddisk) { d->usbMediaMenu->addAction(action); } else { d->cardReaderMenu->addAction(action); } } /* //TODO: Find best usable solution when no devices are connected: One entry, hide, or disable? // Add one entry telling that no device is available if (d->cameraSolidMenu->isEmpty()) { QAction* const action = d->cameraSolidMenu->addAction(i18n("No Camera Connected")); action->setEnabled(false); } if (d->usbMediaMenu->isEmpty()) { QAction* const action = d->usbMediaMenu->addAction(i18n("No Storage Devices Found")); action->setEnabled(false); } if (d->cardReaderMenu->isEmpty()) { QAction* const action = d->cardReaderMenu->addAction(i18n("No Card Readers Available")); action->setEnabled(false); } // hide empty menus d->cameraSolidMenu->menuAction()->setVisible(!d->cameraSolidMenu->isEmpty()); d->usbMediaMenu->menuAction()->setVisible(!d->usbMediaMenu->isEmpty()); d->cardReaderMenu->menuAction()->setVisible(!d->cardReaderMenu->isEmpty()); */ d->cameraAppearanceTimes = newAppearanceTimes; // disable empty menus d->usbMediaMenu->setEnabled(!d->usbMediaMenu->isEmpty()); d->cardReaderMenu->setEnabled(!d->cardReaderMenu->isEmpty()); updateCameraMenu(); updateQuickImportAction(); } bool DigikamApp::checkSolidCamera(const Solid::Device& cameraDevice) { const Solid::Camera* const camera = cameraDevice.as(); if (!camera) { qCDebug(DIGIKAM_GENERAL_LOG) << "Solid device" << cameraDevice.description() << "is not a camera"; return false; } QStringList drivers = camera->supportedDrivers(); qCDebug(DIGIKAM_GENERAL_LOG) << "checkSolidCamera: Found Camera " << QString::fromUtf8("%1 %2").arg(cameraDevice.vendor()).arg(cameraDevice.product()) << " protocols " << camera->supportedProtocols() << " drivers " << camera->supportedDrivers(QLatin1String("ptp")); // We handle gphoto2 cameras in this loop if (!(camera->supportedDrivers().contains(QLatin1String("gphoto")) || camera->supportedProtocols().contains(QLatin1String("ptp"))) ) { return false; } QVariant driverHandle = camera->driverHandle(QLatin1String("gphoto")); if (!driverHandle.canConvert(QVariant::List)) { qCWarning(DIGIKAM_GENERAL_LOG) << "Solid returns unsupported driver handle for gphoto2"; return false; } QList driverHandleList = driverHandle.toList(); if ((driverHandleList.size() < 3) || (driverHandleList.at(0).toString() != QLatin1String("usb")) || !driverHandleList.at(1).canConvert(QVariant::Int) || !driverHandleList.at(2).canConvert(QVariant::Int) ) { qCWarning(DIGIKAM_GENERAL_LOG) << "Solid returns unsupported driver handle for gphoto2"; return false; } return true; } void DigikamApp::openSolidCamera(const QString& udi, const QString& cameraLabel) { // if there is already an open ImportUI for the device, show and raise it, and be done if (d->cameraUIMap.contains(udi)) { ImportUI* const ui = d->cameraUIMap.value(udi); if (ui && !ui->isClosed()) { if (ui->isMinimized()) { KWindowSystem::unminimizeWindow(ui->winId()); } KWindowSystem::activateWindow(ui->winId()); return; } } // recreate device from unambiguous UDI Solid::Device device(udi); - if ( device.isValid() ) + if (device.isValid()) { if (cameraLabel.isNull()) { QString label = labelForSolidCamera(device); } Solid::Camera* const camera = device.as(); QList list = camera->driverHandle(QLatin1String("gphoto")).toList(); // all sanity checks have already been done when creating the action if (list.size() < 3) { return; } // NOTE: See bug #262296: With KDE 4.6, Solid API return device vendor id // and product id in hexadecimal strings. bool ok; int vendorId = list.at(1).toString().toInt(&ok, 16); int productId = list.at(2).toString().toInt(&ok, 16); QString model, port; if (CameraList::findConnectedCamera(vendorId, productId, model, port)) { qCDebug(DIGIKAM_GENERAL_LOG) << "Found camera from ids " << vendorId << " " << productId << " camera is: " << model << " at " << port; // the ImportUI will delete itself when it has finished ImportUI* const cgui = new ImportUI(cameraLabel, model, port, QLatin1String("/"), 1); d->cameraUIMap[udi] = cgui; cgui->show(); connect(cgui, SIGNAL(signalLastDestination(QUrl)), d->view, SLOT(slotSelectAlbum(QUrl))); } else { qCDebug(DIGIKAM_GENERAL_LOG) << "Failed to detect camera with GPhoto2 from Solid information"; } } } QString DigikamApp::labelForSolidCamera(const Solid::Device& cameraDevice) { QString vendor = cameraDevice.vendor(); QString product = cameraDevice.product(); if (product == QLatin1String("USB Imaging Interface") || product == QLatin1String("USB Vendor Specific Interface")) { Solid::Device parentUsbDevice = cameraDevice.parent(); if (parentUsbDevice.isValid()) { vendor = parentUsbDevice.vendor(); product = parentUsbDevice.product(); if (!vendor.isEmpty() && !product.isEmpty()) { if (vendor == QLatin1String("Canon, Inc.")) { vendor = QLatin1String("Canon"); if (product.startsWith(QLatin1String("Canon "))) { product = product.mid(6); // cut off another "Canon " from product } if (product.endsWith(QLatin1String(" (ptp)"))) { product.chop(6); // cut off " (ptp)" } } else if (vendor == QLatin1String("Fuji Photo Film Co., Ltd")) { vendor = QLatin1String("Fuji"); } else if (vendor == QLatin1String("Nikon Corp.")) { vendor = QLatin1String("Nikon"); if (product.startsWith(QLatin1String("NIKON "))) { product = product.mid(6); } } } } } return vendor + QLatin1Char(' ') + product; } void DigikamApp::openSolidUsmDevice(const QString& udi, const QString& givenLabel) { QString mediaLabel = givenLabel; // if there is already an open ImportUI for the device, show and raise it if (d->cameraUIMap.contains(udi)) { ImportUI* const ui = d->cameraUIMap.value(udi); if (ui && !ui->isClosed()) { if (ui->isMinimized()) { KWindowSystem::unminimizeWindow(ui->winId()); } KWindowSystem::activateWindow(ui->winId()); return; } } // recreate device from unambiguous UDI Solid::Device device(udi); - if ( device.isValid() ) + if (device.isValid()) { Solid::StorageAccess* const access = device.as(); if (!access) { return; } if (!access->isAccessible()) { QApplication::setOverrideCursor(Qt::WaitCursor); if (!access->setup()) { return; } d->eventLoop = new QEventLoop(this); connect(access, SIGNAL(setupDone(Solid::ErrorType,QVariant,QString)), this, SLOT(slotSolidSetupDone(Solid::ErrorType,QVariant,QString))); int returnCode = d->eventLoop->exec(QEventLoop::ExcludeUserInputEvents); delete d->eventLoop; d->eventLoop = 0; QApplication::restoreOverrideCursor(); if (returnCode == 1) { QMessageBox::critical(this, qApp->applicationName(), d->solidErrorMessage); return; } } // Create Camera UI QString path = QDir::fromNativeSeparators(access->filePath()); if (mediaLabel.isNull()) { mediaLabel = path; } // the ImportUI will delete itself when it has finished ImportUI* const cgui = new ImportUI(i18n("Images on %1", mediaLabel), QLatin1String("directory browse"), QLatin1String("Fixed"), path, 1); d->cameraUIMap[udi] = cgui; cgui->show(); connect(cgui, SIGNAL(signalLastDestination(QUrl)), d->view, SLOT(slotSelectAlbum(QUrl))); } } void DigikamApp::slotOpenSolidCamera(QAction* action) { QString udi = action->data().toString(); openSolidCamera(udi, action->iconText()); } void DigikamApp::slotOpenSolidUsmDevice(QAction* action) { QString udi = action->data().toString(); openSolidUsmDevice(udi, action->iconText()); } void DigikamApp::slotOpenSolidDevice(const QString& udi) { // Identifies device as either Camera or StorageAccess and calls methods accordingly Solid::Device device(udi); if (!device.isValid()) { QMessageBox::critical(this, qApp->applicationName(), i18n("The specified device (\"%1\") is not valid.", udi)); return; } if (device.is()) { openSolidUsmDevice(udi); } else if (device.is()) { if (!checkSolidCamera(device)) { QMessageBox::critical(this, qApp->applicationName(), i18n("The specified camera (\"%1\") is not supported.", udi)); return; } openSolidCamera(udi); } } void DigikamApp::slotSolidSetupDone(Solid::ErrorType errorType, QVariant errorData, const QString& /*udi*/) { if (!d->eventLoop) { return; } if (errorType == Solid::NoError) { d->eventLoop->exit(0); } else { d->solidErrorMessage = i18n("Cannot access the storage device.\n"); d->solidErrorMessage += errorData.toString(); d->eventLoop->exit(1); } } void DigikamApp::slotSolidDeviceChanged(const QString& udi) { qCDebug(DIGIKAM_GENERAL_LOG) << "slotSolidDeviceChanged:" << udi; fillSolidMenus(); } } // namespace Digikam diff --git a/core/app/views/sidebar/leftsidebarwidgets.cpp b/core/app/views/sidebar/leftsidebarwidgets.cpp index 5266ed8ebc..1ae6b6a869 100644 --- a/core/app/views/sidebar/leftsidebarwidgets.cpp +++ b/core/app/views/sidebar/leftsidebarwidgets.cpp @@ -1,1527 +1,1527 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2000-12-05 * Description : left sidebar widgets * * Copyright (C) 2009-2010 by Johannes Wienke * Copyright (C) 2010-2019 by Gilles Caulier * Copyright (C) 2012 by Andi Clemens * Copyright (C) 2014 by Mohamed_Anwer * * 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 "leftsidebarwidgets.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include // Local includes #include "digikam_debug.h" #include "albummanager.h" #include "albummodificationhelper.h" #include "albumselectiontreeview.h" #include "applicationsettings.h" #include "datefolderview.h" #include "editablesearchtreeview.h" #include "fuzzysearchview.h" #include "searchfolderview.h" #include "searchtabheader.h" #include "searchtextbar.h" #include "searchtreeview.h" #include "coredbsearchxml.h" #include "tagfolderview.h" #include "timelinewidget.h" #include "facescandialog.h" #include "facesdetector.h" #include "tagsmanager.h" #include "labelstreeview.h" #include "coredb.h" #include "dexpanderbox.h" namespace Digikam { class Q_DECL_HIDDEN AlbumFolderViewSideBarWidget::Private { public: explicit Private() : albumModificationHelper(0), albumFolderView(0), searchTextBar(0) { } AlbumModificationHelper* albumModificationHelper; AlbumSelectionTreeView* albumFolderView; SearchTextBar* searchTextBar; }; AlbumFolderViewSideBarWidget::AlbumFolderViewSideBarWidget(QWidget* const parent, AlbumModel* const model, AlbumModificationHelper* const albumModificationHelper) : SidebarWidget(parent), d(new Private) { setObjectName(QLatin1String("AlbumFolderView Sidebar")); setProperty("Shortcut", Qt::CTRL + Qt::SHIFT + Qt::Key_F1); d->albumModificationHelper = albumModificationHelper; const int spacing = QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); QVBoxLayout* const layout = new QVBoxLayout(this); d->albumFolderView = new AlbumSelectionTreeView(this, model, d->albumModificationHelper); d->albumFolderView->setObjectName(QLatin1String("AlbumFolderView")); d->albumFolderView->setConfigGroup(getConfigGroup()); d->albumFolderView->setExpandNewCurrentItem(true); d->albumFolderView->setAlbumManagerCurrentAlbum(true); d->searchTextBar = new SearchTextBar(this, QLatin1String("ItemIconViewFolderSearchBar")); d->searchTextBar->setHighlightOnResult(true); d->searchTextBar->setModel(model, AbstractAlbumModel::AlbumIdRole, AbstractAlbumModel::AlbumTitleRole); d->searchTextBar->setFilterModel(d->albumFolderView->albumFilterModel()); layout->addWidget(d->albumFolderView); layout->addWidget(d->searchTextBar); layout->setContentsMargins(0, 0, spacing, 0); // setup connection connect(d->albumFolderView, SIGNAL(signalFindDuplicates(PAlbum*)), this, SIGNAL(signalFindDuplicates(PAlbum*))); } AlbumFolderViewSideBarWidget::~AlbumFolderViewSideBarWidget() { delete d; } void AlbumFolderViewSideBarWidget::setActive(bool active) { if (active) { AlbumManager::instance()->setCurrentAlbums(QList() << d->albumFolderView->currentAlbum()); } } void AlbumFolderViewSideBarWidget::doLoadState() { d->albumFolderView->loadState(); } void AlbumFolderViewSideBarWidget::doSaveState() { d->albumFolderView->saveState(); } void AlbumFolderViewSideBarWidget::applySettings() { ApplicationSettings* const settings = ApplicationSettings::instance(); d->albumFolderView->setEnableToolTips(settings->getShowAlbumToolTips()); } void AlbumFolderViewSideBarWidget::changeAlbumFromHistory(const QList& album) { d->albumFolderView->setCurrentAlbums(album); } AlbumPointer AlbumFolderViewSideBarWidget::currentAlbum() const { return AlbumPointer (d->albumFolderView->currentAlbum()); } void AlbumFolderViewSideBarWidget::setCurrentAlbum(PAlbum* album) { // Change the current album in list view. d->albumFolderView->setCurrentAlbums(QList() << album); } const QIcon AlbumFolderViewSideBarWidget::getIcon() { return QIcon::fromTheme(QLatin1String("folder-pictures")); } const QString AlbumFolderViewSideBarWidget::getCaption() { return i18n("Albums"); } // ----------------------------------------------------------------------------- class Q_DECL_HIDDEN TagViewSideBarWidget::Private { public: enum TagsSource { NoTags = 0, ExistingTags }; public: explicit Private() : openTagMngr(0), tagSearchBar(0), tagFolderView(0), btnGroup(0), noTagsBtn(0), tagsBtn(0), noTagsWasChecked(false), ExistingTagsWasChecked(false) { } public: QPushButton* openTagMngr; SearchTextBar* tagSearchBar; TagFolderView* tagFolderView; QButtonGroup* btnGroup; QRadioButton* noTagsBtn; QRadioButton* tagsBtn; bool noTagsWasChecked; bool ExistingTagsWasChecked; QString noTagsSearchXml; static const QString configTagsSourceEntry; }; const QString TagViewSideBarWidget::Private::configTagsSourceEntry(QLatin1String("TagsSource")); TagViewSideBarWidget::TagViewSideBarWidget(QWidget* const parent, TagModel* const model) : SidebarWidget(parent), d(new Private) { setObjectName(QLatin1String("TagView Sidebar")); setProperty("Shortcut", Qt::CTRL + Qt::SHIFT + Qt::Key_F2); const int spacing = QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); QVBoxLayout* const layout = new QVBoxLayout(this); d->openTagMngr = new QPushButton( i18n("Open Tag Manager")); d->noTagsBtn = new QRadioButton(i18n("No Tags"), this); d->tagsBtn = new QRadioButton(i18n("Existing Tags"), this); d->btnGroup = new QButtonGroup(this); d->btnGroup->addButton(d->noTagsBtn); d->btnGroup->addButton(d->tagsBtn); d->btnGroup->setId(d->noTagsBtn, 0); d->btnGroup->setId(d->tagsBtn, 1); d->btnGroup->setExclusive(true); d->tagFolderView = new TagFolderView(this, model); d->tagFolderView->setConfigGroup(getConfigGroup()); d->tagFolderView->setExpandNewCurrentItem(true); d->tagFolderView->setAlbumManagerCurrentAlbum(true); d->tagSearchBar = new SearchTextBar(this, QLatin1String("ItemIconViewTagSearchBar")); d->tagSearchBar->setHighlightOnResult(true); d->tagSearchBar->setModel(model, AbstractAlbumModel::AlbumIdRole, AbstractAlbumModel::AlbumTitleRole); d->tagSearchBar->setFilterModel(d->tagFolderView->albumFilterModel()); layout->addWidget(d->openTagMngr); layout->addWidget(d->noTagsBtn); layout->addWidget(d->tagsBtn); layout->addWidget(d->tagFolderView); layout->addWidget(d->tagSearchBar); layout->setContentsMargins(0, 0, spacing, 0); connect(d->openTagMngr, SIGNAL(clicked()), this,SLOT(slotOpenTagManager())); connect(d->tagFolderView, SIGNAL(signalFindDuplicates(QList)), this, SIGNAL(signalFindDuplicates(QList))); connect(d->btnGroup, SIGNAL(buttonClicked(int)), this, SLOT(slotToggleTagsSelection(int))); } TagViewSideBarWidget::~TagViewSideBarWidget() { delete d; } void TagViewSideBarWidget::setActive(bool active) { if (active) { - if(d->noTagsBtn->isChecked()) + if (d->noTagsBtn->isChecked()) { setNoTagsAlbum(); } else { AlbumManager::instance()->setCurrentAlbums(d->tagFolderView->selectedTags()); } } } void TagViewSideBarWidget::doLoadState() { KConfigGroup group = getConfigGroup(); bool noTagsBtnWasChecked = group.readEntry(d->configTagsSourceEntry, false); d->noTagsBtn->setChecked(noTagsBtnWasChecked); d->tagsBtn->setChecked(!noTagsBtnWasChecked); d->noTagsWasChecked = noTagsBtnWasChecked; d->ExistingTagsWasChecked = !noTagsBtnWasChecked; d->tagFolderView->loadState(); d->tagFolderView->setDisabled(noTagsBtnWasChecked); } void TagViewSideBarWidget::doSaveState() { KConfigGroup group = getConfigGroup(); group.writeEntry(d->configTagsSourceEntry, d->noTagsBtn->isChecked()); d->tagFolderView->saveState(); group.sync(); } void TagViewSideBarWidget::applySettings() { } void TagViewSideBarWidget::changeAlbumFromHistory(const QList& album) { if (album.first()->type() == Album::TAG) { d->tagsBtn->setChecked(true); d->tagFolderView->setEnabled(true); d->ExistingTagsWasChecked = true; d->noTagsWasChecked = false; d->tagFolderView->setCurrentAlbums(album); } else { d->noTagsBtn->setChecked(true); d->tagFolderView->setDisabled(true); d->noTagsWasChecked = true; d->ExistingTagsWasChecked = false; } } AlbumPointer TagViewSideBarWidget::currentAlbum() const { return AlbumPointer (d->tagFolderView->currentAlbum()); } void TagViewSideBarWidget::setNoTagsAlbum() { if (d->noTagsSearchXml.isEmpty()) { SearchXmlWriter writer; writer.setFieldOperator((SearchXml::standardFieldOperator())); writer.writeGroup(); writer.writeField(QLatin1String("nottagged"), SearchXml::Equal); writer.finishField(); writer.finishGroup(); writer.finish(); d->noTagsSearchXml = writer.xml(); } QString title = SAlbum::getTemporaryTitle(DatabaseSearch::AdvancedSearch); SAlbum* album = AlbumManager::instance()->findSAlbum(title); int id; if (album) { id = album->id(); CoreDbAccess().db()->updateSearch(id,DatabaseSearch::AdvancedSearch, SAlbum::getTemporaryTitle(DatabaseSearch::AdvancedSearch), d->noTagsSearchXml); } else { id = CoreDbAccess().db()->addSearch(DatabaseSearch::AdvancedSearch, SAlbum::getTemporaryTitle(DatabaseSearch::AdvancedSearch), d->noTagsSearchXml); } album = new SAlbum(i18n("No Tags Album"), id); if (album) { AlbumManager::instance()->setCurrentAlbums(QList() << album); } } const QIcon TagViewSideBarWidget::getIcon() { return QIcon::fromTheme(QLatin1String("tag")); } const QString TagViewSideBarWidget::getCaption() { return i18n("Tags"); } void TagViewSideBarWidget::setCurrentAlbum(TAlbum* album) { d->tagFolderView->setCurrentAlbums(QList() << album); } void TagViewSideBarWidget::slotOpenTagManager() { TagsManager* const tagMngr = TagsManager::instance(); tagMngr->show(); tagMngr->activateWindow(); tagMngr->raise(); } void TagViewSideBarWidget::slotToggleTagsSelection(int radioClicked) { switch (Private::TagsSource(radioClicked)) { case Private::NoTags: { if (!d->noTagsWasChecked) { setNoTagsAlbum(); d->tagFolderView->setDisabled(true); d->noTagsWasChecked = d->noTagsBtn->isChecked(); d->ExistingTagsWasChecked = d->tagsBtn->isChecked(); } break; } case Private::ExistingTags: { if (!d->ExistingTagsWasChecked) { d->tagFolderView->setEnabled(true); setActive(true); d->noTagsWasChecked = d->noTagsBtn->isChecked(); d->ExistingTagsWasChecked = d->tagsBtn->isChecked(); } break; } } } // ----------------------------------------------------------------------------- class Q_DECL_HIDDEN LabelsSideBarWidget::Private { public: explicit Private() : labelsTree(0) { } LabelsTreeView* labelsTree; }; LabelsSideBarWidget::LabelsSideBarWidget(QWidget* const parent) : SidebarWidget(parent), d(new Private) { setObjectName(QLatin1String("Labels Sidebar")); setProperty("Shortcut", Qt::CTRL + Qt::SHIFT + Qt::Key_F3); const int spacing = QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); QVBoxLayout* const layout = new QVBoxLayout(this); d->labelsTree = new LabelsTreeView(this); d->labelsTree->setConfigGroup(getConfigGroup()); layout->addWidget(d->labelsTree); layout->setContentsMargins(0, 0, spacing, 0); } LabelsSideBarWidget::~LabelsSideBarWidget() { delete d; } LabelsTreeView *LabelsSideBarWidget::labelsTree() { return d->labelsTree; } void LabelsSideBarWidget::setActive(bool active) { if (active) { d->labelsTree->setCurrentAlbum(); } } void LabelsSideBarWidget::applySettings() { } void LabelsSideBarWidget::changeAlbumFromHistory(const QList& album) { Q_UNUSED(album); } void LabelsSideBarWidget::doLoadState() { d->labelsTree->doLoadState(); } void LabelsSideBarWidget::doSaveState() { d->labelsTree->doSaveState(); } const QIcon LabelsSideBarWidget::getIcon() { return QIcon::fromTheme(QLatin1String("folder-favorites")); } const QString LabelsSideBarWidget::getCaption() { return i18n("Labels"); } QHash > LabelsSideBarWidget::selectedLabels() { return d->labelsTree->selectedLabels(); } // ----------------------------------------------------------------------------- class Q_DECL_HIDDEN DateFolderViewSideBarWidget::Private { public: explicit Private() : dateFolderView(0) { } DateFolderView* dateFolderView; }; DateFolderViewSideBarWidget::DateFolderViewSideBarWidget(QWidget* const parent, DateAlbumModel* const model, ItemAlbumFilterModel* const imageFilterModel) : SidebarWidget(parent), d(new Private) { setObjectName(QLatin1String("DateFolderView Sidebar")); setProperty("Shortcut", Qt::CTRL + Qt::SHIFT + Qt::Key_F4); const int spacing = QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); QVBoxLayout* const layout = new QVBoxLayout(this); d->dateFolderView = new DateFolderView(this, model); d->dateFolderView->setConfigGroup(getConfigGroup()); d->dateFolderView->setItemModel(imageFilterModel); layout->addWidget(d->dateFolderView); layout->setContentsMargins(0, 0, spacing, 0); } DateFolderViewSideBarWidget::~DateFolderViewSideBarWidget() { delete d; } void DateFolderViewSideBarWidget::setActive(bool active) { d->dateFolderView->setActive(active); } void DateFolderViewSideBarWidget::doLoadState() { d->dateFolderView->loadState(); } void DateFolderViewSideBarWidget::doSaveState() { d->dateFolderView->saveState(); } void DateFolderViewSideBarWidget::applySettings() { } void DateFolderViewSideBarWidget::changeAlbumFromHistory(const QList& album) { d->dateFolderView->changeAlbumFromHistory(dynamic_cast(album.first())); } AlbumPointer DateFolderViewSideBarWidget::currentAlbum() const { return d->dateFolderView->currentAlbum(); } void DateFolderViewSideBarWidget::gotoDate(const QDate& date) { d->dateFolderView->gotoDate(date); } const QIcon DateFolderViewSideBarWidget::getIcon() { return QIcon::fromTheme(QLatin1String("view-calendar-list")); } const QString DateFolderViewSideBarWidget::getCaption() { return i18n("Dates"); } // ----------------------------------------------------------------------------- class Q_DECL_HIDDEN TimelineSideBarWidget::Private { public: explicit Private() : scaleBG(0), cursorCountLabel(0), scrollBar(0), timer(0), resetButton(0), saveButton(0), timeUnitCB(0), nameEdit(0), cursorDateLabel(0), searchDateBar(0), timeLineFolderView(0), timeLineWidget(0), searchModificationHelper(0) { } static const QString configHistogramTimeUnitEntry; static const QString configHistogramScaleEntry; static const QString configCursorPositionEntry; QButtonGroup* scaleBG; QLabel* cursorCountLabel; QScrollBar* scrollBar; QTimer* timer; QToolButton* resetButton; QToolButton* saveButton; QComboBox* timeUnitCB; QLineEdit* nameEdit; DAdjustableLabel* cursorDateLabel; SearchTextBar* searchDateBar; EditableSearchTreeView* timeLineFolderView; TimeLineWidget* timeLineWidget; SearchModificationHelper* searchModificationHelper; AlbumPointer currentTimelineSearch; }; const QString TimelineSideBarWidget::Private::configHistogramTimeUnitEntry(QLatin1String("Histogram TimeUnit")); const QString TimelineSideBarWidget::Private::configHistogramScaleEntry(QLatin1String("Histogram Scale")); const QString TimelineSideBarWidget::Private::configCursorPositionEntry(QLatin1String("Cursor Position")); // -------------------------------------------------------- TimelineSideBarWidget::TimelineSideBarWidget(QWidget* const parent, SearchModel* const searchModel, SearchModificationHelper* const searchModificationHelper) : SidebarWidget(parent), d(new Private) { setObjectName(QLatin1String("TimeLine Sidebar")); setProperty("Shortcut", Qt::CTRL + Qt::SHIFT + Qt::Key_F5); d->searchModificationHelper = searchModificationHelper; d->timer = new QTimer(this); setAttribute(Qt::WA_DeleteOnClose); const int spacing = QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); QVBoxLayout* const vlay = new QVBoxLayout(this); QFrame* const panel = new QFrame(this); panel->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken); panel->setLineWidth(1); QGridLayout* const grid = new QGridLayout(panel); // --------------------------------------------------------------- QWidget* const hbox1 = new QWidget(panel); QHBoxLayout* const hlay = new QHBoxLayout(hbox1); QLabel* const label1 = new QLabel(i18n("Time Unit:"), hbox1); d->timeUnitCB = new QComboBox(hbox1); d->timeUnitCB->addItem(i18n("Day"), TimeLineWidget::Day); d->timeUnitCB->addItem(i18n("Week"), TimeLineWidget::Week); d->timeUnitCB->addItem(i18n("Month"), TimeLineWidget::Month); d->timeUnitCB->addItem(i18n("Year"), TimeLineWidget::Year); d->timeUnitCB->setCurrentIndex((int)TimeLineWidget::Month); d->timeUnitCB->setFocusPolicy(Qt::NoFocus); d->timeUnitCB->setWhatsThis(i18n("

Select the histogram time unit.

" "

You can change the graph decade to zoom in or zoom out over time.

")); QWidget* const scaleBox = new QWidget(hbox1); QHBoxLayout* const hlay2 = new QHBoxLayout(scaleBox); d->scaleBG = new QButtonGroup(scaleBox); d->scaleBG->setExclusive(true); scaleBox->setWhatsThis( i18n("

Select the histogram scale.

" "

If the date's maximal counts are small, you can use the linear scale.

" "

Logarithmic scale can be used when the maximal counts are big; " "if it is used, all values (small and large) will be visible on the " "graph.

")); QToolButton* const linHistoButton = new QToolButton(scaleBox); linHistoButton->setToolTip( i18n( "Linear" ) ); linHistoButton->setIcon(QIcon::fromTheme(QLatin1String("view-object-histogram-linear"))); linHistoButton->setCheckable(true); d->scaleBG->addButton(linHistoButton, TimeLineWidget::LinScale); QToolButton* const logHistoButton = new QToolButton(scaleBox); logHistoButton->setToolTip( i18n( "Logarithmic" ) ); logHistoButton->setIcon(QIcon::fromTheme(QLatin1String("view-object-histogram-logarithmic"))); logHistoButton->setCheckable(true); d->scaleBG->addButton(logHistoButton, TimeLineWidget::LogScale); hlay2->setContentsMargins(QMargins()); hlay2->setSpacing(0); hlay2->addWidget(linHistoButton); hlay2->addWidget(logHistoButton); hlay->setContentsMargins(QMargins()); hlay->setSpacing(spacing); hlay->addWidget(label1); hlay->addWidget(d->timeUnitCB); hlay->addItem(new QSpacerItem(10, 10, QSizePolicy::Expanding, QSizePolicy::Minimum)); hlay->addWidget(scaleBox); // --------------------------------------------------------------- d->timeLineWidget = new TimeLineWidget(panel); d->scrollBar = new QScrollBar(panel); d->scrollBar->setOrientation(Qt::Horizontal); d->scrollBar->setMinimum(0); d->scrollBar->setSingleStep(1); d->cursorDateLabel = new DAdjustableLabel(panel); d->cursorCountLabel = new QLabel(panel); d->cursorCountLabel->setAlignment(Qt::AlignRight); // --------------------------------------------------------------- DHBox* const hbox2 = new DHBox(panel); hbox2->setContentsMargins(QMargins()); hbox2->setSpacing(spacing); d->resetButton = new QToolButton(hbox2); d->resetButton->setIcon(QIcon::fromTheme(QLatin1String("document-revert"))); d->resetButton->setToolTip(i18n("Clear current selection")); d->resetButton->setWhatsThis(i18n("If you press this button, the current date selection on the time-line will be cleared.")); d->nameEdit = new QLineEdit(hbox2); d->nameEdit->setClearButtonEnabled(true); d->nameEdit->setWhatsThis(i18n("Enter the name of the current dates search to save in the " "\"Searches\" view")); d->saveButton = new QToolButton(hbox2); d->saveButton->setIcon(QIcon::fromTheme(QLatin1String("document-save"))); d->saveButton->setEnabled(false); d->saveButton->setToolTip(i18n("Save current selection to a new virtual Album")); d->saveButton->setWhatsThis(i18n("If you press this button, the dates selected on the time-line will be " "saved to a new search virtual Album using the name set on the left.")); // --------------------------------------------------------------- grid->addWidget(hbox1, 0, 0, 1, 4); grid->addWidget(d->cursorDateLabel, 1, 0, 1, 3); grid->addWidget(d->cursorCountLabel, 1, 3, 1, 1); grid->addWidget(d->timeLineWidget, 2, 0, 1, 4); grid->addWidget(d->scrollBar, 3, 0, 1, 4); grid->addWidget(hbox2, 4, 0, 1, 4); grid->setColumnStretch(2, 10); grid->setContentsMargins(spacing, spacing, spacing, spacing); grid->setSpacing(spacing); // --------------------------------------------------------------- d->timeLineFolderView = new EditableSearchTreeView(this, searchModel, searchModificationHelper); d->timeLineFolderView->setConfigGroup(getConfigGroup()); d->timeLineFolderView->filteredModel()->listTimelineSearches(); d->timeLineFolderView->filteredModel()->setListTemporarySearches(false); d->timeLineFolderView->setAlbumManagerCurrentAlbum(false); d->searchDateBar = new SearchTextBar(this, QLatin1String("TimeLineViewSearchDateBar")); d->searchDateBar->setModel(d->timeLineFolderView->filteredModel(), AbstractAlbumModel::AlbumIdRole, AbstractAlbumModel::AlbumTitleRole); d->searchDateBar->setFilterModel(d->timeLineFolderView->albumFilterModel()); vlay->addWidget(panel); vlay->addWidget(d->timeLineFolderView); vlay->addItem(new QSpacerItem(spacing, spacing, QSizePolicy::Minimum, QSizePolicy::Minimum)); vlay->addWidget(d->searchDateBar); vlay->setContentsMargins(0, 0, spacing, 0); vlay->setSpacing(0); // --------------------------------------------------------------- connect(AlbumManager::instance(), SIGNAL(signalDatesMapDirty(QMap)), d->timeLineWidget, SLOT(slotDatesMap(QMap))); connect(d->timeLineFolderView, SIGNAL(currentAlbumChanged(Album*)), this, SLOT(slotAlbumSelected(Album*))); connect(d->timeUnitCB, SIGNAL(activated(int)), this, SLOT(slotTimeUnitChanged(int))); connect(d->scaleBG, SIGNAL(buttonReleased(int)), this, SLOT(slotScaleChanged(int))); connect(d->timeLineWidget, SIGNAL(signalDateMapChanged()), this, SLOT(slotInit())); connect(d->timeLineWidget, SIGNAL(signalCursorPositionChanged()), this, SLOT(slotCursorPositionChanged())); connect(d->timeLineWidget, SIGNAL(signalSelectionChanged()), this, SLOT(slotSelectionChanged())); connect(d->timeLineWidget, SIGNAL(signalRefDateTimeChanged()), this, SLOT(slotRefDateTimeChanged())); connect(d->timer, SIGNAL(timeout()), this, SLOT(slotUpdateCurrentDateSearchAlbum())); connect(d->resetButton, SIGNAL(clicked()), this, SLOT(slotResetSelection())); connect(d->saveButton, SIGNAL(clicked()), this, SLOT(slotSaveSelection())); connect(d->scrollBar, SIGNAL(valueChanged(int)), this, SLOT(slotScrollBarValueChanged(int))); connect(d->nameEdit, SIGNAL(textChanged(QString)), this, SLOT(slotCheckAboutSelection())); connect(d->nameEdit, SIGNAL(returnPressed()), d->saveButton, SLOT(animateClick())); } TimelineSideBarWidget::~TimelineSideBarWidget() { delete d; } void TimelineSideBarWidget::slotInit() { // Date Maps are loaded from AlbumManager to TimeLineWidget after than GUI is initialized. // AlbumManager query Date KIO slave to stats items from database and it can take a while. // We waiting than TimeLineWidget is ready before to set last config from users. loadState(); disconnect(d->timeLineWidget, SIGNAL(signalDateMapChanged()), this, SLOT(slotInit())); connect(d->timeLineWidget, SIGNAL(signalDateMapChanged()), this, SLOT(slotCursorPositionChanged())); } void TimelineSideBarWidget::setActive(bool active) { if (active) { if (!d->currentTimelineSearch) { d->currentTimelineSearch = d->timeLineFolderView->currentAlbum(); } if (d->currentTimelineSearch) { AlbumManager::instance()->setCurrentAlbums(QList() << d->currentTimelineSearch); } else { slotUpdateCurrentDateSearchAlbum(); } } } void TimelineSideBarWidget::doLoadState() { KConfigGroup group = getConfigGroup(); d->timeUnitCB->setCurrentIndex(group.readEntry(d->configHistogramTimeUnitEntry, (int)TimeLineWidget::Month)); slotTimeUnitChanged(d->timeUnitCB->currentIndex()); int id = group.readEntry(d->configHistogramScaleEntry, (int)TimeLineWidget::LinScale); if (d->scaleBG->button(id)) { d->scaleBG->button(id)->setChecked(true); } slotScaleChanged(d->scaleBG->checkedId()); QDateTime now = QDateTime::currentDateTime(); d->timeLineWidget->setCursorDateTime(group.readEntry(d->configCursorPositionEntry, now)); d->timeLineWidget->setCurrentIndex(d->timeLineWidget->indexForCursorDateTime()); d->timeLineFolderView->loadState(); } void TimelineSideBarWidget::doSaveState() { KConfigGroup group = getConfigGroup(); group.writeEntry(d->configHistogramTimeUnitEntry, d->timeUnitCB->currentIndex()); group.writeEntry(d->configHistogramScaleEntry, d->scaleBG->checkedId()); group.writeEntry(d->configCursorPositionEntry, d->timeLineWidget->cursorDateTime()); d->timeLineFolderView->saveState(); group.sync(); } void TimelineSideBarWidget::applySettings() { // nothing to do here right now } void TimelineSideBarWidget::changeAlbumFromHistory(const QList& album) { d->timeLineFolderView->setCurrentAlbums(album); } const QIcon TimelineSideBarWidget::getIcon() { return QIcon::fromTheme(QLatin1String("player-time")); } const QString TimelineSideBarWidget::getCaption() { return i18n("Timeline"); } void TimelineSideBarWidget::slotRefDateTimeChanged() { d->scrollBar->blockSignals(true); d->scrollBar->setMaximum(d->timeLineWidget->totalIndex()-1); d->scrollBar->setValue(d->timeLineWidget->indexForRefDateTime()-1); d->scrollBar->blockSignals(false); } void TimelineSideBarWidget::slotTimeUnitChanged(int mode) { d->timeLineWidget->setTimeUnit((TimeLineWidget::TimeUnit)mode); } void TimelineSideBarWidget::slotScrollBarValueChanged(int val) { d->timeLineWidget->setCurrentIndex(val); } void TimelineSideBarWidget::slotScaleChanged(int mode) { d->timeLineWidget->setScaleMode((TimeLineWidget::ScaleMode)mode); } void TimelineSideBarWidget::slotCursorPositionChanged() { QString txt; int val = d->timeLineWidget->cursorInfo(txt); d->cursorDateLabel->setAdjustedText(txt); d->cursorCountLabel->setText((val == 0) ? i18n("no item") : i18np("1 item", "%1 items", val)); } void TimelineSideBarWidget::slotSelectionChanged() { d->timer->setSingleShot(true); d->timer->start(500); } /** Called from d->timer event.*/ void TimelineSideBarWidget::slotUpdateCurrentDateSearchAlbum() { slotCheckAboutSelection(); int totalCount = 0; DateRangeList dateRanges = d->timeLineWidget->selectedDateRange(totalCount); d->currentTimelineSearch = d->searchModificationHelper-> slotCreateTimeLineSearch(SAlbum::getTemporaryTitle(DatabaseSearch::TimeLineSearch), dateRanges, true); d->timeLineFolderView->setCurrentAlbum(0); // "temporary" search is not listed in view } void TimelineSideBarWidget::slotSaveSelection() { QString name = d->nameEdit->text(); int totalCount = 0; DateRangeList dateRanges = d->timeLineWidget->selectedDateRange(totalCount); d->currentTimelineSearch = d->searchModificationHelper->slotCreateTimeLineSearch(name, dateRanges); } void TimelineSideBarWidget::slotAlbumSelected(Album* album) { if (d->currentTimelineSearch == album) { return; } SAlbum* const salbum = dynamic_cast(album); if (!salbum) { return; } d->currentTimelineSearch = salbum; AlbumManager::instance()->setCurrentAlbums(QList() << salbum); SearchXmlReader reader(salbum->query()); // The timeline query consists of groups, with two date time fields each DateRangeList list; while (!reader.atEnd()) { // read groups if (reader.readNext() == SearchXml::Group) { QDateTime start, end; int numberOfFields = 0; while (!reader.atEnd()) { // read fields reader.readNext(); if (reader.isEndElement()) { break; } if (reader.isFieldElement()) { if (numberOfFields == 0) { start = reader.valueToDateTime(); } else if (numberOfFields == 1) { end = reader.valueToDateTime(); } ++numberOfFields; } } if (numberOfFields) { list << DateRange(start, end); } } } d->timeLineWidget->setSelectedDateRange(list); } void TimelineSideBarWidget::slotResetSelection() { d->timeLineWidget->slotResetSelection(); slotCheckAboutSelection(); AlbumManager::instance()->setCurrentAlbums(QList()); } void TimelineSideBarWidget::slotCheckAboutSelection() { int totalCount = 0; DateRangeList list = d->timeLineWidget->selectedDateRange(totalCount); if (!list.isEmpty()) { d->nameEdit->setEnabled(true); if (!d->nameEdit->text().isEmpty()) { d->saveButton->setEnabled(true); } } else { d->nameEdit->setEnabled(false); d->saveButton->setEnabled(false); } } // ----------------------------------------------------------------------------- class Q_DECL_HIDDEN SearchSideBarWidget::Private { public: explicit Private() : searchSearchBar(0), searchTreeView(0), searchTabHeader(0) { } SearchTextBar* searchSearchBar; NormalSearchTreeView* searchTreeView; SearchTabHeader* searchTabHeader; }; SearchSideBarWidget::SearchSideBarWidget(QWidget* const parent, SearchModel* const searchModel, SearchModificationHelper* const searchModificationHelper) : SidebarWidget(parent), d(new Private) { setObjectName(QLatin1String("Search Sidebar")); setProperty("Shortcut", Qt::CTRL + Qt::SHIFT + Qt::Key_F6); const int spacing = QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); QVBoxLayout* const layout = new QVBoxLayout(this); d->searchTabHeader = new SearchTabHeader(this); d->searchTreeView = new NormalSearchTreeView(this, searchModel, searchModificationHelper); d->searchTreeView->setConfigGroup(getConfigGroup()); d->searchTreeView->filteredModel()->listNormalSearches(); d->searchTreeView->filteredModel()->setListTemporarySearches(true); d->searchTreeView->setAlbumManagerCurrentAlbum(true); d->searchSearchBar = new SearchTextBar(this, QLatin1String("ItemIconViewSearchSearchBar")); d->searchSearchBar->setModel(d->searchTreeView->filteredModel(), AbstractAlbumModel::AlbumIdRole, AbstractAlbumModel::AlbumTitleRole); d->searchSearchBar->setFilterModel(d->searchTreeView->albumFilterModel()); layout->addWidget(d->searchTabHeader); layout->addWidget(d->searchTreeView); layout->setStretchFactor(d->searchTreeView, 1); layout->addWidget(d->searchSearchBar); layout->setContentsMargins(0, 0, spacing, 0); connect(d->searchTreeView, SIGNAL(newSearch()), d->searchTabHeader, SLOT(newAdvancedSearch())); connect(d->searchTreeView, SIGNAL(editSearch(SAlbum*)), d->searchTabHeader, SLOT(editSearch(SAlbum*))); connect(d->searchTreeView, SIGNAL(currentAlbumChanged(Album*)), d->searchTabHeader, SLOT(selectedSearchChanged(Album*))); connect(d->searchTabHeader, SIGNAL(searchShallBeSelected(QList)), d->searchTreeView, SLOT(setCurrentAlbums(QList))); } SearchSideBarWidget::~SearchSideBarWidget() { delete d; } void SearchSideBarWidget::setActive(bool active) { if (active) { AlbumManager::instance()->setCurrentAlbums(QList() << d->searchTreeView->currentAlbum()); d->searchTabHeader->selectedSearchChanged(d->searchTreeView->currentAlbum()); } } void SearchSideBarWidget::doLoadState() { d->searchTreeView->loadState(); } void SearchSideBarWidget::doSaveState() { d->searchTreeView->saveState(); } void SearchSideBarWidget::applySettings() { } void SearchSideBarWidget::changeAlbumFromHistory(const QList& album) { d->searchTreeView->setCurrentAlbums(album); } const QIcon SearchSideBarWidget::getIcon() { return QIcon::fromTheme(QLatin1String("edit-find")); } const QString SearchSideBarWidget::getCaption() { return i18nc("Advanced search images, access stored searches", "Search"); } void SearchSideBarWidget::newKeywordSearch() { d->searchTabHeader->newKeywordSearch(); } void SearchSideBarWidget::newAdvancedSearch() { d->searchTabHeader->newAdvancedSearch(); } // ----------------------------------------------------------------------------- class Q_DECL_HIDDEN FuzzySearchSideBarWidget::Private { public: explicit Private() : fuzzySearchView(0), searchModificationHelper(0) { } FuzzySearchView* fuzzySearchView; SearchModificationHelper* searchModificationHelper; }; FuzzySearchSideBarWidget::FuzzySearchSideBarWidget(QWidget* const parent, SearchModel* const searchModel, SearchModificationHelper* const searchModificationHelper) : SidebarWidget(parent), d(new Private) { setObjectName(QLatin1String("Fuzzy Search Sidebar")); setProperty("Shortcut", Qt::CTRL + Qt::SHIFT + Qt::Key_F7); const int spacing = QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); d->fuzzySearchView = new FuzzySearchView(searchModel, searchModificationHelper, this); d->fuzzySearchView->setConfigGroup(getConfigGroup()); QVBoxLayout* const layout = new QVBoxLayout(this); layout->addWidget(d->fuzzySearchView); layout->setContentsMargins(0, 0, spacing, 0); } FuzzySearchSideBarWidget::~FuzzySearchSideBarWidget() { delete d; } void FuzzySearchSideBarWidget::setActive(bool active) { d->fuzzySearchView->setActive(active); if (active) { AlbumManager::instance()->setCurrentAlbums(QList() << d->fuzzySearchView->currentAlbum()); } emit signalActive(active); } void FuzzySearchSideBarWidget::doLoadState() { d->fuzzySearchView->loadState(); } void FuzzySearchSideBarWidget::doSaveState() { d->fuzzySearchView->saveState(); } void FuzzySearchSideBarWidget::applySettings() { } void FuzzySearchSideBarWidget::changeAlbumFromHistory(const QList& album) { SAlbum* const salbum = dynamic_cast(album.first()); d->fuzzySearchView->setCurrentAlbum(salbum); } const QIcon FuzzySearchSideBarWidget::getIcon() { return QIcon::fromTheme(QLatin1String("tools-wizard")); } const QString FuzzySearchSideBarWidget::getCaption() { return i18nc("Fuzzy Search images, as duplicates, sketch, searches by similarities", "Similarity"); } void FuzzySearchSideBarWidget::newDuplicatesSearch(PAlbum* album) { d->fuzzySearchView->newDuplicatesSearch(album); } void FuzzySearchSideBarWidget::newDuplicatesSearch(const QList& albums) { d->fuzzySearchView->newDuplicatesSearch(albums); } void FuzzySearchSideBarWidget::newDuplicatesSearch(const QList& albums) { d->fuzzySearchView->newDuplicatesSearch(albums); } void FuzzySearchSideBarWidget::newSimilarSearch(const ItemInfo& imageInfo) { if (imageInfo.isNull()) { return; } d->fuzzySearchView->setItemInfo(imageInfo); } // ----------------------------------------------------------------------------- #ifdef HAVE_MARBLE class Q_DECL_HIDDEN GPSSearchSideBarWidget::Private { public: explicit Private() : gpsSearchView(0) { } GPSSearchView* gpsSearchView; }; GPSSearchSideBarWidget::GPSSearchSideBarWidget(QWidget* const parent, SearchModel* const searchModel, SearchModificationHelper* const searchModificationHelper, ItemFilterModel* const imageFilterModel, QItemSelectionModel* const itemSelectionModel) : SidebarWidget(parent), d(new Private) { setObjectName(QLatin1String("GPS Search Sidebar")); setProperty("Shortcut", Qt::CTRL + Qt::SHIFT + Qt::Key_F8); d->gpsSearchView = new GPSSearchView(this, searchModel, searchModificationHelper, imageFilterModel, itemSelectionModel); d->gpsSearchView->setConfigGroup(getConfigGroup()); const int spacing = QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); QScrollArea* const scrollArea = new QScrollArea(this); QVBoxLayout* const layout = new QVBoxLayout(this); layout->addWidget(scrollArea); layout->setContentsMargins(0, 0, spacing, 0); scrollArea->setWidget(d->gpsSearchView); scrollArea->setWidgetResizable(true); connect(d->gpsSearchView, SIGNAL(signalMapSoloItems(QList,QString)), this, SIGNAL(signalMapSoloItems(QList,QString))); } GPSSearchSideBarWidget::~GPSSearchSideBarWidget() { delete d; } void GPSSearchSideBarWidget::setActive(bool active) { d->gpsSearchView->setActive(active); } void GPSSearchSideBarWidget::doLoadState() { d->gpsSearchView->loadState(); } void GPSSearchSideBarWidget::doSaveState() { d->gpsSearchView->saveState(); } void GPSSearchSideBarWidget::applySettings() { } void GPSSearchSideBarWidget::changeAlbumFromHistory(const QList& album) { d->gpsSearchView->changeAlbumFromHistory(dynamic_cast(album.first())); } const QIcon GPSSearchSideBarWidget::getIcon() { return QIcon::fromTheme(QLatin1String("globe")); } const QString GPSSearchSideBarWidget::getCaption() { return i18nc("Search images on a map", "Map"); } #endif // HAVE_MARBLE // ----------------------------------------------------------------------------- class Q_DECL_HIDDEN PeopleSideBarWidget::Private : public TagViewSideBarWidget::Private { public: explicit Private() { personIcon = 0; textLabel = 0; rescanButton = 0; searchModificationHelper = 0; } QLabel* personIcon; QLabel* textLabel; QPushButton* rescanButton; SearchModificationHelper* searchModificationHelper; }; PeopleSideBarWidget::PeopleSideBarWidget(QWidget* const parent, TagModel* const model, SearchModificationHelper* const searchModificationHelper) : SidebarWidget(parent), d(new Private) { setObjectName(QLatin1String("People Sidebar")); setProperty("Shortcut", Qt::CTRL + Qt::SHIFT + Qt::Key_F9); const int spacing = QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); d->searchModificationHelper = searchModificationHelper; QVBoxLayout* const layout = new QVBoxLayout; QHBoxLayout* const hlay = new QHBoxLayout; d->tagFolderView = new TagFolderView(this, model); d->tagFolderView->setConfigGroup(getConfigGroup()); d->tagFolderView->setExpandNewCurrentItem(true); d->tagFolderView->setAlbumManagerCurrentAlbum(true); d->tagFolderView->setShowDeleteFaceTagsAction(true); d->tagFolderView->filteredModel()->listOnlyTagsWithProperty(TagPropertyName::person()); d->tagFolderView->filteredModel()->setFilterBehavior(AlbumFilterModel::StrictFiltering); d->tagSearchBar = new SearchTextBar(this, QLatin1String("ItemIconViewPeopleSearchBar")); d->tagSearchBar->setHighlightOnResult(true); d->tagSearchBar->setModel(d->tagFolderView->filteredModel(), AbstractAlbumModel::AlbumIdRole, AbstractAlbumModel::AlbumTitleRole); d->tagSearchBar->setFilterModel(d->tagFolderView->albumFilterModel()); d->rescanButton = new QPushButton; d->rescanButton->setText(i18n("Scan collection for faces")); d->personIcon = new QLabel; d->personIcon->setPixmap(QIcon::fromTheme(QLatin1String("edit-image-face-show")).pixmap(48)); d->textLabel = new QLabel(i18n("People Tags")); hlay->addWidget(d->personIcon); hlay->addWidget(d->textLabel); layout->addLayout(hlay); layout->addWidget(d->rescanButton); layout->addWidget(d->tagFolderView); layout->addWidget(d->tagSearchBar); layout->setContentsMargins(0, 0, spacing, 0); setLayout(layout); connect(d->tagFolderView, SIGNAL(signalFindDuplicates(QList)), this, SIGNAL(signalFindDuplicates(QList))); connect(d->rescanButton, SIGNAL(pressed()), this, SLOT(slotScanForFaces()) ); } PeopleSideBarWidget::~PeopleSideBarWidget() { delete d; } void PeopleSideBarWidget::slotInit() { loadState(); } void PeopleSideBarWidget::setActive(bool active) { emit requestFaceMode(active); if (active) { d->tagFolderView->setCurrentAlbums(QList() << d->tagFolderView->currentAlbum()); } } void PeopleSideBarWidget::doLoadState() { d->tagFolderView->loadState(); } void PeopleSideBarWidget::doSaveState() { d->tagFolderView->saveState(); } void PeopleSideBarWidget::applySettings() { } void PeopleSideBarWidget::changeAlbumFromHistory(const QList& album) { d->tagFolderView->setCurrentAlbums(album); } void PeopleSideBarWidget::slotScanForFaces() { FaceScanDialog dialog; if (dialog.exec() == QDialog::Accepted) { FacesDetector* const tool = new FacesDetector(dialog.settings()); tool->start(); } } const QIcon PeopleSideBarWidget::getIcon() { return QIcon::fromTheme(QLatin1String("edit-image-face-show")); } const QString PeopleSideBarWidget::getCaption() { return i18nc("Browse images sorted by depicted people", "People"); } } // namespace Digikam diff --git a/core/app/views/tableview/tableview_treeview.cpp b/core/app/views/tableview/tableview_treeview.cpp index 34312973f0..339203ef81 100644 --- a/core/app/views/tableview/tableview_treeview.cpp +++ b/core/app/views/tableview/tableview_treeview.cpp @@ -1,346 +1,346 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2013-03-02 * Description : Table view: Tree view subelement * * Copyright (C) 2013 by Michael G. Hansen * * 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 "tableview_treeview.h" // Qt includes #include #include #include #include #include // Local includes #include "digikam_debug.h" #include "contextmenuhelper.h" #include "iteminfo.h" #include "itemmodel.h" #include "tableview_column_configuration_dialog.h" #include "tableview_columnfactory.h" #include "tableview_model.h" #include "tableview_selection_model_syncer.h" #include "tableview_treeview_delegate.h" #include "thumbnailsize.h" namespace Digikam { class Q_DECL_HIDDEN TableViewTreeView::Private { public: explicit Private() : headerContextMenuActiveColumn(-1), actionHeaderContextMenuRemoveColumn(0), actionHeaderContextMenuConfigureColumn(0), dragDropThumbnailSize() { } public: int headerContextMenuActiveColumn; QAction* actionHeaderContextMenuRemoveColumn; QAction* actionHeaderContextMenuConfigureColumn; ThumbnailSize dragDropThumbnailSize; }; TableViewTreeView::TableViewTreeView(TableViewShared* const tableViewShared, QWidget* const parent) : QTreeView(parent), d(new Private()), s(tableViewShared) { setModel(s->tableViewModel); setSelectionModel(s->tableViewSelectionModel); s->itemDelegate = new TableViewItemDelegate(s, this); setSelectionMode(QAbstractItemView::ExtendedSelection); setItemDelegate(s->itemDelegate); setAlternatingRowColors(true); setSortingEnabled(true); setAllColumnsShowFocus(true); setDragEnabled(true); setAcceptDrops(true); setWordWrap(true); // viewport()->setAcceptDrops(true); d->actionHeaderContextMenuRemoveColumn = new QAction(QIcon::fromTheme(QLatin1String("edit-table-delete-column")), i18n("Remove this column"), this); connect(d->actionHeaderContextMenuRemoveColumn, SIGNAL(triggered(bool)), this, SLOT(slotHeaderContextMenuActionRemoveColumnTriggered())); d->actionHeaderContextMenuConfigureColumn = new QAction(QIcon::fromTheme(QLatin1String("configure")), i18n("Configure this column"), this); connect(d->actionHeaderContextMenuConfigureColumn, SIGNAL(triggered(bool)), this, SLOT(slotHeaderContextMenuConfigureColumn())); header()->installEventFilter(this); slotModelGroupingModeChanged(); connect(s->tableViewModel, SIGNAL(signalGroupingModeChanged()), this, SLOT(slotModelGroupingModeChanged())); } TableViewTreeView::~TableViewTreeView() { } bool TableViewTreeView::eventFilter(QObject* watched, QEvent* event) { QHeaderView* const headerView = header(); if ((watched == headerView) && (event->type() == QEvent::ContextMenu)) { showHeaderContextMenu(event); return true; } return QObject::eventFilter(watched, event); } void TableViewTreeView::addColumnDescriptionsToMenu(const QList& columnDescriptions, QMenu* const menu) { for (int i = 0; i < columnDescriptions.count(); ++i) { const TableViewColumnDescription& desc = columnDescriptions.at(i); QAction* const action = new QAction(desc.columnTitle, menu); if (!desc.columnIcon.isEmpty()) { action->setIcon(QIcon::fromTheme(desc.columnIcon)); } if (desc.subColumns.isEmpty()) { connect(action, SIGNAL(triggered(bool)), this, SLOT(slotHeaderContextMenuAddColumn())); action->setData(QVariant::fromValue(desc)); } else { QMenu* const subMenu = new QMenu(menu); addColumnDescriptionsToMenu(desc.subColumns, subMenu); action->setMenu(subMenu); } menu->addAction(action); } } void TableViewTreeView::showHeaderContextMenu(QEvent* const event) { QContextMenuEvent* const e = static_cast(event); QHeaderView* const headerView = header(); d->headerContextMenuActiveColumn = headerView->logicalIndexAt(e->pos()); const TableViewColumn* const columnObject = s->tableViewModel->getColumnObject(d->headerContextMenuActiveColumn); QMenu* const menu = new QMenu(this); d->actionHeaderContextMenuRemoveColumn->setEnabled(s->tableViewModel->columnCount(QModelIndex())>1); menu->addAction(d->actionHeaderContextMenuRemoveColumn); const bool columnCanConfigure = columnObject->getColumnFlags().testFlag(TableViewColumn::ColumnHasConfigurationWidget); d->actionHeaderContextMenuConfigureColumn->setEnabled(columnCanConfigure); menu->addAction(d->actionHeaderContextMenuConfigureColumn); menu->addSeparator(); // add actions for all columns QList columnDescriptions = s->columnFactory->getColumnDescriptionList(); addColumnDescriptionsToMenu(columnDescriptions, menu); menu->exec(e->globalPos()); } void TableViewTreeView::slotHeaderContextMenuAddColumn() { QAction* const triggeredAction = qobject_cast(sender()); const QVariant actionData = triggeredAction->data(); if (!actionData.canConvert()) { return; } const TableViewColumnDescription desc = actionData.value(); qCDebug(DIGIKAM_GENERAL_LOG) << "clicked: " << desc.columnTitle; const int newColumnLogicalIndex = d->headerContextMenuActiveColumn+1; s->tableViewModel->addColumnAt(desc, newColumnLogicalIndex); // since the header column order is not the same as the model's column order, we need // to make sure the new column is moved directly behind the current column in the header: const int clickedVisualIndex = header()->visualIndex(d->headerContextMenuActiveColumn); const int newColumnVisualIndex = header()->visualIndex(newColumnLogicalIndex); int newColumnVisualTargetIndex = clickedVisualIndex + 1; // If the column is inserted before the clicked column, we have to // subtract one from the target index because it looks like QHeaderView first removes // the column and then inserts it. if (newColumnVisualIndex < clickedVisualIndex) { newColumnVisualTargetIndex--; } if (newColumnVisualIndex!=newColumnVisualTargetIndex) { header()->moveSection(newColumnVisualIndex, newColumnVisualTargetIndex); } // Ensure that the newly created column is visible. // This is especially important if the new column is the last one, // because then it can be outside of the viewport. const QModelIndex topIndex = indexAt(QPoint(0, 0)); const QModelIndex targetIndex = s->tableViewModel->index(topIndex.row(), newColumnLogicalIndex, topIndex.parent()); scrollTo(targetIndex, EnsureVisible); } void TableViewTreeView::slotHeaderContextMenuActionRemoveColumnTriggered() { qCDebug(DIGIKAM_GENERAL_LOG) << "remove column " << d->headerContextMenuActiveColumn; s->tableViewModel->removeColumnAt(d->headerContextMenuActiveColumn); } void TableViewTreeView::slotHeaderContextMenuConfigureColumn() { TableViewConfigurationDialog* const configurationDialog = new TableViewConfigurationDialog(s, d->headerContextMenuActiveColumn, this); const int result = configurationDialog->exec(); if (result!=QDialog::Accepted) { return; } const TableViewColumnConfiguration newConfiguration = configurationDialog->getNewConfiguration(); s->tableViewModel->getColumnObject(d->headerContextMenuActiveColumn)->setConfiguration(newConfiguration); } AbstractItemDragDropHandler* TableViewTreeView::dragDropHandler() const { qCDebug(DIGIKAM_GENERAL_LOG)<imageModel->dragDropHandler(); return s->imageModel->dragDropHandler(); } QModelIndex TableViewTreeView::mapIndexForDragDrop(const QModelIndex& index) const { // "index" is a TableViewModel index. // We are using the drag-drop-handler of ItemModel, thus // we have to convert it to an index of ItemModel. // map to ItemModel const QModelIndex imageModelIndex = s->tableViewModel->toItemModelIndex(index); return imageModelIndex; } QPixmap TableViewTreeView::pixmapForDrag(const QList< QModelIndex >& indexes) const { const QModelIndex& firstIndex = indexes.at(0); const ItemInfo info = s->tableViewModel->imageInfo(firstIndex); const QString path = info.filePath(); QPixmap thumbnailPixmap; /// @todo The first thumbnail load always fails. We have to add thumbnail pre-generation /// like in ItemModel. Getting thumbnails from ItemModel does not help, because it /// does not necessarily prepare them the same way. /// @todo Make a central drag-drop thumbnail generator? if (!s->thumbnailLoadThread->find(info.thumbnailIdentifier(), thumbnailPixmap, d->dragDropThumbnailSize.size())) { /// @todo better default pixmap? thumbnailPixmap.fill(); } /// @todo Decorate the pixmap like the other drag-drop implementations? /// @todo Write number of images onto the pixmap return thumbnailPixmap; // const QModelIndex& firstIndex = indexes.at(0); // const QModelIndex& imageModelIndex = s->sortModel->toItemModelIndex(firstIndex); // ItemModel* const imageModel = s->imageFilterModel->sourceItemModel(); // // /// @todo Determine how other views choose the size // const QSize thumbnailSize(60, 60); // // imageModel->setData(imageModelIndex, qMax(thumbnailSize.width(), thumbnailSize.height()), ItemModel::ThumbnailRole); // QVariant thumbnailData = imageModel->data(imageModelIndex, ItemModel::ThumbnailRole); // imageModel->setData(imageModelIndex, QVariant(), ItemModel::ThumbnailRole); // // QPixmap thumbnailPixmap = thumbnailData.value(); // // /// @todo Write number of images onto the pixmap // return thumbnailPixmap; } Album* TableViewTreeView::albumAt(const QPoint& pos) const { Q_UNUSED(pos) ItemAlbumModel* const albumModel = qobject_cast(s->imageModel); if (albumModel) { - if(!(albumModel->currentAlbums().isEmpty())) + if (!(albumModel->currentAlbums().isEmpty())) return albumModel->currentAlbums().first(); } return 0; } void TableViewTreeView::wheelEvent(QWheelEvent* event) { if (event->modifiers() & Qt::ControlModifier) { const int delta = event->delta(); if (delta > 0) { emit(signalZoomInStep()); } else if (delta < 0) { emit(signalZoomOutStep()); } event->accept(); return; } QTreeView::wheelEvent(event); } bool TableViewTreeView::hasHiddenGroupedImages(const ItemInfo& info) const { return (info.hasGroupedImages() && (s->tableViewModel->groupingMode() == s->tableViewModel->GroupingMode::GroupingHideGrouped || (s->tableViewModel->groupingMode() == s->tableViewModel->GroupingMode::GroupingShowSubItems && !s->treeView->isExpanded(s->tableViewModel->indexFromImageId(info.id(), 0))))); } void TableViewTreeView::slotModelGroupingModeChanged() { setRootIsDecorated(s->tableViewModel->groupingMode()==TableViewModel::GroupingShowSubItems); } } // namespace Digikam diff --git a/core/dplugins/generic/import/dscanner/scandialog.cpp b/core/dplugins/generic/import/dscanner/scandialog.cpp index bdbf9c74f4..2759536aae 100644 --- a/core/dplugins/generic/import/dscanner/scandialog.cpp +++ b/core/dplugins/generic/import/dscanner/scandialog.cpp @@ -1,272 +1,272 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2007-09-09 * Description : scanner dialog * * Copyright (C) 2007-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 "scandialog.h" // Qt includes #include #include #include #include #include #include #include #include #include #include // KDE includes #include #include // LibKSane includes #include // Local includes #include "digikam_debug.h" #include "saveimgthread.h" #include "statusprogressbar.h" #include "dxmlguiwindow.h" #include "dfiledialog.h" namespace DigikamGenericDScannerPlugin { class Q_DECL_HIDDEN ScanDialog::Private { public: explicit Private() { progress = 0; saneWidget = 0; } QString targetDir; StatusProgressBar* progress; KSaneWidget* saneWidget; }; ScanDialog::ScanDialog(KSaneWidget* const saneWdg, QWidget* const parent) : DPluginDialog(parent, QLatin1String("Scan Tool Dialog")), d(new Private) { setWindowTitle(i18n("Scan Image")); setModal(false); d->saneWidget = saneWdg; d->progress = new StatusProgressBar(this); d->progress->setProgressBarMode(StatusProgressBar::ProgressBarMode); d->progress->setProgressTotalSteps(100); d->progress->setNotify(true); d->progress->setNotificationTitle(i18n("Scan Images"), QIcon::fromTheme(QLatin1String("scanner"))); QVBoxLayout* const vbx = new QVBoxLayout(this); vbx->addWidget(d->saneWidget, 10); vbx->addWidget(d->progress); setLayout(vbx); // ------------------------------------------------------------------------ connect(d->saneWidget, SIGNAL(imageReady(QByteArray&,int,int,int,int)), this, SLOT(slotSaveImage(QByteArray&,int,int,int,int))); connect(this, &QDialog::finished, this, &ScanDialog::slotDialogFinished); } ScanDialog::~ScanDialog() { delete d; } void ScanDialog::setTargetDir(const QString& targetDir) { d->targetDir = targetDir; } void ScanDialog::closeEvent(QCloseEvent* e) { if (!e) { return; } slotDialogFinished(); e->accept(); } void ScanDialog::slotDialogFinished() { d->saneWidget->closeDevice(); } void ScanDialog::slotSaveImage(QByteArray& ksane_data, int width, int height, int bytes_per_line, int ksaneformat) { QStringList writableMimetypes; QList supported = QImageWriter::supportedMimeTypes(); foreach (QByteArray mimeType, supported) { writableMimetypes.append(QLatin1String(mimeType)); } // Put first class citizens at first place writableMimetypes.removeAll(QLatin1String("image/jpeg")); writableMimetypes.removeAll(QLatin1String("image/tiff")); writableMimetypes.removeAll(QLatin1String("image/png")); writableMimetypes.insert(0, QLatin1String("image/png")); writableMimetypes.insert(1, QLatin1String("image/jpeg")); writableMimetypes.insert(2, QLatin1String("image/tiff")); qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "slotSaveImage: Offered mimetypes: " << writableMimetypes; QLatin1String defaultMimeType("image/png"); QLatin1String defaultFileName("image.png"); QPointer imageFileSaveDialog = new DFileDialog(0, i18n("New Image File Name"), d->targetDir); imageFileSaveDialog->setAcceptMode(QFileDialog::AcceptSave); imageFileSaveDialog->setMimeTypeFilters(writableMimetypes); imageFileSaveDialog->selectMimeTypeFilter(defaultMimeType); imageFileSaveDialog->selectFile(defaultFileName); // Start dialog and check if canceled. if (imageFileSaveDialog->exec() != QDialog::Accepted) { delete imageFileSaveDialog; return; } QUrl newURL = imageFileSaveDialog->selectedUrls().at(0); QFileInfo fi(newURL.toLocalFile()); // Parse name filter and extract file extension QString selectedFilterString = imageFileSaveDialog->selectedNameFilter(); QLatin1String triggerString("*."); int triggerPos = selectedFilterString.lastIndexOf(triggerString); QString format; if (triggerPos != -1) { format = selectedFilterString.mid(triggerPos + triggerString.size()); format = format.left(format.size() - 1); format = format.toUpper(); } // If name filter was selected, we guess image type using file extension. if (format.isEmpty()) { format = fi.suffix().toUpper(); QList imgExtList = QImageWriter::supportedImageFormats(); imgExtList << "TIF"; imgExtList << "TIFF"; imgExtList << "JPG"; imgExtList << "JPE"; if (!imgExtList.contains(format.toLatin1()) && !imgExtList.contains(format.toLower().toLatin1())) { QMessageBox::critical(0, i18n("Unsupported Format"), i18n("The target image file format \"%1\" is unsupported.", format)); qCWarning(DIGIKAM_DPLUGIN_GENERIC_LOG) << "target image file format " << format << " is unsupported!"; delete imageFileSaveDialog; return; } } if (!newURL.isValid()) { QMessageBox::critical(0, i18n("Cannot Save File"), i18n("Failed to save file\n\"%1\" to\n\"%2\".", newURL.fileName(), QDir::toNativeSeparators(newURL.toLocalFile().section(QLatin1Char('/'), -2, -2)))); qCWarning(DIGIKAM_DPLUGIN_GENERIC_LOG) << "target URL is not valid !"; delete imageFileSaveDialog; return; } // Check for overwrite ---------------------------------------------------------- - if ( fi.exists() ) + if (fi.exists()) { int result = QMessageBox::warning(0, i18n("Overwrite File?"), i18n("A file named \"%1\" already " "exists. Are you sure you want " "to overwrite it?", newURL.fileName()), QMessageBox::Yes | QMessageBox::No); if (result != QMessageBox::Yes) { delete imageFileSaveDialog; return; } } delete imageFileSaveDialog; QApplication::setOverrideCursor(Qt::WaitCursor); setEnabled(false); // Perform saving --------------------------------------------------------------- SaveImgThread* const thread = new SaveImgThread(this); connect(thread, SIGNAL(signalProgress(QUrl,int)), this, SLOT(slotThreadProgress(QUrl,int))); connect(thread, SIGNAL(signalComplete(QUrl,bool)), this, SLOT(slotThreadDone(QUrl,bool))); thread->setImageData(ksane_data, width, height, bytes_per_line, ksaneformat); thread->setTargetFile(newURL, format); thread->setScannerModel(d->saneWidget->make(), d->saneWidget->model()); thread->start(); } void ScanDialog::slotThreadProgress(const QUrl& url, int percent) { d->progress->setProgressText(i18n("Saving file %1 -", url.fileName())); d->progress->setProgressValue(percent); } void ScanDialog::slotThreadDone(const QUrl& url, bool success) { if (!success) { QMessageBox::critical(0, i18n("File Not Saved"), i18n("Cannot save \"%1\" file", url.fileName())); } d->progress->setProgressText(QLatin1String("")); QApplication::restoreOverrideCursor(); setEnabled(true); if (success) { emit signalImportedImage(url); } } } // namespace DigikamGenericDScannerPlugin diff --git a/core/dplugins/generic/metadata/geolocationedit/searches/searchresultmodel.cpp b/core/dplugins/generic/metadata/geolocationedit/searches/searchresultmodel.cpp index 2c044b676c..748bf6c8f5 100644 --- a/core/dplugins/generic/metadata/geolocationedit/searches/searchresultmodel.cpp +++ b/core/dplugins/generic/metadata/geolocationedit/searches/searchresultmodel.cpp @@ -1,362 +1,362 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-06-01 * Description : A widget to search for places. * * Copyright (C) 2010-2019 by Gilles Caulier * Copyright (C) 2010-2011 by Michael G. Hansen * * 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 "searchresultmodel.h" // Qt includes #include #include #include #include // local includes #include "searchbackend.h" #include "gpscommon.h" #include "gpsundocommand.h" #include "gpsitemmodel.h" namespace DigikamGenericGeolocationEditPlugin { static bool RowRangeLessThan(const QPair& a, const QPair& b) { return a.first < b.first; } class Q_DECL_HIDDEN SearchResultModel::Private { public: explicit Private() { markerNormalUrl = QUrl::fromLocalFile(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("digikam/geolocationedit/searchmarker-normal.png"))); markerNormal = QPixmap(markerNormalUrl.toLocalFile()); markerSelectedUrl = QUrl::fromLocalFile(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("digikam/geolocationedit/searchmarker-selected.png"))); markerSelected = QPixmap(markerSelectedUrl.toLocalFile()); selectionModel = 0; } QList searchResults; QUrl markerNormalUrl; QUrl markerSelectedUrl; QPixmap markerNormal; QPixmap markerSelected; QItemSelectionModel* selectionModel; }; SearchResultModel::SearchResultModel(QObject* const parent) : QAbstractItemModel(parent), d(new Private()) { } SearchResultModel::~SearchResultModel() { delete d; } int SearchResultModel::columnCount(const QModelIndex& parent) const { Q_UNUSED(parent) return 1; } bool SearchResultModel::setData(const QModelIndex& index, const QVariant& value, int role) { Q_UNUSED(index) Q_UNUSED(value) Q_UNUSED(role) return false; } QVariant SearchResultModel::data(const QModelIndex& index, int role) const { const int rowNumber = index.row(); if ((rowNumber < 0) || (rowNumber >= d->searchResults.count())) { return QVariant(); } const int columnNumber = index.column(); if (columnNumber == 0) { switch (role) { case Qt::DisplayRole: return d->searchResults.at(rowNumber).result.name; case Qt::DecorationRole: { QPixmap markerIcon; getMarkerIcon(index, 0, 0, &markerIcon, 0); return markerIcon; } default: return QVariant(); } } return QVariant(); } QModelIndex SearchResultModel::index(int row, int column, const QModelIndex& parent) const { if (parent.isValid()) { // there are no child items, only top level items return QModelIndex(); } - if ( (column < 0) || (column >= 1) || (row < 0) || (row >= d->searchResults.count()) ) + if ((column < 0) || (column >= 1) || (row < 0) || (row >= d->searchResults.count())) { return QModelIndex(); } return createIndex(row, column, (void*)0); } QModelIndex SearchResultModel::parent(const QModelIndex& index) const { Q_UNUSED(index) // we have only top level items return QModelIndex(); } int SearchResultModel::rowCount(const QModelIndex& parent) const { if (parent.isValid()) { return 0; } return d->searchResults.count(); } bool SearchResultModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant& value, int role) { Q_UNUSED(section) Q_UNUSED(orientation) Q_UNUSED(value) Q_UNUSED(role) return false; } QVariant SearchResultModel::headerData(int section, Qt::Orientation orientation, int role) const { Q_UNUSED(role) if ((section >= 1) || (orientation != Qt::Horizontal)) { return false; } return QVariant(QLatin1String("Name")); } Qt::ItemFlags SearchResultModel::flags(const QModelIndex& index) const { return QAbstractItemModel::flags(index); } void SearchResultModel::addResults(const SearchBackend::SearchResult::List& results) { // first check which items are not duplicates QList nonDuplicates; for (int i = 0; i < results.count(); ++i) { const SearchBackend::SearchResult& currentResult = results.at(i); bool isDuplicate = false; for (int j = 0; j < d->searchResults.count(); ++j) { if (currentResult.internalId == d->searchResults.at(j).result.internalId) { isDuplicate = true; break; } } if (!isDuplicate) { nonDuplicates << i; } } if (nonDuplicates.isEmpty()) { return; } beginInsertRows(QModelIndex(), d->searchResults.count(), d->searchResults.count()+nonDuplicates.count()-1); for (int i = 0; i < nonDuplicates.count(); ++i) { SearchResultItem item; item.result = results.at(nonDuplicates.at(i)); d->searchResults << item; } endInsertRows(); } SearchResultModel::SearchResultItem SearchResultModel::resultItem(const QModelIndex& index) const { if (!index.isValid()) { return SearchResultItem(); } return d->searchResults.at(index.row()); } bool SearchResultModel::getMarkerIcon(const QModelIndex& index, QPoint* const offset, QSize* const size, QPixmap* const pixmap, QUrl* const url) const { // determine the id of the marker const int markerNumber = index.row(); const bool itemIsSelected = d->selectionModel ? d->selectionModel->isSelected(index) : false; QPixmap markerPixmap = itemIsSelected ? d->markerSelected : d->markerNormal; // if the caller requests a URL and the marker will not get // a special label, return a URL. Otherwise, return a pixmap. const bool returnViaUrl = url && (markerNumber > 26); if (returnViaUrl) { *url = itemIsSelected ? d->markerSelectedUrl : d->markerNormalUrl; if (size) { *size = markerPixmap.size(); } } else { if (markerNumber <= 26) { const QString markerId = QChar('A' + markerNumber); QPainter painter(&markerPixmap); painter.setRenderHint(QPainter::Antialiasing); painter.setPen(Qt::black); QRect textRect(0, 2, markerPixmap.width(), markerPixmap.height()); painter.drawText(textRect, Qt::AlignHCenter, markerId); } *pixmap = markerPixmap; } if (offset) { *offset = QPoint(markerPixmap.width()/2, markerPixmap.height()-1); } return true; } void SearchResultModel::setSelectionModel(QItemSelectionModel* const selectionModel) { d->selectionModel = selectionModel; } void SearchResultModel::clearResults() { beginResetModel(); d->searchResults.clear(); endResetModel(); } void SearchResultModel::removeRowsByIndexes(const QModelIndexList& rowsList) { // extract the row numbers first: QList rowNumbers; foreach(const QModelIndex& index, rowsList) { if (index.isValid()) { rowNumbers << index.row(); } } if (rowNumbers.isEmpty()) { return; } std::sort(rowNumbers.begin(), rowNumbers.end()); // now delete the rows, starting with the last row: for (int i = rowNumbers.count()-1; i >= 0; i--) { const int rowNumber = rowNumbers.at(i); /// @todo This is very slow for several indexes, because the views update after every removal beginRemoveRows(QModelIndex(), rowNumber, rowNumber); d->searchResults.removeAt(rowNumber); endRemoveRows(); } } void SearchResultModel::removeRowsBySelection(const QItemSelection& selectionList) { // extract the row numbers first: QList > rowRanges; foreach(const QItemSelectionRange& range, selectionList) { rowRanges << QPair(range.top(), range.bottom()); } // we expect the ranges to be sorted here std::sort(rowRanges.begin(), rowRanges.end(), RowRangeLessThan); // now delete the rows, starting with the last row: for (int i = rowRanges.count()-1; i >= 0; i--) { const QPair currentRange = rowRanges.at(i); /// @todo This is very slow for several indexes, because the views update after every removal beginRemoveRows(QModelIndex(), currentRange.first, currentRange.second); for (int j = currentRange.second; j >= currentRange.first; j--) { d->searchResults.removeAt(j); } endRemoveRows(); } } } // namespace DigikamGenericGeolocationEditPlugin diff --git a/core/dplugins/generic/view/presentation/audio/presentation_audiopage.cpp b/core/dplugins/generic/view/presentation/audio/presentation_audiopage.cpp index 878ae5f2c6..bcc6ffba33 100644 --- a/core/dplugins/generic/view/presentation/audio/presentation_audiopage.cpp +++ b/core/dplugins/generic/view/presentation/audio/presentation_audiopage.cpp @@ -1,624 +1,623 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2008-09-14 * Description : a presentation tool. * * Copyright (C) 2008-2009 by Valerio Fuoglio * Copyright (C) 2012-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 "presentation_audiopage.h" // Qt includes #include #include #include #include #include // Local includes #include "presentationaudiowidget.h" #include "presentation_mainpage.h" #include "presentationcontainer.h" #include "digikam_debug.h" #include "dfiledialog.h" using namespace Digikam; namespace DigikamGenericPresentationPlugin { SoundtrackPreview::SoundtrackPreview(QWidget* const parent, const QList& urls, PresentationContainer* const sharedData) : QDialog(parent) { setModal(true); setWindowTitle(i18n("Soundtrack preview")); m_playbackWidget = new PresentationAudioWidget(this, urls, sharedData); QDialogButtonBox* const buttonBox = new QDialogButtonBox(QDialogButtonBox::Close, this); connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); QVBoxLayout* const layout = new QVBoxLayout(this); layout->addWidget(m_playbackWidget); layout->addWidget(buttonBox); setLayout(layout); } SoundtrackPreview::~SoundtrackPreview() { } // ------------------------------------------------------------------------------------ class Q_DECL_HIDDEN PresentationAudioPage::Private { public: explicit Private() { tracksTime = 0; sharedData = 0; soundItems = 0; timeMutex = 0; } QList urlList; PresentationContainer* sharedData; QTime totalTime; QTime imageTime; QMap* tracksTime; QMap* soundItems; QMutex* timeMutex; }; PresentationAudioPage::PresentationAudioPage(QWidget* const parent, PresentationContainer* const sharedData) : QWidget(parent), d(new Private) { setupUi(this); d->sharedData = sharedData; d->totalTime = QTime(0, 0, 0); d->imageTime = QTime(0, 0, 0); d->tracksTime = new QMap(); d->soundItems = new QMap(); d->timeMutex = new QMutex(); m_soundtrackTimeLabel->setText(d->totalTime.toString()); m_previewButton->setEnabled(false); m_rememberSoundtrack->setToolTip(i18n("If set, the soundtrack for the current album " "will be saved and restored automatically on the next startup.")); // -------------------------------------------------------- m_SoundFilesButtonUp->setIcon(QIcon::fromTheme(QLatin1String("go-up"))); m_SoundFilesButtonDown->setIcon(QIcon::fromTheme(QLatin1String("go-down"))); m_SoundFilesButtonAdd->setIcon(QIcon::fromTheme(QLatin1String("list-add"))); m_SoundFilesButtonDelete->setIcon(QIcon::fromTheme(QLatin1String("list-remove"))); m_SoundFilesButtonLoad->setIcon(QIcon::fromTheme(QLatin1String("document-open"))); m_SoundFilesButtonSave->setIcon(QIcon::fromTheme(QLatin1String("document-save"))); m_SoundFilesButtonReset->setIcon(QIcon::fromTheme(QLatin1String("edit-clear"))); m_SoundFilesButtonUp->setText(QString()); m_SoundFilesButtonDown->setText(QString()); m_SoundFilesButtonAdd->setText(QString()); m_SoundFilesButtonDelete->setText(QString()); m_SoundFilesButtonLoad->setText(QString()); m_SoundFilesButtonSave->setText(QString()); m_SoundFilesButtonReset->setText(QString()); m_SoundFilesButtonUp->setToolTip(i18n("Move the selected track up in the playlist.")); m_SoundFilesButtonDown->setToolTip(i18n("Move the selected track down in the playlist.")); m_SoundFilesButtonAdd->setToolTip(i18n("Add new tracks to the playlist.")); m_SoundFilesButtonDelete->setToolTip(i18n("Delete the selected track from the playlist.")); m_SoundFilesButtonLoad->setToolTip(i18n("Load playlist from a file.")); m_SoundFilesButtonSave->setToolTip(i18n("Save playlist to a file.")); m_SoundFilesButtonReset->setToolTip(i18n("Clear the playlist.")); // -------------------------------------------------------- connect( m_SoundFilesListBox, SIGNAL(currentRowChanged(int)), this, SLOT(slotSoundFilesSelected(int)) ); connect( m_SoundFilesListBox, SIGNAL(signalAddedDropItems(QList)), this, SLOT(slotAddDropItems(QList))); connect( m_SoundFilesButtonAdd, SIGNAL(clicked()), this, SLOT(slotSoundFilesButtonAdd()) ); connect( m_SoundFilesButtonDelete, SIGNAL(clicked()), this, SLOT(slotSoundFilesButtonDelete()) ); connect( m_SoundFilesButtonUp, SIGNAL(clicked()), this, SLOT(slotSoundFilesButtonUp()) ); connect( m_SoundFilesButtonDown, SIGNAL(clicked()), this, SLOT(slotSoundFilesButtonDown()) ); connect( m_SoundFilesButtonLoad, SIGNAL(clicked()), this, SLOT(slotSoundFilesButtonLoad()) ); connect( m_SoundFilesButtonSave, SIGNAL(clicked()), this, SLOT(slotSoundFilesButtonSave()) ); connect( m_SoundFilesButtonReset, SIGNAL(clicked()), this, SLOT(slotSoundFilesButtonReset()) ); connect( m_previewButton, SIGNAL(clicked()), this, SLOT(slotPreviewButtonClicked())); connect( d->sharedData->mainPage, SIGNAL(signalTotalTimeChanged(QTime)), this, SLOT(slotImageTotalTimeChanged(QTime))); } PresentationAudioPage::~PresentationAudioPage() { delete d->tracksTime; delete d->soundItems; delete d->timeMutex; delete d; } void PresentationAudioPage::readSettings() { m_rememberSoundtrack->setChecked(d->sharedData->soundtrackRememberPlaylist); m_loopCheckBox->setChecked(d->sharedData->soundtrackLoop); m_playCheckBox->setChecked(d->sharedData->soundtrackPlay); connect( d->sharedData->mainPage, SIGNAL(signalTotalTimeChanged(QTime)), this, SLOT(slotImageTotalTimeChanged(QTime)) ); // if tracks are already set in d->sharedData, add them now if (!d->sharedData->soundtrackUrls.isEmpty()) addItems(d->sharedData->soundtrackUrls); updateFileList(); updateTracksNumber(); } void PresentationAudioPage::saveSettings() { d->sharedData->soundtrackRememberPlaylist = m_rememberSoundtrack->isChecked(); d->sharedData->soundtrackLoop = m_loopCheckBox->isChecked(); d->sharedData->soundtrackPlay = m_playCheckBox->isChecked(); d->sharedData->soundtrackUrls = d->urlList; } void PresentationAudioPage::addItems(const QList& fileList) { if (fileList.isEmpty()) return; QList Files = fileList; for (QList::ConstIterator it = Files.constBegin(); it != Files.constEnd(); ++it) { QUrl currentFile = *it; d->sharedData->soundtrackPath = currentFile; PresentationAudioListItem* const item = new PresentationAudioListItem(m_SoundFilesListBox, currentFile); item->setName(currentFile.fileName()); m_SoundFilesListBox->insertItem(m_SoundFilesListBox->count() - 1, item); d->soundItems->insert(currentFile, item); connect(d->soundItems->value(currentFile), SIGNAL(signalTotalTimeReady(QUrl,QTime)), this, SLOT(slotAddNewTime(QUrl,QTime))); d->urlList.append(currentFile); } m_SoundFilesListBox->setCurrentItem(m_SoundFilesListBox->item(m_SoundFilesListBox->count() - 1)) ; slotSoundFilesSelected(m_SoundFilesListBox->currentRow()); m_SoundFilesListBox->scrollToItem(m_SoundFilesListBox->currentItem()); m_previewButton->setEnabled(true); } void PresentationAudioPage::updateTracksNumber() { QTime displayTime(0, 0, 0); int number = m_SoundFilesListBox->count(); - if ( number > 0 ) + if (number > 0) { displayTime = displayTime.addMSecs(1000 * (number - 1)); - for (QMap::iterator it = d->tracksTime->begin(); it != d->tracksTime->end(); ++it) { int hours = it.value().hour() + displayTime.hour(); int mins = it.value().minute() + displayTime.minute(); int secs = it.value().second() + displayTime.second(); /* QTime doesn't get a overflow value in input. They need * to be cut down to size. */ mins = mins + (int)(secs / 60); secs = secs % 60; hours = hours + (int)(mins / 60); displayTime = QTime(hours, mins, secs); } } m_timeLabel->setText(i18ncp("number of tracks and running time", "1 track [%2]", "%1 tracks [%2]", number, displayTime.toString())); m_soundtrackTimeLabel->setText(displayTime.toString()); d->totalTime = displayTime; compareTimes(); } void PresentationAudioPage::updateFileList() { d->urlList = m_SoundFilesListBox->fileUrls(); m_SoundFilesButtonUp->setEnabled(!d->urlList.isEmpty()); m_SoundFilesButtonDown->setEnabled(!d->urlList.isEmpty()); m_SoundFilesButtonDelete->setEnabled(!d->urlList.isEmpty()); m_SoundFilesButtonSave->setEnabled(!d->urlList.isEmpty()); m_SoundFilesButtonReset->setEnabled(!d->urlList.isEmpty()); d->sharedData->soundtrackPlayListNeedsUpdate = true; } void PresentationAudioPage::compareTimes() { QFont statusBarFont = m_statusBarLabel->font(); - if ( d->imageTime > d->totalTime ) + if (d->imageTime > d->totalTime) { m_statusBarLabel->setText(i18n("Slide time is greater than soundtrack time. Suggestion: add more sound files.")); QPalette paletteStatusBar = m_statusBarLabel->palette(); paletteStatusBar.setColor(QPalette::WindowText, Qt::red); m_statusBarLabel->setPalette(paletteStatusBar); QPalette paletteTimeLabel = m_soundtrackTimeLabel->palette(); paletteTimeLabel.setColor(QPalette::WindowText, Qt::red); m_soundtrackTimeLabel->setPalette(paletteTimeLabel); statusBarFont.setItalic(true); } else { m_statusBarLabel->setText(QLatin1String("")); QPalette paletteStatusBar = m_statusBarLabel->palette(); paletteStatusBar.setColor(QPalette::WindowText, Qt::red); m_statusBarLabel->setPalette(paletteStatusBar); QPalette paletteTimeLabel = m_soundtrackTimeLabel->palette(); - if ( d->imageTime < d->totalTime ) + if (d->imageTime < d->totalTime) paletteTimeLabel.setColor(QPalette::WindowText, Qt::black); else paletteTimeLabel.setColor(QPalette::WindowText, Qt::green); m_soundtrackTimeLabel->setPalette(paletteTimeLabel); statusBarFont.setItalic(false); } m_statusBarLabel->setFont(statusBarFont); } void PresentationAudioPage::slotAddNewTime(const QUrl& url, const QTime& trackTime) { d->timeMutex->lock(); d->tracksTime->insert(url, trackTime); updateTracksNumber(); d->timeMutex->unlock(); } void PresentationAudioPage::slotSoundFilesSelected( int row ) { QListWidgetItem* const item = m_SoundFilesListBox->item(row); - if ( !item || m_SoundFilesListBox->count() == 0 ) + if (!item || m_SoundFilesListBox->count() == 0) { return; } } void PresentationAudioPage::slotAddDropItems(const QList& filesUrl) { if (!filesUrl.isEmpty()) { addItems(filesUrl); updateFileList(); } } void PresentationAudioPage::slotSoundFilesButtonAdd() { QPointer dlg = new DFileDialog(this, i18n("Select sound files"), d->sharedData->soundtrackPath.adjusted(QUrl::RemoveFilename).toLocalFile()); QStringList atm; atm << QLatin1String("audio/mp3"); atm << QLatin1String("audio/wav"); atm << QLatin1String("audio/ogg"); atm << QLatin1String("audio/flac"); dlg->setMimeTypeFilters(atm); dlg->setAcceptMode(QFileDialog::AcceptOpen); dlg->setFileMode(QFileDialog::ExistingFiles); dlg->exec(); QList urls = dlg->selectedUrls(); if (!urls.isEmpty()) { addItems(urls); updateFileList(); } delete dlg; } void PresentationAudioPage::slotSoundFilesButtonDelete() { int Index = m_SoundFilesListBox->currentRow(); - if( Index < 0 ) - return; + if (Index < 0) + return; PresentationAudioListItem* const pitem = static_cast(m_SoundFilesListBox->takeItem(Index)); d->urlList.removeAll(pitem->url()); d->soundItems->remove(pitem->url()); d->timeMutex->lock(); d->tracksTime->remove(pitem->url()); updateTracksNumber(); d->timeMutex->unlock(); delete pitem; slotSoundFilesSelected(m_SoundFilesListBox->currentRow()); if (m_SoundFilesListBox->count() == 0) m_previewButton->setEnabled(false); updateFileList(); } void PresentationAudioPage::slotSoundFilesButtonUp() { int Cpt = 0; for (int i = 0 ; i < m_SoundFilesListBox->count() ; ++i) { if (m_SoundFilesListBox->currentRow() == i) ++Cpt; } if (Cpt == 0) { return; } if (Cpt > 1) { QMessageBox::critical(this, QString(), i18n("You can only move image files up one at a time.")); return; } unsigned int Index = m_SoundFilesListBox->currentRow(); if (Index == 0) { return; } PresentationAudioListItem* const pitem = static_cast(m_SoundFilesListBox->takeItem(Index)); m_SoundFilesListBox->insertItem(Index - 1, pitem); m_SoundFilesListBox->setCurrentItem(pitem); updateFileList(); } void PresentationAudioPage::slotSoundFilesButtonDown() { int Cpt = 0; for (int i = 0 ; i < m_SoundFilesListBox->count() ; ++i) { if (m_SoundFilesListBox->currentRow() == i) ++Cpt; } if (Cpt == 0) { return; } if (Cpt > 1) { QMessageBox::critical(this, QString(), i18n("You can only move files down one at a time.")); return; } int Index = m_SoundFilesListBox->currentRow(); if (Index == m_SoundFilesListBox->count()) { return; } PresentationAudioListItem* const pitem = static_cast(m_SoundFilesListBox->takeItem(Index)); m_SoundFilesListBox->insertItem(Index + 1, pitem); m_SoundFilesListBox->setCurrentItem(pitem); updateFileList(); } void PresentationAudioPage::slotSoundFilesButtonLoad() { QPointer dlg = new DFileDialog(this, i18n("Load playlist"), QString(), i18n("Playlist (*.m3u)")); dlg->setAcceptMode(QFileDialog::AcceptOpen); dlg->setFileMode(QFileDialog::ExistingFile); if (dlg->exec() != QDialog::Accepted) { delete dlg; return; } QString filename = dlg->selectedFiles().isEmpty() ? QString() : dlg->selectedFiles().at(0); if (!filename.isEmpty()) { QFile file(filename); if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { QTextStream in(&file); QList playlistFiles; while (!in.atEnd()) { QString line = in.readLine(); // we ignore the extended information of the m3u playlist file if (line.startsWith(QLatin1Char('#')) || line.isEmpty()) continue; QUrl fUrl = QUrl::fromLocalFile(line); if (fUrl.isValid() && fUrl.isLocalFile()) { playlistFiles << fUrl; } } file.close(); if (!playlistFiles.isEmpty()) { m_SoundFilesListBox->clear(); addItems(playlistFiles); updateFileList(); } } } delete dlg; } void PresentationAudioPage::slotSoundFilesButtonSave() { QPointer dlg = new DFileDialog(this, i18n("Save playlist"), QString(), i18n("Playlist (*.m3u)")); dlg->setAcceptMode(QFileDialog::AcceptSave); dlg->setFileMode(QFileDialog::AnyFile); if (dlg->exec() != QDialog::Accepted) { delete dlg; return; } QString filename = dlg->selectedFiles().isEmpty() ? QString() : dlg->selectedFiles().at(0); if (!filename.isEmpty()) { QFile file(filename); if (file.open(QIODevice::WriteOnly | QIODevice::Text)) { QTextStream out(&file); QList playlistFiles = m_SoundFilesListBox->fileUrls(); for (int i = 0; i < playlistFiles.count(); ++i) { QUrl fUrl(playlistFiles.at(i)); if (fUrl.isValid() && fUrl.isLocalFile()) { out << fUrl.toLocalFile() << endl; } } file.close(); } } delete dlg; } void PresentationAudioPage::slotSoundFilesButtonReset() { m_SoundFilesListBox->clear(); updateFileList(); } void PresentationAudioPage::slotPreviewButtonClicked() { QList urlList; for (int i = 0 ; i < m_SoundFilesListBox->count() ; ++i) { PresentationAudioListItem* const pitem = dynamic_cast(m_SoundFilesListBox->item(i)); if (pitem) { QString path = pitem->url().toLocalFile(); if (!QFile::exists(path)) { QMessageBox::critical(this, QString(), i18n("Cannot access file \"%1\". Please check the path is correct.", path)); return; } urlList << pitem->url(); } } - if ( urlList.isEmpty() ) + if (urlList.isEmpty()) { QMessageBox::critical(this, QString(), i18n("Cannot create a preview of an empty file list.")); return; } // Update PresentationContainer from interface saveSettings(); qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "Tracks : " << urlList; QPointer preview = new SoundtrackPreview(this, urlList, d->sharedData); preview->exec(); delete preview; return; } void PresentationAudioPage::slotImageTotalTimeChanged( const QTime& imageTotalTime ) { d->imageTime = imageTotalTime; m_slideTimeLabel->setText(imageTotalTime.toString()); compareTimes(); } } // namespace DigikamGenericPresentationPlugin diff --git a/core/dplugins/generic/view/presentation/dialogs/presentation_mainpage.cpp b/core/dplugins/generic/view/presentation/dialogs/presentation_mainpage.cpp index 400de61827..9a8d8dfc83 100644 --- a/core/dplugins/generic/view/presentation/dialogs/presentation_mainpage.cpp +++ b/core/dplugins/generic/view/presentation/dialogs/presentation_mainpage.cpp @@ -1,497 +1,497 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2008-09-09 * Description : a presentation tool. * * Copyright (C) 2008-2009 by Valerio Fuoglio * Copyright (C) 2009 by Andi Clemens * Copyright (C) 2012-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. * * ============================================================ */ #define ICONSIZE 256 #include "presentation_mainpage.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include // Local includes #include "digikam_config.h" #include "digikam_debug.h" #include "presentationcontainer.h" #include "presentation_advpage.h" #include "presentation_captionpage.h" #include "thumbnailloadthread.h" #include "ditemslist.h" #include "dmetadata.h" #include "presentationwidget.h" #ifdef HAVE_OPENGL # include "presentationgl.h" # include "presentationkb.h" #endif using namespace Digikam; namespace DigikamGenericPresentationPlugin { class Q_DECL_HIDDEN PresentationMainPage::Private { public: explicit Private() { sharedData = 0; imagesFilesListBox = 0; } PresentationContainer* sharedData; QTime totalTime; DItemsList* imagesFilesListBox; }; PresentationMainPage::PresentationMainPage(QWidget* const parent, PresentationContainer* const sharedData) : QWidget(parent), d(new Private) { setupUi(this); d->sharedData = sharedData; // -------------------------------------------------------- QVBoxLayout* const listBoxContainerLayout = new QVBoxLayout; d->imagesFilesListBox = new DItemsList(m_ImagesFilesListBoxContainer, 32); d->imagesFilesListBox->setObjectName(QLatin1String("Presentation ImagesList")); d->imagesFilesListBox->listView()->header()->hide(); d->imagesFilesListBox->enableControlButtons(true); d->imagesFilesListBox->enableDragAndDrop(true); listBoxContainerLayout->addWidget(d->imagesFilesListBox); listBoxContainerLayout->setContentsMargins(QMargins()); listBoxContainerLayout->setSpacing(0); m_ImagesFilesListBoxContainer->setLayout(listBoxContainerLayout); // -------------------------------------------------------- m_previewLabel->setMinimumWidth(ICONSIZE); m_previewLabel->setMinimumHeight(ICONSIZE); #ifdef HAVE_OPENGL m_openglCheckBox->setEnabled(true); #else m_openglCheckBox->setEnabled(false); #endif } PresentationMainPage::~PresentationMainPage() { delete d; } void PresentationMainPage::readSettings() { #ifdef HAVE_OPENGL m_openglCheckBox->setChecked(d->sharedData->opengl); #endif m_delaySpinBox->setValue(d->sharedData->delay); m_printNameCheckBox->setChecked(d->sharedData->printFileName); m_printProgressCheckBox->setChecked(d->sharedData->printProgress); m_printCommentsCheckBox->setChecked(d->sharedData->printFileComments); m_loopCheckBox->setChecked(d->sharedData->loop); m_shuffleCheckBox->setChecked(d->sharedData->shuffle); m_delaySpinBox->setValue(d->sharedData->useMilliseconds ? d->sharedData->delay - : d->sharedData->delay / 1000 ); + : d->sharedData->delay / 1000); slotUseMillisecondsToggled(); // -------------------------------------------------------- setupConnections(); slotOpenGLToggled(); slotPrintCommentsToggled(); slotEffectChanged(); addItems(d->sharedData->urlList); } void PresentationMainPage::saveSettings() { #ifdef HAVE_OPENGL d->sharedData->opengl = m_openglCheckBox->isChecked(); #endif d->sharedData->delay = d->sharedData->useMilliseconds ? m_delaySpinBox->value() : m_delaySpinBox->value() * 1000; d->sharedData->printFileName = m_printNameCheckBox->isChecked(); d->sharedData->printProgress = m_printProgressCheckBox->isChecked(); d->sharedData->printFileComments = m_printCommentsCheckBox->isChecked(); d->sharedData->loop = m_loopCheckBox->isChecked(); d->sharedData->shuffle = m_shuffleCheckBox->isChecked(); if (!m_openglCheckBox->isChecked()) { QString effect; QMap effectNames = PresentationWidget::effectNamesI18N(); QMap::ConstIterator it; for (it = effectNames.constBegin(); it != effectNames.constEnd(); ++it) { if (it.value() == m_effectsComboBox->currentText()) { effect = it.key(); break; } } d->sharedData->effectName = effect; } #ifdef HAVE_OPENGL else { QMap effects; QMap effectNames; QMap::ConstIterator it; // Load slideshowgl effects effectNames = PresentationGL::effectNamesI18N(); for (it = effectNames.constBegin(); it != effectNames.constEnd(); ++it) { effects.insert(it.key(), it.value()); } // Load Ken Burns effect effectNames = PresentationKB::effectNamesI18N(); for (it = effectNames.constBegin(); it != effectNames.constEnd(); ++it) { effects.insert(it.key(), it.value()); } QString effect; for (it = effects.constBegin(); it != effects.constEnd(); ++it) { - if ( it.value() == m_effectsComboBox->currentText()) + if (it.value() == m_effectsComboBox->currentText()) { effect = it.key(); break; } } d->sharedData->effectNameGL = effect; } #endif } void PresentationMainPage::showNumberImages() { int numberOfImages = d->imagesFilesListBox->imageUrls().count(); QTime totalDuration(0, 0, 0); int transitionDuration = 2000; #ifdef HAVE_OPENGL - if ( m_openglCheckBox->isChecked() ) + if (m_openglCheckBox->isChecked()) transitionDuration += 500; #endif if (numberOfImages != 0) { if (d->sharedData->useMilliseconds) { totalDuration = totalDuration.addMSecs(numberOfImages * m_delaySpinBox->text().toInt()); } else { totalDuration = totalDuration.addSecs(numberOfImages * m_delaySpinBox->text().toInt()); } totalDuration = totalDuration.addMSecs((numberOfImages - 1) * transitionDuration); } d->totalTime = totalDuration; // Notify total time is changed emit signalTotalTimeChanged(d->totalTime); m_label6->setText(i18np("%1 image [%2]", "%1 images [%2]", numberOfImages, totalDuration.toString())); } void PresentationMainPage::loadEffectNames() { m_effectsComboBox->clear(); QMap effectNames = PresentationWidget::effectNamesI18N(); QStringList effects; QMap::Iterator it; for (it = effectNames.begin(); it != effectNames.end(); ++it) { effects.append(it.value()); } m_effectsComboBox->insertItems(0, effects); for (int i = 0; i < m_effectsComboBox->count(); ++i) { if (effectNames[d->sharedData->effectName] == m_effectsComboBox->itemText(i)) { m_effectsComboBox->setCurrentIndex(i); break; } } } void PresentationMainPage::loadEffectNamesGL() { #ifdef HAVE_OPENGL m_effectsComboBox->clear(); QStringList effects; QMap effectNames; QMap::Iterator it; // Load slideshowgl effects effectNames = PresentationGL::effectNamesI18N(); // Add Ken Burns effect effectNames.unite(PresentationKB::effectNamesI18N()); for (it = effectNames.begin(); it != effectNames.end(); ++it) { effects.append(it.value()); } // Update GUI effects.sort(); m_effectsComboBox->insertItems(0, effects); for (int i = 0; i < m_effectsComboBox->count(); ++i) { if (effectNames[d->sharedData->effectNameGL] == m_effectsComboBox->itemText(i)) { m_effectsComboBox->setCurrentIndex(i); break; } } #endif } bool PresentationMainPage::updateUrlList() { d->sharedData->urlList.clear(); QTreeWidgetItemIterator it(d->imagesFilesListBox->listView()); while (*it) { DItemsListViewItem* const item = dynamic_cast(*it); if (!item) continue; if (!QFile::exists(item->url().toLocalFile())) { QMessageBox::critical(this, i18n("Error"), i18n("Cannot access file %1. Please check the path is correct.", item->url().toLocalFile())); return false; } d->sharedData->urlList.append(item->url()); // Input images files. ++it; } return true; } void PresentationMainPage::slotImagesFilesSelected(QTreeWidgetItem* item) { if (!item || d->imagesFilesListBox->imageUrls().isEmpty()) { m_previewLabel->setPixmap(QPixmap()); m_label7->setText(QLatin1String("")); return; } DItemsListViewItem* const pitem = dynamic_cast(item); if (!pitem) return; connect(ThumbnailLoadThread::defaultThread(), SIGNAL(signalThumbnailLoaded(LoadingDescription,QPixmap)), this, SLOT(slotThumbnail(LoadingDescription,QPixmap))); ThumbnailLoadThread::defaultThread()->find(ThumbnailIdentifier(pitem->url().toLocalFile())); QModelIndex index = d->imagesFilesListBox->listView()->currentIndex(); if (index.isValid()) { int rowindex = index.row(); m_label7->setText(i18nc("Image number %1", "Image #%1", rowindex + 1)); } } void PresentationMainPage::addItems(const QList& fileList) { if (fileList.isEmpty()) return; QList files = fileList; d->imagesFilesListBox->slotAddImages(files); slotImagesFilesSelected(d->imagesFilesListBox->listView()->currentItem()); } void PresentationMainPage::slotOpenGLToggled() { if (m_openglCheckBox->isChecked()) { loadEffectNamesGL(); } else { loadEffectNames(); } showNumberImages(); slotEffectChanged(); } void PresentationMainPage::slotEffectChanged() { bool isKB = m_effectsComboBox->currentText() == i18n("Ken Burns"); m_printNameCheckBox->setEnabled(!isKB); m_printProgressCheckBox->setEnabled(!isKB); m_printCommentsCheckBox->setEnabled(!isKB); #ifdef HAVE_OPENGL d->sharedData->advancedPage->m_openGlFullScale->setEnabled(!isKB && m_openglCheckBox->isChecked()); #endif d->sharedData->captionPage->setEnabled((!isKB) && m_printCommentsCheckBox->isChecked()); } -void PresentationMainPage::slotDelayChanged( int delay ) +void PresentationMainPage::slotDelayChanged(int delay) { d->sharedData->delay = d->sharedData->useMilliseconds ? delay : delay * 1000; showNumberImages(); } void PresentationMainPage::slotUseMillisecondsToggled() { int delay = d->sharedData->delay; - if ( d->sharedData->useMilliseconds ) + if (d->sharedData->useMilliseconds) { m_delayLabel->setText(i18n("Delay between images (ms):")); m_delaySpinBox->setRange(d->sharedData->delayMsMinValue, d->sharedData->delayMsMaxValue); m_delaySpinBox->setSingleStep(d->sharedData->delayMsLineStep); } else { m_delayLabel->setText(i18n("Delay between images (s):")); - m_delaySpinBox->setRange(d->sharedData->delayMsMinValue / 100, d->sharedData->delayMsMaxValue / 1000 ); + m_delaySpinBox->setRange(d->sharedData->delayMsMinValue / 100, d->sharedData->delayMsMaxValue / 1000); m_delaySpinBox->setSingleStep(d->sharedData->delayMsLineStep / 100); delay /= 1000; } m_delaySpinBox->setValue(delay); } void PresentationMainPage::slotPortfolioDurationChanged(int) { showNumberImages(); - emit signalTotalTimeChanged( d->totalTime ); + emit signalTotalTimeChanged(d->totalTime); } void PresentationMainPage::slotThumbnail(const LoadingDescription& /*desc*/, const QPixmap& pix) { if (pix.isNull()) { m_previewLabel->setPixmap(QIcon::fromTheme(QLatin1String("view-preview")).pixmap(ICONSIZE, QIcon::Disabled)); } else { m_previewLabel->setPixmap(pix.scaled(ICONSIZE, ICONSIZE, Qt::KeepAspectRatio)); } disconnect(ThumbnailLoadThread::defaultThread(), 0, this, 0); } void PresentationMainPage::slotPrintCommentsToggled() { d->sharedData->printFileComments = m_printCommentsCheckBox->isChecked(); d->sharedData->captionPage->setEnabled(m_printCommentsCheckBox->isChecked()); } void PresentationMainPage::slotImageListChanged() { showNumberImages(); slotImagesFilesSelected(d->imagesFilesListBox->listView()->currentItem()); } void PresentationMainPage::setupConnections() { connect(d->sharedData->advancedPage, SIGNAL(useMillisecondsToggled()), this, SLOT(slotUseMillisecondsToggled())); connect(m_printCommentsCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotPrintCommentsToggled())); connect(m_openglCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotOpenGLToggled())); connect(m_delaySpinBox, SIGNAL(valueChanged(int)), this, SLOT(slotDelayChanged(int))); connect(m_effectsComboBox, SIGNAL(activated(int)), this, SLOT(slotEffectChanged())); connect(d->imagesFilesListBox, SIGNAL(signalImageListChanged()), this, SLOT(slotImageListChanged())); connect(d->imagesFilesListBox, SIGNAL(signalItemClicked(QTreeWidgetItem*)), this, SLOT(slotImagesFilesSelected(QTreeWidgetItem*))); } } // namespace DigikamGenericPresentationPlugin diff --git a/core/dplugins/generic/view/presentation/dialogs/presentationdlg.cpp b/core/dplugins/generic/view/presentation/dialogs/presentationdlg.cpp index 251bb38cd2..dbbad4acc8 100644 --- a/core/dplugins/generic/view/presentation/dialogs/presentationdlg.cpp +++ b/core/dplugins/generic/view/presentation/dialogs/presentationdlg.cpp @@ -1,313 +1,313 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2008-09-09 * Description : a presentation tool. * * Copyright (C) 2008-2009 by Valerio Fuoglio * Copyright (C) 2009 by Andi Clemens * Copyright (C) 2012-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 "presentationdlg.h" // Qt includes #include #include #include #include #include #include #include // KDE includes #include #include // Local includes #include "digikam_config.h" #include "digikam_debug.h" #include "presentationcontainer.h" #include "presentation_mainpage.h" #include "presentation_captionpage.h" #include "presentation_advpage.h" #include "thumbnailloadthread.h" #ifdef HAVE_MEDIAPLAYER # include "presentation_audiopage.h" #endif namespace DigikamGenericPresentationPlugin { class Q_DECL_HIDDEN PresentationDlg::Private { public: explicit Private() { buttonBox = 0; startButton = 0; tab = 0; sharedData = 0; } QDialogButtonBox* buttonBox; QPushButton* startButton; QTabWidget* tab; PresentationContainer* sharedData; }; PresentationDlg::PresentationDlg(QWidget* const parent, PresentationContainer* const sharedData) : DPluginDialog(parent, QLatin1String("Presentation Settings")), d(new Private) { setWindowTitle(i18n("Presentation")); d->sharedData = sharedData; m_buttons->addButton(QDialogButtonBox::Close); m_buttons->addButton(QDialogButtonBox::Ok); m_buttons->button(QDialogButtonBox::Ok)->setText(i18n("Start")); m_buttons->button(QDialogButtonBox::Ok)->setToolTip(i18nc("@info:tooltip", "Start Presentation")); m_buttons->button(QDialogButtonBox::Ok)->setIcon(QIcon::fromTheme(QLatin1String("media-playback-start"))); m_buttons->button(QDialogButtonBox::Ok)->setDefault(true); setModal(true); // --- Pages settings --- d->tab = new QTabWidget(this); d->sharedData->mainPage = new PresentationMainPage(this, d->sharedData); d->tab->addTab(d->sharedData->mainPage, QIcon::fromTheme(QLatin1String("view-presentation")), i18n("Main Settings")); d->sharedData->captionPage = new PresentationCaptionPage(this, d->sharedData); d->tab->addTab(d->sharedData->captionPage, QIcon::fromTheme(QLatin1String("draw-freehand")), i18nc("captions for the slideshow", "Caption")); #ifdef HAVE_MEDIAPLAYER d->sharedData->soundtrackPage = new PresentationAudioPage(this, d->sharedData); d->tab->addTab(d->sharedData->soundtrackPage, QIcon::fromTheme(QLatin1String("speaker")), i18n("Soundtrack")); #endif d->sharedData->advancedPage = new PresentationAdvPage(this, d->sharedData); d->tab->addTab(d->sharedData->advancedPage, QIcon::fromTheme(QLatin1String("configure")), i18n("Advanced")); QVBoxLayout* const mainLayout = new QVBoxLayout(this); mainLayout->addWidget(d->tab); mainLayout->addWidget(m_buttons); setLayout(mainLayout); // Slot connections connect(m_buttons->button(QDialogButtonBox::Ok), SIGNAL(clicked()), this, SLOT(slotStartClicked())); connect(m_buttons->button(QDialogButtonBox::Close), SIGNAL(clicked()), this, SLOT(reject())); readSettings(); } PresentationDlg::~PresentationDlg () { delete d; } void PresentationDlg::readSettings() { KConfig config; KConfigGroup grp = config.group(QLatin1String("Presentation Settings")); d->sharedData->opengl = grp.readEntry("OpenGL", false); d->sharedData->openGlFullScale = grp.readEntry("OpenGLFullScale", false); d->sharedData->delay = grp.readEntry("Delay", 1500); d->sharedData->printFileName = grp.readEntry("Print Filename", true); d->sharedData->printProgress = grp.readEntry("Print Progress Indicator", true); d->sharedData->printFileComments = grp.readEntry("Print Comments", false); d->sharedData->loop = grp.readEntry("Loop", false); d->sharedData->shuffle = grp.readEntry("Shuffle", false); d->sharedData->effectName = grp.readEntry("Effect Name", "Random"); d->sharedData->effectNameGL = grp.readEntry("Effect Name (OpenGL)", "Random"); d->sharedData->delayMsMaxValue = 120000; d->sharedData->delayMsMinValue = 100; d->sharedData->delayMsLineStep = 100; // Comments tab settings QFont* const savedFont = new QFont(); savedFont->setFamily( grp.readEntry("Comments Font Family")); - savedFont->setPointSize( grp.readEntry("Comments Font Size", 10 )); + savedFont->setPointSize( grp.readEntry("Comments Font Size", 10)); savedFont->setBold( grp.readEntry("Comments Font Bold", false)); savedFont->setItalic( grp.readEntry("Comments Font Italic", false)); savedFont->setUnderline( grp.readEntry("Comments Font Underline", false)); savedFont->setOverline( grp.readEntry("Comments Font Overline", false)); savedFont->setStrikeOut( grp.readEntry("Comments Font StrikeOut", false)); savedFont->setFixedPitch(grp.readEntry("Comments Font FixedPitch", false)); d->sharedData->captionFont = savedFont; d->sharedData->commentsFontColor = grp.readEntry("Comments Font Color", 0xffffff); d->sharedData->commentsBgColor = grp.readEntry("Comments Bg Color", 0x000000); d->sharedData->commentsDrawOutline = grp.readEntry("Comments Text Outline", true); d->sharedData->bgOpacity = grp.readEntry("Background Opacity", 10); d->sharedData->commentsLinesLength = grp.readEntry("Comments Lines Length", 72); #ifdef HAVE_MEDIAPLAYER // Soundtrack tab d->sharedData->soundtrackLoop = grp.readEntry("Soundtrack Loop", false); d->sharedData->soundtrackPlay = grp.readEntry("Soundtrack Auto Play", false); - d->sharedData->soundtrackPath = QUrl::fromLocalFile(grp.readEntry("Soundtrack Path", "" )); + d->sharedData->soundtrackPath = QUrl::fromLocalFile(grp.readEntry("Soundtrack Path", "")); d->sharedData->soundtrackRememberPlaylist = grp.readEntry("Soundtrack Remember Playlist", false); #endif // Advanced tab d->sharedData->useMilliseconds = grp.readEntry("Use Milliseconds", false); d->sharedData->enableMouseWheel = grp.readEntry("Enable Mouse Wheel", true); d->sharedData->kbDisableFadeInOut = grp.readEntry("KB Disable FadeInOut", false); d->sharedData->kbDisableCrossFade = grp.readEntry("KB Disable Crossfade", false); d->sharedData->enableCache = grp.readEntry("Enable Cache", false); d->sharedData->cacheSize = grp.readEntry("Cache Size", 5); if (d->sharedData->soundtrackRememberPlaylist) { QString groupName(QLatin1String("Presentation Settings") + QLatin1String(" Soundtrack ")); KConfigGroup soundGrp = config.group(groupName); // load and check playlist files, if valid, add to tracklist widget QList playlistFiles = soundGrp.readEntry("Tracks", QList()); foreach (const QUrl& playlistFile, playlistFiles) { QUrl file(playlistFile); QFileInfo fi(file.toLocalFile()); if (fi.isFile()) { d->sharedData->soundtrackUrls << file; } } } d->sharedData->mainPage->readSettings(); d->sharedData->captionPage->readSettings(); d->sharedData->advancedPage->readSettings(); #ifdef HAVE_MEDIAPLAYER d->sharedData->soundtrackPage->readSettings(); #endif } void PresentationDlg::saveSettings() { KConfig config; d->sharedData->mainPage->saveSettings(); d->sharedData->captionPage->saveSettings(); d->sharedData->advancedPage->saveSettings(); #ifdef HAVE_MEDIAPLAYER d->sharedData->soundtrackPage->saveSettings(); #endif KConfigGroup grp = config.group(QLatin1String("Presentation Settings")); grp.writeEntry("OpenGL", d->sharedData->opengl); grp.writeEntry("OpenGLFullScale", d->sharedData->openGlFullScale); grp.writeEntry("Delay", d->sharedData->delay); grp.writeEntry("Print Filename", d->sharedData->printFileName); grp.writeEntry("Print Progress Indicator", d->sharedData->printProgress); grp.writeEntry("Print Comments", d->sharedData->printFileComments); grp.writeEntry("Loop", d->sharedData->loop); grp.writeEntry("Shuffle", d->sharedData->shuffle); grp.writeEntry("Use Milliseconds", d->sharedData->useMilliseconds); grp.writeEntry("Enable Mouse Wheel", d->sharedData->enableMouseWheel); // Comments tab settings QFont* commentsFont = d->sharedData->captionFont; grp.writeEntry("Comments Font Family", commentsFont->family()); grp.writeEntry("Comments Font Size", commentsFont->pointSize()); grp.writeEntry("Comments Font Bold", commentsFont->bold()); grp.writeEntry("Comments Font Italic", commentsFont->italic()); grp.writeEntry("Comments Font Underline", commentsFont->underline()); grp.writeEntry("Comments Font Overline", commentsFont->overline()); grp.writeEntry("Comments Font StrikeOut", commentsFont->strikeOut()); grp.writeEntry("Comments Font FixedPitch", commentsFont->fixedPitch()); grp.writeEntry("Comments Font Color", d->sharedData->commentsFontColor); grp.writeEntry("Comments Bg Color", d->sharedData->commentsBgColor); grp.writeEntry("Comments Text Outline", d->sharedData->commentsDrawOutline); grp.writeEntry("Background Opacity", d->sharedData->bgOpacity); grp.writeEntry("Comments Lines Length", d->sharedData->commentsLinesLength); grp.writeEntry("Effect Name (OpenGL)", d->sharedData->effectNameGL); grp.writeEntry("Effect Name", d->sharedData->effectName); #ifdef HAVE_MEDIAPLAYER // Soundtrack tab grp.writeEntry("Soundtrack Loop", d->sharedData->soundtrackLoop); grp.writeEntry("Soundtrack Auto Play", d->sharedData->soundtrackPlay); grp.writeEntry("Soundtrack Path", d->sharedData->soundtrackPath.toLocalFile()); grp.writeEntry("Soundtrack Remember Playlist", d->sharedData->soundtrackRememberPlaylist); #endif // Advanced settings grp.writeEntry("KB Disable FadeInOut", d->sharedData->kbDisableFadeInOut); grp.writeEntry("KB Disable Crossfade", d->sharedData->kbDisableCrossFade); grp.writeEntry("Enable Cache", d->sharedData->enableCache); grp.writeEntry("Cache Size", d->sharedData->cacheSize); // -------------------------------------------------------- // only save tracks when option is set and tracklist is NOT empty, to prevent deletion // of older track entries if (d->sharedData->soundtrackRememberPlaylist && d->sharedData->soundtrackPlayListNeedsUpdate) { QString groupName(QLatin1String("Presentation Settings") + QLatin1String(" Soundtrack ")); KConfigGroup soundGrp = config.group(groupName); soundGrp.writeEntry("Tracks", d->sharedData->soundtrackUrls); } config.sync(); } void PresentationDlg::slotStartClicked() { saveSettings(); - if ( d->sharedData->mainPage->updateUrlList() ) + if (d->sharedData->mainPage->updateUrlList()) emit buttonStartClicked(); return; } void PresentationDlg::closeEvent(QCloseEvent* e) { saveSettings(); e->accept(); } } // namespace DigikamGenericPresentationPlugin diff --git a/core/dplugins/generic/view/presentation/opengl/kbimageloader.cpp b/core/dplugins/generic/view/presentation/opengl/kbimageloader.cpp index 40854a7c2f..0235027de5 100644 --- a/core/dplugins/generic/view/presentation/opengl/kbimageloader.cpp +++ b/core/dplugins/generic/view/presentation/opengl/kbimageloader.cpp @@ -1,255 +1,255 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2007-11-14 * Description : a presentation tool. * * Copyright (C) 2007-2009 by Valerio Fuoglio * Copyright (C) 2012-2019 by Gilles Caulier * * Parts of this code are based on smoothslidesaver by Carsten Weinhold * * * 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 "kbimageloader.h" // Qt includes #include #include // Local includes #include "dimg.h" #include "iccsettings.h" #include "digikam_debug.h" #include "presentationkb.h" #include "previewloadthread.h" #include "iccsettingscontainer.h" #include "presentationcontainer.h" using namespace Digikam; namespace DigikamGenericPresentationPlugin { class Q_DECL_HIDDEN KBImageLoader::Private { public: explicit Private() { sharedData = 0; fileIndex = 0; width = 0; height = 0; initialized = false; needImage = true; haveImages = false; quitRequested = false; textureAspect = 0.0; } PresentationContainer* sharedData; int fileIndex; int width; int height; QWaitCondition imageRequest; QMutex condLock; QMutex imageLock; bool initialized; bool needImage; bool haveImages; bool quitRequested; float textureAspect; QImage texture; IccProfile iccProfile; }; KBImageLoader::KBImageLoader(PresentationContainer* const sharedData, int width, int height) : QThread(), d(new Private) { d->sharedData = sharedData; d->width = width; d->height = height; ICCSettingsContainer settings = IccSettings::instance()->settings(); if (settings.enableCM && settings.useManagedPreviews) { d->iccProfile = IccProfile(settings.monitorProfile); } } KBImageLoader::~KBImageLoader() { delete d; } void KBImageLoader::quit() { QMutexLocker locker(&d->condLock); d->quitRequested = true; d->imageRequest.wakeOne(); } void KBImageLoader::requestNewImage() { QMutexLocker locker(&d->condLock); if (!d->needImage) { d->needImage = true; d->imageRequest.wakeOne(); } } void KBImageLoader::run() { QMutexLocker locker(&d->condLock); // we enter the loop with d->needImage==true, so we will immediately // try to load an image while (true) { if (d->quitRequested) break; if (d->needImage) { if (d->fileIndex == (int)d->sharedData->urlList.count()) { if (d->sharedData->loop) { d->fileIndex = 0; } else { d->needImage = false; d->haveImages = false; continue; } } d->needImage = false; d->condLock.unlock(); bool ok; do { ok = loadImage(); if (!ok) invalidateCurrentImageName(); } while (!ok && d->fileIndex < (int)d->sharedData->urlList.count()); if (d->fileIndex == (int)d->sharedData->urlList.count()) { d->condLock.lock(); continue; } - if ( !ok) + if (!ok) { // generate a black dummy image d->texture = QImage(128, 128, QImage::Format_ARGB32); d->texture.fill(Qt::black); } d->condLock.lock(); d->fileIndex++; if (!d->initialized) { d->haveImages = ok; d->initialized = true; } } else { // wait for new requests from the consumer d->imageRequest.wait(&d->condLock); } } } bool KBImageLoader::loadImage() { QString path = d->sharedData->urlList[d->fileIndex].toLocalFile(); QImage image = PreviewLoadThread::loadHighQualitySynchronously(path, PreviewSettings::RawPreviewAutomatic, d->iccProfile).copyQImage(); if (image.isNull()) { return false; } d->imageLock.lock(); d->textureAspect = (float)image.width() / (float)image.height(); d->texture = image.scaled(d->width, d->height, Qt::KeepAspectRatio, Qt::SmoothTransformation); d->imageLock.unlock(); return true; } void KBImageLoader::invalidateCurrentImageName() { d->sharedData->urlList.removeAll(d->sharedData->urlList[d->fileIndex]); d->fileIndex++; } bool KBImageLoader::grabImage() { d->imageLock.lock(); return d->haveImages; } void KBImageLoader::ungrabImage() { d->imageLock.unlock(); } bool KBImageLoader::ready() const { return d->initialized; } const QImage& KBImageLoader::image() const { return d->texture; } float KBImageLoader::imageAspect() const { return d->textureAspect; } } // namespace DigikamGenericPresentationPlugin diff --git a/core/dplugins/generic/view/presentation/opengl/presentationgl.cpp b/core/dplugins/generic/view/presentation/opengl/presentationgl.cpp index c1bfc4950f..ec3192c1fa 100644 --- a/core/dplugins/generic/view/presentation/opengl/presentationgl.cpp +++ b/core/dplugins/generic/view/presentation/opengl/presentationgl.cpp @@ -1,1739 +1,1739 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2004-01-19 * Description : a presentation tool. * * Copyright (C) 2004 by Renchi Raju * Copyright (C) 2006-2009 by Valerio Fuoglio * Copyright (C) 2009 by Andi Clemens * Copyright (C) 2012-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 "presentationgl.h" // C++ includes #include #include // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include #include #include // Local includes #include "digikam_config.h" #include "digikam_debug.h" #include "presentationcontainer.h" #include "presentationctrlwidget.h" #include "presentationloader.h" #ifdef HAVE_MEDIAPLAYER # include "presentationaudiowidget.h" #endif using namespace Digikam; namespace DigikamGenericPresentationPlugin { class Q_DECL_HIDDEN PresentationGL::Private { public: explicit Private() { timer = 0; fileIndex = 0; imageLoader = 0; texture[0] = 0; texture[1] = 0; texture[2] = 0; curr = 0; width = 0; height = 0; xMargin = 0; yMargin = 0; effect = 0; tex1First = true; effectRunning = false; timeout = 0; endOfShow = false; random = false; i = 0; dir = 0; slideCtrlWidget = 0; #ifdef HAVE_MEDIAPLAYER playbackWidget = 0; #endif mouseMoveTimer = 0; deskX = 0; deskY = 0; deskWidth = 0; deskHeight = 0; sharedData = 0; } QMap effects; QTimer* timer; int fileIndex; PresentationLoader* imageLoader; QOpenGLTexture* texture[3]; bool tex1First; int curr; int width; int height; int xMargin; int yMargin; EffectMethod effect; bool effectRunning; int timeout; bool random; bool endOfShow; int i; int dir; float points[40][40][3] = {{{0.0}}}; PresentationCtrlWidget* slideCtrlWidget; #ifdef HAVE_MEDIAPLAYER PresentationAudioWidget* playbackWidget; #endif QTimer* mouseMoveTimer; int deskX; int deskY; int deskWidth; int deskHeight; PresentationContainer* sharedData; }; PresentationGL::PresentationGL(PresentationContainer* const sharedData) : QOpenGLWidget(), d(new Private) { setAttribute(Qt::WA_DeleteOnClose); setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::Popup); QRect deskRect = QApplication::desktop()->screenGeometry(QApplication::activeWindow()); d->deskX = deskRect.x(); d->deskY = deskRect.y(); d->deskWidth = deskRect.width(); d->deskHeight = deskRect.height(); move(d->deskX, d->deskY); resize(d->deskWidth, d->deskHeight); d->sharedData = sharedData; d->slideCtrlWidget = new PresentationCtrlWidget(this); d->slideCtrlWidget->hide(); if (!d->sharedData->loop) { d->slideCtrlWidget->setEnabledPrev(false); } connect(d->slideCtrlWidget, SIGNAL(signalPause()), this, SLOT(slotPause())); connect(d->slideCtrlWidget, SIGNAL(signalPlay()), this, SLOT(slotPlay())); connect(d->slideCtrlWidget, SIGNAL(signalNext()), this, SLOT(slotNext())); connect(d->slideCtrlWidget, SIGNAL(signalPrev()), this, SLOT(slotPrev())); connect(d->slideCtrlWidget, SIGNAL(signalClose()), this, SLOT(slotClose())); #ifdef HAVE_MEDIAPLAYER d->playbackWidget = new PresentationAudioWidget(this, d->sharedData->soundtrackUrls, d->sharedData); d->playbackWidget->hide(); d->playbackWidget->move(d->deskX, d->deskY); #endif int w = d->slideCtrlWidget->width(); d->slideCtrlWidget->move(d->deskX + d->deskWidth - w - 1, d->deskY); // -- Minimal texture size (opengl specs) -------------- d->width = 64; d->height = 64; // -- Margin ------------------------------------------- d->xMargin = int (d->deskWidth / d->width); d->yMargin = int (d->deskWidth / d->height); // ------------------------------------------------------------------ d->fileIndex = -1; // start with -1 d->timeout = d->sharedData->delay; d->imageLoader = new PresentationLoader(d->sharedData, width(), height(), d->fileIndex); // -------------------------------------------------- registerEffects(); if (d->sharedData->effectNameGL == QLatin1String("Random")) { d->effect = getRandomEffect(); d->random = true; } else { d->effect = d->effects[d->sharedData->effectNameGL]; if (!d->effect) d->effect = d->effects[QLatin1String("None")]; d->random = false; } // -------------------------------------------------- d->timer = new QTimer(this); connect(d->timer, SIGNAL(timeout()), this, SLOT(slotTimeOut())); d->timer->setSingleShot(true); d->timer->start(10); // -- hide cursor when not moved -------------------- d->mouseMoveTimer = new QTimer(this); d->mouseMoveTimer->setSingleShot(true); connect(d->mouseMoveTimer, SIGNAL(timeout()), this, SLOT(slotMouseMoveTimeOut())); setMouseTracking(true); slotMouseMoveTimeOut(); #ifdef HAVE_MEDIAPLAYER if (d->sharedData->soundtrackPlay) d->playbackWidget->slotPlay(); #endif } PresentationGL::~PresentationGL() { #ifdef HAVE_MEDIAPLAYER d->playbackWidget->slotStop(); #endif d->timer->stop(); d->mouseMoveTimer->stop(); d->texture[0]->destroy(); d->texture[1]->destroy(); d->texture[2]->destroy(); delete d->texture[0]; delete d->texture[1]; delete d->texture[2]; delete d->imageLoader; delete d; } void PresentationGL::initializeGL() { // Enable Texture Mapping glEnable(GL_TEXTURE_2D); // Clear The Background Color glClearColor(0.0, 0.0, 0.0, 1.0f); // Turn Blending On glEnable(GL_BLEND); // Blending Function For Translucency Based On Source Alpha Value glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // Enable perspective vision glClearDepth(1.0f); // get the maximum texture value. GLint maxTexVal; glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTexVal); // allow only maximum texture value of 1024. anything bigger and things slow down maxTexVal = qMin(1024, maxTexVal); d->width = QApplication::desktop()->width(); d->height = QApplication::desktop()->height(); d->width = 1 << (int)ceil(log((float)d->width) / log((float)2)) ; d->height = 1 << (int)ceil(log((float)d->height) / log((float)2)); d->width = qMin(maxTexVal, d->width); d->height = qMin(maxTexVal, d->height); d->texture[0] = new QOpenGLTexture(QOpenGLTexture::Target2D); d->texture[1] = new QOpenGLTexture(QOpenGLTexture::Target2D); d->texture[2] = new QOpenGLTexture(QOpenGLTexture::Target2D); // end screen texture } void PresentationGL::paintGL() { glDisable(GL_DEPTH_TEST); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glLoadIdentity(); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); if (d->endOfShow) showEndOfShow(); else { if (d->effectRunning && d->effect) (this->*d->effect)(); else paintTexture(); } } void PresentationGL::resizeGL(int w, int h) { // Reset The Current Viewport And Perspective Transformation glViewport(0, 0, (GLint)w, (GLint)h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); } void PresentationGL::keyPressEvent(QKeyEvent* event) { if (!event) return; d->slideCtrlWidget->keyPressEvent(event); #ifdef HAVE_MEDIAPLAYER d->playbackWidget->keyPressEvent(event); #endif } void PresentationGL::mousePressEvent(QMouseEvent* e) { if (d->endOfShow) slotClose(); if (e->button() == Qt::LeftButton) { d->timer->stop(); d->slideCtrlWidget->setPaused(true); slotNext(); } else if (e->button() == Qt::RightButton && d->fileIndex - 1 >= 0) { d->timer->stop(); d->slideCtrlWidget->setPaused(true); slotPrev(); } } void PresentationGL::mouseMoveEvent(QMouseEvent* e) { setCursor(QCursor(Qt::ArrowCursor)); d->mouseMoveTimer->start(1000); if (!d->slideCtrlWidget->canHide() #ifdef HAVE_MEDIAPLAYER || !d->playbackWidget->canHide() #endif ) return; QPoint pos(e->pos()); if ((pos.y() > (d->deskY + 20)) && (pos.y() < (d->deskY + d->deskHeight - 20 - 1))) { if (d->slideCtrlWidget->isHidden() #ifdef HAVE_MEDIAPLAYER || d->playbackWidget->isHidden() #endif ) { return; } else { d->slideCtrlWidget->hide(); #ifdef HAVE_MEDIAPLAYER d->playbackWidget->hide(); #endif setFocus(); } return; } d->slideCtrlWidget->show(); #ifdef HAVE_MEDIAPLAYER d->playbackWidget->show(); #endif } void PresentationGL::wheelEvent(QWheelEvent* e) { if (!d->sharedData->enableMouseWheel) return; if (d->endOfShow) slotClose(); int delta = e->delta(); if (delta < 0) { d->timer->stop(); d->slideCtrlWidget->setPaused(true); slotNext(); } else if (delta > 0 && d->fileIndex - 1 >= 0) { d->timer->stop(); d->slideCtrlWidget->setPaused(true); slotPrev(); } } void PresentationGL::registerEffects() { d->effects.insert(QLatin1String("None"), &PresentationGL::effectNone); d->effects.insert(QLatin1String("Blend"), &PresentationGL::effectBlend); d->effects.insert(QLatin1String("Fade"), &PresentationGL::effectFade); d->effects.insert(QLatin1String("Rotate"), &PresentationGL::effectRotate); d->effects.insert(QLatin1String("Bend"), &PresentationGL::effectBend); d->effects.insert(QLatin1String("In Out"), &PresentationGL::effectInOut); d->effects.insert(QLatin1String("Slide"), &PresentationGL::effectSlide); d->effects.insert(QLatin1String("Flutter"), &PresentationGL::effectFlutter); d->effects.insert(QLatin1String("Cube"), &PresentationGL::effectCube); } QStringList PresentationGL::effectNames() { QStringList effects; effects.append(QLatin1String("None")); effects.append(QLatin1String("Bend")); effects.append(QLatin1String("Blend")); effects.append(QLatin1String("Cube")); effects.append(QLatin1String("Fade")); effects.append(QLatin1String("Flutter")); effects.append(QLatin1String("In Out")); effects.append(QLatin1String("Rotate")); effects.append(QLatin1String("Slide")); effects.append(QLatin1String("Random")); return effects; } QMap PresentationGL::effectNamesI18N() { QMap effects; effects[QLatin1String("None")] = i18nc("Filter Effect: No effect", "None"); effects[QLatin1String("Bend")] = i18nc("Filter Effect: Bend", "Bend"); effects[QLatin1String("Blend")] = i18nc("Filter Effect: Blend", "Blend"); effects[QLatin1String("Cube")] = i18nc("Filter Effect: Cube", "Cube"); effects[QLatin1String("Fade")] = i18nc("Filter Effect: Fade", "Fade"); effects[QLatin1String("Flutter")] = i18nc("Filter Effect: Flutter", "Flutter"); effects[QLatin1String("In Out")] = i18nc("Filter Effect: In Out", "In Out"); effects[QLatin1String("Rotate")] = i18nc("Filter Effect: Rotate", "Rotate"); effects[QLatin1String("Slide")] = i18nc("Filter Effect: Slide", "Slide"); effects[QLatin1String("Random")] = i18nc("Filter Effect: Random effect", "Random"); return effects; } PresentationGL::EffectMethod PresentationGL::getRandomEffect() { QMap tmpMap(d->effects); tmpMap.remove(QLatin1String("None")); QStringList t = tmpMap.keys(); int count = t.count(); int i = (int)((float)(count) * qrand() / (RAND_MAX + 1.0)); QString key = t[i]; return tmpMap[key]; } void PresentationGL::advanceFrame() { d->fileIndex++; d->imageLoader->next(); int num = d->sharedData->urlList.count(); if (d->fileIndex >= num) { if (d->sharedData->loop) { d->fileIndex = 0; } else { d->fileIndex = num - 1; d->endOfShow = true; d->slideCtrlWidget->setEnabledPlay(false); d->slideCtrlWidget->setEnabledNext(false); d->slideCtrlWidget->setEnabledPrev(false); } } if (!d->sharedData->loop && !d->endOfShow) { d->slideCtrlWidget->setEnabledPrev(d->fileIndex > 0); d->slideCtrlWidget->setEnabledNext(d->fileIndex < num - 1); } d->tex1First = !d->tex1First; d->curr = (d->curr == 0) ? 1 : 0; } void PresentationGL::previousFrame() { d->fileIndex--; d->imageLoader->prev(); int num = d->sharedData->urlList.count(); if (d->fileIndex < 0) { if (d->sharedData->loop) { d->fileIndex = num - 1; } else { d->fileIndex = 0; d->endOfShow = true; d->slideCtrlWidget->setEnabledPlay(false); d->slideCtrlWidget->setEnabledNext(false); d->slideCtrlWidget->setEnabledPrev(false); } } if (!d->sharedData->loop && !d->endOfShow) { d->slideCtrlWidget->setEnabledPrev(d->fileIndex > 0); d->slideCtrlWidget->setEnabledNext(d->fileIndex < num - 1); } d->tex1First = !d->tex1First; d->curr = (d->curr == 0) ? 1 : 0; } void PresentationGL::loadImage() { QImage image = d->imageLoader->getCurrent(); int a = d->tex1First ? 0 : 1; if (!image.isNull()) { QImage black(width(), height(), QImage::Format_RGB32); black.fill(QColor(0, 0, 0).rgb()); montage(image, black); if (!d->sharedData->openGlFullScale) { black = black.scaled(d->width, d->height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); } if (d->sharedData->printFileName) printFilename(black); if (d->sharedData->printProgress) printProgress(black); if (d->sharedData->printFileComments) printComments(black); /* create the texture */ d->texture[a]->destroy(); d->texture[a]->setData(black.mirrored()); d->texture[a]->setMinificationFilter(QOpenGLTexture::Linear); d->texture[a]->setMagnificationFilter(QOpenGLTexture::Linear); d->texture[a]->bind(); } } void PresentationGL::montage(QImage& top, QImage& bot) { int tw = top.width(); int th = top.height(); int bw = bot.width(); int bh = bot.height(); if (tw > bw || th > bh) qFatal("Top Image should be smaller or same size as Bottom Image"); if (top.depth() != 32) top = top.convertToFormat(QImage::Format_RGB32); if (bot.depth() != 32) bot = bot.convertToFormat(QImage::Format_RGB32); int sw = bw / 2 - tw / 2; //int ew = bw/2 + tw/2; int sh = bh / 2 - th / 2; int eh = bh / 2 + th / 2; unsigned int* tdata = reinterpret_cast(top.scanLine(0)); unsigned int* bdata = 0; for (int y = sh ; y < eh ; ++y) { bdata = reinterpret_cast(bot.scanLine(y)) + sw; for (int x = 0 ; x < tw ; ++x) { *(bdata++) = *(tdata++); } } } void PresentationGL::printFilename(QImage& layer) { QFileInfo fileinfo(d->sharedData->urlList[d->fileIndex].toLocalFile()); QString filename = fileinfo.fileName(); QPixmap pix = generateOutlinedTextPixmap(filename); // -------------------------------------------------------- QPainter painter; painter.begin(&layer); painter.drawPixmap(d->xMargin, layer.height() - d->yMargin - pix.height(), pix); painter.end(); } void PresentationGL::printProgress(QImage& layer) { QString progress(QString::number(d->fileIndex + 1) + QLatin1Char('/') + QString::number(d->sharedData->urlList.count())); QPixmap pix = generateOutlinedTextPixmap(progress); QPainter painter; painter.begin(&layer); painter.drawPixmap(layer.width() - d->xMargin - pix.width(), d->yMargin, pix); painter.end(); } void PresentationGL::printComments(QImage& layer) { DItemInfo info(d->sharedData->iface->itemInfo(d->imageLoader->currPath())); QString comments = info.comment(); int yPos = 5; // Text Y coordinate if (d->sharedData->printFileName) yPos += 20; QStringList commentsByLines; uint commentsIndex = 0; // Comments QString index while (commentsIndex < (uint) comments.length()) { QString newLine; bool breakLine = false; // End Of Line found uint currIndex; // Comments QString current index // Check miminal lines dimension int commentsLinesLengthLocal = d->sharedData->commentsLinesLength; for (currIndex = commentsIndex ; currIndex < (uint)comments.length() && !breakLine ; ++currIndex) { if (comments[currIndex] == QLatin1Char('\n') || comments[currIndex].isSpace()) { breakLine = true; } } if (commentsLinesLengthLocal <= (int)((currIndex - commentsIndex))) commentsLinesLengthLocal = (currIndex - commentsIndex); breakLine = false; for ( currIndex = commentsIndex ; currIndex <= commentsIndex + commentsLinesLengthLocal && currIndex < (uint)comments.length() && !breakLine ; ++currIndex ) { breakLine = (comments[currIndex] == QLatin1Char('\n')) ? true : false; if (breakLine) newLine.append(QLatin1Char(' ')); else newLine.append( comments[currIndex] ); } commentsIndex = currIndex; // The line is ended - if ( commentsIndex != (uint) comments.length() ) + if (commentsIndex != (uint) comments.length()) { - while ( !newLine.endsWith(QLatin1Char(' ')) ) + while (!newLine.endsWith(QLatin1Char(' '))) { newLine.truncate(newLine.length() - 1); commentsIndex--; } } commentsByLines.prepend(newLine.trimmed()); } yPos += int(2.0 * d->sharedData->captionFont->pointSize()); QFont font(*d->sharedData->captionFont); QColor fgColor(d->sharedData->commentsFontColor); QColor bgColor(d->sharedData->commentsBgColor); bool drawTextOutline = d->sharedData->commentsDrawOutline; int opacity = d->sharedData->bgOpacity; - for ( int lineNumber = 0 ; lineNumber < (int)commentsByLines.count() ; ++lineNumber ) + for (int lineNumber = 0 ; lineNumber < (int)commentsByLines.count() ; ++lineNumber) { QPixmap pix = generateCustomOutlinedTextPixmap(commentsByLines[lineNumber], font, fgColor, bgColor, opacity, drawTextOutline); QPainter painter; painter.begin(&layer); int xPos = (layer.width() / 2) - (pix.width() / 2); painter.drawPixmap(xPos, layer.height() - pix.height() - yPos, pix); painter.end(); yPos += int(pix.height() + d->height / 400); } } void PresentationGL::showEndOfShow() { QPixmap pix(width(), height()); pix.fill(Qt::black); QFont fn(font()); fn.setPointSize(fn.pointSize() + 10); fn.setBold(true); QPainter p(&pix); p.setPen(Qt::white); p.setFont(fn); p.drawText(20, 50, i18n("Slideshow Completed")); p.drawText(20, 100, i18n("Click to Exit...")); // QPixmap logoPixmap = KPSvgPixmapRenderer(width() / 6, width() / 6).getPixmap(); // p.drawPixmap(width()-(width()/12)-logoPixmap.width(), // height()-(height()/12)-logoPixmap.height(), // logoPixmap); p.end(); QImage image(pix.toImage()); /* create the texture */ d->texture[2]->destroy(); d->texture[2]->setData(image.mirrored()); d->texture[2]->setMinificationFilter(QOpenGLTexture::Linear); d->texture[2]->setMagnificationFilter(QOpenGLTexture::Linear); d->texture[2]->bind(); /* paint the texture */ glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glBegin(GL_QUADS); { glColor4f(1.0, 1.0, 1.0, 1.0); glTexCoord2f(0, 0); glVertex3f(-1.0, -1.0, 0); glTexCoord2f(1, 0); glVertex3f(1.0, -1.0, 0); glTexCoord2f(1, 1); glVertex3f(1.0, 1.0, 0); glTexCoord2f(0, 1); glVertex3f(-1.0, 1.0, 0); } glEnd(); } void PresentationGL::slotTimeOut() { if (!d->effect) { qCWarning(DIGIKAM_DPLUGIN_GENERIC_LOG) << "PresentationGL: No transition method"; d->effect = &PresentationGL::effectNone; } if (d->effectRunning) { d->timeout = 10; } else { if (d->timeout == 0) { // effect was running and is complete now // run timer while showing current image d->timeout = d->sharedData->delay; d->i = 0; } else { // timed out after showing current image // load next image and start effect if (d->random) d->effect = getRandomEffect(); advanceFrame(); if (d->endOfShow) { update(); return; } loadImage(); d->timeout = 10; d->effectRunning = true; d->i = 0; } } update(); d->timer->start(d->timeout); } void PresentationGL::slotMouseMoveTimeOut() { QPoint pos(QCursor::pos()); if ((pos.y() < (d->deskY + 20)) || (pos.y() > (d->deskY + d->deskHeight - 20 - 1)) || d->slideCtrlWidget->underMouse() #ifdef HAVE_MEDIAPLAYER || d->playbackWidget->underMouse() #endif ) return; setCursor(QCursor(Qt::BlankCursor)); } void PresentationGL::paintTexture() { glMatrixMode(GL_MODELVIEW); glLoadIdentity(); GLuint tex = d->texture[d->curr]->textureId(); glBindTexture(GL_TEXTURE_2D, tex); glBegin(GL_QUADS); { glColor4f(1.0, 1.0, 1.0, 1.0); glTexCoord2f(0, 0); glVertex3f(-1.0, -1.0, 0); glTexCoord2f(1, 0); glVertex3f(1.0, -1.0, 0); glTexCoord2f(1, 1); glVertex3f(1.0, 1.0, 0); glTexCoord2f(0, 1); glVertex3f(-1.0, 1.0, 0); } glEnd(); } void PresentationGL::effectNone() { paintTexture(); d->effectRunning = false; d->timeout = 0; return; } void PresentationGL::effectBlend() { if (d->i > 100) { paintTexture(); d->effectRunning = false; d->timeout = 0; return; } int a = (d->curr == 0) ? 1 : 0; int b = d->curr; GLuint ta = d->texture[a]->textureId(); GLuint tb = d->texture[b]->textureId(); glBindTexture(GL_TEXTURE_2D, ta); glBegin(GL_QUADS); { glColor4f(1.0, 1.0, 1.0, 1.0); glTexCoord2f(0, 0); glVertex3f(-1.0f, -1.0f, 0); glTexCoord2f(1, 0); glVertex3f(1.0f, -1.0f, 0); glTexCoord2f(1, 1); glVertex3f(1.0f, 1.0f, 0); glTexCoord2f(0, 1); glVertex3f(-1.0f, 1.0f, 0); } glEnd(); glBindTexture(GL_TEXTURE_2D, tb); glBegin(GL_QUADS); { glColor4f(1.0, 1.0, 1.0, 1.0 / (100.0)*(float)d->i); glTexCoord2f(0, 0); glVertex3f(-1.0f, -1.0f, 0); glTexCoord2f(1, 0); glVertex3f(1.0f, -1.0f, 0); glTexCoord2f(1, 1); glVertex3f(1.0f, 1.0f, 0); glTexCoord2f(0, 1); glVertex3f(-1.0f, 1.0f, 0); } glEnd(); d->i++; } void PresentationGL::effectFade() { if (d->i > 100) { paintTexture(); d->effectRunning = false; d->timeout = 0; return; } int a; float opacity; if (d->i <= 50) { a = (d->curr == 0) ? 1 : 0; opacity = 1.0 - 1.0 / 50.0 * (float)(d->i); } else { opacity = 1.0 / 50.0 * (float)(d->i - 50.0); a = d->curr; } GLuint ta = d->texture[a]->textureId(); glBindTexture(GL_TEXTURE_2D, ta); glBegin(GL_QUADS); { glColor4f(1.0, 1.0, 1.0, opacity); glTexCoord2f(0, 0); glVertex3f(-1.0f, -1.0f, 0); glTexCoord2f(1, 0); glVertex3f(1.0f, -1.0f, 0); glTexCoord2f(1, 1); glVertex3f(1.0f, 1.0f, 0); glTexCoord2f(0, 1); glVertex3f(-1.0f, 1.0f, 0); } glEnd(); d->i++; } void PresentationGL::effectRotate() { if (d->i > 100) { paintTexture(); d->effectRunning = false; d->timeout = 0; return; } if (d->i == 0) d->dir = (int)((2.0 * qrand() / (RAND_MAX + 1.0))); int a = (d->curr == 0) ? 1 : 0; int b = d->curr; GLuint ta = d->texture[a]->textureId(); GLuint tb = d->texture[b]->textureId(); glBindTexture(GL_TEXTURE_2D, tb); glBegin(GL_QUADS); { glColor4f(1.0, 1.0, 1.0, 1.0); glTexCoord2f(0, 0); glVertex3f(-1.0f, -1.0f, 0); glTexCoord2f(1, 0); glVertex3f(1.0f, -1.0f, 0); glTexCoord2f(1, 1); glVertex3f(1.0f, 1.0f, 0); glTexCoord2f(0, 1); glVertex3f(-1.0f, 1.0f, 0); } glEnd(); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); float rotate = 360.0 / 100.0 * (float)d->i; glRotatef( ((d->dir == 0) ? -1 : 1) * rotate, 0.0, 0.0, 1.0); float scale = 1.0 / 100.0 * (100.0 - (float)(d->i)); glScalef(scale, scale, 1.0); glBindTexture(GL_TEXTURE_2D, ta); glBegin(GL_QUADS); { glColor4f(1.0, 1.0, 1.0, 1.0); glTexCoord2f(0, 0); glVertex3f(-1.0f, -1.0f, 0); glTexCoord2f(1, 0); glVertex3f(1.0f, -1.0f, 0); glTexCoord2f(1, 1); glVertex3f(1.0f, 1.0f, 0); glTexCoord2f(0, 1); glVertex3f(-1.0f, 1.0f, 0); } glEnd(); d->i++; } void PresentationGL::effectBend() { if (d->i > 100) { paintTexture(); d->effectRunning = false; d->timeout = 0; return; } if (d->i == 0) d->dir = (int)((2.0 * qrand() / (RAND_MAX + 1.0))); int a = (d->curr == 0) ? 1 : 0; int b = d->curr; GLuint ta = d->texture[a]->textureId(); GLuint tb = d->texture[b]->textureId(); glBindTexture(GL_TEXTURE_2D, tb); glBegin(GL_QUADS); { glColor4f(1.0, 1.0, 1.0, 1.0); glTexCoord2f(0, 0); glVertex3f(-1.0f, -1.0f, 0); glTexCoord2f(1, 0); glVertex3f(1.0f, -1.0f, 0); glTexCoord2f(1, 1); glVertex3f(1.0f, 1.0f, 0); glTexCoord2f(0, 1); glVertex3f(-1.0f, 1.0f, 0); } glEnd(); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glRotatef(90.0 / 100.0*(float)d->i, (d->dir == 0) ? 1.0 : 0.0, (d->dir == 1) ? 1.0 : 0.0, 0.0); glBindTexture(GL_TEXTURE_2D, ta); glBegin(GL_QUADS); { glColor4f(1.0, 1.0, 1.0, 1.0); glTexCoord2f(0, 0); glVertex3f(-1.0f, -1.0f, 0); glTexCoord2f(1, 0); glVertex3f(1.0f, -1.0f, 0); glTexCoord2f(1, 1); glVertex3f(1.0f, 1.0f, 0); glTexCoord2f(0, 1); glVertex3f(-1.0f, 1.0f, 0); } glEnd(); d->i++; } void PresentationGL::effectInOut() { if (d->i > 100) { paintTexture(); d->effectRunning = false; d->timeout = 0; return; } if (d->i == 0) { d->dir = 1 + (int) ((4.0 * qrand() / (RAND_MAX + 1.0))); } int a; bool out; if (d->i <= 50) { a = (d->curr == 0) ? 1 : 0; out = 1; } else { a = d->curr; out = 0; } GLuint ta = d->texture[a]->textureId(); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); float t = out ? 1.0 / 50.0 * (50.0 - d->i) : 1.0 / 50.0 * (d->i - 50.0); glScalef(t, t, 1.0); t = 1.0 - t; glTranslatef((d->dir % 2 == 0) ? ((d->dir == 2) ? 1 : -1) * t : 0.0, (d->dir % 2 == 1) ? ((d->dir == 1) ? 1 : -1) * t : 0.0, 0.0); glBindTexture(GL_TEXTURE_2D, ta); glBegin(GL_QUADS); { glColor4f(1.0, 1.0, 1.0, 1.0); glColor4f(1.0, 1.0, 1.0, 1.0); glTexCoord2f(0, 0); glVertex3f(-1.0f, -1.0f, 0); glTexCoord2f(1, 0); glVertex3f(1.0f, -1.0f, 0); glTexCoord2f(1, 1); glVertex3f(1.0f, 1.0f, 0); glTexCoord2f(0, 1); glVertex3f(-1.0f, 1.0f, 0); } glEnd(); d->i++; } void PresentationGL::effectSlide() { if (d->i > 100) { paintTexture(); d->effectRunning = false; d->timeout = 0; return; } if (d->i == 0) d->dir = 1 + (int)((4.0 * qrand() / (RAND_MAX + 1.0))); int a = (d->curr == 0) ? 1 : 0; int b = d->curr; GLuint ta = d->texture[a]->textureId(); GLuint tb = d->texture[b]->textureId(); glBindTexture(GL_TEXTURE_2D, tb); glBegin(GL_QUADS); { glColor4f(1.0, 1.0, 1.0, 1.0); glTexCoord2f(0, 0); glVertex3f(-1.0f, -1.0f, 0); glTexCoord2f(1, 0); glVertex3f(1.0f, -1.0f, 0); glTexCoord2f(1, 1); glVertex3f(1.0f, 1.0f, 0); glTexCoord2f(0, 1); glVertex3f(-1.0f, 1.0f, 0); } glEnd(); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); float trans = 2.0 / 100.0 * (float)d->i; glTranslatef((d->dir % 2 == 0) ? ((d->dir == 2) ? 1 : -1) * trans : 0.0, (d->dir % 2 == 1) ? ((d->dir == 1) ? 1 : -1) * trans : 0.0, 0.0); glBindTexture(GL_TEXTURE_2D, ta); glBegin(GL_QUADS); { glColor4f(1.0, 1.0, 1.0, 1.0); glColor4f(1.0, 1.0, 1.0, 1.0); glTexCoord2f(0, 0); glVertex3f(-1.0f, -1.0f, 0); glTexCoord2f(1, 0); glVertex3f(1.0f, -1.0f, 0); glTexCoord2f(1, 1); glVertex3f(1.0f, 1.0f, 0); glTexCoord2f(0, 1); glVertex3f(-1.0f, 1.0f, 0); } glEnd(); d->i++; } void PresentationGL::effectFlutter() { if (d->i > 100) { paintTexture(); d->effectRunning = false; d->timeout = 0; return; } int a = (d->curr == 0) ? 1 : 0; int b = d->curr; GLuint ta = d->texture[a]->textureId(); GLuint tb = d->texture[b]->textureId(); if (d->i == 0) { for (int x = 0 ; x < 40 ; ++x) { for (int y = 0 ; y < 40 ; ++y) { d->points[x][y][0] = (float) (x / 20.0f - 1.0f); d->points[x][y][1] = (float) (y / 20.0f - 1.0f); d->points[x][y][2] = (float) sin((x / 20.0f - 1.0f) * 3.141592654 * 2.0f) / 5.0; } } } glBindTexture(GL_TEXTURE_2D, tb); glBegin(GL_QUADS); { glColor4f(1.0, 1.0, 1.0, 1.0); glTexCoord2f(0, 0); glVertex3f(-1.0f, -1.0f, 0); glTexCoord2f(1, 0); glVertex3f(1.0f, -1.0f, 0); glTexCoord2f(1, 1); glVertex3f(1.0f, 1.0f, 0); glTexCoord2f(0, 1); glVertex3f(-1.0f, 1.0f, 0); } glEnd(); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); float rotate = 60.0 / 100.0 * (float)d->i; glRotatef(rotate, 1.0f, 0.0f, 0.0f); float scale = 1.0 / 100.0 * (100.0 - (float)d->i); glScalef(scale, scale, scale); glTranslatef(1.0 / 100.0*(float)d->i, 1.0 / 100.0*(float)d->i, 0.0); glBindTexture(GL_TEXTURE_2D, ta); glBegin(GL_QUADS); { glColor4f(1.0, 1.0, 1.0, 1.0); float float_x, float_y, float_xb, float_yb; int x, y; for (x = 0 ; x < 39 ; ++x) { for (y = 0 ; y < 39 ; ++y) { float_x = (float) x / 40.0f; float_y = (float) y / 40.0f; float_xb = (float) (x + 1) / 40.0f; float_yb = (float) (y + 1) / 40.0f; glTexCoord2f(float_x, float_y); glVertex3f(d->points[x][y][0], d->points[x][y][1], d->points[x][y][2]); glTexCoord2f(float_x, float_yb); glVertex3f(d->points[x][y + 1][0], d->points[x][y + 1][1], d->points[x][y + 1][2]); glTexCoord2f(float_xb, float_yb); glVertex3f(d->points[x + 1][y + 1][0], d->points[x + 1][y + 1][1], d->points[x + 1][y + 1][2]); glTexCoord2f(float_xb, float_y); glVertex3f(d->points[x + 1][y][0], d->points[x + 1][y][1], d->points[x + 1][y][2]); } } } glEnd(); // wave every two iterations if (d->i % 2 == 0) { float hold; int x, y; for (y = 0 ; y < 40 ; ++y) { hold = d->points[0][y][2]; for (x = 0 ; x < 39 ; ++x) { d->points[x][y][2] = d->points[x + 1][y][2]; } d->points[39][y][2] = hold; } } d->i++; } void PresentationGL::effectCube() { int tot = 200; int rotStart = 50; if (d->i > tot) { paintTexture(); d->effectRunning = false; d->timeout = 0; return; } // Enable perspective vision glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LEQUAL); glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); int a = (d->curr == 0) ? 1 : 0; int b = d->curr; GLuint ta = d->texture[a]->textureId(); GLuint tb = d->texture[b]->textureId(); glMatrixMode(GL_PROJECTION); glLoadIdentity(); // float PI = 4.0 * atan(1.0); float znear = 3.0; // float theta = 2.0 * atan2((float)2.0 / (float)2.0, (float)znear); // theta = theta * 180.0 / PI; glFrustum(-1.0, 1.0, -1.0, 1.0, znear - 0.01, 10.0); static float xrot; static float yrot; // static float zrot; if (d->i == 0) { xrot = 0.0; yrot = 0.0; // zrot = 0.0; } glMatrixMode( GL_MODELVIEW ); glLoadIdentity(); float trans = 5.0 * (float)((d->i <= tot / 2) ? d->i : tot - d->i) / (float)tot; glTranslatef(0.0, 0.0, -znear - 1.0 - trans); glRotatef(xrot, 1.0f, 0.0f, 0.0f); glRotatef(yrot, 0.0f, 1.0f, 0.0f); glBindTexture(GL_TEXTURE_2D, 0); glBegin(GL_QUADS); { glColor4f(0.0f, 0.0f, 0.0f, 1.0f); /* Front Face */ glVertex3f( -1.00f, -1.00f, 0.99f ); glVertex3f( 1.00f, -1.00f, 0.99f ); glVertex3f( 1.00f, 1.00f, 0.99f ); glVertex3f( -1.00f, 1.00f, 0.99f ); /* Back Face */ glVertex3f( -1.00f, -1.00f, -0.99f ); glVertex3f( -1.00f, 1.00f, -0.99f ); glVertex3f( 1.00f, 1.00f, -0.99f ); glVertex3f( 1.00f, -1.00f, -0.99f ); /* Top Face */ glVertex3f( -1.00f, 0.99f, -1.00f ); glVertex3f( -1.00f, 0.99f, 1.00f ); glVertex3f( 1.00f, 0.99f, 1.00f ); glVertex3f( 1.00f, 0.99f, -1.00f ); /* Bottom Face */ glVertex3f( -1.00f, -0.99f, -1.00f ); glVertex3f( 1.00f, -0.99f, -1.00f ); glVertex3f( 1.00f, -0.99f, 1.00f ); glVertex3f( -1.00f, -0.99f, 1.00f ); /* Right face */ glVertex3f( 0.99f, -1.00f, -1.00f ); glVertex3f( 0.99f, 1.00f, -1.00f ); glVertex3f( 0.99f, 1.00f, 1.00f ); glVertex3f( 0.99f, -1.00f, 1.00f ); /* Left Face */ glVertex3f( -0.99f, -1.00f, -1.00f ); glVertex3f( -0.99f, -1.00f, 1.00f ); glVertex3f( -0.99f, 1.00f, 1.00f ); glVertex3f( -0.99f, 1.00f, -1.00f ); } glEnd(); glBindTexture(GL_TEXTURE_2D, ta); glBegin(GL_QUADS); { glColor4d(1.0, 1.0, 1.0, 1.0); // Front Face glTexCoord2f( 0.0f, 0.0f ); glVertex3f( -1.0f, -1.0f, 1.00f ); glTexCoord2f( 1.0f, 0.0f ); glVertex3f( 1.0f, -1.0f, 1.00f ); glTexCoord2f( 1.0f, 1.0f ); glVertex3f( 1.0f, 1.0f, 1.00f ); glTexCoord2f( 0.0f, 1.0f ); glVertex3f( -1.0f, 1.0f, 1.00f ); // Top Face glTexCoord2f( 1.0f, 1.0f ); glVertex3f( -1.0f, 1.00f, -1.0f ); glTexCoord2f( 1.0f, 0.0f ); glVertex3f( -1.0f, 1.00f, 1.0f ); glTexCoord2f( 0.0f, 0.0f ); glVertex3f( 1.0f, 1.00f, 1.0f ); glTexCoord2f( 0.0f, 1.0f ); glVertex3f( 1.0f, 1.00f, -1.0f ); // Bottom Face glTexCoord2f( 0.0f, 1.0f ); glVertex3f( -1.0f, -1.00f, -1.0f ); glTexCoord2f( 1.0f, 1.0f ); glVertex3f( 1.0f, -1.00f, -1.0f ); glTexCoord2f( 1.0f, 0.0f ); glVertex3f( 1.0f, -1.00f, 1.0f ); glTexCoord2f( 0.0f, 0.0f ); glVertex3f( -1.0f, -1.00f, 1.0f ); // Right face glTexCoord2f( 0.0f, 0.0f ); glVertex3f( 1.00f, -1.0f, -1.0f ); glTexCoord2f( 0.0f, 1.0f ); glVertex3f( 1.00f, -1.0f, 1.0f ); glTexCoord2f( 1.0f, 1.0f ); glVertex3f( 1.00f, 1.0f, 1.0f ); glTexCoord2f( 1.0f, 0.0f ); glVertex3f( 1.00f, 1.0f, -1.0f ); // Left Face glTexCoord2f( 1.0f, 0.0f ); glVertex3f( -1.00f, -1.0f, -1.0f ); glTexCoord2f( 0.0f, 0.0f ); glVertex3f( -1.00f, 1.0f, -1.0f ); glTexCoord2f( 0.0f, 1.0f ); glVertex3f( -1.00f, 1.0f, 1.0f ); glTexCoord2f( 1.0f, 1.0f ); glVertex3f( -1.00f, -1.0f, 1.0f ); } glEnd(); glBindTexture(GL_TEXTURE_2D, tb); glBegin(GL_QUADS); { glColor4d(1.0, 1.0, 1.0, 1.0); // Back Face glTexCoord2f( 1.0f, 0.0f ); glVertex3f( -1.0f, -1.0f, -1.00f ); glTexCoord2f( 1.0f, 1.0f ); glVertex3f( -1.0f, 1.0f, -1.00f ); glTexCoord2f( 0.0f, 1.0f ); glVertex3f( 1.0f, 1.0f, -1.00f ); glTexCoord2f( 0.0f, 0.0f ); glVertex3f( 1.0f, -1.0f, -1.00f ); } glEnd(); if ((d->i >= rotStart) && (d->i < (tot - rotStart))) { xrot += 360.0f / (float)(tot - 2 * rotStart); yrot += 180.0f / (float)(tot - 2 * rotStart); } d->i++; } void PresentationGL::slotPause() { d->timer->stop(); if (d->slideCtrlWidget->isHidden()) { int w = d->slideCtrlWidget->width(); d->slideCtrlWidget->move(d->deskWidth - w - 1, 0); d->slideCtrlWidget->show(); } } void PresentationGL::slotPlay() { d->slideCtrlWidget->hide(); slotTimeOut(); } void PresentationGL::slotPrev() { previousFrame(); if (d->endOfShow) { update(); return; } d->effectRunning = false; loadImage(); update(); } void PresentationGL::slotNext() { advanceFrame(); if (d->endOfShow) { update(); return; } d->effectRunning = false; loadImage(); update(); } void PresentationGL::slotClose() { close(); } QPixmap PresentationGL::generateOutlinedTextPixmap(const QString& text) { QFont fn(font()); fn.setPointSize(fn.pointSize()); fn.setBold(true); return generateOutlinedTextPixmap(text, fn); } QPixmap PresentationGL::generateOutlinedTextPixmap(const QString& text, QFont& fn) { QColor fgColor(Qt::white); QColor bgColor(Qt::black); return generateCustomOutlinedTextPixmap(text, fn, fgColor, bgColor, 0, true); } QPixmap PresentationGL::generateCustomOutlinedTextPixmap(const QString& text, QFont& fn, QColor& fgColor, QColor& bgColor, int opacity, bool drawTextOutline) { QFontMetrics fm(fn); QRect rect = fm.boundingRect(text); rect.adjust( -fm.maxWidth(), -fm.height(), fm.maxWidth(), fm.height() / 2 ); QPixmap pix(rect.width(), rect.height()); pix.fill(Qt::transparent); - if(opacity > 0) + if (opacity > 0) { QPainter pbg(&pix); pbg.setBrush(bgColor); pbg.setPen(bgColor); pbg.setOpacity(opacity / 10.0); pbg.drawRoundedRect(0, 0, (int)pix.width(), (int)pix.height(), (int)pix.height() / 3, (int)pix.height() / 3); } QPainter p(&pix); p.setRenderHint(QPainter::Antialiasing, true); p.setBrush(QBrush()); p.setPen(QPen()); // draw outline QPainterPath path; path.addText(fm.maxWidth(), fm.height() * 1.5, fn, text); QPainterPathStroker stroker; stroker.setWidth(2); stroker.setCapStyle(Qt::RoundCap); stroker.setJoinStyle(Qt::RoundJoin); QPainterPath outline = stroker.createStroke(path); if (drawTextOutline) p.fillPath(outline, Qt::black); p.fillPath(path, QBrush(fgColor)); p.setRenderHint(QPainter::Antialiasing, false); p.end(); return pix; } bool PresentationGL::checkOpenGL() const { // No OpenGL context is found. Are the drivers ok? if (!isValid()) { return false; } // GL_EXT_texture3D is not supported QString s = QString::fromLatin1(reinterpret_cast(glGetString(GL_EXTENSIONS))); if (!s.contains(QString::fromLatin1("GL_EXT_texture3D"), Qt::CaseInsensitive)) { return false; } // Everything is ok! return true; } } // namespace DigikamGenericPresentationPlugin diff --git a/core/dplugins/generic/webservices/debianscreenshots/dsmpform.cpp b/core/dplugins/generic/webservices/debianscreenshots/dsmpform.cpp index 51788bd09b..d8d43d2263 100644 --- a/core/dplugins/generic/webservices/debianscreenshots/dsmpform.cpp +++ b/core/dplugins/generic/webservices/debianscreenshots/dsmpform.cpp @@ -1,158 +1,158 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2010-12-03 * Description : a tool to export images to Debian Screenshots site * * Copyright (C) 2005-2008 by Vardhman Jain * Copyright (C) 2008-2015 by Gilles Caulier * Copyright (C) 2008-2009 by Luka Renko * Copyright (C) 2010 by Pau Garcia i Quiles * * 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 "dsmpform.h" // Qt includes #include #include #include #include #include // Local includes #include "digikam_debug.h" #include "wstoolutils.h" using namespace Digikam; namespace DigikamGenericDebianScreenshotsPlugin { DSMPForm::DSMPForm() { m_boundary = WSToolUtils::randomString(42 + 13).toLatin1(); reset(); } DSMPForm::~DSMPForm() { } void DSMPForm::reset() { m_buffer.resize(0); QByteArray str(contentType().toLatin1()); str += "\r\n"; str += "MIME-version: 1.0"; str += "\r\n\r\n"; m_buffer.append(str); } void DSMPForm::finish() { QByteArray str; str += "--"; str += m_boundary; str += "--"; m_buffer.append(str); } void DSMPForm::addPair(const QString& name, const QString& value) { QByteArray str; QString content_length = QString::number(value.length()); str += "--"; str += m_boundary; str += "\r\n"; if (!name.isEmpty()) { str += "Content-Disposition: form-data; name=\""; str += name.toLatin1(); str += "\"\r\n"; } str += "\r\n"; str += value.toUtf8(); str += "\r\n"; m_buffer.append(str); } bool DSMPForm::addFile(const QString& fileName, const QString& path, const QString& fieldName) { QMimeDatabase db; QMimeType ptr = db.mimeTypeForUrl(QUrl::fromLocalFile(path)); QString mime = ptr.name(); if (mime.isEmpty()) return false; qCDebug(DIGIKAM_WEBSERVICES_LOG) << "mime = " << mime.toLatin1(); QFile imageFile(path); if (!imageFile.open(QIODevice::ReadOnly)) return false; QByteArray imageData = imageFile.readAll(); imageFile.close(); QByteArray str; str += "--"; str += m_boundary; str += "\r\n"; str += "Content-Disposition: form-data; "; - if( !fieldName.isEmpty() ) + if (!fieldName.isEmpty()) { str += "name=\"" + QByteArray(fieldName.toLatin1()) + "\"; "; } str += "filename=\""; str += QFile::encodeName(fileName); str += "\"\r\n"; str += "Content-Type: "; str += mime.toLatin1(); str += "\r\n\r\n"; m_buffer.append(str); m_buffer.append(imageData); m_buffer.append("\r\n"); return true; } QString DSMPForm::contentType() const { return QLatin1String("Content-Type: multipart/form-data; boundary=") + QLatin1String(m_boundary); } QString DSMPForm::boundary() const { return QLatin1String(m_boundary); } QByteArray DSMPForm::formData() const { return m_buffer; } } // namespace DigikamGenericDebianScreenshotsPlugin diff --git a/core/dplugins/generic/webservices/debianscreenshots/dswidget.cpp b/core/dplugins/generic/webservices/debianscreenshots/dswidget.cpp index 21cc87061c..003326204a 100644 --- a/core/dplugins/generic/webservices/debianscreenshots/dswidget.cpp +++ b/core/dplugins/generic/webservices/debianscreenshots/dswidget.cpp @@ -1,341 +1,341 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2010-11-29 * Description : a tool to export images to Debian Screenshots * * Copyright (C) 2010 by Pau Garcia i Quiles * * 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 "dswidget.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include #include // Local includes #include "digikam_debug.h" #include "dspackagedelegate.h" #include "dscommon.h" namespace DigikamGenericDebianScreenshotsPlugin { DSWidget::DSWidget(DInfoInterface* const iface, QWidget* const parent) : QWidget(parent), m_dlGrp(0), - m_lastTip( QString() ), - m_lastQueryUrl( QUrl() ), - m_httpManager( new KIO::AccessManager(this) ), - m_jsonManager( new KIO::AccessManager(this) ) + m_lastTip(QString()), + m_lastQueryUrl(QUrl()), + m_httpManager(new KIO::AccessManager(this)), + m_jsonManager(new KIO::AccessManager(this)) { setObjectName(QLatin1String("DSWidget")); QHBoxLayout* const mainLayout = new QHBoxLayout(this); // ------------------------------------------------------------------- m_imgList = new DItemsList(this); m_imgList->setObjectName(QLatin1String("WebService ImagesList")); m_imgList->setControlButtonsPlacement(DItemsList::ControlButtonsBelow); m_imgList->setAllowRAW(true); m_imgList->setIface(iface); m_imgList->loadImagesFromCurrentSelection(); - m_imgList->listView()->setWhatsThis( i18n("This is the list of images to upload to Debian Screenshots.") ); + m_imgList->listView()->setWhatsThis(i18n("This is the list of images to upload to Debian Screenshots.")); QWidget* const settingsBox = new QWidget(this); QVBoxLayout* const settingsBoxLayout = new QVBoxLayout(settingsBox); m_headerLabel = new DActiveLabel(QUrl(), QString(), settingsBox); m_headerLabel->updateData(QUrl(DigikamGenericDebianScreenshotsPlugin::debshotsUrl), QImage(QLatin1String(":/debianscreenshots/dslogo.png"))); m_headerLabel->setWhatsThis(i18n("This is a clickable link to open the Debian Screenshots home page in a web browser.")); QGroupBox* const pkgGroupBox = new QGroupBox(settingsBox); pkgGroupBox->setTitle(i18n("Package")); pkgGroupBox->setWhatsThis(i18n("This is the Debian Screenshots package to which selected photos will be uploaded.")); QGridLayout* const sdnLayout = new QGridLayout(pkgGroupBox); QLabel* const pkgLabel = new QLabel(i18n("Package:"), pkgGroupBox); m_pkgLineEdit = new QLineEdit(pkgGroupBox); QCompleter* const pkgCompleter = new QCompleter(this); pkgCompleter->setCompletionMode(QCompleter::PopupCompletion); pkgCompleter->setCaseSensitivity(Qt::CaseInsensitive); m_pkgLineEdit->setCompleter(pkgCompleter); QListView* const listView = new QListView; pkgCompleter->setPopup(listView); listView->setItemDelegateForColumn(0, new DSPackageDelegate); connect(m_pkgLineEdit, SIGNAL(textEdited(QString)), this, SLOT(slotCompletePackageName(QString))); connect(m_httpManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(slotCompletePackageNameFinished(QNetworkReply*))); connect(pkgCompleter, SIGNAL(activated(QString)), this, SLOT(slotFindVersionsForPackage(QString))); connect(m_jsonManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(slotFindVersionsForPackageFinished(QNetworkReply*))); QLabel* const versionLabel = new QLabel(i18n("Software version:"), pkgGroupBox); m_versionsComboBox = new QComboBox(pkgGroupBox); m_versionsComboBox->setEditable(false); m_versionsComboBox->setEnabled(false); // Disable until we have a package name m_versionsComboBox->setMinimumContentsLength(40); connect(m_versionsComboBox, SIGNAL(activated(int)), this, SLOT(slotEnableUpload())); QLabel* const descriptionLabel = new QLabel(i18n("Screenshot description:"), pkgGroupBox); m_descriptionLineEdit = new QLineEdit(pkgGroupBox); m_descriptionLineEdit->setMaxLength(40); // 40 is taken from screenshots.debian.net/upload page source m_descriptionLineEdit->setEnabled(false); sdnLayout->addWidget(pkgLabel, 1, 0, 1, 1); sdnLayout->addWidget(m_pkgLineEdit, 1, 1, 1, 4); sdnLayout->addWidget(versionLabel, 2, 0, 1, 1); sdnLayout->addWidget(m_versionsComboBox, 2, 1, 1, 4); sdnLayout->addWidget(descriptionLabel, 3, 0, 1, 1); sdnLayout->addWidget(m_descriptionLineEdit, 3, 1, 1, 4); m_progressBar = new DProgressWdg(settingsBox); m_progressBar->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); m_progressBar->hide(); settingsBoxLayout->addWidget(m_headerLabel); settingsBoxLayout->addWidget(pkgGroupBox); settingsBoxLayout->addWidget(m_progressBar); mainLayout->addWidget(m_imgList); mainLayout->addWidget(settingsBox); mainLayout->setSpacing(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); mainLayout->setContentsMargins(QMargins()); } DSWidget::~DSWidget() { } DItemsList* DSWidget::imagesList() const { return m_imgList; } DProgressWdg* DSWidget::progressBar() const { return m_progressBar; } void DSWidget::slotCompletePackageName(const QString& tip) { - if ((!tip.isEmpty()) && (QString::compare(tip, m_lastTip, Qt::CaseInsensitive) != 0) ) + if ((!tip.isEmpty()) && (QString::compare(tip, m_lastTip, Qt::CaseInsensitive) != 0)) { QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); m_versionsComboBox->clear(); m_versionsComboBox->setEnabled(false); m_descriptionLineEdit->setEnabled(false); emit requiredPackageInfoAvailable(false); QUrl sdnUrl(DigikamGenericDebianScreenshotsPlugin::debshotsUrl + QLatin1String("/packages/ajax_autocomplete_packages")); // DOES NOT RETURN JSON QUrlQuery query(sdnUrl); query.addQueryItem(QLatin1String("q"), tip); // No matter what 'limit' we use, s.d.n will always return 30 results query.addQueryItem(QLatin1String("limit"), QLatin1String("30")); sdnUrl.setQuery(query); QNetworkRequest request(sdnUrl); m_httpManager->get(request); m_lastQueryUrl = sdnUrl; } m_lastTip = tip; } void DSWidget::slotCompletePackageNameFinished(QNetworkReply* reply) { QUrl replyUrl = reply->url(); QApplication::restoreOverrideCursor(); // Check if this is the reply for the last request, or a delayed reply we are receiving just now - if ( QString::compare(replyUrl.toString(), m_lastQueryUrl.toString(), Qt::CaseInsensitive) != 0 ) + if (QString::compare(replyUrl.toString(), m_lastQueryUrl.toString(), Qt::CaseInsensitive) != 0) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Received a delayed reply, discarding it"; return; // It was a delayed reply, discard it } - if ( reply->error() ) + if (reply->error()) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Didn't receive a reply for request " << replyUrl.toEncoded().constData() << " - " << qPrintable(reply->errorString()); } else { QByteArray ba = reply->readAll(); - if( ba.isEmpty() ) + if (ba.isEmpty()) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "No completion data received for request " << replyUrl.toEncoded().constData() << "(probably no package matches that pattern)"; return; } QList pkgSuggestions = ba.split('\n'); QStandardItemModel* const m = new QStandardItemModel(pkgSuggestions.count(), 2, m_pkgLineEdit->completer()); - for ( int i = 0 ; i < pkgSuggestions.count() ; ++i) + for (int i = 0 ; i < pkgSuggestions.count() ; ++i) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Package suggestion:" << pkgSuggestions.at(i); QModelIndex pkgIdx = m->index(i, 0); QModelIndex descIdx = m->index(i, 1); QList pkgDescSplit = pkgSuggestions.at(i).split('|'); if (!pkgDescSplit.isEmpty()) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Package suggestion parsed:" << pkgDescSplit; QString pkg = QString::fromUtf8(pkgDescSplit.at(0)); m->setData(pkgIdx, pkg); if (pkgDescSplit.size() > 1) { QString desc = QString::fromUtf8(pkgDescSplit.at(1)); m->setData(descIdx, desc); } } } m_pkgLineEdit->completer()->setModel(m); } m_pkgLineEdit->completer()->complete(); reply->deleteLater(); } void DSWidget::slotFindVersionsForPackage(const QString& package) { QUrl sdnVersionUrl(DigikamGenericDebianScreenshotsPlugin::debshotsUrl + QLatin1String("/packages/ajax_get_version_for_package")); // DOES RETURN JSON QUrlQuery query(sdnVersionUrl); query.addQueryItem(QLatin1String("q"), QString::fromUtf8(QUrl::toPercentEncoding(package))); query.addQueryItem(QLatin1String("limit"), QLatin1String("30")); sdnVersionUrl.setQuery(query); QNetworkRequest request(sdnVersionUrl); m_jsonManager->get(request); } void DSWidget::slotFindVersionsForPackageFinished(QNetworkReply* reply) { QUrl replyUrl = reply->url(); if (reply->error()) { qCWarning(DIGIKAM_WEBSERVICES_LOG) << "Download of " << replyUrl.toEncoded().constData() << "failed: " << qPrintable(reply->errorString()); } else { QByteArray ba = reply->readAll(); QJsonDocument doc = QJsonDocument::fromJson(ba); QVariant versionSuggestions = doc.toVariant(); if (versionSuggestions.isValid()) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Query " << replyUrl.toEncoded().constData() << "succeeded"; QMap versions = versionSuggestions.toMap(); QMap::const_iterator i = versions.constBegin(); while (i != versions.constEnd()) { m_versionsComboBox->addItem(i.value().toString()); ++i; } m_versionsComboBox->setEnabled(true); - if ( versions.size() == 1 ) + if (versions.size() == 1) { m_descriptionLineEdit->setEnabled(true); slotEnableUpload(); } } else { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Query " << replyUrl.toEncoded().constData() << "failed"; } } reply->deleteLater(); } void DSWidget::slotEnableUpload() { if (!m_imgList->imageUrls().isEmpty()) { emit requiredPackageInfoAvailable(true); } } } // namespace DigikamGenericDebianScreenshotsPlugin diff --git a/core/dplugins/generic/webservices/google/gdrive/gdtalker.cpp b/core/dplugins/generic/webservices/google/gdrive/gdtalker.cpp index c24b349ed0..a53906e802 100644 --- a/core/dplugins/generic/webservices/google/gdrive/gdtalker.cpp +++ b/core/dplugins/generic/webservices/google/gdrive/gdtalker.cpp @@ -1,492 +1,492 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2013-11-18 * Description : a tool to export items to Google web services * * Copyright (C) 2013 by Pankaj Kumar * Copyright (C) 2013-2018 by Caulier Gilles * * 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 "gdtalker.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Local includes #include "wstoolutils.h" #include "digikam_version.h" #include "gswindow.h" #include "gdmpform.h" #include "digikam_debug.h" #include "previewloadthread.h" #include "dmetadata.h" using namespace Digikam; namespace DigikamGenericGoogleServicesPlugin { static bool gdriveLessThan(const GSFolder& p1, const GSFolder& p2) { return (p1.title.toLower() < p2.title.toLower()); } class Q_DECL_HIDDEN GDTalker::Private { public: enum State { GD_LOGOUT = -1, GD_LISTFOLDERS = 0, GD_CREATEFOLDER, GD_ADDPHOTO, GD_USERNAME, }; public: explicit Private() { apiUrl = QLatin1String("https://www.googleapis.com/drive/v2/%1"); uploadUrl = QLatin1String("https://www.googleapis.com/upload/drive/v2/files"); state = GD_LOGOUT; netMngr = 0; rootid = QLatin1String("root"); rootfoldername = QLatin1String("GoogleDrive Root"); listPhotoId = QStringList(); } public: QString apiUrl; QString uploadUrl; QString rootid; QString rootfoldername; QString username; State state; QStringList listPhotoId; QNetworkAccessManager* netMngr; }; GDTalker::GDTalker(QWidget* const parent) : GSTalkerBase(parent, QStringList(QLatin1String("https://www.googleapis.com/auth/drive")), QLatin1String("GoogleDrive")), d(new Private) { d->netMngr = new QNetworkAccessManager(this); connect(d->netMngr, SIGNAL(finished(QNetworkReply*)), this, SLOT(slotFinished(QNetworkReply*))); connect(this, SIGNAL(signalReadyToUpload()), this, SLOT(slotUploadPhoto())); } GDTalker::~GDTalker() { if (m_reply) { m_reply->abort(); } WSToolUtils::removeTemporaryDir("google"); delete d; } /** * Gets username */ void GDTalker::getUserName() { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "getUserName"; QUrl url(d->apiUrl.arg(QLatin1String("about"))); QNetworkRequest netRequest(url); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/json")); netRequest.setRawHeader("Authorization", m_bearerAccessToken.toLatin1()); m_reply = d->netMngr->get(netRequest); d->state = Private::GD_USERNAME; emit signalBusy(true); } /** * Gets list of folder of user in json format */ void GDTalker::listFolders() { QUrl url(d->apiUrl.arg(QLatin1String("files"))); QUrlQuery q; q.addQueryItem(QLatin1String("q"), QLatin1String("mimeType = 'application/vnd.google-apps.folder'")); url.setQuery(q); QNetworkRequest netRequest(url); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/json")); netRequest.setRawHeader("Authorization", m_bearerAccessToken.toLatin1()); m_reply = d->netMngr->get(netRequest); d->state = Private::GD_LISTFOLDERS; emit signalBusy(true); } /** * Creates folder inside any folder(of which id is passed) */ void GDTalker::createFolder(const QString& title, const QString& id) { if (m_reply) { m_reply->abort(); m_reply = 0; } QUrl url(d->apiUrl.arg(QLatin1String("files"))); QByteArray data; data += "{\"title\":\""; data += title.toLatin1(); data += "\",\r\n"; data += "\"parents\":"; data += "[{"; data += "\"id\":\""; data += id.toLatin1(); data += "\"}],\r\n"; data += "\"mimeType\":"; data += "\"application/vnd.google-apps.folder\""; data += "}\r\n"; qCDebug(DIGIKAM_WEBSERVICES_LOG) << "data:" << data; QNetworkRequest netRequest(url); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/json")); netRequest.setRawHeader("Authorization", m_bearerAccessToken.toLatin1()); m_reply = d->netMngr->post(netRequest, data); d->state = Private::GD_CREATEFOLDER; emit signalBusy(true); } bool GDTalker::addPhoto(const QString& imgPath, const GSPhoto& info, const QString& id, bool rescale, int maxDim, int imageQuality) { if (m_reply) { m_reply->abort(); m_reply = 0; } emit signalBusy(true); QString path(imgPath); QMimeDatabase mimeDB; if (mimeDB.mimeTypeForFile(imgPath).name().startsWith(QLatin1String("image/"))) { QImage image = PreviewLoadThread::loadHighQualitySynchronously(imgPath).copyQImage(); if (image.isNull()) { image.load(imgPath); } if (image.isNull()) { emit signalBusy(false); return false; } path = WSToolUtils::makeTemporaryDir("google").filePath(QFileInfo(imgPath) .baseName().trimmed() + QLatin1String(".jpg")); if (rescale && (image.width() > maxDim || image.height() > maxDim)) { image = image.scaled(maxDim,maxDim,Qt::KeepAspectRatio,Qt::SmoothTransformation); } image.save(path, "JPEG", imageQuality); DMetadata meta; if (meta.load(imgPath)) { meta.setItemDimensions(image.size()); meta.setItemOrientation(MetaEngine::ORIENTATION_NORMAL); meta.setMetadataWritingMode((int)DMetadata::WRITE_TO_FILE_ONLY); meta.save(path, true); } } GDMPForm form; form.addPair(QUrl::fromLocalFile(imgPath).fileName(), info.description, imgPath, id); if (!form.addFile(path)) { emit signalBusy(false); return false; } form.finish(); QUrl url(d->uploadUrl); QUrlQuery q; q.addQueryItem(QLatin1String("uploadType"), QLatin1String("multipart")); url.setQuery(q); QNetworkRequest netRequest(url); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, form.contentType()); netRequest.setRawHeader("Authorization", m_bearerAccessToken.toLatin1()); netRequest.setRawHeader("Host", "www.googleapis.com"); m_reply = d->netMngr->post(netRequest, form.formData()); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In add photo"; d->state = Private::GD_ADDPHOTO; return true; } void GDTalker::slotFinished(QNetworkReply* reply) { if (reply != m_reply) { return; } m_reply = 0; if (reply->error() != QNetworkReply::NoError) { emit signalBusy(false); QMessageBox::critical(QApplication::activeWindow(), i18n("Error"), reply->errorString()); reply->deleteLater(); return; } QByteArray buffer = reply->readAll(); switch (d->state) { case (Private::GD_LOGOUT): break; case (Private::GD_LISTFOLDERS): qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In Private::GD_LISTFOLDERS"; parseResponseListFolders(buffer); break; case (Private::GD_CREATEFOLDER): qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In Private::GD_CREATEFOLDER"; parseResponseCreateFolder(buffer); break; case (Private::GD_ADDPHOTO): qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In Private::GD_ADDPHOTO"; // << buffer; parseResponseAddPhoto(buffer); break; case (Private::GD_USERNAME): qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In Private::GD_USERNAME"; // << buffer; parseResponseUserName(buffer); break; default: break; } reply->deleteLater(); } void GDTalker::slotUploadPhoto() { qCDebug(DIGIKAM_WEBSERVICES_LOG) << d->listPhotoId.join(QLatin1String(", ")); emit signalUploadPhotoDone(1, QString(), d->listPhotoId); } void GDTalker::parseResponseUserName(const QByteArray& data) { QJsonParseError err; QJsonDocument doc = QJsonDocument::fromJson(data, &err); if (err.error != QJsonParseError::NoError) { emit signalBusy(false); return; } QJsonObject jsonObject = doc.object(); qCDebug(DIGIKAM_WEBSERVICES_LOG)<<"User Name is: " << jsonObject[QLatin1String("name")].toString(); QString temp = jsonObject[QLatin1String("name")].toString(); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "in parseResponseUserName"; emit signalBusy(false); emit signalSetUserName(temp); } void GDTalker::parseResponseListFolders(const QByteArray& data) { QJsonParseError err; QJsonDocument doc = QJsonDocument::fromJson(data, &err); qCDebug(DIGIKAM_WEBSERVICES_LOG) << doc; if (err.error != QJsonParseError::NoError) { emit signalBusy(false); emit signalListAlbumsDone(0,i18n("Failed to list folders"),QList()); return; } QJsonObject jsonObject = doc.object(); QJsonArray jsonArray = jsonObject[QLatin1String("items")].toArray(); QList albumList; GSFolder fps; fps.id = d->rootid; fps.title = d->rootfoldername; albumList.append(fps); foreach (const QJsonValue& value, jsonArray) { QJsonObject obj = value.toObject(); // Verify if album is in trash QJsonObject labels = obj[QLatin1String("labels")].toObject(); bool trashed = labels[QLatin1String("trashed")].toBool(); // Verify if album is editable bool editable = obj[QLatin1String("editable")].toBool(); /* Verify if album is visualized in a folder inside My Drive * If parents is empty, album is shared by another person and not added to My Drive yet */ QJsonArray parents = obj[QLatin1String("parents")].toArray(); fps.id = obj[QLatin1String("id")].toString(); fps.title = obj[QLatin1String("title")].toString(); - if(editable && !trashed && !parents.isEmpty()) + if (editable && !trashed && !parents.isEmpty()) { albumList.append(fps); } } std::sort(albumList.begin(), albumList.end(), gdriveLessThan); emit signalBusy(false); emit signalListAlbumsDone(1, QString(), albumList); } void GDTalker::parseResponseCreateFolder(const QByteArray& data) { QJsonParseError err; QJsonDocument doc = QJsonDocument::fromJson(data, &err); if (err.error != QJsonParseError::NoError) { emit signalBusy(false); return; } QJsonObject jsonObject = doc.object(); QString temp = jsonObject[QLatin1String("alternateLink")].toString(); bool success = false; if (!(QString::compare(temp, QLatin1String(""), Qt::CaseInsensitive) == 0)) success = true; emit signalBusy(false); if (!success) { emit signalCreateFolderDone(0,i18n("Failed to create folder")); } else { emit signalCreateFolderDone(1,QString()); } } void GDTalker::parseResponseAddPhoto(const QByteArray& data) { QJsonParseError err; QJsonDocument doc = QJsonDocument::fromJson(data, &err); if (err.error != QJsonParseError::NoError) { emit signalBusy(false); return; } QJsonObject jsonObject = doc.object(); QString altLink = jsonObject[QLatin1String("alternateLink")].toString(); QString photoId = jsonObject[QLatin1String("id")].toString(); bool success = false; if (!(QString::compare(altLink, QLatin1String(""), Qt::CaseInsensitive) == 0)) success = true; emit signalBusy(false); if (!success) { emit signalAddPhotoDone(0, i18n("Failed to upload photo")); } else { d->listPhotoId << photoId; emit signalAddPhotoDone(1, QString()); } } void GDTalker::cancel() { if (m_reply) { m_reply->abort(); m_reply = 0; } emit signalBusy(false); } } // namespace DigikamGenericGoogleServicesPlugin diff --git a/core/dplugins/generic/webservices/google/gswindow.cpp b/core/dplugins/generic/webservices/google/gswindow.cpp index a0b59f4943..2fd584edca 100644 --- a/core/dplugins/generic/webservices/google/gswindow.cpp +++ b/core/dplugins/generic/webservices/google/gswindow.cpp @@ -1,1262 +1,1262 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2013-11-18 * Description : a tool to export items to Google web services * * Copyright (C) 2013 by Pankaj Kumar * Copyright (C) 2015 by Shourya Singh Gupta * Copyright (C) 2013-2018 by Caulier Gilles * * 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 "gswindow.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include #include #include // Local includes #include "wstoolutils.h" #include "ditemslist.h" #include "digikam_version.h" #include "dprogresswdg.h" #include "gdtalker.h" #include "gsitem.h" #include "gsnewalbumdlg.h" #include "gswidget.h" #include "gptalker.h" #include "gsreplacedlg.h" #include "digikam_debug.h" namespace DigikamGenericGoogleServicesPlugin { class Q_DECL_HIDDEN GSWindow::Private { public: explicit Private() { widget = 0; albumDlg = 0; gphotoAlbumDlg = 0; talker = 0; gphotoTalker = 0; iface = 0; imagesCount = 0; imagesTotal = 0; renamingOpt = 0; service = GoogleService::GPhotoImport; } unsigned int imagesCount; unsigned int imagesTotal; int renamingOpt; QString serviceName; QString toolName; GoogleService service; QString tmp; GSWidget* widget; GSNewAlbumDlg* albumDlg; GSNewAlbumDlg* gphotoAlbumDlg; GDTalker* talker; GPTalker* gphotoTalker; QString currentAlbumId; QList< QPair > transferQueue; QList< QPair > uploadQueue; DInfoInterface* iface; DMetadata meta; }; GSWindow::GSWindow(DInfoInterface* const iface, QWidget* const /*parent*/, const QString& serviceName) : WSToolDialog(0, QString::fromLatin1("%1Export Dialog").arg(serviceName)), d(new Private) { d->iface = iface; d->serviceName = serviceName; if (QString::compare(d->serviceName, QLatin1String("googledriveexport"), Qt::CaseInsensitive) == 0) { d->service = GoogleService::GDrive; d->toolName = QLatin1String("Google Drive"); } else if (QString::compare(d->serviceName, QLatin1String("googlephotoexport"), Qt::CaseInsensitive) == 0) { d->service = GoogleService::GPhotoExport; d->toolName = QLatin1String("Google Photos"); } else { d->service = GoogleService::GPhotoImport; d->toolName = QLatin1String("Google Photos"); } d->tmp = WSToolUtils::makeTemporaryDir("google").absolutePath() + QLatin1Char('/');; d->widget = new GSWidget(this, d->iface, d->service, d->toolName); setMainWidget(d->widget); setModal(false); switch (d->service) { case GoogleService::GDrive: setWindowTitle(i18n("Export to Google Drive")); startButton()->setText(i18n("Start Upload")); startButton()->setToolTip(i18n("Start upload to Google Drive")); d->widget->setMinimumSize(700,500); d->albumDlg = new GSNewAlbumDlg(this, d->serviceName, d->toolName); d->talker = new GDTalker(this); connect(d->talker,SIGNAL(signalBusy(bool)), this,SLOT(slotBusy(bool))); connect(d->talker,SIGNAL(signalAccessTokenObtained()), this,SLOT(slotAccessTokenObtained())); connect(d->talker, SIGNAL(signalAuthenticationRefused()), this,SLOT(slotAuthenticationRefused())); connect(d->talker,SIGNAL(signalSetUserName(QString)), this,SLOT(slotSetUserName(QString))); connect(d->talker,SIGNAL(signalListAlbumsDone(int,QString,QList)), this,SLOT(slotListAlbumsDone(int,QString,QList))); connect(d->talker,SIGNAL(signalCreateFolderDone(int,QString)), this,SLOT(slotCreateFolderDone(int,QString))); connect(d->talker,SIGNAL(signalAddPhotoDone(int,QString)), this,SLOT(slotAddPhotoDone(int,QString))); connect(d->talker, SIGNAL(signalUploadPhotoDone(int,QString,QStringList)), this, SLOT(slotUploadPhotoDone(int,QString,QStringList))); readSettings(); buttonStateChange(false); d->talker->doOAuth(); break; case GoogleService::GPhotoImport: case GoogleService::GPhotoExport: if (d->service == GoogleService::GPhotoExport) { setWindowTitle(i18n("Export to Google Photos Service")); startButton()->setText(i18n("Start Upload")); startButton()->setToolTip(i18n("Start upload to Google Photos Service")); d->widget->setMinimumSize(700, 500); } else { setWindowTitle(i18n("Import from Google Photos Service")); startButton()->setText(i18n("Start Download")); startButton()->setToolTip(i18n("Start download from Google Photos service")); d->widget->setMinimumSize(300, 400); } d->gphotoAlbumDlg = new GSNewAlbumDlg(this, d->serviceName, d->toolName); d->gphotoTalker = new GPTalker(this); connect(d->gphotoTalker, SIGNAL(signalBusy(bool)), this, SLOT(slotBusy(bool))); connect(d->gphotoTalker,SIGNAL(signalSetUserName(QString)), this,SLOT(slotSetUserName(QString))); connect(d->gphotoTalker, SIGNAL(signalAccessTokenObtained()), this, SLOT(slotAccessTokenObtained())); connect(d->gphotoTalker, SIGNAL(signalAuthenticationRefused()), this,SLOT(slotAuthenticationRefused())); connect(d->gphotoTalker, SIGNAL(signalListAlbumsDone(int,QString,QList)), this, SLOT(slotListAlbumsDone(int,QString,QList))); connect(d->gphotoTalker, SIGNAL(signalCreateAlbumDone(int,QString,QString)), this, SLOT(slotCreateFolderDone(int,QString,QString))); connect(d->gphotoTalker, SIGNAL(signalAddPhotoDone(int,QString)), this, SLOT(slotAddPhotoDone(int,QString))); connect(d->gphotoTalker, SIGNAL(signalUploadPhotoDone(int,QString,QStringList)), this, SLOT(slotUploadPhotoDone(int,QString,QStringList))); connect(d->gphotoTalker, SIGNAL(signalGetPhotoDone(int,QString,QByteArray)), this, SLOT(slotGetPhotoDone(int,QString,QByteArray))); readSettings(); buttonStateChange(false); d->gphotoTalker->doOAuth(); break; } connect(d->widget->imagesList(), SIGNAL(signalImageListChanged()), this, SLOT(slotImageListChanged())); connect(d->widget->getChangeUserBtn(), SIGNAL(clicked()), this, SLOT(slotUserChangeRequest())); connect(d->widget->getNewAlbmBtn(), SIGNAL(clicked()), this,SLOT(slotNewAlbumRequest())); connect(d->widget->getReloadBtn(), SIGNAL(clicked()), this, SLOT(slotReloadAlbumsRequest())); connect(startButton(), SIGNAL(clicked()), this, SLOT(slotStartTransfer())); connect(this, SIGNAL(finished(int)), this, SLOT(slotFinished())); } GSWindow::~GSWindow() { delete d->widget; delete d->albumDlg; delete d->gphotoAlbumDlg; delete d->talker; delete d->gphotoTalker; delete d; } void GSWindow::reactivate() { d->widget->imagesList()->loadImagesFromCurrentSelection(); d->widget->progressBar()->hide(); show(); } void GSWindow::readSettings() { KConfig config; KConfigGroup grp; switch (d->service) { case GoogleService::GDrive: grp = config.group("Google Drive Settings"); break; default: grp = config.group("Google Photo Settings"); break; } d->currentAlbumId = grp.readEntry("Current Album",QString()); if (grp.readEntry("Resize", false)) { d->widget->getResizeCheckBox()->setChecked(true); d->widget->getDimensionSpB()->setEnabled(true); } else { d->widget->getResizeCheckBox()->setChecked(false); d->widget->getDimensionSpB()->setEnabled(false); } d->widget->getDimensionSpB()->setValue(grp.readEntry("Maximum Width", 1600)); d->widget->getImgQualitySpB()->setValue(grp.readEntry("Image Quality", 90)); if (d->service == GoogleService::GPhotoExport && d->widget->m_tagsBGrp) { d->widget->m_tagsBGrp->button(grp.readEntry("Tag Paths", 0))->setChecked(true); } KConfigGroup dialogGroup = config.group(QString::fromLatin1("%1Export Dialog").arg(d->serviceName)); winId(); KWindowConfig::restoreWindowSize(windowHandle(), dialogGroup); resize(windowHandle()->size()); } void GSWindow::writeSettings() { KConfig config; KConfigGroup grp; switch (d->service) { case GoogleService::GDrive: grp = config.group("Google Drive Settings"); break; default: grp = config.group("Google Photo Settings"); break; } grp.writeEntry("Current Album", d->currentAlbumId); grp.writeEntry("Resize", d->widget->getResizeCheckBox()->isChecked()); grp.writeEntry("Maximum Width", d->widget->getDimensionSpB()->value()); grp.writeEntry("Image Quality", d->widget->getImgQualitySpB()->value()); if (d->service == GoogleService::GPhotoExport && d->widget->m_tagsBGrp) { grp.writeEntry("Tag Paths", d->widget->m_tagsBGrp->checkedId()); } KConfigGroup dialogGroup = config.group(QString::fromLatin1("%1Export Dialog").arg(d->serviceName)); KWindowConfig::saveWindowSize(windowHandle(), dialogGroup); config.sync(); } void GSWindow::slotSetUserName(const QString& msg) { d->widget->updateLabels(msg); } void GSWindow::slotListPhotosDoneForDownload(int errCode, const QString& errMsg, const QList & photosList) { disconnect(d->gphotoTalker, SIGNAL(signalListPhotosDone(int,QString,QList)), this, SLOT(slotListPhotosDoneForDownload(int,QString,QList))); if (errCode == 0) { QMessageBox::critical(this, i18nc("@title:window", "Error"), i18n("Google Photos call failed: %1\n", errMsg)); return; } typedef QPair Pair; d->transferQueue.clear(); QList::const_iterator itPWP; for (itPWP = photosList.begin() ; itPWP != photosList.end() ; ++itPWP) { d->transferQueue.append(Pair((*itPWP).originalURL, (*itPWP))); } if (d->transferQueue.isEmpty()) return; d->currentAlbumId = d->widget->getAlbumsCoB()->itemData(d->widget->getAlbumsCoB()->currentIndex()).toString(); d->imagesTotal = d->transferQueue.count(); d->imagesCount = 0; d->widget->progressBar()->setFormat(i18n("%v / %m")); d->widget->progressBar()->show(); d->renamingOpt = 0; // start download with first photo in queue downloadNextPhoto(); } void GSWindow::slotListAlbumsDone(int code, const QString& errMsg, const QList & list) { switch (d->service) { case GoogleService::GDrive: if (code == 0) { QMessageBox::critical(this, i18nc("@title:window", "Error"), i18n("Google Drive call failed: %1\n", errMsg)); return; } d->widget->getAlbumsCoB()->clear(); for (int i = 0 ; i < list.size() ; ++i) { d->widget->getAlbumsCoB()->addItem(QIcon::fromTheme(QLatin1String("system-users")), list.value(i).title, list.value(i).id); if (d->currentAlbumId == list.value(i).id) { d->widget->getAlbumsCoB()->setCurrentIndex(i); } } buttonStateChange(true); d->talker->getUserName(); break; default: if (code == 0) { QMessageBox::critical(this, i18nc("@title:window", "Error"), i18n("Google Photos call failed: %1\n", errMsg)); return; } d->widget->getAlbumsCoB()->clear(); for (int i = 0 ; i < list.size() ; ++i) { QString albumIcon; if (list.at(i).isWriteable) { albumIcon = QLatin1String("folder"); } else { albumIcon = QLatin1String("folder-locked"); } d->widget->getAlbumsCoB()->addItem(QIcon::fromTheme(albumIcon), list.at(i).title, list.at(i).id); if (d->currentAlbumId == list.at(i).id) d->widget->getAlbumsCoB()->setCurrentIndex(i); buttonStateChange(true); } break; } } void GSWindow::slotBusy(bool val) { if (val) { setCursor(Qt::WaitCursor); d->widget->getChangeUserBtn()->setEnabled(false); buttonStateChange(false); } else { setCursor(Qt::ArrowCursor); d->widget->getChangeUserBtn()->setEnabled(true); buttonStateChange(true); } } void GSWindow::slotStartTransfer() { d->widget->imagesList()->clearProcessedStatus(); switch (d->service) { case GoogleService::GDrive: case GoogleService::GPhotoExport: if (d->widget->imagesList()->imageUrls().isEmpty()) { QMessageBox::critical(this, i18nc("@title:window", "Error"), i18n("No image selected. Please select which images should be uploaded.")); return; } break; case GoogleService::GPhotoImport: break; } switch (d->service) { case GoogleService::GDrive: if (!(d->talker->authenticated())) { QPointer warn = new QMessageBox(QMessageBox::Warning, i18n("Warning"), i18n("Authentication failed. Click \"Continue\" to authenticate."), QMessageBox::Yes | QMessageBox::No); (warn->button(QMessageBox::Yes))->setText(i18n("Continue")); (warn->button(QMessageBox::No))->setText(i18n("Cancel")); if (warn->exec() == QMessageBox::Yes) { d->talker->doOAuth(); delete warn; return; } else { delete warn; return; } } break; default: if (!(d->gphotoTalker->authenticated())) { QPointer warn = new QMessageBox(QMessageBox::Warning, i18n("Warning"), i18n("Authentication failed. Click \"Continue\" to authenticate."), QMessageBox::Yes | QMessageBox::No); (warn->button(QMessageBox::Yes))->setText(i18n("Continue")); (warn->button(QMessageBox::No))->setText(i18n("Cancel")); if (warn->exec() == QMessageBox::Yes) { d->gphotoTalker->doOAuth(); delete warn; return; } else { delete warn; return; } } if (d->service == GoogleService::GPhotoImport) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Google Photo Transfer invoked"; // list photos of the album, then start download connect(d->gphotoTalker, SIGNAL(signalListPhotosDone(int,QString,QList)), this, SLOT(slotListPhotosDoneForDownload(int,QString,QList))); d->gphotoTalker->listPhotos( d->widget->getAlbumsCoB()->itemData(d->widget->getAlbumsCoB()->currentIndex()).toString(), d->widget->getDimensionCoB()->itemData(d->widget->getDimensionCoB()->currentIndex()).toString()); return; } } typedef QPair Pair; for (int i = 0 ; i < (d->widget->imagesList()->imageUrls().size()) ; ++i) { DItemInfo info(d->iface->itemInfo(d->widget->imagesList()->imageUrls().value(i))); GSPhoto temp; qCDebug(DIGIKAM_WEBSERVICES_LOG) << "in start transfer info " <service) { case GoogleService::GDrive: temp.title = info.title(); temp.description = info.comment().section(QLatin1String("\n"), 0, 0); break; default: temp.title = info.name(); // Google Photo doesn't support image titles. Include it in descriptions if needed. QStringList descriptions = QStringList() << info.title() << info.comment(); descriptions.removeAll(QLatin1String("")); temp.description = descriptions.join(QLatin1String("\n\n")); break; } temp.gpsLat.setNum(info.latitude()); temp.gpsLon.setNum(info.longitude()); temp.tags = info.tagsPath(); d->transferQueue.append(Pair(d->widget->imagesList()->imageUrls().value(i),temp)); } d->currentAlbumId = d->widget->getAlbumsCoB()->itemData(d->widget->getAlbumsCoB()->currentIndex()).toString(); d->imagesTotal = d->transferQueue.count(); d->imagesCount = 0; d->widget->progressBar()->setFormat(i18n("%v / %m")); d->widget->progressBar()->setMaximum(d->imagesTotal); d->widget->progressBar()->setValue(0); d->widget->progressBar()->show(); switch (d->service) { case GoogleService::GDrive: d->widget->progressBar()->progressScheduled(i18n("Google Drive export"), true, true); d->widget->progressBar()->progressThumbnailChanged( QIcon::fromTheme(QLatin1String("dk-googledrive")).pixmap(22, 22)); break; default: d->widget->progressBar()->progressScheduled(i18n("Google Photo export"), true, true); d->widget->progressBar()->progressThumbnailChanged( QIcon::fromTheme((QLatin1String("dk-googlephoto"))).pixmap(22, 22)); break; } uploadNextPhoto(); } void GSWindow::uploadNextPhoto() { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "in upload nextphoto " << d->transferQueue.count(); if (d->transferQueue.isEmpty()) { //d->widget->progressBar()->hide(); d->widget->progressBar()->progressCompleted(); /** * Now all raw photos have been added, * for GPhoto: prepare to upload on user account * for GDrive: get listPhotoId to write metadata and finish upload */ if (d->service == GoogleService::GPhotoExport) { emit d->gphotoTalker->signalReadyToUpload(); } else { emit d->talker->signalReadyToUpload(); } return; } typedef QPair Pair; Pair pathComments = d->transferQueue.first(); GSPhoto info = pathComments.second; bool res = true; d->widget->imagesList()->processing(pathComments.first); switch (d->service) { case GoogleService::GDrive: { res = d->talker->addPhoto(pathComments.first.toLocalFile(), info, d->currentAlbumId, d->widget->getResizeCheckBox()->isChecked(), d->widget->getDimensionSpB()->value(), d->widget->getImgQualitySpB()->value()); break; } case GoogleService::GPhotoExport: { bool bCancel = false; bool bAdd = true; if (!info.id.isEmpty() && !info.editUrl.isEmpty()) { switch (d->renamingOpt) { case PWR_ADD_ALL: bAdd = true; break; case PWR_REPLACE_ALL: bAdd = false; break; default: { QPointer dlg = new ReplaceDialog(this, QLatin1String(""), d->iface, pathComments.first, info.thumbURL); dlg->exec(); switch (dlg->getResult()) { case PWR_ADD_ALL: d->renamingOpt = PWR_ADD_ALL; break; case PWR_ADD: bAdd = true; break; case PWR_REPLACE_ALL: d->renamingOpt = PWR_REPLACE_ALL; break; case PWR_REPLACE: bAdd = false; break; case PWR_CANCEL: default: bCancel = true; break; } delete dlg; break; } } } // adjust tags according to radio button clicked if (d->widget->m_tagsBGrp) { switch (d->widget->m_tagsBGrp->checkedId()) { case GPTagLeaf: { QStringList newTags; QStringList::const_iterator itT; for (itT = info.tags.constBegin() ; itT != info.tags.constEnd() ; ++itT) { QString strTmp = *itT; int idx = strTmp.lastIndexOf(QLatin1Char('/')); if (idx > 0) { strTmp.remove(0, idx + 1); } newTags.append(strTmp); } info.tags = newTags; break; } case GPTagSplit: { QSet newTagsSet; QStringList::const_iterator itT; for (itT = info.tags.constBegin() ; itT != info.tags.constEnd() ; ++itT) { QStringList strListTmp = itT->split(QLatin1Char('/')); QStringList::const_iterator itT2; for (itT2 = strListTmp.constBegin() ; itT2 != strListTmp.constEnd() ; ++itT2) { if (!newTagsSet.contains(*itT2)) { newTagsSet.insert(*itT2); } } } info.tags.clear(); QSet::const_iterator itT3; for (itT3 = newTagsSet.begin() ; itT3 != newTagsSet.end() ; ++itT3) { info.tags.append(*itT3); } break; } case GPTagCombined: default: break; } } if (bCancel) { slotTransferCancel(); res = true; } else { if (bAdd) { res = d->gphotoTalker->addPhoto(pathComments.first.toLocalFile(), info, d->currentAlbumId, d->widget->getResizeCheckBox()->isChecked(), d->widget->getDimensionSpB()->value(), d->widget->getImgQualitySpB()->value()); } else { res = d->gphotoTalker->updatePhoto(pathComments.first.toLocalFile(), info, d->widget->getResizeCheckBox()->isChecked(), d->widget->getDimensionSpB()->value(), d->widget->getImgQualitySpB()->value()); } } break; } case GoogleService::GPhotoImport: break; } if (!res) { slotAddPhotoDone(0, QLatin1String("")); return; } } void GSWindow::downloadNextPhoto() { if (d->transferQueue.isEmpty()) { d->widget->progressBar()->hide(); d->widget->progressBar()->progressCompleted(); return; } d->widget->progressBar()->setMaximum(d->imagesTotal); d->widget->progressBar()->setValue(d->imagesCount); QString imgPath = d->transferQueue.first().first.url(); d->gphotoTalker->getPhoto(imgPath); } void GSWindow::slotGetPhotoDone(int errCode, const QString& errMsg, const QByteArray& photoData) { GSPhoto item = d->transferQueue.first().second; /** * (Trung) * Google Photo API now does not support title for image, so we use creation time for image name instead */ QString itemName(item.title); QString suffix(item.mimeType.section(QLatin1Char('/'), -1)); if (item.title.isEmpty()) { itemName = QString::fromLatin1("image-%1").arg(item.creationTime); // Replace colon for Windows file systems itemName.replace(QLatin1Char(':'), QLatin1Char('-')); } QUrl tmpUrl = QUrl::fromLocalFile(QString(d->tmp + itemName + QLatin1Char('.') + suffix)); if (errCode == 1) { QString errText; QFile imgFile(tmpUrl.toLocalFile()); if (!imgFile.open(QIODevice::WriteOnly)) { errText = imgFile.errorString(); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "error write"; } else if (imgFile.write(photoData) != photoData.size()) { errText = imgFile.errorString(); } else { imgFile.close(); } if (errText.isEmpty()) { if (d->meta.load(tmpUrl.toLocalFile())) { if (d->meta.supportXmp() && d->meta.canWriteXmp(tmpUrl.toLocalFile())) { d->meta.setXmpTagString("Xmp.digiKam.picasawebGPhotoId", item.id); d->meta.setXmpKeywords(item.tags); } if (!item.gpsLat.isEmpty() && !item.gpsLon.isEmpty()) { d->meta.setGPSInfo(0.0, item.gpsLat.toDouble(), item.gpsLon.toDouble()); } d->meta.setMetadataWritingMode((int)DMetadata::WRITE_TO_FILE_ONLY); d->meta.save(tmpUrl.toLocalFile()); } d->transferQueue.removeFirst(); d->imagesCount++; } else { QPointer warn = new QMessageBox(QMessageBox::Warning, i18n("Warning"), i18n("Failed to save photo: %1\n" "Do you want to continue?", errText), QMessageBox::Yes | QMessageBox::No); (warn->button(QMessageBox::Yes))->setText(i18n("Continue")); (warn->button(QMessageBox::No))->setText(i18n("Cancel")); if (warn->exec() != QMessageBox::Yes) { slotTransferCancel(); delete warn; return; } delete warn; } } else { QPointer warn = new QMessageBox(QMessageBox::Warning, i18n("Warning"), i18n("Failed to download photo: %1\n" "Do you want to continue?", errMsg), QMessageBox::Yes | QMessageBox::No); (warn->button(QMessageBox::Yes))->setText(i18n("Continue")); (warn->button(QMessageBox::No))->setText(i18n("Cancel")); if (warn->exec() != QMessageBox::Yes) { slotTransferCancel(); delete warn; return; } delete warn; } QUrl newUrl = QUrl::fromLocalFile(QString::fromLatin1("%1/%2").arg(d->widget->getDestinationPath()) .arg(tmpUrl.fileName())); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "location " << newUrl.url(); QFileInfo targetInfo(newUrl.toLocalFile()); if (targetInfo.exists()) { int i = 0; bool fileFound = false; do { QFileInfo newTargetInfo(newUrl.toLocalFile()); if (!newTargetInfo.exists()) { fileFound = false; } else { newUrl = newUrl.adjusted(QUrl::RemoveFilename); newUrl.setPath(newUrl.path() + targetInfo.completeBaseName() + QString::fromUtf8("_%1.").arg(++i) + targetInfo.completeSuffix()); fileFound = true; } } while (fileFound); } if (!QFile::rename(tmpUrl.toLocalFile(), newUrl.toLocalFile())) { QMessageBox::critical(this, i18nc("@title:window", "Error"), i18n("Failed to save image to %1", newUrl.toLocalFile())); } downloadNextPhoto(); } void GSWindow::slotAddPhotoDone(int err, const QString& msg) { if (err == 0) { d->widget->imagesList()->processed(d->transferQueue.first().first,false); QPointer warn = new QMessageBox(QMessageBox::Warning, i18n("Warning"), i18n("Failed to upload photo to %1.\n%2\n" "Do you want to continue?", d->toolName,msg), QMessageBox::Yes | QMessageBox::No); (warn->button(QMessageBox::Yes))->setText(i18n("Continue")); (warn->button(QMessageBox::No))->setText(i18n("Cancel")); if (warn->exec() != QMessageBox::Yes) { d->transferQueue.clear(); d->widget->progressBar()->hide(); } else { d->transferQueue.removeFirst(); d->imagesTotal--; d->widget->progressBar()->setMaximum(d->imagesTotal); d->widget->progressBar()->setValue(d->imagesCount); uploadNextPhoto(); } delete warn; } else { /** * (Trung) Take first item out of transferQueue and append to uploadQueue, * in order to use it again to write id in slotUploadPhotoDone */ QPair item = d->transferQueue.first(); d->uploadQueue.append(item); // Remove photo uploaded from the transfer queue d->transferQueue.removeFirst(); d->imagesCount++; qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In slotAddPhotoSucceeded" << d->imagesCount; d->widget->progressBar()->setMaximum(d->imagesTotal); d->widget->progressBar()->setValue(d->imagesCount); uploadNextPhoto(); } } void GSWindow::slotUploadPhotoDone(int err, const QString& msg, const QStringList& listPhotoId) { if (err == 0) { QPointer warn = new QMessageBox(QMessageBox::Warning, i18n("Warning"), i18n("Failed to finish uploading photo to %1.\n%2\n" "No image uploaded to your account.", d->toolName,msg), QMessageBox::Yes); (warn->button(QMessageBox::Yes))->setText(i18n("OK")); d->uploadQueue.clear(); d->widget->progressBar()->hide(); delete warn; } else { foreach (const QString& photoId, listPhotoId) { // Remove image from upload list and from UI QPair item = d->uploadQueue.takeFirst(); d->widget->imagesList()->removeItemByUrl(item.first); QUrl fileUrl = item.first; qCDebug(DIGIKAM_WEBSERVICES_LOG) << "photoID: " << photoId; if (d->meta.supportXmp() && d->meta.canWriteXmp(fileUrl.toLocalFile()) && d->meta.load(fileUrl.toLocalFile()) && !photoId.isEmpty()) { d->meta.setXmpTagString("Xmp.digiKam.picasawebGPhotoId", photoId); d->meta.save(fileUrl.toLocalFile()); } } - if(!d->widget->imagesList()->imageUrls().isEmpty()) + if (!d->widget->imagesList()->imageUrls().isEmpty()) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "continue to upload"; emit d->gphotoTalker->signalReadyToUpload(); } } } void GSWindow::slotImageListChanged() { startButton()->setEnabled(!(d->widget->imagesList()->imageUrls().isEmpty())); } void GSWindow::slotNewAlbumRequest() { switch (d->service) { case GoogleService::GDrive: if (d->albumDlg->exec() == QDialog::Accepted) { GSFolder newFolder; d->albumDlg->getAlbumProperties(newFolder); d->currentAlbumId = d->widget->getAlbumsCoB()->itemData(d->widget->getAlbumsCoB()->currentIndex()).toString(); d->talker->createFolder(newFolder.title, d->currentAlbumId); } break; default: if (d->gphotoAlbumDlg->exec() == QDialog::Accepted) { GSFolder newFolder; d->gphotoAlbumDlg->getAlbumProperties(newFolder); d->gphotoTalker->createAlbum(newFolder); } break; } } void GSWindow::slotReloadAlbumsRequest() { switch (d->service) { case GoogleService::GDrive: d->talker->listFolders(); break; case GoogleService::GPhotoImport: case GoogleService::GPhotoExport: d->gphotoTalker->listAlbums(); break; } } void GSWindow::slotAccessTokenObtained() { switch (d->service) { case GoogleService::GDrive: d->talker->listFolders(); break; case GoogleService::GPhotoImport: case GoogleService::GPhotoExport: d->gphotoTalker->getLoggedInUser(); break; } } void GSWindow::slotAuthenticationRefused() { // QMessageBox::critical(this, i18nc("@title:window", "Error"), // i18n("An authentication error occurred: account failed to link")); // Clear list albums d->widget->getAlbumsCoB()->clear(); // Clear user name d->widget->updateLabels(QString()); return; } void GSWindow::slotCreateFolderDone(int code, const QString& msg, const QString& albumId) { switch (d->service) { case GoogleService::GDrive: if (code == 0) QMessageBox::critical(this, i18nc("@title:window", "Error"), i18n("Google Drive call failed:\n%1", msg)); else { d->currentAlbumId = albumId; d->talker->listFolders(); } break; case GoogleService::GPhotoImport: case GoogleService::GPhotoExport: if (code == 0) QMessageBox::critical(this, i18nc("@title:window", "Error"), i18n("Google Photos call failed:\n%1", msg)); else { d->currentAlbumId = albumId; d->gphotoTalker->listAlbums(); } break; } } void GSWindow::slotTransferCancel() { d->transferQueue.clear(); d->widget->progressBar()->hide(); switch (d->service) { case GoogleService::GDrive: d->talker->cancel(); break; case GoogleService::GPhotoImport: case GoogleService::GPhotoExport: d->gphotoTalker->cancel(); break; } } void GSWindow::slotUserChangeRequest() { QPointer warn = new QMessageBox(QMessageBox::Warning, i18n("Warning"), i18n("You will be logged out of your account, " "click \"Continue\" to authenticate for another account"), QMessageBox::Yes | QMessageBox::No); (warn->button(QMessageBox::Yes))->setText(i18n("Continue")); (warn->button(QMessageBox::No))->setText(i18n("Cancel")); if (warn->exec() == QMessageBox::Yes) { /** * We do not force user to logout from their account * We simply unlink user account and direct use to login page to login new account * (In the future, we may not unlink() user, but let them change account and * choose which one they want to use) * After unlink(), waiting actively until O2 completely unlink() account, before doOAuth() again */ switch (d->service) { case GoogleService::GDrive: d->talker->unlink(); while(d->talker->authenticated()); d->talker->doOAuth(); break; case GoogleService::GPhotoImport: case GoogleService::GPhotoExport: d->gphotoTalker->unlink(); while(d->gphotoTalker->authenticated()); d->gphotoTalker->doOAuth(); break; } } delete warn; } void GSWindow::buttonStateChange(bool state) { d->widget->getNewAlbmBtn()->setEnabled(state); d->widget->getReloadBtn()->setEnabled(state); startButton()->setEnabled(state); } void GSWindow::slotFinished() { writeSettings(); d->widget->imagesList()->listView()->clear(); } void GSWindow::closeEvent(QCloseEvent* e) { if (!e) { return; } slotFinished(); e->accept(); } } // namespace DigikamGenericGoogleServicesPlugin diff --git a/core/dplugins/generic/webservices/imageshack/imageshacktalker.cpp b/core/dplugins/generic/webservices/imageshack/imageshacktalker.cpp index 1fe261efe1..fa55fb5d96 100644 --- a/core/dplugins/generic/webservices/imageshack/imageshacktalker.cpp +++ b/core/dplugins/generic/webservices/imageshack/imageshacktalker.cpp @@ -1,559 +1,559 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2012-02-02 * Description : a tool to export items to ImageShack web service * * Copyright (C) 2012 by Dodon Victor * Copyright (C) 2013-2018 by Caulier Gilles * * 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 "imageshacktalker.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Local includes #include "digikam_version.h" #include "imageshacksession.h" #include "imageshackmpform.h" #include "digikam_debug.h" using namespace Digikam; namespace DigikamGenericImageShackPlugin { class Q_DECL_HIDDEN ImageShackTalker::Private { public: enum State { IMGHCK_AUTHENTICATING, IMGHCK_DONOTHING, IMGHCK_GETGALLERIES, IMGHCK_ADDPHOTO, IMGHCK_ADDVIDEO, IMGHCK_ADDPHOTOGALLERY }; public: explicit Private() { //userAgent = QLatin1String("KIPI-Plugin-ImageShack/%1").arg(kipipluginsVersion()); userAgent = QString::fromLatin1("digiKam-ImageShack/%1").arg(digiKamVersion()); photoApiUrl = QUrl(QLatin1String("https://api.imageshack.com/v2/images")); videoApiUrl = QUrl(QLatin1String("http://render.imageshack.us/upload_api.php")); loginApiUrl = QUrl(QLatin1String("http://my.imageshack.us/setlogin.php")); galleryUrl = QUrl(QLatin1String("http://www.imageshack.us/gallery_api.php")); appKey = QLatin1String("YPZ2L9WV2de2a1e08e8fbddfbcc1c5c39f94f92a"); session = 0; loginInProgress = false; reply = 0; state = IMGHCK_DONOTHING; netMngr = 0; } public: ImageShackSession* session; QString userAgent; QUrl photoApiUrl; QUrl videoApiUrl; QUrl loginApiUrl; QUrl galleryUrl; QString appKey; bool loginInProgress; QNetworkAccessManager* netMngr; QNetworkReply* reply; State state; }; ImageShackTalker::ImageShackTalker(ImageShackSession* const session) : d(new Private) { d->session = session; d->netMngr = new QNetworkAccessManager(this); connect(d->netMngr, SIGNAL(finished(QNetworkReply*)), this, SLOT(slotFinished(QNetworkReply*))); } ImageShackTalker::~ImageShackTalker() { if (d->reply) d->reply->abort(); delete d; } void ImageShackTalker::cancel() { if (d->reply) { d->reply->abort(); d->reply = 0; } emit signalBusy(false); } QString ImageShackTalker::getCallString(QMap< QString, QString >& args) const { QString result; for (QMap::const_iterator it = args.constBegin(); it != args.constEnd(); ++it) { if (!result.isEmpty()) result.append(QLatin1String("&")); result.append(it.key()); result.append(QLatin1String("=")); result.append(it.value()); } return result; } void ImageShackTalker::slotFinished(QNetworkReply* reply) { if (reply != d->reply) { return; } d->reply = 0; if (reply->error() != QNetworkReply::NoError) { if (d->state == Private::IMGHCK_AUTHENTICATING) { checkRegistrationCodeDone(reply->error(), reply->errorString()); emit signalBusy(false); } else if (d->state == Private::IMGHCK_GETGALLERIES) { emit signalBusy(false); emit signalGetGalleriesDone(reply->error(), reply->errorString()); } else if (d->state == Private::IMGHCK_ADDPHOTO || d->state == Private::IMGHCK_ADDPHOTOGALLERY) { emit signalBusy(false); emit signalAddPhotoDone(reply->error(), reply->errorString()); } d->state = Private::IMGHCK_DONOTHING; reply->deleteLater(); return; } QByteArray buffer = reply->readAll(); switch (d->state) { case Private::IMGHCK_AUTHENTICATING: parseAccessToken(buffer); break; case Private::IMGHCK_ADDPHOTOGALLERY: parseAddPhotoToGalleryDone(buffer); break; case Private::IMGHCK_ADDVIDEO: case Private::IMGHCK_ADDPHOTO: parseUploadPhotoDone(buffer); break; case Private::IMGHCK_GETGALLERIES: parseGetGalleries(buffer); break; default: break; } reply->deleteLater(); } void ImageShackTalker::authenticate() { if (d->reply) { d->reply->abort(); d->reply = 0; } emit signalBusy(true); emit signalJobInProgress(1, 4, i18n("Authenticating the user")); QUrl url(QLatin1String("https://api.imageshack.com/v2/user/login")); QUrlQuery q(url); q.addQueryItem(QLatin1String("user"), d->session->email()); q.addQueryItem(QLatin1String("password"), d->session->password()); url.setQuery(q); QNetworkRequest netRequest(url); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/x-www-form-urlencoded")); d->reply = d->netMngr->post(netRequest, QByteArray()); d->state = Private::IMGHCK_AUTHENTICATING; } void ImageShackTalker::getGalleries() { if (d->reply) { d->reply->abort(); d->reply = 0; } emit signalBusy(true); emit signalJobInProgress(3, 4, i18n("Getting galleries from server")); QUrl gUrl(d->galleryUrl); QUrlQuery q(gUrl); q.addQueryItem(QLatin1String("action"), QLatin1String("gallery_list")); q.addQueryItem(QLatin1String("user"), d->session->username()); gUrl.setQuery(q); d->reply = d->netMngr->get(QNetworkRequest(gUrl)); d->state = Private::IMGHCK_GETGALLERIES; } void ImageShackTalker::checkRegistrationCodeDone(int errCode, const QString& errMsg) { emit signalBusy(false); emit signalLoginDone(errCode, errMsg); d->loginInProgress = false; } void ImageShackTalker::parseAccessToken(const QByteArray &data) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Data received is "<< data; QJsonParseError err; QJsonDocument doc = QJsonDocument::fromJson(data, &err); if (err.error != QJsonParseError::NoError) { emit signalBusy(false); return; } QJsonObject jsonObject = doc.object(); if (jsonObject[QLatin1String("success")].toBool()) { d->session->setLoggedIn(true); QJsonObject obj = jsonObject[QLatin1String("result")].toObject(); d->session->setUsername(obj[QLatin1String("username")].toString()); d->session->setEmail(obj[QLatin1String("email")].toString()); d->session->setAuthToken(obj[QLatin1String("auth_token")].toString()); checkRegistrationCodeDone(0,QLatin1String("")); } else { d->session->setLoggedIn(false); QJsonObject obj = jsonObject[QLatin1String("error")].toObject(); checkRegistrationCodeDone(obj[QLatin1String("error_code")].toInt(), obj[QLatin1String("error_message")].toString()); } } void ImageShackTalker::parseGetGalleries(const QByteArray &data) { QDomDocument document; if (!document.setContent(data)) return; QDomElement rootElem = document.documentElement(); QDomNodeList children = rootElem.childNodes(); QStringList gTexts; QStringList gNames; for (int i = 0; i < children.size(); ++i) { QDomElement e = children.at(i).toElement(); if (e.tagName() == QLatin1String("gallery")) { QDomElement nameElem = e.firstChildElement(QLatin1String("name")); QDomElement titleElem = e.firstChildElement(QLatin1String("title")); QDomElement serverElem = e.firstChildElement(QLatin1String("server")); if (!nameElem.isNull()) { QString fmt; fmt = nameElem.firstChild().toText().data(); gNames << nameElem.firstChild().toText().data(); gTexts << titleElem.firstChild().toText().data(); } } } d->state = Private::IMGHCK_DONOTHING; emit signalUpdateGalleries(gTexts, gNames); emit signalGetGalleriesDone(0, i18n("Successfully retrieved galleries")); } void ImageShackTalker::authenticationDone(int errCode, const QString& errMsg) { if (errCode) { d->session->logOut(); } emit signalBusy(false); emit signalLoginDone(errCode, errMsg); d->loginInProgress = false; } void ImageShackTalker::logOut() { d->session->logOut(); d->loginInProgress = false; } void ImageShackTalker::cancelLogIn() { logOut(); emit signalLoginDone(-1, QLatin1String("Canceled by the user!")); } QString ImageShackTalker::mimeType(const QString& path) const { QMimeDatabase db; QMimeType ptr = db.mimeTypeForUrl(QUrl::fromLocalFile(path)); return ptr.name(); } void ImageShackTalker::uploadItem(const QString& path, const QMap& opts) { if (d->reply) { d->reply->abort(); d->reply = 0; } emit signalBusy(true); QMap args; args[QLatin1String("key")] = d->appKey; args[QLatin1String("fileupload")] = QUrl(path).fileName(); ImageShackMPForm form; for (QMap::const_iterator it = opts.constBegin(); it != opts.constEnd(); ++it) { form.addPair(it.key(), it.value()); } for (QMap::const_iterator it = args.constBegin(); it != args.constEnd(); ++it) { form.addPair(it.key(), it.value()); } if (!form.addFile(QUrl(path).fileName(), path)) { emit signalBusy(false); return; } form.finish(); QUrl uploadUrl = QUrl(d->photoApiUrl); d->state = Private::IMGHCK_ADDPHOTO; QNetworkRequest netRequest(uploadUrl); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, form.contentType()); netRequest.setHeader(QNetworkRequest::UserAgentHeader, d->userAgent); d->reply = d->netMngr->post(netRequest, form.formData()); //uploadItemToGallery(path, QLatin1String(""), opts); } void ImageShackTalker::uploadItemToGallery(const QString& path, const QString& /*gallery*/, const QMap& opts) { if (d->reply) { d->reply->abort(); d->reply = 0; } emit signalBusy(true); QMap args; args[QLatin1String("key")] = d->appKey; args[QLatin1String("fileupload")] = QUrl(path).fileName(); ImageShackMPForm form; for (QMap::const_iterator it = opts.constBegin(); it != opts.constEnd(); ++it) { form.addPair(it.key(), it.value()); } for (QMap::const_iterator it = args.constBegin(); it != args.constEnd(); ++it) { form.addPair(it.key(), it.value()); } if (!form.addFile(QUrl(path).fileName(), path)) { emit signalBusy(false); return; } form.finish(); // Check where to upload QString mime = mimeType(path); QUrl uploadUrl; uploadUrl = QUrl(d->photoApiUrl); d->state = Private::IMGHCK_ADDPHOTO; QNetworkRequest netRequest(uploadUrl); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, form.contentType()); netRequest.setHeader(QNetworkRequest::UserAgentHeader, d->userAgent); d->reply = d->netMngr->post(netRequest, form.formData()); } int ImageShackTalker::parseErrorResponse(QDomElement elem, QString& errMsg) const { int errCode = -1; QString err_code; for (QDomNode node = elem.firstChild(); !node.isNull(); node = node.nextSibling()) { if (!node.isElement()) continue; QDomElement e = node.toElement(); if (e.tagName() == QLatin1String("error")) { err_code = e.attributeNode(QLatin1String("id")).value(); errMsg = e.text(); } } if (err_code == QLatin1String("file_too_big")) { errCode = 501; } else { errCode = 502; } return errCode; } void ImageShackTalker::parseUploadPhotoDone(QByteArray data) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "ParseUploadPhotoDone data is "<state == Private::IMGHCK_ADDPHOTO || d->state == Private::IMGHCK_ADDVIDEO || (d->state == Private::IMGHCK_ADDPHOTOGALLERY)) { - if(jsonObject[QLatin1String("success")].toBool()) + if (jsonObject[QLatin1String("success")].toBool()) { emit signalBusy(false); emit signalAddPhotoDone(0,QLatin1String("")); } else { QJsonObject obj = jsonObject[QLatin1String("error")].toObject(); emit signalAddPhotoDone(obj[QLatin1String("error_code")].toInt(), obj[QLatin1String("error_message")].toString()); emit signalBusy(false); } } } void ImageShackTalker::parseAddPhotoToGalleryDone(QByteArray data) { //int errCode = -1; QString errMsg = QLatin1String(""); QDomDocument domDoc(QLatin1String("galleryXML")); qCDebug(DIGIKAM_WEBSERVICES_LOG) << data; if (!domDoc.setContent(data)) return; QDomElement rootElem = domDoc.documentElement(); if (rootElem.isNull() || rootElem.tagName() != QLatin1String("gallery")) { // TODO error cheking } else { emit signalBusy(false); emit signalAddPhotoDone(0, QLatin1String("")); } } } // namespace DigikamGenericImageShackPlugin diff --git a/core/dplugins/generic/webservices/mediawiki/backend/mediawiki_login.cpp b/core/dplugins/generic/webservices/mediawiki/backend/mediawiki_login.cpp index 3b54f8837a..c12583e567 100644 --- a/core/dplugins/generic/webservices/mediawiki/backend/mediawiki_login.cpp +++ b/core/dplugins/generic/webservices/mediawiki/backend/mediawiki_login.cpp @@ -1,276 +1,276 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2011-03-22 * Description : a Iface C++ interface * * Copyright (C) 2011-2019 by Gilles Caulier * Copyright (C) 2011 by Alexandre Mendes * Copyright (C) 2011 by Peter Potrowl * Copyright (C) 2011 by Manuel Campomanes * * 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 "mediawiki_login.h" // Qt includes #include #include #include #include #include #include #include #include // Local includes #include "mediawiki_iface.h" #include "mediawiki_job_p.h" namespace MediaWiki { class Q_DECL_HIDDEN LoginPrivate : public JobPrivate { public: LoginPrivate(Iface& MediaWiki, const QString& login, const QString& password) : JobPrivate(MediaWiki), login(login), password(password) { } static int error(const QString& error) { QStringList list; list << QStringLiteral("NoName") << QStringLiteral("Illegal") << QStringLiteral("NotExists") << QStringLiteral("EmptyPass") << QStringLiteral("WrongPass") << QStringLiteral("WrongPluginPass") << QStringLiteral("CreateBlocked") << QStringLiteral("Throttled") << QStringLiteral("Blocked") << QStringLiteral("NeedToken"); int ret = list.indexOf(error); if (ret == -1) { ret = 0; } return (ret + (int)Login::LoginMissing); } public: QUrl baseUrl; QString login; QString password; QString lgsessionid; QString lgtoken; }; Login::Login(Iface& MediaWiki, const QString& login, const QString& password, QObject* const parent) : Job(*new LoginPrivate(MediaWiki, login, password), parent) { } Login::~Login() { } void Login::start() { QTimer::singleShot(0, this, SLOT(doWorkSendRequest())); } void Login::doWorkSendRequest() { Q_D(Login); // Set the url QUrl url = d->MediaWiki.url(); d->baseUrl = url; QUrlQuery query; query.addQueryItem(QStringLiteral("format"), QStringLiteral("xml")); query.addQueryItem(QStringLiteral("action"), QStringLiteral("login")); query.addQueryItem(QStringLiteral("lgname"), d->login); query.addQueryItem(QStringLiteral("lgpassword"), d->password); // Set the request QNetworkRequest request(url); request.setRawHeader(QByteArrayLiteral("User-Agent"), d->MediaWiki.userAgent().toUtf8()); request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/x-www-form-urlencoded")); // Send the request d->reply = d->manager->post(request, query.toString().toUtf8()); connect(d->reply, SIGNAL(finished()), this, SLOT(doWorkProcessReply())); } void Login::doWorkProcessReply() { Q_D(Login); disconnect(d->reply, SIGNAL(finished()), this, SLOT(doWorkProcessReply())); if (d->reply->error() != QNetworkReply::NoError) { this->setError(Job::NetworkError); d->reply->close(); d->reply->deleteLater(); emitResult(); return; } QXmlStreamReader reader(d->reply); while (!reader.atEnd() && !reader.hasError()) { QXmlStreamReader::TokenType token = reader.readNext(); if (token == QXmlStreamReader::StartElement) { QXmlStreamAttributes attrs = reader.attributes(); if (reader.name() == QLatin1String("login")) { if (attrs.value(QStringLiteral("result")).toString() == QLatin1String("Success")) { this->setError(Job::NoError); d->lgtoken = attrs.value(QStringLiteral("lgtoken")).toString(); d->lgsessionid = attrs.value(QStringLiteral("sessionid")).toString(); - if(d->manager->cookieJar()->cookiesForUrl(d->MediaWiki.url()).isEmpty()) + if (d->manager->cookieJar()->cookiesForUrl(d->MediaWiki.url()).isEmpty()) { QList cookies; QString prefix = attrs.value(QStringLiteral("cookieprefix")).toString(); QString prefixSession = prefix; prefixSession.append(QStringLiteral("_session")); QNetworkCookie cookie1(prefixSession.toUtf8(),attrs.value(QStringLiteral("sessionid")).toString().toUtf8()); cookies.append(cookie1); QString prefixUserName = prefix; prefixUserName.append(QStringLiteral("UserName")); QNetworkCookie cookie2(prefixUserName.toUtf8(),attrs.value(QStringLiteral("lgusername")).toString().toUtf8()); cookies.append(cookie2); QString prefixUserID = prefix; prefixUserID.append(QStringLiteral("UserID")); QNetworkCookie cookie3(prefixUserID.toUtf8(),attrs.value(QStringLiteral("lguserid")).toString().toUtf8()); cookies.append(cookie3); QString prefixToken = prefix; prefixToken.append(QStringLiteral("Token")); QNetworkCookie cookie4(prefixToken.toUtf8(),attrs.value(QStringLiteral("lgtoken")).toString().toUtf8()); cookies.append(cookie4); d->manager->cookieJar()->setCookiesFromUrl(cookies, d->MediaWiki.url()); } d->reply->close(); d->reply->deleteLater(); emitResult(); return; } else if (attrs.value(QStringLiteral("result")).toString() == QLatin1String("NeedToken")) { this->setError(Job::NoError); d->lgtoken = attrs.value(QStringLiteral("token")).toString(); d->lgsessionid = attrs.value(QStringLiteral("sessionid")).toString(); - if(d->manager->cookieJar()->cookiesForUrl(d->MediaWiki.url()).isEmpty()) + if (d->manager->cookieJar()->cookiesForUrl(d->MediaWiki.url()).isEmpty()) { QString prefix = attrs.value(QStringLiteral("cookieprefix")).toString(); prefix.append(QStringLiteral("_session")); QNetworkCookie cookie(prefix.toUtf8(),QString(d->lgsessionid).toUtf8()); QList cookies; cookies.append(cookie); d->manager->cookieJar()->setCookiesFromUrl(cookies, d->MediaWiki.url()); } } else if (attrs.value(QStringLiteral("result")).toString() == QLatin1String("WrongToken")) { this->setError(LoginPrivate::error(attrs.value(QStringLiteral("result")).toString())); d->reply->close(); d->reply->deleteLater(); emitResult(); return; } else if (attrs.value(QStringLiteral("result")).toString() == QLatin1String("Failed")) { this->setError(LoginPrivate::error(attrs.value(QStringLiteral("result")).toString())); d->reply->close(); d->reply->deleteLater(); emitResult(); return; } } else if (reader.name() == QLatin1String("error")) { this->setError(LoginPrivate::error(attrs.value(QStringLiteral("code")).toString())); d->reply->close(); d->reply->deleteLater(); emitResult(); return; } } else if (token == QXmlStreamReader::Invalid && reader.error() != QXmlStreamReader::PrematureEndOfDocumentError) { this->setError(XmlError); d->reply->close(); d->reply->deleteLater(); emitResult(); return; } } d->reply->close(); d->reply->deleteLater(); QUrl url = d->baseUrl; QUrlQuery query; query.addQueryItem(QStringLiteral("format"), QStringLiteral("xml")); query.addQueryItem(QStringLiteral("action"), QStringLiteral("login")); query.addQueryItem(QStringLiteral("lgname"), d->login); query.addQueryItem(QStringLiteral("lgpassword"), d->password); query.addQueryItem(QStringLiteral("lgtoken"), (d->lgtoken).replace(QStringLiteral("+"), QStringLiteral("%2B"))); // Set the request QNetworkRequest request(url); request.setRawHeader("User-Agent", d->MediaWiki.userAgent().toUtf8()); request.setRawHeader("Cookie", d->manager->cookieJar()->cookiesForUrl(d->MediaWiki.url()).at(0).toRawForm()); request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/x-www-form-urlencoded")); // Send the request d->reply = d->manager->post(request, query.toString().toUtf8()); connectReply(); connect(d->reply, SIGNAL(finished()), this, SLOT(doWorkProcessReply())); } } // namespace MediaWiki diff --git a/core/dplugins/generic/webservices/mediawiki/backend/mediawiki_queryrevision.cpp b/core/dplugins/generic/webservices/mediawiki/backend/mediawiki_queryrevision.cpp index 7cd748a7f2..7591ab90ab 100644 --- a/core/dplugins/generic/webservices/mediawiki/backend/mediawiki_queryrevision.cpp +++ b/core/dplugins/generic/webservices/mediawiki/backend/mediawiki_queryrevision.cpp @@ -1,410 +1,410 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2011-03-22 * Description : a Iface C++ interface * * Copyright (C) 2011-2019 by Gilles Caulier * Copyright (C) 2011 by Hormiere Guillaume * Copyright (C) 2011 by Manuel Campomanes * * 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 "mediawiki_queryrevision.h" // Qt includes #include #include #include #include #include #include #include #include // Local includes #include "mediawiki_iface.h" #include "mediawiki_job_p.h" namespace MediaWiki { class Q_DECL_HIDDEN QueryRevisionPrivate : public JobPrivate { public: explicit QueryRevisionPrivate(Iface& MediaWiki) : JobPrivate(MediaWiki) { } QMap requestParameter; }; QueryRevision::QueryRevision(Iface& MediaWiki, QObject* const parent) : Job(*new QueryRevisionPrivate(MediaWiki), parent) { } QueryRevision::~QueryRevision() { } void QueryRevision::start() { QTimer::singleShot(0, this, SLOT(doWorkSendRequest())); } void QueryRevision::setPageName(const QString& pageName) { Q_D(QueryRevision); d->requestParameter[QStringLiteral("titles")] = pageName; } void QueryRevision::setProperties(Properties properties) { Q_D(QueryRevision); QString buff; - if(properties & QueryRevision::Ids) + if (properties & QueryRevision::Ids) { buff.append(QStringLiteral("ids")); } - if(properties & QueryRevision::Flags) + if (properties & QueryRevision::Flags) { if (buff.length()) buff.append(QStringLiteral("|")); buff.append(QStringLiteral("flags")); } - if(properties & QueryRevision::Timestamp) + if (properties & QueryRevision::Timestamp) { if (buff.length()) buff.append(QStringLiteral("|")); buff.append(QStringLiteral("timestamp")); } - if(properties & QueryRevision::User) + if (properties & QueryRevision::User) { if (buff.length()) buff.append(QStringLiteral("|")); buff.append(QStringLiteral("user")); } - if(properties & QueryRevision::Comment) + if (properties & QueryRevision::Comment) { if (buff.length()) buff.append(QStringLiteral("|")); buff.append(QStringLiteral("comment")); } - if(properties & QueryRevision::Size) + if (properties & QueryRevision::Size) { if (buff.length()) buff.append(QStringLiteral("|")); buff.append(QStringLiteral("size")); } - if(properties & QueryRevision::Content) + if (properties & QueryRevision::Content) { if (buff.length()) buff.append(QStringLiteral("|")); buff.append(QStringLiteral("content")); } d->requestParameter[QStringLiteral("rvprop")] = buff; } void QueryRevision::setPageId(unsigned int pageId) { Q_D(QueryRevision); d->requestParameter[QStringLiteral("pageids")] = QString::number(pageId); } void QueryRevision::setRevisionId(unsigned int revisionId) { Q_D(QueryRevision); d->requestParameter[QStringLiteral("revids")] = QString::number(revisionId); } void QueryRevision::setLimit(int limit) { Q_D(QueryRevision); d->requestParameter[QStringLiteral("rvlimit")] = QString::number(limit); } void QueryRevision::setStartId(int startId) { Q_D(QueryRevision); d->requestParameter[QStringLiteral("rvstartid")] = QString::number(startId); } void QueryRevision::setEndId(int endId) { Q_D(QueryRevision); d->requestParameter[QStringLiteral("rvendid")] = QString::number(endId); } void QueryRevision::setStartTimestamp(const QDateTime& start) { Q_D(QueryRevision); d->requestParameter[QStringLiteral("rvstart")] = start.toString(QStringLiteral("yyyy-MM-ddThh:mm:ssZ")); } void QueryRevision::setEndTimestamp(const QDateTime& end) { Q_D(QueryRevision); d->requestParameter[QStringLiteral("rvend")] = end.toString(QStringLiteral("yyyy-MM-ddThh:mm:ssZ")); } void QueryRevision::setUser(const QString& user) { Q_D(QueryRevision); d->requestParameter[QStringLiteral("rvuser")] = user; } void QueryRevision::setExcludeUser(const QString& excludeUser) { Q_D(QueryRevision); d->requestParameter[QStringLiteral("rvexcludeuser")] = excludeUser; } void QueryRevision::setDirection(QueryRevision::Direction direction) { Q_D(QueryRevision); if (direction == QueryRevision::Older) { d->requestParameter[QStringLiteral("rvdir")] = QStringLiteral("older"); } else if (direction == QueryRevision::Newer) { d->requestParameter[QStringLiteral("rvdir")] = QStringLiteral("newer"); } } void QueryRevision::setGenerateXML(bool generateXML) { Q_D(QueryRevision); if (generateXML) { d->requestParameter[QStringLiteral("rvgeneratexml")] = QStringLiteral("on"); } } void QueryRevision::setSection(int section) { Q_D(QueryRevision); d->requestParameter[QStringLiteral("rvsection")] = QString::number(section); } void QueryRevision::setToken(QueryRevision::Token token) { Q_D(QueryRevision); if (QueryRevision::Rollback == token) { d->requestParameter[QStringLiteral("rvtoken")] = QStringLiteral("rollback"); } } void QueryRevision::setExpandTemplates(bool expandTemplates) { Q_D(QueryRevision); if (expandTemplates) { d->requestParameter[QStringLiteral("rvexpandtemplates")] = QStringLiteral("on"); } } void QueryRevision::doWorkSendRequest() { Q_D(QueryRevision); // Set the url QUrl url = d->MediaWiki.url(); QUrlQuery query; query.addQueryItem(QStringLiteral("format"), QStringLiteral("xml")); query.addQueryItem(QStringLiteral("action"), QStringLiteral("query")); query.addQueryItem(QStringLiteral("prop"), QStringLiteral("revisions")); QMapIterator i(d->requestParameter); while (i.hasNext()) { i.next(); query.addQueryItem(i.key(), i.value()); } url.setQuery(query); // Set the request QNetworkRequest request(url); request.setRawHeader("User-Agent", d->MediaWiki.userAgent().toUtf8()); setPercent(25); // Request ready. // Send the request d->reply = d->manager->get(request); connectReply(); connect(d->reply, SIGNAL(finished()), this, SLOT(doWorkProcessReply())); setPercent(50); // Request sent. } void QueryRevision::doWorkProcessReply() { Q_D(QueryRevision); disconnect(d->reply, SIGNAL(finished()), this, SLOT(doWorkProcessReply())); setPercent(75); // Response received. if (d->reply->error() == QNetworkReply::NoError) { QList results; Revision tempR; QString replytmp = QString::fromUtf8(d->reply->readAll()); if (d->requestParameter.contains(QStringLiteral("rvgeneratexml"))) { for (int i = replytmp.indexOf(QStringLiteral("parsetree")); i != -1; i = replytmp.indexOf(QStringLiteral("parsetree"), i+1)) { int count = 0; while (count < 2) { if (replytmp[i] == QLatin1Char('"') && replytmp[i-1] != QLatin1Char('\\')) count++; if (replytmp[i] == QLatin1Char('<')) replytmp[i] = char(255); if (replytmp[i] == QLatin1Char('>')) replytmp[i] = char(254); ++i; } } } QXmlStreamReader reader(replytmp); while (!reader.atEnd() && !reader.hasError()) { QXmlStreamReader::TokenType token = reader.readNext(); if (token == QXmlStreamReader::StartElement) { if (reader.name() == QLatin1String("page") && d->requestParameter.contains(QStringLiteral("rvtoken"))) { tempR.setRollback(reader.attributes().value(QStringLiteral("rollbacktoken")).toString()); } if (reader.name() == QLatin1String("rev")) { if (d->requestParameter.contains(QStringLiteral("rvprop"))) { QString rvprop = d->requestParameter[QStringLiteral("rvprop")]; if (rvprop.contains(QStringLiteral("ids"))) { tempR.setRevisionId(reader.attributes().value(QStringLiteral("revid")).toString().toInt()); tempR.setParentId(reader.attributes().value(QStringLiteral("parentid")).toString().toInt());} if (rvprop.contains(QStringLiteral("size"))) tempR.setSize(reader.attributes().value(QStringLiteral("size")).toString().toInt()); if (rvprop.contains(QStringLiteral("minor"))) tempR.setMinorRevision(true); if (rvprop.contains(QStringLiteral("user"))) tempR.setUser(reader.attributes().value(QStringLiteral("user")).toString()); if (rvprop.contains(QStringLiteral("timestamp"))) tempR.setTimestamp(QDateTime::fromString(reader.attributes().value(QStringLiteral("timestamp")).toString(),QStringLiteral("yyyy-MM-ddThh:mm:ssZ"))); if (rvprop.contains(QStringLiteral("comment"))) tempR.setComment(reader.attributes().value(QStringLiteral("comment")).toString()); if (d->requestParameter.contains(QStringLiteral("rvgeneratexml"))) tempR.setParseTree(reader.attributes().value(QStringLiteral("parsetree")).toString()); if (rvprop.contains(QStringLiteral("content"))) tempR.setContent(reader.readElementText()); } results << tempR; } else if (reader.name() == QLatin1String("error")) { if (reader.attributes().value(QStringLiteral("code")).toString() == QLatin1String("rvrevids")) this->setError(this->WrongRevisionId); else if (reader.attributes().value(QStringLiteral("code")).toString() == QLatin1String("rvmultpages")) this->setError(this->MultiPagesNotAllowed); else if (reader.attributes().value(QStringLiteral("code")).toString() == QLatin1String("rvaccessdenied")) this->setError(this->TitleAccessDenied); else if (reader.attributes().value(QStringLiteral("code")).toString() == QLatin1String("rvbadparams")) this->setError(this->TooManyParams); else if (reader.attributes().value(QStringLiteral("code")).toString() == QLatin1String("rvnosuchsection")) this->setError(this->SectionNotFound); d->reply->close(); d->reply->deleteLater(); //emit revision(QList()); emitResult(); return; } } } if (!reader.hasError()) { setError(KJob::NoError); for (int i = 0 ; i < results.length() ; ++i) { results[i].setParseTree(results[i].parseTree().replace(QChar(254), QStringLiteral(">"))); results[i].setParseTree(results[i].parseTree().replace(QChar(255), QStringLiteral("<"))); } emit revision(results); setPercent(100); // Response parsed successfully. } else { setError(XmlError); d->reply->close(); d->reply->deleteLater(); //emit revision(QList()); } } else { setError(NetworkError); d->reply->close(); d->reply->deleteLater(); //emit revision(QList()); } emitResult(); } } // namespace MediaWiki diff --git a/core/dplugins/generic/webservices/mediawiki/backend/mediawiki_querysiteinfogeneral.cpp b/core/dplugins/generic/webservices/mediawiki/backend/mediawiki_querysiteinfogeneral.cpp index c8136f2097..b39cae57b4 100644 --- a/core/dplugins/generic/webservices/mediawiki/backend/mediawiki_querysiteinfogeneral.cpp +++ b/core/dplugins/generic/webservices/mediawiki/backend/mediawiki_querysiteinfogeneral.cpp @@ -1,171 +1,171 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2011-03-22 * Description : a Iface C++ interface * * Copyright (C) 2011-2019 by Gilles Caulier * Copyright (C) 2011 by Hormiere Guillaume * Copyright (C) 2011 by Manuel Campomanes * * 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 "mediawiki_querysiteinfogeneral.h" // Qt includes #include #include #include #include #include #include #include // Local includes #include "mediawiki_iface.h" #include "mediawiki_job_p.h" namespace MediaWiki { class Q_DECL_HIDDEN QuerySiteInfoGeneralPrivate : public JobPrivate { public: explicit QuerySiteInfoGeneralPrivate(Iface& MediaWiki) : JobPrivate(MediaWiki) { } }; QuerySiteInfoGeneral::QuerySiteInfoGeneral(Iface& MediaWiki, QObject* const /*parent*/) : Job(*new QuerySiteInfoGeneralPrivate(MediaWiki)) { } QuerySiteInfoGeneral::~QuerySiteInfoGeneral() { } void QuerySiteInfoGeneral::start() { QTimer::singleShot(0, this, SLOT(doWorkSendRequest())); } void QuerySiteInfoGeneral::doWorkSendRequest() { Q_D(QuerySiteInfoGeneral); // Set the url QUrl url = d->MediaWiki.url(); QUrlQuery query; query.addQueryItem(QStringLiteral("format"), QStringLiteral("xml")); query.addQueryItem(QStringLiteral("action"), QStringLiteral("query")); query.addQueryItem(QStringLiteral("meta"), QStringLiteral("siteinfo")); query.addQueryItem(QStringLiteral("siprop"), QStringLiteral("general")); url.setQuery(query); // Set the request QNetworkRequest request(url); request.setRawHeader("User-Agent", d->MediaWiki.userAgent().toUtf8()); // Send the request d->reply = d->manager->get(request); connectReply(); connect(d->reply, SIGNAL(finished()), this, SLOT(doWorkProcessReply())); } void QuerySiteInfoGeneral::doWorkProcessReply() { Q_D(QuerySiteInfoGeneral); disconnect(d->reply, SIGNAL(finished()), this, SLOT(doWorkProcessReply())); if (d->reply->error() != QNetworkReply::NoError) { this->setError(Job::NetworkError); d->reply->close(); d->reply->deleteLater(); emitResult(); return; } Generalinfo generalinfo; QXmlStreamReader reader(d->reply); while(!reader.atEnd() && !reader.hasError()) { QXmlStreamReader::TokenType token = reader.readNext(); - if(token == QXmlStreamReader::StartElement) + if (token == QXmlStreamReader::StartElement) { - if(reader.name() == QLatin1String("general")) + if (reader.name() == QLatin1String("general")) { generalinfo.setMainPage(reader.attributes().value(QStringLiteral("mainpage")).toString()); generalinfo.setUrl(QUrl::fromEncoded(reader.attributes().value(QStringLiteral("base")).toString().toLocal8Bit())); generalinfo.setSiteName(reader.attributes().value(QStringLiteral("sitename")).toString()); generalinfo.setGenerator(reader.attributes().value(QStringLiteral("generator")).toString()); generalinfo.setPhpVersion(reader.attributes().value(QStringLiteral("phpversion")).toString()); generalinfo.setPhpApi(reader.attributes().value(QStringLiteral("phpsapi")).toString()); generalinfo.setDataBaseType(reader.attributes().value(QStringLiteral("dbtype")).toString()); generalinfo.setDataBaseVersion(reader.attributes().value(QStringLiteral("dbversion")).toString()); generalinfo.setRev(reader.attributes().value(QStringLiteral("rev")).toString()); generalinfo.setCas(reader.attributes().value(QStringLiteral("case")).toString()); generalinfo.setLicense(reader.attributes().value(QStringLiteral("rights")).toString()); generalinfo.setLanguage(reader.attributes().value(QStringLiteral("lang")).toString()); generalinfo.setFallBack8bitEncoding(reader.attributes().value(QStringLiteral("fallback8bitEncoding")).toString()); generalinfo.setWriteApi(reader.attributes().value(QStringLiteral("writeapi")).toString()); generalinfo.setTimeZone(reader.attributes().value(QStringLiteral("timezone")).toString()); generalinfo.setTimeOffset(reader.attributes().value(QStringLiteral("timeoffset")).toString()); generalinfo.setArticlePath(reader.attributes().value(QStringLiteral("articlepath")).toString()); generalinfo.setScriptPath(reader.attributes().value(QStringLiteral("scriptpath")).toString()); generalinfo.setScript(reader.attributes().value(QStringLiteral("script")).toString()); generalinfo.setVariantArticlePath(reader.attributes().value(QStringLiteral("variantarticlepath")).toString()); generalinfo.setServerUrl(QUrl::fromEncoded(reader.attributes().value(QStringLiteral("server")).toString().toLocal8Bit())); generalinfo.setWikiId(reader.attributes().value(QStringLiteral("wikiid")).toString()); generalinfo.setTime(QDateTime::fromString(reader.attributes().value(QStringLiteral("time")).toString(), QStringLiteral("yyyy-MM-dd'T'hh:mm:ss'Z'"))); } - else if(reader.name() == QLatin1String("error")) + else if (reader.name() == QLatin1String("error")) { this->setError(QuerySiteInfoGeneral::IncludeAllDenied); d->reply->close(); d->reply->deleteLater(); emitResult(); return; } } } if (reader.hasError()) { this->setError(Job::XmlError); } else { emit result(generalinfo); this->setError(Job::NoError); } d->reply->close(); d->reply->deleteLater(); emitResult(); } } // namespace MediaWiki diff --git a/core/dplugins/generic/webservices/mediawiki/backend/mediawiki_upload.cpp b/core/dplugins/generic/webservices/mediawiki/backend/mediawiki_upload.cpp index 2ebdd1baa6..c3b0bf6dae 100644 --- a/core/dplugins/generic/webservices/mediawiki/backend/mediawiki_upload.cpp +++ b/core/dplugins/generic/webservices/mediawiki/backend/mediawiki_upload.cpp @@ -1,289 +1,289 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2011-03-22 * Description : a Iface C++ interface * * Copyright (C) 2011-2019 by Gilles Caulier * Copyright (C) 2011 by Alexandre Mendes * * 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 "mediawiki_upload.h" // Qt includes #include #include #include #include #include #include #include #include #include #include // Local includes #include "mediawiki_job_p.h" #include "mediawiki_iface.h" #include "mediawiki_queryinfo.h" namespace MediaWiki { class Q_DECL_HIDDEN UploadPrivate : public JobPrivate { public: explicit UploadPrivate(Iface& MediaWiki) : JobPrivate(MediaWiki) { file = 0; } static int error(const QString& error) { QString temp = error; int ret = 0; QStringList list; list << QStringLiteral("internalerror") << QStringLiteral("uploaddisabled") << QStringLiteral("invalidsessionkey") << QStringLiteral("badaccessgroups") << QStringLiteral("missingparam") << QStringLiteral("mustbeloggedin") << QStringLiteral("fetchfileerror") << QStringLiteral("nomodule") << QStringLiteral("emptyfile") << QStringLiteral("filetypemissing") << QStringLiteral("filenametooshort") << QStringLiteral("overwrite") << QStringLiteral("stashfailed"); ret = list.indexOf(temp.remove(QChar::fromLatin1('-'))); if (ret == -1) { ret = 0; } return ret + (int)Upload::InternalError ; } QIODevice* file; QString filename; QString comment; QString text; QString token; }; Upload::Upload(Iface& MediaWiki, QObject* const parent) : Job(*new UploadPrivate(MediaWiki), parent) { } Upload::~Upload() { } void Upload::setFilename(const QString& param) { Q_D(Upload); d->filename = param; } void Upload::setFile(QIODevice* const file) { Q_D(Upload); d->file = file; } void Upload::setComment(const QString& param) { Q_D(Upload); d->comment = param; } void Upload::setText(const QString& text) { Q_D(Upload); d->text = text; } void Upload::start() { Q_D(Upload); QueryInfo* const info = new QueryInfo(d->MediaWiki, this); info->setPageName(QStringLiteral("File:") + d->filename); info->setToken(QStringLiteral("edit")); connect(info, SIGNAL(page(Page)), this, SLOT(doWorkSendRequest(Page))); info->start(); } void Upload::doWorkSendRequest(Page page) { Q_D(Upload); QString token = page.pageEditToken(); d->token = token; // Get the extension QStringList filename = d->filename.split(QChar::fromLatin1('.')); QString extension = filename.at(filename.size()-1); if (extension == QLatin1String("jpg")) extension = QStringLiteral("jpeg"); else if (extension == QLatin1String("svg")) extension += QStringLiteral("+xml"); QUrl url = d->MediaWiki.url(); QUrlQuery query; query.addQueryItem(QStringLiteral("action"), QStringLiteral("upload")); query.addQueryItem(QStringLiteral("format"), QStringLiteral("xml")); url.setQuery(query); // Add the cookies QByteArray cookie = ""; QList MediaWikiCookies = d->manager->cookieJar()->cookiesForUrl(d->MediaWiki.url()); for (int i = 0 ; i < MediaWikiCookies.size() ; ++i) { cookie += MediaWikiCookies.at(i).toRawForm(QNetworkCookie::NameAndValueOnly); cookie += ';'; } // Set the request - QNetworkRequest request( url ); + QNetworkRequest request(url); request.setRawHeader("User-Agent", d->MediaWiki.userAgent().toUtf8()); request.setRawHeader("Accept-Charset", "utf-8"); QByteArray boundary = "-----------------------------15827188141577679942014851228"; request.setRawHeader("Content-Type", "multipart/form-data; boundary=" + boundary); - request.setRawHeader("Cookie", cookie ); + request.setRawHeader("Cookie", cookie); // send data boundary = "--" + boundary + "\r\n"; QByteArray out = boundary; // ignore warnings out += "Content-Disposition: form-data; name=\"ignorewarnings\"\r\n\r\n"; out += "true\r\n"; out += boundary; // filename out += "Content-Disposition: form-data; name=\"filename\"\r\n\r\n"; out += d->filename.toUtf8(); out += "\r\n"; out += boundary; // comment if (!d->comment.isEmpty()) { out += "Content-Disposition: form-data; name=\"comment\"\r\n\r\n"; out += d->comment.toUtf8(); out += "\r\n"; out += boundary; } // token out += "Content-Disposition: form-data; name=\"token\"\r\n\r\n"; out += d->token.toUtf8(); out += "\r\n"; out += boundary; // the actual file out += "Content-Disposition: form-data; name=\"file\"; filename=\""; out += d->filename.toUtf8(); out += "\"\r\n"; out += "Content-Type: image/"; out += extension.toUtf8(); out += "\r\n\r\n"; out += d->file->readAll(); out += "\r\n"; out += boundary; // description page out += "Content-Disposition: form-data; name=\"text\"\r\n"; out += "Content-Type: text/plain\r\n\r\n"; out += d->text.toUtf8(); out += "\r\n"; out += boundary.mid(0, boundary.length() - 2); out += "--\r\n"; - d->reply = d->manager->post( request, out ); + d->reply = d->manager->post(request, out); connectReply(); - connect( d->reply, SIGNAL(finished()), - this, SLOT(doWorkProcessReply()) ); + connect(d->reply, SIGNAL(finished()), + this, SLOT(doWorkProcessReply())); } void Upload::doWorkProcessReply() { Q_D(Upload); - disconnect( d->reply, SIGNAL(finished()), - this, SLOT(doWorkProcessReply()) ); + disconnect(d->reply, SIGNAL(finished()), + this, SLOT(doWorkProcessReply())); - if ( d->reply->error() != QNetworkReply::NoError ) + if (d->reply->error() != QNetworkReply::NoError) { this->setError(this->NetworkError); d->reply->close(); d->reply->deleteLater(); emitResult(); return; } - QXmlStreamReader reader( d->reply ); + QXmlStreamReader reader(d->reply); - while ( !reader.atEnd() && !reader.hasError() ) + while (!reader.atEnd() && !reader.hasError()) { QXmlStreamReader::TokenType token = reader.readNext(); - if ( token == QXmlStreamReader::StartElement ) + if (token == QXmlStreamReader::StartElement) { QXmlStreamAttributes attrs = reader.attributes(); - if ( reader.name() == QLatin1String( "upload" ) ) + if (reader.name() == QLatin1String("upload")) { - if ( attrs.value(QStringLiteral("result")).toString() == QLatin1String("Success") ) + if (attrs.value(QStringLiteral("result")).toString() == QLatin1String("Success")) { this->setError(KJob::NoError); } } - else if ( reader.name() == QLatin1String( "error" ) ) + else if (reader.name() == QLatin1String("error")) { this->setErrorText(attrs.value( QStringLiteral("info")).toString()); this->setError(UploadPrivate::error(attrs.value(QStringLiteral("code")).toString())); } } - else if ( token == QXmlStreamReader::Invalid && reader.error() != - QXmlStreamReader::PrematureEndOfDocumentError) + else if (token == QXmlStreamReader::Invalid && reader.error() != + QXmlStreamReader::PrematureEndOfDocumentError) { this->setError(this->XmlError); } } d->reply->close(); d->reply->deleteLater(); emitResult(); } } // namespace MediaWiki diff --git a/core/dplugins/generic/webservices/mediawiki/tests/examples/editsample/mainwindow.cpp b/core/dplugins/generic/webservices/mediawiki/tests/examples/editsample/mainwindow.cpp index 5d2943d6f9..4a466655e4 100644 --- a/core/dplugins/generic/webservices/mediawiki/tests/examples/editsample/mainwindow.cpp +++ b/core/dplugins/generic/webservices/mediawiki/tests/examples/editsample/mainwindow.cpp @@ -1,144 +1,144 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2011-03-22 * Description : a MediaWiki C++ interface * * Copyright (C) 2011-2019 by Gilles Caulier * Copyright (C) 2011 by Alexandre Mendes * Copyright (C) 2011 by Hormiere Guillaume * Copyright (C) 2011 by Manuel Campomanes * * 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 "mainwindow.h" #include "ui_mainwindow.h" MainWindow::MainWindow(QWidget* const parent) : QMainWindow(parent), ui(new Ui::MainWindow), MediaWiki(0) { ui->setupUi(this); } MainWindow::~MainWindow() { delete ui; } //Load page void MainWindow::on_pushButton2_clicked() { MediaWiki = new Iface(QUrl(this->ui->mWikiEdit->text())); QueryRevision* const queryrevision(new QueryRevision(*MediaWiki)); queryrevision->setPageName(this->ui->mPageEdit->text()); queryrevision->setProperties(QueryRevision::Content); queryrevision->setExpandTemplates(true); queryrevision->setLimit(1); connect(queryrevision, SIGNAL(revision(QList)), this, SLOT(revisionHandle(QList))); connect(queryrevision, SIGNAL(result(KJob*)), this, SLOT(revisionError(KJob*))); queryrevision->start(); } void MainWindow::revisionHandle(const QList& revisions) { if (revisions.isEmpty()) { QMessageBox popup; popup.setText(QLatin1String("This page doesn't exist.")); popup.exec(); return; } this->ui->plainTextEdit->setPlainText(revisions[0].content()); } //Send page void MainWindow::on_pushButton1_clicked() { Login* const login = new Login(*MediaWiki, this->ui->mLoginEdit->text(), this->ui->mMdpEdit->text()); connect(login, SIGNAL(result(KJob*)), this, SLOT(loginHandle(KJob*))); login->start(); } void MainWindow::loginHandle(KJob* login) { if (login->error()!= 0) { QMessageBox popup; popup.setText(QLatin1String("Wrong authentication.")); popup.exec(); } else { Edit* const job = new Edit(*MediaWiki, NULL); job->setPageName(this->ui->mPageEdit->text()); job->setText(this->ui->plainTextEdit->toPlainText()); connect(job, SIGNAL(result(KJob*)), this, SLOT(editError(KJob*))); job->start(); } } void MainWindow::editError(KJob* job) { QString errorMessage; if (job->error() == 0) errorMessage = QLatin1String("The Wiki page modified successfully."); else errorMessage = QLatin1String("The Wiki page can't be modified."); QMessageBox popup; popup.setText(errorMessage); popup.exec(); } void MainWindow::revisionError(KJob* job) { - if(job->error() != 0) + if (job->error() != 0) { QMessageBox popup; popup.setText(QLatin1String("The Wiki page can't be loaded.")); popup.exec(); } } void MainWindow::on_mPageEdit_textChanged(QString text) { this->ui->pushButton2->setEnabled(!text.isEmpty() && !text.isNull() && !this->ui->mWikiEdit->text().isEmpty()); } void MainWindow::on_mWikiEdit_textChanged(QString text) { this->ui->pushButton2->setEnabled(!text.isEmpty() && !text.isNull() && !this->ui->mPageEdit->text().isEmpty()); } void MainWindow::on_plainTextEdit_textChanged() { QString text = this->ui->plainTextEdit->toPlainText(); this->ui->pushButton1->setEnabled(!text.isEmpty() && !text.isNull()); } diff --git a/core/dplugins/generic/webservices/piwigo/piwigowindow.cpp b/core/dplugins/generic/webservices/piwigo/piwigowindow.cpp index b2fdf9ba65..d28cc806d7 100644 --- a/core/dplugins/generic/webservices/piwigo/piwigowindow.cpp +++ b/core/dplugins/generic/webservices/piwigo/piwigowindow.cpp @@ -1,647 +1,647 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2014-09-30 * Description : a tool to export items to Piwigo web service * * Copyright (C) 2003-2005 by Renchi Raju * Copyright (C) 2006 by Colin Guthrie * Copyright (C) 2006-2019 by Gilles Caulier * Copyright (C) 2008 by Andrea Diamantini * Copyright (C) 2010-2014 by Frederic Coiffier * * 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 "piwigowindow.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include #include #include // Local includes #include "digikam_debug.h" #include "piwigosession.h" #include "piwigologindlg.h" #include "piwigoitem.h" #include "piwigotalker.h" #include "imagedialog.h" namespace DigikamGenericPiwigoPlugin { class Q_DECL_HIDDEN PiwigoWindow::Private { public: explicit Private(PiwigoWindow* const parent, DInfoInterface* const interface); QWidget* widget; QTreeWidget* albumView; QPushButton* confButton; QCheckBox* resizeCheckBox; QSpinBox* widthSpinBox; QSpinBox* heightSpinBox; QSpinBox* qualitySpinBox; QHash albumDict; PiwigoTalker* talker; PiwigoSession* pPiwigo; DInfoInterface* iface; QProgressDialog* progressDlg; unsigned int uploadCount; unsigned int uploadTotal; QStringList* pUploadList; }; PiwigoWindow::Private::Private(PiwigoWindow* const parent, DInfoInterface* const interface) { iface = interface; talker = 0; pPiwigo = 0; progressDlg = 0; uploadCount = 0; uploadTotal = 0; pUploadList = 0; widget = new QWidget(parent); parent->setMainWidget(widget); parent->setModal(false); QHBoxLayout* const hlay = new QHBoxLayout(widget); // --------------------------------------------------------------------------- QLabel* const logo = new QLabel(); logo->setContentsMargins(QMargins()); logo->setScaledContents(false); logo->setOpenExternalLinks(true); logo->setTextFormat(Qt::RichText); logo->setFocusPolicy(Qt::NoFocus); logo->setTextInteractionFlags(Qt::LinksAccessibleByMouse); logo->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum)); logo->setToolTip(i18n("Visit Piwigo website")); logo->setAlignment(Qt::AlignLeft); QImage img = QImage(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("digikam/pics/logo-piwigo.png"))); QByteArray byteArray; QBuffer buffer(&byteArray); img.save(&buffer, "PNG"); logo->setText(QString::fromLatin1("%2") .arg(QLatin1String("http://piwigo.org")) .arg(QString::fromLatin1("") .arg(QLatin1String(byteArray.toBase64().data())))); // --------------------------------------------------------------------------- albumView = new QTreeWidget; QStringList labels; labels << i18n("Albums"); albumView->setHeaderLabels(labels); albumView->setSortingEnabled(true); albumView->sortByColumn(0, Qt::AscendingOrder); // --------------------------------------------------------------------------- QFrame* const optionFrame = new QFrame; QVBoxLayout* const vlay = new QVBoxLayout(); confButton = new QPushButton; confButton->setText(i18n("Change Account")); confButton->setIcon(QIcon::fromTheme(QLatin1String("system-switch-user"))); confButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); QGroupBox* const optionsBox = new QGroupBox(i18n("Options")); QVBoxLayout* const vlay2 = new QVBoxLayout(); resizeCheckBox = new QCheckBox(optionsBox); resizeCheckBox->setText(i18n("Resize photos before uploading")); QGridLayout* const glay = new QGridLayout; QLabel* const widthLabel = new QLabel(i18n("Maximum width:")); widthSpinBox = new QSpinBox; widthSpinBox->setRange(1,8000); widthSpinBox->setValue(1600); QLabel* const heightLabel = new QLabel(i18n("Maximum height:")); heightSpinBox = new QSpinBox; heightSpinBox->setRange(1,8000); heightSpinBox->setValue(1600); QLabel* const qualityLabel= new QLabel(i18n("Resized JPEG quality:")); qualitySpinBox = new QSpinBox; qualitySpinBox->setRange(1,100); qualitySpinBox->setValue(95); resizeCheckBox->setChecked(false); widthSpinBox->setEnabled(false); heightSpinBox->setEnabled(false); qualitySpinBox->setEnabled(false); // --------------------------------------------------------------------------- glay->addWidget(widthLabel, 0, 0); glay->addWidget(widthSpinBox, 0, 1); glay->addWidget(heightLabel, 1, 0); glay->addWidget(heightSpinBox, 1, 1); glay->addWidget(qualityLabel, 2, 0); glay->addWidget(qualitySpinBox, 2, 1); // --------------------------------------------------------------------------- vlay2->addWidget(resizeCheckBox); vlay2->addLayout(glay); vlay2->addStretch(0); optionsBox->setLayout(vlay2); // --------------------------------------------------------------------------- vlay->addWidget(confButton); vlay->addWidget(optionsBox); optionFrame->setLayout(vlay); // --------------------------------------------------------------------------- hlay->addWidget(logo); hlay->addWidget(albumView); hlay->addWidget(optionFrame); widget->setLayout(hlay); } // --------------------------------------------------------------------------- PiwigoWindow::PiwigoWindow(DInfoInterface* const iface, QWidget* const /*parent*/) : WSToolDialog(0, QLatin1String("PiwigoSync Dialog")), d(new Private(this, iface)) { d->pPiwigo = new PiwigoSession(); setWindowTitle( i18n("Piwigo Export") ); setModal(false); // "Start Upload" button startButton()->setText( i18n("Start Upload") ); startButton()->setEnabled(false); connect(startButton(), SIGNAL(clicked()), this, SLOT(slotAddPhoto())); // we need to let d->talker work.. d->talker = new PiwigoTalker(iface, d->widget); // setting progressDlg and its numeric hints d->progressDlg = new QProgressDialog(this); d->progressDlg->setModal(true); d->progressDlg->setAutoReset(true); d->progressDlg->setAutoClose(true); d->progressDlg->setMaximum(0); d->progressDlg->reset(); d->pUploadList = new QStringList; // connect functions connectSignals(); QPointer configDlg; KConfig config; if (!config.hasGroup("Piwigo Settings") ) { configDlg = new PiwigoLoginDlg(QApplication::activeWindow(), d->pPiwigo, i18n("Edit Piwigo Data") ); if (configDlg->exec() != QDialog::Accepted) { delete configDlg; } } readSettings(); slotDoLogin(); } PiwigoWindow::~PiwigoWindow() { // write config KConfig config; KConfigGroup group = config.group("PiwigoSync Galleries"); group.writeEntry("Resize", d->resizeCheckBox->isChecked()); group.writeEntry("Maximum Width", d->widthSpinBox->value()); group.writeEntry("Maximum Height", d->heightSpinBox->value()); group.writeEntry("Quality", d->qualitySpinBox->value()); delete d->talker; delete d->pUploadList; delete d; } void PiwigoWindow::connectSignals() { connect(d->albumView, SIGNAL(itemSelectionChanged()), this, SLOT(slotAlbumSelected())); connect(d->confButton, SIGNAL(clicked()), this, SLOT(slotSettings())); connect(d->resizeCheckBox, SIGNAL(stateChanged(int)), this, SLOT(slotEnableSpinBox(int))); connect(d->progressDlg, SIGNAL(canceled()), this, SLOT(slotAddPhotoCancel())); connect(d->talker, SIGNAL(signalProgressInfo(QString)), this, SLOT(slotProgressInfo(QString))); connect(d->talker, SIGNAL(signalError(QString)), this, SLOT(slotError(QString))); connect(d->talker, SIGNAL(signalBusy(bool)), this, SLOT(slotBusy(bool))); connect(d->talker, SIGNAL(signalLoginFailed(QString)), this, SLOT(slotLoginFailed(QString))); connect(d->talker, SIGNAL(signalAlbums(QList)), this, SLOT(slotAlbums(QList))); connect(d->talker, SIGNAL(signalAddPhotoSucceeded()), this, SLOT(slotAddPhotoSucceeded())); connect(d->talker, SIGNAL(signalAddPhotoFailed(QString)), this, SLOT(slotAddPhotoFailed(QString))); } void PiwigoWindow::readSettings() { KConfig config; KConfigGroup group = config.group("PiwigoSync Galleries"); if (group.readEntry("Resize", false)) { d->resizeCheckBox->setChecked(true); d->widthSpinBox->setEnabled(true); d->heightSpinBox->setEnabled(true); } else { d->resizeCheckBox->setChecked(false); d->heightSpinBox->setEnabled(false); d->widthSpinBox->setEnabled(false); } d->widthSpinBox->setValue(group.readEntry("Maximum Width", 1600)); d->heightSpinBox->setValue(group.readEntry("Maximum Height", 1600)); d->qualitySpinBox->setValue(group.readEntry("Quality", 95)); } void PiwigoWindow::slotDoLogin() { QUrl url(d->pPiwigo->url()); if (url.scheme().isEmpty()) { url.setScheme(QLatin1String("http")); url.setHost(d->pPiwigo->url()); } // If we've done something clever, save it back to the piwigo. if (!url.url().isEmpty() && d->pPiwigo->url() != url.url()) { d->pPiwigo->setUrl(url.url()); d->pPiwigo->save(); } d->talker->login(url, d->pPiwigo->username(), d->pPiwigo->password()); } void PiwigoWindow::slotLoginFailed(const QString& msg) { if (QMessageBox::question(this, i18n("Login Failed"), i18n("Failed to login into remote piwigo. ") + msg + i18n("\nDo you want to check your settings and try again?")) != QMessageBox::Yes) { close(); return; } QPointer configDlg = new PiwigoLoginDlg(QApplication::activeWindow(), d->pPiwigo, i18n("Edit Piwigo Data") ); - if ( configDlg->exec() != QDialog::Accepted ) + if (configDlg->exec() != QDialog::Accepted) { delete configDlg; return; } slotDoLogin(); delete configDlg; } void PiwigoWindow::slotBusy(bool val) { if (val) { setCursor(Qt::WaitCursor); startButton()->setEnabled(false); } else { setCursor(Qt::ArrowCursor); bool loggedIn = d->talker->loggedIn(); startButton()->setEnabled(loggedIn && d->albumView->currentItem()); } } void PiwigoWindow::slotProgressInfo(const QString& msg) { d->progressDlg->setLabelText(msg); } void PiwigoWindow::slotError(const QString& msg) { d->progressDlg->hide(); QMessageBox::critical(this, QString(), msg); } void PiwigoWindow::slotAlbums(const QList& albumList) { d->albumDict.clear(); d->albumView->clear(); // album work list QList workList(albumList); QList parentItemList; // fill QTreeWidget while ( !workList.isEmpty() ) { // the album to work on PiwigoAlbum album = workList.takeFirst(); int parentRefNum = album.m_parentRefNum; - if ( parentRefNum == -1 ) + if (parentRefNum == -1) { QTreeWidgetItem* const item = new QTreeWidgetItem(); item->setText(0, cleanName(album.m_name) ); item->setIcon(0, QIcon::fromTheme(QLatin1String("inode-directory")) ); item->setData(1, Qt::UserRole, QVariant(album.m_refNum) ); item->setText(2, i18n("Album") ); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Top : " << album.m_name << " " << album.m_refNum << "\n"; d->albumView->addTopLevelItem(item); d->albumDict.insert(album.m_name, album); parentItemList << item; } else { QTreeWidgetItem* parentItem = 0; bool found = false; int i = 0; while ( !found && i < parentItemList.size() ) { parentItem = parentItemList.at(i); if (parentItem && (parentItem->data(1, Qt::UserRole).toInt() == parentRefNum)) { QTreeWidgetItem* const item = new QTreeWidgetItem(parentItem); item->setText(0, cleanName(album.m_name) ); item->setIcon(0, QIcon::fromTheme(QLatin1String("inode-directory")) ); item->setData(1, Qt::UserRole, album.m_refNum ); item->setText(2, i18n("Album") ); parentItem->addChild(item); d->albumDict.insert(album.m_name, album); parentItemList << item; found = true; } i++; } } } } void PiwigoWindow::slotAlbumSelected() { QTreeWidgetItem* const item = d->albumView->currentItem(); // stop loading if user clicked an image if (item && item->text(2) == i18n("Image") ) return; if (!item) { startButton()->setEnabled(false); } else { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Album selected\n"; int albumId = item->data(1, Qt::UserRole).toInt(); qCDebug(DIGIKAM_WEBSERVICES_LOG) << albumId << "\n"; if (d->talker->loggedIn() && albumId ) { startButton()->setEnabled(true); } else { startButton()->setEnabled(false); } } } void PiwigoWindow::slotAddPhoto() { const QList urls(d->iface->currentSelectedItems()); if (urls.isEmpty()) { QMessageBox::critical(this, QString(), i18n("Nothing to upload - please select photos to upload.")); return; } for (QList::const_iterator it = urls.constBegin(); it != urls.constEnd(); ++it) { d->pUploadList->append( (*it).toLocalFile() ); } d->uploadTotal = d->pUploadList->count(); d->progressDlg->reset(); d->progressDlg->setMaximum(d->uploadTotal); d->uploadCount = 0; slotAddPhotoNext(); } void PiwigoWindow::slotAddPhotoNext() { - if ( d->pUploadList->isEmpty() ) + if (d->pUploadList->isEmpty()) { d->progressDlg->reset(); d->progressDlg->hide(); return; } QTreeWidgetItem* const item = d->albumView->currentItem(); int column = d->albumView->currentColumn(); QString albumTitle = item->text(column); const PiwigoAlbum& album = d->albumDict.value(albumTitle); QString photoPath = d->pUploadList->takeFirst(); bool res = d->talker->addPhoto(album.m_refNum, photoPath, d->resizeCheckBox->isChecked(), d->widthSpinBox->value(), d->heightSpinBox->value(), d->qualitySpinBox->value()); if (!res) { slotAddPhotoFailed(i18n("The file %1 is not a supported image or video format", QUrl(photoPath).fileName()) ); return; } d->progressDlg->setLabelText(i18n("Uploading file %1", QUrl(photoPath).fileName()) ); if (d->progressDlg->isHidden()) d->progressDlg->show(); } void PiwigoWindow::slotAddPhotoSucceeded() { d->uploadCount++; d->progressDlg->setValue(d->uploadCount); slotAddPhotoNext(); } void PiwigoWindow::slotAddPhotoFailed(const QString& msg) { d->progressDlg->reset(); d->progressDlg->hide(); if (QMessageBox::question(this, i18n("Uploading Failed"), i18n("Failed to upload media into remote Piwigo. ") + msg + i18n("\nDo you want to continue?")) != QMessageBox::Yes) { return; } else { slotAddPhotoNext(); } } void PiwigoWindow::slotAddPhotoCancel() { d->progressDlg->reset(); d->progressDlg->hide(); d->talker->cancel(); } void PiwigoWindow::slotEnableSpinBox(int n) { bool b; switch (n) { case 0: b = false; break; case 1: case 2: b = true; break; default: b = false; break; } d->widthSpinBox->setEnabled(b); d->heightSpinBox->setEnabled(b); d->qualitySpinBox->setEnabled(b); } void PiwigoWindow::slotSettings() { // TODO: reload albumlist if OK slot used. QPointer dlg = new PiwigoLoginDlg(QApplication::activeWindow(), d->pPiwigo, i18n("Edit Piwigo Data") ); - if ( dlg->exec() == QDialog::Accepted ) + if (dlg->exec() == QDialog::Accepted) { slotDoLogin(); } delete dlg; } QString PiwigoWindow::cleanName(const QString& str) const { QString plain = str; plain.replace(QLatin1String("<"), QLatin1String("<")); plain.replace(QLatin1String(">"), QLatin1String(">")); plain.replace(QLatin1String("""), QLatin1String("\"")); plain.replace(QLatin1String("&"), QLatin1String("&")); return plain; } } // namespace DigikamGenericPiwigoPlugin diff --git a/core/dplugins/generic/webservices/smugmug/smugtalker.cpp b/core/dplugins/generic/webservices/smugmug/smugtalker.cpp index 69d8ba0c4c..731b24fc1a 100644 --- a/core/dplugins/generic/webservices/smugmug/smugtalker.cpp +++ b/core/dplugins/generic/webservices/smugmug/smugtalker.cpp @@ -1,1187 +1,1187 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2008-12-01 * Description : a tool to export images to Smugmug web service * * Copyright (C) 2008-2009 by Luka Renko * Copyright (C) 2008-2019 by Gilles Caulier * Copyright (C) 2018 by Thanh Trung Dinh * * 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 "smugtalker.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Local includes #include "digikam_debug.h" #include "digikam_version.h" #include "smugmpform.h" #include "smugitem.h" // O2 includes #include "wstoolutils.h" #include "o0settingsstore.h" #include "o1requestor.h" #include "o0globals.h" using namespace Digikam; namespace DigikamGenericSmugPlugin { class Q_DECL_HIDDEN SmugTalker::Private { public: enum State { SMUG_LOGIN = 0, SMUG_LOGOUT, SMUG_LISTALBUMS, SMUG_LISTPHOTOS, SMUG_LISTALBUMTEMPLATES, SMUG_CREATEALBUM, SMUG_ADDPHOTO, SMUG_GETPHOTO /* * SMUG_LISTCATEGORIES, * SMUG_LISTSUBCATEGORIES, */ }; public: explicit Private() { parent = 0; userAgent = QString::fromLatin1("digiKam/%1 (digikamdeveloper@gmail.com)").arg(digiKamVersion()); apiVersion = QLatin1String("v2"); apiURL = QLatin1String("https://api.smugmug.com%1"); uploadUrl = QLatin1String("https://upload.smugmug.com/"); requestTokenUrl = QLatin1String("https://api.smugmug.com/services/oauth/1.0a/getRequestToken"); authUrl = QLatin1String("https://api.smugmug.com/services/oauth/1.0a/authorize"); accessTokenUrl = QLatin1String("https://api.smugmug.com/services/oauth/1.0a/getAccessToken"); // apikey = QLatin1String("66NGWpNDWmnsW6qZKLp6hNMTDZ9C24pN"); // clientSecret = QLatin1String("GbtsCvH3GMGnQ6Lf4XmGXwMQs2pm5SpSvVJdPsQDpHMRbsPQSWqzhxXKRRXhMwP5"); apikey = QLatin1String("P3GR322MB4rf3dZRxDZNFv8cbK6sLPdV"); clientSecret = QLatin1String("trJrZT3pHQRpZB8Z3LMGCL39g9q7nWJPBzZTQSWhzCnmTmtqqW5xxXdBn6fVhM3p"); iface = 0; netMngr = 0; reply = 0; state = SMUG_LOGOUT; requestor = 0; o1 = 0; settings = 0; } public: QWidget* parent; QString userAgent; QString apiURL; QString uploadUrl; QString requestTokenUrl; QString authUrl; QString accessTokenUrl; QString apiVersion; QString apikey; QString clientSecret; QString sessionID; SmugUser user; DInfoInterface* iface; QNetworkAccessManager* netMngr; QNetworkReply* reply; State state; QSettings* settings; O1Requestor* requestor; O1SmugMug* o1; }; SmugTalker::SmugTalker(DInfoInterface* const iface, QWidget* const parent) : d(new Private) { d->parent = parent; d->iface = iface; d->netMngr = new QNetworkAccessManager(this); connect(d->netMngr, SIGNAL(finished(QNetworkReply*)), this, SLOT(slotFinished(QNetworkReply*))); // Init d->o1 = new O1SmugMug(this, d->netMngr); // Config for authentication flow d->o1->setRequestTokenUrl(QUrl(d->requestTokenUrl)); d->o1->setAuthorizeUrl(QUrl(d->authUrl)); d->o1->setAccessTokenUrl(QUrl(d->accessTokenUrl)); d->o1->setLocalPort(8000); // Application credentials d->o1->setClientId(d->apikey); d->o1->setClientSecret(d->clientSecret); // Set userAgent to work around error : // O1::onTokenRequestError: 201 "Error transferring requestTokenUrl() - server replied: Forbidden" "Bad bot" d->o1->setUserAgent(d->userAgent.toUtf8()); // Setting to store oauth config d->settings = WSToolUtils::getOauthSettings(this); O0SettingsStore* const store = new O0SettingsStore(d->settings, QLatin1String(O2_ENCRYPTION_KEY), this); store->setGroupKey(QLatin1String("Smugmug")); d->o1->setStore(store); // Connect signaux slots connect(d->o1, SIGNAL(linkingFailed()), this, SLOT(slotLinkingFailed())); connect(this, SIGNAL(signalLinkingSucceeded()), this, SLOT(slotLinkingSucceeded())); connect(d->o1, SIGNAL(linkingSucceeded()), this, SLOT(slotLinkingSucceeded())); connect(d->o1, SIGNAL(openBrowser(QUrl)), this, SLOT(slotOpenBrowser(QUrl))); d->requestor = new O1Requestor(d->netMngr, d->o1, this); } SmugTalker::~SmugTalker() { /** * We shouldn't logout without user's consent * * if (loggedIn()) * { * logout(); * * while (d->reply && d->reply->isRunning()) * { * qApp->processEvents(); * } * } */ if (d->reply) d->reply->abort(); delete d; } //TODO: Porting to O2 void SmugTalker::link() { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "LINK to Smug "; d->o1->link(); } void SmugTalker::unlink() { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "UNLINK to Smug "; d->o1->unlink(); } void SmugTalker::removeUserAccount(const QString& /*userName*/) { /* if (userName.startsWith(d->serviceName)) { d->settings->beginGroup(userName); d->settings->remove(QString()); d->settings->endGroup(); } */ } bool SmugTalker::loggedIn() const { return d->o1->linked(); } void SmugTalker::slotLinkingFailed() { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "LINK to Smug fail"; emit signalBusy(false); getLoginedUser(); } void SmugTalker::slotLinkingSucceeded() { if (!d->o1->linked()) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "UNLINK to Smug ok"; // Remove user account removeUserAccount(d->user.nickName); // Clear user field d->user.clear(); // Set state to SMUG_LOGOUT d->state = Private::SMUG_LOGOUT; emit signalBusy(false); return; } qCDebug(DIGIKAM_WEBSERVICES_LOG) << "LINK to Smug ok"; getLoginedUser(); } void SmugTalker::slotOpenBrowser(const QUrl& url) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Open Browser..."; QDesktopServices::openUrl(url); } void SmugTalker::slotCloseBrowser() { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Close Browser..."; } SmugUser SmugTalker::getUser() const { return d->user; } /** * (Trung) * There are some characters not valid for album title (e.g "_") * and for url (e.g "-") that are not mentioned on the API page, * so if found, that has to be treated here */ QString SmugTalker::createAlbumName(const QString& word) { QString w(word); // First we remove space at beginning and end w = w.trimmed(); // We replace all character "_" with space w = w.replace(QLatin1Char('_'), QLatin1Char(' ')); // Then we replace first letter with its uppercase w.replace(0, 1, w[0].toUpper()); qCDebug(DIGIKAM_WEBSERVICES_LOG) << w; return w; } QString SmugTalker::createAlbumUrl(const QString& name) { QString n(name); // First we create a valid name n = createAlbumName(n); // Then we replace space with "-" QStringList words = n.split(QLatin1Char(' ')); n = words.join(QLatin1Char('-')); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "url name : " << n; return n; } void SmugTalker::cancel() { if (d->reply) { d->reply->abort(); d->reply = 0; } emit signalBusy(false); } void SmugTalker::login() { if (d->reply) { d->reply->abort(); d->reply = 0; } emit signalBusy(true); emit signalLoginProgress(1, 4, i18n("Logging in to SmugMug service...")); // Build authentication url O1SmugMug::AuthorizationUrlBuilder builder; builder.setAccess(O1SmugMug::AccessFull); builder.setPermissions(O1SmugMug::PermissionsModify); d->o1->initAuthorizationUrl(builder); - if(!d->o1->linked()) + if (!d->o1->linked()) { link(); } else { emit signalLinkingSucceeded(); } } void SmugTalker::getLoginedUser() { QUrl url(d->apiURL.arg(QLatin1String("/api/v2!authuser"))); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "url = " << url.url(); QList reqParams = QList(); QNetworkRequest netRequest(url); netRequest.setRawHeader("Accept", "application/json"); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/json")); netRequest.setHeader(QNetworkRequest::UserAgentHeader, d->userAgent); d->reply = d->requestor->get(netRequest, reqParams); d->state = Private::SMUG_LOGIN; } void SmugTalker::logout() { if (d->reply) { d->reply->abort(); d->reply = 0; } emit signalBusy(true); unlink(); } void SmugTalker::listAlbums(const QString& /*nickName*/) { if (d->reply) { d->reply->abort(); d->reply = 0; } emit signalBusy(true); QUrl url(d->apiURL.arg(QString::fromLatin1("%1!albums").arg(d->user.userUri))); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "url = " << url.url(); QList reqParams = QList(); QNetworkRequest netRequest(url); netRequest.setRawHeader("Accept", "application/json"); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/json")); netRequest.setHeader(QNetworkRequest::UserAgentHeader, d->userAgent); d->reply = d->requestor->get(netRequest, reqParams); d->state = Private::SMUG_LISTALBUMS; } void SmugTalker::listPhotos(const qint64 /*albumID*/, const QString& albumKey, const QString& /*albumPassword*/, const QString& /*sitePassword*/) { if (d->reply) { d->reply->abort(); d->reply = 0; } emit signalBusy(true); QUrl url(d->apiURL.arg(QString::fromLatin1("/api/v2/album/%1!images").arg(albumKey))); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "list photo " << url.url(); QList reqParams = QList(); QNetworkRequest netRequest(url); netRequest.setRawHeader("Accept", "application/json"); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/json")); netRequest.setHeader(QNetworkRequest::UserAgentHeader, d->userAgent); d->reply = d->requestor->get(netRequest, reqParams); d->state = Private::SMUG_LISTPHOTOS; } void SmugTalker::listAlbumTmpl() { if (d->reply) { d->reply->abort(); d->reply = 0; } emit signalBusy(true); QUrl url(d->apiURL.arg(QString::fromLatin1("%1!albumtemplates").arg(d->user.userUri))); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "url to listAlbumTmpl " << url.url(); QList reqParams = QList(); QNetworkRequest netRequest(url); netRequest.setRawHeader("Accept", "application/json"); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/json")); netRequest.setHeader(QNetworkRequest::UserAgentHeader, d->userAgent); d->reply = d->requestor->get(netRequest, reqParams); d->state = Private::SMUG_LISTALBUMTEMPLATES; } /** * Categories deprecated in API v2 * void SmugTalker::listCategories() { if (d->reply) { d->reply->abort(); d->reply = 0; } emit signalBusy(true); QUrl url(d->apiURL); QUrlQuery q; q.addQueryItem(QLatin1String("method"), QLatin1String("smugmug.categories.get")); q.addQueryItem(QLatin1String("SessionID"), d->sessionID); url.setQuery(q); QNetworkRequest netRequest(url); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/x-www-form-urlencoded")); netRequest.setHeader(QNetworkRequest::UserAgentHeader, d->userAgent); d->reply = d->netMngr->get(netRequest); d->state = Private::SMUG_LISTCATEGORIES; } void SmugTalker::listSubCategories(qint64 categoryID) { if (d->reply) { d->reply->abort(); d->reply = 0; } emit signalBusy(true); QUrl url(d->apiURL); QUrlQuery q; q.addQueryItem(QLatin1String("method"), QLatin1String("smugmug.subcategories.get")); q.addQueryItem(QLatin1String("SessionID"), d->sessionID); q.addQueryItem(QLatin1String("CategoryID"), QString::number(categoryID)); url.setQuery(q); QNetworkRequest netRequest(url); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/x-www-form-urlencoded")); netRequest.setHeader(QNetworkRequest::UserAgentHeader, d->userAgent); d->reply = d->netMngr->get(netRequest); d->state = Private::SMUG_LISTSUBCATEGORIES; } */ void SmugTalker::createAlbum(const SmugAlbum& album) { if (d->reply) { d->reply->abort(); d->reply = 0; } emit signalBusy(true); QUrl url(d->apiURL.arg(QString::fromLatin1("%1!albums").arg(d->user.folderUri))); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "url to post " << url.url(); QList reqParams = QList(); /** * Something must to be remembered here is that we HAVE TO start a name with upper case !!! * And url name with '-' instead of space */ QByteArray data; data += "{\"Name\": \""; data += createAlbumName(album.title).toUtf8(); data += "\",\"UrlName\":\""; data += createAlbumUrl(album.title).toUtf8(); data += "\",\"Privacy\":\"Public\"}"; qCDebug(DIGIKAM_WEBSERVICES_LOG) << data; QNetworkRequest netRequest(url); netRequest.setRawHeader("Accept", "application/json"); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/json")); netRequest.setHeader(QNetworkRequest::UserAgentHeader, d->userAgent); d->reply = d->requestor->post(netRequest, reqParams, data); d->state = Private::SMUG_CREATEALBUM; } bool SmugTalker::addPhoto(const QString& imgPath, qint64 /*albumID*/, const QString& albumKey, const QString& caption) { if (d->reply) { d->reply->abort(); d->reply = 0; } emit signalBusy(true); QString imgName = QFileInfo(imgPath).fileName(); // load temporary image to buffer QFile imgFile(imgPath); if (!imgFile.open(QIODevice::ReadOnly)) { emit signalBusy(false); return false; } QByteArray imgData = imgFile.readAll(); imgFile.close(); SmugMPForm form; if (!caption.isEmpty()) form.addPair(QLatin1String("Caption"), caption); if (!form.addFile(imgName, imgPath)) return false; form.finish(); QString customHdr; QUrl url(d->uploadUrl); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "url to upload " << url.url(); QList reqParams = QList(); QNetworkRequest netRequest(url); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, form.contentType()); netRequest.setHeader(QNetworkRequest::UserAgentHeader, d->userAgent); netRequest.setRawHeader("X-Smug-Caption", caption.toUtf8()); netRequest.setRawHeader("X-Smug-FileName", imgName.toUtf8()); netRequest.setRawHeader("X-Smug-AlbumUri", QString::fromLatin1("/api/v2/album/%1").arg(albumKey).toUtf8()); netRequest.setRawHeader("X-Smug-ResponseType", "JSON"); netRequest.setRawHeader("X-Smug-Version", d->apiVersion.toLatin1()); d->reply = d->requestor->post(netRequest, reqParams, form.formData()); d->state = Private::SMUG_ADDPHOTO; return true; } void SmugTalker::getPhoto(const QString& imgPath) { if (d->reply) { d->reply->abort(); d->reply = 0; } emit signalBusy(true); QUrl url(imgPath); QUrlQuery q; q.addQueryItem(QLatin1String("APIKey"), d->apikey); url.setQuery(q); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "download link for image " << url.url(); QNetworkRequest netRequest(url); netRequest.setHeader(QNetworkRequest::UserAgentHeader, d->userAgent); d->reply = d->netMngr->get(netRequest); d->state = Private::SMUG_GETPHOTO; } QString SmugTalker::errorToText(int errCode, const QString& errMsg) const { QString transError; qCDebug(DIGIKAM_WEBSERVICES_LOG) << "errorToText: " << errCode << ": " << errMsg; switch (errCode) { case 0: transError = QString(); break; case 1: transError = i18n("Login failed"); break; case 4: transError = i18n("Invalid user/nick/password"); break; case 18: transError = i18n("Invalid API key"); break; default: transError = errMsg; break; } return transError; } void SmugTalker::slotFinished(QNetworkReply* reply) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "error code : " << reply->error() << "error text " << reply->errorString(); if (reply != d->reply) { return; } d->reply = 0; if (reply->error() != QNetworkReply::NoError) { if (d->state == Private::SMUG_LOGIN) { d->sessionID.clear(); d->user.clear(); emit signalBusy(false); emit signalLoginDone(reply->error(), reply->errorString()); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "error code : " << reply->error() << "error text " << reply->errorString(); } else if (d->state == Private::SMUG_ADDPHOTO) { emit signalBusy(false); emit signalAddPhotoDone(reply->error(), reply->errorString()); } else if (d->state == Private::SMUG_GETPHOTO) { emit signalBusy(false); emit signalGetPhotoDone(reply->error(), reply->errorString(), QByteArray()); } else { emit signalBusy(false); QMessageBox::critical(QApplication::activeWindow(), i18n("Error"), reply->errorString()); } reply->deleteLater(); return; } QByteArray buffer = reply->readAll(); switch(d->state) { case (Private::SMUG_LOGIN): parseResponseLogin(buffer); break; case (Private::SMUG_LISTALBUMS): parseResponseListAlbums(buffer); break; case (Private::SMUG_LISTPHOTOS): parseResponseListPhotos(buffer); break; case (Private::SMUG_LISTALBUMTEMPLATES): parseResponseListAlbumTmpl(buffer); break; /* case (Private::SMUG_LISTCATEGORIES): parseResponseListCategories(buffer); break; case (Private::SMUG_LISTSUBCATEGORIES): parseResponseListSubCategories(buffer); break; */ case (Private::SMUG_CREATEALBUM): parseResponseCreateAlbum(buffer); break; case (Private::SMUG_ADDPHOTO): parseResponseAddPhoto(buffer); break; case (Private::SMUG_GETPHOTO): // all we get is data of the image emit signalBusy(false); emit signalGetPhotoDone(0, QString(), buffer); break; default: // Private::SMUG_LOGIN break; } reply->deleteLater(); } void SmugTalker::parseResponseLogin(const QByteArray& data) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseLogin"; QJsonParseError err; QJsonDocument doc = QJsonDocument::fromJson(data, &err); emit signalLoginProgress(3); if (err.error != QJsonParseError::NoError) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "failed to parse to json"; qCDebug(DIGIKAM_WEBSERVICES_LOG) << "errCode " << err.error; emit signalLoginDone(err.error, errorToText(err.error, err.errorString())); emit signalBusy(false); return; } QJsonObject jsonObject = doc.object(); QJsonObject response = jsonObject[QLatin1String("Response")].toObject(); QJsonObject userObject = response[QLatin1String("User")].toObject(); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "json object " << userObject; d->user.displayName = userObject[QLatin1String("Name")].toString(); d->user.nickName = userObject[QLatin1String("NickName")].toString(); d->user.userUri = userObject[QLatin1String("Uri")].toString(); QJsonObject Uris = userObject[QLatin1String("Uris")].toObject(); QJsonObject node = Uris[QLatin1String("Node")].toObject(); QJsonObject folder = Uris[QLatin1String("Folder")].toObject(); d->user.nodeUri = node[QLatin1String("Uri")].toString(); d->user.folderUri = folder[QLatin1String("Uri")].toString(); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "json data parse : " << d->user.displayName << "+ " << d->user.nodeUri; emit signalLoginProgress(4); emit signalBusy(false); emit signalLoginDone(0, QString()); } /** * Not necessary anymore * void SmugTalker::parseResponseLogout(const QByteArray& data) { int errCode = -1; QString errMsg; QDomDocument doc(QLatin1String("logout")); if (!doc.setContent(data)) return; qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Parse Logout response:" << endl << data; QDomElement e = doc.documentElement(); for (QDomNode node = e.firstChild(); !node.isNull(); node = node.nextSibling()) { if (!node.isElement()) continue; e = node.toElement(); if (e.tagName() == QLatin1String("Logout")) { errCode = 0; } else if (e.tagName() == QLatin1String("err")) { errCode = e.attribute(QLatin1String("code")).toInt(); errMsg = e.attribute(QLatin1String("msg")); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Error:" << errCode << errMsg; } } // consider we are logged out in any case d->sessionID.clear(); d->user.clear(); emit signalBusy(false); } */ void SmugTalker::parseResponseAddPhoto(const QByteArray& data) { // A multi-part put response (which we get now) looks like: // // // smugmug.images.upload // 884775096 // L7aq5 // http://froody.smugmug.com/Other/Test/12372176_y7yNq#884775096_L7aq5 // // A simple put response (which we used to get) looks like: // // // smugmug.images.upload // // // Since all we care about is success or not, we can just check the rsp // stat. qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseAddPhoto"; QJsonParseError err; QJsonDocument doc = QJsonDocument::fromJson(data, &err); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "json doc " << doc; - if(err.error != QJsonParseError::NoError) + if (err.error != QJsonParseError::NoError) { emit signalBusy(false); emit signalAddPhotoDone(err.error, errorToText(err.error, err.errorString())); return; } emit signalBusy(false); emit signalAddPhotoDone(err.error, errorToText(err.error, err.errorString())); } void SmugTalker::parseResponseCreateAlbum(const QByteArray& data) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseCreateAlbum"; QJsonParseError err; QJsonDocument doc = QJsonDocument::fromJson(data, &err); if (err.error != QJsonParseError::NoError) { emit signalBusy(false); emit signalCreateAlbumDone(err.error, err.errorString(), 0, QString()); return; } QJsonObject jsonObject = doc.object(); QJsonObject response = jsonObject[QLatin1String("Response")].toObject(); QJsonObject album = response[QLatin1String("Album")].toObject(); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "json data : " << jsonObject; QString newAlbumKey = album[QLatin1String("AlbumKey")].toString(); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "newAlbumKey " << newAlbumKey; emit signalBusy(false); emit signalCreateAlbumDone(0, errorToText(0, QString()), 0, newAlbumKey); } void SmugTalker::parseResponseListAlbums(const QByteArray& data) { QJsonParseError err; QJsonDocument doc = QJsonDocument::fromJson(data, &err); - if(err.error != QJsonParseError::NoError) + if (err.error != QJsonParseError::NoError) { emit signalBusy(false); emit signalListAlbumsDone(err.error,i18n("Failed to list albums"), QList()); return; } QJsonObject jsonObject = doc.object(); QJsonObject response = jsonObject[QLatin1String("Response")].toObject(); QJsonArray jsonArray = response[QLatin1String("Album")].toArray(); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseListAlbum : " << jsonObject; QList albumList; foreach (const QJsonValue& value, jsonArray) { QJsonObject obj = value.toObject(); SmugAlbum album; album.nodeID = obj[QLatin1String("NodeID")].toString(); album.name = obj[QLatin1String("Name")].toString(); album.key = obj[QLatin1String("AlbumKey")].toString(); album.title = obj[QLatin1String("Title")].toString(); album.description = obj[QLatin1String("Description")].toString(); album.keywords = obj[QLatin1String("Keywords")].toString(); album.canShare = obj[QLatin1String("CanShare")].toBool(); album.passwordHint = obj[QLatin1String("PasswordHint")].toString(); album.imageCount = obj[QLatin1String("ImageCount")].toInt(); albumList.append(album); QStringList albumParams; albumParams << album.nodeID << album.name << album.key << album.title << album.description << album.keywords << QString::number(album.canShare) << album.passwordHint << QString::number(album.imageCount); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "album " << albumParams.join(QLatin1String(",")); } std::sort(albumList.begin(), albumList.end(), SmugAlbum::lessThan); emit signalBusy(false); emit signalListAlbumsDone(err.error, errorToText(err.error, err.errorString()), albumList); } void SmugTalker::parseResponseListPhotos(const QByteArray& data) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseListPhotos"; QJsonParseError err; QJsonDocument doc = QJsonDocument::fromJson(data, &err); - if(err.error != QJsonParseError::NoError) + if (err.error != QJsonParseError::NoError) { emit signalBusy(false); emit signalListPhotosDone(err.error, errorToText(err.error, err.errorString()), QList()); return; } QJsonObject jsonObject = doc.object(); QJsonObject response = jsonObject[QLatin1String("Response")].toObject(); QJsonArray jsonArray = response[QLatin1String("AlbumImage")].toArray(); QList photosList; - foreach(const QJsonValue& value, jsonArray) + foreach (const QJsonValue& value, jsonArray) { QJsonObject obj = value.toObject(); SmugPhoto photo; photo.key = obj[QLatin1String("ImageKey")].toString(); photo.caption = obj[QLatin1String("Caption")].toString(); photo.keywords = obj[QLatin1String("Keywords")].toString(); photo.thumbURL = obj[QLatin1String("ThumbnailUrl")].toString(); photo.originalURL = obj[QLatin1String("ArchivedUri")].toString(); photosList.append(photo); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "photo key: " << photo.key << ", captions: " << photo.caption << ", keywords: " << photo.keywords << ", ThumbnailUrl " << photo.thumbURL << ", originalURL " << photo.originalURL; } emit signalBusy(false); emit signalListPhotosDone(0, QString(), photosList); } void SmugTalker::parseResponseListAlbumTmpl(const QByteArray& data) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "ParseResponseListAlbumTmpl"; QJsonParseError err; QJsonDocument doc = QJsonDocument::fromJson(data, &err); - if(err.error != QJsonParseError::NoError) + if (err.error != QJsonParseError::NoError) { emit signalBusy(false); emit signalListAlbumTmplDone(err.error,i18n("Failed to list album template"), QList()); return; } QJsonObject jsonObject = doc.object(); QJsonObject response = jsonObject[QLatin1String("Response")].toObject(); QJsonArray jsonArray = response[QLatin1String("AlbumTemplate")].toArray(); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "listAlbumTmpl = " << jsonArray; QList albumTmplList; - foreach(const QJsonValue &value, jsonArray) + foreach (const QJsonValue &value, jsonArray) { QJsonObject obj = value.toObject(); SmugAlbumTmpl albumTmpl; albumTmpl.name = obj[QLatin1String("Name")].toString(); albumTmpl.uri = obj[QLatin1String("Uri")].toString(); albumTmpl.isPublic = obj[QLatin1String("Public")].toBool(); albumTmpl.password = obj[QLatin1String("Password")].toString(); albumTmpl.passwordHint = obj[QLatin1String("PasswordHint")].toString(); albumTmplList.append(albumTmpl); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "albumTmpl : name " << albumTmpl.name << ", uri : " << albumTmpl.uri << ", isPublic " << albumTmpl.isPublic << ", password " << albumTmpl.password << ", passwordHint " << albumTmpl.passwordHint; } emit signalBusy(false); emit signalListAlbumTmplDone(0, errorToText(0, QString()), albumTmplList); } /** * Categories are deprecated in API v2 * void SmugTalker::parseResponseListCategories(const QByteArray& data) { int errCode = -1; QString errMsg; QDomDocument doc(QLatin1String("categories.get")); if (!doc.setContent(data)) return; qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Parse Categories response:" << endl << data; QList categoriesList; QDomElement e = doc.documentElement(); for (QDomNode node = e.firstChild(); !node.isNull(); node = node.nextSibling()) { if (!node.isElement()) continue; e = node.toElement(); if (e.tagName() == QLatin1String("Categories")) { for (QDomNode nodeC = e.firstChild(); !nodeC.isNull(); nodeC = nodeC.nextSibling()) { if (!nodeC.isElement()) continue; QDomElement e = nodeC.toElement(); if (e.tagName() == QLatin1String("Category")) { SmugCategory category; category.id = e.attribute(QLatin1String("id")).toLongLong(); category.name = htmlToText(e.attribute(QLatin1String("Name"))); categoriesList.append(category); } } errCode = 0; } else if (e.tagName() == QLatin1String("err")) { errCode = e.attribute(QLatin1String("code")).toInt(); errMsg = e.attribute(QLatin1String("msg")); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Error:" << errCode << errMsg; } } if (errCode == 15) // 15: empty list errCode = 0; emit signalBusy(false); emit signalListCategoriesDone(errCode, errorToText(errCode, errMsg), categoriesList); } void SmugTalker::parseResponseListSubCategories(const QByteArray& data) { int errCode = -1; QString errMsg; QDomDocument doc(QLatin1String("subcategories.get")); if (!doc.setContent(data)) return; qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Parse SubCategories response:" << endl << data; QList categoriesList; QDomElement e = doc.documentElement(); for (QDomNode node = e.firstChild(); !node.isNull(); node = node.nextSibling()) { if (!node.isElement()) continue; e = node.toElement(); if (e.tagName() == QLatin1String("SubCategories")) { for (QDomNode nodeC = e.firstChild(); !nodeC.isNull(); nodeC = nodeC.nextSibling()) { if (!nodeC.isElement()) continue; e = nodeC.toElement(); if (e.tagName() == QLatin1String("SubCategory")) { SmugCategory category; category.id = e.attribute(QLatin1String("id")).toLongLong(); category.name = htmlToText(e.attribute(QLatin1String("Name"))); categoriesList.append(category); } } errCode = 0; } else if (e.tagName() == QLatin1String("err")) { errCode = e.attribute(QLatin1String("code")).toInt(); errMsg = e.attribute(QLatin1String("msg")); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Error:" << errCode << errMsg; } } if (errCode == 15) // 15: empty list errCode = 0; emit signalBusy(false); emit signalListSubCategoriesDone(errCode, errorToText(errCode, errMsg), categoriesList); } */ QString SmugTalker::htmlToText(const QString& htmlText) const { QTextDocument txtDoc; txtDoc.setHtml(htmlText); return txtDoc.toPlainText(); } } // namespace DigikamGenericSmugPlugin diff --git a/core/dplugins/generic/webservices/smugmug/smugwindow.cpp b/core/dplugins/generic/webservices/smugmug/smugwindow.cpp index 96919366ea..c921526a92 100644 --- a/core/dplugins/generic/webservices/smugmug/smugwindow.cpp +++ b/core/dplugins/generic/webservices/smugmug/smugwindow.cpp @@ -1,1029 +1,1029 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2005-17-06 * Description : a tool to export images to Smugmug web service * * Copyright (C) 2005-2008 by Vardhman Jain * Copyright (C) 2008-2019 by Gilles Caulier * Copyright (C) 2008-2009 by Luka Renko * * 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 "smugwindow.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include #include #include // Local includes #include "digikam_debug.h" #include "ditemslist.h" #include "wstoolutils.h" #include "digikam_version.h" #include "dprogresswdg.h" #include "dmetadata.h" #include "previewloadthread.h" #include "smugitem.h" #include "smugtalker.h" #include "smugwidget.h" #include "smugnewalbumdlg.h" namespace DigikamGenericSmugPlugin { class Q_DECL_HIDDEN SmugWindow::Private { public: explicit Private() { import = false; imagesCount = 0; imagesTotal = 0; anonymousImport = false; currentAlbumID = 0; currentTmplID = 0; currentCategoryID = 0; loginDlg = 0; talker = 0; widget = 0; albumDlg = 0; iface = 0; } bool import; unsigned int imagesCount; unsigned int imagesTotal; QString tmpDir; QString tmpPath; bool anonymousImport; QString anonymousNick; QString email; QString password; qint64 currentAlbumID; QString currentAlbumKey; qint64 currentTmplID; qint64 currentCategoryID; WSLoginDialog* loginDlg; QList transferQueue; SmugTalker* talker; SmugWidget* widget; SmugNewAlbumDlg* albumDlg; DInfoInterface* iface; }; SmugWindow::SmugWindow(DInfoInterface* const iface, QWidget* const /*parent*/, bool import, QString /*nickName*/) : WSToolDialog(0, QString::fromLatin1("Smug %1 Dialog").arg(import ? QLatin1String("Import") : QLatin1String("Export"))), d(new Private) { d->tmpPath.clear(); d->tmpDir = WSToolUtils::makeTemporaryDir("smug").absolutePath() + QLatin1Char('/');; d->import = import; d->iface = iface; d->widget = new SmugWidget(this, iface, import); setMainWidget(d->widget); setModal(false); if (import) { setWindowTitle(i18n("Import from SmugMug Web Service")); startButton()->setText(i18n("Start Download")); startButton()->setToolTip(i18n("Start download from SmugMug web service")); d->widget->setMinimumSize(300, 400); } else { setWindowTitle(i18n("Export to SmugMug Web Service")); startButton()->setText(i18n("Start Upload")); startButton()->setToolTip(i18n("Start upload to SmugMug web service")); d->widget->setMinimumSize(700, 500); } connect(d->widget, SIGNAL(signalUserChangeRequest(bool)), this, SLOT(slotUserChangeRequest(bool)) ); connect(d->widget->m_imgList, SIGNAL(signalImageListChanged()), this, SLOT(slotImageListChanged()) ); connect(d->widget->m_reloadAlbumsBtn, SIGNAL(clicked()), this, SLOT(slotReloadAlbumsRequest()) ); connect(d->widget->m_newAlbumBtn, SIGNAL(clicked()), this, SLOT(slotNewAlbumRequest()) ); connect(startButton(), &QPushButton::clicked, this, &SmugWindow::slotStartTransfer); connect(this, &WSToolDialog::cancelClicked, this, &SmugWindow::slotCancelClicked); connect(this, &QDialog::finished, this, &SmugWindow::slotDialogFinished); // ------------------------------------------------------------------------ /** * This is deprecated because we know use O2 to login * * if (nickName.isEmpty()) * { * d->loginDlg = new WSLoginDialog(this, * i18n("Enter the email address and password for your " * "SmugMug account")); * } */ // ------------------------------------------------------------------------ d->albumDlg = new SmugNewAlbumDlg(this); /** * Categories are deprecated * * connect(d->albumDlg->categoryCombo(), SIGNAL(currentIndexChanged(int)), * this, SLOT(slotCategorySelectionChanged(int)) ); * connect(d->albumDlg->templateCombo(), SIGNAL(currentIndexChanged(int)), * this, SLOT(slotTemplateSelectionChanged(int)) ); */ // ------------------------------------------------------------------------ d->talker = new SmugTalker(d->iface, this); connect(d->talker, SIGNAL(signalBusy(bool)), this, SLOT(slotBusy(bool))); connect(d->talker, SIGNAL(signalLoginProgress(int,int,QString)), this, SLOT(slotLoginProgress(int,int,QString))); connect(d->talker, SIGNAL(signalLoginDone(int,QString)), this, SLOT(slotLoginDone(int,QString))); connect(d->talker, SIGNAL(signalAddPhotoDone(int,QString)), this, SLOT(slotAddPhotoDone(int,QString))); connect(d->talker, SIGNAL(signalGetPhotoDone(int,QString,QByteArray)), this, SLOT(slotGetPhotoDone(int,QString,QByteArray))); connect(d->talker, SIGNAL(signalCreateAlbumDone(int,QString,qint64,QString)), this, SLOT(slotCreateAlbumDone(int,QString,qint64,QString))); connect(d->talker, SIGNAL(signalListAlbumsDone(int,QString,QList)), this, SLOT(slotListAlbumsDone(int,QString,QList))); connect(d->talker, SIGNAL(signalListPhotosDone(int,QString,QList)), this, SLOT(slotListPhotosDone(int,QString,QList))); connect(d->talker, SIGNAL(signalListAlbumTmplDone(int,QString,QList)), this, SLOT(slotListAlbumTmplDone(int,QString,QList))); /** * Categories deprecated in API v2 * connect(d->talker, SIGNAL(signalListCategoriesDone(int,QString,QList)), * this, SLOT(slotListCategoriesDone(int,QString,QList))); * connect(d->talker, SIGNAL(signalListSubCategoriesDone(int,QString,QList)), * this, SLOT(slotListSubCategoriesDone(int,QString,QList))); */ connect(d->widget->progressBar(), SIGNAL(signalProgressCanceled()), this, SLOT(slotStopAndCloseProgressBar())); // ------------------------------------------------------------------------ readSettings(); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Calling Login method"; buttonStateChange(d->talker->loggedIn()); authenticate(); -// if(!nickName.isEmpty()) +// if (!nickName.isEmpty()) // { // qCDebug(DIGIKAM_WEBSERVICES_LOG) << "login with nickname"; // authenticateWithNickName(nickName); // } // else // { // if (d->import) // { // // if no e-mail, switch to anonymous login // if (d->anonymousImport || d->email.isEmpty()) // { // d->anonymousImport = true; // authenticate(); // } // else // { // authenticate(d->email, d->password); // } // // d->widget->setAnonymous(d->anonymousImport); // } // else // { // // export cannot login anonymously: pop-up login window` // if (d->email.isEmpty()) // slotUserChangeRequest(false); // else // authenticate(d->email, d->password); // } // } } SmugWindow::~SmugWindow() { WSToolUtils::removeTemporaryDir("smug"); delete d->talker; delete d; } void SmugWindow::closeEvent(QCloseEvent* e) { if (!e) { return; } slotDialogFinished(); e->accept(); } void SmugWindow::slotDialogFinished() { slotCancelClicked(); /** * We should not logout without user consent * * if (d->talker->loggedIn()) * d->talker->logout(); */ writeSettings(); d->widget->imagesList()->listView()->clear(); } void SmugWindow::setUiInProgressState(bool inProgress) { setRejectButtonMode(inProgress ? QDialogButtonBox::Cancel : QDialogButtonBox::Close); if (inProgress) { d->widget->progressBar()->show(); } else { d->widget->progressBar()->hide(); d->widget->progressBar()->progressCompleted(); } } void SmugWindow::slotCancelClicked() { d->talker->cancel(); d->transferQueue.clear(); d->widget->m_imgList->cancelProcess(); setUiInProgressState(false); } void SmugWindow::slotStopAndCloseProgressBar() { slotCancelClicked(); writeSettings(); d->widget->imagesList()->listView()->clear(); reject(); } void SmugWindow::reactivate() { d->widget->imagesList()->loadImagesFromCurrentSelection(); show(); } void SmugWindow::authenticate() { setUiInProgressState(true); d->widget->progressBar()->setFormat(QString()); d->talker->login(); } void SmugWindow::readSettings() { KConfig config; KConfigGroup grp = config.group("Smug Settings"); d->anonymousImport = grp.readEntry("AnonymousImport", true); d->email = grp.readEntry("Email"); d->password = grp.readEntry("Password"); d->currentAlbumID = grp.readEntry("Current Album", -1); d->currentAlbumKey = grp.readEntry("Current Key", -1); if (grp.readEntry("Resize", false)) { d->widget->m_resizeChB->setChecked(true); d->widget->m_dimensionSpB->setEnabled(true); d->widget->m_imageQualitySpB->setEnabled(true); } else { d->widget->m_resizeChB->setChecked(false); d->widget->m_dimensionSpB->setEnabled(false); d->widget->m_imageQualitySpB->setEnabled(false); } d->widget->m_dimensionSpB->setValue(grp.readEntry("Maximum Width", 1600)); d->widget->m_imageQualitySpB->setValue(grp.readEntry("Image Quality", 85)); if (d->import) { winId(); KConfigGroup dialogGroup = config.group("Smug Import Dialog"); KWindowConfig::restoreWindowSize(windowHandle(), dialogGroup); resize(windowHandle()->size()); } else { winId(); KConfigGroup dialogGroup = config.group("Smug Export Dialog"); KWindowConfig::restoreWindowSize(windowHandle(), dialogGroup); resize(windowHandle()->size()); } } void SmugWindow::writeSettings() { KConfig config; KConfigGroup grp = config.group("Smug Settings"); grp.writeEntry("AnonymousImport", d->anonymousImport); grp.writeEntry("Email", d->email); grp.writeEntry("Password", d->password); grp.writeEntry("Current Album", d->currentAlbumID); grp.writeEntry("Current Key", d->currentAlbumKey); grp.writeEntry("Resize", d->widget->m_resizeChB->isChecked()); grp.writeEntry("Maximum Width", d->widget->m_dimensionSpB->value()); grp.writeEntry("Image Quality", d->widget->m_imageQualitySpB->value()); if (d->import) { KConfigGroup dialogGroup = config.group("Smug Import Dialog"); KWindowConfig::saveWindowSize(windowHandle(), dialogGroup); } else { KConfigGroup dialogGroup = config.group("Smug Export Dialog"); KWindowConfig::saveWindowSize(windowHandle(), dialogGroup); } config.sync(); } void SmugWindow::slotLoginProgress(int step, int maxStep, const QString &label) { DProgressWdg* const progressBar = d->widget->progressBar(); if (!label.isEmpty()) progressBar->setFormat(label); if (maxStep > 0) progressBar->setMaximum(maxStep); progressBar->setValue(step); } void SmugWindow::slotLoginDone(int errCode, const QString &errMsg) { setUiInProgressState(false); buttonStateChange(d->talker->loggedIn()); SmugUser user = d->talker->getUser(); d->widget->updateLabels(user.email, user.displayName, user.nickName); d->widget->m_albumsCoB->clear(); if (errCode == 0 && d->talker->loggedIn()) { if (d->import) { d->anonymousImport = d->widget->isAnonymous(); // anonymous: list albums after login only if nick is not empty QString nick = d->widget->getNickName(); if (!nick.isEmpty() || !d->anonymousImport) { d->talker->listAlbums(nick); } } else { // get albums from current user d->talker->listAlbums(); } } else { QMessageBox::critical(QApplication::activeWindow(), i18n("Error"), i18n("SmugMug Call Failed: %1\n", errMsg)); } } void SmugWindow::slotListAlbumsDone(int errCode, const QString &errMsg, const QList & albumsList) { if (errCode != 0) { QMessageBox::critical(QApplication::activeWindow(), i18n("Error"), i18n("SmugMug Call Failed: %1\n", errMsg)); return; } d->widget->m_albumsCoB->clear(); for (int i = 0 ; i < albumsList.size() ; ++i) { QString albumIcon; if (!albumsList.at(i).password.isEmpty()) albumIcon = QLatin1String("folder-locked"); else if (albumsList.at(i).isPublic) albumIcon = QLatin1String("folder-image"); else albumIcon = QLatin1String("folder"); QString data = QString::fromLatin1("%1:%2").arg(albumsList.at(i).id).arg(albumsList.at(i).key); d->widget->m_albumsCoB->addItem(QIcon::fromTheme(albumIcon), albumsList.at(i).title, data); if (d->currentAlbumID == albumsList.at(i).id) d->widget->m_albumsCoB->setCurrentIndex(i); } } void SmugWindow::slotListPhotosDone(int errCode, const QString &errMsg, const QList & photosList) { if (errCode != 0) { QMessageBox::critical(QApplication::activeWindow(), i18n("Error"), i18n("SmugMug Call Failed: %1\n", errMsg)); return; } d->transferQueue.clear(); for (int i = 0 ; i < photosList.size() ; ++i) { d->transferQueue.append(QUrl(photosList.at(i).originalURL)); } if (d->transferQueue.isEmpty()) return; d->imagesTotal = d->transferQueue.count(); d->imagesCount = 0; d->widget->progressBar()->setMaximum(d->imagesTotal); d->widget->progressBar()->setValue(0); // start download with first photo in queue downloadNextPhoto(); } void SmugWindow::slotListAlbumTmplDone(int errCode, const QString &errMsg, const QList & albumTList) { // always put at least default subcategory d->albumDlg->templateCombo()->clear(); d->albumDlg->templateCombo()->addItem(i18n("<none>"), 0); if (errCode != 0) { QMessageBox::critical(QApplication::activeWindow(), i18n("Error"), i18n("SmugMug Call Failed: %1\n", errMsg)); return; } for (int i = 0; i < albumTList.size(); ++i) { QString albumIcon; if (!albumTList.at(i).password.isEmpty()) albumIcon = QLatin1String("folder-locked"); else if (albumTList.at(i).isPublic) albumIcon = QLatin1String("folder-image"); else albumIcon = QLatin1String("folder"); d->albumDlg->templateCombo()->addItem(QIcon::fromTheme(albumIcon), albumTList.at(i).name, albumTList.at(i).id); if (d->currentTmplID == albumTList.at(i).id) d->albumDlg->templateCombo()->setCurrentIndex(i+1); } d->currentTmplID = d->albumDlg->templateCombo()->itemData(d->albumDlg->templateCombo()->currentIndex()).toLongLong(); // now fill in categories /** * Categories now are deprecated in API v2 * d->talker->listCategories(); */ } /** * Categories now are deprecated in API v2 * void SmugWindow::slotListCategoriesDone(int errCode, const QString& errMsg, const QList & categoriesList) { if (errCode != 0) { QMessageBox::critical(QApplication::activeWindow(), i18n("Error"), i18n("SmugMug Call Failed: %1\n", errMsg)); return; } d->albumDlg->categoryCombo()->clear(); for (int i = 0; i < categoriesList.size(); ++i) { d->albumDlg->categoryCombo()->addItem( categoriesList.at(i).name, categoriesList.at(i).id); if (d->currentCategoryID == categoriesList.at(i).id) d->albumDlg->categoryCombo()->setCurrentIndex(i); } d->currentCategoryID = d->albumDlg->categoryCombo()->itemData( d->albumDlg->categoryCombo()->currentIndex()).toLongLong(); d->talker->listSubCategories(d->currentCategoryID); } void SmugWindow::slotListSubCategoriesDone(int errCode, const QString &errMsg, const QList & categoriesList) { // always put at least default subcategory d->albumDlg->subCategoryCombo()->clear(); d->albumDlg->subCategoryCombo()->addItem(i18n("<none>"), 0); if (errCode != 0) { QMessageBox::critical(QApplication::activeWindow(), i18n("Error"), i18n("SmugMug Call Failed: %1\n", errMsg)); return; } for (int i = 0; i < categoriesList.size(); ++i) { d->albumDlg->subCategoryCombo()->addItem( categoriesList.at(i).name, categoriesList.at(i).id); } } */ void SmugWindow::slotTemplateSelectionChanged(int index) { if (index < 0) return; d->currentTmplID = d->albumDlg->templateCombo()->itemData(index).toLongLong(); // if template is selected, then disable Security & Privacy d->albumDlg->privateGroupBox()->setEnabled(d->currentTmplID == 0); } /** * Categories now are deprecated in API v2 * void SmugWindow::slotCategorySelectionChanged(int index) { if (index < 0) return; // subcategories are per category -> reload d->currentCategoryID = d->albumDlg->categoryCombo()->itemData(index).toLongLong(); d->talker->listSubCategories(d->currentCategoryID); } */ void SmugWindow::buttonStateChange(bool state) { d->widget->m_newAlbumBtn->setEnabled(state); d->widget->m_reloadAlbumsBtn->setEnabled(state); startButton()->setEnabled(state); } void SmugWindow::slotBusy(bool val) { if (val) { setCursor(Qt::WaitCursor); d->widget->m_changeUserBtn->setEnabled(false); buttonStateChange(false); } else { setCursor(Qt::ArrowCursor); d->widget->m_changeUserBtn->setEnabled(!d->widget->isAnonymous()); buttonStateChange(d->talker->loggedIn()); } } void SmugWindow::slotUserChangeRequest(bool /*anonymous*/) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Slot Change User Request"; QPointer warn = new QMessageBox(QMessageBox::Warning, i18n("Warning"), i18n("You will be logged out of your account, " "click \"Continue\" to authenticate for another account."), QMessageBox::Yes | QMessageBox::No); (warn->button(QMessageBox::Yes))->setText(i18n("Continue")); (warn->button(QMessageBox::No))->setText(i18n("Cancel")); if (warn->exec() == QMessageBox::Yes) { // Unlink user account and wait active until really logged out d->talker->logout(); while (d->talker->loggedIn()); // Re-login authenticate(); } delete warn; /* if (anonymous) { authenticate(); } else { // fill in current email and password d->loginDlg->setLogin(d->email); d->loginDlg->setPassword(d->password); if (d->loginDlg->exec()) { d->email = d->loginDlg->login(); d->password = d->loginDlg->password(); authenticate(d->email, d->password); } } */ } void SmugWindow::slotReloadAlbumsRequest() { if (d->import) { d->talker->listAlbums(d->widget->getNickName()); } else { // get albums for current user d->talker->listAlbums(); } } void SmugWindow::slotNewAlbumRequest() { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Slot New Album Request"; // get list of album templates from SmugMug to fill in dialog d->talker->listAlbumTmpl(); if (d->albumDlg->exec() == QDialog::Accepted) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Calling New Album method"; d->currentTmplID = d->albumDlg->templateCombo()->itemData( d->albumDlg->templateCombo()->currentIndex()).toLongLong(); /** * Categories are deprecated * * d->currentCategoryID = d->albumDlg->categoryCombo()->itemData( * d->albumDlg->categoryCombo()->currentIndex()).toLongLong(); */ SmugAlbum newAlbum; d->albumDlg->getAlbumProperties(newAlbum); d->talker->createAlbum(newAlbum); } } void SmugWindow::slotStartTransfer() { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "slotStartTransfer invoked"; if (d->import) { d->widget->progressBar()->setFormat(i18n("%v / %m")); d->widget->progressBar()->setMaximum(0); d->widget->progressBar()->setValue(0); d->widget->progressBar()->progressScheduled(i18n("SmugMug Import"), true, true); d->widget->progressBar()->progressThumbnailChanged( QIcon::fromTheme(QLatin1String("dk-smugmug")).pixmap(22, 22)); setUiInProgressState(true); // list photos of the album, then start download QString dataStr = d->widget->m_albumsCoB->itemData(d->widget->m_albumsCoB->currentIndex()).toString(); int colonIdx = dataStr.indexOf(QLatin1Char(':')); qint64 albumID = dataStr.left(colonIdx).toLongLong(); QString albumKey = dataStr.right(dataStr.length() - colonIdx - 1); d->talker->listPhotos(albumID, albumKey, d->widget->getAlbumPassword(), d->widget->getSitePassword()); } else { d->widget->m_imgList->clearProcessedStatus(); d->transferQueue = d->widget->m_imgList->imageUrls(); if (d->transferQueue.isEmpty()) return; QString data = d->widget->m_albumsCoB->itemData(d->widget->m_albumsCoB->currentIndex()).toString(); int colonIdx = data.indexOf(QLatin1Char(':')); d->currentAlbumID = data.left(colonIdx).toLongLong(); d->currentAlbumKey = data.right(data.length() - colonIdx - 1); d->imagesTotal = d->transferQueue.count(); d->imagesCount = 0; d->widget->progressBar()->setFormat(i18n("%v / %m")); d->widget->progressBar()->setMaximum(d->imagesTotal); d->widget->progressBar()->setValue(0); d->widget->progressBar()->progressScheduled(i18n("SmugMug Export"), true, true); d->widget->progressBar()->progressThumbnailChanged( QIcon::fromTheme(QLatin1String("dk-smugmug")).pixmap(22, 22)); setUiInProgressState(true); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "d->currentAlbumID" << d->currentAlbumID; uploadNextPhoto(); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "slotStartTransfer done"; } } bool SmugWindow::prepareImageForUpload(const QString& imgPath) const { QImage image = PreviewLoadThread::loadHighQualitySynchronously(imgPath).copyQImage(); if (image.isNull()) { image.load(imgPath); } if (image.isNull()) { return false; } // get temporary file name d->tmpPath = d->tmpDir + QFileInfo(imgPath).baseName().trimmed() + QLatin1String(".jpg"); // rescale image if requested int maxDim = d->widget->m_dimensionSpB->value(); if (d->widget->m_resizeChB->isChecked() && (image.width() > maxDim || image.height() > maxDim)) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Resizing to " << maxDim; image = image.scaled(maxDim, maxDim, Qt::KeepAspectRatio, Qt::SmoothTransformation); } qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Saving to temp file: " << d->tmpPath; image.save(d->tmpPath, "JPEG", d->widget->m_imageQualitySpB->value()); // copy meta-data to temporary image DMetadata meta; if (meta.load(imgPath)) { meta.setItemDimensions(image.size()); meta.setItemOrientation(MetaEngine::ORIENTATION_NORMAL); meta.setMetadataWritingMode((int)DMetadata::WRITE_TO_FILE_ONLY); meta.save(d->tmpPath, true); } return true; } void SmugWindow::uploadNextPhoto() { if (d->transferQueue.isEmpty()) { setUiInProgressState(false); return; } d->widget->m_imgList->processing(d->transferQueue.first()); QUrl imgPath = d->transferQueue.first(); DItemInfo info(d->iface->itemInfo(imgPath)); d->widget->progressBar()->setMaximum(d->imagesTotal); d->widget->progressBar()->setValue(d->imagesCount); bool res; if (d->widget->m_resizeChB->isChecked()) { if (!prepareImageForUpload(imgPath.toLocalFile())) { slotAddPhotoDone(666, i18n("Cannot open file")); return; } res = d->talker->addPhoto(d->tmpPath, d->currentAlbumID, d->currentAlbumKey, info.comment()); } else { d->tmpPath.clear(); res = d->talker->addPhoto(imgPath.toLocalFile(), d->currentAlbumID, d->currentAlbumKey, info.comment()); } if (!res) { slotAddPhotoDone(666, i18n("Cannot open file")); return; } } void SmugWindow::slotAddPhotoDone(int errCode, const QString& errMsg) { // Remove temporary file if it was used if (!d->tmpPath.isEmpty()) { QFile::remove(d->tmpPath); d->tmpPath.clear(); } d->widget->m_imgList->processed(d->transferQueue.first(), (errCode == 0)); if (errCode == 0) { d->transferQueue.removeFirst(); d->imagesCount++; } else { if (QMessageBox::question(this, i18n("Uploading Failed"), i18n("Failed to upload photo to SmugMug." "\n%1\n" "Do you want to continue?", errMsg)) != QMessageBox::Yes) { setUiInProgressState(false); d->transferQueue.clear(); return; } } uploadNextPhoto(); } void SmugWindow::downloadNextPhoto() { if (d->transferQueue.isEmpty()) { setUiInProgressState(false); return; } d->widget->progressBar()->setMaximum(d->imagesTotal); d->widget->progressBar()->setValue(d->imagesCount); QString imgPath = d->transferQueue.first().url(); d->talker->getPhoto(imgPath); } void SmugWindow::slotGetPhotoDone(int errCode, const QString& errMsg, const QByteArray& photoData) { QString imgPath = d->widget->getDestinationPath() + QLatin1Char('/') + d->transferQueue.first().fileName(); qCDebug(DIGIKAM_WEBSERVICES_LOG) << imgPath; if (errCode == 0) { QString errText; QFile imgFile(imgPath); if (!imgFile.open(QIODevice::WriteOnly)) { errText = imgFile.errorString(); } else if (imgFile.write(photoData) != photoData.size()) { errText = imgFile.errorString(); } else { imgFile.close(); } if (errText.isEmpty()) { d->transferQueue.removeFirst(); d->imagesCount++; } else { if (QMessageBox::question(this, i18n("Processing Failed"), i18n("Failed to save photo: %1\n" "Do you want to continue?", errText)) != QMessageBox::Yes) { d->transferQueue.clear(); setUiInProgressState(false); return; } } } else { if (QMessageBox::question(this, i18n("Processing Failed"), i18n("Failed to download photo: %1\n" "Do you want to continue?", errMsg)) != QMessageBox::Yes) { d->transferQueue.clear(); setUiInProgressState(false); return; } } downloadNextPhoto(); } void SmugWindow::slotCreateAlbumDone(int errCode, const QString& errMsg, qint64 newAlbumID, const QString& newAlbumKey) { if (errCode != 0) { QMessageBox::critical(QApplication::activeWindow(), i18n("Error"), i18n("SmugMug Call Failed: %1\n", errMsg)); return; } // reload album list and automatically select new album d->currentAlbumID = newAlbumID; d->currentAlbumKey = newAlbumKey; d->talker->listAlbums(); } void SmugWindow::slotImageListChanged() { startButton()->setEnabled(!(d->widget->m_imgList->imageUrls().isEmpty())); } } // namespace DigikamGenericSmugPlugin diff --git a/core/dplugins/generic/webservices/twitter/twittertalker.cpp b/core/dplugins/generic/webservices/twitter/twittertalker.cpp index 604a670589..727f1f451d 100644 --- a/core/dplugins/generic/webservices/twitter/twittertalker.cpp +++ b/core/dplugins/generic/webservices/twitter/twittertalker.cpp @@ -1,959 +1,959 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2018-06-29 * Description : a tool to export images to Twitter social network * * Copyright (C) 2018 by Tarek Talaat * Copyright (C) 2019 by Thanh Trung Dinh * * 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 "twittertalker.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* #include #include #include #include */ // Local includes #include "digikam_debug.h" #include "digikam_version.h" #include "wstoolutils.h" #include "twitterwindow.h" #include "twitteritem.h" #include "twittermpform.h" #include "previewloadthread.h" #include "o0settingsstore.h" #include "o1requestor.h" namespace DigikamGenericTwitterPlugin { QStringList imageFormat(QString::fromLatin1("jpg,png,gif,webp").split(QLatin1Char(','))); class Q_DECL_HIDDEN TwTalker::Private { public: enum State { TW_USERNAME = 0, TW_LISTFOLDERS, TW_CREATEFOLDER, TW_ADDPHOTO, TW_CREATETWEET, TW_UPLOADINIT, TW_UPLOADAPPEND, TW_UPLOADSTATUSCHECK, TW_UPLOADFINALIZE }; public: explicit Private() { clientId = QLatin1String("lkRgRsucipXsUEvKh0ECblreC"); clientSecret = QLatin1String("6EThTiPQHZTMo7F83iLHrfNO89fkDVvM9hVWaYH9D49xEOyMBe"); //scope = QLatin1String("User.Read Files.ReadWrite"); requestTokenUrl = QLatin1String("https://api.twitter.com/oauth/request_token"); authUrl = QLatin1String("https://api.twitter.com/oauth/authenticate"); accessTokenUrl = QLatin1String("https://api.twitter.com/oauth/access_token"); redirectUrl = QLatin1String("http://127.0.0.1:8000"); uploadUrl = QLatin1String("https://upload.twitter.com/1.1/media/upload.json"); state = TW_USERNAME; parent = 0; netMngr = 0; reply = 0; settings = 0; o1Twitter = 0; } public: QString clientId; QString clientSecret; QString authUrl; QString requestTokenUrl; QString accessTokenUrl; QString scope; QString redirectUrl; QString accessToken; QString uploadUrl; QString mediaUploadedPath; QString mediaId; int segmentIndex; QWidget* parent; QNetworkAccessManager* netMngr; QNetworkReply* reply; State state; DMetadata meta; QMap urlParametersMap; //QWebEngineView* view; QSettings* settings; O1Twitter* o1Twitter; O1Requestor* requestor; }; TwTalker::TwTalker(QWidget* const parent) : d(new Private) { d->parent = parent; d->netMngr = new QNetworkAccessManager(this); connect(d->netMngr, SIGNAL(finished(QNetworkReply*)), this, SLOT(slotFinished(QNetworkReply*))); d->o1Twitter = new O1Twitter(this); d->o1Twitter->setClientId(d->clientId); d->o1Twitter->setClientSecret(d->clientSecret); d->o1Twitter->setLocalPort(8000); d->requestor = new O1Requestor(d->netMngr, d->o1Twitter, this); d->settings = WSToolUtils::getOauthSettings(this); O0SettingsStore* const store = new O0SettingsStore(d->settings, QLatin1String(O2_ENCRYPTION_KEY), this); store->setGroupKey(QLatin1String("Twitter")); d->o1Twitter->setStore(store); connect(d->o1Twitter, SIGNAL(linkingFailed()), this, SLOT(slotLinkingFailed())); connect(d->o1Twitter, SIGNAL(linkingSucceeded()), this, SLOT(slotLinkingSucceeded())); connect(d->o1Twitter, SIGNAL(openBrowser(QUrl)), this, SLOT(slotOpenBrowser(QUrl))); } TwTalker::~TwTalker() { if (d->reply) { d->reply->abort(); } WSToolUtils::removeTemporaryDir("twitter"); delete d; } void TwTalker::link() { /* emit signalBusy(true); QUrl url(d->requestTokenUrl); QNetworkRequest netRequest(url); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/json")); netRequest.setRawHeader("Authorization", QString::fromLatin1("OAuth oauth_callback= \"%1\"").arg(d->redirectUrl).toUtf8()); QNetworkAccessManager requestMngr; QNetworkReply* reply; reply = requestMngr.post(netRequest); if (reply->error() != QNetworkReply::NoError){ } QByteArray buffer; buffer.append(reply->readAll()); QString response = fromLatin1(buffer); QMap headers; // Discard the first line response = response.mid(response.indexOf('\n') + 1).trimmed(); foreach (QString line, response.split('\n')) { int colon = line.indexOf(':'); QString headerName = line.left(colon).trimmed(); QString headerValue = line.mid(colon + 1).trimmed(); headers.insertMulti(headerName, headerValue); } QString oauthToken = headers[oauth_token]; QSting oauthTokenSecret = headers[oauth_token_secret]; QUrlQuery query(url); query.addQueryItem(QLatin1String("client_id"), d->clientId); query.addQueryItem(QLatin1String("scope"), d->scope); query.addQueryItem(QLatin1String("redirect_uri"), d->redirectUrl); query.addQueryItem(QLatin1String("response_type"), "token"); url.setQuery(query); d->view = new QWebEngineView(d->parent); d->view->setWindowFlags(Qt::Dialog); d->view->load(url); d->view->show(); connect(d->view, SIGNAL(urlChanged(QUrl)), this, SLOT(slotCatchUrl(QUrl))); */ emit signalBusy(true); d->o1Twitter->link(); } void TwTalker::unLink() { /* d->accessToken = QString(); d->view->page()->profile()->cookieStore()->deleteAllCookies(); emit oneDriveLinkingSucceeded(); */ d->o1Twitter->unlink(); d->settings->beginGroup(QLatin1String("Twitter")); d->settings->remove(QString()); d->settings->endGroup(); } void TwTalker::slotOpenBrowser(const QUrl& url) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Open Browser..."; QDesktopServices::openUrl(url); } QMap TwTalker::ParseUrlParameters(const QString &url) { QMap urlParameters; if (url.indexOf(QLatin1Char('?')) == -1) { return urlParameters; } QString tmp = url.right(url.length()-url.indexOf(QLatin1Char('?'))-1); tmp = tmp.right(tmp.length() - tmp.indexOf(QLatin1Char('#'))-1); QStringList paramlist = tmp.split(QLatin1Char('&')); for (int i = 0 ; i < paramlist.count() ; ++i) { QStringList paramarg = paramlist.at(i).split(QLatin1Char('=')); urlParameters.insert(paramarg.at(0),paramarg.at(1)); } return urlParameters; } void TwTalker::slotLinkingFailed() { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "LINK to Twitter fail"; emit signalBusy(false); } void TwTalker::slotLinkingSucceeded() { if (!d->o1Twitter->linked()) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "UNLINK to Twitter ok"; emit signalBusy(false); return; } qCDebug(DIGIKAM_WEBSERVICES_LOG) << "LINK to Twitter ok"; QVariantMap extraTokens = d->o1Twitter->extraTokens(); if (!extraTokens.isEmpty()) { //emit extraTokensReady(extraTokens); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Extra tokens in response:"; foreach (const QString& key, extraTokens.keys()) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "\t" << key << ":" << (extraTokens.value(key).toString().left(3) + QLatin1String("...")); } } emit signalLinkingSucceeded(); getUserName(); } bool TwTalker::authenticated() { return d->o1Twitter->linked(); } void TwTalker::cancel() { if (d->reply) { d->reply->abort(); d->reply = 0; } emit signalBusy(false); } bool TwTalker::addPhoto(const QString& imgPath, const QString& /* uploadFolder */, bool rescale, int maxDim, int imageQuality) { QFileInfo imgFileInfo(imgPath); QString path; bool chunked = false; qCDebug(DIGIKAM_WEBSERVICES_LOG) << imgFileInfo.suffix(); if (imgFileInfo.suffix() != QLatin1String("gif") && imgFileInfo.suffix() != QLatin1String("mp4")) { QImage image = PreviewLoadThread::loadHighQualitySynchronously(imgPath).copyQImage(); qint64 imageSize = QFileInfo(imgPath).size(); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "SIZE of image using qfileinfo: " << imageSize; qCDebug(DIGIKAM_WEBSERVICES_LOG) << " "; if (image.isNull()) { emit signalBusy(false); return false; } path = WSToolUtils::makeTemporaryDir("twitter").filePath(imgFileInfo.baseName().trimmed() + QLatin1String(".jpg")); if (rescale && (image.width() > maxDim || image.height() > maxDim)) { image = image.scaled(maxDim, maxDim, Qt::KeepAspectRatio, Qt::SmoothTransformation); } image.save(path, "JPEG", imageQuality); if (d->meta.load(imgPath)) { d->meta.setItemDimensions(image.size()); d->meta.setItemOrientation(DMetadata::ORIENTATION_NORMAL); d->meta.setMetadataWritingMode((int)DMetadata::WRITE_TO_FILE_ONLY); d->meta.save(path, true); } } else { path = imgPath; chunked = true; } - if(chunked) + if (chunked) { return addPhotoInit(path); } else { return addPhotoSingleUpload(path); } } bool TwTalker::addPhotoSingleUpload(const QString& imgPath) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "addPhotoSingleUpload"; emit signalBusy(true); TwMPForm form; if (!form.addFile(imgPath)) { emit signalBusy(false); return false; } form.finish(); if (form.formData().isEmpty()) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Form DATA Empty:"; } if (form.formData().isNull()) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Form DATA null:"; } QUrl url = QUrl(QLatin1String("https://upload.twitter.com/1.1/media/upload.json")); QList reqParams = QList(); QNetworkRequest request(url); request.setHeader(QNetworkRequest::ContentTypeHeader, form.contentType()); d->reply = d->requestor->post(request, reqParams, form.formData()); d->state = Private::TW_ADDPHOTO; return true; } bool TwTalker::addPhotoInit(const QString& imgPath) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "addPhotoInit"; emit signalBusy(true); TwMPForm form; QByteArray mediaType, mediaCategory; QFileInfo fileInfo(imgPath); QString fileFormat(fileInfo.suffix()); form.addPair(form.createPair("command", "INIT")); form.addPair(form.createPair("total_bytes", QString::number(QFileInfo(imgPath).size()).toLatin1())); /* (Feb 2019) * Image file must be <= 5MB * Gif must be <= 15MB * Video must be <= 512MB */ if (imageFormat.indexOf(fileFormat) != -1) { mediaType = "image/jpeg"; if (fileFormat == QLatin1String("gif")) { - if(fileInfo.size() > 15728640) + if (fileInfo.size() > 15728640) { emit signalBusy(false); emit signalAddPhotoFailed(i18n("File too big to upload")); return false; } mediaCategory = "TWEET_GIF"; } else { - if(fileInfo.size() > 5242880) + if (fileInfo.size() > 5242880) { emit signalBusy(false); emit signalAddPhotoFailed(i18n("File too big to upload")); return false; } mediaCategory = "TWEET_IMAGE"; } } else if (fileFormat == QLatin1String("mp4")) { - if(fileInfo.size() > 536870912) + if (fileInfo.size() > 536870912) { emit signalBusy(false); emit signalAddPhotoFailed(i18n("File too big to upload")); return false; } mediaType = "video/mp4"; mediaCategory = "TWEET_VIDEO"; } else { emit signalBusy(false); emit signalAddPhotoFailed(i18n("Media format is not supported yet")); return false; } form.addPair(form.createPair("media_type", mediaType)); form.addPair(form.createPair("media_category", mediaCategory)); form.finish(); qCDebug(DIGIKAM_WEBSERVICES_LOG) << form.formData(); QUrl url(d->uploadUrl); QList reqParams = QList(); QNetworkRequest request(url); request.setHeader(QNetworkRequest::ContentTypeHeader, form.contentType()); d->reply = d->requestor->post(request, reqParams, form.formData()); d->mediaUploadedPath = imgPath; d->state = Private::TW_UPLOADINIT; return true; } bool TwTalker::addPhotoAppend(const QString& mediaId, int segmentIndex) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "addPhotoAppend: "; static TwMPForm form; - if(segmentIndex == 0) + if (segmentIndex == 0) { form.addPair(form.createPair("command", "APPEND")); form.addPair(form.createPair("media_id", mediaId.toLatin1())); form.addFile(d->mediaUploadedPath, true); d->segmentIndex = form.numberOfChunks() - 1; } QByteArray data(form.formData()); data.append(form.createPair("segment_index", QString::number(segmentIndex).toLatin1())); data.append(form.createPair("media", form.getChunk(segmentIndex))); data.append(form.border()); QUrl url(d->uploadUrl); QList reqParams = QList(); QNetworkRequest request(url); request.setHeader(QNetworkRequest::ContentTypeHeader, form.contentType()); d->reply = d->requestor->post(request, reqParams, data); d->state = Private::TW_UPLOADAPPEND; // Reset form for later uploads - if(segmentIndex == d->segmentIndex) + if (segmentIndex == d->segmentIndex) { form.reset(); } return true; } bool TwTalker::addPhotoFinalize(const QString& mediaId) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "addPhotoFinalize: "; TwMPForm form; form.addPair(form.createPair("command", "FINALIZE")); form.addPair(form.createPair("media_id", mediaId.toLatin1())); form.finish(); qCDebug(DIGIKAM_WEBSERVICES_LOG) << form.formData(); QUrl url(d->uploadUrl); QList reqParams = QList(); QNetworkRequest request(url); request.setHeader(QNetworkRequest::ContentTypeHeader, form.contentType()); d->reply = d->requestor->post(request, reqParams, form.formData()); d->state = Private::TW_UPLOADFINALIZE; return true; } void TwTalker::getUserName() { /* * The endpoint below allows to get more than just account name (e.g. profile avatar, links to tweets posted, etc.) * Look at debug message printed to console for futher ideas and exploitation */ QUrl url(QLatin1String("https://api.twitter.com/1.1/account/verify_credentials.json")); QNetworkRequest request(url); QList reqParams = QList(); d->reply = d->requestor->get(request, reqParams); d->state = Private::TW_USERNAME; emit signalBusy(true); } void TwTalker::createTweet(const QString& mediaId) { QUrl url = QUrl(QLatin1String("https://api.twitter.com/1.1/statuses/update.json")); QList reqParams = QList(); reqParams << O0RequestParameter(QByteArray("status"), QByteArray("")); reqParams << O0RequestParameter(QByteArray("media_ids"), mediaId.toUtf8()); QByteArray postData = O1::createQueryParameters(reqParams); QNetworkRequest request(url); request.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String(O2_MIME_TYPE_XFORM)); d->reply = d->requestor->post(request, reqParams, postData); d->state = Private::TW_CREATETWEET; } void TwTalker::slotCheckUploadStatus() { QUrl url = QUrl(d->uploadUrl); QList reqParams = QList(); reqParams << O0RequestParameter(QByteArray("command"), QByteArray("STATUS")); reqParams << O0RequestParameter(QByteArray("media_id"), d->mediaId.toUtf8()); QUrlQuery query; query.addQueryItem(QLatin1String("command"), QLatin1String("STATUS")); query.addQueryItem(QLatin1String("media_id"), d->mediaId); url.setQuery(query); qCDebug(DIGIKAM_WEBSERVICES_LOG) << url.toString(); QNetworkRequest request(url); d->reply = d->requestor->get(request, reqParams); d->state = Private::TW_UPLOADSTATUSCHECK; } void TwTalker::slotFinished(QNetworkReply* reply) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "TwTalker::slotFinished"; if (reply != d->reply) { return; } d->reply = 0; if (reply->error() != QNetworkReply::NoError) { if (d->state != Private::TW_CREATEFOLDER) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << reply->readAll(); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "status code: " << reply->attribute( QNetworkRequest::HttpStatusCodeAttribute ).toInt(); emit signalBusy(false); QMessageBox::critical(QApplication::activeWindow(), i18n("Error"), reply->errorString()); reply->deleteLater(); return; } } QByteArray buffer = reply->readAll(); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "status code: " << reply->attribute( QNetworkRequest::HttpStatusCodeAttribute ).toInt(); switch (d->state) { static int segmentIndex = 0; case Private::TW_LISTFOLDERS: qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In TW_LISTFOLDERS"; parseResponseListFolders(buffer); break; case Private::TW_CREATEFOLDER: qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In TW_CREATEFOLDER"; parseResponseCreateFolder(buffer); break; case Private::TW_ADDPHOTO: qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In TW_ADDPHOTO"; parseResponseAddPhoto(buffer); break; case Private::TW_USERNAME: qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In TW_USERNAME"; parseResponseUserName(buffer); break; case Private::TW_CREATETWEET: qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In TW_CREATETWEET"; parseResponseCreateTweet(buffer); break; case Private::TW_UPLOADINIT: segmentIndex = 0; qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In TW_UPLOADINIT"; parseResponseAddPhotoInit(buffer); break; case Private::TW_UPLOADAPPEND: qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In TW_UPLOADAPPEND (at index " << segmentIndex << ")"; segmentIndex++; parseResponseAddPhotoAppend(buffer, segmentIndex); break; case Private::TW_UPLOADSTATUSCHECK: qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In TW_UPLOADSTATUSCHECK"; parseCheckUploadStatus(buffer); break; case Private::TW_UPLOADFINALIZE: qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In TW_UPLOADFINALIZE"; parseResponseAddPhotoFinalize(buffer); break; default: break; } reply->deleteLater(); } void TwTalker::parseResponseAddPhoto(const QByteArray& data) { QJsonParseError err; QJsonDocument doc = QJsonDocument::fromJson(data, &err); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseAddPhoto: " << doc; - if(err.error != QJsonParseError::NoError) + if (err.error != QJsonParseError::NoError) { emit signalBusy(false); emit signalAddPhotoFailed(i18n("Failed to upload photo")); return; } QJsonObject jsonObject = doc.object(); QString mediaId = jsonObject[QLatin1String("media_id_string")].toString(); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "media id: " << mediaId; // We haven't emit signalAddPhotoSucceeded() here yet, since we need to update the status first createTweet(mediaId); } void TwTalker::parseResponseAddPhotoInit(const QByteArray& data) { QJsonParseError err; QJsonDocument doc = QJsonDocument::fromJson(data, &err); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseAddPhotoInit: " << doc; - if(err.error != QJsonParseError::NoError) + if (err.error != QJsonParseError::NoError) { emit signalBusy(false); emit signalAddPhotoFailed(i18n("Failed to upload photo")); return; } QJsonObject jsonObject = doc.object(); d->mediaId = jsonObject[QLatin1String("media_id_string")].toString(); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "media id: " << d->mediaId; // We haven't emit signalAddPhotoSucceeded() here yet, since we need to update the status first addPhotoAppend(d->mediaId); } void TwTalker::parseResponseAddPhotoAppend(const QByteArray& /*data*/, int segmentIndex) { /* (Fev. 2019) * Currently, we don't parse data of response from addPhotoAppend, since the response is with HTTP 204 * This is indeed an expected response code, because the response should be with an empty body. * However, in order to keep a compatible prototype of parseResponse methodes and reserve for future change, * we should keep argument const QByteArray& data. */ qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseAddPhotoAppend: "; - if(segmentIndex <= d->segmentIndex) + if (segmentIndex <= d->segmentIndex) { addPhotoAppend(d->mediaId, segmentIndex); } else { addPhotoFinalize(d->mediaId); } } void TwTalker::parseResponseAddPhotoFinalize(const QByteArray& data) { QJsonParseError err; QJsonDocument doc = QJsonDocument::fromJson(data, &err); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseAddPhotoFinalize: " << doc; - if(err.error != QJsonParseError::NoError) + if (err.error != QJsonParseError::NoError) { emit signalBusy(false); emit signalAddPhotoFailed(i18n("Failed to upload photo")); return; } QJsonObject jsonObject = doc.object(); QJsonValue processingInfo = jsonObject[QLatin1String("processing_info")]; if (processingInfo != QJsonValue::Undefined) { QString state = processingInfo.toObject()[QLatin1String("state")].toString(); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "state: " << state; if (state == QLatin1String("pending")) { QTimer::singleShot(processingInfo.toObject()[QLatin1String("check_after_secs")].toInt()*1000 /*msec*/, this, SLOT(slotCheckUploadStatus())); } } else { // We haven't emit signalAddPhotoSucceeded() here yet, since we need to update the status first createTweet(d->mediaId); } } void TwTalker::parseCheckUploadStatus(const QByteArray& data) { QJsonParseError err; QJsonDocument doc = QJsonDocument::fromJson(data, &err); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseCheckUploadStatus: " << doc; - if(err.error != QJsonParseError::NoError) + if (err.error != QJsonParseError::NoError) { emit signalBusy(false); emit signalAddPhotoFailed(i18n("Failed to upload photo")); return; } QJsonObject jsonObject = doc.object(); QJsonObject processingInfo = jsonObject[QLatin1String("processing_info")].toObject(); QString state = processingInfo[QLatin1String("state")].toString(); if (state == QLatin1String("in_progress")) { QTimer::singleShot(processingInfo[QLatin1String("check_after_secs")].toInt()*1000 /*msec*/, this, SLOT(slotCheckUploadStatus())); } else if (state == QLatin1String("failed")) { QJsonObject error = processingInfo[QLatin1String("error")].toObject(); emit signalBusy(false); emit signalAddPhotoFailed(i18n("Failed to upload photo\n" "Code: %1, name: %2, message: %3", QString::number(error[QLatin1String("code")].toInt()), error[QLatin1String("name")].toString(), error[QLatin1String("message")].toString())); return; } else // succeeded { // We haven't emit signalAddPhotoSucceeded() here yet, since we need to update the status first createTweet(d->mediaId); } } void TwTalker::parseResponseUserName(const QByteArray& data) { QJsonParseError err; QJsonDocument doc = QJsonDocument::fromJson(data, &err); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseUserName: "< > list; list.append(qMakePair(QLatin1String(""), QLatin1String("root"))); foreach (const QJsonValue& value, jsonArray) { QString path; QString folderName; QJsonObject folder; QJsonObject obj = value.toObject(); folder = obj[QLatin1String("folder")].toObject(); if (!folder.isEmpty()) { folderName = obj[QLatin1String("name")].toString(); path = QLatin1Char('/') + folderName; qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Folder Name is" << folderName; list.append(qMakePair(path, folderName)); } } emit signalBusy(false); emit signalListAlbumsDone(list); } void TwTalker::parseResponseCreateFolder(const QByteArray& data) { QJsonDocument doc = QJsonDocument::fromJson(data); QJsonObject jsonObject = doc.object(); bool fail = jsonObject.contains(QLatin1String("error")); emit signalBusy(false); if (fail) { QJsonParseError err; QJsonDocument doc = QJsonDocument::fromJson(data, &err); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseCreateFolder ERROR: " << doc; emit signalCreateFolderFailed(jsonObject[QLatin1String("error_summary")].toString()); } else { emit signalCreateFolderSucceeded(); } } } // namespace DigikamGenericTwitterPlugin diff --git a/core/libs/album/engine/albummodificationhelper.cpp b/core/libs/album/engine/albummodificationhelper.cpp index 3373133c04..485b2afd90 100644 --- a/core/libs/album/engine/albummodificationhelper.cpp +++ b/core/libs/album/engine/albummodificationhelper.cpp @@ -1,350 +1,350 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2000-12-05 * Description : helper class used to modify physical albums in views * * Copyright (C) 2009-2011 by Johannes Wienke * Copyright (C) 2014-2015 by Mohamed_Anwer * * 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 "albummodificationhelper.h" // Qt includes #include #include #include #include #include // KDE includes #include // Local includes #include "digikam_debug.h" #include "albummanager.h" #include "albumpropsedit.h" #include "albumpointer.h" #include "applicationsettings.h" #include "collectionmanager.h" #include "deletedialog.h" #include "dio.h" #include "itemiconview.h" #include "digikamapp.h" #include "coredb.h" #include "coredbaccess.h" namespace Digikam { class Q_DECL_HIDDEN AlbumModificationHelper::Private { public: explicit Private() : dialogParent(0) { } QWidget* dialogParent; }; AlbumModificationHelper::AlbumModificationHelper(QObject* const parent, QWidget* const dialogParent) : QObject(parent), d(new Private) { d->dialogParent = dialogParent; } AlbumModificationHelper::~AlbumModificationHelper() { delete d; } void AlbumModificationHelper::bindAlbum(QAction* const action, PAlbum* const album) const { action->setData(QVariant::fromValue(AlbumPointer(album))); } PAlbum* AlbumModificationHelper::boundAlbum(QObject* const sender) const { QAction* action = 0; if ( (action = qobject_cast(sender)) ) { return action->data().value >(); } return 0; } PAlbum* AlbumModificationHelper::slotAlbumNew() { return slotAlbumNew(boundAlbum(sender())); } PAlbum* AlbumModificationHelper::slotAlbumNew(PAlbum* parent) { if (!parent) { qCWarning(DIGIKAM_GENERAL_LOG) << "No parent album given"; return 0; } ApplicationSettings* settings = ApplicationSettings::instance(); if (!settings) { qCWarning(DIGIKAM_GENERAL_LOG) << "could not get Album Settings"; return 0; } /* QDir libraryDir(settings->getAlbumLibraryPath()); - if(!libraryDir.exists()) + if (!libraryDir.exists()) { QMessageBox::critical(qApp->activeWindow(), qApp->applicationName(), i18n("The album library has not been set correctly.\n" "Select \"Configure Digikam\" from the Settings " "menu and choose a folder to use for the album " "library.")); return; } */ // if we create an album under root, need to supply the album root path. QString albumRootPath; albumRootPath = CollectionManager::instance()->oneAlbumRootPath(); QString title; QString comments; QString category; QDate date; QStringList albumCategories; int parentSelector; if (!AlbumPropsEdit::createNew(parent, title, comments, date, category, albumCategories, parentSelector)) { return 0; } QStringList oldAlbumCategories(ApplicationSettings::instance()->getAlbumCategoryNames()); if (albumCategories != oldAlbumCategories) { ApplicationSettings::instance()->setAlbumCategoryNames(albumCategories); } QString errMsg; PAlbum* album = 0; if (parent->isRoot() || parentSelector == 1) { album = AlbumManager::instance()->createPAlbum(albumRootPath, title, comments, date, category, errMsg); } else { album = AlbumManager::instance()->createPAlbum(parent, title, comments, date, category, errMsg); } if (!album) { QMessageBox::critical(qApp->activeWindow(), qApp->applicationName(), errMsg); return 0; } return album; } void AlbumModificationHelper::slotAlbumDelete() { slotAlbumDelete(boundAlbum(sender())); } void AlbumModificationHelper::slotAlbumDelete(PAlbum* album) { if (!album || album->isRoot() || album->isAlbumRoot()) { return; } // find subalbums QList childrenList; addAlbumChildrenToList(childrenList, album); DeleteDialog dialog(d->dialogParent); // All subalbums will be presented in the list as well if (!dialog.confirmDeleteList(childrenList, childrenList.size() == 1 ? DeleteDialogMode::Albums : DeleteDialogMode::Subalbums, DeleteDialogMode::UserPreference)) { return; } bool useTrash = !dialog.shouldDelete(); DIO::del(album, useTrash); } void AlbumModificationHelper::slotAlbumRename() { slotAlbumRename(boundAlbum(sender())); } void AlbumModificationHelper::slotAlbumRename(PAlbum* album) { if (!album) { return; } QString oldTitle(album->title()); QPointer textDlg = new QInputDialog(d->dialogParent); textDlg->setWindowTitle(i18n("Rename Album (%1)", oldTitle)); textDlg->setLabelText(i18n("Enter new album name:")); textDlg->resize(450, textDlg->sizeHint().height()); textDlg->setInputMode(QInputDialog::TextInput); textDlg->setTextEchoMode(QLineEdit::Normal); textDlg->setTextValue(oldTitle); if (textDlg->exec() != QDialog::Accepted) { delete textDlg; return; } QString title = textDlg->textValue(); delete textDlg; if (title != oldTitle) { QString errMsg; if (!AlbumManager::instance()->renamePAlbum(album, title, errMsg)) { QMessageBox::critical(qApp->activeWindow(), qApp->applicationName(), errMsg); } } } void AlbumModificationHelper::addAlbumChildrenToList(QList& list, Album* const album) { // simple recursive helper function if (album) { if (!list.contains(album->databaseUrl())) { list.append(album->databaseUrl()); } AlbumIterator it(album); while (it.current()) { addAlbumChildrenToList(list, *it); ++it; } } } void AlbumModificationHelper::slotAlbumEdit() { slotAlbumEdit(boundAlbum(sender())); } void AlbumModificationHelper::slotAlbumEdit(PAlbum* album) { if (!album || album->isRoot() || album->isAlbumRoot()) { return; } QString oldTitle(album->title()); QString oldComments(album->caption()); QString oldCategory(album->category()); QDate oldDate(album->date()); QStringList oldAlbumCategories(ApplicationSettings::instance()->getAlbumCategoryNames()); QString title, comments, category; QDate date; QStringList albumCategories; if (AlbumPropsEdit::editProps(album, title, comments, date, category, albumCategories)) { if (comments != oldComments) { album->setCaption(comments); } if (date != oldDate && date.isValid()) { album->setDate(date); } if (category != oldCategory) { album->setCategory(category); } ApplicationSettings::instance()->setAlbumCategoryNames(albumCategories); // Do this last : so that if anything else changed we can // successfully save to the db with the old name if (title != oldTitle) { QString errMsg; if (!AlbumManager::instance()->renamePAlbum(album, title, errMsg)) { QMessageBox::critical(d->dialogParent, qApp->applicationName(), errMsg); } } // Resorting the tree View after changing metadata DigikamApp::instance()->view()->slotSortAlbums(ApplicationSettings::instance()->getAlbumSortRole()); } } void AlbumModificationHelper::slotAlbumResetIcon(PAlbum* album) { if (!album) { return; } QString err; AlbumManager::instance()->updatePAlbumIcon(album, 0, err); } void AlbumModificationHelper::slotAlbumResetIcon() { slotAlbumResetIcon(boundAlbum(sender())); } } // namespace Digikam diff --git a/core/libs/album/treeview/labelstreeview.cpp b/core/libs/album/treeview/labelstreeview.cpp index 08cf825ef0..2d1e433242 100644 --- a/core/libs/album/treeview/labelstreeview.cpp +++ b/core/libs/album/treeview/labelstreeview.cpp @@ -1,974 +1,974 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2014-05-17 * Description : Album Labels Tree View. * * Copyright (C) 2014-2015 by Mohamed_Anwer * Copyright (C) 2014-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 "labelstreeview.h" // QT includes #include #include #include #include // Local includes #include "digikam_debug.h" #include "digikam_globals.h" #include "coredbsearchxml.h" #include "searchtabheader.h" #include "albummanager.h" #include "albumtreeview.h" #include "coredbconstants.h" #include "itemlister.h" #include "statesavingobject.h" #include "coredbaccess.h" #include "coredb.h" #include "colorlabelfilter.h" #include "picklabelfilter.h" #include "tagscache.h" #include "applicationsettings.h" #include "dnotificationwrapper.h" #include "digikamapp.h" #include "ratingwidget.h" #include "dbjobsmanager.h" namespace Digikam { class Q_DECL_HIDDEN LabelsTreeView::Private { public: explicit Private() : ratings(0), picks(0), colors(0), isCheckableTreeView(false), isLoadingState(false), iconSizeFromSetting(0) { } QFont regularFont; QSize iconSize; QTreeWidgetItem* ratings; QTreeWidgetItem* picks; QTreeWidgetItem* colors; bool isCheckableTreeView; bool isLoadingState; int iconSizeFromSetting; QHash > selectedLabels; static const QString configRatingSelectionEntry; static const QString configPickSelectionEntry; static const QString configColorSelectionEntry; static const QString configExpansionEntry; }; const QString LabelsTreeView::Private::configRatingSelectionEntry(QLatin1String("RatingSelection")); const QString LabelsTreeView::Private::configPickSelectionEntry(QLatin1String("PickSelection")); const QString LabelsTreeView::Private::configColorSelectionEntry(QLatin1String("ColorSelection")); const QString LabelsTreeView::Private::configExpansionEntry(QLatin1String("Expansion")); LabelsTreeView::LabelsTreeView(QWidget* const parent, bool setCheckable) : QTreeWidget(parent), StateSavingObject(this), d(new Private) { d->regularFont = ApplicationSettings::instance()->getTreeViewFont(); d->iconSizeFromSetting = ApplicationSettings::instance()->getTreeViewIconSize(); d->iconSize = QSize(d->iconSizeFromSetting, d->iconSizeFromSetting); d->isCheckableTreeView = setCheckable; setHeaderLabel(i18nc("@title", "Labels")); setUniformRowHeights(false); initTreeView(); if (d->isCheckableTreeView) { QTreeWidgetItemIterator it(this); while (*it) { if ((*it)->parent()) { (*it)->setFlags((*it)->flags()|Qt::ItemIsUserCheckable); (*it)->setCheckState(0, Qt::Unchecked); } ++it; } } else { setSelectionMode(QAbstractItemView::ExtendedSelection); } connect(ApplicationSettings::instance(), SIGNAL(setupChanged()), this, SLOT(slotSettingsChanged())); } LabelsTreeView::~LabelsTreeView() { delete d; } bool LabelsTreeView::isCheckable() const { return d->isCheckableTreeView; } bool LabelsTreeView::isLoadingState() const { return d->isLoadingState; } QPixmap LabelsTreeView::goldenStarPixmap(bool fillin) const { QPixmap pixmap = QPixmap(60, 60); pixmap.fill(Qt::transparent); QPainter p1(&pixmap); p1.setRenderHint(QPainter::Antialiasing, true); if (fillin) p1.setBrush(qApp->palette().color(QPalette::Link)); QPen pen(palette().color(QPalette::Active, foregroundRole())); p1.setPen(pen); QMatrix matrix; matrix.scale(4, 4); // 60px/15px (RatingWidget::starPolygon() size is 15*15px) p1.setMatrix(matrix); p1.drawPolygon(RatingWidget::starPolygon(), Qt::WindingFill); p1.end(); return pixmap; } QPixmap LabelsTreeView::colorRectPixmap(const QColor& color) const { QRect rect(8, 8, 48, 48); QPixmap pixmap = QPixmap(60, 60); pixmap.fill(Qt::transparent); QPainter p1(&pixmap); p1.setRenderHint(QPainter::Antialiasing, true); p1.setBrush(color); p1.setPen(palette().color(QPalette::Active, foregroundRole())); p1.drawRect(rect); p1.end(); return pixmap; } QHash > LabelsTreeView::selectedLabels() { QHash > selectedLabelsHash; QList selectedRatings; QList selectedPicks; QList selectedColors; if (d->isCheckableTreeView) { QTreeWidgetItemIterator it(this, QTreeWidgetItemIterator::Checked); while(*it) { QTreeWidgetItem* const item = (*it); - if(item->parent() == d->ratings) + if (item->parent() == d->ratings) selectedRatings << indexFromItem(item).row(); - else if(item->parent() == d->picks) + else if (item->parent() == d->picks) selectedPicks << indexFromItem(item).row(); else selectedColors << indexFromItem(item).row(); ++it; } } else { foreach (QTreeWidgetItem* const item, selectedItems()) { - if(item->parent() == d->ratings) + if (item->parent() == d->ratings) selectedRatings << indexFromItem(item).row(); - else if(item->parent() == d->picks) + else if (item->parent() == d->picks) selectedPicks << indexFromItem(item).row(); else selectedColors << indexFromItem(item).row(); } } selectedLabelsHash[Ratings] = selectedRatings; selectedLabelsHash[Picks] = selectedPicks; selectedLabelsHash[Colors] = selectedColors; return selectedLabelsHash; } void LabelsTreeView::doLoadState() { d->isLoadingState = true; KConfigGroup configGroup = getConfigGroup(); const QList expansion = configGroup.readEntry(entryName(d->configExpansionEntry), QList()); const QList selectedRatings = configGroup.readEntry(entryName(d->configRatingSelectionEntry), QList()); const QList selectedPicks = configGroup.readEntry(entryName(d->configPickSelectionEntry), QList()); const QList selectedColors = configGroup.readEntry(entryName(d->configColorSelectionEntry), QList()); d->ratings->setExpanded(true); d->picks->setExpanded(true); d->colors->setExpanded(true); foreach (int parent, expansion) { switch (parent) { case 1: d->ratings->setExpanded(false); break; case 2: d->picks->setExpanded(false); break; case 3: d->colors->setExpanded(false); default: break; } } foreach (int rating, selectedRatings) { if (d->isCheckableTreeView) d->ratings->child(rating)->setCheckState(0, Qt::Checked); else d->ratings->child(rating)->setSelected(true); } foreach (int pick, selectedPicks) { if (d->isCheckableTreeView) d->picks->child(pick)->setCheckState(0, Qt::Checked); else d->picks->child(pick)->setSelected(true); } foreach (int color, selectedColors) { if (d->isCheckableTreeView) d->colors->child(color)->setCheckState(0, Qt::Checked); else d->colors->child(color)->setSelected(true); } d->isLoadingState = false; } void LabelsTreeView::doSaveState() { KConfigGroup configGroup = getConfigGroup(); QList expansion; if (!d->ratings->isExpanded()) { expansion << 1; } if (!d->picks->isExpanded()) { expansion << 2; } if (!d->colors->isExpanded()) { expansion << 3; } QHash > labels = selectedLabels(); configGroup.writeEntry(entryName(d->configExpansionEntry), expansion); configGroup.writeEntry(entryName(d->configRatingSelectionEntry), labels[Ratings]); configGroup.writeEntry(entryName(d->configPickSelectionEntry), labels[Picks]); configGroup.writeEntry(entryName(d->configColorSelectionEntry), labels[Colors]); } void LabelsTreeView::setCurrentAlbum() { emit signalSetCurrentAlbum(); } void LabelsTreeView::initTreeView() { setIconSize(QSize(d->iconSizeFromSetting*5,d->iconSizeFromSetting)); initRatingsTree(); initPicksTree(); initColorsTree(); expandAll(); setRootIsDecorated(false); } void LabelsTreeView::initRatingsTree() { d->ratings = new QTreeWidgetItem(this); d->ratings->setText(0, i18n("Rating")); d->ratings->setFont(0, d->regularFont); d->ratings->setFlags(Qt::ItemIsEnabled); QTreeWidgetItem* const noRate = new QTreeWidgetItem(d->ratings); noRate->setText(0, i18n("No Rating")); noRate->setFont(0, d->regularFont); QPixmap pix(goldenStarPixmap().size()); pix.fill(Qt::transparent); QPainter p(&pix); p.setRenderHint(QPainter::Antialiasing, true); p.setPen(palette().color(QPalette::Active, foregroundRole())); p.drawPixmap(0, 0, goldenStarPixmap(false)); noRate->setIcon(0, QIcon(pix)); noRate->setSizeHint(0, d->iconSize); for (int rate = 1 ; rate <= 5 ; ++rate) { QTreeWidgetItem* const rateWidget = new QTreeWidgetItem(d->ratings); QPixmap pix(goldenStarPixmap().width()*rate, goldenStarPixmap().height()); pix.fill(Qt::transparent); QPainter p(&pix); int offset = 0; p.setRenderHint(QPainter::Antialiasing, true); p.setPen(palette().color(QPalette::Active, foregroundRole())); for (int i = 0 ; i < rate ; ++i) { p.drawPixmap(offset, 0, goldenStarPixmap()); offset += goldenStarPixmap().width(); } rateWidget->setIcon(0, QIcon(pix)); rateWidget->setSizeHint(0, d->iconSize); } } void LabelsTreeView::initPicksTree() { d->picks = new QTreeWidgetItem(this); d->picks->setText(0, i18n("Pick")); d->picks->setFont(0, d->regularFont); d->picks->setFlags(Qt::ItemIsEnabled); QStringList pickSetNames; pickSetNames << i18n("No Pick") << i18n("Rejected Item") << i18n("Pending Item") << i18n("Accepted Item"); QStringList pickSetIcons; pickSetIcons << QLatin1String("flag-black") << QLatin1String("flag-red") << QLatin1String("flag-yellow") << QLatin1String("flag-green"); foreach(const QString& pick, pickSetNames) { QTreeWidgetItem* const pickWidgetItem = new QTreeWidgetItem(d->picks); pickWidgetItem->setText(0, pick); pickWidgetItem->setFont(0, d->regularFont); pickWidgetItem->setIcon(0, QIcon::fromTheme(pickSetIcons.at(pickSetNames.indexOf(pick)))); } } void LabelsTreeView::initColorsTree() { d->colors = new QTreeWidgetItem(this); d->colors->setText(0, i18n("Color")); d->colors->setFont(0, d->regularFont); d->colors->setFlags(Qt::ItemIsEnabled); QTreeWidgetItem* noColor = new QTreeWidgetItem(d->colors); noColor->setText(0, i18n("No Color")); noColor->setFont(0, d->regularFont); noColor->setIcon(0, QIcon::fromTheme(QLatin1String("emblem-unmounted"))); QStringList colorSet; colorSet << QLatin1String("red") << QLatin1String("orange") << QLatin1String("yellow") << QLatin1String("darkgreen") << QLatin1String("darkblue") << QLatin1String("magenta") << QLatin1String("darkgray") << QLatin1String("black") << QLatin1String("white"); QStringList colorSetNames; colorSetNames << i18n("Red") << i18n("Orange") << i18n("Yellow") << i18n("Green") << i18n("Blue") << i18n("Magenta") << i18n("Gray") << i18n("Black") << i18n("White"); foreach(const QString& color, colorSet) { QTreeWidgetItem* const colorWidgetItem = new QTreeWidgetItem(d->colors); colorWidgetItem->setText(0, colorSetNames.at(colorSet.indexOf(color))); colorWidgetItem->setFont(0, d->regularFont); QPixmap colorIcon = colorRectPixmap(QColor(color)); colorWidgetItem->setIcon(0, QIcon(colorIcon)); colorWidgetItem->setSizeHint(0, d->iconSize); } } void LabelsTreeView::slotSettingsChanged() { if (d->iconSizeFromSetting != ApplicationSettings::instance()->getTreeViewIconSize()) { d->iconSizeFromSetting = ApplicationSettings::instance()->getTreeViewIconSize(); setIconSize(QSize(d->iconSizeFromSetting*5, d->iconSizeFromSetting)); d->iconSize = QSize(d->iconSizeFromSetting, d->iconSizeFromSetting); QTreeWidgetItemIterator it(this); while(*it) { if (*it) { (*it)->setSizeHint(0, d->iconSize); } ++it; } } if (d->regularFont != ApplicationSettings::instance()->getTreeViewFont()) { d->regularFont = ApplicationSettings::instance()->getTreeViewFont(); QTreeWidgetItemIterator it(this); while(*it) { if (*it) { (*it)->setFont(0, d->regularFont); } ++it; } } } void LabelsTreeView::restoreSelectionFromHistory(QHash > neededLabels) { QTreeWidgetItemIterator it(this, QTreeWidgetItemIterator::Selected); while(*it) { (*it)->setSelected(false); ++it; } foreach (int rateItemIndex, neededLabels[Ratings]) { d->ratings->child(rateItemIndex)->setSelected(true); } foreach (int pickItemIndex, neededLabels[Picks]) { d->picks->child(pickItemIndex)->setSelected(true); } foreach (int colorItemIndex, neededLabels[Colors]) { d->colors->child(colorItemIndex)->setSelected(true); } } // ------------------------------------------------------------------------------- class Q_DECL_HIDDEN AlbumLabelsSearchHandler::Private { public: explicit Private() : treeWidget(0), dbJobThread(0), restoringSelectionFromHistory(0), currentXmlIsEmpty(0), albumForSelectedItems(0) { } LabelsTreeView* treeWidget; SearchesDBJobsThread* dbJobThread; bool restoringSelectionFromHistory; bool currentXmlIsEmpty; QString oldXml; Album* albumForSelectedItems; QString generatedAlbumName; QList urlListForSelectedAlbum; }; AlbumLabelsSearchHandler::AlbumLabelsSearchHandler(LabelsTreeView* const treeWidget) : d(new Private) { d->treeWidget = treeWidget; if (!d->treeWidget->isCheckable()) { connect(d->treeWidget, SIGNAL(itemSelectionChanged()), this, SLOT(slotSelectionChanged())); connect(d->treeWidget, SIGNAL(signalSetCurrentAlbum()), this, SLOT(slotSetCurrentAlbum())); } else { connect(d->treeWidget, SIGNAL(itemClicked(QTreeWidgetItem*,int)), this, SLOT(slotCheckStateChanged())); } } AlbumLabelsSearchHandler::~AlbumLabelsSearchHandler() { delete d; } Album *AlbumLabelsSearchHandler::albumForSelectedItems() const { return d->albumForSelectedItems; } QList AlbumLabelsSearchHandler::imagesUrls() const { return d->urlListForSelectedAlbum; } QString AlbumLabelsSearchHandler::generatedName() const { return d->generatedAlbumName; } void AlbumLabelsSearchHandler::restoreSelectionFromHistory(const QHash >& neededLabels) { d->restoringSelectionFromHistory = true; d->treeWidget->restoreSelectionFromHistory(neededLabels); d->restoringSelectionFromHistory = false; slotSelectionChanged(); } bool AlbumLabelsSearchHandler::isRestoringSelectionFromHistory() const { return d->restoringSelectionFromHistory; } QString AlbumLabelsSearchHandler::createXMLForCurrentSelection(const QHash >& selectedLabels) { SearchXmlWriter writer; writer.setFieldOperator(SearchXml::standardFieldOperator()); QList ratings; QList colorsAndPicks; foreach (int rate, selectedLabels[LabelsTreeView::Ratings]) { if (rate == 0) { ratings << -1; } ratings << rate; } foreach (int color, selectedLabels[LabelsTreeView::Colors]) { colorsAndPicks << TagsCache::instance()->tagForColorLabel(color); } foreach (int pick, selectedLabels[LabelsTreeView::Picks]) { colorsAndPicks << TagsCache::instance()->tagForPickLabel(pick); } d->currentXmlIsEmpty = (ratings.isEmpty() && colorsAndPicks.isEmpty()) ? true : false; if (!ratings.isEmpty() && !colorsAndPicks.isEmpty()) { foreach (int val, ratings) { writer.writeGroup(); writer.writeField(QLatin1String("rating"), SearchXml::Equal); writer.writeValue(val); writer.finishField(); writer.writeField(QLatin1String("tagid"), SearchXml::InTree); writer.writeValue(colorsAndPicks); writer.finishField(); writer.finishGroup(); } } else if (!ratings.isEmpty()) { foreach (int rate, ratings) { writer.writeGroup(); writer.writeField(QLatin1String("rating"), SearchXml::Equal); writer.writeValue(rate); writer.finishField(); writer.finishGroup(); } } else if (!colorsAndPicks.isEmpty()) { writer.writeGroup(); writer.writeField(QLatin1String("tagid"), SearchXml::InTree); writer.writeValue(colorsAndPicks); writer.finishField(); writer.finishGroup(); } else { writer.writeGroup(); writer.finishGroup(); } writer.finish(); generateAlbumNameForExporting(selectedLabels[LabelsTreeView::Ratings], selectedLabels[LabelsTreeView::Colors], selectedLabels[LabelsTreeView::Picks]); return writer.xml(); } SAlbum* AlbumLabelsSearchHandler::search(const QString& xml) const { SAlbum* album = 0; int id; if (!d->treeWidget->isCheckable()) { album = AlbumManager::instance()->findSAlbum(SAlbum::getTemporaryTitle(DatabaseSearch::AdvancedSearch)); if (album) { id = album->id(); CoreDbAccess().db()->updateSearch(id, DatabaseSearch::AdvancedSearch, SAlbum::getTemporaryTitle(DatabaseSearch::AdvancedSearch), xml); } else { id = CoreDbAccess().db()->addSearch(DatabaseSearch::AdvancedSearch, SAlbum::getTemporaryTitle(DatabaseSearch::AdvancedSearch), xml); } album = new SAlbum(getDefaultTitle(), id); } else { album = AlbumManager::instance()->findSAlbum(getDefaultTitle()); if (album) { id = album->id(); CoreDbAccess().db()->updateSearch(id, DatabaseSearch::AdvancedSearch, getDefaultTitle(), xml); } else { id = CoreDbAccess().db()->addSearch(DatabaseSearch::AdvancedSearch, getDefaultTitle(), xml); } album = new SAlbum(d->generatedAlbumName, id); } if (!album->isUsedByLabelsTree()) album->setUsedByLabelsTree(true); return album; } void AlbumLabelsSearchHandler::generateAlbumNameForExporting(const QList& ratings, const QList& colorsList, const QList& picksList) { QString name; QString ratingsString; QString picksString; QString colorsString; if (!ratings.isEmpty()) { ratingsString += i18n("Rating: "); QListIterator it(ratings); while (it.hasNext()) { int rating = it.next(); if (rating == -1) { ratingsString += i18n("No Rating"); } else { ratingsString += QString::number(rating); } if (it.hasNext()) { ratingsString += QLatin1String(", "); } } } if (!colorsList.isEmpty()) { colorsString += i18n("Colors: "); QListIterator it(colorsList); while(it.hasNext()) { switch (it.next()) { case NoColorLabel: colorsString += i18n("No Color"); break; case RedLabel: colorsString += i18n("Red"); break; case OrangeLabel: colorsString += i18n("Orange"); break; case YellowLabel: colorsString += i18n("Yellow"); break; case GreenLabel: colorsString += i18n("Green"); break; case BlueLabel: colorsString += i18n("Blue"); break; case MagentaLabel: colorsString += i18n("Magenta"); break; case GrayLabel: colorsString += i18n("Gray"); break; case BlackLabel: colorsString += i18n("Black"); break; case WhiteLabel: colorsString += i18n("White"); break; default: break; } if (it.hasNext()) { colorsString += QLatin1String(", "); } } } if (!picksList.isEmpty()) { picksString += i18n("Picks: "); QListIterator it(picksList); while(it.hasNext()) { switch (it.next()) { case NoPickLabel: picksString += i18n("No Pick"); break; case RejectedLabel: picksString += i18n("Rejected"); break; case PendingLabel: picksString += i18n("Pending"); break; case AcceptedLabel: picksString += i18n("Accepted"); break; default: break; } if (it.hasNext()) { picksString += QLatin1String(", "); } } } if (ratingsString.isEmpty() && picksString.isEmpty()) { name = colorsString; } else if (ratingsString.isEmpty() && colorsString.isEmpty()) { name = picksString; } else if (colorsString.isEmpty() && picksString.isEmpty()) { name = ratingsString; } else if (ratingsString.isEmpty()) { name = picksString + QLatin1String(" | ") + colorsString; } else if (picksString.isEmpty()) { name = ratingsString + QLatin1String(" | ") + colorsString; } else if (colorsString.isEmpty()) { name = ratingsString + QLatin1String(" | ") + picksString; } else { name = ratingsString + QLatin1String(" | ") + picksString + QLatin1String(" | ") + colorsString; } d->generatedAlbumName = name; } void AlbumLabelsSearchHandler::imagesUrlsForCurrentAlbum() { SearchesDBJobInfo jobInfo; jobInfo.setSearchId( d->albumForSelectedItems->id() ); jobInfo.setRecursive(); d->dbJobThread = DBJobsManager::instance()->startSearchesJobThread(jobInfo); connect(d->dbJobThread, SIGNAL(finished()), this, SLOT(slotResult())); connect(d->dbJobThread, SIGNAL(data(QList)), this, SLOT(slotData(QList))); } QString AlbumLabelsSearchHandler::getDefaultTitle() const { if (d->treeWidget->isCheckable()) { return i18n("Exported Labels"); } else { return i18n("Labels Album"); } } void AlbumLabelsSearchHandler::slotSelectionChanged() { if (d->treeWidget->isLoadingState() || d->restoringSelectionFromHistory) { return; } QString xml = createXMLForCurrentSelection(d->treeWidget->selectedLabels()); SAlbum* const album = search(xml); if (album) { AlbumManager::instance()->setCurrentAlbums(QList() << album); d->albumForSelectedItems = album; d->oldXml = xml; } } void AlbumLabelsSearchHandler::slotCheckStateChanged() { QString currentXml = createXMLForCurrentSelection(d->treeWidget->selectedLabels()); if (currentXml == d->oldXml) { return; } if (d->albumForSelectedItems) { emit checkStateChanged(d->albumForSelectedItems, Qt::Unchecked); } SAlbum* const album = search(currentXml); if (album) { - if(!d->currentXmlIsEmpty) + if (!d->currentXmlIsEmpty) { d->albumForSelectedItems = album; imagesUrlsForCurrentAlbum(); } else { d->albumForSelectedItems = 0; } emit checkStateChanged(album, Qt::Checked); } d->oldXml = currentXml; } void AlbumLabelsSearchHandler::slotSetCurrentAlbum() { slotSelectionChanged(); } void AlbumLabelsSearchHandler::slotResult() { if (d->dbJobThread != sender()) { return; } if (d->dbJobThread->hasErrors()) { qCWarning(DIGIKAM_GENERAL_LOG) << "Failed to list urls: " << d->dbJobThread->errorsList().first(); // Pop-up a message about the error. DNotificationWrapper(QString(), d->dbJobThread->errorsList().first(), DigikamApp::instance(), DigikamApp::instance()->windowTitle()); } } void AlbumLabelsSearchHandler::slotData(const QList& data) { if (d->dbJobThread != sender() || data.isEmpty()) return; QList urlList; foreach (const ItemListerRecord &record, data) { ItemInfo info(record); urlList << info.fileUrl(); } d->urlListForSelectedAlbum = urlList; } } // namespace Digikam diff --git a/core/libs/database/engine/dbengineaccess.cpp b/core/libs/database/engine/dbengineaccess.cpp index 98705c9e5d..e853622be0 100644 --- a/core/libs/database/engine/dbengineaccess.cpp +++ b/core/libs/database/engine/dbengineaccess.cpp @@ -1,94 +1,94 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2007-03-18 * Description : Core database access wrapper. * * Copyright (C) 2016 by Swati Lodha * * 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 "dbengineaccess.h" // Qt includes #include #include #include #include // KDE includes #include // Local includes #include "digikam_debug.h" #include "dbengineparameters.h" #include "dbenginebackend.h" #include "dbengineerrorhandler.h" namespace Digikam { bool DbEngineAccess::checkReadyForUse(QString& error) { QStringList drivers = QSqlDatabase::drivers(); // Retrieving DB settings from config file DbEngineParameters internalServerParameters = DbEngineParameters::parametersFromConfig(KSharedConfig::openConfig()); // Checking for QSQLITE driver - if(internalServerParameters.SQLiteDatabaseType() == QLatin1String("QSQLITE")) + if (internalServerParameters.SQLiteDatabaseType() == QLatin1String("QSQLITE")) { if (!drivers.contains(QLatin1String("QSQLITE"))) { qCDebug(DIGIKAM_COREDB_LOG) << "Core database: no Sqlite3 driver available.\n" "List of QSqlDatabase drivers: " << drivers; error = i18n("The driver \"SQLITE\" for Sqlite3 databases is not available.\n" "digiKam depends on the drivers provided by the Qt::SQL module."); return false; } } // Checking for QMYSQL driver - else if(internalServerParameters.MySQLDatabaseType() == QLatin1String("QMYSQL")) + else if (internalServerParameters.MySQLDatabaseType() == QLatin1String("QMYSQL")) { if (!drivers.contains(QLatin1String("QMYSQL"))) { qCDebug(DIGIKAM_COREDB_LOG) << "Core database: no MySQL driver available.\n" "List of QSqlDatabase drivers: " << drivers; error = i18n("The driver \"MYSQL\" for MySQL databases is not available.\n" "digiKam depends on the drivers provided by the Qt::SQL module."); return false; } } else { qCDebug(DIGIKAM_COREDB_LOG) << "Database could not be found"; error = QLatin1String("No valid database type available."); return false; } return true; } } // namespace Digikam diff --git a/core/libs/database/item/query/itemquerybuilder.cpp b/core/libs/database/item/query/itemquerybuilder.cpp index e999266fc2..4d597ee27f 100644 --- a/core/libs/database/item/query/itemquerybuilder.cpp +++ b/core/libs/database/item/query/itemquerybuilder.cpp @@ -1,1408 +1,1408 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2007-03-22 * Description : Building complex database SQL queries from search descriptions * * Copyright (C) 2005 by Renchi Raju * Copyright (C) 2007-2012 by Marcel Wiesweg * Copyright (C) 2012-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 "itemquerybuilder_p.h" namespace Digikam { ItemQueryBuilder::ItemQueryBuilder() { // build a lookup table for month names for (int i = 1 ; i <= 12 ; ++i) { m_shortMonths[i-1] = QLocale().standaloneMonthName(i, QLocale::ShortFormat).toLower(); m_longMonths[i-1] = QLocale().standaloneMonthName(i, QLocale::LongFormat).toLower(); } m_imageTagPropertiesJoined = false; } void ItemQueryBuilder::setImageTagPropertiesJoined(bool isJoined) { m_imageTagPropertiesJoined = isJoined; } QString ItemQueryBuilder::buildQuery(const QString& q, QList *boundValues, ItemQueryPostHooks* const hooks) const { // Handle legacy query descriptions if (q.startsWith(QLatin1String("digikamsearch:"))) { return buildQueryFromUrl(QUrl(q), boundValues); } else { return buildQueryFromXml(q, boundValues, hooks); } } QString ItemQueryBuilder::buildQueryFromXml(const QString& xml, QList *boundValues, ItemQueryPostHooks* const hooks) const { SearchXmlCachingReader reader(xml); QString sql; bool firstGroup = true; while (!reader.atEnd()) { reader.readNext(); if (reader.isEndElement()) { continue; } if (reader.isGroupElement()) { addSqlOperator(sql, reader.groupOperator(), firstGroup); if (firstGroup) { firstGroup = false; } buildGroup(sql, reader, boundValues, hooks); } } qCDebug(DIGIKAM_DATABASE_LOG) << sql; return sql; } void ItemQueryBuilder::buildGroup(QString& sql, SearchXmlCachingReader& reader, QList *boundValues, ItemQueryPostHooks* const hooks) const { sql += QLatin1String(" ("); SearchXml::Operator mainGroupOp = reader.groupOperator(); bool firstField = true; bool hasContent = false; while (!reader.atEnd()) { reader.readNext(); if (reader.isEndElement()) { break; } // subgroup if (reader.isGroupElement()) { hasContent = true; addSqlOperator(sql, reader.groupOperator(), firstField); if (firstField) { firstField = false; } buildGroup(sql, reader, boundValues, hooks); } if (reader.isFieldElement()) { hasContent = true; SearchXml::Operator fieldOperator = reader.fieldOperator(); addSqlOperator(sql, fieldOperator, firstField); if (firstField) { firstField = false; } if (!buildField(sql, reader, reader.fieldName(), boundValues, hooks)) { addNoEffectContent(sql, fieldOperator); } } } if (!hasContent) { addNoEffectContent(sql, mainGroupOp); } sql += QLatin1String(") "); } bool ItemQueryBuilder::buildField(QString& sql, SearchXmlCachingReader& reader, const QString& name, QList* boundValues, ItemQueryPostHooks* const hooks) const { SearchXml::Relation relation = reader.fieldRelation(); FieldQueryBuilder fieldQuery(sql, reader, boundValues, hooks, relation); // First catch all noeffect fields. Those are only used for message passing when no Signal-Slot-communication is possible if (name.startsWith(QLatin1String("noeffect_"))) { return false; } else if (name == QLatin1String("albumid")) { if (relation == SearchXml::Equal || relation == SearchXml::Unequal) { fieldQuery.addIntField(QLatin1String("Images.album")); } else if (relation == SearchXml::InTree) { // see also: CoreDB::getItemNamesInAlbum QList ids = reader.valueToIntOrIntList(); if (ids.isEmpty()) { qCDebug(DIGIKAM_DATABASE_LOG) << "Relation 'InTree', name 'albumid': No values given"; return false; } sql += QString::fromUtf8("(Images.album IN " " (SELECT DISTINCT id " " FROM Albums WHERE "); bool firstCondition = true; foreach (int albumID, ids) { addSqlOperator(sql, SearchXml::Or, firstCondition); firstCondition = false; CoreDbAccess access; int rootId = access.db()->getAlbumRootId(albumID); QString relativePath = access.db()->getAlbumRelativePath(albumID); QString childrenWildcard; if (relativePath == QLatin1String("/")) { childrenWildcard = QLatin1String("/%"); } else { childrenWildcard = relativePath + QLatin1String("/%"); } sql += QString::fromUtf8(" ( albumRoot=? AND (relativePath=? OR relativePath LIKE ?) ) "); *boundValues << rootId << relativePath << childrenWildcard; } sql += QLatin1String(" ))"); } else if (relation == SearchXml::OneOf) { fieldQuery.addChoiceIntField(QLatin1String("Images.album")); } } else if (name == QLatin1String("albumname")) { fieldQuery.addStringField(QLatin1String("Albums.relativePath")); } else if (name == QLatin1String("albumcaption")) { fieldQuery.addStringField(QLatin1String("Albums.caption")); } else if (name == QLatin1String("albumcollection")) { fieldQuery.addChoiceStringField(QLatin1String("Albums.collection")); } else if (name == QLatin1String("tagid") || name == QLatin1String("labels")) { if (relation == SearchXml::Equal) { sql += QString::fromUtf8(" (Images.id IN " " (SELECT imageid FROM ImageTags " " WHERE tagid = ?)) "); *boundValues << reader.valueToInt(); } else if (relation == SearchXml::Unequal) { sql += QString::fromUtf8(" (Images.id NOT IN " " (SELECT imageid FROM ImageTags " " WHERE tagid = ?)) "); *boundValues << reader.valueToInt(); } else if (relation == SearchXml::InTree || relation == SearchXml::NotInTree) { QList ids = reader.valueToIntOrIntList(); if (ids.isEmpty()) { qCDebug(DIGIKAM_DATABASE_LOG) << "Relation 'InTree', name 'tagid': No values given"; return false; } if (relation == SearchXml::InTree) { sql += QString::fromUtf8(" (Images.id IN "); } else { sql += QString::fromUtf8(" (Images.id NOT IN "); } sql += QString::fromUtf8(" (SELECT ImageTags.imageid FROM ImageTags INNER JOIN TagsTree ON ImageTags.tagid = TagsTree.id " " WHERE "); bool firstCondition = true; foreach (int tagID, ids) { addSqlOperator(sql, SearchXml::Or, firstCondition); firstCondition = false; sql += QString::fromUtf8(" (TagsTree.pid = ? OR ImageTags.tagid = ? ) "); *boundValues << tagID << tagID; } sql += QString::fromUtf8(" )) "); } else if (relation == SearchXml::OneOf) { QList values = reader.valueToIntList(); bool searchForNull = values.removeAll(-1); sql += QLatin1String(" (Images.id IN ("); if (searchForNull) { sql += QLatin1String(") OR ") + name + QLatin1String(" IS NULL) "); } else { sql += QString::fromUtf8(" SELECT imageid FROM ImageTags " " WHERE tagid IN ("); CoreDB::addBoundValuePlaceholders(sql, values.size()); sql += QLatin1String("))) "); } foreach (int tagID, values) { *boundValues << tagID; } } else if (relation == SearchXml::AllOf) { // there must be an entry in ImageTags for every given tag id QList ids = reader.valueToIntOrIntList(); bool firstCondition = true; foreach (int tagID, ids) { addSqlOperator(sql, SearchXml::And, firstCondition); firstCondition = false; sql += QString::fromUtf8(" (Images.id IN " " (SELECT imageid FROM ImageTags " " WHERE tagid = ?)) "); *boundValues << tagID; } } } else if (name == QLatin1String("tagname")) { QString tagname = QLatin1Char('%') + reader.value() + QLatin1Char('%'); if (relation == SearchXml::Equal || relation == SearchXml::Like) { sql += QString::fromUtf8(" (Images.id IN " " (SELECT imageid FROM ImageTags " " WHERE tagid IN " " (SELECT id FROM Tags WHERE name LIKE ?))) "); *boundValues << tagname; } else if (relation == SearchXml::Unequal || relation == SearchXml::NotLike) { sql += QString::fromUtf8(" (Images.id NOT IN " " (SELECT imageid FROM ImageTags " " WHERE tagid IN " " (SELECT id FROM Tags WHERE name LIKE ?))) "); *boundValues << tagname; } else if (relation == SearchXml::InTree) { sql += QString::fromUtf8(" (Images.id IN " " (SELECT ImageTags.imageid FROM ImageTags INNER JOIN TagsTree ON ImageTags.tagid = TagsTree.id " " WHERE TagsTree.pid = (SELECT id FROM Tags WHERE name LIKE ?) " " or ImageTags.tagid = (SELECT id FROM Tags WHERE name LIKE ?) )) "); *boundValues << tagname << tagname; } else if (relation == SearchXml::NotInTree) { sql += QString::fromUtf8(" (Images.id NOT IN " " (SELECT ImageTags.imageid FROM ImageTags INNER JOIN TagsTree ON ImageTags.tagid = TagsTree.id " " WHERE TagsTree.pid = (SELECT id FROM Tags WHERE name LIKE ?) " " or ImageTags.tagid = (SELECT id FROM Tags WHERE name LIKE ?) )) "); *boundValues << tagname << tagname; } } else if (name == QLatin1String("nottagged")) { reader.readToEndOfElement(); sql += QString::fromUtf8(" (Images.id NOT IN (SELECT imageid FROM ImageTags " " WHERE tagid NOT IN (SELECT id FROM Tags " " WHERE pid IN (SELECT id FROM Tags " " WHERE name = '_Digikam_Internal_Tags_') ))) "); } else if (name == QLatin1String("notag")) { reader.readToEndOfElement(); sql += QString::fromUtf8(" (Images.id NOT IN " " (SELECT imageid FROM ImageTags)) "); } else if (name == QLatin1String("imageid")) { fieldQuery.addLongListField(QLatin1String("Images.id")); } else if (name == QLatin1String("filename")) { fieldQuery.addStringField(QLatin1String("Images.name")); } else if (name == QLatin1String("modificationdate")) { fieldQuery.addDateField(QLatin1String("Images.modificationDate")); } else if (name == QLatin1String("filesize")) { fieldQuery.addIntField(QLatin1String("Images.fileSize")); } else if (name == QLatin1String("rating")) { fieldQuery.addIntField(QLatin1String("ImageInformation.rating")); } else if (name == QLatin1String("creationdate")) { fieldQuery.addDateField(QLatin1String("ImageInformation.creationDate")); } else if (name == QLatin1String("digitizationdate")) { fieldQuery.addDateField(QLatin1String("ImageInformation.digitizationDate")); } else if (name == QLatin1String("orientation")) { fieldQuery.addChoiceIntField(QLatin1String("ImageInformation.orientation")); } else if (name == QLatin1String("pageorientation")) { if (relation == SearchXml::Equal) { int pageOrientation = reader.valueToInt(); // "1" is landscape, "2" is portrait, "3" is landscape regardless of Exif, "4" is portrait regardless of Exif if (pageOrientation == 1) { sql += QString::fromUtf8(" ( (ImageInformation.orientation <= ? AND ImageInformation.width >= ImageInformation.height) " " OR (ImageInformation.orientation >= ? AND ImageInformation.width <= ImageInformation.height) ) "); *boundValues << MetaEngine::ORIENTATION_VFLIP << MetaEngine::ORIENTATION_ROT_90_HFLIP; } else if (pageOrientation == 2) { sql += QString::fromUtf8(" ( (ImageInformation.orientation <= ? AND ImageInformation.width < ImageInformation.height) " " OR (ImageInformation.orientation >= ? AND ImageInformation.width > ImageInformation.height) ) "); *boundValues << MetaEngine::ORIENTATION_VFLIP << MetaEngine::ORIENTATION_ROT_90_HFLIP; } else if (pageOrientation == 3 || pageOrientation == 4) { // ignoring Exif orientation sql += QString::fromUtf8(" ( ImageInformation.width "); ItemQueryBuilder::addSqlRelation(sql, pageOrientation == 3 ? SearchXml::GreaterThanOrEqual : SearchXml::LessThanOrEqual); sql += QString::fromUtf8(" ImageInformation.height) "); } } } else if (name == QLatin1String("width")) { sql += QString::fromUtf8(" ( (ImageInformation.orientation <= ? AND "); *boundValues << MetaEngine::ORIENTATION_VFLIP; fieldQuery.addIntField(QLatin1String("ImageInformation.width")); sql += QString::fromUtf8(") OR (ImageInformation.orientation >= ? AND "); *boundValues << MetaEngine::ORIENTATION_ROT_90_HFLIP; fieldQuery.addIntField(QLatin1String("ImageInformation.height")); sql += QString::fromUtf8(" ) ) "); } else if (name == QLatin1String("height")) { sql += QString::fromUtf8(" ( (ImageInformation.orientation <= ? AND "); *boundValues << MetaEngine::ORIENTATION_VFLIP; fieldQuery.addIntField(QLatin1String("ImageInformation.height")); sql += QString::fromUtf8(") OR (ImageInformation.orientation >= ? AND "); *boundValues << MetaEngine::ORIENTATION_ROT_90_HFLIP; fieldQuery.addIntField(QLatin1String("ImageInformation.width")); sql += QString::fromUtf8(" ) ) "); } else if (name == QLatin1String("aspectratioimg")) { QString query; QString readerString = (reader.valueToStringOrStringList()).at(0); - if(readerString.contains(QRegExp(QLatin1String("^\\d+:\\d+$")))) + if (readerString.contains(QRegExp(QLatin1String("^\\d+:\\d+$")))) { QStringList ratioNum = readerString.split(QLatin1Char(':'), QString::SkipEmptyParts); int num = ratioNum.at(0).toInt(); int denominator = ratioNum.at(1).toInt(); query = QString::fromUtf8("ABS((ImageInformation.width/CAST(ImageInformation.height AS DOUBLE)) - ?) < 0.1"); sql += QString::fromUtf8(" (") + query + QString::fromUtf8(") "); *boundValues << (double)num/denominator; } - else if(readerString.contains(QRegExp(QLatin1String("^\\d+(.\\d+)?$")))) + else if (readerString.contains(QRegExp(QLatin1String("^\\d+(.\\d+)?$")))) { query = QString::fromUtf8("ABS((ImageInformation.width/CAST(ImageInformation.height AS DOUBLE)) - ?) < 0.1"); sql += QString::fromUtf8(" (") + query + QString::fromUtf8(") "); *boundValues << readerString.toDouble(); } } else if (name == QLatin1String("pixelsize")) { fieldQuery.addIntField(QLatin1String("(ImageInformation.width * ImageInformation.height)")); } else if (name == QLatin1String("pixels")) { fieldQuery.addIntField(QLatin1String("(ImageInformation.width * ImageInformation.height)")); } else if (name == QLatin1String("format")) { fieldQuery.addChoiceStringField(QLatin1String("ImageInformation.format")); } else if (name == QLatin1String("colordepth")) { fieldQuery.addIntField(QLatin1String("ImageInformation.colorDepth")); } else if (name == QLatin1String("colormodel")) { fieldQuery.addIntField(QLatin1String("ImageInformation.colorModel")); } else if (name == QLatin1String("videoaspectratio")) { if (relation == SearchXml::OneOf) { QStringList values = reader.valueToStringList(); if (values.isEmpty()) { qCDebug(DIGIKAM_DATABASE_LOG) << "List for OneOf is empty"; return false; } QList ratioValues; foreach (const QString& value, values) { *boundValues << value; if (value.contains(QLatin1Char(':'))) { QStringList ratioNum = value.split(QLatin1Char(':'), QString::SkipEmptyParts); int num = ratioNum.at(0).toInt(); int denominator = ratioNum.at(1).toInt(); ratioValues << (double)num/denominator; } } sql += QString::fromUtf8("(VideoMetadata.aspectRatio IN ("); CoreDB::addBoundValuePlaceholders(sql, values.size()); sql += QString::fromUtf8(") "); QString query = QString::fromUtf8("ABS((CAST(VideoMetadata.aspectRatio AS DOUBLE) - ?) < 0.1) "); foreach (double value, ratioValues) { *boundValues << value; sql += QString::fromUtf8("OR ") + query; } sql += QString::fromUtf8(") "); } else { QString value = reader.value(); *boundValues << value; if (value.contains(QLatin1Char(':'))) { QStringList ratioNum = value.split(QLatin1Char(':'), QString::SkipEmptyParts); int num = ratioNum.at(0).toInt(); int denominator = ratioNum.at(1).toInt(); *boundValues << (double)num/denominator; } sql += QString::fromUtf8("(VideoMetadata.aspectRatio=? OR ABS((CAST(VideoMetadata.aspectRatio AS DOUBLE) - ?) < 0.1 )) "); } } else if (name == QLatin1String("videoaudiobitrate")) { //fieldQuery.addIntField("VideoMetadata.audioBitRate"); QList values = reader.valueToIntList(); if (values.size() != 2) { qCWarning(DIGIKAM_DATABASE_LOG) << "Relation Interval requires a list of two values"; return false; } sql += QString::fromUtf8(" ( CAST(VideoMetadata.audioBitRate AS INTEGER)"); ItemQueryBuilder::addSqlRelation(sql, relation == SearchXml::Interval ? SearchXml::GreaterThanOrEqual : SearchXml::GreaterThan); sql += QString::fromUtf8(" ? AND CAST(VideoMetadata.audioBitRate AS INTEGER)"); ItemQueryBuilder::addSqlRelation(sql, relation == SearchXml::Interval ? SearchXml::LessThanOrEqual : SearchXml::LessThan); sql += QString::fromUtf8(" ?) "); *boundValues << values.first() << values.last(); } else if (name == QLatin1String("videoaudiochanneltype")) { if (relation == SearchXml::OneOf) { QStringList values = reader.valueToStringList(); if (values.isEmpty()) { qCDebug(DIGIKAM_DATABASE_LOG) << "List for OneOf is empty"; return false; } foreach (const QString& value, values) { *boundValues << value; if (value == QLatin1String("1")) { *boundValues << QLatin1String("Mono"); } else if (value == QLatin1String("2")) { *boundValues << QLatin1String("Stereo"); } } sql += QString::fromUtf8("(VideoMetadata.audioChannelType IN ("); CoreDB::addBoundValuePlaceholders(sql, boundValues->size()); sql += QString::fromUtf8(")) "); } else { QString value = reader.value(); *boundValues << value; if (value == QLatin1String("1")) { *boundValues << QLatin1String("Mono"); } else if (value == QLatin1String("2")) { *boundValues << QLatin1String("Stereo"); } sql += QString::fromUtf8("(VideoMetadata.audioChannelType IN ("); CoreDB::addBoundValuePlaceholders(sql, boundValues->size()); sql += QString::fromUtf8(")) "); } } else if (name == QLatin1String("videoaudioCodec")) { fieldQuery.addChoiceStringField(QLatin1String("VideoMetadata.audioCompressor")); } else if (name == QLatin1String("videoduration")) { QList values = reader.valueToIntList(); if (values.size() != 2) { qCWarning(DIGIKAM_DATABASE_LOG) << "Relation Interval requires a list of two values"; return false; } sql += QString::fromUtf8(" ( CAST(VideoMetadata.duration AS INTEGER)"); ItemQueryBuilder::addSqlRelation(sql, relation == SearchXml::Interval ? SearchXml::GreaterThanOrEqual : SearchXml::GreaterThan); sql += QString::fromUtf8(" ? AND CAST(VideoMetadata.duration AS INTEGER)"); ItemQueryBuilder::addSqlRelation(sql, relation == SearchXml::Interval ? SearchXml::LessThanOrEqual : SearchXml::LessThan); sql += QString::fromUtf8(" ?) "); *boundValues << values.first()*1000 << values.last()*1000; } else if (name == QLatin1String("videoframerate")) { //fieldQuery.addChoiceStringField("VideoMetadata.frameRate"); QList values = reader.valueToDoubleList(); if (values.size() != 2) { qCWarning(DIGIKAM_DATABASE_LOG) << "Relation Interval requires a list of two values"; return false; } sql += QString::fromUtf8(" ( CAST(VideoMetadata.frameRate AS DOUBLE)"); ItemQueryBuilder::addSqlRelation(sql, relation == SearchXml::Interval ? SearchXml::GreaterThanOrEqual : SearchXml::GreaterThan); sql += QString::fromUtf8(" ? AND CAST(VideoMetadata.frameRate AS DOUBLE)"); ItemQueryBuilder::addSqlRelation(sql, relation == SearchXml::Interval ? SearchXml::LessThanOrEqual : SearchXml::LessThan); sql += QString::fromUtf8(" ?) "); *boundValues << values.first() << values.last(); } else if (name == QLatin1String("videocodec")) { if (relation == SearchXml::OneOf) { QStringList values = reader.valueToStringList(); if (values.isEmpty()) { qCDebug(DIGIKAM_DATABASE_LOG) << "List for OneOf is empty"; return false; } foreach (const QString& value, values) { sql += QString::fromUtf8("( Upper(VideoMetadata.videoCodec) LIKE '%") + value.toUpper() + QString::fromUtf8("%' "); if (value != values.last()) { sql += QString::fromUtf8("OR "); } } sql += QString::fromUtf8(") "); } else { QString value = reader.value(); sql += QString::fromUtf8("(Upper(VideoMetadata.videoCodec) LIKE '%") + value.toUpper() + QString::fromUtf8("%') "); } } else if (name == QLatin1String("make")) { fieldQuery.addChoiceStringField(QLatin1String("ImageMetadata.make")); } else if (name == QLatin1String("model")) { fieldQuery.addChoiceStringField(QLatin1String("ImageMetadata.model")); } else if (name == QLatin1String("lenses")) { fieldQuery.addChoiceStringField(QLatin1String("ImageMetadata.lens")); } else if (name == QLatin1String("aperture")) { fieldQuery.addDoubleField(QLatin1String("ImageMetadata.aperture")); } else if (name == QLatin1String("focallength")) { fieldQuery.addDoubleField(QLatin1String("ImageMetadata.focalLength")); } else if (name == QLatin1String("focallength35")) { fieldQuery.addDoubleField(QLatin1String("ImageMetadata.focalLength35")); } else if (name == QLatin1String("exposuretime")) { fieldQuery.addDoubleField(QLatin1String("ImageMetadata.exposureTime")); } else if (name == QLatin1String("exposureprogram")) { fieldQuery.addChoiceIntField(QLatin1String("ImageMetadata.exposureProgram")); } else if (name == QLatin1String("exposuremode")) { fieldQuery.addChoiceIntField(QLatin1String("ImageMetadata.exposureMode")); } else if (name == QLatin1String("sensitivity")) { fieldQuery.addIntField(QLatin1String("ImageMetadata.sensitivity")); } else if (name == QLatin1String("flashmode")) { fieldQuery.addIntBitmaskField(QLatin1String("ImageMetadata.flash")); } else if (name == QLatin1String("whitebalance")) { fieldQuery.addChoiceIntField(QLatin1String("ImageMetadata.whiteBalance")); } else if (name == QLatin1String("whitebalancecolortemperature")) { fieldQuery.addIntField(QLatin1String("ImageMetadata.whiteBalanceColorTemperature")); } else if (name == QLatin1String("meteringmode")) { fieldQuery.addChoiceIntField(QLatin1String("ImageMetadata.meteringMode")); } else if (name == QLatin1String("subjectdistance")) { fieldQuery.addDoubleField(QLatin1String("ImageMetadata.subjectDistance")); } else if (name == QLatin1String("subjectdistancecategory")) { fieldQuery.addChoiceIntField(QLatin1String("ImageMetadata.subjectDistanceCategory")); } else if (name == QLatin1String("position")) { fieldQuery.addPosition(); } else if (name == QLatin1String("latitude")) { fieldQuery.addDoubleField(QLatin1String("ImagePositions.latitudeNumber")); } else if (name == QLatin1String("longitude")) { fieldQuery.addDoubleField(QLatin1String("ImagePositions.longitudeNumber")); } else if (name == QLatin1String("altitude")) { fieldQuery.addDoubleField(QLatin1String("ImagePositions.altitude")); } else if (name == QLatin1String("positionorientation")) { fieldQuery.addDoubleField(QLatin1String("ImagePositions.orientation")); } else if (name == QLatin1String("positiontilt")) { fieldQuery.addDoubleField(QLatin1String("ImagePositions.tilt")); } else if (name == QLatin1String("positionroll")) { fieldQuery.addDoubleField(QLatin1String("ImagePositions.roll")); } else if (name == QLatin1String("positiondescription")) { fieldQuery.addStringField(QLatin1String("ImagePositions.description")); } else if (name == QLatin1String("nogps")) { sql += QString::fromUtf8(" (ImagePositions.latitudeNumber IS NULL AND ImagePositions.longitudeNumber IS NULL) "); } else if (name == QLatin1String("creator")) { sql += QString::fromUtf8(" (Images.id IN " " (SELECT imageid FROM ImageCopyright " " WHERE property='creator' and value "); ItemQueryBuilder::addSqlRelation(sql, relation); sql += QString::fromUtf8(" ?)) "); *boundValues << fieldQuery.prepareForLike(reader.value()); } else if (name == QLatin1String("comment")) { sql += QString::fromUtf8(" (Images.id IN " " (SELECT imageid FROM ImageComments " " WHERE type=? AND comment "); ItemQueryBuilder::addSqlRelation(sql, relation); sql += QString::fromUtf8(" ?)) "); *boundValues << DatabaseComment::Comment << fieldQuery.prepareForLike(reader.value()); } else if (name == QLatin1String("commentauthor")) { sql += QString::fromUtf8(" (Images.id IN " " (SELECT imageid FROM ImageComments " " WHERE type=? AND author "); ItemQueryBuilder::addSqlRelation(sql, relation); sql += QString::fromUtf8(" ?)) "); *boundValues << DatabaseComment::Comment << fieldQuery.prepareForLike(reader.value()); } else if (name == QLatin1String("headline")) { sql += QString::fromUtf8(" (Images.id IN " " (SELECT imageid FROM ImageComments " " WHERE type=? AND comment "); ItemQueryBuilder::addSqlRelation(sql, relation); sql += QString::fromUtf8(" ?)) "); *boundValues << DatabaseComment::Headline << fieldQuery.prepareForLike(reader.value()); } else if (name == QLatin1String("title")) { sql += QString::fromUtf8(" (Images.id IN " " (SELECT imageid FROM ImageComments " " WHERE type=? AND comment "); ItemQueryBuilder::addSqlRelation(sql, relation); sql += QString::fromUtf8(" ?)) "); *boundValues << DatabaseComment::Title << fieldQuery.prepareForLike(reader.value()); } else if (name == QLatin1String("imagetagproperty")) { if (relation == SearchXml::Equal || relation == SearchXml::InTree) { // First, read attributes QStringRef tagAttribute = reader.attributes().value(QLatin1String("tagid")); int tagId = 0; if (!tagAttribute.isEmpty()) { tagId = tagAttribute.toString().toInt(); } // read values: one or two strings QStringList values = reader.valueToStringOrStringList(); if (values.size() < 1 || values.size() > 2) { qCDebug(DIGIKAM_DATABASE_LOG) << "The imagetagproperty field requires one value (property) or two values (property, value)."; return false; } QString selectQuery; // %1 is resolved to either "ImageTagProperties." or the empty string if (tagId) { if (relation == SearchXml::Equal) { selectQuery += QString::fromUtf8("%1tagid=? AND "); *boundValues << tagId; } else // InTree { selectQuery += QString::fromUtf8("(%1tagid=? OR %1tagid IN (SELECT id FROM TagsTree WHERE pid=?)) AND "); *boundValues << tagId << tagId; } } if (values.size() == 1) { selectQuery += QString::fromUtf8("%1property=? "); *boundValues << values.first(); } else if (values.size() == 2) { selectQuery += QString::fromUtf8("%1property=? AND %1value "); ItemQueryBuilder::addSqlRelation(selectQuery, relation); selectQuery += QString::fromUtf8(" ? "); *boundValues << values.at(0) << fieldQuery.prepareForLike(values.at(1)); } // This indicates that the ImageTagProperties is joined in the SELECT query, // so one entry is listed for each property entry (not for each image id) if (m_imageTagPropertiesJoined) { sql += QString::fromUtf8(" ( "); sql += selectQuery.arg(QString::fromUtf8("ImageTagProperties.")); sql += QString::fromUtf8(" ) "); } else { sql += QString::fromUtf8(" (Images.id IN " " (SELECT imageid FROM ImageTagProperties WHERE "); sql += selectQuery.arg(QString()); sql += QString::fromUtf8(" )) "); } } } else if (name == QLatin1String("keyword")) { // keyword is the common search in the text fields sql += QLatin1String(" ( "); addSqlOperator(sql, SearchXml::Or, true); buildField(sql, reader, QLatin1String("albumname"), boundValues, hooks); addSqlOperator(sql, SearchXml::Or, false); buildField(sql, reader, QLatin1String("filename"), boundValues, hooks); addSqlOperator(sql, SearchXml::Or, false); buildField(sql, reader, QLatin1String("tagname"), boundValues, hooks); addSqlOperator(sql, SearchXml::Or, false); buildField(sql, reader, QLatin1String("albumcaption"), boundValues, hooks); addSqlOperator(sql, SearchXml::Or, false); buildField(sql, reader, QLatin1String("albumcollection"), boundValues, hooks); addSqlOperator(sql, SearchXml::Or, false); buildField(sql, reader, QLatin1String("comment"), boundValues, hooks); addSqlOperator(sql, SearchXml::Or, false); buildField(sql, reader, QLatin1String("title"), boundValues, hooks); sql += QLatin1String(" ) "); } else if (name == QLatin1String("similarity")) { qCWarning(DIGIKAM_DATABASE_LOG) << "Search field \"similarity\" is not supported by ItemQueryBuilder"; } else { qCDebug(DIGIKAM_DATABASE_LOG) << "Search field" << name << "not known by this version of ItemQueryBuilder"; return false; } return true; } void ItemQueryBuilder::addSqlOperator(QString& sql, SearchXml::Operator op, bool isFirst) { if (isFirst) { if (op == SearchXml::AndNot || op == SearchXml::OrNot) { sql += QLatin1String("NOT"); } return; } switch (op) { case SearchXml::And: sql += QLatin1String("AND"); break; case SearchXml::Or: sql += QLatin1String("OR"); break; case SearchXml::AndNot: sql += QLatin1String("AND NOT"); break; case SearchXml::OrNot: sql += QLatin1String("OR NOT"); break; } } void ItemQueryBuilder::addSqlRelation(QString& sql, SearchXml::Relation rel) { switch (rel) { default: case SearchXml::Equal: sql += QLatin1Char('='); break; case SearchXml::Unequal: sql += QLatin1String("<>"); break; case SearchXml::Like: sql += QLatin1String("LIKE"); break; case SearchXml::NotLike: sql += QLatin1String("NOT LIKE"); break; case SearchXml::LessThan: sql += QLatin1Char('<'); break; case SearchXml::GreaterThan: sql += QLatin1Char('>'); break; case SearchXml::LessThanOrEqual: sql += QLatin1String("<="); break; case SearchXml::GreaterThanOrEqual: sql += QLatin1String(">="); break; case SearchXml::OneOf: sql += QLatin1String("IN"); break; } } void ItemQueryBuilder::addNoEffectContent(QString& sql, SearchXml::Operator op) { // add a condition statement with no effect switch (op) { case SearchXml::And: case SearchXml::Or: sql += QLatin1String(" 1 "); break; case SearchXml::AndNot: case SearchXml::OrNot: sql += QLatin1String(" 0 "); break; } } QString ItemQueryBuilder::convertFromUrlToXml(const QUrl& url) const { int count = QUrlQuery(url).queryItemValue(QLatin1String("count")).toInt(); if (count <= 0) { return QString(); } QMap rulesMap; for (int i = 1 ; i <= count ; ++i) { RuleTypeForConversion rule; QString key = QUrlQuery(url).queryItemValue(QString::number(i) + QLatin1String(".key")).toLower(); QString op = QUrlQuery(url).queryItemValue(QString::number(i) + QLatin1String(".op")).toLower(); if (key == QLatin1String("album")) { rule.key = QLatin1String("albumid"); } else if (key == QLatin1String("imagename")) { rule.key = QLatin1String("filename"); } else if (key == QLatin1String("imagecaption")) { rule.key = QLatin1String("comment"); } else if (key == QLatin1String("imagedate")) { rule.key = QLatin1String("creationdate"); } else if (key == QLatin1String("tag")) { rule.key = QLatin1String("tagid"); } else { // other field names did not change: // albumname, albumcaption, albumcollection, tagname, keyword, rating rule.key = key; } if (op == QLatin1String("eq")) { rule.op = SearchXml::Equal; } else if (op == QLatin1String("ne")) { rule.op = SearchXml::Unequal; } else if (op == QLatin1String("lt")) { rule.op = SearchXml::LessThan; } else if (op == QLatin1String("lte")) { rule.op = SearchXml::LessThanOrEqual; } else if (op == QLatin1String("gt")) { rule.op = SearchXml::GreaterThan; } else if (op == QLatin1String("gte")) { rule.op = SearchXml::GreaterThanOrEqual; } else if (op == QLatin1String("like")) { if (key == QLatin1String("tag")) { rule.op = SearchXml::InTree; } else { rule.op = SearchXml::Like; } } else if (op == QLatin1String("nlike")) { if (key == QLatin1String("tag")) { rule.op = SearchXml::NotInTree; } else { rule.op = SearchXml::NotLike; } } rule.val = QUrlQuery(url).queryItemValue(QString::number(i) + QLatin1String(".val")); rulesMap.insert(i, rule); } SearchXmlWriter writer; // set an attribute marking this search as converted from 0.9 style search writer.writeAttribute(QLatin1String("convertedFrom09Url"), QLatin1String("true")); writer.writeGroup(); QStringList strList = url.path().split(QLatin1Char(' '), QString::SkipEmptyParts); for ( QStringList::const_iterator it = strList.constBegin(); it != strList.constEnd(); ++it ) { bool ok; int num = (*it).toInt(&ok); if (ok) { RuleTypeForConversion rule = rulesMap[num]; writer.writeField(rule.key, rule.op); writer.writeValue(rule.val); writer.finishField(); } else { QString expr = (*it).trimmed(); if (expr == QLatin1String("AND")) { // add another field } else if (expr == QLatin1String("OR")) { // open a new group writer.finishGroup(); writer.writeGroup(); writer.setGroupOperator(SearchXml::Or); } else if (expr == QLatin1String("(")) { // open a subgroup writer.writeGroup(); } else if (expr == QLatin1String(")")) { writer.finishGroup(); } } } writer.finishGroup(); writer.finish(); return writer.xml(); } QString ItemQueryBuilder::buildQueryFromUrl(const QUrl& url, QList* boundValues) const { int count = QUrlQuery(url).queryItemValue(QLatin1String("count")).toInt(); if (count <= 0) { return QString(); } QMap rulesMap; for (int i = 1 ; i <= count ; ++i) { RuleType rule; QString key = QUrlQuery(url).queryItemValue(QString::number(i) + QLatin1String(".key")).toLower(); QString op = QUrlQuery(url).queryItemValue(QString::number(i) + QLatin1String(".op")).toLower(); if (key == QLatin1String("album")) { rule.key = ALBUM; } else if (key == QLatin1String("albumname")) { rule.key = ALBUMNAME; } else if (key == QLatin1String("albumcaption")) { rule.key = ALBUMCAPTION; } else if (key == QLatin1String("albumcollection")) { rule.key = ALBUMCOLLECTION; } else if (key == QLatin1String("imagename")) { rule.key = IMAGENAME; } else if (key == QLatin1String("imagecaption")) { rule.key = IMAGECAPTION; } else if (key == QLatin1String("imagedate")) { rule.key = IMAGEDATE; } else if (key == QLatin1String("tag")) { rule.key = TAG; } else if (key == QLatin1String("tagname")) { rule.key = TAGNAME; } else if (key == QLatin1String("keyword")) { rule.key = KEYWORD; } else if (key == QLatin1String("rating")) { rule.key = RATING; } else { qCWarning(DIGIKAM_DATABASE_LOG) << "Unknown rule type: " << key << " passed to kioslave"; continue; } if (op == QLatin1String("eq")) { rule.op = EQ; } else if (op == QLatin1String("ne")) { rule.op = NE; } else if (op == QLatin1String("lt")) { rule.op = LT; } else if (op == QLatin1String("lte")) { rule.op = LTE; } else if (op == QLatin1String("gt")) { rule.op = GT; } else if (op == QLatin1String("gte")) { rule.op = GTE; } else if (op == QLatin1String("like")) { rule.op = LIKE; } else if (op == QLatin1String("nlike")) { rule.op = NLIKE; } else { qCWarning(DIGIKAM_DATABASE_LOG) << "Unknown op type: " << op << " passed to dbjob"; continue; } rule.val = QUrlQuery(url).queryItemValue(QString::number(i) + QLatin1String(".val")); rulesMap.insert(i, rule); } QString sqlQuery; SubQueryBuilder subQuery; QStringList strList = url.path().split(QLatin1Char(' '), QString::SkipEmptyParts); - for ( QStringList::const_iterator it = strList.constBegin(); it != strList.constEnd(); ++it ) + for (QStringList::const_iterator it = strList.constBegin() ; it != strList.constEnd() ; ++it) { bool ok; int num = (*it).toInt(&ok); if (ok) { RuleType rule = rulesMap[num]; if (rule.key == KEYWORD) { bool exact; QString possDate = possibleDate(rule.val, exact); if (!possDate.isEmpty()) { rule.key = IMAGEDATE; rule.val = possDate; if (exact) { rule.op = EQ; } else { rule.op = LIKE; } sqlQuery += subQuery.build(rule.key, rule.op, rule.val, boundValues); } else { QList todo; todo.append( ALBUMNAME ); todo.append( IMAGENAME ); todo.append( TAGNAME ); todo.append( ALBUMCAPTION ); todo.append( ALBUMCOLLECTION ); todo.append( IMAGECAPTION ); todo.append( RATING ); sqlQuery += QLatin1Char('('); QList::const_iterator it = todo.constBegin(); - while ( it != todo.constEnd() ) + while (it != todo.constEnd()) { sqlQuery += subQuery.build(*it, rule.op, rule.val, boundValues); ++it; - if ( it != todo.constEnd() ) + if (it != todo.constEnd()) { sqlQuery += QLatin1String(" OR "); } } sqlQuery += QLatin1Char(')'); } } else { sqlQuery += subQuery.build(rule.key, rule.op, rule.val, boundValues); } } else { sqlQuery += QLatin1Char(' ') + *it + QLatin1Char(' '); } } return sqlQuery; } QString ItemQueryBuilder::possibleDate(const QString& str, bool& exact) const { QDate date = QDate::fromString(str, Qt::ISODate); if (date.isValid()) { exact = true; return date.toString(Qt::ISODate); } exact = false; bool ok; int num = str.toInt(&ok); if (ok) { // ok. its an int, does it look like a year? if (1970 <= num && num <= QDate::currentDate().year()) { // very sure its a year return QString::fromUtf8("%1-%-%").arg(num); } } else { // hmm... not a year. is it a particular month? for (int i = 1 ; i <= 12 ; ++i) { if (str.toLower() == m_shortMonths[i-1] || str.toLower() == m_longMonths[i-1]) { QString monGlob; monGlob.sprintf("%.2d", i); monGlob = QString::fromUtf8("%-") + monGlob + QString::fromUtf8("-%"); return monGlob; } } } return QString(); } } // namespace Digikam diff --git a/core/libs/database/models/itemsortsettings.cpp b/core/libs/database/models/itemsortsettings.cpp index ad911d8378..4342a8c1ab 100644 --- a/core/libs/database/models/itemsortsettings.cpp +++ b/core/libs/database/models/itemsortsettings.cpp @@ -1,427 +1,427 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2009-03-05 * Description : Filter values for use with ItemFilterModel * * Copyright (C) 2009 by Marcel Wiesweg * Copyright (C) 2014 by Mohamed_Anwer * * 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 "itemsortsettings.h" // Qt includes #include #include // Local includes #include "coredbfields.h" #include "iteminfo.h" namespace Digikam { ItemSortSettings::ItemSortSettings() { categorizationMode = NoCategories; categorizationSortOrder = DefaultOrder; categorizationCaseSensitivity = Qt::CaseSensitive; sortRole = SortByFileName; sortOrder = DefaultOrder; strTypeNatural = true; sortCaseSensitivity = Qt::CaseSensitive; currentCategorizationSortOrder = Qt::AscendingOrder; currentSortOrder = Qt::AscendingOrder; } bool ItemSortSettings::operator==(const ItemSortSettings& other) const { return categorizationMode == other.categorizationMode && categorizationSortOrder == other.categorizationSortOrder && categorizationCaseSensitivity == other.categorizationCaseSensitivity && sortRole == other.sortRole && sortOrder == other.sortOrder && sortCaseSensitivity == other.sortCaseSensitivity; } void ItemSortSettings::setCategorizationMode(CategorizationMode mode) { categorizationMode = mode; if (categorizationSortOrder == DefaultOrder) { currentCategorizationSortOrder = defaultSortOrderForCategorizationMode(categorizationMode); } } void ItemSortSettings::setCategorizationSortOrder(SortOrder order) { categorizationSortOrder = order; if (categorizationSortOrder == DefaultOrder) { currentCategorizationSortOrder = defaultSortOrderForCategorizationMode(categorizationMode); } else { currentCategorizationSortOrder = (Qt::SortOrder)categorizationSortOrder; } } void ItemSortSettings::setSortRole(SortRole role) { sortRole = role; if (sortOrder == DefaultOrder) { currentSortOrder = defaultSortOrderForSortRole(sortRole); } } void ItemSortSettings::setSortOrder(SortOrder order) { sortOrder = order; if (sortOrder == DefaultOrder) { currentSortOrder = defaultSortOrderForSortRole(sortRole); } else { currentSortOrder = (Qt::SortOrder)order; } } void ItemSortSettings::setStringTypeNatural(bool natural) { strTypeNatural = natural; } Qt::SortOrder ItemSortSettings::defaultSortOrderForCategorizationMode(CategorizationMode mode) { switch (mode) { case OneCategory: case NoCategories: case CategoryByAlbum: case CategoryByMonth: case CategoryByFormat: default: return Qt::AscendingOrder; } } Qt::SortOrder ItemSortSettings::defaultSortOrderForSortRole(SortRole role) { switch (role) { case SortByFilePath: case SortByFileName: case SortByManualOrder: case SortByCreationDate: case SortByModificationDate: return Qt::AscendingOrder; case SortByRating: case SortByFileSize: case SortByImageSize: case SortBySimilarity: case SortByAspectRatio: return Qt::DescendingOrder; default: return Qt::AscendingOrder; } } int ItemSortSettings::compareCategories(const ItemInfo& left, const ItemInfo& right) const { switch (categorizationMode) { case NoCategories: case OneCategory: return 0; case CategoryByAlbum: { int leftAlbum = left.albumId(); int rightAlbum = right.albumId(); // return comparison result if (leftAlbum == rightAlbum) { return 0; } else if (lessThanByOrder(leftAlbum, rightAlbum, currentCategorizationSortOrder)) { return -1; } else { return 1; } } case CategoryByFormat: { return naturalCompare(left.format(), right.format(), currentCategorizationSortOrder, categorizationCaseSensitivity, strTypeNatural); } case CategoryByMonth: { return compareByOrder(left.dateTime().date(), right.dateTime().date(), currentCategorizationSortOrder); } default: return 0; } } bool ItemSortSettings::lessThan(const ItemInfo& left, const ItemInfo& right) const { int result = compare(left, right, sortRole); if (result != 0) { return result < 0; } // are they identical? if (left == right) { return false; } // If left and right equal for first sort order, use a hierarchy of all sort orders - if ( (result = compare(left, right, SortByFileName)) != 0) + if ((result = compare(left, right, SortByFileName)) != 0) { return result < 0; } - if ( (result = compare(left, right, SortByCreationDate)) != 0) + if ((result = compare(left, right, SortByCreationDate)) != 0) { return result < 0; } - if ( (result = compare(left, right, SortByModificationDate)) != 0) + if ((result = compare(left, right, SortByModificationDate)) != 0) { return result < 0; } - if ( (result = compare(left, right, SortByFilePath)) != 0) + if ((result = compare(left, right, SortByFilePath)) != 0) { return result < 0; } - if ( (result = compare(left, right, SortByFileSize)) != 0) + if ((result = compare(left, right, SortByFileSize)) != 0) { return result < 0; } - if ( (result = compare(left, right, SortBySimilarity)) != 0) + if ((result = compare(left, right, SortBySimilarity)) != 0) { return result < 0; } - if ( (result = compare(left, right, SortByManualOrder)) != 0) + if ((result = compare(left, right, SortByManualOrder)) != 0) { return result < 0; } return false; } int ItemSortSettings::compare(const ItemInfo& left, const ItemInfo& right) const { return compare(left, right, sortRole); } int ItemSortSettings::compare(const ItemInfo& left, const ItemInfo& right, SortRole role) const { switch (role) { case SortByFileName: { return naturalCompare(left.name(), right.name(), currentSortOrder, sortCaseSensitivity, strTypeNatural); } case SortByFilePath: return naturalCompare(left.filePath(), right.filePath(), currentSortOrder, sortCaseSensitivity, strTypeNatural); case SortByFileSize: return compareByOrder(left.fileSize(), right.fileSize(), currentSortOrder); case SortByCreationDate: return compareByOrder(left.dateTime(), right.dateTime(), currentSortOrder); case SortByModificationDate: return compareByOrder(left.modDateTime(), right.modDateTime(), currentSortOrder); case SortByRating: // I have the feeling that inverting the sort order for rating is the natural order return - compareByOrder(left.rating(), right.rating(), currentSortOrder); case SortByImageSize: { QSize leftSize = left.dimensions(); QSize rightSize = right.dimensions(); int leftPixels = leftSize.width() * leftSize.height(); int rightPixels = rightSize.width() * rightSize.height(); return compareByOrder(leftPixels, rightPixels, currentSortOrder); } case SortByAspectRatio: { QSize leftSize = left.dimensions(); QSize rightSize = right.dimensions(); int leftAR = (double(leftSize.width()) / double(leftSize.height())) * 1000000; int rightAR = (double(rightSize.width()) / double(rightSize.height())) * 1000000; return compareByOrder(leftAR, rightAR, currentSortOrder); } case SortBySimilarity: { qlonglong leftReferenceImageId = left.currentReferenceImage(); qlonglong rightReferenceImageId = right.currentReferenceImage(); // make sure that the original image has always the highest similarity. double leftSimilarity = left.id() == leftReferenceImageId ? 1.1 : left.currentSimilarity(); double rightSimilarity = right.id() == rightReferenceImageId ? 1.1 : right.currentSimilarity(); return compareByOrder(leftSimilarity, rightSimilarity, currentSortOrder); } case SortByManualOrder: return compareByOrder(left.manualOrder(), right.manualOrder(), currentSortOrder); default: return 1; } } bool ItemSortSettings::lessThan(const QVariant& left, const QVariant& right) const { if (left.type() != right.type()) { return false; } switch (left.type()) { case QVariant::Int: return compareByOrder(left.toInt(), right.toInt(), currentSortOrder); case QVariant::UInt: return compareByOrder(left.toUInt(), right.toUInt(), currentSortOrder); case QVariant::LongLong: return compareByOrder(left.toLongLong(), right.toLongLong(), currentSortOrder); case QVariant::ULongLong: return compareByOrder(left.toULongLong(), right.toULongLong(), currentSortOrder); case QVariant::Double: return compareByOrder(left.toDouble(), right.toDouble(), currentSortOrder); case QVariant::Date: return compareByOrder(left.toDate(), right.toDate(), currentSortOrder); case QVariant::DateTime: return compareByOrder(left.toDateTime(), right.toDateTime(), currentSortOrder); case QVariant::Time: return compareByOrder(left.toTime(), right.toTime(), currentSortOrder); case QVariant::Rect: case QVariant::RectF: { QRectF rectLeft = left.toRectF(); QRectF rectRight = right.toRectF(); int result; if ((result = compareByOrder(rectLeft.top(), rectRight.top(), currentSortOrder)) != 0) { return result < 0; } if ((result = compareByOrder(rectLeft.left(), rectRight.left(), currentSortOrder)) != 0) { return result < 0; } QSizeF sizeLeft = rectLeft.size(); QSizeF sizeRight = rectRight.size(); if ((result = compareByOrder(sizeLeft.width()*sizeLeft.height(), sizeRight.width()*sizeRight.height(), currentSortOrder)) != 0) { return result < 0; } #if __GNUC__ >= 7 // krazy:exclude=cpp [[fallthrough]]; #endif } default: { return naturalCompare(left.toString(), right.toString(), currentSortOrder, sortCaseSensitivity, strTypeNatural); } } } DatabaseFields::Set ItemSortSettings::watchFlags() const { DatabaseFields::Set set; switch (sortRole) { case SortByFileName: set |= DatabaseFields::Name; break; case SortByFilePath: set |= DatabaseFields::Name; break; case SortByFileSize: set |= DatabaseFields::FileSize; break; case SortByModificationDate: set |= DatabaseFields::ModificationDate; break; case SortByCreationDate: set |= DatabaseFields::CreationDate; break; case SortByRating: set |= DatabaseFields::Rating; break; case SortByImageSize: set |= DatabaseFields::Width | DatabaseFields::Height; break; case SortByAspectRatio: set |= DatabaseFields::Width | DatabaseFields::Height; break; case SortBySimilarity: // TODO: Not sure what to do here.... set |= DatabaseFields::Name; break; case SortByManualOrder: set |= DatabaseFields::ManualOrder; break; } switch (categorizationMode) { case OneCategory: case NoCategories: break; case CategoryByAlbum: set |= DatabaseFields::Album; break; case CategoryByFormat: set |= DatabaseFields::Format; break; case CategoryByMonth: set |= DatabaseFields::CreationDate; break; } return set; } } // namespace Digikam diff --git a/core/libs/database/similaritydb/similaritydbschemaupdater.cpp b/core/libs/database/similaritydb/similaritydbschemaupdater.cpp index de70d34f7b..6d85906b5c 100644 --- a/core/libs/database/similaritydb/similaritydbschemaupdater.cpp +++ b/core/libs/database/similaritydb/similaritydbschemaupdater.cpp @@ -1,279 +1,279 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2017-07-01 * Description : Similarity DB schema update * * Copyright (C) 2007-2009 by Marcel Wiesweg * Copyright (C) 2010-2017 by Gilles Caulier * Copyright (C) 2017 by Swati Lodha * Copyright (C) 2018 by Mario Frank * * 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 "similaritydbschemaupdater.h" // Qt includes #include #include #include // KDE includes #include // Local includes #include "digikam_debug.h" #include "collectionscannerobserver.h" #include "dbenginebackend.h" #include "similaritydbaccess.h" #include "similaritydb.h" namespace Digikam { int SimilarityDbSchemaUpdater::schemaVersion() { return 1; } // ------------------------------------------------------------------------------------- class Q_DECL_HIDDEN SimilarityDbSchemaUpdater::Private { public: explicit Private() : setError(false), currentVersion(0), currentRequiredVersion(0), dbAccess(0), observer(0) { } public: bool setError; int currentVersion; int currentRequiredVersion; SimilarityDbAccess* dbAccess; InitializationObserver* observer; }; SimilarityDbSchemaUpdater::SimilarityDbSchemaUpdater(SimilarityDbAccess* const dbAccess) : d(new Private) { d->dbAccess = dbAccess; } SimilarityDbSchemaUpdater::~SimilarityDbSchemaUpdater() { delete d; } bool SimilarityDbSchemaUpdater::update() { bool success = startUpdates(); // even on failure, try to set current version - it may have incremented if (d->currentVersion) { d->dbAccess->db()->setSetting(QLatin1String("DBSimilarityVersion"), QString::number(d->currentVersion)); } if (d->currentRequiredVersion) { d->dbAccess->db()->setSetting(QLatin1String("DBSimilarityVersionRequired"), QString::number(d->currentRequiredVersion)); } return success; } void SimilarityDbSchemaUpdater::setObserver(InitializationObserver* const observer) { d->observer = observer; } bool SimilarityDbSchemaUpdater::startUpdates() { // First step: do we have an empty database? QStringList tables = d->dbAccess->backend()->tables(); if (tables.contains(QLatin1String("SimilaritySettings"), Qt::CaseInsensitive)) { // Find out schema version of db file QString version = d->dbAccess->db()->getSetting(QLatin1String("DBSimilarityVersion")); QString versionRequired = d->dbAccess->db()->getSetting(QLatin1String("DBSimilarityVersionRequired")); qCDebug(DIGIKAM_SIMILARITYDB_LOG) << "Similarity database: have a structure version " << version; // mini schema update if (version.isEmpty() && d->dbAccess->parameters().isSQLite()) { version = d->dbAccess->db()->getSetting(QLatin1String("DBVersion")); } if (version.isEmpty() && d->dbAccess->parameters().isMySQL()) { version = d->dbAccess->db()->getLegacySetting(QLatin1String("DBSimilarityVersion")); versionRequired = d->dbAccess->db()->getLegacySetting(QLatin1String("DBSimilarityVersionRequired")); } // We absolutely require the DBSimilarityVersion setting if (version.isEmpty()) { // Something is damaged. Give up. qCCritical(DIGIKAM_SIMILARITYDB_LOG) << "Similarity database: database version not available! Giving up schema upgrading."; QString errorMsg = i18n("The database is not valid: " "the \"DBSimilarityVersion\" setting does not exist. " "The current database schema version cannot be verified. " "Try to start with an empty database. "); d->dbAccess->setLastError(errorMsg); if (d->observer) { d->observer->error(errorMsg); d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); } return false; } // current version describes the current state of the schema in the db, // schemaVersion is the version required by the program. d->currentVersion = version.toInt(); if (d->currentVersion > schemaVersion()) { // trying to open a database with a more advanced than this SimilarityDbSchemaUpdater supports if (!versionRequired.isEmpty() && versionRequired.toInt() <= schemaVersion()) { // version required may be less than current version return true; } else { QString errorMsg = i18n("The database has been used with a more recent version of digiKam " "and has been updated to a database schema which cannot be used with this version. " "(This means this digiKam version is too old, or the database format is to recent) " "Please use the more recent version of digikam that you used before. "); d->dbAccess->setLastError(errorMsg); if (d->observer) { d->observer->error(errorMsg); d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); } return false; } } else { return makeUpdates(); } } else { qCDebug(DIGIKAM_SIMILARITYDB_LOG) << "Similarity database: no database file available"; DbEngineParameters parameters = d->dbAccess->parameters(); // No legacy handling: start with a fresh db if (!createDatabase()) { QString errorMsg = i18n("Failed to create tables in database.\n ") + d->dbAccess->backend()->lastError(); d->dbAccess->setLastError(errorMsg); if (d->observer) { d->observer->error(errorMsg); d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); } return false; } return true; } } bool SimilarityDbSchemaUpdater::createDatabase() { - if ( createTables() && - createIndices() && - createTriggers()) + if (createTables() && + createIndices() && + createTriggers()) { d->currentVersion = schemaVersion(); d->currentRequiredVersion = 1; return true; } else { return false; } } bool SimilarityDbSchemaUpdater::createTables() { return d->dbAccess->backend()->execDBAction(d->dbAccess->backend()->getDBAction(QLatin1String("CreateSimilarityDB"))); } bool SimilarityDbSchemaUpdater::createIndices() { return d->dbAccess->backend()->execDBAction(d->dbAccess->backend()->getDBAction(QLatin1String("CreateSimilarityDBIndices"))); } bool SimilarityDbSchemaUpdater::createTriggers() { return d->dbAccess->backend()->execDBAction(d->dbAccess->backend()->getDBAction(QLatin1String("CreateSimilarityDBTriggers"))); } bool SimilarityDbSchemaUpdater::makeUpdates() { if (d->currentVersion < schemaVersion()) { if (d->currentVersion == 1) { updateV1ToV2(); } } return true; } bool SimilarityDbSchemaUpdater::updateV1ToV2() { // Do nothing for now. return true; } } // namespace Digikam diff --git a/core/libs/database/tags/facetags.cpp b/core/libs/database/tags/facetags.cpp index 653d95f21c..2ea1c21aac 100644 --- a/core/libs/database/tags/facetags.cpp +++ b/core/libs/database/tags/facetags.cpp @@ -1,450 +1,450 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2011-08-08 * Description : Accessing face tags * * Copyright (C) 2010-2011 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 "facetags.h" // KDE includes #include // Local includes #include "digikam_debug.h" #include "coredbconstants.h" #include "tagscache.h" #include "tagregion.h" #include "tagproperties.h" namespace Digikam { // --- FaceIfacePriv ---------------------------------------------------------------------------------------- class Q_DECL_HIDDEN FaceTagsHelper { public: static QString tagPath(const QString& name, int parentId); static void makeFaceTag(int tagId, const QString& fullName); static int findFirstTagWithProperty(const QString& property, const QString& value = QString()); static int tagForName(const QString& name, int tagId, int parentId, const QString& givenFullName, bool convert, bool create); }; // --- Private methods --- int FaceTagsHelper::findFirstTagWithProperty(const QString& property, const QString& value) { QList candidates = TagsCache::instance()->tagsWithProperty(property, value); if (!candidates.isEmpty()) { return candidates.first(); } return 0; } QString FaceTagsHelper::tagPath(const QString& name, int parentId) { QString faceParentTagName = TagsCache::instance()->tagName(parentId); if ((faceParentTagName).contains(QRegExp(QLatin1String("(_Digikam_root_tag_/|/_Digikam_root_tag_|_Digikam_root_tag_)")))) { return QLatin1Char('/') + name; } else { return faceParentTagName + QLatin1Char('/') + name; } } void FaceTagsHelper::makeFaceTag(int tagId, const QString& fullName) { QString faceEngineName = fullName; /* * // find a unique FacesEngineId * for (int i=0; d->findFirstTagWithProperty(TagPropertyName::FacesEngineId(), FacesEngineId); ++i) * FacesEngineId = fullName + QString::fromUtf8(" (%1)").arg(i); */ TagProperties props(tagId); props.setProperty(TagPropertyName::person(), fullName); props.setProperty(TagPropertyName::faceEngineName(), faceEngineName); } int FaceTagsHelper::tagForName(const QString& name, int tagId, int parentId, const QString& givenFullName, bool convert, bool create) { if (name.isEmpty() && givenFullName.isEmpty() && !tagId) { return FaceTags::unknownPersonTagId(); } QString fullName = givenFullName.isNull() ? name : givenFullName; if (tagId) { if (FaceTags::isPerson(tagId)) { //qCDebug(DIGIKAM_DATABASE_LOG) << "Proposed tag is already a person"; return tagId; } else if (convert) { if (fullName.isNull()) { fullName = TagsCache::instance()->tagName(tagId); } qCDebug(DIGIKAM_DATABASE_LOG) << "Converting proposed tag to person, full name" << fullName; makeFaceTag(tagId, fullName); return tagId; } return 0; } // First attempt: Find by full name in "person" attribute QList candidates = TagsCache::instance()->tagsWithProperty(TagPropertyName::person(), fullName); foreach(int id, candidates) { qCDebug(DIGIKAM_DATABASE_LOG) << "Candidate with set full name:" << id << fullName; if (parentId == -1) { return id; } else if (TagsCache::instance()->parentTag(id) == parentId) { return id; } } // Second attempt: Find by tag name if (parentId == -1) { candidates = TagsCache::instance()->tagsForName(name); } else { tagId = TagsCache::instance()->tagForName(name, parentId); candidates.clear(); if (tagId) { candidates << tagId; } } foreach(int id, candidates) { // Is this tag already a person tag? if (FaceTags::isPerson(id)) { qCDebug(DIGIKAM_DATABASE_LOG) << "Found tag with name" << name << "is already a person." << id; return id; } else if (convert) { qCDebug(DIGIKAM_DATABASE_LOG) << "Converting tag with name" << name << "to a person." << id; makeFaceTag(id, fullName); return id; } } // Third: If desired, create a new tag if (create) { qCDebug(DIGIKAM_DATABASE_LOG) << "Creating new tag for name" << name << "fullName" << fullName; if (parentId == -1) { parentId = FaceTags::personParentTag(); } tagId = TagsCache::instance()->getOrCreateTag(tagPath(name, parentId)); makeFaceTag(tagId, fullName); return tagId; } return 0; } // --- public methods --- QList FaceTags::allPersonNames() { return TagsCache::instance()->tagNames(allPersonTags()); } QList FaceTags::allPersonPaths() { return TagsCache::instance()->tagPaths(allPersonTags()); } int FaceTags::tagForPerson(const QString& name, int parentId, const QString& fullName) { return FaceTagsHelper::tagForName(name, 0, parentId, fullName, false, false); } int FaceTags::getOrCreateTagForPerson(const QString& name, int parentId, const QString& fullName) { return FaceTagsHelper::tagForName(name, 0, parentId, fullName, true, true); } void FaceTags::ensureIsPerson(int tagId, const QString& fullName) { FaceTagsHelper::tagForName(QString(), tagId, 0, fullName, true, false); } bool FaceTags::isPerson(int tagId) { return TagsCache::instance()->hasProperty(tagId, TagPropertyName::person()); } bool FaceTags::isTheUnknownPerson(int tagId) { return TagsCache::instance()->hasProperty(tagId, TagPropertyName::unknownPerson()); } bool FaceTags::isTheUnconfirmedPerson(int tagId) { return TagsCache::instance()->hasProperty(tagId, TagPropertyName::unconfirmedPerson()); } QList FaceTags::allPersonTags() { return TagsCache::instance()->tagsWithProperty(TagPropertyName::person()); } int FaceTags::scannedForFacesTagId() { return TagsCache::instance()->getOrCreateInternalTag(InternalTagName::scannedForFaces()); // no i18n } QMap FaceTags::identityAttributes(int tagId) { QMap attributes; QString uuid = TagsCache::instance()->propertyValue(tagId, TagPropertyName::faceEngineUuid()); if (!uuid.isEmpty()) { attributes[QLatin1String("uuid")] = uuid; } QString fullName = TagsCache::instance()->propertyValue(tagId, TagPropertyName::person()); if (!fullName.isEmpty()) { attributes[QLatin1String("fullName")] = fullName; } QString faceEngineName = TagsCache::instance()->propertyValue(tagId, TagPropertyName::person()); QString tagName = TagsCache::instance()->tagName(tagId); if (tagName != faceEngineName) { attributes.insertMulti(QLatin1String("name"), faceEngineName); attributes.insertMulti(QLatin1String("name"), tagName); } else { attributes[QLatin1String("name")] = tagName; } return attributes; } void FaceTags::applyTagIdentityMapping(int tagId, const QMap& attributes) { TagProperties props(tagId); if (attributes.contains(QLatin1String("fullName"))) { props.setProperty(TagPropertyName::person(), attributes.value(QLatin1String("fullName"))); } // we do not change the digikam tag name at this point, but we have this extra tag property if (attributes.contains(QLatin1String("name"))) { props.setProperty(TagPropertyName::faceEngineName(), attributes.value(QLatin1String("name"))); } props.setProperty(TagPropertyName::faceEngineUuid(), attributes.value(QLatin1String("uuid"))); } int FaceTags::getOrCreateTagForIdentity(const QMap& attributes) { // Attributes from FacesEngine's Identity object. // The text constants are defines in FacesEngine's API docs if (attributes.isEmpty()) { return FaceTags::unknownPersonTagId(); } int tagId; // First, look for UUID if (!attributes.value(QLatin1String("uuid")).isEmpty()) { - if ( (tagId = FaceTagsHelper::findFirstTagWithProperty(TagPropertyName::faceEngineUuid(), attributes.value(QLatin1String("uuid")))) ) + if ((tagId = FaceTagsHelper::findFirstTagWithProperty(TagPropertyName::faceEngineUuid(), attributes.value(QLatin1String("uuid"))))) { return tagId; } } // Second, look for full name if (!attributes.value(QLatin1String("fullName")).isEmpty()) { - if ( (tagId = FaceTagsHelper::findFirstTagWithProperty(TagPropertyName::person(), attributes.value(QLatin1String("fullName")))) ) + if ((tagId = FaceTagsHelper::findFirstTagWithProperty(TagPropertyName::person(), attributes.value(QLatin1String("fullName"))))) { return tagId; } } // Third, look for either name or full name // TODO: better support for "fullName" QString name = attributes.value(QLatin1String("name")); if (name.isEmpty()) { name = attributes.value(QLatin1String("fullName")); } if (name.isEmpty()) { return FaceTags::unknownPersonTagId(); } - if ( (tagId = FaceTagsHelper::findFirstTagWithProperty(TagPropertyName::faceEngineName(), name)) ) + if ((tagId = FaceTagsHelper::findFirstTagWithProperty(TagPropertyName::faceEngineName(), name))) { return tagId; } - if ( (tagId = FaceTagsHelper::findFirstTagWithProperty(TagPropertyName::person(), name)) ) + if ((tagId = FaceTagsHelper::findFirstTagWithProperty(TagPropertyName::person(), name))) { return tagId; } // identity is in FacesEngine's database, but not in ours, so create. tagId = FaceTagsHelper::tagForName(name, 0, -1, attributes.value(QLatin1String("fullName")), true, true); applyTagIdentityMapping(tagId, attributes); return tagId; } QString FaceTags::faceNameForTag(int tagId) { if (!TagsCache::instance()->hasTag(tagId)) { return QString(); } QString id = TagsCache::instance()->propertyValue(tagId, TagPropertyName::person()); if (id.isNull()) { id = TagsCache::instance()->tagName(tagId); } return id; } int FaceTags::personParentTag() { // check default QString i18nName = i18nc("People on your photos", "People"); int tagId = TagsCache::instance()->tagForPath(i18nName); if (tagId) { return tagId; } // employ a heuristic QList personTags = allPersonTags(); if (!personTags.isEmpty()) { // we find the most toplevel parent tag of a person tag QMultiMap tiers; foreach(int tagId, personTags) { tiers.insert(TagsCache::instance()->parentTags(tagId).size(), tagId); } QList mosttoplevelTags = tiers.values(tiers.begin().key()); // as a pretty weak criterion, take the largest id which usually corresponds to the latest tag creation. std::sort(mosttoplevelTags.begin(), mosttoplevelTags.end()); return TagsCache::instance()->parentTag(mosttoplevelTags.last()); } // create default return TagsCache::instance()->getOrCreateTag(i18nName); } int FaceTags::unknownPersonTagId() { QList ids = TagsCache::instance()->tagsWithPropertyCached(TagPropertyName::unknownPerson()); if (!ids.isEmpty()) { return ids.first(); } int unknownPersonTagId = TagsCache::instance()->getOrCreateTag( FaceTagsHelper::tagPath( i18nc("The list of detected faces from the collections but not recognized", "Unknown"), personParentTag())); TagProperties props(unknownPersonTagId); props.setProperty(TagPropertyName::person(), QString()); // no name associated props.setProperty(TagPropertyName::unknownPerson(), QString()); // special property return unknownPersonTagId; } int FaceTags::unconfirmedPersonTagId() { QList ids = TagsCache::instance()->tagsWithPropertyCached(TagPropertyName::unconfirmedPerson()); if (!ids.isEmpty()) { return ids.first(); } int unknownPersonTagId = TagsCache::instance()->getOrCreateTag( FaceTagsHelper::tagPath( i18nc("The list of recognized faces from the collections but not confirmed", "Unconfirmed"), personParentTag())); TagProperties props(unknownPersonTagId); props.setProperty(TagPropertyName::person(), QString()); // no name associated props.setProperty(TagPropertyName::unconfirmedPerson(), QString()); // special property return unknownPersonTagId; } } // Namespace Digikam diff --git a/core/libs/database/thumbsdb/thumbsdbschemaupdater.cpp b/core/libs/database/thumbsdb/thumbsdbschemaupdater.cpp index 7bb890b8f9..44d81a9ed5 100644 --- a/core/libs/database/thumbsdb/thumbsdbschemaupdater.cpp +++ b/core/libs/database/thumbsdb/thumbsdbschemaupdater.cpp @@ -1,300 +1,300 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2007-04-16 * Description : Schema update * * Copyright (C) 2007-2009 by Marcel Wiesweg * Copyright (C) 2010-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 "thumbsdbschemaupdater.h" // Qt includes #include #include #include // KDE includes #include // Local includes #include "digikam_debug.h" #include "collectionscannerobserver.h" #include "dbenginebackend.h" #include "coredbtransaction.h" #include "thumbsdbaccess.h" #include "thumbsdb.h" namespace Digikam { int ThumbsDbSchemaUpdater::schemaVersion() { return 3; } // ------------------------------------------------------------------------------------- class Q_DECL_HIDDEN ThumbsDbSchemaUpdater::Private { public: explicit Private() : setError(false), currentVersion(0), currentRequiredVersion(0), dbAccess(0), observer(0) { } public: bool setError; int currentVersion; int currentRequiredVersion; ThumbsDbAccess* dbAccess; InitializationObserver* observer; }; ThumbsDbSchemaUpdater::ThumbsDbSchemaUpdater(ThumbsDbAccess* const dbAccess) : d(new Private) { d->dbAccess = dbAccess; } ThumbsDbSchemaUpdater::~ThumbsDbSchemaUpdater() { delete d; } bool ThumbsDbSchemaUpdater::update() { bool success = startUpdates(); // even on failure, try to set current version - it may have incremented if (d->currentVersion) { d->dbAccess->db()->setSetting(QLatin1String("DBThumbnailsVersion"), QString::number(d->currentVersion)); } if (d->currentRequiredVersion) { d->dbAccess->db()->setSetting(QLatin1String("DBThumbnailsVersionRequired"), QString::number(d->currentRequiredVersion)); } return success; } void ThumbsDbSchemaUpdater::setObserver(InitializationObserver* const observer) { d->observer = observer; } bool ThumbsDbSchemaUpdater::startUpdates() { // First step: do we have an empty database? QStringList tables = d->dbAccess->backend()->tables(); if (tables.contains(QLatin1String("Thumbnails"), Qt::CaseInsensitive)) { // Find out schema version of db file QString version = d->dbAccess->db()->getSetting(QLatin1String("DBThumbnailsVersion")); QString versionRequired = d->dbAccess->db()->getSetting(QLatin1String("DBThumbnailsVersionRequired")); qCDebug(DIGIKAM_THUMBSDB_LOG) << "Thumbs database: have a structure version " << version; // mini schema update if (version.isEmpty() && d->dbAccess->parameters().isSQLite()) { version = d->dbAccess->db()->getSetting(QLatin1String("DBVersion")); } if (version.isEmpty() && d->dbAccess->parameters().isMySQL()) { version = d->dbAccess->db()->getLegacySetting(QLatin1String("DBThumbnailsVersion")); versionRequired = d->dbAccess->db()->getLegacySetting(QLatin1String("DBThumbnailsVersionRequired")); } // We absolutely require the DBThumbnailsVersion setting if (version.isEmpty()) { // Something is damaged. Give up. qCCritical(DIGIKAM_THUMBSDB_LOG) << "Thumbs database: database version not available! Giving up schema upgrading."; QString errorMsg = i18n("The database is not valid: " "the \"DBThumbnailsVersion\" setting does not exist. " "The current database schema version cannot be verified. " "Try to start with an empty database. "); d->dbAccess->setLastError(errorMsg); if (d->observer) { d->observer->error(errorMsg); d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); } return false; } // current version describes the current state of the schema in the db, // schemaVersion is the version required by the program. d->currentVersion = version.toInt(); if (d->currentVersion > schemaVersion()) { // trying to open a database with a more advanced than this ThumbsDbSchemaUpdater supports if (!versionRequired.isEmpty() && versionRequired.toInt() <= schemaVersion()) { // version required may be less than current version return true; } else { QString errorMsg = i18n("The database has been used with a more recent version of digiKam " "and has been updated to a database schema which cannot be used with this version. " "(This means this digiKam version is too old, or the database format is to recent) " "Please use the more recent version of digikam that you used before. "); d->dbAccess->setLastError(errorMsg); if (d->observer) { d->observer->error(errorMsg); d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); } return false; } } else { return makeUpdates(); } } else { qCDebug(DIGIKAM_THUMBSDB_LOG) << "Thumbs database: no database file available"; DbEngineParameters parameters = d->dbAccess->parameters(); // No legacy handling: start with a fresh db if (!createDatabase()) { QString errorMsg = i18n("Failed to create tables in database.\n ") + d->dbAccess->backend()->lastError(); d->dbAccess->setLastError(errorMsg); if (d->observer) { d->observer->error(errorMsg); d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); } return false; } return true; } } bool ThumbsDbSchemaUpdater::makeUpdates() { if (d->currentVersion < schemaVersion()) { if (d->currentVersion == 1) { updateV1ToV2(); } if (d->currentVersion <= 2) { updateV2ToV3(); } } return true; } bool ThumbsDbSchemaUpdater::createDatabase() { - if ( createTables() && - createIndices() && - createTriggers()) + if (createTables() && + createIndices() && + createTriggers()) { d->currentVersion = schemaVersion(); d->currentRequiredVersion = 1; return true; } else { return false; } } bool ThumbsDbSchemaUpdater::createTables() { return d->dbAccess->backend()->execDBAction(d->dbAccess->backend()->getDBAction(QLatin1String("CreateThumbnailsDB"))); } bool ThumbsDbSchemaUpdater::createIndices() { return d->dbAccess->backend()->execDBAction(d->dbAccess->backend()->getDBAction(QLatin1String("CreateThumbnailsDBIndices"))); } bool ThumbsDbSchemaUpdater::createTriggers() { return d->dbAccess->backend()->execDBAction(d->dbAccess->backend()->getDBAction(QLatin1String("CreateThumbnailsDBTrigger"))); } bool ThumbsDbSchemaUpdater::updateV1ToV2() { if (!d->dbAccess->backend()->execDBAction(d->dbAccess->backend()->getDBAction(QLatin1String("UpdateThumbnailsDBSchemaFromV1ToV2")))) { qCDebug(DIGIKAM_THUMBSDB_LOG) << "Thumbs database: schema upgrade from V1 to V2 failed!"; return false; } d->currentVersion = 2; d->currentRequiredVersion = 1; return true; } bool ThumbsDbSchemaUpdater::updateV2ToV3() { if (!d->dbAccess->backend()->execDBAction(d->dbAccess->backend()->getDBAction(QLatin1String("UpdateThumbnailsDBSchemaFromV2ToV3")))) { qCDebug(DIGIKAM_THUMBSDB_LOG) << "Thumbs database: schema upgrade from V2 to V3 failed!"; return false; } d->currentVersion = 3; d->currentRequiredVersion = 1; return true; } } // namespace Digikam diff --git a/core/libs/dialogs/deletedialog.cpp b/core/libs/dialogs/deletedialog.cpp index 797f20648b..c94c152754 100644 --- a/core/libs/dialogs/deletedialog.cpp +++ b/core/libs/dialogs/deletedialog.cpp @@ -1,679 +1,679 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2005-05-07 * Description : a dialog to delete item. * * Copyright (C) 2004 by Michael Pyne * Copyright (C) 2006 by Ian Monroe * Copyright (C) 2009 by Andi Clemens * Copyright (C) 2006-2011 by Marcel Wiesweg * Copyright (C) 2008-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 "deletedialog.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include // Local includes #include "digikam_debug.h" #include "applicationsettings.h" #include "coredburl.h" namespace Digikam { class Q_DECL_HIDDEN DeleteItem::Private { public: explicit Private() { hasThumb = false; } bool hasThumb; QUrl url; }; DeleteItem::DeleteItem(QTreeWidget* const parent, const QUrl& url) : QTreeWidgetItem(parent), d(new Private) { d->url = url; if (d->url.scheme() == QLatin1String("digikamalbums")) { if (CoreDbUrl(d->url).isAlbumUrl()) { setThumb(QIcon::fromTheme(QLatin1String("folder")).pixmap(parent->iconSize().width())); } else { setThumb(QIcon::fromTheme(QLatin1String("tag")).pixmap(parent->iconSize().width())); } } else { setThumb(QIcon::fromTheme(QLatin1String("view-preview")).pixmap(parent->iconSize().width(), QIcon::Disabled), false); } setText(1, fileUrl()); } DeleteItem::~DeleteItem() { delete d; } bool DeleteItem::hasValidThumbnail() const { return d->hasThumb; } QUrl DeleteItem::url() const { return d->url; } QString DeleteItem::fileUrl() const { if (d->url.isLocalFile()) { return (d->url.toLocalFile()); } else if (d->url.scheme() == QLatin1String("digikamalbums")) { return (CoreDbUrl(d->url).fileUrl().toLocalFile()); } return (d->url.toDisplayString()); } void DeleteItem::setThumb(const QPixmap& pix, bool hasThumb) { int iconSize = treeWidget()->iconSize().width(); QPixmap pixmap(iconSize+2, iconSize+2); pixmap.fill(Qt::transparent); QPainter p(&pixmap); p.drawPixmap((pixmap.width()/2) - (pix.width()/2), (pixmap.height()/2) - (pix.height()/2), pix); QIcon icon = QIcon(pixmap); // We make sure the preview icon stays the same regardless of the role icon.addPixmap(pixmap, QIcon::Selected, QIcon::On); icon.addPixmap(pixmap, QIcon::Selected, QIcon::Off); icon.addPixmap(pixmap, QIcon::Active, QIcon::On); icon.addPixmap(pixmap, QIcon::Active, QIcon::Off); icon.addPixmap(pixmap, QIcon::Normal, QIcon::On); icon.addPixmap(pixmap, QIcon::Normal, QIcon::Off); setIcon(0, icon); d->hasThumb = hasThumb; } //---------------------------------------------------------------------------- class Q_DECL_HIDDEN DeleteItemList::Private { public: explicit Private() : iconSize(64) { thumbLoadThread = 0; } const int iconSize; ThumbnailLoadThread* thumbLoadThread; }; DeleteItemList::DeleteItemList(QWidget* const parent) : QTreeWidget(parent), d(new Private) { d->thumbLoadThread = ThumbnailLoadThread::defaultThread(); setRootIsDecorated(false); setSelectionMode(QAbstractItemView::SingleSelection); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); setAllColumnsShowFocus(true); setIconSize(QSize(d->iconSize, d->iconSize)); setColumnCount(2); setHeaderLabels(QStringList() << i18n("Thumb") << i18n("Path")); header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); header()->setSectionResizeMode(1, QHeaderView::Stretch); setToolTip(i18n("List of items that are about to be deleted.")); setWhatsThis(i18n("This is the list of items that are about to be deleted.")); connect(d->thumbLoadThread, SIGNAL(signalThumbnailLoaded(LoadingDescription,QPixmap)), this, SLOT(slotThumbnailLoaded(LoadingDescription,QPixmap))); } DeleteItemList::~DeleteItemList() { delete d; } void DeleteItemList::slotThumbnailLoaded(const LoadingDescription& desc, const QPixmap& pix) { QTreeWidgetItemIterator it(this); while (*it) { DeleteItem* const item = dynamic_cast(*it); if (item && item->fileUrl() == desc.filePath) { if (!pix.isNull()) { item->setThumb(pix.scaled(d->iconSize, d->iconSize, Qt::KeepAspectRatio)); } return; } ++it; } } void DeleteItemList::drawRow(QPainter* p, const QStyleOptionViewItem& opt, const QModelIndex& index) const { DeleteItem* const item = dynamic_cast(itemFromIndex(index)); if (item && !item->hasValidThumbnail()) { d->thumbLoadThread->find(ThumbnailIdentifier(item->fileUrl())); } QTreeWidget::drawRow(p, opt, index); } //---------------------------------------------------------------------------- class Q_DECL_HIDDEN DeleteWidget::Private { public: explicit Private() { checkBoxStack = 0; warningIcon = 0; deleteText = 0; numFiles = 0; shouldDelete = 0; doNotShowAgain = 0; fileList = 0; listMode = DeleteDialogMode::Files; deleteMode = DeleteDialogMode::UseTrash; } QStackedWidget* checkBoxStack; QLabel* warningIcon; QLabel* deleteText; QLabel* numFiles; QCheckBox* shouldDelete; QCheckBox* doNotShowAgain; QTreeWidget* fileList; DeleteDialogMode::ListMode listMode; DeleteDialogMode::DeleteMode deleteMode; }; DeleteWidget::DeleteWidget(QWidget* const parent) : QWidget(parent), d(new Private) { setObjectName(QLatin1String("DeleteDialogBase")); resize(540, 370); setMinimumSize(QSize(420, 320)); const int spacing = QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); d->checkBoxStack = new QStackedWidget(this); QLabel* logo = new QLabel(this); logo->setPixmap(QIcon::fromTheme(QLatin1String("digikam")).pixmap(QSize(48,48))); d->warningIcon = new QLabel(this); d->warningIcon->setWordWrap(false); QSizePolicy sizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); sizePolicy.setHeightForWidth(d->warningIcon->sizePolicy().hasHeightForWidth()); sizePolicy.setHorizontalStretch(0); sizePolicy.setVerticalStretch(0); d->warningIcon->setSizePolicy(sizePolicy); d->deleteText = new QLabel(this); d->deleteText->setAlignment(Qt::AlignCenter); d->deleteText->setWordWrap(true); QHBoxLayout* hbox = new QHBoxLayout(); hbox->setSpacing(spacing); hbox->setContentsMargins(QMargins()); hbox->addWidget(logo); hbox->addWidget(d->deleteText, 10); hbox->addWidget(d->warningIcon); d->fileList = new DeleteItemList(this); d->numFiles = new QLabel(this); d->numFiles->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); d->numFiles->setWordWrap(false); d->shouldDelete = new QCheckBox(d->checkBoxStack); d->shouldDelete->setGeometry(QRect(0, 0, 542, 32)); d->shouldDelete->setToolTip(i18n("If checked, files will be permanently removed instead of being placed " "in the Trash.")); d->shouldDelete->setWhatsThis(i18n("

If this box is checked, items will be " "permanently removed instead of " "being placed in the Trash.

" "

Use this option with caution: most filesystems " "are unable to " "undelete deleted items reliably.

")); d->shouldDelete->setText(i18n("&Delete items instead of moving them to the trash")); connect(d->shouldDelete, SIGNAL(toggled(bool)), this, SLOT(slotShouldDelete(bool))); d->doNotShowAgain = new QCheckBox(d->checkBoxStack); d->doNotShowAgain->setGeometry(QRect(0, 0, 100, 30)); d->doNotShowAgain->setText(i18n("Do not &ask again")); QVBoxLayout* const vbox = new QVBoxLayout(this); vbox->setContentsMargins(QMargins()); vbox->setSpacing(spacing); vbox->addLayout(hbox); vbox->addWidget(d->fileList, 10); vbox->addWidget(d->numFiles); vbox->addWidget(d->checkBoxStack); d->checkBoxStack->addWidget(d->shouldDelete); d->checkBoxStack->addWidget(d->doNotShowAgain); d->checkBoxStack->setCurrentWidget(d->shouldDelete); bool deleteInstead = !ApplicationSettings::instance()->getUseTrash(); slotShouldDelete(deleteInstead); d->shouldDelete->setChecked(deleteInstead); } DeleteWidget::~DeleteWidget() { delete d; } void DeleteWidget::setUrls(const QList& urls) { d->fileList->clear(); foreach(const QUrl& url, urls) { new DeleteItem(d->fileList, url); } updateText(); } void DeleteWidget::slotShouldDelete(bool shouldDelete) { setDeleteMode(shouldDelete ? DeleteDialogMode::DeletePermanently : DeleteDialogMode::UseTrash); } void DeleteWidget::setDeleteMode(DeleteDialogMode::DeleteMode deleteMode) { d->deleteMode = deleteMode; updateText(); } void DeleteWidget::setListMode(DeleteDialogMode::ListMode listMode) { d->listMode = listMode; updateText(); } void DeleteWidget::updateText() { // set "do not ask again checkbox text if (d->deleteMode == DeleteDialogMode::DeletePermanently) { d->doNotShowAgain->setToolTip(i18n("If checked, this dialog will no longer be shown, and items will " "be directly and permanently deleted.")); d->doNotShowAgain->setWhatsThis(i18n("If this box is checked, this dialog will no longer be shown, " "and items will be directly and permanently deleted.")); } else { d->doNotShowAgain->setToolTip(i18n("If checked, this dialog will no longer be shown, and items will " "be directly moved to the Trash.")); d->doNotShowAgain->setWhatsThis(i18n("If this box is checked, this dialog will no longer be shown, " "and items will be directly moved to the Trash.")); } switch (d->listMode) { case DeleteDialogMode::Files: { // Delete files if (d->deleteMode == DeleteDialogMode::DeletePermanently) { d->deleteText->setText(i18n("These items will be permanently " "deleted from your hard disk.")); d->warningIcon->setPixmap(QIcon::fromTheme(QLatin1String("dialog-warning")).pixmap(48)); } else { d->deleteText->setText(i18n("These items will be moved to Trash.")); d->warningIcon->setPixmap(QIcon::fromTheme(QLatin1String("user-trash-full")).pixmap(48)); d->numFiles->setText(i18np("1 item selected.", "%1 items selected.", d->fileList->topLevelItemCount())); } break; } case DeleteDialogMode::Albums: { // Delete albums = folders if (d->deleteMode == DeleteDialogMode::DeletePermanently) { d->deleteText->setText(i18n("These albums will be permanently " "deleted from your hard disk.")); d->warningIcon->setPixmap(QIcon::fromTheme(QLatin1String("dialog-warning")).pixmap(48)); } else { d->deleteText->setText(i18n("These albums will be moved to Trash.")); d->warningIcon->setPixmap(QIcon::fromTheme(QLatin1String("user-trash-full")).pixmap(48)); } d->numFiles->setText(i18np("1 album selected.", "%1 albums selected.", d->fileList->topLevelItemCount())); break; } case DeleteDialogMode::Subalbums: { // As above, but display additional warning if (d->deleteMode == DeleteDialogMode::DeletePermanently) { d->deleteText->setText(i18n("

These albums will be permanently " "deleted from your hard disk.

" "

Note that all subalbums " "are included in this list and will " "be deleted permanently as well.

")); d->warningIcon->setPixmap(QIcon::fromTheme(QLatin1String("dialog-warning")).pixmap(48)); } else { d->deleteText->setText(i18n("

These albums will be moved to Trash.

" "

Note that all subalbums " "are included in this list and will " "be moved to Trash as well.

")); d->warningIcon->setPixmap(QIcon::fromTheme(QLatin1String("user-trash-full")).pixmap(48)); } d->numFiles->setText(i18np("1 album selected.", "%1 albums selected.", d->fileList->topLevelItemCount())); break; } } } //---------------------------------------------------------------------------- class Q_DECL_HIDDEN DeleteDialog::Private { public: explicit Private() { saveShouldDeleteUserPreference = true; saveDoNotShowAgainTrash = false; saveDoNotShowAgainPermanent = false; page = 0; buttons = 0; } bool saveShouldDeleteUserPreference; bool saveDoNotShowAgainTrash; bool saveDoNotShowAgainPermanent; DeleteWidget* page; QDialogButtonBox* buttons; }; DeleteDialog::DeleteDialog(QWidget* const parent) : QDialog(parent), d(new Private) { setModal(true); d->buttons = new QDialogButtonBox(QDialogButtonBox::Apply | QDialogButtonBox::Cancel, this); d->buttons->button(QDialogButtonBox::Apply)->setDefault(true); d->page = new DeleteWidget(this); d->page->setMinimumSize(400, 300); QVBoxLayout* const vbx = new QVBoxLayout(this); vbx->addWidget(d->page); vbx->addWidget(d->buttons); setLayout(vbx); setMinimumSize(410, 326); adjustSize(); slotShouldDelete(shouldDelete()); connect(d->page->d->shouldDelete, SIGNAL(toggled(bool)), this, SLOT(slotShouldDelete(bool))); connect(d->buttons->button(QDialogButtonBox::Apply), SIGNAL(clicked()), this, SLOT(slotUser1Clicked())); connect(d->buttons->button(QDialogButtonBox::Cancel), SIGNAL(clicked()), this, SLOT(reject())); } DeleteDialog::~DeleteDialog() { delete d; } bool DeleteDialog::confirmDeleteList(const QList& condemnedFiles, DeleteDialogMode::ListMode listMode, DeleteDialogMode::DeleteMode deleteMode) { setUrls(condemnedFiles); presetDeleteMode(deleteMode); setListMode(listMode); if (deleteMode == DeleteDialogMode::NoChoiceTrash) { if (!ApplicationSettings::instance()->getShowTrashDeleteDialog()) { return true; } } else if (deleteMode == DeleteDialogMode::NoChoiceDeletePermanently) { if (!ApplicationSettings::instance()->getShowPermanentDeleteDialog()) { return true; } } return (exec() == QDialog::Accepted); } void DeleteDialog::setUrls(const QList& urls) { d->page->setUrls(urls); } void DeleteDialog::slotUser1Clicked() { // Save user's preference ApplicationSettings* const settings = ApplicationSettings::instance(); if (d->saveShouldDeleteUserPreference) { settings->setUseTrash(!shouldDelete()); } if (d->saveDoNotShowAgainTrash) { qCDebug(DIGIKAM_GENERAL_LOG) << "setShowTrashDeleteDialog " << !d->page->d->doNotShowAgain->isChecked(); settings->setShowTrashDeleteDialog(!d->page->d->doNotShowAgain->isChecked()); } if (d->saveDoNotShowAgainPermanent) { qCDebug(DIGIKAM_GENERAL_LOG) << "setShowPermanentDeleteDialog " << !d->page->d->doNotShowAgain->isChecked(); settings->setShowPermanentDeleteDialog(!d->page->d->doNotShowAgain->isChecked()); } settings->saveSettings(); QDialog::accept(); } bool DeleteDialog::shouldDelete() const { return d->page->d->shouldDelete->isChecked(); } void DeleteDialog::slotShouldDelete(bool shouldDelete) { // This is called once from constructor, and then when the user changed the checkbox state. // In that case, save the user's preference. d->saveShouldDeleteUserPreference = true; d->buttons->button(QDialogButtonBox::Apply)->setText(shouldDelete ? i18n("&Delete") : i18n("&Move to Trash")); d->buttons->button(QDialogButtonBox::Apply)->setIcon(shouldDelete ? QIcon::fromTheme(QLatin1String("edit-delete")) : QIcon::fromTheme(QLatin1String("user-trash-full"))); } void DeleteDialog::presetDeleteMode(DeleteDialogMode::DeleteMode mode) { switch (mode) { case DeleteDialogMode::NoChoiceTrash: { // access the widget directly, signals will be fired to DeleteDialog and DeleteWidget d->page->d->shouldDelete->setChecked(false); d->page->d->checkBoxStack->setCurrentWidget(d->page->d->doNotShowAgain); d->saveDoNotShowAgainTrash = true; break; } case DeleteDialogMode::NoChoiceDeletePermanently: { d->page->d->shouldDelete->setChecked(true); d->page->d->checkBoxStack->setCurrentWidget(d->page->d->doNotShowAgain); d->saveDoNotShowAgainPermanent = true; //d->page->d->checkBoxStack->hide(); break; } case DeleteDialogMode::UserPreference: { break; } case DeleteDialogMode::UseTrash: case DeleteDialogMode::DeletePermanently: { // toggles signals which do the rest d->page->d->shouldDelete->setChecked(mode == DeleteDialogMode::DeletePermanently); // the preference set by this preset method will be ignored // for the next DeleteDialog instance and not stored as user preference. // Only if the user once changes this value, it will be taken as user preference. d->saveShouldDeleteUserPreference = false; break; } } } void DeleteDialog::setListMode(DeleteDialogMode::ListMode mode) { d->page->setListMode(mode); switch (mode) { case DeleteDialogMode::Files: setWindowTitle(i18n("About to delete selected items")); break; case DeleteDialogMode::Albums: case DeleteDialogMode::Subalbums: setWindowTitle(i18n("About to delete selected albums")); break; } } void DeleteDialog::keyPressEvent(QKeyEvent* e) { - if ( e->modifiers() == 0 ) + if (e->modifiers() == 0) { if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) { if (d->buttons->button(QDialogButtonBox::Apply)->hasFocus()) { e->accept(); d->buttons->button(QDialogButtonBox::Apply)->animateClick(); return; } else if (d->buttons->button(QDialogButtonBox::Cancel)->hasFocus()) { e->accept(); d->buttons->button(QDialogButtonBox::Cancel)->animateClick(); return; } } } QDialog::keyPressEvent(e); } } // namespace Digikam diff --git a/core/libs/dimg/filters/icc/iccprofile.cpp b/core/libs/dimg/filters/icc/iccprofile.cpp index e2d0c384a3..4b5f27a872 100644 --- a/core/libs/dimg/filters/icc/iccprofile.cpp +++ b/core/libs/dimg/filters/icc/iccprofile.cpp @@ -1,620 +1,620 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2009-08-07 * Description : a wrapper class for an ICC color profile * * Copyright (C) 2005-2006 by F.J. Cruz * Copyright (C) 2005-2019 by Gilles Caulier * Copyright (C) 2009-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 "iccprofile.h" #include "digikam-lcms.h" // Qt includes #include #include #include #include #include // Local includes #include "digikam_debug.h" #include "dimg.h" namespace Digikam { class Q_DECL_HIDDEN IccProfile::Private : public QSharedData { public: explicit Private() : type(IccProfile::InvalidType), handle(0) { } explicit Private(const Private& other) : QSharedData(other) { handle = 0; operator = (other); } Private& operator=(const Private& other) { data = other.data; filePath = other.filePath; description = other.description; type = other.type; close(); handle = 0; return *this; } ~Private() { close(); } void close() { if (handle) { LcmsLock lock; dkCmsCloseProfile(handle); handle = 0; } } public: QByteArray data; QString filePath; QString description; IccProfile::ProfileType type; cmsHPROFILE handle; }; // ---------------------------------------------------------------------------------- class Q_DECL_HIDDEN IccProfileStatic { public: IccProfileStatic() : lcmsMutex() { } QMutex lcmsMutex; QString adobeRGBPath; }; Q_GLOBAL_STATIC(IccProfileStatic, static_d) // ---------------------------------------------------------------------------------- LcmsLock::LcmsLock() { static_d->lcmsMutex.lock(); } LcmsLock::~LcmsLock() { static_d->lcmsMutex.unlock(); } // ---------------------------------------------------------------------------------- IccProfile::IccProfile() : d(0) { } IccProfile::IccProfile(const QByteArray& data) : d(new Private) { d->data = data; } IccProfile::IccProfile(const QString& filePath) : d(new Private) { d->filePath = filePath; } IccProfile::IccProfile(const char* const location, const QString& relativePath) : d(0) { QString filePath; // NOTE: if necessary, implement new location support here. if (QLatin1String(location) == QLatin1String("data")) { //qCDebug(DIGIKAM_DIMG_LOG) << "Searching ICC profile from data directory with relative path:" << relativePath; filePath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, relativePath); } else { qCDebug(DIGIKAM_DIMG_LOG) << "Data location " << location << " to handle bundled profile is not supported."; } if (filePath.isNull()) { qCDebug(DIGIKAM_DIMG_LOG) << "The bundled profile" << relativePath << "cannot be found. Check your installation."; return; } d = new Private; d->filePath = filePath; } IccProfile IccProfile::sRGB() { // The srgb.icm file seems to have a whitepoint of D50, see #133913 return IccProfile("data", QLatin1String("digikam/profiles/srgb-d65.icm")); } IccProfile IccProfile::adobeRGB() { QString path = static_d->adobeRGBPath; if (path.isEmpty()) { path = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("digikam/profiles/compatibleWithAdobeRGB1998.icc")); } return IccProfile(path); } IccProfile IccProfile::wideGamutRGB() { return IccProfile("data", QLatin1String("digikam/profiles/widegamut.icm")); } IccProfile IccProfile::proPhotoRGB() { return IccProfile("data", QLatin1String("digikam/profiles/prophoto.icm")); } QList IccProfile::defaultProfiles() { QList profiles; profiles << sRGB() << adobeRGB() << proPhotoRGB() << wideGamutRGB(); return profiles; } IccProfile::IccProfile(const IccProfile& other) : d(other.d) { } IccProfile::~IccProfile() { } IccProfile& IccProfile::operator=(const IccProfile& other) { d = other.d; return *this; } bool IccProfile::isNull() const { return !d; } bool IccProfile::operator==(const IccProfile& other) const { if (d == other.d) { return true; } if (d && other.d) { if (!d->filePath.isNull() || !other.d->filePath.isNull()) { return (d->filePath == other.d->filePath); } if (!d->data.isNull() || other.d->data.isNull()) { return (d->data == other.d->data); } } return false; } bool IccProfile::isSameProfileAs(IccProfile& other) { if (d == other.d) { return true; } if (d && other.d) { // uses memcmp return (data() == other.data()); } return false; } QByteArray IccProfile::data() { if (!d) { return QByteArray(); } if (!d->data.isEmpty()) { return d->data; } else if (!d->filePath.isNull()) { QFile file(d->filePath); if (!file.open(QIODevice::ReadOnly)) { return QByteArray(); } d->data = file.readAll(); file.close(); return d->data; } return QByteArray(); } bool IccProfile::open() { if (!d) { return false; } if (d->handle) { return true; } if (!d->data.isEmpty()) { LcmsLock lock; d->handle = dkCmsOpenProfileFromMem(d->data.data(), (DWORD)d->data.size()); } else if (!d->filePath.isNull()) { // read file data(); if (d->data.isEmpty()) { return false; } LcmsLock lock; d->handle = dkCmsOpenProfileFromMem(d->data.data(), (DWORD)d->data.size()); } return d->handle; } void IccProfile::close() { if (!d) { return; } d->close(); } bool IccProfile::isOpen() const { if (!d) { return false; } return d->handle; } QString IccProfile::filePath() const { if (!d) { return QString(); } return d->filePath; } QString IccProfile::description() { if (!d) { return QString(); } if (!d->description.isNull()) { return d->description; } if (!open()) { return QString(); } LcmsLock lock; - if ( !QString(dkCmsTakeProductDesc(d->handle)).isEmpty() ) + if (!QString(dkCmsTakeProductDesc(d->handle)).isEmpty()) { d->description = QString(dkCmsTakeProductDesc(d->handle)).replace(QLatin1Char('\n'), QLatin1Char(' ')); } return d->description; } IccProfile::ProfileType IccProfile::type() { if (!d) { return InvalidType; } if (d->type != InvalidType) { return d->type; } if (!open()) { return InvalidType; } LcmsLock lock; switch ((int)dkCmsGetDeviceClass(d->handle)) { case icSigInputClass: case 0x6e6b7066: // 'nkbf', proprietary in Nikon profiles d->type = Input; break; case icSigDisplayClass: d->type = Display; break; case icSigOutputClass: d->type = Output; break; case icSigColorSpaceClass: d->type = ColorSpace; break; case icSigLinkClass: d->type = DeviceLink; break; case icSigAbstractClass: d->type = Abstract; break; case icSigNamedColorClass: d->type = NamedColor; break; default: break; } return d->type; } bool IccProfile::writeToFile(const QString& filePath) { if (!d) { return false; } QByteArray profile = data(); if (!profile.isEmpty()) { QFile file(filePath); if (!file.open(QIODevice::WriteOnly)) { return false; } if (file.write(profile) == -1) { return false; } file.close(); return true; } return false; } void* IccProfile::handle() const { if (!d) { return 0; } return d->handle; } QStringList IccProfile::defaultSearchPaths() { QStringList paths; QStringList candidates; paths << QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QLatin1String("color/icc"), QStandardPaths::LocateDirectory); #ifdef Q_OS_WIN candidates << QDir::rootPath() + QLatin1String("/Windows/Spool/Drivers/Color/"); // For Win2K and WinXP candidates << QDir::rootPath() + QLatin1String("/Windows/Color/"); // For Win98 and WinMe #elif defined (Q_OS_OSX) // Use a scheme highly identical to the Linux scheme, adapted for MacPorts in /opt/local, ofcial PKG installer, and the OS X standard ColorSync directories candidates << QLatin1String("/System/Library/ColorSync/Profiles"); candidates << QLatin1String("/Library/ColorSync/Profiles"); candidates << QDir::homePath() + QLatin1String("/Library/ColorSync/Profiles"); // MacPorts installs for KDE, so we include the XDG data dirs, including /usr/share/color/icc QStringList dataDirs = QString::fromLocal8Bit(qgetenv("XDG_DATA_DIRS")).split(QLatin1Char(':'), QString::SkipEmptyParts); if (!dataDirs.contains(QLatin1String("/opt/local/share"))) { dataDirs << QLatin1String("/opt/local/share"); } if (!dataDirs.contains(QLatin1String("/opt/digikam/share"))) { dataDirs << QLatin1String("/opt/digikam/share"); } foreach(const QString& dataDir, dataDirs) { candidates << dataDir + QLatin1String("/color/icc"); } // XDG_DATA_HOME QString dataHomeDir = QString::fromLocal8Bit(qgetenv("XDG_DATA_HOME")); if (!dataHomeDir.isEmpty()) { candidates << dataHomeDir + QLatin1String("/color/icc"); candidates << dataHomeDir + QLatin1String("/icc"); } // home dir directories candidates << QDir::homePath() + QLatin1String("/.local/share/color/icc/"); candidates << QDir::homePath() + QLatin1String("/.local/share/icc/"); candidates << QDir::homePath() + QLatin1String("/.color/icc/"); #else // LINUX // XDG data dirs, including /usr/share/color/icc QStringList dataDirs = QString::fromLocal8Bit(qgetenv("XDG_DATA_DIRS")).split(QLatin1Char(':'), QString::SkipEmptyParts); if (!dataDirs.contains(QLatin1String("/usr/share"))) { dataDirs << QLatin1String("/usr/share"); } if (!dataDirs.contains(QLatin1String("/usr/local/share"))) { dataDirs << QLatin1String("/usr/local/share"); } foreach(const QString& dataDir, dataDirs) { candidates << dataDir + QLatin1String("/color/icc"); } // XDG_DATA_HOME QString dataHomeDir = QString::fromLocal8Bit(qgetenv("XDG_DATA_HOME")); if (!dataHomeDir.isEmpty()) { candidates << dataHomeDir + QLatin1String("/color/icc"); candidates << dataHomeDir + QLatin1String("/icc"); } // home dir directories candidates << QDir::homePath() + QLatin1String("/.local/share/color/icc/"); candidates << QDir::homePath() + QLatin1String("/.local/share/icc/"); candidates << QDir::homePath() + QLatin1String("/.color/icc/"); #endif foreach(const QString& candidate, candidates) { QDir dir(candidate); if (dir.exists() && dir.isReadable()) { QString path = dir.canonicalPath(); if (!paths.contains(path)) { paths << path; } } } //qCDebug(DIGIKAM_DIMG_LOG) << candidates << '\n' << paths; return paths; } void IccProfile::considerOriginalAdobeRGB(const QString& filePath) { if (!static_d->adobeRGBPath.isNull()) { return; } QFile file(filePath); if (file.open(QIODevice::ReadOnly)) { QCryptographicHash md5(QCryptographicHash::Md5); md5.addData(&file); file.close(); if (md5.result().toHex() == QByteArray("dea88382d899d5f6e573b432473ae138")) { qCDebug(DIGIKAM_DIMG_LOG) << "The original Adobe RGB (1998) profile has been found at" << filePath; static_d->adobeRGBPath = filePath; } } } } // namespace Digikam diff --git a/core/libs/dimg/filters/lens/lensfuniface.cpp b/core/libs/dimg/filters/lens/lensfuniface.cpp index 0545805472..9bfd7f44bf 100644 --- a/core/libs/dimg/filters/lens/lensfuniface.cpp +++ b/core/libs/dimg/filters/lens/lensfuniface.cpp @@ -1,560 +1,560 @@ /* ============================================================ * * Date : 2008-02-10 * Description : a tool to fix automatically camera lens aberrations * * Copyright (C) 2008 by Adrian Schroeter * Copyright (C) 2008-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 "lensfuniface.h" // Qt includes #include #include #include // Local includes #include "digikam_debug.h" namespace Digikam { class Q_DECL_HIDDEN LensFunIface::Private { public: explicit Private() { usedLens = 0; usedCamera = 0; lfDb = 0; lfCameras = 0; } // To be used for modification LensFunContainer settings; // Database items lfDatabase* lfDb; const lfCamera* const* lfCameras; QString makeDescription; QString modelDescription; QString lensDescription; LensPtr usedLens; DevicePtr usedCamera; }; LensFunIface::LensFunIface() : d(new Private) { d->lfDb = lf_db_new(); // Lensfun host XML files in a dedicated sub-directory. QString lensPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("lensfun"), QStandardPaths::LocateDirectory); QDir lensDir; // In first try to use last Lensfun version data dir. lensDir = QDir(lensPath + QLatin1String("/version_2"), QLatin1String("*.xml")); if (lensDir.entryList().isEmpty()) { // Fail-back to revision 1. lensDir = QDir(lensPath + QLatin1String("/version_1"), QLatin1String("*.xml")); if (lensDir.entryList().isEmpty()) { // Fail-back to revision 0 which host XML data in root data directory. lensDir = QDir(lensPath, QLatin1String("*.xml")); } } qCDebug(DIGIKAM_DIMG_LOG) << "Using root lens database dir: " << lensPath; - foreach(const QString& lens, lensDir.entryList()) + foreach (const QString& lens, lensDir.entryList()) { qCDebug(DIGIKAM_DIMG_LOG) << "Load lens database file: " << lens; d->lfDb->Load(QFile::encodeName(lensDir.absoluteFilePath(lens)).constData()); } d->lfDb->Load(); d->lfCameras = d->lfDb->GetCameras(); } LensFunIface::~LensFunIface() { lf_db_destroy(d->lfDb); delete d; } void LensFunIface::setSettings(const LensFunContainer& other) { d->settings = other; d->usedCamera = findCamera(d->settings.cameraMake, d->settings.cameraModel); d->usedLens = findLens(d->settings.lensModel); } LensFunContainer LensFunIface::settings() const { return d->settings; } LensFunIface::DevicePtr LensFunIface::usedCamera() const { return d->usedCamera; } void LensFunIface::setUsedCamera(DevicePtr cam) { d->usedCamera = cam; d->settings.cameraMake = d->usedCamera ? QLatin1String(d->usedCamera->Maker) : QString(); d->settings.cameraModel = d->usedCamera ? QLatin1String(d->usedCamera->Model) : QString(); d->settings.cropFactor = d->usedCamera ? d->usedCamera->CropFactor : -1.0; } LensFunIface::LensPtr LensFunIface::usedLens() const { return d->usedLens; } void LensFunIface::setUsedLens(LensPtr lens) { d->usedLens = lens; d->settings.lensModel = d->usedLens ? QLatin1String(d->usedLens->Model) : QString(); } lfDatabase* LensFunIface::lensFunDataBase() const { return d->lfDb; } QString LensFunIface::makeDescription() const { return d->makeDescription; } QString LensFunIface::modelDescription() const { return d->modelDescription; } QString LensFunIface::lensDescription() const { return d->lensDescription; } const lfCamera* const* LensFunIface::lensFunCameras() const { return d->lfCameras; } void LensFunIface::setFilterSettings(const LensFunContainer& other) { d->settings.filterCCA = other.filterCCA; d->settings.filterVIG = other.filterVIG; d->settings.filterDST = other.filterDST; d->settings.filterGEO = other.filterGEO; } LensFunIface::DevicePtr LensFunIface::findCamera(const QString& make, const QString& model) const { const lfCamera* const* cameras = d->lfDb->GetCameras(); while (cameras && *cameras) { DevicePtr cam = *cameras; // qCDebug(DIGIKAM_DIMG_LOG) << "Query camera:" << cam->Maker << "-" << cam->Model; if (QString::fromLatin1(cam->Maker).toLower() == make.toLower() && QString::fromLatin1(cam->Model).toLower() == model.toLower()) { qCDebug(DIGIKAM_DIMG_LOG) << "Search for camera " << make << "-" << model << " ==> true"; return cam; } ++cameras; } qCDebug(DIGIKAM_DIMG_LOG) << "Search for camera " << make << "-" << model << " ==> false"; return 0; } LensFunIface::LensPtr LensFunIface::findLens(const QString& model) const { const lfLens* const* lenses = d->lfDb->GetLenses(); while (lenses && *lenses) { LensPtr lens = *lenses; if (QString::fromLatin1(lens->Model) == model) { qCDebug(DIGIKAM_DIMG_LOG) << "Search for lens " << model << " ==> true"; return lens; } ++lenses; } qCDebug(DIGIKAM_DIMG_LOG) << "Search for lens " << model << " ==> false"; return 0; } LensFunIface::LensList LensFunIface::findLenses(const lfCamera* const lfCamera, const QString& lensDesc, const QString& lensMaker) const { LensList lensList; const lfLens** lfLens = 0; if (lfCamera) { if (!lensMaker.isEmpty()) { lfLens = d->lfDb->FindLenses(lfCamera, lensMaker.toLatin1().constData(), lensDesc.toLatin1().constData()); } else { lfLens = d->lfDb->FindLenses(lfCamera, NULL, lensDesc.toLatin1().constData()); } while (lfLens && *lfLens) { lensList << (*lfLens); ++lfLens; } } return lensList; } LensFunIface::MetadataMatch LensFunIface::findFromMetadata(const DMetadata& meta) { MetadataMatch ret = MetadataNoMatch; d->settings = LensFunContainer(); d->usedCamera = 0; d->usedLens = 0; d->lensDescription.clear(); if (meta.isEmpty()) { qCDebug(DIGIKAM_DIMG_LOG) << "No metadata available"; return LensFunIface::MetadataUnavailable; } PhotoInfoContainer photoInfo = meta.getPhotographInformation(); d->makeDescription = photoInfo.make.trimmed(); d->modelDescription = photoInfo.model.trimmed(); bool exactMatch = true; if (d->makeDescription.isEmpty()) { qCDebug(DIGIKAM_DIMG_LOG) << "No camera maker info available"; exactMatch = false; } else { // NOTE: see bug #184156: // Some rules to wrap unknown camera device from Lensfun database, which have equivalent in fact. if (d->makeDescription == QLatin1String("Canon")) { if (d->modelDescription == QLatin1String("Canon EOS Kiss Digital X")) { d->modelDescription = QLatin1String("Canon EOS 400D DIGITAL"); } if (d->modelDescription == QLatin1String("G1 X")) { d->modelDescription = QLatin1String("G1X"); } } d->lensDescription = photoInfo.lens.trimmed(); // ------------------------------------------------------------------------------------------------ DevicePtr lfCamera = findCamera(d->makeDescription, d->modelDescription); if (lfCamera) { setUsedCamera(lfCamera); qCDebug(DIGIKAM_DIMG_LOG) << "Camera maker : " << d->settings.cameraMake; qCDebug(DIGIKAM_DIMG_LOG) << "Camera model : " << d->settings.cameraModel; // ------------------------------------------------------------------------------------------------ // -- Performing lens description searches. if (!d->lensDescription.isEmpty()) { LensList lensMatches; QString lensCutted; LensList lensList; // STAGE 1, search in LensFun database as well. lensList = findLenses(d->usedCamera, d->lensDescription); qCDebug(DIGIKAM_DIMG_LOG) << "* Check for lens by direct query (" << d->lensDescription << " : " << lensList.count() << ")"; lensMatches.append(lensList); // STAGE 2, Adapt exiv2 strings to lensfun strings for Nikon. lensCutted = d->lensDescription; if (lensCutted.contains(QLatin1String("Nikon"))) { lensCutted.remove(QLatin1String("Nikon ")); lensCutted.remove(QLatin1String("Zoom-")); lensCutted.replace(QLatin1String("IF-ID"), QLatin1String("ED-IF")); lensList = findLenses(d->usedCamera, lensCutted); qCDebug(DIGIKAM_DIMG_LOG) << "* Check for Nikon lens (" << lensCutted << " : " << lensList.count() << ")"; lensMatches.append(lensList); } // TODO : Add here more specific lens maker rules. // LAST STAGE, Adapt exiv2 strings to lensfun strings. Some lens description use something like that : // "10.0 - 20.0 mm". This must be adapted like this : "10-20mm" lensCutted = d->lensDescription; lensCutted.replace(QRegExp(QLatin1String("\\.[0-9]")), QLatin1String("")); //krazy:exclude=doublequote_chars lensCutted.replace(QLatin1String(" - "), QLatin1String("-")); lensCutted.replace(QLatin1String(" mm"), QLatin1String("mn")); lensList = findLenses(d->usedCamera, lensCutted); qCDebug(DIGIKAM_DIMG_LOG) << "* Check for no maker lens (" << lensCutted << " : " << lensList.count() << ")"; lensMatches.append(lensList); // Remove all duplicate lenses in the list by using QSet. lensMatches = lensMatches.toSet().toList(); // Display the results. if (lensMatches.isEmpty()) { qCDebug(DIGIKAM_DIMG_LOG) << "lens matches : NOT FOUND"; exactMatch &= false; } else { // Best case for an exact match is to have only one item returned by Lensfun searches. - if(lensMatches.count() == 1) + if (lensMatches.count() == 1) { setUsedLens(lensMatches.first()); qCDebug(DIGIKAM_DIMG_LOG) << "Lens found : " << d->settings.lensModel; qCDebug(DIGIKAM_DIMG_LOG) << "Crop Factor : " << d->settings.cropFactor; } else { qCDebug(DIGIKAM_DIMG_LOG) << "lens matches : more than one..."; const lfLens* exact = 0; - foreach(const lfLens* const l, lensMatches) + foreach (const lfLens* const l, lensMatches) { - if(QLatin1String(l->Model) == d->lensDescription) + if (QLatin1String(l->Model) == d->lensDescription) { qCDebug(DIGIKAM_DIMG_LOG) << "found exact match from" << lensMatches.count() << "possibilities:" << l->Model; exact = l; } } - if(exact) + if (exact) { setUsedLens(exact); } else { exactMatch &= false; } } } } else { qCDebug(DIGIKAM_DIMG_LOG) << "Lens description string is empty"; exactMatch &= false; } } else { qCDebug(DIGIKAM_DIMG_LOG) << "Cannot find Lensfun camera device for (" << d->makeDescription << " - " << d->modelDescription << ")"; exactMatch &= false; } } // ------------------------------------------------------------------------------------------------ // Performing Lens settings searches. QString temp = photoInfo.focalLength; if (temp.isEmpty()) { qCDebug(DIGIKAM_DIMG_LOG) << "Focal Length : NOT FOUND"; exactMatch &= false; } d->settings.focalLength = temp.mid(0, temp.length() - 3).toDouble(); // HACK: strip the " mm" at the end ... qCDebug(DIGIKAM_DIMG_LOG) << "Focal Length : " << d->settings.focalLength; // ------------------------------------------------------------------------------------------------ temp = photoInfo.aperture; if (temp.isEmpty()) { qCDebug(DIGIKAM_DIMG_LOG) << "Aperture : NOT FOUND"; exactMatch &= false; } d->settings.aperture = temp.mid(1).toDouble(); qCDebug(DIGIKAM_DIMG_LOG) << "Aperture : " << d->settings.aperture; // ------------------------------------------------------------------------------------------------ // Try to get subject distance value. // From standard Exif. temp = meta.getExifTagString("Exif.Photo.SubjectDistance"); if (temp.isEmpty()) { // From standard XMP. temp = meta.getXmpTagString("Xmp.exif.SubjectDistance"); } if (temp.isEmpty()) { // From Canon Makernote. temp = meta.getExifTagString("Exif.CanonSi.SubjectDistance"); } if (temp.isEmpty()) { // From Nikon Makernote. temp = meta.getExifTagString("Exif.NikonLd2.FocusDistance"); } if (temp.isEmpty()) { // From Nikon Makernote. temp = meta.getExifTagString("Exif.NikonLd3.FocusDistance"); } if (temp.isEmpty()) { // From Olympus Makernote. temp = meta.getExifTagString("Exif.OlympusFi.FocusDistance"); } // TODO: Add here others Makernotes tags. if (temp.isEmpty()) { qCDebug(DIGIKAM_DIMG_LOG) << "Subject dist. : NOT FOUND : Use default value."; temp = QLatin1String("1000"); } temp = temp.remove(QLatin1String(" m")); bool ok; d->settings.subjectDistance = temp.toDouble(&ok); if (!ok) { d->settings.subjectDistance = -1.0; } qCDebug(DIGIKAM_DIMG_LOG) << "Subject dist. : " << d->settings.subjectDistance; // ------------------------------------------------------------------------------------------------ ret = exactMatch ? MetadataExactMatch : MetadataPartialMatch; qCDebug(DIGIKAM_DIMG_LOG) << "Metadata match : " << metadataMatchDebugStr(ret); return ret; } QString LensFunIface::metadataMatchDebugStr(MetadataMatch val) const { QString ret; switch (val) { case MetadataNoMatch: ret = QLatin1String("No Match"); break; case MetadataPartialMatch: ret = QLatin1String("Partial Match"); break; default: ret = QLatin1String("Exact Match"); break; } return ret; } bool LensFunIface::supportsDistortion() const { if (!d->usedLens) { return false; } lfLensCalibDistortion res; return d->usedLens->InterpolateDistortion(d->settings.focalLength, res); } bool LensFunIface::supportsCCA() const { if (!d->usedLens) { return false; } lfLensCalibTCA res; return d->usedLens->InterpolateTCA(d->settings.focalLength, res); } bool LensFunIface::supportsVig() const { if (!d->usedLens) { return false; } lfLensCalibVignetting res; return d->usedLens->InterpolateVignetting(d->settings.focalLength, d->settings.aperture, d->settings.subjectDistance, res); } bool LensFunIface::supportsGeometry() const { return supportsDistortion(); } QString LensFunIface::lensFunVersion() { return QString::fromLatin1("%1.%2.%3-%4").arg(LF_VERSION_MAJOR).arg(LF_VERSION_MINOR).arg(LF_VERSION_MICRO).arg(LF_VERSION_BUGFIX); } } // namespace Digikam diff --git a/core/libs/dimg/loaders/pgfloader.cpp b/core/libs/dimg/loaders/pgfloader.cpp index 42f8ae9ba3..06fee18738 100644 --- a/core/libs/dimg/loaders/pgfloader.cpp +++ b/core/libs/dimg/loaders/pgfloader.cpp @@ -1,552 +1,553 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2009-06-03 * Description : A PGF IO file for DImg framework * * Copyright (C) 2009-2019 by Gilles Caulier * * This implementation use LibPGF API * * 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 "digikam_config.h" #include "pgfloader.h" // krazy:exclude=includes // C Ansi includes extern "C" { #include #include #include #include } // C++ includes #include #include #include // Qt includes #include #include #include #include #include #include // Windows includes #ifdef Q_OS_WIN32 #include #endif // Libpgf includes #include "PGFimage.h" // Local includes #include "digikam_debug.h" #include "dimg.h" #include "dimgloaderobserver.h" #include "pgfutils.h" #include "metaengine.h" namespace Digikam { static bool CallbackForLibPGF(double percent, bool escapeAllowed, void* data) { if (data) { PGFLoader* const d = static_cast(data); if (d) { return d->progressCallback(percent, escapeAllowed); } } return false; } PGFLoader::PGFLoader(DImg* const image) : DImgLoader(image) { m_hasAlpha = false; m_sixteenBit = false; m_observer = 0; } bool PGFLoader::load(const QString& filePath, DImgLoaderObserver* const observer) { m_observer = observer; readMetadata(filePath, DImg::PGF); FILE* const file = fopen(QFile::encodeName(filePath).constData(), "rb"); if (!file) { qCWarning(DIGIKAM_DIMG_LOG_PGF) << "Error: Could not open source file."; loadingFailed(); return false; } unsigned char header[3]; if (fread(&header, 3, 1, file) != 1) { fclose(file); loadingFailed(); return false; } unsigned char pgfID[3] = { 0x50, 0x47, 0x46 }; if (memcmp(&header[0], &pgfID, 3) != 0) { // not a PGF file fclose(file); loadingFailed(); return false; } fclose(file); // ------------------------------------------------------------------- // Initialize PGF API. #ifdef Q_OS_WIN32 #ifdef UNICODE HANDLE fd = CreateFileW((LPCWSTR)filePath.utf16(), GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, 0); #else HANDLE fd = CreateFile(QFile::encodeName(filePath).constData(), GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, 0); #endif if (fd == INVALID_HANDLE_VALUE) { qCWarning(DIGIKAM_DIMG_LOG_PGF) << "Error: Could not open source file."; qCWarning(DIGIKAM_DIMG_LOG_PGF) << "Last error code:" << GetLastError(); loadingFailed(); return false; } #else int fd = QT_OPEN(QFile::encodeName(filePath).constData(), O_RDONLY); if (fd == -1) { loadingFailed(); return false; } #endif CPGFFileStream stream(fd); CPGFImage pgf; int colorModel = DImg::COLORMODELUNKNOWN; try { // open pgf image pgf.Open(&stream); switch (pgf.Mode()) { case ImageModeRGBColor: case ImageModeRGB48: m_hasAlpha = false; colorModel = DImg::RGB; break; case ImageModeRGBA: m_hasAlpha = true; colorModel = DImg::RGB; break; default: qCWarning(DIGIKAM_DIMG_LOG_PGF) << "Cannot load PGF image: color mode not supported (" << pgf.Mode() << ")"; loadingFailed(); return false; break; } switch (pgf.Channels()) { case 3: case 4: break; default: qCWarning(DIGIKAM_DIMG_LOG_PGF) << "Cannot load PGF image: color channels number not supported (" << pgf.Channels() << ")"; loadingFailed(); return false; break; } int bitDepth = pgf.BPP(); switch (bitDepth) { case 24: // RGB 8 bits. case 32: // RGBA 8 bits. m_sixteenBit = false; break; case 48: // RGB 16 bits. case 64: // RGBA 16 bits. m_sixteenBit = true; break; default: qCWarning(DIGIKAM_DIMG_LOG_PGF) << "Cannot load PGF image: color bits depth not supported (" << bitDepth << ")"; loadingFailed(); return false; break; } - if(DIGIKAM_DIMG_LOG_PGF().isDebugEnabled()) { + if (DIGIKAM_DIMG_LOG_PGF().isDebugEnabled()) + { const PGFHeader* header = pgf.GetHeader(); qCDebug(DIGIKAM_DIMG_LOG_PGF) << "PGF width = " << header->width; qCDebug(DIGIKAM_DIMG_LOG_PGF) << "PGF height = " << header->height; qCDebug(DIGIKAM_DIMG_LOG_PGF) << "PGF bbp = " << header->bpp; qCDebug(DIGIKAM_DIMG_LOG_PGF) << "PGF channels = " << header->channels; qCDebug(DIGIKAM_DIMG_LOG_PGF) << "PGF quality = " << header->quality; qCDebug(DIGIKAM_DIMG_LOG_PGF) << "PGF mode = " << header->mode; qCDebug(DIGIKAM_DIMG_LOG_PGF) << "Has Alpha = " << m_hasAlpha; qCDebug(DIGIKAM_DIMG_LOG_PGF) << "Is 16 bits = " << m_sixteenBit; } // NOTE: see bug #273765 : Loading PGF thumbs with OpenMP support through a separated thread do not work properly with libppgf 6.11.24 pgf.ConfigureDecoder(false); int width = pgf.Width(); int height = pgf.Height(); uchar* data = 0; QSize originalSize(width, height); if (m_loadFlags & LoadImageData) { // ------------------------------------------------------------------- // Find out if we do the fast-track loading with reduced size. PGF specific. int level = 0; QVariant attribute = imageGetAttribute(QLatin1String("scaledLoadingSize")); if (attribute.isValid() && pgf.Levels() > 0) { int scaledLoadingSize = attribute.toInt(); int i, w, h; for (i = pgf.Levels() - 1 ; i >= 0 ; --i) { w = pgf.Width(i); h = pgf.Height(i); if (qMin(w, h) >= scaledLoadingSize) { break; } } if (i >= 0) { width = w; height = h; level = i; qCDebug(DIGIKAM_DIMG_LOG_PGF) << "Loading PGF scaled version at level " << i << " (" << w << " x " << h << ") for size " << scaledLoadingSize; } } if (m_sixteenBit) { data = new_failureTolerant(width, height, 8); // 16 bits/color/pixel } else { data = new_failureTolerant(width, height, 4); // 8 bits/color/pixel } // Fill all with 255 including alpha channel. memset(data, 0xFF, width * height * (m_sixteenBit ? 8 : 4)); pgf.Read(level, CallbackForLibPGF, this); pgf.GetBitmap(m_sixteenBit ? width * 8 : width * 4, (UINT8*)data, m_sixteenBit ? 64 : 32, NULL, CallbackForLibPGF, this); if (observer) { observer->progressInfo(m_image, 1.0); } } // ------------------------------------------------------------------- // Get ICC color profile. if (m_loadFlags & LoadICCData) { // TODO: Implement proper storage in PGF for color profiles checkExifWorkingColorSpace(); } imageWidth() = width; imageHeight() = height; imageData() = data; imageSetAttribute(QLatin1String("format"), QLatin1String("PGF")); imageSetAttribute(QLatin1String("originalColorModel"), colorModel); imageSetAttribute(QLatin1String("originalBitDepth"), bitDepth); imageSetAttribute(QLatin1String("originalSize"), originalSize); #ifdef Q_OS_WIN32 CloseHandle(fd); #else close(fd); #endif return true; } catch (IOException& e) { int err = e.error; if (err >= AppError) { err -= AppError; } qCWarning(DIGIKAM_DIMG_LOG_PGF) << "Error: Opening and reading PGF image failed (" << err << ")!"; #ifdef Q_OS_WIN32 CloseHandle(fd); #else close(fd); #endif loadingFailed(); return false; } catch (std::bad_alloc& e) { qCWarning(DIGIKAM_DIMG_LOG_PGF) << "Failed to allocate memory for loading" << filePath << e.what(); #ifdef Q_OS_WIN32 CloseHandle(fd); #else close(fd); #endif loadingFailed(); return false; } return true; } bool PGFLoader::save(const QString& filePath, DImgLoaderObserver* const observer) { m_observer = observer; #ifdef Q_OS_WIN32 #ifdef UNICODE HANDLE fd = CreateFileW((LPCWSTR)filePath.utf16(), GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); #else HANDLE fd = CreateFile(QFile::encodeName(filePath).constData(), GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); #endif if (fd == INVALID_HANDLE_VALUE) { qCWarning(DIGIKAM_DIMG_LOG_PGF) << "Error: Could not open destination file."; qCWarning(DIGIKAM_DIMG_LOG_PGF) << "Last error code:" << GetLastError(); return false; } #elif defined(__POSIX__) int fd = QT_OPEN(QFile::encodeName(filePath).constData(), O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); if (fd == -1) { qCWarning(DIGIKAM_DIMG_LOG_PGF) << "Error: Could not open destination file."; return false; } #endif try { QVariant qualityAttr = imageGetAttribute(QLatin1String("quality")); int quality = qualityAttr.isValid() ? qualityAttr.toInt() : 3; qCDebug(DIGIKAM_DIMG_LOG_PGF) << "PGF quality: " << quality; CPGFFileStream stream(fd); CPGFImage pgf; PGFHeader header; header.width = imageWidth(); header.height = imageHeight(); header.quality = quality; if (imageHasAlpha()) { if (imageSixteenBit()) { // NOTE : there is no PGF color mode in 16 bits with alpha. header.channels = 3; header.bpp = 48; header.mode = ImageModeRGB48; } else { header.channels = 4; header.bpp = 32; header.mode = ImageModeRGBA; } } else { if (imageSixteenBit()) { header.channels = 3; header.bpp = 48; header.mode = ImageModeRGB48; } else { header.channels = 3; header.bpp = 24; header.mode = ImageModeRGBColor; } } #ifdef PGFCodecVersionID # if PGFCodecVersionID < 0x061142 header.background.rgbtBlue = 0; header.background.rgbtGreen = 0; header.background.rgbtRed = 0; # endif #endif pgf.SetHeader(header); // NOTE: see bug #273765 : Loading PGF thumbs with OpenMP support through a separated thread do not work properly with libppgf 6.11.24 pgf.ConfigureEncoder(false); pgf.ImportBitmap(4 * imageWidth() * (imageSixteenBit() ? 2 : 1), (UINT8*)imageData(), imageBitsDepth() * 4, NULL, CallbackForLibPGF, this); UINT32 nWrittenBytes = 0; #ifdef PGFCodecVersionID # if PGFCodecVersionID >= 0x061124 pgf.Write(&stream, &nWrittenBytes, CallbackForLibPGF, this); # endif #else pgf.Write(&stream, 0, CallbackForLibPGF, &nWrittenBytes, this); #endif qCDebug(DIGIKAM_DIMG_LOG_PGF) << "PGF width = " << header.width; qCDebug(DIGIKAM_DIMG_LOG_PGF) << "PGF height = " << header.height; qCDebug(DIGIKAM_DIMG_LOG_PGF) << "PGF bbp = " << header.bpp; qCDebug(DIGIKAM_DIMG_LOG_PGF) << "PGF channels = " << header.channels; qCDebug(DIGIKAM_DIMG_LOG_PGF) << "PGF quality = " << header.quality; qCDebug(DIGIKAM_DIMG_LOG_PGF) << "PGF mode = " << header.mode; qCDebug(DIGIKAM_DIMG_LOG_PGF) << "Bytes Written = " << nWrittenBytes; #ifdef Q_OS_WIN32 CloseHandle(fd); #else close(fd); #endif // TODO: Store ICC profile in an appropriate place in the image storeColorProfileInMetadata(); if (observer) { observer->progressInfo(m_image, 1.0); } imageSetAttribute(QLatin1String("savedformat"), QLatin1String("PGF")); saveMetadata(filePath); return true; } catch (IOException& e) { int err = e.error; if (err >= AppError) { err -= AppError; } qCWarning(DIGIKAM_DIMG_LOG_PGF) << "Error: Opening and saving PGF image failed (" << err << ")!"; #ifdef Q_OS_WIN32 CloseHandle(fd); #else close(fd); #endif return false; } return true; } bool PGFLoader::hasAlpha() const { return m_hasAlpha; } bool PGFLoader::sixteenBit() const { return m_sixteenBit; } bool PGFLoader::progressCallback(double percent, bool escapeAllowed) { if (m_observer) { m_observer->progressInfo(m_image, percent); if (escapeAllowed) { return (!m_observer->continueQuery(m_image)); } } return false; } bool PGFLoader::isReadOnly() const { return false; } } // namespace Digikam diff --git a/core/libs/dplugins/widgets/dpreviewimage.cpp b/core/libs/dplugins/widgets/dpreviewimage.cpp index 62c8bb07a5..e4435b3ee0 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->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); 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 (obj == d->toolBar) { - if ( ev->type() == QEvent::Enter) + if (ev->type() == QEvent::Enter) setCursor(Qt::ArrowCursor); - else if ( ev->type() == QEvent::Leave) + else if (ev->type() == QEvent::Leave) unsetCursor(); return false; } - else if ( obj == verticalScrollBar() && verticalScrollBar()->isVisible()) + else if (obj == verticalScrollBar() && verticalScrollBar()->isVisible()) { - if ( ev->type() == QEvent::Enter) + if (ev->type() == QEvent::Enter) setCursor(Qt::ArrowCursor); - else if ( ev->type() == QEvent::Leave) + else if (ev->type() == QEvent::Leave) unsetCursor(); return false; } - else if ( obj == horizontalScrollBar() && horizontalScrollBar()->isVisible()) + else if (obj == horizontalScrollBar() && horizontalScrollBar()->isVisible()) { - if ( ev->type() == QEvent::Enter) + if (ev->type() == QEvent::Enter) setCursor(Qt::ArrowCursor); - else if ( ev->type() == QEvent::Leave) + 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/dtrash/dtrashitemmodel.cpp b/core/libs/dtrash/dtrashitemmodel.cpp index 688f9e0a50..79494fb2cd 100644 --- a/core/libs/dtrash/dtrashitemmodel.cpp +++ b/core/libs/dtrash/dtrashitemmodel.cpp @@ -1,384 +1,386 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2015-08-09 * Description : DTrash item info model * * Copyright (C) 2015 by Mohamed_Anwer * * 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 "dtrashitemmodel.h" // Qt includes #include #include #include #include #include #include // KDE includes #include "klocalizedstring.h" // Local includes #include "digikam_debug.h" #include "thumbnailsize.h" #include "iojobsmanager.h" namespace Digikam { class Q_DECL_HIDDEN DTrashItemModel::Private { public: explicit Private() : thumbSize(ThumbnailSize::Large), sortColumn(2), sortOrder(Qt::DescendingOrder), itemsLoadingThread(0), thumbnailThread(0) { } public: int thumbSize; int sortColumn; Qt::SortOrder sortOrder; IOJobsThread* itemsLoadingThread; ThumbnailLoadThread* thumbnailThread; QList failedThumbnails; DTrashItemInfoList data; }; DTrashItemModel::DTrashItemModel(QObject* const parent) : QAbstractTableModel(parent), d(new Private) { qRegisterMetaType("DTrashItemInfo"); d->thumbnailThread = new ThumbnailLoadThread; d->thumbnailThread->setSendSurrogatePixmap(false); connect(d->thumbnailThread, SIGNAL(signalThumbnailLoaded(LoadingDescription,QPixmap)), this, SLOT(refreshThumbnails(LoadingDescription,QPixmap))); } DTrashItemModel::~DTrashItemModel() { delete d->thumbnailThread; delete d; } int DTrashItemModel::rowCount(const QModelIndex&) const { return d->data.count(); } int DTrashItemModel::columnCount(const QModelIndex&) const { return 3; } QVariant DTrashItemModel::data(const QModelIndex& index, int role) const { - if ( role != Qt::DisplayRole && - role != Qt::DecorationRole && - role != Qt::TextAlignmentRole && - role != Qt::ToolTipRole) + if (role != Qt::DisplayRole && + role != Qt::DecorationRole && + role != Qt::TextAlignmentRole && + role != Qt::ToolTipRole) + { return QVariant(); + } const DTrashItemInfo& item = d->data[index.row()]; if (role == Qt::TextAlignmentRole) return Qt::AlignCenter; if (role == Qt::DecorationRole && index.column() == 0) { QPixmap pix; QString thumbPath; if (!d->failedThumbnails.contains(item.collectionPath)) { thumbPath = item.collectionPath; } else { thumbPath = item.trashPath; } if (pixmapForItem(thumbPath, pix)) { if (pix.isNull()) { QMimeType mimeType = QMimeDatabase().mimeTypeForFile(item.trashPath); if (mimeType.isValid()) { pix = QIcon::fromTheme(mimeType.genericIconName()).pixmap(128); } } return pix; } else { return QVariant(QVariant::Pixmap); } } if (role == Qt::ToolTipRole && index.column() == 1) return item.collectionRelativePath; switch (index.column()) { case 1: return item.collectionRelativePath; case 2: { QString dateTimeFormat = QLocale().dateTimeFormat(); if (!dateTimeFormat.contains(QLatin1String("yyyy"))) { dateTimeFormat.replace(QLatin1String("yy"), QLatin1String("yyyy")); } return item.deletionTimestamp.toString(dateTimeFormat); } default: return QVariant(); }; } void DTrashItemModel::sort(int column, Qt::SortOrder order) { d->sortColumn = column; d->sortOrder = order; if (d->data.count() < 2) { return; } std::sort(d->data.begin(), d->data.end(), [column, order](const DTrashItemInfo& a, const DTrashItemInfo& b) { if (column == 2 && a.deletionTimestamp != b.deletionTimestamp) { if (order == Qt::DescendingOrder) { return a.deletionTimestamp > b.deletionTimestamp; } else { return a.deletionTimestamp < b.deletionTimestamp; } } if (order == Qt::DescendingOrder) { return a.collectionRelativePath > b.collectionRelativePath; } return a.collectionRelativePath < b.collectionRelativePath; }); const QModelIndex topLeft = index(0, 0); const QModelIndex bottomRight = index(rowCount(QModelIndex())-1, columnCount(QModelIndex())-1); dataChanged(topLeft, bottomRight); } bool DTrashItemModel::pixmapForItem(const QString& path, QPixmap& pix) const { return d->thumbnailThread->find(ThumbnailIdentifier(path), pix, d->thumbSize); } QVariant DTrashItemModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation != Qt::Horizontal) return QVariant(); if (role != Qt::DisplayRole) return QVariant(); switch (section) { case 0: return i18n("Thumbnail"); case 1: return i18n("Relative Path"); case 2: return i18n("Deletion Time"); default: return QVariant(); } } void DTrashItemModel::append(const DTrashItemInfo& itemInfo) { if (d->itemsLoadingThread != sender()) return; beginInsertRows(QModelIndex(), d->data.count(), d->data.count()); d->data.append(itemInfo); endInsertRows(); sort(d->sortColumn, d->sortOrder); emit dataChange(); } void DTrashItemModel::removeItems(const QModelIndexList& indexes) { QList persistentIndexes; foreach (const QModelIndex& index, indexes) { persistentIndexes << index; } layoutAboutToBeChanged(); foreach (const QPersistentModelIndex& index, persistentIndexes) { if (!index.isValid()) continue; const DTrashItemInfo& item = d->data[index.row()]; d->failedThumbnails.removeAll(item.collectionPath); d->failedThumbnails.removeAll(item.trashPath); beginRemoveRows(QModelIndex(), index.row(), index.row()); removeRow(index.row()); d->data.removeAt(index.row()); endRemoveRows(); } layoutChanged(); emit dataChange(); } void DTrashItemModel::refreshLayout() { const QModelIndex topLeft = index(0, 0); const QModelIndex bottomRight = index(rowCount(QModelIndex())-1, 0); dataChanged(topLeft, bottomRight); layoutAboutToBeChanged(); layoutChanged(); } void DTrashItemModel::refreshThumbnails(const LoadingDescription& desc, const QPixmap& pix) { if (pix.isNull()) { if (!d->failedThumbnails.contains(desc.filePath)) { d->failedThumbnails << desc.filePath; } } const QModelIndex topLeft = index(0, 0); const QModelIndex bottomRight = index(rowCount(QModelIndex())-1, 0); dataChanged(topLeft, bottomRight); } void DTrashItemModel::clearCurrentData() { d->failedThumbnails.clear(); beginResetModel(); d->data.clear(); endResetModel(); emit dataChange(); } void DTrashItemModel::loadItemsForCollection(const QString& colPath) { clearCurrentData(); d->itemsLoadingThread = IOJobsManager::instance()->startDTrashItemsListingForCollection(colPath); connect(d->itemsLoadingThread, SIGNAL(collectionTrashItemInfo(DTrashItemInfo)), this, SLOT(append(DTrashItemInfo)), Qt::QueuedConnection); } DTrashItemInfo DTrashItemModel::itemForIndex(const QModelIndex& index) { if (!index.isValid()) return DTrashItemInfo(); return d->data.at(index.row()); } DTrashItemInfoList DTrashItemModel::itemsForIndexes(const QList& indexes) { DTrashItemInfoList items; foreach (const QModelIndex& index, indexes) { if (!index.isValid()) continue; items << itemForIndex(index); } return items; } QModelIndex DTrashItemModel::indexForItem(const DTrashItemInfo& itemInfo) const { int index = d->data.indexOf(itemInfo); if (index != -1) { return createIndex(index, 0); } return QModelIndex(); } DTrashItemInfoList DTrashItemModel::allItems() { return d->data; } bool DTrashItemModel::isEmpty() { return d->data.isEmpty(); } void DTrashItemModel::changeThumbSize(int size) { d->thumbSize = size; if (isEmpty()) return; QTimer::singleShot(100, this, SLOT(refreshLayout())); } } // namespace Digikam diff --git a/core/libs/facesengine/facedb/facedb.cpp b/core/libs/facesengine/facedb/facedb.cpp index 27982f9ba5..c8c25ff153 100644 --- a/core/libs/facesengine/facedb/facedb.cpp +++ b/core/libs/facesengine/facedb/facedb.cpp @@ -1,828 +1,828 @@ /* ============================================================ * * This file is a part of digiKam * * Date : 02-02-2012 * Description : Face database interface to train identities. * * Copyright (C) 2012-2013 by Marcel Wiesweg * Copyright (C) 2010-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 "digikam_config.h" // OpenCV includes need to show up before Qt includes #ifdef HAVE_FACESENGINE_DNN # include "tensor.h" # include "input.h" # include "layers.h" # include "loss.h" # include "core.h" # include "solvers.h" # include "cpu_dlib.h" # include "tensor_tools.h" # include "utilities.h" # include "validation.h" # include "serialize.h" # include "matrix.h" # include "matrix_utilities.h" # include "matrix_subexp.h" # include "matrix_math_functions.h" # include "matrix_generic_image.h" # include "assign_image.h" # include "interpolation.h" # include "frontal_face_detector.h" # include "cv_image.h" # include "dnnfacemodel.h" # include "dnn_face.h" #endif // Local includes #include "eigenfacemodel.h" #include "fisherfacemodel.h" #include "lbphfacemodel.h" #include "facedb.h" // krazy:exclude=includes #include "digikam_debug.h" namespace Digikam { class Q_DECL_HIDDEN FaceDb::Private { public: explicit Private() : db(0) { } FaceDbBackend* db; }; /* * NOTE: This constructor is only used in facerec_dnnborrowed.cpp. * Create an object of FaceDb to invoke the method getFaceVector */ FaceDb::FaceDb() : d(new Private) { } FaceDb::FaceDb(FaceDbBackend* const db) : d(new Private) { d->db = db; } FaceDb::~FaceDb() { delete d; } BdEngineBackend::QueryState FaceDb::setSetting(const QString& keyword, const QString& value) { QMap parameters; parameters.insert(QLatin1String(":keyword"), keyword); parameters.insert(QLatin1String(":value"), value); return d->db->execDBAction(d->db->getDBAction(QLatin1String("ReplaceFaceSetting")), parameters); } QString FaceDb::setting(const QString& keyword) const { QMap parameters; parameters.insert(QLatin1String(":keyword"), keyword); QList values; // TODO Should really check return status here BdEngineBackend::QueryState queryStateResult = d->db->execDBAction(d->db->getDBAction(QLatin1String("SelectFaceSetting")), parameters, &values); qCDebug(DIGIKAM_FACEDB_LOG) << "FaceDB SelectFaceSetting val ret = " << (BdEngineBackend::QueryStateEnum)queryStateResult; if (values.isEmpty()) { return QString(); } else { return values.first().toString(); } } int FaceDb::addIdentity() const { QVariant id; d->db->execSql(QLatin1String("INSERT INTO Identities (`type`) VALUES (0);"), 0, &id); return id.toInt(); } void FaceDb::updateIdentity(const Identity& p) { d->db->execSql(QLatin1String("DELETE FROM IdentityAttributes WHERE id=?;"), p.id()); const QMap map = p.attributesMap(); QMap::const_iterator it; for (it = map.constBegin() ; it != map.constEnd() ; ++it) { d->db->execSql(QLatin1String("INSERT INTO IdentityAttributes (id, attribute, `value`) VALUES (?, ?,?);"), p.id(), it.key(), it.value()); } } void FaceDb::deleteIdentity(int id) { // Triggers do the rest d->db->execSql(QLatin1String("DELETE FROM Identities WHERE id=?;"), id); } void FaceDb::deleteIdentity(const QString & uuid) { QList ids; d->db->execSql(QLatin1String("SELECT Identities.id FROM Identities LEFT JOIN IdentityAttributes ON " " Identities.id=IdentityAttributes.id WHERE " " IdentityAttributes.attribute='uuid' AND IdentityAttributes.value=?;"), uuid, &ids); if (ids.size() == 1) { deleteIdentity(ids.first().toInt()); } else { qCWarning(DIGIKAM_FACEDB_LOG) << "Cannot delete identity with uuid " << uuid << ". There are " << ids.size() << " identities with this uuid."; } } QList FaceDb::identities() const { QList ids; QList results; d->db->execSql(QLatin1String("SELECT id FROM Identities;"), &ids); foreach (const QVariant& v, ids) { QList values; Identity p; p.setId(v.toInt()); d->db->execSql(QLatin1String("SELECT attribute, `value` FROM IdentityAttributes WHERE id=?;"), p.id(), &values); for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ;) { QString attribute = it->toString(); ++it; QString value = it->toString(); ++it; p.setAttribute(attribute, value); } results << p; } return results; } QList FaceDb::identityIds() const { QList ids; d->db->execSql(QLatin1String("SELECT id FROM Identities;"), &ids); QList results; foreach (const QVariant& var, ids) { results << var.toInt(); } return results; } namespace { enum { LBPHStorageVersion = 1 }; } void FaceDb::updateLBPHFaceModel(LBPHFaceModel& model) { QVariantList values; values << LBPHStorageVersion << model.radius() << model.neighbors() << model.gridX() << model.gridY(); if (model.databaseId) { values << model.databaseId; d->db->execSql(QLatin1String("UPDATE OpenCVLBPHRecognizer SET version=?, radius=?, neighbors=?, grid_x=?, grid_y=? WHERE id=?;"), values); } else { QVariant insertedId; d->db->execSql(QLatin1String("INSERT INTO OpenCVLBPHRecognizer (version, radius, neighbors, grid_x, grid_y) VALUES (?,?,?,?,?);"), values, 0, &insertedId); model.databaseId = insertedId.toInt(); } QList metadataList = model.histogramMetadata(); for (int i = 0 ; i < metadataList.size() ; ++i) { const LBPHistogramMetadata& metadata = metadataList[i]; if (metadata.storageStatus == LBPHistogramMetadata::Created) { OpenCVMatData data = model.histogramData(i); if (data.data.isEmpty()) { qCWarning(DIGIKAM_FACEDB_LOG) << "Histogram data to commit in database are empty for Identity " << metadata.identity; } else { QByteArray compressed = qCompress(data.data); if (compressed.isEmpty()) { qCWarning(DIGIKAM_FACEDB_LOG) << "Cannot compress histogram data to commit in database for Identity " << metadata.identity; } else { QVariantList histogramValues; QVariant insertedId; histogramValues << model.databaseId << metadata.identity << metadata.context << data.type << data.rows << data.cols << compressed; values.clear(); d->db->execSql(QLatin1String("SELECT id FROM OpenCVLBPHistograms " "WHERE recognizerid=? AND identity=? AND `type`=?;"), model.databaseId, metadata.identity, data.type, &values); if (values.count() > 20) { for (int j = 0 ; j < values.count() - 20 ; ++j) { qCDebug(DIGIKAM_FACEDB_LOG) << "Delete compressed histogram " << values.at(j).toInt() << " for identity " << metadata.identity; d->db->execSql(QLatin1String("DELETE FROM OpenCVLBPHistograms " "WHERE id=? AND recognizerid=? AND identity=? AND `type`=?;"), values.at(j).toInt(), model.databaseId, metadata.identity, data.type); } } d->db->execSql(QLatin1String("INSERT INTO OpenCVLBPHistograms (recognizerid, identity, `context`, `type`, `rows`, `cols`, `data`) " "VALUES (?,?,?,?,?,?,?);"), histogramValues, 0, &insertedId); model.setWrittenToDatabase(i, insertedId.toInt()); qCDebug(DIGIKAM_FACEDB_LOG) << "Commit compressed histogram " << insertedId.toInt() << " for identity " << metadata.identity << " with size " << compressed.size(); } } } } } LBPHFaceModel FaceDb::lbphFaceModel() const { QVariantList values; qCDebug(DIGIKAM_FACEDB_LOG) << "Loading LBPH model"; d->db->execSql(QLatin1String("SELECT id, version, radius, neighbors, grid_x, grid_y FROM OpenCVLBPHRecognizer;"), &values); for (QList::const_iterator it = values.constBegin(); it != values.constEnd();) { LBPHFaceModel model; model.databaseId = it->toInt(); ++it; qCDebug(DIGIKAM_FACEDB_LOG) << "Found model id" << model.databaseId; int version = it->toInt(); ++it; if (version > LBPHStorageVersion) { qCDebug(DIGIKAM_FACEDB_LOG) << "Unsupported LBPH storage version" << version; it += 4; continue; } model.setRadius(it->toInt()); ++it; model.setNeighbors(it->toInt()); ++it; model.setGridX(it->toInt()); ++it; model.setGridY(it->toInt()); ++it; DbEngineSqlQuery query = d->db->execQuery(QLatin1String("SELECT id, identity, `context`, `type`, `rows`, `cols`, `data` " "FROM OpenCVLBPHistograms WHERE recognizerid=?;"), model.databaseId); QList histograms; QList histogramMetadata; while (query.next()) { LBPHistogramMetadata metadata; OpenCVMatData data; metadata.databaseId = query.value(0).toInt(); metadata.identity = query.value(1).toInt(); metadata.context = query.value(2).toString(); metadata.storageStatus = LBPHistogramMetadata::InDatabase; // cv::Mat data.type = query.value(3).toInt(); data.rows = query.value(4).toInt(); data.cols = query.value(5).toInt(); QByteArray cData = query.value(6).toByteArray(); if (!cData.isEmpty()) { data.data = qUncompress(cData); if (data.data.isEmpty()) { qCWarning(DIGIKAM_FACEDB_LOG) << "Cannot uncompress histogram data to checkout from database for Identity " << metadata.identity; } else { qCDebug(DIGIKAM_FACEDB_LOG) << "Checkout compressed histogram " << metadata.databaseId << " for identity " << metadata.identity << " with size " << cData.size(); histograms << data; histogramMetadata << metadata; } } else { qCWarning(DIGIKAM_FACEDB_LOG) << "Histogram data to checkout from database are empty for Identity " << metadata.identity; } } model.setHistograms(histograms, histogramMetadata); return model; } return LBPHFaceModel(); } void FaceDb::clearLBPHTraining(const QString& context) { if (context.isNull()) { d->db->execSql(QLatin1String("DELETE FROM OpenCVLBPHistograms;")); d->db->execSql(QLatin1String("DELETE FROM OpenCVLBPHRecognizer;")); } else { d->db->execSql(QLatin1String("DELETE FROM OpenCVLBPHistograms WHERE `context`=?;"), context); } } void FaceDb::clearLBPHTraining(const QList& identities, const QString& context) { foreach (int id, identities) { if (context.isNull()) { d->db->execSql(QLatin1String("DELETE FROM OpenCVLBPHistograms WHERE identity=?;"), id); } else { d->db->execSql(QLatin1String("DELETE FROM OpenCVLBPHistograms WHERE identity=? AND `context`=?;"), id, context); } } } #ifdef HAVE_FACESENGINE_DNN void FaceDb::getFaceVector(cv::Mat data, std::vector& vecdata) { DNNFaceKernel dnnFaceKernel; dnnFaceKernel.getFaceVector(data, vecdata); } #endif void FaceDb::updateEIGENFaceModel(EigenFaceModel& model, const std::vector& images_rgb) { QList metadataList = model.matMetadata(); for (size_t i = 0, j = 0 ; i < (size_t)metadataList.size() ; ++i) { const EigenFaceMatMetadata& metadata = metadataList[i]; if (metadata.storageStatus == EigenFaceMatMetadata::Created) { OpenCVMatData data = model.matData(i); cv::Mat mat_rgb; if (j >= images_rgb.size()) { qCWarning(DIGIKAM_FACEDB_LOG) << "updateEIGENFaceModel: the size of images_rgb is wrong"; } else { mat_rgb = images_rgb[j++]; } if (data.data.isEmpty()) { qCWarning(DIGIKAM_FACEDB_LOG) << "Eigenface data to commit in database are empty for Identity " << metadata.identity; } else { QByteArray compressed = qCompress(data.data); std::vector vecdata; // FIXME !!! Why the Eigen face use DNN code here ??? #ifdef HAVE_FACESENGINE_DNN this->getFaceVector(mat_rgb, vecdata); #endif qCDebug(DIGIKAM_FACEDB_LOG) << "vecdata: " << vecdata[vecdata.size()-2] << vecdata[vecdata.size()-1]; QByteArray vec_byte(vecdata.size()*sizeof(float), 0); float* const fp = (float*)vec_byte.data(); for (size_t k = 0 ; k < vecdata.size() ; ++k) { *(fp + k) = vecdata[k]; } QByteArray compressed_vecdata = qCompress(vec_byte); if (compressed.isEmpty()) { qCWarning(DIGIKAM_FACEDB_LOG) << "Cannot compress mat data to commit in database for Identity " << metadata.identity; } else if (compressed_vecdata.isEmpty()) { qCWarning(DIGIKAM_FACEDB_LOG) << "Cannot compress face vec data to commit in database for Identity " << metadata.identity; } else { QVariantList histogramValues; QVariant insertedId; histogramValues << metadata.identity << metadata.context << data.type << data.rows << data.cols << compressed << compressed_vecdata; d->db->execSql(QLatin1String("INSERT INTO FaceMatrices (identity, `context`, `type`, `rows`, `cols`, `data`, vecdata) " "VALUES (?,?,?,?,?,?,?);"), histogramValues, 0, &insertedId); model.setWrittenToDatabase(i, insertedId.toInt()); qCDebug(DIGIKAM_FACEDB_LOG) << "Commit compressed matData " << insertedId << " for identity " << metadata.identity << " with size " << compressed.size(); } } } } } EigenFaceModel FaceDb::eigenFaceModel() const { qCDebug(DIGIKAM_FACEDB_LOG) << "Loading EIGEN model"; DbEngineSqlQuery query = d->db->execQuery(QLatin1String("SELECT id, identity, `context`, `type`, `rows`, `cols`, `data`, vecdata " "FROM FaceMatrices;")); EigenFaceModel model = EigenFaceModel(); QList mats; QList matMetadata; while (query.next()) { EigenFaceMatMetadata metadata; OpenCVMatData data; metadata.databaseId = query.value(0).toInt(); metadata.identity = query.value(1).toInt(); metadata.context = query.value(2).toString(); metadata.storageStatus = EigenFaceMatMetadata::InDatabase; // cv::Mat data.type = query.value(3).toInt(); data.rows = query.value(4).toInt(); data.cols = query.value(5).toInt(); QByteArray cData = query.value(6).toByteArray(); if (!cData.isEmpty()) { data.data = qUncompress(cData); if (data.data.isEmpty()) { qCWarning(DIGIKAM_FACEDB_LOG) << "Cannot uncompress mat data to checkout from database for Identity " << metadata.identity; } else { qCDebug(DIGIKAM_FACEDB_LOG) << "Checkout compressed histogram " << metadata.databaseId << " for identity " << metadata.identity << " with size " << cData.size(); mats << data; matMetadata << metadata; } } else { qCWarning(DIGIKAM_FACEDB_LOG) << "Mat data to checkout from database are empty for Identity " << metadata.identity; } } model.setMats(mats, matMetadata); return model; } FisherFaceModel FaceDb::fisherFaceModel() const { qCDebug(DIGIKAM_FACEDB_LOG) << "Loading FISHER model from FaceMatrices"; DbEngineSqlQuery query = d->db->execQuery(QLatin1String("SELECT id, identity, `context`, `type`, `rows`, `cols`, `data`, vecdata " "FROM FaceMatrices;")); FisherFaceModel model = FisherFaceModel(); QList mats; QList matMetadata; while (query.next()) { FisherFaceMatMetadata metadata; OpenCVMatData data; metadata.databaseId = query.value(0).toInt(); metadata.identity = query.value(1).toInt(); metadata.context = query.value(2).toString(); metadata.storageStatus = FisherFaceMatMetadata::InDatabase; // cv::Mat data.type = query.value(3).toInt(); data.rows = query.value(4).toInt(); data.cols = query.value(5).toInt(); QByteArray cData = query.value(6).toByteArray(); if (!cData.isEmpty()) { data.data = qUncompress(cData); if (data.data.isEmpty()) { qCWarning(DIGIKAM_FACEDB_LOG) << "Cannot uncompress mat data to checkout from database for Identity " << metadata.identity; } else { qCDebug(DIGIKAM_FACEDB_LOG) << "Checkout compressed histogram " << metadata.databaseId << " for identity " << metadata.identity << " with size " << cData.size(); mats << data; matMetadata << metadata; } } else { qCWarning(DIGIKAM_FACEDB_LOG) << "Mat data to checkout from database are empty for Identity " << metadata.identity; } } model.setMats(mats, matMetadata); return model; } #ifdef HAVE_FACESENGINE_DNN void FaceDb::updateDNNFaceModel(DNNFaceModel& model) { QList metadataList = model.vecMetadata(); for (size_t i = 0 ; i < (size_t)metadataList.size() ; ++i) { const DNNFaceVecMetadata& metadata = metadataList[i]; if (metadata.storageStatus == DNNFaceVecMetadata::Created) { std::vector vecdata = model.vecData(i); qCDebug(DIGIKAM_FACEDB_LOG) << "vecdata: " << vecdata[vecdata.size()-2] << vecdata[vecdata.size()-1]; QByteArray vec_byte(vecdata.size()*sizeof(float), 0); float* const fp = (float*)vec_byte.data(); for (size_t k = 0 ; k < vecdata.size() ; ++k) { *(fp + k) = vecdata[k]; } QByteArray compressed_vecdata = qCompress(vec_byte); if (compressed_vecdata.isEmpty()) { qCWarning(DIGIKAM_FACEDB_LOG) << "Cannot compress face vec data to commit in database for Identity " << metadata.identity; } else { QVariantList histogramValues; QVariant insertedId; histogramValues << metadata.identity << metadata.context << compressed_vecdata; d->db->execSql(QLatin1String("INSERT INTO FaceMatrices (identity, `context`, vecdata) " "VALUES (?,?,?);"), histogramValues, 0, &insertedId); model.setWrittenToDatabase(i, insertedId.toInt()); qCDebug(DIGIKAM_FACEDB_LOG) << "Commit compressed vecData " << insertedId << " for identity " << metadata.identity << " with size " << compressed_vecdata.size(); } } } } DNNFaceModel FaceDb::dnnFaceModel() const { qCDebug(DIGIKAM_FACEDB_LOG) << "Loading DNN model"; DbEngineSqlQuery query = d->db->execQuery(QLatin1String("SELECT id, identity, `context`, `type`, `rows`, `cols`, `data`, vecdata " "FROM FaceMatrices;")); DNNFaceModel model = DNNFaceModel(); QList> mats; QList matMetadata; while (query.next()) { DNNFaceVecMetadata metadata; std::vector vecdata; metadata.databaseId = query.value(0).toInt(); metadata.identity = query.value(1).toInt(); metadata.context = query.value(2).toString(); metadata.storageStatus = DNNFaceVecMetadata::InDatabase; QByteArray cData = query.value(7).toByteArray(); if (!cData.isEmpty()) { QByteArray new_vec = qUncompress(cData); if (new_vec.isEmpty()) { qCWarning(DIGIKAM_FACEDB_LOG) << "Cannot uncompress mat data to checkout from database for Identity " << metadata.identity; } else { qCDebug(DIGIKAM_FACEDB_LOG) << "Checkout compressed histogram " << metadata.databaseId << " for identity " << metadata.identity << " with size " << cData.size(); float* const it = (float *)new_vec.data(); for (int i = 0; i < 128; ++i) { vecdata.push_back(*(it+i)); } mats << vecdata; matMetadata << metadata; } } else { qCWarning(DIGIKAM_FACEDB_LOG) << "Mat data to checkout from database are empty for Identity " << metadata.identity; } } model.setMats(mats, matMetadata); return model; } #endif void FaceDb::clearEIGENTraining(const QString& context) { if (context.isNull()) { d->db->execSql(QLatin1String("DELETE FROM FaceMatrices;")); } else { d->db->execSql(QLatin1String("DELETE FROM FaceMatrices WHERE `context`=?;"), context); } } void FaceDb::clearEIGENTraining(const QList& identities, const QString& context) { foreach (int id, identities) { if (context.isNull()) { d->db->execSql(QLatin1String("DELETE FROM FaceMatrices WHERE identity=?;"), id); } else { d->db->execSql(QLatin1String("DELETE FROM FaceMatrices WHERE identity=? AND `context`=?;"), id, context); } } } bool FaceDb::integrityCheck() { QList values; d->db->execDBAction(d->db->getDBAction(QString::fromUtf8("checkRecognitionDbIntegrity")), &values); switch (d->db->databaseType()) { case BdEngineBackend::DbType::SQLite: // For SQLite the integrity check returns a single row with one string column "ok" on success and multiple rows on error. return( (values.size() == 1) && (values.first().toString().toLower().compare(QLatin1String("ok")) == 0) ); case BdEngineBackend::DbType::MySQL: // For MySQL, for every checked table, the table name, operation (check), message type (status) and the message text (ok on success) // are returned. So we check if there are four elements and if yes, whether the fourth element is "ok". //qCDebug(DIGIKAM_DATABASE_LOG) << "MySQL check returned " << values.size() << " rows"; - if ( (values.size() % 4) != 0) + if ((values.size() % 4) != 0) { return false; } for (QList::iterator it = values.begin() ; it != values.end() ; ) { QString tableName = (*it).toString(); ++it; QString operation = (*it).toString(); ++it; QString messageType = (*it).toString(); ++it; QString messageText = (*it).toString(); ++it; if (messageText.toLower().compare(QLatin1String("ok")) != 0) { qCDebug(DIGIKAM_DATABASE_LOG) << "Failed integrity check for table " << tableName << ". Reason:" << messageText; return false; } else { qCDebug(DIGIKAM_DATABASE_LOG) << "Passed integrity check for table " << tableName; } } // No error conditions. Db passed the integrity check. return true; default: return false; } } void FaceDb::vacuum() { d->db->execDBAction(d->db->getDBAction(QString::fromUtf8("vacuumRecognitionDB"))); } } // namespace Digikam diff --git a/core/libs/facesengine/facedb/facedbschemaupdater.cpp b/core/libs/facesengine/facedb/facedbschemaupdater.cpp index 7746c46904..f4ce42635f 100644 --- a/core/libs/facesengine/facedb/facedbschemaupdater.cpp +++ b/core/libs/facesengine/facedb/facedbschemaupdater.cpp @@ -1,279 +1,279 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2007-04-16 * Description : Face database schema updater * * Copyright (C) 2007-2009 by Marcel Wiesweg * Copyright (C) 2010-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 "facedbschemaupdater.h" // KDE includes #include // Local includes #include "digikam_debug.h" #include "dbenginebackend.h" #include "facedbaccess.h" #include "facedb.h" namespace Digikam { int FaceDbSchemaUpdater::schemaVersion() { return 3; } // ------------------------------------------------------------------------------------- class Q_DECL_HIDDEN FaceDbSchemaUpdater::Private { public: explicit Private() : setError(false), currentVersion(0), currentRequiredVersion(0), dbAccess(0), observer(0) { } bool setError; int currentVersion; int currentRequiredVersion; FaceDbAccess* dbAccess; InitializationObserver* observer; }; FaceDbSchemaUpdater::FaceDbSchemaUpdater(FaceDbAccess* const dbAccess) : d(new Private) { d->dbAccess = dbAccess; } FaceDbSchemaUpdater::~FaceDbSchemaUpdater() { delete d; } void FaceDbSchemaUpdater::setObserver(InitializationObserver* const observer) { d->observer = observer; } bool FaceDbSchemaUpdater::update() { bool success = startUpdates(); // even on failure, try to set current version - it may have incremented if (d->currentVersion) { d->dbAccess->db()->setSetting(QLatin1String("DBFaceVersion"), QString::number(d->currentVersion)); } if (d->currentRequiredVersion) { d->dbAccess->db()->setSetting(QLatin1String("DBFaceVersionRequired"), QString::number(d->currentRequiredVersion)); } return success; } bool FaceDbSchemaUpdater::startUpdates() { // First step: do we have an empty database? QStringList tables = d->dbAccess->backend()->tables(); if (tables.contains(QLatin1String("Identities"), Qt::CaseInsensitive)) { // Find out schema version of db file QString version = d->dbAccess->db()->setting(QLatin1String("DBFaceVersion")); QString versionRequired = d->dbAccess->db()->setting(QLatin1String("DBFaceVersionRequired")); qCDebug(DIGIKAM_FACEDB_LOG) << "Face database: have a structure version " << version; // mini schema update if (version.isEmpty() && d->dbAccess->parameters().isSQLite()) { version = d->dbAccess->db()->setting(QLatin1String("DBVersion")); } // We absolutely require the DBFaceVersion setting if (version.isEmpty()) { // Something is damaged. Give up. qCWarning(DIGIKAM_FACEDB_LOG) << "DBFaceVersion not available! Giving up schema upgrading."; QString errorMsg = i18n("The database is not valid: " "the \"DBFaceVersion\" setting does not exist. " "The current database schema version cannot be verified. " "Try to start with an empty database. "); d->dbAccess->setLastError(errorMsg); if (d->observer) { d->observer->error(errorMsg); d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); } return false; } // current version describes the current state of the schema in the db, // schemaVersion is the version required by the program. d->currentVersion = version.toInt(); if (d->currentVersion > schemaVersion()) { // trying to open a database with a more advanced than this FaceDbSchemaUpdater supports if (!versionRequired.isEmpty() && versionRequired.toInt() <= schemaVersion()) { // version required may be less than current version return true; } else { QString errorMsg = i18n("The database has been used with a more recent version of digiKam " "and has been updated to a database schema which cannot be used with this version. " "(This means this digiKam version is too old, or the database format is to recent.) " "Please use the more recent version of digiKam that you used before."); d->dbAccess->setLastError(errorMsg); if (d->observer) { d->observer->error(errorMsg); d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); } return false; } } else { return makeUpdates(); } } else { qCDebug(DIGIKAM_FACEDB_LOG) << "Face database: no database file available"; DbEngineParameters parameters = d->dbAccess->parameters(); // No legacy handling: start with a fresh db if (!createDatabase()) { QString errorMsg = i18n("Failed to create tables in database.\n%1", d->dbAccess->backend()->lastError()); d->dbAccess->setLastError(errorMsg); if (d->observer) { d->observer->error(errorMsg); d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); } return false; } return true; } } bool FaceDbSchemaUpdater::makeUpdates() { if (d->currentVersion < schemaVersion()) { if (d->currentVersion == 1) { updateV1ToV2(); } else if (d->currentVersion == 2) { updateV2ToV3(); } } return true; } bool FaceDbSchemaUpdater::createDatabase() { - if ( createTables() && createIndices() && createTriggers()) + if (createTables() && createIndices() && createTriggers()) { d->currentVersion = schemaVersion(); d->currentRequiredVersion = 3; return true; } else { return false; } } bool FaceDbSchemaUpdater::createTables() { return d->dbAccess->backend()->execDBAction(d->dbAccess->backend()->getDBAction(QLatin1String("CreateFaceDB"))) && d->dbAccess->backend()->execDBAction(d->dbAccess->backend()->getDBAction(QLatin1String("CreateFaceDBOpenCVLBPH"))) && d->dbAccess->backend()->execDBAction(d->dbAccess->backend()->getDBAction(QLatin1String("CreateFaceDBFaceMatrices"))); } bool FaceDbSchemaUpdater::createIndices() { return d->dbAccess->backend()->execDBAction(d->dbAccess->backend()->getDBAction(QLatin1String("CreateFaceIndices"))); } bool FaceDbSchemaUpdater::createTriggers() { return d->dbAccess->backend()->execDBAction(d->dbAccess->backend()->getDBAction(QLatin1String("CreateFaceTriggers"))); } bool FaceDbSchemaUpdater::updateV1ToV2() { /* if (!d->dbAccess->backend()->execDBAction(d->dbAccess->backend()->getDBAction("UpdateDBSchemaFromV1ToV2"))) { qError() << "Schema upgrade in DB from V1 to V2 failed!"; return false; } */ d->currentVersion = 2; d->currentRequiredVersion = 1; return true; } bool FaceDbSchemaUpdater::updateV2ToV3() { d->currentVersion = 3; d->currentRequiredVersion = 3; d->dbAccess->backend()->execDBAction(d->dbAccess->backend()->getDBAction(QLatin1String("CreateFaceDBFaceMatrices"))); return true; } } // namespace Digikam diff --git a/core/libs/filters/ratingfilter.cpp b/core/libs/filters/ratingfilter.cpp index fafbb15886..4da38b56d3 100644 --- a/core/libs/filters/ratingfilter.cpp +++ b/core/libs/filters/ratingfilter.cpp @@ -1,336 +1,336 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2007-10-09 * Description : a widget to filter album contents by rating * * Copyright (C) 2007-2019 by Gilles Caulier * Copyright (C) 2007 by Arnd Baecker * Copyright (C) 2014 by Mohamed_Anwer * * 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 "ratingfilter.h" // Qt includes #include #include #include // KDE includes #include // Local includes #include "digikam_debug.h" #include "dcursortracker.h" #include "thememanager.h" namespace Digikam { class Q_DECL_HIDDEN RatingFilterWidget::Private { public: explicit Private() { dirty = false; ratingTracker = 0; filterCond = ItemFilterSettings::GreaterEqualCondition; excludeUnrated = 0; } bool dirty; DCursorTracker* ratingTracker; ItemFilterSettings::RatingCondition filterCond; bool excludeUnrated; }; RatingFilterWidget::RatingFilterWidget(QWidget* const parent) : RatingWidget(parent), d(new Private) { d->ratingTracker = new DCursorTracker(QLatin1String(""), this); updateRatingTooltip(); setMouseTracking(true); setWhatsThis(i18n("Select the rating value used to filter " "albums' contents. Use the context pop-up menu to " "set rating filter conditions.")); // To dispatch signal from parent widget with filter condition. connect(this, SIGNAL(signalRatingChanged(int)), this, SLOT(slotRatingChanged())); } RatingFilterWidget::~RatingFilterWidget() { delete d; } void RatingFilterWidget::slotRatingChanged() { emit signalRatingFilterChanged(rating(), d->filterCond, d->excludeUnrated); } void RatingFilterWidget::setRatingFilterCondition(ItemFilterSettings::RatingCondition cond) { d->filterCond = cond; updateRatingTooltip(); slotRatingChanged(); } ItemFilterSettings::RatingCondition RatingFilterWidget::ratingFilterCondition() { return d->filterCond; } void RatingFilterWidget::setExcludeUnratedItems(bool excluded) { d->excludeUnrated = excluded; slotRatingChanged(); } bool RatingFilterWidget::isUnratedItemsExcluded() { return d->excludeUnrated; } void RatingFilterWidget::mouseMoveEvent(QMouseEvent* e) { // This method have been re-implemented to display and update the famous TipTracker contents. if ( d->dirty ) { int pos = e->x() / regPixmapWidth() +1; if (rating() != pos) { setRating(pos); } updateRatingTooltip(); } } void RatingFilterWidget::mousePressEvent(QMouseEvent* e) { // This method must be re-implemented to handle which mouse button is pressed // and show the rating filter settings pop-up menu with right mouse button. // NOTE: Left and Middle Mouse buttons continue to change rating filter value. d->dirty = false; if ( e->button() == Qt::LeftButton || e->button() == Qt::MidButton ) { d->dirty = true; int pos = e->x() / regPixmapWidth() +1; if (rating() == pos) { setRating(rating()-1); } else { setRating(pos); } updateRatingTooltip(); } } void RatingFilterWidget::mouseReleaseEvent(QMouseEvent*) { d->dirty = false; } void RatingFilterWidget::updateRatingTooltip() { // Adapt tip message with rating filter condition settings. switch (d->filterCond) { case ItemFilterSettings::GreaterEqualCondition: { d->ratingTracker->setText(i18n("Rating greater than or equal to %1.", rating())); break; } case ItemFilterSettings::EqualCondition: { d->ratingTracker->setText(i18n("Rating equal to %1.", rating())); break; } case ItemFilterSettings::LessEqualCondition: { d->ratingTracker->setText( i18n("Rating less than or equal to %1.", rating())); break; } default: break; } } // ----------------------------------------------------------------------------------------------- class Q_DECL_HIDDEN RatingFilter::Private { public: explicit Private() { ratingWidget = 0; optionsBtn = 0; optionsMenu = 0; geCondAction = 0; eqCondAction = 0; leCondAction = 0; excludeUnrated = 0; } QToolButton* optionsBtn; QAction* geCondAction; QAction* eqCondAction; QAction* leCondAction; QAction* excludeUnrated; QMenu* optionsMenu; RatingFilterWidget* ratingWidget; }; RatingFilter::RatingFilter(QWidget* const parent) : DHBox(parent), d(new Private) { d->ratingWidget = new RatingFilterWidget(this); d->optionsBtn = new QToolButton(this); d->optionsBtn->setToolTip( i18n("Rating Filter Options")); d->optionsBtn->setIcon(QIcon::fromTheme(QLatin1String("configure"))); d->optionsBtn->setPopupMode(QToolButton::InstantPopup); d->optionsMenu = new QMenu(d->optionsBtn); d->geCondAction = d->optionsMenu->addAction(i18n("Greater Than or Equals Condition")); d->geCondAction->setCheckable(true); d->eqCondAction = d->optionsMenu->addAction(i18n("Equals Condition")); d->eqCondAction->setCheckable(true); d->leCondAction = d->optionsMenu->addAction(i18n("Less Than or Equals Condition")); d->leCondAction->setCheckable(true); d->optionsMenu->addSeparator(); d->excludeUnrated = d->optionsMenu->addAction(i18n("Exclude Items Without Rating")); d->excludeUnrated->setCheckable(true); d->optionsBtn->setMenu(d->optionsMenu); layout()->setAlignment(d->ratingWidget, Qt::AlignVCenter|Qt::AlignRight); setContentsMargins(QMargins()); setSpacing(0); connect(d->optionsMenu, SIGNAL(triggered(QAction*)), this, SLOT(slotOptionsTriggered(QAction*))); connect(d->optionsMenu, SIGNAL(aboutToShow()), this, SLOT(slotOptionsMenu())); connect(d->ratingWidget, SIGNAL(signalRatingFilterChanged(int,ItemFilterSettings::RatingCondition,bool)), this, SIGNAL(signalRatingFilterChanged(int,ItemFilterSettings::RatingCondition,bool))); } RatingFilter::~RatingFilter() { delete d; } void RatingFilter::setRatingFilterCondition(ItemFilterSettings::RatingCondition cond) { d->ratingWidget->setRatingFilterCondition(cond); } ItemFilterSettings::RatingCondition RatingFilter::ratingFilterCondition() { return d->ratingWidget->ratingFilterCondition(); } void RatingFilter::setExcludeUnratedItems(bool excluded) { d->ratingWidget->setExcludeUnratedItems(excluded); d->excludeUnrated->setChecked(excluded); } bool RatingFilter::isUnratedItemsExcluded() { return d->ratingWidget->isUnratedItemsExcluded(); } void RatingFilter::slotOptionsMenu() { d->geCondAction->setChecked(false); d->eqCondAction->setChecked(false); d->leCondAction->setChecked(false); switch (ratingFilterCondition()) { case ItemFilterSettings::GreaterEqualCondition: d->geCondAction->setChecked(true); break; case ItemFilterSettings::EqualCondition: d->eqCondAction->setChecked(true); break; case ItemFilterSettings::LessEqualCondition: d->leCondAction->setChecked(true); break; } } void RatingFilter::slotOptionsTriggered(QAction* action) { if (action) { if (action == d->geCondAction) { setRatingFilterCondition(ItemFilterSettings::GreaterEqualCondition); } else if (action == d->eqCondAction) { setRatingFilterCondition(ItemFilterSettings::EqualCondition); } else if (action == d->leCondAction) { setRatingFilterCondition(ItemFilterSettings::LessEqualCondition); } - else if(action == d->excludeUnrated) + else if (action == d->excludeUnrated) { setExcludeUnratedItems(d->excludeUnrated->isChecked()); } } } void RatingFilter::setRating(int val) { d->ratingWidget->setRating(val); } int RatingFilter::rating() const { return d->ratingWidget->rating(); } } // namespace Digikam diff --git a/core/libs/models/albummodel.cpp b/core/libs/models/albummodel.cpp index 72b0dd1d31..e06811240b 100644 --- a/core/libs/models/albummodel.cpp +++ b/core/libs/models/albummodel.cpp @@ -1,395 +1,395 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2008-03-22 * Description : Qt Model for Albums * * Copyright (C) 2008-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 "albummodel.h" // Qt includes #include #include // KDE includes #include // Local includes #include "digikam_debug.h" #include "albummanager.h" #include "applicationsettings.h" #include "albumthumbnailloader.h" namespace Digikam { AlbumModel::AlbumModel(RootAlbumBehavior rootBehavior, QObject* const parent) : AbstractCheckableAlbumModel(Album::PHYSICAL, AlbumManager::instance()->findPAlbum(0), rootBehavior, parent) { m_columnHeader = i18n("Albums"); setupThumbnailLoading(); connect(AlbumManager::instance(), SIGNAL(signalPAlbumsDirty(QMap)), this, SLOT(setCountMap(QMap))); setCountMap(AlbumManager::instance()->getPAlbumsCount()); } AlbumModel::~AlbumModel() { } PAlbum* AlbumModel::albumForIndex(const QModelIndex& index) const { return static_cast(AbstractCheckableAlbumModel::albumForIndex(index)); } QVariant AlbumModel::decorationRoleData(Album* album) const { // asynchronous signals are handled by parent class QPixmap pix = AlbumThumbnailLoader::instance()->getAlbumThumbnailDirectly(static_cast(album)); prepareAddExcludeDecoration(album, pix); return pix; } Album* AlbumModel::albumForId(int id) const { return AlbumManager::instance()->findPAlbum(id); } // ------------------------------------------------------------------ TagModel::TagModel(RootAlbumBehavior rootBehavior, QObject* const parent) : AbstractCheckableAlbumModel(Album::TAG, AlbumManager::instance()->findTAlbum(0), rootBehavior, parent) { m_columnHeader = i18n("Tags"); setupThumbnailLoading(); setTagCount(NormalTagCount); } void TagModel::setColumnHeader(const QString& header) { m_columnHeader = header; } TAlbum* TagModel::albumForIndex(const QModelIndex& index) const { return static_cast(AbstractCheckableAlbumModel::albumForIndex(index)); } QVariant TagModel::decorationRoleData(Album* album) const { QPixmap pix = AlbumThumbnailLoader::instance()->getTagThumbnailDirectly(static_cast(album)); prepareAddExcludeDecoration(album, pix); return pix; } Album* TagModel::albumForId(int id) const { return AlbumManager::instance()->findTAlbum(id); } void TagModel::setTagCount(TagCountMode mode) { disconnect(AlbumManager::instance(), SIGNAL(signalTAlbumsDirty(QMap)), this, SLOT(setCountMap(QMap))); disconnect(AlbumManager::instance(), SIGNAL(signalFaceCountsDirty(QMap)), this, SLOT(setCountMap(QMap))); if (mode == NormalTagCount) { connect(AlbumManager::instance(), SIGNAL(signalTAlbumsDirty(QMap)), this, SLOT(setCountMap(QMap))); setCountMap(AlbumManager::instance()->getTAlbumsCount()); } else { connect(AlbumManager::instance(), SIGNAL(signalFaceCountsDirty(QMap)), this, SLOT(setCountMap(QMap))); setCountMap(AlbumManager::instance()->getFaceCount()); } } // ------------------------------------------------------------------ SearchModel::SearchModel(QObject* const parent) : AbstractCheckableAlbumModel(Album::SEARCH, AlbumManager::instance()->findSAlbum(0), IgnoreRootAlbum, parent) { m_columnHeader = i18n("Searches"); setShowCount(false); // handle search icons albumSettingsChanged(); connect(ApplicationSettings::instance(), SIGNAL(setupChanged()), this, SLOT(albumSettingsChanged())); } SAlbum* SearchModel::albumForIndex(const QModelIndex& index) const { return static_cast(AbstractCheckableAlbumModel::albumForIndex(index)); } void SearchModel::setReplaceNames(const QHash& replaceNames) { m_replaceNames = replaceNames; } void SearchModel::addReplaceName(const QString& technicalName, const QString& userVisibleName) { m_replaceNames.insert(technicalName, userVisibleName); } void SearchModel::setPixmapForNormalSearches(const QPixmap& pix) { m_pixmaps.insert(-1, pix); } void SearchModel::setDefaultPixmap(const QPixmap& pix) { m_pixmaps.insert(-2, pix); } void SearchModel::setPixmapForTimelineSearches(const QPixmap& pix) { m_pixmaps.insert(DatabaseSearch::TimeLineSearch, pix); } void SearchModel::setPixmapForHaarSearches(const QPixmap& pix) { m_pixmaps.insert(DatabaseSearch::HaarSearch, pix); } void SearchModel::setPixmapForMapSearches(const QPixmap& pix) { m_pixmaps.insert(DatabaseSearch::MapSearch, pix); } void SearchModel::setPixmapForDuplicatesSearches(const QPixmap& pix) { m_pixmaps.insert(DatabaseSearch::DuplicatesSearch, pix); } QVariant SearchModel::albumData(Album* a, int role) const { if (role == Qt::DisplayRole || role == AlbumTitleRole || role == Qt::ToolTipRole) { SAlbum* const salbum = static_cast(a); QString title = a->title(); QString displayTitle = salbum->displayTitle(); return m_replaceNames.value(title, displayTitle); } else if (role == Qt::DecorationRole) { SAlbum* const salbum = static_cast(a); QPixmap pixmap = m_pixmaps.value(salbum->searchType()); if (pixmap.isNull() && salbum->isNormalSearch()) { pixmap = m_pixmaps.value(-1); } if (pixmap.isNull()) { pixmap = m_pixmaps.value(-2); } return pixmap; } return AbstractCheckableAlbumModel::albumData(a, role); } Album* SearchModel::albumForId(int id) const { return AlbumManager::instance()->findSAlbum(id); } void SearchModel::albumSettingsChanged() { setPixmapForMapSearches(QIcon::fromTheme(QLatin1String("globe")).pixmap(ApplicationSettings::instance()->getTreeViewIconSize())); setPixmapForHaarSearches(QIcon::fromTheme(QLatin1String("tools-wizard")).pixmap(ApplicationSettings::instance()->getTreeViewIconSize())); setPixmapForNormalSearches(QIcon::fromTheme(QLatin1String("edit-find")).pixmap(ApplicationSettings::instance()->getTreeViewIconSize())); setPixmapForTimelineSearches(QIcon::fromTheme(QLatin1String("chronometer")).pixmap(ApplicationSettings::instance()->getTreeViewIconSize())); } // ------------------------------------------------------------------ DateAlbumModel::DateAlbumModel(QObject* const parent) : AbstractCountingAlbumModel(Album::DATE, AlbumManager::instance()->findDAlbum(0), IgnoreRootAlbum, parent) { m_columnHeader = i18n("Dates"); connect(AlbumManager::instance(), SIGNAL(signalDAlbumsDirty(QMap)), this, SLOT(setYearMonthMap(QMap))); setYearMonthMap(AlbumManager::instance()->getDAlbumsCount()); setup(); } DAlbum* DateAlbumModel::albumForIndex(const QModelIndex& index) const { return (static_cast(AbstractCountingAlbumModel::albumForIndex(index))); } QModelIndex DateAlbumModel::monthIndexForDate(const QDate& date) const { // iterate over all years for (int yearIndex = 0; yearIndex < rowCount(); ++yearIndex) { QModelIndex year = index(yearIndex, 0); DAlbum* const yearAlbum = albumForIndex(year); // do not search through months if we are sure, that the year already // does not match if (yearAlbum && (yearAlbum->range() == DAlbum::Year) && (yearAlbum->date().year() != date.year())) { continue; } // search the album with the correct month for (int monthIndex = 0; monthIndex < rowCount(year); ++monthIndex) { QModelIndex month = index(monthIndex, 0, year); DAlbum* const monthAlbum = albumForIndex(month); if (monthAlbum && (monthAlbum->range() == DAlbum::Month) && (monthAlbum->date().year() == date.year()) && (monthAlbum->date().month() == date.month())) { return month; } } } return QModelIndex(); } void DateAlbumModel::setPixmaps(const QPixmap& forYearAlbums, const QPixmap& forMonthAlbums) { m_yearPixmap = forYearAlbums; m_monthPixmap = forMonthAlbums; } QString DateAlbumModel::albumName(Album* album) const { DAlbum* const dalbum = static_cast(album); if (dalbum->range() == DAlbum::Year) { return QString::number(dalbum->date().year()); } else { return QLocale().standaloneMonthName(dalbum->date().month(), QLocale::LongFormat); } } QVariant DateAlbumModel::decorationRoleData(Album* album) const { DAlbum* const dalbum = static_cast(album); if (dalbum->range() == DAlbum::Year) { return m_yearPixmap; } else { return m_monthPixmap; } } QVariant DateAlbumModel::sortRoleData(Album* a) const { DAlbum* const dalbum = static_cast(a); if (dalbum) { return dalbum->date(); } qCDebug(DIGIKAM_GENERAL_LOG) << "There must be a data album."; return QDate(); } Album* DateAlbumModel::albumForId(int id) const { return AlbumManager::instance()->findDAlbum(id); } void DateAlbumModel::setYearMonthMap(const QMap& yearMonthMap) { AlbumIterator it(rootAlbum()); QMap albumToCountMap; while (it.current()) { DAlbum* const dalbum = static_cast(*it); QDate date = dalbum->date(); switch (dalbum->range()) { case DAlbum::Month: { QMap::const_iterator it2 = yearMonthMap.constFind(YearMonth(date.year(), date.month())); - if ( it2 != yearMonthMap.constEnd() ) + if (it2 != yearMonthMap.constEnd()) { albumToCountMap.insert((*it)->id(), it2.value()); } break; } case DAlbum::Year: // a year itself cannot contain images and therefore always has count 0 albumToCountMap.insert((*it)->id(), 0); break; default: qCDebug(DIGIKAM_GENERAL_LOG) << "Untreated DAlbum range " << dalbum->range(); albumToCountMap.insert((*it)->id(), 0); break; } ++it; } setCountMap(albumToCountMap); } } // namespace Digikam diff --git a/core/libs/properties/captions/captionedit.cpp b/core/libs/properties/captions/captionedit.cpp index 5f49b65451..a2ca4b4afb 100644 --- a/core/libs/properties/captions/captionedit.cpp +++ b/core/libs/properties/captions/captionedit.cpp @@ -1,203 +1,203 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2009-07-12 * Description : caption editor * * Copyright (C) 2009-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 "captionedit.h" // Qt includes #include // KDE includes #include // Local includes #include "altlangstredit.h" #include "digikam_debug.h" namespace Digikam { class Q_DECL_HIDDEN CaptionEdit::Private { public: explicit Private() { altLangStrEdit = 0; authorEdit = 0; } QLineEdit* authorEdit; AltLangStrEdit* altLangStrEdit; CaptionsMap captionsValues; QString lastDeletedLanguage; CaptionValues lastDeletedValues; }; CaptionEdit::CaptionEdit(QWidget* const parent) : DVBox(parent), d(new Private) { d->altLangStrEdit = new AltLangStrEdit(this); d->altLangStrEdit->setTitle(i18n("Captions: ")); d->altLangStrEdit->setPlaceholderText(i18n("Enter caption text here.")); d->authorEdit = new QLineEdit(this); d->authorEdit->setClearButtonEnabled(true); d->authorEdit->setPlaceholderText(i18n("Enter caption author name here.")); setContentsMargins(QMargins()); setSpacing(0); connect(d->altLangStrEdit, SIGNAL(signalSelectionChanged(QString)), this, SLOT(slotSelectionChanged(QString))); connect(d->altLangStrEdit, SIGNAL(signalModified(QString,QString)), this, SLOT(slotCaptionModified(QString,QString))); connect(d->altLangStrEdit, SIGNAL(signalValueAdded(QString,QString)), this, SLOT(slotAddValue(QString,QString))); connect(d->altLangStrEdit, SIGNAL(signalValueDeleted(QString)), this, SLOT(slotDeleteValue(QString))); connect(d->authorEdit, SIGNAL(textChanged(QString)), this, SLOT(slotAuthorChanged(QString))); } CaptionEdit::~CaptionEdit() { delete d; } void CaptionEdit::reset() { d->altLangStrEdit->reset(); d->authorEdit->blockSignals(true); d->authorEdit->clear(); d->authorEdit->blockSignals(false); d->captionsValues.clear(); } QString CaptionEdit::currentLanguageCode() const { return d->altLangStrEdit->currentLanguageCode(); } void CaptionEdit::setCurrentLanguageCode(const QString& lang) { - if(d->altLangStrEdit->currentLanguageCode().isEmpty()) + if (d->altLangStrEdit->currentLanguageCode().isEmpty()) { d->altLangStrEdit->setCurrentLanguageCode(QLatin1String("x-default")); } else { d->altLangStrEdit->setCurrentLanguageCode(lang); } } void CaptionEdit::slotAddValue(const QString& lang, const QString& text) { CaptionValues val; val.caption = text; val.author = d->authorEdit->text(); val.date = QDateTime::currentDateTime(); // The user may have removed the text and directly entered a new one. Do not drop author then. if (val.author.isEmpty() && d->lastDeletedLanguage == lang) { val.author = d->lastDeletedValues.author; d->authorEdit->blockSignals(true); d->authorEdit->setText(val.author); d->authorEdit->blockSignals(false); } d->lastDeletedLanguage.clear(); d->captionsValues.insert(lang, val); emit signalModified(); } void CaptionEdit::slotCaptionModified(const QString& lang, const QString& text) { slotAddValue(lang, text); } void CaptionEdit::slotDeleteValue(const QString& lang) { d->lastDeletedLanguage = lang; d->lastDeletedValues = d->captionsValues.value(lang); d->captionsValues.remove(lang); d->authorEdit->blockSignals(true); d->authorEdit->clear(); d->authorEdit->blockSignals(false); emit signalModified(); } void CaptionEdit::slotSelectionChanged(const QString& lang) { QString author = d->captionsValues.value(lang).author; d->authorEdit->blockSignals(true); d->authorEdit->setText(author); d->authorEdit->blockSignals(false); } void CaptionEdit::setValues(const CaptionsMap& values) { d->lastDeletedLanguage.clear(); d->captionsValues = values; d->altLangStrEdit->setValues(d->captionsValues.toAltLangMap()); slotSelectionChanged(d->altLangStrEdit->currentLanguageCode()); } CaptionsMap& CaptionEdit::values() const { return d->captionsValues; } void CaptionEdit::slotAuthorChanged(const QString& text) { CaptionValues captionValues = d->captionsValues.value(d->altLangStrEdit->currentLanguageCode()); if (text != captionValues.author) { d->altLangStrEdit->addCurrent(); } } QTextEdit* CaptionEdit::textEdit() const { return d->altLangStrEdit->textEdit(); } } // namespace Digikam diff --git a/core/libs/properties/itempropertiescolorstab.cpp b/core/libs/properties/itempropertiescolorstab.cpp index 89fd568c85..f84adf2b12 100644 --- a/core/libs/properties/itempropertiescolorstab.cpp +++ b/core/libs/properties/itempropertiescolorstab.cpp @@ -1,746 +1,746 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2004-11-17 * Description : a tab to display item colors information * * 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 "itempropertiescolorstab.h" // C++ includes #include // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include #include // Local includes #include "dlayoutbox.h" #include "dimg.h" #include "imagehistogram.h" #include "histogramwidget.h" #include "histogrambox.h" #include "colorgradientwidget.h" #include "sharedloadsavethread.h" #include "iccprofilewidget.h" #include "cietonguewidget.h" #include "itempropertiestxtlabel.h" #include "digikam_globals.h" namespace Digikam { class Q_DECL_HIDDEN ItemPropertiesColorsTab::Private { public: enum MetadataTab { HISTOGRAM=0, ICCPROFILE }; public: explicit Private() : minInterv(0), maxInterv(0), labelMeanValue(0), labelPixelsValue(0), labelStdDevValue(0), labelCountValue(0), labelMedianValue(0), labelPercentileValue(0), labelColorDepth(0), labelAlphaChannel(0), labelImageRegion(0), iccProfileWidget(0), imageLoaderThread(0), histogramBox(0), redHistogram(0), greenHistogram(0), blueHistogram(0) { } public: QSpinBox* minInterv; QSpinBox* maxInterv; DTextLabelValue* labelMeanValue; DTextLabelValue* labelPixelsValue; DTextLabelValue* labelStdDevValue; DTextLabelValue* labelCountValue; DTextLabelValue* labelMedianValue; DTextLabelValue* labelPercentileValue; DTextLabelValue* labelColorDepth; DTextLabelValue* labelAlphaChannel; DTextLabelValue* labelImageRegion; QString currentFilePath; LoadingDescription currentLoadingDescription; QRect selectionArea; IccProfile embeddedProfile; DImg image; DImg imageSelection; ICCProfileWidget* iccProfileWidget; SharedLoadSaveThread* imageLoaderThread; HistogramBox* histogramBox; HistogramWidget* redHistogram; HistogramWidget* greenHistogram; HistogramWidget* blueHistogram; }; ItemPropertiesColorsTab::ItemPropertiesColorsTab(QWidget* const parent) : QTabWidget(parent), d(new Private) { // Histogram tab area ----------------------------------------------------- QScrollArea* const sv = new QScrollArea(this); sv->setFrameStyle(QFrame::NoFrame); sv->setWidgetResizable(true); QWidget* const histogramPage = new QWidget(sv->viewport()); QGridLayout* const topLayout = new QGridLayout(histogramPage); sv->setWidget(histogramPage); // ------------------------------------------------------------- DVBox* const histoBox = new DVBox(histogramPage); d->histogramBox = new HistogramBox(histoBox, LRGBAC, true); d->histogramBox->setStatisticsVisible(false); QLabel* const space = new QLabel(histoBox); space->setFixedHeight(1); // ------------------------------------------------------------- QHBoxLayout* const hlay3 = new QHBoxLayout(); QLabel* const label3 = new QLabel(i18n("Range:"), histogramPage); label3->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); d->minInterv = new QSpinBox(histogramPage); d->minInterv->setRange(0, 255); d->minInterv->setSingleStep(1); d->minInterv->setValue(0); d->minInterv->setWhatsThis( i18n("Select the minimal intensity " "value of the histogram selection here.")); d->maxInterv = new QSpinBox(histogramPage); d->minInterv->setRange(0, 255); d->minInterv->setSingleStep(1); d->maxInterv->setValue(255); d->minInterv->setWhatsThis( i18n("Select the maximal intensity value " "of the histogram selection here.")); hlay3->addWidget(label3); hlay3->addWidget(d->minInterv); hlay3->addWidget(d->maxInterv); // ------------------------------------------------------------- QGroupBox* const gbox = new QGroupBox(i18n("Statistics"), histogramPage); gbox->setWhatsThis(i18n("Here you can see the statistical results calculated from the " "selected histogram part. These values are available for all " "channels.")); QGridLayout* const grid = new QGridLayout(gbox); DTextLabelName* const label5 = new DTextLabelName(i18n("Pixels: "), gbox); d->labelPixelsValue = new DTextLabelValue(QString(), gbox); DTextLabelName* const label7 = new DTextLabelName(i18n("Count: "), gbox); d->labelCountValue = new DTextLabelValue(QString(), gbox); DTextLabelName* const label4 = new DTextLabelName(i18n("Mean: "), gbox); d->labelMeanValue = new DTextLabelValue(QString(), gbox); DTextLabelName* const label6 = new DTextLabelName(i18n("Std. deviation: "), gbox); d->labelStdDevValue = new DTextLabelValue(QString(), gbox); DTextLabelName* const label8 = new DTextLabelName(i18n("Median: "), gbox); d->labelMedianValue = new DTextLabelValue(QString(), gbox); DTextLabelName* const label9 = new DTextLabelName(i18n("Percentile: "), gbox); d->labelPercentileValue = new DTextLabelValue(QString(), gbox); DTextLabelName* const label10 = new DTextLabelName(i18n("Color depth: "), gbox); d->labelColorDepth = new DTextLabelValue(QString(), gbox); DTextLabelName* const label11 = new DTextLabelName(i18n("Alpha Channel: "), gbox); d->labelAlphaChannel = new DTextLabelValue(QString(), gbox); DTextLabelName* const label12 = new DTextLabelName(i18n("Source: "), gbox); d->labelImageRegion = new DTextLabelValue(QString(), gbox); const int spacing = QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); grid->addWidget(label5, 0, 0, 1, 1); grid->addWidget(d->labelPixelsValue, 0, 1, 1, 1); grid->addWidget(label7, 1, 0, 1, 1); grid->addWidget(d->labelCountValue, 1, 1, 1, 1); grid->addWidget(label4, 2, 0, 1, 1); grid->addWidget(d->labelMeanValue, 2, 1, 1, 1); grid->addWidget(label6, 3, 0, 1, 1); grid->addWidget(d->labelStdDevValue, 3, 1, 1, 1); grid->addWidget(label8, 4, 0, 1, 1); grid->addWidget(d->labelMedianValue, 4, 1, 1, 1); grid->addWidget(label9, 5, 0, 1, 1); grid->addWidget(d->labelPercentileValue, 5, 1, 1, 1); grid->addWidget(label10, 6, 0, 1, 1); grid->addWidget(d->labelColorDepth, 6, 1, 1, 1); grid->addWidget(label11, 7, 0, 1, 1); grid->addWidget(d->labelAlphaChannel, 7, 1, 1, 1); grid->addWidget(label12, 8, 0, 1, 1); grid->addWidget(d->labelImageRegion, 8, 1, 1, 1); grid->setContentsMargins(spacing, spacing, spacing, spacing); grid->setSpacing(0); grid->setColumnStretch(0, 10); grid->setColumnStretch(1, 10); // ------------------------------------------------------------- d->redHistogram = new HistogramWidget(256, 100, histogramPage); d->greenHistogram = new HistogramWidget(256, 100, histogramPage); d->blueHistogram = new HistogramWidget(256, 100, histogramPage); d->redHistogram->setChannelType(RedChannel); d->redHistogram->setStatisticsVisible(true); d->greenHistogram->setChannelType(GreenChannel); d->greenHistogram->setStatisticsVisible(true); d->blueHistogram->setChannelType(BlueChannel); d->blueHistogram->setStatisticsVisible(true); // ------------------------------------------------------------- topLayout->addWidget(histoBox, 0, 0, 2, 4); topLayout->addLayout(hlay3, 2, 0, 1, 4); topLayout->addWidget(gbox, 3, 0, 1, 4); topLayout->addWidget(d->redHistogram, 4, 0, 1, 4); topLayout->addWidget(d->greenHistogram, 5, 0, 1, 4); topLayout->addWidget(d->blueHistogram, 6, 0, 1, 4); topLayout->setRowStretch(7, 10); topLayout->setColumnStretch(2, 10); topLayout->setContentsMargins(spacing, spacing, spacing, spacing); topLayout->setSpacing(spacing); insertTab(Private::HISTOGRAM, sv, i18n("Histogram")); // ICC Profiles tab area --------------------------------------- QScrollArea* const sv2 = new QScrollArea(this); sv2->setFrameStyle(QFrame::NoFrame); sv2->setWidgetResizable(true); d->iccProfileWidget = new ICCProfileWidget(sv2->viewport()); sv2->setWidget(d->iccProfileWidget); insertTab(Private::ICCPROFILE, sv2, i18n("ICC profile")); // ------------------------------------------------------------- // histogramBox connections connect(d->histogramBox->histogram(), SIGNAL(signalIntervalChanged(int,int)), this, SLOT(slotUpdateInterval(int,int))); connect(d->redHistogram, SIGNAL(signalIntervalChanged(int,int)), this, SLOT(slotUpdateIntervalFromRGB(int,int))); connect(d->greenHistogram, SIGNAL(signalIntervalChanged(int,int)), this, SLOT(slotUpdateIntervalFromRGB(int,int))); connect(d->blueHistogram, SIGNAL(signalIntervalChanged(int,int)), this, SLOT(slotUpdateIntervalFromRGB(int,int))); connect(d->histogramBox->histogram(), SIGNAL(signalMaximumValueChanged(int)), this, SLOT(slotUpdateIntervRange(int))); connect(d->histogramBox->histogram(), SIGNAL(signalHistogramComputationDone(bool)), this, SLOT(slotRefreshOptions())); connect(d->histogramBox->histogram(), SIGNAL(signalHistogramComputationFailed()), this, SLOT(slotHistogramComputationFailed())); connect(d->histogramBox, SIGNAL(signalChannelChanged(ChannelType)), this, SLOT(slotChannelChanged())); connect(d->histogramBox, SIGNAL(signalScaleChanged(HistogramScale)), this, SLOT(slotScaleChanged())); // ------------------------------------------------------------- connect(d->minInterv, SIGNAL(valueChanged(int)), this, SLOT(slotMinValueChanged(int))); connect(d->maxInterv, SIGNAL(valueChanged(int)), this, SLOT(slotMaxValueChanged(int))); } ItemPropertiesColorsTab::~ItemPropertiesColorsTab() { // If there is a currently histogram computation when dialog is closed, // stop it before the d->image data are deleted automatically! d->histogramBox->histogram()->stopHistogramComputation(); d->redHistogram->stopHistogramComputation(); d->greenHistogram->stopHistogramComputation(); d->blueHistogram->stopHistogramComputation(); if (d->imageLoaderThread) { delete d->imageLoaderThread; } delete d; } void ItemPropertiesColorsTab::readSettings(const KConfigGroup& group) { setCurrentIndex(group.readEntry("ImagePropertiesColors Tab", (int)Private::HISTOGRAM)); d->iccProfileWidget->setMode(group.readEntry("ICC Level", (int)ICCProfileWidget::CUSTOM)); d->iccProfileWidget->setCurrentItemByKey(group.readEntry("Current ICC Item", QString())); d->histogramBox->setChannel((ChannelType)group.readEntry("Histogram Channel", (int)LuminosityChannel)); d->histogramBox->setScale((HistogramScale)group.readEntry("Histogram Scale", (int)LogScaleHistogram)); } void ItemPropertiesColorsTab::writeSettings(KConfigGroup& group) { group.writeEntry("ImagePropertiesColors Tab", currentIndex()); group.writeEntry("Histogram Channel", (int)d->histogramBox->channel()); group.writeEntry("Histogram Scale", (int)d->histogramBox->scale()); group.writeEntry("ICC Level", d->iccProfileWidget->getMode()); group.writeEntry("Current ICC Item", d->iccProfileWidget->getCurrentItemKey()); } void ItemPropertiesColorsTab::setData(const QUrl& url, const QRect& selectionArea, DImg* const img) { // We might be getting duplicate events from AlbumIconView, // which will cause all sorts of duplicate work. // More importantly, while the loading thread can handle this pretty well, // this will completely mess up the timing of progress info in the histogram widget. // So filter here, before the stopHistogramComputation! // But do not filter if current path is null, as it would not disable the widget on first run. if (!img && !d->currentFilePath.isNull() && url.toLocalFile() == d->currentFilePath) { return; } // This is necessary to stop computation because d->image.bits() is currently used by // threaded histogram algorithm. d->histogramBox->histogram()->stopHistogramComputation(); d->redHistogram->stopHistogramComputation(); d->greenHistogram->stopHistogramComputation(); d->blueHistogram->stopHistogramComputation(); d->currentFilePath.clear(); d->currentLoadingDescription = LoadingDescription(); d->iccProfileWidget->loadFromURL(QUrl()); // Clear information. d->labelMeanValue->setAdjustedText(); d->labelPixelsValue->setAdjustedText(); d->labelStdDevValue->setAdjustedText(); d->labelCountValue->setAdjustedText(); d->labelMedianValue->setAdjustedText(); d->labelPercentileValue->setAdjustedText(); d->labelColorDepth->setAdjustedText(); d->labelAlphaChannel->setAdjustedText(); if (url.isEmpty()) { setEnabled(false); d->image.reset(); return; } d->selectionArea = selectionArea; d->image.reset(); setEnabled(true); if (!img) { loadImageFromUrl(url); } else { d->image = img->copy(); - if ( !d->image.isNull() ) + if (!d->image.isNull()) { getICCData(); // If a selection area is done in Image Editor and if the current image is the same // in Image Editor, then compute too the histogram for this selection. if (d->selectionArea.isValid()) { d->imageSelection = d->image.copy(d->selectionArea); d->histogramBox->histogram()->updateData(d->image, d->imageSelection); d->redHistogram->updateData(d->image, d->imageSelection); d->greenHistogram->updateData(d->image, d->imageSelection); d->blueHistogram->updateData(d->image, d->imageSelection); slotRenderingChanged(ImageSelectionHistogram); updateInformation(); } else { d->histogramBox->histogram()->updateData(d->image); d->redHistogram->updateData(d->image); d->greenHistogram->updateData(d->image); d->blueHistogram->updateData(d->image); slotRenderingChanged(FullImageHistogram); updateInformation(); } } else { d->histogramBox->histogram()->setLoadingFailed(); d->redHistogram->setLoadingFailed(); d->greenHistogram->setLoadingFailed(); d->blueHistogram->setLoadingFailed(); d->iccProfileWidget->setLoadingFailed(); slotHistogramComputationFailed(); } } } void ItemPropertiesColorsTab::loadImageFromUrl(const QUrl& url) { // create thread on demand if (!d->imageLoaderThread) { d->imageLoaderThread = new SharedLoadSaveThread(); connect(d->imageLoaderThread, SIGNAL(signalImageLoaded(LoadingDescription,DImg)), this, SLOT(slotLoadImageFromUrlComplete(LoadingDescription,DImg))); connect(d->imageLoaderThread, SIGNAL(signalMoreCompleteLoadingAvailable(LoadingDescription,LoadingDescription)), this, SLOT(slotMoreCompleteLoadingAvailable(LoadingDescription,LoadingDescription))); } LoadingDescription desc = LoadingDescription(url.toLocalFile()); if (DImg::fileFormat(desc.filePath) == DImg::RAW) { // use raw settings optimized for speed DRawDecoding rawDecodingSettings = DRawDecoding(); rawDecodingSettings.optimizeTimeLoading(); desc = LoadingDescription(desc.filePath, rawDecodingSettings, LoadingDescription::RawDecodingTimeOptimized); } if (d->currentLoadingDescription.equalsOrBetterThan(desc)) { return; } d->currentFilePath = desc.filePath; d->currentLoadingDescription = desc; d->imageLoaderThread->load(d->currentLoadingDescription, SharedLoadSaveThread::AccessModeRead, SharedLoadSaveThread::LoadingPolicyFirstRemovePrevious); d->histogramBox->histogram()->setDataLoading(); d->redHistogram->setDataLoading(); d->greenHistogram->setDataLoading(); d->blueHistogram->setDataLoading(); d->iccProfileWidget->setDataLoading(); } void ItemPropertiesColorsTab::slotLoadImageFromUrlComplete(const LoadingDescription& loadingDescription, const DImg& img) { // Discard any leftover messages from previous, possibly aborted loads - if ( !loadingDescription.equalsOrBetterThan(d->currentLoadingDescription) ) + if (!loadingDescription.equalsOrBetterThan(d->currentLoadingDescription)) { return; } - if ( !img.isNull() ) + if (!img.isNull()) { d->histogramBox->histogram()->updateData(img); d->redHistogram->updateData(img); d->greenHistogram->updateData(img); d->blueHistogram->updateData(img); // As a safety precaution, this must be changed only after updateData is called, // which stops computation because d->image.bits() is currently used by threaded histogram algorithm. d->image = img; updateInformation(); getICCData(); } else { d->histogramBox->histogram()->setLoadingFailed(); d->redHistogram->setLoadingFailed(); d->greenHistogram->setLoadingFailed(); d->blueHistogram->setLoadingFailed(); d->iccProfileWidget->setLoadingFailed(); slotHistogramComputationFailed(); } } void ItemPropertiesColorsTab::slotMoreCompleteLoadingAvailable(const LoadingDescription& oldLoadingDescription, const LoadingDescription& newLoadingDescription) { if (oldLoadingDescription == d->currentLoadingDescription && newLoadingDescription.equalsOrBetterThan(d->currentLoadingDescription)) { // Yes, we do want to stop our old time-optimized loading and chain to the current, more complete loading. // Even the time-optimized raw loading takes significant time, and we must avoid two Raw engine instances running // at a time. d->currentLoadingDescription = newLoadingDescription; d->imageLoaderThread->load(newLoadingDescription, SharedLoadSaveThread::AccessModeRead, SharedLoadSaveThread::LoadingPolicyFirstRemovePrevious); } } void ItemPropertiesColorsTab::setSelection(const QRect& selectionArea) { if (selectionArea == d->selectionArea) { return; } // This is necessary to stop computation because d->image.bits() is currently used by // threaded histogram algorithm. d->histogramBox->histogram()->stopHistogramComputation(); d->redHistogram->stopHistogramComputation(); d->greenHistogram->stopHistogramComputation(); d->blueHistogram->stopHistogramComputation(); d->selectionArea = selectionArea; if (d->selectionArea.isValid()) { d->imageSelection = d->image.copy(d->selectionArea); d->histogramBox->histogram()->updateSelectionData(d->imageSelection); d->redHistogram->updateSelectionData(d->imageSelection); d->greenHistogram->updateSelectionData(d->imageSelection); d->blueHistogram->updateSelectionData(d->imageSelection); slotRenderingChanged(ImageSelectionHistogram); } else { d->imageSelection.reset(); slotRenderingChanged(FullImageHistogram); } } void ItemPropertiesColorsTab::slotRefreshOptions() { slotChannelChanged(); slotScaleChanged(); if (d->selectionArea.isValid()) { slotRenderingChanged(ImageSelectionHistogram); } } void ItemPropertiesColorsTab::slotHistogramComputationFailed() { d->imageSelection.reset(); d->image.reset(); } void ItemPropertiesColorsTab::slotChannelChanged() { updateStatistics(); } void ItemPropertiesColorsTab::slotScaleChanged() { HistogramScale scale = d->histogramBox->histogram()->scaleType(); d->redHistogram->setScaleType(scale); d->greenHistogram->setScaleType(scale); d->blueHistogram->setScaleType(scale); updateStatistics(); } void ItemPropertiesColorsTab::slotRenderingChanged(int rendering) { d->histogramBox->histogram()->setRenderingType((HistogramRenderingType)rendering); d->redHistogram->setRenderingType((HistogramRenderingType)rendering); d->greenHistogram->setRenderingType((HistogramRenderingType)rendering); d->blueHistogram->setRenderingType((HistogramRenderingType)rendering); updateStatistics(); } void ItemPropertiesColorsTab::slotMinValueChanged(int min) { // Called when user changes values of spin box. // Communicate the change to histogram widget. // make the one control "push" the other if (min == d->maxInterv->value()+1) { d->maxInterv->setValue(min); } d->maxInterv->setMinimum(min-1); d->histogramBox->histogram()->slotMinValueChanged(min); d->redHistogram->slotMinValueChanged(min); d->greenHistogram->slotMinValueChanged(min); d->blueHistogram->slotMinValueChanged(min); updateStatistics(); } void ItemPropertiesColorsTab::slotMaxValueChanged(int max) { if (max == d->minInterv->value()-1) { d->minInterv->setValue(max); } d->minInterv->setMaximum(max+1); d->histogramBox->histogram()->slotMaxValueChanged(max); d->redHistogram->slotMaxValueChanged(max); d->greenHistogram->slotMaxValueChanged(max); d->blueHistogram->slotMaxValueChanged(max); updateStatistics(); } void ItemPropertiesColorsTab::slotUpdateIntervalFromRGB(int min, int max) { d->histogramBox->histogram()->slotMinValueChanged(min); d->histogramBox->histogram()->slotMaxValueChanged(max); slotUpdateInterval(min, max); } void ItemPropertiesColorsTab::slotUpdateInterval(int min, int max) { // Called when value is set from within histogram widget. // Block signals to prevent slotMinValueChanged and // slotMaxValueChanged being called. d->minInterv->blockSignals(true); d->minInterv->setMaximum(max+1); d->minInterv->setValue(min); d->minInterv->blockSignals(false); d->maxInterv->blockSignals(true); d->maxInterv->setMinimum(min-1); d->maxInterv->setValue(max); d->maxInterv->blockSignals(false); d->redHistogram->slotMinValueChanged(min); d->redHistogram->slotMaxValueChanged(max); d->greenHistogram->slotMinValueChanged(min); d->greenHistogram->slotMaxValueChanged(max); d->blueHistogram->slotMinValueChanged(min); d->blueHistogram->slotMaxValueChanged(max); updateStatistics(); } void ItemPropertiesColorsTab::slotUpdateIntervRange(int range) { d->maxInterv->setMaximum( range ); } void ItemPropertiesColorsTab::updateInformation() { d->labelColorDepth->setAdjustedText(d->image.sixteenBit() ? i18n("16 bits") : i18n("8 bits")); d->labelAlphaChannel->setAdjustedText(d->image.hasAlpha() ? i18n("Yes") : i18n("No")); } void ItemPropertiesColorsTab::updateStatistics() { ImageHistogram* const renderedHistogram = d->histogramBox->histogram()->currentHistogram(); if (!renderedHistogram) { return; } QString value; int min = d->minInterv->value(); int max = d->maxInterv->value(); int channel = d->histogramBox->channel(); HistogramRenderingType type = d->histogramBox->histogram()->renderingType(); - if ( channel == ColorChannels ) + if (channel == ColorChannels) { channel = LuminosityChannel; } double mean = renderedHistogram->getMean(channel, min, max); d->labelMeanValue->setAdjustedText(value.setNum(mean, 'f', 1)); double pixels = renderedHistogram->getPixels(); d->labelPixelsValue->setAdjustedText(value.setNum((float)pixels, 'f', 0)); double stddev = renderedHistogram->getStdDev(channel, min, max); d->labelStdDevValue->setAdjustedText(value.setNum(stddev, 'f', 1)); double counts = renderedHistogram->getCount(channel, min, max); d->labelCountValue->setAdjustedText(value.setNum((float)counts, 'f', 0)); double median = renderedHistogram->getMedian(channel, min, max); d->labelMedianValue->setAdjustedText(value.setNum(median, 'f', 1)); double percentile = (pixels > 0 ? (100.0 * counts / pixels) : 0.0); d->labelPercentileValue->setAdjustedText(value.setNum(percentile, 'f', 1)); d->labelImageRegion->setAdjustedText((type == FullImageHistogram) ? i18n("Full Image") : i18n("Image Region")); } void ItemPropertiesColorsTab::getICCData() { if (DImg::fileFormat(d->currentFilePath) == DImg::RAW) { d->iccProfileWidget->setUncalibratedColor(); } else if (!d->image.getIccProfile().isNull()) { d->embeddedProfile = d->image.getIccProfile(); d->iccProfileWidget->loadProfile(d->currentFilePath, d->embeddedProfile); } else { d->iccProfileWidget->setLoadingFailed(); } } } // namespace Digikam diff --git a/core/libs/tags/engine/tagmodificationhelper.cpp b/core/libs/tags/engine/tagmodificationhelper.cpp index d0d0d070fb..13a4031c45 100644 --- a/core/libs/tags/engine/tagmodificationhelper.cpp +++ b/core/libs/tags/engine/tagmodificationhelper.cpp @@ -1,711 +1,711 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2000-12-05 * Description : helper class used to modify tag albums in views * * Copyright (C) 2009-2010 by Johannes Wienke * Copyright (C) 2010-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 "tagmodificationhelper.h" // Qt includes #include #include #include // KDE includes #include // Local includes #include "digikam_debug.h" #include "album.h" #include "albumpointer.h" #include "coredb.h" #include "coredbtransaction.h" #include "iteminfo.h" #include "itemtagpair.h" #include "metadatahub.h" #include "scancontroller.h" #include "statusprogressbar.h" #include "tagsactionmngr.h" #include "tagproperties.h" #include "tageditdlg.h" #include "facetags.h" #include "facedbaccess.h" #include "facedb.h" namespace Digikam { class Q_DECL_HIDDEN TagModificationHelper::Private { public: explicit Private() { parentTag = 0; dialogParent = 0; } AlbumPointer parentTag; QWidget* dialogParent; }; TagModificationHelper::TagModificationHelper(QObject* const parent, QWidget* const dialogParent) : QObject(parent), d(new Private) { d->dialogParent = dialogParent; } TagModificationHelper::~TagModificationHelper() { delete d; } void TagModificationHelper::bindTag(QAction* action, TAlbum* album) const { action->setData(QVariant::fromValue(AlbumPointer(album))); } TAlbum* TagModificationHelper::boundTag(QObject* sender) const { QAction* action = 0; - if ( (action = qobject_cast(sender)) ) + if ((action = qobject_cast(sender))) { return action->data().value >(); } return 0; } void TagModificationHelper::bindMultipleTags(QAction* action, QList tags) { action->setData(QVariant::fromValue(tags)); } QList TagModificationHelper::boundMultipleTags(QObject* sender) { QAction* action = 0; if ((action = qobject_cast(sender))) { return (action->data().value >()); } return QList(); } TAlbum* TagModificationHelper::slotTagNew(TAlbum* parent, const QString& title, const QString& iconName) { // ensure that there is a parent AlbumPointer p(parent); if (!p) { p = AlbumManager::instance()->findTAlbum(0); if (!p) { qCDebug(DIGIKAM_GENERAL_LOG) << "Could not find root tag album"; return 0; } } QString editTitle = title; QString editIconName = iconName; QKeySequence ks; if (title.isEmpty()) { bool doCreate = TagEditDlg::tagCreate(d->dialogParent, p, editTitle, editIconName, ks); if (!doCreate || !p) { return 0; } } QMap errMap; AlbumList tList = TagEditDlg::createTAlbum(p, editTitle, editIconName, ks, errMap); TagEditDlg::showtagsListCreationError(d->dialogParent, errMap); if (errMap.isEmpty() && !tList.isEmpty()) { TAlbum* const tag = static_cast(tList.last()); emit tagCreated(tag); return tag; } else { return 0; } } TAlbum* TagModificationHelper::slotTagNew() { return slotTagNew(boundTag(sender())); } void TagModificationHelper::slotTagEdit(TAlbum* t) { if (!t) { return; } AlbumPointer tag(t); QString title, icon; QKeySequence ks; bool doEdit = TagEditDlg::tagEdit(d->dialogParent, tag, title, icon, ks); if (!doEdit || !tag) { return; } if (tag && tag->title() != title) { QString errMsg; if (AlbumManager::instance()->renameTAlbum(tag, title, errMsg)) { // TODO: make an option to edit the full name of a face tag if (FaceTags::isPerson(tag->id())) { TagProperties props(tag->id()); props.setProperty(TagPropertyName::person(), title); props.setProperty(TagPropertyName::faceEngineName(), title); } } else { QMessageBox::critical(qApp->activeWindow(), qApp->applicationName(), errMsg); } } if (tag && tag->icon() != icon) { QString errMsg; if (!AlbumManager::instance()->updateTAlbumIcon(tag, icon, 0, errMsg)) { QMessageBox::critical(qApp->activeWindow(), qApp->applicationName(), errMsg); } } if (tag && tag->property(TagPropertyName::tagKeyboardShortcut()) != ks.toString()) { TagsActionMngr::defaultManager()->updateTagShortcut(tag->id(), ks); } emit tagEdited(tag); } void TagModificationHelper::slotTagEdit() { slotTagEdit(boundTag(sender())); } void TagModificationHelper::slotTagDelete(TAlbum* t) { if (!t || t->isRoot()) { return; } AlbumPointer tag(t); // find number of subtags int children = 0; AlbumIterator iter(tag); while (iter.current()) { ++children; ++iter; } // ask for deletion of children if (children) { int result = QMessageBox::warning(d->dialogParent, qApp->applicationName(), i18np("Tag '%2' has one subtag. " "Deleting this will also delete " "the subtag.\n" "Do you want to continue?", "Tag '%2' has %1 subtags. " "Deleting this will also delete " "the subtags.\n" "Do you want to continue?", children, tag->title()), QMessageBox::Yes | QMessageBox::Cancel); if (result != QMessageBox::Yes || !tag) { return; } } QString message; QList assignedItems = CoreDbAccess().db()->getItemIDsInTag(tag->id()); if (!assignedItems.isEmpty()) { message = i18np("Tag '%2' is assigned to one item. " "Do you want to continue?", "Tag '%2' is assigned to %1 items. " "Do you want to continue?", assignedItems.count(), tag->title()); } else { message = i18n("Delete '%1' tag?", tag->title()); } int result = QMessageBox::warning(qApp->activeWindow(), i18n("Delete Tag"), message, QMessageBox::Yes | QMessageBox::Cancel); if (result == QMessageBox::Yes && tag) { emit aboutToDeleteTag(tag); QString errMsg; if (!AlbumManager::instance()->deleteTAlbum(tag, errMsg)) { QMessageBox::critical(qApp->activeWindow(), qApp->applicationName(), errMsg); } } } void TagModificationHelper::slotTagDelete() { slotTagDelete(boundTag(sender())); } void TagModificationHelper::slotMultipleTagDel(QList& tags) { QString tagWithChildrens; QString tagWithoutImages; QString tagWithImages; QMultiMap sortedTags; foreach (TAlbum* const t, tags) { if (!t || t->isRoot()) { continue; } AlbumPointer tag(t); // find number of subtags int children = 0; AlbumIterator iter(tag); while (iter.current()) { ++children; ++iter; } if (children) tagWithChildrens.append(tag->title() + QLatin1Char(' ')); QList assignedItems = CoreDbAccess().db()->getItemIDsInTag(tag->id()); if (!assignedItems.isEmpty()) { tagWithImages.append(tag->title() + QLatin1Char(' ')); } else { tagWithoutImages.append(tag->title() + QLatin1Char(' ')); } /** * Tags must be deleted from children to parents, if we don't want * to step on invalid index. Use QMultiMap to order them by distance * to root tag */ Album* parent = t; int depth = 0; while (!parent->isRoot()) { parent = parent->parent(); depth++; } sortedTags.insert(depth, tag); } // ask for deletion of children if (!tagWithChildrens.isEmpty()) { int result = QMessageBox::warning(qApp->activeWindow(), qApp->applicationName(), i18n("Tags '%1' have one or more subtags. " "Deleting them will also delete " "the subtags.\n" "Do you want to continue?", tagWithChildrens), QMessageBox::Yes | QMessageBox::Cancel); if (result != QMessageBox::Yes) { return; } } QString message; if (!tagWithImages.isEmpty()) { message = i18n("Tags '%1' are assigned to one or more items. " "Do you want to continue?", tagWithImages); } else { message = i18n("Delete '%1' tag(s)?", tagWithoutImages); } int result = QMessageBox::warning(qApp->activeWindow(), i18n("Delete Tag"), message, QMessageBox::Yes | QMessageBox::Cancel); if (result == QMessageBox::Yes) { QMultiMap::iterator it; /** * QMultimap doesn't provide reverse iterator, -1 is required * because end() points after the last element */ for (it = sortedTags.end()-1 ; it != sortedTags.begin()-1 ; --it) { emit aboutToDeleteTag(it.value()); QString errMsg; if (!AlbumManager::instance()->deleteTAlbum(it.value(), errMsg)) { QMessageBox::critical(qApp->activeWindow(), qApp->applicationName(), errMsg); } } } } void TagModificationHelper::slotMultipleTagDel() { QList lst = boundMultipleTags(sender()); qCDebug(DIGIKAM_GENERAL_LOG) << lst.size(); slotMultipleTagDel(lst); } void TagModificationHelper::slotFaceTagDelete(TAlbum* t) { QList tag; tag.append(t); slotMultipleFaceTagDel(tag); } void TagModificationHelper::slotFaceTagDelete() { slotFaceTagDelete(boundTag(sender())); } void TagModificationHelper::slotMultipleFaceTagDel(QList& tags) { QString tagsWithChildren; QString tagsWithImages; // We use a set here since else one tag could occur more than once // which could lead to undefined behaviour. QSet allPersonTagsToDelete; int tagsWithChildrenCount = 0; QSet allAssignedItems; int tagsWithImagesCount = 0; foreach (TAlbum* const selectedTag, tags) { if (!selectedTag || selectedTag->isRoot()) { continue; } // find tags and subtags with person property QSet personTagsToDelete = getFaceTags(selectedTag).toSet(); // If there is more than one person tag in the list, // the tag to remove has at least one sub tag that is a face tag. // Thus, we have to warn. // // If there is only one face tag, it is either the original tag, // or it is the only sub tag of the original tag. // Behave, like the face tag itself was selected. if (personTagsToDelete.size() > 1) { if (tagsWithChildrenCount > 0) { tagsWithChildren.append(QLatin1String(",")); } tagsWithChildren.append(selectedTag->title()); ++tagsWithChildrenCount; } // Get the assigned faces for all person tags to delete foreach (TAlbum* const tAlbum, personTagsToDelete) { // If the global set does not yet contain the tag if (!allPersonTagsToDelete.contains(tAlbum)) { QSet assignedItems = CoreDbAccess().db()->getImagesWithImageTagProperty( tAlbum->id(), Digikam::ImageTagPropertyName::tagRegion()).toSet(); assignedItems.unite(CoreDbAccess().db()->getImagesWithImageTagProperty( tAlbum->id(), Digikam::ImageTagPropertyName::autodetectedFace()).toSet()); if (!assignedItems.isEmpty()) { // Add the items to the global set for potential untagging allAssignedItems.unite(assignedItems); if (tagsWithImagesCount > 0) { tagsWithImages.append(QLatin1String(",")); } tagsWithImages.append(tAlbum->title()); ++tagsWithImagesCount; } } } // Add the found tags to the global set. allPersonTagsToDelete.unite(personTagsToDelete); } // ask for deletion of children if (tagsWithChildrenCount) { QString message = i18np("Face tag '%2' has at least one face tag child. " "Deleting it will also delete the children.\n" "Do you want to continue?", "Face tags '%2' have at least one face tag child. " "Deleting it will also delete the children.\n" "Do you want to continue?", tagsWithChildrenCount, tagsWithChildren); bool removeChildren = QMessageBox::Yes == (QMessageBox::warning(qApp->activeWindow(), qApp->applicationName(), message, QMessageBox::Yes | QMessageBox::Cancel)); if (!removeChildren) { return; } } QString message; if (!allAssignedItems.isEmpty()) { message = i18np("Face tag '%2' is assigned to at least one item. " "Do you want to continue?", "Face tags '%2' are assigned to at least one item. " "Do you want to continue?", tagsWithImagesCount, tagsWithImages); } else { message = i18np("Remove face tag?", "Remove face tags?", tags.size()); } bool removeFaceTag = QMessageBox::Yes == (QMessageBox::warning(qApp->activeWindow(), qApp->applicationName(), message, QMessageBox::Yes | QMessageBox::Cancel)); if (removeFaceTag) { // Now we ask the user if we should also remove the tags from the images. QString msg = i18np("Remove the tag corresponding to this face tag from the images?", "Remove the %1 tags corresponding to this face tags from the images?", allPersonTagsToDelete.size()); bool removeTagFromImages = QMessageBox::Yes == (QMessageBox::warning(qApp->activeWindow(), qApp->applicationName(), msg, QMessageBox::Yes | QMessageBox::No)); MetadataHub metadataHub; // remove the face region from images and unassign the tag if wished foreach (const qlonglong& imageId, allAssignedItems) { foreach (TAlbum* const tagToRemove, allPersonTagsToDelete) { ItemTagPair imageTagAssociation(imageId,tagToRemove->id()); if (imageTagAssociation.isAssigned()) { imageTagAssociation.removeProperties(ImageTagPropertyName::autodetectedFace()); imageTagAssociation.removeProperties(ImageTagPropertyName::tagRegion()); if (removeTagFromImages) { imageTagAssociation.unAssignTag(); // Load the current metadata and sync the tags ItemInfo info(imageId); if (!info.isNull()) { metadataHub.load(info); if (!metadataHub.writeToMetadata(info)) { qCWarning(DIGIKAM_GENERAL_LOG) << "Failed writing tags to image " << info.filePath(); } } } } } } foreach (TAlbum* const tAlbum, allPersonTagsToDelete) { TagProperties props(tAlbum->id()); // Delete TagPropertyName::person() and TagPropertyName::faceEngineName() // fetch the UUID to delete the identity from facesdb props.removeProperties(TagPropertyName::person()); props.removeProperties(TagPropertyName::faceEngineName()); QString uuid = props.value(TagPropertyName::faceEngineUuid()); qCDebug(DIGIKAM_GENERAL_LOG) << "Deleting person tag properties for tag " << tAlbum->title() << " with uuid " << uuid; if (!uuid.isEmpty()) { // Delete the UUID props.removeProperties(TagPropertyName::faceEngineUuid()); // delete the faces db identity with this uuid. FaceDbAccess access; access.db()->deleteIdentity(uuid); } } } } void TagModificationHelper::slotMultipleFaceTagDel() { QList lst = boundMultipleTags(sender()); qCDebug(DIGIKAM_GENERAL_LOG) << lst.size(); slotMultipleFaceTagDel(lst); } void TagModificationHelper::slotTagToFaceTag(TAlbum* tAlbum) { if (!tAlbum) { return; } if (!FaceTags::isPerson(tAlbum->id())) { FaceTags::ensureIsPerson(tAlbum->id()); } } void TagModificationHelper::slotTagToFaceTag() { slotTagToFaceTag(boundTag(sender())); } void TagModificationHelper::slotMultipleTagsToFaceTags(QList& tags) { foreach (TAlbum* const selectedTag, tags) { slotTagToFaceTag(selectedTag); } } void TagModificationHelper::slotMultipleTagsToFaceTags() { QList lst = boundMultipleTags(sender()); qCDebug(DIGIKAM_GENERAL_LOG) << lst.size(); slotMultipleTagsToFaceTags(lst); } QList TagModificationHelper::getFaceTags(TAlbum* rootTag) { if (!rootTag) { return QList(); } QList tags; tags.append(rootTag); return getFaceTags(tags).toList(); } QSet TagModificationHelper::getFaceTags(QList tags) { QSet faceTags; foreach (TAlbum* const tAlbum, tags) { if (FaceTags::isPerson(tAlbum->id())) { faceTags.insert(tAlbum); } AlbumPointer tag(tAlbum); AlbumIterator iter(tag); // Get all shild tags which have the person property. while (iter.current()) { Album* const album = iter.current(); // Make sure that no nullp pointer dereference is done. // though while(iter.current()) already tests for the current // album being true, i.e. > 0 , i.e. non-null if (album) { TAlbum* const tAlbum = dynamic_cast(album); if (tAlbum && FaceTags::isPerson(tAlbum->id())) { faceTags.insert(tAlbum); } ++iter; } } } return faceTags; } } // namespace Digikam diff --git a/core/libs/tags/widgets/tagspopupmenu.cpp b/core/libs/tags/widgets/tagspopupmenu.cpp index e5b15bcfd3..4e8465532b 100644 --- a/core/libs/tags/widgets/tagspopupmenu.cpp +++ b/core/libs/tags/widgets/tagspopupmenu.cpp @@ -1,841 +1,841 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2004-09-07 * Description : a pop-up menu implementation to display a * hierarchical view of digiKam tags. * * Copyright (C) 2004 by Renchi Raju * Copyright (C) 2006-2019 by Gilles Caulier * Copyright (C) 2006-2011 by Marcel Wiesweg * * Parts of the drawing code are inspired by from Trolltech ASA implementation. * * 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 "tagspopupmenu.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include // Local includes #include "digikam_debug.h" #include "album.h" #include "coredb.h" #include "albummanager.h" #include "albumthumbnailloader.h" #include "tageditdlg.h" #include "tagscache.h" namespace Digikam { class Q_DECL_HIDDEN TagToggleAction : public QWidgetAction { public: TagToggleAction(const QString& text, QObject* const parent); TagToggleAction(const QIcon& icon, const QString& text, QObject* const parent); virtual QWidget* createWidget(QWidget* parent); void setSpecialChecked(bool checked); bool isChecked() const; void setCheckBoxHidden(bool hidden); bool isCheckBoxHidden() const; private: bool m_checked; bool m_checkBoxHidden; }; // ------------------------------------------------------------------------ class Q_DECL_HIDDEN TagToggleMenuWidget : public QWidget { public: TagToggleMenuWidget(QMenu* const parent, TagToggleAction* const action); protected: virtual QSize sizeHint() const; virtual void paintEvent(QPaintEvent*); private: void initMenuStyleOption(QStyleOptionMenuItem* option) const; void initViewStyleOption(QStyleOptionViewItem* option) const; QSize menuItemSize(QStyleOptionMenuItem* opt) const; QRect checkIndicatorSize(QStyleOption* option) const; private: QMenu* m_menu; TagToggleAction* m_action; }; // ------------------------------------------------------------------------ TagToggleMenuWidget::TagToggleMenuWidget(QMenu* const parent, TagToggleAction* const action) : QWidget(parent) { m_menu = parent; m_action = action; setMouseTracking(style()->styleHint(QStyle::SH_Menu_MouseTracking, 0, this)); } QSize TagToggleMenuWidget::sizeHint() const { // init style option for menu item QStyleOptionMenuItem opt; initMenuStyleOption(&opt); // get the individual sizes QSize menuSize = menuItemSize(&opt); QRect checkRect = checkIndicatorSize(&opt); const int margin = style()->pixelMetric(QStyle::PM_FocusFrameHMargin, 0, this) + 1; // return widget size int width = margin + checkRect.width() + menuSize.width() + margin; QSize size(width, qMax(checkRect.height(), menuSize.height())); return size; } void TagToggleMenuWidget::paintEvent(QPaintEvent*) { // init style option for menu item QStyleOptionMenuItem menuOpt; initMenuStyleOption(&menuOpt); // init style option for check indicator QStyleOptionViewItem viewOpt; initViewStyleOption(&viewOpt); // get a suitable margin const int margin = style()->pixelMetric(QStyle::PM_FocusFrameHMargin, 0, this); const int frameMargin = style()->pixelMetric(QStyle::PM_MenuDesktopFrameWidth, 0, this); // create painter QPainter p(this); // the menu rect should not go beyond the parent menu in width // move by margin and free room for menu frame if (menuOpt.direction == Qt::RightToLeft) { // right-to-left untested viewOpt.rect.translate(-margin, 0); menuOpt.rect.translate(-margin, 0); menuOpt.menuRect.adjust(margin, 0, 0, 0); } else { viewOpt.rect.translate(margin, 0); menuOpt.rect.translate(margin, 0); menuOpt.menuRect.adjust(0, 0, -margin, 0); } // clear the background of the check indicator QStyleOptionMenuItem clearOpt(menuOpt); clearOpt.state = QStyle::State_None; clearOpt.menuItemType = QStyleOptionMenuItem::EmptyArea; clearOpt.checkType = QStyleOptionMenuItem::NotCheckable; clearOpt.rect = viewOpt.rect; style()->drawControl(QStyle::CE_MenuEmptyArea, &menuOpt, &p, this); // draw a check indicator like the one used in a treeview QRect checkRect = checkIndicatorSize(&menuOpt); viewOpt.rect = checkRect; if (!m_action->isCheckBoxHidden()) { style()->drawPrimitive(QStyle::PE_IndicatorViewItemCheck, &viewOpt, &p, this); } // move by size of check indicator if (menuOpt.direction == Qt::RightToLeft) { menuOpt.rect.translate( - checkRect.width() - margin, 0); menuOpt.rect.adjust( checkRect.width() + margin, 0, 0, 0); } else { menuOpt.rect.translate(checkRect.right() + margin, 0); menuOpt.rect.adjust(0, 0, - checkRect.right() - margin, 0); } // draw a full menu item - icon, text and menu indicator style()->drawControl(QStyle::CE_MenuItem, &menuOpt, &p, this); // draw the frame on the right if (frameMargin) { QRegion borderReg; - borderReg += QRect( width() - frameMargin, 0, frameMargin, height() ); // right + borderReg += QRect(width() - frameMargin, 0, frameMargin, height()); // right p.setClipRegion(borderReg); QStyleOptionFrame frame; frame.rect = rect(); frame.palette = palette(); frame.state = QStyle::State_None; frame.lineWidth = style()->pixelMetric(QStyle::PM_MenuPanelWidth); frame.midLineWidth = 0; style()->drawPrimitive(QStyle::PE_FrameMenu, &frame, &p, this); } } void TagToggleMenuWidget::initMenuStyleOption(QStyleOptionMenuItem* option) const { // set basic option from widget properties option->initFrom(this); // set menu item state option->state = QStyle::State_None; option->state |= QStyle::State_Enabled; if (m_menu->activeAction() == m_action) // if hovered etc. { option->state |= QStyle::State_Selected; } // if (mouseDown) option->state |= QStyle::State_Sunken; // We have a special case here: menu items which are checked are not selectable, // it is an "Assign Tags" menu. To signal this, we change the pallette. // But only if there is no submenu... if (m_action->isChecked() && !m_action->menu()) { option->palette.setCurrentColorGroup(QPalette::Disabled); option->state &= ~QStyle::State_Enabled; } // set options from m_action option->font = m_action->font(); option->icon = m_action->icon(); option->text = m_action->text(); // we do the check mark ourselves option->checked = false; option->menuHasCheckableItems = false; option->checkType = QStyleOptionMenuItem::NotCheckable; // Don't forget the submenu indicator if (m_action->menu()) { option->menuItemType = QStyleOptionMenuItem::SubMenu; } else { option->menuItemType = QStyleOptionMenuItem::Normal; } // seems QMenu does it like this option->maxIconWidth = style()->pixelMetric(QStyle::PM_SmallIconSize, 0, this); option->rect = rect(); option->menuRect = parentWidget()->rect(); } void TagToggleMenuWidget::initViewStyleOption(QStyleOptionViewItem* option) const { // set basic option from widget properties option->initFrom(this); // set check state if (m_action->isChecked()) { option->state |= QStyle::State_On; } else { option->state |= QStyle::State_Off; } } QSize TagToggleMenuWidget::menuItemSize(QStyleOptionMenuItem* opt) const { QSize size; QFontMetrics fm(fontMetrics()); size.setWidth(fm.width(m_action->text())); size.setHeight(fm.height()); if (!m_action->icon().isNull()) { if (size.height() < opt->maxIconWidth) { size.setHeight(opt->maxIconWidth); } } return style()->sizeFromContents(QStyle::CT_MenuItem, opt, size, this); } QRect TagToggleMenuWidget::checkIndicatorSize(QStyleOption* option) const { if (m_action->isCheckBoxHidden()) { return QRect(); } QStyleOptionButton opt; opt.QStyleOption::operator=(*option); //opt.rect = bounding; return style()->subElementRect(QStyle::SE_ViewItemCheckIndicator, &opt, this); } // ------------------------------------------------------------------------ TagToggleAction::TagToggleAction(const QString& text, QObject* const parent) : QWidgetAction(parent), m_checked(false), m_checkBoxHidden(false) { setText(text); setCheckable(true); } TagToggleAction::TagToggleAction(const QIcon& icon, const QString& text, QObject* const parent) : QWidgetAction(parent), m_checked(false), m_checkBoxHidden(false) { setIcon(icon); setText(text); setCheckable(true); } QWidget* TagToggleAction::createWidget(QWidget* parent) { QMenu* const menu = qobject_cast(parent); if (menu) { return (new TagToggleMenuWidget(menu, this)); } else { return 0; } } void TagToggleAction::setSpecialChecked(bool checked) { // something is resetting the checked property when there is a submenu. // Use this to store "checked" anyway. // Note: the method isChecked() is not virtual. m_checked = checked; setChecked(checked); } bool TagToggleAction::isChecked() const { return m_checked || QWidgetAction::isChecked(); } void TagToggleAction::setCheckBoxHidden(bool hidden) { m_checkBoxHidden = hidden; } bool TagToggleAction::isCheckBoxHidden() const { return m_checkBoxHidden; } // ------------------------------------------------------------------------ class Q_DECL_HIDDEN TagsPopupMenu::Private { public: explicit Private() { addTagActions = 0; toggleTagActions = 0; mode = ASSIGN; } QPixmap addTagPix; QPixmap recentTagPix; QPixmap tagViewPix; QSet assignedTags; QSet parentAssignedTags; QList selectedImageIDs; QActionGroup* addTagActions; QActionGroup* toggleTagActions; TagsPopupMenu::Mode mode; }; TagsPopupMenu::TagsPopupMenu(qlonglong selectedImageId, Mode mode, QWidget* const parent) : QMenu(parent), d(new Private) { d->selectedImageIDs << selectedImageId; setup(mode); } TagsPopupMenu::TagsPopupMenu(const QList& selectedImageIds, Mode mode, QWidget* const parent) : QMenu(parent), d(new Private) { d->selectedImageIDs = selectedImageIds; setup(mode); } void TagsPopupMenu::setup(Mode mode) { d->mode = mode; d->addTagPix = QIcon::fromTheme(QLatin1String("tag")).pixmap(style()->pixelMetric(QStyle::PM_SmallIconSize)); d->recentTagPix = QIcon::fromTheme(QLatin1String("tag-assigned")).pixmap(style()->pixelMetric(QStyle::PM_SmallIconSize)); d->tagViewPix = QIcon::fromTheme(QLatin1String("edit-text-frame-update")).pixmap(style()->pixelMetric(QStyle::PM_SmallIconSize)); d->addTagActions = new QActionGroup(this); d->toggleTagActions = new QActionGroup(this); setSeparatorsCollapsible(true); connect(d->addTagActions, SIGNAL(triggered(QAction*)), this, SLOT(slotAddTag(QAction*))); connect(d->toggleTagActions, SIGNAL(triggered(QAction*)), this, SLOT(slotToggleTag(QAction*))); connect(this, SIGNAL(aboutToShow()), this, SLOT(slotAboutToShow())); AlbumThumbnailLoader* const loader = AlbumThumbnailLoader::instance(); connect(loader, SIGNAL(signalThumbnail(Album*,QPixmap)), this, SLOT(slotTagThumbnail(Album*,QPixmap))); // we are not interested in signalThumbnailFailed } TagsPopupMenu::~TagsPopupMenu() { delete d; } void TagsPopupMenu::clearPopup() { d->assignedTags.clear(); d->parentAssignedTags.clear(); clear(); } void TagsPopupMenu::slotAboutToShow() { clearPopup(); AlbumManager* const man = AlbumManager::instance(); if (d->mode == REMOVE || d->mode == DISPLAY) { if (d->selectedImageIDs.isEmpty()) { menuAction()->setEnabled(false); return; } d->assignedTags = QSet::fromList(CoreDbAccess().db()->getItemCommonTagIDs(d->selectedImageIDs)); if (d->assignedTags.isEmpty()) { menuAction()->setEnabled(false); return; } // also add the parents of the assigned tags bool hasValidTag = false; - for (QSet::const_iterator it = d->assignedTags.constBegin(); it != d->assignedTags.constEnd(); ++it) + for (QSet::const_iterator it = d->assignedTags.constBegin() ; it != d->assignedTags.constEnd() ; ++it) { TAlbum* const album = man->findTAlbum(*it); if (!album || album->isInternalTag()) { continue; } hasValidTag = true; Album* a = album->parent(); while (a) { d->parentAssignedTags << a->id(); a = a->parent(); } } if (!hasValidTag) { menuAction()->setEnabled(false); return; } } else if (d->mode == ASSIGN) { if (d->selectedImageIDs.count() == 1) { d->assignedTags = QSet::fromList(CoreDbAccess().db()->getItemCommonTagIDs(d->selectedImageIDs)); } } else if (d->mode == RECENTLYASSIGNED) { AlbumList recentTags = man->getRecentlyAssignedTags(); if (recentTags.isEmpty()) { addSection(d->recentTagPix, i18n("No Recently Assigned Tags")); } else { addSection(d->recentTagPix, i18n("Recently Assigned Tags")); - for (AlbumList::const_iterator it = recentTags.constBegin(); - it != recentTags.constEnd(); ++it) + for (AlbumList::const_iterator it = recentTags.constBegin() ; + it != recentTags.constEnd() ; ++it) { TAlbum* const album = static_cast(*it); if (album) { TAlbum* const parent = dynamic_cast (album->parent()); if (parent) { QString t = album->title() + QLatin1String(" (") + parent->prettyUrl() + QLatin1Char(')'); t.replace(QLatin1Char('&'), QLatin1String("&&")); TagToggleAction* action = new TagToggleAction(t, d->toggleTagActions); action->setData(album->id()); action->setCheckBoxHidden(true); setAlbumIcon(action, album); addAction(action); } else { qCDebug(DIGIKAM_GENERAL_LOG) << "Tag" << album << "do not have a valid parent"; } } } } } - if ( (d->mode == REMOVE || d->mode == DISPLAY) && d->assignedTags.count() < 10) + if ((d->mode == REMOVE || d->mode == DISPLAY) && d->assignedTags.count() < 10) { buildFlatMenu(this); } else { TAlbum* const album = man->findTAlbum(0); if (!album) { return; } iterateAndBuildMenu(this, album); if (d->mode == ASSIGN || d->mode == RECENTLYASSIGNED) { addSeparator(); TagToggleAction* const addTag = new TagToggleAction(d->addTagPix, i18n("Add New Tag..."), d->addTagActions); addTag->setData(0); // root id addTag->setCheckBoxHidden(true); addAction(addTag); addSeparator(); TagToggleAction* const moreTag = new TagToggleAction(d->tagViewPix, i18n("More Tags..."), d->addTagActions); moreTag->setData(-1); // special id to query tag view moreTag->setCheckBoxHidden(true); addAction(moreTag); } } } // for qSort bool lessThanByTitle(const Album* first, const Album* second) { return first->title() < second->title(); } void TagsPopupMenu::iterateAndBuildMenu(QMenu* menu, TAlbum* album) { QList sortedTags; - for (Album* a = album->firstChild(); a; a = a->next()) + for (Album* a = album->firstChild() ; a ; a = a->next()) { sortedTags << a; } std::stable_sort(sortedTags.begin(), sortedTags.end(), lessThanByTitle); - for (QList::const_iterator it = sortedTags.constBegin(); it != sortedTags.constEnd(); ++it) + for (QList::const_iterator it = sortedTags.constBegin() ; it != sortedTags.constEnd() ; ++it) { TAlbum* const a = (TAlbum*)(*it); if (a->isInternalTag()) { continue; } if (d->mode == RECENTLYASSIGNED) { continue; } else if (d->mode == REMOVE || d->mode == DISPLAY) { if (!d->assignedTags.contains(a->id()) && !d->parentAssignedTags.contains(a->id())) { continue; } } QString t = a->title(); t.replace(QLatin1Char('&'), QLatin1String("&&")); TagToggleAction* action = 0; if (d->mode == ASSIGN) { action = new TagToggleAction(t, d->toggleTagActions); if (d->assignedTags.contains(a->id())) { action->setSpecialChecked(true); } } else // REMOVE or DISPLAY mode { action = new TagToggleAction(t, d->toggleTagActions); action->setCheckBoxHidden(true); } action->setData(a->id()); menu->addAction(action); // get icon setAlbumIcon(action, a); if (a->firstChild()) { if ((d->mode != REMOVE && d->mode != DISPLAY) || d->parentAssignedTags.contains(a->id())) { action->setMenu(buildSubMenu(a->id())); } } } } QMenu* TagsPopupMenu::buildSubMenu(int tagid) { AlbumManager* const man = AlbumManager::instance(); TAlbum* const album = man->findTAlbum(tagid); if (!album) { return 0; } QMenu* const popup = new QMenu(this); popup->setSeparatorsCollapsible(true); if (d->mode == ASSIGN && !d->assignedTags.contains(album->id())) { TagToggleAction* const action = new TagToggleAction(i18n("Assign this Tag"), d->toggleTagActions); action->setData(album->id()); action->setCheckBoxHidden(true); setAlbumIcon(action, album); popup->addAction(action); popup->addSeparator(); } else if (d->mode == REMOVE && d->assignedTags.contains(tagid)) { TagToggleAction* const action = new TagToggleAction(i18n("Remove this Tag"), d->toggleTagActions); action->setData(album->id()); action->setCheckBoxHidden(true); setAlbumIcon(action, album); popup->addAction(action); popup->addSeparator(); d->toggleTagActions->addAction(action); } else if (d->mode == DISPLAY) { TagToggleAction* const action = new TagToggleAction(i18n("Go to this Tag"), d->toggleTagActions); action->setData(album->id()); action->setCheckBoxHidden(true); setAlbumIcon(action, album); popup->addAction(action); popup->addSeparator(); d->toggleTagActions->addAction(action); } iterateAndBuildMenu(popup, album); if (d->mode == ASSIGN) { popup->addSeparator(); TagToggleAction* const action = new TagToggleAction(d->addTagPix, i18n("Add New Tag..."), d->addTagActions); action->setData(album->id()); action->setCheckBoxHidden(true); popup->addAction(action); } return popup; } void TagsPopupMenu::buildFlatMenu(QMenu* menu) { QList ids; QStringList shortenedPaths = TagsCache::instance()->shortenedTagPaths(d->assignedTags.toList(), &ids, TagsCache::NoLeadingSlash, TagsCache::NoHiddenTags); - for (int i=0; ifindTAlbum(ids.at(i)); if (!a) { continue; } TagToggleAction* const action = new TagToggleAction(t, d->toggleTagActions); if (d->mode == ASSIGN) { if (d->assignedTags.contains(a->id())) { action->setSpecialChecked(true); } } else // REMOVE or DISPLAY mode { action->setCheckBoxHidden(true); } action->setData(a->id()); menu->addAction(action); // get icon setAlbumIcon(action, a); } } void TagsPopupMenu::setAlbumIcon(QAction* action, TAlbum* album) { AlbumThumbnailLoader* const loader = AlbumThumbnailLoader::instance(); QPixmap pix; if (!loader->getTagThumbnail(album, pix)) { if (pix.isNull()) { action->setIcon(loader->getStandardTagIcon(album)); } else { action->setIcon(pix); } } else { // for the time while loading, set standard icon // usually this code path will not be used, as icons are cached action->setIcon(loader->getStandardTagIcon(album)); } } void TagsPopupMenu::slotToggleTag(QAction* action) { int tagID = action->data().toInt(); emit signalTagActivated(tagID); } void TagsPopupMenu::slotAddTag(QAction* action) { int tagID = action->data().toInt(); AlbumManager* const man = AlbumManager::instance(); if (tagID == -1) { emit signalPopupTagsView(); return; } TAlbum* const parent = man->findTAlbum(tagID); if (!parent) { qCWarning(DIGIKAM_GENERAL_LOG) << "Failed to find album with id " << tagID; return; } QString title, icon; QKeySequence ks; if (!TagEditDlg::tagCreate(qApp->activeWindow(), parent, title, icon, ks)) { return; } QMap errMap; AlbumList tList = TagEditDlg::createTAlbum(parent, title, icon, ks, errMap); TagEditDlg::showtagsListCreationError(qApp->activeWindow(), errMap); - for (AlbumList::const_iterator it = tList.constBegin(); it != tList.constEnd(); ++it) + for (AlbumList::const_iterator it = tList.constBegin() ; it != tList.constEnd() ; ++it) { emit signalTagActivated((*it)->id()); } } void TagsPopupMenu::slotTagThumbnail(Album* album, const QPixmap& pix) { QList actionList = actions(); foreach(QAction* const action, actionList) { if (action->data().toInt() == album->id()) { action->setIcon(pix); return; } } } } // namespace Digikam diff --git a/core/libs/threadimageio/thumb/thumbnailbasic.cpp b/core/libs/threadimageio/thumb/thumbnailbasic.cpp index d46b612a63..a78c058c4c 100644 --- a/core/libs/threadimageio/thumb/thumbnailbasic.cpp +++ b/core/libs/threadimageio/thumb/thumbnailbasic.cpp @@ -1,315 +1,315 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2007-07-20 * Description : Loader for thumbnails * * Copyright (C) 2003-2005 by Renchi Raju * Copyright (C) 2003-2019 by Gilles Caulier * Copyright (C) 2006-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. * * ============================================================ */ // C++ includes #include #include #include // Qt includes #include #include #include #include // C ANSI includes extern "C" { #include #include #include #ifndef Q_OS_WIN32 # include # include #endif #include #include } // Local includes #include "thumbnailcreator.h" #include "thumbnailcreator_p.h" // Definitions #define PNG_BYTES_TO_CHECK 4 namespace Digikam { // --- Static methods: Generate the thumbnail path according to FreeDesktop spec --- QString ThumbnailCreator::normalThumbnailDir() { return QDir::homePath() + QLatin1String("/.thumbnails/normal/"); } QString ThumbnailCreator::largeThumbnailDir() { return QDir::homePath() + QLatin1String("/.thumbnails/large/"); } QString ThumbnailCreator::thumbnailPath(const QString& filePath, const QString& basePath) { return thumbnailPathFromUri(thumbnailUri(filePath), basePath); } QString ThumbnailCreator::thumbnailUri(const QString& filePath) { return QUrl::fromLocalFile(filePath).url(); } QString ThumbnailCreator::thumbnailPathFromUri(const QString& uri, const QString& basePath) { QCryptographicHash md5(QCryptographicHash::Md5); - md5.addData( QFile::encodeName(uri).constData() ); - return ( basePath + QString::fromUtf8(QFile::encodeName(QString::fromUtf8(md5.result().toHex()))) + QLatin1String(".png") ); + md5.addData(QFile::encodeName(uri).constData()); + return (basePath + QString::fromUtf8(QFile::encodeName(QString::fromUtf8(md5.result().toHex()))) + QLatin1String(".png")); } // --- non-static methods --- void ThumbnailCreator::initThumbnailDirs() { d->smallThumbPath = normalThumbnailDir(); d->bigThumbPath = largeThumbnailDir(); if (!QDir(d->smallThumbPath).exists()) { if (QDir().mkpath(d->smallThumbPath)) { QFile f(d->smallThumbPath); f.setPermissions(QFile::ReadUser | QFile::WriteUser | QFile::ExeUser); // 0700 } } if (!QDir(d->bigThumbPath).exists()) { if (QDir().mkpath(d->bigThumbPath)) { QFile f(d->bigThumbPath); f.setPermissions(QFile::ReadUser | QFile::WriteUser | QFile::ExeUser); // 0700 } } } QString ThumbnailCreator::thumbnailPath(const QString& filePath) const { QString basePath = (d->storageSize() == 128) ? d->smallThumbPath : d->bigThumbPath; return thumbnailPath(filePath, basePath); } // --- Basic PNG loading --- QImage ThumbnailCreator::loadPNG(const QString& path) const { png_uint_32 w32, h32; int w, h; bool has_alpha = 0; png_structp png_ptr = NULL; png_infop info_ptr = NULL; int bit_depth, color_type, interlace_type; QImage qimage; FILE* const f = fopen(path.toLatin1().constData(), "rb"); if (!f) { return qimage; } unsigned char buf[PNG_BYTES_TO_CHECK]; size_t itemsRead = fread(buf, 1, PNG_BYTES_TO_CHECK, f); #if PNG_LIBPNG_VER >= 10400 if (itemsRead != 1 || png_sig_cmp(buf, 0, PNG_BYTES_TO_CHECK)) #else if (itemsRead != 1 || !png_check_sig(buf, PNG_BYTES_TO_CHECK)) #endif { fclose(f); return qimage; } rewind(f); png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (!png_ptr) { fclose(f); return qimage; } info_ptr = png_create_info_struct(png_ptr); if (!info_ptr) { png_destroy_read_struct(&png_ptr, NULL, NULL); fclose(f); return qimage; } #if PNG_LIBPNG_VER >= 10400 if (setjmp(png_jmpbuf(png_ptr))) #else if (setjmp(png_ptr->jmpbuf)) #endif { png_destroy_read_struct(&png_ptr, &info_ptr, NULL); fclose(f); return qimage; } png_init_io(png_ptr, f); png_read_info(png_ptr, info_ptr); png_get_IHDR(png_ptr, info_ptr, (png_uint_32*) (&w32), (png_uint_32*) (&h32), &bit_depth, &color_type, &interlace_type, NULL, NULL); bool has_grey = 0; w = w32; h = h32; qimage = QImage(w, h, QImage::Format_ARGB32); if (color_type == PNG_COLOR_TYPE_PALETTE) { png_set_expand(png_ptr); } #if PNG_LIBPNG_VER >= 10400 png_byte info_color_type = png_get_color_type(png_ptr, info_ptr); #else png_byte info_color_type = info_ptr->color_type; #endif if (info_color_type == PNG_COLOR_TYPE_RGB_ALPHA) { has_alpha = 1; } if (info_color_type == PNG_COLOR_TYPE_GRAY_ALPHA) { has_alpha = 1; has_grey = 1; } if (info_color_type == PNG_COLOR_TYPE_GRAY) { has_grey = 1; } unsigned char** lines = 0; int i; if (has_alpha) { png_set_expand(png_ptr); } if (QSysInfo::ByteOrder == QSysInfo::LittleEndian) // Intel { png_set_filler(png_ptr, 0xff, PNG_FILLER_AFTER); png_set_bgr(png_ptr); } else // PPC { png_set_swap_alpha(png_ptr); png_set_filler(png_ptr, 0xff, PNG_FILLER_BEFORE); } /* 16bit color -> 8bit color */ - if ( bit_depth == 16 ) + if (bit_depth == 16) { png_set_strip_16(png_ptr); } /* pack all pixels to byte boundaries */ png_set_packing(png_ptr); if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { png_set_expand(png_ptr); } lines = (unsigned char**)malloc(h * sizeof(unsigned char*)); if (!lines) { png_read_end(png_ptr, info_ptr); png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) NULL); fclose(f); return qimage; } if (has_grey) { png_set_gray_to_rgb(png_ptr); if (png_get_bit_depth(png_ptr, info_ptr) < 8) #if PNG_LIBPNG_VER >= 10400 png_set_expand_gray_1_2_4_to_8(png_ptr); #else png_set_gray_1_2_4_to_8(png_ptr); #endif } int sizeOfUint = sizeof(unsigned int); for (i = 0 ; i < h ; ++i) { lines[i] = ((unsigned char*)(qimage.bits())) + (i * w * sizeOfUint); } png_read_image(png_ptr, lines); free(lines); png_textp text_ptr; int num_text = 0; png_get_text(png_ptr,info_ptr, &text_ptr, &num_text); while (num_text--) { qimage.setText(QString::fromUtf8(text_ptr->key), QString::fromUtf8(text_ptr->text)); ++text_ptr; } png_read_end(png_ptr, info_ptr); png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) NULL); fclose(f); return qimage; } } // namespace Digikam diff --git a/core/libs/versionmanager/versionmanager.cpp b/core/libs/versionmanager/versionmanager.cpp index d88b401b4a..7e4104228a 100644 --- a/core/libs/versionmanager/versionmanager.cpp +++ b/core/libs/versionmanager/versionmanager.cpp @@ -1,773 +1,773 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-06-18 * Description : class for determining new file name in terms of version management * * Copyright (C) 2010-2011 by Marcel Wiesweg * 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 "versionmanager.h" // Qt includes #include #include // KDE includes #include // Local includes #include "digikam_debug.h" #include "dimgfiltermanager.h" namespace Digikam { class Q_DECL_HIDDEN VersionManagerSettingsConfig { public: static const QString configEnabled; static const QString configIntermediateAfterEachSession; static const QString configIntermediateAfterRawConversion; static const QString configIntermediateWhenNotReproducible; static const QString configViewShowIntermediates; static const QString configViewShowOriginal; static const QString configAutoSaveWhenClosingEditor; static const QString configVersionStorageFormat; }; const QString VersionManagerSettingsConfig::configEnabled(QLatin1String("Non-Destructive Editing Enabled")); const QString VersionManagerSettingsConfig::configIntermediateAfterEachSession(QLatin1String("Save Intermediate After Each Session")); const QString VersionManagerSettingsConfig::configIntermediateAfterRawConversion(QLatin1String("Save Intermediate After Raw Conversion")); const QString VersionManagerSettingsConfig::configIntermediateWhenNotReproducible(QLatin1String("Save Intermediate When Not Reproducible")); const QString VersionManagerSettingsConfig::configViewShowIntermediates(QLatin1String("Show Intermediates in View")); const QString VersionManagerSettingsConfig::configViewShowOriginal(QLatin1String("Show Original in View")); const QString VersionManagerSettingsConfig::configAutoSaveWhenClosingEditor(QLatin1String("Auto-Save When Closing Editor")); const QString VersionManagerSettingsConfig::configVersionStorageFormat(QLatin1String("Saving Format for Versions")); VersionManagerSettings::VersionManagerSettings() { enabled = true; saveIntermediateVersions = NoIntermediates; showInViewFlags = ShowOriginal; editorClosingMode = AlwaysAsk; format = QLatin1String("JPG"); } void VersionManagerSettings::readFromConfig(KConfigGroup& group) { enabled = group.readEntry(VersionManagerSettingsConfig::configEnabled, true); saveIntermediateVersions = NoIntermediates; if (group.readEntry(VersionManagerSettingsConfig::configIntermediateAfterEachSession, false)) saveIntermediateVersions |= AfterEachSession; if (group.readEntry(VersionManagerSettingsConfig::configIntermediateAfterRawConversion, false)) saveIntermediateVersions |= AfterRawConversion; if (group.readEntry(VersionManagerSettingsConfig::configIntermediateWhenNotReproducible, false)) saveIntermediateVersions |= WhenNotReproducible; showInViewFlags = OnlyShowCurrent; if (group.readEntry(VersionManagerSettingsConfig::configViewShowOriginal, true)) showInViewFlags |= ShowOriginal; if (group.readEntry(VersionManagerSettingsConfig::configViewShowIntermediates, true)) showInViewFlags |= ShowIntermediates; bool autoSave = group.readEntry(VersionManagerSettingsConfig::configAutoSaveWhenClosingEditor, false); editorClosingMode = autoSave ? AutoSave : AlwaysAsk; format = group.readEntry(VersionManagerSettingsConfig::configVersionStorageFormat, QString::fromLatin1("JPG")).toUpper(); } void VersionManagerSettings::writeToConfig(KConfigGroup& group) const { group.writeEntry(VersionManagerSettingsConfig::configEnabled, enabled); group.writeEntry(VersionManagerSettingsConfig::configIntermediateAfterEachSession, bool(saveIntermediateVersions & AfterEachSession)); group.writeEntry(VersionManagerSettingsConfig::configIntermediateAfterRawConversion, bool(saveIntermediateVersions & AfterRawConversion)); group.writeEntry(VersionManagerSettingsConfig::configIntermediateWhenNotReproducible, bool(saveIntermediateVersions & WhenNotReproducible)); group.writeEntry(VersionManagerSettingsConfig::configViewShowIntermediates, bool(showInViewFlags & ShowIntermediates)); group.writeEntry(VersionManagerSettingsConfig::configViewShowOriginal, bool(showInViewFlags & ShowOriginal)); group.writeEntry(VersionManagerSettingsConfig::configAutoSaveWhenClosingEditor, bool(editorClosingMode == AutoSave)); group.writeEntry(VersionManagerSettingsConfig::configVersionStorageFormat, format); } // --------------------------------------------------------------------------------------------- class Q_DECL_HIDDEN DefaultVersionNamingScheme : public VersionNamingScheme { public: virtual QString baseName(const QString& currentPath, const QString& filename, QVariant* counter, QVariant* intermediateCounter); virtual QString versionFileName(const QString& currentPath, const QString& filename, const QVariant& counter); virtual QString intermediateFileName(const QString& currentPath, const QString& filename, const QVariant& version, const QVariant& counter); virtual QString directory(const QString& currentPath, const QString& filename); virtual QString intermediateDirectory(const QString& currentPath, const QString& fileName); virtual QVariant initialCounter(); virtual QVariant incrementedCounter(const QVariant& counter); }; QVariant DefaultVersionNamingScheme::initialCounter() { // start with _v1 return 1; } QVariant DefaultVersionNamingScheme::incrementedCounter(const QVariant& counter) { return counter.toInt() + 1; } QString DefaultVersionNamingScheme::baseName(const QString& currentPath, const QString& fileName, QVariant* counter, QVariant* intermediateCounter) { Q_UNUSED(currentPath); // Perl: /^(.+?)(_v\d+?)?(-\d+?)?\.([^\.]+?)$/ // But setMinimal() cannot replace Perl's non-greedy quantifiers, so we need three regexps int index = fileName.lastIndexOf(QLatin1Char('.')); QString completeBaseName = (index == -1) ? fileName : fileName.left(index); // DSC000636_v5-3.JPG: intermediate QRegExp versionIntermediate(QLatin1String("(.+)_v(\\d+)-(\\d+)")); if (versionIntermediate.exactMatch(completeBaseName)) { if (counter) { *counter = versionIntermediate.cap(2).toInt(); } if (intermediateCounter) { *intermediateCounter = versionIntermediate.cap(3).toInt(); } return versionIntermediate.cap(1); } // DSC000636_v5.JPG: version QRegExp version(QLatin1String("(.+)_v(\\d+)")); if (version.exactMatch(completeBaseName)) { if (counter) { *counter = version.cap(2).toInt(); } return version.cap(1); } // DSC000636.JPG: original file or different naming scheme return completeBaseName; } QString DefaultVersionNamingScheme::versionFileName(const QString& currentPath, const QString& baseName, const QVariant& counter) { Q_UNUSED(currentPath); return QString::fromUtf8("%1_v%2").arg(baseName).arg(counter.toInt()); } QString DefaultVersionNamingScheme::intermediateFileName(const QString& currentPath, const QString& baseName, const QVariant& version, const QVariant& counter) { Q_UNUSED(currentPath); return QString::fromUtf8("%1_v%2-%3").arg(baseName).arg(version.toInt()).arg(counter.toInt()); } QString DefaultVersionNamingScheme::directory(const QString& currentPath, const QString& fileName) { Q_UNUSED(fileName); return currentPath; } QString DefaultVersionNamingScheme::intermediateDirectory(const QString& currentPath, const QString& fileName) { Q_UNUSED(fileName); return currentPath; } // ------------------------------------------------------------------------------------------------------- class Q_DECL_HIDDEN VersionNameCreator { public: VersionNameCreator(const VersionFileInfo& loadedFile, const DImageHistory& m_resolvedInitialHistory, const DImageHistory& m_currentHistory, VersionManager* const q); void checkNeedNewVersion(); void fork(); void setSaveDirectory(); void setSaveFormat(); void setSaveFileName(); void setSaveDirectory(const QString& path); void setSaveFormat(const QString& format); void setSaveFileName(const QString& name); void initOperation(); void checkIntermediates(); protected: VersionFileInfo nextIntermediate(const QString& format); void setFileSuffix(QString& fileName, const QString& format) const; void addFileSuffix(QString& baseName, const QString& format, const QString& originalName = QString()) const; public: VersionManagerSettings m_settings; VersionFileInfo m_result; VersionFileInfo m_loadedFile; VersionFileOperation m_operation; const DImageHistory m_resolvedInitialHistory; const DImageHistory m_currentHistory; bool m_fromRaw; bool m_newVersion; QVariant m_version; QVariant m_intermediateCounter; QString m_baseName; QString m_intermediatePath; VersionManager* const q; }; VersionNameCreator::VersionNameCreator(const VersionFileInfo& loadedFile, const DImageHistory& m_resolvedInitialHistory, const DImageHistory& m_currentHistory, VersionManager* const q) : m_settings(q->settings()), m_loadedFile(loadedFile), m_resolvedInitialHistory(m_resolvedInitialHistory), m_currentHistory(m_currentHistory), m_fromRaw(false), m_newVersion(false), q(q) { m_loadedFile.format = m_loadedFile.format.toUpper(); m_fromRaw = (m_loadedFile.format.startsWith(QLatin1String("RAW"))); // also accept RAW-... format m_version = q->namingScheme()->initialCounter(); m_intermediateCounter = q->namingScheme()->initialCounter(); } void VersionNameCreator::checkNeedNewVersion() { // First we check if we have any other files available. // The resolved initial history contains only referred files found in the collection // Note: The loaded file will have type Current qCDebug(DIGIKAM_GENERAL_LOG) << m_resolvedInitialHistory.hasReferredImageOfType(HistoryImageId::Original) << m_resolvedInitialHistory.hasReferredImageOfType(HistoryImageId::Intermediate) << m_fromRaw << q->workspaceFileFormats().contains(m_loadedFile.format); if (!m_resolvedInitialHistory.hasReferredImageOfType(HistoryImageId::Original) && !m_resolvedInitialHistory.hasReferredImageOfType(HistoryImageId::Intermediate)) { m_newVersion = true; } // We check the loaded format: If it is not one of the workspace formats, or even raw, we need a new version else if (m_fromRaw || !q->workspaceFileFormats().contains(m_loadedFile.format)) { m_newVersion = true; } else { m_newVersion = false; } } void VersionNameCreator::fork() { m_newVersion = true; } void VersionNameCreator::setSaveDirectory() { m_result.path = q->namingScheme()->directory(m_loadedFile.path, m_loadedFile.fileName); m_intermediatePath = q->namingScheme()->directory(m_loadedFile.path, m_loadedFile.fileName); } void VersionNameCreator::setSaveDirectory(const QString& path) { m_result.path = path; m_intermediatePath = path; } void VersionNameCreator::setSaveFormat() { m_result.format = m_settings.format; /* if (m_fromRaw) { m_result.format = m_settings.formatForStoringRAW; } else { m_result.format = m_settings.formatForStoringSubversions; } */ } void VersionNameCreator::setSaveFormat(const QString& override) { m_result.format = override; } void VersionNameCreator::setSaveFileName() { qCDebug(DIGIKAM_GENERAL_LOG) << "need new version" << m_newVersion; VersionNamingScheme* scheme = q->namingScheme(); // initialize m_baseName, m_version, and m_intermediateCounter for intermediates m_baseName = scheme->baseName(m_loadedFile.path, m_loadedFile.fileName, &m_version, &m_intermediateCounter); qCDebug(DIGIKAM_GENERAL_LOG) << "analyzing file" << m_loadedFile.fileName << m_version << m_intermediateCounter; if (!m_newVersion) { m_result.fileName = m_loadedFile.fileName; if (m_loadedFile.format != m_result.format) { setFileSuffix(m_result.fileName, m_result.format); } } else { QDir dirInfo(m_result.path); // To find the right number for the new version, go through all the items in the given dir, // the version number won't be bigger than count() - for (uint i = 0; i <= dirInfo.count(); ++i) + for (uint i = 0 ; i <= dirInfo.count() ; ++i) { QString suggestedName = scheme->versionFileName(m_result.path, m_baseName, m_version); // Note: Always give a hard guarantee that the file does not exist if (dirInfo.entryList(QStringList() << suggestedName + QLatin1String(".*"), QDir::Files).isEmpty()) { m_result.fileName = suggestedName; addFileSuffix(m_result.fileName, m_result.format, m_loadedFile.fileName); break; } // increment for next attempt m_version = scheme->incrementedCounter(m_version); } } } void VersionNameCreator::setSaveFileName(const QString& fileName) { m_result.fileName = fileName; m_baseName = fileName.section(QLatin1Char('.'), 0, 0); // m_version remains unknown } void VersionNameCreator::initOperation() { m_operation.loadedFile = m_loadedFile; m_operation.saveFile = m_result; if (m_newVersion) { m_operation.tasks |= VersionFileOperation::NewFile; } else { if (m_result.fileName == m_loadedFile.fileName) { m_operation.tasks |= VersionFileOperation::Replace; } else { m_operation.tasks |= VersionFileOperation::SaveAndDelete; } } } void VersionNameCreator::checkIntermediates() { // call when task has been determined qCDebug(DIGIKAM_GENERAL_LOG) << "Will replace" << bool(m_operation.tasks & VersionFileOperation::Replace) << "save after each session" << bool(m_settings.saveIntermediateVersions & VersionManagerSettings::AfterEachSession) << "save after raw" << bool(m_settings.saveIntermediateVersions & VersionManagerSettings::AfterRawConversion) << "save when not repro" << bool(m_settings.saveIntermediateVersions & VersionManagerSettings::WhenNotReproducible); - if ( (m_settings.saveIntermediateVersions & VersionManagerSettings::AfterEachSession) && - (m_operation.tasks & VersionFileOperation::Replace) ) + if ((m_settings.saveIntermediateVersions & VersionManagerSettings::AfterEachSession) && + (m_operation.tasks & VersionFileOperation::Replace)) { // We want a snapshot after each session. The main file will be overwritten by the current state. // So we consider the current file as snapshot of the last session and move // it to an intermediate before it is overwritten m_operation.tasks |= VersionFileOperation::MoveToIntermediate; m_operation.intermediateForLoadedFile = nextIntermediate(m_loadedFile.format); // this amounts to storing firstStep - 1 } // These are, inclusively, the first and last step the state after which we may have to store. // m_resolvedInitialHistory.size() - 1 is the loaded file // m_currentHistory.size() - 1 is the current version int firstStep = m_resolvedInitialHistory.size(); int lastStep = m_currentHistory.size() - 2; // index of last but one entry qCDebug(DIGIKAM_GENERAL_LOG) << "initial history" << m_resolvedInitialHistory.size() << "current history" << m_currentHistory.size() << "first step" << firstStep << "last step" << lastStep; if (lastStep < firstStep) { // only a single editing step, or even "backwards" in history (possible with redo) return; } if (!firstStep) { qCDebug(DIGIKAM_GENERAL_LOG) << "Invalid history: resolved initial history has no entries"; firstStep = 1; } if (m_settings.saveIntermediateVersions & VersionManagerSettings::AfterRawConversion) { int rawConversionStep = -1; - for (int i = firstStep; i <= lastStep; ++i) + for (int i = firstStep ; i <= lastStep ; ++i) { if (DImgFilterManager::instance()->isRawConversion(m_currentHistory.action(i).identifier())) { rawConversionStep = i; // break? multiple raw conversion steps?? } } if (rawConversionStep != -1) { m_operation.intermediates.insert(rawConversionStep, VersionFileInfo()); } } if (m_settings.saveIntermediateVersions & VersionManagerSettings::WhenNotReproducible) { - for (int i = firstStep; i <= lastStep; ++i) + for (int i = firstStep ; i <= lastStep ; ++i) { qCDebug(DIGIKAM_GENERAL_LOG) << "step" << i << "is reproducible" << (m_currentHistory.action(i).category() == FilterAction::ReproducibleFilter); switch (m_currentHistory.action(i).category()) { case FilterAction::ReproducibleFilter: break; case FilterAction::ComplexFilter: case FilterAction::DocumentedHistory: m_operation.intermediates.insert(i, VersionFileInfo()); break; } } } qCDebug(DIGIKAM_GENERAL_LOG) << "Save intermediates after steps" << m_operation.intermediates.keys(); if (!m_operation.intermediates.isEmpty()) { m_operation.tasks |= VersionFileOperation::StoreIntermediates; // now all steps are available, already ordered thanks to QMap. Just fill in the empty VersionFileInfos. QMap::iterator it; - for (it = m_operation.intermediates.begin(); it != m_operation.intermediates.end(); ++it) + for (it = m_operation.intermediates.begin() ; it != m_operation.intermediates.end() ; ++it) { it.value() = nextIntermediate(m_result.format); } } } VersionFileInfo VersionNameCreator::nextIntermediate(const QString& format) { VersionNamingScheme* scheme = q->namingScheme(); VersionFileInfo intermediate; intermediate.path = m_intermediatePath; intermediate.format = format; QDir dirInfo(m_intermediatePath); - for (uint i = 0; i <= dirInfo.count(); ++i) + for (uint i = 0 ; i <= dirInfo.count() ; ++i) { QString suggestedName = scheme->intermediateFileName(m_intermediatePath, m_baseName, m_version, m_intermediateCounter); // it is important to increment before returning - we may have to produce a number of files m_intermediateCounter = scheme->incrementedCounter(m_intermediateCounter); // Note: Always give a hard guarantee that the file does not exist if (dirInfo.entryList(QStringList() << suggestedName + QLatin1String(".*"), QDir::Files).isEmpty()) { intermediate.fileName = suggestedName; setFileSuffix(intermediate.fileName, format); break; } } return intermediate; } void VersionNameCreator::setFileSuffix(QString& fileName, const QString& format) const { if (fileName.isEmpty()) { return; } // QFileInfo uses the same string manipulation int lastDot = fileName.lastIndexOf(QLatin1Char('.')); bool isLower = true; if (lastDot == -1) { fileName += QLatin1Char('.'); lastDot = fileName.size() - 1; } else { isLower = fileName.at(fileName.size() - 1).isLower(); } int suffixBegin = lastDot + 1; if (fileName.mid(suffixBegin).compare(format, Qt::CaseInsensitive)) { fileName.replace(suffixBegin, fileName.size() - suffixBegin, isLower ? format.toLower() : format); } } void VersionNameCreator::addFileSuffix(QString& fileName, const QString& format, const QString& originalName) const { if (fileName.isEmpty()) { return; } bool isLower = true; if (!originalName.isEmpty()) { // if original name had upper case suffix, continue using upper case isLower = originalName.at(originalName.size() - 1).isLower(); } if (!fileName.endsWith(QLatin1Char('.'))) { fileName += QLatin1Char('.'); } fileName += (isLower ? format.toLower() : format); } // ----------------------------------------------------------------------------------------------------------- bool VersionFileInfo::isNull() const { return fileName.isNull(); } QString VersionFileInfo::filePath() const { return path + QLatin1Char('/') + fileName; } QUrl VersionFileInfo::fileUrl() const { QUrl url = QUrl::fromLocalFile(path); url = url.adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + QLatin1Char('/') + fileName); return url; } QStringList VersionFileOperation::allFilePaths() const { QStringList paths; if (!saveFile.isNull()) paths << saveFile.filePath(); if (!intermediateForLoadedFile.isNull()) paths << intermediateForLoadedFile.filePath(); foreach(const VersionFileInfo& intermediate, intermediates) { paths << intermediate.filePath(); } return paths; } // ------------------------------------------------------------------------------------------------------ class Q_DECL_HIDDEN VersionManager::VersionManagerPriv { public: VersionManagerPriv() { scheme = 0; } VersionManagerSettings settings; VersionNamingScheme* scheme; DefaultVersionNamingScheme defaultScheme; }; // ------------------------------------------------------------------------------------------------------ VersionManager::VersionManager() : d(new VersionManagerPriv) { } VersionManager::~VersionManager() { delete d->scheme; delete d; } void VersionManager::setSettings(const VersionManagerSettings& settings) { d->settings = settings; } VersionManagerSettings VersionManager::settings() const { return d->settings; } void VersionManager::setNamingScheme(VersionNamingScheme* scheme) { d->scheme = scheme; } VersionNamingScheme* VersionManager::namingScheme() const { if (d->scheme) { return d->scheme; } else { return &d->defaultScheme; } } VersionFileOperation VersionManager::operation(FileNameType request, const VersionFileInfo& loadedFile, const DImageHistory& initialResolvedHistory, const DImageHistory& currentHistory) { VersionNameCreator name(loadedFile, initialResolvedHistory, currentHistory, this); if (request == CurrentVersionName) { name.checkNeedNewVersion(); } else if (request == NewVersionName) { name.fork(); } name.setSaveDirectory(); name.setSaveFormat(); name.setSaveFileName(); name.initOperation(); name.checkIntermediates(); return name.m_operation; } VersionFileOperation VersionManager::operationNewVersionInFormat(const VersionFileInfo& loadedFile, const QString& format, const DImageHistory& initialResolvedHistory, const DImageHistory& currentHistory) { VersionNameCreator name(loadedFile, initialResolvedHistory, currentHistory, this); name.fork(); name.setSaveDirectory(); name.setSaveFormat(format); name.setSaveFileName(); name.initOperation(); name.checkIntermediates(); return name.m_operation; } VersionFileOperation VersionManager::operationNewVersionAs(const VersionFileInfo& loadedFile, const VersionFileInfo& saveLocation, const DImageHistory& initialResolvedHistory, const DImageHistory& currentHistory) { VersionNameCreator name(loadedFile, initialResolvedHistory, currentHistory, this); name.fork(); name.setSaveDirectory(saveLocation.path); name.setSaveFormat(saveLocation.format); name.setSaveFileName(saveLocation.fileName); name.initOperation(); name.checkIntermediates(); return name.m_operation; } QString VersionManager::toplevelDirectory(const QString& path) { Q_UNUSED(path); return QLatin1String("/"); } QStringList VersionManager::workspaceFileFormats() const { QStringList formats; formats << QLatin1String("JPG") << QLatin1String("PNG") << QLatin1String("TIFF") << QLatin1String("PGF") << QLatin1String("JP2"); QString f = d->settings.format.toUpper(); if (!formats.contains(f)) { formats << f; } /* f = d->settings.formatForStoringSubversions.toUpper(); if (!formats.contains(f)) { formats << f; } */ return formats; } } // namespace Digikam diff --git a/core/libs/widgets/colors/colorgradientwidget.cpp b/core/libs/widgets/colors/colorgradientwidget.cpp index 5893ae9720..404c7fb9dc 100644 --- a/core/libs/widgets/colors/colorgradientwidget.cpp +++ b/core/libs/widgets/colors/colorgradientwidget.cpp @@ -1,169 +1,169 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2004-07-28 * Description : a color gradient widget * * 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 "colorgradientwidget.h" // Qt includes #include #include namespace Digikam { class Q_DECL_HIDDEN ColorGradientWidget::Private { public: explicit Private() { orientation = Qt::Horizontal; } Qt::Orientation orientation; QColor color1; QColor color2; }; ColorGradientWidget::ColorGradientWidget(Qt::Orientation orientation, int size, QWidget* const parent) : QWidget(parent), d(new Private) { d->orientation = orientation; d->color1.setRgb(0, 0, 0); d->color2.setRgb(255, 255, 255); setAttribute(Qt::WA_DeleteOnClose); - if ( d->orientation == Qt::Horizontal ) + if (d->orientation == Qt::Horizontal) { setFixedHeight(size); } else { setFixedWidth(size); } setContentsMargins(1, 1, 1, 1); } ColorGradientWidget::~ColorGradientWidget() { delete d; } void ColorGradientWidget::setColors(const QColor& col1, const QColor& col2) { d->color1 = col1; d->color2 = col2; update(); } void ColorGradientWidget::paintEvent(QPaintEvent*) { QImage image(contentsRect().width(), contentsRect().height(), QImage::Format_ARGB32); QColor col, color1, color2, colorf; float scale; if (!isEnabled()) { // Widget is disable : drawing grayed frame. color1 = palette().color(QPalette::Disabled, QPalette::WindowText); color2 = palette().color(QPalette::Disabled, QPalette::Window); colorf = palette().color(QPalette::Disabled, QPalette::WindowText); } else { color1 = d->color1; color2 = d->color2; colorf = palette().color(QPalette::Active, QPalette::WindowText); } int redDiff = color2.red() - color1.red(); int greenDiff = color2.green() - color1.green(); int blueDiff = color2.blue() - color1.blue(); if (d->orientation == Qt::Vertical) { - for (int y = 0; y < image.height(); ++y) + for (int y = 0 ; y < image.height() ; ++y) { scale = 1.0 * y / image.height(); - col.setRgb( color1.red() + int(redDiff * scale), - color1.green() + int(greenDiff * scale), - color1.blue() + int(blueDiff * scale) ); + col.setRgb(color1.red() + int(redDiff * scale), + color1.green() + int(greenDiff * scale), + color1.blue() + int(blueDiff * scale)); unsigned int* p = reinterpret_cast(image.scanLine(y)); - for (int x = 0; x < image.width(); ++x) + for (int x = 0 ; x < image.width() ; ++x) { *p++ = col.rgb(); } } } else { unsigned int* p = reinterpret_cast(image.scanLine(0)); - for (int x = 0; x < image.width(); ++x) + for (int x = 0 ; x < image.width() ; ++x) { scale = 1.0 * x / image.width(); - col.setRgb( color1.red() + int(redDiff * scale), - color1.green() + int(greenDiff * scale), - color1.blue() + int(blueDiff * scale) ); + col.setRgb(color1.red() + int(redDiff * scale), + color1.green() + int(greenDiff * scale), + color1.blue() + int(blueDiff * scale)); *p++ = col.rgb(); } - for (int y = 1; y < image.height(); ++y) + for (int y = 1 ; y < image.height() ; ++y) { memcpy(image.scanLine(y), image.scanLine(y - 1), sizeof(unsigned int) * image.width()); } } const int psize = 256; QColor ditherPalette[psize]; - for (int s = 0; s < psize; ++s) + for (int s = 0 ; s < psize ; ++s) { ditherPalette[s].setRgb(color1.red() + redDiff * s / psize, color1.green() + greenDiff * s / psize, color1.blue() + blueDiff * s / psize); } QPixmap pm = QPixmap::fromImage(image); QPainter p(this); p.drawPixmap(contentsRect(), pm); p.setPen(colorf); p.drawRect(rect()); p.end(); } } // namespace Digikam diff --git a/core/libs/widgets/colors/dcolorvalueselector.cpp b/core/libs/widgets/colors/dcolorvalueselector.cpp index e2171ac69d..da0c7c35c8 100644 --- a/core/libs/widgets/colors/dcolorvalueselector.cpp +++ b/core/libs/widgets/colors/dcolorvalueselector.cpp @@ -1,488 +1,488 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 1997-02-20 * Description : color chooser widgets * * Copyright (C) 1997 by Martin Jones * Copyright (C) 2015-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 "dcolorvalueselector.h" // Qt includes #include #include #include #include #include #include // Local includes #include "dcolorchoosermode_p.h" namespace Digikam { class Q_DECL_HIDDEN DSelector::Private { public: explicit Private() : arrowsize(5) { arrowPE = QStyle::PE_IndicatorArrowLeft; m_indent = true; } const int arrowsize; bool m_indent; QStyle::PrimitiveElement arrowPE; }; DSelector::DSelector(QWidget* const parent) : QAbstractSlider(parent), d(new Private) { setOrientation(Qt::Horizontal); } DSelector::DSelector(Qt::Orientation o, QWidget* const parent) : QAbstractSlider(parent), d(new Private) { setOrientation(o); if (o == Qt::Horizontal) setArrowDirection(Qt::UpArrow); } DSelector::~DSelector() { delete d; } void DSelector::setIndent(bool i) { d->m_indent = i; } bool DSelector::indent() const { return d->m_indent; } QRect DSelector::contentsRect() const { - int w = indent() ? style()->pixelMetric( QStyle::PM_DefaultFrameWidth ) : 0; + int w = indent() ? style()->pixelMetric(QStyle::PM_DefaultFrameWidth) : 0; int iw = (w < d->arrowsize) ? d->arrowsize : w; if (orientation() == Qt::Vertical) { if (arrowDirection() == Qt::RightArrow) { return QRect(w + d->arrowsize, iw, width() - w*2 - d->arrowsize, height() - iw*2); } else { return QRect(w, iw, width() - w*2 - d->arrowsize, height() - iw*2); } } else { // Qt::Horizontal if (arrowDirection() == Qt::UpArrow) { return QRect(iw, w, width() - 2*iw, height() - w*2 - d->arrowsize); } else { return QRect(iw, w + d->arrowsize, width() - 2*iw, height() - w*2 - d->arrowsize); } } } void DSelector::paintEvent(QPaintEvent*) { QPainter painter; - int w = style()->pixelMetric( QStyle::PM_DefaultFrameWidth ); + int w = style()->pixelMetric(QStyle::PM_DefaultFrameWidth); int iw = (w < d->arrowsize) ? d->arrowsize : w; painter.begin(this); drawContents(&painter); QBrush brush; QPoint pos = calcArrowPos(value()); drawArrow(&painter, pos); if (indent()) { QStyleOptionFrame opt; - opt.initFrom( this ); + opt.initFrom(this); opt.state = QStyle::State_Sunken; if (orientation() == Qt::Vertical) - opt.rect.adjust( 0, iw - w, -5, w - iw ); + opt.rect.adjust(0, iw - w, -5, w - iw); else opt.rect.adjust(iw - w, 0, w - iw, -5); QBrush oldBrush = painter.brush(); - painter.setBrush( Qt::NoBrush ); - style()->drawPrimitive( QStyle::PE_Frame, &opt, &painter, this ); - painter.setBrush( oldBrush ); + painter.setBrush(Qt::NoBrush); + style()->drawPrimitive(QStyle::PE_Frame, &opt, &painter, this); + painter.setBrush(oldBrush); } painter.end(); } void DSelector::mousePressEvent(QMouseEvent* e) { setSliderDown(true); - moveArrow( e->pos() ); + moveArrow(e->pos()); } void DSelector::mouseMoveEvent(QMouseEvent* e) { - moveArrow( e->pos() ); + moveArrow(e->pos()); } void DSelector::mouseReleaseEvent(QMouseEvent* e) { - moveArrow( e->pos() ); + moveArrow(e->pos()); setSliderDown(false); } void DSelector::wheelEvent(QWheelEvent* e) { int val = value() + e->delta()/120; setSliderDown(true); setValue(val); setSliderDown(false); } void DSelector::moveArrow(const QPoint& pos) { int val; int w = style()->pixelMetric(QStyle::PM_DefaultFrameWidth); int iw = (w < d->arrowsize) ? d->arrowsize : w; - if ( orientation() == Qt::Vertical ) + if (orientation() == Qt::Vertical) { - val = ( maximum() - minimum() ) * (height() - pos.y() - iw) + val = (maximum() - minimum()) * (height() - pos.y() - iw) / (height() - iw * 2) + minimum(); } else { - val = ( maximum() - minimum() ) * ( pos.x() - iw) + val = (maximum() - minimum()) * ( pos.x() - iw) / (width() - iw * 2) + minimum(); } - setValue( val ); + setValue(val); update(); } -QPoint DSelector::calcArrowPos( int val ) +QPoint DSelector::calcArrowPos(int val) { QPoint p; int w = style()->pixelMetric(QStyle::PM_DefaultFrameWidth); int iw = (w < d->arrowsize) ? d->arrowsize : w; if (orientation() == Qt::Vertical) { - p.setY(height() - iw - 1 - (height() - 2 * iw - 1) * val / ( maximum() - minimum() )); + p.setY(height() - iw - 1 - (height() - 2 * iw - 1) * val / (maximum() - minimum())); if (d->arrowPE == QStyle::PE_IndicatorArrowRight) { - p.setX( 0 ); + p.setX(0); } else { p.setX(width() - 5); } } else { - p.setX(iw + (width() - 2 * iw - 1) * val / ( maximum() - minimum() )); + p.setX(iw + (width() - 2 * iw - 1) * val / (maximum() - minimum())); if (d->arrowPE == QStyle::PE_IndicatorArrowDown) { p.setY(0); } else { p.setY(height() - 5); } } return p; } -void DSelector::setArrowDirection( Qt::ArrowType direction ) +void DSelector::setArrowDirection(Qt::ArrowType direction) { - switch ( direction ) + switch (direction) { case Qt::UpArrow: - if ( orientation() == Qt::Horizontal ) + if (orientation() == Qt::Horizontal) { d->arrowPE = QStyle::PE_IndicatorArrowUp; } else { d->arrowPE = QStyle::PE_IndicatorArrowLeft; } break; case Qt::DownArrow: - if ( orientation() == Qt::Horizontal ) + if (orientation() == Qt::Horizontal) { d->arrowPE = QStyle::PE_IndicatorArrowDown; } else { d->arrowPE = QStyle::PE_IndicatorArrowRight; } break; case Qt::LeftArrow: - if ( orientation() == Qt::Vertical ) + if (orientation() == Qt::Vertical) { d->arrowPE = QStyle::PE_IndicatorArrowLeft; } else { d->arrowPE = QStyle::PE_IndicatorArrowDown; } break; case Qt::RightArrow: - if ( orientation() == Qt::Vertical ) + if (orientation() == Qt::Vertical) { d->arrowPE = QStyle::PE_IndicatorArrowRight; } else { d->arrowPE = QStyle::PE_IndicatorArrowUp; } break; case Qt::NoArrow: break; } } Qt::ArrowType DSelector::arrowDirection() const { switch (d->arrowPE) { case QStyle::PE_IndicatorArrowUp: return Qt::UpArrow; break; case QStyle::PE_IndicatorArrowDown: return Qt::DownArrow; break; case QStyle::PE_IndicatorArrowRight: return Qt::RightArrow; break; case QStyle::PE_IndicatorArrowLeft: default: return Qt::LeftArrow; break; } } void DSelector::drawArrow(QPainter* painter, const QPoint& pos) { painter->setPen(QPen()); - painter->setBrush(QBrush( palette().color(QPalette::ButtonText) )); + painter->setBrush(QBrush(palette().color(QPalette::ButtonText))); QStyleOption o; - if ( orientation() == Qt::Vertical ) + if (orientation() == Qt::Vertical) { - o.rect = QRect( pos.x(), pos.y() - d->arrowsize / 2, - d->arrowsize, d->arrowsize ); + o.rect = QRect(pos.x(), pos.y() - d->arrowsize / 2, + d->arrowsize, d->arrowsize); } else { - o.rect = QRect( pos.x() - d->arrowsize / 2, pos.y(), - d->arrowsize, d->arrowsize ); + o.rect = QRect(pos.x() - d->arrowsize / 2, pos.y(), + d->arrowsize, d->arrowsize); } style()->drawPrimitive(d->arrowPE, &o, painter, this); } // ------------------------------------------------------------------------------------- class Q_DECL_HIDDEN DColorValueSelector::Private { public: explicit Private(DColorValueSelector* const q) : q(q), hue(0), saturation(0), color(0), mode(ChooserClassic) { } DColorValueSelector* q; int hue; int saturation; int color; DColorChooserMode mode; QPixmap pixmap; }; DColorValueSelector::DColorValueSelector(QWidget* const parent) : DSelector(Qt::Vertical, parent), d(new Private(this)) { setRange(0, 255); } DColorValueSelector::DColorValueSelector(Qt::Orientation o, QWidget* const parent) : DSelector(o, parent), d(new Private(this)) { setRange(0, 255); } DColorValueSelector::~DColorValueSelector() { delete d; } int DColorValueSelector::hue() const { return d->hue; } void DColorValueSelector::setHue(int hue) { d->hue = hue; } int DColorValueSelector::saturation() const { return d->saturation; } void DColorValueSelector::setSaturation(int saturation) { d->saturation = saturation; } int DColorValueSelector::colorValue() const { return d->color; } void DColorValueSelector::setColorValue(int colorValue) { d->color = colorValue; } void DColorValueSelector::updateContents() { drawPalette(&d->pixmap); } void DColorValueSelector::resizeEvent(QResizeEvent*) { updateContents(); } void DColorValueSelector::drawContents(QPainter* painter) { painter->drawPixmap(contentsRect().x(), contentsRect().y(), d->pixmap); } void DColorValueSelector::setChooserMode(DColorChooserMode c) { if (c == ChooserHue) { setRange(0, 360); } else { - setRange( 0, 255 ); + setRange(0, 255); } d->mode = c; } DColorChooserMode DColorValueSelector::chooserMode() const { return d->mode; } void DColorValueSelector::drawPalette(QPixmap* pixmap) { QColor color; if (chooserMode() == ChooserHue) { color.setHsv(hue(), 255, 255); } else { color.setHsv(hue(), saturation(), colorValue()); } QLinearGradient gradient; if (orientation() == Qt::Vertical) { gradient.setStart(0, contentsRect().height()); gradient.setFinalStop(0, 0); } else { gradient.setStart(0, 0); gradient.setFinalStop(contentsRect().width(), 0); } const int steps = componentValueSteps(chooserMode()); for (int v = 0; v <= steps; ++v) { setComponentValue(color, chooserMode(), v * (1.0 / steps)); gradient.setColorAt(v * (1.0 / steps), color); } *pixmap = QPixmap(contentsRect().size()); QPainter painter(pixmap); painter.fillRect(pixmap->rect(), gradient); } } // namespace Digikam diff --git a/core/libs/widgets/colors/dgradientslider.cpp b/core/libs/widgets/colors/dgradientslider.cpp index c73a222b85..7d11145294 100644 --- a/core/libs/widgets/colors/dgradientslider.cpp +++ b/core/libs/widgets/colors/dgradientslider.cpp @@ -1,312 +1,312 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2008-07-03 * Description : a color gradient slider * * Copyright (C) 2008-2019 by Gilles Caulier * Copyright (C) 2008 by Cyrille Berger * * 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 "dgradientslider.h" // Qt includes #include #include #include #include namespace Digikam { class Q_DECL_HIDDEN DGradientSlider::Private { public: enum Cursor { NoCursor = 0, LeftCursor, RightCursor, MiddleCursor }; public: explicit Private() { activeCursor = NoCursor; parent = 0; leftCursor = 0.0; middleCursor = 0.5; rightCursor = 1.0; showMiddleCursor = false; leftColor = Qt::black; rightColor = Qt::white; middleColor.setRgb((leftColor.red() + rightColor.red()) / 2, (leftColor.green() + rightColor.green()) / 2, (leftColor.blue() + rightColor.blue()) / 2); } bool showMiddleCursor; double leftCursor; double middleCursor; double rightCursor; QColor leftColor; QColor rightColor; QColor middleColor; DGradientSlider* parent; Cursor activeCursor; public: int gradientHeight() const { return parent->height() / 3; } int cursorWidth() const { return gradientHeight(); } int gradientWidth() const { return parent->width() - cursorWidth(); } int gradientOffset() const { return cursorWidth() / 2; } }; DGradientSlider::DGradientSlider(QWidget* const parent) : QWidget(parent), d(new Private) { d->parent = this; setMinimumWidth(256); setFixedHeight(20); } DGradientSlider::~DGradientSlider() { delete d; } int DGradientSlider::gradientOffset() const { return d->gradientOffset(); } void DGradientSlider::drawCursorAt(QPainter& painter, double pos, const QColor& brushColor, int width, int height, int gradientWidth) { - painter.setBrush( brushColor ); + painter.setBrush(brushColor); int pos2 = (int)(gradientWidth * pos); QPoint points[3]; points[0] = QPoint(pos2, 3 * height - 1); points[1] = QPoint(pos2 + width / 2, 2 * height); points[2] = QPoint(pos2 + width, 3 * height - 1); painter.drawPolygon(points, 3); } void DGradientSlider::paintEvent(QPaintEvent*) { int gradientHeight = d->gradientHeight(); int cursorWidth = d->cursorWidth(); int gradientWidth = d->gradientWidth(); int gradientOffset = d->gradientOffset(); QPainter painter(this); // Draw first gradient QLinearGradient lrGradient(QPointF(0, 0), QPointF(gradientWidth, 0)); - lrGradient.setColorAt(0.0, d->leftColor ); - lrGradient.setColorAt(1.0, d->rightColor ); - painter.setPen( Qt::NoPen ); - painter.setBrush( lrGradient ); - painter.drawRect(gradientOffset, 0, gradientWidth, gradientHeight ); + lrGradient.setColorAt(0.0, d->leftColor); + lrGradient.setColorAt(1.0, d->rightColor); + painter.setPen(Qt::NoPen); + painter.setBrush(lrGradient); + painter.drawRect(gradientOffset, 0, gradientWidth, gradientHeight); // Draw second gradient QLinearGradient lrcGradient(QPointF(0, 0), QPointF(gradientWidth, 0)); - lrcGradient.setColorAt( d->leftCursor, d->leftColor ); + lrcGradient.setColorAt(d->leftCursor, d->leftColor); - if ( d->showMiddleCursor ) + if (d->showMiddleCursor) { - lrcGradient.setColorAt( d->middleCursor, d->middleColor ); + lrcGradient.setColorAt(d->middleCursor, d->middleColor); } - lrcGradient.setColorAt( d->rightCursor, d->rightColor ); - painter.setBrush( lrcGradient ); - painter.drawRect(gradientOffset, gradientHeight, gradientWidth, gradientHeight ); + lrcGradient.setColorAt(d->rightCursor, d->rightColor); + painter.setBrush(lrcGradient); + painter.drawRect(gradientOffset, gradientHeight, gradientWidth, gradientHeight); // Draw cursors - painter.setPen( palette().color(QPalette::Text) ); - drawCursorAt( painter, d->leftCursor, d->leftColor, cursorWidth, gradientHeight, gradientWidth ); + painter.setPen(palette().color(QPalette::Text)); + drawCursorAt(painter, d->leftCursor, d->leftColor, cursorWidth, gradientHeight, gradientWidth); if (d->showMiddleCursor) { - drawCursorAt( painter, d->middleCursor, d->middleColor, cursorWidth, gradientHeight, gradientWidth ); + drawCursorAt(painter, d->middleCursor, d->middleColor, cursorWidth, gradientHeight, gradientWidth); } - drawCursorAt( painter, d->rightCursor, d->rightColor, cursorWidth, gradientHeight, gradientWidth ); + drawCursorAt(painter, d->rightCursor, d->rightColor, cursorWidth, gradientHeight, gradientWidth); } inline bool isCursorClicked(const QPoint& pos, double cursorPos, int width, int height, int gradientWidth) { int pos2 = (int)(gradientWidth * cursorPos); return ((pos.y() >= 2 * height) && (pos.y() < 3 * height) && (pos.x() >= pos2) && (pos.x() <= (pos2 + width))); } void DGradientSlider::mousePressEvent(QMouseEvent* e) { - if ( e->button() == Qt::LeftButton ) + if (e->button() == Qt::LeftButton) { int gradientHeight = d->gradientHeight(); int cursorWidth = d->cursorWidth(); int gradientWidth = d->gradientWidth(); // Select cursor - if ( isCursorClicked( e->pos(), d->leftCursor , cursorWidth, gradientHeight, gradientWidth ) ) + if (isCursorClicked(e->pos(), d->leftCursor , cursorWidth, gradientHeight, gradientWidth)) { d->activeCursor = Private::LeftCursor; } - else if ( d->showMiddleCursor && isCursorClicked( e->pos(), d->middleCursor , cursorWidth, gradientHeight, gradientWidth ) ) + else if (d->showMiddleCursor && isCursorClicked(e->pos(), d->middleCursor , cursorWidth, gradientHeight, gradientWidth)) { d->activeCursor = Private::MiddleCursor; } - else if ( isCursorClicked( e->pos(), d->rightCursor , cursorWidth, gradientHeight, gradientWidth ) ) + else if (isCursorClicked(e->pos(), d->rightCursor , cursorWidth, gradientHeight, gradientWidth)) { d->activeCursor = Private::RightCursor; } } } void DGradientSlider::mouseReleaseEvent(QMouseEvent*) { d->activeCursor = Private::NoCursor; } void DGradientSlider::mouseMoveEvent(QMouseEvent* e) { - double v = ( e->pos().x() - d->gradientOffset() ) / (double) d->gradientWidth(); + double v = (e->pos().x() - d->gradientOffset()) / (double) d->gradientWidth(); switch (d->activeCursor) { case Private::LeftCursor: - setLeftValue( v ); + setLeftValue(v); break; case Private::MiddleCursor: - setMiddleValue( v ); + setMiddleValue(v); break; case Private::RightCursor: - setRightValue( v ); + setRightValue(v); break; default: break; } } void DGradientSlider::leaveEvent(QEvent*) { d->activeCursor = Private::NoCursor; } void DGradientSlider::showMiddleCursor(bool b) { d->showMiddleCursor = b; } double DGradientSlider::leftValue() const { return d->leftCursor; } double DGradientSlider::rightValue() const { return d->rightCursor; } double DGradientSlider::middleValue() const { return d->middleCursor; } -void DGradientSlider::adjustMiddleValue( double newLeftValue, double newRightValue ) +void DGradientSlider::adjustMiddleValue(double newLeftValue, double newRightValue) { double newDist = newRightValue - newLeftValue; double oldDist = d->rightCursor - d->leftCursor; double oldPos = d->middleCursor - d->leftCursor; d->middleCursor = oldPos * newDist / oldDist + newLeftValue; } void DGradientSlider::setRightValue(double v) { - if ( v <= 1.0 && v > d->leftCursor && v != d->rightCursor ) + if (v <= 1.0 && v > d->leftCursor && v != d->rightCursor) { - adjustMiddleValue( d->leftCursor, v ); + adjustMiddleValue(d->leftCursor, v); d->rightCursor = v; update(); emit rightValueChanged(v); emit middleValueChanged(d->middleCursor); } } void DGradientSlider::setLeftValue(double v) { - if ( v >= 0.0 && v != d->leftCursor && v < d->rightCursor ) + if (v >= 0.0 && v != d->leftCursor && v < d->rightCursor) { - adjustMiddleValue( v, d->rightCursor ); + adjustMiddleValue(v, d->rightCursor); d->leftCursor = v; update(); emit leftValueChanged(v); emit middleValueChanged(d->middleCursor); } } void DGradientSlider::setMiddleValue(double v) { - if ( v > d->leftCursor && v != d->middleCursor && v < d->rightCursor ) + if (v > d->leftCursor && v != d->middleCursor && v < d->rightCursor) { d->middleCursor = v; update(); emit middleValueChanged(v); } } void DGradientSlider::setColors(const QColor& lcolor, const QColor& rcolor) { d->leftColor = lcolor; d->rightColor = rcolor; update(); } } // namespace Digikam diff --git a/core/libs/widgets/colors/dhuesaturationselect.cpp b/core/libs/widgets/colors/dhuesaturationselect.cpp index 2bf842d3b7..c7e0fa8aa0 100644 --- a/core/libs/widgets/colors/dhuesaturationselect.cpp +++ b/core/libs/widgets/colors/dhuesaturationselect.cpp @@ -1,408 +1,408 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 1997-02-20 * Description : color chooser widgets * * Copyright (C) 1997 by Martin Jones (mjones at kde dot org) * Copyright (C) 2015-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 "dhuesaturationselect.h" // Qt includes #include #include #include #include // Local includes #include "digikam_debug.h" #include "dcolorchoosermode_p.h" namespace Digikam { class Q_DECL_HIDDEN DPointSelect::Private { public: explicit Private(DPointSelect* const q): q(q), px(0), py(0), xPos(0), yPos(0), minX(0), maxX(100), minY(0), maxY(100), m_markerColor(Qt::white) { } void setValues(int _xPos, int _yPos); public: DPointSelect* q; int px; int py; int xPos; int yPos; int minX; int maxX; int minY; int maxY; QColor m_markerColor; }; void DPointSelect::Private::setValues(int _xPos, int _yPos) { int w = q->style()->pixelMetric(QStyle::PM_DefaultFrameWidth); xPos = _xPos; yPos = _yPos; - if ( xPos > maxX ) + if (xPos > maxX) xPos = maxX; - else if ( xPos < minX ) + else if (xPos < minX) xPos = minX; - if ( yPos > maxY ) + if (yPos > maxY) yPos = maxY; - else if ( yPos < minY ) + else if (yPos < minY) yPos = minY; Q_ASSERT(maxX != minX); int xp = w + (q->width() - 2 * w) * xPos / (maxX - minX); Q_ASSERT(maxY != minY); int yp = q->height() - w - (q->height() - 2 * w) * yPos / (maxY - minY); - q->setPosition( xp, yp ); + q->setPosition(xp, yp); } DPointSelect::DPointSelect(QWidget* const parent) : QWidget(parent), d(new Private(this)) { } DPointSelect::~DPointSelect() { delete d; } int DPointSelect::xValue() const { return d->xPos; } int DPointSelect::yValue() const { return d->yPos; } void DPointSelect::setRange(int _minX, int _minY, int _maxX, int _maxY) { if (_maxX == _minX) { qCWarning(DIGIKAM_GENERAL_LOG) << "DPointSelect::setRange invalid range: " << _maxX << " == " << _minX << " (for X) "; return; } if (_maxY == _minY) { qCWarning(DIGIKAM_GENERAL_LOG) << "DPointSelect::setRange invalid range: " << _maxY << " == " << _minY << " (for Y) "; return; } int w = style()->pixelMetric(QStyle::PM_DefaultFrameWidth); d->px = w; d->py = w; d->minX = _minX; d->minY = _minY; d->maxX = _maxX; d->maxY = _maxY; } void DPointSelect::setXValue(int _xPos) { setValues(_xPos, d->yPos); } void DPointSelect::setYValue(int _yPos) { setValues(d->xPos, _yPos); } void DPointSelect::setValues(int _xPos, int _yPos) { d->setValues(_xPos, _yPos); } -void DPointSelect::setMarkerColor( const QColor &col ) +void DPointSelect::setMarkerColor(const QColor &col) { d->m_markerColor = col; } QRect DPointSelect::contentsRect() const { int w = style()->pixelMetric(QStyle::PM_DefaultFrameWidth); return rect().adjusted(w, w, -w, -w); } QSize DPointSelect::minimumSizeHint() const { int w = style()->pixelMetric(QStyle::PM_DefaultFrameWidth); - return QSize( 2 * w, 2 * w ); + return QSize(2 * w, 2 * w); } -void DPointSelect::paintEvent( QPaintEvent * /* ev */ ) +void DPointSelect::paintEvent(QPaintEvent * /* ev */) { QStyleOptionFrame opt; opt.initFrom(this); QPainter painter; painter.begin(this); drawContents(&painter); drawMarker(&painter, d->px, d->py); style()->drawPrimitive(QStyle::PE_Frame, &opt, &painter, this); painter.end(); } void DPointSelect::mousePressEvent(QMouseEvent* e) { mouseMoveEvent(e); } void DPointSelect::mouseMoveEvent(QMouseEvent* e) { int xVal, yVal; - int w = style()->pixelMetric( QStyle::PM_DefaultFrameWidth ); - valuesFromPosition( e->pos().x() - w, e->pos().y() - w, xVal, yVal ); - setValues( xVal, yVal ); + int w = style()->pixelMetric(QStyle::PM_DefaultFrameWidth); + valuesFromPosition(e->pos().x() - w, e->pos().y() - w, xVal, yVal); + setValues(xVal, yVal); - emit valueChanged( d->xPos, d->yPos ); + emit valueChanged(d->xPos, d->yPos); } void DPointSelect::wheelEvent(QWheelEvent* e) { - if ( e->orientation() == Qt::Horizontal ) - setValues( xValue() + e->delta()/120, yValue() ); + if (e->orientation() == Qt::Horizontal) + setValues(xValue() + e->delta()/120, yValue()); else - setValues( xValue(), yValue() + e->delta()/120 ); + setValues(xValue(), yValue() + e->delta()/120); - emit valueChanged( d->xPos, d->yPos ); + emit valueChanged(d->xPos, d->yPos); } void DPointSelect::valuesFromPosition(int x, int y, int& xVal, int& yVal) const { - int w = style()->pixelMetric( QStyle::PM_DefaultFrameWidth ); - xVal = ( ( d->maxX - d->minX ) * ( x - w ) ) / ( width() - 2 * w ); - yVal = d->maxY - ( ( ( d->maxY - d->minY ) * (y - w) ) / ( height() - 2 * w ) ); + int w = style()->pixelMetric(QStyle::PM_DefaultFrameWidth); + xVal = ((d->maxX - d->minX) * (x - w)) / (width() - 2 * w); + yVal = d->maxY - (((d->maxY - d->minY) * (y - w)) / (height() - 2 * w)); - if ( xVal > d->maxX ) + if (xVal > d->maxX) xVal = d->maxX; - else if ( xVal < d->minX ) + else if (xVal < d->minX) xVal = d->minX; - if ( yVal > d->maxY ) + if (yVal > d->maxY) yVal = d->maxY; - else if ( yVal < d->minY ) + else if (yVal < d->minY) yVal = d->minY; } void DPointSelect::setPosition(int xp, int yp) { - int w = style()->pixelMetric( QStyle::PM_DefaultFrameWidth ); + int w = style()->pixelMetric(QStyle::PM_DefaultFrameWidth); - if ( xp < w ) + if (xp < w) xp = w; - else if ( xp > width() - w ) + else if (xp > width() - w) xp = width() - w; - if ( yp < w ) + if (yp < w) yp = w; - else if ( yp > height() - w ) + else if (yp > height() - w) yp = height() - w; d->px = xp; d->py = yp; update(); } void DPointSelect::drawMarker(QPainter* p, int xp, int yp) { QPen pen(d->m_markerColor); p->setPen(pen); p->drawEllipse(xp - 4, yp - 4, 8, 8); } // -------------------------------------------------------------------------------------------------------- class Q_DECL_HIDDEN DHueSaturationSelector::Private { public: explicit Private(DHueSaturationSelector* const q) : q(q), mode(ChooserClassic), hue(0), saturation(0), color(0) { } DHueSaturationSelector* q; QPixmap pixmap; /** * Stores the chooser mode */ DColorChooserMode mode; /** * Stores the values for hue, saturation and luminosity */ int hue; int saturation; int color; }; DHueSaturationSelector::DHueSaturationSelector(QWidget* const parent) : DPointSelect(parent), d(new Private(this)) { setChooserMode(ChooserClassic); } DHueSaturationSelector::~DHueSaturationSelector() { delete d; } DColorChooserMode DHueSaturationSelector::chooserMode() const { return d->mode; } void DHueSaturationSelector::setChooserMode(DColorChooserMode chooserMode) { int x = 0; int y = 255; switch (chooserMode) { case ChooserSaturation: case ChooserValue: x = 359; break; default: x = 255; break; } setRange(0, 0, x, y); d->mode = chooserMode; } int DHueSaturationSelector::hue() const { return d->hue; } void DHueSaturationSelector::setHue(int hue) { d->hue = hue; } int DHueSaturationSelector::saturation() const { return d->saturation; } void DHueSaturationSelector::setSaturation(int saturation) { d->saturation = saturation; } int DHueSaturationSelector::colorValue() const { return d->color; } void DHueSaturationSelector::setColorValue(int color) { d->color = color; } void DHueSaturationSelector::updateContents() { drawPalette(&d->pixmap); } void DHueSaturationSelector::resizeEvent(QResizeEvent*) { updateContents(); } void DHueSaturationSelector::drawContents(QPainter* painter) { painter->drawPixmap(contentsRect().x(), contentsRect().y(), d->pixmap); } void DHueSaturationSelector::drawPalette(QPixmap* pixmap) { int xSteps = componentXSteps(chooserMode()); int ySteps = componentYSteps(chooserMode()); QColor color; color.setHsv(hue(), saturation(), chooserMode() == ChooserClassic ? 192 : colorValue()); QImage image(QSize(xSteps + 1, ySteps + 1), QImage::Format_RGB32); for (int y = 0; y <= ySteps; ++y) { setComponentY(color, chooserMode(), y * (1.0 / ySteps)); for (int x = 0; x <= xSteps; ++x) { setComponentX(color, chooserMode(), x * (1.0 / xSteps)); image.setPixel(x, ySteps - y, color.rgb()); } } QPixmap pix(contentsRect().size()); QPainter painter(&pix); // Bilinear filtering painter.setRenderHint(QPainter::SmoothPixmapTransform, true); QRectF srcRect(0.5, 0.5, xSteps, ySteps); QRectF destRect(QPointF(0, 0), contentsRect().size()); painter.drawImage(destRect, image, srcRect); painter.end(); *pixmap = pix; } } // namespace Digikam diff --git a/core/libs/widgets/iccprofiles/cietonguewidget.cpp b/core/libs/widgets/iccprofiles/cietonguewidget.cpp index c71fe8730b..095640dcf9 100644 --- a/core/libs/widgets/iccprofiles/cietonguewidget.cpp +++ b/core/libs/widgets/iccprofiles/cietonguewidget.cpp @@ -1,823 +1,823 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2006-01-10 * Description : a widget to display CIE tongue from an ICC profile. * * Copyright (C) 2006-2019 by Gilles Caulier * * Any source code are inspired from lprof project and * Copyright (C) 1998-2001 Marti Maria * * 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 "cietonguewidget.h" // C++ includes #include // Qt includes #include #include #include #include #include #include // KDE includes #include // Local includes #include "digikam_config.h" #include "digikam_debug.h" #include "iccprofile.h" #include "dlayoutbox.h" #include "dworkingpixmap.h" namespace Digikam { /** The following table gives the CIE color matching functions \f$\bar{x}(\lambda)\f$, \f$\bar{y}(\lambda)\f$, and \f$\bar{z}(\lambda)\f$, for wavelengths \f$\lambda\f$ at 5 nanometer increments from 380 nm through 780 nm. This table is used in conjunction with Planck's law for the energy spectrum of a black body at a given temperature to plot the black body curve on the CIE chart. The following table gives the spectral chromaticity co-ordinates \f$x(\lambda)\f$ and \f$y(\lambda)\f$ for wavelengths in 5 nanometer increments from 380 nm through 780 nm. These coordinates represent the position in the CIE x-y space of pure spectral colors of the given wavelength, and thus define the outline of the CIE "tongue" diagram. */ static const double spectral_chromaticity[81][3] = { { 0.1741, 0.0050 }, // 380 nm { 0.1740, 0.0050 }, { 0.1738, 0.0049 }, { 0.1736, 0.0049 }, { 0.1733, 0.0048 }, { 0.1730, 0.0048 }, { 0.1726, 0.0048 }, { 0.1721, 0.0048 }, { 0.1714, 0.0051 }, { 0.1703, 0.0058 }, { 0.1689, 0.0069 }, { 0.1669, 0.0086 }, { 0.1644, 0.0109 }, { 0.1611, 0.0138 }, { 0.1566, 0.0177 }, { 0.1510, 0.0227 }, { 0.1440, 0.0297 }, { 0.1355, 0.0399 }, { 0.1241, 0.0578 }, { 0.1096, 0.0868 }, { 0.0913, 0.1327 }, { 0.0687, 0.2007 }, { 0.0454, 0.2950 }, { 0.0235, 0.4127 }, { 0.0082, 0.5384 }, { 0.0039, 0.6548 }, { 0.0139, 0.7502 }, { 0.0389, 0.8120 }, { 0.0743, 0.8338 }, { 0.1142, 0.8262 }, { 0.1547, 0.8059 }, { 0.1929, 0.7816 }, { 0.2296, 0.7543 }, { 0.2658, 0.7243 }, { 0.3016, 0.6923 }, { 0.3373, 0.6589 }, { 0.3731, 0.6245 }, { 0.4087, 0.5896 }, { 0.4441, 0.5547 }, { 0.4788, 0.5202 }, { 0.5125, 0.4866 }, { 0.5448, 0.4544 }, { 0.5752, 0.4242 }, { 0.6029, 0.3965 }, { 0.6270, 0.3725 }, { 0.6482, 0.3514 }, { 0.6658, 0.3340 }, { 0.6801, 0.3197 }, { 0.6915, 0.3083 }, { 0.7006, 0.2993 }, { 0.7079, 0.2920 }, { 0.7140, 0.2859 }, { 0.7190, 0.2809 }, { 0.7230, 0.2770 }, { 0.7260, 0.2740 }, { 0.7283, 0.2717 }, { 0.7300, 0.2700 }, { 0.7311, 0.2689 }, { 0.7320, 0.2680 }, { 0.7327, 0.2673 }, { 0.7334, 0.2666 }, { 0.7340, 0.2660 }, { 0.7344, 0.2656 }, { 0.7346, 0.2654 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 }, { 0.7347, 0.2653 } // 780 nm }; class Q_DECL_HIDDEN CIETongueWidget::Private { public: explicit Private() : profileDataAvailable(true), loadingImageMode(false), loadingImageSucess(false), needUpdatePixmap(false), uncalibratedColor(false), xBias(0), yBias(0), pxcols(0), pxrows(0), progressCount(0), gridside(0), progressTimer(0), hMonitorProfile(0), hXFORM(0) { progressPix = DWorkingPixmap(); } bool profileDataAvailable; bool loadingImageMode; bool loadingImageSucess; bool needUpdatePixmap; bool uncalibratedColor; int xBias; int yBias; int pxcols; int pxrows; int progressCount; // Position of animation during loading/calculation. double gridside; QPainter painter; QTimer* progressTimer; QPixmap pixmap; DWorkingPixmap progressPix; cmsHPROFILE hMonitorProfile; cmsHTRANSFORM hXFORM; cmsCIExyYTRIPLE Primaries; cmsCIEXYZ MediaWhite; }; CIETongueWidget::CIETongueWidget(int w, int h, QWidget* const parent, cmsHPROFILE hMonitor) : QWidget(parent), d(new Private) { cmsHPROFILE hXYZProfile; d->progressTimer = new QTimer(this); setMinimumSize(w, h); setAttribute(Qt::WA_DeleteOnClose); dkCmsErrorAction(LCMS_ERROR_SHOW); if (hMonitor) { d->hMonitorProfile = hMonitor; } else { d->hMonitorProfile = dkCmsCreate_sRGBProfile(); } hXYZProfile = dkCmsCreateXYZProfile(); if (hXYZProfile == NULL) return; d->hXFORM = dkCmsCreateTransform(hXYZProfile, TYPE_XYZ_16, d->hMonitorProfile, TYPE_RGB_8, INTENT_PERCEPTUAL, 0); dkCmsCloseProfile(hXYZProfile); if (d->hXFORM == NULL) qCDebug(DIGIKAM_WIDGETS_LOG) << "Wrong d->hXFORM" ; connect(d->progressTimer, SIGNAL(timeout()), this, SLOT(slotProgressTimerDone())); } CIETongueWidget::~CIETongueWidget() { dkCmsDeleteTransform(d->hXFORM); dkCmsCloseProfile(d->hMonitorProfile); delete d; } int CIETongueWidget::grids(double val) const { return (int) floor(val * d->gridside + 0.5); } bool CIETongueWidget::setProfileData(const QByteArray& profileData) { if (!profileData.isEmpty()) { LcmsLock lock; cmsHPROFILE hProfile = dkCmsOpenProfileFromMem((void*)profileData.data(), (DWORD)profileData.size()); if (!hProfile) { d->profileDataAvailable = false; d->loadingImageSucess = false; } else { setProfile(hProfile); dkCmsCloseProfile(hProfile); d->profileDataAvailable = true; d->loadingImageSucess = true; } } else { d->profileDataAvailable = false; d->loadingImageSucess = false; } d->loadingImageMode = false; d->uncalibratedColor = false; d->progressTimer->stop(); d->needUpdatePixmap = true; update(); return (d->profileDataAvailable); } bool CIETongueWidget::setProfileFromFile(const QUrl& file) { if (!file.isEmpty() && file.isValid()) { LcmsLock lock; cmsHPROFILE hProfile = dkCmsOpenProfileFromFile(QFile::encodeName(file.toLocalFile()).constData(), "r"); if (!hProfile) { d->profileDataAvailable = false; d->loadingImageSucess = false; } else { setProfile(hProfile); dkCmsCloseProfile(hProfile); d->profileDataAvailable = true; d->loadingImageSucess = true; } } else { d->profileDataAvailable = false; d->loadingImageSucess = false; } d->loadingImageMode = false; d->uncalibratedColor = false; d->progressTimer->stop(); d->needUpdatePixmap = true; update(); return (d->profileDataAvailable); } void CIETongueWidget::setProfile(cmsHPROFILE hProfile) { // Get the white point. dkCmsTakeMediaWhitePoint(&(d->MediaWhite), hProfile); cmsCIExyY White; dkCmsXYZ2xyY(&White, &(d->MediaWhite)); qCDebug(DIGIKAM_WIDGETS_LOG) << "Profile white point : x=" << White.x << " y=" << White.y << " Y=" << White.Y; // Get the colorant matrix. memset(&(d->Primaries),0,sizeof(cmsCIExyYTRIPLE)); if (dkCmsIsTag(hProfile, icSigRedColorantTag) && dkCmsIsTag(hProfile, icSigGreenColorantTag) && dkCmsIsTag(hProfile, icSigBlueColorantTag)) { MAT3 Mat; if (dkCmsReadICCMatrixRGB2XYZ(&Mat, hProfile)) { #if defined(USE_LCMS_VERSION_1000) qCDebug(DIGIKAM_WIDGETS_LOG) << "dkCmsReadICCMatrixRGB2XYZ(1): " \ << "[" << Mat.v[0].n[0] << ", " << Mat.v[0].n[1] << ", " << Mat.v[0].n[2] << "]" \ << "[" << Mat.v[1].n[0] << ", " << Mat.v[1].n[1] << ", " << Mat.v[1].n[2] << "]" \ << "[" << Mat.v[2].n[0] << ", " << Mat.v[2].n[1] << ", " << Mat.v[2].n[2] << "]" ; #else qCDebug(DIGIKAM_WIDGETS_LOG) << "dkCmsReadICCMatrixRGB2XYZ(2): " \ << "[" << Mat.Red.X << ", " << Mat.Red.Y << ", " << Mat.Red.Z << "]" \ << "[" << Mat.Green.X << ", " << Mat.Green.Y << ", " << Mat.Green.Z << "]" \ << "[" << Mat.Blue.X << ", " << Mat.Blue.Y << ", " << Mat.Blue.Z << "]" ; #endif // Undo chromatic adaptation if (dkCmsAdaptMatrixFromD50(&Mat, &White)) { #if defined(USE_LCMS_VERSION_1000) cmsCIEXYZ tmp; tmp.X = Mat.v[0].n[0]; tmp.Y = Mat.v[1].n[0]; tmp.Z = Mat.v[2].n[0]; qCDebug(DIGIKAM_WIDGETS_LOG) << "d->Primaries.Red : X=" << tmp.X << " Y=" << tmp.Y << " Z=" << tmp.Z; // ScaleToWhite(&MediaWhite, &tmp); dkCmsXYZ2xyY(&(d->Primaries.Red), &tmp); tmp.X = Mat.v[0].n[1]; tmp.Y = Mat.v[1].n[1]; tmp.Z = Mat.v[2].n[1]; qCDebug(DIGIKAM_WIDGETS_LOG) << "d->Primaries.Green : X=" << tmp.X << " Y=" << tmp.Y << " Z=" << tmp.Z; // ScaleToWhite(&MediaWhite, &tmp); dkCmsXYZ2xyY(&(d->Primaries.Green), &tmp); tmp.X = Mat.v[0].n[2]; tmp.Y = Mat.v[1].n[2]; tmp.Z = Mat.v[2].n[2]; qCDebug(DIGIKAM_WIDGETS_LOG) << "d->Primaries.Blue : X=" << tmp.X << " Y=" << tmp.Y << " Z=" << tmp.Z; // ScaleToWhite(&MediaWhite, &tmp); dkCmsXYZ2xyY(&(d->Primaries.Blue), &tmp); #else cmsCIEXYZ tmp; tmp.X = Mat.Red.X; tmp.Y = Mat.Green.X; tmp.Z = Mat.Blue.X; qCDebug(DIGIKAM_WIDGETS_LOG) << "d->Primaries.Red : X=" << tmp.X << " Y=" << tmp.Y << " Z=" << tmp.Z; // ScaleToWhite(&MediaWhite, &tmp); dkCmsXYZ2xyY(&(d->Primaries.Red), &tmp); tmp.X = Mat.Red.Y; tmp.Y = Mat.Green.Y; tmp.Z = Mat.Blue.Y; qCDebug(DIGIKAM_WIDGETS_LOG) << "d->Primaries.Green : X=" << tmp.X << " Y=" << tmp.Y << " Z=" << tmp.Z; // ScaleToWhite(&MediaWhite, &tmp); dkCmsXYZ2xyY(&(d->Primaries.Green), &tmp); tmp.X = Mat.Red.Z; tmp.Y = Mat.Green.Z; tmp.Z = Mat.Blue.Z; qCDebug(DIGIKAM_WIDGETS_LOG) << "d->Primaries.Blue : X=" << tmp.X << " Y=" << tmp.Y << " Z=" << tmp.Z; // ScaleToWhite(&MediaWhite, &tmp); dkCmsXYZ2xyY(&(d->Primaries.Blue), &tmp); #endif } } } } void CIETongueWidget::mapPoint(int& icx, int& icy, LPcmsCIExyY xyY) { icx = (int) floor((xyY->x * (d->pxcols - 1)) + .5); icy = (int) floor(((d->pxrows - 1) - xyY->y * (d->pxrows - 1)) + .5); } void CIETongueWidget::biasedLine(int x1, int y1, int x2, int y2) { d->painter.drawLine(x1 + d->xBias, y1, x2 + d->xBias, y2); } void CIETongueWidget::biasedText(int x, int y, const QString& txt) { d->painter.drawText(QPoint(d->xBias + x, y), txt); } QRgb CIETongueWidget::colorByCoord(double x, double y) { // Get xyz components scaled from coordinates double cx = ((double) x) / (d->pxcols - 1); double cy = 1.0 - ((double) y) / (d->pxrows - 1); double cz = 1.0 - cx - cy; // Project xyz to XYZ space. Note that in this // particular case we are substituting XYZ with xyz cmsCIEXYZ XYZ = { cx , cy , cz }; WORD XYZW[3]; BYTE RGB[3]; dkCmsFloat2XYZEncoded(XYZW, &XYZ); dkCmsDoTransform(d->hXFORM, XYZW, RGB, 1); return qRgb(RGB[0], RGB[1], RGB[2]); } void CIETongueWidget::outlineTongue() { int lx=0, ly=0; int fx=0, fy=0; for (int x = 380; x <= 700; x += 5) { int ix = (x - 380) / 5; cmsCIExyY p = {spectral_chromaticity[ix][0], spectral_chromaticity[ix][1], 1 }; int icx, icy; mapPoint(icx, icy, &p); if (x > 380) { biasedLine(lx, ly, icx, icy); } else { fx = icx; fy = icy; } lx = icx; ly = icy; } biasedLine(lx, ly, fx, fy); } void CIETongueWidget::fillTongue() { QImage Img = d->pixmap.toImage(); int x; for (int y = 0; y < d->pxrows; ++y) { int xe = 0; // Find horizontal extents of tongue on this line. for (x = 0; x < d->pxcols; ++x) { if (QColor(Img.pixel(x + d->xBias, y)) != QColor(Qt::black)) { for (xe = d->pxcols - 1; xe >= x; --xe) { if (QColor(Img.pixel(xe + d->xBias, y)) != QColor(Qt::black)) { break; } } break; } } if (x < d->pxcols) { for ( ; x <= xe; ++x) { QRgb Color = colorByCoord(x, y); Img.setPixel(x + d->xBias, y, Color); } } } d->pixmap = QPixmap::fromImage(Img, Qt::AvoidDither); } void CIETongueWidget::drawTongueAxis() { QFont font; font.setPointSize(6); d->painter.setFont(font); d->painter.setPen(qRgb(255, 255, 255)); biasedLine(0, 0, 0, d->pxrows - 1); biasedLine(0, d->pxrows-1, d->pxcols-1, d->pxrows - 1); for (int y = 1; y <= 9; y += 1) { QString s; int xstart = (y * (d->pxcols - 1)) / 10; int ystart = (y * (d->pxrows - 1)) / 10; s.sprintf("0.%d", y); biasedLine(xstart, d->pxrows - grids(1), xstart, d->pxrows - grids(4)); biasedText(xstart - grids(11), d->pxrows + grids(15), s); s.sprintf("0.%d", 10 - y); biasedLine(0, ystart, grids(3), ystart); biasedText(grids(-25), ystart + grids(5), s); } } void CIETongueWidget::drawTongueGrid() { d->painter.setPen(qRgb(80, 80, 80)); for (int y = 1; y <= 9; y += 1) { int xstart = (y * (d->pxcols - 1)) / 10; int ystart = (y * (d->pxrows - 1)) / 10; biasedLine(xstart, grids(4), xstart, d->pxrows - grids(4) - 1); biasedLine(grids(7), ystart, d->pxcols-1-grids(7), ystart); } } void CIETongueWidget::drawLabels() { QFont font; font.setPointSize(5); d->painter.setFont(font); for (int x = 450; x <= 650; x += (x > 470 && x < 600) ? 5 : 10) { QString wl; int bx = 0, by = 0, tx, ty; if (x < 520) { bx = grids(-22); by = grids(2); } else if (x < 535) { bx = grids(-8); by = grids(-6); } else { bx = grids(4); } int ix = (x - 380) / 5; cmsCIExyY p = {spectral_chromaticity[ix][0], spectral_chromaticity[ix][1], 1 }; int icx, icy; mapPoint(icx, icy, &p); tx = icx + ((x < 520) ? grids(-2) : ((x >= 535) ? grids(2) : 0)); ty = icy + ((x < 520) ? 0 : ((x >= 535) ? grids(-1) : grids(-2))); d->painter.setPen(qRgb(255, 255, 255)); biasedLine(icx, icy, tx, ty); QRgb Color = colorByCoord(icx, icy); d->painter.setPen(Color); wl.sprintf("%d", x); biasedText(icx+bx, icy+by, wl); } } void CIETongueWidget::drawSmallElipse(LPcmsCIExyY xyY, BYTE r, BYTE g, BYTE b, int sz) { int icx, icy; mapPoint(icx, icy, xyY); d->painter.setPen(qRgb(r, g, b)); d->painter.drawEllipse(icx + d->xBias- sz/2, icy-sz/2, sz, sz); } void CIETongueWidget::drawColorantTriangle() { drawSmallElipse(&(d->Primaries.Red), 255, 128, 128, 6); drawSmallElipse(&(d->Primaries.Green), 128, 255, 128, 6); drawSmallElipse(&(d->Primaries.Blue), 128, 128, 255, 6); int x1, y1, x2, y2, x3, y3; mapPoint(x1, y1, &(d->Primaries.Red)); mapPoint(x2, y2, &(d->Primaries.Green)); mapPoint(x3, y3, &(d->Primaries.Blue)); d->painter.setPen(qRgb(255, 255, 255)); biasedLine(x1, y1, x2, y2); biasedLine(x2, y2, x3, y3); biasedLine(x3, y3, x1, y1); } void CIETongueWidget::drawWhitePoint() { cmsCIExyY Whitem_pntxyY; dkCmsXYZ2xyY(&Whitem_pntxyY, &(d->MediaWhite)); drawSmallElipse(&Whitem_pntxyY, 255, 255, 255, 8); } void CIETongueWidget::loadingStarted() { d->progressCount = 0; d->loadingImageMode = true; d->loadingImageSucess = false; update(); d->progressTimer->start(200); } void CIETongueWidget::loadingFailed() { d->progressTimer->stop(); d->progressCount = 0; d->loadingImageMode = false; d->loadingImageSucess = false; update(); } void CIETongueWidget::uncalibratedColor() { d->progressTimer->stop(); d->progressCount = 0; d->loadingImageMode = false; d->loadingImageSucess = false; d->uncalibratedColor = true; update(); } void CIETongueWidget::updatePixmap() { d->needUpdatePixmap = false; d->pixmap = QPixmap(size()); // Draw the CIE tongue curve. d->pixmap.fill(Qt::black); d->painter.begin(&d->pixmap); int pixcols = d->pixmap.width(); int pixrows = d->pixmap.height(); d->gridside = (qMin(pixcols, pixrows)) / 512.0; d->xBias = grids(32); d->yBias = grids(20); d->pxcols = pixcols - d->xBias; d->pxrows = pixrows - d->yBias; d->painter.setBackground(QBrush(qRgb(0, 0, 0))); d->painter.setPen(qRgb(255, 255, 255)); outlineTongue(); d->painter.end(); fillTongue(); d->painter.begin(&d->pixmap); drawTongueAxis(); drawLabels(); drawTongueGrid(); if (d->MediaWhite.Y > 0.0) { drawWhitePoint(); } if (d->Primaries.Red.Y != 0.0) { drawColorantTriangle(); } d->painter.end(); } void CIETongueWidget::paintEvent(QPaintEvent*) { QPainter p(this); // Widget is disable : drawing grayed frame. - if ( !isEnabled() ) + if (!isEnabled()) { p.fillRect(0, 0, width(), height(), palette().color(QPalette::Disabled, QPalette::Window)); QPen pen(palette().color(QPalette::Disabled, QPalette::WindowText)); pen.setStyle(Qt::SolidLine); pen.setWidth(1); p.setPen(pen); p.drawRect(0, 0, width(), height()); return; } // Loading image mode. if (d->loadingImageMode && !d->loadingImageSucess) { // In first, we draw an animation. QPixmap anim(d->progressPix.frameAt(d->progressCount)); d->progressCount++; if (d->progressCount >= d->progressPix.frameCount()) { d->progressCount = 0; } // ... and we render busy text. p.fillRect(0, 0, width(), height(), palette().color(QPalette::Active, QPalette::Window)); p.drawPixmap(width()/2 - anim.width() /2, anim.height(), anim); QPen pen(palette().color(QPalette::Active, QPalette::Text)); pen.setStyle(Qt::SolidLine); pen.setWidth(1); p.setPen(pen); p.drawRect(0, 0, width(), height()); p.drawText(0, 0, width(), height(), Qt::AlignCenter, i18n("Loading image...")); return; } // No profile data to show, or RAW file if (!d->profileDataAvailable || (!d->loadingImageMode && !d->loadingImageSucess)) { p.fillRect(0, 0, width(), height(), palette().color(QPalette::Active, QPalette::Window)); QPen pen(palette().color(QPalette::Active, QPalette::Text)); pen.setStyle(Qt::SolidLine); pen.setWidth(1); p.setPen(pen); p.drawRect(0, 0, width(), height()); if (d->uncalibratedColor) { p.drawText(0, 0, width(), height(), Qt::AlignCenter, i18n("Uncalibrated color space")); } else { p.setPen(Qt::red); p.drawText(0, 0, width(), height(), Qt::AlignCenter, i18n("No profile available...")); } return; } // Create CIE tongue if needed if (d->needUpdatePixmap) { updatePixmap(); } // draw prerendered tongue p.drawPixmap(0, 0, d->pixmap); } void CIETongueWidget::resizeEvent(QResizeEvent* event) { Q_UNUSED(event); setMinimumHeight(width()); d->needUpdatePixmap = true; } void CIETongueWidget::slotProgressTimerDone() { update(); d->progressTimer->start(200); } } // namespace Digikam diff --git a/core/libs/widgets/iccprofiles/iccpreviewwidget.cpp b/core/libs/widgets/iccprofiles/iccpreviewwidget.cpp index e836b1e6c3..e68bd39280 100644 --- a/core/libs/widgets/iccprofiles/iccpreviewwidget.cpp +++ b/core/libs/widgets/iccprofiles/iccpreviewwidget.cpp @@ -1,75 +1,75 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2006-01-12 * Description : a widget to display ICC profiles descriptions * in file dialog preview. * * Copyright (C) 2006-2007 by Francisco J. Cruz * Copyright (C) 2006-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 "iccpreviewwidget.h" // Qt includes #include #include #include // Local includes #include "digikam_debug.h" #include "iccprofilewidget.h" namespace Digikam { ICCPreviewWidget::ICCPreviewWidget(QWidget* const parent) : QScrollArea(parent) { m_iccProfileWidget = new ICCProfileWidget(this); setWidget(m_iccProfileWidget); setWidgetResizable(true); } ICCPreviewWidget::~ICCPreviewWidget() { } void ICCPreviewWidget::slotShowPreview(const QUrl& url) { slotClearPreview(); QFileInfo fInfo(url.toLocalFile()); - if ( url.isLocalFile() && fInfo.isFile() && fInfo.isReadable() ) + if (url.isLocalFile() && fInfo.isFile() && fInfo.isReadable()) { qCDebug(DIGIKAM_WIDGETS_LOG) << url << " is a readable local file"; m_iccProfileWidget->loadFromURL(url); } else { qCDebug(DIGIKAM_WIDGETS_LOG) << url << " is not a readable local file"; } } void ICCPreviewWidget::slotClearPreview() { m_iccProfileWidget->loadFromURL(QUrl()); } } // namespace Digikam diff --git a/core/libs/widgets/iccprofiles/iccprofilewidget.cpp b/core/libs/widgets/iccprofiles/iccprofilewidget.cpp index b9d5bc4b6d..b8577429a9 100644 --- a/core/libs/widgets/iccprofiles/iccprofilewidget.cpp +++ b/core/libs/widgets/iccprofiles/iccprofilewidget.cpp @@ -1,500 +1,500 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2006-06-23 * Description : a tab widget to display ICC profile infos * * Copyright (C) 2006-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 "iccprofilewidget.h" // Qt includes #include #include #include #include #include #include // KDE includes #include // Local includes #include "iccprofile.h" #include "digikam_debug.h" #include "cietonguewidget.h" #include "metadatalistview.h" namespace { static const char* ICCHumanList[] = { "Icc.Header.ColorSpace", "Icc.Header.Copyright", "Icc.Header.DeviceClass", "Icc.Header.Name", "Icc.Header.Description", "Icc.Header.RenderingIntent", "-1" }; // This entry list is only require for compatibility with MetadataWidget implementation. static const char* ICCEntryList[] = { "Header", "-1" }; } namespace Digikam { class Q_DECL_HIDDEN ICCTagInfo { public: ICCTagInfo() { } ICCTagInfo(const QString& title, const QString& description) : m_title(title), m_description(description) { } QString title() const { return m_title; } QString description() const { return m_description; } private: QString m_title; QString m_description; }; typedef QMap ICCTagInfoMap; // --------------------------------------------------------------------------------------- class Q_DECL_HIDDEN ICCProfileWidget::Private { public: explicit Private() { cieTongue = 0; } IccProfile profile; QStringList keysFilter; CIETongueWidget* cieTongue; ICCTagInfoMap iccTagsDescription; }; ICCProfileWidget::ICCProfileWidget(QWidget* const parent, int w, int h) : MetadataWidget(parent), d(new Private) { setup(); dkCmsErrorAction(LCMS_ERROR_SHOW); // Set the translated ICC tags titles/descriptions list d->iccTagsDescription[QLatin1String("Icc.Header.Name")] = ICCTagInfo(i18n("Name"), i18n("The ICC profile product name")); d->iccTagsDescription[QLatin1String("Icc.Header.Description")] = ICCTagInfo(i18n("Description"), i18n("The ICC profile product description")); d->iccTagsDescription[QLatin1String("Icc.Header.Information")] = ICCTagInfo(i18n("Information"), i18n("Additional ICC profile information")); d->iccTagsDescription[QLatin1String("Icc.Header.Manufacturer")] = ICCTagInfo(i18n("Manufacturer"), i18n("Raw information about the ICC profile manufacturer")); d->iccTagsDescription[QLatin1String("Icc.Header.Model")] = ICCTagInfo(i18n("Model"), i18n("Raw information about the ICC profile model")); d->iccTagsDescription[QLatin1String("Icc.Header.Copyright")] = ICCTagInfo(i18n("Copyright"), i18n("Raw information about the ICC profile copyright")); d->iccTagsDescription[QLatin1String("Icc.Header.ProfileID")] = ICCTagInfo(i18n("Profile ID"), i18n("The ICC profile ID number")); d->iccTagsDescription[QLatin1String("Icc.Header.ColorSpace")] = ICCTagInfo(i18n("Color Space"), i18n("The color space used by the ICC profile")); d->iccTagsDescription[QLatin1String("Icc.Header.ConnectionSpace")] = ICCTagInfo(i18n("Connection Space"), i18n("The connection space used by the ICC profile")); d->iccTagsDescription[QLatin1String("Icc.Header.DeviceClass")] = ICCTagInfo(i18n("Device Class"), i18n("The ICC profile device class")); d->iccTagsDescription[QLatin1String("Icc.Header.RenderingIntent")] = ICCTagInfo(i18n("Rendering Intent"), i18n("The ICC profile rendering intent")); d->iccTagsDescription[QLatin1String("Icc.Header.ProfileVersion")] = ICCTagInfo(i18n("Profile Version"), i18n("The ICC version used to record the profile")); d->iccTagsDescription[QLatin1String("Icc.Header.CMMFlags")] = ICCTagInfo(i18n("CMM Flags"), i18n("The ICC profile color management flags")); // Set the list of keys and tags filters. for (int i = 0 ; QLatin1String(ICCEntryList[i]) != QLatin1String("-1") ; ++i) { d->keysFilter << QLatin1String(ICCEntryList[i]); } QStringList tagsFilter; for (int i = 0 ; QLatin1String(ICCHumanList[i]) != QLatin1String("-1") ; ++i) { tagsFilter << QLatin1String(ICCHumanList[i]); } setTagsFilter(tagsFilter); // Add CIE tongue graph to the widget area d->cieTongue = new CIETongueWidget(w, h, this); d->cieTongue->setWhatsThis( i18n("

This area contains a CIE or chromaticity diagram. " "A CIE diagram is a representation of all the colors " "that a person with normal vision can see. This is represented " "by the colored sail-shaped area. In addition you will see a " "triangle that is superimposed on the diagram outlined in white. " "This triangle represents the outer boundaries of the color space " "of the device that is characterized by the inspected profile. " "This is called the device gamut.

" "

In addition there are black dots and yellow lines on the diagram. " "Each black dot represents one of the measurement points that were " "used to create this profile. The yellow line represents the " "amount that each point is corrected by the profile, and the " "direction of this correction.

")); setUserAreaWidget(d->cieTongue); decodeMetadata(); } ICCProfileWidget::~ICCProfileWidget() { delete d; } bool ICCProfileWidget::setProfile(const IccProfile& profile) { // Cleanup all metadata contents. setMetadataMap(); d->profile = profile; if (!d->profile.open()) { setMetadataEmpty(); d->cieTongue->setProfileData(); d->profile = IccProfile(); return false; } // Try to decode current metadata. enabledToolButtons(decodeMetadata()); // Refresh view using decoded metadata. buildView(); return true; } IccProfile ICCProfileWidget::getProfile() const { return d->profile; } void ICCProfileWidget::setDataLoading() { d->cieTongue->loadingStarted(); } void ICCProfileWidget::setLoadingFailed() { d->cieTongue->loadingFailed(); } void ICCProfileWidget::setUncalibratedColor() { d->cieTongue->uncalibratedColor(); } QString ICCProfileWidget::getMetadataTitle() { return i18n("ICC Color Profile Information"); } bool ICCProfileWidget::loadFromURL(const QUrl& url) { setFileName(url.toLocalFile()); if (url.isEmpty()) { setProfile(IccProfile()); d->cieTongue->setProfileData(); return false; } else { IccProfile profile(url.toLocalFile()); if (!setProfile(profile)) { setProfile(IccProfile()); d->cieTongue->setProfileData(); return false; } } return true; } bool ICCProfileWidget::loadFromProfileData(const QString& fileName, const QByteArray& data) { setFileName(fileName); return(setProfile(IccProfile(data))); } bool ICCProfileWidget::loadProfile(const QString& fileName, const IccProfile& profile) { setFileName(fileName); return(setProfile(profile)); } bool ICCProfileWidget::decodeMetadata() { if (!d->profile.isOpen()) { return false; } d->cieTongue->setProfileData(d->profile.data()); LcmsLock lock; cmsHPROFILE hProfile = d->profile.handle(); if (!hProfile) { qCDebug(DIGIKAM_WIDGETS_LOG) << "Cannot parse ICC profile tags using LCMS"; return false; } DMetadata::MetaDataMap metaDataMap; - if ( !QString(dkCmsTakeProductName(hProfile)).isEmpty() ) + if (!QString(dkCmsTakeProductName(hProfile)).isEmpty()) { metaDataMap.insert(QLatin1String("Icc.Header.Name"), dkCmsTakeProductName(hProfile).replace(QLatin1Char('\n'), QLatin1Char(' '))); } - if ( !dkCmsTakeProductDesc(hProfile).isEmpty() ) + if (!dkCmsTakeProductDesc(hProfile).isEmpty()) { metaDataMap.insert(QLatin1String("Icc.Header.Description"), dkCmsTakeProductDesc(hProfile).replace(QLatin1Char('\n'), QLatin1Char(' '))); } - if ( !dkCmsTakeProductInfo(hProfile).isEmpty() ) + if (!dkCmsTakeProductInfo(hProfile).isEmpty()) { metaDataMap.insert(QLatin1String("Icc.Header.Information"), dkCmsTakeProductInfo(hProfile).replace(QLatin1Char('\n'), QLatin1Char(' '))); } - if ( !dkCmsTakeManufacturer(hProfile).isEmpty() ) + if (!dkCmsTakeManufacturer(hProfile).isEmpty()) { metaDataMap.insert(QLatin1String("Icc.Header.Manufacturer"), dkCmsTakeManufacturer(hProfile).replace(QLatin1Char('\n'), QLatin1Char(' '))); } - if ( !dkCmsTakeModel(hProfile).isEmpty() ) + if (!dkCmsTakeModel(hProfile).isEmpty()) { metaDataMap.insert(QLatin1String("Icc.Header.Model"), dkCmsTakeModel(hProfile).replace(QLatin1Char('\n'), QLatin1Char(' '))); } - if ( !dkCmsTakeCopyright(hProfile).isEmpty() ) + if (!dkCmsTakeCopyright(hProfile).isEmpty()) { metaDataMap.insert(QLatin1String("Icc.Header.Copyright"), dkCmsTakeCopyright(hProfile).replace(QLatin1Char('\n'), QLatin1Char(' '))); } metaDataMap.insert(QLatin1String("Icc.Header.ProfileID"), QString::number((uint)*dkCmsTakeProfileID(hProfile))); metaDataMap.insert(QLatin1String("Icc.Header.ProfileVersion"), QString::number((uint)dkCmsGetProfileICCversion(hProfile))); metaDataMap.insert(QLatin1String("Icc.Header.CMMFlags"), QString::number((uint)dkCmsTakeHeaderFlags(hProfile))); QString colorSpace; switch (dkCmsGetColorSpace(hProfile)) { case icSigLabData: colorSpace = i18n("Lab"); break; case icSigLuvData: colorSpace = i18n("Luv"); break; case icSigRgbData: colorSpace = i18n("RGB"); break; case icSigGrayData: colorSpace = i18n("GRAY"); break; case icSigHsvData: colorSpace = i18n("HSV"); break; case icSigHlsData: colorSpace = i18n("HLS"); break; case icSigCmykData: colorSpace = i18n("CMYK"); break; case icSigCmyData: colorSpace= i18n("CMY"); break; default: colorSpace = i18n("Unknown"); break; } metaDataMap.insert(QLatin1String("Icc.Header.ColorSpace"), colorSpace); QString connectionSpace; switch (dkCmsGetPCS(hProfile)) { case icSigLabData: connectionSpace = i18n("Lab"); break; case icSigLuvData: connectionSpace = i18n("Luv"); break; case icSigRgbData: connectionSpace = i18n("RGB"); break; case icSigGrayData: connectionSpace = i18n("GRAY"); break; case icSigHsvData: connectionSpace = i18n("HSV"); break; case icSigHlsData: connectionSpace = i18n("HLS"); break; case icSigCmykData: connectionSpace = i18n("CMYK"); break; case icSigCmyData: connectionSpace= i18n("CMY"); break; default: connectionSpace = i18n("Unknown"); break; } metaDataMap.insert(QLatin1String("Icc.Header.ConnectionSpace"), connectionSpace); QString device; switch ((int)dkCmsGetDeviceClass(hProfile)) { case icSigInputClass: device = i18n("Input device"); break; case icSigDisplayClass: device = i18n("Display device"); break; case icSigOutputClass: device = i18n("Output device"); break; case icSigColorSpaceClass: device = i18n("Color space"); break; case icSigLinkClass: device = i18n("Link device"); break; case icSigAbstractClass: device = i18n("Abstract"); break; case icSigNamedColorClass: device = i18n("Named color"); break; default: device = i18n("Unknown"); break; } metaDataMap.insert(QLatin1String("Icc.Header.DeviceClass"), device); QString intent; switch (dkCmsTakeRenderingIntent(hProfile)) { case 0: intent = i18n("Perceptual"); break; case 1: intent = i18n("Relative Colorimetric"); break; case 2: intent = i18n("Saturation"); break; case 3: intent = i18n("Absolute Colorimetric"); break; default: intent = i18n("Unknown"); break; } metaDataMap.insert(QLatin1String("Icc.Header.RenderingIntent"), intent); // Update all metadata contents. setMetadataMap(metaDataMap); return true; } void ICCProfileWidget::buildView() { if (getMode() == CUSTOM) { setIfdList(getMetadataMap(), d->keysFilter, getTagsFilter()); } else { setIfdList(getMetadataMap(), d->keysFilter, QStringList() << QLatin1String("FULL")); } MetadataWidget::buildView(); } QString ICCProfileWidget::getTagTitle(const QString& key) { ICCTagInfoMap::const_iterator it = d->iccTagsDescription.constFind(key); if (it != d->iccTagsDescription.constEnd()) { return(it.value().title()); } return key.section(QLatin1Char('.'), 2, 2); } void ICCProfileWidget::slotSaveMetadataToFile() { QUrl url = saveMetadataToFile(i18n("ICC color profile File to Save"), QString(QLatin1String("*.icc *.icm|") + i18n("ICC Files (*.icc; *.icm)"))); storeMetadataToFile(url, d->profile.data()); } QString ICCProfileWidget::getTagDescription(const QString& key) { ICCTagInfoMap::const_iterator it = d->iccTagsDescription.constFind(key); if (it != d->iccTagsDescription.constEnd()) { return(it.value().description()); } return key.section(QLatin1Char('.'), 2, 2); } } // namespace Digikam diff --git a/core/libs/widgets/layout/dexpanderbox.cpp b/core/libs/widgets/layout/dexpanderbox.cpp index 34961b330c..620593c60c 100644 --- a/core/libs/widgets/layout/dexpanderbox.cpp +++ b/core/libs/widgets/layout/dexpanderbox.cpp @@ -1,971 +1,971 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2008-03-14 * Description : A widget to host settings as expander box * * Copyright (C) 2008-2013 by Marcel Wiesweg * Copyright (C) 2008-2019 by Gilles Caulier * Copyright (C) 2010 by Manuel Viet * * 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 "dexpanderbox.h" // Qt includes #include #include #include #include #include #include #include #include #include // KDE includes #include // Local includes #include "dlayoutbox.h" namespace Digikam { DLineWidget::DLineWidget(Qt::Orientation orientation, QWidget* const parent) : QFrame(parent) { setLineWidth(1); setMidLineWidth(0); setFrameShadow(QFrame::Sunken); if (orientation == Qt::Vertical) { setFrameShape(QFrame::VLine); setMinimumSize(2, 0); } else { setFrameShape(QFrame::HLine); setMinimumSize(0, 2); } updateGeometry(); } DLineWidget::~DLineWidget() { } // ------------------------------------------------------------------------------------ class Q_DECL_HIDDEN DAdjustableLabel::Private { public: explicit Private() { emode = Qt::ElideMiddle; } QString ajdText; Qt::TextElideMode emode; }; DAdjustableLabel::DAdjustableLabel(QWidget* const parent) : QLabel(parent), d(new Private) { setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed)); } DAdjustableLabel::~DAdjustableLabel() { delete d; } void DAdjustableLabel::resizeEvent(QResizeEvent*) { adjustTextToLabel(); } QSize DAdjustableLabel::minimumSizeHint() const { QSize sh = QLabel::minimumSizeHint(); sh.setWidth(-1); return sh; } QSize DAdjustableLabel::sizeHint() const { QFontMetrics fm(fontMetrics()); int maxW = QApplication::desktop()->screenGeometry(this).width() * 3 / 4; int currentW = fm.width(d->ajdText); return (QSize(currentW > maxW ? maxW : currentW, QLabel::sizeHint().height())); } void DAdjustableLabel::setAdjustedText(const QString& text) { d->ajdText = text; if (d->ajdText.isNull()) QLabel::clear(); adjustTextToLabel(); } QString DAdjustableLabel::adjustedText() const { return d->ajdText; } void DAdjustableLabel::setAlignment(Qt::Alignment alignment) { QString tmp(d->ajdText); QLabel::setAlignment(alignment); d->ajdText = tmp; } void DAdjustableLabel::setElideMode(Qt::TextElideMode mode) { d->emode = mode; adjustTextToLabel(); } void DAdjustableLabel::adjustTextToLabel() { QFontMetrics fm(fontMetrics()); QStringList adjustedLines; int lblW = size().width(); bool adjusted = false; foreach(const QString& line, d->ajdText.split(QLatin1Char('\n'))) { int lineW = fm.width(line); if (lineW > lblW) { adjusted = true; adjustedLines << fm.elidedText(line, d->emode, lblW); } else { adjustedLines << line; } } if (adjusted) { QLabel::setText(adjustedLines.join(QLatin1Char('\n'))); setToolTip(d->ajdText); } else { QLabel::setText(d->ajdText); setToolTip(QString()); } } // ------------------------------------------------------------------------------------ DClickLabel::DClickLabel(QWidget* const parent) : QLabel(parent) { setCursor(Qt::PointingHandCursor); } DClickLabel::DClickLabel(const QString& text, QWidget* const parent) : QLabel(text, parent) { setCursor(Qt::PointingHandCursor); } DClickLabel::~DClickLabel() { } void DClickLabel::mousePressEvent(QMouseEvent* event) { QLabel::mousePressEvent(event); /* * In some contexts, like QGraphicsView, there will be no * release event if the press event was not accepted. */ if (event->button() == Qt::LeftButton) { event->accept(); } } void DClickLabel::mouseReleaseEvent(QMouseEvent* event) { QLabel::mouseReleaseEvent(event); if (event->button() == Qt::LeftButton) { emit leftClicked(); emit activated(); event->accept(); } } void DClickLabel::keyPressEvent(QKeyEvent* e) { switch (e->key()) { case Qt::Key_Down: case Qt::Key_Right: case Qt::Key_Space: emit activated(); return; default: break; } QLabel::keyPressEvent(e); } // ------------------------------------------------------------------------ DSqueezedClickLabel::DSqueezedClickLabel(QWidget* const parent) : DAdjustableLabel(parent) { setCursor(Qt::PointingHandCursor); } DSqueezedClickLabel::DSqueezedClickLabel(const QString& text, QWidget* const parent) : DAdjustableLabel(parent) { setAdjustedText(text); setCursor(Qt::PointingHandCursor); } DSqueezedClickLabel::~DSqueezedClickLabel() { } void DSqueezedClickLabel::mouseReleaseEvent(QMouseEvent* event) { QLabel::mouseReleaseEvent(event); if (event->button() == Qt::LeftButton) { emit leftClicked(); emit activated(); event->accept(); } } void DSqueezedClickLabel::mousePressEvent(QMouseEvent* event) { QLabel::mousePressEvent(event); /* * In some contexts, like QGraphicsView, there will be no * release event if the press event was not accepted. */ if (event->button() == Qt::LeftButton) { event->accept(); } } void DSqueezedClickLabel::keyPressEvent(QKeyEvent* e) { switch (e->key()) { case Qt::Key_Down: case Qt::Key_Right: case Qt::Key_Space: emit activated(); return; default: break; } QLabel::keyPressEvent(e); } // ------------------------------------------------------------------------ DArrowClickLabel::DArrowClickLabel(QWidget* const parent) : QWidget(parent), m_arrowType(Qt::DownArrow) { setCursor(Qt::PointingHandCursor); setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); m_size = 8; m_margin = 2; } void DArrowClickLabel::setArrowType(Qt::ArrowType type) { m_arrowType = type; update(); } DArrowClickLabel::~DArrowClickLabel() { } Qt::ArrowType DArrowClickLabel::arrowType() const { return m_arrowType; } void DArrowClickLabel::mousePressEvent(QMouseEvent* event) { /* * In some contexts, like QGraphicsView, there will be no * release event if the press event was not accepted. */ if (event->button() == Qt::LeftButton) { event->accept(); } } void DArrowClickLabel::mouseReleaseEvent(QMouseEvent* event) { if (event->button() == Qt::LeftButton) { emit leftClicked(); } } void DArrowClickLabel::paintEvent(QPaintEvent*) { // Inspired by karrowbutton.cpp, // Copyright (C) 2001 Frerich Raabe QPainter p(this); QStyleOptionFrame opt; opt.init(this); opt.lineWidth = 2; opt.midLineWidth = 0; /* p.fillRect( rect(), palette().brush( QPalette::Window ) ); style()->drawPrimitive( QStyle::PE_Frame, &opt, &p, this); */ if (m_arrowType == Qt::NoArrow) return; if (width() < m_size + m_margin || height() < m_size + m_margin) return; // don't draw arrows if we are too small unsigned int x = 0, y = 0; if (m_arrowType == Qt::DownArrow) { x = (width() - m_size) / 2; y = height() - (m_size + m_margin); } else if (m_arrowType == Qt::UpArrow) { x = (width() - m_size) / 2; y = m_margin; } else if (m_arrowType == Qt::RightArrow) { x = width() - (m_size + m_margin); y = (height() - m_size) / 2; } else // arrowType == LeftArrow { x = m_margin; y = (height() - m_size) / 2; } /* if (isDown()) { ++x; ++y; } */ QStyle::PrimitiveElement e = QStyle::PE_IndicatorArrowLeft; switch (m_arrowType) { case Qt::LeftArrow: e = QStyle::PE_IndicatorArrowLeft; break; case Qt::RightArrow: e = QStyle::PE_IndicatorArrowRight; break; case Qt::UpArrow: e = QStyle::PE_IndicatorArrowUp; break; case Qt::DownArrow: e = QStyle::PE_IndicatorArrowDown; break; case Qt::NoArrow: break; } opt.state |= QStyle::State_Enabled; opt.rect = QRect( x, y, m_size, m_size); style()->drawPrimitive( e, &opt, &p, this ); } QSize DArrowClickLabel::sizeHint() const { return QSize(m_size + 2*m_margin, m_size + 2*m_margin); } // ------------------------------------------------------------------------ class Q_DECL_HIDDEN DLabelExpander::Private { public: explicit Private() { clickLabel = 0; containerWidget = 0; pixmapLabel = 0; grid = 0; arrow = 0; line = 0; hbox = 0; checkBox = 0; expandByDefault = true; } bool expandByDefault; QCheckBox* checkBox; QLabel* pixmapLabel; QWidget* containerWidget; QGridLayout* grid; DLineWidget* line; QWidget* hbox; DArrowClickLabel* arrow; DClickLabel* clickLabel; }; DLabelExpander::DLabelExpander(QWidget* const parent) : QWidget(parent), d(new Private) { const int spacing = QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); d->grid = new QGridLayout(this); d->line = new DLineWidget(Qt::Horizontal, this); d->hbox = new QWidget(this); d->arrow = new DArrowClickLabel(d->hbox); d->checkBox = new QCheckBox(d->hbox); d->pixmapLabel = new QLabel(d->hbox); d->clickLabel = new DClickLabel(d->hbox); QHBoxLayout* const hlay = new QHBoxLayout(d->hbox); hlay->addWidget(d->arrow); hlay->addWidget(d->checkBox); hlay->addWidget(d->pixmapLabel); hlay->addWidget(d->clickLabel, 10); hlay->setContentsMargins(QMargins()); hlay->setSpacing(spacing); d->pixmapLabel->installEventFilter(this); d->pixmapLabel->setCursor(Qt::PointingHandCursor); d->hbox->setCursor(Qt::PointingHandCursor); setCheckBoxVisible(false); d->grid->addWidget(d->line, 0, 0, 1, 3); d->grid->addWidget(d->hbox, 1, 0, 1, 3); d->grid->setColumnStretch(2, 10); d->grid->setContentsMargins(spacing, spacing, spacing, spacing); d->grid->setSpacing(spacing); connect(d->arrow, &DArrowClickLabel::leftClicked, this, &DLabelExpander::slotToggleContainer); connect(d->clickLabel, &DClickLabel::activated, this, &DLabelExpander::slotToggleContainer); connect(d->checkBox, &QCheckBox::toggled, this, &DLabelExpander::signalToggled); } DLabelExpander::~DLabelExpander() { delete d; } void DLabelExpander::setCheckBoxVisible(bool b) { d->checkBox->setVisible(b); } bool DLabelExpander::checkBoxIsVisible() const { return d->checkBox->isVisible(); } void DLabelExpander::setChecked(bool b) { d->checkBox->setChecked(b); } bool DLabelExpander::isChecked() const { return d->checkBox->isChecked(); } void DLabelExpander::setLineVisible(bool b) { d->line->setVisible(b); } bool DLabelExpander::lineIsVisible() const { return d->line->isVisible(); } void DLabelExpander::setText(const QString& txt) { d->clickLabel->setText(QString::fromUtf8("%1").arg(txt)); } QString DLabelExpander::text() const { return d->clickLabel->text(); } void DLabelExpander::setIcon(const QIcon& icon) { d->pixmapLabel->setPixmap(icon.pixmap(style()->pixelMetric(QStyle::PM_SmallIconSize))); } QIcon DLabelExpander::icon() const { return QIcon(*d->pixmapLabel->pixmap()); } void DLabelExpander::setWidget(QWidget* const widget) { if (widget) { d->containerWidget = widget; d->containerWidget->setParent(this); d->grid->addWidget(d->containerWidget, 2, 0, 1, 3); } } QWidget* DLabelExpander::widget() const { return d->containerWidget; } void DLabelExpander::setExpandByDefault(bool b) { d->expandByDefault = b; } bool DLabelExpander::isExpandByDefault() const { return d->expandByDefault; } void DLabelExpander::setExpanded(bool b) { if (d->containerWidget) { d->containerWidget->setVisible(b); if (b) d->arrow->setArrowType(Qt::DownArrow); else d->arrow->setArrowType(Qt::RightArrow); } emit signalExpanded(b); } bool DLabelExpander::isExpanded() const { return (d->arrow->arrowType() == Qt::DownArrow); } void DLabelExpander::slotToggleContainer() { if (d->containerWidget) setExpanded(!d->containerWidget->isVisible()); } bool DLabelExpander::eventFilter(QObject* obj, QEvent* ev) { - if ( obj == d->pixmapLabel) + if (obj == d->pixmapLabel) { - if ( ev->type() == QEvent::MouseButtonRelease) + if (ev->type() == QEvent::MouseButtonRelease) { slotToggleContainer(); return false; } else { return false; } } else { // pass the event on to the parent class return QWidget::eventFilter(obj, ev); } } // ------------------------------------------------------------------------ class Q_DECL_HIDDEN DExpanderBox::Private { public: explicit Private(DExpanderBox* const box) { parent = box; vbox = 0; } void createItem(int index, QWidget* const w, const QIcon& icon, const QString& txt, const QString& objName, bool expandBydefault) { DLabelExpander* const exp = new DLabelExpander(parent->viewport()); exp->setText(txt); exp->setIcon(icon.pixmap(QApplication::style()->pixelMetric(QStyle::PM_SmallIconSize))); exp->setWidget(w); exp->setLineVisible(!wList.isEmpty()); exp->setObjectName(objName); exp->setExpandByDefault(expandBydefault); if (index >= 0) { vbox->insertWidget(index, exp); wList.insert(index, exp); } else { vbox->addWidget(exp); wList.append(exp); } parent->connect(exp, SIGNAL(signalExpanded(bool)), parent, SLOT(slotItemExpanded(bool))); parent->connect(exp, SIGNAL(signalToggled(bool)), parent, SLOT(slotItemToggled(bool))); } public: QList wList; QVBoxLayout* vbox; DExpanderBox* parent; }; DExpanderBox::DExpanderBox(QWidget* const parent) : QScrollArea(parent), d(new Private(this)) { setFrameStyle(QFrame::NoFrame); setWidgetResizable(true); QWidget* const main = new QWidget(viewport()); d->vbox = new QVBoxLayout(main); d->vbox->setSpacing(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); d->vbox->setContentsMargins(QMargins()); setWidget(main); setAutoFillBackground(false); viewport()->setAutoFillBackground(false); main->setAutoFillBackground(false); } DExpanderBox::~DExpanderBox() { d->wList.clear(); delete d; } void DExpanderBox::setCheckBoxVisible(int index, bool b) { if (index > d->wList.count() || index < 0) return; d->wList[index]->setCheckBoxVisible(b); } bool DExpanderBox::checkBoxIsVisible(int index) const { if (index > d->wList.count() || index < 0) return false; return d->wList[index]->checkBoxIsVisible(); } void DExpanderBox::setChecked(int index, bool b) { if (index > d->wList.count() || index < 0) return; d->wList[index]->setChecked(b); } bool DExpanderBox::isChecked(int index) const { if (index > d->wList.count() || index < 0) return false; return d->wList[index]->isChecked(); } void DExpanderBox::addItem(QWidget* const w, const QIcon& icon, const QString& txt, const QString& objName, bool expandBydefault) { d->createItem(-1, w, icon, txt, objName, expandBydefault); } void DExpanderBox::addItem(QWidget* const w, const QString& txt, const QString& objName, bool expandBydefault) { addItem(w, QIcon(), txt, objName, expandBydefault); } void DExpanderBox::addStretch() { d->vbox->addStretch(10); } void DExpanderBox::insertItem(int index, QWidget* const w, const QIcon& icon, const QString& txt, const QString& objName, bool expandBydefault) { d->createItem(index, w, icon, txt, objName, expandBydefault); } void DExpanderBox::slotItemExpanded(bool b) { DLabelExpander* const exp = dynamic_cast(sender()); if (exp) { int index = indexOf(exp); emit signalItemExpanded(index, b); } } void DExpanderBox::slotItemToggled(bool b) { DLabelExpander* const exp = dynamic_cast(sender()); if (exp) { int index = indexOf(exp); emit signalItemToggled(index, b); } } void DExpanderBox::insertItem(int index, QWidget* const w, const QString& txt, const QString& objName, bool expandBydefault) { insertItem(index, w, QIcon(), txt, objName, expandBydefault); } void DExpanderBox::insertStretch(int index) { d->vbox->insertStretch(index, 10); } void DExpanderBox::removeItem(int index) { if (index > d->wList.count() || index < 0) return; d->wList[index]->hide(); d->wList.removeAt(index); } void DExpanderBox::setItemText(int index, const QString& txt) { if (index > d->wList.count() || index < 0) return; d->wList[index]->setText(txt); } QString DExpanderBox::itemText(int index) const { if (index > d->wList.count() || index < 0) return QString(); return d->wList[index]->text(); } void DExpanderBox::setItemIcon(int index, const QIcon& icon) { if (index > d->wList.count() || index < 0) return; d->wList[index]->setIcon(icon.pixmap(style()->pixelMetric(QStyle::PM_SmallIconSize))); } QIcon DExpanderBox::itemIcon(int index) const { if (index > d->wList.count() || index < 0) return QIcon(); return d->wList[index]->icon(); } int DExpanderBox::count() const { return d->wList.count(); } void DExpanderBox::setItemToolTip(int index, const QString& tip) { if (index > d->wList.count() || index < 0) return; d->wList[index]->setToolTip(tip); } QString DExpanderBox::itemToolTip(int index) const { if (index > d->wList.count() || index < 0) return QString(); return d->wList[index]->toolTip(); } void DExpanderBox::setItemEnabled(int index, bool enabled) { if (index > d->wList.count() || index < 0) return; d->wList[index]->setEnabled(enabled); } bool DExpanderBox::isItemEnabled(int index) const { if (index > d->wList.count() || index < 0) return false; return d->wList[index]->isEnabled(); } DLabelExpander* DExpanderBox::widget(int index) const { if (index > d->wList.count() || index < 0) return 0; return d->wList[index]; } int DExpanderBox::indexOf(DLabelExpander* const widget) const { for (int i = 0 ; i < count(); ++i) { DLabelExpander* const exp = d->wList[i]; if (widget == exp) return i; } return -1; } void DExpanderBox::setItemExpanded(int index, bool b) { if (index > d->wList.count() || index < 0) return; DLabelExpander* const exp = d->wList[index]; if (!exp) return; exp->setExpanded(b); } bool DExpanderBox::isItemExpanded(int index) const { if (index > d->wList.count() || index < 0) return false; DLabelExpander* const exp = d->wList[index]; if (!exp) return false; return (exp->isExpanded()); } void DExpanderBox::readSettings(KConfigGroup& group) { for (int i = 0 ; i < count(); ++i) { DLabelExpander* const exp = d->wList[i]; if (exp) { exp->setExpanded(group.readEntry(QString::fromUtf8("%1 Expanded").arg(exp->objectName()), exp->isExpandByDefault())); } } } void DExpanderBox::writeSettings(KConfigGroup& group) { for (int i = 0 ; i < count(); ++i) { DLabelExpander* const exp = d->wList[i]; if (exp) { group.writeEntry(QString::fromUtf8("%1 Expanded").arg(exp->objectName()), exp->isExpanded()); } } } // ------------------------------------------------------------------------ DExpanderBoxExclusive::DExpanderBoxExclusive(QWidget* const parent) : DExpanderBox(parent) { setIsToolBox(true); } DExpanderBoxExclusive::~DExpanderBoxExclusive() { } void DExpanderBoxExclusive::slotItemExpanded(bool b) { DLabelExpander* const exp = dynamic_cast(sender()); if (!exp) return; if (isToolBox() && b) { int item = 0; while (item < count()) { if (isItemExpanded(item) && item != indexOf(exp)) { setItemExpanded(item, false); } item++; } } emit signalItemExpanded(indexOf(exp), b); } void DExpanderBoxExclusive::setIsToolBox(bool b) { m_toolbox = b; } bool DExpanderBoxExclusive::isToolBox() const { return (m_toolbox); } } // namespace Digikam diff --git a/core/libs/widgets/metadata/colorlabelwidget.cpp b/core/libs/widgets/metadata/colorlabelwidget.cpp index 8707bb68c9..5dca447cba 100644 --- a/core/libs/widgets/metadata/colorlabelwidget.cpp +++ b/core/libs/widgets/metadata/colorlabelwidget.cpp @@ -1,549 +1,549 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2011-01-28 * Description : color label widget * * 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 "colorlabelwidget.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include #include // Local includes #include "dlayoutbox.h" #include "dxmlguiwindow.h" #include "dexpanderbox.h" namespace Digikam { class Q_DECL_HIDDEN ColorLabelWidget::Private { public: explicit Private() { colorBtns = 0; btnNone = 0; btnRed = 0; btnOrange = 0; btnYellow = 0; btnGreen = 0; btnBlue = 0; btnMagenta = 0; btnGray = 0; btnBlack = 0; btnWhite = 0; desc = 0; descBox = 0; shortcut = 0; } QButtonGroup* colorBtns; QLabel* desc; QToolButton* btnNone; QToolButton* btnRed; QToolButton* btnOrange; QToolButton* btnYellow; QToolButton* btnGreen; QToolButton* btnBlue; QToolButton* btnMagenta; QToolButton* btnGray; QToolButton* btnBlack; QToolButton* btnWhite; DHBox* descBox; DAdjustableLabel* shortcut; }; ColorLabelWidget::ColorLabelWidget(QWidget* const parent) : DVBox(parent), d(new Private) { setAttribute(Qt::WA_DeleteOnClose); setFocusPolicy(Qt::NoFocus); DHBox* const hbox = new DHBox(this); hbox->setContentsMargins(QMargins()); hbox->setSpacing(0); d->btnNone = new QToolButton(hbox); d->btnNone->setCheckable(true); d->btnNone->setFocusPolicy(Qt::NoFocus); d->btnNone->setIcon(buildIcon(NoColorLabel)); d->btnNone->installEventFilter(this); d->btnRed = new QToolButton(hbox); d->btnRed->setCheckable(true); d->btnRed->setFocusPolicy(Qt::NoFocus); d->btnRed->setIcon(buildIcon(RedLabel)); d->btnRed->installEventFilter(this); d->btnOrange = new QToolButton(hbox); d->btnOrange->setCheckable(true); d->btnOrange->setFocusPolicy(Qt::NoFocus); d->btnOrange->setIcon(buildIcon(OrangeLabel)); d->btnOrange->installEventFilter(this); d->btnYellow = new QToolButton(hbox); d->btnYellow->setCheckable(true); d->btnYellow->setFocusPolicy(Qt::NoFocus); d->btnYellow->setIcon(buildIcon(YellowLabel)); d->btnYellow->installEventFilter(this); d->btnGreen = new QToolButton(hbox); d->btnGreen->setCheckable(true); d->btnGreen->setFocusPolicy(Qt::NoFocus); d->btnGreen->setIcon(buildIcon(GreenLabel)); d->btnGreen->installEventFilter(this); d->btnBlue = new QToolButton(hbox); d->btnBlue->setCheckable(true); d->btnBlue->setFocusPolicy(Qt::NoFocus); d->btnBlue->setIcon(buildIcon(BlueLabel)); d->btnBlue->installEventFilter(this); d->btnMagenta = new QToolButton(hbox); d->btnMagenta->setCheckable(true); d->btnMagenta->setFocusPolicy(Qt::NoFocus); d->btnMagenta->setIcon(buildIcon(MagentaLabel)); d->btnMagenta->installEventFilter(this); d->btnGray = new QToolButton(hbox); d->btnGray->setCheckable(true); d->btnGray->setFocusPolicy(Qt::NoFocus); d->btnGray->setIcon(buildIcon(GrayLabel)); d->btnGray->installEventFilter(this); d->btnBlack = new QToolButton(hbox); d->btnBlack->setCheckable(true); d->btnBlack->setFocusPolicy(Qt::NoFocus); d->btnBlack->setIcon(buildIcon(BlackLabel)); d->btnBlack->installEventFilter(this); d->btnWhite = new QToolButton(hbox); d->btnWhite->setCheckable(true); d->btnWhite->setFocusPolicy(Qt::NoFocus); d->btnWhite->setIcon(buildIcon(WhiteLabel)); d->btnWhite->installEventFilter(this); d->colorBtns = new QButtonGroup(hbox); d->colorBtns->addButton(d->btnNone, NoColorLabel); d->colorBtns->addButton(d->btnRed, RedLabel); d->colorBtns->addButton(d->btnOrange, OrangeLabel); d->colorBtns->addButton(d->btnYellow, YellowLabel); d->colorBtns->addButton(d->btnGreen, GreenLabel); d->colorBtns->addButton(d->btnBlue, BlueLabel); d->colorBtns->addButton(d->btnMagenta, MagentaLabel); d->colorBtns->addButton(d->btnGray, GrayLabel); d->colorBtns->addButton(d->btnBlack, BlackLabel); d->colorBtns->addButton(d->btnWhite, WhiteLabel); d->descBox = new DHBox(this); d->descBox->setContentsMargins(QMargins()); d->descBox->setSpacing(0); d->desc = new QLabel(d->descBox); d->shortcut = new DAdjustableLabel(d->descBox); QFont fnt = d->shortcut->font(); fnt.setItalic(true); d->shortcut->setFont(fnt); d->shortcut->setAlignment(Qt::AlignRight | Qt::AlignVCenter); d->shortcut->setWordWrap(false); setSpacing(0); setContentsMargins(QMargins()); setColorLabels(QList() << NoColorLabel); setDescriptionBoxVisible(true); setButtonsExclusive(true); // ------------------------------------------------------------- connect(d->colorBtns, SIGNAL(buttonReleased(int)), this, SIGNAL(signalColorLabelChanged(int))); } ColorLabelWidget::~ColorLabelWidget() { delete d; } void ColorLabelWidget::setDescriptionBoxVisible(bool b) { d->descBox->setVisible(b); if (!b) { foreach(QAbstractButton* const btn, d->colorBtns->buttons()) { ColorLabel id = (ColorLabel)(d->colorBtns->id(btn)); btn->setToolTip(labelColorName(id)); } } } void ColorLabelWidget::setButtonsExclusive(bool b) { d->colorBtns->setExclusive(b); } void ColorLabelWidget::updateDescription(ColorLabel label) { d->desc->setText(labelColorName(label)); DXmlGuiWindow* const app = dynamic_cast(qApp->activeWindow()); if (app) { QAction* const ac = app->actionCollection()->action(QString::fromLatin1("colorshortcut-%1").arg(label)); if (ac) { d->shortcut->setAdjustedText(ac->shortcut().toString()); } } } bool ColorLabelWidget::eventFilter(QObject* obj, QEvent* ev) { - if ( obj == d->btnNone) + if (obj == d->btnNone) { - if ( ev->type() == QEvent::Enter) + if (ev->type() == QEvent::Enter) { updateDescription(NoColorLabel); return false; } } - if ( obj == d->btnRed) + if (obj == d->btnRed) { - if ( ev->type() == QEvent::Enter) + if (ev->type() == QEvent::Enter) { updateDescription(RedLabel); return false; } } - if ( obj == d->btnOrange) + if (obj == d->btnOrange) { - if ( ev->type() == QEvent::Enter) + if (ev->type() == QEvent::Enter) { updateDescription(OrangeLabel); return false; } } - if ( obj == d->btnYellow) + if (obj == d->btnYellow) { - if ( ev->type() == QEvent::Enter) + if (ev->type() == QEvent::Enter) { updateDescription(YellowLabel); return false; } } - if ( obj == d->btnGreen) + if (obj == d->btnGreen) { - if ( ev->type() == QEvent::Enter) + if (ev->type() == QEvent::Enter) { updateDescription(GreenLabel); return false; } } - if ( obj == d->btnBlue) + if (obj == d->btnBlue) { - if ( ev->type() == QEvent::Enter) + if (ev->type() == QEvent::Enter) { updateDescription(BlueLabel); return false; } } - if ( obj == d->btnMagenta) + if (obj == d->btnMagenta) { - if ( ev->type() == QEvent::Enter) + if (ev->type() == QEvent::Enter) { updateDescription(MagentaLabel); return false; } } - if ( obj == d->btnGray) + if (obj == d->btnGray) { - if ( ev->type() == QEvent::Enter) + if (ev->type() == QEvent::Enter) { updateDescription(GrayLabel); return false; } } - if ( obj == d->btnBlack) + if (obj == d->btnBlack) { - if ( ev->type() == QEvent::Enter) + if (ev->type() == QEvent::Enter) { updateDescription(BlackLabel); return false; } } - if ( obj == d->btnWhite) + if (obj == d->btnWhite) { - if ( ev->type() == QEvent::Enter) + if (ev->type() == QEvent::Enter) { updateDescription(WhiteLabel); return false; } } // pass the event on to the parent class return QWidget::eventFilter(obj, ev); } void ColorLabelWidget::setColorLabels(const QList& list) { foreach(QAbstractButton* const btn, d->colorBtns->buttons()) { ColorLabel id = (ColorLabel)(d->colorBtns->id(btn)); btn->setChecked(list.contains(id)); updateDescription(id); } } QList ColorLabelWidget::colorLabels() const { QList list; foreach(QAbstractButton* const btn, d->colorBtns->buttons()) { if (btn && btn->isChecked()) list.append((ColorLabel)(d->colorBtns->id(btn))); } return list; } QIcon ColorLabelWidget::buildIcon(ColorLabel label, int size) { if (label != NoColorLabel) { QPixmap pix(size, size); QPainter p(&pix); p.setPen(qApp->palette().color(QPalette::Active, QPalette::ButtonText)); p.fillRect(0, 0, pix.width()-1, pix.height()-1, labelColor(label)); p.drawRect(0, 0, pix.width()-1, pix.height()-1); return QIcon(pix); } return QIcon::fromTheme(QLatin1String("emblem-unmounted")); } QColor ColorLabelWidget::labelColor(ColorLabel label) { QColor color; switch(label) { case RedLabel: color = qRgb(0xDF, 0x6E, 0x5F); break; case OrangeLabel: color = qRgb(0xEE, 0xAF, 0x6B); break; case YellowLabel: color = qRgb(0xE4, 0xD3, 0x78); break; case GreenLabel: color = qRgb(0xAF, 0xD8, 0x78); break; case BlueLabel: color = qRgb(0x77, 0xBA, 0xE8); break; case MagentaLabel: color = qRgb(0xCB, 0x98, 0xE1); break; case GrayLabel: color = qRgb(0xB7, 0xB7, 0xB7); break; case BlackLabel: color = qRgb(0x28, 0x28, 0x28); break; case WhiteLabel: color = qRgb(0xF7, 0xFE, 0xFA); break; default: // NoColorLabel break; } return color; } QString ColorLabelWidget::labelColorName(ColorLabel label) { QString name; switch(label) { case RedLabel: name = i18n("Red"); break; case OrangeLabel: name = i18n("Orange"); break; case YellowLabel: name = i18n("Yellow"); break; case GreenLabel: name = i18n("Green"); break; case BlueLabel: name = i18n("Blue"); break; case MagentaLabel: name = i18n("Magenta"); break; case GrayLabel: name = i18n("Gray"); break; case BlackLabel: name = i18n("Black"); break; case WhiteLabel: name = i18n("White"); break; default: // NoColorLabel name = i18n("None"); break; } return name; } // ----------------------------------------------------------------------------- class Q_DECL_HIDDEN ColorLabelSelector::Private { public: explicit Private() { clw = 0; } ColorLabelWidget* clw; }; ColorLabelSelector::ColorLabelSelector(QWidget* parent) : QPushButton(parent), d(new Private) { QMenu* const popup = new QMenu(this); setMenu(popup); QWidgetAction* const action = new QWidgetAction(this); d->clw = new ColorLabelWidget(this); action->setDefaultWidget(d->clw); popup->addAction(action); slotColorLabelChanged(NoColorLabel); connect(d->clw, SIGNAL(signalColorLabelChanged(int)), this, SLOT(slotColorLabelChanged(int))); } ColorLabelSelector::~ColorLabelSelector() { delete d; } ColorLabelWidget* ColorLabelSelector::colorLabelWidget() const { return d->clw; } void ColorLabelSelector::setColorLabel(ColorLabel label) { d->clw->setColorLabels(QList() << label); slotColorLabelChanged(label); } ColorLabel ColorLabelSelector::colorLabel() { QList list = d->clw->colorLabels(); if (!list.isEmpty()) return list.first(); return NoColorLabel; } void ColorLabelSelector::slotColorLabelChanged(int id) { setText(QString()); setIcon(d->clw->buildIcon((ColorLabel)id)); setToolTip(i18n("Color Label: %1", d->clw->labelColorName((ColorLabel)id))); menu()->close(); emit signalColorLabelChanged(id); } // ----------------------------------------------------------------------------- ColorLabelMenuAction::ColorLabelMenuAction(QMenu* const parent) : QMenu(parent) { setTitle(i18n("Color")); QWidgetAction* const wa = new QWidgetAction(this); ColorLabelWidget* const clw = new ColorLabelWidget(parent); wa->setDefaultWidget(clw); addAction(wa); connect(clw, SIGNAL(signalColorLabelChanged(int)), this, SIGNAL(signalColorLabelChanged(int))); connect(clw, SIGNAL(signalColorLabelChanged(int)), parent, SLOT(close())); } ColorLabelMenuAction::~ColorLabelMenuAction() { } } // namespace Digikam diff --git a/core/libs/widgets/metadata/metadatalistview.cpp b/core/libs/widgets/metadata/metadatalistview.cpp index 7917f77322..2c2a6ee685 100644 --- a/core/libs/widgets/metadata/metadatalistview.cpp +++ b/core/libs/widgets/metadata/metadatalistview.cpp @@ -1,444 +1,444 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2006-02-21 * Description : a generic list view widget to * display metadata * * Copyright (C) 2006-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 "metadatalistview.h" // Qt includes #include #include #include // KDE includes #include // Local includes #include "mdkeylistviewitem.h" #include "metadatalistviewitem.h" namespace Digikam { MetadataListView::MetadataListView(QWidget* const parent) : QTreeWidget(parent) { setRootIsDecorated(false); setSelectionMode(QAbstractItemView::SingleSelection); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); setAllColumnsShowFocus(true); setColumnCount(2); header()->setSectionResizeMode(QHeaderView::Stretch); header()->hide(); QStringList labels; labels.append(QLatin1String("Name")); // no i18n here: hidden header labels.append(QLatin1String("Value")); // no i18n here: hidden header setHeaderLabels(labels); setSortingEnabled(true); sortByColumn(0, Qt::AscendingOrder); m_parent = dynamic_cast(parent); connect(this, SIGNAL(itemClicked(QTreeWidgetItem*,int)), this, SLOT(slotSelectionChanged(QTreeWidgetItem*,int))); } MetadataListView::~MetadataListView() { } QString MetadataListView::getCurrentItemKey() { if (currentItem() && (currentItem()->flags() & Qt::ItemIsSelectable)) { MetadataListViewItem* item = static_cast(currentItem()); return item->getKey(); } return QString(); } void MetadataListView::setCurrentItemByKey(const QString& itemKey) { if (itemKey.isNull()) { return; } int i = 0; QTreeWidgetItem* item = 0; do { item = topLevelItem(i); if (item && (item->flags() & Qt::ItemIsSelectable)) { MetadataListViewItem* const lvItem = dynamic_cast(item); if (lvItem) { if (lvItem->getKey() == itemKey) { setCurrentItem(item); scrollToItem(item); m_selectedItemKey = itemKey; return; } } } ++i; } while (item); } void MetadataListView::slotSelectionChanged(QTreeWidgetItem* item, int) { if (!item) { return; } MetadataListViewItem* const viewItem = static_cast(item); m_selectedItemKey = viewItem->getKey(); QString tagValue = viewItem->getValue().simplified(); QString tagTitle = m_parent->getTagTitle(m_selectedItemKey); QString tagDesc = m_parent->getTagDescription(m_selectedItemKey); if (tagValue.length() > 128) { tagValue.truncate(128); tagValue.append(QLatin1String("...")); } this->setWhatsThis(i18n("Title:

%1

" "Value:

%2

" "Description:

%3

", tagTitle, tagValue, tagDesc)); } void MetadataListView::setIfdList(const DMetadata::MetaDataMap& ifds, const QStringList& tagsFilter) { clear(); uint subItems = 0; MdKeyListViewItem* parentifDItem = 0; QStringList filters = tagsFilter; QString ifDItemName; for (DMetadata::MetaDataMap::const_iterator it = ifds.constBegin(); it != ifds.constEnd(); ++it) { // We checking if we have changed of ifDName QString currentIfDName = it.key().section(QLatin1Char('.'), 1, 1); - if ( currentIfDName != ifDItemName ) + if (currentIfDName != ifDItemName) { ifDItemName = currentIfDName; // Check if the current IfD have any items. If no remove it before to toggle to the next IfD. - if ( subItems == 0 && parentifDItem) + if (subItems == 0 && parentifDItem) { delete parentifDItem; } parentifDItem = new MdKeyListViewItem(this, currentIfDName); subItems = 0; } if (filters.isEmpty()) { QString tagTitle = m_parent->getTagTitle(it.key()); new MetadataListViewItem(parentifDItem, it.key(), tagTitle, it.value()); ++subItems; } else if (!it.key().section(QLatin1Char('.'), 2, 2).startsWith(QLatin1String("0x"))) { // We ignore all unknown tags if necessary. if (filters.contains(QLatin1String("FULL"))) { // We don't filter the output (Photo Mode) QString tagTitle = m_parent->getTagTitle(it.key()); new MetadataListViewItem(parentifDItem, it.key(), tagTitle, it.value()); ++subItems; } else if (!filters.isEmpty()) { // We using the filter to make a more user friendly output (Custom Mode) // Filter is not a list of complete tag keys if (!filters.at(0).contains(QLatin1Char('.')) && filters.contains(it.key().section(QLatin1Char('.'), 2, 2))) { QString tagTitle = m_parent->getTagTitle(it.key()); new MetadataListViewItem(parentifDItem, it.key(), tagTitle, it.value()); ++subItems; filters.removeAll(it.key()); } else if (filters.contains(it.key())) { QString tagTitle = m_parent->getTagTitle(it.key()); new MetadataListViewItem(parentifDItem, it.key(), tagTitle, it.value()); ++subItems; filters.removeAll(it.key()); } } } } // To check if the last IfD have any items... - if ( subItems == 0 && parentifDItem) + if (subItems == 0 && parentifDItem) { delete parentifDItem; } // Add not found tags from filter as grey items. if (!filters.isEmpty() && filters.at(0) != QLatin1String("FULL") && filters.at(0).contains(QLatin1Char('.'))) { foreach(const QString& key, filters) { MdKeyListViewItem* pitem = findMdKeyItem(key); if (!pitem) { pitem = new MdKeyListViewItem(this, key.section(QLatin1Char('.'), 1, 1)); } QString tagTitle = m_parent->getTagTitle(key); new MetadataListViewItem(pitem, key, tagTitle); } } setCurrentItemByKey(m_selectedItemKey); update(); } void MetadataListView::setIfdList(const DMetadata::MetaDataMap& ifds, const QStringList& keysFilter, const QStringList& tagsFilter) { clear(); QStringList filters = tagsFilter; uint subItems = 0; MdKeyListViewItem* parentifDItem = 0; if (ifds.count() == 0) { return; } for (QStringList::const_iterator itKeysFilter = keysFilter.constBegin(); itKeysFilter != keysFilter.constEnd(); ++itKeysFilter) { subItems = 0; parentifDItem = new MdKeyListViewItem(this, *itKeysFilter); DMetadata::MetaDataMap::const_iterator it = ifds.constEnd(); while (it != ifds.constBegin()) { --it; - if ( *itKeysFilter == it.key().section(QLatin1Char('.'), 1, 1) ) + if (*itKeysFilter == it.key().section(QLatin1Char('.'), 1, 1)) { if (filters.isEmpty()) { QString tagTitle = m_parent->getTagTitle(it.key()); new MetadataListViewItem(parentifDItem, it.key(), tagTitle, it.value()); ++subItems; } else if (!it.key().section(QLatin1Char('.'), 2, 2).startsWith(QLatin1String("0x"))) { // We ignore all unknown tags if necessary. if (filters.contains(QLatin1String("FULL"))) { // We don't filter the output (Photo Mode) QString tagTitle = m_parent->getTagTitle(it.key()); new MetadataListViewItem(parentifDItem, it.key(), tagTitle, it.value()); ++subItems; } else if (!filters.isEmpty()) { // We using the filter to make a more user friendly output (Custom Mode) // Filter is not a list of complete tag keys if (!filters.at(0).contains(QLatin1Char('.')) && filters.contains(it.key().section(QLatin1Char('.'), 2, 2))) { QString tagTitle = m_parent->getTagTitle(it.key()); new MetadataListViewItem(parentifDItem, it.key(), tagTitle, it.value()); ++subItems; filters.removeAll(it.key()); } else if (filters.contains(it.key())) { QString tagTitle = m_parent->getTagTitle(it.key()); new MetadataListViewItem(parentifDItem, it.key(), tagTitle, it.value()); ++subItems; filters.removeAll(it.key()); } } } } } // We checking if the last IfD have any items. If no, we remove it. - if ( subItems == 0 && parentifDItem) + if (subItems == 0 && parentifDItem) { delete parentifDItem; } } // Add not found tags from filter as grey items. if (!filters.isEmpty() && filters.at(0) != QLatin1String("FULL") && filters.at(0).contains(QLatin1Char('.'))) { foreach(const QString& key, filters) { MdKeyListViewItem* pitem = findMdKeyItem(key); if (!pitem) { pitem = new MdKeyListViewItem(this, key.section(QLatin1Char('.'), 1, 1)); } QString tagTitle = m_parent->getTagTitle(key); new MetadataListViewItem(pitem, key, tagTitle); } } setCurrentItemByKey(m_selectedItemKey); update(); } void MetadataListView::slotSearchTextChanged(const SearchTextSettings& settings) { bool query = false; QString search = settings.text; // Restore all MdKey items. QTreeWidgetItemIterator it2(this); while (*it2) { MdKeyListViewItem* const item = dynamic_cast(*it2); if (item) { item->setHidden(false); } ++it2; } QTreeWidgetItemIterator it(this); while (*it) { MetadataListViewItem* const item = dynamic_cast(*it); if (item) { if (item->text(0).contains(search, settings.caseSensitive) || item->text(1).contains(search, settings.caseSensitive)) { query = true; item->setHidden(false); } else { item->setHidden(true); } } ++it; } // If we found MdKey items alone, we hide it... cleanUpMdKeyItem(); emit signalTextFilterMatch(query); } void MetadataListView::cleanUpMdKeyItem() { QTreeWidgetItemIterator it(this); while (*it) { MdKeyListViewItem* const item = dynamic_cast(*it); if (item) { int children = item->childCount(); int visibles = 0; for (int i = 0 ; i < children; ++i) { QTreeWidgetItem* const citem = (*it)->child(i); if (!citem->isHidden()) { ++visibles; } } if (!children || !visibles) { item->setHidden(true); } } ++it; } } MdKeyListViewItem* MetadataListView::findMdKeyItem(const QString& key) { QTreeWidgetItemIterator it(this); while (*it) { MdKeyListViewItem* const item = dynamic_cast(*it); if (item) { if (key.section(QLatin1Char('.'), 1, 1) == item->getKey()) { return item; } } ++it; } return 0; } } // namespace Digikam diff --git a/core/libs/widgets/metadata/metadataselector.cpp b/core/libs/widgets/metadata/metadataselector.cpp index 4d52522642..426a57afdc 100644 --- a/core/libs/widgets/metadata/metadataselector.cpp +++ b/core/libs/widgets/metadata/metadataselector.cpp @@ -1,490 +1,490 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2009-07-16 * Description : metadata selector. * * Copyright (C) 2009-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 "metadataselector.h" // Qt includes #include #include #include #include #include #include // KDE includes #include // Local includes #include "ditemtooltip.h" #include "mdkeylistviewitem.h" namespace Digikam { MetadataSelectorItem::MetadataSelectorItem(MdKeyListViewItem* const parent, const QString& key, const QString& title, const QString& desc) : QTreeWidgetItem(parent), m_key(key), m_parent(parent) { setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable); setCheckState(0, Qt::Unchecked); setChildIndicatorPolicy(QTreeWidgetItem::DontShowIndicator); setText(0, title); QString descVal = desc.simplified(); if (descVal.length() > 512) { descVal.truncate(512); descVal.append(QLatin1String("...")); } setText(1, descVal); DToolTipStyleSheet cnt; setToolTip(1, QLatin1String("

") + cnt.breakString(descVal) + QLatin1String("

")); } MetadataSelectorItem::~MetadataSelectorItem() { } QString MetadataSelectorItem::key() const { return m_key; } QString MetadataSelectorItem::mdKeyTitle() const { return (m_parent ? m_parent->text(0) : QString()); } // ------------------------------------------------------------------------------------ MetadataSelector::MetadataSelector(QWidget* const parent) : QTreeWidget(parent) { setRootIsDecorated(false); setSelectionMode(QAbstractItemView::SingleSelection); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); setAllColumnsShowFocus(true); setColumnCount(2); QStringList labels; labels.append(i18n("Name")); labels.append(i18n("Description")); setHeaderLabels(labels); header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); header()->setSectionResizeMode(1, QHeaderView::Stretch); } MetadataSelector::~MetadataSelector() { } void MetadataSelector::setTagsMap(const DMetadata::TagsMap& map) { clear(); uint subItems = 0; QString ifDItemName, currentIfDName; MdKeyListViewItem* parentifDItem = 0; QList toplevelItems; for (DMetadata::TagsMap::const_iterator it = map.constBegin(); it != map.constEnd(); ++it) { // We checking if we have changed of ifDName currentIfDName = it.key().section(QLatin1Char('.'), 1, 1); - if ( currentIfDName != ifDItemName ) + if (currentIfDName != ifDItemName) { ifDItemName = currentIfDName; // Check if the current IfD have any items. If not, remove it before to toggle to the next IfD. - if ( subItems == 0 && parentifDItem) + if (subItems == 0 && parentifDItem) { delete parentifDItem; } parentifDItem = new MdKeyListViewItem(0, currentIfDName); toplevelItems << parentifDItem; subItems = 0; } // We ignore all unknown tags if necessary. if (!it.key().section(QLatin1Char('.'), 2, 2).startsWith(QLatin1String("0x"))) { new MetadataSelectorItem(parentifDItem, it.key(), it.value().at(0), it.value().at(2)); ++subItems; } } addTopLevelItems(toplevelItems); // We need to call setFirstColumnSpanned() in here again because the widgets were added parentless and therefore // no layout information was present at construction time. Now that all items have a parent, we need to trigger the // method again. for (QList::const_iterator it = toplevelItems.constBegin(); it != toplevelItems.constEnd(); ++it) { if (*it) { (*it)->setFirstColumnSpanned(true); } } expandAll(); } void MetadataSelector::setcheckedTagsList(const QStringList& list) { QTreeWidgetItemIterator it(this); while (*it) { MetadataSelectorItem* const item = dynamic_cast(*it); if (item && list.contains(item->key())) { item->setCheckState(0, Qt::Checked); } ++it; } } QStringList MetadataSelector::checkedTagsList() { QStringList list; QTreeWidgetItemIterator it(this, QTreeWidgetItemIterator::Checked); while (*it) { MetadataSelectorItem* const item = dynamic_cast(*it); if (item) { list.append(item->key()); } ++it; } return list; } void MetadataSelector::clearSelection() { collapseAll(); QTreeWidgetItemIterator it(this, QTreeWidgetItemIterator::Checked); while (*it) { MetadataSelectorItem* const item = dynamic_cast(*it); if (item) { item->setCheckState(0, Qt::Unchecked); } ++it; } expandAll(); } void MetadataSelector::selectAll() { collapseAll(); QTreeWidgetItemIterator it(this, QTreeWidgetItemIterator::NotChecked); while (*it) { MetadataSelectorItem* const item = dynamic_cast(*it); if (item) { item->setCheckState(0, Qt::Checked); } ++it; } expandAll(); } // ------------------------------------------------------------------------------------ class Q_DECL_HIDDEN MetadataSelectorView::Private { public: explicit Private() { selectAllBtn = 0; clearSelectionBtn = 0; defaultSelectionBtn = 0; selector = 0; searchBar = 0; } QStringList defaultFilter; QPushButton* selectAllBtn; QPushButton* clearSelectionBtn; QPushButton* defaultSelectionBtn; MetadataSelector* selector; SearchTextBar* searchBar; }; MetadataSelectorView::MetadataSelectorView(QWidget* const parent) : QWidget(parent), d(new Private) { const int spacing = QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); QGridLayout* const grid = new QGridLayout(this); d->selector = new MetadataSelector(this); d->searchBar = new SearchTextBar(this, QLatin1String("MetadataSelectorView")); d->selectAllBtn = new QPushButton(i18n("Select All"),this); d->clearSelectionBtn = new QPushButton(i18n("Clear"),this); d->defaultSelectionBtn = new QPushButton(i18n("Default"),this); grid->addWidget(d->selector, 0, 0, 1, 5); grid->addWidget(d->searchBar, 1, 0, 1, 1); grid->addWidget(d->selectAllBtn, 1, 2, 1, 1); grid->addWidget(d->clearSelectionBtn, 1, 3, 1, 1); grid->addWidget(d->defaultSelectionBtn, 1, 4, 1, 1); grid->setColumnStretch(0, 10); grid->setRowStretch(0, 10); grid->setContentsMargins(spacing, spacing, spacing, spacing); grid->setSpacing(spacing); setControlElements(SearchBar | SelectAllBtn | DefaultBtn | ClearBtn); connect(d->searchBar, SIGNAL(signalSearchTextSettings(SearchTextSettings)), this, SLOT(slotSearchTextChanged(SearchTextSettings))); connect(d->selectAllBtn, SIGNAL(clicked()), this, SLOT(slotSelectAll())); connect(d->defaultSelectionBtn, SIGNAL(clicked()), this, SLOT(slotDeflautSelection())); connect(d->clearSelectionBtn, SIGNAL(clicked()), this, SLOT(slotClearSelection())); } MetadataSelectorView::~MetadataSelectorView() { delete d; } void MetadataSelectorView::setTagsMap(const DMetadata::TagsMap& map) { d->selector->setTagsMap(map); } void MetadataSelectorView::setcheckedTagsList(const QStringList& list) { d->selector->setcheckedTagsList(list); } void MetadataSelectorView::setDefaultFilter(const QStringList& list) { d->defaultFilter = list; } QStringList MetadataSelectorView::defaultFilter() const { return d->defaultFilter; } int MetadataSelectorView::itemsCount() const { return d->selector->model()->rowCount(); } QStringList MetadataSelectorView::checkedTagsList() const { d->searchBar->clear(); return d->selector->checkedTagsList(); } void MetadataSelectorView::slotSearchTextChanged(const SearchTextSettings& settings) { QString search = settings.text; bool atleastOneMatch = false; // Restore all MdKey items. QTreeWidgetItemIterator it2(d->selector); while (*it2) { MdKeyListViewItem* const item = dynamic_cast(*it2); if (item) { item->setHidden(false); } ++it2; } QTreeWidgetItemIterator it(d->selector); while (*it) { MetadataSelectorItem* const item = dynamic_cast(*it); if (item) { bool match = item->text(0).contains(search, settings.caseSensitive) || item->mdKeyTitle().contains(search, settings.caseSensitive); if (match) { atleastOneMatch = true; item->setHidden(false); } else { item->setHidden(true); } } ++it; } // If we found MdKey items alone, we hide it... cleanUpMdKeyItem(); d->searchBar->slotSearchResult(atleastOneMatch); } void MetadataSelectorView::cleanUpMdKeyItem() { QTreeWidgetItemIterator it(d->selector); while (*it) { MdKeyListViewItem* const item = dynamic_cast(*it); if (item) { int children = item->childCount(); int visibles = 0; for (int i = 0 ; i < children; ++i) { QTreeWidgetItem* const citem = (*it)->child(i); if (!citem->isHidden()) { ++visibles; } } if (!children || !visibles) { item->setHidden(true); } } ++it; } } void MetadataSelectorView::slotDeflautSelection() { slotClearSelection(); QApplication::setOverrideCursor(Qt::WaitCursor); d->selector->collapseAll(); QTreeWidgetItemIterator it(d->selector); while (*it) { MetadataSelectorItem* const item = dynamic_cast(*it); if (item) { if (d->defaultFilter.contains(item->text(0))) { item->setCheckState(0, Qt::Checked); } } ++it; } d->selector->expandAll(); QApplication::restoreOverrideCursor(); } void MetadataSelectorView::slotSelectAll() { QApplication::setOverrideCursor(Qt::WaitCursor); d->selector->selectAll(); QApplication::restoreOverrideCursor(); } void MetadataSelectorView::slotClearSelection() { QApplication::setOverrideCursor(Qt::WaitCursor); d->selector->clearSelection(); QApplication::restoreOverrideCursor(); } void MetadataSelectorView::setControlElements(ControlElements controllerMask) { d->searchBar->setVisible(controllerMask & SearchBar); d->selectAllBtn->setVisible(controllerMask & SelectAllBtn); d->clearSelectionBtn->setVisible(controllerMask & ClearBtn); d->defaultSelectionBtn->setVisible(controllerMask & DefaultBtn); } void MetadataSelectorView::clearSelection() { slotClearSelection(); } void MetadataSelectorView::selectAll() { slotSelectAll(); } void MetadataSelectorView::selectDefault() { slotDeflautSelection(); } } // namespace Digikam diff --git a/core/libs/widgets/metadata/metadatawidget.cpp b/core/libs/widgets/metadata/metadatawidget.cpp index c90d2e408f..125e5649f2 100644 --- a/core/libs/widgets/metadata/metadatawidget.cpp +++ b/core/libs/widgets/metadata/metadatawidget.cpp @@ -1,527 +1,527 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2006-02-22 * Description : a generic widget to display metadata * * Copyright (C) 2006-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 "metadatawidget.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include // Local includes #include "digikam_debug.h" #include "metadatalistview.h" #include "metadatalistviewitem.h" #include "mdkeylistviewitem.h" #include "searchtextbar.h" #include "setup.h" #include "dfiledialog.h" namespace Digikam { class Q_DECL_HIDDEN MetadataWidget::Private { public: explicit Private() { view = 0; mainLayout = 0; filterBtn = 0; toolBtn = 0; searchBar = 0; optionsMenu = 0; noneAction = 0; photoAction = 0; customAction = 0; settingsAction = 0; } QAction* noneAction; QAction* photoAction; QAction* customAction; QAction* settingsAction; QGridLayout* mainLayout; QToolButton* filterBtn; QToolButton* toolBtn; QString fileName; QStringList tagsFilter; QAction* saveMetadata; QAction* printMetadata; QAction* copy2ClipBoard; QMenu* optionsMenu; MetadataListView* view; SearchTextBar* searchBar; DMetadata metadata; DMetadata::MetaDataMap metaDataMap; }; MetadataWidget::MetadataWidget(QWidget* const parent, const QString& name) : QWidget(parent), d(new Private) { setObjectName(name); const int spacing = QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); d->mainLayout = new QGridLayout(this); // ----------------------------------------------------------------- d->filterBtn = new QToolButton(this); d->filterBtn->setToolTip(i18n("Tags filter options")); d->filterBtn->setIcon(QIcon::fromTheme(QLatin1String("view-filter"))); d->filterBtn->setPopupMode(QToolButton::InstantPopup); d->filterBtn->setWhatsThis(i18n("Apply tags filter over metadata.")); d->optionsMenu = new QMenu(d->filterBtn); QActionGroup* const filterGroup = new QActionGroup(this); d->noneAction = d->optionsMenu->addAction(i18n("No filter")); d->noneAction->setCheckable(true); filterGroup->addAction(d->noneAction); d->photoAction = d->optionsMenu->addAction(i18n("Photograph")); d->photoAction->setCheckable(true); filterGroup->addAction(d->photoAction); d->customAction = d->optionsMenu->addAction(i18n("Custom")); d->customAction->setCheckable(true); filterGroup->addAction(d->customAction); d->optionsMenu->addSeparator(); d->settingsAction = d->optionsMenu->addAction(i18n("Settings")); d->settingsAction->setCheckable(false); filterGroup->setExclusive(true); d->filterBtn->setMenu(d->optionsMenu); // ----------------------------------------------------------------- d->toolBtn = new QToolButton(this); d->toolBtn->setToolTip(i18n("Tools")); d->toolBtn->setIcon(QIcon::fromTheme(QLatin1String("system-run"))); d->toolBtn->setPopupMode(QToolButton::InstantPopup); d->toolBtn->setWhatsThis(i18n("Run tool over metadata tags.")); QMenu* const toolMenu = new QMenu(d->toolBtn); d->saveMetadata = toolMenu->addAction(i18nc("@action:inmenu", "Save in file")); d->printMetadata = toolMenu->addAction(i18nc("@action:inmenu", "Print")); d->copy2ClipBoard = toolMenu->addAction(i18nc("@action:inmenu", "Copy to Clipboard")); d->toolBtn->setMenu(toolMenu); d->view = new MetadataListView(this); QString barName = name + QLatin1String("SearchBar"); d->searchBar = new SearchTextBar(this, barName); // ----------------------------------------------------------------- d->mainLayout->addWidget(d->filterBtn, 0, 0, 1, 1); d->mainLayout->addWidget(d->searchBar, 0, 1, 1, 3); d->mainLayout->addWidget(d->toolBtn, 0, 4, 1, 1); d->mainLayout->addWidget(d->view, 1, 0, 1, 5); d->mainLayout->setColumnStretch(2, 10); d->mainLayout->setRowStretch(1, 10); d->mainLayout->setContentsMargins(spacing, spacing, spacing, spacing); d->mainLayout->setSpacing(0); } MetadataWidget::~MetadataWidget() { delete d; } void MetadataWidget::setup() { connect(d->optionsMenu, SIGNAL(triggered(QAction*)), this, SLOT(slotFilterChanged(QAction*))); connect(d->copy2ClipBoard, SIGNAL(triggered(bool)), this, SLOT(slotCopy2Clipboard())); connect(d->printMetadata, SIGNAL(triggered(bool)), this, SLOT(slotPrintMetadata())); connect(d->saveMetadata, SIGNAL(triggered(bool)), this, SLOT(slotSaveMetadataToFile())); connect(d->searchBar, SIGNAL(signalSearchTextSettings(SearchTextSettings)), d->view, SLOT(slotSearchTextChanged(SearchTextSettings))); connect(d->view, SIGNAL(signalTextFilterMatch(bool)), d->searchBar, SLOT(slotSearchResult(bool))); } void MetadataWidget::slotFilterChanged(QAction* action) { if (action == d->settingsAction) { emit signalSetupMetadataFilters(); } else if (action == d->noneAction || action == d->photoAction || action == d->customAction) { buildView(); } } QStringList MetadataWidget::getTagsFilter() const { return d->tagsFilter; } void MetadataWidget::setTagsFilter(const QStringList& list) { d->tagsFilter = list; buildView(); } MetadataListView* MetadataWidget::view() const { return d->view; } void MetadataWidget::enabledToolButtons(bool b) { d->toolBtn->setEnabled(b); } bool MetadataWidget::setMetadata(const DMetadata& data) { d->metadata = DMetadata(data); // Cleanup all metadata contents. setMetadataMap(); if (d->metadata.isEmpty()) { setMetadataEmpty(); return false; } // Try to decode current metadata. if (decodeMetadata()) { enabledToolButtons(true); } else { enabledToolButtons(false); } // Refresh view using decoded metadata. buildView(); return true; } void MetadataWidget::setMetadataEmpty() { d->view->clear(); enabledToolButtons(false); } const DMetadata& MetadataWidget::getMetadata() { return d->metadata; } bool MetadataWidget::storeMetadataToFile(const QUrl& url, const QByteArray& metaData) { - if ( url.isEmpty() ) + if (url.isEmpty()) { return false; } QFile file(url.toLocalFile()); - if ( !file.open(QIODevice::WriteOnly) ) + if (!file.open(QIODevice::WriteOnly)) { return false; } QDataStream stream( &file ); stream.writeRawData(metaData.data(), metaData.size()); file.close(); return true; } void MetadataWidget::setMetadataMap(const DMetadata::MetaDataMap& data) { d->metaDataMap = data; } const DMetadata::MetaDataMap& MetadataWidget::getMetadataMap() { return d->metaDataMap; } void MetadataWidget::setIfdList(const DMetadata::MetaDataMap& ifds, const QStringList& tagsFilter) { d->view->setIfdList(ifds, tagsFilter); } void MetadataWidget::setIfdList(const DMetadata::MetaDataMap& ifds, const QStringList& keysFilter, const QStringList& tagsFilter) { d->view->setIfdList(ifds, keysFilter, tagsFilter); } void MetadataWidget::slotCopy2Clipboard() { QString textmetadata = i18n("File name: %1 (%2)",d->fileName,getMetadataTitle()); int i = 0; QTreeWidgetItem* item = 0; do { item = d->view->topLevelItem(i); MdKeyListViewItem* const lvItem = dynamic_cast(item); if (lvItem) { textmetadata.append(QLatin1String("\n\n>>> ")); textmetadata.append(lvItem->getDecryptedKey()); textmetadata.append(QLatin1String(" <<<\n\n")); int j = 0; QTreeWidgetItem* item2 = 0; do { item2 = dynamic_cast(lvItem)->child(j); MetadataListViewItem* const lvItem2 = dynamic_cast(item2); if (lvItem2) { textmetadata.append(lvItem2->text(0)); textmetadata.append(QLatin1String(" : ")); textmetadata.append(lvItem2->text(1)); textmetadata.append(QLatin1Char('\n')); } ++j; } while (item2); } ++i; } while (item); QMimeData* const mimeData = new QMimeData(); mimeData->setText(textmetadata); QApplication::clipboard()->setMimeData(mimeData, QClipboard::Clipboard); } void MetadataWidget::slotPrintMetadata() { QString textmetadata = i18n("

File name: %1 (%2)", d->fileName, getMetadataTitle()); int i = 0; QTreeWidgetItem* item = 0; do { item = d->view->topLevelItem(i); MdKeyListViewItem* const lvItem = dynamic_cast(item); if (lvItem) { textmetadata.append(QLatin1String("

")); textmetadata.append(lvItem->getDecryptedKey()); textmetadata.append(QLatin1String("

")); int j = 0; QTreeWidgetItem* item2 = 0; do { item2 = dynamic_cast(lvItem)->child(j); MetadataListViewItem* const lvItem2 = dynamic_cast(item2); if (lvItem2) { textmetadata.append(lvItem2->text(0)); textmetadata.append(QLatin1String(" : ")); textmetadata.append(lvItem2->text(1)); textmetadata.append(QLatin1String("
")); } ++j; } while (item2); } ++i; } while (item); textmetadata.append(QLatin1String("

")); QPrinter printer; printer.setFullPage(true); QPointer dialog = new QPrintDialog(&printer, qApp->activeWindow()); if (dialog->exec()) { QTextDocument doc; doc.setHtml(textmetadata); QFont font(QApplication::font()); font.setPointSize(10); // we define 10pt to be a nice base size for printing. doc.setDefaultFont(font); doc.print(&printer); } delete dialog; } QUrl MetadataWidget::saveMetadataToFile(const QString& caption, const QString& fileFilter) { QPointer fileSaveDialog = new DFileDialog(this, caption, QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)); fileSaveDialog->setAcceptMode(QFileDialog::AcceptSave); fileSaveDialog->setFileMode(QFileDialog::AnyFile); fileSaveDialog->selectFile(d->fileName); fileSaveDialog->setNameFilter(fileFilter); QList urls; // Check for cancel. if (fileSaveDialog->exec() == QDialog::Accepted) { urls = fileSaveDialog->selectedUrls(); } delete fileSaveDialog; return (!urls.isEmpty() ? urls[0] : QUrl()); } void MetadataWidget::setMode(int mode) { if (getMode() == mode) { return; } if (mode == NONE) d->noneAction->setChecked(true); else if (mode == PHOTO) d->photoAction->setChecked(true); else d->customAction->setChecked(true); buildView(); } int MetadataWidget::getMode() const { if (d->noneAction->isChecked()) return NONE; else if (d->photoAction->isChecked()) return PHOTO; return CUSTOM; } QString MetadataWidget::getCurrentItemKey() const { return d->view->getCurrentItemKey(); } void MetadataWidget::setCurrentItemByKey(const QString& itemKey) { d->view->setCurrentItemByKey(itemKey); } bool MetadataWidget::loadFromData(const QString& fileName, const DMetadata& data) { setFileName(fileName); return(setMetadata(data)); } QString MetadataWidget::getTagTitle(const QString&) { return QString(); } QString MetadataWidget::getTagDescription(const QString&) { return QString(); } void MetadataWidget::setFileName(const QString& fileName) { d->fileName = fileName; } void MetadataWidget::setUserAreaWidget(QWidget* const w) { QVBoxLayout* const vLayout = new QVBoxLayout(); vLayout->setSpacing(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); vLayout->addWidget(w); vLayout->addStretch(); d->mainLayout->addLayout(vLayout, 3, 0, 1, 5); } void MetadataWidget::buildView() { d->view->slotSearchTextChanged(d->searchBar->searchTextSettings()); } } // namespace Digikam diff --git a/core/libs/widgets/metadata/picklabelwidget.cpp b/core/libs/widgets/metadata/picklabelwidget.cpp index d40297dcc5..ac86a8c3be 100644 --- a/core/libs/widgets/metadata/picklabelwidget.cpp +++ b/core/libs/widgets/metadata/picklabelwidget.cpp @@ -1,388 +1,388 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2011-02-14 * Description : pick label widget * * 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 "picklabelwidget.h" // Qt includes #include #include #include #include #include #include #include #include #include #include // KDE includes #include #include // Local includes #include "dlayoutbox.h" #include "dxmlguiwindow.h" #include "dexpanderbox.h" namespace Digikam { class Q_DECL_HIDDEN PickLabelWidget::Private { public: explicit Private() { pickBtns = 0; btnNone = 0; btnRej = 0; btnPndg = 0; btnAccpt = 0; desc = 0; descBox = 0; shortcut = 0; } QButtonGroup* pickBtns; QLabel* desc; QToolButton* btnNone; QToolButton* btnRej; QToolButton* btnPndg; QToolButton* btnAccpt; DHBox* descBox; DAdjustableLabel* shortcut; }; PickLabelWidget::PickLabelWidget(QWidget* const parent) : DVBox(parent), d(new Private) { setAttribute(Qt::WA_DeleteOnClose); setFocusPolicy(Qt::NoFocus); DHBox* const hbox = new DHBox(this); hbox->setContentsMargins(QMargins()); hbox->setSpacing(0); d->btnNone = new QToolButton(hbox); d->btnNone->setCheckable(true); d->btnNone->setFocusPolicy(Qt::NoFocus); d->btnNone->setIcon(buildIcon(NoPickLabel)); d->btnNone->installEventFilter(this); d->btnRej = new QToolButton(hbox); d->btnRej->setCheckable(true); d->btnRej->setFocusPolicy(Qt::NoFocus); d->btnRej->setIcon(buildIcon(RejectedLabel)); d->btnRej->installEventFilter(this); d->btnPndg = new QToolButton(hbox); d->btnPndg->setCheckable(true); d->btnPndg->setFocusPolicy(Qt::NoFocus); d->btnPndg->setIcon(buildIcon(PendingLabel)); d->btnPndg->installEventFilter(this); d->btnAccpt = new QToolButton(hbox); d->btnAccpt->setCheckable(true); d->btnAccpt->setFocusPolicy(Qt::NoFocus); d->btnAccpt->setIcon(buildIcon(AcceptedLabel)); d->btnAccpt->installEventFilter(this); d->pickBtns = new QButtonGroup(hbox); d->pickBtns->addButton(d->btnNone, NoPickLabel); d->pickBtns->addButton(d->btnRej, RejectedLabel); d->pickBtns->addButton(d->btnPndg, PendingLabel); d->pickBtns->addButton(d->btnAccpt, AcceptedLabel); d->descBox = new DHBox(this); d->descBox->setContentsMargins(QMargins()); d->descBox->setSpacing(0); d->desc = new QLabel(d->descBox); d->shortcut = new DAdjustableLabel(d->descBox); QFont fnt = d->shortcut->font(); fnt.setItalic(true); d->shortcut->setFont(fnt); d->shortcut->setAlignment(Qt::AlignRight | Qt::AlignVCenter); d->shortcut->setWordWrap(false); setSpacing(0); setContentsMargins(QMargins()); setPickLabels(QList() << NoPickLabel); setDescriptionBoxVisible(true); setButtonsExclusive(true); // ------------------------------------------------------------- connect(d->pickBtns, SIGNAL(buttonReleased(int)), this, SIGNAL(signalPickLabelChanged(int))); } PickLabelWidget::~PickLabelWidget() { delete d; } void PickLabelWidget::setDescriptionBoxVisible(bool b) { d->descBox->setVisible(b); if (!b) { foreach(QAbstractButton* const btn, d->pickBtns->buttons()) { PickLabel id = (PickLabel)(d->pickBtns->id(btn)); btn->setToolTip(labelPickName(id)); } } } void PickLabelWidget::setButtonsExclusive(bool b) { d->pickBtns->setExclusive(b); } void PickLabelWidget::updateDescription(PickLabel label) { d->desc->setText(labelPickName(label)); DXmlGuiWindow* const app = dynamic_cast(qApp->activeWindow()); if (app) { QAction* const ac = app->actionCollection()->action(QString::fromLatin1("pickshortcut-%1").arg(label)); if (ac) { d->shortcut->setAdjustedText(ac->shortcut().toString()); } } } bool PickLabelWidget::eventFilter(QObject* obj, QEvent* ev) { - if ( obj == d->btnNone) + if (obj == d->btnNone) { - if ( ev->type() == QEvent::Enter) + if (ev->type() == QEvent::Enter) { updateDescription(NoPickLabel); return false; } } - if ( obj == d->btnRej) + if (obj == d->btnRej) { - if ( ev->type() == QEvent::Enter) + if (ev->type() == QEvent::Enter) { updateDescription(RejectedLabel); return false; } } - if ( obj == d->btnPndg) + if (obj == d->btnPndg) { - if ( ev->type() == QEvent::Enter) + if (ev->type() == QEvent::Enter) { updateDescription(PendingLabel); return false; } } - if ( obj == d->btnAccpt) + if (obj == d->btnAccpt) { - if ( ev->type() == QEvent::Enter) + if (ev->type() == QEvent::Enter) { updateDescription(AcceptedLabel); return false; } } // pass the event on to the parent class return QWidget::eventFilter(obj, ev); } void PickLabelWidget::setPickLabels(const QList& list) { foreach(QAbstractButton* const btn, d->pickBtns->buttons()) { PickLabel id = (PickLabel)(d->pickBtns->id(btn)); btn->setChecked(list.contains(id)); updateDescription(id); } } QList PickLabelWidget::colorLabels() const { QList list; foreach(QAbstractButton* const btn, d->pickBtns->buttons()) { if (btn && btn->isChecked()) list.append((PickLabel)(d->pickBtns->id(btn))); } return list; } QIcon PickLabelWidget::buildIcon(PickLabel label) { switch(label) { case RejectedLabel: return QIcon::fromTheme(QLatin1String("flag-red")); break; case PendingLabel: return QIcon::fromTheme(QLatin1String("flag-yellow")); break; case AcceptedLabel: return QIcon::fromTheme(QLatin1String("flag-green")); break; default: break; } // default : NoPickLabel return QIcon::fromTheme(QLatin1String("flag-black")); } QString PickLabelWidget::labelPickName(PickLabel label) { QString name; switch(label) { case RejectedLabel: name = i18n("Rejected"); break; case PendingLabel: name = i18n("Pending"); break; case AcceptedLabel: name = i18n("Accepted"); break; default: // NoPickLabel name = i18n("None"); break; } return name; } // ----------------------------------------------------------------------------- class Q_DECL_HIDDEN PickLabelSelector::Private { public: explicit Private() { plw = 0; } PickLabelWidget* plw; }; PickLabelSelector::PickLabelSelector(QWidget* const parent) : QPushButton(parent), d(new Private) { QMenu* const popup = new QMenu(this); setMenu(popup); QWidgetAction* const action = new QWidgetAction(this); d->plw = new PickLabelWidget(this); action->setDefaultWidget(d->plw); popup->addAction(action); slotPickLabelChanged(NoPickLabel); connect(d->plw, SIGNAL(signalPickLabelChanged(int)), this, SLOT(slotPickLabelChanged(int))); } PickLabelSelector::~PickLabelSelector() { delete d; } PickLabelWidget* PickLabelSelector::pickLabelWidget() const { return d->plw; } void PickLabelSelector::setPickLabel(PickLabel label) { d->plw->setPickLabels(QList() << label); slotPickLabelChanged(label); } PickLabel PickLabelSelector::colorLabel() { QList list = d->plw->colorLabels(); if (!list.isEmpty()) return list.first(); return NoPickLabel; } void PickLabelSelector::slotPickLabelChanged(int id) { setText(QString()); setIcon(d->plw->buildIcon((PickLabel)id)); setToolTip(i18n("Pick Label: %1", d->plw->labelPickName((PickLabel)id))); menu()->close(); emit signalPickLabelChanged(id); } // ----------------------------------------------------------------------------- PickLabelMenuAction::PickLabelMenuAction(QMenu* const parent) : QMenu(parent) { setTitle(i18n("Pick")); QWidgetAction* const wa = new QWidgetAction(this); PickLabelWidget* const plw = new PickLabelWidget(parent); wa->setDefaultWidget(plw); addAction(wa); connect(plw, SIGNAL(signalPickLabelChanged(int)), this, SIGNAL(signalPickLabelChanged(int))); connect(plw, SIGNAL(signalPickLabelChanged(int)), parent, SLOT(close())); } PickLabelMenuAction::~PickLabelMenuAction() { } } // namespace Digikam diff --git a/core/showfoto/thumbbar/showfotothumbnailmodel.cpp b/core/showfoto/thumbbar/showfotothumbnailmodel.cpp index 02622daadc..9cbaa3085f 100644 --- a/core/showfoto/thumbbar/showfotothumbnailmodel.cpp +++ b/core/showfoto/thumbbar/showfotothumbnailmodel.cpp @@ -1,371 +1,371 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2013-07-22 * Description : Qt item model for Showfoto thumbnails entries * * Copyright (C) 2013 by Mohamed_Anwer * * 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 "showfotothumbnailmodel.h" // Local includes #include "drawdecoder.h" #include "digikam_debug.h" #include "dmetadata.h" #include "itemscanner.h" #include "thumbnailsize.h" #include "thumbnailloadthread.h" #include "loadingdescription.h" using namespace Digikam; namespace ShowFoto { class Q_DECL_HIDDEN ShowfotoThumbnailModel::Private { public: explicit Private() : thread(0), preloadThread(0), thumbSize(0), lastGlobalThumbSize(0), preloadThumbSize(0), emitDataChanged(true) { maxThumbSize = ThumbnailSize::Huge; } int preloadThumbnailSize() const { if (preloadThumbSize.size()) { return preloadThumbSize.size(); } return thumbSize.size(); } public: ThumbnailLoadThread* thread; ThumbnailLoadThread* preloadThread; ThumbnailSize thumbSize; ThumbnailSize lastGlobalThumbSize; ThumbnailSize preloadThumbSize; QRect detailRect; int maxThumbSize; bool emitDataChanged; }; ShowfotoThumbnailModel::ShowfotoThumbnailModel(QObject* const parent) : ShowfotoItemModel(parent), d(new Private) { connect(this, &ShowfotoThumbnailModel::signalThumbInfo, this, &ShowfotoThumbnailModel::slotThumbInfoLoaded); } ShowfotoThumbnailModel::~ShowfotoThumbnailModel() { delete d->preloadThread; delete d; } void ShowfotoThumbnailModel::setThumbnailLoadThread(ThumbnailLoadThread* thread) { d->thread = thread; connect(d->thread, &ThumbnailLoadThread::signalThumbnailLoaded, this, &ShowfotoThumbnailModel::slotThumbnailLoaded); } ThumbnailLoadThread* ShowfotoThumbnailModel::thumbnailLoadThread() const { return d->thread; } ThumbnailSize ShowfotoThumbnailModel::thumbnailSize() const { return d->thumbSize; } void ShowfotoThumbnailModel::setThumbnailSize(const ThumbnailSize& size) { d->lastGlobalThumbSize = size; d->thumbSize = size; } void ShowfotoThumbnailModel::setPreloadThumbnailSize(const ThumbnailSize& size) { d->preloadThumbSize = size; } void ShowfotoThumbnailModel::setEmitDataChanged(bool emitSignal) { d->emitDataChanged = emitSignal; } void ShowfotoThumbnailModel::showfotoItemInfosCleared() { if (d->preloadThread) { d->preloadThread->stopAllTasks(); } } QVariant ShowfotoThumbnailModel::data(const QModelIndex& index, int role) const { if (role == ThumbnailRole && d->thread && index.isValid()) { QImage thumbnailImage; QPixmap pixmap; ShowfotoItemInfo info = showfotoItemInfo(index); QString url = info.url.toDisplayString(); QString path = info.folder + QLatin1Char('/') + info.name; if (info.isNull() || url.isEmpty()) { return QVariant(QVariant::Pixmap); } - if(pixmapForItem(path,pixmap)) + if (pixmapForItem(path,pixmap)) { return pixmap; } //if pixmapForItem Failed if (getThumbnail(info,thumbnailImage)) { thumbnailImage = thumbnailImage.scaled(d->thumbSize.size(),d->thumbSize.size(), Qt::KeepAspectRatio); emit signalThumbInfo(info, thumbnailImage); return thumbnailImage; } return QVariant(QVariant::Pixmap); } return ShowfotoItemModel::data(index, role); } bool ShowfotoThumbnailModel::setData(const QModelIndex& index, const QVariant& value, int role) { if (role == ThumbnailRole) { switch (value.type()) { case QVariant::Invalid: d->thumbSize = d->lastGlobalThumbSize; d->detailRect = QRect(); break; case QVariant::Int: if (value.isNull()) { d->thumbSize = ThumbnailSize(d->lastGlobalThumbSize); } else { d->thumbSize = ThumbnailSize(value.toInt()); } break; case QVariant::Rect: if (value.isNull()) { d->detailRect = QRect(); } else { d->detailRect = value.toRect(); } break; default: break; } } return ShowfotoItemModel::setData(index, value, role); } void ShowfotoThumbnailModel::slotThumbnailLoaded(const LoadingDescription& loadingDescription, const QPixmap& thumb) { if (thumb.isNull()) { return; } // In case of multiple occurrence, we currently do not know which thumbnail is this. Signal change on all. foreach (const QModelIndex& index, indexesForUrl(QUrl::fromLocalFile(loadingDescription.filePath))) { if (thumb.isNull()) { emit thumbnailFailed(index, loadingDescription.previewParameters.size); } else { emit thumbnailAvailable(index, loadingDescription.previewParameters.size); if (d->emitDataChanged) { emit dataChanged(index, index); } } } } bool ShowfotoThumbnailModel::getThumbnail(const ShowfotoItemInfo& itemInfo, QImage& thumbnail) const { QString path = itemInfo.folder + QLatin1Char('/') + itemInfo.name; // Try to get preview from Exif data (good quality). Can work with Raw files DMetadata metadata(path); metadata.getItemPreview(thumbnail); if (!thumbnail.isNull()) { return true; } // RAW files : try to extract embedded thumbnail using RawEngine DRawDecoder::loadRawPreview(thumbnail, path); if (!thumbnail.isNull()) { return true; } KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(QLatin1String("Camera Settings")); bool turnHighQualityThumbs = group.readEntry(QLatin1String("TurnHighQualityThumbs"), false); // Try to get thumbnail from Exif data (poor quality). if (!turnHighQualityThumbs) { thumbnail = metadata.getExifThumbnail(true); if (!thumbnail.isNull()) { return true; } } // THM files: try to get thumbnail from '.thm' files if we didn't manage to get // thumbnail from Exif. Any cameras provides *.thm files like JPEG files with RAW files. // Using this way is always speed up than ultimate loading using DImg. // Note: the thumbnail extracted with this method can be in poor quality. // 2006/27/01 - Gilles - Tested with my Minolta Dynax 5D USM camera. QFileInfo fi(path); if (thumbnail.load(itemInfo.folder + QLatin1Char('/') + fi.baseName() + QLatin1String(".thm"))) // Lowercase { if (!thumbnail.isNull()) { return true; } } else if (thumbnail.load(itemInfo.folder + QLatin1Char('/') + fi.baseName() + QLatin1String(".THM"))) // Uppercase { if (!thumbnail.isNull()) { return true; } } // Finally, we trying to get thumbnail using DImg API (slow). // qCDebug(DIGIKAM_SHOWFOTO_LOG) << "Use DImg loader to get thumbnail from : " << path; // DImg dimgThumb(path); // if (!dimgThumb.isNull()) // { // thumbnail = dimgThumb.copyQImage(); // return true; // } return false; } bool ShowfotoThumbnailModel::pixmapForItem(QString url, QPixmap& pix) const { if (d->thumbSize.size() > d->maxThumbSize) { //TODO: Install a widget maximum size to prevent this situation bool hasPixmap = d->thread->find(ThumbnailIdentifier(url), pix, d->maxThumbSize); if (hasPixmap) { qCWarning(DIGIKAM_GENERAL_LOG) << "Thumbbar: Requested thumbnail size" << d->thumbSize.size() << "is larger than the maximum thumbnail size" << d->maxThumbSize << ". Returning a scaled-up image."; pix = pix.scaled(d->thumbSize.size(), d->thumbSize.size(), Qt::KeepAspectRatio, Qt::SmoothTransformation); return true; } else { return false; } } else { return d->thread->find(ThumbnailIdentifier(url), pix, d->thumbSize.size()); } } void ShowfotoThumbnailModel::slotThumbInfoLoaded(const ShowfotoItemInfo& info, const QImage& thumbnailImage) { QImage thumbnail = thumbnailImage; if (thumbnail.isNull()) { thumbnail = QImage(); } foreach (const QModelIndex& index, indexesForUrl(info.url)) { if (thumbnail.isNull()) { emit thumbnailFailed(index, d->thumbSize.size()); } else { emit thumbnailAvailable(index, d->thumbSize.size()); if (d->emitDataChanged) { emit dataChanged(index, index); } } } } } // namespace ShowFoto diff --git a/core/tests/database/testdatabaseswitch.cpp b/core/tests/database/testdatabaseswitch.cpp index b48ae7508d..f030299293 100644 --- a/core/tests/database/testdatabaseswitch.cpp +++ b/core/tests/database/testdatabaseswitch.cpp @@ -1,219 +1,219 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-06-21 * Description : CLI test program for switch digiKam DB from sqlite to mysql * * Copyright (C) 2014-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. * * ============================================================ */ // Qt includes #include #include #include #include #include #include #include #include // KDE includes #include // Local includes #include "daboutdata.h" #include "albummanager.h" #include "coredbaccess.h" #include "thumbsdbaccess.h" #include "facedbaccess.h" #include "dbengineparameters.h" #include "scancontroller.h" #include "digikam_version.h" using namespace Digikam; const QString IMAGE_PATH(QFINDTESTDATA("data/testimages/")); int main(int argc, char** argv) { if (argc != 2) { qDebug() << "testdatabaseswitch - test database switch"; qDebug() << "Usage: "; return -1; } KAboutData aboutData(QLatin1String("digikam"), QLatin1String("digiKam"), // No need i18n here. digiKamVersion()); QApplication app(argc, argv); QCommandLineParser parser; KAboutData::setApplicationData(aboutData); parser.addVersionOption(); parser.addHelpOption(); aboutData.setupCommandLine(&parser); parser.process(app); aboutData.processCommandLine(&parser); QString switchCondition = QLatin1String(argv[1]); // ------------------------------------------------------------------------------------ if (switchCondition == QLatin1String("sqliteToMysql")) { qDebug() << "Setup sqlite Database..."; DbEngineParameters params; params.databaseType = DbEngineParameters::SQLiteDatabaseType(); params.setCoreDatabasePath(QDir::currentPath() + QLatin1String("/digikam-core-test.db")); params.setThumbsDatabasePath(QDir::currentPath() + QLatin1String("/digikam-thumbs-test.db")); params.setFaceDatabasePath(QDir::currentPath() + QLatin1String("/digikam-faces-test.db")); params.legacyAndDefaultChecks(); qDebug() << "Initializing database..."; bool b = AlbumManager::instance()->setDatabase(params, false, IMAGE_PATH); qDebug() << "Database initialization done: " << b; QTest::qWait(3000); //qDebug() << "Shutting down database"; //ScanController::instance()->shutDown(); //AlbumManager::instance()->cleanUp(); qDebug() << "Cleaning DB now"; CoreDbAccess::cleanUpDatabase(); ThumbsDbAccess::cleanUpDatabase(); FaceDbAccess::cleanUpDatabase(); // ------------------------------------------------------------------------------------ qDebug() << "Setup mysql Database..."; params = DbEngineParameters(); QString defaultAkDir = DbEngineParameters::internalServerPrivatePath(); QString miscDir = QDir(defaultAkDir).absoluteFilePath(QLatin1String("db_misc")); params.databaseType = DbEngineParameters::MySQLDatabaseType(); params.databaseNameCore = QLatin1String("digikam"); params.databaseNameThumbnails = QLatin1String("digikam"); params.databaseNameFace = QLatin1String("digikam"); params.userName = QLatin1String("root"); params.password = QString(); params.internalServer = true; params.internalServerDBPath = QDir::currentPath(); params.internalServerMysqlServCmd = DbEngineParameters::defaultMysqlServerCmd(); params.internalServerMysqlInitCmd = DbEngineParameters::defaultMysqlInitCmd(); params.hostName = QString(); params.port = -1; params.connectOptions = QString::fromLatin1("UNIX_SOCKET=%1/mysql.socket").arg(miscDir); // ------------------------------------------------------------------------------------ qDebug() << "Initializing database..."; AlbumManager::instance()->changeDatabase(params); qDebug() << "Database switch done"; QTimer::singleShot(5000, &app, SLOT(quit())); app.exec(); qDebug() << "Shutting down database"; ScanController::instance()->shutDown(); AlbumManager::instance()->cleanUp(); qDebug() << "Cleaning DB now"; CoreDbAccess::cleanUpDatabase(); ThumbsDbAccess::cleanUpDatabase(); FaceDbAccess::cleanUpDatabase(); } - else if(switchCondition == QLatin1String("mysqlToSqlite")) + else if (switchCondition == QLatin1String("mysqlToSqlite")) { DbEngineParameters params; qDebug() << "Setup mysql Database..."; QString defaultAkDir = DbEngineParameters::internalServerPrivatePath(); QString miscDir = QDir(defaultAkDir).absoluteFilePath(QLatin1String("db_misc")); params.databaseType = DbEngineParameters::MySQLDatabaseType(); params.databaseNameCore = QLatin1String("digikam"); params.databaseNameThumbnails = QLatin1String("digikam"); params.databaseNameFace = QLatin1String("digikam"); params.userName = QLatin1String("root"); params.password = QString(); params.internalServer = true; params.internalServerDBPath = QDir::currentPath(); params.internalServerMysqlServCmd = DbEngineParameters::defaultMysqlServerCmd(); params.internalServerMysqlInitCmd = DbEngineParameters::defaultMysqlInitCmd(); params.hostName = QString(); params.port = -1; params.connectOptions = QString::fromLatin1("UNIX_SOCKET=%1/mysql.socket").arg(miscDir); // ------------------------------------------------------------------------------------ qDebug() << "Initializing database..."; AlbumManager::instance()->changeDatabase(params); qDebug() << "Database switch done"; QTimer::singleShot(5000, &app, SLOT(quit())); app.exec(); qDebug() << "Shutting down database"; ScanController::instance()->shutDown(); AlbumManager::instance()->cleanUp(); qDebug() << "Cleaning DB now"; CoreDbAccess::cleanUpDatabase(); ThumbsDbAccess::cleanUpDatabase(); FaceDbAccess::cleanUpDatabase(); // ------------------------------------------------------------------------------------ qDebug() << "Setup sqlite Database..."; params.databaseType = DbEngineParameters::SQLiteDatabaseType(); params.setCoreDatabasePath(QDir::currentPath() + QLatin1String("/digikam-core-test.db")); params.setThumbsDatabasePath(QDir::currentPath() + QLatin1String("/digikam-thumbs-test.db")); params.setFaceDatabasePath(QDir::currentPath() + QLatin1String("/digikam-faces-test.db")); params.legacyAndDefaultChecks(); qDebug() << "Initializing database..."; bool b = AlbumManager::instance()->setDatabase(params, false, IMAGE_PATH); qDebug() << "Database initialization done: " << b; QTest::qWait(3000); //qDebug() << "Shutting down database"; //ScanController::instance()->shutDown(); //AlbumManager::instance()->cleanUp(); qDebug() << "Cleaning DB now"; CoreDbAccess::cleanUpDatabase(); ThumbsDbAccess::cleanUpDatabase(); FaceDbAccess::cleanUpDatabase(); } return 0; } diff --git a/core/tests/dngwriter/dnginfo.cpp b/core/tests/dngwriter/dnginfo.cpp index 51fb9e5507..f692e52c61 100644 --- a/core/tests/dngwriter/dnginfo.cpp +++ b/core/tests/dngwriter/dnginfo.cpp @@ -1,182 +1,182 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2011-01-07 * Description : a command line tool to extract embedded originals * * Copyright (C) 2011 by Jens Mueller * * 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. * * ============================================================ */ // Qt includes #include #include #include #include // DNG SDK includes #include "dng_file_stream.h" #include "dng_host.h" #include "dng_image.h" #include "dng_info.h" #include "dng_memory_stream.h" #include "dng_xmp_sdk.h" // Local includes #define CHUNK 65536 int main(int argc, char** argv) { try { bool extractOriginal = false; - if(argc == 1) + if (argc == 1) { qDebug() << "\n" "dnginfo - DNG information tool\n" "Usage: %s [options] dngfile\n" "Valid options:\n" " -extractraw extract embedded original\n" << argv[0]; return -1; } qint32 index; for (index = 1 ; index < argc && argv[index][0] == '-' ; ++index) { QString option = QString::fromUtf8(&argv[index][1]); if (option == QLatin1String("extractraw")) { extractOriginal = true; } } if (index == argc) { qCritical() << "*** No file specified\n"; return 1; } QFileInfo dngFileInfo(QString::fromUtf8(argv[index])); dng_xmp_sdk::InitializeSDK(); dng_file_stream stream(QFile::encodeName(dngFileInfo.absoluteFilePath()).constData()); dng_host host; host.SetKeepOriginalFile(true); AutoPtr negative; { dng_info info; info.Parse(host, stream); info.PostParse(host); if (!info.IsValidDNG()) { return dng_error_bad_format; } negative.Reset(host.Make_dng_negative()); negative->Parse(host, stream, info); negative->PostParse(host, stream, info); QString originalFileName(QString::fromUtf8(negative->OriginalRawFileName().Get())); // dng_fingerprint originalDigest = negative->OriginalRawFileDigest(); quint32 originalDataLength = negative->OriginalRawFileDataLength(); const void* originalData = negative->OriginalRawFileData(); if (extractOriginal) { if (originalDataLength > 0) { dng_memory_allocator memalloc(gDefaultDNGMemoryAllocator); dng_memory_stream compressedDataStream(memalloc); compressedDataStream.Put(originalData, originalDataLength); compressedDataStream.SetReadPosition(0); compressedDataStream.SetBigEndian(true); quint32 forkLength = compressedDataStream.Get_uint32(); quint32 forkBlocks = (uint32)floor((forkLength + 65535.0) / 65536.0); QVector offsets; for (quint32 block = 0 ; block <= forkBlocks ; ++block) { quint32 offset = compressedDataStream.Get_uint32(); offsets.push_back(offset); } QFile originalFile(dngFileInfo.absolutePath() + QLatin1Char('/') + originalFileName); qDebug() << "extracting embedded original to " << dngFileInfo.fileName(); if (!originalFile.open(QIODevice::WriteOnly)) { qDebug() << "Cannot open file. Aborted..."; return 1; } QDataStream originalDataStream(&originalFile); for (quint32 block = 0 ; block < forkBlocks ; ++block) { QByteArray compressedDataBlock; compressedDataBlock.resize(offsets[block + 1] - offsets[block]); compressedDataStream.Get(compressedDataBlock.data(), compressedDataBlock.size()); quint32 uncompressedDataSize = qMin((quint32)CHUNK, forkLength); compressedDataBlock.prepend(uncompressedDataSize & 0xFF); compressedDataBlock.prepend((uncompressedDataSize >> 8) & 0xFF); compressedDataBlock.prepend((uncompressedDataSize >> 16) & 0xFF); compressedDataBlock.prepend((uncompressedDataSize >> 24) & 0xFF); forkLength -= uncompressedDataSize; QByteArray originalDataBlock = qUncompress((const uchar*)compressedDataBlock.data(), compressedDataBlock.size()); //qDebug() << "compressed data block " << compressedDataBlock.size() << " -> " << originalDataBlock.size(); originalDataStream.writeRawData(originalDataBlock.data(), originalDataBlock.size()); } originalFile.close(); } else { qCritical() << "no embedded originals found\n"; } } } dng_xmp_sdk::TerminateSDK(); return 0; } catch (const dng_exception& exception) { int ret = exception.ErrorCode(); qDebug() << "DNGWriter: DNG SDK exception code (" << ret << ")" ; return -1; } catch (...) { qDebug() << "DNGWriter: DNG SDK exception code unknow" ; return -1; } } diff --git a/core/tests/dngwriter/raw2dng.cpp b/core/tests/dngwriter/raw2dng.cpp index 48d350a5d8..b97ef665e3 100644 --- a/core/tests/dngwriter/raw2dng.cpp +++ b/core/tests/dngwriter/raw2dng.cpp @@ -1,44 +1,44 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2008-02-17 * Description : a command line tool to convert RAW file to DNG * * Copyright (C) 2008-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. * * ============================================================ */ // Qt includes #include // Local includes #include "dngwriter.h" int main(int argc, char **argv) { - if(argc != 2) + if (argc != 2) { qDebug() << "raw2dng - RAW Camera Image to DNG Converter"; qDebug() << "Usage: "; return -1; } Digikam::DNGWriter dngProcessor; dngProcessor.setInputFile(QString::fromUtf8(argv[1])); int ret = dngProcessor.convert(); return ret; } diff --git a/core/tests/fileio/loadpgfdata.cpp b/core/tests/fileio/loadpgfdata.cpp index a3d147e355..5738410c93 100644 --- a/core/tests/fileio/loadpgfdata.cpp +++ b/core/tests/fileio/loadpgfdata.cpp @@ -1,74 +1,74 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2009-02-04 * Description : a command line tool to load PGF data and convert to QImage * * 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. * * ============================================================ */ // Qt includes #include #include #include #include // Local includes #include "pgfutils.h" using namespace Digikam; int main(int argc, char** argv) { if (argc != 2) { qDebug() << "loadpgfdata - Load PGF data and save to PNG"; qDebug() << "Usage: "; return -1; } qDebug() << "Using LibPGF version: " << PGFUtils::libPGFVersion(); QImage img; // Write PGF file. QString fname(QString::fromUtf8(argv[1])); QFile file(fname); - if ( !file.open(QIODevice::ReadOnly) ) + if (!file.open(QIODevice::ReadOnly)) { qDebug() << "Cannot open PGF file to read..."; return -1; } QByteArray data(file.size(), '\x00'); QDataStream stream(&file); stream.readRawData(data.data(), data.size()); // PGF => QImage conversion if (!PGFUtils::readPGFImageData(data, img)) { qDebug() << "loadPGFScaled failed..."; return -1; } img.save(file.fileName() + QString::fromUtf8("-converted.png"), "PNG"); return 0; } diff --git a/core/tests/fileio/pgfscaled.cpp b/core/tests/fileio/pgfscaled.cpp index 637aa36239..99ce0726ba 100644 --- a/core/tests/fileio/pgfscaled.cpp +++ b/core/tests/fileio/pgfscaled.cpp @@ -1,70 +1,70 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2009-02-04 * Description : a command line tool to test PGF scaled to QImage * * Copyright (C) 2009-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. * * ============================================================ */ // Qt includes #include #include #include #include // Local includes #include "pgfutils.h" using namespace Digikam; int main(int argc, char** argv) { if (argc != 2) { qDebug() << "pgfscaled - Load scaled version of PGF image and save to PNG"; qDebug() << "Usage: "; return -1; } qDebug() << "Using LibPGF version: " << PGFUtils::libPGFVersion(); QImage img; // Write PGF file. QString fname = QString::fromUtf8(argv[1]); QFile file(fname); - if ( !file.open(QIODevice::ReadOnly) ) + if (!file.open(QIODevice::ReadOnly)) { qDebug() << "Cannot open PGF file to read..."; return -1; } // PGF => QImage conversion if (!PGFUtils::loadPGFScaled(img, file.fileName(), 1280)) { qDebug() << "loadPGFScaled failed..."; return -1; } img.save(file.fileName() + QString::fromUtf8("-scaled.png"), "PNG"); return 0; } diff --git a/core/tests/fileio/qtpgftest.cpp b/core/tests/fileio/qtpgftest.cpp index 24c7d5ca61..6d7bce8005 100644 --- a/core/tests/fileio/qtpgftest.cpp +++ b/core/tests/fileio/qtpgftest.cpp @@ -1,187 +1,187 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2009-02-04 * Description : a command line tool to test qt PGF interface * * Copyright (C) 2009-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. * * ============================================================ */ // C++ includes #include // Qt includes #include #include #include #include #include #include #include // Local includes #include "pgfutils.h" using namespace Digikam; int main(int /*argc*/, char** /*argv*/) { clock_t start, end; QImage img; QByteArray pgfData, jpgData, pngData; // QImage => PGF conversion qDebug() << "Using LibPGF version: " << PGFUtils::libPGFVersion(); img.load(QLatin1String("test.png")); qDebug() << "Generate PGF file using file stream"; // First, write QImage as PGF file using file stream if (!PGFUtils::writePGFImageFile(img, QLatin1String("test-filestream.pgf"), 0, true)) { qDebug() << "writePGFImageData failed..."; return -1; } qDebug() << "Generate PGF file using data stream"; start = clock(); // Second, write QImage as PGF file using data stream if (!PGFUtils::writePGFImageData(img, pgfData, 0, true)) { qDebug() << "writePGFImageData failed..."; return -1; } end = clock(); qDebug() << "PGF Encoding time: " << double(end - start)/CLOCKS_PER_SEC << " s"; // Write PGF file. QFile file(QLatin1String("test-datastream.pgf")); - if ( !file.open(QIODevice::WriteOnly) ) + if (!file.open(QIODevice::WriteOnly)) { qDebug() << "Cannot open PGF file to write..."; return -1; } QDataStream stream(&file); stream.writeRawData(pgfData.data(), pgfData.size()); file.close(); // PGF => QImage conversion qDebug() << "Load PGF file generated by data stream"; start = clock(); if (!PGFUtils::readPGFImageData(pgfData, img, true)) { qDebug() << "readPGFImageData failed..."; return -1; } end = clock(); img.save(QLatin1String("test2.png"), "PNG"); qDebug() << "PGF Decoding time: " << double(end - start)/CLOCKS_PER_SEC << " s"; // JPEG tests for comparisons. img.load(QLatin1String("test.png")); qDebug() << "Generate JPG file to compare performances"; start = clock(); QBuffer buffer(&jpgData); buffer.open(QIODevice::WriteOnly); img.save(&buffer, "JPEG", 85); // Here we will use JPEG quality = 85 to reduce artifacts. if (jpgData.isNull()) { qDebug() << "Save JPG image data to byte array failed..."; return -1; } end = clock(); qDebug() << "JPG Encoding time: " << double(end - start)/CLOCKS_PER_SEC << " s"; start = clock(); buffer.open(QIODevice::ReadOnly); img.load(&buffer, "JPEG"); if (jpgData.isNull()) { qDebug() << "Load JPG image data from byte array failed..."; return -1; } end = clock(); qDebug() << "JPG Decoding time: " << double(end - start)/CLOCKS_PER_SEC << " s"; // PNG tests for comparisons. img.load(QLatin1String("test.png")); qDebug() << "Generate PNG file to compare performances"; start = clock(); QBuffer buffer2(&pngData); buffer2.open(QIODevice::WriteOnly); img.save(&buffer2, "PNG", 100); if (pngData.isNull()) { qDebug() << "Save PNG image data to byte array failed..."; return -1; } end = clock(); qDebug() << "PNG Encoding time: " << double(end - start)/CLOCKS_PER_SEC << " s"; start = clock(); buffer2.open(QIODevice::ReadOnly); img.load(&buffer2, "PNG"); if (pngData.isNull()) { qDebug() << "Load PNG image data from byte array failed..."; return -1; } end = clock(); qDebug() << "PNG Decoding time: " << double(end - start)/CLOCKS_PER_SEC << " s"; return 0; } diff --git a/core/utilities/facemanagement/facescandialog.cpp b/core/utilities/facemanagement/facescandialog.cpp index ce3ad85040..237ef66ed3 100644 --- a/core/utilities/facemanagement/facescandialog.cpp +++ b/core/utilities/facemanagement/facescandialog.cpp @@ -1,485 +1,485 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-10-09 * Description : Dialog to choose options for face scanning * * Copyright (C) 2010-2012 by Marcel Wiesweg * Copyright (C) 2012-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 "facescandialog.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include // Local includes #include "digikam_config.h" #include "dlayoutbox.h" #include "dnuminput.h" #include "digikam_debug.h" #include "albummodel.h" #include "albumselectors.h" #include "applicationsettings.h" #include "dexpanderbox.h" namespace Digikam { class Q_DECL_HIDDEN FaceScanDialog::Private { public: explicit Private() : configName(QLatin1String("Face Detection Dialog")), configMainTask(QLatin1String("Face Scan Main Task")), configValueDetect(QLatin1String("Detect")), configValueDetectAndRecognize(QLatin1String("Detect and Recognize Faces")), configValueRecognizedMarkedFaces(QLatin1String("Recognize Marked Faces")), configAlreadyScannedHandling(QLatin1String("Already Scanned Handling")), configUseFullCpu(QLatin1String("Use Full CPU")), configSettingsVisible(QLatin1String("Settings Widget Visible")), configRecognizeAlgorithm(QLatin1String("Recognize Algorithm")) { buttons = 0; optionGroupBox = 0; detectAndRecognizeButton = 0; detectButton = 0; alreadyScannedBox = 0; reRecognizeButton = 0; tabWidget = 0; albumSelectors = 0; accuracyInput = 0; useFullCpuButton = 0; retrainAllButton = 0; recognizeBox = 0; } QDialogButtonBox* buttons; QGroupBox* optionGroupBox; QRadioButton* detectAndRecognizeButton; QRadioButton* detectButton; QComboBox* alreadyScannedBox; QRadioButton* reRecognizeButton; QTabWidget* tabWidget; AlbumSelectors* albumSelectors; DIntNumInput* accuracyInput; QCheckBox* useFullCpuButton; QCheckBox* retrainAllButton; QComboBox* recognizeBox; const QString configName; const QString configMainTask; const QString configValueDetect; const QString configValueDetectAndRecognize; const QString configValueRecognizedMarkedFaces; const QString configAlreadyScannedHandling; const QString configUseFullCpu; const QString configSettingsVisible; const QString configRecognizeAlgorithm; }; FaceScanDialog::FaceScanDialog(QWidget* const parent) : QDialog(parent), StateSavingObject(this), d(new Private) { setWindowFlags((windowFlags() & ~Qt::Dialog) | Qt::Window | Qt::WindowCloseButtonHint | Qt::WindowMinMaxButtonsHint); setWindowTitle(i18nc("@title:window", "Scanning faces")); setModal(true); d->buttons = new QDialogButtonBox(QDialogButtonBox::Reset | QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); d->buttons->button(QDialogButtonBox::Ok)->setDefault(true); d->buttons->button(QDialogButtonBox::Ok)->setText(i18nc("@action:button", "Scan")); setupUi(); setupConnections(); setObjectName(d->configName); loadState(); } FaceScanDialog::~FaceScanDialog() { delete d; } void FaceScanDialog::doLoadState() { qCDebug(DIGIKAM_GENERAL_LOG) << getConfigGroup().name(); KConfigGroup group = getConfigGroup(); QString mainTask = group.readEntry(entryName(d->configMainTask), d->configValueDetect); if (mainTask == d->configValueRecognizedMarkedFaces) { d->reRecognizeButton->setChecked(true); } else if (mainTask == d->configValueDetectAndRecognize) { d->detectAndRecognizeButton->setChecked(true); } else { d->detectButton->setChecked(true); } FaceScanSettings::AlreadyScannedHandling handling; QString skipHandling = group.readEntry(entryName(d->configAlreadyScannedHandling), QString::fromLatin1("Skip")); if (skipHandling == QLatin1String("Rescan")) { handling = FaceScanSettings::Rescan; } else if (skipHandling == QLatin1String("Merge")) { handling = FaceScanSettings::Merge; } else // Skip { handling = FaceScanSettings::Skip; } d->alreadyScannedBox->setCurrentIndex(d->alreadyScannedBox->findData(handling)); d->accuracyInput->setValue(ApplicationSettings::instance()->getFaceDetectionAccuracy() * 100); d->albumSelectors->loadState(); d->useFullCpuButton->setChecked(group.readEntry(entryName(d->configUseFullCpu), false)); RecognitionDatabase::RecognizeAlgorithm algo = (RecognitionDatabase::RecognizeAlgorithm)group.readEntry(entryName(d->configRecognizeAlgorithm), (int)RecognitionDatabase::RecognizeAlgorithm::LBP); int index = d->recognizeBox->findData(algo); d->recognizeBox->setCurrentIndex(index == -1 ? (int)RecognitionDatabase::RecognizeAlgorithm::LBP : index); // do not load retrainAllButton state from config, dangerous d->tabWidget->setVisible(group.readEntry(entryName(d->configSettingsVisible), false)); adjustDetailsButton(d->tabWidget->isVisible()); } void FaceScanDialog::doSaveState() { qCDebug(DIGIKAM_GENERAL_LOG) << getConfigGroup().name(); KConfigGroup group = getConfigGroup(); QString mainTask; if (d->detectButton->isChecked()) { mainTask = d->configValueDetect; } else if (d->detectAndRecognizeButton->isChecked()) { mainTask = d->configValueDetectAndRecognize; } else // d->reRecognizeButton { mainTask = d->configValueRecognizedMarkedFaces; } group.writeEntry(entryName(d->configMainTask), mainTask); QString handling; switch ((FaceScanSettings::AlreadyScannedHandling)(d->alreadyScannedBox->itemData(d->alreadyScannedBox->currentIndex()).toInt())) { case FaceScanSettings::Skip: handling = QLatin1String("Skip"); break; case FaceScanSettings::Rescan: handling = QLatin1String("Rescan"); break; case FaceScanSettings::Merge: handling = QLatin1String("Merge"); break; } group.writeEntry(entryName(d->configAlreadyScannedHandling), handling); ApplicationSettings::instance()->setFaceDetectionAccuracy(double(d->accuracyInput->value()) / 100); d->albumSelectors->saveState(); group.writeEntry(entryName(d->configUseFullCpu), d->useFullCpuButton->isChecked()); group.writeEntry(entryName(d->configSettingsVisible), d->tabWidget->isVisible()); group.writeEntry(entryName(d->configRecognizeAlgorithm), d->recognizeBox->itemData(d->recognizeBox->currentIndex())); } void FaceScanDialog::setupUi() { QWidget* const mainWidget = new QWidget; QGridLayout* const mainLayout = new QGridLayout; d->tabWidget = new QTabWidget; // ---- Introductory labels ---- QLabel* const personIcon = new QLabel; personIcon->setPixmap(QIcon::fromTheme(QLatin1String("edit-image-face-show")).pixmap(48)); QLabel* const introduction = new QLabel; introduction->setTextFormat(Qt::RichText); introduction->setText(i18nc("@info", "digiKam can search for faces in your photos.
" "When you have identified your friends on a number of photos,
" "it can also recognize the people shown on your photos.
")); // ---- Main option box -------- d->optionGroupBox = new QGroupBox; QGridLayout* const optionLayout = new QGridLayout; d->detectButton = new QRadioButton(i18nc("@option:radio", "Detect faces")); d->detectButton->setToolTip(i18nc("@info", "Find all faces in your photos")); d->detectAndRecognizeButton = new QRadioButton(i18nc("@option:radio", "Detect and recognize faces (experimental)")); d->detectAndRecognizeButton->setToolTip(i18nc("@info", "Find all faces in your photos and try to recognize which person is depicted")); d->alreadyScannedBox = new QComboBox; d->alreadyScannedBox->addItem(i18nc("@label:listbox", "Skip images already scanned"), FaceScanSettings::Skip); d->alreadyScannedBox->addItem(i18nc("@label:listbox", "Scan again and merge results"), FaceScanSettings::Merge); d->alreadyScannedBox->addItem(i18nc("@label:listbox", "Clear unconfirmed results and rescan"), FaceScanSettings::Rescan); d->alreadyScannedBox->setCurrentIndex(FaceScanSettings::Skip); d->reRecognizeButton = new QRadioButton(i18nc("@option:radio", "Recognize faces (experimental)")); d->reRecognizeButton->setToolTip(i18nc("@info", "Try again to recognize the people depicted on marked but yet unconfirmed faces.")); optionLayout->addWidget(d->alreadyScannedBox, 0, 0, 1, 2); optionLayout->addWidget(d->detectButton, 1, 0, 1, 2); optionLayout->addWidget(d->detectAndRecognizeButton, 2, 0, 1, 2); optionLayout->addWidget(d->reRecognizeButton, 3, 0, 1, 2); QStyleOptionButton buttonOption; buttonOption.initFrom(d->detectAndRecognizeButton); int indent = style()->subElementRect(QStyle::SE_RadioButtonIndicator, &buttonOption, d->detectAndRecognizeButton).width(); optionLayout->setColumnMinimumWidth(0, indent); d->optionGroupBox->setLayout(optionLayout); // ------------------------ mainLayout->addWidget(personIcon, 0, 0); mainLayout->addWidget(introduction, 0, 1); mainLayout->addWidget(d->optionGroupBox, 1, 0, 1, -1); mainLayout->setColumnStretch(1, 1); mainLayout->setRowStretch(2, 1); mainWidget->setLayout(mainLayout); // ---- Album tab --------- d->albumSelectors = new AlbumSelectors(i18nc("@label", "Search in:"), d->configName, d->tabWidget); d->tabWidget->addTab(d->albumSelectors, i18nc("@title:tab", "Albums")); // ---- Parameters tab ---- QWidget* const parametersTab = new QWidget(d->tabWidget); QGridLayout* const parametersLayout = new QGridLayout(parametersTab); QLabel* const detectionLabel = new QLabel(i18nc("@label", "Parameters for face detection and Recognition"), parametersTab); QLabel* const accuracyLabel = new QLabel(i18nc("@label Two extremities of a scale", "Fast - Accurate"), parametersTab); accuracyLabel->setAlignment(Qt::AlignTop | Qt::AlignHCenter); d->accuracyInput = new DIntNumInput(parametersTab); d->accuracyInput->setDefaultValue(80); d->accuracyInput->setRange(0, 100, 10); d->accuracyInput->setToolTip(i18nc("@info:tooltip", "Adjust speed versus accuracy: The higher the value, the more accurate the results " "will be, but it will take more time.")); parametersLayout->addWidget(detectionLabel, 0, 0, 1, 1); parametersLayout->addWidget(d->accuracyInput, 1, 0, 1, 1); parametersLayout->addWidget(accuracyLabel, 2, 0, 1, 1); parametersLayout->setColumnStretch(0, 10); parametersLayout->setRowStretch(3, 10); d->tabWidget->addTab(parametersTab, i18nc("@title:tab", "Parameters")); // ---- Advanced tab ------ QWidget* const advancedTab = new QWidget(d->tabWidget); QGridLayout* const advancedLayout = new QGridLayout(advancedTab); QLabel* const cpuExplanation = new QLabel(advancedTab); cpuExplanation->setText(i18nc("@info", "Face detection is a time-consuming task. " "You can choose if you wish to employ all processor cores " "on your system, or work in the background only on one core. " "Warning: this features still experimental and it is disabled by default.")); cpuExplanation->setWordWrap(true); d->useFullCpuButton = new QCheckBox(advancedTab); d->useFullCpuButton->setText(i18nc("@option:check", "Work on all processor cores (experimental)")); // ---- Recognize algorithm ComboBox ----- d->recognizeBox = new QComboBox; d->recognizeBox->addItem(i18nc("@label:listbox", "Recognize faces using LBP algorithm"), RecognitionDatabase::RecognizeAlgorithm::LBP); //d->recognizeBox->addItem(i18nc("@label:listbox", "Recognize faces using EigenFaces algorithm"), RecognitionDatabase::RecognizeAlgorithm::EigenFace); //d->recognizeBox->addItem(i18nc("@label:listbox", "Recognize faces using FisherFaces algorithm"), RecognitionDatabase::RecognizeAlgorithm::FisherFace); #ifdef HAVE_FACESENGINE_DNN d->recognizeBox->addItem(i18nc("@label:listbox", "Recognize faces using Deep Learning algorithm"), RecognitionDatabase::RecognizeAlgorithm::DNN); #endif d->recognizeBox->setCurrentIndex(RecognitionDatabase::RecognizeAlgorithm::LBP); d->retrainAllButton = new QCheckBox(advancedTab); d->retrainAllButton->setText(i18nc("@option:check", "Clear and rebuild all training data")); d->retrainAllButton->setToolTip(i18nc("@info:tooltip", "This will clear all training data for recognition " "and rebuild it from all available faces.")); advancedLayout->addWidget(cpuExplanation, 0, 0); advancedLayout->addWidget(d->useFullCpuButton, 1, 0); advancedLayout->addWidget(new DLineWidget(Qt::Horizontal), 2, 0); advancedLayout->addWidget(d->retrainAllButton, 3, 0); advancedLayout->addWidget(d->recognizeBox, 4, 0); advancedLayout->setRowStretch(5, 10); d->tabWidget->addTab(advancedTab, i18nc("@title:tab", "Advanced")); // ------------------------ QVBoxLayout* const vbx = new QVBoxLayout(this); vbx->addWidget(mainWidget); vbx->addWidget(d->tabWidget); vbx->addWidget(d->buttons); setLayout(vbx); } void FaceScanDialog::setupConnections() { connect(d->detectButton, SIGNAL(toggled(bool)), d->alreadyScannedBox, SLOT(setEnabled(bool))); connect(d->detectAndRecognizeButton, SIGNAL(toggled(bool)), d->alreadyScannedBox, SLOT(setEnabled(bool))); connect(d->retrainAllButton, SIGNAL(toggled(bool)), this, SLOT(retrainAllButtonToggled(bool))); connect(d->buttons->button(QDialogButtonBox::Ok), SIGNAL(clicked()), this, SLOT(slotOk())); connect(d->buttons->button(QDialogButtonBox::Cancel), SIGNAL(clicked()), this, SLOT(reject())); connect(d->buttons->button(QDialogButtonBox::Reset), SIGNAL(clicked()), this, SLOT(slotDetails())); } void FaceScanDialog::slotDetails() { bool on = !d->tabWidget->isVisible(); d->tabWidget->setVisible(on); adjustSize(); adjustDetailsButton(on); } void FaceScanDialog::adjustDetailsButton(bool on) { d->buttons->button(QDialogButtonBox::Reset)->setText(on ? i18nc("@action:button", "Options <<") : i18nc("@action:button", "Options >>")); } void FaceScanDialog::slotOk() { accept(); saveState(); } void FaceScanDialog::retrainAllButtonToggled(bool on) { d->optionGroupBox->setEnabled(!on); d->albumSelectors->setEnabled(!on); d->recognizeBox->setEnabled(!on); } FaceScanSettings FaceScanDialog::settings() const { FaceScanSettings settings; if (d->retrainAllButton->isChecked()) { settings.task = FaceScanSettings::RetrainAll; } - else if(d->detectButton->isChecked()) + else if (d->detectButton->isChecked()) { settings.task = FaceScanSettings::Detect; } else { if (d->detectAndRecognizeButton->isChecked()) { settings.task = FaceScanSettings::DetectAndRecognize; } else { settings.task = FaceScanSettings::RecognizeMarkedFaces; } } settings.alreadyScannedHandling = (FaceScanSettings::AlreadyScannedHandling) d->alreadyScannedBox->itemData(d->alreadyScannedBox->currentIndex()).toInt(); settings.accuracy = double(d->accuracyInput->value()) / 100; settings.albums << d->albumSelectors->selectedAlbumsAndTags(); settings.useFullCpu = d->useFullCpuButton->isChecked(); settings.recognizeAlgorithm = (RecognitionDatabase::RecognizeAlgorithm) d->recognizeBox->itemData(d->recognizeBox->currentIndex()).toInt(); return settings; } } // namespace Digikam diff --git a/core/utilities/geolocation/geoiface/items/gpsitemcontainer.cpp b/core/utilities/geolocation/geoiface/items/gpsitemcontainer.cpp index b9cb35edb2..8398021a99 100644 --- a/core/utilities/geolocation/geoiface/items/gpsitemcontainer.cpp +++ b/core/utilities/geolocation/geoiface/items/gpsitemcontainer.cpp @@ -1,969 +1,969 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-03-21 * Description : A container to hold GPS information about an item. * * Copyright (C) 2010-2019 by Gilles Caulier * Copyright (C) 2010-2014 by Michael G. Hansen * * 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 "gpsitemcontainer.h" // Qt includes #include #include #include #include // KDE includes #include // local includes #include "gpsitemmodel.h" #include "metaenginesettings.h" namespace Digikam { bool setExifXmpTagDataVariant(DMetadata* const meta, const char* const exifTagName, const char* const xmpTagName, const QVariant& value) { bool success = meta->setExifTagVariant(exifTagName, value); if (success) { /** @todo Here we save all data types as XMP Strings. Is that okay or do we have to store them as some other type? */ switch (value.type()) { case QVariant::Int: case QVariant::UInt: case QVariant::Bool: case QVariant::LongLong: case QVariant::ULongLong: success = meta->setXmpTagString(xmpTagName, QString::number(value.toInt())); break; case QVariant::Double: { long num, den; meta->convertToRationalSmallDenominator(value.toDouble(), &num, &den); success = meta->setXmpTagString(xmpTagName, QString::fromLatin1("%1/%2").arg(num).arg(den)); break; } case QVariant::List: { long num = 0, den = 1; QList list = value.toList(); if (list.size() >= 1) num = list[0].toInt(); if (list.size() >= 2) den = list[1].toInt(); success = meta->setXmpTagString(xmpTagName, QString::fromLatin1("%1/%2").arg(num).arg(den)); break; } case QVariant::Date: case QVariant::DateTime: { QDateTime dateTime = value.toDateTime(); - if(!dateTime.isValid()) + if (!dateTime.isValid()) { success = false; break; } success = meta->setXmpTagString(xmpTagName, dateTime.toString(QLatin1String("yyyy:MM:dd hh:mm:ss"))); break; } case QVariant::String: case QVariant::Char: success = meta->setXmpTagString(xmpTagName, value.toString()); break; case QVariant::ByteArray: /// @todo I don't know a straightforward way to convert a byte array to XMP success = false; break; default: success = false; break; } } return success; } GPSItemContainer::GPSItemContainer(const QUrl& url) : m_model(0), m_url(url), m_dateTime(), m_dirty(false), m_gpsData(), m_savedState(), m_tagListDirty(false), m_tagList(), m_savedTagList(), m_writeXmpTags(true) { } GPSItemContainer::~GPSItemContainer() { } DMetadata* GPSItemContainer::getMetadataForFile() const { QScopedPointer meta(new DMetadata); if (!meta->load(m_url.toLocalFile())) { // It is possible that no sidecar file has yet been created. // If writing to sidecar file is activated, we ignore the loading error of the metadata. if (MetaEngineSettings::instance()->settings().metadataWritingMode == DMetadata::WRITE_TO_FILE_ONLY) { return 0; } } return meta.take(); } int getWarningLevelFromGPSDataContainer(const GPSDataContainer& data) { if (data.hasDop()) { const int dopValue = data.getDop(); if (dopValue < 2) return 1; if (dopValue < 4) return 2; if (dopValue < 10) return 3; return 4; } else if (data.hasFixType()) { if (data.getFixType() < 3) return 4; } else if (data.hasNSatellites()) { if (data.getNSatellites() < 4) return 4; } // no warning level return -1; } bool GPSItemContainer::loadImageData() { QScopedPointer meta(getMetadataForFile()); if (meta && !m_dateTime.isValid()) { m_dateTime = meta->getItemDateTime(); } if (!m_dateTime.isValid()) { // Get date from filesystem. QFileInfo info(m_url.toLocalFile()); QDateTime ctime = info.created(); QDateTime mtime = info.lastModified(); if (ctime.isNull() || mtime.isNull()) { m_dateTime = qMax(ctime, mtime); } else { m_dateTime = qMin(ctime, mtime); } } if (!meta) return false; // The way we read the coordinates here is problematic // if the coordinates were in the file initially, but // the user deleted them in the database. Then we still load // them from the file. On the other hand, we can not clear // the coordinates, because then we would loose them if // they are only stored in the database. // m_gpsData.clear(); if (!m_gpsData.hasCoordinates()) { // could not load the coordinates from the interface, // read them directly from the file double lat, lng; bool haveCoordinates = meta->getGPSLatitudeNumber(&lat) && meta->getGPSLongitudeNumber(&lng); if (haveCoordinates) { GeoCoordinates coordinates(lat, lng); double alt; if (meta->getGPSAltitude(&alt)) { coordinates.setAlt(alt); } m_gpsData.setCoordinates(coordinates); } } /** @todo It seems that exiv2 provides EXIF entries if XMP sidecar entries exist, * therefore no need to read XMP as well? */ // read the remaining GPS information from the file: const QByteArray speedRef = meta->getExifTagData("Exif.GPSInfo.GPSSpeedRef"); bool success = !speedRef.isEmpty(); long num, den; success &= meta->getExifTagRational("Exif.GPSInfo.GPSSpeed", num, den); if (success) { // be relaxed about 0/0 if ((num == 0.0) && (den == 0.0)) den = 1.0; const qreal speedInRef = qreal(num)/qreal(den); qreal FactorToMetersPerSecond; if (speedRef.startsWith('K')) { // km/h = 1000 * 3600 FactorToMetersPerSecond = 1.0/3.6; } else if (speedRef.startsWith('M')) { // TODO: someone please check that this is the 'right' mile // miles/hour = 1609.344 meters / hour = 1609.344 meters / 3600 seconds FactorToMetersPerSecond = 1.0 / (1609.344 / 3600.0); } else if (speedRef.startsWith('N')) { // speed is in knots. // knot = one nautical mile / hour = 1852 meters / hour = 1852 meters / 3600 seconds FactorToMetersPerSecond = 1.0 / (1852.0 / 3600.0); } else { success = false; } if (success) { const qreal speedInMetersPerSecond = speedInRef * FactorToMetersPerSecond; m_gpsData.setSpeed(speedInMetersPerSecond); } } // number of satellites const QString gpsSatellitesString = meta->getExifTagString("Exif.GPSInfo.GPSSatellites"); bool satellitesOkay = !gpsSatellitesString.isEmpty(); if (satellitesOkay) { /** * @todo Here we only accept a single integer denoting the number of satellites used * but not detailed information about all satellites. */ const int nSatellites = gpsSatellitesString.toInt(&satellitesOkay); if (satellitesOkay) { m_gpsData.setNSatellites(nSatellites); } } // fix type / measure mode const QByteArray gpsMeasureModeByteArray = meta->getExifTagData("Exif.GPSInfo.GPSMeasureMode"); bool measureModeOkay = !gpsMeasureModeByteArray.isEmpty(); if (measureModeOkay) { const int measureMode = gpsMeasureModeByteArray.toInt(&measureModeOkay); if (measureModeOkay) { if ((measureMode == 2) || (measureMode == 3)) { m_gpsData.setFixType(measureMode); } } } // read the DOP value: success = meta->getExifTagRational("Exif.GPSInfo.GPSDOP", num, den); if (success) { // be relaxed about 0/0 if ((num == 0.0) && (den == 0.0)) den = 1.0; const qreal dop = qreal(num)/qreal(den); m_gpsData.setDop(dop); } // mark us as not-dirty, because the data was just loaded: m_dirty = false; m_savedState = m_gpsData; emitDataChanged(); return true; } QVariant GPSItemContainer::data(const int column, const int role) const { if ((column == ColumnFilename) && (role == Qt::DisplayRole)) { return m_url.fileName(); } else if ((column == ColumnDateTime) && (role == Qt::DisplayRole)) { if (m_dateTime.isValid()) { return QLocale().toString(m_dateTime, QLocale::ShortFormat); } return i18n("Not available"); } else if (role == RoleCoordinates) { return QVariant::fromValue(m_gpsData.getCoordinates()); } else if ((column == ColumnLatitude) && (role == Qt::DisplayRole)) { if (!m_gpsData.getCoordinates().hasLatitude()) return QString(); return QString::fromLatin1("%1").arg(m_gpsData.getCoordinates().lat(), 7); } else if ((column == ColumnLongitude) && (role == Qt::DisplayRole)) { if (!m_gpsData.getCoordinates().hasLongitude()) return QString(); return QString::fromLatin1("%1").arg(m_gpsData.getCoordinates().lon(), 7); } else if ((column == ColumnAltitude) && (role == Qt::DisplayRole)) { if (!m_gpsData.getCoordinates().hasAltitude()) return QString(); return QString::fromLatin1("%1").arg(m_gpsData.getCoordinates().alt(), 7); } else if (column == ColumnAccuracy) { if (role == Qt::DisplayRole) { if (m_gpsData.hasDop()) { return i18n("DOP: %1", m_gpsData.getDop()); } if (m_gpsData.hasFixType()) { return i18n("Fix: %1d", m_gpsData.getFixType()); } if (m_gpsData.hasNSatellites()) { return i18n("#Sat: %1", m_gpsData.getNSatellites()); } } else if (role == Qt::BackgroundRole) { const int warningLevel = getWarningLevelFromGPSDataContainer(m_gpsData); switch (warningLevel) { case 1: return QBrush(Qt::green); case 2: return QBrush(Qt::yellow); case 3: // orange return QBrush(QColor(0xff, 0x80, 0x00)); case 4: return QBrush(Qt::red); default: break; } } } else if ((column == ColumnDOP) && (role == Qt::DisplayRole)) { if (!m_gpsData.hasDop()) return QString(); return QString::number(m_gpsData.getDop()); } else if ((column == ColumnFixType) && (role == Qt::DisplayRole)) { if (!m_gpsData.hasFixType()) return QString(); return i18n("%1d", m_gpsData.getFixType()); } else if ((column == ColumnNSatellites) && (role == Qt::DisplayRole)) { if (!m_gpsData.hasNSatellites()) return QString(); return QString::number(m_gpsData.getNSatellites()); } else if ((column == ColumnSpeed) && (role == Qt::DisplayRole)) { if (!m_gpsData.hasSpeed()) return QString(); return QString::number(m_gpsData.getSpeed()); } else if ((column == ColumnStatus) && (role == Qt::DisplayRole)) { if (m_dirty || m_tagListDirty) { return i18n("Modified"); } return QString(); } else if ((column == ColumnTags) && (role == Qt::DisplayRole)) { if (!m_tagList.isEmpty()) { QString myTagsList; for (int i = 0 ; i < m_tagList.count() ; ++i) { QString myTag; for (int j = 0 ; j < m_tagList[i].count() ; ++j) { myTag.append(QLatin1Char('/') + m_tagList[i].at(j).tagName); if (j == 0) myTag.remove(0, 1); } if (!myTagsList.isEmpty()) myTagsList.append(QLatin1String(", ")); myTagsList.append(myTag); } return myTagsList; } return QString(); } return QVariant(); } void GPSItemContainer::setCoordinates(const GeoCoordinates& newCoordinates) { m_gpsData.setCoordinates(newCoordinates); m_dirty = true; emitDataChanged(); } void GPSItemContainer::setModel(GPSItemModel* const model) { m_model = model; } void GPSItemContainer::emitDataChanged() { if (m_model) { m_model->itemChanged(this); } } void GPSItemContainer::setHeaderData(GPSItemModel* const model) { model->setColumnCount(ColumnGPSItemContainerCount); model->setHeaderData(ColumnThumbnail, Qt::Horizontal, i18n("Thumbnail"), Qt::DisplayRole); model->setHeaderData(ColumnFilename, Qt::Horizontal, i18n("Filename"), Qt::DisplayRole); model->setHeaderData(ColumnDateTime, Qt::Horizontal, i18n("Date and time"), Qt::DisplayRole); model->setHeaderData(ColumnLatitude, Qt::Horizontal, i18n("Latitude"), Qt::DisplayRole); model->setHeaderData(ColumnLongitude, Qt::Horizontal, i18n("Longitude"), Qt::DisplayRole); model->setHeaderData(ColumnAltitude, Qt::Horizontal, i18n("Altitude"), Qt::DisplayRole); model->setHeaderData(ColumnAccuracy, Qt::Horizontal, i18n("Accuracy"), Qt::DisplayRole); model->setHeaderData(ColumnDOP, Qt::Horizontal, i18n("DOP"), Qt::DisplayRole); model->setHeaderData(ColumnFixType, Qt::Horizontal, i18n("Fix type"), Qt::DisplayRole); model->setHeaderData(ColumnNSatellites, Qt::Horizontal, i18n("# satellites"), Qt::DisplayRole); model->setHeaderData(ColumnSpeed, Qt::Horizontal, i18n("Speed"), Qt::DisplayRole); model->setHeaderData(ColumnStatus, Qt::Horizontal, i18n("Status"), Qt::DisplayRole); model->setHeaderData(ColumnTags, Qt::Horizontal, i18n("Tags"), Qt::DisplayRole); } bool GPSItemContainer::lessThan(const GPSItemContainer* const otherItem, const int column) const { switch (column) { case ColumnThumbnail: return false; case ColumnFilename: return m_url < otherItem->m_url; case ColumnDateTime: return m_dateTime < otherItem->m_dateTime; case ColumnAltitude: { if (!m_gpsData.hasAltitude()) return false; if (!otherItem->m_gpsData.hasAltitude()) return true; return m_gpsData.getCoordinates().alt() < otherItem->m_gpsData.getCoordinates().alt(); } case ColumnNSatellites: { if (!m_gpsData.hasNSatellites()) return false; if (!otherItem->m_gpsData.hasNSatellites()) return true; return m_gpsData.getNSatellites() < otherItem->m_gpsData.getNSatellites(); } case ColumnAccuracy: { const int myWarning = getWarningLevelFromGPSDataContainer(m_gpsData); const int otherWarning = getWarningLevelFromGPSDataContainer(otherItem->m_gpsData); if (myWarning < 0) return false; if (otherWarning < 0) return true; if (myWarning != otherWarning) return myWarning < otherWarning; // TODO: this may not be the best way to sort images with equal warning levels // but it works for now if (m_gpsData.hasDop() != otherItem->m_gpsData.hasDop()) return !m_gpsData.hasDop(); if (m_gpsData.hasDop() && otherItem->m_gpsData.hasDop()) { return m_gpsData.getDop() < otherItem->m_gpsData.getDop(); } if (m_gpsData.hasFixType() != otherItem->m_gpsData.hasFixType()) return m_gpsData.hasFixType(); if (m_gpsData.hasFixType() && otherItem->m_gpsData.hasFixType()) { return m_gpsData.getFixType() > otherItem->m_gpsData.getFixType(); } if (m_gpsData.hasNSatellites() != otherItem->m_gpsData.hasNSatellites()) return m_gpsData.hasNSatellites(); if (m_gpsData.hasNSatellites() && otherItem->m_gpsData.hasNSatellites()) { return m_gpsData.getNSatellites() > otherItem->m_gpsData.getNSatellites(); } return false; } case ColumnDOP: { if (!m_gpsData.hasDop()) return false; if (!otherItem->m_gpsData.hasDop()) return true; return m_gpsData.getDop() < otherItem->m_gpsData.getDop(); } case ColumnFixType: { if (!m_gpsData.hasFixType()) return false; if (!otherItem->m_gpsData.hasFixType()) return true; return m_gpsData.getFixType() < otherItem->m_gpsData.getFixType(); } case ColumnSpeed: { if (!m_gpsData.hasSpeed()) return false; if (!otherItem->m_gpsData.hasSpeed()) return true; return m_gpsData.getSpeed() < otherItem->m_gpsData.getSpeed(); } case ColumnLatitude: { if (!m_gpsData.hasCoordinates()) return false; if (!otherItem->m_gpsData.hasCoordinates()) return true; return m_gpsData.getCoordinates().lat() < otherItem->m_gpsData.getCoordinates().lat(); } case ColumnLongitude: { if (!m_gpsData.hasCoordinates()) return false; if (!otherItem->m_gpsData.hasCoordinates()) return true; return m_gpsData.getCoordinates().lon() < otherItem->m_gpsData.getCoordinates().lon(); } case ColumnStatus: { return m_dirty && !otherItem->m_dirty; } default: return false; } } SaveProperties GPSItemContainer::saveProperties() const { SaveProperties p; // do we have gps information? if (m_gpsData.hasCoordinates()) { p.shouldWriteCoordinates = true; p.latitude = m_gpsData.getCoordinates().lat(); p.longitude = m_gpsData.getCoordinates().lon(); if (m_gpsData.hasAltitude()) { p.shouldWriteAltitude = true; p.altitude = m_gpsData.getCoordinates().alt(); } else { p.shouldRemoveAltitude = true; } } else { p.shouldRemoveCoordinates = true; } return p; } QString GPSItemContainer::saveChanges() { SaveProperties p = saveProperties(); QString returnString; // first try to write the information to the image file bool success = false; QScopedPointer meta(getMetadataForFile()); if (!meta) { // TODO: more verbosity! returnString = i18n("Failed to open file."); } else { if (p.shouldWriteCoordinates) { if (p.shouldWriteAltitude) { success = meta->setGPSInfo(p.altitude, p.latitude, p.longitude); } else { success = meta->setGPSInfo(const_cast(static_cast(0)), p.latitude, p.longitude); } // write all other GPS information here too if (success && m_gpsData.hasSpeed()) { success = setExifXmpTagDataVariant(meta.data(), "Exif.GPSInfo.GPSSpeedRef", "Xmp.exif.GPSSpeedRef", QVariant(QLatin1String("K"))); if (success) { const qreal speedInMetersPerSecond = m_gpsData.getSpeed(); // km/h = 0.001 * m / ( s * 1/(60*60) ) = 3.6 * m/s const qreal speedInKilometersPerHour = 3.6 * speedInMetersPerSecond; success = setExifXmpTagDataVariant(meta.data(), "Exif.GPSInfo.GPSSpeed", "Xmp.exif.GPSSpeed", QVariant(speedInKilometersPerHour)); } } if (success && m_gpsData.hasNSatellites()) { /** * @todo According to the EXIF 2.2 spec, GPSSatellites is a free form field which can either hold only the * number of satellites or more details about each satellite used. For now, we just write * the number of satellites. Are we using the correct format for the number of satellites here? */ success = setExifXmpTagDataVariant(meta.data(), "Exif.GPSInfo.GPSSatellites", "Xmp.exif.GPSSatellites", QVariant(QString::number(m_gpsData.getNSatellites()))); } if (success && m_gpsData.hasFixType()) { success = setExifXmpTagDataVariant(meta.data(), "Exif.GPSInfo.GPSMeasureMode", "Xmp.exif.GPSMeasureMode", QVariant(QString::number(m_gpsData.getFixType()))); } // write DOP if (success && m_gpsData.hasDop()) { success = setExifXmpTagDataVariant(meta.data(), "Exif.GPSInfo.GPSDOP", "Xmp.exif.GPSDOP", QVariant(m_gpsData.getDop())); } if (!success) { returnString = i18n("Failed to add GPS info to image."); } } if (p.shouldRemoveCoordinates) { // TODO: remove only the altitude if requested success = meta->removeGPSInfo(); if (!success) { returnString = i18n("Failed to remove GPS info from image"); } } if (!m_tagList.isEmpty() && m_writeXmpTags) { QStringList tagSeq; for (int i = 0 ; i < m_tagList.count() ; ++i) { QList currentTagList = m_tagList[i]; QString tag; for (int j = 0 ; j < currentTagList.count() ; ++j) { tag.append(QLatin1Char('/') + currentTagList[j].tagName); } tag.remove(0, 1); tagSeq.append(tag); } bool success = meta->setXmpTagStringSeq("Xmp.digiKam.TagsList", tagSeq); if (!success) { returnString = i18n("Failed to save tags to file."); } success = meta->setXmpTagStringSeq("Xmp.dc.subject", tagSeq); if (!success) { returnString = i18n("Failed to save tags to file."); } } } if (success) { success = meta->save(m_url.toLocalFile()); if (!success) { returnString = i18n("Unable to save changes to file"); } else { m_dirty = false; m_savedState = m_gpsData; m_tagListDirty = false; m_savedTagList = m_tagList; } } if (returnString.isEmpty()) { // mark all changes as not dirty and tell the model: emitDataChanged(); } return returnString; } /** * @brief Restore the gps data to @p container. Sets m_dirty to false if container equals savedState. */ void GPSItemContainer::restoreGPSData(const GPSDataContainer& container) { m_dirty = !(container == m_savedState); m_gpsData = container; emitDataChanged(); } void GPSItemContainer::restoreRGTagList(const QList >& tagList) { //TODO: override == operator if (tagList.count() != m_savedTagList.count()) { m_tagListDirty = true; } else { for (int i = 0 ; i < tagList.count() ; ++i) { bool foundNotEqual = false; if (tagList[i].count() != m_savedTagList[i].count()) { m_tagListDirty = true; break; } for (int j = 0 ; j < tagList[i].count() ; ++j) { if (tagList[i].at(j).tagName != m_savedTagList[i].at(j).tagName) { foundNotEqual = true; break; } } if (foundNotEqual) { m_tagListDirty = true; break; } } } m_tagList = tagList; emitDataChanged(); } bool GPSItemContainer::isDirty() const { return m_dirty; } QUrl GPSItemContainer::url() const { return m_url; } QDateTime GPSItemContainer::dateTime() const { return m_dateTime; } GeoCoordinates GPSItemContainer::coordinates() const { return m_gpsData.getCoordinates(); } GPSDataContainer GPSItemContainer::gpsData() const { return m_gpsData; } void GPSItemContainer::setGPSData(const GPSDataContainer& container) { m_gpsData = container; m_dirty = true; emitDataChanged(); } void GPSItemContainer::setTagList(const QList >& externalTagList) { m_tagList = externalTagList; m_tagListDirty = true; emitDataChanged(); } bool GPSItemContainer::isTagListDirty() const { return m_tagListDirty; } QList > GPSItemContainer::getTagList() const { return m_tagList; } } // namespace Digikam diff --git a/core/utilities/imageeditor/widgets/canvas.cpp b/core/utilities/imageeditor/widgets/canvas.cpp index 9be2e4c85d..d378092239 100644 --- a/core/utilities/imageeditor/widgets/canvas.cpp +++ b/core/utilities/imageeditor/widgets/canvas.cpp @@ -1,705 +1,705 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2013-08-04 * Description : image editor canvas management class * * Copyright (C) 2013-2014 by Yiou Wang * 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 "canvas.h" // Qt includes #include #include #include #include #include #include // KDE includes #include // Local includes #include "imagehistogram.h" #include "iccsettingscontainer.h" #include "icctransform.h" #include "exposurecontainer.h" #include "iofilesettings.h" #include "loadingcacheinterface.h" #include "imagepreviewitem.h" #include "previewlayout.h" #include "imagezoomsettings.h" #include "clickdragreleaseitem.h" #include "rubberitem.h" namespace Digikam { class Q_DECL_HIDDEN Canvas::Private { public: explicit Private() { rubber = 0; wrapItem = 0; canvasItem = 0; core = 0; } QString errorMessage; ImagePreviewItem* canvasItem; RubberItem* rubber; ClickDragReleaseItem* wrapItem; EditorCore* core; }; Canvas::Canvas(QWidget* const parent) : GraphicsDImgView(parent), d(new Private) { d->core = new EditorCore(); d->canvasItem = new ImagePreviewItem; setItem(d->canvasItem); setFrameStyle(QFrame::NoFrame); addRubber(); layout()->fitToWindow(); installPanIcon(); setAcceptDrops(true); viewport()->setAcceptDrops(true); // ------------------------------------------------------------ connect(d->core, SIGNAL(signalModified()), this, SLOT(slotModified())); connect(d->core, SIGNAL(signalLoadingStarted(QString)), this, SIGNAL(signalLoadingStarted(QString))); connect(d->core, SIGNAL(signalImageLoaded(QString,bool)), this, SLOT(slotImageLoaded(QString,bool))); connect(d->core, SIGNAL(signalImageSaved(QString,bool)), this, SLOT(slotImageSaved(QString,bool))); connect(d->core, SIGNAL(signalLoadingProgress(QString,float)), this, SIGNAL(signalLoadingProgress(QString,float))); connect(d->core, SIGNAL(signalSavingStarted(QString)), this, SIGNAL(signalSavingStarted(QString))); connect(d->core, SIGNAL(signalSavingProgress(QString,float)), this, SIGNAL(signalSavingProgress(QString,float))); connect(this, SIGNAL(signalSelected(bool)), this, SLOT(slotSelected())); connect(d->canvasItem, SIGNAL(showContextMenu(QGraphicsSceneContextMenuEvent*)), this, SIGNAL(signalRightButtonClicked())); connect(layout(), SIGNAL(zoomFactorChanged(double)), this, SIGNAL(signalZoomChanged(double))); } Canvas::~Canvas() { delete d->core; delete d->canvasItem; delete d; } void Canvas::resetImage() { reset(); d->core->resetImage(); } void Canvas::reset() { if (d->rubber && d->rubber->isVisible()) { d->rubber->setVisible(false); if (d->core->isValid()) { emit signalSelected(false); } } addRubber(); d->errorMessage.clear(); } void Canvas::load(const QString& filename, IOFileSettings* const IOFileSettings) { reset(); emit signalPrepareToLoad(); d->core->load(filename, IOFileSettings); } void Canvas::slotImageLoaded(const QString& filePath, bool success) { - if(d->core->getImg()) + if (d->core->getImg()) { d->canvasItem->setImage(*d->core->getImg()); } // Note: in showFoto, we using a null filename to clear canvas. if (!success && !filePath.isEmpty()) { QFileInfo info(filePath); d->errorMessage = i18n("Failed to load image\n\"%1\"", info.fileName()); } else { d->errorMessage.clear(); } viewport()->update(); emit signalLoadingFinished(filePath, success); } void Canvas::fitToSelect() { QRect sel = d->core->getSelectedArea(); if (!sel.size().isNull()) { // If selected area, use center of selection // and recompute zoom factor accordingly. double cpx = sel.x() + sel.width() / 2.0; double cpy = sel.y() + sel.height() / 2.0; double srcWidth = sel.width(); double srcHeight = sel.height(); double dstWidth = contentsRect().width(); double dstHeight = contentsRect().height(); double zoom = qMin(dstWidth / srcWidth, dstHeight / srcHeight); emit signalToggleOffFitToWindow(); layout()->setZoomFactor(zoom); centerOn(cpx * zoom, cpy * zoom); viewport()->update(); } } void Canvas::applyTransform(const IccTransform& t) { IccTransform transform(t); if (transform.willHaveEffect()) { d->core->applyTransform(transform); } else { viewport()->update(); } } void Canvas::preload(const QString& /*filename*/) { // d->core->preload(filename); } void Canvas::slotImageSaved(const QString& filePath, bool success) { emit signalSavingFinished(filePath, success); } void Canvas::abortSaving() { d->core->abortSaving(); } void Canvas::setModified() { d->core->setModified(); } QString Canvas::ensureHasCurrentUuid() const { return d->core->ensureHasCurrentUuid(); } DImg Canvas::currentImage() const { DImg* const image = d->core->getImg(); if (image) { return DImg(*image); } return DImg(); } QString Canvas::currentImageFileFormat() const { return d->core->getImageFormat(); } QString Canvas::currentImageFilePath() const { return d->core->getImageFilePath(); } int Canvas::imageWidth() const { return d->core->origWidth(); } int Canvas::imageHeight() const { return d->core->origHeight(); } bool Canvas::isReadOnly() const { return d->core->isReadOnly(); } QRect Canvas::getSelectedArea() const { return d->core->getSelectedArea(); } EditorCore* Canvas::interface() const { return d->core; } void Canvas::makeDefaultEditingCanvas() { EditorCore::setDefaultInstance(d->core); } bool Canvas::exifRotated() const { return d->core->exifRotated(); } void Canvas::slotRotate90() { d->canvasItem->clearCache(); d->core->rotate90(); } void Canvas::slotRotate180() { d->canvasItem->clearCache(); d->core->rotate180(); } void Canvas::slotRotate270() { d->canvasItem->clearCache(); d->core->rotate270(); } void Canvas::slotFlipHoriz() { d->canvasItem->clearCache(); d->core->flipHoriz(); } void Canvas::slotFlipVert() { d->canvasItem->clearCache(); d->core->flipVert(); } void Canvas::slotCrop() { d->canvasItem->clearCache(); QRect sel = d->core->getSelectedArea(); if (sel.size().isNull()) // No current selection. { return; } d->core->crop(sel); if (d->rubber && d->rubber->isVisible()) { d->rubber->setVisible(false); } emit signalSelected(false); addRubber(); } void Canvas::setICCSettings(const ICCSettingsContainer& cmSettings) { d->canvasItem->clearCache(); d->core->setICCSettings(cmSettings); viewport()->update(); } void Canvas::setSoftProofingEnabled(bool enable) { d->canvasItem->clearCache(); d->core->setSoftProofingEnabled(enable); viewport()->update(); } void Canvas::setExposureSettings(ExposureSettingsContainer* const expoSettings) { d->canvasItem->clearCache(); d->core->setExposureSettings(expoSettings); viewport()->update(); } void Canvas::setExifOrient(bool exifOrient) { d->canvasItem->clearCache(); d->core->setExifOrient(exifOrient); viewport()->update(); } void Canvas::slotRestore() { d->core->restore(); viewport()->update(); } void Canvas::slotUndo(int steps) { emit signalUndoSteps(steps); d->canvasItem->clearCache(); while (steps > 0) { d->core->undo(); --steps; } } void Canvas::slotRedo(int steps) { emit signalRedoSteps(steps); d->canvasItem->clearCache(); while (steps > 0) { d->core->redo(); --steps; } } void Canvas::slotCopy() { QRect sel = d->core->getSelectedArea(); if (sel.size().isNull()) // No current selection. { return; } QApplication::setOverrideCursor(Qt::WaitCursor); DImg selDImg = d->core->getImgSelection(); QImage selImg = selDImg.copyQImage(); QMimeData* const mimeData = new QMimeData(); mimeData->setImageData(selImg); QApplication::clipboard()->setMimeData(mimeData, QClipboard::Clipboard); QApplication::restoreOverrideCursor(); } void Canvas::slotSelected() { QRect sel = QRect(0, 0, 0, 0); if (d->wrapItem) { cancelAddItem(); return; } if (d->rubber) { sel = calcSelectedArea(); } d->core->setSelectedArea(sel); emit signalSelectionChanged(sel); } void Canvas::slotSelectionMoved() { QRect sel = QRect(0, 0, 0, 0); if (d->rubber) { sel = calcSelectedArea(); } emit signalSelectionSetText(sel); } QRect Canvas::calcSelectedArea() const { int x = 0, y = 0, w = 0, h = 0; if (d->rubber && d->rubber->isVisible()) { QRect r(d->rubber->boundingRect().toRect()); if (r.isValid()) { r.translate((int)d->rubber->x(), (int)d->rubber->y()); x = (int)((double)r.x() / layout()->zoomFactor()); y = (int)((double)r.y() / layout()->zoomFactor()); w = (int)((double)r.width() / layout()->zoomFactor()); h = (int)((double)r.height() / layout()->zoomFactor()); x = qMin(imageWidth(), qMax(x, 0)); y = qMin(imageHeight(), qMax(y, 0)); w = qMin(imageWidth(), qMax(w, 0)); h = qMin(imageHeight(), qMax(h, 0)); // Avoid empty selection by rubberband - at least mark one pixel // At high zoom factors, the rubberband may operate at subpixel level! if (w == 0) { w = 1; } if (h == 0) { h = 1; } } } return QRect(x, y, w, h); } void Canvas::slotModified() { d->canvasItem->setImage(currentImage()); emit signalChanged(); } void Canvas::slotSelectAll() { if (d->rubber) { delete d->rubber; } d->rubber = new RubberItem(d->canvasItem); d->rubber->setCanvas(this); d->rubber->setRectInSceneCoordinatesAdjusted(d->canvasItem->boundingRect()); viewport()->setMouseTracking(true); viewport()->update(); if (d->core->isValid()) { emit signalSelected(true); } } void Canvas::slotSelectNone() { reset(); viewport()->update(); } void Canvas::keyPressEvent(QKeyEvent* event) { if (!event) { return; } int mult = 1; if ((event->modifiers() & Qt::ControlModifier)) { mult = 10; } switch (event->key()) { case Qt::Key_Right: { horizontalScrollBar()->setValue(horizontalScrollBar()->value() + horizontalScrollBar()->singleStep()*mult); break; } case Qt::Key_Left: { horizontalScrollBar()->setValue(horizontalScrollBar()->value() - horizontalScrollBar()->singleStep()*mult); break; } case Qt::Key_Up: { verticalScrollBar()->setValue(verticalScrollBar()->value() - verticalScrollBar()->singleStep()*mult); break; } case Qt::Key_Down: { verticalScrollBar()->setValue(verticalScrollBar()->value() + verticalScrollBar()->singleStep()*mult); break; } default: { event->ignore(); break; } } } void Canvas::addRubber() { if (!d->wrapItem) { d->wrapItem = new ClickDragReleaseItem(d->canvasItem); } d->wrapItem->setFocus(); setFocus(); connect(d->wrapItem, SIGNAL(started(QPointF)), this, SLOT(slotAddItemStarted(QPointF))); connect(d->wrapItem, SIGNAL(moving(QRectF)), this, SLOT(slotAddItemMoving(QRectF))); connect(d->wrapItem, SIGNAL(finished(QRectF)), this, SLOT(slotAddItemFinished(QRectF))); connect(d->wrapItem, SIGNAL(cancelled()), this, SLOT(cancelAddItem())); } void Canvas::slotAddItemStarted(const QPointF& pos) { Q_UNUSED(pos); } void Canvas::slotAddItemMoving(const QRectF& rect) { if (d->rubber) { delete d->rubber; } d->rubber = new RubberItem(d->canvasItem); d->rubber->setCanvas(this); d->rubber->setRectInSceneCoordinatesAdjusted(rect); } void Canvas::slotAddItemFinished(const QRectF& rect) { if (d->rubber) { d->rubber->setRectInSceneCoordinatesAdjusted(rect); //d->wrapItem->stackBefore(d->canvasItem); } cancelAddItem(); } void Canvas::cancelAddItem() { if (d->wrapItem) { this->scene()->removeItem(d->wrapItem); d->wrapItem->deleteLater(); d->wrapItem = 0; } emit signalSelected(true); } void Canvas::mousePressEvent(QMouseEvent* event) { GraphicsDImgView::mousePressEvent(event); if (event->button() == Qt::LeftButton) { GraphicsDImgItem* const item = dynamic_cast(itemAt(event->pos())); if (item) { QLatin1String className(item->metaObject()->className()); if (!(className == QLatin1String("Digikam::RubberItem") || className == QLatin1String("Digikam::ClickDragReleaseItem"))) { if (d->rubber && d->rubber->isVisible()) { d->rubber->setVisible(false); } emit signalSelected(false); addRubber(); } } } } void Canvas::dragEnterEvent(QDragEnterEvent* e) { QGraphicsView::dragEnterEvent(e); if (e->mimeData()->hasUrls()) { e->acceptProposedAction(); } } void Canvas::dragMoveEvent(QDragMoveEvent* e) { QGraphicsView::dragMoveEvent(e); if (e->mimeData()->hasUrls()) { e->acceptProposedAction(); } } void Canvas::dropEvent(QDropEvent* e) { QGraphicsView::dropEvent(e); emit signalAddedDropedItems(e); } } // namespace Digikam diff --git a/core/utilities/imageeditor/widgets/previewlist.cpp b/core/utilities/imageeditor/widgets/previewlist.cpp index 51f3e19d25..1950b0f44d 100644 --- a/core/utilities/imageeditor/widgets/previewlist.cpp +++ b/core/utilities/imageeditor/widgets/previewlist.cpp @@ -1,425 +1,425 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-02-13 * Description : a list of selectable options with preview * effects as thumbnails. * * Copyright (C) 2010-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 "previewlist.h" // Qt includes #include #include #include #include // KDE includes #include // Local includes #include "dimg.h" #include "dimgthreadedfilter.h" #include "imageiface.h" #include "digikam_debug.h" #include "dlayoutbox.h" #include "dworkingpixmap.h" namespace Digikam { class Q_DECL_HIDDEN PreviewThreadWrapper::Private { public: explicit Private() { } QMap map; }; PreviewThreadWrapper::PreviewThreadWrapper(QObject* const parent) : QObject(parent), d(new Private) { } PreviewThreadWrapper::~PreviewThreadWrapper() { qDeleteAll(d->map.values()); d->map.clear(); delete d; } void PreviewThreadWrapper::registerFilter(int id, DImgThreadedFilter* const filter) { - if(d->map.contains(id)) + if (d->map.contains(id)) return; filter->setParent(this); d->map.insert(id, filter); connect(filter, SIGNAL(started()), this, SLOT(slotFilterStarted())); connect(filter, SIGNAL(finished(bool)), this, SLOT(slotFilterFinished(bool))); connect(filter, SIGNAL(progress(int)), this, SLOT(slotFilterProgress(int))); } void PreviewThreadWrapper::slotFilterStarted() { DImgThreadedFilter* const filter = dynamic_cast(sender()); if (!filter) { return; } emit signalFilterStarted(d->map.key(filter)); } void PreviewThreadWrapper::slotFilterFinished(bool success) { DImgThreadedFilter* const filter = dynamic_cast(sender()); if (!filter) { return; } if (success) { int key = d->map.key(filter); QPixmap pix = filter->getTargetImage().smoothScale(128, 128, Qt::KeepAspectRatio).convertToPixmap(); emit signalFilterFinished(key, pix); } } void PreviewThreadWrapper::slotFilterProgress(int /*progress*/) { DImgThreadedFilter* const filter = dynamic_cast(sender()); if (!filter) { return; } //qCDebug(DIGIKAM_GENERAL_LOG) << filter->filterName() << " : " << progress << " %"; } void PreviewThreadWrapper::startFilters() { foreach(DImgThreadedFilter* const filter, d->map) { filter->startFilter(); } } void PreviewThreadWrapper::stopFilters() { foreach(DImgThreadedFilter* const filter, d->map) { filter->cancelFilter(); filter->deleteLater(); } } // --------------------------------------------------------------------- class Q_DECL_HIDDEN PreviewListItem::Private { public: explicit Private() : busy(false), id(0) { } bool busy; int id; }; PreviewListItem::PreviewListItem(QListWidget* const parent) : QListWidgetItem(parent), d(new Private) { } PreviewListItem::~PreviewListItem() { delete d; } void PreviewListItem::setPixmap(const QPixmap& pix) { QIcon icon = QIcon(pix); // We make sure the preview icon stays the same regardless of the role icon.addPixmap(pix, QIcon::Selected, QIcon::On); icon.addPixmap(pix, QIcon::Selected, QIcon::Off); icon.addPixmap(pix, QIcon::Active, QIcon::On); icon.addPixmap(pix, QIcon::Active, QIcon::Off); icon.addPixmap(pix, QIcon::Normal, QIcon::On); icon.addPixmap(pix, QIcon::Normal, QIcon::Off); setIcon(icon); } void PreviewListItem::setId(int id) { d->id = id; } int PreviewListItem::id() const { return d->id; } void PreviewListItem::setBusy(bool b) { d->busy = b; } bool PreviewListItem::isBusy() const { return d->busy; } // --------------------------------------------------------------------- class Q_DECL_HIDDEN PreviewList::Private { public: explicit Private() : progressCount(0), progressTimer(0), wrapper(0) { progressPix = DWorkingPixmap(); } int progressCount; QTimer* progressTimer; DWorkingPixmap progressPix; PreviewThreadWrapper* wrapper; }; PreviewList::PreviewList(QObject* const /*parent*/) : QListWidget(), d(new Private) { d->wrapper = new PreviewThreadWrapper(this); setSelectionMode(QAbstractItemView::SingleSelection); setDropIndicatorShown(true); setSortingEnabled(false); setIconSize(QSize(96, 96)); setViewMode(QListView::IconMode); setWrapping(true); setWordWrap(false); setMovement(QListView::Static); setSpacing(5); setGridSize(QSize(125, 100 + fontMetrics().height())); setResizeMode(QListView::Adjust); setTextElideMode(Qt::ElideRight); setCursor(Qt::PointingHandCursor); setStyleSheet(QLatin1String("QListWidget::item:selected:!active {show-decoration-selected: 0}")); d->progressTimer = new QTimer(this); d->progressTimer->setInterval(300); connect(d->progressTimer, SIGNAL(timeout()), this, SLOT(slotProgressTimerDone())); connect(d->wrapper, SIGNAL(signalFilterStarted(int)), this, SLOT(slotFilterStarted(int))); connect(d->wrapper, SIGNAL(signalFilterFinished(int,QPixmap)), this, SLOT(slotFilterFinished(int,QPixmap))); } PreviewList::~PreviewList() { stopFilters(); delete d; } void PreviewList::startFilters() { d->progressTimer->start(); d->wrapper->startFilters(); } void PreviewList::stopFilters() { d->progressTimer->stop(); d->wrapper->stopFilters(); } PreviewListItem* PreviewList::addItem(DImgThreadedFilter* const filter, const QString& txt, int id) { if (!filter) { return 0; } d->wrapper->registerFilter(id, filter); PreviewListItem* const item = new PreviewListItem(this); item->setText(txt); // in case text is mangled by textelide, it is displayed by hovering. item->setToolTip(txt); item->setId(id); return item; } PreviewListItem* PreviewList::findItem(int id) const { int it = 0; while (it <= this->count()) { PreviewListItem* const item = dynamic_cast(this->item(it)); if (item && item->id() == id) { return item; } ++it; } return 0; } void PreviewList::setCurrentId(int id) { int it = 0; while (it <= this->count()) { PreviewListItem* const item = dynamic_cast(this->item(it)); if (item && item->id() == id) { setCurrentItem(item); item->setSelected(true); return; } ++it; } } int PreviewList::currentId() const { PreviewListItem* const item = dynamic_cast(currentItem()); if (item) { return item->id(); } return 0; } void PreviewList::slotProgressTimerDone() { QPixmap ppix(d->progressPix.frameAt(d->progressCount)); QPixmap pixmap(128, 128); pixmap.fill(Qt::transparent); QPainter p(&pixmap); p.drawPixmap((pixmap.width() / 2) - (ppix.width() / 2), (pixmap.height() / 2) - (ppix.height() / 2), ppix); int busy = 0; int it = 0; PreviewListItem* selectedItem = 0; while (it <= this->count()) { PreviewListItem* const item = dynamic_cast(this->item(it)); if (item && item->isSelected()) { selectedItem = item; } if (item && item->isBusy()) { item->setPixmap(pixmap); ++busy; } ++it; } d->progressCount++; if (d->progressCount >= d->progressPix.frameCount()) { d->progressCount = 0; } if (!busy) { d->progressTimer->stop(); // Qt 4.5 doesn't display icons correctly centred over i18n(text), // Qt 4.6 doesn't even reset the previous selection correctly. this->reset(); if (selectedItem) { setCurrentItem(selectedItem); } } } void PreviewList::slotFilterStarted(int id) { PreviewListItem* const item = findItem(id); item->setBusy(true); } void PreviewList::slotFilterFinished(int id, const QPixmap& pix) { PreviewListItem* const item = findItem(id); if (item) { item->setBusy(false); item->setPixmap(pix); update(); } } } // namespace Digikam diff --git a/core/utilities/import/backend/gpcamera.cpp b/core/utilities/import/backend/gpcamera.cpp index acb6824fb2..9b59d6b2a7 100644 --- a/core/utilities/import/backend/gpcamera.cpp +++ b/core/utilities/import/backend/gpcamera.cpp @@ -1,1807 +1,1807 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2003-01-21 * Description : Gphoto2 camera interface * * Copyright (C) 2003-2005 by Renchi Raju * Copyright (C) 2006-2019 by Gilles Caulier * Copyright (C) 2006-2012 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 "gpcamera.h" // C ANSI includes extern "C" { #include #include } // C++ includes #include #include // Qt includes #include #include #include #include #include #include #include #include // KDE includes #include // Local includes #include "digikam_debug.h" #include "digikam_config.h" #include "dmetadata.h" //#define GPHOTO2_DEBUG 1 #ifdef HAVE_GPHOTO2 // LibGphoto2 includes extern "C" { #include } #endif /* HAVE_GPHOTO2 */ namespace Digikam { class Q_DECL_HIDDEN GPStatus { public: GPStatus() { #ifdef HAVE_GPHOTO2 context = gp_context_new(); cancel = false; gp_context_set_cancel_func(context, cancel_func, 0); #ifdef GPHOTO2_DEBUG gp_context_set_progress_funcs(context, start_func, update_func, stop_func, 0); gp_context_set_error_func(context, error_func, 0); gp_context_set_status_func(context, status_func, 0); #endif /* GPHOTO2_DEBUG */ #endif /* HAVE_GPHOTO2 */ } ~GPStatus() { #ifdef HAVE_GPHOTO2 gp_context_unref(context); cancel = false; #endif /* HAVE_GPHOTO2 */ } static bool cancel; #ifdef HAVE_GPHOTO2 GPContext* context; static GPContextFeedback cancel_func(GPContext*, void*) { return (cancel ? GP_CONTEXT_FEEDBACK_CANCEL : GP_CONTEXT_FEEDBACK_OK); } #ifdef GPHOTO2_DEBUG static void error_func(GPContext*, const char* msg, void*) { qCDebug(DIGIKAM_IMPORTUI_LOG) << "error:" << msg; } static void status_func(GPContext*, const char* msg, void*) { qCDebug(DIGIKAM_IMPORTUI_LOG) << "status:" << msg; } static unsigned int start_func(GPContext*, float target, const char *text, void *data) { Q_UNUSED(data); qCDebug(DIGIKAM_IMPORTUI_LOG) << "start:" << target << "- text:" << text; return 0; } static void update_func(GPContext*, unsigned int id, float target, void *data) { Q_UNUSED(data); qCDebug(DIGIKAM_IMPORTUI_LOG) << "update:" << id << "- target:" << target; } static void stop_func(GPContext*, unsigned int id, void *data) { Q_UNUSED(data); qCDebug(DIGIKAM_IMPORTUI_LOG) << "stop:" << id; } #endif /* GPHOTO2_DEBUG */ #endif /* HAVE_GPHOTO2 */ }; bool GPStatus::cancel = false; // --------------------------------------------------------------------------- class Q_DECL_HIDDEN GPCamera::Private { public: explicit Private() #ifdef HAVE_GPHOTO2 : cameraInitialized(false), camera(0), status(0) #endif /* HAVE_GPHOTO2 */ { } #ifdef HAVE_GPHOTO2 bool cameraInitialized; Camera* camera; CameraAbilities cameraAbilities; GPStatus* status; #endif /* HAVE_GPHOTO2 */ }; // --------------------------------------------------------------------------- GPCamera::GPCamera(const QString& title, const QString& model, const QString& port, const QString& path) : DKCamera(title, model, port, path), d(new Private) { } GPCamera::~GPCamera() { #ifdef HAVE_GPHOTO2 if (d->status) { gp_context_unref(d->status->context); d->status = 0; } if (d->camera) { gp_camera_unref(d->camera); d->camera = 0; } #endif /* HAVE_GPHOTO2 */ delete d; } DKCamera::CameraDriverType GPCamera::cameraDriverType() { return DKCamera::GPhotoDriver; } QByteArray GPCamera::cameraMD5ID() { QByteArray md5data; #ifdef HAVE_GPHOTO2 QString camData; // We don't use camera title from digiKam settings panel to compute MD5 fingerprint, // because it can be changed by users between session. camData.append(model()); // TODO is it really necessary to have a path here? I think model+filename+size+ctime should be enough to give unique fingerprint // while still allowing you to move files around in the camera if needed camData.append(path()); QCryptographicHash md5(QCryptographicHash::Md5); md5.addData(camData.toUtf8()); md5data = md5.result().toHex(); #endif /* HAVE_GPHOTO2 */ return md5data; } bool GPCamera::doConnect() { #ifdef HAVE_GPHOTO2 int errorCode; // -- first step - setup the camera -------------------- if (d->camera) { gp_camera_unref(d->camera); d->camera = 0; } CameraAbilitiesList* abilList = 0; GPPortInfoList* infoList = 0; GPPortInfo info; gp_camera_new(&d->camera); delete d->status; d->status = 0; d->status = new GPStatus(); gp_abilities_list_new(&abilList); gp_abilities_list_load(abilList, d->status->context); gp_port_info_list_new(&infoList); gp_port_info_list_load(infoList); int modelNum = gp_abilities_list_lookup_model(abilList, m_model.toLatin1().constData()); int portNum = gp_port_info_list_lookup_path(infoList, m_port.toLatin1().constData()); gp_abilities_list_get_abilities(abilList, modelNum, &d->cameraAbilities); errorCode = gp_camera_set_abilities(d->camera, d->cameraAbilities); if (errorCode != GP_OK) { qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to set camera Abilities!"; printGphotoErrorDescription(errorCode); gp_camera_unref(d->camera); d->camera = 0; gp_abilities_list_free(abilList); gp_port_info_list_free(infoList); return false; } if (m_model != QLatin1String("Directory Browse")) { gp_port_info_list_get_info(infoList, portNum, &info); errorCode = gp_camera_set_port_info(d->camera, info); if (errorCode != GP_OK) { qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to set camera port!"; printGphotoErrorDescription(errorCode); gp_camera_unref(d->camera); d->camera = 0; gp_abilities_list_free(abilList); gp_port_info_list_free(infoList); return false; } } gp_abilities_list_free(abilList); gp_port_info_list_free(infoList); if (d->cameraAbilities.file_operations & GP_FILE_OPERATION_PREVIEW) { m_thumbnailSupport = true; } if (d->cameraAbilities.file_operations & GP_FILE_OPERATION_DELETE) { m_deleteSupport = true; } if (d->cameraAbilities.folder_operations & GP_FOLDER_OPERATION_PUT_FILE) { m_uploadSupport = true; } if (d->cameraAbilities.folder_operations & GP_FOLDER_OPERATION_MAKE_DIR) { m_mkDirSupport = true; } if (d->cameraAbilities.folder_operations & GP_FOLDER_OPERATION_REMOVE_DIR) { m_delDirSupport = true; } if (d->cameraAbilities.operations & GP_OPERATION_CAPTURE_IMAGE) { m_captureImageSupport = true; } if (d->cameraAbilities.operations & GP_OPERATION_CAPTURE_PREVIEW) { m_captureImagePreviewSupport = true; } // -- Try and initialize the camera to see if its connected ----------------- errorCode = gp_camera_init(d->camera, d->status->context); if (errorCode != GP_OK) { qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to initialize camera!"; printGphotoErrorDescription(errorCode); gp_camera_unref(d->camera); d->camera = 0; return false; } d->cameraInitialized = true; return true; #else return false; #endif /* HAVE_GPHOTO2 */ } void GPCamera::cancel() { #ifdef HAVE_GPHOTO2 /* TODO what to do on cancel, if there's nothing to cancel? if (!d->status) { return; }*/ d->status->cancel = true; #endif /* HAVE_GPHOTO2 */ } bool GPCamera::getFreeSpace(unsigned long& kBSize, unsigned long& kBAvail) { #ifdef HAVE_GPHOTO2 // NOTE: This method depends of libgphoto2 2.4.0 int nrofsinfos; CameraStorageInformation* sinfos = 0; d->status->cancel = false; int errorCode = gp_camera_get_storageinfo(d->camera, &sinfos, &nrofsinfos, d->status->context); if (errorCode != GP_OK) { qCDebug(DIGIKAM_IMPORTUI_LOG) << "Getting storage information not supported for this camera!"; printGphotoErrorDescription(errorCode); return false; } for (int i = 0 ; i < nrofsinfos ; ++i) { if (sinfos[i].fields & GP_STORAGEINFO_FILESYSTEMTYPE) { switch(sinfos[i].fstype) { case GP_STORAGEINFO_FST_UNDEFINED: qCDebug(DIGIKAM_IMPORTUI_LOG) << "Storage fstype: undefined"; break; case GP_STORAGEINFO_FST_GENERICFLAT: qCDebug(DIGIKAM_IMPORTUI_LOG) << "Storage fstype: flat, all in one directory"; break; case GP_STORAGEINFO_FST_GENERICHIERARCHICAL: qCDebug(DIGIKAM_IMPORTUI_LOG) << "Storage fstype: generic tree hierarchy"; break; case GP_STORAGEINFO_FST_DCF: qCDebug(DIGIKAM_IMPORTUI_LOG) << "DCIM style storage"; break; } } if (sinfos[i].fields & GP_STORAGEINFO_LABEL) { qCDebug(DIGIKAM_IMPORTUI_LOG) << "Storage label: " << QString::fromUtf8(sinfos[i].label); } if (sinfos[i].fields & GP_STORAGEINFO_DESCRIPTION) { qCDebug(DIGIKAM_IMPORTUI_LOG) << "Storage description: " << QString::fromUtf8(sinfos[i].description); } if (sinfos[i].fields & GP_STORAGEINFO_BASE) { qCDebug(DIGIKAM_IMPORTUI_LOG) << "Storage base-dir: " << QString::fromUtf8(sinfos[i].basedir); // TODO in order for this to work, the upload dialog needs to be fixed. /* - if(nrofsinfos == 1) + if (nrofsinfos == 1) { qCDebug(DIGIKAM_IMPORTUI_LOG) << "Only one storage, so setting storage directory to" << sinfos[i].basedir; m_path = QString(sinfos[i].basedir); } */ } if (sinfos[i].fields & GP_STORAGEINFO_ACCESS) { switch (sinfos[i].access) { case GP_STORAGEINFO_AC_READWRITE: qCDebug(DIGIKAM_IMPORTUI_LOG) << "Storage access: R/W"; break; case GP_STORAGEINFO_AC_READONLY: qCDebug(DIGIKAM_IMPORTUI_LOG) << "Storage access: RO"; break; case GP_STORAGEINFO_AC_READONLY_WITH_DELETE: qCDebug(DIGIKAM_IMPORTUI_LOG) << "Storage access: RO + Del"; break; default: break; } } if (sinfos[i].fields & GP_STORAGEINFO_STORAGETYPE) { switch (sinfos[i].type) { case GP_STORAGEINFO_ST_FIXED_ROM: qCDebug(DIGIKAM_IMPORTUI_LOG) << "Storage type: fixed ROM"; break; case GP_STORAGEINFO_ST_REMOVABLE_ROM: qCDebug(DIGIKAM_IMPORTUI_LOG) << "Storage type: removable ROM"; break; case GP_STORAGEINFO_ST_FIXED_RAM: qCDebug(DIGIKAM_IMPORTUI_LOG) << "Storage type: fixed RAM"; break; case GP_STORAGEINFO_ST_REMOVABLE_RAM: qCDebug(DIGIKAM_IMPORTUI_LOG) << "Storage type: removable RAM"; break; case GP_STORAGEINFO_ST_UNKNOWN: default: qCDebug(DIGIKAM_IMPORTUI_LOG) << "Storage type: unknown"; break; } } if (sinfos[i].fields & GP_STORAGEINFO_MAXCAPACITY) { kBSize += sinfos[i].capacitykbytes; qCDebug(DIGIKAM_IMPORTUI_LOG) << "Storage capacity: " << kBSize; } if (sinfos[i].fields & GP_STORAGEINFO_FREESPACEKBYTES) { kBAvail += sinfos[i].freekbytes; qCDebug(DIGIKAM_IMPORTUI_LOG) << "Storage free-space: " << kBAvail; } } return true; #else Q_UNUSED(kBSize); Q_UNUSED(kBAvail); return false; #endif /* HAVE_GPHOTO2 */ } bool GPCamera::getPreview(QImage& preview) { #ifdef HAVE_GPHOTO2 int errorCode; CameraFile* cfile = 0; const char* data = 0; unsigned long int size; d->status->cancel = false; gp_file_new(&cfile); errorCode = gp_camera_capture_preview(d->camera, cfile, d->status->context); if (errorCode != GP_OK) { qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to initialize camera preview mode!"; printGphotoErrorDescription(errorCode); gp_file_unref(cfile); return false; } errorCode = gp_file_get_data_and_size(cfile, &data, &size); if (errorCode != GP_OK) { qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get preview from camera!"; printGphotoErrorDescription(errorCode); gp_file_unref(cfile); return false; } preview.loadFromData((const uchar*) data, (uint) size); gp_file_unref(cfile); return true; #else Q_UNUSED(preview); return false; #endif /* HAVE_GPHOTO2 */ } bool GPCamera::capture(CamItemInfo& itemInfo) { #ifdef HAVE_GPHOTO2 int errorCode; CameraFilePath path; d->status->cancel = false; errorCode = gp_camera_capture(d->camera, GP_CAPTURE_IMAGE, &path, d->status->context); if (errorCode != GP_OK) { qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to take camera capture!"; printGphotoErrorDescription(errorCode); return false; } // Get new camera item information. itemInfo.folder = QString::fromUtf8(path.folder); itemInfo.name = QString::fromUtf8(path.name); CameraFileInfo info; errorCode = gp_camera_file_get_info(d->camera, QFile::encodeName(itemInfo.folder).constData(), QFile::encodeName(itemInfo.name).constData(), &info, d->status->context); if (errorCode != GP_OK) { qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get camera item information!"; printGphotoErrorDescription(errorCode); return false; } itemInfo.ctime = QDateTime(); itemInfo.mime = QString(); itemInfo.size = -1; itemInfo.width = -1; itemInfo.height = -1; itemInfo.downloaded = CamItemInfo::DownloadUnknown; itemInfo.readPermissions = -1; itemInfo.writePermissions = -1; /* The mime type returned by Gphoto2 is dummy with all RAW files. if (info.file.fields & GP_FILE_INFO_TYPE) itemInfo.mime = info.file.type;*/ itemInfo.mime = mimeType(itemInfo.name.section(QLatin1Char('.'), -1).toLower()); if (info.file.fields & GP_FILE_INFO_MTIME) { itemInfo.ctime = QDateTime::fromTime_t(info.file.mtime); } if (info.file.fields & GP_FILE_INFO_SIZE) { itemInfo.size = info.file.size; } if (info.file.fields & GP_FILE_INFO_WIDTH) { itemInfo.width = info.file.width; } if (info.file.fields & GP_FILE_INFO_HEIGHT) { itemInfo.height = info.file.height; } if (info.file.fields & GP_FILE_INFO_STATUS) { if (info.file.status == GP_FILE_STATUS_DOWNLOADED) { itemInfo.downloaded = CamItemInfo::DownloadedYes; } else { itemInfo.downloaded = CamItemInfo::DownloadedNo; } } if (info.file.fields & GP_FILE_INFO_PERMISSIONS) { if (info.file.permissions & GP_FILE_PERM_READ) { itemInfo.readPermissions = 1; } else { itemInfo.readPermissions = 0; } if (info.file.permissions & GP_FILE_PERM_DELETE) { itemInfo.writePermissions = 1; } else { itemInfo.writePermissions = 0; } } return true; #else Q_UNUSED(itemInfo); return false; #endif /* HAVE_GPHOTO2 */ } bool GPCamera::getFolders(const QString& folder) { #ifdef HAVE_GPHOTO2 int errorCode; CameraList* clist = 0; gp_list_new(&clist); d->status->cancel = false; errorCode = gp_camera_folder_list_folders(d->camera, QFile::encodeName(folder).constData(), clist, d->status->context); if (errorCode != GP_OK) { qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get folders list from camera!"; printGphotoErrorDescription(errorCode); gp_list_unref(clist); return false; } QStringList subFolderList; int count = gp_list_count(clist); if (count < 1) { return true; } for (int i = 0 ; i < count ; ++i) { const char* subFolder = 0; errorCode = gp_list_get_name(clist, i, &subFolder); if (errorCode != GP_OK) { qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get folder name from camera!"; printGphotoErrorDescription(errorCode); gp_list_unref(clist); return false; } subFolderList.append(folder + QFile::decodeName(subFolder) + QLatin1Char('/')); } gp_list_unref(clist); emit signalFolderList(subFolderList); return true; #else Q_UNUSED(folder); return false; #endif /* HAVE_GPHOTO2 */ } // TODO unused, remove? bool GPCamera::getItemsList(const QString& folder, QStringList& itemsList) { #ifdef HAVE_GPHOTO2 int errorCode; CameraList* clist = 0; const char* cname = 0; gp_list_new(&clist); d->status->cancel = false; errorCode = gp_camera_folder_list_files(d->camera, QFile::encodeName(folder).constData(), clist, d->status->context); if (errorCode != GP_OK) { qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get folder files list from camera!"; printGphotoErrorDescription(errorCode); gp_list_unref(clist); return false; } int count = gp_list_count(clist); for (int i = 0 ; i < count ; ++i) { errorCode = gp_list_get_name(clist, i, &cname); if (errorCode != GP_OK) { qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get file name from camera!"; printGphotoErrorDescription(errorCode); gp_list_unref(clist); return false; } itemsList.append(QFile::decodeName(cname)); } gp_list_unref(clist); return true; #else Q_UNUSED(folder); Q_UNUSED(itemsList); return false; #endif /* HAVE_GPHOTO2 */ } bool GPCamera::getItemsInfoList(const QString& folder, bool useMetadata, CamItemInfoList& items) { #ifdef HAVE_GPHOTO2 int errorCode; CameraList* clist = 0; const char* cname = 0; gp_list_new(&clist); d->status->cancel = false; errorCode = gp_camera_folder_list_files(d->camera, QFile::encodeName(folder).constData(), clist, d->status->context); if (errorCode != GP_OK) { qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get folder files list from camera!"; printGphotoErrorDescription(errorCode); gp_list_unref(clist); return false; } int count = gp_list_count(clist); for (int i = 0 ; i < count ; ++i) { errorCode = gp_list_get_name(clist, i, &cname); if (errorCode != GP_OK) { qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get file name from camera!"; printGphotoErrorDescription(errorCode); gp_list_unref(clist); return false; } // TODO for further speed-up, getItemInfoInternal call could be called separately when needed CamItemInfo info; getItemInfoInternal(folder, QFile::decodeName(cname), info, useMetadata); items.append(info); } gp_list_unref(clist); return true; #else Q_UNUSED(folder); Q_UNUSED(useMetadata); Q_UNUSED(items); return false; #endif /* HAVE_GPHOTO2 */ } void GPCamera::getItemInfo(const QString& folder, const QString& itemName, CamItemInfo& info, bool useMetadata) { #ifdef HAVE_GPHOTO2 getItemInfoInternal(folder, itemName, info, useMetadata); #else Q_UNUSED(folder); Q_UNUSED(itemName); Q_UNUSED(info); Q_UNUSED(useMetadata); #endif /* HAVE_GPHOTO2 */ } void GPCamera::getItemInfoInternal(const QString& folder, const QString& itemName, CamItemInfo& info, bool useMetadata) { #ifdef HAVE_GPHOTO2 info.folder = folder; info.name = itemName; d->status->cancel = false; info.previewPossible = m_captureImagePreviewSupport; CameraFileInfo cfinfo; gp_camera_file_get_info(d->camera, QFile::encodeName(info.folder).constData(), QFile::encodeName(info.name).constData(), &cfinfo, d->status->context); // if preview has size field, it's a valid preview most likely, otherwise we'll skip it later on if (cfinfo.preview.fields & GP_FILE_INFO_SIZE) { info.previewPossible = true; } if (cfinfo.file.fields & GP_FILE_INFO_STATUS) { if (cfinfo.file.status == GP_FILE_STATUS_DOWNLOADED) { info.downloaded = CamItemInfo::DownloadedYes; } } if (cfinfo.file.fields & GP_FILE_INFO_SIZE) { info.size = cfinfo.file.size; } if (cfinfo.file.fields & GP_FILE_INFO_PERMISSIONS) { if (cfinfo.file.permissions & GP_FILE_PERM_READ) { info.readPermissions = 1; } else { info.readPermissions = 0; } if (cfinfo.file.permissions & GP_FILE_PERM_DELETE) { info.writePermissions = 1; } else { info.writePermissions = 0; } } /* The mime type returned by Gphoto2 is dummy with all RAW files. if (cfinfo.file.fields & GP_FILE_INFO_TYPE) info.mime = cfinfo.file.type; */ info.mime = mimeType(info.name.section(QLatin1Char('.'), -1).toLower()); if (!info.mime.isEmpty()) { if (useMetadata) { // Try to use file metadata DMetadata meta; getMetadata(folder, itemName, meta); fillItemInfoFromMetadata(info, meta); // Fall back to camera file system info if (info.ctime.isNull()) { if (cfinfo.file.fields & GP_FILE_INFO_MTIME) { info.ctime = QDateTime::fromTime_t(cfinfo.file.mtime); } else { info.ctime = QDateTime::currentDateTime(); } } } else { // Only use properties provided by camera. if (cfinfo.file.fields & GP_FILE_INFO_MTIME) { info.ctime = QDateTime::fromTime_t(cfinfo.file.mtime); } else { info.ctime = QDateTime::currentDateTime(); } if (cfinfo.file.fields & GP_FILE_INFO_WIDTH) { info.width = cfinfo.file.width; } if (cfinfo.file.fields & GP_FILE_INFO_HEIGHT) { info.height = cfinfo.file.height; } } } #else Q_UNUSED(folder); Q_UNUSED(itemName); Q_UNUSED(info); Q_UNUSED(useMetadata); #endif /* HAVE_GPHOTO2 */ } bool GPCamera::getThumbnail(const QString& folder, const QString& itemName, QImage& thumbnail) { #ifdef HAVE_GPHOTO2 int errorCode; CameraFile* cfile = 0; const char* data = 0; unsigned long int size; gp_file_new(&cfile); d->status->cancel = false; errorCode = gp_camera_file_get(d->camera, QFile::encodeName(folder).constData(), QFile::encodeName(itemName).constData(), GP_FILE_TYPE_PREVIEW, cfile, d->status->context); if (errorCode != GP_OK) { qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get camera item!" << folder << itemName; printGphotoErrorDescription(errorCode); gp_file_unref(cfile); return false; } errorCode = gp_file_get_data_and_size(cfile, &data, &size); if (errorCode != GP_OK) { qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get thumbnail from camera item!" << folder << itemName; printGphotoErrorDescription(errorCode); gp_file_unref(cfile); return false; } thumbnail.loadFromData((const uchar*) data, (uint) size); gp_file_unref(cfile); return !thumbnail.isNull(); #else Q_UNUSED(folder); Q_UNUSED(itemName); Q_UNUSED(thumbnail); return false; #endif /* HAVE_GPHOTO2 */ } bool GPCamera::getMetadata(const QString& folder, const QString& itemName, DMetadata& meta) { #ifdef HAVE_GPHOTO2 int errorCode; CameraFile* cfile = 0; const char* data = 0; unsigned long int size; gp_file_new(&cfile); d->status->cancel = false; errorCode = gp_camera_file_get(d->camera, QFile::encodeName(folder).constData(), QFile::encodeName(itemName).constData(), GP_FILE_TYPE_EXIF, cfile, d->status->context); if (errorCode != GP_OK) { qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get camera item!"; printGphotoErrorDescription(errorCode); gp_file_unref(cfile); return false; } errorCode = gp_file_get_data_and_size(cfile, &data, &size); if (errorCode != GP_OK) { qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get Exif data from camera item!"; printGphotoErrorDescription(errorCode); gp_file_unref(cfile); return false; } QByteArray exifData(data, size); gp_file_unref(cfile); // Sometimes, GPhoto2 drivers return complete APP1 JFIF section. Exiv2 cannot // decode (yet) exif metadata from APP1. We will find Exif header to get data at this place // to please with Exiv2... qCDebug(DIGIKAM_IMPORTUI_LOG) << "Size of Exif metadata from camera = " << exifData.size(); if (!exifData.isEmpty()) { char exifHeader[] = { 0x45, 0x78, 0x69, 0x66, 0x00, 0x00 }; int i = exifData.indexOf(*exifHeader); if (i != -1) { qCDebug(DIGIKAM_IMPORTUI_LOG) << "Exif header found at position " << i; i = i + sizeof(exifHeader); QByteArray data; data.resize(exifData.size() - i); memcpy(data.data(), exifData.data() + i, data.size()); meta.setExif(data); return true; } } return false; #else Q_UNUSED(folder); Q_UNUSED(itemName); Q_UNUSED(meta); return false; #endif /* HAVE_GPHOTO2 */ } bool GPCamera::downloadItem(const QString& folder, const QString& itemName, const QString& saveFile) { #ifdef HAVE_GPHOTO2 int errorCode; CameraFile* cfile = 0; d->status->cancel = false; QFile file(saveFile); if (!file.open(QIODevice::ReadWrite)) { qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to open file" << file.fileName() << file.errorString(); return false; } // dup fd, passing fd control to gphoto2 later int handle = dup(file.handle()); if (handle == -1) { qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to dup file descriptor"; return false; } errorCode = gp_file_new_from_fd(&cfile, handle); if (errorCode != GP_OK) { qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get camera item!"; printGphotoErrorDescription(errorCode); return false; } errorCode = gp_camera_file_get(d->camera, QFile::encodeName(folder).constData(), QFile::encodeName(itemName).constData(), GP_FILE_TYPE_NORMAL, cfile, d->status->context); if (errorCode != GP_OK) { qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get camera item!"; printGphotoErrorDescription(errorCode); gp_file_unref(cfile); return false; } time_t mtime; errorCode = gp_file_get_mtime(cfile, &mtime); if (errorCode == GP_OK && mtime) { struct utimbuf ut; ut.modtime = mtime; ut.actime = mtime; ::utime(QFile::encodeName(saveFile).constData(), &ut); } file.close(); gp_file_unref(cfile); return true; #else Q_UNUSED(folder); Q_UNUSED(itemName); Q_UNUSED(saveFile); return false; #endif /* HAVE_GPHOTO2 */ } bool GPCamera::setLockItem(const QString& folder, const QString& itemName, bool lock) { #ifdef HAVE_GPHOTO2 int errorCode; d->status->cancel = false; CameraFileInfo info; errorCode = gp_camera_file_get_info(d->camera, QFile::encodeName(folder).constData(), QFile::encodeName(itemName).constData(), &info, d->status->context); if (errorCode != GP_OK) { qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get camera item properties!"; printGphotoErrorDescription(errorCode); return false; } if (info.file.fields & GP_FILE_INFO_PERMISSIONS) { if (lock) { // Lock the file to set read only flag info.file.permissions = (CameraFilePermissions)GP_FILE_PERM_READ; } else { // Unlock the file to set read/write flag info.file.permissions = (CameraFilePermissions)(GP_FILE_PERM_READ | GP_FILE_PERM_DELETE); } } // Some gphoto2 drivers need to have only the right flag at on to process properties update in camera. info.file.fields = GP_FILE_INFO_PERMISSIONS; info.preview.fields = GP_FILE_INFO_NONE; info.audio.fields = GP_FILE_INFO_NONE; errorCode = gp_camera_file_set_info(d->camera, QFile::encodeName(folder).constData(), QFile::encodeName(itemName).constData(), info, d->status->context); if (errorCode != GP_OK) { qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to set camera item lock properties!"; printGphotoErrorDescription(errorCode); return false; } return true; #else Q_UNUSED(folder); Q_UNUSED(itemName); Q_UNUSED(lock); return false; #endif /* HAVE_GPHOTO2 */ } bool GPCamera::deleteItem(const QString& folder, const QString& itemName) { #ifdef HAVE_GPHOTO2 d->status->cancel = false; int errorCode = gp_camera_file_delete(d->camera, QFile::encodeName(folder).constData(), QFile::encodeName(itemName).constData(), d->status->context); if (errorCode != GP_OK) { qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to delete camera item!"; printGphotoErrorDescription(errorCode); return false; } return true; #else Q_UNUSED(folder); Q_UNUSED(itemName); return false; #endif /* HAVE_GPHOTO2 */ } // TODO fix to go through all folders // TODO this was never even used.. bool GPCamera::deleteAllItems(const QString& folder) { #ifdef HAVE_GPHOTO2 int errorCode; QStringList folderList; d->status->cancel = false; errorCode = gp_camera_folder_delete_all(d->camera, QFile::encodeName(folder).constData(), d->status->context); if (errorCode != GP_OK) { qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to delete camera folder!"; printGphotoErrorDescription(errorCode); return false; } return true; #else Q_UNUSED(folder); return false; #endif /* HAVE_GPHOTO2 */ } bool GPCamera::uploadItem(const QString& folder, const QString& itemName, const QString& localFile, CamItemInfo& itemInfo) { #ifdef HAVE_GPHOTO2 int errorCode; CameraFile* cfile = 0; errorCode = gp_file_new(&cfile); d->status->cancel = false; if (errorCode != GP_OK) { qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to init new camera file instance!"; printGphotoErrorDescription(errorCode); return false; } errorCode = gp_file_open(cfile, QFile::encodeName(localFile).constData()); if (errorCode != GP_OK) { qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to open file!"; printGphotoErrorDescription(errorCode); gp_file_unref(cfile); return false; } errorCode = gp_file_set_name(cfile, QFile::encodeName(itemName).constData()); if (errorCode != GP_OK) { qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to rename item from camera!"; printGphotoErrorDescription(errorCode); gp_file_unref(cfile); return false; } #ifdef HAVE_GPHOTO25 errorCode = gp_camera_folder_put_file(d->camera, QFile::encodeName(folder).constData(), QFile::encodeName(itemName).constData(), GP_FILE_TYPE_NORMAL, cfile, d->status->context); #else errorCode = gp_camera_folder_put_file(d->camera, QFile::encodeName(folder).constData(), cfile, d->status->context); #endif if (errorCode != GP_OK) { qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to upload item to camera!"; printGphotoErrorDescription(errorCode); gp_file_unref(cfile); return false; } // Get new camera item information. itemInfo.name = itemName; itemInfo.folder = folder; CameraFileInfo info; errorCode = gp_camera_file_get_info(d->camera, QFile::encodeName(folder).constData(), QFile::encodeName(itemName).constData(), &info, d->status->context); if (errorCode != GP_OK) { qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get camera item information!"; printGphotoErrorDescription(errorCode); gp_file_unref(cfile); return false; } itemInfo.ctime = QDateTime(); itemInfo.mime = QString(); itemInfo.size = -1; itemInfo.width = -1; itemInfo.height = -1; itemInfo.downloaded = CamItemInfo::DownloadUnknown; itemInfo.readPermissions = -1; itemInfo.writePermissions = -1; /* The mime type returned by Gphoto2 is dummy with all RAW files. if (info.file.fields & GP_FILE_INFO_TYPE) itemInfo.mime = info.file.type; */ itemInfo.mime = mimeType(itemInfo.name.section(QLatin1Char('.'), -1).toLower()); if (info.file.fields & GP_FILE_INFO_MTIME) { itemInfo.ctime = QDateTime::fromTime_t(info.file.mtime); } if (info.file.fields & GP_FILE_INFO_SIZE) { itemInfo.size = info.file.size; } if (info.file.fields & GP_FILE_INFO_WIDTH) { itemInfo.width = info.file.width; } if (info.file.fields & GP_FILE_INFO_HEIGHT) { itemInfo.height = info.file.height; } if (info.file.fields & GP_FILE_INFO_STATUS) { if (info.file.status == GP_FILE_STATUS_DOWNLOADED) { itemInfo.downloaded = CamItemInfo::DownloadedYes; } else { itemInfo.downloaded = CamItemInfo::DownloadedNo; } } if (info.file.fields & GP_FILE_INFO_PERMISSIONS) { if (info.file.permissions & GP_FILE_PERM_READ) { itemInfo.readPermissions = 1; } else { itemInfo.readPermissions = 0; } if (info.file.permissions & GP_FILE_PERM_DELETE) { itemInfo.writePermissions = 1; } else { itemInfo.writePermissions = 0; } } gp_file_unref(cfile); return true; #else Q_UNUSED(folder); Q_UNUSED(itemName); Q_UNUSED(localFile); Q_UNUSED(itemInfo); return false; #endif /* HAVE_GPHOTO2 */ } bool GPCamera::cameraSummary(QString& summary) { #ifdef HAVE_GPHOTO2 int errorCode; CameraText sum; d->status->cancel = false; errorCode = gp_camera_get_summary(d->camera, &sum, d->status->context); if (errorCode != GP_OK) { qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get camera summary!"; printGphotoErrorDescription(errorCode); return false; } // we do not expect titel/model/etc. to contain newlines, // so we just escape HTML characters summary = i18nc("@info List of device properties", "Title: %1
" "Model: %2
" "Port: %3
" "Path: %4

", title().toHtmlEscaped(), model().toHtmlEscaped(), port().toHtmlEscaped(), path().toHtmlEscaped()); summary += i18nc("@info List of supported device operations", "Thumbnails: %1
" "Capture image: %2
" "Delete items: %3
" "Upload items: %4
" "Create directories: %5
" "Delete Directories: %6

", thumbnailSupport() ? i18n("yes") : i18n("no"), captureImageSupport() ? i18n("yes") : i18n("no"), deleteSupport() ? i18n("yes") : i18n("no"), uploadSupport() ? i18n("yes") : i18n("no"), mkDirSupport() ? i18n("yes") : i18n("no"), delDirSupport() ? i18n("yes") : i18n("no")); // here we need to make sure whitespace and newlines // are converted to HTML properly summary.append(Qt::convertFromPlainText(QString::fromLocal8Bit(sum.text), Qt::WhiteSpacePre)); return true; #else Q_UNUSED(summary); return false; #endif /* HAVE_GPHOTO2 */ } bool GPCamera::cameraManual(QString& manual) { #ifdef HAVE_GPHOTO2 int errorCode; CameraText man; d->status->cancel = false; errorCode = gp_camera_get_manual(d->camera, &man, d->status->context); if (errorCode != GP_OK) { qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get camera manual!"; printGphotoErrorDescription(errorCode); return false; } // I guess manual is plain text and not HTML? // Can't test it. (Michael G. Hansen) manual = Qt::convertFromPlainText(QString::fromLocal8Bit(man.text), Qt::WhiteSpacePre); return true; #else Q_UNUSED(manual); return false; #endif /* HAVE_GPHOTO2 */ } bool GPCamera::cameraAbout(QString& about) { #ifdef HAVE_GPHOTO2 int errorCode; CameraText abt; d->status->cancel = false; errorCode = gp_camera_get_about(d->camera, &abt, d->status->context); if (errorCode != GP_OK) { qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get information about camera!"; printGphotoErrorDescription(errorCode); return false; } // here we need to make sure whitespace and newlines // are converted to HTML properly about = Qt::convertFromPlainText(QString::fromLocal8Bit(abt.text), Qt::WhiteSpacePre); about.append(QString::fromUtf8("

To report problems about this driver, please contact " "the gphoto2 team at:

http://gphoto.org/bugs")); return true; #else Q_UNUSED(about); return false; #endif /* HAVE_GPHOTO2 */ } // -- Static methods --------------------------------------------------------------------- void GPCamera::printGphotoErrorDescription(int errorCode) { #ifdef HAVE_GPHOTO2 qCDebug(DIGIKAM_IMPORTUI_LOG) << "Libgphoto2 error: " << gp_result_as_string(errorCode) << " (" << errorCode << ")"; #else Q_UNUSED(errorCode); #endif /* HAVE_GPHOTO2 */ } void GPCamera::getSupportedCameras(int& count, QStringList& clist) { #ifdef HAVE_GPHOTO2 clist.clear(); count = 0; CameraAbilities abil; CameraAbilitiesList* abilList = 0; GPContext* context = 0; context = gp_context_new(); gp_abilities_list_new(&abilList); gp_abilities_list_load(abilList, context); count = gp_abilities_list_count(abilList); if (count < 0) { qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get list of cameras!"; printGphotoErrorDescription(count); gp_context_unref(context); return; } else { for (int i = 0 ; i < count ; ++i) { gp_abilities_list_get_abilities(abilList, i, &abil); const char* cname = abil.model; clist.append(QString::fromLocal8Bit(cname)); } } gp_abilities_list_free(abilList); gp_context_unref(context); #else Q_UNUSED(count); Q_UNUSED(clist); #endif /* HAVE_GPHOTO2 */ } void GPCamera::getSupportedPorts(QStringList& plist) { #ifdef HAVE_GPHOTO2 GPPortInfoList* list = 0; GPPortInfo info; plist.clear(); gp_port_info_list_new(&list); gp_port_info_list_load(list); int numPorts = gp_port_info_list_count(list); if (numPorts < 0) { qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get list of port!"; printGphotoErrorDescription(numPorts); gp_port_info_list_free(list); return; } else { for (int i = 0 ; i < numPorts ; ++i) { gp_port_info_list_get_info(list, i, &info); #ifdef HAVE_GPHOTO25 char* xpath = 0; gp_port_info_get_name (info, &xpath); plist.append(QString::fromUtf8(xpath)); #else plist.append(QString::fromUtf8(info.path)); #endif } } gp_port_info_list_free(list); #else Q_UNUSED(plist); #endif /* HAVE_GPHOTO2 */ } void GPCamera::getCameraSupportedPorts(const QString& model, QStringList& plist) { #ifdef HAVE_GPHOTO2 int i = 0; plist.clear(); CameraAbilities abilities; CameraAbilitiesList* abilList = 0; GPContext* context = 0; context = gp_context_new(); gp_abilities_list_new(&abilList); gp_abilities_list_load(abilList, context); i = gp_abilities_list_lookup_model(abilList, model.toLocal8Bit().data()); gp_abilities_list_get_abilities(abilList, i, &abilities); gp_abilities_list_free(abilList); if (abilities.port & GP_PORT_SERIAL) { plist.append(QLatin1String("serial")); } if (abilities.port & GP_PORT_PTPIP) { plist.append(QLatin1String("ptpip")); } if (abilities.port & GP_PORT_USB) { plist.append(QLatin1String("usb")); } gp_context_unref(context); #else Q_UNUSED(model); Q_UNUSED(plist); #endif /* HAVE_GPHOTO2 */ } int GPCamera::autoDetect(QString& model, QString& port) { #ifdef HAVE_GPHOTO2 CameraList* camList = 0; CameraAbilitiesList* abilList = 0; GPPortInfoList* infoList = 0; const char* camModel_ = 0, *camPort_ = 0; GPContext* context = 0; context = gp_context_new(); gp_list_new(&camList); gp_abilities_list_new(&abilList); gp_abilities_list_load(abilList, context); gp_port_info_list_new(&infoList); gp_port_info_list_load(infoList); gp_abilities_list_detect(abilList, infoList, camList, context); gp_abilities_list_free(abilList); gp_port_info_list_free(infoList); gp_context_unref(context); int count = gp_list_count(camList); if (count <= 0) { qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to autodetect camera!"; printGphotoErrorDescription(count); gp_list_free(camList); return -1; } camModel_ = 0; camPort_ = 0; for (int i = 0 ; i < count ; ++i) { if (gp_list_get_name(camList, i, &camModel_) != GP_OK) { qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to autodetect camera!"; gp_list_free(camList); return -1; } if (gp_list_get_value(camList, i, &camPort_) != GP_OK) { qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to autodetect camera!"; gp_list_free(camList); return -1; } if (camModel_ && camPort_) { model = QLatin1String(camModel_); port = QLatin1String(camPort_); gp_list_free(camList); return 0; } } qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to autodetect camera!"; gp_list_free(camList); #else Q_UNUSED(model); Q_UNUSED(port); #endif /* HAVE_GPHOTO2 */ return -1; } bool GPCamera::findConnectedUsbCamera(int vendorId, int productId, QString& model, QString& port) { #ifdef HAVE_GPHOTO2 CameraAbilitiesList* abilList = 0; GPPortInfoList* list = 0; GPContext* context = 0; CameraList* camList = 0; bool success = false; // get name and port of detected camera const char* model_str = 0; const char* port_str = 0; context = gp_context_new(); // get list of all ports gp_port_info_list_new(&list); gp_port_info_list_load(list); gp_abilities_list_new(&abilList); // get list of all supported cameras gp_abilities_list_load(abilList, context); // autodetect all cameras, then match the list to the passed in USB ids gp_list_new (&camList); gp_abilities_list_detect(abilList, list, camList, context); gp_context_unref(context); int count = gp_list_count(camList); int cnt = 0; for (int i = 0 ; i < count ; ++i) { const char* xmodel = 0; gp_list_get_name(camList, i, &xmodel); int model = gp_abilities_list_lookup_model (abilList, xmodel); CameraAbilities ab; gp_abilities_list_get_abilities(abilList, model, &ab); if (ab.port != GP_PORT_USB) continue; /* KDE provides us USB Vendor and Product, but we might just * have covered this via a class match. Check class matched * cameras also for matchingo USB vendor/product id */ if (ab.usb_vendor == 0) { int ret; GPPortInfo info; const char* xport = 0; GPPort* gpport = 0; /* get the port path so we only look at this bus position */ gp_list_get_value(camList, i, &xport); ret = gp_port_info_list_lookup_path (list, xport); if (ret < GP_OK) /* should not happen */ continue; /* get the lowlevel port info for the path */ gp_port_info_list_get_info(list, ret, &info); /* open lowlevel driver interface briefly to search */ gp_port_new(&gpport); gp_port_set_info(gpport, info); /* And now call into the lowlevel usb driver to see if the bus position * has that specific vendor/product id */ if (gp_port_usb_find_device(gpport, vendorId, productId) == GP_OK) { ab.usb_vendor = vendorId; ab.usb_product = productId; } gp_port_free (gpport); } if (ab.usb_vendor != vendorId) continue; if (ab.usb_product != productId) continue; /* keep it, and continue iterating, in case we find another one */ gp_list_get_name (camList, i, &model_str); gp_list_get_value(camList, i, &port_str); cnt++; } gp_port_info_list_free(list); gp_abilities_list_free(abilList); if (cnt > 0) { if (cnt > 1) { qCWarning(DIGIKAM_IMPORTUI_LOG) << "More than one camera detected on port " << port << ". Due to restrictions in the GPhoto2 API, " << "only the first camera is used."; } model = QLatin1String(model_str); port = QLatin1String(port_str); success = true; } else { qCDebug(DIGIKAM_IMPORTUI_LOG) << "Failed to get information for the listed camera"; } gp_list_free(camList); return success; #else Q_UNUSED(vendorId); Q_UNUSED(productId); Q_UNUSED(model); Q_UNUSED(port); return false; #endif /* HAVE_GPHOTO2 */ } } // namespace Digikam diff --git a/core/utilities/import/items/itemviewimportdelegate.cpp b/core/utilities/import/items/itemviewimportdelegate.cpp index 4f3619def0..a30c138422 100644 --- a/core/utilities/import/items/itemviewimportdelegate.cpp +++ b/core/utilities/import/items/itemviewimportdelegate.cpp @@ -1,707 +1,707 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2012-07-08 * Description : Item delegate for import interface items. * * Copyright (C) 2012 by Islam Wazery * Copyright (C) 2012-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 "itemviewimportdelegate.h" #include "itemviewimportdelegate_p.h" // Qt includes #include #include #include #include // KDE includes #include // Local includes #include "digikam_debug.h" #include "itemdelegateoverlay.h" #include "thememanager.h" #include "itemscanner.h" #include "itempropertiestab.h" #include "camiteminfo.h" #include "colorlabelwidget.h" #include "ratingwidget.h" namespace Digikam { ItemViewImportDelegatePrivate::ItemViewImportDelegatePrivate() { q = 0; spacing = 0; thumbSize = ThumbnailSize(0); // painting constants radius = 3; margin = 5; makeStarPolygon(); ratingPixmaps = QVector(10); } void ItemViewImportDelegatePrivate::init(ItemViewImportDelegate* const _q) { q = _q; q->connect(ThemeManager::instance(), SIGNAL(signalThemeChanged()), q, SLOT(slotThemeChanged())); } void ItemViewImportDelegatePrivate::clearRects() { gridSize = QSize(0, 0); rect = QRect(0, 0, 0, 0); ratingRect = QRect(0, 0, 0, 0); } void ItemViewImportDelegatePrivate::makeStarPolygon() { // Pre-computed star polygon for a 15x15 pixmap. starPolygon = RatingWidget::starPolygon(); starPolygonSize = QSize(15, 15); } // ---- ItemViewImportDelegate ----------------------------------------------- ItemViewImportDelegate::ItemViewImportDelegate(QObject* const parent) : DItemDelegate(parent), d_ptr(new ItemViewImportDelegatePrivate) { d_ptr->init(this); } ItemViewImportDelegate::ItemViewImportDelegate(ItemViewImportDelegatePrivate& dd, QObject* const parent) : DItemDelegate(parent), d_ptr(&dd) { d_ptr->init(this); } ItemViewImportDelegate::~ItemViewImportDelegate() { Q_D(ItemViewImportDelegate); removeAllOverlays(); delete d; } ThumbnailSize ItemViewImportDelegate::thumbnailSize() const { Q_D(const ItemViewImportDelegate); return d->thumbSize; } void ItemViewImportDelegate::setThumbnailSize(const ThumbnailSize& thumbSize) { Q_D(ItemViewImportDelegate); - if ( d->thumbSize != thumbSize) + if (d->thumbSize != thumbSize) { d->thumbSize = thumbSize; invalidatePaintingCache(); } } void ItemViewImportDelegate::setSpacing(int spacing) { Q_D(ItemViewImportDelegate); if (d->spacing == spacing) { return; } d->spacing = spacing; invalidatePaintingCache(); } int ItemViewImportDelegate::spacing() const { Q_D(const ItemViewImportDelegate); return d->spacing; } QRect ItemViewImportDelegate::rect() const { Q_D(const ItemViewImportDelegate); return d->rect; } QRect ItemViewImportDelegate::pixmapRect() const { return QRect(); } QRect ItemViewImportDelegate::imageInformationRect() const { return QRect(); } QRect ItemViewImportDelegate::ratingRect() const { Q_D(const ItemViewImportDelegate); return d->ratingRect; } void ItemViewImportDelegate::setRatingEdited(const QModelIndex& index) { Q_D(ItemViewImportDelegate); d->editingRating = index; } QSize ItemViewImportDelegate::sizeHint(const QStyleOptionViewItem& /*option*/, const QModelIndex& /*index*/) const { Q_D(const ItemViewImportDelegate); return d->rect.size(); } QSize ItemViewImportDelegate::gridSize() const { Q_D(const ItemViewImportDelegate); return d->gridSize; } bool ItemViewImportDelegate::acceptsToolTip(const QPoint&, const QRect& visualRect, const QModelIndex&, QRect* retRect) const { if (retRect) { *retRect = visualRect; } return true; } bool ItemViewImportDelegate::acceptsActivation(const QPoint& , const QRect& visualRect, const QModelIndex&, QRect* retRect) const { if (retRect) { *retRect = visualRect; } return true; } QAbstractItemDelegate* ItemViewImportDelegate::asDelegate() { return this; } void ItemViewImportDelegate::overlayDestroyed(QObject* o) { ItemDelegateOverlayContainer::overlayDestroyed(o); } void ItemViewImportDelegate::mouseMoved(QMouseEvent* e, const QRect& visualRect, const QModelIndex& index) { // 3-way indirection AbstractImportItemDelegate -> ItemViewImportDelegate -> ItemDelegateOverlayContainer ItemDelegateOverlayContainer::mouseMoved(e, visualRect, index); } void ItemViewImportDelegate::setDefaultViewOptions(const QStyleOptionViewItem& option) { Q_D(ItemViewImportDelegate); d->font = option.font; invalidatePaintingCache(); } void ItemViewImportDelegate::slotThemeChanged() { invalidatePaintingCache(); } void ItemViewImportDelegate::slotSetupChanged() { invalidatePaintingCache(); } void ItemViewImportDelegate::invalidatePaintingCache() { Q_D(ItemViewImportDelegate); QSize oldGridSize = d->gridSize; updateSizeRectsAndPixmaps(); if (oldGridSize != d->gridSize) { emit gridSizeChanged(d->gridSize); // emit sizeHintChanged(QModelIndex()); } emit visualChange(); } QRect ItemViewImportDelegate::drawThumbnail(QPainter* p, const QRect& thumbRect, const QPixmap& background, const QPixmap& thumbnail) const { p->drawPixmap(0, 0, background); if (thumbnail.isNull()) { return QRect(); } QRect r = thumbRect; QRect actualPixmapRect(r.x() + (r.width()-thumbnail.width())/2, r.y() + (r.height()-thumbnail.height())/2, thumbnail.width(), thumbnail.height()); QPixmap borderPix = thumbnailBorderPixmap(actualPixmapRect.size()); p->drawPixmap(actualPixmapRect.x()-3, actualPixmapRect.y()-3, borderPix); p->drawPixmap(r.x() + (r.width()-thumbnail.width())/2, r.y() + (r.height()-thumbnail.height())/2, thumbnail); return actualPixmapRect; } void ItemViewImportDelegate::drawRating(QPainter* p, const QModelIndex& index, const QRect& ratingRect, int rating, bool isSelected) const { Q_D(const ItemViewImportDelegate); if (d->editingRating != index) { p->drawPixmap(ratingRect, ratingPixmap(rating, isSelected)); } } void ItemViewImportDelegate::drawName(QPainter* p,const QRect& nameRect, const QString& name) const { Q_D(const ItemViewImportDelegate); p->setFont(d->fontReg); p->drawText(nameRect, Qt::AlignCenter, name);//squeezedTextCached(p, nameRect.width(), name)); } void ItemViewImportDelegate::drawCreationDate(QPainter* p, const QRect& dateRect, const QDateTime& date) const { Q_D(const ItemViewImportDelegate); p->setFont(d->fontXtra); QString str = dateToString(date); str = i18nc("date of image creation", "created: %1", str); p->drawText(dateRect, Qt::AlignCenter, str);//squeezedTextCached(p, dateRect.width(), str)); } void ItemViewImportDelegate::drawImageFormat(QPainter* p, const QRect& r, const QString& mime) const { Q_D(const ItemViewImportDelegate); if (!mime.isEmpty() && !r.isNull()) { QString type = mime.split(QLatin1Char('/')).at(1); type = ItemScanner::formatToString(type); p->save(); QFont fnt(d->fontReg); fnt.setWeight(QFont::Black); fnt.setItalic(false); p->setFont(fnt); p->setPen(QPen(Qt::gray)); p->setOpacity(0.50); QRect bRect = p->boundingRect(r, Qt::AlignBottom | Qt::AlignHCenter, type.toUpper()); bRect.adjust(1, 1, -1, -1); bRect.translate(0, 1); p->fillRect(bRect, Qt::SolidPattern); p->setPen(QPen(Qt::white)); p->setOpacity(1.0); p->drawText(bRect, Qt::AlignBottom | Qt::AlignHCenter, type.toUpper()); p->restore(); } } void ItemViewImportDelegate::drawImageSize(QPainter* p, const QRect& dimsRect, const QSize& dims) const { Q_D(const ItemViewImportDelegate); if (dims.isValid()) { p->setFont(d->fontXtra); QString mpixels, resolution; mpixels.setNum(dims.width()*dims.height()/1000000.0, 'f', 2); if (dims.isValid()) { resolution = i18nc("%1 width, %2 height, %3 mpixels", "%1x%2 (%3Mpx)", dims.width(), dims.height(), mpixels); } else { resolution = i18nc("unknown image resolution", "Unknown"); } p->drawText(dimsRect, Qt::AlignCenter, resolution); } } void ItemViewImportDelegate::drawFileSize(QPainter* p, const QRect& r, qlonglong bytes) const { Q_D(const ItemViewImportDelegate); p->setFont(d->fontXtra); p->drawText(r, Qt::AlignCenter, ItemPropertiesTab::humanReadableBytesCount(bytes)); } void ItemViewImportDelegate::drawTags(QPainter* p, const QRect& r, const QString& tagsString, bool isSelected) const { Q_D(const ItemViewImportDelegate); p->setFont(d->fontCom); p->setPen(isSelected ? qApp->palette().color(QPalette::HighlightedText) : qApp->palette().color(QPalette::Link)); p->drawText(r, Qt::AlignCenter, squeezedTextCached(p, r.width(), tagsString)); } void ItemViewImportDelegate::drawPickLabelIcon(QPainter* p, const QRect& r, int pickId) const { // Draw Pick Label icon if (pickId != NoPickLabel) { QIcon icon; if (pickId == RejectedLabel) { icon = QIcon::fromTheme(QLatin1String("flag-red")); } else if (pickId == PendingLabel) { icon = QIcon::fromTheme(QLatin1String("flag-yellow")); } else if (pickId == AcceptedLabel) { icon = QIcon::fromTheme(QLatin1String("flag-green")); } icon.paint(p, r); } } void ItemViewImportDelegate::drawColorLabelRect(QPainter* p, const QStyleOptionViewItem& option, bool isSelected, int colorId) const { Q_D(const ItemViewImportDelegate); Q_UNUSED(option); Q_UNUSED(isSelected); if (colorId > NoColorLabel) { // This draw a simple rectangle around item. p->setPen(QPen(ColorLabelWidget::labelColor((ColorLabel)colorId), 5, Qt::SolidLine)); p->drawRect(3, 3, d->rect.width()-7, d->rect.height()-7); } } void ItemViewImportDelegate::drawGeolocationIndicator(QPainter* p, const QRect& r) const { if (!r.isNull()) { QIcon icon(QIcon::fromTheme(QLatin1String("globe")).pixmap(r.size())); qreal op = p->opacity(); p->setOpacity(0.5); icon.paint(p, r); p->setOpacity(op); } } void ItemViewImportDelegate::drawDownloadIndicator(QPainter* p, const QRect& r, int itemType) const { QIcon icon; if (itemType == CamItemInfo::DownloadUnknown) { icon = QIcon::fromTheme(QLatin1String("dialog-information")); } if (itemType == CamItemInfo::DownloadedNo) // TODO: CamItemInfo::NewPicture { icon = QIcon::fromTheme(QLatin1String("folder-favorites")); } if (itemType == CamItemInfo::DownloadedYes) { icon = QIcon::fromTheme(QLatin1String("dialog-ok-apply")); } qreal op = p->opacity(); p->setOpacity(0.5); icon.paint(p, r); p->setOpacity(op); } void ItemViewImportDelegate::drawLockIndicator(QPainter* p, const QRect& r, int lockStatus) const { QIcon icon; if (lockStatus == 1) { return; // draw lock only when image is locked //icon = QIcon::fromTheme(QLatin1String("object-unlocked")); } if (lockStatus == 0) { icon = QIcon::fromTheme(QLatin1String("object-locked")); } qreal op = p->opacity(); p->setOpacity(0.5); icon.paint(p, r); p->setOpacity(op); } void ItemViewImportDelegate::drawFocusRect(QPainter* p, const QStyleOptionViewItem& option, bool isSelected) const { Q_D(const ItemViewImportDelegate); if (option.state & QStyle::State_HasFocus) //?? is current item { p->setPen(QPen(isSelected ? qApp->palette().color(QPalette::HighlightedText) : qApp->palette().color(QPalette::Text), 1, Qt::DotLine)); p->drawRect(1, 1, d->rect.width()-3, d->rect.height()-3); } } void ItemViewImportDelegate::drawGroupIndicator(QPainter* p, const QRect& r, int numberOfGroupedImages, bool open) const { if (numberOfGroupedImages) { QIcon icon; if (open) { icon = QIcon::fromTheme(QLatin1String("document-import")); } else { icon = QIcon::fromTheme(QLatin1String("document-multiple")); } qreal op = p->opacity(); p->setOpacity(0.5); icon.paint(p, r); p->setOpacity(op); QString text = QString::number(numberOfGroupedImages); p->drawText(r, Qt::AlignCenter, text); } } void ItemViewImportDelegate::drawMouseOverRect(QPainter* p, const QStyleOptionViewItem& option) const { Q_D(const ItemViewImportDelegate); if (option.state & QStyle::State_MouseOver) { p->setPen(QPen(option.palette.color(QPalette::Highlight), 3, Qt::SolidLine)); p->drawRect(1, 1, d->rect.width()-3, d->rect.height()-3); } } void ItemViewImportDelegate::prepareFonts() { Q_D(ItemViewImportDelegate); d->fontReg = d->font; d->fontCom = d->font; d->fontXtra = d->font; d->fontCom.setItalic(true); int fnSz = d->fontReg.pointSize(); if (fnSz > 0) { d->fontCom.setPointSize(fnSz-1); d->fontXtra.setPointSize(fnSz-2); } else { fnSz = d->fontReg.pixelSize(); d->fontCom.setPixelSize(fnSz-1); d->fontXtra.setPixelSize(fnSz-2); } } void ItemViewImportDelegate::prepareMetrics(int maxWidth) { Q_D(ItemViewImportDelegate); QFontMetrics fm(d->fontReg); d->oneRowRegRect = fm.boundingRect(0, 0, maxWidth, 0xFFFFFFFF, Qt::AlignTop | Qt::AlignHCenter, QLatin1String("XXXXXXXXX")); fm = QFontMetrics(d->fontCom); d->oneRowComRect = fm.boundingRect(0, 0, maxWidth, 0xFFFFFFFF, Qt::AlignTop | Qt::AlignHCenter, QLatin1String("XXXXXXXXX")); fm = QFontMetrics(d->fontXtra); d->oneRowXtraRect = fm.boundingRect(0, 0, maxWidth, 0xFFFFFFFF, Qt::AlignTop | Qt::AlignHCenter, QLatin1String("XXXXXXXXX")); } void ItemViewImportDelegate::prepareBackground() { Q_D(ItemViewImportDelegate); if (!d->rect.isValid()) { d->regPixmap = QPixmap(); d->selPixmap = QPixmap(); } else { d->regPixmap = QPixmap(d->rect.width(), d->rect.height()); d->regPixmap.fill(qApp->palette().color(QPalette::Base)); QPainter p1(&d->regPixmap); p1.setPen(qApp->palette().color(QPalette::Midlight)); p1.drawRect(0, 0, d->rect.width()-1, d->rect.height()-1); d->selPixmap = QPixmap(d->rect.width(), d->rect.height()); d->selPixmap.fill(qApp->palette().color(QPalette::Highlight)); QPainter p2(&d->selPixmap); p2.setPen(qApp->palette().color(QPalette::Midlight)); p2.drawRect(0, 0, d->rect.width()-1, d->rect.height()-1); } } void ItemViewImportDelegate::prepareRatingPixmaps(bool composeOverBackground) { /// Please call this method after prepareBackground() and when d->ratingPixmap is set Q_D(ItemViewImportDelegate); if (!d->ratingRect.isValid()) { return; } // We use antialiasing and want to pre-render the pixmaps. // So we need the background at the time of painting, // and the background may be a gradient, and will be different for selected items. // This makes 5*2 (small) pixmaps. for (int sel=0; sel<2; ++sel) { QPixmap basePix; if (composeOverBackground) { // do this once for regular, once for selected backgrounds if (sel) { basePix = d->selPixmap.copy(d->ratingRect); } else { basePix = d->regPixmap.copy(d->ratingRect); } } else { basePix = QPixmap(d->ratingRect.size()); basePix.fill(Qt::transparent); } for (int rating=1; rating<=5; ++rating) { // we store first the 5 regular, then the 5 selected pixmaps, for simplicity int index = (sel * 5 + rating) - 1; // copy background d->ratingPixmaps[index] = basePix; // open a painter QPainter painter(&d->ratingPixmaps[index]); // use antialiasing painter.setRenderHint(QPainter::Antialiasing, true); painter.setBrush(qApp->palette().color(QPalette::Link)); QPen pen(qApp->palette().color(QPalette::Text)); // set a pen which joins the lines at a filled angle pen.setJoinStyle(Qt::MiterJoin); painter.setPen(pen); // move painter while drawing polygons painter.translate( lround((d->ratingRect.width() - d->margin - rating*(d->starPolygonSize.width()+1))/2.0) + 2, 1 ); for (int s=0; sstarPolygon, Qt::WindingFill); painter.translate(d->starPolygonSize.width() + 1, 0); } } } } QPixmap ItemViewImportDelegate::ratingPixmap(int rating, bool selected) const { Q_D(const ItemViewImportDelegate); if (rating < 1 || rating > 5) { /* QPixmap pix; if (selected) pix = d->selPixmap.copy(d->ratingRect); else pix = d->regPixmap.copy(d->ratingRect); return pix; */ return QPixmap(); } --rating; if (selected) { return d->ratingPixmaps.at(5 + rating); } else { return d->ratingPixmaps.at(rating); } } } // namespace Digikam diff --git a/core/utilities/import/models/camitemsortsettings.cpp b/core/utilities/import/models/camitemsortsettings.cpp index 965ccadd42..26b6e9292e 100644 --- a/core/utilities/import/models/camitemsortsettings.cpp +++ b/core/utilities/import/models/camitemsortsettings.cpp @@ -1,299 +1,299 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2012-23-06 * Description : Sort settings for camera item infos * * Copyright (C) 2012 by Islam Wazery * * 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 "camitemsortsettings.h" // Qt includes #include namespace Digikam { CamItemSortSettings::CamItemSortSettings() { categorizationMode = NoCategories; categorizationSortOrder = DefaultOrder; categorizationCaseSensitivity = Qt::CaseSensitive; sortRole = SortByFileName; sortOrder = DefaultOrder; strTypeNatural = true; sortCaseSensitivity = Qt::CaseSensitive; currentCategorizationSortOrder = Qt::AscendingOrder; currentSortOrder = Qt::AscendingOrder; } CamItemSortSettings::~CamItemSortSettings() { } bool CamItemSortSettings::operator ==(const CamItemSortSettings& other) const { return (categorizationMode == other.categorizationMode && categorizationSortOrder == other.categorizationSortOrder && categorizationCaseSensitivity == other.categorizationCaseSensitivity && sortRole == other.sortRole && sortOrder == other.sortOrder && sortCaseSensitivity == other.sortCaseSensitivity); } void CamItemSortSettings::setCategorizationMode(CategorizationMode mode) { categorizationMode = mode; if (categorizationSortOrder == DefaultOrder) { currentCategorizationSortOrder = defaultSortOrderForCategorizationMode(categorizationMode); } } void CamItemSortSettings::setCategorizationSortOrder(SortOrder order) { categorizationSortOrder = order; if (categorizationSortOrder == DefaultOrder) { currentCategorizationSortOrder = defaultSortOrderForCategorizationMode(categorizationMode); } else { currentCategorizationSortOrder = (Qt::SortOrder)categorizationSortOrder; } } void CamItemSortSettings::setSortRole(SortRole role) { sortRole = role; if (sortOrder == DefaultOrder) { currentSortOrder = defaultSortOrderForSortRole(sortRole); } } void CamItemSortSettings::setSortOrder(SortOrder order) { sortOrder = order; if (sortOrder == DefaultOrder) { currentSortOrder = defaultSortOrderForSortRole(sortRole); } else { currentSortOrder = (Qt::SortOrder)order; } } void CamItemSortSettings::setStringTypeNatural(bool natural) { strTypeNatural = natural; } Qt::SortOrder CamItemSortSettings::defaultSortOrderForCategorizationMode(CategorizationMode mode) { switch (mode) { case NoCategories: case CategoryByFolder: case CategoryByFormat: case CategoryByDate: default: return Qt::AscendingOrder; } } Qt::SortOrder CamItemSortSettings::defaultSortOrderForSortRole(SortRole role) { switch (role) { case SortByFileName: case SortByFilePath: return Qt::AscendingOrder; case SortByFileSize: return Qt::DescendingOrder; case SortByCreationDate: return Qt::AscendingOrder; case SortByDownloadState: return Qt::AscendingOrder; case SortByRating: return Qt::DescendingOrder; default: return Qt::AscendingOrder; } } int CamItemSortSettings::compareCategories(const CamItemInfo& left, const CamItemInfo& right) const { switch (categorizationMode) { case NoCategories: case CategoryByFolder: return naturalCompare(left.folder, right.folder, currentCategorizationSortOrder, categorizationCaseSensitivity, strTypeNatural); case CategoryByFormat: return naturalCompare(left.mime, right.mime, currentCategorizationSortOrder, categorizationCaseSensitivity, strTypeNatural); case CategoryByDate: return compareByOrder(left.ctime.date(), right.ctime.date(), currentCategorizationSortOrder); default: return 0; } } bool CamItemSortSettings::lessThan(const CamItemInfo& left, const CamItemInfo& right) const { int result = compare(left, right, sortRole); if (result != 0) { return result < 0; } if (left == right) { return false; } if ((result = compare(left, right, SortByFileName)) != 0) { return result < 0; } if ((result = compare(left, right, SortByCreationDate)) != 0) { return result < 0; } - if ( (result = compare(left, right, SortByFilePath)) != 0) + if ((result = compare(left, right, SortByFilePath)) != 0) { return result < 0; } - if ( (result = compare(left, right, SortByFileSize)) != 0) + if ((result = compare(left, right, SortByFileSize)) != 0) { return result < 0; } - if ( (result = compare(left, right, SortByRating)) != 0) + if ((result = compare(left, right, SortByRating)) != 0) { return result < 0; } - if ( (result = compare(left, right, SortByDownloadState)) != 0) + if ((result = compare(left, right, SortByDownloadState)) != 0) { return result < 0; } return false; } int CamItemSortSettings::compare(const CamItemInfo& left, const CamItemInfo& right) const { return compare(left, right, sortRole); } int CamItemSortSettings::compare(const CamItemInfo& left, const CamItemInfo& right, SortRole role) const { switch (role) { case SortByFileName: { return naturalCompare(left.name, right.name, currentSortOrder, sortCaseSensitivity, strTypeNatural); } case SortByFilePath: return naturalCompare(left.url().toLocalFile(), right.url().toLocalFile(), currentSortOrder, sortCaseSensitivity, strTypeNatural); case SortByFileSize: return compareByOrder(left.size, right.size, currentSortOrder); //FIXME: Change it to creation date instead of modification date. case SortByCreationDate: return compareByOrder(left.ctime, right.ctime, currentSortOrder); case SortByRating: return compareByOrder(left.rating, right.rating, currentSortOrder); case SortByDownloadState: return compareByOrder(left.downloaded, right.downloaded, currentSortOrder); default: return 1; } } bool CamItemSortSettings::lessThan(const QVariant& left, const QVariant& right) const { if (left.type() != right.type()) { return false; } switch (left.type()) { case QVariant::Int: return compareByOrder(left.toInt(), right.toInt(), currentSortOrder); case QVariant::UInt: return compareByOrder(left.toUInt(), right.toUInt(), currentSortOrder); case QVariant::LongLong: return compareByOrder(left.toLongLong(), right.toLongLong(), currentSortOrder); case QVariant::ULongLong: return compareByOrder(left.toULongLong(), right.toULongLong(), currentSortOrder); case QVariant::Double: return compareByOrder(left.toDouble(), right.toDouble(), currentSortOrder); case QVariant::Date: return compareByOrder(left.toDate(), right.toDate(), currentSortOrder); case QVariant::DateTime: return compareByOrder(left.toDateTime(), right.toDateTime(), currentSortOrder); case QVariant::Time: return compareByOrder(left.toTime(), right.toTime(), currentSortOrder); case QVariant::Rect: case QVariant::RectF: { QRectF rectLeft = left.toRectF(); QRectF rectRight = right.toRectF(); int result; if ((result = compareByOrder(rectLeft.top(), rectRight.top(), currentSortOrder)) != 0) { return result < 0; } if ((result = compareByOrder(rectLeft.left(), rectRight.left(), currentSortOrder)) != 0) { return result < 0; } QSizeF sizeLeft = rectLeft.size(); QSizeF sizeRight = rectRight.size(); if ((result = compareByOrder(sizeLeft.width()*sizeLeft.height(), sizeRight.width()*sizeRight.height(), currentSortOrder)) != 0) { return result < 0; } #if __GNUC__ >= 7 // krazy:exclude=cpp [[fallthrough]]; #endif } default: return naturalCompare(left.toString(), right.toString(), currentSortOrder, sortCaseSensitivity, strTypeNatural); } } } // namespace Digikam diff --git a/core/utilities/import/models/importfiltermodel.cpp b/core/utilities/import/models/importfiltermodel.cpp index badd9119be..02f1a882cb 100644 --- a/core/utilities/import/models/importfiltermodel.cpp +++ b/core/utilities/import/models/importfiltermodel.cpp @@ -1,561 +1,563 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2012-21-06 * Description : Qt filter model for import items * * Copyright (C) 2012 by Islam Wazery * * 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 "importfiltermodel.h" #include "camiteminfo.h" #include "filtercombo.h" #include "filter.h" #include "importimagemodel.h" namespace Digikam { ImportSortFilterModel::ImportSortFilterModel(QObject* const parent) : DCategorizedSortFilterProxyModel(parent), m_chainedModel(0) { } ImportSortFilterModel::~ImportSortFilterModel() { } void ImportSortFilterModel::setSourceImportModel(ImportItemModel* const sourceModel) { if (m_chainedModel) { m_chainedModel->setSourceImportModel(sourceModel); } else { setDirectSourceImportModel(sourceModel); } } ImportItemModel* ImportSortFilterModel::sourceImportModel() const { if (m_chainedModel) { return m_chainedModel->sourceImportModel(); } return static_cast(sourceModel()); } void ImportSortFilterModel::setSourceFilterModel(ImportSortFilterModel* const sourceModel) { if (sourceModel) { ImportItemModel* const model = sourceImportModel(); if (model) { sourceModel->setSourceImportModel(model); } } m_chainedModel = sourceModel; setSourceModel(sourceModel); } ImportSortFilterModel* ImportSortFilterModel::sourceFilterModel() const { return m_chainedModel; } QModelIndex ImportSortFilterModel::mapToSourceImportModel(const QModelIndex& proxyIndex) const { if (m_chainedModel) { return m_chainedModel->mapToSourceImportModel(mapToSource(proxyIndex)); } return mapToSource(proxyIndex); } QModelIndex ImportSortFilterModel::mapFromSourceImportModel(const QModelIndex& importModelIndex) const { if (m_chainedModel) { return mapFromSource(m_chainedModel->mapFromSourceImportModel(importModelIndex)); } return mapFromSource(importModelIndex); } QModelIndex ImportSortFilterModel::mapFromDirectSourceToSourceImportModel(const QModelIndex& sourceModelIndex) const { if (m_chainedModel) { return m_chainedModel->mapToSourceImportModel(sourceModelIndex); } return sourceModelIndex; } QList ImportSortFilterModel::mapListToSource(const QList& indexes) const { QList sourceIndexes; foreach (const QModelIndex& index, indexes) { sourceIndexes << mapToSourceImportModel(index); } return sourceIndexes; } QList ImportSortFilterModel::mapListFromSource(const QList& sourceIndexes) const { QList indexes; foreach (const QModelIndex& index, sourceIndexes) { indexes << mapFromSourceImportModel(index); } return indexes; } CamItemInfo ImportSortFilterModel::camItemInfo(const QModelIndex& index) const { return sourceImportModel()->camItemInfo(mapToSourceImportModel(index)); } qlonglong ImportSortFilterModel::camItemId(const QModelIndex& index) const { return sourceImportModel()->camItemId(mapToSourceImportModel(index)); } QList ImportSortFilterModel::camItemInfos(const QList& indexes) const { QList infos; foreach (const QModelIndex& index, indexes) { infos << camItemInfo(index); } return infos; } QList ImportSortFilterModel::camItemIds(const QList& indexes) const { QList ids; foreach (const QModelIndex& index, indexes) { ids << camItemId(index); } return ids; } QModelIndex ImportSortFilterModel::indexForPath(const QString& filePath) const { QUrl fileUrl = QUrl::fromLocalFile(filePath); return mapFromSourceImportModel(sourceImportModel()->indexForUrl(fileUrl)); } QModelIndex ImportSortFilterModel::indexForCamItemInfo(const CamItemInfo& info) const { return mapFromSourceImportModel(sourceImportModel()->indexForCamItemInfo(info)); } QModelIndex ImportSortFilterModel::indexForCamItemId(qlonglong id) const { return mapFromSourceImportModel(sourceImportModel()->indexForCamItemId(id)); } QList ImportSortFilterModel::camItemInfosSorted() const { QList infos; const int size = rowCount(); for (int i = 0 ; i < size ; ++i) { infos << camItemInfo(index(i, 0)); } return infos; } ImportFilterModel* ImportSortFilterModel::importFilterModel() const { if (m_chainedModel) { return m_chainedModel->importFilterModel(); } return 0; } void ImportSortFilterModel::setSourceModel(QAbstractItemModel* sourceModel) { DCategorizedSortFilterProxyModel::setSourceModel(sourceModel); } void ImportSortFilterModel::setDirectSourceImportModel(ImportItemModel* const sourceModel) { setSourceModel(sourceModel); } //--- ImportFilterModel methods --------------------------------- class Q_DECL_HIDDEN ImportFilterModel::ImportFilterModelPrivate : public QObject { public: ImportFilterModelPrivate() { - q = 0; + q = 0; importItemModel = 0; - filter = 0; + filter = 0; } void init(ImportFilterModel* const _q); Q_SIGNALS: void reAddCamItemInfos(const QList&); void reAddingFinished(); public: ImportFilterModel* q; - ImportItemModel* importItemModel; + ImportItemModel* importItemModel; CamItemSortSettings sorter; Filter* filter; }; void ImportFilterModel::ImportFilterModelPrivate::init(ImportFilterModel* const _q) { q = _q; } ImportFilterModel::ImportFilterModel(QObject* const parent) : ImportSortFilterModel(parent), d_ptr(new ImportFilterModelPrivate) { d_ptr->init(this); } ImportFilterModel::~ImportFilterModel() { Q_D(ImportFilterModel); delete d; } QVariant ImportFilterModel::data(const QModelIndex& index, int role) const { Q_D(const ImportFilterModel); if (!index.isValid()) { return QVariant(); } switch (role) { case DCategorizedSortFilterProxyModel::CategoryDisplayRole: return categoryIdentifier(d->importItemModel->camItemInfoRef(mapToSource(index))); case CategorizationModeRole: return d->sorter.categorizationMode; case SortOrderRole: return d->sorter.sortRole; case CategoryFormatRole: return d->importItemModel->camItemInfoRef(mapToSource(index)).mime; case CategoryDateRole: return d->importItemModel->camItemInfoRef(mapToSource(index)).ctime; case ImportFilterModelPointerRole: return QVariant::fromValue(const_cast(this)); } return DCategorizedSortFilterProxyModel::data(index, role); } ImportFilterModel* ImportFilterModel::importFilterModel() const { return const_cast(this); } // --- Sorting and Categorization ---------------------------------------------- void ImportFilterModel::setCamItemSortSettings(const CamItemSortSettings& sorter) { Q_D(ImportFilterModel); d->sorter = sorter; setCategorizedModel(d->sorter.categorizationMode != CamItemSortSettings::NoCategories); invalidate(); } void ImportFilterModel::setCategorizationMode(CamItemSortSettings::CategorizationMode mode) { Q_D(ImportFilterModel); d->sorter.setCategorizationMode(mode); setCamItemSortSettings(d->sorter); } void ImportFilterModel::setSortRole(CamItemSortSettings::SortRole role) { Q_D(ImportFilterModel); d->sorter.setSortRole(role); setCamItemSortSettings(d->sorter); } void ImportFilterModel::setSortOrder(CamItemSortSettings::SortOrder order) { Q_D(ImportFilterModel); d->sorter.setSortOrder(order); setCamItemSortSettings(d->sorter); } void ImportFilterModel::setStringTypeNatural(bool natural) { Q_D(ImportFilterModel); d->sorter.setStringTypeNatural(natural); setCamItemSortSettings(d->sorter); } void ImportFilterModel::setFilter(Digikam::Filter* filter) { Q_D(ImportFilterModel); d->filter = filter; invalidateFilter(); } void ImportFilterModel::setCameraThumbsController(CameraThumbsCtrl* const thumbsCtrl) { Q_D(ImportFilterModel); d->importItemModel->setCameraThumbsController(thumbsCtrl); } void ImportFilterModel::setSendCamItemInfoSignals(bool sendSignals) { if (sendSignals) { connect(this, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(slotRowsInserted(QModelIndex,int,int))); connect(this, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), this, SLOT(slotRowsAboutToBeRemoved(QModelIndex,int,int))); } else { disconnect(this, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(slotRowsInserted(QModelIndex,int,int))); disconnect(this, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), this, SLOT(slotRowsAboutToBeRemoved(QModelIndex,int,int))); } } void ImportFilterModel::slotRowsInserted(const QModelIndex& /*parent*/, int start, int end) { QList infos; for (int i = start ; i < end ; ++i) { infos << camItemInfo(index(i, 0)); } emit camItemInfosAdded(infos); } void ImportFilterModel::slotRowsAboutToBeRemoved(const QModelIndex& /*parent*/, int start, int end) { QList infos; for (int i = start ; i < end ; ++i) { infos << camItemInfo(index(i, 0)); } emit camItemInfosAboutToBeRemoved(infos); } void ImportFilterModel::setDirectSourceImportModel(ImportItemModel* const sourceModel) { Q_D(ImportFilterModel); if (d->importItemModel) { //disconnect(d->importItemModel, SIGNAL(modelReset()), //this, SLOT(slotModelReset())); //TODO: slotModelReset(); will be added when implementing filtering options disconnect(d->importItemModel, SIGNAL(processAdded(QList)), this, SLOT(slotProcessAdded(QList))); } // TODO do we need to delete the old one? d->importItemModel = sourceModel; if (d->importItemModel) { //connect(d, SIGNAL(reAddCamItemInfos(QList)), //d->importItemModel, SLOT(reAddCamItemInfos(QList))); //connect(d, SIGNAL(reAddingFinished()), //d->importItemModel, SLOT(reAddingFinished())); //TODO: connect(d->importItemModel, SIGNAL(modelReset()), this, SLOT(slotModelReset())); connect(d->importItemModel, SIGNAL(processAdded(QList)), this, SLOT(slotProcessAdded(QList))); } setSourceModel(d->importItemModel); } void ImportFilterModel::slotProcessAdded(const QList&) { invalidate(); } int ImportFilterModel::compareCategories(const QModelIndex& left, const QModelIndex& right) const { Q_D(const ImportFilterModel); if (!d->sorter.isCategorized()) { return 0; } if (!left.isValid() || !right.isValid()) { return -1; } return compareInfosCategories(d->importItemModel->camItemInfoRef(left), d->importItemModel->camItemInfoRef(right)); } bool ImportFilterModel::subSortLessThan(const QModelIndex& left, const QModelIndex& right) const { Q_D(const ImportFilterModel); if (!left.isValid() || !right.isValid()) { return true; } if (left == right) { return false; } const CamItemInfo& leftInfo = d->importItemModel->camItemInfoRef(left); const CamItemInfo& rightInfo = d->importItemModel->camItemInfoRef(right); if (leftInfo == rightInfo) { return d->sorter.lessThan(left.data(ImportItemModel::ExtraDataRole), right.data(ImportItemModel::ExtraDataRole)); } return infosLessThan(leftInfo, rightInfo); } int ImportFilterModel::compareInfosCategories(const CamItemInfo& left, const CamItemInfo& right) const { Q_D(const ImportFilterModel); return d->sorter.compareCategories(left, right); } bool ImportFilterModel::infosLessThan(const CamItemInfo& left, const CamItemInfo& right) const { Q_D(const ImportFilterModel); return d->sorter.lessThan(left, right); } QString ImportFilterModel::categoryIdentifier(const CamItemInfo& info) const { Q_D(const ImportFilterModel); switch (d->sorter.categorizationMode) { case CamItemSortSettings::NoCategories: return QString(); case CamItemSortSettings::CategoryByFolder: return info.folder; case CamItemSortSettings::CategoryByFormat: return info.mime; case CamItemSortSettings::CategoryByDate: return info.ctime.date().toString(Qt::ISODate); default: return QString(); } } bool ImportFilterModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const { Q_D(const ImportFilterModel); - if(!d->filter) { + if (!d->filter) + { return true; } QModelIndex idx = sourceModel()->index(source_row, 0, source_parent); const CamItemInfo &info = d->importItemModel->camItemInfo(idx); - if(d->filter->matchesCurrentFilter(info)) { + if (d->filter->matchesCurrentFilter(info)) + { return true; } return false; } // ------------------------------------------------------------------------------------------------------- NoDuplicatesImportFilterModel::NoDuplicatesImportFilterModel(QObject* const parent) : ImportSortFilterModel(parent) { } bool NoDuplicatesImportFilterModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const { QModelIndex index = sourceModel()->index(source_row, 0, source_parent); if (index.data(ImportItemModel::ExtraDataDuplicateCount).toInt() <= 1) { return true; } QModelIndex previousIndex = sourceModel()->index(source_row - 1, 0, source_parent); if (!previousIndex.isValid()) { return true; } if (sourceImportModel()->camItemId(mapFromDirectSourceToSourceImportModel(index)) == sourceImportModel()->camItemId(mapFromDirectSourceToSourceImportModel(previousIndex))) { return false; } return true; } } // namespace Digikam diff --git a/core/utilities/import/views/importpreviewview.cpp b/core/utilities/import/views/importpreviewview.cpp index ff60fd99cf..c4be7adffb 100644 --- a/core/utilities/import/views/importpreviewview.cpp +++ b/core/utilities/import/views/importpreviewview.cpp @@ -1,502 +1,502 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2012-14-07 * Description : An embedded view to show the cam item preview widget. * * Copyright (C) 2012 by Islam Wazery * * 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 "importpreviewview.h" // Qt includes #include #include #include #include #include #include // KDE includes #include // Local includes #include "dimgpreviewitem.h" #include "fileactionmngr.h" #include "importcontextmenu.h" #include "previewlayout.h" #include "thememanager.h" #include "importsettings.h" #include "previewsettings.h" namespace Digikam { class Q_DECL_HIDDEN ImportPreviewViewItem : public DImgPreviewItem { public: explicit ImportPreviewViewItem(ImportPreviewView* const view) : m_view(view)/*, m_group(0)*/ { setAcceptHoverEvents(true); } /* void setFaceGroup(FaceGroup* group) { m_group = group; } */ void contextMenuEvent(QGraphicsSceneContextMenuEvent* event) { m_view->showContextMenu(m_info, event); } void setCamItemInfo(const CamItemInfo& info) { m_info = info; - if(!info.isNull()) + if (!info.isNull()) { setPath(info.url().toLocalFile(), true); } } void hoverEnterEvent(QGraphicsSceneHoverEvent* e) { Q_UNUSED(e) //FIXME //m_group->itemHoverEnterEvent(e); } void hoverLeaveEvent(QGraphicsSceneHoverEvent* e) { Q_UNUSED(e) //FIXME: //m_group->itemHoverLeaveEvent(e); } void hoverMoveEvent(QGraphicsSceneHoverEvent* e) { Q_UNUSED(e) //FIXME: //m_group->itemHoverMoveEvent(e); } CamItemInfo camItemInfo() const { return m_info; } protected: ImportPreviewView* m_view; //FaceGroup* m_group; CamItemInfo m_info; }; // --------------------------------------------------------------------- class Q_DECL_HIDDEN ImportPreviewView::Private { public: explicit Private() { //peopleTagsShown = false; fullSize = 0; scale = 1.0; item = 0; isValid = false; toolBar = 0; escapePreviewAction = 0; prevAction = 0; nextAction = 0; rotLeftAction = 0; rotRightAction = 0; //peopleToggleAction = 0; //addPersonAction = 0; //faceGroup = 0; mode = ImportPreviewView::IconViewPreview; } //bool peopleTagsShown; bool fullSize; double scale; bool isValid; ImportPreviewView::Mode mode; ImportPreviewViewItem* item; QAction* escapePreviewAction; QAction* prevAction; QAction* nextAction; QAction* rotLeftAction; QAction* rotRightAction; //QAction* peopleToggleAction; //QAction* addPersonAction; //QAction* forgetFacesAction; QToolBar* toolBar; //FaceGroup* faceGroup; }; ImportPreviewView::ImportPreviewView(QWidget* const parent, Mode mode) : GraphicsDImgView(parent), d(new Private) { d->mode = mode; d->item = new ImportPreviewViewItem(this); setItem(d->item); //d->faceGroup = new FaceGroup(this); //d->faceGroup->setShowOnHover(true); //d->item->setFaceGroup(d->faceGroup); connect(d->item, SIGNAL(loaded()), this, SLOT(camItemLoaded())); connect(d->item, SIGNAL(loadingFailed()), this, SLOT(camItemLoadingFailed())); // set default zoom layout()->fitToWindow(); // ------------------------------------------------------------ installPanIcon(); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); // ------------------------------------------------------------ d->escapePreviewAction = new QAction(QIcon::fromTheme(QLatin1String("folder-pictures")), i18n("Escape preview"), this); d->prevAction = new QAction(QIcon::fromTheme(QLatin1String("go-previous")), i18nc("go to previous image", "Back"), this); d->nextAction = new QAction(QIcon::fromTheme(QLatin1String("go-next")), i18nc("go to next image", "Forward"), this); d->rotLeftAction = new QAction(QIcon::fromTheme(QLatin1String("object-rotate-left")), i18nc("@info:tooltip", "Rotate Left"), this); d->rotRightAction = new QAction(QIcon::fromTheme(QLatin1String("object-rotate-right")), i18nc("@info:tooltip", "Rotate Right"), this); //FIXME: d->addPersonAction = new QAction(QIcon::fromTheme(QLatin1String("list-add-user")), i18n("Add a Face Tag"), this); //FIXME: d->forgetFacesAction = new QAction(QIcon::fromTheme(QLatin1String("list-remove-user")), i18n("Clear all faces on this image"), this); //FIXME: d->peopleToggleAction = new Qaction(QIcon::fromTheme(QLatin1String("im-user")), i18n("Show Face Tags"), this); //FIXME: d->peopleToggleAction->setCheckable(true); d->toolBar = new QToolBar(this); if (mode == IconViewPreview) { d->toolBar->addAction(d->prevAction); d->toolBar->addAction(d->nextAction); d->toolBar->addAction(d->escapePreviewAction); } d->toolBar->addAction(d->rotLeftAction); d->toolBar->addAction(d->rotRightAction); //FIXME: d->toolBar->addAction(d->peopleToggleAction); //FIXME: d->toolBar->addAction(d->addPersonAction); connect(d->prevAction, SIGNAL(triggered()), this, SIGNAL(toPreviousImage())); connect(d->nextAction, SIGNAL(triggered()), this, SIGNAL(toNextImage())); connect(d->escapePreviewAction, SIGNAL(triggered()), this, SIGNAL(signalEscapePreview())); connect(d->rotLeftAction, SIGNAL(triggered()), this, SLOT(slotRotateLeft())); connect(d->rotRightAction, SIGNAL(triggered()), this, SLOT(slotRotateRight())); //FIXME: connect(d->peopleToggleAction, SIGNAL(toggled(bool)), //d->faceGroup, SLOT(setVisible(bool))); //FIXME: connect(d->addPersonAction, SIGNAL(triggered()), //d->faceGroup, SLOT(addFace())); //FIXME: connect(d->forgetFacesAction, SIGNAL(triggered()), //d->faceGroup, SLOT(rejectAll())); // ------------------------------------------------------------ connect(this, SIGNAL(toNextImage()), this, SIGNAL(signalNextItem())); connect(this, SIGNAL(toPreviousImage()), this, SIGNAL(signalPrevItem())); connect(this, SIGNAL(activated()), this, SIGNAL(signalEscapePreview())); connect(ThemeManager::instance(), SIGNAL(signalThemeChanged()), this, SLOT(slotThemeChanged())); connect(ImportSettings::instance(), SIGNAL(setupChanged()), this, SLOT(slotSetupChanged())); slotSetupChanged(); } ImportPreviewView::~ImportPreviewView() { delete d->item; delete d; } void ImportPreviewView::reload() { previewItem()->reload(); } void ImportPreviewView::camItemLoaded() { emit signalPreviewLoaded(true); d->rotLeftAction->setEnabled(true); d->rotRightAction->setEnabled(true); //FIXME: d->faceGroup->setInfo(d->item->camItemInfo()); } void ImportPreviewView::camItemLoadingFailed() { emit signalPreviewLoaded(false); d->rotLeftAction->setEnabled(false); d->rotRightAction->setEnabled(false); //FIXME: d->faceGroup->setInfo(CamItemInfo()); } void ImportPreviewView::setCamItemInfo(const CamItemInfo& info, const CamItemInfo& previous, const CamItemInfo& next) { //FIXME: d->faceGroup->aboutToSetInfo(info); d->item->setCamItemInfo(info); d->prevAction->setEnabled(!previous.isNull()); d->nextAction->setEnabled(!next.isNull()); QStringList previewPaths; if (identifyCategoryforMime(next.mime) == QLatin1String("image")) { previewPaths << next.url().toLocalFile(); } if (identifyCategoryforMime(previous.mime) == QLatin1String("image")) { previewPaths << previous.url().toLocalFile(); } d->item->setPreloadPaths(previewPaths); } QString ImportPreviewView::identifyCategoryforMime(QString mime) { return mime.split(QLatin1Char('/')).at(0); } CamItemInfo ImportPreviewView::getCamItemInfo() const { return d->item->camItemInfo(); } bool ImportPreviewView::acceptsMouseClick(QMouseEvent* e) { if (!GraphicsDImgView::acceptsMouseClick(e)) { return false; } return true; //FIXME: return d->faceGroup->acceptsMouseClick(mapToScene(e->pos())); } void ImportPreviewView::enterEvent(QEvent* e) { Q_UNUSED(e) //FIXME //FIXME: d->faceGroup->enterEvent(e); } void ImportPreviewView::leaveEvent(QEvent* e) { Q_UNUSED(e) //FIXME //FIXME: d->faceGroup->leaveEvent(e); } void ImportPreviewView::showEvent(QShowEvent* e) { GraphicsDImgView::showEvent(e); //FIXME: d->faceGroup->setVisible(d->peopleToggleAction->isChecked()); } void ImportPreviewView::showContextMenu(const CamItemInfo& info, QGraphicsSceneContextMenuEvent* event) { if (info.isNull()) { return; } event->accept(); QList idList; idList << info.id; QList selectedItems; selectedItems << info.url(); // -------------------------------------------------------- QMenu popmenu(this); ImportContextMenuHelper cmhelper(&popmenu); cmhelper.addAction(QLatin1String("importui_fullscreen")); cmhelper.addAction(QLatin1String("options_show_menubar")); cmhelper.addSeparator(); // -------------------------------------------------------- if (d->mode == IconViewPreview) { cmhelper.addAction(d->prevAction, true); cmhelper.addAction(d->nextAction, true); cmhelper.addAction(QLatin1String("importui_icon_view")); //cmhelper.addGotoMenu(idList); cmhelper.addSeparator(); } // -------------------------------------------------------- //FIXME: cmhelper.addAction(d->peopleToggleAction, true); //FIXME: cmhelper.addAction(d->addPersonAction, true); //FIXME: cmhelper.addAction(d->forgetFacesAction, true); //FIXME: cmhelper.addSeparator(); // -------------------------------------------------------- cmhelper.addServicesMenu(selectedItems); cmhelper.addRotateMenu(idList); cmhelper.addSeparator(); // -------------------------------------------------------- cmhelper.addAction(QLatin1String("importui_delete")); cmhelper.addSeparator(); // -------------------------------------------------------- //FIXME: cmhelper.addAssignTagsMenu(idList); //FIXME: cmhelper.addRemoveTagsMenu(idList); //FIXME: cmhelper.addSeparator(); // -------------------------------------------------------- cmhelper.addLabelsAction(); // special action handling -------------------------------- //FIXME: connect(&cmhelper, SIGNAL(signalAssignTag(int)), //this, SLOT(slotAssignTag(int))); //FIXME: connect(&cmhelper, SIGNAL(signalPopupTagsView()), //this, SIGNAL(signalPopupTagsView())); //FIXME: connect(&cmhelper, SIGNAL(signalRemoveTag(int)), //this, SLOT(slotRemoveTag(int))); //FIXME: connect(&cmhelper, SIGNAL(signalAssignPickLabel(int)), //this, SLOT(slotAssignPickLabel(int))); //FIXME: connect(&cmhelper, SIGNAL(signalAssignColorLabel(int)), //this, SLOT(slotAssignColorLabel(int))); connect(&cmhelper, SIGNAL(signalAssignPickLabel(int)), this, SIGNAL(signalAssignPickLabel(int))); connect(&cmhelper, SIGNAL(signalAssignColorLabel(int)), this, SIGNAL(signalAssignColorLabel(int))); connect(&cmhelper, SIGNAL(signalAssignRating(int)), this, SIGNAL(signalAssignRating(int))); //FIXME: connect(&cmhelper, SIGNAL(signalAddToExistingQueue(int)), //this, SIGNAL(signalAddToExistingQueue(int))); //FIXME: connect(&cmhelper, SIGNAL(signalGotoTag(int)), //this, SIGNAL(signalGotoTagAndItem(int))); cmhelper.exec(event->screenPos()); } /* void ImportPreviewView::slotAssignTag(int tagID) { FileActionMngr::instance()->assignTag(d->item->camItemInfo(), tagID); } void ImportPreviewView::slotRemoveTag(int tagID) { FileActionMngr::instance()->removeTag(d->item->camItemInfo(), tagID); } */ void ImportPreviewView::slotThemeChanged() { QPalette plt(palette()); plt.setColor(backgroundRole(), qApp->palette().color(QPalette::Base)); setPalette(plt); } void ImportPreviewView::slotSetupChanged() { PreviewSettings settings; settings.quality = ImportSettings::instance()->getPreviewLoadFullImageSize() ? PreviewSettings::HighQualityPreview : PreviewSettings::FastPreview; previewItem()->setPreviewSettings(settings); d->toolBar->setVisible(ImportSettings::instance()->getPreviewShowIcons()); setShowText(ImportSettings::instance()->getPreviewShowIcons()); // pass auto-suggest? } void ImportPreviewView::slotRotateLeft() { /* ItemInfo info(d->item->camItemInfo().url().toLocalFile()); FileActionMngr::instance()->transform(QList() << info, MetaEngineRotation::Rotate270); */ } void ImportPreviewView::slotRotateRight() { /* ItemInfo info(d->item->camItemInfo().url().toLocalFile()); FileActionMngr::instance()->transform(QList() << info, MetaEngineRotation::Rotate90); */ } void ImportPreviewView::slotDeleteItem() { emit signalDeleteItem(); } } // namespace Digikam diff --git a/core/utilities/import/views/importview.cpp b/core/utilities/import/views/importview.cpp index e3d8a15bfc..51bef52218 100644 --- a/core/utilities/import/views/importview.cpp +++ b/core/utilities/import/views/importview.cpp @@ -1,861 +1,861 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2012-26-07 * Description : Main view for import tool * * Copyright (C) 2012 by Islam Wazery * Copyright (C) 2012-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 "importview.h" // Qt includes #include #include #include // Local includes #include "digikam_debug.h" #include "digikam_config.h" #include "digikam_globals.h" #include "dmessagebox.h" #include "importui.h" #include "importiconview.h" #include "thumbnailsize.h" #include "fileactionmngr.h" #include "importsettings.h" #include "sidebar.h" #include "dzoombar.h" #include "camitemsortsettings.h" #ifdef HAVE_MARBLE # include "mapwidgetview.h" #endif // HAVE_MARBLE namespace Digikam { class Q_DECL_HIDDEN ImportView::Private { public: explicit Private() : needDispatchSelection(false), thumbSize(ThumbnailSize::Medium), dockArea(0), splitter(0), selectionTimer(0), thumbSizeTimer(0), parent(0), iconView(0), #ifdef HAVE_MARBLE mapView(0), #endif // HAVE_MARBLE stackedView(0), lastViewMode(ImportStackedView::PreviewCameraMode) //FIXME: filterWidget(0) { } void addPageUpDownActions(ImportView* const q, QWidget* const w); public: bool needDispatchSelection; int thumbSize; QMainWindow* dockArea; SidebarSplitter* splitter; QTimer* selectionTimer; QTimer* thumbSizeTimer; ImportUI* parent; ImportIconView* iconView; #ifdef HAVE_MARBLE MapWidgetView* mapView; #endif // HAVE_MARBLE ImportStackedView* stackedView; ImportStackedView::StackedViewMode lastViewMode; //FIXME: FilterSideBarWidget* filterWidget; QString optionAlbumViewPrefix; }; void ImportView::Private::addPageUpDownActions(ImportView* const q, QWidget* const w) { defineShortcut(w, Qt::Key_PageDown, q, SLOT(slotNextItem())); defineShortcut(w, Qt::Key_Down, q, SLOT(slotNextItem())); defineShortcut(w, Qt::Key_Right, q, SLOT(slotNextItem())); defineShortcut(w, Qt::Key_PageUp, q, SLOT(slotPrevItem())); defineShortcut(w, Qt::Key_Up, q, SLOT(slotPrevItem())); defineShortcut(w, Qt::Key_Left, q, SLOT(slotPrevItem())); } ImportView::ImportView(ImportUI* const ui, QWidget* const parent) : DHBox(parent), d(new Private) { d->parent = static_cast(ui); d->splitter = new SidebarSplitter; d->splitter->setFrameStyle(QFrame::NoFrame); d->splitter->setFrameShadow(QFrame::Plain); d->splitter->setFrameShape(QFrame::NoFrame); d->splitter->setOpaqueResize(false); d->splitter->setParent(this); // The dock area where the thumbnail bar is allowed to go. // TODO qmainwindow here, wtf? d->dockArea = new QMainWindow(this, Qt::Widget); d->splitter->addWidget(d->dockArea); d->stackedView = new ImportStackedView(d->dockArea); d->stackedView->setViewMode(ImportStackedView::PreviewCameraMode); // call here, because the models need to be set first.. d->dockArea->setCentralWidget(d->stackedView); d->stackedView->setDockArea(d->dockArea); d->iconView = d->stackedView->importIconView(); #ifdef HAVE_MARBLE d->mapView = d->stackedView->mapWidgetView(); #endif // HAVE_MARBLE d->addPageUpDownActions(this, d->stackedView->importPreviewView()); d->addPageUpDownActions(this, d->stackedView->thumbBar()); #ifdef HAVE_MEDIAPLAYER d->addPageUpDownActions(this, d->stackedView->mediaPlayerView()); #endif //HAVE_MEDIAPLAYER d->selectionTimer = new QTimer(this); d->selectionTimer->setSingleShot(true); d->selectionTimer->setInterval(75); d->thumbSizeTimer = new QTimer(this); d->thumbSizeTimer->setSingleShot(true); d->thumbSizeTimer->setInterval(300); setupConnections(); loadViewState(); } ImportView::~ImportView() { saveViewState(); delete d; } void ImportView::applySettings() { //refreshView(); } void ImportView::refreshView() { //d->rightSideBar->refreshTagsView(); } void ImportView::setupConnections() { // -- ImportUI connections ---------------------------------- connect(d->parent, SIGNAL(signalEscapePressed()), this, SLOT(slotEscapePreview())); connect(d->parent, SIGNAL(signalEscapePressed()), d->stackedView, SLOT(slotEscapePreview())); // Preview items while download. connect(d->parent, SIGNAL(signalPreviewRequested(CamItemInfo,bool)), this, SLOT(slotTogglePreviewMode(CamItemInfo,bool))); // -- IconView Connections ------------------------------------- connect(d->iconView->model(), SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(slotImageSelected())); connect(d->iconView->model(), SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(slotImageSelected())); connect(d->iconView->model(), SIGNAL(layoutChanged()), this, SLOT(slotImageSelected())); connect(d->iconView, SIGNAL(selectionChanged()), this, SLOT(slotImageSelected())); connect(d->iconView, SIGNAL(previewRequested(CamItemInfo,bool)), this, SLOT(slotTogglePreviewMode(CamItemInfo,bool))); connect(d->iconView, SIGNAL(zoomOutStep()), this, SLOT(slotZoomOut())); connect(d->iconView, SIGNAL(zoomInStep()), this, SLOT(slotZoomIn())); // -- Preview image widget Connections ------------------------ connect(d->stackedView, SIGNAL(signalNextItem()), this, SLOT(slotNextItem())); connect(d->stackedView, SIGNAL(signalPrevItem()), this, SLOT(slotPrevItem())); connect(d->stackedView, SIGNAL(signalViewModeChanged()), this, SLOT(slotViewModeChanged())); connect(d->stackedView, SIGNAL(signalEscapePreview()), this, SLOT(slotEscapePreview())); connect(d->stackedView, SIGNAL(signalZoomFactorChanged(double)), this, SLOT(slotZoomFactorChanged(double))); // -- FileActionMngr progress --------------- connect(FileActionMngr::instance(), SIGNAL(signalImageChangeFailed(QString,QStringList)), this, SLOT(slotImageChangeFailed(QString,QStringList))); // -- timers --------------- connect(d->selectionTimer, SIGNAL(timeout()), this, SLOT(slotDispatchImageSelected())); connect(d->thumbSizeTimer, SIGNAL(timeout()), this, SLOT(slotThumbSizeEffect()) ); // -- Import Settings ---------------- //connect(ImportSettings::instance(), SIGNAL(setupChanged()), //this, SLOT(slotSidebarTabTitleStyleChanged())); } /* void ImportView::connectIconViewFilter(FilterStatusBar* filterbar) { ItemAlbumFilterModel* const model = d->iconView->imageAlbumFilterModel(); connect(model, SIGNAL(filterMatches(bool)), filterbar, SLOT(slotFilterMatches(bool))); connect(model, SIGNAL(filterSettingsChanged(ItemFilterSettings)), filterbar, SLOT(slotFilterSettingsChanged(ItemFilterSettings))); connect(filterbar, SIGNAL(signalResetFilters()), d->filterWidget, SLOT(slotResetFilters())); connect(filterbar, SIGNAL(signalPopupFiltersView()), this, SLOT(slotPopupFiltersView())); } void ImportView::slotPopupFiltersView() { d->rightSideBar->setActiveTab(d->filterWidget); d->filterWidget->setFocusToTextFilter(); } */ void ImportView::loadViewState() { //TODO: d->filterWidget->loadState(); KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group("Import MainWindow"); // Restore the splitter d->splitter->restoreState(group); // Restore the thumbnail bar dock. QByteArray thumbbarState; thumbbarState = group.readEntry("ThumbbarState", thumbbarState); d->dockArea->restoreState(QByteArray::fromBase64(thumbbarState)); #ifdef HAVE_MARBLE d->mapView->loadState(); #endif // HAVE_MARBLE } void ImportView::saveViewState() { KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group("Import MainWindow"); //TODO: d->filterWidget->saveState(); // Save the splitter states. d->splitter->saveState(group); // Save the position and size of the thumbnail bar. The thumbnail bar dock // needs to be closed explicitly, because when it is floating and visible // (when the user is in image preview mode) when the layout is saved, it // also reappears when restoring the view, while it should always be hidden. d->stackedView->thumbBarDock()->close(); group.writeEntry("ThumbbarState", d->dockArea->saveState().toBase64()); #ifdef HAVE_MARBLE d->mapView->saveState(); #endif // HAVE_MARBLE } CamItemInfo ImportView::camItemInfo(const QString& folder, const QString& file) const { return d->iconView->camItemInfo(folder, file); } CamItemInfo& ImportView::camItemInfoRef(const QString& folder, const QString& file) const { return d->iconView->camItemInfoRef(folder, file); } bool ImportView::hasImage(const CamItemInfo& info) const { return d->iconView->importItemModel()->hasImage(info); } QList ImportView::allUrls() const { return d->iconView->urls(); } QList ImportView::selectedUrls() const { return d->iconView->selectedUrls(); } QList ImportView::selectedCamItemInfos() const { return d->iconView->selectedCamItemInfos(); } QList ImportView::allItems() const { return d->iconView->camItemInfos(); } void ImportView::setSelectedCamItemInfos(const CamItemInfoList& infos) const { d->iconView->setSelectedCamItemInfos(infos); } int ImportView::downloadedCamItemInfos() const { QList infos = d->iconView->camItemInfos(); int numberOfDownloaded = 0; - foreach(const CamItemInfo& info, infos) + foreach (const CamItemInfo& info, infos) { if (info.downloaded == CamItemInfo::DownloadedYes) { ++numberOfDownloaded; } } return numberOfDownloaded; } bool ImportView::isSelected(const QUrl& url) const { QList urlsList = selectedUrls(); - foreach(const QUrl& selected, urlsList) + foreach (const QUrl& selected, urlsList) { if (url == selected) { return true; } } return false; } void ImportView::slotFirstItem() { d->iconView->toFirstIndex(); } void ImportView::slotPrevItem() { d->iconView->toPreviousIndex(); } void ImportView::slotNextItem() { d->iconView->toNextIndex(); } void ImportView::slotLastItem() { d->iconView->toLastIndex(); } void ImportView::slotSelectItemByUrl(const QUrl& url) { d->iconView->toIndex(url); } void ImportView::slotImageSelected() { // delay to slotDispatchImageSelected d->needDispatchSelection = true; d->selectionTimer->start(); emit signalSelectionChanged(d->iconView->numberOfSelectedIndexes()); } void ImportView::slotDispatchImageSelected() { if (d->needDispatchSelection) { // the list of CamItemInfos of currently selected items, currentItem first // since the iconView tracks the changes also while we are in map widget mode, // we can still pull the data from the iconView const CamItemInfoList list = d->iconView->selectedCamItemInfosCurrentFirst(); const CamItemInfoList allImages = d->iconView->camItemInfos(); if (list.isEmpty()) { d->stackedView->setPreviewItem(); emit signalImageSelected(list, allImages); emit signalNewSelection(false); emit signalNoCurrentItem(); } else { CamItemInfo previousInfo; CamItemInfo nextInfo; if (d->stackedView->viewMode() != ImportStackedView::MapWidgetMode) { previousInfo = d->iconView->previousInfo(list.first()); nextInfo = d->iconView->nextInfo(list.first()); } - if ( (d->stackedView->viewMode() != ImportStackedView::PreviewCameraMode) && - (d->stackedView->viewMode() != ImportStackedView::MapWidgetMode) ) + if ((d->stackedView->viewMode() != ImportStackedView::PreviewCameraMode) && + (d->stackedView->viewMode() != ImportStackedView::MapWidgetMode)) { d->stackedView->setPreviewItem(list.first(), previousInfo, nextInfo); } emit signalImageSelected(list, allImages); emit signalNewSelection(true); } d->needDispatchSelection = false; } } double ImportView::zoomMin() const { return d->stackedView->zoomMin(); } double ImportView::zoomMax() const { return d->stackedView->zoomMax(); } void ImportView::setZoomFactor(double zoom) { d->stackedView->setZoomFactorSnapped(zoom); } void ImportView::slotZoomFactorChanged(double zoom) { toggleZoomActions(); emit signalZoomChanged(zoom); } void ImportView::setThumbSize(int size) { if (d->stackedView->viewMode() == ImportStackedView::PreviewImageMode) { double z = DZoomBar::zoomFromSize(size, zoomMin(), zoomMax()); setZoomFactor(z); } else if (d->stackedView->viewMode() == ImportStackedView::PreviewCameraMode) { if (size > ThumbnailSize::maxThumbsSize()) { d->thumbSize = ThumbnailSize::maxThumbsSize(); } else if (size < ThumbnailSize::Small) { d->thumbSize = ThumbnailSize::Small; } else { d->thumbSize = size; } emit signalThumbSizeChanged(d->thumbSize); d->thumbSizeTimer->start(); } } ThumbnailSize ImportView::thumbnailSize() const { return ThumbnailSize(d->thumbSize); } void ImportView::slotThumbSizeEffect() { d->iconView->setThumbnailSize(ThumbnailSize(d->thumbSize)); toggleZoomActions(); ImportSettings::instance()->setDefaultIconSize(d->thumbSize); } void ImportView::toggleZoomActions() { if (d->stackedView->viewMode() == ImportStackedView::PreviewImageMode) { d->parent->enableZoomMinusAction(true); d->parent->enableZoomPlusAction(true); if (d->stackedView->maxZoom()) { d->parent->enableZoomPlusAction(false); } if (d->stackedView->minZoom()) { d->parent->enableZoomMinusAction(false); } } else if (d->stackedView->viewMode() == ImportStackedView::PreviewCameraMode) { d->parent->enableZoomMinusAction(true); d->parent->enableZoomPlusAction(true); if (d->thumbSize >= ThumbnailSize::maxThumbsSize()) { d->parent->enableZoomPlusAction(false); } if (d->thumbSize <= ThumbnailSize::Small) { d->parent->enableZoomMinusAction(false); } } else { d->parent->enableZoomMinusAction(false); d->parent->enableZoomPlusAction(false); } } void ImportView::slotZoomIn() { if (d->stackedView->viewMode() == ImportStackedView::PreviewCameraMode) { setThumbSize(d->thumbSize + ThumbnailSize::Step); toggleZoomActions(); emit signalThumbSizeChanged(d->thumbSize); } else if (d->stackedView->viewMode() == ImportStackedView::PreviewImageMode) { d->stackedView->increaseZoom(); } } void ImportView::slotZoomOut() { if (d->stackedView->viewMode() == ImportStackedView::PreviewCameraMode) { setThumbSize(d->thumbSize - ThumbnailSize::Step); toggleZoomActions(); emit signalThumbSizeChanged(d->thumbSize); } else if (d->stackedView->viewMode() == ImportStackedView::PreviewImageMode) { d->stackedView->decreaseZoom(); } } void ImportView::slotZoomTo100Percents() { if (d->stackedView->viewMode() == ImportStackedView::PreviewImageMode) { d->stackedView->toggleFitToWindowOr100(); } } void ImportView::slotFitToWindow() { if (d->stackedView->viewMode() == ImportStackedView::PreviewCameraMode) { int nts = d->iconView->fitToWidthIcons(); setThumbSize(nts); toggleZoomActions(); emit signalThumbSizeChanged(d->thumbSize); } else if (d->stackedView->viewMode() == ImportStackedView::PreviewImageMode) { d->stackedView->fitToWindow(); } } void ImportView::slotEscapePreview() { if (d->stackedView->viewMode() == ImportStackedView::PreviewCameraMode) //TODO: || d->stackedView->viewMode() == ImportStackedView::WelcomePageMode) { return; } // pass a null camera item info, because we want to fall back to the old // view mode slotTogglePreviewMode(CamItemInfo(), false); } void ImportView::slotMapWidgetView() { d->stackedView->setViewMode(ImportStackedView::MapWidgetMode); } void ImportView::slotIconView() { if (d->stackedView->viewMode() == ImportStackedView::PreviewImageMode) { emit signalThumbSizeChanged(d->iconView->thumbnailSize().size()); } // and switch to icon view d->stackedView->setViewMode(ImportStackedView::PreviewCameraMode); // make sure the next/previous buttons are updated slotImageSelected(); } void ImportView::slotImagePreview() { const int currentPreviewMode = d->stackedView->viewMode(); CamItemInfo currentInfo; if (currentPreviewMode == ImportStackedView::PreviewCameraMode) { currentInfo = d->iconView->currentInfo(); } #ifdef HAVE_MARBLE //TODO: Implement MapWidget else if (currentPreviewMode == ImportStackedView::MapWidgetMode) { currentInfo = d->mapView->currentCamItemInfo(); } #endif // HAVE_MARBLE slotTogglePreviewMode(currentInfo, false); } /** * @brief This method toggles between IconView/MapWidgetView and ImportPreview modes, depending on the context. */ void ImportView::slotTogglePreviewMode(const CamItemInfo& info, bool downloadPreview) { if (!d->parent->cameraUseUMSDriver()) { return; } - if ( (d->stackedView->viewMode() == ImportStackedView::PreviewCameraMode || - d->stackedView->viewMode() == ImportStackedView::MapWidgetMode || downloadPreview) && - !info.isNull() ) + if ((d->stackedView->viewMode() == ImportStackedView::PreviewCameraMode || + d->stackedView->viewMode() == ImportStackedView::MapWidgetMode || downloadPreview) && + !info.isNull()) { d->lastViewMode = d->stackedView->viewMode(); CamItemInfo previous = CamItemInfo(); if (!downloadPreview) { previous = d->iconView->previousInfo(info); } d->stackedView->setPreviewItem(info, previous, d->iconView->nextInfo(info)); } else { // go back to either CameraViewMode or MapWidgetMode d->stackedView->setViewMode(d->lastViewMode); } - if(!downloadPreview) + if (!downloadPreview) { // make sure the next/previous buttons are updated slotImageSelected(); } } void ImportView::slotViewModeChanged() { toggleZoomActions(); switch (d->stackedView->viewMode()) { case ImportStackedView::PreviewCameraMode: emit signalSwitchedToIconView(); emit signalThumbSizeChanged(d->iconView->thumbnailSize().size()); break; case ImportStackedView::PreviewImageMode: emit signalSwitchedToPreview(); slotZoomFactorChanged(d->stackedView->zoomFactor()); break; /* TODO case ImportStackedView::WelcomePageMode: emit signalSwitchedToIconView(); break; */ case ImportStackedView::MediaPlayerMode: emit signalSwitchedToPreview(); break; case ImportStackedView::MapWidgetMode: emit signalSwitchedToMapView(); //TODO: connect map view's zoom buttons to main status bar zoom buttons break; } } //TODO: Delete or implement this. void ImportView::slotImageRename() { d->iconView->rename(); } void ImportView::slotSelectAll() { d->iconView->selectAll(); } void ImportView::slotSelectNone() { d->iconView->clearSelection(); } void ImportView::slotSelectInvert() { d->iconView->invertSelection(); } void ImportView::slotSortImagesBy(int sortBy) { ImportSettings* const settings = ImportSettings::instance(); if (!settings) { return; } settings->setImageSortBy(sortBy); d->iconView->importFilterModel()->setSortRole((CamItemSortSettings::SortRole) sortBy); } void ImportView::slotSortImagesOrder(int order) { ImportSettings* const settings = ImportSettings::instance(); if (!settings) { return; } settings->setImageSortOrder(order); d->iconView->importFilterModel()->setSortOrder((CamItemSortSettings::SortOrder) order); } void ImportView::slotSeparateImages(int categoryMode) { ImportSettings* const settings = ImportSettings::instance(); if (!settings) { return; } settings->setImageSeparationMode(categoryMode); d->iconView->importFilterModel()->setCategorizationMode((CamItemSortSettings::CategorizationMode) categoryMode); } void ImportView::toggleShowBar(bool b) { d->stackedView->thumbBarDock()->showThumbBar(b); // See bug #319876 : force to reload current view mode to set thumbbar visibility properly. d->stackedView->setViewMode(viewMode()); } void ImportView::scrollTo(const QString& folder, const QString& file) { CamItemInfo info = camItemInfo(folder, file); QModelIndex index = d->iconView->importFilterModel()->indexForCamItemInfo(info); d->iconView->scrollToRelaxed(index); d->iconView->setSelectedCamItemInfos(CamItemInfoList() << info); } void ImportView::slotImageChangeFailed(const QString& message, const QStringList& fileNames) { if (fileNames.isEmpty()) { return; } DMessageBox::showInformationList(QMessageBox::Critical, qApp->activeWindow(), qApp->applicationName(), message, fileNames); } bool ImportView::hasCurrentItem() const { // We should actually get this directly from the selection model, // but the iconView is fine for now. return !d->iconView->currentInfo().isNull(); } /* void ImportView::slotImageExifOrientation(int orientation) { FileActionMngr::instance()->setExifOrientation(d->iconView->selectedCamItemInfos(), orientation); } */ ImportFilterModel* ImportView::importFilterModel() const { return d->iconView->importFilterModel(); } ImportStackedView::StackedViewMode ImportView::viewMode() const { return d->stackedView->viewMode(); } void ImportView::toggleFullScreen(bool set) { d->stackedView->importPreviewView()->toggleFullScreen(set); } void ImportView::updateIconView() { d->iconView->viewport()->update(); } } // namespace Digikam diff --git a/core/utilities/queuemanager/views/queuepool.cpp b/core/utilities/queuemanager/views/queuepool.cpp index ea3d5d620e..3094f4932d 100644 --- a/core/utilities/queuemanager/views/queuepool.cpp +++ b/core/utilities/queuemanager/views/queuepool.cpp @@ -1,574 +1,574 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2009-02-13 * Description : tabbed queue items list. * * Copyright (C) 2009-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 "queuepool.h" // Qt includes #include #include #include // KDE includes #include // Local includes #include "digikam_debug.h" #include "dmessagebox.h" #include "applicationsettings.h" #include "iccsettings.h" #include "metaenginesettings.h" #include "ddragobjects.h" #include "queuelist.h" #include "workflowmanager.h" #include "workflowdlg.h" #include "queuesettings.h" #include "loadingcacheinterface.h" namespace Digikam { QueuePoolBar::QueuePoolBar(QWidget* const parent) : QTabBar(parent) { setAcceptDrops(true); setMouseTracking(true); } QueuePoolBar::~QueuePoolBar() { } void QueuePoolBar::dragEnterEvent(QDragEnterEvent* e) { int tab = tabAt(e->pos()); - if ( tab != -1 ) + if (tab != -1) { bool accept = false; // The receivers of the testCanDecode() signal has to adjust 'accept' accordingly. emit signalTestCanDecode(e, accept); e->setAccepted(accept); return; } QTabBar::dragEnterEvent(e); } void QueuePoolBar::dragMoveEvent(QDragMoveEvent* e) { int tab = tabAt(e->pos()); - if ( tab != -1 ) + if (tab != -1) { bool accept = false; // The receivers of the testCanDecode() signal has to adjust 'accept' accordingly. emit signalTestCanDecode(e, accept); e->setAccepted(accept); return; } QTabBar::dragMoveEvent(e); } // -------------------------------------------------------------------------------------------- QueuePool::QueuePool(QWidget* const parent) : QTabWidget(parent) { setTabBar(new QueuePoolBar(this)); setTabsClosable(false); setAcceptDrops(true); slotAddQueue(); connect(this, SIGNAL(currentChanged(int)), this, SLOT(slotQueueSelected(int))); connect(this, SIGNAL(tabCloseRequested(int)), this, SLOT(slotCloseQueueRequest(int))); connect(tabBar(), SIGNAL(signalTestCanDecode(const QDragMoveEvent*,bool&)), this, SLOT(slotTestCanDecode(const QDragMoveEvent*,bool&))); // -- FileWatch connections ------------------------------ LoadingCacheInterface::connectToSignalFileChanged(this, SLOT(slotFileChanged(QString))); } QueuePool::~QueuePool() { } void QueuePool::keyPressEvent(QKeyEvent* event) { if (event->key() == Qt::Key_Delete) { slotRemoveSelectedItems(); } else { QTabWidget::keyPressEvent(event); } } void QueuePool::setBusy(bool b) { tabBar()->setEnabled(!b); for (int i = 0; i < count(); ++i) { QueueListView* const queue = dynamic_cast(widget(i)); if (queue) queue->viewport()->setEnabled(!b); } } QueueListView* QueuePool::currentQueue() const { return (dynamic_cast(currentWidget())); } QString QueuePool::currentTitle() const { return queueTitle(currentIndex()); } QueueListView* QueuePool::findQueueByItemId(qlonglong id) const { for (int i = 0; i < count(); ++i) { QueueListView* const queue = dynamic_cast(widget(i)); if (queue && queue->findItemById(id)) { return queue; } } return 0; } void QueuePool::setItemBusy(qlonglong id) { QueueListView* const queue = findQueueByItemId(id); if (queue) queue->setItemBusy(id); } QueueListView* QueuePool::findQueueByIndex(int index) const { return (dynamic_cast(widget(index))); } QMap QueuePool::queuesMap() const { QMap map; for (int i = 0; i < count(); ++i) { map.insert(i, queueTitle(i)); } return map; } QString QueuePool::queueTitle(int index) const { // NOTE: clean up tab title. With QTabWidget, it sound like mistake is added, as '&' and space. // NOTE update, & is an usability helper to allow keyboard access -teemu return (tabText(index).remove(QLatin1Char('&')).remove(QLatin1Char(' '))); } void QueuePool::slotAddQueue() { QueueListView* const queue = new QueueListView(this); if (!queue) return; int index = addTab(queue, QIcon::fromTheme(QLatin1String("run-build")), QString::fromUtf8("#%1").arg(count() + 1)); connect(queue, SIGNAL(signalQueueContentsChanged()), this, SIGNAL(signalQueueContentsChanged())); connect(queue, SIGNAL(itemSelectionChanged()), this, SIGNAL(signalItemSelectionChanged())); emit signalQueuePoolChanged(); setCurrentIndex(index); } QueuePoolItemsList QueuePool::queueItemsList(int index) const { QueuePoolItemsList qpool; QueueListView* const queue = dynamic_cast(widget(index)); if (queue) { ItemInfoList list = queue->pendingItemsList(); for (ItemInfoList::const_iterator it = list.constBegin() ; it != list.constEnd() ; ++it) { ItemInfo info = *it; ItemInfoSet set(index, info); qpool.append(set); } } return qpool; } int QueuePool::totalPendingItems() const { int items = 0; for (int i = 0; i < count(); ++i) { QueueListView* const queue = dynamic_cast(widget(i)); if (queue) items += queue->pendingItemsCount(); } return items; } int QueuePool::totalPendingTasks() const { int tasks = 0; for (int i = 0; i < count(); ++i) { QueueListView* const queue = dynamic_cast(widget(i)); if (queue) tasks += queue->pendingTasksCount(); } return tasks; } void QueuePool::slotRemoveCurrentQueue() { QueueListView* const queue = currentQueue(); if (!queue) { return; } removeTab(indexOf(queue)); if (count() == 0) { slotAddQueue(); } else { for (int i = 0; i < count(); ++i) { setTabText(i, QString::fromUtf8("#%1").arg(i + 1)); } } emit signalQueuePoolChanged(); } bool QueuePool::saveWorkflow() const { QueueListView* const queue = currentQueue(); if (queue) { WorkflowManager* const mngr = WorkflowManager::instance(); Workflow wf; wf.qSettings = queue->settings(); wf.aTools = queue->assignedTools().m_toolsList; if (WorkflowDlg::createNew(wf)) { mngr->insert(wf); mngr->save(); return true; } } return false; } void QueuePool::slotClearList() { QueueListView* const queue = currentQueue(); if (queue) { queue->slotClearList(); } } void QueuePool::slotRemoveSelectedItems() { QueueListView* const queue = currentQueue(); if (queue) { queue->slotRemoveSelectedItems(); } } void QueuePool::slotRemoveItemsDone() { QueueListView* const queue = currentQueue(); if (queue) { queue->slotRemoveItemsDone(); } } void QueuePool::slotAddItems(const ItemInfoList& list, int queueId) { QueueListView* const queue = findQueueByIndex(queueId); if (queue) { queue->slotAddItems(list); } } void QueuePool::slotAssignedToolsChanged(const AssignedBatchTools& tools4Item) { QueueListView* const queue = currentQueue(); if (queue) { queue->slotAssignedToolsChanged(tools4Item); } } void QueuePool::slotQueueSelected(int index) { QueueListView* const queue = dynamic_cast(widget(index)); if (queue) { emit signalItemSelectionChanged(); emit signalQueueSelected(index, queue->settings(), queue->assignedTools()); } } void QueuePool::slotCloseQueueRequest(int index) { removeTab(index); if (count() == 0) { slotAddQueue(); } emit signalQueuePoolChanged(); } void QueuePool::removeTab(int index) { QueueListView* const queue = dynamic_cast(widget(index)); if (!queue) return; int count = queue->pendingItemsCount(); if (count > 0) { int ret = QMessageBox::question(this, qApp->applicationName(), i18np("There is still 1 unprocessed item in \"%2\".\nDo you want to close this queue?", "There are still %1 unprocessed items in \"%2\".\nDo you want to close this queue?", count, queueTitle(index)), QMessageBox::Yes | QMessageBox::No); if (ret == QMessageBox::No) { return; } } QTabWidget::removeTab(index); } void QueuePool::slotTestCanDecode(const QDragMoveEvent* e, bool& accept) { 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())) { accept = true; return; } accept = false; } void QueuePool::slotSettingsChanged(const QueueSettings& settings) { QueueListView* const queue = currentQueue(); if (queue) { queue->setSettings(settings); } } bool QueuePool::customRenamingRulesAreValid() const { QStringList list; for (int i = 0; i < count(); ++i) { QueueListView* const queue = findQueueByIndex(i); if (queue) { if (queue->settings().renamingRule == QueueSettings::CUSTOMIZE && queue->settings().renamingParser.isEmpty()) { list.append(queueTitle(i)); } } } if (!list.isEmpty()) { DMessageBox::showInformationList(QMessageBox::Critical, qApp->activeWindow(), qApp->applicationName(), i18n("Custom renaming rules are invalid for Queues listed below. " "Please fix them."), list); return false; } return true; } bool QueuePool::assignedBatchToolsListsAreValid() const { QStringList list; for (int i = 0; i < count(); ++i) { QueueListView* const queue = findQueueByIndex(i); if (queue) { if (queue->assignedTools().m_toolsList.isEmpty()) { list.append(queueTitle(i)); } } } if (!list.isEmpty()) { DMessageBox::showInformationList(QMessageBox::Critical, qApp->activeWindow(), qApp->applicationName(), i18n("Assigned batch tools list is empty for Queues listed below. " "Please assign tools."), list); return false; } return true; } void QueuePool::slotFileChanged(const QString& filePath) { for (int i = 0; i < count(); ++i) { QueueListView* const queue = dynamic_cast(widget(i)); if (queue) { queue->reloadThumbs(QUrl::fromLocalFile(filePath)); } } } void QueuePool::applySettings() { for (int i = 0; i < count(); ++i) { QueueListView* const queue = dynamic_cast(widget(i)); if (queue) { // Show/hide tool-tips settings. queue->setEnableToolTips(ApplicationSettings::instance()->getShowToolTips()); // Reset Exif Orientation settings. QueueSettings prm = queue->settings(); prm.exifSetOrientation = MetaEngineSettings::instance()->settings().exifRotate; // Apply Color Management rules to RAW images decoding settings // If digiKam Color Management is enable, no need to correct color of decoded RAW image, // else, sRGB color workspace will be used. ICCSettingsContainer ICCSettings = IccSettings::instance()->settings(); if (ICCSettings.enableCM) { if (ICCSettings.defaultUncalibratedBehavior & ICCSettingsContainer::AutomaticColors) { prm.rawDecodingSettings.outputColorSpace = DRawDecoderSettings::CUSTOMOUTPUTCS; prm.rawDecodingSettings.outputProfile = ICCSettings.workspaceProfile; } else { prm.rawDecodingSettings.outputColorSpace = DRawDecoderSettings::RAWCOLOR; } } else { prm.rawDecodingSettings.outputColorSpace = DRawDecoderSettings::SRGB; } queue->setSettings(prm); } } } } // namespace Digikam diff --git a/core/utilities/setup/metadata/namespaceeditdlg.cpp b/core/utilities/setup/metadata/namespaceeditdlg.cpp index d262cd2b67..88a833dbc5 100644 --- a/core/utilities/setup/metadata/namespaceeditdlg.cpp +++ b/core/utilities/setup/metadata/namespaceeditdlg.cpp @@ -1,688 +1,688 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2015-07-03 * Description : dialog to edit and create digiKam xmp namespaces * * Copyright (C) 2015 by Veaceslav Munteanu * * 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 "namespaceeditdlg.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include // Local includes #include "dxmlguiwindow.h" #include "digikam_debug.h" namespace Digikam { class Q_DECL_HIDDEN NamespaceEditDlg::Private { public: explicit Private() { buttons = 0; create = 0; topLabel = 0; logo = 0; gridLayout = 0; page = 0; // NamespaceEntry variables subspaceCombo = 0; specialOptsCombo = 0; altSpecialOptsCombo = 0; namespaceName = 0; alternativeName = 0; nameSpaceSeparator = 0; isPath = 0; ratingMappings = 0; zeroStars = 0; oneStar = 0; twoStars = 0; threeStars = 0; fourStars = 0; fiveStars = 0; // Labels tagTipLabel = 0; ratingTipLabel = 0; commentTipLabel = 0; subspaceLabel = 0; titleLabel = 0; specialOptsLabel = 0; alternativeNameLabel = 0; altspecialOptsLabel = 0; isTagLabel = 0; separatorLabel = 0; tipLabel2 = 0; nsType = NamespaceEntry::TAGS; } QDialogButtonBox* buttons; bool create; QLabel* topLabel; QLabel* logo; QGridLayout* gridLayout; QWidget* page; // NamespaceEntry variables QComboBox* subspaceCombo; QComboBox* specialOptsCombo; QComboBox* altSpecialOptsCombo; QLineEdit* namespaceName; QLineEdit* alternativeName; QLineEdit* nameSpaceSeparator; QCheckBox* isPath; QGroupBox* ratingMappings; QSpinBox* zeroStars; QSpinBox* oneStar; QSpinBox* twoStars; QSpinBox* threeStars; QSpinBox* fourStars; QSpinBox* fiveStars; // Labels QLabel* tagTipLabel; QLabel* ratingTipLabel; QLabel* commentTipLabel; QLabel* subspaceLabel; QLabel* titleLabel; QLabel* specialOptsLabel; QLabel* alternativeNameLabel; QLabel* altspecialOptsLabel; QLabel* isTagLabel; QLabel* separatorLabel; QLabel* tipLabel2; NamespaceEntry::NamespaceType nsType; }; NamespaceEditDlg::NamespaceEditDlg(bool create, NamespaceEntry& entry, QWidget* const parent) : QDialog(parent), d(new Private()) { setModal(true); d->buttons = new QDialogButtonBox(QDialogButtonBox::Help | QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); d->buttons->button(QDialogButtonBox::Ok)->setDefault(true); if (create) { setWindowTitle(i18n("New Xmp Namespace")); } else { setWindowTitle(i18n("Edit Xmp Namespace")); } d->create = create; d->nsType = entry.nsType; setupTagGui(entry); // -------------------------------------------------------- connect(d->buttons->button(QDialogButtonBox::Ok), SIGNAL(clicked()), this, SLOT(accept())); connect(d->buttons->button(QDialogButtonBox::Cancel), SIGNAL(clicked()), this, SLOT(reject())); connect(d->buttons->button(QDialogButtonBox::Help), SIGNAL(clicked()), this, SLOT(slotHelp())); // -------------------------------------------------------- if (!d->create) { populateFields(entry); } setType(entry.nsType); if (entry.isDefault) { makeReadOnly(); } qCDebug(DIGIKAM_GENERAL_LOG) << "Entry type" << entry.nsType << "subspace" << entry.subspace << entry.isDefault; adjustSize(); } NamespaceEditDlg::~NamespaceEditDlg() { delete d; } bool NamespaceEditDlg::create(QWidget* const parent, NamespaceEntry& entry) { QPointer dlg = new NamespaceEditDlg(true,entry,parent); qCDebug(DIGIKAM_GENERAL_LOG) << "Name before save: " << entry.namespaceName; bool valRet = dlg->exec(); if (valRet == QDialog::Accepted) { dlg->saveData(entry); } qCDebug(DIGIKAM_GENERAL_LOG) << "Name after save: " << entry.namespaceName; delete dlg; return valRet; } bool NamespaceEditDlg::edit(QWidget* const parent, NamespaceEntry& entry) { QPointer dlg = new NamespaceEditDlg(false, entry, parent); qCDebug(DIGIKAM_GENERAL_LOG) << "Name before save: " << entry.namespaceName; bool valRet = dlg->exec(); if (valRet == QDialog::Accepted && !entry.isDefault) { dlg->saveData(entry); } qCDebug(DIGIKAM_GENERAL_LOG) << "Name before save: " << entry.namespaceName; delete dlg; return valRet; } void NamespaceEditDlg::setupTagGui(NamespaceEntry& entry) { d->page = new QWidget(this); d->gridLayout = new QGridLayout(d->page); d->logo = new QLabel(d->page); d->logo->setPixmap(QIcon::fromTheme(QLatin1String("digikam")).pixmap(QSize(48,48))); d->topLabel = new QLabel(d->page); d->topLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); d->topLabel->setWordWrap(false); d->topLabel->setText(i18n("Add metadata namespace")); d->subspaceCombo = new QComboBox(this); d->subspaceLabel = new QLabel(d->page); d->subspaceLabel->setText(i18n("Metadata Subspace")); d->subspaceCombo->addItem(QLatin1String("EXIF"), (int)NamespaceEntry::EXIF); d->subspaceCombo->addItem(QLatin1String("IPTC"), (int)NamespaceEntry::IPTC); d->subspaceCombo->addItem(QLatin1String("XMP"), (int)NamespaceEntry::XMP); d->subspaceCombo->setCurrentIndex((int)entry.subspace); qCDebug(DIGIKAM_GENERAL_LOG) << "Entry subspace" << (int)entry.subspace; // -------------------Tag Elements--------------------------------- d->titleLabel = new QLabel(d->page); d->titleLabel->setText(i18n("Name:")); d->namespaceName = new QLineEdit(this); //----------------- Tip Labels -------------------------------------- d->tagTipLabel = new QLabel(d->page); d->tagTipLabel->setTextFormat(Qt::RichText); d->tagTipLabel->setWordWrap(true); d->tagTipLabel->setText(i18n("

To create new namespaces, you need to specify parameters:

" "

  • Namespace name with dots.
    " "Ex.: \"Xmp.digiKam.TagsList\"
  • " "
  • Separator parameter, used by tag paths
    " "Ex.: \"City/Paris\" or \"City|Paris\"
  • " "
  • Specify if only keyword or the whole path must be written.

" )); d->ratingTipLabel = new QLabel(d->page); d->ratingTipLabel->setTextFormat(Qt::RichText); d->ratingTipLabel->setWordWrap(true); d->ratingTipLabel->setText(i18n("

To create new rating namespaces, you need to specify parameters:

" "

  • Namespace name with dots.
    " "Ex.: \"Xmp.xmp.Rating\"
  • " "
  • Rating mappings, if namespace need other values than 0-5
    " "Ex.: Microsoft uses 0 1 25 50 75 99
  • " "
  • Select the correct namespace option from list.

" )); d->commentTipLabel = new QLabel(d->page); d->commentTipLabel->setTextFormat(Qt::RichText); d->commentTipLabel->setWordWrap(true); d->commentTipLabel->setText(i18n("

To create new comment namespaces, you need to specify parameters:

" "

  • Namespace name with dots.
    " "Ex.: \"Xmp.xmp.Rating\"
  • " "
  • Select the correct namespace option from list.

" )); // ------------------------------------------------------- d->specialOptsLabel = new QLabel(d->page); d->specialOptsLabel->setText(i18n("Special Options")); d->specialOptsCombo = new QComboBox(d->page); d->specialOptsCombo->addItem(QLatin1String("NO_OPTS"), (int)NamespaceEntry::NO_OPTS); if (entry.nsType == NamespaceEntry::COMMENT) { d->specialOptsCombo->addItem(QLatin1String("COMMENT_ALTLANG"), NamespaceEntry::COMMENT_ALTLANG); d->specialOptsCombo->addItem(QLatin1String("COMMENT_ALTLANGLIST"), NamespaceEntry::COMMENT_ATLLANGLIST); d->specialOptsCombo->addItem(QLatin1String("COMMENT_XMP"), NamespaceEntry::COMMENT_XMP); d->specialOptsCombo->addItem(QLatin1String("COMMENT_JPEG"), NamespaceEntry::COMMENT_JPEG); } if (entry.nsType == NamespaceEntry::TAGS) { d->specialOptsCombo->addItem(QLatin1String("TAG_XMPBAG"), NamespaceEntry::TAG_XMPBAG); d->specialOptsCombo->addItem(QLatin1String("TAG_XMPSEQ"), NamespaceEntry::TAG_XMPSEQ); d->specialOptsCombo->addItem(QLatin1String("TAG_ACDSEE"), NamespaceEntry::TAG_ACDSEE); } d->alternativeNameLabel = new QLabel(d->page); d->alternativeNameLabel->setText(i18n("Alternative name")); d->alternativeName = new QLineEdit(d->page); d->altspecialOptsLabel = new QLabel(d->page); d->altspecialOptsLabel->setText(i18n("Alternative special options")); d->altSpecialOptsCombo = new QComboBox(d->page); d->altSpecialOptsCombo->addItem(QLatin1String("NO_OPTS"), (int)NamespaceEntry::NO_OPTS); if (entry.nsType == NamespaceEntry::COMMENT) { d->altSpecialOptsCombo->addItem(QLatin1String("COMMENT_ALTLANG"), NamespaceEntry::COMMENT_ALTLANG); d->altSpecialOptsCombo->addItem(QLatin1String("COMMENT_ALTLANGLIST"), NamespaceEntry::COMMENT_ATLLANGLIST); d->altSpecialOptsCombo->addItem(QLatin1String("COMMENT_XMP"), NamespaceEntry::COMMENT_XMP); d->altSpecialOptsCombo->addItem(QLatin1String("COMMENT_JPEG"), NamespaceEntry::COMMENT_JPEG); } if (entry.nsType == NamespaceEntry::TAGS) { d->altSpecialOptsCombo->addItem(QLatin1String("TAG_XMPBAG"), NamespaceEntry::TAG_XMPBAG); d->altSpecialOptsCombo->addItem(QLatin1String("TAG_XMPSEQ"), NamespaceEntry::TAG_XMPSEQ); d->altSpecialOptsCombo->addItem(QLatin1String("TAG_ACDSEE"), NamespaceEntry::TAG_ACDSEE); } // -------------------------------------------------------- d->separatorLabel = new QLabel(d->page); d->separatorLabel->setText(i18n("Separator:")); d->nameSpaceSeparator = new QLineEdit(this); // -------------------------------------------------------- d->isTagLabel = new QLabel(d->page); d->isTagLabel->setText(i18n("Set Tags Path:")); d->isPath = new QCheckBox(this); d->tipLabel2 = new QLabel(d->page); d->tipLabel2->setTextFormat(Qt::RichText); d->tipLabel2->setWordWrap(true); QPalette sample_palette; sample_palette.setColor(QPalette::Window, QColor(255, 51, 51, 150)); sample_palette.setColor(QPalette::WindowText, Qt::black); d->tipLabel2->setAutoFillBackground(true); d->tipLabel2->setPalette(sample_palette); d->tipLabel2->hide(); // ----------------------Rating Elements---------------------------------- d->ratingMappings = new QGroupBox(this); d->ratingMappings->setFlat(true); QGridLayout* const ratingMappingsLayout = new QGridLayout(d->ratingMappings); QLabel* const ratingLabel = new QLabel(d->page); ratingLabel->setText(i18n("Rating Mapping:")); d->zeroStars = new QSpinBox(this); d->zeroStars->setValue(0); d->oneStar = new QSpinBox(this); d->oneStar->setValue(1); d->twoStars = new QSpinBox(this); d->twoStars->setValue(2); d->threeStars = new QSpinBox(this); d->threeStars->setValue(3); d->fourStars = new QSpinBox(this); d->fourStars->setValue(4); d->fiveStars = new QSpinBox(this); d->fiveStars->setValue(5); const int spacing = QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); const int cmargin = QApplication::style()->pixelMetric(QStyle::PM_DefaultChildMargin); ratingMappingsLayout->addWidget(ratingLabel, 0, 0, 1, 2); ratingMappingsLayout->addWidget(d->zeroStars, 1, 0, 1, 1); ratingMappingsLayout->addWidget(d->oneStar, 1, 1, 1, 1); ratingMappingsLayout->addWidget(d->twoStars, 1, 2, 1, 1); ratingMappingsLayout->addWidget(d->threeStars, 1, 3, 1, 1); ratingMappingsLayout->addWidget(d->fourStars, 1, 4, 1, 1); ratingMappingsLayout->addWidget(d->fiveStars, 1, 5, 1, 1); d->gridLayout->addWidget(d->logo, 0, 0, 1, 2); d->gridLayout->addWidget(d->topLabel, 0, 1, 1, 4); d->gridLayout->addWidget(d->tagTipLabel, 1, 0, 1, 6); d->gridLayout->addWidget(d->ratingTipLabel, 2, 0, 1, 6); d->gridLayout->addWidget(d->commentTipLabel, 3, 0, 1, 6); d->gridLayout->addWidget(d->subspaceLabel, 5, 0, 1, 2); d->gridLayout->addWidget(d->subspaceCombo, 5, 2, 1, 4); d->gridLayout->addWidget(d->titleLabel, 6, 0, 1, 2); d->gridLayout->addWidget(d->namespaceName, 6, 2, 1, 4); d->gridLayout->addWidget(d->specialOptsLabel, 7, 0, 1, 2); d->gridLayout->addWidget(d->specialOptsCombo, 7, 2, 1, 4); d->gridLayout->addWidget(d->alternativeNameLabel, 8, 0, 1, 2); d->gridLayout->addWidget(d->alternativeName, 8, 2, 1, 4); d->gridLayout->addWidget(d->altspecialOptsLabel, 9, 0, 1, 2); d->gridLayout->addWidget(d->altSpecialOptsCombo, 9, 2, 1, 4); d->gridLayout->addWidget(d->separatorLabel, 10, 0, 1, 2); d->gridLayout->addWidget(d->nameSpaceSeparator, 10, 2, 1, 4); d->gridLayout->addWidget(d->isTagLabel, 11, 0, 1, 2); d->gridLayout->addWidget(d->isPath, 11, 2, 1, 3); d->gridLayout->addWidget(d->ratingMappings, 12, 0, 2, 6); d->gridLayout->addWidget(d->tipLabel2, 14, 0, 1, 6); d->gridLayout->setContentsMargins(cmargin, cmargin, cmargin, cmargin); d->gridLayout->setSpacing(spacing); QVBoxLayout* const vbx = new QVBoxLayout(this); vbx->addWidget(d->page); vbx->addWidget(d->buttons); } void NamespaceEditDlg::populateFields(NamespaceEntry& entry) { d->namespaceName->setText(entry.namespaceName); d->nameSpaceSeparator->setText(entry.separator); if (entry.tagPaths == NamespaceEntry::TAGPATH) { d->isPath->setChecked(true); } else { d->isPath->setChecked(false); } d->alternativeName->setText(entry.alternativeName); d->specialOptsCombo->setCurrentIndex(d->specialOptsCombo->findData(entry.specialOpts)); d->altSpecialOptsCombo->setCurrentIndex(d->altSpecialOptsCombo->findData(entry.secondNameOpts)); if (entry.convertRatio.size() == 6) { d->zeroStars->setValue(entry.convertRatio.at(0)); d->oneStar->setValue(entry.convertRatio.at(1)); d->twoStars->setValue(entry.convertRatio.at(2)); d->threeStars->setValue(entry.convertRatio.at(3)); d->fourStars->setValue(entry.convertRatio.at(4)); d->fiveStars->setValue(entry.convertRatio.at(5)); } } void NamespaceEditDlg::setType(NamespaceEntry::NamespaceType type) { switch(type) { case NamespaceEntry::TAGS: qCDebug(DIGIKAM_GENERAL_LOG) << "Setting up tags"; d->ratingTipLabel->hide(); d->commentTipLabel->hide(); d->ratingMappings->hide(); // disable IPTC and EXIV for tags d->subspaceCombo->setItemData(0, 0, Qt::UserRole -1); d->subspaceCombo->setItemData(1, 0, Qt::UserRole -1); break; case NamespaceEntry::RATING: d->tagTipLabel->hide(); d->commentTipLabel->hide(); d->isPath->hide(); d->isTagLabel->hide(); d->separatorLabel->hide(); d->nameSpaceSeparator->hide(); break; case NamespaceEntry::COMMENT: d->tagTipLabel->hide(); d->ratingTipLabel->hide(); d->isPath->hide(); d->isTagLabel->hide(); d->separatorLabel->hide(); d->nameSpaceSeparator->hide(); d->ratingMappings->hide(); break; default: break; } } void NamespaceEditDlg::makeReadOnly() { QString txt = i18n("This is a default namespace. Default namespaces can only be disabled"); d->tipLabel2->setText(txt); d->tipLabel2->show(); d->subspaceCombo->setDisabled(true); d->specialOptsCombo->setDisabled(true); d->altSpecialOptsCombo->setDisabled(true); d->namespaceName->setDisabled(true); d->alternativeName->setDisabled(true); d->nameSpaceSeparator->setDisabled(true); d->isPath->setDisabled(true); d->ratingMappings->setDisabled(true); d->zeroStars->setDisabled(true); d->oneStar->setDisabled(true); d->twoStars->setDisabled(true); d->threeStars->setDisabled(true); d->fourStars->setDisabled(true); d->fiveStars->setDisabled(true); } bool NamespaceEditDlg::validifyCheck(QString& errMsg) { // bool result = true; NOT USED if (d->namespaceName->text().isEmpty()) { errMsg = i18n("The namespace name is required"); return false; } switch (d->subspaceCombo->currentData().toInt()) { case NamespaceEntry::EXIF: if (d->namespaceName->text().split(QLatin1Char('.')).first() != QLatin1String("Exif")) { errMsg = i18n("EXIF namespace name must start with \"Exif\"."); return false; } if (!d->alternativeName->text().isEmpty() && d->alternativeName->text().split(QLatin1Char('.')).first() != QLatin1String("Exif")) { errMsg = i18n("EXIF alternative namespace name must start with \"Exif\"."); return false; } break; case NamespaceEntry::IPTC: if (d->namespaceName->text().split(QLatin1Char('.')).first() != QLatin1String("Iptc")) { errMsg = i18n("IPTC namespace name must start with \"Iptc\"."); return false; } - if(!d->alternativeName->text().isEmpty() && - d->alternativeName->text().split(QLatin1Char('.')).first() != QLatin1String("Iptc")) + if (!d->alternativeName->text().isEmpty() && + d->alternativeName->text().split(QLatin1Char('.')).first() != QLatin1String("Iptc")) { errMsg = i18n("IPTC alternative namespace name must start with \"Iptc\"."); return false; } break; case NamespaceEntry::XMP: if (d->namespaceName->text().split(QLatin1Char('.')).first() != QLatin1String("Xmp")) { errMsg = i18n("XMP namespace name must start with \"Xmp\"."); return false; } if (!d->alternativeName->text().isEmpty() && d->alternativeName->text().split(QLatin1Char('.')).first() != QLatin1String("Xmp")) { errMsg = i18n("XMP alternative namespace name must start with \"Xmp\"."); return false; } break; default: break; } switch (d->nsType) { case NamespaceEntry::TAGS: if (d->nameSpaceSeparator->text().isEmpty()) { errMsg = i18n("Tag Path separator is required"); return false; } if (d->nameSpaceSeparator->text().size() > 1) { errMsg = i18n("Only one character is now supported as tag path separator"); return false; } break; case NamespaceEntry::RATING: break; case NamespaceEntry::COMMENT: break; default: break; } return true; } void NamespaceEditDlg::saveData(NamespaceEntry& entry) { entry.namespaceName = d->namespaceName->text(); entry.separator = d->nameSpaceSeparator->text(); if (d->isPath->isChecked()) { entry.tagPaths = NamespaceEntry::TAGPATH; } else { entry.tagPaths = NamespaceEntry::TAG; } entry.alternativeName = d->alternativeName->text(); entry.specialOpts = (NamespaceEntry::SpecialOptions)d->specialOptsCombo->currentData().toInt(); entry.secondNameOpts = (NamespaceEntry::SpecialOptions)d->altSpecialOptsCombo->currentData().toInt(); entry.subspace = (NamespaceEntry::NsSubspace)d->subspaceCombo->currentData().toInt(); entry.convertRatio.clear(); entry.convertRatio.append(d->zeroStars->value()); entry.convertRatio.append(d->oneStar->value()); entry.convertRatio.append(d->twoStars->value()); entry.convertRatio.append(d->threeStars->value()); entry.convertRatio.append(d->fourStars->value()); entry.convertRatio.append(d->fiveStars->value()); } void NamespaceEditDlg::accept() { QString errMsg; if (validifyCheck(errMsg)) { QDialog::accept(); } else { d->tipLabel2->setText(errMsg); d->tipLabel2->show(); } } void NamespaceEditDlg::slotHelp() { DXmlGuiWindow::openHandbook(); } } // namespace Digikam