]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/tools/XRCed/xrced.py
0.1.8-3
[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 wxWidgets/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 from globals import *
24 import os, sys, getopt, re, traceback, tempfile, shutil, cPickle
25 from xml.parsers import expat
26
27 # Local modules
28 from tree import * # imports xxx which imports params
29 from panel import *
30 from tools import *
31 from params import genericStyles
32 # Cleanup recursive import sideeffects, otherwise we can't create undoMan
33 import undo
34 undo.ParamPage = ParamPage
35 undoMan = g.undoMan = UndoManager()
36
37 # Set application path for loading resources
38 if __name__ == '__main__':
39 basePath = os.path.dirname(sys.argv[0])
40 else:
41 basePath = os.path.dirname(__file__)
42
43 # 1 adds CMD command to Help menu
44 debug = 0
45
46 g.helpText = """\
47 <HTML><H2>Welcome to XRC<font color="blue">ed</font></H2><H3><font color="green">DON'T PANIC :)</font></H3>
48 Read this note before clicking on anything!<P>
49 To start select tree root, then popup menu with your right mouse button,
50 select "Append Child", and then any command.<P>
51 Or just press one of the buttons on the tools palette.<P>
52 Enter XML ID, change properties, create children.<P>
53 To test your interface select Test command (View menu).<P>
54 Consult README file for the details.</HTML>
55 """
56
57 defaultIDs = {xxxPanel:'PANEL', xxxDialog:'DIALOG', xxxFrame:'FRAME',
58 xxxMenuBar:'MENUBAR', xxxMenu:'MENU', xxxToolBar:'TOOLBAR',
59 xxxWizard:'WIZARD', xxxBitmap:'BITMAP', xxxIcon:'ICON'}
60
61 defaultName = 'UNTITLED.xrc'
62
63 ################################################################################
64
65 # ScrolledMessageDialog - modified from wxPython lib to set fixed-width font
66 class ScrolledMessageDialog(wx.Dialog):
67 def __init__(self, parent, msg, caption, pos = wx.DefaultPosition, size = (500,300)):
68 from wx.lib.layoutf import Layoutf
69 wx.Dialog.__init__(self, parent, -1, caption, pos, size)
70 text = wx.TextCtrl(self, -1, msg, wx.DefaultPosition,
71 wx.DefaultSize, wx.TE_MULTILINE | wx.TE_READONLY)
72 text.SetFont(g.modernFont())
73 dc = wx.WindowDC(text)
74 # !!! possible bug - GetTextExtent without font returns sysfont dims
75 w, h = dc.GetFullTextExtent(' ', g.modernFont())[:2]
76 ok = wx.Button(self, wx.ID_OK, "OK")
77 text.SetConstraints(Layoutf('t=t5#1;b=t5#2;l=l5#1;r=r5#1', (self,ok)))
78 text.SetSize((w * 80 + 30, h * 40))
79 text.ShowPosition(1)
80 ok.SetConstraints(Layoutf('b=b5#1;x%w50#1;w!80;h!25', (self,)))
81 self.SetAutoLayout(True)
82 self.Fit()
83 self.CenterOnScreen(wx.BOTH)
84
85 ################################################################################
86
87 # Event handler for using during location
88 class Locator(wx.EvtHandler):
89 def ProcessEvent(self, evt):
90 print evt
91
92 class Frame(wx.Frame):
93 def __init__(self, pos, size):
94 wx.Frame.__init__(self, None, -1, '', pos, size)
95 global frame
96 frame = g.frame = self
97 bar = self.CreateStatusBar(2)
98 bar.SetStatusWidths([-1, 40])
99 self.SetIcon(images.getIconIcon())
100
101 # Idle flag
102 self.inIdle = False
103
104 # Load our own resources
105 self.res = xrc.XmlResource('')
106 # !!! Blocking of assert failure occurring in older unicode builds
107 try:
108 quietlog = wx.LogNull()
109 self.res.Load(os.path.join(basePath, 'xrced.xrc'))
110 except wx._core.PyAssertionError:
111 print 'PyAssertionError was ignored'
112
113 # Make menus
114 menuBar = wx.MenuBar()
115
116 menu = wx.Menu()
117 menu.Append(wx.ID_NEW, '&New\tCtrl-N', 'New file')
118 menu.AppendSeparator()
119 menu.Append(wx.ID_OPEN, '&Open...\tCtrl-O', 'Open XRC file')
120 self.recentMenu = wx.Menu()
121 self.AppendRecent(self.recentMenu)
122 menu.AppendMenu(-1, 'Open Recent', self.recentMenu, 'Open a recent file')
123 menu.AppendSeparator()
124 menu.Append(wx.ID_SAVE, '&Save\tCtrl-S', 'Save XRC file')
125 menu.Append(wx.ID_SAVEAS, 'Save &As...', 'Save XRC file under different name')
126 self.ID_GENERATE_PYTHON = wx.NewId()
127 menu.Append(self.ID_GENERATE_PYTHON, '&Generate Python...',
128 'Generate a Python module that uses this XRC')
129 menu.AppendSeparator()
130 menu.Append(wx.ID_EXIT, '&Quit\tCtrl-Q', 'Exit application')
131
132 menuBar.Append(menu, '&File')
133
134 menu = wx.Menu()
135 menu.Append(wx.ID_UNDO, '&Undo\tCtrl-Z', 'Undo')
136 menu.Append(wx.ID_REDO, '&Redo\tCtrl-Y', 'Redo')
137 menu.AppendSeparator()
138 menu.Append(wx.ID_CUT, 'Cut\tCtrl-X', 'Cut to the clipboard')
139 menu.Append(wx.ID_COPY, '&Copy\tCtrl-C', 'Copy to the clipboard')
140 menu.Append(wx.ID_PASTE, '&Paste\tCtrl-V', 'Paste from the clipboard')
141 self.ID_DELETE = wx.NewId()
142 menu.Append(self.ID_DELETE, '&Delete\tCtrl-D', 'Delete object')
143 menu.AppendSeparator()
144 self.ID_LOCATE = wx.NewId()
145 self.ID_TOOL_LOCATE = wx.NewId()
146 self.ID_TOOL_PASTE = wx.NewId()
147 menu.Append(self.ID_LOCATE, '&Locate\tCtrl-L', 'Locate control in test window and select it')
148 menuBar.Append(menu, '&Edit')
149
150 menu = wx.Menu()
151 self.ID_EMBED_PANEL = wx.NewId()
152 menu.Append(self.ID_EMBED_PANEL, '&Embed Panel',
153 'Toggle embedding properties panel in the main window', True)
154 menu.Check(self.ID_EMBED_PANEL, conf.embedPanel)
155 self.ID_SHOW_TOOLS = wx.NewId()
156 menu.Append(self.ID_SHOW_TOOLS, 'Show &Tools', 'Toggle tools', True)
157 menu.Check(self.ID_SHOW_TOOLS, conf.showTools)
158 menu.AppendSeparator()
159 self.ID_TEST = wx.NewId()
160 menu.Append(self.ID_TEST, '&Test\tF5', 'Show test window')
161 self.ID_REFRESH = wx.NewId()
162 menu.Append(self.ID_REFRESH, '&Refresh\tCtrl-R', 'Refresh test window')
163 self.ID_AUTO_REFRESH = wx.NewId()
164 menu.Append(self.ID_AUTO_REFRESH, '&Auto-refresh\tCtrl-A',
165 'Toggle auto-refresh mode', True)
166 menu.Check(self.ID_AUTO_REFRESH, conf.autoRefresh)
167 self.ID_TEST_HIDE = wx.NewId()
168 menu.Append(self.ID_TEST_HIDE, '&Hide\tCtrl-H', 'Close test window')
169 menuBar.Append(menu, '&View')
170
171 menu = wx.Menu()
172 self.ID_MOVEUP = wx.NewId()
173 menu.Append(self.ID_MOVEUP, '&Up', 'Move before previous sibling')
174 self.ID_MOVEDOWN = wx.NewId()
175 menu.Append(self.ID_MOVEDOWN, '&Down', 'Move after next sibling')
176 self.ID_MOVELEFT = wx.NewId()
177 menu.Append(self.ID_MOVELEFT, '&Make sibling', 'Make sibling of parent')
178 self.ID_MOVERIGHT = wx.NewId()
179 menu.Append(self.ID_MOVERIGHT, '&Make child', 'Make child of previous sibling')
180 menuBar.Append(menu, '&Move')
181
182 menu = wx.Menu()
183 menu.Append(wx.ID_ABOUT, '&About...', 'About XCRed')
184 self.ID_README = wx.NewId()
185 menu.Append(self.ID_README, '&Readme...', 'View the README file')
186 if debug:
187 self.ID_DEBUG_CMD = wx.NewId()
188 menu.Append(self.ID_DEBUG_CMD, 'CMD', 'Python command line')
189 wx.EVT_MENU(self, self.ID_DEBUG_CMD, self.OnDebugCMD)
190 menuBar.Append(menu, '&Help')
191
192 self.menuBar = menuBar
193 self.SetMenuBar(menuBar)
194
195 # Create toolbar
196 tb = self.CreateToolBar(wx.TB_HORIZONTAL | wx.NO_BORDER | wx.TB_FLAT)
197 tb.SetToolBitmapSize((24,24))
198 new_bmp = wx.ArtProvider.GetBitmap(wx.ART_NORMAL_FILE, wx.ART_TOOLBAR)
199 open_bmp = wx.ArtProvider.GetBitmap(wx.ART_FILE_OPEN, wx.ART_TOOLBAR)
200 save_bmp = wx.ArtProvider.GetBitmap(wx.ART_FILE_SAVE, wx.ART_TOOLBAR)
201 undo_bmp = wx.ArtProvider.GetBitmap(wx.ART_UNDO, wx.ART_TOOLBAR)
202 redo_bmp = wx.ArtProvider.GetBitmap(wx.ART_REDO, wx.ART_TOOLBAR)
203 cut_bmp = wx.ArtProvider.GetBitmap(wx.ART_CUT, wx.ART_TOOLBAR)
204 copy_bmp = wx.ArtProvider.GetBitmap(wx.ART_COPY, wx.ART_TOOLBAR)
205 paste_bmp= wx.ArtProvider.GetBitmap(wx.ART_PASTE, wx.ART_TOOLBAR)
206
207 tb.AddSimpleTool(wx.ID_NEW, new_bmp, 'New', 'New file')
208 tb.AddSimpleTool(wx.ID_OPEN, open_bmp, 'Open', 'Open file')
209 tb.AddSimpleTool(wx.ID_SAVE, save_bmp, 'Save', 'Save file')
210 tb.AddControl(wx.StaticLine(tb, -1, size=(-1,23), style=wx.LI_VERTICAL))
211 tb.AddSimpleTool(wx.ID_UNDO, undo_bmp, 'Undo', 'Undo')
212 tb.AddSimpleTool(wx.ID_REDO, redo_bmp, 'Redo', 'Redo')
213 tb.AddControl(wx.StaticLine(tb, -1, size=(-1,23), style=wx.LI_VERTICAL))
214 tb.AddSimpleTool(wx.ID_CUT, cut_bmp, 'Cut', 'Cut')
215 tb.AddSimpleTool(wx.ID_COPY, copy_bmp, 'Copy', 'Copy')
216 tb.AddSimpleTool(self.ID_TOOL_PASTE, paste_bmp, 'Paste', 'Paste')
217 tb.AddControl(wx.StaticLine(tb, -1, size=(-1,23), style=wx.LI_VERTICAL))
218 tb.AddSimpleTool(self.ID_TOOL_LOCATE,
219 images.getLocateBitmap(), #images.getLocateArmedBitmap(),
220 'Locate', 'Locate control in test window and select it', True)
221 tb.AddControl(wx.StaticLine(tb, -1, size=(-1,23), style=wx.LI_VERTICAL))
222 tb.AddSimpleTool(self.ID_TEST, images.getTestBitmap(), 'Test', 'Test window')
223 tb.AddSimpleTool(self.ID_REFRESH, images.getRefreshBitmap(),
224 'Refresh', 'Refresh view')
225 tb.AddSimpleTool(self.ID_AUTO_REFRESH, images.getAutoRefreshBitmap(),
226 'Auto-refresh', 'Toggle auto-refresh mode', True)
227 tb.AddControl(wx.StaticLine(tb, -1, size=(-1,23), style=wx.LI_VERTICAL))
228 tb.AddSimpleTool(self.ID_MOVEUP, images.getToolMoveUpBitmap(),
229 'Up', 'Move before previous sibling')
230 tb.AddSimpleTool(self.ID_MOVEDOWN, images.getToolMoveDownBitmap(),
231 'Down', 'Move after next sibling')
232 tb.AddSimpleTool(self.ID_MOVELEFT, images.getToolMoveLeftBitmap(),
233 'Make Sibling', 'Make sibling of parent')
234 tb.AddSimpleTool(self.ID_MOVERIGHT, images.getToolMoveRightBitmap(),
235 'Make Child', 'Make child of previous sibling')
236 # if wx.Platform == '__WXGTK__':
237 # tb.AddSeparator() # otherwise auto-refresh sticks in status line
238 tb.ToggleTool(self.ID_AUTO_REFRESH, conf.autoRefresh)
239 tb.Realize()
240
241 self.tb = tb
242 self.minWidth = tb.GetSize()[0] # minimal width is the size of toolbar
243
244 # File
245 wx.EVT_MENU(self, wx.ID_NEW, self.OnNew)
246 wx.EVT_MENU(self, wx.ID_OPEN, self.OnOpen)
247 wx.EVT_MENU(self, wx.ID_SAVE, self.OnSaveOrSaveAs)
248 wx.EVT_MENU(self, wx.ID_SAVEAS, self.OnSaveOrSaveAs)
249 wx.EVT_MENU(self, self.ID_GENERATE_PYTHON, self.OnGeneratePython)
250 wx.EVT_MENU(self, wx.ID_EXIT, self.OnExit)
251 # Edit
252 wx.EVT_MENU(self, wx.ID_UNDO, self.OnUndo)
253 wx.EVT_MENU(self, wx.ID_REDO, self.OnRedo)
254 wx.EVT_MENU(self, wx.ID_CUT, self.OnCutDelete)
255 wx.EVT_MENU(self, wx.ID_COPY, self.OnCopy)
256 wx.EVT_MENU(self, wx.ID_PASTE, self.OnPaste)
257 wx.EVT_MENU(self, self.ID_TOOL_PASTE, self.OnPaste)
258 wx.EVT_MENU(self, self.ID_DELETE, self.OnCutDelete)
259 wx.EVT_MENU(self, self.ID_LOCATE, self.OnLocate)
260 wx.EVT_MENU(self, self.ID_TOOL_LOCATE, self.OnLocate)
261 # View
262 wx.EVT_MENU(self, self.ID_EMBED_PANEL, self.OnEmbedPanel)
263 wx.EVT_MENU(self, self.ID_SHOW_TOOLS, self.OnShowTools)
264 wx.EVT_MENU(self, self.ID_TEST, self.OnTest)
265 wx.EVT_MENU(self, self.ID_REFRESH, self.OnRefresh)
266 wx.EVT_MENU(self, self.ID_AUTO_REFRESH, self.OnAutoRefresh)
267 wx.EVT_MENU(self, self.ID_TEST_HIDE, self.OnTestHide)
268 # Move
269 wx.EVT_MENU(self, self.ID_MOVEUP, self.OnMoveUp)
270 wx.EVT_MENU(self, self.ID_MOVEDOWN, self.OnMoveDown)
271 wx.EVT_MENU(self, self.ID_MOVELEFT, self.OnMoveLeft)
272 wx.EVT_MENU(self, self.ID_MOVERIGHT, self.OnMoveRight)
273 # Help
274 wx.EVT_MENU(self, wx.ID_ABOUT, self.OnAbout)
275 wx.EVT_MENU(self, self.ID_README, self.OnReadme)
276
277 # Update events
278 wx.EVT_UPDATE_UI(self, wx.ID_SAVE, self.OnUpdateUI)
279 wx.EVT_UPDATE_UI(self, wx.ID_CUT, self.OnUpdateUI)
280 wx.EVT_UPDATE_UI(self, wx.ID_COPY, self.OnUpdateUI)
281 wx.EVT_UPDATE_UI(self, wx.ID_PASTE, self.OnUpdateUI)
282 wx.EVT_UPDATE_UI(self, self.ID_LOCATE, self.OnUpdateUI)
283 wx.EVT_UPDATE_UI(self, self.ID_TOOL_LOCATE, self.OnUpdateUI)
284 wx.EVT_UPDATE_UI(self, self.ID_TOOL_PASTE, self.OnUpdateUI)
285 wx.EVT_UPDATE_UI(self, wx.ID_UNDO, self.OnUpdateUI)
286 wx.EVT_UPDATE_UI(self, wx.ID_REDO, self.OnUpdateUI)
287 wx.EVT_UPDATE_UI(self, self.ID_DELETE, self.OnUpdateUI)
288 wx.EVT_UPDATE_UI(self, self.ID_TEST, self.OnUpdateUI)
289 wx.EVT_UPDATE_UI(self, self.ID_REFRESH, self.OnUpdateUI)
290
291 # Build interface
292 sizer = wx.BoxSizer(wx.VERTICAL)
293 sizer.Add(wx.StaticLine(self, -1), 0, wx.EXPAND)
294 # Horizontal sizer for toolbar and splitter
295 self.toolsSizer = sizer1 = wx.BoxSizer()
296 splitter = wx.SplitterWindow(self, -1, style=wx.SP_3DSASH)
297 self.splitter = splitter
298 splitter.SetMinimumPaneSize(100)
299 # Create tree
300 global tree
301 g.tree = tree = XML_Tree(splitter, -1)
302
303 # Init pull-down menu data
304 global pullDownMenu
305 g.pullDownMenu = pullDownMenu = PullDownMenu(self)
306
307 # Vertical toolbar for GUI buttons
308 g.tools = tools = Tools(self)
309 tools.Show(conf.showTools)
310 if conf.showTools: sizer1.Add(tools, 0, wx.EXPAND)
311
312 tree.RegisterKeyEvents()
313
314 # Miniframe for split mode
315 miniFrame = wx.MiniFrame(self, -1, 'Properties & Style',
316 (conf.panelX, conf.panelY),
317 (conf.panelWidth, conf.panelHeight))
318 self.miniFrame = miniFrame
319 sizer2 = wx.BoxSizer()
320 miniFrame.SetAutoLayout(True)
321 miniFrame.SetSizer(sizer2)
322 wx.EVT_CLOSE(self.miniFrame, self.OnCloseMiniFrame)
323 # Create panel for parameters
324 global panel
325 if conf.embedPanel:
326 panel = Panel(splitter)
327 # Set plitter windows
328 splitter.SplitVertically(tree, panel, conf.sashPos)
329 else:
330 panel = Panel(miniFrame)
331 sizer2.Add(panel, 1, wx.EXPAND)
332 miniFrame.Show(True)
333 splitter.Initialize(tree)
334 sizer1.Add(splitter, 1, wx.EXPAND)
335 sizer.Add(sizer1, 1, wx.EXPAND)
336 self.SetAutoLayout(True)
337 self.SetSizer(sizer)
338
339 # Initialize
340 self.Clear()
341
342 # Other events
343 wx.EVT_IDLE(self, self.OnIdle)
344 wx.EVT_CLOSE(self, self.OnCloseWindow)
345 wx.EVT_KEY_DOWN(self, tools.OnKeyDown)
346 wx.EVT_KEY_UP(self, tools.OnKeyUp)
347 wx.EVT_ICONIZE(self, self.OnIconize)
348
349 def AppendRecent(self, menu):
350 # add recently used files to the menu
351 for id,name in conf.recentfiles.iteritems():
352 menu.Append(id,name)
353 wx.EVT_MENU(self,id,self.OnRecentFile)
354 return
355
356 def OnRecentFile(self,evt):
357 # open recently used file
358 if not self.AskSave(): return
359 wx.BeginBusyCursor()
360 try:
361 path=conf.recentfiles[evt.GetId()]
362 if self.Open(path):
363 self.SetStatusText('Data loaded')
364 else:
365 self.SetStatusText('Failed')
366 except KeyError:
367 self.SetStatusText('No such file')
368 wx.EndBusyCursor()
369
370 def OnNew(self, evt):
371 if not self.AskSave(): return
372 self.Clear()
373
374 def OnOpen(self, evt):
375 if not self.AskSave(): return
376 dlg = wx.FileDialog(self, 'Open', os.path.dirname(self.dataFile),
377 '', '*.xrc', wx.OPEN | wx.CHANGE_DIR)
378 if dlg.ShowModal() == wx.ID_OK:
379 path = dlg.GetPath()
380 self.SetStatusText('Loading...')
381 wx.BeginBusyCursor()
382 try:
383 if self.Open(path):
384 self.SetStatusText('Data loaded')
385 else:
386 self.SetStatusText('Failed')
387 self.SaveRecent(path)
388 finally:
389 wx.EndBusyCursor()
390 dlg.Destroy()
391
392 def OnSaveOrSaveAs(self, evt):
393 if evt.GetId() == wx.ID_SAVEAS or not self.dataFile:
394 if self.dataFile: name = ''
395 else: name = defaultName
396 dirname = os.path.abspath(os.path.dirname(self.dataFile))
397 dlg = wx.FileDialog(self, 'Save As', dirname, name, '*.xrc',
398 wx.SAVE | wx.OVERWRITE_PROMPT | wx.CHANGE_DIR)
399 if dlg.ShowModal() == wx.ID_OK:
400 path = dlg.GetPath()
401 if isinstance(path, unicode):
402 path = path.encode(sys.getfilesystemencoding())
403 dlg.Destroy()
404 else:
405 dlg.Destroy()
406 return
407
408 if conf.localconf:
409 # if we already have a localconf then it needs to be
410 # copied to a new config with the new name
411 lc = conf.localconf
412 nc = self.CreateLocalConf(path)
413 flag, key, idx = lc.GetFirstEntry()
414 while flag:
415 nc.Write(key, lc.Read(key))
416 flag, key, idx = lc.GetNextEntry(idx)
417 conf.localconf = nc
418 else:
419 # otherwise create a new one
420 conf.localconf = self.CreateLocalConf(path)
421 else:
422 path = self.dataFile
423 self.SetStatusText('Saving...')
424 wx.BeginBusyCursor()
425 try:
426 try:
427 tmpFile,tmpName = tempfile.mkstemp(prefix='xrced-')
428 os.close(tmpFile)
429 self.Save(tmpName) # save temporary file first
430 shutil.move(tmpName, path)
431 self.dataFile = path
432 if conf.localconf.ReadBool("autogenerate", False):
433 pypath = conf.localconf.Read("filename")
434 embed = conf.localconf.ReadBool("embedResource", False)
435 genGettext = conf.localconf.ReadBool("genGettext", False)
436 self.GeneratePython(self.dataFile, pypath, embed, genGettext)
437
438 self.SetStatusText('Data saved')
439 self.SaveRecent(path)
440 except IOError:
441 self.SetStatusText('Failed')
442 finally:
443 wx.EndBusyCursor()
444
445 def SaveRecent(self,path):
446 # append to recently used files
447 if path not in conf.recentfiles.values():
448 newid = wx.NewId()
449 self.recentMenu.Append(newid, path)
450 wx.EVT_MENU(self, newid, self.OnRecentFile)
451 conf.recentfiles[newid] = path
452
453 def GeneratePython(self, dataFile, pypath, embed, genGettext):
454 try:
455 import wx.tools.pywxrc
456 rescomp = wx.tools.pywxrc.XmlResourceCompiler()
457 rescomp.MakePythonModule([dataFile], pypath, embed, genGettext)
458 except:
459 inf = sys.exc_info()
460 wx.LogError(traceback.format_exception(inf[0], inf[1], None)[-1])
461 wx.LogError('Error generating python code : %s' % pypath)
462 raise
463
464
465 def OnGeneratePython(self, evt):
466 if self.modified or not conf.localconf:
467 wx.MessageBox("Save the XRC file first!", "Error")
468 return
469
470 dlg = PythonOptions(self, conf.localconf, self.dataFile)
471 dlg.ShowModal()
472 dlg.Destroy()
473
474
475 def OnExit(self, evt):
476 self.Close()
477
478 def OnUndo(self, evt):
479 # Extra check to not mess with idle updating
480 if undoMan.CanUndo():
481 undoMan.Undo()
482
483 def OnRedo(self, evt):
484 if undoMan.CanRedo():
485 undoMan.Redo()
486
487 def OnCopy(self, evt):
488 selected = tree.selection
489 if not selected: return # key pressed event
490 xxx = tree.GetPyData(selected)
491 if wx.TheClipboard.Open():
492 data = wx.CustomDataObject('XRCED')
493 # Set encoding in header
494 # (False,True)
495 s = xxx.element.toxml(encoding=expat.native_encoding)
496 data.SetData(cPickle.dumps(s))
497 wx.TheClipboard.SetData(data)
498 wx.TheClipboard.Close()
499 self.SetStatusText('Copied')
500 else:
501 wx.MessageBox("Unable to open the clipboard", "Error")
502
503 def OnPaste(self, evt):
504 selected = tree.selection
505 if not selected: return # key pressed event
506 # For pasting with Ctrl pressed
507 appendChild = True
508 if evt.GetId() == pullDownMenu.ID_PASTE_SIBLING: appendChild = False
509 elif evt.GetId() == self.ID_TOOL_PASTE:
510 if g.tree.ctrl: appendChild = False
511 else: appendChild = not tree.NeedInsert(selected)
512 else: appendChild = not tree.NeedInsert(selected)
513 xxx = tree.GetPyData(selected)
514 if not appendChild:
515 # If has next item, insert, else append to parent
516 nextItem = tree.GetNextSibling(selected)
517 parentLeaf = tree.GetItemParent(selected)
518 # Expanded container (must have children)
519 elif tree.IsExpanded(selected) and tree.GetChildrenCount(selected, False):
520 # Insert as first child
521 nextItem = tree.GetFirstChild(selected)[0]
522 parentLeaf = selected
523 else:
524 # No children or unexpanded item - appendChild stays True
525 nextItem = wx.TreeItemId() # no next item
526 parentLeaf = selected
527 parent = tree.GetPyData(parentLeaf).treeObject()
528
529 # Create a copy of clipboard pickled element
530 success = False
531 if wx.TheClipboard.Open():
532 data = wx.CustomDataObject('XRCED')
533 if wx.TheClipboard.IsSupported(data.GetFormat()):
534 success = wx.TheClipboard.GetData(data)
535 wx.TheClipboard.Close()
536
537 if not success:
538 wx.MessageBox(
539 "There is no data in the clipboard in the required format",
540 "Error")
541 return
542
543 xml = cPickle.loads(data.GetData()) # xml representation of element
544 elem = minidom.parseString(xml).childNodes[0]
545
546 # Tempopary xxx object to test things
547 xxx = MakeXXXFromDOM(parent, elem)
548
549 # Check compatibility
550 if not self.ItemsAreCompatible(parent, xxx.treeObject()): return
551
552 # Check parent and child relationships.
553 # If parent is sizer or notebook, child is of wrong class or
554 # parent is normal window, child is child container then detach child.
555 isChildContainer = isinstance(xxx, xxxChildContainer)
556 parentIsBook = parent.__class__ in [xxxNotebook, xxxChoicebook, xxxListbook]
557 if isChildContainer and \
558 ((parent.isSizer and not isinstance(xxx, xxxSizerItem)) or \
559 (parentIsBook and not isinstance(xxx, xxxPage)) or \
560 not (parent.isSizer or parentIsBook)):
561 elem.removeChild(xxx.child.element) # detach child
562 elem.unlink() # delete child container
563 elem = xxx.child.element # replace
564 # This may help garbage collection
565 xxx.child.parent = None
566 isChildContainer = False
567 # Parent is sizer or notebook, child is not child container
568 if parent.isSizer and not isChildContainer and not isinstance(xxx, xxxSpacer):
569 # Create sizer item element
570 sizerItemElem = MakeEmptyDOM(parent.itemTag)
571 sizerItemElem.appendChild(elem)
572 elem = sizerItemElem
573 elif isinstance(parent, xxxNotebook) and not isChildContainer:
574 pageElem = MakeEmptyDOM('notebookpage')
575 pageElem.appendChild(elem)
576 elem = pageElem
577 elif isinstance(parent, xxxChoicebook) and not isChildContainer:
578 pageElem = MakeEmptyDOM('choicebookpage')
579 pageElem.appendChild(elem)
580 elem = pageElem
581 elif isinstance(parent, xxxListbook) and not isChildContainer:
582 pageElem = MakeEmptyDOM('listbookpage')
583 pageElem.appendChild(elem)
584 elem = pageElem
585 # Insert new node, register undo
586 newItem = tree.InsertNode(parentLeaf, parent, elem, nextItem)
587 undoMan.RegisterUndo(UndoPasteCreate(parentLeaf, parent, newItem, selected))
588 # Scroll to show new item (!!! redundant?)
589 tree.EnsureVisible(newItem)
590 tree.SelectItem(newItem)
591 if not tree.IsVisible(newItem):
592 tree.ScrollTo(newItem)
593 tree.Refresh()
594 # Update view?
595 if g.testWin and tree.IsHighlatable(newItem):
596 if conf.autoRefresh:
597 tree.needUpdate = True
598 tree.pendingHighLight = newItem
599 else:
600 tree.pendingHighLight = None
601 self.SetModified()
602 self.SetStatusText('Pasted')
603
604
605 def ItemsAreCompatible(self, parent, child):
606 # Check compatibility
607 error = False
608 # Top-level
609 if child.__class__ in [xxxDialog, xxxFrame, xxxWizard]:
610 # Top-level classes
611 if parent.__class__ != xxxMainNode: error = True
612 elif child.__class__ == xxxMenuBar:
613 # Menubar can be put in frame or dialog
614 if parent.__class__ not in [xxxMainNode, xxxFrame, xxxDialog]: error = True
615 elif child.__class__ == xxxToolBar:
616 # Toolbar can be top-level of child of panel or frame
617 if parent.__class__ not in [xxxMainNode, xxxPanel, xxxFrame] and \
618 not parent.isSizer: error = True
619 elif child.__class__ == xxxPanel and parent.__class__ == xxxMainNode:
620 pass
621 elif child.__class__ == xxxSpacer:
622 if not parent.isSizer: error = True
623 elif child.__class__ == xxxSeparator:
624 if not parent.__class__ in [xxxMenu, xxxToolBar]: error = True
625 elif child.__class__ == xxxTool:
626 if parent.__class__ != xxxToolBar: error = True
627 elif child.__class__ == xxxMenu:
628 if not parent.__class__ in [xxxMainNode, xxxMenuBar, xxxMenu]: error = True
629 elif child.__class__ == xxxMenuItem:
630 if not parent.__class__ in [xxxMenuBar, xxxMenu]: error = True
631 elif child.isSizer and parent.__class__ in [xxxNotebook, xxxChoicebook, xxxListbook]:
632 error = True
633 else: # normal controls can be almost anywhere
634 if parent.__class__ == xxxMainNode or \
635 parent.__class__ in [xxxMenuBar, xxxMenu]: error = True
636 if error:
637 if parent.__class__ == xxxMainNode: parentClass = 'root'
638 else: parentClass = parent.className
639 wx.LogError('Incompatible parent/child: parent is %s, child is %s!' %
640 (parentClass, child.className))
641 return False
642 return True
643
644 def OnMoveUp(self, evt):
645 selected = tree.selection
646 if not selected: return
647
648 index = tree.ItemIndex(selected)
649 if index == 0: return # No previous sibling found
650
651 # Undo info
652 self.lastOp = 'MOVEUP'
653 status = 'Moved before previous sibling'
654
655 # Prepare undo data
656 panel.Apply()
657
658 parent = tree.GetItemParent(selected)
659 elem = tree.RemoveLeaf(selected)
660 nextItem = tree.GetFirstChild(parent)[0]
661 for i in range(index - 1): nextItem = tree.GetNextSibling(nextItem)
662 selected = tree.InsertNode(parent, tree.GetPyData(parent).treeObject(), elem, nextItem)
663 newIndex = tree.ItemIndex(selected)
664 tree.SelectItem(selected)
665
666 undoMan.RegisterUndo(UndoMove(parent, index, parent, newIndex))
667
668 self.modified = True
669 self.SetStatusText(status)
670
671 return
672
673 def OnMoveDown(self, evt):
674 selected = tree.selection
675 if not selected: return
676
677 index = tree.ItemIndex(selected)
678 next = tree.GetNextSibling(selected)
679 if not next: return
680
681 # Undo info
682 self.lastOp = 'MOVEDOWN'
683 status = 'Moved after next sibling'
684
685 # Prepare undo data
686 panel.Apply()
687
688 parent = tree.GetItemParent(selected)
689 elem = tree.RemoveLeaf(selected)
690 nextItem = tree.GetFirstChild(parent)[0]
691 for i in range(index + 1): nextItem = tree.GetNextSibling(nextItem)
692 selected = tree.InsertNode(parent, tree.GetPyData(parent).treeObject(), elem, nextItem)
693 newIndex = tree.ItemIndex(selected)
694 tree.SelectItem(selected)
695
696 undoMan.RegisterUndo(UndoMove(parent, index, parent, newIndex))
697
698 self.modified = True
699 self.SetStatusText(status)
700
701 return
702
703 def OnMoveLeft(self, evt):
704 selected = tree.selection
705 if not selected: return
706
707 oldParent = tree.GetItemParent(selected)
708 if not oldParent: return
709 pparent = tree.GetItemParent(oldParent)
710 if not pparent: return
711
712 # Check compatibility
713 if not self.ItemsAreCompatible(tree.GetPyData(pparent).treeObject(), tree.GetPyData(selected).treeObject()): return
714
715 # Undo info
716 self.lastOp = 'MOVELEFT'
717 status = 'Made next sibling of parent'
718
719 oldIndex = tree.ItemIndex(selected)
720 elem = tree.RemoveLeaf(selected)
721 nextItem = tree.GetFirstChild(pparent)[0]
722 parentIndex = tree.ItemIndex(oldParent)
723 for i in range(parentIndex + 1): nextItem = tree.GetNextSibling(nextItem)
724
725 # Check parent and child relationships.
726 # If parent is sizer or notebook, child is of wrong class or
727 # parent is normal window, child is child container then detach child.
728 parent = tree.GetPyData(pparent).treeObject()
729 xxx = MakeXXXFromDOM(parent, elem)
730 isChildContainer = isinstance(xxx, xxxChildContainer)
731 if isChildContainer and \
732 ((parent.isSizer and not isinstance(xxx, xxxSizerItem)) or \
733 (isinstance(parent, xxxNotebook) and not isinstance(xxx, xxxNotebookPage)) or \
734 not (parent.isSizer or isinstance(parent, xxxNotebook))):
735 elem.removeChild(xxx.child.element) # detach child
736 elem.unlink() # delete child container
737 elem = xxx.child.element # replace
738 # This may help garbage collection
739 xxx.child.parent = None
740 isChildContainer = False
741 # Parent is sizer or notebook, child is not child container
742 if parent.isSizer and not isChildContainer and not isinstance(xxx, xxxSpacer):
743 # Create sizer item element
744 sizerItemElem = MakeEmptyDOM('sizeritem')
745 sizerItemElem.appendChild(elem)
746 elem = sizerItemElem
747 elif isinstance(parent, xxxNotebook) and not isChildContainer:
748 pageElem = MakeEmptyDOM('notebookpage')
749 pageElem.appendChild(elem)
750 elem = pageElem
751
752 selected = tree.InsertNode(pparent, tree.GetPyData(pparent).treeObject(), elem, nextItem)
753 newIndex = tree.ItemIndex(selected)
754 tree.SelectItem(selected)
755
756 undoMan.RegisterUndo(UndoMove(oldParent, oldIndex, pparent, newIndex))
757
758 self.modified = True
759 self.SetStatusText(status)
760
761 def OnMoveRight(self, evt):
762 selected = tree.selection
763 if not selected: return
764
765 oldParent = tree.GetItemParent(selected)
766 if not oldParent: return
767
768 newParent = tree.GetPrevSibling(selected)
769 if not newParent: return
770
771 parent = tree.GetPyData(newParent).treeObject()
772
773 # Check compatibility
774 if not self.ItemsAreCompatible(parent, tree.GetPyData(selected).treeObject()): return
775
776 # Undo info
777 self.lastOp = 'MOVERIGHT'
778 status = 'Made last child of previous sibling'
779
780 oldIndex = tree.ItemIndex(selected)
781 elem = tree.RemoveLeaf(selected)
782
783 # Check parent and child relationships.
784 # If parent is sizer or notebook, child is of wrong class or
785 # parent is normal window, child is child container then detach child.
786 xxx = MakeXXXFromDOM(parent, elem)
787 isChildContainer = isinstance(xxx, xxxChildContainer)
788 if isChildContainer and \
789 ((parent.isSizer and not isinstance(xxx, xxxSizerItem)) or \
790 (isinstance(parent, xxxNotebook) and not isinstance(xxx, xxxNotebookPage)) or \
791 not (parent.isSizer or isinstance(parent, xxxNotebook))):
792 elem.removeChild(xxx.child.element) # detach child
793 elem.unlink() # delete child container
794 elem = xxx.child.element # replace
795 # This may help garbage collection
796 xxx.child.parent = None
797 isChildContainer = False
798 # Parent is sizer or notebook, child is not child container
799 if parent.isSizer and not isChildContainer and not isinstance(xxx, xxxSpacer):
800 # Create sizer item element
801 sizerItemElem = MakeEmptyDOM('sizeritem')
802 sizerItemElem.appendChild(elem)
803 elem = sizerItemElem
804 elif isinstance(parent, xxxNotebook) and not isChildContainer:
805 pageElem = MakeEmptyDOM('notebookpage')
806 pageElem.appendChild(elem)
807 elem = pageElem
808
809 selected = tree.InsertNode(newParent, tree.GetPyData(newParent).treeObject(), elem, wx.TreeItemId())
810
811 newIndex = tree.ItemIndex(selected)
812 tree.SelectItem(selected)
813
814 undoMan.RegisterUndo(UndoMove(oldParent, oldIndex, newParent, newIndex))
815
816 self.modified = True
817 self.SetStatusText(status)
818
819
820 def OnCutDelete(self, evt):
821 selected = tree.selection
822 if not selected: return # key pressed event
823 # Undo info
824 if evt.GetId() == wx.ID_CUT:
825 self.lastOp = 'CUT'
826 status = 'Removed to clipboard'
827 else:
828 self.lastOp = 'DELETE'
829 status = 'Deleted'
830 # Delete testWin?
831 if g.testWin:
832 # If deleting top-level item, delete testWin
833 if selected == g.testWin.item:
834 g.testWin.Destroy()
835 g.testWin = None
836 else:
837 # Remove highlight, update testWin
838 if g.testWin.highLight:
839 g.testWin.highLight.Remove()
840 tree.needUpdate = True
841 # Prepare undo data
842 panel.Apply()
843 index = tree.ItemFullIndex(selected)
844 parent = tree.GetPyData(tree.GetItemParent(selected)).treeObject()
845 elem = tree.RemoveLeaf(selected)
846 undoMan.RegisterUndo(UndoCutDelete(index, parent, elem))
847 if evt.GetId() == wx.ID_CUT:
848 if wx.TheClipboard.Open():
849 data = wx.CustomDataObject('XRCED')
850 # (False, True)
851 s = elem.toxml(encoding=expat.native_encoding)
852 data.SetData(cPickle.dumps(s))
853 wx.TheClipboard.SetData(data)
854 wx.TheClipboard.Close()
855 else:
856 wx.MessageBox("Unable to open the clipboard", "Error")
857 tree.pendingHighLight = None
858 tree.UnselectAll()
859 tree.selection = None
860 # Update tools
861 g.tools.UpdateUI()
862 panel.Clear()
863 self.SetModified()
864 self.SetStatusText(status)
865
866 def OnSubclass(self, evt):
867 selected = tree.selection
868 xxx = tree.GetPyData(selected).treeObject()
869 elem = xxx.element
870 subclass = xxx.subclass
871 dlg = wx.TextEntryDialog(self, 'Subclass:', defaultValue=subclass)
872 if dlg.ShowModal() == wx.ID_OK:
873 subclass = dlg.GetValue()
874 if subclass:
875 elem.setAttribute('subclass', subclass)
876 elif elem.hasAttribute('subclass'):
877 elem.removeAttribute('subclass')
878 self.SetModified()
879 xxx.subclass = elem.getAttribute('subclass')
880 tree.SetItemText(selected, xxx.treeName())
881 panel.pages[0].box.SetLabel(xxx.panelName())
882 dlg.Destroy()
883
884 def OnEmbedPanel(self, evt):
885 conf.embedPanel = evt.IsChecked()
886 if conf.embedPanel:
887 # Remember last dimentions
888 conf.panelX, conf.panelY = self.miniFrame.GetPosition()
889 conf.panelWidth, conf.panelHeight = self.miniFrame.GetSize()
890 size = self.GetSize()
891 pos = self.GetPosition()
892 sizePanel = panel.GetSize()
893 panel.Reparent(self.splitter)
894 self.miniFrame.GetSizer().Remove(panel)
895 # Widen
896 self.SetDimensions(pos.x, pos.y, size.width + sizePanel.width, size.height)
897 self.splitter.SplitVertically(tree, panel, conf.sashPos)
898 self.miniFrame.Show(False)
899 else:
900 conf.sashPos = self.splitter.GetSashPosition()
901 pos = self.GetPosition()
902 size = self.GetSize()
903 sizePanel = panel.GetSize()
904 self.splitter.Unsplit(panel)
905 sizer = self.miniFrame.GetSizer()
906 panel.Reparent(self.miniFrame)
907 panel.Show(True)
908 sizer.Add(panel, 1, wx.EXPAND)
909 self.miniFrame.Show(True)
910 self.miniFrame.SetDimensions(conf.panelX, conf.panelY,
911 conf.panelWidth, conf.panelHeight)
912 self.miniFrame.Layout()
913 # Reduce width
914 self.SetDimensions(pos.x, pos.y,
915 max(size.width - sizePanel.width, self.minWidth), size.height)
916
917 def OnShowTools(self, evt):
918 conf.showTools = evt.IsChecked()
919 g.tools.Show(conf.showTools)
920 if conf.showTools:
921 self.toolsSizer.Prepend(g.tools, 0, wx.EXPAND)
922 else:
923 self.toolsSizer.Remove(g.tools)
924 self.toolsSizer.Layout()
925
926 def OnTest(self, evt):
927 if not tree.selection: return # key pressed event
928 tree.ShowTestWindow(tree.selection)
929
930 def OnTestHide(self, evt):
931 tree.CloseTestWindow()
932
933 # Find object by relative position
934 def FindObject(self, item, obj):
935 # We simply perform depth-first traversal, sinse it's too much
936 # hassle to deal with all sizer/window combinations
937 w = tree.FindNodeObject(item)
938 if w == obj or isinstance(w, wx.GBSizerItem) and w.GetWindow() == obj:
939 return item
940 if tree.ItemHasChildren(item):
941 child = tree.GetFirstChild(item)[0]
942 while child:
943 found = self.FindObject(child, obj)
944 if found: return found
945 child = tree.GetNextSibling(child)
946 return None
947
948 def OnTestWinLeftDown(self, evt):
949 pos = evt.GetPosition()
950 self.SetHandler(g.testWin)
951 g.testWin.Disconnect(wx.ID_ANY, wx.ID_ANY, wx.wxEVT_LEFT_DOWN)
952 item = self.FindObject(g.testWin.item, evt.GetEventObject())
953 if item:
954 tree.EnsureVisible(item)
955 tree.SelectItem(item)
956 self.tb.ToggleTool(self.ID_TOOL_LOCATE, False)
957 if item:
958 self.SetStatusText('Selected %s' % tree.GetItemText(item))
959 else:
960 self.SetStatusText('Locate failed!')
961
962 def SetHandler(self, w, h=None):
963 if h:
964 w.SetEventHandler(h)
965 w.SetCursor(wx.CROSS_CURSOR)
966 else:
967 w.SetEventHandler(w)
968 w.SetCursor(wx.NullCursor)
969 for ch in w.GetChildren():
970 self.SetHandler(ch, h)
971
972 def OnLocate(self, evt):
973 if g.testWin:
974 if evt.GetId() == self.ID_LOCATE or \
975 evt.GetId() == self.ID_TOOL_LOCATE and evt.IsChecked():
976 self.SetHandler(g.testWin, g.testWin)
977 g.testWin.Connect(wx.ID_ANY, wx.ID_ANY, wx.wxEVT_LEFT_DOWN, self.OnTestWinLeftDown)
978 if evt.GetId() == self.ID_LOCATE:
979 self.tb.ToggleTool(self.ID_TOOL_LOCATE, True)
980 elif evt.GetId() == self.ID_TOOL_LOCATE and not evt.IsChecked():
981 self.SetHandler(g.testWin, None)
982 g.testWin.Disconnect(wx.ID_ANY, wx.ID_ANY, wx.wxEVT_LEFT_DOWN)
983 self.SetStatusText('Click somewhere in your test window now')
984
985 def OnRefresh(self, evt):
986 # If modified, apply first
987 selection = tree.selection
988 if selection:
989 xxx = tree.GetPyData(selection)
990 if xxx and panel.IsModified():
991 tree.Apply(xxx, selection)
992 if g.testWin:
993 # (re)create
994 tree.CreateTestWin(g.testWin.item)
995 panel.modified = False
996 tree.needUpdate = False
997
998 def OnAutoRefresh(self, evt):
999 conf.autoRefresh = evt.IsChecked()
1000 self.menuBar.Check(self.ID_AUTO_REFRESH, conf.autoRefresh)
1001 self.tb.ToggleTool(self.ID_AUTO_REFRESH, conf.autoRefresh)
1002
1003 def OnAbout(self, evt):
1004 str = '''\
1005 XRCed version %s
1006
1007 (c) Roman Rolinsky <rollrom@users.sourceforge.net>
1008 Homepage: http://xrced.sourceforge.net\
1009 ''' % version
1010 dlg = wx.MessageDialog(self, str, 'About XRCed', wx.OK | wx.CENTRE)
1011 dlg.ShowModal()
1012 dlg.Destroy()
1013
1014 def OnReadme(self, evt):
1015 text = open(os.path.join(basePath, 'README.txt'), 'r').read()
1016 dlg = ScrolledMessageDialog(self, text, "XRCed README")
1017 dlg.ShowModal()
1018 dlg.Destroy()
1019
1020 # Simple emulation of python command line
1021 def OnDebugCMD(self, evt):
1022 while 1:
1023 try:
1024 exec raw_input('C:\> ')
1025 except EOFError:
1026 print '^D'
1027 break
1028 except:
1029 (etype, value, tb) =sys.exc_info()
1030 tblist =traceback.extract_tb(tb)[1:]
1031 msg =' '.join(traceback.format_exception_only(etype, value)
1032 +traceback.format_list(tblist))
1033 print msg
1034
1035 def OnCreate(self, evt):
1036 selected = tree.selection
1037 if tree.ctrl: appendChild = False
1038 else: appendChild = not tree.NeedInsert(selected)
1039 xxx = tree.GetPyData(selected)
1040 if not appendChild:
1041 # If insert before
1042 if tree.shift:
1043 # If has previous item, insert after it, else append to parent
1044 nextItem = selected
1045 parentLeaf = tree.GetItemParent(selected)
1046 else:
1047 # If has next item, insert, else append to parent
1048 nextItem = tree.GetNextSibling(selected)
1049 parentLeaf = tree.GetItemParent(selected)
1050 # Expanded container (must have children)
1051 elif tree.shift and tree.IsExpanded(selected) \
1052 and tree.GetChildrenCount(selected, False):
1053 nextItem = tree.GetFirstChild(selected)[0]
1054 parentLeaf = selected
1055 else:
1056 nextItem = wx.TreeItemId()
1057 parentLeaf = selected
1058 parent = tree.GetPyData(parentLeaf)
1059 if parent.hasChild: parent = parent.child
1060
1061 # Create object_ref?
1062 if evt.GetId() == ID_NEW.REF:
1063 ref = wx.GetTextFromUser('Create reference to:', 'Create reference')
1064 if not ref: return
1065 xxx = MakeEmptyRefXXX(parent, ref)
1066 else:
1067 # Create empty element
1068 className = pullDownMenu.createMap[evt.GetId()]
1069 xxx = MakeEmptyXXX(parent, className)
1070
1071 # Set default name for top-level windows
1072 if parent.__class__ == xxxMainNode:
1073 cl = xxx.treeObject().__class__
1074 frame.maxIDs[cl] += 1
1075 xxx.setTreeName('%s%d' % (defaultIDs[cl], frame.maxIDs[cl]))
1076 # And for some other standard controls
1077 elif parent.__class__ == xxxStdDialogButtonSizer:
1078 xxx.setTreeName(pullDownMenu.stdButtonIDs[evt.GetId()][0])
1079 # We can even set label
1080 obj = xxx.treeObject()
1081 elem = g.tree.dom.createElement('label')
1082 elem.appendChild(g.tree.dom.createTextNode(pullDownMenu.stdButtonIDs[evt.GetId()][1]))
1083 obj.params['label'] = xxxParam(elem)
1084 xxx.treeObject().element.appendChild(elem)
1085
1086 # Insert new node, register undo
1087 elem = xxx.element
1088 newItem = tree.InsertNode(parentLeaf, parent, elem, nextItem)
1089 undoMan.RegisterUndo(UndoPasteCreate(parentLeaf, parent, newItem, selected))
1090 tree.EnsureVisible(newItem)
1091 tree.SelectItem(newItem)
1092 if not tree.IsVisible(newItem):
1093 tree.ScrollTo(newItem)
1094 tree.Refresh()
1095 # Update view?
1096 if g.testWin and tree.IsHighlatable(newItem):
1097 if conf.autoRefresh:
1098 tree.needUpdate = True
1099 tree.pendingHighLight = newItem
1100 else:
1101 tree.pendingHighLight = None
1102 tree.SetFocus()
1103 self.SetModified()
1104
1105 # Replace one object with another
1106 def OnReplace(self, evt):
1107 selected = tree.selection
1108 xxx = tree.GetPyData(selected).treeObject()
1109 elem = xxx.element
1110 parent = elem.parentNode
1111 undoMan.RegisterUndo(UndoReplace(selected))
1112 # New class
1113 className = pullDownMenu.createMap[evt.GetId() - 1000]
1114
1115 # Create temporary empty node (with default values)
1116 dummy = MakeEmptyDOM(className)
1117 if className == 'spacer' and xxx.className != 'spacer':
1118 klass = xxxSpacer
1119 elif xxx.className == 'spacer' and className != 'spacer':
1120 klass = xxxSizerItem
1121 else:
1122 klass = xxxDict[className]
1123 # Remove non-compatible children
1124 if tree.ItemHasChildren(selected) and not klass.hasChildren:
1125 tree.DeleteChildren(selected)
1126 nodes = elem.childNodes[:]
1127 tags = []
1128 for node in nodes:
1129 if node.nodeType != minidom.Node.ELEMENT_NODE: continue
1130 remove = False
1131 tag = node.tagName
1132 if tag == 'object':
1133 if not klass.hasChildren: remove = True
1134 elif tag not in klass.allParams and \
1135 (not klass.hasStyle or tag not in klass.styles):
1136 remove = True
1137 else:
1138 tags.append(tag)
1139 if remove:
1140 elem.removeChild(node)
1141 node.unlink()
1142
1143 # Remove sizeritem child if spacer
1144 if className == 'spacer' and xxx.className != 'spacer':
1145 sizeritem = elem.parentNode
1146 assert sizeritem.getAttribute('class') == 'sizeritem'
1147 sizeritem.removeChild(elem)
1148 elem.unlink()
1149 elem = sizeritem
1150 tree.GetPyData(selected).hasChild = False
1151 elif xxx.className == 'spacer' and className != 'spacer':
1152 # Create sizeritem element
1153 assert xxx.parent.isSizer
1154 elem.setAttribute('class', 'sizeritem')
1155 node = MakeEmptyDOM(className)
1156 elem.appendChild(node)
1157 # Replace to point to new object
1158 xxx = xxxSizerItem(xxx.parent, elem)
1159 elem = node
1160 tree.SetPyData(selected, xxx)
1161 xxx = xxx.child
1162 else:
1163 # Copy parameters present in dummy but not in elem
1164 for node in dummy.childNodes:
1165 if node.tagName not in tags: elem.appendChild(node.cloneNode(True))
1166 dummy.unlink()
1167
1168 # Change class name
1169 elem.setAttribute('class', className)
1170 if elem.hasAttribute('subclass'):
1171 elem.removeAttribute('subclass') # clear subclassing
1172 # Re-create xxx element
1173 xxx = MakeXXXFromDOM(xxx.parent, elem)
1174 # Remove incompatible style flags
1175 if 'style' in xxx.params:
1176 styles = map(string.strip, xxx.params['style'].value().split('|'))
1177 newStyles = [s for s in styles if s in klass.winStyles or s in genericStyles]
1178 if newStyles != styles:
1179 if newStyles:
1180 value = reduce(lambda a,b: a+'|'+b, newStyles)
1181 else:
1182 value = ''
1183 xxx.params['style'].update(value)
1184
1185 # Update parent in child objects
1186 if tree.ItemHasChildren(selected):
1187 i, cookie = tree.GetFirstChild(selected)
1188 while i.IsOk():
1189 x = tree.GetPyData(i)
1190 x.parent = xxx
1191 if x.hasChild: x.child.parent = xxx
1192 i, cookie = tree.GetNextChild(selected, cookie)
1193
1194 # Update tree
1195 if tree.GetPyData(selected).hasChild: # child container
1196 container = tree.GetPyData(selected)
1197 container.resetChild(xxx)
1198 xxx = container
1199 else:
1200 tree.SetPyData(selected, xxx)
1201 tree.SetItemText(selected, xxx.treeName())
1202 tree.SetItemImage(selected, xxx.treeImage())
1203
1204 # Set default name for top-level windows
1205 if parent.__class__ == xxxMainNode:
1206 cl = xxx.treeObject().__class__
1207 frame.maxIDs[cl] += 1
1208 xxx.setTreeName('%s%d' % (defaultIDs[cl], frame.maxIDs[cl]))
1209
1210 # Update panel
1211 g.panel.SetData(xxx)
1212 # Update tools
1213 g.tools.UpdateUI()
1214
1215 #undoMan.RegisterUndo(UndoPasteCreate(parentLeaf, parent, newItem, selected))
1216 # Update view?
1217 if g.testWin and tree.IsHighlatable(selected):
1218 if conf.autoRefresh:
1219 tree.needUpdate = True
1220 tree.pendingHighLight = selected
1221 else:
1222 tree.pendingHighLight = None
1223 tree.SetFocus()
1224 self.SetModified()
1225
1226 # Expand/collapse subtree
1227 def OnExpand(self, evt):
1228 if tree.selection: tree.ExpandAll(tree.selection)
1229 else: tree.ExpandAll(tree.root)
1230 def OnCollapse(self, evt):
1231 if tree.selection: tree.CollapseAll(tree.selection)
1232 else: tree.CollapseAll(tree.root)
1233
1234 def OnPullDownHighlight(self, evt):
1235 menuId = evt.GetMenuId()
1236 if menuId != -1:
1237 menu = evt.GetEventObject()
1238 help = menu.GetHelpString(menuId)
1239 self.SetStatusText(help)
1240 else:
1241 self.SetStatusText('')
1242
1243 def OnUpdateUI(self, evt):
1244 if evt.GetId() in [wx.ID_CUT, wx.ID_COPY, self.ID_DELETE]:
1245 evt.Enable(tree.selection is not None and tree.selection != tree.root)
1246 elif evt.GetId() == wx.ID_SAVE:
1247 evt.Enable(self.modified)
1248 elif evt.GetId() in [wx.ID_PASTE, self.ID_TOOL_PASTE]:
1249 evt.Enable(tree.selection is not None)
1250 elif evt.GetId() == self.ID_TEST:
1251 evt.Enable(tree.selection is not None and tree.selection != tree.root)
1252 elif evt.GetId() in [self.ID_LOCATE, self.ID_TOOL_LOCATE]:
1253 evt.Enable(g.testWin is not None)
1254 elif evt.GetId() == wx.ID_UNDO: evt.Enable(undoMan.CanUndo())
1255 elif evt.GetId() == wx.ID_REDO: evt.Enable(undoMan.CanRedo())
1256
1257 def OnIdle(self, evt):
1258 if self.inIdle: return # Recursive call protection
1259 self.inIdle = True
1260 try:
1261 if tree.needUpdate:
1262 if conf.autoRefresh:
1263 if g.testWin:
1264 self.SetStatusText('Refreshing test window...')
1265 # (re)create
1266 tree.CreateTestWin(g.testWin.item)
1267 self.SetStatusText('')
1268 tree.needUpdate = False
1269 elif tree.pendingHighLight:
1270 try:
1271 tree.HighLight(tree.pendingHighLight)
1272 except:
1273 # Remove highlight if any problem
1274 if g.testWin.highLight:
1275 g.testWin.highLight.Remove()
1276 tree.pendingHighLight = None
1277 raise
1278 else:
1279 evt.Skip()
1280 finally:
1281 self.inIdle = False
1282
1283 # We don't let close panel window
1284 def OnCloseMiniFrame(self, evt):
1285 return
1286
1287 def OnIconize(self, evt):
1288 if evt.Iconized():
1289 conf.x, conf.y = self.GetPosition()
1290 conf.width, conf.height = self.GetSize()
1291 if conf.embedPanel:
1292 conf.sashPos = self.splitter.GetSashPosition()
1293 else:
1294 conf.panelX, conf.panelY = self.miniFrame.GetPosition()
1295 conf.panelWidth, conf.panelHeight = self.miniFrame.GetSize()
1296 self.miniFrame.Iconize()
1297 else:
1298 if not conf.embedPanel:
1299 self.miniFrame.Iconize(False)
1300 evt.Skip()
1301
1302 def OnCloseWindow(self, evt):
1303 if not self.AskSave(): return
1304 if g.testWin: g.testWin.Destroy()
1305 if not panel.GetPageCount() == 2:
1306 panel.page2.Destroy()
1307 else:
1308 # If we don't do this, page does not get destroyed (a bug?)
1309 panel.RemovePage(1)
1310 if not self.IsIconized():
1311 conf.x, conf.y = self.GetPosition()
1312 conf.width, conf.height = self.GetSize()
1313 if conf.embedPanel:
1314 conf.sashPos = self.splitter.GetSashPosition()
1315 else:
1316 conf.panelX, conf.panelY = self.miniFrame.GetPosition()
1317 conf.panelWidth, conf.panelHeight = self.miniFrame.GetSize()
1318 evt.Skip()
1319
1320
1321 def CreateLocalConf(self, path):
1322 name = os.path.splitext(path)[0]
1323 name += '.xcfg'
1324 return wx.FileConfig(localFilename=name)
1325
1326
1327 def Clear(self):
1328 self.dataFile = ''
1329 conf.localconf = None
1330 undoMan.Clear()
1331 self.SetModified(False)
1332 tree.Clear()
1333 panel.Clear()
1334 if g.testWin:
1335 g.testWin.Destroy()
1336 g.testWin = None
1337 # Numbers for new controls
1338 self.maxIDs = {}
1339 for cl in [xxxPanel, xxxDialog, xxxFrame,
1340 xxxMenuBar, xxxMenu, xxxToolBar,
1341 xxxWizard, xxxBitmap, xxxIcon]:
1342 self.maxIDs[cl] = 0
1343
1344 def SetModified(self, state=True):
1345 self.modified = state
1346 name = os.path.basename(self.dataFile)
1347 if not name: name = defaultName
1348 if state:
1349 self.SetTitle(progname + ': ' + name + ' *')
1350 else:
1351 self.SetTitle(progname + ': ' + name)
1352
1353 def Open(self, path):
1354 if not os.path.exists(path):
1355 wx.LogError('File does not exists: %s' % path)
1356 return False
1357 # Try to read the file
1358 try:
1359 f = open(path)
1360 self.Clear()
1361 dom = minidom.parse(f)
1362 f.close()
1363 # Set encoding global variable and default encoding
1364 if dom.encoding:
1365 g.currentEncoding = dom.encoding
1366 wx.SetDefaultPyEncoding(g.currentEncoding.encode())
1367 else:
1368 g.currentEncoding = ''
1369 # Change dir
1370 self.dataFile = path = os.path.abspath(path)
1371 dir = os.path.dirname(path)
1372 if dir: os.chdir(dir)
1373 tree.SetData(dom)
1374 self.SetTitle(progname + ': ' + os.path.basename(path))
1375 conf.localconf = self.CreateLocalConf(self.dataFile)
1376 except:
1377 # Nice exception printing
1378 inf = sys.exc_info()
1379 wx.LogError(traceback.format_exception(inf[0], inf[1], None)[-1])
1380 wx.LogError('Error reading file: %s' % path)
1381 if debug: raise
1382 return False
1383 return True
1384
1385 def Indent(self, node, indent = 0):
1386 # Copy child list because it will change soon
1387 children = node.childNodes[:]
1388 # Main node doesn't need to be indented
1389 if indent:
1390 text = self.domCopy.createTextNode('\n' + ' ' * indent)
1391 node.parentNode.insertBefore(text, node)
1392 if children:
1393 # Append newline after last child, except for text nodes
1394 if children[-1].nodeType == minidom.Node.ELEMENT_NODE:
1395 text = self.domCopy.createTextNode('\n' + ' ' * indent)
1396 node.appendChild(text)
1397 # Indent children which are elements
1398 for n in children:
1399 if n.nodeType == minidom.Node.ELEMENT_NODE:
1400 self.Indent(n, indent + 2)
1401
1402 def Save(self, path):
1403 try:
1404 import codecs
1405 # Apply changes
1406 if tree.selection and panel.IsModified():
1407 self.OnRefresh(wx.CommandEvent())
1408 if g.currentEncoding:
1409 f = codecs.open(path, 'wt', g.currentEncoding)
1410 else:
1411 f = codecs.open(path, 'wt')
1412 # Make temporary copy for formatting it
1413 # !!! We can't clone dom node, it works only once
1414 #self.domCopy = tree.dom.cloneNode(True)
1415 self.domCopy = MyDocument()
1416 mainNode = self.domCopy.appendChild(tree.mainNode.cloneNode(True))
1417 # Remove first child (test element)
1418 testElem = mainNode.firstChild
1419 mainNode.removeChild(testElem)
1420 testElem.unlink()
1421 self.Indent(mainNode)
1422 self.domCopy.writexml(f, encoding = g.currentEncoding)
1423 f.close()
1424 self.domCopy.unlink()
1425 self.domCopy = None
1426 self.SetModified(False)
1427 panel.SetModified(False)
1428 conf.localconf.Flush()
1429 except:
1430 inf = sys.exc_info()
1431 wx.LogError(traceback.format_exception(inf[0], inf[1], None)[-1])
1432 wx.LogError('Error writing file: %s' % path)
1433 raise
1434
1435 def AskSave(self):
1436 if not (self.modified or panel.IsModified()): return True
1437 flags = wx.ICON_EXCLAMATION | wx.YES_NO | wx.CANCEL | wx.CENTRE
1438 dlg = wx.MessageDialog( self, 'File is modified. Save before exit?',
1439 'Save before too late?', flags )
1440 say = dlg.ShowModal()
1441 dlg.Destroy()
1442 wx.Yield()
1443 if say == wx.ID_YES:
1444 self.OnSaveOrSaveAs(wx.CommandEvent(wx.ID_SAVE))
1445 # If save was successful, modified flag is unset
1446 if not self.modified: return True
1447 elif say == wx.ID_NO:
1448 self.SetModified(False)
1449 panel.SetModified(False)
1450 return True
1451 return False
1452
1453 def SaveUndo(self):
1454 pass # !!!
1455
1456 ################################################################################
1457
1458 class PythonOptions(wx.Dialog):
1459
1460 def __init__(self, parent, cfg, dataFile):
1461 pre = wx.PreDialog()
1462 g.frame.res.LoadOnDialog(pre, parent, "PYTHON_OPTIONS")
1463 self.PostCreate(pre)
1464
1465 self.cfg = cfg
1466 self.dataFile = dataFile
1467
1468 self.AutoGenerateCB = xrc.XRCCTRL(self, "AutoGenerateCB")
1469 self.EmbedCB = xrc.XRCCTRL(self, "EmbedCB")
1470 self.GettextCB = xrc.XRCCTRL(self, "GettextCB")
1471 self.MakeXRSFileCB = xrc.XRCCTRL(self, "MakeXRSFileCB")
1472 self.FileNameTC = xrc.XRCCTRL(self, "FileNameTC")
1473 self.BrowseBtn = xrc.XRCCTRL(self, "BrowseBtn")
1474 self.GenerateBtn = xrc.XRCCTRL(self, "GenerateBtn")
1475 self.SaveOptsBtn = xrc.XRCCTRL(self, "SaveOptsBtn")
1476
1477 self.Bind(wx.EVT_BUTTON, self.OnBrowse, self.BrowseBtn)
1478 self.Bind(wx.EVT_BUTTON, self.OnGenerate, self.GenerateBtn)
1479 self.Bind(wx.EVT_BUTTON, self.OnSaveOpts, self.SaveOptsBtn)
1480
1481 if self.cfg.Read("filename", "") != "":
1482 self.FileNameTC.SetValue(self.cfg.Read("filename"))
1483 else:
1484 name = os.path.splitext(os.path.split(dataFile)[1])[0]
1485 name += '_xrc.py'
1486 self.FileNameTC.SetValue(name)
1487 self.AutoGenerateCB.SetValue(self.cfg.ReadBool("autogenerate", False))
1488 self.EmbedCB.SetValue(self.cfg.ReadBool("embedResource", False))
1489 self.MakeXRSFileCB.SetValue(self.cfg.ReadBool("makeXRS", False))
1490 self.GettextCB.SetValue(self.cfg.ReadBool("genGettext", False))
1491
1492
1493 def OnBrowse(self, evt):
1494 path = self.FileNameTC.GetValue()
1495 dirname = os.path.abspath(os.path.dirname(path))
1496 name = os.path.split(path)[1]
1497 dlg = wx.FileDialog(self, 'Save As', dirname, name, '*.py',
1498 wx.SAVE | wx.OVERWRITE_PROMPT)
1499 if dlg.ShowModal() == wx.ID_OK:
1500 path = dlg.GetPath()
1501 self.FileNameTC.SetValue(path)
1502 dlg.Destroy()
1503
1504
1505 def OnGenerate(self, evt):
1506 pypath = self.FileNameTC.GetValue()
1507 embed = self.EmbedCB.GetValue()
1508 genGettext = self.GettextCB.GetValue()
1509 frame.GeneratePython(self.dataFile, pypath, embed, genGettext)
1510 self.OnSaveOpts()
1511
1512
1513 def OnSaveOpts(self, evt=None):
1514 self.cfg.Write("filename", self.FileNameTC.GetValue())
1515 self.cfg.WriteBool("autogenerate", self.AutoGenerateCB.GetValue())
1516 self.cfg.WriteBool("embedResource", self.EmbedCB.GetValue())
1517 self.cfg.WriteBool("makeXRS", self.MakeXRSFileCB.GetValue())
1518 self.cfg.WriteBool("genGettext", self.GettextCB.GetValue())
1519
1520 self.EndModal(wx.ID_OK)
1521
1522
1523 ################################################################################
1524
1525 def usage():
1526 print >> sys.stderr, 'usage: xrced [-dhiv] [file]'
1527
1528 class App(wx.App):
1529 def OnInit(self):
1530 # Check version
1531 if wx.VERSION[:3] < MinWxVersion:
1532 wx.LogWarning('''\
1533 This version of XRCed may not work correctly on your version of wxWidgets. \
1534 Please upgrade wxWidgets to %d.%d.%d or higher.''' % MinWxVersion)
1535 global debug
1536 # Process comand-line
1537 opts = args = None
1538 try:
1539 opts, args = getopt.getopt(sys.argv[1:], 'dhiv')
1540 for o,a in opts:
1541 if o == '-h':
1542 usage()
1543 sys.exit(0)
1544 elif o == '-d':
1545 debug = True
1546 elif o == '-v':
1547 print 'XRCed version', version
1548 sys.exit(0)
1549
1550 except getopt.GetoptError:
1551 if wx.Platform != '__WXMAC__': # macs have some extra parameters
1552 print >> sys.stderr, 'Unknown option'
1553 usage()
1554 sys.exit(1)
1555
1556 self.SetAppName('xrced')
1557 # Settings
1558 global conf
1559 conf = g.conf = wx.Config(style = wx.CONFIG_USE_LOCAL_FILE)
1560 conf.localconf = None
1561 conf.autoRefresh = conf.ReadInt('autorefresh', True)
1562 pos = conf.ReadInt('x', -1), conf.ReadInt('y', -1)
1563 size = conf.ReadInt('width', 800), conf.ReadInt('height', 600)
1564 conf.embedPanel = conf.ReadInt('embedPanel', True)
1565 conf.showTools = conf.ReadInt('showTools', True)
1566 conf.sashPos = conf.ReadInt('sashPos', 200)
1567 # read recently used files
1568 recentfiles=conf.Read('recentFiles','')
1569 conf.recentfiles={}
1570 if recentfiles:
1571 for fil in recentfiles.split('|'):
1572 conf.recentfiles[wx.NewId()]=fil
1573 if not conf.embedPanel:
1574 conf.panelX = conf.ReadInt('panelX', -1)
1575 conf.panelY = conf.ReadInt('panelY', -1)
1576 else:
1577 conf.panelX = conf.panelY = -1
1578 conf.panelWidth = conf.ReadInt('panelWidth', 200)
1579 conf.panelHeight = conf.ReadInt('panelHeight', 200)
1580 conf.panic = not conf.HasEntry('nopanic')
1581 # Add handlers
1582 wx.FileSystem.AddHandler(wx.MemoryFSHandler())
1583 # Create main frame
1584 frame = Frame(pos, size)
1585 frame.Show(True)
1586
1587 # Load file after showing
1588 if args:
1589 conf.panic = False
1590 frame.open = frame.Open(args[0])
1591
1592 return True
1593
1594 def OnExit(self):
1595 # Write config
1596 global conf
1597 wc = conf
1598 wc.WriteInt('autorefresh', conf.autoRefresh)
1599 wc.WriteInt('x', conf.x)
1600 wc.WriteInt('y', conf.y)
1601 wc.WriteInt('width', conf.width)
1602 wc.WriteInt('height', conf.height)
1603 wc.WriteInt('embedPanel', conf.embedPanel)
1604 wc.WriteInt('showTools', conf.showTools)
1605 if not conf.embedPanel:
1606 wc.WriteInt('panelX', conf.panelX)
1607 wc.WriteInt('panelY', conf.panelY)
1608 wc.WriteInt('sashPos', conf.sashPos)
1609 wc.WriteInt('panelWidth', conf.panelWidth)
1610 wc.WriteInt('panelHeight', conf.panelHeight)
1611 wc.WriteInt('nopanic', True)
1612 wc.Write('recentFiles', '|'.join(conf.recentfiles.values()[-5:]))
1613 wc.Flush()
1614
1615 def main():
1616 app = App(0, useBestVisual=False)
1617 #app.SetAssertMode(wx.PYAPP_ASSERT_LOG)
1618 app.MainLoop()
1619 app.OnExit()
1620 global conf
1621 del conf
1622
1623 if __name__ == '__main__':
1624 main()