~ubuntu-branches/ubuntu/oneiric/monodevelop/oneiric

« back to all changes in this revision

Viewing changes to src/core/MonoDevelop.Ide/MonoDevelop.Components/SectionList.cs

  • Committer: Bazaar Package Importer
  • Author(s): Jo Shields
  • Date: 2011-06-27 17:03:13 UTC
  • mto: (1.8.1 upstream)
  • mto: This revision was merged to the branch mainline in revision 54.
  • Revision ID: james.westby@ubuntu.com-20110627170313-6cvz3s19x6e9hqe9
ImportĀ upstreamĀ versionĀ 2.5.92+dfsg

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// 
 
2
// SectionList.cs
 
3
//  
 
4
// Author:
 
5
//       Michael Hutchinson <mhutchinson@novell.com>
 
6
// 
 
7
// Copyright (c) 2011 Novell, Inc.
 
8
// 
 
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:
 
15
// 
 
16
// The above copyright notice and this permission notice shall be included in
 
17
// all copies or substantial portions of the Software.
 
18
// 
 
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
 
25
// THE SOFTWARE.
 
26
 
 
27
using System;
 
28
using System.Collections.Generic;
 
29
using Gtk;
 
30
using Gdk;
 
31
using Cairo;
 
32
 
 
33
namespace MonoDevelop.Components
 
34
{
 
35
        class SectionList : Container
 
36
        {
 
37
                const int borderLineWidth = 1;
 
38
                const int headerPadding = 2;
 
39
                
 
40
                List<Section> sections = new List<Section> ();
 
41
                
 
42
                Gdk.Window inputWindow;
 
43
                Pango.Layout layout;
 
44
                int headerHeight = 20;
 
45
                int activeIndex = 0;
 
46
                int hoverIndex = -1;
 
47
                bool trackingHover = false;
 
48
                
 
49
                public int ActiveIndex {
 
50
                        get { return activeIndex; }
 
51
                        set {
 
52
                                if (value >= sections.Count || value < 0)
 
53
                                        throw new ArgumentOutOfRangeException ();
 
54
                                
 
55
                                if (activeIndex == value)
 
56
                                        return;
 
57
                                
 
58
                                int oldIndex = activeIndex;
 
59
                                activeIndex = value;
 
60
                                //FIXME: implement real focus support
 
61
                                FocusChain = new Widget[] { sections[value].Child };
 
62
                                UpdateVisibility ();
 
63
                                RepaintSectionHeader (oldIndex);
 
64
                                RepaintSectionHeader (activeIndex);
 
65
                                OnActiveSectionChanged ();
 
66
                                sections[activeIndex].OnActivated ();
 
67
                        }
 
68
                }
 
69
                
 
70
                public Section ActiveSection {
 
71
                        get {
 
72
                                return ActiveIndex >= 0 && sections.Count > 0? sections[activeIndex] : null; 
 
73
                        }
 
74
                        set {
 
75
                                int idx = sections.IndexOf (value);
 
76
                                if (idx < 0)
 
77
                                        throw new ArgumentException ("Section is not in this container");
 
78
                                ActiveIndex = idx;
 
79
                        }
 
80
                }
 
81
                
 
82
                void OnActiveSectionChanged ()
 
83
                {
 
84
                        var evt = ActiveSectionChanged;
 
85
                        if (evt != null)
 
86
                                evt (this, EventArgs.Empty);
 
87
                }
 
88
                
 
89
                public event EventHandler ActiveSectionChanged;
 
90
                
 
91
                public SectionList ()
 
92
                {
 
93
                        this.WidgetFlags |= WidgetFlags.NoWindow;
 
94
                        WidthRequest = 100;
 
95
                        EnsureLayout ();
 
96
                }
 
97
                
 
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)
 
100
                {
 
101
                }
 
102
                
 
103
                protected override void OnRealized ()
 
104
                {
 
105
                        base.OnRealized ();
 
106
                        
 
107
                        var alloc = Allocation;
 
108
                        int bw = (int) BorderWidth;
 
109
                        
 
110
                        var attributes = new Gdk.WindowAttr () {
 
111
                                WindowType = Gdk.WindowType.Child,
 
112
                                Wclass = Gdk.WindowClass.InputOnly,
 
113
                                EventMask = (int) (
 
114
                                        EventMask.EnterNotifyMask |
 
115
                                        EventMask.LeaveNotifyMask |
 
116
                                        EventMask.PointerMotionMask |
 
117
                                        EventMask.ButtonPressMask | 
 
118
                                        EventMask.ButtonReleaseMask
 
119
                                ),
 
120
                                X = alloc.X + bw,
 
121
                                Y = alloc.Y + bw,
 
122
                                Width = alloc.Width - bw * 2,
 
123
                                Height = alloc.Height - bw * 2,
 
124
                        };
 
125
                        
 
126
                        var attrMask = Gdk.WindowAttributesType.X | Gdk.WindowAttributesType.Y;
 
127
                        
 
128
                        inputWindow = new Gdk.Window (Parent.GdkWindow, attributes, (int) attrMask);
 
129
                        inputWindow.UserData = Handle;
 
130
                }
 
131
                
 
132
                protected override void OnUnrealized ()
 
133
                {
 
134
                        base.OnUnrealized ();
 
135
                        
 
136
                        inputWindow.UserData = IntPtr.Zero;
 
137
                        inputWindow.Destroy ();
 
138
                        inputWindow = null;
 
139
                }
 
140
                
 
141
                protected override void OnMapped ()
 
142
                {
 
143
                        //show our window before the children, so they're on top
 
144
                        inputWindow.Show ();
 
145
                        base.OnMapped ();
 
146
                }
 
147
                
 
148
                protected override void OnUnmapped ()
 
149
                {
 
150
                        base.OnUnmapped ();
 
151
                        inputWindow.Hide ();
 
152
                }
 
153
                
 
154
                public Section AddSection (string title, Widget child)
 
155
                {
 
156
                        var s = new Section (this, title, child);
 
157
                        sections.Add (s);
 
158
                        child.Parent = this;
 
159
                        return s;
 
160
                }
 
161
                
 
162
                protected override void OnStyleSet (Style previous)
 
163
                {
 
164
                        base.OnStyleSet (previous);
 
165
                        KillLayout ();
 
166
                        EnsureLayout ();
 
167
                }
 
168
                
 
169
                void EnsureLayout ()
 
170
                {
 
171
                        if (layout != null)
 
172
                                layout.Dispose ();
 
173
                        layout = new Pango.Layout (PangoContext);
 
174
                        
 
175
                        layout.SetText ("lp");
 
176
                        int w, h;
 
177
                        layout.GetPixelSize (out w, out h);
 
178
                        headerHeight = Math.Max (20, h + headerPadding + headerPadding);
 
179
                }
 
180
                
 
181
                void KillLayout ()
 
182
                {
 
183
                        if (layout == null)
 
184
                                return;
 
185
                        layout.Dispose ();
 
186
                        layout = null;
 
187
                }
 
188
 
 
189
                protected override void OnAdded (Widget widget)
 
190
                {
 
191
                        throw new InvalidOperationException ("Cannot add widget directly, must add a section");
 
192
                }
 
193
                
 
194
                protected override void OnRemoved (Widget widget)
 
195
                {
 
196
                        for (int i = 0; i < sections.Count; i++) {
 
197
                                var section = sections[i];
 
198
                                if (section.Child == widget) {
 
199
                                        widget.Unparent ();
 
200
                                        section.Parent = null;
 
201
                                        section.Child = null;
 
202
                                        sections.RemoveAt (i);
 
203
                                        break;
 
204
                                }
 
205
                        }
 
206
                }
 
207
                
 
208
                protected override void OnShown ()
 
209
                {
 
210
                        UpdateVisibility ();
 
211
                        base.OnShown ();
 
212
                }
 
213
 
 
214
                protected override void OnSizeRequested (ref Requisition requisition)
 
215
                {
 
216
                        int wr = 0, hr = 0;
 
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);
 
221
                        }
 
