ed_bookmark.py 9.9 KB
###############################################################################
# Name: Cody Precord                                                          #
# Purpose: Bookmark Manager window and plugin interface                       #
# Author: Cody Precord <cprecord@editra.org>                                  #
# Copyright: (c) 2010 Cody Precord <staff@editra.org>                         #
# License: wxWindows License                                                  #
###############################################################################

"""
Bookmark manager

"""

__author__ = "Cody Precord <cprecord@editra.org>"
__svnid__ = "$Id: ed_bookmark.py 69115 2011-09-17 16:51:49Z CJP $"
__revision__ = "$Revision: 69115 $"

#--------------------------------------------------------------------------#
# Imports
import os
import re
import wx

# Editra Libraries
import ed_msg
import iface
import plugin
from profiler import Profile_Get, Profile_Set
import ed_glob
import util
import eclib
import ebmlib
import ed_basewin
from ed_marker import Bookmark

#-----------------------------------------------------------------------------#
# Globals
_ = wx.GetTranslation

#-----------------------------------------------------------------------------#

# Interface Implementation
class EdBookmarks(plugin.Plugin):
    """Shelf interface implementation for the bookmark manager"""
    plugin.Implements(iface.ShelfI)

    __name__ = u'Bookmarks'

    @staticmethod
    def AllowMultiple():
        """EdBookmark only allows one instance"""
        return False

    @staticmethod
    def CreateItem(parent):
        """Returns a bookmark panel"""
        return BookmarkWindow(parent)

    def GetBitmap(self):
        """Get the log viewers tab icon
        @return: wx.Bitmap

        """
        bmp = wx.ArtProvider.GetBitmap(str(ed_glob.ID_ADD_BM), wx.ART_MENU)
        return bmp

    @staticmethod
    def GetId():
        """Plugin menu identifier ID"""
        return ed_glob.ID_BOOKMARK_MGR

    @staticmethod
    def GetMenuEntry(menu):
        """Get the menu entry for the bookmark viewer
        @param menu: the menu items parent menu

        """
        item = wx.MenuItem(menu, ed_glob.ID_BOOKMARK_MGR,
                           _("Bookmarks"),
                           _("View all bookmarks"))
        bmp = wx.ArtProvider.GetBitmap(str(ed_glob.ID_ADD_BM), wx.ART_MENU)
        item.SetBitmap(bmp)
        return item

    def GetName(self):
        """Return the name of this control"""
        return self.__name__

    @staticmethod
    def IsStockable():
        """EdBookmark can be saved in the shelf preference stack"""
        return True

    # Bookmark storage
    _marks = list()
    @classmethod
    def OnStoreBM(cls, msg):
        data = msg.GetData()
        buf = data.get('stc')
        line = data.get('line')
        mark = Bookmark()
        mark.Filename = buf.GetFileName()
        mark.Line = line
        if data.get('added', False):
            if mark not in cls._marks:
                # Store the stc bookmark handle
                mark.Handle = data.get('handle', None)
                # Store an alias for the bookmark
                name = u""
                cline = buf.GetCurrentLine()
                if line == cline:
                    name = buf.GetSelectedText()
                if not name:
                    name = buf.GetLine(line)
                mark.Name = name.strip()
                cls._marks.append(mark)
        else:
            if mark in cls._marks:
                idx = cls._marks.index(mark)
                cls._marks.pop(idx)

    @classmethod
    def GetMarks(cls):
        return cls._marks

ed_msg.Subscribe(EdBookmarks.OnStoreBM, ed_msg.EDMSG_UI_STC_BOOKMARK)

#-----------------------------------------------------------------------------#

class BookmarkWindow(ed_basewin.EdBaseCtrlBox):
    """Shelf window for managing bookmarks"""
    def __init__(self, parent):
        super(BookmarkWindow, self).__init__(parent)

        # Attributes
        self._list = BookmarkList(self)

        #Setup
        self.SetWindow(self._list)
        ctrlbar = self.CreateControlBar(wx.TOP)
        ctrlbar.AddStretchSpacer()
        self._delbtn = self.AddPlateButton(_("Delete"), ed_glob.ID_DELETE,
                                           wx.ALIGN_RIGHT)
        self._delbtn.ToolTip = wx.ToolTip(_("Delete Bookmark"))
        self._delallbtn = self.AddPlateButton(_("Delete All"),
                                              ed_glob.ID_DELETE_ALL,
                                              wx.ALIGN_RIGHT)
        self._delallbtn.ToolTip = wx.ToolTip(_("Delete all bookmarks"))

        # Message Handlers
        ed_msg.Subscribe(self.OnBookmark, ed_msg.EDMSG_UI_STC_BOOKMARK)

        # Event Handlers
        self.Bind(wx.EVT_WINDOW_DESTROY, self.OnDestroy, self)
        self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnItemActivate, self._list)
        self.Bind(wx.EVT_BUTTON, self.OnDelBm, self._delbtn)
        self.Bind(wx.EVT_BUTTON, self.OnDelAllBm, self._delallbtn)
        # BUG in wxAUI UpdateUI events not processed when docked
        # TODO: renable when switch to agw aui
