]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/tools/XRCed/xrced.py
added missing comma
[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 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]
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 OnSubclass(self, evt):
477 selected = tree.selection
478 xxx = tree.GetPyData(selected).treeObject()
479 elem = xxx.element
480 subclass = xxx.subclass
481 dlg = wxTextEntryDialog(self, 'Subclass:', defaultValue=subclass)
482 if dlg.ShowModal() == wxID_OK:
483 subclass = dlg.GetValue()
484 if subclass:
485 elem.setAttribute('subclass', subclass)
486 self.modified = True
487 elif elem.hasAttribute('subclass'):
488 elem.removeAttribute('subclass')
489 self.modified = True
490 xxx.subclass = elem.getAttribute('subclass')
491 tree.SetItemText(selected, xxx.treeName())
492 panel.pages[0].box.SetLabel(xxx.panelName())
493 dlg.Destroy()
494
495 def OnSelect(self, evt):
496 print >> sys.stderr, 'Xperimental function!'
497 wxYield()
498 self.SetCursor(wxCROSS_CURSOR)
499 self.CaptureMouse()
500
501 def OnLeftDown(self, evt):
502 pos = evt.GetPosition()
503 self.SetCursor(wxNullCursor)
504 self.ReleaseMouse()
505
506 def OnEmbedPanel(self, evt):
507 conf.embedPanel = evt.IsChecked()
508 if conf.embedPanel:
509 # Remember last dimentions
510 conf.panelX, conf.panelY = self.miniFrame.GetPosition()
511 conf.panelWidth, conf.panelHeight = self.miniFrame.GetSize()
512 size = self.GetSize()
513 pos = self.GetPosition()
514 sizePanel = panel.GetSize()
515 panel.Reparent(self.splitter)
516 self.miniFrame.GetSizer().RemoveWindow(panel)
517 wxYield()
518 # Widen
519 self.SetDimensions(pos.x, pos.y, size.width + sizePanel.width, size.height)
520 self.splitter.SplitVertically(tree, panel, conf.sashPos)
521 self.miniFrame.Show(False)
522 else:
523 conf.sashPos = self.splitter.GetSashPosition()
524 pos = self.GetPosition()
525 size = self.GetSize()
526 sizePanel = panel.GetSize()
527 self.splitter.Unsplit(panel)
528 sizer = self.miniFrame.GetSizer()
529 panel.Reparent(self.miniFrame)
530 panel.Show(True)
531 sizer.Add(panel, 1, wxEXPAND)
532 self.miniFrame.Show(True)
533 self.miniFrame.SetDimensions(conf.panelX, conf.panelY,
534 conf.panelWidth, conf.panelHeight)
535 wxYield()
536 # Reduce width
537 self.SetDimensions(pos.x, pos.y,
538 max(size.width - sizePanel.width, self.minWidth), size.height)
539
540 def OnShowTools(self, evt):
541 conf.showTools = evt.IsChecked()
542 g.tools.Show(conf.showTools)
543 if conf.showTools:
544 self.toolsSizer.Prepend(g.tools, 0, wxEXPAND)
545 else:
546 self.toolsSizer.Remove(g.tools)
547 self.toolsSizer.Layout()
548
549 def OnTest(self, evt):
550 if not tree.selection: return # key pressed event
551 tree.ShowTestWindow(tree.selection)
552
553 def OnRefresh(self, evt):
554 # If modified, apply first
555 selection = tree.selection
556 if selection:
557 xxx = tree.GetPyData(selection)
558 if xxx and panel.IsModified():
559 tree.Apply(xxx, selection)
560 if g.testWin:
561 # (re)create
562 tree.CreateTestWin(g.testWin.item)
563 panel.modified = False
564 tree.needUpdate = False
565
566 def OnAutoRefresh(self, evt):
567 conf.autoRefresh = evt.IsChecked()
568 self.menuBar.Check(self.ID_AUTO_REFRESH, conf.autoRefresh)
569 self.tb.ToggleTool(self.ID_AUTO_REFRESH, conf.autoRefresh)
570
571 def OnAbout(self, evt):
572 str = '''\
573 XRCed version %s
574
575 (c) Roman Rolinsky <rollrom@users.sourceforge.net>
576 Homepage: http://xrced.sourceforge.net\
577 ''' % version
578 dlg = wxMessageDialog(self, str, 'About XRCed', wxOK | wxCENTRE)
579 dlg.ShowModal()
580 dlg.Destroy()
581
582 def OnReadme(self, evt):
583 text = open(os.path.join(basePath, 'README.txt'), 'r').read()
584 dlg = ScrolledMessageDialog(self, text, "XRCed README")
585 dlg.ShowModal()
586 dlg.Destroy()
587
588 # Simple emulation of python command line
589 def OnDebugCMD(self, evt):
590 import traceback
591 while 1:
592 try:
593 exec raw_input('C:\> ')
594 except EOFError:
595 print '^D'
596 break
597 except:
598 (etype, value, tb) =sys.exc_info()
599 tblist =traceback.extract_tb(tb)[1:]
600 msg =' '.join(traceback.format_exception_only(etype, value)
601 +traceback.format_list(tblist))
602 print msg
603
604 def OnCreate(self, evt):
605 selected = tree.selection
606 if tree.ctrl: appendChild = False
607 else: appendChild = not tree.NeedInsert(selected)
608 xxx = tree.GetPyData(selected)
609 if not appendChild:
610 # If insert before
611 if tree.shift:
612 # If has previous item, insert after it, else append to parent
613 nextItem = selected
614 parentLeaf = tree.GetItemParent(selected)
615 else:
616 # If has next item, insert, else append to parent
617 nextItem = tree.GetNextSibling(selected)
618 parentLeaf = tree.GetItemParent(selected)
619 # Expanded container (must have children)
620 elif tree.shift and tree.IsExpanded(selected) \
621 and tree.GetChildrenCount(selected, False):
622 nextItem = tree.GetFirstChild(selected)[0]
623 parentLeaf = selected
624 else:
625 nextItem = wxTreeItemId()
626 parentLeaf = selected
627 parent = tree.GetPyData(parentLeaf)
628 if parent.hasChild: parent = parent.child
629
630 # Create element
631 className = pullDownMenu.createMap[evt.GetId()]
632 xxx = MakeEmptyXXX(parent, className)
633
634 # Set default name for top-level windows
635 if parent.__class__ == xxxMainNode:
636 cl = xxx.treeObject().__class__
637 frame.maxIDs[cl] += 1
638 xxx.treeObject().name = '%s%d' % (defaultIDs[cl], frame.maxIDs[cl])
639 xxx.treeObject().element.setAttribute('name', xxx.treeObject().name)
640
641 # Insert new node, register undo
642 elem = xxx.element
643 newItem = tree.InsertNode(parentLeaf, parent, elem, nextItem)
644 undoMan.RegisterUndo(UndoPasteCreate(parentLeaf, parent, newItem, selected))
645 tree.EnsureVisible(newItem)
646 tree.SelectItem(newItem)
647 if not tree.IsVisible(newItem):
648 tree.ScrollTo(newItem)
649 tree.Refresh()
650 # Update view?
651 if g.testWin and tree.IsHighlatable(newItem):
652 if conf.autoRefresh:
653 tree.needUpdate = True
654 tree.pendingHighLight = newItem
655 else:
656 tree.pendingHighLight = None
657 tree.SetFocus()
658 self.modified = True
659
660 # Replace one object with another
661 def OnReplace(self, evt):
662 selected = tree.selection
663 xxx = tree.GetPyData(selected).treeObject()
664 elem = xxx.element
665 parent = elem.parentNode
666 parentXXX = xxx.parent
667 # New class
668 className = pullDownMenu.createMap[evt.GetId() - 1000]
669 # Create temporary empty node (with default values)
670 dummy = MakeEmptyDOM(className)
671 xxxClass = xxxDict[className]
672 # Remove non-compatible children
673 if tree.ItemHasChildren(selected) and not xxxClass.hasChildren:
674 tree.DeleteChildren(selected)
675 nodes = elem.childNodes[:]
676 tags = []
677 for node in nodes:
678 remove = False
679 tag = node.tagName
680 if tag == 'object':
681 if not xxxClass.hasChildren:
682 remove = True
683 elif tag not in xxxClass.allParams and \
684 (not xxxClass.hasStyle or tag not in xxxClass.styles):
685 remove = True
686 else:
687 tags.append(tag)
688 if remove:
689 elem.removeChild(node)
690 node.unlink()
691
692 # Copy parameters present in dummy but not in elem
693 for node in dummy.childNodes:
694 tag = node.tagName
695 if tag not in tags:
696 elem.appendChild(node.cloneNode(True))
697 dummy.unlink()
698 # Change class name
699 elem.setAttribute('class', className)
700 # Re-create xxx element
701 xxx = MakeXXXFromDOM(parentXXX, elem)
702 # Update parent in child objects
703 if tree.ItemHasChildren(selected):
704 i, cookie = tree.GetFirstChild(selected)
705 while i.IsOk():
706 x = tree.GetPyData(i)
707 x.parent = xxx
708 if x.hasChild: x.child.parent = xxx
709 i, cookie = tree.GetNextChild(selected, cookie)
710
711 # Update tree
712 if tree.GetPyData(selected).hasChild: # child container
713 container = tree.GetPyData(selected)
714 container.child = xxx
715 container.hasChildren = xxx.hasChildren
716 container.isSizer = xxx.isSizer
717 else:
718 tree.SetPyData(selected, xxx)
719 tree.SetItemText(selected, xxx.treeName())
720 tree.SetItemImage(selected, xxx.treeImage())
721
722 # Set default name for top-level windows
723 if parent.__class__ == xxxMainNode:
724 cl = xxx.treeObject().__class__
725 frame.maxIDs[cl] += 1
726 xxx.treeObject().name = '%s%d' % (defaultIDs[cl], frame.maxIDs[cl])
727 xxx.treeObject().element.setAttribute('name', xxx.treeObject().name)
728
729 # Update panel
730 g.panel.SetData(xxx)
731 # Update tools
732 g.tools.UpdateUI()
733
734 #undoMan.RegisterUndo(UndoPasteCreate(parentLeaf, parent, newItem, selected))
735 # Update view?
736 if g.testWin and tree.IsHighlatable(selected):
737 if conf.autoRefresh:
738 tree.needUpdate = True
739 tree.pendingHighLight = selected
740 else:
741 tree.pendingHighLight = None
742 tree.SetFocus()
743 self.modified = True
744
745 # Expand/collapse subtree
746 def OnExpand(self, evt):
747 if tree.selection: tree.ExpandAll(tree.selection)
748 else: tree.ExpandAll(tree.root)
749 def OnCollapse(self, evt):
750 if tree.selection: tree.CollapseAll(tree.selection)
751 else: tree.CollapseAll(tree.root)
752
753 def OnPullDownHighlight(self, evt):
754 menuId = evt.GetMenuId()
755 if menuId != -1:
756 menu = evt.GetEventObject()
757 help = menu.GetHelpString(menuId)
758 self.SetStatusText(help)
759 else:
760 self.SetStatusText('')
761
762 def OnUpdateUI(self, evt):
763 if evt.GetId() in [wxID_CUT, wxID_COPY, self.ID_DELETE]:
764 evt.Enable(tree.selection is not None and tree.selection != tree.root)
765 elif evt.GetId() == wxID_PASTE:
766 evt.Enable((self.clipboard and tree.selection) != None)
767 elif evt.GetId() == self.ID_TEST:
768 evt.Enable(tree.selection is not None and tree.selection != tree.root)
769 elif evt.GetId() == wxID_UNDO: evt.Enable(undoMan.CanUndo())
770 elif evt.GetId() == wxID_REDO: evt.Enable(undoMan.CanRedo())
771
772 def OnIdle(self, evt):
773 if self.inIdle: return # Recursive call protection
774 self.inIdle = True
775 if tree.needUpdate:
776 if conf.autoRefresh:
777 if g.testWin:
778 self.SetStatusText('Refreshing test window...')
779 # (re)create
780 tree.CreateTestWin(g.testWin.item)
781 wxYield()
782 self.SetStatusText('')
783 tree.needUpdate = False
784 elif tree.pendingHighLight:
785 tree.HighLight(tree.pendingHighLight)
786 else:
787 evt.Skip()
788 self.inIdle = False
789
790 # We don't let close panel window
791 def OnCloseMiniFrame(self, evt):
792 return
793
794 def OnCloseWindow(self, evt):
795 if not self.AskSave(): return
796 if g.testWin: g.testWin.Destroy()
797 # Destroy cached windows
798 panel.cacheParent.Destroy()
799 if not panel.GetPageCount() == 2:
800 panel.page2.Destroy()
801 if not self.IsIconized():
802 conf.x, conf.y = self.GetPosition()
803 conf.width, conf.height = self.GetSize()
804 if conf.embedPanel:
805 conf.sashPos = self.splitter.GetSashPosition()
806 else:
807 conf.panelX, conf.panelY = self.miniFrame.GetPosition()
808 conf.panelWidth, conf.panelHeight = self.miniFrame.GetSize()
809 evt.Skip()
810
811 def Clear(self):
812 self.dataFile = ''
813 if self.clipboard:
814 self.clipboard.unlink()
815 self.clipboard = None
816 undoMan.Clear()
817 self.modified = False
818 tree.Clear()
819 panel.Clear()
820 if g.testWin:
821 g.testWin.Destroy()
822 g.testWin = None
823 self.SetTitle(progname)
824 # Numbers for new controls
825 self.maxIDs = {}
826 self.maxIDs[xxxPanel] = self.maxIDs[xxxDialog] = self.maxIDs[xxxFrame] = \
827 self.maxIDs[xxxMenuBar] = self.maxIDs[xxxMenu] = self.maxIDs[xxxToolBar] = 0
828
829 def Open(self, path):
830 if not os.path.exists(path):
831 wxLogError('File does not exists: %s' % path)
832 return False
833 # Try to read the file
834 try:
835 f = open(path)
836 self.Clear()
837 # Parse first line to get encoding (!! hack, I don't know a better way)
838 line = f.readline()
839 mo = re.match(r'^<\?xml ([^<>]* )?encoding="(?P<encd>[^<>].*)"\?>', line)
840 # Build wx tree
841 f.seek(0)
842 dom = minidom.parse(f)
843 # Set encoding global variable and document encoding property
844 if mo:
845 dom.encoding = g.currentEncoding = mo.group('encd')
846 if dom.encoding not in ['ascii', sys.getdefaultencoding()]:
847 wxLogWarning('Encoding is different from system default')
848 else:
849 g.currentEncoding = 'ascii'
850 dom.encoding = ''
851 f.close()
852 # Change dir
853 dir = os.path.dirname(path)
854 if dir: os.chdir(dir)
855 tree.SetData(dom)
856 self.dataFile = path
857 self.SetTitle(progname + ': ' + os.path.basename(path))
858 except:
859 # Nice exception printing
860 inf = sys.exc_info()
861 wxLogError(traceback.format_exception(inf[0], inf[1], None)[-1])
862 wxLogError('Error reading file: %s' % path)
863 return False
864 return True
865
866 def Indent(self, node, indent = 0):
867 # Copy child list because it will change soon
868 children = node.childNodes[:]
869 # Main node doesn't need to be indented
870 if indent:
871 text = self.domCopy.createTextNode('\n' + ' ' * indent)
872 node.parentNode.insertBefore(text, node)
873 if children:
874 # Append newline after last child, except for text nodes
875 if children[-1].nodeType == minidom.Node.ELEMENT_NODE:
876 text = self.domCopy.createTextNode('\n' + ' ' * indent)
877 node.appendChild(text)
878 # Indent children which are elements
879 for n in children:
880 if n.nodeType == minidom.Node.ELEMENT_NODE:
881 self.Indent(n, indent + 2)
882
883 def Save(self, path):
884 try:
885 # Apply changes
886 if tree.selection and panel.IsModified():
887 self.OnRefresh(wxCommandEvent())
888 f = open(path, 'w')
889 # Make temporary copy for formatting it
890 # !!! We can't clone dom node, it works only once
891 #self.domCopy = tree.dom.cloneNode(True)
892 self.domCopy = MyDocument()
893 mainNode = self.domCopy.appendChild(tree.mainNode.cloneNode(True))
894 self.Indent(mainNode)
895 self.domCopy.writexml(f, encoding=tree.rootObj.params['encoding'].value())
896 f.close()
897 self.domCopy.unlink()
898 self.domCopy = None
899 self.modified = False
900 panel.SetModified(False)
901 except:
902 wxLogError('Error writing file: %s' % path)
903 raise
904
905 def AskSave(self):
906 if not (self.modified or panel.IsModified()): return True
907 flags = wxICON_EXCLAMATION | wxYES_NO | wxCANCEL | wxCENTRE
908 dlg = wxMessageDialog( self, 'File is modified. Save before exit?',
909 'Save before too late?', flags )
910 say = dlg.ShowModal()
911 dlg.Destroy()
912 if say == wxID_YES:
913 self.OnSaveOrSaveAs(wxCommandEvent(wxID_SAVE))
914 # If save was successful, modified flag is unset
915 if not self.modified: return True
916 elif say == wxID_NO:
917 self.modified = False
918 panel.SetModified(False)
919 return True
920 return False
921
922 def SaveUndo(self):
923 pass # !!!
924
925 ################################################################################
926
927 def usage():
928 print >> sys.stderr, 'usage: xrced [-dhiv] [file]'
929
930 class App(wxApp):
931 def OnInit(self):
932 global debug
933 # Process comand-line
934 try:
935 opts = args = None
936 opts, args = getopt.getopt(sys.argv[1:], 'dhiv')
937 for o,a in opts:
938 if o == '-h':
939 usage()
940 sys.exit(0)
941 elif o == '-d':
942 debug = True
943 elif o == '-v':
944 print 'XRCed version', version
945 sys.exit(0)
946
947 except getopt.GetoptError:
948 if wxPlatform != '__WXMAC__': # macs have some extra parameters
949 print >> sys.stderr, 'Unknown option'
950 usage()
951 sys.exit(1)
952
953 self.SetAppName('xrced')
954 # Settings
955 global conf
956 conf = g.conf = wxConfig(style = wxCONFIG_USE_LOCAL_FILE)
957 conf.autoRefresh = conf.ReadInt('autorefresh', True)
958 pos = conf.ReadInt('x', -1), conf.ReadInt('y', -1)
959 size = conf.ReadInt('width', 800), conf.ReadInt('height', 600)
960 conf.embedPanel = conf.ReadInt('embedPanel', True)
961 conf.showTools = conf.ReadInt('showTools', True)
962 conf.sashPos = conf.ReadInt('sashPos', 200)
963 if not conf.embedPanel:
964 conf.panelX = conf.ReadInt('panelX', -1)
965 conf.panelY = conf.ReadInt('panelY', -1)
966 else:
967 conf.panelX = conf.panelY = -1
968 conf.panelWidth = conf.ReadInt('panelWidth', 200)
969 conf.panelHeight = conf.ReadInt('panelHeight', 200)
970 conf.panic = not conf.HasEntry('nopanic')
971 # Add handlers
972 wxFileSystem_AddHandler(wxMemoryFSHandler())
973 wxInitAllImageHandlers()
974 # Create main frame
975 frame = Frame(pos, size)
976 frame.Show(True)
977 # Load resources from XRC file (!!! should be transformed to .py later?)
978 frame.res = wxXmlResource('')
979 frame.res.Load(os.path.join(basePath, 'xrced.xrc'))
980
981 # Load file after showing
982 if args:
983 conf.panic = False
984 frame.open = frame.Open(args[0])
985
986 return True
987
988 def OnExit(self):
989 # Write config
990 global conf
991 wc = wxConfigBase_Get()
992 wc.WriteInt('autorefresh', conf.autoRefresh)
993 wc.WriteInt('x', conf.x)
994 wc.WriteInt('y', conf.y)
995 wc.WriteInt('width', conf.width)
996 wc.WriteInt('height', conf.height)
997 wc.WriteInt('embedPanel', conf.embedPanel)
998 wc.WriteInt('showTools', conf.showTools)
999 if not conf.embedPanel:
1000 wc.WriteInt('panelX', conf.panelX)
1001 wc.WriteInt('panelY', conf.panelY)
1002 wc.WriteInt('sashPos', conf.sashPos)
1003 wc.WriteInt('panelWidth', conf.panelWidth)
1004 wc.WriteInt('panelHeight', conf.panelHeight)
1005 wc.WriteInt('nopanic', True)
1006 wc.Flush()
1007
1008 def main():
1009 app = App(0, useBestVisual=False)
1010 #app.SetAssertMode(wxPYAPP_ASSERT_LOG)
1011 app.MainLoop()
1012 app.OnExit()
1013 global conf
1014 del conf
1015
1016 if __name__ == '__main__':
1017 main()