1 /* ============================================================
2  *
3  * This file is a part of digiKam project
4  * https://www.digikam.org
5  *
6  * Date        : 2004-11-22
7  * Description : digiKam image editor - Internal setup
8  *
9  * Copyright (C) 2004-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 "imagewindow_p.h"
25 
26 namespace Digikam
27 {
28 
setupActions()29 void ImageWindow::setupActions()
30 {
31     setupStandardActions();
32 
33     KActionCollection* const ac = actionCollection();
34 
35     d->toMainWindowAction = new QAction(QIcon::fromTheme(QLatin1String("view-list-icons")),
36                                         i18nc("@action Finish editing, close editor, back to main window", "Close Editor"), this);
37     connect(d->toMainWindowAction, SIGNAL(triggered()), this, SLOT(slotToMainWindow()));
38     ac->addAction(QLatin1String("imageview_tomainwindow"), d->toMainWindowAction);
39 
40 
41     // -- Special Delete actions ---------------------------------------------------------------
42 
43     // Pop up dialog to ask user whether to permanently delete
44 
45     d->fileDeletePermanentlyAction = new QAction(QIcon::fromTheme(QLatin1String("edit-delete")), i18n("Delete File Permanently"), this);
46     connect(d->fileDeletePermanentlyAction, SIGNAL(triggered()),
47             this, SLOT(slotDeleteCurrentItemPermanently()));
48     ac->addAction(QLatin1String("image_delete_permanently"), d->fileDeletePermanentlyAction);
49     ac->setDefaultShortcut(d->fileDeletePermanentlyAction, Qt::SHIFT + Qt::Key_Delete);
50 
51     // These two actions are hidden, no menu entry, no toolbar entry, no shortcut.
52     // Power users may add them.
53 
54     d->fileDeletePermanentlyDirectlyAction = new QAction(QIcon::fromTheme(QLatin1String("edit-delete")),
55                                                          i18n("Delete Permanently without Confirmation"), this);
56     connect(d->fileDeletePermanentlyDirectlyAction, SIGNAL(triggered()),
57             this, SLOT(slotDeleteCurrentItemPermanentlyDirectly()));
58     ac->addAction(QLatin1String("image_delete_permanently_directly"),
59                                   d->fileDeletePermanentlyDirectlyAction);
60 
61     d->fileTrashDirectlyAction = new QAction(QIcon::fromTheme(QLatin1String("user-trash")),
62                                              i18n("Move to Trash without Confirmation"), this);
63     connect(d->fileTrashDirectlyAction, SIGNAL(triggered()),
64             this, SLOT(slotTrashCurrentItemDirectly()));
65     ac->addAction(QLatin1String("image_trash_directly"), d->fileTrashDirectlyAction);
66 
67     // ---------------------------------------------------------------------------------
68 
69     createHelpActions();
70 
71     // Labels shortcuts must be registered here to be saved in XML GUI files if user customize it.
72 
73     TagsActionMngr::defaultManager()->registerLabelsActions(ac);
74 
75     QAction* const editTitles = new QAction(i18n("Edit Titles"), this);
76     ac->addAction(QLatin1String("edit_titles"), editTitles);
77     ac->setDefaultShortcut(editTitles, Qt::ALT + Qt::SHIFT + Qt::Key_T);
78     connect(editTitles, SIGNAL(triggered()), this, SLOT(slotRightSideBarActivateTitles()));
79 
80     QAction* const editComments = new QAction(i18n("Edit Comments"), this);
81     ac->addAction(QLatin1String("edit_comments"), editComments);
82     ac->setDefaultShortcut(editComments, Qt::ALT + Qt::SHIFT + Qt::Key_C);
83     connect(editComments, SIGNAL(triggered()), this, SLOT(slotRightSideBarActivateComments()));
84 
85     QAction* const assignedTags = new QAction(i18n("Show Assigned Tags"), this);
86     ac->addAction(QLatin1String("assigned_tags"), assignedTags);
87     ac->setDefaultShortcut(assignedTags, Qt::ALT + Qt::SHIFT + Qt::Key_A);
88     connect(assignedTags, SIGNAL(triggered()), this, SLOT(slotRightSideBarActivateAssignedTags()));
89 }
90 
setupConnections()91 void ImageWindow::setupConnections()
92 {
93     setupStandardConnections();
94 
95     connect(this, SIGNAL(loadCurrentLater()),
96             this, SLOT(slotLoadCurrent()), Qt::QueuedConnection);
97 
98     // To toggle properly keyboards shortcuts from comments & tags side bar tab.
99 
100     connect(d->rightSideBar, SIGNAL(signalNextItem()),
101             this, SLOT(slotForward()));
102 
103     connect(d->rightSideBar, SIGNAL(signalPrevItem()),
104             this, SLOT(slotBackward()));
105 
106     connect(d->rightSideBar->getFiltersHistoryTab(), SIGNAL(actionTriggered(ItemInfo)),
107             this, SLOT(openImage(ItemInfo)));
108 
109     connect(this, SIGNAL(signalSelectionChanged(QRect)),
110             d->rightSideBar, SLOT(slotImageSelectionChanged(QRect)));
111 
112     connect(this, SIGNAL(signalNoCurrentItem()),
113             d->rightSideBar, SLOT(slotNoCurrentItem()));
114 
115     ItemAttributesWatch* watch = ItemAttributesWatch::instance();
116 
117     connect(watch, SIGNAL(signalFileMetadataChanged(QUrl)),
118             this, SLOT(slotFileMetadataChanged(QUrl)));
119 
120 /*
121     connect(CoreDbAccess::databaseWatch(), SIGNAL(collectionImageChange(CollectionImageChangeset)),
122             this, SLOT(slotCollectionImageChange(CollectionImageChangeset)),
123             Qt::QueuedConnection);
124 
125     connect(d->imageFilterModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
126             this, SLOT(slotRowsAboutToBeRemoved(QModelIndex,int,int)));
127 */
128 
129     connect(d->thumbBar, SIGNAL(currentChanged(ItemInfo)),
130             this, SLOT(slotThumbBarImageSelected(ItemInfo)));
131 
132     connect(d->dragDropHandler, SIGNAL(itemInfosDropped(QList<ItemInfo>)),
133             this, SLOT(slotDroppedOnThumbbar(QList<ItemInfo>)));
134 
135     connect(d->thumbBarDock, SIGNAL(dockLocationChanged(Qt::DockWidgetArea)),
136             d->thumbBar, SLOT(slotDockLocationChanged(Qt::DockWidgetArea)));
137 
138     connect(d->imageInfoModel, SIGNAL(allRefreshingFinished()),
139             this, SLOT(slotThumbBarModelReady()));
140 
141     connect(ApplicationSettings::instance(), SIGNAL(setupChanged()),
142             this, SLOT(slotSetupChanged()));
143 }
144 
setupUserArea()145 void ImageWindow::setupUserArea()
146 {
147     KSharedConfig::Ptr config = KSharedConfig::openConfig();
148     KConfigGroup group        = config->group(configGroupName());
149 
150     QWidget* const widget   = new QWidget(this);
151     QHBoxLayout* const hlay = new QHBoxLayout(widget);
152     m_splitter              = new SidebarSplitter(widget);
153 
154     d->viewContainer        = new KMainWindow(widget, Qt::Widget);
155     m_splitter->addWidget(d->viewContainer);
156     m_stackView             = new EditorStackView(d->viewContainer);
157     m_canvas                = new Canvas(m_stackView);
158     d->viewContainer->setCentralWidget(m_stackView);
159 
160     m_splitter->setFrameStyle(QFrame::NoFrame);
161     m_splitter->setFrameShape(QFrame::NoFrame);
162     m_splitter->setFrameShadow(QFrame::Plain);
163     m_splitter->setStretchFactor(0, 10);      // set Canvas default size to max.
164     m_splitter->setOpaqueResize(false);
165 
166     m_canvas->makeDefaultEditingCanvas();
167     m_stackView->setCanvas(m_canvas);
168     m_stackView->setViewMode(EditorStackView::CanvasMode);
169 
170     d->rightSideBar = new ItemPropertiesSideBarDB(widget, m_splitter, Qt::RightEdge, true);
171     d->rightSideBar->setObjectName(QLatin1String("ImageEditor Right Sidebar"));
172     d->rightSideBar->getFiltersHistoryTab()->addOpenImageAction();
173 
174     hlay->addWidget(m_splitter);
175     hlay->addWidget(d->rightSideBar);
176     hlay->setContentsMargins(QMargins());
177     hlay->setSpacing(0);
178 
179     // Code to check for the now depreciated HorizontalThumbar directive. It
180     // is found, it is honored and deleted. The state will from than on be saved
181     // by d->viewContainers built-in mechanism.
182 
183     Qt::DockWidgetArea dockArea = Qt::LeftDockWidgetArea;
184 
185     if (group.hasKey(d->configHorizontalThumbbarEntry))
186     {
187         if (group.readEntry(d->configHorizontalThumbbarEntry, true))
188         {
189             // Horizontal thumbbar layout
190 
191             dockArea    = Qt::TopDockWidgetArea;
192         }
193 
194         group.deleteEntry(d->configHorizontalThumbbarEntry);
195     }
196 
197     d->imageInfoModel   = new ItemListModel(this);
198 
199     d->imageFilterModel = new ItemFilterModel(this);
200     d->imageFilterModel->setSourceItemModel(d->imageInfoModel);
201 
202     d->imageInfoModel->setWatchFlags(d->imageFilterModel->suggestedWatchFlags());
203     d->imageInfoModel->setThumbnailLoadThread(ThumbnailLoadThread::defaultIconViewThread());
204 
205     d->imageFilterModel->setCategorizationMode(ItemSortSettings::NoCategories);
206     d->imageFilterModel->setStringTypeNatural(ApplicationSettings::instance()->isStringTypeNatural());
207     d->imageFilterModel->setSortRole((ItemSortSettings::SortRole)ApplicationSettings::instance()->getImageSortOrder());
208     d->imageFilterModel->setSortOrder((ItemSortSettings::SortOrder)ApplicationSettings::instance()->getImageSorting());
209     d->imageFilterModel->setAllGroupsOpen(true);    // disable filtering out by group, see bug #283847
210     d->imageFilterModel->sort(0);                   // an initial sorting is necessary
211 
212     d->dragDropHandler  = new ItemDragDropHandler(d->imageInfoModel);
213     d->dragDropHandler->setReadOnlyDrop(true);
214     d->imageInfoModel->setDragDropHandler(d->dragDropHandler);
215 
216     // The thumb bar is placed in a detachable/dockable widget.
217 
218     d->thumbBarDock     = new ThumbBarDock(d->viewContainer, Qt::Tool);
219     d->thumbBarDock->setObjectName(QLatin1String("editor_thumbbar"));
220 
221     d->thumbBar         = new ItemThumbnailBar(d->thumbBarDock);
222     d->thumbBar->setModels(d->imageInfoModel, d->imageFilterModel);
223 
224     d->thumbBarDock->setWidget(d->thumbBar);
225     d->viewContainer->addDockWidget(dockArea, d->thumbBarDock);
226     d->thumbBarDock->setFloating(false);
227 /*
228     d->thumbBar->slotDockLocationChanged(dockArea);
229 */
230     setCentralWidget(widget);
231 }
232 
addServicesMenu()233 void ImageWindow::addServicesMenu()
234 {
235     addServicesMenuForUrl(d->currentUrl());
236 }
237 
slotContextMenu()238 void ImageWindow::slotContextMenu()
239 {
240     if (m_contextMenu)
241     {
242         m_contextMenu->addSeparator();
243         addServicesMenu();
244         m_contextMenu->addSeparator();
245 
246         // Bulk assignment/removal of tags --------------------------
247 
248         QList<qlonglong> idList;
249         idList << d->currentItemInfo.id();
250 
251         TagsPopupMenu* const assignTagsMenu = new TagsPopupMenu(idList, TagsPopupMenu::RECENTLYASSIGNED, this);
252         TagsPopupMenu* const removeTagsMenu = new TagsPopupMenu(idList, TagsPopupMenu::REMOVE, this);
253         assignTagsMenu->menuAction()->setText(i18n("Assign Tag"));
254         removeTagsMenu->menuAction()->setText(i18n("Remove Tag"));
255 
256         m_contextMenu->addSeparator();
257 
258         m_contextMenu->addMenu(assignTagsMenu);
259         m_contextMenu->addMenu(removeTagsMenu);
260 
261         connect(assignTagsMenu, SIGNAL(signalTagActivated(int)),
262                 this, SLOT(slotAssignTag(int)));
263 
264         connect(removeTagsMenu, SIGNAL(signalTagActivated(int)),
265                 this, SLOT(slotRemoveTag(int)));
266 
267         connect(assignTagsMenu, SIGNAL(signalPopupTagsView()),
268                 d->rightSideBar, SLOT(slotPopupTagsView()));
269 
270         if (!CoreDbAccess().db()->hasTags(idList))
271         {
272             m_contextMenu->menuAction()->setEnabled(false);
273         }
274 
275         m_contextMenu->addSeparator();
276 
277         // Assign Labels -------------------------------------------
278 
279         QMenu* const menuLabels           = new QMenu(i18n("Assign Labels"), m_contextMenu);
280         PickLabelMenuAction* const pmenu  = new PickLabelMenuAction(m_contextMenu);
281         ColorLabelMenuAction* const cmenu = new ColorLabelMenuAction(m_contextMenu);
282         RatingMenuAction* const rmenu     = new RatingMenuAction(m_contextMenu);
283         menuLabels->addAction(pmenu->menuAction());
284         menuLabels->addAction(cmenu->menuAction());
285         menuLabels->addAction(rmenu->menuAction());
286         m_contextMenu->addMenu(menuLabels);
287 
288         connect(pmenu, SIGNAL(signalPickLabelChanged(int)),
289                 this, SLOT(slotAssignPickLabel(int)));
290 
291         connect(cmenu, SIGNAL(signalColorLabelChanged(int)),
292                 this, SLOT(slotAssignColorLabel(int)));
293 
294         connect(rmenu, SIGNAL(signalRatingChanged(int)),
295                 this, SLOT(slotAssignRating(int)));
296 
297         // --------------------------------------------------------------
298 
299         m_contextMenu->exec(QCursor::pos());
300 
301         delete assignTagsMenu;
302         delete removeTagsMenu;
303         delete cmenu;
304         delete pmenu;
305         delete rmenu;
306         delete menuLabels;
307     }
308 }
309 
310 } // namespace Digikam
311