1# This file is part of MyPaint.
2# -*- coding: utf-8 -*-
3# Copyright (C) 2017 by the MyPaint Development Team
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation; either version 2 of the License, or
8# (at your option) any later version.
9
10"""Bulk management of layer visibility: presentation layer and view."""
11
12# Imports:
13
14from __future__ import print_function, division
15
16import gui.mvp
17import logging
18import gui.dialogs
19import gui.application
20import lib.layervis
21from lib.gettext import C_
22from lib.xml import escape
23
24from lib.gibindings import Gtk
25
26
27# Module vars:
28
29logger = logging.getLogger(__name__)
30
31
32# Class definitions:
33
34class LayerViewUI (gui.mvp.BuiltUIPresenter, object):
35    """Presents the document's named layer views in a row of controls."""
36
37    def __init__(self, docmodel):
38        super(LayerViewUI, self).__init__()
39        self._docmodel = docmodel
40        self._lvm = docmodel.layer_view_manager
41        self._app = gui.application.get_app()
42
43    def _init_model(self):
44        lvm = self._lvm
45        lvm.current_view_changed += self._current_view_changed_cb
46        lvm.view_names_changed += self._view_names_changed_cb
47
48    def init_view(self):
49        self._init_model()
50        store = Gtk.ListStore(str, str)   # columns: <our_id, display_markup>
51        markup = C_(
52            "view controls: dropdown: item markup",
53            u"<i>{builtin_view_name}</i>",
54        ).format(
55            builtin_view_name=escape(lib.layervis.UNSAVED_VIEW_DISPLAY_NAME),
56        )
57        # None has a special meaning for GTK combo ID columns,
58        # so we substitute the empty string. Corrolory: you can't name
59        # views to the empty string.
60        store.append([u"", markup])
61        store.set_sort_column_id(0, Gtk.SortType.ASCENDING)
62        self._store = store
63
64        combo = self.view.current_view_combo
65        combo.set_model(store)
66        combo.set_id_column(0)
67
68        cell = self.view.layer_text_cell
69        combo.add_attribute(cell, "markup", 1)
70
71        self._refresh_ui()
72
73    # MVP implementation:
74
75    @property
76    def widget(self):
77        return self.view.layout_grid
78
79    # View updater callbacks:
80
81    @gui.mvp.view_updater
82    def _current_view_changed_cb(self, lvm):
83        self._update_current_view_name()
84        self._update_buttons()
85
86    @gui.mvp.view_updater
87    def _view_names_changed_cb(self, lvm):
88        self._update_view_combo_names()
89
90    @gui.mvp.view_updater
91    def _refresh_ui(self):
92        self._update_view_combo_names()
93        self._update_current_view_name()
94        self._update_buttons()
95
96    @gui.mvp.view_updater
97    def _update_view_combo_names(self):
98        """Update the combo box to reflect a changed list names."""
99        model_names = set(self._lvm.view_names)
100        store = self._store
101
102        kept_names = set()
103        it = store.get_iter_first()
104        while it is not None:
105            name, = store.get(it, 0)
106            if isinstance(name, bytes):
107                name = name.decode("utf-8")
108            # Skip over our representation of the builtin
109            if name == u"":
110                it = store.iter_next(it)
111                continue
112            # Drop or keep
113            if name not in model_names:
114                it_still_valid = store.remove(it)  # advances it
115                if not it_still_valid:
116                    it = None
117            else:
118                it = store.iter_next(it)
119                kept_names.add(name)
120
121        for name in model_names:
122            if name not in kept_names:
123                markup = C_(
124                    "view controls: dropdown: item markup",
125                    u"{user_chosen_view_name}",
126                ).format(
127                    user_chosen_view_name=escape(name),
128                )
129                store.append([name, markup])
130
131    @gui.mvp.view_updater
132    def _update_buttons(self):
133        current = self._lvm.current_view_name
134        op_buttons = [
135            self.view.rename_button,
136            self.view.remove_button,
137            self.view.locked_button,
138        ]
139        for b in op_buttons:
140            b.set_sensitive(current is not None)
141
142        icon_name = "mypaint-object-unlocked-symbolic"
143        active = self._lvm.current_view_locked
144        if self._lvm.current_view_locked:
145            icon_name = "mypaint-object-locked-symbolic"
146        self.view.locked_image.set_from_icon_name(
147            icon_name,
148            Gtk.IconSize.BUTTON,
149        )
150        btn = self.view.locked_button
151        if bool(active) != bool(btn.get_active()):
152            btn.set_active(active)
153
154    @gui.mvp.view_updater
155    def _update_current_view_name(self):
156        combo = self.view.current_view_combo
157        id_ = self._lvm.current_view_name
158        if id_ is None:
159            id_ = ""
160        combo.set_active_id(id_)
161
162    # Model updaters:
163
164    @gui.mvp.model_updater
165    def _add_button_clicked_cb(self, button):
166        doc = self._docmodel
167        name = self._lvm.current_view_name
168        if name is None:
169            name = lib.layervis.NEW_VIEW_IDENT
170        cmd = lib.layervis.AddLayerView(doc, name=name)
171        doc.do(cmd)
172
173    @gui.mvp.model_updater
174    def _remove_button_clicked_cb(self, button):
175        doc = self._docmodel
176        cmd = lib.layervis.RemoveActiveLayerView(doc)
177        doc.do(cmd)
178
179    @gui.mvp.model_updater
180    def _rename_button_clicked_cb(self, button):
181        old_name = self._lvm.current_view_name
182        if old_name is None:
183            return
184        new_name = gui.dialogs.ask_for_name(
185            title=C_(
186                "view controls: rename view dialog",
187                u"Rename View",
188            ),
189            widget=self._app.drawWindow,
190            default=old_name,
191        )
192        if new_name is None:
193            return
194        new_name = new_name.strip()
195        if new_name == old_name:
196            return
197        doc = self._docmodel
198        cmd = lib.layervis.RenameActiveLayerView(doc, new_name)
199        doc.do(cmd)
200
201    @gui.mvp.model_updater
202    def _locked_button_toggled_cb(self, button):
203        if self._lvm.current_view_name is None:
204            return
205        new_locked = button.get_active()
206        doc = self._docmodel
207        cmd = lib.layervis.SetActiveLayerViewLocked(doc, locked=new_locked)
208        doc.do(cmd)
209
210    @gui.mvp.model_updater
211    def _current_view_combo_changed_cb(self, combo):
212        name = combo.get_active_id()
213        if name is None:
214            return  # specifically means "nothing active" in GTK
215        if name == "":
216            name = None
217        if name == self._lvm.current_view_name:
218            return  # nothing to do
219        doc = self._docmodel
220        cmd = lib.layervis.ActivateLayerView(doc, name)
221        doc.do(cmd)
222