/* Copyright 2007 Robert Knight Copyright 2007 Kevin Ottens Copyright 2009 Ivan Cukic This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "ui/launcher.h" // System #include // Qt #include #include #include #include #include #include #include #include #include // KDE #include #include #include #include #include #include #include #include #include // Local #include "core/favoritesmodel.h" #include "core/recentlyusedmodel.h" #include "core/applicationmodel.h" #include "core/leavemodel.h" #include "core/itemhandlers.h" //#include "core/searchmodel.h" #include "core/krunnermodel.h" #include "core/systemmodel.h" #include "ui/itemdelegate.h" #include "ui/brandingbutton.h" #include "ui/contextmenufactory.h" #include "ui/urlitemview.h" #include "ui/flipscrollview.h" #include "ui/searchbar.h" #include "ui/tabbar.h" #include "ui/contentareacap.h" Q_DECLARE_METATYPE(QPersistentModelIndex) using namespace Kickoff; class Launcher::Private { public: Private(Launcher *launcher) : q(launcher) , applet(0) , urlLauncher(new UrlItemLauncher(launcher)) , searchModel(0) , leaveModel(0) , searchBar(0) , footer(0) , contentAreaHeader(0) , contentArea(0) , contentAreaFooter(0) , contentSwitcher(0) , applicationView(0) , searchView(0) , favoritesView(0) , contextMenuFactory(0) , autoHide(false) , appViewIsReceivingKeyEvents(false) , visibleItemCount(10) , placement(Plasma::TopPosedLeftAlignedPopup) , panelEdge(Plasma::BottomEdge) { } ~Private() { } enum TabOrder { NormalTabOrder, ReverseTabOrder }; void setupEventHandler(QAbstractItemView *view) { view->viewport()->installEventFilter(q); view->installEventFilter(q); } void addView(const QString& name, const QIcon& icon, QAbstractItemModel *model = 0, QAbstractItemView *view = 0, QWidget *headerWidget = 0) { view->setFrameStyle(QFrame::NoFrame); // prevent the view from stealing focus from the search bar view->setFocusPolicy(Qt::NoFocus); view->setContextMenuPolicy(Qt::CustomContextMenu); view->setSelectionMode(QAbstractItemView::SingleSelection); view->setDragEnabled(true); view->setAcceptDrops(true); view->setDropIndicatorShown(true); if (name == i18n("Favorites")) { view->setDragDropMode(QAbstractItemView::DragDrop); } else if(name == i18n("Applications") || name == i18n("Computer") || name == i18n("Recently Used")) { view->setDragDropMode(QAbstractItemView::DragOnly); } view->setModel(model); //view->setCurrentIndex(QModelIndex()); setupEventHandler(view); connect(view, SIGNAL(customContextMenuRequested(QPoint)), q, SLOT(showViewContextMenu(QPoint))); contentSwitcher->addTab(icon, name); if (!headerWidget) { contentArea->addWidget(view); } else { QWidget *parent = new QWidget; parent->setLayout(new QVBoxLayout); parent->layout()->setSpacing(0); parent->layout()->setContentsMargins(0, 0, 0, 0); parent->layout()->addWidget(headerWidget); parent->layout()->addWidget(view); contentArea->addWidget(parent); } } void initTabs() { // Favorites view setupFavoritesView(); // All Programs view setupAllProgramsView(); // System view setupSystemView(); // Recently Used view setupRecentView(); // Leave view setupLeaveView(); // Search Bar setupSearchView(); } void setupLeaveView() { leaveModel = new LeaveModel(q); leaveModel->updateModel(); UrlItemView *view = new UrlItemView(); ItemDelegate *delegate = new ItemDelegate(q); delegate->setRoleMapping(Plasma::Delegate::SubTitleRole, SubTitleRole); delegate->setRoleMapping(Plasma::Delegate::SubTitleMandatoryRole, SubTitleMandatoryRole); view->setItemDelegate(delegate); view->setItemStateProvider(delegate); addView(i18n("Leave"), KIcon("system-shutdown"), leaveModel, view); } void setupFavoritesView() { favoritesModel = new FavoritesModel(q); UrlItemView *view = new UrlItemView(); ItemDelegate *delegate = new ItemDelegate(q); delegate->setRoleMapping(Plasma::Delegate::SubTitleRole, SubTitleRole); delegate->setRoleMapping(Plasma::Delegate::SubTitleMandatoryRole, SubTitleMandatoryRole); view->setItemDelegate(delegate); view->setItemStateProvider(delegate); addView(i18n("Favorites"), KIcon("bookmarks"), favoritesModel, view); QAction *sortAscendingAction = new QAction(KIcon("view-sort-ascending"), i18n("Sort Alphabetically (A to Z)"), q); QAction *sortDescendingAction = new QAction(KIcon("view-sort-descending"), i18n("Sort Alphabetically (Z to A)"), q); connect(favoritesModel, SIGNAL(rowsInserted(QModelIndex,int,int)), q, SLOT(focusFavoritesView())); connect(sortAscendingAction, SIGNAL(triggered()), favoritesModel, SLOT(sortFavoritesAscending())); connect(sortDescendingAction, SIGNAL(triggered()), favoritesModel, SLOT(sortFavoritesDescending())); favoritesView = view; QList actions; actions << sortAscendingAction << sortDescendingAction; contextMenuFactory->setViewActions(view, actions); } void setupAllProgramsView() { applicationModel = new ApplicationModel(q); applicationModel->setDuplicatePolicy(ApplicationModel::ShowLatestOnlyPolicy); FlipScrollView *view = new FlipScrollView(); applicationView = view; ItemDelegate *delegate = new ItemDelegate(q); delegate->setRoleMapping(Plasma::Delegate::SubTitleRole, SubTitleRole); delegate->setRoleMapping(Plasma::Delegate::SubTitleMandatoryRole, SubTitleMandatoryRole); applicationView->setItemDelegate(delegate); applicationBreadcrumbs = new QWidget; applicationBreadcrumbs->setMinimumHeight(ItemDelegate::HEADER_HEIGHT); applicationBreadcrumbs->setLayout(new QHBoxLayout); applicationBreadcrumbs->layout()->setContentsMargins(0, 0, 0, 0); applicationBreadcrumbs->layout()->setSpacing(0); QPalette palette = applicationBreadcrumbs->palette(); palette.setColor(QPalette::Window, palette.color(QPalette::Active, QPalette::Base)); applicationBreadcrumbs->setPalette(palette); applicationBreadcrumbs->setAutoFillBackground(true); connect(applicationView, SIGNAL(currentRootChanged(QModelIndex)), q, SLOT(fillBreadcrumbs(QModelIndex))); connect(applicationView, SIGNAL(focusNextViewLeft()), q, SLOT(moveViewToLeft())); q->fillBreadcrumbs(QModelIndex()); addView(i18n("Applications"), KIcon("applications-other"), applicationModel, applicationView, applicationBreadcrumbs); } void setupRecentView() { recentlyUsedModel = new RecentlyUsedModel(q); UrlItemView *view = new UrlItemView(); ItemDelegate *delegate = new ItemDelegate(q); delegate->setRoleMapping(Plasma::Delegate::SubTitleRole, SubTitleRole); delegate->setRoleMapping(Plasma::Delegate::SubTitleMandatoryRole, SubTitleMandatoryRole); view->setItemDelegate(delegate); view->setItemStateProvider(delegate); addView(i18n("Recently Used"), KIcon("document-open-recent"), recentlyUsedModel, view); QAction *clearApplications = new QAction(KIcon("edit-clear-history"), i18n("Clear Recent Applications"), q); QAction *clearDocuments = new QAction(KIcon("edit-clear-history"), i18n("Clear Recent Documents"), q); connect(clearApplications, SIGNAL(triggered()), recentlyUsedModel, SLOT(clearRecentApplications())); connect(clearDocuments, SIGNAL(triggered()), recentlyUsedModel, SLOT(clearRecentDocuments())); contextMenuFactory->setViewActions(view, QList() << clearApplications << clearDocuments); } void setupSystemView() { systemModel = new SystemModel(q); UrlItemView *view = new UrlItemView(); ItemDelegate *delegate = new ItemDelegate(q); delegate->setRoleMapping(Plasma::Delegate::SubTitleRole, SubTitleRole); delegate->setRoleMapping(Plasma::Delegate::SubTitleMandatoryRole, SubTitleMandatoryRole); view->setItemDelegate(delegate); view->setItemStateProvider(delegate); addView(i18n("Computer"), systemIcon(), systemModel, view); } void setupSearchView() { searchModel = new KRunnerModel(q); UrlItemView *view = new UrlItemView(); ItemDelegate *delegate = new ItemDelegate(q); delegate->setRoleMapping(Plasma::Delegate::SubTitleRole, SubTitleRole); delegate->setRoleMapping(Plasma::Delegate::SubTitleMandatoryRole, SubTitleMandatoryRole); view->setItemDelegate(delegate); view->setItemStateProvider(delegate); view->setModel(searchModel); view->setFrameStyle(QFrame::NoFrame); view->setSelectionMode(QAbstractItemView::SingleSelection); // prevent view from stealing focus from the search bar view->setFocusPolicy(Qt::StrongFocus); view->setDragEnabled(true); setupEventHandler(view); connect(searchModel, SIGNAL(resultsAvailable()), q, SLOT(resultsAvailable())); connect(searchBar, SIGNAL(queryChanged(QString)), searchModel, SLOT(setQuery(QString))); connect(searchBar, SIGNAL(queryChanged(QString)), q, SLOT(focusSearchView(QString))); view->setContextMenuPolicy(Qt::CustomContextMenu); connect(view, SIGNAL(customContextMenuRequested(QPoint)), q, SLOT(showViewContextMenu(QPoint))); contentArea->addWidget(view); searchView = view; } void registerUrlHandlers() { UrlItemLauncher::addGlobalHandler(UrlItemLauncher::ExtensionHandler, "desktop", new ServiceItemHandler); UrlItemLauncher::addGlobalHandler(UrlItemLauncher::ProtocolHandler, "leave", new LeaveItemHandler); UrlItemLauncher::addGlobalHandler(UrlItemLauncher::ProtocolHandler, "krunner", new KRunnerItemHandler); } QIcon systemIcon() { QList batteryList = Solid::Device::listFromType(Solid::DeviceInterface::Battery, QString()); if (batteryList.isEmpty()) { return KIcon("computer"); } else { return KIcon("computer-laptop"); } } void setNorthLayout(TabOrder tabOrder) { contentSwitcher->setShape(QTabBar::RoundedNorth); QLayout * layout = q->layout(); delete layout; layout = new QVBoxLayout(); layout->addWidget(contentSwitcher); layout->addWidget(contentAreaHeader); layout->addWidget(contentArea); layout->addWidget(contentAreaFooter); layout->addWidget(searchBar); layout->addWidget(footer); layout->setSpacing(0); layout->setMargin(0); q->setLayout(layout); setTabOrder(tabOrder); } void setSouthLayout(TabOrder tabOrder) { contentSwitcher->setShape(QTabBar::RoundedSouth); QLayout * layout = q->layout(); delete layout; layout = new QVBoxLayout(); layout->addWidget(footer); layout->addWidget(searchBar); layout->addWidget(contentAreaHeader); layout->addWidget(contentArea); layout->addWidget(contentAreaFooter); layout->addWidget(contentSwitcher); layout->setSpacing(0); layout->setMargin(0); q->setLayout(layout); setTabOrder(tabOrder); } void setWestLayout(TabOrder tabOrder) { contentSwitcher->setShape(QTabBar::RoundedWest); QLayout * layout = q->layout(); delete layout; layout = new QHBoxLayout(); layout->addWidget(contentSwitcher); QBoxLayout * contentLayout = new QVBoxLayout(); contentLayout->addWidget(contentAreaHeader); contentLayout->addWidget(contentArea); contentLayout->addWidget(contentAreaFooter); layout->addItem(contentLayout); QBoxLayout * layout2 = new QVBoxLayout(); if (tabOrder == NormalTabOrder) { layout2->addLayout(layout); layout2->addWidget(searchBar); layout2->addWidget(footer); } else { layout2->addWidget(footer); layout2->addWidget(searchBar); layout2->addLayout(layout); } layout->setSpacing(0); layout->setMargin(0); layout2->setSpacing(0); layout2->setMargin(0); q->setLayout(layout2); setTabOrder(tabOrder); } void setEastLayout(TabOrder tabOrder) { contentSwitcher->setShape(QTabBar::RoundedEast); QLayout * layout = q->layout(); delete layout; layout = new QHBoxLayout(); QBoxLayout * contentLayout = new QVBoxLayout(); contentLayout->addWidget(contentAreaHeader); contentLayout->addWidget(contentArea); contentLayout->addWidget(contentAreaFooter); layout->addItem(contentLayout); layout->addWidget(contentSwitcher); QBoxLayout * layout2 = new QVBoxLayout(); if (tabOrder == NormalTabOrder) { layout2->addLayout(layout); layout2->addWidget(searchBar); layout2->addWidget(footer); } else { layout2->addWidget(footer); layout2->addWidget(searchBar); layout2->addLayout(layout); } layout->setSpacing(0); layout->setMargin(0); layout2->setSpacing(0); layout2->setMargin(0); q->setLayout(layout2); setTabOrder(tabOrder); } void setTabOrder(TabOrder newOrder) { // identify current TabOrder, assumes favoritesView is first in normal order TabOrder oldOrder; if (contentArea->widget(0) == favoritesView) { oldOrder = NormalTabOrder; } else { oldOrder = ReverseTabOrder; } if (newOrder == oldOrder) { return; } // remove the and widgets and store their data in a separate structure // remove this first so we can cleanly remove the widgets controlled by contentSwitcher contentArea->removeWidget(searchView); Q_ASSERT(contentArea->count() == contentSwitcher->count()); QList removedTabs; for (int i = contentSwitcher->count() - 1; i >= 0 ; i--) { WidgetTabData wtd; wtd.tabText = contentSwitcher->tabText(i); wtd.tabToolTip = contentSwitcher->tabToolTip(i); wtd.tabWhatsThis = contentSwitcher->tabWhatsThis(i); wtd.tabIcon = contentSwitcher->tabIcon(i); wtd.widget = contentArea->widget(i); removedTabs.append(wtd); contentSwitcher->removeTab(i); contentArea->removeWidget(contentArea->widget(i)); } // then reinsert them in reversed order int i = 0; foreach(const WidgetTabData &wtd, removedTabs) { contentSwitcher->addTab(wtd.tabIcon, wtd.tabText); contentSwitcher->setTabToolTip(i, wtd.tabToolTip); contentSwitcher->setTabWhatsThis(i, wtd.tabWhatsThis); contentArea->addWidget(wtd.widget); ++i; } //finally replace the searchView contentArea->addWidget(searchView); } struct WidgetTabData { QString tabText; QString tabToolTip; QString tabWhatsThis; QIcon tabIcon; QWidget *widget; }; Launcher * const q; Plasma::Applet *applet; UrlItemLauncher *urlLauncher; FavoritesModel *favoritesModel; ApplicationModel *applicationModel; RecentlyUsedModel *recentlyUsedModel; KRunnerModel *searchModel; SystemModel *systemModel; LeaveModel *leaveModel; SearchBar *searchBar; QWidget *footer; QLabel *userinfo; ContentAreaCap *contentAreaHeader; QStackedWidget *contentArea; ContentAreaCap *contentAreaFooter; TabBar *contentSwitcher; FlipScrollView *applicationView; QWidget *applicationBreadcrumbs; UrlItemView *searchView; QAbstractItemView *favoritesView; ContextMenuFactory *contextMenuFactory; bool autoHide; bool appViewIsReceivingKeyEvents; int visibleItemCount; Plasma::PopupPlacement placement; Plasma::Location panelEdge; }; Launcher::Launcher(QWidget *parent) : QWidget(parent, Qt::Window) , d(new Private(this)) { init(); } Launcher::Launcher(Plasma::Applet *applet) : QWidget(0, Qt::Window) , d(new Private(this)) { init(); setApplet(applet); } void Launcher::init() { QVBoxLayout *layout = new QVBoxLayout; layout->setSpacing(0); layout->setMargin(0); const int rightHeaderMargin = style()->pixelMetric(QStyle::PM_ScrollBarExtent); d->searchBar = new SearchBar(this); if (layoutDirection() == Qt::LeftToRight) { d->searchBar->setContentsMargins(0, 0, rightHeaderMargin, 0); } else { d->searchBar->setContentsMargins(rightHeaderMargin, 0, 0, 0); } d->searchBar->installEventFilter(this); d->contentAreaHeader = new ContentAreaCap(this); d->contentArea = new QStackedWidget(this); bool flipCap = true; d->contentAreaFooter = new ContentAreaCap(this, flipCap); d->contentSwitcher = new TabBar(this); d->contentSwitcher->installEventFilter(this); d->contentSwitcher->setIconSize(QSize(48, 48)); d->contentSwitcher->setShape(QTabBar::RoundedSouth); connect(d->contentSwitcher, SIGNAL(currentChanged(int)), d->contentArea, SLOT(setCurrentIndex(int))); d->contextMenuFactory = new ContextMenuFactory(this); d->initTabs(); d->registerUrlHandlers(); // Add status information footer d->footer = new QWidget; char hostname[256]; hostname[0] = '\0'; if (!gethostname(hostname, sizeof(hostname))) { hostname[sizeof(hostname)-1] = '\0'; } KUser user; QString fullName = user.property(KUser::FullName).toString(); QString labelText; if (fullName.isEmpty()) { labelText = i18nc("login name, hostname", "User %1 on %2", user.loginName(), hostname); } else { labelText = i18nc("full name, login name, hostname", "%1 (%2) on %3", fullName, user.loginName(), hostname); } d->userinfo = new QLabel(labelText); QToolButton *branding = new BrandingButton(this); branding->setAutoRaise(false); branding->setToolButtonStyle(Qt::ToolButtonIconOnly); connect(branding, SIGNAL(clicked()), this, SIGNAL(aboutToHide())); QHBoxLayout *brandingLayout = new QHBoxLayout; brandingLayout->setMargin(3); brandingLayout->addSpacing(ItemDelegate::ITEM_LEFT_MARGIN - 3); brandingLayout->addWidget(d->userinfo); brandingLayout->addStretch(2); brandingLayout->addWidget(branding); brandingLayout->addSpacing(rightHeaderMargin); d->footer->setLayout(brandingLayout); layout->addWidget(d->footer); layout->addWidget(d->searchBar); layout->addWidget(d->contentAreaHeader); layout->addWidget(d->contentArea); layout->addWidget(d->contentAreaFooter); layout->addWidget(d->contentSwitcher); setLayout(layout); setAttribute(Qt::WA_TranslucentBackground); //setBackgroundRole(QPalette::AlternateBase); //setAutoFillBackground(true); updateThemedPalette(); connect(Plasma::Theme::defaultTheme(), SIGNAL(themeChanged()), this, SLOT(updateThemedPalette())); setAppViewIsReceivingKeyEvents(false); // Left/Right arrow keys are delivered elsewhere (for now) } void Launcher::updateThemedPalette() { QColor color = Plasma::Theme::defaultTheme()->color(Plasma::Theme::TextColor); QPalette p = d->userinfo->palette(); p.setColor(QPalette::Normal, QPalette::WindowText, color); p.setColor(QPalette::Inactive, QPalette::WindowText, color); d->userinfo->setPalette(p); } QSize Launcher::minimumSizeHint() const { QSize size; switch (d->panelEdge) { case Plasma::LeftEdge: case Plasma::RightEdge: size.rheight() = d->searchBar->sizeHint().height() + d->footer->sizeHint().height() + qMax(d->favoritesView->sizeHintForRow(0) * 3 + ItemDelegate::HEADER_HEIGHT, d->contentSwitcher->sizeHint().height()); size.rwidth() = d->contentSwitcher->sizeHint().width() + d->favoritesView->sizeHint().width(); break; case Plasma::TopEdge: case Plasma::BottomEdge: default: size.rheight() = d->searchBar->sizeHint().height() + d->contentSwitcher->sizeHint().height() + d->footer->sizeHint().height() + d->favoritesView->sizeHintForRow(0) * 3 + ItemDelegate::HEADER_HEIGHT; size.rwidth() = d->contentSwitcher->sizeHint().width(); break; } return size; } QSize Launcher::sizeHint() const { return QSize(minimumSizeHint().width(), 500); } void Launcher::setAutoHide(bool hide) { d->autoHide = hide; } bool Launcher::autoHide() const { return d->autoHide; } void Launcher::setSwitchTabsOnHover(bool switchOnHover) { if (d->applet && switchOnHover != d->contentSwitcher->switchTabsOnHover()) { KConfigGroup cg = d->applet->globalConfig(); cg.writeEntry("SwitchTabsOnHover", switchOnHover); emit configNeedsSaving(); } d->contentSwitcher->setSwitchTabsOnHover(switchOnHover); } void Launcher::setShowAppsByName(bool showAppsByName) { //ApplicationModel *m_applicationModel = applicationModel; const bool wasByName = d->applicationModel->nameDisplayOrder() == Kickoff::NameBeforeDescription; if (d->applet && showAppsByName != wasByName) { KConfigGroup cg = d->applet->config(); cg.writeEntry("ShowAppsByName", showAppsByName); emit configNeedsSaving(); } if (showAppsByName) { d->applicationModel->setNameDisplayOrder(Kickoff::NameBeforeDescription); d->applicationModel->setPrimaryNamePolicy(Kickoff::ApplicationModel::AppNamePrimary); d->recentlyUsedModel->setNameDisplayOrder(Kickoff::NameBeforeDescription); d->favoritesModel->setNameDisplayOrder(Kickoff::NameBeforeDescription); d->searchModel->setNameDisplayOrder(Kickoff::NameBeforeDescription); } else { d->applicationModel->setNameDisplayOrder(Kickoff::NameAfterDescription); d->applicationModel->setPrimaryNamePolicy(Kickoff::ApplicationModel::GenericNamePrimary); d->recentlyUsedModel->setNameDisplayOrder(Kickoff::NameAfterDescription); d->favoritesModel->setNameDisplayOrder(Kickoff::NameAfterDescription); d->searchModel->setNameDisplayOrder(Kickoff::NameAfterDescription); } } void Launcher::setShowRecentlyInstalled(bool showRecentlyInstalled) { const bool wasShowRecentlyInstalled = d->applicationModel->showRecentlyInstalled(); if (d->applet && showRecentlyInstalled != wasShowRecentlyInstalled) { KConfigGroup cg = d->applet->config(); cg.writeEntry("ShowRecentlyInstalled", showRecentlyInstalled); emit configNeedsSaving(); } d->applicationModel->setShowRecentlyInstalled(showRecentlyInstalled); } bool Launcher::switchTabsOnHover() const { return d->contentSwitcher->switchTabsOnHover(); } bool Launcher::showAppsByName() const { return d->applicationModel->nameDisplayOrder() == Kickoff::NameBeforeDescription; } bool Launcher::showRecentlyInstalled() const { return d->applicationModel->showRecentlyInstalled(); } void Launcher::setVisibleItemCount(int count) { d->visibleItemCount = count; } int Launcher::visibleItemCount() const { return d->visibleItemCount; } void Launcher::setAppViewIsReceivingKeyEvents(bool isReceiving) { d->appViewIsReceivingKeyEvents = isReceiving; } bool Launcher::appViewIsReceivingKeyEvents() const { return d->appViewIsReceivingKeyEvents; } void Launcher::setApplet(Plasma::Applet *applet) { KConfigGroup cg = applet->globalConfig(); setSwitchTabsOnHover(cg.readEntry("SwitchTabsOnHover", switchTabsOnHover())); cg = applet->config(); setShowAppsByName(cg.readEntry("ShowAppsByName", showAppsByName())); setVisibleItemCount(cg.readEntry("VisibleItemsCount", visibleItemCount())); setShowRecentlyInstalled(cg.readEntry("ShowRecentlyInstalled", showRecentlyInstalled())); d->applet = applet; d->contextMenuFactory->setApplet(applet); d->applicationModel->setApplet(applet); } void Launcher::reset() { d->contentSwitcher->setCurrentIndexWithoutAnimation(d->contentArea->indexOf(d->favoritesView)); d->contentArea->setCurrentWidget(d->favoritesView); d->searchBar->clear(); d->applicationView->viewRoot(); d->leaveModel->updateModel(); } Launcher::~Launcher() { delete d; } void Launcher::focusSearchView(const QString& query) { bool queryEmpty = query.isEmpty(); d->contentSwitcher->setVisible(queryEmpty); if (!queryEmpty) { d->contentArea->setCurrentWidget(d->searchView); } else { focusFavoritesView(); } } void Launcher::focusFavoritesView() { d->contentSwitcher->setCurrentIndex(d->contentArea->indexOf(d->favoritesView)); d->contentArea->setCurrentWidget(d->favoritesView); d->contentSwitcher->setVisible(true); d->searchBar->clear(); setAppViewIsReceivingKeyEvents(false); } void Launcher::moveViewToLeft() { d->contentSwitcher->setCurrentIndex(d->contentArea->indexOf(d->favoritesView)); d->contentArea->setCurrentWidget(d->favoritesView); d->contentSwitcher->setVisible(true); d->searchBar->clear(); setAppViewIsReceivingKeyEvents(false); } bool Launcher::eventFilter(QObject *object, QEvent *event) { // deliver unhandled key presses from the search bar // (mainly arrow keys, enter) to the active view if (event->type() == QEvent::KeyPress) { QKeyEvent *keyEvent = static_cast(event); if ((object == d->contentSwitcher || object == d->searchBar)) { // left/right should Nav the tabbar, unless ApplicationView is receiving them. if (keyEvent->modifiers() == Qt::NoModifier && (keyEvent->key() == Qt::Key_Left || keyEvent->key() == Qt::Key_Right)) { kDebug() << "launcher, at filter #2"; if (d->applicationView->isVisible() && appViewIsReceivingKeyEvents()) { QCoreApplication::sendEvent(d->applicationView, event); return true; } if (object == d->contentSwitcher) { return false; } else { QCoreApplication::sendEvent(d->contentSwitcher, event); return true; } } // Next: Up/Down should ALWAYS go into the ApplicationView, if it is visible. if (keyEvent->modifiers() == Qt::NoModifier && (keyEvent->key() == Qt::Key_Up || keyEvent->key() == Qt::Key_Down)) { kDebug() << "launcher, at filter #3A"; if (d->applicationView->isVisible()) { kDebug() << "launcher, at filter #3B"; setAppViewIsReceivingKeyEvents(true); QCoreApplication::sendEvent(d->applicationView, event); return true; } } // Third: Up/Down, Key_Enter, and Key_Return must be explicitly sent into the ApplicationView., if it is receiving Keys if (keyEvent->key() == Qt::Key_Up || keyEvent->key() == Qt::Key_Down || keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return) { kDebug() << "launcher, at filter #3"; if (d->applicationView->isVisible()) { if (appViewIsReceivingKeyEvents()) { QCoreApplication::sendEvent(d->applicationView, event); return true; } } } // if the search view is visible, we are passing the events to it if (d->searchView->isVisible()) { if (!d->searchView->initializeSelection() || keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter) { kDebug() << "Passing the event to the search view" << event; QCoreApplication::sendEvent(d->searchView, event); } return true; } // getting for the active view, and passing the events to it QAbstractItemView *activeView = qobject_cast(d->contentArea->currentWidget()); if (activeView) { QCoreApplication::sendEvent(activeView, event); return true; } } } // the mouse events we are interested in are delivered to the viewport, // other events are delivered to the view itself. QAbstractItemView *view = qobject_cast(object); if (!view) { view = qobject_cast(object->parent()); } if (view) { QModelIndex openIndex; if (event->type() == QEvent::MouseButtonRelease) { QMouseEvent *mouseEvent = (QMouseEvent*)event; const QModelIndex index = view->indexAt(mouseEvent->pos()); if (index.isValid() && index.model()->hasChildren(index) == false && mouseEvent->button() == Qt::LeftButton) { openIndex = index; } } else if (event->type() == QEvent::KeyPress) { QKeyEvent *keyEvent = (QKeyEvent*)event; const QModelIndex index = view->currentIndex(); if (index.isValid() && index.model()->hasChildren(index) == false && (keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return)) { openIndex = index; } } if (openIndex.isValid()) { d->urlLauncher->openItem(openIndex); // Clear the search bar when enter was pressed if (event->type() == QEvent::KeyPress) { d->searchBar->clear(); } if (d->autoHide) { emit aboutToHide(); } return true; } } return QWidget::eventFilter(object, event); } void Launcher::showViewContextMenu(const QPoint& pos) { QAbstractItemView *view = qobject_cast(sender()); if (view) { const QModelIndex index = view->indexAt(pos); d->contextMenuFactory->showContextMenu(view, index, pos); } } void Launcher::hideEvent(QHideEvent *event) { Q_UNUSED(event) reset(); d->systemModel->stopRefreshingUsageInfo(); } void Launcher::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Escape) { emit aboutToHide(); } #if 0 // allow tab switching by pressing the left or right arrow keys if (event->key() == Qt::Key_Left && d->contentSwitcher->currentIndex() > 0) { d->contentSwitcher->setCurrentIndex(d->contentSwitcher->currentIndex() - 1); } else if (event->key() == Qt::Key_Right && d->contentSwitcher->currentIndex() < d->contentSwitcher->count() - 1) { d->contentSwitcher->setCurrentIndex(d->contentSwitcher->currentIndex() + 1); } #endif } void Launcher::showEvent(QShowEvent *e) { d->searchBar->setFocus(); d->systemModel->refreshUsageInfo(); QWidget::showEvent(e); } void Launcher::resultsAvailable() { const QModelIndex root = d->searchModel->index(0, 0); d->searchView->setCurrentIndex(d->searchModel->index(0, 0, root)); } void Launcher::setLauncherOrigin(const Plasma::PopupPlacement placement, Plasma::Location location) { if (d->placement != placement) { d->placement = placement; Private::TabOrder normalOrder; Private::TabOrder reverseOrder; //QTabBar attempts to be smart and flips what we say in rtl mode so say the opposite when the tabbar is horizontal if (QApplication::layoutDirection() == Qt::RightToLeft) { normalOrder = Private::ReverseTabOrder; reverseOrder = Private::NormalTabOrder; } else { normalOrder = Private::NormalTabOrder; reverseOrder = Private::ReverseTabOrder; } switch (placement) { case Plasma::TopPosedRightAlignedPopup: d->setSouthLayout(reverseOrder); break; case Plasma::LeftPosedTopAlignedPopup: //when the tabbar is vertical it's fine d->setEastLayout(Private::NormalTabOrder); break; case Plasma::LeftPosedBottomAlignedPopup: d->setEastLayout(Private::ReverseTabOrder); break; case Plasma::BottomPosedLeftAlignedPopup: d->setNorthLayout(normalOrder); break; case Plasma::BottomPosedRightAlignedPopup: d->setNorthLayout(reverseOrder); break; case Plasma::RightPosedTopAlignedPopup: d->setWestLayout(Private::NormalTabOrder); break; case Plasma::RightPosedBottomAlignedPopup: d->setWestLayout(Private::ReverseTabOrder); break; case Plasma::TopPosedLeftAlignedPopup: default: d->setSouthLayout(normalOrder); break; } } d->panelEdge = location; reset(); } void Launcher::fillBreadcrumbs(const QModelIndex &index) { QList children = d->applicationBreadcrumbs->findChildren(); foreach (QWidget *child, children) { child->setParent(0); child->hide(); child->deleteLater(); } QHBoxLayout *layout = static_cast(d->applicationBreadcrumbs->layout()); while (layout->count() > 0) { delete layout->takeAt(0); } layout->addStretch(10); QModelIndex current = index; while (current.isValid()) { addBreadcrumb(current, current == index); current = current.parent(); } // show a '>' only if the index is valid, and therefore All Applications is not alone up there addBreadcrumb(QModelIndex(), !index.isValid()); } void Launcher::addBreadcrumb(const QModelIndex &index, bool isLeaf) { QPushButton *button = new QPushButton(d->applicationBreadcrumbs); button->setFont(KGlobalSettings::smallestReadableFont()); button->setFlat(true); button->setStyleSheet("* { padding: 4 }"); button->setCursor(Qt::PointingHandCursor); QPalette palette = button->palette(); palette.setColor(QPalette::ButtonText, palette.color(QPalette::Disabled, QPalette::ButtonText)); button->setPalette(palette); QString suffix; if (isLeaf) { button->setEnabled(false); } else { suffix = " >"; } if (index.isValid()) { button->setText(index.data().toString()+suffix); } else { button->setText(i18n("All Applications")+suffix); } QVariant data = QVariant::fromValue(QPersistentModelIndex(index)); button->setProperty("applicationIndex", data); connect(button, SIGNAL(clicked()), this, SLOT(breadcrumbActivated())); QHBoxLayout *layout = static_cast(d->applicationBreadcrumbs->layout()); layout->insertWidget(1, button); } void Launcher::breadcrumbActivated() { QPushButton *button = static_cast(sender()); QModelIndex index = button->property("applicationIndex").value(); d->applicationView->setCurrentRoot(index); } #include "launcher.moc"