2 * Copyright (C) 2006 Aaron Seigo <aseigo@kde.org>
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Library General Public License version 2 as
6 * published by the Free Software Foundation
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details
13 * You should have received a copy of the GNU Library General Public
14 * License along with this program; if not, write to the
15 * Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 #include "interface.h"
22 #include <QApplication>
23 #include <QDesktopWidget>
25 #include <QListWidget>
26 #include <QVBoxLayout>
27 #include <QHBoxLayout>
32 #include <KActionCollection>
33 #include <KHistoryComboBox>
34 #include <KCompletionBox>
39 #include <KGlobalSettings>
40 #include <KPushButton>
41 #include <KStandardGuiItem>
42 #include <KTitleWidget>
43 #include <KWindowSystem>
45 #include <Solid/Device>
46 #include <Solid/DeviceInterface>
48 // #include <threadweaver/DebuggingAids.h>
49 // #include <ThreadWeaver/Thread>
50 #include <ThreadWeaver/Job>
51 #include <ThreadWeaver/QueuePolicy>
52 #include <ThreadWeaver/Weaver>
55 #include <plasma/abstractrunner.h>
57 #include "collapsiblewidget.h"
58 #include "interfaceadaptor.h"
59 #include "krunnersettings.h"
61 using ThreadWeaver::Weaver
;
62 using ThreadWeaver::Job
;
64 // A little hack of a class to let us easily activate a match
66 class SearchMatch
: public QListWidgetItem
69 SearchMatch(const Plasma::SearchMatch
* action
, QListWidget
* parent
)
70 : QListWidgetItem( parent
),
77 void activate(const Plasma::SearchContext
*context
)
79 m_action
->exec(context
);
84 return m_action
->isEnabled();
87 bool hasMatchOptions()
89 return m_action
->runner()->hasMatchOptions();
92 void setAction(const Plasma::SearchMatch
* action
)
95 setIcon(m_action
->icon());
96 setText(i18n("%1 (%2)",
98 m_action
->runner()->objectName()));
100 // in case our new action is now enabled and the old one wasn't, or
102 setDefault(m_default
);
107 return m_action
->data().toString();
110 Plasma::SearchMatch::Type
actionType()
112 return m_action
->type();
115 /*Plasma::SearchMatch* action()
120 void createMatchOptions(QWidget
* parent
)
122 m_action
->runner()->createMatchOptions(parent
);
125 void setDefault( bool def
) {
126 if ( m_default
== def
) {
133 if ( m_action
->isEnabled() ) {
134 setText( text().prepend( i18n("Default: ") ) );
137 setText( text().mid( 9 ) );
141 bool operator<(const QListWidgetItem
& other
) const
144 // 0. Default wins. Always.
145 // 1. Exact trumps informational
146 // 2. Informational trumps possible
147 // 3. Higher relevance wins
149 const SearchMatch
*otherMatch
= dynamic_cast<const SearchMatch
*>(&other
);
152 return QListWidgetItem::operator<(other
);
155 if (otherMatch
->m_default
) {
159 Plasma::SearchMatch::Type myType
= m_action
->type();
160 Plasma::SearchMatch::Type otherType
= otherMatch
->m_action
->type();
162 if (myType
!= otherType
) {
163 return myType
< otherType
;
166 return m_action
->relevance() < otherMatch
->m_action
->relevance();
171 const Plasma::SearchMatch
* m_action
;
174 // Restricts simultaneous jobs of the same type
175 // Similar to ResourceRestrictionPolicy but check the object type first
176 class RunnerRestrictionPolicy
: public ThreadWeaver::QueuePolicy
179 ~RunnerRestrictionPolicy();
181 static RunnerRestrictionPolicy
& instance();
192 bool canRun(Job
* job
);
194 void release(Job
* job
);
195 void destructed(Job
* job
);
197 RunnerRestrictionPolicy();
199 // QHash<QString, int> m_runCounts;
205 RunnerRestrictionPolicy::RunnerRestrictionPolicy()
211 RunnerRestrictionPolicy::~RunnerRestrictionPolicy()
215 RunnerRestrictionPolicy
& RunnerRestrictionPolicy::instance()
217 static RunnerRestrictionPolicy policy
;
221 bool RunnerRestrictionPolicy::canRun(Job
* job
)
224 QMutexLocker
l(&m_mutex
);
225 // QString type = job->objectName();
226 if (m_count
/*m_runCounts.value(type)*/ > m_cap
) {
227 // kDebug() << "Denying job " << type << " because of " << m_count/*m_runCounts[type]*/ << " current jobs" << endl;
230 // m_runCounts[type]++;
236 void RunnerRestrictionPolicy::free(Job
* job
)
239 QMutexLocker
l(&m_mutex
);
240 // QString type = job->objectName();
242 // if (m_runCounts.value(type)) {
243 // m_runCounts[type]--;
247 void RunnerRestrictionPolicy::release(Job
* job
)
252 void RunnerRestrictionPolicy::destructed(Job
* job
)
257 // Class to run queries in different threads
258 class FindMatchesJob
: public Job
261 FindMatchesJob(const QString
& term
, Plasma::AbstractRunner
* runner
, Plasma::SearchContext
* context
, QObject
* parent
= 0);
263 int priority() const;
269 Plasma::SearchContext
* m_context
;
270 Plasma::AbstractRunner
* m_runner
;
273 FindMatchesJob::FindMatchesJob( const QString
& term
, Plasma::AbstractRunner
* runner
, Plasma::SearchContext
* context
, QObject
* parent
)
274 : ThreadWeaver::Job(parent
),
279 // setObjectName( runner->objectName() );
280 if (runner
->speed() == Plasma::AbstractRunner::SlowSpeed
) {
281 assignQueuePolicy(&RunnerRestrictionPolicy::instance());
285 void FindMatchesJob::run()
287 // kDebug() << "Running match for " << m_runner->objectName() << " in Thread " << thread()->id() << endl;
288 m_runner
->performMatch(*m_context
);
291 int FindMatchesJob::priority() const
293 return m_runner
->priority();
296 Interface::Interface(QWidget
* parent
)
297 : KRunnerDialog( parent
),
303 setWindowTitle( i18n("Run Command") );
304 setWindowIcon(KIcon("system-run"));
306 m_matchTimer
.setSingleShot(true);
307 connect(&m_matchTimer
, SIGNAL(timeout()), this, SLOT(match()));
309 m_updateTimer
.setSingleShot(true);
310 connect(&m_updateTimer
, SIGNAL(timeout()), this, SLOT(updateMatches()));
312 const int numProcs
= qMax(Solid::Device::listFromType(Solid::DeviceInterface::Processor
).count(), 1);
313 const int numThreads
= qMin(KRunnerSettings::maxThreads(), 2 + ((numProcs
- 1) * 2));
314 //kDebug() << "setting up" << numThreads << "threads for" << numProcs << "processors";
315 Weaver::instance()->setMaximumNumberOfThreads(numThreads
);
317 QWidget
* w
= mainWidget();
318 m_layout
= new QVBoxLayout(w
);
319 m_layout
->setMargin(0);
321 m_header
= new KTitleWidget(w
);
322 m_header
->setBackgroundRole( QPalette::Base
);
323 m_layout
->addWidget( m_header
);
325 m_searchTerm
= new KHistoryComboBox(false,w
);
326 m_searchTerm
->setDuplicatesEnabled(false);
327 KLineEdit
*lineEdit
= new KLineEdit(m_searchTerm
);
328 lineEdit
->setCompletionObject(m_context
.completionObject());
329 lineEdit
->setClearButtonShown(true);
330 m_searchTerm
->setLineEdit(lineEdit
);
331 m_header
->setBuddy(m_searchTerm
);
332 m_layout
->addWidget(m_searchTerm
);
333 connect(m_searchTerm
, SIGNAL(editTextChanged(QString
)),
334 this, SLOT(queueMatch()));
335 connect(m_searchTerm
, SIGNAL(returnPressed()),
338 QStringList executions
= KRunnerSettings::pastQueries();
339 //Handle updates to the completion object as well
340 m_searchTerm
->setHistoryItems(executions
, true);
342 //TODO: temporary feedback, change later with the "icon parade" :)
343 m_matchList
= new QListWidget(w
);
344 //m_matchList->setSortingEnabled(true);
346 connect( m_matchList
, SIGNAL(itemActivated(QListWidgetItem
*)),
347 SLOT(matchActivated(QListWidgetItem
*)) );
348 connect( m_matchList
, SIGNAL(itemClicked(QListWidgetItem
*)),
349 SLOT(setDefaultItem(QListWidgetItem
*)) );
350 m_layout
->addWidget(m_matchList
);
352 // buttons at the bottom
353 QHBoxLayout
* bottomLayout
= new QHBoxLayout(w
);
354 m_optionsButton
= new KPushButton(KStandardGuiItem::configure(), this);
355 m_optionsButton
->setText( i18n( "Show Options" ) );
356 m_optionsButton
->setEnabled( false );
357 m_optionsButton
->setCheckable( true );
358 connect( m_optionsButton
, SIGNAL(toggled(bool)), SLOT(showOptions(bool)) );
359 bottomLayout
->addWidget( m_optionsButton
);
361 KPushButton
*activityButton
= new KPushButton(w
);
362 activityButton
->setText(i18n("Show System Activity"));
363 activityButton
->setIcon(KIcon("utilities-system-monitor"));
364 connect(activityButton
, SIGNAL(clicked()), qApp
, SLOT(showTaskManager()));
365 connect(activityButton
, SIGNAL(clicked()), this, SLOT(close()));
366 bottomLayout
->addWidget(activityButton
);
368 bottomLayout
->addStretch();
370 QString runButtonWhatsThis
= i18n( "Click to execute the selected item above" );
371 m_runButton
= new KPushButton(KGuiItem(i18n( "Launch" ), "system-run",
372 QString(), runButtonWhatsThis
),
374 m_runButton
->setEnabled( false );
375 m_runButton
->setDefault(true);
376 connect( m_runButton
, SIGNAL( clicked(bool) ), SLOT(exec()) );
377 bottomLayout
->addWidget( m_runButton
);
379 m_cancelButton
= new KPushButton(KStandardGuiItem::cancel(), w
);
380 connect( m_cancelButton
, SIGNAL(clicked(bool)), SLOT(close()) );
381 bottomLayout
->addWidget( m_cancelButton
);
383 m_layout
->addLayout (bottomLayout
);
385 new InterfaceAdaptor( this );
386 QDBusConnection::sessionBus().registerObject( "/Interface", this );
388 new QShortcut( QKeySequence( Qt::Key_Escape
), this, SLOT(close()) );
390 //FIXME: what size should we be?
393 //setWidgetPalettes();
394 //connect(KGlobalSettings::self(), SIGNAL(kdisplayPaletteChanged()),
395 // SLOT(setWidgetPalettes()));
397 //TODO: how should we order runners, particularly ones loaded from plugins?
398 QStringList whitelist
= KRunnerSettings::runners();
399 m_runners
+= Plasma::AbstractRunner::load(this, whitelist
);
401 connect(&m_context
, SIGNAL(matchesChanged()), this, SLOT(queueUpdates()));
405 // ThreadWeaver::setDebugLevel(true, 4);
408 Interface::~Interface()
410 KRunnerSettings::setPastQueries(m_searchTerm
->historyItems());
411 m_context
.clearMatches();
414 void Interface::clearHistory()
416 m_searchTerm
->clearHistory();
417 KRunnerSettings::setPastQueries(m_searchTerm
->historyItems());
420 void Interface::display(const QString
& term
)
422 m_searchTerm
->setFocus();
424 if (!term
.isEmpty()) {
425 m_searchTerm
->setItemText(0, term
);
432 KWindowSystem::setOnDesktop(winId(), KWindowSystem::currentDesktop());
435 if (QApplication::desktop()->numScreens() > 1) {
436 screen
= QApplication::desktop()->screenNumber(QCursor::pos());
439 KDialog::centerOnScreen(this, screen
);
441 KWindowSystem::forceActiveWindow(winId());
444 void Interface::switchUser()
446 Plasma::AbstractRunner
*sessionrunner
= 0;
447 foreach (Plasma::AbstractRunner
* runner
, m_runners
) {
448 if (qstrcmp(runner
->metaObject()->className(), "SessionRunner") == 0) {
449 sessionrunner
= runner
;
454 if (!sessionrunner
) {
455 kDebug() << "Could not find the Sessionrunner; not showing any sessions!";
458 //TODO: ugh, magic strings. See sessions/sessionrunner.cpp
460 m_header
->setText(i18n("Switch users"));
461 m_header
->setPixmap("system-switch-user");
463 m_context
.resetSearchTerm("SESSIONS");
464 sessionrunner
->match(&m_context
);
466 foreach (const Plasma::SearchMatch
*action
, m_context
.matches()) {
467 bool makeDefault
= !m_defaultMatch
&& action
->type() != Plasma::SearchMatch::InformationalMatch
;
469 SearchMatch
*match
= new SearchMatch(action
, m_matchList
);
472 m_defaultMatch
= match
;
473 m_defaultMatch
->setDefault(true);
474 m_runButton
->setEnabled(true);
475 m_optionsButton
->setEnabled(sessionrunner
->hasMatchOptions());
479 if (!m_defaultMatch
) {
480 m_matchList
->addItem(i18n("No desktop sessions available"));
482 m_matchList
->sortItems(Qt::DescendingOrder
);
486 void Interface::setWidgetPalettes()
488 // a nice palette to use with the widgets
489 QPalette widgetPalette
= palette();
490 QColor headerBgColor
= widgetPalette
.color( QPalette::Active
,
492 headerBgColor
.setAlpha( 200 );
493 widgetPalette
.setColor( QPalette::Base
, headerBgColor
);
495 m_header
->setPalette( widgetPalette
);
496 m_searchTerm
->setPalette( widgetPalette
);
497 m_matchList
->setPalette( widgetPalette
);
500 void Interface::resetInterface()
502 m_header
->setText(i18n("Enter the name of an application, location or search term below."));
503 m_header
->setPixmap("system-search");
505 m_context
.resetSearchTerm(QString());
506 m_searchTerm
->setCurrentItem(QString(), true, 0);
507 m_matchList
->clear();
508 m_runButton
->setEnabled( false );
509 m_optionsButton
->setEnabled( false );
510 showOptions( false );
514 void Interface::closeEvent(QCloseEvent
* e
)
520 void Interface::matchActivated(QListWidgetItem
* item
)
522 SearchMatch
* match
= dynamic_cast<SearchMatch
*>(item
);
528 if (!match
->actionEnabled()) {
532 QString searchTerm
= m_searchTerm
->currentText();
533 m_searchTerm
->addToHistory(searchTerm
);
535 if (match
->actionType() == Plasma::SearchMatch::InformationalMatch
) {
536 //kDebug() << "informational match activated" << match->toString();
537 m_searchTerm
->setItemText(m_searchTerm
->currentIndex(), match
->toString());
539 //kDebug() << "match activated! " << match->text();
540 QString term
= m_searchTerm
->currentText().trimmed();
541 m_context
.setSearchTerm(term
);
543 match
->activate(&m_context
);
548 void Interface::queueMatch()
550 if (m_matchTimer
.isActive()) {
553 Weaver::instance()->dequeue();
554 m_matchTimer
.start(200);
557 void Interface::queueUpdates()
559 if (m_updateTimer
.isActive()) {
562 //Wait 100ms between updating matches
563 m_updateTimer
.start(100);
566 void Interface::match()
568 // If ThreadWeaver is idle, it is safe to clear previous jobs
569 if ( Weaver::instance()->isIdle() ) {
570 qDeleteAll( m_searchJobs
);
571 m_searchJobs
.clear();
575 QString term
= m_searchTerm
->currentText().trimmed();
577 if (term
.isEmpty()) {
579 m_execQueued
= false;
582 m_context
.resetSearchTerm(term
);
583 m_context
.addStringCompletions(m_searchTerm
->historyItems());
585 foreach (Plasma::AbstractRunner
* runner
, m_runners
) {
586 Job
*job
= new FindMatchesJob(term
, runner
, &m_context
, this);
587 Weaver::instance()->enqueue( job
);
588 m_searchJobs
.append( job
);
592 void Interface::updateMatches()
594 m_matchList
->clear();
597 foreach (const Plasma::SearchMatch
*action
, m_context
.matches()) {
598 SearchMatch
*match
= new SearchMatch(action
, m_matchList
);
600 if (action
->isEnabled() && action
->relevance() > 0 &&
601 (!m_defaultMatch
|| *m_defaultMatch
< *match
) &&
602 (action
->type() != Plasma::SearchMatch::InformationalMatch
||
603 !action
->data().toString().isEmpty())) {
604 if (m_defaultMatch
) {
605 m_defaultMatch
->setDefault(false);
608 match
->setDefault(true);
609 m_defaultMatch
= match
;
610 m_optionsButton
->setEnabled(action
->runner()->hasMatchOptions());
611 m_runButton
->setEnabled(true);
615 m_matchList
->sortItems(Qt::DescendingOrder
);
616 m_execQueued
= false;
618 if (!m_defaultMatch
) {
620 m_runButton
->setEnabled(false);
621 } else if (m_execQueued
) {
626 void Interface::exec()
628 if (!m_execQueued
&& m_searchTerm
->completionBox() && m_searchTerm
->completionBox()->isVisible()) {
633 kDebug() << "match list has" << m_matchList
->count() << "items";
635 QListWidgetItem
* currentMatch
= m_matchList
->currentItem();
637 if (m_defaultMatch
) {
638 currentMatch
= m_defaultMatch
;
639 } else if (m_matchList
->count() < 2) {
640 //TODO: the < 2 is a bit of a hack; we *always* get a search option returned,
641 // so if we only have 1 item and it's not selected, guess what it is? ;)
642 // we might be able to do better here.
648 matchActivated(currentMatch
);
651 void Interface::showOptions(bool show
)
653 //TODO: in the case where we are no longer showing options
654 // should we have the runner delete it's options?
656 if (!m_defaultMatch
|| !m_defaultMatch
->hasMatchOptions()) {
657 // in this case, there is nothing to show
662 //kDebug() << "creating m_expander";
663 m_expander
= new CollapsibleWidget( this );
665 connect( m_expander
, SIGNAL( collapseCompleted() ),
666 m_expander
, SLOT( close() ) );
667 m_layout
->insertWidget( 3, m_expander
);
670 //kDebug() << "set inner widget to " << m_defaultMatch->runner()->options();
671 delete m_optionsWidget
;
672 m_optionsWidget
= new QWidget(this);
673 m_defaultMatch
->createMatchOptions(m_optionsWidget
);
674 m_optionsButton
->setText( i18n( "Hide Options" ) );
676 delete m_optionsWidget
;
678 m_optionsButton
->setText( i18n( "Show Options" ) );
683 //TODO: we need to insert an element into the krunner dialog
684 // that is big enough for the options. this will prevent
685 // other items in the dialog from moving around and look
686 // more "natural"; it should appear as if a "drawer" is
687 // being pulled open, e.g. an expander.
688 m_expander
->setInnerWidget(m_optionsWidget
);
689 m_expander
->setVisible(show
);
690 m_expander
->setExpanded(show
);
693 m_optionsButton
->setChecked(show
);
696 void Interface::setDefaultItem( QListWidgetItem
* item
)
698 bool hasOptions
= false;
701 if (m_defaultMatch
) {
702 m_defaultMatch
->setDefault(false);
705 m_defaultMatch
= dynamic_cast<SearchMatch
*>(item
);
706 if (!m_defaultMatch
) {
709 hasOptions
= m_defaultMatch
&& m_defaultMatch
->hasMatchOptions();
712 m_optionsButton
->setEnabled(hasOptions
);
715 if (m_expander
&& m_expander
->isExpanded()) {
716 m_optionsButton
->setText(i18n("Hide Options"));
718 m_optionsButton
->setText(i18n("Show Options"));
721 m_optionsButton
->setText(i18n("Show Options"));
722 m_optionsButton
->setChecked(false);
729 #include "interface.moc"