*this* is how we give it time to finish loading without keeping it around forever. :)
[kdebase.git] / workspace / systemsettings / mainwindow.cpp
blob6fde06db32adc4956b1831feb608b048fe4a4563
1 /**
2 * This file is part of the System Settings package
3 * Copyright (C) 2005 Benjamin C Meyer (ben+systempreferences at meyerhome dot net)
4 * (C) 2007 Will Stephenson <wstephenson@kde.org>
5 * (C) 2007 Michael Jansen <kde@michael-jansen.biz>
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Library General Public License for more details.
17 * You should have received a copy of the GNU Library General Public License
18 * along with this library; see the file COPYING.LIB. If not, write to
19 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 * Boston, MA 02110-1301, USA.
23 #include "mainwindow.h"
25 #include <QApplication>
26 #include <QLabel>
28 #include <QLayout>
29 #include <QStackedWidget>
31 #include <KAction>
32 #include <KActionCollection>
33 #include <KCModuleInfo>
34 #include <KCModuleProxy>
35 #include <KDebug>
36 #include <KDialog> // for spacing
37 #include <KGlobalSettings>
38 #include <KLineEdit>
39 #include <KLocale>
40 #include <KMenuBar>
41 #include <KServiceTypeTrader>
42 #include <KStandardAction>
43 #include <KTabWidget>
44 #include <kcategorizedsortfilterproxymodel.h>
45 #include <kcategorizedview.h>
46 #include <kcategorydrawer.h>
47 #include <kiconloader.h>
48 #include <kfileitemdelegate.h>
50 #include "kcmodulemodel.h"
51 #include "kcmultiwidget.h"
52 #include "menuitem.h"
53 #include "moduleiconitem.h"
55 Q_DECLARE_METATYPE(MenuItem *)
57 MainWindow::MainWindow(QWidget *parent) :
58 KXmlGuiWindow(parent),
59 categories( KServiceTypeTrader::self()->query("SystemSettingsCategory") ),
60 modules( KServiceTypeTrader::self()->query("KCModule") ),
61 rootItem(new MenuItem( true, 0 )),
62 groupWidget(0)
65 // Load the menu structure in from disk.
66 readMenu( rootItem );
67 qStableSort( rootItem->children.begin(), rootItem->children.end(), pageLessThan ); // sort tabs by weight
68 moduleTabs = new KTabWidget(this);
69 buildActions();
70 buildMainWidget();
71 // We hide the menubar. So ensure the toolbar is always visible because you cannot get it back
72 setupGUI(Save|Create,QString());
73 menuBar()->hide();
75 connect(moduleTabs, SIGNAL(currentChanged(int)), SLOT(widgetChange()));
78 MainWindow::~MainWindow()
80 delete moduleTabs;
81 delete rootItem;
84 QSize MainWindow::sizeHint() const
86 return QSize(780, 580);
89 void MainWindow::readMenu( MenuItem * parent )
91 // look for any categories inside this level, and recurse into them
92 int depth = 0;
93 MenuItem * current = parent;
94 while ( current && current->parent ) {
95 depth++;
96 current = current->parent;
99 QString space;
100 space.fill( ' ', depth * 2 );
101 kDebug() << space << "Looking for children in '" << parent->name << "'";
102 for (int i = 0; i < categories.size(); ++i) {
103 KService::Ptr entry = categories.at(i);
104 QString parentCategory = entry->property("X-KDE-System-Settings-Parent-Category").toString();
105 QString category = entry->property("X-KDE-System-Settings-Category").toString();
106 //kDebug() << "Examining category " << parentCategory << "/" << category;
107 if ( parentCategory == parent->name ) {
108 KCModuleInfo module( entry->entryPath() );
110 MenuItem * menuItem = new MenuItem(true, parent);
111 menuItem->name = category;
112 menuItem->service = entry;
113 menuItem->item = module;
114 readMenu( menuItem );
118 // scan for any modules at this level and add them
119 for (int i = 0; i < modules.size(); ++i) {
120 KService::Ptr entry = modules.at(i);
121 QString category = entry->property("X-KDE-System-Settings-Parent-Category").toString();
122 //kDebug() << "Examining module " << category;
123 if(!parent->name.isEmpty() && category == parent->name ) {
124 kDebug() << space << "found module '" << entry->name() << "' " << entry->entryPath();
125 // Add the module info to the menu
126 KCModuleInfo module(entry->entryPath());
127 kDebug() << space << "filename is " << module.fileName();
128 //append(module);
129 MenuItem * infoItem = new MenuItem(false, parent);
130 infoItem->name = category;
131 infoItem->service = entry;
132 infoItem->item = module;
136 parent->sortChildrenByWeight();
140 // \virtual
141 bool MainWindow::queryClose()
143 if ( groupWidget ) {
144 return groupWidget->queryClose();
146 return true;
150 void MainWindow::buildMainWidget()
152 windowStack = new QStackedWidget(this);
154 // Top level pages.
156 moduleTabs->show();
158 foreach ( MenuItem* item, rootItem->children ) {
159 model = new KCModuleModel( item, this );
160 KCategoryDrawer * drawer = new KCategoryDrawer;
161 KCategorizedView * tv = new KCategorizedView( this );
162 tv->setSelectionMode(QAbstractItemView::SingleSelection);
163 tv->setSpacing(KDialog::spacingHint());
164 tv->setCategoryDrawer( drawer );
165 tv->setViewMode( QListView::IconMode );
166 tv->setMouseTracking( true );
167 tv->viewport()->setAttribute( Qt::WA_Hover );
168 tv->setItemDelegate( new KFileItemDelegate( tv ) );
169 KCategorizedSortFilterProxyModel * kcsfpm = new SystemSettingsProxyModel( this );
170 kcsfpm->setCategorizedModel( true );
171 kcsfpm->setSourceModel( model );
172 kcsfpm->setFilterRole( KCModuleModel::UserFilterRole );
173 kcsfpm->setFilterCaseSensitivity( Qt::CaseInsensitive );
174 kcsfpm->sort( 0 );
175 tv->setModel( kcsfpm );
176 connect( tv,
177 SIGNAL(activated(const QModelIndex&)),
178 SLOT(selectionChanged(const QModelIndex&)) );
179 if (KGlobalSettings::singleClick()) {
180 connect( tv, SIGNAL(clicked(const QModelIndex&)),
181 SLOT(selectionChanged(const QModelIndex&)));
182 } else {
183 connect( tv, SIGNAL(doubleClicked(const QModelIndex&)),
184 SLOT(selectionChanged(const QModelIndex&)));
186 connect( search, SIGNAL(textChanged(const QString&)),
187 kcsfpm, SLOT(setFilterRegExp(const QString&)));
188 connect( kcsfpm, SIGNAL(layoutChanged()),
189 this, SLOT(updateSearchHits()) );
190 moduleTabs->addTab(tv, item->service->name() );
194 // record the index of the newly added tab so that we can later update the label showing
195 // number of search hits
196 modelToTabHash.insert( kcsfpm, moduleTabs->count() - 1 );
198 windowStack->addWidget(moduleTabs);
199 windowStack->setCurrentWidget(moduleTabs);
200 setCentralWidget(windowStack);
203 void MainWindow::buildActions()
205 actionCollection()->addAction(
206 KStandardAction::Quit,
207 qobject_cast<QObject*>(this),
208 SLOT(close()));
210 showOverviewAction = actionCollection()->addAction(
211 KStandardAction::Back,
212 qobject_cast<QObject*>(this),
213 SLOT(showOverview()));
214 showOverviewAction->setText(
215 i18n("Overview (%1)", showOverviewAction->shortcut().primary().toString()));
216 showOverviewAction->setEnabled(false);
218 QWidget *searchWid = new QWidget( this );
219 QLabel * searchIcon = new QLabel( searchWid );
220 searchIcon->setPixmap( BarIcon( "system-search" ) );
221 QLabel *searchLabel = new QLabel( searchWid );
222 searchLabel->setObjectName( QLatin1String("SearchLabel"));
223 searchLabel->setText( i18n("S&earch:") );
224 searchLabel->setFont(KGlobalSettings::toolBarFont());
225 searchLabel->setMargin(2);
226 QHBoxLayout * hlay = new QHBoxLayout( searchWid );
227 hlay->addWidget( searchIcon );
228 hlay->addWidget( searchLabel );
229 searchWid->setLayout( hlay );
231 searchText = new KAction( this );
232 searchText->setDefaultWidget(searchWid);
234 actionCollection()->addAction( "searchText", searchText );
235 searchText->setShortcut(Qt::Key_F6);
237 // Search edit box and result labels
238 QWidget *hbox = new QWidget( this );
240 search = new KLineEdit( hbox );
241 search->setObjectName(QLatin1String("search"));
242 search->setClearButtonShown( true );
243 search->setFocusPolicy( Qt::StrongFocus );
244 searchLabel->setBuddy( search );
245 // Is that needed? I thought that's what a buddy is for?
246 connect(searchText, SIGNAL(triggered()), search, SLOT(setFocus()));
248 QWidget* vbox = new QWidget(hbox);
249 // Set a non empty content to prevent the toolbar from getting taller when
250 // starting a search (at least with Oxygen style).
251 generalHitLabel = new QLabel(" ", vbox);
252 advancedHitLabel = new QLabel(" ", vbox);
254 QVBoxLayout* vlayout = new QVBoxLayout;
255 vlayout->setMargin(0);
256 vlayout->setSpacing(0);
257 vlayout->addWidget(generalHitLabel);
258 vlayout->addWidget(advancedHitLabel);
259 vlayout->setStretchFactor(generalHitLabel,1);
260 vlayout->setStretchFactor(advancedHitLabel,1);
261 vbox->setLayout(vlayout);
263 QHBoxLayout* hlayout = new QHBoxLayout;
264 hlayout->setMargin(0);
265 hlayout->addWidget(search);
266 hlayout->addWidget(vbox);
267 hlayout->setStretchFactor(search,1);
268 hlayout->setStretchFactor(vbox,1);
269 hbox->setLayout(hlayout);
271 searchAction = new KAction( "none", this );
272 searchAction->setDefaultWidget(hbox);
273 actionCollection()->addAction( "search", searchAction );
274 searchAction->setShortcutConfigurable( false );
275 hbox->setWhatsThis( i18n("Search Bar<p>Enter a search term.</p>") );
280 void MainWindow::showOverview()
282 if (!groupWidget->queryClose()) {
283 return;
285 windowStack->setCurrentWidget(moduleTabs);
287 // Reset the widget for normal all widget viewing
288 groupWidget = 0;
289 widgetChange();
291 showOverviewAction->setEnabled(false);
293 searchText->setEnabled(true);
294 search->setEnabled(true);
295 searchAction->setEnabled(true);
299 void MainWindow::selectionChanged( const QModelIndex& selected )
301 if ( !selected.isValid() )
302 return;
304 MenuItem * mItem = selected.data( Qt::UserRole ).value<MenuItem*>();
305 if ( mItem ) {
306 kDebug() << "Selected item: " << mItem->service->name();
307 kDebug() << "Comment: " << mItem->service->comment();
308 } else {
309 kDebug() << ":'( Got dud pointer from " << selected.data( Qt::DisplayRole ).toString();
310 Q_ASSERT(mItem); // Would core dump below. Do it now
311 return; // For production
313 // Because some KCMultiWidgets take an age to load, it is possible
314 // for the user to click another ModuleIconItem while loading.
315 // This causes execution to return here while the first groupWidget is shown
316 if ( groupWidget )
317 return;
319 groupWidget = moduleItemToWidgetDict[mItem->service];
321 if( !groupWidget ) {
322 groupWidget = new KCMultiWidget(windowStack, Qt::NonModal); // THAT ZERO IS NEW (actually the 0 can go, jr)
323 windowStack->addWidget(groupWidget);
324 moduleItemToWidgetDict.insert(mItem->service,groupWidget);
326 // That shouldn't be needed.
327 connect(groupWidget, SIGNAL(finished()), this, SLOT(showOverview()));
329 if ( ! mItem->menu ) {
330 groupWidget->addModule( mItem->item );
331 } else {
332 foreach ( MenuItem * i, mItem->children ) {
333 kDebug() << "adding " , i->item.fileName();
334 groupWidget->addModule( i->item );
339 // calling this with a shown KCMultiWidget sets groupWidget to 0
340 // which makes the show() call below segfault. The groupWidget test
341 // above should prevent execution reaching here while the KCMultiWidget is
342 // visible
343 windowStack->setCurrentWidget( groupWidget );
345 setCaption( mItem->service->name() );
346 showOverviewAction->setEnabled(true);
347 searchText->setEnabled(false);
348 search->setEnabled(false);
349 searchAction->setEnabled(false);
354 * This is called when we go back to the overview page. It resets the name and
355 * clears the selection
357 void MainWindow::widgetChange() {
358 if( !groupWidget ) {
359 setCaption(QString());
360 KCategorizedView * currentView = qobject_cast<KCategorizedView *>( moduleTabs->currentWidget() );
361 currentView->selectionModel()->clear();
366 /* :BUG: This is a nice idea. It just doesn't work. It looks like that
367 * layoutChanged() signal connected to this slot doesn't work the way it is
368 * expected here. It signals that the order of the items changed. I can't find
369 * a signal that informs us that set sortfilterproxy has done it's work.
371 void MainWindow::updateSearchHits()
373 // if the search lineedit is empty, clear the search labels
374 if ( search->text().isEmpty() ) {
375 generalHitLabel->setText(QString());
376 advancedHitLabel->setText(QString());
377 } else { // otherwise update the tab for the sender()
378 for ( int i = 0; i < moduleTabs->count(); i++ ) {
379 const KCategorizedSortFilterProxyModel * kcsfpm = static_cast<KCategorizedSortFilterProxyModel*>( sender() );
380 if (kcsfpm && modelToTabHash.contains( kcsfpm ) ) {
381 switch ( modelToTabHash[ kcsfpm ] ) {
382 case 0:
383 generalHitLabel->setText(i18np("%1 hit in General","%1 hits in General", kcsfpm->rowCount()));
384 break;
385 case 1:
386 advancedHitLabel->setText(i18np("%1 hit in Advanced","%1 hits in Advanced",kcsfpm->rowCount()));
387 break;
388 default:
389 kDebug() << "Hits found in top level system settings other than General, Advanced, and the UI is hardcoded to only indicate hits in these tabs";
396 bool pageLessThan( MenuItem *page1, MenuItem *page2 )
398 return page1->item.weight() < page2->item.weight();
401 #include "mainwindow.moc"