File: preferenceswindow.py

package info (click to toggle)
mypaint 2.0.1-14
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 27,884 kB
  • sloc: python: 43,893; cpp: 6,931; xml: 2,475; sh: 473; makefile: 25
file content (434 lines) | stat: -rw-r--r-- 16,213 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
# This file is part of MyPaint.
# Copyright (C) 2010-2018 by the MyPaint Development Team
# Copyright (C) 2008-2012 by Martin Renold <martinxyz@gmx.ch>
#
# 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.

"""Preferences dialog."""

from __future__ import division, print_function

import os.path
from logging import getLogger
from gettext import gettext as _

from lib.gibindings import Gtk

from gui.compatibility import CompatibilityPreferences
import lib.config
import lib.localecodes
from lib.i18n import USER_LOCALE_PREF
from lib.gettext import C_
from . import windowing


logger = getLogger(__name__)
RESPONSE_REVERT = 1


class PreferencesWindow (windowing.Dialog):
    """Window for manipulating preferences.
    """

    def __init__(self):
        import gui.application
        app = gui.application.get_app()
        assert app is not None

        super(PreferencesWindow, self).__init__(
            app=app,
            title=_('Preferences'),
            transient_for=app.drawWindow,
            destroy_with_parent=True,
        )
        self.add_buttons(
            Gtk.STOCK_REVERT_TO_SAVED, RESPONSE_REVERT,
            Gtk.STOCK_OK, Gtk.ResponseType.ACCEPT,
        )

        self.connect('response', self.on_response)

        self.in_update_ui = False

        # Set up widgets
        builder = Gtk.Builder()
        builder.set_translation_domain("mypaint")
        ui_dir = os.path.dirname(os.path.abspath(__file__))
        xml_path = os.path.join(ui_dir, 'preferenceswindow.glade')
        builder.add_from_file(xml_path)
        self._builder = builder

        getobj = builder.get_object
        self.compat_preferences = CompatibilityPreferences(app, builder)

        # Populate locale/language combo box
        locale_combo = getobj("locale_combobox")
        setup_locale_combobox(locale_combo)

        # Notebook
        nb = getobj("prefs_notebook")
        self.nb = nb
        self.vbox.pack_start(nb, True, True, 0)

        # Curve init
        curve = getobj("mapping_curve")
        curve.changed_cb = self.pressure_curve_changed_cb
        curve.magnetic = False
        self._pressure_curve = curve

        # Button mappings editor
        assert "input.button_mapping" in app.preferences
        reg = gui.mode.ModeRegistry
        actions_possible = [n for n in reg.get_action_names()
                            if issubclass(reg.get_mode_class(n),
                                          gui.mode.DragMode)]
        actions_possible += gui.mode.BUTTON_BINDING_ACTIONS
        bm_ed = getobj("button_mapping_editor")
        bm_ed.set_bindings(app.preferences["input.button_mapping"])
        bm_ed.set_actions(actions_possible)
        bm_ed.bindings_observers.append(self.button_mapping_edited_cb)

        # Autosave controls
        autosave_interval_spinbut = getobj("autosave_interval_spinbutton")
        self._autosave_interval_spinbutton = autosave_interval_spinbut

        # Signal hookup now everything is in the right initial state
        self._builder.connect_signals(self)

    def on_response(self, dialog, response, *args):
        if response == Gtk.ResponseType.ACCEPT:
            self.app.save_settings()
            self.app.apply_settings()
            self.hide()
        elif response == RESPONSE_REVERT:
            self.app.load_settings()
            self.app.apply_settings()

    def update_ui(self):
        """Update the preferences window to reflect the current settings.
        """
        if self.in_update_ui:
            return
        self.in_update_ui = True

        p = self.app.preferences
        getobj = self._builder.get_object

        # Pen input curve
        self._pressure_curve.points = p['input.global_pressure_mapping']

        # prefix for saving scarps
        entry = getobj("scrap_prefix_entry")
        entry.set_text(p['saving.scrap_prefix'])

        # Locale/language
        locale_combo = getobj("locale_combobox")
        active_locale = p.get(USER_LOCALE_PREF, None)
        if active_locale is None:
            locale_combo.set_active(0)
        else:
            locale_combo.set_active_id(active_locale)

        # Zoom
        zoom_float = p.get('view.default_zoom', 1.0)
        zoom_idcolstr = "%0.2f" % (zoom_float,)
        zoom_combo = getobj("default_zoom_combobox")
        zoom_combo.set_active_id(zoom_idcolstr)

        # Toolbar icon size radios
        size = str(p.get("ui.toolbar_icon_size", "large")).lower()
        for size_name in ["small", "large"]:
            if size_name != size:
                continue
            radio_name = "toolbar_icon_size_%s_radio" % (size,)
            radio = getobj(radio_name)
            radio.set_active(True)
            logger.debug("Set %r active", radio_name)
            break

        # Dark theme
        dark = bool(p.get("ui.dark_theme_variant", True))
        dark_checkbutton = getobj("dark_theme_checkbutton")
        dark_checkbutton.set_active(dark)

        # Smooth scrolling
        smoothsc = bool(p.get("ui.support_smooth_scrolling", True))
        smoothsc_checkbutton = getobj("smooth_scrolling_checkbutton")
        smoothsc_checkbutton.set_active(smoothsc)

        # Blink layers on selection/creation
        blink_layers = bool(p.get("ui.blink_layers", True))
        blink_layers_checkbutton = getobj("blink_layers_checkbutton")
        blink_layers_checkbutton.set_active(blink_layers)

        # Use real or faked alpha checks (faked is faster...)
        real_alpha_checks_checkbutton = getobj("real_alpha_checks_checkbutton")
        real_alpha_checks_checkbutton.set_active(p['view.real_alpha_checks'])

        # Hide cursor when painting
        hidecsr = bool(p.get("ui.hide_cursor_while_painting", False))
        hidecsr_checkbut = getobj("hide_cursor_while_painting_checkbutton")
        hidecsr_checkbut.set_active(hidecsr)

        # Default save format
        fmt_config = p['saving.default_format']
        fmt_combo = getobj("default_save_format_combobox")
        fmt_combo.set_active_id(fmt_config)

        # Display colorspace setting
        # Only affects loading and saving PNGs and ORAs currently,
        # so it's located on the Load & Save tab for now.
        disp_colorspace_setting = p["display.colorspace"]
        disp_colorspace_radiobtn = getobj(
            "display_colorspace_%s_radiobutton" % (disp_colorspace_setting,)
        )
        if disp_colorspace_radiobtn:
            disp_colorspace_radiobtn.set_active(True)

        # Button mapping
        bm_ed = getobj("button_mapping_editor")
        bm_ed.set_bindings(p.get("input.button_mapping", {}))

        # Input curve
        self._pressure_curve.queue_draw()

        # Cursor presets
        cursor_config = p.get("cursor.freehand.style", "thin")
        cursor_combo = getobj("freehand_cursor_combobox")
        cursor_combo.set_active_id(cursor_config)

        # Color wheel type
        cm = self.app.brush_color_manager

        wheel_radiobutton_name = "color_wheel_%s_radiobutton" \
            % (cm.get_wheel_type(),)
        wheel_radiobutton = getobj(wheel_radiobutton_name)
        if wheel_radiobutton:
            wheel_radiobutton.set_active(True)

        # Autosave
        autosave = bool(p["document.autosave_backups"])
        autosave_switch = getobj("autosave_backups_switch")
        autosave_switch.set_active(autosave)
        autosave_interval = int(p["document.autosave_interval"])
        autosave_interval_adj = getobj("autosave_interval_adjustment")
        autosave_interval_adj.set_value(autosave_interval)
        self._autosave_interval_spinbutton.set_sensitive(autosave)

        self.compat_preferences.update_ui()

        self.in_update_ui = False

    ## Callbacks for widgets that manipulate settings

    def input_mode_combobox_changed_cb(self, combobox):
        mode = combobox.get_active_id()
        self.app.preferences['input.device_mode'] = mode
        self.app.apply_settings()

    def button_mapping_edited_cb(self, editor):
        self.app.button_mapping.update(editor.bindings)

    def pressure_curve_changed_cb(self, widget):
        points = self._pressure_curve.points[:]
        self.app.preferences['input.global_pressure_mapping'] = points
        self.app.apply_settings()

    def scrap_prefix_entry_changed_cb(self, widget):
        scrap_prefix = widget.get_text()
        if isinstance(scrap_prefix, bytes):
            scrap_prefix = scrap_prefix.decode("utf-8")
        self.app.preferences['saving.scrap_prefix'] = scrap_prefix

    def default_zoom_combobox_changed_cb(self, combobox):
        zoom_idcolstr = combobox.get_active_id()
        zoom = float(zoom_idcolstr)
        self.app.preferences['view.default_zoom'] = zoom

    def toolbar_icon_size_small_toggled_cb(self, radio):
        if not radio.get_active():
            return
        self.app.preferences["ui.toolbar_icon_size"] = "small"

    def toolbar_icon_size_large_toggled_cb(self, radio):
        if not radio.get_active():
            return
        self.app.preferences["ui.toolbar_icon_size"] = "large"

    def dark_theme_toggled_cb(self, checkbut):
        dark = bool(checkbut.get_active())
        self.app.preferences["ui.dark_theme_variant"] = dark

    def real_alpha_checks_checkbutton_toggled_cb(self, button):
        real = bool(button.get_active())
        self.app.preferences['view.real_alpha_checks'] = real

    def default_save_format_combobox_changed_cb(self, combobox):
        formatstr = combobox.get_active_id()
        self.app.preferences['saving.default_format'] = formatstr

    def display_colorspace_unknown_radiobutton_toggled_cb(self, radiobtn):
        if self.in_update_ui or not radiobtn.get_active():
            return
        p = self.app.preferences
        p["display.colorspace"] = "unknown"

    def display_colorspace_srgb_radiobutton_toggled_cb(self, radiobtn):
        if self.in_update_ui or not radiobtn.get_active():
            return
        p = self.app.preferences
        p["display.colorspace"] = "srgb"

    def color_wheel_rgb_radiobutton_toggled_cb(self, radiobtn):
        if self.in_update_ui or not radiobtn.get_active():
            return
        cm = self.app.brush_color_manager
        cm.set_wheel_type("rgb")

    def color_wheel_ryb_radiobutton_toggled_cb(self, radiobtn):
        if self.in_update_ui or not radiobtn.get_active():
            return
        cm = self.app.brush_color_manager
        cm.set_wheel_type("ryb")

    def color_wheel_rygb_radiobutton_toggled_cb(self, radiobtn):
        if self.in_update_ui or not radiobtn.get_active():
            return
        cm = self.app.brush_color_manager
        cm.set_wheel_type("rygb")

    def freehand_cursor_combobox_changed_cb(self, combobox):
        cname = combobox.get_active_id()
        if self.in_update_ui:
            return
        p = self.app.preferences
        p["cursor.freehand.style"] = cname
        if cname == 'thin':
            # The default.
            p.pop("cursor.freehand.min_size", None)
            p.pop("cursor.freehand.outer_line_width", None)
            p.pop("cursor.freehand.inner_line_width", None)
            p.pop("cursor.freehand.inner_line_inset", None)
            p.pop("cursor.freehand.outer_line_color", None)
            p.pop("cursor.freehand.inner_line_color", None)
        elif cname == "medium":
            p["cursor.freehand.min_size"] = 5
            p["cursor.freehand.outer_line_width"] = 2.666
            p["cursor.freehand.inner_line_width"] = 1.333
            p["cursor.freehand.inner_line_inset"] = 2
            p["cursor.freehand.outer_line_color"] = (0, 0, 0, 1)
            p["cursor.freehand.inner_line_color"] = (1, 1, 1, 1)
        elif cname == "thick":
            p["cursor.freehand.min_size"] = 7
            p["cursor.freehand.outer_line_width"] = 3.75
            p["cursor.freehand.inner_line_width"] = 2.25
            p["cursor.freehand.inner_line_inset"] = 3
            p["cursor.freehand.outer_line_color"] = (0, 0, 0, 1)
            p["cursor.freehand.inner_line_color"] = (1, 1, 1, 1)

    def autosave_backups_switch_active_notify_cb(self, switch, param):
        active = bool(switch.props.active)
        self.app.preferences["document.autosave_backups"] = active
        self._autosave_interval_spinbutton.set_sensitive(active)

    def autosave_interval_adjustment_value_changed_cb(self, adj):
        interval = int(round(adj.get_value()))
        self.app.preferences["document.autosave_interval"] = interval

    def smooth_scrolling_toggled_cb(self, checkbut):
        smoothsc = bool(checkbut.get_active())
        self.app.preferences["ui.support_smooth_scrolling"] = smoothsc

    def _hide_cursor_while_painting_toggled_cb(self, checkbut):
        hide = bool(checkbut.get_active())
        self.app.preferences["ui.hide_cursor_while_painting"] = hide

    def blink_layers_toggled_cb(self, checkbut):
        blink = bool(checkbut.get_active())
        self.app.preferences["ui.blink_layers"] = blink

    def locale_changed_cb(self, combo):
        active = combo.get_active()
        locale = combo.get_model()[active][0]
        if locale is None:
            self.app.preferences.pop(USER_LOCALE_PREF, None)
        else:
            self.app.preferences[USER_LOCALE_PREF] = locale


