1# -*- coding: utf-8 -*-
2#    Pluma External Tools plugin
3#    Copyright (C) 2005-2006  Steve Frécinaux <steve@istique.net>
4#    Copyright (C) 2012-2021 MATE Developers
5#
6#    This program is free software; you can redistribute it and/or modify
7#    it under the terms of the GNU General Public License as published by
8#    the Free Software Foundation; either version 2 of the License, or
9#    (at your option) any later version.
10#
11#    This program is distributed in the hope that it will be useful,
12#    but WITHOUT ANY WARRANTY; without even the implied warranty of
13#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14#    GNU General Public License for more details.
15#
16#    You should have received a copy of the GNU General Public License
17#    along with this program; if not, write to the Free Software
18#    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19
20__all__ = ('Manager', )
21
22import os.path
23import re
24from .library import *
25from .functions import *
26import hashlib
27from xml.sax import saxutils
28from gi.repository import GObject, Gio, Gdk, Gtk, GtkSource, Pluma
29
30class LanguagesPopup(Gtk.Popover):
31    __gtype_name__ = "LanguagesPopup"
32
33    COLUMN_NAME = 0
34    COLUMN_ID = 1
35    COLUMN_ENABLED = 2
36
37    def __init__(self, widget, languages):
38        Gtk.Popover.__init__(self, relative_to=widget)
39
40        self.props.can_focus = True
41
42        self.build()
43        self.init_languages(languages)
44
45        self.view.get_selection().select_path((0,))
46
47    def build(self):
48        self.model = Gtk.ListStore(str, str, bool)
49
50        self.sw = Gtk.ScrolledWindow()
51        self.sw.set_size_request(-1, 200)
52        self.sw.show()
53
54        self.sw.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
55        self.sw.set_shadow_type(Gtk.ShadowType.ETCHED_IN)
56
57        self.view = Gtk.TreeView(model=self.model)
58        self.view.show()
59
60        self.view.set_headers_visible(False)
61
62        column = Gtk.TreeViewColumn()
63
64        renderer = Gtk.CellRendererToggle()
65        column.pack_start(renderer, False)
66        column.add_attribute(renderer, 'active', self.COLUMN_ENABLED)
67
68        renderer.connect('toggled', self.on_language_toggled)
69
70        renderer = Gtk.CellRendererText()
71        column.pack_start(renderer, True)
72        column.add_attribute(renderer, 'text', self.COLUMN_NAME)
73
74        self.view.append_column(column)
75        self.view.set_row_separator_func(self.on_separator, None)
76
77        self.sw.add(self.view)
78
79        self.add(self.sw)
80
81    def enabled_languages(self, model, path, piter, ret):
82        enabled = model.get_value(piter, self.COLUMN_ENABLED)
83
84        if path.get_indices()[0] == 0 and enabled:
85            return True
86
87        if enabled:
88            ret.append(model.get_value(piter, self.COLUMN_ID))
89
90        return False
91
92    def languages(self):
93        ret = []
94
95        self.model.foreach(self.enabled_languages, ret)
96        return ret
97
98    def on_separator(self, model, piter, user_data=None):
99        val = model.get_value(piter, self.COLUMN_NAME)
100        return val == '-'
101
102    def init_languages(self, languages):
103        manager = GtkSource.LanguageManager()
104        langs = [manager.get_language(x) for x in manager.get_language_ids()]
105        langs.sort(key=lambda x: x.get_name())
106
107        self.model.append([_('All languages'), None, not languages])
108        self.model.append(['-', None, False])
109        self.model.append([_('Plain Text'), 'plain', 'plain' in languages])
110        self.model.append(['-', None, False])
111
112        for lang in langs:
113            self.model.append([lang.get_name(), lang.get_id(), lang.get_id() in languages])
114
115    def correct_all(self, model, path, piter, enabled):
116        if path.get_indices()[0] == 0:
117            return False
118
119        model.set_value(piter, self.COLUMN_ENABLED, enabled)
120
121    def on_language_toggled(self, renderer, path):
122        piter = self.model.get_iter(path)
123
124        enabled = self.model.get_value(piter, self.COLUMN_ENABLED)
125        self.model.set_value(piter, self.COLUMN_ENABLED, not enabled)
126
127        if path == '0':
128            self.model.foreach(self.correct_all, False)
129        else:
130            self.model.set_value(self.model.get_iter_first(), self.COLUMN_ENABLED, False)
131
132
133class Manager(GObject.Object):
134    TOOL_COLUMN = 0 # For Tree
135    NAME_COLUMN = 1 # For Combo
136
137    __gsignals__ = {
138        'tools-updated': (GObject.SignalFlags.RUN_LAST, None, ())
139    }
140
141    def __init__(self, datadir):
142        GObject.Object.__init__(self)
143        self.datadir = datadir
144        self.dialog = None
145        self._size = (0, 0)
146        self._languages = {}
147        self._tool_rows = {}
148
149        self.build()
150
151    def get_final_size(self):
152        return self._size
153
154    def build(self):
155        callbacks = {
156            'on_new_tool_button_clicked'      : self.on_new_tool_button_clicked,
157            'on_remove_tool_button_clicked'   : self.on_remove_tool_button_clicked,
158            'on_tool_manager_dialog_response' : self.on_tool_manager_dialog_response,
159            'on_tool_manager_dialog_configure_event': self.on_tool_manager_dialog_configure_event,
160            'on_tool_manager_dialog_focus_out': self.on_tool_manager_dialog_focus_out,
161            'on_accelerator_key_press'        : self.on_accelerator_key_press,
162            'on_accelerator_focus_in'         : self.on_accelerator_focus_in,
163            'on_accelerator_focus_out'        : self.on_accelerator_focus_out,
164            'on_languages_button_clicked'     : self.on_languages_button_clicked
165        }
166
167        # Load the "main-window" widget from the ui file.
168        self.ui = Gtk.Builder()
169        self.ui.add_from_file(os.path.join(self.datadir, 'ui', 'tools.ui'))
170        self.ui.connect_signals(callbacks)
171        self.dialog = self.ui.get_object('tool-manager-dialog')
172
173        self.view = self.ui.get_object('view')
174
175        self.__init_tools_model()
176        self.__init_tools_view()
177
178        for name in ['input', 'output', 'applicability', 'save-files']:
179            self.__init_combobox(name)
180
181        self.do_update()
182
183    def expand_from_doc(self, doc):
184        row = None
185
186        if doc:
187            if doc.get_language():
188                lid = doc.get_language().get_id()
189
190                if lid in self._languages:
191                    row = self._languages[lid]
192            elif 'plain' in self._languages:
193                row = self._languages['plain']
194
195        if not row and None in self._languages:
196            row = self._languages[None]
197
198        if not row:
199            return
200
201        self.view.expand_row(row.get_path(), False)
202        self.view.get_selection().select_path(row.get_path())
203
204    def run(self, window):
205        if self.dialog == None:
206            self.build()
207
208        # Open up language
209        self.expand_from_doc(window.get_active_document())
210
211        self.dialog.set_transient_for(window)
212        window.get_group().add_window(self.dialog)
213        self.dialog.present()
214
215    def add_accelerator(self, item):
216        if not item.shortcut:
217            return
218
219        if item.shortcut in self.accelerators:
220            if not item in self.accelerators[item.shortcut]:
221                self.accelerators[item.shortcut].append(item)
222        else:
223            self.accelerators[item.shortcut] = [item]
224
225    def remove_accelerator(self, item, shortcut=None):
226        if not shortcut:
227            shortcut = item.shortcut
228
229        if not shortcut in self.accelerators:
230            return
231
232        self.accelerators[shortcut].remove(item)
233
234        if not self.accelerators[shortcut]:
235            del self.accelerators[shortcut]
236
237    def add_tool_to_language(self, tool, language):
238        if isinstance(language, GtkSource.Language):
239            lid = language.get_id()
240        else:
241            lid = language
242
243        if not lid in self._languages:
244            piter = self.model.append(None, [language])
245
246            parent = Gtk.TreeRowReference.new(self.model, self.model.get_path(piter))
247            self._languages[lid] = parent
248        else:
249            parent = self._languages[lid]
250
251        piter = self.model.get_iter(parent.get_path())
252        child = self.model.append(piter, [tool])
253
254        if not tool in self._tool_rows:
255            self._tool_rows[tool] = []
256
257        self._tool_rows[tool].append(Gtk.TreeRowReference.new(self.model, self.model.get_path(child)))
258        return child
259
260    def add_tool(self, tool):
261        manager = GtkSource.LanguageManager()
262        ret = None
263
264        for lang in tool.languages:
265            l = manager.get_language(lang)
266
267            if l:
268                ret = self.add_tool_to_language(tool, l)
269            elif lang == 'plain':
270                ret = self.add_tool_to_language(tool, 'plain')
271
272        if not ret:
273            ret = self.add_tool_to_language(tool, None)
274
275        self.add_accelerator(tool)
276        return ret
277
278    def __init_tools_model(self):
279        self.tools = ToolLibrary()
280        self.current_node = None
281        self.script_hash = None
282        self.accelerators = dict()
283
284        self.model = Gtk.TreeStore(object)
285        self.view.set_model(self.model)
286
287        for tool in self.tools.tree.tools:
288            self.add_tool(tool)
289
290        self.model.set_default_sort_func(self.sort_tools)
291        self.model.set_sort_column_id(-1, Gtk.SortType.ASCENDING)
292
293    def sort_tools(self, model, iter1, iter2, user_data=None):
294        # For languages, sort All before everything else, otherwise alphabetical
295        t1 = model.get_value(iter1, self.TOOL_COLUMN)
296        t2 = model.get_value(iter2, self.TOOL_COLUMN)
297
298        if model.iter_parent(iter1) == None:
299            if t1 == None:
300                return -1
301
302            if t2 == None:
303                return 1
304
305            def lang_name(lang):
306                if isinstance(lang, GtkSource.Language):
307                    return lang.get_name()
308                else:
309                    return _('Plain Text')
310
311            n1 = lang_name(t1)
312            n2 = lang_name(t2)
313        else:
314            n1 = t1.name
315            n2 = t2.name
316
317        n1 = n1.lower()
318        n2 = n2.lower()
319        return (n1 > n2) - (n1 < n2)
320
321    def __init_tools_view(self):
322        # Tools column
323        column = Gtk.TreeViewColumn('Tools')
324        renderer = Gtk.CellRendererText()
325        column.pack_start(renderer, False)
326        renderer.set_property('editable', True)
327        self.view.append_column(column)
328
329        column.set_cell_data_func(renderer, self.get_cell_data_cb, None)
330
331        renderer.connect('edited', self.on_view_label_cell_edited)
332        renderer.connect('editing-started', self.on_view_label_cell_editing_started)
333
334        self.selection_changed_id = self.view.get_selection().connect('changed', self.on_view_selection_changed, None)
335
336    def __init_combobox(self, name):
337        combo = self[name]
338        combo.set_active(0)
339
340    # Convenience function to get an object from its name
341    def __getitem__(self, key):
342        return self.ui.get_object(key)
343
344    def set_active_by_name(self, combo_name, option_name):
345        combo = self[combo_name]
346        model = combo.get_model()
347        piter = model.get_iter_first()
348        while piter is not None:
349            if model.get_value(piter, self.NAME_COLUMN) == option_name:
350                combo.set_active_iter(piter)
351                return True
352            piter = model.iter_next(piter)
353        return False
354
355    def get_selected_tool(self):
356        model, piter = self.view.get_selection().get_selected()
357
358        if piter is not None:
359            tool = model.get_value(piter, self.TOOL_COLUMN)
360
361            if not isinstance(tool, Tool):
362                tool = None
363
364            return piter, tool
365        else:
366            return None, None
367
368    def compute_hash(self, stringofbytes):
369        return hashlib.md5(stringofbytes).hexdigest()
370
371    def save_current_tool(self):
372        if self.current_node is None:
373             return
374
375        if self.current_node.filename is None:
376            self.current_node.autoset_filename()
377
378        def combo_value(o, name):
379            combo = o[name]
380            return combo.get_model().get_value(combo.get_active_iter(), self.NAME_COLUMN)
381
382        self.current_node.input = combo_value(self, 'input')
383        self.current_node.output = combo_value(self, 'output')
384        self.current_node.applicability = combo_value(self, 'applicability')
385        self.current_node.save_files = combo_value(self, 'save-files')
386
387        buf = self['commands'].get_buffer()
388        (start, end) = buf.get_bounds()
389        script  = buf.get_text(start, end, False)
390        scriptbytes = script
391        if not isinstance(scriptbytes, bytes):
392            scriptbytes = scriptbytes.encode('utf-8')
393        h = self.compute_hash(scriptbytes)
394        if h != self.script_hash:
395            # script has changed -> save it
396            self.current_node.save_with_script([line + "\n" for line in script.splitlines()])
397            self.script_hash = h
398        else:
399            self.current_node.save()
400
401        self.update_remove_revert()
402
403    def clear_fields(self):
404        self['accelerator'].set_text('')
405
406        buf = self['commands'].get_buffer()
407        buf.begin_not_undoable_action()
408        buf.set_text('')
409        buf.end_not_undoable_action()
410
411        for nm in ('input', 'output', 'applicability', 'save-files'):
412            self[nm].set_active(0)
413
414        self['languages_label'].set_text(_('All Languages'))
415
416    def fill_languages_button(self):
417        if not self.current_node or not self.current_node.languages:
418            self['languages_label'].set_text(_('All Languages'))
419        else:
420            manager = GtkSource.LanguageManager()
421            langs = []
422
423            for lang in self.current_node.languages:
424                if lang == 'plain':
425                    langs.append(_('Plain Text'))
426                else:
427                    l = manager.get_language(lang)
428
429                    if l:
430                        langs.append(l.get_name())
431
432            self['languages_label'].set_text(', '.join(langs))
433
434    def fill_fields(self):
435        node = self.current_node
436        self['accelerator'].set_text(default(node.shortcut, ''))
437
438        buf = self['commands'].get_buffer()
439        script = default(''.join(node.get_script()), '')
440
441        buf.begin_not_undoable_action()
442        buf.set_text(script)
443        buf.end_not_undoable_action()
444
445        if not isinstance(script, bytes):
446            script = script.encode('utf-8')
447
448        self.script_hash = self.compute_hash(script)
449        contenttype, uncertain = Gio.content_type_guess(None, script)
450        lmanager = GtkSource.LanguageManager.get_default()
451        language = lmanager.guess_language(content_type=contenttype)
452
453        if language is not None:
454            buf.set_language(language)
455            buf.set_highlight_syntax(True)
456        else:
457            buf.set_highlight_syntax(False)
458
459        for nm in ('input', 'output', 'applicability', 'save-files'):
460            model = self[nm].get_model()
461            piter = model.get_iter_first()
462
463            self.set_active_by_name(nm,
464                                    default(node.__getattribute__(nm.replace('-', '_')),
465                                    model.get_value(piter, self.NAME_COLUMN)))
466
467        self.fill_languages_button()
468
469    def update_remove_revert(self):
470        piter, node = self.get_selected_tool()
471
472        removable = node is not None and node.is_local()
473
474        self['remove-tool-button'].set_sensitive(removable)
475        self['revert-tool-button'].set_sensitive(removable)
476
477        if node is not None and node.is_global():
478            self['remove-tool-button'].hide()
479            self['revert-tool-button'].show()
480        else:
481            self['remove-tool-button'].show()
482            self['revert-tool-button'].hide()
483
484    def do_update(self):
485        self.update_remove_revert()
486
487        piter, node = self.get_selected_tool()
488        self.current_node = node
489
490        if node is not None:
491            self.fill_fields()
492            self['tool-table'].set_sensitive(True)
493        else:
494            self.clear_fields()
495            self['tool-table'].set_sensitive(False)
496
497    def language_id_from_iter(self, piter):
498        if not piter:
499            return None
500
501        tool = self.model.get_value(piter, self.TOOL_COLUMN)
502
503        if isinstance(tool, Tool):
504            piter = self.model.iter_parent(piter)
505            tool = self.model.get_value(piter, self.TOOL_COLUMN)
506
507        if isinstance(tool, GtkSource.Language):
508            return tool.get_id()
509        elif tool:
510            return 'plain'
511
512        return None
513
514    def selected_language_id(self):
515        # Find current language if there is any
516        model, piter = self.view.get_selection().get_selected()
517
518        return self.language_id_from_iter(piter)
519
520    def on_new_tool_button_clicked(self, button):
521        self.save_current_tool()
522
523        # block handlers while inserting a new item
524        self.view.get_selection().handler_block(self.selection_changed_id)
525
526        self.current_node = Tool(self.tools.tree);
527        self.current_node.name = _('New tool')
528        self.tools.tree.tools.append(self.current_node)
529
530        lang = self.selected_language_id()
531
532        if lang:
533            self.current_node.languages = [lang]
534
535        piter = self.add_tool(self.current_node)
536
537        self.view.set_cursor(self.model.get_path(piter), self.view.get_column(self.TOOL_COLUMN), True)
538        self.fill_fields()
539
540        self['tool-table'].set_sensitive(True)
541        self.view.get_selection().handler_unblock(self.selection_changed_id)
542
543    def tool_changed(self, tool, refresh=False):
544        for row in self._tool_rows[tool]:
545            self.model.row_changed(row.get_path(), self.model.get_iter(row.get_path()))
546
547        if refresh and tool == self.current_node:
548            self.fill_fields()
549
550        self.update_remove_revert()
551
552    def on_remove_tool_button_clicked(self, button):
553        piter, node = self.get_selected_tool()
554
555        if not node:
556            return
557
558        if node.is_global():
559            shortcut = node.shortcut
560
561            if node.parent.revert_tool(node):
562                self.remove_accelerator(node, shortcut)
563                self.add_accelerator(node)
564
565                self['revert-tool-button'].set_sensitive(False)
566                self.fill_fields()
567
568                self.tool_changed(node)
569        else:
570            parent = self.model.iter_parent(piter)
571            language = self.language_id_from_iter(parent)
572
573            self.model.remove(piter)
574
575            if language in node.languages:
576                node.languages.remove(language)
577
578            self._tool_rows[node] = [x for x in self._tool_rows[node] if x.valid()]
579
580            if not self._tool_rows[node]:
581                del self._tool_rows[node]
582
583                if node.parent.delete_tool(node):
584                    self.remove_accelerator(node)
585                    self.current_node = None
586                    self.script_hash = None
587
588                    if self.model.iter_is_valid(piter):
589                        self.view.set_cursor(self.model.get_path(piter),
590                                             self.view.get_column(self.TOOL_COLUMN),
591                                             False)
592
593                self.view.grab_focus()
594
595            path = self._languages[language].get_path()
596            parent = self.model.get_iter(path)
597
598            if not self.model.iter_has_child(parent):
599                self.model.remove(parent)
600                del self._languages[language]
601
602    def on_view_label_cell_edited(self, cell, path, new_text):
603        if new_text != '':
604            piter = self.model.get_iter(path)
605            tool = self.model.get_value(piter, self.TOOL_COLUMN)
606
607            tool.name = new_text
608
609            self.save_current_tool()
610            self.tool_changed(tool)
611
612    def on_view_label_cell_editing_started(self, renderer, editable, path):
613            piter = self.model.get_iter(path)
614            tool = self.model.get_value(piter, self.TOOL_COLUMN)
615
616            if isinstance(editable, Gtk.Entry):
617                editable.set_text(tool.name)
618                editable.grab_focus()
619
620    def on_view_selection_changed(self, selection, userdata):
621        self.save_current_tool()
622        self.do_update()
623
624    def accelerator_collision(self, name, node):
625        if not name in self.accelerators:
626            return []
627
628        ret = []
629
630        for other in self.accelerators[name]:
631            if not other.languages or not node.languages:
632                ret.append(other)
633                continue
634
635            for lang in other.languages:
636                if lang in node.languages:
637                    ret.append(other)
638                    continue
639
640        return ret
641
642    def set_accelerator(self, keyval, mod):
643        # Check whether accelerator already exists
644        self.remove_accelerator(self.current_node)
645
646        name = Gtk.accelerator_name(keyval, mod)
647
648        if name == '':
649            self.current_node.shorcut = None
650            self.save_current_tool()
651            return True
652
653        col = self.accelerator_collision(name, self.current_node)
654
655        if col:
656            dialog = Gtk.MessageDialog(self.dialog,
657                                       Gtk.DialogFlags.MODAL,
658                                       Gtk.MessageType.ERROR,
659                                       Gtk.ButtonsType.CLOSE,
660                                       _('This accelerator is already bound to %s') % (', '.join(map(lambda x: x.name, col)),))
661
662            dialog.run()
663            dialog.destroy()
664
665            self.add_accelerator(self.current_node)
666            return False
667
668        self.current_node.shortcut = name
669        self.add_accelerator(self.current_node)
670        self.save_current_tool()
671
672        return True
673
674    def on_accelerator_key_press(self, entry, event):
675        mask = event.state & Gtk.accelerator_get_default_mod_mask()
676        keyname = Gdk.keyval_name(event.keyval)
677
678        if keyname == 'Escape':
679            entry.set_text(default(self.current_node.shortcut, ''))
680            self['commands'].grab_focus()
681            return True
682        elif keyname == 'Delete' or keyname == 'BackSpace':
683            entry.set_text('')
684            self.remove_accelerator(self.current_node)
685            self.current_node.shortcut = None
686            self['commands'].grab_focus()
687            return True
688        elif re.match('^F(:1[012]?|[2-9])$', keyname):
689            # New accelerator
690            if self.set_accelerator(event.keyval, mask):
691                entry.set_text(default(self.current_node.shortcut, ''))
692                self['commands'].grab_focus()
693
694            # Capture all `normal characters`
695            return True
696        elif Gdk.keyval_to_unicode(event.keyval):
697            if mask:
698                # New accelerator
699                if self.set_accelerator(event.keyval, mask):
700                    entry.set_text(default(self.current_node.shortcut, ''))
701                    self['commands'].grab_focus()
702            # Capture all `normal characters`
703            return True
704        else:
705            return False
706
707    def on_accelerator_focus_in(self, entry, event):
708        if self.current_node is None:
709            return
710        if self.current_node.shortcut:
711            entry.set_text(_('Type a new accelerator, or press Backspace to clear'))
712        else:
713            entry.set_text(_('Type a new accelerator'))
714
715    def on_accelerator_focus_out(self, entry, event):
716        if self.current_node is not None:
717            entry.set_text(default(self.current_node.shortcut, ''))
718            self.tool_changed(self.current_node)
719
720    def on_tool_manager_dialog_response(self, dialog, response):
721        if response == Gtk.ResponseType.HELP:
722            Pluma.help_display(self.dialog, 'pluma', 'pluma-external-tools-plugin')
723            return
724
725        self.on_tool_manager_dialog_focus_out(dialog, None)
726
727        self.dialog.destroy()
728        self.dialog = None
729        self.tools = None
730
731    def on_tool_manager_dialog_configure_event(self, dialog, event):
732        if dialog.get_realized():
733            alloc = dialog.get_allocation()
734            self._size = (alloc.width, alloc.height)
735
736    def on_tool_manager_dialog_focus_out(self, dialog, event):
737        self.save_current_tool()
738        self.emit('tools-updated')
739
740    def get_cell_data_cb(self, column, cell, model, piter, user_data=None):
741        tool = model.get_value(piter, self.TOOL_COLUMN)
742
743        if tool == None or not isinstance(tool, Tool):
744            if tool == None:
745                label = _('All Languages')
746            elif not isinstance(tool, GtkSource.Language):
747                label = _('Plain Text')
748            else:
749                label = tool.get_name()
750
751            markup = saxutils.escape(label)
752            editable = False
753        else:
754            escaped = saxutils.escape(tool.name)
755
756            if tool.shortcut:
757                markup = '%s (<b>%s</b>)' % (escaped, saxutils.escape(tool.shortcut))
758            else:
759                markup = escaped
760
761            editable = True
762
763        cell.set_properties(markup=markup, editable=editable)
764
765    def tool_in_language(self, tool, lang):
766        if not lang in self._languages:
767            return False
768
769        ref = self._languages[lang]
770        parent = ref.get_path()
771
772        for row in self._tool_rows[tool]:
773            path = row.get_path()
774
775            if path.get_indices()[0] == parent.get_indices()[0]:
776                return True
777
778        return False
779
780    def update_languages(self, popup):
781        self.current_node.languages = popup.languages()
782        self.fill_languages_button()
783
784        piter, node = self.get_selected_tool()
785        ret = None
786
787        if node:
788            ref = Gtk.TreeRowReference.new(self.model, self.model.get_path(piter))
789
790        # Update languages, make sure to inhibit selection change stuff
791        self.view.get_selection().handler_block(self.selection_changed_id)
792
793        # Remove all rows that are no longer
794        for row in list(self._tool_rows[self.current_node]):
795            piter = self.model.get_iter(row.get_path())
796            language = self.language_id_from_iter(piter)
797
798            if (not language and not self.current_node.languages) or \
799               (language in self.current_node.languages):
800                continue
801
802            # Remove from language
803            self.model.remove(piter)
804            self._tool_rows[self.current_node].remove(row)
805
806            # If language is empty, remove it
807            parent = self.model.get_iter(self._languages[language].get_path())
808
809            if not self.model.iter_has_child(parent):
810                self.model.remove(parent)
811                del self._languages[language]
812
813        # Now, add for any that are new
814        manager = GtkSource.LanguageManager()
815
816        for lang in self.current_node.languages:
817            if not self.tool_in_language(self.current_node, lang):
818                l = manager.get_language(lang)
819
820                if not l:
821                    l = 'plain'
822
823                self.add_tool_to_language(self.current_node, l)
824
825        if not self.current_node.languages and not self.tool_in_language(self.current_node, None):
826            self.add_tool_to_language(self.current_node, None)
827
828        # Check if we can still keep the current
829        if not ref or not ref.valid():
830            # Change selection to first language
831            path = self._tool_rows[self.current_node][0].get_path()
832            piter = self.model.get_iter(path)
833            parent = self.model.iter_parent(piter)
834
835            # Expand parent, select child and scroll to it
836            self.view.expand_row(self.model.get_path(parent), False)
837            self.view.get_selection().select_path(path)
838            self.view.set_cursor(path, self.view.get_column(self.TOOL_COLUMN), False)
839
840        self.view.get_selection().handler_unblock(self.selection_changed_id)
841
842    def on_languages_button_clicked(self, button):
843        popup = LanguagesPopup(button, self.current_node.languages)
844        popup.show()
845        popup.connect('closed', self.update_languages)
846
847# ex:et:ts=4:
848