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