1 /***************************************************************************
2 * Copyright (C) 2008-2012 by Peter Penz <peter.penz19@gmail.com> *
4 * Based on KFilePlacesView from kdelibs: *
5 * Copyright (C) 2007 Kevin Ottens <ervin@kde.org> *
6 * Copyright (C) 2007 David Faure <faure@kde.org> *
8 * This program is free software; you can redistribute it and/or modify *
9 * it under the terms of the GNU General Public License as published by *
10 * the Free Software Foundation; either version 2 of the License, or *
11 * (at your option) any later version. *
13 * This program is distributed in the hope that it will be useful, *
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
16 * GNU General Public License for more details. *
18 * You should have received a copy of the GNU General Public License *
19 * along with this program; if not, write to the *
20 * Free Software Foundation, Inc., *
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
22 ***************************************************************************/
24 #include "placespanel.h"
26 #include <KConfigGroup>
31 #include <KIO/JobUiDelegate>
33 #include <kitemviews/kitemlistcontainer.h>
34 #include <kitemviews/kitemlistcontroller.h>
35 #include <kitemviews/kitemlistselectionmanager.h>
36 #include <kitemviews/kstandarditem.h>
37 #include <kitemviews/kstandarditemlistview.h>
39 #include <KMessageBox>
40 #include <KNotification>
41 #include "placesitem.h"
42 #include "placesitemeditdialog.h"
43 #include "placesitemlistgroupheader.h"
44 #include "placesitemlistwidget.h"
45 #include "placesitemmodel.h"
46 #include <views/draganddrophelper.h>
47 #include <QVBoxLayout>
51 #include <Nepomuk/Query/ComparisonTerm>
52 #include <Nepomuk/Query/LiteralTerm>
53 #include <Nepomuk/Query/Query>
54 #include <Nepomuk/Query/ResourceTypeTerm>
55 #include <Nepomuk/Vocabulary/NFO>
56 #include <Nepomuk/Vocabulary/NIE>
59 PlacesPanel::PlacesPanel(QWidget
* parent
) :
66 PlacesPanel::~PlacesPanel()
70 bool PlacesPanel::urlChanged()
75 void PlacesPanel::showEvent(QShowEvent
* event
)
77 if (event
->spontaneous()) {
78 Panel::showEvent(event
);
83 // Postpone the creating of the controller to the first show event.
84 // This assures that no performance and memory overhead is given when the folders panel is not
85 // used at all and stays invisible.
86 m_model
= new PlacesItemModel(this);
87 m_model
->setGroupedSorting(true);
88 m_model
->setSortRole("group");
89 connect(m_model
, SIGNAL(errorMessage(QString
)),
90 this, SIGNAL(errorMessage(QString
)));
92 KStandardItemListView
* view
= new KStandardItemListView();
93 view
->setWidgetCreator(new KItemListWidgetCreator
<PlacesItemListWidget
>());
94 view
->setGroupHeaderCreator(new KItemListGroupHeaderCreator
<PlacesItemListGroupHeader
>());
96 m_controller
= new KItemListController(m_model
, view
, this);
97 m_controller
->setSelectionBehavior(KItemListController::SingleSelection
);
98 connect(m_controller
, SIGNAL(itemActivated(int)), this, SLOT(slotItemActivated(int)));
99 connect(m_controller
, SIGNAL(itemMiddleClicked(int)), this, SLOT(slotItemMiddleClicked(int)));
100 connect(m_controller
, SIGNAL(itemContextMenuRequested(int,QPointF
)), this, SLOT(slotItemContextMenuRequested(int,QPointF
)));
101 connect(m_controller
, SIGNAL(viewContextMenuRequested(QPointF
)), this, SLOT(slotViewContextMenuRequested(QPointF
)));
103 KItemListContainer
* container
= new KItemListContainer(m_controller
, this);
104 container
->setEnabledFrame(false);
106 QVBoxLayout
* layout
= new QVBoxLayout(this);
107 layout
->setMargin(0);
108 layout
->addWidget(container
);
113 Panel::showEvent(event
);
116 void PlacesPanel::slotItemActivated(int index
)
118 const KUrl url
= m_model
->data(index
).value("url").value
<KUrl
>();
119 if (!url
.isEmpty()) {
120 emit
placeActivated(convertedUrl(url
));
124 void PlacesPanel::slotItemMiddleClicked(int index
)
126 const KUrl url
= m_model
->data(index
).value("url").value
<KUrl
>();
127 if (!url
.isEmpty()) {
128 emit
placeMiddleClicked(convertedUrl(url
));
132 void PlacesPanel::slotItemContextMenuRequested(int index
, const QPointF
& pos
)
134 PlacesItem
* item
= m_model
->placesItem(index
);
141 QAction
* emptyTrashAction
= 0;
142 QAction
* addAction
= 0;
143 QAction
* mainSeparator
= 0;
144 QAction
* editAction
= 0;
145 QAction
* teardownAction
= 0;
146 QAction
* ejectAction
= 0;
148 const QString label
= item
->text();
150 const bool isDevice
= !item
->udi().isEmpty();
152 ejectAction
= m_model
->ejectAction(index
);
154 ejectAction
->setParent(&menu
);
155 menu
.addAction(ejectAction
);
158 teardownAction
= m_model
->teardownAction(index
);
159 if (teardownAction
) {
160 teardownAction
->setParent(&menu
);
161 menu
.addAction(teardownAction
);
164 if (teardownAction
|| ejectAction
) {
165 mainSeparator
= menu
.addSeparator();
168 if (item
->url() == KUrl("trash:/")) {
169 emptyTrashAction
= menu
.addAction(KIcon("trash-empty"), i18nc("@action:inmenu", "Empty Trash"));
170 KConfig
trashConfig("trashrc", KConfig::SimpleConfig
);
171 emptyTrashAction
->setEnabled(!trashConfig
.group("Status").readEntry("Empty", true));
174 addAction
= menu
.addAction(KIcon("document-new"), i18nc("@item:inmenu", "Add Entry..."));
175 mainSeparator
= menu
.addSeparator();
176 editAction
= menu
.addAction(KIcon("document-properties"), i18nc("@item:inmenu", "Edit '%1'...", label
));
180 addAction
= menu
.addAction(KIcon("document-new"), i18nc("@item:inmenu", "Add Entry..."));
183 QAction
* openInNewTabAction
= menu
.addAction(i18nc("@item:inmenu", "Open '%1' in New Tab", label
));
184 openInNewTabAction
->setIcon(KIcon("tab-new"));
186 QAction
* removeAction
= 0;
187 if (!isDevice
&& !item
->isSystemItem()) {
188 removeAction
= menu
.addAction(KIcon("edit-delete"), i18nc("@item:inmenu", "Remove '%1'", label
));
191 QAction
* hideAction
= menu
.addAction(i18nc("@item:inmenu", "Hide '%1'", label
));
192 hideAction
->setCheckable(true);
193 hideAction
->setChecked(item
->isHidden());
195 QAction
* showAllAction
= 0;
196 if (m_model
->hiddenCount() > 0) {
197 if (!mainSeparator
) {
198 mainSeparator
= menu
.addSeparator();
200 showAllAction
= menu
.addAction(i18nc("@item:inmenu", "Show All Entries"));
201 showAllAction
->setCheckable(true);
202 showAllAction
->setChecked(m_model
->hiddenItemsShown());
206 foreach (QAction
* action
, customContextMenuActions()) {
207 menu
.addAction(action
);
210 QAction
* action
= menu
.exec(pos
.toPoint());
212 if (action
== emptyTrashAction
) {
214 } else if (action
== addAction
) {
216 } else if (action
== editAction
) {
218 } else if (action
== removeAction
) {
219 m_model
->removeItem(index
);
220 } else if (action
== hideAction
) {
221 item
->setHidden(hideAction
->isChecked());
222 } else if (action
== openInNewTabAction
) {
223 const KUrl url
= m_model
->item(index
)->dataValue("url").value
<KUrl
>();
224 emit
placeMiddleClicked(url
);
225 } else if (action
== showAllAction
) {
226 m_model
->setHiddenItemsShown(showAllAction
->isChecked());
227 } else if (action
== teardownAction
) {
228 m_model
->requestTeardown(index
);
229 } else if (action
== ejectAction
) {
230 m_model
->requestEject(index
);
237 void PlacesPanel::slotViewContextMenuRequested(const QPointF
& pos
)
241 QAction
* addAction
= menu
.addAction(KIcon("document-new"), i18nc("@item:inmenu", "Add Entry..."));
243 QAction
* showAllAction
= 0;
244 if (m_model
->hiddenCount() > 0) {
245 showAllAction
= menu
.addAction(i18nc("@item:inmenu", "Show All Entries"));
246 showAllAction
->setCheckable(true);
247 showAllAction
->setChecked(m_model
->hiddenItemsShown());
251 foreach (QAction
* action
, customContextMenuActions()) {
252 menu
.addAction(action
);
255 QAction
* action
= menu
.exec(pos
.toPoint());
257 if (action
== addAction
) {
259 } else if (action
== showAllAction
) {
260 m_model
->setHiddenItemsShown(showAllAction
->isChecked());
267 void PlacesPanel::slotUrlsDropped(const KUrl
& dest
, QDropEvent
* event
, QWidget
* parent
)
270 const QString error
= DragAndDropHelper::dropUrls(KFileItem(), dest
, event
);
271 if (!error
.isEmpty()) {
272 emit
errorMessage(error
);
277 void PlacesPanel::slotTrashUpdated(KJob
* job
)
280 emit
errorMessage(job
->errorString());
282 org::kde::KDirNotify::emitFilesAdded("trash:/");
285 void PlacesPanel::emptyTrash()
287 const QString text
= i18nc("@info", "Do you really want to empty the Trash? All items will be deleted.");
288 const bool del
= KMessageBox::warningContinueCancel(window(),
291 KGuiItem(i18nc("@action:button", "Empty Trash"),
293 ) == KMessageBox::Continue
;
295 QByteArray packedArgs
;
296 QDataStream
stream(&packedArgs
, QIODevice::WriteOnly
);
298 KIO::Job
*job
= KIO::special(KUrl("trash:/"), packedArgs
);
299 KNotification::event("Trash: emptied", QString() , QPixmap() , 0, KNotification::DefaultEvent
);
300 job
->ui()->setWindow(parentWidget());
301 connect(job
, SIGNAL(result(KJob
*)), SLOT(slotTrashUpdated(KJob
*)));
305 void PlacesPanel::addEntry()
307 const int index
= m_controller
->selectionManager()->currentItem();
308 const KUrl url
= m_model
->data(index
).value("url").value
<KUrl
>();
310 QPointer
<PlacesItemEditDialog
> dialog
= new PlacesItemEditDialog(this);
311 dialog
->setCaption(i18nc("@title:window", "Add Places Entry"));
312 dialog
->setAllowGlobal(true);
314 if (dialog
->exec() == QDialog::Accepted
) {
315 KStandardItem
* item
= createStandardItemFromDialog(dialog
);
317 // Insert the item as last item of the corresponding group.
319 while (i
< m_model
->count() && m_model
->item(i
)->group() != item
->group()) {
323 bool inserted
= false;
324 while (!inserted
&& i
< m_model
->count()) {
325 if (m_model
->item(i
)->group() != item
->group()) {
326 m_model
->insertItem(i
, item
);
333 m_model
->appendItem(item
);
340 void PlacesPanel::editEntry(int index
)
342 QHash
<QByteArray
, QVariant
> data
= m_model
->data(index
);
344 QPointer
<PlacesItemEditDialog
> dialog
= new PlacesItemEditDialog(this);
345 dialog
->setCaption(i18nc("@title:window", "Edit Places Entry"));
346 dialog
->setIcon(data
.value("iconName").toString());
347 dialog
->setText(data
.value("text").toString());
348 dialog
->setUrl(data
.value("url").value
<KUrl
>());
349 dialog
->setAllowGlobal(true);
350 if (dialog
->exec() == QDialog::Accepted
) {
351 KStandardItem
* oldItem
= m_model
->item(index
);
353 KStandardItem
* item
= createStandardItemFromDialog(dialog
);
354 // Although the user might have changed the URL of the item in a way
355 // that another group should be assigned, we still apply the old
356 // group to keep the same position for the item.
357 item
->setGroup(oldItem
->group());
358 m_model
->changeItem(index
, item
);
365 void PlacesPanel::selectClosestItem()
367 const int index
= m_model
->closestItem(url());
368 KItemListSelectionManager
* selectionManager
= m_controller
->selectionManager();
369 selectionManager
->setCurrentItem(index
);
370 selectionManager
->clearSelection();
371 selectionManager
->setSelected(index
);
374 KStandardItem
* PlacesPanel::createStandardItemFromDialog(PlacesItemEditDialog
* dialog
) const
378 const KUrl newUrl
= dialog
->url();
379 KStandardItem
* item
= new KStandardItem();
380 item
->setIcon(dialog
->icon());
381 item
->setText(dialog
->text());
382 item
->setDataValue("url", newUrl
);
383 item
->setGroup(m_model
->groupName(newUrl
));
388 KUrl
PlacesPanel::convertedUrl(const KUrl
& url
)
391 if (url
.protocol() == QLatin1String("timeline")) {
392 newUrl
= createTimelineUrl(url
);
393 } else if (url
.protocol() == QLatin1String("search")) {
394 newUrl
= createSearchUrl(url
);
400 KUrl
PlacesPanel::createTimelineUrl(const KUrl
& url
)
402 // TODO: Clarify with the Nepomuk-team whether it makes sense
403 // provide default-timeline-URLs like 'yesterday', 'this month'
407 const QString path
= url
.pathOrUrl();
408 if (path
.endsWith("yesterday")) {
409 const QDate date
= QDate::currentDate().addDays(-1);
410 const int year
= date
.year();
411 const int month
= date
.month();
412 const int day
= date
.day();
413 timelineUrl
= "timeline:/" + timelineDateString(year
, month
) +
414 '/' + timelineDateString(year
, month
, day
);
415 } else if (path
.endsWith("thismonth")) {
416 const QDate date
= QDate::currentDate();
417 timelineUrl
= "timeline:/" + timelineDateString(date
.year(), date
.month());
418 } else if (path
.endsWith("lastmonth")) {
419 const QDate date
= QDate::currentDate().addMonths(-1);
420 timelineUrl
= "timeline:/" + timelineDateString(date
.year(), date
.month());
422 Q_ASSERT(path
.endsWith("today"));
429 QString
PlacesPanel::timelineDateString(int year
, int month
, int day
)
431 QString date
= QString::number(year
) + '-';
435 date
+= QString::number(month
);
442 date
+= QString::number(day
);
448 KUrl
PlacesPanel::createSearchUrl(const KUrl
& url
)
453 const QString path
= url
.pathOrUrl();
454 if (path
.endsWith("documents")) {
455 searchUrl
= searchUrlForTerm(Nepomuk::Query::ResourceTypeTerm(Nepomuk::Vocabulary::NFO::Document()));
456 } else if (path
.endsWith("images")) {
457 searchUrl
= searchUrlForTerm(Nepomuk::Query::ResourceTypeTerm(Nepomuk::Vocabulary::NFO::Image()));
458 } else if (path
.endsWith("audio")) {
459 searchUrl
= searchUrlForTerm(Nepomuk::Query::ComparisonTerm(Nepomuk::Vocabulary::NIE::mimeType(),
460 Nepomuk::Query::LiteralTerm("audio")));
461 } else if (path
.endsWith("videos")) {
462 searchUrl
= searchUrlForTerm(Nepomuk::Query::ComparisonTerm(Nepomuk::Vocabulary::NIE::mimeType(),
463 Nepomuk::Query::LiteralTerm("video")));
475 KUrl
PlacesPanel::searchUrlForTerm(const Nepomuk::Query::Term
& term
)
477 const Nepomuk::Query::Query
query(term
);
478 return query
.toSearchUrl();
482 #include "placespanel.moc"