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