2 # Purpose: XRC editor, main module
3 # Author: Roman Rolinsky <rolinsky@mema.ucl.ac.be>
9 xrced -- Simple resource editor for XRC format used by wxWindows/wxPython
14 xrced [ -h ] [ -v ] [ XRC-file ]
18 -h output short usage info and exit
20 -v output version info and exit
25 import os
, sys
, getopt
, re
, traceback
, tempfile
, shutil
28 from tree
import * # imports xxx which imports params
31 # Cleanup recursive import sideeffects, otherwise we can't create undoMan
33 undo
.ParamPage
= ParamPage
34 undoMan
= g
.undoMan
= UndoManager()
36 # Set application path for loading resources
37 if __name__
== '__main__':
38 basePath
= os
.path
.dirname(sys
.argv
[0])
40 basePath
= os
.path
.dirname(__file__
)
42 # 1 adds CMD command to Help menu
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>
56 defaultIDs
= {xxxPanel
:'PANEL', xxxDialog
:'DIALOG', xxxFrame
:'FRAME',
57 xxxMenuBar
:'MENUBAR', xxxMenu
:'MENU', xxxToolBar
:'TOOLBAR',
60 ################################################################################
62 # ScrolledMessageDialog - modified from wxPython lib to set fixed-width font
63 class ScrolledMessageDialog(wxDialog
):
64 def __init__(self
, parent
, msg
, caption
, pos
= wxDefaultPosition
, size
= (500,300)):
65 from wxPython
.lib
.layoutf
import Layoutf
66 wxDialog
.__init
__(self
, parent
, -1, caption
, pos
, size
)
67 text
= wxTextCtrl(self
, -1, msg
, wxDefaultPosition
,
68 wxDefaultSize
, wxTE_MULTILINE | wxTE_READONLY
)
69 text
.SetFont(g
.modernFont())
71 # !!! possible bug - GetTextExtent without font returns sysfont dims
72 w
, h
= dc
.GetFullTextExtent(' ', g
.modernFont())[:2]
73 ok
= wxButton(self
, wxID_OK
, "OK")
74 text
.SetConstraints(Layoutf('t=t5#1;b=t5#2;l=l5#1;r=r5#1', (self
,ok
)))
75 text
.SetSize((w
* 80 + 30, h
* 40))
77 ok
.SetConstraints(Layoutf('b=b5#1;x%w50#1;w!80;h!25', (self
,)))
78 self
.SetAutoLayout(True)
80 self
.CenterOnScreen(wxBOTH
)
82 ################################################################################
84 # Event handler for using during location
85 class Locator(wxEvtHandler
):
86 def ProcessEvent(self
, evt
):
90 def __init__(self
, pos
, size
):
91 wxFrame
.__init
__(self
, None, -1, '', pos
, size
)
93 frame
= g
.frame
= self
94 bar
= self
.CreateStatusBar(2)
95 bar
.SetStatusWidths([-1, 40])
96 self
.SetIcon(images
.getIconIcon())
101 # Load our own resources
102 self
.res
= wxXmlResource('')
103 # !!! Blocking of assert failure occuring in older unicode builds
105 self
.res
.Load(os
.path
.join(basePath
, 'xrced.xrc'))
106 except wx
._core
.PyAssertionError
:
107 print 'PyAssertionError was ignored'
110 menuBar
= wxMenuBar()
113 menu
.Append(wxID_NEW
, '&New\tCtrl-N', 'New file')
114 menu
.AppendSeparator()
115 menu
.Append(wxID_OPEN
, '&Open...\tCtrl-O', 'Open XRC file')
116 self
.recentMenu
= wxMenu()
117 self
.AppendRecent(self
.recentMenu
)
118 menu
.AppendMenu(-1, 'Open Recent', self
.recentMenu
, 'Open a recent file')
119 menu
.AppendSeparator()
120 menu
.Append(wxID_SAVE
, '&Save\tCtrl-S', 'Save XRC file')
121 menu
.Append(wxID_SAVEAS
, 'Save &As...', 'Save XRC file under different name')
122 menu
.AppendSeparator()
123 menu
.Append(wxID_EXIT
, '&Quit\tCtrl-Q', 'Exit application')
125 menuBar
.Append(menu
, '&File')
128 menu
.Append(wxID_UNDO
, '&Undo\tCtrl-Z', 'Undo')
129 menu
.Append(wxID_REDO
, '&Redo\tCtrl-Y', 'Redo')
130 menu
.AppendSeparator()
131 menu
.Append(wxID_CUT
, 'Cut\tCtrl-X', 'Cut to the clipboard')
132 menu
.Append(wxID_COPY
, '&Copy\tCtrl-C', 'Copy to the clipboard')
133 menu
.Append(wxID_PASTE
, '&Paste\tCtrl-V', 'Paste from the clipboard')
134 self
.ID_DELETE
= wxNewId()
135 menu
.Append(self
.ID_DELETE
, '&Delete\tCtrl-D', 'Delete object')
136 menu
.AppendSeparator()
137 self
.ID_LOCATE
= wxNewId()
138 self
.ID_TOOL_LOCATE
= wxNewId()
139 self
.ID_TOOL_PASTE
= wxNewId()
140 menu
.Append(self
.ID_LOCATE
, '&Locate\tCtrl-L', 'Locate control in test window and select it')
141 menuBar
.Append(menu
, '&Edit')
144 self
.ID_EMBED_PANEL
= wxNewId()
145 menu
.Append(self
.ID_EMBED_PANEL
, '&Embed Panel',
146 'Toggle embedding properties panel in the main window', True)
147 menu
.Check(self
.ID_EMBED_PANEL
, conf
.embedPanel
)
148 self
.ID_SHOW_TOOLS
= wxNewId()
149 menu
.Append(self
.ID_SHOW_TOOLS
, 'Show &Tools', 'Toggle tools', True)
150 menu
.Check(self
.ID_SHOW_TOOLS
, conf
.showTools
)
151 menu
.AppendSeparator()
152 self
.ID_TEST
= wxNewId()
153 menu
.Append(self
.ID_TEST
, '&Test\tF5', 'Show test window')
154 self
.ID_REFRESH
= wxNewId()
155 menu
.Append(self
.ID_REFRESH
, '&Refresh\tCtrl-R', 'Refresh test window')
156 self
.ID_AUTO_REFRESH
= wxNewId()
157 menu
.Append(self
.ID_AUTO_REFRESH
, '&Auto-refresh\tCtrl-A',
158 'Toggle auto-refresh mode', True)
159 menu
.Check(self
.ID_AUTO_REFRESH
, conf
.autoRefresh
)
160 self
.ID_TEST_HIDE
= wxNewId()
161 menu
.Append(self
.ID_TEST_HIDE
, '&Hide\tCtrl-H', 'Close test window')
162 menuBar
.Append(menu
, '&View')
165 menu
.Append(wxID_ABOUT
, '&About...', 'About XCRed')
166 self
.ID_README
= wxNewId()
167 menu
.Append(self
.ID_README
, '&Readme...', 'View the README file')
169 self
.ID_DEBUG_CMD
= wxNewId()
170 menu
.Append(self
.ID_DEBUG_CMD
, 'CMD', 'Python command line')
171 EVT_MENU(self
, self
.ID_DEBUG_CMD
, self
.OnDebugCMD
)
172 menuBar
.Append(menu
, '&Help')
174 self
.menuBar
= menuBar
175 self
.SetMenuBar(menuBar
)
178 tb
= self
.CreateToolBar(wxTB_HORIZONTAL | wxNO_BORDER | wxTB_FLAT
)
179 tb
.SetToolBitmapSize((24,24))
180 new_bmp
= wx
.ArtProvider
.GetBitmap(wx
.ART_NORMAL_FILE
, wx
.ART_TOOLBAR
)
181 open_bmp
= wx
.ArtProvider
.GetBitmap(wx
.ART_FILE_OPEN
, wx
.ART_TOOLBAR
)
182 save_bmp
= wx
.ArtProvider
.GetBitmap(wx
.ART_FILE_SAVE
, wx
.ART_TOOLBAR
)
183 undo_bmp
= wx
.ArtProvider
.GetBitmap(wx
.ART_UNDO
, wx
.ART_TOOLBAR
)
184 redo_bmp
= wx
.ArtProvider
.GetBitmap(wx
.ART_REDO
, wx
.ART_TOOLBAR
)
185 cut_bmp
= wx
.ArtProvider
.GetBitmap(wx
.ART_CUT
, wx
.ART_TOOLBAR
)
186 copy_bmp
= wx
.ArtProvider
.GetBitmap(wx
.ART_COPY
, wx
.ART_TOOLBAR
)
187 paste_bmp
= wx
.ArtProvider
.GetBitmap(wx
.ART_PASTE
, wx
.ART_TOOLBAR
)
189 tb
.AddSimpleTool(wxID_NEW
, new_bmp
, 'New', 'New file')
190 tb
.AddSimpleTool(wxID_OPEN
, open_bmp
, 'Open', 'Open file')
191 tb
.AddSimpleTool(wxID_SAVE
, save_bmp
, 'Save', 'Save file')
192 tb
.AddControl(wxStaticLine(tb
, -1, size
=(-1,23), style
=wxLI_VERTICAL
))
193 tb
.AddSimpleTool(wxID_UNDO
, undo_bmp
, 'Undo', 'Undo')
194 tb
.AddSimpleTool(wxID_REDO
, redo_bmp
, 'Redo', 'Redo')
195 tb
.AddControl(wxStaticLine(tb
, -1, size
=(-1,23), style
=wxLI_VERTICAL
))
196 tb
.AddSimpleTool(wxID_CUT
, cut_bmp
, 'Cut', 'Cut')
197 tb
.AddSimpleTool(wxID_COPY
, copy_bmp
, 'Copy', 'Copy')
198 tb
.AddSimpleTool(self
.ID_TOOL_PASTE
, paste_bmp
, 'Paste', 'Paste')
199 tb
.AddControl(wxStaticLine(tb
, -1, size
=(-1,23), style
=wxLI_VERTICAL
))
200 tb
.AddSimpleTool(self
.ID_TOOL_LOCATE
,
201 images
.getLocateBitmap(), #images.getLocateArmedBitmap(),
202 'Locate', 'Locate control in test window and select it', True)
203 tb
.AddControl(wxStaticLine(tb
, -1, size
=(-1,23), style
=wxLI_VERTICAL
))
204 tb
.AddSimpleTool(self
.ID_TEST
, images
.getTestBitmap(), 'Test', 'Test window')
205 tb
.AddSimpleTool(self
.ID_REFRESH
, images
.getRefreshBitmap(),
206 'Refresh', 'Refresh view')
207 tb
.AddSimpleTool(self
.ID_AUTO_REFRESH
, images
.getAutoRefreshBitmap(),
208 'Auto-refresh', 'Toggle auto-refresh mode', True)
209 if wxPlatform
== '__WXGTK__':
210 tb
.AddSeparator() # otherwise auto-refresh sticks in status line
211 tb
.ToggleTool(self
.ID_AUTO_REFRESH
, conf
.autoRefresh
)
215 self
.minWidth
= tb
.GetSize()[0] # minimal width is the size of toolbar
218 EVT_MENU(self
, wxID_NEW
, self
.OnNew
)
219 EVT_MENU(self
, wxID_OPEN
, self
.OnOpen
)
220 EVT_MENU(self
, wxID_SAVE
, self
.OnSaveOrSaveAs
)
221 EVT_MENU(self
, wxID_SAVEAS
, self
.OnSaveOrSaveAs
)
222 EVT_MENU(self
, wxID_EXIT
, self
.OnExit
)
224 EVT_MENU(self
, wxID_UNDO
, self
.OnUndo
)
225 EVT_MENU(self
, wxID_REDO
, self
.OnRedo
)
226 EVT_MENU(self
, wxID_CUT
, self
.OnCutDelete
)
227 EVT_MENU(self
, wxID_COPY
, self
.OnCopy
)
228 EVT_MENU(self
, wxID_PASTE
, self
.OnPaste
)
229 EVT_MENU(self
, self
.ID_TOOL_PASTE
, self
.OnPaste
)
230 EVT_MENU(self
, self
.ID_DELETE
, self
.OnCutDelete
)
231 EVT_MENU(self
, self
.ID_LOCATE
, self
.OnLocate
)
232 EVT_MENU(self
, self
.ID_TOOL_LOCATE
, self
.OnLocate
)
234 EVT_MENU(self
, self
.ID_EMBED_PANEL
, self
.OnEmbedPanel
)
235 EVT_MENU(self
, self
.ID_SHOW_TOOLS
, self
.OnShowTools
)
236 EVT_MENU(self
, self
.ID_TEST
, self
.OnTest
)
237 EVT_MENU(self
, self
.ID_REFRESH
, self
.OnRefresh
)
238 EVT_MENU(self
, self
.ID_AUTO_REFRESH
, self
.OnAutoRefresh
)
239 EVT_MENU(self
, self
.ID_TEST_HIDE
, self
.OnTestHide
)
241 EVT_MENU(self
, wxID_ABOUT
, self
.OnAbout
)
242 EVT_MENU(self
, self
.ID_README
, self
.OnReadme
)
245 EVT_UPDATE_UI(self
, wxID_CUT
, self
.OnUpdateUI
)
246 EVT_UPDATE_UI(self
, wxID_COPY
, self
.OnUpdateUI
)
247 EVT_UPDATE_UI(self
, wxID_PASTE
, self
.OnUpdateUI
)
248 EVT_UPDATE_UI(self
, self
.ID_LOCATE
, self
.OnUpdateUI
)
249 EVT_UPDATE_UI(self
, self
.ID_TOOL_LOCATE
, self
.OnUpdateUI
)
250 EVT_UPDATE_UI(self
, self
.ID_TOOL_PASTE
, self
.OnUpdateUI
)
251 EVT_UPDATE_UI(self
, wxID_UNDO
, self
.OnUpdateUI
)
252 EVT_UPDATE_UI(self
, wxID_REDO
, self
.OnUpdateUI
)
253 EVT_UPDATE_UI(self
, self
.ID_DELETE
, self
.OnUpdateUI
)
254 EVT_UPDATE_UI(self
, self
.ID_TEST
, self
.OnUpdateUI
)
255 EVT_UPDATE_UI(self
, self
.ID_REFRESH
, self
.OnUpdateUI
)
258 sizer
= wxBoxSizer(wxVERTICAL
)
259 sizer
.Add(wxStaticLine(self
, -1), 0, wxEXPAND
)
260 # Horizontal sizer for toolbar and splitter
261 self
.toolsSizer
= sizer1
= wxBoxSizer()
262 splitter
= wxSplitterWindow(self
, -1, style
=wxSP_3DSASH
)
263 self
.splitter
= splitter
264 splitter
.SetMinimumPaneSize(100)
267 g
.tree
= tree
= XML_Tree(splitter
, -1)
269 # Init pull-down menu data
271 g
.pullDownMenu
= pullDownMenu
= PullDownMenu(self
)
273 # Vertical toolbar for GUI buttons
274 g
.tools
= tools
= Tools(self
)
275 tools
.Show(conf
.showTools
)
276 if conf
.showTools
: sizer1
.Add(tools
, 0, wxEXPAND
)
278 tree
.RegisterKeyEvents()
280 # !!! frame styles are broken
281 # Miniframe for not embedded mode
282 miniFrame
= wxFrame(self
, -1, 'Properties Panel',
283 (conf
.panelX
, conf
.panelY
),
284 (conf
.panelWidth
, conf
.panelHeight
))
285 self
.miniFrame
= miniFrame
286 sizer2
= wxBoxSizer()
287 miniFrame
.SetAutoLayout(True)
288 miniFrame
.SetSizer(sizer2
)
289 EVT_CLOSE(self
.miniFrame
, self
.OnCloseMiniFrame
)
290 # Create panel for parameters
293 panel
= Panel(splitter
)
294 # Set plitter windows
295 splitter
.SplitVertically(tree
, panel
, conf
.sashPos
)
297 panel
= Panel(miniFrame
)
298 sizer2
.Add(panel
, 1, wxEXPAND
)
300 splitter
.Initialize(tree
)
301 sizer1
.Add(splitter
, 1, wxEXPAND
)
302 sizer
.Add(sizer1
, 1, wxEXPAND
)
303 self
.SetAutoLayout(True)
307 self
.clipboard
= None
311 EVT_IDLE(self
, self
.OnIdle
)
312 EVT_CLOSE(self
, self
.OnCloseWindow
)
313 EVT_KEY_DOWN(self
, tools
.OnKeyDown
)
314 EVT_KEY_UP(self
, tools
.OnKeyUp
)
316 def AppendRecent(self
, menu
):
317 # add recently used files to the menu
318 for id,name
in conf
.recentfiles
.iteritems():
320 EVT_MENU(self
,id,self
.OnRecentFile
)
323 def OnRecentFile(self
,evt
):
324 # open recently used file
325 if not self
.AskSave(): return
328 path
=conf
.recentfiles
[evt
.GetId()]
330 self
.SetStatusText('Data loaded')
332 self
.SetStatusText('Failed')
334 self
.SetStatusText('No such file')
337 def OnNew(self
, evt
):
338 if not self
.AskSave(): return
341 def OnOpen(self
, evt
):
342 if not self
.AskSave(): return
343 dlg
= wxFileDialog(self
, 'Open', os
.path
.dirname(self
.dataFile
),
344 '', '*.xrc', wxOPEN | wxCHANGE_DIR
)
345 if dlg
.ShowModal() == wxID_OK
:
347 self
.SetStatusText('Loading...')
352 self
.SetStatusText('Data loaded')
354 self
.SetStatusText('Failed')
355 self
.SaveRecent(path
)
360 def OnSaveOrSaveAs(self
, evt
):
361 if evt
.GetId() == wxID_SAVEAS
or not self
.dataFile
:
362 if self
.dataFile
: defaultName
= ''
363 else: defaultName
= 'UNTITLED.xrc'
364 dirname
= os
.path
.dirname(self
.dataFile
)
365 dlg
= wxFileDialog(self
, 'Save As', dirname
, defaultName
, '*.xrc',
366 wxSAVE | wxOVERWRITE_PROMPT | wxCHANGE_DIR
)
367 if dlg
.ShowModal() == wxID_OK
:
375 self
.SetStatusText('Saving...')
380 tmpFile
,tmpName
= tempfile
.mkstemp(prefix
='xrced-')
382 self
.Save(tmpName
) # save temporary file first
383 shutil
.move(tmpName
, path
)
385 self
.SetStatusText('Data saved')
386 self
.SaveRecent(path
)
388 self
.SetStatusText('Failed')
392 def SaveRecent(self
,path
):
393 # append to recently used files
394 if path
not in conf
.recentfiles
.values():
396 self
.recentMenu
.Append(newid
, path
)
397 EVT_MENU(self
, newid
, self
.OnRecentFile
)
398 conf
.recentfiles
[newid
] = path
400 def OnExit(self
, evt
):
403 def OnUndo(self
, evt
):
404 # Extra check to not mess with idle updating
405 if undoMan
.CanUndo():
408 def OnRedo(self
, evt
):
409 if undoMan
.CanRedo():
412 def OnCopy(self
, evt
):
413 selected
= tree
.selection
414 if not selected
: return # key pressed event
415 xxx
= tree
.GetPyData(selected
)
416 self
.clipboard
= xxx
.element
.cloneNode(True)
417 self
.SetStatusText('Copied')
419 def OnPaste(self
, evt
):
420 selected
= tree
.selection
421 if not selected
: return # key pressed event
422 # For pasting with Ctrl pressed
424 if evt
.GetId() == pullDownMenu
.ID_PASTE_SIBLING
: appendChild
= False
425 elif evt
.GetId() == self
.ID_TOOL_PASTE
:
426 if g
.tree
.ctrl
: appendChild
= False
427 else: appendChild
= not tree
.NeedInsert(selected
)
428 else: appendChild
= not tree
.NeedInsert(selected
)
429 xxx
= tree
.GetPyData(selected
)
431 # If has next item, insert, else append to parent
432 nextItem
= tree
.GetNextSibling(selected
)
433 parentLeaf
= tree
.GetItemParent(selected
)
434 # Expanded container (must have children)
435 elif tree
.IsExpanded(selected
) and tree
.GetChildrenCount(selected
, False):
436 # Insert as first child
437 nextItem
= tree
.GetFirstChild(selected
)[0]
438 parentLeaf
= selected
440 # No children or unexpanded item - appendChild stays True
441 nextItem
= wxTreeItemId() # no next item
442 parentLeaf
= selected
443 parent
= tree
.GetPyData(parentLeaf
).treeObject()
445 # Create a copy of clipboard element
446 elem
= self
.clipboard
.cloneNode(True)
447 # Tempopary xxx object to test things
448 xxx
= MakeXXXFromDOM(parent
, elem
)
450 # Check compatibility
454 if x
.__class
__ in [xxxDialog
, xxxFrame
, xxxMenuBar
, xxxWizard
]:
456 if parent
.__class
__ != xxxMainNode
: error
= True
457 elif x
.__class
__ == xxxToolBar
:
458 # Toolbar can be top-level of child of panel or frame
459 if parent
.__class
__ not in [xxxMainNode
, xxxPanel
, xxxFrame
]: error
= True
460 elif x
.__class
__ == xxxPanel
and parent
.__class
__ == xxxMainNode
:
462 elif x
.__class
__ == xxxSpacer
:
463 if not parent
.isSizer
: error
= True
464 elif x
.__class
__ == xxxSeparator
:
465 if not parent
.__class
__ in [xxxMenu
, xxxToolBar
]: error
= True
466 elif x
.__class
__ == xxxTool
:
467 if parent
.__class
__ != xxxToolBar
: error
= True
468 elif x
.__class
__ == xxxMenu
:
469 if not parent
.__class
__ in [xxxMainNode
, xxxMenuBar
, xxxMenu
]: error
= True
470 elif x
.__class
__ == xxxMenuItem
:
471 if not parent
.__class
__ in [xxxMenuBar
, xxxMenu
]: error
= True
472 elif x
.isSizer
and parent
.__class
__ == xxxNotebook
: error
= True
473 else: # normal controls can be almost anywhere
474 if parent
.__class
__ == xxxMainNode
or \
475 parent
.__class
__ in [xxxMenuBar
, xxxMenu
]: error
= True
477 if parent
.__class
__ == xxxMainNode
: parentClass
= 'root'
478 else: parentClass
= parent
.className
479 wxLogError('Incompatible parent/child: parent is %s, child is %s!' %
480 (parentClass
, x
.className
))
483 # Check parent and child relationships.
484 # If parent is sizer or notebook, child is of wrong class or
485 # parent is normal window, child is child container then detach child.
486 isChildContainer
= isinstance(xxx
, xxxChildContainer
)
487 if isChildContainer
and \
488 ((parent
.isSizer
and not isinstance(xxx
, xxxSizerItem
)) or \
489 (isinstance(parent
, xxxNotebook
) and not isinstance(xxx
, xxxNotebookPage
)) or \
490 not (parent
.isSizer
or isinstance(parent
, xxxNotebook
))):
491 elem
.removeChild(xxx
.child
.element
) # detach child
492 elem
.unlink() # delete child container
493 elem
= xxx
.child
.element
# replace
494 # This may help garbage collection
495 xxx
.child
.parent
= None
496 isChildContainer
= False
497 # Parent is sizer or notebook, child is not child container
498 if parent
.isSizer
and not isChildContainer
and not isinstance(xxx
, xxxSpacer
):
499 # Create sizer item element
500 sizerItemElem
= MakeEmptyDOM('sizeritem')
501 sizerItemElem
.appendChild(elem
)
503 elif isinstance(parent
, xxxNotebook
) and not isChildContainer
:
504 pageElem
= MakeEmptyDOM('notebookpage')
505 pageElem
.appendChild(elem
)
507 # Insert new node, register undo
508 newItem
= tree
.InsertNode(parentLeaf
, parent
, elem
, nextItem
)
509 undoMan
.RegisterUndo(UndoPasteCreate(parentLeaf
, parent
, newItem
, selected
))
510 # Scroll to show new item (!!! redundant?)
511 tree
.EnsureVisible(newItem
)
512 tree
.SelectItem(newItem
)
513 if not tree
.IsVisible(newItem
):
514 tree
.ScrollTo(newItem
)
517 if g
.testWin
and tree
.IsHighlatable(newItem
):
519 tree
.needUpdate
= True
520 tree
.pendingHighLight
= newItem
522 tree
.pendingHighLight
= None
524 self
.SetStatusText('Pasted')
526 def OnCutDelete(self
, evt
):
527 selected
= tree
.selection
528 if not selected
: return # key pressed event
530 if evt
.GetId() == wxID_CUT
:
532 status
= 'Removed to clipboard'
534 self
.lastOp
= 'DELETE'
538 # If deleting top-level item, delete testWin
539 if selected
== g
.testWin
.item
:
543 # Remove highlight, update testWin
544 if g
.testWin
.highLight
:
545 g
.testWin
.highLight
.Remove()
546 tree
.needUpdate
= True
549 index
= tree
.ItemFullIndex(selected
)
550 parent
= tree
.GetPyData(tree
.GetItemParent(selected
)).treeObject()
551 elem
= tree
.RemoveLeaf(selected
)
552 undoMan
.RegisterUndo(UndoCutDelete(index
, parent
, elem
))
553 if evt
.GetId() == wxID_CUT
:
554 if self
.clipboard
: self
.clipboard
.unlink()
555 self
.clipboard
= elem
.cloneNode(True)
556 tree
.pendingHighLight
= None
560 self
.SetStatusText(status
)
562 def OnSubclass(self
, evt
):
563 selected
= tree
.selection
564 xxx
= tree
.GetPyData(selected
).treeObject()
566 subclass
= xxx
.subclass
567 dlg
= wxTextEntryDialog(self
, 'Subclass:', defaultValue
=subclass
)
568 if dlg
.ShowModal() == wxID_OK
:
569 subclass
= dlg
.GetValue()
571 elem
.setAttribute('subclass', subclass
)
573 elif elem
.hasAttribute('subclass'):
574 elem
.removeAttribute('subclass')
576 xxx
.subclass
= elem
.getAttribute('subclass')
577 tree
.SetItemText(selected
, xxx
.treeName())
578 panel
.pages
[0].box
.SetLabel(xxx
.panelName())
581 def OnEmbedPanel(self
, evt
):
582 conf
.embedPanel
= evt
.IsChecked()
584 # Remember last dimentions
585 conf
.panelX
, conf
.panelY
= self
.miniFrame
.GetPosition()
586 conf
.panelWidth
, conf
.panelHeight
= self
.miniFrame
.GetSize()
587 size
= self
.GetSize()
588 pos
= self
.GetPosition()
589 sizePanel
= panel
.GetSize()
590 panel
.Reparent(self
.splitter
)
591 self
.miniFrame
.GetSizer().Remove(panel
)
594 self
.SetDimensions(pos
.x
, pos
.y
, size
.width
+ sizePanel
.width
, size
.height
)
595 self
.splitter
.SplitVertically(tree
, panel
, conf
.sashPos
)
596 self
.miniFrame
.Show(False)
598 conf
.sashPos
= self
.splitter
.GetSashPosition()
599 pos
= self
.GetPosition()
600 size
= self
.GetSize()
601 sizePanel
= panel
.GetSize()
602 self
.splitter
.Unsplit(panel
)
603 sizer
= self
.miniFrame
.GetSizer()
604 panel
.Reparent(self
.miniFrame
)
606 sizer
.Add(panel
, 1, wxEXPAND
)
607 self
.miniFrame
.Show(True)
608 self
.miniFrame
.SetDimensions(conf
.panelX
, conf
.panelY
,
609 conf
.panelWidth
, conf
.panelHeight
)
612 self
.SetDimensions(pos
.x
, pos
.y
,
613 max(size
.width
- sizePanel
.width
, self
.minWidth
), size
.height
)
615 def OnShowTools(self
, evt
):
616 conf
.showTools
= evt
.IsChecked()
617 g
.tools
.Show(conf
.showTools
)
619 self
.toolsSizer
.Prepend(g
.tools
, 0, wxEXPAND
)
621 self
.toolsSizer
.Remove(g
.tools
)
622 self
.toolsSizer
.Layout()
624 def OnTest(self
, evt
):
625 if not tree
.selection
: return # key pressed event
626 tree
.ShowTestWindow(tree
.selection
)
628 def OnTestHide(self
, evt
):
629 tree
.CloseTestWindow()
631 # Find object by relative position
632 def FindObject(self
, item
, obj
):
633 # We simply perform depth-first traversal, sinse it's too much
634 # hassle to deal with all sizer/window combinations
635 w
= tree
.FindNodeObject(item
)
638 if tree
.ItemHasChildren(item
):
639 child
= tree
.GetFirstChild(item
)[0]
641 found
= self
.FindObject(child
, obj
)
642 if found
: return found
643 child
= tree
.GetNextSibling(child
)
646 def OnTestWinLeftDown(self
, evt
):
647 pos
= evt
.GetPosition()
648 self
.SetHandler(g
.testWin
)
649 g
.testWin
.Disconnect(wxID_ANY
, wxID_ANY
, wxEVT_LEFT_DOWN
)
650 item
= self
.FindObject(g
.testWin
.item
, evt
.GetEventObject())
652 tree
.SelectItem(item
)
653 self
.tb
.ToggleTool(self
.ID_TOOL_LOCATE
, False)
655 self
.SetStatusText('Selected %s' % tree
.GetItemText(item
))
657 self
.SetStatusText('Locate failed!')
659 def SetHandler(self
, w
, h
=None):
662 w
.SetCursor(wxCROSS_CURSOR
)
665 w
.SetCursor(wxNullCursor
)
666 for ch
in w
.GetChildren():
667 self
.SetHandler(ch
, h
)
669 def OnLocate(self
, evt
):
671 if evt
.GetId() == self
.ID_LOCATE
or \
672 evt
.GetId() == self
.ID_TOOL_LOCATE
and evt
.IsChecked():
673 self
.SetHandler(g
.testWin
, g
.testWin
)
674 g
.testWin
.Connect(wxID_ANY
, wxID_ANY
, wxEVT_LEFT_DOWN
, self
.OnTestWinLeftDown
)
675 if evt
.GetId() == self
.ID_LOCATE
:
676 self
.tb
.ToggleTool(self
.ID_TOOL_LOCATE
, True)
677 elif evt
.GetId() == self
.ID_TOOL_LOCATE
and not evt
.IsChecked():
678 self
.SetHandler(g
.testWin
, None)
679 g
.testWin
.Disconnect(wxID_ANY
, wxID_ANY
, wxEVT_LEFT_DOWN
)
680 self
.SetStatusText('Click somewhere in your test window now')
682 def OnRefresh(self
, evt
):
683 # If modified, apply first
684 selection
= tree
.selection
686 xxx
= tree
.GetPyData(selection
)
687 if xxx
and panel
.IsModified():
688 tree
.Apply(xxx
, selection
)
691 tree
.CreateTestWin(g
.testWin
.item
)
692 panel
.modified
= False
693 tree
.needUpdate
= False
695 def OnAutoRefresh(self
, evt
):
696 conf
.autoRefresh
= evt
.IsChecked()
697 self
.menuBar
.Check(self
.ID_AUTO_REFRESH
, conf
.autoRefresh
)
698 self
.tb
.ToggleTool(self
.ID_AUTO_REFRESH
, conf
.autoRefresh
)
700 def OnAbout(self
, evt
):
704 (c) Roman Rolinsky <rollrom@users.sourceforge.net>
705 Homepage: http://xrced.sourceforge.net\
707 dlg
= wxMessageDialog(self
, str, 'About XRCed', wxOK | wxCENTRE
)
711 def OnReadme(self
, evt
):
712 text
= open(os
.path
.join(basePath
, 'README.txt'), 'r').read()
713 dlg
= ScrolledMessageDialog(self
, text
, "XRCed README")
717 # Simple emulation of python command line
718 def OnDebugCMD(self
, evt
):
722 exec raw_input('C:\> ')
727 (etype
, value
, tb
) =sys
.exc_info()
728 tblist
=traceback
.extract_tb(tb
)[1:]
729 msg
=' '.join(traceback
.format_exception_only(etype
, value
)
730 +traceback
.format_list(tblist
))
733 def OnCreate(self
, evt
):
734 selected
= tree
.selection
735 if tree
.ctrl
: appendChild
= False
736 else: appendChild
= not tree
.NeedInsert(selected
)
737 xxx
= tree
.GetPyData(selected
)
741 # If has previous item, insert after it, else append to parent
743 parentLeaf
= tree
.GetItemParent(selected
)
745 # If has next item, insert, else append to parent
746 nextItem
= tree
.GetNextSibling(selected
)
747 parentLeaf
= tree
.GetItemParent(selected
)
748 # Expanded container (must have children)
749 elif tree
.shift
and tree
.IsExpanded(selected
) \
750 and tree
.GetChildrenCount(selected
, False):
751 nextItem
= tree
.GetFirstChild(selected
)[0]
752 parentLeaf
= selected
754 nextItem
= wxTreeItemId()
755 parentLeaf
= selected
756 parent
= tree
.GetPyData(parentLeaf
)
757 if parent
.hasChild
: parent
= parent
.child
760 className
= pullDownMenu
.createMap
[evt
.GetId()]
761 xxx
= MakeEmptyXXX(parent
, className
)
763 # Set default name for top-level windows
764 if parent
.__class
__ == xxxMainNode
:
765 cl
= xxx
.treeObject().__class
__
766 frame
.maxIDs
[cl
] += 1
767 xxx
.treeObject().name
= '%s%d' % (defaultIDs
[cl
], frame
.maxIDs
[cl
])
768 xxx
.treeObject().element
.setAttribute('name', xxx
.treeObject().name
)
770 # Insert new node, register undo
772 newItem
= tree
.InsertNode(parentLeaf
, parent
, elem
, nextItem
)
773 undoMan
.RegisterUndo(UndoPasteCreate(parentLeaf
, parent
, newItem
, selected
))
774 tree
.EnsureVisible(newItem
)
775 tree
.SelectItem(newItem
)
776 if not tree
.IsVisible(newItem
):
777 tree
.ScrollTo(newItem
)
780 if g
.testWin
and tree
.IsHighlatable(newItem
):
782 tree
.needUpdate
= True
783 tree
.pendingHighLight
= newItem
785 tree
.pendingHighLight
= None
789 # Replace one object with another
790 def OnReplace(self
, evt
):
791 selected
= tree
.selection
792 xxx
= tree
.GetPyData(selected
).treeObject()
794 parent
= elem
.parentNode
795 parentXXX
= xxx
.parent
797 className
= pullDownMenu
.createMap
[evt
.GetId() - 1000]
798 # Create temporary empty node (with default values)
799 dummy
= MakeEmptyDOM(className
)
800 xxxClass
= xxxDict
[className
]
801 # Remove non-compatible children
802 if tree
.ItemHasChildren(selected
) and not xxxClass
.hasChildren
:
803 tree
.DeleteChildren(selected
)
804 nodes
= elem
.childNodes
[:]
807 if node
.nodeType
!= minidom
.Node
.ELEMENT_NODE
: continue
811 if not xxxClass
.hasChildren
:
813 elif tag
not in xxxClass
.allParams
and \
814 (not xxxClass
.hasStyle
or tag
not in xxxClass
.styles
):
819 elem
.removeChild(node
)
822 # Copy parameters present in dummy but not in elem
823 for node
in dummy
.childNodes
:
826 elem
.appendChild(node
.cloneNode(True))
829 elem
.setAttribute('class', className
)
830 if elem
.hasAttribute('subclass'):
831 elem
.removeAttribute('subclass') # clear subclassing
832 # Re-create xxx element
833 xxx
= MakeXXXFromDOM(parentXXX
, elem
)
834 # Update parent in child objects
835 if tree
.ItemHasChildren(selected
):
836 i
, cookie
= tree
.GetFirstChild(selected
)
838 x
= tree
.GetPyData(i
)
840 if x
.hasChild
: x
.child
.parent
= xxx
841 i
, cookie
= tree
.GetNextChild(selected
, cookie
)
844 if tree
.GetPyData(selected
).hasChild
: # child container
845 container
= tree
.GetPyData(selected
)
846 container
.child
= xxx
847 container
.hasChildren
= xxx
.hasChildren
848 container
.isSizer
= xxx
.isSizer
850 tree
.SetPyData(selected
, xxx
)
851 tree
.SetItemText(selected
, xxx
.treeName())
852 tree
.SetItemImage(selected
, xxx
.treeImage())
854 # Set default name for top-level windows
855 if parent
.__class
__ == xxxMainNode
:
856 cl
= xxx
.treeObject().__class
__
857 frame
.maxIDs
[cl
] += 1
858 xxx
.treeObject().name
= '%s%d' % (defaultIDs
[cl
], frame
.maxIDs
[cl
])
859 xxx
.treeObject().element
.setAttribute('name', xxx
.treeObject().name
)
866 #undoMan.RegisterUndo(UndoPasteCreate(parentLeaf, parent, newItem, selected))
868 if g
.testWin
and tree
.IsHighlatable(selected
):
870 tree
.needUpdate
= True
871 tree
.pendingHighLight
= selected
873 tree
.pendingHighLight
= None
877 # Expand/collapse subtree
878 def OnExpand(self
, evt
):
879 if tree
.selection
: tree
.ExpandAll(tree
.selection
)
880 else: tree
.ExpandAll(tree
.root
)
881 def OnCollapse(self
, evt
):
882 if tree
.selection
: tree
.CollapseAll(tree
.selection
)
883 else: tree
.CollapseAll(tree
.root
)
885 def OnPullDownHighlight(self
, evt
):
886 menuId
= evt
.GetMenuId()
888 menu
= evt
.GetEventObject()
889 help = menu
.GetHelpString(menuId
)
890 self
.SetStatusText(help)
892 self
.SetStatusText('')
894 def OnUpdateUI(self
, evt
):
895 if evt
.GetId() in [wxID_CUT
, wxID_COPY
, self
.ID_DELETE
]:
896 evt
.Enable(tree
.selection
is not None and tree
.selection
!= tree
.root
)
897 elif evt
.GetId() in [wxID_PASTE
, self
.ID_TOOL_PASTE
]:
898 evt
.Enable((self
.clipboard
and tree
.selection
) != None)
899 elif evt
.GetId() == self
.ID_TEST
:
900 evt
.Enable(tree
.selection
is not None and tree
.selection
!= tree
.root
)
901 elif evt
.GetId() in [self
.ID_LOCATE
, self
.ID_TOOL_LOCATE
]:
902 evt
.Enable(g
.testWin
is not None)
903 elif evt
.GetId() == wxID_UNDO
: evt
.Enable(undoMan
.CanUndo())
904 elif evt
.GetId() == wxID_REDO
: evt
.Enable(undoMan
.CanRedo())
906 def OnIdle(self
, evt
):
907 if self
.inIdle
: return # Recursive call protection
912 self
.SetStatusText('Refreshing test window...')
914 tree
.CreateTestWin(g
.testWin
.item
)
916 self
.SetStatusText('')
917 tree
.needUpdate
= False
918 elif tree
.pendingHighLight
:
919 tree
.HighLight(tree
.pendingHighLight
)
924 # We don't let close panel window
925 def OnCloseMiniFrame(self
, evt
):
928 def OnCloseWindow(self
, evt
):
929 if not self
.AskSave(): return
930 if g
.testWin
: g
.testWin
.Destroy()
931 if not panel
.GetPageCount() == 2:
932 panel
.page2
.Destroy()
934 # If we don't do this, page does not get destroyed (a bug?)
936 if not self
.IsIconized():
937 conf
.x
, conf
.y
= self
.GetPosition()
938 conf
.width
, conf
.height
= self
.GetSize()
940 conf
.sashPos
= self
.splitter
.GetSashPosition()
942 conf
.panelX
, conf
.panelY
= self
.miniFrame
.GetPosition()
943 conf
.panelWidth
, conf
.panelHeight
= self
.miniFrame
.GetSize()
949 self
.clipboard
.unlink()
950 self
.clipboard
= None
952 self
.modified
= False
958 self
.SetTitle(progname
)
959 # Numbers for new controls
961 self
.maxIDs
[xxxPanel
] = self
.maxIDs
[xxxDialog
] = self
.maxIDs
[xxxFrame
] = \
962 self
.maxIDs
[xxxMenuBar
] = self
.maxIDs
[xxxMenu
] = self
.maxIDs
[xxxToolBar
] = \
963 self
.maxIDs
[xxxWizard
] = 0
965 def Open(self
, path
):
966 if not os
.path
.exists(path
):
967 wxLogError('File does not exists: %s' % path
)
969 # Try to read the file
973 dom
= minidom
.parse(f
)
975 # Set encoding global variable and default encoding
977 g
.currentEncoding
= dom
.encoding
978 wx
.SetDefaultPyEncoding(g
.currentEncoding
.encode())
980 g
.currentEncoding
= ''
982 self
.dataFile
= path
= os
.path
.abspath(path
)
983 dir = os
.path
.dirname(path
)
984 if dir: os
.chdir(dir)
986 self
.SetTitle(progname
+ ': ' + os
.path
.basename(path
))
988 # Nice exception printing
990 wxLogError(traceback
.format_exception(inf
[0], inf
[1], None)[-1])
991 wxLogError('Error reading file: %s' % path
)
996 def Indent(self
, node
, indent
= 0):
997 # Copy child list because it will change soon
998 children
= node
.childNodes
[:]
999 # Main node doesn't need to be indented
1001 text
= self
.domCopy
.createTextNode('\n' + ' ' * indent
)
1002 node
.parentNode
.insertBefore(text
, node
)
1004 # Append newline after last child, except for text nodes
1005 if children
[-1].nodeType
== minidom
.Node
.ELEMENT_NODE
:
1006 text
= self
.domCopy
.createTextNode('\n' + ' ' * indent
)
1007 node
.appendChild(text
)
1008 # Indent children which are elements
1010 if n
.nodeType
== minidom
.Node
.ELEMENT_NODE
:
1011 self
.Indent(n
, indent
+ 2)
1013 def Save(self
, path
):
1017 if tree
.selection
and panel
.IsModified():
1018 self
.OnRefresh(wxCommandEvent())
1019 if g
.currentEncoding
:
1020 f
= codecs
.open(path
, 'wt', g
.currentEncoding
)
1022 f
= codecs
.open(path
, 'wt')
1023 # Make temporary copy for formatting it
1024 # !!! We can't clone dom node, it works only once
1025 #self.domCopy = tree.dom.cloneNode(True)
1026 self
.domCopy
= MyDocument()
1027 mainNode
= self
.domCopy
.appendChild(tree
.mainNode
.cloneNode(True))
1028 self
.Indent(mainNode
)
1029 self
.domCopy
.writexml(f
, encoding
= g
.currentEncoding
)
1031 self
.domCopy
.unlink()
1033 self
.modified
= False
1034 panel
.SetModified(False)
1036 wxLogError('Error writing file: %s' % path
)
1040 if not (self
.modified
or panel
.IsModified()): return True
1041 flags
= wxICON_EXCLAMATION | wxYES_NO | wxCANCEL | wxCENTRE
1042 dlg
= wxMessageDialog( self
, 'File is modified. Save before exit?',
1043 'Save before too late?', flags
)
1044 say
= dlg
.ShowModal()
1047 self
.OnSaveOrSaveAs(wxCommandEvent(wxID_SAVE
))
1048 # If save was successful, modified flag is unset
1049 if not self
.modified
: return True
1050 elif say
== wxID_NO
:
1051 self
.modified
= False
1052 panel
.SetModified(False)
1059 ################################################################################
1062 print >> sys
.stderr
, 'usage: xrced [-dhiv] [file]'
1067 # Process comand-line
1070 opts
, args
= getopt
.getopt(sys
.argv
[1:], 'dhiv')
1078 print 'XRCed version', version
1081 except getopt
.GetoptError
:
1082 if wxPlatform
!= '__WXMAC__': # macs have some extra parameters
1083 print >> sys
.stderr
, 'Unknown option'
1087 self
.SetAppName('xrced')
1090 conf
= g
.conf
= wxConfig(style
= wxCONFIG_USE_LOCAL_FILE
)
1091 conf
.autoRefresh
= conf
.ReadInt('autorefresh', True)
1092 pos
= conf
.ReadInt('x', -1), conf
.ReadInt('y', -1)
1093 size
= conf
.ReadInt('width', 800), conf
.ReadInt('height', 600)
1094 conf
.embedPanel
= conf
.ReadInt('embedPanel', True)
1095 conf
.showTools
= conf
.ReadInt('showTools', True)
1096 conf
.sashPos
= conf
.ReadInt('sashPos', 200)
1097 # read recently used files
1098 recentfiles
=conf
.Read('recentFiles','')
1101 for fil
in recentfiles
.split('|'):
1102 conf
.recentfiles
[wxNewId()]=fil
1103 if not conf
.embedPanel
:
1104 conf
.panelX
= conf
.ReadInt('panelX', -1)
1105 conf
.panelY
= conf
.ReadInt('panelY', -1)
1107 conf
.panelX
= conf
.panelY
= -1
1108 conf
.panelWidth
= conf
.ReadInt('panelWidth', 200)
1109 conf
.panelHeight
= conf
.ReadInt('panelHeight', 200)
1110 conf
.panic
= not conf
.HasEntry('nopanic')
1112 wxFileSystem_AddHandler(wxMemoryFSHandler())
1113 wxInitAllImageHandlers()
1115 frame
= Frame(pos
, size
)
1118 # Load file after showing
1121 frame
.open = frame
.Open(args
[0])
1128 wc
= wxConfigBase_Get()
1129 wc
.WriteInt('autorefresh', conf
.autoRefresh
)
1130 wc
.WriteInt('x', conf
.x
)
1131 wc
.WriteInt('y', conf
.y
)
1132 wc
.WriteInt('width', conf
.width
)
1133 wc
.WriteInt('height', conf
.height
)
1134 wc
.WriteInt('embedPanel', conf
.embedPanel
)
1135 wc
.WriteInt('showTools', conf
.showTools
)
1136 if not conf
.embedPanel
:
1137 wc
.WriteInt('panelX', conf
.panelX
)
1138 wc
.WriteInt('panelY', conf
.panelY
)
1139 wc
.WriteInt('sashPos', conf
.sashPos
)
1140 wc
.WriteInt('panelWidth', conf
.panelWidth
)
1141 wc
.WriteInt('panelHeight', conf
.panelHeight
)
1142 wc
.WriteInt('nopanic', True)
1143 wc
.Write('recentFiles', '|'.join(conf
.recentfiles
.values()[-5:]))
1147 app
= App(0, useBestVisual
=False)
1148 #app.SetAssertMode(wxPYAPP_ASSERT_LOG)
1154 if __name__
== '__main__':