using Gtk; using System; using Mono.Unix; using System.Collections.Generic; namespace Stetic { internal class ProjectViewBackend : ScrolledWindow { ProjectViewBackendNodeView nodeView; public ProjectViewBackend (ProjectViewFrontend frontend) { ShadowType = Gtk.ShadowType.In; nodeView = new ProjectViewBackendNodeView (frontend); Add (nodeView); ShowAll (); } public event Wrapper.WidgetEventHandler WidgetActivated { add { nodeView.WidgetActivated += value; } remove { nodeView.WidgetActivated -= value; } } public void Bind (WidgetEditSession newSession) { nodeView.Bind (newSession); } } internal class ProjectViewBackendNodeView : TreeView { ProjectViewFrontend frontend; TreeStore store; WidgetEditSession editSession; const int ColIcon = 0; const int ColName = 1; const int ColWrapper = 2; const int ColFilled = 3; public event Wrapper.WidgetEventHandler WidgetActivated; public ProjectViewBackendNodeView (ProjectViewFrontend frontend) { this.frontend = frontend; HeadersVisible = false; store = new TreeStore (typeof(Gdk.Pixbuf), typeof(string), typeof(ObjectWrapper), typeof(bool)); Model = store; TreeViewColumn col; CellRenderer renderer; col = new TreeViewColumn (); renderer = new CellRendererPixbuf (); col.PackStart (renderer, false); col.AddAttribute (renderer, "pixbuf", 0); renderer = new CellRendererText (); col.PackStart (renderer, true); col.AddAttribute (renderer, "text", 1); AppendColumn (col); Selection.Mode = SelectionMode.Single; Selection.Changed += RowSelected; TestExpandRow += OnTestExpandRow; ShowAll (); } public void Bind (WidgetEditSession newSession) { if (editSession != null) { editSession.SelectionChanged -= WidgetSelected; editSession.EditingBackend.ProjectReloaded -= OnProjectReloaded; editSession.EditingBackend.WidgetNameChanged -= OnWidgetNameChanged; editSession.EditingBackend.WidgetContentsChanged -= OnContentsChanged; } editSession = newSession; if (editSession != null) { editSession.SelectionChanged += WidgetSelected; editSession.EditingBackend.ProjectReloaded += OnProjectReloaded; editSession.EditingBackend.WidgetNameChanged += OnWidgetNameChanged; editSession.EditingBackend.WidgetContentsChanged += OnContentsChanged; } LoadProject (); } public override void Dispose () { if (editSession != null) { editSession.SelectionChanged -= WidgetSelected; editSession.EditingBackend.ProjectReloaded -= OnProjectReloaded; editSession.EditingBackend.WidgetNameChanged -= OnWidgetNameChanged; editSession.EditingBackend.WidgetContentsChanged -= OnContentsChanged; } base.Dispose (); } public void LoadProject () { Clear (); if (editSession == null || editSession.RootWidget == null) return; AddNode (TreeIter.Zero, editSession.RootWidget.Wrapped); } public void AddNode (TreeIter iter, Gtk.Widget widget) { Stetic.Wrapper.Widget wrapper = GetVisibleWrapper (widget); if (wrapper == null) return; Gdk.Pixbuf icon = wrapper.ClassDescriptor.Icon.ScaleSimple (16, 16, Gdk.InterpType.Bilinear); string txt = widget.Name; if (!iter.Equals (TreeIter.Zero)) iter = store.AppendValues (iter, icon, txt, wrapper, true); else iter = store.AppendValues (icon, txt, wrapper, true); FillChildren (iter, wrapper); } void FillChildren (TreeIter it, Wrapper.Widget wrapper) { Stetic.Wrapper.Container container = wrapper as Wrapper.Container; if (container != null) { foreach (Gtk.Widget w in container.RealChildren) { Stetic.Wrapper.Widget ww = GetVisibleWrapper (w); if (ww != null) { // Add a dummy node to allow lazy loading store.SetValue (it, ColFilled, false); store.AppendValues (it, null, "", null, false); return; } } } store.SetValue (it, ColFilled, true); } Wrapper.Widget GetVisibleWrapper (Gtk.Widget w) { Stetic.Wrapper.Widget wrapper = Stetic.Wrapper.Widget.Lookup (w); if (wrapper == null || wrapper.Unselectable) return null; else return wrapper; } void EnsureFilled (TreeIter it) { bool filled = (bool) store.GetValue (it, ColFilled); if (filled) return; // Remove the dummy node TreeIter cit; store.IterChildren (out cit, it); store.Remove (ref cit); // Add the real children Stetic.Wrapper.Container container = store.GetValue (it, ColWrapper) as Wrapper.Container; if (container != null) { foreach (Gtk.Widget w in container.RealChildren) AddNode (it, w); } store.SetValue (it, ColFilled, true); } void OnTestExpandRow (object s, Gtk.TestExpandRowArgs args) { EnsureFilled (args.Iter); args.RetVal = false; } public void Clear () { TreeIter it; while (store.GetIterFirst (out it)) { store.Remove (ref it); } } void RefreshNode (TreeIter it) { Wrapper.Widget wrapper = (Wrapper.Widget) store.GetValue (it, ColWrapper); store.SetValue (it, ColName, wrapper.Wrapped.Name); TreeIter cit; while (store.IterChildren (out cit, it)) { store.Remove (ref cit); } FillChildren (it, wrapper); } void OnWidgetNameChanged (object s, Wrapper.WidgetNameChangedArgs args) { TreeIter? node = FindNode (args.WidgetWrapper, false); if (node == null) return; store.SetValue (node.Value, ColName, args.NewName); } void OnContentsChanged (object s, Wrapper.WidgetEventArgs args) { TreeIter? node = FindNode (args.WidgetWrapper, false); if (node != null) RefreshNode (node.Value); } void OnProjectReloaded (object o, EventArgs a) { LoadProject (); } Stetic.Wrapper.Widget SelectedWrapper { get { TreeIter iter; if (!Selection.GetSelected (out iter)) return null; Wrapper.Widget w = (Wrapper.Widget) store.GetValue (iter, ColWrapper); return w; } } bool syncing = false; void RowSelected (object obj, EventArgs args) { if (!syncing) { syncing = true; Stetic.Wrapper.Widget selection = SelectedWrapper; if (selection != null) selection.Select (); syncing = false; NotifySelectionChanged (selection); } } void WidgetSelected (object s, Wrapper.WidgetEventArgs args) { if (!syncing) { syncing = true; if (args.Widget != null) { Wrapper.Widget w = Wrapper.Widget.Lookup (args.Widget); if (w != null) { TreeIter? it = FindNode (w, true); if (it != null) { ExpandToPath (store.GetPath (it.Value)); Selection.SelectIter (it.Value); ScrollToCell (store.GetPath (it.Value), Columns[0], false, 0, 0); NotifySelectionChanged (w); } } } else { Selection.UnselectAll (); NotifySelectionChanged (null); } syncing = false; } } TreeIter? FindNode (Wrapper.Widget w, bool loadBranches) { Wrapper.Widget parent = w.ParentWrapper; TreeIter it; if (parent == null) { if (!store.GetIterFirst (out it)) return null; } else { TreeIter? pi = FindNode (parent, loadBranches); if (pi == null) return null; if (loadBranches) EnsureFilled (pi.Value); if (!store.IterChildren (out it, pi.Value)) return null; } do { Wrapper.Widget cw = (Wrapper.Widget) store.GetValue (it, ColWrapper); if (cw == w) return it; } while (store.IterNext (ref it)); return null; } void NotifySelectionChanged (Stetic.Wrapper.Widget w) { if (frontend == null) return; if (w != null) frontend.NotifySelectionChanged (Component.GetSafeReference (w), w.Wrapped.Name, w.ClassDescriptor.Name); else frontend.NotifySelectionChanged (null, null, null); } //HACK: show context menu in release event instead of show event to work around gtk bug protected override bool OnButtonReleaseEvent (Gdk.EventButton evt) { if (Stetic.Editor.GtkWorkarounds.IsContextMenuButton (evt)) return OnPopupMenu (); return base.OnButtonReleaseEvent (evt); } protected override bool OnPopupMenu () { Stetic.Wrapper.Widget selection = SelectedWrapper; if (selection != null) { Menu m = new ContextMenu (selection); if (selection.IsTopLevel) { // Allow deleting top levels from the project view ImageMenuItem item = new ImageMenuItem (Gtk.Stock.Delete, null); item.Activated += delegate (object obj, EventArgs args) { selection.Delete (); }; item.Show (); m.Add (item); } m.Popup (); return true; } else return base.OnPopupMenu (); } protected override void OnRowActivated (TreePath path, TreeViewColumn col) { base.OnRowActivated (path, col); Stetic.Wrapper.Widget w = SelectedWrapper; if (w != null) { if (frontend != null) frontend.NotifyWidgetActivated (Component.GetSafeReference (w), w.Wrapped.Name, w.ClassDescriptor.Name); if (WidgetActivated != null) WidgetActivated (this, new Wrapper.WidgetEventArgs (w)); } } } }