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