Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/monodevelop.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Hutchinson <mhutchinson@novell.com>2011-02-19 11:55:11 +0300
committerMichael Hutchinson <mhutchinson@novell.com>2011-02-19 12:05:37 +0300
commit954a1bd28ac9a896ecf97aacb84aabff4c5ee1cc (patch)
tree7787a47a4b96374e5905e0a41bff58ae246f0ba8 /main/src/core/MonoDevelop.Ide/MonoDevelop.Components
parentc55ca026e5c369e6c59c25b8e175124c064f18dc (diff)
[Ide[ New widget for sectioned list
Diffstat (limited to 'main/src/core/MonoDevelop.Ide/MonoDevelop.Components')
-rw-r--r--main/src/core/MonoDevelop.Ide/MonoDevelop.Components/SectionList.cs479
1 files changed, 479 insertions, 0 deletions
diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components/SectionList.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components/SectionList.cs
new file mode 100644
index 0000000000..09a10caf55
--- /dev/null
+++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components/SectionList.cs
@@ -0,0 +1,479 @@
+//
+// SectionList.cs
+//
+// Author:
+// Michael Hutchinson <mhutchinson@novell.com>
+//
+// Copyright (c) 2011 Novell, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+using Gtk;
+using Gdk;
+using Cairo;
+
+namespace MonoDevelop.Components
+{
+ [System.ComponentModel.ToolboxItem (true)]
+ class SectionList : Container
+ {
+ const int borderLineWidth = 1;
+ const int headerPadding = 2;
+
+ List<Section> sections = new List<Section> ();
+
+ Gdk.Window inputWindow;
+ Pango.Layout layout;
+ int headerHeight = 20;
+ int activeIndex = 0;
+ int hoverIndex = -1;
+ bool trackingHover = false;
+
+ public int ActiveIndex {
+ get { return activeIndex; }
+ set {
+ if (value >= sections.Count || value < 0)
+ throw new ArgumentOutOfRangeException ();
+ int oldIndex = activeIndex;
+ activeIndex = value;
+ FocusChain = new Widget[] { sections[value].Child };
+ UpdateVisibility ();
+ RepaintSectionHeader (oldIndex);
+ RepaintSectionHeader (activeIndex);
+ }
+ }
+
+ public Section ActiveSection {
+ get {
+ return ActiveIndex >= 0 && sections.Count > 0? sections[activeIndex] : null;
+ }
+ set {
+ int idx = sections.IndexOf (value);
+ if (idx < 0)
+ throw new ArgumentException ("Section is not in this container");
+ ActiveIndex = idx;
+ }
+ }
+
+ public SectionList ()
+ {
+ this.WidgetFlags |= WidgetFlags.NoWindow;
+ WidthRequest = 100;
+ EnsureLayout ();
+ }
+
+ //HACK: for some reason GTK# tries to resurrect this for a child forall callback even after it's destroyed
+ protected SectionList (IntPtr handle) : base (handle)
+ {
+ }
+
+ protected override void OnRealized ()
+ {
+ base.OnRealized ();
+
+ var alloc = Allocation;
+ int bw = (int) BorderWidth;
+
+ var attributes = new Gdk.WindowAttr () {
+ WindowType = Gdk.WindowType.Child,
+ Wclass = Gdk.WindowClass.InputOnly,
+ EventMask = (int) (
+ EventMask.EnterNotifyMask |
+ EventMask.LeaveNotifyMask |
+ EventMask.PointerMotionMask |
+ EventMask.ButtonPressMask |
+ EventMask.ButtonReleaseMask
+ ),
+ X = alloc.X + bw,
+ Y = alloc.Y + bw,
+ Width = alloc.Width - bw * 2,
+ Height = alloc.Height - bw * 2,
+ };
+
+ var attrMask = Gdk.WindowAttributesType.X | Gdk.WindowAttributesType.Y;
+
+ inputWindow = new Gdk.Window (Parent.GdkWindow, attributes, (int) attrMask);
+ inputWindow.UserData = Handle;
+ }
+
+ protected override void OnUnrealized ()
+ {
+ base.OnUnrealized ();
+
+ inputWindow.UserData = IntPtr.Zero;
+ inputWindow.Destroy ();
+ inputWindow = null;
+ }
+
+ protected override void OnMapped ()
+ {
+ base.OnMapped ();
+ inputWindow.Show ();
+ }
+
+ protected override void OnUnmapped ()
+ {
+ base.OnUnmapped ();
+ inputWindow.Hide ();
+ }
+
+ public Section AddSection (string title, Widget child)
+ {
+ var s = new Section (this, title, child);
+ sections.Add (s);
+ child.Parent = this;
+ return s;
+ }
+
+ protected override void OnStyleSet (Style previous)
+ {
+ base.OnStyleSet (previous);
+ KillLayout ();
+ EnsureLayout ();
+ }
+
+ void EnsureLayout ()
+ {
+ if (layout != null)
+ layout.Dispose ();
+ layout = new Pango.Layout (PangoContext);
+
+ layout.SetText ("lp");
+ int w, h;
+ layout.GetPixelSize (out w, out h);
+ headerHeight = Math.Max (20, h + headerPadding + headerPadding);
+ }
+
+ void KillLayout ()
+ {
+ if (layout == null)
+ return;
+ layout.Dispose ();
+ layout = null;
+ }
+
+ protected override void OnAdded (Widget widget)
+ {
+ throw new InvalidOperationException ("Cannot add widget directly, must add a section");
+ }
+
+ protected override void OnRemoved (Widget widget)
+ {
+ for (int i = 0; i < sections.Count; i++) {
+ var section = sections[i];
+ if (section.Child == widget) {
+ widget.Unparent ();
+ section.Parent = null;
+ section.Child = null;
+ sections.RemoveAt (i);
+ break;
+ }
+ }
+ }
+
+ protected override void OnShown ()
+ {
+ UpdateVisibility ();
+ base.OnShown ();
+ }
+
+ protected override void OnSizeRequested (ref Requisition requisition)
+ {
+ int wr = 0, hr = 0;
+ foreach (var section in sections) {
+ var req = section.Child.SizeRequest ();
+ wr = Math.Max (wr, req.Width);
+ hr = Math.Max (hr, req.Height);
+ }
+
+ hr += sections.Count * headerHeight + borderLineWidth;
+
+ int bw2 = ((int)BorderWidth + borderLineWidth) * 2;
+
+ hr += bw2;
+ wr += bw2;
+
+ hr = Math.Max (hr, HeightRequest);
+ wr = Math.Max (wr, WidthRequest);
+
+ requisition.Height = hr;
+ requisition.Width = wr;
+ }
+
+ protected override void OnSizeAllocated (Gdk.Rectangle allocation)
+ {
+ base.OnSizeAllocated (allocation);
+ if (sections.Count == 0)
+ return;
+
+ int bw = (int) BorderWidth + borderLineWidth;
+ int bw2 = bw * 2;
+ allocation.Width -= bw2;
+ allocation.Height -= bw2;
+ allocation.X += bw;
+ allocation.Y += bw;
+
+ if (IsRealized) {
+ inputWindow.MoveResize (allocation);
+ }
+
+ allocation.Height -= (borderLineWidth + headerHeight) * sections.Count;
+ allocation.Y += (headerHeight + borderLineWidth) * (activeIndex + 1);
+
+ sections[activeIndex].Child.SizeAllocate (allocation);
+ }
+
+ void UpdateVisibility ()
+ {
+ for (int i = 0; i < sections.Count; i++) {
+ var section = sections[i];
+ if (activeIndex == i) {
+ section.Child.Show ();
+ } else {
+ section.Child.Hide ();
+ }
+ }
+ }
+
+ static Cairo.Color Convert (Gdk.Color color)
+ {
+ return new Cairo.Color (
+ color.Red / (double) ushort.MaxValue,
+ color.Green / (double) ushort.MaxValue,
+ color.Blue / (double) ushort.MaxValue);
+ }
+
+ //FIXME: respect damage regions not just the whole areas, and skip more work when possible
+ protected override bool OnExposeEvent (Gdk.EventExpose evnt)
+ {
+ if (sections.Count == 0)
+ return false;
+
+ var alloc = Allocation;
+
+ int bw = (int) BorderWidth;
+ double halfLineWidth = borderLineWidth / 2.0;
+ int bw2 = bw * 2;
+ int w = alloc.Width - bw2;
+ int h = alloc.Height - bw2;
+
+ using (var cr = CairoHelper.Create (evnt.Window)) {
+ CairoHelper.Region (cr, evnt.Region);
+ cr.Clip ();
+
+ cr.Translate (alloc.X + bw, alloc.Y + bw);
+
+ var borderCol = Convert (Style.Dark (StateType.Normal));
+ cr.Color = borderCol;
+ cr.Rectangle (halfLineWidth, halfLineWidth, w - borderLineWidth, h - borderLineWidth);
+ cr.LineWidth = borderLineWidth;
+ cr.Stroke ();
+
+ cr.Translate (borderLineWidth, borderLineWidth);
+ w = w - (2 * borderLineWidth);
+
+ var unselectedGrad = new LinearGradient (0, 0, 0, headerHeight);
+ var unselectedCol = Convert (Style.Mid (StateType.Normal));
+ var unselectedTextCol = Convert (Style.Text (StateType.Normal));
+ unselectedCol.A = 0.6;
+ unselectedGrad.AddColorStop (0, unselectedCol);
+ unselectedCol.A = 1;
+ unselectedGrad.AddColorStop (1, unselectedCol);
+
+ var hoverGrad = new LinearGradient (0, 0, 0, headerHeight);
+ var hoverCol = Convert (Style.Mid (StateType.Prelight));
+ var hoverTextCol = Convert (Style.Text (StateType.Prelight));
+ hoverCol.A = 0.6;
+ hoverGrad.AddColorStop (0, unselectedCol);
+ hoverCol.A = 1;
+ hoverGrad.AddColorStop (1, unselectedCol);
+
+ var selectedGrad = new LinearGradient (0, 0, 0, headerHeight);
+ var selectedCol = Convert (Style.Mid (StateType.Normal));
+ var selectedTextCol = Convert (Style.Text (StateType.Normal));
+ selectedCol.A = 0.6;
+ selectedGrad.AddColorStop (0, selectedCol);
+ selectedCol.A = 1;
+ selectedGrad.AddColorStop (1, selectedCol);
+
+ for (int i = 0; i < sections.Count; i++) {
+ var section = sections[i];
+ bool isActive = activeIndex == i;
+ bool isHover = hoverIndex == i;
+
+ cr.Rectangle (0, 0, w, headerHeight);
+ cr.Pattern = isActive? selectedGrad : (isHover? hoverGrad : unselectedGrad);
+ cr.Fill ();
+
+ cr.Color = isActive? selectedTextCol : (isHover? hoverTextCol : unselectedTextCol);
+ layout.SetText (section.Title);
+ layout.Ellipsize = Pango.EllipsizeMode.End;
+ layout.Width = (int) ((w - headerPadding - headerPadding) * Pango.Scale.PangoScale);
+ cr.MoveTo (headerPadding, headerPadding);
+ PangoCairoHelper.ShowLayout (cr, layout);
+
+ cr.MoveTo (-halfLineWidth, i > activeIndex? -halfLineWidth : headerHeight + halfLineWidth);
+ cr.RelLineTo (w + borderLineWidth, 0.0);
+ cr.Color = borderCol;
+ cr.Stroke ();
+
+ cr.Translate (0, headerHeight + borderLineWidth);
+ if (isActive)
+ cr.Translate (0, section.Child.Allocation.Height + borderLineWidth);
+ }
+ }
+
+ PropagateExpose (sections[activeIndex].Child, evnt);
+ return true;// base.OnExposeEvent (evnt);
+ }
+
+ protected override void ForAll (bool include_internals, Callback callback)
+ {
+ for (int i = 0; i < sections.Count; i++) {
+ var section = sections[i];
+ callback (section.Child);
+ //callbacks can remove the widget
+ if (sections.Count > 0 && section != sections[i])
+ i--;
+ }
+ }
+
+ protected override bool OnEnterNotifyEvent (EventCrossing evnt)
+ {
+ trackingHover = true;
+ var hoverIdx = GetSectionHeaderAtPosition ((int)evnt.X, (int)evnt.Y);
+ SetHoverIndex (hoverIdx);
+ return base.OnEnterNotifyEvent (evnt);
+ }
+
+ protected override bool OnLeaveNotifyEvent (EventCrossing evnt)
+ {
+ trackingHover = false;
+ SetHoverIndex (-1);
+ return base.OnEnterNotifyEvent (evnt);
+ }
+
+ protected override bool OnMotionNotifyEvent (EventMotion evnt)
+ {
+ if (trackingHover) {
+ var hoverIdx = GetSectionHeaderAtPosition ((int)evnt.X, (int)evnt.Y);
+ SetHoverIndex (hoverIdx);
+ }
+ return base.OnMotionNotifyEvent (evnt);
+ }
+
+ protected override bool OnButtonPressEvent (EventButton evnt)
+ {
+ var idx = GetSectionHeaderAtPosition ((int)evnt.X, (int)evnt.Y);
+ if (idx >= 0) {
+ ActiveIndex = idx;
+ return true;
+ }
+ return base.OnButtonPressEvent (evnt);
+ }
+
+ int GetSectionHeaderAtPosition (int x, int y)
+ {
+ if (sections.Count == 0)
+ return -1;
+
+ //the event window already is within the border so only need to calc width
+ var alloc = Allocation;
+ int borderWidth = (int) BorderWidth;
+
+ if (y < 0 || x < 0 || x > (alloc.Width - borderWidth - borderWidth))
+ return -1;
+
+ int childWidgetStart = (headerHeight + borderLineWidth) * (activeIndex + 1);
+ if (y < childWidgetStart)
+ return y / (headerHeight + borderLineWidth);
+
+ y -= ActiveSection.Child.Allocation.Height;
+ if (y < childWidgetStart)
+ return -1;
+
+ int idx = y / (headerHeight + borderLineWidth);
+ return idx < sections.Count? idx : -1;
+ }
+
+ Gdk.Rectangle GetSectionHeaderArea (int index)
+ {
+ int borderWidth = (int) BorderWidth;
+ var rect = Allocation;
+ rect.X += borderWidth;
+ rect.Y += borderWidth;
+ rect.Width -= borderWidth * 2;
+ rect.Height = headerHeight + borderLineWidth + borderLineWidth;
+
+ rect.Y += (headerHeight + borderLineWidth) * (index);
+ if (index > activeIndex)
+ rect.Y += ActiveSection.Child.Allocation.Height;
+
+ return rect;
+ }
+
+ void SetHoverIndex (int index)
+ {
+ if (hoverIndex == index)
+ return;
+ int old = hoverIndex;
+ hoverIndex = index;
+ RepaintSectionHeader (old);
+ RepaintSectionHeader (index);
+ }
+
+ void RepaintSectionHeader (int index)
+ {
+ if (index < 0 || index >= sections.Count)
+ return;
+ var rect = GetSectionHeaderArea (index);
+ QueueDrawArea (rect.X, rect.Y, rect.Width, rect.Height);
+ }
+
+ public class Section
+ {
+ string title;
+ public SectionList Parent { get; internal set; }
+ public Widget Child { get; internal set; }
+
+ internal Section (SectionList parent, string title, Widget child)
+ {
+ this.Parent = parent;
+ this.title = title;
+ this.Child = child;
+ }
+
+ public bool IsActive {
+ get { return Parent.ActiveSection == this; }
+ set { Parent.ActiveSection = this; }
+ }
+
+ public string Title {
+ get { return this.title; }
+ set {
+ this.title = value;
+ int idx = Parent.sections.IndexOf (this);
+ Parent.RepaintSectionHeader (idx);
+ }
+ }
+ }
+ }
+} \ No newline at end of file