diff --git a/core/app/date/ddatepicker.cpp b/core/app/date/ddatepicker.cpp index 89d5f9c4c4..ce0e97ead5 100644 --- a/core/app/date/ddatepicker.cpp +++ b/core/app/date/ddatepicker.cpp @@ -1,579 +1,579 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 1997-04-21 * Description : A date selection widget. * * Copyright (C) 2011-2018 by Gilles Caulier * Copyright (C) 1997 by Tim D. Gilman * Copyright (C) 1998-2001 by Mirko Boehm * Copyright (C) 2007 by John Layt * * 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 "ddatepicker.h" #include "ddatepicker_p.h" // Qt includes #include #include #include #include #include #include #include #include // KDE includes #include // Local includes #include "ddatetable_p.h" #include "dpopupframe.h" namespace Digikam { DDatePicker::DDatePicker(QWidget* const parent) : QFrame(parent), d(new Private(this)) { initWidget(QDate::currentDate()); } DDatePicker::DDatePicker(const QDate& dt, QWidget* const parent) : QFrame(parent), d(new Private(this)) { initWidget(dt); } void DDatePicker::initWidget(const QDate& dt) { const int spacingHint = style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); QBoxLayout* const topLayout = new QVBoxLayout(this); topLayout->setSpacing(0); topLayout->setMargin(0); d->navigationLayout = new QHBoxLayout(); d->navigationLayout->setSpacing(0); d->navigationLayout->setMargin(0); topLayout->addLayout(d->navigationLayout); d->navigationLayout->addStretch(); d->yearBackward = new QToolButton(this); d->yearBackward->setAutoRaise(true); d->navigationLayout->addWidget(d->yearBackward); d->monthBackward = new QToolButton(this); d->monthBackward ->setAutoRaise(true); d->navigationLayout->addWidget(d->monthBackward); d->navigationLayout->addSpacing(spacingHint); d->selectMonth = new QToolButton(this); d->selectMonth ->setAutoRaise(true); d->navigationLayout->addWidget(d->selectMonth); d->selectYear = new QToolButton(this); d->selectYear->setCheckable(true); d->selectYear->setAutoRaise(true); d->navigationLayout->addWidget(d->selectYear); d->navigationLayout->addSpacing(spacingHint); d->monthForward = new QToolButton(this); d->monthForward ->setAutoRaise(true); d->navigationLayout->addWidget(d->monthForward); d->yearForward = new QToolButton(this); d->yearForward ->setAutoRaise(true); d->navigationLayout->addWidget(d->yearForward); d->navigationLayout->addStretch(); d->line = new QLineEdit(this); d->val = new DatePickerValidator(this); d->table = new DDateTable(this); setFocusProxy(d->table); d->fontsize = QFontDatabase::systemFont(QFontDatabase::GeneralFont).pointSize(); if (d->fontsize == -1) { d->fontsize = QFontInfo(QFontDatabase::systemFont(QFontDatabase::GeneralFont)).pointSize(); } d->fontsize++; // Make a little bigger d->selectWeek = new QComboBox(this); // read only week selection d->selectWeek->setFocusPolicy(Qt::NoFocus); d->todayButton = new QToolButton(this); d->todayButton->setIcon(QIcon::fromTheme(QLatin1String("go-jump-today"))); d->yearForward->setToolTip(i18n("Next year")); d->yearBackward->setToolTip(i18n("Previous year")); d->monthForward->setToolTip(i18n("Next month")); d->monthBackward->setToolTip(i18n("Previous month")); d->selectWeek->setToolTip(i18n("Select a week")); d->selectMonth->setToolTip(i18n("Select a month")); d->selectYear->setToolTip(i18n("Select a year")); d->todayButton->setToolTip(i18n("Select the current day")); // ----- setFontSize(d->fontsize); d->line->setValidator(d->val); d->line->installEventFilter(this); if (QApplication::isRightToLeft()) { d->yearForward->setIcon(QIcon::fromTheme(QLatin1String("arrow-left-double"))); d->yearBackward->setIcon(QIcon::fromTheme(QLatin1String("arrow-right-double"))); d->monthForward->setIcon(QIcon::fromTheme(QLatin1String("go-previous"))); d->monthBackward->setIcon(QIcon::fromTheme(QLatin1String("go-next"))); } else { d->yearForward->setIcon(QIcon::fromTheme(QLatin1String("arrow-right-double"))); d->yearBackward->setIcon(QIcon::fromTheme(QLatin1String("arrow-left-double"))); d->monthForward->setIcon(QIcon::fromTheme(QLatin1String("go-next"))); d->monthBackward->setIcon(QIcon::fromTheme(QLatin1String("go-previous"))); } connect(d->table, SIGNAL(dateChanged(QDate)), this, SLOT(dateChangedSlot(QDate))); connect(d->table, &DDateTable::tableClicked, this, &DDatePicker::tableClickedSlot); connect(d->monthForward, &QAbstractButton::clicked, this, &DDatePicker::monthForwardClicked); connect(d->monthBackward, &QAbstractButton::clicked, this, &DDatePicker::monthBackwardClicked); connect(d->yearForward, &QAbstractButton::clicked, this, &DDatePicker::yearForwardClicked); connect(d->yearBackward, &QAbstractButton::clicked, this, &DDatePicker::yearBackwardClicked); connect(d->selectWeek, SIGNAL(activated(int)), this, SLOT(weekSelected(int))); connect(d->todayButton, &QAbstractButton::clicked, this, &DDatePicker::todayButtonClicked); connect(d->selectMonth, &QAbstractButton::clicked, this, &DDatePicker::selectMonthClicked); connect(d->selectYear, &QAbstractButton::toggled, this, &DDatePicker::selectYearClicked); connect(d->line, &QLineEdit::returnPressed, this, &DDatePicker::lineEnterPressed); topLayout->addWidget(d->table); QBoxLayout* const bottomLayout = new QHBoxLayout(); bottomLayout->setMargin(0); bottomLayout->setSpacing(0); topLayout->addLayout(bottomLayout); bottomLayout->addWidget(d->todayButton); bottomLayout->addWidget(d->line); bottomLayout->addWidget(d->selectWeek); d->table->setDate(dt); dateChangedSlot(dt); // needed because table emits changed only when newDate != oldDate } DDatePicker::~DDatePicker() { delete d; } bool DDatePicker::eventFilter(QObject* o, QEvent* e) { if (e->type() == QEvent::KeyPress) { QKeyEvent* const k = (QKeyEvent *)e; if ((k->key() == Qt::Key_PageUp) || (k->key() == Qt::Key_PageDown) || (k->key() == Qt::Key_Up) || (k->key() == Qt::Key_Down)) { QApplication::sendEvent(d->table, e); d->table->setFocus(); return true; // eat event } } return QFrame::eventFilter(o, e); } void DDatePicker::resizeEvent(QResizeEvent* e) { QWidget::resizeEvent(e); } void DDatePicker::dateChangedSlot(const QDate& dt) { QString dateFormat = locale().dateFormat(QLocale::ShortFormat); if (!dateFormat.contains(QLatin1String("yyyy"))) { dateFormat.replace(QLatin1String("yy"), QLatin1String("yyyy")); } d->line->setText(dt.toString(dateFormat)); d->selectMonth->setText(locale().standaloneMonthName(dt.month(), QLocale::LongFormat)); d->fillWeeksCombo(); // calculate the item num in the week combo box; normalize selected day so as if 1.1. is the first day of the week QDate firstDay(dt.year(), 1, 1); // If we cannot successfully create the 1st of the year, this can only mean that // the 1st is before the earliest valid date in the current calendar system, so use // the earliestValidDate as the first day. // In particular covers the case of Gregorian where 1/1/-4713 is not a valid QDate d->selectWeek->setCurrentIndex((dt.dayOfYear() + firstDay.dayOfWeek() - 2) / 7); d->selectYear->setText(QString::number(dt.year()).rightJustified(4, QLatin1Char('0'))); emit(dateChanged(dt)); } void DDatePicker::tableClickedSlot() { emit(dateSelected(date())); emit(tableClicked()); } const QDate &DDatePicker::date() const { return d->table->date(); } bool DDatePicker::setDate(const QDate& dt) { // the table setDate does validity checking for us // this also emits dateChanged() which then calls our dateChangedSlot() return d->table->setDate(dt); } void DDatePicker::monthForwardClicked() { if (! setDate(date().addMonths(1))) { QApplication::beep(); } d->table->setFocus(); } void DDatePicker::monthBackwardClicked() { if (! setDate(date().addMonths(-1))) { QApplication::beep(); } d->table->setFocus(); } void DDatePicker::yearForwardClicked() { if (! setDate(d->table->date().addYears(1))) { QApplication::beep(); } d->table->setFocus(); } void DDatePicker::yearBackwardClicked() { if (! setDate(d->table->date().addYears(-1))) { QApplication::beep(); } d->table->setFocus(); } void DDatePicker::weekSelected(int index) { QDate targetDay = d->selectWeek->itemData(index).toDateTime().date(); if (! setDate(targetDay)) { QApplication::beep(); } d->table->setFocus(); } void DDatePicker::selectMonthClicked() { QDate thisDate(date()); d->table->setFocus(); QMenu popup(d->selectMonth); // Populate the pick list with all the month names, this may change by year // JPL do we need to do something here for months that fall outside valid range? const int monthsInYear = QDate(thisDate.year() + 1, 1, 1).addDays(-1).month(); - for (int m = 1; m <= monthsInYear; m++) + for (int m = 1 ; m <= monthsInYear ; ++m) { popup.addAction(locale().standaloneMonthName(m))->setData(m); } QAction* item = popup.actions()[ thisDate.month() - 1 ]; // if this happens the above should already given an assertion if (item) { popup.setActiveAction(item); } // cancelled if ((item = popup.exec(d->selectMonth->mapToGlobal(QPoint(0, 0)), item)) == 0) { return; } // We need to create a valid date in the month selected so we can find out how many days are // in the month. QDate newDate(thisDate.year(), item->data().toInt(), 1); // If we have succeeded in creating a date in the new month, then try to create the new date, // checking we don't set a day after the last day of the month newDate.setDate(newDate.year(), newDate.month(), qMin(thisDate.day(), newDate.daysInMonth())); // Set the date, if it's invalid in any way then alert user and don't update if (! setDate(newDate)) { QApplication::beep(); } } void DDatePicker::selectYearClicked() { if (!d->selectYear->isChecked()) { return; } QDate thisDate(date()); DPopupFrame* const popup = new DPopupFrame(this); DatePickerYearSelector* const picker = new DatePickerYearSelector(date(), popup); picker->resize(picker->sizeHint()); picker->setYear(thisDate.year()); picker->selectAll(); popup->setMainWidget(picker); connect(picker, SIGNAL(closeMe(int)), popup, SLOT(close(int))); picker->setFocus(); if (popup->exec(d->selectYear->mapToGlobal(QPoint(0, d->selectMonth->height())))) { // We need to create a valid date in the year/month selected so we can find out how many // days are in the month. QDate newDate(picker->year(), thisDate.month(), 1); // If we have succeeded in creating a date in the new month, then try to create the new // date, checking we don't set a day after the last day of the month newDate = QDate(newDate.year(), newDate.month(), qMin(thisDate.day(), newDate.daysInMonth())); // Set the date, if it's invalid in any way then alert user and don't update if (! setDate(newDate)) { QApplication::beep(); } } delete popup; d->selectYear->setChecked(false); } void DDatePicker::uncheckYearSelector() { d->selectYear->setChecked(false); d->selectYear->update(); } void DDatePicker::changeEvent(QEvent* e) { if (e && e->type() == QEvent::EnabledChange) { if (isEnabled()) { d->table->setFocus(); } } } DDateTable *DDatePicker::dateTable() const { return d->table; } void DDatePicker::lineEnterPressed() { QString dateFormat = locale().dateFormat(QLocale::ShortFormat); if (!dateFormat.contains(QLatin1String("yyyy"))) { dateFormat.replace(QLatin1String("yy"), QLatin1String("yyyy")); } QDate newDate = QDate::fromString(d->line->text(), dateFormat); if (newDate.isValid()) { emit(dateEntered(newDate)); setDate(newDate); d->table->setFocus(); } else { QApplication::beep(); } } void DDatePicker::todayButtonClicked() { setDate(QDate::currentDate()); d->table->setFocus(); } QSize DDatePicker::sizeHint() const { return QWidget::sizeHint(); } void DDatePicker::setFontSize(int s) { QWidget* const buttons[] = { d->selectMonth, d->selectYear, }; const int NoOfButtons = sizeof(buttons) / sizeof(buttons[0]); int count; QFont font; QRect r; // ----- d->fontsize = s; for (count = 0; count < NoOfButtons; ++count) { font = buttons[count]->font(); font.setPointSize(s); buttons[count]->setFont(font); } d->table->setFontSize(s); QFontMetrics metrics(d->selectMonth->fontMetrics()); QString longestMonth; for (int i = 1;; ++i) { QString str = locale().standaloneMonthName(i, QLocale::LongFormat); if (str.isNull()) { break; } r = metrics.boundingRect(str); if (r.width() > d->maxMonthRect.width()) { d->maxMonthRect.setWidth(r.width()); longestMonth = str; } if (r.height() > d->maxMonthRect.height()) { d->maxMonthRect.setHeight(r.height()); } } QStyleOptionToolButton opt; opt.initFrom(d->selectMonth); opt.text = longestMonth; // stolen from QToolButton QSize textSize = metrics.size(Qt::TextShowMnemonic, longestMonth); textSize.setWidth(textSize.width() + metrics.width(QLatin1Char(' ')) * 2); int w = textSize.width(); int h = textSize.height(); opt.rect.setHeight(h); // PM_MenuButtonIndicator depends on the height QSize metricBound = style()->sizeFromContents(QStyle::CT_ToolButton, &opt, QSize(w, h), d->selectMonth) .expandedTo(QApplication::globalStrut()); d->selectMonth->setMinimumSize(metricBound); } int DDatePicker::fontSize() const { return d->fontsize; } void DDatePicker::setCloseButton(bool enable) { if (enable == (d->closeButton != 0L)) { return; } if (enable) { d->closeButton = new QToolButton(this); d->closeButton->setAutoRaise(true); const int spacingHint = style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); d->navigationLayout->addSpacing(spacingHint); d->navigationLayout->addWidget(d->closeButton); d->closeButton->setToolTip(i18nc("@action:button", "Close")); d->closeButton->setIcon(QIcon::fromTheme(QLatin1String("window-close"))); connect(d->closeButton, &QAbstractButton::clicked, topLevelWidget(), &QWidget::close); } else { delete d->closeButton; d->closeButton = 0L; } updateGeometry(); } bool DDatePicker::hasCloseButton() const { return (d->closeButton); } } // namespace Digikam diff --git a/core/app/date/ddatepicker_p.cpp b/core/app/date/ddatepicker_p.cpp index 3c0ca7a148..53a94001e9 100644 --- a/core/app/date/ddatepicker_p.cpp +++ b/core/app/date/ddatepicker_p.cpp @@ -1,223 +1,223 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 1997-04-21 * Description : A date selection widget. * * Copyright (C) 2011-2018 by Gilles Caulier * Copyright (C) 1997 by Tim D. Gilman * Copyright (C) 1998-2001 by Mirko Boehm * Copyright (C) 2007 by John Layt * * 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 "ddatepicker_p.h" // Qt includes #include #include // KDE includes #include namespace Digikam { DatePickerValidator::DatePickerValidator(DDatePicker* const parent) : QValidator(parent), m_picker(parent) { } QValidator::State DatePickerValidator::validate(QString& text, int&) const { QLocale::FormatType formats[] = { QLocale::LongFormat, QLocale::ShortFormat, QLocale::NarrowFormat }; QLocale locale = m_picker->locale(); - for (int i = 0; i < 3; i++) + for (int i = 0 ; i < 3 ; ++i) { QDate tmp = locale.toDate(text, formats[i]); if (tmp.isValid()) { return Acceptable; } } return QValidator::Intermediate; } // ------------------------------------------------------------------------------ // Week numbers are defined by ISO 8601 // See http://www.merlyn.demon.co.uk/weekinfo.htm for details DatePickerYearSelector::DatePickerYearSelector(const QDate& currentDate, QWidget* const parent) : QLineEdit(parent), val(new QIntValidator(this)), result(0) { oldDate = currentDate; setFont(QFontDatabase::systemFont(QFontDatabase::GeneralFont)); setFrame(false); // TODO: Find a way to get that from QLocale //val->setRange( calendar->year( calendar->earliestValidDate() ), // calendar->year( calendar->latestValidDate() ) ); setValidator(val); connect(this, &QLineEdit::returnPressed, this, &DatePickerYearSelector::yearEnteredSlot); } void DatePickerYearSelector::yearEnteredSlot() { bool ok; int newYear; // check if entered value is a number newYear = text().toInt(&ok); if (!ok) { QApplication::beep(); return; } // check if new year will lead to a valid date if (QDate(newYear, oldDate.month(), oldDate.day()).isValid()) { result = newYear; emit(closeMe(1)); } else { QApplication::beep(); } } int DatePickerYearSelector::year() const { return result; } void DatePickerYearSelector::setYear(int year) { setText(QString::number(year)); } // ------------------------------------------------------------------------------ DDatePicker::Private::Private(DDatePicker* const qq) : q(qq) { closeButton = 0; selectWeek = 0; todayButton = 0; navigationLayout = 0; yearForward = 0; yearBackward = 0; monthForward = 0; monthBackward = 0; selectMonth = 0; selectYear = 0; line = 0; val = 0; table = 0; fontsize = 0; } void DDatePicker::Private::fillWeeksCombo() { // every year can have a different number of weeks // it could be that we had 53,1..52 and now 1..53 which is the same number but different // so always fill with new values // We show all week numbers for all weeks between first day of year to last day of year // This of course can be a list like 53,1,2..52 const QDate thisDate = q->date(); const int thisYear = thisDate.year(); QDate day(thisDate.year(), 1, 1); const QDate lastDayOfYear = QDate(thisDate.year() + 1, 1, 1).addDays(-1); selectWeek->clear(); // Starting from the first day in the year, loop through the year a week at a time // adding an entry to the week combo for each week in the year for (; day.isValid() && day <= lastDayOfYear; day = day.addDays(7)) { // Get the ISO week number for the current day and what year that week is in // e.g. 1st day of this year may fall in week 53 of previous year int weekYear = thisYear; const int week = day.weekNumber(&weekYear); QString weekString = i18n("Week %1", week); // show that this is a week from a different year if (weekYear != thisYear) { weekString += QLatin1Char('*'); } // when the week is selected, go to the same weekday as the one // that is currently selected in the date table QDate targetDate = day.addDays(thisDate.dayOfWeek() - day.dayOfWeek()); selectWeek->addItem(weekString, targetDate); // make sure that the week of the lastDayOfYear is always inserted: in Chinese calendar // system, this is not always the case if (day < lastDayOfYear && day.daysTo(lastDayOfYear) < 7 && lastDayOfYear.weekNumber() != day.weekNumber()) { day = lastDayOfYear.addDays(-7); } } } QDate DDatePicker::Private::validDateInYearMonth(int year, int month) { QDate newDate; // Try to create a valid date in this year and month // First try the first of the month, then try last of month if (QDate(year, month, 1).isValid()) { newDate = QDate(year, month, 1); } else if (QDate(year, month + 1, 1).isValid()) { newDate = QDate(year, month + 1, 1).addDays(-1); } else { newDate = QDate::fromJulianDay(0); } return newDate; } } // namespace Digikam diff --git a/core/app/items/views/digikamitemview.cpp b/core/app/items/views/digikamitemview.cpp index f4a0128f11..f447b5d0bb 100644 --- a/core/app/items/views/digikamitemview.cpp +++ b/core/app/items/views/digikamitemview.cpp @@ -1,600 +1,600 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2009-04-24 * Description : Qt model-view for items * * Copyright (C) 2009-2011 by Marcel Wiesweg * Copyright (C) 2009-2018 by Gilles Caulier * Copyright (C) 2011 by Andi Clemens * Copyright (C) 2013 by Michael G. Hansen * Copyright (C) 2014 by Mohamed_Anwer * Copyright (C) 2017 by Simon Frei * * 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 "digikamitemview.h" #include "digikamitemview_p.h" // Qt includes #include #include #include #include #include #include // Local includes #include "digikam_debug.h" #include "albummanager.h" #include "coredb.h" #include "coredboperationgroup.h" #include "advancedrenamedialog.h" #include "advancedrenameprocessdialog.h" #include "applicationsettings.h" #include "assignnameoverlay.h" #include "contextmenuhelper.h" #include "coredbaccess.h" #include "ddragobjects.h" #include "digikamapp.h" #include "digikamitemdelegate.h" #include "itemfacedelegate.h" #include "dio.h" #include "groupindicatoroverlay.h" #include "itemalbumfiltermodel.h" #include "itemalbummodel.h" #include "itemdragdrop.h" #include "itemratingoverlay.h" #include "itemfullscreenoverlay.h" #include "itemcoordinatesoverlay.h" #include "tagslineeditoverlay.h" #include "itemviewutilities.h" #include "imagewindow.h" #include "fileactionmngr.h" #include "fileactionprogress.h" #include "thumbnailloadthread.h" #include "tagregion.h" #include "addtagslineedit.h" #include "facerejectionoverlay.h" #include "facetagsiface.h" namespace Digikam { DigikamItemView::DigikamItemView(QWidget* const parent) : ItemCategorizedView(parent), d(new Private(this)) { installDefaultModels(); d->editPipeline.plugDatabaseEditor(); d->editPipeline.plugTrainer(); d->editPipeline.construct(); connect(&d->editPipeline, SIGNAL(scheduled()), this, SLOT(slotInitProgressIndicator())); d->normalDelegate = new DigikamItemDelegate(this); d->faceDelegate = new ItemFaceDelegate(this); setItemDelegate(d->normalDelegate); setSpacing(10); ApplicationSettings* const settings = ApplicationSettings::instance(); imageFilterModel()->setCategorizationMode(ItemSortSettings::CategoryByAlbum); imageAlbumModel()->setThumbnailLoadThread(ThumbnailLoadThread::defaultIconViewThread()); setThumbnailSize(ThumbnailSize(settings->getDefaultIconSize())); imageAlbumModel()->setPreloadThumbnails(true); imageModel()->setDragDropHandler(new ItemDragDropHandler(imageModel())); setDragEnabled(true); setAcceptDrops(true); setDropIndicatorShown(false); setToolTipEnabled(settings->showToolTipsIsValid()); imageFilterModel()->setStringTypeNatural(settings->isStringTypeNatural()); imageFilterModel()->setSortRole((ItemSortSettings::SortRole)settings->getImageSortOrder()); imageFilterModel()->setSortOrder((ItemSortSettings::SortOrder)settings->getImageSorting()); imageFilterModel()->setCategorizationMode((ItemSortSettings::CategorizationMode)settings->getImageSeparationMode()); imageFilterModel()->setCategorizationSortOrder((ItemSortSettings::SortOrder) settings->getImageSeparationSortOrder()); // selection overlay addSelectionOverlay(d->normalDelegate); addSelectionOverlay(d->faceDelegate); // rotation overlays d->rotateLeftOverlay = ItemRotateOverlay::left(this); d->rotateRightOverlay = ItemRotateOverlay::right(this); d->fullscreenOverlay = ItemFullScreenOverlay::instance(this); d->updateOverlays(); // rating overlay ItemRatingOverlay* const ratingOverlay = new ItemRatingOverlay(this); addOverlay(ratingOverlay); // face overlays // NOTE: order to plug this overlay is important, else rejection cant be suitable (see bug #324759). addAssignNameOverlay(d->faceDelegate); addRejectionOverlay(d->faceDelegate); GroupIndicatorOverlay* const groupOverlay = new GroupIndicatorOverlay(this); addOverlay(groupOverlay); addOverlay(new ItemCoordinatesOverlay(this)); connect(ratingOverlay, SIGNAL(ratingEdited(QList,int)), this, SLOT(assignRating(QList,int))); connect(groupOverlay, SIGNAL(toggleGroupOpen(QModelIndex)), this, SLOT(groupIndicatorClicked(QModelIndex))); connect(groupOverlay, SIGNAL(showButtonContextMenu(QModelIndex,QContextMenuEvent*)), this, SLOT(showGroupContextMenu(QModelIndex,QContextMenuEvent*))); d->utilities = new ItemViewUtilities(this); connect(imageModel()->dragDropHandler(), SIGNAL(assignTags(QList,QList)), FileActionMngr::instance(), SLOT(assignTags(QList,QList))); connect(imageModel()->dragDropHandler(), SIGNAL(addToGroup(ItemInfo,QList)), FileActionMngr::instance(), SLOT(addToGroup(ItemInfo,QList))); connect(imageModel()->dragDropHandler(), SIGNAL(dragDropSort(ItemInfo,QList)), this, SLOT(dragDropSort(ItemInfo,QList))); connect(d->utilities, SIGNAL(editorCurrentUrlChanged(QUrl)), this, SLOT(setCurrentUrlWhenAvailable(QUrl))); connect(settings, SIGNAL(setupChanged()), this, SLOT(slotSetupChanged())); slotSetupChanged(); } DigikamItemView::~DigikamItemView() { delete d; } ItemViewUtilities* DigikamItemView::utilities() const { return d->utilities; } void DigikamItemView::setThumbnailSize(const ThumbnailSize& size) { imageThumbnailModel()->setPreloadThumbnailSize(size); ItemCategorizedView::setThumbnailSize(size); } ItemInfoList DigikamItemView::allItemInfos(bool grouping) const { if (grouping) { return resolveGrouping(ItemCategorizedView::allItemInfos()); } return ItemCategorizedView::allItemInfos(); } ItemInfoList DigikamItemView::selectedItemInfos(bool grouping) const { if (grouping) { return resolveGrouping(ItemCategorizedView::selectedItemInfos()); } return ItemCategorizedView::selectedItemInfos(); } ItemInfoList DigikamItemView::selectedItemInfosCurrentFirst(bool grouping) const { if (grouping) { return resolveGrouping(ItemCategorizedView::selectedItemInfosCurrentFirst()); } return ItemCategorizedView::selectedItemInfosCurrentFirst(); } void DigikamItemView::dragDropSort(const ItemInfo& pick, const QList& infos) { if (pick.isNull() || infos.isEmpty()) { return; } ItemInfoList infoList = allItemInfos(false); qlonglong counter = pick.manualOrder(); bool order = (ApplicationSettings::instance()-> getImageSorting() == Qt::AscendingOrder); bool found = false; QApplication::setOverrideCursor(Qt::WaitCursor); CoreDbOperationGroup group; group.setMaximumTime(200); foreach (ItemInfo info, infoList) { if (!found && info.id() == pick.id()) { foreach (ItemInfo dropInfo, infos) { dropInfo.setManualOrder(counter); counter += (order ? 1 : -1); } info.setManualOrder(counter); found = true; } else if (found && !infos.contains(info)) { if (( order && info.manualOrder() > counter) || (!order && info.manualOrder() < counter)) { break; } counter += (order ? 100 : -100); info.setManualOrder(counter); } group.allowLift(); } QApplication::restoreOverrideCursor(); imageFilterModel()->invalidate(); } bool DigikamItemView::allNeedGroupResolving(const ApplicationSettings::OperationType type) const { return needGroupResolving(type, allItemInfos()); } bool DigikamItemView::selectedNeedGroupResolving(const ApplicationSettings::OperationType type) const { return needGroupResolving(type, selectedItemInfos()); } int DigikamItemView::fitToWidthIcons() { return delegate()->calculatethumbSizeToFit(viewport()->size().width()); } void DigikamItemView::slotSetupChanged() { imageFilterModel()->setStringTypeNatural(ApplicationSettings::instance()->isStringTypeNatural()); setToolTipEnabled(ApplicationSettings::instance()->showToolTipsIsValid()); setFont(ApplicationSettings::instance()->getIconViewFont()); d->updateOverlays(); ItemCategorizedView::slotSetupChanged(); } bool DigikamItemView::hasHiddenGroupedImages(const ItemInfo& info) const { return info.hasGroupedImages() && !imageFilterModel()->isGroupOpen(info.id()); } ItemInfoList DigikamItemView::imageInfos(const QList& indexes, ApplicationSettings::OperationType type) const { ItemInfoList infos = ItemCategorizedView::imageInfos(indexes); if (needGroupResolving(type, infos)) { return resolveGrouping(infos); } return infos; } void DigikamItemView::setFaceMode(bool on) { d->faceMode = on; if (on) { // See ItemLister, which creates a search the implements listing tag in the ioslave imageAlbumModel()->setSpecialTagListing(QLatin1String("faces")); setItemDelegate(d->faceDelegate); // grouping is not very much compatible with faces imageFilterModel()->setAllGroupsOpen(true); } else { imageAlbumModel()->setSpecialTagListing(QString()); setItemDelegate(d->normalDelegate); imageFilterModel()->setAllGroupsOpen(false); } } void DigikamItemView::addRejectionOverlay(ItemDelegate* delegate) { FaceRejectionOverlay* const rejectionOverlay = new FaceRejectionOverlay(this); connect(rejectionOverlay, SIGNAL(rejectFaces(QList)), this, SLOT(removeFaces(QList))); addOverlay(rejectionOverlay, delegate); } /* void DigikamItemView::addTagEditOverlay(ItemDelegate* delegate) { TagsLineEditOverlay* tagOverlay = new TagsLineEditOverlay(this); connect(tagOverlay, SIGNAL(tagEdited(QModelIndex,QString)), this, SLOT(assignTag(QModelIndex,QString))); addOverlay(tagOverlay, delegate); } */ void DigikamItemView::addAssignNameOverlay(ItemDelegate* delegate) { AssignNameOverlay* const nameOverlay = new AssignNameOverlay(this); addOverlay(nameOverlay, delegate); connect(nameOverlay, SIGNAL(confirmFaces(QList,int)), this, SLOT(confirmFaces(QList,int))); connect(nameOverlay, SIGNAL(removeFaces(QList)), this, SLOT(removeFaces(QList))); } void DigikamItemView::confirmFaces(const QList& indexes, int tagId) { QList infos; QList faces; QList sourceIndexes; // fast-remove in the "unknown person" view bool needFastRemove = false; if (imageAlbumModel()->currentAlbums().size() == 1) { needFastRemove = d->faceMode && (tagId != imageAlbumModel()->currentAlbums().first()->id()); } foreach (const QModelIndex& index, indexes) { infos << ItemModel::retrieveItemInfo(index); faces << d->faceDelegate->face(index); if (needFastRemove) { sourceIndexes << imageSortFilterModel()->mapToSourceItemModel(index); } } imageAlbumModel()->removeIndexes(sourceIndexes); - for (int i = 0 ; i < infos.size() ; i++) + for (int i = 0 ; i < infos.size() ; ++i) { d->editPipeline.confirm(infos[i], faces[i], tagId); } } void DigikamItemView::removeFaces(const QList& indexes) { QList infos; QList faces; QList sourceIndexes; foreach (const QModelIndex& index, indexes) { infos << ItemModel::retrieveItemInfo(index); faces << d->faceDelegate->face(index); sourceIndexes << imageSortFilterModel()->mapToSourceItemModel(index); } imageAlbumModel()->removeIndexes(sourceIndexes); - for (int i = 0 ; i < infos.size() ; i++) + for (int i = 0 ; i < infos.size() ; ++i) { d->editPipeline.remove(infos[i], faces[i]); } } void DigikamItemView::activated(const ItemInfo& info, Qt::KeyboardModifiers modifiers) { if (info.isNull()) { return; } if (modifiers != Qt::MetaModifier) { if (ApplicationSettings::instance()->getItemLeftClickAction() == ApplicationSettings::ShowPreview) { emit previewRequested(info); } else { openFile(info); } } else { d->utilities->openInfosWithDefaultApplication(QList() << info); } } void DigikamItemView::showContextMenuOnInfo(QContextMenuEvent* event, const ItemInfo& info) { emit signalShowContextMenuOnInfo(event, info, QList(), imageFilterModel()); } void DigikamItemView::showGroupContextMenu(const QModelIndex& index, QContextMenuEvent* event) { Q_UNUSED(index); emit signalShowGroupContextMenu(event, selectedItemInfosCurrentFirst(), imageFilterModel()); } void DigikamItemView::showContextMenu(QContextMenuEvent* event) { emit signalShowContextMenu(event); } void DigikamItemView::openFile(const ItemInfo& info) { d->utilities->openInfos(info, allItemInfos(), currentAlbum()); } void DigikamItemView::deleteSelected(const ItemViewUtilities::DeleteMode deleteMode) { ItemInfoList imageInfoList = selectedItemInfos(true); if (d->utilities->deleteImages(imageInfoList, deleteMode)) { awayFromSelection(); } } void DigikamItemView::deleteSelectedDirectly(const ItemViewUtilities::DeleteMode deleteMode) { ItemInfoList imageInfoList = selectedItemInfos(true); d->utilities->deleteImagesDirectly(imageInfoList, deleteMode); awayFromSelection(); } void DigikamItemView::assignRating(const QList& indexes, int rating) { ItemInfoList infos = imageInfos(indexes, ApplicationSettings::Metadata); FileActionMngr::instance()->assignRating(infos, rating); } void DigikamItemView::groupIndicatorClicked(const QModelIndex& index) { ItemInfo info = imageFilterModel()->imageInfo(index); if (info.isNull()) { return; } setCurrentIndex(index); imageFilterModel()->toggleGroupOpen(info.id()); imageAlbumModel()->ensureHasGroupedImages(info); } void DigikamItemView::rename() { ItemInfoList infos = selectedItemInfos(); if (needGroupResolving(ApplicationSettings::Rename, infos)) { infos = resolveGrouping(infos); } QList urls = infos.toImageUrlList(); bool loop = false; NewNamesList newNamesList; do { qCDebug(DIGIKAM_GENERAL_LOG) << "Selected URLs to rename: " << urls; QPointer dlg = new AdvancedRenameDialog(this); dlg->slotAddImages(urls); if (dlg->exec() != QDialog::Accepted) { delete dlg; break; } if (!loop) { QUrl nextUrl = nextInOrder(infos.last(), 1).fileUrl(); setCurrentUrl(nextUrl); loop = true; } newNamesList = dlg->newNames(); delete dlg; setFocus(); qApp->processEvents(); if (!newNamesList.isEmpty()) { QPointer dlg = new AdvancedRenameProcessDialog(newNamesList, this); dlg->exec(); imageFilterModel()->invalidate(); urls = dlg->failedUrls(); delete dlg; } } while (!urls.isEmpty() && !newNamesList.isEmpty()); } void DigikamItemView::slotRotateLeft(const QList& indexes) { ItemInfoList infos = imageInfos(indexes, ApplicationSettings::Metadata); FileActionMngr::instance()->transform(infos, MetaEngineRotation::Rotate270); } void DigikamItemView::slotRotateRight(const QList& indexes) { ItemInfoList infos = imageInfos(indexes, ApplicationSettings::Metadata); FileActionMngr::instance()->transform(infos, MetaEngineRotation::Rotate90); } void DigikamItemView::slotFullscreen(const QList& indexes) { QList infos = imageInfos(indexes, ApplicationSettings::Slideshow); if (infos.isEmpty()) { return; } // Just fullscreen the first. const ItemInfo& info = infos.at(0); emit fullscreenRequested(info); } void DigikamItemView::slotInitProgressIndicator() { if (!ProgressManager::instance()->findItembyId(QLatin1String("FaceActionProgress"))) { FileActionProgress* const item = new FileActionProgress(QLatin1String("FaceActionProgress")); connect(&d->editPipeline, SIGNAL(started(QString)), item, SLOT(slotProgressStatus(QString))); connect(&d->editPipeline, SIGNAL(progressValueChanged(float)), item, SLOT(slotProgressValue(float))); connect(&d->editPipeline, SIGNAL(finished()), item, SLOT(slotCompleted())); } } } // namespace Digikam diff --git a/core/libs/album/treeview/labelstreeview.cpp b/core/libs/album/treeview/labelstreeview.cpp index 2593f8ca41..4f8fea5b7e 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 * http://www.digikam.org * * Date : 2014-05-17 * Description : Album Labels Tree View. * * Copyright (C) 2014-2015 by Mohamed_Anwer * Copyright (C) 2014-2018 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) selectedRatings << indexFromItem(item).row(); 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) selectedRatings << indexFromItem(item).row(); 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++) + 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) { 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/coredb/coredb.cpp b/core/libs/database/coredb/coredb.cpp index 82891a7779..670c20a0c7 100644 --- a/core/libs/database/coredb/coredb.cpp +++ b/core/libs/database/coredb/coredb.cpp @@ -1,5021 +1,5021 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2004-06-18 * Description : Core database interface. * * Copyright (C) 2004-2005 by Renchi Raju * Copyright (C) 2006-2018 by Gilles Caulier * Copyright (C) 2006-2012 by Marcel Wiesweg * Copyright (C) 2012 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 "coredb.h" // C ANSI includes extern "C" { #include } // C++ includes #include #include #include // Qt includes #include #include #include #include // KDE includes #include #include // Local includes #include "digikam_debug.h" #include "coredbbackend.h" #include "collectionmanager.h" #include "collectionlocation.h" #include "dbengineactiontype.h" #include "tagscache.h" #include "album.h" namespace Digikam { class Q_DECL_HIDDEN CoreDB::Private { public: explicit Private() : db(0), uniqueHashVersion(-1) { } static const QString configGroupName; static const QString configRecentlyUsedTags; CoreDbBackend* db; QList recentlyAssignedTags; int uniqueHashVersion; public: QString constructRelatedImagesSQL(bool fromOrTo, DatabaseRelation::Type type, bool boolean); QList execRelatedImagesQuery(DbEngineSqlQuery& query, qlonglong id, DatabaseRelation::Type type); }; const QString CoreDB::Private::configGroupName(QLatin1String("CoreDB Settings")); const QString CoreDB::Private::configRecentlyUsedTags(QLatin1String("Recently Used Tags")); QString CoreDB::Private::constructRelatedImagesSQL(bool fromOrTo, DatabaseRelation::Type type, bool boolean) { QString sql; if (fromOrTo) { sql = QString::fromUtf8("SELECT object FROM ImageRelations " "INNER JOIN Images ON ImageRelations.object=Images.id " "WHERE subject=? %1 AND status!=3 %2;"); } else { sql = QString::fromUtf8("SELECT subject FROM ImageRelations " "INNER JOIN Images ON ImageRelations.subject=Images.id " "WHERE object=? %1 AND status!=3 %2;"); } if (type != DatabaseRelation::UndefinedType) { sql = sql.arg(QString::fromUtf8("AND type=?")); } else { sql = sql.arg(QString()); } if (boolean) { sql = sql.arg(QString::fromUtf8("LIMIT 1")); } else { sql = sql.arg(QString()); } return sql; } QList CoreDB::Private::execRelatedImagesQuery(DbEngineSqlQuery& query, qlonglong id, DatabaseRelation::Type type) { QVariantList values; if (type == DatabaseRelation::UndefinedType) { db->execSql(query, id, &values); } else { db->execSql(query, id, type, &values); } QList imageIds; if (values.isEmpty()) { return imageIds; } for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ; ++it) { imageIds << (*it).toInt(); } return imageIds; } // -------------------------------------------------------- CoreDB::CoreDB(CoreDbBackend* const backend) : d(new Private) { d->db = backend; readSettings(); } CoreDB::~CoreDB() { writeSettings(); delete d; } QList CoreDB::getAlbumRoots() { QList list; QList values; d->db->execSql(QString::fromUtf8("SELECT id, label, status, type, identifier, specificPath FROM AlbumRoots;"), &values); for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ;) { AlbumRootInfo info; info.id = (*it).toInt(); ++it; info.label = (*it).toString(); ++it; info.status = (*it).toInt(); ++it; info.type = (AlbumRoot::Type)(*it).toInt(); ++it; info.identifier = (*it).toString(); ++it; info.specificPath = (*it).toString(); ++it; list << info; } return list; } int CoreDB::addAlbumRoot(AlbumRoot::Type type, const QString& identifier, const QString& specificPath, const QString& label) { QVariant id; d->db->execSql(QString::fromUtf8("REPLACE INTO AlbumRoots (type, label, status, identifier, specificPath) " "VALUES(?, ?, 0, ?, ?);"), (int)type, label, identifier, specificPath, 0, &id); d->db->recordChangeset(AlbumRootChangeset(id.toInt(), AlbumRootChangeset::Added)); return id.toInt(); } void CoreDB::deleteAlbumRoot(int rootId) { d->db->execSql(QString::fromUtf8("DELETE FROM AlbumRoots WHERE id=?;"), rootId); QMap parameters; parameters.insert(QLatin1String(":albumRoot"), rootId); if (BdEngineBackend::NoErrors != d->db->execDBAction(d->db->getDBAction(QLatin1String("deleteAlbumRoot")), parameters)) { return; } d->db->recordChangeset(AlbumRootChangeset(rootId, AlbumRootChangeset::Deleted)); } void CoreDB::migrateAlbumRoot(int rootId, const QString& identifier) { d->db->execSql(QString::fromUtf8("UPDATE AlbumRoots SET identifier=? WHERE id=?;"), identifier, rootId); d->db->recordChangeset(AlbumRootChangeset(rootId, AlbumRootChangeset::PropertiesChanged)); } void CoreDB::setAlbumRootLabel(int rootId, const QString& newLabel) { d->db->execSql(QString::fromUtf8("UPDATE AlbumRoots SET label=? WHERE id=?;"), newLabel, rootId); d->db->recordChangeset(AlbumRootChangeset(rootId, AlbumRootChangeset::PropertiesChanged)); } void CoreDB::changeAlbumRootType(int rootId, AlbumRoot::Type newType) { d->db->execSql(QString::fromUtf8("UPDATE AlbumRoots SET type=? WHERE id=?;"), (int)newType, rootId); d->db->recordChangeset(AlbumRootChangeset(rootId, AlbumRootChangeset::PropertiesChanged)); } AlbumInfo::List CoreDB::scanAlbums() { AlbumInfo::List aList; QList values; d->db->execSql(QString::fromUtf8("SELECT albumRoot, id, relativePath, date, caption, collection, icon " "FROM Albums WHERE albumRoot != 0;"), // exclude stale albums &values); QString iconAlbumUrl, iconName; for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ;) { AlbumInfo info; info.albumRootId = (*it).toInt(); ++it; info.id = (*it).toInt(); ++it; info.relativePath = (*it).toString(); ++it; info.date = (*it).toDate(); ++it; info.caption = (*it).toString(); ++it; info.category = (*it).toString(); ++it; info.iconId = (*it).toLongLong(); ++it; aList.append(info); } return aList; } TagInfo::List CoreDB::scanTags() { TagInfo::List tList; QList values; d->db->execSql(QString::fromUtf8("SELECT id, pid, name, icon, iconkde FROM Tags;"), &values); for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ; ) { TagInfo info; info.id = (*it).toInt(); ++it; info.pid = (*it).toInt(); ++it; info.name = (*it).toString(); ++it; info.iconId = (*it).toLongLong(); ++it; info.icon = (*it).toString(); ++it; tList.append(info); } return tList; } TagInfo CoreDB::getTagInfo(int tagId) { QList values; d->db->execSql(QString::fromUtf8("SELECT id, pid, name, icon, iconkde WHERE id=? FROM Tags;"), tagId, &values); TagInfo info; if (!values.isEmpty() && values.size() == 5) { QList::const_iterator it = values.constBegin(); info.id = (*it).toInt(); ++it; info.pid = (*it).toInt(); ++it; info.name = (*it).toString(); ++it; info.iconId = (*it).toLongLong(); ++it; info.icon = (*it).toString(); ++it; } return info; } SearchInfo::List CoreDB::scanSearches() { SearchInfo::List searchList; QList values; d->db->execSql(QString::fromUtf8("SELECT id, type, name, query FROM Searches;"), &values); for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ;) { SearchInfo info; info.id = (*it).toInt(); ++it; info.type = (DatabaseSearch::Type)(*it).toInt(); ++it; info.name = (*it).toString(); ++it; info.query = (*it).toString(); ++it; searchList.append(info); } return searchList; } QList CoreDB::getAlbumShortInfos() { QList values; d->db->execSql(QString::fromUtf8("SELECT id, relativePath, albumRoot FROM Albums ORDER BY id;"), &values); QList albumList; for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ;) { AlbumShortInfo info; info.id = (*it).toInt(); ++it; info.relativePath = (*it).toString(); ++it; info.albumRootId = (*it).toInt(); ++it; albumList << info; } return albumList; } QList CoreDB::getTagShortInfos() { QList values; d->db->execSql(QString::fromUtf8("SELECT id, pid, name FROM Tags ORDER BY id;"), &values); QList tagList; for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ;) { TagShortInfo info; info.id = (*it).toInt(); ++it; info.pid = (*it).toInt(); ++it; info.name = (*it).toString(); ++it; tagList << info; } return tagList; } /* QStringList CoreDB::getSubalbumsForPath(const QString& albumRoot, const QString& path, bool onlyDirectSubalbums) { CollectionLocation location = CollectionManager::instance()->locationForAlbumRootPath(albumRoot); if (location.isNull()) return QStringList(); QString subURL = path; if (!path.endsWith(QLatin1String("/"))) subURL += QLatin1Char('/'); subURL = (subURL); QList values; if (onlyDirectSubalbums) { d->db->execSql( QString::fromUtf8("SELECT relativePath FROM Albums WHERE albumRoot=? AND relativePath LIKE '") + subURL + QString::fromUtf8("%' ") + QString::fromUtf8("AND relativePath NOT LIKE '") + subURL + QString::fromUtf8("%/%'; "), location.id(), &values ); } else { d->db->execSql( QString::fromUtf8("SELECT relativePath FROM Albums WHERE albumRoot=? AND relativePath LIKE '") + subURL + QString::fromUtf8("%'; "), location.id(), &values ); } QStringList subalbums; QString albumRootPath = location.albumRootPath(); for (QList::iterator it = values.begin(); it != values.end(); ++it) subalbums << albumRootPath + it->toString(); return subalbums; } */ /* int CoreDB::addAlbum(const QString& albumRoot, const QString& relativePath, const QString& caption, const QDate& date, const QString& collection) { CollectionLocation location = CollectionManager::instance()->locationForAlbumRootPath(albumRoot); if (location.isNull()) return -1; return addAlbum(location.id(), relativePath, caption, date, collection); } */ int CoreDB::addAlbum(int albumRootId, const QString& relativePath, const QString& caption, const QDate& date, const QString& collection) { QVariant id; QList boundValues; boundValues << albumRootId << relativePath << date << caption << collection; d->db->execSql(QString::fromUtf8("REPLACE INTO Albums (albumRoot, relativePath, date, caption, collection) " "VALUES(?, ?, ?, ?, ?);"), boundValues, 0, &id); d->db->recordChangeset(AlbumChangeset(id.toInt(), AlbumChangeset::Added)); return id.toInt(); } void CoreDB::setAlbumCaption(int albumID, const QString& caption) { d->db->execSql(QString::fromUtf8("UPDATE Albums SET caption=? WHERE id=?;"), caption, albumID); d->db->recordChangeset(AlbumChangeset(albumID, AlbumChangeset::PropertiesChanged)); } void CoreDB::setAlbumCategory(int albumID, const QString& category) { // TODO : change "collection" property in DB ALbum table to "category" d->db->execSql(QString::fromUtf8("UPDATE Albums SET collection=? WHERE id=?;"), category, albumID); d->db->recordChangeset(AlbumChangeset(albumID, AlbumChangeset::PropertiesChanged)); } void CoreDB::setAlbumDate(int albumID, const QDate& date) { d->db->execSql(QString::fromUtf8("UPDATE Albums SET date=? WHERE id=?;"), date, albumID); d->db->recordChangeset(AlbumChangeset(albumID, AlbumChangeset::PropertiesChanged)); } void CoreDB::setAlbumIcon(int albumID, qlonglong iconID) { if (iconID == 0) { d->db->execSql(QString::fromUtf8("UPDATE Albums SET icon=NULL WHERE id=?;"), albumID); } else { d->db->execSql(QString::fromUtf8("UPDATE Albums SET icon=? WHERE id=?;"), iconID, albumID); } d->db->recordChangeset(AlbumChangeset(albumID, AlbumChangeset::PropertiesChanged)); } void CoreDB::deleteAlbum(int albumID) { QMap parameters; parameters.insert(QLatin1String(":albumId"), albumID); if (BdEngineBackend::NoErrors != d->db->execDBAction(d->db->getDBAction(QLatin1String("deleteAlbumID")), parameters)) { return; } d->db->recordChangeset(AlbumChangeset(albumID, AlbumChangeset::Deleted)); } void CoreDB::makeStaleAlbum(int albumID) { // We need to work around the table constraint, no we want to delete older stale albums with // the same relativePath, and adjust relativePaths depending on albumRoot. QList values; // retrieve information d->db->execSql(QString::fromUtf8("SELECT albumRoot, relativePath FROM Albums WHERE id=?;"), albumID, &values); if (values.isEmpty()) { return; } // prepend albumRootId to relativePath. relativePath is unused and officially undefined after this call. QString newRelativePath = values.at(0).toString() + QLatin1Char('-') + values.at(1).toString(); // delete older stale albums QMap parameters; parameters.insert(QLatin1String(":albumRoot"), 0); parameters.insert(QLatin1String(":relativePath"), newRelativePath); if (BdEngineBackend::NoErrors != d->db->execDBAction(d->db->getDBAction(QLatin1String("deleteAlbumRootPath")), parameters)) { return; } // now do our update d->db->setForeignKeyChecks(false); d->db->execSql(QString::fromUtf8("UPDATE Albums SET albumRoot=0, relativePath=? WHERE id=?;"), newRelativePath, albumID); // for now, we make no distinction to deleteAlbums wrt to changeset d->db->recordChangeset(AlbumChangeset(albumID, AlbumChangeset::Deleted)); d->db->setForeignKeyChecks(true); } void CoreDB::deleteStaleAlbums() { QMap parameters; parameters.insert(QLatin1String(":albumRoot"), 0); if (BdEngineBackend::NoErrors != d->db->execDBAction(d->db->getDBAction(QLatin1String("deleteAlbumRoot")), parameters)) { return; } // deliberately no changeset here, is done above } int CoreDB::addTag(int parentTagID, const QString& name, const QString& iconKDE, qlonglong iconID) { QVariant id; QMap parameters; parameters.insert(QLatin1String(":tagPID"), parentTagID); parameters.insert(QLatin1String(":tagname"), name); if (BdEngineBackend::NoErrors != d->db->execDBAction(d->db->getDBAction(QLatin1String("InsertTag")), parameters, 0 , &id)) { return -1; } if (!iconKDE.isEmpty()) { d->db->execSql(QString::fromUtf8("UPDATE Tags SET iconkde=? WHERE id=?;"), iconKDE, id.toInt()); } else if (iconID == 0) { d->db->execSql(QString::fromUtf8("UPDATE Tags SET icon=NULL WHERE id=?;"), id.toInt()); } else { d->db->execSql(QString::fromUtf8("UPDATE Tags SET icon=? WHERE id=?;"), iconID, id.toInt()); } d->db->recordChangeset(TagChangeset(id.toInt(), TagChangeset::Added)); return id.toInt(); } void CoreDB::deleteTag(int tagID) { /* QString("DELETE FROM Tags WHERE id=?;"), tagID */ QMap bindingMap; bindingMap.insert(QLatin1String(":tagID"), tagID); d->db->execDBAction(d->db->getDBAction(QLatin1String("DeleteTag")), bindingMap); d->db->recordChangeset(TagChangeset(tagID, TagChangeset::Deleted)); } void CoreDB::setTagIcon(int tagID, const QString& iconKDE, qlonglong iconID) { int _iconID = iconKDE.isEmpty() ? iconID : 0; QString _iconKDE = iconKDE; if (iconKDE.isEmpty() || iconKDE.toLower() == QLatin1String("tag")) { _iconKDE.clear(); } if (_iconID == 0) { d->db->execSql(QString::fromUtf8("UPDATE Tags SET iconkde=?, icon=NULL WHERE id=?;"), _iconKDE, tagID); } else { d->db->execSql(QString::fromUtf8("UPDATE Tags SET iconkde=?, icon=? WHERE id=?;"), _iconKDE, _iconID, tagID); } d->db->recordChangeset(TagChangeset(tagID, TagChangeset::IconChanged)); } void CoreDB::setTagParentID(int tagID, int newParentTagID) { if (d->db->databaseType() == BdEngineBackend::DbType::SQLite) { d->db->execSql(QString::fromUtf8("UPDATE OR REPLACE Tags SET pid=? WHERE id=?;"), newParentTagID, tagID); } else { d->db->execSql(QString::fromUtf8("UPDATE Tags SET pid=? WHERE id=?;"), newParentTagID, tagID); // NOTE: Update the Mysql TagsTree table which is used only in some search SQL queries (See lft/rgt tag ID properties). // In SQlite, it is nicely maintained by Triggers. // With MySQL, this did not work for some reason, and we patch a tree structure mimics in a different way. QMap bindingMap; bindingMap.insert(QLatin1String(":tagID"), tagID); bindingMap.insert(QLatin1String(":newTagPID"), newParentTagID); d->db->execDBAction(d->db->getDBAction(QLatin1String("MoveTagTree")), bindingMap); } d->db->recordChangeset(TagChangeset(tagID, TagChangeset::Reparented)); } QList CoreDB::getTagProperties(int tagId) { QList values; d->db->execSql(QString::fromUtf8("SELECT property, value FROM TagProperties WHERE tagid=?;"), tagId, &values); QList properties; if (values.isEmpty()) { return properties; } for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ;) { TagProperty property; property.tagId = tagId; property.property = (*it).toString(); ++it; property.value = (*it).toString(); ++it; properties << property; } return properties; } QList CoreDB::getTagProperties(const QString& property) { QList values; d->db->execSql(QString::fromUtf8("SELECT tagid, property, value FROM TagProperties WHERE property=?;"), property, &values); QList properties; if (values.isEmpty()) { return properties; } for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ;) { TagProperty property; property.tagId = (*it).toInt(); ++it; property.property = (*it).toString(); ++it; property.value = (*it).toString(); ++it; properties << property; } return properties; } QList CoreDB::getTagProperties() { QList values; d->db->execSql(QString::fromUtf8("SELECT tagid, property, value FROM TagProperties ORDER BY tagid, property;"), &values); QList properties; if (values.isEmpty()) { return properties; } for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ;) { TagProperty property; property.tagId = (*it).toInt(); ++it; property.property = (*it).toString(); ++it; property.value = (*it).toString(); ++it; properties << property; } return properties; } QList< int > CoreDB::getTagsWithProperty(const QString& property) { QList values; d->db->execSql(QString::fromUtf8("SELECT DISTINCT tagid FROM TagProperties WHERE property=?;"), property, &values); QList tagIds; foreach (const QVariant& var, values) { tagIds << var.toInt(); } return tagIds; } void CoreDB::addTagProperty(int tagId, const QString& property, const QString& value) { d->db->execSql(QString::fromUtf8("INSERT INTO TagProperties (tagid, property, value) VALUES(?, ?, ?);"), tagId, property, value); d->db->recordChangeset(TagChangeset(tagId, TagChangeset::PropertiesChanged)); } void CoreDB::addTagProperty(const TagProperty& property) { addTagProperty(property.tagId, property.property, property.value); } void CoreDB::removeTagProperties(int tagId, const QString& property, const QString& value) { if (property.isNull()) { d->db->execSql(QString::fromUtf8("DELETE FROM TagProperties WHERE tagid=?;"), tagId); } else if (value.isNull()) { d->db->execSql(QString::fromUtf8("DELETE FROM TagProperties WHERE tagid=? AND property=?;"), tagId, property); } else { d->db->execSql(QString::fromUtf8("DELETE FROM TagProperties WHERE tagid=? AND property=? AND value=?;"), tagId, property, value); } d->db->recordChangeset(TagChangeset(tagId, TagChangeset::PropertiesChanged)); } int CoreDB::addSearch(DatabaseSearch::Type type, const QString& name, const QString& query) { QVariant id; if (!d->db->execSql(QString::fromUtf8("INSERT INTO Searches (type, name, query) VALUES(?, ?, ?);"), type, name, query, 0, &id)) { return -1; } d->db->recordChangeset(SearchChangeset(id.toInt(), SearchChangeset::Added)); return id.toInt(); } void CoreDB::updateSearch(int searchID, DatabaseSearch::Type type, const QString& name, const QString& query) { d->db->execSql(QString::fromUtf8("UPDATE Searches SET type=?, name=?, query=? WHERE id=?;"), type, name, query, searchID); d->db->recordChangeset(SearchChangeset(searchID, SearchChangeset::Changed)); } void CoreDB::deleteSearch(int searchID) { d->db->execSql(QString::fromUtf8("DELETE FROM Searches WHERE id=?;"), searchID); d->db->recordChangeset(SearchChangeset(searchID, SearchChangeset::Deleted)); } void CoreDB::deleteSearches(DatabaseSearch::Type type) { d->db->execSql(QString::fromUtf8("DELETE FROM Searches WHERE type=?;"), type); d->db->recordChangeset(SearchChangeset(0, SearchChangeset::Deleted)); } QString CoreDB::getSearchQuery(int searchId) { QList values; d->db->execSql(QString::fromUtf8("SELECT query FROM Searches WHERE id=?;"), searchId, &values); if (values.isEmpty()) { return QString(); } else { return values.first().toString(); } } SearchInfo CoreDB::getSearchInfo(int searchId) { SearchInfo info; QList values; d->db->execSql(QString::fromUtf8("SELECT id, type, name, query FROM Searches WHERE id=?;"), searchId, &values); if (values.size() == 4) { QList::const_iterator it = values.constBegin(); info.id = (*it).toInt(); ++it; info.type = (DatabaseSearch::Type)(*it).toInt(); ++it; info.name = (*it).toString(); ++it; info.query = (*it).toString(); ++it; } return info; } void CoreDB::setSetting(const QString& keyword, const QString& value) { d->db->execSql(QString::fromUtf8("REPLACE INTO Settings VALUES (?,?);"), keyword, value); } QString CoreDB::getSetting(const QString& keyword) { QList values; d->db->execSql(QString::fromUtf8("SELECT value FROM Settings " "WHERE keyword=?;"), keyword, &values); if (values.isEmpty()) { return QString(); } else { return values.first().toString(); } } // helper method static QStringList joinMainAndUserFilterString(const QChar& sep, const QString& filter, const QString& userFilter) { QSet filterSet; QStringList userFilterList; QStringList sortedList; filterSet = filter.split(sep, QString::SkipEmptyParts).toSet(); userFilterList = userFilter.split(sep, QString::SkipEmptyParts); foreach (const QString& userFormat, userFilterList) { if (userFormat.startsWith(QLatin1Char('-'))) { filterSet.remove(userFormat.mid(1)); } else { filterSet << userFormat; } } sortedList = filterSet.toList(); sortedList.sort(); return sortedList; } void CoreDB::getFilterSettings(QStringList* imageFilter, QStringList* videoFilter, QStringList* audioFilter) { QString imageFormats, videoFormats, audioFormats, userImageFormats, userVideoFormats, userAudioFormats; if (imageFilter) { imageFormats = getSetting(QLatin1String("databaseImageFormats")); userImageFormats = getSetting(QLatin1String("databaseUserImageFormats")); *imageFilter = joinMainAndUserFilterString(QLatin1Char(';'), imageFormats, userImageFormats); } if (videoFilter) { videoFormats = getSetting(QLatin1String("databaseVideoFormats")); userVideoFormats = getSetting(QLatin1String("databaseUserVideoFormats")); *videoFilter = joinMainAndUserFilterString(QLatin1Char(';'), videoFormats, userVideoFormats); } if (audioFilter) { audioFormats = getSetting(QLatin1String("databaseAudioFormats")); userAudioFormats = getSetting(QLatin1String("databaseUserAudioFormats")); *audioFilter = joinMainAndUserFilterString(QLatin1Char(';'), audioFormats, userAudioFormats); } } void CoreDB::getUserFilterSettings(QString* imageFilterString, QString* videoFilterString, QString* audioFilterString) { if (imageFilterString) { *imageFilterString = getSetting(QLatin1String("databaseUserImageFormats")); } if (videoFilterString) { *videoFilterString = getSetting(QLatin1String("databaseUserVideoFormats")); } if (audioFilterString) { *audioFilterString = getSetting(QLatin1String("databaseUserAudioFormats")); } } void CoreDB::getUserIgnoreDirectoryFilterSettings(QString* ignoreDirectoryFilterString) { *ignoreDirectoryFilterString = getSetting(QLatin1String("databaseUserIgnoreDirectoryFormats")); } void CoreDB::getIgnoreDirectoryFilterSettings(QStringList* ignoreDirectoryFilter) { QString ignoreDirectoryFormats, userIgnoreDirectoryFormats; ignoreDirectoryFormats = getSetting(QLatin1String("databaseIgnoreDirectoryFormats")); userIgnoreDirectoryFormats = getSetting(QLatin1String("databaseUserIgnoreDirectoryFormats")); *ignoreDirectoryFilter = joinMainAndUserFilterString(QLatin1Char(';'), ignoreDirectoryFormats, userIgnoreDirectoryFormats); } void CoreDB::setFilterSettings(const QStringList& imageFilter, const QStringList& videoFilter, const QStringList& audioFilter) { setSetting(QLatin1String("databaseImageFormats"), imageFilter.join(QLatin1Char(';'))); setSetting(QLatin1String("databaseVideoFormats"), videoFilter.join(QLatin1Char(';'))); setSetting(QLatin1String("databaseAudioFormats"), audioFilter.join(QLatin1Char(';'))); } void CoreDB::setIgnoreDirectoryFilterSettings(const QStringList& ignoreDirectoryFilter) { setSetting(QLatin1String("databaseIgnoreDirectoryFormats"), ignoreDirectoryFilter.join(QLatin1Char(';'))); } void CoreDB::setUserFilterSettings(const QStringList& imageFilter, const QStringList& videoFilter, const QStringList& audioFilter) { setSetting(QLatin1String("databaseUserImageFormats"), imageFilter.join(QLatin1Char(';'))); setSetting(QLatin1String("databaseUserVideoFormats"), videoFilter.join(QLatin1Char(';'))); setSetting(QLatin1String("databaseUserAudioFormats"), audioFilter.join(QLatin1Char(';'))); } void CoreDB::setUserIgnoreDirectoryFilterSettings(const QStringList& ignoreDirectoryFilters) { qCDebug(DIGIKAM_DATABASE_LOG) << "CoreDB::setUserIgnoreDirectoryFilterSettings. " "ignoreDirectoryFilterString: " << ignoreDirectoryFilters.join(QLatin1Char(';')); setSetting(QLatin1String("databaseUserIgnoreDirectoryFormats"), ignoreDirectoryFilters.join(QLatin1Char(';'))); } QUuid CoreDB::databaseUuid() { QString uuidString = getSetting(QLatin1String("databaseUUID")); QUuid uuid = QUuid(uuidString); if (uuidString.isNull() || uuid.isNull()) { uuid = QUuid::createUuid(); setSetting(QLatin1String("databaseUUID"), uuid.toString()); } return uuid; } int CoreDB::getUniqueHashVersion() { if (d->uniqueHashVersion == -1) { QString v = getSetting(QLatin1String("uniqueHashVersion")); if (v.isEmpty()) { d->uniqueHashVersion = 1; } else { d->uniqueHashVersion = v.toInt(); } } return d->uniqueHashVersion; } bool CoreDB::isUniqueHashV2() { return (getUniqueHashVersion() == 2); } void CoreDB::setUniqueHashVersion(int version) { d->uniqueHashVersion = version; setSetting(QLatin1String("uniqueHashVersion"), QString::number(d->uniqueHashVersion)); } /* QString CoreDB::getItemCaption(qlonglong imageID) { QList values; d->db->execSql( QString::fromUtf8("SELECT caption FROM Images " "WHERE id=?;"), imageID, &values ); if (!values.isEmpty()) return values.first().toString(); else return QString(); } QString CoreDB::getItemCaption(int albumID, const QString& name) { QList values; d->db->execSql( QString::fromUtf8("SELECT caption FROM Images " "WHERE dirid=? AND name=?;"), albumID, name, &values ); if (!values.isEmpty()) return values.first().toString(); else return QString(); } QDateTime CoreDB::getItemDate(qlonglong imageID) { QList values; d->db->execSql( QString::fromUtf8("SELECT datetime FROM Images " "WHERE id=?;"), imageID, &values ); if (!values.isEmpty()) return QDateTime::fromString(values.first().toString(), Qt::ISODate); else return QDateTime(); } QDateTime CoreDB::getItemDate(int albumID, const QString& name) { QList values; d->db->execSql( QString::fromUtf8("SELECT datetime FROM Images " "WHERE dirid=? AND name=?;"), albumID, name, &values ); if (values.isEmpty()) return QDateTime::fromString(values.first().toString(), Qt::ISODate); else return QDateTime(); } */ qlonglong CoreDB::getImageId(int albumID, const QString& name) { QList values; d->db->execSql(QString::fromUtf8("SELECT id FROM Images " "WHERE album=? AND name=?;"), albumID, name, &values); if (values.isEmpty()) { return -1; } else { return values.first().toLongLong(); } } QList CoreDB::getImageIds(int albumID, const QString& name, DatabaseItem::Status status) { QList values; if (albumID == -1) { d->db->execSql(QString::fromUtf8("SELECT id FROM Images " "WHERE album IS NULL AND name=? AND status=?;"), name, status, &values); } else { d->db->execSql(QString::fromUtf8("SELECT id FROM Images " "WHERE album=? AND name=? AND status=?;"), albumID, name, status, &values); } QList items; for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ; ++it) { items << it->toLongLong(); } return items; } QList CoreDB::getImageIds(DatabaseItem::Status status) { QList values; d->db->execSql(QString::fromUtf8("SELECT id FROM Images " "WHERE status=?;"), status, &values); QList imageIds; foreach (const QVariant& object, values) { imageIds << object.toLongLong(); } return imageIds; } QList CoreDB::getImageIds(DatabaseItem::Status status, DatabaseItem::Category category) { QList values; d->db->execSql(QString::fromUtf8("SELECT id FROM Images " "WHERE status=? AND category=?;"), status, category, &values); QList imageIds; foreach (const QVariant& object, values) { imageIds << object.toLongLong(); } return imageIds; } qlonglong CoreDB::getImageId(int albumID, const QString& name, DatabaseItem::Status status, DatabaseItem::Category category, const QDateTime& modificationDate, qlonglong fileSize, const QString& uniqueHash) { QList values; QVariantList boundValues; // Add the standard bindings boundValues << name << (int)status << (int)category << modificationDate << fileSize << uniqueHash; // If the album id is -1, no album is assigned. Get all images with NULL album if (albumID == -1) { d->db->execSql(QString::fromUtf8("SELECT id FROM Images " "WHERE name=? AND status=? " "AND category=? AND modificationDate=? " "AND fileSize=? AND uniqueHash=? " "AND album IS NULL;"), boundValues, &values); } else { boundValues << albumID; d->db->execSql(QString::fromUtf8("SELECT id FROM Images " "WHERE name=? AND status=? " "AND category=? AND modificationDate=? " "AND fileSize=? AND uniqueHash=? " "AND album=?;"), boundValues, &values); } if (values.isEmpty() || ( values.size() > 1 )) { return -1; } else { return values.first().toLongLong(); } } QStringList CoreDB::getItemTagNames(qlonglong imageID) { QList values; d->db->execSql(QString::fromUtf8("SELECT name FROM Tags " "WHERE id IN (SELECT tagid FROM ImageTags " " WHERE imageid=?) " " ORDER BY name;"), imageID, &values); QStringList names; for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ; ++it) { names << it->toString(); } return names; } QList CoreDB::getItemTagIDs(qlonglong imageID) { QList values; d->db->execSql(QString::fromUtf8("SELECT tagid FROM ImageTags WHERE imageID=?;"), imageID, &values); QList ids; if (values.isEmpty()) { return ids; } for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ; ++it) { ids << it->toInt(); } return ids; } QVector > CoreDB::getItemsTagIDs(const QList imageIds) { if (imageIds.isEmpty()) { return QVector >(); } QVector > results(imageIds.size()); DbEngineSqlQuery query = d->db->prepareQuery(QString::fromUtf8("SELECT tagid FROM ImageTags WHERE imageID=?;")); QVariantList values; - for (int i = 0 ; i < imageIds.size() ; i++) + for (int i = 0 ; i < imageIds.size() ; ++i) { d->db->execSql(query, imageIds[i], &values); QList& tagIds = results[i]; foreach (const QVariant& v, values) { tagIds << v.toInt(); } } return results; } QList CoreDB::getImageTagProperties(qlonglong imageId, int tagId) { QList values; if (tagId == -1) { d->db->execSql(QString::fromUtf8("SELECT tagid, property, value FROM ImageTagProperties " "WHERE imageid=?;"), imageId, &values); } else { d->db->execSql(QString::fromUtf8("SELECT tagid, property, value FROM ImageTagProperties " "WHERE imageid=? AND tagid=?;"), imageId, tagId, &values); } QList properties; if (values.isEmpty()) { return properties; } for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ;) { ImageTagProperty property; property.imageId = imageId; property.tagId = (*it).toInt(); ++it; property.property = (*it).toString(); ++it; property.value = (*it).toString(); ++it; properties << property; } return properties; } QList CoreDB::getTagIdsWithProperties(qlonglong imageId) { QList values; d->db->execSql(QString::fromUtf8("SELECT DISTINCT tagid FROM ImageTagProperties WHERE imageid=?;"), imageId, &values); QList tagIds; for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ; ++it) { tagIds << (*it).toInt(); } return tagIds; } void CoreDB::addImageTagProperty(qlonglong imageId, int tagId, const QString& property, const QString& value) { d->db->execSql(QString::fromUtf8("INSERT INTO ImageTagProperties (imageid, tagid, property, value) " "VALUES(?, ?, ?, ?);"), imageId, tagId, property, value); d->db->recordChangeset(ImageTagChangeset(imageId, tagId, ImageTagChangeset::PropertiesChanged)); } void CoreDB::addImageTagProperty(const ImageTagProperty& property) { addImageTagProperty(property.imageId, property.tagId, property.property, property.value); } void CoreDB::removeImageTagProperties(qlonglong imageId, int tagId, const QString& property, const QString& value) { if (tagId == -1) { d->db->execSql(QString::fromUtf8("DELETE FROM ImageTagProperties " "WHERE imageid=?;"), imageId); } else if (property.isNull()) { d->db->execSql(QString::fromUtf8("DELETE FROM ImageTagProperties " "WHERE imageid=? AND tagid=?;"), imageId, tagId); } else if (value.isNull()) { d->db->execSql(QString::fromUtf8("DELETE FROM ImageTagProperties " "WHERE imageid=? AND tagid=? AND property=?;"), imageId, tagId, property); } else { d->db->execSql(QString::fromUtf8("DELETE FROM ImageTagProperties " "WHERE imageid=? AND tagid=? AND property=? AND value=?;"), imageId, tagId, property, value); } d->db->recordChangeset(ImageTagChangeset(imageId, tagId, ImageTagChangeset::PropertiesChanged)); } ItemShortInfo CoreDB::getItemShortInfo(qlonglong imageID) { QList values; d->db->execSql(QString::fromUtf8("SELECT Images.name, Albums.albumRoot, Albums.relativePath, Albums.id " "FROM Images " " LEFT JOIN Albums ON Albums.id=Images.album " " WHERE Images.id=?;"), imageID, &values); ItemShortInfo info; if (!values.isEmpty()) { info.id = imageID; info.itemName = values.at(0).toString(); info.albumRootID = values.at(1).toInt(); info.album = values.at(2).toString(); info.albumID = values.at(3).toInt(); } return info; } ItemShortInfo CoreDB::getItemShortInfo(int albumRootId, const QString& relativePath, const QString& name) { QList values; d->db->execSql(QString::fromUtf8("SELECT Images.id, Albums.id FROM Images " "INNER JOIN Albums ON Images.album=Albums.id " " WHERE name=? AND albumRoot=? AND relativePath=?;"), name, albumRootId, relativePath, &values); ItemShortInfo info; if (!values.isEmpty()) { info.id = values.at(0).toLongLong(); info.itemName = name; info.albumRootID = albumRootId; info.album = relativePath; info.albumID = values.at(1).toInt(); } return info; } bool CoreDB::hasTags(const QList& imageIDList) { QList ids; if (imageIDList.isEmpty()) { return false; } QList values; QList boundValues; QString sql = QString::fromUtf8("SELECT COUNT(tagid) FROM ImageTags " "WHERE imageid=? "); boundValues << imageIDList.first(); QList::const_iterator it = imageIDList.constBegin(); ++it; for (; it != imageIDList.constEnd() ; ++it) { sql += QString::fromUtf8(" OR imageid=? "); boundValues << (*it); } sql += QString::fromUtf8(";"); d->db->execSql(sql, boundValues, &values); if (values.isEmpty() || values.first().toInt() == 0) { return false; } else { return true; } } QList CoreDB::getItemCommonTagIDs(const QList& imageIDList) { QList ids; if (imageIDList.isEmpty()) { return ids; } QList values; QList boundValues; QString sql = QString::fromUtf8("SELECT DISTINCT tagid FROM ImageTags " "WHERE imageid=? "); boundValues << imageIDList.first(); QList::const_iterator it = imageIDList.constBegin(); ++it; for (; it != imageIDList.constEnd() ; ++it) { sql += QString::fromUtf8(" OR imageid=? "); boundValues << (*it); } sql += QString::fromUtf8(";"); d->db->execSql(sql, boundValues, &values); if (values.isEmpty()) { return ids; } for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ; ++it) { ids << it->toInt(); } return ids; } QVariantList CoreDB::getImagesFields(qlonglong imageID, DatabaseFields::Images fields) { QVariantList values; if (fields != DatabaseFields::ImagesNone) { QString query(QString::fromUtf8("SELECT ")); QStringList fieldNames = imagesFieldList(fields); query += fieldNames.join(QString::fromUtf8(", ")); query += QString::fromUtf8(" FROM Images WHERE id=?;"); d->db->execSql(query, imageID, &values); // Convert date times to QDateTime, they come as QString if ((fields & DatabaseFields::ModificationDate) && !values.isEmpty()) { int index = fieldNames.indexOf(QLatin1String("modificationDate")); values[index] = values.at(index).toDateTime(); } } return values; } QVariantList CoreDB::getItemInformation(qlonglong imageID, DatabaseFields::ItemInformation fields) { QVariantList values; if (fields != DatabaseFields::ItemInformationNone) { QString query(QString::fromUtf8("SELECT ")); QStringList fieldNames = imageInformationFieldList(fields); query += fieldNames.join(QString::fromUtf8(", ")); query += QString::fromUtf8(" FROM ImageInformation WHERE imageid=?;"); d->db->execSql(query, imageID, &values); // Convert date times to QDateTime, they come as QString if ((fields & DatabaseFields::CreationDate) && !values.isEmpty()) { int index = fieldNames.indexOf(QLatin1String("creationDate")); values[index] = values.at(index).toDateTime(); } if ((fields & DatabaseFields::DigitizationDate) && !values.isEmpty()) { int index = fieldNames.indexOf(QLatin1String("digitizationDate")); values[index] = values.at(index).toDateTime(); } } return values; } QVariantList CoreDB::getImageMetadata(qlonglong imageID, DatabaseFields::ImageMetadata fields) { QVariantList values; if (fields != DatabaseFields::ImageMetadataNone) { QString query(QString::fromUtf8("SELECT ")); QStringList fieldNames = imageMetadataFieldList(fields); query += fieldNames.join(QString::fromUtf8(", ")); query += QString::fromUtf8(" FROM ImageMetadata WHERE imageid=?;"); d->db->execSql(query, imageID, &values); // For some reason, if REAL values may be required from variables stored as QString QVariants. Convert code will come here. } return values; } QVariantList CoreDB::getVideoMetadata(qlonglong imageID, DatabaseFields::VideoMetadata fields) { QVariantList values; if (fields != DatabaseFields::VideoMetadataNone) { QString query(QString::fromUtf8("SELECT ")); QStringList fieldNames = videoMetadataFieldList(fields); query += fieldNames.join(QString::fromUtf8(", ")); query += QString::fromUtf8(" FROM VideoMetadata WHERE imageid=?;"); d->db->execSql(query, imageID, &values); // For some reason REAL values may come as QString QVariants. Convert here. if (values.size() == fieldNames.size() && ((fields & DatabaseFields::Aperture) || (fields & DatabaseFields::FocalLength) || (fields & DatabaseFields::FocalLength35) || (fields & DatabaseFields::ExposureTime) || (fields & DatabaseFields::SubjectDistance)) ) { for (int i = 0 ; i < values.size() ; ++i) { if (values.at(i).type() == QVariant::String && (fieldNames.at(i) == QLatin1String("aperture") || fieldNames.at(i) == QLatin1String("focalLength") || fieldNames.at(i) == QLatin1String("focalLength35") || fieldNames.at(i) == QLatin1String("exposureTime") || fieldNames.at(i) == QLatin1String("subjectDistance")) ) { values[i] = values.at(i).toDouble(); } } } } return values; } QVariantList CoreDB::getItemPosition(qlonglong imageID, DatabaseFields::ItemPositions fields) { QVariantList values; if (fields != DatabaseFields::ItemPositionsNone) { QString query(QString::fromUtf8("SELECT ")); QStringList fieldNames = imagePositionsFieldList(fields); query += fieldNames.join(QString::fromUtf8(", ")); query += QString::fromUtf8(" FROM ImagePositions WHERE imageid=?;"); d->db->execSql(query, imageID, &values); // For some reason REAL values may come as QString QVariants. Convert here. if (values.size() == fieldNames.size() && ((fields & DatabaseFields::LatitudeNumber) || (fields & DatabaseFields::LongitudeNumber) || (fields & DatabaseFields::Altitude) || (fields & DatabaseFields::PositionOrientation) || (fields & DatabaseFields::PositionTilt) || (fields & DatabaseFields::PositionRoll) || (fields & DatabaseFields::PositionAccuracy)) ) { for (int i = 0 ; i < values.size() ; ++i) { if (values.at(i).type() == QVariant::String && (fieldNames.at(i) == QLatin1String("latitudeNumber") || fieldNames.at(i) == QLatin1String("longitudeNumber") || fieldNames.at(i) == QLatin1String("altitude") || fieldNames.at(i) == QLatin1String("orientation") || fieldNames.at(i) == QLatin1String("tilt") || fieldNames.at(i) == QLatin1String("roll") || fieldNames.at(i) == QLatin1String("accuracy")) ) { if (!values.at(i).isNull()) values[i] = values.at(i).toDouble(); } } } } return values; } QVariantList CoreDB::getItemPositions(QList imageIDs, DatabaseFields::ItemPositions fields) { QVariantList values; if (fields != DatabaseFields::ItemPositionsNone) { QString sql(QString::fromUtf8("SELECT ")); QStringList fieldNames = imagePositionsFieldList(fields); sql += fieldNames.join(QString::fromUtf8(", ")); sql += QString::fromUtf8(" FROM ImagePositions WHERE imageid=?;"); DbEngineSqlQuery query = d->db->prepareQuery(sql); foreach (const qlonglong& imageid, imageIDs) { QVariantList singleValueList; d->db->execSql(query, imageid, &singleValueList); values << singleValueList; } // For some reason REAL values may come as QString QVariants. Convert here. if (values.size() == fieldNames.size() && (fields & DatabaseFields::LatitudeNumber || fields & DatabaseFields::LongitudeNumber || fields & DatabaseFields::Altitude || fields & DatabaseFields::PositionOrientation || fields & DatabaseFields::PositionTilt || fields & DatabaseFields::PositionRoll || fields & DatabaseFields::PositionAccuracy) ) { for (int i = 0 ; i < values.size() ; ++i) { if (values.at(i).type() == QVariant::String && (fieldNames.at(i) == QLatin1String("latitudeNumber") || fieldNames.at(i) == QLatin1String("longitudeNumber") || fieldNames.at(i) == QLatin1String("altitude") || fieldNames.at(i) == QLatin1String("orientation") || fieldNames.at(i) == QLatin1String("tilt") || fieldNames.at(i) == QLatin1String("roll") || fieldNames.at(i) == QLatin1String("accuracy")) ) { if (!values.at(i).isNull()) values[i] = values.at(i).toDouble(); } } } } return values; } void CoreDB::addItemInformation(qlonglong imageID, const QVariantList& infos, DatabaseFields::ItemInformation fields) { if (fields == DatabaseFields::ItemInformationNone) { return; } QString query(QString::fromUtf8("REPLACE INTO ImageInformation ( imageid, ")); QStringList fieldNames = imageInformationFieldList(fields); Q_ASSERT(fieldNames.size() == infos.size()); query += fieldNames.join(QLatin1String(", ")); query += QString::fromUtf8(" ) VALUES ("); addBoundValuePlaceholders(query, infos.size() + 1); query += QString::fromUtf8(");"); QVariantList boundValues; boundValues << imageID; boundValues << infos; d->db->execSql(query, boundValues); d->db->recordChangeset(ImageChangeset(imageID, DatabaseFields::Set(fields))); } void CoreDB::changeItemInformation(qlonglong imageId, const QVariantList& infos, DatabaseFields::ItemInformation fields) { if (fields == DatabaseFields::ItemInformationNone) { return; } QStringList fieldNames = imageInformationFieldList(fields); d->db->execUpsertDBAction(QLatin1String("changeItemInformation"), imageId, fieldNames, infos); d->db->recordChangeset(ImageChangeset(imageId, DatabaseFields::Set(fields))); } void CoreDB::addImageMetadata(qlonglong imageID, const QVariantList& infos, DatabaseFields::ImageMetadata fields) { if (fields == DatabaseFields::ImageMetadataNone) { return; } QString query(QString::fromUtf8("REPLACE INTO ImageMetadata ( imageid, ")); QStringList fieldNames = imageMetadataFieldList(fields); Q_ASSERT(fieldNames.size() == infos.size()); query += fieldNames.join(QLatin1String(", ")); query += QString::fromUtf8(" ) VALUES ("); addBoundValuePlaceholders(query, infos.size() + 1); query += QString::fromUtf8(");"); QVariantList boundValues; boundValues << imageID << infos; d->db->execSql(query, boundValues); d->db->recordChangeset(ImageChangeset(imageID, DatabaseFields::Set(fields))); } void CoreDB::changeImageMetadata(qlonglong imageId, const QVariantList& infos, DatabaseFields::ImageMetadata fields) { if (fields == DatabaseFields::ImageMetadataNone) { return; } QString query(QString::fromUtf8("UPDATE ImageMetadata SET ")); QStringList fieldNames = imageMetadataFieldList(fields); Q_ASSERT(fieldNames.size() == infos.size()); query += fieldNames.join(QString::fromUtf8("=?,")); query += QString::fromUtf8("=? WHERE imageid=?;"); QVariantList boundValues; boundValues << infos << imageId; d->db->execSql(query, boundValues); d->db->recordChangeset(ImageChangeset(imageId, DatabaseFields::Set(fields))); } void CoreDB::addVideoMetadata(qlonglong imageID, const QVariantList& infos, DatabaseFields::VideoMetadata fields) { if (fields == DatabaseFields::VideoMetadataNone) { return; } QString query(QString::fromUtf8("REPLACE INTO VideoMetadata ( imageid, ")); // need to create this database QStringList fieldNames = videoMetadataFieldList(fields); Q_ASSERT(fieldNames.size() == infos.size()); query += fieldNames.join(QLatin1String(", ")); query += QString::fromUtf8(" ) VALUES ("); addBoundValuePlaceholders(query, infos.size() + 1); query += QString::fromUtf8(");"); QVariantList boundValues; boundValues << imageID << infos; d->db->execSql(query, boundValues); d->db->recordChangeset(ImageChangeset(imageID, DatabaseFields::Set(fields))); } void CoreDB::changeVideoMetadata(qlonglong imageId, const QVariantList& infos, DatabaseFields::VideoMetadata fields) { if (fields == DatabaseFields::VideoMetadataNone) { return; } QString query(QString::fromUtf8("UPDATE VideoMetadata SET ")); QStringList fieldNames = videoMetadataFieldList(fields); Q_ASSERT(fieldNames.size() == infos.size()); query += fieldNames.join(QString::fromUtf8("=?,")); query += QString::fromUtf8("=? WHERE imageid=?;"); QVariantList boundValues; boundValues << infos << imageId; d->db->execSql(query, boundValues); d->db->recordChangeset(ImageChangeset(imageId, DatabaseFields::Set(fields))); } void CoreDB::addItemPosition(qlonglong imageID, const QVariantList& infos, DatabaseFields::ItemPositions fields) { if (fields == DatabaseFields::ItemPositionsNone) { return; } QString query(QString::fromUtf8("REPLACE INTO ImagePositions ( imageid, ")); QStringList fieldNames = imagePositionsFieldList(fields); Q_ASSERT(fieldNames.size() == infos.size()); query += fieldNames.join(QLatin1String(", ")); query += QString::fromUtf8(" ) VALUES ("); addBoundValuePlaceholders(query, infos.size() + 1); query += QString::fromUtf8(");"); QVariantList boundValues; boundValues << imageID << infos; d->db->execSql(query, boundValues); d->db->recordChangeset(ImageChangeset(imageID, DatabaseFields::Set(fields))); } void CoreDB::changeItemPosition(qlonglong imageId, const QVariantList& infos, DatabaseFields::ItemPositions fields) { if (fields == DatabaseFields::ItemPositionsNone) { return; } QString query(QString::fromUtf8("UPDATE ImagePositions SET ")); QStringList fieldNames = imagePositionsFieldList(fields); Q_ASSERT(fieldNames.size() == infos.size()); query += fieldNames.join(QString::fromUtf8("=?,")); query += QString::fromUtf8("=? WHERE imageid=?;"); QVariantList boundValues; boundValues << infos << imageId; d->db->execSql(query, boundValues); d->db->recordChangeset(ImageChangeset(imageId, DatabaseFields::Set(fields))); } void CoreDB::removeItemPosition(qlonglong imageid) { d->db->execSql(QString(QString::fromUtf8("DELETE FROM ImagePositions WHERE imageid=?;")), imageid); d->db->recordChangeset(ImageChangeset(imageid, DatabaseFields::Set(DatabaseFields::ItemPositionsAll))); } void CoreDB::removeItemPositionAltitude(qlonglong imageid) { d->db->execSql(QString(QString::fromUtf8("UPDATE ImagePositions SET altitude=NULL WHERE imageid=?;")), imageid); d->db->recordChangeset(ImageChangeset(imageid, DatabaseFields::Set(DatabaseFields::Altitude))); } QList CoreDB::getItemComments(qlonglong imageID) { QList list; QList values; d->db->execSql(QString::fromUtf8("SELECT id, type, language, author, date, comment " "FROM ImageComments WHERE imageid=?;"), imageID, &values); for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ;) { CommentInfo info; info.imageId = imageID; info.id = (*it).toInt(); ++it; info.type = (DatabaseComment::Type)(*it).toInt(); ++it; info.language = (*it).toString(); ++it; info.author = (*it).toString(); ++it; info.date = (*it).toDateTime(); ++it; info.comment = (*it).toString(); ++it; list << info; } return list; } int CoreDB::setImageComment(qlonglong imageID, const QString& comment, DatabaseComment::Type type, const QString& language, const QString& author, const QDateTime& date) { QVariantList boundValues; boundValues << imageID << (int)type << language << author << date << comment; QVariant id; d->db->execSql(QString::fromUtf8("REPLACE INTO ImageComments " "( imageid, type, language, author, date, comment ) " " VALUES (?,?,?,?,?,?);"), boundValues, 0, &id); d->db->recordChangeset(ImageChangeset(imageID, DatabaseFields::Set(DatabaseFields::ItemCommentsAll))); return id.toInt(); } void CoreDB::changeImageComment(int commentId, qlonglong imageID, const QVariantList& infos, DatabaseFields::ItemComments fields) { if (fields == DatabaseFields::ItemCommentsNone) { return; } QString query(QString::fromUtf8("UPDATE ImageComments SET ")); QStringList fieldNames = imageCommentsFieldList(fields); Q_ASSERT(fieldNames.size() == infos.size()); query += fieldNames.join(QString::fromUtf8("=?,")); query += QString::fromUtf8("=? WHERE id=?;"); QVariantList boundValues; boundValues << infos << commentId; d->db->execSql(query, boundValues); d->db->recordChangeset(ImageChangeset(imageID, DatabaseFields::Set(fields))); } void CoreDB::removeImageComment(int commentid, qlonglong imageid) { d->db->execSql(QString::fromUtf8("DELETE FROM ImageComments WHERE id=?;"), commentid); d->db->recordChangeset(ImageChangeset(imageid, DatabaseFields::Set(DatabaseFields::ItemCommentsAll))); } QString CoreDB::getImageProperty(qlonglong imageID, const QString& property) { QList values; d->db->execSql(QString::fromUtf8("SELECT value FROM ImageProperties " "WHERE imageid=? and property=?;"), imageID, property, &values); if (!values.isEmpty()) { return values.first().toString(); } else { return QString(); } } void CoreDB::setImageProperty(qlonglong imageID, const QString& property, const QString& value) { d->db->execSql(QString::fromUtf8("REPLACE INTO ImageProperties " "(imageid, property, value) " " VALUES(?, ?, ?);"), imageID, property, value); } void CoreDB::removeImageProperty(qlonglong imageID, const QString& property) { d->db->execSql(QString::fromUtf8("DELETE FROM ImageProperties WHERE imageid=? AND property=?;"), imageID, property); } void CoreDB::removeImagePropertyByName(const QString& property) { d->db->execSql(QString::fromUtf8("DELETE FROM ImageProperties WHERE property=?;"), property); } QList CoreDB::getItemCopyright(qlonglong imageID, const QString& property) { QList list; QList values; if (property.isNull()) { d->db->execSql(QString::fromUtf8("SELECT property, value, extraValue FROM ImageCopyright " "WHERE imageid=?;"), imageID, &values); } else { d->db->execSql(QString::fromUtf8("SELECT property, value, extraValue FROM ImageCopyright " "WHERE imageid=? and property=?;"), imageID, property, &values); } for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ;) { CopyrightInfo info; info.id = imageID; info.property = (*it).toString(); ++it; info.value = (*it).toString(); ++it; info.extraValue = (*it).toString(); ++it; list << info; } return list; } void CoreDB::setItemCopyrightProperty(qlonglong imageID, const QString& property, const QString& value, const QString& extraValue, CopyrightPropertyUnique uniqueness) { if (uniqueness == PropertyUnique) { d->db->execSql(QString::fromUtf8("DELETE FROM ImageCopyright " "WHERE imageid=? AND property=?;"), imageID, property); } else if (uniqueness == PropertyExtraValueUnique) { d->db->execSql(QString::fromUtf8("DELETE FROM ImageCopyright " "WHERE imageid=? AND property=? AND extraValue=?;"), imageID, property, extraValue); } d->db->execSql(QString::fromUtf8("REPLACE INTO ImageCopyright " "(imageid, property, value, extraValue) " " VALUES(?, ?, ?, ?);"), imageID, property, value, extraValue); } void CoreDB::removeItemCopyrightProperties(qlonglong imageID, const QString& property, const QString& extraValue, const QString& value) { int removeBy = 0; if (!property.isNull()) { ++removeBy; } if (!extraValue.isNull()) { ++removeBy; } if (!value.isNull()) { ++removeBy; } switch (removeBy) { case 0: d->db->execSql(QString::fromUtf8("DELETE FROM ImageCopyright " "WHERE imageid=?;"), imageID); break; case 1: d->db->execSql(QString::fromUtf8("DELETE FROM ImageCopyright " "WHERE imageid=? AND property=?;"), imageID, property); break; case 2: d->db->execSql(QString::fromUtf8("DELETE FROM ImageCopyright " "WHERE imageid=? AND property=? AND extraValue=?;"), imageID, property, extraValue); break; case 3: d->db->execSql(QString::fromUtf8("DELETE FROM ImageCopyright " "WHERE imageid=? AND property=? AND extraValue=? AND value=?;"), imageID, property, extraValue, value); break; } } QList CoreDB::findByNameAndCreationDate(const QString& fileName, const QDateTime& creationDate) { QList values; d->db->execSql(QString::fromUtf8("SELECT id FROM Images " "LEFT JOIN ImageInformation ON id=imageid " " WHERE name=? AND creationDate=? AND status!=3;"), fileName, creationDate, &values); QList ids; foreach (const QVariant& var, values) { ids << var.toLongLong(); } return ids; } bool CoreDB::hasImageHistory(qlonglong imageId) { QList values; d->db->execSql(QString::fromUtf8("SELECT history FROM ImageHistory WHERE imageid=?;"), imageId, &values); return !values.isEmpty(); } ImageHistoryEntry CoreDB::getItemHistory(qlonglong imageId) { QList values; d->db->execSql(QString::fromUtf8("SELECT uuid, history FROM ImageHistory WHERE imageid=?;"), imageId, &values); ImageHistoryEntry entry; entry.imageId = imageId; if (values.count() != 2) { return entry; } QList::const_iterator it = values.constBegin(); entry.uuid = (*it).toString(); ++it; entry.history = (*it).toString(); ++it; return entry; } QList CoreDB::getItemsForUuid(const QString& uuid) { QList values; d->db->execSql(QString::fromUtf8("SELECT imageid FROM ImageHistory " "INNER JOIN Images ON imageid=id " " WHERE uuid=? AND status!=3;"), uuid, &values); QList imageIds; if (values.isEmpty()) { return imageIds; } for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ; ++it) { imageIds << (*it).toInt(); } return imageIds; } QString CoreDB::getImageUuid(qlonglong imageId) { QList values; d->db->execSql(QString::fromUtf8("SELECT uuid FROM ImageHistory WHERE imageid=?;"), imageId, &values); if (values.isEmpty()) { return QString(); } QString uuid = values.first().toString(); if (uuid.isEmpty()) { return QString(); } return uuid; } void CoreDB::setItemHistory(qlonglong imageId, const QString& history) { d->db->execUpsertDBAction(QLatin1String("changeImageHistory"), imageId, QStringList() << QLatin1String("history"), QVariantList() << history); d->db->recordChangeset(ImageChangeset(imageId, DatabaseFields::Set(DatabaseFields::ImageHistory))); } void CoreDB::setImageUuid(qlonglong imageId, const QString& uuid) { d->db->execUpsertDBAction(QLatin1String("changeImageHistory"), imageId, QStringList() << QLatin1String("uuid"), QVariantList() << uuid); d->db->recordChangeset(ImageChangeset(imageId, DatabaseFields::Set(DatabaseFields::ImageUUID))); } void CoreDB::addImageRelation(qlonglong subjectId, qlonglong objectId, DatabaseRelation::Type type) { d->db->execSql(QString::fromUtf8("REPLACE INTO ImageRelations (subject, object, type) " "VALUES (?, ?, ?);"), subjectId, objectId, type); d->db->recordChangeset(ImageChangeset(QList() << subjectId << objectId, DatabaseFields::Set(DatabaseFields::ImageRelations))); } void CoreDB::addImageRelations(const QList& subjectIds, const QList& objectIds, DatabaseRelation::Type type) { DbEngineSqlQuery query = d->db->prepareQuery(QString::fromUtf8("REPLACE INTO ImageRelations (subject, object, type) " "VALUES (?, ?, ?);")); QVariantList subjects, objects, types; for (int i = 0 ; i < subjectIds.size() ; ++i) { subjects << subjectIds.at(i); objects << objectIds.at(i); types << type; } query.addBindValue(subjects); query.addBindValue(objects); query.addBindValue(types); d->db->execBatch(query); d->db->recordChangeset(ImageChangeset(subjectIds + objectIds, DatabaseFields::Set(DatabaseFields::ImageRelations))); } void CoreDB::addImageRelation(const ImageRelation& relation) { addImageRelation(relation.subjectId, relation.objectId, relation.type); } void CoreDB::removeImageRelation(qlonglong subjectId, qlonglong objectId, DatabaseRelation::Type type) { d->db->execSql(QString::fromUtf8("DELETE FROM ImageRelations WHERE subject=? AND object=? AND type=?;"), subjectId, objectId, type); d->db->recordChangeset(ImageChangeset(QList() << subjectId << objectId, DatabaseFields::Set(DatabaseFields::ImageRelations))); } void CoreDB::removeImageRelation(const ImageRelation& relation) { removeImageRelation(relation.subjectId, relation.objectId, relation.type); } QList CoreDB::removeAllImageRelationsTo(qlonglong objectId, DatabaseRelation::Type type) { QList affected = getImagesRelatingTo(objectId, type); if (affected.isEmpty()) { return affected; } d->db->execSql(QString::fromUtf8("DELETE FROM ImageRelations WHERE object=? AND type=?;"), objectId, type); d->db->recordChangeset(ImageChangeset(QList() << affected << objectId, DatabaseFields::Set(DatabaseFields::ImageRelations))); return affected; } QList CoreDB::removeAllImageRelationsFrom(qlonglong subjectId, DatabaseRelation::Type type) { QList affected = getImagesRelatedFrom(subjectId, type); if (affected.isEmpty()) { return affected; } d->db->execSql(QString::fromUtf8("DELETE FROM ImageRelations WHERE subject=? AND type=?;"), subjectId, type); d->db->recordChangeset(ImageChangeset(QList() << affected << subjectId, DatabaseFields::Set(DatabaseFields::ImageRelations))); return affected; } QList CoreDB::getImagesRelatedFrom(qlonglong subjectId, DatabaseRelation::Type type) { return getRelatedImages(subjectId, true, type, false); } QVector > CoreDB::getImagesRelatedFrom(QList subjectIds, DatabaseRelation::Type type) { return getRelatedImages(subjectIds, true, type, false); } bool CoreDB::hasImagesRelatedFrom(qlonglong subjectId, DatabaseRelation::Type type) { // returns 0 or 1 item in list return !getRelatedImages(subjectId, true, type, true).isEmpty(); } QList CoreDB::getImagesRelatingTo(qlonglong objectId, DatabaseRelation::Type type) { return getRelatedImages(objectId, false, type, false); } QVector > CoreDB::getImagesRelatingTo(QList objectIds, DatabaseRelation::Type type) { return getRelatedImages(objectIds, false, type, false); } bool CoreDB::hasImagesRelatingTo(qlonglong objectId, DatabaseRelation::Type type) { // returns 0 or 1 item in list return !getRelatedImages(objectId, false, type, true).isEmpty(); } QList CoreDB::getRelatedImages(qlonglong id, bool fromOrTo, DatabaseRelation::Type type, bool boolean) { QString sql = d->constructRelatedImagesSQL(fromOrTo, type, boolean); DbEngineSqlQuery query = d->db->prepareQuery(sql); return d->execRelatedImagesQuery(query, id, type); } QVector > CoreDB::getRelatedImages(QList ids, bool fromOrTo, DatabaseRelation::Type type, bool boolean) { if (ids.isEmpty()) { return QVector >(); } QVector > result(ids.size()); QString sql = d->constructRelatedImagesSQL(fromOrTo, type, boolean); DbEngineSqlQuery query = d->db->prepareQuery(sql); - for (int i = 0 ; i < ids.size() ; i++) + for (int i = 0 ; i < ids.size() ; ++i) { result[i] = d->execRelatedImagesQuery(query, ids[i], type); } return result; } QList > CoreDB::getRelationCloud(qlonglong imageId, DatabaseRelation::Type type) { QSet todo, done; QSet > pairs; todo << imageId; QString sql = QString::fromUtf8("SELECT subject, object FROM ImageRelations " "INNER JOIN Images AS SubjectImages " "ON ImageRelations.subject=SubjectImages.id " " INNER JOIN Images AS ObjectImages " " ON ImageRelations.object=ObjectImages.id " " WHERE (subject=? OR object=?) %1 " " AND SubjectImages.status!=3 " " AND ObjectImages.status!=3;"); if (type == DatabaseRelation::UndefinedType) { sql = sql.arg(QString()); } else { sql = sql.arg(QString::fromUtf8("AND type=?")); } DbEngineSqlQuery query = d->db->prepareQuery(sql); QList values; qlonglong subject, object; while (!todo.isEmpty()) { qlonglong id = *todo.begin(); todo.erase(todo.begin()); done << id; if (type == DatabaseRelation::UndefinedType) { d->db->execSql(query, id, id, &values); } else { d->db->execSql(query, id, id, type, &values); } for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ;) { subject = (*it).toLongLong(); ++it; object = (*it).toLongLong(); ++it; pairs << qMakePair(subject, object); if (!done.contains(subject)) { todo << subject; } if (!done.contains(object)) { todo << object; } } } return pairs.toList(); } QList CoreDB::getOneRelatedImageEach(const QList& ids, DatabaseRelation::Type type) { QString sql = QString::fromUtf8("SELECT subject, object FROM ImageRelations " "INNER JOIN Images AS SubjectImages " "ON ImageRelations.subject=SubjectImages.id " " INNER JOIN Images AS ObjectImages " " ON ImageRelations.object=ObjectImages.id " " WHERE ( (subject=? AND ObjectImages.status!=3) " " OR (object=? AND SubjectImages.status!=3) ) " " %1 LIMIT 1;"); if (type == DatabaseRelation::UndefinedType) { sql = sql.arg(QString()); } else { sql = sql.arg(QString::fromUtf8("AND type=?")); } DbEngineSqlQuery query = d->db->prepareQuery(sql); QSet result; QList values; foreach (const qlonglong& id, ids) { if (type == DatabaseRelation::UndefinedType) { d->db->execSql(query, id, id, &values); } else { d->db->execSql(query, id, id, type, &values); } if (values.size() != 2) { continue; } // one of subject and object is the given id, the other our result if (values.first() != id) { result << values.first().toLongLong(); } else { result << values.last().toLongLong(); } } return result.toList(); } QList CoreDB::getRelatedImagesToByType(DatabaseRelation::Type type) { QList values; d->db->execSql(QString::fromUtf8("SELECT object FROM ImageRelations " "INNER JOIN Images ON ImageRelations.object=Images.id " " WHERE type=? AND status!=3;"), (int)type, &values); QList imageIds; if (values.isEmpty()) { return imageIds; } for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ; ++it) { imageIds << (*it).toLongLong(); } return imageIds; } QStringList CoreDB::getItemsURLsWithTag(int tagId) { QList values; d->db->execSql(QString::fromUtf8("SELECT Albums.albumRoot, Albums.relativePath, Images.name FROM Images " "LEFT JOIN ImageTags ON Images.id=ImageTags.imageid " "LEFT JOIN Albums ON Albums.id=Images.album " " WHERE Images.status=1 AND Images.category=1 AND ImageTags.tagid=?;"), tagId, &values); QStringList urls; QString albumRootPath, relativePath, name; for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ;) { albumRootPath = CollectionManager::instance()->albumRootPath((*it).toInt()); ++it; relativePath = (*it).toString(); ++it; name = (*it).toString(); ++it; if (relativePath == QLatin1String("/")) { urls << albumRootPath + relativePath + name; } else { urls << albumRootPath + relativePath + QLatin1Char('/') + name; } } return urls; } QStringList CoreDB::getDirtyOrMissingFaceImageUrls() { QList values; d->db->execSql(QString::fromUtf8("SELECT Albums.albumRoot, Albums.relativePath, Images.name FROM Images " "LEFT JOIN ImageScannedMatrix ON Images.id=ImageScannedMatrix.imageid " "LEFT JOIN Albums ON Albums.id=Images.album " " WHERE Images.status=1 AND Images.category=1 AND " " ( ImageScannedMatrix.imageid IS NULL " " OR Images.modificationDate != ImageScannedMatrix.modificationDate " " OR Images.uniqueHash != ImageScannedMatrix.uniqueHash );"), &values); QStringList urls; QString albumRootPath, relativePath, name; for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ;) { albumRootPath = CollectionManager::instance()->albumRootPath((*it).toInt()); ++it; relativePath = (*it).toString(); ++it; name = (*it).toString(); ++it; if (relativePath == QLatin1String("/")) { urls << albumRootPath + relativePath + name; } else { urls << albumRootPath + relativePath + QLatin1Char('/') + name; } } return urls; } QList CoreDB::getIdenticalFiles(qlonglong id) { if (!id) { return QList(); } QList values; // retrieve unique hash and file size d->db->execSql(QString::fromUtf8("SELECT uniqueHash, fileSize FROM Images WHERE id=?;"), id, &values); if (values.isEmpty()) { return QList(); } QString uniqueHash = values.at(0).toString(); qlonglong fileSize = values.at(1).toLongLong(); return getIdenticalFiles(uniqueHash, fileSize, id); } QList CoreDB::getIdenticalFiles(const QString& uniqueHash, qlonglong fileSize, qlonglong sourceId) { // enforce validity if (uniqueHash.isEmpty() || fileSize <= 0) { return QList(); } QList values; // find items with same fingerprint d->db->execSql(QString::fromUtf8("SELECT id, album, name, status, category, modificationDate, fileSize " "FROM Images WHERE fileSize=? AND uniqueHash=? AND album IS NOT NULL;"), fileSize, uniqueHash, &values); QList list; for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ;) { ItemScanInfo info; info.id = (*it).toLongLong(); ++it; info.albumID = (*it).toInt(); ++it; info.itemName = (*it).toString(); ++it; info.status = (DatabaseItem::Status)(*it).toInt(); ++it; info.category = (DatabaseItem::Category)(*it).toInt(); ++it; info.modificationDate = (*it).toDateTime(); ++it; info.fileSize = (*it).toLongLong(); ++it; // exclude one source id from list if (sourceId == info.id) { continue; } // same for all here, per definition info.uniqueHash = uniqueHash; list << info; } return list; } QStringList CoreDB::imagesFieldList(DatabaseFields::Images fields) { // adds no spaces at beginning or end QStringList list; if (fields & DatabaseFields::Album) { list << QLatin1String("album"); } if (fields & DatabaseFields::Name) { list << QLatin1String("name"); } if (fields & DatabaseFields::Status) { list << QLatin1String("status"); } if (fields & DatabaseFields::Category) { list << QLatin1String("category"); } if (fields & DatabaseFields::ModificationDate) { list << QLatin1String("modificationDate"); } if (fields & DatabaseFields::FileSize) { list << QLatin1String("fileSize"); } if (fields & DatabaseFields::UniqueHash) { list << QLatin1String("uniqueHash"); } if (fields & DatabaseFields::ManualOrder) { list << QLatin1String("manualOrder"); } return list; } QStringList CoreDB::imageInformationFieldList(DatabaseFields::ItemInformation fields) { // adds no spaces at beginning or end QStringList list; if (fields & DatabaseFields::Rating) { list << QLatin1String("rating"); } if (fields & DatabaseFields::CreationDate) { list << QLatin1String("creationDate"); } if (fields & DatabaseFields::DigitizationDate) { list << QLatin1String("digitizationDate"); } if (fields & DatabaseFields::Orientation) { list << QLatin1String("orientation"); } if (fields & DatabaseFields::Width) { list << QLatin1String("width"); } if (fields & DatabaseFields::Height) { list << QLatin1String("height"); } if (fields & DatabaseFields::Format) { list << QLatin1String("format"); } if (fields & DatabaseFields::ColorDepth) { list << QLatin1String("colorDepth"); } if (fields & DatabaseFields::ColorModel) { list << QLatin1String("colorModel"); } return list; } QStringList CoreDB::videoMetadataFieldList(DatabaseFields::VideoMetadata fields) { // adds no spaces at beginning or end QStringList list; if (fields & DatabaseFields::AspectRatio) { list << QLatin1String("aspectRatio"); } if (fields & DatabaseFields::AudioBitRate) { list << QLatin1String("audioBitRate"); } if (fields & DatabaseFields::AudioChannelType) { list << QLatin1String("audioChannelType"); } if (fields & DatabaseFields::AudioCodec) { list << QLatin1String("audioCompressor"); } if (fields & DatabaseFields::Duration) { list << QLatin1String("duration"); } if (fields & DatabaseFields::FrameRate) { list << QLatin1String("frameRate"); } if (fields & DatabaseFields::VideoCodec) { list << QLatin1String("videoCodec"); } return list; } QStringList CoreDB::imageMetadataFieldList(DatabaseFields::ImageMetadata fields) { // adds no spaces at beginning or end QStringList list; if (fields & DatabaseFields::Make) { list << QLatin1String("make"); } if (fields & DatabaseFields::Model) { list << QLatin1String("model"); } if (fields & DatabaseFields::Lens) { list << QLatin1String("lens"); } if (fields & DatabaseFields::Aperture) { list << QLatin1String("aperture"); } if (fields & DatabaseFields::FocalLength) { list << QLatin1String("focalLength"); } if (fields & DatabaseFields::FocalLength35) { list << QLatin1String("focalLength35"); } if (fields & DatabaseFields::ExposureTime) { list << QLatin1String("exposureTime"); } if (fields & DatabaseFields::ExposureProgram) { list << QLatin1String("exposureProgram"); } if (fields & DatabaseFields::ExposureMode) { list << QLatin1String("exposureMode"); } if (fields & DatabaseFields::Sensitivity) { list << QLatin1String("sensitivity"); } if (fields & DatabaseFields::FlashMode) { list << QLatin1String("flash"); } if (fields & DatabaseFields::WhiteBalance) { list << QLatin1String("whiteBalance"); } if (fields & DatabaseFields::WhiteBalanceColorTemperature) { list << QLatin1String("whiteBalanceColorTemperature"); } if (fields & DatabaseFields::MeteringMode) { list << QLatin1String("meteringMode"); } if (fields & DatabaseFields::SubjectDistance) { list << QLatin1String("subjectDistance"); } if (fields & DatabaseFields::SubjectDistanceCategory) { list << QLatin1String("subjectDistanceCategory"); } return list; } QStringList CoreDB::imagePositionsFieldList(DatabaseFields::ItemPositions fields) { // adds no spaces at beginning or end QStringList list; if (fields & DatabaseFields::Latitude) { list << QLatin1String("latitude"); } if (fields & DatabaseFields::LatitudeNumber) { list << QLatin1String("latitudeNumber"); } if (fields & DatabaseFields::Longitude) { list << QLatin1String("longitude"); } if (fields & DatabaseFields::LongitudeNumber) { list << QLatin1String("longitudeNumber"); } if (fields & DatabaseFields::Altitude) { list << QLatin1String("altitude"); } if (fields & DatabaseFields::PositionOrientation) { list << QLatin1String("orientation"); } if (fields & DatabaseFields::PositionTilt) { list << QLatin1String("tilt"); } if (fields & DatabaseFields::PositionRoll) { list << QLatin1String("roll"); } if (fields & DatabaseFields::PositionAccuracy) { list << QLatin1String("accuracy"); } if (fields & DatabaseFields::PositionDescription) { list << QLatin1String("description"); } return list; } QStringList CoreDB::imageCommentsFieldList(DatabaseFields::ItemComments fields) { // adds no spaces at beginning or end QStringList list; if (fields & DatabaseFields::CommentType) { list << QLatin1String("type"); } if (fields & DatabaseFields::CommentLanguage) { list << QLatin1String("language"); } if (fields & DatabaseFields::CommentAuthor) { list << QLatin1String("author"); } if (fields & DatabaseFields::CommentDate) { list << QLatin1String("date"); } if (fields & DatabaseFields::Comment) { list << QLatin1String("comment"); } return list; } void CoreDB::addBoundValuePlaceholders(QString& query, int count) { // adds no spaces at beginning or end QString questionMarks; questionMarks.reserve(count * 2); QString questionMark(QString::fromUtf8("?,")); for (int i = 0 ; i < count ; ++i) { questionMarks += questionMark; } // remove last ',' questionMarks.chop(1); query += questionMarks; } int CoreDB::findInDownloadHistory(const QString& identifier, const QString& name, qlonglong fileSize, const QDateTime& date) { QList values; QVariantList boundValues; boundValues << identifier << name << fileSize << date.addSecs(-2) << date.addSecs(2); d->db->execSql(QString::fromUtf8("SELECT id FROM DownloadHistory " " WHERE identifier=? AND filename=? " " AND filesize=? AND (filedate>? " " AND filedatedb->execSql(QString::fromUtf8("REPLACE INTO DownloadHistory " "(identifier, filename, filesize, filedate) " " VALUES (?,?,?,?);"), identifier, name, fileSize, date, 0, &id); return id.toInt(); } /* void CoreDB::setItemCaption(qlonglong imageID,const QString& caption) { QList values; d->db->execSql( QString::fromUtf8("UPDATE Images SET caption=? " "WHERE id=?;"), caption, imageID ); CoreDbAccess::attributesWatch() ->sendImageFieldChanged(imageID, DatabaseAttributesWatch::ImageComment); } void CoreDB::setItemCaption(int albumID, const QString& name, const QString& caption) { / * QList values; d->db->execSql( QString::fromUtf8("UPDATE Images SET caption=? " "WHERE dirid=? AND name=?;") .(caption, QString::number(albumID), (name)) ); * / // easier because of attributes watch return setItemCaption(getImageId(albumID, name), caption); } */ void CoreDB::addItemTag(qlonglong imageID, int tagID) { d->db->execSql(QString::fromUtf8("REPLACE INTO ImageTags (imageid, tagid) " "VALUES(?, ?);"), imageID, tagID); d->db->recordChangeset(ImageTagChangeset(imageID, tagID, ImageTagChangeset::Added)); //don't save pick or color tags if (TagsCache::instance()->isInternalTag(tagID)) return; //move current tag to front d->recentlyAssignedTags.removeAll(tagID); d->recentlyAssignedTags.prepend(tagID); if (d->recentlyAssignedTags.size() > 10) { d->recentlyAssignedTags.removeLast(); } } void CoreDB::addItemTag(int albumID, const QString& name, int tagID) { /* d->db->execSql( QString::fromUtf8("REPLACE INTO ImageTags (imageid, tagid) \n " "(SELECT id, ? FROM Images \n " " WHERE dirid=? AND name=?);") .tagID .albumID .(name) ); */ // easier because of attributes watch return addItemTag(getImageId(albumID, name), tagID); } void CoreDB::addTagsToItems(QList imageIDs, QList tagIDs) { if (imageIDs.isEmpty() || tagIDs.isEmpty()) { return; } DbEngineSqlQuery query = d->db->prepareQuery(QString::fromUtf8("REPLACE INTO ImageTags (imageid, tagid) " "VALUES(?, ?);")); QVariantList images; QVariantList tags; foreach (const qlonglong& imageid, imageIDs) { foreach (int tagid, tagIDs) { images << imageid; tags << tagid; } } query.addBindValue(images); query.addBindValue(tags); d->db->execBatch(query); d->db->recordChangeset(ImageTagChangeset(imageIDs, tagIDs, ImageTagChangeset::Added)); } QList CoreDB::getRecentlyAssignedTags() const { return d->recentlyAssignedTags; } void CoreDB::removeItemTag(qlonglong imageID, int tagID) { d->db->execSql(QString::fromUtf8("DELETE FROM ImageTags " "WHERE imageID=? AND tagid=?;"), imageID, tagID); d->db->recordChangeset(ImageTagChangeset(imageID, tagID, ImageTagChangeset::Removed)); } void CoreDB::removeItemAllTags(qlonglong imageID, const QList& currentTagIds) { d->db->execSql(QString::fromUtf8("DELETE FROM ImageTags " "WHERE imageID=?;"), imageID); d->db->recordChangeset(ImageTagChangeset(imageID, currentTagIds, ImageTagChangeset::RemovedAll)); } void CoreDB::removeTagsFromItems(QList imageIDs, const QList& tagIDs) { if (imageIDs.isEmpty() || tagIDs.isEmpty()) { return; } DbEngineSqlQuery query = d->db->prepareQuery(QString::fromUtf8("DELETE FROM ImageTags WHERE imageID=? AND tagid=?;")); QVariantList images; QVariantList tags; foreach (const qlonglong& imageid, imageIDs) { foreach (int tagid, tagIDs) { images << imageid; tags << tagid; } } query.addBindValue(images); query.addBindValue(tags); d->db->execBatch(query); d->db->recordChangeset(ImageTagChangeset(imageIDs, tagIDs, ImageTagChangeset::Removed)); } QStringList CoreDB::getItemNamesInAlbum(int albumID, bool recursive) { QList values; if (recursive) { int rootId = getAlbumRootId(albumID); QString path = getAlbumRelativePath(albumID); d->db->execSql(QString::fromUtf8("SELECT Images.name FROM Images WHERE Images.album IN " " (SELECT DISTINCT id FROM Albums " " WHERE albumRoot=? AND (relativePath=? OR relativePath LIKE ?));"), rootId, path, path == QLatin1String("/") ? QLatin1String("/%") : QString(path + QLatin1String("/%")), &values); } else { d->db->execSql(QString::fromUtf8("SELECT name FROM Images " "WHERE album=?;"), albumID, &values); } QStringList names; for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ; ++it) { names << it->toString(); } return names; } qlonglong CoreDB::getItemFromAlbum(int albumID, const QString& fileName) { QList values; d->db->execSql(QString::fromUtf8("SELECT id FROM Images " "WHERE album=? AND name=?;"), albumID, fileName, &values); if (values.isEmpty()) { return -1; } else { return values.first().toLongLong(); } } /* QStringList CoreDB::getAllItemURLsWithoutDate() { QList values; d->db->execSql( QString::fromUtf8("SELECT AlbumRoots.absolutePath||Albums.relativePath||'/'||Images.name " "FROM Images " " LEFT JOIN Albums ON Images.album=Albums.id " " LEFT JOIN AlbumRoots ON AlbumRoots.id=Albums.albumRoot " "WHERE (Images.datetime is null or " " Images.datetime == '');"), &values ); QStringList urls; for (QList::iterator it = values.begin(); it != values.end(); ++it) urls << it->toString(); return urls; } */ QList CoreDB::getAllCreationDates() { QList values; d->db->execSql(QString::fromUtf8("SELECT creationDate FROM ImageInformation " "INNER JOIN Images ON Images.id=ImageInformation.imageid " " WHERE Images.status=1;"), &values); QList list; foreach (const QVariant& value, values) { if (!value.isNull()) { list << value.toDateTime(); } } return list; } QMap CoreDB::getAllCreationDatesAndNumberOfImages() { QList values; d->db->execSql(QString::fromUtf8("SELECT creationDate FROM ImageInformation " "INNER JOIN Images ON Images.id=ImageInformation.imageid " " WHERE Images.status=1;"), &values); QMap datesStatMap; foreach (const QVariant& value, values) { if (!value.isNull()) { QDateTime dateTime = value.toDateTime(); if (!dateTime.isValid()) { continue; } QMap::iterator it2 = datesStatMap.find(dateTime); if (it2 == datesStatMap.end()) { datesStatMap.insert(dateTime, 1); } else { it2.value()++; } } } return datesStatMap; } QMap CoreDB::getNumberOfImagesInAlbums() { QList values, allAbumIDs; QMap albumsStatMap; int albumID; // initialize allAbumIDs with all existing albums from db to prevent // wrong album image counters d->db->execSql(QString::fromUtf8("SELECT id from Albums"), &allAbumIDs); for (QList::const_iterator it = allAbumIDs.constBegin() ; it != allAbumIDs.constEnd() ; ++it) { albumID = (*it).toInt(); albumsStatMap.insert(albumID, 0); } d->db->execSql(QString::fromUtf8("SELECT album FROM Images WHERE Images.status=1;"), &values); for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ;) { albumID = (*it).toInt(); ++it; QMap::iterator it2 = albumsStatMap.find(albumID); if (it2 == albumsStatMap.end()) { albumsStatMap.insert(albumID, 1); } else { it2.value()++; } } return albumsStatMap; } QMap CoreDB::getNumberOfImagesInTags() { QList values, allTagIDs; QMap tagsStatMap; int tagID; // initialize allTagIDs with all existing tags from db to prevent // wrong tag counters d->db->execSql(QString::fromUtf8("SELECT id from Tags"), &allTagIDs); for (QList::const_iterator it = allTagIDs.constBegin(); it != allTagIDs.constEnd(); ++it) { tagID = (*it).toInt(); tagsStatMap.insert(tagID, 0); } d->db->execSql(QString::fromUtf8("SELECT tagid FROM ImageTags " "LEFT JOIN Images ON Images.id=ImageTags.imageid " " WHERE Images.status=1;"), &values); for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ;) { tagID = (*it).toInt(); ++it; QMap::iterator it2 = tagsStatMap.find(tagID); if (it2 == tagsStatMap.end()) { tagsStatMap.insert(tagID, 1); } else { it2.value()++; } } return tagsStatMap; } QMap CoreDB::getNumberOfImagesInTagProperties(const QString& property) { QList values; QMap tagsStatMap; int tagID, count; d->db->execSql(QString::fromUtf8("SELECT tagid, COUNT(*) FROM ImageTagProperties " "LEFT JOIN Images ON Images.id=ImageTagProperties.imageid " " WHERE ImageTagProperties.property=? AND Images.status=1 " " GROUP BY tagid;"), property, &values); for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ;) { tagID = (*it).toInt(); ++it; count = (*it).toInt(); ++it; tagsStatMap[tagID] = count; } return tagsStatMap; } int CoreDB::getNumberOfImagesInTagProperties(int tagId, const QString& property) { QList values; d->db->execSql(QString::fromUtf8("SELECT COUNT(*) FROM ImageTagProperties " "LEFT JOIN Images ON Images.id=ImageTagProperties.imageid " " WHERE ImageTagProperties.property=? AND Images.status=1 " " AND ImageTagProperties.tagid=? ;"), property, tagId, &values); return values.first().toInt(); } QList CoreDB::getImagesWithImageTagProperty(int tagId, const QString& property) { QList values; QList imageIds; d->db->execSql(QString::fromUtf8("SELECT DISTINCT Images.id FROM ImageTagProperties " "LEFT JOIN Images ON Images.id=ImageTagProperties.imageid " " WHERE ImageTagProperties.property=? AND Images.status=1 " " AND ImageTagProperties.tagid=? ;"), property, tagId, &values); for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ; ++it) { imageIds.append((*it).toInt()); } return imageIds; } QMap CoreDB::getFormatStatistics() { return getFormatStatistics(DatabaseItem::UndefinedCategory); } QMap CoreDB::getFormatStatistics(DatabaseItem::Category category) { QMap map; QString queryString = QString::fromUtf8("SELECT COUNT(*), II.format " "FROM ImageInformation AS II " "INNER JOIN Images ON II.imageid=Images.id " " WHERE Images.status=1 "); if (category != DatabaseItem::UndefinedCategory) { queryString.append(QString::fromUtf8("AND Images.category=%1").arg(category)); } queryString.append(QString::fromUtf8(" GROUP BY II.format;")); qCDebug(DIGIKAM_DATABASE_LOG) << queryString; DbEngineSqlQuery query = d->db->prepareQuery(queryString); if (d->db->exec(query)) { while (query.next()) { QString quantity = query.value(0).toString(); QString format = query.value(1).toString(); if (format.isEmpty()) { continue; } map[format] = quantity.isEmpty() ? 0 : quantity.toInt(); } } return map; } QStringList CoreDB::getListFromImageMetadata(DatabaseFields::ImageMetadata field) { QStringList list; QList values; QStringList fieldName = imageMetadataFieldList(field); if (fieldName.count() != 1) { return list; } QString sql = QString::fromUtf8("SELECT DISTINCT %1 FROM ImageMetadata " "INNER JOIN Images ON imageid=Images.id;"); sql = sql.arg(fieldName.first()); d->db->execSql(sql, &values); for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ; ++it) { if (!it->isNull()) { list << it->toString(); } } return list; } /* QList > CoreDB::getItemsAndDate() { QList values; d->db->execSql( QString::fromUtf8("SELECT Images.name, datetime FROM Images;", &values )); QList > data; for ( QList::iterator it = values.begin(); it != values.end(); ) { QPair pair; pair.first = (*it).toString(); ++it; pair.second = QDateTime::fromString( (*it).toString(), Qt::ISODate ); ++it; if (!pair.second.isValid()) continue; data << pair; } return data; } */ /* int CoreDB::getAlbumForPath(const QString& albumRoot, const QString& folder, bool create) { CollectionLocation location = CollectionManager::instance()->locationForAlbumRootPath(albumRoot); if (location.isNull()) return -1; return getAlbumForPath(location.id(), folder, create); } */ int CoreDB::getAlbumForPath(int albumRootId, const QString& folder, bool create) { QList values; d->db->execSql(QString::fromUtf8("SELECT id FROM Albums WHERE albumRoot=? AND relativePath=?;"), albumRootId, folder, &values); int albumID = -1; if (values.isEmpty()) { if (create) { albumID = addAlbum(albumRootId, folder, QString(), QDate::currentDate(), QString()); } } else { albumID = values.first().toInt(); } return albumID; } QList CoreDB::getAlbumAndSubalbumsForPath(int albumRootId, const QString& relativePath) { QList values; d->db->execSql(QString::fromUtf8("SELECT id, relativePath FROM Albums " "WHERE albumRoot=? AND (relativePath=? OR relativePath LIKE ?);"), albumRootId, relativePath, (relativePath == QLatin1String("/") ? QLatin1String("/%") : QString(relativePath + QLatin1String("/%"))), &values); QList albumIds; int id; QString albumRelativePath; for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ;) { id = (*it).toInt(); ++it; QString albumRelativePath = (*it).toString(); ++it; // bug #223050: The LIKE operator is case insensitive if (albumRelativePath.startsWith(relativePath)) { albumIds << id; } } return albumIds; } QList CoreDB::getAlbumsOnAlbumRoot(int albumRootId) { QList values; d->db->execSql(QString::fromUtf8("SELECT id FROM Albums WHERE albumRoot=?;"), albumRootId, &values); QList albumIds; for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ; ++it) { albumIds << (*it).toInt(); } return albumIds; } qlonglong CoreDB::addItem(int albumID, const QString& name, DatabaseItem::Status status, DatabaseItem::Category category, const QDateTime& modificationDate, qlonglong fileSize, const QString& uniqueHash) { QVariantList boundValues; boundValues << albumID << name << (int)status << (int)category << modificationDate << fileSize << uniqueHash; QVariant id; d->db->execSql(QString::fromUtf8("REPLACE INTO Images " "( album, name, status, category, modificationDate, fileSize, uniqueHash ) " " VALUES (?,?,?,?,?,?,?);"), boundValues, 0, &id); if (id.isNull()) { return -1; } d->db->recordChangeset(ImageChangeset(id.toLongLong(), DatabaseFields::Set(DatabaseFields::ImagesAll))); d->db->recordChangeset(CollectionImageChangeset(id.toLongLong(), albumID, CollectionImageChangeset::Added)); return id.toLongLong(); } void CoreDB::updateItem(qlonglong imageID, DatabaseItem::Category category, const QDateTime& modificationDate, qlonglong fileSize, const QString& uniqueHash) { QVariantList boundValues; boundValues << (int)category << modificationDate << fileSize << uniqueHash << imageID; d->db->execSql(QString::fromUtf8("UPDATE Images SET category=?, modificationDate=?, fileSize=?, uniqueHash=? " "WHERE id=?;"), boundValues); d->db->recordChangeset(ImageChangeset(imageID, DatabaseFields::Set(DatabaseFields::Category | DatabaseFields::ModificationDate | DatabaseFields::FileSize | DatabaseFields::UniqueHash))); } void CoreDB::setItemStatus(qlonglong imageID, DatabaseItem::Status status) { QVariantList boundValues; boundValues << (int)status << imageID; d->db->execSql(QString::fromUtf8("UPDATE Images SET status=? WHERE id=?;"), boundValues); d->db->recordChangeset(ImageChangeset(imageID, DatabaseFields::Set(DatabaseFields::Status))); } void CoreDB::setItemAlbum(qlonglong imageID, qlonglong album) { QVariantList boundValues; boundValues << album << imageID; d->db->execSql(QString::fromUtf8("UPDATE Images SET album=? WHERE id=?;"), boundValues); // record that the image was assigned a new album d->db->recordChangeset(ImageChangeset(imageID, DatabaseFields::Set(DatabaseFields::Album))); // also record that the collection was changed by adding an image to an album. d->db->recordChangeset(CollectionImageChangeset(imageID, album, CollectionImageChangeset::Added)); } void CoreDB::setItemManualOrder(qlonglong imageID, qlonglong value) { QVariantList boundValues; boundValues << value << imageID; d->db->execSql(QString::fromUtf8("UPDATE Images SET manualOrder=? WHERE id=?;"), boundValues); d->db->recordChangeset(ImageChangeset(imageID, DatabaseFields::Set(DatabaseFields::ManualOrder))); } void CoreDB::setItemModificationDate(qlonglong imageID, const QDateTime& modificationDate) { QVariantList boundValues; boundValues << modificationDate << imageID; d->db->execSql(QString::fromUtf8("UPDATE Images SET modificationDate=? WHERE id=?;"), boundValues); d->db->recordChangeset(ImageChangeset(imageID, DatabaseFields::Set(DatabaseFields::ModificationDate))); } void CoreDB::renameItem(qlonglong imageID, const QString& newName) { d->db->execSql(QString::fromUtf8("UPDATE Images SET name=? WHERE id=?;"), newName, imageID); } /* QList CoreDB::getTagsFromTagPaths(const QStringList& keywordsList, bool create) { if (keywordsList.isEmpty()) return QList(); QList tagIDs; QStringList keywordsList2Create; // Create a list of the tags currently in database TagInfo::List currentTagsList; QList values; d->db->execSql( "SELECT id, pid, name FROM Tags;", &values ); for (QList::const_iterator it = values.constBegin(); it != values.constEnd();) { TagInfo info; info.id = (*it).toInt(); ++it; info.pid = (*it).toInt(); ++it; info.name = (*it).toString(); ++it; currentTagsList.append(info); } // For every tag in keywordsList, scan taglist to check if tag already exists. for (QStringList::const_iterator kwd = keywordsList.constBegin(); kwd != keywordsList.constEnd(); ++kwd ) { // split full tag "url" into list of single tag names QStringList tagHierarchy = (*kwd).split('/', QString::SkipEmptyParts); if (tagHierarchy.isEmpty()) continue; // last entry in list is the actual tag name bool foundTag = false; QString tagName = tagHierarchy.back(); tagHierarchy.pop_back(); for (TagInfo::List::const_iterator tag = currentTagsList.constBegin(); tag != currentTagsList.constEnd(); ++tag ) { // There might be multiple tags with the same name, but in different // hierarchies. We must check them all until we find the correct hierarchy if ((*tag).name == tagName) { int parentID = (*tag).pid; // Check hierarchy, from bottom to top bool foundParentTag = true; QStringList::iterator parentTagName = tagHierarchy.end(); while (foundParentTag && parentTagName != tagHierarchy.begin()) { --parentTagName; foundParentTag = false; for (TagInfo::List::const_iterator parentTag = currentTagsList.constBegin(); parentTag != currentTagsList.constEnd(); ++parentTag ) { // check if name is the same, and if ID is identical // to the parent ID we got from the child tag if ( (*parentTag).id == parentID && (*parentTag).name == (*parentTagName) ) { parentID = (*parentTag).pid; foundParentTag = true; break; } } // If we traversed the list without a match, // foundParentTag will be false, the while loop breaks. } // If we managed to traverse the full hierarchy, // we have our tag. if (foundParentTag) { // add to result list tagIDs.append((*tag).id); foundTag = true; break; } } } if (!foundTag) keywordsList2Create.append(*kwd); } // If tags do not exist in database, create them. if (create && !keywordsList2Create.isEmpty()) { for (QStringList::const_iterator kwd = keywordsList2Create.constBegin(); kwd != keywordsList2Create.constEnd(); ++kwd ) { // split full tag "url" into list of single tag names QStringList tagHierarchy = (*kwd).split('/', QString::SkipEmptyParts); if (tagHierarchy.isEmpty()) continue; int parentTagID = 0; int tagID = 0; bool parentTagExisted = true; // Traverse hierarchy from top to bottom for (QStringList::const_iterator tagName = tagHierarchy.constBegin(); tagName != tagHierarchy.constEnd(); ++tagName) { tagID = 0; // if the parent tag did not exist, we need not check if the child exists if (parentTagExisted) { for (TagInfo::List::const_iterator tag = currentTagsList.constBegin(); tag != currentTagsList.constEnd(); ++tag ) { // find the tag with tag name according to tagHierarchy, // and parent ID identical to the ID of the tag we found in // the previous run. if ((*tag).name == (*tagName) && (*tag).pid == parentTagID) { tagID = (*tag).id; break; } } } if (tagID != 0) { // tag already found in DB parentTagID = tagID; continue; } // Tag does not yet exist in DB, add it tagID = addTag(parentTagID, (*tagName), QString(), 0); if (tagID == -1) { // Something is wrong in database. Abort. break; } // append to our list of existing tags (for following keywords) TagInfo info; info.id = tagID; info.pid = parentTagID; info.name = (*tagName); currentTagsList.append(info); parentTagID = tagID; parentTagExisted = false; } // add to result list tagIDs.append(tagID); } } return tagIDs; } */ int CoreDB::getItemAlbum(qlonglong imageID) { QList values; d->db->execSql(QString::fromUtf8("SELECT album FROM Images WHERE id=?;"), imageID, &values); if (!values.isEmpty()) { return values.first().toInt(); } else { return 1; } } QString CoreDB::getItemName(qlonglong imageID) { QList values; d->db->execSql(QString::fromUtf8("SELECT name FROM Images WHERE id=?;"), imageID, &values); if (!values.isEmpty()) { return values.first().toString(); } else { return QString(); } } /* bool CoreDB::setItemDate(qlonglong imageID, const QDateTime& datetime) { d->db->execSql ( QString::fromUtf8("UPDATE Images SET datetime=?" "WHERE id=?;"), datetime.toString(Qt::ISODate), imageID ); CoreDbAccess::attributesWatch() ->sendImageFieldChanged(imageID, DatabaseAttributesWatch::ImageDate); return true; } bool CoreDB::setItemDate(int albumID, const QString& name, const QDateTime& datetime) { / * d->db->execSql ( QString::fromUtf8("UPDATE Images SET datetime=?" "WHERE dirid=? AND name=?;") .datetime.toString(Qt::ISODate, QString::number(albumID), (name)) ); return true; * / // easier because of attributes watch return setItemDate(getImageId(albumID, name), datetime); } void CoreDB::setItemRating(qlonglong imageID, int rating) { d->db->execSql ( QString::fromUtf8("REPLACE INTO ImageProperties " "(imageid, property, value) " "VALUES(?, ?, ?);"), imageID, QString("Rating"), rating ); CoreDbAccess::attributesWatch() ->sendImageFieldChanged(imageID, DatabaseAttributesWatch::ImageRating); } int CoreDB::getItemRating(qlonglong imageID) { QList values; d->db->execSql( QString::fromUtf8(SELECT value FROM ImageProperties " "WHERE imageid=? and property=?;"), imageID, QString("Rating"), &values); if (!values.isEmpty()) return values.first().toInt(); else return 0; } */ QStringList CoreDB::getItemURLsInAlbum(int albumID, ItemSortOrder sortOrder) { QList values; int albumRootId = getAlbumRootId(albumID); if (albumRootId == -1) { return QStringList(); } QString albumRootPath = CollectionManager::instance()->albumRootPath(albumRootId); if (albumRootPath.isNull()) { return QStringList(); } QMap bindingMap; bindingMap.insert(QString::fromUtf8(":albumID"), albumID); switch (sortOrder) { case ByItemName: d->db->execDBAction(d->db->getDBAction(QString::fromUtf8("getItemURLsInAlbumByItemName")), bindingMap, &values); break; case ByItemPath: // Don't collate on the path - this is to maintain the same behavior // that happens when sort order is "By Path" d->db->execDBAction(d->db->getDBAction(QString::fromUtf8("getItemURLsInAlbumByItemPath")), bindingMap, &values); break; case ByItemDate: d->db->execDBAction(d->db->getDBAction(QString::fromUtf8("getItemURLsInAlbumByItemDate")), bindingMap, &values); break; case ByItemRating: d->db->execDBAction(d->db->getDBAction(QString::fromUtf8("getItemURLsInAlbumByItemRating")), bindingMap, &values); break; case NoItemSorting: default: d->db->execDBAction(d->db->getDBAction(QString::fromUtf8("getItemURLsInAlbumNoItemSorting")), bindingMap, &values); break; } QStringList urls; QString relativePath, name; for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ;) { relativePath = (*it).toString(); ++it; name = (*it).toString(); ++it; if (relativePath == QLatin1String("/")) { urls << albumRootPath + relativePath + name; } else { urls << albumRootPath + relativePath + QLatin1Char('/') + name; } } return urls; } QList CoreDB::getItemIDsInAlbum(int albumID) { QList itemIDs; QList values; d->db->execSql(QString::fromUtf8("SELECT id FROM Images WHERE album=?;"), albumID, &values); for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ; ++it) { itemIDs << (*it).toLongLong(); } return itemIDs; } QMap CoreDB::getItemIDsAndURLsInAlbum(int albumID) { int albumRootId = getAlbumRootId(albumID); if (albumRootId == -1) { return QMap(); } QString albumRootPath = CollectionManager::instance()->albumRootPath(albumRootId); if (albumRootPath.isNull()) { return QMap(); } QMap itemsMap; QList values; d->db->execSql(QString::fromUtf8("SELECT Images.id, Albums.relativePath, Images.name " "FROM Images JOIN Albums ON Albums.id=Images.album " " WHERE Albums.id=?;"), albumID, &values); QString path; qlonglong id; QString relativePath, name; for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ;) { id = (*it).toLongLong(); ++it; relativePath = (*it).toString(); ++it; name = (*it).toString(); ++it; if (relativePath == QLatin1String("/")) { path = albumRootPath + relativePath + name; } else { path = albumRootPath + relativePath + QLatin1Char('/') + name; } itemsMap.insert(id, path); }; return itemsMap; } QList CoreDB::getAllItems() { QList values; d->db->execSql(QString::fromUtf8("SELECT id FROM Images;"), &values); QList items; foreach (const QVariant& item, values) { items << item.toLongLong(); } return items; } QList CoreDB::getItemScanInfos(int albumID) { QList values; d->db->execSql(QString::fromUtf8("SELECT id, album, name, status, category, modificationDate, fileSize, uniqueHash " "FROM Images WHERE album=?;"), albumID, &values); QList list; for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ;) { ItemScanInfo info; info.id = (*it).toLongLong(); ++it; info.albumID = (*it).toInt(); ++it; info.itemName = (*it).toString(); ++it; info.status = (DatabaseItem::Status)(*it).toInt(); ++it; info.category = (DatabaseItem::Category)(*it).toInt(); ++it; info.modificationDate = (*it).toDateTime(); ++it; info.fileSize = (*it).toLongLong(); ++it; info.uniqueHash = (*it).toString(); ++it; list << info; } return list; } ItemScanInfo CoreDB::getItemScanInfo(qlonglong imageID) { QList values; d->db->execSql(QString::fromUtf8("SELECT id, album, name, status, category, modificationDate, fileSize, uniqueHash " "FROM Images WHERE id=?;"), imageID, &values); ItemScanInfo info; if (!values.isEmpty()) { QList::const_iterator it = values.constBegin(); info.id = (*it).toLongLong(); ++it; info.albumID = (*it).toInt(); ++it; info.itemName = (*it).toString(); ++it; info.status = (DatabaseItem::Status)(*it).toInt(); ++it; info.category = (DatabaseItem::Category)(*it).toInt(); ++it; info.modificationDate = (*it).toDateTime(); ++it; info.fileSize = (*it).toLongLong(); ++it; info.uniqueHash = (*it).toString(); ++it; } return info; } QStringList CoreDB::getItemURLsInTag(int tagID, bool recursive) { QList values; QMap bindingMap; bindingMap.insert(QString::fromUtf8(":tagID"), tagID); bindingMap.insert(QString::fromUtf8(":tagID2"), tagID); if (recursive) { d->db->execDBAction(d->db->getDBAction(QString::fromUtf8("GetItemURLsInTagRecursive")), bindingMap, &values); } else { d->db->execDBAction(d->db->getDBAction(QString::fromUtf8("GetItemURLsInTag")), bindingMap, &values); } QStringList urls; QString albumRootPath, relativePath, name; for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ;) { albumRootPath = CollectionManager::instance()->albumRootPath((*it).toInt()); ++it; relativePath = (*it).toString(); ++it; name = (*it).toString(); ++it; if (relativePath == QLatin1String("/")) { urls << albumRootPath + relativePath + name; } else { urls << albumRootPath + relativePath + QLatin1Char('/') + name; } } return urls; } QList CoreDB::getItemIDsInTag(int tagID, bool recursive) { QList itemIDs; QList values; QMap parameters; parameters.insert(QString::fromUtf8(":tagPID"), tagID); parameters.insert(QString::fromUtf8(":tagID"), tagID); if (recursive) { d->db->execDBAction(d->db->getDBAction(QString::fromUtf8("getItemIDsInTagRecursive")), parameters, &values); } else { d->db->execDBAction(d->db->getDBAction(QString::fromUtf8("getItemIDsInTag")), parameters, &values); } for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ; ++it) { itemIDs << (*it).toLongLong(); } return itemIDs; } /* QString CoreDB::getAlbumPath(int albumID) { QList values; d->db->execSql( QString::fromUtf8("SELECT AlbumRoots.absolutePath||Albums.relativePath " "FROM Albums, AlbumRoots WHERE Albums.id=? AND AlbumRoots.id=Albums.albumRoot; "), albumID, &values); if (!values.isEmpty()) return values.first().toString(); else return QString(); } */ QString CoreDB::getAlbumRelativePath(int albumID) { QList values; d->db->execSql(QString::fromUtf8("SELECT relativePath from Albums WHERE id=?;"), albumID, &values); if (!values.isEmpty()) { return values.first().toString(); } else { return QString(); } } int CoreDB::getAlbumRootId(int albumID) { QList values; d->db->execSql(QString::fromUtf8("SELECT albumRoot FROM Albums WHERE id=?;"), albumID, &values); if (!values.isEmpty()) { return values.first().toInt(); } else { return -1; } } QDate CoreDB::getAlbumLowestDate(int albumID) { QList values; d->db->execSql(QString::fromUtf8("SELECT MIN(creationDate) FROM ImageInformation " "INNER JOIN Images ON Images.id=ImageInformation.imageid " " WHERE Images.album=? GROUP BY Images.album;"), albumID, &values); if (!values.isEmpty()) { return values.first().toDate(); } else { return QDate(); } } QDate CoreDB::getAlbumHighestDate(int albumID) { QList values; d->db->execSql(QString::fromUtf8("SELECT MAX(creationDate) FROM ImageInformation " "INNER JOIN Images ON Images.id=ImageInformation.imageid " " WHERE Images.album=? GROUP BY Images.album;"), albumID , &values); if (!values.isEmpty()) { return values.first().toDate(); } else { return QDate(); } } QDate CoreDB::getAlbumAverageDate(int albumID) { QList values; d->db->execSql(QString::fromUtf8("SELECT creationDate FROM ImageInformation " "INNER JOIN Images ON Images.id=ImageInformation.imageid " " WHERE Images.album=?;"), albumID , &values); QList dates; for (QList::const_iterator it = values.constBegin() ; it != values.constEnd() ; ++it) { QDateTime itemDateTime = (*it).toDateTime(); if (itemDateTime.isValid()) { dates << itemDateTime.date(); } } if (dates.isEmpty()) { return QDate(); } qint64 julianDays = 0; foreach (const QDate& date, dates) { julianDays += date.toJulianDay(); } return QDate::fromJulianDay(julianDays / dates.size()); } void CoreDB::deleteItem(int albumID, const QString& file) { qlonglong imageId = getImageId(albumID, file); if (imageId == -1) { return; } d->db->execSql(QString::fromUtf8("DELETE FROM Images WHERE id=?;"), imageId); d->db->recordChangeset(CollectionImageChangeset(imageId, albumID, CollectionImageChangeset::Deleted)); } void CoreDB::deleteItem(qlonglong imageId) { d->db->execSql(QString::fromUtf8("DELETE FROM Images WHERE id=? AND album IS NULL;"), imageId); } void CoreDB::removeItemsFromAlbum(int albumID, const QList& ids_forInformation) { d->db->execSql(QString::fromUtf8("UPDATE Images SET status=?, album=NULL WHERE album=?;"), (int)DatabaseItem::Trashed, albumID); d->db->recordChangeset(CollectionImageChangeset(ids_forInformation, albumID, CollectionImageChangeset::RemovedAll)); } void CoreDB::removeItems(QList itemIDs, const QList& albumIDs) { DbEngineSqlQuery query = d->db->prepareQuery(QString::fromUtf8("UPDATE Images SET status=?, album=NULL WHERE id=?;")); QVariantList imageIds; QVariantList status; foreach (const qlonglong& id, itemIDs) { status << (int)DatabaseItem::Trashed; imageIds << id; } query.addBindValue(status); query.addBindValue(imageIds); d->db->execBatch(query); d->db->recordChangeset(CollectionImageChangeset(itemIDs, albumIDs, CollectionImageChangeset::Removed)); } void CoreDB::removeItemsPermanently(QList itemIDs, const QList& albumIDs) { DbEngineSqlQuery query = d->db->prepareQuery(QString::fromUtf8("UPDATE Images SET status=?, album=NULL WHERE id=?;")); QVariantList imageIds; QVariantList status; foreach (const qlonglong& id, itemIDs) { status << (int)DatabaseItem::Obsolete; imageIds << id; } query.addBindValue(status); query.addBindValue(imageIds); d->db->execBatch(query); d->db->recordChangeset(CollectionImageChangeset(itemIDs, albumIDs, CollectionImageChangeset::Removed)); } void CoreDB::deleteRemovedItems() { d->db->execSql(QString::fromUtf8("DELETE FROM Images WHERE status=?;"), (int)DatabaseItem::Obsolete); d->db->recordChangeset(CollectionImageChangeset(QList(), QList(), CollectionImageChangeset::RemovedDeleted)); } /* // This method is probably nonsense because a remove image no longer has an associated album void CoreDB::deleteRemovedItems(QList albumIds) { DbEngineSqlQuery query = d->db->prepareQuery( QString::fromUtf8("DELETE FROM Images WHERE status=? AND album=?;") ); QVariantList albumBindIds; QVariantList status; foreach (int albumId, albumIds) { status << (int)DatabaseItem::Removed; albumBindIds << albumId; } query.addBindValue(status); query.addBindValue(albumBindIds); d->db->execBatch(query); d->db->recordChangeset(CollectionImageChangeset(QList(), albumIds, CollectionImageChangeset::RemovedDeleted)); } */ void CoreDB::renameAlbum(int albumID, int newAlbumRoot, const QString& newRelativePath) { int albumRoot = getAlbumRootId(albumID); QString relativePath = getAlbumRelativePath(albumID); if (relativePath == newRelativePath && albumRoot == newAlbumRoot) { return; } // first delete any stale albums left behind at the destination of renaming QMap parameters; parameters.insert(QString::fromUtf8(":albumRoot"), newAlbumRoot); parameters.insert(QString::fromUtf8(":relativePath"), newRelativePath); if (BdEngineBackend::NoErrors != d->db->execDBAction(d->db->getDBAction(QString::fromUtf8("deleteAlbumRootPath")), parameters)) { return; } // now update the album d->db->execSql(QString::fromUtf8("UPDATE Albums SET albumRoot=?, relativePath=? WHERE id=? AND albumRoot=?;"), newAlbumRoot, newRelativePath, albumID, albumRoot); d->db->recordChangeset(AlbumChangeset(albumID, AlbumChangeset::Renamed)); /* if (renameSubalbums) { // now find the list of all subalbums which need to be updated QList values; d->db->execSql( QString::fromUtf8("SELECT id, relativePath FROM Albums WHERE albumRoot=? AND relativePath LIKE ?;"), albumRoot, oldUrl + "/%", &values ); // and update their url QString newChildURL; for (QList::iterator it = values.begin(); it != values.end(); ) { int childAlbumId = (*it).toInt(); ++it; newChildURL = (*it).toString(); ++it; newChildURL.replace(oldUrl, newRelativePath); d->db->execSql(QString::fromUtf8("UPDATE Albums SET albumRoot=?, relativePath=? WHERE albumRoot=? AND relativePath=?"), newAlbumRoot, newChildURL, albumRoot, (*it) ); d->db->recordChangeset(AlbumChangeset(childAlbumId, AlbumChangeset::Renamed)); } } */ } void CoreDB::setTagName(int tagID, const QString& name) { d->db->execSql(QString::fromUtf8("UPDATE Tags SET name=? WHERE id=?;"), name, tagID); d->db->recordChangeset(TagChangeset(tagID, TagChangeset::Renamed)); } void CoreDB::moveItem(int srcAlbumID, const QString& srcName, int dstAlbumID, const QString& dstName) { // find id of src image qlonglong imageId = getImageId(srcAlbumID, srcName); if (imageId == -1) { return; } // first delete any stale database entries (for destination) if any deleteItem(dstAlbumID, dstName); d->db->execSql(QString::fromUtf8("UPDATE Images SET album=?, name=? " "WHERE id=?;"), dstAlbumID, dstName, imageId); d->db->recordChangeset(CollectionImageChangeset(imageId, srcAlbumID, CollectionImageChangeset::Moved)); d->db->recordChangeset(CollectionImageChangeset(imageId, srcAlbumID, CollectionImageChangeset::Removed)); d->db->recordChangeset(CollectionImageChangeset(imageId, dstAlbumID, CollectionImageChangeset::Added)); } int CoreDB::copyItem(int srcAlbumID, const QString& srcName, int dstAlbumID, const QString& dstName) { // find id of src image qlonglong srcId = getImageId(srcAlbumID, srcName); if (srcId == -1 || dstAlbumID == -1 || dstName.isEmpty()) { return -1; } // check for src == dest if (srcAlbumID == dstAlbumID && srcName == dstName) { return srcId; } // first delete any stale database entries if any deleteItem(dstAlbumID, dstName); // copy entry in Images table QVariant id; d->db->execSql(QString::fromUtf8("INSERT INTO Images " "( album, name, status, category, modificationDate, fileSize, uniqueHash ) " " SELECT ?, ?, status, category, modificationDate, fileSize, uniqueHash " " FROM Images WHERE id=?;"), dstAlbumID, dstName, srcId, 0, &id); if (id.isNull()) { return -1; } d->db->recordChangeset(ImageChangeset(id.toLongLong(), DatabaseFields::Set(DatabaseFields::ImagesAll))); d->db->recordChangeset(CollectionImageChangeset(id.toLongLong(), srcAlbumID, CollectionImageChangeset::Copied)); d->db->recordChangeset(CollectionImageChangeset(id.toLongLong(), dstAlbumID, CollectionImageChangeset::Added)); // copy all other tables copyImageAttributes(srcId, id.toLongLong()); return id.toLongLong(); } void CoreDB::copyImageAttributes(qlonglong srcId, qlonglong dstId) { // Go through all image-specific tables and copy the entries DatabaseFields::Set fields; d->db->execSql(QString::fromUtf8("REPLACE INTO ImageInformation " "(imageid, rating, creationDate, digitizationDate, orientation, " " width, height, format, colorDepth, colorModel) " "SELECT ?, rating, creationDate, digitizationDate, orientation, " " width, height, format, colorDepth, colorModel " "FROM ImageInformation WHERE imageid=?;"), dstId, srcId); fields |= DatabaseFields::ItemInformationAll; d->db->execSql(QString::fromUtf8("REPLACE INTO ImageMetadata " "(imageid, make, model, lens, aperture, focalLength, focalLength35, " " exposureTime, exposureProgram, exposureMode, sensitivity, flash, whiteBalance, " " whiteBalanceColorTemperature, meteringMode, subjectDistance, subjectDistanceCategory) " "SELECT ?, make, model, lens, aperture, focalLength, focalLength35, " " exposureTime, exposureProgram, exposureMode, sensitivity, flash, whiteBalance, " " whiteBalanceColorTemperature, meteringMode, subjectDistance, subjectDistanceCategory " "FROM ImageMetadata WHERE imageid=?;"), dstId, srcId); fields |= DatabaseFields::ImageMetadataAll; d->db->execSql(QString::fromUtf8("REPLACE INTO VideoMetadata " "(imageid, aspectRatio, audioBitRate, audioChannelType, audioCompressor, duration, " " frameRate, videoCodec) " "SELECT ?, aspectRatio, audioBitRate, audioChannelType, audioCompressor, duration, " " frameRate, videoCodec " "FROM VideoMetadata WHERE imageid=?;"), dstId, srcId); fields |= DatabaseFields::VideoMetadataAll; d->db->execSql(QString::fromUtf8("REPLACE INTO ImagePositions " "(imageid, latitude, latitudeNumber, longitude, longitudeNumber, " " altitude, orientation, tilt, roll, accuracy, description) " "SELECT ?, latitude, latitudeNumber, longitude, longitudeNumber, " " altitude, orientation, tilt, roll, accuracy, description " "FROM ImagePositions WHERE imageid=?;"), dstId, srcId); fields |= DatabaseFields::ItemPositionsAll; d->db->execSql(QString::fromUtf8("REPLACE INTO ImageComments " "(imageid, type, language, author, date, comment) " "SELECT ?, type, language, author, date, comment " "FROM ImageComments WHERE imageid=?;"), dstId, srcId); fields |= DatabaseFields::ItemCommentsAll; d->db->execSql(QString::fromUtf8("REPLACE INTO ImageCopyright " "(imageid, property, value, extraValue) " "SELECT ?, property, value, extraValue " "FROM ImageCopyright WHERE imageid=?;"), dstId, srcId); d->db->execSql(QString::fromUtf8("REPLACE INTO ImageHistory " "(imageid, uuid, history) " "SELECT ?, uuid, history " "FROM ImageHistory WHERE imageid=?;"), dstId, srcId); fields |= DatabaseFields::ImageHistoryInfoAll; d->db->execSql(QString::fromUtf8("REPLACE INTO ImageRelations " "(subject, object, type) " "SELECT ?, object, type " "FROM ImageRelations WHERE subject=?;"), dstId, srcId); d->db->execSql(QString::fromUtf8("REPLACE INTO ImageRelations " "(subject, object, type) " "SELECT subject, ?, type " "FROM ImageRelations WHERE object=?;"), dstId, srcId); fields |= DatabaseFields::ImageRelations; d->db->recordChangeset(ImageChangeset(dstId, fields)); copyImageTags(srcId, dstId); copyImageProperties(srcId, dstId); } void CoreDB::copyImageProperties(qlonglong srcId, qlonglong dstId) { d->db->execSql(QString::fromUtf8("REPLACE INTO ImageProperties " "(imageid, property, value) " "SELECT ?, property, value " "FROM ImageProperties WHERE imageid=?;"), dstId, srcId); } void CoreDB::copyImageTags(qlonglong srcId, qlonglong dstId) { d->db->execSql(QString::fromUtf8("REPLACE INTO ImageTags " "(imageid, tagid) " "SELECT ?, tagid " "FROM ImageTags WHERE imageid=?;"), dstId, srcId); d->db->execSql(QString::fromUtf8("REPLACE INTO ImageTagProperties " "(imageid, tagid, property, value) " "SELECT ?, tagid, property, value " "FROM ImageTagProperties WHERE imageid=?;"), dstId, srcId); // leave empty tag list for now d->db->recordChangeset(ImageTagChangeset(dstId, QList(), ImageTagChangeset::Added)); d->db->recordChangeset(ImageTagChangeset(dstId, QList(), ImageTagChangeset::PropertiesChanged)); } bool CoreDB::copyAlbumProperties(int srcAlbumID, int dstAlbumID) { if (srcAlbumID == dstAlbumID) { return true; } QList values; d->db->execSql(QString::fromUtf8("SELECT date, caption, collection, icon " "FROM Albums WHERE id=?;"), srcAlbumID, &values); if (values.isEmpty()) { qCWarning(DIGIKAM_DATABASE_LOG) << " src album ID " << srcAlbumID << " does not exist"; return false; } QList boundValues; boundValues << values.at(0) << values.at(1) << values.at(2) << values.at(3); boundValues << dstAlbumID; d->db->execSql(QString::fromUtf8("UPDATE Albums SET date=?, caption=?, " "collection=?, icon=? WHERE id=?;"), boundValues); return true; } QList CoreDB::getImageIdsFromArea(qreal lat1, qreal lat2, qreal lng1, qreal lng2, int /*sortMode*/, const QString& /*sortBy*/) { QList values; QList boundValues; boundValues << lat1 << lat2 << lng1 << lng2; //CoreDbAccess access; d->db->execSql(QString::fromUtf8("Select ImageInformation.imageid, ImageInformation.rating, " "ImagePositions.latitudeNumber, ImagePositions.longitudeNumber " "FROM ImageInformation INNER JOIN ImagePositions " " ON ImageInformation.imageid = ImagePositions.imageid " " WHERE (ImagePositions.latitudeNumber>? AND ImagePositions.latitudeNumber? AND ImagePositions.longitudeNumberdb->execSql(QString::fromUtf8("DELETE FROM ImagePositions WHERE imageid=?;"), imageID); fields |= DatabaseFields::ItemPositionsAll; d->db->execSql(QString::fromUtf8("DELETE FROM ImageCopyright WHERE imageid=?;"), imageID); d->db->execSql(QString::fromUtf8("DELETE FROM ImageComments WHERE imageid=?;"), imageID); fields |= DatabaseFields::ItemCommentsAll; d->db->execSql(QString::fromUtf8("DELETE FROM ImageMetadata WHERE imageid=?;"), imageID); fields |= DatabaseFields::ImageMetadataAll; d->db->execSql(QString::fromUtf8("DELETE FROM VideoMetadata WHERE imageid=?;"), imageID); fields |= DatabaseFields::VideoMetadataAll; d->db->recordChangeset(ImageChangeset(imageID, fields)); QList tagIds = getItemTagIDs(imageID); if (!tagIds.isEmpty()) { d->db->execSql(QString::fromUtf8("DELETE FROM ImageTags WHERE imageid=?;"), imageID); d->db->recordChangeset(ImageTagChangeset(imageID, tagIds, ImageTagChangeset::RemovedAll)); } QList properties = getImageTagProperties(imageID); if (!properties.isEmpty()) { QList tagIds; foreach (const ImageTagProperty& property, properties) { tagIds << property.imageId; } d->db->execSql(QString::fromUtf8("DELETE FROM ImageTagProperties WHERE imageid=?;"), imageID); d->db->recordChangeset(ImageTagChangeset(imageID, tagIds, ImageTagChangeset::PropertiesChanged)); } } bool CoreDB::integrityCheck() { QList values; d->db->execDBAction(d->db->getDBAction(QString::fromUtf8("checkCoreDbIntegrity")), &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) { 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 CoreDB::vacuum() { d->db->execDBAction(d->db->getDBAction(QString::fromUtf8("vacuumCoreDB"))); } void CoreDB::readSettings() { KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(d->configGroupName); d->recentlyAssignedTags = group.readEntry(d->configRecentlyUsedTags, QList()); } void CoreDB::writeSettings() { KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(d->configGroupName); group.writeEntry(d->configRecentlyUsedTags, d->recentlyAssignedTags); } } // namespace Digikam diff --git a/core/libs/database/coredb/coredbschemaupdater.cpp b/core/libs/database/coredb/coredbschemaupdater.cpp index 2b123f852a..3bebbc0c05 100644 --- a/core/libs/database/coredb/coredbschemaupdater.cpp +++ b/core/libs/database/coredb/coredbschemaupdater.cpp @@ -1,1591 +1,1591 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2007-04-16 * Description : Core database Schema updater * * Copyright (C) 2007-2012 by Marcel Wiesweg * Copyright (C) 2009-2018 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 "coredbschemaupdater.h" // Qt includes #include #include #include #include #include // KDE includes #include #include // Local includes #include "drawdecoder.h" #include "digikam_debug.h" #include "coredbbackend.h" #include "coredbtransaction.h" #include "coredbchecker.h" #include "collectionmanager.h" #include "collectionlocation.h" #include "collectionscanner.h" #include "itemquerybuilder.h" #include "collectionscannerobserver.h" #include "digikam_config.h" namespace Digikam { int CoreDbSchemaUpdater::schemaVersion() { return 10; } int CoreDbSchemaUpdater::filterSettingsVersion() { return 8; } int CoreDbSchemaUpdater::uniqueHashVersion() { return 2; } bool CoreDbSchemaUpdater::isUniqueHashUpToDate() { return CoreDbAccess().db()->getUniqueHashVersion() >= uniqueHashVersion(); } // -------------------------------------------------------------------------------------- class Q_DECL_HIDDEN CoreDbSchemaUpdater::Private { public: explicit Private() : setError(false), backend(0), albumDB(0), dbAccess(0), observer(0) { } bool setError; QVariant currentVersion; QVariant currentRequiredVersion; CoreDbBackend* backend; CoreDB* albumDB; DbEngineParameters parameters; // legacy CoreDbAccess* dbAccess; QString lastErrorMessage; InitializationObserver* observer; }; CoreDbSchemaUpdater::CoreDbSchemaUpdater(CoreDB* const albumDB, CoreDbBackend* const backend, DbEngineParameters parameters) : d(new Private) { d->backend = backend; d->albumDB = albumDB; d->parameters = parameters; } CoreDbSchemaUpdater::~CoreDbSchemaUpdater() { delete d; } void CoreDbSchemaUpdater::setCoreDbAccess(CoreDbAccess* const dbAccess) { d->dbAccess = dbAccess; } const QString CoreDbSchemaUpdater::getLastErrorMessage() { return d->lastErrorMessage; } bool CoreDbSchemaUpdater::update() { qCDebug(DIGIKAM_COREDB_LOG) << "Core database: running schema update"; bool success = startUpdates(); // cancelled? if (d->observer && !d->observer->continueQuery()) { return false; } // even on failure, try to set current version - it may have incremented setVersionSettings(); if (!success) { return false; } updateFilterSettings(); if (d->observer) { d->observer->finishedSchemaUpdate(InitializationObserver::UpdateSuccess); } return success; } void CoreDbSchemaUpdater::setVersionSettings() { if (d->currentVersion.isValid()) { d->albumDB->setSetting(QLatin1String("DBVersion"), QString::number(d->currentVersion.toInt())); } if (d->currentRequiredVersion.isValid()) { d->albumDB->setSetting(QLatin1String("DBVersionRequired"), QString::number(d->currentRequiredVersion.toInt())); } } static QVariant safeToVariant(const QString& s) { if (s.isEmpty()) { return QVariant(); } else { return s.toInt(); } } void CoreDbSchemaUpdater::readVersionSettings() { d->currentVersion = safeToVariant(d->albumDB->getSetting(QLatin1String("DBVersion"))); d->currentRequiredVersion = safeToVariant(d->albumDB->getSetting(QLatin1String("DBVersionRequired"))); } void CoreDbSchemaUpdater::setObserver(InitializationObserver* const observer) { d->observer = observer; } bool CoreDbSchemaUpdater::startUpdates() { if (!d->parameters.isSQLite()) { // Do we have sufficient privileges QStringList insufficientRights; CoreDbPrivilegesChecker checker(d->parameters); if (!checker.checkPrivileges(insufficientRights)) { qCDebug(DIGIKAM_COREDB_LOG) << "Core database: insufficient rights on database."; QString errorMsg = i18n("You have insufficient privileges on the database.\n" "Following privileges are not assigned to you:\n %1\n" "Check your privileges on the database and restart digiKam.", insufficientRights.join(QLatin1String(",\n"))); d->lastErrorMessage = errorMsg; if (d->observer) { d->observer->error(errorMsg); d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); } return false; } } // First step: do we have an empty database? QStringList tables = d->backend->tables(); if (tables.contains(QLatin1String("Albums"), Qt::CaseInsensitive)) { // Find out schema version of db file readVersionSettings(); qCDebug(DIGIKAM_COREDB_LOG) << "Core database: have a structure version " << d->currentVersion.toInt(); // We absolutely require the DBVersion setting if (!d->currentVersion.isValid()) { // Something is damaged. Give up. qCDebug(DIGIKAM_COREDB_LOG) << "Core database: version not available! Giving up schema upgrading."; QString errorMsg = i18n("The database is not valid: " "the \"DBVersion\" setting does not exist. " "The current database schema version cannot be verified. " "Try to start with an empty database. "); d->lastErrorMessage=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. if (d->currentVersion.toInt() > schemaVersion()) { // trying to open a database with a more advanced than this CoreDbSchemaUpdater supports if (d->currentRequiredVersion.isValid() && d->currentRequiredVersion.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 too recent.) " "Please use the more recent version of digiKam that you used before. "); d->lastErrorMessage=errorMsg; if (d->observer) { d->observer->error(errorMsg); d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); } return false; } } else { return makeUpdates(); } } else { qCDebug(DIGIKAM_COREDB_LOG) << "Core database: no database file available"; // Legacy handling? // first test if there are older files that need to be upgraded. // This applies to "digikam.db" for 0.7 and "digikam3.db" for 0.8 and 0.9, // all only SQLite databases. // Version numbers used in this source file are a bit confused for the historic versions. // Version 1 is 0.6 (no db), Version 2 is 0.7 (SQLite 2), // Version 3 is 0.8-0.9, // Version 3 wrote the setting "DBVersion", "1", // Version 4 is 0.10, the digikam3.db file copied to digikam4.db, // no schema changes. // Version 4 writes "4", and from now on version x writes "x". // Version 5 includes the schema changes from 0.9 to 0.10 // Version 6 brought new tables for history and ImageTagProperties, with version 2.0 // Version 7 brought the VideoMetadata table with 3.0 if (d->parameters.isSQLite()) { QFileInfo currentDBFile(d->parameters.databaseNameCore); QFileInfo digikam3DB(currentDBFile.dir(), QLatin1String("digikam3.db")); if (digikam3DB.exists()) { if (!copyV3toV4(digikam3DB.filePath(), currentDBFile.filePath())) { return false; } // d->currentVersion is now 4; return makeUpdates(); } } // No legacy handling: start with a fresh db if (!createDatabase() || !createFilterSettings()) { QString errorMsg = i18n("Failed to create tables in database.\n ") + d->backend->lastError(); d->lastErrorMessage = errorMsg; if (d->observer) { d->observer->error(errorMsg); d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); } return false; } return true; } } bool CoreDbSchemaUpdater::beginWrapSchemaUpdateStep() { if (!d->backend->beginTransaction()) { QFileInfo currentDBFile(d->parameters.databaseNameCore); QString errorMsg = i18n("Failed to open a database transaction on your database file \"%1\". " "This is unusual. Please check that you can access the file and no " "other process has currently locked the file. " "If the problem persists you can get help from the digikam developers mailing list (see www.digikam.org/support). " "As well, please have a look at what digiKam prints on the console. ", QDir::toNativeSeparators(currentDBFile.filePath())); d->observer->error(errorMsg); d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); return false; } return true; } bool CoreDbSchemaUpdater::endWrapSchemaUpdateStep(bool stepOperationSuccess, const QString& errorMsg) { if (!stepOperationSuccess) { d->backend->rollbackTransaction(); if (d->observer) { // error or cancelled? if (!d->observer->continueQuery()) { qCDebug(DIGIKAM_COREDB_LOG) << "Core database: schema update cancelled by user"; } else if (!d->setError) { d->observer->error(errorMsg); d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); } } return false; } qCDebug(DIGIKAM_COREDB_LOG) << "Core database: success updating to version " << d->currentVersion.toInt(); d->backend->commitTransaction(); return true; } bool CoreDbSchemaUpdater::makeUpdates() { qCDebug(DIGIKAM_COREDB_LOG) << "Core database: makeUpdates " << d->currentVersion.toInt() << " to " << schemaVersion(); if (d->currentVersion.toInt() < schemaVersion()) { if (d->currentVersion.toInt() < 5) { if (!beginWrapSchemaUpdateStep()) { return false; } // v4 was always SQLite QFileInfo currentDBFile(d->parameters.databaseNameCore); QString errorMsg = i18n("The schema updating process from version 4 to 6 failed, " "caused by an error that we did not expect. " "You can try to discard your old database and start with an empty one. " "(In this case, please move the database files " "\"%1\" and \"%2\" from the directory \"%3\"). " "More probably you will want to report this error to the digikam developers mailing list (see www.digikam.org/support). " "As well, please have a look at what digiKam prints on the console. ", QLatin1String("digikam3.db"), QLatin1String("digikam4.db"), QDir::toNativeSeparators(currentDBFile.dir().path())); if (!endWrapSchemaUpdateStep(updateV4toV7(), errorMsg)) { return false; } qCDebug(DIGIKAM_COREDB_LOG) << "Core database: success updating v4 to v6"; // Still set these even in >= 1.4 because 0.10 - 1.3 may want to apply the updates if not set setLegacySettingEntries(); } // Incremental updates, starting from version 5 - for (int v = d->currentVersion.toInt(); v < schemaVersion(); v++) + for (int v = d->currentVersion.toInt() ; v < schemaVersion() ; ++v) { int targetVersion = v + 1; if (!beginWrapSchemaUpdateStep()) { return false; } QString errorMsg = i18n("Failed to update the database schema from version %1 to version %2. " "Please read the error messages printed on the console and " "report this error as a bug at bugs.kde.org. ", d->currentVersion.toInt(), targetVersion); if (!endWrapSchemaUpdateStep(updateToVersion(targetVersion), errorMsg)) { return false; } qCDebug(DIGIKAM_COREDB_LOG) << "Core database: success updating to version " << d->currentVersion.toInt(); } // add future updates here } return true; } void CoreDbSchemaUpdater::defaultFilterSettings(QStringList& defaultItemFilter, QStringList& defaultVideoFilter, QStringList& defaultAudioFilter) { //NOTE for updating: //When changing anything here, just increment filterSettingsVersion() so that the changes take effect // https://en.wikipedia.org/wiki/Image_file_formats defaultItemFilter << QLatin1String("jpg") << QLatin1String("jpeg") << QLatin1String("jpe") // JPEG << QLatin1String("jp2") << QLatin1String("j2k") << QLatin1String("jpx") << QLatin1String("jpc") << QLatin1String("pgx") // JPEG-2000 << QLatin1String("tif") << QLatin1String("tiff") // TIFF << QLatin1String("png") // PNG << QLatin1String("gif") << QLatin1String("xpm") << QLatin1String("ppm") << QLatin1String("pnm") << QLatin1String("pgf") << QLatin1String("bmp") << QLatin1String("pcx") << QLatin1String("webp"); // Raster graphics editor containers: https://en.wikipedia.org/wiki/Raster_graphics_editor defaultItemFilter << QLatin1String("xcf") << QLatin1String("psd") << QLatin1String("psb") << QLatin1String("kra") << QLatin1String("ora"); // Raw images: https://en.wikipedia.org/wiki/Raw_image_format defaultItemFilter << DRawDecoder::rawFilesList(); // Video files: https://en.wikipedia.org/wiki/Video_file_format defaultVideoFilter << QLatin1String("mpeg") << QLatin1String("mpg") << QLatin1String("mpo") << QLatin1String("mpe") << QLatin1String("mts") << QLatin1String("vob") // MPEG << QLatin1String("avi") << QLatin1String("divx") // RIFF << QLatin1String("wmv") << QLatin1String("wmf") << QLatin1String("asf") // ASF << QLatin1String("mp4") << QLatin1String("3gp") << QLatin1String("mov") << QLatin1String("3g2") << QLatin1String("m4v") << QLatin1String("m2v") // QuickTime << QLatin1String("mkv") << QLatin1String("webm") // Matroska << QLatin1String("mng"); // Animated PNG image // Audio files: https://en.wikipedia.org/wiki/Audio_file_format defaultAudioFilter << QLatin1String("ogg") << QLatin1String("oga") << QLatin1String("flac") << QLatin1String("wv") << QLatin1String("ape") // Linux audio << QLatin1String("mpc") << QLatin1String("au") // Linux audio << QLatin1String("m4b") << QLatin1String("aax") << QLatin1String("aa") // Book audio << QLatin1String("mp3") << QLatin1String("aac") // MPEG based audio << QLatin1String("m4a") << QLatin1String("m4p") << QLatin1String("caf") << QLatin1String("aiff") // Apple audio << QLatin1String("wma") << QLatin1String("wav"); // Windows audio } void CoreDbSchemaUpdater::defaultIgnoreDirectoryFilterSettings(QStringList& defaultIgnoreDirectoryFilter) { // NOTE: when update this section, // just increment filterSettingsVersion() so that the changes take effect defaultIgnoreDirectoryFilter << QLatin1String("@eaDir"); } bool CoreDbSchemaUpdater::createFilterSettings() { QStringList defaultItemFilter, defaultVideoFilter, defaultAudioFilter, defaultIgnoreDirectoryFilter; defaultFilterSettings(defaultItemFilter, defaultVideoFilter, defaultAudioFilter); defaultIgnoreDirectoryFilterSettings(defaultIgnoreDirectoryFilter); d->albumDB->setFilterSettings(defaultItemFilter, defaultVideoFilter, defaultAudioFilter); d->albumDB->setIgnoreDirectoryFilterSettings(defaultIgnoreDirectoryFilter); d->albumDB->setSetting(QLatin1String("FilterSettingsVersion"), QString::number(filterSettingsVersion())); d->albumDB->setSetting(QLatin1String("DcrawFilterSettingsVersion"), QString::number(DRawDecoder::rawFilesVersion())); return true; } bool CoreDbSchemaUpdater::updateFilterSettings() { QString filterVersion = d->albumDB->getSetting(QLatin1String("FilterSettingsVersion")); QString dcrawFilterVersion = d->albumDB->getSetting(QLatin1String("DcrawFilterSettingsVersion")); if (filterVersion.toInt() < filterSettingsVersion() || dcrawFilterVersion.toInt() < DRawDecoder::rawFilesVersion()) { createFilterSettings(); } return true; } bool CoreDbSchemaUpdater::createDatabase() { if ( createTables() && createIndices() && createTriggers()) { setLegacySettingEntries(); d->currentVersion = schemaVersion(); // if we start with the V2 hash, version 6 is required d->albumDB->setUniqueHashVersion(uniqueHashVersion()); d->currentRequiredVersion = schemaVersion(); /* // Digikam for database version 5 can work with version 6, though not using the new features d->currentRequiredVersion = 5; */ return true; } else { return false; } } bool CoreDbSchemaUpdater::createTables() { return d->backend->execDBAction(d->backend->getDBAction(QLatin1String("CreateDB"))); } bool CoreDbSchemaUpdater::createIndices() { // TODO: see which more indices are needed // create indices return d->backend->execDBAction(d->backend->getDBAction(QLatin1String("CreateIndices"))); } bool CoreDbSchemaUpdater::createTriggers() { return d->backend->execDBAction(d->backend->getDBAction(QLatin1String("CreateTriggers"))); } bool CoreDbSchemaUpdater::updateUniqueHash() { if (isUniqueHashUpToDate()) { return true; } readVersionSettings(); { CoreDbTransaction transaction; CoreDbAccess().db()->setUniqueHashVersion(uniqueHashVersion()); CollectionScanner scanner; scanner.setNeedFileCount(true); scanner.setUpdateHashHint(); if (d->observer) { d->observer->connectCollectionScanner(&scanner); scanner.setObserver(d->observer); } scanner.completeScan(); // earlier digikam does not know about the hash if (d->currentRequiredVersion.toInt() < 6) { d->currentRequiredVersion = 6; setVersionSettings(); } } return true; } bool CoreDbSchemaUpdater::performUpdateToVersion(const QString& actionName, int newVersion, int newRequiredVersion) { if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->moreSchemaUpdateSteps(1); } DbEngineAction updateAction = d->backend->getDBAction(actionName); if (updateAction.name.isNull()) { QString errorMsg = i18n("The database update action cannot be found. Please ensure that " "the dbconfig.xml file of the current version of digiKam is installed " "at the correct place. "); } if (!d->backend->execDBAction(updateAction)) { qCDebug(DIGIKAM_COREDB_LOG) << "Core database: schema update to V" << newVersion << "failed!"; // resort to default error message, set above return false; } if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->schemaUpdateProgress(i18n("Updated schema to version %1.", newVersion)); } d->currentVersion = newVersion; // Digikam for database version 5 can work with version 6, though not using the new features // Note: We do not upgrade the uniqueHash d->currentRequiredVersion = newRequiredVersion; return true; } bool CoreDbSchemaUpdater::updateToVersion(int targetVersion) { if (d->currentVersion != targetVersion-1) { qCDebug(DIGIKAM_COREDB_LOG) << "Core database: updateToVersion performs only incremental updates. Called to update from" << d->currentVersion << "to" << targetVersion << ", aborting."; return false; } switch (targetVersion) { case 6: // Digikam for database version 5 can work with version 6, though not using the new features // Note: We do not upgrade the uniqueHash return performUpdateToVersion(QLatin1String("UpdateSchemaFromV5ToV6"), 6, 5); case 7: // Digikam for database version 5 and 6 can work with version 7, though not using the support for video files. return performUpdateToVersion(QLatin1String("UpdateSchemaFromV6ToV7"), 7, 5); // NOTE: If you add a new update step, please check the d->currentVersion at the bottom of updateV4toV7 // If the update already comes with createTables, createTriggers, we don't need the extra update here case 8: // Digikam for database version 7 can work with version 8, now using COLLATE utf8_general_ci for MySQL. return performUpdateToVersion(QLatin1String("UpdateSchemaFromV7ToV9"), 8, 5); case 9: // Digikam for database version 8 can work with version 9, now using COLLATE utf8_general_ci for MySQL. return performUpdateToVersion(QLatin1String("UpdateSchemaFromV7ToV9"), 9, 5); case 10: // Digikam for database version 9 can work with version 10, remove ImageHaarMatrix table and add manualOrder column. return performUpdateToVersion(QLatin1String("UpdateSchemaFromV9ToV10"), 10, 5); default: qCDebug(DIGIKAM_COREDB_LOG) << "Core database: unsupported update to version" << targetVersion; return false; } } bool CoreDbSchemaUpdater::copyV3toV4(const QString& digikam3DBPath, const QString& currentDBPath) { if (d->observer) { d->observer->moreSchemaUpdateSteps(2); } d->backend->close(); // We cannot use KIO here because KIO only works from the main thread QFile oldFile(digikam3DBPath); QFile newFile(currentDBPath); // QFile won't override. Remove the empty db file created when a non-existent file is opened newFile.remove(); if (!oldFile.copy(currentDBPath)) { QString errorMsg = i18n("Failed to copy the old database file (\"%1\") " "to its new location (\"%2\"). " "Error message: \"%3\". " "Please make sure that the file can be copied, " "or delete it.", digikam3DBPath, currentDBPath, oldFile.errorString()); d->lastErrorMessage = errorMsg; d->setError = true; if (d->observer) { d->observer->error(errorMsg); d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); } return false; } if (d->observer) { d->observer->schemaUpdateProgress(i18n("Copied database file")); } if (!d->backend->open(d->parameters)) { QString errorMsg = i18n("The old database file (\"%1\") has been copied " "to the new location (\"%2\") but it cannot be opened. " "Please delete both files and try again, " "starting with an empty database. ", digikam3DBPath, currentDBPath); d->lastErrorMessage = errorMsg; d->setError = true; if (d->observer) { d->observer->error(errorMsg); d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); } return false; } if (d->observer) { d->observer->schemaUpdateProgress(i18n("Opened new database file")); } d->currentVersion = 4; return true; } static QStringList cleanUserFilterString(const QString& filterString) { // splits by either ; or space, removes "*.", trims QStringList filterList; QString wildcard(QLatin1String("*.")); QChar dot(QLatin1Char('.')); QChar sep(QLatin1Char(';')); int i = filterString.indexOf( sep ); if ( i == -1 && filterString.indexOf(QLatin1Char(' ')) != -1 ) { sep = QChar(QLatin1Char(' ')); } QStringList sepList = filterString.split(sep, QString::SkipEmptyParts); foreach(const QString& f, sepList) { if (f.startsWith(wildcard)) { filterList << f.mid(2).trimmed().toLower(); } else { filterList << f.trimmed().toLower(); } } return filterList; } bool CoreDbSchemaUpdater::updateV4toV7() { qCDebug(DIGIKAM_COREDB_LOG) << "Core database : running updateV4toV7"; if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->moreSchemaUpdateSteps(11); } // This update was introduced from digikam version 0.9 to digikam 0.10 // We operator on an SQLite3 database under a transaction (which will be rolled back on error) // --- Make space for new tables --- if (!d->backend->execSql(QString::fromUtf8("ALTER TABLE Albums RENAME TO AlbumsV3;"))) { return false; } if (!d->backend->execSql(QString::fromUtf8("ALTER TABLE Images RENAME TO ImagesV3;"))) { return false; } if (!d->backend->execSql(QString::fromUtf8("ALTER TABLE Searches RENAME TO SearchesV3;"))) { return false; } qCDebug(DIGIKAM_COREDB_LOG) << "Core database: moved tables"; // --- Drop some triggers and indices --- // Don't check for errors here. The "IF EXISTS" clauses seem not supported in SQLite d->backend->execSql(QString::fromUtf8("DROP TRIGGER delete_album;")); d->backend->execSql(QString::fromUtf8("DROP TRIGGER delete_image;")); d->backend->execSql(QString::fromUtf8("DROP TRIGGER delete_tag;")); d->backend->execSql(QString::fromUtf8("DROP TRIGGER insert_tagstree;")); d->backend->execSql(QString::fromUtf8("DROP TRIGGER delete_tagstree;")); d->backend->execSql(QString::fromUtf8("DROP TRIGGER move_tagstree;")); d->backend->execSql(QString::fromUtf8("DROP INDEX dir_index;")); d->backend->execSql(QString::fromUtf8("DROP INDEX tag_index;")); if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->schemaUpdateProgress(i18n("Prepared table creation")); } qCDebug(DIGIKAM_COREDB_LOG) << "Core database: dropped triggers"; // --- Create new tables --- if (!createTables() || !createIndices()) { return false; } if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->schemaUpdateProgress(i18n("Created tables")); } // --- Populate AlbumRoots (from config) --- KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(QLatin1String("Album Settings")); QString albumLibraryPath = group.readEntry(QLatin1String("Album Path"), QString()); if (albumLibraryPath.isEmpty()) { qCDebug(DIGIKAM_COREDB_LOG) << "Core database: Album library path from config file is empty. Aborting update."; QString errorMsg = i18n("No album library path has been found in the configuration file. " "Giving up the schema updating process. " "Please try with an empty database, or repair your configuration."); d->lastErrorMessage = errorMsg; d->setError = true; if (d->observer) { d->observer->error(errorMsg); d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); } return false; } QUrl albumLibrary(QUrl::fromLocalFile(albumLibraryPath)); CollectionLocation location = CollectionManager::instance()->addLocation(albumLibrary, albumLibrary.fileName()); if (location.isNull()) { qCDebug(DIGIKAM_COREDB_LOG) << "Core database: failure to create a collection location. Aborting update."; QString errorMsg = i18n("There was an error associating your albumLibraryPath (\"%1\") " "with a storage volume of your system. " "This problem may indicate that there is a problem with your installation. " "If you are working on Linux, check that HAL is installed and running. " "In any case, you can seek advice from the digikam developers mailing list (see www.digikam.org/support). " "The database updating process will now be aborted because we do not want " "to create a new database based on false assumptions from a broken installation.", albumLibraryPath); d->lastErrorMessage = errorMsg; d->setError = true; if (d->observer) { d->observer->error(errorMsg); d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); } return false; } if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->schemaUpdateProgress(i18n("Configured one album root")); } qCDebug(DIGIKAM_COREDB_LOG) << "Core database: inserted album root"; // --- With the album root, populate albums --- if (!d->backend->execSql(QString::fromUtf8( "REPLACE INTO Albums " " (id, albumRoot, relativePath, date, caption, collection, icon) " "SELECT id, ?, url, date, caption, collection, icon " " FROM AlbumsV3;" ), location.id()) ) { return false; } if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->schemaUpdateProgress(i18n("Imported albums")); } qCDebug(DIGIKAM_COREDB_LOG) << "Core database: populated albums"; // --- Add images --- if (!d->backend->execSql(QString::fromUtf8( "REPLACE INTO Images " " (id, album, name, status, category, modificationDate, fileSize, uniqueHash) " "SELECT id, dirid, name, ?, ?, NULL, NULL, NULL" " FROM ImagesV3;" ), DatabaseItem::Visible, DatabaseItem::UndefinedCategory) ) { return false; } if (!d->dbAccess->backend()->execSql(QString::fromUtf8( "REPLACE INTO ImageInformation (imageId) SELECT id FROM Images;")) ) { return false; } // remove orphan images that would not be removed by CollectionScanner d->backend->execSql(QString::fromUtf8("DELETE FROM Images WHERE album NOT IN (SELECT id FROM Albums);")); if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->schemaUpdateProgress(i18n("Imported images information")); } qCDebug(DIGIKAM_COREDB_LOG) << "Core database: populated Images"; // --- Port searches --- if (!d->backend->execSql(QString::fromUtf8( "REPLACE INTO Searches " " (id, type, name, query) " "SELECT id, ?, name, url" " FROM SearchesV3;"), DatabaseSearch::LegacyUrlSearch) ) { return false; } SearchInfo::List sList = d->albumDB->scanSearches(); for (SearchInfo::List::const_iterator it = sList.constBegin(); it != sList.constEnd(); ++it) { QUrl url((*it).query); ItemQueryBuilder builder; QString query = builder.convertFromUrlToXml(url); QString name = (*it).name; if (name == i18n("Last Search")) { name = i18n("Last Search (0.9)"); } if (QUrlQuery(url).queryItemValue(QLatin1String("type")) == QLatin1String("datesearch")) { d->albumDB->updateSearch((*it).id, DatabaseSearch::TimeLineSearch, name, query); } else if (QUrlQuery(url).queryItemValue(QLatin1String("1.key")) == QLatin1String("keyword")) { d->albumDB->updateSearch((*it).id, DatabaseSearch::KeywordSearch, name, query); } else { d->albumDB->updateSearch((*it).id, DatabaseSearch::AdvancedSearch, name, query); } } // --- Create triggers --- if (!createTriggers()) { return false; } qCDebug(DIGIKAM_COREDB_LOG) << "Core database: created triggers"; // --- Populate name filters --- createFilterSettings(); // --- Set user settings from config --- QStringList defaultItemFilter, defaultVideoFilter, defaultAudioFilter; defaultFilterSettings(defaultItemFilter, defaultVideoFilter, defaultAudioFilter); QSet configItemFilter, configVideoFilter, configAudioFilter; configItemFilter = cleanUserFilterString(group.readEntry(QLatin1String("File Filter"), QString())).toSet(); configItemFilter += cleanUserFilterString(group.readEntry(QLatin1String("Raw File Filter"), QString())).toSet(); configVideoFilter = cleanUserFilterString(group.readEntry(QLatin1String("Movie File Filter"), QString())).toSet(); configAudioFilter = cleanUserFilterString(group.readEntry(QLatin1String("Audio File Filter"), QString())).toSet(); // remove those that are included in the default filter configItemFilter.subtract(defaultItemFilter.toSet()); configVideoFilter.subtract(defaultVideoFilter.toSet()); configAudioFilter.subtract(defaultAudioFilter.toSet()); d->albumDB->setUserFilterSettings(configItemFilter.toList(), configVideoFilter.toList(), configAudioFilter.toList()); qCDebug(DIGIKAM_COREDB_LOG) << "Core database: set initial filter settings with user settings" << configItemFilter; if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->schemaUpdateProgress(i18n("Initialized and imported file suffix filter")); } // --- do a full scan --- CollectionScanner scanner; if (d->observer) { d->observer->connectCollectionScanner(&scanner); scanner.setObserver(d->observer); } scanner.completeScan(); if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->schemaUpdateProgress(i18n("Did the initial full scan")); } // --- Port date, comment and rating (_after_ the scan) --- // Port ImagesV3.date -> ImageInformation.creationDate if (!d->backend->execSql(QString::fromUtf8( "UPDATE ImageInformation SET " " creationDate=(SELECT datetime FROM ImagesV3 WHERE ImagesV3.id=ImageInformation.imageid) " "WHERE imageid IN (SELECT id FROM ImagesV3);") ) ) { return false; } if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->schemaUpdateProgress(i18n("Imported creation dates")); } // Port ImagesV3.comment to ItemComments // An author of NULL will inhibt the UNIQUE restriction to take effect (but #189080). Work around. d->backend->execSql(QString::fromUtf8( "DELETE FROM ImageComments WHERE " "type=? AND language=? AND author IS NULL " "AND imageid IN ( SELECT id FROM ImagesV3 ); "), (int)DatabaseComment::Comment, QLatin1String("x-default")); if (!d->backend->execSql(QString::fromUtf8( "REPLACE INTO ImageComments " " (imageid, type, language, comment) " "SELECT id, ?, ?, caption FROM ImagesV3;" ), (int)DatabaseComment::Comment, QLatin1String("x-default")) ) { return false; } if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->schemaUpdateProgress(i18n("Imported comments")); } // Port rating storage in ImageProperties to ItemInformation if (!d->backend->execSql(QString::fromUtf8( "UPDATE ImageInformation SET " " rating=(SELECT value FROM ImageProperties " " WHERE ImageInformation.imageid=ImageProperties.imageid AND ImageProperties.property=?) " "WHERE imageid IN (SELECT imageid FROM ImageProperties WHERE property=?);" ), QString::fromUtf8("Rating"), QString::fromUtf8("Rating")) ) { return false; } d->backend->execSql(QString::fromUtf8("DELETE FROM ImageProperties WHERE property=?;"), QString::fromUtf8("Rating")); d->backend->execSql(QString::fromUtf8("UPDATE ImageInformation SET rating=0 WHERE rating<0;")); if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->schemaUpdateProgress(i18n("Imported ratings")); } // --- Drop old tables --- d->backend->execSql(QString::fromUtf8("DROP TABLE ImagesV3;")); d->backend->execSql(QString::fromUtf8("DROP TABLE AlbumsV3;")); d->backend->execSql(QString::fromUtf8("DROP TABLE SearchesV3;")); if (d->observer) { d->observer->schemaUpdateProgress(i18n("Dropped v3 tables")); } d->currentRequiredVersion = 5; d->currentVersion = 7; qCDebug(DIGIKAM_COREDB_LOG) << "Core database: returning true from updating to 5"; return true; } void CoreDbSchemaUpdater::setLegacySettingEntries() { d->albumDB->setSetting(QLatin1String("preAlpha010Update1"), QLatin1String("true")); d->albumDB->setSetting(QLatin1String("preAlpha010Update2"), QLatin1String("true")); d->albumDB->setSetting(QLatin1String("preAlpha010Update3"), QLatin1String("true")); d->albumDB->setSetting(QLatin1String("beta010Update1"), QLatin1String("true")); d->albumDB->setSetting(QLatin1String("beta010Update2"), QLatin1String("true")); } // ---------- Legacy code ------------ void CoreDbSchemaUpdater::preAlpha010Update1() { QString hasUpdate = d->albumDB->getSetting(QLatin1String("preAlpha010Update1")); if (!hasUpdate.isNull()) { return; } if (!d->backend->execSql(QString::fromUtf8("ALTER TABLE Searches RENAME TO SearchesV3;"))) { return; } if ( !d->backend->execSql( QString::fromUtf8( "CREATE TABLE IF NOT EXISTS Searches \n" " (id INTEGER PRIMARY KEY, \n" " type INTEGER, \n" " name TEXT NOT NULL, \n" " query TEXT NOT NULL);" ) )) { return; } if (!d->backend->execSql(QString::fromUtf8( "REPLACE INTO Searches " " (id, type, name, query) " "SELECT id, ?, name, url" " FROM SearchesV3;"), DatabaseSearch::LegacyUrlSearch) ) { return; } SearchInfo::List sList = d->albumDB->scanSearches(); for (SearchInfo::List::const_iterator it = sList.constBegin(); it != sList.constEnd(); ++it) { QUrl url((*it).query); ItemQueryBuilder builder; QString query = builder.convertFromUrlToXml(url); if (QUrlQuery(url).queryItemValue(QLatin1String("type")) == QLatin1String("datesearch")) { d->albumDB->updateSearch((*it).id, DatabaseSearch::TimeLineSearch, (*it).name, query); } else if (QUrlQuery(url).queryItemValue(QLatin1String("1.key")) == QLatin1String("keyword")) { d->albumDB->updateSearch((*it).id, DatabaseSearch::KeywordSearch, (*it).name, query); } else { d->albumDB->updateSearch((*it).id, DatabaseSearch::AdvancedSearch, (*it).name, query); } } d->backend->execSql(QString::fromUtf8("DROP TABLE SearchesV3;")); d->albumDB->setSetting(QLatin1String("preAlpha010Update1"), QLatin1String("true")); } void CoreDbSchemaUpdater::preAlpha010Update2() { QString hasUpdate = d->albumDB->getSetting(QLatin1String("preAlpha010Update2")); if (!hasUpdate.isNull()) { return; } if (!d->backend->execSql(QString::fromUtf8("ALTER TABLE ImagePositions RENAME TO ItemPositionsTemp;"))) { return; } if (!d->backend->execSql(QString::fromUtf8("ALTER TABLE ImageMetadata RENAME TO ImageMetadataTemp;"))) { return; } d->backend->execSql( QString::fromUtf8("CREATE TABLE ItemPositions\n" " (imageid INTEGER PRIMARY KEY,\n" " latitude TEXT,\n" " latitudeNumber REAL,\n" " longitude TEXT,\n" " longitudeNumber REAL,\n" " altitude REAL,\n" " orientation REAL,\n" " tilt REAL,\n" " roll REAL,\n" " accuracy REAL,\n" " description TEXT);") ); d->backend->execSql(QString::fromUtf8("REPLACE INTO ImagePositions " " (imageid, latitude, latitudeNumber, longitude, longitudeNumber, " " altitude, orientation, tilt, roll, accuracy, description) " "SELECT imageid, latitude, latitudeNumber, longitude, longitudeNumber, " " altitude, orientation, tilt, roll, 0, description " " FROM ItemPositionsTemp;")); d->backend->execSql( QString::fromUtf8("CREATE TABLE ImageMetadata\n" " (imageid INTEGER PRIMARY KEY,\n" " make TEXT,\n" " model TEXT,\n" " lens TEXT,\n" " aperture REAL,\n" " focalLength REAL,\n" " focalLength35 REAL,\n" " exposureTime REAL,\n" " exposureProgram INTEGER,\n" " exposureMode INTEGER,\n" " sensitivity INTEGER,\n" " flash INTEGER,\n" " whiteBalance INTEGER,\n" " whiteBalanceColorTemperature INTEGER,\n" " meteringMode INTEGER,\n" " subjectDistance REAL,\n" " subjectDistanceCategory INTEGER);") ); d->backend->execSql( QString::fromUtf8("INSERT INTO ImageMetadata " " (imageid, make, model, lens, aperture, focalLength, focalLength35, " " exposureTime, exposureProgram, exposureMode, sensitivity, flash, whiteBalance, " " whiteBalanceColorTemperature, meteringMode, subjectDistance, subjectDistanceCategory) " "SELECT imageid, make, model, NULL, aperture, focalLength, focalLength35, " " exposureTime, exposureProgram, exposureMode, sensitivity, flash, whiteBalance, " " whiteBalanceColorTemperature, meteringMode, subjectDistance, subjectDistanceCategory " "FROM ImageMetadataTemp;")); d->backend->execSql(QString::fromUtf8("DROP TABLE ItemPositionsTemp;")); d->backend->execSql(QString::fromUtf8("DROP TABLE ImageMetadataTemp;")); d->albumDB->setSetting(QLatin1String("preAlpha010Update2"), QLatin1String("true")); } void CoreDbSchemaUpdater::preAlpha010Update3() { QString hasUpdate = d->albumDB->getSetting(QLatin1String("preAlpha010Update3")); if (!hasUpdate.isNull()) { return; } d->backend->execSql(QString::fromUtf8("DROP TABLE ItemCopyright;")); d->backend->execSql(QString::fromUtf8("CREATE TABLE ItemCopyright\n" " (imageid INTEGER,\n" " property TEXT,\n" " value TEXT,\n" " extraValue TEXT,\n" " UNIQUE(imageid, property, value, extraValue));") ); d->albumDB->setSetting(QLatin1String("preAlpha010Update3"), QLatin1String("true")); } void CoreDbSchemaUpdater::beta010Update1() { QString hasUpdate = d->albumDB->getSetting(QLatin1String("beta010Update1")); if (!hasUpdate.isNull()) { return; } // if Image has been deleted d->backend->execSql(QString::fromUtf8("DROP TRIGGER delete_image;")); d->backend->execSql(QString::fromUtf8("CREATE TRIGGER delete_image DELETE ON Images\n" "BEGIN\n" " DELETE FROM ImageTags\n" " WHERE imageid=OLD.id;\n" " DELETE From ImageHaarMatrix\n " " WHERE imageid=OLD.id;\n" " DELETE From ItemInformation\n " " WHERE imageid=OLD.id;\n" " DELETE From ImageMetadata\n " " WHERE imageid=OLD.id;\n" " DELETE From ItemPositions\n " " WHERE imageid=OLD.id;\n" " DELETE From ItemComments\n " " WHERE imageid=OLD.id;\n" " DELETE From ItemCopyright\n " " WHERE imageid=OLD.id;\n" " DELETE From ImageProperties\n " " WHERE imageid=OLD.id;\n" " UPDATE Albums SET icon=null \n " " WHERE icon=OLD.id;\n" " UPDATE Tags SET icon=null \n " " WHERE icon=OLD.id;\n" "END;")); d->albumDB->setSetting(QLatin1String("beta010Update1"), QLatin1String("true")); } void CoreDbSchemaUpdater::beta010Update2() { QString hasUpdate = d->albumDB->getSetting(QLatin1String("beta010Update2")); if (!hasUpdate.isNull()) { return; } // force rescan and creation of ImageInformation entry for videos and audio d->backend->execSql(QString::fromUtf8("DELETE FROM Images WHERE category=2 OR category=3;")); d->albumDB->setSetting(QLatin1String("beta010Update2"), QLatin1String("true")); } bool CoreDbSchemaUpdater::createTablesV3() { if (!d->backend->execSql( QString::fromUtf8("CREATE TABLE Albums\n" " (id INTEGER PRIMARY KEY,\n" " url TEXT NOT NULL UNIQUE,\n" " date DATE NOT NULL,\n" " caption TEXT,\n" " collection TEXT,\n" " icon INTEGER);") )) { return false; } if (!d->backend->execSql( QString::fromUtf8("CREATE TABLE Tags\n" " (id INTEGER PRIMARY KEY,\n" " pid INTEGER,\n" " name TEXT NOT NULL,\n" " icon INTEGER,\n" " iconkde TEXT,\n" " UNIQUE (name, pid));") )) { return false; } if (!d->backend->execSql( QString::fromUtf8("CREATE TABLE TagsTree\n" " (id INTEGER NOT NULL,\n" " pid INTEGER NOT NULL,\n" " UNIQUE (id, pid));") )) { return false; } if (!d->backend->execSql( QString::fromUtf8("CREATE TABLE Images\n" " (id INTEGER PRIMARY KEY,\n" " name TEXT NOT NULL,\n" " dirid INTEGER NOT NULL,\n" " caption TEXT,\n" " datetime DATETIME,\n" " UNIQUE (name, dirid));") )) { return false; } if (!d->backend->execSql( QString::fromUtf8("CREATE TABLE ImageTags\n" " (imageid INTEGER NOT NULL,\n" " tagid INTEGER NOT NULL,\n" " UNIQUE (imageid, tagid));") )) { return false; } if (!d->backend->execSql( QString::fromUtf8("CREATE TABLE ImageProperties\n" " (imageid INTEGER NOT NULL,\n" " property TEXT NOT NULL,\n" " value TEXT NOT NULL,\n" " UNIQUE (imageid, property));") )) { return false; } if ( !d->backend->execSql( QString::fromUtf8("CREATE TABLE Searches \n" " (id INTEGER PRIMARY KEY, \n" " name TEXT NOT NULL UNIQUE, \n" " url TEXT NOT NULL);") ) ) { return false; } if (!d->backend->execSql( QString::fromUtf8("CREATE TABLE Settings \n" "(keyword TEXT NOT NULL UNIQUE,\n" " value TEXT);") )) { return false; } // TODO: see which more indices are needed // create indices d->backend->execSql(QString::fromUtf8("CREATE INDEX dir_index ON Images (dirid);")); d->backend->execSql(QString::fromUtf8("CREATE INDEX tag_index ON ImageTags (tagid);")); // create triggers // trigger: delete from Images/ImageTags/ImageProperties // if Album has been deleted d->backend->execSql(QString::fromUtf8("CREATE TRIGGER delete_album DELETE ON Albums\n" "BEGIN\n" " DELETE FROM ImageTags\n" " WHERE imageid IN (SELECT id FROM Images WHERE dirid=OLD.id);\n" " DELETE From ImageProperties\n" " WHERE imageid IN (SELECT id FROM Images WHERE dirid=OLD.id);\n" " DELETE FROM Images\n" " WHERE dirid = OLD.id;\n" "END;")); // trigger: delete from ImageTags/ImageProperties // if Image has been deleted d->backend->execSql(QString::fromUtf8("CREATE TRIGGER delete_image DELETE ON Images\n" "BEGIN\n" " DELETE FROM ImageTags\n" " WHERE imageid=OLD.id;\n" " DELETE From ImageProperties\n " " WHERE imageid=OLD.id;\n" " UPDATE Albums SET icon=null \n " " WHERE icon=OLD.id;\n" " UPDATE Tags SET icon=null \n " " WHERE icon=OLD.id;\n" "END;")); // trigger: delete from ImageTags if Tag has been deleted d->backend->execSql(QString::fromUtf8("CREATE TRIGGER delete_tag DELETE ON Tags\n" "BEGIN\n" " DELETE FROM ImageTags WHERE tagid=OLD.id;\n" "END;")); // trigger: insert into TagsTree if Tag has been added d->backend->execSql(QString::fromUtf8("CREATE TRIGGER insert_tagstree AFTER INSERT ON Tags\n" "BEGIN\n" " INSERT INTO TagsTree\n" " SELECT NEW.id, NEW.pid\n" " UNION\n" " SELECT NEW.id, pid FROM TagsTree WHERE id=NEW.pid;\n" "END;")); // trigger: delete from TagsTree if Tag has been deleted d->backend->execSql(QString::fromUtf8("CREATE TRIGGER delete_tagstree DELETE ON Tags\n" "BEGIN\n" " DELETE FROM Tags\n" " WHERE id IN (SELECT id FROM TagsTree WHERE pid=OLD.id);\n" " DELETE FROM TagsTree\n" " WHERE id IN (SELECT id FROM TagsTree WHERE pid=OLD.id);\n" " DELETE FROM TagsTree\n" " WHERE id=OLD.id;\n" "END;")); // trigger: delete from TagsTree if Tag has been deleted d->backend->execSql(QString::fromUtf8("CREATE TRIGGER move_tagstree UPDATE OF pid ON Tags\n" "BEGIN\n" " DELETE FROM TagsTree\n" " WHERE\n" " ((id = OLD.id)\n" " OR\n" " id IN (SELECT id FROM TagsTree WHERE pid=OLD.id))\n" " AND\n" " pid IN (SELECT pid FROM TagsTree WHERE id=OLD.id);\n" " INSERT INTO TagsTree\n" " SELECT NEW.id, NEW.pid\n" " UNION\n" " SELECT NEW.id, pid FROM TagsTree WHERE id=NEW.pid\n" " UNION\n" " SELECT id, NEW.pid FROM TagsTree WHERE pid=NEW.id\n" " UNION\n" " SELECT A.id, B.pid FROM TagsTree A, TagsTree B\n" " WHERE\n" " A.pid = NEW.id AND B.id = NEW.pid;\n" "END;")); return true; } } // namespace Digikam diff --git a/core/libs/database/item/lister/itemlister_palbum.cpp b/core/libs/database/item/lister/itemlister_palbum.cpp index 1434aa22f7..e8761ad0c9 100644 --- a/core/libs/database/item/lister/itemlister_palbum.cpp +++ b/core/libs/database/item/lister/itemlister_palbum.cpp @@ -1,165 +1,165 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2007-03-20 * Description : Listing information from database - PAlbum helpers. * * Copyright (C) 2007-2012 by Marcel Wiesweg * Copyright (C) 2007-2018 by Gilles Caulier * Copyright (C) 2015 by Mohamed_Anwer * 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 "itemlister_p.h" namespace Digikam { void ItemLister::listPAlbum(ItemListerReceiver* const receiver, int albumRootId, const QString& album) { if (d->listOnlyAvailableImages) { if (!CollectionManager::instance()->locationForAlbumRootId(albumRootId).isAvailable()) { return; } } QList albumIds; if (d->recursive) { QList intAlbumIds = CoreDbAccess().db()->getAlbumAndSubalbumsForPath(albumRootId, album); if (intAlbumIds.isEmpty()) { return; } foreach (int id, intAlbumIds) { albumIds << id; } } else { int albumId = CoreDbAccess().db()->getAlbumForPath(albumRootId, album, false); if (albumId == -1) { return; } albumIds << albumId; } QList values; QString query = QString::fromUtf8("SELECT DISTINCT Images.id, Images.name, Images.album, " " ImageInformation.rating, Images.category, " " ImageInformation.format, ImageInformation.creationDate, " " Images.modificationDate, Images.fileSize, " " ImageInformation.width, ImageInformation.height " " FROM Images " " LEFT JOIN ImageInformation ON Images.id=ImageInformation.imageid " " WHERE Images.status=1 AND "); if (d->recursive) { // SQLite allows no more than 999 parameters const int maxParams = CoreDbAccess().backend()->maximumBoundValues(); - for (int i = 0 ; i < albumIds.size() ; i++) + for (int i = 0 ; i < albumIds.size() ; ++i) { QString q = query; QList ids = (albumIds.size() <= maxParams) ? albumIds : albumIds.mid(i, maxParams); i += ids.count(); QList v; CoreDbAccess access; q += QString::fromUtf8("Images.album IN ("); access.db()->addBoundValuePlaceholders(q, ids.size()); q += QString::fromUtf8(");"); access.backend()->execSql(q, ids, &v); values += v; } } else { CoreDbAccess access; query += QString::fromUtf8("Images.album = ?;"); access.backend()->execSql(query, albumIds, &values); } int width, height; for (QList::const_iterator it = values.constBegin(); it != values.constEnd();) { ItemListerRecord record; record.imageID = (*it).toLongLong(); ++it; record.name = (*it).toString(); ++it; record.albumID = (*it).toInt(); ++it; record.rating = (*it).toInt(); ++it; record.category = (DatabaseItem::Category)(*it).toInt(); ++it; record.format = (*it).toString(); ++it; record.creationDate = (*it).toDateTime(); ++it; record.modificationDate = (*it).toDateTime(); ++it; record.fileSize = d->toInt32BitSafe(it); ++it; width = (*it).toInt(); ++it; height = (*it).toInt(); ++it; record.imageSize = QSize(width, height); record.albumRootID = albumRootId; receiver->receive(record); } } QSet ItemLister::albumRootsToList() const { if (!d->listOnlyAvailableImages) { return QSet(); // invalid value, all album roots shall be listed } QList locations = CollectionManager::instance()->allAvailableLocations(); QSet ids; foreach (const CollectionLocation& location, locations) { ids << location.id(); } return ids; } } // namespace Digikam diff --git a/core/libs/database/item/scanner/itemscanner_photo.cpp b/core/libs/database/item/scanner/itemscanner_photo.cpp index 125e282a13..39ddb3434f 100644 --- a/core/libs/database/item/scanner/itemscanner_photo.cpp +++ b/core/libs/database/item/scanner/itemscanner_photo.cpp @@ -1,544 +1,544 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2007-09-19 * Description : Scanning a single item - photo metadata helper. * * Copyright (C) 2007-2013 by Marcel Wiesweg * Copyright (C) 2013-2018 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 "itemscanner_p.h" namespace Digikam { void ItemScanner::fillMetadataContainer(qlonglong imageid, ImageMetadataContainer* const container) { // read from database QVariantList fields = CoreDbAccess().db()->getImageMetadata(imageid); // check we have at least one valid field container->allFieldsNull = !hasValidField(fields); if (container->allFieldsNull) { return; } // DMetadata does all translation work QStringList strings = DMetadata::valuesToString(fields, allImageMetadataFields()); // associate with hard-coded variables container->make = strings.at(0); container->model = strings.at(1); container->lens = strings.at(2); container->aperture = strings.at(3); container->focalLength = strings.at(4); container->focalLength35 = strings.at(5); container->exposureTime = strings.at(6); container->exposureProgram = strings.at(7); container->exposureMode = strings.at(8); container->sensitivity = strings.at(9); container->flashMode = strings.at(10); container->whiteBalance = strings.at(11); container->whiteBalanceColorTemperature = strings.at(12); container->meteringMode = strings.at(13); container->subjectDistance = strings.at(14); container->subjectDistanceCategory = strings.at(15); } QString ItemScanner::iptcCorePropertyName(MetadataInfo::Field field) { // These strings are specified in DBSCHEMA.ods switch (field) { // copyright table case MetadataInfo::IptcCoreCopyrightNotice: return QLatin1String("copyrightNotice"); case MetadataInfo::IptcCoreCreator: return QLatin1String("creator"); case MetadataInfo::IptcCoreProvider: return QLatin1String("provider"); case MetadataInfo::IptcCoreRightsUsageTerms: return QLatin1String("rightsUsageTerms"); case MetadataInfo::IptcCoreSource: return QLatin1String("source"); case MetadataInfo::IptcCoreCreatorJobTitle: return QLatin1String("creatorJobTitle"); case MetadataInfo::IptcCoreInstructions: return QLatin1String("instructions"); // ImageProperties table case MetadataInfo::IptcCoreCountryCode: return QLatin1String("countryCode"); case MetadataInfo::IptcCoreCountry: return QLatin1String("country"); case MetadataInfo::IptcCoreCity: return QLatin1String("city"); case MetadataInfo::IptcCoreLocation: return QLatin1String("location"); case MetadataInfo::IptcCoreProvinceState: return QLatin1String("provinceState"); case MetadataInfo::IptcCoreIntellectualGenre: return QLatin1String("intellectualGenre"); case MetadataInfo::IptcCoreJobID: return QLatin1String("jobId"); case MetadataInfo::IptcCoreScene: return QLatin1String("scene"); case MetadataInfo::IptcCoreSubjectCode: return QLatin1String("subjectCode"); case MetadataInfo::IptcCoreContactInfoCity: return QLatin1String("creatorContactInfo.city"); case MetadataInfo::IptcCoreContactInfoCountry: return QLatin1String("creatorContactInfo.country"); case MetadataInfo::IptcCoreContactInfoAddress: return QLatin1String("creatorContactInfo.address"); case MetadataInfo::IptcCoreContactInfoPostalCode: return QLatin1String("creatorContactInfo.postalCode"); case MetadataInfo::IptcCoreContactInfoProvinceState: return QLatin1String("creatorContactInfo.provinceState"); case MetadataInfo::IptcCoreContactInfoEmail: return QLatin1String("creatorContactInfo.email"); case MetadataInfo::IptcCoreContactInfoPhone: return QLatin1String("creatorContactInfo.phone"); case MetadataInfo::IptcCoreContactInfoWebUrl: return QLatin1String("creatorContactInfo.webUrl"); default: return QString(); } } QString ItemScanner::detectImageFormat() const { DImg::FORMAT dimgFormat = d->img.detectedFormat(); switch (dimgFormat) { case DImg::JPEG: return QLatin1String("JPG"); case DImg::PNG: return QLatin1String("PNG"); case DImg::TIFF: return QLatin1String("TIFF"); case DImg::PPM: return QLatin1String("PPM"); case DImg::JP2K: return QLatin1String("JP2"); case DImg::PGF: return QLatin1String("PGF"); case DImg::RAW: { QString format = QLatin1String("RAW-"); format += d->fileInfo.suffix().toUpper(); return format; } case DImg::NONE: case DImg::QIMAGE: { QByteArray format = QImageReader::imageFormat(d->fileInfo.filePath()); if (!format.isEmpty()) { return QString::fromUtf8(format).toUpper(); } break; } } // See BUG #339341: Take file name suffix instead type mime analyze. return d->fileInfo.suffix().toUpper(); } void ItemScanner::scanImageMetadata() { QVariantList metadataInfos = d->metadata.getMetadataFields(allImageMetadataFields()); if (hasValidField(metadataInfos)) { d->commit.commitImageMetadata = true; d->commit.imageMetadataInfos = metadataInfos; } } void ItemScanner::commitImageMetadata() { CoreDbAccess().db()->addImageMetadata(d->scanInfo.id, d->commit.imageMetadataInfos); } void ItemScanner::scanItemPosition() { // This list must reflect the order required by CoreDB::addItemPosition MetadataFields fields; fields << MetadataInfo::Latitude << MetadataInfo::LatitudeNumber << MetadataInfo::Longitude << MetadataInfo::LongitudeNumber << MetadataInfo::Altitude << MetadataInfo::PositionOrientation << MetadataInfo::PositionTilt << MetadataInfo::PositionRoll << MetadataInfo::PositionAccuracy << MetadataInfo::PositionDescription; QVariantList metadataInfos = d->metadata.getMetadataFields(fields); if (hasValidField(metadataInfos)) { d->commit.commitItemPosition = true; d->commit.imagePositionInfos = metadataInfos; } } void ItemScanner::commitItemPosition() { CoreDbAccess().db()->addItemPosition(d->scanInfo.id, d->commit.imagePositionInfos); } void ItemScanner::scanItemComments() { MetadataFields fields; fields << MetadataInfo::Headline << MetadataInfo::Title; QVariantList metadataInfos = d->metadata.getMetadataFields(fields); // handles all possible fields, multi-language, author, date CaptionsMap captions = d->metadata.getItemComments(); if (captions.isEmpty() && !hasValidField(metadataInfos)) { return; } d->commit.commitItemComments = true; d->commit.captions = captions; // Headline if (!metadataInfos.at(0).isNull()) { d->commit.headline = metadataInfos.at(0).toString(); } // Title if (!metadataInfos.at(1).isNull()) { d->commit.title = metadataInfos.at(1).toMap()[QLatin1String("x-default")].toString(); } } void ItemScanner::commitItemComments() { CoreDbAccess access; ItemComments comments(access, d->scanInfo.id); // Description if (!d->commit.captions.isEmpty()) { comments.replaceComments(d->commit.captions); } // Headline if (!d->commit.headline.isNull()) { comments.addHeadline(d->commit.headline); } // Title if (!d->commit.title.isNull()) { comments.addTitle(d->commit.title); } } void ItemScanner::scanItemCopyright() { Template t; if (!d->metadata.getCopyrightInformation(t)) { return; } d->commit.commitItemCopyright = true; d->commit.copyrightTemplate = t; } void ItemScanner::commitItemCopyright() { ItemCopyright copyright(d->scanInfo.id); // It is not clear if removeAll() should be called if d->scanMode == Rescan copyright.removeAll(); copyright.setFromTemplate(d->commit.copyrightTemplate); } void ItemScanner::scanIPTCCore() { MetadataFields fields; fields << MetadataInfo::IptcCoreLocationInfo << MetadataInfo::IptcCoreIntellectualGenre << MetadataInfo::IptcCoreJobID << MetadataInfo::IptcCoreScene << MetadataInfo::IptcCoreSubjectCode; QVariantList metadataInfos = d->metadata.getMetadataFields(fields); if (!hasValidField(metadataInfos)) { return; } d->commit.commitIPTCCore = true; d->commit.iptcCoreMetadataInfos = metadataInfos; } void ItemScanner::commitIPTCCore() { ItemExtendedProperties props(d->scanInfo.id); if (!d->commit.iptcCoreMetadataInfos.at(0).isNull()) { IptcCoreLocationInfo loc = d->commit.iptcCoreMetadataInfos.at(0).value(); if (!loc.isNull()) { props.setLocation(loc); } } if (!d->commit.iptcCoreMetadataInfos.at(1).isNull()) { props.setIntellectualGenre(d->commit.iptcCoreMetadataInfos.at(1).toString()); } if (!d->commit.iptcCoreMetadataInfos.at(2).isNull()) { props.setJobId(d->commit.iptcCoreMetadataInfos.at(2).toString()); } if (!d->commit.iptcCoreMetadataInfos.at(3).isNull()) { props.setScene(d->commit.iptcCoreMetadataInfos.at(3).toStringList()); } if (!d->commit.iptcCoreMetadataInfos.at(4).isNull()) { props.setSubjectCode(d->commit.iptcCoreMetadataInfos.at(4).toStringList()); } } void ItemScanner::scanTags() { // Check Keywords tag paths. QVariant var = d->metadata.getMetadataField(MetadataInfo::Keywords); QStringList keywords = var.toStringList(); QStringList filteredKeywords; // Extra empty tags check, empty tag = root tag which is not asignable - for (int index = 0 ; index < keywords.size() ; index++) + for (int index = 0 ; index < keywords.size() ; ++index) { QString keyword = keywords.at(index); if (!keyword.isEmpty()) { // _Digikam_root_tag_ is present in some photos tagged with older // version of digiKam, must be removed if (keyword.contains(QRegExp(QLatin1String("(_Digikam_root_tag_/|/_Digikam_root_tag_|_Digikam_root_tag_)")))) { keyword = keyword.replace(QRegExp(QLatin1String("(_Digikam_root_tag_/|/_Digikam_root_tag_|_Digikam_root_tag_)")), QLatin1String("")); } filteredKeywords.append(keyword); } } if (!filteredKeywords.isEmpty()) { // get tag ids, create if necessary QList tagIds = TagsCache::instance()->getOrCreateTags(filteredKeywords); d->commit.tagIds += tagIds; } // Check Pick Label tag. int pickId = d->metadata.getItemPickLabel(); if (pickId != -1) { qCDebug(DIGIKAM_DATABASE_LOG) << "Pick Label found : " << pickId; int tagId = TagsCache::instance()->tagForPickLabel((PickLabel)pickId); if (tagId) { d->commit.tagIds << tagId; d->commit.hasPickTag = true; qCDebug(DIGIKAM_DATABASE_LOG) << "Assigned Pick Label Tag : " << tagId; } else { qCDebug(DIGIKAM_DATABASE_LOG) << "Cannot find Pick Label Tag for : " << pickId; } } // Check Color Label tag. int colorId = d->metadata.getItemColorLabel(); if (colorId != -1) { qCDebug(DIGIKAM_DATABASE_LOG) << "Color Label found : " << colorId; int tagId = TagsCache::instance()->tagForColorLabel((ColorLabel)colorId); if (tagId) { d->commit.tagIds << tagId; d->commit.hasColorTag = true; qCDebug(DIGIKAM_DATABASE_LOG) << "Assigned Color Label Tag : " << tagId; } else { qCDebug(DIGIKAM_DATABASE_LOG) << "Cannot find Color Label Tag for : " << colorId; } } } void ItemScanner::commitTags() { QList currentTags = CoreDbAccess().db()->getItemTagIDs(d->scanInfo.id); QVector colorTags = TagsCache::instance()->colorLabelTags(); QVector pickTags = TagsCache::instance()->pickLabelTags(); QList removeTags; foreach (int cTag, currentTags) { if ((d->commit.hasColorTag && colorTags.contains(cTag)) || (d->commit.hasPickTag && pickTags.contains(cTag))) { removeTags << cTag; } } if (!removeTags.isEmpty()) { CoreDbAccess().db()->removeTagsFromItems(QList() << d->scanInfo.id, removeTags); } CoreDbAccess().db()->addTagsToItems(QList() << d->scanInfo.id, d->commit.tagIds); } void ItemScanner::scanFaces() { QSize size = d->img.size(); if (!size.isValid()) { return; } QMultiMap metadataFacesMap; if (!d->metadata.getItemFacesMap(metadataFacesMap)) { return; } d->commit.commitFaces = true; d->commit.metadataFacesMap = metadataFacesMap; } void ItemScanner::commitFaces() { QSize size = d->img.size(); QMap::const_iterator it; for (it = d->commit.metadataFacesMap.constBegin() ; it != d->commit.metadataFacesMap.constEnd() ; ++it) { QString name = it.key(); QRectF rect = it.value().toRectF(); if (name.isEmpty() || !rect.isValid()) { continue; } int tagId = FaceTags::getOrCreateTagForPerson(name); if (!tagId) { qCDebug(DIGIKAM_DATABASE_LOG) << "Failed to create a person tag for name" << name; } TagRegion region(TagRegion::relativeToAbsolute(rect, size)); FaceTagsEditor editor; editor.add(d->scanInfo.id, tagId, region, false); } } void ItemScanner::checkCreationDateFromMetadata(QVariant& dateFromMetadata) const { // creation date: fall back to file system property if (dateFromMetadata.isNull() || !dateFromMetadata.toDateTime().isValid()) { dateFromMetadata = creationDateFromFilesystem(d->fileInfo); } } bool ItemScanner::checkRatingFromMetadata(const QVariant& ratingFromMetadata) const { // should only be overwritten if set in metadata if (d->scanMode == Rescan) { if (ratingFromMetadata.isNull() || ratingFromMetadata.toInt() == -1) { return false; } } return true; } MetadataFields ItemScanner::allImageMetadataFields() { // This list must reflect the order required by CoreDB::addImageMetadata MetadataFields fields; fields << MetadataInfo::Make << MetadataInfo::Model << MetadataInfo::Lens << MetadataInfo::Aperture << MetadataInfo::FocalLength << MetadataInfo::FocalLengthIn35mm << MetadataInfo::ExposureTime << MetadataInfo::ExposureProgram << MetadataInfo::ExposureMode << MetadataInfo::Sensitivity << MetadataInfo::FlashMode << MetadataInfo::WhiteBalance << MetadataInfo::WhiteBalanceColorTemperature << MetadataInfo::MeteringMode << MetadataInfo::SubjectDistance << MetadataInfo::SubjectDistanceCategory; return fields; } } // namespace Digikam diff --git a/core/libs/database/models/itemmodel.cpp b/core/libs/database/models/itemmodel.cpp index 7e189be9c5..beba0c5bbd 100644 --- a/core/libs/database/models/itemmodel.cpp +++ b/core/libs/database/models/itemmodel.cpp @@ -1,1370 +1,1370 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2009-03-05 * Description : Qt item model for database entries * * 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 "itemmodel.h" // Qt includes #include #include // Local includes #include "digikam_debug.h" #include "coredbchangesets.h" #include "coredbfields.h" #include "coredbwatch.h" #include "iteminfo.h" #include "iteminfolist.h" #include "abstractitemdragdrophandler.h" namespace Digikam { class Q_DECL_HIDDEN ItemModel::Private { public: explicit Private() { preprocessor = 0; keepFilePathCache = false; sendRemovalSignals = false; incrementalUpdater = 0; refreshing = false; reAdding = false; incrementalRefreshRequested = false; } ItemInfoList infos; QList extraValues; QHash idHash; bool keepFilePathCache; QHash filePathHash; bool sendRemovalSignals; QObject* preprocessor; bool refreshing; bool reAdding; bool incrementalRefreshRequested; DatabaseFields::Set watchFlags; class ItemModelIncrementalUpdater* incrementalUpdater; ItemInfoList pendingInfos; QList pendingExtraValues; inline bool isValid(const QModelIndex& index) { if (!index.isValid()) { return false; } if (index.row() < 0 || index.row() >= infos.size()) { qCDebug(DIGIKAM_GENERAL_LOG) << "Invalid index" << index; return false; } return true; } inline bool extraValueValid(const QModelIndex& index) { // we assume isValid() being called before, no duplicate checks if (index.row() >= extraValues.size()) { qCDebug(DIGIKAM_GENERAL_LOG) << "Invalid index for extraData" << index; return false; } return true; } }; typedef QPair IntPair; // to make foreach macro happy typedef QList IntPairList; class Q_DECL_HIDDEN ItemModelIncrementalUpdater { public: explicit ItemModelIncrementalUpdater(ItemModel::Private* d); void appendInfos(const QList& infos, const QList& extraValues); void aboutToBeRemovedInModel(const IntPairList& aboutToBeRemoved); QList oldIndexes(); static QList toContiguousPairs(const QList& ids); public: QHash oldIds; QList oldExtraValues; QList newInfos; QList newExtraValues; QList modelRemovals; }; ItemModel::ItemModel(QObject* const parent) : QAbstractListModel(parent), d(new Private) { connect(CoreDbAccess::databaseWatch(), SIGNAL(imageChange(ImageChangeset)), this, SLOT(slotImageChange(ImageChangeset))); connect(CoreDbAccess::databaseWatch(), SIGNAL(imageTagChange(ImageTagChangeset)), this, SLOT(slotImageTagChange(ImageTagChangeset))); } ItemModel::~ItemModel() { delete d->incrementalUpdater; delete d; } // ------------ Access methods ------------- void ItemModel::setKeepsFilePathCache(bool keepCache) { d->keepFilePathCache = keepCache; } bool ItemModel::keepsFilePathCache() const { return d->keepFilePathCache; } bool ItemModel::isEmpty() const { return d->infos.isEmpty(); } void ItemModel::setWatchFlags(const DatabaseFields::Set& set) { d->watchFlags = set; } ItemInfo ItemModel::imageInfo(const QModelIndex& index) const { if (!d->isValid(index)) { return ItemInfo(); } return d->infos.at(index.row()); } ItemInfo& ItemModel::imageInfoRef(const QModelIndex& index) const { return d->infos[index.row()]; } qlonglong ItemModel::imageId(const QModelIndex& index) const { if (!d->isValid(index)) { return 0; } return d->infos.at(index.row()).id(); } QList ItemModel::imageInfos(const QList& indexes) const { QList infos; foreach (const QModelIndex& index, indexes) { infos << imageInfo(index); } return infos; } QList ItemModel::imageIds(const QList& indexes) const { QList ids; foreach (const QModelIndex& index, indexes) { ids << imageId(index); } return ids; } ItemInfo ItemModel::imageInfo(int row) const { if (row >= d->infos.size()) { return ItemInfo(); } return d->infos.at(row); } ItemInfo& ItemModel::imageInfoRef(int row) const { return d->infos[row]; } qlonglong ItemModel::imageId(int row) const { if (row < 0 || row >= d->infos.size()) { return -1; } return d->infos.at(row).id(); } QModelIndex ItemModel::indexForItemInfo(const ItemInfo& info) const { return indexForImageId(info.id()); } QModelIndex ItemModel::indexForItemInfo(const ItemInfo& info, const QVariant& extraValue) const { return indexForImageId(info.id(), extraValue); } QList ItemModel::indexesForItemInfo(const ItemInfo& info) const { return indexesForImageId(info.id()); } QModelIndex ItemModel::indexForImageId(qlonglong id) const { int index = d->idHash.value(id, -1); if (index != -1) { return createIndex(index, 0); } return QModelIndex(); } QModelIndex ItemModel::indexForImageId(qlonglong id, const QVariant& extraValue) const { if (d->extraValues.isEmpty()) return indexForImageId(id); QHash::const_iterator it; for (it = d->idHash.constFind(id) ; it != d->idHash.constEnd() && it.key() == id ; ++it) { if (d->extraValues.at(it.value()) == extraValue) return createIndex(it.value(), 0); } return QModelIndex(); } QList ItemModel::indexesForImageId(qlonglong id) const { QList indexes; QHash::const_iterator it; for (it = d->idHash.constFind(id) ; it != d->idHash.constEnd() && it.key() == id ; ++it) { indexes << createIndex(it.value(), 0); } return indexes; } int ItemModel::numberOfIndexesForItemInfo(const ItemInfo& info) const { return numberOfIndexesForImageId(info.id()); } int ItemModel::numberOfIndexesForImageId(qlonglong id) const { if (d->extraValues.isEmpty()) { return 0; } int count = 0; QHash::const_iterator it; for (it = d->idHash.constFind(id) ; it != d->idHash.constEnd() && it.key() == id ; ++it) { ++count; } return count; } // static method ItemInfo ItemModel::retrieveItemInfo(const QModelIndex& index) { if (!index.isValid()) { return ItemInfo(); } ItemModel* const model = index.data(ItemModelPointerRole).value(); int row = index.data(ItemModelInternalId).toInt(); if (!model) { return ItemInfo(); } return model->imageInfo(row); } // static method qlonglong ItemModel::retrieveImageId(const QModelIndex& index) { if (!index.isValid()) { return 0; } ItemModel* const model = index.data(ItemModelPointerRole).value(); int row = index.data(ItemModelInternalId).toInt(); if (!model) { return 0; } return model->imageId(row); } QModelIndex ItemModel::indexForPath(const QString& filePath) const { if (d->keepFilePathCache) { return indexForImageId(d->filePathHash.value(filePath)); } else { const int size = d->infos.size(); for (int i = 0 ; i < size ; ++i) { if (d->infos.at(i).filePath() == filePath) { return createIndex(i, 0); } } } return QModelIndex(); } QList ItemModel::indexesForPath(const QString& filePath) const { if (d->keepFilePathCache) { return indexesForImageId(d->filePathHash.value(filePath)); } else { QList indexes; const int size = d->infos.size(); for (int i = 0 ; i < size ; ++i) { if (d->infos.at(i).filePath() == filePath) { indexes << createIndex(i, 0); } } return indexes; } } ItemInfo ItemModel::imageInfo(const QString& filePath) const { if (d->keepFilePathCache) { qlonglong id = d->filePathHash.value(filePath); if (id) { int index = d->idHash.value(id, -1); if (index != -1) { return d->infos.at(index); } } } else { foreach (const ItemInfo& info, d->infos) { if (info.filePath() == filePath) { return info; } } } return ItemInfo(); } QList ItemModel::imageInfos(const QString& filePath) const { QList infos; if (d->keepFilePathCache) { qlonglong id = d->filePathHash.value(filePath); if (id) { foreach (int index, d->idHash.values(id)) { infos << d->infos.at(index); } } } else { foreach (const ItemInfo& info, d->infos) { if (info.filePath() == filePath) { infos << info; } } } return infos; } void ItemModel::addItemInfo(const ItemInfo& info) { addItemInfos(QList() << info, QList()); } void ItemModel::addItemInfos(const QList& infos) { addItemInfos(infos, QList()); } void ItemModel::addItemInfos(const QList& infos, const QList& extraValues) { if (infos.isEmpty()) { return; } if (d->incrementalUpdater) { d->incrementalUpdater->appendInfos(infos, extraValues); } else { appendInfos(infos, extraValues); } } void ItemModel::addItemInfoSynchronously(const ItemInfo& info) { addItemInfosSynchronously(QList() << info, QList()); } void ItemModel::addItemInfosSynchronously(const QList& infos) { addItemInfos(infos, QList()); } void ItemModel::addItemInfosSynchronously(const QList& infos, const QList& extraValues) { if (infos.isEmpty()) { return; } publiciseInfos(infos, extraValues); emit processAdded(infos, extraValues); } void ItemModel::ensureHasItemInfo(const ItemInfo& info) { ensureHasItemInfos(QList() << info, QList()); } void ItemModel::ensureHasItemInfos(const QList& infos) { ensureHasItemInfos(infos, QList()); } void ItemModel::ensureHasItemInfos(const QList& infos, const QList& extraValues) { if (extraValues.isEmpty()) { if (!d->pendingExtraValues.isEmpty()) { qCDebug(DIGIKAM_GENERAL_LOG) << "ExtraValue / No Extra Value mismatch. Ignoring added infos."; return; } } else { if (d->pendingInfos.size() != d->pendingExtraValues.size()) { qCDebug(DIGIKAM_GENERAL_LOG) << "ExtraValue / No Extra Value mismatch. Ignoring added infos."; return; } } d->pendingInfos << infos; d->pendingExtraValues << extraValues; cleanSituationChecks(); } void ItemModel::clearItemInfos() { beginResetModel(); d->infos.clear(); d->extraValues.clear(); d->idHash.clear(); d->filePathHash.clear(); delete d->incrementalUpdater; d->incrementalUpdater = 0; d->pendingInfos.clear(); d->pendingExtraValues.clear(); d->refreshing = false; d->reAdding = false; d->incrementalRefreshRequested = false; imageInfosCleared(); endResetModel(); } void ItemModel::setItemInfos(const QList& infos) { clearItemInfos(); addItemInfos(infos); } QList ItemModel::imageInfos() const { return d->infos; } QList ItemModel::imageIds() const { return d->idHash.keys(); } bool ItemModel::hasImage(qlonglong id) const { return d->idHash.contains(id); } bool ItemModel::hasImage(const ItemInfo& info) const { return d->idHash.contains(info.id()); } bool ItemModel::hasImage(const ItemInfo& info, const QVariant& extraValue) const { return hasImage(info.id(), extraValue); } bool ItemModel::hasImage(qlonglong id, const QVariant& extraValue) const { if (d->extraValues.isEmpty()) return hasImage(id); QHash::const_iterator it; for (it = d->idHash.constFind(id) ; it != d->idHash.constEnd() && it.key() == id ; ++it) { if (d->extraValues.at(it.value()) == extraValue) return true; } return false;; } QList ItemModel::uniqueItemInfos() const { if (d->extraValues.isEmpty()) { return d->infos; } QList uniqueInfos; const int size = d->infos.size(); for (int i = 0 ; i < size ; ++i) { const ItemInfo& info = d->infos.at(i); if (d->idHash.value(info.id()) == i) { uniqueInfos << info; } } return uniqueInfos; } void ItemModel::emitDataChangedForAll() { if (d->infos.isEmpty()) { return; } QModelIndex first = createIndex(0, 0); QModelIndex last = createIndex(d->infos.size() - 1, 0); emit dataChanged(first, last); } void ItemModel::emitDataChangedForSelection(const QItemSelection& selection) { if (!selection.isEmpty()) { foreach (const QItemSelectionRange& range, selection) { emit dataChanged(range.topLeft(), range.bottomRight()); } } } void ItemModel::ensureHasGroupedImages(const ItemInfo& groupLeader) { ensureHasItemInfos(groupLeader.groupedImages()); } // ------------ Preprocessing ------------- void ItemModel::setPreprocessor(QObject* const preprocessor) { unsetPreprocessor(d->preprocessor); d->preprocessor = preprocessor; } void ItemModel::unsetPreprocessor(QObject* const preprocessor) { if (preprocessor && d->preprocessor == preprocessor) { disconnect(this, SIGNAL(preprocess(QList,QList)), 0, 0); disconnect(d->preprocessor, 0, this, SLOT(reAddItemInfos(QList,QList))); disconnect(d->preprocessor, 0, this, SLOT(reAddingFinished())); } } void ItemModel::appendInfos(const QList& infos, const QList& extraValues) { if (infos.isEmpty()) { return; } if (d->preprocessor) { d->reAdding = true; emit preprocess(infos, extraValues); } else { publiciseInfos(infos, extraValues); } } void ItemModel::appendInfosChecked(const QList& infos, const QList& extraValues) { // This method does deduplication. It is private because in context of readding or refreshing it is of no use. if (extraValues.isEmpty()) { QList checkedInfos; foreach (const ItemInfo& info, infos) { if (!hasImage(info)) { checkedInfos << info; } } appendInfos(checkedInfos, QList()); } else { QList checkedInfos; QList checkedExtraValues; const int size = infos.size(); - for (int i = 0 ; i < size ; i++) + for (int i = 0 ; i < size ; ++i) { if (!hasImage(infos[i], extraValues[i])) { checkedInfos << infos[i]; checkedExtraValues << extraValues[i]; } } appendInfos(checkedInfos, checkedExtraValues); } } void ItemModel::reAddItemInfos(const QList& infos, const QList& extraValues) { // addItemInfos -> appendInfos -> preprocessor -> reAddItemInfos publiciseInfos(infos, extraValues); } void ItemModel::reAddingFinished() { d->reAdding = false; cleanSituationChecks(); } void ItemModel::startRefresh() { d->refreshing = true; } void ItemModel::finishRefresh() { d->refreshing = false; cleanSituationChecks(); } bool ItemModel::isRefreshing() const { return d->refreshing; } void ItemModel::cleanSituationChecks() { // For starting an incremental refresh we want a clear situation: // Any remaining batches from non-incremental refreshing subclasses have been received in appendInfos(), // any batches sent to preprocessor for re-adding have been re-added. if (d->refreshing || d->reAdding) { return; } if (!d->pendingInfos.isEmpty()) { appendInfosChecked(d->pendingInfos, d->pendingExtraValues); d->pendingInfos.clear(); d->pendingExtraValues.clear(); cleanSituationChecks(); return; } if (d->incrementalRefreshRequested) { d->incrementalRefreshRequested = false; emit readyForIncrementalRefresh(); } else { emit allRefreshingFinished(); } } void ItemModel::publiciseInfos(const QList& infos, const QList& extraValues) { if (infos.isEmpty()) { return; } Q_ASSERT(infos.size() == extraValues.size() || (extraValues.isEmpty() && d->extraValues.isEmpty())); emit imageInfosAboutToBeAdded(infos); const int firstNewIndex = d->infos.size(); const int lastNewIndex = d->infos.size() + infos.size() - 1; beginInsertRows(QModelIndex(), firstNewIndex, lastNewIndex); d->infos << infos; d->extraValues << extraValues; for (int i = firstNewIndex ; i <= lastNewIndex ; ++i) { const ItemInfo& info = d->infos.at(i); qlonglong id = info.id(); d->idHash.insertMulti(id, i); if (d->keepFilePathCache) { d->filePathHash[info.filePath()] = id; } } endInsertRows(); emit imageInfosAdded(infos); } void ItemModel::requestIncrementalRefresh() { if (d->reAdding) { d->incrementalRefreshRequested = true; } else { emit readyForIncrementalRefresh(); } } bool ItemModel::hasIncrementalRefreshPending() const { return d->incrementalRefreshRequested; } void ItemModel::startIncrementalRefresh() { delete d->incrementalUpdater; d->incrementalUpdater = new ItemModelIncrementalUpdater(d); } void ItemModel::finishIncrementalRefresh() { if (!d->incrementalUpdater) { return; } // remove old entries QList > pairs = d->incrementalUpdater->oldIndexes(); removeRowPairs(pairs); // add new indexes appendInfos(d->incrementalUpdater->newInfos, d->incrementalUpdater->newExtraValues); delete d->incrementalUpdater; d->incrementalUpdater = 0; } void ItemModel::removeIndex(const QModelIndex& index) { removeIndexes(QList() << index); } void ItemModel::removeIndexes(const QList& indexes) { QList listIndexes; foreach (const QModelIndex& index, indexes) { if (d->isValid(index)) { listIndexes << index.row(); } } if (listIndexes.isEmpty()) { return; } removeRowPairsWithCheck(ItemModelIncrementalUpdater::toContiguousPairs(listIndexes)); } void ItemModel::removeItemInfo(const ItemInfo& info) { removeItemInfos(QList() << info); } void ItemModel::removeItemInfos(const QList& infos) { QList listIndexes; foreach (const ItemInfo& info, infos) { QModelIndex index = indexForImageId(info.id()); if (index.isValid()) { listIndexes << index.row(); } } removeRowPairsWithCheck(ItemModelIncrementalUpdater::toContiguousPairs(listIndexes)); } void ItemModel::removeItemInfos(const QList& infos, const QList& extraValues) { if (extraValues.isEmpty()) { removeItemInfos(infos); return; } QList listIndexes; for (int i = 0 ; i < infos.size() ; ++i) { QModelIndex index = indexForImageId(infos.at(i).id(), extraValues.at(i)); if (index.isValid()) { listIndexes << index.row(); } } removeRowPairsWithCheck(ItemModelIncrementalUpdater::toContiguousPairs(listIndexes)); } void ItemModel::setSendRemovalSignals(bool send) { d->sendRemovalSignals = send; } template static bool pairsContain(const List& list, T value) { typename List::const_iterator middle; typename List::const_iterator begin = list.begin(); typename List::const_iterator end = list.end(); int n = int(end - begin); int half; while (n > 0) { half = n >> 1; middle = begin + half; if (middle->first <= value && middle->second >= value) { return true; } else if (middle->second < value) { begin = middle + 1; n -= half + 1; } else { n = half; } } return false; } void ItemModel::removeRowPairsWithCheck(const QList >& toRemove) { if (d->incrementalUpdater) { d->incrementalUpdater->aboutToBeRemovedInModel(toRemove); } removeRowPairs(toRemove); } void ItemModel::removeRowPairs(const QList >& toRemove) { if (toRemove.isEmpty()) { return; } // Remove old indexes // Keep in mind that when calling beginRemoveRows all structures announced to be removed // must still be valid, and this includes our hashes as well, which limits what we can optimize int removedRows = 0, offset = 0; typedef QPair IntPair; // to make foreach macro happy foreach (const IntPair& pair, toRemove) { const int begin = pair.first - offset; const int end = pair.second - offset; // inclusive removedRows = end - begin + 1; // when removing from the list, all subsequent indexes are affected offset += removedRows; QList removedInfos; if (d->sendRemovalSignals) { std::copy(d->infos.begin() + begin, d->infos.begin() + end, removedInfos.begin()); emit imageInfosAboutToBeRemoved(removedInfos); } imageInfosAboutToBeRemoved(begin, end); beginRemoveRows(QModelIndex(), begin, end); // update idHash - which points to indexes of d->infos, and these change now! QHash::iterator it; for (it = d->idHash.begin() ; it != d->idHash.end() ; ) { if (it.value() >= begin) { if (it.value() > end) { // after the removed interval: adjust index it.value() -= removedRows; } else { // in the removed interval it = d->idHash.erase(it); continue; } } ++it; } // remove from list d->infos.erase(d->infos.begin() + begin, d->infos.begin() + (end + 1)); if (!d->extraValues.isEmpty()) { d->extraValues.erase(d->extraValues.begin() + begin, d->extraValues.begin() + (end + 1)); } endRemoveRows(); if (d->sendRemovalSignals) { emit imageInfosRemoved(removedInfos); } } // tidy up: remove old indexes from file path hash now if (d->keepFilePathCache) { QHash::iterator it; for (it = d->filePathHash.begin() ; it != d->filePathHash.end() ; ) { if (pairsContain(toRemove, it.value())) { it = d->filePathHash.erase(it); } else { ++it; } } } } ItemModelIncrementalUpdater::ItemModelIncrementalUpdater(ItemModel::Private* d) { oldIds = d->idHash; oldExtraValues = d->extraValues; } void ItemModelIncrementalUpdater::appendInfos(const QList& infos, const QList& extraValues) { if (extraValues.isEmpty()) { foreach (const ItemInfo& info, infos) { QHash::iterator it = oldIds.find(info.id()); if (it != oldIds.end()) { oldIds.erase(it); } else { newInfos << info; } } } else { for (int i = 0 ; i < infos.size() ; ++i) { const ItemInfo& info = infos.at(i); bool found = false; QHash::iterator it; for (it = oldIds.find(info.id()) ; it != oldIds.end() && it.key() == info.id() ; ++it) { // first check is for bug #262596. Not sure if needed. if (it.value() < oldExtraValues.size() && extraValues.at(i) == oldExtraValues.at(it.value())) { found = true; break; } } if (found) { oldIds.erase(it); // do not erase from oldExtraValues - oldIds is a hash id -> index. } else { newInfos << info; newExtraValues << extraValues.at(i); } } } } void ItemModelIncrementalUpdater::aboutToBeRemovedInModel(const IntPairList& toRemove) { modelRemovals << toRemove; } QList > ItemModelIncrementalUpdater::oldIndexes() { // first, apply all changes to indexes by direct removal in model // while the updater was active foreach (const IntPairList& list, modelRemovals) { int removedRows = 0, offset = 0; foreach (const IntPair& pair, list) { const int begin = pair.first - offset; const int end = pair.second - offset; // inclusive removedRows = end - begin + 1; // when removing from the list, all subsequent indexes are affected offset += removedRows; // update idHash - which points to indexes of d->infos, and these change now! QHash::iterator it; for (it = oldIds.begin() ; it != oldIds.end() ; ) { if (it.value() >= begin) { if (it.value() > end) { // after the removed interval: adjust index it.value() -= removedRows; } else { // in the removed interval it = oldIds.erase(it); continue; } } ++it; } } } modelRemovals.clear(); return toContiguousPairs(oldIds.values()); } QList > ItemModelIncrementalUpdater::toContiguousPairs(const QList& unsorted) { // Take the given indices and return them as contiguous pairs [begin, end] QList > pairs; if (unsorted.isEmpty()) { return pairs; } QList indices(unsorted); std::sort(indices.begin(), indices.end()); QPair pair(indices.first(), indices.first()); for (int i = 1 ; i < indices.size() ; ++i) { const int &index = indices.at(i); if (index == pair.second + 1) { pair.second = index; continue; } pairs << pair; // insert last pair pair.first = index; pair.second = index; } pairs << pair; return pairs; } // ------------ QAbstractItemModel implementation ------------- QVariant ItemModel::data(const QModelIndex& index, int role) const { if (!d->isValid(index)) { return QVariant(); } switch (role) { case Qt::DisplayRole: case Qt::ToolTipRole: return d->infos.at(index.row()).name(); case ItemModelPointerRole: return QVariant::fromValue(const_cast(this)); case ItemModelInternalId: return index.row(); case CreationDateRole: return d->infos.at(index.row()).dateTime(); case ExtraDataRole: if (d->extraValueValid(index)) { return d->extraValues.at(index.row()); } else { return QVariant(); } case ExtraDataDuplicateCount: { qlonglong id = d->infos.at(index.row()).id(); return numberOfIndexesForImageId(id); } } return QVariant(); } QVariant ItemModel::headerData(int section, Qt::Orientation orientation, int role) const { Q_UNUSED(section) Q_UNUSED(orientation) Q_UNUSED(role) return QVariant(); } int ItemModel::rowCount(const QModelIndex& parent) const { if (parent.isValid()) { return 0; } return d->infos.size(); } Qt::ItemFlags ItemModel::flags(const QModelIndex& index) const { if (!d->isValid(index)) { return 0; } Qt::ItemFlags f = Qt::ItemIsSelectable | Qt::ItemIsEnabled; f |= dragDropFlags(index); return f; } QModelIndex ItemModel::index(int row, int column, const QModelIndex& parent) const { if (column != 0 || row < 0 || parent.isValid() || row >= d->infos.size()) { return QModelIndex(); } return createIndex(row, 0); } // ------------ Database watch ------------- void ItemModel::slotImageChange(const ImageChangeset& changeset) { if (d->infos.isEmpty()) { return; } if (d->watchFlags & changeset.changes()) { QItemSelection items; foreach (const qlonglong& id, changeset.ids()) { QModelIndex index = indexForImageId(id); if (index.isValid()) { items.select(index, index); } } if (!items.isEmpty()) { emitDataChangedForSelection(items); emit imageChange(changeset, items); } } } void ItemModel::slotImageTagChange(const ImageTagChangeset& changeset) { if (d->infos.isEmpty()) { return; } QItemSelection items; foreach (const qlonglong& id, changeset.ids()) { QModelIndex index = indexForImageId(id); if (index.isValid()) { items.select(index, index); } } if (!items.isEmpty()) { emitDataChangedForSelection(items); emit imageTagChange(changeset, items); } } } // namespace Digikam diff --git a/core/libs/database/tags/tagscache.cpp b/core/libs/database/tags/tagscache.cpp index e5ba450b40..e1378b2376 100644 --- a/core/libs/database/tags/tagscache.cpp +++ b/core/libs/database/tags/tagscache.cpp @@ -1,1151 +1,1151 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2010-04-02 * Description : Cache for Tag information * * Copyright (C) 2010-2011 by Marcel Wiesweg * Copyright (C) 2011-2018 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 "tagscache.h" // Qt includes #include #include #include #include #include // Local includes #include "digikam_debug.h" #include "coredb.h" #include "coredbaccess.h" #include "coredbwatch.h" #include "digikam_globals.h" #include "itempropertiestab.h" #include "tagproperties.h" namespace Digikam { static bool lessThanForTagShortInfo(const TagShortInfo& first, const TagShortInfo& second) { return first.id < second.id; } static bool lessThanForTagProperty(const TagProperty& first, const TagProperty& second) { return first.tagId < second.tagId; } typedef QList::const_iterator TagPropertiesConstIterator; typedef QPair TagPropertiesRange; // ------------------------------------------------------------------------------------------ class Q_DECL_HIDDEN TagsCache::Private { public: explicit Private(TagsCache* const q) : initialized(false), needUpdateInfos(true), needUpdateHash(true), needUpdateProperties(true), needUpdateLabelTags(true), changingDB(false), q(q) { } public: volatile bool initialized; volatile bool needUpdateInfos; volatile bool needUpdateHash; volatile bool needUpdateProperties; volatile bool needUpdateLabelTags; volatile bool changingDB; QReadWriteLock lock; QList infos; QMultiHash nameHash; QList tagProperties; QHash > tagsWithProperty; QSet internalTags; QVector colorLabelsTags; // index = Label enum, value = tagId QVector pickLabelsTags; TagsCache* const q; public: void checkInfos() { if (needUpdateInfos && initialized) { QList newInfos = CoreDbAccess().db()->getTagShortInfos(); QWriteLocker locker(&lock); infos = newInfos; needUpdateInfos = false; } } void checkNameHash() { checkInfos(); if (needUpdateHash && initialized) { QWriteLocker locker(&lock); nameHash.clear(); foreach(const TagShortInfo& info, infos) { nameHash.insert(info.name, info.id); } needUpdateHash = false; } } void checkProperties() { if (needUpdateProperties && initialized) { QList props = CoreDbAccess().db()->getTagProperties(); // Ensure not to lock both locks at the same time QWriteLocker locker(&lock); needUpdateProperties = false; tagProperties = props; tagsWithProperty.clear(); QLatin1String internalProp = TagsCache::propertyNameDigikamInternalTag(); foreach(const TagProperty& property, tagProperties) { if (property.property == internalProp) { internalTags << property.tagId; } } } } // remember to call under lock QList::const_iterator find(int id) const { TagShortInfo info; info.id = id; // we use the fact that d->infos is sorted by id return qBinaryFind(infos.constBegin(), infos.constEnd(), info, lessThanForTagShortInfo); } TagPropertiesRange findProperties(int id) const { TagProperty prop; prop.tagId = id; TagPropertiesRange range; range.first = std::lower_bound(tagProperties.begin(), tagProperties.end(), prop, lessThanForTagProperty); range.second = std::upper_bound(range.first, tagProperties.end(), prop, lessThanForTagProperty); return range; } inline TagPropertiesConstIterator toNextTag(TagPropertiesConstIterator it) const { // increment iterator until the next tagid is reached int currentId = it->tagId; for (++it ; it != tagProperties.end() ; ++it) { if (it->tagId != currentId) { break; } } return it; } inline bool compareProperty(const TagPropertiesConstIterator& it, const QString& property, const QString& value) { if (value.isNull()) { return it->property == property; } else { return it->property == property && it->value == value; } } template inline bool sortedListContains(const QList& list, const T& value) { return qBinaryFind(list, value) != list.end(); } void checkLabelTags() { if (needUpdateLabelTags && initialized) { QVector colorTags(NumberOfColorLabels); colorTags[NoColorLabel] = q->getOrCreateInternalTag(InternalTagName::colorLabelNone()); colorTags[RedLabel] = q->getOrCreateInternalTag(InternalTagName::colorLabelRed()); colorTags[OrangeLabel] = q->getOrCreateInternalTag(InternalTagName::colorLabelOrange()); colorTags[YellowLabel] = q->getOrCreateInternalTag(InternalTagName::colorLabelYellow()); colorTags[GreenLabel] = q->getOrCreateInternalTag(InternalTagName::colorLabelGreen()); colorTags[BlueLabel] = q->getOrCreateInternalTag(InternalTagName::colorLabelBlue()); colorTags[MagentaLabel] = q->getOrCreateInternalTag(InternalTagName::colorLabelMagenta()); colorTags[GrayLabel] = q->getOrCreateInternalTag(InternalTagName::colorLabelGray()); colorTags[BlackLabel] = q->getOrCreateInternalTag(InternalTagName::colorLabelBlack()); colorTags[WhiteLabel] = q->getOrCreateInternalTag(InternalTagName::colorLabelWhite()); QVector pickTags(NumberOfPickLabels); pickTags[NoPickLabel] = q->getOrCreateInternalTag(InternalTagName::pickLabelNone()); pickTags[RejectedLabel] = q->getOrCreateInternalTag(InternalTagName::pickLabelRejected()); pickTags[PendingLabel] = q->getOrCreateInternalTag(InternalTagName::pickLabelPending()); pickTags[AcceptedLabel] = q->getOrCreateInternalTag(InternalTagName::pickLabelAccepted()); QWriteLocker locker(&lock); needUpdateLabelTags = false; colorLabelsTags = colorTags; pickLabelsTags = pickTags; } } QList tagsForFragment(bool (QString::*stringFunction)(const QString&, Qt::CaseSensitivity cs) const, const QString& fragment, Qt::CaseSensitivity caseSensitivity, HiddenTagsPolicy hiddenTagsPolicy); }; // ------------------------------------------------------------------------------------------ class Q_DECL_HIDDEN ChangingDB { public: explicit ChangingDB(TagsCache::Private* const d) : d(d) { d->changingDB = true; } ~ChangingDB() { d->changingDB = false; } TagsCache::Private* const d; }; // ------------------------------------------------------------------------------------------ class Q_DECL_HIDDEN TagsCacheCreator { public: TagsCache object; }; Q_GLOBAL_STATIC(TagsCacheCreator, creator) // ------------------------------------------------------------------------------------------ TagsCache* TagsCache::instance() { return &creator->object; } TagsCache::TagsCache() : d(new Private(this)) { } TagsCache::~TagsCache() { delete d; } void TagsCache::initialize() { if (d->initialized) { return; } connect(CoreDbAccess::databaseWatch(), SIGNAL(tagChange(TagChangeset)), this, SLOT(slotTagChanged(TagChangeset)), Qt::DirectConnection); d->initialized = true; } void TagsCache::invalidate() { d->needUpdateInfos = true; d->needUpdateHash = true; d->needUpdateProperties = true; d->needUpdateLabelTags = true; } QLatin1String TagsCache::tagPathOfDigikamInternalTags(LeadingSlashPolicy slashPolicy) { if (slashPolicy == IncludeLeadingSlash) { return QLatin1String("/_Digikam_Internal_Tags_"); } else { return QLatin1String("_Digikam_Internal_Tags_"); } } QLatin1String TagsCache::propertyNameDigikamInternalTag() { // Do not change, is written to users' databases return QLatin1String("internalTag"); } QLatin1String TagsCache::propertyNameExcludedFromWriting() { // Do not change, is written to users' databases return QLatin1String("noMetadataTag"); } QString TagsCache::tagName(int id) const { d->checkInfos(); QReadLocker locker(&d->lock); QList::const_iterator it = d->find(id); if (it != d->infos.constEnd()) { return it->name; } return QString(); } QStringList TagsCache::tagNames(const QList& ids, HiddenTagsPolicy hiddenTagsPolicy) const { QStringList names; if (!ids.isEmpty()) { foreach(int id, ids) { if (hiddenTagsPolicy == IncludeHiddenTags || !isInternalTag(id)) { names << tagName(id); } } } return names; } QString TagsCache::tagPath(int id, LeadingSlashPolicy slashPolicy) const { d->checkInfos(); QString path; QReadLocker locker(&d->lock); QList::const_iterator it; for (it = d->find(id) ; it != d->infos.constEnd() ; it = d->find(it->pid)) { if (path.isNull()) { path = it->name; } else { if ((it->name).contains(QRegExp(QLatin1String("(_Digikam_root_tag_/|/_Digikam_root_tag_|_Digikam_root_tag_)")))) { continue; } else { path = it->name + QLatin1Char('/') + path; } } } if (slashPolicy == IncludeLeadingSlash) { path.prepend(QLatin1Char('/')); } return path; } QStringList TagsCache::tagPaths(const QList& ids, LeadingSlashPolicy slashPolicy, HiddenTagsPolicy hiddenTagsPolicy) const { QStringList paths; if (!ids.isEmpty()) { foreach(int id, ids) { if (hiddenTagsPolicy == IncludeHiddenTags || !isInternalTag(id)) { paths << tagPath(id, slashPolicy); } } } return paths; } QList TagsCache::tagsForName(const QString& tagName, HiddenTagsPolicy hiddenTagsPolicy) const { d->checkNameHash(); if (hiddenTagsPolicy == NoHiddenTags) { d->checkProperties(); QList ids; QMultiHash::const_iterator it; for (it = d->nameHash.constFind(tagName) ; it != d->nameHash.constEnd() && it.key() == tagName ; ++it) { if (!d->internalTags.contains(it.value())) { ids << it.value(); } } return ids; } else { return d->nameHash.values(tagName); } } int TagsCache::tagForName(const QString& tagName, int parentId) const { d->checkNameHash(); QReadLocker locker(&d->lock); QList::const_iterator tag; foreach(int id, d->nameHash.values(tagName)) { tag = d->find(id); if (tag == d->infos.constEnd()) { continue; // error } if (tag->pid == parentId) { return tag->id; } } return 0; } bool TagsCache::hasTag(int id) const { d->checkInfos(); QReadLocker locker(&d->lock); return d->find(id) != d->infos.constEnd(); } int TagsCache::parentTag(int id) const { d->checkInfos(); QReadLocker locker(&d->lock); QList::const_iterator tag = d->find(id); if (tag != d->infos.constEnd()) { return tag->pid; } return 0; } QList TagsCache::parentTags(int id) const { d->checkInfos(); QList ids; QReadLocker locker(&d->lock); QList::const_iterator it; for (it = d->find(id); it != d->infos.constEnd() && it->pid; it = d->find(it->pid)) { if ((it->pid) != 0) ids.prepend(it->pid); } return ids; } int TagsCache::tagForPath(const QString& tagPath) const { // split full tag "url" into list of single tag names QStringList tagHierarchy = tagPath.split(QLatin1Char('/'), QString::SkipEmptyParts); if (tagHierarchy.isEmpty()) { return 0; } d->checkNameHash(); // last entry in list is the actual tag name int tagID = 0; QString tagName = tagHierarchy.last(); tagHierarchy.removeLast(); QList::const_iterator tag, parentTag; QReadLocker locker(&d->lock); // There might be multiple tags with the same name, but in different // hierarchies. We must check them all until we find the correct hierarchy foreach(int id, d->nameHash.values(tagName)) { tag = d->find(id); if (tag == d->infos.constEnd()) { continue; // error } int parentID = tag->pid; // Check hierarchy, from bottom to top bool foundParentTag = true; QStringList::const_iterator parentTagName = tagHierarchy.constEnd(); while (foundParentTag && parentTagName != tagHierarchy.constBegin()) { --parentTagName; foundParentTag = false; parentTag = d->find(parentID); // check if the parent is found and has the name we need if (parentTag != d->infos.constEnd() && parentTag->name == (*parentTagName)) { parentID = parentTag->pid; foundParentTag = true; } // If the parent matches, we continue with the grandparent. // If the candidate's parent do not match, // foundParentTag will be false, the while loop breaks. } // If we managed to traverse the full hierarchy, // we have our tag. if (foundParentTag) { tagID = tag->id; break; } } return tagID; } QList TagsCache::tagsForPaths(const QStringList& tagPaths) const { QList ids; if (!tagPaths.isEmpty()) { foreach(const QString& tagPath, tagPaths) { ids << tagForPath(tagPath); } } return ids; } int TagsCache::createTag(const QString& tagPathToCreate) { // split full tag "url" into list of single tag names QStringList tagHierarchy = tagPathToCreate.split(QLatin1Char('/'), QString::SkipEmptyParts); if (tagHierarchy.isEmpty()) { return 0; } d->checkNameHash(); int tagID = 0; bool parentTagExisted = true; int parentTagIDForCreation = 0; QStringList tagsToCreate; { int parentTagID = 0; QReadLocker locker(&d->lock); // Traverse hierarchy from top to bottom foreach(const QString& tagName, tagHierarchy) { tagID = 0; // if the parent tag did not exist, we need not check if the child exists if (parentTagExisted) { QList::const_iterator tag; // find the tag with tag name according to tagHierarchy, // and parent ID identical to the ID of the tag we found in // the previous run. foreach(int id, d->nameHash.values(tagName)) { tag = d->find(id); if (tag != d->infos.constEnd() && tag->pid == parentTagID) { tagID = tag->id; break; } } } if (tagID) { // tag already found in DB parentTagID = tagID; parentTagExisted = true; continue; } else { tagsToCreate << tagName; if (parentTagExisted) { parentTagIDForCreation = parentTagID; } parentTagID = 0; parentTagExisted = false; } } } { CoreDbAccess access; foreach(const QString& tagName, tagsToCreate) { tagID = access.db()->addTag(parentTagIDForCreation, tagName, QString(), 0); if (tagID == -1) { break; // something wrong with DB } else { // change signals may be queued within a transaction. We know it changed. d->needUpdateInfos = true; d->needUpdateHash = true; } parentTagIDForCreation = tagID; } } return tagID; } QList TagsCache::createTags(const QStringList& tagPaths) { QList ids; if (!tagPaths.isEmpty()) { foreach(const QString& tagPath, tagPaths) { ids << createTag(tagPath); } } return ids; } QList TagsCache::getOrCreateTags(const QStringList& tagPaths) { QList ids; if (!tagPaths.isEmpty()) { foreach(const QString& tagPath, tagPaths) { ids << getOrCreateTag(tagPath); } } return ids; } int TagsCache::getOrCreateTag(const QString& tagPath) { int id = tagForPath(tagPath); if (!id) { id = createTag(tagPath); // There is a weakness when createTag is called concurrently. // The attempt coming second to CoreDbAccess will fail, but the tag was created // So at least try again finding the tag read-only. if (id == -1) { id = tagForPath(tagPath); } } return id; } int TagsCache::getOrCreateTagWithProperty(const QString& tagPath, const QString& property, const QString& value) { int tagId = getOrCreateTag(tagPath); if (!hasProperty(tagId, property, value)) { TagProperties props(tagId); props.setProperty(property, value); } return tagId; } bool TagsCache::hasProperty(int tagId, const QString& property, const QString& value) const { d->checkProperties(); QReadLocker locker(&d->lock); TagPropertiesRange range = d->findProperties(tagId); for (TagPropertiesConstIterator it = range.first ; it != range.second ; ++it) { if (d->compareProperty(it, property, value)) { return true; } } return false; } QString TagsCache::propertyValue(int tagId, const QString& property) const { d->checkProperties(); QReadLocker locker(&d->lock); TagPropertiesRange range = d->findProperties(tagId); for (TagPropertiesConstIterator it = range.first ; it != range.second ; ++it) { if (it->property == property) { return it->value; } } return QString(); } QStringList TagsCache::propertyValues(int tagId, const QString& property) const { d->checkProperties(); QReadLocker locker(&d->lock); TagPropertiesRange range = d->findProperties(tagId); QStringList values; for (TagPropertiesConstIterator it = range.first ; it != range.second ; ++it) { if (it->property == property) { // the list is ordered by property, after id for (; it != range.second && it->property == property ; ++it) { values << it->value; } return values; } } return values; } QMap TagsCache::properties(int tagId) const { d->checkProperties(); QReadLocker locker(&d->lock); QMap map; TagPropertiesRange range = d->findProperties(tagId); QStringList values; for (TagPropertiesConstIterator it = range.first ; it != range.second ; ++it) { map[it->property] = it->value; } return map; } QList TagsCache::tagsWithProperty(const QString& property, const QString& value) const { d->checkProperties(); QReadLocker locker(&d->lock); QList ids; for (TagPropertiesConstIterator it = d->tagProperties.constBegin() ; it != d->tagProperties.constEnd() ;) { // sort out invalid entries, see bug #277169 if (it->tagId <= 0) { ++it; continue; } if (d->compareProperty(it, property, value)) { ids << it->tagId; it = d->toNextTag(it); } else { ++it; } } return ids; } QList TagsCache::tagsWithPropertyCached(const QString& property) const { d->checkProperties(); { QReadLocker locker(&d->lock); QHash >::const_iterator it; it = d->tagsWithProperty.constFind(property); if (it != d->tagsWithProperty.constEnd()) { return it.value(); } } QList tags = tagsWithProperty(property); { QWriteLocker locker(&d->lock); d->tagsWithProperty[property] = tags; } return tags; } bool TagsCache::isInternalTag(int tagId) const { d->checkProperties(); QReadLocker locker(&d->lock); return d->internalTags.contains(tagId); } QList TagsCache::publicTags(const QList& tagIds) const { d->checkProperties(); QReadLocker locker(&d->lock); QList::const_iterator it, it2; for (it = tagIds.begin() ; it != tagIds.end() ; ++it) { if (d->internalTags.contains(*it)) { break; } } if (it == tagIds.end()) { return tagIds; } QList publicIds; publicIds.reserve(it - tagIds.begin()); // copy to the point of the first internal tag for (it2 = tagIds.begin() ; it2 != it ; ++it2) { publicIds << *it2; } // continue filtering for (; it2 != tagIds.end() ; ++it2) { if (!d->internalTags.contains(*it2)) { publicIds << *it2; } } return publicIds; } bool TagsCache::containsPublicTags(const QList& tagIds) const { d->checkProperties(); QReadLocker locker(&d->lock); foreach(int id, tagIds) { if (!d->internalTags.contains(id)) { return true; } } return false; } bool TagsCache::canBeWrittenToMetadata(int tagId) const { // as long as we always call isInternalTag first, no need to call checkProperties() again //d->checkProperties(); if (isInternalTag(tagId)) { return false; } if (d->sortedListContains(tagsWithPropertyCached(propertyNameExcludedFromWriting()), tagId)) { return false; } return true; } int TagsCache::getOrCreateInternalTag(const QString& tagName) { // ensure the parent tag exists, including the internal property getOrCreateTagWithProperty(tagPathOfDigikamInternalTags(IncludeLeadingSlash), propertyNameDigikamInternalTag()); QString tagPath = tagPathOfDigikamInternalTags(IncludeLeadingSlash) + QLatin1Char('/') + tagName; return getOrCreateTagWithProperty(tagPath, propertyNameDigikamInternalTag()); } void TagsCache::slotTagChanged(const TagChangeset& changeset) { if (changeset.operation() == TagChangeset::Deleted) { QString name = this->tagName(changeset.tagId()); emit tagAboutToBeDeleted(name); } if (!d->changingDB && changeset.operation() != TagChangeset::IconChanged) { invalidate(); } if (changeset.operation() == TagChangeset::Added) { emit tagAdded(changeset.tagId()); } else if (changeset.operation() == TagChangeset::Deleted) { emit tagDeleted(changeset.tagId()); } } int TagsCache::tagForColorLabel(int label) { if (label < FirstColorLabel || label > LastColorLabel) return 0; d->checkLabelTags(); QReadLocker locker(&d->lock); return d->colorLabelsTags[label]; } QVector TagsCache::colorLabelTags() { d->checkLabelTags(); QReadLocker locker(&d->lock); return d->colorLabelsTags; } int TagsCache::colorLabelForTag(int tagId) { d->checkLabelTags(); QReadLocker locker(&d->lock); return d->colorLabelsTags.indexOf(tagId); } int TagsCache::colorLabelFromTags(QList tagIds) { d->checkLabelTags(); QReadLocker locker(&d->lock); foreach(int tagId, tagIds) { - for (int i = FirstColorLabel ; i <= LastColorLabel ; i++) + for (int i = FirstColorLabel ; i <= LastColorLabel ; ++i) { if (d->colorLabelsTags[i] == tagId) { return i; } } } return -1; } int TagsCache::tagForPickLabel(int label) { if (label < FirstPickLabel || label > LastPickLabel) return 0; d->checkLabelTags(); QReadLocker locker(&d->lock); return d->pickLabelsTags[label]; } QVector TagsCache::pickLabelTags() { d->checkLabelTags(); QReadLocker locker(&d->lock); return d->pickLabelsTags; } int TagsCache::pickLabelForTag(int tagId) { d->checkLabelTags(); QReadLocker locker(&d->lock); return d->pickLabelsTags.indexOf(tagId); } int TagsCache::pickLabelFromTags(QList tagIds) { d->checkLabelTags(); QReadLocker locker(&d->lock); foreach(int tagId, tagIds) { - for (int i = FirstPickLabel ; i <= LastPickLabel ; i++) + for (int i = FirstPickLabel ; i <= LastPickLabel ; ++i) { if (d->pickLabelsTags[i] == tagId) { return i; } } } return -1; } QStringList TagsCache::shortenedTagPaths(const QList& ids, QList* sortedIds, LeadingSlashPolicy slashPolicy, HiddenTagsPolicy hiddenTagsPolicy) const { QStringList paths; QList variantIds; // duplicates tagPath(), but we need the additional list of tag ids foreach(int id, ids) { if (hiddenTagsPolicy == IncludeHiddenTags || !isInternalTag(id)) { paths << tagPath(id, slashPolicy); variantIds << id; } } // The code is needed in libdigikamcore, so it cannot be moved here. TODO: Find a good place QStringList shortenedPaths = ItemPropertiesTab::shortenedTagPaths(paths, &variantIds); foreach(const QVariant& var, variantIds) { (*sortedIds) << var.toInt(); } return shortenedPaths; } QStringList TagsCache::shortenedTagPaths(const QList& ids, LeadingSlashPolicy slashPolicy, HiddenTagsPolicy hiddenTagsPolicy) const { return ItemPropertiesTab::shortenedTagPaths(tagPaths(ids, slashPolicy, hiddenTagsPolicy)); } QList TagsCache::Private::tagsForFragment(bool (QString::*stringFunction)(const QString&, Qt::CaseSensitivity cs) const, const QString& fragment, Qt::CaseSensitivity caseSensitivity, HiddenTagsPolicy hiddenTagsPolicy) { checkNameHash(); QList ids; QMultiHash::const_iterator it; const bool excludeHiddenTags = hiddenTagsPolicy == NoHiddenTags; if (excludeHiddenTags) { checkProperties(); } QReadLocker locker(&lock); for (it = nameHash.constBegin() ; it != nameHash.constEnd() ; ++it) { if ((!excludeHiddenTags || !internalTags.contains(it.value())) && (it.key().*stringFunction)(fragment, caseSensitivity)) { ids << it.value(); } } return ids; } QList TagsCache::tagsStartingWith(const QString& fragment, Qt::CaseSensitivity caseSensitivity, HiddenTagsPolicy hiddenTagsPolicy) { return d->tagsForFragment(&QString::startsWith, fragment, caseSensitivity, hiddenTagsPolicy); } QList TagsCache::tagsContaining(const QString& fragment, Qt::CaseSensitivity caseSensitivity, HiddenTagsPolicy hiddenTagsPolicy) { return d->tagsForFragment(&QString::contains, fragment, caseSensitivity, hiddenTagsPolicy); } } // namespace Digikam diff --git a/core/libs/dialogs/dsplashscreen.cpp b/core/libs/dialogs/dsplashscreen.cpp index 76f0b4b7ed..2f1344e429 100644 --- a/core/libs/dialogs/dsplashscreen.cpp +++ b/core/libs/dialogs/dsplashscreen.cpp @@ -1,236 +1,236 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2003-02-10 * Description : a widget to display splash with progress bar * * Copyright (C) 2003-2005 by Renchi Raju * Copyright (C) 2006-2018 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 "dsplashscreen.h" // Qt includes #include #include #include #include #include #include #include #include #include // Local includes #include "digikam_debug.h" #include "daboutdata.h" #include "digikam_version.h" namespace Digikam { class Q_DECL_HIDDEN DSplashScreen::Private { public: explicit Private() { state = 0; progressBarSize = 3; state = 0; messageAlign = Qt::AlignLeft; version = QLatin1String(digikam_version_short); versionColor = Qt::white; messageColor = Qt::white; lastStateUpdateTime = QTime::currentTime(); } int state; int progressBarSize; int messageAlign; QString message; QString version; QColor messageColor; QColor versionColor; QTime lastStateUpdateTime; }; DSplashScreen::DSplashScreen() : QSplashScreen(QPixmap()), d(new Private) { QPixmap splash; if (QApplication::applicationName() == QLatin1String("digikam")) { splash = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("digikam/data/splash-digikam.png")); } else { splash = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("showfoto/data/splash-showfoto.png")); } // Under Linux, only test versions has Beta stage. // cppcheck-suppress redundantAssignment bool isBeta = !QString::fromUtf8(digikam_version_suffix).isEmpty(); #if defined Q_OS_WIN isBeta = true; // Windows version is always beta for the moment. #elif defined Q_OS_OSX isBeta = true; // MAC version is always beta for the moment. #endif if (isBeta) { QPainter p(&splash); p.drawPixmap(380, 27, QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("digikam/data/logo-beta.png"))); p.end(); } setPixmap(splash); QTimer* const timer = new QTimer(this); connect(timer, SIGNAL(timeout()), this, SLOT(slotAnimate())); timer->start(150); } DSplashScreen::~DSplashScreen() { delete d; } void DSplashScreen::setColor(const QColor& color) { d->messageColor = color; } void DSplashScreen::setAlignment(int alignment) { d->messageAlign = alignment; } void DSplashScreen::setMessage(const QString& message) { d->message = message; QSplashScreen::showMessage(d->message, d->messageAlign, d->messageColor); // krazy:exclude=qclasses slotAnimate(); qApp->processEvents(); } void DSplashScreen::slotAnimate() { QTime currentTime = QTime::currentTime(); if (d->lastStateUpdateTime.msecsTo(currentTime) > 100) { d->state = ((d->state + 1) % (2 * d->progressBarSize - 1)); d->lastStateUpdateTime = currentTime; } update(); } void DSplashScreen::drawContents(QPainter* p) { int position = 0; QColor basecolor(155, 192, 231); // -- Draw background circles ------------------------------------ QPainter::RenderHints hints = p->renderHints(); p->setRenderHints(QPainter::Antialiasing); p->setPen(Qt::NoPen); p->setBrush(QColor(225, 234, 231)); p->drawEllipse(21, 6, 9, 9); p->drawEllipse(32, 6, 9, 9); p->drawEllipse(43, 6, 9, 9); p->setRenderHints(hints); // -- Draw animated circles -------------------------------------- // Increments are chosen to get close to background's color // (didn't work well with QColor::light function) - for (int i = 0 ; i < d->progressBarSize ; i++) + for (int i = 0 ; i < d->progressBarSize ; ++i) { position = (d->state + i) % (2 * d->progressBarSize - 1); if (position < 3) { p->setBrush(QColor(basecolor.red() - 18*i, basecolor.green() - 28*i, basecolor.blue() - 10*i)); p->drawEllipse(21 + position*11, 6, 9, 9); } } // We use a device dependent font with a fixed size. QFont fnt(QFontDatabase::systemFont(QFontDatabase::GeneralFont)); fnt.setPixelSize(10); fnt.setBold(false); p->setFont(fnt); QRect r = rect(); r.setCoords(r.x() + 60, r.y() + 4, r.width() - 10, r.height() - 10); // -- Draw message -------------------------------------------------------- // Message is draw at given position, limited to 49 chars // If message is too long, string is truncated if (d->message.length() > 50) { d->message.truncate(49); } p->setPen(d->messageColor); p->drawText(r, d->messageAlign, d->message); // -- Draw version string ------------------------------------------------- QFontMetrics fontMt(fnt); QRect r2 = fontMt.boundingRect(rect(), 0, d->version); r2.moveTopLeft(QPoint(width()-r2.width()-10, r.y())); p->setPen(d->versionColor); p->drawText(r2, Qt::AlignLeft, d->version); // -- Draw slogan and family ---------------------------------------------- // NOTE: splashscreen size is 469*288 pixels r = rect(); r.setCoords(r.x() + 210, r.y() + 215, r.x() + 462, r.y() + 315); p->translate(r.x(), r.y()); QTextDocument slogan; slogan.setDefaultTextOption(QTextOption(Qt::AlignRight | Qt::AlignVCenter)); slogan.setHtml(DAboutData::digiKamSloganFormated()); slogan.setPageSize(r.size()); slogan.setDefaultFont(fnt); slogan.drawContents(p, QRect(0, 0, r.width(), r.height())); } } // namespace Digikam diff --git a/core/libs/dimg/filters/film/filmfilter.cpp b/core/libs/dimg/filters/film/filmfilter.cpp index 0809eb8c2f..41cd450e76 100644 --- a/core/libs/dimg/filters/film/filmfilter.cpp +++ b/core/libs/dimg/filters/film/filmfilter.cpp @@ -1,420 +1,420 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2012-02-05 * Description : film color negative inverter filter * * Copyright (C) 2012 by Matthias Welwarsky * * 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 "filmfilter_p.h" // C++ includes #include // Qt includes #include // Local includes #include "invertfilter.h" namespace Digikam { FilmContainer::FilmContainer() : d(QSharedPointer(new Private)) { } FilmContainer::FilmContainer(CNFilmProfile profile, double gamma, bool sixteenBit) : d(QSharedPointer(new Private)) { d->gamma = gamma; d->sixteenBit = sixteenBit; d->whitePoint = DColor(QColor("white"), sixteenBit); setCNType(profile); } void FilmContainer::setWhitePoint(const DColor& wp) { d->whitePoint = wp; } DColor FilmContainer::whitePoint() const { return d->whitePoint; } void FilmContainer::setExposure(double strength) { d->exposure = strength; } double FilmContainer::exposure() const { return d->exposure; } void FilmContainer::setSixteenBit(bool val) { d->sixteenBit = val; } void FilmContainer::setGamma(double val) { d->gamma = val; } double FilmContainer::gamma() const { return d->gamma; } void FilmContainer::setCNType(CNFilmProfile profile) { d->cnType = profile; switch (profile) { default: d->profile = FilmProfile(1.0, 1.0, 1.0); d->cnType = CNNeutral; break; case CNKodakGold100: d->profile = FilmProfile(1.53, 2.00, 2.40); // check break; case CNKodakGold200: d->profile = FilmProfile(1.53, 2.00, 2.40); // check break; case CNKodakEktar100: d->profile = FilmProfile(1.40, 1.85, 2.34); break; case CNKodakProfessionalPortra160NC: d->profile = FilmProfile(1.49, 1.96, 2.46); // check break; case CNKodakProfessionalPortra160VC: d->profile = FilmProfile(1.56, 2.03, 2.55); // check break; case CNKodakProfessionalPortra400NC: d->profile = FilmProfile(1.69, 2.15, 2.69); // check break; case CNKodakProfessionalPortra400VC: d->profile = FilmProfile(1.78, 2.21, 2.77); // check break; case CNKodakProfessionalPortra800Box: d->profile = FilmProfile(1.89, 2.29, 2.89); // check break; case CNKodakProfessionalPortra800P1: d->profile = FilmProfile(1.53, 2.01, 2.46); // check break; case CNKodakProfessionalPortra800P2: d->profile = FilmProfile(1.74, 2.22, 2.64); // check break; case CNKodakProfessionalNewPortra160: d->profile = FilmProfile(1.41, 1.88, 2.32); break; case CNKodakProfessionalNewPortra400: d->profile = FilmProfile(1.69, 2.15, 2.68); // check break; case CNKodakFarbwelt100: d->profile = FilmProfile(1.86, 2.33, 2.77); // fix, check break; case CNKodakFarbwelt200: d->profile = FilmProfile(1.55, 2.03, 2.42); // check break; case CNKodakFarbwelt400: d->profile = FilmProfile(1.93, 2.43, 2.95); // fix, check break; case CNKodakRoyalGold400: d->profile = FilmProfile(2.24, 2.76, 3.27); // fix, check break; case CNAgfaphotoVistaPlus200: d->profile = FilmProfile(1.70, 2.13, 2.50); break; case CNAgfaphotoVistaPlus400: d->profile = FilmProfile(1.86, 2.35, 2.67); // fix, check break; case CNFujicolorPro160S: d->profile = FilmProfile(1.73, 2.27, 2.53); // fix, check break; case CNFujicolorPro160C: d->profile = FilmProfile(1.96, 2.46, 2.69); // fix, check break; case CNFujicolorNPL160: d->profile = FilmProfile(2.13, 2.36, 2.92); // fix, check break; case CNFujicolorPro400H: d->profile = FilmProfile(1.95, 2.37, 2.62); // fix, check break; case CNFujicolorPro800Z: d->profile = FilmProfile(2.12, 2.37, 2.56); // fix, check break; case CNFujicolorSuperiaReala: d->profile = FilmProfile(1.79, 2.14, 2.49); // check break; case CNFujicolorSuperia100: d->profile = FilmProfile(2.02, 2.46, 2.81); // fix, check break; case CNFujicolorSuperia200: d->profile = FilmProfile(2.11, 2.50, 2.79); // check break; case CNFujicolorSuperiaXtra400: d->profile = FilmProfile(2.11, 2.58, 2.96); // check break; case CNFujicolorSuperiaXtra800: d->profile = FilmProfile(2.44, 2.83, 3.18); // fix, check break; case CNFujicolorTrueDefinition400: d->profile = FilmProfile(1.93, 2.21, 2.39); // fix, check break; case CNFujicolorSuperia1600: d->profile = FilmProfile(2.35, 2.68, 2.96); // fix, check break; } } FilmContainer::CNFilmProfile FilmContainer::cnType() const { return d->cnType; } void FilmContainer::setApplyBalance(bool val) { d->applyBalance = val; } bool FilmContainer::applyBalance() const { return d->applyBalance; } int FilmContainer::whitePointForChannel(int ch) const { int max = d->sixteenBit ? 65535 : 255; switch (ch) { case RedChannel: return d->whitePoint.red(); case GreenChannel: return d->whitePoint.green(); case BlueChannel: return d->whitePoint.blue(); default: return max; } // not reached return max; } double FilmContainer::blackPointForChannel(int ch) const { if (ch == LuminosityChannel || ch == AlphaChannel) return 0.0; return pow(10, -d->profile.dmax(ch)); } double FilmContainer::gammaForChannel(int ch) const { int max = d->sixteenBit ? 65535 : 255; if (ch == GreenChannel || ch == BlueChannel) { double bpc = blackPointForChannel(ch)*d->exposure; double wpc = (double)whitePointForChannel(ch)/(double)max; double bpr = blackPointForChannel(RedChannel)*d->exposure; double wpr = (double)whitePointForChannel(RedChannel)/(double)max; return log10( bpc / wpc ) / log10( bpr / wpr ); } return 1.0; } LevelsContainer FilmContainer::toLevels() const { LevelsContainer l; int max = d->sixteenBit ? 65535 : 255; - for (int i = LuminosityChannel ; i <= AlphaChannel ; i++) + for (int i = LuminosityChannel ; i <= AlphaChannel ; ++i) { l.lInput[i] = blackPointForChannel(i) * max * d->exposure; l.hInput[i] = whitePointForChannel(i) * d->profile.wp(i); l.lOutput[i] = 0; l.hOutput[i] = max; if (d->applyBalance) l.gamma[i] = gammaForChannel(i); else l.gamma[i] = 1.0; } return l; } CBContainer FilmContainer::toCB() const { CBContainer cb; cb.red = d->profile.balance(RedChannel); cb.green = d->profile.balance(GreenChannel); cb.blue = d->profile.balance(BlueChannel); cb.gamma = 1.0; return cb; } QList FilmContainer::profileItemList(QListWidget* view) { QList itemList; QMap::ConstIterator it; for (it = profileMap.constBegin() ; it != profileMap.constEnd() ; ++it) itemList << new ListItem(it.value(), view, (CNFilmProfile)it.key()); return itemList; } QMap FilmContainer::profileMapInitializer() { QMap profileMap; profileMap[CNNeutral] = QLatin1String("Neutral"); profileMap[CNKodakGold100] = QLatin1String("Kodak Gold 100"); profileMap[CNKodakGold200] = QLatin1String("Kodak Gold 200"); profileMap[CNKodakProfessionalNewPortra160] = QLatin1String("Kodak Professional New Portra 160"); profileMap[CNKodakProfessionalNewPortra400] = QLatin1String("Kodak Professional New Portra 400"); profileMap[CNKodakEktar100] = QLatin1String("Kodak Ektar 100"); profileMap[CNKodakFarbwelt100] = QLatin1String("Kodak Farbwelt 100"); profileMap[CNKodakFarbwelt200] = QLatin1String("Kodak Farbwelt 200"); profileMap[CNKodakFarbwelt400] = QLatin1String("Kodak Farbwelt 400"); profileMap[CNKodakProfessionalPortra160NC] = QLatin1String("Kodak Professional Portra 160NC"); profileMap[CNKodakProfessionalPortra160VC] = QLatin1String("Kodak Professional Portra 160VC"); profileMap[CNKodakProfessionalPortra400NC] = QLatin1String("Kodak Professional Portra 400NC"); profileMap[CNKodakProfessionalPortra400VC] = QLatin1String("Kodak Professional Portra 400VC"); profileMap[CNKodakProfessionalPortra800Box] = QLatin1String("Kodak Professional Portra 800 (Box Speed"); profileMap[CNKodakProfessionalPortra800P1] = QLatin1String("Kodak Professional Portra 800 (Push 1 stop"); profileMap[CNKodakProfessionalPortra800P2] = QLatin1String("Kodak Professional Portra 800 (Push 2 stop"); profileMap[CNKodakRoyalGold400] = QLatin1String("Kodak Royal Gold 400"); profileMap[CNAgfaphotoVistaPlus200] = QLatin1String("Agfaphoto Vista Plus 200"); profileMap[CNAgfaphotoVistaPlus400] = QLatin1String("Agfaphoto Vista Plus 400"); profileMap[CNFujicolorPro160S] = QLatin1String("Fujicolor Pro 160S"); profileMap[CNFujicolorPro160C] = QLatin1String("Fujicolor Pro 160C"); profileMap[CNFujicolorNPL160] = QLatin1String("Fujicolor NPL 160"); profileMap[CNFujicolorPro400H] = QLatin1String("Fujicolor Pro 400H"); profileMap[CNFujicolorPro800Z] = QLatin1String("Fujicolor Pro 800Z"); profileMap[CNFujicolorSuperiaReala] = QLatin1String("Fujicolor Superia Reala"); profileMap[CNFujicolorSuperia100] = QLatin1String("Fujicolor Superia 100"); profileMap[CNFujicolorSuperia200] = QLatin1String("Fujicolor Superia 200"); profileMap[CNFujicolorSuperiaXtra400] = QLatin1String("Fujicolor Superia X-Tra 400"); profileMap[CNFujicolorSuperiaXtra800] = QLatin1String("Fujicolor Superia X-Tra 800"); profileMap[CNFujicolorTrueDefinition400] = QLatin1String("Fujicolor Superia True Definition 400"); profileMap[CNFujicolorSuperia1600] = QLatin1String("Fujicolor Superia 1600"); return profileMap; } const QMap FilmContainer::profileMap = FilmContainer::profileMapInitializer(); // ------------------------------------------------------------------ FilmFilter::FilmFilter(QObject* const parent) : DImgThreadedFilter(parent, QLatin1String("FilmFilter")), d(new Private()) { d->film = FilmContainer(); initFilter(); } FilmFilter::FilmFilter(DImg* const orgImage, QObject* const parent, const FilmContainer& settings) : DImgThreadedFilter(orgImage, parent, QLatin1String("FilmFilter")), d(new Private()) { d->film = settings; initFilter(); } FilmFilter::~FilmFilter() { cancelFilter(); delete d; } void FilmFilter::filterImage() { DImg tmpLevel; DImg tmpGamma; DImg tmpInv; LevelsContainer l = d->film.toLevels(); CBContainer cb = d->film.toCB(); CBContainer gamma; // level the image first, this removes the orange mask and corrects // colors according to the density ranges of the film profile LevelsFilter(l, this, m_orgImage, tmpLevel, 0, 40); // in case of a linear raw scan, gamma needs to be // applied after leveling the image, otherwise the image will // look too bright. The standard value is 2.2, but 1.8 is also // frequently found in literature gamma.gamma = d->film.gamma(); CBFilter(gamma, this, tmpLevel, tmpGamma, 40, 80); // invert the image to have a positive image InvertFilter(this, tmpGamma, tmpInv, 80, 100); m_destImage = tmpInv; postProgress(100); } FilterAction FilmFilter::filterAction() { FilterAction action(FilterIdentifier(), CurrentVersion()); action.setDisplayableName(DisplayableName()); action.addParameter(QLatin1String("CNType"), d->film.cnType()); action.addParameter(QLatin1String("ProfileName"), FilmContainer::profileMap[d->film.cnType()]); action.addParameter(QLatin1String("Exposure"), d->film.exposure()); action.addParameter(QLatin1String("Gamma"), d->film.gamma()); action.addParameter(QLatin1String("ApplyColorBalance"), d->film.applyBalance()); action.addParameter(QLatin1String("WhitePointRed"), d->film.whitePoint().red()); action.addParameter(QLatin1String("WhitePointGreen"), d->film.whitePoint().green()); action.addParameter(QLatin1String("WhitePointBlue"), d->film.whitePoint().blue()); action.addParameter(QLatin1String("WhitePointAlpha"), d->film.whitePoint().alpha()); action.addParameter(QLatin1String("WhitePointSixteenBit"), d->film.whitePoint().sixteenBit()); return action; } void FilmFilter::readParameters(const FilterAction& action) { double red = action.parameter(QLatin1String("WhitePointRed")).toDouble(); double green = action.parameter(QLatin1String("WhitePointGreen")).toDouble(); double blue = action.parameter(QLatin1String("WhitePointBlue")).toDouble(); double alpha = action.parameter(QLatin1String("WhitePointAlpha")).toDouble(); bool sb = action.parameter(QLatin1String("WhitePointSixteenBit")).toBool(); bool balance = action.parameter(QLatin1String("ApplyColorBalance")).toBool(); d->film.setWhitePoint(DColor(red, green, blue, alpha, sb)); d->film.setExposure(action.parameter(QLatin1String("Exposure")).toDouble()); d->film.setGamma(action.parameter(QLatin1String("Gamma")).toDouble()); d->film.setCNType((FilmContainer::CNFilmProfile)(action.parameter(QLatin1String("CNType")).toInt())); d->film.setApplyBalance(balance); } } // namespace Digikam diff --git a/core/libs/dimg/filters/fx/colorfxsettings.cpp b/core/libs/dimg/filters/fx/colorfxsettings.cpp index dd4233c199..443b88e209 100644 --- a/core/libs/dimg/filters/fx/colorfxsettings.cpp +++ b/core/libs/dimg/filters/fx/colorfxsettings.cpp @@ -1,425 +1,425 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2012-11-08 * Description : Color effects settings view. * * Copyright (C) 2012 by Alexander Dymo * * 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 "colorfxsettings.h" // Qt includes #include #include #include #include #include #include #include #include #include // Local includes #include "dexpanderbox.h" #include "previewlist.h" #include "imageiface.h" #include "dcombobox.h" #include "dnuminput.h" namespace Digikam { class Q_DECL_HIDDEN ColorFXSettings::Private { public: explicit Private() : stack(0), effectType(0), levelInput(0), iterationInput(0), intensityInput(0), iterationLabel(0), correctionTools(0) { } static const QString configEffectTypeEntry; static const QString configLevelAdjustmentEntry; static const QString configIterationAdjustmentEntry; static const QString configLut3DFilterEntry; static const QString configLut3DIntensityEntry; QStackedWidget* stack; DComboBox* effectType; DIntNumInput* levelInput; DIntNumInput* iterationInput; DIntNumInput* intensityInput; QLabel* iterationLabel; PreviewList* correctionTools; QStringList luts; }; const QString ColorFXSettings::Private::configEffectTypeEntry(QLatin1String("EffectType")); const QString ColorFXSettings::Private::configLevelAdjustmentEntry(QLatin1String("LevelAdjustment")); const QString ColorFXSettings::Private::configIterationAdjustmentEntry(QLatin1String("IterationAdjustment")); const QString ColorFXSettings::Private::configLut3DFilterEntry(QLatin1String("Lut3D Color Correction Filter")); const QString ColorFXSettings::Private::configLut3DIntensityEntry(QLatin1String("Lut3D Color Correction Intensity")); // -------------------------------------------------------- ColorFXSettings::ColorFXSettings(QWidget* const parent, bool useGenericImg) : QWidget(parent), d(new Private) { DImg thumbImage; findLuts(); if (useGenericImg) { thumbImage = DImg(QIcon::fromTheme(QLatin1String("view-preview")).pixmap(128).toImage()); } else { ImageIface iface; thumbImage = iface.original()->smoothScale(128, 128, Qt::KeepAspectRatio); } // ------------------------------------------------------------- const int spacing = QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); QGridLayout* const grid = new QGridLayout(parent); QLabel* const effectTypeLabel = new QLabel(i18n("Type:"), parent); d->effectType = new DComboBox(parent); d->effectType->addItem(i18n("Solarize")); d->effectType->addItem(i18n("Vivid")); d->effectType->addItem(i18n("Neon")); d->effectType->addItem(i18n("Find Edges")); d->effectType->addItem(i18n("Lut3D")); d->effectType->setDefaultIndex(ColorFXFilter::Solarize); d->effectType->setWhatsThis(i18n("

Select the effect type to apply to the image here.

" "

Solarize: simulates solarization of photograph.

" "

Vivid: simulates the Velvia(tm) slide film colors.

" "

Neon: coloring the edges in a photograph to " "reproduce a fluorescent light effect.

" "

Find Edges: detects the edges in a photograph " "and their strength.

" "

Lut3D: coloring images with Lut3D filters

")); d->stack = new QStackedWidget(parent); grid->addWidget(effectTypeLabel, 0, 0, 1, 1); grid->addWidget(d->effectType, 1, 0, 1, 1); grid->addWidget(new DLineWidget(Qt::Horizontal, parent), 2, 0, 1, 1); grid->addWidget(d->stack, 3, 0, 1, 1); grid->setRowStretch(3, 10); grid->setContentsMargins(spacing, spacing, spacing, spacing); grid->setSpacing(spacing); // ------------------------------------------------------------- QWidget* const solarizeSettings = new QWidget(d->stack); QGridLayout* const grid1 = new QGridLayout(solarizeSettings); QLabel* const levelLabel = new QLabel(i18nc("level of the effect", "Level:"), solarizeSettings); d->levelInput = new DIntNumInput(solarizeSettings); d->levelInput->setRange(0, 100, 1); d->levelInput->setDefaultValue(3); d->levelInput->setWhatsThis( i18n("Set here the level of the effect.")); d->iterationLabel = new QLabel(i18n("Iteration:"), solarizeSettings); d->iterationInput = new DIntNumInput(solarizeSettings); d->iterationInput->setRange(0, 100, 1); d->iterationInput->setDefaultValue(2); d->iterationInput->setWhatsThis(i18n("This value controls the number of iterations " "to use with the Neon and Find Edges effects.")); grid1->addWidget(levelLabel, 0, 0, 1, 1); grid1->addWidget(d->levelInput, 1, 0, 1, 1); grid1->addWidget(d->iterationLabel, 2, 0, 1, 1); grid1->addWidget(d->iterationInput, 3, 0, 1, 1); grid1->setRowStretch(4, 10); grid1->setContentsMargins(QMargins()); grid1->setSpacing(0); d->stack->insertWidget(0, solarizeSettings); // ------------------------------------------------------------- QWidget* const lut3DSettings = new QWidget(d->stack); QGridLayout* const grid2 = new QGridLayout(lut3DSettings); d->correctionTools = new PreviewList(lut3DSettings); - for (int idx = 0; idx < d->luts.count(); idx++) + for (int idx = 0 ; idx < d->luts.count() ; ++idx) { ColorFXContainer prm; prm.colorFXType = ColorFXFilter::Lut3D; prm.path = d->luts[idx]; QFileInfo fi(prm.path); d->correctionTools->addItem(new ColorFXFilter(&thumbImage, lut3DSettings, prm), translateLuts(fi.baseName()), idx); } QLabel* const intensityLabel = new QLabel(i18n("Intensity:"), lut3DSettings); d->intensityInput = new DIntNumInput(lut3DSettings); d->intensityInput->setRange(1, 100, 1); d->intensityInput->setDefaultValue(100); d->intensityInput->setWhatsThis(i18n("Set here the intensity of the filter.")); grid2->addWidget(d->correctionTools, 0, 0, 1, 1); grid2->addWidget(intensityLabel, 1, 0, 1, 1); grid2->addWidget(d->intensityInput, 2, 0, 1, 1); grid2->setRowStretch(0, 10); grid2->setContentsMargins(QMargins()); grid2->setSpacing(0); d->stack->insertWidget(1, lut3DSettings); // ------------------------------------------------------------- connect(d->effectType, SIGNAL(activated(int)), this, SLOT(slotEffectTypeChanged(int))); connect(d->levelInput, SIGNAL(valueChanged(int)), this, SIGNAL(signalSettingsChanged())); connect(d->iterationInput, SIGNAL(valueChanged(int)), this, SIGNAL(signalSettingsChanged())); connect(d->correctionTools, SIGNAL(itemSelectionChanged()), this, SIGNAL(signalSettingsChanged())); connect(d->intensityInput, SIGNAL(valueChanged(int)), this, SIGNAL(signalSettingsChanged())); } ColorFXSettings::~ColorFXSettings() { delete d; } void ColorFXSettings::startPreviewFilters() { d->correctionTools->startFilters(); } void ColorFXSettings::slotEffectTypeChanged(int type) { d->iterationInput->blockSignals(true); d->levelInput->blockSignals(true); int w = (type == ColorFXFilter::Lut3D ? 1 : 0); d->stack->setCurrentWidget(d->stack->widget(w)); switch (type) { case ColorFXFilter::Solarize: d->levelInput->setRange(0, 100, 1); d->levelInput->setValue(20); d->iterationInput->setEnabled(false); d->iterationLabel->setEnabled(false); break; case ColorFXFilter::Vivid: d->levelInput->setRange(0, 50, 1); d->levelInput->setValue(5); d->iterationInput->setEnabled(false); d->iterationLabel->setEnabled(false); break; case ColorFXFilter::Neon: case ColorFXFilter::FindEdges: d->levelInput->setRange(0, 5, 1); d->levelInput->setValue(3); d->iterationInput->setEnabled(true); d->iterationLabel->setEnabled(true); d->iterationInput->setRange(0, 5, 1); d->iterationInput->setValue(2); break; } d->iterationInput->blockSignals(false); d->levelInput->blockSignals(false); emit signalSettingsChanged(); } ColorFXContainer ColorFXSettings::settings() const { ColorFXContainer prm; prm.colorFXType = d->effectType->currentIndex(); prm.level = d->levelInput->value(); prm.iterations = d->iterationInput->value(); prm.intensity = d->intensityInput->value(); prm.path = d->luts.value(d->correctionTools->currentId()); return prm; } void ColorFXSettings::setSettings(const ColorFXContainer& settings) { blockSignals(true); d->effectType->setCurrentIndex(settings.colorFXType); slotEffectTypeChanged(settings.colorFXType); d->levelInput->setValue(settings.level); d->iterationInput->setValue(settings.iterations); int filterId = d->luts.indexOf(settings.path); if (filterId == -1) { filterId = 0; } d->intensityInput->setValue(settings.intensity); d->correctionTools->setCurrentId(filterId); blockSignals(false); } void ColorFXSettings::resetToDefault() { setSettings(defaultSettings()); } ColorFXContainer ColorFXSettings::defaultSettings() const { return ColorFXContainer(); } void ColorFXSettings::readSettings(KConfigGroup& group) { ColorFXContainer prm; ColorFXContainer defaultPrm = defaultSettings(); prm.colorFXType = group.readEntry(d->configEffectTypeEntry, defaultPrm.colorFXType); prm.level = group.readEntry(d->configLevelAdjustmentEntry, defaultPrm.level); prm.iterations = group.readEntry(d->configIterationAdjustmentEntry, defaultPrm.iterations); prm.intensity = group.readEntry(d->configLut3DIntensityEntry, defaultPrm.intensity); prm.path = group.readEntry(d->configLut3DFilterEntry, defaultPrm.path); setSettings(prm); } void ColorFXSettings::writeSettings(KConfigGroup& group) { ColorFXContainer prm = settings(); group.writeEntry(d->configEffectTypeEntry, prm.colorFXType); group.writeEntry(d->configLevelAdjustmentEntry, prm.level); group.writeEntry(d->configIterationAdjustmentEntry, prm.iterations); group.writeEntry(d->configLut3DIntensityEntry, prm.intensity); group.writeEntry(d->configLut3DFilterEntry, prm.path); } void ColorFXSettings::findLuts() { QStringList dirpaths; dirpaths << QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QLatin1String("digikam/data/lut3d"), QStandardPaths::LocateDirectory); foreach (const QString& dirpath, dirpaths) { QDirIterator dirIt(dirpath, QDirIterator::Subdirectories); while (dirIt.hasNext()) { dirIt.next(); if (QFileInfo(dirIt.filePath()).isFile()) { d->luts << dirIt.filePath(); } } } d->luts.sort(); } QString ColorFXSettings::translateLuts(const QString& name) const { if (name.toLower() == QLatin1String("bleach")) { return i18n("Bleach"); } else if (name.toLower() == QLatin1String("blue_crush")) { return i18n("Blue Crush"); } else if (name.toLower() == QLatin1String("bw_contrast")) { return i18n("BW Contrast"); } else if (name.toLower() == QLatin1String("instant")) { return i18n("Instant"); } else if (name.toLower() == QLatin1String("original")) { return i18n("Original"); } else if (name.toLower() == QLatin1String("punch")) { return i18n("Punch"); } else if (name.toLower() == QLatin1String("summer")) { return i18n("Summer"); } else if (name.toLower() == QLatin1String("tokyo")) { return i18n("Tokyo"); } else if (name.toLower() == QLatin1String("vintage")) { return i18n("Vintage"); } else if (name.toLower() == QLatin1String("washout")) { return i18n("Washout"); } else if (name.toLower() == QLatin1String("washout_color")) { return i18n("Washout Color"); } else if (name.toLower() == QLatin1String("x_process")) { return i18n("X Process"); } return name; } } // namespace Digikam diff --git a/core/libs/dimg/filters/nr/nrestimate.cpp b/core/libs/dimg/filters/nr/nrestimate.cpp index 0514f6cf09..f8d7856112 100644 --- a/core/libs/dimg/filters/nr/nrestimate.cpp +++ b/core/libs/dimg/filters/nr/nrestimate.cpp @@ -1,506 +1,506 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2012-10-18 * Description : Wavelets YCrCb Noise Reduction settings estimation by image content analys. * Wavelets theory is based on "À Trous" Discrete Wavelet Transform * described into "The Handbook of Astronomical Image Processing" book * from Richard Berry and James Burnell, chapter 18. * See this wiki page for details: * http://community.kde.org/Digikam/SoK2012/AutoNR * * Copyright (C) 2012-2013 by Sayantan Datta * Copyright (C) 2012-2018 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 "nrestimate.h" // C++ includes #include #include // OpenCV includes #include "digikam_opencv.h" // Qt includes. #include #include // Local includes #include "digikam_debug.h" #include "nrfilter.h" namespace Digikam { class Q_DECL_HIDDEN NREstimate::Private { public: explicit Private() : clusterCount(30), size(512) { for (int c = 0 ; c < 3 ; ++c) { fimg[c] = 0; } } NRContainer prm; QString path; // Path to host log files. float* fimg[3]; const uint clusterCount; const uint size; // Size of squared original image. }; NREstimate::NREstimate(DImg* const img, QObject* const parent) : DImgThreadedAnalyser(parent, QLatin1String("NREstimate")), d(new Private) { // Use the Top/Left corner of 256x256 pixels to analys noise contents from image. // This will speed-up computation time with OpenCV int w = (img->width() > d->size) ? d->size : img->width(); int h = (img->height() > d->size) ? d->size : img->height(); setOriginalImage(img->copy(0, 0, w, h)); } NREstimate::~NREstimate() { delete d; } void NREstimate::setLogFilesPath(const QString& path) { d->path = path; } void NREstimate::readImage() const { DColor col; for (int c = 0 ; runningFlag() && (c < 3) ; ++c) { d->fimg[c] = new float[m_orgImage.numPixels()]; } int j = 0; for (uint y = 0 ; runningFlag() && (y < m_orgImage.height()) ; ++y) { for (uint x = 0 ; runningFlag() && (x < m_orgImage.width()) ; ++x) { col = m_orgImage.getPixelColor(x, y); d->fimg[0][j] = col.red(); d->fimg[1][j] = col.green(); d->fimg[2][j] = col.blue(); j++; } } } NRContainer NREstimate::settings() const { return d->prm; } void NREstimate::startAnalyse() { readImage(); postProgress(5); //--convert fimg to CvMat*------------------------------------------------------------------------------- // convert the image into YCrCb color model NRFilter::srgb2ycbcr(d->fimg, m_orgImage.numPixels()); // One dimensional CvMat which stores the image CvMat* points = cvCreateMat(m_orgImage.numPixels(), 3, CV_32FC1); // matrix to store the index of the clusters CvMat* clusters = cvCreateMat(m_orgImage.numPixels(), 1, CV_32SC1); // pointer variable to handle the CvMat* points (the image in CvMat format) float* pointsPtr = reinterpret_cast(points->data.ptr); for (uint x = 0 ; runningFlag() && (x < m_orgImage.numPixels()) ; ++x) { for (int y = 0 ; runningFlag() && (y < 3) ; ++y) { *pointsPtr++ = (float)d->fimg[y][x]; } } // Array to store the centers of the clusters CvArr* centers = 0; qCDebug(DIGIKAM_DIMG_LOG) << "Everything ready for the cvKmeans2 or as it seems to"; postProgress(10); //-- KMEANS --------------------------------------------------------------------------------------------- cvKMeans2(points, d->clusterCount, clusters, cvTermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 10, 1.0), 3, 0, 0, centers, 0); qCDebug(DIGIKAM_DIMG_LOG) << "cvKmeans2 successfully run"; postProgress(15); //-- Divide into cluster->columns, sample->rows, in matrix standard deviation --------------------------- QScopedArrayPointer rowPosition(new int[d->clusterCount]); // The row position array would just make the hold the number of elements in each cluster for (uint i = 0 ; runningFlag() && (i < d->clusterCount) ; ++i) { //initializing the cluster count array rowPosition[i] = 0; } int rowIndex = 0; int columnIndex = 0; for (uint i = 0 ; runningFlag() && (i < m_orgImage.numPixels()) ; ++i) { columnIndex = clusters->data.i[i]; rowPosition[columnIndex]++; } /* qCDebug(DIGIKAM_DIMG_LOG) << "Lets see what the rowPosition array looks like : "; - for (uint i = 0 ; runningFlag() && (i < d->clusterCount) ; i++) + for (uint i = 0 ; runningFlag() && (i < d->clusterCount) ; ++i) { qCDebug(DIGIKAM_DIMG_LOG) << "Cluster : "<< i << " the count is :" << rowPosition[i]; } */ qCDebug(DIGIKAM_DIMG_LOG) << "array indexed, and ready to find maximum"; postProgress(20); //-- Finding maximum of the rowPosition array ------------------------------------------------------------ int max = rowPosition[0]; for (uint i = 1 ; runningFlag() && (i < d->clusterCount) ; ++i) { if (rowPosition[i] > max) { max = rowPosition[i]; } } QString maxString; maxString.append(QString::number(max)); qCDebug(DIGIKAM_DIMG_LOG) << QString::fromLatin1("maximum declared = %1").arg(maxString); postProgress(25); //-- Divide and conquer --------------------------------------------------------------------------------- CvMat* sd = cvCreateMat(max, (d->clusterCount * points->cols), CV_32FC1); postProgress(30); //-- Initialize the rowPosition array ------------------------------------------------------------------- QScopedArrayPointer rPosition(new int[d->clusterCount]); for (uint i = 0 ; runningFlag() && (i < d->clusterCount) ; ++i) { rPosition[i] = 0; } float* ptr = 0; qCDebug(DIGIKAM_DIMG_LOG) << "The rowPosition array is ready!"; postProgress(40); for (uint i = 0 ; runningFlag() && (i < m_orgImage.numPixels()) ; ++i) { columnIndex = clusters->data.i[i]; rowIndex = rPosition[columnIndex]; //moving to the right row ptr = reinterpret_cast(sd->data.ptr + rowIndex*(sd->step)); //moving to the right column for (int j = 0 ; runningFlag() && (j < columnIndex) ; ++j) { for (int z = 0 ; runningFlag() && (z < (points->cols)) ; ++z) { ptr++; } } for (int z = 0 ; runningFlag() && (z < (points->cols)) ; ++z) { *ptr++ = cvGet2D(points, i, z).val[0]; } rPosition[columnIndex] = rPosition[columnIndex] + 1; } qCDebug(DIGIKAM_DIMG_LOG) << "sd matrix creation over!"; postProgress(50); //-- This part of the code would involve the sd matrix and make the mean and the std of the data ------------------- CvScalar std; CvScalar mean; int totalcount = 0; // Number of non-empty clusters CvMat* meanStore = cvCreateMat(d->clusterCount, points->cols, CV_32FC1); CvMat* stdStore = cvCreateMat(d->clusterCount, points->cols, CV_32FC1); float* meanStorePtr = reinterpret_cast(meanStore->data.ptr); float* stdStorePtr = reinterpret_cast(stdStore->data.ptr); for (int i = 0 ; runningFlag() && (i < sd->cols) ; ++i) { if (runningFlag() && (rowPosition[(i/points->cols)] >= 1)) { CvMat* workingArr = cvCreateMat(rowPosition[(i / points->cols)], 1, CV_32FC1); ptr = reinterpret_cast(workingArr->data.ptr); for (int j = 0 ; runningFlag() && (j < rowPosition[(i / (points->cols))]) ; ++j) { *ptr++ = cvGet2D(sd, j, i).val[0]; } cvAvgSdv(workingArr, &mean, &std); *meanStorePtr++ = (float)mean.val[0]; *stdStorePtr++ = (float)std.val[0]; totalcount++; cvReleaseMat(&workingArr); } } qCDebug(DIGIKAM_DIMG_LOG) << "Make the mean and the std of the data"; postProgress(60); // ----------------------------------------------------------------------------------------------------------------- meanStorePtr = reinterpret_cast(meanStore->data.ptr); stdStorePtr = reinterpret_cast(stdStore->data.ptr); if (runningFlag() && !d->path.isEmpty()) { QString logFile = d->path; logFile = logFile.section(QLatin1Char('/'), -1); logFile = logFile.left(logFile.indexOf(QLatin1Char('.'))); logFile.append(QLatin1String("logMeanStd.txt")); QFile filems(logFile); if (filems.open(QIODevice::WriteOnly | QIODevice::Text)) { QTextStream oms(&filems); oms << "Mean Data\n"; for (int i = 0 ; i < totalcount ; ++i) { oms << *meanStorePtr++; oms << "\t"; if ((i+1)%3 == 0) { oms << "\n"; } } oms << "\nStd Data\n"; for (int i = 0 ; i < totalcount ; ++i) { oms << *stdStorePtr++; oms << "\t"; if ((i+1)%3 == 0) { oms << "\n"; } } filems.close(); qCDebug(DIGIKAM_DIMG_LOG) << "Done with the basic work of storing the mean and the std"; } } postProgress(70); //-- Calculating weighted mean, and weighted std ----------------------------------------------------------- QTextStream owms; QFile filewms; if (runningFlag() && !d->path.isEmpty()) { QString logFile2 = d->path; logFile2 = logFile2.section(QLatin1Char('/'), -1); logFile2 = logFile2.left(logFile2.indexOf(QLatin1Char('.'))); logFile2.append(QLatin1String("logWeightedMeanStd.txt")); filewms.setFileName(logFile2); if (filewms.open(QIODevice::WriteOnly | QIODevice::Text)) owms.setDevice(&filewms); } QString info; float weightedMean = 0.0f; float weightedStd = 0.0f; float datasd[3] = {0.0f, 0.0f, 0.0f}; for (int j = 0 ; runningFlag() && (j < points->cols) ; ++j) { meanStorePtr = reinterpret_cast(meanStore->data.ptr); stdStorePtr = reinterpret_cast(stdStore->data.ptr); for (int moveToChannel = 0 ; moveToChannel <= j ; ++moveToChannel) { meanStorePtr++; stdStorePtr++; } for (uint i = 0 ; i < d->clusterCount ; ++i) { if (rowPosition[i] >= 1) { weightedMean += (*meanStorePtr) * rowPosition[i]; weightedStd += (*stdStorePtr) * rowPosition[i]; meanStorePtr += points->cols; stdStorePtr += points->cols; } } weightedMean = weightedMean / (m_orgImage.numPixels()); weightedStd = weightedStd / (m_orgImage.numPixels()); datasd[j] = weightedStd; if (!d->path.isEmpty()) { owms << QLatin1String("\nChannel : ") << j << QLatin1Char('\n'); owms << QLatin1String("Weighted Mean : ") << weightedMean << QLatin1Char('\n'); owms << QLatin1String("Weighted Std : ") << weightedStd << QLatin1Char('\n'); } info.append(QLatin1String("\n\nChannel: ")); info.append(QString::number(j)); info.append(QLatin1String("\nWeighted Mean: ")); info.append(QString::number(weightedMean)); info.append(QLatin1String("\nWeighted Standard Deviation: ")); info.append(QString::number(weightedStd)); } if (runningFlag() && !d->path.isEmpty()) { filewms.close(); } qCDebug(DIGIKAM_DIMG_LOG) << "Info : " << info; postProgress(80); // -- adaptation --------------------------------------------------------------------------------------- double L = 1.2; double LSoft = 0.9; double Cr = 1.2; double CrSoft = 0.9; double Cb = 1.2; double CbSoft = 0.9; if (runningFlag()) { // for 16 bits images only if (m_orgImage.sixteenBit()) { for (int i = 0 ; i < points->cols ; ++i) { if (i < 3) datasd[i] = datasd[i] / 256; } } if (datasd[0] < 7) L = datasd[0] - 0.98; else if (datasd[0] >= 7 && datasd[0] < 8) L = datasd[0] - 1.2; else if (datasd[0] >= 8 && datasd[0] < 9) L = datasd[0] - 1.5; else L = datasd[0] - 1.7; if (L < 0) L = 0; if (L > 9) L = 9; Cr = datasd[2] * 0.8; Cb = datasd[1] * 0.8; if (Cr > 7) Cr = 7; if (Cb > 7) Cb = 7; L = floorf(L * 100) / 100; Cb = floorf(Cb * 100) / 100; Cr = floorf(Cr * 100) / 100; if ( L > 9 ) LSoft = CrSoft = CbSoft = 0.8; else if ( L > 3) LSoft = CrSoft = CbSoft = 0.7; else LSoft = CrSoft = CbSoft = 0.6; } d->prm.thresholds[0] = L; d->prm.thresholds[1] = Cb; d->prm.thresholds[2] = Cr; d->prm.softness[0] = LSoft; d->prm.softness[1] = CbSoft; d->prm.softness[2] = CrSoft; qCDebug(DIGIKAM_DIMG_LOG) << "All is completed"; postProgress(90); //-- releasing matrices and closing files ---------------------------------------------------------------------- cvReleaseMat(&sd); cvReleaseMat(&stdStore); cvReleaseMat(&meanStore); cvReleaseMat(&points); cvReleaseMat(&clusters); for (uint i = 0 ; i < 3 ; ++i) { delete [] d->fimg[i]; } postProgress(100); } } // namespace Digikam diff --git a/core/libs/facesengine/preprocessing/shape-predictor/matrixoperations.h b/core/libs/facesengine/preprocessing/shape-predictor/matrixoperations.h index 1f216160b0..736e6ae1ea 100644 --- a/core/libs/facesengine/preprocessing/shape-predictor/matrixoperations.h +++ b/core/libs/facesengine/preprocessing/shape-predictor/matrixoperations.h @@ -1,579 +1,579 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 17-8-2016 * Description : Some matrix utility functions including singular value * decomposition, inverse, and pseudo-inverse. * * Copyright (C) 2016 by Omar Amin * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #ifndef DIGIKAM_MATRIX_OPERATIONS_H #define DIGIKAM_MATRIX_OPERATIONS_H // C++ includes #include #include // Local includes #include "digikam_opencv.h" namespace Digikam { std::vector > inv2(const std::vector >& mat) { assert(mat.size() == 2 && mat[0].size() == 2); std::vector > m(2,std::vector(2, 0)); float det = mat[0][0] * mat[1][1] - mat[0][1]*mat[1][0]; assert(det != 0); m[0][0] = mat[1][1]/det; m[0][1] = - mat[0][1]/det; m[1][0] = - mat[1][0]/det; m[1][1] = mat[0][0]/det; return m; } std::vector > pinv(const std::vector >& mat) { std::vector > result(mat[0].size(), std::vector(mat.size())); cv::Mat B(mat[0].size(), mat.size() , CV_32FC1); cv::Mat A(mat.size() , mat[0].size(), CV_32FC1); - for (unsigned int i = 0 ; i < mat.size() ; i++) + for (unsigned int i = 0 ; i < mat.size() ; ++i) { - for (unsigned int j =0 ; j < mat[0].size() ; j++) + for (unsigned int j =0 ; j < mat[0].size() ; ++j) { A.at(i, j) = mat[i][j]; } } cv::invert(A, B, cv::DECOMP_SVD); - for (int i = 0 ; i < B.rows ; i++) + for (int i = 0 ; i < B.rows ; ++i) { - for (int j = 0 ; j < B.cols ; j++) + for (int j = 0 ; j < B.cols ; ++j) { result[i][j] = B.at(i, j); } } return result; } void stdmattocvmat(const std::vector >& src, cv::Mat& dst) { - for (unsigned int i = 0 ; i < src.size() ; i++) + for (unsigned int i = 0 ; i < src.size() ; ++i) { - for (unsigned int j = 0 ; j < src[0].size() ; j++) + for (unsigned int j = 0 ; j < src[0].size() ; ++j) { dst.at(i, j) = src[i][j]; } } } void cvmattostdmat(const cv::Mat& dst, std::vector >& src) { - for (unsigned int i = 0 ; i < src.size() ; i++) + for (unsigned int i = 0 ; i < src.size() ; ++i) { - for (unsigned int j = 0 ; j < src[0].size() ; j++) + for (unsigned int j = 0 ; j < src[0].size() ; ++j) { src[i][j] = dst.at(i, j); } } } template inline T signdlib(const T& a, const T& b) { if (b < 0) { return -std::abs(a); } else { return std::abs(a); } } template inline T pythag(const T& a, const T& b) { T absa = std::abs(a); T absb = std::abs(b); if (absa > absb) { T val = absb/absa; val *= val; return (absa * std::sqrt(1.0 + val)); } else { if (absb == 0.0) { return 0.0; } else { T val = absa/absb; val *= val; return (absb * std::sqrt(1.0 + val)); } } } void transpose(std::vector >& src, std::vector >& dst) { - for(unsigned int i = 0;i >& src) { float result = 0; - for (unsigned int i = 0 ; i < src.size() ; i++) + for (unsigned int i = 0 ; i < src.size() ; ++i) { - for (unsigned int j = 0 ; j < src[0].size() ; j++) + for (unsigned int j = 0 ; j < src[0].size() ; ++j) { if (i == j) { result += src[i][i]; } } } return result; } bool svd3(std::vector >& a, std::vector& w, std::vector >& v, std::vector& rv1) { const float one = 1.0; const long max_iter = 300; // a is a square matrix // columns const long n = a.size(); // rows const long m = a.size(); const float eps = std::numeric_limits::epsilon(); long nm = 0; long l = 0; float g = 0.0; float scale = 0.0; float anorm = 0.0; bool flag; float c, f, h, s, x, y, z; for (long i = 0 ; i < n ; ++i) { l = i+1; rv1[i] = scale * g; g = 0.0; s = 0.0; scale = 0.0; if (i < m) { for (long k = i ; k < m ; ++k) { scale += std::abs(a[k][i]); } if (scale) { for (long k = i ; k < m ; ++k) { a[k][i] /= scale; s += a[k][i] * a[k][i]; } f = a[i][i]; g = -signdlib(std::sqrt(s), f); h = f * g - s; a[i][i] = f - g; for (long j = l ; j < n ; ++j) { s = 0.0; for (long k = i ; k < m ; ++k) { s += a[k][i] * a[k][j]; } f = s / h; for (long k = i ; k < m ; ++k) { a[k][j] += f * a[k][i]; } } for (long k = i ; k < m ; ++k) { a[k][i] *= scale; } } } w[i] = scale *g; g = 0.0; s = 0.0; scale = 0.0; if (i < m && i < n-1) { for (long k = l ; k < n ; ++k) { scale += std::abs(a[i][k]); } if (scale) { for (long k = l ; k < n ; ++k) { a[i][k] /= scale; s += a[i][k] * a[i][k]; } f = a[i][l]; g = -signdlib(std::sqrt(s), f); h = f * g - s; a[i][l] = f - g; for (long k = l ; k < n ; ++k) { rv1[k] = a[i][k] / h; } for (long j = l ; j < m ; ++j) { s = 0.0; for (long k = l; k < n; ++k) { s += a[j][k] * a[i][k]; } for (long k = l; k < n; ++k) { a[j][k] += s * rv1[k]; } } for (long k = l ; k < n ; ++k) { a[i][k] *= scale; } } } anorm = std::max(anorm, (std::abs(w[i]) + std::abs(rv1[i]))); } for (long i = n-1 ; i >= 0 ; --i) { if (i < n-1) { if (g != 0) { for (long j = l ; j < n ; ++j) { v[j][i] = (a[i][j] / a[i][l]) / g; } for (long j = l ; j < n ; ++j) { s = 0.0; for (long k = l ; k < n ; ++k) { s += a[i][k] * v[k][j]; } for (long k = l ; k < n ; ++k) { v[k][j] += s*v[k][i]; } } } for (long j = l ; j < n ; ++j) { v[i][j] = v[j][i] = 0.0; } } v[i][i] = 1.0; g = rv1[i]; l = i; } for (long i = std::min(m,n) - 1 ; i >= 0 ; --i) { l = i + 1; g = w[i]; for (long j = l ; j < n ; ++j) { a[i][j] = 0.0; } if (g != 0) { g = 1.0 / g; for (long j = l ; j < n ; ++j) { s = 0.0; for (long k = l ; k < m ; ++k) { s += a[k][i] * a[k][j]; } f = (s / a[i][i]) * g; for (long k = i ; k < m ; ++k) { a[k][j] += f * a[k][i]; } } for (long j = i ; j < m ; ++j) { a[j][i] *= g; } } else { for (long j = i ; j < m ; ++j) { a[j][i] = 0.0; } } ++a[i][i]; } for (long k = n-1 ; k >= 0 ; --k) { for (long its = 1 ; its <= max_iter ; ++its) { flag = true; for (l = k ; l >= 1 ; --l) { nm = l - 1; if (std::abs(rv1[l]) <= eps * anorm) { flag = false; break; } if (std::abs(w[nm]) <= eps * anorm) { break; } } if (flag) { c = 0.0; s = 1.0; for (long i = l ; i <= k ; ++i) { f = s * rv1[i]; rv1[i] = c * rv1[i]; if (std::abs(f) <= eps * anorm) break; g = w[i]; h = pythag(f, g); w[i] = h; h = 1.0 / h; c = g * h; s = -f * h; for (long j = 0 ; j < m ; ++j) { y = a[j][nm]; z = a[j][i]; a[j][nm] = y * c + z * s; a[j][i] = z * c - y * s; } } } z = w[k]; if (l == k) { if (z < 0.0) { w[k] = -z; for (long j = 0 ; j < n ; ++j) { v[j][k] = -v[j][k]; } } break; } if (its == max_iter) return false; x = w[l]; nm = k - 1; y = w[nm]; g = rv1[nm]; h = rv1[k]; f = ((y - z) * (y + z) + (g - h) * (g + h)) / (2.0 * h * y); g = pythag(f, one); f = ((x - z) * (x + z) + h * ((y / (f + signdlib(g, f))) - h)) / x; c = s = 1.0; for (long j = l ; j <= nm ; ++j) { long i = j + 1; g = rv1[i]; y = w[i]; h = s * g; g = c * g; z = pythag(f, h); rv1[j] = z; c = f/z; s = h/z; f = x*c + g*s; g = g*c - x*s; h = y*s; y *= c; for (long jj = 0 ; jj < n ; ++jj) { x = v[jj][j]; z = v[jj][i]; v[jj][j] = x*c + z*s; v[jj][i] = z*c - x*s; } z = pythag(f,h); w[j] = z; if (z != 0) { z = 1.0 / z; c = f * z; s = h * z; } f = c*g + s*y; x = c*y - s*g; for (long jj = 0 ; jj < m ; ++jj) { y = a[jj][j]; z = a[jj][i]; a[jj][j] = y*c + z*s; a[jj][i] = z*c - y*s; } } rv1[l] = 0.0; rv1[k] = f; w[k] = x; } } return true; } void svd(const std::vector >& m, std::vector >& u, std::vector >& w, std::vector >& v) { // initialization u.resize(2); w.resize(2); v.resize(2); - for(unsigned int i = 0 ; i < 2 ; i++) + for (unsigned int i = 0 ; i < 2 ; ++i) { u[i].resize(2); w[i].resize(2); v[i].resize(2); - for(unsigned int j = 0 ; j < 2 ; j++) + for (unsigned int j = 0 ; j < 2 ; ++j) { u[i][j] = m[i][j]; } } std::vector W(2); std::vector rv1(2); svd3(u,W,v,rv1); // get w from W - for(unsigned int i = 0 ; i < 2 ; i++) + for (unsigned int i = 0 ; i < 2 ; ++i) { - for(unsigned int j = 0 ; j < 2 ; j++) + for (unsigned int j = 0 ; j < 2 ; ++j) { if (i == j) w[i][j] = W[i]; else w[i][j] = 0.00; } } } float determinant(const std::vector >& u) { float result = u[0][0]*u[1][1] - u[1][0]*u[0][1]; return result; } } // namespace Digikam #endif // DIGIKAM_MATRIX_OPERATIONS_H diff --git a/core/libs/facesengine/preprocessing/shape-predictor/pointtransformaffine.h b/core/libs/facesengine/preprocessing/shape-predictor/pointtransformaffine.h index 88d0e833ae..c24197cc69 100644 --- a/core/libs/facesengine/preprocessing/shape-predictor/pointtransformaffine.h +++ b/core/libs/facesengine/preprocessing/shape-predictor/pointtransformaffine.h @@ -1,217 +1,217 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 16/08/2016 * Description : point transform class and its utilities that models * affine transformations between two sets of 2d-points. * * Copyright (C) 2016 by Omar Amin * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #ifndef DIGIKAM_POINT_TRANSFORM_AFFINE_H #define DIGIKAM_POINT_TRANSFORM_AFFINE_H // C++ includes #include #include // Local includes #include "matrixoperations.h" #include "vectoroperations.h" using namespace std; namespace Digikam { class PointTransformAffine { public: PointTransformAffine() { m = std::vector >(2, std::vector(2, 0)); m[0][0] = 1.0; m[1][1] = 1.0; b = std::vector(2, 0); } PointTransformAffine(const std::vector >& m_, const std::vector& b_) : m(m_), b(b_) { } explicit PointTransformAffine(const std::vector >& m_) { m = std::vector >(2, std::vector(2, 0)); b = std::vector(2, 0); - for (unsigned int i = 0 ; i < m_.size() ; i++) + for (unsigned int i = 0 ; i < m_.size() ; ++i) { - for (unsigned int j = 0 ; j < m_[0].size() ; j++) + for (unsigned int j = 0 ; j < m_[0].size() ; ++j) { if (j == 2) { b[i] = m_[i][2]; } else { m[i][j] = m_[i][j]; } } } } const std::vector operator() (const std::vector& p) const { return m*p + b; } const std::vector >& get_m() const { return m; } const std::vector& get_b() const { return b; } private: std::vector > m; std::vector b; }; // ---------------------------------------------------------------------------------------- inline PointTransformAffine operator* (const PointTransformAffine& lhs, const PointTransformAffine& rhs) { return PointTransformAffine(lhs.get_m() * rhs.get_m(), lhs.get_m() * rhs.get_b() + lhs.get_b()); } // ---------------------------------------------------------------------------------------- inline PointTransformAffine inv (const PointTransformAffine& trans) { std::vector > im = inv2(trans.get_m()); return PointTransformAffine(im, -(im * trans.get_b())); } // ---------------------------------------------------------------------------------------- template PointTransformAffine find_affine_transform(const std::vector >& from_points, const std::vector >& to_points) { std::vector > P(3, std::vector(from_points.size())); std::vector > Q(2, std::vector(from_points.size())); for (unsigned long i = 0 ; i < from_points.size() ; ++i) { P[0][i] = from_points[i][0]; P[1][i] = from_points[i][1]; P[2][i] = 1; Q[0][i] = to_points[i][0]; Q[1][i] = to_points[i][1]; } const std::vector > m = Q * pinv(P); return PointTransformAffine(m); } // ---------------------------------------------------------------------------------------- PointTransformAffine find_similarity_transform(const std::vector >& from_points, const std::vector >& to_points) { // We use the formulas from the paper: Least-squares estimation of transformation // parameters between two point patterns by Umeyama. They are equations 34 through // 43. std::vector mean_from(2, 0), mean_to(2, 0); float sigma_from = 0; float sigma_to = 0; std::vector > cov(2, std::vector(2, 0)); for (unsigned long i = 0 ; i < from_points.size() ; ++i) { mean_from = mean_from + from_points[i]; mean_to = mean_to + to_points[i]; } mean_from = mean_from / from_points.size(); mean_to = mean_to / from_points.size(); for (unsigned long i = 0 ; i < from_points.size() ; ++i) { sigma_from = sigma_from + length_squared(from_points[i] - mean_from); sigma_to = sigma_to + length_squared(to_points[i] - mean_to); cov = cov + (to_points[i] - mean_to)*(from_points[i] - mean_from); } sigma_from = sigma_from / from_points.size(); sigma_to = sigma_to / from_points.size(); cov = cov / from_points.size(); (void)sigma_to; // to silent clang scan-build std::vector > u(2,std::vector(2)); std::vector > v(2,std::vector(2)); std::vector > vt(2,std::vector(2)); std::vector > d(2,std::vector(2)); std::vector > s(2,std::vector(2,0)); svd(cov, u,d,vt); s[0][0] = 1; s[1][1] = 1; if (determinant(cov) < 0 || (determinant(cov) == 0 && determinant(u) * determinant(v) < 0)) { if (d[1][1] < d[0][0]) s[1][1] = -1; else s[0][0] = -1; } transpose(vt,v); std::vector > r = u * s * v; float c = 1; if (sigma_from != 0) { c = 1.0 / sigma_from * trace(d * s); } std::vector t = mean_to - r * mean_from * c; return PointTransformAffine(r * c, t); } } // namespace Digikam #endif // DIGIKAM_POINT_TRANSFORM_AFFINE_H diff --git a/core/libs/facesengine/preprocessing/tantriggs/tantriggspreprocessor.cpp b/core/libs/facesengine/preprocessing/tantriggs/tantriggspreprocessor.cpp index 0750cbce2b..82fa0be344 100644 --- a/core/libs/facesengine/preprocessing/tantriggs/tantriggspreprocessor.cpp +++ b/core/libs/facesengine/preprocessing/tantriggs/tantriggspreprocessor.cpp @@ -1,146 +1,146 @@ /* ============================================================ * * This file is a part of digiKam * * Date : 2012-01-03 * Description : Calculates the TanTriggs Preprocessing as described in: * Tan, X., and Triggs, B. "Enhanced local texture feature sets for face * recognition under difficult lighting conditions.". IEEE Transactions * on Image Processing 19 (2010), 1635–650. * Default parameters are taken from the paper. * * Copyright (C) 2012-2013 by Marcel Wiesweg * Copyright (C) 2012 Philipp Wagner * * 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 "tantriggspreprocessor.h" namespace Digikam { TanTriggsPreprocessor::TanTriggsPreprocessor() : alpha(0.1f), tau(10.0f), gamma(0.2f), sigma0(1), sigma1(2) { } cv::Mat TanTriggsPreprocessor::preprocess(const cv::Mat& inputImage) { return normalize(preprocessRaw(inputImage)); } cv::Mat TanTriggsPreprocessor::preprocessRaw(const cv::Mat& inputImage) { cv::Mat X = inputImage; // ensure it's grayscale if (X.channels() > 1) { cvtColor(X, X, CV_RGB2GRAY); } // Convert to floating point: X.convertTo(X, CV_32FC1); // Start preprocessing: cv::Mat I; // Gamma correction cv::pow(X, gamma, I); // Calculate the DOG (Difference of Gaussian) Image: { cv::Mat gaussian0, gaussian1; // Kernel Size: int kernel_sz0 = (int)(3*sigma0); int kernel_sz1 = (int)(3*sigma1); // Make them odd for OpenCV: kernel_sz0 += ((kernel_sz0 % 2) == 0) ? 1 : 0; kernel_sz1 += ((kernel_sz1 % 2) == 0) ? 1 : 0; cv::GaussianBlur(I, gaussian0, cv::Size(kernel_sz0,kernel_sz0), sigma0, sigma0, cv::BORDER_CONSTANT); cv::GaussianBlur(I, gaussian1, cv::Size(kernel_sz1,kernel_sz1), sigma1, sigma1, cv::BORDER_CONSTANT); cv::subtract(gaussian0, gaussian1, I); } { double meanI = 0.0; { cv::Mat tmp; cv::pow(cv::abs(I), alpha, tmp); meanI = cv::mean(tmp).val[0]; } I = I / cv::pow(meanI, 1.0/alpha); } { double meanI = 0.0; { cv::Mat tmp; cv::pow(cv::min(cv::abs(I), tau), alpha, tmp); meanI = cv::mean(tmp).val[0]; } I = I / cv::pow(meanI, 1.0/alpha); } // Squash into the tanh: { - for(int r = 0; r < I.rows; r++) + for (int r = 0 ; r < I.rows ; ++r) { - for(int c = 0; c < I.cols; c++) + for (int c = 0 ; c < I.cols ; ++c) { - I.at(r,c) = tanh(I.at(r,c) / tau); + I.at(r, c) = tanh(I.at(r, c) / tau); } } I = tau * I; } return I; } /** Normalizes a given image into a value range between 0 and 255. */ cv::Mat TanTriggsPreprocessor::normalize(const cv::Mat& src) { // Create and return normalized image: cv::Mat dst; switch(src.channels()) { case 1: cv::normalize(src, dst, 0, 255, cv::NORM_MINMAX, CV_8UC1); break; case 3: cv::normalize(src, dst, 0, 255, cv::NORM_MINMAX, CV_8UC3); break; default: src.copyTo(dst); break; } return dst; } } // namespace Digikam diff --git a/core/libs/fileactionmanager/iteminfotasksplitter.cpp b/core/libs/fileactionmanager/iteminfotasksplitter.cpp index 1907e19d4c..201938dca6 100644 --- a/core/libs/fileactionmanager/iteminfotasksplitter.cpp +++ b/core/libs/fileactionmanager/iteminfotasksplitter.cpp @@ -1,71 +1,71 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2012-01-18 * Description : item info task splitter * * Copyright (C) 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. * * ============================================================ */ // Local includes #include "iteminfotasksplitter.h" #include "parallelworkers.h" namespace Digikam { ItemInfoTaskSplitter::ItemInfoTaskSplitter(const FileActionItemInfoList& list) : FileActionItemInfoList(list) { int parts = ParallelWorkers::optimalWorkerCount(); m_n = qMax(1, list.size() / parts); } ItemInfoTaskSplitter::~ItemInfoTaskSplitter() { } FileActionItemInfoList ItemInfoTaskSplitter::next() { QList list; if (size() <= m_n) { list = *this; clear(); } else { list.reserve(m_n); // qCopy does not work with QList - for (int i = 0; i < m_n ; i++) + for (int i = 0; i < m_n ; ++i) list << at(i); erase(begin(), begin() + m_n); } return FileActionItemInfoList::continueTask(list, progress()); } bool ItemInfoTaskSplitter::hasNext() const { return !isEmpty(); } } // namespace Digikam diff --git a/core/libs/kmemoryinfo/libstatgrab/kmemoryinfo_backend.cpp b/core/libs/kmemoryinfo/libstatgrab/kmemoryinfo_backend.cpp index 2dc52005b5..cb74ed4ffd 100644 --- a/core/libs/kmemoryinfo/libstatgrab/kmemoryinfo_backend.cpp +++ b/core/libs/kmemoryinfo/libstatgrab/kmemoryinfo_backend.cpp @@ -1,800 +1,800 @@ /* * Copyright 2010 Pino Toscano * Copyright 2011 Marcel Wiesweg * * Based on i-scream libstatgrab implementation. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License (LGPL) as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * * $Id: memory_stats.c,v 1.36 2010/02/21 10:04:26 tdb Exp $ */ //krazy:skip /** Value returned : -1 : unsupported platform * 0 : parse failure from supported platform * 1 : parse done with success from supported platform */ static int get_mem_stats(Digikam::KMemoryInfo::KMemoryInfoData* const data); static int get_swap_stats(Digikam::KMemoryInfo::KMemoryInfoData* const data); static int fillMemoryInfo(Digikam::KMemoryInfo::KMemoryInfoData* const data) { int ret = get_mem_stats(data); if (ret < 1) { data->valid = ret; return ret; } ret = get_swap_stats(data); if (ret < 1) { data->valid = ret; return ret; } data->valid = 1; return 1; } #ifdef Q_OS_SOLARIS #include #include #endif #if defined(Q_OS_LINUX)// || defined(Q_OS_CYGWIN) #include #include #endif #if defined(Q_OS_FREEBSD) #include #include #include #include #include #include #endif #if defined(Q_OS_NETBSD) #include #include #include #endif #if defined(Q_OS_OPENBSD) #include #include #include #include #endif #ifdef Q_OS_HPUX #include #include #include #endif #ifdef Q_OS_WIN #include #endif #if defined(Q_OS_LINUX) char* sg_f_read_line(FILE* f, const char* string) { /* Max line length. 8k should be more than enough */ static char line[8192]; while((fgets(line, sizeof(line), f))!=NULL) { if (strncmp(string, line, strlen(string))==0) { return line; } } //sg_set_error(SG_ERROR_PARSE, NULL); return NULL; } #endif // defined(Q_OS_LINUX) #if (defined(Q_OS_FREEBSD) && !defined(FREEBSD5)) || defined(DFBSD) kvm_t* sg_get_kvm() { static kvm_t* kvmd = NULL; if (kvmd != NULL) { return kvmd; } kvmd = kvm_openfiles(NULL, "/dev/null", NULL, O_RDONLY, NULL); if (kvmd == NULL) { //sg_set_error(SG_ERROR_KVM_OPENFILES, NULL); } return kvmd; } /* Can't think of a better name for this function */ kvm_t* sg_get_kvm2() { static kvm_t* kvmd2 = NULL; if (kvmd2 != NULL) { return kvmd2; } kvmd2 = kvm_openfiles(_PATH_DEVNULL, _PATH_DEVNULL, NULL, O_RDONLY, NULL); if (kvmd2 == NULL) { //sg_set_error(SG_ERROR_KVM_OPENFILES, NULL); } return kvmd2; } #endif // (defined(Q_OS_FREEBSD) && !defined(FREEBSD5)) || defined(DFBSD) #if defined(Q_OS_NETBSD) || defined(Q_OS_OPENBSD) struct uvmexp* sg_get_uvmexp() { int mib[2]; size_t size = sizeof(struct uvmexp); static struct uvmexp uvm; // struct uvmexp* new; mib[0] = CTL_VM; mib[1] = VM_UVMEXP; if (sysctl(mib, 2, &uvm, &size, NULL, 0) < 0) { //sg_set_error_with_errno(SG_ERROR_SYSCTL, "CTL_VM.VM_UVMEXP"); return NULL; } return &uvm; } #endif // defined(Q_OS_NETBSD) || defined(Q_OS_OPENBSD) #ifdef Q_OS_HPUX struct pst_KMemoryInfo::static* sg_get_pstat_static() { static int got = 0; static struct pst_static pst; if (!got) { if (pstat_getstatic(&pst, sizeof pst, 1, 0) == -1) { //sg_set_error_with_errno(SG_ERROR_PSTAT, "pstat_static"); return NULL; } got = 1; } return &pst; } #endif // Q_OS_HPUX // ---------------------------------------------------------------------------- int get_mem_stats(Digikam::KMemoryInfo::KMemoryInfoData* const data) { #ifdef Q_OS_HPUX struct pst_static* pstat_static = 0; struct pst_dynamic pstat_dynamic; long long pagesize; #endif // Q_OS_HPUX #ifdef Q_OS_SOLARIS kstat_ctl_t* kc = 0; kstat_t* ksp = 0; kstat_named_t* kn = 0; long totalmem; int pagesize; #endif // Q_OS_SOLARIS #if defined(Q_OS_LINUX) || defined(Q_OS_CYGWIN) char* line_ptr = 0; unsigned long long value; FILE* f = 0; #endif // defined(Q_OS_LINUX) || defined(Q_OS_CYGWIN) #if defined(Q_OS_FREEBSD) || defined(Q_OS_DFBSD) int mib[2]; u_long physmem; size_t size; u_int free_count; u_int cache_count; u_int inactive_count; int pagesize; #endif // defined(Q_OS_FREEBSD) || defined(Q_OS_DFBSD) #if defined(Q_OS_NETBSD) struct uvmexp* uvm = 0; #endif // defined(Q_OS_NETBSD) #if defined(Q_OS_OPENBSD) int mib[2]; struct vmtotal vmtotal; size_t size; int pagesize, page_multiplier; #endif // defined(Q_OS_OPENBSD) #ifdef Q_OS_WIN MEMORYSTATUSEX memstats; #endif #ifdef Q_OS_OSX Q_UNUSED(data); #endif #ifdef Q_OS_HPUX data->platform = QLatin1String("HPUX"); if((pagesize = sysconf(_SC_PAGESIZE)) == -1) { //sg_set_error_with_errno(SG_ERROR_SYSCONF, "_SC_PAGESIZE"); return 0; } if (pstat_getdynamic(&pstat_dynamic, sizeof(pstat_dynamic), 1, 0) == -1) { //sg_set_error_with_errno(SG_ERROR_PSTAT, "pstat_dynamic"); return 0; } pstat_static = sg_get_pstat_static(); if (pstat_static == NULL) { return 0; } /* FIXME Does this include swap? */ data->totalRam = ((long long) pstat_static->physical_memory) * pagesize; data->freeRam = ((long long) pstat_dynamic.psd_free) * pagesize; data->usedRam = data->totalRam - data->freeRam; return 1; #endif // Q_OS_HPUX #ifdef Q_OS_SOLARIS data->platform = QLatin1String("SOLARIS"); if((pagesize = sysconf(_SC_PAGESIZE)) == -1) { //sg_set_error_with_errno(SG_ERROR_SYSCONF, "_SC_PAGESIZE"); return 0; } if((totalmem = sysconf(_SC_PHYS_PAGES)) == -1) { //sg_set_error_with_errno(SG_ERROR_SYSCONF, "_SC_PHYS_PAGES"); return 0; } if ((kc = kstat_open()) == NULL) { //sg_set_error(SG_ERROR_KSTAT_OPEN, NULL); return 0; } if((ksp = kstat_lookup(kc, "unix", 0, "system_pages")) == NULL) { //sg_set_error(SG_ERROR_KSTAT_LOOKUP, "unix,0,system_pages"); return 0; } if (kstat_read(kc, ksp, 0) == -1) { //sg_set_error(SG_ERROR_KSTAT_READ, NULL); return 0; } if((kn = (kstat_named_t*)kstat_data_lookup(ksp, "freemem")) == NULL) { //sg_set_error(SG_ERROR_KSTAT_DATA_LOOKUP, "freemem"); return 0; } kstat_close(kc); data->totalRam = (long long)totalmem * (long long)pagesize; data->freeRam = ((long long)kn->value.ul) * (long long)pagesize; data->usedRam = data->totalRam - data->freeRam; return 1; #endif // Q_OS_SOLARIS #if defined(Q_OS_LINUX) || defined(Q_OS_CYGWIN) data->platform = QLatin1String("LINUX"); if ((f = fopen("/proc/meminfo", "r")) == NULL) { //sg_set_error_with_errno(SG_ERROR_OPEN, "/proc/meminfo"); return 0; } while ((line_ptr = sg_f_read_line(f, "")) != NULL) { if (sscanf(line_ptr, "%*s %llu kB", &value) != 1) { continue; } value *= 1024; if (strncmp(line_ptr, "MemTotal:", 9) == 0) { data->totalRam = value; } else if (strncmp(line_ptr, "MemFree:", 8) == 0) { data->freeRam = value; } else if (strncmp(line_ptr, "Cached:", 7) == 0) { data->cacheRam = value; } } fclose(f); data->usedRam = data->totalRam - data->freeRam; return 1; #endif // defined(Q_OS_LINUX) || defined(Q_OS_CYGWIN) #if defined(Q_OS_FREEBSD) data->platform = QLatin1String("FREEBSD"); /* Returns bytes */ mib[0] = CTL_HW; mib[1] = HW_PHYSMEM; size = sizeof physmem; if (sysctl(mib, 2, &physmem, &size, NULL, 0) < 0) { //sg_set_error_with_errno(SG_ERROR_SYSCTL, "CTL_HW.HW_PHYSMEM"); return 0; } data->totalRam = physmem; /*returns pages*/ size = sizeof free_count; if (sysctlbyname("vm.stats.vm.v_free_count", &free_count, &size, NULL, 0) < 0) { //sg_set_error_with_errno(SG_ERROR_SYSCTLBYNAME, "vm.stats.vm.v_free_count"); return 0; } size = sizeof inactive_count; if (sysctlbyname("vm.stats.vm.v_inactive_count", &inactive_count , &size, NULL, 0) < 0) { //sg_set_error_with_errno(SG_ERROR_SYSCTLBYNAME, "vm.stats.vm.v_inactive_count"); return 0; } size = sizeof cache_count; if (sysctlbyname("vm.stats.vm.v_cache_count", &cache_count, &size, NULL, 0) < 0) { //sg_set_error_with_errno(SG_ERROR_SYSCTLBYNAME, "vm.stats.vm.v_cache_count"); return 0; } /* Because all the vm.stats returns pages, I need to get the page size. * After that I then need to multiple the anything that used vm.stats to * get the system statistics by pagesize */ pagesize = getpagesize(); data->cacheRam = cache_count * pagesize; /* Of couse nothing is ever that simple :) And I have inactive pages to * deal with too. So I'm going to add them to free memory :) */ data->freeRam = (free_count*pagesize)+(inactive_count*pagesize); data->usedRam = physmem-data->freeRam; return 1; #endif // defined(Q_OS_FREEBSD) #if defined(Q_OS_NETBSD) data->platform = QLatin1String("NETBSD"); if ((uvm = sg_get_uvmexp()) == NULL) { return 0; } data->totalRam = uvm->pagesize * uvm->npages; data->cacheRam = uvm->pagesize * (uvm->filepages + uvm->execpages); data->freeRam = uvm->pagesize * (uvm->free + uvm->inactive); data->usedRam = data->totalRam - data->freeRam; return 1; #endif // defined(Q_OS_NETBSD) #if defined(Q_OS_OPENBSD) data->platform = QLatin1String("OPENBSD"); /* The code in this section is based on the code in the OpenBSD * top utility, located at src/usr.bin/top/machine.c in the * OpenBSD source tree. * * For fun, and like OpenBSD top, we will do the multiplication * converting the memory stats in pages to bytes in base 2. */ /* All memory stats in OpenBSD are returned as the number of pages. * To convert this into the number of bytes we need to know the * page size on this system. */ pagesize = sysconf(_SC_PAGESIZE); /* The pagesize gives us the base 10 multiplier, so we need to work * out what the base 2 multiplier is. This means dividing * pagesize by 2 until we reach unity, and counting the number of * divisions required. */ page_multiplier = 0; while (pagesize > 1) { page_multiplier++; pagesize >>= 1; } /* We can now ret the raw VM stats (in pages) using the * sysctl interface. */ mib[0] = CTL_VM; mib[1] = VM_METER; size = sizeof(vmtotal); if (sysctl(mib, 2, &vmtotal, &size, NULL, 0) < 0) { bzero(&vmtotal, sizeof(vmtotal)); //sg_set_error_with_errno(SG_ERROR_SYSCTL, "CTL_VM.VM_METER"); return 0; } /* Convert the raw stats to bytes, and return these to the caller */ data->usedRam = (vmtotal.t_rm << page_multiplier); /* total real mem in use */ data->cacheRam = 0; /* no cache stats */ data->freeRam = (vmtotal.t_free << page_multiplier); /* free memory pages */ data->totalRam = (data->usedRam + data->freeRam); return 1; #endif // defined(Q_OS_OPENBSD) #ifdef Q_OS_WIN data->platform = QLatin1String("WINDOWS"); memstats.dwLength = sizeof(memstats); if (!GlobalMemoryStatusEx(&memstats)) { //sg_set_error_with_errno(SG_ERROR_MEMSTATUS, NULL); return 0; } data->freeRam = memstats.ullAvailPhys; data->totalRam = memstats.ullTotalPhys; data->usedRam = data->totalRam - data->freeRam; //if(read_counter_large(SG_WIN32_MEM_CACHE, &data->cacheRam)) { data->cacheRam = 0; //} return 1; #endif // Q_OS_WIN return -1; } // ---------------------------------------------------------------------------- #ifdef Q_OS_SOLARIS #ifdef _FILE_OFFSET_BITS #undef _FILE_OFFSET_BITS #endif #include #include #include #endif #if defined(Q_OS_LINUX) //|| defined(Q_OS_CYGWIN) #include #include #endif #if defined(Q_OS_FREEBSD) #ifdef Q_OS_FREEBSD5 #include #include #include #else #include #include #endif #include #endif #if defined(Q_OS_NETBSD) || defined(Q_OS_OPENBSD) #include #include #include #include #endif #ifdef Q_OS_HPUX #include #include #include #define SWAP_BATCH 5 #endif #ifdef Q_OS_WIN #include #endif int get_swap_stats(Digikam::KMemoryInfo::KMemoryInfoData* const data) { #ifdef Q_OS_OSX Q_UNUSED(data); #endif #ifdef Q_OS_HPUX struct pst_swapinfo pstat_swapinfo[SWAP_BATCH]; int swapidx = 0; int num, i; #endif // Q_OS_HPUX #ifdef Q_OS_SOLARIS struct anoninfo ai; int pagesize; #endif // Q_OS_SOLARIS #if defined(Q_OS_LINUX) //|| defined(Q_OS_CYGWIN) FILE* f = 0; char* line_ptr = 0; unsigned long long value; #endif // defined(Q_OS_LINUX) #if defined(Q_OS_FREEBSD) int pagesize; #ifdef Q_OS_FREEBSD5 struct xswdev xsw; int mib[16], n; size_t mibsize, size; #else struct kvm_swap swapinfo; kvm_t* kvmd = 0; #endif // Q_OS_FREEBSD5 #endif // defined(Q_OS_FREEBSD) #if defined(Q_OS_NETBSD) || defined(Q_OS_OPENBSD) struct uvmexp* uvm = 0; #endif // defined(Q_OS_NETBSD) || defined(Q_OS_OPENBSD) #ifdef Q_OS_WIN MEMORYSTATUSEX memstats; #endif // Q_OS_WIN #ifdef Q_OS_HPUX data->totalSwap = 0; data->usedSwap = 0; data->freeSwap = 0; while (1) { num = pstat_getswap(pstat_swapinfo, sizeof pstat_swapinfo[0], SWAP_BATCH, swapidx); if (num == -1) { //sg_set_error_with_errno(SG_ERROR_PSTAT,"pstat_getswap"); return 0; } else if (num == 0) { break; } - for (i = 0; i < num; i++) + for (i = 0; i < num; ++i) { struct pst_swapinfo* si = &pstat_swapinfo[i]; if ((si->pss_flags & SW_ENABLED) != SW_ENABLED) { continue; } if ((si->pss_flags & SW_BLOCK) == SW_BLOCK) { data->totalSwap += ((long long) si->pss_nblksavail) * 1024LL; data->usedSwap += ((long long) si->pss_nfpgs) * 1024LL; data->freeSwap = data->totalSwap - data->usedSwap; } if ((si->pss_flags & SW_FS) == SW_FS) { data->totalSwap += ((long long) si->pss_limit) * 1024LL; data->usedSwap += ((long long) si->pss_allocated) * 1024LL; data->freeSwap = data->totalSwap - data->usedSwap; } } swapidx = pstat_swapinfo[num - 1].pss_idx + 1; } return 1; #endif // Q_OS_HPUX #ifdef Q_OS_SOLARIS if((pagesize=sysconf(_SC_PAGESIZE)) == -1) { //sg_set_error_with_errno(SG_ERROR_SYSCONF, "_SC_PAGESIZE"); return 0; } if (swapctl(SC_AINFO, &ai) == -1) { //sg_set_error_with_errno(SG_ERROR_SWAPCTL, NULL); return 0; } data->totalSwap = (long long)ai.ani_max * (long long)pagesize; data->usedSwap = (long long)ai.ani_resv * (long long)pagesize; data->freeSwap = data->totalSwap - data->usedSwap; return 1; #endif // Q_OS_SOLARIS #if defined(Q_OS_LINUX) || defined(Q_OS_CYGWIN) if ((f = fopen("/proc/meminfo", "r")) == NULL) { //sg_set_error_with_errno(SG_ERROR_OPEN, "/proc/meminfo"); return 0; } while ((line_ptr = sg_f_read_line(f, "")) != NULL) { if (sscanf(line_ptr, "%*s %llu kB", &value) != 1) { continue; } value *= 1024; if (strncmp(line_ptr, "SwapTotal:", 10) == 0) { data->totalSwap = value; } else if (strncmp(line_ptr, "SwapFree:", 9) == 0) { data->freeSwap = value; } } fclose(f); data->usedSwap = data->totalSwap - data->freeSwap; return 1; #endif // defined(Q_OS_LINUX) || defined(Q_OS_CYGWIN) #if defined(Q_OS_FREEBSD) || defined(Q_OS_DFBSD) pagesize = getpagesize(); #ifdef Q_OS_FREEBSD5 data->totalSwap = 0; data->usedSwap = 0; mibsize = sizeof mib / sizeof mib[0]; if (sysctlnametomib("vm.swap_info", mib, &mibsize) < 0) { //sg_set_error_with_errno(SG_ERROR_SYSCTLNAMETOMIB, "vm.swap_info"); return 0; } for (n = 0; ; ++n) { mib[mibsize] = n; size = sizeof xsw; if (sysctl(mib, mibsize + 1, &xsw, &size, NULL, 0) < 0) { break; } if (xsw.xsw_version != XSWDEV_VERSION) { //sg_set_error(SG_ERROR_XSW_VER_MISMATCH, NULL); return 0; } data->totalSwap += (long long) xsw.xsw_nblks; data->usedSwap += (long long) xsw.xsw_used; } if (errno != ENOENT) { //sg_set_error_with_errno(SG_ERROR_SYSCTL, "vm.swap_info"); return 0; } #else // Q_OS_FREEBSD5 if((kvmd = sg_get_kvm()) == NULL) { return 0; } if ((kvm_getswapinfo(kvmd, &swapinfo, 1,0)) == -1) { //sg_set_error(SG_ERROR_KVM_GETSWAPINFO, NULL); return 0; } data->totalSwap = (long long)swapinfo.ksw_total; data->usedSwap = (long long)swapinfo.ksw_used; #endif // Q_OS_FREEBSD5 data->totalSwap *= pagesize; data->usedSwap *= pagesize; data->freeSwap = data->totalSwap - data->usedSwap; return 1; #endif // defined(Q_OS_FREEBSD) || defined(Q_OS_DFBSD) #if defined(Q_OS_NETBSD) || defined(Q_OS_OPENBSD) if ((uvm = sg_get_uvmexp()) == NULL) { return 0; } data->totalSwap = (long long)uvm->pagesize * (long long)uvm->swpages; data->usedSwap = (long long)uvm->pagesize * (long long)uvm->swpginuse; data->freeSwap = data->totalSwap - data->usedSwap; return 1; #endif // defined(Q_OS_NETBSD) || defined(Q_OS_OPENBSD) #ifdef Q_OS_WIN memstats.dwLength = sizeof(memstats); if (!GlobalMemoryStatusEx(&memstats)) { //sg_set_error_with_errno(SG_ERROR_MEMSTATUS, "GloblaMemoryStatusEx"); return 0; } /* the PageFile stats include Phys memory "minus an overhead". * Due to this unknown "overhead" there's no way to extract just page * file use from these numbers */ data->totalSwap = memstats.ullTotalPageFile; data->freeSwap = memstats.ullAvailPageFile; data->usedSwap = data->totalSwap - data->freeSwap; return 1; #endif return -1; } diff --git a/core/libs/metadataengine/dmetadata/dmetadata_labels.cpp b/core/libs/metadataengine/dmetadata/dmetadata_labels.cpp index bfbc01518b..13628cadb3 100644 --- a/core/libs/metadataengine/dmetadata/dmetadata_labels.cpp +++ b/core/libs/metadataengine/dmetadata/dmetadata_labels.cpp @@ -1,369 +1,369 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2006-02-23 * Description : item metadata interface - labels helpers. * * Copyright (C) 2006-2018 by Gilles Caulier * Copyright (C) 2006-2013 by Marcel Wiesweg * Copyright (C) 2011 by Leif Huhn * * 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 "dmetadata.h" // Qt includes #include // Local includes #include "metaenginesettings.h" #include "digikam_version.h" #include "digikam_globals.h" #include "digikam_debug.h" namespace Digikam { int DMetadata::getItemPickLabel() const { if (getFilePath().isEmpty()) { return -1; } if (hasXmp()) { QString value = getXmpTagString("Xmp.digiKam.PickLabel", false); if (!value.isEmpty()) { bool ok = false; long pickId = value.toLong(&ok); if (ok && pickId >= NoPickLabel && pickId <= AcceptedLabel) { return pickId; } } } return -1; } int DMetadata::getItemColorLabel() const { if (getFilePath().isEmpty()) { return -1; } if (hasXmp()) { QString value = getXmpTagString("Xmp.digiKam.ColorLabel", false); if (value.isEmpty()) { // Nikon NX use this XMP tags to store Color Labels value = getXmpTagString("Xmp.photoshop.Urgency", false); } if (!value.isEmpty()) { bool ok = false; long colorId = value.toLong(&ok); if (ok && colorId >= NoColorLabel && colorId <= WhiteLabel) { return colorId; } } // LightRoom use this tag to store color name as string. // Values are limited : see bug #358193. value = getXmpTagString("Xmp.xmp.Label", false); if (value == QLatin1String("Blue")) { return BlueLabel; } else if (value == QLatin1String("Green")) { return GreenLabel; } else if (value == QLatin1String("Red")) { return RedLabel; } else if (value == QLatin1String("Yellow")) { return YellowLabel; } else if (value == QLatin1String("Purple")) { return MagentaLabel; } } return -1; } int DMetadata::getItemRating(const DMetadataSettingsContainer& settings) const { if (getFilePath().isEmpty()) { return -1; } long rating = -1; bool xmpSupported = hasXmp(); bool iptcSupported = hasIptc(); bool exivSupported = hasExif(); for (NamespaceEntry entry : settings.getReadMapping(QString::fromUtf8(DM_RATING_CONTAINER))) { if (entry.isDisabled) continue; const std::string myStr = entry.namespaceName.toStdString(); const char* nameSpace = myStr.data(); QString value; switch(entry.subspace) { case NamespaceEntry::XMP: if (xmpSupported) value = getXmpTagString(nameSpace, false); break; case NamespaceEntry::IPTC: if (iptcSupported) value = QString::fromUtf8(getIptcTagData(nameSpace)); break; case NamespaceEntry::EXIF: if (exivSupported) getExifTagLong(nameSpace, rating); break; default: break; } if (!value.isEmpty()) { bool ok = false; rating = value.toLong(&ok); if (!ok) { return -1; } } int index = entry.convertRatio.indexOf(rating); // Exact value was not found,but rating is in range, // so we try to approximate it if ((index == -1) && (rating > entry.convertRatio.first()) && (rating < entry.convertRatio.last())) { - for (int i = 0 ; i < entry.convertRatio.size() ; i++) + for (int i = 0 ; i < entry.convertRatio.size() ; ++i) { if (rating > entry.convertRatio.at(i)) { index = i; } } } if (index != -1) { return index; } } return -1; } bool DMetadata::setItemPickLabel(int pickId) const { if (pickId < NoPickLabel || pickId > AcceptedLabel) { qCDebug(DIGIKAM_METAENGINE_LOG) << "Pick Label value to write is out of range!"; return false; } //qCDebug(DIGIKAM_METAENGINE_LOG) << getFilePath() << " ==> Pick Label: " << pickId; if (supportXmp()) { if (!setXmpTagString("Xmp.digiKam.PickLabel", QString::number(pickId))) { return false; } } return true; } bool DMetadata::setItemColorLabel(int colorId) const { if (colorId < NoColorLabel || colorId > WhiteLabel) { qCDebug(DIGIKAM_METAENGINE_LOG) << "Color Label value to write is out of range!"; return false; } //qCDebug(DIGIKAM_METAENGINE_LOG) << getFilePath() << " ==> Color Label: " << colorId; if (supportXmp()) { if (!setXmpTagString("Xmp.digiKam.ColorLabel", QString::number(colorId))) { return false; } // Nikon NX use this XMP tags to store Color Labels if (!setXmpTagString("Xmp.photoshop.Urgency", QString::number(colorId))) { return false; } // LightRoom use this XMP tags to store Color Labels name // Values are limited : see bug #358193. QString LRLabel; switch(colorId) { case BlueLabel: LRLabel = QLatin1String("Blue"); break; case GreenLabel: LRLabel = QLatin1String("Green"); break; case RedLabel: LRLabel = QLatin1String("Red"); break; case YellowLabel: LRLabel = QLatin1String("Yellow"); break; case MagentaLabel: LRLabel = QLatin1String("Purple"); break; } if (!LRLabel.isEmpty()) { if (!setXmpTagString("Xmp.xmp.Label", LRLabel)) { return false; } } } return true; } bool DMetadata::setItemRating(int rating, const DMetadataSettingsContainer& settings) const { // NOTE : with digiKam 0.9.x, we have used IPTC Urgency to store Rating. // Now this way is obsolete, and we use standard XMP rating tag instead. if (rating < RatingMin || rating > RatingMax) { qCDebug(DIGIKAM_METAENGINE_LOG) << "Rating value to write is out of range!"; return false; } //qCDebug(DIGIKAM_METAENGINE_LOG) << getFilePath() << " ==> Rating:" << rating; QList toWrite = settings.getReadMapping(QString::fromUtf8(DM_RATING_CONTAINER)); if (!settings.unifyReadWrite()) toWrite = settings.getWriteMapping(QString::fromUtf8(DM_RATING_CONTAINER)); for (NamespaceEntry entry : toWrite) { if (entry.isDisabled) continue; const std::string myStr = entry.namespaceName.toStdString(); const char* nameSpace = myStr.data(); switch(entry.subspace) { case NamespaceEntry::XMP: if (!setXmpTagString(nameSpace, QString::number(entry.convertRatio.at(rating)))) { qCDebug(DIGIKAM_METAENGINE_LOG) << "Setting rating failed" << nameSpace; return false; } break; case NamespaceEntry::EXIF: if (!setExifTagLong(nameSpace, rating)) { return false; } break; case NamespaceEntry::IPTC: // IPTC rating deprecated default: break; } } // Set Exif rating tag used by Windows Vista. if (!setExifTagLong("Exif.Image.0x4746", rating)) { return false; } // Wrapper around rating percents managed by Windows Vista. int ratePercents = 0; switch (rating) { case 0: ratePercents = 0; break; case 1: ratePercents = 1; break; case 2: ratePercents = 25; break; case 3: ratePercents = 50; break; case 4: ratePercents = 75; break; case 5: ratePercents = 99; break; } if (!setExifTagLong("Exif.Image.0x4749", ratePercents)) { return false; } return true; } } // namespace Digikam diff --git a/core/libs/properties/itempropertiestab.cpp b/core/libs/properties/itempropertiestab.cpp index 85ae295ee0..167b4b6416 100644 --- a/core/libs/properties/itempropertiestab.cpp +++ b/core/libs/properties/itempropertiestab.cpp @@ -1,952 +1,952 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2006-04-19 * Description : A tab to display general item information * * Copyright (C) 2006-2018 by Gilles Caulier * 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 "itempropertiestab.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include // Local includes #include "itempropertiestxtlabel.h" #include "picklabelwidget.h" #include "colorlabelwidget.h" #include "tagscache.h" namespace Digikam { class Q_DECL_HIDDEN ItemPropertiesTab::Private { public: enum Section { FileProperties = 0, ImageProperties, PhotoProperties, VideoProperties, digiKamProperties }; public: explicit Private() : caption(0), pickLabel(0), colorLabel(0), rating(0), tags(0), labelFile(0), labelFolder(0), labelFileModifiedDate(0), labelFileSize(0), labelFileOwner(0), labelFilePermissions(0), labelImageMime(0), labelImageDimensions(0), labelImageRatio(0), labelImageBitDepth(0), labelImageColorMode(0), labelPhotoMake(0), labelPhotoModel(0), labelPhotoDateTime(0), labelPhotoLens(0), labelPhotoAperture(0), labelPhotoFocalLength(0), labelPhotoExposureTime(0), labelPhotoSensitivity(0), labelPhotoExposureMode(0), labelPhotoFlash(0), labelPhotoWhiteBalance(0), labelCaption(0), labelTags(0), labelPickLabel(0), labelColorLabel(0), labelRating(0), labelVideoAspectRatio(0), labelVideoDuration(0), labelVideoFrameRate(0), labelVideoVideoCodec(0), labelVideoAudioBitRate(0), labelVideoAudioChannelType(0), labelVideoAudioCodec(0) { } DTextLabelName* caption; DTextLabelName* pickLabel; DTextLabelName* colorLabel; DTextLabelName* rating; DTextLabelName* tags; DTextLabelValue* labelFile; DTextLabelValue* labelFolder; DTextLabelValue* labelFileModifiedDate; DTextLabelValue* labelFileSize; DTextLabelValue* labelFileOwner; DTextLabelValue* labelFilePermissions; DTextLabelValue* labelImageMime; DTextLabelValue* labelImageDimensions; DTextLabelValue* labelImageRatio; DTextLabelValue* labelImageBitDepth; DTextLabelValue* labelImageColorMode; DTextLabelValue* labelPhotoMake; DTextLabelValue* labelPhotoModel; DTextLabelValue* labelPhotoDateTime; DTextLabelValue* labelPhotoLens; DTextLabelValue* labelPhotoAperture; DTextLabelValue* labelPhotoFocalLength; DTextLabelValue* labelPhotoExposureTime; DTextLabelValue* labelPhotoSensitivity; DTextLabelValue* labelPhotoExposureMode; DTextLabelValue* labelPhotoFlash; DTextLabelValue* labelPhotoWhiteBalance; DTextLabelValue* labelCaption; DTextLabelValue* labelTags; DTextLabelValue* labelPickLabel; DTextLabelValue* labelColorLabel; DTextLabelValue* labelRating; DTextLabelValue* labelVideoAspectRatio; DTextLabelValue* labelVideoDuration; DTextLabelValue* labelVideoFrameRate; DTextLabelValue* labelVideoVideoCodec; DTextLabelValue* labelVideoAudioBitRate; DTextLabelValue* labelVideoAudioChannelType; DTextLabelValue* labelVideoAudioCodec; }; ItemPropertiesTab::ItemPropertiesTab(QWidget* const parent) : DExpanderBox(parent), d(new Private) { setFrameStyle(QFrame::StyledPanel | QFrame::Sunken); setLineWidth(style()->pixelMetric(QStyle::PM_DefaultFrameWidth)); // -------------------------------------------------- const int spacing = QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); QWidget* const w1 = new QWidget(this); QGridLayout* const glay1 = new QGridLayout(w1); DTextLabelName* const file = new DTextLabelName(i18n("File: "), w1); DTextLabelName* const folder = new DTextLabelName(i18n("Folder: "), w1); DTextLabelName* const modifiedDate = new DTextLabelName(i18n("Date: "), w1); DTextLabelName* const size = new DTextLabelName(i18n("Size: "), w1); DTextLabelName* const owner = new DTextLabelName(i18n("Owner: "), w1); DTextLabelName* const permissions = new DTextLabelName(i18n("Permissions: "), w1); d->labelFile = new DTextLabelValue(QString(), w1); d->labelFolder = new DTextLabelValue(QString(), w1); d->labelFileModifiedDate = new DTextLabelValue(QString(), w1); d->labelFileSize = new DTextLabelValue(QString(), w1); d->labelFileOwner = new DTextLabelValue(QString(), w1); d->labelFilePermissions = new DTextLabelValue(QString(), w1); glay1->addWidget(file, 0, 0, 1, 1); glay1->addWidget(d->labelFile, 0, 1, 1, 1); glay1->addWidget(folder, 1, 0, 1, 1); glay1->addWidget(d->labelFolder, 1, 1, 1, 1); glay1->addWidget(modifiedDate, 2, 0, 1, 1); glay1->addWidget(d->labelFileModifiedDate, 2, 1, 1, 1); glay1->addWidget(size, 3, 0, 1, 1); glay1->addWidget(d->labelFileSize, 3, 1, 1, 1); glay1->addWidget(owner, 4, 0, 1, 1); glay1->addWidget(d->labelFileOwner, 4, 1, 1, 1); glay1->addWidget(permissions, 5, 0, 1, 1); glay1->addWidget(d->labelFilePermissions, 5, 1, 1, 1); glay1->setContentsMargins(spacing, spacing, spacing, spacing); glay1->setColumnStretch(0, 10); glay1->setColumnStretch(1, 10); glay1->setSpacing(0); insertItem(ItemPropertiesTab::Private::FileProperties, w1, QIcon::fromTheme(QLatin1String("dialog-information")), i18n("File Properties"), QLatin1String("FileProperties"), true); // -------------------------------------------------- QWidget* const w2 = new QWidget(this); QGridLayout* const glay2 = new QGridLayout(w2); DTextLabelName* const mime = new DTextLabelName(i18n("Type: "), w2); DTextLabelName* const dimensions = new DTextLabelName(i18n("Dimensions: "), w2); DTextLabelName* const ratio = new DTextLabelName(i18n("Aspect Ratio: "), w2); DTextLabelName* const bitDepth = new DTextLabelName(i18n("Bit depth: "), w2); DTextLabelName* const colorMode = new DTextLabelName(i18n("Color mode: "), w2); d->labelImageMime = new DTextLabelValue(QString(), w2); d->labelImageDimensions = new DTextLabelValue(QString(), w2); d->labelImageRatio = new DTextLabelValue(QString(), w2); d->labelImageBitDepth = new DTextLabelValue(QString(), w2); d->labelImageColorMode = new DTextLabelValue(QString(), w2); glay2->addWidget(mime, 0, 0, 1, 1); glay2->addWidget(d->labelImageMime, 0, 1, 1, 1); glay2->addWidget(dimensions, 1, 0, 1, 1); glay2->addWidget(d->labelImageDimensions, 1, 1, 1, 1); glay2->addWidget(ratio, 2, 0, 1, 1); glay2->addWidget(d->labelImageRatio, 2, 1, 1, 1); glay2->addWidget(bitDepth, 3, 0, 1, 1); glay2->addWidget(d->labelImageBitDepth, 3, 1, 1, 1); glay2->addWidget(colorMode, 4, 0, 1, 1); glay2->addWidget(d->labelImageColorMode, 4, 1, 1, 1); glay2->setContentsMargins(spacing, spacing, spacing, spacing); glay2->setColumnStretch(0, 10); glay2->setColumnStretch(1, 10); glay2->setSpacing(0); insertItem(ItemPropertiesTab::Private::ImageProperties, w2, QIcon::fromTheme(QLatin1String("view-preview")), i18n("Item Properties"), QLatin1String("ItemProperties"), true); // -------------------------------------------------- QWidget* const w3 = new QWidget(this); QGridLayout* const glay3 = new QGridLayout(w3); DTextLabelName* const make = new DTextLabelName(i18n("Make: "), w3); DTextLabelName* const model = new DTextLabelName(i18n("Model: "), w3); DTextLabelName* const photoDate = new DTextLabelName(i18n("Created: "), w3); DTextLabelName* const lens = new DTextLabelName(i18n("Lens: "), w3); DTextLabelName* const aperture = new DTextLabelName(i18n("Aperture: "), w3); DTextLabelName* const focalLength = new DTextLabelName(i18n("Focal: "), w3); DTextLabelName* const exposureTime = new DTextLabelName(i18n("Exposure: "), w3); DTextLabelName* const sensitivity = new DTextLabelName(i18n("Sensitivity: "), w3); DTextLabelName* const exposureMode = new DTextLabelName(i18n("Mode/Program: "), w3); DTextLabelName* const flash = new DTextLabelName(i18n("Flash: "), w3); DTextLabelName* const whiteBalance = new DTextLabelName(i18n("White balance: "), w3); d->labelPhotoMake = new DTextLabelValue(QString(), w3); d->labelPhotoModel = new DTextLabelValue(QString(), w3); d->labelPhotoDateTime = new DTextLabelValue(QString(), w3); d->labelPhotoLens = new DTextLabelValue(QString(), w3); d->labelPhotoAperture = new DTextLabelValue(QString(), w3); d->labelPhotoFocalLength = new DTextLabelValue(QString(), w3); d->labelPhotoExposureTime = new DTextLabelValue(QString(), w3); d->labelPhotoSensitivity = new DTextLabelValue(QString(), w3); d->labelPhotoExposureMode = new DTextLabelValue(QString(), w3); d->labelPhotoFlash = new DTextLabelValue(QString(), w3); d->labelPhotoWhiteBalance = new DTextLabelValue(QString(), w3); glay3->addWidget(make, 0, 0, 1, 1); glay3->addWidget(d->labelPhotoMake, 0, 1, 1, 1); glay3->addWidget(model, 1, 0, 1, 1); glay3->addWidget(d->labelPhotoModel, 1, 1, 1, 1); glay3->addWidget(photoDate, 2, 0, 1, 1); glay3->addWidget(d->labelPhotoDateTime, 2, 1, 1, 1); glay3->addWidget(lens, 3, 0, 1, 1); glay3->addWidget(d->labelPhotoLens, 3, 1, 1, 1); glay3->addWidget(aperture, 4, 0, 1, 1); glay3->addWidget(d->labelPhotoAperture, 4, 1, 1, 1); glay3->addWidget(focalLength, 5, 0, 1, 1); glay3->addWidget(d->labelPhotoFocalLength, 5, 1, 1, 1); glay3->addWidget(exposureTime, 6, 0, 1, 1); glay3->addWidget(d->labelPhotoExposureTime, 6, 1, 1, 1); glay3->addWidget(sensitivity, 7, 0, 1, 1); glay3->addWidget(d->labelPhotoSensitivity, 7, 1, 1, 1); glay3->addWidget(exposureMode, 8, 0, 1, 1); glay3->addWidget(d->labelPhotoExposureMode, 8, 1, 1, 1); glay3->addWidget(flash, 9, 0, 1, 1); glay3->addWidget(d->labelPhotoFlash, 9, 1, 1, 1); glay3->addWidget(whiteBalance, 10, 0, 1, 1); glay3->addWidget(d->labelPhotoWhiteBalance, 10, 1, 1, 1); glay3->setContentsMargins(spacing, spacing, spacing, spacing); glay3->setColumnStretch(0, 10); glay3->setColumnStretch(1, 10); glay3->setSpacing(0); insertItem(ItemPropertiesTab::Private::PhotoProperties, w3, QIcon::fromTheme(QLatin1String("camera-photo")), i18n("Photograph Properties"), QLatin1String("PhotographProperties"), true); // -------------------------------------------------- QWidget* const w4 = new QWidget(this); QGridLayout* const glay4 = new QGridLayout(w4); DTextLabelName* const aspectRatio = new DTextLabelName(i18n("Aspect Ratio: "), w4); DTextLabelName* const duration = new DTextLabelName(i18n("Duration: "), w4); DTextLabelName* const frameRate = new DTextLabelName(i18n("Frame Rate: "), w4); DTextLabelName* const videoCodec = new DTextLabelName(i18n("Video Codec: "), w4); DTextLabelName* const audioBitRate = new DTextLabelName(i18n("Audio Bit Rate: "), w4); DTextLabelName* const audioChannelType = new DTextLabelName(i18n("Audio Channel Type: "), w4); DTextLabelName* const audioCodec = new DTextLabelName(i18n("Audio Codec: "), w4); d->labelVideoAspectRatio = new DTextLabelValue(QString(), w4); d->labelVideoDuration = new DTextLabelValue(QString(), w4); d->labelVideoFrameRate = new DTextLabelValue(QString(), w4); d->labelVideoVideoCodec = new DTextLabelValue(QString(), w4); d->labelVideoAudioBitRate = new DTextLabelValue(QString(), w4); d->labelVideoAudioChannelType = new DTextLabelValue(QString(), w4); d->labelVideoAudioCodec = new DTextLabelValue(QString(), w4); glay4->addWidget(aspectRatio, 0, 0, 1, 1); glay4->addWidget(d->labelVideoAspectRatio, 0, 1, 1, 1); glay4->addWidget(duration, 1, 0, 1, 1); glay4->addWidget(d->labelVideoDuration, 1, 1, 1, 1); glay4->addWidget(frameRate, 2, 0, 1, 1); glay4->addWidget(d->labelVideoFrameRate, 2, 1, 1, 1); glay4->addWidget(videoCodec, 3, 0, 1, 1); glay4->addWidget(d->labelVideoVideoCodec, 3, 1, 1, 1); glay4->addWidget(audioBitRate, 4, 0, 1, 1); glay4->addWidget(d->labelVideoAudioBitRate, 4, 1, 1, 1); glay4->addWidget(audioChannelType, 5, 0, 1, 1); glay4->addWidget(d->labelVideoAudioChannelType, 5, 1, 1, 1); glay4->addWidget(audioCodec, 6, 0, 1, 1); glay4->addWidget(d->labelVideoAudioCodec, 6, 1, 1, 1); glay4->setContentsMargins(spacing, spacing, spacing, spacing); glay4->setColumnStretch(0, 10); glay4->setColumnStretch(1, 10); glay4->setSpacing(0); insertItem(ItemPropertiesTab::Private::VideoProperties, w4, QIcon::fromTheme(QLatin1String("video-x-generic")), i18n("Audio/Video Properties"), QLatin1String("VideoProperties"), true); // -------------------------------------------------- QWidget* const w5 = new QWidget(this); QGridLayout* const glay5 = new QGridLayout(w5); d->caption = new DTextLabelName(i18n("Caption: "), w5); d->pickLabel = new DTextLabelName(i18n("Pick label: "), w5); d->colorLabel = new DTextLabelName(i18n("Color label: "), w5); d->rating = new DTextLabelName(i18n("Rating: "), w5); d->tags = new DTextLabelName(i18n("Tags: "), w5); d->labelCaption = new DTextLabelValue(QString(), w5); d->labelPickLabel = new DTextLabelValue(QString(), w5); d->labelColorLabel = new DTextLabelValue(QString(), w5); d->labelRating = new DTextLabelValue(QString(), w5); d->labelTags = new DTextLabelValue(QString(), w5); d->labelTags->setElideMode(Qt::ElideLeft); glay5->addWidget(d->caption, 0, 0, 1, 1); glay5->addWidget(d->labelCaption, 0, 1, 1, 1); glay5->addWidget(d->tags, 1, 0, 1, 1); glay5->addWidget(d->labelTags, 1, 1, 1, 1); glay5->addWidget(d->pickLabel, 2, 0, 1, 1); glay5->addWidget(d->labelPickLabel, 2, 1, 1, 1); glay5->addWidget(d->colorLabel, 3, 0, 1, 1); glay5->addWidget(d->labelColorLabel, 3, 1, 1, 1); glay5->addWidget(d->rating, 4, 0, 1, 1); glay5->addWidget(d->labelRating, 4, 1, 1, 1); glay5->setContentsMargins(spacing, spacing, spacing, spacing); glay5->setColumnStretch(0, 10); glay5->setColumnStretch(1, 10); glay5->setSpacing(0); insertItem(ItemPropertiesTab::Private::digiKamProperties, w5, QIcon::fromTheme(QLatin1String("edit-text-frame-update")), i18n("digiKam Properties"), QLatin1String("DigikamProperties"), true); // -------------------------------------------------- addStretch(); } ItemPropertiesTab::~ItemPropertiesTab() { delete d; } void ItemPropertiesTab::setCurrentURL(const QUrl& url) { if (url.isEmpty()) { d->labelFile->setAdjustedText(); d->labelFolder->setAdjustedText(); d->labelFileModifiedDate->setAdjustedText(); d->labelFileSize->setAdjustedText(); d->labelFileOwner->setAdjustedText(); d->labelFilePermissions->setAdjustedText(); d->labelImageMime->setAdjustedText(); d->labelImageDimensions->setAdjustedText(); d->labelImageRatio->setAdjustedText(); d->labelImageBitDepth->setAdjustedText(); d->labelImageColorMode->setAdjustedText(); d->labelPhotoMake->setAdjustedText(); d->labelPhotoModel->setAdjustedText(); d->labelPhotoDateTime->setAdjustedText(); d->labelPhotoLens->setAdjustedText(); d->labelPhotoAperture->setAdjustedText(); d->labelPhotoFocalLength->setAdjustedText(); d->labelPhotoExposureTime->setAdjustedText(); d->labelPhotoSensitivity->setAdjustedText(); d->labelPhotoExposureMode->setAdjustedText(); d->labelPhotoFlash->setAdjustedText(); d->labelPhotoWhiteBalance->setAdjustedText(); d->labelCaption->setAdjustedText(); d->labelPickLabel->setAdjustedText(); d->labelColorLabel->setAdjustedText(); d->labelRating->setAdjustedText(); d->labelTags->setAdjustedText(); d->labelVideoAspectRatio->setAdjustedText(); d->labelVideoDuration->setAdjustedText(); d->labelVideoFrameRate->setAdjustedText(); d->labelVideoVideoCodec->setAdjustedText(); d->labelVideoAudioBitRate->setAdjustedText(); d->labelVideoAudioChannelType->setAdjustedText(); d->labelVideoAudioCodec->setAdjustedText(); setEnabled(false); return; } setEnabled(true); d->labelFile->setAdjustedText(url.fileName()); d->labelFolder->setAdjustedText(QDir::toNativeSeparators(url.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).toLocalFile())); } void ItemPropertiesTab::setPhotoInfoDisable(const bool b) { if (b) { widget(ItemPropertiesTab::Private::PhotoProperties)->hide(); } else { widget(ItemPropertiesTab::Private::PhotoProperties)->show(); } } void ItemPropertiesTab::setVideoInfoDisable(const bool b) { if (b) { widget(ItemPropertiesTab::Private::VideoProperties)->hide(); } else { widget(ItemPropertiesTab::Private::VideoProperties)->show(); } } void ItemPropertiesTab::setFileModifiedDate(const QString& str) { d->labelFileModifiedDate->setAdjustedText(str); } void ItemPropertiesTab::setFileSize(const QString& str) { d->labelFileSize->setAdjustedText(str); } void ItemPropertiesTab::setFileOwner(const QString& str) { d->labelFileOwner->setAdjustedText(str); } void ItemPropertiesTab::setFilePermissions(const QString& str) { d->labelFilePermissions->setAdjustedText(str); } void ItemPropertiesTab::setImageMime(const QString& str) { d->labelImageMime->setAdjustedText(str); } void ItemPropertiesTab::setItemDimensions(const QString& str) { d->labelImageDimensions->setAdjustedText(str); } void ItemPropertiesTab::setImageRatio(const QString& str) { d->labelImageRatio->setAdjustedText(str); } void ItemPropertiesTab::setImageBitDepth(const QString& str) { d->labelImageBitDepth->setAdjustedText(str); } void ItemPropertiesTab::setImageColorMode(const QString& str) { d->labelImageColorMode->setAdjustedText(str); } void ItemPropertiesTab::setPhotoMake(const QString& str) { d->labelPhotoMake->setAdjustedText(str); } void ItemPropertiesTab::setPhotoModel(const QString& str) { d->labelPhotoModel->setAdjustedText(str); } void ItemPropertiesTab::setPhotoDateTime(const QString& str) { d->labelPhotoDateTime->setAdjustedText(str); } void ItemPropertiesTab::setPhotoLens(const QString& str) { d->labelPhotoLens->setAdjustedText(str); } void ItemPropertiesTab::setPhotoAperture(const QString& str) { d->labelPhotoAperture->setAdjustedText(str); } void ItemPropertiesTab::setPhotoFocalLength(const QString& str) { d->labelPhotoFocalLength->setAdjustedText(str); } void ItemPropertiesTab::setPhotoExposureTime(const QString& str) { d->labelPhotoExposureTime->setAdjustedText(str); } void ItemPropertiesTab::setPhotoSensitivity(const QString& str) { d->labelPhotoSensitivity->setAdjustedText(str); } void ItemPropertiesTab::setPhotoExposureMode(const QString& str) { d->labelPhotoExposureMode->setAdjustedText(str); } void ItemPropertiesTab::setPhotoFlash(const QString& str) { d->labelPhotoFlash->setAdjustedText(str); } void ItemPropertiesTab::setPhotoWhiteBalance(const QString& str) { d->labelPhotoWhiteBalance->setAdjustedText(str); } void ItemPropertiesTab::showOrHideCaptionAndTags() { bool hasCaption = !d->labelCaption->adjustedText().isEmpty(); bool hasPickLabel = !d->labelPickLabel->adjustedText().isEmpty(); bool hasColorLabel = !d->labelColorLabel->adjustedText().isEmpty(); bool hasRating = !d->labelRating->adjustedText().isEmpty(); bool hasTags = !d->labelTags->adjustedText().isEmpty(); d->caption->setVisible(hasCaption); d->labelCaption->setVisible(hasCaption); d->pickLabel->setVisible(hasPickLabel); d->labelPickLabel->setVisible(hasPickLabel); d->colorLabel->setVisible(hasColorLabel); d->labelColorLabel->setVisible(hasColorLabel); d->rating->setVisible(hasRating); d->labelRating->setVisible(hasRating); d->tags->setVisible(hasTags); d->labelTags->setVisible(hasTags); widget(ItemPropertiesTab::Private::digiKamProperties)->setVisible(hasCaption || hasRating || hasTags || hasPickLabel || hasColorLabel); } void ItemPropertiesTab::setCaption(const QString& str) { d->labelCaption->setAdjustedText(str); } void ItemPropertiesTab::setColorLabel(int colorId) { if (colorId == NoColorLabel) { d->labelColorLabel->setAdjustedText(QString()); } else { d->labelColorLabel->setAdjustedText(ColorLabelWidget::labelColorName((ColorLabel)colorId)); } } void ItemPropertiesTab::setPickLabel(int pickId) { if (pickId == NoPickLabel) { d->labelPickLabel->setAdjustedText(QString()); } else { d->labelPickLabel->setAdjustedText(PickLabelWidget::labelPickName((PickLabel)pickId)); } } void ItemPropertiesTab::setRating(int rating) { QString str; if ((rating > RatingMin) && (rating <= RatingMax)) { str = QLatin1Char(' '); - for (int i = 0 ; i < rating ; i++) + for (int i = 0 ; i < rating ; ++i) { str += QChar(0x2730); str += QLatin1Char(' '); } } d->labelRating->setAdjustedText(str); } void ItemPropertiesTab::setVideoAspectRatio(const QString& str) { d->labelVideoAspectRatio->setAdjustedText(str); } void ItemPropertiesTab::setVideoAudioBitRate(const QString& str) { // use string given as parameter by default because it contains the value for "unavailable" if needed QString audioBitRateString = str; bool ok = false; const int audioBitRateInt = str.toInt(&ok); if (ok) { audioBitRateString = QLocale().toString(audioBitRateInt); } d->labelVideoAudioBitRate->setAdjustedText(audioBitRateString); } void ItemPropertiesTab::setVideoAudioChannelType(const QString& str) { d->labelVideoAudioChannelType->setAdjustedText(str); } void ItemPropertiesTab::setVideoAudioCodec(const QString& str) { d->labelVideoAudioCodec->setAdjustedText(str); } void ItemPropertiesTab::setVideoDuration(const QString& str) { // duration is given as a string in milliseconds // use string given as parameter by default because it contains the value for "unavailable" if needed QString durationString = str; bool ok = false; const int durationVal = str.toInt(&ok); if (ok) { unsigned int r, d, h, m, s, f; r = qAbs(durationVal); d = r / 86400000; r = r % 86400000; h = r / 3600000; r = r % 3600000; m = r / 60000; r = r % 60000; s = r / 1000; f = r % 1000; durationString = QString().sprintf("%d.%02d:%02d:%02d.%03d", d, h, m, s, f); } d->labelVideoDuration->setAdjustedText(durationString); } void ItemPropertiesTab::setVideoFrameRate(const QString& str) { // use string given as parameter by default because it contains the value for "unavailable" if needed QString frameRateString = str; bool ok; const double frameRateDouble = str.toDouble(&ok); if (ok) { frameRateString = QLocale().toString(frameRateDouble) + i18n(" fps"); } d->labelVideoFrameRate->setAdjustedText(frameRateString); } void ItemPropertiesTab::setVideoVideoCodec(const QString& str) { d->labelVideoVideoCodec->setAdjustedText(str); } void ItemPropertiesTab::setTags(const QStringList& tagPaths, const QStringList& tagNames) { Q_UNUSED(tagNames); d->labelTags->setAdjustedText(shortenedTagPaths(tagPaths).join(QLatin1Char('\n'))); } typedef QPair PathValuePair; static bool naturalLessThan(const PathValuePair& a, const PathValuePair& b) { return (QCollator().compare(a.first, b.first) < 0); } QStringList ItemPropertiesTab::shortenedTagPaths(const QStringList& tagPaths, QList* identifiers) { QList tagsSorted; if (identifiers) { for (int i = 0 ; i < tagPaths.size() ; ++i) { tagsSorted << PathValuePair(tagPaths.at(i), (*identifiers).at(i)); } } else { for (int i = 0 ; i < tagPaths.size() ; ++i) { tagsSorted << PathValuePair(tagPaths.at(i), QVariant()); } } std::stable_sort(tagsSorted.begin(), tagsSorted.end(), naturalLessThan); if (identifiers) { identifiers->clear(); } QStringList tagsShortened; QString previous; foreach(const PathValuePair& pair, tagsSorted) { const QString& tagPath = pair.first; QString shortenedPath = tagPath; QStringList currentPath = tagPath.split(QLatin1Char('/'), QString::SkipEmptyParts); QStringList previousPath = previous.split(QLatin1Char('/'), QString::SkipEmptyParts); int depth; for (depth = 0 ; depth < currentPath.size() && depth < previousPath.size() ; ++depth) { if (currentPath.at(depth) != previousPath.at(depth)) break; } if (depth) { QString indent; indent.fill(QLatin1Char(' '), qMin(depth, 5)); //indent += QChar(0x2026); shortenedPath = indent + tagPath.section(QLatin1Char('/'), depth); } shortenedPath.replace(QLatin1Char('/'), QLatin1String(" / ")); tagsShortened << shortenedPath; previous = tagPath; if (identifiers) { (*identifiers) << pair.second; } } return tagsShortened; } void ItemPropertiesTab::shortenedMakeInfo(QString& make) { make.remove(QLatin1String(" CORPORATION"), Qt::CaseInsensitive); // from Nikon, Pentax, and Olympus make.remove(QLatin1String("EASTMAN "), Qt::CaseInsensitive); // from Kodak make.remove(QLatin1String(" COMPANY"), Qt::CaseInsensitive); // from Kodak make.remove(QLatin1String(" OPTICAL CO.,LTD"), Qt::CaseInsensitive); // from Olympus make.remove(QLatin1String(" IMAGING CORP."), Qt::CaseInsensitive); // from Olympus make.remove(QLatin1String(" Techwin co.,Ltd."), Qt::CaseInsensitive); // from Samsung make.remove(QLatin1String(" Co.,Ltd."), Qt::CaseInsensitive); // from Minolta make.remove(QLatin1String(" Electric Co.,Ltd."), Qt::CaseInsensitive); // from Sanyo make.remove(QLatin1String(" Electric Co.,Ltd"), Qt::CaseInsensitive); // from Sanyo } void ItemPropertiesTab::shortenedModelInfo(QString& model) { model.remove(QLatin1String("Canon "), Qt::CaseInsensitive); model.remove(QLatin1String("NIKON "), Qt::CaseInsensitive); model.remove(QLatin1String("PENTAX "), Qt::CaseInsensitive); model.remove(QLatin1String(" DIGITAL"), Qt::CaseInsensitive); // from Canon model.remove(QLatin1String("KODAK "), Qt::CaseInsensitive); model.remove(QLatin1String(" CAMERA"), Qt::CaseInsensitive); // from Kodak } /** * Find rational approximation to given real number * * val : double value to convert as humain readable fraction * num : fraction numerator * den : fraction denominator * maxden : the maximum denominator allowed * * This function return approximation error of the fraction * * Based on the theory of continued fractions * if x = a1 + 1/(a2 + 1/(a3 + 1/(a4 + ...))) * Then best approximation is found by truncating this series * wwith some adjustments in the last term. * * Note the fraction can be recovered as the first column of the matrix * ( a1 1 ) ( a2 1 ) ( a3 1 ) ... * ( 1 0 ) ( 1 0 ) ( 1 0 ) * Instead of keeping the sequence of continued fraction terms, * we just keep the last partial product of these matrices. * * Details: http://stackoverflow.com/questions/95727/how-to-convert-floats-to-human-readable-fractions * */ double ItemPropertiesTab::doubleToHumanReadableFraction(double val, long* num, long* den, long maxden) { double x = val; long m[2][2]; long ai; // Initialize matrix m[0][0] = m[1][1] = 1; m[0][1] = m[1][0] = 0; // Loop finding terms until denominator gets too big while (m[1][0] * (ai = (long)x) + m[1][1] <= maxden) { long t = m[0][0] * ai + m[0][1]; m[0][1] = m[0][0]; m[0][0] = t; t = m[1][0] * ai + m[1][1]; m[1][1] = m[1][0]; m[1][0] = t; if (x == (double)ai) { break; // division by zero } x = 1 / (x - (double)ai); if (x > (double)0x7FFFFFFF) { break; // representation failure } } // Now remaining x is between 0 and 1/ai // Approx as either 0 or 1/m where m is max that will fit in maxden *num = m[0][0]; *den = m[1][0]; // Return approximation error return (val - ((double)m[0][0] / (double)m[1][0])); } bool ItemPropertiesTab::aspectRatioToString(int width, int height, QString& arString) { if ((width == 0) || (height == 0)) { return false; } double ratio = (double)qMax(width, height) / (double)qMin(width, height); long num = 0; long den = 0; doubleToHumanReadableFraction(ratio, &num, &den, 10); double aratio = (double)qMax(num, den) / (double)qMin(num, den); arString = i18nc("width : height (Aspect Ratio)", "%1:%2 (%3)", (width > height) ? num : den, (width > height) ? den : num, QLocale().toString(aratio, 'g', 2)); return true; } QString ItemPropertiesTab::permissionsString(const QFileInfo& fi) { QString str; QFile::Permissions perms = fi.permissions(); str.append(fi.isSymLink() ? QLatin1String("l") : QLatin1String("-")); str.append((perms & QFileDevice::ReadOwner) ? QLatin1String("r") : QLatin1String("-")); str.append((perms & QFileDevice::WriteOwner) ? QLatin1String("w") : QLatin1String("-")); str.append((perms & QFileDevice::ExeOwner) ? QLatin1String("x") : QLatin1String("-")); str.append((perms & QFileDevice::ReadGroup) ? QLatin1String("r") : QLatin1String("-")); str.append((perms & QFileDevice::WriteGroup) ? QLatin1String("w") : QLatin1String("-")); str.append((perms & QFileDevice::ExeGroup) ? QLatin1String("x") : QLatin1String("-")); str.append((perms & QFileDevice::ReadOther) ? QLatin1String("r") : QLatin1String("-")); str.append((perms & QFileDevice::WriteOther) ? QLatin1String("w") : QLatin1String("-")); str.append((perms & QFileDevice::ExeOther) ? QLatin1String("x") : QLatin1String("-")); return str; } QString ItemPropertiesTab::humanReadableBytesCount(qint64 bytes, bool si) { int unit = si ? 1000 : 1024; QString byteStr = i18nc("unit file size in bytes", "B"); QString ret = QString::number(bytes); if (bytes >= unit) { int exp = (int)(qLn(bytes) / qLn(unit)); QString pre = QString(si ? QLatin1String("kMGTPEZY") : QLatin1String("KMGTPEZY")).at(exp-1) + (si ? QLatin1String("") : QLatin1String("i")); ret.sprintf("%.1f %s", bytes / qPow(unit, exp), pre.toLatin1().constData()); } return (QString::fromUtf8("%1%2").arg(ret).arg(byteStr)); } } // namespace Digikam diff --git a/core/libs/threads/parallelworkers.cpp b/core/libs/threads/parallelworkers.cpp index 0ebdce24a9..0da3149e1f 100644 --- a/core/libs/threads/parallelworkers.cpp +++ b/core/libs/threads/parallelworkers.cpp @@ -1,256 +1,256 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2012-01-13 * Description : Multithreaded worker object * * Copyright (C) 2010-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 "parallelworkers.h" // Qt includes #include #include #include #include #include #include #include // Local includes #include "digikam_debug.h" #include "threadmanager.h" namespace Digikam { ParallelWorkers::ParallelWorkers() : m_currentIndex(0), m_replacementMetaObject(0), m_originalStaticMetacall(0) { } ParallelWorkers::~ParallelWorkers() { foreach (WorkerObject* const object, m_workers) { delete object; } delete m_replacementMetaObject; } int ParallelWorkers::optimalWorkerCount() { return qMax(1, QThread::idealThreadCount()); } bool ParallelWorkers::optimalWorkerCountReached() const { return m_workers.size() >= optimalWorkerCount(); } void ParallelWorkers::schedule() { foreach (WorkerObject* const object, m_workers) { object->schedule(); } } void ParallelWorkers::deactivate(WorkerObject::DeactivatingMode mode) { foreach (WorkerObject* const object, m_workers) { object->deactivate(mode); } } void ParallelWorkers::wait() { foreach (WorkerObject* const object, m_workers) { object->wait(); } } void ParallelWorkers::setPriority(QThread::Priority priority) { foreach (WorkerObject* const object, m_workers) { object->setPriority(priority); } } void ParallelWorkers::add(WorkerObject* const worker) { /* if (!asQObject()->inherits(worker->metaObject()->className())) { qCDebug(DIGIKAM_GENERAL_LOG) << "You need to derive the ParallelWorkers class from the WorkerObject you want to use"; return; } QMetaObject* meta = asQObject()->metaObject(); - for (int i=0; imethodCount(); i++) + for (int i=0; imethodCount(); ++i) { QMetaMethod method = meta->method(index); if (!method->methodType() == QMetaMethod:: } */ m_workers << worker; } /* bool ParallelWorkers::connect(const QObject* sender, const char* signal, const char* method, Qt::ConnectionType type) const { foreach (WorkerObject* object, m_workers) { if (!WorkerObject::connect(sender, signal, object, method, type)) { return false; } } return true; } */ bool ParallelWorkers::connect(const char* const signal, const QObject* const receiver, const char* const method, Qt::ConnectionType type) const { foreach (WorkerObject* const object, m_workers) { if (!QObject::connect(object, signal, receiver, method, type)) { return false; } } return true; } int ParallelWorkers::replacementStaticQtMetacall(QMetaObject::Call _c, int _id, void **_a) { if (_c == QMetaObject::InvokeMetaMethod) { // This is the common ancestor's meta object, below WorkerObject const QMetaObject* const mobj = mocMetaObject(); const int properMethods = mobj->methodCount() - mobj->methodOffset(); if (_id >= properMethods) { return _id - properMethods; } // Get the relevant meta method. I'm not quite sure if this is rock solid. QMetaMethod method = mobj->method(_id + mobj->methodOffset()); // Copy the argument data - _a is going to be deleted in our current thread QList types = method.parameterTypes(); QVector args(10); - for (int i = 0; i < types.size(); i++) + for (int i = 0 ; i < types.size() ; ++i) { int typeId = QMetaType::type(types[i].constData()); if (!typeId && _a[i+1]) { qCWarning(DIGIKAM_GENERAL_LOG) << "Unable to handle unregistered datatype" << types[i] << "Dropping signal."; return _id - properMethods; } // we use QMetaType to copy the data. _a[0] is reserved for a return parameter. void* const data = QMetaType::create(typeId, _a[i+1]); args[i] = QGenericArgument(types[i].constData(), data); } // Find the object to be invoked WorkerObject* const obj = m_workers.at(m_currentIndex); if (++m_currentIndex == m_workers.size()) { m_currentIndex = 0; } obj->schedule(); // Invoke across-thread method.invoke(obj, Qt::QueuedConnection, args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9]); return _id - properMethods; // this return is used by replacementQtMetacall } else { m_originalStaticMetacall(asQObject(), _c, _id, _a); } return _id; // this return will be ignored (qt_static_metacall is void) } int ParallelWorkers::replacementQtMetacall(QMetaObject::Call _c, int _id, void **_a) { _id = WorkerObjectQtMetacall(_c, _id, _a); if (_id < 0) { return _id; } if (_c == QMetaObject::InvokeMetaMethod) { return replacementStaticQtMetacall(_c, _id, _a); } return _id; } const QMetaObject* ParallelWorkers::replacementMetaObject() const { if (!m_replacementMetaObject) { QMetaObject* rmo = new QMetaObject(*mocMetaObject()); ParallelWorkers* nonConstThis = const_cast(this); nonConstThis->m_originalStaticMetacall = rmo->d.static_metacall; rmo->d.static_metacall = nonConstThis->staticMetacallPointer(); nonConstThis->m_replacementMetaObject = rmo; } return m_replacementMetaObject; } } // namespace Digikam diff --git a/core/libs/widgets/fonts/dfontproperties.cpp b/core/libs/widgets/fonts/dfontproperties.cpp index 4884286d35..b0f454d904 100644 --- a/core/libs/widgets/fonts/dfontproperties.cpp +++ b/core/libs/widgets/fonts/dfontproperties.cpp @@ -1,1427 +1,1427 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2008-12-23 * Description : a widget to change font properties. * * Copyright (C) 2008-2018 by Gilles Caulier * Copyright (C) 1996 by Bernd Johannes Wuebben * Copyright (c) 1999 by Preston Brown * Copyright (c) 1999 by Mario Weilguni * * 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 "dfontproperties.h" // C++ includes #include // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include namespace Digikam { static bool localeLessThan(const QString& a, const QString& b) { return QString::localeAwareCompare(a, b) < 0; } static int minimumListWidth(const QListWidget* list) { int w = 0; - for (int i = 0; i < list->count(); i++) + for (int i = 0; i < list->count(); ++i) { int itemWidth = list->visualItemRect(list->item(i)).width(); // ...and add a space on both sides for not too tight look. itemWidth += list->fontMetrics().width(QLatin1Char(' ')) * 2; w = qMax(w, itemWidth); } if (w == 0) { w = 40; } w += list->frameWidth() * 2; w += list->verticalScrollBar()->sizeHint().width(); return w; } static int minimumListHeight(const QListWidget* list, int numVisibleEntry) { int w = (list->count() > 0) ? list->visualItemRect(list->item(0)).height() : list->fontMetrics().lineSpacing(); if (w < 0) { w = 10; } if (numVisibleEntry <= 0) { numVisibleEntry = 4; } return (w * numVisibleEntry + 2 * list->frameWidth()); } static QString formatFontSize(qreal size) { return QLocale::system().toString(size, 'f', (size == floor(size)) ? 0 : 1); } // ----------------------------------------------------------------------------------- class Q_DECL_HIDDEN DFontProperties::Private { public: explicit Private(DFontProperties* const qq) : q(qq) { palette.setColor(QPalette::Active, QPalette::Text, Qt::black); palette.setColor(QPalette::Active, QPalette::Base, Qt::white); signalsAllowed = true; selectedSize = -1; customSizeRow = -1; usingFixed = true; sizeOfFont = 0; sampleEdit = 0; familyLabel = 0; styleLabel = 0; familyCheckbox = 0; styleCheckbox = 0; sizeCheckbox = 0; sizeLabel = 0; familyListBox = 0; styleListBox = 0; sizeListBox = 0; sizeIsRelativeCheckBox = 0; } void setFamilyBoxItems(const QStringList& fonts); void fillFamilyListBox(bool onlyFixedFonts = false); int nearestSizeRow(qreal val, bool customize); qreal fillSizeList(const QList& sizes = QList()); qreal setupSizeListBox(const QString& family, const QString& style); void setupDisplay(); QString styleIdentifier(const QFont& font); /** * Split the compound raw font name into family and foundry. * * @param name the raw font name reported by Qt * @param family the storage for family name * @param foundry the storage for foundry name */ void splitFontString(const QString& name, QString* family, QString* foundry = 0); /** * Translate the font name for the user. * Primarily for generic fonts like Serif, Sans-Serif, etc. * * @param name the raw font name reported by Qt * @return translated font name */ QString translateFontName(const QString& name); /** * Compose locale-aware sorted list of translated font names, * with generic fonts handled in a special way. * The mapping of translated to raw names can be reported too if required. * * @param names raw font names as reported by Qt * @param trToRawNames storage for mapping of translated to raw names * @return sorted list of translated font names */ QStringList translateFontNameList(const QStringList& names, QHash* trToRawNames = 0); void _d_toggled_checkbox(); void _d_family_chosen_slot(const QString&); void _d_size_chosen_slot(const QString&); void _d_style_chosen_slot(const QString&); void _d_displaySample(const QFont& font); void _d_size_value_slot(double); public: DFontProperties* q; QPalette palette; QDoubleSpinBox* sizeOfFont; QTextEdit* sampleEdit; QLabel* familyLabel; QLabel* styleLabel; QCheckBox* familyCheckbox; QCheckBox* styleCheckbox; QCheckBox* sizeCheckbox; QLabel* sizeLabel; QListWidget* familyListBox; QListWidget* styleListBox; QListWidget* sizeListBox; QCheckBox* sizeIsRelativeCheckBox; QFont selFont; QString selectedStyle; qreal selectedSize; QString standardSizeAtCustom; int customSizeRow; bool signalsAllowed; bool usingFixed; // Mappings of translated to Qt originated family and style strings. QHash qtFamilies; QHash qtStyles; // Mapping of translated style strings to internal style identifiers. QHash styleIDs; }; DFontProperties::DFontProperties(QWidget* const parent, const DisplayFlags& flags, const QStringList& fontList, int visibleListSize, Qt::CheckState* const sizeIsRelativeState) : QWidget(parent), d(new DFontProperties::Private(this)) { d->usingFixed = flags & FixedFontsOnly; setWhatsThis(i18n("Here you can choose the font to be used.")); // The top layout is divided vertically into a splitter with font // attribute widgets and preview on the top, and XLFD data at the bottom. QVBoxLayout* const topLayout = new QVBoxLayout(this); topLayout->setMargin(0); const int spacingHint = style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); int checkBoxGap = spacingHint / 2; // The splitter contains font attribute widgets in the top part, // and the font preview in the bottom part. // The splitter is there to allow the user to resize the font preview. QSplitter* const splitter = new QSplitter(Qt::Vertical, this); splitter->setChildrenCollapsible(false); topLayout->addWidget(splitter); // Build the grid of font attribute widgets for the upper splitter part. QWidget* page = 0; QGridLayout* gridLayout = 0; int row = 0; if (flags & DisplayFrame) { page = new QGroupBox(i18n("Requested Font"), this); splitter->addWidget(page); gridLayout = new QGridLayout(page); row = 1; } else { page = new QWidget(this); splitter->addWidget(page); gridLayout = new QGridLayout(page); gridLayout->setMargin(0); } // first, create the labels across the top QHBoxLayout* const familyLayout = new QHBoxLayout(); familyLayout->addSpacing(checkBoxGap); if (flags & ShowDifferences) { d->familyCheckbox = new QCheckBox(i18n("Font"), page); connect(d->familyCheckbox, SIGNAL(toggled(bool)), this, SLOT(_d_toggled_checkbox())); familyLayout->addWidget(d->familyCheckbox, 0, Qt::AlignLeft); d->familyCheckbox->setWhatsThis(i18n("Enable this checkbox to change the font family settings.")); d->familyCheckbox->setToolTip(i18n("Change font family?")); d->familyLabel = 0; } else { d->familyCheckbox = 0; d->familyLabel = new QLabel(i18nc("@label", "Font:"), page); familyLayout->addWidget(d->familyLabel, 1, Qt::AlignLeft); } gridLayout->addLayout(familyLayout, row, 0); QHBoxLayout* const styleLayout = new QHBoxLayout(); if (flags & ShowDifferences) { d->styleCheckbox = new QCheckBox(i18n("Font style"), page); connect(d->styleCheckbox, SIGNAL(toggled(bool)), this, SLOT(_d_toggled_checkbox())); styleLayout->addWidget(d->styleCheckbox, 0, Qt::AlignLeft); d->styleCheckbox->setWhatsThis(i18n("Enable this checkbox to change the font style settings.")); d->styleCheckbox->setToolTip(i18n("Change font style?")); d->styleLabel = 0; } else { d->styleCheckbox = 0; d->styleLabel = new QLabel(i18n("Font style:"), page); styleLayout->addWidget(d->styleLabel, 1, Qt::AlignLeft); } styleLayout->addSpacing(checkBoxGap); gridLayout->addLayout(styleLayout, row, 1); QHBoxLayout* const sizeLayout = new QHBoxLayout(); if (flags & ShowDifferences) { d->sizeCheckbox = new QCheckBox(i18n("Size"), page); connect(d->sizeCheckbox, SIGNAL(toggled(bool)), this, SLOT(_d_toggled_checkbox())); sizeLayout->addWidget(d->sizeCheckbox, 0, Qt::AlignLeft); d->sizeCheckbox->setWhatsThis(i18n("Enable this checkbox to change the font size settings.")); d->sizeCheckbox->setToolTip(i18n("Change font size?")); d->sizeLabel = 0; } else { d->sizeCheckbox = 0; d->sizeLabel = new QLabel(i18nc("@label:listbox Font size", "Size:"), page); sizeLayout->addWidget(d->sizeLabel, 1, Qt::AlignLeft); } sizeLayout->addSpacing(checkBoxGap); sizeLayout->addSpacing(checkBoxGap); // prevent label from eating border gridLayout->addLayout(sizeLayout, row, 2); row ++; // now create the actual boxes that hold the info d->familyListBox = new QListWidget(page); d->familyListBox->setEnabled(flags ^ ShowDifferences); gridLayout->addWidget(d->familyListBox, row, 0); QString fontFamilyWhatsThisText(i18n("Here you can choose the font family to be used.")); d->familyListBox->setWhatsThis(fontFamilyWhatsThisText); if (flags & ShowDifferences) { d->familyCheckbox->setWhatsThis(fontFamilyWhatsThisText); } else { d->familyLabel->setWhatsThis(fontFamilyWhatsThisText); } connect(d->familyListBox, SIGNAL(currentTextChanged(QString)), this, SLOT(_d_family_chosen_slot(QString))); if (!fontList.isEmpty()) { d->setFamilyBoxItems(fontList); } else { d->fillFamilyListBox(flags & FixedFontsOnly); } d->familyListBox->setMinimumWidth(minimumListWidth(d->familyListBox)); d->familyListBox->setMinimumHeight(minimumListHeight(d->familyListBox, visibleListSize)); d->styleListBox = new QListWidget(page); d->styleListBox->setEnabled(flags ^ ShowDifferences); gridLayout->addWidget(d->styleListBox, row, 1); d->styleListBox->setWhatsThis(i18n("Here you can choose the font style to be used.")); if (flags & ShowDifferences) { ((QWidget *)d->styleCheckbox)->setWhatsThis(fontFamilyWhatsThisText); } else { ((QWidget *)d->styleLabel)->setWhatsThis(fontFamilyWhatsThisText); } // Populate usual styles, to determine minimum list width; // will be replaced later with correct styles. d->styleListBox->addItem(i18n("Normal")); d->styleListBox->addItem(i18n("Italic")); d->styleListBox->addItem(i18n("Oblique")); d->styleListBox->addItem(i18n("Bold")); d->styleListBox->addItem(i18n("Bold Italic")); d->styleListBox->setMinimumWidth(minimumListWidth(d->styleListBox)); d->styleListBox->setMinimumHeight(minimumListHeight(d->styleListBox, visibleListSize)); connect(d->styleListBox, SIGNAL(currentTextChanged(QString)), this, SLOT(_d_style_chosen_slot(QString))); d->sizeListBox = new QListWidget(page); d->sizeOfFont = new QDoubleSpinBox(page); d->sizeOfFont->setMinimum(4); d->sizeOfFont->setMaximum(999); d->sizeOfFont->setDecimals(1); d->sizeOfFont->setSingleStep(1); d->sizeListBox->setEnabled(flags ^ ShowDifferences); d->sizeOfFont->setEnabled(flags ^ ShowDifferences); if (sizeIsRelativeState) { QString sizeIsRelativeCBText = i18n("Relative"); QString sizeIsRelativeCBToolTipText = i18n("Font size
fixed or relative
to environment"); QString sizeIsRelativeCBWhatsThisText = i18n("Here you can switch between fixed font size and font size " "to be calculated dynamically and adjusted to changing " "environment (e.g. widget dimensions, paper size)."); d->sizeIsRelativeCheckBox = new QCheckBox(sizeIsRelativeCBText, page); d->sizeIsRelativeCheckBox->setTristate(flags & ShowDifferences); QGridLayout* const sizeLayout2 = new QGridLayout(); sizeLayout2->setSpacing(spacingHint / 2); gridLayout->addLayout(sizeLayout2, row, 2); sizeLayout2->setColumnStretch(1, 1); // to prevent text from eating the right border sizeLayout2->addWidget(d->sizeOfFont, 0, 0, 1, 2); sizeLayout2->addWidget(d->sizeListBox, 1, 0, 1, 2); sizeLayout2->addWidget(d->sizeIsRelativeCheckBox, 2, 0, Qt::AlignLeft); d->sizeIsRelativeCheckBox->setWhatsThis(sizeIsRelativeCBWhatsThisText); d->sizeIsRelativeCheckBox->setToolTip(sizeIsRelativeCBToolTipText); } else { d->sizeIsRelativeCheckBox = 0; QGridLayout* const sizeLayout2 = new QGridLayout(); sizeLayout2->setSpacing(spacingHint / 2); gridLayout->addLayout(sizeLayout2, row, 2); sizeLayout2->addWidget(d->sizeOfFont, 0, 0); sizeLayout2->addWidget(d->sizeListBox, 1, 0); } QString fontSizeWhatsThisText = i18n("Here you can choose the font size to be used."); d->sizeListBox->setWhatsThis(fontSizeWhatsThisText); if (flags & ShowDifferences) { ((QWidget*)d->sizeCheckbox)->setWhatsThis(fontSizeWhatsThisText); } else { ((QWidget*)d->sizeLabel)->setWhatsThis(fontSizeWhatsThisText); } // Populate with usual sizes, to determine minimum list width; // will be replaced later with correct sizes. d->fillSizeList(); d->sizeListBox->setMinimumWidth(minimumListWidth(d->sizeListBox) + d->sizeListBox->fontMetrics().maxWidth()); d->sizeListBox->setMinimumHeight(minimumListHeight(d->sizeListBox, visibleListSize)); connect(d->sizeOfFont, SIGNAL(valueChanged(double)), this, SLOT(_d_size_value_slot(double))); connect(d->sizeListBox, SIGNAL(currentTextChanged(QString)), this, SLOT(_d_size_chosen_slot(QString))); row ++; // Completed the font attribute grid. // Add the font preview into the lower part of the splitter. d->sampleEdit = new QTextEdit(page); d->sampleEdit->setAcceptRichText(false); QFont tmpFont(font().family(), 64, QFont::Black); d->sampleEdit->setFont(tmpFont); d->sampleEdit->setMinimumHeight(d->sampleEdit->fontMetrics().lineSpacing()); // tr: A classical test phrase, with all letters of the English alphabet. // Replace it with a sample text in your language, such that it is // representative of language's writing system. // If you wish, you can input several lines of text separated by \n. setSampleText(i18n("The Quick Brown Fox Jumps Over The Lazy Dog")); d->sampleEdit->setTextCursor(QTextCursor(d->sampleEdit->document())); QString sampleEditWhatsThisText = i18n("This sample text illustrates the current settings. " "You may edit it to test special characters."); d->sampleEdit->setWhatsThis(sampleEditWhatsThisText); connect(this, SIGNAL(fontSelected(QFont)), this, SLOT(_d_displaySample(QFont))); splitter->addWidget(d->sampleEdit); // Finished setting up the splitter. // Finished setting up the chooser layout. // lets initialize the display if possible if (d->usingFixed) { setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont), d->usingFixed); } else { setFont(QGuiApplication::font(), d->usingFixed); } // check or uncheck or gray out the "relative" checkbox if (sizeIsRelativeState && d->sizeIsRelativeCheckBox) { setSizeIsRelative(*sizeIsRelativeState); } // Set focus to the size list as this is the most commonly changed property d->sizeListBox->setFocus(); } DFontProperties::~DFontProperties() { delete d; } void DFontProperties::setColor(const QColor& col) { d->palette.setColor(QPalette::Active, QPalette::Text, col); QPalette pal = d->sampleEdit->palette(); pal.setColor(QPalette::Active, QPalette::Text, col); d->sampleEdit->setPalette(pal); QTextCursor cursor = d->sampleEdit->textCursor(); d->sampleEdit->selectAll(); d->sampleEdit->setTextColor(col); d->sampleEdit->setTextCursor(cursor); } QColor DFontProperties::color() const { return d->palette.color(QPalette::Active, QPalette::Text); } void DFontProperties::setBackgroundColor(const QColor& col) { d->palette.setColor(QPalette::Active, QPalette::Base, col); QPalette pal = d->sampleEdit->palette(); pal.setColor(QPalette::Active, QPalette::Base, col); d->sampleEdit->setPalette(pal); } QColor DFontProperties::backgroundColor() const { return d->palette.color(QPalette::Active, QPalette::Base); } void DFontProperties::setSizeIsRelative(Qt::CheckState relative) { // check or uncheck or gray out the "relative" checkbox if (d->sizeIsRelativeCheckBox) { if (Qt::PartiallyChecked == relative) { d->sizeIsRelativeCheckBox->setCheckState(Qt::PartiallyChecked); } else { d->sizeIsRelativeCheckBox->setCheckState((Qt::Checked == relative) ? Qt::Checked : Qt::Unchecked); } } } Qt::CheckState DFontProperties::sizeIsRelative() const { return d->sizeIsRelativeCheckBox ? d->sizeIsRelativeCheckBox->checkState() : Qt::PartiallyChecked; } QString DFontProperties::sampleText() const { return d->sampleEdit->toPlainText(); } void DFontProperties::setSampleText(const QString& text) { d->sampleEdit->setPlainText(text); } void DFontProperties::setSampleBoxVisible(bool visible) { d->sampleEdit->setVisible(visible); } QSize DFontProperties::sizeHint(void) const { return minimumSizeHint(); } void DFontProperties::enableColumn(int column, bool state) { if (column & FamilyList) { d->familyListBox->setEnabled(state); } if (column & StyleList) { d->styleListBox->setEnabled(state); } if (column & SizeList) { d->sizeListBox->setEnabled(state); d->sizeOfFont->setEnabled(state); } } void DFontProperties::makeColumnVisible(int column, bool state) { if (column & FamilyList) { d->familyListBox->setVisible(state); d->familyLabel->setVisible(state); } if (column & StyleList) { d->styleListBox->setVisible(state); d->styleLabel->setVisible(state); } if (column & SizeList) { d->sizeListBox->setVisible(state); d->sizeOfFont->setVisible(state); d->sizeLabel->setVisible(state); } } void DFontProperties::setFont(const QFont& aFont, bool onlyFixed) { d->selFont = aFont; d->selectedSize = aFont.pointSizeF(); if (d->selectedSize == -1) { d->selectedSize = QFontInfo(aFont).pointSizeF(); } if (onlyFixed != d->usingFixed) { d->usingFixed = onlyFixed; d->fillFamilyListBox(d->usingFixed); } d->setupDisplay(); } DFontProperties::FontDiffFlags DFontProperties::fontDiffFlags() const { FontDiffFlags diffFlags = NoFontDiffFlags; if (d->familyCheckbox && d->familyCheckbox->isChecked()) { diffFlags |= FontDiffFamily; } if (d->styleCheckbox && d->styleCheckbox->isChecked()) { diffFlags |= FontDiffStyle; } if (d->sizeCheckbox && d->sizeCheckbox->isChecked()) { diffFlags |= FontDiffSize; } return diffFlags; } QFont DFontProperties::font() const { return d->selFont; } void DFontProperties::Private::_d_toggled_checkbox() { familyListBox->setEnabled(familyCheckbox->isChecked()); styleListBox->setEnabled(styleCheckbox->isChecked()); sizeListBox->setEnabled(sizeCheckbox->isChecked()); sizeOfFont->setEnabled(sizeCheckbox->isChecked()); } void DFontProperties::Private::_d_family_chosen_slot(const QString& family) { if (!signalsAllowed) { return; } signalsAllowed = false; QString currentFamily; if (family.isEmpty()) { Q_ASSERT(familyListBox->currentItem()); if (familyListBox->currentItem()) { currentFamily = qtFamilies[familyListBox->currentItem()->text()]; } } else { currentFamily = qtFamilies[family]; } // Get the list of styles available in this family. QFontDatabase dbase; QStringList styles = dbase.styles(currentFamily); if (styles.isEmpty()) { styles.append(i18n("Normal")); } // Filter style strings and add to the listbox. QString pureFamily; splitFontString(family, &pureFamily); QStringList filteredStyles; qtStyles.clear(); styleIDs.clear(); foreach(const QString& style, styles) { // Sometimes the font database will report an invalid style, // that falls back to another when set. // Remove such styles, by checking set/get round-trip. QFont testFont = dbase.font(currentFamily, style, 10); if (dbase.styleString(testFont) != style) { styles.removeAll(style); continue; } QString fstyle = style; if (!filteredStyles.contains(fstyle)) { filteredStyles.append(fstyle); qtStyles.insert(fstyle, style); styleIDs.insert(fstyle, styleIdentifier(testFont)); } } styleListBox->clear(); styleListBox->addItems(filteredStyles); // Try to set the current style in the listbox to that previous. int listPos = filteredStyles.indexOf(selectedStyle.isEmpty() ? i18n("Normal") : selectedStyle); if (listPos < 0) { // Make extra effort to have Italic selected when Oblique was chosen, // and vice versa, as that is what the user would probably want. QString styleIt = i18n("Italic"); QString styleOb = i18n("Oblique"); for (int i = 0 ; i < 2 ; ++i) { int pos = selectedStyle.indexOf(styleIt); if (pos >= 0) { QString style = selectedStyle; style.replace(pos, styleIt.length(), styleOb); listPos = filteredStyles.indexOf(style); if (listPos >= 0) { break; } } std::swap(styleIt, styleOb); } } styleListBox->setCurrentRow(listPos >= 0 ? listPos : 0); QString currentStyle = qtStyles[styleListBox->currentItem()->text()]; // Recompute the size listbox for this family/style. qreal currentSize = setupSizeListBox(currentFamily, currentStyle); sizeOfFont->setValue(currentSize); selFont = dbase.font(currentFamily, currentStyle, int(currentSize)); if (dbase.isSmoothlyScalable(currentFamily, currentStyle) && selFont.pointSize() == floor(currentSize)) { selFont.setPointSizeF(currentSize); } emit q->fontSelected(selFont); signalsAllowed = true; } void DFontProperties::Private::_d_style_chosen_slot(const QString& style) { if (!signalsAllowed) { return; } signalsAllowed = false; QFontDatabase dbase; QString currentFamily = qtFamilies[familyListBox->currentItem()->text()]; QString currentStyle; if (style.isEmpty()) { currentStyle = qtStyles[styleListBox->currentItem()->text()]; } else { currentStyle = qtStyles[style]; } // Recompute the size listbox for this family/style. qreal currentSize = setupSizeListBox(currentFamily, currentStyle); sizeOfFont->setValue(currentSize); selFont = dbase.font(currentFamily, currentStyle, int(currentSize)); if (dbase.isSmoothlyScalable(currentFamily, currentStyle) && selFont.pointSize() == floor(currentSize)) { selFont.setPointSizeF(currentSize); } emit q->fontSelected(selFont); if (!style.isEmpty()) { selectedStyle = currentStyle; } signalsAllowed = true; } void DFontProperties::Private::_d_size_chosen_slot(const QString& size) { if (!signalsAllowed) { return; } signalsAllowed = false; qreal currentSize; if (size.isEmpty()) { currentSize = QLocale::system().toDouble(sizeListBox->currentItem()->text()); } else { currentSize = QLocale::system().toDouble(size); } // Reset the customized size slot in the list if not needed. if (customSizeRow >= 0 && selFont.pointSizeF() != currentSize) { sizeListBox->item(customSizeRow)->setText(standardSizeAtCustom); customSizeRow = -1; } sizeOfFont->setValue(currentSize); selFont.setPointSizeF(currentSize); emit q->fontSelected(selFont); if (!size.isEmpty()) { selectedSize = currentSize; } signalsAllowed = true; } void DFontProperties::Private::_d_size_value_slot(double dval) { if (!signalsAllowed) { return; } signalsAllowed = false; // We compare with qreal, so convert for platforms where qreal != double. qreal val = qreal(dval); QFontDatabase dbase; QString family = qtFamilies[familyListBox->currentItem()->text()]; QString style = qtStyles[styleListBox->currentItem()->text()]; // Reset current size slot in list if it was customized. if (customSizeRow >= 0 && sizeListBox->currentRow() == customSizeRow) { sizeListBox->item(customSizeRow)->setText(standardSizeAtCustom); customSizeRow = -1; } bool canCustomize = true; // For Qt-bad-sizes workaround: skip this block unconditionally if (!dbase.isSmoothlyScalable(family, style)) { // Bitmap font, allow only discrete sizes. // Determine the nearest in the direction of change. canCustomize = false; int nrows = sizeListBox->count(); int row = sizeListBox->currentRow(); int nrow; if (val - selFont.pointSizeF() > 0) { for (nrow = row + 1; nrow < nrows; ++nrow) { if (QLocale::system().toDouble(sizeListBox->item(nrow)->text()) >= val) { break; } } } else { for (nrow = row - 1; nrow >= 0; --nrow) { if (QLocale::system().toDouble(sizeListBox->item(nrow)->text()) <= val) { break; } } } // Make sure the new row is not out of bounds. nrow = nrow < 0 ? 0 : nrow >= nrows ? nrows - 1 : nrow; // Get the size from the new row and set the spinbox to that size. val = QLocale::system().toDouble(sizeListBox->item(nrow)->text()); sizeOfFont->setValue(val); } // Set the current size in the size listbox. int row = nearestSizeRow(val, canCustomize); sizeListBox->setCurrentRow(row); selectedSize = val; selFont.setPointSizeF(val); emit q->fontSelected(selFont); signalsAllowed = true; } void DFontProperties::Private::_d_displaySample(const QFont& font) { sampleEdit->setFont(font); } int DFontProperties::Private::nearestSizeRow(qreal val, bool customize) { qreal diff = 1000; int row = 0; for (int r = 0; r < sizeListBox->count(); ++r) { qreal cval = QLocale::system().toDouble(sizeListBox->item(r)->text()); if (qAbs(cval - val) < diff) { diff = qAbs(cval - val); row = r; } } // For Qt-bad-sizes workaround: ignore value of customize, use true if (customize && diff > 0) { customSizeRow = row; standardSizeAtCustom = sizeListBox->item(row)->text(); sizeListBox->item(row)->setText(formatFontSize(val)); } return row; } qreal DFontProperties::Private::fillSizeList(const QList& sizes_) { if (!sizeListBox) { return 0; } QList sizes = sizes_; bool canCustomize = false; if (sizes.count() == 0) { static const int c[] = { 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 22, 24, 26, 28, 32, 48, 64, 72, 80, 96, 128, 0 }; for (int i = 0 ; c[i] ; ++i) { sizes.append(c[i]); } // Since sizes were not supplied, this is a vector font, // and size slot customization is allowed. canCustomize = true; } // Insert sizes into the listbox. sizeListBox->clear(); std::sort(sizes.begin(), sizes.end()); foreach(qreal size, sizes) { sizeListBox->addItem(formatFontSize(size)); } // Return the nearest to selected size. // If the font is vector, the nearest size is always same as selected, // thus size slot customization is allowed. // If the font is bitmap, the nearest size need not be same as selected, // thus size slot customization is not allowed. customSizeRow = -1; int row = nearestSizeRow(selectedSize, canCustomize); return QLocale::system().toDouble(sizeListBox->item(row)->text()); } qreal DFontProperties::Private::setupSizeListBox(const QString& family, const QString& style) { QFontDatabase dbase; QList sizes; if (dbase.isSmoothlyScalable(family, style)) { // A vector font. //sampleEdit->setPaletteBackgroundPixmap( VectorPixmap ); // TODO } else { // A bitmap font. //sampleEdit->setPaletteBackgroundPixmap( BitmapPixmap ); // TODO QList smoothSizes = dbase.smoothSizes(family, style); foreach(int size, smoothSizes) { sizes.append(size); } } // Fill the listbox (uses default list of sizes if the given is empty). // Collect the best fitting size to selected size, to use if not smooth. qreal bestFitSize = fillSizeList(sizes); // Set the best fit size as current in the listbox if available. const QList selectedSizeList = sizeListBox->findItems(formatFontSize(bestFitSize), Qt::MatchExactly); if (!selectedSizeList.isEmpty()) { sizeListBox->setCurrentItem(selectedSizeList.first()); } return bestFitSize; } void DFontProperties::Private::setupDisplay() { QFontDatabase dbase; QString family = selFont.family().toLower(); QString styleID = styleIdentifier(selFont); qreal size = selFont.pointSizeF(); if (size == -1) { size = QFontInfo(selFont).pointSizeF(); } int numEntries, i; // Direct family match. numEntries = familyListBox->count(); for (i = 0 ; i < numEntries ; ++i) { if (family == qtFamilies[familyListBox->item(i)->text()].toLower()) { familyListBox->setCurrentRow(i); break; } } // 1st family fallback. if (i == numEntries) { if (family.contains(QLatin1Char('['))) { family = family.left(family.indexOf(QLatin1Char('['))).trimmed(); for (i = 0 ; i < numEntries ; ++i) { if (family == qtFamilies[familyListBox->item(i)->text()].toLower()) { familyListBox->setCurrentRow(i); break; } } } } // 2nd family fallback. if (i == numEntries) { QString fallback = family + QLatin1String(" ["); for (i = 0 ; i < numEntries ; ++i) { if (qtFamilies[familyListBox->item(i)->text()].toLower().startsWith(fallback)) { familyListBox->setCurrentRow(i); break; } } } // 3rd family fallback. if (i == numEntries) { for (i = 0 ; i < numEntries ; ++i) { if (qtFamilies[familyListBox->item(i)->text()].toLower().startsWith(family)) { familyListBox->setCurrentRow(i); break; } } } // Family fallback in case nothing matched. Otherwise, diff doesn't work if (i == numEntries) { familyListBox->setCurrentRow(0); } // By setting the current item in the family box, the available // styles and sizes for that family have been collected. // Try now to set the current items in the style and size boxes. // Set current style in the listbox. numEntries = styleListBox->count(); for (i = 0 ; i < numEntries ; ++i) { if (styleID == styleIDs[styleListBox->item(i)->text()]) { styleListBox->setCurrentRow(i); break; } } if (i == numEntries) { // Style not found, fallback. styleListBox->setCurrentRow(0); } // Set current size in the listbox. // If smoothly scalable, allow customizing one of the standard size slots, // otherwise just select the nearest available size. QString currentFamily = qtFamilies[familyListBox->currentItem()->text()]; QString currentStyle = qtStyles[styleListBox->currentItem()->text()]; bool canCustomize = dbase.isSmoothlyScalable(currentFamily, currentStyle); sizeListBox->setCurrentRow(nearestSizeRow(size, canCustomize)); // Set current size in the spinbox. sizeOfFont->setValue(QLocale::system().toDouble(sizeListBox->currentItem()->text())); } void DFontProperties::getFontList(QStringList& list, uint fontListCriteria) { QFontDatabase dbase; QStringList lstSys(dbase.families()); // if we have criteria; then check fonts before adding if (fontListCriteria) { QStringList lstFonts; for (QStringList::const_iterator it = lstSys.constBegin(); it != lstSys.constEnd(); ++it) { if ((fontListCriteria & FixedWidthFonts) > 0 && !dbase.isFixedPitch(*it)) { continue; } if (((fontListCriteria & (SmoothScalableFonts | ScalableFonts)) == ScalableFonts) && !dbase.isBitmapScalable(*it)) { continue; } if ((fontListCriteria & SmoothScalableFonts) > 0 && !dbase.isSmoothlyScalable(*it)) { continue; } lstFonts.append(*it); } if ((fontListCriteria & FixedWidthFonts) > 0) { // Fallback.. if there are no fixed fonts found, it's probably a // bug in the font server or Qt. In this case, just use 'fixed' if (lstFonts.count() == 0) { lstFonts.append(QLatin1String("fixed")); } } lstSys = lstFonts; } lstSys.sort(); list = lstSys; } void DFontProperties::Private::setFamilyBoxItems(const QStringList& fonts) { signalsAllowed = false; QStringList trfonts = translateFontNameList(fonts, &qtFamilies); familyListBox->clear(); familyListBox->addItems(trfonts); signalsAllowed = true; } void DFontProperties::Private::fillFamilyListBox(bool onlyFixedFonts) { QStringList fontList; getFontList(fontList, onlyFixedFonts ? FixedWidthFonts : 0); setFamilyBoxItems(fontList); } /** Human-readable style identifiers returned by QFontDatabase::styleString() * do not always survive round trip of QFont serialization/deserialization, * causing wrong style in the style box to be highlighted when * the chooser dialog is opened. This will cause the style to be changed * when the dialog is closed and the user did not touch the style box. * Hence, construct custom style identifiers sufficient for the purpose. */ QString DFontProperties::Private::styleIdentifier(const QFont& font) { const QChar comma(QLatin1Char(',')); return (QString::number(font.weight()) + comma + QString::number((int)font.style()) + comma + QString::number(font.stretch()) ); } void DFontProperties::Private::splitFontString(const QString& name, QString* family, QString* foundry) { int p1 = name.indexOf(QLatin1Char('[')); if (p1 < 0) { if (family) { *family = name.trimmed(); } if (foundry) { foundry->clear(); } } else { int p2 = name.indexOf(QLatin1Char(']'), p1); p2 = p2 > p1 ? p2 : name.length(); if (family) { *family = name.left(p1).trimmed(); } if (foundry) { *foundry = name.mid(p1 + 1, p2 - p1 - 1).trimmed(); } } } QString DFontProperties::Private::translateFontName(const QString& name) { QString family, foundry; splitFontString(name, &family, &foundry); // Obtain any regular translations for the family and foundry. QString trFamily = QCoreApplication::translate("FontHelpers", family.toUtf8().constData(), "@item Font name"); QString trFoundry = foundry; if (!foundry.isEmpty()) { trFoundry = QCoreApplication::translate("FontHelpers", foundry.toUtf8().constData(), "@item Font foundry"); } // Assemble full translation. QString trfont; if (foundry.isEmpty()) { // i18n: Filter by which the translators can translate, or otherwise // operate on the font names not put up for regular translation. trfont = QCoreApplication::translate("FontHelpers", "%1", "@item Font name").arg(trFamily); } else { // i18n: Filter by which the translators can translate, or otherwise // operate on the font names not put up for regular translation. trfont = QCoreApplication::translate("FontHelpers", "%1 [%2]", "@item Font name [foundry]") .arg(trFamily).arg(trFoundry); } return trfont; } QStringList DFontProperties::Private::translateFontNameList(const QStringList& names, QHash* trToRawNames) { // Generic fonts, in the inverse of desired order. QStringList genericNames; genericNames.append(QLatin1String("Monospace")); genericNames.append(QLatin1String("Serif")); genericNames.append(QLatin1String("Sans Serif")); // Translate fonts, but do not add generics to the list right away. QStringList trNames; QHash trMap; foreach(const QString& name, names) { QString trName = translateFontName(name); if (!genericNames.contains(name)) { trNames.append(trName); } trMap.insert(trName, name); } // Sort real fonts alphabetically. std::sort(trNames.begin(), trNames.end(), localeLessThan); // Prepend generic fonts, in the predefined order. foreach(const QString& genericName, genericNames) { QString trGenericName = translateFontName(genericName); if (trMap.contains(trGenericName)) { trNames.prepend(trGenericName); } } if (trToRawNames) { *trToRawNames = trMap; } return trNames; } } // namespace Digikam #include "moc_dfontproperties.cpp" diff --git a/core/libs/widgets/mainview/schememanager.cpp b/core/libs/widgets/mainview/schememanager.cpp index 0ca681fd3b..a643a33dc9 100644 --- a/core/libs/widgets/mainview/schememanager.cpp +++ b/core/libs/widgets/mainview/schememanager.cpp @@ -1,1144 +1,1144 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2004-08-02 * Description : colors scheme manager * * Copyright (C) 2006-2018 by Gilles Caulier * Copyright (C) 2007 by Matthew Woehlke * * 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 "schememanager.h" // C++ includes #include // Qt includes #include #include // KDE includes #include #include namespace Digikam { // HCY color space management class Q_DECL_HIDDEN HCYColorSpace { public: explicit HCYColorSpace(const QColor&); explicit HCYColorSpace(qreal h_, qreal c_, qreal y_, qreal a_ = 1.0); QColor qColor() const; static qreal luma(const QColor &); public: qreal h; qreal c; qreal y; qreal a; private: static qreal gamma(qreal); static qreal igamma(qreal); static qreal lumag(qreal, qreal, qreal); }; // ------------------------------------------------------------------------------ namespace ColorTools { static inline qreal wrap(qreal a, qreal d = 1.0) { qreal r = fmod(a, d); return (r < 0.0 ? d + r : (r > 0.0 ? r : 0.0)); } /** * normalize: like qBound(a, 0.0, 1.0) but without needing the args and with * "safer" behavior on NaN (isnan(a) -> return 0.0) */ static inline qreal normalize(qreal a) { return (a < 1.0 ? (a > 0.0 ? a : 0.0) : 1.0); } static inline qreal mixQreal(qreal a, qreal b, qreal bias) { return (a + (b - a) * bias); } /** * Calculate the luma of a color. Luma is weighted sum of gamma-adjusted * R'G'B' components of a color. The result is similar to qGray. The range * is from 0.0 (black) to 1.0 (white). * */ qreal luma(const QColor& color) { return HCYColorSpace::luma(color); } /** * Calculate hue, chroma and luma of a color in one call. */ void getHcy(const QColor& color, qreal* h, qreal* c, qreal* y, qreal* a = 0) { if (!c || !h || !y) { return; } HCYColorSpace khcy(color); *c = khcy.c; *h = khcy.h; *y = khcy.y; if (a) { *a = khcy.a; } } /** * Calculate the contrast ratio between two colors, according to the * W3C/WCAG2.0 algorithm, (Lmax + 0.05)/(Lmin + 0.05), where Lmax and Lmin * are the luma values of the lighter color and the darker color, * respectively. * * A contrast ration of 5:1 (result == 5.0) is the minimum for "normal" * text to be considered readable (large text can go as low as 3:1). The * ratio ranges from 1:1 (result == 1.0) to 21:1 (result == 21.0). */ static qreal contrastRatioForLuma(qreal y1, qreal y2) { if (y1 > y2) { return (y1 + 0.05) / (y2 + 0.05); } return (y2 + 0.05) / (y1 + 0.05); } qreal contrastRatio(const QColor& c1, const QColor& c2) { return contrastRatioForLuma(luma(c1), luma(c2)); } /** * Adjust the luma of a color by changing its distance from white. */ QColor lighten(const QColor& color, qreal ky = 0.5, qreal kc = 1.0) { HCYColorSpace c(color); c.y = 1.0 - ColorTools::normalize((1.0 - c.y) * (1.0 - ky)); c.c = 1.0 - ColorTools::normalize((1.0 - c.c) * kc); return c.qColor(); } /** * Adjust the luma of a color by changing its distance from black. */ QColor darken(const QColor& color, qreal ky = 0.5, qreal kc = 1.0) { HCYColorSpace c(color); c.y = ColorTools::normalize(c.y * (1.0 - ky)); c.c = ColorTools::normalize(c.c * kc); return c.qColor(); } /** * Adjust the luma and chroma components of a color. The amount is added * to the corresponding component. */ QColor shade(const QColor& color, qreal ky, qreal kc = 0.0) { HCYColorSpace c(color); c.y = ColorTools::normalize(c.y + ky); c.c = ColorTools::normalize(c.c + kc); return c.qColor(); } /** * Blend two colors into a new color by linear combination. */ QColor mix(const QColor& c1, const QColor& c2, qreal bias) { if (bias <= 0.0) { return c1; } if (bias >= 1.0) { return c2; } if (qIsNaN(bias)) { return c1; } qreal r = mixQreal(c1.redF(), c2.redF(), bias); qreal g = mixQreal(c1.greenF(), c2.greenF(), bias); qreal b = mixQreal(c1.blueF(), c2.blueF(), bias); qreal a = mixQreal(c1.alphaF(), c2.alphaF(), bias); return QColor::fromRgbF(r, g, b, a); } static QColor tintHelper(const QColor& base, qreal baseLuma, const QColor& color, qreal amount) { HCYColorSpace result(mix(base, color, pow(amount, 0.3))); result.y = mixQreal(baseLuma, result.y, amount); return result.qColor(); } /** * Create a new color by tinting one color with another. This function is * meant for creating additional colors withings the same class (background, * foreground) from colors in a different class. Therefore when @p amount * is low, the luma of @p base is mostly preserved, while the hue and * chroma of @p color is mostly inherited. * * @param base color to be tinted * @param color color with which to tint * @param amount how strongly to tint the base; 0.0 gives @p base, * 1.0 gives @p color */ QColor tint(const QColor& base, const QColor& color, qreal amount = 0.3) { if (amount <= 0.0) { return base; } if (amount >= 1.0) { return color; } if (qIsNaN(amount)) { return base; } qreal baseLuma = luma(base); //cache value because luma call is expensive double ri = contrastRatioForLuma(baseLuma, luma(color)); double rg = 1.0 + ((ri + 1.0) * amount * amount * amount); double u = 1.0, l = 0.0; QColor result; for (int i = 12; i; --i) { double a = 0.5 * (l + u); result = tintHelper(base, baseLuma, color, a); double ra = contrastRatioForLuma(baseLuma, luma(result)); if (ra > rg) { u = a; } else { l = a; } } return result; } /** * Blend two colors into a new color by painting the second color over the * first using the specified composition mode. * * @param base the base color (alpha channel is ignored). * @param paint the color to be overlayed onto the base color. * @param comp the CompositionMode used to do the blending. */ QColor overlayColors(const QColor& base, const QColor& paint, QPainter::CompositionMode comp) { // This isn't the fastest way, but should be "fast enough". // It's also the only safe way to use QPainter::CompositionMode QImage img(1, 1, QImage::Format_ARGB32_Premultiplied); QPainter p(&img); QColor start = base; start.setAlpha(255); // opaque p.fillRect(0, 0, 1, 1, start); p.setCompositionMode(comp); p.fillRect(0, 0, 1, 1, paint); p.end(); return img.pixel(0, 0); } } // namespace ColorTools // ------------------------------------------------------------------------------ #define HCY_REC 709 // use 709 for now #if HCY_REC == 601 static const qreal yc[3] = {0.299, 0.587, 0.114 }; #elif HCY_REC == 709 static const qreal yc[3] = {0.2126, 0.7152, 0.0722 }; #else // use Qt values static const qreal yc[3] = {0.34375, 0.5, 0.15625}; #endif qreal HCYColorSpace::gamma(qreal n) { return pow(ColorTools::normalize(n), 2.2); } qreal HCYColorSpace::igamma(qreal n) { return pow(ColorTools::normalize(n), 1.0 / 2.2); } qreal HCYColorSpace::lumag(qreal r, qreal g, qreal b) { return r * yc[0] + g * yc[1] + b * yc[2]; } HCYColorSpace::HCYColorSpace(qreal h_, qreal c_, qreal y_, qreal a_) { h = h_; c = c_; y = y_; a = a_; } HCYColorSpace::HCYColorSpace(const QColor& color) { qreal r = gamma(color.redF()); qreal g = gamma(color.greenF()); qreal b = gamma(color.blueF()); a = color.alphaF(); // luma component y = lumag(r, g, b); // hue component qreal p = qMax(qMax(r, g), b); qreal n = qMin(qMin(r, g), b); qreal d = 6.0 * (p - n); if (n == p) { h = 0.0; } else if (r == p) { h = ((g - b) / d); } else if (g == p) { h = ((b - r) / d) + (1.0 / 3.0); } else { h = ((r - g) / d) + (2.0 / 3.0); } // chroma component if (r == g && g == b) { c = 0.0; } else { c = qMax((y - n) / y, (p - y) / (1 - y)); } } QColor HCYColorSpace::qColor() const { // start with sane component values qreal _h = ColorTools::wrap(h); qreal _c = ColorTools::normalize(c); qreal _y = ColorTools::normalize(y); // calculate some needed variables qreal _hs = _h * 6.0, th, tm; if (_hs < 1.0) { th = _hs; tm = yc[0] + yc[1] * th; } else if (_hs < 2.0) { th = 2.0 - _hs; tm = yc[1] + yc[0] * th; } else if (_hs < 3.0) { th = _hs - 2.0; tm = yc[1] + yc[2] * th; } else if (_hs < 4.0) { th = 4.0 - _hs; tm = yc[2] + yc[1] * th; } else if (_hs < 5.0) { th = _hs - 4.0; tm = yc[2] + yc[0] * th; } else { th = 6.0 - _hs; tm = yc[0] + yc[2] * th; } // calculate RGB channels in sorted order qreal tn, to, tp; if (tm >= _y) { tp = _y + _y * _c * (1.0 - tm) / tm; to = _y + _y * _c * (th - tm) / tm; tn = _y - (_y * _c); } else { tp = _y + (1.0 - _y) * _c; to = _y + (1.0 - _y) * _c * (th - tm) / (1.0 - tm); tn = _y - (1.0 - _y) * _c * tm / (1.0 - tm); } // return RGB channels in appropriate order if (_hs < 1.0) { return QColor::fromRgbF(igamma(tp), igamma(to), igamma(tn), a); } else if (_hs < 2.0) { return QColor::fromRgbF(igamma(to), igamma(tp), igamma(tn), a); } else if (_hs < 3.0) { return QColor::fromRgbF(igamma(tn), igamma(tp), igamma(to), a); } else if (_hs < 4.0) { return QColor::fromRgbF(igamma(tn), igamma(to), igamma(tp), a); } else if (_hs < 5.0) { return QColor::fromRgbF(igamma(to), igamma(tn), igamma(tp), a); } else { return QColor::fromRgbF(igamma(tp), igamma(tn), igamma(to), a); } } qreal HCYColorSpace::luma(const QColor& color) { return lumag(gamma(color.redF()), gamma(color.greenF()), gamma(color.blueF())); } // ------------------------------------------------------------------------------------- class Q_DECL_HIDDEN StateEffects { public: explicit StateEffects(QPalette::ColorGroup state, const KSharedConfigPtr&); ~StateEffects() { } QBrush brush(const QBrush& background) const; QBrush brush(const QBrush& foreground, const QBrush& background) const; private: enum Effects { // Effects Intensity = 0, Color = 1, Contrast = 2, // Intensity IntensityNoEffect = 0, IntensityShade = 1, IntensityDarken = 2, IntensityLighten = 3, // Color ColorNoEffect = 0, ColorDesaturate = 1, ColorFade = 2, ColorTint = 3, // Contrast ContrastNoEffect = 0, ContrastFade = 1, ContrastTint = 2 }; private: int _effects[3]; double _amount[3]; QColor _color; }; StateEffects::StateEffects(QPalette::ColorGroup state, const KSharedConfigPtr& config) : _color(0, 0, 0, 0) { QString group; if (state == QPalette::Disabled) { group = QLatin1String("ColorEffects:Disabled"); } else if (state == QPalette::Inactive) { group = QLatin1String("ColorEffects:Inactive"); } _effects[0] = 0; _effects[1] = 0; _effects[2] = 0; if (!group.isEmpty()) { KConfigGroup cfg(config, group); const bool enabledByDefault = (state == QPalette::Disabled); if (cfg.readEntry("Enable", enabledByDefault)) { _effects[Intensity] = cfg.readEntry("IntensityEffect", (int)((state == QPalette::Disabled) ? IntensityDarken : IntensityNoEffect)); _effects[Color] = cfg.readEntry("ColorEffect", (int)((state == QPalette::Disabled) ? ColorNoEffect : ColorDesaturate)); _effects[Contrast] = cfg.readEntry("ContrastEffect", (int)((state == QPalette::Disabled) ? ContrastFade : ContrastTint)); _amount[Intensity] = cfg.readEntry("IntensityAmount", (state == QPalette::Disabled) ? 0.10 : 0.0); _amount[Color] = cfg.readEntry("ColorAmount", (state == QPalette::Disabled) ? 0.0 : -0.9); _amount[Contrast] = cfg.readEntry("ContrastAmount", (state == QPalette::Disabled) ? 0.65 : 0.25); if (_effects[Color] > ColorNoEffect) { _color = cfg.readEntry("Color", (state == QPalette::Disabled) ? QColor(56, 56, 56) : QColor(112, 111, 110)); } } } } QBrush StateEffects::brush(const QBrush& background) const { QColor color = background.color(); // TODO - actually work on brushes switch (_effects[Intensity]) { case IntensityShade: color = ColorTools::shade(color, _amount[Intensity]); break; case IntensityDarken: color = ColorTools::darken(color, _amount[Intensity]); break; case IntensityLighten: color = ColorTools::lighten(color, _amount[Intensity]); break; } switch (_effects[Color]) { case ColorDesaturate: color = ColorTools::darken(color, 0.0, 1.0 - _amount[Color]); break; case ColorFade: color = ColorTools::mix(color, _color, _amount[Color]); break; case ColorTint: color = ColorTools::tint(color, _color, _amount[Color]); break; } return QBrush(color); } QBrush StateEffects::brush(const QBrush& foreground, const QBrush& background) const { QColor color = foreground.color(); QColor bg = background.color(); // Apply the foreground effects switch (_effects[Contrast]) { case ContrastFade: color = ColorTools::mix(color, bg, _amount[Contrast]); break; case ContrastTint: color = ColorTools::tint(color, bg, _amount[Contrast]); break; } // Now apply global effects return brush(color); } // ------------------------------------------------------------------------------------ struct SetDefaultColors { int NormalBackground[3]; int AlternateBackground[3]; int NormalText[3]; int InactiveText[3]; int ActiveText[3]; int LinkText[3]; int VisitedText[3]; int NegativeText[3]; int NeutralText[3]; int PositiveText[3]; }; struct DecoDefaultColors { int Hover[3]; int Focus[3]; }; // these numbers come from the Breeze color scheme static const SetDefaultColors defaultViewColors = { { 252, 252, 252 }, // Background { 239, 240, 241 }, // Alternate { 49, 54, 59 }, // Normal { 127, 140, 141 }, // Inactive { 61, 174, 233 }, // Active { 41, 128, 185 }, // Link { 127, 140, 141 }, // Visited { 218, 68, 83 }, // Negative { 246, 116, 0 }, // Neutral { 39, 174, 96 } // Positive }; static const SetDefaultColors defaultWindowColors = { { 239, 240, 241 }, // Background { 189, 195, 199 }, // Alternate { 49, 54, 59 }, // Normal { 127, 140, 141 }, // Inactive { 61, 174, 233 }, // Active { 41, 128, 185 }, // Link { 127, 140, 141 }, // Visited { 218, 68, 83 }, // Negative { 246, 116, 0 }, // Neutral { 39, 174, 96 } // Positive }; static const SetDefaultColors defaultButtonColors = { { 239, 240, 241 }, // Background { 189, 195, 199 }, // Alternate { 49, 54, 59 }, // Normal { 127, 140, 141 }, // Inactive { 61, 174, 233 }, // Active { 41, 128, 185 }, // Link { 127, 140, 141 }, // Visited { 218, 68, 83 }, // Negative { 246, 116, 0 }, // Neutral { 39, 174, 96 } // Positive }; static const SetDefaultColors defaultSelectionColors = { { 61, 174, 233 }, // Background { 29, 153, 243 }, // Alternate { 239, 240, 241 }, // Normal { 239, 240, 241 }, // Inactive { 252, 252, 252 }, // Active { 253, 188, 75 }, // Link { 189, 195, 199 }, // Visited { 218, 68, 83 }, // Negative { 246, 116, 0 }, // Neutral { 39, 174, 96 } // Positive }; static const SetDefaultColors defaultTooltipColors = { { 49, 54, 59 }, // Background { 77, 77, 77 }, // Alternate { 239, 240, 241 }, // Normal { 189, 195, 199 }, // Inactive { 61, 174, 233 }, // Active { 41, 128, 185 }, // Link { 127, 140, 141 }, // Visited { 218, 68, 83 }, // Negative { 246, 116, 0 }, // Neutral { 39, 174, 96 } // Positive }; static const SetDefaultColors defaultComplementaryColors = { { 49, 54, 59 }, // Background { 77, 77, 77 }, // Alternate { 239, 240, 241 }, // Normal { 189, 195, 199 }, // Inactive { 61, 174, 233 }, // Active { 41, 128, 185 }, // Link { 127, 140, 141 }, // Visited { 218, 68, 83 }, // Negative { 246, 116, 0 }, // Neutral { 39, 174, 96 } // Positive }; static const DecoDefaultColors defaultDecorationColors = { { 147, 206, 233 }, // Hover { 61, 174, 233 }, // Focus }; // ------------------------------------------------------------------------------------ class Q_DECL_HIDDEN SchemeManagerPrivate : public QSharedData { public: explicit SchemeManagerPrivate(const KSharedConfigPtr&, QPalette::ColorGroup, const char*, SetDefaultColors); explicit SchemeManagerPrivate(const KSharedConfigPtr&, QPalette::ColorGroup, const char*, SetDefaultColors, const QBrush&); ~SchemeManagerPrivate() { } QBrush background(SchemeManager::BackgroundRole) const; QBrush foreground(SchemeManager::ForegroundRole) const; QBrush decoration(SchemeManager::DecorationRole) const; qreal contrast() const; private: void init(const KSharedConfigPtr&, QPalette::ColorGroup, const char*, SetDefaultColors); private: struct { QBrush fg[8], bg[8], deco[2]; } _brushes; qreal _contrast; }; #define DEFAULT(c) QColor( c[0], c[1], c[2] ) #define SET_DEFAULT(a) DEFAULT( defaults.a ) #define DECO_DEFAULT(a) DEFAULT( defaultDecorationColors.a ) SchemeManagerPrivate::SchemeManagerPrivate(const KSharedConfigPtr& config, QPalette::ColorGroup state, const char* group, SetDefaultColors defaults) { KConfigGroup cfg(config, group); _contrast = SchemeManager::contrastF(config); // loaded-from-config colors (no adjustment) _brushes.bg[0] = cfg.readEntry("BackgroundNormal", SET_DEFAULT(NormalBackground)); _brushes.bg[1] = cfg.readEntry("BackgroundAlternate", SET_DEFAULT(AlternateBackground)); // the rest init(config, state, group, defaults); } SchemeManagerPrivate::SchemeManagerPrivate(const KSharedConfigPtr& config, QPalette::ColorGroup state, const char* group, SetDefaultColors defaults, const QBrush& tint) { KConfigGroup cfg(config, group); _contrast = SchemeManager::contrastF(config); // loaded-from-config colors _brushes.bg[0] = cfg.readEntry("BackgroundNormal", SET_DEFAULT(NormalBackground)); _brushes.bg[1] = cfg.readEntry("BackgroundAlternate", SET_DEFAULT(AlternateBackground)); // adjustment _brushes.bg[0] = ColorTools::tint(_brushes.bg[0].color(), tint.color(), 0.4); _brushes.bg[1] = ColorTools::tint(_brushes.bg[1].color(), tint.color(), 0.4); // the rest init(config, state, group, defaults); } void SchemeManagerPrivate::init(const KSharedConfigPtr& config, QPalette::ColorGroup state, const char* group, SetDefaultColors defaults) { KConfigGroup cfg(config, group); // loaded-from-config colors _brushes.fg[0] = cfg.readEntry("ForegroundNormal", SET_DEFAULT(NormalText)); _brushes.fg[1] = cfg.readEntry("ForegroundInactive", SET_DEFAULT(InactiveText)); _brushes.fg[2] = cfg.readEntry("ForegroundActive", SET_DEFAULT(ActiveText)); _brushes.fg[3] = cfg.readEntry("ForegroundLink", SET_DEFAULT(LinkText)); _brushes.fg[4] = cfg.readEntry("ForegroundVisited", SET_DEFAULT(VisitedText)); _brushes.fg[5] = cfg.readEntry("ForegroundNegative", SET_DEFAULT(NegativeText)); _brushes.fg[6] = cfg.readEntry("ForegroundNeutral", SET_DEFAULT(NeutralText)); _brushes.fg[7] = cfg.readEntry("ForegroundPositive", SET_DEFAULT(PositiveText)); _brushes.deco[0] = cfg.readEntry("DecorationHover", DECO_DEFAULT(Hover)); _brushes.deco[1] = cfg.readEntry("DecorationFocus", DECO_DEFAULT(Focus)); // apply state adjustments if (state != QPalette::Active) { StateEffects effects(state, config); - for (int i = 0; i < 8; i++) + for (int i = 0 ; i < 8 ; ++i) { _brushes.fg[i] = effects.brush(_brushes.fg[i], _brushes.bg[0]); } _brushes.deco[0] = effects.brush(_brushes.deco[0], _brushes.bg[0]); _brushes.deco[1] = effects.brush(_brushes.deco[1], _brushes.bg[0]); _brushes.bg[0] = effects.brush(_brushes.bg[0]); _brushes.bg[1] = effects.brush(_brushes.bg[1]); } // calculated backgrounds _brushes.bg[2] = ColorTools::tint(_brushes.bg[0].color(), _brushes.fg[2].color()); _brushes.bg[3] = ColorTools::tint(_brushes.bg[0].color(), _brushes.fg[3].color()); _brushes.bg[4] = ColorTools::tint(_brushes.bg[0].color(), _brushes.fg[4].color()); _brushes.bg[5] = ColorTools::tint(_brushes.bg[0].color(), _brushes.fg[5].color()); _brushes.bg[6] = ColorTools::tint(_brushes.bg[0].color(), _brushes.fg[6].color()); _brushes.bg[7] = ColorTools::tint(_brushes.bg[0].color(), _brushes.fg[7].color()); } QBrush SchemeManagerPrivate::background(SchemeManager::BackgroundRole role) const { switch (role) { case SchemeManager::AlternateBackground: return _brushes.bg[1]; case SchemeManager::ActiveBackground: return _brushes.bg[2]; case SchemeManager::LinkBackground: return _brushes.bg[3]; case SchemeManager::VisitedBackground: return _brushes.bg[4]; case SchemeManager::NegativeBackground: return _brushes.bg[5]; case SchemeManager::NeutralBackground: return _brushes.bg[6]; case SchemeManager::PositiveBackground: return _brushes.bg[7]; default: return _brushes.bg[0]; } } QBrush SchemeManagerPrivate::foreground(SchemeManager::ForegroundRole role) const { switch (role) { case SchemeManager::InactiveText: return _brushes.fg[1]; case SchemeManager::ActiveText: return _brushes.fg[2]; case SchemeManager::LinkText: return _brushes.fg[3]; case SchemeManager::VisitedText: return _brushes.fg[4]; case SchemeManager::NegativeText: return _brushes.fg[5]; case SchemeManager::NeutralText: return _brushes.fg[6]; case SchemeManager::PositiveText: return _brushes.fg[7]; default: return _brushes.fg[0]; } } QBrush SchemeManagerPrivate::decoration(SchemeManager::DecorationRole role) const { switch (role) { case SchemeManager::FocusColor: return _brushes.deco[1]; default: return _brushes.deco[0]; } } qreal SchemeManagerPrivate::contrast() const { return _contrast; } // ------------------------------------------------------------------------------------ SchemeManager::SchemeManager(const SchemeManager& other) : d(other.d) { } SchemeManager& SchemeManager::operator=(const SchemeManager& other) { d = other.d; return *this; } SchemeManager::~SchemeManager() { } SchemeManager::SchemeManager(QPalette::ColorGroup state, ColorSet set, KSharedConfigPtr config) { if (!config) { config = KSharedConfig::openConfig(); } switch (set) { case Window: d = new SchemeManagerPrivate(config, state, "Colors:Window", defaultWindowColors); break; case Button: d = new SchemeManagerPrivate(config, state, "Colors:Button", defaultButtonColors); break; case Selection: { KConfigGroup group(config, "ColorEffects:Inactive"); // NOTE: keep this in sync with kdebase/workspace/kcontrol/colors/colorscm.cpp bool inactiveSelectionEffect = group.readEntry("ChangeSelectionColor", group.readEntry("Enable", true)); // if enabled, inactiver/disabled uses Window colors instead, ala gtk // ...except tinted with the Selection:NormalBackground color so it looks more like selection if (state == QPalette::Active || (state == QPalette::Inactive && !inactiveSelectionEffect)) { d = new SchemeManagerPrivate(config, state, "Colors:Selection", defaultSelectionColors); } else if (state == QPalette::Inactive) { d = new SchemeManagerPrivate(config, state, "Colors:Window", defaultWindowColors, SchemeManager(QPalette::Active, Selection, config).background()); } else { // disabled (...and still want this branch when inactive+disabled exists) d = new SchemeManagerPrivate(config, state, "Colors:Window", defaultWindowColors); } } break; case Tooltip: d = new SchemeManagerPrivate(config, state, "Colors:Tooltip", defaultTooltipColors); break; case Complementary: d = new SchemeManagerPrivate(config, state, "Colors:Complementary", defaultComplementaryColors); break; default: d = new SchemeManagerPrivate(config, state, "Colors:View", defaultViewColors); } } int SchemeManager::contrast() { KConfigGroup g(KSharedConfig::openConfig(), "KDE"); return g.readEntry("contrast", 7); } qreal SchemeManager::contrastF(const KSharedConfigPtr& config) { if (config) { KConfigGroup g(config, "KDE"); return 0.1 * g.readEntry("contrast", 7); } return 0.1 * (qreal)contrast(); } QBrush SchemeManager::background(BackgroundRole role) const { return d->background(role); } QBrush SchemeManager::foreground(ForegroundRole role) const { return d->foreground(role); } QBrush SchemeManager::decoration(DecorationRole role) const { return d->decoration(role); } QColor SchemeManager::shade(ShadeRole role) const { return shade(background().color(), role, d->contrast()); } QColor SchemeManager::shade(const QColor& color, ShadeRole role) { return shade(color, role, SchemeManager::contrastF()); } QColor SchemeManager::shade(const QColor& color, ShadeRole role, qreal contrast, qreal chromaAdjust) { // nan -> 1.0 contrast = ((1.0 > contrast) ? ((-1.0 < contrast) ? contrast : -1.0) : 1.0); qreal y = ColorTools::luma(color); qreal yi = 1.0 - y; // handle very dark colors (base, mid, dark, shadow == midlight, light) if (y < 0.006) { switch (role) { case SchemeManager::LightShade: return ColorTools::shade(color, 0.05 + 0.95 * contrast, chromaAdjust); case SchemeManager::MidShade: return ColorTools::shade(color, 0.01 + 0.20 * contrast, chromaAdjust); case SchemeManager::DarkShade: return ColorTools::shade(color, 0.02 + 0.40 * contrast, chromaAdjust); default: return ColorTools::shade(color, 0.03 + 0.60 * contrast, chromaAdjust); } } // handle very light colors (base, midlight, light == mid, dark, shadow) if (y > 0.93) { switch (role) { case SchemeManager::MidlightShade: return ColorTools::shade(color, -0.02 - 0.20 * contrast, chromaAdjust); case SchemeManager::DarkShade: return ColorTools::shade(color, -0.06 - 0.60 * contrast, chromaAdjust); case SchemeManager::ShadowShade: return ColorTools::shade(color, -0.10 - 0.90 * contrast, chromaAdjust); default: return ColorTools::shade(color, -0.04 - 0.40 * contrast, chromaAdjust); } } // handle everything else qreal lightAmount = (0.05 + y * 0.55) * (0.25 + contrast * 0.75); qreal darkAmount = (- y) * (0.55 + contrast * 0.35); switch (role) { case SchemeManager::LightShade: return ColorTools::shade(color, lightAmount, chromaAdjust); case SchemeManager::MidlightShade: return ColorTools::shade(color, (0.15 + 0.35 * yi) * lightAmount, chromaAdjust); case SchemeManager::MidShade: return ColorTools::shade(color, (0.35 + 0.15 * y) * darkAmount, chromaAdjust); case SchemeManager::DarkShade: return ColorTools::shade(color, darkAmount, chromaAdjust); default: return ColorTools::darken(ColorTools::shade(color, darkAmount, chromaAdjust), 0.5 + 0.3 * y); } } void SchemeManager::adjustBackground(QPalette& palette, BackgroundRole newRole, QPalette::ColorRole color, ColorSet set, KSharedConfigPtr config) { palette.setBrush(QPalette::Active, color, SchemeManager(QPalette::Active, set, config).background(newRole)); palette.setBrush(QPalette::Inactive, color, SchemeManager(QPalette::Inactive, set, config).background(newRole)); palette.setBrush(QPalette::Disabled, color, SchemeManager(QPalette::Disabled, set, config).background(newRole)); } void SchemeManager::adjustForeground(QPalette& palette, ForegroundRole newRole, QPalette::ColorRole color, ColorSet set, KSharedConfigPtr config) { palette.setBrush(QPalette::Active, color, SchemeManager(QPalette::Active, set, config).foreground(newRole)); palette.setBrush(QPalette::Inactive, color, SchemeManager(QPalette::Inactive, set, config).foreground(newRole)); palette.setBrush(QPalette::Disabled, color, SchemeManager(QPalette::Disabled, set, config).foreground(newRole)); } QPalette SchemeManager::createApplicationPalette(const KSharedConfigPtr& config) { QPalette palette; static const QPalette::ColorGroup states[3] = { QPalette::Active, QPalette::Inactive, QPalette::Disabled }; // TT thinks tooltips shouldn't use active, so we use our active colors for all states SchemeManager schemeTooltip(QPalette::Active, SchemeManager::Tooltip, config); - for (int i = 0; i < 3; i++) + for (int i = 0 ; i < 3 ; ++i) { QPalette::ColorGroup state = states[i]; SchemeManager schemeView(state, SchemeManager::View, config); SchemeManager schemeWindow(state, SchemeManager::Window, config); SchemeManager schemeButton(state, SchemeManager::Button, config); SchemeManager schemeSelection(state, SchemeManager::Selection, config); palette.setBrush(state, QPalette::WindowText, schemeWindow.foreground()); palette.setBrush(state, QPalette::Window, schemeWindow.background()); palette.setBrush(state, QPalette::Base, schemeView.background()); palette.setBrush(state, QPalette::Text, schemeView.foreground()); palette.setBrush(state, QPalette::Button, schemeButton.background()); palette.setBrush(state, QPalette::ButtonText, schemeButton.foreground()); palette.setBrush(state, QPalette::Highlight, schemeSelection.background()); palette.setBrush(state, QPalette::HighlightedText, schemeSelection.foreground()); palette.setBrush(state, QPalette::ToolTipBase, schemeTooltip.background()); palette.setBrush(state, QPalette::ToolTipText, schemeTooltip.foreground()); palette.setColor(state, QPalette::Light, schemeWindow.shade(SchemeManager::LightShade)); palette.setColor(state, QPalette::Midlight, schemeWindow.shade(SchemeManager::MidlightShade)); palette.setColor(state, QPalette::Mid, schemeWindow.shade(SchemeManager::MidShade)); palette.setColor(state, QPalette::Dark, schemeWindow.shade(SchemeManager::DarkShade)); palette.setColor(state, QPalette::Shadow, schemeWindow.shade(SchemeManager::ShadowShade)); palette.setBrush(state, QPalette::AlternateBase, schemeView.background(SchemeManager::AlternateBackground)); palette.setBrush(state, QPalette::Link, schemeView.foreground(SchemeManager::LinkText)); palette.setBrush(state, QPalette::LinkVisited, schemeView.foreground(SchemeManager::VisitedText)); } return palette; } } // namespace Digikam diff --git a/core/libs/widgets/mainview/sidebar.cpp b/core/libs/widgets/mainview/sidebar.cpp index 08f66bfe3b..475386bae9 100644 --- a/core/libs/widgets/mainview/sidebar.cpp +++ b/core/libs/widgets/mainview/sidebar.cpp @@ -1,1344 +1,1344 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2005-03-22 * Description : a widget to manage sidebar in GUI. * * Copyright (C) 2005-2006 by Joern Ahrens * Copyright (C) 2006-2018 by Gilles Caulier * Copyright (C) 2008-2011 by Marcel Wiesweg * Copyright (C) 2001-2003 by Joseph Wenninger * * 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 "sidebar.h" #include // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Local includes #include "digikam_debug.h" namespace Digikam { class Q_DECL_HIDDEN DMultiTabBarFrame::Private { public: QBoxLayout* mainLayout; QList tabs; Qt::Edge position; DMultiTabBar::TextStyle style; }; DMultiTabBarFrame::DMultiTabBarFrame(QWidget* const parent, Qt::Edge pos) : QFrame(parent), d(new Private) { d->position = pos; if (pos == Qt::LeftEdge || pos == Qt::RightEdge) d->mainLayout = new QVBoxLayout(this); else d->mainLayout = new QHBoxLayout(this); d->mainLayout->setContentsMargins(QMargins()); d->mainLayout->setSpacing(0); d->mainLayout->addStretch(); setFrameStyle(NoFrame); setBackgroundRole(QPalette::Background); } DMultiTabBarFrame::~DMultiTabBarFrame() { qDeleteAll(d->tabs); d->tabs.clear(); delete d; } void DMultiTabBarFrame::setStyle(DMultiTabBar::TextStyle style) { d->style = style; - for (int i = 0 ; i < d->tabs.count() ; i++) + for (int i = 0 ; i < d->tabs.count() ; ++i) d->tabs.at(i)->setStyle(d->style); updateGeometry(); } void DMultiTabBarFrame::contentsMousePressEvent(QMouseEvent* e) { e->ignore(); } void DMultiTabBarFrame::mousePressEvent(QMouseEvent* e) { e->ignore(); } DMultiTabBarTab* DMultiTabBarFrame::tab(int id) const { QListIterator it(d->tabs); while (it.hasNext()) { DMultiTabBarTab* const tab = it.next(); if (tab->id() == id) return tab; } return 0; } int DMultiTabBarFrame::appendTab(const QPixmap& pic, int id, const QString& text) { DMultiTabBarTab* const tab = new DMultiTabBarTab(pic, text, id, this, d->position, d->style); d->tabs.append(tab); // Insert before the stretch. d->mainLayout->insertWidget(d->tabs.size()-1, tab); tab->show(); return 0; } void DMultiTabBarFrame::removeTab(int id) { - for (int pos = 0 ; pos < d->tabs.count() ; pos++) + for (int pos = 0 ; pos < d->tabs.count() ; ++pos) { if (d->tabs.at(pos)->id() == id) { // remove & delete the tab delete d->tabs.takeAt(pos); break; } } } void DMultiTabBarFrame::setPosition(Qt::Edge pos) { d->position = pos; - for (int i = 0 ; i < d->tabs.count() ; i++) + for (int i = 0 ; i < d->tabs.count() ; ++i) d->tabs.at(i)->setPosition(d->position); updateGeometry(); } QList* DMultiTabBarFrame::tabs() { return &d->tabs; } // ------------------------------------------------------------------------------------- DMultiTabBarButton::DMultiTabBarButton(const QPixmap& pic, const QString& text, int id, QWidget* const parent) : QPushButton(QIcon(pic), text, parent), m_id(id) { connect(this, SIGNAL(clicked()), this, SLOT(slotClicked())); // we can't see the focus, so don't take focus. #45557 // If keyboard navigation is wanted, then only the bar should take focus, // and arrows could change the focused button; but generally, tabbars don't take focus anyway. setFocusPolicy(Qt::NoFocus); // See RB #128005 setAttribute(Qt::WA_LayoutUsesWidgetRect); } DMultiTabBarButton::~DMultiTabBarButton() { } void DMultiTabBarButton::setText(const QString& text) { QPushButton::setText(text); } void DMultiTabBarButton::slotClicked() { updateGeometry(); emit clicked(m_id); } int DMultiTabBarButton::id() const { return m_id; } void DMultiTabBarButton::hideEvent(QHideEvent* e) { QPushButton::hideEvent(e); DMultiTabBar* const tb = dynamic_cast(parentWidget()); if (tb) tb->updateSeparator(); } void DMultiTabBarButton::showEvent(QShowEvent* e) { QPushButton::showEvent(e); DMultiTabBar* const tb = dynamic_cast(parentWidget()); if (tb) tb->updateSeparator(); } void DMultiTabBarButton::paintEvent(QPaintEvent*) { QStyleOptionButton opt; opt.initFrom(this); opt.icon = icon(); opt.iconSize = iconSize(); // removes the QStyleOptionButton::HasMenu ButtonFeature opt.features = QStyleOptionButton::Flat; QPainter painter(this); style()->drawControl(QStyle::CE_PushButton, &opt, &painter, this); } // ------------------------------------------------------------------------------------- class Q_DECL_HIDDEN DMultiTabBarTab::Private { public: Qt::Edge position; DMultiTabBar::TextStyle style; }; DMultiTabBarTab::DMultiTabBarTab(const QPixmap& pic, const QString& text, int id, QWidget* const parent, Qt::Edge pos, DMultiTabBar::TextStyle style) : DMultiTabBarButton(pic, text, id, parent), d(new Private) { d->style = style; d->position = pos; setToolTip(text); setCheckable(true); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); // shrink down to icon only, but prefer to show text if it's there } DMultiTabBarTab::~DMultiTabBarTab() { delete d; } void DMultiTabBarTab::setPosition(Qt::Edge pos) { d->position = pos; updateGeometry(); } void DMultiTabBarTab::setStyle(DMultiTabBar::TextStyle style) { d->style = style; updateGeometry(); } QPixmap DMultiTabBarTab::iconPixmap() const { int iconSize = style()->pixelMetric(QStyle::PM_SmallIconSize, 0, this); return icon().pixmap(iconSize); } void DMultiTabBarTab::initStyleOption(QStyleOptionToolButton* opt) const { opt->initFrom(this); // Setup icon.. if (!icon().isNull()) { opt->iconSize = iconPixmap().size(); opt->icon = icon(); } // Should we draw text? if (shouldDrawText()) opt->text = text(); if (underMouse()) opt->state |= QStyle::State_AutoRaise | QStyle::State_MouseOver | QStyle::State_Raised; if (isChecked()) opt->state |= QStyle::State_Sunken | QStyle::State_On; opt->font = font(); opt->toolButtonStyle = shouldDrawText() ? Qt::ToolButtonTextBesideIcon : Qt::ToolButtonIconOnly; opt->subControls = QStyle::SC_ToolButton; } QSize DMultiTabBarTab::sizeHint() const { return computeSizeHint(shouldDrawText()); } QSize DMultiTabBarTab::minimumSizeHint() const { return computeSizeHint(false); } void DMultiTabBarTab::computeMargins(int* hMargin, int* vMargin) const { // Unfortunately, QStyle does not give us enough information to figure out // where to place things, so we try to reverse-engineer it QStyleOptionToolButton opt; initStyleOption(&opt); QPixmap iconPix = iconPixmap(); QSize trialSize = iconPix.size(); QSize expandSize = style()->sizeFromContents(QStyle::CT_ToolButton, &opt, trialSize, this); *hMargin = (expandSize.width() - trialSize.width())/2; *vMargin = (expandSize.height() - trialSize.height())/2; } QSize DMultiTabBarTab::computeSizeHint(bool withText) const { // Compute as horizontal first, then flip around if need be. QStyleOptionToolButton opt; initStyleOption(&opt); int hMargin, vMargin; computeMargins(&hMargin, &vMargin); // Compute interior size, starting from pixmap.. QPixmap iconPix = iconPixmap(); QSize size = iconPix.size(); // Always include text height in computation, to avoid resizing the minor direction // when expanding text.. QSize textSize = fontMetrics().size(0, text()); size.setHeight(qMax(size.height(), textSize.height())); // Pick margins for major/minor direction, depending on orientation int majorMargin = isVertical() ? vMargin : hMargin; int minorMargin = isVertical() ? hMargin : vMargin; size.setWidth (size.width() + 2*majorMargin); size.setHeight(size.height() + 2*minorMargin); if (withText) { // Add enough room for the text, and an extra major margin. size.setWidth(size.width() + textSize.width() + majorMargin); } if (isVertical()) { return QSize(size.height(), size.width()); } return size; } void DMultiTabBarTab::setState(bool newState) { setChecked(newState); updateGeometry(); } void DMultiTabBarTab::setIcon(const QString& icon) { const QIcon i = QIcon::fromTheme(icon); const int iconSize = style()->pixelMetric(QStyle::PM_SmallIconSize, 0, this); setIcon(i.pixmap(iconSize)); } void DMultiTabBarTab::setIcon(const QPixmap& icon) { QPushButton::setIcon(icon); } bool DMultiTabBarTab::shouldDrawText() const { return (d->style == DMultiTabBar::AllIconsText) || isChecked(); } bool DMultiTabBarTab::isVertical() const { return (d->position == Qt::RightEdge || d->position == Qt::LeftEdge); } void DMultiTabBarTab::paintEvent(QPaintEvent*) { QPainter painter(this); QStyleOptionToolButton opt; initStyleOption(&opt); // Paint bevel.. if (underMouse() || isChecked()) { opt.text.clear(); opt.icon = QIcon(); style()->drawComplexControl(QStyle::CC_ToolButton, &opt, &painter, this); } int hMargin, vMargin; computeMargins(&hMargin, &vMargin); // We first figure out how much room we have for the text, based on // icon size and margin, try to fit in by eliding, and perhaps // give up on drawing the text entirely if we're too short on room QPixmap icon = iconPixmap(); int textRoom = 0; int iconRoom = 0; QString t; if (shouldDrawText()) { if (isVertical()) { iconRoom = icon.height() + 2*vMargin; textRoom = height() - iconRoom - vMargin; } else { iconRoom = icon.width() + 2*hMargin; textRoom = width() - iconRoom - hMargin; } t = painter.fontMetrics().elidedText(text(), Qt::ElideRight, textRoom); // See whether anything is left. Qt will return either // ... or the ellipsis unicode character, 0x2026 if (t == QLatin1String("...") || t == QChar(0x2026)) { t.clear(); } } // Label time.... Simple case: no text, so just plop down the icon right in the center // We only do this when the button never draws the text, to avoid jumps in icon position // when resizing if (!shouldDrawText()) { style()->drawItemPixmap(&painter, rect(), Qt::AlignCenter | Qt::AlignVCenter, icon); return; } // Now where the icon/text goes depends on text direction and tab position QRect iconArea; QRect labelArea; bool bottomIcon = false; bool rtl = layoutDirection() == Qt::RightToLeft; if (isVertical()) { if (d->position == Qt::LeftEdge && !rtl) bottomIcon = true; if (d->position == Qt::RightEdge && rtl) bottomIcon = true; } if (isVertical()) { if (bottomIcon) { labelArea = QRect(0, vMargin, width(), textRoom); iconArea = QRect(0, vMargin + textRoom, width(), iconRoom); } else { labelArea = QRect(0, iconRoom, width(), textRoom); iconArea = QRect(0, 0, width(), iconRoom); } } else { // Pretty simple --- depends only on RTL/LTR if (rtl) { labelArea = QRect(hMargin, 0, textRoom, height()); iconArea = QRect(hMargin + textRoom, 0, iconRoom, height()); } else { labelArea = QRect(iconRoom, 0, textRoom, height()); iconArea = QRect(0, 0, iconRoom, height()); } } style()->drawItemPixmap(&painter, iconArea, Qt::AlignCenter | Qt::AlignVCenter, icon); if (t.isEmpty()) { return; } QRect labelPaintArea = labelArea; if (isVertical()) { // If we're vertical, we paint to a simple 0,0 origin rect, // and get the transformations to get us in the right place labelPaintArea = QRect(0, 0, labelArea.height(), labelArea.width()); QTransform tr; if (bottomIcon) { tr.translate(labelArea.x(), labelPaintArea.width() + labelArea.y()); tr.rotate(-90); } else { tr.translate(labelPaintArea.height() + labelArea.x(), labelArea.y()); tr.rotate(90); } painter.setTransform(tr); } style()->drawItemText(&painter, labelPaintArea, Qt::AlignLeading | Qt::AlignVCenter, palette(), true, t, QPalette::ButtonText); } // ------------------------------------------------------------------------------------- class Q_DECL_HIDDEN DMultiTabBar::Private { public: DMultiTabBarFrame* internal; QBoxLayout* layout; QFrame* btnTabSep; QList buttons; Qt::Edge position; }; DMultiTabBar::DMultiTabBar(Qt::Edge pos, QWidget* const parent) : QWidget(parent), d(new Private) { if (pos == Qt::LeftEdge || pos == Qt::RightEdge) { d->layout = new QVBoxLayout(this); setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); } else { d->layout = new QHBoxLayout(this); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); } d->layout->setContentsMargins(QMargins()); d->layout->setSpacing(0); d->internal = new DMultiTabBarFrame(this, pos); setPosition(pos); setStyle(ActiveIconText); d->layout->insertWidget(0, d->internal); d->layout->insertWidget(0, d->btnTabSep = new QFrame(this)); d->btnTabSep->setFixedHeight(4); d->btnTabSep->setFrameStyle(QFrame::Panel | QFrame::Sunken); d->btnTabSep->setLineWidth(2); d->btnTabSep->hide(); updateGeometry(); } DMultiTabBar::~DMultiTabBar() { qDeleteAll(d->buttons); d->buttons.clear(); delete d; } int DMultiTabBar::appendButton(const QPixmap &pic, int id, QMenu *popup, const QString&) { DMultiTabBarButton* const btn = new DMultiTabBarButton(pic, QString(), id, this); // a button with a QMenu can have another size. Make sure the button has always the same size. btn->setFixedWidth(btn->height()); btn->setMenu(popup); d->buttons.append(btn); d->layout->insertWidget(0,btn); btn->show(); d->btnTabSep->show(); return 0; } void DMultiTabBar::updateSeparator() { bool hideSep = true; QListIterator it(d->buttons); while (it.hasNext()) { if (it.next()->isVisibleTo(this)) { hideSep = false; break; } } if (hideSep) d->btnTabSep->hide(); else d->btnTabSep->show(); } int DMultiTabBar::appendTab(const QPixmap& pic, int id, const QString& text) { d->internal->appendTab(pic,id,text); return 0; } DMultiTabBarButton* DMultiTabBar::button(int id) const { QListIterator it(d->buttons); while (it.hasNext()) { DMultiTabBarButton* const button = it.next(); if (button->id() == id) return button; } return 0; } DMultiTabBarTab* DMultiTabBar::tab(int id) const { return d->internal->tab(id); } void DMultiTabBar::removeButton(int id) { - for (int pos = 0 ; pos < d->buttons.count() ; pos++) + for (int pos = 0 ; pos < d->buttons.count() ; ++pos) { if (d->buttons.at(pos)->id() == id) { d->buttons.takeAt(pos)->deleteLater(); break; } } if (d->buttons.count() == 0) d->btnTabSep->hide(); } void DMultiTabBar::removeTab(int id) { d->internal->removeTab(id); } void DMultiTabBar::setTab(int id,bool state) { DMultiTabBarTab* const ttab = tab(id); if (ttab) ttab->setState(state); } bool DMultiTabBar::isTabRaised(int id) const { DMultiTabBarTab* const ttab = tab(id); if (ttab) return ttab->isChecked(); return false; } void DMultiTabBar::setStyle(TextStyle style) { d->internal->setStyle(style); } DMultiTabBar::TextStyle DMultiTabBar::tabStyle() const { return d->internal->d->style; } void DMultiTabBar::setPosition(Qt::Edge pos) { d->position = pos; d->internal->setPosition(pos); } Qt::Edge DMultiTabBar::position() const { return d->position; } void DMultiTabBar::fontChange(const QFont&) { updateGeometry(); } // ------------------------------------------------------------------------------------- class Q_DECL_HIDDEN SidebarState { public: SidebarState() : activeWidget(0), size(0) { } SidebarState(QWidget* const w, int size) : activeWidget(w), size(size) { } QWidget* activeWidget; int size; }; // ------------------------------------------------------------------------------------- class Q_DECL_HIDDEN Sidebar::Private { public: explicit Private() : minimizedDefault(false), minimized(false), isMinimized(false), tabs(0), activeTab(-1), dragSwitchId(-1), restoreSize(0), stack(0), splitter(0), dragSwitchTimer(0), appendedTabsStateCache(), optionActiveTabEntry(QLatin1String("ActiveTab")), optionMinimizedEntry(QLatin1String("Minimized")), optionRestoreSizeEntry(QLatin1String("RestoreSize")) { } bool minimizedDefault; bool minimized; bool isMinimized; // Backup of shrinked status before backup(), restored by restore() // NOTE: when sidebar is hidden, only icon bar is affected. If sidebar view is // visible, this one must be shrink and restored accordingly. int tabs; int activeTab; int dragSwitchId; int restoreSize; QStackedWidget* stack; SidebarSplitter* splitter; QTimer* dragSwitchTimer; QHash appendedTabsStateCache; const QString optionActiveTabEntry; const QString optionMinimizedEntry; const QString optionRestoreSizeEntry; }; class Q_DECL_HIDDEN SidebarSplitter::Private { public: QList sidebars; }; // ------------------------------------------------------------------------------------- Sidebar::Sidebar(QWidget* const parent, SidebarSplitter* const sp, Qt::Edge side, bool minimizedDefault) : DMultiTabBar(side, parent), StateSavingObject(this), d(new Private) { d->splitter = sp; d->minimizedDefault = minimizedDefault; d->stack = new QStackedWidget(d->splitter); d->dragSwitchTimer = new QTimer(this); connect(d->dragSwitchTimer, SIGNAL(timeout()), this, SLOT(slotDragSwitchTimer())); d->splitter->d->sidebars << this; setStyle(DMultiTabBar::ActiveIconText); } Sidebar::~Sidebar() { saveState(); if (d->splitter) { d->splitter->d->sidebars.removeAll(this); } delete d; } SidebarSplitter* Sidebar::splitter() const { return d->splitter; } void Sidebar::doLoadState() { KConfigGroup group = getConfigGroup(); int tab = group.readEntry(entryName(d->optionActiveTabEntry), 0); bool minimized = group.readEntry(entryName(d->optionMinimizedEntry), d->minimizedDefault); d->restoreSize = group.readEntry(entryName(d->optionRestoreSizeEntry), -1); // validate if (tab >= d->tabs || tab < 0) { tab = 0; } if (minimized) { d->activeTab = tab; setTab(d->activeTab, false); d->stack->setCurrentIndex(d->activeTab); shrink(); emit signalChangedTab(d->stack->currentWidget()); return; } d->activeTab = -1; clicked(tab); } void Sidebar::doSaveState() { KConfigGroup group = getConfigGroup(); group.writeEntry(entryName(d->optionActiveTabEntry), d->activeTab); group.writeEntry(entryName(d->optionMinimizedEntry), d->minimized); group.writeEntry(entryName(d->optionRestoreSizeEntry), d->minimized ? d->restoreSize : -1); } void Sidebar::backup() { // backup preview state of sidebar view (shrink or not) d->isMinimized = d->minimized; // In all case, shrink sidebar view shrink(); DMultiTabBar::hide(); } void Sidebar::backup(const QList thirdWidgetsToBackup, QList* const sizes) { sizes->clear(); foreach(QWidget* const widget, thirdWidgetsToBackup) { *sizes << d->splitter->size(widget); } backup(); } void Sidebar::restore() { DMultiTabBar::show(); // restore preview state of sidebar view, stored in backup() if (!d->isMinimized) { expand(); } } void Sidebar::restore(const QList thirdWidgetsToRestore, const QList& sizes) { restore(); if (thirdWidgetsToRestore.size() == sizes.size()) { for (int i=0; isplitter->setSize(thirdWidgetsToRestore.at(i), sizes.at(i)); } } } void Sidebar::appendTab(QWidget* const w, const QIcon& pic, const QString& title) { // Store state (but not on initialization) if (isVisible()) { d->appendedTabsStateCache[w] = SidebarState(d->stack->currentWidget(), d->splitter->size(this)); } // Add tab w->setParent(d->stack); DMultiTabBar::appendTab(pic.pixmap(style()->pixelMetric(QStyle::PM_SmallIconSize)), d->tabs, title); d->stack->insertWidget(d->tabs, w); tab(d->tabs)->setAcceptDrops(true); tab(d->tabs)->installEventFilter(this); connect(tab(d->tabs), SIGNAL(clicked(int)), this, SLOT(clicked(int))); d->tabs++; } void Sidebar::deleteTab(QWidget* const w) { int tab = d->stack->indexOf(w); if (tab < 0) { return; } bool removingActiveTab = (tab == d->activeTab); if (removingActiveTab) { d->activeTab = -1; } d->stack->removeWidget(d->stack->widget(tab)); // delete widget removeTab(tab); d->tabs--; // restore or reset active tab and width if (!d->minimized) { // restore to state before adding tab // using a hash is simple, but does not handle well multiple add/removal operations at a time SidebarState state = d->appendedTabsStateCache.take(w); if (state.activeWidget) { int tab = d->stack->indexOf(state.activeWidget); if (tab != -1) { switchTabAndStackToTab(tab); emit signalChangedTab(d->stack->currentWidget()); if (state.size == 0) { d->minimized = true; setTab(d->activeTab, false); } d->splitter->setSize(this, state.size); } } else { if (removingActiveTab) { clicked(d->tabs - 1); } d->splitter->setSize(this, -1); } } else { d->restoreSize = -1; } } void Sidebar::clicked(int tab) { if (tab >= d->tabs || tab < 0) { return; } if (tab == d->activeTab) { d->stack->isHidden() ? expand() : shrink(); } else { switchTabAndStackToTab(tab); if (d->minimized) { expand(); } emit signalChangedTab(d->stack->currentWidget()); } } void Sidebar::setActiveTab(QWidget* const w) { int tab = d->stack->indexOf(w); if (tab < 0) { return; } switchTabAndStackToTab(tab); if (d->minimized) { expand(); } emit signalChangedTab(d->stack->currentWidget()); } void Sidebar::activePreviousTab() { int tab = d->stack->indexOf(d->stack->currentWidget()); if (tab == 0) tab = d->tabs-1; else tab--; setActiveTab(d->stack->widget(tab)); } void Sidebar::activeNextTab() { int tab = d->stack->indexOf(d->stack->currentWidget()); if (tab== d->tabs-1) tab = 0; else tab++; setActiveTab(d->stack->widget(tab)); } void Sidebar::switchTabAndStackToTab(int tab) { if (d->activeTab >= 0) { setTab(d->activeTab, false); } d->activeTab = tab; setTab(d->activeTab, true); d->stack->setCurrentIndex(d->activeTab); } QWidget* Sidebar::getActiveTab() const { if (d->splitter) { return d->stack->currentWidget(); } else { return 0; } } void Sidebar::shrink() { d->minimized = true; // store the size that we had. We may later need it when we restore to visible. int currentSize = d->splitter->size(this); if (currentSize) { d->restoreSize = currentSize; } d->stack->hide(); emit signalViewChanged(); } void Sidebar::expand() { d->minimized = false; d->stack->show(); // Do not expand to size 0 (only splitter handle visible) // but either to previous size, or the minimum size hint if (d->splitter->size(this) == 0) { setTab(d->activeTab, true); d->splitter->setSize(this, d->restoreSize ? d->restoreSize : -1); } emit signalViewChanged(); } bool Sidebar::isExpanded() const { return !d->minimized; } bool Sidebar::eventFilter(QObject* obj, QEvent* ev) { for (int i = 0 ; i < d->tabs; ++i) { if ( obj == tab(i) ) { if ( ev->type() == QEvent::DragEnter) { QDragEnterEvent* const e = static_cast(ev); enterEvent(e); e->accept(); return false; } else if (ev->type() == QEvent::DragMove) { if (!d->dragSwitchTimer->isActive()) { d->dragSwitchTimer->setSingleShot(true); d->dragSwitchTimer->start(800); d->dragSwitchId = i; } return false; } else if (ev->type() == QEvent::DragLeave) { d->dragSwitchTimer->stop(); QDragLeaveEvent* const e = static_cast(ev); leaveEvent(e); return false; } else if (ev->type() == QEvent::Drop) { d->dragSwitchTimer->stop(); QDropEvent* const e = static_cast(ev); leaveEvent(e); return false; } else { return false; } } } // Else, pass the event on to the parent class return DMultiTabBar::eventFilter(obj, ev); } void Sidebar::slotDragSwitchTimer() { clicked(d->dragSwitchId); } void Sidebar::slotSplitterBtnClicked() { clicked(d->activeTab); } // ----------------------------------------------------------------------------- const QString SidebarSplitter::DEFAULT_CONFIG_KEY = QLatin1String("SplitterState"); SidebarSplitter::SidebarSplitter(QWidget* const parent) : QSplitter(parent), d(new Private) { connect(this, SIGNAL(splitterMoved(int,int)), this, SLOT(slotSplitterMoved(int,int))); } SidebarSplitter::SidebarSplitter(Qt::Orientation orientation, QWidget* const parent) : QSplitter(orientation, parent), d(new Private) { connect(this, SIGNAL(splitterMoved(int,int)), this, SLOT(slotSplitterMoved(int,int))); } SidebarSplitter::~SidebarSplitter() { // retreat cautiously from sidebars that live longer foreach(Sidebar* const sidebar, d->sidebars) { sidebar->d->splitter = 0; } delete d; } void SidebarSplitter::restoreState(KConfigGroup& group) { restoreState(group, DEFAULT_CONFIG_KEY); } void SidebarSplitter::restoreState(KConfigGroup& group, const QString& key) { if (group.hasKey(key)) { QByteArray state; state = group.readEntry(key, state); QSplitter::restoreState(QByteArray::fromBase64(state)); } } void SidebarSplitter::saveState(KConfigGroup& group) { saveState(group, DEFAULT_CONFIG_KEY); } void SidebarSplitter::saveState(KConfigGroup& group, const QString& key) { group.writeEntry(key, QSplitter::saveState().toBase64()); } int SidebarSplitter::size(Sidebar* const bar) const { return size(bar->d->stack); } int SidebarSplitter::size(QWidget* const widget) const { int index = indexOf(widget); if (index == -1) { return -1; } return sizes().at(index); } void SidebarSplitter::setSize(Sidebar* const bar, int size) { setSize(bar->d->stack, size); } void SidebarSplitter::setSize(QWidget* const widget, int size) { int index = indexOf(widget); if (index == -1) { return; } // special case: Use minimum size hint if (size == -1) { if (orientation() == Qt::Horizontal) { size = widget->minimumSizeHint().width(); } if (orientation() == Qt::Vertical) { size = widget->minimumSizeHint().height(); } } QList sizeList = sizes(); sizeList[index] = size; setSizes(sizeList); } void SidebarSplitter::slotSplitterMoved(int pos, int index) { Q_UNUSED(pos); // When the user moves the splitter so that size is 0 (collapsed), // it may be difficult to restore the sidebar as clicking the buttons // has no effect (only hides/shows the splitter handle) // So we want to transform this size-0-sidebar // to a sidebar that is shrunk (d->stack hidden) // and can be restored by clicking a tab bar button // We need to look at the widget between index-1 and index // and the one between index and index+1 QList sizeList = sizes(); // Is there a sidebar with size 0 before index ? if (index > 0 && sizeList.at(index-1) == 0) { QWidget* const w = widget(index-1); foreach(Sidebar* const sidebar, d->sidebars) { if (w == sidebar->d->stack) { if (!sidebar->d->minimized) { sidebar->setTab(sidebar->d->activeTab, false); sidebar->shrink(); } break; } } } // Is there a sidebar with size 0 after index ? if (sizeList.at(index) == 0) { QWidget* const w = widget(index); foreach(Sidebar* const sidebar, d->sidebars) { if (w == sidebar->d->stack) { if (!sidebar->d->minimized) { sidebar->setTab(sidebar->d->activeTab, false); sidebar->shrink(); } break; } } } } } // namespace Digikam diff --git a/core/libs/widgets/metadata/countryselector.cpp b/core/libs/widgets/metadata/countryselector.cpp index b87702cb8b..3ba945ee72 100644 --- a/core/libs/widgets/metadata/countryselector.cpp +++ b/core/libs/widgets/metadata/countryselector.cpp @@ -1,373 +1,373 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2009-07-07 * Description : country selector combo-box. * * Copyright (C) 2009-2018 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 "countryselector.h" // Qt includes #include // KDE includes #include // Local includes #include "digikam_debug.h" namespace Digikam { class Q_DECL_HIDDEN CountrySelector::Private { public: explicit Private() { // We cannot use KLocale::allCountriesList() here because KDE only // support 2 characters country codes. XMP require 3 characters country // following ISO 3166 (http://userpage.chemie.fu-berlin.de/diverse/doc/ISO_3166.html) // Standard ISO 3166 country codes. countryCodeMap.insert( QLatin1String("AFG"), i18n("Afghanistan") ); countryCodeMap.insert( QLatin1String("ALB"), i18n("Albania") ); countryCodeMap.insert( QLatin1String("DZA"), i18n("Algeria") ); countryCodeMap.insert( QLatin1String("ASM"), i18n("American Samoa") ); countryCodeMap.insert( QLatin1String("AND"), i18n("Andorra") ); countryCodeMap.insert( QLatin1String("AGO"), i18n("Angola") ); countryCodeMap.insert( QLatin1String("AIA"), i18n("Anguilla") ); countryCodeMap.insert( QLatin1String("AGO"), i18n("Angola") ); countryCodeMap.insert( QLatin1String("ATA"), i18n("Antarctica") ); countryCodeMap.insert( QLatin1String("ATG"), i18n("Antigua and Barbuda") ); countryCodeMap.insert( QLatin1String("ARG"), i18n("Argentina") ); countryCodeMap.insert( QLatin1String("ARM"), i18n("Armenia") ); countryCodeMap.insert( QLatin1String("ABW"), i18n("Aruba") ); countryCodeMap.insert( QLatin1String("AUS"), i18n("Australia") ); countryCodeMap.insert( QLatin1String("AUT"), i18n("Austria") ); countryCodeMap.insert( QLatin1String("AZE"), i18n("Azerbaijan") ); countryCodeMap.insert( QLatin1String("BHS"), i18n("Bahamas") ); countryCodeMap.insert( QLatin1String("BHR"), i18n("Bahrain") ); countryCodeMap.insert( QLatin1String("BGD"), i18n("Bangladesh") ); countryCodeMap.insert( QLatin1String("BRB"), i18n("Barbados") ); countryCodeMap.insert( QLatin1String("BLR"), i18n("Belarus") ); countryCodeMap.insert( QLatin1String("BEL"), i18n("Belgium") ); countryCodeMap.insert( QLatin1String("BLZ"), i18n("Belize") ); countryCodeMap.insert( QLatin1String("BEN"), i18n("Benin") ); countryCodeMap.insert( QLatin1String("BMU"), i18n("Bermuda") ); countryCodeMap.insert( QLatin1String("BTN"), i18n("Bhutan") ); countryCodeMap.insert( QLatin1String("BOL"), i18n("Bolivia") ); countryCodeMap.insert( QLatin1String("BIH"), i18n("Bosnia and Herzegovina") ); countryCodeMap.insert( QLatin1String("BWA"), i18n("Botswana") ); countryCodeMap.insert( QLatin1String("BVT"), i18n("Bouvet Island") ); countryCodeMap.insert( QLatin1String("BRA"), i18n("Brazil") ); countryCodeMap.insert( QLatin1String("IOT"), i18n("British Indian Ocean Territory") ); countryCodeMap.insert( QLatin1String("VGB"), i18n("British Virgin Islands") ); countryCodeMap.insert( QLatin1String("BRN"), i18n("Brunei Darussalam") ); countryCodeMap.insert( QLatin1String("BGR"), i18n("Bulgaria") ); countryCodeMap.insert( QLatin1String("BFA"), i18n("Burkina Faso") ); countryCodeMap.insert( QLatin1String("BDI"), i18n("Burundi") ); countryCodeMap.insert( QLatin1String("KHM"), i18n("Cambodia") ); countryCodeMap.insert( QLatin1String("CMR"), i18n("Cameroon") ); countryCodeMap.insert( QLatin1String("CAN"), i18n("Canada") ); countryCodeMap.insert( QLatin1String("CPV"), i18n("Cape Verde") ); countryCodeMap.insert( QLatin1String("CYM"), i18n("Cayman Islands") ); countryCodeMap.insert( QLatin1String("CAF"), i18n("Central African Republic") ); countryCodeMap.insert( QLatin1String("TCD"), i18n("Chad") ); countryCodeMap.insert( QLatin1String("CHL"), i18n("Chile") ); countryCodeMap.insert( QLatin1String("CHN"), i18n("China") ); countryCodeMap.insert( QLatin1String("CXR"), i18n("Christmas Island ") ); countryCodeMap.insert( QLatin1String("CCK"), i18n("Cocos Islands") ); countryCodeMap.insert( QLatin1String("COL"), i18n("Colombia") ); countryCodeMap.insert( QLatin1String("COM"), i18n("Comoros") ); countryCodeMap.insert( QLatin1String("COD"), i18n("Zaire") ); countryCodeMap.insert( QLatin1String("COG"), i18n("Congo") ); countryCodeMap.insert( QLatin1String("COK"), i18n("Cook Islands") ); countryCodeMap.insert( QLatin1String("CRI"), i18n("Costa Rica") ); countryCodeMap.insert( QLatin1String("CIV"), i18n("Ivory Coast") ); countryCodeMap.insert( QLatin1String("CUB"), i18n("Cuba") ); countryCodeMap.insert( QLatin1String("CYP"), i18n("Cyprus") ); countryCodeMap.insert( QLatin1String("CZE"), i18n("Czechia") ); countryCodeMap.insert( QLatin1String("DNK"), i18n("Denmark") ); countryCodeMap.insert( QLatin1String("DJI"), i18n("Djibouti") ); countryCodeMap.insert( QLatin1String("DMA"), i18n("Dominica") ); countryCodeMap.insert( QLatin1String("DOM"), i18n("Dominican Republic") ); countryCodeMap.insert( QLatin1String("ECU"), i18n("Ecuador") ); countryCodeMap.insert( QLatin1String("EGY"), i18n("Egypt") ); countryCodeMap.insert( QLatin1String("SLV"), i18n("El Salvador") ); countryCodeMap.insert( QLatin1String("GNQ"), i18n("Equatorial Guinea") ); countryCodeMap.insert( QLatin1String("ERI"), i18n("Eritrea") ); countryCodeMap.insert( QLatin1String("EST"), i18n("Estonia") ); countryCodeMap.insert( QLatin1String("ETH"), i18n("Ethiopia") ); countryCodeMap.insert( QLatin1String("FRO"), i18n("Faeroe Islands") ); countryCodeMap.insert( QLatin1String("FLK"), i18n("Falkland Islands") ); countryCodeMap.insert( QLatin1String("FJI"), i18n("Fiji Islands") ); countryCodeMap.insert( QLatin1String("FIN"), i18n("Finland") ); countryCodeMap.insert( QLatin1String("FRA"), i18n("France") ); countryCodeMap.insert( QLatin1String("GUF"), i18n("French Guiana") ); countryCodeMap.insert( QLatin1String("PYF"), i18n("French Polynesia") ); countryCodeMap.insert( QLatin1String("ATF"), i18n("French Southern Territories") ); countryCodeMap.insert( QLatin1String("GAB"), i18n("Gabon") ); countryCodeMap.insert( QLatin1String("GMB"), i18n("Gambia") ); countryCodeMap.insert( QLatin1String("GEO"), i18n("Georgia") ); countryCodeMap.insert( QLatin1String("DEU"), i18n("Germany") ); countryCodeMap.insert( QLatin1String("GHA"), i18n("Ghana") ); countryCodeMap.insert( QLatin1String("GIB"), i18n("Gibraltar") ); countryCodeMap.insert( QLatin1String("GRC"), i18n("Greece") ); countryCodeMap.insert( QLatin1String("GRL"), i18n("Greenland") ); countryCodeMap.insert( QLatin1String("GRD"), i18n("Grenada") ); countryCodeMap.insert( QLatin1String("GLP"), i18n("Guadaloupe") ); countryCodeMap.insert( QLatin1String("GUM"), i18n("Guam") ); countryCodeMap.insert( QLatin1String("GTM"), i18n("Guatemala") ); countryCodeMap.insert( QLatin1String("GIN"), i18n("Guinea") ); countryCodeMap.insert( QLatin1String("GNB"), i18n("Guinea-Bissau") ); countryCodeMap.insert( QLatin1String("GUY"), i18n("Guyana") ); countryCodeMap.insert( QLatin1String("HTI"), i18n("Haiti") ); countryCodeMap.insert( QLatin1String("HMD"), i18n("Heard and McDonald Islands") ); countryCodeMap.insert( QLatin1String("VAT"), i18n("Vatican") ); countryCodeMap.insert( QLatin1String("HND"), i18n("Honduras") ); countryCodeMap.insert( QLatin1String("HKG"), i18n("Hong Kong") ); countryCodeMap.insert( QLatin1String("HRV"), i18n("Croatia") ); countryCodeMap.insert( QLatin1String("HUN"), i18n("Hungary") ); countryCodeMap.insert( QLatin1String("ISL"), i18n("Iceland") ); countryCodeMap.insert( QLatin1String("IND"), i18n("India") ); countryCodeMap.insert( QLatin1String("IDN"), i18n("Indonesia") ); countryCodeMap.insert( QLatin1String("IRN"), i18n("Iran") ); countryCodeMap.insert( QLatin1String("IRQ"), i18n("Iraq") ); countryCodeMap.insert( QLatin1String("IRL"), i18n("Ireland") ); countryCodeMap.insert( QLatin1String("ISR"), i18n("Israel") ); countryCodeMap.insert( QLatin1String("ITA"), i18n("Italy") ); countryCodeMap.insert( QLatin1String("JAM"), i18n("Jamaica") ); countryCodeMap.insert( QLatin1String("JPN"), i18n("Japan") ); countryCodeMap.insert( QLatin1String("JOR"), i18n("Jordan") ); countryCodeMap.insert( QLatin1String("KAZ"), i18n("Kazakhstan") ); countryCodeMap.insert( QLatin1String("KEN"), i18n("Kenya") ); countryCodeMap.insert( QLatin1String("KIR"), i18n("Kiribati") ); countryCodeMap.insert( QLatin1String("PRK"), i18n("North-Korea") ); countryCodeMap.insert( QLatin1String("KOR"), i18n("South-Korea") ); countryCodeMap.insert( QLatin1String("KWT"), i18n("Kuwait") ); countryCodeMap.insert( QLatin1String("KGZ"), i18n("Kyrgyz Republic") ); countryCodeMap.insert( QLatin1String("LAO"), i18n("Lao") ); countryCodeMap.insert( QLatin1String("LVA"), i18n("Latvia") ); countryCodeMap.insert( QLatin1String("LBN"), i18n("Lebanon") ); countryCodeMap.insert( QLatin1String("LSO"), i18n("Lesotho") ); countryCodeMap.insert( QLatin1String("LBR"), i18n("Liberia") ); countryCodeMap.insert( QLatin1String("LBY"), i18n("Libyan Arab Jamahiriya") ); countryCodeMap.insert( QLatin1String("LIE"), i18n("Liechtenstein") ); countryCodeMap.insert( QLatin1String("LTU"), i18n("Lithuania") ); countryCodeMap.insert( QLatin1String("LUX"), i18n("Luxembourg") ); countryCodeMap.insert( QLatin1String("MAC"), i18n("Macao") ); countryCodeMap.insert( QLatin1String("MKD"), i18n("Macedonia") ); countryCodeMap.insert( QLatin1String("MDG"), i18n("Madagascar") ); countryCodeMap.insert( QLatin1String("MWI"), i18n("Malawi") ); countryCodeMap.insert( QLatin1String("MYS"), i18n("Malaysia") ); countryCodeMap.insert( QLatin1String("MDV"), i18n("Maldives") ); countryCodeMap.insert( QLatin1String("MLI"), i18n("Mali") ); countryCodeMap.insert( QLatin1String("MLT"), i18n("Malta") ); countryCodeMap.insert( QLatin1String("MHL"), i18n("Marshall Islands") ); countryCodeMap.insert( QLatin1String("MTQ"), i18n("Martinique") ); countryCodeMap.insert( QLatin1String("MRT"), i18n("Mauritania") ); countryCodeMap.insert( QLatin1String("MUS"), i18n("Mauritius") ); countryCodeMap.insert( QLatin1String("MYT"), i18n("Mayotte") ); countryCodeMap.insert( QLatin1String("MEX"), i18n("Mexico") ); countryCodeMap.insert( QLatin1String("FSM"), i18n("Micronesia") ); countryCodeMap.insert( QLatin1String("MDA"), i18n("Moldova") ); countryCodeMap.insert( QLatin1String("MCO"), i18n("Monaco") ); countryCodeMap.insert( QLatin1String("MNG"), i18n("Mongolia") ); countryCodeMap.insert( QLatin1String("MSR"), i18n("Montserrat") ); countryCodeMap.insert( QLatin1String("MAR"), i18n("Morocco") ); countryCodeMap.insert( QLatin1String("MOZ"), i18n("Mozambique") ); countryCodeMap.insert( QLatin1String("MMR"), i18n("Myanmar") ); countryCodeMap.insert( QLatin1String("NAM"), i18n("Namibia") ); countryCodeMap.insert( QLatin1String("NRU"), i18n("Nauru") ); countryCodeMap.insert( QLatin1String("NPL"), i18n("Nepal") ); countryCodeMap.insert( QLatin1String("ANT"), i18n("Netherlands Antilles") ); countryCodeMap.insert( QLatin1String("NLD"), i18n("Netherlands") ); countryCodeMap.insert( QLatin1String("NCL"), i18n("New Caledonia") ); countryCodeMap.insert( QLatin1String("NZL"), i18n("New Zealand") ); countryCodeMap.insert( QLatin1String("NIC"), i18n("Nicaragua") ); countryCodeMap.insert( QLatin1String("NER"), i18n("Niger") ); countryCodeMap.insert( QLatin1String("NGA"), i18n("Nigeria") ); countryCodeMap.insert( QLatin1String("NIU"), i18n("Niue") ); countryCodeMap.insert( QLatin1String("NFK"), i18n("Norfolk Island") ); countryCodeMap.insert( QLatin1String("MNP"), i18n("Northern Mariana Islands") ); countryCodeMap.insert( QLatin1String("NOR"), i18n("Norway") ); countryCodeMap.insert( QLatin1String("OMN"), i18n("Oman") ); countryCodeMap.insert( QLatin1String("PAK"), i18n("Pakistan") ); countryCodeMap.insert( QLatin1String("PLW"), i18n("Palau") ); countryCodeMap.insert( QLatin1String("PSE"), i18n("Palestinian Territory") ); countryCodeMap.insert( QLatin1String("PAN"), i18n("Panama") ); countryCodeMap.insert( QLatin1String("PNG"), i18n("Papua New Guinea") ); countryCodeMap.insert( QLatin1String("PRY"), i18n("Paraguay") ); countryCodeMap.insert( QLatin1String("PER"), i18n("Peru") ); countryCodeMap.insert( QLatin1String("PHL"), i18n("Philippines") ); countryCodeMap.insert( QLatin1String("PCN"), i18n("Pitcairn Island") ); countryCodeMap.insert( QLatin1String("POL"), i18n("Poland") ); countryCodeMap.insert( QLatin1String("PRT"), i18n("Portugal") ); countryCodeMap.insert( QLatin1String("PRI"), i18n("Puerto Rico") ); countryCodeMap.insert( QLatin1String("QAT"), i18n("Qatar") ); countryCodeMap.insert( QLatin1String("REU"), i18n("Reunion") ); countryCodeMap.insert( QLatin1String("ROU"), i18n("Romania") ); countryCodeMap.insert( QLatin1String("RUS"), i18n("Russian Federation") ); countryCodeMap.insert( QLatin1String("RWA"), i18n("Rwanda") ); countryCodeMap.insert( QLatin1String("SHN"), i18n("St. Helena") ); countryCodeMap.insert( QLatin1String("KNA"), i18n("St. Kitts and Nevis") ); countryCodeMap.insert( QLatin1String("LCA"), i18n("St. Lucia") ); countryCodeMap.insert( QLatin1String("SPM"), i18n("St. Pierre and Miquelon") ); countryCodeMap.insert( QLatin1String("VCT"), i18n("St. Vincent and the Grenadines") ); countryCodeMap.insert( QLatin1String("WSM"), i18n("Samoa") ); countryCodeMap.insert( QLatin1String("SMR"), i18n("San Marino") ); countryCodeMap.insert( QLatin1String("STP"), i18n("Sao Tome and Principe") ); countryCodeMap.insert( QLatin1String("SAU"), i18n("Saudi Arabia") ); countryCodeMap.insert( QLatin1String("SEN"), i18n("Senegal") ); countryCodeMap.insert( QLatin1String("SCG"), i18n("Serbia") ); countryCodeMap.insert( QLatin1String("SYC"), i18n("Seychelles") ); countryCodeMap.insert( QLatin1String("SLE"), i18n("Sierra Leone") ); countryCodeMap.insert( QLatin1String("SGP"), i18n("Singapore") ); countryCodeMap.insert( QLatin1String("SVK"), i18n("Slovakia") ); countryCodeMap.insert( QLatin1String("SVN"), i18n("Slovenia") ); countryCodeMap.insert( QLatin1String("SLB"), i18n("Solomon Islands") ); countryCodeMap.insert( QLatin1String("SOM"), i18n("Somalia") ); countryCodeMap.insert( QLatin1String("ZAF"), i18n("South Africa") ); countryCodeMap.insert( QLatin1String("SGS"), i18n("South Georgia and the South Sandwich Islands") ); countryCodeMap.insert( QLatin1String("ESP"), i18n("Spain") ); countryCodeMap.insert( QLatin1String("LKA"), i18n("Sri Lanka") ); countryCodeMap.insert( QLatin1String("SDN"), i18n("Sudan") ); countryCodeMap.insert( QLatin1String("SUR"), i18n("Suriname") ); countryCodeMap.insert( QLatin1String("SJM"), i18n("Svalbard & Jan Mayen Islands") ); countryCodeMap.insert( QLatin1String("SWZ"), i18n("Swaziland") ); countryCodeMap.insert( QLatin1String("SWE"), i18n("Sweden") ); countryCodeMap.insert( QLatin1String("CHE"), i18n("Switzerland") ); countryCodeMap.insert( QLatin1String("SYR"), i18n("Syrian Arab Republic") ); countryCodeMap.insert( QLatin1String("TWN"), i18n("Taiwan") ); countryCodeMap.insert( QLatin1String("TJK"), i18n("Tajikistan") ); countryCodeMap.insert( QLatin1String("TZA"), i18n("Tanzania") ); countryCodeMap.insert( QLatin1String("THA"), i18n("Thailand") ); countryCodeMap.insert( QLatin1String("TLS"), i18n("Timor-Leste") ); countryCodeMap.insert( QLatin1String("TGO"), i18n("Togo") ); countryCodeMap.insert( QLatin1String("TKL"), i18n("Tokelau Islands") ); countryCodeMap.insert( QLatin1String("TON"), i18n("Tonga") ); countryCodeMap.insert( QLatin1String("TTO"), i18n("Trinidad and Tobago") ); countryCodeMap.insert( QLatin1String("TUN"), i18n("Tunisia") ); countryCodeMap.insert( QLatin1String("TUR"), i18n("Turkey") ); countryCodeMap.insert( QLatin1String("TKM"), i18n("Turkmenistan") ); countryCodeMap.insert( QLatin1String("TCA"), i18n("Turks and Caicos Islands") ); countryCodeMap.insert( QLatin1String("TUV"), i18n("Tuvalu") ); countryCodeMap.insert( QLatin1String("VIR"), i18n("US Virgin Islands") ); countryCodeMap.insert( QLatin1String("UGA"), i18n("Uganda") ); countryCodeMap.insert( QLatin1String("UKR"), i18n("Ukraine") ); countryCodeMap.insert( QLatin1String("ARE"), i18n("United Arab Emirates") ); countryCodeMap.insert( QLatin1String("GBR"), i18n("United Kingdom") ); countryCodeMap.insert( QLatin1String("UMI"), i18n("United States Minor Outlying Islands") ); countryCodeMap.insert( QLatin1String("USA"), i18n("United States of America") ); countryCodeMap.insert( QLatin1String("URY"), i18n("Uruguay, Eastern Republic of") ); countryCodeMap.insert( QLatin1String("UZB"), i18n("Uzbekistan") ); countryCodeMap.insert( QLatin1String("VUT"), i18n("Vanuatu") ); countryCodeMap.insert( QLatin1String("VEN"), i18n("Venezuela") ); countryCodeMap.insert( QLatin1String("VNM"), i18n("Viet Nam") ); countryCodeMap.insert( QLatin1String("WLF"), i18n("Wallis and Futuna Islands ") ); countryCodeMap.insert( QLatin1String("ESH"), i18n("Western Sahara") ); countryCodeMap.insert( QLatin1String("YEM"), i18n("Yemen") ); countryCodeMap.insert( QLatin1String("ZMB"), i18n("Zambia") ); countryCodeMap.insert( QLatin1String("ZWE"), i18n("Zimbabwe") ); // Supplemental IPTC/IIM country codes. countryCodeMap.insert( QLatin1String("XUN"), i18n("United Nations") ); countryCodeMap.insert( QLatin1String("XEU"), i18n("European Union") ); countryCodeMap.insert( QLatin1String("XSP"), i18n("Space") ); countryCodeMap.insert( QLatin1String("XSE"), i18n("At Sea") ); countryCodeMap.insert( QLatin1String("XIF"), i18n("In Flight") ); countryCodeMap.insert( QLatin1String("XEN"), i18n("England") ); countryCodeMap.insert( QLatin1String("XSC"), i18n("Scotland") ); countryCodeMap.insert( QLatin1String("XNI"), i18n("Northern Ireland") ); countryCodeMap.insert( QLatin1String("XWA"), i18n("Wales") ); countryCodeMap.insert( QLatin1String("PSE"), i18n("Palestine") ); countryCodeMap.insert( QLatin1String("GZA"), i18n("Gaza") ); countryCodeMap.insert( QLatin1String("JRO"), i18n("Jericho") ); } typedef QMap CountryCodeMap; CountryCodeMap countryCodeMap; }; CountrySelector::CountrySelector(QWidget* const parent) : QComboBox(parent), d(new Private) { for (Private::CountryCodeMap::Iterator it = d->countryCodeMap.begin(); it != d->countryCodeMap.end(); ++it) { addItem(QString::fromLatin1("%1 - %2").arg(it.key()).arg(it.value())); } model()->sort(0); insertSeparator(count()); addItem(i18nc("Unknown country", "Unknown")); } CountrySelector::~CountrySelector() { delete d; } void CountrySelector::setCountry(const QString& countryCode) { // NOTE: if countryCode is empty or do not matches code map, unknow is selected from the list. int id = count()-1; - for (int i = 0 ; i < d->countryCodeMap.count() ; i++) + for (int i = 0 ; i < d->countryCodeMap.count() ; ++i) { if (itemText(i).left(3) == countryCode) { id = i; break; } } setCurrentIndex(id); qCDebug(DIGIKAM_WIDGETS_LOG) << count() << " :: " << id; } bool CountrySelector::country(QString& countryCode, QString& countryName) const { // Unknow is selected ? if (currentIndex() == count()-1) return false; countryName = currentText().mid(6); countryCode = currentText().left(3); return true; } QString CountrySelector::countryForCode(const QString& countryCode) { Private priv; return (priv.countryCodeMap[countryCode]); } } // namespace Digikam diff --git a/core/libs/widgets/metadata/subjectwidget.cpp b/core/libs/widgets/metadata/subjectwidget.cpp index 72d822a924..ad46d13873 100644 --- a/core/libs/widgets/metadata/subjectwidget.cpp +++ b/core/libs/widgets/metadata/subjectwidget.cpp @@ -1,630 +1,630 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2006-10-15 * Description : IPTC subjects editor. * * Copyright (C) 2006-2018 by Gilles Caulier * Copyright (C) 2009-2012 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 "subjectwidget.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include // Local includes #include "digikam_debug.h" namespace Digikam { class Q_DECL_HIDDEN SubjectWidget::Private { public: enum EditionMode { STANDARD = 0, CUSTOM }; public: explicit Private() { addSubjectButton = 0; delSubjectButton = 0; repSubjectButton = 0; subjectsBox = 0; iprLabel = 0; refLabel = 0; nameLabel = 0; matterLabel = 0; detailLabel = 0; btnGroup = 0; stdBtn = 0; customBtn = 0; refCB = 0; optionsBox = 0; } typedef QMap SubjectCodesMap; SubjectCodesMap subMap; QStringList subjectsList; QWidget* optionsBox; QPushButton* addSubjectButton; QPushButton* delSubjectButton; QPushButton* repSubjectButton; QLabel* iprLabel; QLabel* refLabel; QLabel* nameLabel; QLabel* matterLabel; QLabel* detailLabel; QButtonGroup* btnGroup; QRadioButton* stdBtn; QRadioButton* customBtn; QComboBox* refCB; QListWidget* subjectsBox; }; // -------------------------------------------------------------------------------- SubjectWidget::SubjectWidget(QWidget* const parent) : QWidget(parent), d(new Private) { // Load subject codes provided by IPTC/NAA as xml file. // See http://iptc.cms.apa.at/std/topicset/topicset.iptc-subjectcode.xml for details. QString path = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("digikam/metadata/topicset.iptc-subjectcode.xml")); if (!loadSubjectCodesFromXML(QUrl::fromLocalFile(path))) qCDebug(DIGIKAM_WIDGETS_LOG) << "Cannot load IPTC/NAA subject codes XML database"; // -------------------------------------------------------- // Subject Reference Number only accept digit. QRegExp refDigitRx(QLatin1String("^[0-9]{8}$")); QValidator* const refValidator = new QRegExpValidator(refDigitRx, this); // -------------------------------------------------------- m_subjectsCheck = new QCheckBox(i18n("Use structured definition of the subject matter:"), this); d->optionsBox = new QWidget; d->btnGroup = new QButtonGroup(this); d->stdBtn = new QRadioButton; d->customBtn = new QRadioButton; d->refCB = new QComboBox; QLabel* const codeLink = new QLabel(i18n("Use standard " "" "reference code")); codeLink->setOpenExternalLinks(true); codeLink->setWordWrap(false); // By default, check box is not visible. m_subjectsCheck->setVisible(false); m_subjectsCheck->setEnabled(false); QLabel* const customLabel = new QLabel(i18n("Use custom definition")); d->btnGroup->addButton(d->stdBtn, Private::STANDARD); d->btnGroup->addButton(d->customBtn, Private::CUSTOM); d->btnGroup->setExclusive(true); d->stdBtn->setChecked(true); for (Private::SubjectCodesMap::Iterator it = d->subMap.begin(); it != d->subMap.end(); ++it) d->refCB->addItem(it.key()); // -------------------------------------------------------- m_iprEdit = new QLineEdit; m_iprEdit->setClearButtonEnabled(true); m_iprEdit->setMaxLength(32); // -------------------------------------------------------- m_refEdit = new QLineEdit; m_refEdit->setClearButtonEnabled(true); m_refEdit->setValidator(refValidator); m_refEdit->setMaxLength(8); // -------------------------------------------------------- m_nameEdit = new QLineEdit; m_nameEdit->setClearButtonEnabled(true); m_nameEdit->setMaxLength(64); // -------------------------------------------------------- m_matterEdit = new QLineEdit; m_matterEdit->setClearButtonEnabled(true); m_matterEdit->setMaxLength(64); // -------------------------------------------------------- m_detailEdit = new QLineEdit; m_detailEdit->setClearButtonEnabled(true); m_detailEdit->setMaxLength(64); // -------------------------------------------------------- d->iprLabel = new QLabel(i18nc("Information Provider Reference: " "A name, registered with the IPTC/NAA, " "identifying the provider that guarantees " "the uniqueness of the UNO", "I.P.R:")); d->refLabel = new QLabel(i18n("Reference:")); d->nameLabel = new QLabel(i18n("Name:")); d->matterLabel = new QLabel(i18n("Matter:")); d->detailLabel = new QLabel(i18n("Detail:")); // -------------------------------------------------------- d->subjectsBox = new QListWidget; d->subjectsBox->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); d->addSubjectButton = new QPushButton(i18n("&Add")); d->delSubjectButton = new QPushButton(i18n("&Delete")); d->repSubjectButton = new QPushButton(i18n("&Replace")); d->addSubjectButton->setIcon(QIcon::fromTheme(QLatin1String("list-add")).pixmap(16, 16)); d->delSubjectButton->setIcon(QIcon::fromTheme(QLatin1String("edit-delete")).pixmap(16, 16)); d->repSubjectButton->setIcon(QIcon::fromTheme(QLatin1String("view-refresh")).pixmap(16, 16)); d->delSubjectButton->setEnabled(false); d->repSubjectButton->setEnabled(false); // -------------------------------------------------------- m_note = new QLabel; m_note->setMaximumWidth(150); m_note->setOpenExternalLinks(true); m_note->setWordWrap(true); m_note->setFrameStyle(QFrame::StyledPanel | QFrame::Raised); // -------------------------------------------------------- QGridLayout* const optionsBoxLayout = new QGridLayout; optionsBoxLayout->addWidget(d->stdBtn, 0, 0, 1, 1); optionsBoxLayout->addWidget(codeLink, 0, 1, 1, 2); optionsBoxLayout->addWidget(d->refCB, 0, 3, 1, 1); optionsBoxLayout->addWidget(d->customBtn, 1, 0, 1, 4); optionsBoxLayout->addWidget(customLabel, 1, 1, 1, 4); optionsBoxLayout->addWidget(d->iprLabel, 2, 0, 1, 1); optionsBoxLayout->addWidget(m_iprEdit, 2, 1, 1, 4); optionsBoxLayout->addWidget(d->refLabel, 3, 0, 1, 1); optionsBoxLayout->addWidget(m_refEdit, 3, 1, 1, 1); optionsBoxLayout->addWidget(d->nameLabel, 4, 0, 1, 1); optionsBoxLayout->addWidget(m_nameEdit, 4, 1, 1, 4); optionsBoxLayout->addWidget(d->matterLabel, 5, 0, 1, 1); optionsBoxLayout->addWidget(m_matterEdit, 5, 1, 1, 4); optionsBoxLayout->addWidget(d->detailLabel, 6, 0, 1, 1); optionsBoxLayout->addWidget(m_detailEdit, 6, 1, 1, 4); optionsBoxLayout->setColumnStretch(4, 10); optionsBoxLayout->setContentsMargins(QMargins()); optionsBoxLayout->setSpacing(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); d->optionsBox->setLayout(optionsBoxLayout); // -------------------------------------------------------- QGridLayout* const mainLayout = new QGridLayout; mainLayout->setAlignment( Qt::AlignTop ); mainLayout->addWidget(m_subjectsCheck, 0, 0, 1, 4); mainLayout->addWidget(d->optionsBox, 1, 0, 1, 4); mainLayout->addWidget(d->subjectsBox, 2, 0, 5, 3); mainLayout->addWidget(d->addSubjectButton, 2, 3, 1, 1); mainLayout->addWidget(d->delSubjectButton, 3, 3, 1, 1); mainLayout->addWidget(d->repSubjectButton, 4, 3, 1, 1); mainLayout->addWidget(m_note, 5, 3, 1, 1); mainLayout->setRowStretch(6, 10); mainLayout->setColumnStretch(2, 1); mainLayout->setContentsMargins(QMargins()); mainLayout->setSpacing(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); setLayout(mainLayout); // -------------------------------------------------------- connect(d->subjectsBox, &QListWidget::itemSelectionChanged, this, &SubjectWidget::slotSubjectSelectionChanged); connect(d->addSubjectButton, &QPushButton::clicked, this, &SubjectWidget::slotAddSubject); connect(d->delSubjectButton, &QPushButton::clicked, this, &SubjectWidget::slotDelSubject); connect(d->repSubjectButton, &QPushButton::clicked, this, &SubjectWidget::slotRepSubject); connect(d->btnGroup, static_cast(&QButtonGroup::buttonReleased), this, &SubjectWidget::slotEditOptionChanged); connect(d->refCB, static_cast(&QComboBox::activated), this, &SubjectWidget::slotRefChanged); // -------------------------------------------------------- connect(m_subjectsCheck, &QCheckBox::toggled, this, &SubjectWidget::slotSubjectsToggled); // -------------------------------------------------------- connect(m_subjectsCheck, &QCheckBox::toggled, this, &SubjectWidget::signalModified); connect(d->addSubjectButton, &QPushButton::clicked, this, &SubjectWidget::signalModified); connect(d->delSubjectButton, &QPushButton::clicked, this, &SubjectWidget::signalModified); connect(d->repSubjectButton, &QPushButton::clicked, this, &SubjectWidget::signalModified); // -------------------------------------------------------- slotEditOptionChanged(d->btnGroup->id(d->btnGroup->checkedButton())); } SubjectWidget::~SubjectWidget() { delete d; } void SubjectWidget::slotSubjectsToggled(bool b) { d->optionsBox->setEnabled(b); d->subjectsBox->setEnabled(b); d->addSubjectButton->setEnabled(b); d->delSubjectButton->setEnabled(b); d->repSubjectButton->setEnabled(b); slotEditOptionChanged(d->btnGroup->id(d->btnGroup->checkedButton())); } void SubjectWidget::slotEditOptionChanged(int b) { if (b == Private::CUSTOM) { d->refCB->setEnabled(false); m_iprEdit->setEnabled(true); m_refEdit->setEnabled(true); m_nameEdit->setEnabled(true); m_matterEdit->setEnabled(true); m_detailEdit->setEnabled(true); } else { d->refCB->setEnabled(true); m_iprEdit->setEnabled(false); m_refEdit->setEnabled(false); m_nameEdit->setEnabled(false); m_matterEdit->setEnabled(false); m_detailEdit->setEnabled(false); slotRefChanged(); } } void SubjectWidget::slotRefChanged() { QString key = d->refCB->currentText(); QString name, matter, detail; for (Private::SubjectCodesMap::Iterator it = d->subMap.begin(); it != d->subMap.end(); ++it) { if (key == it.key()) { name = it.value().name; matter = it.value().matter; detail = it.value().detail; } } m_refEdit->setText(key); m_nameEdit->setText(name); m_matterEdit->setText(matter); m_detailEdit->setText(detail); } QString SubjectWidget::buildSubject() const { QString subject = m_iprEdit->text(); subject.append(QLatin1Char(':')); subject.append(m_refEdit->text()); subject.append(QLatin1Char(':')); subject.append(m_nameEdit->text()); subject.append(QLatin1Char(':')); subject.append(m_matterEdit->text()); subject.append(QLatin1Char(':')); subject.append(m_detailEdit->text()); return subject; } void SubjectWidget::slotDelSubject() { QListWidgetItem* const item = d->subjectsBox->currentItem(); if (!item) return; d->subjectsBox->takeItem(d->subjectsBox->row(item)); delete item; } void SubjectWidget::slotRepSubject() { QString newSubject = buildSubject(); if (newSubject.isEmpty()) return; if (!d->subjectsBox->selectedItems().isEmpty()) { d->subjectsBox->selectedItems()[0]->setText(newSubject); m_iprEdit->clear(); m_refEdit->clear(); m_nameEdit->clear(); m_matterEdit->clear(); m_detailEdit->clear(); } } void SubjectWidget::slotSubjectSelectionChanged() { if (!d->subjectsBox->selectedItems().isEmpty()) { QString subject = d->subjectsBox->selectedItems()[0]->text(); m_iprEdit->setText(subject.section(QLatin1Char(':'), 0, 0)); m_refEdit->setText(subject.section(QLatin1Char(':'), 1, 1)); m_nameEdit->setText(subject.section(QLatin1Char(':'), 2, 2)); m_matterEdit->setText(subject.section(QLatin1Char(':'), 3, 3)); m_detailEdit->setText(subject.section(QLatin1Char(':'), 4, 4)); d->delSubjectButton->setEnabled(true); d->repSubjectButton->setEnabled(true); } else { d->delSubjectButton->setEnabled(false); d->repSubjectButton->setEnabled(false); } } void SubjectWidget::slotAddSubject() { QString newSubject = buildSubject(); if (newSubject.isEmpty()) return; bool found = false; - for (int i = 0 ; i < d->subjectsBox->count(); i++) + for (int i = 0 ; i < d->subjectsBox->count() ; ++i) { QListWidgetItem* const item = d->subjectsBox->item(i); if (newSubject == item->text()) { found = true; break; } } if (!found) { d->subjectsBox->insertItem(d->subjectsBox->count(), newSubject); m_iprEdit->clear(); m_refEdit->clear(); m_nameEdit->clear(); m_matterEdit->clear(); m_detailEdit->clear(); } } bool SubjectWidget::loadSubjectCodesFromXML(const QUrl& url) { QFile xmlfile(url.toLocalFile()); if (!xmlfile.open(QIODevice::ReadOnly)) return false; QDomDocument xmlDoc(QLatin1String("NewsML")); if (!xmlDoc.setContent(&xmlfile)) { xmlfile.close(); return false; } QDomElement xmlDocElem = xmlDoc.documentElement(); if (xmlDocElem.tagName() != QLatin1String("NewsML")) { xmlfile.close(); return false; } for (QDomNode nbE1 = xmlDocElem.firstChild(); !nbE1.isNull(); nbE1 = nbE1.nextSibling()) { QDomElement newsItemElement = nbE1.toElement(); if (newsItemElement.isNull()) continue; if (newsItemElement.tagName() != QLatin1String("NewsItem")) continue; for (QDomNode nbE2 = newsItemElement.firstChild(); !nbE2.isNull(); nbE2 = nbE2.nextSibling()) { QDomElement topicSetElement = nbE2.toElement(); if (topicSetElement.isNull()) continue; if (topicSetElement.tagName() != QLatin1String("TopicSet")) continue; for (QDomNode nbE3 = topicSetElement.firstChild(); !nbE3.isNull(); nbE3 = nbE3.nextSibling()) { QDomElement topicElement = nbE3.toElement(); if (topicElement.isNull()) continue; if (topicElement.tagName() != QLatin1String("Topic")) continue; QString type, name, matter, detail, ref; for (QDomNode nbE4 = topicElement.firstChild(); !nbE4.isNull(); nbE4 = nbE4.nextSibling()) { QDomElement topicSubElement = nbE4.toElement(); if (topicSubElement.isNull()) continue; if (topicSubElement.tagName() == QLatin1String("TopicType")) type = topicSubElement.attribute(QLatin1String("FormalName")); if (topicSubElement.tagName() == QLatin1String("FormalName")) ref = topicSubElement.text(); if (topicSubElement.tagName() == QLatin1String("Description") && topicSubElement.attribute(QLatin1String("Variant")) == QLatin1String("Name")) { if (type == QLatin1String("Subject")) name = topicSubElement.text(); else if (type == QLatin1String("SubjectMatter")) matter = topicSubElement.text(); else if (type == QLatin1String("SubjectDetail")) detail = topicSubElement.text(); } } d->subMap.insert(ref, SubjectData(name, matter, detail)); } } } xmlfile.close(); // Set the Subject Name everywhere on the map. for (Private::SubjectCodesMap::Iterator it = d->subMap.begin(); it != d->subMap.end(); ++it) { QString name, keyPrefix; if (it.key().endsWith(QLatin1String("00000"))) { keyPrefix = it.key().left(3); name = it.value().name; for (Private::SubjectCodesMap::Iterator it2 = d->subMap.begin(); it2 != d->subMap.end(); ++it2) { if (it2.key().startsWith(keyPrefix) && !it2.key().endsWith(QLatin1String("00000"))) { it2.value().name = name; } } } } // Set the Subject Matter Name everywhere on the map. for (Private::SubjectCodesMap::Iterator it = d->subMap.begin(); it != d->subMap.end(); ++it) { QString matter, keyPrefix; if (it.key().endsWith(QLatin1String("000"))) { keyPrefix = it.key().left(5); matter = it.value().matter; for (Private::SubjectCodesMap::Iterator it2 = d->subMap.begin(); it2 != d->subMap.end(); ++it2) { if (it2.key().startsWith(keyPrefix) && !it2.key().endsWith(QLatin1String("000"))) { it2.value().matter = matter; } } } } return true; } void SubjectWidget::setSubjectsList(const QStringList& list) { d->subjectsList = list; blockSignals(true); d->subjectsBox->clear(); if (m_subjectsCheck->isEnabled()) m_subjectsCheck->setChecked(false); if (!d->subjectsList.isEmpty()) { d->subjectsBox->insertItems(0, d->subjectsList); if (m_subjectsCheck->isEnabled()) m_subjectsCheck->setChecked(true); } blockSignals(false); if (m_subjectsCheck->isEnabled()) slotSubjectsToggled(m_subjectsCheck->isChecked()); } QStringList SubjectWidget::subjectsList() const { QStringList newSubjects; - for (int i = 0 ; i < d->subjectsBox->count(); i++) + for (int i = 0 ; i < d->subjectsBox->count() ; ++i) { QListWidgetItem* item = d->subjectsBox->item(i); newSubjects.append(item->text()); } return newSubjects; } } // namespace Digikam diff --git a/core/showfoto/setup/showfotosetupmisc.cpp b/core/showfoto/setup/showfotosetupmisc.cpp index f0e5c8e007..b378bfabbc 100644 --- a/core/showfoto/setup/showfotosetupmisc.cpp +++ b/core/showfoto/setup/showfotosetupmisc.cpp @@ -1,292 +1,292 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2005-04-02 * Description : setup Misc tab. * * Copyright (C) 2005-2018 by Gilles Caulier * Copyright (C) 2008 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 "showfotosetupmisc.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include // Local includes #include "digikam_config.h" #include "dlayoutbox.h" #include "dfontselect.h" #include "showfotosettings.h" using namespace Digikam; namespace ShowFoto { class Q_DECL_HIDDEN SetupMisc::Private { public: explicit Private() : tab(0), sidebarTypeLabel(0), applicationStyleLabel(0), applicationIconLabel(0), showSplash(0), nativeFileDialog(0), itemCenter(0), showMimeOverImage(0), showCoordinates(0), sortReverse(0), sidebarType(0), sortOrderComboBox(0), applicationStyle(0), applicationIcon(0), applicationFont(0), settings(ShowfotoSettings::instance()) { } QTabWidget* tab; QLabel* sidebarTypeLabel; QLabel* applicationStyleLabel; QLabel* applicationIconLabel; QCheckBox* showSplash; QCheckBox* nativeFileDialog; QCheckBox* itemCenter; QCheckBox* showMimeOverImage; QCheckBox* showCoordinates; QCheckBox* sortReverse; QComboBox* sidebarType; QComboBox* sortOrderComboBox; QComboBox* applicationStyle; QComboBox* applicationIcon; DFontSelect* applicationFont; ShowfotoSettings* settings; }; // -------------------------------------------------------- SetupMisc::SetupMisc(QWidget* const parent) : QScrollArea(parent), d(new Private) { d->tab = new QTabWidget(viewport()); setWidget(d->tab); setWidgetResizable(true); const int spacing = QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); // -- Application Behavior Options -------------------------------------------------------- QWidget* const behaviourPanel = new QWidget(d->tab); QVBoxLayout* const layout = new QVBoxLayout(behaviourPanel); // -- Sort Order Options -------------------------------------------------------- QGroupBox* const sortOptionsGroup = new QGroupBox(i18n("Images Sort Order"), behaviourPanel); QVBoxLayout* const gLayout4 = new QVBoxLayout(); DHBox* const sortBox = new DHBox(sortOptionsGroup); new QLabel(i18n("Sort images by:"), sortBox); d->sortOrderComboBox = new QComboBox(sortBox); d->sortOrderComboBox->insertItem(SortByDate, i18nc("sort images by date", "Date")); d->sortOrderComboBox->insertItem(SortByName, i18nc("sort images by name", "Name")); d->sortOrderComboBox->insertItem(SortByFileSize, i18nc("sort images by size", "File Size")); d->sortOrderComboBox->setWhatsThis(i18n("Here, select whether newly-loaded " "images are sorted by their date, name, or size on disk.")); d->sortReverse = new QCheckBox(i18n("Reverse ordering"), sortOptionsGroup); d->sortReverse->setWhatsThis(i18n("If this option is enabled, newly-loaded " "images will be sorted in descending order.")); gLayout4->addWidget(sortBox); gLayout4->addWidget(d->sortReverse); sortOptionsGroup->setLayout(gLayout4); // Thumbnails Options ---------------------------------------------------------------------- QGroupBox* const thOptionsGroup = new QGroupBox(i18n("Thumbnails"), behaviourPanel); QVBoxLayout* const gLayout3 = new QVBoxLayout(); d->showMimeOverImage = new QCheckBox(i18n("&Show image Format"), thOptionsGroup); d->showMimeOverImage->setWhatsThis(i18n("Set this option to show image format over image thumbnail.")); d->showCoordinates = new QCheckBox(i18n("&Show Geolocation Indicator"), thOptionsGroup); d->showCoordinates->setWhatsThis(i18n("Set this option to indicate if image has geolocation information.")); d->itemCenter = new QCheckBox(i18n("Scroll current item to center of thumbbar"), thOptionsGroup); gLayout3->addWidget(d->showMimeOverImage); gLayout3->addWidget(d->showCoordinates); gLayout3->addWidget(d->itemCenter); thOptionsGroup->setLayout(gLayout3); // --------------------------------------------------------- layout->setContentsMargins(spacing, spacing, spacing, spacing); layout->setSpacing(spacing); layout->addWidget(sortOptionsGroup); layout->addWidget(thOptionsGroup); layout->addStretch(); d->tab->insertTab(Behaviour, behaviourPanel, i18nc("@title:tab", "Behaviour")); // -- Application Appearance Options -------------------------------------------------------- QWidget* const appearancePanel = new QWidget(d->tab); QVBoxLayout* const layout2 = new QVBoxLayout(appearancePanel); d->showSplash = new QCheckBox(i18n("&Show splash screen at startup"), appearancePanel); d->nativeFileDialog = new QCheckBox(i18n("Use native file dialogs from the system"), appearancePanel); DHBox* const tabStyleHbox = new DHBox(appearancePanel); d->sidebarTypeLabel = new QLabel(i18n("Sidebar tab title:"), tabStyleHbox); d->sidebarType = new QComboBox(tabStyleHbox); d->sidebarType->addItem(i18n("Only For Active Tab"), 0); d->sidebarType->addItem(i18n("For All Tabs"), 1); d->sidebarType->setToolTip(i18n("Set this option to configure how sidebars tab title are visible.")); DHBox* const appStyleHbox = new DHBox(appearancePanel); d->applicationStyleLabel = new QLabel(i18n("Widget style:"), appStyleHbox); d->applicationStyle = new QComboBox(appStyleHbox); d->applicationStyle->setToolTip(i18n("Set this option to choose the default window decoration and looks.")); QStringList styleList = QStyleFactory::keys(); - for (int i = 0 ; i < styleList.count() ; i++) + for (int i = 0 ; i < styleList.count() ; ++i) { d->applicationStyle->addItem(styleList.at(i)); } #ifndef HAVE_APPSTYLE_SUPPORT // See Bug #365262 appStyleHbox->setVisible(false); #endif DHBox* const iconThemeHbox = new DHBox(appearancePanel); d->applicationIconLabel = new QLabel(i18n("Icon theme (changes after restart):"), iconThemeHbox); d->applicationIcon = new QComboBox(iconThemeHbox); d->applicationIcon->setToolTip(i18n("Set this option to choose the default icon theme.")); d->applicationIcon->addItem(i18n("Use Icon Theme From System"), QString()); const QString indexTheme = QLatin1String("/index.theme"); const QString breezeDark = QLatin1String("/breeze-dark"); const QString breeze = QLatin1String("/breeze"); bool foundBreezeDark = false; bool foundBreeze = false; foreach (const QString& path, QIcon::themeSearchPaths()) { if (!foundBreeze && QFile::exists(path + breeze + indexTheme)) { d->applicationIcon->addItem(i18n("Breeze"), breeze.mid(1)); foundBreeze = true; } if (!foundBreezeDark && QFile::exists(path + breezeDark + indexTheme)) { d->applicationIcon->addItem(i18n("Breeze Dark"), breezeDark.mid(1)); foundBreezeDark = true; } } d->applicationFont = new DFontSelect(i18n("Application font:"), appearancePanel); d->applicationFont->setToolTip(i18n("Select here the font used to display text in whole application.")); // -------------------------------------------------------- layout2->setContentsMargins(spacing, spacing, spacing, spacing); layout2->setSpacing(spacing); layout2->addWidget(d->showSplash); layout2->addWidget(d->nativeFileDialog); layout2->addWidget(tabStyleHbox); layout2->addWidget(appStyleHbox); layout2->addWidget(iconThemeHbox); layout2->addWidget(d->applicationFont); layout2->addStretch(); d->tab->insertTab(Appearance, appearancePanel, i18nc("@title:tab", "Appearance")); // -------------------------------------------------------- readSettings(); } SetupMisc::~SetupMisc() { delete d; } void SetupMisc::readSettings() { d->showSplash->setChecked(d->settings->getShowSplash()); d->nativeFileDialog->setChecked(d->settings->getNativeFileDialog()); d->itemCenter->setChecked(d->settings->getItemCenter()); d->showMimeOverImage->setChecked(d->settings->getShowFormatOverThumbnail()); d->showCoordinates->setChecked(d->settings->getShowCoordinates()); d->sidebarType->setCurrentIndex(d->settings->getRightSideBarStyle()); d->sortOrderComboBox->setCurrentIndex(d->settings->getSortRole()); d->sortReverse->setChecked(d->settings->getReverseSort()); #ifdef HAVE_APPSTYLE_SUPPORT d->applicationStyle->setCurrentIndex(d->applicationStyle->findText(d->settings->getApplicationStyle(), Qt::MatchFixedString)); #endif d->applicationIcon->setCurrentIndex(d->applicationIcon->findData(d->settings->getIconTheme())); d->applicationFont->setFont(d->settings->getApplicationFont()); } void SetupMisc::applySettings() { d->settings->setShowSplash(d->showSplash->isChecked()); d->settings->setNativeFileDialog(d->nativeFileDialog->isChecked()); d->settings->setItemCenter(d->itemCenter->isChecked()); d->settings->setShowFormatOverThumbnail(d->showMimeOverImage->isChecked()); d->settings->setShowCoordinates(d->showCoordinates->isChecked()); d->settings->setRightSideBarStyle(d->sidebarType->currentIndex()); d->settings->setSortRole(d->sortOrderComboBox->currentIndex()); d->settings->setReverseSort(d->sortReverse->isChecked()); #ifdef HAVE_APPSTYLE_SUPPORT d->settings->setApplicationStyle(d->applicationStyle->currentText()); #endif d->settings->setIconTheme(d->applicationIcon->currentData().toString()); d->settings->setApplicationFont(d->applicationFont->font()); d->settings->syncConfig(); } } // namespace ShowFoto diff --git a/core/utilities/assistants/htmlgallery/generator/galleryinfo.cpp b/core/utilities/assistants/htmlgallery/generator/galleryinfo.cpp index 8ea2e838da..9b6d50f682 100644 --- a/core/utilities/assistants/htmlgallery/generator/galleryinfo.cpp +++ b/core/utilities/assistants/htmlgallery/generator/galleryinfo.cpp @@ -1,139 +1,139 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2006-04-04 * Description : a tool to generate HTML image galleries * * Copyright (C) 2006-2010 by Aurelien Gateau * Copyright (C) 2012-2018 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 "galleryinfo.h" // KDE includes #include namespace Digikam { static const char* THEME_GROUP_PREFIX = "Theme "; GalleryInfo::GalleryInfo(DInfoInterface* const iface) { m_iface = iface; m_getOption = IMAGES; } GalleryInfo::~GalleryInfo() { } QString GalleryInfo::fullFormatString() const { return getEnumString(QLatin1String("fullFormat")); } QString GalleryInfo::thumbnailFormatString() const { return getEnumString(QLatin1String("thumbnailFormat")); } QString GalleryInfo::getThemeParameterValue(const QString& theme, const QString& parameter, const QString& defaultValue) const { QString groupName = QLatin1String(THEME_GROUP_PREFIX) + theme; KConfigGroup group = config()->group(groupName); return group.readEntry(parameter, defaultValue); } void GalleryInfo::setThemeParameterValue(const QString& theme, const QString& parameter, const QString& value) { // FIXME: This is hackish, but config() is const :'( KConfig* const localConfig = const_cast(config()); QString groupName = QLatin1String(THEME_GROUP_PREFIX) + theme; KConfigGroup group = localConfig->group(groupName); group.writeEntry(parameter, value); } QString GalleryInfo::getEnumString(const QString& itemName) const { // findItem is not marked const :-( GalleryInfo* const that = const_cast(this); KConfigSkeletonItem* const tmp = that->findItem(itemName); KConfigSkeleton::ItemEnum* const item = dynamic_cast(tmp); Q_ASSERT(item); if (!item) return QString(); int value = item->value(); QList lst = item->choices(); QList::ConstIterator it = lst.constBegin(); QList::ConstIterator end = lst.constEnd(); - for (int pos = 0 ; it != end ; ++it, pos++) + for (int pos = 0 ; it != end ; ++it, ++pos) { if (pos == value) { return (*it).name; } } return QString(); } QDebug operator<<(QDebug dbg, const GalleryInfo& t) { dbg.nospace() << "GalleryInfo::Albums: " << t.m_albumList << ", "; dbg.nospace() << "GalleryInfo::Theme: " << t.theme() << ", "; dbg.nospace() << "GalleryInfo::UseOriginalImageAsFullImage: " << t.useOriginalImageAsFullImage() << ", "; dbg.nospace() << "GalleryInfo::FullResize: " << t.fullResize() << ", "; dbg.nospace() << "GalleryInfo::FullSize: " << t.fullSize() << ", "; dbg.nospace() << "GalleryInfo::FullFormat: " << t.fullFormat() << ", "; dbg.nospace() << "GalleryInfo::FullQuality: " << t.fullQuality() << ", "; dbg.nospace() << "GalleryInfo::CopyOriginalImage: " << t.copyOriginalImage() << ", "; dbg.nospace() << "GalleryInfo::ThumbnailSize: " << t.thumbnailSize() << ", "; dbg.nospace() << "GalleryInfo::ThumbnailFormat: " << t.thumbnailFormat() << ", "; dbg.nospace() << "GalleryInfo::ThumbnailQuality: " << t.thumbnailQuality(); dbg.nospace() << "GalleryInfo::ThumbnailSquare: " << t.thumbnailSquare(); dbg.nospace() << "GalleryInfo::DestUrl: " << t.destUrl(); dbg.nospace() << "GalleryInfo::OpenInBrowser: " << t.openInBrowser(); dbg.nospace() << "GalleryInfo::ImageSelectionTitle: " << t.imageSelectionTitle(); return dbg.space(); } } // namespace Digikam diff --git a/core/utilities/assistants/panorama/manager/panoactionthread.cpp b/core/utilities/assistants/panorama/manager/panoactionthread.cpp index 90143d2d64..fedd30fadc 100644 --- a/core/utilities/assistants/panorama/manager/panoactionthread.cpp +++ b/core/utilities/assistants/panorama/manager/panoactionthread.cpp @@ -1,555 +1,555 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2011-05-23 * Description : a tool to create panorama by fusion of several images. * * Copyright (C) 2011-2016 by Benjamin Girault * Copyright (C) 2009-2018 by Gilles Caulier * Copyright (C) 2009-2011 by Johannes Wienke * * 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 "panoactionthread.h" // Qt includes #include #include #include // KDE includes #include #include #include #include // Local includes #include "digikam_debug.h" #include "panotasks.h" using namespace ThreadWeaver; namespace Digikam { class Q_DECL_HIDDEN PanoActionThread::Private { public: explicit Private(QObject* const parent = 0) : threadQueue(new Queue(parent)) { ThreadWeaver::setDebugLevel(true, 10); } ~Private() { threadQueue->dequeue(); threadQueue->requestAbort(); threadQueue->finish(); } QSharedPointer preprocessingTmpDir; QSharedPointer threadQueue; }; PanoActionThread::PanoActionThread(QObject* const parent) : QObject(parent), d(new Private(this)) { // PanoActionThread init qRegisterMetaType(); d->threadQueue->setMaximumNumberOfThreads(qMax(QThread::idealThreadCount(), 1)); qCDebug(DIGIKAM_GENERAL_LOG) << "Starting Main Thread"; } PanoActionThread::~PanoActionThread() { qCDebug(DIGIKAM_GENERAL_LOG) << "Calling action thread destructor"; delete d; } void PanoActionThread::cancel() { qCDebug(DIGIKAM_GENERAL_LOG) << "Cancel (PanoAction Thread)"; d->threadQueue->dequeue(); d->threadQueue->requestAbort(); } void PanoActionThread::finish() { qCDebug(DIGIKAM_GENERAL_LOG) << "Finish (PanoAction Thread)"; // Wait for all queued jobs to finish d->threadQueue->finish(); d->threadQueue->resume(); } void PanoActionThread::preProcessFiles(const QList& urlList, PanoramaItemUrlsMap& preProcessedMap, QUrl& baseUrl, QUrl& cpFindPtoUrl, QUrl& cpCleanPtoUrl, bool celeste, PanoramaFileType fileType, bool gPano, const QString& huginVersion, const QString& cpCleanPath, const QString& cpFindPath) { QString prefix = QDir::tempPath() + QLatin1Char('/') + QLatin1String("digiKam-panorama-tmp-XXXXXX"); d->preprocessingTmpDir = QSharedPointer(new QTemporaryDir(prefix)); QSharedPointer jobSeq(new Sequence()); QSharedPointer preprocessJobs(new Collection()); int id = 0; for (const QUrl& file : urlList) { preProcessedMap.insert(file, PanoramaPreprocessedUrls()); QObjectDecorator* const t = new QObjectDecorator(new PreProcessTask(d->preprocessingTmpDir->path(), id++, preProcessedMap[file], file)); connect(t, SIGNAL(started(ThreadWeaver::JobPointer)), this, SLOT(slotStarting(ThreadWeaver::JobPointer))); connect(t, SIGNAL(done(ThreadWeaver::JobPointer)), this, SLOT(slotStepDone(ThreadWeaver::JobPointer))); (*preprocessJobs) << JobPointer(t); } (*jobSeq) << preprocessJobs; QObjectDecorator* const pto = new QObjectDecorator(new CreatePtoTask(d->preprocessingTmpDir->path(), fileType, baseUrl, urlList, preProcessedMap, gPano, huginVersion)); connect(pto, SIGNAL(started(ThreadWeaver::JobPointer)), this, SLOT(slotStarting(ThreadWeaver::JobPointer))); connect(pto, SIGNAL(done(ThreadWeaver::JobPointer)), this, SLOT(slotStepDone(ThreadWeaver::JobPointer))); (*jobSeq) << JobPointer(pto); QObjectDecorator* const cpFind = new QObjectDecorator(new CpFindTask(d->preprocessingTmpDir->path(), baseUrl, cpFindPtoUrl, celeste, cpFindPath)); connect(cpFind, SIGNAL(started(ThreadWeaver::JobPointer)), this, SLOT(slotStarting(ThreadWeaver::JobPointer))); connect(cpFind, SIGNAL(done(ThreadWeaver::JobPointer)), this, SLOT(slotStepDone(ThreadWeaver::JobPointer))); (*jobSeq) << JobPointer(cpFind); QObjectDecorator* const cpClean = new QObjectDecorator(new CpCleanTask(d->preprocessingTmpDir->path(), cpFindPtoUrl, cpCleanPtoUrl, cpCleanPath)); connect(cpClean, SIGNAL(started(ThreadWeaver::JobPointer)), this, SLOT(slotStarting(ThreadWeaver::JobPointer))); connect(cpClean, SIGNAL(done(ThreadWeaver::JobPointer)), this, SLOT(slotDone(ThreadWeaver::JobPointer))); (*jobSeq) << JobPointer(cpClean); d->threadQueue->enqueue(jobSeq); } void PanoActionThread::optimizeProject(QUrl& ptoUrl, QUrl& optimizePtoUrl, QUrl& viewCropPtoUrl, bool levelHorizon, bool buildGPano, const QString& autooptimiserPath, const QString& panoModifyPath) { QSharedPointer jobs(new Sequence()); QObjectDecorator* const ot = new QObjectDecorator(new OptimisationTask(d->preprocessingTmpDir->path(), ptoUrl, optimizePtoUrl, levelHorizon, buildGPano, autooptimiserPath)); connect(ot, SIGNAL(started(ThreadWeaver::JobPointer)), this, SLOT(slotStarting(ThreadWeaver::JobPointer))); connect(ot, SIGNAL(done(ThreadWeaver::JobPointer)), this, SLOT(slotStepDone(ThreadWeaver::JobPointer))); (*jobs) << ot; QObjectDecorator* const act = new QObjectDecorator(new AutoCropTask(d->preprocessingTmpDir->path(), optimizePtoUrl, viewCropPtoUrl, buildGPano, panoModifyPath)); connect(act, SIGNAL(started(ThreadWeaver::JobPointer)), this, SLOT(slotStarting(ThreadWeaver::JobPointer))); connect(act, SIGNAL(done(ThreadWeaver::JobPointer)), this, SLOT(slotDone(ThreadWeaver::JobPointer))); (*jobs) << act; d->threadQueue->enqueue(jobs); } void PanoActionThread::generatePanoramaPreview(QSharedPointer ptoData, QUrl& previewPtoUrl, QUrl& previewMkUrl, QUrl& previewUrl, const PanoramaItemUrlsMap& preProcessedUrlsMap, const QString& makePath, const QString& pto2mkPath, const QString& huginExecutorPath, bool hugin2015, const QString& enblendPath, const QString& nonaPath) { QSharedPointer jobs(new Sequence()); QObjectDecorator* const ptoTask = new QObjectDecorator(new CreatePreviewTask(d->preprocessingTmpDir->path(), ptoData, previewPtoUrl, preProcessedUrlsMap)); connect(ptoTask, SIGNAL(started(ThreadWeaver::JobPointer)), this, SLOT(slotStarting(ThreadWeaver::JobPointer))); connect(ptoTask, SIGNAL(done(ThreadWeaver::JobPointer)), this, SLOT(slotStepDone(ThreadWeaver::JobPointer))); (*jobs) << ptoTask; if (!hugin2015) { appendStitchingJobs(jobs, previewPtoUrl, previewMkUrl, previewUrl, preProcessedUrlsMap, JPEG, makePath, pto2mkPath, enblendPath, nonaPath, true); } else { QObjectDecorator* const huginExecutorTask = new QObjectDecorator(new HuginExecutorTask(d->preprocessingTmpDir->path(), previewPtoUrl, previewUrl, JPEG, huginExecutorPath, true)); connect(huginExecutorTask, SIGNAL(started(ThreadWeaver::JobPointer)), this, SLOT(slotStarting(ThreadWeaver::JobPointer))); connect(huginExecutorTask, SIGNAL(done(ThreadWeaver::JobPointer)), this, SLOT(slotStepDone(ThreadWeaver::JobPointer))); (*jobs) << huginExecutorTask; } d->threadQueue->enqueue(jobs); } void PanoActionThread::compileProject(QSharedPointer basePtoData, QUrl& panoPtoUrl, QUrl& mkUrl, QUrl& panoUrl, const PanoramaItemUrlsMap& preProcessedUrlsMap, PanoramaFileType fileType, const QRect& crop, const QString& makePath, const QString& pto2mkPath, const QString& huginExecutorPath, bool hugin2015, const QString& enblendPath, const QString& nonaPath) { QSharedPointer jobs(new Sequence()); QObjectDecorator* const ptoTask = new QObjectDecorator(new CreateFinalPtoTask(d->preprocessingTmpDir->path(), basePtoData, panoPtoUrl, crop)); connect(ptoTask, SIGNAL(started(ThreadWeaver::JobPointer)), this, SLOT(slotStarting(ThreadWeaver::JobPointer))); connect(ptoTask, SIGNAL(done(ThreadWeaver::JobPointer)), this, SLOT(slotStepDone(ThreadWeaver::JobPointer))); (*jobs) << ptoTask; if (!hugin2015) { appendStitchingJobs(jobs, panoPtoUrl, mkUrl, panoUrl, preProcessedUrlsMap, fileType, makePath, pto2mkPath, enblendPath, nonaPath, false); } else { QObjectDecorator* const huginExecutorTask = new QObjectDecorator(new HuginExecutorTask(d->preprocessingTmpDir->path(), panoPtoUrl, panoUrl, fileType, huginExecutorPath, false)); connect(huginExecutorTask, SIGNAL(started(ThreadWeaver::JobPointer)), this, SLOT(slotStarting(ThreadWeaver::JobPointer))); connect(huginExecutorTask, SIGNAL(done(ThreadWeaver::JobPointer)), this, SLOT(slotDone(ThreadWeaver::JobPointer))); (*jobs) << huginExecutorTask; } d->threadQueue->enqueue(jobs); } void PanoActionThread::copyFiles(const QUrl& ptoUrl, const QUrl& panoUrl, const QUrl& finalPanoUrl, const PanoramaItemUrlsMap& preProcessedUrlsMap, bool savePTO, bool addGPlusMetadata) { QObjectDecorator* const t = new QObjectDecorator(new CopyFilesTask(d->preprocessingTmpDir->path(), panoUrl, finalPanoUrl, ptoUrl, preProcessedUrlsMap, savePTO, addGPlusMetadata)); connect(t, SIGNAL(started(ThreadWeaver::JobPointer)), this, SLOT(slotStarting(ThreadWeaver::JobPointer))); connect(t, SIGNAL(done(ThreadWeaver::JobPointer)), this, SLOT(slotDone(ThreadWeaver::JobPointer))); d->threadQueue->enqueue(JobPointer(t)); } void PanoActionThread::slotStarting(JobPointer j) { QSharedPointer dec = j.staticCast(); PanoTask* const t = static_cast(dec->job()); PanoActionData ad; ad.starting = true; ad.action = t->action; ad.id = -1; qCDebug(DIGIKAM_GENERAL_LOG) << "Starting (PanoAction Thread) (action):" << ad.action; if (t->action == PANO_NONAFILE) { CompileMKStepTask* const c = static_cast(t); ad.id = c->id; } else if (t->action == PANO_PREPROCESS_INPUT) { PreProcessTask* const p = static_cast(t); ad.id = p->id; } emit starting(ad); } void PanoActionThread::slotStepDone(JobPointer j) { QSharedPointer dec = j.staticCast(); PanoTask* const t = static_cast(dec->job()); PanoActionData ad; ad.starting = false; ad.action = t->action; ad.id = -1; ad.success = t->success(); ad.message = t->errString; qCDebug(DIGIKAM_GENERAL_LOG) << "Step done (PanoAction Thread) (action, success):" << ad.action << ad.success; if (t->action == PANO_NONAFILE) { CompileMKStepTask* const c = static_cast(t); ad.id = c->id; } else if (t->action == PANO_PREPROCESS_INPUT) { PreProcessTask* const p = static_cast(t); ad.id = p->id; } if (!ad.success) { d->threadQueue->dequeue(); } emit stepFinished(ad); } void PanoActionThread::slotDone(JobPointer j) { QSharedPointer dec = j.staticCast(); PanoTask* const t = static_cast(dec->job()); PanoActionData ad; ad.starting = false; ad.action = t->action; ad.id = -1; ad.success = t->success(); ad.message = t->errString; qCDebug(DIGIKAM_GENERAL_LOG) << "Done (PanoAction Thread) (action, success):" << ad.action << ad.success; if (t->action == PANO_NONAFILE) { CompileMKStepTask* const c = static_cast(t); ad.id = c->id; } else if (t->action == PANO_PREPROCESS_INPUT) { PreProcessTask* const p = static_cast(t); ad.id = p->id; } emit jobCollectionFinished(ad); } void PanoActionThread::appendStitchingJobs(QSharedPointer& js, const QUrl& ptoUrl, QUrl& mkUrl, QUrl& outputUrl, const PanoramaItemUrlsMap& preProcessedUrlsMap, PanoramaFileType fileType, const QString& makePath, const QString& pto2mkPath, const QString& enblendPath, const QString& nonaPath, bool preview) { QSharedPointer jobs(new Sequence()); QObjectDecorator* const createMKTask = new QObjectDecorator(new CreateMKTask(d->preprocessingTmpDir->path(), ptoUrl, mkUrl, outputUrl, fileType, pto2mkPath, preview)); connect(createMKTask, SIGNAL(started(ThreadWeaver::JobPointer)), this, SLOT(slotStarting(ThreadWeaver::JobPointer))); connect(createMKTask, SIGNAL(done(ThreadWeaver::JobPointer)), this, SLOT(slotStepDone(ThreadWeaver::JobPointer))); (*jobs) << createMKTask; - for (int i = 0 ; i < preProcessedUrlsMap.size() ; i++) + for (int i = 0 ; i < preProcessedUrlsMap.size() ; ++i) { QObjectDecorator* const t = new QObjectDecorator(new CompileMKStepTask(d->preprocessingTmpDir->path(), i, mkUrl, nonaPath, enblendPath, makePath, preview)); connect(t, SIGNAL(started(ThreadWeaver::JobPointer)), this, SLOT(slotStarting(ThreadWeaver::JobPointer))); connect(t, SIGNAL(done(ThreadWeaver::JobPointer)), this, SLOT(slotStepDone(ThreadWeaver::JobPointer))); (*jobs) << t; } QObjectDecorator* const compileMKTask = new QObjectDecorator(new CompileMKTask(d->preprocessingTmpDir->path(), mkUrl, outputUrl, nonaPath, enblendPath, makePath, preview)); connect(compileMKTask, SIGNAL(started(ThreadWeaver::JobPointer)), this, SLOT(slotStarting(ThreadWeaver::JobPointer))); connect(compileMKTask, SIGNAL(done(ThreadWeaver::JobPointer)), this, SLOT(slotDone(ThreadWeaver::JobPointer))); (*jobs) << compileMKTask; (*js) << jobs; } } // namespace Digikam diff --git a/core/utilities/assistants/panorama/ptotype/ptotype.cpp b/core/utilities/assistants/panorama/ptotype/ptotype.cpp index e97dc623e2..c1065533aa 100644 --- a/core/utilities/assistants/panorama/ptotype/ptotype.cpp +++ b/core/utilities/assistants/panorama/ptotype/ptotype.cpp @@ -1,452 +1,452 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2012-02-04 * Description : a tool to create panorama by fusion of several images. * This type is based on pto file format described here: * http://hugin.sourceforge.net/docs/nona/nona.txt, and * on pto files produced by Hugin's tools. * * Copyright (C) 2012-2015 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 "ptotype.h" // C/C++ includes #include // Qt includes #include // Local includes #include "digikam_debug.h" namespace Digikam { bool PTOType::createFile(const QString& filepath) { QFile file(filepath); if (!file.open(QFile::WriteOnly | QFile::Truncate)) { qCDebug(DIGIKAM_GENERAL_LOG) << "Cannot open " << filepath << " to write the pto file"; return false; } QTextStream out(&file); out.setRealNumberPrecision(15); // First, the pano line if (project.previousComments.size() > 0) out << project.previousComments.join(QLatin1Char('\n')) << endl; out << "p"; out << " f" << project.projection; if (project.size.width() > 0) out << " w" << project.size.width(); if (project.size.height() > 0) out << " h" << project.size.height(); if (project.fieldOfView > 0) out << " v" << project.fieldOfView; out << " k" << project.photometricReferenceId; out << " E" << project.exposure; out << " R" << project.hdr; switch (project.bitDepth) { case Project::UINT16: out << " T\"UINT16\""; break; case Project::FLOAT: out << " T\"FLOAT\""; break; default: break; } if (project.crop.height() > 1 && project.crop.width() > 1) { out << " S"; out << project.crop.left(); out << "," << project.crop.right(); out << "," << project.crop.top(); out << "," << project.crop.bottom(); } out << " n\""; switch (project.fileFormat.fileType) { case Project::FileFormat::PNG: out << "PNG"; break; case Project::FileFormat::JPEG: out << "JPEG"; out << " q" << project.fileFormat.quality; break; case Project::FileFormat::TIFF: out << "TIFF c:" << project.fileFormat.compressionMethod; break; case Project::FileFormat::TIFF_m: case Project::FileFormat::TIFF_multilayer: if (project.fileFormat.fileType == Project::FileFormat::TIFF_multilayer) { out << "TIFF_multilayer"; } else { out << "TIFF_m"; } out << " c:"; switch (project.fileFormat.compressionMethod) { case Project::FileFormat::PANO_NONE: out << "PANO_NONE"; break; case Project::FileFormat::LZW: out << "LZW"; break; case Project::FileFormat::DEFLATE: out << "DEFLATE"; break; } if (project.fileFormat.savePositions) out << " p1"; if (project.fileFormat.cropped) out << " r:CROP"; break; default: qCCritical(DIGIKAM_GENERAL_LOG) << "Unknown file format for pto file generation!"; file.close(); return false; } out << "\""; out << project.unmatchedParameters.join(QLatin1Char(' ')) << endl; // Second, the stitcher line if (stitcher.previousComments.size() > 0) out << stitcher.previousComments.join(QLatin1Char('\n')) << endl; out << "m"; out << " g" << stitcher.gamma; out << " i" << (int) stitcher.interpolator; if (stitcher.speedUp != Stitcher::SLOW) { out << " f" << 2 - ((int) stitcher.speedUp); } out << " m" << stitcher.huberSigma; out << " p" << stitcher.photometricHuberSigma; out << stitcher.unmatchedParameters.join(QLatin1Char(' ')) << endl; // Third, the images // Note: the order is very important here - for (int id = 0; id < images.size(); id++) + for (int id = 0 ; id < images.size() ; ++id) { const Image &image = images[id]; if (image.previousComments.size() > 0) out << image.previousComments.join(QLatin1Char('\n')) << endl; out << "i"; out << " w" << image.size.width(); out << " h" << image.size.height(); out << " f" << (int) image.lensProjection; if (image.fieldOfView.referenceId >= 0 || image.fieldOfView.value > 0) out << " v" << image.fieldOfView; out << " Ra" << image.photometricEMoRA; out << " Rb" << image.photometricEMoRB; out << " Rc" << image.photometricEMoRC; out << " Rd" << image.photometricEMoRD; out << " Re" << image.photometricEMoRE; out << " Eev" << image.exposure; out << " Er" << image.whiteBalanceRed; out << " Eb" << image.whiteBalanceBlue; out << " r" << image.roll; out << " p" << image.pitch; out << " y" << image.yaw; out << " TrX" << image.mosaicCameraPositionX; out << " TrY" << image.mosaicCameraPositionY; out << " TrZ" << image.mosaicCameraPositionZ; if (version == V2014) { out << " Tpy" << image.mosaicProjectionPlaneYaw; out << " Tpp" << image.mosaicProjectionPlanePitch; } out << " j" << image.stackNumber; out << " a" << image.lensBarrelCoefficientA; out << " b" << image.lensBarrelCoefficientB; out << " c" << image.lensBarrelCoefficientC; out << " d" << image.lensCenterOffsetX; out << " e" << image.lensCenterOffsetY; out << " g" << image.lensShearX; out << " t" << image.lensShearY; const Image* imageVM = ℑ if (image.vignettingMode.referenceId >= 0) imageVM = &images[image.vignettingMode.referenceId]; if (((int) imageVM->vignettingMode.value) & ((int) Image::RADIAL)) { out << " Va" << image.vignettingCorrectionI; out << " Vb" << image.vignettingCorrectionJ; out << " Vc" << image.vignettingCorrectionK; out << " Vd" << image.vignettingCorrectionL; } else { out << " Vf" << image.vignettingFlatfieldImageName; } out << " Vx" << image.vignettingOffsetX; out << " Vy" << image.vignettingOffsetY; out << " Vm" << image.vignettingMode; out << image.unmatchedParameters.join(QLatin1Char(' ')); out << " n\"" << image.fileName << "\""; out << endl; } // Fourth, the variable to optimize - for (int id = 0; id < images.size(); id++) + for (int id = 0 ; id < images.size() ; ++id) { const Image& image = images[id]; foreach (Optimization optim, image.optimizationParameters) { if (optim.previousComments.size() > 0) out << optim.previousComments.join(QLatin1Char('\n')) << endl; out << "v "; switch (optim.parameter) { case Optimization::LENSA: out << 'a'; break; case Optimization::LENSB: out << 'b'; break; case Optimization::LENSC: out << 'c'; break; case Optimization::LENSD: out << 'd'; break; case Optimization::LENSE: out << 'e'; break; case Optimization::LENSHFOV: out << 'v'; break; case Optimization::LENSYAW: out << 'y'; break; case Optimization::LENSPITCH: out << 'p'; break; case Optimization::LENSROLL: out << 'r'; break; case Optimization::EXPOSURE: out << "Eev"; break; case Optimization::WBR: out << "Er"; break; case Optimization::WBB: out << "Eb"; break; case Optimization::VA: out << "Va"; break; case Optimization::VB: out << "Vb"; break; case Optimization::VC: out << "Vc"; break; case Optimization::VD: out << "Vd"; break; case Optimization::VX: out << "Vx"; break; case Optimization::VY: out << "Vy"; break; case Optimization::RA: out << "Ra"; break; case Optimization::RB: out << "Rb"; break; case Optimization::RC: out << "Rc"; break; case Optimization::RD: out << "Rd"; break; case Optimization::RE: out << "Re"; break; case Optimization::UNKNOWN: qCCritical(DIGIKAM_GENERAL_LOG) << "Unknown optimization parameter!"; file.close(); return false; } out << id << endl; } } out << "v" << endl; // Fifth, the masks - for (int id = 0; id < images.size(); id++) + for (int id = 0 ; id < images.size() ; ++id) { const Image& image = images[id]; foreach (Mask mask, image.masks) { if (mask.previousComments.size() > 0) out << mask.previousComments.join(QLatin1Char('\n')) << endl; out << "k i" << id; out << " t" << (int) mask.type; out << " p\""; - for (int pid = 0; pid < mask.hull.size(); pid++) + for (int pid = 0 ; pid < mask.hull.size() ; ++pid) { out << (pid == 0 ? "" : " "); out << mask.hull[pid].x() << ' ' << mask.hull[pid].y(); } out << "\"" << endl; } } // Sixth, the control points foreach (ControlPoint cp, controlPoints) { if (cp.previousComments.size() > 0) out << cp.previousComments.join(QLatin1Char('\n')) << endl; out << "c n" << cp.image1Id; out << " N" << cp.image2Id; out << " x" << cp.p1_x; out << " y" << cp.p1_y; out << " X" << cp.p2_x; out << " Y" << cp.p2_y; out << " t" << cp.type; out << endl; } // Finally the ending comments out << lastComments.join(QLatin1Char('\n')) << endl; file.close(); return true; } /* QPair PTOType::standardDeviation(int image1Id, int image2Id) { double mean_x = 0, mean_y = 0; double n = 0; foreach (ControlPoint cp, controlPoints) { if ((cp.image1Id == image1Id && cp.image2Id == image2Id) || (cp.image1Id == image2Id && cp.image2Id == image1Id)) { mean_x += cp.p2_x - cp.p1_x; mean_y += cp.p2_y - cp.p1_y; n++; } } if (n == 0) { return QPair(0, 0); } mean_x /= n; mean_y /= n; double result = 0; foreach (PTOType::ControlPoint cp, controlPoints) { if ((cp.image1Id == image1Id && cp.image2Id == image2Id) || (cp.image1Id == image2Id && cp.image2Id == image1Id)) { double epsilon_x = (cp.p2_x - cp.p1_x) - mean_x; double epsilon_y = (cp.p2_y - cp.p1_y) - mean_y; result += epsilon_x * epsilon_x + epsilon_y * epsilon_y; } } return QPair(result, n); } QPair PTOType::standardDeviation(int imageId) { int n = 0; double result = 0; for (int i = 0; i < images.size(); ++i) { QPair tmp = standardDeviation(imageId, i); result += tmp.first; n += tmp.second; } return QPair(result, n); } QPair PTOType::standardDeviation() { int n = 0; double result = 0; for (int i = 0; i < images.size(); ++i) { QPair tmp = standardDeviation(i); result += tmp.first; n += tmp.second; } return QPair(result, n); } */ } // namespace Digikam diff --git a/core/utilities/assistants/webservices/dropbox/dbwindow.cpp b/core/utilities/assistants/webservices/dropbox/dbwindow.cpp index 8e1eafbbb8..a48883d382 100644 --- a/core/utilities/assistants/webservices/dropbox/dbwindow.cpp +++ b/core/utilities/assistants/webservices/dropbox/dbwindow.cpp @@ -1,461 +1,461 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2013-11-18 * Description : a tool to export images to Dropbox web service * * Copyright (C) 2013 by Pankaj Kumar * Copyright (C) 2013-2018 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 "dbwindow.h" // Qt includes #include #include #include #include #include // KDE includes #include #include #include // Local includes #include "digikam_debug.h" #include "ditemslist.h" #include "digikam_version.h" #include "dbtalker.h" #include "dbitem.h" #include "dbnewalbumdlg.h" #include "dbwidget.h" namespace Digikam { class Q_DECL_HIDDEN DBWindow::Private { public: explicit Private() { imagesCount = 0; imagesTotal = 0; widget = 0; albumDlg = 0; talker = 0; } unsigned int imagesCount; unsigned int imagesTotal; DBWidget* widget; DBNewAlbumDlg* albumDlg; DBTalker* talker; QString currentAlbumName; QList transferQueue; }; DBWindow::DBWindow(DInfoInterface* const iface, QWidget* const /*parent*/) : WSToolDialog(0), d(new Private) { d->widget = new DBWidget(this, iface, QLatin1String("Dropbox")); d->widget->imagesList()->setIface(iface); setMainWidget(d->widget); setModal(false); setWindowTitle(i18n("Export to Dropbox")); startButton()->setText(i18n("Start Upload")); startButton()->setToolTip(i18n("Start upload to Dropbox")); d->widget->setMinimumSize(700, 500); 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())); d->albumDlg = new DBNewAlbumDlg(this, QLatin1String("Dropbox")); d->talker = new DBTalker(this); connect(d->talker, SIGNAL(signalBusy(bool)), this, SLOT(slotBusy(bool))); connect(d->talker, SIGNAL(signalLinkingFailed()), this, SLOT(slotSignalLinkingFailed())); connect(d->talker, SIGNAL(signalLinkingSucceeded()), this, SLOT(slotSignalLinkingSucceeded())); connect(d->talker, SIGNAL(signalSetUserName(QString)), this, SLOT(slotSetUserName(QString))); connect(d->talker, SIGNAL(signalListAlbumsFailed(QString)), this, SLOT(slotListAlbumsFailed(QString))); connect(d->talker, SIGNAL(signalListAlbumsDone(QList >)), // krazy:exclude=normalize this, SLOT(slotListAlbumsDone(QList >))); // krazy:exclude=normalize connect(d->talker, SIGNAL(signalCreateFolderFailed(QString)), this, SLOT(slotCreateFolderFailed(QString))); connect(d->talker, SIGNAL(signalCreateFolderSucceeded()), this, SLOT(slotCreateFolderSucceeded())); connect(d->talker, SIGNAL(signalAddPhotoFailed(QString)), this, SLOT(slotAddPhotoFailed(QString))); connect(d->talker, SIGNAL(signalAddPhotoSucceeded()), this, SLOT(slotAddPhotoSucceeded())); connect(this, SIGNAL(finished(int)), this, SLOT(slotFinished())); readSettings(); buttonStateChange(false); d->talker->link(); } DBWindow::~DBWindow() { delete d->widget; delete d->albumDlg; delete d->talker; delete d; } void DBWindow::setItemsList(const QList& urls) { d->widget->imagesList()->slotAddImages(urls); } void DBWindow::reactivate() { d->widget->imagesList()->loadImagesFromCurrentSelection(); d->widget->progressBar()->hide(); show(); } void DBWindow::readSettings() { KConfig config; KConfigGroup grp = config.group("Dropbox Settings"); d->currentAlbumName = 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)); winId(); KConfigGroup dialogGroup = config.group("Dropbox Export Dialog"); KWindowConfig::restoreWindowSize(windowHandle(), dialogGroup); resize(windowHandle()->size()); } void DBWindow::writeSettings() { KConfig config; KConfigGroup grp = config.group("Dropbox Settings"); grp.writeEntry("Current Album", d->currentAlbumName); grp.writeEntry("Resize", d->widget->getResizeCheckBox()->isChecked()); grp.writeEntry("Maximum Width", d->widget->getDimensionSpB()->value()); grp.writeEntry("Image Quality", d->widget->getImgQualitySpB()->value()); KConfigGroup dialogGroup = config.group("Dropbox Export Dialog"); KWindowConfig::saveWindowSize(windowHandle(), dialogGroup); config.sync(); } void DBWindow::slotSetUserName(const QString& msg) { d->widget->updateLabels(msg, QLatin1String("")); } void DBWindow::slotListAlbumsDone(const QList >& list) { d->widget->getAlbumsCoB()->clear(); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "slotListAlbumsDone:" << list.size(); - for (int i = 0 ; i < list.size() ; i++) + for (int i = 0 ; i < list.size() ; ++i) { d->widget->getAlbumsCoB()->addItem( QIcon::fromTheme(QLatin1String("system-users")), list.value(i).second, list.value(i).first); if (d->currentAlbumName == list.value(i).first) { d->widget->getAlbumsCoB()->setCurrentIndex(i); } } buttonStateChange(true); d->talker->getUserName(); } void DBWindow::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 DBWindow::slotStartTransfer() { d->widget->imagesList()->clearProcessedStatus(); 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; } if (!(d->talker->authenticated())) { if (QMessageBox::question(this, i18n("Login Failed"), i18n("Authentication failed. Do you want to try again?")) == QMessageBox::Yes) { d->talker->link(); return; } else { return; } } d->transferQueue = d->widget->imagesList()->imageUrls(); if (d->transferQueue.isEmpty()) { return; } d->currentAlbumName = 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(); d->widget->progressBar()->progressScheduled(i18n("Dropbox export"), true, true); d->widget->progressBar()->progressThumbnailChanged( QIcon(QLatin1String("dropbox")).pixmap(22, 22)); uploadNextPhoto(); } void DBWindow::uploadNextPhoto() { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "uploadNextPhoto:" << d->transferQueue.count(); if (d->transferQueue.isEmpty()) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "empty"; d->widget->progressBar()->progressCompleted(); return; } QString imgPath = d->transferQueue.first().toLocalFile(); QString temp = d->currentAlbumName + QLatin1Char('/'); bool res = d->talker->addPhoto(imgPath, temp, d->widget->getResizeCheckBox()->isChecked(), d->widget->getDimensionSpB()->value(), d->widget->getImgQualitySpB()->value()); if (!res) { slotAddPhotoFailed(QLatin1String("")); return; } } void DBWindow::slotAddPhotoFailed(const QString& msg) { if (QMessageBox::question(this, i18n("Uploading Failed"), i18n("Failed to upload photo to Dropbox." "\n%1\n" "Do you want to continue?", msg)) != 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(); } } void DBWindow::slotAddPhotoSucceeded() { // Remove photo uploaded from the list d->widget->imagesList()->removeItemByUrl(d->transferQueue.first()); d->transferQueue.removeFirst(); d->imagesCount++; d->widget->progressBar()->setMaximum(d->imagesTotal); d->widget->progressBar()->setValue(d->imagesCount); uploadNextPhoto(); } void DBWindow::slotImageListChanged() { startButton()->setEnabled(!(d->widget->imagesList()->imageUrls().isEmpty())); } void DBWindow::slotNewAlbumRequest() { if (d->albumDlg->exec() == QDialog::Accepted) { DBFolder newFolder; d->albumDlg->getFolderTitle(newFolder); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "slotNewAlbumRequest:" << newFolder.title; d->currentAlbumName = d->widget->getAlbumsCoB()->itemData(d->widget->getAlbumsCoB()->currentIndex()).toString(); QString temp = d->currentAlbumName + newFolder.title; d->talker->createFolder(temp); } } void DBWindow::slotReloadAlbumsRequest() { d->talker->listFolders(); } void DBWindow::slotSignalLinkingFailed() { slotSetUserName(QLatin1String("")); d->widget->getAlbumsCoB()->clear(); if (QMessageBox::question(this, i18n("Login Failed"), i18n("Authentication failed. Do you want to try again?")) == QMessageBox::Yes) { d->talker->link(); } } void DBWindow::slotSignalLinkingSucceeded() { d->talker->listFolders(); } void DBWindow::slotListAlbumsFailed(const QString& msg) { QMessageBox::critical(this, QString(), i18n("Dropbox call failed:\n%1", msg)); } void DBWindow::slotCreateFolderFailed(const QString& msg) { QMessageBox::critical(this, QString(), i18n("Dropbox call failed:\n%1", msg)); } void DBWindow::slotCreateFolderSucceeded() { d->talker->listFolders(); } void DBWindow::slotTransferCancel() { d->transferQueue.clear(); d->widget->progressBar()->hide(); d->talker->cancel(); } void DBWindow::slotUserChangeRequest() { slotSetUserName(QLatin1String("")); d->widget->getAlbumsCoB()->clear(); d->talker->unLink(); d->talker->link(); } void DBWindow::buttonStateChange(bool state) { d->widget->getNewAlbmBtn()->setEnabled(state); d->widget->getReloadBtn()->setEnabled(state); startButton()->setEnabled(state); } void DBWindow::slotFinished() { writeSettings(); d->widget->imagesList()->listView()->clear(); } void DBWindow::closeEvent(QCloseEvent* e) { if (!e) { return; } slotFinished(); e->accept(); } } // namespace Digikam diff --git a/core/utilities/assistants/webservices/filetransfer/ftexportwidget.cpp b/core/utilities/assistants/webservices/filetransfer/ftexportwidget.cpp index 074211b68f..c7a6b76fcc 100644 --- a/core/utilities/assistants/webservices/filetransfer/ftexportwidget.cpp +++ b/core/utilities/assistants/webservices/filetransfer/ftexportwidget.cpp @@ -1,205 +1,205 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2009-09-28 * Description : a tool to export image to a KIO accessible * location * * Copyright (C) 2006-2009 by Johannes Wienke * Copyright (C) 2011-2018 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 "ftexportwidget.h" // Qt includes #include #include #include #include // KDE includes #include #include #include // Local includes #include "digikam_debug.h" #include "dfiledialog.h" #include "ditemslist.h" #include "wstoolutils.h" #include "dlayoutbox.h" namespace Digikam { class Q_DECL_HIDDEN FTExportWidget::Private { public: explicit Private() { targetLabel = 0; targetDialog = 0; targetSearchButton = 0; targetUrl = 0; imageList = 0; } KUrlComboRequester* targetLabel; DFileDialog* targetDialog; QPushButton* targetSearchButton; QUrl targetUrl; DItemsList* imageList; }; FTExportWidget::FTExportWidget(DInfoInterface* const iface, QWidget* const parent) : QWidget(parent), d(new Private) { // setup remote target selection DHBox* const hbox = new DHBox(this); QLabel* const label = new QLabel(hbox); d->targetLabel = new KUrlComboRequester(hbox); if (d->targetLabel->button()) d->targetLabel->button()->hide(); d->targetLabel->comboBox()->setEditable(true); label->setText(i18n("Target location: ")); d->targetLabel->setWhatsThis(i18n("Sets the target address to upload the images to. " "This can be any address as used in Dolphin or Konqueror, " "e.g. ftp://my.server.org/sub/folder.")); d->targetSearchButton = new QPushButton(i18n("Select export location..."), this); d->targetSearchButton->setIcon(QIcon::fromTheme(QLatin1String("folder-remote"))); // setup image list d->imageList = new DItemsList(this); d->imageList->setIface(iface); d->imageList->loadImagesFromCurrentSelection(); d->imageList->setAllowRAW(true); d->imageList->listView()->setWhatsThis(i18n("This is the list of images to upload " "to the specified target.")); // layout dialog QVBoxLayout* const layout = new QVBoxLayout(this); layout->addWidget(hbox); layout->addWidget(d->targetSearchButton); layout->addWidget(d->imageList); layout->setSpacing(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); layout->setContentsMargins(QMargins()); // ------------------------------------------------------------------------ connect(d->targetSearchButton, SIGNAL(clicked(bool)), this, SLOT(slotShowTargetDialogClicked(bool))); connect(d->targetLabel, SIGNAL(textChanged(QString)), this, SLOT(slotLabelUrlChanged())); // ------------------------------------------------------------------------ updateTargetLabel(); } FTExportWidget::~FTExportWidget() { delete d; } QUrl FTExportWidget::targetUrl() const { return d->targetUrl; } QList FTExportWidget::history() const { QList urls; - for (int i = 0 ; i <= d->targetLabel->comboBox()->count() ; i++) + for (int i = 0 ; i <= d->targetLabel->comboBox()->count() ; ++i) urls << QUrl(d->targetLabel->comboBox()->itemText(i)); return urls; } void FTExportWidget::setHistory(const QList& urls) { d->targetLabel->comboBox()->clear(); foreach(const QUrl& url, urls) d->targetLabel->comboBox()->addUrl(url); } void FTExportWidget::setTargetUrl(const QUrl& url) { d->targetUrl = url; updateTargetLabel(); } void FTExportWidget::slotShowTargetDialogClicked(bool checked) { Q_UNUSED(checked); d->targetDialog = new DFileDialog(this, i18n("Select target..."), d->targetUrl.toString(), i18n("All Files (*)")); d->targetDialog->setAcceptMode(QFileDialog::AcceptSave); d->targetDialog->setFileMode(QFileDialog::Directory); d->targetDialog->setOptions(QFileDialog::ShowDirsOnly); if (d->targetDialog->exec() == QDialog::Accepted) { d->targetUrl = d->targetDialog->selectedUrls().isEmpty() ? QUrl() : d->targetDialog->selectedUrls().at(0); updateTargetLabel(); emit signalTargetUrlChanged(d->targetUrl); } delete d->targetDialog; } void FTExportWidget::updateTargetLabel() { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Call for url " << d->targetUrl.toDisplayString() << ", valid = " << d->targetUrl.isValid(); QString urlString = i18n(""); if (d->targetUrl.isValid()) { urlString = d->targetUrl.toDisplayString(); d->targetLabel->setUrl(QUrl(urlString)); } } DItemsList* FTExportWidget::imagesList() const { return d->imageList; } void FTExportWidget::slotLabelUrlChanged() { d->targetUrl = d->targetLabel->url(); emit signalTargetUrlChanged(d->targetUrl); } } // namespace Digikam diff --git a/core/utilities/assistants/webservices/mediawiki/iface/mediawiki_queryrevision.cpp b/core/utilities/assistants/webservices/mediawiki/iface/mediawiki_queryrevision.cpp index 5f70c50ac1..5655fa3baf 100644 --- a/core/utilities/assistants/webservices/mediawiki/iface/mediawiki_queryrevision.cpp +++ b/core/utilities/assistants/webservices/mediawiki/iface/mediawiki_queryrevision.cpp @@ -1,410 +1,410 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2011-03-22 * Description : a Iface C++ interface * * Copyright (C) 2011-2018 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) { buff.append(QStringLiteral("ids")); } if(properties & QueryRevision::Flags) { if (buff.length()) buff.append(QStringLiteral("|")); buff.append(QStringLiteral("flags")); } if(properties & QueryRevision::Timestamp) { if (buff.length()) buff.append(QStringLiteral("|")); buff.append(QStringLiteral("timestamp")); } if(properties & QueryRevision::User) { if (buff.length()) buff.append(QStringLiteral("|")); buff.append(QStringLiteral("user")); } if(properties & QueryRevision::Comment) { if (buff.length()) buff.append(QStringLiteral("|")); buff.append(QStringLiteral("comment")); } if(properties & QueryRevision::Size) { if (buff.length()) buff.append(QStringLiteral("|")); buff.append(QStringLiteral("size")); } 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++) + 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/utilities/assistants/webservices/vkontakte/vkalbumchooser.cpp b/core/utilities/assistants/webservices/vkontakte/vkalbumchooser.cpp index e1fcbb2b93..8f46eedb26 100644 --- a/core/utilities/assistants/webservices/vkontakte/vkalbumchooser.cpp +++ b/core/utilities/assistants/webservices/vkontakte/vkalbumchooser.cpp @@ -1,447 +1,447 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2011-02-19 * Description : a tool to export images to VKontakte web service * * Copyright (C) 2011-2015 by Alexander Potashev * Copyright (C) 2011-2018 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 "vkalbumchooser.h" // Qt includes #include #include #include #include #include #include // KDE includes #include // LibKvkontakte includes #include #include #include #include #include // Local includes #include "vknewalbumdlg.h" namespace Digikam { class Q_DECL_HIDDEN VKAlbumChooser::Private { public: explicit Private() { albumsCombo = 0; newAlbumButton = 0; reloadAlbumsButton = 0; editAlbumButton = 0; deleteAlbumButton = 0; albumToSelect = -1; vkapi = 0; } QComboBox* albumsCombo; QPushButton* newAlbumButton; QPushButton* reloadAlbumsButton; QToolButton* editAlbumButton; QToolButton* deleteAlbumButton; QList albums; /** Album with this "aid" will * be selected in slotAlbumsReloadDone() */ int albumToSelect; Vkontakte::VkApi* vkapi; }; VKAlbumChooser::VKAlbumChooser(QWidget* const parent, Vkontakte::VkApi* const vkapi) : QGroupBox(i18nc("@title:group Header above controls for managing albums", "Album"), parent), d(new Private) { d->vkapi = vkapi; setWhatsThis(i18n("This is the VKontakte album that will be used for the transfer.")); QVBoxLayout* const albumsBoxLayout = new QVBoxLayout(this); d->albumsCombo = new QComboBox(this); d->albumsCombo->setEditable(false); d->newAlbumButton = new QPushButton(QIcon::fromTheme(QLatin1String("list-add")), i18n("New Album"), this); d->newAlbumButton->setToolTip(i18n("Create new VKontakte album")); d->reloadAlbumsButton = new QPushButton(QIcon::fromTheme(QLatin1String("view-refresh")), i18nc("reload albums list", "Reload"), this); d->reloadAlbumsButton->setToolTip(i18n("Reload albums list")); d->editAlbumButton = new QToolButton(this); d->editAlbumButton->setToolTip(i18n("Edit selected album")); d->editAlbumButton->setEnabled(false); d->editAlbumButton->setIcon(QIcon::fromTheme(QLatin1String("document-edit"))); d->deleteAlbumButton = new QToolButton(this); d->deleteAlbumButton->setToolTip(i18n("Delete selected album")); d->deleteAlbumButton->setEnabled(false); d->deleteAlbumButton->setIcon(QIcon::fromTheme(QLatin1String("edit-delete"))); QWidget* const currentAlbumWidget = new QWidget(this); QHBoxLayout* const currentAlbumWidgetLayout = new QHBoxLayout(currentAlbumWidget); currentAlbumWidgetLayout->setContentsMargins(0, 0, 0, 0); currentAlbumWidgetLayout->addWidget(d->albumsCombo); currentAlbumWidgetLayout->addWidget(d->editAlbumButton); currentAlbumWidgetLayout->addWidget(d->deleteAlbumButton); QWidget* const albumButtons = new QWidget(this); QHBoxLayout* const albumButtonsLayout = new QHBoxLayout(albumButtons); albumButtonsLayout->setContentsMargins(0, 0, 0, 0); albumButtonsLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum)); albumButtonsLayout->addWidget(d->newAlbumButton); albumButtonsLayout->addWidget(d->reloadAlbumsButton); albumsBoxLayout->addWidget(currentAlbumWidget); albumsBoxLayout->addWidget(albumButtons); connect(d->newAlbumButton, SIGNAL(clicked()), this, SLOT(slotNewAlbumRequest())); connect(d->editAlbumButton, SIGNAL(clicked()), this, SLOT(slotEditAlbumRequest())); connect(d->deleteAlbumButton, SIGNAL(clicked()), this, SLOT(slotDeleteAlbumRequest())); connect(d->reloadAlbumsButton, SIGNAL(clicked()), this, SLOT(slotReloadAlbumsRequest())); connect(d->vkapi, SIGNAL(authenticated()), this, SLOT(slotReloadAlbumsRequest())); } VKAlbumChooser::~VKAlbumChooser() { delete d; } /** * @brief Clear the list of albums **/ void VKAlbumChooser::clearList() { d->albumsCombo->clear(); } bool VKAlbumChooser::getCurrentAlbumInfo(VKNewAlbumDlg::AlbumProperties& out) { int index = d->albumsCombo->currentIndex(); if (index >= 0) { Vkontakte::AlbumInfo album = d->albums.at(index); out.title = album.title(); out.description = album.description(); out.privacy = album.privacy(); out.commentPrivacy = album.commentPrivacy(); return true; } else { return false; } } bool VKAlbumChooser::getCurrentAlbumId(int& out) { int index = d->albumsCombo->currentIndex(); if (index >= 0) { Vkontakte::AlbumInfo album = d->albums.at(index); out = album.aid(); return true; } else { return false; } } void VKAlbumChooser::selectAlbum(int aid) { /* * If the album list is not ready yet, select this album later */ d->albumToSelect = aid; - for (int i = 0 ; i < d->albums.size() ; i ++) + for (int i = 0 ; i < d->albums.size() ; ++i) { if (d->albums.at(i).aid() == aid) { d->albumsCombo->setCurrentIndex(i); break; } } } //------------------------------ void VKAlbumChooser::slotNewAlbumRequest() { QPointer dlg = new VKNewAlbumDlg(this); if (dlg->exec() == QDialog::Accepted) { updateBusyStatus(true); slotStartAlbumCreation(dlg->album()); } delete dlg; } void VKAlbumChooser::slotStartAlbumCreation(const VKNewAlbumDlg::AlbumProperties &album) { Vkontakte::CreateAlbumJob* const job = new Vkontakte::CreateAlbumJob(d->vkapi->accessToken(), album.title, album.description, album.privacy, album.commentPrivacy); connect(job, SIGNAL(result(KJob*)), this, SLOT(slotAlbumCreationDone(KJob*))); job->start(); } void VKAlbumChooser::slotAlbumCreationDone(KJob* kjob) { Vkontakte::CreateAlbumJob* const job = dynamic_cast(kjob); Q_ASSERT(job); if (job == 0 || job->error()) { handleVkError(job); updateBusyStatus(false); } else { // Select the newly created album in the combobox later (in "slotAlbumsReloadDone()") d->albumToSelect = job->album().aid(); slotStartAlbumsReload(); updateBusyStatus(true); } } //------------------------------ void VKAlbumChooser::slotEditAlbumRequest() { VKNewAlbumDlg::AlbumProperties album; int aid = 0; if (!getCurrentAlbumInfo(album) || !getCurrentAlbumId(aid)) { return; } QPointer dlg = new VKNewAlbumDlg(this, album); if (dlg->exec() == QDialog::Accepted) { updateBusyStatus(true); slotStartAlbumEditing(aid, dlg->album()); } delete dlg; } void VKAlbumChooser::slotStartAlbumEditing(int aid, const VKNewAlbumDlg::AlbumProperties& album) { // Select the same album again in the combobox later (in "slotAlbumsReloadDone()") d->albumToSelect = aid; Vkontakte::EditAlbumJob* const job = new Vkontakte::EditAlbumJob(d->vkapi->accessToken(), aid, album.title, album.description, album.privacy, album.commentPrivacy); connect(job, SIGNAL(result(KJob*)), this, SLOT(slotAlbumEditingDone(KJob*))); job->start(); } void VKAlbumChooser::slotAlbumEditingDone(KJob* kjob) { Vkontakte::EditAlbumJob* const job = dynamic_cast(kjob); Q_ASSERT(job); if (job && job->error()) { handleVkError(job); return; } slotStartAlbumsReload(); updateBusyStatus(true); } //------------------------------ void VKAlbumChooser::slotDeleteAlbumRequest() { VKNewAlbumDlg::AlbumProperties album; int aid = 0; if (!getCurrentAlbumInfo(album) || !getCurrentAlbumId(aid)) { return; } if (QMessageBox::question(this, i18nc("@title:window", "Confirm Album Deletion"), i18n("Are you sure you want to remove the album %1 " "including all photos in it?", album.title)) != QMessageBox::Yes) { return; } updateBusyStatus(true); slotStartAlbumDeletion(aid); } void VKAlbumChooser::slotStartAlbumDeletion(int aid) { Vkontakte::DeleteAlbumJob* const job = new Vkontakte::DeleteAlbumJob(d->vkapi->accessToken(), aid); connect(job, SIGNAL(result(KJob*)), this, SLOT(slotAlbumDeletionDone(KJob*))); job->start(); } void VKAlbumChooser::slotAlbumDeletionDone(KJob* kjob) { Vkontakte::DeleteAlbumJob* const job = dynamic_cast(kjob); Q_ASSERT(job); if (job && job->error()) { handleVkError(job); return; } slotStartAlbumsReload(); updateBusyStatus(true); } //------------------------------ void VKAlbumChooser::slotReloadAlbumsRequest() { updateBusyStatus(true); int aid = 0; if (getCurrentAlbumId(aid)) { d->albumToSelect = aid; } slotStartAlbumsReload(); } void VKAlbumChooser::slotStartAlbumsReload() { updateBusyStatus(true); Vkontakte::AlbumListJob* const job = new Vkontakte::AlbumListJob(d->vkapi->accessToken()); connect(job, SIGNAL(result(KJob*)), this, SLOT(slotAlbumsReloadDone(KJob*))); job->start(); } void VKAlbumChooser::slotAlbumsReloadDone(KJob* kjob) { Vkontakte::AlbumListJob* const job = dynamic_cast(kjob); Q_ASSERT(job); if (job && job->error()) { handleVkError(job); return; } if (!job) return; d->albumsCombo->clear(); d->albums = job->list(); foreach (const Vkontakte::AlbumInfo &album, d->albums) d->albumsCombo->addItem(QIcon::fromTheme(QLatin1String("folder-image")), album.title()); if (d->albumToSelect != -1) { selectAlbum(d->albumToSelect); d->albumToSelect = -1; } d->albumsCombo->setEnabled(true); if (!d->albums.isEmpty()) { d->editAlbumButton->setEnabled(true); d->deleteAlbumButton->setEnabled(true); } updateBusyStatus(false); } //------------------------------ void VKAlbumChooser::updateBusyStatus(bool busy) { setEnabled(!busy); } // TODO: share this code with `vkwindow.cpp` void VKAlbumChooser::handleVkError(KJob* kjob) { QMessageBox::critical(this, i18nc("@title:window", "Request to VKontakte failed"), kjob == 0 ? i18n("Internal error: Null pointer to KJob instance.") : kjob->errorText()); } } // namespace Digikam diff --git a/core/utilities/geolocation/editor/bookmark/bookmarknode.cpp b/core/utilities/geolocation/editor/bookmark/bookmarknode.cpp index 29090914ab..0c61cb3f77 100644 --- a/core/utilities/geolocation/editor/bookmark/bookmarknode.cpp +++ b/core/utilities/geolocation/editor/bookmark/bookmarknode.cpp @@ -1,360 +1,360 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2017-05-15 * Description : a node container for GPS bookmarks * * Copyright (C) 2017-2018 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 "bookmarknode.h" // Qt includes #include #include // KDE includes #include // Local includes #include "digikam_debug.h" namespace Digikam { class Q_DECL_HIDDEN BookmarkNode::Private { public: explicit Private() : parent(0), type(BookmarkNode::Root) { } BookmarkNode* parent; Type type; QList children; }; BookmarkNode::BookmarkNode(BookmarkNode::Type type, BookmarkNode* const parent) : d(new Private) { expanded = false; d->parent = parent; d->type = type; if (parent) parent->add(this); } BookmarkNode::~BookmarkNode() { if (d->parent) d->parent->remove(this); qDeleteAll(d->children); d->parent = 0; d->type = BookmarkNode::Root; delete d; } bool BookmarkNode::operator==(const BookmarkNode& other) const { if (url != other.url || title != other.title || desc != other.desc || expanded != other.expanded || dateAdded != other.dateAdded || d->type != other.d->type || d->children.count() != other.d->children.count()) { return false; } - for (int i = 0 ; i < d->children.count() ; i++) + for (int i = 0 ; i < d->children.count() ; ++i) { if (!((*(d->children[i])) == (*(other.d->children[i])))) return false; } return true; } BookmarkNode::Type BookmarkNode::type() const { return d->type; } void BookmarkNode::setType(Type type) { d->type = type; } QList BookmarkNode::children() const { return d->children; } BookmarkNode* BookmarkNode::parent() const { return d->parent; } void BookmarkNode::add(BookmarkNode* const child, int offset) { Q_ASSERT(child->d->type != Root); if (child->d->parent) child->d->parent->remove(child); child->d->parent = this; if (offset == -1) offset = d->children.size(); d->children.insert(offset, child); } void BookmarkNode::remove(BookmarkNode* const child) { child->d->parent = 0; d->children.removeAll(child); } // ------------------------------------------------------- XbelReader::XbelReader() { } BookmarkNode* XbelReader::read(const QString& fileName) { QFile file(fileName); if (!file.exists() || !file.open(QFile::ReadOnly)) { BookmarkNode* const root = new BookmarkNode(BookmarkNode::Root); BookmarkNode* const folder = new BookmarkNode(BookmarkNode::RootFolder, root); folder->title = i18n("Bookmark folder"); return root; } return read(&file, true); } BookmarkNode* XbelReader::read(QIODevice* const device, bool addRootFolder) { BookmarkNode* const root = new BookmarkNode(BookmarkNode::Root); setDevice(device); if (readNextStartElement()) { QString version = attributes().value(QLatin1String("version")).toString(); if (name() == QLatin1String("xbel") && (version.isEmpty() || version == QLatin1String("1.0"))) { if (addRootFolder) { BookmarkNode* const folder = new BookmarkNode(BookmarkNode::RootFolder, root); folder->title = i18n("Bookmark folder"); readXBEL(folder); } else { readXBEL(root); } } else { raiseError(i18n("The file is not an XBEL version 1.0 file.")); } } return root; } void XbelReader::readXBEL(BookmarkNode* const parent) { Q_ASSERT(isStartElement() && name() == QLatin1String("xbel")); while (readNextStartElement()) { if (name() == QLatin1String("folder")) readFolder(parent); else if (name() == QLatin1String("bookmark")) readBookmarkNode(parent); else if (name() == QLatin1String("separator")) readSeparator(parent); else skipCurrentElement(); } } void XbelReader::readFolder(BookmarkNode* const parent) { Q_ASSERT(isStartElement() && name() == QLatin1String("folder")); BookmarkNode* const folder = new BookmarkNode(BookmarkNode::Folder, parent); folder->expanded = (attributes().value(QLatin1String("folded")) == QLatin1String("no")); while (readNextStartElement()) { if (name() == QLatin1String("title")) readTitle(folder); else if (name() == QLatin1String("desc")) readDescription(folder); else if (name() == QLatin1String("folder")) readFolder(folder); else if (name() == QLatin1String("bookmark")) readBookmarkNode(folder); else if (name() == QLatin1String("separator")) readSeparator(folder); else skipCurrentElement(); } } void XbelReader::readTitle(BookmarkNode* const parent) { Q_ASSERT(isStartElement() && name() == QLatin1String("title")); parent->title = readElementText(); } void XbelReader::readDescription(BookmarkNode* const parent) { Q_ASSERT(isStartElement() && name() == QLatin1String("desc")); parent->desc = readElementText(); } void XbelReader::readSeparator(BookmarkNode* const parent) { new BookmarkNode(BookmarkNode::Separator, parent); // empty elements have a start and end element readNext(); } void XbelReader::readBookmarkNode(BookmarkNode* const parent) { Q_ASSERT(isStartElement() && name() == QLatin1String("bookmark")); BookmarkNode* const bookmark = new BookmarkNode(BookmarkNode::Bookmark, parent); bookmark->url = attributes().value(QLatin1String("href")).toString(); QString date = attributes().value(QLatin1String("added")).toString(); bookmark->dateAdded = QDateTime::fromString(date, Qt::ISODate); while (readNextStartElement()) { if (name() == QLatin1String("title")) readTitle(bookmark); else if (name() == QLatin1String("desc")) readDescription(bookmark); else skipCurrentElement(); } if (bookmark->title.isEmpty()) bookmark->title = i18n("Unknown title"); } // ------------------------------------------------------- XbelWriter::XbelWriter() { setAutoFormatting(true); } bool XbelWriter::write(const QString& fileName, const BookmarkNode* const root) { QFile file(fileName); if (!root || !file.open(QFile::WriteOnly)) return false; return write(&file, root); } bool XbelWriter::write(QIODevice* const device, const BookmarkNode* const root) { setDevice(device); writeStartDocument(); writeDTD(QLatin1String("")); writeStartElement(QLatin1String("xbel")); writeAttribute(QLatin1String("version"), QLatin1String("1.0")); if (root->type() == BookmarkNode::Root) { BookmarkNode* const rootFolder = root->children().first(); - for (int i = 0 ; i < rootFolder->children().count() ; i++) + for (int i = 0 ; i < rootFolder->children().count() ; ++i) writeItem(rootFolder->children().at(i)); } else { writeItem(root); } writeEndDocument(); return true; } void XbelWriter::writeItem(const BookmarkNode* const parent) { switch (parent->type()) { case BookmarkNode::Folder: writeStartElement(QLatin1String("folder")); writeAttribute(QLatin1String("folded"), parent->expanded ? QLatin1String("no") : QLatin1String("yes")); writeTextElement(QLatin1String("title"), parent->title); - for (int i = 0 ; i < parent->children().count() ; i++) + for (int i = 0 ; i < parent->children().count() ; ++i) writeItem(parent->children().at(i)); writeEndElement(); break; case BookmarkNode::Bookmark: writeStartElement(QLatin1String("bookmark")); if (!parent->url.isEmpty()) writeAttribute(QLatin1String("href"), parent->url); if (parent->dateAdded.isValid()) writeAttribute(QLatin1String("added"), parent->dateAdded.toString(Qt::ISODate)); if (!parent->desc.isEmpty()) writeAttribute(QLatin1String("desc"), parent->desc); writeTextElement(QLatin1String("title"), parent->title); writeEndElement(); break; case BookmarkNode::Separator: writeEmptyElement(QLatin1String("separator")); break; default: break; } } } // namespace Digikam diff --git a/core/utilities/geolocation/editor/bookmark/bookmarksdlg.cpp b/core/utilities/geolocation/editor/bookmark/bookmarksdlg.cpp index 5ea6b3ac09..b16420db01 100644 --- a/core/utilities/geolocation/editor/bookmark/bookmarksdlg.cpp +++ b/core/utilities/geolocation/editor/bookmark/bookmarksdlg.cpp @@ -1,480 +1,480 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2017-05-15 * Description : Managemenet dialogs for GPS bookmarks * * Copyright (C) 2017-2018 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 "bookmarksdlg.h" // Qt includes #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 "bookmarksmngr.h" #include "bookmarknode.h" #include "dxmlguiwindow.h" #include "itempropertiesgpstab.h" #include "gpsiteminfo.h" namespace Digikam { class Q_DECL_HIDDEN AddBookmarkDialog::Private { public: explicit Private() : manager(0), proxyModel(0), location(0), title(0), desc(0) { } QString url; BookmarksManager* manager; AddBookmarkProxyModel* proxyModel; QComboBox* location; QLineEdit* title; QLineEdit* desc; }; AddBookmarkDialog::AddBookmarkDialog(const QString& url, const QString& title, QWidget* const parent, BookmarksManager* const mngr) : QDialog(parent), d(new Private) { d->url = url; d->manager = mngr; setWindowFlags(Qt::Sheet); setWindowTitle(tr2i18n("Add Bookmark", 0)); setObjectName(QLatin1String("AddBookmarkDialog")); resize(350, 300); QLabel* const label = new QLabel(this); label->setText(i18n("Type a name and a comment for the bookmark, " "and choose where to keep it.")); label->setTextFormat(Qt::PlainText); label->setWordWrap(true); d->title = new QLineEdit(this); d->title->setPlaceholderText(i18n("Bookmark title")); d->title->setText(title); d->desc = new QLineEdit(this); d->desc->setPlaceholderText(i18n("Bookmark comment")); d->location = new QComboBox(this); QSpacerItem* const verticalSpacer = new QSpacerItem(20, 2, QSizePolicy::Minimum, QSizePolicy::Expanding); QDialogButtonBox* const buttonBox = new QDialogButtonBox(this); buttonBox->setOrientation(Qt::Horizontal); buttonBox->setStandardButtons(QDialogButtonBox::Cancel | QDialogButtonBox::Ok); buttonBox->setCenterButtons(false); QVBoxLayout* const vbox = new QVBoxLayout(this); vbox->addWidget(label); vbox->addWidget(d->title); vbox->addWidget(d->desc); vbox->addWidget(d->location); vbox->addItem(verticalSpacer); vbox->addWidget(buttonBox); QTreeView* const view = new QTreeView(this); d->proxyModel = new AddBookmarkProxyModel(this); BookmarksModel* const model = d->manager->bookmarksModel(); d->proxyModel->setSourceModel(model); view->setModel(d->proxyModel); view->expandAll(); view->header()->setStretchLastSection(true); view->header()->hide(); view->setItemsExpandable(false); view->setRootIsDecorated(false); view->setIndentation(10); view->show(); BookmarkNode* const menu = d->manager->bookmarks(); QModelIndex idx = d->proxyModel->mapFromSource(model->index(menu)); view->setCurrentIndex(idx); d->location->setModel(d->proxyModel); d->location->setView(view); connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); } AddBookmarkDialog::~AddBookmarkDialog() { delete d; } void AddBookmarkDialog::accept() { QModelIndex index = d->location->view()->currentIndex(); index = d->proxyModel->mapToSource(index); if (!index.isValid()) index = d->manager->bookmarksModel()->index(0, 0); BookmarkNode* const parent = d->manager->bookmarksModel()->node(index); BookmarkNode* const bookmark = new BookmarkNode(BookmarkNode::Bookmark); bookmark->url = d->url; bookmark->title = d->title->text(); bookmark->desc = d->desc->text(); bookmark->dateAdded = QDateTime::currentDateTime(); d->manager->addBookmark(parent, bookmark); d->manager->save(); QDialog::accept(); } // ---------------------------------------------------------------- class Q_DECL_HIDDEN BookmarksDialog::Private { public: explicit Private() : manager(0), bookmarksModel(0), proxyModel(0), search(0), tree(0), mapView(0) { } BookmarksManager* manager; BookmarksModel* bookmarksModel; TreeProxyModel* proxyModel; SearchTextBar* search; QTreeView* tree; ItemPropertiesGPSTab* mapView; }; BookmarksDialog::BookmarksDialog(QWidget* const parent, BookmarksManager* const mngr) : QDialog(parent), d(new Private) { d->manager = mngr; setObjectName(QLatin1String("GeolocationBookmarksEditDialog")); setAttribute(Qt::WA_DeleteOnClose); setWindowTitle(i18n("Edit Geolocation Bookmarks")); resize(750, 450); d->search = new SearchTextBar(this, QLatin1String("DigikamBookmarksGeolocationSearchBar")); d->search->setObjectName(QLatin1String("search")); d->tree = new QTreeView(this); d->tree->setUniformRowHeights(true); d->tree->setSelectionBehavior(QAbstractItemView::SelectRows); d->tree->setSelectionMode(QAbstractItemView::ContiguousSelection); d->tree->setTextElideMode(Qt::ElideMiddle); d->tree->setDragDropMode(QAbstractItemView::InternalMove); d->tree->setAlternatingRowColors(true); d->tree->setContextMenuPolicy(Qt::CustomContextMenu); d->mapView = new ItemPropertiesGPSTab(this); QPushButton* const removeButton = new QPushButton(this); removeButton->setText(i18n("&Remove")); QPushButton* const addFolderButton = new QPushButton(this); addFolderButton->setText(i18n("Add Folder")); QSpacerItem* const spacerItem1 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); QDialogButtonBox* const buttonBox = new QDialogButtonBox(this); buttonBox->setStandardButtons(QDialogButtonBox::Cancel | QDialogButtonBox::Ok); QHBoxLayout* const hbox = new QHBoxLayout(); hbox->addWidget(removeButton); hbox->addWidget(addFolderButton); hbox->addItem(spacerItem1); hbox->addWidget(buttonBox); QGridLayout* const grid = new QGridLayout(this); grid->addWidget(d->search, 0, 0, 1, 2); grid->addWidget(d->tree, 1, 0, 1, 2); grid->addLayout(hbox, 2, 0, 1, 3); grid->addWidget(d->mapView, 0, 2, 2, 1); grid->setColumnStretch(1, 10); d->bookmarksModel = d->manager->bookmarksModel(); d->proxyModel = new TreeProxyModel(this); d->proxyModel->setSourceModel(d->bookmarksModel); d->tree->setModel(d->proxyModel); d->tree->setExpanded(d->proxyModel->index(0, 0), true); d->tree->header()->setSectionResizeMode(QHeaderView::Stretch); connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); connect(d->search, SIGNAL(textChanged(QString)), d->proxyModel, SLOT(setFilterFixedString(QString))); connect(d->proxyModel, SIGNAL(signalFilterAccepts(bool)), d->search, SLOT(slotSearchResult(bool))); connect(removeButton, SIGNAL(clicked()), this, SLOT(slotRemoveOne())); connect(d->tree, SIGNAL(clicked(QModelIndex)), this, SLOT(slotOpenInMap(QModelIndex))); connect(d->tree, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(slotCustomContextMenuRequested(QPoint))); connect(addFolderButton, SIGNAL(clicked()), this, SLOT(slotNewFolder())); readSettings(); } BookmarksDialog::~BookmarksDialog() { delete d; } void BookmarksDialog::accept() { d->manager->save(); QDialog::accept(); } void BookmarksDialog::closeEvent(QCloseEvent* e) { if (!e) return; saveSettings(); e->accept(); } bool BookmarksDialog::saveExpandedNodes(const QModelIndex& parent) { bool changed = false; - for (int i = 0 ; i < d->proxyModel->rowCount(parent) ; i++) + for (int i = 0 ; i < d->proxyModel->rowCount(parent) ; ++i) { QModelIndex child = d->proxyModel->index(i, 0, parent); QModelIndex sourceIndex = d->proxyModel->mapToSource(child); BookmarkNode* const childNode = d->bookmarksModel->node(sourceIndex); bool wasExpanded = childNode->expanded; if (d->tree->isExpanded(child)) { childNode->expanded = true; changed |= saveExpandedNodes(child); } else { childNode->expanded = false; } changed |= (wasExpanded != childNode->expanded); } return changed; } void BookmarksDialog::expandNodes(BookmarkNode* const node) { - for (int i = 0 ; i < node->children().count() ; i++) + for (int i = 0 ; i < node->children().count() ; ++i) { BookmarkNode* const childNode = node->children()[i]; if (childNode->expanded) { QModelIndex idx = d->bookmarksModel->index(childNode); idx = d->proxyModel->mapFromSource(idx); d->tree->setExpanded(idx, true); expandNodes(childNode); } } } void BookmarksDialog::slotCustomContextMenuRequested(const QPoint& pos) { QModelIndex index = d->tree->indexAt(pos); index = index.sibling(index.row(), 0); if (index.isValid()) { index = d->proxyModel->mapToSource(index); BookmarkNode* const node = d->manager->bookmarksModel()->node(index); if (node && node->type() != BookmarkNode::RootFolder) { QMenu menu; menu.addAction(i18n("Remove"), this, SLOT(slotRemoveOne())); menu.exec(QCursor::pos()); } } } void BookmarksDialog::slotOpenInMap(const QModelIndex& index) { if (!index.isValid()) { d->mapView->setGPSInfoList(GPSItemInfo::List()); d->mapView->setActive(false); return; } QModelIndexList list = d->tree->selectionModel()->selectedIndexes(); GPSItemInfo::List ilst; foreach (const QModelIndex& item, list) { QUrl url = item.sibling(index.row(), 1) .data(BookmarksModel::UrlRole).toUrl(); bool ok = false; GeoCoordinates coordinate = GeoCoordinates::fromGeoUrl(url.toString(), &ok); if (ok) { GPSItemInfo gpsInfo; gpsInfo.coordinates = coordinate; gpsInfo.dateTime = item.sibling(index.row(), 1) .data(BookmarksModel::DateAddedRole).toDateTime(); gpsInfo.rating = -1; gpsInfo.url = url; ilst << gpsInfo; } } d->mapView->setGPSInfoList(GPSItemInfo::List() << ilst); d->mapView->setActive(!ilst.isEmpty()); } void BookmarksDialog::slotNewFolder() { QModelIndex currentIndex = d->tree->currentIndex(); QModelIndex idx = currentIndex; if (idx.isValid() && !idx.model()->hasChildren(idx)) idx = idx.parent(); if (!idx.isValid()) idx = d->tree->rootIndex(); idx = d->proxyModel->mapToSource(idx); BookmarkNode* const parent = d->manager->bookmarksModel()->node(idx); BookmarkNode* const node = new BookmarkNode(BookmarkNode::Folder); node->title = i18n("New Folder"); d->manager->addBookmark(parent, node, currentIndex.row() + 1); } void BookmarksDialog::slotRemoveOne() { QModelIndex index = d->tree->currentIndex(); if (index.isValid()) { index = d->proxyModel->mapToSource(index); BookmarkNode* const node = d->manager->bookmarksModel()->node(index); if (node->type() == BookmarkNode::RootFolder) { return; } if (QMessageBox::question(this, qApp->applicationName(), i18nc("@info", "Do you want to remove \"%1\" " "from your Bookmarks collection?", node->title), QMessageBox::Yes | QMessageBox::No ) == QMessageBox::No) { return; } d->manager->removeBookmark(node); } } void BookmarksDialog::readSettings() { expandNodes(d->manager->bookmarks()); KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(objectName()); KConfigGroup groupGPSTab = KConfigGroup(&group, QLatin1String("GPS Properties Tab")); d->mapView->readSettings(groupGPSTab); KConfigGroup groupDialog = KConfigGroup(&group, "Dialog"); winId(); DXmlGuiWindow::restoreWindowSize(windowHandle(), groupDialog); resize(windowHandle()->size()); } void BookmarksDialog::saveSettings() { if (saveExpandedNodes(d->tree->rootIndex())) d->manager->changeExpanded(); KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(objectName()); KConfigGroup groupGPSTab = KConfigGroup(&group, QLatin1String("GPS Properties Tab")); d->mapView->writeSettings(groupGPSTab); KConfigGroup groupDialog = KConfigGroup(&group, "Dialog"); DXmlGuiWindow::saveWindowSize(windowHandle(), groupDialog); } } // namespace Digikam diff --git a/core/utilities/geolocation/editor/bookmark/bookmarksmngr.cpp b/core/utilities/geolocation/editor/bookmark/bookmarksmngr.cpp index 8b529959c6..feec273896 100644 --- a/core/utilities/geolocation/editor/bookmark/bookmarksmngr.cpp +++ b/core/utilities/geolocation/editor/bookmark/bookmarksmngr.cpp @@ -1,848 +1,848 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2017-05-15 * Description : low level manager for GPS bookmarks * * Copyright (C) 2017-2018 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 "bookmarksmngr.h" // Qt includes #include #include #include #include #include #include #include #include #include #include // KDE includes #include // Local includes #include #include "bookmarknode.h" #include "digikam_debug.h" namespace Digikam { RemoveBookmarksCommand::RemoveBookmarksCommand(BookmarksManager* const mngr, BookmarkNode* const parent, int row) : QUndoCommand(i18n("Remove Bookmark")), m_row(row), m_bookmarkManager(mngr), m_node(parent->children().value(row)), m_parent(parent), m_done(false) { } RemoveBookmarksCommand::~RemoveBookmarksCommand() { if (m_done && !m_node->parent()) { delete m_node; } } void RemoveBookmarksCommand::undo() { m_parent->add(m_node, m_row); emit m_bookmarkManager->entryAdded(m_node); m_done = false; } void RemoveBookmarksCommand::redo() { m_parent->remove(m_node); emit m_bookmarkManager->entryRemoved(m_parent, m_row, m_node); m_done = true; } // -------------------------------------------------------------- InsertBookmarksCommand::InsertBookmarksCommand(BookmarksManager* const mngr, BookmarkNode* const parent, BookmarkNode* const node, int row) : RemoveBookmarksCommand(mngr, parent, row) { setText(i18n("Insert Bookmark")); m_node = node; } void InsertBookmarksCommand::undo() { RemoveBookmarksCommand::redo(); } void InsertBookmarksCommand::redo() { RemoveBookmarksCommand::undo(); } // -------------------------------------------------------------- class Q_DECL_HIDDEN ChangeBookmarkCommand::Private { public: explicit Private() : manager(0), type(Url), node(0) { } BookmarksManager* manager; BookmarkData type; QString oldValue; QString newValue; BookmarkNode* node; }; ChangeBookmarkCommand::ChangeBookmarkCommand(BookmarksManager* const mngr, BookmarkNode* const node, const QString& newValue, BookmarkData type) : QUndoCommand(), d(new Private) { d->manager = mngr; d->type = type; d->newValue = newValue; d->node = node; switch (d->type) { case Title: d->oldValue = d->node->title; setText(i18n("Title Change")); break; case Desc: d->oldValue = d->node->desc; setText(i18n("Comment Change")); break; default: // Url d->oldValue = d->node->url; setText(i18n("Address Change")); break; } } ChangeBookmarkCommand::~ChangeBookmarkCommand() { delete d; } void ChangeBookmarkCommand::undo() { switch (d->type) { case Title: d->node->title = d->oldValue; break; case Desc: d->node->desc = d->oldValue; break; default: // Url d->node->url = d->oldValue; break; } emit d->manager->entryChanged(d->node); } void ChangeBookmarkCommand::redo() { switch (d->type) { case Title: d->node->title = d->newValue; break; case Desc: d->node->desc = d->newValue; break; default: // Url d->node->url = d->newValue; break; } emit d->manager->entryChanged(d->node); } // -------------------------------------------------------------- class Q_DECL_HIDDEN BookmarksModel::Private { public: explicit Private() : manager(0), endMacro(false) { } BookmarksManager* manager; bool endMacro; }; BookmarksModel::BookmarksModel(BookmarksManager* const mngr, QObject* const parent) : QAbstractItemModel(parent), d(new Private) { d->manager = mngr; connect(d->manager, SIGNAL(entryAdded(BookmarkNode*)), this, SLOT(entryAdded(BookmarkNode*))); connect(d->manager, SIGNAL(entryRemoved(BookmarkNode*,int,BookmarkNode*)), this, SLOT(entryRemoved(BookmarkNode*,int,BookmarkNode*))); connect(d->manager, SIGNAL(entryChanged(BookmarkNode*)), this, SLOT(entryChanged(BookmarkNode*))); } BookmarksModel::~BookmarksModel() { delete d; } BookmarksManager* BookmarksModel::bookmarksManager() const { return d->manager; } QModelIndex BookmarksModel::index(BookmarkNode* node) const { BookmarkNode* const parent = node->parent(); if (!parent) return QModelIndex(); return createIndex(parent->children().indexOf(node), 0, node); } void BookmarksModel::entryAdded(BookmarkNode* item) { Q_ASSERT(item && item->parent()); int row = item->parent()->children().indexOf(item); BookmarkNode* const parent = item->parent(); // item was already added so remove before beginInsertRows is called parent->remove(item); beginInsertRows(index(parent), row, row); parent->add(item, row); endInsertRows(); } void BookmarksModel::entryRemoved(BookmarkNode* parent, int row, BookmarkNode* item) { // item was already removed, re-add so beginRemoveRows works parent->add(item, row); beginRemoveRows(index(parent), row, row); parent->remove(item); endRemoveRows(); } void BookmarksModel::entryChanged(BookmarkNode* item) { QModelIndex idx = index(item); emit dataChanged(idx, idx); } bool BookmarksModel::removeRows(int row, int count, const QModelIndex& parent) { if (row < 0 || count <= 0 || (row + count) > rowCount(parent)) return false; BookmarkNode* const bookmarkNode = node(parent); for (int i = (row + count - 1) ; i >= row ; i--) { BookmarkNode* const node = bookmarkNode->children().at(i); d->manager->removeBookmark(node); } if (d->endMacro) { d->manager->undoRedoStack()->endMacro(); d->endMacro = false; } return true; } QVariant BookmarksModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { switch (section) { case 0: return i18n("Title"); case 1: return i18n("Comment"); } } return QAbstractItemModel::headerData(section, orientation, role); } QVariant BookmarksModel::data(const QModelIndex& index, int role) const { if (!index.isValid() || index.model() != this) return QVariant(); const BookmarkNode* const bookmarkNode = node(index); switch (role) { case Qt::EditRole: case Qt::DisplayRole: if (bookmarkNode->type() == BookmarkNode::Separator) { switch (index.column()) { case 0: return QString(50, 0xB7); case 1: return QString(); } } switch (index.column()) { case 0: return bookmarkNode->title; case 1: return bookmarkNode->desc; } break; case BookmarksModel::UrlRole: return QUrl(bookmarkNode->url); break; case BookmarksModel::UrlStringRole: return bookmarkNode->url; break; case BookmarksModel::DateAddedRole: return bookmarkNode->dateAdded; break; case BookmarksModel::TypeRole: return bookmarkNode->type(); break; case BookmarksModel::SeparatorRole: return (bookmarkNode->type() == BookmarkNode::Separator); break; case Qt::DecorationRole: if (index.column() == 0) { if (bookmarkNode->type() == BookmarkNode::Bookmark) { return QIcon::fromTheme(QLatin1String("globe")); } else { return QIcon::fromTheme(QLatin1String("folder")); } } } return QVariant(); } int BookmarksModel::columnCount(const QModelIndex& parent) const { return (parent.column() > 0) ? 0 : 2; } int BookmarksModel::rowCount(const QModelIndex& parent) const { if (parent.column() > 0) return 0; if (!parent.isValid()) return d->manager->bookmarks()->children().count(); const BookmarkNode* const item = static_cast(parent.internalPointer()); return item->children().count(); } QModelIndex BookmarksModel::index(int row, int column, const QModelIndex& parent) const { if (row < 0 || column < 0 || row >= rowCount(parent) || column >= columnCount(parent)) return QModelIndex(); // get the parent node BookmarkNode* const parentNode = node(parent); return createIndex(row, column, parentNode->children().at(row)); } QModelIndex BookmarksModel::parent(const QModelIndex& index) const { if (!index.isValid()) return QModelIndex(); BookmarkNode* const itemNode = node(index); BookmarkNode* const parentNode = (itemNode ? itemNode->parent() : 0); if (!parentNode || parentNode == d->manager->bookmarks()) return QModelIndex(); // get the parent's row BookmarkNode* const grandParentNode = parentNode->parent(); int parentRow = grandParentNode->children().indexOf(parentNode); Q_ASSERT(parentRow >= 0); return createIndex(parentRow, 0, parentNode); } bool BookmarksModel::hasChildren(const QModelIndex& parent) const { if (!parent.isValid()) return true; const BookmarkNode* const parentNode = node(parent); return (parentNode->type() == BookmarkNode::Folder || parentNode->type() == BookmarkNode::RootFolder); } Qt::ItemFlags BookmarksModel::flags(const QModelIndex& index) const { if (!index.isValid()) return Qt::NoItemFlags; Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled; BookmarkNode* const bookmarkNode = node(index); if (bookmarkNode->type() != BookmarkNode::RootFolder) flags |= Qt::ItemIsDragEnabled; if (bookmarkNode->type() != BookmarkNode::Separator && bookmarkNode->type() != BookmarkNode::RootFolder) { flags |= Qt::ItemIsEditable; } if (hasChildren(index)) flags |= Qt::ItemIsDropEnabled; return flags; } Qt::DropActions BookmarksModel::supportedDropActions() const { return Qt::CopyAction | Qt::MoveAction; } QStringList BookmarksModel::mimeTypes() const { QStringList types; types << QLatin1String("application/bookmarks.xbel"); return types; } QMimeData* BookmarksModel::mimeData(const QModelIndexList& indexes) const { QMimeData* const mimeData = new QMimeData(); QByteArray data; QDataStream stream(&data, QIODevice::WriteOnly); foreach (QModelIndex index, indexes) { if (index.column() != 0 || !index.isValid()) continue; QByteArray encodedData; QBuffer buffer(&encodedData); buffer.open(QBuffer::ReadWrite); XbelWriter writer; const BookmarkNode* const parentNode = node(index); writer.write(&buffer, parentNode); stream << encodedData; } mimeData->setData(QLatin1String("application/bookmarks.xbel"), data); return mimeData; } bool BookmarksModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) { if (action == Qt::IgnoreAction) return true; if (!data->hasFormat(QLatin1String("application/bookmarks.xbel")) || column > 0) return false; QByteArray ba = data->data(QLatin1String("application/bookmarks.xbel")); QDataStream stream(&ba, QIODevice::ReadOnly); if (stream.atEnd()) return false; QUndoStack* const undoStack = d->manager->undoRedoStack(); undoStack->beginMacro(QLatin1String("Move Bookmarks")); while (!stream.atEnd()) { QByteArray encodedData; stream >> encodedData; QBuffer buffer(&encodedData); buffer.open(QBuffer::ReadOnly); XbelReader reader; BookmarkNode* const rootNode = reader.read(&buffer); QList children = rootNode->children(); - for (int i = 0 ; i < children.count() ; i++) + for (int i = 0 ; i < children.count() ; ++i) { BookmarkNode* const bookmarkNode = children.at(i); rootNode->remove(bookmarkNode); row = qMax(0, row); BookmarkNode* const parentNode = node(parent); d->manager->addBookmark(parentNode, bookmarkNode, row); d->endMacro = true; } delete rootNode; } return true; } bool BookmarksModel::setData(const QModelIndex& index, const QVariant& value, int role) { if (!index.isValid() || (flags(index) & Qt::ItemIsEditable) == 0) return false; BookmarkNode* const item = node(index); switch (role) { case Qt::EditRole: case Qt::DisplayRole: if (index.column() == 0) { d->manager->setTitle(item, value.toString()); break; } if (index.column() == 1) { d->manager->setComment(item, value.toString()); break; } return false; case BookmarksModel::UrlRole: d->manager->setUrl(item, value.toUrl().toString()); break; case BookmarksModel::UrlStringRole: d->manager->setUrl(item, value.toString()); break; default: return false; } return true; } BookmarkNode* BookmarksModel::node(const QModelIndex& index) const { BookmarkNode* const itemNode = static_cast(index.internalPointer()); if (!itemNode) return d->manager->bookmarks(); return itemNode; } // -------------------------------------------------------------- AddBookmarkProxyModel::AddBookmarkProxyModel(QObject* const parent) : QSortFilterProxyModel(parent) { } int AddBookmarkProxyModel::columnCount(const QModelIndex& parent) const { return qMin(1, QSortFilterProxyModel::columnCount(parent)); } bool AddBookmarkProxyModel::filterAcceptsRow(int srow, const QModelIndex& sparent) const { QModelIndex idx = sourceModel()->index(srow, 0, sparent); return sourceModel()->hasChildren(idx); } // -------------------------------------------------------------- TreeProxyModel::TreeProxyModel(QObject* const parent) : QSortFilterProxyModel(parent) { setFilterCaseSensitivity(Qt::CaseInsensitive); } int TreeProxyModel::columnCount(const QModelIndex&) const { // 1th column : Title // 2th column : Comment return 2; } bool TreeProxyModel::filterAcceptsRow(int srow, const QModelIndex& sparent) const { QModelIndex index = sourceModel()->index(srow, 0, sparent); if (!index.isValid()) { return false; } if (index.data().toString().contains(filterRegExp())) { return true; } - for (int i = 0 ; i < sourceModel()->rowCount(index) ; i++) + for (int i = 0 ; i < sourceModel()->rowCount(index) ; ++i) { if (filterAcceptsRow(i, index)) { return true; } } return false; } void TreeProxyModel::emitResult(bool v) { emit signalFilterAccepts(v); } // -------------------------------------------------------------- class Q_DECL_HIDDEN BookmarksManager::Private { public: explicit Private() : loaded(false), bookmarkRootNode(0), bookmarkModel(0) { } bool loaded; BookmarkNode* bookmarkRootNode; BookmarksModel* bookmarkModel; QUndoStack commands; QString bookmarksFile; }; BookmarksManager::BookmarksManager(const QString& bookmarksFile, QObject* const parent) : QObject(parent), d(new Private) { d->bookmarksFile = bookmarksFile; load(); } BookmarksManager::~BookmarksManager() { delete d; } void BookmarksManager::changeExpanded() { } void BookmarksManager::load() { if (d->loaded) return; qCDebug(DIGIKAM_GEOIFACE_LOG) << "Loading GPS bookmarks from" << d->bookmarksFile; d->loaded = true; XbelReader reader; d->bookmarkRootNode = reader.read(d->bookmarksFile); if (reader.error() != QXmlStreamReader::NoError) { QMessageBox::warning(0, i18n("Loading Bookmark"), i18n("Error when loading bookmarks on line %1, column %2:\n%3", reader.lineNumber(), reader.columnNumber(), reader.errorString())); } } void BookmarksManager::save() { if (!d->loaded) return; qCDebug(DIGIKAM_GEOIFACE_LOG) << "Saving GPS bookmarks to" << d->bookmarksFile; XbelWriter writer; if (!writer.write(d->bookmarksFile, d->bookmarkRootNode)) { qCWarning(DIGIKAM_GEOIFACE_LOG) << "BookmarkManager: error saving to" << d->bookmarksFile; } } void BookmarksManager::addBookmark(BookmarkNode* const parent, BookmarkNode* const node, int row) { if (!d->loaded) return; Q_ASSERT(parent); InsertBookmarksCommand* const command = new InsertBookmarksCommand(this, parent, node, row); d->commands.push(command); } void BookmarksManager::removeBookmark(BookmarkNode* const node) { if (!d->loaded) return; Q_ASSERT(node); BookmarkNode* const parent = node->parent(); int row = parent->children().indexOf(node); RemoveBookmarksCommand* const command = new RemoveBookmarksCommand(this, parent, row); d->commands.push(command); } void BookmarksManager::setTitle(BookmarkNode* const node, const QString& newTitle) { if (!d->loaded) return; Q_ASSERT(node); ChangeBookmarkCommand* const command = new ChangeBookmarkCommand(this, node, newTitle, ChangeBookmarkCommand::Title); d->commands.push(command); } void BookmarksManager::setUrl(BookmarkNode* const node, const QString& newUrl) { if (!d->loaded) return; Q_ASSERT(node); ChangeBookmarkCommand* const command = new ChangeBookmarkCommand(this, node, newUrl, ChangeBookmarkCommand::Url); d->commands.push(command); } void BookmarksManager::setComment(BookmarkNode* const node, const QString& newDesc) { if (!d->loaded) return; Q_ASSERT(node); ChangeBookmarkCommand* const command = new ChangeBookmarkCommand(this, node, newDesc, ChangeBookmarkCommand::Desc); d->commands.push(command); } BookmarkNode* BookmarksManager::bookmarks() { if (!d->loaded) load(); return d->bookmarkRootNode; } BookmarksModel* BookmarksManager::bookmarksModel() { if (!d->bookmarkModel) d->bookmarkModel = new BookmarksModel(this, this); return d->bookmarkModel; } QUndoStack* BookmarksManager::undoRedoStack() const { return &d->commands; } void BookmarksManager::importBookmarks() { QString fileName = DFileDialog::getOpenFileName(0, i18n("Open File"), QString(), i18n("XBEL (*.xbel *.xml)")); if (fileName.isEmpty()) return; XbelReader reader; BookmarkNode* const importRootNode = reader.read(fileName); if (reader.error() != QXmlStreamReader::NoError) { QMessageBox::warning(0, i18n("Loading Bookmark"), i18n("Error when loading bookmarks on line %1, column %2:\n%3", reader.lineNumber(), reader.columnNumber(), reader.errorString())); } importRootNode->setType(BookmarkNode::Folder); importRootNode->title = i18n("Imported %1", QDate::currentDate().toString(Qt::SystemLocaleShortDate)); addBookmark(bookmarks(), importRootNode); } void BookmarksManager::exportBookmarks() { QString fileName = DFileDialog::getSaveFileName(0, i18n("Save File"), i18n("%1 Bookmarks.xbel", QCoreApplication::applicationName()), i18n("XBEL (*.xbel *.xml)")); if (fileName.isEmpty()) return; XbelWriter writer; if (!writer.write(fileName, d->bookmarkRootNode)) QMessageBox::critical(0, i18n("Export error"), i18n("error saving bookmarks")); } } // namespace Digikam diff --git a/core/utilities/geolocation/geoiface/tiles/abstractmarkertiler.cpp b/core/utilities/geolocation/geoiface/tiles/abstractmarkertiler.cpp index 9a3071fef8..b61725fcd1 100644 --- a/core/utilities/geolocation/geoiface/tiles/abstractmarkertiler.cpp +++ b/core/utilities/geolocation/geoiface/tiles/abstractmarkertiler.cpp @@ -1,601 +1,601 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2009-12-01 * Description : An abstract base class for tiling of markers * * Copyright (C) 2009-2018 by Gilles Caulier * Copyright (C) 2009-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 "abstractmarkertiler.h" // Qt includes #include // Local includes #include "digikam_debug.h" #include "geoifacecommon.h" namespace Digikam { class Q_DECL_HIDDEN AbstractMarkerTiler::Private { public: explicit Private() : rootTile(0), isDirty(true) { } AbstractMarkerTiler::Tile* rootTile; bool isDirty; }; AbstractMarkerTiler::AbstractMarkerTiler(QObject* const parent) : QObject(parent), d(new Private()) { } AbstractMarkerTiler::~AbstractMarkerTiler() { // delete all tiles clear(); delete d; } AbstractMarkerTiler::Tile* AbstractMarkerTiler::rootTile() { if (isDirty()) { regenerateTiles(); } return d->rootTile; } bool AbstractMarkerTiler::isDirty() const { return d->isDirty; } void AbstractMarkerTiler::setDirty(const bool state) { if (state && !d->isDirty) { d->isDirty = true; emit(signalTilesOrSelectionChanged()); } else { d->isDirty = state; } } AbstractMarkerTiler::Tile* AbstractMarkerTiler::resetRootTile() { tileDelete(d->rootTile); d->rootTile = tileNew(); return d->rootTile; } void AbstractMarkerTiler::onIndicesClicked(const ClickInfo& clickInfo) { Q_UNUSED(clickInfo) } void AbstractMarkerTiler::onIndicesMoved(const TileIndex::List& tileIndicesList, const GeoCoordinates& targetCoordinates, const QPersistentModelIndex& targetSnapIndex) { Q_UNUSED(tileIndicesList); Q_UNUSED(targetCoordinates); Q_UNUSED(targetSnapIndex); } AbstractMarkerTiler::Tile* AbstractMarkerTiler::tileNew() { return new Tile(); } void AbstractMarkerTiler::tileDelete(AbstractMarkerTiler::Tile* const tile) { tileDeleteChildren(tile); tileDeleteInternal(tile); } void AbstractMarkerTiler::tileDeleteInternal(AbstractMarkerTiler::Tile* const tile) { delete tile; } void AbstractMarkerTiler::tileDeleteChildren(AbstractMarkerTiler::Tile* const tile) { if (!tile) return; QVector tileChildren = tile->takeChildren(); foreach (Tile* tilec, tileChildren) { tileDelete(tilec); } } void AbstractMarkerTiler::tileDeleteChild(AbstractMarkerTiler::Tile* const parentTile, AbstractMarkerTiler::Tile* const childTile, const int knownLinearIndex) { int tileIndex = knownLinearIndex; if (tileIndex < 0) { tileIndex = parentTile->indexOfChildTile(childTile); } parentTile->clearChild(tileIndex); tileDelete(childTile); } AbstractMarkerTiler::TilerFlags AbstractMarkerTiler::tilerFlags() const { return FlagNull; } void AbstractMarkerTiler::clear() { tileDelete(d->rootTile); d->rootTile = 0; } // ------------------------------------------------------------------------- class Q_DECL_HIDDEN AbstractMarkerTiler::NonEmptyIterator::Private { public: explicit Private() : model(0), level(0), startIndex(), endIndex(), currentIndex(), atEnd(false), atStartOfLevel(true) { } AbstractMarkerTiler* model; int level; QList > boundsList; TileIndex startIndex; TileIndex endIndex; TileIndex currentIndex; bool atEnd; bool atStartOfLevel; }; AbstractMarkerTiler::NonEmptyIterator::~NonEmptyIterator() { delete d; } AbstractMarkerTiler::NonEmptyIterator::NonEmptyIterator(AbstractMarkerTiler* const model, const int level) : d(new Private()) { d->model = model; GEOIFACE_ASSERT(level <= TileIndex::MaxLevel); d->level = level; TileIndex startIndex; TileIndex endIndex; - for (int i = 0 ; i <= level ; i++) + for (int i = 0 ; i <= level ; ++i) { startIndex.appendLinearIndex(0); endIndex.appendLinearIndex(TileIndex::Tiling * TileIndex::Tiling - 1); } // qCDebug(DIGIKAM_GEOIFACE_LOG) << d->startIndexLinear << d->endIndexLinear; d->boundsList << QPair(startIndex, endIndex); initializeNextBounds(); } AbstractMarkerTiler::NonEmptyIterator::NonEmptyIterator(AbstractMarkerTiler* const model, const int level, const TileIndex& startIndex, const TileIndex& endIndex) : d(new Private()) { d->model = model; GEOIFACE_ASSERT(level <= TileIndex::MaxLevel); d->level = level; GEOIFACE_ASSERT(startIndex.level() == level); GEOIFACE_ASSERT(endIndex.level() == level); d->boundsList << QPair(startIndex, endIndex); initializeNextBounds(); } AbstractMarkerTiler::NonEmptyIterator::NonEmptyIterator(AbstractMarkerTiler* const model, const int level, const GeoCoordinates::PairList& normalizedMapBounds) : d(new Private()) { d->model = model; GEOIFACE_ASSERT(level <= TileIndex::MaxLevel); d->level = level; // store the coordinates of the bounds as indices: - for (int i = 0 ; i < normalizedMapBounds.count() ; i++) + for (int i = 0 ; i < normalizedMapBounds.count() ; ++i) { GeoCoordinates::Pair currentBounds = normalizedMapBounds.at(i); GEOIFACE_ASSERT(currentBounds.first.lat() < currentBounds.second.lat()); GEOIFACE_ASSERT(currentBounds.first.lon() < currentBounds.second.lon()); const TileIndex startIndex = TileIndex::fromCoordinates(currentBounds.first, d->level); const TileIndex endIndex = TileIndex::fromCoordinates(currentBounds.second, d->level); /* qCDebug(DIGIKAM_GEOIFACE_LOG) << currentBounds.first.geoUrl() << startIndex << currentBounds.second.geoUrl() << endIndex; */ d->boundsList << QPair(startIndex, endIndex); } initializeNextBounds(); } bool AbstractMarkerTiler::NonEmptyIterator::initializeNextBounds() { if (d->boundsList.isEmpty()) { d->atEnd = true; return false; } QPair nextBounds = d->boundsList.takeFirst(); d->startIndex = nextBounds.first; d->endIndex = nextBounds.second; GEOIFACE_ASSERT(d->startIndex.level() == d->level); GEOIFACE_ASSERT(d->endIndex.level() == d->level); d->currentIndex = d->startIndex.mid(0, 1); d->atStartOfLevel = true; nextIndex(); return d->atEnd; } TileIndex AbstractMarkerTiler::NonEmptyIterator::nextIndex() { if (d->atEnd) { return d->currentIndex; } Q_FOREVER { const int currentLevel = d->currentIndex.level(); /* qCDebug(DIGIKAM_GEOIFACE_LOG) << d->level << currentLevel << d->atStartOfLevel << d->currentIndex; */ if (d->atStartOfLevel) { d->atStartOfLevel = false; } else { // go to the next tile at the current level, if that is possible: // determine the limits in the current tile: int limitLatBL = 0; int limitLonBL = 0; int limitLatTR = TileIndex::Tiling-1; int limitLonTR = TileIndex::Tiling-1; int compareLevel = currentLevel - 1; // check limit on the left side: bool onLimit = true; for (int i = 0 ; onLimit && (i <= compareLevel) ; ++i) { onLimit = d->currentIndex.indexLat(i) == d->startIndex.indexLat(i); } if (onLimit) { limitLatBL = d->startIndex.indexLat(currentLevel); } // check limit on the bottom side: onLimit = true; for (int i = 0 ; onLimit && (i <= compareLevel) ; ++i) { onLimit = d->currentIndex.indexLon(i) == d->startIndex.indexLon(i); } if (onLimit) { limitLonBL = d->startIndex.indexLon(currentLevel); } // check limit on the right side: onLimit = true; for (int i = 0 ; onLimit && (i <= compareLevel) ; ++i) { onLimit = d->currentIndex.indexLat(i) == d->endIndex.indexLat(i); } if (onLimit) { limitLatTR = d->endIndex.indexLat(currentLevel); } // check limit on the top side: onLimit = true; for (int i = 0 ; onLimit && (i <= compareLevel) ; ++i) { onLimit = d->currentIndex.indexLon(i) == d->endIndex.indexLon(i); } if (onLimit) { limitLonTR = d->endIndex.indexLon(currentLevel); } GEOIFACE_ASSERT(limitLatBL <= limitLatTR); GEOIFACE_ASSERT(limitLonBL <= limitLonTR); /* qCDebug(DIGIKAM_GEOIFACE_LOG) << limitLatBL << limitLonBL << limitLatTR << limitLonTR << compareLevel << currentLevel; */ int currentLat = d->currentIndex.indexLat(d->currentIndex.level()); int currentLon = d->currentIndex.indexLon(d->currentIndex.level()); currentLon++; if (currentLon>limitLonTR) { currentLon = limitLonBL; currentLat++; if (currentLat>limitLatTR) { if (currentLevel == 0) { // we are at the end! // are there other bounds to iterate over? initializeNextBounds(); // initializeNextBounds() call nextIndex which updates d->currentIndexLinear, if possible: return d->currentIndex; } // we need to go one level up, trim the indices: d->currentIndex.oneUp(); continue; } } // save the new position: d->currentIndex.oneUp(); d->currentIndex.appendLatLonIndex(currentLat, currentLon); } // is the tile empty? if (d->model->getTileMarkerCount(d->currentIndex)==0) { continue; } // are we at the target level? if (currentLevel == d->level) { // yes, return the current index: return d->currentIndex; } // go one level down: int compareLevel = currentLevel; // determine the limits for the next level: int limitLatBL = 0; int limitLonBL = 0; int limitLatTR = TileIndex::Tiling-1; int limitLonTR = TileIndex::Tiling-1; // check limit on the left side: bool onLimit = true; for (int i = 0 ; onLimit&&(i <= compareLevel) ; ++i) { onLimit = d->currentIndex.indexLat(i)==d->startIndex.indexLat(i); } if (onLimit) { limitLatBL = d->startIndex.indexLat(currentLevel+1); } // check limit on the bottom side: onLimit = true; for (int i = 0 ; onLimit && (i <= compareLevel) ; ++i) { onLimit = d->currentIndex.indexLon(i)==d->startIndex.indexLon(i); } if (onLimit) { limitLonBL = d->startIndex.indexLon(currentLevel+1); } // check limit on the right side: onLimit = true; for (int i = 0 ; onLimit && (i <= compareLevel) ; ++i) { onLimit = d->currentIndex.indexLat(i) == d->endIndex.indexLat(i); } if (onLimit) { limitLatTR = d->endIndex.indexLat(currentLevel+1); } // check limit on the top side: onLimit = true; for (int i = 0 ; onLimit && (i <= compareLevel) ; ++i) { onLimit = d->currentIndex.indexLon(i) == d->endIndex.indexLon(i); } if (onLimit) { limitLonTR = d->endIndex.indexLon(currentLevel+1); } GEOIFACE_ASSERT(limitLatBL <= limitLatTR); GEOIFACE_ASSERT(limitLonBL <= limitLonTR); // go one level down: d->currentIndex.appendLatLonIndex(limitLatBL, limitLonBL); d->atStartOfLevel = true; } } TileIndex AbstractMarkerTiler::NonEmptyIterator::currentIndex() const { return d->currentIndex; } bool AbstractMarkerTiler::NonEmptyIterator::atEnd() const { return d->atEnd; } AbstractMarkerTiler* AbstractMarkerTiler::NonEmptyIterator::model() const { return d->model; } // ------------------------------------------------------------------------- AbstractMarkerTiler::Tile::Tile() : children() { } AbstractMarkerTiler::Tile::~Tile() { } int AbstractMarkerTiler::Tile::maxChildCount() { return TileIndex::Tiling * TileIndex::Tiling; } AbstractMarkerTiler::Tile* AbstractMarkerTiler::Tile::getChild(const int linearIndex) { if (children.isEmpty()) { return 0; } return children.at(linearIndex); } void AbstractMarkerTiler::Tile::addChild(const int linearIndex, Tile* const tilePointer) { if ((tilePointer==0) && children.isEmpty()) { return; } prepareForChildren(); children[linearIndex] = tilePointer; } void AbstractMarkerTiler::Tile::clearChild(const int linearIndex) { if (children.isEmpty()) { return; } children[linearIndex] = 0; } int AbstractMarkerTiler::Tile::indexOfChildTile(Tile* const tile) { return children.indexOf(tile); } bool AbstractMarkerTiler::Tile::childrenEmpty() const { return children.isEmpty(); } QVector AbstractMarkerTiler::Tile::takeChildren() { QVector childrenCopy = children; children.clear(); return childrenCopy; } void AbstractMarkerTiler::Tile::prepareForChildren() { if (!children.isEmpty()) { return; } children = QVector(maxChildCount(), 0); } } // namespace Digikam diff --git a/core/utilities/geolocation/geoiface/widgets/mapwidget.cpp b/core/utilities/geolocation/geoiface/widgets/mapwidget.cpp index 49521a2040..a96352deab 100644 --- a/core/utilities/geolocation/geoiface/widgets/mapwidget.cpp +++ b/core/utilities/geolocation/geoiface/widgets/mapwidget.cpp @@ -1,2335 +1,2335 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2009-12-01 * Description : world map widget library * * Copyright (C) 2010-2018 by Gilles Caulier * Copyright (C) 2009-2011 by Michael G. Hansen * Copyright (C) 2014 by Justus Schwartz * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "mapwidget.h" // C++ includes #include // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include #include #include // Marbel includes #include #include #include // local includes #include "geoifacecommon.h" #include "geodragdrophandler.h" #include "geomodelhelper.h" #include "trackmanager.h" #include "placeholderwidget.h" #include "tilegrouper.h" #include "digikam_debug.h" #include "abstractmarkertiler.h" #include "backendgooglemaps.h" #include "backendmarble.h" namespace Digikam { /** * @class MapWidget * @brief The central map view class of geolocation interface * * The MapWidget class is the central widget of geolocation interface. It provides a widget which can display maps using * either the Marble or Google Maps backend. Using a model, items can be displayed on the map. For * models containing only a small number of items, the items can be shown directly, but for models with * a larger number of items, the items can also be grouped. Currently, any number of ungrouped models * can be shown, but only one grouped model. Item selection models can also be used along with the models, * to interact with the selection states of the items on the map. In order to use a model with geolocation interface, however, * a model helper has to be implemented, which extracts data from the model that is not provided by the Qt part * of a model's API. * * Now, a brief introduction on how to get geolocation interface working is provided: * @li First, an instance of @c MapWidget has to be created. * @li Next, @c GeoModelHelper has to be subclassed and at least the pure virtual functions have to be implemented. * @li To show the model's data ungrouped, the model helper has to be added to @c MapWidget instance using addUngroupedModel. * @li To show the model's data grouped, an instance of @c AbstractMarkerTiler has to be created and the model helper has to be * set to it using setMarkerGeoModelHelper. The @c AbstractMarkerTiler has then to be given to MapWidget using setGroupedModel. If * the items to be displayed do not reside in a model, a subclass of @c AbstractMarkerTiler can be created which returns * just the number of items in a particular area, and picks representative items for thumbnails. * @li To handle dropping of items from the host applications UI onto the map, @c DragDropHandler has to be subclassed * as well and added to the model using setDragDropHandler. * @li Finally, setActive() has to be called to tell the widget that it should start displaying things. */ class Q_DECL_HIDDEN MapWidget::Private { public: explicit Private() : loadedBackends(), currentBackend(0), currentBackendName(), stackedLayout(0), cacheCenterCoordinate(52.0,6.0), cacheZoom(QLatin1String("marble:900")), configurationMenu(0), actionGroupBackendSelection(0), actionZoomIn(0), actionZoomOut(0), actionShowThumbnails(0), mouseModesHolder(0), controlWidget(0), actionPreviewSingleItems(0), actionPreviewGroupedItems(0), actionShowNumbersOnItems(0), lazyReclusteringRequested(false), dragDropHandler(0), sortMenu(0), actionIncreaseThumbnailSize(0), actionDecreaseThumbnailSize(0), hBoxForAdditionalControlWidgetItems(0), mouseModeActionGroup(0), actionRemoveCurrentRegionSelection(0), actionSetRegionSelectionMode(0), actionSetPanMode(0), actionSetZoomIntoGroupMode(0), actionSetRegionSelectionFromIconMode(0), actionSetFilterMode(0), actionRemoveFilter(0), actionSetSelectThumbnailMode(0), setPanModeButton(0), setSelectionModeButton(0), removeCurrentSelectionButton(0), setZoomModeButton(0), setRegionSelectionFromIconModeButton(0), setFilterModeButton(0), removeFilterModeButton(0), setSelectThumbnailMode(0), thumbnailTimer(0), thumbnailTimerCount(0), thumbnailsHaveBeenLoaded(false), availableExtraActions(0), visibleExtraActions(0), actionStickyMode(0), buttonStickyMode(0), placeholderWidget(0) { } QList loadedBackends; MapBackend* currentBackend; QString currentBackendName; QStackedLayout* stackedLayout; // these values are cached in case the backend is not ready: GeoCoordinates cacheCenterCoordinate; QString cacheZoom; // actions for controlling the widget QMenu* configurationMenu; QActionGroup* actionGroupBackendSelection; QAction* actionZoomIn; QAction* actionZoomOut; QAction* actionShowThumbnails; QWidget* mouseModesHolder; QPointer controlWidget; QAction* actionPreviewSingleItems; QAction* actionPreviewGroupedItems; QAction* actionShowNumbersOnItems; bool lazyReclusteringRequested; GeoDragDropHandler* dragDropHandler; QMenu* sortMenu; QAction* actionIncreaseThumbnailSize; QAction* actionDecreaseThumbnailSize; QWidget* hBoxForAdditionalControlWidgetItems; QActionGroup* mouseModeActionGroup; QAction* actionRemoveCurrentRegionSelection; QAction* actionSetRegionSelectionMode; QAction* actionSetPanMode; QAction* actionSetZoomIntoGroupMode; QAction* actionSetRegionSelectionFromIconMode; QAction* actionSetFilterMode; QAction* actionRemoveFilter; QAction* actionSetSelectThumbnailMode; QToolButton* setPanModeButton; QToolButton* setSelectionModeButton; QToolButton* removeCurrentSelectionButton; QToolButton* setZoomModeButton; QToolButton* setRegionSelectionFromIconModeButton; QToolButton* setFilterModeButton; QToolButton* removeFilterModeButton; QToolButton* setSelectThumbnailMode; QTimer* thumbnailTimer; int thumbnailTimerCount; bool thumbnailsHaveBeenLoaded; GeoExtraActions availableExtraActions; GeoExtraActions visibleExtraActions; QAction* actionStickyMode; QToolButton* buttonStickyMode; // to be sorted later PlaceholderWidget* placeholderWidget; }; MapWidget::MapWidget(QWidget* const parent) : QWidget(parent), s(new GeoIfaceSharedData), d(new Private) { createActions(); s->worldMapWidget = this; s->tileGrouper = new TileGrouper(s, this); d->stackedLayout = new QStackedLayout(this); setLayout(d->stackedLayout); d->placeholderWidget = new PlaceholderWidget(); d->stackedLayout->addWidget(d->placeholderWidget); d->loadedBackends.append(new BackendGoogleMaps(s, this)); d->loadedBackends.append(new BackendMarble(s, this)); // d->loadedBackends.append(new BackendOSM(s, this)); createActionsForBackendSelection(); setAcceptDrops(true); } void MapWidget::createActions() { d->actionZoomIn = new QAction(this); d->actionZoomIn->setIcon(QIcon::fromTheme( QLatin1String("zoom-in") )); d->actionZoomIn->setToolTip(i18n("Zoom in")); connect(d->actionZoomIn, &QAction::triggered, this, &MapWidget::slotZoomIn); d->actionZoomOut = new QAction(this); d->actionZoomOut->setIcon(QIcon::fromTheme( QLatin1String("zoom-out") )); d->actionZoomOut->setToolTip(i18n("Zoom out")); connect(d->actionZoomOut, &QAction::triggered, this, &MapWidget::slotZoomOut); d->actionShowThumbnails = new QAction(this); d->actionShowThumbnails->setToolTip(i18n("Switch between markers and thumbnails.")); d->actionShowThumbnails->setCheckable(true); d->actionShowThumbnails->setChecked(true); connect(d->actionShowThumbnails, &QAction::triggered, this, &MapWidget::slotShowThumbnailsChanged); // create backend selection entries: d->actionGroupBackendSelection = new QActionGroup(this); d->actionGroupBackendSelection->setExclusive(true); connect(d->actionGroupBackendSelection, &QActionGroup::triggered, this, &MapWidget::slotChangeBackend); createActionsForBackendSelection(); d->configurationMenu = new QMenu(this); d->actionPreviewSingleItems = new QAction(i18n("Preview single items"), this); d->actionPreviewSingleItems->setCheckable(true); d->actionPreviewSingleItems->setChecked(true); d->actionPreviewGroupedItems = new QAction(i18n("Preview grouped items"), this); d->actionPreviewGroupedItems->setCheckable(true); d->actionPreviewGroupedItems->setChecked(true); d->actionShowNumbersOnItems = new QAction(i18n("Show numbers"), this); d->actionShowNumbersOnItems->setCheckable(true); d->actionShowNumbersOnItems->setChecked(true); d->actionIncreaseThumbnailSize = new QAction(i18n("T+"), this); d->actionIncreaseThumbnailSize->setToolTip(i18n("Increase the thumbnail size on the map")); d->actionDecreaseThumbnailSize = new QAction(i18n("T-"), this); d->actionDecreaseThumbnailSize->setToolTip(i18n("Decrease the thumbnail size on the map")); d->actionRemoveCurrentRegionSelection = new QAction(this); //d->actionRemoveCurrentRegionSelection->setEnabled(false); d->actionRemoveCurrentRegionSelection->setIcon(QIcon::fromTheme( QLatin1String("edit-clear") )); d->actionRemoveCurrentRegionSelection->setToolTip(i18n("Remove the current region selection")); d->mouseModeActionGroup = new QActionGroup(this); d->mouseModeActionGroup->setExclusive(true); d->actionSetRegionSelectionMode = new QAction(d->mouseModeActionGroup); d->actionSetRegionSelectionMode->setCheckable(true); d->actionSetRegionSelectionMode->setIcon(QIcon::fromTheme( QLatin1String("select-rectangular") )); d->actionSetRegionSelectionMode->setToolTip(i18n("Select images by drawing a rectangle")); d->actionSetRegionSelectionMode->setData(QVariant::fromValue(MouseModeRegionSelection)); d->actionSetPanMode = new QAction(d->mouseModeActionGroup); d->actionSetPanMode->setCheckable(true); d->actionSetPanMode->setToolTip(i18n("Pan mode")); d->actionSetPanMode->setIcon(QIcon::fromTheme( QLatin1String("transform-move") )); d->actionSetPanMode->setChecked(true); d->actionSetPanMode->setData(QVariant::fromValue(MouseModePan)); d->actionSetZoomIntoGroupMode = new QAction(d->mouseModeActionGroup); d->actionSetZoomIntoGroupMode->setCheckable(true); d->actionSetZoomIntoGroupMode->setToolTip(i18n("Zoom into a group")); d->actionSetZoomIntoGroupMode->setIcon(QIcon::fromTheme( QLatin1String("zoom-fit-best") )); d->actionSetZoomIntoGroupMode->setData(QVariant::fromValue(MouseModeZoomIntoGroup)); d->actionSetRegionSelectionFromIconMode = new QAction(d->mouseModeActionGroup); d->actionSetRegionSelectionFromIconMode->setCheckable(true); d->actionSetRegionSelectionFromIconMode->setToolTip(i18n("Create a region selection from a thumbnail")); d->actionSetRegionSelectionFromIconMode->setIcon(QIcon::fromTheme( QLatin1String("edit-node") )); d->actionSetRegionSelectionFromIconMode->setData(QVariant::fromValue(MouseModeRegionSelectionFromIcon)); d->actionSetFilterMode = new QAction(d->mouseModeActionGroup); d->actionSetFilterMode->setCheckable(true); d->actionSetFilterMode->setToolTip(i18n("Filter images")); d->actionSetFilterMode->setIcon(QIcon::fromTheme( QLatin1String("view-filter") )); d->actionSetFilterMode->setData(QVariant::fromValue(MouseModeFilter)); d->actionRemoveFilter = new QAction(this); d->actionRemoveFilter->setToolTip(i18n("Remove the current filter")); d->actionRemoveFilter->setIcon(QIcon::fromTheme( QLatin1String("window-close") )); d->actionSetSelectThumbnailMode = new QAction(d->mouseModeActionGroup); d->actionSetSelectThumbnailMode->setCheckable(true); d->actionSetSelectThumbnailMode->setToolTip(i18n("Select images")); d->actionSetSelectThumbnailMode->setIcon(QIcon::fromTheme( QLatin1String("edit-select") )); d->actionSetSelectThumbnailMode->setData(QVariant::fromValue(MouseModeSelectThumbnail)); d->actionStickyMode = new QAction(this); d->actionStickyMode->setCheckable(true); d->actionStickyMode->setToolTip(i18n("Lock the map position")); connect(d->actionStickyMode, &QAction::triggered, this, &MapWidget::slotStickyModeChanged); connect(d->actionIncreaseThumbnailSize, &QAction::triggered, this, &MapWidget::slotIncreaseThumbnailSize); connect(d->actionDecreaseThumbnailSize, &QAction::triggered, this, &MapWidget::slotDecreaseThumbnailSize); connect(d->actionPreviewSingleItems, &QAction::changed, this, &MapWidget::slotItemDisplaySettingsChanged); connect(d->actionPreviewGroupedItems, &QAction::changed, this, &MapWidget::slotItemDisplaySettingsChanged); connect(d->actionShowNumbersOnItems, &QAction::changed, this, &MapWidget::slotItemDisplaySettingsChanged); connect(d->mouseModeActionGroup, &QActionGroup::triggered, this, &MapWidget::slotMouseModeChanged); connect(d->actionRemoveFilter, &QAction::triggered, this, &MapWidget::signalRemoveCurrentFilter); connect(d->actionRemoveCurrentRegionSelection, &QAction::triggered, this, &MapWidget::slotRemoveCurrentRegionSelection); } void MapWidget::createActionsForBackendSelection() { // delete the existing actions: qDeleteAll(d->actionGroupBackendSelection->actions()); // create actions for all backends: for (int i = 0 ; i < d->loadedBackends.size() ; ++i) { const QString backendName = d->loadedBackends.at(i)->backendName(); QAction* const backendAction = new QAction(d->actionGroupBackendSelection); backendAction->setData(backendName); backendAction->setText(d->loadedBackends.at(i)->backendHumanName()); backendAction->setCheckable(true); } } MapWidget::~MapWidget() { // release all widgets: for (int i = 0 ; i < d->stackedLayout->count() ; ++i) { d->stackedLayout->removeWidget(d->stackedLayout->widget(i)); } qDeleteAll(d->loadedBackends); delete d; /// @todo delete s, but make sure it is not accessed by any other objects any more! } QStringList MapWidget::availableBackends() const { QStringList result; MapBackend* backend = 0; foreach(backend, d->loadedBackends) { result.append(backend->backendName()); } return result; } bool MapWidget::setBackend(const QString& backendName) { if (backendName == d->currentBackendName) { return true; } saveBackendToCache(); // switch to the placeholder widget: setShowPlaceholderWidget(true); removeMapWidgetFromFrame(); // disconnect signals from old backend: if (d->currentBackend) { d->currentBackend->setActive(false); disconnect(d->currentBackend, SIGNAL(signalBackendReadyChanged(QString)), this, SLOT(slotBackendReadyChanged(QString))); disconnect(d->currentBackend, SIGNAL(signalZoomChanged(QString)), this, SLOT(slotBackendZoomChanged(QString))); disconnect(d->currentBackend, SIGNAL(signalClustersMoved(QIntList,QPair)), this, SLOT(slotClustersMoved(QIntList,QPair))); disconnect(d->currentBackend, SIGNAL(signalClustersClicked(QIntList)), this, SLOT(slotClustersClicked(QIntList))); disconnect(this, SIGNAL(signalUngroupedModelChanged(int)), d->currentBackend, SLOT(slotUngroupedModelChanged(int))); if (s->markerModel) { disconnect(s->markerModel, SIGNAL(signalThumbnailAvailableForIndex(QVariant,QPixmap)), d->currentBackend, SLOT(slotThumbnailAvailableForIndex(QVariant,QPixmap))); } disconnect(d->currentBackend, SIGNAL(signalSelectionHasBeenMade(Digikam::GeoCoordinates::Pair)), this, SLOT(slotNewSelectionFromMap(Digikam::GeoCoordinates::Pair))); } MapBackend* backend = 0; foreach(backend, d->loadedBackends) { if (backend->backendName() == backendName) { qCDebug(DIGIKAM_GEOIFACE_LOG) << QString::fromLatin1("setting backend %1").arg(backendName); d->currentBackend = backend; d->currentBackendName = backendName; connect(d->currentBackend, &MapBackend::signalBackendReadyChanged, this, &MapWidget::slotBackendReadyChanged); connect(d->currentBackend, &MapBackend::signalZoomChanged, this, &MapWidget::slotBackendZoomChanged); connect(d->currentBackend, &MapBackend::signalClustersMoved, this, &MapWidget::slotClustersMoved); connect(d->currentBackend, &MapBackend::signalClustersClicked, this, &MapWidget::slotClustersClicked); /** * @todo This connection is queued because otherwise QAbstractItemModel::itemSelected does not * reflect the true state. Maybe monitor another signal instead? */ connect(this, SIGNAL(signalUngroupedModelChanged(int)), d->currentBackend, SLOT(slotUngroupedModelChanged(int)), Qt::QueuedConnection); if (s->markerModel) { connect(s->markerModel, SIGNAL(signalThumbnailAvailableForIndex(QVariant,QPixmap)), d->currentBackend, SLOT(slotThumbnailAvailableForIndex(QVariant,QPixmap))); } connect(d->currentBackend, &MapBackend::signalSelectionHasBeenMade, this, &MapWidget::slotNewSelectionFromMap); if (s->activeState) { setMapWidgetInFrame(d->currentBackend->mapWidget()); // call this slot manually in case the backend was ready right away: if (d->currentBackend->isReady()) { slotBackendReadyChanged(d->currentBackendName); } else { rebuildConfigurationMenu(); } } d->currentBackend->setActive(s->activeState); return true; } } return false; } void MapWidget::applyCacheToBackend() { if ((!currentBackendReady()) || (!s->activeState)) { return; } /// @todo Only do this if the zoom was changed! qCDebug(DIGIKAM_GEOIFACE_LOG) << d->cacheZoom; setZoom(d->cacheZoom); setCenter(d->cacheCenterCoordinate); d->currentBackend->mouseModeChanged(); d->currentBackend->regionSelectionChanged(); } void MapWidget::saveBackendToCache() { if (!currentBackendReady()) { return; } d->cacheCenterCoordinate = getCenter(); d->cacheZoom = getZoom(); } GeoCoordinates MapWidget::getCenter() const { if (!currentBackendReady()) { return d->cacheCenterCoordinate; } return d->currentBackend->getCenter(); } void MapWidget::setCenter(const GeoCoordinates& coordinate) { d->cacheCenterCoordinate = coordinate; if (!currentBackendReady()) { return; } d->currentBackend->setCenter(coordinate); } void MapWidget::slotBackendReadyChanged(const QString& backendName) { qCDebug(DIGIKAM_GEOIFACE_LOG) << QString::fromLatin1("backend %1 is ready!").arg(backendName); if (backendName != d->currentBackendName) { return; } if (!currentBackendReady()) { return; } applyCacheToBackend(); setShowPlaceholderWidget(false); if (!d->thumbnailsHaveBeenLoaded) { d->thumbnailTimer = new QTimer(this); d->thumbnailTimerCount = 0; connect(d->thumbnailTimer, &QTimer::timeout, this, &MapWidget::stopThumbnailTimer); d->thumbnailTimer->start(2000); } updateMarkers(); markClustersAsDirty(); rebuildConfigurationMenu(); } void MapWidget::stopThumbnailTimer() { d->currentBackend->updateMarkers(); d->thumbnailTimerCount++; if (d->thumbnailTimerCount == 10) { d->thumbnailTimer->stop(); d->thumbnailsHaveBeenLoaded = true; } } void MapWidget::saveSettingsToGroup(KConfigGroup* const group) { GEOIFACE_ASSERT(group != 0); if (!group) return; if (!d->currentBackendName.isEmpty()) { group->writeEntry("Backend", d->currentBackendName); } group->writeEntry("Center", getCenter().geoUrl()); group->writeEntry("Zoom", getZoom()); group->writeEntry("Preview Single Items", s->previewSingleItems); group->writeEntry("Preview Grouped Items", s->previewGroupedItems); group->writeEntry("Show numbers on items", s->showNumbersOnItems); group->writeEntry("Thumbnail Size", s->thumbnailSize); group->writeEntry("Thumbnail Grouping Radius", s->thumbnailGroupingRadius); group->writeEntry("Marker Grouping Radius", s->markerGroupingRadius); group->writeEntry("Show Thumbnails", s->showThumbnails); group->writeEntry("Mouse Mode", int(s->currentMouseMode)); if (d->visibleExtraActions.testFlag(ExtraActionSticky)) { group->writeEntry("Sticky Mode State", d->actionStickyMode->isChecked()); } for (int i = 0 ; i < d->loadedBackends.size() ; ++i) { d->loadedBackends.at(i)->saveSettingsToGroup(group); } } void MapWidget::readSettingsFromGroup(const KConfigGroup* const group) { GEOIFACE_ASSERT(group != 0); if (!group) { return; } setBackend(group->readEntry("Backend", "marble")); // Options concerning the display of markers d->actionPreviewSingleItems->setChecked(group->readEntry("Preview Single Items", true)); d->actionPreviewGroupedItems->setChecked(group->readEntry("Preview Grouped Items", true)); d->actionShowNumbersOnItems->setChecked(group->readEntry("Show numbers on items", true)); setThumnailSize(group->readEntry("Thumbnail Size", 2*GeoIfaceMinThumbnailSize)); setThumbnailGroupingRadius(group->readEntry("Thumbnail Grouping Radius", 2*GeoIfaceMinThumbnailGroupingRadius)); setMarkerGroupingRadius(group->readEntry("Edit Grouping Radius", GeoIfaceMinMarkerGroupingRadius)); s->showThumbnails = group->readEntry("Show Thumbnails", s->showThumbnails); d->actionShowThumbnails->setChecked(s->showThumbnails); d->actionStickyMode->setChecked(group->readEntry("Sticky Mode State", d->actionStickyMode->isChecked())); // let the backends load their settings for (int i = 0 ; i < d->loadedBackends.size() ; ++i) { d->loadedBackends.at(i)->readSettingsFromGroup(group); } // current map state const GeoCoordinates centerDefault = GeoCoordinates(52.0, 6.0); const QString centerGeoUrl = group->readEntry("Center", centerDefault.geoUrl()); bool centerGeoUrlValid = false; const GeoCoordinates centerCoordinate = GeoCoordinates::fromGeoUrl(centerGeoUrl, ¢erGeoUrlValid); d->cacheCenterCoordinate = centerGeoUrlValid ? centerCoordinate : centerDefault; d->cacheZoom = group->readEntry("Zoom", d->cacheZoom); s->currentMouseMode = GeoMouseModes(group->readEntry("Mouse Mode", int(s->currentMouseMode))); // propagate the loaded values to the map, if appropriate applyCacheToBackend(); slotUpdateActionsEnabled(); } void MapWidget::rebuildConfigurationMenu() { d->configurationMenu->clear(); const QList backendSelectionActions = d->actionGroupBackendSelection->actions(); for (int i = 0 ; i < backendSelectionActions.count() ; ++i) { QAction* const backendAction = backendSelectionActions.at(i); if (backendAction->data().toString()==d->currentBackendName) { backendAction->setChecked(true); } d->configurationMenu->addAction(backendAction); } if (currentBackendReady()) { d->currentBackend->addActionsToConfigurationMenu(d->configurationMenu); } if (s->showThumbnails) { d->configurationMenu->addSeparator(); if (d->sortMenu) { d->configurationMenu->addMenu(d->sortMenu); } d->configurationMenu->addAction(d->actionPreviewSingleItems); d->configurationMenu->addAction(d->actionPreviewGroupedItems); d->configurationMenu->addAction(d->actionShowNumbersOnItems); } slotUpdateActionsEnabled(); } QAction* MapWidget::getControlAction(const QString& actionName) { if (actionName == QLatin1String("zoomin")) { return d->actionZoomIn; } else if (actionName == QLatin1String("zoomout")) { return d->actionZoomOut; } else if (actionName == QLatin1String("mousemode-regionselectionmode")) { return d->actionSetRegionSelectionMode; } else if (actionName == QLatin1String("mousemode-removecurrentregionselection")) { return d->actionRemoveCurrentRegionSelection; } else if (actionName == QLatin1String("mousemode-regionselectionfromiconmode")) { return d->actionSetRegionSelectionFromIconMode; } else if (actionName == QLatin1String("mousemode-removefilter")) { return d->actionRemoveFilter; } return 0; } /** * @brief Returns the control widget. */ QWidget* MapWidget::getControlWidget() { if (!d->controlWidget) { d->controlWidget = new QWidget(this); QHBoxLayout* const controlWidgetHBoxLayout = new QHBoxLayout(d->controlWidget); controlWidgetHBoxLayout->setContentsMargins(QMargins()); QToolButton* const configurationButton = new QToolButton(d->controlWidget); controlWidgetHBoxLayout->addWidget(configurationButton); configurationButton->setToolTip(i18n("Map settings")); configurationButton->setIcon(QIcon::fromTheme( QLatin1String("globe") )); configurationButton->setMenu(d->configurationMenu); configurationButton->setPopupMode(QToolButton::InstantPopup); QToolButton* const zoomInButton = new QToolButton(d->controlWidget); controlWidgetHBoxLayout->addWidget(zoomInButton); zoomInButton->setDefaultAction(d->actionZoomIn); QToolButton* const zoomOutButton = new QToolButton(d->controlWidget); controlWidgetHBoxLayout->addWidget(zoomOutButton); zoomOutButton->setDefaultAction(d->actionZoomOut); QToolButton* const showThumbnailsButton = new QToolButton(d->controlWidget); controlWidgetHBoxLayout->addWidget(showThumbnailsButton); showThumbnailsButton->setDefaultAction(d->actionShowThumbnails); QFrame* const vline1 = new QFrame(d->controlWidget); vline1->setLineWidth(1); vline1->setMidLineWidth(0); vline1->setFrameShape(QFrame::VLine); vline1->setFrameShadow(QFrame::Sunken); vline1->setMinimumSize(2, 0); vline1->updateGeometry(); controlWidgetHBoxLayout->addWidget(vline1); QToolButton* const increaseThumbnailSizeButton = new QToolButton(d->controlWidget); controlWidgetHBoxLayout->addWidget(increaseThumbnailSizeButton); increaseThumbnailSizeButton->setDefaultAction(d->actionIncreaseThumbnailSize); QToolButton* const decreaseThumbnailSizeButton = new QToolButton(d->controlWidget); controlWidgetHBoxLayout->addWidget(decreaseThumbnailSizeButton); decreaseThumbnailSizeButton->setDefaultAction(d->actionDecreaseThumbnailSize); /* --- --- --- */ d->mouseModesHolder = new QWidget(d->controlWidget); QHBoxLayout* const mouseModesHolderHBoxLayout = new QHBoxLayout(d->mouseModesHolder); mouseModesHolderHBoxLayout->setContentsMargins(QMargins()); controlWidgetHBoxLayout->addWidget(d->mouseModesHolder); QFrame* const vline2 = new QFrame(d->mouseModesHolder); vline2->setLineWidth(1); vline2->setMidLineWidth(0); vline2->setFrameShape(QFrame::VLine); vline2->setFrameShadow(QFrame::Sunken); vline2->setMinimumSize(2, 0); vline2->updateGeometry(); mouseModesHolderHBoxLayout->addWidget(vline2); d->setPanModeButton = new QToolButton(d->mouseModesHolder); mouseModesHolderHBoxLayout->addWidget(d->setPanModeButton); d->setPanModeButton->setDefaultAction(d->actionSetPanMode); d->setSelectionModeButton = new QToolButton(d->mouseModesHolder); mouseModesHolderHBoxLayout->addWidget(d->setSelectionModeButton); d->setSelectionModeButton->setDefaultAction(d->actionSetRegionSelectionMode); d->setRegionSelectionFromIconModeButton = new QToolButton(d->mouseModesHolder); mouseModesHolderHBoxLayout->addWidget(d->setRegionSelectionFromIconModeButton); d->setRegionSelectionFromIconModeButton->setDefaultAction(d->actionSetRegionSelectionFromIconMode); d->removeCurrentSelectionButton = new QToolButton(d->mouseModesHolder); mouseModesHolderHBoxLayout->addWidget(d->removeCurrentSelectionButton); d->removeCurrentSelectionButton->setDefaultAction(d->actionRemoveCurrentRegionSelection); d->setZoomModeButton = new QToolButton(d->mouseModesHolder); mouseModesHolderHBoxLayout->addWidget(d->setZoomModeButton); d->setZoomModeButton->setDefaultAction(d->actionSetZoomIntoGroupMode); d->setFilterModeButton = new QToolButton(d->mouseModesHolder); mouseModesHolderHBoxLayout->addWidget(d->setFilterModeButton); d->setFilterModeButton->setDefaultAction(d->actionSetFilterMode); d->removeFilterModeButton = new QToolButton(d->mouseModesHolder); mouseModesHolderHBoxLayout->addWidget(d->removeFilterModeButton); d->removeFilterModeButton->setDefaultAction(d->actionRemoveFilter); d->setSelectThumbnailMode = new QToolButton(d->mouseModesHolder); mouseModesHolderHBoxLayout->addWidget(d->setSelectThumbnailMode); d->setSelectThumbnailMode->setDefaultAction(d->actionSetSelectThumbnailMode); d->buttonStickyMode = new QToolButton(d->controlWidget); controlWidgetHBoxLayout->addWidget(d->buttonStickyMode); d->buttonStickyMode->setDefaultAction(d->actionStickyMode); d->hBoxForAdditionalControlWidgetItems = new QWidget(d->controlWidget); QHBoxLayout *hBoxForAdditionalControlWidgetItemsHBoxLayout = new QHBoxLayout(d->hBoxForAdditionalControlWidgetItems); hBoxForAdditionalControlWidgetItemsHBoxLayout->setContentsMargins(QMargins()); controlWidgetHBoxLayout->addWidget(d->hBoxForAdditionalControlWidgetItems); setVisibleMouseModes(s->visibleMouseModes); setVisibleExtraActions(d->visibleExtraActions); // add stretch after the controls: QHBoxLayout* const hBoxLayout = reinterpret_cast(d->controlWidget->layout()); if (hBoxLayout) { hBoxLayout->addStretch(); } } // make sure the menu exists, even if no backend has been set: rebuildConfigurationMenu(); return d->controlWidget; } void MapWidget::slotZoomIn() { if (!currentBackendReady()) return; d->currentBackend->zoomIn(); } void MapWidget::slotZoomOut() { if (!currentBackendReady()) return; d->currentBackend->zoomOut(); } void MapWidget::slotUpdateActionsEnabled() { if (!s->activeState) { // this widget is not active, no need to update the action availability return; } d->actionDecreaseThumbnailSize->setEnabled((s->showThumbnails)&&(s->thumbnailSize>GeoIfaceMinThumbnailSize)); /// @todo Define an upper limit for the thumbnail size! d->actionIncreaseThumbnailSize->setEnabled(s->showThumbnails); d->actionSetRegionSelectionMode->setEnabled(s->availableMouseModes.testFlag(MouseModeRegionSelection)); d->actionSetPanMode->setEnabled(s->availableMouseModes.testFlag(MouseModePan)); d->actionSetZoomIntoGroupMode->setEnabled(s->availableMouseModes.testFlag(MouseModeZoomIntoGroup)); d->actionSetRegionSelectionFromIconMode->setEnabled(s->availableMouseModes.testFlag(MouseModeRegionSelectionFromIcon)); d->actionSetFilterMode->setEnabled(s->availableMouseModes.testFlag(MouseModeFilter)); d->actionSetSelectThumbnailMode->setEnabled(s->availableMouseModes.testFlag(MouseModeSelectThumbnail)); // the 'Remove X' actions are only available if the corresponding X is actually there: bool clearRegionSelectionAvailable = s->availableMouseModes.testFlag(MouseModeRegionSelection); if (clearRegionSelectionAvailable && s->markerModel) { clearRegionSelectionAvailable = s->markerModel->getGlobalGroupState() & RegionSelectedMask; } d->actionRemoveCurrentRegionSelection->setEnabled(clearRegionSelectionAvailable); bool clearFilterAvailable = s->availableMouseModes.testFlag(MouseModeRegionSelectionFromIcon); if (clearFilterAvailable && s->markerModel) { clearFilterAvailable = s->markerModel->getGlobalGroupState() & FilteredPositiveMask; } d->actionRemoveFilter->setEnabled(clearFilterAvailable); d->actionStickyMode->setEnabled(d->availableExtraActions.testFlag(ExtraActionSticky)); /// @todo Only set the icons if they have to be changed! d->actionStickyMode->setIcon(QIcon::fromTheme(QLatin1String(d->actionStickyMode->isChecked() ? "document-encrypted" : "document-decrypt"))); d->actionShowThumbnails->setIcon(d->actionShowThumbnails->isChecked() ? QIcon::fromTheme(QLatin1String("folder-pictures")) : GeoIfaceGlobalObject::instance()->getMarkerPixmap(QLatin1String("marker-icon-16x16"))); // make sure the action for the current mouse mode is checked const QList mouseModeActions = d->mouseModeActionGroup->actions(); foreach(QAction* const action, mouseModeActions) { if (action->data().value() == s->currentMouseMode) { action->setChecked(true); break; } } } void MapWidget::slotChangeBackend(QAction* action) { GEOIFACE_ASSERT(action != 0); if (!action) return; const QString newBackendName = action->data().toString(); setBackend(newBackendName); } void MapWidget::updateMarkers() { if (!currentBackendReady()) { return; } // tell the backend to update the markers d->currentBackend->updateMarkers(); } void MapWidget::updateClusters() { /// @todo Find a better way to tell the TileGrouper about the backend s->tileGrouper->setCurrentBackend(d->currentBackend); s->tileGrouper->updateClusters(); } void MapWidget::slotClustersNeedUpdating() { if (currentBackendReady()) { d->currentBackend->slotClustersNeedUpdating(); } } /** * @brief Return color and style information for rendering the cluster * @param clusterIndex Index of the cluster * @param fillColor Color used to fill the circle * @param strokeColor Color used for the stroke around the circle * @param strokeStyle Style used to draw the stroke around the circle * @param labelText Text for the label * @param labelColor Color for the label text * @param overrideSelection Get the colors for a different selection state * @param overrideCount Get the colors for a different amount of markers */ void MapWidget::getColorInfos(const int clusterIndex, QColor* fillColor, QColor* strokeColor, Qt::PenStyle* strokeStyle, QString* labelText, QColor* labelColor, const GeoGroupState* const overrideSelection, const int* const overrideCount) const { /// @todo Call the new getColorInfos function! const GeoIfaceCluster& cluster = s->clusterList.at(clusterIndex); /// @todo Check that this number is already valid! const int nMarkers = overrideCount ? *overrideCount : cluster.markerCount; getColorInfos(overrideSelection ? *overrideSelection : cluster.groupState, nMarkers, fillColor, strokeColor, strokeStyle, labelText, labelColor); } void MapWidget::getColorInfos(const GeoGroupState groupState, const int nMarkers, QColor* fillColor, QColor* strokeColor, Qt::PenStyle* strokeStyle, QString* labelText, QColor* labelColor) const { if (nMarkers < 1000) { *labelText = QString::number(nMarkers); } else if ((nMarkers >= 1000) && (nMarkers <= 1950)) { *labelText = QString::fromLatin1("%L1k").arg(qreal(nMarkers)/1000.0, 0, 'f', 1); } else if ((nMarkers >= 1951) && (nMarkers < 19500)) { *labelText = QString::fromLatin1("%L1k").arg(qreal(nMarkers)/1000.0, 0, 'f', 0); } else { // convert to "1E5" notation for numbers >=20k: qreal exponent = floor(log((qreal)nMarkers)/log((qreal)10)); qreal nMarkersFirstDigit = round(qreal(nMarkers)/pow(10,exponent)); if (nMarkersFirstDigit >= 10) { nMarkersFirstDigit=round(nMarkersFirstDigit/10.0); exponent++; } *labelText = QString::fromLatin1("%1E%2").arg(int(nMarkersFirstDigit)).arg(int(exponent)); } *labelColor = QColor(Qt::black); *strokeStyle = Qt::NoPen; /// @todo On my system, digikam uses QColor(67, 172, 232) as the selection color. Or should we just use blue? switch (groupState & SelectedMask) { case SelectedNone: *strokeStyle = Qt::SolidLine; *strokeColor = QColor(Qt::black); break; case SelectedSome: *strokeStyle = Qt::DotLine; *strokeColor = QColor(Qt::blue);//67, 172, 232); break; case SelectedAll: *strokeStyle = Qt::SolidLine; *strokeColor = QColor(Qt::blue);//67, 172, 232); break; } /// @todo These are the fill colors for the circles, for cases in which only some or all of the images are positively filtered. Filtering is implemented in GeoIface, but the code here has not been adapted yet. QColor fillAll, fillSome, fillNone; if (nMarkers >= 100) { fillAll = QColor(255, 0, 0); fillSome = QColor(255, 188, 125); fillNone = QColor(255, 185, 185); } else if (nMarkers >= 50) { fillAll = QColor(255, 127, 0); fillSome = QColor(255, 190, 125); fillNone = QColor(255, 220, 185); } else if (nMarkers >= 10) { fillAll = QColor(255, 255, 0); fillSome = QColor(255, 255, 105); fillNone = QColor(255, 255, 185); } else if (nMarkers >= 2) { fillAll = QColor(0, 255, 0); fillSome = QColor(125, 255, 125); fillNone = QColor(185, 255, 255); } else { fillAll = QColor(0, 255, 255); fillSome = QColor(125, 255, 255); fillNone = QColor(185, 255, 255); } *fillColor = fillAll; // switch (groupState) // { // case PartialAll: // *fillColor = fillAll; // break; // case PartialSome: // *fillColor = fillSome; // break; // case PartialNone: // if (haveAnySolo) // { // *fillColor = fillNone; // } // else // { // *fillColor = fillAll; // } // break; // } } QString MapWidget::convertZoomToBackendZoom(const QString& someZoom, const QString& targetBackend) const { const QStringList zoomParts = someZoom.split(QLatin1Char( ':' )); GEOIFACE_ASSERT(zoomParts.count() == 2); const QString sourceBackend = zoomParts.first(); if (sourceBackend == targetBackend) { return someZoom; } const int sourceZoom = zoomParts.last().toInt(); int targetZoom = -1; // all of these values were found experimentally! if (targetBackend == QLatin1String("marble" )) { if (sourceZoom == 0) { targetZoom = 900; } else if (sourceZoom == 1) { targetZoom = 970; } else if (sourceZoom == 2) { targetZoom = 1108; } else if (sourceZoom == 3) { targetZoom = 1250; } else if (sourceZoom == 4) { targetZoom = 1384; } else if (sourceZoom == 5) { targetZoom = 1520; } else if (sourceZoom == 6) { targetZoom = 1665; } else if (sourceZoom == 7) { targetZoom = 1800; } else if (sourceZoom == 8) { targetZoom = 1940; } else if (sourceZoom == 9) { targetZoom = 2070; } else if (sourceZoom ==10) { targetZoom = 2220; } else if (sourceZoom ==11) { targetZoom = 2357; } else if (sourceZoom ==12) { targetZoom = 2510; } else if (sourceZoom ==13) { targetZoom = 2635; } else if (sourceZoom ==14) { targetZoom = 2775; } else if (sourceZoom ==15) { targetZoom = 2900; } else if (sourceZoom ==16) { targetZoom = 3051; } else if (sourceZoom ==17) { targetZoom = 3180; } else if (sourceZoom ==18) { targetZoom = 3295; } else if (sourceZoom ==19) { targetZoom = 3450; } else { targetZoom = 3500; } /// @todo Find values for level 20 and up } if (targetBackend == QLatin1String("googlemaps" )) { if (sourceZoom <= 900) { targetZoom = 0; } else if (sourceZoom <= 970) { targetZoom = 1; } else if (sourceZoom <=1108) { targetZoom = 2; } else if (sourceZoom <=1250) { targetZoom = 3; } else if (sourceZoom <=1384) { targetZoom = 4; } else if (sourceZoom <=1520) { targetZoom = 5; } else if (sourceZoom <=1665) { targetZoom = 6; } else if (sourceZoom <=1800) { targetZoom = 7; } else if (sourceZoom <=1940) { targetZoom = 8; } else if (sourceZoom <=2070) { targetZoom = 9; } else if (sourceZoom <=2220) { targetZoom = 10; } else if (sourceZoom <=2357) { targetZoom = 11; } else if (sourceZoom <=2510) { targetZoom = 12; } else if (sourceZoom <=2635) { targetZoom = 13; } else if (sourceZoom <=2775) { targetZoom = 14; } else if (sourceZoom <=2900) { targetZoom = 15; } else if (sourceZoom <=3051) { targetZoom = 16; } else if (sourceZoom <=3180) { targetZoom = 17; } else if (sourceZoom <=3295) { targetZoom = 18; } else if (sourceZoom <=3450) { targetZoom = 19; } else { targetZoom = 20; } /// @todo Find values for level 20 and up } GEOIFACE_ASSERT(targetZoom >= 0); return QString::fromLatin1("%1:%2").arg(targetBackend).arg(targetZoom); } void MapWidget::slotBackendZoomChanged(const QString& newZoom) { d->cacheZoom = newZoom; } void MapWidget::setZoom(const QString& newZoom) { d->cacheZoom = newZoom; if (currentBackendReady()) { d->currentBackend->setZoom(d->cacheZoom); } } QString MapWidget::getZoom() { if (currentBackendReady()) { d->cacheZoom = d->currentBackend->getZoom(); } return d->cacheZoom; } GeoCoordinates::Pair MapWidget::getRegionSelection() { return s->selectionRectangle; } void MapWidget::slotClustersMoved(const QIntList& clusterIndices, const QPair& snapTarget) { qCDebug(DIGIKAM_GEOIFACE_LOG) << clusterIndices; /// @todo We actually expect only one clusterindex int clusterIndex = clusterIndices.first(); GeoCoordinates targetCoordinates = s->clusterList.at(clusterIndex).coordinates; TileIndex::List movedTileIndices; if (s->clusterList.at(clusterIndex).groupState == SelectedNone) { // a not-selected marker was moved. update all of its items: const GeoIfaceCluster& cluster = s->clusterList.at(clusterIndex); for (int i = 0 ; i < cluster.tileIndicesList.count() ; ++i) { const TileIndex tileIndex = cluster.tileIndicesList.at(i); movedTileIndices << tileIndex; } } else { // selected items were moved. The model helper should know which tiles are selected, // therefore we give him an empty list } s->markerModel->onIndicesMoved(movedTileIndices, targetCoordinates, snapTarget.second); /** * @todo Clusters are marked as dirty by slotClustersNeedUpdating * which is called while we update the model */ } void MapWidget::addUngroupedModel(GeoModelHelper* const modelHelper) { s->ungroupedModels << modelHelper; /// @todo monitor all model signals! connect(modelHelper->model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(slotUngroupedModelChanged())); connect(modelHelper->model(), SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(slotUngroupedModelChanged())); connect(modelHelper->model(), SIGNAL(modelReset()), this, SLOT(slotUngroupedModelChanged())); connect(modelHelper, SIGNAL(signalVisibilityChanged()), this, SLOT(slotUngroupedModelChanged())); if (modelHelper->selectionModel()) { connect(modelHelper->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(slotUngroupedModelChanged())); } emit(signalUngroupedModelChanged(s->ungroupedModels.count() - 1)); } void MapWidget::removeUngroupedModel(GeoModelHelper* const modelHelper) { if (!modelHelper) return; const int modelIndex = s->ungroupedModels.indexOf(modelHelper); if (modelIndex < 0) return; /// @todo monitor all model signals! disconnect(modelHelper->model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(slotUngroupedModelChanged())); disconnect(modelHelper->model(), SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(slotUngroupedModelChanged())); disconnect(modelHelper->model(), SIGNAL(modelReset()), this, SLOT(slotUngroupedModelChanged())); disconnect(modelHelper, SIGNAL(signalVisibilityChanged()), this, SLOT(slotUngroupedModelChanged())); if (modelHelper->selectionModel()) { disconnect(modelHelper->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(slotUngroupedModelChanged())); } s->ungroupedModels.removeAt(modelIndex); // the indices changed, therefore send out notifications // sending out a signal with i=s->ungroupedModel.count() // will cause the backends to see that the last model is missing for (int i = modelIndex ; i <= s->ungroupedModels.count() ; ++i) { emit(signalUngroupedModelChanged(i)); } } void MapWidget::setGroupedModel(AbstractMarkerTiler* const markerModel) { s->markerModel = markerModel; if (s->markerModel) { s->markerModel->setActive(s->activeState); /// @todo this needs some buffering for the google maps backend connect(s->markerModel, SIGNAL(signalTilesOrSelectionChanged()), this, SLOT(slotRequestLazyReclustering())); if (d->currentBackend) { connect(s->markerModel, SIGNAL(signalThumbnailAvailableForIndex(QVariant,QPixmap)), d->currentBackend, SLOT(slotThumbnailAvailableForIndex(QVariant,QPixmap))); } } slotRequestLazyReclustering(); } void MapWidget::setShowThumbnails(const bool state) { s->showThumbnails = state; rebuildConfigurationMenu(); slotUpdateActionsEnabled(); slotRequestLazyReclustering(); } void MapWidget::slotShowThumbnailsChanged() { setShowThumbnails(d->actionShowThumbnails->isChecked()); } /** * @brief Request reclustering, repeated calls should generate only one actual update of the clusters */ void MapWidget::slotRequestLazyReclustering() { if (d->lazyReclusteringRequested) return; s->tileGrouper->setClustersDirty(); if (s->activeState) { d->lazyReclusteringRequested = true; QTimer::singleShot(0, this, SLOT(slotLazyReclusteringRequestCallBack())); } } /** * @brief Helper function to buffer reclustering */ void MapWidget::slotLazyReclusteringRequestCallBack() { if (!d->lazyReclusteringRequested) return; d->lazyReclusteringRequested = false; slotClustersNeedUpdating(); } /** * @todo Clicking on several clusters at once is not actually possible */ void MapWidget::slotClustersClicked(const QIntList& clusterIndices) { qCDebug(DIGIKAM_GEOIFACE_LOG)<currentMouseMode == MouseModeZoomIntoGroup) || (s->currentMouseMode == MouseModeRegionSelectionFromIcon) ) { int maxTileLevel = 0; Marble::GeoDataLineString tileString; for (int i = 0 ; i < clusterIndices.count() ; ++i) { const int clusterIndex = clusterIndices.at(i); const GeoIfaceCluster currentCluster = s->clusterList.at(clusterIndex); for (int j = 0 ; j < currentCluster.tileIndicesList.count() ; ++j) { const TileIndex& currentTileIndex = currentCluster.tileIndicesList.at(j); for (int corner = 1 ; corner <= 4 ; ++corner) { GeoCoordinates currentTileCoordinate; if (corner == 1) currentTileCoordinate = currentTileIndex.toCoordinates(TileIndex::CornerNW); else if (corner == 2) currentTileCoordinate = currentTileIndex.toCoordinates(TileIndex::CornerSW); else if (corner == 3) currentTileCoordinate = currentTileIndex.toCoordinates(TileIndex::CornerNE); else if (corner == 4) currentTileCoordinate = currentTileIndex.toCoordinates(TileIndex::CornerSE); const Marble::GeoDataCoordinates tileCoordinate(currentTileCoordinate.lon(), currentTileCoordinate.lat(), 0, Marble::GeoDataCoordinates::Degree); if (maxTileLevel < currentTileIndex.level()) { maxTileLevel = currentTileIndex.level(); } tileString.append(tileCoordinate); } } } Marble::GeoDataLatLonBox latLonBox = Marble::GeoDataLatLonBox::fromLineString(tileString); /// @todo Review this section /* if (maxTileLevel != 0) { //increase the selection boundaries with 0.1 degrees because some thumbnails aren't caught by selection latLonBox.setWest((latLonBox.west(Marble::GeoDataCoordinates::Degree)-(0.1/maxTileLevel)), Marble::GeoDataCoordinates::Degree); latLonBox.setNorth((latLonBox.north(Marble::GeoDataCoordinates::Degree)+(0.1/maxTileLevel)), Marble::GeoDataCoordinates::Degree); latLonBox.setEast((latLonBox.east(Marble::GeoDataCoordinates::Degree)+(0.1/maxTileLevel)), Marble::GeoDataCoordinates::Degree); latLonBox.setSouth((latLonBox.south(Marble::GeoDataCoordinates::Degree)-(0.1/maxTileLevel)), Marble::GeoDataCoordinates::Degree); } else { */ latLonBox.setWest((latLonBox.west(Marble::GeoDataCoordinates::Degree)-0.0001), Marble::GeoDataCoordinates::Degree); latLonBox.setNorth((latLonBox.north(Marble::GeoDataCoordinates::Degree)+0.0001), Marble::GeoDataCoordinates::Degree); latLonBox.setEast((latLonBox.east(Marble::GeoDataCoordinates::Degree)+0.0001), Marble::GeoDataCoordinates::Degree); latLonBox.setSouth((latLonBox.south(Marble::GeoDataCoordinates::Degree)-0.0001), Marble::GeoDataCoordinates::Degree); // } if (s->currentMouseMode == MouseModeZoomIntoGroup) { /// @todo Very small latLonBoxes can crash Marble d->currentBackend->centerOn(latLonBox); } else { const GeoCoordinates::Pair newSelection( GeoCoordinates(latLonBox.north(Marble::GeoDataCoordinates::Degree), latLonBox.west(Marble::GeoDataCoordinates::Degree)), GeoCoordinates(latLonBox.south(Marble::GeoDataCoordinates::Degree), latLonBox.east(Marble::GeoDataCoordinates::Degree)) ); s->selectionRectangle = newSelection; d->currentBackend->regionSelectionChanged(); emit(signalRegionSelectionChanged()); } } else if ((s->currentMouseMode == MouseModeFilter && s->selectionRectangle.first.hasCoordinates()) || (s->currentMouseMode == MouseModeSelectThumbnail) ) { // update the selection and filtering state of the clusters for (int i = 0 ; i < clusterIndices.count() ; ++i) { const int clusterIndex = clusterIndices.at(i); const GeoIfaceCluster currentCluster = s->clusterList.at(clusterIndex); const TileIndex::List tileIndices = currentCluster.tileIndicesList; /// @todo Isn't this cached in the cluster? const QVariant representativeIndex = getClusterRepresentativeMarker(clusterIndex, s->sortKey); AbstractMarkerTiler::ClickInfo clickInfo; clickInfo.tileIndicesList = tileIndices; clickInfo.representativeIndex = representativeIndex; clickInfo.groupSelectionState = currentCluster.groupState; clickInfo.currentMouseMode = s->currentMouseMode; s->markerModel->onIndicesClicked(clickInfo); } } } void MapWidget::dragEnterEvent(QDragEnterEvent* event) { /// @todo ignore drops if no marker tiler or model can accept them if (!d->dragDropHandler) { event->ignore(); return; } if (d->dragDropHandler->accepts(event) == Qt::IgnoreAction) { event->ignore(); return; } /// @todo need data about the dragged object: #markers, selected, icon, ... event->accept(); // if (!dragData->haveDragPixmap) // d->currentBackend->updateDragDropMarker(event->pos(), dragData); } void MapWidget::dragMoveEvent(QDragMoveEvent* event) { Q_UNUSED(event); /// @todo update the position of the drag marker if it is to be shown // if (!dragData->haveDragPixmap) // d->currentBackend->updateDragDropMarkerPosition(event->pos()); } void MapWidget::dropEvent(QDropEvent* event) { // remove the drag marker: // d->currentBackend->updateDragDropMarker(QPoint(), 0); if (!d->dragDropHandler) { event->ignore(); return; } GeoCoordinates dropCoordinates; if (!d->currentBackend->geoCoordinates(event->pos(), &dropCoordinates)) return; // the drag and drop handler handled the drop if it returned true here if (d->dragDropHandler->dropEvent(event, dropCoordinates)) { event->acceptProposedAction(); } } void MapWidget::dragLeaveEvent(QDragLeaveEvent* event) { Q_UNUSED(event); // remove the marker: // d->currentBackend->updateDragDropMarker(QPoint(), 0); } void MapWidget::markClustersAsDirty() { s->tileGrouper->setClustersDirty(); } void MapWidget::setDragDropHandler(GeoDragDropHandler* const dragDropHandler) { d->dragDropHandler = dragDropHandler; } QVariant MapWidget::getClusterRepresentativeMarker(const int clusterIndex, const int sortKey) { if (!s->markerModel) return QVariant(); const GeoIfaceCluster cluster = s->clusterList.at(clusterIndex); QMap::const_iterator it = cluster.representativeMarkers.find(sortKey); if (it != cluster.representativeMarkers.end()) return *it; QList repIndices; for (int i = 0 ; i < cluster.tileIndicesList.count() ; ++i) { repIndices << s->markerModel->getTileRepresentativeMarker(cluster.tileIndicesList.at(i), sortKey); } const QVariant clusterRepresentative = s->markerModel->bestRepresentativeIndexFromList(repIndices, sortKey); s->clusterList[clusterIndex].representativeMarkers[sortKey] = clusterRepresentative; return clusterRepresentative; } void MapWidget::slotItemDisplaySettingsChanged() { s->previewSingleItems = d->actionPreviewSingleItems->isChecked(); s->previewGroupedItems = d->actionPreviewGroupedItems->isChecked(); s->showNumbersOnItems = d->actionShowNumbersOnItems->isChecked(); /// @todo Update action availability? /// @todo We just need to update the display, no need to recluster? slotRequestLazyReclustering(); } void MapWidget::setSortOptionsMenu(QMenu* const sortMenu) { d->sortMenu = sortMenu; rebuildConfigurationMenu(); } void MapWidget::setSortKey(const int sortKey) { s->sortKey = sortKey; // this is probably faster than writing a function that changes all the clusters icons... /// @todo We just need to update the display, no need to recluster? slotRequestLazyReclustering(); } QPixmap MapWidget::getDecoratedPixmapForCluster(const int clusterId, const GeoGroupState* const selectedStateOverride, const int* const countOverride, QPoint* const centerPoint) { GeoIfaceCluster& cluster = s->clusterList[clusterId]; int markerCount = cluster.markerCount; GeoGroupState groupState = cluster.groupState; if (selectedStateOverride) { groupState = *selectedStateOverride; markerCount = *countOverride; } const GeoGroupState selectedState = groupState & SelectedMask; // first determine all the color and style values QColor fillColor; QColor strokeColor; Qt::PenStyle strokeStyle; QColor labelColor; QString labelText; getColorInfos(clusterId, &fillColor, &strokeColor, &strokeStyle, &labelText, &labelColor, &selectedState, &markerCount); // determine whether we should use a pixmap or a placeholder if (!s->showThumbnails) { /// @todo Handle positive filtering and region selection! QString pixmapName = fillColor.name().mid(1); if (selectedState == SelectedAll) { pixmapName += QLatin1String("-selected"); } if (selectedState == SelectedSome) { pixmapName += QLatin1String("-someselected"); } const QPixmap& markerPixmap = GeoIfaceGlobalObject::instance()->getMarkerPixmap(pixmapName); // update the display information stored in the cluster: cluster.pixmapType = GeoIfaceCluster::PixmapMarker; cluster.pixmapOffset = QPoint(markerPixmap.width()/2, markerPixmap.height()-1); cluster.pixmapSize = markerPixmap.size(); if (centerPoint) { *centerPoint = cluster.pixmapOffset; } return markerPixmap; } /// @todo This check is strange, there can be no clusters without a markerModel? bool displayThumbnail = (s->markerModel != 0); if (displayThumbnail) { if (markerCount==1) { displayThumbnail = s->previewSingleItems; } else { displayThumbnail = s->previewGroupedItems; } } if (displayThumbnail) { const QVariant representativeMarker = getClusterRepresentativeMarker(clusterId, s->sortKey); const int undecoratedThumbnailSize = getUndecoratedThumbnailSize(); QPixmap clusterPixmap = s->markerModel->pixmapFromRepresentativeIndex(representativeMarker, QSize(undecoratedThumbnailSize, undecoratedThumbnailSize)); if (!clusterPixmap.isNull()) { QPixmap resultPixmap(clusterPixmap.size() + QSize(2,2)); // we may draw with partially transparent pixmaps later, so make sure we have a defined // background color resultPixmap.fill(QColor::fromRgb(0xff, 0xff, 0xff)); QPainter painter(&resultPixmap); // painter.setRenderHint(QPainter::Antialiasing); const int borderWidth = (groupState&SelectedSome) ? 2 : 1; QPen borderPen; borderPen.setWidth(borderWidth); borderPen.setJoinStyle(Qt::MiterJoin); GeoGroupState globalState = s->markerModel->getGlobalGroupState(); /// @todo What about partially in the region or positively filtered? const bool clusterIsNotInRegionSelection = (globalState & RegionSelectedMask) && ((groupState & RegionSelectedMask) == RegionSelectedNone); const bool clusterIsNotPositivelyFiltered = (globalState & FilteredPositiveMask) && ((groupState & FilteredPositiveMask) == FilteredPositiveNone); const bool shouldGrayOut = clusterIsNotInRegionSelection || clusterIsNotPositivelyFiltered; const bool shouldCrossOut = clusterIsNotInRegionSelection; if (shouldGrayOut) { /// @todo Cache the alphaPixmap! QPixmap alphaPixmap(clusterPixmap.size()); alphaPixmap.fill(QColor::fromRgb(0x80, 0x80, 0x80)); /* NOTE : old Qt4 code ported to Qt5 due to deprecated QPixmap::setAlphaChannel() clusterPixmap.setAlphaChannel(alphaPixmap); */ QPainter p(&clusterPixmap); p.setOpacity(0.2); p.drawPixmap(0, 0, alphaPixmap); p.end(); } painter.drawPixmap(QPoint(1,1), clusterPixmap); if (shouldGrayOut || shouldCrossOut) { // draw a red cross above the pixmap QPen crossPen(Qt::red); if (!shouldCrossOut) { /// @todo Maybe we should also do a cross for not positively filtered images? crossPen.setColor(Qt::blue); } crossPen.setWidth(2); painter.setPen(crossPen); const int width = resultPixmap.size().width(); const int height = resultPixmap.size().height(); painter.drawLine(0, 0, width-1, height-1); painter.drawLine(width-1, 0, 0, height-1); } if (strokeStyle != Qt::SolidLine) { // paint a white border around the image borderPen.setColor(Qt::white); painter.setPen(borderPen); painter.drawRect(borderWidth-1, borderWidth-1, resultPixmap.size().width()-borderWidth, resultPixmap.size().height()-borderWidth); } // now draw the selection border borderPen.setColor(strokeColor); borderPen.setStyle(strokeStyle); painter.setPen(borderPen); painter.drawRect(borderWidth-1, borderWidth-1, resultPixmap.size().width()-borderWidth, resultPixmap.size().height()-borderWidth); if (s->showNumbersOnItems) { QPen labelPen(labelColor); // note: the pen has to be set, otherwise the bounding rect is 0 x 0!!! painter.setPen(labelPen); const QRect textRect(0, 0, resultPixmap.width(), resultPixmap.height()); QRect textBoundingRect = painter.boundingRect(textRect, Qt::AlignHCenter | Qt::AlignVCenter, labelText); textBoundingRect.adjust(-1, -1, 1, 1); // fill the bounding rect: painter.setPen(Qt::NoPen); painter.setBrush(QColor::fromRgb(0xff, 0xff, 0xff, 0x80)); painter.drawRect(textBoundingRect); // draw the text: painter.setPen(labelPen); painter.setBrush(Qt::NoBrush); painter.drawText(textRect, Qt::AlignHCenter|Qt::AlignVCenter, labelText); } // update the display information stored in the cluster: cluster.pixmapType = GeoIfaceCluster::PixmapImage; cluster.pixmapOffset = QPoint(resultPixmap.width()/2, resultPixmap.height()/2); cluster.pixmapSize = resultPixmap.size(); if (centerPoint) { *centerPoint = cluster.pixmapOffset; } return resultPixmap; } } // we do not have a thumbnail, draw the circle instead: const int circleRadius = s->thumbnailSize/2; QPen circlePen; circlePen.setColor(strokeColor); circlePen.setStyle(strokeStyle); circlePen.setWidth(2); QBrush circleBrush(fillColor); QPen labelPen; labelPen.setColor(labelColor); const QRect circleRect(0, 0, 2*circleRadius, 2*circleRadius); const int pixmapDiameter = 2*(circleRadius+1); QPixmap circlePixmap(pixmapDiameter, pixmapDiameter); /// @todo cache this somehow circlePixmap.fill(QColor(0, 0, 0, 0)); QPainter circlePainter(&circlePixmap); circlePainter.setPen(circlePen); circlePainter.setBrush(circleBrush); circlePainter.drawEllipse(circleRect); circlePainter.setPen(labelPen); circlePainter.setBrush(Qt::NoBrush); circlePainter.drawText(circleRect, Qt::AlignHCenter|Qt::AlignVCenter, labelText); // update the display information stored in the cluster: cluster.pixmapType = GeoIfaceCluster::PixmapCircle; cluster.pixmapOffset = QPoint(circlePixmap.width()/2, circlePixmap.height()/2); cluster.pixmapSize = circlePixmap.size(); if (centerPoint) { *centerPoint = QPoint(circlePixmap.width()/2, circlePixmap.height()/2); } return circlePixmap; } void MapWidget::setThumnailSize(const int newThumbnailSize) { s->thumbnailSize = qMax(GeoIfaceMinThumbnailSize, newThumbnailSize); // make sure the grouping radius is larger than the thumbnail size if (2*s->thumbnailGroupingRadius < newThumbnailSize) { /// @todo more straightforward way for this? s->thumbnailGroupingRadius = newThumbnailSize/2 + newThumbnailSize%2; } if (s->showThumbnails) { slotRequestLazyReclustering(); } slotUpdateActionsEnabled(); } void MapWidget::setThumbnailGroupingRadius(const int newGroupingRadius) { s->thumbnailGroupingRadius = qMax(GeoIfaceMinThumbnailGroupingRadius, newGroupingRadius); // make sure the thumbnails are smaller than the grouping radius if (2*s->thumbnailGroupingRadius < s->thumbnailSize) { s->thumbnailSize = 2*newGroupingRadius; } if (s->showThumbnails) { slotRequestLazyReclustering(); } slotUpdateActionsEnabled(); } void MapWidget::setMarkerGroupingRadius(const int newGroupingRadius) { s->markerGroupingRadius = qMax(GeoIfaceMinMarkerGroupingRadius, newGroupingRadius); if (!s->showThumbnails) { slotRequestLazyReclustering(); } slotUpdateActionsEnabled(); } void MapWidget::slotDecreaseThumbnailSize() { if (!s->showThumbnails) return; if (s->thumbnailSize>GeoIfaceMinThumbnailSize) { const int newThumbnailSize = qMax(GeoIfaceMinThumbnailSize, s->thumbnailSize-5); // make sure the grouping radius is also decreased // this will automatically decrease the thumbnail size as well setThumbnailGroupingRadius(newThumbnailSize/2); } } void MapWidget::slotIncreaseThumbnailSize() { if (!s->showThumbnails) return; setThumnailSize(s->thumbnailSize+5); } int MapWidget::getThumbnailSize() const { return s->thumbnailSize; } int MapWidget::getUndecoratedThumbnailSize() const { return s->thumbnailSize-2; } void MapWidget::setRegionSelection(const GeoCoordinates::Pair& region) { s->selectionRectangle = region; d->currentBackend->regionSelectionChanged(); slotUpdateActionsEnabled(); } void MapWidget::clearRegionSelection() { s->selectionRectangle.first.clear(); d->currentBackend->regionSelectionChanged(); slotUpdateActionsEnabled(); } void MapWidget::slotNewSelectionFromMap(const Digikam::GeoCoordinates::Pair& sel) { /// @todo Should the backend update s on its own? s->selectionRectangle = sel; slotUpdateActionsEnabled(); emit(signalRegionSelectionChanged()); } void MapWidget::slotRemoveCurrentRegionSelection() { clearRegionSelection(); d->currentBackend->regionSelectionChanged(); slotUpdateActionsEnabled(); emit(signalRegionSelectionChanged()); } void MapWidget::slotUngroupedModelChanged() { // determine the index under which we handle this model QObject* const senderObject = sender(); QAbstractItemModel* const senderModel = qobject_cast(senderObject); if (senderModel) { for (int i = 0 ; i < s->ungroupedModels.count() ; ++i) { if (s->ungroupedModels.at(i)->model() == senderModel) { emit(signalUngroupedModelChanged(i)); break; } } return; } GeoModelHelper* const senderHelper = qobject_cast(senderObject); if (senderHelper) { for (int i = 0 ; i < s->ungroupedModels.count() ; ++i) { if (s->ungroupedModels.at(i) == senderHelper) { emit(signalUngroupedModelChanged(i)); break; } } } QItemSelectionModel* const senderSelectionModel = qobject_cast(senderObject); if (senderSelectionModel) { for (int i = 0 ; i < s->ungroupedModels.count() ; ++i) { if (s->ungroupedModels.at(i)->selectionModel()==senderSelectionModel) { emit(signalUngroupedModelChanged(i)); break; } } return; } } void MapWidget::addWidgetToControlWidget(QWidget* const newWidget) { // make sure the control widget exists if (!d->controlWidget) getControlWidget(); QHBoxLayout* const hBoxLayout = reinterpret_cast(d->hBoxForAdditionalControlWidgetItems->layout()); if (hBoxLayout) { hBoxLayout->addWidget(newWidget); } } // Static methods --------------------------------------------------------- QString MapWidget::MarbleWidgetVersion() { return QString(Marble::MARBLE_VERSION_STRING).section(QLatin1Char(' '), 0, 0); } void MapWidget::setActive(const bool state) { const bool oldState = s->activeState; s->activeState = state; if (d->currentBackend) { d->currentBackend->setActive(state); } if (s->markerModel) { s->markerModel->setActive(state); } if (state) { // do we have a map widget shown? if ( (d->stackedLayout->count() == 1) && d->currentBackend ) { setMapWidgetInFrame(d->currentBackend->mapWidget()); // call this slot manually in case the backend was ready right away: if (d->currentBackend->isReady()) { slotBackendReadyChanged(d->currentBackendName); } else { rebuildConfigurationMenu(); } } } if (state && !oldState && s->tileGrouper->getClustersDirty()) { slotRequestLazyReclustering(); } } bool MapWidget::getActiveState() { return s->activeState; } void MapWidget::setVisibleMouseModes(const GeoMouseModes mouseModes) { s->visibleMouseModes = mouseModes; if (d->mouseModesHolder) { d->mouseModesHolder->setVisible(s->visibleMouseModes); d->setSelectionModeButton->setVisible(s->visibleMouseModes.testFlag(MouseModeRegionSelection)); d->removeCurrentSelectionButton->setVisible(s->visibleMouseModes.testFlag(MouseModeRegionSelection)); d->setPanModeButton->setVisible(s->visibleMouseModes.testFlag(MouseModePan)); d->setZoomModeButton->setVisible(s->visibleMouseModes.testFlag(MouseModeZoomIntoGroup)); d->setRegionSelectionFromIconModeButton->setVisible(s->visibleMouseModes.testFlag(MouseModeRegionSelectionFromIcon)); d->setFilterModeButton->setVisible(s->visibleMouseModes.testFlag(MouseModeFilter)); d->removeFilterModeButton->setVisible(s->visibleMouseModes.testFlag(MouseModeFilter)); d->setSelectThumbnailMode->setVisible(s->visibleMouseModes.testFlag(MouseModeSelectThumbnail)); } } void MapWidget::setAvailableMouseModes(const GeoMouseModes mouseModes) { s->availableMouseModes = mouseModes; } bool MapWidget::getStickyModeState() const { return d->actionStickyMode->isChecked(); } void MapWidget::setStickyModeState(const bool state) { d->actionStickyMode->setChecked(state); slotUpdateActionsEnabled(); } void MapWidget::setVisibleExtraActions(const GeoExtraActions actions) { d->visibleExtraActions = actions; if (d->buttonStickyMode) { d->buttonStickyMode->setVisible(actions.testFlag(ExtraActionSticky)); } slotUpdateActionsEnabled(); } void MapWidget::setEnabledExtraActions(const GeoExtraActions actions) { d->availableExtraActions = actions; slotUpdateActionsEnabled(); } void MapWidget::slotStickyModeChanged() { slotUpdateActionsEnabled(); emit(signalStickyModeChanged()); } void MapWidget::setAllowModifications(const bool state) { s->modificationsAllowed = state; slotUpdateActionsEnabled(); slotRequestLazyReclustering(); } /** * @brief Adjusts the visible map area such that all grouped markers are visible. * * Note that a call to this function currently has no effect if the widget has been * set inactive via setActive() or the backend is not yet ready. * * @param useSaneZoomLevel Stop zooming at a sane level, if markers are too close together. */ void MapWidget::adjustBoundariesToGroupedMarkers(const bool useSaneZoomLevel) { if ( (!s->activeState) || (!s->markerModel) || (!currentBackendReady()) ) { return; } Marble::GeoDataLineString tileString; /// @todo not sure that this is the best way to find the bounding box of all items for (AbstractMarkerTiler::NonEmptyIterator tileIterator(s->markerModel, TileIndex::MaxLevel); !tileIterator.atEnd(); tileIterator.nextIndex()) { const TileIndex tileIndex = tileIterator.currentIndex(); - for(int corner = 1 ; corner <= 4 ; corner++) + for (int corner = 1 ; corner <= 4 ; ++corner) { const GeoCoordinates currentTileCoordinate = tileIndex.toCoordinates(TileIndex::CornerPosition(corner)); const Marble::GeoDataCoordinates tileCoordinate(currentTileCoordinate.lon(), currentTileCoordinate.lat(), 0, Marble::GeoDataCoordinates::Degree); tileString.append(tileCoordinate); } } const Marble::GeoDataLatLonBox latLonBox = Marble::GeoDataLatLonBox::fromLineString(tileString); /// @todo use a sane zoom level d->currentBackend->centerOn(latLonBox, useSaneZoomLevel); } void MapWidget::refreshMap() { slotRequestLazyReclustering(); } void MapWidget::setShowPlaceholderWidget(const bool state) { if (state) { d->stackedLayout->setCurrentIndex(0); } else { if (d->stackedLayout->count() > 1) { d->stackedLayout->setCurrentIndex(1); } } } /** * @brief Set @p widgetForFrame as the widget in the frame, but does not show it. */ void MapWidget::setMapWidgetInFrame(QWidget* const widgetForFrame) { if (d->stackedLayout->count() > 1) { // widget 0 is the status widget, widget 1 is the map widget if (d->stackedLayout->widget(1) == widgetForFrame) { return; } // there is some other widget at the target position. // remove it and add our widget instead d->stackedLayout->removeWidget(d->stackedLayout->widget(1)); } d->stackedLayout->addWidget(widgetForFrame); } void MapWidget::removeMapWidgetFromFrame() { if (d->stackedLayout->count() > 1) { d->stackedLayout->removeWidget(d->stackedLayout->widget(1)); } d->stackedLayout->setCurrentIndex(0); } void MapWidget::slotMouseModeChanged(QAction* triggeredAction) { // determine the new mouse mode: const QVariant triggeredActionData = triggeredAction->data(); const GeoMouseModes newMouseMode = triggeredActionData.value(); if (newMouseMode == s->currentMouseMode) { return; } // store the new mouse mode: s->currentMouseMode = newMouseMode; if (d->currentBackend) { d->currentBackend->mouseModeChanged(); } emit(signalMouseModeChanged(s->currentMouseMode)); /// @todo Update action availability? } bool MapWidget::currentBackendReady() const { if (!d->currentBackend) { return false; } return d->currentBackend->isReady(); } void MapWidget::setMouseMode(const GeoMouseModes mouseMode) { s->currentMouseMode = mouseMode; if (currentBackendReady()) { d->currentBackend->mouseModeChanged(); } slotUpdateActionsEnabled(); } void MapWidget::setTrackManager(TrackManager* const trackManager) { s->trackManager = trackManager; // Some backends track the track manager activity even when not active // therefore they have to be notified. foreach(MapBackend* const backend, d->loadedBackends) { backend->slotTrackManagerChanged(); } } } // namespace Digikam diff --git a/core/utilities/imageeditor/main/imagewindow.cpp b/core/utilities/imageeditor/main/imagewindow.cpp index 964e689a04..b7865320bf 100644 --- a/core/utilities/imageeditor/main/imagewindow.cpp +++ b/core/utilities/imageeditor/main/imagewindow.cpp @@ -1,1302 +1,1302 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2004-02-12 * Description : digiKam image editor GUI * * Copyright (C) 2004-2005 by Renchi Raju * Copyright (C) 2004-2018 by Gilles Caulier * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "imagewindow.h" #include "imagewindow_p.h" namespace Digikam { ImageWindow* ImageWindow::m_instance = 0; ImageWindow* ImageWindow::imageWindow() { if (!m_instance) { new ImageWindow(); } return m_instance; } bool ImageWindow::imageWindowCreated() { return m_instance; } ImageWindow::ImageWindow() : EditorWindow(QLatin1String("Image Editor")), d(new Private) { setXMLFile(QLatin1String("imageeditorui5.rc")); m_instance = this; // We don't want to be deleted on close setAttribute(Qt::WA_DeleteOnClose, false); setAcceptDrops(true); // -- Build the GUI ------------------------------- setupUserArea(); setupActions(); setupStatusBar(); createGUI(xmlFile()); cleanupActions(); showMenuBarAction()->setChecked(!menuBar()->isHidden()); // NOTE: workaround for bug #171080 // Create tool selection view setupSelectToolsAction(); // Create context menu. setupContextMenu(); // Make signals/slots connections setupConnections(); // -- Read settings -------------------------------- readSettings(); KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(configGroupName()); applyMainWindowSettings(group); d->thumbBarDock->setShouldBeVisible(group.readEntry(d->configShowThumbbarEntry, false)); setAutoSaveSettings(configGroupName(), true); d->viewContainer->setAutoSaveSettings(QLatin1String("ImageViewer Thumbbar"), true); //------------------------------------------------------------- d->rightSideBar->setConfigGroup(KConfigGroup(&group, QLatin1String("Right Sidebar"))); d->rightSideBar->loadState(); d->rightSideBar->populateTags(); slotSetupChanged(); } ImageWindow::~ImageWindow() { m_instance = 0; delete d->rightSideBar; delete d->thumbBar; delete d; } void ImageWindow::closeEvent(QCloseEvent* e) { if (!queryClose()) { e->ignore(); return; } // put right side bar in a defined state emit signalNoCurrentItem(); m_canvas->resetImage(); // There is one nasty habit with the thumbnail bar if it is floating: it // doesn't close when the parent window does, so it needs to be manually // closed. If the light table is opened again, its original state needs to // be restored. // This only needs to be done when closing a visible window and not when // destroying a closed window, since the latter case will always report that // the thumbnail bar isn't visible. if (isVisible()) { thumbBar()->hide(); } KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(configGroupName()); d->rightSideBar->setConfigGroup(KConfigGroup(&group, "Right Sidebar")); d->rightSideBar->saveState(); saveSettings(); DXmlGuiWindow::closeEvent(e); } void ImageWindow::showEvent(QShowEvent*) { // Restore the visibility of the thumbbar and start autosaving again. thumbBar()->restoreVisibility(); } bool ImageWindow::queryClose() { // Note: we re-implement closeEvent above for this window. // Additionally, queryClose is called from DigikamApp. // wait if a save operation is currently running if (!waitForSavingToComplete()) { return false; } return promptUserSave(d->currentUrl()); } void ImageWindow::loadItemInfos(const ItemInfoList& imageInfoList, const ItemInfo& imageInfoCurrent, const QString& caption) { // Very first thing is to check for changes, user may choose to cancel operation if (!promptUserSave(d->currentUrl(), AskIfNeeded)) { return; } d->currentItemInfo = imageInfoCurrent; // Note: Addition is asynchronous, indexes not yet available // We enable thumbbar as soon as indexes are available // If not, we load imageInfoCurrent, then the index 0, then again imageInfoCurrent d->thumbBar->setEnabled(false); d->imageInfoModel->setItemInfos(imageInfoList); d->setThumbBarToCurrent(); if (!caption.isEmpty()) { setCaption(i18n("Image Editor - %1", caption)); } else { setCaption(i18n("Image Editor")); } // it can slightly improve the responsiveness when inserting an event loop run here QTimer::singleShot(0, this, SLOT(slotLoadItemInfosStage2())); } void ImageWindow::slotLoadItemInfosStage2() { // if window is minimized, show it if (isMinimized()) { KWindowSystem::unminimizeWindow(winId()); } slotLoadCurrent(); } void ImageWindow::slotThumbBarModelReady() { d->thumbBar->setEnabled(true); } void ImageWindow::openImage(const ItemInfo& info) { if (d->currentItemInfo == info) { return; } d->currentItemInfo = info; d->ensureModelContains(d->currentItemInfo); slotLoadCurrent(); } void ImageWindow::slotLoadCurrent() { if (!d->currentIsValid()) { return; } m_canvas->load(d->currentItemInfo.filePath(), m_IOFileSettings); QModelIndex next = d->nextIndex(); if (next.isValid()) { m_canvas->preload(d->imageInfo(next).filePath()); } d->setThumbBarToCurrent(); // Do this _after_ the canvas->load(), so that the main view histogram does not load // a smaller version if a raw image, and after that the EditorCore loads the full version. // So first let EditorCore create its loading task, only then any external objects. setViewToURL(d->currentUrl()); } void ImageWindow::setViewToURL(const QUrl& url) { emit signalURLChanged(url); } void ImageWindow::slotThumbBarImageSelected(const ItemInfo& info) { if (d->currentItemInfo == info || !d->thumbBar->isEnabled()) { return; } if (!promptUserSave(d->currentUrl(), AskIfNeeded, false)) { return; } d->currentItemInfo = info; slotLoadCurrent(); } void ImageWindow::slotDroppedOnThumbbar(const QList& infos) { // Check whether dropped image list is empty if (infos.isEmpty()) { return; } // Create new list and images that are not present currently in the thumbbar QList toAdd; foreach (const ItemInfo& it, infos) { QModelIndex index(d->imageFilterModel->indexForItemInfo(it)); if (!index.isValid()) { toAdd.append(it); } } // Loading images if new images are dropped if (!toAdd.isEmpty()) { loadItemInfos(ItemInfoList(toAdd), toAdd.first(), QString()); } } void ImageWindow::slotFileOriginChanged(const QString& filePath) { // By redo or undo, we have virtually switched to a new image. // So we do _not_ load anything! ItemInfo newCurrent = ItemInfo::fromLocalFile(filePath); if (newCurrent.isNull() || !d->imageInfoModel->hasImage(newCurrent)) { return; } d->currentItemInfo = newCurrent; d->setThumbBarToCurrent(); setViewToURL(d->currentUrl()); } void ImageWindow::loadIndex(const QModelIndex& index) { if (!promptUserSave(d->currentUrl(), AskIfNeeded)) { return; } if (!index.isValid()) { return; } d->currentItemInfo = d->imageFilterModel->imageInfo(index); slotLoadCurrent(); } void ImageWindow::slotForward() { loadIndex(d->nextIndex()); } void ImageWindow::slotBackward() { loadIndex(d->previousIndex()); } void ImageWindow::slotFirst() { loadIndex(d->firstIndex()); } void ImageWindow::slotLast() { loadIndex(d->lastIndex()); } void ImageWindow::slotChanged() { QString mpixels; QSize dims(m_canvas->imageWidth(), m_canvas->imageHeight()); mpixels.setNum(dims.width()*dims.height() / 1000000.0, 'f', 2); QString str = (!dims.isValid()) ? i18n("Unknown") : i18n("%1x%2 (%3Mpx)", dims.width(), dims.height(), mpixels); m_resLabel->setAdjustedText(str); if (!d->currentIsValid()) { return; } DImg* const img = m_canvas->interface()->getImg(); DImageHistory history = m_canvas->interface()->getItemHistory(); DImageHistory redoHistory = m_canvas->interface()->getImageHistoryOfFullRedo(); d->rightSideBar->itemChanged(d->currentItemInfo, m_canvas->getSelectedArea(), img, redoHistory); // Filters for redo will be turn in grey out d->rightSideBar->getFiltersHistoryTab()->setEnabledHistorySteps(history.actionCount()); /* if (!d->currentItemInfo.isNull()) { } else { d->rightSideBar->itemChanged(d->currentUrl(), m_canvas->getSelectedArea(), img); } */ } void ImageWindow::slotToggleTag(const QUrl& url, int tagID) { toggleTag(ItemInfo::fromUrl(url), tagID); } void ImageWindow::toggleTag(int tagID) { toggleTag(d->currentItemInfo, tagID); } void ImageWindow::toggleTag(const ItemInfo& info, int tagID) { if (!info.isNull()) { if (info.tagIds().contains(tagID)) { FileActionMngr::instance()->removeTag(info, tagID); } else { FileActionMngr::instance()->assignTag(info, tagID); } } } void ImageWindow::slotAssignTag(int tagID) { if (!d->currentItemInfo.isNull()) { FileActionMngr::instance()->assignTag(d->currentItemInfo, tagID); } } void ImageWindow::slotRemoveTag(int tagID) { if (!d->currentItemInfo.isNull()) { FileActionMngr::instance()->removeTag(d->currentItemInfo, tagID); } } void ImageWindow::slotAssignPickLabel(int pickId) { assignPickLabel(d->currentItemInfo, pickId); } void ImageWindow::slotAssignColorLabel(int colorId) { assignColorLabel(d->currentItemInfo, colorId); } void ImageWindow::assignPickLabel(const ItemInfo& info, int pickId) { if (!info.isNull()) { FileActionMngr::instance()->assignPickLabel(info, pickId); } } void ImageWindow::assignColorLabel(const ItemInfo& info, int colorId) { if (!info.isNull()) { FileActionMngr::instance()->assignColorLabel(info, colorId); } } void ImageWindow::slotAssignRating(int rating) { assignRating(d->currentItemInfo, rating); } void ImageWindow::assignRating(const ItemInfo& info, int rating) { rating = qMin(RatingMax, qMax(RatingMin, rating)); if (!info.isNull()) { FileActionMngr::instance()->assignRating(info, rating); } } void ImageWindow::slotRatingChanged(const QUrl& url, int rating) { assignRating(ItemInfo::fromUrl(url), rating); } void ImageWindow::slotColorLabelChanged(const QUrl& url, int color) { assignColorLabel(ItemInfo::fromUrl(url), color); } void ImageWindow::slotPickLabelChanged(const QUrl& url, int pick) { assignPickLabel(ItemInfo::fromUrl(url), pick); } void ImageWindow::slotUpdateItemInfo() { QString text = i18nc(" ( of )", "%1 (%2 of %3)", d->currentItemInfo.name(), d->currentIndex().row() + 1, d->imageFilterModel->rowCount()); m_nameLabel->setText(text); if (!m_actionEnabledState) { return; } if (d->imageInfoModel->rowCount() == 1) { m_backwardAction->setEnabled(false); m_forwardAction->setEnabled(false); m_firstAction->setEnabled(false); m_lastAction->setEnabled(false); } else { m_backwardAction->setEnabled(true); m_forwardAction->setEnabled(true); m_firstAction->setEnabled(true); m_lastAction->setEnabled(true); } if (d->currentIndex() == d->firstIndex()) { m_backwardAction->setEnabled(false); m_firstAction->setEnabled(false); } if (d->currentIndex() == d->lastIndex()) { m_forwardAction->setEnabled(false); m_lastAction->setEnabled(false); } /* // Disable some menu actions if the current root image URL // is not include in the digiKam Albums library database. // This is necessary when ImageEditor is opened from cameraclient. QUrl u(d->currentUrl().directory()); PAlbum* palbum = AlbumManager::instance()->findPAlbum(u); if (!palbum) { m_fileDeleteAction->setEnabled(false); } else { m_fileDeleteAction->setEnabled(true); } */ } void ImageWindow::slotToMainWindow() { close(); } void ImageWindow::saveIsComplete() { // With save(), we do not reload the image but just continue using the data. // This means that a saving operation does not lead to quality loss for // subsequent editing operations. // put image in cache, the LoadingCacheInterface cares for the details LoadingCacheInterface::putImage(m_savingContext.destinationURL.toLocalFile(), m_canvas->currentImage()); ItemInfo info = ScanController::instance()->scannedInfo(m_savingContext.destinationURL.toLocalFile()); // Save new face tags to the image saveFaceTagsToImage(info); // reset the orientation flag in the database DMetadata meta(m_canvas->currentImage().getMetadata()); d->currentItemInfo.setOrientation(meta.getItemOrientation()); // Pop-up a message to bring user when save is done. DNotificationWrapper(QLatin1String("editorsavefilecompleted"), i18n("Image saved successfully"), this, windowTitle()); resetOrigin(); QModelIndex next = d->nextIndex(); if (next.isValid()) { m_canvas->preload(d->imageInfo(next).filePath()); } slotUpdateItemInfo(); setViewToURL(d->currentItemInfo.fileUrl()); } void ImageWindow::saveVersionIsComplete() { qCDebug(DIGIKAM_GENERAL_LOG) << "save version done"; saveAsIsComplete(); } void ImageWindow::saveAsIsComplete() { qCDebug(DIGIKAM_GENERAL_LOG) << "Saved" << m_savingContext.srcURL << "to" << m_savingContext.destinationURL; // Nothing to be done if operating without database if (d->currentItemInfo.isNull()) { return; } if (CollectionManager::instance()->albumRootPath(m_savingContext.srcURL).isNull() || CollectionManager::instance()->albumRootPath(m_savingContext.destinationURL).isNull()) { // not in-collection operation - nothing to do return; } // copy the metadata of the original file to the new file qCDebug(DIGIKAM_GENERAL_LOG) << "was versioned" << (m_savingContext.executedOperation == SavingContext::SavingStateVersion) << "current" << d->currentItemInfo.id() << d->currentItemInfo.name() << "destinations" << m_savingContext.versionFileOperation.allFilePaths(); ItemInfo sourceInfo = d->currentItemInfo; // Set new current index. Employ synchronous scanning for this main file. d->currentItemInfo = ScanController::instance()->scannedInfo(m_savingContext.destinationURL.toLocalFile()); if (m_savingContext.destinationExisted) { // reset the orientation flag in the database DMetadata meta(m_canvas->currentImage().getMetadata()); d->currentItemInfo.setOrientation(meta.getItemOrientation()); } QStringList derivedFilePaths; if (m_savingContext.executedOperation == SavingContext::SavingStateVersion) { derivedFilePaths = m_savingContext.versionFileOperation.allFilePaths(); } else { derivedFilePaths << m_savingContext.destinationURL.toLocalFile(); } // Will ensure files are scanned, and then copy attributes in a thread FileActionMngr::instance()->copyAttributes(sourceInfo, derivedFilePaths); // The model updates asynchronously, so we need to force addition of the main entry d->ensureModelContains(d->currentItemInfo); // Save new face tags to the image saveFaceTagsToImage(d->currentItemInfo); // set origin of EditorCore: "As if" the last saved image was loaded directly resetOriginSwitchFile(); // If the DImg is put in the cache under the new name, this means the new file will not be reloaded. // This may irritate users who want to check for quality loss in lossy formats. // In any case, only do that if the format did not change - too many assumptions otherwise (see bug #138949). if (m_savingContext.originalFormat == m_savingContext.format) { LoadingCacheInterface::putImage(m_savingContext.destinationURL.toLocalFile(), m_canvas->currentImage()); } // all that is done in slotLoadCurrent, except for loading d->thumbBar->setCurrentIndex(d->currentIndex()); QModelIndex next = d->nextIndex(); if (next.isValid()) { m_canvas->preload(d->imageInfo(next).filePath()); } setViewToURL(d->currentItemInfo.fileUrl()); slotUpdateItemInfo(); // Pop-up a message to bring user when save is done. DNotificationWrapper(QLatin1String("editorsavefilecompleted"), i18n("Image saved successfully"), this, windowTitle()); } void ImageWindow::prepareImageToSave() { if (!d->currentItemInfo.isNull()) { MetadataHub hub; hub.load(d->currentItemInfo); // Get face tags d->newFaceTags.clear(); QMultiMap faceTags; faceTags = hub.loadIntegerFaceTags(d->currentItemInfo); if (!faceTags.isEmpty()) { QSize tempS = d->currentItemInfo.dimensions(); QMap::const_iterator it; for (it = faceTags.constBegin() ; it != faceTags.constEnd() ; ++it) { // Start transform each face rect QRect faceRect = it.value().toRect(); int tempH = tempS.height(); int tempW = tempS.width(); qCDebug(DIGIKAM_GENERAL_LOG) << ">>>>>>>>>face rect before:" << faceRect.x() << faceRect.y() << faceRect.width() << faceRect.height(); - for (int i = 0 ; i < m_transformQue.size() ; i++) + for (int i = 0 ; i < m_transformQue.size() ; ++i) { EditorWindow::TransformType type = m_transformQue[i]; switch (type) { case EditorWindow::TransformType::RotateLeft: faceRect = TagRegion::ajustToRotatedImg(faceRect, QSize(tempW, tempH), 1); std::swap(tempH, tempW); break; case EditorWindow::TransformType::RotateRight: faceRect = TagRegion::ajustToRotatedImg(faceRect, QSize(tempW, tempH), 0); std::swap(tempH, tempW); break; case EditorWindow::TransformType::FlipHorizontal: faceRect = TagRegion::ajustToFlippedImg(faceRect, QSize(tempW, tempH), 0); break; case EditorWindow::TransformType::FlipVertical: faceRect = TagRegion::ajustToFlippedImg(faceRect, QSize(tempW, tempH), 1); break; default: break; } qCDebug(DIGIKAM_GENERAL_LOG) << ">>>>>>>>>face rect transform:" << faceRect.x() << faceRect.y() << faceRect.width() << faceRect.height(); } d->newFaceTags.insertMulti(it.key(), QVariant(faceRect)); } } // Ensure there is a UUID for the source image in the database, // even if not in the source image's metadata if (d->currentItemInfo.uuid().isNull()) { QString uuid = m_canvas->interface()->ensureHasCurrentUuid(); d->currentItemInfo.setUuid(uuid); } else { m_canvas->interface()->provideCurrentUuid(d->currentItemInfo.uuid()); } } } void ImageWindow::saveFaceTagsToImage(const ItemInfo& info) { if (!info.isNull() && !d->newFaceTags.isEmpty()) { // Delete all old faces FaceTagsEditor().removeAllFaces(info.id()); QMap::const_iterator it; for (it = d->newFaceTags.constBegin() ; it != d->newFaceTags.constEnd() ; ++it) { int tagId = FaceTags::getOrCreateTagForPerson(it.key()); if (tagId) { TagRegion region(it.value().toRect()); FaceTagsEditor().add(info.id(), tagId, region, false); } else { qCDebug(DIGIKAM_GENERAL_LOG) << "Failed to create a person tag for name" << it.key(); } } MetadataHub hub; hub.load(info); QSize tempS = info.dimensions(); hub.setFaceTags(d->newFaceTags, tempS); hub.write(info.filePath(), MetadataHub::WRITE_ALL); } m_transformQue.clear(); d->newFaceTags.clear(); } VersionManager* ImageWindow::versionManager() const { return &d->versionManager; } bool ImageWindow::save() { prepareImageToSave(); startingSave(d->currentUrl()); return true; } bool ImageWindow::saveAs() { prepareImageToSave(); return startingSaveAs(d->currentUrl()); } bool ImageWindow::saveNewVersion() { prepareImageToSave(); return startingSaveNewVersion(d->currentUrl()); } bool ImageWindow::saveCurrentVersion() { prepareImageToSave(); return startingSaveCurrentVersion(d->currentUrl()); } bool ImageWindow::saveNewVersionAs() { prepareImageToSave(); return startingSaveNewVersionAs(d->currentUrl()); } bool ImageWindow::saveNewVersionInFormat(const QString& format) { prepareImageToSave(); return startingSaveNewVersionInFormat(d->currentUrl(), format); } QUrl ImageWindow::saveDestinationUrl() { return d->currentUrl(); } void ImageWindow::slotDeleteCurrentItem() { deleteCurrentItem(true, false); } void ImageWindow::slotDeleteCurrentItemPermanently() { deleteCurrentItem(true, true); } void ImageWindow::slotDeleteCurrentItemPermanentlyDirectly() { deleteCurrentItem(false, true); } void ImageWindow::slotTrashCurrentItemDirectly() { deleteCurrentItem(false, false); } void ImageWindow::deleteCurrentItem(bool ask, bool permanently) { // This function implements all four of the above slots. // The meaning of permanently differs depending on the value of ask if (d->currentItemInfo.isNull()) { return; } if (!promptUserDelete(d->currentUrl())) { return; } bool useTrash; if (ask) { bool preselectDeletePermanently = permanently; DeleteDialog dialog(this); QList urlList; urlList << d->currentUrl(); if (!dialog.confirmDeleteList(urlList, DeleteDialogMode::Files, preselectDeletePermanently ? DeleteDialogMode::NoChoiceDeletePermanently : DeleteDialogMode::NoChoiceTrash)) { return; } useTrash = !dialog.shouldDelete(); } else { useTrash = !permanently; } DIO::del(d->currentItemInfo, useTrash); // bring all (sidebar) to a defined state without letting them sit on the deleted file emit signalNoCurrentItem(); // We have database information, which means information will get through // everywhere. Just do it asynchronously. removeCurrent(); } void ImageWindow::removeCurrent() { if (!d->currentIsValid()) { return; } if (m_canvas->interface()->undoState().hasChanges) { m_canvas->slotRestore(); } d->imageInfoModel->removeItemInfo(d->currentItemInfo); if (d->imageInfoModel->isEmpty()) { // No image in the current Album -> Quit ImageEditor... QMessageBox::information(this, i18n("No Image in Current Album"), i18n("There is no image to show in the current album.\n" "The image editor will be closed.")); close(); } } void ImageWindow::slotFileMetadataChanged(const QUrl& url) { if (url == d->currentUrl()) { m_canvas->interface()->readMetadataFromFile(url.toLocalFile()); } } /* * Should all be done by ItemViewCategorized * void ImageWindow::slotRowsAboutToBeRemoved(const QModelIndex& parent, int start, int end) { // ignore when closed if (!isVisible() || !d->currentIsValid()) { return; } QModelIndex currentIndex = d->currentIndex(); if (currentIndex.row() >= start && currentIndex.row() <= end) { promptUserSave(d->currentUrl(), AlwaysNewVersion, false); // ensure selection int totalToRemove = end - start + 1; if (d->imageFilterModel->rowCount(parent) > totalToRemove) { if (end == d->imageFilterModel->rowCount(parent) - 1) { loadIndex(d->imageFilterModel->index(start - 1, 0)); // last remaining, no next one left } else { loadIndex(d->imageFilterModel->index(end + 1, 0)); // next remaining } } } }*/ /* void ImageWindow::slotCollectionImageChange(const CollectionImageChangeset& changeset) { bool needLoadCurrent = false; switch (changeset.operation()) { case CollectionImageChangeset::Removed: for (int i=0; iimageInfoList.size(); ++i) { if (changeset.containsImage(d->imageInfoList[i].id())) { if (d->currentItemInfo == d->imageInfoList[i]) { promptUserSave(d->currentUrl(), AlwaysNewVersion, false); if (removeItem(i)) { needLoadCurrent = true; } } else { removeItem(i); } --i; } } break; case CollectionImageChangeset::RemovedAll: for (int i=0; iimageInfoList.size(); ++i) { if (changeset.containsAlbum(d->imageInfoList[i].albumId())) { if (d->currentItemInfo == d->imageInfoList[i]) { promptUserSave(d->currentUrl(), AlwaysNewVersion, false); if (removeItem(i)) { needLoadCurrent = true; } } else { removeItem(i); } --i; } } break; default: break; } if (needLoadCurrent) { QTimer::singleShot(0, this, SLOT(slotLoadCurrent())); } } */ void ImageWindow::dragMoveEvent(QDragMoveEvent* e) { int albumID; QList albumIDs; QList imageIDs; QList urls; if (DItemDrag::decode(e->mimeData(), urls, albumIDs, imageIDs) || DAlbumDrag::decode(e->mimeData(), urls, albumID) || DTagListDrag::canDecode(e->mimeData())) { e->accept(); return; } e->ignore(); } void ImageWindow::dropEvent(QDropEvent* e) { int albumID; QList albumIDs; QList imageIDs; QList urls; if (DItemDrag::decode(e->mimeData(), urls, albumIDs, imageIDs)) { ItemInfoList imageInfoList(imageIDs); if (imageInfoList.isEmpty()) { e->ignore(); return; } QString ATitle; AlbumManager* const man = AlbumManager::instance(); PAlbum* const palbum = man->findPAlbum(albumIDs.first()); if (palbum) { ATitle = palbum->title(); } loadItemInfos(imageInfoList, imageInfoList.first(), i18n("Album \"%1\"", ATitle)); e->accept(); } else if (DAlbumDrag::decode(e->mimeData(), urls, albumID)) { AlbumManager* const man = AlbumManager::instance(); QList itemIDs = CoreDbAccess().db()->getItemIDsInAlbum(albumID); ItemInfoList imageInfoList(itemIDs); if (imageInfoList.isEmpty()) { e->ignore(); return; } QString ATitle; PAlbum* const palbum = man->findPAlbum(albumIDs.first()); if (palbum) { ATitle = palbum->title(); } loadItemInfos(imageInfoList, imageInfoList.first(), i18n("Album \"%1\"", ATitle)); e->accept(); } else if (DTagListDrag::canDecode(e->mimeData())) { QList tagIDs; if (!DTagListDrag::decode(e->mimeData(), tagIDs)) { return; } AlbumManager* const man = AlbumManager::instance(); QList itemIDs = CoreDbAccess().db()->getItemIDsInTag(tagIDs.first(), true); ItemInfoList imageInfoList(itemIDs); if (imageInfoList.isEmpty()) { e->ignore(); return; } QString ATitle; TAlbum* const talbum = man->findTAlbum(tagIDs.first()); if (talbum) { ATitle = talbum->title(); } loadItemInfos(imageInfoList, imageInfoList.first(), i18n("Album \"%1\"", ATitle)); e->accept(); } else { e->ignore(); } } void ImageWindow::slotRevert() { if (!promptUserSave(d->currentUrl(), AskIfNeeded)) { return; } if (m_canvas->interface()->undoState().hasChanges) { m_canvas->slotRestore(); } } void ImageWindow::slotOpenOriginal() { if (!hasOriginalToRestore()) { return; } if (!promptUserSave(d->currentUrl(), AskIfNeeded)) { return; } // this time, with mustBeAvailable = true DImageHistory availableResolved = ItemScanner::resolvedImageHistory(m_canvas->interface()->getItemHistory(), true); QList originals = availableResolved.referredImagesOfType(HistoryImageId::Original); HistoryImageId originalId = m_canvas->interface()->getItemHistory().originalReferredImage(); if (originals.isEmpty()) { //TODO: point to remote collection QMessageBox::warning(this, i18nc("@title", "File Not Available"), i18nc("@info", "The original file (%1) is currently not available", originalId.m_fileName)); return; } QList imageInfos; foreach(const HistoryImageId& id, originals) { QUrl url = QUrl::fromLocalFile(id.m_filePath); url = url.adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + QLatin1Char('/') + (id.m_fileName)); imageInfos << ItemInfo::fromUrl(url); } ItemScanner::sortByProximity(imageInfos, d->currentItemInfo); if (!imageInfos.isEmpty() && !imageInfos.first().isNull()) { openImage(imageInfos.first()); } } bool ImageWindow::hasOriginalToRestore() { // not implemented for db-less situation, so check for ItemInfo return !d->currentItemInfo.isNull() && EditorWindow::hasOriginalToRestore(); } DImageHistory ImageWindow::resolvedImageHistory(const DImageHistory& history) { return ItemScanner::resolvedImageHistory(history); } ThumbBarDock* ImageWindow::thumbBar() const { return d->thumbBarDock; } Sidebar* ImageWindow::rightSideBar() const { return (dynamic_cast(d->rightSideBar)); } void ImageWindow::slotComponentsInfo() { showDigikamComponentsInfo(); } void ImageWindow::slotDBStat() { showDigikamDatabaseStat(); } void ImageWindow::slotAddedDropedItems(QDropEvent* e) { int albumID; QList albumIDs; QList imageIDs; QList urls; ItemInfoList imgList; if (DItemDrag::decode(e->mimeData(), urls, albumIDs, imageIDs)) { imgList = ItemInfoList(imageIDs); } else if (DAlbumDrag::decode(e->mimeData(), urls, albumID)) { QList itemIDs = CoreDbAccess().db()->getItemIDsInAlbum(albumID); imgList = ItemInfoList(itemIDs); } else if (DTagListDrag::canDecode(e->mimeData())) { QList tagIDs; if (!DTagListDrag::decode(e->mimeData(), tagIDs)) { return; } QList itemIDs = CoreDbAccess().db()->getItemIDsInTag(tagIDs.first(), true); imgList = ItemInfoList(itemIDs); } e->accept(); if (!imgList.isEmpty()) { loadItemInfos(imgList, imgList.first(), QString()); } } void ImageWindow::slotFileWithDefaultApplication() { DFileOperations::openFilesWithDefaultApplication(QList() << d->currentUrl()); } void ImageWindow::slotOpenWith(QAction* action) { openWith(d->currentUrl(), action); } void ImageWindow::slotRightSideBarActivateTitles() { d->rightSideBar->setActiveTab(d->rightSideBar->imageDescEditTab()); d->rightSideBar->imageDescEditTab()->setFocusToTitlesEdit(); } void ImageWindow::slotRightSideBarActivateComments() { d->rightSideBar->setActiveTab(d->rightSideBar->imageDescEditTab()); d->rightSideBar->imageDescEditTab()->setFocusToCommentsEdit(); } void ImageWindow::slotRightSideBarActivateAssignedTags() { d->rightSideBar->setActiveTab(d->rightSideBar->imageDescEditTab()); d->rightSideBar->imageDescEditTab()->activateAssignedTagsButton(); } } // namespace Digikam diff --git a/core/utilities/imageeditor/tools/colors/adjustlevelstool.cpp b/core/utilities/imageeditor/tools/colors/adjustlevelstool.cpp index 827a8dd2ba..080e1009c7 100644 --- a/core/utilities/imageeditor/tools/colors/adjustlevelstool.cpp +++ b/core/utilities/imageeditor/tools/colors/adjustlevelstool.cpp @@ -1,937 +1,937 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2004-07-20 * Description : image histogram adjust levels. * * Copyright (C) 2004-2018 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 "adjustlevelstool.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 "dnuminput.h" #include "dgradientslider.h" #include "dfiledialog.h" #include "dimg.h" #include "editortoolsettings.h" #include "histogrambox.h" #include "histogramwidget.h" #include "imagehistogram.h" #include "imageiface.h" #include "levelsfilter.h" #include "imagelevels.h" #include "imageregionwidget.h" namespace Digikam { class Q_DECL_HIDDEN AdjustLevelsTool::Private { public: enum ColorPicker { NoPicker = -1, BlackTonal = 0, GrayTonal, WhiteTonal }; public: explicit Private() : histoSegments(0), pickerBox(0), resetButton(0), autoButton(0), pickBlack(0), pickGray(0), pickWhite(0), pickerType(0), minInput(0), maxInput(0), minOutput(0), maxOutput(0), gammaInput(0), levelsHistogramWidget(0), inputLevels(0), outputLevels(0), previewWidget(0), levels(0), originalImage(0), gboxSettings(0) {} static const QString configGroupName; static const QString configGammaChannelEntry; static const QString configLowInputChannelEntry; static const QString configLowOutputChannelEntry; static const QString configHighInputChannelEntry; static const QString configHighOutputChannelEntry; static const QString configHistogramChannelEntry; static const QString configHistogramScaleEntry; int histoSegments; QWidget* pickerBox; QPushButton* resetButton; QToolButton* autoButton; QToolButton* pickBlack; QToolButton* pickGray; QToolButton* pickWhite; QButtonGroup* pickerType; DIntNumInput* minInput; DIntNumInput* maxInput; DIntNumInput* minOutput; DIntNumInput* maxOutput; DDoubleNumInput* gammaInput; HistogramWidget* levelsHistogramWidget; DGradientSlider* inputLevels; DGradientSlider* outputLevels; ImageRegionWidget* previewWidget; ImageLevels* levels; DImg* originalImage; EditorToolSettings* gboxSettings; }; const QString AdjustLevelsTool::Private::configGroupName(QLatin1String("adjustlevels Tool")); const QString AdjustLevelsTool::Private::configGammaChannelEntry(QLatin1String("GammaChannel%1")); const QString AdjustLevelsTool::Private::configLowInputChannelEntry(QLatin1String("LowInputChannel%1")); const QString AdjustLevelsTool::Private::configLowOutputChannelEntry(QLatin1String("LowOutputChannel%1")); const QString AdjustLevelsTool::Private::configHighInputChannelEntry(QLatin1String("HighInputChannel%1")); const QString AdjustLevelsTool::Private::configHighOutputChannelEntry(QLatin1String("HighOutputChannel%1")); const QString AdjustLevelsTool::Private::configHistogramChannelEntry(QLatin1String("Histogram Channel")); const QString AdjustLevelsTool::Private::configHistogramScaleEntry(QLatin1String("Histogram Scale")); // -------------------------------------------------------- AdjustLevelsTool::AdjustLevelsTool(QObject* const parent) : EditorToolThreaded(parent), d(new Private) { setObjectName(QLatin1String("adjustlevels")); setToolName(i18n("Adjust Levels")); setToolIcon(QIcon::fromTheme(QLatin1String("adjustlevels"))); setInitPreview(true); ImageIface iface; d->originalImage = iface.original(); d->histoSegments = d->originalImage->sixteenBit() ? 65535 : 255; d->levels = new ImageLevels(d->originalImage->sixteenBit()); // ------------------------------------------------------------- d->previewWidget = new ImageRegionWidget; setToolView(d->previewWidget); setPreviewModeMask(PreviewToolBar::AllPreviewModes); // ------------------------------------------------------------- d->gboxSettings = new EditorToolSettings(0); d->gboxSettings->setButtons(EditorToolSettings::Default| EditorToolSettings::Load| EditorToolSettings::SaveAs| EditorToolSettings::Ok| EditorToolSettings::Cancel); d->gboxSettings->setTools(EditorToolSettings::Histogram); d->gboxSettings->setHistogramType(Digikam::LRGBAC); // we don't need to use the Gradient widget in this tool d->gboxSettings->histogramBox()->setGradientVisible(false); // ------------------------------------------------------------- d->levelsHistogramWidget = new HistogramWidget(256, 140, d->gboxSettings->plainPage(), false); d->levelsHistogramWidget->updateData(*d->originalImage); d->levelsHistogramWidget->setWhatsThis(i18n("This is the histogram drawing of the selected channel " "from the original image.")); QHBoxLayout* const inputLevelsLayout = new QHBoxLayout; inputLevelsLayout->addWidget(d->levelsHistogramWidget); // ------------------------------------------------------------- d->inputLevels = new DGradientSlider(); d->inputLevels->setWhatsThis( i18n("Select the input intensity of the histogram here.")); d->inputLevels->setToolTip( i18n( "Input intensity." ) ); d->inputLevels->installEventFilter(this); d->gboxSettings->histogramBox()->setHistogramMargin(d->inputLevels->gradientOffset()); inputLevelsLayout->setContentsMargins(d->inputLevels->gradientOffset(), 0, d->inputLevels->gradientOffset(), 0); d->outputLevels = new DGradientSlider(); d->outputLevels->setWhatsThis( i18n("Select the output intensity of the histogram here.")); d->outputLevels->setToolTip( i18n( "Output intensity." ) ); d->outputLevels->installEventFilter(this); d->minInput = new DIntNumInput(); d->minInput->setRange(0, d->histoSegments, 1); d->minInput->setDefaultValue(0); d->minInput->setWhatsThis( i18n("Select the minimal input intensity value of the histogram here.")); d->minInput->setToolTip( i18n( "Minimal input intensity." ) ); d->gammaInput = new DDoubleNumInput(); d->gammaInput->setDecimals(2); d->gammaInput->setRange(0.1, 3.0, 0.01); d->gammaInput->setDefaultValue(1.0); d->gammaInput->setToolTip( i18n( "Gamma input value." ) ); d->gammaInput->setWhatsThis( i18n("Select the gamma input value here.")); d->maxInput = new DIntNumInput(); d->maxInput->setRange(0, d->histoSegments, 1); d->maxInput->setDefaultValue(d->histoSegments); d->maxInput->setToolTip( i18n( "Maximal input intensity." ) ); d->maxInput->setWhatsThis( i18n("Select the maximal input intensity value of the histogram here.")); d->minOutput = new DIntNumInput(); d->minOutput->setRange(0, d->histoSegments, 1); d->minOutput->setDefaultValue(0); d->minOutput->setToolTip( i18n( "Minimal output intensity." ) ); d->minOutput->setWhatsThis( i18n("Select the minimal output intensity value of the histogram here.")); d->maxOutput = new DIntNumInput(); d->maxOutput->setRange(0, d->histoSegments, 1); d->maxOutput->setDefaultValue(d->histoSegments); d->maxOutput->setToolTip( i18n( "Maximal output intensity." ) ); d->maxOutput->setWhatsThis( i18n("Select the maximal output intensity value of the histogram here.")); // ------------------------------------------------------------- d->pickerBox = new QWidget(); d->pickBlack = new QToolButton(); d->pickBlack->setIcon(QIcon::fromTheme(QLatin1String("color-picker-black"))); d->pickBlack->setCheckable(true); d->pickBlack->setToolTip( i18n( "All channels shadow tone color picker" ) ); d->pickBlack->setWhatsThis(i18n("With this button, you can pick the color from the original " "image used to set Shadow Tone " "input levels on the Red, Green, Blue, and Luminosity channels.")); d->pickGray = new QToolButton(); d->pickGray->setIcon(QIcon::fromTheme(QLatin1String("color-picker-grey"))); d->pickGray->setCheckable(true); d->pickGray->setToolTip( i18n( "All channels middle tone color picker" ) ); d->pickGray->setWhatsThis(i18n("With this button, you can pick the color from the original " "image used to set Middle Tone " "input levels on the Red, Green, Blue, and Luminosity channels.")); d->pickWhite = new QToolButton(); d->pickWhite->setIcon(QIcon::fromTheme(QLatin1String("color-picker-white"))); d->pickWhite->setCheckable(true); d->pickWhite->setToolTip( i18n( "All channels highlight tone color picker" ) ); d->pickWhite->setWhatsThis(i18n("With this button, you can pick the color from the original " "image used to set Highlight Tone " "input levels on the Red, Green, Blue, and Luminosity channels.")); d->pickerType = new QButtonGroup(d->pickerBox); d->pickerType->addButton(d->pickBlack, Private::BlackTonal); d->pickerType->addButton(d->pickGray, Private::GrayTonal); d->pickerType->addButton(d->pickWhite, Private::WhiteTonal); QHBoxLayout* pickerBoxLayout = new QHBoxLayout; pickerBoxLayout->setContentsMargins(QMargins()); pickerBoxLayout->setSpacing(0); pickerBoxLayout->addWidget(d->pickBlack); pickerBoxLayout->addWidget(d->pickGray); pickerBoxLayout->addWidget(d->pickWhite); d->pickerBox->setLayout(pickerBoxLayout); d->pickerType->setExclusive(true); // ------------------------------------------------------------- d->autoButton = new QToolButton(); d->autoButton->setIcon(QIcon::fromTheme(QLatin1String("system-run"))); d->autoButton->setToolTip( i18n( "Adjust all levels automatically." ) ); d->autoButton->setWhatsThis(i18n("If you press this button, all channel levels will be adjusted " "automatically.")); d->resetButton = new QPushButton(i18n("&Reset")); d->resetButton->setIcon(QIcon::fromTheme(QLatin1String("document-revert"))); d->resetButton->setToolTip( i18n( "Reset current channel levels' values." ) ); d->resetButton->setWhatsThis(i18n("If you press this button, all levels' values " "from the currently selected channel " "will be reset to the default values.")); QLabel* const space = new QLabel(); space->setFixedWidth(d->gboxSettings->spacingHint()); QHBoxLayout* const l3 = new QHBoxLayout(); l3->addWidget(d->pickerBox); l3->addWidget(d->autoButton); l3->addWidget(space); l3->addWidget(d->resetButton); l3->addStretch(10); // ------------------------------------------------------------- QGridLayout* const grid = new QGridLayout(); grid->addLayout(inputLevelsLayout, 0, 0, 1, 7); grid->addWidget(d->inputLevels, 1, 0, 1, 7); grid->addWidget(d->minInput, 2, 1, 1, 1); grid->addWidget(d->maxInput, 2, 5, 1, 1); grid->addWidget(d->gammaInput, 3, 0, 1, 7); grid->addWidget(d->outputLevels, 4, 0, 1, 7); grid->addWidget(d->minOutput, 5, 1, 1, 1); grid->addWidget(d->maxOutput, 5, 5, 1, 1); grid->addLayout(l3, 6, 0, 1, 7); grid->setRowStretch(7, 10); grid->setColumnStretch(2, 10); grid->setColumnStretch(4, 10); grid->setContentsMargins(QMargins()); grid->setSpacing(d->gboxSettings->spacingHint()); d->gboxSettings->plainPage()->setLayout(grid); // ------------------------------------------------------------- setToolSettings(d->gboxSettings); // ------------------------------------------------------------- // Channels and scale selection slots. connect(d->previewWidget, SIGNAL(signalCapturedPointFromOriginal(Digikam::DColor,QPoint)), this, SLOT(slotSpotColorChanged(Digikam::DColor))); /* connect(d->previewWidget, SIGNAL(spotPositionChangedFromTarget(Digikam::DColor,QPoint)), this, SLOT(slotColorSelectedFromTarget(Digikam::DColor))); */ // ------------------------------------------------------------- // Color sliders and spinbox slots. connect(d->inputLevels, SIGNAL(leftValueChanged(double)), this, SLOT(slotAdjustMinInputSpinBox(double))); connect(d->inputLevels, SIGNAL(rightValueChanged(double)), this, SLOT(slotAdjustMaxInputSpinBox(double))); connect(d->outputLevels, SIGNAL(leftValueChanged(double)), this, SLOT(slotAdjustMinOutputSpinBox(double))); connect(d->outputLevels, SIGNAL(rightValueChanged(double)), this, SLOT(slotAdjustMaxOutputSpinBox(double))); connect(d->minInput, SIGNAL(valueChanged(int)), this, SLOT(slotAdjustSliders())); connect(d->maxInput, SIGNAL(valueChanged(int)), this, SLOT(slotAdjustSliders())); connect(d->minOutput, SIGNAL(valueChanged(int)), this, SLOT(slotAdjustSliders())); connect(d->maxOutput, SIGNAL(valueChanged(int)), this, SLOT(slotAdjustSliders())); connect(d->gammaInput, SIGNAL(valueChanged(double)), this, SLOT(slotGammaInputchanged(double))); // ------------------------------------------------------------- // Buttons slots. connect(d->autoButton, SIGNAL(clicked()), this, SLOT(slotAutoLevels())); connect(d->resetButton, SIGNAL(clicked()), this, SLOT(slotResetCurrentChannel())); connect(d->pickerType, SIGNAL(buttonReleased(int)), this, SLOT(slotPickerColorButtonActived(int))); } AdjustLevelsTool::~AdjustLevelsTool() { delete d->levels; delete d; } // See bug #146636: use event filter with all level slider to display a // guide over level histogram. bool AdjustLevelsTool::eventFilter(QObject* obj, QEvent* ev) { if ( obj == d->inputLevels ) { if ( ev->type() == QEvent::MouseButtonPress) { connect(d->inputLevels, SIGNAL(leftValueChanged(double)), this, SLOT(slotShowInputHistogramGuide(double))); connect(d->inputLevels, SIGNAL(rightValueChanged(double)), this, SLOT(slotShowInputHistogramGuide(double))); return false; } if ( ev->type() == QEvent::MouseButtonRelease) { disconnect(d->inputLevels, SIGNAL(leftValueChanged(double)), this, SLOT(slotShowInputHistogramGuide(double))); disconnect(d->inputLevels, SIGNAL(rightValueChanged(double)), this, SLOT(slotShowInputHistogramGuide(double))); d->levelsHistogramWidget->reset(); return false; } else { return false; } } if ( obj == d->outputLevels ) { if ( ev->type() == QEvent::MouseButtonPress) { connect(d->outputLevels, SIGNAL(leftValueChanged(double)), this, SLOT(slotShowOutputHistogramGuide(double))); connect(d->outputLevels, SIGNAL(rightValueChanged(double)), this, SLOT(slotShowOutputHistogramGuide(double))); return false; } if ( ev->type() == QEvent::MouseButtonRelease) { disconnect(d->outputLevels, SIGNAL(leftValueChanged(double)), this, SLOT(slotShowOutputHistogramGuide(double))); disconnect(d->outputLevels, SIGNAL(rightValueChanged(double)), this, SLOT(slotShowOutputHistogramGuide(double))); d->gboxSettings->histogramBox()->histogram()->reset(); return false; } else { return false; } } else { // pass the event on to the parent class return EditorToolThreaded::eventFilter(obj, ev); } } void AdjustLevelsTool::slotShowInputHistogramGuide(double v) { int val = (int)(v * d->histoSegments); DColor color(val, val, val, val, d->originalImage->sixteenBit()); d->levelsHistogramWidget->setHistogramGuideByColor(color); } void AdjustLevelsTool::slotShowOutputHistogramGuide(double v) { int val = (int)(v * d->histoSegments); DColor color(val, val, val, val, d->originalImage->sixteenBit()); d->gboxSettings->histogramBox()->histogram()->setHistogramGuideByColor(color); } void AdjustLevelsTool::slotPickerColorButtonActived(int type) { if (type == Private::NoPicker) { return; } d->previewWidget->setCapturePointMode(true); } void AdjustLevelsTool::slotSpotColorChanged(const DColor& color) { ChannelType channel = d->gboxSettings->histogramBox()->channel(); if ( d->pickBlack->isChecked() ) { if (channel != ColorChannels) { // Black tonal levels point. d->levels->levelsBlackToneAdjustByColors(channel, color); } else { - for (int i = RedChannel; i <= BlueChannel; i++) + for (int i = RedChannel ; i <= BlueChannel ; ++i) d->levels->levelsBlackToneAdjustByColors(i, color); } } else if ( d->pickGray->isChecked() ) { if (channel != ColorChannels) { // Gray tonal levels point. d->levels->levelsGrayToneAdjustByColors(channel, color); } } else if ( d->pickWhite->isChecked() ) { if (channel != ColorChannels) { // White tonal levels point. d->levels->levelsWhiteToneAdjustByColors(channel, color); } else { - for (int i = RedChannel; i <= BlueChannel; i++) + for (int i = RedChannel ; i <= BlueChannel ; ++i) d->levels->levelsWhiteToneAdjustByColors(i, color); } } else { d->levelsHistogramWidget->setHistogramGuideByColor(color); return; } d->pickerType->setExclusive(false); d->pickBlack->setChecked(false); d->pickGray->setChecked(false); d->pickWhite->setChecked(false); d->pickerType->setExclusive(true); // Refresh the current levels config. slotChannelChanged(); d->previewWidget->setCapturePointMode(false); slotPreview(); } void AdjustLevelsTool::slotColorSelectedFromTarget(const DColor& color) { d->gboxSettings->histogramBox()->histogram()->setHistogramGuideByColor(color); } void AdjustLevelsTool::slotGammaInputchanged(double val) { ChannelType channel = d->gboxSettings->histogramBox()->channel(); if (channel == ColorChannels) channel = LuminosityChannel; blockSignals(true); d->levels->setLevelGammaValue(channel, val); blockSignals(false); slotTimer(); } void AdjustLevelsTool::slotAdjustMinInputSpinBox(double val) { d->minInput->blockSignals(true); int newVal = (int)(val*d->histoSegments); d->minInput->setValue(newVal); d->minInput->blockSignals(false); slotAdjustSliders(); } void AdjustLevelsTool::slotAdjustMaxInputSpinBox(double val) { d->maxInput->blockSignals(true); int newVal = (int)(val*d->histoSegments); d->maxInput->setValue(newVal); d->maxInput->blockSignals(false); slotAdjustSliders(); } void AdjustLevelsTool::slotAdjustMinOutputSpinBox(double val) { d->minOutput->blockSignals(true); int newVal = (int)(val*d->histoSegments); d->minOutput->setValue(newVal); d->minOutput->blockSignals(false); slotAdjustSliders(); } void AdjustLevelsTool::slotAdjustMaxOutputSpinBox(double val) { d->maxOutput->blockSignals(true); int newVal = (int)(val*d->histoSegments); d->maxOutput->setValue(newVal); d->maxOutput->blockSignals(false); slotAdjustSliders(); } void AdjustLevelsTool::slotAdjustSliders() { adjustSliders(d->minInput->value(), d->gammaInput->value(), d->maxInput->value(), d->minOutput->value(), d->maxOutput->value()); slotTimer(); } void AdjustLevelsTool::adjustSlidersAndSpinboxes(int minIn, double gamIn, int maxIn, int minOut, int maxOut) { d->minInput->blockSignals(true); d->maxInput->blockSignals(true); d->minOutput->blockSignals(true); d->maxOutput->blockSignals(true); d->minInput->setValue(minIn); d->maxInput->setValue(maxIn); d->minOutput->setValue(minOut); d->maxOutput->setValue(maxOut); d->minInput->blockSignals(false); d->maxInput->blockSignals(false); d->minOutput->blockSignals(false); d->maxOutput->blockSignals(false); adjustSliders(minIn, gamIn, maxIn, minOut, maxOut); } void AdjustLevelsTool::adjustSliders(int minIn, double gamIn, int maxIn, int minOut, int maxOut) { ChannelType channel = d->gboxSettings->histogramBox()->channel(); if (channel == ColorChannels) channel = LuminosityChannel; d->inputLevels->blockSignals(true); d->gammaInput->blockSignals(true); d->outputLevels->blockSignals(true); d->inputLevels->setLeftValue((double)minIn/(double)d->histoSegments); d->inputLevels->setRightValue((double)maxIn/(double)d->histoSegments); d->gammaInput->setValue(gamIn); d->outputLevels->setLeftValue((double)minOut/(double)d->histoSegments); d->outputLevels->setRightValue((double)maxOut/(double)d->histoSegments); d->levels->setLevelLowInputValue(channel, minIn); d->levels->setLevelHighInputValue(channel, maxIn); d->levels->setLevelLowOutputValue(channel, minOut); d->levels->setLevelHighOutputValue(channel, maxOut); d->inputLevels->blockSignals(false); d->gammaInput->blockSignals(false); d->outputLevels->blockSignals(false); } void AdjustLevelsTool::slotResetCurrentChannel() { ChannelType channel = d->gboxSettings->histogramBox()->channel(); if (channel == ColorChannels) channel = LuminosityChannel; d->levels->levelsChannelReset(channel); // Refresh the current levels config. slotChannelChanged(); d->levelsHistogramWidget->reset(); slotPreview(); } void AdjustLevelsTool::slotAutoLevels() { // Calculate Auto levels. d->levels->levelsAuto(d->levelsHistogramWidget->currentHistogram()); // Refresh the current levels config. slotChannelChanged(); slotPreview(); } void AdjustLevelsTool::slotChannelChanged() { ChannelType channel = d->gboxSettings->histogramBox()->channel(); d->levelsHistogramWidget->setChannelType(channel); if (channel == ColorChannels) channel = LuminosityChannel; switch (channel) { case RedChannel: d->inputLevels->setColors(QColor("black"), QColor("red")); d->outputLevels->setColors(QColor("black"), QColor("red")); break; case GreenChannel: d->inputLevels->setColors(QColor("black"), QColor("green")); d->outputLevels->setColors(QColor("black"), QColor("green")); break; case BlueChannel: d->inputLevels->setColors(QColor("black"), QColor("blue")); d->outputLevels->setColors(QColor("black"), QColor("blue")); break; default: d->inputLevels->setColors(QColor("black"), QColor("white")); d->outputLevels->setColors(QColor("black"), QColor("white")); break; } adjustSlidersAndSpinboxes(d->levels->getLevelLowInputValue(channel), d->levels->getLevelGammaValue(channel), d->levels->getLevelHighInputValue(channel), d->levels->getLevelLowOutputValue(channel), d->levels->getLevelHighOutputValue(channel)); } void AdjustLevelsTool::slotScaleChanged() { d->levelsHistogramWidget->setScaleType(d->gboxSettings->histogramBox()->scale()); } void AdjustLevelsTool::readSettings() { KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(d->configGroupName); { bool sb = d->originalImage->sixteenBit(); double gamma = 0.0; int lowInput = 0; int lowOutput = 0; int highInput = 0; int highOutput = 0; for (int i = 0 ; i < 5 ; ++i) { gamma = group.readEntry(d->configGammaChannelEntry.arg(i), 1.0); lowInput = group.readEntry(d->configLowInputChannelEntry.arg(i), 0); lowOutput = group.readEntry(d->configLowOutputChannelEntry.arg(i), 0); highInput = group.readEntry(d->configHighInputChannelEntry.arg(i), 65535); highOutput = group.readEntry(d->configHighOutputChannelEntry.arg(i), 65535); d->levels->setLevelGammaValue(i, gamma); d->levels->setLevelLowInputValue(i, sb ? lowInput : lowInput / 256); d->levels->setLevelHighInputValue(i, sb ? highInput : highInput / 256); d->levels->setLevelLowOutputValue(i, sb ? lowOutput : lowOutput / 256); d->levels->setLevelHighOutputValue(i, sb ? highOutput : highOutput / 256); } } d->levelsHistogramWidget->reset(); d->gboxSettings->histogramBox()->histogram()->reset(); ChannelType ch = (ChannelType)group.readEntry(d->configHistogramChannelEntry, (int)LuminosityChannel); // restore the previous channel d->gboxSettings->histogramBox()->setChannel(ch); d->gboxSettings->histogramBox()->setScale((HistogramScale)group.readEntry(d->configHistogramScaleEntry, (int)LogScaleHistogram)); // if ColorChannels was set, make sure to take values from LuminosityChannel if (ch == ColorChannels) ch = LuminosityChannel; // This is mandatory here to set spinbox values because slot connections // can be not set completely at tool startup. d->minInput->setValue(d->levels->getLevelLowInputValue(ch)); d->minOutput->setValue(d->levels->getLevelLowOutputValue(ch)); d->maxInput->setValue(d->levels->getLevelHighInputValue(ch)); d->maxOutput->setValue(d->levels->getLevelHighOutputValue(ch)); slotChannelChanged(); slotScaleChanged(); } void AdjustLevelsTool::writeSettings() { KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(d->configGroupName); group.writeEntry(d->configHistogramChannelEntry, (int)d->gboxSettings->histogramBox()->channel()); group.writeEntry(d->configHistogramScaleEntry, (int)d->gboxSettings->histogramBox()->scale()); { bool sb = d->originalImage->sixteenBit(); double gamma = 0.0; int lowInput = 0; int lowOutput = 0; int highInput = 0; int highOutput = 0; for (int i = 0 ; i < 5 ; ++i) { gamma = d->levels->getLevelGammaValue(i); lowInput = d->levels->getLevelLowInputValue(i); lowOutput = d->levels->getLevelLowOutputValue(i); highInput = d->levels->getLevelHighInputValue(i); highOutput = d->levels->getLevelHighOutputValue(i); group.writeEntry(d->configGammaChannelEntry.arg(i), gamma); group.writeEntry(d->configLowInputChannelEntry.arg(i), sb ? lowInput : lowInput * 256); group.writeEntry(d->configLowOutputChannelEntry.arg(i), sb ? lowOutput : lowOutput * 256); group.writeEntry(d->configHighInputChannelEntry.arg(i), sb ? highInput : highInput * 256); group.writeEntry(d->configHighOutputChannelEntry.arg(i), sb ? highOutput : highOutput * 256); } } config->sync(); } void AdjustLevelsTool::slotResetSettings() { for (int channel = 0 ; channel < 5 ; ++channel) { d->levels->levelsChannelReset(channel); } // Refresh the current levels config. slotChannelChanged(); d->levelsHistogramWidget->reset(); slotPreview(); } void AdjustLevelsTool::preparePreview() { LevelsContainer settings; for (int i=0 ; i<5 ; ++i) { settings.lInput[i] = d->levels->getLevelLowInputValue(i); settings.hInput[i] = d->levels->getLevelHighInputValue(i); settings.lOutput[i] = d->levels->getLevelLowOutputValue(i); settings.hOutput[i] = d->levels->getLevelHighOutputValue(i); settings.gamma[i] = d->levels->getLevelGammaValue(i); } d->gboxSettings->histogramBox()->histogram()->stopHistogramComputation(); DImg preview = d->previewWidget->getOriginalRegionImage(true); setFilter(new LevelsFilter(&preview, this, settings)); } void AdjustLevelsTool::setPreviewImage() { DImg preview = filter()->getTargetImage(); d->previewWidget->setPreviewImage(preview); // Update histogram. d->gboxSettings->histogramBox()->histogram()->updateData(preview.copy(), DImg(), false); } void AdjustLevelsTool::prepareFinal() { LevelsContainer settings; for (int i=0 ; i<5 ; ++i) { settings.lInput[i] = d->levels->getLevelLowInputValue(i); settings.hInput[i] = d->levels->getLevelHighInputValue(i); settings.lOutput[i] = d->levels->getLevelLowOutputValue(i); settings.hOutput[i] = d->levels->getLevelHighOutputValue(i); settings.gamma[i] = d->levels->getLevelGammaValue(i); } ImageIface iface; setFilter(new LevelsFilter(iface.original(), this, settings)); } void AdjustLevelsTool::setFinalImage() { ImageIface iface; iface.setOriginal(i18n("Adjust Levels"), filter()->filterAction(), filter()->getTargetImage()); } void AdjustLevelsTool::slotLoadSettings() { QUrl loadLevelsFile; loadLevelsFile = DFileDialog::getOpenFileUrl(qApp->activeWindow(), i18n("Select Gimp Levels File to Load"), QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)), QLatin1String("*")); if ( loadLevelsFile.isEmpty() ) { return; } if ( d->levels->loadLevelsFromGimpLevelsFile( loadLevelsFile ) == false ) { QMessageBox::critical(qApp->activeWindow(), qApp->applicationName(), i18n("Cannot load from the Gimp levels text file.")); return; } // Refresh the current levels config. slotChannelChanged(); slotPreview(); } void AdjustLevelsTool::slotSaveAsSettings() { QUrl saveLevelsFile; saveLevelsFile = DFileDialog::getSaveFileUrl(qApp->activeWindow(), i18n("Gimp Levels File to Save"), QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)), QLatin1String("*")); if ( saveLevelsFile.isEmpty() ) { return; } if ( d->levels->saveLevelsToGimpLevelsFile( saveLevelsFile ) == false ) { QMessageBox::critical(qApp->activeWindow(), qApp->applicationName(), i18n("Cannot save to the Gimp levels text file.")); return; } // Refresh the current levels config. slotChannelChanged(); } } // namespace Digikam diff --git a/core/utilities/imageeditor/tools/colors/filmtool.cpp b/core/utilities/imageeditor/tools/colors/filmtool.cpp index b3f70ea410..57647846a8 100644 --- a/core/utilities/imageeditor/tools/colors/filmtool.cpp +++ b/core/utilities/imageeditor/tools/colors/filmtool.cpp @@ -1,689 +1,689 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2012-02-05 * Description : film color negative inverter tool * * Copyright (C) 2012 by Matthias Welwarsky * * 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 "filmtool.h" // C++ includes #include // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include #include // Local includes #include "dnuminput.h" #include "dgradientslider.h" #include "dimg.h" #include "editortoolsettings.h" #include "histogrambox.h" #include "histogramwidget.h" #include "imagehistogram.h" #include "imageiface.h" #include "imagelevels.h" #include "imageregionwidget.h" #include "filmfilter_p.h" namespace Digikam { class Q_DECL_HIDDEN FilmTool::Private { public: enum ColorPicker { NoPicker = 0, OrangeMask = 1 }; public: explicit Private() : histoSegments(0), resetButton(0), pickWhitePoint(0), autoButton(0), exposureInput(0), gammaInput(0), cnType(0), colorBalanceInput(0), levelsHistogramWidget(0), redInputLevels(0), greenInputLevels(0), blueInputLevels(0), previewWidget(0), levels(0), originalImage(0), gboxSettings(0) { } static const QString configGroupName; static const QString configGammaInputEntry; static const QString configExposureEntry; static const QString configFilmProfileEntry; static const QString configFilmProfileName; static const QString configWhitePointEntry; static const QString configHistogramChannelEntry; static const QString configHistogramScaleEntry; static const QString configApplyColorBalance; int histoSegments; QPushButton* resetButton; QToolButton* pickWhitePoint; QToolButton* autoButton; FilmContainer filmContainer; DDoubleNumInput* exposureInput; DDoubleNumInput* gammaInput; QListWidget* cnType; QCheckBox* colorBalanceInput; HistogramWidget* levelsHistogramWidget; DGradientSlider* redInputLevels; DGradientSlider* greenInputLevels; DGradientSlider* blueInputLevels; ImageRegionWidget* previewWidget; ImageLevels* levels; DImg* originalImage; EditorToolSettings* gboxSettings; }; const QString FilmTool::Private::configGroupName(QLatin1String("film Tool")); const QString FilmTool::Private::configGammaInputEntry(QLatin1String("GammaInput")); const QString FilmTool::Private::configExposureEntry(QLatin1String("Exposure")); const QString FilmTool::Private::configFilmProfileEntry(QLatin1String("FilmProfile")); const QString FilmTool::Private::configFilmProfileName(QLatin1String("FilmProfileName")); const QString FilmTool::Private::configWhitePointEntry(QLatin1String("WhitePoint_%1")); const QString FilmTool::Private::configHistogramChannelEntry(QLatin1String("Histogram Channel")); const QString FilmTool::Private::configHistogramScaleEntry(QLatin1String("Histogram Scale")); const QString FilmTool::Private::configApplyColorBalance(QLatin1String("Apply Color Balance")); // -------------------------------------------------------- FilmTool::FilmTool(QObject* const parent) : EditorToolThreaded(parent), d(new Private) { setObjectName(QLatin1String("film")); setToolName(i18n("Color Negative Film")); setToolIcon(QIcon::fromTheme(QLatin1String("colorneg"))); setInitPreview(true); ImageIface iface; d->originalImage = iface.original(); d->histoSegments = d->originalImage->sixteenBit() ? 65535 : 255; d->levels = new ImageLevels(d->originalImage->sixteenBit()); // ------------------------------------------------------------- d->previewWidget = new ImageRegionWidget; setToolView(d->previewWidget); setPreviewModeMask(PreviewToolBar::AllPreviewModes); // ------------------------------------------------------------- d->gboxSettings = new EditorToolSettings(0); d->gboxSettings->setButtons(EditorToolSettings::Default| EditorToolSettings::Ok| EditorToolSettings::Cancel); d->gboxSettings->setTools(EditorToolSettings::Histogram); d->gboxSettings->setHistogramType(LRGBC); // we don't need to use the Gradient in this tool d->gboxSettings->histogramBox()->setGradientVisible(false); d->gboxSettings->histogramBox()->setChannel(ColorChannels); // ------------------------------------------------------------- d->levelsHistogramWidget = new HistogramWidget(256, 140, d->gboxSettings->plainPage(), false); d->levelsHistogramWidget->updateData(*d->originalImage); d->levelsHistogramWidget->setWhatsThis(i18n("This is the histogram drawing of the selected channel " "from the original image.")); d->levelsHistogramWidget->setChannelType(ColorChannels); QHBoxLayout* const inputLevelsLayout = new QHBoxLayout; inputLevelsLayout->addWidget(d->levelsHistogramWidget); // ------------------------------------------------------------- d->redInputLevels = new DGradientSlider(); d->redInputLevels->setColors(QColor("Red"), QColor("White")); d->redInputLevels->setToolTip( i18n( "Input range of red color channel." ) ); d->redInputLevels->installEventFilter(this); d->greenInputLevels = new DGradientSlider(); d->greenInputLevels->setColors(QColor("Green"), QColor("White")); d->greenInputLevels->setToolTip( i18n( "Input range of green color channel." ) ); d->greenInputLevels->installEventFilter(this); d->blueInputLevels = new DGradientSlider(); d->blueInputLevels->setColors(QColor("Blue"), QColor("White")); d->blueInputLevels->setToolTip( i18n( "Input range of blue color channel." ) ); d->blueInputLevels->installEventFilter(this); d->gboxSettings->histogramBox()->setHistogramMargin(d->redInputLevels->gradientOffset()); inputLevelsLayout->setContentsMargins(d->redInputLevels->gradientOffset(), 0, d->redInputLevels->gradientOffset(), 0); // ------------------------------------------------------------- d->cnType = new QListWidget(); QList profiles = d->filmContainer.profileItemList(d->cnType); QList::ConstIterator it; for (it = profiles.constBegin(); it != profiles.constEnd(); ++it) d->cnType->addItem(*it); // ------------------------------------------------------------- d->colorBalanceInput = new QCheckBox(i18n("Color Balance")); d->colorBalanceInput->setCheckState(Qt::Checked); d->colorBalanceInput->setToolTip(i18n("Check to apply the built-in color balance of the film profile. " "Un-check if you want to apply color balance yourself.")); // ------------------------------------------------------------- d->pickWhitePoint = new QToolButton(); d->pickWhitePoint->setIcon(QIcon::fromTheme(QLatin1String("color-picker-white"))); d->pickWhitePoint->setCheckable(true); d->pickWhitePoint->setToolTip( i18n( "White point color picker" ) ); d->pickWhitePoint->setWhatsThis(i18n("With this button, you can pick the color of the orange mask " "of the scanned color negative. It represents white point of the negative, " "or the darkest black tone of the positive image " "after inversion. It is also the reference point for applying the film profile.")); d->resetButton = new QPushButton(i18n("&Reset")); d->resetButton->setIcon(QIcon::fromTheme(QLatin1String("document-revert"))); d->resetButton->setToolTip( i18n( "Reset white point." ) ); d->resetButton->setWhatsThis(i18n("If you press this button, the white point is " "reset to pure white.")); d->autoButton = new QToolButton(); d->autoButton->setIcon(QIcon::fromTheme(QLatin1String("system-run"))); d->autoButton->setToolTip( i18n( "Adjust white point automatically." ) ); d->autoButton->setWhatsThis(i18n("If you press this button, the white point is calculated " "from the image data automatically. This function requires to have some residual " "orange mask around the exposed area of the negative.")); QLabel* const space = new QLabel(); space->setFixedWidth(d->gboxSettings->spacingHint()); QHBoxLayout* const l3 = new QHBoxLayout(); l3->addWidget(d->pickWhitePoint); l3->addWidget(d->autoButton); l3->addWidget(space); l3->addWidget(d->resetButton); l3->addStretch(10); // ------------------------------------------------------------- d->exposureInput = new DDoubleNumInput(); d->exposureInput->setDecimals(2); d->exposureInput->setRange(0.0, 40.0, 0.01); d->exposureInput->setDefaultValue(1.0); d->exposureInput->setToolTip( i18n( "Exposure correction." ) ); d->exposureInput->setWhatsThis(i18n("Move the slider to higher values until maximum brightness is achieved " "without clipping any color channel. Use the output histogram to evaluate each channel.")); d->gammaInput = new DDoubleNumInput(); d->gammaInput->setDecimals(2); d->gammaInput->setRange(0.1, 3.0, 0.01); d->gammaInput->setDefaultValue(1.8); d->gammaInput->setToolTip(i18n( "Gamma input value." )); d->gammaInput->setWhatsThis(i18n("Linear raw scans of film negatives require application of a gamma curve. " "Standard values are 1.8 or 2.2.")); // ------------------------------------------------------------- QGridLayout* const grid = new QGridLayout(); grid->addLayout(inputLevelsLayout, 0, 0, 1, 4); grid->addWidget(d->redInputLevels, 1, 0, 1, 4); grid->addWidget(d->greenInputLevels, 2, 0, 1, 4); grid->addWidget(d->blueInputLevels, 3, 0, 1, 4); grid->addWidget(d->cnType, 4, 0, 1, 4); grid->addWidget(d->exposureInput, 5, 0, 1, 4); grid->addWidget(d->gammaInput, 6, 0, 1, 4); grid->addLayout(l3, 7, 0, 1, 2); grid->addWidget(d->colorBalanceInput, 7, 2, 1, 2, Qt::AlignRight); // TODO: fill in rest of settings elements //grid->setRowStretch(7, 10); //grid->setColumnStretch(2, 10); //grid->setColumnStretch(4, 10); grid->setContentsMargins(QMargins()); grid->setSpacing(d->gboxSettings->spacingHint()); d->gboxSettings->plainPage()->setLayout(grid); // ------------------------------------------------------------- d->filmContainer.setSixteenBit(d->originalImage->sixteenBit()); d->filmContainer.setWhitePoint(DColor(QColor(QLatin1String("white")), d->originalImage->sixteenBit())); // ------------------------------------------------------------- setToolSettings(d->gboxSettings); // Button Slots ------------------------------------------------- connect(d->autoButton, SIGNAL(clicked()), this, SLOT(slotAutoWhitePoint())); connect(d->pickWhitePoint, SIGNAL(toggled(bool)), this, SLOT(slotPickerColorButtonActived(bool))); // Slots -------------------------------------------------------- connect(d->previewWidget, SIGNAL(signalCapturedPointFromOriginal(Digikam::DColor,QPoint)), this, SLOT(slotColorSelectedFromTarget(Digikam::DColor,QPoint))); connect(d->exposureInput, SIGNAL(valueChanged(double)), this, SLOT(slotExposureChanged(double))); connect(d->gammaInput, SIGNAL(valueChanged(double)), this, SLOT(slotGammaInputChanged(double))); connect(d->resetButton, SIGNAL(clicked()), this, SLOT(slotResetWhitePoint())); connect(d->cnType, SIGNAL(itemActivated(QListWidgetItem*)), this, SLOT(slotFilmItemActivated(QListWidgetItem*))); connect(d->colorBalanceInput, SIGNAL(stateChanged(int)), this, SLOT(slotColorBalanceStateChanged(int))); } FilmTool::~FilmTool() { delete d->levels; delete d; } void FilmTool::slotResetSettings() { bool sb = d->originalImage->sixteenBit(); int max = sb ? 65535 : 255; FilmContainer::CNFilmProfile cnType = FilmContainer::CNNeutral; QString profileName = QLatin1String("Neutral"); QList matchingItems = d->cnType->findItems(profileName, Qt::MatchExactly); d->cnType->setCurrentItem(matchingItems.first()); double gamma = 1.8; d->gammaInput->setValue(gamma); gammaInputChanged(gamma); double exposure = 1.0; d->exposureInput->setValue(exposure); d->filmContainer = FilmContainer(cnType, gamma, d->originalImage->sixteenBit()); d->filmContainer.setExposure(exposure); int red = max; int green = max; int blue = max; red = sb ? red : red / 256; green = sb ? green : green / 256; blue = sb ? blue : blue / 256; DColor whitePoint = DColor(red, green, blue, max, sb); d->filmContainer.setWhitePoint(whitePoint); setLevelsFromFilm(); d->levelsHistogramWidget->reset(); d->gboxSettings->histogramBox()->histogram()->reset(); d->gboxSettings->histogramBox()->setChannel(ColorChannels); d->gboxSettings->histogramBox()->setScale(LogScaleHistogram); slotAdjustSliders(); slotChannelChanged(); slotScaleChanged(); } void FilmTool::slotChannelChanged() { d->levelsHistogramWidget->setChannelType(d->gboxSettings->histogramBox()->channel()); } void FilmTool::slotScaleChanged() { d->levelsHistogramWidget->setScaleType(d->gboxSettings->histogramBox()->scale()); } void FilmTool::slotAdjustSliders() { // adjust all Levels sliders d->redInputLevels->setLeftValue( (double)d->levels->getLevelLowInputValue(RedChannel) / d->histoSegments); d->redInputLevels->setRightValue( (double)d->levels->getLevelHighInputValue(RedChannel) / d->histoSegments); d->greenInputLevels->setLeftValue( (double)d->levels->getLevelLowInputValue(GreenChannel) / d->histoSegments); d->greenInputLevels->setRightValue( (double)d->levels->getLevelHighInputValue(GreenChannel) / d->histoSegments); d->blueInputLevels->setLeftValue( (double)d->levels->getLevelLowInputValue(BlueChannel) / d->histoSegments); d->blueInputLevels->setRightValue( (double)d->levels->getLevelHighInputValue(BlueChannel) / d->histoSegments); } void FilmTool::setLevelsFromFilm() { LevelsContainer l = d->filmContainer.toLevels(); - for (int i = RedChannel; i <= BlueChannel; i++) + for (int i = RedChannel ; i <= BlueChannel ; ++i) { d->levels->setLevelLowInputValue(i, l.lInput[i]); d->levels->setLevelHighInputValue(i, l.hInput[i]); d->levels->setLevelLowOutputValue(i, l.lOutput[i]); d->levels->setLevelHighOutputValue(i, l.hOutput[i]); d->levels->setLevelGammaValue(i, l.gamma[i]); } slotAdjustSliders(); } void FilmTool::slotExposureChanged(double val) { d->filmContainer.setExposure(val); setLevelsFromFilm(); slotTimer(); } void FilmTool::gammaInputChanged(double val) { d->filmContainer.setGamma(val); setLevelsFromFilm(); } void FilmTool::slotGammaInputChanged(double val) { gammaInputChanged(val); slotTimer(); } void FilmTool::slotFilmItemActivated(QListWidgetItem* item) { double gamma = d->filmContainer.gamma(); double strength = d->filmContainer.exposure(); DColor wp = d->filmContainer.whitePoint(); FilmContainer::CNFilmProfile type = (FilmContainer::CNFilmProfile)(item->type()-QListWidgetItem::UserType); d->filmContainer = FilmContainer(type, gamma, d->originalImage->sixteenBit()); d->filmContainer.setExposure(strength); d->filmContainer.setApplyBalance(d->colorBalanceInput->checkState() == Qt::Checked); d->filmContainer.setWhitePoint(wp); setLevelsFromFilm(); slotTimer(); } void FilmTool::slotColorSelectedFromTarget(const Digikam::DColor& color, const QPoint& p) { DColor wp00 = color; DColor wp01 = d->originalImage->getPixelColor(p.x(), p.y()+1); DColor wp10 = d->originalImage->getPixelColor(p.x()+1, p.y()); DColor wp11 = d->originalImage->getPixelColor(p.x()+1, p.y()+1); wp00.blendAdd(wp01); wp00.blendAdd(wp10); wp00.blendAdd(wp11); wp00.multiply(0.25); d->filmContainer.setWhitePoint(wp00); d->previewWidget->setCapturePointMode(false); d->pickWhitePoint->setChecked(false); setLevelsFromFilm(); slotTimer(); } void FilmTool::slotPickerColorButtonActived(bool checked) { if (checked) d->previewWidget->setCapturePointMode(true); } void FilmTool::slotAutoWhitePoint() { ImageHistogram* const hist = d->levelsHistogramWidget->currentHistogram(); bool sixteenBit = d->originalImage->sixteenBit(); int high_input[4]; - for (int channel = RedChannel; channel <= BlueChannel; channel++) + for (int channel = RedChannel ; channel <= BlueChannel ; ++channel) { double new_count = 0.0; double percentage; double next_percentage; double count = hist->getCount(channel, 0, sixteenBit ? 65535 : 255); for (int i = (sixteenBit ? 65535 : 255) ; i > 0 ; --i) { new_count += hist->getValue(channel, i); percentage = new_count / count; next_percentage = (new_count + hist->getValue(channel, i - 1)) / count; if (fabs(percentage - 0.006) < fabs(next_percentage - 0.006)) { high_input[channel] = i - 1; break; } } } DColor wp = DColor(high_input[RedChannel], high_input[GreenChannel], high_input[BlueChannel], 0, sixteenBit); d->filmContainer.setWhitePoint(wp); setLevelsFromFilm(); slotPreview(); } void FilmTool::slotResetWhitePoint() { d->filmContainer.setSixteenBit(d->originalImage->sixteenBit()); d->filmContainer.setWhitePoint(DColor(QColor(QLatin1String("white")), d->originalImage->sixteenBit())); setLevelsFromFilm(); slotPreview(); } void FilmTool::slotColorBalanceStateChanged(int state) { bool apply = (state == Qt::Checked); d->filmContainer.setApplyBalance(apply); slotPreview(); } void FilmTool::readSettings() { KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(d->configGroupName); bool sb = d->originalImage->sixteenBit(); int max = sb ? 65535 : 255; FilmContainer::CNFilmProfile cnType = (FilmContainer::CNFilmProfile) group.readEntry(d->configFilmProfileEntry, (int)FilmContainer::CNNeutral); QString profileName = group.readEntry(d->configFilmProfileName, "Neutral"); QList matchingItems = d->cnType->findItems(profileName, Qt::MatchExactly); d->cnType->setCurrentItem(matchingItems.first()); double gamma = group.readEntry(d->configGammaInputEntry, 1.8); d->gammaInput->setValue(gamma); gammaInputChanged(gamma); double exposure = group.readEntry(d->configExposureEntry, 1.0); d->exposureInput->setValue(exposure); d->filmContainer = FilmContainer(cnType, gamma, d->originalImage->sixteenBit()); d->filmContainer.setExposure(exposure); int red = group.readEntry(d->configWhitePointEntry.arg(1), max); int green = group.readEntry(d->configWhitePointEntry.arg(2), max); int blue = group.readEntry(d->configWhitePointEntry.arg(3), max); red = sb ? red : red / 256; green = sb ? green : green / 256; blue = sb ? blue : blue / 256; DColor whitePoint = DColor(red, green, blue, max, sb); d->filmContainer.setWhitePoint(whitePoint); setLevelsFromFilm(); bool apply = group.readEntry(d->configApplyColorBalance, true); d->filmContainer.setApplyBalance(apply); d->colorBalanceInput->setCheckState(apply? Qt::Checked : Qt::Unchecked); d->levelsHistogramWidget->reset(); d->gboxSettings->histogramBox()->histogram()->reset(); ChannelType ch = (ChannelType)group.readEntry(d->configHistogramChannelEntry, (int)ColorChannels); // restore the previous channel d->gboxSettings->histogramBox()->setChannel(ch); d->gboxSettings->histogramBox()->setScale((HistogramScale)group.readEntry(d->configHistogramScaleEntry, (int)LogScaleHistogram)); slotAdjustSliders(); slotChannelChanged(); slotScaleChanged(); } void FilmTool::writeSettings() { KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(d->configGroupName); bool sb = d->originalImage->sixteenBit(); group.writeEntry(d->configHistogramChannelEntry, (int)d->gboxSettings->histogramBox()->channel()); group.writeEntry(d->configHistogramScaleEntry, (int)d->gboxSettings->histogramBox()->scale()); double gamma = d->gammaInput->value(); group.writeEntry(d->configGammaInputEntry, gamma); double exposure = d->exposureInput->value(); group.writeEntry(d->configExposureEntry, exposure); int cnType = (int)d->filmContainer.cnType(); group.writeEntry(d->configFilmProfileEntry, cnType); group.writeEntry(d->configFilmProfileName, d->cnType->currentItem()->text()); int red = d->filmContainer.whitePoint().red(); int green = d->filmContainer.whitePoint().green(); int blue = d->filmContainer.whitePoint().blue(); group.writeEntry(d->configWhitePointEntry.arg(1), sb ? red : red * 256); group.writeEntry(d->configWhitePointEntry.arg(2), sb ? green : green * 256); group.writeEntry(d->configWhitePointEntry.arg(3), sb ? blue : blue * 256); bool apply = d->colorBalanceInput->checkState() == Qt::Checked; group.writeEntry(d->configApplyColorBalance, apply); config->sync(); } void FilmTool::preparePreview() { d->gboxSettings->histogramBox()->histogram()->stopHistogramComputation(); DImg preview = d->previewWidget->getOriginalRegionImage(true); setFilter(new FilmFilter(&preview, this, d->filmContainer)); } void FilmTool::prepareFinal() { ImageIface iface; setFilter(new FilmFilter(iface.original(), this, d->filmContainer)); } void FilmTool::setPreviewImage() { DImg preview = filter()->getTargetImage(); d->previewWidget->setPreviewImage(preview); // Update histogram. d->gboxSettings->histogramBox()->histogram()->updateData(preview.copy(), DImg(), false); } void FilmTool::setFinalImage() { ImageIface iface; iface.setOriginal(i18n("Film"), filter()->filterAction(), filter()->getTargetImage()); } bool FilmTool::eventFilter(QObject* obj, QEvent* ev) { // swallow mouse evens for level sliders to make them immutable if (obj == d->redInputLevels || obj == d->greenInputLevels || obj == d->blueInputLevels) { if (ev->type() == QEvent::MouseButtonPress || ev->type() == QEvent::MouseButtonRelease || ev->type() == QEvent::MouseMove || ev->type() == QEvent::MouseButtonDblClick) return true; } // pass all other events to the parent class return EditorToolThreaded::eventFilter(obj, ev); } } // namespace Digikam diff --git a/core/utilities/imageeditor/tools/enhance/healingclonetool.cpp b/core/utilities/imageeditor/tools/enhance/healingclonetool.cpp index 4d19d4cd6d..4a580104cc 100644 --- a/core/utilities/imageeditor/tools/enhance/healingclonetool.cpp +++ b/core/utilities/imageeditor/tools/enhance/healingclonetool.cpp @@ -1,255 +1,255 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2017-06-15 * Description : a tool to replace part of the image using another * * Copyright (C) 2004-2018 by Gilles Caulier * Copyright (C) 2017 by Shaza Ismail Kaoud * * 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 "healingclonetool.h" // Qt includes #include #include #include #include #include // KDE includes #include #include // Local includes #include "dexpanderbox.h" #include "dnuminput.h" #include "editortoolsettings.h" #include "imageiface.h" #include "imageguidewidget.h" #include "imagebrushguidewidget.h" namespace Digikam { class Q_DECL_HIDDEN HealingCloneTool::Private { public: explicit Private() : radiusInput(0), blurPercent(0), previewWidget(0), gboxSettings(0), srcButton(0) { } static const QString configGroupName; static const QString configRadiusAdjustmentEntry; static const QString configBlurAdjustmentEntry; DIntNumInput* radiusInput; DDoubleNumInput* blurPercent; ImageBrushGuideWidget* previewWidget; EditorToolSettings* gboxSettings; QPoint sourcePoint; QPoint destinationStartPoint; QPushButton* srcButton; }; const QString HealingCloneTool::Private::configGroupName(QLatin1String("Healing Clone Tool")); const QString HealingCloneTool::Private::configRadiusAdjustmentEntry(QLatin1String("RadiusAdjustment")); const QString HealingCloneTool::Private::configBlurAdjustmentEntry(QLatin1String("BlurAdjustment")); // -------------------------------------------------------- HealingCloneTool::HealingCloneTool(QObject* const parent) : EditorTool(parent), d(new Private) { setObjectName(QLatin1String("healing clone")); setToolName(i18n("Healing Clone Tool")); setToolIcon(QIcon::fromTheme(QLatin1String("healimage"))); setToolHelp(QLatin1String("healingclonetool.anchor")); d->gboxSettings = new EditorToolSettings(0); d->previewWidget = new ImageBrushGuideWidget(0, true, ImageGuideWidget::PickColorMode, Qt::red); setToolView(d->previewWidget); setPreviewModeMask(PreviewToolBar::PreviewTargetImage); // -------------------------------------------------------- QLabel* const label = new QLabel(i18n("Brush Radius:")); d->radiusInput = new DIntNumInput(); d->radiusInput->setRange(0, 50, 1); d->radiusInput->setDefaultValue(10); d->radiusInput->setWhatsThis(i18n("A radius of 0 has no effect, " "1 and above determine the brush radius " "that determines the size of parts copied in the image.")); // -------------------------------------------------------- QLabel* const label2 = new QLabel(i18n("Radial Blur Percent:")); d->blurPercent = new DDoubleNumInput(); d->blurPercent->setRange(0, 100, 0.1); d->blurPercent->setDefaultValue(0); d->blurPercent->setWhatsThis(i18n("A percent of 0 has no effect, values " "above 0 represent a factor for mixing " "the destination color with source color " "this is done radially i.e. the inner part of " "the brush radius is totally from source and mixing " "with destination is done gradually till the outer part " "of the circle.")); // -------------------------------------------------------- QLabel* const label3 = new QLabel(i18n("Source:")); d->srcButton = new QPushButton(i18n("Set Source Point"), d->gboxSettings->plainPage()); // -------------------------------------------------------- const int spacing = d->gboxSettings->spacingHint(); QGridLayout* const grid = new QGridLayout( ); grid->addWidget(label3, 1, 0, 1, 2); grid->addWidget(d->srcButton, 2, 0, 1, 2); grid->addWidget(new DLineWidget(Qt::Horizontal, d->gboxSettings->plainPage()), 3, 0, 1, 2); grid->addWidget(label, 4, 0, 1, 2); grid->addWidget(d->radiusInput, 5, 0, 1, 2); grid->addWidget(label2, 6, 0, 1, 2); grid->addWidget(d->blurPercent, 7, 0, 1, 2); grid->setRowStretch(8, 8); grid->setContentsMargins(spacing, spacing, spacing, spacing); grid->setSpacing(spacing); d->gboxSettings->plainPage()->setLayout(grid); // -------------------------------------------------------- setPreviewModeMask(PreviewToolBar::PreviewTargetImage); setToolSettings(d->gboxSettings); setToolView(d->previewWidget); // -------------------------------------------------------- connect(d->radiusInput, SIGNAL(valueChanged(int)), this, SLOT(slotRadiusChanged(int))); connect(d->srcButton, SIGNAL(clicked(bool)), d->previewWidget, SLOT(slotSetSourcePoint())); connect(d->previewWidget, SIGNAL(signalClone(QPoint,QPoint)), this, SLOT(slotReplace(QPoint,QPoint))); connect(d->previewWidget, SIGNAL(signalResized()), this, SLOT(slotResized())); } HealingCloneTool::~HealingCloneTool() { delete d; } void HealingCloneTool::readSettings() { KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(d->configGroupName); d->radiusInput->setValue(group.readEntry(d->configRadiusAdjustmentEntry, d->radiusInput->defaultValue())); d->blurPercent->setValue(group.readEntry(d->configBlurAdjustmentEntry, d->blurPercent->defaultValue())); } void HealingCloneTool::writeSettings() { KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(d->configGroupName); group.writeEntry(d->configRadiusAdjustmentEntry, d->radiusInput->value()); group.writeEntry(d->configBlurAdjustmentEntry, d->blurPercent->value()); config->sync(); } void HealingCloneTool::finalRendering() { ImageIface iface; DImg dest = d->previewWidget->imageIface()->preview(); FilterAction action(QLatin1String("digikam:healingCloneTool"), 1); iface.setOriginal(i18n("healingClone"), action, dest); } void HealingCloneTool::slotResetSettings() { d->radiusInput->blockSignals(true); d->radiusInput->slotReset(); d->radiusInput->blockSignals(false); } void HealingCloneTool::slotResized() { toolView()->update(); } void HealingCloneTool::slotReplace(const QPoint& srcPoint, const QPoint& dstPoint) { ImageIface* const iface = d->previewWidget->imageIface(); DImg* const current = iface->previewReference(); clone(current, srcPoint, dstPoint, d->radiusInput->value()); d->previewWidget->updatePreview(); } void HealingCloneTool::slotRadiusChanged(int r) { d->previewWidget->setMaskPenSize(r); } void HealingCloneTool::clone(DImg* const img, const QPoint& srcPoint, const QPoint& dstPoint, int radius) { double blurPercent = d->blurPercent->value() / 100; - for (int i = -1 * radius ; i < radius ; i++) + for (int i = -1 * radius ; i < radius ; ++i) { - for (int j = -1 * radius ; j < radius ; j++) + for (int j = -1 * radius ; j < radius ; ++j) { int rPercent = (i * i) + (j * j); if (rPercent < (radius * radius)) // Check for inside the circle { if (srcPoint.x()+i < 0 || srcPoint.x()+i >= (int)img->width() || srcPoint.y()+j < 0 || srcPoint.y()+j >= (int)img->height() || dstPoint.x()+i < 0 || dstPoint.x()+i >= (int)img->width() || dstPoint.y()+j < 0 || dstPoint.y()+j >= (int)img->height()) { continue; } double rP = blurPercent * rPercent / (radius * radius); DColor cSrc = img->getPixelColor(srcPoint.x()+i, srcPoint.y()+j); DColor cDst = img->getPixelColor(dstPoint.x()+i, dstPoint.y()+j); cSrc.multiply(1 - rP); cDst.multiply(rP); cSrc.blendAdd(cDst); img->setPixelColor(dstPoint.x()+i, dstPoint.y()+j, cSrc); } } } } } // namespace Digikam diff --git a/core/utilities/import/backend/gpcamera.cpp b/core/utilities/import/backend/gpcamera.cpp index 2b65b2d051..de58e0c435 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 * http://www.digikam.org * * Date : 2003-01-21 * Description : Gphoto2 camera interface * * Copyright (C) 2003-2005 by Renchi Raju * Copyright (C) 2006-2018 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) { 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++) + 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++) + 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++) + 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++) + 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/models/importfiltermodel.cpp b/core/utilities/import/models/importfiltermodel.cpp index 73167b72dd..32fe28f806 100644 --- a/core/utilities/import/models/importfiltermodel.cpp +++ b/core/utilities/import/models/importfiltermodel.cpp @@ -1,561 +1,561 @@ /* ============================================================ * * This file is a part of digiKam project * http://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++) + 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; importItemModel = 0; filter = 0; } void init(ImportFilterModel* const _q); Q_SIGNALS: void reAddCamItemInfos(const QList&); void reAddingFinished(); public: ImportFilterModel* q; 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++) + 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++) + 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) { return true; } QModelIndex idx = sourceModel()->index(source_row, 0, source_parent); const CamItemInfo &info = d->importItemModel->camItemInfo(idx); 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/queuemanager/manager/actionthread.cpp b/core/utilities/queuemanager/manager/actionthread.cpp index f465e86a61..0b016a86ca 100644 --- a/core/utilities/queuemanager/manager/actionthread.cpp +++ b/core/utilities/queuemanager/manager/actionthread.cpp @@ -1,145 +1,145 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2009-02-06 * Description : Thread actions manager. * * Copyright (C) 2009-2018 by Gilles Caulier * Copyright (C) 2012 by Pankaj Kumar * * 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 "actionthread.h" // Local includes #include "digikam_debug.h" #include "digikam_config.h" #include "collectionscanner.h" #include "task.h" namespace Digikam { class Q_DECL_HIDDEN ActionThread::Private { public: explicit Private() { } QueueSettings settings; }; // -------------------------------------------------------------------------------------- ActionThread::ActionThread(QObject* const parent) : ActionThreadBase(parent), d(new Private) { setObjectName(QLatin1String("QueueMngrThread")); qRegisterMetaType(); connect(this, SIGNAL(finished()), this, SLOT(slotThreadFinished())); } ActionThread::~ActionThread() { cancel(); wait(); delete d; } void ActionThread::setSettings(const QueueSettings& settings) { d->settings = settings; if (!d->settings.useMultiCoreCPU) { setMaximumNumberOfThreads(1); } else { defaultMaximumNumberOfThreads(); } } void ActionThread::processQueueItems(const QList& items) { ActionJobCollection collection; - for (int i = 0 ; i < items.size() ; i++) + for (int i = 0 ; i < items.size() ; ++i) { Task* const t = new Task(); t->setSettings(d->settings); t->setItem(items.at(i)); connect(t, SIGNAL(signalStarting(Digikam::ActionData)), this, SIGNAL(signalStarting(Digikam::ActionData))); connect(t, SIGNAL(signalFinished(Digikam::ActionData)), this, SLOT(slotUpdateItemInfo(Digikam::ActionData)), Qt::BlockingQueuedConnection); connect(this, SIGNAL(signalCancelTask()), t, SLOT(slotCancel()), Qt::QueuedConnection); collection.insert(t, 0); } appendJobs(collection); } void ActionThread::cancel() { if (isRunning()) emit signalCancelTask(); ActionThreadBase::cancel(); } void ActionThread::slotUpdateItemInfo(const Digikam::ActionData& ad) { if (ad.status == ActionData::BatchDone) { CollectionScanner scanner; ItemInfo source = ItemInfo::fromUrl(ad.fileUrl); qlonglong id = scanner.scanFile(ad.destUrl.toLocalFile(), CollectionScanner::NormalScan); ItemInfo info(id); // Copy the digiKam attributes from original file to the new file CollectionScanner::copyFileProperties(source, info); // Read again new file that the database is up to date scanner.scanFile(info, CollectionScanner::Rescan); } emit signalFinished(ad); } void ActionThread::slotThreadFinished() { if (isEmpty()) { qCDebug(DIGIKAM_GENERAL_LOG) << "List of Pending Jobs is empty"; emit signalQueueProcessed(); } } } // namespace Digikam diff --git a/core/utilities/searchwindow/searchfields.cpp b/core/utilities/searchwindow/searchfields.cpp index 578fd6949a..f75111a6f9 100644 --- a/core/utilities/searchwindow/searchfields.cpp +++ b/core/utilities/searchwindow/searchfields.cpp @@ -1,3082 +1,3082 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2008-01-20 * Description : User interface for searches * * Copyright (C) 2008-2012 by Marcel Wiesweg * Copyright (C) 2011-2018 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 "searchfields.h" // C++ includes #include // Qt includes #include #include #include #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 "squeezedcombobox.h" #include "albummanager.h" #include "albummodel.h" #include "dexpanderbox.h" #include "dlayoutbox.h" #include "albumselectcombobox.h" #include "choicesearchutilities.h" #include "dimg.h" #include "dmetadata.h" #include "itemscanner.h" #include "ddateedit.h" #include "tagtreeview.h" #include "ratingsearchutilities.h" #include "searchfieldgroup.h" #include "searchwindow.h" #include "tagscache.h" #include "colorlabelfilter.h" #include "picklabelfilter.h" #include "applicationsettings.h" #include "itempropertiestab.h" namespace Digikam { SearchField* SearchField::createField(const QString& name, SearchFieldGroup* const parent) { if (name == QLatin1String("albumid")) { SearchFieldAlbum* const field = new SearchFieldAlbum(parent, SearchFieldAlbum::TypeAlbum); field->setFieldName(name); field->setText(i18n("Album"), i18n("Search items located in")); return field; } else if (name == QLatin1String("albumname")) { SearchFieldText* const field = new SearchFieldText(parent); field->setFieldName(name); field->setText(i18n("Album"), i18n("The album name contains")); return field; } else if (name == QLatin1String("albumcaption")) { SearchFieldText* const field = new SearchFieldText(parent); field->setFieldName(name); field->setText(i18n("Album"), i18n("The album caption contains")); return field; } else if (name == QLatin1String("albumcollection")) { SearchFieldChoice* const field = new SearchFieldChoice(parent); field->setFieldName(name); field->setText(i18n("Album"), i18n("The album category is")); ApplicationSettings* const settings = ApplicationSettings::instance(); if (settings) { QStringList Categories = settings->getAlbumCategoryNames(); int size = Categories.size(); QStringList categorychoices; - for (int i=0; isetChoice(categorychoices); } return field; } else if (name == QLatin1String("tagid")) { SearchFieldAlbum* const field = new SearchFieldAlbum(parent, SearchFieldAlbum::TypeTag); field->setFieldName(name); field->setText(i18n("Tags"), i18n("Return items with tag")); return field; } else if (name == QLatin1String("tagname")) { SearchFieldText* const field = new SearchFieldText(parent); field->setFieldName(name); field->setText(i18n("Tags"), i18n("A tag of the item contains")); return field; } else if (name == QLatin1String("notag")) { /** * @todo Merge a "Not tagged" field into TagModel together with AND/OR control * for checked tags and logical connections (AND and Not Tagged checked => all other tags disabled) */ SearchFieldCheckBox* const field = new SearchFieldCheckBox(parent); field->setFieldName(name); field->setText(i18n("Tags"), i18n("item has no tags")); field->setLabel(i18n("Not Tagged")); return field; } else if (name == QLatin1String("filename")) { SearchFieldText* const field = new SearchFieldText(parent); field->setFieldName(name); field->setText(i18n("File Name"), i18n("Return items whose file name contains")); return field; } else if (name == QLatin1String("modificationdate")) { SearchFieldRangeDate* const field = new SearchFieldRangeDate(parent, SearchFieldRangeDate::DateTime); field->setFieldName(name); field->setText(i18n("Modification"), i18n("Return items modified between")); field->setBetweenText(i18nc("'Return items modified between...and...", "and")); return field; } else if (name == QLatin1String("filesize")) { SearchFieldRangeDouble* const field = new SearchFieldRangeDouble(parent); field->setFieldName(name); field->setText(i18n("File Size"), i18n("Size of the file")); field->setBetweenText(i18nc("Size of the file ...-...", "-")); field->setNumberPrefixAndSuffix(QString(), QLatin1String("MB")); field->setBoundary(0, 1000000, 1, 0.5); field->setFactor(1024 * 1024); return field; } else if (name == QLatin1String("labels")) { SearchFieldLabels* const field = new SearchFieldLabels(parent); field->setFieldName(name); field->setText(i18n("Labels"), i18n("Return items with labels")); return field; } else if (name == QLatin1String("rating")) { SearchFieldRating* const field = new SearchFieldRating(parent); field->setFieldName(name); field->setText(i18n("Rating"), i18n("Return items rated at least")); field->setBetweenText(i18nc("Return items rated at least...at most...", "at most")); return field; } else if (name == QLatin1String("creationdate")) { SearchFieldRangeDate* const field = new SearchFieldRangeDate(parent, SearchFieldRangeDate::DateTime); field->setFieldName(name); field->setText(i18n("Date"), i18n("Return items created between")); field->setBetweenText(i18nc("'Return items created between...and...", "and")); return field; } else if (name == QLatin1String("digitizationdate")) { SearchFieldRangeDate* const field = new SearchFieldRangeDate(parent, SearchFieldRangeDate::DateTime); field->setFieldName(name); field->setText(i18n("Digitization"), i18n("Return items digitized between")); field->setBetweenText(i18nc("'Return items digitized between...and...", "and")); return field; } else if (name == QLatin1String("orientation")) { //choice SearchFieldChoice* const field = new SearchFieldChoice(parent); field->setFieldName(name); field->setText(i18n("Exif Orientation"), i18n("Find items with orientation flag")); QMap map = DMetadata::possibleValuesForEnumField(MetadataInfo::Orientation); field->setChoice(map); return field; } else if (name == QLatin1String("dimension")) { // "width", "height", "pixels" } else if (name == QLatin1String("width")) { SearchFieldRangeInt* const field = new SearchFieldRangeInt(parent); field->setFieldName(name); field->setText(i18n("Width"), i18n("Find items with a width between")); field->setBetweenText(i18nc("Find items with a width between...and...", "and")); field->setNumberPrefixAndSuffix(QString(), i18nc("Pixels", "px")); field->setBoundary(1, 1000000, 250); field->setSuggestedValues(QList() << 50 << 100 << 200 << 300 << 400 << 500 << 600 << 700 << 800 << 900 << 1000 << 1250 << 1500 << 1750 << 2000 << 3000 << 4000 << 5000 << 6000 << 7000 << 8000 << 9000 << 10000 ); field->setSuggestedInitialValue(1000); field->setSingleSteps(50, 1000); return field; } else if (name == QLatin1String("height")) { SearchFieldRangeInt* const field = new SearchFieldRangeInt(parent); field->setFieldName(name); field->setText(i18n("Height"), i18n("Find items with a height between")); field->setBetweenText(i18nc("Find items with a height between...and...", "and")); field->setNumberPrefixAndSuffix(QString(), i18nc("Pixels", "px")); field->setBoundary(1, 1000000, 250); field->setSuggestedValues(QList() << 50 << 100 << 200 << 300 << 400 << 500 << 600 << 700 << 800 << 900 << 1000 << 1250 << 1500 << 1750 << 2000 << 3000 << 4000 << 5000 << 6000 << 7000 << 8000 << 9000 << 10000 ); field->setSuggestedInitialValue(1000); field->setSingleSteps(50, 1000); return field; } else if (name == QLatin1String("pageorientation")) { SearchFieldPageOrientation* const field = new SearchFieldPageOrientation(parent); field->setFieldName(name); field->setText(i18n("Orientation"), i18nc("Find items with any orientation / landscape / portrait orientation...", "Find items with")); return field; } else if (name == QLatin1String("format")) { //choice SearchFieldChoice* const field = new SearchFieldChoice(parent); field->setFieldName(name); field->setText(i18n("File Format"), i18n("Return items with the file format")); QStringList formats; foreach(const QString& fmt, CoreDbAccess().db()->getFormatStatistics(DatabaseItem::Image).keys()) formats << fmt << i18n("%1 [Image]", fmt); foreach(const QString& fmt, CoreDbAccess().db()->getFormatStatistics(DatabaseItem::Video).keys()) formats << fmt << i18n("%1 [Video]", fmt); foreach(const QString& fmt, CoreDbAccess().db()->getFormatStatistics(DatabaseItem::Audio).keys()) formats << fmt << i18n("%1 [Audio]", fmt); /* FIXME: This can report 2 times JPG : one as image, one as other. Where is the problem ? foreach(const QString& fmt, CoreDbAccess().db()->getFormatStatistics(DatabaseItem::Other).keys()) formats << fmt << i18n("%1 [Other]", fmt); */ formats.sort(); qCDebug(DIGIKAM_GENERAL_LOG) << formats; field->setChoice(formats); return field; } else if (name == QLatin1String("colordepth")) { //choice SearchFieldColorDepth* const field = new SearchFieldColorDepth(parent); field->setFieldName(name); field->setText(i18n("Color Depth"), i18nc("Find items with any color depth / 8 bits per channel...", "Find items with")); return field; } else if (name == QLatin1String("colormodel")) { //choice SearchFieldChoice* const field = new SearchFieldChoice(parent); field->setFieldName(name); field->setText(i18n("Color Model"), i18n("Find items with the color model")); QMap map; // Images map.insert(DImg::COLORMODELUNKNOWN, i18n("%1 [Image]", DImg::colorModelToString(DImg::COLORMODELUNKNOWN))); map.insert(DImg::RGB, i18n("%1 [Image]", DImg::colorModelToString(DImg::RGB))); map.insert(DImg::GRAYSCALE, i18n("%1 [Image]", DImg::colorModelToString(DImg::GRAYSCALE))); map.insert(DImg::MONOCHROME, i18n("%1 [Image]", DImg::colorModelToString(DImg::MONOCHROME))); map.insert(DImg::INDEXED, i18n("%1 [Image]", DImg::colorModelToString(DImg::INDEXED))); map.insert(DImg::YCBCR, i18n("%1 [Image]", DImg::colorModelToString(DImg::YCBCR))); map.insert(DImg::CMYK, i18n("%1 [Image]", DImg::colorModelToString(DImg::CMYK))); map.insert(DImg::CIELAB, i18n("%1 [Image]", DImg::colorModelToString(DImg::CIELAB))); map.insert(DImg::COLORMODELRAW, i18n("%1 [Image]", DImg::colorModelToString(DImg::COLORMODELRAW))); // Video map.insert(DMetadata::VIDEOCOLORMODEL_SRGB, i18n("%1 [Video]", DMetadata::videoColorModelToString(DMetadata::VIDEOCOLORMODEL_SRGB))); map.insert(DMetadata::VIDEOCOLORMODEL_BT709, i18n("%1 [Video]", DMetadata::videoColorModelToString(DMetadata::VIDEOCOLORMODEL_BT709))); map.insert(DMetadata::VIDEOCOLORMODEL_BT601, i18n("%1 [Video]", DMetadata::videoColorModelToString(DMetadata::VIDEOCOLORMODEL_BT601))); map.insert(DMetadata::VIDEOCOLORMODEL_OTHER, i18n("%1 [Video]", DMetadata::videoColorModelToString(DMetadata::VIDEOCOLORMODEL_OTHER))); field->setChoice(map); return field; } else if (name == QLatin1String("make")) { //choice SearchFieldChoice* const field = new SearchFieldChoice(parent); field->setFieldName(name); field->setText(i18n("Camera"), i18n("The make of the camera")); QStringList make = CoreDbAccess().db()->getListFromImageMetadata(DatabaseFields::Make); - for (int i = 0 ; i < make.count() ; i++) + for (int i = 0 ; i < make.count() ; ++i) { ItemPropertiesTab::shortenedMakeInfo(make[i]); make[i] = make[i].trimmed(); } make.removeDuplicates(); make += make; make.sort(); for (int i = 0 ; i < make.count() ; i += 2) { make[i] = QLatin1Char('*') + make[i] + QLatin1Char('*'); } field->setChoice(make); return field; } else if (name == QLatin1String("model")) { //choice SearchFieldChoice* const field = new SearchFieldChoice(parent); field->setFieldName(name); field->setText(i18n("Camera"), i18n("The model of the camera")); QStringList model = CoreDbAccess().db()->getListFromImageMetadata(DatabaseFields::Model); - for (int i = 0 ; i < model.count() ; i++) + for (int i = 0 ; i < model.count() ; ++i) { ItemPropertiesTab::shortenedModelInfo(model[i]); model[i] = model[i].trimmed(); } model.removeDuplicates(); model += model; model.sort(); for (int i = 0 ; i < model.count() ; i += 2) { model[i] = QLatin1Char('*') + model[i] + QLatin1Char('*'); } field->setChoice(model); return field; } else if (name == QLatin1String("lenses")) { //choice SearchFieldChoice* const field = new SearchFieldChoice(parent); field->setFieldName(name); field->setText(i18n("Lens"), i18n("The type of the lens")); QStringList lens = CoreDbAccess().db()->getListFromImageMetadata(DatabaseFields::Lens); lens += lens; lens.sort(); field->setChoice(lens); return field; } else if (name == QLatin1String("aperture")) { //double SearchFieldRangeDouble* const field = new SearchFieldRangeDouble(parent); field->setFieldName(name); field->setText(i18n("Aperture"), i18n("Lens aperture as f-number")); field->setBetweenText(i18nc("Lens aperture as f-number ...-...", "-")); field->setNoValueText(QLatin1String("f/#")); field->setNumberPrefixAndSuffix(QLatin1String("f/"), QString()); field->setBoundary(0.3, 65536, 1, 0.1); field->setSuggestedValues(QList() << 0.5 << 0.7 << 1.0 << 1.4 << 2 << 2.8 << 4 << 5.6 << 8 << 11 << 16 << 22 << 32 << 45 << 64 << 90 << 128 ); field->setSuggestedInitialValue(1.0); field->setSingleSteps(0.1, 10); return field; } else if (name == QLatin1String("focallength")) { //double SearchFieldRangeInt* const field = new SearchFieldRangeInt(parent); field->setFieldName(name); field->setText(i18n("Focal length"), i18n("Focal length of the lens")); field->setBetweenText(i18nc("Focal length of the lens ...-...", "-")); field->setNumberPrefixAndSuffix(QString(), QLatin1String("mm")); field->setBoundary(0, 200000, 10); field->setSuggestedValues(QList() << 10 << 15 << 20 << 25 << 30 << 40 << 50 << 60 << 70 << 80 << 90 << 100 << 150 << 200 << 250 << 300 << 400 << 500 << 750 << 1000 ); field->setSuggestedInitialValue(30); field->setSingleSteps(2, 500); return field; } else if (name == QLatin1String("focallength35")) { //double SearchFieldRangeInt* const field = new SearchFieldRangeInt(parent); field->setFieldName(name); field->setText(i18n("Focal length"), i18n("35mm equivalent focal length")); field->setBetweenText(i18nc("35mm equivalent focal length ...-...", "-")); field->setNumberPrefixAndSuffix(QString(), QLatin1String("mm")); field->setBoundary(0, 200000, 10); field->setSuggestedValues(QList() << 8 << 10 << 15 << 16 << 20 << 28 << 30 << 40 << 50 << 60 << 70 << 80 << 90 << 100 << 150 << 200 << 250 << 300 << 400 << 500 << 750 << 1000 ); field->setSuggestedInitialValue(28); field->setSingleSteps(2, 500); return field; } else if (name == QLatin1String("exposuretime")) { //double SearchFieldRangeInt* const field = new SearchFieldRangeInt(parent); field->setFieldName(name); field->setText(i18n("Exposure"), i18n("Exposure time")); field->setBetweenText(i18nc("Exposure time ...-...", "-")); field->setNumberPrefixAndSuffix(QString(), QLatin1String("s")); field->enableFractionMagic(QLatin1String("1/")); // it's 1/250, not 250 as in the spin box field->setBoundary(86400, -1024000, 10); // negative is 1/ field->setSuggestedValues(QList() << 30 << 15 << 8 << 4 << 2 << 1 << -2 << -4 << -8 << -15 << -30 << -50 << -60 << -100 << -125 << -150 << -200 << -250 << -500 << -750 << -1000 << -2000 << -4000 << -8000 << -16000 ); field->setSuggestedInitialValue(-200); field->setSingleSteps(2000, 5); return field; } else if (name == QLatin1String("exposureprogram")) { //choice SearchFieldChoice* const field = new SearchFieldChoice(parent); field->setFieldName(name); field->setText(i18n("Exposure"), i18n("Automatic exposure program")); QMap map = DMetadata::possibleValuesForEnumField(MetadataInfo::ExposureProgram); field->setChoice(map); return field; } else if (name == QLatin1String("exposuremode")) { //choice SearchFieldChoice* const field = new SearchFieldChoice(parent); field->setFieldName(name); field->setText(i18n("Exposure"), i18n("Automatic or manual exposure")); QMap map = DMetadata::possibleValuesForEnumField(MetadataInfo::ExposureMode); field->setChoice(map); return field; } else if (name == QLatin1String("sensitivity")) { //int SearchFieldRangeInt* const field = new SearchFieldRangeInt(parent); field->setFieldName(name); field->setText(i18n("Sensitivity"), i18n("ISO film speed (linear scale, ASA)")); field->setBetweenText(i18nc("ISO film speed (linear scale, ASA) ...-...", "-")); field->setBoundary(0, 2000000, 50); field->setSuggestedValues(QList() << 6 << 8 << 10 << 12 << 16 << 20 << 25 << 32 << 40 << 50 << 64 << 80 << 100 << 125 << 160 << 200 << 250 << 320 << 400 << 500 << 640 << 800 << 1000 << 1250 << 1600 << 2000 << 2500 << 3200 << 4000 << 5000 << 6400 ); field->setSuggestedInitialValue(200); field->setSingleSteps(1, 400); return field; } else if (name == QLatin1String("flashmode")) { //choice /** * @todo This is a bitmask, and gives some more information */ SearchFieldChoice* const field = new SearchFieldChoice(parent); field->setFieldName(name); field->setText(i18n("Flash"), i18n("Flash mode")); QMap map = DMetadata::possibleValuesForEnumField(MetadataInfo::FlashMode); field->setChoice(map); return field; } else if (name == QLatin1String("whitebalance")) { //choice SearchFieldChoice* const field = new SearchFieldChoice(parent); field->setFieldName(name); field->setText(i18n("White Balance"), i18n("Automatic or manual white balance")); QMap map = DMetadata::possibleValuesForEnumField(MetadataInfo::WhiteBalance); field->setChoice(map); return field; } else if (name == QLatin1String("whitebalancecolortemperature")) { //int SearchFieldRangeInt* const field = new SearchFieldRangeInt(parent); field->setFieldName(name); field->setText(i18n("White balance"), i18n("Color temperature used for white balance")); field->setBetweenText(i18nc("Color temperature used for white balance ...-...", "-")); field->setNumberPrefixAndSuffix(QString(), QLatin1String("K")); field->setBoundary(1, 100000, 100); return field; } else if (name == QLatin1String("meteringmode")) { //choice SearchFieldChoice* const field = new SearchFieldChoice(parent); field->setFieldName(name); field->setText(i18n("Metering Mode"), i18n("Method to determine the exposure")); QMap map = DMetadata::possibleValuesForEnumField(MetadataInfo::MeteringMode); field->setChoice(map); return field; } else if (name == QLatin1String("subjectdistance")) { //double SearchFieldRangeDouble* const field = new SearchFieldRangeDouble(parent); field->setFieldName(name); field->setText(i18n("Subject Distance"), i18n("Distance of the subject from the lens")); field->setBetweenText(i18nc("Distance of the subject from the lens ...-...", "-")); field->setNumberPrefixAndSuffix(QString(), QLatin1String("m")); field->setBoundary(0, 50000, 1, 0.1); return field; } else if (name == QLatin1String("subjectdistancecategory")) { //choice SearchFieldChoice* const field = new SearchFieldChoice(parent); field->setFieldName(name); field->setText(i18n("Subject Distance"), i18n("Macro, close or distant view")); QMap map = DMetadata::possibleValuesForEnumField(MetadataInfo::SubjectDistanceCategory); field->setChoice(map); return field; } else if (name == QLatin1String("latitude")) { } else if (name == QLatin1String("longitude")) { } else if (name == QLatin1String("altitude")) { SearchFieldRangeDouble* const field = new SearchFieldRangeDouble(parent); field->setFieldName(name); field->setText(i18n("GPS"), i18n("Altitude range")); field->setBetweenText(i18nc("Altitude range ...-...", "-")); field->setNumberPrefixAndSuffix(QString(), QLatin1String("m")); field->setBoundary(0, 10000, 4, 1); return field; } else if (name == QLatin1String("positionorientation")) { } else if (name == QLatin1String("positiontilt")) { } else if (name == QLatin1String("positionroll")) { } else if (name == QLatin1String("positiondescription")) { } else if (name == QLatin1String("nogps")) { SearchFieldCheckBox* const field = new SearchFieldCheckBox(parent); field->setFieldName(name); field->setText(i18n("GPS"), i18n("Item has no GPS info")); field->setLabel(i18n("Not Geo-located")); return field; } else if (name == QLatin1String("creator")) { SearchFieldText* const field = new SearchFieldText(parent); field->setFieldName(name); field->setText(i18n("Creator"), i18n("Return items created by")); return field; } else if (name == QLatin1String("comment")) { SearchFieldText* const field = new SearchFieldText(parent); field->setFieldName(name); field->setText(i18n("Caption"), i18n("Return items whose comment contains")); return field; } else if (name == QLatin1String("commentauthor")) { SearchFieldText* const field = new SearchFieldText(parent); field->setFieldName(name); field->setText(i18n("Author"), i18n("Return items commented by")); return field; } else if (name == QLatin1String("headline")) { SearchFieldText* const field = new SearchFieldText(parent); field->setFieldName(name); field->setText(i18n("Headline"), i18n("Return items with the IPTC headline")); return field; } else if (name == QLatin1String("title")) { SearchFieldText* const field = new SearchFieldText(parent); field->setFieldName(name); field->setText(i18n("Title"), i18n("Return items with the IPTC title")); return field; } else if (name == QLatin1String("keyword")) { SearchFieldText* const field = new SearchFieldKeyword(parent); field->setFieldName(name); field->setText(QString(), i18n("Find items that have associated all these words:")); return field; } else if (name == QLatin1String("aspectratioimg")) { SearchFieldText* const field = new SearchFieldText(parent); field->setFieldName(name); field->setText(i18n("Aspect Ratio"), i18n("Return items with the aspect ratio")); return field; } else if (name == QLatin1String("pixelsize")) { SearchFieldRangeInt* const field = new SearchFieldRangeInt(parent); field->setFieldName(name); field->setText(i18n("Pixel Size"), i18n("Value of (Width * Height) between")); field->setBetweenText(i18nc("Value of (Width * Height) between...and...", "and")); field->setNumberPrefixAndSuffix(QString(), QLatin1String("px")); field->setBoundary(1, 2000000000, 100); return field; } else if (name == QLatin1String("videoaspectratio")) { SearchFieldChoice* const field = new SearchFieldChoice(parent); field->setFieldName(name); field->setText(i18n("Aspect Ratio"), i18n("Return video with the frame aspect ratio")); QStringList ratio; ratio << QLatin1String("4:3") << QLatin1String("4:3"); ratio << QLatin1String("3:2") << QLatin1String("3:2"); ratio << QLatin1String("16:9") << QLatin1String("16:9"); ratio << QLatin1String("2:1") << QLatin1String("2:1"); // TODO: add more possible aspect ratio field->setChoice(ratio); return field; } else if (name == QLatin1String("videoduration")) { SearchFieldRangeInt* const field = new SearchFieldRangeInt(parent); field->setFieldName(name); field->setText(i18n("Duration"), i18n("Length of the video")); field->setBetweenText(i18nc("Find video with a length between...and...", "and")); field->setNumberPrefixAndSuffix(QString(), i18nc("Seconds", "s")); field->setBoundary(1, 10000, 100); field->setSuggestedValues(QList() << 10 << 30 << 60 << 90 << 120 << 240 << 360 << 500 << 1000 << 2000 << 3000 << 4000 << 5000 << 6000 << 7000 << 8000 << 9000 << 10000 // TODO : adjust default values ); field->setSuggestedInitialValue(10); field->setSingleSteps(10, 100); return field; } else if (name == QLatin1String("videoframerate")) { SearchFieldRangeInt* const field = new SearchFieldRangeInt(parent); field->setFieldName(name); field->setText(i18n("Frame Rate"), i18n("Return video with the frame rate")); field->setBetweenText(i18nc("Find video with frame rate between...and...", "and")); field->setNumberPrefixAndSuffix(QString(), i18nc("Frames per Second", "fps")); field->setBoundary(10, 60, 5); field->setSuggestedValues(QList() << 10 << 15 << 20 << 25 << 30 << 35 << 40 << 45 << 55 << 60 // TODO : adjust default values ); field->setSuggestedInitialValue(10); field->setSingleSteps(5, 60); return field; } else if (name == QLatin1String("videocodec")) { SearchFieldChoice* const field = new SearchFieldChoice(parent); field->setFieldName(name); field->setText(i18n("Codec"), i18n("Return video codec")); QStringList codec; // List of most common video codecs supported by FFMpeg (see "ffpmpeg -codecs" for details) // // FFMpeg codec name FFMpeg codec description codec << QLatin1String("8bps") << QLatin1String("QuickTime 8BPS video"); codec << QLatin1String("amv") << QLatin1String("AMV Video"); codec << QLatin1String("avs") << QLatin1String("AVS (Audio Video Standard) video"); codec << QLatin1String("cavs") << QLatin1String("Chinese AVS (Audio Video Standard) (AVS1-P2, JiZhun profile)"); codec << QLatin1String("cinepak") << QLatin1String("Cinepak"); codec << QLatin1String("dirac") << QLatin1String("Dirac"); codec << QLatin1String("flv1") << QLatin1String("FLV / Sorenson Spark / Sorenson H.263 (Flash Video)"); codec << QLatin1String("h261") << QLatin1String("H.261"); codec << QLatin1String("h263") << QLatin1String("H.263 / H.263-1996, H.263+ / H.263-1998 / H.263 version 2"); codec << QLatin1String("h263i") << QLatin1String("Intel H.263"); codec << QLatin1String("h263p") << QLatin1String("H.263+ / H.263-1998 / H.263 version 2"); codec << QLatin1String("h264") << QLatin1String("H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10"); codec << QLatin1String("hevc") << QLatin1String("H.265 / HEVC (High Efficiency Video Coding)"); codec << QLatin1String("jpeg2000") << QLatin1String("JPEG 2000"); codec << QLatin1String("mjpeg") << QLatin1String("Motion JPEG"); codec << QLatin1String("mjpegb") << QLatin1String("Apple MJPEG-B"); codec << QLatin1String("mpeg1video") << QLatin1String("MPEG-1 video"); codec << QLatin1String("mpeg2video") << QLatin1String("MPEG-2 video"); codec << QLatin1String("mpeg4") << QLatin1String("MPEG-4 part 2"); codec << QLatin1String("msmpeg4v1") << QLatin1String("MPEG-4 part 2 Microsoft variant version 1"); codec << QLatin1String("msmpeg4v2") << QLatin1String("MPEG-4 part 2 Microsoft variant version 2"); codec << QLatin1String("msmpeg4v3") << QLatin1String("MPEG-4 part 2 Microsoft variant version 3"); codec << QLatin1String("msvideo1") << QLatin1String("Microsoft Video 1"); codec << QLatin1String("msrle") << QLatin1String("Microsoft RLE"); codec << QLatin1String("mvc1") << QLatin1String("Silicon Graphics Motion Video Compressor 1"); codec << QLatin1String("mvc2") << QLatin1String("Silicon Graphics Motion Video Compressor 2"); codec << QLatin1String("qtrle") << QLatin1String("QuickTime Animation (RLE) video"); codec << QLatin1String("rawvideo") << QLatin1String("Raw video"); codec << QLatin1String("rpza") << QLatin1String("QuickTime video (RPZA)"); codec << QLatin1String("rv10") << QLatin1String("RealVideo 1.0"); codec << QLatin1String("rv20") << QLatin1String("RealVideo 2.0"); codec << QLatin1String("rv30") << QLatin1String("RealVideo 3.0"); codec << QLatin1String("rv40") << QLatin1String("RealVideo 4.0"); codec << QLatin1String("smc") << QLatin1String("QuickTime Graphics (SMC)"); codec << QLatin1String("snow") << QLatin1String("Snow"); codec << QLatin1String("svq1") << QLatin1String("Sorenson Vector Quantizer 1 / Sorenson Video 1 / SVQ1"); codec << QLatin1String("svq3") << QLatin1String("Sorenson Vector Quantizer 3 / Sorenson Video 3 / SVQ3"); codec << QLatin1String("theora") << QLatin1String("Theora"); codec << QLatin1String("vc1") << QLatin1String("SMPTE VC-1"); codec << QLatin1String("vc1image") << QLatin1String("Windows Media Video 9 Image v2"); codec << QLatin1String("vp3") << QLatin1String("On2 VP3"); codec << QLatin1String("vp5") << QLatin1String("On2 VP5"); codec << QLatin1String("vp6") << QLatin1String("On2 VP6"); codec << QLatin1String("vp6a") << QLatin1String("On2 VP6 (Flash version, with alpha channel)"); codec << QLatin1String("vp6f") << QLatin1String("On2 VP6 (Flash version)"); codec << QLatin1String("vp7") << QLatin1String("On2 VP7"); codec << QLatin1String("vp8") << QLatin1String("On2 VP8"); codec << QLatin1String("vp9") << QLatin1String("Google VP9"); codec << QLatin1String("wmv1") << QLatin1String("Windows Media Video 7"); codec << QLatin1String("wmv2") << QLatin1String("Windows Media Video 8"); codec << QLatin1String("wmv3") << QLatin1String("Windows Media Video 9"); codec << QLatin1String("wmv3image") << QLatin1String("Windows Media Video 9 Image"); // TODO: add more possible codec field->setChoice(codec); return field; } else if (name == QLatin1String("videoaudiobitrate")) { SearchFieldRangeInt* const field = new SearchFieldRangeInt(parent); field->setFieldName(name); field->setText(i18n("Audio Bit Rate"), i18n("Return Audio Bits Rate")); field->setBetweenText(i18nc("Find files with audio bit rate between...and...", "and")); field->setNumberPrefixAndSuffix(QString(), i18nc("Bits per Second", "bps")); field->setBoundary(1000, 100000, 1000); field->setSuggestedValues(QList() << 1000 << 4000 << 8000 << 12000 << 16000 << 20000 << 30000 << 40000 << 50000 << 60000 << 700000 << 800000 << 900000 << 100000 // TODO : adjust default values ); field->setSuggestedInitialValue(1000); field->setSingleSteps(1000, 1000); return field; } else if (name == QLatin1String("videoaudiochanneltype")) { SearchFieldChoice* const field = new SearchFieldChoice(parent); field->setFieldName(name); field->setText(i18n("Audio Channel Type"), i18n("Return Audio Channel Type")); QStringList type; type << QLatin1String("Mono") << i18n("Mono"); type << QLatin1String("Stereo") << i18n("Stereo"); type << QLatin1String("5.1") << i18n("5.1 Surround Sound"); type << QLatin1String("7.1") << i18n("7.1 Surround Sound"); type << QLatin1String("16 Channel") << i18n("16 Channels Sequence"); type << QLatin1String("Other") << i18n("Other Channel Type"); // TODO: add more possible audio channel type field->setChoice(type); return field; } else if (name == QLatin1String("videoaudioCodec")) { SearchFieldChoice* const field = new SearchFieldChoice(parent); field->setFieldName(name); field->setText(i18n("Audio Codec"), i18n("Return Audio Codec")); QStringList type; // List of most common audio codecs supported by FFMpeg (see "ffpmpeg -codecs" for details) // // FFMpeg codec name FFMpeg codec description type << QLatin1String("aac") << QLatin1String("AAC (Advanced Audio Coding)"); type << QLatin1String("aac_latm") << QLatin1String("AAC LATM (Advanced Audio Coding LATM syntax)"); type << QLatin1String("ac3") << QLatin1String("ATSC A/52A (AC-3)"); type << QLatin1String("adpcm_g722") << QLatin1String("G.722 ADPCM"); type << QLatin1String("adpcm_g726") << QLatin1String("G.726 ADPCM"); type << QLatin1String("adpcm_g726le") << QLatin1String("G.726 ADPCM little-endian"); type << QLatin1String("adpcm_ima_wav") << QLatin1String("ADPCM IMA WAV"); type << QLatin1String("adpcm_ima_qt") << QLatin1String("ADPCM IMA QuickTime"); type << QLatin1String("adpcm_swf") << QLatin1String("ADPCM Shockwave Flash"); type << QLatin1String("alac") << QLatin1String("ALAC (Apple Lossless Audio Codec)"); type << QLatin1String("amr_nb") << QLatin1String("AMR-NB (Adaptive Multi-Rate NarrowBand)"); type << QLatin1String("amr_wb") << QLatin1String("AMR-WB (Adaptive Multi-Rate WideBand)"); type << QLatin1String("ape") << QLatin1String("Monkey's Audio"); type << QLatin1String("atrac1") << QLatin1String("ATRAC1 (Adaptive TRansform Acoustic Coding)"); type << QLatin1String("atrac3") << QLatin1String("ATRAC3 (Adaptive TRansform Acoustic Coding 3)"); type << QLatin1String("atrac3al") << QLatin1String("ATRAC3 AL (Adaptive TRansform Acoustic Coding 3 Advanced Lossless)"); type << QLatin1String("atrac3p") << QLatin1String("ATRAC3+ (Adaptive TRansform Acoustic Coding 3+)"); type << QLatin1String("atrac3pal") << QLatin1String("ATRAC3+ AL (Adaptive TRansform Acoustic Coding 3+ Advanced Lossless)"); type << QLatin1String("celt") << QLatin1String("Constrained Energy Lapped Transform (CELT)"); type << QLatin1String("cook") << QLatin1String("Cook / Cooker / Gecko (RealAudio G2)"); type << QLatin1String("dts") << QLatin1String("DCA (DTS Coherent Acoustics)"); type << QLatin1String("eac3") << QLatin1String("ATSC A/52B (AC-3, E-AC-3)"); type << QLatin1String("flac") << QLatin1String("FLAC (Free Lossless Audio Codec)"); type << QLatin1String("g723_1") << QLatin1String("G.723.1"); type << QLatin1String("g729") << QLatin1String("G.729"); type << QLatin1String("mp1") << QLatin1String("MP1 (MPEG audio layer 1)"); type << QLatin1String("mp2") << QLatin1String("MP2 (MPEG audio layer 2)"); type << QLatin1String("mp3") << QLatin1String("MP3 (MPEG audio layer 3)"); type << QLatin1String("mp3adu") << QLatin1String("ADU (Application Data Unit) MP3 (MPEG audio layer 3)"); type << QLatin1String("mp3on4") << QLatin1String("MP3 on MP4"); type << QLatin1String("mp4als") << QLatin1String("MPEG-4 Audio Lossless Coding (ALS)"); type << QLatin1String("musepack7") << QLatin1String("Musepack SV7"); type << QLatin1String("musepack8") << QLatin1String("Musepack SV8"); type << QLatin1String("nellymoser") << QLatin1String("Nellymoser Asao"); type << QLatin1String("opus") << QLatin1String("Opus (Opus Interactive Audio Codec)"); type << QLatin1String("pcm_alaw") << QLatin1String("PCM A-law / G.711 A-law"); type << QLatin1String("pcm_bluray") << QLatin1String("PCM signed 16|20|24-bit big-endian for Blu-ray media"); type << QLatin1String("pcm_dvd") << QLatin1String("PCM signed 20|24-bit big-endian"); type << QLatin1String("pcm_f16le") << QLatin1String("PCM 16.8 floating point little-endian"); type << QLatin1String("pcm_f24le") << QLatin1String("PCM 24.0 floating point little-endian"); type << QLatin1String("pcm_f32be") << QLatin1String("PCM 32-bit floating point big-endian"); type << QLatin1String("pcm_f32le") << QLatin1String("PCM 32-bit floating point little-endian"); type << QLatin1String("pcm_f64be") << QLatin1String("PCM 64-bit floating point big-endian"); type << QLatin1String("pcm_f64le") << QLatin1String("PCM 64-bit floating point little-endian"); type << QLatin1String("pcm_lxf") << QLatin1String("PCM signed 20-bit little-endian planar"); type << QLatin1String("pcm_mulaw") << QLatin1String("PCM mu-law / G.711 mu-law"); type << QLatin1String("pcm_s16be") << QLatin1String("PCM signed 16-bit big-endian"); type << QLatin1String("pcm_s16be_planar") << QLatin1String("PCM signed 16-bit big-endian planar"); type << QLatin1String("pcm_s16le") << QLatin1String("PCM signed 16-bit little-endian"); type << QLatin1String("pcm_s16le_planar") << QLatin1String("PCM signed 16-bit little-endian planar"); type << QLatin1String("pcm_s24be") << QLatin1String("PCM signed 24-bit big-endian"); type << QLatin1String("pcm_s24daud") << QLatin1String("PCM D-Cinema audio signed 24-bit"); type << QLatin1String("pcm_s24le") << QLatin1String("PCM signed 24-bit little-endian"); type << QLatin1String("pcm_s24le_planar") << QLatin1String("PCM signed 24-bit little-endian planar"); type << QLatin1String("pcm_s32be") << QLatin1String("PCM signed 32-bit big-endian"); type << QLatin1String("pcm_s32le") << QLatin1String("PCM signed 32-bit little-endian"); type << QLatin1String("pcm_s32le_planar") << QLatin1String("PCM signed 32-bit little-endian planar"); type << QLatin1String("pcm_s64be") << QLatin1String("PCM signed 64-bit big-endian"); type << QLatin1String("pcm_s64le") << QLatin1String("PCM signed 64-bit little-endian"); type << QLatin1String("pcm_s8") << QLatin1String("PCM signed 8-bit"); type << QLatin1String("pcm_s8_planar") << QLatin1String("PCM signed 8-bit planar"); type << QLatin1String("pcm_u16be") << QLatin1String("PCM unsigned 16-bit big-endian"); type << QLatin1String("pcm_u16le") << QLatin1String("PCM unsigned 16-bit little-endian"); type << QLatin1String("pcm_u24be") << QLatin1String("PCM unsigned 24-bit big-endian"); type << QLatin1String("pcm_u24le") << QLatin1String("PCM unsigned 24-bit little-endian"); type << QLatin1String("pcm_u32be") << QLatin1String("PCM unsigned 32-bit big-endian"); type << QLatin1String("pcm_u32le") << QLatin1String("PCM unsigned 32-bit little-endian"); type << QLatin1String("pcm_u8") << QLatin1String("PCM unsigned 8-bit"); type << QLatin1String("pcm_zork") << QLatin1String("PCM Zork"); type << QLatin1String("ra_144") << QLatin1String("RealAudio 1.0 (14.4K)"); type << QLatin1String("ra_288") << QLatin1String("RealAudio 2.0 (28.8K)"); type << QLatin1String("ralf") << QLatin1String("RealAudio Lossless"); type << QLatin1String("sipr") << QLatin1String("RealAudio SIPR / ACELP.NET"); type << QLatin1String("speex") << QLatin1String("Speex"); type << QLatin1String("tak") << QLatin1String("TAK (Tom's lossless Audio Kompressor)"); type << QLatin1String("wavpack") << QLatin1String("WavPack"); type << QLatin1String("wmalossless") << QLatin1String("Windows Media Audio Lossless"); type << QLatin1String("wmapro") << QLatin1String("Windows Media Audio 9 Professional"); type << QLatin1String("wmav1") << QLatin1String("Windows Media Audio 1"); type << QLatin1String("wmav2") << QLatin1String("Windows Media Audio 2"); type << QLatin1String("wmavoice") << QLatin1String("Windows Media Audio Voice"); // TODO: add more possible audio Codec field->setChoice(type); return field; } else { qCWarning(DIGIKAM_GENERAL_LOG) << "SearchField::createField: cannot create SearchField for" << name; } return 0; } // ------------------------------------------------------------------------------------------- SearchField::SearchField(QObject* const parent) : QObject(parent) { m_label = new QLabel; m_detailLabel = new QLabel; m_clearButton = new AnimatedClearButton; m_categoryLabelVisible = true; m_valueIsValid = false; } void SearchField::setup(QGridLayout* const layout, int line) { if (line == -1) { line = layout->rowCount(); } // 10px indent layout->setColumnMinimumWidth(0, 10); // set stretch for the value widget columns layout->setColumnStretch(3, 1); layout->setColumnStretch(5, 1); // push value widgets to the left layout->setColumnStretch(6, 1); setupLabels(layout, line); // value widgets can use columns 3,4,5. // In the case of "from ... to ..." fields, column 3 and 5 can contain spin boxes etc., // and 4 can contain a label in between. // In other cases, a widget or sublayout spanning the three columns is recommended. setupValueWidgets(layout, line, 3); // setup the clear button that appears dynamically if (qApp->isLeftToRight()) { m_clearButton->setPixmap(QIcon::fromTheme(QLatin1String("edit-clear-locationbar-rtl")).pixmap(QApplication::style()->pixelMetric(QStyle::PM_SmallIconSize))); } else { m_clearButton->setPixmap(QIcon::fromTheme(QLatin1String("edit-clear-locationbar-ltr")).pixmap(QApplication::style()->pixelMetric(QStyle::PM_SmallIconSize))); } // Important: Don't cause re-layouting when button gets hidden/shown! m_clearButton->stayVisibleWhenAnimatedOut(true); m_clearButton->setToolTip(i18n("Reset contents")); connect(m_clearButton, SIGNAL(clicked()), this, SLOT(clearButtonClicked())); layout->addWidget(m_clearButton, line, 7); } void SearchField::setupLabels(QGridLayout* layout, int line) { m_label->setObjectName(QLatin1String("SearchField_MainLabel")); m_detailLabel->setObjectName(QLatin1String("SearchField_DetailLabel")); layout->addWidget(m_label, line, 1); layout->addWidget(m_detailLabel, line, 2); } void SearchField::setFieldName(const QString& fieldName) { m_name = fieldName; } void SearchField::setText(const QString& label, const QString& detailLabel) { m_label->setText(label); m_detailLabel->setText(detailLabel); } bool SearchField::supportsField(const QString& fieldName) { return m_name == fieldName; } void SearchField::setVisible(bool visible) { m_label->setVisible(visible && m_categoryLabelVisible); m_detailLabel->setVisible(visible); m_clearButton->setShallBeShown(visible); setValueWidgetsVisible(visible); } bool SearchField::isVisible() { // the detail label is considered representative for all widgets return m_detailLabel->isVisible(); } void SearchField::setCategoryLabelVisible(bool visible) { if (m_categoryLabelVisible == visible) { return; } m_categoryLabelVisible = visible; // update status: compare setVisible() and isVisible() m_label->setVisible(m_detailLabel->isVisible() && m_categoryLabelVisible); } void SearchField::setCategoryLabelVisibleFromPreviousField(SearchField* previousField) { if (previousField->m_label->text() == m_label->text()) { setCategoryLabelVisible(false); } else { setCategoryLabelVisible(true); } } QList SearchField::widgetRects(WidgetRectType type) const { QList rects; if (type == LabelAndValueWidgetRects) { rects << m_label->geometry(); rects << m_detailLabel->geometry(); } rects += valueWidgetRects(); return rects; } void SearchField::clearButtonClicked() { reset(); } void SearchField::setValidValueState(bool valueIsValid) { if (valueIsValid != m_valueIsValid) { m_valueIsValid = valueIsValid; // Note: setVisible visibility is independent from animateVisible visibility! m_clearButton->animateVisible(m_valueIsValid); } } // ------------------------------------------------------------------------- SearchFieldText::SearchFieldText(QObject* const parent) : SearchField(parent), m_edit(0) { } void SearchFieldText::setupValueWidgets(QGridLayout* layout, int row, int column) { m_edit = new QLineEdit; layout->addWidget(m_edit, row, column, 1, 3); connect(m_edit, SIGNAL(textChanged(QString)), this, SLOT(valueChanged(QString))); } void SearchFieldText::read(SearchXmlCachingReader& reader) { QString value = reader.value(); m_edit->setText(value); } void SearchFieldText::write(SearchXmlWriter& writer) { QString value = m_edit->text(); if (!value.isEmpty()) { writer.writeField(m_name, SearchXml::Like); writer.writeValue(value); writer.finishField(); } } void SearchFieldText::reset() { m_edit->setText(QString()); } void SearchFieldText::setValueWidgetsVisible(bool visible) { m_edit->setVisible(visible); } QList SearchFieldText::valueWidgetRects() const { QList rects; rects << m_edit->geometry(); return rects; } void SearchFieldText::valueChanged(const QString& text) { setValidValueState(!text.isEmpty()); } // ------------------------------------------------------------------------- SearchFieldKeyword::SearchFieldKeyword(QObject* const parent) : SearchFieldText(parent) { } void SearchFieldKeyword::read(SearchXmlCachingReader& reader) { QString keyword = reader.value(); m_edit->setText(KeywordSearch::merge(m_edit->text(), keyword)); } void SearchFieldKeyword::write(SearchXmlWriter& writer) { QStringList keywordList = KeywordSearch::split(m_edit->text()); foreach(const QString& keyword, keywordList) { if (!keyword.isEmpty()) { writer.writeField(m_name, SearchXml::Like); writer.writeValue(keyword); writer.finishField(); } } } // ------------------------------------------------------------------------- SearchFieldRangeDate::SearchFieldRangeDate(QObject* const parent, Type type) : SearchField(parent), m_firstTimeEdit(0), m_firstDateEdit(0), m_secondTimeEdit(0), m_secondDateEdit(0), m_type(type) { m_betweenLabel = new QLabel; } void SearchFieldRangeDate::setupValueWidgets(QGridLayout* layout, int row, int column) { // QHBoxLayout *hbox = new QHBoxLayout; // layout->addLayout(hbox, row, column, 1, 3); m_firstDateEdit = new DDateEdit; m_secondDateEdit = new DDateEdit; if (m_type == DateOnly) { layout->addWidget(m_firstDateEdit, row, column); layout->addWidget(m_betweenLabel, row, column + 1, Qt::AlignHCenter); layout->addWidget(m_secondDateEdit, row, column + 2); } else { QHBoxLayout* const hbox1 = new QHBoxLayout; QHBoxLayout* const hbox2 = new QHBoxLayout; m_firstTimeEdit = new QTimeEdit; m_secondTimeEdit = new QTimeEdit; hbox1->addWidget(m_firstDateEdit); hbox1->addWidget(m_firstTimeEdit); hbox2->addWidget(m_secondDateEdit); hbox2->addWidget(m_secondTimeEdit); layout->addLayout(hbox1, row, column); layout->addWidget(m_betweenLabel, row, column + 1, Qt::AlignHCenter); layout->addLayout(hbox2, row, column + 2); } connect(m_firstDateEdit, SIGNAL(dateChanged(QDate)), this, SLOT(valueChanged())); connect(m_secondDateEdit, SIGNAL(dateChanged(QDate)), this, SLOT(valueChanged())); } void SearchFieldRangeDate::setBetweenText(const QString& between) { m_betweenLabel->setText(between); } void SearchFieldRangeDate::read(SearchXmlCachingReader& reader) { SearchXml::Relation relation = reader.fieldRelation(); if (relation == SearchXml::Interval || relation == SearchXml::IntervalOpen) { QList dates = reader.valueToDateTimeList(); if (dates.size() != 2) { return; } if (m_type == DateTime) { m_firstDateEdit->setDate(dates.first().date()); m_firstTimeEdit->setTime(dates.first().time()); m_secondDateEdit->setDate(dates.last().date()); m_secondTimeEdit->setTime(dates.last().time()); } else { if (relation == SearchXml::Interval) { dates.last() = dates.last().addDays(-1); } m_firstDateEdit->setDate(dates.first().date()); m_secondDateEdit->setDate(dates.last().date()); } } else { QDateTime dt = reader.valueToDateTime(); if (m_type == DateTime) { if (relation == SearchXml::Equal) { m_firstDateEdit->setDate(dt.date()); m_firstTimeEdit->setTime(dt.time()); m_secondDateEdit->setDate(dt.date()); m_secondTimeEdit->setTime(dt.time()); } else if (relation == SearchXml::GreaterThanOrEqual || relation == SearchXml::GreaterThan) { m_firstDateEdit->setDate(dt.date()); m_firstTimeEdit->setTime(dt.time()); } { m_secondDateEdit->setDate(dt.date()); m_secondTimeEdit->setTime(dt.time()); } } else { // In DateOnly mode, we always assume dealing with the beginning of the day, QTime(0,0,0). // In the UI, we show the date only (including the whole day). // The difference between ...Than and ...ThanOrEqual is only one second, ignored. if (relation == SearchXml::Equal) { m_firstDateEdit->setDate(dt.date()); m_secondDateEdit->setDate(dt.date()); } else if (relation == SearchXml::GreaterThanOrEqual || relation == SearchXml::GreaterThan) { m_firstDateEdit->setDate(dt.date()); } else if (relation == SearchXml::LessThanOrEqual || relation == SearchXml::LessThan) { dt = dt.addDays(-1); m_secondDateEdit->setDate(dt.date()); } } } valueChanged(); } void SearchFieldRangeDate::write(SearchXmlWriter& writer) { if (m_firstDateEdit->date().isValid() && m_secondDateEdit->date().isValid()) { QDateTime firstDate(m_firstDateEdit->date()); if (m_type == DateTime) { firstDate.setTime(m_firstTimeEdit->time()); } QDateTime secondDate(m_secondDateEdit->date()); if (m_type == DateTime) { secondDate.setTime(m_secondTimeEdit->time()); } if (firstDate == secondDate) { writer.writeField(m_name, SearchXml::Equal); writer.writeValue(firstDate); writer.finishField(); } else { if (m_type == DateOnly) { secondDate = secondDate.addDays(1); } writer.writeField(m_name, SearchXml::Interval); writer.writeValue(QList() << firstDate << secondDate); writer.finishField(); } } else { QDate date = m_firstDateEdit->date(); if (date.isValid()) { writer.writeField(m_name, SearchXml::GreaterThanOrEqual); QDateTime dt(date); if (m_type == DateTime) { dt.setTime(m_firstTimeEdit->time()); } writer.writeValue(dt); writer.finishField(); } date = m_secondDateEdit->date(); if (date.isValid()) { writer.writeField(m_name, SearchXml::LessThan); QDateTime dt(date); if (m_type == DateTime) { dt.setTime(m_secondTimeEdit->time()); } else { dt = dt.addDays(1); // include whole day } writer.writeValue(dt); writer.finishField(); } } } void SearchFieldRangeDate::reset() { m_firstDateEdit->setDate(QDate()); if (m_type == DateTime) { m_firstTimeEdit->setTime(QTime(0, 0, 0, 0)); } m_secondDateEdit->setDate(QDate()); if (m_type == DateTime) { m_secondTimeEdit->setTime(QTime(0, 0, 0, 0)); } valueChanged(); } void SearchFieldRangeDate::setBoundary(const QDateTime& min, const QDateTime& max) { //something here? Q_UNUSED(min); Q_UNUSED(max); } void SearchFieldRangeDate::setValueWidgetsVisible(bool visible) { m_firstDateEdit->setVisible(visible); if (m_firstTimeEdit) { m_firstTimeEdit->setVisible(visible); } m_secondDateEdit->setVisible(visible); if (m_secondTimeEdit) { m_secondTimeEdit->setVisible(visible); } m_betweenLabel->setVisible(visible); } QList SearchFieldRangeDate::valueWidgetRects() const { QList rects; rects << m_firstDateEdit->geometry(); if (m_firstTimeEdit) { rects << m_firstTimeEdit->geometry(); } rects << m_secondDateEdit->geometry(); if (m_secondTimeEdit) { rects << m_secondTimeEdit->geometry(); } return rects; } void SearchFieldRangeDate::valueChanged() { setValidValueState(m_firstDateEdit->date().isValid() || m_secondDateEdit->date().isValid()); } // ------------------------------------------------------------------------- SearchFieldRangeInt::SearchFieldRangeInt(QObject* const parent) : SearchField(parent), m_min(0), m_max(100), m_reciprocal(false), m_firstBox(0), m_secondBox(0) { m_betweenLabel = new QLabel; m_firstBox = new CustomStepsIntSpinBox; m_secondBox = new CustomStepsIntSpinBox; } void SearchFieldRangeInt::setupValueWidgets(QGridLayout* layout, int row, int column) { // QHBoxLayout *hbox = new QHBoxLayout; // layout->addLayout(hbox, row, column); m_firstBox->setSpecialValueText(QLatin1String(" ")); m_secondBox->setSpecialValueText(QLatin1String(" ")); // hbox->addWidget(m_firstBox); // hbox->addWidget(m_betweenLabel); // hbox->addWidget(m_secondBox); // hbox->addStretch(1); layout->addWidget(m_firstBox, row, column); layout->addWidget(m_betweenLabel, row, column + 1, Qt::AlignHCenter); layout->addWidget(m_secondBox, row, column + 2); connect(m_firstBox, SIGNAL(valueChanged(int)), this, SLOT(valueChanged())); connect(m_secondBox, SIGNAL(valueChanged(int)), this, SLOT(valueChanged())); } void SearchFieldRangeInt::read(SearchXmlCachingReader& reader) { SearchXml::Relation relation = reader.fieldRelation(); if (m_reciprocal) { switch (relation) { case SearchXml::LessThanOrEqual: case SearchXml::LessThan: m_firstBox->setFractionMagicValue(reader.valueToDouble()); break; case SearchXml::GreaterThanOrEqual: case SearchXml::GreaterThan: m_secondBox->setFractionMagicValue(reader.valueToDouble()); break; case SearchXml::Equal: m_firstBox->setFractionMagicValue(reader.valueToDouble()); m_secondBox->setFractionMagicValue(reader.valueToDouble()); break; case SearchXml::Interval: case SearchXml::IntervalOpen: { QList list = reader.valueToDoubleList(); if (list.size() != 2) { return; } m_secondBox->setFractionMagicValue(list.first()); m_firstBox->setFractionMagicValue(list.last()); break; } default: break; } } else { switch (relation) { case SearchXml::GreaterThanOrEqual: m_firstBox->setValue(reader.valueToInt()); break; case SearchXml::GreaterThan: m_firstBox->setValue(reader.valueToInt() - 1); break; case SearchXml::LessThanOrEqual: m_secondBox->setValue(reader.valueToInt()); break; case SearchXml::LessThan: m_secondBox->setValue(reader.valueToInt() + 1); break; case SearchXml::Equal: m_firstBox->setValue(reader.valueToInt()); m_secondBox->setValue(reader.valueToInt()); break; case SearchXml::Interval: case SearchXml::IntervalOpen: { QList list = reader.valueToIntList(); if (list.size() != 2) { return; } m_firstBox->setValue(list.first()); m_secondBox->setValue(list.last()); break; } default: break; } } } void SearchFieldRangeInt::write(SearchXmlWriter& writer) { if (m_firstBox->value() != m_firstBox->minimum() && m_secondBox->value() != m_secondBox->minimum()) { if (m_firstBox->value() != m_secondBox->value()) { writer.writeField(m_name, SearchXml::Interval); if (m_reciprocal) { writer.writeValue(QList() << m_secondBox->fractionMagicValue() << m_firstBox->fractionMagicValue()); } else { writer.writeValue(QList() << m_firstBox->value() << m_secondBox->value()); } writer.finishField(); } else { /** * @todo : This condition is never met. * Right value is either displayed empty (minimum, greater than left) * or one step larger than left */ writer.writeField(m_name, SearchXml::Equal); if (m_reciprocal) { writer.writeValue(m_firstBox->fractionMagicValue()); } else { writer.writeValue(m_firstBox->value()); } writer.finishField(); } } else { if (m_firstBox->value() != m_firstBox->minimum()) { if (m_reciprocal) { writer.writeField(m_name, SearchXml::LessThanOrEqual); writer.writeValue(m_firstBox->fractionMagicValue()); } else { writer.writeField(m_name, SearchXml::GreaterThanOrEqual); writer.writeValue(m_firstBox->value()); } writer.finishField(); } if (m_secondBox->value() != m_secondBox->minimum()) { if (m_reciprocal) { writer.writeField(m_name, SearchXml::GreaterThanOrEqual); writer.writeValue(m_secondBox->fractionMagicValue()); } else { writer.writeField(m_name, SearchXml::LessThanOrEqual); writer.writeValue(m_secondBox->value()); } writer.finishField(); } } } void SearchFieldRangeInt::setBetweenText(const QString& text) { m_betweenLabel->setText(text); } void SearchFieldRangeInt::setNumberPrefixAndSuffix(const QString& prefix, const QString& suffix) { m_firstBox->setPrefix(prefix); m_secondBox->setPrefix(prefix); m_firstBox->setSuffix(suffix); m_secondBox->setSuffix(suffix); } void SearchFieldRangeInt::setBoundary(int min, int max, int step) { if (m_reciprocal) { m_min = max; m_max = min; } else { m_min = min; m_max = max; } m_firstBox->setRange(m_min, m_max); m_firstBox->setSingleStep(step); m_firstBox->setValue(m_min); m_secondBox->setRange(m_min, m_max); m_secondBox->setSingleStep(step); m_secondBox->setValue(m_min); } void SearchFieldRangeInt::enableFractionMagic(const QString& prefix) { m_reciprocal = true; m_firstBox->enableFractionMagic(prefix); m_firstBox->setInvertStepping(true); m_secondBox->enableFractionMagic(prefix); m_secondBox->setInvertStepping(true); } void SearchFieldRangeInt::setSuggestedValues(const QList& values) { m_firstBox->setSuggestedValues(values); m_secondBox->setSuggestedValues(values); } void SearchFieldRangeInt::setSuggestedInitialValue(int value) { m_firstBox->setSuggestedInitialValue(value); m_secondBox->setSuggestedInitialValue(value); } void SearchFieldRangeInt::setSingleSteps(int smaller, int larger) { m_firstBox->setSingleSteps(smaller, larger); m_secondBox->setSingleSteps(smaller, larger); } void SearchFieldRangeInt::setInvertStepping(bool invert) { m_firstBox->setInvertStepping(invert); m_secondBox->setInvertStepping(invert); } void SearchFieldRangeInt::valueChanged() { bool validValue = false; if (m_reciprocal) { bool firstAtMinimum = (m_firstBox->value() == m_firstBox->minimum()); bool secondAtMinimum = (m_secondBox->value() == m_secondBox->minimum()); if (!secondAtMinimum) { m_firstBox->setRange(m_secondBox->value() - 1, m_max); validValue = true; } if (!firstAtMinimum) { m_secondBox->setRange(m_min - 1, m_firstBox->value()); if (secondAtMinimum) { m_firstBox->setRange(m_min, m_max); m_secondBox->setValue(m_secondBox->minimum()); } validValue = true; } if (firstAtMinimum && secondAtMinimum) { m_firstBox->setRange(m_min, m_max); m_secondBox->setRange(m_min, m_max); } } else { bool firstAtMinimum = (m_firstBox->value() == m_firstBox->minimum()); bool secondAtMinimum = (m_secondBox->value() == m_secondBox->minimum()); if (!secondAtMinimum) { m_firstBox->setRange(m_min, m_secondBox->value()); validValue = true; } if (!firstAtMinimum) { m_secondBox->setRange(m_firstBox->value() - 1, m_max); if (secondAtMinimum) { m_firstBox->setRange(m_min, m_max); m_secondBox->setValue(m_secondBox->minimum()); } validValue = true; } if (firstAtMinimum && secondAtMinimum) { m_firstBox->setRange(m_min, m_max); m_secondBox->setRange(m_min, m_max); } } setValidValueState(validValue); } void SearchFieldRangeInt::reset() { m_firstBox->setRange(m_min, m_max); m_secondBox->setRange(m_min, m_max); m_firstBox->reset(); m_secondBox->reset(); } void SearchFieldRangeInt::setValueWidgetsVisible(bool visible) { m_firstBox->setVisible(visible); m_secondBox->setVisible(visible); m_betweenLabel->setVisible(visible); } QList SearchFieldRangeInt::valueWidgetRects() const { QList rects; rects << m_firstBox->geometry(); rects << m_secondBox->geometry(); return rects; } // ------------------------------------------------------------------------- SearchFieldRangeDouble::SearchFieldRangeDouble(QObject* const parent) : SearchField(parent), m_min(0), m_max(100), m_factor(1), m_firstBox(0), m_secondBox(0) { m_betweenLabel = new QLabel; m_firstBox = new CustomStepsDoubleSpinBox; m_secondBox = new CustomStepsDoubleSpinBox; } void SearchFieldRangeDouble::setupValueWidgets(QGridLayout* layout, int row, int column) { // QHBoxLayout *hbox = new QHBoxLayout; // layout->addLayout(hbox, row, column); m_firstBox->setSpecialValueText(QLatin1String(" ")); m_secondBox->setSpecialValueText(QLatin1String(" ")); /* hbox->addWidget(m_firstBox); hbox->addWidget(m_betweenLabel); hbox->addWidget(m_secondBox); hbox->addStretch(1);*/ layout->addWidget(m_firstBox, row, column); layout->addWidget(m_betweenLabel, row, column + 1, Qt::AlignHCenter); layout->addWidget(m_secondBox, row, column + 2); connect(m_firstBox, SIGNAL(valueChanged(double)), this, SLOT(valueChanged())); connect(m_secondBox, SIGNAL(valueChanged(double)), this, SLOT(valueChanged())); } void SearchFieldRangeDouble::read(SearchXmlCachingReader& reader) { SearchXml::Relation relation = reader.fieldRelation(); if (relation == SearchXml::GreaterThanOrEqual || relation == SearchXml::GreaterThan) { m_firstBox->setValue(reader.valueToDouble() / m_factor); } else if (relation == SearchXml::LessThanOrEqual || relation == SearchXml::LessThan) { m_secondBox->setValue(reader.valueToDouble() / m_factor); } else if (relation == SearchXml::Interval || relation == SearchXml::IntervalOpen) { QList list = reader.valueToDoubleList(); if (list.size() != 2) { return; } m_firstBox->setValue(list.first() / m_factor); m_secondBox->setValue(list.last() / m_factor); } } void SearchFieldRangeDouble::write(SearchXmlWriter& writer) { if (m_firstBox->value() != m_firstBox->minimum() && m_secondBox->value() != m_secondBox->minimum()) { if (m_firstBox->value() != m_secondBox->value()) { writer.writeField(m_name, SearchXml::Interval); writer.writeValue(QList() << (m_firstBox->value() * m_factor) << (m_secondBox->value() * m_factor)); writer.finishField(); } else { //TODO: See SearchFieldRangeInt writer.writeField(m_name, SearchXml::Equal); writer.writeValue(m_firstBox->value() * m_factor); writer.finishField(); } } else { if (m_firstBox->value() != m_firstBox->minimum()) { writer.writeField(m_name, SearchXml::GreaterThanOrEqual); writer.writeValue(m_firstBox->value() * m_factor); writer.finishField(); } if (m_secondBox->value() != m_secondBox->minimum()) { writer.writeField(m_name, SearchXml::LessThanOrEqual); writer.writeValue(m_secondBox->value() * m_factor); writer.finishField(); } } } void SearchFieldRangeDouble::setBetweenText(const QString& text) { m_betweenLabel->setText(text); } void SearchFieldRangeDouble::setNoValueText(const QString& text) { m_firstBox->setSpecialValueText(text); m_secondBox->setSpecialValueText(text); } void SearchFieldRangeDouble::setNumberPrefixAndSuffix(const QString& prefix, const QString& suffix) { m_firstBox->setPrefix(prefix); m_secondBox->setPrefix(prefix); m_firstBox->setSuffix(suffix); m_secondBox->setSuffix(suffix); } void SearchFieldRangeDouble::setBoundary(double min, double max, int decimals, double step) { m_min = min; m_max = max; m_firstBox->setRange(min, max); m_firstBox->setSingleStep(step); m_firstBox->setDecimals(decimals); m_firstBox->setValue(min); m_secondBox->setRange(min, max); m_secondBox->setSingleStep(step); m_secondBox->setDecimals(decimals); m_secondBox->setValue(min); } void SearchFieldRangeDouble::setFactor(double factor) { m_factor = factor; } void SearchFieldRangeDouble::setSuggestedValues(const QList& values) { m_firstBox->setSuggestedValues(values); m_secondBox->setSuggestedValues(values); } void SearchFieldRangeDouble::setSuggestedInitialValue(double value) { m_firstBox->setSuggestedInitialValue(value); m_secondBox->setSuggestedInitialValue(value); } void SearchFieldRangeDouble::setSingleSteps(double smaller, double larger) { m_firstBox->setSingleSteps(smaller, larger); m_secondBox->setSingleSteps(smaller, larger); } void SearchFieldRangeDouble::setInvertStepping(bool invert) { m_firstBox->setInvertStepping(invert); m_secondBox->setInvertStepping(invert); } void SearchFieldRangeDouble::valueChanged() { bool validValue = false; bool firstAtMinimum = (m_firstBox->value() == m_firstBox->minimum()); bool secondAtMinimum = (m_secondBox->value() == m_secondBox->minimum()); if (!secondAtMinimum) { m_firstBox->setRange(m_min, m_secondBox->value()); validValue = true; } if (!firstAtMinimum) { m_secondBox->setRange(m_firstBox->value() - 0.1, m_max); if (secondAtMinimum) { m_firstBox->setRange(m_min, m_max); m_secondBox->setValue(m_secondBox->minimum()); } validValue = true; } if (firstAtMinimum && secondAtMinimum) { m_firstBox->setRange(m_min, m_max); m_secondBox->setRange(m_min, m_max); } setValidValueState(validValue); } void SearchFieldRangeDouble::reset() { m_firstBox->setRange(m_min, m_max); m_secondBox->setRange(m_min, m_max); m_firstBox->reset(); m_secondBox->reset(); } void SearchFieldRangeDouble::setValueWidgetsVisible(bool visible) { m_firstBox->setVisible(visible); m_secondBox->setVisible(visible); m_betweenLabel->setVisible(visible); } QList SearchFieldRangeDouble::valueWidgetRects() const { QList rects; rects << m_firstBox->geometry(); rects << m_secondBox->geometry(); return rects; } // ------------------------------------------------------------------------- SearchFieldChoice::SearchFieldChoice(QObject* const parent) : SearchField(parent), m_comboBox(0), m_type(QVariant::Invalid) { m_model = new ChoiceSearchModel(this); m_anyText = i18n("Any"); } void SearchFieldChoice::setupValueWidgets(QGridLayout* layout, int row, int column) { m_comboBox = new ChoiceSearchComboBox; m_comboBox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); connect(m_model, SIGNAL(checkStateChanged(QVariant,bool)), this, SLOT(checkStateChanged())); m_comboBox->setModel(m_model); // set object name for style sheet m_comboBox->setObjectName(QLatin1String("SearchFieldChoice_ComboBox")); // label is created only after setting the model m_comboBox->label()->setObjectName(QLatin1String("SearchFieldChoice_ClickLabel")); updateComboText(); layout->addWidget(m_comboBox, row, column, 1, 3); } void SearchFieldChoice::setChoice(const QMap& map) { m_type = QVariant::Int; m_model->setChoice(map); } void SearchFieldChoice::setChoice(const QStringList& choice) { m_type = QVariant::String; m_model->setChoice(choice); } void SearchFieldChoice::setAnyText(const QString& anyText) { m_anyText = anyText; } void SearchFieldChoice::checkStateChanged() { updateComboText(); } void SearchFieldChoice::updateComboText() { QStringList checkedChoices = m_model->checkedDisplayTexts(); if (checkedChoices.isEmpty()) { m_comboBox->setLabelText(m_anyText); setValidValueState(false); } else if (checkedChoices.count() == 1) { m_comboBox->setLabelText(checkedChoices.first()); setValidValueState(true); } else { m_comboBox->setLabelText(i18n("Any of: %1", checkedChoices.join(QLatin1String(", ")))); setValidValueState(true); } } void SearchFieldChoice::read(SearchXmlCachingReader& reader) { SearchXml::Relation relation = reader.fieldRelation(); QList values; if (relation == SearchXml::OneOf) { if (m_type == QVariant::Int) { m_model->setChecked(reader.valueToIntList()); } else if (m_type == QVariant::String) { m_model->setChecked(reader.valueToStringList()); } } else { if (m_type == QVariant::Int) { m_model->setChecked(reader.valueToInt(), relation); } else if (m_type == QVariant::String) { // The testRelation magic only really makes sense for integers. "Like" is not implemented. //m_model->setChecked(reader.value(), relation); m_model->setChecked(reader.value()); } } } void SearchFieldChoice::write(SearchXmlWriter& writer) { if (m_type == QVariant::Int) { QList v = m_model->checkedKeys(); if (!v.isEmpty()) { if (v.size() == 1) { writer.writeField(m_name, SearchXml::Equal); writer.writeValue(v.first()); writer.finishField(); } else { writer.writeField(m_name, SearchXml::OneOf); writer.writeValue(v); writer.finishField(); } } } else if (m_type == QVariant::String) { QList v = m_model->checkedKeys(); if (!v.isEmpty()) { if (v.size() == 1) { // For choice string fields, we have the possibility to specify the wildcard // position with the position of *. if (v.first().contains(QLatin1Char('*'))) { writer.writeField(m_name, SearchXml::Like); } else { writer.writeField(m_name, SearchXml::Equal); } writer.writeValue(v.first()); writer.finishField(); } else { // OneOf handles wildcards automatically writer.writeField(m_name, SearchXml::OneOf); writer.writeValue(v); writer.finishField(); } } } } void SearchFieldChoice::reset() { m_model->resetChecked(); } void SearchFieldChoice::setValueWidgetsVisible(bool visible) { m_comboBox->setVisible(visible); } QList SearchFieldChoice::valueWidgetRects() const { QList rects; rects << m_comboBox->geometry(); return rects; } /* class Q_DECL_HIDDEN SearchFieldChoice : public SearchField { // Note: Someone added this space on purpose (Marcel?) // It seems that automoc4 is not recognizing this macro to be in a comment // block and therefore will fail when parsing this file. Adding a space to the // macro name will fix this issue. When uncommenting this block again, make sure to // fix the macro name of course. Q_ OBJECT public: SearchFieldChoice(SearchFieldGroup *parent); virtual void read(SearchXmlCachingReader &reader); virtual void write(SearchXmlWriter &writer); virtual void reset(); void setChoice(const QMap &map); void setAnyText(const QString& string); virtual void setupValueWidgets(QGridLayout *layout, int row, int column); virtual void setValueWidgetsVisible(bool visible); protected Q_SLOTS: void slotClicked(); void slotUpdateLabel(); protected: void setValues(const QList &values); void setValues(int value, SearchXml::Relation relation); QList values() const; QString valueText() const; virtual void setupChoiceWidgets(); protected: QString m_anyText; SqueezedClickLabel *m_label; QVBoxLayout *m_vbox; QMap m_choiceMap; QMap m_widgetMap; VisibilityController *m_controller; }; SearchFieldChoice::SearchFieldChoice(SearchFieldGroup *parent) : SearchField(parent), m_vbox(0) { m_anyText = i18n("Any"); m_label = new SqueezedClickLabel; m_label->setObjectName(QLatin1String("SearchFieldChoice_ClickLabel")); m_controller = new VisibilityController(this); m_controller->setContainerWidget(parent); } void SearchFieldChoice::setupValueWidgets(QGridLayout *layout, int row, int column) { m_vbox = new QVBoxLayout; layout->addLayout(m_vbox, row, column, 1, 3); m_label->setElideMode(Qt::ElideRight); m_vbox->addWidget(m_label); connect(m_label, SIGNAL(activated()), this, SLOT(slotClicked())); setupChoiceWidgets(); slotUpdateLabel(); } void SearchFieldChoice::slotClicked() { m_controller->triggerVisibility(); } void SearchFieldChoice::slotUpdateLabel() { QString text = valueText(); if (text.isNull()) text = m_anyText; m_label->setText(text); } void SearchFieldChoice::setValueWidgetsVisible(bool visible) { m_label->setVisible(visible); if (!visible) m_controller->hide(); } void SearchFieldChoice::setupChoiceWidgets() { QGroupBox *groupbox = new QGroupBox; m_vbox->addWidget(groupbox); m_controller->addWidget(groupbox); QVBoxLayout *vbox = new QVBoxLayout; QMap::const_iterator it; for (it = m_choiceMap.begin(); it != m_choiceMap.end(); ++it) { QCheckBox *box = new QCheckBox; box->setText(it.value()); vbox->addWidget(box); m_controller->addWidget(box); m_widgetMap[box] = it.key(); connect(box, SIGNAL(stateChanged(int)), this, SLOT(slotUpdateLabel())); } groupbox->setLayout(vbox); } QString SearchFieldChoice::valueText() const { QStringList list; QMap::const_iterator it; for (it = m_widgetMap.begin(); it != m_widgetMap.end(); ++it) { if (it.key()->isChecked()) list << it.key()->text(); } if (list.isEmpty()) return QString(); else if (list.size() == 1) { return list.first(); } else { return i18n("Either of: %1", list.join(", ")); } } void SearchFieldChoice::read(SearchXmlCachingReader& reader) { SearchXml::Relation relation = reader.fieldRelation(); QList values; if (relation == SearchXml::OneOf) { setValues(reader.valueToIntList()); } else { setValues(reader.valueToInt(), relation); } } void SearchFieldChoice::write(SearchXmlWriter& writer) { QList v = values(); if (!v.isEmpty()) { if (v.size() == 1) { writer.writeField(m_name, SearchXml::Equal); writer.writeValue(v.first()); writer.finishField(); } else { writer.writeField(m_name, SearchXml::OneOf); writer.writeValue(v); writer.finishField(); } } } void SearchFieldChoice::reset() { setValues(QList()); } void SearchFieldChoice::setChoice(const QMap& map) { m_choiceMap = map; } void SearchFieldChoice::setValues(const QList& values) { QMap::const_iterator it; for (it = m_widgetMap.begin(); it != m_widgetMap.end(); ++it) { it.key()->setChecked(values.contains(it.value())); } } void SearchFieldChoice::setValues(int value, SearchXml::Relation relation) { QMap::const_iterator it; for (it = m_widgetMap.begin(); it != m_widgetMap.end(); ++it) { it.key()->setChecked(SearchXml::testRelation(it.value(), value, relation)); } } QList SearchFieldChoice::values() const { QList list; QMap::const_iterator it; for (it = m_widgetMap.begin(); it != m_widgetMap.end(); ++it) { if (it.key()->isChecked()) list << it.value(); } return list; } */ // ------------------------------------------------------------------------- SearchFieldAlbum::SearchFieldAlbum(QObject* const parent, Type type) : SearchField(parent), m_wrapperBox(0), m_albumComboBox(0), m_tagComboBox(0), m_operation(0), m_type(type), m_model(0) { } void SearchFieldAlbum::setupValueWidgets(QGridLayout* layout, int row, int column) { if (m_type == TypeAlbum) { m_albumComboBox = new AlbumTreeViewSelectComboBox; m_wrapperBox = m_albumComboBox; m_albumComboBox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); m_albumComboBox->setDefaultModel(); m_albumComboBox->setNoSelectionText(i18n("Any Album")); m_albumComboBox->addCheckUncheckContextMenuActions(); m_model = m_albumComboBox->model(); layout->addWidget(m_wrapperBox, row, column, 1, 3); } else if (m_type == TypeTag) { m_wrapperBox = new DHBox(0); m_tagComboBox = new TagTreeViewSelectComboBox(m_wrapperBox); m_operation = new SqueezedComboBox(m_wrapperBox); m_operation->addSqueezedItem(i18nc("@label:listbox", "In All"), Operation::All); m_operation->addSqueezedItem(i18nc("@label:listbox", "In One of"), Operation::OneOf); m_tagComboBox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); m_tagComboBox->setDefaultModel(); m_tagComboBox->setNoSelectionText(i18n("Any Tag")); m_tagComboBox->addCheckUncheckContextMenuActions(); m_model = m_tagComboBox->model(); layout->addWidget(m_wrapperBox, row, column, 1, 3); } connect(m_model, SIGNAL(checkStateChanged(Album*,Qt::CheckState)), this, SLOT(updateState())); updateState(); } void SearchFieldAlbum::updateState() { setValidValueState(!m_model->checkedAlbums().isEmpty()); } void SearchFieldAlbum::read(SearchXmlCachingReader& reader) { QList ids = reader.valueToIntOrIntList(); Album* a = 0; if (m_type == TypeAlbum) { foreach(int id, ids) { a = AlbumManager::instance()->findPAlbum(id); if (!a) { qCDebug(DIGIKAM_GENERAL_LOG) << "Search: Did not find album for ID" << id << "given in Search XML"; return; } m_model->setChecked(a, true); } } else if (m_type == TypeTag) { if (reader.fieldRelation() == SearchXml::AllOf) { m_operation->setCurrentIndex(Operation::All); } else { m_operation->setCurrentIndex(Operation::OneOf); } foreach(int id, ids) { a = AlbumManager::instance()->findTAlbum(id); // Ignore internal tags here. if (a && TagsCache::instance()->isInternalTag(a->id())) { a = 0; } if (!a) { qCDebug(DIGIKAM_GENERAL_LOG) << "Search: Did not find album for ID" << id << "given in Search XML"; return; } m_model->setChecked(a, true); } } } void SearchFieldAlbum::write(SearchXmlWriter& writer) { AlbumList checkedAlbums = m_model->checkedAlbums(); if (checkedAlbums.isEmpty()) { return; } QList albumIds; foreach(Album* const album, checkedAlbums) { albumIds << album->id(); } SearchXml::Relation relation = SearchXml::OneOf; if (m_operation) { if (m_operation->itemData(m_operation->currentIndex()).toInt() == Operation::All) { relation = SearchXml::AllOf; } } if (albumIds.size() > 1) { writer.writeField(m_name, relation); writer.writeValue(albumIds); } else { writer.writeField(m_name, SearchXml::Equal); writer.writeValue(albumIds.first()); } writer.finishField(); } void SearchFieldAlbum::reset() { m_model->resetCheckedAlbums(); } void SearchFieldAlbum::setValueWidgetsVisible(bool visible) { m_wrapperBox->setVisible(visible); } QList SearchFieldAlbum::valueWidgetRects() const { QList rects; rects << m_wrapperBox->geometry(); return rects; } // ------------------------------------------------------------------------- SearchFieldRating::SearchFieldRating(QObject* const parent) : SearchField(parent) { m_betweenLabel = new QLabel; m_firstBox = new RatingComboBox; m_secondBox = new RatingComboBox; } void SearchFieldRating::setupValueWidgets(QGridLayout* layout, int row, int column) { layout->addWidget(m_firstBox, row, column); layout->addWidget(m_betweenLabel, row, column + 1, Qt::AlignHCenter); layout->addWidget(m_secondBox, row, column + 2); connect(m_firstBox, SIGNAL(ratingValueChanged(int)), this, SLOT(firstValueChanged())); connect(m_secondBox, SIGNAL(ratingValueChanged(int)), this, SLOT(secondValueChanged())); } void SearchFieldRating::read(SearchXmlCachingReader& reader) { SearchXml::Relation relation = reader.fieldRelation(); switch (relation) { case SearchXml::GreaterThanOrEqual: m_firstBox->setRatingValue((RatingComboBox::RatingValue)reader.valueToInt()); break; case SearchXml::GreaterThan: m_firstBox->setRatingValue((RatingComboBox::RatingValue)(reader.valueToInt() - 1)); break; case SearchXml::LessThanOrEqual: m_secondBox->setRatingValue((RatingComboBox::RatingValue)reader.valueToInt()); break; case SearchXml::LessThan: m_secondBox->setRatingValue((RatingComboBox::RatingValue)(reader.valueToInt() + 1)); break; case SearchXml::Equal: m_firstBox->setRatingValue((RatingComboBox::RatingValue)reader.valueToInt()); m_secondBox->setRatingValue((RatingComboBox::RatingValue)reader.valueToInt()); break; case SearchXml::Interval: case SearchXml::IntervalOpen: { QList list = reader.valueToIntList(); if (list.size() != 2) { return; } m_firstBox->setRatingValue((RatingComboBox::RatingValue)list.first()); m_secondBox->setRatingValue((RatingComboBox::RatingValue)list.last()); break; } default: break; } } void SearchFieldRating::write(SearchXmlWriter& writer) { RatingComboBox::RatingValue first = m_firstBox->ratingValue(); RatingComboBox::RatingValue second = m_secondBox->ratingValue(); if (first == RatingComboBox::NoRating) { writer.writeField(m_name, SearchXml::Equal); writer.writeValue(-1); writer.finishField(); } else if (first != RatingComboBox::Null && first == second) { writer.writeField(m_name, SearchXml::Equal); writer.writeValue(first); writer.finishField(); } else if (first != RatingComboBox::Null && second != RatingComboBox::Null) { writer.writeField(m_name, SearchXml::Interval); writer.writeValue(QList() << first << second); writer.finishField(); } else { if (first != RatingComboBox::Null) { writer.writeField(m_name, SearchXml::GreaterThanOrEqual); writer.writeValue(first); writer.finishField(); } if (second != RatingComboBox::Null) { writer.writeField(m_name, SearchXml::LessThanOrEqual); writer.writeValue(second); writer.finishField(); } } } void SearchFieldRating::setBetweenText(const QString& text) { m_betweenLabel->setText(text); } void SearchFieldRating::firstValueChanged() { RatingComboBox::RatingValue first = m_firstBox->ratingValue(); RatingComboBox::RatingValue second = m_secondBox->ratingValue(); if (first == RatingComboBox::NoRating) { m_secondBox->setRatingValue(RatingComboBox::Null); m_secondBox->setEnabled(false); } else { m_secondBox->setEnabled(true); } if (first >= RatingComboBox::Rating0 && first <= RatingComboBox::Rating5) { if (first > second) { m_secondBox->setRatingValue(RatingComboBox::Null); } } setValidValueState(first != RatingComboBox::Null || second != RatingComboBox::Null); } void SearchFieldRating::secondValueChanged() { RatingComboBox::RatingValue first = m_firstBox->ratingValue(); RatingComboBox::RatingValue second = m_secondBox->ratingValue(); // NoRating is not possible for the second box if (second >= RatingComboBox::Rating0 && second <= RatingComboBox::Rating5) { if (first > second) { m_firstBox->setRatingValue(second); } } setValidValueState(first != RatingComboBox::Null || second != RatingComboBox::Null); } void SearchFieldRating::reset() { m_firstBox->setRatingValue(RatingComboBox::Null); m_secondBox->setRatingValue(RatingComboBox::Null); } void SearchFieldRating::setValueWidgetsVisible(bool visible) { m_firstBox->setVisible(visible); m_secondBox->setVisible(visible); m_betweenLabel->setVisible(visible); } QList SearchFieldRating::valueWidgetRects() const { QList rects; rects << m_firstBox->geometry(); rects << m_secondBox->geometry(); return rects; } // ------------------------------------------------------------------------- SearchFieldComboBox::SearchFieldComboBox(QObject* const parent) : SearchField(parent), m_comboBox(0) { } void SearchFieldComboBox::setupValueWidgets(QGridLayout* layout, int row, int column) { m_comboBox = new QComboBox; m_comboBox->setEditable(false); layout->addWidget(m_comboBox, row, column, 1, 3); connect(m_comboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(indexChanged(int))); } void SearchFieldComboBox::write(SearchXmlWriter& writer) { int index = m_comboBox->currentIndex(); if (index != -1) { QVariant bits = m_comboBox->itemData(index); if (!bits.isNull()) { writer.writeField(m_name, SearchXml::Equal); writer.writeValue(bits.toInt()); writer.finishField(); } } } void SearchFieldComboBox::setValueWidgetsVisible(bool visible) { m_comboBox->setVisible(visible); } void SearchFieldComboBox::reset() { m_comboBox->setCurrentIndex(0); } QList SearchFieldComboBox::valueWidgetRects() const { QList rects; rects << m_comboBox->geometry(); return rects; } void SearchFieldComboBox::indexChanged(int index) { setValidValueState(index != 0); } // ------------------------------------------------------------------------- SearchFieldCheckBox::SearchFieldCheckBox(QObject* const parent) : SearchField(parent), m_checkBox(0) { } void SearchFieldCheckBox::setupValueWidgets(QGridLayout* layout, int row, int column) { m_checkBox = new QCheckBox(m_text); layout->addWidget(m_checkBox, row, column, 1, 3); connect(m_checkBox, SIGNAL(toggled(bool)), this, SLOT(slotToggled(bool))); } void SearchFieldCheckBox::setLabel(const QString& text) { m_text = text; if (m_checkBox) { m_checkBox->setText(m_text); } } void SearchFieldCheckBox::write(SearchXmlWriter& writer) { if (m_checkBox->isChecked()) { writer.writeField(m_name, SearchXml::Equal); writer.finishField(); } } void SearchFieldCheckBox::read(SearchXmlCachingReader& reader) { SearchXml::Relation relation = reader.fieldRelation(); reader.readToEndOfElement(); if (relation == SearchXml::Equal) { m_checkBox->setChecked(true); } } void SearchFieldCheckBox::setValueWidgetsVisible(bool visible) { m_checkBox->setVisible(visible); } void SearchFieldCheckBox::reset() { m_checkBox->setChecked(false); } QList SearchFieldCheckBox::valueWidgetRects() const { QList rects; rects << m_checkBox->geometry(); return rects; } void SearchFieldCheckBox::slotToggled(bool checked) { setValidValueState(checked); } // ------------------------------------------------------------------------- SearchFieldColorDepth::SearchFieldColorDepth(QObject* const parent) : SearchFieldComboBox(parent) { } void SearchFieldColorDepth::setupValueWidgets(QGridLayout* layout, int row, int column) { SearchFieldComboBox::setupValueWidgets(layout, row, column); m_comboBox->addItem(i18n("any color depth")); m_comboBox->addItem(i18n("8 bits per channel"), 8); m_comboBox->addItem(i18n("16 bits per channel"), 16); m_comboBox->setCurrentIndex(0); } void SearchFieldColorDepth::read(SearchXmlCachingReader& reader) { SearchXml::Relation relation = reader.fieldRelation(); if (relation == SearchXml::Equal) { int bits = reader.valueToInt(); if (bits == 8) { m_comboBox->setCurrentIndex(1); } else if (bits == 16) { m_comboBox->setCurrentIndex(2); } } } // ------------------------------------------------------------------------- SearchFieldPageOrientation::SearchFieldPageOrientation(QObject* const parent) : SearchFieldComboBox(parent) { } void SearchFieldPageOrientation::setupValueWidgets(QGridLayout* layout, int row, int column) { SearchFieldComboBox::setupValueWidgets(layout, row, column); m_comboBox->addItem(i18n("Any Orientation")); m_comboBox->addItem(i18n("Landscape Orientation"), 1); m_comboBox->addItem(i18n("Portrait orientation"), 2); m_comboBox->setCurrentIndex(0); } void SearchFieldPageOrientation::read(SearchXmlCachingReader& reader) { SearchXml::Relation relation = reader.fieldRelation(); if (relation == SearchXml::Equal) { int value = reader.valueToInt(); if (value == 1) { m_comboBox->setCurrentIndex(1); } else if (value == 2) { m_comboBox->setCurrentIndex(2); } } } // ------------------------------------------------------------------------- SearchFieldLabels::SearchFieldLabels(QObject* const parent) : SearchField(parent), m_pickLabelFilter(0), m_colorLabelFilter(0) { } void SearchFieldLabels::setupValueWidgets(QGridLayout* layout, int row, int column) { QHBoxLayout* const hbox = new QHBoxLayout; m_pickLabelFilter = new PickLabelFilter; m_colorLabelFilter = new ColorLabelFilter; hbox->addWidget(m_pickLabelFilter); hbox->addStretch(10); hbox->addWidget(m_colorLabelFilter); connect(m_pickLabelFilter, SIGNAL(signalPickLabelSelectionChanged(QList)), this, SLOT(updateState())); connect(m_colorLabelFilter, SIGNAL(signalColorLabelSelectionChanged(QList)), this, SLOT(updateState())); updateState(); layout->addLayout(hbox, row, column, 1, 3); } void SearchFieldLabels::updateState() { setValidValueState(!m_colorLabelFilter->colorLabels().isEmpty()); } void SearchFieldLabels::read(SearchXmlCachingReader& reader) { TAlbum* a = 0; QList ids = reader.valueToIntOrIntList(); QList clabels; QList plabels; foreach(int id, ids) { a = AlbumManager::instance()->findTAlbum(id); if (!a) { qCDebug(DIGIKAM_GENERAL_LOG) << "Search: Did not find Label album for ID" << id << "given in Search XML"; } else { int cl = TagsCache::instance()->colorLabelForTag(a->id()); if (cl != -1) { clabels.append((ColorLabel)cl); } else { int pl = TagsCache::instance()->pickLabelForTag(a->id()); if (pl != -1) { plabels.append((PickLabel)pl); } } } } m_colorLabelFilter->setColorLabels(clabels); m_pickLabelFilter->setPickLabels(plabels); } void SearchFieldLabels::write(SearchXmlWriter& writer) { QList albumIds; QList clAlbums = m_colorLabelFilter->getCheckedColorLabelTags(); if (!clAlbums.isEmpty()) { foreach(TAlbum* const album, clAlbums) { albumIds << album->id(); } } QList plAlbums = m_pickLabelFilter->getCheckedPickLabelTags(); if (!plAlbums.isEmpty()) { foreach(TAlbum* const album, plAlbums) { albumIds << album->id(); } } if (albumIds.isEmpty()) { return; } // NOTE: As Color Labels are internal tags, we trig database on "tagid" // with "labels" in ItemQueryBuilder::buildField(). writer.writeField(m_name, SearchXml::InTree); if (albumIds.size() > 1) { writer.writeValue(albumIds); } else { writer.writeValue(albumIds.first()); } writer.finishField(); } void SearchFieldLabels::reset() { m_colorLabelFilter->reset(); m_pickLabelFilter->reset(); } void SearchFieldLabels::setValueWidgetsVisible(bool visible) { m_colorLabelFilter->setVisible(visible); m_pickLabelFilter->setVisible(visible); } QList SearchFieldLabels::valueWidgetRects() const { QList rects; rects << m_pickLabelFilter->geometry(); rects << m_colorLabelFilter->geometry(); return rects; } } // namespace Digikam diff --git a/core/utilities/setup/camera/setupcamera.cpp b/core/utilities/setup/camera/setupcamera.cpp index b33cbcf864..4b6f2e6c63 100644 --- a/core/utilities/setup/camera/setupcamera.cpp +++ b/core/utilities/setup/camera/setupcamera.cpp @@ -1,1018 +1,1018 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2003-02-10 * Description : camera setup tab. * * Copyright (C) 2003-2005 by Renchi Raju * Copyright (C) 2006-2018 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 "setupcamera.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 // Local includes #include "albumselectwidget.h" #include "cameralist.h" #include "cameraselection.h" #include "cameratype.h" #include "digikam_config.h" #include "gpcamera.h" #include "filtercombo.h" #include "importfilters.h" #include "dfontselect.h" #include "importsettings.h" #include "fullscreensettings.h" #include "dxmlguiwindow.h" #include "dactivelabel.h" namespace Digikam { class Q_DECL_HIDDEN SetupCameraItem : public QTreeWidgetItem { public: explicit SetupCameraItem(QTreeWidget* const parent, CameraType* const ctype) : QTreeWidgetItem(parent), m_ctype(0) { setCameraType(ctype); }; ~SetupCameraItem() { delete m_ctype; }; void setCameraType(CameraType* const ctype) { delete m_ctype; m_ctype = new CameraType(*ctype); if (m_ctype) { setText(0, m_ctype->title()); setText(1, m_ctype->model()); setText(2, m_ctype->port()); setText(3, m_ctype->path()); } }; CameraType* cameraType() const { return m_ctype; }; private: CameraType* m_ctype; }; // ------------------------------------------------------------------- class Q_DECL_HIDDEN CameraAutoDetectThread::Private { public: explicit Private() { result = 0; } int result; QString model; QString port; }; CameraAutoDetectThread::CameraAutoDetectThread(QObject* const parent) : DBusyThread(parent), d(new Private) { d->result = -1; } CameraAutoDetectThread::~CameraAutoDetectThread() { delete d; } void CameraAutoDetectThread::run() { d->result = GPCamera::autoDetect(d->model, d->port); emit signalComplete(); } int CameraAutoDetectThread::result() const { return(d->result); } QString CameraAutoDetectThread::model() const { return(d->model); } QString CameraAutoDetectThread::port() const { return(d->port); } // ------------------------------------------------------------------- class Q_DECL_HIDDEN SetupCamera::Private { public: explicit Private() : addButton(0), removeButton(0), editButton(0), autoDetectButton(0), importAddButton(0), importRemoveButton(0), importEditButton(0), storeDiffButton(0), overwriteButton(0), skipFileButton(0), conflictButtonGroup(0), useFileMetadata(0), turnHighQualityThumbs(0), useDefaultTargetAlbum(0), iconShowNameBox(0), iconShowSizeBox(0), iconShowDateBox(0), iconShowResolutionBox(0), iconShowTagsBox(0), iconShowOverlaysBox(0), iconShowRatingBox(0), iconShowFormatBox(0), iconShowCoordinatesBox(0), previewLoadFullImageSize(0), previewItemsWhileDownload(0), previewShowIcons(0), leftClickActionComboBox(0), iconViewFontSelect(0), target1AlbumSelector(0), listView(0), importListView(0), tab(0), ignoreNamesEdit(0), ignoreExtensionsEdit(0), fullScreenSettings(0) { } static const QString configGroupName; static const QString configUseFileMetadata; static const QString configTrunHighQualityThumbs; static const QString configUseDefaultTargetAlbum; static const QString configDefaultTargetAlbumId; static const QString configFileSaveConflictRule; static const QString importFiltersConfigGroupName; QPushButton* addButton; QPushButton* removeButton; QPushButton* editButton; QPushButton* autoDetectButton; QPushButton* importAddButton; QPushButton* importRemoveButton; QPushButton* importEditButton; QRadioButton* storeDiffButton; QRadioButton* overwriteButton; QRadioButton* skipFileButton; QButtonGroup* conflictButtonGroup; QCheckBox* useFileMetadata; QCheckBox* turnHighQualityThumbs; QCheckBox* useDefaultTargetAlbum; QCheckBox* iconShowNameBox; QCheckBox* iconShowSizeBox; QCheckBox* iconShowDateBox; QCheckBox* iconShowResolutionBox; QCheckBox* iconShowTagsBox; QCheckBox* iconShowOverlaysBox; QCheckBox* iconShowRatingBox; QCheckBox* iconShowFormatBox; QCheckBox* iconShowCoordinatesBox; QCheckBox* previewLoadFullImageSize; QCheckBox* previewItemsWhileDownload; QCheckBox* previewShowIcons; QComboBox* leftClickActionComboBox; DFontSelect* iconViewFontSelect; AlbumSelectWidget* target1AlbumSelector; QTreeWidget* listView; QListWidget* importListView; QTabWidget* tab; QLineEdit* ignoreNamesEdit; QLineEdit* ignoreExtensionsEdit; FilterList filters; FullScreenSettings* fullScreenSettings; }; const QString SetupCamera::Private::configGroupName(QLatin1String("Camera Settings")); const QString SetupCamera::Private::configUseFileMetadata(QLatin1String("UseFileMetadata")); const QString SetupCamera::Private::configTrunHighQualityThumbs(QLatin1String("TurnHighQualityThumbs")); const QString SetupCamera::Private::configUseDefaultTargetAlbum(QLatin1String("UseDefaultTargetAlbum")); const QString SetupCamera::Private::configDefaultTargetAlbumId(QLatin1String("DefaultTargetAlbumId")); const QString SetupCamera::Private::configFileSaveConflictRule(QLatin1String("FileSaveConflictRule")); const QString SetupCamera::Private::importFiltersConfigGroupName(QLatin1String("Import Filters")); SetupCamera::SetupCamera(QWidget* const parent) : QScrollArea(parent), d(new Private) { d->tab = new QTabWidget(viewport()); setWidget(d->tab); setWidgetResizable(true); const int spacing = QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); QWidget* const panel = new QWidget(d->tab); QGridLayout* const grid = new QGridLayout(panel); d->listView = new QTreeWidget(panel); d->listView->sortItems(0, Qt::AscendingOrder); d->listView->setColumnCount(4); d->listView->setRootIsDecorated(false); d->listView->setSelectionMode(QAbstractItemView::SingleSelection); d->listView->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); d->listView->setAllColumnsShowFocus(true); d->listView->setWhatsThis(i18n("Here you can see the digital camera list used by digiKam " "via the Gphoto interface.")); QStringList labels; labels.append(i18n("Title")); labels.append(i18n("Model")); labels.append(i18n("Port")); labels.append(i18n("Path")); d->listView->setHeaderLabels(labels); d->listView->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); d->listView->header()->setSectionResizeMode(1, QHeaderView::Stretch); d->listView->header()->setSectionResizeMode(2, QHeaderView::Stretch); d->listView->header()->setSectionResizeMode(3, QHeaderView::Stretch); // ------------------------------------------------------------- d->addButton = new QPushButton(panel); d->removeButton = new QPushButton(panel); d->editButton = new QPushButton(panel); d->autoDetectButton = new QPushButton(panel); d->addButton->setText(i18n("&Add...")); d->addButton->setIcon(QIcon::fromTheme(QLatin1String("list-add"))); d->removeButton->setText(i18n("&Remove")); d->removeButton->setIcon(QIcon::fromTheme(QLatin1String("list-remove"))); d->editButton->setText(i18n("&Edit...")); d->editButton->setIcon(QIcon::fromTheme(QLatin1String("configure"))); d->autoDetectButton->setText(i18n("Auto-&Detect")); d->autoDetectButton->setIcon(QIcon::fromTheme(QLatin1String("edit-find"))); d->removeButton->setEnabled(false); d->editButton->setEnabled(false); // ------------------------------------------------------------- QSpacerItem* const spacer = new QSpacerItem(20, 20, QSizePolicy::Minimum, QSizePolicy::Expanding); DActiveLabel* const gphotoLogoLabel = new DActiveLabel(QUrl(QLatin1String("http://www.gphoto.org")), QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("digikam/data/logo-gphoto.png")), panel); gphotoLogoLabel->setToolTip(i18n("Visit Gphoto project website")); #ifndef HAVE_GPHOTO2 // If digiKam is compiled without Gphoto2 support, we hide widgets relevant. d->autoDetectButton->hide(); gphotoLogoLabel->hide(); #endif /* HAVE_GPHOTO2 */ // ------------------------------------------------------------- grid->setContentsMargins(spacing, spacing, spacing, spacing); grid->setSpacing(spacing); grid->setAlignment(Qt::AlignTop); grid->addWidget(d->listView, 0, 0, 6, 1); grid->addWidget(d->addButton, 0, 1, 1, 1); grid->addWidget(d->removeButton, 1, 1, 1, 1); grid->addWidget(d->editButton, 2, 1, 1, 1); grid->addWidget(d->autoDetectButton, 3, 1, 1, 1); grid->addItem(spacer, 4, 1, 1, 1); grid->addWidget(gphotoLogoLabel, 5, 1, 1, 1); d->tab->insertTab(0, panel, i18n("Devices")); // ------------------------------------------------------------- QWidget* const panel2 = new QWidget(d->tab); QVBoxLayout* const layout = new QVBoxLayout(panel2); d->useFileMetadata = new QCheckBox(i18n("Use file metadata (makes connection slower)"), panel2); d->turnHighQualityThumbs = new QCheckBox(i18n("Turn on high quality thumbnail loading (slower loading)"), panel2); d->useDefaultTargetAlbum = new QCheckBox(i18n("Use a default target album to download from camera"), panel2); d->target1AlbumSelector = new AlbumSelectWidget(panel2); QGroupBox* const conflictBox = new QGroupBox(panel2); QLabel* const conflictIcon = new QLabel(conflictBox); conflictIcon->setPixmap(QIcon::fromTheme(QLatin1String("document-save-as")).pixmap(32)); QLabel* const conflictLabel = new QLabel(i18n("If target file exists when downloaded from camera"), conflictBox); QGridLayout* const boxLayout = new QGridLayout(conflictBox); d->conflictButtonGroup = new QButtonGroup(conflictBox); d->storeDiffButton = new QRadioButton(i18n("Store as a different name"), conflictBox); d->overwriteButton = new QRadioButton(i18n("Overwrite automatically"), conflictBox); d->skipFileButton = new QRadioButton(i18n("Skip automatically"), conflictBox); d->conflictButtonGroup->addButton(d->overwriteButton, OVERWRITE); d->conflictButtonGroup->addButton(d->storeDiffButton, DIFFNAME); d->conflictButtonGroup->addButton(d->skipFileButton, SKIPFILE); d->conflictButtonGroup->setExclusive(true); d->storeDiffButton->setChecked(true); boxLayout->setContentsMargins(spacing, spacing, spacing, spacing); boxLayout->setSpacing(spacing); boxLayout->addWidget(conflictIcon, 0, 0); boxLayout->addWidget(conflictLabel, 0, 1); boxLayout->addWidget(d->storeDiffButton, 1, 0, 1, 3); boxLayout->addWidget(d->overwriteButton, 2, 0, 1, 3); boxLayout->addWidget(d->skipFileButton, 3, 0, 1, 3); boxLayout->setColumnStretch(2, 1); conflictBox->setLayout(boxLayout); d->tab->insertTab(1, panel2, i18n("Behavior")); layout->setContentsMargins(spacing, spacing, spacing, spacing); layout->setSpacing(spacing); layout->addWidget(d->useFileMetadata); layout->addWidget(d->turnHighQualityThumbs); layout->addWidget(d->useDefaultTargetAlbum); layout->addWidget(d->target1AlbumSelector); layout->addWidget(conflictBox); layout->addStretch(); // ------------------------------------------------------------- QWidget* const panel3 = new QWidget(d->tab); QGridLayout* const importGrid = new QGridLayout(panel3); d->importListView = new QListWidget(panel3); d->importListView->setSelectionMode(QAbstractItemView::SingleSelection); d->importListView->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); d->importListView->setWhatsThis(i18n("Here you can see filters that can be used to filter " "files in import dialog.")); d->importAddButton = new QPushButton(panel3); d->importRemoveButton = new QPushButton(panel3); d->importEditButton = new QPushButton(panel3); QSpacerItem* const spacer2 = new QSpacerItem(20, 20, QSizePolicy::Minimum, QSizePolicy::Expanding); QGroupBox* const groupBox = new QGroupBox(panel3); QVBoxLayout* const verticalLayout = new QVBoxLayout(groupBox); QLabel* const label = new QLabel(groupBox); verticalLayout->addWidget(label); d->ignoreNamesEdit = new QLineEdit(groupBox); verticalLayout->addWidget(d->ignoreNamesEdit); QLabel* const label2 = new QLabel(groupBox); verticalLayout->addWidget(label2); d->ignoreExtensionsEdit = new QLineEdit(groupBox); verticalLayout->addWidget(d->ignoreExtensionsEdit); groupBox->setTitle(i18n("Always ignore")); label->setText(i18n("Ignored file names:")); label2->setText(i18n("Ignored file extensions:")); d->importAddButton->setText(i18n("&Add...")); d->importAddButton->setIcon(QIcon::fromTheme(QLatin1String("list-add"))); d->importRemoveButton->setText(i18n("&Remove")); d->importRemoveButton->setIcon(QIcon::fromTheme(QLatin1String("list-remove"))); d->importEditButton->setText(i18n("&Edit...")); d->importEditButton->setIcon(QIcon::fromTheme(QLatin1String("configure"))); d->importRemoveButton->setEnabled(false); d->importEditButton->setEnabled(false); importGrid->setContentsMargins(spacing, spacing, spacing, spacing); importGrid->setSpacing(spacing); importGrid->setAlignment(Qt::AlignTop); importGrid->addWidget(d->importListView, 0, 0, 4, 1); importGrid->addWidget(groupBox, 5, 0, 1, 1); importGrid->addWidget(d->importAddButton, 0, 1, 1, 1); importGrid->addWidget(d->importRemoveButton, 1, 1, 1, 1); importGrid->addWidget(d->importEditButton, 2, 1, 1, 1); importGrid->addItem(spacer2, 3, 1, 1, 1); d->tab->insertTab(2, panel3, i18n("Import Filters")); // -- Import Icon View ---------------------------------------------------------- QWidget* const panel4 = new QWidget(d->tab); QVBoxLayout* const layout2 = new QVBoxLayout(panel4); QGroupBox* const iconViewGroup = new QGroupBox(i18n("Icon-View Options"), panel4); QGridLayout* const grid2 = new QGridLayout(iconViewGroup); d->iconShowNameBox = new QCheckBox(i18n("Show file&name"), iconViewGroup); d->iconShowNameBox->setWhatsThis(i18n("Set this option to show the filename below the image thumbnail.")); d->iconShowSizeBox = new QCheckBox(i18n("Show file si&ze"), iconViewGroup); d->iconShowSizeBox->setWhatsThis(i18n("Set this option to show the file size below the image thumbnail.")); d->iconShowDateBox = new QCheckBox(i18n("Show camera creation &date"), iconViewGroup); d->iconShowDateBox->setWhatsThis(i18n("Set this option to show the camera creation date " "below the image thumbnail.")); /* d->iconShowResolutionBox = new QCheckBox(i18n("Show ima&ge dimensions"), iconViewGroup); d->iconShowResolutionBox->setWhatsThis(i18n("Set this option to show the image size in pixels " "below the image thumbnail.")); */ d->iconShowFormatBox = new QCheckBox(i18n("Show image Format"), iconViewGroup); d->iconShowFormatBox->setWhatsThis(i18n("Set this option to show image format over image thumbnail.")); d->iconShowCoordinatesBox = new QCheckBox(i18n("Show Geolocation Indicator"), iconViewGroup); d->iconShowCoordinatesBox->setWhatsThis(i18n("Set this option to indicate if image has geolocation information.")); d->iconShowTagsBox = new QCheckBox(i18n("Show digiKam &tags"), iconViewGroup); d->iconShowTagsBox->setWhatsThis(i18n("Set this option to show the digiKam tags " "below the image thumbnail.")); d->iconShowRatingBox = new QCheckBox(i18n("Show item &rating"), iconViewGroup); d->iconShowRatingBox->setWhatsThis(i18n("Set this option to show the item rating " "below the image thumbnail.")); d->iconShowOverlaysBox = new QCheckBox(i18n("Show rotation overlay buttons"), iconViewGroup); d->iconShowOverlaysBox->setWhatsThis(i18n("Set this option to show overlay buttons on " "the image thumbnail for image rotation.")); QLabel* const leftClickLabel = new QLabel(i18n("Thumbnail click action:"), iconViewGroup); d->leftClickActionComboBox = new QComboBox(iconViewGroup); d->leftClickActionComboBox->addItem(i18n("Show embedded preview"), ImportSettings::ShowPreview); d->leftClickActionComboBox->addItem(i18n("Start image editor"), ImportSettings::StartEditor); d->leftClickActionComboBox->setToolTip(i18n("Choose what should happen when you click on a thumbnail.")); d->iconViewFontSelect = new DFontSelect(i18n("Icon View font:"), panel); d->iconViewFontSelect->setToolTip(i18n("Select here the font used to display text in Icon Views.")); grid2->addWidget(d->iconShowNameBox, 0, 0, 1, 1); grid2->addWidget(d->iconShowSizeBox, 1, 0, 1, 1); grid2->addWidget(d->iconShowDateBox, 2, 0, 1, 1); grid2->addWidget(d->iconShowFormatBox, 3, 0, 1, 1); // grid2->addWidget(d->iconShowResolutionBox, 4, 0, 1, 1); TODO grid2->addWidget(d->iconShowTagsBox, 0, 1, 1, 1); grid2->addWidget(d->iconShowRatingBox, 1, 1, 1, 1); grid2->addWidget(d->iconShowOverlaysBox, 2, 1, 1, 1); grid2->addWidget(d->iconShowCoordinatesBox, 3, 1, 1, 1); grid2->addWidget(leftClickLabel, 5, 0, 1, 1); grid2->addWidget(d->leftClickActionComboBox, 5, 1, 1, 1); grid2->addWidget(d->iconViewFontSelect, 6, 0, 1, 2); grid2->setContentsMargins(spacing, spacing, spacing, spacing); grid2->setSpacing(spacing); // -------------------------------------------------------- QGroupBox* const interfaceOptionsGroup = new QGroupBox(i18n("Preview Options"), panel4); QGridLayout* const grid3 = new QGridLayout(interfaceOptionsGroup); d->previewLoadFullImageSize = new QCheckBox(i18n("Embedded preview loads full-sized images"), interfaceOptionsGroup); d->previewLoadFullImageSize->setWhatsThis(i18n("

Set this option to load images at their full size " "for preview, rather than at a reduced size. As this option " "will make it take longer to load images, only use it if you have " "a fast computer.

" "

Note: for Raw images, a half size version of the Raw data " "is used instead of the embedded JPEG preview.

")); d->previewItemsWhileDownload = new QCheckBox(i18n("Preview each item while downloading it"), interfaceOptionsGroup); d->previewItemsWhileDownload->setWhatsThis(i18n("

Set this option to preview each item while downloading.

")); d->previewShowIcons = new QCheckBox(i18n("Show icons and text over preview"), interfaceOptionsGroup); d->previewShowIcons->setWhatsThis(i18n("Uncheck this if you do not want to see icons and text in the image preview.")); grid3->setContentsMargins(spacing, spacing, spacing, spacing); grid3->setSpacing(spacing); grid3->addWidget(d->previewLoadFullImageSize, 0, 0, 1, 2); grid3->addWidget(d->previewItemsWhileDownload, 1, 0, 1, 2); grid3->addWidget(d->previewShowIcons, 2, 0, 1, 2); // -------------------------------------------------------- d->fullScreenSettings = new FullScreenSettings(FS_IMPORTUI, panel4); layout2->setContentsMargins(QMargins()); layout2->setSpacing(spacing); layout2->addWidget(iconViewGroup); layout2->addWidget(interfaceOptionsGroup); layout2->addWidget(d->fullScreenSettings); layout2->addStretch(); d->tab->insertTab(3, panel4, i18n("Import Window")); // ------------------------------------------------------------- adjustSize(); // ------------------------------------------------------------- connect(d->listView, SIGNAL(itemSelectionChanged()), this, SLOT(slotSelectionChanged())); connect(d->addButton, SIGNAL(clicked()), this, SLOT(slotAddCamera())); connect(d->removeButton, SIGNAL(clicked()), this, SLOT(slotRemoveCamera())); connect(d->editButton, SIGNAL(clicked()), this, SLOT(slotEditCamera())); connect(d->autoDetectButton, SIGNAL(clicked()), this, SLOT(slotAutoDetectCamera())); connect(d->useDefaultTargetAlbum, SIGNAL(toggled(bool)), d->target1AlbumSelector, SLOT(setEnabled(bool))); connect(d->previewItemsWhileDownload, SIGNAL(clicked()), this, SLOT(slotPreviewItemsClicked())); connect(d->previewLoadFullImageSize, SIGNAL(clicked()), this, SLOT(slotPreviewFullImageSizeClicked())); // ------------------------------------------------------------- connect(d->importListView, SIGNAL(itemSelectionChanged()), this, SLOT(slotImportSelectionChanged())); connect(d->importAddButton, SIGNAL(clicked()), this, SLOT(slotAddFilter())); connect(d->importRemoveButton, SIGNAL(clicked()), this, SLOT(slotRemoveFilter())); connect(d->importEditButton, SIGNAL(clicked()), this, SLOT(slotEditFilter())); // ------------------------------------------------------------- connect(d->useFileMetadata, SIGNAL(toggled(bool)), this, SIGNAL(signalUseFileMetadataChanged(bool))); // ------------------------------------------------------------- readSettings(); } SetupCamera::~SetupCamera() { delete d; } bool SetupCamera::useFileMetadata() { KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(d->configGroupName); return (group.readEntry(d->configUseFileMetadata, false)); } void SetupCamera::readSettings() { // Populate cameras -------------------------------------- CameraList* const clist = CameraList::defaultList(); if (clist) { QList* const cl = clist->cameraList(); foreach(CameraType* const ctype, *cl) { new SetupCameraItem(d->listView, ctype); } } // ------------------------------------------------------- KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(d->configGroupName); d->useFileMetadata->setChecked(useFileMetadata()); d->turnHighQualityThumbs->setChecked(group.readEntry(d->configTrunHighQualityThumbs, false)); d->useDefaultTargetAlbum->setChecked(group.readEntry(d->configUseDefaultTargetAlbum, false)); PAlbum* const album = AlbumManager::instance()->findPAlbum(group.readEntry(d->configDefaultTargetAlbumId, 0)); d->target1AlbumSelector->setCurrentAlbum(album); d->target1AlbumSelector->setEnabled(d->useDefaultTargetAlbum->isChecked()); d->conflictButtonGroup->button(group.readEntry(d->configFileSaveConflictRule, (int)DIFFNAME))->setChecked(true); d->fullScreenSettings->readSettings(group); // ------------------------------------------------------- KConfigGroup importGroup = config->group(d->importFiltersConfigGroupName); for (int i = 0; true; ++i) { QString filter = importGroup.readEntry(QString::fromUtf8("Filter%1").arg(i), QString()); if (filter.isEmpty()) { break; } Filter* const f = new Filter; f->fromString(filter); d->filters.append(f); } FilterComboBox::defaultFilters(&d->filters); foreach(Filter* const f, d->filters) { new QListWidgetItem(f->name, d->importListView); } d->importListView->sortItems(); d->ignoreNamesEdit->setText(importGroup.readEntry(QLatin1String("IgnoreNames"), FilterComboBox::defaultIgnoreNames)); d->ignoreExtensionsEdit->setText(importGroup.readEntry(QLatin1String("IgnoreExtensions"), FilterComboBox::defaultIgnoreExtensions)); ImportSettings* const settings = ImportSettings::instance(); if (!settings) { return; } d->iconShowNameBox->setChecked(settings->getIconShowName()); d->iconShowTagsBox->setChecked(settings->getIconShowTags()); d->iconShowSizeBox->setChecked(settings->getIconShowSize()); d->iconShowDateBox->setChecked(settings->getIconShowDate()); //TODO: d->iconShowResolutionBox->setChecked(settings->getIconShowResolution()); d->iconShowOverlaysBox->setChecked(settings->getIconShowOverlays()); d->iconShowRatingBox->setChecked(settings->getIconShowRating()); d->iconShowFormatBox->setChecked(settings->getIconShowImageFormat()); d->iconShowCoordinatesBox->setChecked(settings->getIconShowCoordinates()); d->iconViewFontSelect->setFont(settings->getIconViewFont()); d->leftClickActionComboBox->setCurrentIndex((int)settings->getItemLeftClickAction()); d->previewLoadFullImageSize->setChecked(settings->getPreviewLoadFullImageSize()); d->previewItemsWhileDownload->setChecked(settings->getPreviewItemsWhileDownload()); d->previewShowIcons->setChecked(settings->getPreviewShowIcons()); } void SetupCamera::applySettings() { // Save camera devices ----------------------------------- CameraList* const clist = CameraList::defaultList(); if (clist) { clist->clear(); QTreeWidgetItemIterator it(d->listView); while (*it) { SetupCameraItem* const item = dynamic_cast(*it); if (item) { CameraType* const ctype = item->cameraType(); if (ctype) { clist->insert(new CameraType(*ctype)); } } ++it; } clist->save(); } // ------------------------------------------------------- KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(d->configGroupName); group.writeEntry(d->configUseFileMetadata, d->useFileMetadata->isChecked()); group.writeEntry(d->configTrunHighQualityThumbs, d->turnHighQualityThumbs->isChecked()); group.writeEntry(d->configUseDefaultTargetAlbum, d->useDefaultTargetAlbum->isChecked()); PAlbum* const album = d->target1AlbumSelector->currentAlbum(); group.writeEntry(d->configDefaultTargetAlbumId, album ? album->id() : 0); group.writeEntry(d->configFileSaveConflictRule, d->conflictButtonGroup->checkedId()); d->fullScreenSettings->saveSettings(group); group.sync(); // ------------------------------------------------------- KConfigGroup importGroup = config->group(d->importFiltersConfigGroupName); importGroup.deleteGroup(); for (int i = 0; i < d->filters.count(); ++i) { importGroup.writeEntry(QString::fromUtf8("Filter%1").arg(i), d->filters[i]->toString()); } importGroup.writeEntry(QLatin1String("IgnoreNames"), d->ignoreNamesEdit->text()); importGroup.writeEntry(QLatin1String("IgnoreExtensions"), d->ignoreExtensionsEdit->text()); importGroup.sync(); ImportSettings* const settings = ImportSettings::instance(); if (!settings) { return; } settings->setIconShowName(d->iconShowNameBox->isChecked()); settings->setIconShowTags(d->iconShowTagsBox->isChecked()); settings->setIconShowSize(d->iconShowSizeBox->isChecked()); settings->setIconShowDate(d->iconShowDateBox->isChecked()); //TODO: settings->setIconShowResolution(d->iconShowResolutionBox->isChecked()); settings->setIconShowOverlays(d->iconShowOverlaysBox->isChecked()); settings->setIconShowRating(d->iconShowRatingBox->isChecked()); settings->setIconShowImageFormat(d->iconShowFormatBox->isChecked()); settings->setIconShowCoordinates(d->iconShowCoordinatesBox->isChecked()); settings->setIconViewFont(d->iconViewFontSelect->font()); settings->setItemLeftClickAction((ImportSettings::ItemLeftClickAction) d->leftClickActionComboBox->currentIndex()); settings->setPreviewLoadFullImageSize(d->previewLoadFullImageSize->isChecked()); settings->setPreviewItemsWhileDownload(d->previewItemsWhileDownload->isChecked()); settings->setPreviewShowIcons(d->previewShowIcons->isChecked()); settings->saveSettings(); } bool SetupCamera::checkSettings() { if (d->useDefaultTargetAlbum->isChecked() && !d->target1AlbumSelector->currentAlbum()) { d->tab->setCurrentIndex(1); QMessageBox::information(this, qApp->applicationName(), i18n("No default target album have been selected to process download " "from camera device. Please select one.")); return false; } return true; } void SetupCamera::slotSelectionChanged() { QTreeWidgetItem* const item = d->listView->currentItem(); if (!item) { d->removeButton->setEnabled(false); d->editButton->setEnabled(false); return; } d->removeButton->setEnabled(true); d->editButton->setEnabled(true); } void SetupCamera::slotAddCamera() { CameraSelection* const select = new CameraSelection; connect(select, SIGNAL(signalOkClicked(QString,QString,QString,QString)), this, SLOT(slotAddedCamera(QString,QString,QString,QString))); select->show(); } void SetupCamera::slotAddedCamera(const QString& title, const QString& model, const QString& port, const QString& path) { CameraType ctype(title, model, port, path, 1); new SetupCameraItem(d->listView, &ctype); } void SetupCamera::slotRemoveCamera() { SetupCameraItem* const item = dynamic_cast(d->listView->currentItem()); delete item; } void SetupCamera::slotEditCamera() { SetupCameraItem* const item = dynamic_cast(d->listView->currentItem()); if (!item) { return; } CameraType* const ctype = item->cameraType(); if (!ctype) { return; } CameraSelection* const select = new CameraSelection; select->setCamera(ctype->title(), ctype->model(), ctype->port(), ctype->path()); connect(select, SIGNAL(signalOkClicked(QString,QString,QString,QString)), this, SLOT(slotEditedCamera(QString,QString,QString,QString))); select->show(); } void SetupCamera::slotEditedCamera(const QString& title, const QString& model, const QString& port, const QString& path) { SetupCameraItem* const item = dynamic_cast(d->listView->currentItem()); if (!item) { return; } CameraType ctype(title, model, port, path, 1); item->setCameraType(&ctype); } void SetupCamera::slotAutoDetectCamera() { DBusyDlg* const dlg = new DBusyDlg(i18n("Device detection under progress, please wait..."), this); CameraAutoDetectThread* const thread = new CameraAutoDetectThread(this); dlg->setBusyThread(thread); dlg->exec(); QString model = thread->model(); QString port = thread->port(); int ret = thread->result(); if (ret != 0) { QMessageBox::critical(this, qApp->applicationName(), i18n("Failed to auto-detect camera.\n" "Please check if your camera is turned on " "and retry or try setting it manually.")); return; } // NOTE: See note in digikam/digikam/cameralist.cpp if (port.startsWith(QLatin1String("usb:"))) { port = QLatin1String("usb:"); } if (!d->listView->findItems(model, Qt::MatchExactly, 1).isEmpty()) { QMessageBox::information(this, qApp->applicationName(), i18n("Camera '%1' (%2) is already in list.", model, port)); } else { QMessageBox::information(this, qApp->applicationName(), i18n("Found camera '%1' (%2) and added it to the list.", model, port)); slotAddedCamera(model, model, port, QLatin1String("/")); } } void SetupCamera::slotImportSelectionChanged() { QListWidgetItem* const item = d->importListView->currentItem(); d->importRemoveButton->setEnabled(item); d->importEditButton->setEnabled(item); } void SetupCamera::slotAddFilter() { Filter filter; filter.name = i18n("Untitled"); QPointer dlg = new ImportFilters(this); dlg->setData(filter); if (dlg->exec() == QDialog::Accepted) { Filter* const f = new Filter; dlg->getData(f); d->filters.append(f); new QListWidgetItem(f->name, d->importListView); } delete dlg; slotImportSelectionChanged(); } void SetupCamera::slotRemoveFilter() { QListWidgetItem* const item = d->importListView->currentItem(); int current = d->importListView->currentRow(); - for (int i = 0 ; i < d->filters.count() ; i++) + for (int i = 0 ; i < d->filters.count() ; ++i) { if (d->filters.at(i)->name == item->text()) { delete d->filters.takeAt(i); delete d->importListView->takeItem(current); slotImportSelectionChanged(); break; } } } void SetupCamera::slotEditFilter() { QListWidgetItem* const item = d->importListView->currentItem(); - for (int i = 0 ; i < d->filters.count() ; i++) + for (int i = 0 ; i < d->filters.count() ; ++i) { if (d->filters.at(i)->name == item->text()) { Filter filter = *d->filters.at(i); QPointer dlg = new ImportFilters(this); dlg->setData(filter); if (dlg->exec() == QDialog::Accepted) { Filter* const f = d->filters.at(i); dlg->getData(f); item->setText(f->name); } delete dlg; break; } } } void SetupCamera::slotPreviewItemsClicked() { if (d->previewItemsWhileDownload->isChecked() && d->previewLoadFullImageSize->isChecked()) { QMessageBox::information(this, qApp->applicationName(), i18n("In order to enable this feature, the full-sized preview will be disabled.")); d->previewLoadFullImageSize->setChecked(false); } } void SetupCamera::slotPreviewFullImageSizeClicked() { if (d->previewItemsWhileDownload->isChecked() && d->previewLoadFullImageSize) { QMessageBox::information(this, qApp->applicationName(), i18n("If the full-sized preview is enabled it will affect the speed of previewing each item while download.")); d->previewItemsWhileDownload->setChecked(false); } } } // namespace Digikam diff --git a/core/utilities/setup/metadata/advancedmetadatatab.cpp b/core/utilities/setup/metadata/advancedmetadatatab.cpp index 0c9221678f..2abd4f26b5 100644 --- a/core/utilities/setup/metadata/advancedmetadatatab.cpp +++ b/core/utilities/setup/metadata/advancedmetadatatab.cpp @@ -1,533 +1,533 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2015-06-16 * Description : Advanced Configuration tab for metadata. * * 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 "advancedmetadatatab.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include // Local includes #include "dmetadatasettings.h" #include "namespacelistview.h" #include "namespaceeditdlg.h" #include "dmessagebox.h" #include "digikam_debug.h" namespace Digikam { class Q_DECL_HIDDEN AdvancedMetadataTab::Private { public: explicit Private() { metadataType = 0; operationType = 0; addButton = 0; editButton = 0; deleteButton = 0; moveUpButton = 0; moveDownButton = 0; revertChanges = 0; resetButton = 0; unifyReadWrite = 0; namespaceView = 0; metadataTypeSize = 0; changed = false; } QComboBox* metadataType; QComboBox* operationType; QPushButton* addButton; QPushButton* editButton; QPushButton* deleteButton; QPushButton* moveUpButton; QPushButton* moveDownButton; QPushButton* revertChanges; QPushButton* resetButton; QCheckBox* unifyReadWrite; QList models; NamespaceListView* namespaceView; DMetadataSettingsContainer container; int metadataTypeSize; bool changed; }; AdvancedMetadataTab::AdvancedMetadataTab(QWidget* const parent) : QWidget(parent), d(new Private()) { // ---------- Advanced Configuration Panel ----------------------------- d->container = DMetadataSettings::instance()->settings(); setUi(); setModels(); connectButtons(); d->unifyReadWrite->setChecked(d->container.unifyReadWrite()); connect(d->unifyReadWrite, SIGNAL(toggled(bool)), this, SLOT(slotUnifyChecked(bool))); connect(d->metadataType, SIGNAL(currentIndexChanged(int)), this, SLOT(slotIndexChanged())); connect(d->operationType, SIGNAL(currentIndexChanged(int)), this, SLOT(slotIndexChanged())); /** * Connect all actions to slotRevertAvailable, which will enable revert to original * if an add, edit, delete, or reorder was made */ connect(d->namespaceView, SIGNAL(signalItemsChanged()), this, SLOT(slotRevertChangesAvailable())); if (d->unifyReadWrite->isChecked()) { d->operationType->setEnabled(false); } } AdvancedMetadataTab::~AdvancedMetadataTab() { delete d; } void AdvancedMetadataTab::slotResetToDefault() { const int result = DMessageBox::showContinueCancel(QMessageBox::Warning, this, i18n("Warning"), i18n("This option will reset configuration to default\n" "All your changes will be lost.\n " "Do you want to continue?")); if (result != QMessageBox::Yes) { return; } d->container.defaultValues(); d->models.at(getModelIndex())->clear(); setModelData(d->models.at(getModelIndex()), getCurrentContainer()); d->namespaceView->setModel(d->models.at(getModelIndex())); } void AdvancedMetadataTab::slotRevertChanges() { d->models.at(getModelIndex())->clear(); setModelData(d->models.at(getModelIndex()), getCurrentContainer()); d->namespaceView->setModel(d->models.at(getModelIndex())); d->changed = false; d->revertChanges->setEnabled(false); } void AdvancedMetadataTab::slotAddNewNamespace() { NamespaceEntry entry; // Setting some default parameters; if (d->metadataType->currentData().toString() == QString::fromUtf8(DM_TAG_CONTAINER)) { entry.nsType = NamespaceEntry::TAGS; } else if (d->metadataType->currentData().toString() == QString::fromUtf8(DM_RATING_CONTAINER)) { entry.nsType = NamespaceEntry::RATING; } else if (d->metadataType->currentData().toString() == QString::fromUtf8(DM_COMMENT_CONTAINER)) { entry.nsType = NamespaceEntry::COMMENT; } entry.isDefault = false; entry.subspace = NamespaceEntry::XMP; if (!NamespaceEditDlg::create(qApp->activeWindow(), entry)) { return; } QStandardItem* const root = d->models.at(getModelIndex())->invisibleRootItem(); QStandardItem* const item = new QStandardItem(entry.namespaceName); setDataToItem(item, entry); item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsEnabled); root->appendRow(item); getCurrentContainer().append(entry); slotRevertChangesAvailable(); } void AdvancedMetadataTab::slotEditNamespace() { if (!d->namespaceView->currentIndex().isValid()) { return; } NamespaceEntry entry = getCurrentContainer().at(d->namespaceView->currentIndex().row()); if (!NamespaceEditDlg::edit(qApp->activeWindow(), entry)) { return; } QStandardItem* const root = d->models.at(getModelIndex())->invisibleRootItem(); QStandardItem* const item = root->child(d->namespaceView->currentIndex().row()); getCurrentContainer().replace(d->namespaceView->currentIndex().row(), entry); setDataToItem(item, entry); slotRevertChangesAvailable(); } void AdvancedMetadataTab::applySettings() { QList keys = d->container.mappingKeys(); int index = 0; foreach(const QString& str, keys) { d->container.getReadMapping(str).clear(); saveModelData(d->models.at(index++), d->container.getReadMapping(str)); } foreach(const QString& str, keys) { d->container.getWriteMapping(str).clear(); saveModelData(d->models.at(index++), d->container.getWriteMapping(str)); } DMetadataSettings::instance()->setSettings(d->container); } void AdvancedMetadataTab::slotUnifyChecked(bool value) { d->operationType->setDisabled(value); d->container.setUnifyReadWrite(value); d->operationType->setCurrentIndex(0); slotIndexChanged(); } void AdvancedMetadataTab::slotIndexChanged() { d->namespaceView->setModel(d->models.at(getModelIndex())); } void AdvancedMetadataTab::slotRevertChangesAvailable() { if (!d->changed) { d->revertChanges->setEnabled(true); d->changed = true; } } void AdvancedMetadataTab::connectButtons() { connect(d->addButton, SIGNAL(clicked()), this, SLOT(slotAddNewNamespace())); connect(d->editButton, SIGNAL(clicked()), this, SLOT(slotEditNamespace())); connect(d->deleteButton, SIGNAL(clicked()), d->namespaceView, SLOT(slotDeleteSelected())); connect(d->resetButton, SIGNAL(clicked()), this, SLOT(slotResetToDefault())); connect(d->revertChanges, SIGNAL(clicked()), this, SLOT(slotRevertChanges())); connect(d->moveUpButton, SIGNAL(clicked()), d->namespaceView, SLOT(slotMoveItemUp())); connect(d->moveDownButton, SIGNAL(clicked()), d->namespaceView, SLOT(slotMoveItemDown())); } void AdvancedMetadataTab::setModelData(QStandardItemModel* model, const QList& container) { QStandardItem* const root = model->invisibleRootItem(); for (NamespaceEntry e : container) { QStandardItem* const item = new QStandardItem(e.namespaceName); item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsEnabled); setDataToItem(item, e); root->appendRow(item); } connect(model, SIGNAL(itemChanged(QStandardItem*)), this, SLOT(slotRevertChangesAvailable())); } void AdvancedMetadataTab::setUi() { QVBoxLayout* const advancedConfLayout = new QVBoxLayout(this); QHBoxLayout* const topLayout = new QHBoxLayout(); QHBoxLayout* const bottomLayout = new QHBoxLayout(); QLabel* const tipLabel = new QLabel(this); tipLabel->setTextFormat(Qt::RichText); tipLabel->setWordWrap(true); tipLabel->setText(i18n("Advanced configuration menu allow you to manage metadata namespaces" " used by digiKam to store and retrieve tags, rating and comments.
" "Note: Order is important when reading metadata" )); //--- Top layout ---------------- d->metadataType = new QComboBox(this); d->operationType = new QComboBox(this); d->operationType->insertItems(0, QStringList() << i18n("Read Options") << i18n("Write Options")); d->unifyReadWrite = new QCheckBox(i18n("Unify read and write")); topLayout->addWidget(d->metadataType); topLayout->addWidget(d->operationType); topLayout->addWidget(d->unifyReadWrite); //------------ Bottom Layout------------- // View d->namespaceView = new NamespaceListView(this); // Buttons QVBoxLayout* const buttonsLayout = new QVBoxLayout(); buttonsLayout->setAlignment(Qt::AlignTop); d->addButton = new QPushButton(QIcon::fromTheme(QLatin1String("list-add")), i18n("Add")); d->editButton = new QPushButton(QIcon::fromTheme(QLatin1String("document-edit")), i18n("Edit")); d->deleteButton = new QPushButton(QIcon::fromTheme(QLatin1String("window-close")), i18n("Delete")); d->moveUpButton = new QPushButton(QIcon::fromTheme(QLatin1String("go-up")), i18n("Move Up")); d->moveDownButton = new QPushButton(QIcon::fromTheme(QLatin1String("go-down")), i18n("Move Down")); d->revertChanges = new QPushButton(QIcon::fromTheme(QLatin1String("edit-undo")), i18n("Revert Changes")); // Revert changes is disabled, until a change is made d->revertChanges->setEnabled(false); d->resetButton = new QPushButton(QIcon::fromTheme(QLatin1String("view-refresh")), i18n("Reset to Default")); buttonsLayout->addWidget(d->addButton); buttonsLayout->addWidget(d->editButton); buttonsLayout->addWidget(d->deleteButton); buttonsLayout->addWidget(d->moveUpButton); buttonsLayout->addWidget(d->moveDownButton); buttonsLayout->addWidget(d->revertChanges); buttonsLayout->addWidget(d->resetButton); QVBoxLayout* const vbox = new QVBoxLayout(); vbox->addWidget(d->namespaceView); bottomLayout->addLayout(vbox); bottomLayout->addLayout(buttonsLayout); advancedConfLayout->addWidget(tipLabel); advancedConfLayout->addLayout(topLayout); advancedConfLayout->addLayout(bottomLayout); } void AdvancedMetadataTab::setDataToItem(QStandardItem* item, NamespaceEntry& entry) { item->setData(entry.namespaceName, Qt::DisplayRole); item->setData(entry.namespaceName, NAME_ROLE); item->setData((int)entry.tagPaths, ISTAG_ROLE); item->setData(entry.separator, SEPARATOR_ROLE); item->setData((int)entry.nsType, NSTYPE_ROLE); if (entry.nsType == NamespaceEntry::RATING) { item->setData(entry.convertRatio.at(0), ZEROSTAR_ROLE); item->setData(entry.convertRatio.at(1), ONESTAR_ROLE); item->setData(entry.convertRatio.at(2), TWOSTAR_ROLE); item->setData(entry.convertRatio.at(3), THREESTAR_ROLE); item->setData(entry.convertRatio.at(4), FOURSTAR_ROLE); item->setData(entry.convertRatio.at(5), FIVESTAR_ROLE); } item->setData((int)entry.specialOpts, SPECIALOPTS_ROLE); item->setData(entry.alternativeName, ALTNAME_ROLE); item->setData((int)entry.subspace, SUBSPACE_ROLE); item->setData((int)entry.secondNameOpts, ALTNAMEOPTS_ROLE); item->setData(entry.isDefault, ISDEFAULT_ROLE); item->setCheckable(true); if (!entry.isDisabled) { item->setCheckState(Qt::Checked); } } int AdvancedMetadataTab::getModelIndex() { if (d->unifyReadWrite->isChecked()) { return d->metadataType->currentIndex(); } else { // for 3 metadata types: // read operation = 3*0 + (0, 1, 2) // write operation = 3*1 + (0, 1, 2) = (3, 4 ,5) return (d->metadataTypeSize * d->operationType->currentIndex()) + d->metadataType->currentIndex(); } } QList& AdvancedMetadataTab::getCurrentContainer() { int currentIndex = getModelIndex(); if (currentIndex >= d->metadataTypeSize) { return d->container.getWriteMapping(QString::fromUtf8(d->metadataType->currentData().toByteArray())); } else { return d->container.getReadMapping(QString::fromUtf8(d->metadataType->currentData().toByteArray())); } } void AdvancedMetadataTab::setModels() { QList keys = d->container.mappingKeys(); foreach(const QString& str, keys) { d->metadataType->addItem(str, str); } d->metadataTypeSize = keys.size(); - for (int i = 0 ; i < keys.size() * 2; i++) + for (int i = 0 ; i < keys.size() * 2 ; ++i) { d->models.append(new QStandardItemModel(this)); } int index = 0; foreach(const QString& str, keys) { setModelData(d->models.at(index++), d->container.getReadMapping(str)); } foreach(const QString& str, keys) { setModelData(d->models.at(index++), d->container.getWriteMapping(str)); } slotIndexChanged(); } void AdvancedMetadataTab::saveModelData(QStandardItemModel* model, QList& container) { QStandardItem* const root = model->invisibleRootItem(); if (!root->hasChildren()) { return; } - for (int i = 0 ; i < root->rowCount(); i++) + for (int i = 0 ; i < root->rowCount() ; ++i) { NamespaceEntry ns; QStandardItem* const current = root->child(i); ns.namespaceName = current->data(NAME_ROLE).toString(); ns.tagPaths = (NamespaceEntry::TagType)current->data(ISTAG_ROLE).toInt(); ns.separator = current->data(SEPARATOR_ROLE).toString(); ns.nsType = (NamespaceEntry::NamespaceType)current->data(NSTYPE_ROLE).toInt(); if (ns.nsType == NamespaceEntry::RATING) { ns.convertRatio.append(current->data(ZEROSTAR_ROLE).toInt()); ns.convertRatio.append(current->data(ONESTAR_ROLE).toInt()); ns.convertRatio.append(current->data(TWOSTAR_ROLE).toInt()); ns.convertRatio.append(current->data(THREESTAR_ROLE).toInt()); ns.convertRatio.append(current->data(FOURSTAR_ROLE).toInt()); ns.convertRatio.append(current->data(FIVESTAR_ROLE).toInt()); } ns.specialOpts = (NamespaceEntry::SpecialOptions)current->data(SPECIALOPTS_ROLE).toInt(); ns.alternativeName = current->data(ALTNAME_ROLE).toString(); ns.subspace = (NamespaceEntry::NsSubspace)current->data(SUBSPACE_ROLE).toInt(); ns.secondNameOpts = (NamespaceEntry::SpecialOptions)current->data(ALTNAMEOPTS_ROLE).toInt(); ns.index = i; ns.isDefault = current->data(ISDEFAULT_ROLE).toBool(); if (current->checkState() == Qt::Checked) { ns.isDisabled = false; } else { ns.isDisabled = true; } container.append(ns); } } } // namespace Digikam