]>
Commit | Line | Data |
---|---|---|
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 | ||
6 | from wxPython.wx import * | |
7 | from wxPython.xrc import * | |
8 | from wxPython.html import * | |
9 | import wxPython.lib.wxpTag | |
10 | from xml.dom import minidom | |
11 | import os | |
12 | import tempfile | |
13 | ||
14 | import images | |
15 | ||
16 | # String constants | |
17 | htmlHeader = '<html><body bgcolor="#b0c4de">\n' | |
18 | htmlFooter = '</body></html>\n' | |
19 | progname = 'XRCed' | |
20 | version = '0.0.3' | |
21 | ||
22 | # Local modules | |
23 | from xxx import * | |
24 | ||
25 | # Globals | |
26 | testWin = None | |
27 | testWinPos = wxDefaultPosition | |
28 | ||
29 | # 1 adds CMD command to Help menu | |
30 | debug = 1 | |
31 | ||
32 | if debug: | |
33 | import traceback | |
34 | import time | |
35 | ||
36 | # Set menu to list items. | |
37 | # Each menu command is a tuple (id, label, help) | |
38 | # submenus are lists [id, label, help, submenu] | |
39 | # and separators are any other type | |
40 | def SetMenu(m, list): | |
41 | for l in list: | |
42 | if type(l) == types.TupleType: | |
43 | apply(m.Append, l) | |
44 | elif type(l) == types.ListType: | |
45 | subMenu = wxMenu() | |
46 | SetMenu(subMenu, l[2:]) | |
47 | m.AppendMenu(wxNewId(), l[0], subMenu, l[1]) | |
48 | else: # separator | |
49 | m.AppendSeparator() | |
50 | ||
51 | # Properties panel | |
52 | class Panel(wxHtmlWindow): | |
53 | def __init__(self, parent, id): | |
54 | wxHtmlWindow.__init__(self, parent, id) | |
55 | self.SetBorders(5) | |
56 | self.SetFonts('', '', [8, 10, 12, 14, 16, 19, 24]) | |
57 | EVT_CHECKBOX(self, xxxObject.ID_CHECK_PARAMS, self.OnCheckParams) | |
58 | EVT_CHECKBOX(self, xxxChildContainer.ID_CHECK_PARAMS, self.OnCheckParams) | |
59 | self.modified = false | |
60 | ||
61 | def Clear(self): | |
62 | self.SetPage(htmlHeader + 'select a tree item on the left' + htmlFooter) | |
63 | ||
64 | def OnCheckParams(self, evt): | |
65 | selected = tree.GetSelection() | |
66 | xxx = tree.GetPyData(selected) | |
67 | if xxx.hasChild and evt.GetId() != xxxChildContainer.ID_CHECK_PARAMS: | |
68 | xxx = xxx.child | |
69 | # Set current object | |
70 | param = evt.GetEventObject().GetName()[6:] | |
71 | if xxx.hasChild: | |
72 | w = GetRegistered('_'+param) | |
73 | else: | |
74 | w = GetRegistered(param) | |
75 | elem = xxx.element | |
76 | if evt.IsChecked(): | |
77 | # Ad new text node in order of allParams | |
78 | w.SetValue('') | |
79 | textElem = tree.dom.createElement(param) | |
80 | textNode = tree.dom.createTextNode('') | |
81 | textElem.appendChild(textNode) | |
82 | # Find place to put new element: first present element after param | |
83 | found = false | |
84 | for p in xxx.allParams[xxx.allParams.index(param) + 1:]: | |
85 | # Content params don't have same type | |
86 | if xxx.params.has_key(p) and p != 'content': | |
87 | found = true | |
88 | break | |
89 | if found: | |
90 | nextTextElem = xxx.params[p].parentNode | |
91 | elem.insertBefore(textElem, nextTextElem) | |
92 | else: | |
93 | elem.appendChild(textElem) | |
94 | xxx.params[param] = textNode | |
95 | else: | |
96 | # Remove parameter element and following text node | |
97 | textElem = xxx.params[param].parentNode | |
98 | newline = textElem.nextSibling | |
99 | if newline and newline.nodeType == minidom.Node.TEXT_NODE: | |
100 | elem.removeChild(newline) | |
101 | elem.removeChild(textElem) | |
102 | del xxx.params[param] | |
103 | w.SetValue('') | |
104 | w.Enable(evt.IsChecked()) | |
105 | # Set modified flas | |
106 | self.SetModified(true) | |
107 | ||
108 | # If some parameter was changed | |
109 | def IsModified(self): | |
110 | return self.modified | |
111 | def SetModified(self, value): | |
112 | self.modified = value | |
113 | ||
114 | class HightLightBox: | |
115 | def __init__(self, pos, size): | |
116 | w = testWin.panel | |
117 | l1 = wxWindow(w, -1, pos, wxSize(size.x, 2)) | |
118 | l1.SetBackgroundColour(wxRED) | |
119 | l2 = wxWindow(w, -1, pos, wxSize(2, size.y)) | |
120 | l2.SetBackgroundColour(wxRED) | |
121 | l3 = wxWindow(w, -1, wxPoint(pos.x + size.x - 2, pos.y), wxSize(2, size.y)) | |
122 | l3.SetBackgroundColour(wxRED) | |
123 | l4 = wxWindow(w, -1, wxPoint(pos.x, pos.y + size.y - 2), wxSize(size.x, 2)) | |
124 | l4.SetBackgroundColour(wxRED) | |
125 | self.lines = [l1, l2, l3, l4] | |
126 | # Move highlight to a new position | |
127 | def Replace(self, pos, size): | |
128 | self.lines[0].SetDimensions(pos.x, pos.y, size.x, 2, wxSIZE_ALLOW_MINUS_ONE) | |
129 | self.lines[1].SetDimensions(pos.x, pos.y, 2, size.y, wxSIZE_ALLOW_MINUS_ONE) | |
130 | self.lines[2].SetDimensions(pos.x + size.x - 2, pos.y, 2, size.y, | |
131 | wxSIZE_ALLOW_MINUS_ONE) | |
132 | self.lines[3].SetDimensions(pos.x, pos.y + size.y - 2, size.x, 2, | |
133 | wxSIZE_ALLOW_MINUS_ONE) | |
134 | # Remove it | |
135 | def Remove(self): | |
136 | map(wxWindow.Destroy, self.lines) | |
137 | testWin.highLight = None | |
138 | ||
139 | class MemoryFile: | |
140 | def __init__(self, name): | |
141 | self.name = name | |
142 | self.buffer = '' | |
143 | def write(self, data): | |
144 | self.buffer = self.buffer + data.encode() | |
145 | def close(self): | |
146 | f = open(self.name, 'w') | |
147 | f.write(self.buffer) | |
148 | f.close() | |
149 | # !!! memory FS will work someday | |
150 | #self.file = wxMemoryFSHandler_AddFile(self.name, self.buffer) | |
151 | ||
152 | class XML_Tree(wxTreeCtrl): | |
153 | def __init__(self, parent, id): | |
154 | wxTreeCtrl.__init__(self, parent, id) | |
155 | self.SetBackgroundColour(wxColour(224, 248, 224)) | |
156 | EVT_TREE_SEL_CHANGED(self, self.GetId(), self.OnSelChanged) | |
157 | EVT_TREE_ITEM_ACTIVATED(self, self.GetId(), self.OnItemActivated) | |
158 | EVT_RIGHT_DOWN(self, self.OnRightDown) | |
159 | self.needUpdate = false | |
160 | self.pendingHighLight = None | |
161 | self.ctrl = false | |
162 | self.dom = None | |
163 | # Create image list | |
164 | il = wxImageList(16, 16, true) | |
165 | xxxPanel.image = il.AddIcon( wxIconFromXPMData(images.getTreePanelData()) ) | |
166 | xxxDialog.image = il.AddIcon( wxIconFromXPMData(images.getTreeDialogData()) ) | |
167 | xxxFrame.image = il.AddIcon( wxIconFromXPMData(images.getTreeFrameData()) ) | |
168 | xxxMenuBar.image = il.AddIcon( wxIconFromXPMData(images.getTreeMenuBarData()) ) | |
169 | xxxMenu.image = il.AddIcon( wxIconFromXPMData(images.getTreeMenuData()) ) | |
170 | xxxSizer.imageH = il.AddIcon( wxIconFromXPMData(images.getTreeSizerHData()) ) | |
171 | xxxSizer.imageV = il.AddIcon( wxIconFromXPMData(images.getTreeSizerVData()) ) | |
172 | xxxStaticBoxSizer.imageH = il.AddIcon( wxIconFromXPMData(images.getTreeStaticBoxSizerHData()) ) | |
173 | xxxStaticBoxSizer.imageV = il.AddIcon( wxIconFromXPMData(images.getTreeStaticBoxSizerVData()) ) | |
174 | xxxGridSizer.image = il.AddIcon( wxIconFromXPMData(images.getTreeSizerGridData()) ) | |
175 | xxxFlexGridSizer.image = il.AddIcon( wxIconFromXPMData(images.getTreeSizerFlexGridData()) ) | |
176 | self.il = il | |
177 | self.SetImageList(il) | |
178 | ||
179 | # !!! temporary solution for GetOldItem problem | |
180 | def Unselect(self): | |
181 | self.selection = wxTreeItemId() | |
182 | wxTreeCtrl.Unselect(self) | |
183 | def GetSelection(self): | |
184 | return self.selection | |
185 | ||
186 | def ExpandAll(self, item): | |
187 | if self.ItemHasChildren(item): | |
188 | self.Expand(item) | |
189 | i, cookie = self.GetFirstChild(item, 0) | |
190 | children = [] | |
191 | while i.IsOk(): | |
192 | children.append(i) | |
193 | i, cookie = self.GetNextChild(item, cookie) | |
194 | for i in children: | |
195 | self.ExpandAll(i) | |
196 | ||
197 | def SetData(self, dom): | |
198 | self.dom = dom | |
199 | # Find 'resource' child, add it's children | |
200 | self.mainNode = dom.getElementsByTagName('resource')[0] | |
201 | nodes = self.mainNode.childNodes[:] | |
202 | for node in nodes: | |
203 | if IsObject(node): | |
204 | self.AddNode(self.GetRootItem(), None, node) | |
205 | else: | |
206 | self.mainNode.removeChild(node) | |
207 | node.unlink() | |
208 | self.Unselect() | |
209 | ||
210 | # Add tree item for given parent item if node is DOM element node with | |
211 | # 'object' tag. xxxParent is parent xxx object | |
212 | def AddNode(self, itemParent, xxxParent, node): | |
213 | # Set item data to current node | |
214 | xxx = MakeXXXFromDOM(xxxParent, node) | |
215 | treeObj = xxx.treeObject() | |
216 | # Append tree item | |
217 | item = self.AppendItem(itemParent, treeObj.treeName(), | |
218 | image=treeObj.treeImage(), | |
219 | data=wxTreeItemData(xxx)) | |
220 | # Try to find children objects | |
221 | if treeObj.hasChildren: | |
222 | nodes = treeObj.element.childNodes[:] | |
223 | for n in nodes: | |
224 | if IsObject(n): | |
225 | self.AddNode(item, treeObj, n) | |
226 | elif n.nodeType != minidom.Node.ELEMENT_NODE: | |
227 | treeObj.element.removeChild(n) | |
228 | n.unlink() | |
229 | ||
230 | ||
231 | # Remove leaf of tree, return it's data object | |
232 | def RemoveLeaf(self, leaf): | |
233 | xxx = self.GetPyData(leaf) | |
234 | node = xxx.element | |
235 | parent = node.parentNode | |
236 | parent.removeChild(node) | |
237 | self.Delete(leaf) | |
238 | # Update view? | |
239 | #if testWin and self.GetItemAncestor(leaf) == testWin.item: | |
240 | # if testWin.highLight: | |
241 | # testWin.highLight.Remove() | |
242 | # self.needUpdate = true | |
243 | return node | |
244 | ||
245 | # Find position relative to the top-level window | |
246 | def FindNodePos(self, item): | |
247 | itemParent = self.GetItemParent(item) | |
248 | if itemParent == self.GetRootItem(): return wxPoint(0, 0) | |
249 | obj = self.FindNodeObject(item) | |
250 | # Find first ancestor which is a wxWindow (not a sizer) | |
251 | winParent = itemParent | |
252 | while self.GetPyData(winParent).isSizer: | |
253 | winParent = self.GetItemParent(winParent) | |
254 | parentPos = self.FindNodePos(winParent) | |
255 | return parentPos + obj.GetPosition() | |
256 | ||
257 | # Find window (or sizer) corresponding to a tree item. | |
258 | def FindNodeObject(self, item): | |
259 | itemParent = self.GetItemParent(item) | |
260 | # If top-level, return testWin (or panel if wxFrame) | |
261 | if itemParent == self.GetRootItem(): return testWin.panel | |
262 | xxx = self.GetPyData(item).treeObject() | |
263 | parentWin = self.FindNodeObject(itemParent) | |
264 | # Top-level sizer? return window's sizer | |
265 | if xxx.isSizer and isinstance(parentWin, wxWindowPtr): | |
266 | return parentWin.GetSizer() | |
267 | # Otherwise get parent's object and it's child | |
268 | n = 0 # index of sibling | |
269 | prev = self.GetPrevSibling(item) | |
270 | while prev.IsOk(): | |
271 | prev = self.GetPrevSibling(prev) | |
272 | n += 1 | |
273 | child = parentWin.GetChildren()[n] | |
274 | # Return window or sizer for sizer items | |
275 | if child.GetClassName() == 'wxSizerItem': | |
276 | if child.IsWindow(): child = child.GetWindow() | |
277 | elif child.IsSizer(): | |
278 | child = child.GetSizer() | |
279 | # Test for notebook sizers | |
280 | if isinstance(child, wxNotebookSizerPtr): | |
281 | child = child.GetNotebook() | |
282 | return child | |
283 | ||
284 | def OnSelChanged(self, evt): | |
285 | # Apply changes | |
286 | # !!! problem with wxGTK | |
287 | #oldItem = evt.GetOldItem() | |
288 | oldItem = self.selection | |
289 | if oldItem.IsOk(): | |
290 | xxx = self.GetPyData(oldItem) | |
291 | # If some data was modified, apply changes | |
292 | if xxx: | |
293 | if panel.IsModified(): | |
294 | self.Apply(xxx, oldItem) | |
295 | #if conf.autoRefresh: | |
296 | if testWin and testWin.highLight: | |
297 | testWin.highLight.Remove() | |
298 | self.needUpdate = true | |
299 | # Generate HTML view | |
300 | item = evt.GetItem() | |
301 | self.selection = item # !!! fix | |
302 | xxx = self.GetPyData(item) | |
303 | html = htmlHeader | |
304 | # List of parameters tuples (parameter, isDefined) | |
305 | if not xxx: # root item | |
306 | html += 'this item has no properties' + htmlFooter | |
307 | panel.SetPage(html) | |
308 | if testWin and testWin.highLight: | |
309 | testWin.highLight.Remove() | |
310 | return | |
311 | # Normal nodes | |
312 | ClearRegister() # empty register | |
313 | html += xxx.generateHtml() | |
314 | html += htmlFooter | |
315 | panel.SetPage(html) | |
316 | # Set values, checkboxes to false, disable defaults | |
317 | if xxx.hasChild: prefix = '_' | |
318 | else: prefix = '' | |
319 | for param in xxx.allParams: | |
320 | if xxx.params.has_key(param): | |
321 | if param == 'content': | |
322 | value = [] | |
323 | for text in xxx.params[param]: | |
324 | value.append(str(text.data)) # convert from unicode | |
325 | else: | |
326 | value = xxx.params[param].data | |
327 | GetRegistered(prefix + param).SetValue(value) | |
328 | if not param in xxx.required: | |
329 | panel.FindWindowByName('check_' + param).SetValue(true) | |
330 | else: | |
331 | GetRegistered(prefix + param).Enable(false) | |
332 | # Same for the child of sizeritem | |
333 | if xxx.hasChild: | |
334 | xxx = xxx.child | |
335 | for param in xxx.allParams: | |
336 | if xxx.params.has_key(param): | |
337 | if param == 'content': | |
338 | value = [] | |
339 | for text in xxx.params[param]: | |
340 | value.append(str(text.data)) # convert from unicode | |
341 | else: | |
342 | value = xxx.params[param].data | |
343 | GetRegistered(param).SetValue(value) | |
344 | if not param in xxx.required: | |
345 | panel.FindWindowByName('check_' + param).SetValue(true) | |
346 | else: | |
347 | GetRegistered(param).Enable(false) | |
348 | # Clear flag | |
349 | panel.SetModified(false) | |
350 | # Hightlighting is done in OnIdle | |
351 | tree.pendingHighLight = item | |
352 | ||
353 | # Find top-level parent | |
354 | def GetItemAncestor(self, item): | |
355 | while self.GetItemParent(item) != self.GetRootItem(): | |
356 | item = self.GetItemParent(item) | |
357 | return item | |
358 | ||
359 | # Highlight selected item | |
360 | def HighLight(self, item): | |
361 | self.pendingHighLight = None | |
362 | if not testWin or self.GetPyData(testWin.item).className \ | |
363 | not in ['wxDialog', 'wxPanel', 'wxFrame']: | |
364 | return | |
365 | # Top-level does not have highlight | |
366 | if item == testWin.item: | |
367 | if testWin.highLight: testWin.highLight.Remove() | |
368 | return | |
369 | # If a control from another window is selected, remove highlight | |
370 | if self.GetItemAncestor(item) != testWin.item and testWin.highLight: | |
371 | testWin.highLight.Remove() | |
372 | return | |
373 | # Get window/sizer object | |
374 | obj, pos = self.FindNodeObject(item), self.FindNodePos(item) | |
375 | size = obj.GetSize() | |
376 | # For notebook, select item's page. | |
377 | # For children of page, nothing happens (too much work) | |
378 | if isinstance(self.GetPyData(item).parent, xxxNotebook): | |
379 | notebook = self.FindNodeObject(self.GetItemParent(item)) | |
380 | # Find position | |
381 | n = 0 | |
382 | prev = self.GetPrevSibling(item) | |
383 | while prev.IsOk(): | |
384 | n += 1 | |
385 | prev = self.GetPrevSibling(prev) | |
386 | notebook.SetSelection(n) | |
387 | # Highlight | |
388 | try: # finally I use exceptions | |
389 | testWin.highLight.Replace(pos, size) | |
390 | except AttributeError: | |
391 | testWin.highLight = HightLightBox(pos, size) | |
392 | testWin.highLight.item = item | |
393 | ||
394 | # Double-click | |
395 | def OnItemActivated(self, evt): | |
396 | item = evt.GetItem() | |
397 | xxx = self.GetPyData(item) | |
398 | if not xxx: return # if root selected, do nothing | |
399 | if panel.IsModified(): | |
400 | self.Apply(xxx, item) # apply changes | |
401 | self.CreateTestWin(item) | |
402 | ||
403 | # (re)create test window | |
404 | def CreateTestWin(self, node): | |
405 | global testWin | |
406 | # Create a window with this resource | |
407 | xxx = self.GetPyData(node).treeObject() | |
408 | if not xxx: return # if root selected, do nothing | |
409 | # If noname element, display error | |
410 | if not xxx.hasName or not xxx.name: | |
411 | wxLogError("Can't display a noname element") | |
412 | return | |
413 | # Close old window, remember where it was | |
414 | highLight = None | |
415 | if testWin: | |
416 | pos = testWin.GetPosition() | |
417 | if node == testWin.item: | |
418 | # Remember highlight if same top-level window | |
419 | if testWin.highLight: | |
420 | highLight = testWin.highLight.item | |
421 | # !!! if 0 is removed, refresh is broken (notebook not deleted?) | |
422 | if 0 and xxx.className == 'wxPanel': | |
423 | if testWin.highLight: | |
424 | testWin.pendingHighLight = highLight | |
425 | testWin.highLight.Remove() | |
426 | testWin.panel.Destroy() | |
427 | testWin.panel = None | |
428 | else: | |
429 | testWin.Destroy() | |
430 | testWin = None | |
431 | else: | |
432 | testWin.Destroy() | |
433 | testWin = None | |
434 | else: | |
435 | pos = testWinPos | |
436 | # Save in temporary file before activating | |
437 | memFile = MemoryFile(tempfile.mktemp('xrc')) | |
438 | #memFile = MemoryFile('core.xrc') # to write debug file | |
439 | # Create partial XML file - faster for big files | |
440 | ||
441 | dom = minidom.Document() | |
442 | mainNode = dom.createElement('resource') | |
443 | dom.appendChild(mainNode) | |
444 | ||
445 | # Remove temporarily from old parent | |
446 | elem = xxx.element | |
447 | parent = elem.parentNode | |
448 | next = elem.nextSibling | |
449 | parent.replaceChild(self.dummyNode, elem) | |
450 | # Append to new DOM, write it | |
451 | mainNode.appendChild(elem) | |
452 | dom.writexml(memFile) | |
453 | # Put back in place | |
454 | mainNode.removeChild(elem) | |
455 | dom.unlink() | |
456 | parent.replaceChild(elem, self.dummyNode) | |
457 | ||
458 | memFile.close() # write to wxMemoryFS | |
459 | res = wxXmlResource('') | |
460 | res.Load(memFile.name) | |
461 | if xxx.className == 'wxFrame': | |
462 | # Create new frame | |
463 | testWin = wxPreFrame() | |
464 | res.LoadFrame(testWin, frame, xxx.name) | |
465 | testWin.panel = testWin | |
466 | testWin.SetPosition(pos) | |
467 | testWin.Show(true) | |
468 | elif xxx.className == 'wxPanel': | |
469 | # Create new frame | |
470 | if not testWin: | |
471 | testWin = wxFrame(frame, -1, 'Panel: ' + xxx.name, pos=pos) | |
472 | testWin.panel = res.LoadPanel(testWin, xxx.name) | |
473 | testWin.SetSize(testWin.panel.GetSize()) | |
474 | testWin.Show(true) | |
475 | elif xxx.className == 'wxDialog': | |
476 | # Create new frame | |
477 | testWin = res.LoadDialog(None, xxx.name) | |
478 | testWin.panel = testWin | |
479 | testWin.SetPosition(pos) | |
480 | testWin.Show(true) | |
481 | elif xxx.className == 'wxMenuBar': | |
482 | testWin = wxFrame(frame, -1, 'MenuBar: ' + xxx.name, pos=pos) | |
483 | # Set status bar to display help | |
484 | testWin.CreateStatusBar() | |
485 | testWin.menuBar = res.LoadMenuBar(xxx.name) | |
486 | testWin.SetMenuBar(testWin.menuBar) | |
487 | testWin.Show(true) | |
488 | else: | |
489 | wxLogMessage('No view for this element yet') | |
490 | return | |
491 | os.unlink(memFile.name) # remove tmp file | |
492 | testWin.item = node | |
493 | testWin.Connect(testWin.GetId(), -1, wxEVT_CLOSE_WINDOW, self.OnCloseTestWin) | |
494 | testWin.highLight = None | |
495 | if highLight and not tree.pendingHighLight: | |
496 | self.HighLight(highLight) | |
497 | ||
498 | def OnCloseTestWin(self, evt): | |
499 | global testWin, testWinPos | |
500 | testWinPos = testWin.GetPosition() | |
501 | testWin.Destroy() | |
502 | testWin = None | |
503 | evt.Skip() | |
504 | ||
505 | # True if next item should be inserted after current (vs. appended to it) | |
506 | def NeedInsert(self, item): | |
507 | xxx = self.GetPyData(item) | |
508 | if not xxx: return false # root item | |
509 | if self.ctrl: return true # if Ctrl pressed, always insert | |
510 | if xxx.hasChildren and not self.ItemHasChildren(item): | |
511 | return false | |
512 | return not (self.IsExpanded(item) and self.ItemHasChildren(item)) | |
513 | ||
514 | # Pull-down | |
515 | def OnRightDown(self, evt): | |
516 | # Setup menu | |
517 | pullDownMenu.menu = wxMenu() | |
518 | item = self.GetSelection() | |
519 | if not item.IsOk(): | |
520 | pullDownMenu.menu.Append(pullDownMenu.ID_EXPAND, 'Expand', 'Expand tree') | |
521 | else: | |
522 | self.ctrl = evt.ControlDown() # save Ctrl state | |
523 | m = wxMenu() # create menu | |
524 | if item != self.GetRootItem(): needInsert = self.NeedInsert(item) | |
525 | if item == self.GetRootItem() or \ | |
526 | self.GetItemParent(item) == self.GetRootItem() and needInsert: | |
527 | m.Append(pullDownMenu.ID_NEW_PANEL, 'Panel', 'Create panel') | |
528 | m.Append(pullDownMenu.ID_NEW_DIALOG, 'Dialog', 'Create dialog') | |
529 | m.Append(pullDownMenu.ID_NEW_FRAME, 'Frame', 'Create frame') | |
530 | m.AppendSeparator() | |
531 | m.Append(pullDownMenu.ID_NEW_MENU_BAR, 'MenuBar', 'Create menu bar') | |
532 | m.Append(pullDownMenu.ID_NEW_MENU, 'Menu', 'Create menu') | |
533 | else: | |
534 | xxx = self.GetPyData(item) | |
535 | if xxx.__class__ == xxxMenuBar: | |
536 | m.Append(pullDownMenu.ID_NEW_MENU, 'Menu', 'Create menu') | |
537 | elif xxx.__class__ in [xxxMenu, xxxMenuItem]: | |
538 | SetMenu(m, pullDownMenu.menuControls) | |
539 | else: | |
540 | SetMenu(m, pullDownMenu.controls) | |
541 | if not (xxx.isSizer or \ | |
542 | xxx.parent and xxx.parent.isSizer): | |
543 | m.Enable(pullDownMenu.ID_NEW_SPACER, false) | |
544 | # Select correct label for create menu | |
545 | if item == self.GetRootItem(): | |
546 | pullDownMenu.menu.AppendMenu(wxNewId(), 'Create', m, 'Create top-level object') | |
547 | else: | |
548 | if not needInsert: | |
549 | pullDownMenu.menu.AppendMenu(wxNewId(), 'Create child', m, | |
550 | 'Create child object') | |
551 | else: | |
552 | pullDownMenu.menu.AppendMenu(wxNewId(), 'Create Sibling', m, | |
553 | 'Create sibling of selected object') | |
554 | pullDownMenu.menu.AppendSeparator() | |
555 | pullDownMenu.menu.Append(wxID_CUT, 'Cut', 'Cut to the clipboard') | |
556 | pullDownMenu.menu.Append(wxID_COPY, 'Copy', 'Copy to the clipboard') | |
557 | pullDownMenu.menu.Append(wxID_PASTE, 'Paste', 'Paste from the clipboard') | |
558 | pullDownMenu.menu.Append(pullDownMenu.ID_DELETE, | |
559 | 'Delete', 'Delete object') | |
560 | if item.IsOk() and self.ItemHasChildren(item): | |
561 | pullDownMenu.menu.AppendSeparator() | |
562 | pullDownMenu.menu.Append(pullDownMenu.ID_EXPAND, 'Expand', 'Expand subtree') | |
563 | self.PopupMenu(pullDownMenu.menu, evt.GetPosition()) | |
564 | pullDownMenu.menu.Destroy() | |
565 | pullDownMenu.menu = None | |
566 | ||
567 | ||
568 | # Clear tree | |
569 | def Clear(self): | |
570 | self.DeleteAllItems() | |
571 | # Add minimal structure | |
572 | root = self.AddRoot('XML tree') | |
573 | self.Unselect() | |
574 | if self.dom: self.dom.unlink() | |
575 | self.dom = minidom.Document() | |
576 | self.dummyNode = self.dom.createComment('dummy node') | |
577 | # Create main node | |
578 | self.mainNode = self.dom.createElement('resource') | |
579 | self.dom.appendChild(self.mainNode) | |
580 | ||
581 | # Apply changes | |
582 | def Apply(self, xxx, item): | |
583 | if not xxx: return | |
584 | # !!! Save undo info | |
585 | if xxx.undo: xxx.undo.unlink() | |
586 | xxx.undo = xxx.element.cloneNode(false) | |
587 | if xxx.hasName: | |
588 | name = GetRegistered('name').GetValue() | |
589 | if xxx.name != name: | |
590 | xxx.name = name | |
591 | xxx.element.setAttribute('name', name) | |
592 | self.SetItemText(item, xxx.treeName()) | |
593 | if xxx.hasChild: prefix = '_' | |
594 | else: prefix = '' | |
595 | for param, data in xxx.params.items(): | |
596 | value = GetRegistered(prefix + param).GetValue() | |
597 | if param == 'content': | |
598 | # If number if items is not the same, recreate children | |
599 | if len(value) != len(data): | |
600 | elem = xxx.element.getElementsByTagName('content')[0] | |
601 | for n in elem.childNodes: | |
602 | elem.removeChild(n) | |
603 | data = [] | |
604 | for str in value: | |
605 | itemElem = tree.dom.createElement('item') | |
606 | itemText = tree.dom.createTextNode(str) | |
607 | itemElem.appendChild(itemText) | |
608 | elem.appendChild(itemElem) | |
609 | data.append(itemText) | |
610 | xxx.params[param] = data | |
611 | else: | |
612 | for i in range(len(value)): | |
613 | data[i].data = value[i] | |
614 | else: | |
615 | data.data = value | |
616 | if xxx.hasChild: | |
617 | self.Apply(xxx.child, item) | |
618 | else: | |
619 | # Change tree icon for sizers | |
620 | if isinstance(xxx, xxxBoxSizer): | |
621 | self.SetItemImage(item, xxx.treeImage()) | |
622 | # Set global modified state | |
623 | frame.modified = true | |
624 | ||
625 | class Frame(wxFrame): | |
626 | def __init__(self, size): | |
627 | wxFrame.__init__(self, None, -1, '', size=size) | |
628 | self.CreateStatusBar() | |
629 | ||
630 | # Make menus | |
631 | menuBar = wxMenuBar() | |
632 | ||
633 | menu = wxMenu() | |
634 | menu.Append(wxID_NEW, '&New\tCtrl-N', 'New file') | |
635 | menu.Append(wxID_OPEN, '&Open...\tCtrl-O', 'Open XRC file') | |
636 | menu.Append(wxID_SAVE, '&Save\tCtrl-S', 'Save XRC file') | |
637 | menu.Append(wxID_SAVEAS, 'Save &As...', 'Save XRC file under different name') | |
638 | menu.AppendSeparator() | |
639 | menu.Append(wxID_EXIT, '&Quit\tCtrl-Q', 'Exit application') | |
640 | menuBar.Append(menu, '&File') | |
641 | ||
642 | menu = wxMenu() | |
643 | menu.Append(wxID_UNDO, '&Undo\tCtrl-Z', 'Undo') | |
644 | menu.Append(wxID_REDO, '&Redo\tCtrl-R', 'Redo') | |
645 | menu.AppendSeparator() | |
646 | menu.Append(wxID_CUT, 'Cut\tCtrl-X', 'Cut to the clipboard') | |
647 | menu.Append(wxID_COPY, '&Copy\tCtrl-C', 'Copy to the clipboard') | |
648 | menu.Append(wxID_PASTE, '&Paste\tCtrl-V', 'Paste from the clipboard') | |
649 | self.ID_DELETE = wxNewId() | |
650 | menu.Append(self.ID_DELETE, '&Delete\tCtrl-D', 'Delete object') | |
651 | menuBar.Append(menu, '&Edit') | |
652 | ||
653 | menu = wxMenu() | |
654 | self.ID_REFRESH = wxNewId() | |
655 | menu.Append(self.ID_REFRESH, '&Refresh\tCtrl-R', 'Refresh view') | |
656 | self.ID_AUTO_REFRESH = wxNewId() | |
657 | menu.Append(self.ID_AUTO_REFRESH, '&Auto-refresh\tCtrl-A', | |
658 | 'Toggle auto-refresh mode', true) | |
659 | menu.Check(self.ID_AUTO_REFRESH, conf.autoRefresh) | |
660 | menuBar.Append(menu, '&View') | |
661 | ||
662 | menu = wxMenu() | |
663 | menu.Append(wxID_ABOUT, 'About...', 'About XCRed') | |
664 | if debug: | |
665 | self.ID_DEBUG_CMD = wxNewId() | |
666 | menu.Append(self.ID_DEBUG_CMD, 'CMD', 'Python command line') | |
667 | EVT_MENU(self, self.ID_DEBUG_CMD, self.OnDebugCMD) | |
668 | menuBar.Append(menu, '&Help') | |
669 | ||
670 | self.menuBar = menuBar | |
671 | self.SetMenuBar(menuBar) | |
672 | ||
673 | # Create toolbar | |
674 | tb = self.CreateToolBar()#wxTB_DOCKABLE | wxTB_FLAT) | |
675 | tb.SetToolBitmapSize((24,23)) | |
676 | tb.AddSimpleTool(wxID_NEW, images.getNewBitmap(), 'New', 'New file') | |
677 | tb.AddSimpleTool(wxID_OPEN, images.getOpenBitmap(), 'Open', 'Open file') | |
678 | tb.AddSimpleTool(wxID_SAVE, images.getSaveBitmap(), 'Save', 'Save file') | |
679 | tb.AddSeparator() | |
680 | tb.AddSimpleTool(wxID_CUT, images.getCutBitmap(), 'Cut', 'Cut') | |
681 | tb.AddSimpleTool(wxID_COPY, images.getCopyBitmap(), 'Copy', 'Copy') | |
682 | tb.AddSimpleTool(wxID_PASTE, images.getPasteBitmap(), 'Paste', 'Paste') | |
683 | tb.AddSeparator() | |
684 | tb.AddSimpleTool(self.ID_REFRESH, images.getRefreshBitmap(), | |
685 | 'Refresh', 'Refresh view') | |
686 | tb.AddSimpleTool(self.ID_AUTO_REFRESH, images.getAutoRefreshBitmap(), | |
687 | 'Auto-refresh', 'Toggle auto-refresh mode', true) | |
688 | tb.ToggleTool(self.ID_AUTO_REFRESH, conf.autoRefresh) | |
689 | self.tb = tb | |
690 | tb.Realize() | |
691 | ||
692 | # File | |
693 | EVT_MENU(self, wxID_NEW, self.OnNew) | |
694 | EVT_MENU(self, wxID_OPEN, self.OnOpen) | |
695 | EVT_MENU(self, wxID_SAVE, self.OnSaveOrSaveAs) | |
696 | EVT_MENU(self, wxID_SAVEAS, self.OnSaveOrSaveAs) | |
697 | EVT_MENU(self, wxID_EXIT, self.OnExit) | |
698 | # Edit | |
699 | EVT_MENU(self, wxID_UNDO, self.OnUndo) | |
700 | EVT_MENU(self, wxID_REDO, self.OnRedo) | |
701 | EVT_MENU(self, wxID_CUT, self.OnCut) | |
702 | EVT_MENU(self, wxID_COPY, self.OnCopy) | |
703 | EVT_MENU(self, wxID_PASTE, self.OnPaste) | |
704 | EVT_MENU(self, self.ID_DELETE, self.OnDelete) | |
705 | # View | |
706 | EVT_MENU(self, self.ID_REFRESH, self.OnRefresh) | |
707 | EVT_MENU(self, self.ID_AUTO_REFRESH, self.OnAutoRefresh) | |
708 | # Help | |
709 | EVT_MENU(self, wxID_ABOUT, self.OnAbout) | |
710 | ||
711 | # Update events | |
712 | EVT_UPDATE_UI(self, wxID_CUT, self.OnUpdateUI) | |
713 | EVT_UPDATE_UI(self, wxID_COPY, self.OnUpdateUI) | |
714 | EVT_UPDATE_UI(self, wxID_PASTE, self.OnUpdateUI) | |
715 | EVT_UPDATE_UI(self, self.ID_DELETE, self.OnUpdateUI) | |
716 | ||
717 | # Build interface | |
718 | splitter = wxSplitterWindow(self, -1) | |
719 | # Create tree | |
720 | global tree | |
721 | tree = XML_Tree(splitter, -1) | |
722 | sys.modules['xxx'].tree = tree | |
723 | # Create panel for parameters | |
724 | global panel | |
725 | #panel = wxPanel(self, -1) | |
726 | # Sizer for static box | |
727 | #sizer = wxBoxSizer() | |
728 | panel = Panel(splitter, -1) | |
729 | sys.modules['params'].panel = panel | |
730 | #sizer.Add(panel, 1, wxEXPAND) | |
731 | #box = wxStaticBox(panel, -1, 'Parameters') | |
732 | #boxSizer = wxStaticBoxSizer(box) | |
733 | #boxSizer.Add(wxButton(panel, -1, 'BUTT ON')) | |
734 | #sizer.Add(boxSizer, 1, wxEXPAND | wxALL, 10) | |
735 | #panel.SetAutoLayout(true) | |
736 | #panel.SetSizer(sizer) | |
737 | # Set plitter windows | |
738 | splitter.SplitVertically(tree, panel, 200) | |
739 | #topSizer = wxBoxSizer() | |
740 | #topSizer.Add(splitter, 1, wxEXPAND) | |
741 | #self.SetAutoLayout(true) | |
742 | #self.SetSizer(topSizer) | |
743 | ||
744 | # Init pull-down menu data | |
745 | class MenuData: pass | |
746 | global pullDownMenu | |
747 | pullDownMenu = MenuData() | |
748 | pullDownMenu.menu = None | |
749 | pullDownMenu.ID_NEW_PANEL = wxNewId() | |
750 | pullDownMenu.ID_NEW_DIALOG = wxNewId() | |
751 | pullDownMenu.ID_NEW_FRAME = wxNewId() | |
752 | pullDownMenu.ID_NEW_MENU_BAR = wxNewId() | |
753 | pullDownMenu.ID_NEW_MENU = wxNewId() | |
754 | ||
755 | pullDownMenu.ID_NEW_STATIC_TEXT = wxNewId() | |
756 | pullDownMenu.ID_NEW_TEXT_CTRL = wxNewId() | |
757 | ||
758 | pullDownMenu.ID_NEW_BUTTON = wxNewId() | |
759 | pullDownMenu.ID_NEW_BITMAP_BUTTON = wxNewId() | |
760 | pullDownMenu.ID_NEW_RADIO_BUTTON = wxNewId() | |
761 | pullDownMenu.ID_NEW_SPIN_BUTTON = wxNewId() | |
762 | ||
763 | pullDownMenu.ID_NEW_STATIC_BOX = wxNewId() | |
764 | pullDownMenu.ID_NEW_CHECK_BOX = wxNewId() | |
765 | pullDownMenu.ID_NEW_RADIO_BOX = wxNewId() | |
766 | pullDownMenu.ID_NEW_COMBO_BOX = wxNewId() | |
767 | pullDownMenu.ID_NEW_LIST_BOX = wxNewId() | |
768 | ||
769 | pullDownMenu.ID_NEW_STATIC_LINE = wxNewId() | |
770 | pullDownMenu.ID_NEW_CHOICE = wxNewId() | |
771 | pullDownMenu.ID_NEW_SLIDER = wxNewId() | |
772 | pullDownMenu.ID_NEW_GAUGE = wxNewId() | |
773 | pullDownMenu.ID_NEW_SCROLL_BAR = wxNewId() | |
774 | pullDownMenu.ID_NEW_TREE_CTRL = wxNewId() | |
775 | pullDownMenu.ID_NEW_LIST_CTRL = wxNewId() | |
776 | pullDownMenu.ID_NEW_CHECK_LIST = wxNewId() | |
777 | pullDownMenu.ID_NEW_NOTEBOOK = wxNewId() | |
778 | pullDownMenu.ID_NEW_HTML_WINDOW = wxNewId() | |
779 | pullDownMenu.ID_NEW_CALENDAR = wxNewId() | |
780 | ||
781 | pullDownMenu.ID_NEW_BOX_SIZER = wxNewId() | |
782 | pullDownMenu.ID_NEW_STATIC_BOX_SIZER = wxNewId() | |
783 | pullDownMenu.ID_NEW_GRID_SIZER = wxNewId() | |
784 | pullDownMenu.ID_NEW_FLEX_GRID_SIZER = wxNewId() | |
785 | pullDownMenu.ID_NEW_SPACER = wxNewId() | |
786 | pullDownMenu.ID_NEW_MENU = wxNewId() | |
787 | pullDownMenu.ID_NEW_MENU_ITEM = wxNewId() | |
788 | pullDownMenu.ID_NEW_SEPARATOR = wxNewId() | |
789 | pullDownMenu.ID_NEW_LAST = wxNewId() | |
790 | pullDownMenu.ID_DELETE = self.ID_DELETE | |
791 | pullDownMenu.ID_EXPAND = wxNewId() | |
792 | EVT_MENU_RANGE(self, pullDownMenu.ID_NEW_PANEL, | |
793 | pullDownMenu.ID_NEW_LAST, self.OnCreate) | |
794 | EVT_MENU(self, pullDownMenu.ID_EXPAND, self.OnExpand) | |
795 | # We connect to tree, but process in frame | |
796 | EVT_MENU_HIGHLIGHT_ALL(tree, self.OnPullDownHighlight) | |
797 | # Mapping from IDs to element names | |
798 | self.createMap = { | |
799 | pullDownMenu.ID_NEW_PANEL: 'wxPanel', | |
800 | pullDownMenu.ID_NEW_DIALOG: 'wxDialog', | |
801 | pullDownMenu.ID_NEW_FRAME: 'wxFrame', | |
802 | pullDownMenu.ID_NEW_MENU_BAR: 'wxMenuBar', | |
803 | pullDownMenu.ID_NEW_MENU: 'wxMenu', | |
804 | ||
805 | pullDownMenu.ID_NEW_STATIC_TEXT: 'wxStaticText', | |
806 | pullDownMenu.ID_NEW_TEXT_CTRL: 'wxTextCtrl', | |
807 | ||
808 | pullDownMenu.ID_NEW_BUTTON: 'wxButton', | |
809 | pullDownMenu.ID_NEW_BITMAP_BUTTON: 'wxBitmapButton', | |
810 | pullDownMenu.ID_NEW_RADIO_BUTTON: 'wxRadioButton', | |
811 | pullDownMenu.ID_NEW_SPIN_BUTTON: 'wxSpinButton', | |
812 | ||
813 | pullDownMenu.ID_NEW_STATIC_BOX: 'wxStaticBox', | |
814 | pullDownMenu.ID_NEW_CHECK_BOX: 'wxCheckBox', | |
815 | pullDownMenu.ID_NEW_RADIO_BOX: 'wxRadioBox', | |
816 | pullDownMenu.ID_NEW_COMBO_BOX: 'wxComboBox', | |
817 | pullDownMenu.ID_NEW_LIST_BOX: 'wxListBox', | |
818 | ||
819 | pullDownMenu.ID_NEW_STATIC_LINE: 'wxStaticLine', | |
820 | pullDownMenu.ID_NEW_CHOICE: 'wxChoice', | |
821 | pullDownMenu.ID_NEW_SLIDER: 'wxSlider', | |
822 | pullDownMenu.ID_NEW_GAUGE: 'wxGauge', | |
823 | pullDownMenu.ID_NEW_SCROLL_BAR: 'wxScrollBar', | |
824 | pullDownMenu.ID_NEW_TREE_CTRL: 'wxTreeCtrl', | |
825 | pullDownMenu.ID_NEW_LIST_CTRL: 'wxListCtrl', | |
826 | pullDownMenu.ID_NEW_CHECK_LIST: 'wxCheckList', | |
827 | pullDownMenu.ID_NEW_NOTEBOOK: 'wxNotebook', | |
828 | pullDownMenu.ID_NEW_HTML_WINDOW: 'wxHtmlWindow', | |
829 | pullDownMenu.ID_NEW_CALENDAR: 'wxCalendar', | |
830 | ||
831 | pullDownMenu.ID_NEW_BOX_SIZER: 'wxBoxSizer', | |
832 | pullDownMenu.ID_NEW_STATIC_BOX_SIZER: 'wxStaticBoxSizer', | |
833 | pullDownMenu.ID_NEW_GRID_SIZER: 'wxGridSizer', | |
834 | pullDownMenu.ID_NEW_FLEX_GRID_SIZER: 'wxFlexGridSizer', | |
835 | pullDownMenu.ID_NEW_SPACER: 'spacer', | |
836 | pullDownMenu.ID_NEW_MENU: 'wxMenu', | |
837 | pullDownMenu.ID_NEW_MENU_ITEM: 'wxMenuItem', | |
838 | pullDownMenu.ID_NEW_SEPARATOR: 'separator', | |
839 | } | |
840 | pullDownMenu.controls = [ | |
841 | ['control', 'Various controls', | |
842 | (pullDownMenu.ID_NEW_STATIC_TEXT, 'Label', 'Create static label'), | |
843 | (pullDownMenu.ID_NEW_STATIC_LINE, 'Line', 'Create static line'), | |
844 | (pullDownMenu.ID_NEW_TEXT_CTRL, 'TextBox', 'Create text box control'), | |
845 | (pullDownMenu.ID_NEW_CHOICE, 'Choice', 'Create choice control'), | |
846 | (pullDownMenu.ID_NEW_SLIDER, 'Slider', 'Create slider control'), | |
847 | (pullDownMenu.ID_NEW_GAUGE, 'Gauge', 'Create gauge control'), | |
848 | (pullDownMenu.ID_NEW_SCROLL_BAR, 'ScrollBar', 'Create scroll bar'), | |
849 | (pullDownMenu.ID_NEW_TREE_CTRL, 'TreeCtrl', 'Create tree control'), | |
850 | (pullDownMenu.ID_NEW_LIST_CTRL, 'ListCtrl', 'Create list control'), | |
851 | (pullDownMenu.ID_NEW_CHECK_LIST, 'CheckList', 'Create check list control'), | |
852 | (pullDownMenu.ID_NEW_HTML_WINDOW, 'HtmlWindow', 'Create HTML window'), | |
853 | (pullDownMenu.ID_NEW_CALENDAR, 'Calendar', 'Create calendar control'), | |
854 | (pullDownMenu.ID_NEW_PANEL, 'Panel', 'Create panel'), | |
855 | (pullDownMenu.ID_NEW_NOTEBOOK, 'Notebook', 'Create notebook control'), | |
856 | ], | |
857 | ['button', 'Buttons', | |
858 | (pullDownMenu.ID_NEW_BUTTON, 'Button', 'Create button'), | |
859 | (pullDownMenu.ID_NEW_BITMAP_BUTTON, 'BitmapButton', 'Create bitmap button'), | |
860 | (pullDownMenu.ID_NEW_RADIO_BUTTON, 'RadioButton', 'Create radio button'), | |
861 | (pullDownMenu.ID_NEW_SPIN_BUTTON, 'SpinButton', 'Create spin button'), | |
862 | ], | |
863 | ['box', 'Boxes', | |
864 | (pullDownMenu.ID_NEW_STATIC_BOX, 'StaticBox', 'Create static box'), | |
865 | (pullDownMenu.ID_NEW_CHECK_BOX, 'CheckBox', 'Create check box'), | |
866 | (pullDownMenu.ID_NEW_RADIO_BOX, 'RadioBox', 'Create radio box'), | |
867 | (pullDownMenu.ID_NEW_COMBO_BOX, 'ComboBox', 'Create combo box'), | |
868 | (pullDownMenu.ID_NEW_LIST_BOX, 'ListBox', 'Create list box'), | |
869 | ], | |
870 | ['sizer', 'Sizers', | |
871 | (pullDownMenu.ID_NEW_BOX_SIZER, 'BoxSizer', 'Create box sizer'), | |
872 | (pullDownMenu.ID_NEW_STATIC_BOX_SIZER, 'StaticBoxSizer', | |
873 | 'Create static box sizer'), | |
874 | (pullDownMenu.ID_NEW_GRID_SIZER, 'GridSizer', 'Create grid sizer'), | |
875 | (pullDownMenu.ID_NEW_FLEX_GRID_SIZER, 'FlexGridSizer', | |
876 | 'Create flexgrid sizer'), | |
877 | (pullDownMenu.ID_NEW_SPACER, 'Spacer', 'Create spacer'), | |
878 | ] | |
879 | ] | |
880 | pullDownMenu.menuControls = [ | |
881 | (pullDownMenu.ID_NEW_MENU, 'Menu', 'Create menu'), | |
882 | (pullDownMenu.ID_NEW_MENU_ITEM, 'MenuItem', 'Create menu item'), | |
883 | (pullDownMenu.ID_NEW_SEPARATOR, 'Separator', 'Create separator'), | |
884 | ] | |
885 | ||
886 | # Initialize | |
887 | self.Clear() | |
888 | ||
889 | # Other events | |
890 | EVT_IDLE(self, self.OnIdle) | |
891 | EVT_CLOSE(self, self.OnCloseWindow) | |
892 | ||
893 | def OnNew(self, evt): | |
894 | self.Clear() | |
895 | ||
896 | def OnOpen(self, evt): | |
897 | if not self.AskSave(): return | |
898 | dlg = wxFileDialog(self, 'Open', os.path.dirname(self.dataFile), | |
899 | '', '*.xrc', wxOPEN | wxCHANGE_DIR) | |
900 | if dlg.ShowModal() == wxID_OK: | |
901 | path = dlg.GetPath() | |
902 | self.SetStatusText('Loading...') | |
903 | wxYield() | |
904 | wxBeginBusyCursor() | |
905 | self.Open(path) | |
906 | wxEndBusyCursor() | |
907 | self.SetStatusText('Ready') | |
908 | dlg.Destroy() | |
909 | ||
910 | def OnSaveOrSaveAs(self, evt): | |
911 | if evt.GetId() == wxID_SAVEAS or not self.dataFile: | |
912 | if self.dataFile: defaultName = '' | |
913 | else: defaultName = 'UNTITLED.xrc' | |
914 | dlg = wxFileDialog(self, 'Save As', os.path.dirname(self.dataFile), | |
915 | defaultName, '*.xrc', | |
916 | wxSAVE | wxOVERWRITE_PROMPT | wxCHANGE_DIR) | |
917 | if dlg.ShowModal() == wxID_CANCEL: return | |
918 | path = dlg.GetPath() | |
919 | dlg.Destroy() | |
920 | else: | |
921 | path = self.dataFile | |
922 | self.SetStatusText('Saving...') | |
923 | wxYield() | |
924 | wxBeginBusyCursor() | |
925 | self.Save(path) | |
926 | self.dataFile = path | |
927 | wxEndBusyCursor() | |
928 | self.SetStatusText('Ready') | |
929 | ||
930 | def OnExit(self, evt): | |
931 | self.Close() | |
932 | ||
933 | def OnUndo(self, evt): | |
934 | print '*** being implemented' | |
935 | print self.lastOp, self.undo | |
936 | if self.lastOp == 'DELETE': | |
937 | parent, prev, elem = self.undo | |
938 | if prev.IsOk(): | |
939 | xxx = MakeXXXFromDOM(tree.GetPyData(parent).treeObject(), elem) | |
940 | item = tree.InsertItem( parent, prev, xxx.treeObject().className, | |
941 | data=wxTreeItemData(xxx) ) | |
942 | ||
943 | def OnRedo(self, evt): | |
944 | print '*** being implemented' | |
945 | ||
946 | def OnCut(self, evt): | |
947 | selected = tree.GetSelection() | |
948 | # Undo info | |
949 | self.lastOp = 'CUT' | |
950 | self.undo = [tree.GetItemParent(selected), tree.GetPrevSibling(selected)] | |
951 | # Delete testWin? | |
952 | global testWin | |
953 | if testWin: | |
954 | # If deleting top-level item, delete testWin | |
955 | if selected == testWin.item: | |
956 | testWin.Destroy() | |
957 | testWin = None | |
958 | else: | |
959 | # Remove highlight, update testWin | |
960 | if tree.GetItemAncestor(selected) == testWin.item: | |
961 | if testWin.highLight: testWin.highLight.Remove() | |
962 | tree.needUpdate = true | |
963 | self.clipboard = tree.RemoveLeaf(selected) | |
964 | tree.pendingHighLight = None | |
965 | tree.Unselect() | |
966 | panel.Clear() | |
967 | self.modified = true | |
968 | ||
969 | def OnCopy(self, evt): | |
970 | selected = tree.GetSelection() | |
971 | xxx = tree.GetPyData(selected) | |
972 | self.clipboard = xxx.element.cloneNode(true) | |
973 | ||
974 | def OnPaste(self, evt): | |
975 | selected = tree.GetSelection() | |
976 | appendChild = not tree.NeedInsert(selected) | |
977 | xxx = tree.GetPyData(selected) | |
978 | if not appendChild: | |
979 | # If has next item, insert, else append to parent | |
980 | nextItem = tree.GetNextSibling(selected) | |
981 | if nextItem.IsOk(): | |
982 | # Insert before nextItem | |
983 | parentLeaf = tree.GetItemParent(selected) | |
984 | else: # last child: change selected to parent | |
985 | appendChild = true | |
986 | selected = tree.GetItemParent(selected) | |
987 | # Expanded container (must have children) | |
988 | elif tree.IsExpanded(selected) and tree.ItemHasChildren(selected): | |
989 | appendChild = false | |
990 | nextItem = tree.GetFirstChild(selected, 0)[0] | |
991 | parentLeaf = selected | |
992 | # Parent should be tree element or None | |
993 | if appendChild: | |
994 | parent = tree.GetPyData(selected) | |
995 | else: | |
996 | parent = tree.GetPyData(parentLeaf) | |
997 | if parent and parent.hasChild: parent = parent.child | |
998 | ||
999 | # Create a copy of clipboard element | |
1000 | elem = self.clipboard.cloneNode(true) | |
1001 | # Tempopary xxx object to test things | |
1002 | xxx = MakeXXXFromDOM(parent, elem) | |
1003 | className = xxx.treeObject().className | |
1004 | # Check parent and child relationships | |
1005 | # Parent is sizer or notebook, child is of wrong class or | |
1006 | # parent is normal window, child is child container: detach child | |
1007 | isChildContainer = isinstance(xxx, xxxChildContainer) | |
1008 | if not parent and isChildContainer or \ | |
1009 | (parent.isSizer and not isinstance(xxx, xxxSizerItem)) or \ | |
1010 | (isinstance(parent, xxxNotebook) and not isinstance(xxx, xxxNotebookPage)) or \ | |
1011 | (not parent.isSizer and not isinstance(parent, xxxNotebook) and \ | |
1012 | isChildContainer): | |
1013 | if isChildContainer: | |
1014 | elem.removeChild(xxx.child.element) # detach child | |
1015 | elem.unlink() # delete child container | |
1016 | elem = xxx.child.element # replace | |
1017 | # This should help garbage collection (!!! maybe not needed?) | |
1018 | xxx.child.parent = None | |
1019 | xxx.child = None | |
1020 | if parent: | |
1021 | # Parent is sizer or notebook, child is not child container | |
1022 | if parent.isSizer and not isChildContainer and \ | |
1023 | not isinstance(xxx, xxxSpacer): | |
1024 | # Create sizer item element | |
1025 | sizerItemElem = MakeEmptyDOM('sizeritem') | |
1026 | sizerItemElem.appendChild(elem) | |
1027 | elem = sizerItemElem | |
1028 | elif isinstance(parent, xxxNotebook) and not isChildContainer: | |
1029 | pageElem = MakeEmptyDOM('notebookpage') | |
1030 | pageElem.appendChild(elem) | |
1031 | elem = pageElem | |
1032 | xxx = MakeXXXFromDOM(parent, elem) | |
1033 | # Figure out if we must append a new child or sibling | |
1034 | if appendChild: | |
1035 | if parent: node = parent.element | |
1036 | else: node = tree.mainNode | |
1037 | node.appendChild(elem) | |
1038 | newItem = tree.AppendItem(selected, xxx.treeName(), image=xxx.treeImage(), | |
1039 | data=wxTreeItemData(xxx)) | |
1040 | else: | |
1041 | node = tree.GetPyData(nextItem).element | |
1042 | if parent: | |
1043 | elemParent = parent.element | |
1044 | else: | |
1045 | elemParent = tree.mainNode | |
1046 | elemParent.insertBefore(elem, node) | |
1047 | # Inserting before is difficult, se we insert after or first child | |
1048 | newItem = tree.InsertItem(parentLeaf, selected, xxx.treeName(), | |
1049 | image=xxx.treeImage(), data=wxTreeItemData(xxx)) | |
1050 | # Add children items | |
1051 | if xxx.hasChildren: | |
1052 | treeObj = xxx.treeObject() | |
1053 | for n in treeObj.element.childNodes: | |
1054 | if IsObject(n): | |
1055 | tree.AddNode(newItem, treeObj, n) | |
1056 | # Scroll to show new item | |
1057 | tree.EnsureVisible(newItem) | |
1058 | tree.SelectItem(newItem) | |
1059 | if not tree.IsVisible(newItem): | |
1060 | tree.ScrollTo(newItem) | |
1061 | tree.Refresh() | |
1062 | # Update view? | |
1063 | if testWin and tree.GetItemAncestor(newItem) == testWin.item: | |
1064 | if conf.autoRefresh: | |
1065 | tree.needUpdate = true | |
1066 | tree.pendingHighLight = newItem | |
1067 | else: | |
1068 | tree.pendingHighLight = None | |
1069 | self.modified = true | |
1070 | ||
1071 | def OnDelete(self, evt): | |
1072 | selected = tree.GetSelection() | |
1073 | # Undo info | |
1074 | self.lastOp = 'DELETE' | |
1075 | self.undo = [tree.GetItemParent(selected), tree.GetPrevSibling(selected)] | |
1076 | # Delete testWin? | |
1077 | global testWin | |
1078 | if testWin: | |
1079 | # If deleting top-level item, delete testWin | |
1080 | if selected == testWin.item: | |
1081 | testWin.Destroy() | |
1082 | testWin = None | |
1083 | else: | |
1084 | # Remove highlight, update testWin | |
1085 | if tree.GetItemAncestor(selected) == testWin.item: | |
1086 | if testWin.highLight: testWin.highLight.Remove() | |
1087 | tree.needUpdate = true | |
1088 | xnode = tree.RemoveLeaf(selected) | |
1089 | self.undo.append(xnode.cloneNode(true)) | |
1090 | xnode.unlink() | |
1091 | tree.pendingHighLight = None | |
1092 | tree.Unselect() | |
1093 | panel.Clear() | |
1094 | self.modified = true | |
1095 | ||
1096 | def OnRefresh(self, evt): | |
1097 | # If modified, apply first | |
1098 | selection = tree.GetSelection() | |
1099 | if selection.IsOk(): | |
1100 | xxx = tree.GetPyData(selection) | |
1101 | if xxx and panel.IsModified(): | |
1102 | tree.Apply(xxx, selection) | |
1103 | if testWin: | |
1104 | # (re)create | |
1105 | tree.CreateTestWin(testWin.item) | |
1106 | tree.needUpdate = false | |
1107 | ||
1108 | def OnAutoRefresh(self, evt): | |
1109 | conf.autoRefresh = evt.IsChecked() | |
1110 | self.menuBar.Check(self.ID_AUTO_REFRESH, conf.autoRefresh) | |
1111 | self.tb.ToggleTool(self.ID_AUTO_REFRESH, conf.autoRefresh) | |
1112 | ||
1113 | def OnAbout(self, evt): | |
1114 | dlg = wxMessageDialog(self, '%s %s\n\nRoman Rolinsky <rolinsky@mema.ucl.ac.be>' % \ | |
1115 | (progname, version), | |
1116 | 'About %s' % progname, wxOK | wxCENTRE) | |
1117 | dlg.ShowModal() | |
1118 | dlg.Destroy() | |
1119 | ||
1120 | # Simple emulation of python command line | |
1121 | def OnDebugCMD(self, evt): | |
1122 | while 1: | |
1123 | try: | |
1124 | exec raw_input('C:\> ') | |
1125 | except EOFError: | |
1126 | print '^D' | |
1127 | break | |
1128 | except: | |
1129 | (etype, value, tb) =sys.exc_info() | |
1130 | tblist =traceback.extract_tb(tb)[1:] | |
1131 | msg =string.join(traceback.format_exception_only(etype, value) | |
1132 | +traceback.format_list(tblist)) | |
1133 | print msg | |
1134 | ||
1135 | def OnCreate(self, evt): | |
1136 | selected = tree.GetSelection() | |
1137 | appendChild = not tree.NeedInsert(selected) | |
1138 | xxx = tree.GetPyData(selected) | |
1139 | if not appendChild: | |
1140 | # If has next item, insert, else append to parent | |
1141 | nextItem = tree.GetNextSibling(selected) | |
1142 | if nextItem.IsOk(): | |
1143 | # Insert before nextItem | |
1144 | parentLeaf = tree.GetItemParent(selected) | |
1145 | else: # last child: change selected to parent | |
1146 | appendChild = true | |
1147 | selected = tree.GetItemParent(selected) | |
1148 | # Expanded container (must have children) | |
1149 | elif tree.IsExpanded(selected) and tree.ItemHasChildren(selected): | |
1150 | appendChild = false | |
1151 | nextItem = tree.GetFirstChild(selected, 0)[0] | |
1152 | parentLeaf = selected | |
1153 | # Parent should be tree element or None | |
1154 | if appendChild: | |
1155 | parent = tree.GetPyData(selected) | |
1156 | else: | |
1157 | parent = tree.GetPyData(parentLeaf) | |
1158 | if parent and parent.hasChild: parent = parent.child | |
1159 | ||
1160 | # Create element | |
1161 | className = self.createMap[evt.GetId()] | |
1162 | xxx = MakeEmptyXXX(parent, className) | |
1163 | # Figure out if we must append a new child or sibling | |
1164 | elem = xxx.element | |
1165 | if appendChild: | |
1166 | if parent: node = parent.element | |
1167 | else: node = tree.mainNode | |
1168 | # Insert newline for debug purposes | |
1169 | node.appendChild(tree.dom.createTextNode('\n')) | |
1170 | node.appendChild(elem) | |
1171 | newItem = tree.AppendItem(selected, xxx.treeName(), image=xxx.treeImage(), | |
1172 | data=wxTreeItemData(xxx)) | |
1173 | else: | |
1174 | node = tree.GetPyData(nextItem).element | |
1175 | if parent: | |
1176 | elemParent = parent.element | |
1177 | else: | |
1178 | elemParent = tree.mainNode | |
1179 | elemParent.insertBefore(tree.dom.createTextNode('\n'), node) | |
1180 | elemParent.insertBefore(elem, node) | |
1181 | # Inserting before is difficult, se we insert after or first child | |
1182 | newItem = tree.InsertItem(parentLeaf, selected, | |
1183 | xxx.treeName(), image=xxx.treeImage(), | |
1184 | data=wxTreeItemData(xxx)) | |
1185 | tree.EnsureVisible(newItem) | |
1186 | tree.SelectItem(newItem) | |
1187 | if not tree.IsVisible(newItem): | |
1188 | tree.ScrollTo(newItem) | |
1189 | tree.Refresh() | |
1190 | # Update view? | |
1191 | if testWin and tree.GetItemAncestor(newItem) == testWin.item: | |
1192 | if conf.autoRefresh: | |
1193 | tree.needUpdate = true | |
1194 | tree.pendingHighLight = newItem | |
1195 | else: | |
1196 | tree.pendingHighLight = None | |
1197 | ||
1198 | def OnExpand(self, evt): | |
1199 | if tree.GetSelection().IsOk(): | |
1200 | tree.ExpandAll(tree.GetSelection()) | |
1201 | else: | |
1202 | tree.ExpandAll(tree.GetRootItem()) | |
1203 | ||
1204 | def OnPullDownHighlight(self, evt): | |
1205 | menuId = evt.GetMenuId() | |
1206 | help = '' | |
1207 | if menuId != -1: help = pullDownMenu.GetHelpString(menuId) | |
1208 | self.SetStatusText(help) | |
1209 | ||
1210 | def OnUpdateUI(self, evt): | |
1211 | if evt.GetId() in [wxID_CUT, wxID_COPY, self.ID_DELETE]: | |
1212 | enable = tree.GetSelection().IsOk() and \ | |
1213 | tree.GetSelection() != tree.GetRootItem() | |
1214 | evt.Enable(enable) | |
1215 | elif evt.GetId() == wxID_PASTE: | |
1216 | enable = self.clipboard != None | |
1217 | evt.Enable(enable) | |
1218 | ||
1219 | def OnIdle(self, evt): | |
1220 | if tree.needUpdate: | |
1221 | if conf.autoRefresh: | |
1222 | if testWin: | |
1223 | # (re)create | |
1224 | tree.CreateTestWin(testWin.item) | |
1225 | tree.needUpdate = false | |
1226 | elif tree.pendingHighLight: | |
1227 | tree.HighLight(tree.pendingHighLight) | |
1228 | evt.Skip() | |
1229 | ||
1230 | def OnCloseWindow(self, evt): | |
1231 | if not self.AskSave(): return | |
1232 | if testWin: testWin.Destroy() | |
1233 | conf.width, conf.height = self.GetSize() | |
1234 | evt.Skip() | |
1235 | ||
1236 | def Clear(self): | |
1237 | self.dataFile = '' | |
1238 | self.clipboard = None | |
1239 | self.modified = false | |
1240 | panel.SetModified(false) | |
1241 | panel.Clear() | |
1242 | tree.Clear() | |
1243 | global testWin | |
1244 | if testWin: | |
1245 | testWin.Destroy() | |
1246 | testWin = None | |
1247 | self.SetTitle(progname) | |
1248 | ||
1249 | def Open(self, path): | |
1250 | # Try to read the file | |
1251 | try: | |
1252 | open(path) | |
1253 | self.Clear() | |
1254 | # Build wx tree | |
1255 | dom = minidom.parse(path) | |
1256 | tree.SetData(dom) | |
1257 | self.dataFile = path | |
1258 | self.SetTitle(progname + ': ' + os.path.basename(path)) | |
1259 | except: | |
1260 | wxLogError('Error reading file: ' + path) | |
1261 | ||
1262 | def Save(self, path): | |
1263 | try: | |
1264 | memFile = MemoryFile(path) | |
1265 | tree.dom.writexml(memFile) | |
1266 | memFile.close() | |
1267 | self.modified = false | |
1268 | panel.SetModified(false) | |
1269 | except: | |
1270 | wxLogError('Error writing file: ' + path) | |
1271 | ||
1272 | def AskSave(self): | |
1273 | if not (self.modified or panel.IsModified()): return true | |
1274 | flags = wxICON_EXCLAMATION | wxYES_NO | wxCANCEL | wxCENTRE | |
1275 | dlg = wxMessageDialog( self, 'File is modified. Save before exit?', | |
1276 | 'Save before too late?', flags ) | |
1277 | say = dlg.ShowModal() | |
1278 | dlg.Destroy() | |
1279 | if say == wxID_YES: | |
1280 | self.OnSaveOrSaveAs(wxCommandEvent(wxID_SAVE)) | |
1281 | # If save was successful, modified flag is unset | |
1282 | if not self.modified: return true | |
1283 | elif say == wxID_NO: | |
1284 | self.modified = false | |
1285 | panel.SetModified(false) | |
1286 | return true | |
1287 | return false | |
1288 | ||
1289 | class App(wxApp): | |
1290 | def OnInit(self): | |
1291 | self.SetAppName("xrced") | |
1292 | # Settings | |
1293 | global conf | |
1294 | # !!! wxConfigBase_Get doesn't seem to work | |
1295 | conf = wxConfig(style=wxCONFIG_USE_LOCAL_FILE) | |
1296 | conf.autoRefresh = conf.ReadInt('autorefresh', true) | |
1297 | size = conf.ReadInt('width', 800), conf.ReadInt('height', 600) | |
1298 | # Add handlers | |
1299 | wxFileSystem_AddHandler(wxMemoryFSHandler()) | |
1300 | wxInitAllImageHandlers() | |
1301 | # Create main frame | |
1302 | global frame | |
1303 | frame = self.frame = Frame(size) | |
1304 | self.frame.Show(true) | |
1305 | # Load resources from XRC file (!!! should be transformed to .py later) | |
1306 | sys.modules['params'].frame = frame | |
1307 | frame.res = wxXmlResource('') | |
1308 | frame.res.Load(os.path.join(sys.path[0], 'xrced.xrc')) | |
1309 | return true | |
1310 | ||
1311 | def OnExit(self): | |
1312 | # Write config | |
1313 | wc = wxConfigBase_Get() | |
1314 | wc.WriteInt('autorefresh', conf.autoRefresh) | |
1315 | wc.WriteInt('width', conf.width) | |
1316 | wc.WriteInt('height', conf.height) | |
1317 | wc.Flush() | |
1318 | ||
1319 | def main(): | |
1320 | app = App() | |
1321 | app.MainLoop() | |
1322 | app.OnExit() | |
1323 | ||
1324 | if __name__ == '__main__': | |
1325 | main() |