1 #----------------------------------------------------------------------------
2 # Name: ExtensionService.py
3 # Purpose: Extension Service for IDE
9 # Copyright: (c) 2005-2006 ActiveGrid, Inc.
10 # License: wxWindows License
11 #----------------------------------------------------------------------------
14 import wx
.lib
.pydocview
19 import activegrid
.util
.xmlutils
as xmlutils
23 #----------------------------------------------------------------------------
25 #----------------------------------------------------------------------------
30 #----------------------------------------------------------------------------
32 #----------------------------------------------------------------------------
37 def __init__(self
, menuItemName
=None):
38 self
.menuItemName
= menuItemName
40 self
.menuItemDesc
= ''
42 self
.commandPreArgs
= ''
43 self
.commandPostArgs
= ''
45 self
.opOnSelectedFile
= True
48 class ExtensionService(wx
.lib
.pydocview
.DocService
):
50 EXTENSIONS_KEY
= "/AG_Extensions"
56 def __getExtensionKeyName(extensionName
):
57 return "%s/%s" % (ExtensionService
.EXTENSIONS_KEY
, extensionName
)
60 __getExtensionKeyName
= staticmethod(__getExtensionKeyName
)
63 def LoadExtensions(self
):
67 config
= wx
.ConfigBase_Get()
68 path
= config
.GetPath()
70 config
.SetPath(ExtensionService
.EXTENSIONS_KEY
)
71 cont
, value
, index
= config
.GetFirstEntry()
73 extensionNames
.append(value
)
74 cont
, value
, index
= config
.GetNextEntry(index
)
78 for extensionName
in extensionNames
:
79 extensionData
= config
.Read(self
.__getExtensionKeyName
(extensionName
))
81 extension
= xmlutils
.unmarshal(extensionData
.encode('utf-8'))
82 self
._extensions
.append(extension
)
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
))
92 def GetExtensions(self
):
93 return self
._extensions
96 def SetExtensions(self
, extensions
):
97 self
._extensions
= extensions
100 def CheckSumExtensions(self
):
101 return xmlutils
.marshal(self
._extensions
)
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
)
109 toolsMenu
= wx
.Menu()
112 if toolsMenu
.GetMenuItems():
113 toolsMenu
.AppendSeparator()
114 for ext
in self
._extensions
:
115 # Append a tool menu item for each extension
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
)
121 if toolsMenuIndex
== -1:
122 index
= menuBar
.FindMenu(_("&Run"))
124 index
= menuBar
.FindMenu(_("&Project"))
126 index
= menuBar
.FindMenu(_("&Format"))
128 index
= menuBar
.FindMenu(_("&View"))
129 menuBar
.Insert(index
+ 1, toolsMenu
, _("&Tools"))
132 def ProcessEvent(self
, event
):
134 for extension
in self
._extensions
:
135 if id == extension
.id:
136 self
.OnExecuteExtension(extension
)
141 def ProcessUpdateUIEvent(self
, event
):
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
:
151 for fileExt
in extension
.fileExt
:
152 if fileExt
in doc
.GetDocumentTemplate().GetFileFilter():
155 if extension
.opOnSelectedFile
and isinstance(doc
, ProjectEditor
.ProjectDocument
):
156 filename
= doc
.GetFirstView().GetSelectedFile()
158 template
= wx
.GetApp().GetDocumentManager().FindTemplateForPath(filename
)
159 for fileExt
in extension
.fileExt
:
160 if fileExt
in template
.GetFileFilter():
168 def OnExecuteExtension(self
, extension
):
169 if extension
.fileExt
:
170 doc
= wx
.GetApp().GetDocumentManager().GetCurrentDocument()
173 if extension
.opOnSelectedFile
and isinstance(doc
, ProjectEditor
.ProjectDocument
):
174 filename
= doc
.GetFirstView().GetSelectedFile()
176 filename
= doc
.GetFilename()
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
:
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
)
192 cmd
= extension
.command
193 if extension
.commandPreArgs
:
194 cmd
= cmd
+ ' ' + extension
.commandPreArgs
195 if extension
.commandPostArgs
:
196 cmd
= cmd
+ ' ' + extension
.commandPostArgs
198 messageService
= wx
.GetApp().GetService(MessageService
.MessageService
)
199 messageService
.ShowWindow()
200 view
= messageService
.GetView()
201 for line
in f
.readlines():
203 view
.GetControl().EnsureCaretVisible()
207 class ExtensionOptionsPanel(wx
.Panel
):
210 def __init__(self
, parent
, id):
211 wx
.Panel
.__init
__(self
, parent
, id)
213 extOptionsPanelBorderSizer
= wx
.BoxSizer(wx
.VERTICAL
)
215 extOptionsPanelSizer
= wx
.BoxSizer(wx
.HORIZONTAL
)
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
)
238 self
._extDetailPanel
= wx
.Panel(self
)
239 staticBox
= wx
.StaticBox(self
, label
=_("Selected External Tool"))
240 staticBoxSizer
= wx
.StaticBoxSizer(staticBox
, wx
.VERTICAL
)
242 extDetailSizer
= wx
.FlexGridSizer(cols
=2, vgap
=5, hgap
=5)
243 extDetailSizer
.AddGrowableCol(1,1)
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
)
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
)
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()
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())
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
)
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
)
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
)
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
)
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."))
291 self
._extDetailPanel
.SetSizer(extDetailSizer
)
292 staticBoxSizer
.Add(self
._extDetailPanel
, 1, wx
.ALL|wx
.EXPAND
, SPACE
)
294 extOptionsPanelSizer
.Add(staticBoxSizer
, 1, wx
.LEFT|wx
.EXPAND
, SPACE
)
296 extOptionsPanelBorderSizer
.Add(extOptionsPanelSizer
, 1, wx
.ALL|wx
.EXPAND
, SPACE
)
297 self
.SetSizer(extOptionsPanelBorderSizer
)
299 if self
.PopulateItems():
300 self
._extListBox
.SetSelection(0)
301 self
.OnListBoxSelect()
305 parent
.AddPage(self
, _("External Tools"))
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()
316 msgTitle
= _("Document Options")
317 wx
.MessageBox(_("Extension changes will not appear until the application is restarted."),
319 wx
.OK | wx
.ICON_INFORMATION
,
323 def PopulateItems(self
):
324 extensionsService
= wx
.GetApp().GetService(ExtensionService
)
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
)
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)
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
)
352 def SaveCurrentItem(self
, event
=None):
353 extension
= self
._currentItem
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(' ','')
364 extension
.fileExt
= None
366 extension
.fileExt
= fileExt
.split(',')
367 extension
.opOnSelectedFile
= self
._selFileCtrl
.GetValue()
370 def LoadItem(self
, 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
:
379 for ext
in extension
.fileExt
:
383 self
._fileExtTextCtrl
.SetValue(list)
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()
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)
401 def OnAdd(self
, event
):
402 self
.SaveCurrentItem()
405 while self
._extListBox
.FindString(name
) != wx
.NOT_FOUND
:
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)
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()
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
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