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>
29 #include <QStackedWidget>
32 #include <KActionCollection>
33 #include <KCModuleInfo>
34 #include <KCModuleProxy>
36 #include <KDialog> // for spacing
37 #include <KGlobalSettings>
41 #include <KServiceTypeTrader>
42 #include <KStandardAction>
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"
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 )),
65 // Load the menu structure in from disk.
67 qStableSort( rootItem
->children
.begin(), rootItem
->children
.end(), pageLessThan
); // sort tabs by weight
68 moduleTabs
= new KTabWidget(this);
71 // We hide the menubar. So ensure the toolbar is always visible because you cannot get it back
72 setupGUI(Save
|Create
,QString());
77 connect(moduleTabs
, SIGNAL(currentChanged(int)), SLOT(widgetChange()));
80 MainWindow::~MainWindow()
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
95 MenuItem
* current
= parent
;
96 while ( current
&& current
->parent
) {
98 current
= current
->parent
;
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();
131 MenuItem
* infoItem
= new MenuItem(false, parent
);
132 infoItem
->name
= category
;
133 infoItem
->service
= entry
;
134 infoItem
->item
= module
;
138 parent
->sortChildrenByWeight();
143 bool MainWindow::queryClose()
146 return groupWidget
->queryClose();
152 void MainWindow::buildMainWidget()
154 windowStack
= new QStackedWidget(this);
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(KDialog::spacingHint());
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
);
177 tv
->setModel( kcsfpm
);
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
&)));
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),
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()) {
287 windowStack
->setCurrentWidget(moduleTabs
);
289 // Reset the widget for normal all widget viewing
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() )
306 MenuItem
* mItem
= selected
.data( Qt::UserRole
).value
<MenuItem
*>();
308 kDebug() << "Selected item: " << mItem
->service
->name();
309 kDebug() << "Comment: " << mItem
->service
->comment();
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
321 groupWidget
= moduleItemToWidgetDict
[mItem
->service
];
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
);
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
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() {
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
] ) {
385 generalHitLabel
->setText(i18np("%1 hit in General","%1 hits in General", kcsfpm
->rowCount()));
388 advancedHitLabel
->setText(i18np("%1 hit in Advanced","%1 hits in Advanced",kcsfpm
->rowCount()));
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"