]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/tools/XRCed/xrced.py
warning fix
[wxWidgets.git] / wxPython / wx / tools / XRCed / xrced.py
1 # Name: xrced.py
2 # Purpose: XRC editor, main module
3 # Author: Roman Rolinsky <rolinsky@mema.ucl.ac.be>
4 # Created: 20.08.2001
5 # RCS-ID: $Id$
6
7 """
8
9 xrced -- Simple resource editor for XRC format used by wxWindows/wxPython
10 GUI toolkit.
11
12 Usage:
13
14 xrced [ -h ] [ -v ] [ XRC-file ]
15
16 Options:
17
18 -h output short usage info and exit
19
20 -v output version info and exit
21 """
22
23
24 from globals import *
25 import os, sys, getopt, re, traceback
26
27 # Local modules
28 from tree import * # imports xxx which imports params
29 from panel import *
30 from tools import *
31 # Cleanup recursive import sideeffects, otherwise we can't create undoMan
32 import undo
33 undo.ParamPage = ParamPage
34 undoMan = g.undoMan = UndoManager()
35
36 # Set application path for loading resources
37 if __name__ == '__main__':
38 basePath = os.path.dirname(sys.argv[0])
39 else:
40 basePath = os.path.dirname(__file__)
41
42 # 1 adds CMD command to Help menu
43 debug = 0
44
45 g.helpText = """\
46 <HTML><H2>Welcome to XRC<font color="blue">ed</font></H2><H3><font color="green">DON'T PANIC :)</font></H3>
47 Read this note before clicking on anything!<P>
48 To start select tree root, then popup menu with your right mouse button,
49 select "Append Child", and then any command.<P>
50 Or just press one of the buttons on the tools palette.<P>
51 Enter XML ID, change properties, create children.<P>
52 To test your interface select Test command (View menu).<P>
53 Consult README file for the details.</HTML>
54 """
55
56 defaultIDs = {xxxPanel:'PANEL', xxxDialog:'DIALOG', xxxFrame:'FRAME',
57 xxxMenuBar:'MENUBAR', xxxMenu:'MENU', xxxToolBar:'TOOLBAR'}
58
59 ################################################################################
60
61 # ScrolledMessageDialog - modified from wxPython lib to set fixed-width font
62 class ScrolledMessageDialog(wxDialog):
63 def __init__(self, parent, msg, caption, pos = wxDefaultPosition, size = (500,300)):
64 from wxPython.lib.layoutf import Layoutf
65 wxDialog.__init__(self, parent, -1, caption, pos, size)
66 text = wxTextCtrl(self, -1, msg, wxDefaultPosition,
67 wxDefaultSize, wxTE_MULTILINE | wxTE_READONLY)
68 text.SetFont(g.modernFont())
69 dc = wxWindowDC(text)
70 # !!! possible bug - GetTextExtent without font returns sysfont dims
71 w, h = dc.GetFullTextExtent(' ', g.modernFont())[:2]
72 ok = wxButton(self, wxID_OK, "OK")
73 text.SetConstraints(Layoutf('t=t5#1;b=t5#2;l=l5#1;r=r5#1', (self,ok)))
74 text.SetSize((w * 80 + 30, h * 40))
75 ok.SetConstraints(Layoutf('b=b5#1;x%w50#1;w!80;h!25', (self,)))
76 self.SetAutoLayout(True)
77 self.Fit()
78 self.CenterOnScreen(wxBOTH)
79
80 ################################################################################
81
82 # Event handler for using during location
83 class Locator(wxEvtHandler):
84 def ProcessEvent(self, evt):
85 print evt
86
87 class Frame(wxFrame):
88 def __init__(self, pos, size):
89 wxFrame.__init__(self, None, -1, '', pos, size)
90 global frame
91 frame = g.frame = self
92 bar = self.CreateStatusBar(2)
93 bar.SetStatusWidths([-1, 40])
94 self.SetIcon(images.getIconIcon())
95
96 # Idle flag
97 self.inIdle = False
98
99 # Make menus
100 menuBar = wxMenuBar()
101
102 menu = wxMenu()
103 menu.Append(wxID_NEW, '&New\tCtrl-N', 'New file')
104 menu.AppendSeparator()
105 menu.Append(wxID_OPEN, '&Open...\tCtrl-O', 'Open XRC file')
106 self.recentMenu = wxMenu()
107 self.AppendRecent(self.recentMenu)
108 menu.AppendMenu(-1, 'Open Recent', self.recentMenu, 'Open a recent file')
109 menu.AppendSeparator()
110 menu.Append(wxID_SAVE, '&Save\tCtrl-S', 'Save XRC file')
111 menu.Append(wxID_SAVEAS, 'Save &As...', 'Save XRC file under different name')
112 menu.AppendSeparator()
113 menu.Append(wxID_EXIT, '&Quit\tCtrl-Q', 'Exit application')
114
115 menuBar.Append(menu, '&File')
116
117 menu = wxMenu()
118 menu.Append(wxID_UNDO, '&Undo\tCtrl-Z', 'Undo')
119 menu.Append(wxID_REDO, '&Redo\tCtrl-Y', 'Redo')
120 menu.AppendSeparator()
121 menu.Append(wxID_CUT, 'Cut\tCtrl-X', 'Cut to the clipboard')
122 menu.Append(wxID_COPY, '&Copy\tCtrl-C', 'Copy to the clipboard')
123 menu.Append(wxID_PASTE, '&Paste\tCtrl-V', 'Paste from the clipboard')
124 self.ID_DELETE = wxNewId()
125 menu.Append(self.ID_DELETE, '&Delete\tCtrl-D', 'Delete object')
126 # menu.AppendSeparator()
127 self.ID_LOCATE = wxNewId()
128 menu.Append(self.ID_LOCATE, '&Locate\tCtrl-L', 'Locate control in test window and select it')
129 menuBar.Append(menu, '&Edit')
130
131 menu = wxMenu()
132 self.ID_EMBED_PANEL = wxNewId()
133 menu.Append(self.ID_EMBED_PANEL, '&Embed Panel',
134 'Toggle embedding properties panel in the main window', True)
135 menu.Check(self.ID_EMBED_PANEL, conf.embedPanel)
136 self.ID_SHOW_TOOLS = wxNewId()
137 menu.Append(self.ID_SHOW_TOOLS, 'Show &Tools', 'Toggle tools', True)
138 menu.Check(self.ID_SHOW_TOOLS, conf.showTools)
139 menu.AppendSeparator()
140 self.ID_TEST = wxNewId()
141 menu.Append(self.ID_TEST, '&Test\tF5', 'Show test window')
142 self.ID_REFRESH = wxNewId()
143 menu.Append(self.ID_REFRESH, '&Refresh\tCtrl-R', 'Refresh test window')
144 self.ID_AUTO_REFRESH = wxNewId()
145 menu.Append(self.ID_AUTO_REFRESH, '&Auto-refresh\tCtrl-A',
146 'Toggle auto-refresh mode', True)
147 menu.Check(self.ID_AUTO_REFRESH, conf.autoRefresh)
148 menuBar.Append(menu, '&View')
149
150 menu = wxMenu()
151 menu.Append(wxID_ABOUT, '&About...', 'About XCRed')
152 self.ID_README = wxNewId()
153 menu.Append(self.ID_README, '&Readme...', 'View the README file')
154 if debug:
155 self.ID_DEBUG_CMD = wxNewId()
156 menu.Append(self.ID_DEBUG_CMD, 'CMD', 'Python command line')
157 EVT_MENU(self, self.ID_DEBUG_CMD, self.OnDebugCMD)
158 menuBar.Append(menu, '&Help')
159
160 self.menuBar = menuBar
161 self.SetMenuBar(menuBar)
162
163 # Create toolbar
164 tb = self.CreateToolBar(wxTB_HORIZONTAL | wxNO_BORDER | wxTB_FLAT)
165 tb.SetToolBitmapSize((24, 23))
166 tb.AddSimpleTool(wxID_NEW, images.getNewBitmap(), 'New', 'New file')
167 tb.AddSimpleTool(wxID_OPEN, images.getOpenBitmap(), 'Open', 'Open file')
168 tb.AddSimpleTool(wxID_SAVE, images.getSaveBitmap(), 'Save', 'Save file')
169 tb.AddControl(wxStaticLine(tb, -1, size=(-1,23), style=wxLI_VERTICAL))
170 tb.AddSimpleTool(wxID_UNDO, images.getUndoBitmap(), 'Undo', 'Undo')
171 tb.AddSimpleTool(wxID_REDO, images.getRedoBitmap(), 'Redo', 'Redo')
172 tb.AddControl(wxStaticLine(tb, -1, size=(-1,23), style=wxLI_VERTICAL))
173 tb.AddSimpleTool(wxID_CUT, images.getCutBitmap(), 'Cut', 'Cut')
174 tb.AddSimpleTool(wxID_COPY, images.getCopyBitmap(), 'Copy', 'Copy')
175 tb.AddSimpleTool(wxID_PASTE, images.getPasteBitmap(), 'Paste', 'Paste')
176 tb.AddControl(wxStaticLine(tb, -1, size=(-1,23), style=wxLI_VERTICAL))
177 tb.AddSimpleTool(self.ID_TEST, images.getTestBitmap(), 'Test', 'Test window')
178 tb.AddSimpleTool(self.ID_REFRESH, images.getRefreshBitmap(),
179 'Refresh', 'Refresh view')
180 tb.AddSimpleTool(self.ID_AUTO_REFRESH, images.getAutoRefreshBitmap(),
181 'Auto-refresh', 'Toggle auto-refresh mode', True)
182 if wxPlatform == '__WXGTK__':
183 tb.AddSeparator() # otherwise auto-refresh sticks in status line
184 tb.ToggleTool(self.ID_AUTO_REFRESH, conf.autoRefresh)
185 tb.Realize()
186 self.tb = tb
187 self.minWidth = tb.GetSize()[0] # minimal width is the size of toolbar
188
189 # File
190 EVT_MENU(self, wxID_NEW, self.OnNew)
191 EVT_MENU(self, wxID_OPEN, self.OnOpen)
192 EVT_MENU(self, wxID_SAVE, self.OnSaveOrSaveAs)
193 EVT_MENU(self, wxID_SAVEAS, self.OnSaveOrSaveAs)
194 EVT_MENU(self, wxID_EXIT, self.OnExit)
195 # Edit
196 EVT_MENU(self, wxID_UNDO, self.OnUndo)
197 EVT_MENU(self, wxID_REDO, self.OnRedo)
198 EVT_MENU(self, wxID_CUT, self.OnCutDelete)
199 EVT_MENU(self, wxID_COPY, self.OnCopy)
200 EVT_MENU(self, wxID_PASTE, self.OnPaste)
201 EVT_MENU(self, self.ID_DELETE, self.OnCutDelete)
202 EVT_MENU(self, self.ID_LOCATE, self.OnLocate)
203 # View
204 EVT_MENU(self, self.ID_EMBED_PANEL, self.OnEmbedPanel)
205 EVT_MENU(self, self.ID_SHOW_TOOLS, self.OnShowTools)
206 EVT_MENU(self, self.ID_TEST, self.OnTest)
207 EVT_MENU(self, self.ID_REFRESH, self.OnRefresh)
208 EVT_MENU(self, self.ID_AUTO_REFRESH, self.OnAutoRefresh)
209 # Help
210 EVT_MENU(self, wxID_ABOUT, self.OnAbout)
211 EVT_MENU(self, self.ID_README, self.OnReadme)
212
213 # Update events
214 EVT_UPDATE_UI(self, wxID_CUT, self.OnUpdateUI)
215 EVT_UPDATE_UI(self, wxID_COPY, self.OnUpdateUI)
216 EVT_UPDATE_UI(self, wxID_PASTE, self.OnUpdateUI)
217 EVT_UPDATE_UI(self, wxID_UNDO, self.OnUpdateUI)
218 EVT_UPDATE_UI(self, wxID_REDO, self.OnUpdateUI)
219 EVT_UPDATE_UI(self, self.ID_DELETE, self.OnUpdateUI)
220 EVT_UPDATE_UI(self, self.ID_TEST, self.OnUpdateUI)
221 EVT_UPDATE_UI(self, self.ID_REFRESH, self.OnUpdateUI)
222
223 # Build interface
224 sizer = wxBoxSizer(wxVERTICAL)
225 sizer.Add(wxStaticLine(self, -1), 0, wxEXPAND)
226 # Horizontal sizer for toolbar and splitter
227 self.toolsSizer = sizer1 = wxBoxSizer()
228 splitter = wxSplitterWindow(self, -1, style=wxSP_3DSASH)
229 self.splitter = splitter
230 splitter.SetMinimumPaneSize(100)
231 # Create tree
232 global tree
233 g.tree = tree = XML_Tree(splitter, -1)
234
235 # Init pull-down menu data
236 global pullDownMenu
237 g.pullDownMenu = pullDownMenu = PullDownMenu(self)
238
239 # Vertical toolbar for GUI buttons
240 g.tools = tools = Tools(self)
241 tools.Show(conf.showTools)
242 if conf.showTools: sizer1.Add(tools, 0, wxEXPAND)
243
244 tree.RegisterKeyEvents()
245
246 # !!! frame styles are broken
247 # Miniframe for not embedded mode
248 miniFrame = wxFrame(self, -1, 'Properties Panel',
249 (conf.panelX, conf.panelY),
250 (conf.panelWidth, conf.panelHeight))
251 self.miniFrame = miniFrame
252 sizer2 = wxBoxSizer()
253 miniFrame.SetAutoLayout(True)
254 miniFrame.SetSizer(sizer2)
255 EVT_CLOSE(self.miniFrame, self.OnCloseMiniFrame)
256 # Create panel for parameters
257 global panel
258 if conf.embedPanel:
259 panel = Panel(splitter)
260 # Set plitter windows
261 splitter.SplitVertically(tree, panel, conf.sashPos)
262 else:
263 panel = Panel(miniFrame)
264 sizer2.Add(panel, 1, wxEXPAND)
265 miniFrame.Show(True)
266 splitter.Initialize(tree)
267 sizer1.Add(splitter, 1, wxEXPAND)
268 sizer.Add(sizer1, 1, wxEXPAND)
269 self.SetAutoLayout(True)
270 self.SetSizer(sizer)
271
272 # Initialize
273 self.clipboard = None
274 self.Clear()
275
276 # Other events
277 EVT_IDLE(self, self.OnIdle)
278 EVT_CLOSE(self, self.OnCloseWindow)
279 EVT_KEY_DOWN(self, tools.OnKeyDown)
280 EVT_KEY_UP(self, tools.OnKeyUp)
281
282 def AppendRecent(self, menu):
283 # add recently used files to the menu
284 for id,name in conf.recentfiles.iteritems():
285 menu.Append(id,name)
286 EVT_MENU(self,id,self.OnRecentFile)
287 return
288
289 def OnRecentFile(self,evt):
290 # open recently used file
291 if not self.AskSave(): return
292 wxBeginBusyCursor()
293 try:
294 path=conf.recentfiles[evt.GetId()]
295 if self.Open(path):
296 self.SetStatusText('Data loaded')
297 else:
298 self.SetStatusText('Failed')
299 except KeyError:
300 self.SetStatusText('No such file')
301 wxEndBusyCursor()
302
303 def OnNew(self, evt):
304 if not self.AskSave(): return
305 self.Clear()
306
307 def OnOpen(self, evt):
308 if not self.AskSave(): return
309 dlg = wxFileDialog(self, 'Open', os.path.dirname(self.dataFile),
310 '', '*.xrc', wxOPEN | wxCHANGE_DIR)
311 if dlg.ShowModal() == wxID_OK:
312 path = dlg.GetPath()
313 self.SetStatusText('Loading...')
314 wxYield()
315 wxBeginBusyCursor()
316 if self.Open(path):
317 self.SetStatusText('Data loaded')
318 else:
319 self.SetStatusText('Failed')
320 self.SaveRecent(path)
321 wxEndBusyCursor()
322 dlg.Destroy()
323
324 def OnSaveOrSaveAs(self, evt):
325 if evt.GetId() == wxID_SAVEAS or not self.dataFile:
326 if self.dataFile: defaultName = ''
327 else: defaultName = 'UNTITLED.xrc'
328 dirname = os.path.dirname(self.dataFile)
329 dlg = wxFileDialog(self, 'Save As', dirname, defaultName, '*.xrc',
330 wxSAVE | wxOVERWRITE_PROMPT | wxCHANGE_DIR)
331 if dlg.ShowModal() == wxID_OK:
332 path = dlg.GetPath()
333 dlg.Destroy()
334 else:
335 dlg.Destroy()
336 return
337 else:
338 path = self.dataFile
339 self.SetStatusText('Saving...')
340 wxYield()
341 wxBeginBusyCursor()
342 try:
343 self.Save(path)
344 self.dataFile = path
345 self.SetStatusText('Data saved')
346 self.SaveRecent(path)
347 except IOError:
348 self.SetStatusText('Failed')
349 wxEndBusyCursor()
350
351 def SaveRecent(self,path):
352 # append to recently used files
353 if path not in conf.recentfiles.values():
354 newid = wxNewId()
355 self.recentMenu.Append(newid, path)
356 EVT_MENU(self, newid, self.OnRecentFile)
357 conf.recentfiles[newid] = path
358
359 def OnExit(self, evt):
360 self.Close()
361
362 def OnUndo(self, evt):
363 # Extra check to not mess with idle updating
364 if undoMan.CanUndo():
365 undoMan.Undo()
366
367 def OnRedo(self, evt):
368 if undoMan.CanRedo():
369 undoMan.Redo()
370
371 def OnCopy(self, evt):
372 selected = tree.selection
373 if not selected: return # key pressed event
374 xxx = tree.GetPyData(selected)
375 self.clipboard = xxx.element.cloneNode(True)
376 self.SetStatusText('Copied')
377
378 def OnPaste(self, evt):
379 selected = tree.selection
380 if not selected: return # key pressed event
381 # For pasting with Ctrl pressed
382 if evt.GetId() == pullDownMenu.ID_PASTE_SIBLING: appendChild = False
383 else: appendChild = not tree.NeedInsert(selected)
384 xxx = tree.GetPyData(selected)
385 if not appendChild:
386 # If has next item, insert, else append to parent
387 nextItem = tree.GetNextSibling(selected)
388 parentLeaf = tree.GetItemParent(selected)
389 # Expanded container (must have children)
390 elif tree.IsExpanded(selected) and tree.GetChildrenCount(selected, False):
391 # Insert as first child
392 nextItem = tree.GetFirstChild(selected)[0]
393 parentLeaf = selected
394 else:
395 # No children or unexpanded item - appendChild stays True
396 nextItem = wxTreeItemId() # no next item
397 parentLeaf = selected
398 parent = tree.GetPyData(parentLeaf).treeObject()
399
400 # Create a copy of clipboard element
401 elem = self.clipboard.cloneNode(True)
402 # Tempopary xxx object to test things
403 xxx = MakeXXXFromDOM(parent, elem)
404
405 # Check compatibility
406 error = False
407 # Top-level
408 x = xxx.treeObject()
409 if x.__class__ in [xxxDialog, xxxFrame, xxxMenuBar]:
410 # Top-level classes
411 if parent.__class__ != xxxMainNode: error = True
412 elif x.__class__ == xxxToolBar:
413 # Toolbar can be top-level of child of panel or frame
414 if parent.__class__ not in [xxxMainNode, xxxPanel, xxxFrame]: error = True
415 elif x.__class__ == xxxPanel and parent.__class__ == xxxMainNode:
416 pass
417 elif x.__class__ == xxxSpacer:
418 if not parent.isSizer: error = True
419 elif x.__class__ == xxxSeparator:
420 if not parent.__class__ in [xxxMenu, xxxToolBar]: error = True
421 elif x.__class__ == xxxTool:
422 if parent.__class__ != xxxToolBar: error = True
423 elif x.__class__ == xxxMenu:
424 if not parent.__class__ in [xxxMainNode, xxxMenuBar, xxxMenu]: error = True
425 elif x.__class__ == xxxMenuItem:
426 if not parent.__class__ in [xxxMenuBar, xxxMenu]: error = True
427 elif x.isSizer and parent.__class__ == xxxNotebook: error = True
428 else: # normal controls can be almost anywhere
429 if parent.__class__ == xxxMainNode or \
430 parent.__class__ in [xxxMenuBar, xxxMenu]: error = True
431 if error:
432 if parent.__class__ == xxxMainNode: parentClass = 'root'
433 else: parentClass = parent.className
434 wxLogError('Incompatible parent/child: parent is %s, child is %s!' %
435 (parentClass, x.className))
436 return
437
438 # Check parent and child relationships.
439 # If parent is sizer or notebook, child is of wrong class or
440 # parent is normal window, child is child container then detach child.
441 isChildContainer = isinstance(xxx, xxxChildContainer)
442 if isChildContainer and \
443 ((parent.isSizer and not isinstance(xxx, xxxSizerItem)) or \
444 (isinstance(parent, xxxNotebook) and not isinstance(xxx, xxxNotebookPage)) or \
445 not (parent.isSizer or isinstance(parent, xxxNotebook))):
446 elem.removeChild(xxx.child.element) # detach child
447 elem.unlink() # delete child container
448 elem = xxx.child.element # replace
449 # This may help garbage collection
450 xxx.child.parent = None
451 isChildContainer = False
452 # Parent is sizer or notebook, child is not child container
453 if parent.isSizer and not isChildContainer and not isinstance(xxx, xxxSpacer):
454 # Create sizer item element
455 sizerItemElem = MakeEmptyDOM('sizeritem')
456 sizerItemElem.appendChild(elem)
457 elem = sizerItemElem
458 elif isinstance(parent, xxxNotebook) and not isChildContainer:
459 pageElem = MakeEmptyDOM('notebookpage')
460 pageElem.appendChild(elem)
461 elem = pageElem
462 # Insert new node, register undo
463 newItem = tree.InsertNode(parentLeaf, parent, elem, nextItem)
464 undoMan.RegisterUndo(UndoPasteCreate(parentLeaf, parent, newItem, selected))
465 # Scroll to show new item (!!! redundant?)
466 tree.EnsureVisible(newItem)
467 tree.SelectItem(newItem)
468 if not tree.IsVisible(newItem):
469 tree.ScrollTo(newItem)
470 tree.Refresh()
471 # Update view?
472 if g.testWin and tree.IsHighlatable(newItem):
473 if conf.autoRefresh:
474 tree.needUpdate = True
475 tree.pendingHighLight = newItem
476 else:
477 tree.pendingHighLight = None
478 self.modified = True
479 self.SetStatusText('Pasted')
480
481 def OnCutDelete(self, evt):
482 selected = tree.selection
483 if not selected: return # key pressed event
484 # Undo info
485 if evt.GetId() == wxID_CUT:
486 self.lastOp = 'CUT'
487 status = 'Removed to clipboard'
488 else:
489 self.lastOp = 'DELETE'
490 status = 'Deleted'
491 # Delete testWin?
492 if g.testWin:
493 # If deleting top-level item, delete testWin
494 if selected == g.testWin.item:
495 g.testWin.Destroy()
496 g.testWin = None
497 else:
498 # Remove highlight, update testWin
499 if g.testWin.highLight:
500 g.testWin.highLight.Remove()
501 tree.needUpdate = True
502 # Prepare undo data
503 panel.Apply()
504 index = tree.ItemFullIndex(selected)
505 parent = tree.GetPyData(tree.GetItemParent(selected)).treeObject()
506 elem = tree.RemoveLeaf(selected)
507 undoMan.RegisterUndo(UndoCutDelete(index, parent, elem))
508 if evt.GetId() == wxID_CUT:
509 if self.clipboard: self.clipboard.unlink()
510 self.clipboard = elem.cloneNode(True)
511 tree.pendingHighLight = None
512 tree.Unselect()
513 panel.Clear()
514 self.modified = True
515 self.SetStatusText(status)
516
517 def OnSubclass(self, evt):
518 selected = tree.selection
519 xxx = tree.GetPyData(selected).treeObject()
520 elem = xxx.element
521 subclass = xxx.subclass
522 dlg = wxTextEntryDialog(self, 'Subclass:', defaultValue=subclass)
523 if dlg.ShowModal() == wxID_OK:
524 subclass = dlg.GetValue()
525 if subclass:
526 elem.setAttribute('subclass', subclass)
527 self.modified = True
528 elif elem.hasAttribute('subclass'):
529 elem.removeAttribute('subclass')
530 self.modified = True
531 xxx.subclass = elem.getAttribute('subclass')
532 tree.SetItemText(selected, xxx.treeName())
533 panel.pages[0].box.SetLabel(xxx.panelName())
534 dlg.Destroy()
535
536 def OnEmbedPanel(self, evt):
537 conf.embedPanel = evt.IsChecked()
538 if conf.embedPanel:
539 # Remember last dimentions
540 conf.panelX, conf.panelY = self.miniFrame.GetPosition()
541 conf.panelWidth, conf.panelHeight = self.miniFrame.GetSize()
542 size = self.GetSize()
543 pos = self.GetPosition()
544 sizePanel = panel.GetSize()
545 panel.Reparent(self.splitter)
546 self.miniFrame.GetSizer().Remove(panel)
547 wxYield()
548 # Widen
549 self.SetDimensions(pos.x, pos.y, size.width + sizePanel.width, size.height)
550 self.splitter.SplitVertically(tree, panel, conf.sashPos)
551 self.miniFrame.Show(False)
552 else:
553 conf.sashPos = self.splitter.GetSashPosition()
554 pos = self.GetPosition()
555 size = self.GetSize()
556 sizePanel = panel.GetSize()
557 self.splitter.Unsplit(panel)
558 sizer = self.miniFrame.GetSizer()
559 panel.Reparent(self.miniFrame)
560 panel.Show(True)
561 sizer.Add(panel, 1, wxEXPAND)
562 self.miniFrame.Show(True)
563 self.miniFrame.SetDimensions(conf.panelX, conf.panelY,
564 conf.panelWidth, conf.panelHeight)
565 wxYield()
566 # Reduce width
567 self.SetDimensions(pos.x, pos.y,
568 max(size.width - sizePanel.width, self.minWidth), size.height)
569
570 def OnShowTools(self, evt):
571 conf.showTools = evt.IsChecked()
572 g.tools.Show(conf.showTools)
573 if conf.showTools:
574 self.toolsSizer.Prepend(g.tools, 0, wxEXPAND)
575 else:
576 self.toolsSizer.Remove(g.tools)
577 self.toolsSizer.Layout()
578
579 def OnTest(self, evt):
580 if not tree.selection: return # key pressed event
581 tree.ShowTestWindow(tree.selection)
582
583 # Find object by relative position
584 def FindObject(self, item, obj):
585 # We simply perform depth-first traversal, sinse it's too much
586 # hassle to deal with all sizer/window combinations
587 w = tree.FindNodeObject(item)
588 if w == obj:
589 return item
590 if tree.ItemHasChildren(item):
591 child = tree.GetFirstChild(item)[0]
592 while child:
593 found = self.FindObject(child, obj)
594 if found: return found
595 child = tree.GetNextSibling(child)
596 return None
597
598 def OnTestWinLeftDown(self, evt):
599 pos = evt.GetPosition()
600 self.SetHandler(g.testWin)
601 g.testWin.Disconnect(wxID_ANY, wxID_ANY, wxEVT_LEFT_DOWN)
602 item = self.FindObject(g.testWin.item, evt.GetEventObject())
603 if item:
604 tree.SelectItem(item)
605
606 def SetHandler(self, w, h=None):
607 if h:
608 w.SetEventHandler(h)
609 w.SetCursor(wxCROSS_CURSOR)
610 else:
611 w.SetEventHandler(w)
612 w.SetCursor(wxNullCursor)
613 for ch in w.GetChildren():
614 self.SetHandler(ch, h)
615
616 def OnLocate(self, evt):
617 if g.testWin:
618 self.SetHandler(g.testWin, g.testWin)
619 g.testWin.Connect(wxID_ANY, wxID_ANY, wxEVT_LEFT_DOWN, self.OnTestWinLeftDown)
620
621 def OnRefresh(self, evt):
622 # If modified, apply first
623 selection = tree.selection
624 if selection:
625 xxx = tree.GetPyData(selection)
626 if xxx and panel.IsModified():
627 tree.Apply(xxx, selection)
628 if g.testWin:
629 # (re)create
630 tree.CreateTestWin(g.testWin.item)
631 panel.modified = False
632 tree.needUpdate = False
633
634 def OnAutoRefresh(self, evt):
635 conf.autoRefresh = evt.IsChecked()
636 self.menuBar.Check(self.ID_AUTO_REFRESH, conf.autoRefresh)
637 self.tb.ToggleTool(self.ID_AUTO_REFRESH, conf.autoRefresh)
638
639 def OnAbout(self, evt):
640 str = '''\
641 XRCed version %s
642
643 (c) Roman Rolinsky <rollrom@users.sourceforge.net>
644 Homepage: http://xrced.sourceforge.net\
645 ''' % version
646 dlg = wxMessageDialog(self, str, 'About XRCed', wxOK | wxCENTRE)
647 dlg.ShowModal()
648 dlg.Destroy()
649
650 def OnReadme(self, evt):
651 text = open(os.path.join(basePath, 'README.txt'), 'r').read()
652 dlg = ScrolledMessageDialog(self, text, "XRCed README")
653 dlg.ShowModal()
654 dlg.Destroy()
655
656 # Simple emulation of python command line
657 def OnDebugCMD(self, evt):
658 import traceback
659 while 1:
660 try:
661 exec raw_input('C:\> ')
662 except EOFError:
663 print '^D'
664 break
665 except:
666 (etype, value, tb) =sys.exc_info()
667 tblist =traceback.extract_tb(tb)[1:]
668 msg =' '.join(traceback.format_exception_only(etype, value)
669 +traceback.format_list(tblist))
670 print msg
671
672 def OnCreate(self, evt):
673 selected = tree.selection
674 if tree.ctrl: appendChild = False
675 else: appendChild = not tree.NeedInsert(selected)
676 xxx = tree.GetPyData(selected)
677 if not appendChild:
678 # If insert before
679 if tree.shift:
680 # If has previous item, insert after it, else append to parent
681 nextItem = selected
682 parentLeaf = tree.GetItemParent(selected)
683 else:
684 # If has next item, insert, else append to parent
685 nextItem = tree.GetNextSibling(selected)
686 parentLeaf = tree.GetItemParent(selected)
687 # Expanded container (must have children)
688 elif tree.shift and tree.IsExpanded(selected) \
689 and tree.GetChildrenCount(selected, False):
690 nextItem = tree.GetFirstChild(selected)[0]
691 parentLeaf = selected
692 else:
693 nextItem = wxTreeItemId()
694 parentLeaf = selected
695 parent = tree.GetPyData(parentLeaf)
696 if parent.hasChild: parent = parent.child
697
698 # Create element
699 className = pullDownMenu.createMap[evt.GetId()]
700 xxx = MakeEmptyXXX(parent, className)
701
702 # Set default name for top-level windows
703 if parent.__class__ == xxxMainNode:
704 cl = xxx.treeObject().__class__
705 frame.maxIDs[cl] += 1
706 xxx.treeObject().name = '%s%d' % (defaultIDs[cl], frame.maxIDs[cl])
707 xxx.treeObject().element.setAttribute('name', xxx.treeObject().name)
708
709 # Insert new node, register undo
710 elem = xxx.element
711 newItem = tree.InsertNode(parentLeaf, parent, elem, nextItem)
712 undoMan.RegisterUndo(UndoPasteCreate(parentLeaf, parent, newItem, selected))
713 tree.EnsureVisible(newItem)
714 tree.SelectItem(newItem)
715 if not tree.IsVisible(newItem):
716 tree.ScrollTo(newItem)
717 tree.Refresh()
718 # Update view?
719 if g.testWin and tree.IsHighlatable(newItem):
720 if conf.autoRefresh:
721 tree.needUpdate = True
722 tree.pendingHighLight = newItem
723 else:
724 tree.pendingHighLight = None
725 tree.SetFocus()
726 self.modified = True
727
728 # Replace one object with another
729 def OnReplace(self, evt):
730 selected = tree.selection
731 xxx = tree.GetPyData(selected).treeObject()
732 elem = xxx.element
733 parent = elem.parentNode
734 parentXXX = xxx.parent
735 # New class
736 className = pullDownMenu.createMap[evt.GetId() - 1000]
737 # Create temporary empty node (with default values)
738 dummy = MakeEmptyDOM(className)
739 xxxClass = xxxDict[className]
740 # Remove non-compatible children
741 if tree.ItemHasChildren(selected) and not xxxClass.hasChildren:
742 tree.DeleteChildren(selected)
743 nodes = elem.childNodes[:]
744 tags = []
745 for node in nodes:
746 remove = False
747 tag = node.tagName
748 if tag == 'object':
749 if not xxxClass.hasChildren:
750 remove = True
751 elif tag not in xxxClass.allParams and \
752 (not xxxClass.hasStyle or tag not in xxxClass.styles):
753 remove = True
754 else:
755 tags.append(tag)
756 if remove:
757 elem.removeChild(node)
758 node.unlink()
759
760 # Copy parameters present in dummy but not in elem
761 for node in dummy.childNodes:
762 tag = node.tagName
763 if tag not in tags:
764 elem.appendChild(node.cloneNode(True))
765 dummy.unlink()
766 # Change class name
767 elem.setAttribute('class', className)
768 # Re-create xxx element
769 xxx = MakeXXXFromDOM(parentXXX, elem)
770 # Update parent in child objects
771 if tree.ItemHasChildren(selected):
772 i, cookie = tree.GetFirstChild(selected)
773 while i.IsOk():
774 x = tree.GetPyData(i)
775 x.parent = xxx
776 if x.hasChild: x.child.parent = xxx
777 i, cookie = tree.GetNextChild(selected, cookie)
778
779 # Update tree
780 if tree.GetPyData(selected).hasChild: # child container
781 container = tree.GetPyData(selected)
782 container.child = xxx
783 container.hasChildren = xxx.hasChildren
784 container.isSizer = xxx.isSizer
785 else:
786 tree.SetPyData(selected, xxx)
787 tree.SetItemText(selected, xxx.treeName())
788 tree.SetItemImage(selected, xxx.treeImage())
789
790 # Set default name for top-level windows
791 if parent.__class__ == xxxMainNode:
792 cl = xxx.treeObject().__class__
793 frame.maxIDs[cl] += 1
794 xxx.treeObject().name = '%s%d' % (defaultIDs[cl], frame.maxIDs[cl])
795 xxx.treeObject().element.setAttribute('name', xxx.treeObject().name)
796
797 # Update panel
798 g.panel.SetData(xxx)
799 # Update tools
800 g.tools.UpdateUI()
801
802 #undoMan.RegisterUndo(UndoPasteCreate(parentLeaf, parent, newItem, selected))
803 # Update view?
804 if g.testWin and tree.IsHighlatable(selected):
805 if conf.autoRefresh:
806 tree.needUpdate = True
807 tree.pendingHighLight = selected
808 else:
809 tree.pendingHighLight = None
810 tree.SetFocus()
811 self.modified = True
812
813 # Expand/collapse subtree
814 def OnExpand(self, evt):
815 if tree.selection: tree.ExpandAll(tree.selection)
816 else: tree.ExpandAll(tree.root)
817 def OnCollapse(self, evt):
818 if tree.selection: tree.CollapseAll(tree.selection)
819 else: tree.CollapseAll(tree.root)
820
821 def OnPullDownHighlight(self, evt):
822 menuId = evt.GetMenuId()
823 if menuId != -1:
824 menu = evt.GetEventObject()
825 help = menu.GetHelpString(menuId)
826 self.SetStatusText(help)
827 else:
828 self.SetStatusText('')
829
830 def OnUpdateUI(self, evt):
831 if evt.GetId() in [wxID_CUT, wxID_COPY, self.ID_DELETE]:
832 evt.Enable(tree.selection is not None and tree.selection != tree.root)
833 elif evt.GetId() == wxID_PASTE:
834 evt.Enable((self.clipboard and tree.selection) != None)
835 elif evt.GetId() == self.ID_TEST:
836 evt.Enable(tree.selection is not None and tree.selection != tree.root)
837 elif evt.GetId() == wxID_UNDO: evt.Enable(undoMan.CanUndo())
838 elif evt.GetId() == wxID_REDO: evt.Enable(undoMan.CanRedo())
839
840 def OnIdle(self, evt):
841 if self.inIdle: return # Recursive call protection
842 self.inIdle = True
843 if tree.needUpdate:
844 if conf.autoRefresh:
845 if g.testWin:
846 self.SetStatusText('Refreshing test window...')
847 # (re)create
848 tree.CreateTestWin(g.testWin.item)
849 wxYield()
850 self.SetStatusText('')
851 tree.needUpdate = False
852 elif tree.pendingHighLight:
853 tree.HighLight(tree.pendingHighLight)
854 else:
855 evt.Skip()
856 self.inIdle = False
857
858 # We don't let close panel window
859 def OnCloseMiniFrame(self, evt):
860 return
861
862 def OnCloseWindow(self, evt):
863 if not self.AskSave(): return
864 if g.testWin: g.testWin.Destroy()
865 if not panel.GetPageCount() == 2:
866 panel.page2.Destroy()
867 else:
868 # If we don't do this, page does not get destroyed (a bug?)
869 panel.RemovePage(1)
870 if not self.IsIconized():
871 conf.x, conf.y = self.GetPosition()
872 conf.width, conf.height = self.GetSize()
873 if conf.embedPanel:
874 conf.sashPos = self.splitter.GetSashPosition()
875 else:
876 conf.panelX, conf.panelY = self.miniFrame.GetPosition()
877 conf.panelWidth, conf.panelHeight = self.miniFrame.GetSize()
878 evt.Skip()
879
880 def Clear(self):
881 self.dataFile = ''
882 if self.clipboard:
883 self.clipboard.unlink()
884 self.clipboard = None
885 undoMan.Clear()
886 self.modified = False
887 tree.Clear()
888 panel.Clear()
889 if g.testWin:
890 g.testWin.Destroy()
891 g.testWin = None
892 self.SetTitle(progname)
893 # Numbers for new controls
894 self.maxIDs = {}
895 self.maxIDs[xxxPanel] = self.maxIDs[xxxDialog] = self.maxIDs[xxxFrame] = \
896 self.maxIDs[xxxMenuBar] = self.maxIDs[xxxMenu] = self.maxIDs[xxxToolBar] = 0
897
898 def Open(self, path):
899 if not os.path.exists(path):
900 wxLogError('File does not exists: %s' % path)
901 return False
902 # Try to read the file
903 try:
904 f = open(path)
905 self.Clear()
906 dom = minidom.parse(f)
907 f.close()
908 # Set encoding global variable
909 if dom.encoding: g.currentEncoding = dom.encoding
910 # Change dir
911 self.dataFile = path = os.path.abspath(path)
912 dir = os.path.dirname(path)
913 if dir: os.chdir(dir)
914 tree.SetData(dom)
915 self.SetTitle(progname + ': ' + os.path.basename(path))
916 except:
917 # Nice exception printing
918 inf = sys.exc_info()
919 wxLogError(traceback.format_exception(inf[0], inf[1], None)[-1])
920 wxLogError('Error reading file: %s' % path)
921 return False
922 return True
923
924 def Indent(self, node, indent = 0):
925 # Copy child list because it will change soon
926 children = node.childNodes[:]
927 # Main node doesn't need to be indented
928 if indent:
929 text = self.domCopy.createTextNode('\n' + ' ' * indent)
930 node.parentNode.insertBefore(text, node)
931 if children:
932 # Append newline after last child, except for text nodes
933 if children[-1].nodeType == minidom.Node.ELEMENT_NODE:
934 text = self.domCopy.createTextNode('\n' + ' ' * indent)
935 node.appendChild(text)
936 # Indent children which are elements
937 for n in children:
938 if n.nodeType == minidom.Node.ELEMENT_NODE:
939 self.Indent(n, indent + 2)
940
941 def Save(self, path):
942 try:
943 import codecs
944 # Apply changes
945 if tree.selection and panel.IsModified():
946 self.OnRefresh(wxCommandEvent())
947 f = codecs.open(path, 'w', g.currentEncoding)
948 # Make temporary copy for formatting it
949 # !!! We can't clone dom node, it works only once
950 #self.domCopy = tree.dom.cloneNode(True)
951 self.domCopy = MyDocument()
952 mainNode = self.domCopy.appendChild(tree.mainNode.cloneNode(True))
953 self.Indent(mainNode)
954 self.domCopy.writexml(f, encoding = g.currentEncoding)
955 f.close()
956 self.domCopy.unlink()
957 self.domCopy = None
958 self.modified = False
959 panel.SetModified(False)
960 except:
961 wxLogError('Error writing file: %s' % path)
962 raise
963
964 def AskSave(self):
965 if not (self.modified or panel.IsModified()): return True
966 flags = wxICON_EXCLAMATION | wxYES_NO | wxCANCEL | wxCENTRE
967 dlg = wxMessageDialog( self, 'File is modified. Save before exit?',
968 'Save before too late?', flags )
969 say = dlg.ShowModal()
970 dlg.Destroy()
971 if say == wxID_YES:
972 self.OnSaveOrSaveAs(wxCommandEvent(wxID_SAVE))
973 # If save was successful, modified flag is unset
974 if not self.modified: return True
975 elif say == wxID_NO:
976 self.modified = False
977 panel.SetModified(False)
978 return True
979 return False
980
981 def SaveUndo(self):
982 pass # !!!
983
984 ################################################################################
985
986 def usage():
987 print >> sys.stderr, 'usage: xrced [-dhiv] [file]'
988
989 class App(wxApp):
990 def OnInit(self):
991 global debug
992 # Process comand-line
993 try:
994 opts = args = None
995 opts, args = getopt.getopt(sys.argv[1:], 'dhiv')
996 for o,a in opts:
997 if o == '-h':
998 usage()
999 sys.exit(0)
1000 elif o == '-d':
1001 debug = True
1002 elif o == '-v':
1003 print 'XRCed version', version
1004 sys.exit(0)
1005
1006 except getopt.GetoptError:
1007 if wxPlatform != '__WXMAC__': # macs have some extra parameters
1008 print >> sys.stderr, 'Unknown option'
1009 usage()
1010 sys.exit(1)
1011
1012 self.SetAppName('xrced')
1013 # Settings
1014 global conf
1015 conf = g.conf = wxConfig(style = wxCONFIG_USE_LOCAL_FILE)
1016 conf.autoRefresh = conf.ReadInt('autorefresh', True)
1017 pos = conf.ReadInt('x', -1), conf.ReadInt('y', -1)
1018 size = conf.ReadInt('width', 800), conf.ReadInt('height', 600)
1019 conf.embedPanel = conf.ReadInt('embedPanel', True)
1020 conf.showTools = conf.ReadInt('showTools', True)
1021 conf.sashPos = conf.ReadInt('sashPos', 200)
1022 # read recently used files
1023 recentfiles=conf.Read('recentFiles','')
1024 conf.recentfiles={}
1025 if recentfiles:
1026 for fil in recentfiles.split('|'):
1027 conf.recentfiles[wxNewId()]=fil
1028 if not conf.embedPanel:
1029 conf.panelX = conf.ReadInt('panelX', -1)
1030 conf.panelY = conf.ReadInt('panelY', -1)
1031 else:
1032 conf.panelX = conf.panelY = -1
1033 conf.panelWidth = conf.ReadInt('panelWidth', 200)
1034 conf.panelHeight = conf.ReadInt('panelHeight', 200)
1035 conf.panic = not conf.HasEntry('nopanic')
1036 # Add handlers
1037 wxFileSystem_AddHandler(wxMemoryFSHandler())
1038 wxInitAllImageHandlers()
1039 # Create main frame
1040 frame = Frame(pos, size)
1041 frame.Show(True)
1042 # Load resources from XRC file (!!! should be transformed to .py later?)
1043 frame.res = wxXmlResource('')
1044 # !!! Temporary blocking of assert failure occuring in unicode build
1045 try:
1046 frame.res.Load(os.path.join(basePath, 'xrced.xrc'))
1047 except wx._core.PyAssertionError:
1048 pass
1049
1050 # Load file after showing
1051 if args:
1052 conf.panic = False
1053 frame.open = frame.Open(args[0])
1054
1055 return True
1056
1057 def OnExit(self):
1058 # Write config
1059 global conf
1060 wc = wxConfigBase_Get()
1061 wc.WriteInt('autorefresh', conf.autoRefresh)
1062 wc.WriteInt('x', conf.x)
1063 wc.WriteInt('y', conf.y)
1064 wc.WriteInt('width', conf.width)
1065 wc.WriteInt('height', conf.height)
1066 wc.WriteInt('embedPanel', conf.embedPanel)
1067 wc.WriteInt('showTools', conf.showTools)
1068 if not conf.embedPanel:
1069 wc.WriteInt('panelX', conf.panelX)
1070 wc.WriteInt('panelY', conf.panelY)
1071 wc.WriteInt('sashPos', conf.sashPos)
1072 wc.WriteInt('panelWidth', conf.panelWidth)
1073 wc.WriteInt('panelHeight', conf.panelHeight)
1074 wc.WriteInt('nopanic', True)
1075 wc.Write('recentFiles', '|'.join(conf.recentfiles.values()[-5:]))
1076 wc.Flush()
1077
1078 def main():
1079 app = App(0, useBestVisual=False)
1080 #app.SetAssertMode(wxPYAPP_ASSERT_LOG)
1081 app.MainLoop()
1082 app.OnExit()
1083 global conf
1084 del conf
1085
1086 if __name__ == '__main__':
1087 main()