1 /* ============================================================
2  *
3  * This file is a part of digiKam project
4  * https://www.digikam.org
5  *
6  * Date        : 2009-02-13
7  * Description : tabbed queue items list.
8  *
9  * Copyright (C) 2009-2021 by Gilles Caulier <caulier dot gilles at gmail dot com>
10  *
11  * This program is free software; you can redistribute it
12  * and/or modify it under the terms of the GNU General
13  * Public License as published by the Free Software Foundation;
14  * either version 2, or (at your option)
15  * any later version.
16  *
17  * This program is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  *
22  * ============================================================ */
23 
24 #include "queuepool.h"
25 
26 // Qt includes
27 
28 #include <QTabBar>
29 #include <QApplication>
30 #include <QIcon>
31 
32 // KDE includes
33 
34 #include <klocalizedstring.h>
35 
36 // Local includes
37 
38 #include "digikam_debug.h"
39 #include "dmessagebox.h"
40 #include "applicationsettings.h"
41 #include "iccsettings.h"
42 #include "metaenginesettings.h"
43 #include "ddragobjects.h"
44 #include "queuelist.h"
45 #include "workflowmanager.h"
46 #include "workflowdlg.h"
47 #include "queuesettings.h"
48 #include "loadingcacheinterface.h"
49 
50 namespace Digikam
51 {
52 
QueuePoolBar(QWidget * const parent)53 QueuePoolBar::QueuePoolBar(QWidget* const parent)
54     : QTabBar(parent)
55 {
56   setAcceptDrops(true);
57   setMouseTracking(true);
58 }
59 
~QueuePoolBar()60 QueuePoolBar::~QueuePoolBar()
61 {
62 }
63 
dragEnterEvent(QDragEnterEvent * e)64 void QueuePoolBar::dragEnterEvent(QDragEnterEvent* e)
65 {
66     int tab = tabAt(e->pos());
67 
68     if (tab != -1)
69     {
70         bool accept = false;
71 
72         // The receivers of the testCanDecode() signal has to adjust 'accept' accordingly.
73 
74         emit signalTestCanDecode(e, accept);
75 
76         e->setAccepted(accept);
77 
78         return;
79     }
80 
81     QTabBar::dragEnterEvent(e);
82 }
83 
dragMoveEvent(QDragMoveEvent * e)84 void QueuePoolBar::dragMoveEvent(QDragMoveEvent* e)
85 {
86     int tab = tabAt(e->pos());
87 
88     if (tab != -1)
89     {
90         bool accept = false;
91 
92         // The receivers of the testCanDecode() signal has to adjust 'accept' accordingly.
93 
94         emit signalTestCanDecode(e, accept);
95 
96         e->setAccepted(accept);
97 
98         return;
99     }
100 
101     QTabBar::dragMoveEvent(e);
102 }
103 
104 // --------------------------------------------------------------------------------------------
105 
QueuePool(QWidget * const parent)106 QueuePool::QueuePool(QWidget* const parent)
107     : QTabWidget(parent)
108 {
109     setTabBar(new QueuePoolBar(this));
110     setTabsClosable(false);
111     setAcceptDrops(true);
112     slotAddQueue();
113 
114     connect(this, SIGNAL(currentChanged(int)),
115             this, SLOT(slotQueueSelected(int)));
116 
117     connect(this, SIGNAL(tabCloseRequested(int)),
118             this, SLOT(slotCloseQueueRequest(int)));
119 
120     connect(tabBar(), SIGNAL(signalTestCanDecode(const QDragMoveEvent*,bool&)),
121             this, SLOT(slotTestCanDecode(const QDragMoveEvent*,bool&)));
122 
123     // -- FileWatch connections ------------------------------
124 
125     LoadingCacheInterface::connectToSignalFileChanged(this, SLOT(slotFileChanged(QString)));
126 }
127 
~QueuePool()128 QueuePool::~QueuePool()
129 {
130 }
131 
keyPressEvent(QKeyEvent * event)132 void QueuePool::keyPressEvent(QKeyEvent* event)
133 {
134     if (event->key() == Qt::Key_Delete)
135     {
136         slotRemoveSelectedItems();
137     }
138     else
139     {
140         QTabWidget::keyPressEvent(event);
141     }
142 }
143 
setBusy(bool b)144 void QueuePool::setBusy(bool b)
145 {
146     tabBar()->setEnabled(!b);
147 
148     for (int i = 0 ; i < count() ; ++i)
149     {
150         QueueListView* const queue = dynamic_cast<QueueListView*>(widget(i));
151 
152         if (queue)
153         {
154             queue->viewport()->setEnabled(!b);
155         }
156     }
157 }
158 
currentQueue() const159 QueueListView* QueuePool::currentQueue() const
160 {
161     return (dynamic_cast<QueueListView*>(currentWidget()));
162 }
163 
currentTitle() const164 QString QueuePool::currentTitle() const
165 {
166     return queueTitle(currentIndex());
167 }
168 
findQueueByItemId(qlonglong id) const169 QueueListView* QueuePool::findQueueByItemId(qlonglong id) const
170 {
171     for (int i = 0 ; i < count() ; ++i)
172     {
173         QueueListView* const queue = dynamic_cast<QueueListView*>(widget(i));
174 
175         if (queue && queue->findItemById(id))
176         {
177             return queue;
178         }
179     }
180 
181     return nullptr;
182 }
183 
setItemBusy(qlonglong id)184 void QueuePool::setItemBusy(qlonglong id)
185 {
186     QueueListView* const queue = findQueueByItemId(id);
187 
188     if (queue)
189     {
190         queue->setItemBusy(id);
191     }
192 }
193 
findQueueByIndex(int index) const194 QueueListView* QueuePool::findQueueByIndex(int index) const
195 {
196     return (dynamic_cast<QueueListView*>(widget(index)));
197 }
198 
queuesMap() const199 QMap<int, QString> QueuePool::queuesMap() const
200 {
201     QMap<int, QString> map;
202 
203     for (int i = 0 ; i < count() ; ++i)
204     {
205         map.insert(i, queueTitle(i));
206     }
207 
208     return map;
209 }
210 
queueTitle(int index) const211 QString QueuePool::queueTitle(int index) const
212 {
213     // NOTE: clean up tab title. With QTabWidget, it sound like mistake is added, as '&' and space.
214     // NOTE update, & is an usability helper to allow keyboard access -teemu
215 
216     return (tabText(index).remove(QLatin1Char('&')).remove(QLatin1Char(' ')));
217 }
218 
slotAddQueue()219 void QueuePool::slotAddQueue()
220 {
221     QueueListView* const queue = new QueueListView(this);
222 
223     if (!queue)
224     {
225         return;
226     }
227 
228     int index = addTab(queue, QIcon::fromTheme(QLatin1String("run-build")), QString::fromUtf8("#%1").arg(count() + 1));
229 
230     connect(queue, SIGNAL(signalQueueContentsChanged()),
231             this, SIGNAL(signalQueueContentsChanged()));
232 
233     connect(queue, SIGNAL(itemSelectionChanged()),
234             this, SIGNAL(signalItemSelectionChanged()));
235 
236     emit signalQueuePoolChanged();
237 
238     setCurrentIndex(index);
239 }
240 
queueItemsList(int index) const241 QueuePoolItemsList QueuePool::queueItemsList(int index) const
242 {
243     QueuePoolItemsList qpool;
244 
245     QueueListView* const queue = dynamic_cast<QueueListView*>(widget(index));
246 
247     if (queue)
248     {
249         ItemInfoList list = queue->pendingItemsList();
250 
251         for (ItemInfoList::const_iterator it = list.constBegin() ; it != list.constEnd() ; ++it)
252         {
253             ItemInfo info = *it;
254             ItemInfoSet set(index, info);
255             qpool.append(set);
256         }
257     }
258 
259     return qpool;
260 }
261 
totalPendingItems() const262 int QueuePool::totalPendingItems() const
263 {
264     int items = 0;
265 
266     for (int i = 0 ; i < count() ; ++i)
267     {
268         QueueListView* const queue = dynamic_cast<QueueListView*>(widget(i));
269 
270         if (queue)
271         {
272             items += queue->pendingItemsCount();
273         }
274     }
275 
276     return items;
277 }
278 
totalPendingTasks() const279 int QueuePool::totalPendingTasks() const
280 {
281     int tasks = 0;
282 
283     for (int i = 0 ; i < count() ; ++i)
284     {
285         QueueListView* const queue = dynamic_cast<QueueListView*>(widget(i));
286 
287         if (queue)
288         {
289             tasks += queue->pendingTasksCount();
290         }
291     }
292 
293     return tasks;
294 }
295 
slotRemoveCurrentQueue()296 void QueuePool::slotRemoveCurrentQueue()
297 {
298     QueueListView* const queue = currentQueue();
299 
300     if (!queue)
301     {
302         return;
303     }
304 
305     removeTab(indexOf(queue));
306 
307     if (count() == 0)
308     {
309         slotAddQueue();
310     }
311     else
312     {
313         for (int i = 0; i < count(); ++i)
314         {
315             setTabText(i, QString::fromUtf8("#%1").arg(i + 1));
316         }
317     }
318 
319     emit signalQueuePoolChanged();
320 }
321 
saveWorkflow() const322 bool QueuePool::saveWorkflow() const
323 {
324     QueueListView* const queue = currentQueue();
325 
326     if (queue)
327     {
328         WorkflowManager* const mngr = WorkflowManager::instance();
329         Workflow wf;
330         wf.qSettings = queue->settings();
331         wf.aTools    = queue->assignedTools().m_toolsList;
332 
333         if (WorkflowDlg::createNew(wf))
334         {
335             mngr->insert(wf);
336             mngr->save();
337 
338             return true;
339         }
340     }
341 
342     return false;
343 }
344 
slotClearList()345 void QueuePool::slotClearList()
346 {
347     QueueListView* const queue = currentQueue();
348 
349     if (queue)
350     {
351         queue->slotClearList();
352     }
353 }
354 
slotRemoveSelectedItems()355 void QueuePool::slotRemoveSelectedItems()
356 {
357     QueueListView* const queue = currentQueue();
358 
359     if (queue)
360     {
361         queue->slotRemoveSelectedItems();
362     }
363 }
364 
slotRemoveItemsDone()365 void QueuePool::slotRemoveItemsDone()
366 {
367     QueueListView* const queue = currentQueue();
368 
369     if (queue)
370     {
371         queue->slotRemoveItemsDone();
372     }
373 }
374 
slotAddItems(const ItemInfoList & list,int queueId)375 void QueuePool::slotAddItems(const ItemInfoList& list, int queueId)
376 {
377     QueueListView* const queue = findQueueByIndex(queueId);
378 
379     if (queue)
380     {
381         queue->slotAddItems(list);
382     }
383 }
384 
slotAssignedToolsChanged(const AssignedBatchTools & tools4Item)385 void QueuePool::slotAssignedToolsChanged(const AssignedBatchTools& tools4Item)
386 {
387     QueueListView* const queue = currentQueue();
388 
389     if (queue)
390     {
391         queue->slotAssignedToolsChanged(tools4Item);
392     }
393 }
394 
slotQueueSelected(int index)395 void QueuePool::slotQueueSelected(int index)
396 {
397     QueueListView* const queue = dynamic_cast<QueueListView*>(widget(index));
398 
399     if (queue)
400     {
401         emit signalItemSelectionChanged();
402         emit signalQueueSelected(index, queue->settings(), queue->assignedTools());
403     }
404 }
405 
slotCloseQueueRequest(int index)406 void QueuePool::slotCloseQueueRequest(int index)
407 {
408     removeTab(index);
409 
410     if (count() == 0)
411     {
412         slotAddQueue();
413     }
414 
415     emit signalQueuePoolChanged();
416 }
417 
removeTab(int index)418 void QueuePool::removeTab(int index)
419 {
420     QueueListView* const queue = dynamic_cast<QueueListView*>(widget(index));
421 
422     if (!queue)
423     {
424         return;
425     }
426 
427     int count = queue->pendingItemsCount();
428 
429     if (count > 0)
430     {
431         int ret = QMessageBox::question(this, qApp->applicationName(),
432                                         i18np("There is still 1 unprocessed item in \"%2\".\nDo you want to close this queue?",
433                                               "There are still %1 unprocessed items in \"%2\".\nDo you want to close this queue?",
434                                               count, queueTitle(index)),
435                                         QMessageBox::Yes | QMessageBox::No);
436 
437         if (ret == QMessageBox::No)
438         {
439             return;
440         }
441     }
442 
443     QTabWidget::removeTab(index);
444 }
445 
slotTestCanDecode(const QDragMoveEvent * e,bool & accept)446 void QueuePool::slotTestCanDecode(const QDragMoveEvent* e, bool& accept)
447 {
448     int              albumID;
449     QList<int>       albumIDs;
450     QList<qlonglong> imageIDs;
451     QList<QUrl>      urls;
452 
453     if (DItemDrag::decode(e->mimeData(), urls, albumIDs, imageIDs) ||
454         DAlbumDrag::decode(e->mimeData(), urls, albumID)           ||
455         DTagListDrag::canDecode(e->mimeData()))
456     {
457         accept = true;
458 
459         return;
460     }
461 
462     accept = false;
463 }
464 
slotSettingsChanged(const QueueSettings & settings)465 void QueuePool::slotSettingsChanged(const QueueSettings& settings)
466 {
467     QueueListView* const queue = currentQueue();
468 
469     if (queue)
470     {
471         queue->setSettings(settings);
472     }
473 }
474 
customRenamingRulesAreValid() const475 bool QueuePool::customRenamingRulesAreValid() const
476 {
477     QStringList list;
478 
479     for (int i = 0 ; i < count() ; ++i)
480     {
481         QueueListView* const queue = findQueueByIndex(i);
482 
483         if (queue)
484         {
485             if (queue->settings().renamingRule == QueueSettings::CUSTOMIZE &&
486                 queue->settings().renamingParser.isEmpty())
487             {
488                 list.append(queueTitle(i));
489             }
490         }
491     }
492 
493     if (!list.isEmpty())
494     {
495         DMessageBox::showInformationList(QMessageBox::Critical,
496                                          qApp->activeWindow(),
497                                          qApp->applicationName(),
498                                          i18n("Custom renaming rules are invalid for Queues listed below. "
499                                               "Please fix them."),
500                                          list);
501         return false;
502     }
503 
504     return true;
505 }
506 
assignedBatchToolsListsAreValid() const507 bool QueuePool::assignedBatchToolsListsAreValid() const
508 {
509     QStringList list;
510 
511     for (int i = 0 ; i < count() ; ++i)
512     {
513         QueueListView* const queue = findQueueByIndex(i);
514 
515         if (queue)
516         {
517             if (queue->assignedTools().m_toolsList.isEmpty())
518             {
519                 list.append(queueTitle(i));
520             }
521         }
522     }
523 
524     if (!list.isEmpty())
525     {
526         DMessageBox::showInformationList(QMessageBox::Critical,
527                                          qApp->activeWindow(),
528                                          qApp->applicationName(),
529                                          i18n("Assigned batch tools list is empty for Queues listed below. "
530                                               "Please assign tools."),
531                                          list);
532         return false;
533     }
534 
535     return true;
536 }
537 
slotFileChanged(const QString & filePath)538 void QueuePool::slotFileChanged(const QString& filePath)
539 {
540     for (int i = 0 ; i < count() ; ++i)
541     {
542         QueueListView* const queue = dynamic_cast<QueueListView*>(widget(i));
543 
544         if (queue)
545         {
546             queue->reloadThumbs(QUrl::fromLocalFile(filePath));
547         }
548     }
549 }
550 
applySettings()551 void QueuePool::applySettings()
552 {
553     for (int i = 0 ; i < count() ; ++i)
554     {
555         QueueListView* const queue = dynamic_cast<QueueListView*>(widget(i));
556 
557         if (queue)
558         {
559             // Show/hide tool-tips settings.
560 
561             queue->setEnableToolTips(ApplicationSettings::instance()->getShowToolTips());
562 
563             // Reset Exif Orientation settings.
564 
565             QueueSettings prm = queue->settings();
566             prm.exifSetOrientation = MetaEngineSettings::instance()->settings().exifRotate;
567 
568             // Apply Color Management rules to RAW images decoding settings
569 
570             // If digiKam Color Management is enable, no need to correct color of decoded RAW image,
571             // else, sRGB color workspace will be used.
572 
573             ICCSettingsContainer ICCSettings = IccSettings::instance()->settings();
574 
575             if (ICCSettings.enableCM)
576             {
577                 if ((ICCSettings.defaultUncalibratedBehavior & ICCSettingsContainer::AskUser) ||
578                     (ICCSettings.defaultUncalibratedBehavior & ICCSettingsContainer::AutomaticColors))
579                 {
580                     prm.rawDecodingSettings.outputColorSpace = DRawDecoderSettings::CUSTOMOUTPUTCS;
581                     prm.rawDecodingSettings.outputProfile    = ICCSettings.workspaceProfile;
582                 }
583                 else
584                 {
585                     prm.rawDecodingSettings.outputColorSpace = DRawDecoderSettings::RAWCOLOR;
586                 }
587             }
588             else
589             {
590                 prm.rawDecodingSettings.outputColorSpace = DRawDecoderSettings::SRGB;
591             }
592 
593             queue->setSettings(prm);
594         }
595     }
596 }
597 
598 } // namespace Digikam
599