/[thuban]/branches/WIP-pyshapelib-bramz/Thuban/UI/application.py
ViewVC logotype

Contents of /branches/WIP-pyshapelib-bramz/Thuban/UI/application.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2446 - (show annotations)
Mon Dec 13 11:52:34 2004 UTC (20 years, 2 months ago) by frank
Original Path: trunk/thuban/Thuban/UI/application.py
File MIME type: text/x-python
File size: 14275 byte(s)
Alternative Path feature:
	* test/test_load.py (TestAltPath): New, tests for alternative path feature
	in load_session()
	(Shapefile_CallBack): Helper, implements controllable callback.

	* Thuban/UI/application.py (ThubanApplication.OnInit):
	Added "alt_path" to self.path
	(ThubanApplication.OpenSession): Added shapefile_callback as second
	callback similar to db_connection_callback.
	(ThubanApplication.run_alt_path_dialog): New, implementaion of
	shapefile_callback. In addition to raising the dialog the control of
	self.path('alt_path') is implemented here.

	* Thuban/Model/load.py (SessionLoader.__init__): Added shapefile_callback.
	(SessionLoader.open_shapefile): Open shapefile, eventually with
	alternative path. This wrapps the "theSession.OpenShapefile(filename)"
	formerly used in start_fileshapesource()/start_layer().
	(SessionLoader.start_fileshapesource): Call open_shapefile().
	(SessionLoader.start_layer): Call open_shapefile().
	(load_session): Added shapefile_callback.

	* Thuban/UI/altpathdialog.py: New, implements dialogs for alternative path
	feature (search / check).

