]>
Commit | Line | Data |
---|---|---|
1 | #---------------------------------------------------------------------------- | |
2 | # Name: ExtensionService.py | |
3 | # Purpose: Extension Service for IDE | |
4 | # | |
5 | # Author: Peter Yared | |
6 | # | |
7 | # Created: 5/23/05 | |
8 | # CVS-ID: $ID:$ | |
9 | # Copyright: (c) 2005-2006 ActiveGrid, Inc. | |
10 | # License: wxWindows License | |
11 | #---------------------------------------------------------------------------- | |
12 | ||
13 | import wx | |
14 | import wx.lib.pydocview | |
15 | import MessageService | |
16 | import ProjectEditor | |
17 | import os | |
18 | import os.path | |
19 | import activegrid.util.xmlutils as xmlutils | |
20 | _ = wx.GetTranslation | |
21 | ||
22 | ||
23 | #---------------------------------------------------------------------------- | |
24 | # Constants | |
25 | #---------------------------------------------------------------------------- | |
26 | SPACE = 10 | |
27 | HALF_SPACE = 5 | |
28 | ||
29 | ||
30 | #---------------------------------------------------------------------------- | |
31 | # Classes | |
32 | #---------------------------------------------------------------------------- | |
33 | ||
34 | class Extension: | |
35 | ||
36 | ||
37 | def __init__(self, menuItemName=None): | |
38 | self.menuItemName = menuItemName | |
39 | self.id = 0 | |
40 | self.menuItemDesc = '' | |
41 | self.command = '' | |
42 | self.commandPreArgs = '' | |
43 | self.commandPostArgs = '' | |
44 | self.fileExt = None | |
45 | self.opOnSelectedFile = True | |
46 | ||
47 | ||
48 | class ExtensionService(wx.lib.pydocview.DocService): | |
49 | ||
50 | EXTENSIONS_KEY = "/AG_Extensions" | |
51 | ||
52 | def __init__(self): | |
53 | self.LoadExtensions() | |
54 | ||
55 | ||
56 | def __getExtensionKeyName(extensionName): | |
57 | return "%s/%s" % (ExtensionService.EXTENSIONS_KEY, extensionName) | |
58 | ||
59 | ||
60 | __getExtensionKeyName = staticmethod(__getExtensionKeyName) | |
61 | ||
62 | ||
63 | def LoadExtensions(self): | |
64 | self._extensions = [] | |
65 | ||
66 | extensionNames = [] | |
67 | config = wx.ConfigBase_Get() | |
68 | path = config.GetPath() | |
69 | try: | |
70 | config.SetPath(ExtensionService.EXTENSIONS_KEY) | |
71 | cont, value, index = config.GetFirstEntry() | |
72 | while cont: | |
73 | extensionNames.append(value) | |
74 | cont, value, index = config.GetNextEntry(index) | |
75 | finally: | |
76 | config.SetPath(path) | |
77 | ||
78 | for extensionName in extensionNames: | |
79 | extensionData = config.Read(self.__getExtensionKeyName(extensionName)) | |
80 | if extensionData: | |
81 | extension = xmlutils.unmarshal(extensionData.encode('utf-8')) | |
82 | self._extensions.append(extension) | |
83 | ||
84 | ||
85 | def SaveExtensions(self): | |
86 | config = wx.ConfigBase_Get() | |
87 | config.DeleteGroup(ExtensionService.EXTENSIONS_KEY) | |
88 | for extension in self._extensions: | |
89 | config.Write(self.__getExtensionKeyName(extension.menuItemName), xmlutils.marshal(extension)) | |
90 | ||
91 | ||
92 | def GetExtensions(self): | |
93 | return self._extensions | |
94 | ||
95 | ||
96 | def SetExtensions(self, extensions): | |
97 | self._extensions = extensions | |
98 | ||
99 | ||
100 | def CheckSumExtensions(self): | |
101 | return xmlutils.marshal(self._extensions) | |
102 | ||
103 | ||
104 | def InstallControls(self, frame, menuBar = None, toolBar = None, statusBar = None, document = None): | |
105 | toolsMenuIndex = menuBar.FindMenu(_("&Tools")) | |
106 | if toolsMenuIndex > -1: | |
107 | toolsMenu = menuBar.GetMenu(toolsMenuIndex) | |
108 | else: | |
109 | toolsMenu = wx.Menu() | |
110 | ||
111 | if self._extensions: | |
112 | if toolsMenu.GetMenuItems(): | |
113 | toolsMenu.AppendSeparator() | |
114 | for ext in self._extensions: | |
115 | # Append a tool menu item for each extension | |
116 | ext.id = wx.NewId() | |
117 | toolsMenu.Append(ext.id, ext.menuItemName) | |
118 | wx.EVT_MENU(frame, ext.id, frame.ProcessEvent) | |
119 | wx.EVT_UPDATE_UI(frame, ext.id, frame.ProcessUpdateUIEvent) | |
120 | ||
121 | if toolsMenuIndex == -1: | |
122 | index = menuBar.FindMenu(_("&Run")) | |
123 | if index == -1: | |
124 | index = menuBar.FindMenu(_("&Project")) | |
125 | if index == -1: | |
126 | index = menuBar.FindMenu(_("&Format")) | |
127 | if index == -1: | |
128 | index = menuBar.FindMenu(_("&View")) | |
129 | menuBar.Insert(index + 1, toolsMenu, _("&Tools")) | |
130 | ||
131 | ||
132 | def ProcessEvent(self, event): | |
133 | id = event.GetId() | |
134 | for extension in self._extensions: | |
135 | if id == extension.id: | |
136 | self.OnExecuteExtension(extension) | |
137 | return True | |
138 | return False | |
139 | ||
140 | ||
141 | def ProcessUpdateUIEvent(self, event): | |
142 | id = event.GetId() | |
143 | for extension in self._extensions: | |
144 | if id == extension.id: | |
145 | if extension.fileExt: | |
146 | doc = wx.GetApp().GetDocumentManager().GetCurrentDocument() | |
147 | if doc and '*' in extension.fileExt: | |
148 | event.Enable(True) | |
149 | return True | |
150 | if doc: | |
151 | for fileExt in extension.fileExt: | |
152 | if fileExt in doc.GetDocumentTemplate().GetFileFilter(): | |
153 | event.Enable(True) | |
154 | return True | |
155 | if extension.opOnSelectedFile and isinstance(doc, ProjectEditor.ProjectDocument): | |
156 | filename = doc.GetFirstView().GetSelectedFile() | |
157 | if filename: | |
158 | template = wx.GetApp().GetDocumentManager().FindTemplateForPath(filename) | |
159 | for fileExt in extension.fileExt: | |
160 | if fileExt in template.GetFileFilter(): | |
161 | event.Enable(True) | |
162 | return True | |
163 | event.Enable(False) | |
164 | return False | |
165 | return False | |
166 | ||
167 | ||
168 | def OnExecuteExtension(self, extension): | |
169 | if extension.fileExt: | |
170 | doc = wx.GetApp().GetDocumentManager().GetCurrentDocument() | |
171 | if not doc: | |
172 | return | |
173 | if extension.opOnSelectedFile and isinstance(doc, ProjectEditor.ProjectDocument): | |
174 | filename = doc.GetFirstView().GetSelectedFile() | |
175 | if not filename: | |
176 | filename = doc.GetFilename() | |
177 | else: | |
178 | filename = doc.GetFilename() | |
179 | ext = os.path.splitext(filename)[1] | |
180 | if not '*' in extension.fileExt: | |
181 | if not ext or ext[1:] not in extension.fileExt: | |
182 | return | |
183 | cmds = [extension.command] | |
184 | if extension.commandPreArgs: | |
185 | cmds.append(extension.commandPreArgs) | |
186 | cmds.append(filename) | |
187 | if extension.commandPostArgs: | |
188 | cmds.append(extension.commandPostArgs) | |
189 | os.spawnv(os.P_NOWAIT, extension.command, cmds) | |
190 | ||
191 | else: | |
192 | cmd = extension.command | |
193 | if extension.commandPreArgs: | |
194 | cmd = cmd + ' ' + extension.commandPreArgs | |
195 | if extension.commandPostArgs: | |
196 | cmd = cmd + ' ' + extension.commandPostArgs | |
197 | f = os.popen(cmd) | |
198 | messageService = wx.GetApp().GetService(MessageService.MessageService) | |
199 | messageService.ShowWindow() | |
200 | view = messageService.GetView() | |
201 | for line in f.readlines(): | |
202 | view.AddLines(line) | |
203 | view.GetControl().EnsureCaretVisible() | |
204 | f.close() | |
205 | ||
206 | ||
207 | class ExtensionOptionsPanel(wx.Panel): | |
208 | ||
209 | ||
210 | def __init__(self, parent, id): | |
211 | wx.Panel.__init__(self, parent, id) | |
212 | ||
213 | extOptionsPanelBorderSizer = wx.BoxSizer(wx.VERTICAL) | |
214 | ||
215 | extOptionsPanelSizer = wx.BoxSizer(wx.HORIZONTAL) | |
216 | ||
217 | extCtrlSizer = wx.BoxSizer(wx.VERTICAL) | |
218 | extCtrlSizer.Add(wx.StaticText(self, -1, _("External Tools:")), 0, wx.BOTTOM, HALF_SPACE) | |
219 | self._extListBox = wx.ListBox(self, -1, style=wx.LB_SINGLE) | |
220 | self.Bind(wx.EVT_LISTBOX, self.OnListBoxSelect, self._extListBox) | |
221 | extCtrlSizer.Add(self._extListBox, 1, wx.BOTTOM | wx.EXPAND, SPACE) | |
222 | buttonSizer = wx.GridSizer(cols=2, vgap=HALF_SPACE, hgap=HALF_SPACE) | |
223 | self._moveUpButton = wx.Button(self, -1, _("Move Up")) | |
224 | self.Bind(wx.EVT_BUTTON, self.OnMoveUp, self._moveUpButton) | |
225 | buttonSizer.Add(self._moveUpButton, 1, wx.EXPAND) | |
226 | self._moveDownButton = wx.Button(self, -1, _("Move Down")) | |
227 | self.Bind(wx.EVT_BUTTON, self.OnMoveDown, self._moveDownButton) | |
228 | buttonSizer.Add(self._moveDownButton, 1, wx.EXPAND) | |
229 | self._addButton = wx.Button(self, wx.ID_ADD) | |
230 | self.Bind(wx.EVT_BUTTON, self.OnAdd, self._addButton) | |
231 | buttonSizer.Add(self._addButton, 1, wx.EXPAND) | |
232 | self._deleteButton = wx.Button(self, wx.ID_DELETE, label=_("Delete")) # get rid of accelerator for letter d in "&Delete" | |
233 | self.Bind(wx.EVT_BUTTON, self.OnDelete, self._deleteButton) | |
234 | buttonSizer.Add(self._deleteButton, 1, wx.EXPAND) | |
235 | extCtrlSizer.Add(buttonSizer, 0, wx.ALIGN_CENTER) | |
236 | extOptionsPanelSizer.Add(extCtrlSizer, 0, wx.EXPAND) | |
237 | ||
238 | self._extDetailPanel = wx.Panel(self) | |
239 | staticBox = wx.StaticBox(self, label=_("Selected External Tool")) | |
240 | staticBoxSizer = wx.StaticBoxSizer(staticBox, wx.VERTICAL) | |
241 | ||
242 | extDetailSizer = wx.FlexGridSizer(cols=2, vgap=5, hgap=5) | |
243 | extDetailSizer.AddGrowableCol(1,1) | |
244 | ||
245 | extDetailSizer.Add(wx.StaticText(self._extDetailPanel, -1, _("Menu Item Name:")), flag=wx.ALIGN_CENTER_VERTICAL) | |
246 | self._menuItemNameTextCtrl = wx.TextCtrl(self._extDetailPanel, -1, size = (-1, -1)) | |
247 | extDetailSizer.Add(self._menuItemNameTextCtrl, 0, wx.EXPAND) | |
248 | self.Bind(wx.EVT_TEXT, self.SaveCurrentItem, self._menuItemNameTextCtrl) | |
249 | ||
250 | extDetailSizer.Add(wx.StaticText(self._extDetailPanel, -1, _("Menu Item Description:")), flag=wx.ALIGN_CENTER_VERTICAL) | |
251 | self._menuItemDescTextCtrl = wx.TextCtrl(self._extDetailPanel, -1, size = (-1, -1)) | |
252 | extDetailSizer.Add(self._menuItemDescTextCtrl, 0, wx.EXPAND) | |
253 | ||
254 | extDetailSizer.Add(wx.StaticText(self._extDetailPanel, -1, _("Command Path:")), flag=wx.ALIGN_CENTER_VERTICAL) | |
255 | self._commandTextCtrl = wx.TextCtrl(self._extDetailPanel, -1, size = (-1, -1)) | |
256 | findFileButton = wx.Button(self._extDetailPanel, -1, _("Browse...")) | |
257 | def OnBrowseButton(event): | |
258 | fileDlg = wx.FileDialog(self, _("Choose an Executable:"), style=wx.OPEN|wx.FILE_MUST_EXIST|wx.HIDE_READONLY|wx.CHANGE_DIR) | |
259 | path = self._commandTextCtrl.GetValue() | |
260 | if path: | |
261 | fileDlg.SetPath(path) | |
262 | # fileDlg.CenterOnParent() # wxBug: caused crash with wx.FileDialog | |
263 | if fileDlg.ShowModal() == wx.ID_OK: | |
264 | self._commandTextCtrl.SetValue(fileDlg.GetPath()) | |
265 | self._commandTextCtrl.SetInsertionPointEnd() | |
266 | self._commandTextCtrl.SetToolTipString(fileDlg.GetPath()) | |
267 | fileDlg.Destroy() | |
268 | wx.EVT_BUTTON(findFileButton, -1, OnBrowseButton) | |
269 | hsizer = wx.BoxSizer(wx.HORIZONTAL) | |
270 | hsizer.Add(self._commandTextCtrl, 1, wx.EXPAND) | |
271 | hsizer.Add(findFileButton, 0, wx.LEFT, HALF_SPACE) | |
272 | extDetailSizer.Add(hsizer, 0, wx.EXPAND) | |
273 | ||
274 | extDetailSizer.Add(wx.StaticText(self._extDetailPanel, -1, _("Command Pre Args:")), flag=wx.ALIGN_CENTER_VERTICAL) | |
275 | self._commandPreArgsTextCtrl = wx.TextCtrl(self._extDetailPanel, -1, size = (-1, -1)) | |
276 | extDetailSizer.Add(self._commandPreArgsTextCtrl, 0, wx.EXPAND) | |
277 | ||
278 | extDetailSizer.Add(wx.StaticText(self._extDetailPanel, -1, _("Command Post Args:")), flag=wx.ALIGN_CENTER_VERTICAL) | |
279 | self._commandPostArgsTextCtrl = wx.TextCtrl(self._extDetailPanel, -1, size = (-1, -1)) | |
280 | extDetailSizer.Add(self._commandPostArgsTextCtrl, 0, wx.EXPAND) | |
281 | ||
282 | extDetailSizer.Add(wx.StaticText(self._extDetailPanel, -1, _("File Extensions:")), flag=wx.ALIGN_CENTER_VERTICAL) | |
283 | self._fileExtTextCtrl = wx.TextCtrl(self._extDetailPanel, -1, size = (-1, -1)) | |
284 | self._fileExtTextCtrl.SetToolTipString(_("""For example: "txt, text" (comma separated) or "*" for all files""")) | |
285 | extDetailSizer.Add(self._fileExtTextCtrl, 0, wx.EXPAND) | |
286 | ||
287 | self._selFileCtrl = wx.CheckBox(self._extDetailPanel, -1, _("Operate on Selected File")) | |
288 | extDetailSizer.Add(self._selFileCtrl, 0, wx.ALIGN_CENTER_VERTICAL|wx.TOP, SPACE) | |
289 | self._selFileCtrl.SetToolTipString(_("If focus is in the project, instead of operating on the project file, operate on the selected file.")) | |
290 | ||
291 | self._extDetailPanel.SetSizer(extDetailSizer) | |
292 | staticBoxSizer.Add(self._extDetailPanel, 1, wx.ALL|wx.EXPAND, SPACE) | |
293 | ||
294 | extOptionsPanelSizer.Add(staticBoxSizer, 1, wx.LEFT|wx.EXPAND, SPACE) | |
295 | ||
296 | extOptionsPanelBorderSizer.Add(extOptionsPanelSizer, 1, wx.ALL|wx.EXPAND, SPACE) | |
297 | self.SetSizer(extOptionsPanelBorderSizer) | |
298 | ||
299 | if self.PopulateItems(): | |
300 | self._extListBox.SetSelection(0) | |
301 | self.OnListBoxSelect() | |
302 | ||
303 | self.Layout() | |
304 | ||
305 | parent.AddPage(self, _("External Tools")) | |
306 | ||
307 | ||
308 | def OnOK(self, optionsDialog): | |
309 | self.SaveCurrentItem() | |
310 | extensionsService = wx.GetApp().GetService(ExtensionService) | |
311 | extensionsService.SetExtensions(self._extensions) | |
312 | extensionsService.SaveExtensions() | |
313 | if extensionsService.CheckSumExtensions() != self._oldExtensions: # see PopulateItems() note about self._oldExtensions | |
314 | msgTitle = wx.GetApp().GetAppName() | |
315 | if not msgTitle: | |
316 | msgTitle = _("Document Options") | |
317 | wx.MessageBox(_("Extension changes will not appear until the application is restarted."), | |
318 | msgTitle, | |
319 | wx.OK | wx.ICON_INFORMATION, | |
320 | self.GetParent()) | |
321 | ||
322 | ||
323 | def PopulateItems(self): | |
324 | extensionsService = wx.GetApp().GetService(ExtensionService) | |
325 | import copy | |
326 | self._extensions = copy.deepcopy(extensionsService.GetExtensions()) | |
327 | self._oldExtensions = extensionsService.CheckSumExtensions() # wxBug: need to make a copy now since the deepcopy reorders fields, so we must compare the prestine copy with the modified copy | |
328 | for extension in self._extensions: | |
329 | self._extListBox.Append(extension.menuItemName, extension) | |
330 | self._currentItem = None | |
331 | self._currentItemIndex = -1 | |
332 | return len(self._extensions) | |
333 | ||
334 | ||
335 | def OnListBoxSelect(self, event=None): | |
336 | self.SaveCurrentItem() | |
337 | if self._extListBox.GetSelection() == wx.NOT_FOUND: | |
338 | self._currentItemIndex = -1 | |
339 | self._currentItem = None | |
340 | self._deleteButton.Enable(False) | |
341 | self._moveUpButton.Enable(False) | |
342 | self._moveDownButton.Enable(False) | |
343 | else: | |
344 | self._currentItemIndex = self._extListBox.GetSelection() | |
345 | self._currentItem = self._extListBox.GetClientData(self._currentItemIndex) | |
346 | self._deleteButton.Enable() | |
347 | self._moveUpButton.Enable(self._extListBox.GetCount() > 1 and self._currentItemIndex > 0) | |
348 | self._moveDownButton.Enable(self._extListBox.GetCount() > 1 and self._currentItemIndex < self._extListBox.GetCount() - 1) | |
349 | self.LoadItem(self._currentItem) | |
350 | ||
351 | ||
352 | def SaveCurrentItem(self, event=None): | |
353 | extension = self._currentItem | |
354 | if extension: | |
355 | if extension.menuItemName != self._menuItemNameTextCtrl.GetValue(): | |
356 | extension.menuItemName = self._menuItemNameTextCtrl.GetValue() | |
357 | self._extListBox.SetString(self._currentItemIndex, extension.menuItemName) | |
358 | extension.menuItemDesc = self._menuItemDescTextCtrl.GetValue() | |
359 | extension.command = self._commandTextCtrl.GetValue() | |
360 | extension.commandPreArgs = self._commandPreArgsTextCtrl.GetValue() | |
361 | extension.commandPostArgs = self._commandPostArgsTextCtrl.GetValue() | |
362 | fileExt = self._fileExtTextCtrl.GetValue().replace(' ','') | |
363 | if not fileExt: | |
364 | extension.fileExt = None | |
365 | else: | |
366 | extension.fileExt = fileExt.split(',') | |
367 | extension.opOnSelectedFile = self._selFileCtrl.GetValue() | |
368 | ||
369 | ||
370 | def LoadItem(self, extension): | |
371 | if extension: | |
372 | self._menuItemDescTextCtrl.SetValue(extension.menuItemDesc or '') | |
373 | self._commandTextCtrl.SetValue(extension.command or '') | |
374 | self._commandTextCtrl.SetToolTipString(extension.command or '') | |
375 | self._commandPreArgsTextCtrl.SetValue(extension.commandPreArgs or '') | |
376 | self._commandPostArgsTextCtrl.SetValue(extension.commandPostArgs or '') | |
377 | if extension.fileExt: | |
378 | list = "" | |
379 | for ext in extension.fileExt: | |
380 | if list: | |
381 | list = list + ", " | |
382 | list = list + ext | |
383 | self._fileExtTextCtrl.SetValue(list) | |
384 | else: | |
385 | self._fileExtTextCtrl.SetValue('') | |
386 | self._selFileCtrl.SetValue(extension.opOnSelectedFile) | |
387 | self._menuItemNameTextCtrl.SetValue(extension.menuItemName or '') # Do the name last since it triggers the write event that updates the entire item | |
388 | self._extDetailPanel.Enable() | |
389 | else: | |
390 | self._menuItemNameTextCtrl.SetValue('') | |
391 | self._menuItemDescTextCtrl.SetValue('') | |
392 | self._commandTextCtrl.SetValue('') | |
393 | self._commandTextCtrl.SetToolTipString(_("Path to executable")) | |
394 | self._commandPreArgsTextCtrl.SetValue('') | |
395 | self._commandPostArgsTextCtrl.SetValue('') | |
396 | self._fileExtTextCtrl.SetValue('') | |
397 | self._selFileCtrl.SetValue(True) | |
398 | self._extDetailPanel.Enable(False) | |
399 | ||
400 | ||
401 | def OnAdd(self, event): | |
402 | self.SaveCurrentItem() | |
403 | name = _("Untitled") | |
404 | count = 1 | |
405 | while self._extListBox.FindString(name) != wx.NOT_FOUND: | |
406 | count = count + 1 | |
407 | name = _("Untitled%s") % count | |
408 | extension = Extension(name) | |
409 | self._extensions.append(extension) | |
410 | self._extListBox.Append(extension.menuItemName, extension) | |
411 | self._extListBox.SetStringSelection(extension.menuItemName) | |
412 | self.OnListBoxSelect() | |
413 | self._menuItemNameTextCtrl.SetFocus() | |
414 | self._menuItemNameTextCtrl.SetSelection(-1, -1) | |
415 | ||
416 | ||
417 | def OnDelete(self, event): | |
418 | self._extListBox.Delete(self._currentItemIndex) | |
419 | self._extensions.remove(self._currentItem) | |
420 | self._currentItemIndex = min(self._currentItemIndex, self._extListBox.GetCount() - 1) | |
421 | if self._currentItemIndex > -1: | |
422 | self._extListBox.SetSelection(self._currentItemIndex) | |
423 | self._currentItem = None # Don't update it since it no longer exists | |
424 | self.OnListBoxSelect() | |
425 | ||
426 | ||
427 | def OnMoveUp(self, event): | |
428 | itemAboveString = self._extListBox.GetString(self._currentItemIndex - 1) | |
429 | itemAboveData = self._extListBox.GetClientData(self._currentItemIndex - 1) | |
430 | self._extListBox.Delete(self._currentItemIndex - 1) | |
431 | self._extListBox.Insert(itemAboveString, self._currentItemIndex) | |
432 | self._extListBox.SetClientData(self._currentItemIndex, itemAboveData) | |
433 | self._currentItemIndex = self._currentItemIndex - 1 | |
434 | self.OnListBoxSelect() # Reset buttons | |
435 | ||
436 | ||
437 | def OnMoveDown(self, event): | |
438 | itemBelowString = self._extListBox.GetString(self._currentItemIndex + 1) | |
439 | itemBelowData = self._extListBox.GetClientData(self._currentItemIndex + 1) | |
440 | self._extListBox.Delete(self._currentItemIndex + 1) | |
441 | self._extListBox.Insert(itemBelowString, self._currentItemIndex) | |
442 | self._extListBox.SetClientData(self._currentItemIndex, itemBelowData) | |
443 | self._currentItemIndex = self._currentItemIndex + 1 | |
444 | self.OnListBoxSelect() # Reset buttons |