Merge branch 'central-widget'
[krunner.git] / interface.cpp
blobe2946c09a66a81eac8b4e31764c5e13254d171b3
1 /*
2 * Copyright (C) 2006 Aaron Seigo <aseigo@kde.org>
3 * Copyright (C) 2007 Riccardo Iaconelli <riccardo@kde.org>
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU Library General Public License version 2 as
7 * published by the Free Software Foundation
9 * This program 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
12 * GNU General Public License for more details
14 * You should have received a copy of the GNU Library General Public
15 * License along with this program; if not, write to the
16 * Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 #include <QAction>
21 #include <QApplication>
22 #include <QLabel>
23 #include <QListWidget>
24 #include <QVBoxLayout>
25 #include <QHBoxLayout>
26 #include <QShortcut>
27 #include <QTimer>
28 #include <QHideEvent>
29 #include <QGraphicsItem>
30 #include <QTimeLine>
32 #include <KActionCollection>
33 #include <KDebug>
34 #include <KDialog>
35 #include <KLineEdit>
36 #include <KLocale>
37 #include <KGlobalSettings>
38 #include <KPushButton>
39 #include <KStandardGuiItem>
40 #include <KTitleWidget>
41 #include <KWindowSystem>
43 #include <plasma/abstractrunner.h>
44 #include <QPainter>
45 #include <QSvgRenderer>
46 #include <QResizeEvent>
47 #include <QBitmap>
49 #include <KDebug>
51 #include <plasma/widgets/lineedit.h>
53 #include "runners/services/servicerunner.h"
54 #include "runners/sessions/sessionrunner.h"
55 #include "runners/shell/shellrunner.h"
56 #include "collapsiblewidget.h"
57 #include "interface.h"
58 #include "interfaceadaptor.h"
59 #include "krunnerapp.h"
61 //FIXME:
63 #define KRUNNER_SIZE 350, 101
66 // A little hack of a class to let us easily activate a match
68 class SearchMatch : public QListWidgetItem
70 public:
71 SearchMatch( QAction* action, Plasma::AbstractRunner* runner, QListWidget* parent )
72 : QListWidgetItem( parent ),
73 m_default( false ),
74 m_action( 0 ),
75 m_runner( runner )
77 setAction( action );
80 void activate()
82 m_action->activate( QAction::Trigger );
85 bool actionEnabled()
87 return m_action->isEnabled();
90 void setAction( QAction* action )
92 m_action = action;
93 setIcon( m_action->icon() );
94 setText( i18n("%1 (%2)",
95 m_action->text(),
96 m_runner->objectName() ) );
98 // in case our new action is now enabled and the old one wasn't, or
99 // vice versa
100 setDefault( m_default );
103 Plasma::AbstractRunner* runner()
105 return m_runner;
108 void setDefault( bool def ) {
109 if ( m_default == def ) {
110 return;
113 m_default = def;
115 if ( m_default ) {
116 if ( m_action->isEnabled() ) {
117 setText( text().prepend( i18n("Default: ") ) );
119 } else {
120 setText( text().mid( 9 ) );
124 private:
125 bool m_default;
126 QAction* m_action;
127 Plasma::AbstractRunner* m_runner;
130 GenericItem::GenericItem(Plasma::Svg *renderer, Interface *qgv)
132 m_background = renderer;
133 m_qgv = qgv;
137 void GenericItem::setElement(const QString &element)
139 m_element = element;
140 m_background->resize(350, 101);
144 QRectF GenericItem::itemRectF() const
146 QRectF rect;
147 if (m_element == "top-left" || m_element == "top-right" ||
148 m_element == "bottom-left" || m_element == "bottom-right") {
150 rect = QRectF(0, 0, 9, 50);
152 } else if (m_element == "top" || m_element == "bottom") {
154 rect = QRectF(0, 0, 332, 50);
156 } else if (m_element == "separator") {
157 rect = QRectF(0, 0, 348, 1);
158 } else if (m_element == "background") {
159 rect = QRectF(0, 0, 350, m_h);
161 return rect;
164 QRectF GenericItem::boundingRect() const
166 if (m_element.isEmpty()) {
167 return QRectF(0, 0, 0, 0);
169 return itemRectF();
172 void GenericItem::paint(QPainter *painter,
173 const QStyleOptionGraphicsItem *option,
174 QWidget *widget)
176 m_background->paint(painter, itemRectF(), m_element);
180 // MainItem::MainItem()
181 // {
182 // m_background = new Plasma::Svg( "dialogs/krunner", this );
183 // m_background->setContentType(Plasma::Svg::ImageSet);
184 // // connect( m_background, SIGNAL(repaintNeeded()), this, SLOT(update()) );
185 // m_background->resize(KRUNNER_SIZE);
186 // }
188 // void MainItem::paint(QPainter *painter,
189 // const QStyleOptionGraphicsItem *option,
190 // QWidget *widget)
191 // {
192 // // m_background->paint( painter, QRectF(0, 0, 9, 50), "top-left" );
193 // // m_background->paint( painter, QRectF(9, 0, 332, 50), "top" );
194 // // m_background->paint( painter, QRectF(341, 0, 9, 50), "top-right" );
195 // // m_background->paint( painter, QRectF(1, 50, 348, 1), "separator" );
196 // // m_background->paint( painter, QRectF(0, 51, 9, 50), "bottom-left" );
197 // // m_background->paint( painter, QRectF(9, 51, 332, 50), "bottom" );
198 // // m_background->paint( painter, QRectF(341, 51, 9, 50), "bottom-right" );
199 // // m_background->paint( painter, 0, 0 );
200 // }
202 void PushIcon::setIcon(const QString &icon)
204 m_icon = icon;
207 void PushIcon::paint(QPainter *painter,
208 const QStyleOptionGraphicsItem *option,
209 QWidget *widget)
211 KIcon exec(m_icon);
212 painter->drawPixmap(0, 0, exec.pixmap(22, 22));
215 Interface::Interface(QWidget* parent)
216 : QGraphicsView( parent ),
217 // m_expander( 0 ),
218 m_defaultMatch( 0 )
220 m_expanded = false;
221 m_background = new Plasma::Svg("dialogs/krunner", this);
222 m_background->setContentType(Plasma::Svg::ImageSet);
223 m_background->resize(350, 101);
224 connect(m_background, SIGNAL(repaintNeeded()), this, SLOT(update()));
226 setWindowTitle(i18n("Run Command"));
228 connect(&m_searchTimer, SIGNAL(timeout()),
229 this, SLOT(fuzzySearch()));
231 topleft = new GenericItem(m_background, this);
232 topleft->setElement("top-left");
233 top = new GenericItem(m_background, this);
234 top->setElement("top");
235 topright = new GenericItem(m_background, this);
236 topright->setElement("top-right");
237 separator = new GenericItem(m_background, this);
238 separator->setElement("separator");
239 separator2 = new GenericItem(m_background, this);
240 separator2->setElement("separator");
241 background = new GenericItem(m_background, this);
242 background->setElement("background");
243 bottomleft = new GenericItem(m_background, this);
244 bottomleft->setElement("bottom-left");
245 bottom = new GenericItem(m_background, this);
246 bottom->setElement("bottom");
247 bottomright = new GenericItem(m_background, this);
248 bottomright->setElement("bottom-right");
249 lineEdit = new Plasma::LineEdit;
250 m_toggleExpand = new PushIcon(this);
251 m_toggleExpand->setIcon("arrow-down");
252 close = new PushIcon(this);
253 close->setIcon("no");
255 QGraphicsScene *scene = new QGraphicsScene(this);
256 scene->setItemIndexMethod(QGraphicsScene::NoIndex);
257 scene->setSceneRect(0, 0, KRUNNER_SIZE);
258 setScene(scene);
259 setCacheMode(CacheBackground);
260 setRenderHint(QPainter::Antialiasing);
263 scene->addItem(background);
264 scene->addItem(topleft);
265 scene->addItem(top);
266 scene->addItem(topright);
267 scene->addItem(separator);
268 scene->addItem(separator2);
269 scene->addItem(bottomleft);
270 scene->addItem(bottom);
271 scene->addItem(bottomright);
272 scene->addItem(lineEdit);
273 scene->addItem(m_toggleExpand);
274 scene->addItem(close);
276 topleft->setPos(0, 0);
277 top->setPos(9, 0);
278 topright->setPos(341, 0);
279 separator->setPos(1, 50);
280 separator2->setPos(1, 52);
281 separator2->setVisible(false);
282 background->setPos(0, 51);
283 background->setVisible(false);
284 // background->setZValue(0);
285 bottomleft->setPos(0, 51);
286 // bottomleft->setZValue(135);
287 bottom->setPos(9, 51);
288 // bottom->setZValue(134);
289 bottomright->setPos(341, 51);
290 // bottomright->setZValue(133);
291 m_toggleExpand->setPos(30, 16);
292 lineEdit->setPos(75, 15);
293 lineEdit->setZValue(5);
294 lineEdit->setTextWidth(200);
295 //FIXME: the label doesn't really behaves correctly (try to write some more text in).
296 lineEdit->setDefaultText("Type something here please...");
297 close->setPos(305, 16);
299 connect(close, SIGNAL(clicked()), this, SLOT(close()));
300 connect(m_toggleExpand, SIGNAL(clicked()), this, SLOT(expand()));
302 resize(KRUNNER_SIZE);
304 setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
305 setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
306 setFrameShape(QFrame::NoFrame);
307 new QShortcut(QKeySequence(Qt::Key_Escape), this, SLOT(hide()));
308 KDialog::centerOnScreen(this);
312 void Interface::resizeEvent( QResizeEvent *e)
314 // QGraphicsView::resizeEvent( e );
315 int addedHeight = e->size().height()-101;
316 QBitmap bitmap(350, 101+addedHeight);
317 bitmap.fill(Qt::color0);
318 QPainter p(&bitmap);
319 p.setBrush(Qt::black);
321 // NOTE: this is just for the clipping, so I don't really care if the look of the stuff painted
322 // is good or not
323 m_background->resize(350, 101);
324 m_background->paint( &p, QRectF(0, 0, 9, 50), "top-left" );
325 m_background->paint( &p, QRectF(9, 0, 332, 50), "top" );
326 m_background->paint( &p, QRectF(341, 0, 9, 50), "top-right" );
327 m_background->paint( &p, QRectF(1, 50, 348, 1), "separator" );
328 p.drawRect(0, 50, 349, addedHeight);
329 m_background->paint( &p, QRectF(0, 51+addedHeight, 9, 50), "bottom-left" );
330 m_background->paint( &p, QRectF(9, 51+addedHeight, 332, 50), "bottom" );
331 m_background->paint( &p, QRectF(341, 51+addedHeight, 9, 50), "bottom-right" );
333 p.end();
334 setMask(bitmap);
336 topleft->setPos(0, 0);
337 top->setPos(9, 0);
338 topright->setPos(341, 0);
339 m_toggleExpand->setPos(30, 16);
340 lineEdit->setPos(75, 15);
341 separator->setPos(1, 50);
342 if (addedHeight > 0) { //if expanded
343 separator->setPos(1, 50);
344 background->setPos(0, 51);
345 background->setSize(350, addedHeight-1);
346 background->setVisible(true);
347 separator2->setPos(1, 50+addedHeight);
348 separator2->setVisible(true);
349 } else {
350 background->setVisible(false);
351 separator2->setVisible(false);
353 bottomleft->setPos(0, 51+addedHeight);
354 bottom->setPos(9, 51+addedHeight);
355 bottomright->setPos(341, 51+addedHeight);
358 Interface::~Interface()
362 void Interface::expand()
364 disconnect(m_toggleExpand, SIGNAL(clicked()), this, SLOT(expand()));
365 connect(m_toggleExpand, SIGNAL(clicked()), this, SLOT(collapse()));
366 kDebug() << "expanding..." << endl;
367 m_expanded = true;
368 QTimeLine *tl = new QTimeLine(200, this);
369 tl->setFrameRange(10, 30);
370 tl->start();
371 connect(tl, SIGNAL(frameChanged(int)), this, SLOT(slotResize(int)));
372 m_toggleExpand->setIcon("arrow-up");
375 void Interface::collapse()
377 disconnect(m_toggleExpand, SIGNAL(clicked()), this, SLOT(collapse()));
378 connect(m_toggleExpand, SIGNAL(clicked()), this, SLOT(expand()));
379 kDebug() << "collapsing..." << endl;
380 m_expanded = false;
381 QTimeLine *tl = new QTimeLine(200, this);
382 tl->setFrameRange(30, 10);
383 tl->start();
384 connect(tl, SIGNAL(frameChanged(int)), this, SLOT(slotResize(int)));
385 m_toggleExpand->setIcon("arrow-down");
388 void Interface::slotResize(int size)
390 resize(350, size*101*0.1);
393 void Interface::display( const QString& term)
395 kDebug() << "display() called, are we visible? " << isVisible() << endl;
396 // m_searchTerm->setFocus();
398 if ( !term.isEmpty() ) {
399 // m_searchTerm->setText( term );
402 KWindowSystem::setOnDesktop(winId(), KWindowSystem::currentDesktop());
403 KDialog::centerOnScreen( this );
404 show();
405 KWindowSystem::forceActiveWindow(winId());
407 kDebug() << "about to match now that we've shown " << isVisible() << endl;
409 match(term);
412 void Interface::switchUser()
414 Plasma::AbstractRunner *sessionrunner = 0;
415 foreach (Plasma::AbstractRunner* runner, m_runners) {
416 if (qstrcmp(runner->metaObject()->className(), "SessionRunner") == 0) {
417 sessionrunner = runner;
418 break;
422 if (!sessionrunner) {
423 kDebug() << "Could not find the Sessionrunner; not showing any sessions!" << endl;
424 return;
427 display();
428 // m_header->setText(i18n("Switch users"));
429 // m_header->setPixmap("user");
430 KActionCollection *matches = sessionrunner->matches("SESSIONS", 0, 0);
432 foreach (QAction *action, matches->actions()) {
433 bool makeDefault = !m_defaultMatch && action->isEnabled();
435 // SearchMatch *match = new SearchMatch(action, sessionrunner, m_matchList);
436 // m_searchMatches.append(match);
438 if (makeDefault) {
439 // m_defaultMatch = match;
440 m_defaultMatch->setDefault(true);
441 // m_runButton->setEnabled(true);
442 // m_optionsButton->setEnabled(sessionrunner->hasOptions());
446 if (!m_defaultMatch) {
447 // m_matchList->addItem(i18n("No desktop sessions available"));
451 void Interface::setWidgetPalettes()
453 // a nice palette to use with the widgets
454 QPalette widgetPalette = palette();
455 QColor headerBgColor = widgetPalette.color( QPalette::Active,
456 QPalette::Base );
457 headerBgColor.setAlpha( 200 );
458 widgetPalette.setColor( QPalette::Base, headerBgColor );
460 // m_header->setPalette( widgetPalette );
461 // m_searchTerm->setPalette( widgetPalette );
462 // m_matchList->setPalette( widgetPalette );
465 void Interface::resetInterface()
467 // m_header->setText(i18n("Enter the name of an application, location or search term below."));
468 // m_header->setPixmap("system-search");
469 // m_searchTerm->clear();
470 m_matches.clear();
471 m_searchMatches.clear();
472 // m_matchList->clear();
473 // m_runButton->setEnabled( false );
474 // m_optionsButton->setEnabled( false );
475 showOptions( false );
478 void Interface::showEvent( QShowEvent* e )
480 Q_UNUSED( e )
482 kDebug() << "show event" << endl;
483 QGraphicsView::showEvent( e );
486 void Interface::hideEvent( QHideEvent* e )
488 kDebug() << "hide event" << endl;
489 resetInterface();
490 e->accept();
493 void Interface::matchActivated(QListWidgetItem* item)
495 SearchMatch* match = dynamic_cast<SearchMatch*>(item);
496 // m_optionsButton->setEnabled( match && match->runner()->hasOptions() );
498 if ( match && match->actionEnabled() ) {
499 //kDebug() << "match activated! " << match->text() << endl;
500 match->activate();
501 hide();
505 void Interface::match(const QString& t)
507 m_searchTimer.stop();
509 m_defaultMatch = 0;
510 QString term = t.trimmed();
512 if ( term.isEmpty() ) {
513 resetInterface();
514 return;
517 QMap<Plasma::AbstractRunner*, SearchMatch*> matches;
519 int matchCount = 0;
521 // get the exact matches
522 foreach ( Plasma::AbstractRunner* runner, m_runners ) {
523 //kDebug() << "\trunner: " << runner->objectName() << endl;
524 QAction* exactMatch = runner->exactMatch( term ) ;
526 if ( exactMatch ) {
527 SearchMatch* match = 0;
528 bool makeDefault = !m_defaultMatch && exactMatch->isEnabled();
530 QMap<Plasma::AbstractRunner*, SearchMatch*>::iterator it = m_matches.find( runner );
531 if ( it != m_matches.end() ) {
532 match = it.value();
533 match->setAction( exactMatch );
534 matches[runner] = match;
535 m_matches.erase( it );
536 } else {
537 match = new SearchMatch( exactMatch, runner, 0 );
538 // m_matchList->insertItem( matchCount, match );
541 if ( makeDefault ) {
542 match->setDefault( true );
543 m_defaultMatch = match;
544 // m_optionsButton->setEnabled( runner->hasOptions() );
545 // m_runButton->setEnabled( true );
548 ++matchCount;
549 matches[runner] = match;
553 if ( !m_defaultMatch ) {
554 showOptions( false );
555 // m_runButton->setEnabled( false );
558 qDeleteAll(m_matches);
559 m_matches = matches;
560 m_searchTimer.start( 200 );
563 void Interface::fuzzySearch()
565 m_searchTimer.stop();
567 // TODO: we may want to stop this from flickering about as well,
568 // similar to match above
569 foreach ( SearchMatch* match, m_searchMatches ) {
570 delete match;
573 m_searchMatches.clear();
575 // QString term = m_searchTerm->text().trimmed();
577 // get the inexact matches
578 // foreach ( Plasma::AbstractRunner* runner, m_runners ) {
579 // KActionCollection* matches = runner->matches( term, 10, 0 );
580 //kDebug() << "\t\tturned up " << matches->actions().count() << " matches " << endl;
581 // foreach ( QAction* action, matches->actions() ) {
582 // bool makeDefault = !m_defaultMatch && action->isEnabled();
583 //kDebug() << "\t\t " << action << ": " << action->text() << " " << !m_defaultMatch << " " << action->isEnabled() << endl;
584 // SearchMatch* match = new SearchMatch( action, runner, m_matchList );
585 // m_searchMatches.append( match );
587 // if ( makeDefault ) {
588 // m_defaultMatch = match;
589 // m_defaultMatch->setDefault( true );
590 // m_runButton->setEnabled( true );
591 // m_optionsButton->setEnabled( runner->hasOptions() );
592 // }
593 // }
594 // }
597 void Interface::updateMatches()
599 //TODO: implement
602 void Interface::exec()
604 // SearchMatch* match = dynamic_cast<SearchMatch*>( m_matchList->currentItem() );
606 // if ( match && match->actionEnabled() ) {
607 // matchActivated( match );
608 // } else if ( m_defaultMatch ) {
609 // matchActivated( m_defaultMatch );
610 // }
613 void Interface::showOptions(bool show)
615 //TODO: in the case where we are no longer showing options
616 // should we have the runner delete it's options?
617 if ( show ) {
618 if ( !m_defaultMatch || !m_defaultMatch->runner()->hasOptions() ) {
619 // in this case, there is nothing to show
620 return;
623 // if ( !m_expander ) {
624 //kDebug() << "creating m_expander" << endl;
625 // m_expander = new CollapsibleWidget( this );
626 // connect( m_expander, SIGNAL( collapseCompleted() ),
627 // m_expander, SLOT( hide() ) );
628 // m_layout->insertWidget( 3, m_expander );
629 // }
631 //kDebug() << "set inner widget to " << m_defaultMatch->runner()->options() << endl;
632 // m_expander->setInnerWidget( m_defaultMatch->runner()->options() );
633 // m_expander->show();
634 // m_optionsButton->setText( i18n( "Hide Options" ) );
635 } else {
636 // m_optionsButton->setText( i18n( "Show Options" ) );
637 // resize( 400, 250 );
638 resize(KRUNNER_SIZE);
641 // if ( m_expander ) {
642 //TODO: we need to insert an element into the krunner dialog
643 // that is big enough for the options. this will prevent
644 // other items in the dialog from moving around and look
645 // more "natural"; it should appear as if a "drawer" is
646 // being pulled open, e.g. an expander.
647 // m_expander->setExpanded( show );
648 // }
649 // m_optionsButton->setChecked( show );
652 void Interface::setDefaultItem( QListWidgetItem* item )
654 if ( !item ) {
655 return;
658 if ( m_defaultMatch ) {
659 m_defaultMatch->setDefault( false );
662 m_defaultMatch = dynamic_cast<SearchMatch*>( item );
664 bool hasOptions = m_defaultMatch && m_defaultMatch->runner()->hasOptions();
665 // m_optionsButton->setEnabled( hasOptions );
667 // if ( m_expander && !hasOptions ) {
668 // m_expander->hide();
669 // }
672 #include "interface.moc"