Improve the docs we show on properties and variables
[reinteract/rox.git] / lib / reinteract / completion_popup.py
blob4714d937f839decca82569b97b9a0433aaf6dc13
1 import inspect
2 import gtk
4 from doc_popup import DocPopup
6 # Space between the line of text where the cursor is and the popup
7 VERTICAL_GAP = 5
9 # Size of the popup
10 WIDTH = 300
11 HEIGHT = 300
13 class CompletionPopup(gtk.Window):
15 """Class implementing a completion popup for ShellView
17 This class encapsulates the user interface logic for completion
18 popups. The actual code to determine possible completions lives in
19 tokenized_statement.py.
21 """
23 __gsignals__ = {
24 'expose-event': 'override',
27 def __init__(self, view):
28 gtk.Window.__init__(self, gtk.WINDOW_POPUP)
29 self.__view = view
30 self.set_border_width(1)
32 sw = gtk.ScrolledWindow()
33 self.add(sw)
35 sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
37 self.__tree_model = gtk.ListStore(str, str, object)
38 self.__tree = gtk.TreeView(self.__tree_model)
39 self.__tree.set_headers_visible(False)
41 self.__tree.get_selection().connect('changed', self.__on_selection_changed)
43 cell = gtk.CellRendererText()
44 column = gtk.TreeViewColumn(None, cell, text=0)
45 self.__tree.append_column(column)
47 sw.add(self.__tree)
48 sw.show_all()
50 self.set_default_size(WIDTH, HEIGHT)
52 # A small amount of background shows between the scrollbar and the list;
53 # which looks ugly if it is the only gray thing in the window, so change
54 # the window background to white
55 self.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(65535, 65535, 65535))
57 self.__doc_popup= DocPopup()
59 self._in_change = False
60 self.showing = False
62 def do_expose_event(self, event):
63 gtk.Window.do_expose_event(self, event)
65 # Draw a black rectangle around the popup
66 cr = event.window.cairo_create()
67 cr.set_line_width(1)
68 cr.set_source_rgb(0., 0., 0.)
69 cr.rectangle(0.5, 0.5, self.allocation.width - 1, self.allocation.height - 1)
70 cr.stroke()
72 return False
74 def __update_completions(self):
75 buf = self.__view.get_buffer()
77 self.__in_change = True
78 self.__tree_model.clear()
79 for display, completion, obj in buf.find_completions():
80 self.__tree_model.append([display, completion, obj])
82 self.__tree.set_cursor(0)
83 self.__in_change = False
84 self.__update_doc_popup()
86 def __update_position(self):
87 buf = self.__view.get_buffer()
89 insert = buf.get_iter_at_mark(buf.get_insert())
91 cursor_rect = self.__view.get_iter_location(insert)
92 cursor_rect.x, cursor_rect.y = self.__view.buffer_to_window_coords(gtk.TEXT_WINDOW_TEXT, cursor_rect.x, cursor_rect.y)
94 window = self.__view.get_window(gtk.TEXT_WINDOW_LEFT)
95 window_x, window_y = window.get_origin()
96 cursor_rect.x += window_x
97 cursor_rect.y += window_y
99 x = cursor_rect.x
100 y = cursor_rect.y + cursor_rect.height + VERTICAL_GAP
102 # If the popup would go off the screen, pop it up above instead; should we
103 # reverse the direction of the items here, as for a menu? I think that would
104 # be more confusing than not doing so.
105 if y + HEIGHT > window.get_screen().get_height():
106 y = cursor_rect.y - VERTICAL_GAP - HEIGHT
108 if self.showing:
109 old_x, old_y = self.window.get_position()
110 if y == old_y or x >= old_x:
111 return
112 self.move(x, y)
114 def __update_doc_popup(self):
115 if not self.showing:
116 self.__doc_popup.popdown()
117 return
119 model, iter = self.__tree.get_selection().get_selected()
120 if not iter:
121 self.__doc_popup.popdown()
122 return
124 obj = model.get_value(iter, 2)
126 # Long term it would be nice to preview the value of the
127 # object, but it's distracting to show the class docs on int
128 # for every integer constant, etc, which is what the DocPopup
129 # does currently.
130 if (obj == None or
131 not (inspect.ismodule(obj) or
132 inspect.isclass(obj) or
133 inspect.isroutine(obj) or
134 inspect.isgetsetdescriptor(obj) or
135 inspect.ismemberdescriptor(obj) or
136 isinstance(obj, property))):
137 self.__doc_popup.popdown()
138 return
140 self.__doc_popup.set_target(obj)
141 self.__doc_popup.popup()
143 def __insert_selected(self):
144 model, iter = self.__tree.get_selection().get_selected()
145 completion = model.get_value(iter, 1)
147 self.__view.get_buffer().insert_interactive_at_cursor(completion, True)
149 def __on_selection_changed(self, selection):
150 if not self.__in_change:
151 self.__update_doc_popup()
153 def popup(self):
154 """Pop up the completion popup.
156 If there are no possibilities completion at the insert cursor
157 location, the popup is not popped up. If there is exactly one
158 possibility, then completion is done immediately to that one
159 possibility.
163 if self.showing:
164 return
166 self.__update_completions()
167 if len(self.__tree_model) < 2:
168 if len(self.__tree_model) == 0:
169 return
170 self.__insert_selected()
171 return
173 self.__update_position()
175 self.show()
176 self.showing = True
178 self.__doc_popup.position_next_to_window(self)
179 self.__update_doc_popup()
181 # Send a synthetic focus in so that the TreeView thinks it is
182 # focused
183 focus_in = gtk.gdk.Event(gtk.gdk.FOCUS_CHANGE)
184 focus_in.window = self.window
185 focus_in.in_ = True
186 self.event(focus_in)
188 def update(self):
189 """Update the completion popup after the cursor is moved, or text is inserted.
191 If there are no completion possibilities at the cursor when this is called,
192 the popup is popped down.
196 if not self.showing:
197 return
199 self.__update_completions()
200 if len(self.__tree_model) == 0:
201 self.popdown()
202 return
204 self.__update_position()
206 def popdown(self):
207 """Hide the completion if it is currently showing"""
209 if not self.showing:
210 return
212 self.showing = False
214 if self.__doc_popup.showing:
215 self.__doc_popup.popdown()
217 self.hide()
219 def on_key_press_event(self, event):
220 """Do key press handling while the popup is active.
222 Returns True if the key press is handled, False otherwise.
226 if event.keyval == gtk.keysyms.Escape:
227 self.popdown()
228 return True
229 elif event.keyval in (gtk.keysyms.KP_Enter, gtk.keysyms.Return):
230 self.__insert_selected()
231 self.popdown()
232 return True
233 # These keys are forwarded to the popup to move the selected row
234 elif event.keyval in (gtk.keysyms.Up, gtk.keysyms.KP_Up,
235 gtk.keysyms.Down, gtk.keysyms.KP_Down,
236 gtk.keysyms.Page_Up, gtk.keysyms.KP_Page_Up,
237 gtk.keysyms.Page_Down, gtk.keysyms.KP_Page_Down):
238 self.event(event)
239 return True
241 return False