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