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