File indexing completed on 2025-01-19 03:55:42

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2008-05-21
0007  * Description : widget to display a list of items
0008  *
0009  * SPDX-FileCopyrightText: 2006-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0010  * SPDX-FileCopyrightText: 2008-2010 by Andi Clemens <andi dot clemens at googlemail dot com>
0011  * SPDX-FileCopyrightText: 2009-2010 by Luka Renko <lure at kubuntu dot org>
0012  *
0013  * SPDX-License-Identifier: GPL-2.0-or-later
0014  *
0015  * ============================================================ */
0016 
0017 #include "ditemslist_p.h"
0018 
0019 namespace Digikam
0020 {
0021 
0022 class Q_DECL_HIDDEN DItemsList::Private
0023 {
0024 public:
0025 
0026     explicit Private()
0027       : allowRAW             (true),
0028         allowDuplicate       (false),
0029         controlButtonsEnabled(true),
0030         iconSize             (48),
0031         addButton            (nullptr),
0032         removeButton         (nullptr),
0033         moveUpButton         (nullptr),
0034         moveDownButton       (nullptr),
0035         clearButton          (nullptr),
0036         loadButton           (nullptr),
0037         saveButton           (nullptr),
0038         extraWidget          (nullptr),
0039         progressPix          (nullptr),
0040         progressCount        (0),
0041         progressTimer        (nullptr),
0042         listView             (nullptr),
0043         iface                (nullptr),
0044         isLessThan           (nullptr)
0045     {
0046         thumbLoadThread = ThumbnailLoadThread::defaultThread();
0047     }
0048 
0049     bool                        allowRAW;
0050     bool                        allowDuplicate;
0051     bool                        controlButtonsEnabled;
0052     int                         iconSize;
0053 
0054     CtrlButton*                 addButton;
0055     CtrlButton*                 removeButton;
0056     CtrlButton*                 moveUpButton;
0057     CtrlButton*                 moveDownButton;
0058     CtrlButton*                 clearButton;
0059     CtrlButton*                 loadButton;
0060     CtrlButton*                 saveButton;
0061     QWidget*                    extraWidget;        ///< Extra widget append to the end of control buttons layout.
0062 
0063     QList<QUrl>                 processItems;
0064     DWorkingPixmap*             progressPix;
0065     int                         progressCount;
0066     QTimer*                     progressTimer;
0067 
0068     DItemsListView*             listView;
0069     ThumbnailLoadThread*        thumbLoadThread;
0070 
0071     DInfoInterface*             iface;
0072     DItemsListIsLessThanHandler isLessThan;
0073 };
0074 
0075 DItemsList::DItemsList(QWidget* const parent)
0076     : QWidget(parent),
0077       d      (new Private)
0078 {
0079     d->progressPix    = new DWorkingPixmap(this);
0080     d->listView       = new DItemsListView(this);
0081     d->listView->setSelectionMode(QAbstractItemView::ExtendedSelection);
0082 
0083     // --------------------------------------------------------
0084 
0085     d->addButton      = new CtrlButton(QIcon::fromTheme(QLatin1String("list-add")).pixmap(16, 16),      this);
0086     d->removeButton   = new CtrlButton(QIcon::fromTheme(QLatin1String("list-remove")).pixmap(16, 16),   this);
0087     d->moveUpButton   = new CtrlButton(QIcon::fromTheme(QLatin1String("go-up")).pixmap(16, 16),         this);
0088     d->moveDownButton = new CtrlButton(QIcon::fromTheme(QLatin1String("go-down")).pixmap(16, 16),       this);
0089     d->clearButton    = new CtrlButton(QIcon::fromTheme(QLatin1String("edit-clear")).pixmap(16, 16),    this);
0090     d->loadButton     = new CtrlButton(QIcon::fromTheme(QLatin1String("document-open")).pixmap(16, 16), this);
0091     d->saveButton     = new CtrlButton(QIcon::fromTheme(QLatin1String("document-save")).pixmap(16, 16), this);
0092 
0093     d->addButton->setToolTip(i18nc("@info", "Add new images to the list"));
0094     d->removeButton->setToolTip(i18nc("@info", "Remove selected images from the list"));
0095     d->moveUpButton->setToolTip(i18nc("@info", "Move current selected image up in the list"));
0096     d->moveDownButton->setToolTip(i18nc("@info", "Move current selected image down in the list"));
0097     d->clearButton->setToolTip(i18nc("@info", "Clear the list."));
0098     d->loadButton->setToolTip(i18nc("@info", "Load a saved list."));
0099     d->saveButton->setToolTip(i18nc("@info", "Save the list."));
0100 
0101     d->progressTimer  = new QTimer(this);
0102 
0103     // --------------------------------------------------------
0104 
0105     setIconSize(d->iconSize);
0106     setControlButtons(Add | Remove | MoveUp | MoveDown | Clear | Save | Load ); // add all buttons      (default)
0107     setControlButtonsPlacement(ControlButtonsBelow);                            // buttons on the bottom (default)
0108     enableDragAndDrop(true);                                                    // enable drag and drop (default)
0109 
0110     // --------------------------------------------------------
0111 
0112     connect(d->listView, &DItemsListView::signalAddedDropedItems,
0113             this, &DItemsList::slotAddImages);
0114 
0115     connect(d->thumbLoadThread, SIGNAL(signalThumbnailLoaded(LoadingDescription,QPixmap)),
0116             this, SLOT(slotThumbnail(LoadingDescription,QPixmap)));
0117 
0118     connect(d->listView, &DItemsListView::signalItemClicked,
0119             this, &DItemsList::signalItemClicked);
0120 
0121     connect(d->listView, &DItemsListView::signalContextMenuRequested,
0122             this, &DItemsList::signalContextMenuRequested);
0123 
0124     // queue this connection because itemSelectionChanged is emitted
0125     // while items are deleted, and accessing selectedItems at that
0126     // time causes a crash ...
0127 
0128     connect(d->listView, &DItemsListView::itemSelectionChanged,
0129             this, &DItemsList::slotImageListChanged, Qt::QueuedConnection);
0130 
0131     connect(this, &DItemsList::signalImageListChanged,
0132             this, &DItemsList::slotImageListChanged);
0133 
0134     // --------------------------------------------------------
0135 
0136     connect(d->addButton, &CtrlButton::clicked,
0137             this, &DItemsList::slotAddItems);
0138 
0139     connect(d->removeButton, &CtrlButton::clicked,
0140             this, &DItemsList::slotRemoveItems);
0141 
0142     connect(d->moveUpButton, &CtrlButton::clicked,
0143             this, &DItemsList::slotMoveUpItems);
0144 
0145     connect(d->moveDownButton, &CtrlButton::clicked,
0146             this, &DItemsList::slotMoveDownItems);
0147 
0148     connect(d->clearButton, &CtrlButton::clicked,
0149             this, &DItemsList::slotClearItems);
0150 
0151     connect(d->loadButton, &CtrlButton::clicked,
0152             this, &DItemsList::slotLoadItems);
0153 
0154     connect(d->saveButton, &CtrlButton::clicked,
0155             this, &DItemsList::slotSaveItems);
0156 
0157     connect(d->progressTimer, &QTimer::timeout,
0158             this, &DItemsList::slotProgressTimerDone);
0159 
0160     // --------------------------------------------------------
0161 
0162     QTimer::singleShot(1000, this, SIGNAL(signalImageListChanged()));
0163 }
0164 
0165 DItemsList::~DItemsList()
0166 {
0167     delete d;
0168 }
0169 
0170 void DItemsList::enableControlButtons(bool enable)
0171 {
0172     d->controlButtonsEnabled = enable;
0173     slotImageListChanged();
0174 }
0175 
0176 void DItemsList::enableDragAndDrop(const bool enable)
0177 {
0178     d->listView->enableDragAndDrop(enable);
0179 }
0180 
0181 void DItemsList::appendControlButtonsWidget(QWidget* const widget)
0182 {
0183     d->extraWidget = widget;
0184 }
0185 
0186 QBoxLayout* DItemsList::setControlButtonsPlacement(ControlButtonPlacement placement)
0187 {
0188     delete layout();
0189 
0190     QBoxLayout* lay               = nullptr;        // Layout instance to return;
0191     const int spacing             = qMin(QApplication::style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing),
0192                                          QApplication::style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing));
0193 
0194     QGridLayout* const mainLayout = new QGridLayout;
0195     mainLayout->addWidget(d->listView, 1, 1, 1, 1);
0196     mainLayout->setRowStretch(1, 10);
0197     mainLayout->setColumnStretch(1, 10);
0198     mainLayout->setContentsMargins(spacing, spacing, spacing, spacing);
0199     mainLayout->setSpacing(spacing);
0200 
0201     // --------------------------------------------------------
0202 
0203     QHBoxLayout* const hBtnLayout = new QHBoxLayout;
0204     hBtnLayout->addWidget(d->moveUpButton);
0205     hBtnLayout->addWidget(d->moveDownButton);
0206     hBtnLayout->addWidget(d->addButton);
0207     hBtnLayout->addWidget(d->removeButton);
0208     hBtnLayout->addWidget(d->loadButton);
0209     hBtnLayout->addWidget(d->saveButton);
0210     hBtnLayout->addWidget(d->clearButton);
0211     hBtnLayout->addStretch(1);
0212 
0213     if (d->extraWidget)
0214     {
0215         hBtnLayout->addWidget(d->extraWidget);
0216     }
0217 
0218     // --------------------------------------------------------
0219 
0220     QVBoxLayout* const vBtnLayout = new QVBoxLayout;
0221     vBtnLayout->addWidget(d->moveUpButton);
0222     vBtnLayout->addWidget(d->moveDownButton);
0223     vBtnLayout->addWidget(d->addButton);
0224     vBtnLayout->addWidget(d->removeButton);
0225     vBtnLayout->addWidget(d->loadButton);
0226     vBtnLayout->addWidget(d->saveButton);
0227     vBtnLayout->addWidget(d->clearButton);
0228     vBtnLayout->addStretch(1);
0229 
0230     if (d->extraWidget)
0231     {
0232         vBtnLayout->addWidget(d->extraWidget);
0233     }
0234 
0235     // --------------------------------------------------------
0236 
0237     switch (placement)
0238     {
0239         case ControlButtonsAbove:
0240         {
0241             lay = hBtnLayout;
0242             mainLayout->addLayout(hBtnLayout, 0, 1, 1, 1);
0243             delete vBtnLayout;
0244             break;
0245         }
0246 
0247         case ControlButtonsBelow:
0248         {
0249             lay = hBtnLayout;
0250             mainLayout->addLayout(hBtnLayout, 2, 1, 1, 1);
0251             delete vBtnLayout;
0252             break;
0253         }
0254 
0255         case ControlButtonsLeft:
0256         {
0257             lay = vBtnLayout;
0258             mainLayout->addLayout(vBtnLayout, 1, 0, 1, 1);
0259             delete hBtnLayout;
0260             break;
0261         }
0262 
0263         case ControlButtonsRight:
0264         {
0265             lay = vBtnLayout;
0266             mainLayout->addLayout(vBtnLayout, 1, 2, 1, 1);
0267             delete hBtnLayout;
0268             break;
0269         }
0270 
0271         case NoControlButtons:
0272         default:
0273         {
0274             delete vBtnLayout;
0275             delete hBtnLayout;
0276 
0277             // set all buttons invisible
0278 
0279             setControlButtons(ControlButtons());
0280 
0281             if (d->extraWidget)
0282             {
0283                 d->extraWidget->setVisible(false);
0284             }
0285 
0286             break;
0287         }
0288     }
0289 
0290     setLayout(mainLayout);
0291 
0292     return lay;
0293 }
0294 
0295 void DItemsList::setControlButtons(ControlButtons buttonMask)
0296 {
0297     d->addButton->setVisible(buttonMask & Add);
0298     d->removeButton->setVisible(buttonMask & Remove);
0299     d->moveUpButton->setVisible(buttonMask & MoveUp);
0300     d->moveDownButton->setVisible(buttonMask & MoveDown);
0301     d->clearButton->setVisible(buttonMask & Clear);
0302     d->loadButton->setVisible(buttonMask & Load);
0303     d->saveButton->setVisible(buttonMask & Save);
0304 }
0305 
0306 void DItemsList::setIface(DInfoInterface* const iface)
0307 {
0308     d->iface = iface;
0309 }
0310 
0311 DInfoInterface* DItemsList::iface() const
0312 {
0313     return d->iface;
0314 }
0315 
0316 void DItemsList::setAllowDuplicate(bool allow)
0317 {
0318   d->allowDuplicate = allow;
0319 }
0320 
0321 void DItemsList::setAllowRAW(bool allow)
0322 {
0323     d->allowRAW = allow;
0324 }
0325 
0326 void DItemsList::setIconSize(int size)
0327 {
0328     if      (size < 16)
0329     {
0330         d->iconSize = 16;
0331     }
0332     else if (size > 128)
0333     {
0334         d->iconSize = 128;
0335     }
0336     else
0337     {
0338         d->iconSize = size;
0339     }
0340 
0341     d->listView->setIconSize(QSize(iconSize(), iconSize()));
0342 }
0343 
0344 int DItemsList::iconSize() const
0345 {
0346     return d->iconSize;
0347 }
0348 
0349 void DItemsList::loadImagesFromCurrentSelection()
0350 {
0351     bool selection = checkSelection();
0352 
0353     if (selection && d->iface)
0354     {
0355         QList<QUrl> images = d->iface->currentSelectedItems();
0356 
0357         if (!images.isEmpty())
0358         {
0359             slotAddImages(images);
0360         }
0361     }
0362     else
0363     {
0364         loadImagesFromCurrentAlbum();
0365     }
0366 }
0367 
0368 void DItemsList::loadImagesFromCurrentAlbum()
0369 {
0370     if (!d->iface)
0371     {
0372         return;
0373     }
0374 
0375     QList<QUrl> images = d->iface->currentAlbumItems();
0376 
0377     if (!images.isEmpty())
0378     {
0379         slotAddImages(images);
0380     }
0381 }
0382 
0383 bool DItemsList::checkSelection()
0384 {
0385     if (!d->iface)
0386     {
0387         return false;
0388     }
0389 
0390     QList<QUrl> images = d->iface->currentSelectedItems();
0391 
0392     return (!images.isEmpty());
0393 }
0394 
0395 void DItemsList::slotAddImages(const QList<QUrl>& list)
0396 {
0397     if (list.count() == 0)
0398     {
0399         return;
0400     }
0401 
0402     QList<QUrl> urls;
0403     bool raw = false;
0404 
0405     for (QList<QUrl>::ConstIterator it = list.constBegin() ; it != list.constEnd() ; ++it)
0406     {
0407         QUrl imageUrl = *it;
0408 
0409         // Check if the new item already exist in the list.
0410 
0411         bool found    = false;
0412 
0413         QTreeWidgetItemIterator iter(d->listView);
0414 
0415         while (*iter)
0416         {
0417             DItemsListViewItem* const item = dynamic_cast<DItemsListViewItem*>(*iter);
0418 
0419             if (item && (item->url() == imageUrl))
0420             {
0421                 found = true;
0422             }
0423 
0424             ++iter;
0425         }
0426 
0427         if (d->allowDuplicate || !found)
0428         {
0429             // if RAW files are not allowed, skip the image
0430 
0431             if (!d->allowRAW && DRawDecoder::isRawFile(imageUrl))
0432             {
0433                 raw = true;
0434                 continue;
0435             }
0436 
0437             new DItemsListViewItem(listView(), imageUrl);
0438             urls.append(imageUrl);
0439         }
0440     }
0441 
0442     Q_EMIT signalAddItems(urls);
0443     Q_EMIT signalImageListChanged();
0444     Q_EMIT signalFoundRAWImages(raw);
0445 }
0446 
0447 void DItemsList::slotAddItems()
0448 {
0449     KSharedConfigPtr config = KSharedConfig::openConfig();
0450     KConfigGroup grp        = config->group(objectName());
0451     QUrl lastFileUrl        = QUrl::fromLocalFile(grp.readEntry("Last Image Path",
0452                                                   QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)));
0453 
0454     QList<QUrl> urls        = ImageDialog::getImageURLs(this, lastFileUrl);
0455 
0456     if (!urls.isEmpty())
0457     {
0458         slotAddImages(urls);
0459         grp.writeEntry("Last Image Path", urls.first().adjusted(QUrl::RemoveFilename).toLocalFile());
0460         config->sync();
0461     }
0462 }
0463 
0464 void DItemsList::slotRemoveItems()
0465 {
0466     QList<QTreeWidgetItem*> selectedItemsList = d->listView->selectedItems();
0467     QList<int> itemsIndex;
0468 
0469     for (QList<QTreeWidgetItem*>::const_iterator it = selectedItemsList.constBegin() ;
0470          it != selectedItemsList.constEnd() ; ++it)
0471     {
0472         DItemsListViewItem* const item = dynamic_cast<DItemsListViewItem*>(*it);
0473 
0474         if (item)
0475         {
0476             itemsIndex.append(d->listView->indexFromItem(item).row());
0477 
0478             if (d->processItems.contains(item->url()))
0479             {
0480                 d->processItems.removeAll(item->url());
0481             }
0482 
0483             d->listView->removeItemWidget(*it, 0);
0484             delete *it;
0485         }
0486     }
0487 
0488     Q_EMIT signalRemovedItems(itemsIndex);
0489     Q_EMIT signalImageListChanged();
0490 }
0491 
0492 void DItemsList::slotMoveUpItems()
0493 {
0494     // move above item down, then we don't have to fix the focus
0495 
0496     QModelIndex curIndex   = listView()->currentIndex();
0497 
0498     if (!curIndex.isValid())
0499     {
0500         return;
0501     }
0502 
0503     QModelIndex aboveIndex = listView()->indexAbove(curIndex);
0504 
0505     if (!aboveIndex.isValid())
0506     {
0507         return;
0508     }
0509 
0510     QTreeWidgetItem* const temp  = listView()->takeTopLevelItem(aboveIndex.row());
0511     listView()->insertTopLevelItem(curIndex.row(), temp);
0512 
0513     // this is a quick fix. We loose the extra tags in flickr upload, but at list we don't get a crash
0514 
0515     DItemsListViewItem* const uw = dynamic_cast<DItemsListViewItem*>(temp);
0516 
0517     if (uw)
0518     {
0519         uw->updateItemWidgets();
0520     }
0521 
0522     Q_EMIT signalImageListChanged();
0523     Q_EMIT signalMoveUpItem();
0524 }
0525 
0526 void DItemsList::slotMoveDownItems()
0527 {
0528     // move below item up, then we don't have to fix the focus
0529 
0530     QModelIndex curIndex   = listView()->currentIndex();
0531 
0532     if (!curIndex.isValid())
0533     {
0534         return;
0535     }
0536 
0537     QModelIndex belowIndex = listView()->indexBelow(curIndex);
0538 
0539     if (!belowIndex.isValid())
0540     {
0541         return;
0542     }
0543 
0544     QTreeWidgetItem* const temp  = listView()->takeTopLevelItem(belowIndex.row());
0545     listView()->insertTopLevelItem(curIndex.row(), temp);
0546 
0547     // This is a quick fix. We can loose extra tags in uploader, but at least we don't get a crash
0548 
0549     DItemsListViewItem* const uw = dynamic_cast<DItemsListViewItem*>(temp);
0550 
0551     if (uw)
0552     {
0553         uw->updateItemWidgets();
0554     }
0555 
0556     Q_EMIT signalImageListChanged();
0557     Q_EMIT signalMoveDownItem();
0558 }
0559 
0560 void DItemsList::slotClearItems()
0561 {
0562     listView()->selectAll();
0563     slotRemoveItems();
0564     listView()->clear();
0565 }
0566 
0567 void DItemsList::slotLoadItems()
0568 {
0569     KSharedConfigPtr config = KSharedConfig::openConfig();
0570     KConfigGroup grp        = config->group(objectName());
0571     QUrl lastFileUrl        = QUrl::fromLocalFile(grp.readEntry("Last Images List Path",
0572                                                   QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)));
0573     QUrl loadLevelsFile;
0574     loadLevelsFile          = DFileDialog::getOpenFileUrl(this,
0575                                                           i18nc("@title:window", "Select the Image File List to Load"), lastFileUrl,
0576                                                           i18nc("@option", "All Files (*)"));
0577 
0578     if (loadLevelsFile.isEmpty())
0579     {
0580         qCDebug(DIGIKAM_GENERAL_LOG) << "empty url";
0581         return;
0582     }
0583 
0584     QFile file(loadLevelsFile.toLocalFile());
0585 
0586     qCDebug(DIGIKAM_GENERAL_LOG) << "file path " << loadLevelsFile.toLocalFile();
0587 
0588     if (!file.open(QIODevice::ReadOnly))
0589     {
0590         qCDebug(DIGIKAM_GENERAL_LOG) << "Cannot open file";
0591         return;
0592     }
0593 
0594     QXmlStreamReader xmlReader;
0595     xmlReader.setDevice(&file);
0596 
0597     while (!xmlReader.atEnd())
0598     {
0599         if      (xmlReader.isStartElement() && (xmlReader.name() == QLatin1String("Image")))
0600         {
0601             // get all attributes and its value of a tag in attrs variable.
0602 
0603             QXmlStreamAttributes attrs = xmlReader.attributes();
0604 
0605             // get value of each attribute from QXmlStreamAttributes
0606 
0607             QStringView url             = attrs.value(QLatin1String("url"));
0608 
0609             if (url.isEmpty())
0610             {
0611                 xmlReader.readNext();
0612                 continue;
0613             }
0614 
0615             QList<QUrl> urls;
0616             urls.append(QUrl(url.toString()));
0617 
0618             //allow tools to append a new file
0619 
0620             slotAddImages(urls);
0621 
0622             // read tool Image custom attributes and children element
0623 
0624             Q_EMIT signalXMLLoadImageElement(xmlReader);
0625         }
0626         else if (xmlReader.isStartElement() && (xmlReader.name() != QLatin1String("Images")))
0627         {
0628             // unmanaged start element (it should be tools one)
0629 
0630             Q_EMIT signalXMLCustomElements(xmlReader);
0631         }
0632         else if (xmlReader.isEndElement() && (xmlReader.name() == QLatin1String("Images")))
0633         {
0634             // if EndElement is Images return
0635 
0636             grp.writeEntry("Last Images List Path", loadLevelsFile.adjusted(QUrl::RemoveFilename).toLocalFile());
0637             config->sync();
0638             file.close();
0639             return;
0640         }
0641 
0642         xmlReader.readNext();
0643     }
0644 
0645     file.close();
0646 }
0647 
0648 void DItemsList::slotSaveItems()
0649 {
0650     KSharedConfigPtr config = KSharedConfig::openConfig();
0651     KConfigGroup grp        = config->group(objectName());
0652     QUrl lastFileUrl        = QUrl::fromLocalFile(grp.readEntry("Last Images List Path",
0653                                                   QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)));
0654     QUrl saveLevelsFile;
0655     saveLevelsFile          = DFileDialog::getSaveFileUrl(this,
0656                                                           i18nc("@title:window", "Select the Image File List to Save"),
0657                                                           lastFileUrl,
0658                                                           i18nc("@option", "All Files (*)"));
0659 
0660     qCDebug(DIGIKAM_GENERAL_LOG) << "file url " << saveLevelsFile.toDisplayString();
0661 
0662     if (saveLevelsFile.isEmpty())
0663     {
0664         qCDebug(DIGIKAM_GENERAL_LOG) << "empty url";
0665         return;
0666     }
0667 
0668     QFile file(saveLevelsFile.toLocalFile());
0669 
0670     if (!file.open(QIODevice::WriteOnly))
0671     {
0672         qCDebug(DIGIKAM_GENERAL_LOG) << "Cannot open target file";
0673         return;
0674     }
0675 
0676     QXmlStreamWriter xmlWriter;
0677     xmlWriter.setDevice(&file);
0678 
0679     xmlWriter.setAutoFormatting(true);
0680     xmlWriter.writeStartDocument();
0681 
0682     xmlWriter.writeStartElement(QLatin1String("Images"));
0683 
0684     QTreeWidgetItemIterator it(listView());
0685 
0686     while (*it)
0687     {
0688         DItemsListViewItem* const lvItem = dynamic_cast<DItemsListViewItem*>(*it);
0689 
0690         if (lvItem)
0691         {
0692             xmlWriter.writeStartElement(QLatin1String("Image"));
0693 
0694             xmlWriter.writeAttribute(QLatin1String("url"), lvItem->url().toDisplayString());
0695 
0696             Q_EMIT signalXMLSaveItem(xmlWriter, listView()->indexFromItem(lvItem).row());
0697 
0698             xmlWriter.writeEndElement(); // Image
0699         }
0700 
0701         ++it;
0702     }
0703 
0704     Q_EMIT signalXMLCustomElements(xmlWriter);
0705 
0706     xmlWriter.writeEndElement();  // Images
0707 
0708     xmlWriter.writeEndDocument(); // end document
0709 
0710     grp.writeEntry("Last Images List Path", saveLevelsFile.adjusted(QUrl::RemoveFilename).toLocalFile());
0711     config->sync();
0712     file.close();
0713 }
0714 
0715 void DItemsList::removeItemByUrl(const QUrl& url)
0716 {
0717     bool found;
0718     QList<int> itemsIndex;
0719 
0720     do
0721     {
0722         found = false;
0723         QTreeWidgetItemIterator it(d->listView);
0724 
0725         while (*it)
0726         {
0727             DItemsListViewItem* const item = dynamic_cast<DItemsListViewItem*>(*it);
0728 
0729             if (item && (item->url() == url))
0730             {
0731                 itemsIndex.append(d->listView->indexFromItem(item).row());
0732 
0733                 if (d->processItems.contains(item->url()))
0734                 {
0735                     d->processItems.removeAll(item->url());
0736                 }
0737 
0738                 delete item;
0739                 found = true;
0740                 break;
0741             }
0742 
0743             ++it;
0744         }
0745     }
0746     while (found);
0747 
0748     Q_EMIT signalRemovedItems(itemsIndex);
0749     Q_EMIT signalImageListChanged();
0750 }
0751 
0752 QList<QUrl> DItemsList::imageUrls(bool onlyUnprocessed) const
0753 {
0754     QList<QUrl> list;
0755     QTreeWidgetItemIterator it(d->listView);
0756 
0757     while (*it)
0758     {
0759         DItemsListViewItem* const item = dynamic_cast<DItemsListViewItem*>(*it);
0760 
0761         if (item)
0762         {
0763             if ((onlyUnprocessed == false) || (item->state() != DItemsListViewItem::Success))
0764             {
0765                 list.append(item->url());
0766             }
0767         }
0768 
0769         ++it;
0770     }
0771 
0772     return list;
0773 }
0774 
0775 void DItemsList::slotProgressTimerDone()
0776 {
0777     if (!d->processItems.isEmpty())
0778     {
0779         Q_FOREACH (const QUrl& url, d->processItems)
0780         {
0781             DItemsListViewItem* const item = listView()->findItem(url);
0782 
0783             if (item)
0784             {
0785                 item->setProgressAnimation(d->progressPix->frameAt(d->progressCount));
0786             }
0787         }
0788 
0789         d->progressCount++;
0790 
0791         if (d->progressCount == 8)
0792         {
0793             d->progressCount = 0;
0794         }
0795 
0796         d->progressTimer->start(300);
0797     }
0798 }
0799 
0800 void DItemsList::processing(const QUrl& url)
0801 {
0802     DItemsListViewItem* const item = listView()->findItem(url);
0803 
0804     if (item)
0805     {
0806         d->processItems.append(url);
0807         d->listView->setCurrentItem(item, true);
0808         d->listView->scrollToItem(item);
0809         d->progressTimer->start(300);
0810     }
0811 }
0812 
0813 void DItemsList::processed(const QUrl& url, bool success)
0814 {
0815     DItemsListViewItem* const item = listView()->findItem(url);
0816 
0817     if (item)
0818     {
0819         d->processItems.removeAll(url);
0820         item->setProcessedIcon(QIcon::fromTheme(success ? QLatin1String("dialog-ok-apply")
0821                                                         : QLatin1String("dialog-cancel")).pixmap(16, 16));
0822         item->setState(success ? DItemsListViewItem::Success
0823                                : DItemsListViewItem::Failed);
0824 
0825         if (d->processItems.isEmpty())
0826         {
0827             d->progressTimer->stop();
0828         }
0829     }
0830 }
0831 
0832 void DItemsList::cancelProcess()
0833 {
0834     Q_FOREACH (const QUrl& url, d->processItems)
0835     {
0836         processed(url, false);
0837     }
0838 }
0839 
0840 void DItemsList::clearProcessedStatus()
0841 {
0842     QTreeWidgetItemIterator it(d->listView);
0843 
0844     while (*it)
0845     {
0846         DItemsListViewItem* const lvItem = dynamic_cast<DItemsListViewItem*>(*it);
0847 
0848         if (lvItem)
0849         {
0850             lvItem->setProcessedIcon(QIcon());
0851         }
0852 
0853         ++it;
0854     }
0855 }
0856 
0857 DItemsListView* DItemsList::listView() const
0858 {
0859     return d->listView;
0860 }
0861 
0862 void DItemsList::slotImageListChanged()
0863 {
0864     const QList<QTreeWidgetItem*> selectedItemsList = d->listView->selectedItems();
0865     const bool haveImages                           = !(imageUrls().isEmpty())         && d->controlButtonsEnabled;
0866     const bool haveSelectedImages                   = !(selectedItemsList.isEmpty())   && d->controlButtonsEnabled;
0867     const bool haveOnlyOneSelectedImage             = (selectedItemsList.count() == 1) && d->controlButtonsEnabled;
0868 
0869     d->removeButton->setEnabled(haveSelectedImages);
0870     d->moveUpButton->setEnabled(haveOnlyOneSelectedImage);
0871     d->moveDownButton->setEnabled(haveOnlyOneSelectedImage);
0872     d->clearButton->setEnabled(haveImages);
0873 
0874     // All buttons are enabled / disabled now, but the "Add" button should always be
0875     // enabled, if the buttons are not explicitly disabled with enableControlButtons()
0876 
0877     d->addButton->setEnabled(d->controlButtonsEnabled);
0878 
0879     // TODO: should they be enabled by default now?
0880 
0881     d->loadButton->setEnabled(d->controlButtonsEnabled);
0882     d->saveButton->setEnabled(d->controlButtonsEnabled);
0883 }
0884 
0885 void DItemsList::updateThumbnail(const QUrl& url)
0886 {
0887     d->thumbLoadThread->find(ThumbnailIdentifier(url.toLocalFile()));
0888 }
0889 
0890 void DItemsList::slotThumbnail(const LoadingDescription& desc, const QPixmap& pix)
0891 {
0892     QTreeWidgetItemIterator it(d->listView);
0893 
0894     while (*it)
0895     {
0896         DItemsListViewItem* const item = dynamic_cast<DItemsListViewItem*>(*it);
0897 
0898         if (item && (item->url() == QUrl::fromLocalFile(desc.filePath)))
0899         {
0900             if (!pix.isNull())
0901             {
0902                 item->setThumb(pix.scaled(d->iconSize, d->iconSize, Qt::KeepAspectRatio));
0903             }
0904 
0905             if (!d->allowDuplicate)
0906             {
0907                 return;
0908             }
0909         }
0910 
0911         ++it;
0912     }
0913 }
0914 
0915 DItemsListViewItem* DItemsListView::getCurrentItem() const
0916 {
0917     QTreeWidgetItem* const currentTreeItem = currentItem();
0918 
0919     if (!currentTreeItem)
0920     {
0921         return nullptr;
0922     }
0923 
0924     return dynamic_cast<DItemsListViewItem*>(currentTreeItem);
0925 }
0926 
0927 QUrl DItemsList::getCurrentUrl() const
0928 {
0929     DItemsListViewItem* const currentItem = d->listView->getCurrentItem();
0930 
0931     if (!currentItem)
0932     {
0933         return QUrl();
0934     }
0935 
0936     return currentItem->url();
0937 }
0938 
0939 void DItemsList::setCurrentUrl(const QUrl& url)
0940 {
0941     QTreeWidgetItemIterator it(d->listView);
0942 
0943     while (*it)
0944     {
0945         DItemsListViewItem* const item = dynamic_cast<DItemsListViewItem*>(*it);
0946 
0947         if (item && (item->url() == url))
0948         {
0949             d->listView->setCurrentItem(item);
0950             return;
0951         }
0952 
0953         ++it;
0954     }
0955 }
0956 
0957 void DItemsList::setIsLessThanHandler(DItemsListIsLessThanHandler fncptr)
0958 {
0959     d->isLessThan = fncptr;
0960 }
0961 
0962 DItemsListIsLessThanHandler DItemsList::isLessThanHandler() const
0963 {
0964     return d->isLessThan;
0965 }
0966 
0967 } // namespace Digikam
0968 
0969 #include "moc_ditemslist.cpp"
0970 
0971 #include "moc_ditemslist_p.cpp"