222
                        
 
223
                        hr += sections.Count * headerHeight + borderLineWidth;
 
224
                        
 
225
                        int bw2 = ((int)BorderWidth + borderLineWidth) * 2;
 
226
                        
 
227
                        hr += bw2;
 
228
                        wr += bw2;
 
229
                        
 
230
                        hr = Math.Max (hr, HeightRequest);
 
231
                        wr = Math.Max (wr, WidthRequest);
 
232
                        
 
233
                        requisition.Height = hr;
 
234
                        requisition.Width = wr;
 
235
                }
 
236
 
 
237
                protected override void OnSizeAllocated (Gdk.Rectangle allocation)
 
238
                {
 
239
                        base.OnSizeAllocated (allocation);
 
240
                        if (sections.Count == 0)
 
241
                                return;
 
242
                        
 
243
                        int bw = (int) BorderWidth + borderLineWidth;
 
244
                        int bw2 = bw * 2;
 
245
                        allocation.Width -= bw2;
 
246
                        allocation.Height -= bw2;
 
247
                        allocation.X += bw;
 
248
                        allocation.Y += bw;
 
249
                        
 
250
                        if (IsRealized) {
 
251
                                inputWindow.MoveResize (allocation);
 
252
                        }
 
253
                        
 
254
                        allocation.Height -= (borderLineWidth + headerHeight) * sections.Count;
 
255
                        allocation.Y += (headerHeight + borderLineWidth) * (activeIndex + 1);
 
256
                        
 
257
                        sections[activeIndex].Child.SizeAllocate (allocation);
 
258
                }
 
