add more spacing
[personal-kdebase.git] / workspace / systemsettings / mainwindow.cpp
blob1b247ca9fc04be89a0a2839b3994dce241746087
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 search->setFocus();
77 connect(moduleTabs, SIGNAL(currentChanged(int)), SLOT(widgetChange()));
80 MainWindow::~MainWindow()
82 delete moduleTabs;
83 delete rootItem;
86 QSize MainWindow::sizeHint() const
88 return QSize(780, 580);
91 void MainWindow::readMenu( MenuItem * parent )
93 // look for any categories inside this level, and recurse into them
94 int depth = 0;
95 MenuItem * current = parent;
96 while ( current && current->parent ) {
97 depth++;
98 current = current->parent;
101 QString space;
102 space.fill( ' ', depth * 2 );
103 kDebug() << space << "Looking for children in '" << parent->name << "'";
104 for (int i = 0; i < categories.size(); ++i) {
105 KService::Ptr entry = categories.at(i);
106 QString parentCategory = entry->property("X-KDE-System-Settings-Parent-Category").toString();
107 QString category = entry->property("X-KDE-System-Settings-Category").toString();
108 //kDebug() << "Examining category " << parentCategory << "/" << category;
109 if ( parentCategory == parent->name ) {
110 KCModuleInfo module( entry->entryPath() );
112 MenuItem * menuItem = new MenuItem(true, parent);
113 menuItem->name = category;
114 menuItem->service = entry;
115 menuItem->item = module;
116 readMenu( menuItem );
120 // scan for any modules at this level and add them
121 for (int i = 0; i < modules.size(); ++i) {
122 KService::Ptr entry = modules.at(i);
123 QString category = entry->property("X-KDE-System-Settings-Parent-Category").toString();
124 //kDebug() << "Examining module " << category;
125 if(!parent->name.isEmpty() && category == parent->name ) {
126 kDebug() << space << "found module '" << entry->name() << "' " << entry->entryPath();
127 // Add the module info to the menu
128 KCModuleInfo module(entry->entryPath());
129 kDebug() << space << "filename is " << module.fileName();
130 //append(module);
131 MenuItem * infoItem = new MenuItem(false, parent);
132 infoItem->name = category;
133 infoItem->service = entry;
134 infoItem->item = module;
138 parent->sortChildrenByWeight();
142 // \virtual
143 bool MainWindow::queryClose()
145 if ( groupWidget ) {
146 return groupWidget->queryClose();
148 return true;
152 void MainWindow::buildMainWidget()
154 windowStack = new QStackedWidget(this);
156 // Top level pages.
158 moduleTabs->show();
160 foreach ( MenuItem* item, rootItem->children ) {
161 model = new KCModuleModel( item, this );
162 KCategoryDrawer * drawer = new KCategoryDrawer;
163 KCategorizedView * tv = new KCategorizedView( this );
164 tv->setSelectionMode(QAbstractItemView::SingleSelection);
165 tv->setSpacing(20);
166 tv->setCategoryDrawer( drawer );
167 tv->setViewMode( QListView::IconMode );
168 tv->setMouseTracking( true );
169 tv->viewport()->setAttribute( Qt::WA_Hover );
170 tv->setItemDelegate( new KFileItemDelegate( tv ) );
171 KCategorizedSortFilterProxyModel * kcsfpm = new SystemSettingsProxyModel( this );
172 kcsfpm->setCategorizedModel( true );
173 kcsfpm->setSourceModel( model );
174 kcsfpm->setFilterRole( KCModuleModel::UserFilterRole );
175 kcsfpm->setFilterCaseSensitivity( Qt::CaseInsensitive );
176 kcsfpm->sort( 0 );
177 tv->setModel( kcsfpm );
178 connect( tv,
179 SIGNAL(activated(const QModelIndex&)),
180 SLOT(selectionChanged(const QModelIndex&)) );
181 if (KGlobalSettings::singleClick()) {
182 connect( tv, SIGNAL(clicked(const QModelIndex&)),
183 SLOT(selectionChanged(const QModelIndex&)));
184 } else {
185 connect( tv, SIGNAL(doubleClicked(const QModelIndex&)),
186 SLOT(selectionChanged(const QModelIndex&)));
188 connect( search, SIGNAL(textChanged(const QString&)),
189 kcsfpm, SLOT(setFilterRegExp(const QString&)));
190 connect( kcsfpm, SIGNAL(layoutChanged()),
191 this, SLOT(updateSearchHits()) );
192 moduleTabs->addTab(tv, item->service->name() );
196 // record the index of the newly added tab so that we can later update the label showing
197 // number of search hits
198 modelToTabHash.insert( kcsfpm, moduleTabs->count() - 1 );
200 windowStack->addWidget(moduleTabs);
201 windowStack->setCurrentWidget(moduleTabs);
202 setCentralWidget(windowStack);
205 void MainWindow::buildActions()
207 actionCollection()->addAction(
208 KStandardAction::Quit,
209 qobject_cast<QObject*>(this),
210 SLOT(close()));
212 showOverviewAction = actionCollection()->addAction(
213 KStandardAction::Back,
214 qobject_cast<QObject*>(this),
215 SLOT(showOverview()));
216 showOverviewAction->setText(
217 i18n("Overview (%1)", showOverviewAction->shortcut().primary().toString(QKeySequence::NativeText)));
218 showOverviewAction->setEnabled(false);
220 QWidget *searchWid = new QWidget( this );
221 QLabel * searchIcon = new QLabel( searchWid );
222 searchIcon->setPixmap( BarIcon( "system-search" ) );
223 QLabel *searchLabel = new QLabel( searchWid );
224 searchLabel->setObjectName( QLatin1String("SearchLabel"));
225 searchLabel->setText( i18n("S&earch:") );
226 searchLabel->setFont(KGlobalSettings::toolBarFont());
227 searchLabel->setMargin(2);
228 QHBoxLayout * hlay = new QHBoxLayout( searchWid );
229 hlay->addWidget( searchIcon );
230 hlay->addWidget( searchLabel );
231 searchWid->setLayout( hlay );
233 searchText = new KAction( this );
234 searchText->setDefaultWidget(searchWid);
236 actionCollection()->addAction( "searchText", searchText );
237 searchText->setShortcut(Qt::Key_F6);
239 // Search edit box and result labels
240 QWidget *hbox = new QWidget( this );
242 search = new KLineEdit( hbox );
243 search->setObjectName(QLatin1String("search"));
244 search->setClearButtonShown( true );
245 search->setFocusPolicy( Qt::StrongFocus );
246 searchLabel->setBuddy( search );
247 // Is that needed? I thought that's what a buddy is for?
248 connect(searchText, SIGNAL(triggered()), search, SLOT(setFocus()));
250 QWidget* vbox = new QWidget(hbox);
251 // Set a non empty content to prevent the toolbar from getting taller when
252 // starting a search (at least with Oxygen style).
253 generalHitLabel = new QLabel(" ", vbox);
254 advancedHitLabel = new QLabel(" ", vbox);
256 QVBoxLayout* vlayout = new QVBoxLayout;
257 vlayout->setMargin(0);
258 vlayout->setSpacing(0);
259 vlayout->addWidget(generalHitLabel);
260 vlayout->addWidget(advancedHitLabel);
261 vlayout->setStretchFactor(generalHitLabel,1);
262 vlayout->setStretchFactor(advancedHitLabel,1);
263 vbox->setLayout(vlayout);
265 QHBoxLayout* hlayout = new QHBoxLayout;
266 hlayout->setMargin(0);
267 hlayout->addWidget(search);
268 hlayout->addWidget(vbox);
269 hlayout->setStretchFactor(search,1);
270 hlayout->setStretchFactor(vbox,1);
271 hbox->setLayout(hlayout);
273 searchAction = new KAction( "none", this );
274 searchAction->setDefaultWidget(hbox);
275 actionCollection()->addAction( "search", searchAction );
276 searchAction->setShortcutConfigurable( false );
277 hbox->setWhatsThis( i18n("Search Bar<p>Enter a search term.</p>") );
282 void MainWindow::showOverview()
284 if (!groupWidget->queryClose()) {
285 return;
287 windowStack->setCurrentWidget(moduleTabs);
289 // Reset the widget for normal all widget viewing
290 groupWidget = 0;
291 widgetChange();
293 showOverviewAction->setEnabled(false);
295 searchText->setEnabled(true);
296 search->setEnabled(true);
297 searchAction->setEnabled(true);
301 void MainWindow::selectionChanged( const QModelIndex& selected )
303 if ( !selected.isValid() )
304 return;
306 MenuItem * mItem = selected.data( Qt::UserRole ).value<MenuItem*>();
307 if ( mItem ) {
308 kDebug() << "Selected item: " << mItem->service->name();
309 kDebug() << "Comment: " << mItem->service->comment();
310 } else {
311 kDebug() << ":'( Got dud pointer from " << selected.data( Qt::DisplayRole ).toString();
312 Q_ASSERT(mItem); // Would core dump below. Do it now
313 return; // For production
315 // Because some KCMultiWidgets take an age to load, it is possible
316 // for the user to click another ModuleIconItem while loading.
317 // This causes execution to return here while the first groupWidget is shown
318 if ( groupWidget )
319 return;
321 groupWidget = moduleItemToWidgetDict[mItem->service];
323 if( !groupWidget ) {
324 groupWidget = new KCMultiWidget(windowStack, Qt::NonModal); // THAT ZERO IS NEW (actually the 0 can go, jr)
325 windowStack->addWidget(groupWidget);
326 moduleItemToWidgetDict.insert(mItem->service,groupWidget);
328 // That shouldn't be needed.
329 connect(groupWidget, SIGNAL(finished()), this, SLOT(showOverview()));
331 if ( ! mItem->menu ) {
332 groupWidget->addModule( mItem->item );
333 } else {
334 foreach ( MenuItem * i, mItem->children ) {
335 kDebug() << "adding " , i->item.fileName();
336 groupWidget->addModule( i->item );
341 // calling this with a shown KCMultiWidget sets groupWidget to 0
342 // which makes the show() call below segfault. The groupWidget test
343 // above should prevent execution reaching here while the KCMultiWidget is
344 // visible
345 windowStack->setCurrentWidget( groupWidget );
347 setCaption( mItem->service->name() );
348 showOverviewAction->setEnabled(true);
349 searchText->setEnabled(false);
350 search->setEnabled(false);
351 searchAction->setEnabled(false);
356 * This is called when we go back to the overview page. It resets the name and
357 * clears the selection
359 void MainWindow::widgetChange() {
360 if( !groupWidget ) {
361 setCaption(QString());
362 KCategorizedView * currentView = qobject_cast<KCategorizedView *>( moduleTabs->currentWidget() );
363 currentView->selectionModel()->clear();
368 /* :BUG: This is a nice idea. It just doesn't work. It looks like that
369 * layoutChanged() signal connected to this slot doesn't work the way it is
370 * expected here. It signals that the order of the items changed. I can't find
371 * a signal that informs us that set sortfilterproxy has done it's work.
373 void MainWindow::updateSearchHits()
375 // if the search lineedit is empty, clear the search labels
376 if ( search->text().isEmpty() ) {
377 generalHitLabel->setText(QString());
378 advancedHitLabel->setText(QString());
379 } else { // otherwise update the tab for the sender()
380 for ( int i = 0; i < moduleTabs->count(); i++ ) {
381 const KCategorizedSortFilterProxyModel * kcsfpm = static_cast<KCategorizedSortFilterProxyModel*>( sender() );
382 if (kcsfpm && modelToTabHash.contains( kcsfpm ) ) {
383 switch ( modelToTabHash[ kcsfpm ] ) {
384 case 0:
385 generalHitLabel->setText(i18np("%1 hit in General","%1 hits in General", kcsfpm->rowCount()));
386 break;
387 case 1:
388 advancedHitLabel->setText(i18np("%1 hit in Advanced","%1 hits in Advanced",kcsfpm->rowCount()));
389 break;
390 default:
391 kDebug() << "Hits found in top level system settings other than General, Advanced, and the UI is hardcoded to only indicate hits in these tabs";
398 bool pageLessThan( MenuItem *page1, MenuItem *page2 )
400 return page1->item.weight() < page2->item.weight();
403 #include "mainwindow.moc"