1# Copyright (C) 2002-2009 Stephen Kennedy <stevek@gnome.org>
2# Copyright (C) 2010-2013 Kai Willadsen <kai.willadsen@gmail.com>
3#
4# This program is free software: you can redistribute it and/or modify
5# it under the terms of the GNU General Public License as published by
6# the Free Software Foundation, either version 2 of the License, or (at
7# your option) any later version.
8#
9# This program is distributed in the hope that it will be useful, but
10# WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12# General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
17from gi.repository import Gio
18from gi.repository import GLib
19from gi.repository import GObject
20from gi.repository import Gtk
21from gi.repository import GtkSource
22
23from meld.conf import _
24from meld.filters import FilterEntry
25from meld.settings import settings
26from meld.ui.gnomeglade import Component
27from meld.ui.listwidget import ListWidget
28
29
30class FilterList(ListWidget):
31
32    def __init__(self, key, filter_type):
33        default_entry = [_("label"), False, _("pattern"), True]
34        super().__init__(
35            "EditableList.ui", "list_vbox", ["EditableListStore"],
36            "EditableList", default_entry)
37        self.key = key
38        self.filter_type = filter_type
39
40        self.pattern_column.set_cell_data_func(
41            self.validity_renderer, self.valid_icon_celldata)
42
43        for filter_params in settings.get_value(self.key):
44            filt = FilterEntry.new_from_gsetting(filter_params, filter_type)
45            if filt is None:
46                continue
47            valid = filt.filter is not None
48            self.model.append(
49                [filt.label, filt.active, filt.filter_string, valid])
50
51        for signal in ('row-changed', 'row-deleted', 'row-inserted',
52                       'rows-reordered'):
53            self.model.connect(signal, self._update_filter_string)
54
55        self._update_sensitivity()
56
57    def valid_icon_celldata(self, col, cell, model, it, user_data=None):
58        is_valid = model.get_value(it, 3)
59        icon_name = "gtk-dialog-warning" if not is_valid else None
60        cell.set_property("stock-id", icon_name)
61
62    def on_name_edited(self, ren, path, text):
63        self.model[path][0] = text
64
65    def on_cellrenderertoggle_toggled(self, ren, path):
66        self.model[path][1] = not ren.get_active()
67
68    def on_pattern_edited(self, ren, path, text):
69        valid = FilterEntry.check_filter(text, self.filter_type)
70        self.model[path][2] = text
71        self.model[path][3] = valid
72
73    def _update_filter_string(self, *args):
74        value = [(row[0], row[1], row[2]) for row in self.model]
75        settings.set_value(self.key, GLib.Variant('a(sbs)', value))
76
77
78class ColumnList(ListWidget):
79
80    available_columns = {
81        "size": _("Size"),
82        "modification time": _("Modification time"),
83        "permissions": _("Permissions"),
84    }
85
86    def __init__(self, key):
87        super().__init__(
88            "EditableList.ui", "columns_ta", ["ColumnsListStore"],
89            "columns_treeview")
90        self.key = key
91
92        # Unwrap the variant
93        prefs_columns = [(k, v) for k, v in settings.get_value(self.key)]
94        column_vis = {}
95        column_order = {}
96        for sort_key, (column_name, visibility) in enumerate(prefs_columns):
97            column_vis[column_name] = bool(int(visibility))
98            column_order[column_name] = sort_key
99
100        columns = [
101            (column_vis.get(name, True), name, label)
102            for name, label in self.available_columns.items()
103        ]
104        columns = sorted(columns, key=lambda c: column_order.get(c[1], 0))
105
106        for visibility, name, label in columns:
107            self.model.append([visibility, name, label])
108
109        for signal in ('row-changed', 'row-deleted', 'row-inserted',
110                       'rows-reordered'):
111            self.model.connect(signal, self._update_columns)
112
113        self._update_sensitivity()
114
115    def on_cellrenderertoggle_toggled(self, ren, path):
116        self.model[path][0] = not ren.get_active()
117
118    def _update_columns(self, *args):
119        value = [(c[1].lower(), c[0]) for c in self.model]
120        settings.set_value(self.key, GLib.Variant('a(sb)', value))
121
122
123class GSettingsComboBox(Gtk.ComboBox):
124
125    def __init__(self):
126        super().__init__()
127        self.connect('notify::gsettings-value', self._setting_changed)
128        self.connect('notify::active', self._active_changed)
129
130    def bind_to(self, key):
131        settings.bind(
132            key, self, 'gsettings-value', Gio.SettingsBindFlags.DEFAULT)
133
134    def _setting_changed(self, obj, val):
135        column = self.get_property('gsettings-column')
136        value = self.get_property('gsettings-value')
137
138        for row in self.get_model():
139            if value == row[column]:
140                idx = row.path[0]
141                break
142        else:
143            idx = 0
144
145        if self.get_property('active') != idx:
146            self.set_property('active', idx)
147
148    def _active_changed(self, obj, val):
149        active_iter = self.get_active_iter()
150        if active_iter is None:
151            return
152        column = self.get_property('gsettings-column')
153        value = self.get_model()[active_iter][column]
154        self.set_property('gsettings-value', value)
155
156
157class GSettingsIntComboBox(GSettingsComboBox):
158
159    __gtype_name__ = "GSettingsIntComboBox"
160
161    gsettings_column = GObject.Property(type=int, default=0)
162    gsettings_value = GObject.Property(type=int)
163
164
165class GSettingsBoolComboBox(GSettingsComboBox):
166
167    __gtype_name__ = "GSettingsBoolComboBox"
168
169    gsettings_column = GObject.Property(type=int, default=0)
170    gsettings_value = GObject.Property(type=bool, default=False)
171
172
173class GSettingsStringComboBox(GSettingsComboBox):
174
175    __gtype_name__ = "GSettingsStringComboBox"
176
177    gsettings_column = GObject.Property(type=int, default=0)
178    gsettings_value = GObject.Property(type=str, default="")
179
180
181class PreferencesDialog(Component):
182
183    def __init__(self, parent):
184        super().__init__(
185            "preferences.ui", "preferencesdialog", [
186                "adjustment1", "adjustment2", "fileorderstore",
187                "sizegroup_editor", "timestampstore", "mergeorderstore",
188                "sizegroup_file_order_labels", "sizegroup_file_order_combos",
189                "syntaxschemestore"
190            ])
191        self.widget.set_transient_for(parent)
192
193        bindings = [
194            ('use-system-font', self.checkbutton_default_font, 'active'),
195            ('custom-font', self.fontpicker, 'font'),
196            ('indent-width', self.spinbutton_tabsize, 'value'),
197            ('insert-spaces-instead-of-tabs', self.checkbutton_spaces_instead_of_tabs, 'active'),  # noqa: E501
198            ('highlight-current-line', self.checkbutton_highlight_current_line, 'active'),  # noqa: E501
199            ('show-line-numbers', self.checkbutton_show_line_numbers, 'active'),  # noqa: E501
200            ('highlight-syntax', self.checkbutton_use_syntax_highlighting, 'active'),  # noqa: E501
201            ('use-system-editor', self.system_editor_checkbutton, 'active'),
202            ('custom-editor-command', self.custom_edit_command_entry, 'text'),
203            ('folder-shallow-comparison', self.checkbutton_shallow_compare, 'active'),  # noqa: E501
204            ('folder-filter-text', self.checkbutton_folder_filter_text, 'active'),  # noqa: E501
205            ('folder-ignore-symlinks', self.checkbutton_ignore_symlinks, 'active'),  # noqa: E501
206            ('vc-show-commit-margin', self.checkbutton_show_commit_margin, 'active'),  # noqa: E501
207            ('vc-commit-margin', self.spinbutton_commit_margin, 'value'),
208            ('vc-break-commit-message', self.checkbutton_break_commit_lines, 'active'),  # noqa: E501
209            ('ignore-blank-lines', self.checkbutton_ignore_blank_lines, 'active'),  # noqa: E501
210            # Sensitivity bindings must come after value bindings, or the key
211            # writability in gsettings overrides manual sensitivity setting.
212            ('vc-show-commit-margin', self.spinbutton_commit_margin, 'sensitive'),  # noqa: E501
213            ('vc-show-commit-margin', self.checkbutton_break_commit_lines, 'sensitive'),  # noqa: E501
214        ]
215        for key, obj, attribute in bindings:
216            settings.bind(key, obj, attribute, Gio.SettingsBindFlags.DEFAULT)
217
218        invert_bindings = [
219            ('use-system-editor', self.custom_edit_command_entry, 'sensitive'),
220            ('use-system-font', self.fontpicker, 'sensitive'),
221            ('folder-shallow-comparison', self.checkbutton_folder_filter_text, 'sensitive'),  # noqa: E501
222        ]
223        for key, obj, attribute in invert_bindings:
224            settings.bind(
225                key, obj, attribute, Gio.SettingsBindFlags.DEFAULT |
226                Gio.SettingsBindFlags.INVERT_BOOLEAN)
227
228        self.checkbutton_wrap_text.bind_property(
229            'active', self.checkbutton_wrap_word, 'sensitive',
230            GObject.BindingFlags.DEFAULT)
231
232        # TODO: Fix once bind_with_mapping is available
233        self.checkbutton_show_whitespace.set_active(
234            bool(settings.get_flags('draw-spaces')))
235
236        wrap_mode = settings.get_enum('wrap-mode')
237        self.checkbutton_wrap_text.set_active(wrap_mode != Gtk.WrapMode.NONE)
238        self.checkbutton_wrap_word.set_active(wrap_mode == Gtk.WrapMode.WORD)
239
240        filefilter = FilterList("filename-filters", FilterEntry.SHELL)
241        self.file_filters_vbox.pack_start(filefilter.widget, True, True, 0)
242
243        textfilter = FilterList("text-filters", FilterEntry.REGEX)
244        self.text_filters_vbox.pack_start(textfilter.widget, True, True, 0)
245
246        columnlist = ColumnList("folder-columns")
247        self.column_list_vbox.pack_start(columnlist.widget, True, True, 0)
248
249        self.combo_timestamp.bind_to('folder-time-resolution')
250        self.combo_file_order.bind_to('vc-left-is-local')
251        self.combo_merge_order.bind_to('vc-merge-file-order')
252
253        # Fill color schemes
254        manager = GtkSource.StyleSchemeManager.get_default()
255        for scheme_id in manager.get_scheme_ids():
256            scheme = manager.get_scheme(scheme_id)
257            self.syntaxschemestore.append([scheme_id, scheme.get_name()])
258        self.combobox_style_scheme.bind_to('style-scheme')
259
260        self.widget.show()
261
262    def on_checkbutton_wrap_text_toggled(self, button):
263        if not self.checkbutton_wrap_text.get_active():
264            wrap_mode = Gtk.WrapMode.NONE
265        elif self.checkbutton_wrap_word.get_active():
266            wrap_mode = Gtk.WrapMode.WORD
267        else:
268            wrap_mode = Gtk.WrapMode.CHAR
269        settings.set_enum('wrap-mode', wrap_mode)
270
271    def on_checkbutton_show_whitespace_toggled(self, widget):
272        value = GtkSource.DrawSpacesFlags.ALL if widget.get_active() else 0
273        settings.set_flags('draw-spaces', value)
274
275    def on_response(self, dialog, response_id):
276        self.widget.destroy()
277