1 # -*- coding: utf-8 -*-
2 '''An autocompletion plug-in for emesene. Currently it will autocomplete only
3 slash commands, but I'm planning to allow emoticons completion, too
15 class CompletionEngine
:
17 self
.complete_functions
= []
19 def add_complete_function(self
, function
):
20 self
.complete_functions
.append(function
)
22 def complete_word(self
, buffer):
24 for func
in self
.complete_functions
:
25 complete
.extend(func(buffer))
28 class CompletionWindow(gtk
.Window
):
29 """Window for displaying a list of completions."""
31 def __init__(self
, parent
, callback
):
32 gtk
.Window
.__init
__(self
, gtk
.WINDOW_TOPLEVEL
)
33 self
.set_decorated(False)
36 self
.completions
= None
37 self
.complete_callback
= callback
38 self
.set_transient_for(parent
)
39 self
.set_border_width(1)
40 self
.text
= gtk
.TextView()
41 self
.text_buffer
= gtk
.TextBuffer()
42 self
.text
.set_buffer(self
.text_buffer
)
43 self
.text
.set_size_request(300, 200)
44 self
.text
.set_sensitive(False)
48 self
.cb_ids
['focus-out'] = self
.connect('focus-out-event', self
.focus_out_event
)
49 self
.cb_ids
['key-press'] = self
.connect('key-press-event', self
.key_press_event
)
53 def key_press_event(self
, widget
, event
):
54 print 'pressed', event
.keyval
55 if event
.keyval
== gtk
.keysyms
.Escape
:
58 if event
.keyval
== gtk
.keysyms
.BackSpace
:
61 if event
.keyval
in (gtk
.keysyms
.Return
, gtk
.keysyms
.Right
):
64 if event
.keyval
== gtk
.keysyms
.Up
:
65 self
.select_previous()
67 if event
.keyval
== gtk
.keysyms
.Down
:
71 char
= gtk
.gdk
.keyval_to_unicode(event
.keyval
)
73 self
.complete_callback(chr(char
))
77 self
.complete_callback(self
.completions
[self
.get_selected()]['completion'])
79 def focus_out_event(self
, *args
):
82 def get_selected(self
):
83 """Get the selected row."""
85 selection
= self
.view
.get_selection()
86 return selection
.get_selected_rows()[1][0][0]
89 """Initialize the frame and scroller around the tree view."""
91 scroller
= gtk
.ScrolledWindow()
92 scroller
.set_policy(gtk
.POLICY_AUTOMATIC
, gtk
.POLICY_NEVER
)
93 scroller
.add(self
.view
)
95 frame
.set_shadow_type(gtk
.SHADOW_OUT
)
99 scroller_text
= gtk
.ScrolledWindow()
100 scroller_text
.set_policy(gtk
.POLICY_AUTOMATIC
, gtk
.POLICY_AUTOMATIC
)
101 scroller_text
.add(self
.text
)
102 hbox
.add(scroller_text
)
107 def init_tree_view(self
):
108 """Initialize the tree view listing the completions."""
110 self
.store
= gtk
.ListStore(gobject
.TYPE_STRING
)
111 self
.view
= gtk
.TreeView(self
.store
)
112 renderer
= gtk
.CellRendererText()
113 column
= gtk
.TreeViewColumn("", renderer
, text
=0)
114 self
.view
.append_column(column
)
115 self
.view
.set_enable_search(False)
116 self
.view
.set_headers_visible(False)
117 self
.view
.set_rules_hint(True)
118 selection
= self
.view
.get_selection()
119 selection
.set_mode(gtk
.SELECTION_SINGLE
)
120 self
.view
.set_size_request(200, 200)
121 self
.view
.connect('row-activated', self
.row_activated
)
124 def row_activated(self
, tree
, path
, view_column
, data
= None):
128 def select_next(self
):
129 """Select the next completion."""
131 row
= min(self
.get_selected() + 1, len(self
.store
) - 1)
132 selection
= self
.view
.get_selection()
133 selection
.unselect_all()
134 selection
.select_path(row
)
135 self
.view
.scroll_to_cell(row
)
136 self
.text_buffer
.set_text(self
.completions
[self
.get_selected()]['info'])
138 def select_previous(self
):
139 """Select the previous completion."""
141 row
= max(self
.get_selected() - 1, 0)
142 selection
= self
.view
.get_selection()
143 selection
.unselect_all()
144 selection
.select_path(row
)
145 self
.view
.scroll_to_cell(row
)
146 self
.text_buffer
.set_text(self
.completions
[self
.get_selected()]['info'])
148 def set_completions(self
, completions
):
149 """Set the completions to display."""
151 self
.completions
= completions
152 self
.completions
.reverse()
155 for completion
in completions
:
156 self
.store
.append([unicode(completion
['abbr'])])
157 self
.view
.columns_autosize()
158 self
.view
.get_selection().select_path(0)
159 self
.text_buffer
.set_text(self
.completions
[self
.get_selected()]['info'])
161 def set_font_description(self
, font_desc
):
162 """Set the label's font description."""
164 self
.view
.modify_font(font_desc
)
167 '''This class provides completion feature for ONE conversation'''
168 def __init__(self
, engine
, view
, window
):
172 self
.popup
= CompletionWindow(None, self
.complete
)
174 self
.key_press_id
= view
.connect("key-press-event", self
.on_key_pressed
)
176 def disconnect(self
):
177 self
.view
.disconnect(self
.key_press_id
)
179 def complete(self
, completion
):
180 """Complete the current word."""
182 doc
= self
.view
.get_buffer()
183 doc
.insert_at_cursor(completion
)
190 def hide_popup(self
):
191 """Hide the completion window."""
194 self
.completes
= None
196 def show_popup(self
, completions
, x
, y
):
197 """Show the completion window."""
199 root_x
, root_y
= self
.window
.get_position()
200 self
.popup
.move(root_x
+ x
+ 24, root_y
+ y
+ 44)
201 self
.popup
.set_completions(completions
)
202 self
.popup
.show_all()
204 def display_completions(self
, view
, event
):
205 doc
= view
.get_buffer()
206 insert
= doc
.get_iter_at_mark(doc
.get_insert())
208 window
= gtk
.TEXT_WINDOW_TEXT
209 rect
= view
.get_iter_location(insert
)
210 x
, y
= view
.buffer_to_window_coords(window
, rect
.x
, rect
.y
)
211 x
, y
= view
.translate_coordinates(self
.window
, x
, y
)
212 completes
= self
.engine
.complete_word(doc
)
214 self
.show_popup(completes
, x
, y
)
219 def on_key_pressed(self
, view
, event
):
220 if event
.keyval
== gtk
.keysyms
.Tab
:
221 #if event.state & gtk.gdk.CONTROL_MASK and event.state & gtk.gdk.MOD1_MASK and event.keyval == gtk.keysyms.Enter:
222 return self
.display_completions(view
, event
)
223 if event
.state
& gtk
.gdk
.CONTROL_MASK
:
225 if event
.state
& gtk
.gdk
.MOD1_MASK
:
231 class MainClass( Plugin
.Plugin
):
232 '''Main plugin class'''
233 def __init__(self
, controller
, msn
):
235 Plugin
.Plugin
.__init
__( self
, controller
, msn
)
236 self
.description
= _('Autocompletion for slash commands in the inputBox')
237 self
.authors
= { 'BoySka' : 'boyska gmail com' }
238 self
.website
= 'http://emesene.org'
239 self
.displayName
= _('Autocomplete')
240 self
.name
= 'Completion'
243 self
.controller
= controller
249 self
.new_conv_id
= None
250 self
.close_conv_id
= None
252 self
.config
= controller
.config
253 self
.config
.readPluginConfig(self
.name
)
258 '''start the plugin'''
259 conv_manager
= self
.controller
.conversationManager
260 self
.new_conv_id
= conv_manager
.connect(
261 'new-conversation-ui', self
.on_new_conversation
)
262 self
.close_conv_id
= conv_manager
.connect(
263 'close-conversation-ui', self
.on_close_conversation
)
265 self
.engine
= CompletionEngine()
267 for conv
in self
.getOpenConversations():
268 self
.completers
[conv
] = Completer(
271 conv
.parentConversationWindow
)
273 self
.engine
.add_complete_function(self
.slash_complete
)
274 #we can add multiple complete functions
275 #self.engine.add_complete_function(self.simple_complete)
279 '''stop the plugin'''
280 for conversation
in self
.completers
.keys():
281 self
.completers
[conversation
].disconnect()
282 conv_manager
= self
.controller
.conversationManager
283 conv_manager
.disconnect(self
.new_conv_id
)
284 conv_manager
.disconnect(self
.close_conv_id
)
288 '''check if the plugin can be enabled'''
289 return ( True, 'Ok' )
291 def on_new_conversation(self
, conversationManager
, conversation
, window
):
292 self
.completers
[conversation
] = Completer(
294 conversation
.ui
.input.input,
295 conversation
.parentConversationWindow
)
297 def on_close_conversation(self
, conversationManager
, conversation
, window
):
298 self
.completers
[conversation
].disconnect()
299 del self
.completers
[conversation
]
301 def slash_complete(self
, buffer):
303 c
= buffer.get_start_iter().get_char()
306 start
= buffer.get_iter_at_mark(buffer.get_insert()).copy()
307 while start
.backward_char():
308 char
= unicode(start
.get_char())
309 if char
in [' ', '\n']:
312 incomplete
= _get_last_word(buffer) #exclude the slash
314 commands
= self
.controller
.Slash
.commands
315 for x
in commands
.keys():
316 if x
.startswith(incomplete
):
317 complete
.append({'completion':x
[len(incomplete
):],
319 'info':commands
[x
][1] or 'no description'})
324 def simple_complete(self
, buffer):
325 '''just a test function'''
326 words
= ['foobar', 'peloponneso', 'pelonelluovo', 'pelota', 'pescasseroli']
327 incomplete
= _get_last_word(buffer)
330 if x
.startswith(incomplete
):
331 complete
.append({'completion':x
[len(incomplete
):], 'abbr':x
, 'info':'simple'})
334 def _get_last_word(buffer):
335 re_alpha
= re
.compile(r
"\w+", re
.UNICODE | re
.MULTILINE
)
336 insert
= buffer.get_iter_at_mark(buffer.get_insert())
337 start
= insert
.copy()
339 while start
.backward_char():
340 char
= unicode(start
.get_char())
341 if not re_alpha
.match(char
) and not char
== ".":
344 incomplete
= unicode(buffer.get_text(start
, insert
))
345 #incomplete += unicode(event.string)