Source

pida-main / pida / ui / completer.py

Full commit
# -*- coding: utf-8 -*- 
"""
    :copyright: 2005-2008 by The PIDA Project
    :license: GPL 2 or later (see README/COPYING/LICENSE)
"""

import gtk
import gobject
import os
from pida.utils.languages import COMPLETER
from pygtkhelpers.utils import gsignal


#XXX: register as stock icons
def _load_pix(fn):
    return gtk.gdk.pixbuf_new_from_file(
        os.path.join(os.path.dirname(__file__),
        "..","services","language","pixmaps", fn))


_PIXMAPS = {
    COMPLETER.UNKNOWN:     _load_pix('element-event-16.png'),
    COMPLETER.ATTRIBUTE:   _load_pix('source-attribute.png'),
    COMPLETER.CLASS:       _load_pix('source-class.png'),
    COMPLETER.FUNCTION:    _load_pix('source-attribute.png'), # FIXME
    COMPLETER.METHOD:      _load_pix('source-attribute.png'),
    COMPLETER.MODULE:      _load_pix('source-method.png'),
    COMPLETER.PROPERTY:    _load_pix('source-property.png'),
    COMPLETER.EXTRAMETHOD: _load_pix('source-extramethod.png'),
    COMPLETER.VARIABLE:    _load_pix('source-attribute.png'), # FIXME
    COMPLETER.IMPORT:      _load_pix('source-import.png'),
    COMPLETER.PARAMETER:   _load_pix('source-attribute.png'), # FIXME
    COMPLETER.BUILTIN:     _load_pix('source-attribute.png'), # FIXME
    COMPLETER.KEYWORD:     _load_pix('source-attribute.png'), # FIXME
}


class SuggestionsList(gtk.ListStore):
    def __init__(self):
        gtk.ListStore.__init__(self, gtk.gdk.Pixbuf, str)

    @staticmethod
    def from_dbus(args):
        rv = list(args)
        rv[0] = _PIXMAPS.get(args[0], _PIXMAPS[COMPLETER.UNKNOWN])
        return rv

class PidaCompleterWindow(gtk.Window):
    def __init__(self, type_=gtk.WINDOW_TOPLEVEL, 
                    show_input=True, show_icons=True):
        super(PidaCompleterWindow, self).__init__(type_)
        self.set_focus_on_map(False)
        self.widget = PidaCompleter(show_input=show_input, show_icons=show_icons)
        self.add(self.widget)
        self.set_decorated(False)
        self.set_keep_above(True)
        self.set_role('completer')
        #XXX: messed with the testing, couldnt input text
        #self.set_property('accept-focus', False)
        self.set_property('skip-pager-hint', True)
        self.set_property('skip-taskbar-hint', True)
        self.set_property('type-hint', gtk.gdk.WINDOW_TYPE_HINT_TOOLTIP)




class PidaCompleter(gtk.HBox):

   
    gsignal('user-abort', str, flags=gobject.SIGNAL_RUN_LAST)
    gsignal('user-accept', str, flags=gobject.SIGNAL_RUN_LAST)
    gsignal('suggestion-selected', str, flags=gobject.SIGNAL_RUN_LAST)


    def __init__(self, show_input=True, show_icons=True,
                 filter_=""):
        self._model = None
        super(PidaCompleter, self).__init__()
        self._show_input = show_input
        self._filter = ''
        self._create_ui()
        self._ignore_change = False
        self.show_icons = show_icons
        
        #self._tree.set_model(SuggestionsTree())
        self.filter = filter_
        
        self._tree.connect("row-activated", self.on_row_activated)
        
        if show_input:
            self._entry.connect('key-press-event', self.on_key_press_event)
            self._entry.grab_focus()
        
        # public vars
        self.min_height = 130
        self.max_height = 400
        self.min_width = 200
        self.max_width = 500
        
        # ignore case does case insensetive filter
        self.ignore_case = True
        # tab
        self.accept_keys = (65289,)
        # esc
        self.abort_keys = (65307,)

    def filter_function(self, model, iter_):
        if not self._filter:
            return True
        if self.ignore_case:
            var = self._modelreal.get_value(iter_, 1)
            if var: var = var.lower()
            filter_ = self._filter.lower()
        else:
            var = self._modelreal.get_value(iter_, 1)
            filter_ = self._filter
        if not var:
            return False
        if var[:len(self._filter)] == filter_:
                return True
        return False

    def on_entry_changed(self, widget, *args):
        if self._ignore_change:
            return True
        self._filter = self._get_non_sel() #widget.get_text()
        if self._model: self._model.refilter()

    def on_row_activated(self, treeview, path, view_column):
        self._sig_clean("user-accept", self._modelreal[path[0]][1])

    def _sig_clean(self, sig, val):
        self.emit(sig, val)
        self._filter = ''
        self.hide()

    def _update_sel(self, new_text):
        text = self._get_non_sel()
        self._ignore_change = True
        #print "non sel", text, new_text
        #print text + new_text[len(text):]
        op = self._entry.get_position()
        nt = new_text
        self._entry.set_text(nt)
        self._entry.set_position(op)
        self._entry.select_region(len(nt),op)
        self._ignore_change = False
        self.emit("suggestion-selected", nt)

    def _get_non_sel(self):
        sel = self._entry.get_selection_bounds()
        if not sel:
            return self._entry.get_text()
        return self._entry.get_text()[:sel[0]]

    def on_key_press_event(self, widget, event):
        if event.type == gtk.gdk.KEY_PRESS:
            #print "key_pressed ", event.keyval
            #tab 65289
            if event.keyval in self.accept_keys:
                self._sig_clean("user-accept", self._filter)
                return True
            elif event.keyval in self.abort_keys:
                self._sig_clean("user-abort", self._filter)
                return True
            elif event.keyval == 65364: #key down
                s = self._tree.get_selection()
                it = s.get_selected()[1]
                if not it:
                    ni = self._model.get_iter_first()
                else:
                    ni = self._model.iter_next(it)
                if not ni:
                    return True
                s.select_iter(ni)
                self._tree.scroll_to_cell(self._model.get_path(ni))
                nt = self._model.get_value(ni, 1)
                self._update_sel(nt)
                return True
            elif event.keyval == 65362: #key up
                s = self._tree.get_selection()
                it = s.get_selected()[1]
                if not it:
                    return True
                path = self._model.get_path(it)
                if path[0] == 0:
                    return True
                np = (path[0]-1,)
                s.select_path(np)
                self._tree.scroll_to_cell(np, use_align=False, row_align=0.0, col_align=0.0)
                nt = self._model.get_value(self._model.get_iter(np), 1)
                self._update_sel(nt)
                return True
            elif event.keyval == gtk.gdk.keyval_from_name('Page_Up'):
                s = self._tree.get_selection()
                vr = self._tree.get_visible_range()
                diff = vr[1][0]-vr[0][0]
                np = (max(vr[0][0]-diff,0),)
                s.select_path(np)
                self._tree.scroll_to_cell(np, use_align=False, row_align=0.0, col_align=0.0)
                nt = self._model.get_value(self._model.get_iter(np), 1)
                self._update_sel(nt)
                return True
                
            elif event.keyval == gtk.gdk.keyval_from_name('Page_Down'):
                s = self._tree.get_selection()
                vr = self._tree.get_visible_range()
                diff = vr[1][0]-vr[0][0]
                np = (vr[0][0]+diff,)
                s.select_path(np)
                self._tree.scroll_to_cell(np, use_align=True, row_align=0.0, col_align=0.0)
                nt = self._model.get_value(self._model.get_iter(np), 1)
                self._update_sel(nt)
                return True
            elif event.keyval == 65293: # enter
                self._sig_clean("user-accept", self._entry.get_text())
                return True
                #pass
            else:
                return
                # 
                #if event.string:
                # we pass the event to the entry box so it can handle
                # the key events like a normal entry box
                return
    
    #@property
    def get_model(self):
        return self._model

    def __len__(self):
        return len(self._modelreal)

    def set_model(self, model):
        #model = gtk.(gobject.TYPE_INT, gobject.TYPE_STRING)
        #self._tree.set_model(model)
        self._modelreal = model
        self._model = model.filter_new()
        self._model.set_visible_func(self.filter_function)
        self._tree.set_model(self._model)

        #return self._tree.get_model()
    model = property(get_model, set_model)
        
    def get_filter(self):
        """
        The filter is used to increate completion speed
        """
        return self._entry.get_text()
        
    def set_filter(self, value, update_field=True):
        self._filter = value
        if update_field:
            self._entry.set_text(value)
        if self._model: self._model.refilter()
        #self._model.
        
    filter = property(get_filter, set_filter)
    
    def _create_ui(self):
        self._box = gtk.VBox()
        scrolled_window = gtk.ScrolledWindow()
        scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        #self._box = scrolled_window
        #self.add(scrolled_window)
        #self.set_size_request(150, 300)
        #self.set_decorated(False)
        self._entry = gtk.Entry()
        #self._entry.connect("insert-text", self.on_entry_changed)
        #self._entry.connect("delete-text", self.on_entry_changed)
        self._entry.connect("changed", self.on_entry_changed)
        #self._entry.set_text(self.filter)
        if self._show_input:
            self._box.pack_start(self._entry, False, False)
        #self._entry.set_size_request(0, 0)
        self._tree = gtk.TreeView()
        #self._tree.set_size_request(100, 100)
        self._tree.set_headers_visible(False)
        self._tree.set_enable_search(False)
        #self._tree.set_fixed_height_mode(True)
        self._tree_icons = ic = gtk.TreeViewColumn('Typ', gtk.CellRendererPixbuf(), pixbuf=0)
        ic.set_visible(self.show_icons)
        ic.set_resizable(False)
        ic.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
        ic.set_fixed_width(32)
        ic.set_reorderable(False)
        self._tree.append_column(ic)
        self._tree_label = ic = gtk.TreeViewColumn('Name', gtk.CellRendererText(), text=1)
        ic.set_resizable(True)
        ic.set_sizing(gtk.TREE_VIEW_COLUMN_GROW_ONLY)
        #ic.set_fixed_width(32)
        ic.set_reorderable(False)
        self._tree.append_column(ic)
        scrolled_window.add(self._tree)
        self._box.add(scrolled_window)
        self.add(self._box)
        self.show_all()
        self.hide()

    def place(self, x, y, height, below=True):
        """
        Try to place the completer optimal on the window.
        x, y is top left corner of the position the window should be placed
        and height the hight of the line.The line will not be overlapped.
        """
        swid = gtk.gdk.screen_width()
        shei = gtk.gdk.screen_height()

        BELOW = 1
        ABOVE = 2

        pos = below and BELOW or ABOVE

        tar_x, tar_y = 0, 0
        tar_height, tar_widht = self.max_height, self.min_width

        if self.min_height + height + y > shei:
            pos = ABOVE

        if pos == ABOVE and y - self.min_height < 0:
            pos = BELOW

        if pos == BELOW:
            tar_x = x 
            tar_y = y + height
            tar_height = min(shei - y - height, tar_height)
        else:
            tar_height = min(y, tar_height)
            tar_x = x
            tar_y = y - tar_height

        self.parent.set_size_request(tar_widht, tar_height)
        self.parent.resize(tar_widht, tar_height)
        self.parent.move(tar_x, tar_y)


    def get_show_icons(self):
        return self._tree_icons.get_visible()

    def set_show_icons(self, value):
        return self._tree_icons.set_visible(value)

    def add_str(self, line, type_=None):
        # we only hold a uniqe list of items
        # because a later suggestion may give us a better type, we update the
        # old one
        for entry in self._modelreal:
            if entry[1] == line:
                if entry[0] == None:
                   entry[0] = _PIXMAPS.get(type_, None)
                return

        self._modelreal.append((_PIXMAPS.get(type_, None), line))

    show_icons = property(get_show_icons, set_show_icons)

gobject.type_register(PidaCompleter)



if __name__ == "__main__":
    def accepted(*args):
        print "user accepted ", args

    def abort(*args):
        print "user abort ", args

    w = PidaCompleterWindow()#gtk.WINDOW_POPUP)
    p = w.widget
    l = SuggestionsList()

    i = 0
    for x in ['ase', 'assdf', 'Asdf', 'zasd', 'Zase', 'form', 'in', 'of', 'BLA',
              'taT', 'tUt', 'df', 'asdf', 'asdfa', 'sd', 'sdfsf', 'ssdfsdf', 'sd']:
        l.append(SuggestionsList.from_dbus((COMPLETER[i], x)))
        i = (i + 1)%7

    p.set_model(l)

    p.connect("user-abort", abort)
    p.connect("user-accept", accepted)

    w.show_all()
    w.grab_focus()
#     gtk.rc_parse('/home/poelzi/Projects/pida/loadfix/pida/resources/data/gtkrc-2.0')
#     pw = PidaDocWindow(
#     'bla.blubb',
#     'some small info', 
#     '''this is a long
#     text and more info
#     more bla bla and something
#     * blubb
#     ''')
#     pw.present()

    gtk.main()