1/* 2 * This file is part of gitg 3 * 4 * Copyright (C) 2013 - Jesse van den Kieboom 5 * 6 * gitg is free software; you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation; either version 2 of the License, or 9 * (at your option) any later version. 10 * 11 * gitg is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with gitg. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20namespace Gitg 21{ 22 23public enum SidebarHint 24{ 25 NONE, 26 HEADER, 27 SEPARATOR, 28 DUMMY 29} 30 31public enum SidebarColumn 32{ 33 HINT, 34 SECTION, 35 ITEM 36} 37 38public interface SidebarItem : Object 39{ 40 public abstract string text { owned get; } 41 public abstract string? icon_name { owned get; } 42 43 public signal void activated(int numclick); 44 45 public virtual void activate(int numclick) 46 { 47 activated(numclick); 48 } 49} 50 51public class SidebarStore : Gtk.TreeStore 52{ 53 private uint d_sections; 54 private SList<Gtk.TreeIter?> d_parents; 55 private bool d_clearing; 56 57 protected class SidebarText : Object, SidebarItem 58 { 59 private string d_text; 60 61 public SidebarText(string text) 62 { 63 d_text = text; 64 } 65 66 public string text 67 { 68 owned get { return d_text; } 69 } 70 71 public string? icon_name 72 { 73 owned get { return null; } 74 } 75 } 76 77 public class SidebarHeader : SidebarText 78 { 79 private uint d_id; 80 81 public uint id 82 { 83 get { return d_id; } 84 } 85 86 public SidebarHeader(string text, uint id) 87 { 88 base(text); 89 90 d_id = id; 91 } 92 } 93 94 private void append_real(SidebarItem item, 95 uint hint, 96 out Gtk.TreeIter iter) 97 { 98 if (d_parents != null) 99 { 100 base.append(out iter, d_parents.data); 101 } 102 else 103 { 104 base.append(out iter, null); 105 } 106 107 @set(iter, 108 SidebarColumn.ITEM, item, 109 SidebarColumn.HINT, hint, 110 SidebarColumn.SECTION, d_sections); 111 } 112 113 public SidebarStore append_dummy(string text) 114 { 115 Gtk.TreeIter iter; 116 append_real(new SidebarText(text), SidebarHint.DUMMY, out iter); 117 118 return this; 119 } 120 121 public new SidebarStore append(SidebarItem item) 122 { 123 Gtk.TreeIter iter; 124 append_real(item, SidebarHint.NONE, out iter); 125 126 return this; 127 } 128 129 public SidebarHeader begin_header(string text, uint id = 0) 130 { 131 Gtk.TreeIter iter; 132 133 var item = new SidebarHeader(text, id); 134 135 append_real(item, SidebarHint.HEADER, out iter); 136 d_parents.prepend(iter); 137 138 return item; 139 } 140 141 public SidebarStore end_header() 142 { 143 if (d_parents != null) 144 { 145 d_parents.delete_link(d_parents); 146 } 147 148 return this; 149 } 150 151 public uint begin_section() 152 { 153 d_parents = null; 154 return d_sections; 155 } 156 157 public void end_section() 158 { 159 ++d_sections; 160 } 161 162 public bool clearing 163 { 164 get { return d_clearing; } 165 } 166 167 public new void clear() 168 { 169 d_clearing = true; 170 base.clear(); 171 d_clearing = false; 172 173 d_sections = 0; 174 } 175 176 public SidebarItem item_for_iter(Gtk.TreeIter iter) 177 { 178 SidebarItem item; 179 180 @get(iter, SidebarColumn.ITEM, out item); 181 182 return item; 183 } 184 185 public void activate(Gtk.TreeIter iter, int numclick) 186 { 187 SidebarItem? item; 188 189 @get(iter, SidebarColumn.ITEM, out item); 190 191 if (item != null) 192 { 193 item.activate(numclick); 194 } 195 } 196} 197 198[GtkTemplate ( ui = "/org/gnome/gitg/ui/gitg-sidebar.ui" )] 199public class Sidebar : Gtk.TreeView 200{ 201 [GtkChild (name = "column")] 202 private Gtk.TreeViewColumn d_column; 203 204 [GtkChild (name = "renderer_icon")] 205 private Gtk.CellRendererPixbuf d_renderer_icon; 206 207 [GtkChild (name = "renderer_header")] 208 private Gtk.CellRendererText d_renderer_header; 209 210 [GtkChild (name = "renderer_text")] 211 private Gtk.CellRendererText d_renderer_text; 212 213 public signal void deselected(); 214 215 public signal void populate_popup(Gtk.Menu menu); 216 217 construct 218 { 219 d_column.set_cell_data_func(d_renderer_icon, (layout, cell, model, iter) => { 220 SidebarItem item; 221 model.get(iter, SidebarColumn.ITEM, out item); 222 223 cell.visible = (item.icon_name != null); 224 225 var r = (Gtk.CellRendererPixbuf)cell; 226 r.icon_name = item.icon_name; 227 }); 228 229 d_column.set_cell_data_func(d_renderer_header, (layout, cell, model, iter) => { 230 SidebarHint hint; 231 SidebarItem item; 232 233 model.get(iter, SidebarColumn.HINT, out hint, SidebarColumn.ITEM, out item); 234 235 cell.visible = (hint == SidebarHint.HEADER); 236 237 var r = (Gtk.CellRendererText)cell; 238 r.text = item.text; 239 }); 240 241 d_column.set_cell_data_func(d_renderer_text, (layout, cell, model, iter) => { 242 SidebarHint hint; 243 SidebarItem item; 244 245 model.get(iter, SidebarColumn.HINT, out hint, SidebarColumn.ITEM, out item); 246 247 cell.visible = (hint != SidebarHint.HEADER); 248 249 var r = (Gtk.CellRendererText)cell; 250 r.text = item.text; 251 252 if (hint == SidebarHint.DUMMY) 253 { 254 var context = get_style_context(); 255 256 context.save(); 257 context.set_state(Gtk.StateFlags.INSENSITIVE); 258 var col = context.get_color(context.get_state()); 259 context.restore(); 260 261 r.foreground_rgba = col; 262 } 263 else 264 { 265 r.foreground_set = false; 266 } 267 }); 268 269 set_row_separator_func((model, iter) => { 270 SidebarHint hint; 271 model.get(iter, SidebarColumn.HINT, out hint); 272 273 return hint == SidebarHint.SEPARATOR; 274 }); 275 276 var sel = get_selection(); 277 278 sel.set_select_function(select_function); 279 280 sel.changed.connect(selection_changed); 281 } 282 283 protected virtual bool select_function(Gtk.TreeSelection sel, 284 Gtk.TreeModel model, 285 Gtk.TreePath path, 286 bool cursel) 287 { 288 Gtk.TreeIter iter; 289 model.get_iter(out iter, path); 290 291 uint hint; 292 293 model.get(iter, SidebarColumn.HINT, out hint); 294 295 return hint != SidebarHint.HEADER && hint != SidebarHint.DUMMY; 296 } 297 298 protected virtual void selection_changed(Gtk.TreeSelection sel) 299 { 300 Gtk.TreeIter iter; 301 302 if (model.clearing) 303 { 304 return; 305 } 306 307 if (get_selected_iter(out iter)) 308 { 309 SidebarHint hint; 310 model.get(iter, SidebarColumn.HINT, out hint); 311 312 if (hint != SidebarHint.HEADER && hint != SidebarHint.DUMMY) 313 { 314 model.activate(iter, 1); 315 } 316 else 317 { 318 deselected(); 319 } 320 } 321 else 322 { 323 deselected(); 324 } 325 } 326 327 protected bool get_selected_iter(out Gtk.TreeIter iter) 328 { 329 var sel = get_selection(); 330 331 if (sel.count_selected_rows() == 1) 332 { 333 Gtk.TreeModel m; 334 335 var rows = sel.get_selected_rows(out m); 336 m.get_iter(out iter, rows.data); 337 338 return true; 339 } 340 else 341 { 342 iter = Gtk.TreeIter(); 343 } 344 345 return false; 346 } 347 348 public T? get_selected_item<T>() 349 { 350 Gtk.TreeIter iter; 351 352 if (get_selected_iter(out iter)) 353 { 354 return (T)model.item_for_iter(iter); 355 } 356 357 return null; 358 } 359 360 public T[] get_selected_items<T>() 361 { 362 var sel = get_selection(); 363 364 Gtk.TreeModel m; 365 Gtk.TreeIter iter; 366 367 var rows = sel.get_selected_rows(out m); 368 var ret = new T[0]; 369 370 foreach (var row in rows) 371 { 372 m.get_iter(out iter, row); 373 ret += (T)model.item_for_iter(iter); 374 } 375 376 return ret; 377 } 378 379 public void select(SidebarItem item) 380 { 381 model.foreach((m, path, iter) => { 382 if (model.item_for_iter(iter) == item) 383 { 384 get_selection().select_iter(iter); 385 return true; 386 } 387 388 return false; 389 }); 390 } 391 392 public bool is_selected(SidebarItem item) 393 { 394 bool retval = false; 395 396 model.foreach((m, path, iter) => { 397 if (model.item_for_iter(iter) == item) 398 { 399 retval = get_selection().iter_is_selected(iter); 400 return true; 401 } 402 403 return false; 404 }); 405 406 return retval; 407 } 408 409 protected override void row_activated(Gtk.TreePath path, Gtk.TreeViewColumn column) 410 { 411 if (model.clearing) 412 { 413 return; 414 } 415 416 Gtk.TreeIter iter; 417 418 if (model.get_iter(out iter, path)) 419 { 420 model.activate(iter, 2); 421 } 422 } 423 424 protected override bool key_press_event(Gdk.EventKey event) 425 { 426 if ((event.state & Gtk.accelerator_get_default_mod_mask()) != 0) 427 { 428 return base.key_press_event(event); 429 } 430 431 switch (event.keyval) { 432 case Gdk.Key.Return: 433 case Gdk.Key.ISO_Enter: 434 case Gdk.Key.KP_Enter: 435 case Gdk.Key.space: 436 case Gdk.Key.KP_Space: 437 Gtk.TreePath? path = null; 438 Gtk.TreeIter iter; 439 440 get_cursor(out path, null); 441 442 var sel = get_selection(); 443 444 if (path != null) 445 { 446 if (model.get_iter(out iter, path)) 447 { 448 if (sel.iter_is_selected(iter)) 449 { 450 model.activate(iter, 2); 451 } 452 else 453 { 454 sel.unselect_all(); 455 sel.select_iter(iter); 456 } 457 } 458 } 459 460 return true; 461 } 462 463 return base.key_press_event(event); 464 } 465 466 public new SidebarStore model 467 { 468 get { return base.get_model() as SidebarStore; } 469 set { base.set_model(value); } 470 } 471 472 private bool do_populate_popup(Gdk.EventButton? event) 473 { 474 Gtk.Menu menu = new Gtk.Menu(); 475 476 populate_popup(menu); 477 478 if (menu.get_children() == null) 479 { 480 return false; 481 } 482 483 menu.show_all(); 484 menu.attach_to_widget(this, null); 485 486 menu.popup_at_pointer(event); 487 return true; 488 } 489 490 protected override bool button_press_event(Gdk.EventButton event) 491 { 492 Gdk.Event *ev = (Gdk.Event *)event; 493 494 if (ev->triggers_context_menu()) 495 { 496 if (get_selection().count_selected_rows() <= 1) 497 { 498 base.button_press_event(event); 499 } 500 501 return do_populate_popup(event); 502 } 503 else 504 { 505 return base.button_press_event(event); 506 } 507 } 508 509 protected override bool popup_menu() 510 { 511 return do_populate_popup(null); 512 } 513} 514 515} 516 517// ex: ts=4 noet 518