Commit de138c65 authored by Jesse van den Kieboom's avatar Jesse van den Kieboom

[snippets] Make better use of new plugin architecture

parent e8a05b7d
......@@ -18,7 +18,8 @@ plugin_PYTHON = \
importer.py \
exporter.py \
languagemanager.py \
completion.py
completion.py \
signals.py
uidir = $(GEDIT_PLUGINS_DATA_DIR)/snippets/ui
ui_DATA = snippets.ui
......
......@@ -17,5 +17,6 @@
from appactivatable import AppActivatable
from windowactivatable import WindowActivatable
from document import Document
# ex:ts=8:et:
......@@ -25,25 +25,27 @@ from library import Library
from snippet import Snippet
from placeholder import *
import completion
from signals import Signals
from shareddata import SharedData
class DynamicSnippet(dict):
def __init__(self, text):
self['text'] = text
self.valid = True
class Document:
TAB_KEY_VAL = (Gdk.KEY_Tab, \
Gdk.KEY_ISO_Left_Tab)
class Document(GObject.Object, Gedit.ViewActivatable, Signals):
TAB_KEY_VAL = (Gdk.KEY_Tab, Gdk.KEY_ISO_Left_Tab)
SPACE_KEY_VAL = (Gdk.KEY_space,)
def __init__(self, instance, view):
self.view = None
self.instance = instance
view = GObject.property(type=Gedit.View)
def __init__(self):
GObject.Object.__init__(self)
Signals.__init__(self)
self.placeholders = []
self.active_snippets = []
self.active_placeholder = None
self.signal_ids = {}
self.ordered_placeholders = []
self.update_placeholders = []
......@@ -54,107 +56,58 @@ class Document:
self.provider = completion.Provider(_('Snippets'), self.language_id, self.on_proposal_activated)
self.defaults_provider = completion.Defaults(self.on_default_activated)
def do_activate(self):
# Always have a reference to the global snippets
Library().ref(None)
self.set_view(view)
# Stop controlling the view. Remove all active snippets, remove references
# to the view and the plugin instance, disconnect all signal handlers
def stop(self):
if self.timeout_update_id != 0:
GObject.source_remove(self.timeout_update_id)
self.timeout_update_id = 0
del self.update_placeholders[:]
del self.jump_placeholders[:]
# Always release the reference to the global snippets
Library().unref(None)
self.set_view(None)
self.instance = None
self.active_placeholder = None
def disconnect_signal(self, obj, signal):
if (obj, signal) in self.signal_ids:
obj.disconnect(self.signal_ids[(obj, signal)])
del self.signal_ids[(obj, signal)]
def connect_signal(self, obj, signal, cb):
self.disconnect_signal(obj, signal)
self.signal_ids[(obj, signal)] = obj.connect(signal, cb)
def connect_signal_after(self, obj, signal, cb):
self.disconnect_signal(obj, signal)
self.signal_ids[(obj, signal)] = obj.connect_after(signal, cb)
# Set the view to be controlled. Installs signal handlers and sets current
# language. If there is already a view set this function will first remove
# all currently active snippets and disconnect all current signals. So
# self.set_view(None) will effectively remove all the control from the
# current view
def _set_view(self, view):
if self.view:
buf = self.view.get_buffer()
# Remove signals
signals = {self.view: ('key-press-event', 'destroy',
'notify::editable', 'drag-data-received', 'expose-event'),
buf: ('notify::language', 'changed', 'cursor-moved', 'insert-text'),
self.view.get_completion(): ('hide',)}
for obj, sig in signals.items():
if obj:
for s in sig:
self.disconnect_signal(obj, s)
# Remove all active snippets
for snippet in list(self.active_snippets):
self.deactivate_snippet(snippet, True)
buf = self.view.get_buffer()
completion = self.view.get_completion()
self.connect_signal(self.view, 'key-press-event', self.on_view_key_press)
self.connect_signal(buf, 'notify::language', self.on_notify_language)
self.connect_signal(self.view, 'drag-data-received', self.on_drag_data_received)
if completion:
completion.remove_provider(self.provider)
completion.remove_provider(self.defaults_provider)
self.connect_signal_after(self.view, 'draw', self.on_draw)
self.view = view
self.update_language()
if view != None:
buf = view.get_buffer()
completion = self.view.get_completion()
self.connect_signal(view, 'destroy', self.on_view_destroy)
completion.add_provider(self.provider)
completion.add_provider(self.defaults_provider)
if view.get_editable():
self.connect_signal(view, 'key-press-event', self.on_view_key_press)
self.connect_signal(completion, 'hide', self.on_completion_hide)
self.connect_signal(buf, 'notify::language', self.on_notify_language)
self.connect_signal(view, 'notify::editable', self.on_notify_editable)
self.connect_signal(view, 'drag-data-received', self.on_drag_data_received)
self.connect_signal_after(view, 'draw', self.on_draw)
SharedData().register_controller(self.view, self)
self.update_language()
def do_deactivate(self):
if self.timeout_update_id != 0:
GObject.source_remove(self.timeout_update_id)
self.timeout_update_id = 0
completion = view.get_completion()
del self.update_placeholders[:]
del self.jump_placeholders[:]
completion.add_provider(self.provider)
completion.add_provider(self.defaults_provider)
# Always release the reference to the global snippets
Library().unref(None)
self.active_placeholder = None
self.connect_signal(completion, 'hide', self.on_completion_hide)
elif self.language_id != 0:
langid = self.language_id
self.disconnect_signals(self.view)
self.disconnect_signals(self.view.get_buffer())
self.language_id = None;
self.provider.language_id = self.language_id
# Remove all active snippets
for snippet in list(self.active_snippets):
self.deactivate_snippet(snippet, True)
if self.instance:
self.instance.language_changed(self)
completion = self.view.get_completion()
Library().unref(langid)
if completion:
completion.remove_provider(self.provider)
completion.remove_provider(self.defaults_provider)
def set_view(self, view):
if view == self.view:
return
if self.language_id != 0:
Library().unref(self.language_id)
self._set_view(view)
SharedData().unregister_controller(self.view, self)
# Call this whenever the language in the view changes. This makes sure that
# the correct language is used when finding snippets
......@@ -173,15 +126,14 @@ class Document:
else:
self.language_id = None
if self.instance:
self.instance.language_changed(self)
if langid != 0:
Library().unref(langid)
Library().ref(self.language_id)
self.provider.language_id = self.language_id
SharedData().update_state(self.view.get_toplevel())
def accelerator_activate(self, keyval, mod):
if not self.view or not self.view.get_editable():
return False
......@@ -190,8 +142,6 @@ class Document:
snippets = Library().from_accelerator(accelerator, \
self.language_id)
snippets_debug('Accel!')
if len(snippets) == 0:
return False
elif len(snippets) == 1:
......@@ -558,14 +508,12 @@ class Document:
cur = buf.get_iter_at_mark(buf.get_insert())
last = sn.end_iter()
# FIXME: get_iter_location doesnt work
curloc = self.view.get_iter_location(cur)
lastloc = self.view.get_iter_location(last)
#curloc = self.view.get_iter_location(cur)
#lastloc = self.view.get_iter_location(last)
#if (lastloc.y + lastloc.height) - curloc.y <= \
# self.view.get_visible_rect().height:
# self.view.scroll_mark_onscreen(sn.end_mark)
if (lastloc.y + lastloc.height) - curloc.y <= \
self.view.get_visible_rect().height:
self.view.scroll_mark_onscreen(sn.end_mark)
buf.end_user_action()
self.view.grab_focus()
......@@ -604,12 +552,18 @@ class Document:
return (word, start, end)
def parse_and_run_snippet(self, data, iter):
if not self.view.get_editable():
return
self.apply_snippet(DynamicSnippet(data), iter, iter)
def run_snippet_trigger(self, trigger, bounds):
if not self.view:
return False
if not self.view.get_editable():
return False
snippets = Library().from_tag(trigger, self.language_id)
buf = self.view.get_buffer()
......@@ -630,6 +584,9 @@ class Document:
if not self.view:
return False
if not self.view.get_editable():
return False
buf = self.view.get_buffer()
# get the word preceding the current insertion position
......@@ -702,11 +659,6 @@ class Document:
return False
# Callbacks
def on_view_destroy(self, view):
self.stop()
return
def on_buffer_cursor_moved(self, buf):
piter = buf.get_iter_at_mark(buf.get_insert())
......@@ -784,14 +736,14 @@ class Document:
def on_notify_language(self, buf, spec):
self.update_language()
def on_notify_editable(self, view, spec):
self._set_view(view)
def on_view_key_press(self, view, event):
library = Library()
state = event.get_state()
if not self.view.get_editable():
return False
if not (state & Gdk.ModifierType.CONTROL_MASK) and \
not (state & Gdk.ModifierType.MOD1_MASK) and \
event.keyval in self.TAB_KEY_VAL:
......@@ -909,6 +861,9 @@ class Document:
if not (Gtk.targets_include_uri(context.targets) and data.data and self.in_bounds(x, y)):
return
if not self.view.get_editable():
return
uris = drop_get_uris(data)
uris.reverse()
stop = False
......@@ -943,6 +898,9 @@ class Document:
self.provider.set_proposals(None)
def on_proposal_activated(self, proposal, piter):
if not self.view.get_editable():
return False
buf = self.view.get_buffer()
bounds = buf.get_selection_bounds()
......
......@@ -47,7 +47,6 @@ class Manager(Gtk.Dialog, Gtk.Buildable):
def __init__(self):
self.snippet = None
self._temp_export = None
self.snippets_doc = None
self.key_press_id = 0
......@@ -310,7 +309,6 @@ class Manager(Gtk.Dialog, Gtk.Buildable):
if lang:
source_view.get_buffer().set_highlight_syntax(True)
source_view.get_buffer().set_language(lang)
self.snippets_doc = Document(None, source_view)
combo = self['combo_drop_targets']
......@@ -588,9 +586,6 @@ class Manager(Gtk.Dialog, Gtk.Buildable):
shutil.rmtree(os.path.dirname(self._temp_export))
self._temp_export = None
if self.snippets_doc:
self.snippets_doc.stop()
self.unref_languages()
self.snippet = None
self.model = None
......
......@@ -26,6 +26,40 @@ class SharedData(object):
def __init__(self):
self.dlg = None
self.dlg_default_size = None
self.controller_registry = {}
self.windows = {}
def register_controller(self, view, controller):
self.controller_registry[view] = controller
def unregister_controller(self, view, controller):
if self.controller_registry[view] == controller:
del self.controller_registry[view]
def register_window(self, window):
self.windows[window.window] = window
def unregister_window(self, window):
if window.window in self.windows:
del self.windows[window.window]
def update_state(self, window):
if window in self.windows:
self.windows[window].do_update_state()
def get_active_controller(self, window):
view = window.get_active_view()
if not view or not view in self.controller_registry:
return None
return self.controller_registry[view]
def get_controller(self, view):
if view in self.controller_registry:
return self.controller_registry[view]
else:
return None
def manager_destroyed(self, dlg):
self.dlg_default_size = [dlg.get_allocation().width, dlg.get_allocation().height]
......@@ -42,9 +76,7 @@ class SharedData(object):
if self.dlg_default_size:
self.dlg.set_default_size(self.dlg_default_size[0], self.dlg_default_size[1])
if window:
self.dlg.set_transient_for(window)
self.dlg.set_transient_for(window)
self.dlg.present()
# vi:ex:ts=4:et
# -*- coding: utf-8 -*-
#
# signals.py
#
# Copyright (C) 2009 - Jesse van den Kieboom
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330,
# Boston, MA 02111-1307, USA.
class Signals:
def __init__(self):
self._signals = {}
def _connect(self, obj, name, handler, connector):
ret = self._signals.setdefault(obj, {})
hid = connector(name, handler)
ret.setdefault(name, []).append(hid)
return hid
def connect_signal(self, obj, name, handler):
return self._connect(obj, name, handler, obj.connect)
def connect_signal_after(self, obj, name, handler):
return self._connect(obj, name, handler, obj.connect_after)
def disconnect_signals(self, obj):
if obj not in self._signals:
return False
for name in self._signals[obj]:
for hid in self._signals[obj][name]:
obj.disconnect(hid)
del self._signals[obj]
return True
def block_signal(self, obj, name):
if obj not in self._signals:
return False
if name not in self._signals[obj]:
return False
for hid in self._signals[obj][name]:
obj.handler_block(hid)
return True
def unblock_signal(self, obj, name):
if obj not in self._signals:
return False
if name not in self._signals[obj]:
return False
for hid in self._signals[obj][name]:
obj.handler_unblock(hid)
return True
def disconnect_signal(self, obj, name):
if obj not in self._signals:
return False
if name not in self._signals[obj]:
return False
for hid in self._signals[obj][name]:
obj.disconnect(hid)
del self._signals[obj][name]
if len(self._signals[obj]) == 0:
del self._signals[obj]
return True
# ex:ts=4:et:
......@@ -24,6 +24,7 @@ from gi.repository import Gtk, Gdk, Gedit, GObject
from document import Document
from library import Library
from shareddata import SharedData
from signals import Signals
class Activate(Gedit.Message):
view = GObject.property(type=Gedit.View)
......@@ -31,14 +32,16 @@ class Activate(Gedit.Message):
# iter = GObject.property(type=Gtk.TextIter)
trigger = GObject.property(type=str)
class WindowActivatable(GObject.Object, Gedit.WindowActivatable):
class WindowActivatable(GObject.Object, Gedit.WindowActivatable, Signals):
__gtype_name__ = "GeditSnippetsWindowActivatable"
window = GObject.property(type=Gedit.Window)
def __init__(self):
self.current_controller = None
self.current_language = None
GObject.Object.__init__(self)
Signals.__init__(self)
self.current_language_accel_group = None
self.signal_ids = {}
def do_activate(self):
......@@ -53,15 +56,14 @@ class WindowActivatable(GObject.Object, Gedit.WindowActivatable):
if self.accel_group:
self.window.add_accel_group(self.accel_group)
self.window.connect('tab-added', self.on_tab_added)
# Add controllers to all the current views
for view in self.window.get_views():
if isinstance(view, Gedit.View) and not self.has_controller(view):
view._snippet_controller = Document(self, view)
self.connect_signal(self.window,
'active-tab-changed',
self.on_active_tab_changed)
self.do_update_state()
SharedData().register_window(self)
def do_deactivate(self):
if self.accel_group:
self.window.remove_accel_group(self.accel_group)
......@@ -71,26 +73,17 @@ class WindowActivatable(GObject.Object, Gedit.WindowActivatable):
self.remove_menu()
self.unregister_messages()
# Iterate over all the tabs and remove every controller
for view in self.window.get_views():
if isinstance(view, Gedit.View) and self.has_controller(view):
view._snippet_controller.stop()
view._snippet_controller = None
library = Library()
library.remove_accelerator_callback(self.accelerator_activated)
def do_update_state(self):
view = self.window.get_active_view()
self.disconnect_signals(self.window)
if not view or not self.has_controller(view):
return
SharedData().unregister_window(self)
controller = view._snippet_controller
def do_update_state(self):
controller = SharedData().get_active_controller(self.window)
if controller != self.current_controller:
self.current_controller = controller
self.update_language()
self.update_language(controller)
def register_messages(self):
bus = self.window.get_message_bus()
......@@ -111,7 +104,9 @@ class WindowActivatable(GObject.Object, Gedit.WindowActivatable):
if not view:
view = self.window.get_active_view()
if not self.has_controller(view):
controller = SharedData().get_controller(view)
if not controller:
return
# TODO: fix me as soon as the property fix lands in pygobject
......@@ -119,8 +114,6 @@ class WindowActivatable(GObject.Object, Gedit.WindowActivatable):
#if not iter:
iter = view.get_buffer().get_iter_at_mark(view.get_buffer().get_insert())
controller = view._snippet_controller
controller.run_snippet_trigger(message.props.trigger, (iter, iter))
def on_message_parse_and_activate(self, bus, message, userdata):
......@@ -129,15 +122,16 @@ class WindowActivatable(GObject.Object, Gedit.WindowActivatable):
if not view:
view = self.window.get_active_view()
if not self.has_controller(view):
return
iter = message.props.iter
controller = SharedData().get_controller(view)
if not iter:
iter = view.get_buffer().get_iter_at_mark(view.get_buffer().get_insert())
if not controller:
return
controller = view._snippet_controller
# TODO: fix me as soon as the property fix lands in pygobject
#iter = message.props.iter
#if not iter:
iter = view.get_buffer().get_iter_at_mark(view.get_buffer().get_insert())
controller.parse_and_run_snippet(message.snippet, iter)
def insert_menu(self):
......@@ -170,43 +164,33 @@ class WindowActivatable(GObject.Object, Gedit.WindowActivatable):
return result
def has_controller(self, view):
return hasattr(view, '_snippet_controller') and view._snippet_controller
def update_language(self):
def update_language(self, controller):
if not self.window:
return
if self.current_language:
accel_group = Library().get_accel_group( \
self.current_language)
self.window.remove_accel_group(accel_group)
if self.current_controller:
self.current_language = self.current_controller.language_id
if self.current_language != None:
accel_group = Library().get_accel_group( \
self.current_language)
self.window.add_accel_group(accel_group)
if controller:
langid = controller.language_id
else:
self.current_language = None
langid = None
def language_changed(self, controller):
if controller == self.current_controller:
self.update_language()
if langid != None:
accelgroup = Library().get_accel_group(langid)
else:
accelgroup = None
# Callbacks
if accelgroup != self.current_language_accel_group:
if self.current_language_accel_group:
self.window.remove_accel_group(self.current_language_accel_group)
def on_tab_added(self, window, tab):
# Create a new controller for this tab if it has a standard gedit view
view = tab.get_view()
if accelgroup:
self.window.add_accel_group(accelgroup)
if isinstance(view, Gedit.View) and not self.has_controller(view):
view._snippet_controller = Document(self, view)
self.current_language_accel_group = accelgroup
self.do_update_state()
def on_active_tab_changed(self, window, tab):
self.update_language(SharedData().get_controller(tab.get_view()))
# Callbacks
def create_configure_dialog(self):
SharedData().show_manager(self.window, self.plugin_info.get_data_dir())
......@@ -215,7 +199,10 @@ class WindowActivatable(GObject.Object, Gedit.WindowActivatable):
def accelerator_activated(self, group, obj, keyval, mod):
if obj == self.window:
return self.current_controller.accelerator_activate(keyval, mod)
controller = SharedData().get_active_controller(self.window)
if controller:
return controller.accelerator_activate(keyval, mod)
else:
return False
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment