/guitone-1.0rc5/src/GuitoneStandalone.cpp
C++ | 360 lines | 252 code | 52 blank | 56 comment | 26 complexity | 4065a372bbe0842b1a7fb6410f629cbe MD5 | raw file
Possible License(s): GPL-3.0
1/***************************************************************************
2 * Copyright (C) 2008 by Thomas Keller *
3 * me@thomaskeller.biz *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation, either version 3 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program. If not, see <http://www.gnu.org/licenses/>. *
17 ***************************************************************************/
18
19#include "GuitoneStandalone.h"
20#include "WorkspaceWindow.h"
21#include "DatabaseWindow.h"
22#include "ServerWindow.h"
23#include "Settings.h"
24
25#include <QMutexLocker>
26#include <QMessageBox>
27#include <QFileInfo>
28#include <QFileDialog>
29#include <QDesktopWidget>
30#include <QFileOpenEvent>
31
32#ifdef Q_WS_MACX
33#include <Carbon/Carbon.h>
34#endif
35
36GuitoneStandalone::GuitoneStandalone(int & argc, char** argv)
37 : GuitoneCore(argc, argv), openPrompt(0), somethingLoaded(false)
38{
39#ifdef Q_WS_MACX
40 // this is kind of backwards, but there is no other way to do it:
41 // to hide an application from the doc on Mac OS X, you have to
42 // set LSUIElement to 1 in its Info.plist - this is done globally
43 // for guitone so we don't get an application menu if we're running
44 // in driver mode; however this also hides the app in standalone mode!
45 // so, as a little trick we're doing what Qt does inside
46 // qapplication_mac.cpp: we're explicitely foregrounding the
47 // standalone version, and therefor ignoring the plist setting
48 ProcessSerialNumber psn;
49 if (GetCurrentProcess(&psn) == noErr)
50 {
51 OSStatus returnCode =
52 TransformProcessType(&psn, kProcessTransformToForegroundApplication);
53 if (returnCode != 0)
54 {
55 C(QString("Could not bring the application to front (error %1)")
56 .arg(returnCode));
57 }
58 }
59#endif
60
61 setQuitOnLastWindowClosed(false);
62
63 programArgs = arguments();
64
65 connect(
66 this, SIGNAL(monotoneBinaryPathSet()),
67 this, SLOT(tryLoadSomething()),
68 Qt::QueuedConnection
69 );
70}
71
72GuitoneStandalone::~GuitoneStandalone()
73{
74 // wait until all windows have been closed
75 while (openWindows.size() > 0)
76 QCoreApplication::processEvents();
77}
78
79void GuitoneStandalone::tryLoadSomething()
80{
81 I(manager()->monotoneBinaryPathSet());
82
83 D("checking command line arguments and file open requests");
84 for (int i=1, j=programArgs.size(); i<j; i++)
85 {
86 // we're ok if any of these arguments could be loaded
87 somethingLoaded = somethingLoaded || loadFromString(programArgs.at(i));
88 }
89
90 if (somethingLoaded) return;
91
92 // if nothing was requested, prompt the user to load a workspace or database
93 openPrompt = new OpenPrompt(NULL);
94
95 connect(
96 openPrompt, SIGNAL(loadFromHandle(const MonotoneHandlePtr &)),
97 this, SLOT(loadFromHandle(const MonotoneHandlePtr &))
98 );
99
100 connect(
101 openPrompt, SIGNAL(loadFromString(const QString &)),
102 this, SLOT(loadFromString(const QString &))
103 );
104
105 connect(
106 openPrompt, SIGNAL(quitApp()),
107 this, SLOT(quit())
108 );
109
110 connect(
111 openPrompt, SIGNAL(quitApp()),
112 openPrompt, SLOT(deleteLater())
113 );
114
115 connect(
116 this, SIGNAL(updateRecentLists()),
117 openPrompt, SLOT(updateRecentLists())
118 );
119
120 connect(
121 this, SIGNAL(loadFromStringFailed(const QString &, const QString &)),
122 openPrompt, SLOT(loadFromStringFailed(const QString &, const QString &))
123 );
124
125 openPrompt->show();
126}
127
128// this code is borrowed and adapted from QDesigner - thanks to the Trolls!
129bool GuitoneStandalone::event(QEvent * ev)
130{
131 bool eaten = false;
132 switch (ev->type())
133 {
134 case QEvent::FileOpen:
135 {
136 QFileOpenEvent * fopev = reinterpret_cast<QFileOpenEvent *>(ev);
137
138 QString fileOrUri = fopev->file();
139 if (fileOrUri.isEmpty())
140 {
141 fileOrUri = QString(fopev->url().toEncoded());
142 }
143
144 if (!manager()->monotoneBinaryPathSet())
145 {
146 D("deferring FileOpen event since binary path is not yet set");
147 programArgs.push_back(fileOrUri);
148 }
149 else
150 {
151 loadFromString(fileOrUri);
152 }
153 eaten = true;
154 break;
155 }
156#ifdef Q_WS_MAC
157 // I'm not sure if this is a bug in Qt or a bug in my code, but
158 // if you click on "Quit" in the application menu in the dock,
159 // Qt receives a Close event on the application, not on the widget
160 // and completly ignores that. (Close events are only documented on
161 // widgets, _not_ the application!) Calling quit() here directly will
162 // at first close all windows (see our quit() implementation above)
163 // and then call quit() again, so we just close all windos here
164 // and, oh magic, even though quitOnLastWindowClose is set to false
165 // Qt magically ends the event loop as well!
166 case QEvent::Close:
167 {
168 eaten = closeAllWindows();
169 }
170#endif
171 default:
172 eaten = QCoreApplication::event(ev);
173 break;
174 }
175 return eaten;
176}
177
178void GuitoneStandalone::loadFromHandle(const MonotoneHandlePtr & handle)
179{
180 QMutexLocker locker(&lock);
181 MainWindow * wnd = 0;
182
183 I(!handle.isNull());
184
185 switch (handle->getType())
186 {
187 case MonotoneHandle::database_handle:
188 wnd = new DatabaseWindow();
189 Settings::addItemToList("RecentDatabaseList", handle->getData(), 5);
190 break;
191 case MonotoneHandle::workspace_handle:
192 wnd = new WorkspaceWindow();
193 Settings::addItemToList("RecentWorkspaceList", handle->getData(), 5);
194 break;
195 case MonotoneHandle::server_handle:
196 wnd = new ServerWindow();
197 Settings::addItemToList("RecentServerList", handle->getData(), 5);
198 break;
199 default:
200 I(false);
201 }
202
203 wnd->init();
204 wnd->load(handle);
205
206 openWindows.append(wnd);
207 ensureCascadedWindowPlacement(wnd);
208 wnd->show();
209
210 triggerUpdateWindowList();
211 emit updateRecentLists();
212
213 if (openPrompt)
214 {
215 openPrompt->hide();
216 // we delete the cruft and invalidate all the set-up connections
217 // once we have something loaded to avoid unwanted side effects
218 // calling delete here directly will fail because there are still
219 // some signals which might have to be delivered to the dialog
220 // before
221 openPrompt->deleteLater();
222 openPrompt = 0;
223 }
224}
225
226bool GuitoneStandalone::loadFromString(const QString & pathOrURI)
227{
228 MonotoneHandlePtr handle;
229 try
230 {
231 handle = MonotoneHandle::create(pathOrURI);
232
233 if (handle->getType() == MonotoneHandle::empty_handle)
234 {
235 throw new GuitoneException(tr("Source was empty"));
236 }
237 }
238 catch (GuitoneException & e)
239 {
240 Settings::removeItemFromList("RecentWorkspaceList", pathOrURI);
241 Settings::removeItemFromList("RecentDatabaseList", pathOrURI);
242 Settings::removeItemFromList("RecentServerList", pathOrURI);
243
244 emit updateRecentLists();
245
246 W(QString("loading of '%1' failed: %2").arg(pathOrURI).arg(e.what()));
247
248 emit loadFromStringFailed(pathOrURI, e.what());
249
250 return false;
251 }
252
253 loadFromHandle(handle);
254
255 return true;
256}
257
258void GuitoneStandalone::windowClosed(MainWindow * wnd)
259{
260 QMutexLocker locker(&lock);
261 int i = openWindows.indexOf(wnd);
262 I(i != -1);
263 openWindows.removeAt(i);
264 delete wnd;
265
266 if (openWindows.size() == 0)
267 {
268 quit();
269 }
270
271 triggerUpdateWindowList();
272}
273
274void GuitoneStandalone::triggerUpdateWindowList()
275{
276 QStringList list;
277 foreach (MainWindow * win, openWindows)
278 {
279 list.append(win->windowTitle());
280 }
281 emit updateWindowList(list);
282}
283
284bool GuitoneStandalone::closeAllWindows()
285{
286 // do we need to close any windows?
287 if (openWindows.size() > 0)
288 {
289 // let all open windows close themselves to allow them cleaning up
290 // stuff and saving settings
291 // note that if the last window is closed, quit() is called again,
292 // so we return here after closing all windows to avoid recursion
293 // problems
294 foreach (MainWindow * wnd, openWindows)
295 {
296 // QWidget::close() returns true if the widget could be closed,
297 // i.e. hasn't ignored the close event
298 if (!wnd->close()) return false;
299 }
300 }
301 return true;
302}
303
304void GuitoneStandalone::quit()
305{
306 if (!closeAllWindows()) return;
307 GuitoneCore::quit();
308}
309
310/*!
311 Ensures somewhat that new windows do not overdraw current ones
312 by adding a little x/y offset the original position of the window
313 opened before this window
314*/
315void GuitoneStandalone::ensureCascadedWindowPlacement(MainWindow * window)
316{
317 int curIdx = openWindows.indexOf(window);
318
319 if (curIdx > 0)
320 {
321 MainWindow * prevWnd = openWindows.at(curIdx - 1);
322 I(prevWnd);
323
324 QDesktopWidget * desk = QApplication::desktop();
325
326 int cascade = 20;
327 QRect geom = desk->availableGeometry(prevWnd);
328 int newX = prevWnd->x() + cascade;
329 int newY = prevWnd->y() + cascade;
330
331 if (newX + window->width() > geom.right() ||
332 newY + window->height() > geom.bottom())
333 {
334 newX = geom.x();
335 newY = geom.y();
336 }
337 window->move(newX, newY);
338 }
339}
340
341void GuitoneStandalone::activateWindow(int idx)
342{
343 if (idx < 0 || idx > openWindows.size() - 1)
344 return;
345
346 MainWindow * wnd = openWindows.at(idx);
347 wnd->activateWindow();
348 wnd->raise();
349}
350
351void GuitoneStandalone::raiseAllWindows()
352{
353 for (int i=0, j=openWindows.size(); i<j; i++)
354 {
355 MainWindow * wnd = openWindows.at(i);
356 wnd->activateWindow();
357 wnd->raise();
358 }
359}
360