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