2
* Copyright © 2009 Fredrik Höglund <fredrik@kde.org>
4
* This library is free software; you can redistribute it and/or
5
* modify it under the terms of the GNU Library General Public
6
* License as published by the Free Software Foundation; either
7
* version 2 of the License, or (at your option) any later version.
9
* This library is distributed in the hope that it will be useful,
10
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
* Library General Public License for more details.
14
* You should have received a copy of the GNU Library General Public License
15
* along with this library; see the file COPYING.LIB. If not, write to
16
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17
* Boston, MA 02110-1301, USA.
20
#include "popupview.h"
22
#include <QApplication>
24
#include <QDesktopWidget>
25
#include <QGraphicsScene>
26
#include <QGraphicsView>
27
#include <QGraphicsWidget>
28
#include <QItemSelectionModel>
32
#include <KBookmarkManager>
33
#include <KDesktopFile>
35
#include <kfileitemactions.h>
36
#include <KFileItemDelegate>
37
#include <kfileitemlistproperties.h>
38
#include <kfilepreviewgenerator.h>
40
#include <KWindowSystem>
43
#include <kio/fileundomanager.h>
44
#include <kio/paste.h>
46
#include <konqmimedata.h>
47
#include <konq_operations.h>
48
#include <konq_popupmenu.h>
50
#include "dirlister.h"
51
#include "folderviewadapter.h"
53
#include "proxymodel.h"
55
#include <Plasma/Applet>
56
#include <Plasma/BusyWidget>
57
#include <Plasma/FrameSvg>
58
#include <Plasma/Theme>
59
#include <Plasma/WindowEffects>
62
QTime PopupView::s_lastOpenClose;
64
PopupView::PopupView(const QModelIndex &index, const QPoint &pos,
65
const bool &showPreview, const QStringList &previewPlugins,
66
const IconView *parentView)
67
: QWidget(0, Qt::X11BypassWindowManagerHint),
69
m_parentView(parentView),
74
m_actionCollection(this),
78
m_showPreview(showPreview),
79
m_previewPlugins(previewPlugins)
81
setAttribute(Qt::WA_TranslucentBackground);
83
if (KWindowSystem::compositingActive()) {
84
setAttribute(Qt::WA_NoSystemBackground, false);
89
setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::Tool);
92
KWindowSystem::setState(effectiveWinId(), NET::SkipTaskbar | NET::SkipPager);
96
QPalette pal = palette();
97
pal.setColor(backgroundRole(), Qt::transparent);
98
pal.setColor(QPalette::Text, Plasma::Theme::defaultTheme()->color(Plasma::Theme::TextColor));
101
KFileItem item = static_cast<const ProxyModel*>(index.model())->itemForIndex(index);
102
if (item.isDesktopFile()) {
103
KDesktopFile file(item.localPath());
104
m_url = file.readUrl();
106
m_url = item.targetUrl();
109
m_background = new Plasma::FrameSvg(this);
110
m_background->setImagePath("widgets/tooltip");
112
int left = m_background->marginSize(Plasma::LeftMargin);
113
int top = m_background->marginSize(Plasma::TopMargin);
114
int right = m_background->marginSize(Plasma::RightMargin);
115
int bottom = m_background->marginSize(Plasma::BottomMargin);
117
setContentsMargins(left, top, right, bottom);
119
resize(parentView->sizeForRowsColumns(2, 3) + QSize(left + right, top + bottom));
121
const QRect available = QApplication::desktop()->availableGeometry(pos);
124
if (pt.x() + width() > available.right()) {
127
if (pt.x() < available.left()) {
128
pt.rx() = available.left();
131
if (pt.y() + height() > available.bottom()) {
134
if (pt.y() < available.top()) {
135
pt.ry() = available.top();
138
Plasma::WindowEffects::overrideShadow(winId(), true);
143
QTimer::singleShot(10, this, SLOT(init()));
144
s_lastOpenClose.restart();
147
PopupView::~PopupView()
150
s_lastOpenClose.restart();
153
void PopupView::delayedHide()
155
if (!m_iconView || !m_iconView->dragInProgress()) {
156
m_hideTimer.start(400, this);
160
bool PopupView::dragInProgress()
162
return m_iconView && m_iconView->dragInProgress();
165
void PopupView::init()
171
m_scene = new QGraphicsScene(this);
172
m_view = new QGraphicsView(m_scene, this);
173
m_view->setFrameShape(QFrame::NoFrame);
174
m_view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
175
m_view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
176
m_view->viewport()->setAutoFillBackground(false);
177
m_view->setGeometry(contentsRect());
180
DirLister *lister = new DirLister(this);
181
lister->setDelayedMimeTypes(true);
182
lister->setAutoErrorHandlingEnabled(false, 0);
183
lister->openUrl(m_url);
185
m_dirModel = new KDirModel(this);
186
m_dirModel->setDropsAllowed(KDirModel::DropOnDirectory | KDirModel::DropOnLocalExecutable);
187
m_dirModel->setDirLister(lister);
189
m_model = new ProxyModel(this);
190
m_model->setSourceModel(m_dirModel);
191
m_model->setSortLocaleAware(true);
192
m_model->setDynamicSortFilter(true);
193
m_model->setFilterCaseSensitivity(Qt::CaseInsensitive);
194
m_model->setParseDesktopFiles(m_url.protocol() == "desktop");
195
m_model->sort(KDirModel::Name, Qt::AscendingOrder);
197
m_delegate = new KFileItemDelegate(this);
198
m_selectionModel = new QItemSelectionModel(m_model, this);
200
m_iconView = new IconView(0);
201
m_iconView->setModel(m_model);
202
m_iconView->setItemDelegate(m_delegate);
203
m_iconView->setSelectionModel(m_selectionModel);
204
m_iconView->setFont(m_parentView->font());
205
m_iconView->setPalette(palette());
206
m_iconView->setDrawShadows(m_parentView->drawShadows());
207
m_iconView->setIconSize(m_parentView->iconSize());
208
m_iconView->setGridSize(m_parentView->gridSize());
209
m_iconView->setWordWrap(m_parentView->wordWrap());
210
m_iconView->setIconsMoveable(false);
211
m_iconView->setClickToViewFolders(false);
213
connect(m_iconView, SIGNAL(activated(QModelIndex)), SLOT(activated(QModelIndex)));
214
connect(m_iconView, SIGNAL(contextMenuRequest(QWidget*,QPoint)), SLOT(contextMenuRequest(QWidget*,QPoint)));
215
connect(m_iconView, SIGNAL(busy(bool)), SLOT(setBusy(bool)));
216
connect(m_iconView, SIGNAL(popupViewClosed()), SLOT(maybeClose()));
218
FolderViewAdapter *adapter = new FolderViewAdapter(m_iconView);
219
m_previewGenerator = new KFilePreviewGenerator(adapter, m_model);
220
m_previewGenerator->setPreviewShown(m_showPreview);
221
m_previewGenerator->setEnabledPlugins(m_previewPlugins);
223
m_iconView->setGeometry(contentsRect());
226
m_scene->addItem(m_iconView);
230
void PopupView::createActions()
232
// Remove the Shift+Delete shortcut from the cut action, since it's used for deleting files
233
KAction *cut = KStandardAction::cut(this, SLOT(cut()), this);
234
KShortcut cutShortCut = cut->shortcut();
235
cutShortCut.remove(Qt::SHIFT + Qt::Key_Delete);
236
cut->setShortcut(cutShortCut);
238
KAction *copy = KStandardAction::copy(this, SLOT(copy()), this);
240
KIO::FileUndoManager *manager = KIO::FileUndoManager::self();
242
KAction *undo = KStandardAction::undo(manager, SLOT(undo()), this);
243
connect(manager, SIGNAL(undoAvailable(bool)), undo, SLOT(setEnabled(bool)));
244
connect(manager, SIGNAL(undoTextChanged(QString)), SLOT(undoTextChanged(QString)));
245
undo->setEnabled(manager->undoAvailable());
247
KAction *paste = KStandardAction::paste(this, SLOT(paste()), this);
248
KAction *pasteTo = KStandardAction::paste(this, SLOT(pasteTo()), this);
249
pasteTo->setEnabled(false); // Only enabled during popupMenu()
251
QString actionText = KIO::pasteActionText();
252
if (!actionText.isEmpty()) {
253
paste->setText(actionText);
255
paste->setEnabled(false);
258
KAction *rename = new KAction(KIcon("edit-rename"), i18n("&Rename"), this);
259
rename->setShortcut(Qt::Key_F2);
260
connect(rename, SIGNAL(triggered()), SLOT(renameSelectedIcon()));
262
KAction *trash = new KAction(KIcon("user-trash"), i18n("&Move to Trash"), this);
263
trash->setShortcut(Qt::Key_Delete);
264
connect(trash, SIGNAL(triggered(Qt::MouseButtons, Qt::KeyboardModifiers)),
265
SLOT(moveToTrash(Qt::MouseButtons, Qt::KeyboardModifiers)));
267
KAction *emptyTrash = new KAction(KIcon("trash-empty"), i18n("&Empty Trash Bin"), this);
268
KConfig trashConfig("trashrc", KConfig::SimpleConfig);
269
emptyTrash->setEnabled(!trashConfig.group("Status").readEntry("Empty", true));
270
connect(emptyTrash, SIGNAL(triggered()), SLOT(emptyTrashBin()));
272
KAction *del = new KAction(i18n("&Delete"), this);
273
del->setIcon(KIcon("edit-delete"));
274
del->setShortcut(Qt::SHIFT + Qt::Key_Delete);
275
connect(del, SIGNAL(triggered()), SLOT(deleteSelectedIcons()));
277
// Create the new menu
278
m_newMenu = new KNewFileMenu(&m_actionCollection, "new_menu", this);
279
connect(m_newMenu->menu(), SIGNAL(aboutToShow()), this, SLOT(aboutToShowCreateNew()));
281
m_actionCollection.addAction("undo", undo);
282
m_actionCollection.addAction("cut", cut);
283
m_actionCollection.addAction("copy", copy);
284
m_actionCollection.addAction("paste", paste);
285
m_actionCollection.addAction("pasteto", pasteTo);
286
m_actionCollection.addAction("rename", rename);
287
m_actionCollection.addAction("trash", trash);
288
m_actionCollection.addAction("del", del);
289
m_actionCollection.addAction("empty_trash", emptyTrash);
292
void PopupView::contextMenuRequest(QWidget *widget, const QPoint &screenPos)
295
// contextMenuRequest is only called from the icon view, which is created in init()
296
// which mean m_model should always be initialized
299
if (m_actionCollection.isEmpty()) {
304
bool hasRemoteFiles = false;
305
bool isTrashLink = false;
307
foreach (const QModelIndex &index, m_selectionModel->selectedIndexes()) {
308
KFileItem item = m_model->itemForIndex(index);
309
if (!item.isNull()) {
310
hasRemoteFiles |= item.localPath().isEmpty();
315
// Check if we're showing the menu for the trash link
316
if (items.count() == 1 && items.at(0).isDesktopFile()) {
317
KDesktopFile file(items.at(0).localPath());
318
if (file.readType() == "Link" && file.readUrl() == "trash:/") {
323
QAction* pasteTo = m_actionCollection.action("pasteto");
325
pasteTo->setEnabled(m_actionCollection.action("paste")->isEnabled());
326
pasteTo->setText(m_actionCollection.action("paste")->text());
329
QList<QAction*> editActions;
330
editActions.append(m_actionCollection.action("rename"));
332
KConfigGroup configGroup(KGlobal::config(), "KDE");
333
bool showDeleteCommand = configGroup.readEntry("ShowDeleteCommand", false);
335
// Don't add the "Move to Trash" action if we're showing the menu for the trash link
337
if (!hasRemoteFiles) {
338
editActions.append(m_actionCollection.action("trash"));
340
showDeleteCommand = true;
343
if (showDeleteCommand) {
344
editActions.append(m_actionCollection.action("del"));
347
KParts::BrowserExtension::ActionGroupMap actionGroups;
348
actionGroups.insert("editactions", editActions);
350
KParts::BrowserExtension::PopupFlags flags = KParts::BrowserExtension::ShowProperties;
351
flags |= KParts::BrowserExtension::ShowUrlOperations;
353
// m_newMenu can be NULL here but KonqPopupMenu does handle this.
354
KonqPopupMenu *contextMenu = new KonqPopupMenu(items, m_url, m_actionCollection, m_newMenu,
355
KonqPopupMenu::ShowNewWindow, flags,
356
QApplication::desktop(),
357
KBookmarkManager::userBookmarksManager(),
360
m_showingMenu = true;
361
contextMenu->exec(screenPos);
363
m_showingMenu = false;
366
pasteTo->setEnabled(false);
370
KUrl::List PopupView::selectedUrls() const
375
foreach (const QModelIndex &index, m_selectionModel->selectedIndexes())
377
KFileItem item = m_model->itemForIndex(index);
378
// Prefer the local URL if there is one, since we can't trash remote URL's
379
const QString path = item.localPath();
380
if (!path.isEmpty()) {
383
urls.append(item.url());
389
void PopupView::cut()
391
QMimeData *mimeData = m_model->mimeData(m_selectionModel->selectedIndexes());
392
KonqMimeData::addIsCutSelection(mimeData, true);
393
QApplication::clipboard()->setMimeData(mimeData);
396
void PopupView::copy()
398
QMimeData *mimeData = m_model->mimeData(m_selectionModel->selectedIndexes());
399
QApplication::clipboard()->setMimeData(mimeData);
402
void PopupView::paste()
404
KonqOperations::doPaste(QApplication::desktop(), m_url);
407
void PopupView::pasteTo()
409
KUrl::List urls = selectedUrls();
410
Q_ASSERT(urls.count() == 1);
411
KonqOperations::doPaste(QApplication::desktop(), urls.first());
414
void PopupView::moveToTrash(Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers)
417
if (!m_iconView->renameInProgress()) {
418
KonqOperations::Operation op = (modifiers & Qt::ShiftModifier) ?
419
KonqOperations::DEL : KonqOperations::TRASH;
421
KonqOperations::del(QApplication::desktop(), op, selectedUrls());
425
void PopupView::deleteSelectedIcons()
427
if (!m_iconView->renameInProgress()) {
428
KonqOperations::del(QApplication::desktop(), KonqOperations::DEL, selectedUrls());
432
void PopupView::renameSelectedIcon()
435
m_iconView->renameSelectedIcon();
438
void PopupView::activated(const QModelIndex &index)
440
const KFileItem item = m_model->itemForIndex(index);
443
closeThisAndParentPopup();
446
void PopupView::setBusy(bool busy)
449
if (busy && !m_busyWidget) {
450
QTimer::singleShot(100, this, SLOT(createBusyWidgetIfNeeded()));
457
void PopupView::createBusyWidgetIfNeeded()
459
if (m_busy && !m_busyWidget) {
460
const int size = qMin(width(), height()) * .3;
461
m_busyWidget = new Plasma::BusyWidget;
462
m_busyWidget->setGeometry(QStyle::alignedRect(layoutDirection(), Qt::AlignCenter, QSize(size, size), contentsRect()));
463
m_scene->addItem(m_busyWidget);
467
void PopupView::emptyTrashBin()
469
KonqOperations::emptyTrash(QApplication::desktop());
472
void PopupView::undoTextChanged(const QString &text)
474
if (QAction *action = m_actionCollection.action("undo")) {
475
action->setText(text);
479
void PopupView::aboutToShowCreateNew()
482
m_newMenu->checkUpToDate();
483
m_newMenu->setPopupFiles(m_url);
487
void PopupView::resizeEvent(QResizeEvent *event)
491
m_background->resizeFrame(rect().size());
494
m_view->setGeometry(contentsRect());
497
if (KWindowSystem::compositingActive()) {
498
Plasma::WindowEffects::enableBlurBehind(winId(), true, m_background->mask());
500
setMask(m_background->mask());
504
void PopupView::paintEvent(QPaintEvent *event)
509
p.setCompositionMode(QPainter::CompositionMode_Source);
510
p.fillRect(rect(), Qt::transparent);
511
p.setCompositionMode(QPainter::CompositionMode_SourceOver);
512
m_background->paintFrame(&p);
515
void PopupView::contextMenuEvent(QContextMenuEvent *event)
521
if (m_actionCollection.isEmpty()) {
525
KFileItem rootItem = m_model->itemForIndex(QModelIndex());
526
//The root item is invalid (non-existent)
527
if (rootItem.isNull()) {
532
menu.addAction(m_actionCollection.action("new_menu"));
534
menu.addAction(m_actionCollection.action("undo"));
535
menu.addAction(m_actionCollection.action("paste"));
538
// Add an action for opening the folder in the preferred application.
539
if (!m_itemActions) {
540
// Create a new KFileItem to prevent the target URL in the root item
541
// from being used. In this case we want the configured URL instead.
542
KFileItem item(rootItem.mode(), rootItem.permissions(), m_url);
544
KFileItemListProperties itemList(KFileItemList() << item);
546
m_itemActions = new KFileItemActions(this);
547
m_itemActions->setItemListProperties(itemList);
549
menu.addAction(m_itemActions->preferredOpenWithAction(QString()));
551
if (m_url.protocol() == "trash") {
552
menu.addAction(m_actionCollection.action("empty_trash"));
555
m_showingMenu = true;
556
menu.exec(event->globalPos());
557
m_showingMenu = false;
560
// This function calls a given method in the parent PopupView, and returns true
561
// if successful or false otherwise.
562
bool PopupView::callOnParent(const char *method)
564
// Since the scene is a child of the popup view, we can get to the parent view easily
565
PopupView *parentView = qobject_cast<PopupView*>(m_parentView->scene()->parent());
568
// We use a delayed call to give enter and leave events time be delivered
569
QMetaObject::invokeMethod(parentView, method, Qt::QueuedConnection);
576
void PopupView::maybeClose()
578
if (!underMouse() && !m_showingMenu &&
579
(!m_iconView || (!m_iconView->isUnderMouse() && !m_iconView->dragInProgress())) &&
580
!callOnParent("maybeClose") && !m_hideTimer.isActive()) {
581
m_hideTimer.start(400, this);
585
void PopupView::closeThisAndParentPopup() {
588
callOnParent("closeThisAndParentPopup");
591
void PopupView::cancelHideTimer()
595
// Propagate the call down the chain of popups
596
callOnParent("cancelHideTimer");
599
void PopupView::enterEvent(QEvent *event)
603
// Make sure that any hide timer down the popup chain is stopped
607
void PopupView::leaveEvent(QEvent *event)
611
// The popups are normally closed by the icon views that created them
612
// in response to hover events, but when the cursor leaves a popup and
613
// enters a widget that isn't an icon view in the popup chain, that
614
// mechanism doesn't work.
616
// To make sure that the popups are closed when this happens, we call
617
// maybeClose() which checks if the popup is under the mouse cursor,
618
// and if it isn't it calls maybeClose() in the next popup in the chain
619
// and so on. If no popups in the chain is under the mouse cursor,
620
// the root popup will start the hide timer which will close the whole
621
// chain when it fires.
622
if (!m_iconView || !m_iconView->popupVisible()) {
627
void PopupView::dragEnterEvent(QDragEnterEvent *event)
630
callOnParent("cancelHideTimer");
632
// If the popup is open during a drag and drop operation,
633
// assume that we accept the mimetype.
634
event->setAccepted(true);
637
void PopupView::dragLeaveEvent(QDragLeaveEvent *event)
639
if (!m_iconView || !m_iconView->popupVisible()) {
643
// If the popup is open during a drag and drop operation,
644
// assume that we accept the mimetype.
645
event->setAccepted(true);
648
void PopupView::timerEvent(QTimerEvent *event)
650
if (event->timerId() == m_hideTimer.timerId()) {
656
#include "popupview.moc"