1 # Copyright (C) 2001, 2002, 2003, 2004 by Intevation GmbH
2 # Authors:
3 # Jan-Oliver Wagner <[email protected]>
4 # Bernhard Herzog <[email protected]>
5 #
6 # This program is free software under the GPL (>=v2)
7 # Read the file COPYING coming with Thuban for details.
8
9 """
10 Thuban's application object.
11 """
12
13 __version__ = "$Revision$"
14
15 import sys, os
16 import os.path
17
18 import traceback
19
20 from wxPython.wx import *
21
22 from Thuban.Lib.connector import Publisher
23 from Thuban.Lib.fileutil import get_application_dir
24
25 from Thuban import _
26 from Thuban.Model.session import create_empty_session
27 from Thuban.Model.save import save_session
28 from Thuban.Model.load import load_session, LoadCancelled
29 from Thuban.Model.messages import MAPS_CHANGED
30 from Thuban.Model.layer import RasterLayer
31 import Thuban.Model.resource
32
33 import view
34 import tree
35 import mainwindow
36 import dbdialog
37 import altpathdialog
38 import exceptiondialog
39
40 from messages import SESSION_REPLACED
41
42
43 class ThubanApplication(wxApp, Publisher):
44
45 """
46 Thuban's application class.
47
48 All wxWindows programs have to have an instance of an application
49 class derived from wxApp. In Thuban the application class holds
50 references to the main window and the session.
51 """
52
53 def OnInit(self):
54 sys.excepthook = self.ShowExceptionDialog
55
56 # Initialize instance variables before trying to create any
57 # windows. Creating windows can start an event loop if
58 # e.g. message boxes are popped up for some reason, and event
59 # handlers, especially EVT_UPDATE_UI may want to access things
60 # from the application.
61
62 # Defaults for the directories used in file dialogs
63 self.path={"data":".", "projection":".", "alt_path":""}
64
65 self.session = None
66 self.top = None
67 self.create_session()
68
69 # Create an optional splash screen and then the mainwindow
70 self.splash = self.splash_screen()
71 if self.splash is not None:
72 self.splash.Show()
73 self.read_startup_files()
74 self.top = self.CreateMainWindow()
75 # The session was alredy created above and we need to get the
76 # map into the mainwindow. maps_changed does that.
77 self.maps_changed()
78 self.SetTopWindow(self.top)
79 if self.splash is None:
80 self.ShowMainWindow()
81
82 return True
83
84 def OnExit(self):
85 """Clean up code.
86
87 Extend this in derived classes if needed.
88 """
89 self.session.Destroy()
90 self.session = None
91 Publisher.Destroy(self)
92
93 def read_startup_files(self):
94 """Read the startup files."""
95 # for now the startup file is ~/.thuban/thubanstart.py
96 dir = get_application_dir()
97 if os.path.isdir(dir):
98 sys.path.append(dir)
99 try:
100 import thubanstart
101 except ImportError:
102 tb = sys.exc_info()[2]
103 try:
104 if tb.tb_next is not None:
105 # The ImportError exception was raised from
106 # inside the thubanstart module.
107 sys.stderr.write(_("Cannot import the thubanstart"
108 " module\n"))
109 traceback.print_exc(None, sys.stderr)
110 else:
111 # There's no thubanstart module.
112 sys.stderr.write(_("No thubanstart module available\n"))
113 finally:
114 # make sure we delete the traceback object,
115 # otherwise there's be circular references involving
116 # the current stack frame
117 del tb
118 except:
119 sys.stderr.write(_("Cannot import the thubanstart module\n"))
120 traceback.print_exc(None, sys.stderr)
121 else:
122 # There's no .thuban directory
123 sys.stderr.write(_("No ~/.thuban directory\n"))
124
125 def splash_screen(self):
126 """Create and return a splash screen.
127
128 This method is called by OnInit to determine whether the
129 application should have a splashscreen. If the application
130 should display a splash screen override this method in a derived
131 class and have it create and return the wxSplashScreen instance.
132 The implementation of this method in the derived class should
133 also arranged for ShowMainWindow to be called.
134
135 The default implementation simply returns None so that no splash
136 screen is shown and ShowMainWindow will be called automatically.
137 """
138 return None
139
140 def ShowMainWindow(self):
141 """Show the main window
142
143 Normally this method is automatically called by OnInit to show
144 the main window. However, if the splash_screen method has
145 returned a splashscreen it is expected that the derived class
146 also arranges for ShowMainWindow to be called at the appropriate
147 time.
148 """
149 self.top.Show(True)
150
151 def CreateMainWindow(self):
152 """Create and return the main window for the application.
153
154 Override this in subclasses to instantiate the Thuban mainwindow
155 with different parameters or to use a different class for the
156 main window.
157 """
158 msg = (_("This is the wxPython-based Graphical User Interface"
159 " for exploring geographic data"))
160 return mainwindow.MainWindow(NULL, -1, "Thuban", self, None,
161 initial_message = msg,
162 size = (600, 400))
163
164 def Session(self):
165 """Return the application's session object"""
166 return self.session
167
168 def SetSession(self, session):
169 """Make session the new session.
170
171 Issue SESSION_REPLACED after self.session has become the new
172 session. After the session has been assigned call
173 self.subscribe_session() with the new session and
174 self.unsubscribe_session with the old one.
175 """
176 oldsession = self.session
177 self.session = session
178 self.subscribe_session(self.session)
179 self.issue(SESSION_REPLACED)
180 self.maps_changed()
181 if oldsession is not None:
182 self.unsubscribe_session(oldsession)
183 oldsession.Destroy()
184
185 def SetPath(self, group, filename):
186 """Store the application's default path for file dialogs extracted
187 from a given filename.
188 """
189 self.path[group] = os.path.dirname( filename )
190
191 def Path(self, group):
192 """Return the application's default path for file dialogs."""
193 return self.path[group]
194
195 def subscribe_session(self, session):
196 """Subscribe to some of the sessions channels.
197
198 Extend this method in derived classes if you need additional
199 channels.
200 """
201 session.Subscribe(MAPS_CHANGED, self.maps_changed)
202
203 def unsubscribe_session(self, session):
204 """Unsubscribe from the sessions channels.
205
206 Extend this method in derived classes if you subscribed to
207 additional channels in subscribe_session().
208 """
209 session.Unsubscribe(MAPS_CHANGED, self.maps_changed)
210
211 def create_session(self):
212 """Create a default session.
213
214 Override this method in derived classes to instantiate the
215 session differently or to use a different session class. Don't
216 subscribe to channels here yet. Do that in the
217 subscribe_session() method.
218 """
219 self.SetSession(create_empty_session())
220
221 def OpenSession(self, filename, db_connection_callback = None,
222 shapefile_callback = None):
223 """Open the session in the file named filename"""
224 # Make sure we deal with an absolute pathname. Otherwise we can
225 # get problems when saving because the saving code expects an
226 # absolute directory name
227 filename = os.path.abspath(filename)
228 if db_connection_callback is None:
229 db_connection_callback = self.run_db_param_dialog
230 if shapefile_callback is None:
231 shapefile_callback = self.run_alt_path_dialog
232 try:
233 session = load_session(filename,
234 db_connection_callback=db_connection_callback,
235 shapefile_callback=shapefile_callback)
236 except LoadCancelled:
237 return
238 session.SetFilename(filename)
239 session.UnsetModified()
240 self.SetSession(session)
241
242 for map in session.Maps():
243 for layer in map.Layers():
244 if isinstance(layer, RasterLayer) \
245 and not Thuban.Model.resource.has_gdal_support():
246 msg = _("The current session contains Image layers,\n"
247 "but the GDAL library is not available to "
248 "draw them.")
249 dlg = wx.wxMessageDialog(None,
250 msg,
251 _("Library not available"),
252 wx.wxOK | wx.wxICON_INFORMATION)
253 print msg
254 dlg.ShowModal()
255 dlg.Destroy()
256 break
257
258 def run_db_param_dialog(self, parameters, message):
259 """Implementation of the db_connection_callback for loading sessions"""
260 dlg = dbdialog.DBDialog(None, _("DB Connection Parameters"),
261 parameters, message)
262 return dlg.RunDialog()
263
264 # run_alt_path_dialog: Raise a dialog to ask for an alternative path
265 # if the shapefile couldn't be found.
266 # TODO:
267 # - Store a list of already used alternative paths and return these
268 # iteratively (using a generator)
269 # - How do we interact with the user to tell him we used a different
270 # shapefile (location), mode "check"? The current approach with the
271 # file dialog is not that comfortable.
272 #
273 def run_alt_path_dialog(self, filename, mode = None, second_try = 0):
274 """Implemetation of the shapefile_callback while loading sessions.
275
276 This implements two modes:
277 - search: Search for an alternative path. If available from a
278 list of alrady known paths, else interactivly by file dialog.
279 Currently the "second_try" is important since else the user might
280 be caught in a loop.
281 - check: Ask the user for confirmation, if a path from list has
282 been found successful.
283
284 Returns:
285 - fname: The full path to the (shape) file.
286 - from_list: Flags if the path was taken from list or entered
287 manually.
288 """
289
290 if mode == "search":
291 if self.Path('alt_path') == "" or second_try:
292 dlg = altpathdialog.AltPathFileDialog(filename)
293 fname = dlg.RunDialog()
294 if fname is not None:
295 self.SetPath('alt_path', fname)
296 from_list = 0
297 else:
298 fname = os.path.join(self.Path('alt_path'),
299 os.path.basename(filename))
300 from_list = 1
301 elif mode == "check":
302 dlg = altpathdialog.AltPathConfirmDialog(filename)
303 fname = dlg.RunDialog()
304 if fname is not None:
305 self.SetPath('alt_path', fname)
306 from_list = 0
307 else:
308 fname = None
309 from_list = 0
310 return fname, from_list
311
312
313 def SaveSession(self):
314 save_session(self.session, self.session.filename)
315
316 def maps_changed(self, *args):
317 """Subscribed to the session's MAPS_CHANGED messages.
318
319 Set the toplevel window's map to the map in the session. This is
320 done by calling the window's SetMap method with the map as
321 argument. If the session doesn't have any maps None is used
322 instead.
323
324 Currently Thuban can only really handle at most one map in a
325 sessions so the first map in the session's list of maps as
326 returned by the Maps method is used.
327 """
328 # The mainwindow may not have been created yet, so check whether
329 # it has been created before calling any of its methods
330 if self.top is not None:
331 if self.session.HasMaps():
332 self.top.SetMap(self.session.Maps()[0])
333 else:
334 self.top.SetMap(None)
335
336 in_exception_dialog = 0 # flag: are we already inside the exception dialog?
337
338 def ShowExceptionDialog(self, exc_type, exc_value, exc_traceback):
339 """Show a message box with information about an exception.
340
341 The parameters are the usual values describing an exception in
342 Python, the exception type, the value and the traceback.
343
344 This method can be used as a value for the sys.excepthook.
345 """
346
347 if self.in_exception_dialog:
348 return
349 self.in_exception_dialog = 1
350 while wxIsBusy():
351 wxEndBusyCursor() # reset the mouse cursor
352
353 try:
354 lines = traceback.format_exception(exc_type, exc_value,
355 exc_traceback)
356 message = _("An unhandled exception occurred:\n%s\n"
357 "(please report to"
358 " http://thuban.intevation.org/bugtracker.html)"
359 "\n\n%s") % (exc_value, "".join(lines))
360 print message
361
362 # We don't use an explicit parent here because this method might
363 # be called in circumstances where the main window doesn't exist
364 # anymore.
365 exceptiondialog.run_exception_dialog(None, message)
366
367 finally:
368 self.in_exception_dialog = 0
369 # delete the last exception info that python keeps in
370 # sys.last_* because especially last_traceback keeps
371 # indirect references to all objects bound to local
372 # variables and this might prevent some object from being
373 # collected early enough.
374 sys.last_type = sys.last_value = sys.last_traceback = None
375

Properties

Name Value
svn:eol-style native
svn:keywords Author Date Id Revision

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26