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;
33
namespace MonoDevelop.Components
35
class SectionList : Container
37
const int borderLineWidth = 1;
38
const int headerPadding = 2;
40
List<Section> sections = new List<Section> ();
42
Gdk.Window inputWindow;
44
int headerHeight = 20;
47
bool trackingHover = false;
49
public int ActiveIndex {
50
get { return activeIndex; }
52
if (value >= sections.Count || value < 0)
53
throw new ArgumentOutOfRangeException ();
55
if (activeIndex == value)
58
int oldIndex = activeIndex;
60
//FIXME: implement real focus support
61
FocusChain = new Widget[] { sections[value].Child };
63
RepaintSectionHeader (oldIndex);
64
RepaintSectionHeader (activeIndex);
65
OnActiveSectionChanged ();
66
sections[activeIndex].OnActivated ();
70
public Section ActiveSection {
72
return ActiveIndex >= 0 && sections.Count > 0? sections[activeIndex] : null;
75
int idx = sections.IndexOf (value);
77
throw new ArgumentException ("Section is not in this container");
82
void OnActiveSectionChanged ()
84
var evt = ActiveSectionChanged;
86
evt (this, EventArgs.Empty);
89
public event EventHandler ActiveSectionChanged;
93
this.WidgetFlags |= WidgetFlags.NoWindow;
98
//HACK: for some reason GTK# tries to resurrect this for a child forall callback even after it's destroyed
99
protected SectionList (IntPtr handle) : base (handle)
103
protected override void OnRealized ()
107
var alloc = Allocation;
108
int bw = (int) BorderWidth;
110
var attributes = new Gdk.WindowAttr () {
111
WindowType = Gdk.WindowType.Child,
112
Wclass = Gdk.WindowClass.InputOnly,
114
EventMask.EnterNotifyMask |
115
EventMask.LeaveNotifyMask |
116
EventMask.PointerMotionMask |
117
EventMask.ButtonPressMask |
118
EventMask.ButtonReleaseMask
122
Width = alloc.Width - bw * 2,
123
Height = alloc.Height - bw * 2,
126
var attrMask = Gdk.WindowAttributesType.X | Gdk.WindowAttributesType.Y;
128
inputWindow = new Gdk.Window (Parent.GdkWindow, attributes, (int) attrMask);
129
inputWindow.UserData = Handle;
132
protected override void OnUnrealized ()
134
base.OnUnrealized ();
136
inputWindow.UserData = IntPtr.Zero;
137
inputWindow.Destroy ();
141
protected override void OnMapped ()
143
//show our window before the children, so they're on top
148
protected override void OnUnmapped ()
154
public Section AddSection (string title, Widget child)
156
var s = new Section (this, title, child);
162
protected override void OnStyleSet (Style previous)
164
base.OnStyleSet (previous);
173
layout = new Pango.Layout (PangoContext);
175
layout.SetText ("lp");
177
layout.GetPixelSize (out w, out h);
178
headerHeight = Math.Max (20, h + headerPadding + headerPadding);
189
protected override void OnAdded (Widget widget)
191
throw new InvalidOperationException ("Cannot add widget directly, must add a section");
194
protected override void OnRemoved (Widget widget)
196
for (int i = 0; i < sections.Count; i++) {
197
var section = sections[i];
198
if (section.Child == widget) {
200
section.Parent = null;
201
section.Child = null;
202
sections.RemoveAt (i);
208
protected override void OnShown ()
214
protected override void OnSizeRequested (ref Requisition requisition)
217
foreach (var section in sections) {
218
var req = section.Child.SizeRequest ();
219
wr = Math.Max (wr, req.Width);
220
hr = Math.Max (hr, req.Height);
223
hr += sections.Count * headerHeight + borderLineWidth;
225
int bw2 = ((int)BorderWidth + borderLineWidth) * 2;
230
hr = Math.Max (hr, HeightRequest);
231
wr = Math.Max (wr, WidthRequest);
233
requisition.Height = hr;
234
requisition.Width = wr;
237
protected override void OnSizeAllocated (Gdk.Rectangle allocation)
239
base.OnSizeAllocated (allocation);
240
if (sections.Count == 0)
243
int bw = (int) BorderWidth + borderLineWidth;
245
allocation.Width -= bw2;
246
allocation.Height -= bw2;
251
inputWindow.MoveResize (allocation);
254
allocation.Height -= (borderLineWidth + headerHeight) * sections.Count;
255
allocation.Y += (headerHeight + borderLineWidth) * (activeIndex + 1);
257
sections[activeIndex].Child.SizeAllocate (allocation);
260
void UpdateVisibility ()
262
for (int i = 0; i < sections.Count; i++) {
263
var section = sections[i];
264
if (activeIndex == i) {
265
section.Child.Show ();
267
section.Child.Hide ();
272
static Cairo.Color Convert (Gdk.Color color)
274
return new Cairo.Color (
275
color.Red / (double) ushort.MaxValue,
276
color.Green / (double) ushort.MaxValue,
277
color.Blue / (double) ushort.MaxValue);
280
//FIXME: respect damage regions not just the whole areas, and skip more work when possible
281
protected override bool OnExposeEvent (Gdk.EventExpose evnt)
283
if (sections.Count == 0)
286
var alloc = Allocation;
288
int bw = (int) BorderWidth;
289
double halfLineWidth = borderLineWidth / 2.0;
291
int w = alloc.Width - bw2;
292
int h = alloc.Height - bw2;
294
using (var cr = CairoHelper.Create (evnt.Window)) {
295
CairoHelper.Region (cr, evnt.Region);
298
cr.Translate (alloc.X + bw, alloc.Y + bw);
300
var borderCol = Convert (Style.Dark (StateType.Normal));
301
cr.Color = borderCol;
302
cr.Rectangle (halfLineWidth, halfLineWidth, w - borderLineWidth, h - borderLineWidth);
303
cr.LineWidth = borderLineWidth;
306
cr.Translate (borderLineWidth, borderLineWidth);
307
w = w - (2 * borderLineWidth);
309
var unselectedGrad = new LinearGradient (0, 0, 0, headerHeight);
310
var unselectedCol = Convert (Style.Mid (StateType.Normal));
311
var unselectedTextCol = Convert (Style.Text (StateType.Normal));
312
unselectedCol.A = 0.6;
313
unselectedGrad.AddColorStop (0, unselectedCol);
315
unselectedGrad.AddColorStop (1, unselectedCol);
317
var hoverGrad = new LinearGradient (0, 0, 0, headerHeight);
318
var hoverCol = Convert (Style.Mid (StateType.Prelight));
319
var hoverTextCol = Convert (Style.Text (StateType.Prelight));
321
hoverGrad.AddColorStop (0, unselectedCol);
323
hoverGrad.AddColorStop (1, unselectedCol);
325
var selectedGrad = new LinearGradient (0, 0, 0, headerHeight);
326
var selectedCol = Convert (Style.Mid (StateType.Normal));
327
var selectedTextCol = Convert (Style.Text (StateType.Normal));
329
selectedGrad.AddColorStop (0, selectedCol);
331
selectedGrad.AddColorStop (1, selectedCol);
333
for (int i = 0; i < sections.Count; i++) {
334
var section = sections[i];
335
bool isActive = activeIndex == i;
336
bool isHover = hoverIndex == i;
338
cr.Rectangle (0, 0, w, headerHeight);
339
cr.Pattern = isActive? selectedGrad : (isHover? hoverGrad : unselectedGrad);
342
cr.Color = isActive? selectedTextCol : (isHover? hoverTextCol : unselectedTextCol);
343
layout.SetText (section.Title);
344
layout.Ellipsize = Pango.EllipsizeMode.End;
345
layout.Width = (int) ((w - headerPadding - headerPadding) * Pango.Scale.PangoScale);
346
cr.MoveTo (headerPadding, headerPadding);
347
PangoCairoHelper.ShowLayout (cr, layout);
349
cr.MoveTo (-halfLineWidth, i > activeIndex? -halfLineWidth : headerHeight + halfLineWidth);
350
cr.RelLineTo (w + borderLineWidth, 0.0);
351
cr.Color = borderCol;
354
cr.Translate (0, headerHeight + borderLineWidth);
356
cr.Translate (0, section.Child.Allocation.Height + borderLineWidth);
360
PropagateExpose (sections[activeIndex].Child, evnt);
361
return true;// base.OnExposeEvent (evnt);
364
protected override void ForAll (bool include_internals, Callback callback)
366
for (int i = 0; i < sections.Count; i++) {
367
var section = sections[i];
368
callback (section.Child);
369
//callbacks can remove the widget
370
if (sections.Count > 0 && section != sections[i])
375
protected override bool OnEnterNotifyEvent (EventCrossing evnt)
377
trackingHover = true;
378
var hoverIdx = GetSectionHeaderAtPosition ((int)evnt.X, (int)evnt.Y);
379
SetHoverIndex (hoverIdx);
380
return base.OnEnterNotifyEvent (evnt);
383
protected override bool OnLeaveNotifyEvent (EventCrossing evnt)
385
trackingHover = false;
387
return base.OnEnterNotifyEvent (evnt);
390
protected override bool OnMotionNotifyEvent (EventMotion evnt)
393
var hoverIdx = GetSectionHeaderAtPosition ((int)evnt.X, (int)evnt.Y);
394
SetHoverIndex (hoverIdx);
396
return base.OnMotionNotifyEvent (evnt);
399
protected override bool OnButtonPressEvent (EventButton evnt)
401
var idx = GetSectionHeaderAtPosition ((int)evnt.X, (int)evnt.Y);
406
return base.OnButtonPressEvent (evnt);
409
int GetSectionHeaderAtPosition (int x, int y)
411
if (sections.Count == 0)
414
//the event window already is within the border so only need to calc width
415
var alloc = Allocation;
416
int borderWidth = (int) BorderWidth;
418
if (y < 0 || x < 0 || x > (alloc.Width - borderWidth - borderWidth))
421
int childWidgetStart = (headerHeight + borderLineWidth) * (activeIndex + 1);
422
if (y < childWidgetStart)
423
return y / (headerHeight + borderLineWidth);
425
y -= ActiveSection.Child.Allocation.Height;
426
if (y < childWidgetStart)
429
int idx = y / (headerHeight + borderLineWidth);
430
return idx < sections.Count? idx : -1;
433
Gdk.Rectangle GetSectionHeaderArea (int index)
435
int borderWidth = (int) BorderWidth;
436
var rect = Allocation;
437
rect.X += borderWidth;
438
rect.Y += borderWidth;
439
rect.Width -= borderWidth * 2;
440
rect.Height = headerHeight + borderLineWidth + borderLineWidth;
442
rect.Y += (headerHeight + borderLineWidth) * (index);
443
if (index > activeIndex)
444
rect.Y += ActiveSection.Child.Allocation.Height;
449
void SetHoverIndex (int index)
451
if (hoverIndex == index)
453
int old = hoverIndex;
455
RepaintSectionHeader (old);
456
RepaintSectionHeader (index);
459
void RepaintSectionHeader (int index)
461
if (index < 0 || index >= sections.Count)
463
var rect = GetSectionHeaderArea (index);
464
QueueDrawArea (rect.X, rect.Y, rect.Width, rect.Height);
470
public SectionList Parent { get; internal set; }
471
public Widget Child { get; internal set; }
473
internal Section (SectionList parent, string title, Widget child)
475
this.Parent = parent;
480
public bool IsActive {
481
get { return Parent.ActiveSection == this; }
482
set { Parent.ActiveSection = this; }
485
public string Title {
486
get { return this.title; }
489
int idx = Parent.sections.IndexOf (this);
490
Parent.RepaintSectionHeader (idx);
494
internal void OnActivated ()
498
evt (this, EventArgs.Empty);
501
public event EventHandler Activated;
b'\\ No newline at end of file'