00001 /* 00002 * mdiapplication.cpp 00003 * Nodal 00004 * 00005 * Imported into Nodal by Aidan Lane on Thu Feb 24 2005. 00006 * Copyright (c) 2005 CEMA, Monash University. All rights reserved. 00007 * 00008 * Original file: 00009 * 00010 * mdiapplication.h 00011 * EverGreen 00012 * 00013 * Created by Aidan Lane on Mon Jul 19 2004. 00014 * Copyright (c) 2004 Aidan Lane. All rights reserved. 00015 * 00016 * This program is free software; you can redistribute it and/or modify 00017 * it under the terms of the GNU General Public License as published by 00018 * the Free Software Foundation; either version 2 of the License, or 00019 * (at your option) any later version. 00020 * 00021 * This program is distributed in the hope that it will be useful, 00022 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00023 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00024 * GNU General Public License for more details. 00025 * 00026 * You should have received a copy of the GNU General Public License 00027 * along with this program; if not, write to the Free Software 00028 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 00029 */ 00030 00031 /* 00032 * Multi-Document-Interface Manager - manage "root-less" windows 00033 * This is similar at an abstact level to a widget layout manager: 00034 * - We layout QWidget MDI "children" (as "root-less" windows - e.g. UNIX or MacOS style) 00035 * - We layout QWidget "tool windows" and connect them to the active/focused MDI children 00036 * 00037 * An MDIApplication object instance does NOT show itself - TODO: revise this 00038 * 00039 * Note: MDIApplication only shown when there are no MDIChild objects that it is managing 00040 */ 00041 00042 /* From the old MDIParent system: 00043 * I wrote this so that MDI could be supported by Qt on Mac OS X properly. 00044 * MDIChild objects are NOT children of MDIParent in the eyes of Qt, as they 00045 * need to be "root-less" work properly (instead we maintain track of it here). 00046 * Using addToolwindow and removeToolwindow, you can set which tool windows the 00047 * MDIChild objects are to "own" when they are focused. 00048 * Note: these tool windows can be ANY widget - but ideally of type QDialog. 00049 */ 00050 00051 00052 #ifndef MDIAPPLICATION_H 00053 #define MDIAPPLICATION_H 00054 00055 00056 #include <QApplication> 00057 #include <QDesktopWidget> 00058 #include <QStyle> 00059 00060 00061 // TODO: make the following settable members of MDIApplication 00062 #define CASCADE_BASE_X 50 00063 #define CASCADE_BASE_Y 00 00064 00065 00066 class MDIChildBase { 00067 public: 00068 MDIChildBase() {} 00069 virtual ~MDIChildBase() {} 00070 virtual QWidget* widgetPtr() const = 0; 00071 }; 00072 00073 00074 00077 class MDIApplication : public QApplication { 00078 00079 public: 00080 MDIApplication(int& argc, char** argv ) 00081 : QApplication(argc, argv) { m_activeChildWindow = 0; } 00082 virtual ~MDIApplication() {} 00083 00084 virtual QWidget* topLevel() const { return 0; } // i.e. no parent, but could be used on Windows to provide a common workspace - FIXME: implement this system! 00085 QList<MDIChildBase*> childWindows() const { return m_childWindows; } // QList is implicitly shared, so we don't need to return a "const ref" 00086 MDIChildBase* activeChildWindow() const { return m_activeChildWindow; } 00087 00110 virtual bool childWindowCloseRequest( MDIChildBase* child ) 00111 { 00112 Q_ASSERT( child != 0 ); 00113 // WARNING!!! don't call close() on the child -> we would loop infinitely! 00114 // WARNING!!! MDIChild's destructor will call our removeChildWindow() to remove itself 00115 delete child; 00116 return true; 00117 } 00118 00119 00120 // TODO: implement better window positioning - CURRENTLY CASCADING 00121 // 00122 // Asserts that the child is non-null. 00123 virtual void addChildWindow( MDIChildBase* child ) 00124 { 00125 Q_ASSERT( child != 0 ); 00126 00127 m_childWindows.append( child ); 00128 00129 QWidget* w = child->widgetPtr(); 00130 00131 Q_ASSERT( QApplication::desktop() != 0 ); 00132 QRect availableGeometry = QApplication::desktop()->availableGeometry(); 00133 00134 // TODO: implement better window positioning - CURRENTLY CASCADING 00135 if ( m_childWindows.count() == 1 ) 00136 { 00137 w->move( availableGeometry.left() + CASCADE_BASE_X, 00138 availableGeometry.top() + CASCADE_BASE_Y ); 00139 } 00140 else // note: we assume that m_childWindows.count() > 1 (we append one just above and it !=1) 00141 { 00142 QPoint secLast = (m_childWindows[m_childWindows.count()-2]->widgetPtr())->pos(); // and get the SECOND LAST element 00143 Q_ASSERT( QApplication::style() != 0 ); 00144 int cascadeOffset = QApplication::style()->pixelMetric( QStyle::PM_TitleBarHeight, 0, w ); 00145 if ( cascadeOffset <= 0 ) cascadeOffset = 22; // TODO: remove the need for this? 00146 int tryX = secLast.x() + cascadeOffset; 00147 int tryY = secLast.y() + cascadeOffset; 00148 00149 if ( (tryX + w->width()) <= availableGeometry.right() 00150 && (tryY + w->height()) <= availableGeometry.bottom() ) 00151 w->move( tryX, tryY ); 00152 else 00153 w->move( availableGeometry.left() + CASCADE_BASE_X, 00154 availableGeometry.top() + CASCADE_BASE_Y ); 00155 } 00156 } 00157 00158 00159 // TODO: UPDATE DOC!!! 00160 // If no child windows are left, then make ourself the active window so that 00161 // the active menu bar is ours. 00162 // If "w" == 0, the we return without doing anything 00163 // Calls setActiveChild(0) if no children are left. 00164 // Note that if 1+ still exist, then one will automatically become active, automatically calling setActiveWindow for us. 00165 // 00166 // Asserts that the child is non-null. 00167 virtual void removeChildWindow( MDIChildBase* child ) 00168 { 00169 Q_ASSERT( child != 0 ); 00170 m_childWindows.removeAll( child ); 00171 if ( m_childWindows.isEmpty() ) setActiveChild(0); 00172 } 00173 00174 00175 // TODO: update my doc! 00176 // This "reparents" the child windows to "belong" to the MDI child 00177 // Won't reparent the tool window if the "new" MDI child is already its parent 00178 // Warning: this should only be called by the QWidget object themselves - don't touch! 00179 // Returns true iff OUR child was CHNAGED 00180 // If the child is not registed with us or the child is already the active one, then this will return w/o doing anything. 00181 // It IS valid to pass 0 in as the child, the the case where no child is active 00182 virtual void setActiveChild( MDIChildBase* child ) 00183 { 00184 m_activeChildWindow = child; // ALWAYS updates this! 00185 } 00186 00187 00188 private: 00189 QList<MDIChildBase*> m_childWindows; 00190 MDIChildBase* m_activeChildWindow; 00191 }; 00192 00193 00194 00195 // WARNING! WARNING! WARNING! : 00196 // - We can't use Qt's signals'n'slots to communicate with the manager, as both of 00197 // these are templates! 00198 // - We need to be able to speak to the manager, but because it is a template, we 00199 // need to be told what the manager's definite class is (e.g. MDIApplication<QObejct,QWidget>). 00200 // 00201 // TODO: rewrite the following! 00202 /* 00203 * Convenience class 00204 * 00205 * Looks after the adding, removal and activation to/from the MDIApplication 00206 * Designed to be used with existing classes - such as those created with Qt Designer 00207 * 00208 * T should be a QWidget or a derivative of it. 00209 * 00210 * managerT should beMDIApplication or a derivative of it. 00211 * 00212 * Note: the DESTRUCTOR calls removeChildWindow(), NOT close() 00213 */ 00214 template <class T, class managerT> 00215 class MDIChild : public T, public MDIChildBase { // note: T is a QObject, so it must be first 00216 00217 public: 00227 MDIChild( managerT* manager, QWidget* parent = 0 ) 00228 : T(parent), m_manager(manager) 00229 { 00230 Q_ASSERT( m_manager != 0 ); 00231 m_manager->addChildWindow( this ); 00232 } 00233 00235 // Absolutely must be virtual, see MDIApplication::childWindowCloseRequest(). 00236 virtual ~MDIChild() 00237 { 00238 Q_ASSERT( m_manager != 0 ); 00239 m_manager->removeChildWindow( this ); 00240 } 00241 00243 virtual QWidget* widgetPtr() const 00244 { 00245 return (QWidget*)( this ); // note: a dynamic_cast or static_cast won't work here 00246 } 00247 00255 virtual bool event( QEvent* e ) 00256 { 00257 Q_ASSERT( e != 0 ); 00258 Q_ASSERT( m_manager != 0 ); 00259 00260 // FIXME: DOC: 00261 // Update eveything first, making sure that the window states are updated before be check them! 00262 bool ret = false; 00263 00264 // TODO: optimise me! 00265 switch( e->type() ) 00266 { 00267 case QEvent::Close: // TODO: doc that this is here! 00268 /* WARNING!!! We are _NOT_ to de-allocate our self! 00269 * The manager may need to ask the user something before closing the window 00270 * (e.g. "Save changes before closing?) or perform some other processing 00271 * before we are closed. 00272 */ 00273 if ( m_manager->childWindowCloseRequest(this) ) 00274 return true; // if closed -> nothing left to do! (and if we made any further calls, we would be in trouble!) 00275 else { 00276 e->ignore(); // VERY IMPORTANT! Ignore the close event! 00277 return true; // don't process the event any further 00278 } 00279 00280 case QEvent::WindowActivate: 00281 m_manager->setActiveChild( this ); 00282 break; 00283 00284 case QEvent::WindowDeactivate: 00285 // TODO: re-doc me: - PUT IN A __BIG__ warning of never to do this!!! 00286 // only setActiveChild(0) when we remove the last child window OR if the only window we have has been minimized! 00287 // if ( m_manager->activeChildWindow() == this ) m_manager->setActiveChild( 0 ); 00288 // ret = true; 00289 break; 00290 00291 // TODO: the following should not be needed, as I consider it a bug in Qt that windowActivationChange is not called on minimize or restore... 00292 // REMOVE ME ONCE FIXED! 00293 case QEvent::WindowStateChange: 00294 //i.e. minimized, maximized, restored or made full-screen 00295 // WARNING! When I used windowState() != Qt::WindowMinimized it returned false-positives (mac-snapshot-20050318) 00296 if ( ! T::isMinimized() ) { // this is not the same as == maximized, as there are other states 00297 m_manager->setActiveChild( this ); 00298 } 00299 else if ( m_manager->activeChildWindow() == this ) { // redundant: && isMinimized() ) { 00300 m_manager->setActiveChild( 0 ); 00301 } 00302 ret = true; 00303 break; 00304 00305 default: 00306 break; 00307 } 00308 00309 // WARNING!!! Try the T's event (always give it a go, which may call close() or windowActivationChange()). 00310 // If that fails, then and only then, default to the value of ret. 00311 return T::event(e) || ret; 00312 } 00313 00314 00315 private: 00316 managerT* m_manager; 00317 }; 00318 00319 00320 #endif // ! MDIAPPLICATION_H
1.5.2