1 | /* This file is part of the KDE libraries |
2 | Copyright |
3 | (C) 2000 Reginald Stadlbauer (reggie@kde.org) |
4 | (C) 1997 Stephan Kulow (coolo@kde.org) |
5 | (C) 1997-2000 Sven Radej (radej@kde.org) |
6 | (C) 1997-2000 Matthias Ettrich (ettrich@kde.org) |
7 | (C) 1999 Chris Schlaeger (cs@kde.org) |
8 | (C) 2002 Joseph Wenninger (jowenn@kde.org) |
9 | (C) 2005-2006 Hamish Rodda (rodda@kde.org) |
10 | (C) 2000-2008 David Faure (faure@kde.org) |
11 | |
12 | This library is free software; you can redistribute it and/or |
13 | modify it under the terms of the GNU Library General Public |
14 | License version 2 as published by the Free Software Foundation. |
15 | |
16 | This library is distributed in the hope that it will be useful, |
17 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
18 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
19 | Library General Public License for more details. |
20 | |
21 | You should have received a copy of the GNU Library General Public License |
22 | along with this library; see the file COPYING.LIB. If not, write to |
23 | the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
24 | Boston, MA 02110-1301, USA. |
25 | */ |
26 | |
27 | #include "kmainwindow.h" |
28 | #include "kmainwindow_p.h" |
29 | #include "kmainwindowiface_p.h" |
30 | #include "ktoolbarhandler_p.h" |
31 | #include "kcmdlineargs.h" |
32 | #include "ktoggleaction.h" |
33 | #include "ksessionmanager.h" |
34 | #include "kstandardaction.h" |
35 | |
36 | #include <QtCore/QList> |
37 | #include <QtCore/QObject> |
38 | #include <QtCore/QTimer> |
39 | #include <QtGui/QCloseEvent> |
40 | #include <QtGui/QDesktopWidget> |
41 | #include <QtGui/QDockWidget> |
42 | #include <QtGui/QLayout> |
43 | #include <QtGui/QSessionManager> |
44 | #include <QtGui/QStyle> |
45 | #include <QtGui/QWidget> |
46 | |
47 | #include <kaction.h> |
48 | #include <kapplication.h> |
49 | #include <kauthorized.h> |
50 | #include <kconfig.h> |
51 | #include <kdebug.h> |
52 | #include <kdialog.h> |
53 | #include <khelpmenu.h> |
54 | #include <klocale.h> |
55 | #include <kmenubar.h> |
56 | #include <kstandarddirs.h> |
57 | #include <kstatusbar.h> |
58 | #include <ktoolbar.h> |
59 | #include <kwindowsystem.h> |
60 | #include <kconfiggroup.h> |
61 | #include <kglobalsettings.h> |
62 | |
63 | #if defined Q_WS_X11 |
64 | #include <qx11info_x11.h> |
65 | #include <netwm.h> |
66 | #endif |
67 | |
68 | #include <stdlib.h> |
69 | #include <ctype.h> |
70 | #include <assert.h> |
71 | |
72 | #include <config.h> |
73 | |
74 | static bool no_query_exit = false; |
75 | |
76 | static KMenuBar *internalMenuBar(KMainWindow *mw) |
77 | { |
78 | return KGlobal::findDirectChild<KMenuBar *>(mw); |
79 | } |
80 | |
81 | static KStatusBar *internalStatusBar(KMainWindow *mw) |
82 | { |
83 | // Don't use qFindChild here, it's recursive! |
84 | // (== slow, but also finds konqueror's per-view statusbars) |
85 | return KGlobal::findDirectChild<KStatusBar *>(mw); |
86 | } |
87 | |
88 | /** |
89 | * Listens to resize events from QDockWidgets. The KMainWindow |
90 | * settings are set as dirty, as soon as at least one resize |
91 | * event occurred. The listener is attached to the dock widgets |
92 | * by dock->installEventFilter(dockResizeListener) inside |
93 | * KMainWindow::event(). |
94 | */ |
95 | class DockResizeListener : public QObject |
96 | { |
97 | public: |
98 | DockResizeListener(KMainWindow *win); |
99 | virtual ~DockResizeListener(); |
100 | virtual bool eventFilter(QObject *watched, QEvent *event); |
101 | |
102 | private: |
103 | KMainWindow *m_win; |
104 | }; |
105 | |
106 | DockResizeListener::DockResizeListener(KMainWindow *win) : |
107 | QObject(win), |
108 | m_win(win) |
109 | { |
110 | } |
111 | |
112 | DockResizeListener::~DockResizeListener() |
113 | { |
114 | } |
115 | |
116 | bool DockResizeListener::eventFilter(QObject *watched, QEvent *event) |
117 | { |
118 | switch( event->type() ) { |
119 | case QEvent::Resize: |
120 | case QEvent::Move: |
121 | case QEvent::Hide: |
122 | m_win->k_ptr->setSettingsDirty(KMainWindowPrivate::CompressCalls); |
123 | break; |
124 | |
125 | default: |
126 | break; |
127 | } |
128 | |
129 | return QObject::eventFilter(watched, event); |
130 | } |
131 | |
132 | class KMWSessionManager : public KSessionManager |
133 | { |
134 | public: |
135 | KMWSessionManager() |
136 | { |
137 | } |
138 | ~KMWSessionManager() |
139 | { |
140 | } |
141 | bool dummyInit() { return true; } |
142 | bool saveState( QSessionManager& ) |
143 | { |
144 | KConfig* config = KApplication::kApplication()->sessionConfig(); |
145 | if ( KMainWindow::memberList().count() ){ |
146 | // According to Jochen Wilhelmy <digisnap@cs.tu-berlin.de>, this |
147 | // hook is useful for better document orientation |
148 | KMainWindow::memberList().first()->saveGlobalProperties(config); |
149 | } |
150 | |
151 | int n = 0; |
152 | foreach (KMainWindow* mw, KMainWindow::memberList()) { |
153 | n++; |
154 | mw->savePropertiesInternal(config, n); |
155 | } |
156 | |
157 | KConfigGroup group( config, "Number" ); |
158 | group.writeEntry("NumberOfWindows" , n ); |
159 | return true; |
160 | } |
161 | |
162 | bool commitData( QSessionManager& sm ) |
163 | { |
164 | // not really a fast method but the only compatible one |
165 | if ( sm.allowsInteraction() ) { |
166 | bool canceled = false; |
167 | ::no_query_exit = true; |
168 | |
169 | foreach (KMainWindow *window, KMainWindow::memberList()) { |
170 | if ( !window->testAttribute( Qt::WA_WState_Hidden ) ) { |
171 | QCloseEvent e; |
172 | QApplication::sendEvent( window, &e ); |
173 | canceled = !e.isAccepted(); |
174 | if (canceled) |
175 | break; |
176 | /* Don't even think_about deleting widgets with |
177 | Qt::WDestructiveClose flag set at this point. We |
178 | are faking a close event, but we are *not*_ |
179 | closing the window. The purpose of the faked |
180 | close event is to prepare the application so it |
181 | can safely be quit without the user losing data |
182 | (possibly showing a message box "do you want to |
183 | save this or that?"). It is possible that the |
184 | session manager quits the application later |
185 | (emitting QApplication::aboutToQuit() when this |
186 | happens), but it is also possible that the user |
187 | cancels the shutdown, so the application will |
188 | continue to run. |
189 | */ |
190 | } |
191 | } |
192 | ::no_query_exit = false; |
193 | if (canceled) |
194 | return false; |
195 | |
196 | KMainWindow* last = 0; |
197 | foreach (KMainWindow *window, KMainWindow::memberList()) { |
198 | if ( !window->testAttribute( Qt::WA_WState_Hidden ) ) { |
199 | last = window; |
200 | } |
201 | } |
202 | if ( last ) |
203 | return last->queryExit(); |
204 | // else |
205 | return true; |
206 | } |
207 | |
208 | // the user wants it, the user gets it |
209 | return true; |
210 | } |
211 | }; |
212 | |
213 | K_GLOBAL_STATIC(KMWSessionManager, ksm) |
214 | K_GLOBAL_STATIC(QList<KMainWindow*>, sMemberList) |
215 | static bool being_first = true; |
216 | |
217 | KMainWindow::KMainWindow( QWidget* parent, Qt::WindowFlags f ) |
218 | : QMainWindow(parent, f), k_ptr(new KMainWindowPrivate) |
219 | { |
220 | k_ptr->init(this); |
221 | } |
222 | |
223 | KMainWindow::KMainWindow(KMainWindowPrivate &dd, QWidget *parent, Qt::WindowFlags f) |
224 | : QMainWindow(parent, f), k_ptr(&dd) |
225 | { |
226 | k_ptr->init(this); |
227 | } |
228 | |
229 | void KMainWindowPrivate::init(KMainWindow *_q) |
230 | { |
231 | KGlobal::ref(); |
232 | |
233 | // We set allow quit to true when the first mainwindow is created, so that when the refcounting |
234 | // reaches 0 the application can quit. We don't want this to happen before the first mainwindow |
235 | // is created, otherwise running a job in main would exit the app too early. |
236 | KGlobal::setAllowQuit(true); |
237 | |
238 | q = _q; |
239 | |
240 | q->setAnimated(KGlobalSettings::graphicEffectsLevel() & KGlobalSettings::SimpleAnimationEffects); |
241 | |
242 | q->setAttribute( Qt::WA_DeleteOnClose ); |
243 | |
244 | // We handle this functionality (quitting the app) ourselves, with KGlobal::ref/deref. |
245 | // This makes apps stay alive even if they only have a systray icon visible, or |
246 | // a progress widget with "keep open" checked, for instance. |
247 | // So don't let the default Qt mechanism allow any toplevel widget to just quit the app on us. |
248 | // Setting WA_QuitOnClose to false for all KMainWindows is not enough, any progress widget |
249 | // or dialog box would still quit the app... |
250 | if (qApp) |
251 | qApp->setQuitOnLastWindowClosed(false); |
252 | |
253 | helpMenu = 0; |
254 | |
255 | //actionCollection()->setWidget( this ); |
256 | QObject::connect(qApp, SIGNAL(aboutToQuit()), q, SLOT(_k_shuttingDown())); |
257 | QObject::connect(KGlobalSettings::self(), SIGNAL(settingsChanged(int)), |
258 | q, SLOT(_k_slotSettingsChanged(int))); |
259 | |
260 | // force KMWSessionManager creation - someone a better idea? |
261 | ksm->dummyInit(); |
262 | |
263 | sMemberList->append( q ); |
264 | |
265 | settingsDirty = false; |
266 | autoSaveSettings = false; |
267 | autoSaveWindowSize = true; // for compatibility |
268 | //d->kaccel = actionCollection()->kaccel(); |
269 | settingsTimer = 0; |
270 | sizeTimer = 0; |
271 | shuttingDown = false; |
272 | if ((care_about_geometry = being_first)) { |
273 | being_first = false; |
274 | |
275 | QString geometry; |
276 | KCmdLineArgs *args = KCmdLineArgs::parsedArgs("kde" ); |
277 | if (args && args->isSet("geometry" )) |
278 | geometry = args->getOption("geometry" ); |
279 | |
280 | if ( geometry.isNull() ) // if there is no geometry, it doesn't matter |
281 | care_about_geometry = false; |
282 | else |
283 | q->parseGeometry(false); |
284 | } |
285 | |
286 | q->setWindowTitle( KGlobal::caption() ); |
287 | |
288 | dockResizeListener = new DockResizeListener(_q); |
289 | letDirtySettings = true; |
290 | |
291 | sizeApplied = false; |
292 | } |
293 | |
294 | static bool endsWithHashNumber( const QString& s ) |
295 | { |
296 | for( int i = s.length() - 1; |
297 | i > 0; |
298 | --i ) |
299 | { |
300 | if( s[ i ] == '#' && i != s.length() - 1 ) |
301 | return true; // ok |
302 | if( !s[ i ].isDigit()) |
303 | break; |
304 | } |
305 | return false; |
306 | } |
307 | |
308 | static inline bool isValidDBusObjectPathCharacter(const QChar &c) |
309 | { |
310 | register ushort u = c.unicode(); |
311 | return (u >= 'a' && u <= 'z') |
312 | || (u >= 'A' && u <= 'Z') |
313 | || (u >= '0' && u <= '9') |
314 | || (u == '_') || (u == '/'); |
315 | } |
316 | |
317 | void KMainWindowPrivate::polish(KMainWindow *q) |
318 | { |
319 | // Set a unique object name. Required by session management, window management, and for the dbus interface. |
320 | QString objname; |
321 | QString s; |
322 | int unusedNumber = 1; |
323 | const QString name = q->objectName(); |
324 | bool startNumberingImmediately = true; |
325 | bool tryReuse = false; |
326 | if ( name.isEmpty() ) |
327 | { // no name given |
328 | objname = "MainWindow#" ; |
329 | } |
330 | else if( name.endsWith( QLatin1Char( '#' ) ) ) |
331 | { // trailing # - always add a number - KWin uses this for better grouping |
332 | objname = name; |
333 | } |
334 | else if( endsWithHashNumber( name )) |
335 | { // trailing # with a number - like above, try to use the given number first |
336 | objname = name; |
337 | tryReuse = true; |
338 | startNumberingImmediately = false; |
339 | } |
340 | else |
341 | { |
342 | objname = name; |
343 | startNumberingImmediately = false; |
344 | } |
345 | |
346 | s = objname; |
347 | if ( startNumberingImmediately ) |
348 | s += '1'; |
349 | |
350 | for(;;) { |
351 | const QList<QWidget*> list = qApp->topLevelWidgets(); |
352 | bool found = false; |
353 | foreach ( QWidget* w, list ) { |
354 | if( w != q && w->objectName() == s ) |
355 | { |
356 | found = true; |
357 | break; |
358 | } |
359 | } |
360 | if( !found ) |
361 | break; |
362 | if( tryReuse ) { |
363 | objname = name.left( name.length() - 1 ); // lose the hash |
364 | unusedNumber = 0; // start from 1 below |
365 | tryReuse = false; |
366 | } |
367 | s.setNum( ++unusedNumber ); |
368 | s = objname + s; |
369 | } |
370 | q->setObjectName( s ); |
371 | q->winId(); // workaround for setWindowRole() crashing, and set also window role, just in case TT |
372 | q->setWindowRole( s ); // will keep insisting that object name suddenly should not be used for window role |
373 | |
374 | dbusName = '/' + qApp->applicationName() + '/' + q->objectName().replace(QLatin1Char('/'), QLatin1Char('_')); |
375 | // Clean up for dbus usage: any non-alphanumeric char should be turned into '_' |
376 | const int len = dbusName.length(); |
377 | for ( int i = 0; i < len; ++i ) { |
378 | if ( !isValidDBusObjectPathCharacter( dbusName[i] ) ) |
379 | dbusName[i] = QLatin1Char('_'); |
380 | } |
381 | |
382 | QDBusConnection::sessionBus().registerObject(dbusName, q, QDBusConnection::ExportScriptableSlots | |
383 | QDBusConnection::ExportScriptableProperties | |
384 | QDBusConnection::ExportNonScriptableSlots | |
385 | QDBusConnection::ExportNonScriptableProperties | |
386 | QDBusConnection::ExportAdaptors); |
387 | } |
388 | |
389 | void KMainWindowPrivate::setSettingsDirty(CallCompression callCompression) |
390 | { |
391 | if (!letDirtySettings) { |
392 | return; |
393 | } |
394 | |
395 | settingsDirty = true; |
396 | if (autoSaveSettings) { |
397 | if (callCompression == CompressCalls) { |
398 | if (!settingsTimer) { |
399 | settingsTimer = new QTimer(q); |
400 | settingsTimer->setInterval(500); |
401 | settingsTimer->setSingleShot(true); |
402 | QObject::connect(settingsTimer, SIGNAL(timeout()), q, SLOT(saveAutoSaveSettings())); |
403 | } |
404 | settingsTimer->start(); |
405 | } else { |
406 | q->saveAutoSaveSettings(); |
407 | } |
408 | } |
409 | } |
410 | |
411 | void KMainWindowPrivate::setSizeDirty() |
412 | { |
413 | if (autoSaveWindowSize) { |
414 | if (!sizeTimer) { |
415 | sizeTimer = new QTimer(q); |
416 | sizeTimer->setInterval(500); |
417 | sizeTimer->setSingleShot(true); |
418 | QObject::connect(sizeTimer, SIGNAL(timeout()), q, SLOT(_k_slotSaveAutoSaveSize())); |
419 | } |
420 | sizeTimer->start(); |
421 | } |
422 | } |
423 | |
424 | void KMainWindow::parseGeometry(bool parsewidth) |
425 | { |
426 | K_D(KMainWindow); |
427 | QString cmdlineGeometry; |
428 | KCmdLineArgs *args = KCmdLineArgs::parsedArgs("kde" ); |
429 | if (args->isSet("geometry" )) |
430 | cmdlineGeometry = args->getOption("geometry" ); |
431 | |
432 | assert ( !cmdlineGeometry.isNull() ); |
433 | assert ( d->care_about_geometry ); |
434 | Q_UNUSED(d); // fix compiler warning in release mode |
435 | |
436 | #if defined Q_WS_X11 |
437 | int x, y; |
438 | int w, h; |
439 | int m = XParseGeometry( cmdlineGeometry.toLatin1(), &x, &y, (unsigned int*)&w, (unsigned int*)&h); |
440 | if (parsewidth) { |
441 | const QSize minSize = minimumSize(); |
442 | const QSize maxSize = maximumSize(); |
443 | if ( !(m & WidthValue) ) |
444 | w = width(); |
445 | if ( !(m & HeightValue) ) |
446 | h = height(); |
447 | w = qMin(w,maxSize.width()); |
448 | h = qMin(h,maxSize.height()); |
449 | w = qMax(w,minSize.width()); |
450 | h = qMax(h,minSize.height()); |
451 | resize(w, h); |
452 | } else { |
453 | if ( (m & XNegative) ) |
454 | x = KApplication::desktop()->width() + x - w; |
455 | else if ( !(m & XValue) ) |
456 | x = geometry().x(); |
457 | if ( (m & YNegative) ) |
458 | y = KApplication::desktop()->height() + y - h; |
459 | else if ( !(m & YValue) ) |
460 | y = geometry().y(); |
461 | |
462 | move(x, y); |
463 | } |
464 | #endif |
465 | } |
466 | |
467 | KMainWindow::~KMainWindow() |
468 | { |
469 | sMemberList->removeAll( this ); |
470 | delete static_cast<QObject *>(k_ptr->dockResizeListener); //so we don't get anymore events after k_ptr is destroyed |
471 | delete k_ptr; |
472 | KGlobal::deref(); |
473 | } |
474 | |
475 | KMenu* KMainWindow::helpMenu( const QString &aboutAppText, bool showWhatsThis ) |
476 | { |
477 | K_D(KMainWindow); |
478 | if(!d->helpMenu) { |
479 | if ( aboutAppText.isEmpty() ) |
480 | d->helpMenu = new KHelpMenu( this, KGlobal::mainComponent().aboutData(), showWhatsThis); |
481 | else |
482 | d->helpMenu = new KHelpMenu( this, aboutAppText, showWhatsThis ); |
483 | |
484 | if (!d->helpMenu) |
485 | return 0; |
486 | } |
487 | |
488 | return d->helpMenu->menu(); |
489 | } |
490 | |
491 | KMenu* KMainWindow::customHelpMenu( bool showWhatsThis ) |
492 | { |
493 | K_D(KMainWindow); |
494 | if (!d->helpMenu) { |
495 | d->helpMenu = new KHelpMenu( this, QString(), showWhatsThis ); |
496 | connect(d->helpMenu, SIGNAL(showAboutApplication()), |
497 | this, SLOT(showAboutApplication())); |
498 | } |
499 | |
500 | return d->helpMenu->menu(); |
501 | } |
502 | |
503 | bool KMainWindow::canBeRestored( int number ) |
504 | { |
505 | if ( !qApp->isSessionRestored() ) |
506 | return false; |
507 | KConfig *config = kapp->sessionConfig(); |
508 | if ( !config ) |
509 | return false; |
510 | |
511 | KConfigGroup group( config, "Number" ); |
512 | const int n = group.readEntry( "NumberOfWindows" , 1 ); |
513 | return number >= 1 && number <= n; |
514 | } |
515 | |
516 | const QString KMainWindow::classNameOfToplevel( int number ) |
517 | { |
518 | if ( !qApp->isSessionRestored() ) |
519 | return QString(); |
520 | KConfig *config = kapp->sessionConfig(); |
521 | if ( !config ) |
522 | return QString(); |
523 | QString s; |
524 | s.setNum( number ); |
525 | s.prepend( QLatin1String("WindowProperties" ) ); |
526 | |
527 | KConfigGroup group( config, s ); |
528 | if ( !group.hasKey( "ClassName" ) ) |
529 | return QString(); |
530 | else |
531 | return group.readEntry( "ClassName" ); |
532 | } |
533 | |
534 | bool KMainWindow::restore( int number, bool show ) |
535 | { |
536 | if ( !canBeRestored( number ) ) |
537 | return false; |
538 | KConfig *config = kapp->sessionConfig(); |
539 | if ( readPropertiesInternal( config, number ) ){ |
540 | if ( show ) |
541 | KMainWindow::show(); |
542 | return false; |
543 | } |
544 | return false; |
545 | } |
546 | |
547 | void KMainWindow::setCaption( const QString &caption ) |
548 | { |
549 | setPlainCaption( KDialog::makeStandardCaption( caption, this ) ); |
550 | } |
551 | |
552 | void KMainWindow::setCaption( const QString &caption, bool modified ) |
553 | { |
554 | KDialog::CaptionFlags flags = KDialog::HIGCompliantCaption; |
555 | |
556 | if ( modified ) |
557 | { |
558 | flags |= KDialog::ModifiedCaption; |
559 | } |
560 | |
561 | setPlainCaption( KDialog::makeStandardCaption(caption, this, flags) ); |
562 | } |
563 | |
564 | void KMainWindow::setPlainCaption( const QString &caption ) |
565 | { |
566 | setWindowTitle(caption); |
567 | } |
568 | |
569 | void KMainWindow::appHelpActivated( void ) |
570 | { |
571 | K_D(KMainWindow); |
572 | if( !d->helpMenu ) { |
573 | d->helpMenu = new KHelpMenu( this ); |
574 | if ( !d->helpMenu ) |
575 | return; |
576 | } |
577 | d->helpMenu->appHelpActivated(); |
578 | } |
579 | |
580 | void KMainWindow::closeEvent ( QCloseEvent *e ) |
581 | { |
582 | K_D(KMainWindow); |
583 | |
584 | // Save settings if auto-save is enabled, and settings have changed |
585 | if (d->settingsTimer && d->settingsTimer->isActive()) { |
586 | d->settingsTimer->stop(); |
587 | saveAutoSaveSettings(); |
588 | } |
589 | if (d->sizeTimer && d->sizeTimer->isActive()) { |
590 | d->sizeTimer->stop(); |
591 | d->_k_slotSaveAutoSaveSize(); |
592 | } |
593 | |
594 | if (queryClose()) { |
595 | e->accept(); |
596 | |
597 | int not_withdrawn = 0; |
598 | foreach (KMainWindow* mw, KMainWindow::memberList()) { |
599 | if ( !mw->isHidden() && mw->isTopLevel() && mw != this ) |
600 | not_withdrawn++; |
601 | } |
602 | |
603 | if ( !no_query_exit && not_withdrawn <= 0 ) { // last window close accepted? |
604 | if (!( queryExit() && ( !kapp || !kapp->sessionSaving() ) && !d->shuttingDown )) { |
605 | // cancel closing, it's stupid to end up with no windows at all.... |
606 | e->ignore(); |
607 | } |
608 | } |
609 | } else e->ignore(); //if the window should not be closed, don't close it |
610 | } |
611 | |
612 | bool KMainWindow::queryExit() |
613 | { |
614 | return true; |
615 | } |
616 | |
617 | bool KMainWindow::queryClose() |
618 | { |
619 | return true; |
620 | } |
621 | |
622 | void KMainWindow::saveGlobalProperties( KConfig* ) |
623 | { |
624 | } |
625 | |
626 | void KMainWindow::readGlobalProperties( KConfig* ) |
627 | { |
628 | } |
629 | |
630 | void KMainWindow::showAboutApplication() |
631 | { |
632 | } |
633 | |
634 | void KMainWindow::savePropertiesInternal( KConfig *config, int number ) |
635 | { |
636 | K_D(KMainWindow); |
637 | const bool oldASWS = d->autoSaveWindowSize; |
638 | d->autoSaveWindowSize = true; // make saveMainWindowSettings save the window size |
639 | |
640 | QString s; |
641 | s.setNum(number); |
642 | s.prepend(QLatin1String("WindowProperties" )); |
643 | KConfigGroup cg(config, s); |
644 | |
645 | // store objectName, className, Width and Height for later restoring |
646 | // (Only useful for session management) |
647 | cg.writeEntry(QLatin1String("ObjectName" ), objectName()); |
648 | cg.writeEntry(QLatin1String("ClassName" ), metaObject()->className()); |
649 | |
650 | saveMainWindowSettings(cg); // Menubar, statusbar and Toolbar settings. |
651 | |
652 | s.setNum(number); |
653 | cg = KConfigGroup(config, s); |
654 | saveProperties(cg); |
655 | |
656 | d->autoSaveWindowSize = oldASWS; |
657 | } |
658 | |
659 | void KMainWindow::saveMainWindowSettings(const KConfigGroup &_cg) |
660 | { |
661 | K_D(KMainWindow); |
662 | //kDebug(200) << "KMainWindow::saveMainWindowSettings " << _cg.name(); |
663 | |
664 | // Called by session management - or if we want to save the window size anyway |
665 | if ( d->autoSaveWindowSize ) |
666 | saveWindowSize( _cg ); |
667 | |
668 | KConfigGroup cg(_cg); // for saving |
669 | |
670 | // One day will need to save the version number, but for now, assume 0 |
671 | // Utilise the QMainWindow::saveState() functionality. |
672 | const QByteArray state = saveState(); |
673 | cg.writeEntry(QString("State" ), state.toBase64()); |
674 | |
675 | QStatusBar* sb = internalStatusBar(this); |
676 | if (sb) { |
677 | if(!cg.hasDefault("StatusBar" ) && !sb->isHidden() ) |
678 | cg.revertToDefault("StatusBar" ); |
679 | else |
680 | cg.writeEntry("StatusBar" , sb->isHidden() ? "Disabled" : "Enabled" ); |
681 | } |
682 | |
683 | QMenuBar* mb = internalMenuBar(this); |
684 | if (mb) { |
685 | const QString = QLatin1String("MenuBar" ); |
686 | if(!cg.hasDefault("MenuBar" ) && !mb->isHidden() ) |
687 | cg.revertToDefault("MenuBar" ); |
688 | else |
689 | cg.writeEntry("MenuBar" , mb->isHidden() ? "Disabled" : "Enabled" ); |
690 | } |
691 | |
692 | if ( !autoSaveSettings() || cg.name() == autoSaveGroup() ) { // TODO should be cg == d->autoSaveGroup, to compare both kconfig and group name |
693 | if(!cg.hasDefault("ToolBarsMovable" ) && !KToolBar::toolBarsLocked()) |
694 | cg.revertToDefault("ToolBarsMovable" ); |
695 | else |
696 | cg.writeEntry("ToolBarsMovable" , KToolBar::toolBarsLocked() ? "Disabled" : "Enabled" ); |
697 | } |
698 | |
699 | int n = 1; // Toolbar counter. toolbars are counted from 1, |
700 | foreach (KToolBar* toolbar, toolBars()) { |
701 | QString group("Toolbar" ); |
702 | // Give a number to the toolbar, but prefer a name if there is one, |
703 | // because there's no real guarantee on the ordering of toolbars |
704 | group += (toolbar->objectName().isEmpty() ? QString::number(n) : QString(" " )+toolbar->objectName()); |
705 | |
706 | KConfigGroup toolbarGroup(&cg, group); |
707 | toolbar->saveSettings(toolbarGroup); |
708 | n++; |
709 | } |
710 | } |
711 | |
712 | bool KMainWindow::readPropertiesInternal( KConfig *config, int number ) |
713 | { |
714 | K_D(KMainWindow); |
715 | |
716 | const bool oldLetDirtySettings = d->letDirtySettings; |
717 | d->letDirtySettings = false; |
718 | |
719 | if ( number == 1 ) |
720 | readGlobalProperties( config ); |
721 | |
722 | // in order they are in toolbar list |
723 | QString s; |
724 | s.setNum(number); |
725 | s.prepend(QLatin1String("WindowProperties" )); |
726 | |
727 | KConfigGroup cg(config, s); |
728 | |
729 | // restore the object name (window role) |
730 | if ( cg.hasKey(QLatin1String("ObjectName" )) ) |
731 | setObjectName( cg.readEntry("ObjectName" ).toLatin1()); // latin1 is right here |
732 | |
733 | d->sizeApplied = false; // since we are changing config file, reload the size of the window |
734 | // if necessary. Do it before the call to applyMainWindowSettings. |
735 | applyMainWindowSettings(cg); // Menubar, statusbar and toolbar settings. |
736 | |
737 | s.setNum(number); |
738 | KConfigGroup grp(config, s); |
739 | readProperties(grp); |
740 | |
741 | d->letDirtySettings = oldLetDirtySettings; |
742 | |
743 | return true; |
744 | } |
745 | |
746 | void KMainWindow::applyMainWindowSettings(const KConfigGroup &cg, bool force) |
747 | { |
748 | K_D(KMainWindow); |
749 | kDebug(200) << "KMainWindow::applyMainWindowSettings " << cg.name(); |
750 | |
751 | QWidget *focusedWidget = QApplication::focusWidget(); |
752 | |
753 | const bool oldLetDirtySettings = d->letDirtySettings; |
754 | d->letDirtySettings = false; |
755 | |
756 | if (!d->sizeApplied) { |
757 | restoreWindowSize(cg); |
758 | d->sizeApplied = true; |
759 | } |
760 | |
761 | QStatusBar* sb = internalStatusBar(this); |
762 | if (sb) { |
763 | QString entry = cg.readEntry("StatusBar" , "Enabled" ); |
764 | if ( entry == "Disabled" ) |
765 | sb->hide(); |
766 | else |
767 | sb->show(); |
768 | } |
769 | |
770 | QMenuBar* mb = internalMenuBar(this); |
771 | if (mb) { |
772 | QString entry = cg.readEntry ("MenuBar" , "Enabled" ); |
773 | if ( entry == "Disabled" ) |
774 | mb->hide(); |
775 | else |
776 | mb->show(); |
777 | } |
778 | |
779 | if ( !autoSaveSettings() || cg.name() == autoSaveGroup() ) { // TODO should be cg == d->autoSaveGroup, to compare both kconfig and group name |
780 | QString entry = cg.readEntry ("ToolBarsMovable" , "Disabled" ); |
781 | if ( entry == "Disabled" ) |
782 | KToolBar::setToolBarsLocked(true); |
783 | else |
784 | KToolBar::setToolBarsLocked(false); |
785 | } |
786 | |
787 | int n = 1; // Toolbar counter. toolbars are counted from 1, |
788 | foreach (KToolBar* toolbar, toolBars()) { |
789 | QString group("Toolbar" ); |
790 | // Give a number to the toolbar, but prefer a name if there is one, |
791 | // because there's no real guarantee on the ordering of toolbars |
792 | group += (toolbar->objectName().isEmpty() ? QString::number(n) : QString(" " )+toolbar->objectName()); |
793 | |
794 | KConfigGroup toolbarGroup(&cg, group); |
795 | toolbar->applySettings(toolbarGroup, force); |
796 | n++; |
797 | } |
798 | |
799 | QByteArray state; |
800 | if (cg.hasKey("State" )) { |
801 | state = cg.readEntry("State" , state); |
802 | state = QByteArray::fromBase64(state); |
803 | // One day will need to load the version number, but for now, assume 0 |
804 | restoreState(state); |
805 | } |
806 | |
807 | if (focusedWidget) { |
808 | focusedWidget->setFocus(); |
809 | } |
810 | |
811 | d->settingsDirty = false; |
812 | d->letDirtySettings = oldLetDirtySettings; |
813 | } |
814 | |
815 | #ifdef Q_WS_WIN |
816 | |
817 | /* |
818 | The win32 implementation for restoring/savin windows size differs |
819 | from the unix/max implementation in three topics: |
820 | |
821 | 1. storing and restoring the position, which may not work on x11 |
822 | see http://doc.trolltech.com/4.3/geometry.html#x11-peculiarities |
823 | 2. using QWidget::saveGeometry() and QWidget::restoreGeometry() |
824 | this would probably be usable on x11 and/or on mac, but I'm unable to |
825 | check this on unix/mac, so I leave this fix to the x11/mac experts. |
826 | 3. store geometry separately for each resolution -> on unix/max the size |
827 | and with of the window are already saved separately on non windows |
828 | system although not using ...Geometry functions -> could also be |
829 | fixed by x11/mac experts. |
830 | */ |
831 | void KMainWindow::restoreWindowSize( const KConfigGroup & _cg ) |
832 | { |
833 | K_D(KMainWindow); |
834 | |
835 | int scnum = QApplication::desktop()->screenNumber(window()); |
836 | QRect desk = QApplication::desktop()->screenGeometry(scnum); |
837 | |
838 | QString geometryKey = QString::fromLatin1("geometry-%1-%2" ).arg(desk.width()).arg(desk.height()); |
839 | QByteArray geometry = _cg.readEntry( geometryKey, QByteArray() ); |
840 | // if first time run, center window |
841 | if (!restoreGeometry( QByteArray::fromBase64(geometry) )) |
842 | move( (desk.width()-width())/2, (desk.height()-height())/2 ); |
843 | } |
844 | |
845 | void KMainWindow::saveWindowSize( const KConfigGroup & _cg ) const |
846 | { |
847 | K_D(const KMainWindow); |
848 | int scnum = QApplication::desktop()->screenNumber(window()); |
849 | QRect desk = QApplication::desktop()->screenGeometry(scnum); |
850 | |
851 | // geometry is saved separately for each resolution |
852 | QString geometryKey = QString::fromLatin1("geometry-%1-%2" ).arg(desk.width()).arg(desk.height()); |
853 | QByteArray geometry = saveGeometry(); |
854 | KConfigGroup cg(_cg); |
855 | cg.writeEntry( geometryKey, geometry.toBase64() ); |
856 | } |
857 | #else |
858 | void KMainWindow::saveWindowSize( const KConfigGroup & _cg ) const |
859 | { |
860 | K_D(const KMainWindow); |
861 | int scnum = QApplication::desktop()->screenNumber(window()); |
862 | QRect desk = QApplication::desktop()->screenGeometry(scnum); |
863 | |
864 | int w, h; |
865 | #if defined Q_WS_X11 |
866 | // save maximalization as desktop size + 1 in that direction |
867 | KWindowInfo info = KWindowSystem::windowInfo( winId(), NET::WMState ); |
868 | w = info.state() & NET::MaxHoriz ? desk.width() + 1 : width(); |
869 | h = info.state() & NET::MaxVert ? desk.height() + 1 : height(); |
870 | #else |
871 | if (isMaximized()) { |
872 | w = desk.width() + 1; |
873 | h = desk.height() + 1; |
874 | } else { |
875 | w = width(); |
876 | h = height(); |
877 | } |
878 | //TODO: add "Maximized" property instead "+1" hack |
879 | #endif |
880 | KConfigGroup cg(_cg); |
881 | |
882 | QRect size( desk.width(), w, desk.height(), h ); |
883 | bool defaultSize = (size == d->defaultWindowSize); |
884 | QString widthString = QString::fromLatin1("Width %1" ).arg(desk.width()); |
885 | QString heightString = QString::fromLatin1("Height %1" ).arg(desk.height()); |
886 | if (!cg.hasDefault(widthString) && defaultSize) |
887 | cg.revertToDefault(widthString); |
888 | else |
889 | cg.writeEntry(widthString, w ); |
890 | |
891 | if (!cg.hasDefault(heightString) && defaultSize) |
892 | cg.revertToDefault(heightString); |
893 | else |
894 | cg.writeEntry(heightString, h ); |
895 | } |
896 | |
897 | void KMainWindow::restoreWindowSize( const KConfigGroup & config ) |
898 | { |
899 | K_D(KMainWindow); |
900 | if (d->care_about_geometry) { |
901 | parseGeometry(true); |
902 | } else { |
903 | // restore the size |
904 | const int scnum = QApplication::desktop()->screenNumber(window()); |
905 | QRect desk = QApplication::desktop()->screenGeometry(scnum); |
906 | |
907 | if ( d->defaultWindowSize.isNull() ) // only once |
908 | d->defaultWindowSize = QRect(desk.width(), width(), desk.height(), height()); // store default values |
909 | const QSize size( config.readEntry( QString::fromLatin1("Width %1" ).arg(desk.width()), 0 ), |
910 | config.readEntry( QString::fromLatin1("Height %1" ).arg(desk.height()), 0 ) ); |
911 | if ( !size.isEmpty() ) { |
912 | #ifdef Q_WS_X11 |
913 | int state = ( size.width() > desk.width() ? NET::MaxHoriz : 0 ) |
914 | | ( size.height() > desk.height() ? NET::MaxVert : 0 ); |
915 | if(( state & NET::Max ) == NET::Max ) |
916 | resize( desk.width(), desk.height() ); // WORKAROUND: this should not be needed. KWindowSystem::setState |
917 | // should be enough for maximizing. (ereslibre) |
918 | else if(( state & NET::MaxHoriz ) == NET::MaxHoriz ) |
919 | resize( desk.width(), size.height() ); |
920 | else if(( state & NET::MaxVert ) == NET::MaxVert ) |
921 | resize( size.width(), desk.height() ); |
922 | else |
923 | resize( size ); |
924 | // QWidget::showMaximized() is both insufficient and broken |
925 | KWindowSystem::setState( winId(), state ); |
926 | #else |
927 | if (size.width() > desk.width() || size.height() > desk.height()) |
928 | setWindowState( Qt::WindowMaximized ); |
929 | else |
930 | resize( size ); |
931 | #endif |
932 | } |
933 | } |
934 | } |
935 | #endif |
936 | |
937 | bool KMainWindow::initialGeometrySet() const |
938 | { |
939 | K_D(const KMainWindow); |
940 | return d->care_about_geometry; |
941 | } |
942 | |
943 | void KMainWindow::ignoreInitialGeometry() |
944 | { |
945 | K_D(KMainWindow); |
946 | d->care_about_geometry = false; |
947 | } |
948 | |
949 | void KMainWindow::setSettingsDirty() |
950 | { |
951 | K_D(KMainWindow); |
952 | d->setSettingsDirty(); |
953 | } |
954 | |
955 | bool KMainWindow::settingsDirty() const |
956 | { |
957 | K_D(const KMainWindow); |
958 | return d->settingsDirty; |
959 | } |
960 | |
961 | void KMainWindow::setAutoSaveSettings( const QString & groupName, bool saveWindowSize ) |
962 | { |
963 | setAutoSaveSettings(KConfigGroup(KGlobal::config(), groupName), saveWindowSize); |
964 | } |
965 | |
966 | void KMainWindow::setAutoSaveSettings( const KConfigGroup & group, |
967 | bool saveWindowSize ) |
968 | { |
969 | K_D(KMainWindow); |
970 | d->autoSaveSettings = true; |
971 | d->autoSaveGroup = group; |
972 | d->autoSaveWindowSize = saveWindowSize; |
973 | |
974 | if (!saveWindowSize && d->sizeTimer) { |
975 | d->sizeTimer->stop(); |
976 | } |
977 | |
978 | // Now read the previously saved settings |
979 | applyMainWindowSettings(d->autoSaveGroup); |
980 | } |
981 | |
982 | void KMainWindow::resetAutoSaveSettings() |
983 | { |
984 | K_D(KMainWindow); |
985 | d->autoSaveSettings = false; |
986 | if (d->settingsTimer) { |
987 | d->settingsTimer->stop(); |
988 | } |
989 | } |
990 | |
991 | bool KMainWindow::autoSaveSettings() const |
992 | { |
993 | K_D(const KMainWindow); |
994 | return d->autoSaveSettings; |
995 | } |
996 | |
997 | QString KMainWindow::autoSaveGroup() const |
998 | { |
999 | K_D(const KMainWindow); |
1000 | return d->autoSaveSettings ? d->autoSaveGroup.name() : QString(); |
1001 | } |
1002 | |
1003 | KConfigGroup KMainWindow::autoSaveConfigGroup() const |
1004 | { |
1005 | K_D(const KMainWindow); |
1006 | return d->autoSaveSettings ? d->autoSaveGroup : KConfigGroup(); |
1007 | } |
1008 | |
1009 | void KMainWindow::saveAutoSaveSettings() |
1010 | { |
1011 | K_D(KMainWindow); |
1012 | Q_ASSERT( d->autoSaveSettings ); |
1013 | //kDebug(200) << "KMainWindow::saveAutoSaveSettings -> saving settings"; |
1014 | saveMainWindowSettings(d->autoSaveGroup); |
1015 | d->autoSaveGroup.sync(); |
1016 | d->settingsDirty = false; |
1017 | } |
1018 | |
1019 | bool KMainWindow::event( QEvent* ev ) |
1020 | { |
1021 | K_D(KMainWindow); |
1022 | switch( ev->type() ) { |
1023 | #ifdef Q_WS_WIN |
1024 | case QEvent::Move: |
1025 | #endif |
1026 | case QEvent::Resize: |
1027 | d->setSizeDirty(); |
1028 | break; |
1029 | case QEvent::Polish: |
1030 | d->polish(this); |
1031 | break; |
1032 | case QEvent::ChildPolished: |
1033 | { |
1034 | QChildEvent *event = static_cast<QChildEvent*>(ev); |
1035 | QDockWidget *dock = qobject_cast<QDockWidget*>(event->child()); |
1036 | KToolBar *toolbar = qobject_cast<KToolBar*>(event->child()); |
1037 | QMenuBar * = qobject_cast<QMenuBar*>(event->child()); |
1038 | if (dock) { |
1039 | connect(dock, SIGNAL(dockLocationChanged(Qt::DockWidgetArea)), |
1040 | this, SLOT(setSettingsDirty())); |
1041 | connect(dock, SIGNAL(visibilityChanged(bool)), |
1042 | this, SLOT(setSettingsDirty()), Qt::QueuedConnection); |
1043 | connect(dock, SIGNAL(topLevelChanged(bool)), |
1044 | this, SLOT(setSettingsDirty())); |
1045 | |
1046 | // there is no signal emitted if the size of the dock changes, |
1047 | // hence install an event filter instead |
1048 | dock->installEventFilter(k_ptr->dockResizeListener); |
1049 | } else if (toolbar) { |
1050 | // there is no signal emitted if the size of the toolbar changes, |
1051 | // hence install an event filter instead |
1052 | toolbar->installEventFilter(k_ptr->dockResizeListener); |
1053 | } else if (menubar) { |
1054 | // there is no signal emitted if the size of the menubar changes, |
1055 | // hence install an event filter instead |
1056 | menubar->installEventFilter(k_ptr->dockResizeListener); |
1057 | } |
1058 | } |
1059 | break; |
1060 | case QEvent::ChildRemoved: |
1061 | { |
1062 | QChildEvent *event = static_cast<QChildEvent*>(ev); |
1063 | QDockWidget *dock = qobject_cast<QDockWidget*>(event->child()); |
1064 | KToolBar *toolbar = qobject_cast<KToolBar*>(event->child()); |
1065 | QMenuBar * = qobject_cast<QMenuBar*>(event->child()); |
1066 | if (dock) { |
1067 | disconnect(dock, SIGNAL(dockLocationChanged(Qt::DockWidgetArea)), |
1068 | this, SLOT(setSettingsDirty())); |
1069 | disconnect(dock, SIGNAL(visibilityChanged(bool)), |
1070 | this, SLOT(setSettingsDirty())); |
1071 | disconnect(dock, SIGNAL(topLevelChanged(bool)), |
1072 | this, SLOT(setSettingsDirty())); |
1073 | dock->removeEventFilter(k_ptr->dockResizeListener); |
1074 | } else if (toolbar) { |
1075 | toolbar->removeEventFilter(k_ptr->dockResizeListener); |
1076 | } else if (menubar) { |
1077 | menubar->removeEventFilter(k_ptr->dockResizeListener); |
1078 | } |
1079 | } |
1080 | break; |
1081 | default: |
1082 | break; |
1083 | } |
1084 | return QMainWindow::event( ev ); |
1085 | } |
1086 | |
1087 | bool KMainWindow::hasMenuBar() |
1088 | { |
1089 | return internalMenuBar(this); |
1090 | } |
1091 | |
1092 | KMenuBar *KMainWindow::menuBar() |
1093 | { |
1094 | KMenuBar * mb = internalMenuBar(this); |
1095 | if ( !mb ) { |
1096 | mb = new KMenuBar( this ); |
1097 | // trigger a re-layout and trigger a call to the private |
1098 | // setMenuBar method. |
1099 | setMenuBar(mb); |
1100 | } |
1101 | return mb; |
1102 | } |
1103 | |
1104 | KStatusBar *KMainWindow::statusBar() |
1105 | { |
1106 | KStatusBar * sb = internalStatusBar(this); |
1107 | if ( !sb ) { |
1108 | sb = new KStatusBar( this ); |
1109 | // trigger a re-layout and trigger a call to the private |
1110 | // setStatusBar method. |
1111 | setStatusBar(sb); |
1112 | } |
1113 | return sb; |
1114 | } |
1115 | |
1116 | void KMainWindowPrivate::_k_shuttingDown() |
1117 | { |
1118 | // Needed for Qt <= 3.0.3 at least to prevent reentrancy |
1119 | // when queryExit() shows a dialog. Check before removing! |
1120 | static bool reentrancy_protection = false; |
1121 | if (!reentrancy_protection) |
1122 | { |
1123 | reentrancy_protection = true; |
1124 | shuttingDown = true; |
1125 | // call the virtual queryExit |
1126 | q->queryExit(); |
1127 | reentrancy_protection = false; |
1128 | } |
1129 | } |
1130 | |
1131 | void KMainWindowPrivate::_k_slotSettingsChanged(int category) |
1132 | { |
1133 | Q_UNUSED(category); |
1134 | |
1135 | // This slot will be called when the style KCM changes settings that need |
1136 | // to be set on the already running applications. |
1137 | |
1138 | // At this level (KMainWindow) the only thing we need to restore is the |
1139 | // animations setting (whether the user wants builtin animations or not). |
1140 | |
1141 | q->setAnimated(KGlobalSettings::graphicEffectsLevel() & KGlobalSettings::SimpleAnimationEffects); |
1142 | } |
1143 | |
1144 | void KMainWindowPrivate::_k_slotSaveAutoSaveSize() |
1145 | { |
1146 | if (autoSaveGroup.isValid()) { |
1147 | q->saveWindowSize(autoSaveGroup); |
1148 | } |
1149 | } |
1150 | |
1151 | KToolBar *KMainWindow::toolBar( const QString& name ) |
1152 | { |
1153 | QString childName = name; |
1154 | if (childName.isEmpty()) |
1155 | childName = "mainToolBar" ; |
1156 | |
1157 | KToolBar *tb = findChild<KToolBar*>(childName); |
1158 | if ( tb ) |
1159 | return tb; |
1160 | |
1161 | KToolBar* toolbar = new KToolBar(childName, this); // non-XMLGUI toolbar |
1162 | return toolbar; |
1163 | } |
1164 | |
1165 | QList<KToolBar*> KMainWindow::toolBars() const |
1166 | { |
1167 | QList<KToolBar*> ret; |
1168 | |
1169 | foreach (QObject* child, children()) |
1170 | if (KToolBar* toolBar = qobject_cast<KToolBar*>(child)) |
1171 | ret.append(toolBar); |
1172 | |
1173 | return ret; |
1174 | } |
1175 | |
1176 | QList<KMainWindow*> KMainWindow::memberList() { return *sMemberList; } |
1177 | |
1178 | QString KMainWindow::dbusName() const |
1179 | { |
1180 | return k_func()->dbusName; |
1181 | } |
1182 | |
1183 | #include "kmainwindow.moc" |
1184 | |
1185 | |