def setup_locale_combobox(locale_combo):
    # Set up locales liststore
    locale_liststore = Gtk.ListStore()
    default_loc = C_(
        "Language preferences menu - default option",
        # TRANSLATORS: This option means that MyPaint will try to use
        # TRANSLATORS: the language the system tells it to use.
        "System Language"
    )
    # Default value - use the system locale
    locale_liststore.set_column_types((str, str, str))
    locale_liststore.append((None, default_loc, default_loc))

    loc_names = lib.localecodes.LOCALE_DICT
    # Base language - US english
    base_locale = "en_US"
    locale_liststore.append((base_locale, loc_names[base_locale][0], ""))

    # Separator
    locale_liststore.append((None, None, None))

    supported_locales = lib.config.supported_locales

    def tuplify(loc):
        if loc in loc_names:
            name_en, name_native = loc_names[loc]
            return loc, name_en, name_native
        else:
            logger.warning("Locale name not found: (%s)", loc)
            return loc, loc, loc

    # Sort alphabetically on english name of language
    for i in sorted(map(tuplify, supported_locales), key=lambda a: a[1]):
        locale_liststore.append(i)

    def sep_func(model, it):
        return model[it][1] is None

    def render_language_names(_, name_cell, model, it):
        locale, lang_en, lang_nat = model[it][:3]
        # Mark default with bold font
        if locale is None:
            name_cell.set_property(
                "markup", "<b>{lang_en}</b>".format(lang_en=lang_en)
            )
        # If a language does not have its native spelling
        # available, only show its name in english.
        elif lang_en == lang_nat or lang_nat == "":
            name_cell.set_property(
                "text", lang_en
            )
        else:
            name_cell.set_property(
                "text", C_(
                    "Prefs Dialog|View|Interface - menu entries",
                    # TRANSLATORS: lang_en is the english name of the language
                    # TRANSLATORS: lang_nat is the native name of the language
                    # TRANSLATORS: in that _same language_.
                    # TRANSLATORS: This can just be copied most of the time.
                    "{lang_en} - ({lang_nat})"
                ).format(
                    lang_en=lang_en, lang_nat=lang_nat
                )
            )

    # Remove the existing cell renderer
    locale_combo.clear()
    locale_combo.set_row_separator_func(sep_func)

    cell = Gtk.CellRendererText()
    locale_combo.pack_start(cell, True)
    locale_combo.set_cell_data_func(cell, render_language_names)
    locale_combo.set_model(locale_liststore)