4
// Lluis Sanchez Gual <lluis@novell.com>
6
// Copyright (c) 2008 Novell, Inc (http://www.novell.com)
8
// Permission is hereby granted, free of charge, to any person obtaining a copy
9
// of this software and associated documentation files (the "Software"), to deal
10
// in the Software without restriction, including without limitation the rights
11
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
// copies of the Software, and to permit persons to whom the Software is
13
// furnished to do so, subject to the following conditions:
15
// The above copyright notice and this permission notice shall be included in
16
// all copies or substantial portions of the Software.
18
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
29
using System.Collections.Generic;
32
using MonoDevelop.Core.Gui.Codons;
34
namespace MonoDevelop.Core.Gui.Dialogs
36
public partial class OptionsDialog : Gtk.Dialog
38
protected TreeStore store;
39
Dictionary<OptionsDialogSection, SectionPage> pages = new Dictionary<OptionsDialogSection, SectionPage> ();
40
Dictionary<OptionsPanelNode, PanelInstance> panels = new Dictionary<OptionsPanelNode,PanelInstance> ();
41
object mainDataObject;
43
ExtensionContext extensionContext;
44
HashSet<object> modifiedObjects = new HashSet<object> ();
45
bool removeEmptySections;
47
public object DataObject {
49
return mainDataObject;
53
public ExtensionContext ExtensionContext {
55
return extensionContext;
59
public HashSet<object> ModifiedObjects {
60
get { return modifiedObjects; }
63
public OptionsDialog (string extensionPath): this (null, null, extensionPath)
67
public OptionsDialog (Gtk.Window parentWindow, object dataObject, string extensionPath) : this (parentWindow, dataObject, extensionPath, true)
70
public OptionsDialog (Gtk.Window parentWindow, object dataObject, string extensionPath, bool removeEmptySections)
73
this.removeEmptySections = removeEmptySections;
74
extensionContext = AddinManager.CreateExtensionContext ();
76
this.mainDataObject = dataObject;
77
this.extensionPath = extensionPath;
79
if (parentWindow != null)
80
TransientFor = parentWindow;
82
store = new TreeStore (typeof(OptionsDialogSection), typeof(string), typeof(string), typeof(bool), typeof(string));
84
tree.HeadersVisible = false;
86
TreeViewColumn col = new TreeViewColumn ();
87
CellRendererPixbuf crp = new CellRendererPixbuf ();
88
crp.StockSize = (uint) IconSize.Menu;
89
col.PackStart (crp, false);
90
col.AddAttribute (crp, "stock-id", 1);
91
col.AddAttribute (crp, "visible", 3);
92
col.AddAttribute (crp, "cell-background", 4);
93
CellRendererText crt = new CellRendererText ();
94
col.PackStart (crt, true);
95
col.AddAttribute (crt, "markup", 2);
96
col.AddAttribute (crt, "cell-background", 4);
97
tree.AppendColumn (col);
99
tree.Selection.Changed += OnSelectionChanged;
101
InitializeContext (extensionContext);
105
this.DefaultResponse = Gtk.ResponseType.Ok;
108
protected void ExpandCategories ()
111
if (store.GetIterFirst (out it)) {
112
tree.Selection.SelectIter (it);
114
tree.ExpandRow (store.GetPath (it), false);
115
} while (store.IterNext (ref it));
119
public void FillTree ()
124
foreach (OptionsDialogSection section in extensionContext.GetExtensionNodes (extensionPath)) {
125
AddSection (section, mainDataObject);
129
protected virtual void InitializeContext (ExtensionContext extensionContext)
133
protected override void OnDestroyed()
135
foreach (PanelInstance pi in panels.Values)
136
if (pi.Widget != null)
137
pi.Widget.Destroy ();
141
public void SelectPanel (string id)
143
foreach (OptionsDialogSection section in pages.Keys) {
144
if (section.Id == id) {
151
public void AddChildSection (IOptionsPanel parent, OptionsDialogSection section, object dataObject)
153
foreach (SectionPage page in pages.Values) {
154
foreach (PanelInstance pi in page.Panels) {
155
if (pi.Panel == parent) {
156
AddSection (page.Iter, section, dataObject);
161
throw new InvalidOperationException ("Parent options panel not found in the dialog.");
164
public void RemoveSection (OptionsDialogSection section)
167
if (pages.TryGetValue (section, out page))
168
RemoveSection (page.Iter);
171
void RemoveSection (Gtk.TreeIter it)
174
if (store.IterChildren (out ci, it)) {
177
} while (store.IterNext (ref ci));
180
// If the this panel is the selection, select the parent before removing
182
if (tree.Selection.GetSelected (out itsel)) {
183
if (store.GetPath (itsel).Equals (store.GetPath (it))) {
185
if (store.IterParent (out pi, it))
186
tree.Selection.SelectIter (pi);
189
OptionsDialogSection section = (OptionsDialogSection) store.GetValue (it, 0);
190
if (section != null) {
192
if (pages.TryGetValue (section, out page)) {
193
foreach (PanelInstance pi in page.Panels)
194
panels.Remove (pi.Node);
195
pages.Remove (section);
196
if (page.Widget != null)
197
page.Widget.Destroy ();
200
store.Remove (ref it);
203
protected TreeIter AddSection (OptionsDialogSection section, object dataObject)
205
return AddSection (TreeIter.Zero, section, dataObject);
208
protected TreeIter AddSection (TreeIter parentIter, OptionsDialogSection section, object dataObject)
211
if (parentIter.Equals (TreeIter.Zero)) {
212
string sectionLabel = "<b>" + GLib.Markup.EscapeText (section.Label) + "</b>";
213
it = store.AppendValues (section, null, sectionLabel, false, null);
216
string icon = string.IsNullOrEmpty (section.Icon) ? "md-empty-category" : section.Icon;
217
it = store.AppendValues (parentIter, section, icon, section.Label, true, null);
220
if (!section.CustomNode)
221
AddChildSections (it, section, dataObject);
223
// Remove the section if it doesn't have children nor panels
224
SectionPage page = CreatePage (it, section, dataObject);
226
if (removeEmptySections && page.Panels.Count == 0 && !store.IterChildren (out cit, it))
227
store.Remove (ref it);
231
protected virtual void AddChildSections (TreeIter parentIter, OptionsDialogSection section, object dataObject)
233
foreach (ExtensionNode nod in section.ChildNodes) {
234
if (nod is OptionsDialogSection)
235
AddSection (parentIter, (OptionsDialogSection) nod, dataObject);
239
void DetachWidgets ()
241
foreach (Gtk.Widget w in pageFrame.Children)
242
pageFrame.Remove (w);
244
foreach (SectionPage sp in pages.Values) {
245
foreach (PanelInstance pi in sp.Panels) {
246
if (pi.Widget != null && pi.Widget.Parent != null)
247
((Gtk.Container)pi.Widget.Parent).Remove (pi.Widget);
249
if (sp.Widget != null) {
250
sp.Widget.Destroy ();
256
protected override void OnResponse (ResponseType resp)
258
base.OnResponse (resp);
262
protected virtual bool ValidateChanges ()
264
foreach (SectionPage sp in pages.Values) {
265
if (sp.Widget == null)
267
foreach (PanelInstance pi in sp.Panels) {
268
if (!pi.Panel.ValidateChanges ())
269
return false; // Not valid
275
protected virtual void ApplyChanges ()
277
foreach (SectionPage sp in pages.Values) {
278
if (sp.Widget == null)
280
foreach (PanelInstance pi in sp.Panels)
281
pi.Panel.ApplyChanges ();
286
void OnSelectionChanged (object s, EventArgs a)
289
if (tree.Selection.GetSelected (out it)) {
290
OptionsDialogSection section = (OptionsDialogSection) store.GetValue (it, 0);
295
public void ShowPage (OptionsDialogSection section)
298
if (!pages.TryGetValue (section, out page))
301
if (page.Panels.Count == 0) {
302
foreach (ExtensionNode node in section.ChildNodes) {
303
if (node is OptionsDialogSection) {
304
ShowPage ((OptionsDialogSection) node);
310
foreach (Gtk.Widget w in pageFrame.Children) {
311
Container cc = w as Gtk.Container;
313
foreach (Gtk.Widget cw in cc)
316
pageFrame.Remove (w);
319
if (page.Widget == null)
320
CreatePageWidget (page);
322
labelTitle.Markup = "<span weight=\"bold\" size=\"x-large\">" + GLib.Markup.EscapeText (section.Label) + "</span>";
323
if (!string.IsNullOrEmpty (section.Icon))
324
image.Stock = section.Icon;
326
image.Stock = "md-empty-category";
327
pageFrame.PackStart (page.Widget, true, true, 0);
329
// Ensures that the Shown event is fired for each panel
330
Container c = page.Widget as Gtk.Container;
332
foreach (Gtk.Widget cw in c)
336
tree.ExpandToPath (store.GetPath (page.Iter));
337
tree.Selection.SelectIter (page.Iter);
340
SectionPage CreatePage (TreeIter it, OptionsDialogSection section, object dataObject)
343
if (pages.TryGetValue (section, out page))
346
page = new SectionPage ();
348
page.Panels = new List<PanelInstance> ();
349
pages [section] = page;
351
List<OptionsPanelNode> nodes = new List<OptionsPanelNode> ();
352
if (!string.IsNullOrEmpty (section.TypeName) || section.CustomNode)
355
if (!section.CustomNode) {
356
foreach (ExtensionNode nod in section.ChildNodes) {
357
OptionsPanelNode node = nod as OptionsPanelNode;
358
if (node != null && !(node is OptionsDialogSection))
363
foreach (OptionsPanelNode node in nodes)
365
PanelInstance pi = null;
366
if (panels.TryGetValue (node, out pi)) {
367
if (pi.DataObject == dataObject) {
368
// The panel can be reused
369
if (!pi.Panel.IsVisible ())
372
// If the data object has changed, this panel instance can't be
373
// reused anymore. Destroy it.
374
panels.Remove (node);
375
if (pi.Widget != null)
376
pi.Widget.Destroy ();
381
IOptionsPanel panel = node.CreatePanel ();
382
pi = new PanelInstance ();
385
pi.DataObject = dataObject;
386
page.Panels.Add (pi);
387
panel.Initialize (this, dataObject);
388
if (!panel.IsVisible ()) {
389
page.Panels.Remove (pi);
394
page.Panels.Add (pi);
399
void CreatePageWidget (SectionPage page)
401
List<PanelInstance> boxPanels = new List<PanelInstance> ();
402
List<PanelInstance> tabPanels = new List<PanelInstance> ();
404
foreach (PanelInstance pi in page.Panels) {
405
if (pi.Widget == null) {
406
pi.Widget = pi.Panel.CreatePanelWidget ();
407
//HACK: work around bug 469427 - broken themes match on widget names
408
if (pi.Widget.Name.IndexOf ("Panel") > 0)
409
pi.Widget.Name = pi.Widget.Name.Replace ("Panel", "_");
411
else if (pi.Widget.Parent != null)
412
((Gtk.Container) pi.Widget.Parent).Remove (pi.Widget);
414
if (pi.Node.Grouping == PanelGrouping.Tab)
420
// Try to fit panels with grouping=box or auto in the main page.
421
// If they don't fit. Move auto panels to its own tab page.
426
PanelInstance lastAuto = null;
428
foreach (PanelInstance pi in boxPanels) {
429
if (pi.Node.Grouping == PanelGrouping.Auto)
431
mainPageSize += pi.Widget.SizeRequest ().Height + 6;
433
fits = mainPageSize <= pageFrame.Allocation.Height;
435
if (lastAuto != null && boxPanels.Count > 1) {
436
boxPanels.Remove (lastAuto);
437
tabPanels.Insert (0, lastAuto);
444
Gtk.VBox box = new VBox (false, 12);
446
for (int n=0; n<boxPanels.Count; n++) {
448
HSeparator sep = new HSeparator ();
450
box.PackStart (sep, false, false, 0);
452
PanelInstance pi = boxPanels [n];
453
box.PackStart (pi.Widget, pi.Node.Fill, pi.Node.Fill, 0);
457
if (tabPanels.Count > 0) {
458
Gtk.Notebook nb = new Notebook ();
460
Gtk.Label blab = new Gtk.Label (GettextCatalog.GetString ("General"));
463
nb.InsertPage (box, blab, -1);
464
foreach (PanelInstance pi in tabPanels) {
465
Gtk.Label lab = new Gtk.Label (GettextCatalog.GetString (pi.Node.Label));
467
Gtk.Alignment a = new Alignment (0, 0, 1, 1);
471
nb.InsertPage (a, lab, -1);
480
protected virtual void OnButtonOkClicked (object sender, System.EventArgs e)
482
// Validate changes before saving
483
if (!ValidateChanges ())
489
if (DataObject != null)
490
modifiedObjects.Add (DataObject);
492
this.Respond (ResponseType.Ok);
497
public IOptionsPanel Panel;
498
public Gtk.Widget Widget;
499
public OptionsPanelNode Node;
500
public object DataObject;
505
public List<PanelInstance> Panels;
506
public Gtk.Widget Widget;
507
public Gtk.TreeIter Iter;