259
                
 
260
                void UpdateVisibility ()
 
261
                {
 
262
                        for (int i = 0; i < sections.Count; i++) {
 
263
                                var section = sections[i];
 
264
                                if (activeIndex == i) {
 
265
                                        section.Child.Show ();
 
266
                                } else {
 
267
                                        section.Child.Hide ();
 
268
                                }
 
269
                        }
 
270
                }
 
271
                
 
272
                static Cairo.Color Convert (Gdk.Color color)
 
273
                {
 
274
                        return new Cairo.Color (
 
275
                                color.Red   / (double) ushort.MaxValue,
 
276
                                color.Green / (double) ushort.MaxValue,
 
277
                                color.Blue  / (double) ushort.MaxValue);
 
278
                }
 
279
                
 
280
                //FIXME: respect damage regions not just the whole areas, and skip more work when possible
 
281
                protected override bool OnExposeEvent (Gdk.EventExpose evnt)
 
282
                {
 
283
                        if (sections.Count == 0)
 
284
                                return false;
 
285
                        
 
286
                        var alloc = Allocation;
 
287
                        
 
288
                        int bw = (int) BorderWidth;
 
289
                        double halfLineWidth = borderLineWidth / 2.0;
 
290
                        int bw2 = bw * 2;
 
291
                        int w = alloc.Width - bw2;
 
292
                        int h = alloc.Height - bw2;
 
293
                        
 
294
                        using (var cr = CairoHelper.Create (evnt.Window)) {
 
295
                                CairoHelper.Region (cr, evnt.Region);
 
296
                                cr.Clip ();
 
297
                                
 
298
                                cr.Translate (alloc.X + bw, alloc.Y + bw);
 
299
                                
 
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;
 
304
                                cr.Stroke ();
 
305
                                
 
306
                                cr.Translate (borderLineWidth, borderLineWidth);
 
307
                                w = w - (2 * borderLineWidth);
 
308
                                
 
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);
 
314
                                unselectedCol.A = 1;
 
315
                                unselectedGrad.AddColorStop (1, unselectedCol);
 
316
                                
 
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));
 
320
                                hoverCol.A = 0.6;
 
321
                                hoverGrad.AddColorStop (0, unselectedCol);
 
322
                                hoverCol.A = 1;
 
323
                                hoverGrad.AddColorStop (1, unselectedCol);
 
324
                                
 
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));
 
328
                                selectedCol.A = 0.6;
 
329
                                selectedGrad.AddColorStop (0, selectedCol);
 
330
                                selectedCol.A = 1;
 
331
                                selectedGrad.AddColorStop (1, selectedCol);
 
332
                                
 
333
                                for (int i = 0; i < sections.Count; i++) {
 
334
                                        var section = sections[i];
 
335
                                        bool isActive = activeIndex == i;
 
336
                                        bool isHover = hoverIndex == i;
 
337
                                        
 
338
                                        cr.Rectangle (0, 0, w, headerHeight);
 
339
                                        cr.Pattern = isActive? selectedGrad : (isHover? hoverGrad : unselectedGrad);
 
340
                                        cr.Fill ();
 
341
                                        
 
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);
 
348
                                        
 
349
                                        cr.MoveTo (-halfLineWidth, i > activeIndex? -halfLineWidth : headerHeight + halfLineWidth);
 
350
                                        cr.RelLineTo (w + borderLineWidth, 0.0);
 
351
                                        cr.Color = borderCol;
 
352
                                        cr.Stroke ();
 
353
                                        
 
354
                                        cr.Translate (0, headerHeight + borderLineWidth);
 
355
                                        if (isActive)
 
356
                                                cr.Translate (0, section.Child.Allocation.Height + borderLineWidth);
 
357
                                }
 
358
                        }
 
359
                        
 
360
                        PropagateExpose (sections[activeIndex].Child, evnt);
 
361
                        return true;// base.OnExposeEvent (evnt);
 
362
                }
 
363
 
 
364
                protected override void ForAll (bool include_internals, Callback callback)
 
365
                {
 
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])
 
371
                                        i--;
 
372
                        }
 
373
                }
 
374
                
 
375
                protected override bool OnEnterNotifyEvent (EventCrossing evnt)
 
376
                {
 
377
                        trackingHover = true;
 
378
                        var hoverIdx = GetSectionHeaderAtPosition ((int)evnt.X, (int)evnt.Y);
 
379
                        SetHoverIndex (hoverIdx);
 
380
                        return base.OnEnterNotifyEvent (evnt);
 
381
                }
 
