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