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