2
* Copyright 2009 by Aaron Seigo <aseigo@kde.org>
3
* Copyright 2009 by Artur Duque de Souza <asouza@kde.org>
4
* Copyright 2009 by Marco Martin <notmart@gmail.com>
6
* This program is free software; you can redistribute it and/or modify
7
* it under the terms of the GNU Library General Public License version 2,
8
* or (at your option) any later version.
10
* This program is distributed in the hope that it will be useful,
11
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
* GNU General Public License for more details
15
* You should have received a copy of the GNU Library General Public
16
* License along with this program; if not, write to the
17
* Free Software Foundation, Inc.,
18
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22
#include "stripwidget.h"
24
#include "runnersconfig.h"
25
#include "../common/linearappletoverlay.h"
26
#include "../common/appletmovespacer.h"
27
#include "iconactioncollection.h"
28
#include "models/commonmodel.h"
29
#include "models/kservicemodel.h"
33
#include <QGraphicsSceneMouseEvent>
34
#include <QGraphicsAnchorLayout>
35
#include <QGraphicsView>
37
#include <QVBoxLayout>
42
#include <KIconLoader>
44
#include <KConfigDialog>
47
#include <KKeySequenceWidget>
49
#include <Plasma/AbstractToolBox>
50
#include <Plasma/Theme>
51
#include <Plasma/Frame>
52
#include <Plasma/Corona>
53
#include <Plasma/LineEdit>
54
#include <Plasma/IconWidget>
55
#include <Plasma/RunnerManager>
56
#include <Plasma/ScrollWidget>
57
#include <Plasma/ToolButton>
60
SearchLaunch::SearchLaunch(QObject *parent, const QVariantList &args)
61
: Containment(parent, args),
68
m_orientation(Qt::Vertical),
72
m_iconActionCollection(0),
73
m_stripUninitialized(true)
75
setContainmentType(Containment::CustomContainment);
76
m_iconActionCollection = new IconActionCollection(this, this);
77
setHasConfigurationInterface(true);
78
setFocusPolicy(Qt::StrongFocus);
79
setFlag(QGraphicsItem::ItemIsFocusable, true);
80
m_background = new Plasma::FrameSvg(this);
81
m_background->setImagePath("widgets/frame");
82
m_background->setElementPrefix("raised");
83
m_background->setEnabledBorders(Plasma::FrameSvg::BottomBorder);
86
SearchLaunch::~SearchLaunch()
88
KConfigGroup cg = config();
89
m_stripWidget->save(cg);
90
config().writeEntry("orientation", (int)m_orientation);
93
void SearchLaunch::init()
96
connect(this, SIGNAL(appletAdded(Plasma::Applet*,QPointF)),
97
this, SLOT(layoutApplet(Plasma::Applet*,QPointF)));
98
connect(this, SIGNAL(appletRemoved(Plasma::Applet*)),
99
this, SLOT(appletRemoved(Plasma::Applet*)));
101
connect(this, SIGNAL(toolBoxVisibilityChanged(bool)), this, SLOT(updateConfigurationMode(bool)));
104
setToolBox(Plasma::AbstractToolBox::load(corona()->preferredToolBoxPlugin(Plasma::Containment::DesktopContainment), QVariantList(), this));
106
QAction *a = action("add widgets");
113
connect(toolBox(), SIGNAL(toggled()), this, SIGNAL(toolBoxToggled()));
114
connect(toolBox(), SIGNAL(visibilityChanged(bool)), this, SIGNAL(toolBoxVisibilityChanged(bool)));
118
a = action("configure");
121
a->setText(i18n("Configure Search and Launch"));
125
QAction *lockAction = 0;
127
lockAction = corona()->action("lock widgets");
130
if (!lockAction || !lockAction->isEnabled()) {
131
lockAction = new QAction(this);
132
addAction("lock page", lockAction);
133
lockAction->setText(i18n("Lock Page"));
134
lockAction->setIcon(KIcon("object-locked"));
135
QObject::connect(lockAction, SIGNAL(triggered(bool)), this, SLOT(toggleImmutability()));
138
addToolBoxAction(lockAction);
140
//FIXME: two different use cases for the desktop and the newspaper, another reason to move the toolbox management out of here
141
QAction *activityAction = 0;
143
activityAction = corona()->action("manage activities");
145
if (activityAction) {
146
addToolBoxAction(activityAction);
149
a = new QAction(i18n("Next activity"), this);
150
addAction("next containment", a);
151
a = new QAction(i18n("Previous activity"), this);
152
addAction("previous containment", a);
155
connect(corona(), SIGNAL(availableScreenRegionChanged()), this, SLOT(availableScreenRegionChanged()));
156
availableScreenRegionChanged();
159
Plasma::FormFactor form = formFactor();
160
Qt::Orientation layoutOtherDirection = form == Plasma::Vertical ? \
161
Qt::Horizontal : Qt::Vertical;
163
// create main layout
164
m_mainLayout = new QGraphicsLinearLayout();
165
m_mainLayout->setOrientation(layoutOtherDirection);
166
m_mainLayout->setContentsMargins(0, 0, 0, 0);
167
m_mainLayout->setSizePolicy(QSizePolicy(QSizePolicy::Expanding,
168
QSizePolicy::Expanding));
169
setLayout(m_mainLayout);
171
// create launch grid and make it centered
172
m_resultsLayout = new QGraphicsLinearLayout;
175
m_resultsView = new ItemView(this);
177
m_resultsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
178
m_resultsView->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
179
m_resultsLayout->addItem(m_resultsView);
181
connect(m_resultsView, SIGNAL(dragStartRequested(QModelIndex)), this, SLOT(resultsViewRequestedDrag(QModelIndex)));
182
connect(m_resultsView, SIGNAL(itemActivated(QModelIndex)), this, SLOT(launch(QModelIndex)));
183
connect(m_resultsView, SIGNAL(addActionTriggered(const QModelIndex &)), this, SLOT(addFavourite(const QModelIndex &)));
186
//TODO how to do the strip widget?
187
m_stripWidget = new StripWidget(this);
188
m_stripWidget->setImmutability(immutability());
189
connect(m_stripWidget, SIGNAL(saveNeeded()), this, SLOT(saveFavourites()));
191
//load all config, only at this point we are sure it won't crash
194
m_appletsLayout = new QGraphicsLinearLayout(Qt::Horizontal);
195
m_appletsLayout->setPreferredHeight(KIconLoader::SizeMedium);
196
m_appletsLayout->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
197
QGraphicsWidget *leftSpacer = new QGraphicsWidget(this);
198
QGraphicsWidget *rightSpacer = new QGraphicsWidget(this);
199
m_appletsLayout->addItem(leftSpacer);
200
m_appletsLayout->addItem(rightSpacer);
202
m_backButton = new Plasma::IconWidget(this);
203
m_backButton->setIcon(KIcon("go-previous"));
204
m_backButton->setText(i18n("Back"));
205
m_backButton->setOrientation(Qt::Horizontal);
206
m_backButton->setPreferredSize(m_backButton->sizeFromIconSize(KIconLoader::SizeSmallMedium));
207
connect(m_backButton, SIGNAL(clicked()), this, SLOT(reset()));
208
connect(m_resultsView, SIGNAL(resetRequested()), this, SLOT(reset()));
210
QGraphicsAnchorLayout *searchLayout = new QGraphicsAnchorLayout();
211
searchLayout->setSpacing(10);
213
m_searchField = new Plasma::LineEdit(this);
214
m_searchField->setPreferredWidth(200);
215
m_searchField->nativeWidget()->setClearButtonShown(true);
216
m_searchField->nativeWidget()->setClickMessage(i18n("Search..."));
217
connect(m_searchField, SIGNAL(returnPressed()), this, SLOT(searchReturnPressed()));
218
connect(m_searchField->nativeWidget(), SIGNAL(textEdited(const QString &)), this, SLOT(delayedQuery()));
219
m_searchTimer = new QTimer(this);
220
m_searchTimer->setSingleShot(true);
221
connect(m_searchTimer, SIGNAL(timeout()), this, SLOT(query()));
222
searchLayout->addAnchor(m_searchField, Qt::AnchorHorizontalCenter, searchLayout, Qt::AnchorHorizontalCenter);
223
searchLayout->addAnchors(m_searchField, searchLayout, Qt::Vertical);
224
searchLayout->addAnchors(m_backButton, searchLayout, Qt::Vertical);
225
searchLayout->addAnchor(m_backButton, Qt::AnchorRight, m_searchField, Qt::AnchorLeft);
228
// add our layouts to main vertical layout
229
m_mainLayout->addItem(m_stripWidget);
230
m_mainLayout->addItem(searchLayout);
231
m_mainLayout->addItem(m_resultsLayout);
234
// correctly set margins
235
m_mainLayout->activate();
236
m_mainLayout->updateGeometry();
238
setTabOrder(m_stripWidget, m_searchField);
239
setTabOrder(m_searchField, m_resultsView);
240
setFormFactorFromLocation(location());
242
if (action("remove")) {
243
addToolBoxAction(action("remove"));
247
void SearchLaunch::launchPackageManager()
250
toolBox()->setShowing(false);
252
KRun::run(*m_packageManagerService.data(), KUrl::List(), 0);
255
void SearchLaunch::configChanged()
257
setOrientation((Qt::Orientation)config().readEntry("Orientation", (int)Qt::Vertical));
259
m_stripWidget->setIconSize(config().readEntry("FavouritesIconSize", (int)KIconLoader::SizeLarge));
261
m_resultsView->setIconSize(config().readEntry("ResultsIconSize", (int)KIconLoader::SizeHuge));
263
QString packageManagerName = config().readEntry("PackageManager", "kpackagekit");
265
if (!packageManagerName.isEmpty()) {
266
m_packageManagerService = KService::serviceByDesktopName(packageManagerName);
268
if (!action("add applications") && m_packageManagerService && !m_packageManagerService->exec().isEmpty()) {
269
KAction *addApplicationsAction = new KAction(this);
270
addAction("add applications", addApplicationsAction);
271
addApplicationsAction->setText(i18n("Add applications"));
272
addApplicationsAction->setIcon(KIcon("applications-other"));
273
addToolBoxAction(addApplicationsAction);
275
connect(addApplicationsAction, SIGNAL(triggered()), this, SLOT(launchPackageManager()));
280
void SearchLaunch::availableScreenRegionChanged()
288
//we don't want the bounding rect (that could include panels too), but the maximumone representing the desktop
289
foreach (const QRect &rect, corona()->availableScreenRegion(screen()).rects()) {
290
int area = rect.width() * rect.height();
291
if (area > maxArea) {
297
QGraphicsView *ownView = view();
299
//FIXME: the second check is a workaround to a qt bug: when a qwidget has just been created, maptoglobal and mapfromglobal aren't symmetryc. remove as soon as the bug is fixed
300
if (ownView && (ownView->mapFromGlobal(QPoint(0,0)) == -ownView->mapToGlobal(QPoint(0,0)))) {
301
maxRect.moveTopLeft(ownView->mapFromGlobal(maxRect.topLeft()));
304
maxRect.moveTopLeft(QPoint(qMax(0, maxRect.left()), qMax(0, maxRect.top())));
306
setContentsMargins(maxRect.left(), maxRect.top(), qMax((qreal)0.0, size().width() - maxRect.right()), qMax((qreal)0.0, size().height() - maxRect.bottom()));
309
void SearchLaunch::toggleImmutability()
311
if (immutability() == Plasma::UserImmutable) {
312
setImmutability(Plasma::Mutable);
313
} else if (immutability() == Plasma::Mutable) {
314
setImmutability(Plasma::UserImmutable);
318
void SearchLaunch::doSearch(const QString &query, const QString &runner)
320
if (!query.isEmpty()) {
321
m_resultsView->setModel(m_runnerModel);
323
m_resultsView->setModel(m_serviceModel);
324
m_serviceModel->setPath("/");
327
m_runnerModel->setQuery(query, runner);
329
//enable or disable drag and drop
330
if (immutability() == Plasma::Mutable && (m_resultsView->model() != m_serviceModel || m_serviceModel->path() != "/")) {
331
m_resultsView->setDragAndDropMode(ItemContainer::CopyDragAndDrop);
333
m_resultsView->setDragAndDropMode(ItemContainer::NoDragAndDrop);
337
void SearchLaunch::reset()
339
if (m_resultsView->model() != m_serviceModel || m_serviceModel->path() != "/") {
340
m_searchField->setText(QString());
342
m_serviceModel->setPath("/");
343
m_resultsView->setModel(m_serviceModel);
347
void SearchLaunch::launch(QModelIndex index)
349
KUrl url(index.data(CommonModel::Url).value<QString>());
351
if (m_resultsView->model() == m_runnerModel) {
352
KRunnerItemHandler::openUrl(url);
354
emit releaseVisualFocus();
356
QString id = url.path();
357
if (id.startsWith(QLatin1Char('/'))) {
358
id = id.remove(0, 1);
360
if (url.protocol() == "kservicegroup") {
361
m_serviceModel->setPath(id);
362
} else if (url.protocol() == "krunner") {
363
m_resultsView->setModel(m_runnerModel);
364
m_runnerModel->setQuery(id, url.host());
366
KServiceItemHandler::openUrl(url);
368
emit releaseVisualFocus();
372
//enable or disable drag and drop
373
if (immutability() == Plasma::Mutable && (m_resultsView->model() != m_serviceModel || m_serviceModel->path() != "/")) {
374
m_resultsView->setDragAndDropMode(ItemContainer::CopyDragAndDrop);
376
m_resultsView->setDragAndDropMode(ItemContainer::NoDragAndDrop);
380
void SearchLaunch::addFavourite(const QModelIndex &index)
382
QMimeData *mimeData = m_resultsView->model()->mimeData(QModelIndexList()<<index);
383
if (mimeData && !mimeData->urls().isEmpty()) {
384
m_stripWidget->add(mimeData->urls().first());
388
void SearchLaunch::layoutApplet(Plasma::Applet* applet, const QPointF &pos)
392
if (!m_appletsLayout) {
396
if (m_appletsLayout->count() == 2) {
397
m_mainLayout->removeItem(m_appletsLayout);
398
m_mainLayout->addItem(m_appletsLayout);
401
Plasma::FormFactor f = formFactor();
402
int insertIndex = -1;
404
//if pos is (-1,-1) insert at the end of the panel
405
if (pos != QPoint(-1, -1)) {
406
for (int i = 1; i < m_appletsLayout->count()-1; ++i) {
407
if (!dynamic_cast<Plasma::Applet *>(m_appletsLayout->itemAt(i)) &&
408
!dynamic_cast<AppletMoveSpacer *>(m_appletsLayout->itemAt(i))) {
412
QRectF siblingGeometry = m_appletsLayout->itemAt(i)->geometry();
413
if (f == Plasma::Horizontal) {
414
qreal middle = (siblingGeometry.left() + siblingGeometry.right()) / 2.0;
415
if (pos.x() < middle) {
418
} else if (pos.x() <= siblingGeometry.right()) {
422
} else { // Plasma::Vertical
423
qreal middle = (siblingGeometry.top() + siblingGeometry.bottom()) / 2.0;
424
if (pos.y() < middle) {
427
} else if (pos.y() <= siblingGeometry.bottom()) {
435
if (insertIndex != -1) {
436
m_appletsLayout->insertItem(insertIndex, applet);
438
m_appletsLayout->insertItem(m_appletsLayout->count()-1, applet);
441
applet->setBackgroundHints(NoBackground);
444
void SearchLaunch::appletRemoved(Plasma::Applet* applet)
448
if (!m_appletOverlay && m_appletsLayout->count() == 3) {
449
m_mainLayout->removeItem(m_appletsLayout);
453
void SearchLaunch::constraintsEvent(Plasma::Constraints constraints)
455
//kDebug() << "constraints updated with" << constraints << "!!!!!!";
457
if (constraints & Plasma::FormFactorConstraint ||
458
constraints & Plasma::StartupCompletedConstraint) {
461
if (!m_runnerModel) {
462
m_runnerModel = new KRunnerModel(this);
463
m_serviceModel = new KServiceModel(config(), this);
464
m_resultsView->setModel(m_serviceModel);
467
resize(corona()->screenGeometry(screen()).size());
470
if (constraints & Plasma::LocationConstraint) {
471
setFormFactorFromLocation(location());
474
if (constraints & Plasma::SizeConstraint) {
475
availableScreenRegionChanged();
476
if (m_appletsLayout) {
477
m_appletsLayout->setMaximumHeight(size().height()/4);
479
if (m_appletOverlay) {
480
m_appletOverlay->resize(size());
484
if (constraints & Plasma::StartupCompletedConstraint) {
485
Plasma::DataEngine *engine = dataEngine("searchlaunch");
486
engine->connectSource("query", this);
489
if (constraints & Plasma::ScreenConstraint) {
490
if (screen() != -1 && m_searchField) {
491
m_searchField->setFocus();
495
if (constraints & Plasma::ImmutableConstraint) {
497
QAction *a = action("lock page");
499
switch (immutability()) {
500
case Plasma::SystemImmutable:
501
a->setEnabled(false);
502
a->setVisible(false);
505
case Plasma::UserImmutable:
506
a->setText(i18n("Unlock Page"));
507
a->setIcon(KIcon("object-unlocked"));
512
case Plasma::Mutable:
513
a->setText(i18n("Lock Page"));
514
a->setIcon(KIcon("object-locked"));
521
//kill or create the config overlay if needed
522
if (immutability() == Plasma::Mutable && !m_appletOverlay && toolBox() && toolBox()->isShowing()) {
523
m_appletOverlay = new LinearAppletOverlay(this, m_appletsLayout);
524
m_appletOverlay->resize(size());
525
} else if (immutability() != Plasma::Mutable && m_appletOverlay && toolBox() && toolBox()->isShowing()) {
526
m_appletOverlay->deleteLater();
530
//enable or disable drag and drop
531
if (immutability() == Plasma::Mutable && (m_resultsView->model() != m_serviceModel || m_serviceModel->path() != "/")) {
532
m_resultsView->setDragAndDropMode(ItemContainer::CopyDragAndDrop);
534
m_resultsView->setDragAndDropMode(ItemContainer::NoDragAndDrop);
536
m_stripWidget->setImmutability(immutability());
540
void SearchLaunch::setOrientation(Qt::Orientation orientation)
542
if (m_orientation == orientation) {
546
m_orientation = orientation;
547
m_resultsView->setOrientation(orientation);
550
void SearchLaunch::setFormFactorFromLocation(Plasma::Location loc)
553
case Plasma::BottomEdge:
554
case Plasma::TopEdge:
555
//kDebug() << "setting horizontal form factor";
556
setFormFactor(Plasma::Horizontal);
558
case Plasma::RightEdge:
559
case Plasma::LeftEdge:
560
//kDebug() << "setting vertical form factor";
561
setFormFactor(Plasma::Vertical);
564
setFormFactor(Plasma::Horizontal);
568
void SearchLaunch::restoreStrip()
570
KConfigGroup cg = config();
571
m_stripWidget->restore(cg);
575
void SearchLaunch::updateConfigurationMode(bool config)
578
if (config && !m_appletOverlay && immutability() == Plasma::Mutable) {
579
if (m_appletsLayout->count() == 2) {
580
m_mainLayout->addItem(m_appletsLayout);
582
m_appletOverlay = new LinearAppletOverlay(this, m_appletsLayout);
583
m_appletOverlay->resize(size());
584
connect (m_appletOverlay, SIGNAL(dropRequested(QGraphicsSceneDragDropEvent *)),
585
this, SLOT(overlayRequestedDrop(QGraphicsSceneDragDropEvent *)));
586
} else if (!config) {
587
delete m_appletOverlay;
589
if (m_appletsLayout->count() == 2) {
590
m_mainLayout->removeItem(m_appletsLayout);
595
void SearchLaunch::overlayRequestedDrop(QGraphicsSceneDragDropEvent *event)
601
void SearchLaunch::resultsViewRequestedDrag(QModelIndex index)
603
if (!m_resultsView->model()) {
608
QModelIndexList list;
610
QMimeData *mimeData = m_resultsView->model()->mimeData(list);
612
QDrag *drag = new QDrag(view());
613
drag->setMimeData(mimeData);
614
drag->setPixmap(index.data(Qt::DecorationRole).value<QIcon>().pixmap(KIconLoader::SizeHuge, KIconLoader::SizeHuge));
616
drag->exec(Qt::CopyAction);
619
void SearchLaunch::paintInterface(QPainter *painter, const QStyleOptionGraphicsItem *, const QRect &)
621
if (m_stripUninitialized) {
622
m_stripUninitialized = false;
623
QTimer::singleShot(100, this, SLOT(restoreStrip()));
625
m_background->resizeFrame(QSizeF(size().width(), m_stripWidget->geometry().bottom()));
626
m_background->paintFrame(painter);
630
void SearchLaunch::delayedQuery()
632
m_searchTimer->start(500);
635
void SearchLaunch::query()
637
QString query = m_searchField->text();
642
void SearchLaunch::searchReturnPressed()
644
QString query = m_searchField->text();
645
//by pressing enter do a query or
646
if (query == m_lastQuery && !query.isEmpty()) {
647
launch(m_resultsView->model()->index(0, 0, QModelIndex()));
655
void SearchLaunch::dataUpdated(const QString &sourceName, const Plasma::DataEngine::Data &data)
657
Q_UNUSED(sourceName);
659
const QString query(data["query"].toString());
660
//Take ownership of the screen if we don't have one
661
//FIXME: hardcoding 0 is bad: maybe pass the screen from the dataengine?
662
if (!query.isEmpty()) {
671
m_searchField->setText(query);
675
void SearchLaunch::focusInEvent(QFocusEvent *event)
678
m_searchField->setFocus();
683
Containment::focusInEvent(event);
686
void SearchLaunch::changeEvent(QEvent *event)
688
if (event->type() == QEvent::ContentsRectChange) {
690
if (toolBox() && toolBox()->isShowing()) {
691
updateConfigurationMode(true);
694
Plasma::Containment::changeEvent(event);
697
void SearchLaunch::createConfigurationInterface(KConfigDialog *parent)
699
RunnersConfig *runnersConfig = new RunnersConfig(m_runnerModel->runnerManager(), parent);
700
parent->addPage(runnersConfig, i18nc("Title of the page that lets the user choose the loaded krunner plugins", "Search plugins"), "edit-find");
702
connect(parent, SIGNAL(applyClicked()), runnersConfig, SLOT(accept()));
703
connect(parent, SIGNAL(okClicked()), runnersConfig, SLOT(accept()));
705
QListView *enabledEntries = new QListView(parent);
706
enabledEntries->setModel(m_serviceModel->allRootEntriesModel());
707
enabledEntries->setModelColumn(0);
708
parent->addPage(enabledEntries, i18nc("Title of the page that lets the user choose what entries will be allowed in the main menu", "Main menu"), "view-list-icons");
710
QWidget *page = new QWidget;
711
QVBoxLayout *layout = new QVBoxLayout(page);
713
if (!m_shortcutEditor) {
714
m_shortcutEditor = new KKeySequenceWidget(page);
715
connect(parent, SIGNAL(applyClicked()), this, SLOT(configDialogFinished()));
716
connect(parent, SIGNAL(okClicked()), this, SLOT(configDialogFinished()));
719
m_shortcutEditor.data()->setKeySequence(globalShortcut().primary());
720
layout->addWidget(m_shortcutEditor.data());
721
layout->addStretch();
722
parent->addPage(page, i18n("Keyboard Shortcut"), "preferences-desktop-keyboard");
724
connect(parent, SIGNAL(applyClicked()), m_serviceModel, SLOT(saveConfig()));
725
connect(parent, SIGNAL(okClicked()), m_serviceModel, SLOT(saveConfig()));
728
void SearchLaunch::configDialogFinished()
730
if (m_shortcutEditor) {
731
QKeySequence sequence = m_shortcutEditor.data()->keySequence();
732
if (sequence != globalShortcut().primary()) {
733
setGlobalShortcut(KShortcut(sequence));
734
emit configNeedsSaving();
739
void SearchLaunch::saveFavourites()
741
KConfigGroup cg = config();
742
m_stripWidget->save(cg);
745
K_EXPORT_PLASMA_APPLET(sal, SearchLaunch)