2
// TemplatePickerWidget.cs
5
// Michael Hutchinson <mhutchinson@novell.com>
7
// Copyright (c) 2011 Novell, Inc.
9
// Permission is hereby granted, free of charge, to any person obtaining a copy
10
// of this software and associated documentation files (the "Software"), to deal
11
// in the Software without restriction, including without limitation the rights
12
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
// copies of the Software, and to permit persons to whom the Software is
14
// furnished to do so, subject to the following conditions:
16
// The above copyright notice and this permission notice shall be included in
17
// all copies or substantial portions of the Software.
19
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
28
using System.Collections.Generic;
31
using MonoDevelop.Core;
32
using MonoDevelop.Components;
33
using MonoDevelop.Ide.Templates;
35
using MonoDevelop.Ide.Gui.Components;
37
namespace MonoDevelop.Ide.Projects
39
class TemplatePickerWidget : Bin
41
SectionList sectionList = new SectionList ();
42
SectionList.Section recentSection, installedSection, onlineSection;
44
CategoryTreeView recentTemplateCatView = new CategoryTreeView ();
45
CategoryTreeView installedTemplateCatView = new CategoryTreeView ();
46
CategoryTreeView onlineTemplateCatView = new CategoryTreeView ();
48
List<TemplateItem> recentTemplates = new List<TemplateItem> ();
49
List<TemplateItem> installedTemplates = new List<TemplateItem> ();
50
List<TemplateItem> onlineTemplates = new List<TemplateItem> ();
52
HPaned hsplit = new HPaned ();
53
VPaned vsplit = new VPaned ();
54
VBox rightVbox = new VBox ();
55
HBox searchHbox = new HBox ();
57
CompactScrolledWindow infoScrolledWindow = new CompactScrolledWindow ();
58
VBox infoBox = new VBox ();
59
Label infoHeaderLabel = new Label ();
60
Label infoDecriptionLabel = new Label ();
62
SearchEntry searchEntry = new SearchEntry ();
64
TemplateView templateView = new TemplateView ();
66
public TemplatePickerWidget ()
68
Stetic.BinContainer.Attach (this);
70
infoBox.BorderWidth = 4;
72
infoHeaderLabel.Wrap = true;
73
infoHeaderLabel.Xalign = 0;
74
infoDecriptionLabel.Wrap = true;
75
infoDecriptionLabel.Xalign = 0;
76
infoDecriptionLabel.Yalign = 0;
78
infoBox.SizeAllocated += delegate {
79
var w = infoBox.Allocation.Width - 10;
80
if (infoHeaderLabel.WidthRequest != w) {
81
infoHeaderLabel.WidthRequest = w;
82
infoDecriptionLabel.WidthRequest = w;
86
recentSection = sectionList.AddSection (GettextCatalog.GetString ("Recent Templates"), recentTemplateCatView);
87
installedSection = sectionList.AddSection (GettextCatalog.GetString ("Installed Templates"), installedTemplateCatView);
88
onlineSection = sectionList.AddSection (GettextCatalog.GetString ("Online Templates"), onlineTemplateCatView);
90
recentSection.Activated += delegate {
91
LoadTemplatesIntoView (recentTemplates);
92
templateView.SetCategoryFilter (recentTemplateCatView.GetSelection ());
95
installedSection.Activated += delegate {
96
LoadTemplatesIntoView (installedTemplates);
97
templateView.SetCategoryFilter (installedTemplateCatView.GetSelection ());
100
onlineSection.Activated += delegate {
101
LoadTemplatesIntoView (onlineTemplates);
102
templateView.SetCategoryFilter (onlineTemplateCatView.GetSelection ());
105
recentTemplateCatView.SelectionChanged += delegate {
106
if (recentSection.IsActive)
107
templateView.SetCategoryFilter (recentTemplateCatView.GetSelection ());
110
installedTemplateCatView.SelectionChanged += delegate {
111
if (installedSection.IsActive)
112
templateView.SetCategoryFilter (installedTemplateCatView.GetSelection ());
115
onlineTemplateCatView.SelectionChanged += delegate {
116
if (onlineSection.IsActive)
117
templateView.SetCategoryFilter (onlineTemplateCatView.GetSelection ());
120
searchEntry.WidthRequest = 150;
121
searchEntry.EmptyMessage = GettextCatalog.GetString ("Search...");
122
searchEntry.Changed += delegate {
123
templateView.SetSearchFilter (searchEntry.Entry.Text);
125
searchEntry.Activated += delegate {
126
templateView.Child.GrabFocus ();
128
searchEntry.Ready = true;
131
installedTemplateCatView.SelectionChanged += delegate (object sender, EventArgs e) {
132
var selection = installedTemplateCatView.GetSelection ();
133
templateView.SetCategoryFilter (selection);
136
templateView.SelectionChanged += TemplateSelectionChanged;
137
templateView.DoubleClicked += delegate {
142
hsplit.Pack1 (sectionList, true, false);
143
hsplit.Pack2 (rightVbox, true, false);
144
rightVbox.PackStart (searchHbox, false, false, 0);
145
rightVbox.Spacing = 6;
146
searchHbox.PackStart (new Label (), true, true, 0);
147
searchHbox.PackStart (searchEntry, false, false, 0);
148
rightVbox.PackStart (vsplit, true, true, 0);
149
vsplit.Pack1 (templateView, true, false);
150
vsplit.Pack2 (infoScrolledWindow, true, false);
151
infoScrolledWindow.ShowBorderLine = true;
152
var vp = new Viewport ();
153
vp.ShadowType = ShadowType.None;
155
infoScrolledWindow.Add (vp);
156
infoBox.PackStart (infoHeaderLabel, false, false, 0);
157
infoBox.PackStart (infoDecriptionLabel, true, true, 0);
160
//sane proportions for the splitter children
161
templateView.HeightRequest = 200;
162
infoScrolledWindow.HeightRequest = 75;
163
sectionList.WidthRequest = 150;
164
rightVbox.WidthRequest = 300;
166
sectionList.ActiveIndex = 1;
169
void TemplateSelectionChanged (object sender, EventArgs e)
171
var ti = templateView.CurrentlySelected;
173
infoHeaderLabel.Markup = "";
174
infoDecriptionLabel.Text = "";
176
infoHeaderLabel.Markup = "<b>" + GLib.Markup.EscapeText (ti.Name) + "</b>";
177
infoDecriptionLabel.Text = StringParserService.Parse (ti.Template.Description);
179
OnSelectionChanged ();
182
void OnSelectionChanged ()
184
var evt = SelectionChanged;
196
public ProjectTemplate Selection {
198
var sel = templateView.CurrentlySelected;
205
public void SelectTemplate (string id)
207
templateView.SelectTemplate (id);
210
public event EventHandler SelectionChanged;
211
public event EventHandler Activated;
213
void LoadTemplatesIntoView (List<TemplateItem> templates)
215
templateView.Clear ();
216
foreach (var t in templates) {
217
templateView.AddItem (t);
221
public void LoadInstalledTemplates (IEnumerable<ProjectTemplate> templates)
223
foreach (var template in templates) {
224
if (template == null)
225
throw new ArgumentException ("Null template");
226
installedTemplates.Add (new TemplateItem (template));
228
installedTemplates.Sort ((a,b) => String.Compare (a.Name, b.Name));
229
installedTemplateCatView.Load (installedTemplates);
232
if (installedTemplateCatView.GetSelection () == null)
233
installedTemplateCatView.SetSelection (new string[0]);
235
if (installedSection.IsActive)
236
LoadTemplatesIntoView (installedTemplates);
239
public void LoadRecentTemplates (IEnumerable<ProjectTemplate> templates)
241
foreach (var template in templates) {
242
if (template == null)
243
throw new ArgumentException ("Null template");
244
recentTemplates.Add (new TemplateItem (template));
246
//don't sort recent templates, they should already be in most->least recent
247
recentTemplateCatView.Load (recentTemplates);
249
if (recentTemplateCatView.GetSelection () == null)
250
recentTemplateCatView.SetSelection (new string[0]);
252
if (recentSection.IsActive)
253
LoadTemplatesIntoView (installedTemplates);
256
public string InstalledTemplateSelectedCategory {
258
return string.Join ("/", installedTemplateCatView.GetSelection ());
261
var cat = value.Split (new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
262
installedTemplateCatView.SetSelection (cat);
266
public string RecentTemplateSelectedCategory {
268
return string.Join ("/", recentTemplateCatView.GetSelection ());
271
var cat = value.Split (new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
272
recentTemplateCatView.SetSelection (cat);
276
class CategoryTreeView : CompactScrolledWindow
278
TreeView view = new TreeView ();
279
TreeStore store = new TreeStore (typeof (string));
281
public CategoryTreeView ()
284
view.HeadersVisible = false;
285
var col = new TreeViewColumn ();
286
var crt = new CellRendererText ();
287
col.PackStart (crt, false);
288
col.AddAttribute (crt, "markup", 0);
289
view.AppendColumn (col);
290
view.Selection.Changed += delegate {
291
if (SelectionChanged != null)
292
SelectionChanged (this, EventArgs.Empty);
297
public void Load (List<TemplateItem> templates)
301
string general = GettextCatalog.GetString ("General");
302
string all = GettextCatalog.GetString ("All");
303
var categories = new Dictionary<string, object> ();
305
foreach (var tp in templates) {
307
if (tp.Category == null || tp.Category.Length == 0)
308
catPath = new string[] { general };
310
catPath = tp.Category;
312
Dictionary<string, object> searchCats = categories;
313
for (int i = 0; i < catPath.Length; i++) {
314
string s = catPath[i];
315
KeyValuePair<string,object>? found = null;
316
foreach (var searchCat in searchCats)
317
if (searchCat.Key == s)
319
if (i < catPath.Length - 1) {
320
if (found.HasValue && found.Value.Value != null) {
321
searchCats = (Dictionary<string,object>) found.Value.Value;
323
var d = new Dictionary<string, object> ();
327
} else if (!found.HasValue) {
328
searchCats.Add (s, null);
333
var iter = store.AppendValues (all);
334
BuildTree (iter, categories);
338
void BuildTree (TreeIter parent, Dictionary<string, object> cats)
340
foreach (var cat in cats.OrderBy (kv => kv.Key)) {
341
var iter = store.AppendValues (parent, cat.Key);
342
if (cat.Value != null)
343
BuildTree (iter, (Dictionary<string, object>) cat.Value);
347
public IList<string> GetSelection ()
349
TreeIter selectedIter;
350
if (!view.Selection.GetSelected (out selectedIter))
353
string[] selection = new string[store.IterDepth (selectedIter)];
356
//root is the "all" category
357
store.GetIterFirst (out iter);
358
if (iter.Equals (selectedIter))
360
store.IterChildren (out iter, iter);
362
for (int i = 0; i < selection.Length; i++) {
364
if (iter.Equals (selectedIter)) {
365
selection[i] = (string) store.GetValue (iter, 0);
367
} else if (store.IsAncestor (iter, selectedIter)) {
368
selection[i] = (string) store.GetValue (iter, 0);
369
store.IterChildren (out iter, iter);
372
} while (store.IterNext (ref iter));
377
public void SetSelection (IList<string> value)
380
if (!store.GetIterFirst (out parent))
383
//root is the "all" node, matched by zero-length selection array
384
if (value.Count == 0) {
385
view.Selection.SelectIter (parent);
390
store.IterChildren (out iter, parent);
393
while (store.IterNext (ref iter)) {
394
var s = (string) store.GetValue (iter, 0);
398
if (i == value.Count || !store.IterChildren (out child, iter)) {
399
view.Selection.SelectIter (iter);
406
view.Selection.SelectIter (parent);
409
public event EventHandler SelectionChanged;
412
class TemplateView: CompactScrolledWindow
414
TemplateTreeView tree;
415
IList<string> categoryFilter;
418
public TemplateView ()
420
ShowBorderLine = true;
421
tree = new TemplateTreeView ();
422
tree.Selection.Changed += delegate {
423
if (SelectionChanged != null)
424
SelectionChanged (this, EventArgs.Empty);
426
tree.RowActivated += delegate {
427
if (DoubleClicked != null)
428
DoubleClicked (this, EventArgs.Empty);
432
HscrollbarPolicy = PolicyType.Automatic;
433
VscrollbarPolicy = PolicyType.Automatic;
434
ShadowType = ShadowType.None;
438
public TemplateItem CurrentlySelected {
439
get { return tree.CurrentlySelected; }
442
public void SelectTemplate (string id)
444
tree.SelectItem (id);
447
public void AddItem (TemplateItem templateItem)
449
tree.AddItem (templateItem);
457
public void SetCategoryFilter (IList<string> categoryFilter)
459
this.categoryFilter = categoryFilter;
463
public void SetSearchFilter (string search)
465
this.searchFilter = search;
469
bool MatchesCategory (TemplateItem item)
471
if (categoryFilter == null)
473
if (item.Category == null || item.Category.Length < categoryFilter.Count)
475
for (int i = 0; i < categoryFilter.Count; i++)
476
if (item.Category[i] != categoryFilter[i])
481
bool MatchesSearch (TemplateItem item)
483
return string.IsNullOrWhiteSpace (searchFilter)
484
|| item.Name.IndexOf (searchFilter, StringComparison.CurrentCultureIgnoreCase) >= 0
485
|| item.Template.Description.IndexOf (searchFilter, StringComparison.CurrentCultureIgnoreCase) >= 0;
490
tree.Filter (item => MatchesSearch (item) && MatchesCategory (item));
493
public event EventHandler SelectionChanged;
494
public event EventHandler DoubleClicked;
497
class TemplateTreeView: TreeView
499
Gtk.ListStore templateStore;
500
TreeModelFilter filterModel;
501
Func<TemplateItem,bool> filterFunc;
503
public TemplateTreeView ()
505
HeadersVisible = false;
506
templateStore = new ListStore (typeof(TemplateItem));
507
Model = filterModel = new TreeModelFilter (templateStore, null);
508
filterModel.VisibleFunc = FilterFuncWrapper;
510
var col = new TreeViewColumn ();
511
var crp = new CellRendererIcon () {
512
StockSize = (uint) Gtk.IconSize.Dnd,
515
col.PackStart (crp, false);
516
col.SetCellDataFunc (crp, CellDataFuncIcon);
518
var crt = new CellRendererText ();
519
col.PackStart (crt, false);
520
col.SetCellDataFunc (crt, CellDataFuncText);
526
bool FilterFuncWrapper (TreeModel model, TreeIter iter)
528
if (filterFunc == null)
530
var item = (TemplateItem) model.GetValue (iter, 0);
532
//gets called while the rows are being inserted and don't yet have data
536
return filterFunc (item);
539
void CellDataFuncText (TreeViewColumn col, CellRenderer cell, TreeModel model, TreeIter iter)
541
var item = (TemplateItem) model.GetValue (iter, 0);
542
string name = GLib.Markup.EscapeText (item.Name);
543
if (!string.IsNullOrEmpty (item.Template.LanguageName))
544
name += "\n<span foreground='darkgrey'><small>" + item.Template.LanguageName + "</small></span>";
546
((CellRendererText)cell).Markup = name;
549
void CellDataFuncIcon (TreeViewColumn col, CellRenderer cell, TreeModel model, TreeIter iter)
551
var item = (TemplateItem) model.GetValue (iter, 0);
552
var id = item.Template.Icon.IsNull ? "md-project" : item.Template.Icon.ToString ();
553
((CellRendererIcon)cell).StockId = id;
556
public void Filter (Func<TemplateItem,bool> filter)
558
this.filterFunc = filter;
559
this.filterModel.Refilter ();
562
public TemplateItem CurrentlySelected {
565
if (!Selection.GetSelected (out iter))
567
return (TemplateItem) filterModel.GetValue (iter, 0);
571
public void SelectItem (string id)
574
if (filterModel.GetIterFirst (out iter)) {
576
var t = (TemplateItem) filterModel.GetValue (iter, 0);
577
if (t.Template.Id == id) {
578
Selection.SelectIter (iter);
581
} while (filterModel.IterNext (ref iter));
585
public void AddItem (TemplateItem templateItem)
587
templateStore.AppendValues (templateItem);
592
templateStore.Clear ();
595
protected override void OnDestroyed ()
597
filterModel.Dispose ();
598
templateStore.Dispose ();
604
public TemplateItem (ProjectTemplate template)
606
Name = StringParserService.Parse (template.Name);
607
this.Template = template;
608
if (!string.IsNullOrEmpty (template.Category))
609
this.Category = template.Category.Split (new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
611
this.Category = new string[0];
614
public string Name { get; private set; }
615
public string[] Category { get; private set; }
616
public ProjectTemplate Template { get; private set; }