1/* vim:set et sts=4 sw=4:
2 *
3 * ibus - The Input Bus
4 *
5 * Copyright(c) 2011-2015 Peng Huang <shawn.p.huang@gmail.com>
6 * Copyright(c) 2015-2019 Takao Fujiwara <takao.fujiwara1@gmail.com>
7 *
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2.1 of the License, or (at your option) any later version.
12 *
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 * Lesser General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
21 * USA
22 */
23
24class CandidateArea : Gtk.Box {
25    private bool m_vertical;
26    private Gtk.Label[] m_labels;
27    private Gtk.Label[] m_candidates;
28    private Gtk.Widget[] m_widgets;
29
30    private IBus.Text[] m_ibus_candidates;
31    private uint m_focus_candidate;
32    private bool m_show_cursor;
33    private ThemedRGBA m_rgba;
34
35    private Pango.Attribute m_language_attribute;
36
37    private const string LABELS[] = {
38        "1.", "2.", "3.", "4.", "5.", "6.", "7.", "8.",
39        "9.", "0.", "a.", "b.", "c.", "d.", "e.", "f."
40    };
41
42    private const string PREV_PAGE_ICONS[] = {
43        "go-previous",
44        "go-up"
45    };
46
47    private const string NEXT_PAGE_ICONS[] = {
48        "go-next",
49        "go-down"
50    };
51
52    public signal void candidate_clicked(uint index, uint button, uint state);
53    public signal void page_up();
54    public signal void page_down();
55    public signal void cursor_up();
56    public signal void cursor_down();
57
58    public CandidateArea(bool vertical) {
59        GLib.Object();
60        set_vertical(vertical, true);
61        m_rgba = new ThemedRGBA(this);
62    }
63
64    public bool candidate_scrolled(Gdk.EventScroll event) {
65        switch (event.direction) {
66        case Gdk.ScrollDirection.UP:
67            cursor_up();
68            break;
69        case Gdk.ScrollDirection.DOWN:
70            cursor_down();
71            break;
72        }
73        return true;
74    }
75
76    public bool get_vertical() {
77        return m_vertical;
78    }
79
80    public void set_vertical(bool vertical, bool force = false) {
81        if (!force && m_vertical == vertical)
82            return;
83        m_vertical = vertical;
84        orientation = vertical ?
85            Gtk.Orientation.VERTICAL :
86            Gtk.Orientation.HORIZONTAL;
87        recreate_ui();
88
89        if (m_ibus_candidates.length > 0) {
90            // Workaround a vala issue
91            // https://bugzilla.gnome.org/show_bug.cgi?id=661130
92            set_candidates((owned)m_ibus_candidates,
93                           m_focus_candidate,
94                           m_show_cursor);
95            show_all();
96        }
97    }
98
99    public void set_labels(IBus.Text[] labels) {
100        int i;
101        for (i = 0; i < int.min(16, labels.length); i++)
102            m_labels[i].set_text(labels[i].get_text());
103        for (; i < 16; i++)
104            m_labels[i].set_text(LABELS[i]);
105    }
106
107    public void set_language(Pango.Attribute language_attribute) {
108        m_language_attribute = language_attribute.copy();
109    }
110
111    public void set_candidates(IBus.Text[] candidates,
112                               uint focus_candidate = 0,
113                               bool show_cursor = true) {
114        m_ibus_candidates = candidates;
115        m_focus_candidate = focus_candidate;
116        m_show_cursor = show_cursor;
117
118        assert(candidates.length <= 16);
119        for (int i = 0 ; i < 16 ; i++) {
120            Gtk.Label label = m_candidates[i];
121            bool visible = false;
122            if (i < candidates.length) {
123                Pango.AttrList attrs = get_pango_attr_list_from_ibus_text(candidates[i]);
124                attrs.change(m_language_attribute.copy());
125                if (i == focus_candidate && show_cursor) {
126                    Pango.Attribute pango_attr = Pango.attr_foreground_new(
127                            (uint16)(m_rgba.selected_fg.red * uint16.MAX),
128                            (uint16)(m_rgba.selected_fg.green * uint16.MAX),
129                            (uint16)(m_rgba.selected_fg.blue * uint16.MAX));
130                    pango_attr.start_index = 0;
131                    pango_attr.end_index = candidates[i].get_text().length;
132                    attrs.insert((owned)pango_attr);
133
134                    pango_attr = Pango.attr_background_new(
135                           (uint16)(m_rgba.selected_bg.red * uint16.MAX),
136                           (uint16)(m_rgba.selected_bg.green * uint16.MAX),
137                           (uint16)(m_rgba.selected_bg.blue * uint16.MAX));
138                    pango_attr.start_index = 0;
139                    pango_attr.end_index = candidates[i].get_text().length;
140                    attrs.insert((owned)pango_attr);
141                }
142                label.set_text(candidates[i].get_text());
143                label.set_attributes(attrs);
144                visible = true;
145            } else {
146                label.set_text("");
147                label.set_attributes(new Pango.AttrList());
148            }
149            if (m_vertical) {
150                m_widgets[i * 2].set_visible(visible);
151                m_widgets[i * 2 +1].set_visible(visible);
152            } else {
153                m_widgets[i].set_visible(visible);
154            }
155        }
156    }
157
158    private void recreate_ui() {
159        foreach (Gtk.Widget w in get_children()) {
160            w.destroy();
161        }
162
163        Gtk.Button prev_button = new Gtk.Button();
164        prev_button.clicked.connect((b) => page_up());
165        prev_button.set_image(new Gtk.Image.from_icon_name(
166                                  PREV_PAGE_ICONS[orientation],
167                                  Gtk.IconSize.MENU));
168        prev_button.set_relief(Gtk.ReliefStyle.NONE);
169
170        Gtk.Button next_button = new Gtk.Button();
171        next_button.clicked.connect((b) => page_down());
172        next_button.set_image(new Gtk.Image.from_icon_name(
173                                  NEXT_PAGE_ICONS[orientation],
174                                  Gtk.IconSize.MENU));
175        next_button.set_relief(Gtk.ReliefStyle.NONE);
176
177        if (m_vertical) {
178            Gtk.EventBox container_ebox = new Gtk.EventBox();
179            container_ebox.add_events(Gdk.EventMask.SCROLL_MASK);
180            container_ebox.scroll_event.connect(candidate_scrolled);
181            add(container_ebox);
182
183            Gtk.Box vbox = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
184            container_ebox.add(vbox);
185
186            // Add Candidates
187            Gtk.Box candidates_hbox = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 0);
188            vbox.pack_start(candidates_hbox, false, false, 0);
189            Gtk.Box labels_vbox = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
190            labels_vbox.set_homogeneous(true);
191            Gtk.Box candidates_vbox = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
192            candidates_vbox.set_homogeneous(true);
193            candidates_hbox.pack_start(labels_vbox, false, false, 4);
194            candidates_hbox.pack_start(new VSeparator(), false, false, 0);
195            candidates_hbox.pack_start(candidates_vbox, true, true, 4);
196
197            // Add HSeparator
198            vbox.pack_start(new HSeparator(), false, false, 0);
199
200            // Add buttons
201            Gtk.Box buttons_hbox = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 0);
202            Gtk.Label state_label = new Gtk.Label(null);
203            state_label.set_size_request(20, -1);
204            buttons_hbox.pack_start(state_label, true, true, 0);
205            buttons_hbox.pack_start(prev_button, false, false, 0);
206            buttons_hbox.pack_start(next_button, false, false, 0);
207            vbox.pack_start(buttons_hbox, false, false, 0);
208
209            m_labels = {};
210            m_candidates = {};
211            m_widgets = {};
212            for (int i = 0; i < 16; i++) {
213                Gtk.Label label = new Gtk.Label(LABELS[i]);
214                label.set_halign(Gtk.Align.START);
215                label.set_valign(Gtk.Align.CENTER);
216                label.show();
217                m_labels += label;
218
219                Gtk.Label candidate = new Gtk.Label("test");
220                candidate.set_halign(Gtk.Align.START);
221                candidate.set_valign(Gtk.Align.CENTER);
222                candidate.show();
223                m_candidates += candidate;
224
225                label.set_margin_start (8);
226                label.set_margin_end (8);
227                candidate.set_margin_start (8);
228                candidate.set_margin_end (8);
229
230                // Make a copy of i to workaround a bug in vala.
231                // https://bugzilla.gnome.org/show_bug.cgi?id=628336
232                int index = i;
233                Gtk.EventBox label_ebox = new Gtk.EventBox();
234                label_ebox.set_no_show_all(true);
235                label_ebox.button_press_event.connect((w, e) => {
236                    candidate_clicked(i, e.button, e.state);
237                    return true;
238                });
239                label_ebox.add(label);
240                labels_vbox.pack_start(label_ebox, false, false, 2);
241                m_widgets += label_ebox;
242
243                Gtk.EventBox candidate_ebox = new Gtk.EventBox();
244                candidate_ebox.set_no_show_all(true);
245                candidate_ebox.button_press_event.connect((w, e) => {
246                    candidate_clicked(index, e.button, e.state);
247                    return true;
248                });
249                candidate_ebox.add(candidate);
250                candidates_vbox.pack_start(candidate_ebox, false, false, 2);
251                m_widgets += candidate_ebox;
252            }
253        } else {
254            Gtk.EventBox container_ebox = new Gtk.EventBox();
255            container_ebox.add_events(Gdk.EventMask.SCROLL_MASK);
256            container_ebox.scroll_event.connect(candidate_scrolled);
257            add(container_ebox);
258
259            Gtk.Box hbox = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 0);
260            container_ebox.add(hbox);
261
262            m_labels = {};
263            m_candidates = {};
264            m_widgets = {};
265            for (int i = 0; i < 16; i++) {
266                Gtk.Label label = new Gtk.Label(LABELS[i]);
267                label.set_halign(Gtk.Align.START);
268                label.set_valign(Gtk.Align.CENTER);
269                label.show();
270                m_labels += label;
271
272                Gtk.Label candidate = new Gtk.Label("test");
273                candidate.set_halign(Gtk.Align.START);
274                candidate.set_valign(Gtk.Align.CENTER);
275                candidate.show();
276                m_candidates += candidate;
277
278                Gtk.Box candidate_hbox = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 0);
279                candidate_hbox.show();
280                candidate_hbox.pack_start(label, false, false, 2);
281                candidate_hbox.pack_start(candidate, false, false, 2);
282
283                // Make a copy of i to workaround a bug in vala.
284                // https://bugzilla.gnome.org/show_bug.cgi?id=628336
285                int index = i;
286                Gtk.EventBox ebox = new Gtk.EventBox();
287                ebox.set_no_show_all(true);
288                ebox.button_press_event.connect((w, e) => {
289                    candidate_clicked(index, e.button, e.state);
290                    return true;
291                });
292                ebox.add(candidate_hbox);
293                hbox.pack_start(ebox, false, false, 4);
294                m_widgets += ebox;
295            }
296            hbox.pack_start(new VSeparator(), false, false, 0);
297            hbox.pack_start(prev_button, false, false, 0);
298            hbox.pack_start(next_button, false, false, 0);
299        }
300    }
301}
302
303