382
                
 
383
                protected override bool OnLeaveNotifyEvent (EventCrossing evnt)
 
384
                {
 
385
                        trackingHover = false;
 
386
                        SetHoverIndex (-1);
 
387
                        return base.OnEnterNotifyEvent (evnt);
 
388
                }
 
389
                
 
390
                protected override bool OnMotionNotifyEvent (EventMotion evnt)
 
391
                {
 
392
                        if (trackingHover) {
 
393
                                var hoverIdx = GetSectionHeaderAtPosition ((int)evnt.X, (int)evnt.Y);
 
394
                                SetHoverIndex (hoverIdx);
 
395
                        }
 
396
                        return base.OnMotionNotifyEvent (evnt);
 
397
                }
 
398
                
 
399
                protected override bool OnButtonPressEvent (EventButton evnt)
 
400
                {
 
401
                        var idx = GetSectionHeaderAtPosition ((int)evnt.X, (int)evnt.Y);
 
402
                        if (idx >= 0) {
 
403
                                ActiveIndex = idx;
 
404
                                return true;
 
405
                        }
 
406
                        return base.OnButtonPressEvent (evnt);
 
407
                }
 
408
                
 
409
                int GetSectionHeaderAtPosition (int x, int y)
 
410
                {
 
411
                        if (sections.Count == 0)
 
412
                                return -1;
 
413
                        
 
414
                        //the event window already is within the border so only need to calc width
 
415
                        var alloc = Allocation;
 
416
                        int borderWidth = (int) BorderWidth;
 
417
                        
 
418
                        if (y < 0 || x < 0 || x > (alloc.Width - borderWidth - borderWidth))
 
419
                                return -1;
 
420
                        
 
421
                        int childWidgetStart = (headerHeight + borderLineWidth) * (activeIndex + 1);
 
422
                        if (y < childWidgetStart)
 
423
                                return y / (headerHeight + borderLineWidth);
 
424
                        
 
425
                        y -= ActiveSection.Child.Allocation.Height;
 
426
                        if (y < childWidgetStart)
 
427
                                return -1;
 
428
                        
 
429
                        int idx = y / (headerHeight + borderLineWidth);
 
430
                        return idx < sections.Count? idx : -1;
 
431
                }
 
432
                
 
433
                Gdk.Rectangle GetSectionHeaderArea (int index)
 
434
                {
 
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;
 
441
                        
 
442
                        rect.Y += (headerHeight + borderLineWidth) * (index);
 
443
                        if (index > activeIndex)
 
444
                                rect.Y += ActiveSection.Child.Allocation.Height;
 
445
                        
 
446
                        return rect;
 
447
                }
 
448
                
 
449
                void SetHoverIndex (int index)
 
450
                {
 
451
                        if (hoverIndex == index)
 
452
                                return;
 
453
                        int old = hoverIndex;
 
454
                        hoverIndex = index;
 
455
                        RepaintSectionHeader (old);
 
456
                        RepaintSectionHeader (index);
 
457
                }
 
458
                
 
459
                void RepaintSectionHeader (int index)
 
460
                {
 
461
                        if (index < 0 || index >= sections.Count)
 
462
                                return;
 
463
                        var rect = GetSectionHeaderArea (index);
 
464
                        QueueDrawArea (rect.X, rect.Y, rect.Width, rect.Height);
 
465
                }
 
466
                
 
467
                public class Section
 
468
                {
 
469
                        string title;
 
470
                        public SectionList Parent { get; internal set; }
 
471
                        public Widget Child { get; internal set; }
 
472
                        
 
473
                        internal Section (SectionList parent, string title, Widget child)
 
474
                        {
 
475
                                this.Parent = parent;
 
476
                                this.title = title;
 
477
                                this.Child = child;
 
478
                        }
 
479
                        
 
480
                        public bool IsActive {
 
481
                                get { return Parent.ActiveSection == this; }
 
482
                                set { Parent.ActiveSection = this; }
 
483
                        }
 
484
                        
 
485
                        public string Title {
 
486
                                get { return this.title; }
 
487
                                set {
 
488
                                        this.title = value;
 
489
                                        int idx = Parent.sections.IndexOf (this);
 
490
                                        Parent.RepaintSectionHeader (idx);
 
491
                                }
 
492
                        }
 
493
                        
 
494
                        internal void OnActivated ()
 
495
                        {
 
496
                                var evt = Activated;
 
497
                                if (evt != null)
 
498
                                        evt (this, EventArgs.Empty);
 
499
                        }
 
500
                        
 
501
                        public event EventHandler Activated;
 
502
                }
 
503
        }
 
504
}
 
 
b'\\ No newline at end of file'