1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 """
22 Contains code for the toplevel window
23 """
24
25
26 import gtk
27 import os
28 import logging
29
30 import preferences
31 import mapcontroller
32 import editortools
33 import tilepalette
34 import mapgrid
35 import layers
36 import dialogs
37 import datafiles
38 import undo
39
40 programName = "Arctographer"
41
42 uiString = """
43 <ui>
44 <menubar name="MenuBar">
45 <menu action="File">
46 <menuitem action="New"/>
47 <menuitem action="Open"/>
48 <separator/>
49 <menuitem action="Save"/>
50 <menuitem action="SaveAs"/>
51 <separator/>
52 <menuitem action="Properties"/>
53 <separator/>
54 <menuitem action="Close"/>
55 <menuitem action="Quit"/>
56 </menu>
57 <menu action="Edit">
58 <menuitem action="Undo"/>
59 <menuitem action="Redo"/>
60 <separator/>
61 <menuitem action="Resize"/>
62 <menuitem action="Background"/>
63 <separator/>
64 <menuitem action="Preferences"/>
65 </menu>
66 <menu action="View">
67 <menuitem action="ToggleToolbar"/>
68 <separator/>
69 <menuitem action="ToggleGridMap"/>
70 <menuitem action="ToggleGridPalette"/>
71 <separator/>
72 <menuitem action="ZoomIn"/>
73 <menuitem action="ZoomOut"/>
74 <menuitem action="ZoomNormal"/>
75 </menu>
76 <menu action="Help">
77 <menuitem action="About"/>
78 </menu>
79 </menubar>
80 <toolbar name="MapTools">
81 <toolitem action="TileDraw"/>
82 <toolitem action="TileDelete"/>
83 <separator/>
84 <toolitem action="PhysicsSelect"/>
85 <toolitem action="PolygonDraw"/>
86 <toolitem action="CircleDraw"/>
87 </toolbar>
88 <toolbar name="Layers">
89 <toolitem action="addLayer"/>
90 <toolitem action="removeLayer"/>
91 <toolitem action="raiseLayer"/>
92 <toolitem action="lowerLayer"/>
93 </toolbar>
94 <toolbar name="MainBar">
95 <toolitem action="New"/>
96 <toolitem action="Open"/>
97 <toolitem action="Save"/>
98 <toolitem action="SaveAs"/>
99 <separator/>
100 <toolitem action="ZoomIn"/>
101 <toolitem action="ZoomOut"/>
102 <toolitem action="ZoomNormal"/>
103 <separator/>
104 <toolitem action="Undo"/>
105 <toolitem action="Redo"/>
106 </toolbar>
107 </ui>
108 """
109
110
111 -class MainWindow(mapcontroller.MapListener):
112 """ Toplevel window for the map editor """
113 - def __init__(self, fileName = None):
114 """
115 @type fileName: str
116 @param fileName: the name of the file to try to open the program with
117 """
118 controller = mapcontroller.MapController()
119 mapcontroller.MapListener.__init__(self, controller)
120 self.mapGrid = None
121 self.__createGUI()
122
123 failureReason = controller.open(fileName)
124 if failureReason != None and fileName != None:
125 dialog = gtk.MessageDialog(self.window,
126 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
127 gtk.MESSAGE_ERROR,
128 gtk.BUTTONS_OK,
129 "Error opening the file %s:" % fileName)
130 dialog.format_secondary_markup(failureReason)
131 dialog.run()
132 dialog.destroy()
133 controller.setToplevel(self.window)
134 fileName = None
135
136 - def __createGUI(self):
137 """
138 Assembles the widgets together onto the window
139 """
140 self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
141 self.window.set_title(programName)
142 self.window.connect("destroy", self.destroy)
143 self.window.connect("delete_event", self.delete_event)
144 self.window.connect("key-press-event", self.keyPress)
145
146 self.uimanager = gtk.UIManager()
147 accelGroup = self.uimanager.get_accel_group()
148 self.window.add_accel_group(accelGroup)
149 actionGroup = gtk.ActionGroup("ActionGroup")
150
151 self.paletteManager = None
152
153
154 actionGroup.add_actions([
155 ("New", gtk.STOCK_NEW, "_New", "<Control>N",
156 "Create a new file", self.file_new),
157 ("Open", gtk.STOCK_OPEN, "_Open", "<Control>O", "Open a saved map",
158 self.file_open),
159 ("Save", gtk.STOCK_SAVE, "_Save", "<Control>S",
160 "Save the current file", self.file_save),
161 ("SaveAs", gtk.STOCK_SAVE_AS, "Save _As...", "<Control><Shift>S",
162 "Save the current file under a different name",
163 self.file_saveAs),
164 ("Properties", gtk.STOCK_PROPERTIES, "Properties", "<Alt>Return",
165 "Modify the properties of the map", self.file_properties),
166 ("Close", gtk.STOCK_CLOSE, "_Close", "<Control>W",
167 "Close the current file", self.file_close),
168 ("Quit", gtk.STOCK_QUIT, "_Quit", "<Control>Q", "Quit the program",
169 self.file_quit),
170 ("File", None, "_File"),
171 ("Edit", None, "_Edit"),
172 ("View", None, "_View"),
173 ("Help", None, "_Help"),
174 ])
175
176
177 actionGroup.add_actions([
178 ("Resize", None, "R_esize", None, "Resize the map",
179 self.edit_resize),
180 ("Preferences", gtk.STOCK_PREFERENCES, "_Preferences", None,
181 "Application Preferences", self.edit_preferences),
182 ("Undo", gtk.STOCK_UNDO, "_Undo", "<Control>Z",
183 "Undo the last change", self.edit_undo),
184 ("Redo", gtk.STOCK_REDO, "_Redo", "<Control>Y",
185 "Redo the last change", self.edit_redo),
186 ("Background", None, "_Background", "<Control>B",
187 "Change background information for the map",
188 self.edit_background)
189 ])
190
191
192 actionGroup.add_actions([
193 ("ZoomIn", gtk.STOCK_ZOOM_IN, "Zoom _In", None, "Zoom In",
194 self.view_zoomIn),
195 ("ZoomOut", gtk.STOCK_ZOOM_OUT, "Zoom _Out", None, "Zoom Out",
196 self.view_zoomOut),
197 ("ZoomNormal", gtk.STOCK_ZOOM_100, "_Normal Size", "<Control>0",
198 "Normal Size", self.view_zoomNormal)
199 ])
200
201
202 actionGroup.add_actions([("About", gtk.STOCK_ABOUT, "_About", None,
203 "Information about the program.", self.help_about)])
204
205
206 actionGroup.add_actions([
207 ("addLayer", gtk.STOCK_NEW, None, None,
208 "Add a new layer to the map", self.addLayer),
209 ("removeLayer", gtk.STOCK_DELETE, None, None,
210 "Remove the current layer from the map", self.removeLayer),
211 ("raiseLayer", gtk.STOCK_GO_UP, None, None,
212 "Raise the current layer", self.raiseLayer),
213 ("lowerLayer", gtk.STOCK_GO_DOWN, None, None,
214 "Lower the current layer", self.lowerLayer)
215 ])
216 actionGroup.add_toggle_actions([
217 ("ToggleGridMap", None, "Show Grid on _Map", "<Control>G",
218 "Show a grid overlaid on the map", self.view_toggleGridMap),
219 ("ToggleGridPalette", None, "Show Grid on _Palette",
220 "<Control><Shift>G", "Show a grid overlaid on the tile palette",
221 self.view_toggleGridPalette),
222 ("ToggleToolbar", None, "Show _Toolbar", None, "Show the toolbar",
223 self.view_toggleToolbar)
224 ])
225
226
227 actionGroup.add_radio_actions([
228 ("TileDraw", None, None, "<Control>T", "Draw tiles on the map",
229 editortools.TILE_DRAW_ID),
230 ("TileDelete", None, None, "<Contorol><Shift>T", "Remove tiles "+
231 "from the map", editortools.TILE_DELETE_ID),
232 ("PhysicsSelect", None, None, "<Control>P",
233 "Select and edit shapes", editortools.PHYSICS_SELECT_ID),
234 ("PolygonDraw", None, None, "<Control>P", "Draw polygons"
235 + " on the map", editortools.POLYGON_DRAW_ID),
236 ("CircleDraw", None, None, "<Control>C", "Draw circles"
237 + " on the map", editortools.CIRCLE_DRAW_ID),
238 ("LightDraw", None, None, None, "Place lights on the map",
239 editortools.LIGHT_DRAW_ID)
240 ], editortools.TILE_DRAW_ID, self.toolSelect)
241
242 self.uimanager.insert_action_group(actionGroup, 0)
243 self.uimanager.add_ui_from_string(uiString)
244 self.widgets = [
245 "/MenuBar/File/Properties", "/MenuBar/File/Save",
246 "/MenuBar/File/SaveAs", "/MenuBar/File/Close",
247 "/MenuBar/Edit/Undo",
248 "/MenuBar/Edit/Redo", "/MenuBar/Edit/Resize",
249 "/MenuBar/Edit/Background",
250 "/MenuBar/View/ToggleGridMap", "/MenuBar/View/ToggleGridPalette",
251 "/MenuBar/View/ZoomIn",
252 "/MenuBar/View/ZoomOut", "/MenuBar/View/ZoomNormal",
253 "/MainBar/Save", "/MainBar/SaveAs", "/MainBar/ZoomIn",
254 "/MainBar/ZoomOut", "/MainBar/ZoomNormal", "/MainBar/Undo",
255 "/MainBar/Redo", "/MapTools", "/Layers"
256 ]
257
258
259 self.uimanager.get_widget("/MenuBar/View/ToggleToolbar").set_active(True)
260 self.uimanager.get_widget("/MenuBar/View/ToggleGridPalette").set_active(True)
261
262 vbox = gtk.VBox(False, 0)
263 self.window.add(vbox)
264
265 vPaned = gtk.VPaned()
266 self.paletteManager = tilepalette.PaletteManager(self.getController())
267 vPaned.pack1(self.paletteManager.getWidget(), True, False)
268 vPaned.pack2(self.createTreeView(), True, False)
269 vPaned.set_size_request(200, -1)
270
271 hbox = gtk.HBox(False, 0)
272 hbox.pack_start(self.__createToolbar(), False, False, 0)
273
274 self.mapScrolledWindow = gtk.ScrolledWindow()
275 self.mapScrolledWindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
276 hbox.pack_end(self.mapScrolledWindow, True, True, 0)
277
278 hpaned = gtk.HPaned()
279 hpaned.pack1(hbox, True, False)
280 hpaned.pack2(vPaned, False, False)
281
282 vbox.pack_start(self.uimanager.get_widget("/MenuBar"), False, False, 0)
283 vbox.pack_start(self.uimanager.get_widget("/MainBar"), False, False, 0)
284
285 self.statusBar = gtk.Statusbar()
286 id = self.statusBar.get_context_id("Main Window")
287 self.statusBar.push(id, "Open an existing file (Control+O) or create a new one (Control+N)")
288 vbox.pack_end(self.statusBar, False, False, 0)
289 vbox.pack_end(hpaned, True, True, 2)
290
291 self.__setWidgetsInsensitive()
292
293 self.window.set_size_request(900, 560)
294 self.setTitle()
295 self.window.show_all()
296
297 - def setTitle(self):
298 """
299 Sets the window title.
300 """
301 name = self.getController().getFileName()
302 titleString = ""
303 if self.getController().unsaved():
304 titleString = titleString + "*"
305 if name == None:
306
307 titleString = titleString + "Unsaved Map"
308 else:
309 titleString = titleString + os.path.basename(name)
310 self.window.set_title(titleString)
311
313 """
314 Creates the toolbar on the left of the screen that holds the tools
315 defined in editortools
316 """
317 bar = self.uimanager.get_widget("/MapTools")
318 bar.set_border_width(0)
319 bar.set_orientation(gtk.ORIENTATION_VERTICAL)
320
321 tileButton = self.uimanager.get_widget("/MapTools/TileDraw")
322 tileImage = gtk.image_new_from_file(datafiles.getIconPath(
323 "tileDraw.png"))
324 tileImage.show()
325 tileButton.set_icon_widget(tileImage)
326
327 deleteButton = self.uimanager.get_widget("/MapTools/TileDelete")
328 deleteImage = gtk.image_new_from_file(datafiles.getIconPath(
329 "tileDelete.png"))
330 deleteImage.show()
331 deleteButton.set_icon_widget(deleteImage)
332
333 selectButton = self.uimanager.get_widget("/MapTools/PhysicsSelect")
334 selectImage = gtk.image_new_from_file(datafiles.getIconPath(
335 "physicsSelect.png"))
336 selectImage.show()
337 selectButton.set_icon_widget(selectImage)
338
339 staticButton = self.uimanager.get_widget("/MapTools/PolygonDraw")
340 staticImage = gtk.image_new_from_file(datafiles.getIconPath(
341 "staticPolyDraw.png"))
342 staticImage.show()
343 staticButton.set_icon_widget(staticImage)
344
345 staticCButton = self.uimanager.get_widget("/MapTools/CircleDraw")
346 staticCImage = gtk.image_new_from_file(datafiles.getIconPath(
347 "staticCircleDraw.png"))
348 staticCImage.show()
349 staticCButton.set_icon_widget(staticCImage)
350
351
352
353
354
355
356 bar.set_sensitive(False)
357 return bar
358
359 - def createTreeView(self):
360 """
361 Creates the tree view and its associated buttons
362 """
363 self.layerView = layers.LayerView(self.getController())
364 vBox = gtk.VBox()
365 toolbar = self.uimanager.get_widget("/Layers")
366 toolbar.set_sensitive(False)
367 vBox.pack_end(toolbar, False, False)
368 vBox.pack_start(self.layerView.getWidget(), True, True)
369 return vBox
370
371 - def delete_event(self, widget, event, data = None):
372 return (self.closeCheck() == False)
373
374 - def destroy(self, widget, data = None):
376
377 - def file_new(self, widget, data = None):
378 dialog = dialogs.NewDialog(self.window)
379 response = dialog.run()
380 dialog.destroy()
381 if response == gtk.RESPONSE_ACCEPT:
382 width = dialog.getWidth()
383 height = dialog.getHeight()
384 tileSize = dialog.getTileSize()
385 self.getController().new(tileSize, width, height)
386
387 - def file_open(self, window, data = None):
388 openDialog = gtk.FileChooserDialog("Open", self.window,
389 gtk.FILE_CHOOSER_ACTION_OPEN, (gtk.STOCK_CANCEL,
390 gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_ACCEPT))
391 filter = gtk.FileFilter()
392 filter.add_mime_type("application/xml")
393 filter.add_pattern("*.xml")
394 filter.set_name("XML Files")
395 openDialog.add_filter(filter)
396 response = openDialog.run()
397 if response == gtk.RESPONSE_ACCEPT:
398 fileName = openDialog.get_filename()
399 self.getController().open(fileName)
400 self.setTitle()
401 openDialog.destroy()
402
403 - def file_save(self, widget, data = None):
404 if self.getController().hasMap() == False:
405 return
406 if self.getController().getFileName() == None:
407 self.promptSave()
408 else:
409 self.getController().save()
410 self.setTitle()
411
412 - def file_saveAs(self, widget, data = None):
413 if self.getController().hasMap() == False:
414 return
415 self.promptSave()
416
417 - def file_properties(self, widget, data = None):
418 if self.getController().hasMap() == False:
419 return
420
421 d = dialogs.PropertiesDialog(self.window, self.map)
422 response = d.run()
423 d.destroy()
424
425 - def promptSave(self):
426 """
427 @rtype: bool
428 @return: Returns True if the user saved, False otherwise
429 """
430 saveDialog = gtk.FileChooserDialog("Save As", self.window,
431 gtk.FILE_CHOOSER_ACTION_SAVE, (gtk.STOCK_CANCEL,
432 gtk.RESPONSE_CANCEL, gtk.STOCK_SAVE, gtk.RESPONSE_ACCEPT))
433 mapFilter = gtk.FileFilter()
434 mapFilter.add_mime_type("application/xml")
435 mapFilter.set_name("XML Files")
436 saveDialog.add_filter(mapFilter)
437 response = saveDialog.run()
438 if response == gtk.RESPONSE_ACCEPT:
439 self.getController().setFileName(saveDialog.get_filename())
440 self.getController().save()
441 saveDialog.destroy()
442 self.setTitle()
443 return True
444 else:
445 saveDialog.destroy()
446 return False
447
448 - def closeCheck(self):
449 """
450 Checks to see if the user wants to quit and if he/she wants to save the
451 changes to the map.
452 @rtype: bool
453 @return: False if the user hit "Cancel", True if the file should be
454 closed
455 """
456 if self.getController().unsaved():
457 response = dialogs.UnsavedDialog(self.window)
458 if response == gtk.RESPONSE_CANCEL:
459
460 return False
461 elif response == gtk.RESPONSE_ACCEPT:
462
463 if self.getController().getFileName() == None:
464 return self.promptSave()
465 else:
466 self.getController().save()
467 return True
468 elif response == gtk.RESPONSE_CLOSE:
469
470 return True
471 else:
472 return True
473
474 - def file_quit(self, widget, data = None):
475 if self.closeCheck():
476 gtk.main_quit()
477
478 - def file_close(self, widget, data = None):
479 if self.getController().hasMap() == False:
480 return
481 if self.closeCheck():
482 self.getController().close()
483
484 - def edit_resize(self, window, data = None):
485 controller = self.getController()
486 if controller.hasMap() == False:
487 return
488
489 dialog = dialogs.ResizeDialog(self.window,
490 controller.mapWidth(),
491 controller.mapHeight(),
492 controller.getThumbnail(96))
493 response = dialog.run()
494 if response == gtk.RESPONSE_ACCEPT:
495 width = dialog.getWidth()
496 height = dialog.getHeight()
497 xOffset = dialog.getXOffset()
498 yOffset = dialog.getYOffset()
499 action = undo.ResizeAction(controller, width, height,
500 xOffset, yOffset, controller.mapWidth(),
501 controller.mapHeight())
502 controller.addUndoAction(action)
503 controller.resize(width, height, xOffset, yOffset)
504 dialog.destroy()
505
506 - def edit_undo(self, window, data = None):
507 controller = self.getController()
508 if controller.hasMap() == False:
509 return
510 controller.undo()
511
512 - def edit_redo(self, window, data = None):
513 controller = self.getController()
514 if controller.hasMap() == False:
515 return
516 controller.redo()
517
518 - def edit_preferences(self, window, data = None):
519 dialog = dialogs.PreferencesDialog(self.window)
520 dialog.run()
521 dialog.destroy()
522 preferences.save()
523
524 - def edit_background(self, window, data = None):
525 if self.getController().hasMap() == False:
526 return
527
528 d = dialogs.BackgroundDialog(self.window,
529 self.getController().getParallaxes(),
530 self.getController().getBGColor())
531 result = d.run()
532 if result == gtk.RESPONSE_ACCEPT:
533 self.getController().setParallaxes(d.parallaxes)
534 self.getController().setBGColor(d.bgColor)
535
536 d.destroy()
537
538 - def view_toggleGridMap(self, widget, data = None):
539 """
540 We installed this function so you could turn the grid on, and off.
541 NOT SO YOU COULD HOLD GRIDSWITCH RAVES!
542 """
543 if self.getController().hasMap() == False:
544 return
545 self.mapGrid.toggleGrid()
546
547 - def view_toggleGridPalette(self, widget, data = None):
548 if self.getController().hasMap() == False:
549 return
550 if self.paletteManager != None:
551 self.paletteManager.toggleGrid()
552
554 toolbar = self.uimanager.get_widget("/MainBar")
555 if widget.get_active():
556 toolbar.show()
557 else:
558 toolbar.hide()
559 pass
560
561 - def view_zoomIn(self, widget, data = None):
562 if self.getController().hasMap() == False:
563 return
564 self.mapGrid.zoomIn()
565 self.setTitle()
566
567 - def view_zoomOut(self, widget, data = None):
568 if self.getController().hasMap() == False:
569 return
570 self.mapGrid.zoomOut()
571 self.setTitle()
572
573 - def view_zoomNormal(self, widget, data = None):
574 if self.getController().hasMap() == False:
575 return
576 self.mapGrid.zoomNormal()
577 self.setTitle()
578
579 - def help_about(self, window, data = None):
580 """
581 The answer to the age-old question "Who's responsible for this mess?"
582 """
583 dialog = gtk.AboutDialog()
584 dialog.set_license("""
585 MapEditor is free software: you can redistribute it and/or modify
586 it under the terms of the GNU General Public License as published by
587 the Free Software Foundation, either version 3 of the License, or
588 (at your option) any later version.
589
590 MapEditor is distributed in the hope that it will be useful,
591 but WITHOUT ANY WARRANTY; without even the implied warranty of
592 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
593 GNU General Public License for more details.
594
595 You should have received a copy of the GNU General Public License
596 along with """ + programName + ". If not, see <http://www.gnu.org/licenses/>.")
597 dialog.set_authors(["Brian Schott (Sir Alaran)"])
598 dialog.set_documenters(["Brian Schott (Sir Alaran)"])
599 dialog.set_website("http://www.hackerpilot.org/map_editor.php")
600 dialog.set_program_name(programName)
601 dialog.set_version("0.3 Beta")
602 dialog.run()
603 dialog.destroy()
604
606 """ Handles tool selection on the map toolbar """
607 self.mapGrid.toolSelect(action.get_current_value())
608
609 - def addLayer(self, window, data = None):
610 """ Adds a layer to the map """
611 self.layerView.addLayer()
612
613 - def removeLayer(self, window, data = None):
614 """ Removes a layer from the map """
615 self.layerView.removeLayer()
616
617 - def raiseLayer(self, window, data = None):
618 """ Raises the currently selected layer in the map """
619 self.layerView.raiseLayer()
620
621 - def lowerLayer(self, window, data = None):
622 """ Lowers the currently selected layer in the map """
623 self.layerView.lowerLayer()
624
625 - def keyPress(self, window, event):
626 if self.mapGrid != None:
627 self.mapGrid.keyPress(window, event)
628
629 - def listenModified(self, modified):
631
633 for w in self.widgets:
634 self.uimanager.get_widget(w).set_sensitive(False)
635
637 self.__setWidgetsInsensitive()
638 self.setTitle()
639 self.mapScrolledWindow.remove(self.mapScrolledWindow.get_child())
640 self.setTitle()
641
643
644 for w in self.widgets:
645 self.uimanager.get_widget(w).set_sensitive(True)
646
647
648 self.uimanager.get_action("/MenuBar/Edit/Undo").set_sensitive(
649 False)
650 self.uimanager.get_action("/MenuBar/Edit/Redo").set_sensitive(
651 False)
652
653 self.mapGrid = mapgrid.MapGrid(self.getController(), self.statusBar)
654 self.mapScrolledWindow.add_with_viewport(self.mapGrid.eventBox)
655 self.setTitle()
656
657 - def listenUndoRedo(self):
658 self.uimanager.get_action("/MenuBar/Edit/Undo").set_sensitive(
659 undo.canUndo())
660 self.uimanager.get_action("/MenuBar/Edit/Redo").set_sensitive(
661 undo.canRedo())
662