############################################################################### # Name: ed_cmdbar.py # # Purpose: Creates a small slit panel that holds small controls for searching # # and other actions. # # Author: Cody Precord # # Copyright: (c) 2008 Cody Precord # # License: wxWindows License # ############################################################################### """ This class creates a custom panel that can hide and show different controls based an id value. The panel is generally between 24-32 pixels in height but can grow to fit the controls inserted in it. The the background is painted with a gradient using system defined colors. @summary: The buffers CommandBar control with search/goto line/command entry """ __author__ = "Cody Precord " __svnid__ = "$Id: ed_cmdbar.py 70229 2012-01-01 01:27:10Z CJP $" __revision__ = "$Revision: 70229 $" #--------------------------------------------------------------------------# # Imports import os import sys import re import wx # Local Imports import util import ed_glob import ed_search import ed_event import ed_msg import ebmlib import eclib import ed_basewin from profiler import Profile_Get _ = wx.GetTranslation #--------------------------------------------------------------------------# # Close Button Bitmap from extern.embeddedimage import PyEmbeddedImage XButton = PyEmbeddedImage( "iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAIAAACQKrqGAAAAA3NCSVQICAjb4U/gAAAB6UlE" "QVQokW2SvWsTYRjAn7tcctdLc7kmxtJqj8ZECIqi7eJHhywVxLWLSBctXQKFOhSE0IJKB0EH" "wf/BwUkEBxEFFR0cSoei1ZaSJjQ56Zn0vDd3977v8zrcUTr0mZ6PH8+39Onxw97Xz0FnL2w1" "4DhJnbLU4ZHs1Snpza0bk1cum0MFLvwoJmg/pmjs6bW7a5u7StDZM8Zu0v0W3W/HAGMAgJxF" "pmYaxumc+nNLDlsNUBKceNJAKj27KI2OIWfImWKVcnMPuKr53QOmJsVfWz7sSZ+pqZWJ7L26" "YpUUq5SfX1YrE4XZRR+pwikAKAAgBBMQkPevkuMVWdPz88sAIGs6+sR5+/IwlwIA0CXBrsM2" "toPm/ZMrz2RNBwD0SaO+4G/9AACeG41R9o8wL6CuLwXs6Jow5Mz1OSJ3XMG4DAAiZIgidfb8" "yOrzqC76RNb08Scv9HMXAAAoFyGNx0Kk2dt3I25nqbazVIvo/J05ABAsAMTETEYrX7pIbNv7" "8iFZLLeePiKbG7TT9ta+y7lCY7UuM6oNGZ1mW3p9fXKqeq3/xz6wm8yNz8MRIyWRSg6amfTg" "wHqzp+hW0XUcI3NCy6QBQGAYNRfVZYgJztxeH3LDilmd/vXxHVn/5m3/PvZd0mfKulU0q9P/" "AeP28JG84F5KAAAAAElFTkSuQmCC") #-----------------------------------------------------------------------------# # Globals ID_CLOSE_BUTTON = wx.NewId() ID_SEARCH_NEXT = wx.NewId() ID_SEARCH_PRE = wx.NewId() ID_FIND_ALL = wx.NewId() ID_MATCH_CASE = wx.NewId() ID_WHOLE_WORD = wx.NewId() ID_REGEX = wx.NewId() #-----------------------------------------------------------------------------# class CommandBarBase(ed_basewin.EdBaseCtrlBar, ebmlib.FactoryMixin): """Base class for control bars""" def __init__(self, parent): super(CommandBarBase, self).__init__(parent, style=eclib.CTRLBAR_STYLE_GRADIENT) if wx.Platform == '__WXGTK__': self.SetWindowStyle(eclib.CTRLBAR_STYLE_DEFAULT) self.SetVMargin(2, 2) # Attributes self._parent = parent self._menu = None self._menu_enabled = True self.ctrl = None self.close_b = eclib.PlateButton(self, ID_CLOSE_BUTTON, bmp=XButton.GetBitmap(), style=eclib.PB_STYLE_NOBG) # Setup self.AddControl(self.close_b, wx.ALIGN_LEFT) # Event Handlers self.Bind(wx.EVT_BUTTON, self.OnClose, self.close_b) self.Bind(wx.EVT_CONTEXT_MENU, self.OnContext) self.Bind(wx.EVT_MENU, self.OnContextMenu) self.Bind(wx.EVT_SHOW, self.OnShowBar) @classmethod def GetMetaDefaults(cls): return dict(id=-1, config_key=None) #---- Properties ----# MainControl = property(lambda self: self.ctrl, lambda self, ctrl: self.SetControl(ctrl)) #---- Implementation ----# def OnClose(self, evt): """Handles events from the buttons on the bar @param evt: Event that called this handler """ if evt.Id == ID_CLOSE_BUTTON: self.Hide() else: evt.Skip() def OnContext(self, evt): """Show the custom menu""" if self._menu_enabled: if self._menu is None: # Lazy init the menu self._menu = wx.Menu(_("Customize")) # Ensure the label is disabled (wxMSW Bug) if len(self._menu.MenuItems): item = self._menu.MenuItems[0] self._menu.Enable(item.GetId(), False) to_menu = list() for child in self.GetChildren(): if self.IsCustomizable(child): to_menu.append(child) if len(to_menu): to_menu.sort(key=wx.Window.GetLabel) for item in to_menu: if not item.GetLabel(): continue self._menu.Append(item.Id, item.GetLabel(), kind=wx.ITEM_CHECK) self._menu.Check(item.Id, item.IsShown()) self.PopupMenu(self._menu) else: evt.Skip() def OnContextMenu(self, evt): """Hide and Show controls""" ctrl = self.FindWindowById(evt.Id) if ctrl is not None: self.ShowControl(ctrl.GetName(), not ctrl.IsShown()) self.Layout() # Update the persistent configuration key = self.GetConfigKey() if key is not None: cfg = Profile_Get('CTRLBAR', default=dict()) state = self.GetControlStates() cfg[key] = state def OnShowBar(self, evt): """Update the session list""" if evt.IsShown(): if self and evt.EventObject is self: self.OnBarShown() evt.Skip() def OnBarShown(self): """virtual override for subclasses that wish to receive window show event callbacks. """ pass def EnableMenu(self, enable=True): """Enable the popup customization menu @keyword enable: bool """ self._menu_enabled = enable if not enable and self._menu is not None: self._menu.Destroy() self._menu = None def GetConfigKey(self): """Get the key to use for the layout config persistence. This value is set in the class definitions meta class. @return: string """ return self.meta.config_key def GetControlStates(self): """Get the map of control name id's to their shown state True/False @return: dict() """ state = dict() for child in self.GetChildren(): if self.IsCustomizable(child): state[child.GetName()] = child.IsShown() return state def SetControlStates(self, state): """Set visibility state of the customizable controls @param state: dict(ctrl_name=bool) """ for name, show in state.iteritems(): self.ShowControl(name, show) self.Layout() def Hide(self): """Hides the control and notifies the parent @postcondition: CommandBar is hidden @todo: don't reference nb directly here """ super(CommandBarBase, self).Hide() self._parent.SendSizeEvent() nb = self._parent.GetNotebook() ctrl = nb.GetCurrentCtrl() if ctrl: ctrl.SetFocus() return True def ShowControl(self, ctrl_name, show=True): """Show/Hide a control @param ctrl_name: string @note: assumes all left aligned controls """ sizer = self.GetControlSizer() gonext = False for item in sizer.GetChildren(): if gonext: if item.IsSpacer(): item.Show(show) break if item.Window and item.Window.GetName() == ctrl_name: item.Show(show) gonext = True def IsCustomizable(self, ctrl): """Is the control of a type that can be customized @param ctrl: wx.Window @return: bool """ ok = (ctrl is not self.close_b) ok = ok and (isinstance(ctrl, wx.CheckBox) or \ isinstance(ctrl, eclib.PlateButton)) return ok def SetControl(self, ctrl): """Set the main control of this command bar @param ctrl: window """ self.ctrl = ctrl def SetFocus(self): """Set the focus to the bar and its main control""" super(CommandBarBase, self).SetFocus() if self.MainControl: self.MainControl.SetFocus() #-----------------------------------------------------------------------------# class SearchBar(CommandBarBase): """Commandbar for searching text in the current buffer.""" class meta: id = ed_glob.ID_QUICK_FIND config_key = 'SearchBar' def __init__(self, parent): super(SearchBar, self).__init__(parent) # Attributes self.SetControl(ed_search.EdSearchCtrl(self, wx.ID_ANY, menulen=5, size=(180, -1))) self._sctrl = self.MainControl.GetSearchController() # Setup f_lbl = wx.StaticText(self, label=_("Find") + u": ") t_bmp = wx.ArtProvider.GetBitmap(str(ed_glob.ID_DOWN), wx.ART_MENU) next_btn = eclib.PlateButton(self, ID_SEARCH_NEXT, _("Next"), t_bmp, style=eclib.PB_STYLE_NOBG, name="NextBtn") self.AddControl(f_lbl, wx.ALIGN_LEFT) self.AddControl(self.ctrl, wx.ALIGN_LEFT) self.AddControl(next_btn, wx.ALIGN_LEFT) t_bmp = wx.ArtProvider.GetBitmap(str(ed_glob.ID_UP), wx.ART_MENU) pre_btn = eclib.PlateButton(self, ID_SEARCH_PRE, _("Previous"), t_bmp, style=eclib.PB_STYLE_NOBG, name="PreBtn") self.AddControl(pre_btn, wx.ALIGN_LEFT) t_bmp = wx.ArtProvider.GetBitmap(str(ed_glob.ID_FIND), wx.ART_MENU) fa_btn = eclib.PlateButton(self, ID_FIND_ALL, _("Find All"), t_bmp, style=eclib.PB_STYLE_NOBG, name="FindAllBtn") self.AddControl(fa_btn) fa_btn.Show(False) # Hide this button by default match_case = wx.CheckBox(self, ID_MATCH_CASE, _("Match Case"), name="MatchCase") match_case.SetValue(self.ctrl.IsMatchCase()) self.AddControl(match_case, wx.ALIGN_LEFT) match_case.Show(False) # Hide by default ww_cb = wx.CheckBox(self, ID_WHOLE_WORD, _("Whole Word"), name="WholeWord") ww_cb.SetValue(self.ctrl.IsWholeWord()) self.AddControl(ww_cb, wx.ALIGN_LEFT) regex_cb = wx.CheckBox(self, ID_REGEX, _("Regular Expression"), name="RegEx") regex_cb.SetValue(self.ctrl.IsRegEx()) self.AddControl(regex_cb, wx.ALIGN_LEFT) # HACK: workaround bug in mac control that resets size to # that of the default variant after any text has been # typed in it. Note it reports the best size as the default # variant and causes layout issues. wxBUG if wx.Platform == '__WXMAC__': self.ctrl.SetSizeHints(180, 16, 180, 16) # Event Handlers self.Bind(wx.EVT_WINDOW_DESTROY, self.OnDestroy) self.Bind(wx.EVT_BUTTON, self.OnButton) self.Bind(wx.EVT_CHECKBOX, self.OnCheck) ed_msg.Subscribe(self.OnThemeChange, ed_msg.EDMSG_THEME_CHANGED) self._sctrl.RegisterClient(self) # Set user customizable layout state = Profile_Get('CTRLBAR', default=dict()) cfg = state.get(self.GetConfigKey(), dict()) self.SetControlStates(cfg) def OnDestroy(self, evt): """Cleanup message handlers on destroy""" if evt.GetId() == self.GetId(): ed_msg.Unsubscribe(self.OnThemeChange) self._sctrl.RemoveClient(self) def OnButton(self, evt): """Handle button clicks for the next/previous buttons @param evt: wx.CommandEvent """ e_id = evt.GetId() if e_id in [ID_SEARCH_NEXT, ID_SEARCH_PRE]: self.ctrl.DoSearch(e_id == ID_SEARCH_NEXT) elif e_id == ID_FIND_ALL: self.ctrl.FindAll() else: evt.Skip() def OnCheck(self, evt): """Set search options for match case, regex, ect... @param evt: wx.CommandEvent """ e_id = evt.GetId() if e_id in (ID_MATCH_CASE, ID_REGEX, ID_WHOLE_WORD): ctrl = self.FindWindowById(e_id) if ctrl != None: if e_id == ID_MATCH_CASE: flag = eclib.AFR_MATCHCASE elif e_id == ID_WHOLE_WORD: flag = eclib.AFR_WHOLEWORD else: flag = eclib.AFR_REGEX if self.ctrl != None: if ctrl.GetValue(): self.ctrl.SetSearchFlag(flag) else: self.ctrl.ClearSearchFlag(flag) else: evt.Skip() def NotifyOptionChanged(self, evt): """Callback for L{ed_search.SearchController} to notify of update to the find options. @param evt: eclib.finddlg.FindEvent """ self.FindWindowById(ID_MATCH_CASE).SetValue(evt.IsMatchCase()) self.FindWindowById(ID_REGEX).SetValue(evt.IsRegEx()) self.FindWindowById(ID_WHOLE_WORD).SetValue(evt.IsWholeWord()) def OnThemeChange(self, msg): """Update icons when the theme has changed @param msg: Message Object """ next_btn = self.FindWindowById(ID_SEARCH_NEXT) if next_btn: t_bmp = wx.ArtProvider.GetBitmap(str(ed_glob.ID_DOWN), wx.ART_MENU) next_btn.SetBitmapLabel(t_bmp) next_btn.SetBitmapHover(t_bmp) next_btn.Update() next_btn.Refresh() pre_btn = self.FindWindowById(ID_SEARCH_PRE) if pre_btn: t_bmp = wx.ArtProvider.GetBitmap(str(ed_glob.ID_UP), wx.ART_MENU) pre_btn.SetBitmapLabel(t_bmp) pre_btn.SetBitmapHover(t_bmp) pre_btn.Update() pre_btn.Refresh() #-----------------------------------------------------------------------------# class CommandEntryBar(CommandBarBase): """Commandbar for editor command entry and execution.""" class meta: id = ed_glob.ID_COMMAND def __init__(self, parent): super(CommandEntryBar, self).__init__(parent) # Attributes self.SetControl(CommandExecuter(self, wx.ID_ANY, size=(150, -1))) # Setup cmd_lbl = wx.StaticText(self, label=_("Command") + ": ") self.info_lbl = wx.StaticText(self, label="") self.AddControl(cmd_lbl, wx.ALIGN_LEFT) self.AddControl(self.ctrl, 1, wx.ALIGN_LEFT) self.AddControl(self.info_lbl, wx.ALIGN_RIGHT) # HACK: workaround bug in mac control that resets size to # that of the default variant after any text has been # typed in it. Note it reports the best size as the default # variant and causes layout issues. wxBUG if wx.Platform == '__WXMAC__': self.ctrl.SetSizeHints(200, 16, -1, 16) # Setup self.EnableMenu(False) #-----------------------------------------------------------------------------# class GotoLineBar(CommandBarBase): """Commandbar for Goto Line function""" class meta: id = ed_glob.ID_GOTO_LINE def __init__(self, parent): super(GotoLineBar, self).__init__(parent) # Attributes self.SetControl(LineCtrl(self, wx.ID_ANY, self._parent.nb.GetCurrentCtrl, size=(100, -1))) # Setup self.EnableMenu(False) go_lbl = wx.StaticText(self, label=_("Goto Line") + ": ") self.AddControl(go_lbl, wx.ALIGN_LEFT) self.AddControl(self.ctrl, wx.ALIGN_LEFT) # HACK: workaround bug in mac control that resets size to # that of the default variant after any text has been # typed in it. Note it reports the best size as the default # variant and causes layout issues. wxBUG if wx.Platform == '__WXMAC__': self.ctrl.SetSizeHints(100, 16, 100, 16) #-----------------------------------------------------------------------------# class CommandExecuter(eclib.CommandEntryBase): """Part of the Vi emulation, opens a minibuffer to execute EX commands. @note: based on search ctrl so we get the nice rounded edges on wxmac. """ RE_GO_BUFFER = re.compile('[0-9]*[nN]{1,1}') RE_GO_WIN = re.compile('[0-9]*n[wW]{1,1}') RE_WGO_BUFFER = re.compile('w[0-9]*[nN]') RE_NGO_LINE = re.compile('[+-][0-9]+') def __init__(self, parent, id_, size=wx.DefaultSize): """Initializes the CommandExecuter""" super(CommandExecuter, self).__init__(parent, id_, size=size, style=wx.TE_PROCESS_ENTER|wx.WANTS_CHARS) # Attributes self._history = dict(cmds=[''], index=-1, lastval='') if not hasattr(sys, 'frozen'): self._curdir = os.path.abspath(os.curdir) + os.sep else: self._curdir = wx.GetHomeDir() + os.sep if wx.Platform == '__WXMAC__': self._popup = PopupList(self) self.Bind(wx.EVT_SET_FOCUS, self.OnSetFocus) self.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus) else: self._popup = PopupWinList(self) self.Bind(wx.EVT_WINDOW_DESTROY, self.OnDestroy, self) self.Bind(ed_event.EVT_NOTIFY, self.OnPopupNotify) # Message handlers ed_msg.Subscribe(self._UpdateCwd, ed_msg.EDMSG_UI_NB_CHANGED) ed_msg.Subscribe(self._UpdateCwd, ed_msg.EDMSG_FILE_SAVED) def OnDestroy(self, evt): """Clean up message handlers on destroy""" if evt.GetId() == self.GetId(): ed_msg.Unsubscribe(self._UpdateCwd) evt.Skip() def _AdjustSize(self): """Checks width of text as its added and dynamically resizes the control as needed. @todo: re-enable after resizing issue can be resolved """ pass # ext = self.GetTextExtent(self.GetValue())[0] # curr_w, curr_h = self.GetClientSizeTuple() # if ext > curr_w * .5: # max_w = self.GetParent().GetClientSize().GetWidth() * .8 # nwidth = min(ext * 1.3, max_w) # pwidth = self._popup.GetBestSize()[0] # if pwidth > nwidth: # nwidth = pwidth # self.SetClientSize((nwidth, curr_h)) # self._popup.SetSize((nwidth, -1)) # self.GetParent().Layout() # elif ((curr_w > ext * 1.18) and curr_w > 150): # nwidth = max(ext * 1.18, 150) # self.SetClientSize((nwidth, curr_h)) # self.GetParent().Layout() # else: # pass def _AdjustValue(self, val): """Adjust value of input string as autocomp provides new values @param val: val to use as base for adjustment """ cval = self.GetValue().split(' ', 1) if val.startswith(cval[-1]) or val.startswith('~'): self.AppendText(val.replace(cval[-1], '', 1)) else: self.SetValue(" ".join([cval[0], val])) self.SetInsertionPoint(self.GetLastPosition()) def _UpdateCwd(self, msg): """Update the current working directory to that of the current buffer. @param msg: Message Object """ # Only Update if we are the currently active window tlp = self.GetTopLevelParent() if tlp.IsActive(): ctrl = tlp.GetNotebook().GetCurrentCtrl() fname = ctrl.GetFileName() if len(fname): self._curdir = os.path.dirname(fname) def ChangeDir(self, cmd): """Change to a directory based on cd command @param cmd: cd path """ path = cmd.replace('cd', '', 1).strip() if not os.path.isabs(path): if path.startswith('..'): path = os.path.abspath(path) elif path.startswith('~'): path = path.replace('~', wx.GetHomeDir(), 1) else: path = os.path.join(self._curdir, path) if os.path.exists(path) and os.path.isdir(path): if os.access(path, os.R_OK): os.chdir(path) self._curdir = os.path.abspath(os.path.curdir) + os.sep else: # Doesn't have permissions ed_msg.PostMessage(ed_msg.EDMSG_UI_SB_TXT, (ed_glob.SB_INFO, _("Can't change directory to: %s") % path)) wx.Bell() self.Clear() else: # Invalid path self.Clear() wx.Bell() def CommandPush(self, cmd): """Push a command to the stack popping as necessary to keep stack size less than MAX (currently 25 commands). @param cmd: command string to push @todo: redo this to be more like the code in my terminal project """ cmd = cmd.strip() if not len(cmd): return if len(self._history['cmds']) > 25: self._history['cmds'].pop() if cmd != self._history['cmds'][0]: self._history['cmds'].insert(0, cmd) self._history['index'] = -1 def EditCommand(self, cmd): """Perform an edit related command @param cmd: command string to execute """ # e fname: edit file cmd = cmd[1:].strip() frame = self.GetTopLevelParent() cmd = ebmlib.GetPathFromURI(cmd) if not os.path.isabs(cmd): cmd = os.path.join(self._curdir, cmd) if ebmlib.PathExists(cmd): frame.DoOpen(ed_glob.ID_COMMAND_LINE_OPEN, cmd) else: frame.nb.OpenPage(ebmlib.GetPathName(cmd), ebmlib.GetFileName(cmd)) def ExecuteCommand(self, cmd_str): """Interprets and executes a command then hides the control @param cmd_str: Command string to execute """ frame = self.GetTopLevelParent() cmd = cmd_str.strip().lstrip(':') if cmd in ['x', 'ZZ']: cmd = 'wq' if cmd.startswith(u'w'): frame.OnSave(wx.MenuEvent(wx.wxEVT_COMMAND_MENU_SELECTED, ed_glob.ID_SAVE)) if self.RE_WGO_BUFFER.match(cmd): self.GoBuffer(cmd[1:]) elif cmd == 'wq': self.Quit() elif cmd.startswith(u'e '): self.EditCommand(cmd) elif cmd.rstrip() == u'e!': ctrl = frame.nb.GetCurrentCtrl() ctrl.RevertToSaved() elif self.RE_GO_WIN.match(cmd): self.GoWindow(cmd) elif re.match(self.RE_GO_BUFFER, cmd): self.GoBuffer(cmd) elif cmd.isdigit() or self.RE_NGO_LINE.match(cmd): ctrl = frame.nb.GetCurrentCtrl() cline = ctrl.GetCurrentLine() if cmd[0] in '+-': line = eval("%s %s %s" % (str(cline), cmd[0], cmd[1:])) else: line = int(cmd) - 1 ctrl.GotoLine(line) elif cmd.startswith('cd '): self.ChangeDir(cmd) elif cmd == 'q': self.Quit() else: wx.Bell() return self.CommandPush(cmd_str) self.GetParent().Hide() def GetHistCommand(self, pre=True): """Look up a command from the history of recent commands @param pre: Get previous (default) or get Next @note: pre moves right in stack, next moves left in stack """ val = self.GetValue().strip() if val not in self._history['cmds']: self._history['lastval'] = val if pre: if self._history['index'] < len(self._history['cmds']) - 1\ and self._history['index'] < 25: self._history['index'] += 1 index = self._history['index'] cmd = self._history['cmds'][index] else: if self._history['index'] > -1: self._history['index'] -= 1 index = self._history['index'] if index == -1: cmd = self._history['lastval'] else: cmd = self._history['cmds'][index] self.SetValue(cmd) self.SelectAll() def GoBuffer(self, cmd): """Go to next/previous buffer in notebook @param cmd: cmd string [0-9]*[nN] """ count = cmd[0:-1] cmd = cmd[-1] if count.isdigit(): count = int(count) else: count = 1 frame = self.GetTopLevelParent() numpage = frame.nb.GetPageCount() for x in range(min(count, numpage)): cpage = frame.nb.GetPageIndex(frame.nb.GetCurrentPage()) if (cpage == 0 and cmd == 'N') or \ (cpage + 1 == numpage and cmd == 'n'): break frame.nb.AdvanceSelection(cmd == 'n') def GoWindow(self, cmd): """Go to next/previous open window @param cmd: cmd string [0-9]*n[wW] """ count = cmd[0:-1] cmd = cmd[-1] if count.isdigit(): count = int(count) else: count = 1 wins = wx.GetApp().GetMainWindows() pid = self.GetTopLevelParent().GetId() widx = 0 win = 0 for nwin in xrange(len(wins)): if pid == wins[nwin].GetId(): widx = pid win = nwin break if cmd == 'W': widx = win + count else: widx = win - count if widx < 0: widx = 0 elif widx >= len(wins): widx = len(wins) - 1 self.GetParent().Hide() wins[widx].Raise() wx.CallAfter(wins[widx].nb.GetCurrentCtrl().SetFocus) def GetPaths(self, path, files=False): """Get a list of paths that are part of the given path by default it will only return directories. @param path: Path to enumerate @keyword files: Get list of files too """ def append_slash(path): """Helper function that appends a slash to the path if it's a directory. """ if os.path.isdir(path) and not path.endswith(os.sep): return path + os.sep return path curdir = self._curdir head, tail = os.path.split(path) head = os.path.expanduser(head) head = os.path.expandvars(head) head = os.path.join(curdir, head) if not os.path.isdir(head): return [] # Return empty list of user does not have # read access to the directory if not os.access(head, os.R_OK): ed_msg.PostMessage(ed_msg.EDMSG_UI_SB_TXT, (ed_glob.SB_INFO, _("Access Denied: %s") % head)) wx.Bell() # Beep to alert return list() # We expanded head, so trim the suggestion list of its head # so we can add the tail of the suggestion back to the original head try: candidates = [os.path.basename(p) for p in os.listdir(head) if p.startswith(tail)] candidates = [append_slash(os.path.join(os.path.dirname(path), cand)) for cand in candidates] if not files: candidates = [cand for cand in candidates if os.path.isdir(cand)] except OSError: ed_msg.PostMessage(ed_msg.EDMSG_UI_SB_TXT, (ed_glob.SB_INFO, _("Invalid Path"))) candidates = list() return sorted(list(set(candidates))) def ListDir(self): """List the next directory from the current cmd path""" cmd = self.GetValue() if cmd.startswith('cd '): cstr = 'cd ' elif cmd.startswith('e '): cstr = 'e ' else: return cmd = cmd.replace(cstr, u'', 1).strip() paths = self.GetPaths(cmd, cstr == 'e ') self._popup.SetChoices(paths) if len(paths): self._popup.SetupPosition(self) if not self._popup.IsShown(): self._popup.Show() self.SetInsertionPoint(self.GetLastPosition()) def OnEnter(self, evt): """Get the currently entered command string and execute it. @postcondition: ctrl is cleared and command is executed """ if self._popup.HasSuggestions() and self._popup.HasSelection(): psel = self._popup.GetSelection() if self.GetValue().split(' ', 1)[-1].strip() != psel: self._AdjustValue(psel) self._popup.Hide() return cmd = self.GetValue() self.Clear() self.ExecuteCommand(cmd) if self._popup.IsShown(): self._popup.Hide() def OnKeyDown(self, evt): """Records the key sequence that has been entered and performs actions based on that key sequence. @param evt: event that called this handler """ e_key = evt.GetKeyCode() cmd = self.GetValue() if e_key == wx.WXK_UP: if self._popup.HasSuggestions(): self._popup.AdvanceSelection(False) else: self.GetHistCommand(pre=True) elif e_key == wx.WXK_DOWN: if self._popup.HasSuggestions(): self._popup.AdvanceSelection(True) else: self.GetHistCommand(pre=False) elif e_key == wx.WXK_SPACE and not len(cmd): # Swallow space key when command is empty pass elif e_key == wx.WXK_TAB: # Provide Tab Completion or swallow key if cmd.startswith('cd ') or cmd.startswith('e '): if self._popup.HasSuggestions(): self._AdjustValue(self._popup.GetSelection()) self.ListDir() else: pass elif e_key == wx.WXK_ESCAPE: if self._popup.IsShown(): self._popup.Hide() else: self.Clear() self.GetParent().Hide() else: evt.Skip() def OnKeyUp(self, evt): """Adjust size as needed when characters are entered @param evt: event that called this handler """ e_key = evt.GetKeyCode() if e_key == wx.WXK_ESCAPE: evt.Skip() return val = self.GetValue() cwd_info = "" if val.strip() in ['cwd', 'e', 'cd']: cwd_info = " " + _(u"cwd: ") + self._curdir self.Parent.info_lbl.SetLabel(cwd_info) if self._popup.IsShown(): if not len(val): self._popup.Hide() else: wx.CallAfter(self.UpdateAutoComp) else: if self._popup.HasSuggestions(): self._AdjustValue(self._popup.GetSelection()) self.ListDir() self._AdjustSize() evt.Skip() def OnPopupNotify(self, evt): """Receive the selections from the popup list @param evt: event that called this handler """ val = evt.GetValue() self._AdjustValue(val) def OnKillFocus(self, evt): """Hide the popup when we look focus @param evt: event that called this handler """ self._popup.Hide() evt.Skip() def OnSetFocus(self, evt): """Ensure caret is at end when focus is reset @param evt: event that called this handler """ self.SetInsertionPoint(self.GetLastPosition()) evt.Skip() def RestoreFocus(self): """Restore focus and cursor position @postcondition: ctrl has focus and cursor is moved to last position """ self.SetInsertionPoint(self.GetLastPosition()) self.SetFocus() def Quit(self): """Tell the editor to exit @postcondition: Editor begins exit, confirming file saves """ wx.PostEvent(self.GetTopLevelParent(), wx.CloseEvent(wx.wxEVT_CLOSE_WINDOW)) def SetValue(self, value): """Overrides the controls default function to allow for automatic resizing of the control when text is added. @param value: string to set value of control to """ super(CommandExecuter, self).SetValue(value) self._AdjustSize() def UpdateAutoComp(self): """Update the autocomp list for paths that best match current value""" self.ListDir() def WriteCommand(self, cstr): """Perform a file write related command @param cstr: The command string to execute """ # wn: write and edit next # wN: write and edit previous # wq: write and quit #-----------------------------------------------------------------------------# class LineCtrl(eclib.CommandEntryBase): """A custom int control for providing a go To line control for the Command Bar. """ def __init__(self, parent, id_, get_doc, size=wx.DefaultSize): """Initializes the LineCtrl control and its attributes. @param parent: Parent Window @param id_: Control ID @param get_doc: callback method for retrieving a reference to the current document. @keyword size: Control Size (tuple) """ super(LineCtrl, self).__init__(parent, id_, u"", size=size, style=wx.TE_PROCESS_ENTER, validator=util.IntValidator(0, 65535)) # Attributes self._last = 0 self.GetDoc = get_doc def OnEnter(self, evt): """Processes the entered line number @param evt: wx.EVT_TEXT_ENTER """ val = self.GetValue() if not val.isdigit(): return val = int(val) - 1 doc = self.GetDoc() lines = doc.GetLineCount() if val > lines: val = lines doc.GotoLine(val) doc.SetFocus() self.GetParent().Hide() def OnKeyUp(self, evt): """Handle keyup events""" if evt.GetEventType() != wx.wxEVT_KEY_UP: evt.Skip() return e_key = evt.GetKeyCode() if e_key == wx.WXK_ESCAPE: # TODO change to more safely determine the context # Currently control is only used in command bar self.GetParent().Hide() else: evt.Skip() #-----------------------------------------------------------------------------# # TODO: merge the common parts of these two classes into a single base class class PopupListBase(object): """Common functionality between Popuplist GTK and Mac""" def AdvanceSelection(self, next=True): """Advance the list selection @keyword next: goto the next or previous selection """ sel = self._list.GetSelection() if next: count = self._list.GetCount() sel += 1 if sel < count: self._list.SetSelection(sel) else: sel -= 1 if sel >= 0: self._list.SetSelection(sel) def GetSelection(self): """Get the string that is currently selected in the list @return: string selection """ return self._list.GetStringSelection() def HasSelection(self): """Tells whether anything in the list is selected""" return self._list.GetSelection() != wx.NOT_FOUND def HasSuggestions(self): """Tell whether the list is showing suggestions""" return self.IsShown() and self.ListCount() > 0 def ListCount(self): """return the number of elements in the popup list""" return self._list.GetCount() def GetListCtrl(self): """Get the ListBox control of the popupwindow""" return self._list def GetChoices(self): """Get the items as a list @return: list of strings """ return self._list.GetStrings() def SetSelection(self, index): """Set the selection in the list by index @param index: zero based index to set selection by """ self._list.SetSelection(index) class PopupList(wx.MiniFrame, PopupListBase): """Popup window with a listbox in it""" def __init__(self, parent, choices=list(), pos=wx.DefaultPosition): style = wx.FRAME_NO_TASKBAR | wx.FRAME_FLOAT_ON_PARENT if wx.Platform == '__WXMAC__': style = style | wx.BORDER_NONE | wx.POPUP_WINDOW else: style = style | wx.SIMPLE_BORDER wx.MiniFrame.__init__(self, parent, pos=pos, style=style) PopupListBase.__init__(self) # Attributes self._list = wx.ListBox(self, choices=choices, style=wx.LC_REPORT | wx.LC_SINGLE_SEL | wx.LC_NO_HEADER | wx.NO_BORDER) # Layout self._list.SetWindowVariant(wx.WINDOW_VARIANT_SMALL) self.SetWindowVariant(wx.WINDOW_VARIANT_SMALL) sizer = wx.BoxSizer(wx.HORIZONTAL) sizer.Add(self._list, 1, wx.EXPAND) self.SetSizer(sizer) txt_h = self.GetTextExtent('/')[1] self.SetMaxSize((-1, txt_h * 6)) self.SetAutoLayout(True) # Event Handlers self.Bind(wx.EVT_CHAR, lambda evt: parent.GetEventHandler().ProcessEvent(evt)) self.Bind(wx.EVT_SET_FOCUS, self.OnFocus) self.Bind(wx.EVT_LISTBOX_DCLICK, self.OnSelection) self.Bind(wx.EVT_SIZE, self.OnSize) self._list.Bind(wx.EVT_KEY_UP, self.OnKeyUp) self._list.SetFocus() self.Hide() def __PostEvent(self): """Post notification of selection to parent @postcondition: selected string is posted to parent """ val = self._list.GetStringSelection() evt = ed_event.NotificationEvent(ed_event.edEVT_NOTIFY, self.GetId(), val, self._list) wx.PostEvent(self.GetParent(), evt) self.ActivateParent() def ActivateParent(self): """Activate the parent window @postcondition: parent window is raised """ parent = self.GetParent() parent.Raise() parent.SetFocus() def OnFocus(self, evt): """Raise and reset the focus to the parent window whenever we get focus. @param evt: event that called this handler """ self.ActivateParent() self.GetParent().SetFocus() evt.Skip() def OnKeyUp(self, evt): """Process key up events in the control @param evt: event that called this handler """ if evt.GetKeyCode() == wx.WXK_RETURN: self.__PostEvent() else: evt.Skip() def OnSelection(self, evt): """Handle a selection in list by posting the result to the parent. @param evt: Event that called this handler """ self.__PostEvent() def OnSize(self, evt): """Resize the listbox""" csz = self.GetClientSize() csz.SetWidth(csz.x + wx.SystemSettings.GetMetric(wx.SYS_VSCROLL_X)) self._list.SetSize(csz) evt.Skip() def Show(self, show=True): """Adjust size of popup and then show it @keyword show: Should the window be shown or not """ res = super(PopupList, self).Show(show) if res and show: self.ActivateParent() if wx.Platform == '__WXMAC__': self.GetParent().Refresh(False) return res def SetChoices(self, choices): """Set the available choices that are shown in the list @param choices: list of strings """ selection = self._list.GetSelection() self._list.SetItems(choices) count = self._list.GetCount() if selection == wx.NOT_FOUND or selection >= count: selection = 0 if count > 0: self._list.SetSelection(selection) def SetStringSelection(self, text): """Set the list selection by using a string value @param text: string to select in list """ self._list.SetStringSelection(text) def SetupPosition(self, cmd_ex): """Sets size and position of widget @param cmd_ex: CommandExecuter window """ cmd = cmd_ex.GetValue() cmd = cmd.split(u' ', 1)[0] xpos = cmd_ex.GetTextExtent(cmd + u' ')[0] pos = cmd_ex.GetScreenPosition().Get() csize = cmd_ex.GetSize() self.SetPosition((pos[0] + xpos, pos[1] + csize[1])) self.ActivateParent() #----------------------------------------------------------------------------# class PopupWinList(wx.PopupWindow, PopupListBase): """Popuplist for Windows/GTK""" def __init__(self, parent, choices=list(), pos=wx.DefaultPosition): """Create the popup window and its list control""" wx.PopupWindow.__init__(self, parent) PopupListBase.__init__(self) # Attributes self._list = wx.ListBox(self, choices=choices, pos=(0, 0), style=wx.LC_REPORT | wx.LC_SINGLE_SEL | wx.LC_NO_HEADER) # Layout sizer = wx.BoxSizer(wx.HORIZONTAL) sizer.Add(self._list, 0, wx.EXPAND) self.SetSizer(sizer) txt_h = self.GetTextExtent('/')[1] self.SetMaxSize((-1, txt_h * 6)) self.SetAutoLayout(True) # Event Handlers self.Bind(wx.EVT_SIZE, self.OnSize) def OnSize(self, evt): """Resize the list box to the correct size to fit.""" csz = self.GetClientSize() csz.SetWidth(csz.x + wx.SystemSettings.GetMetric(wx.SYS_VSCROLL_X)) self._list.SetSize(csz) evt.Skip() def SetupPosition(self, cmd_ex): """Sets size and position of widget @param cmd_ex: CommandExecuter window """ cmd = cmd_ex.GetValue() cmd = cmd.split(u' ', 1)[0] pos = cmd_ex.GetScreenPosition().Get() csize = cmd_ex.GetSize() xpos = cmd_ex.GetTextExtent(cmd)[0] self._list.SetInitialSize() self.SetInitialSize() self.SetPosition((pos[0] + xpos, pos[1] + csize[1])) def SetChoices(self, choices): """Set the available choices that are shown in the list @param choices: list of strings """ selection = self._list.GetSelection() self._list.SetItems(choices) count = self._list.GetCount() if selection == wx.NOT_FOUND or selection >= count: selection = 0 if count > 0: self._list.SetSelection(selection) def Show(self, show=True): """Adjust size of popup and then show it @keyword show: Should the window be shown or not """ res = super(PopupWinList, self).Show(show) self._list.Show() self._list.SetInitialSize() self.SetInitialSize() return res