#        self.Bind(wx.EVT_UPDATE_UI,
#                  lambda evt: evt.Enable(bool(len(self._list.GetSelections()))),
#                  self._delbtn)

    def OnDestroy(self, evt):
        """Unsubscribe message handlers on delete"""
        if evt.GetId() == self.GetId():
            ed_msg.Unsubscribe(self.OnBookmark)
        evt.Skip()

    def OnBookmark(self, msg):
        """Bookmark added or removed callback"""
        # Update on next iteration to ensure that handler
        # in the singleton data store have been updated.
        wx.CallAfter(self.DoUpdateListCtrl)

    def OnDelAllBm(self, evt):
        """Delete all bookmarks"""
        items = range(self._list.ItemCount)
        self.DeleteBookmarks(items)

    def OnDelBm(self, evt):
        """Remove the selected bookmark(s) from the list and the buffer"""
        items = self._list.GetSelections()
        self.DeleteBookmarks(items)

    def DeleteBookmarks(self, items):
        """Delete the bookmarks from the passed in list
        @param items: list of indexes in BookmarkList

        """
        assert isinstance(items, list)
        if len(items):
            items.reverse()
            marks = EdBookmarks.GetMarks()
            for item in items:
                if item < len(marks):
                    mark = marks.pop(item)
                    app = wx.GetApp()
                    mw = app.GetActiveWindow()
                    if mw:
                        nb = mw.GetNotebook()
                        buf = nb.FindBuffer(mark.Filename)
                        if buf:
                            buf.MarkerDeleteHandle(mark.Handle)
            self.DoUpdateListCtrl()

    def DoUpdateListCtrl(self):
        """Update the listctrl for changes in the cache"""
        nMarks = len(EdBookmarks.GetMarks())
        self._list.SetItemCount(nMarks)
        # Refresh everything
        # XXX: if optimization is needed only refresh visible items
        self._list.RefreshItems(0, nMarks)
        self._list.Refresh()

    def OnItemActivate(self, evt):
        """Handle double clicks on items to navigate to the
        selected bookmark.

        """
        index = evt.m_itemIndex
        marks = EdBookmarks.GetMarks()
        if index < len(marks):
            mark = marks[index]
            self.GotoBookmark(mark)

    def GotoBookmark(self, mark):
        """Goto the bookmark in the editor
        @param mark: BookMark

        """
        app = wx.GetApp()
        mw = app.GetActiveWindow()
        if mw:
            nb = mw.GetNotebook()
            buf = nb.FindBuffer(mark.Filename)
            use_handle = True
            if not buf:
                nb.OpenPage(ebmlib.GetPathName(mark.Filename),
                            ebmlib.GetFileName(mark.Filename))
                buf = nb.GetCurrentPage()
                use_handle = False # Handle is invalid so use line number

            if buf:
                # Ensure the tab is the current one
                nb.GotoPage(mark.Filename)
                # Jump to the bookmark line
                if use_handle:
                    lnum = buf.MarkerLineFromHandle(mark.Handle)
                else:
                    lnum = mark.Line
                buf.GotoLine(lnum)
        else:
            util.Log("[ed_bookmark][err] Failed to locate mainwindow")

#-----------------------------------------------------------------------------#

class BookmarkList(eclib.EBaseListCtrl):
    """ListCtrl for displaying the bookmarks in"""
    BOOKMARK = 0
    FILE_NAME = 1
    LINE_NUM = 2
    def __init__(self, parent):
        super(BookmarkList, self).__init__(parent,
                                           style=wx.LC_REPORT|\
                                                 wx.LC_EDIT_LABELS|\
                                                 wx.LC_VIRTUAL)

        # Setup
        self._il = wx.ImageList(16,16)
        self._idx = self._il.Add(Bookmark().Bitmap)
        self.SetImageList(self._il, wx.IMAGE_LIST_SMALL)
        self.InsertColumn(BookmarkList.BOOKMARK, _("Bookmark"))
        self.InsertColumn(BookmarkList.FILE_NAME, _("File Location"))
        self.InsertColumn(BookmarkList.LINE_NUM, _("Line Number"))
        self.setResizeColumn(BookmarkList.FILE_NAME+1) #NOTE: +1 bug in mixin
        self.SetItemCount(len(EdBookmarks.GetMarks()))

    def OnGetItemImage(self, item):
        return 0

    def OnGetItemText(self, item, column):
        """Override for virtual control"""
        marks = EdBookmarks.GetMarks()
        val = u""
        if item < len(marks):
            mark = marks[item]
            if column == BookmarkList.BOOKMARK:
                val = mark.Name
                if not val:
                    val = _("Bookmark%d") % item
            elif column == BookmarkList.FILE_NAME:
                val = mark.Filename
            elif column == BookmarkList.LINE_NUM:
                val = unicode(mark.Line + 1)
        return val