~ubuntu-branches/ubuntu/trusty/monodevelop/trusty-proposed

« back to all changes in this revision

Viewing changes to src/core/MonoDevelop.Ide/MonoDevelop.Components.PropertyGrid/PropertyGridTable.cs

  • Committer: Package Import Robot
  • Author(s): Jo Shields
  • Date: 2013-05-12 09:46:03 UTC
  • mto: This revision was merged to the branch mainline in revision 29.
  • Revision ID: package-import@ubuntu.com-20130512094603-mad323bzcxvmcam0
Tags: upstream-4.0.5+dfsg
ImportĀ upstreamĀ versionĀ 4.0.5+dfsg

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// 
 
2
// PropertyGridTable.cs
 
3
//  
 
4
// Author:
 
5
//       Lluis Sanchez <lluis@xamarin.com>
 
6
// 
 
7
// Copyright (c) 2012 Xamarin 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
using System;
 
27
using Gtk;
 
28
using Gdk;
 
29
using System.ComponentModel;
 
30
using System.Collections.Generic;
 
31
using Cairo;
 
32
using System.Linq;
 
33
 
 
34
namespace MonoDevelop.Components.PropertyGrid
 
35
{
 
36
        class PropertyGridTable: Gtk.EventBox
 
37
        {
 
38
                EditorManager editorManager;
 
39
                List<TableRow> rows = new List<TableRow> ();
 
40
                Dictionary<Gtk.Widget, Gdk.Rectangle> children = new Dictionary<Gtk.Widget, Gdk.Rectangle> ();
 
41
                EditSession editSession;
 
42
                Gtk.Widget currentEditor;
 
43
                TableRow currentEditorRow;
 
44
                bool draggingDivider;
 
45
                Gdk.Pixbuf discloseDown;
 
46
                Gdk.Pixbuf discloseUp;
 
47
                bool heightMeasured;
 
48
 
 
49
                const int CategoryTopBottomPadding = 6;
 
50
                const int CategoryLeftPadding = 8;
 
51
                const int PropertyTopBottomPadding = 5;
 
52
                const int PropertyLeftPadding = 8;
 
53
                const int PropertyContentLeftPadding = 8;
 
54
                const int PropertyIndent = 8;
 
55
                static readonly Cairo.Color LabelBackgroundColor = new Cairo.Color (250d/255d, 250d/255d, 250d/255d);
 
56
                static readonly Cairo.Color DividerColor = new Cairo.Color (217d/255d, 217d/255d, 217d/255d);
 
57
                static readonly Cairo.Color CategoryLabelColor = new Cairo.Color (128d/255d, 128d/255d, 128d/255d);
 
58
 
 
59
                const uint animationTimeSpan = 10;
 
60
                const int animationStepSize = 20;
 
61
 
 
62
                double dividerPosition = 0.5;
 
63
 
 
64
                Gdk.Cursor resizeCursor;
 
65
                Gdk.Cursor handCursor;
 
66
 
 
67
                class TableRow
 
68
                {
 
69
                        public bool IsCategory;
 
70
                        public string Label;
 
71
                        public object Instace;
 
72
                        public PropertyDescriptor Property;
 
73
                        public List<TableRow> ChildRows;
 
74
                        public bool Expanded;
 
75
                        public bool Enabled = true;
 
76
                        public Gdk.Rectangle EditorBounds;
 
77
                        public bool AnimatingExpand;
 
78
                        public int AnimationHeight;
 
79
                        public int ChildrenHeight;
 
80
                        public uint AnimationHandle;
 
81
                }
 
82
 
 
83
                public PropertyGridTable (EditorManager editorManager, PropertyGrid parentGrid)
 
84
                {
 
85
                        Mono.TextEditor.GtkWorkarounds.FixContainerLeak (this);
 
86
 
 
87
                        this.editorManager = editorManager;
 
88
                        WidgetFlags |= Gtk.WidgetFlags.AppPaintable;
 
89
                        Events |= Gdk.EventMask.PointerMotionMask;
 
90
                        CanFocus = true;
 
91
                        resizeCursor = new Cursor (CursorType.SbHDoubleArrow);
 
92
                        handCursor = new Cursor (CursorType.Hand1);
 
93
                        discloseDown = Gdk.Pixbuf.LoadFromResource ("disclose-arrow-down.png");
 
94
                        discloseUp = Gdk.Pixbuf.LoadFromResource ("disclose-arrow-up.png");
 
95
                }
 
96
 
 
97
                protected override void OnDestroyed ()
 
98
                {
 
99
                        StopAllAnimations ();
 
100
                        base.OnDestroyed ();
 
101
                        resizeCursor.Dispose ();
 
102
                        handCursor.Dispose ();
 
103
                }
 
104
 
 
105
                public event EventHandler Changed;
 
106
 
 
107
                public PropertySort PropertySort { get; set; }
 
108
 
 
109
                public ShadowType ShadowType { get; set; }
 
110
 
 
111
                public void CommitChanges ()
 
112
                {
 
113
                        EndEditing ();
 
114
                }
 
115
 
 
116
                HashSet<string> expandedStatus;
 
117
 
 
118
                public void SaveStatus ()
 
119
                {
 
120
                        expandedStatus = new HashSet<string> ();
 
121
                        foreach (var r in rows.Where (r => r.IsCategory))
 
122
                                if (!r.Expanded)
 
123
                                        expandedStatus.Add (r.Label);
 
124
                }
 
125
 
 
126
                public void RestoreStatus ()
 
127
                {
 
128
                        if (expandedStatus == null)
 
129
                                return;
 
130
 
 
131
                        foreach (var row in rows.Where (r => r.IsCategory))
 
132
                                row.Expanded = !expandedStatus.Contains (row.Label);
 
133
 
 
134
                        expandedStatus = null;
 
135
 
 
136
                        QueueDraw ();
 
137
                        QueueResize ();
 
138
                }
 
139
 
 
140
                public virtual void Clear ()
 
141
                {
 
142
                        heightMeasured = false;
 
143
                        StopAllAnimations ();
 
144
                        EndEditing ();
 
145
                        rows.Clear ();
 
146
                        QueueDraw ();
 
147
                        QueueResize ();
 
148
                }
 
149
 
 
150
                internal void Populate (PropertyDescriptorCollection properties, object instance)
 
151
                {
 
152
                        bool categorised = PropertySort == PropertySort.Categorized;
 
153
                
 
154
                        //transcribe browsable properties
 
155
                        var sorted = new List<PropertyDescriptor>();
 
156
 
 
157
                        foreach (PropertyDescriptor descriptor in properties)
 
158
                                if (descriptor.IsBrowsable)
 
159
                                        sorted.Add (descriptor);
 
160
                        
 
161
                        if (sorted.Count == 0)
 
162
                                return;
 
163
                        
 
164
                        if (!categorised) {
 
165
                                if (PropertySort != PropertySort.NoSort)
 
166
                                        sorted.Sort ((a,b) => a.DisplayName.CompareTo (b.DisplayName));
 
167
                                foreach (PropertyDescriptor pd in sorted)
 
168
                                        AppendProperty (rows, pd, instance);
 
169
                        }
 
170
                        else {
 
171
                                sorted.Sort ((a,b) => {
 
172
                                        var c = a.Category.CompareTo (b.Category);
 
173
                                        return c != 0 ? c : a.DisplayName.CompareTo (b.DisplayName);
 
174
                                });
 
175
                                TableRow lastCat = null;
 
176
                                List<TableRow> rowList = rows;
 
177
 
 
178
                                foreach (PropertyDescriptor pd in sorted) {
 
179
                                        if (!string.IsNullOrEmpty (pd.Category) && (lastCat == null || pd.Category != lastCat.Label)) {
 
180
                                                TableRow row = new TableRow ();
 
181
                                                row.IsCategory = true;
 
182
                                                row.Expanded = true;
 
183
                                                row.Label = pd.Category;
 
184
                                                row.ChildRows = new List<TableRow> ();
 
185
                                                rows.Add (row);
 
186
                                                lastCat = row;
 
187
                                                rowList = row.ChildRows;
 
188
                                        }
 
189
                                        AppendProperty (rowList, pd, instance);
 
190
                                }
 
191
                        }
 
192
                        QueueDraw ();
 
193
                        QueueResize ();
 
194
                }
 
195
                
 
196
                internal void Update (PropertyDescriptorCollection properties, object instance)
 
197
                {
 
198
                        foreach (PropertyDescriptor pd in properties)
 
199
                                UpdateProperty (pd, instance, rows);
 
200
                        QueueDraw ();
 
201
                        QueueResize ();
 
202
                }
 
203
                
 
204
                bool UpdateProperty (PropertyDescriptor pd, object instance, IEnumerable<TableRow> rowList)
 
205
                {
 
206
                        foreach (var row in rowList) {
 
207
                                if (row.Property != null && row.Property.Name == pd.Name && row.Instace == instance) {
 
208
                                        row.Property = pd;
 
209
                                        return true;
 
210
                                }
 
211
                                if (row.ChildRows != null) {
 
212
                                        if (UpdateProperty (pd, instance, row.ChildRows))
 
213
                                                return true;
 
214
                                }
 
215
                        }
 
216
                        return false;
 
217
                }
 
218
        
 
219
                void AppendProperty (PropertyDescriptor prop, object instance)
 
220
                {
 
221
                        AppendProperty (rows, prop, new InstanceData (instance));
 
222
                }
 
223
                
 
224
                void AppendProperty (List<TableRow> rowList, PropertyDescriptor prop, object instance)
 
225
                {
 
226
                        TableRow row = new TableRow () {
 
227
                                IsCategory = false,
 
228
                                Property = prop,
 
229
                                Label = prop.DisplayName,
 
230
                                Instace = instance
 
231
                        };
 
232
                        rowList.Add (row);
 
233
 
 
234
                        TypeConverter tc = prop.Converter;
 
235
                        if (typeof (ExpandableObjectConverter).IsAssignableFrom (tc.GetType ())) {
 
236
                                object cob = prop.GetValue (instance);
 
237
                                row.ChildRows = new List<TableRow> ();
 
238
                                foreach (PropertyDescriptor cprop in TypeDescriptor.GetProperties (cob))
 
239
                                        AppendProperty (row.ChildRows, cprop, cob);
 
240
                        }
 
241
                }
 
242
 
 
243
                PropertyEditorCell GetCell (TableRow row)
 
244
                {
 
245
                        var e = editorManager.GetEditor (row.Property);
 
246
                        e.Initialize (this, editorManager, row.Property, row.Instace);
 
247
                        return e;
 
248
                }
 
249
                
 
250
                protected override void ForAll (bool includeInternals, Gtk.Callback callback)
 
251
                {
 
252
                        base.ForAll (includeInternals, callback);
 
253
                        foreach (var c in children.Keys.ToArray ())
 
254
                                callback (c);
 
255
                }
 
256
 
 
257
                protected override void OnSizeRequested (ref Requisition requisition)
 
258
                {
 
259
                        requisition.Width = 20;
 
260
 
 
261
                        int dx = (int)((double)Allocation.Width * dividerPosition) - PropertyContentLeftPadding;
 
262
                        if (dx < 0) dx = 0;
 
263
                        int y = 0;
 
264
                        MeasureHeight (rows, ref y);
 
265
                        requisition.Height = y;
 
266
 
 
267
                        foreach (var c in children)
 
268
                                c.Key.SizeRequest ();
 
269
                }
 
270
 
 
271
                protected override void OnSizeAllocated (Gdk.Rectangle allocation)
 
272
                {
 
273
                        base.OnSizeAllocated (allocation);
 
274
                        int y = 0;
 
275
                        MeasureHeight (rows, ref y);
 
276
                        if (currentEditorRow != null)
 
277
                                children [currentEditor] = currentEditorRow.EditorBounds;
 
278
                        foreach (var cr in children) {
 
279
                                var r = cr.Value;
 
280
                                cr.Key.SizeAllocate (new Gdk.Rectangle (r.X, r.Y, r.Width, r.Height));
 
281
                        }
 
282
                }
 
283
 
 
284
                public void SetAllocation (Gtk.Widget w, Gdk.Rectangle rect)
 
285
                {
 
286
                        children [w] = rect;
 
287
                        QueueResize ();
 
288
                }
 
289
                
 
290
                protected override void OnAdded (Gtk.Widget widget)
 
291
                {
 
292
                        children.Add (widget, new Gdk.Rectangle (0,0,0,0));
 
293
                        widget.Parent = this;
 
294
                        QueueResize ();
 
295
                }
 
296
                
 
297
                protected override void OnRemoved (Gtk.Widget widget)
 
298
                {
 
299
                        children.Remove (widget);
 
300
                        widget.Unparent ();
 
301
                }
 
302
 
 
303
                void MeasureHeight (IEnumerable<TableRow> rowList, ref int y)
 
304
                {
 
305
                        heightMeasured = true;
 
306
                        Pango.Layout layout = new Pango.Layout (PangoContext);
 
307
                        foreach (var r in rowList) {
 
308
                                layout.SetText (r.Label);
 
309
                                int w,h;
 
310
                                layout.GetPixelSize (out w, out h);
 
311
                                if (r.IsCategory) {
 
312
                                        r.EditorBounds = new Gdk.Rectangle (0, y, Allocation.Width, h + CategoryTopBottomPadding * 2);
 
313
                                        y += h + CategoryTopBottomPadding * 2;
 
314
                                }
 
315
                                else {
 
316
                                        int eh;
 
317
                                        int dividerX = (int)((double)Allocation.Width * dividerPosition);
 
318
                                        var cell = GetCell (r);
 
319
                                        cell.GetSize (Allocation.Width - dividerX, out w, out eh);
 
320
                                        eh = Math.Max (h + PropertyTopBottomPadding * 2, eh);
 
321
                                        r.EditorBounds = new Gdk.Rectangle (dividerX + PropertyContentLeftPadding, y, Allocation.Width - dividerX - PropertyContentLeftPadding, eh);
 
322
                                        y += eh;
 
323
                                }
 
324
                                if (r.ChildRows != null && (r.Expanded || r.AnimatingExpand)) {
 
325
                                        int py = y;
 
326
                                        MeasureHeight (r.ChildRows, ref y);
 
327
                                        r.ChildrenHeight = y - py;
 
328
                                        if (r.AnimatingExpand)
 
329
                                                y = py + r.AnimationHeight;
 
330
                                }
 
331
                        }
 
332
                        layout.Dispose ();
 
333
                }
 
334
 
 
335
                protected override bool OnExposeEvent (EventExpose evnt)
 
336
                {
 
337
                        using (Cairo.Context ctx = CairoHelper.Create (evnt.Window)) {
 
338
                                int dx = (int)((double)Allocation.Width * dividerPosition);
 
339
                                ctx.LineWidth = 1;
 
340
                                ctx.Rectangle (0, 0, dx, Allocation.Height);
 
341
                                ctx.Color = LabelBackgroundColor;
 
342
                                ctx.Fill ();
 
343
                                ctx.Rectangle (dx, 0, Allocation.Width - dx, Allocation.Height);
 
344
                                ctx.Color = new Cairo.Color (1, 1, 1);
 
345
                                ctx.Fill ();
 
346
                                ctx.MoveTo (dx + 0.5, 0);
 
347
                                ctx.RelLineTo (0, Allocation.Height);
 
348
                                ctx.Color = DividerColor;
 
349
                                ctx.Stroke ();
 
350
        
 
351
                                int y = 0;
 
352
                                Draw (ctx, rows, dx, PropertyLeftPadding, ref y);
 
353
                        }
 
354
                        return base.OnExposeEvent (evnt);
 
355
                }
 
356
 
 
357
                void Draw (Cairo.Context ctx, List<TableRow> rowList, int dividerX, int x, ref int y)
 
358
                {
 
359
                        if (!heightMeasured)
 
360
                                return;
 
361
 
 
362
                        Pango.Layout layout = new Pango.Layout (PangoContext);
 
363
                        TableRow lastCategory = null;
 
364
 
 
365
                        foreach (var r in rowList) {
 
366
                                int w,h;
 
367
                                layout.SetText (r.Label);
 
368
                                layout.GetPixelSize (out w, out h);
 
369
                                int indent = 0;
 
370
 
 
371
                                if (r.IsCategory) {
 
372
                                        var rh = h + CategoryTopBottomPadding*2;
 
373
                                        ctx.Rectangle (0, y, Allocation.Width, rh);
 
374
                                        Cairo.LinearGradient gr = new LinearGradient (0, y, 0, rh);
 
375
                                        gr.AddColorStop (0, new Cairo.Color (248d/255d, 248d/255d, 248d/255d));
 
376
                                        gr.AddColorStop (1, new Cairo.Color (240d/255d, 240d/255d, 240d/255d));
 
377
                                        ctx.Pattern = gr;
 
378
                                        ctx.Fill ();
 
379
 
 
380
                                        if (lastCategory == null || lastCategory.Expanded || lastCategory.AnimatingExpand) {
 
381
                                                ctx.MoveTo (0, y + 0.5);
 
382
                                                ctx.LineTo (Allocation.Width, y + 0.5);
 
383
                                        }
 
384
                                        ctx.MoveTo (0, y + rh - 0.5);
 
385
                                        ctx.LineTo (Allocation.Width, y + rh - 0.5);
 
386
                                        ctx.Color = DividerColor;
 
387
                                        ctx.Stroke ();
 
388
 
 
389
                                        ctx.MoveTo (x, y + CategoryTopBottomPadding);
 
390
                                        ctx.Color = CategoryLabelColor;
 
391
                                        Pango.CairoHelper.ShowLayout (ctx, layout);
 
392
 
 
393
                                        var img = r.Expanded ? discloseUp : discloseDown;
 
394
                                        CairoHelper.SetSourcePixbuf (ctx, img, Allocation.Width - img.Width - CategoryTopBottomPadding, y + (rh - img.Height) / 2);
 
395
                                        ctx.Paint ();
 
396
 
 
397
                                        y += rh;
 
398
                                        lastCategory = r;
 
399
                                }
 
400
                                else {
 
401
                                        var cell = GetCell (r);
 
402
                                        r.Enabled = !r.Property.IsReadOnly || cell.EditsReadOnlyObject;
 
403
                                        var state = r.Enabled ? State : Gtk.StateType.Insensitive;
 
404
                                        ctx.Save ();
 
405
                                        ctx.Rectangle (0, y, dividerX, h + PropertyTopBottomPadding*2);
 
406
                                        ctx.Clip ();
 
407
                                        ctx.MoveTo (x, y + PropertyTopBottomPadding);
 
408
                                        ctx.Color = Style.Text (state).ToCairoColor ();
 
409
                                        Pango.CairoHelper.ShowLayout (ctx, layout);
 
410
                                        ctx.Restore ();
 
411
 
 
412
                                        if (r != currentEditorRow)
 
413
                                                cell.Render (GdkWindow, ctx, r.EditorBounds, state);
 
414
 
 
415
                                        y += r.EditorBounds.Height;
 
416
                                        indent = PropertyIndent;
 
417
                                }
 
418
 
 
419
                                if (r.ChildRows != null && r.ChildRows.Count > 0 && (r.Expanded || r.AnimatingExpand)) {
 
420
                                        int py = y;
 
421
 
 
422
                                        ctx.Save ();
 
423
                                        if (r.AnimatingExpand)
 
424
                                                ctx.Rectangle (0, y, Allocation.Width, r.AnimationHeight);
 
425
                                        else
 
426
                                                ctx.Rectangle (0, 0, Allocation.Width, Allocation.Height);
 
427
 
 
428
                                        ctx.Clip ();
 
429
                                        Draw (ctx, r.ChildRows, dividerX, x + indent, ref y);
 
430
                                        ctx.Restore ();
 
431
 
 
432
                                        if (r.AnimatingExpand) {
 
433
                                                y = py + r.AnimationHeight;
 
434
                                                // Repaing the background because the cairo clip doesn't work for gdk primitives
 
435
                                                int dx = (int)((double)Allocation.Width * dividerPosition);
 
436
                                                ctx.Rectangle (0, y, dx, Allocation.Height - y);
 
437
                                                ctx.Color = LabelBackgroundColor;
 
438
                                                ctx.Fill ();
 
439
                                                ctx.Rectangle (dx + 1, y, Allocation.Width - dx - 1, Allocation.Height - y);
 
440
                                                ctx.Color = new Cairo.Color (1, 1, 1);
 
441
                                                ctx.Fill ();
 
442
                                        }
 
443
                                }
 
444
                        }
 
445
                }
 
446
 
 
447
                IEnumerable<TableRow> GetAllRows (bool onlyVisible)
 
448
                {
 
449
                        return GetAllRows (rows, onlyVisible);
 
450
                }
 
451
 
 
452
                IEnumerable<TableRow> GetAllRows (IEnumerable<TableRow> rows, bool onlyVisible)
 
453
                {
 
454
                        foreach (var r in rows) {
 
455
                                yield return r;
 
456
                                if (r.ChildRows != null && (!onlyVisible || r.Expanded || r.AnimatingExpand)) {
 
457
                                        foreach (var cr in GetAllRows (r.ChildRows, onlyVisible))
 
458
                                                yield return cr;
 
459
                                }
 
460
                        }
 
461
                }
 
462
 
 
463
                protected override bool OnButtonPressEvent (EventButton evnt)
 
464
                {
 
465
                        if (evnt.Type != EventType.ButtonPress)
 
466
                                return base.OnButtonPressEvent (evnt);
 
467
 
 
468
                        var cat = rows.FirstOrDefault (r => r.IsCategory && r.EditorBounds.Contains ((int)evnt.X, (int)evnt.Y));
 
469
                        if (cat != null) {
 
470
                                cat.Expanded = !cat.Expanded;
 
471
                                if (cat.Expanded)
 
472
                                        StartExpandAnimation (cat);
 
473
                                else
 
474
                                        StartCollapseAnimation (cat);
 
475
                                QueueResize ();
 
476
                                return true;
 
477
                        }
 
478
 
 
479
                        int dx = (int)((double)Allocation.Width * dividerPosition);
 
480
                        if (Math.Abs (dx - evnt.X) < 4) {
 
481
                                draggingDivider = true;
 
482
                                GdkWindow.Cursor = resizeCursor;
 
483
                                return true;
 
484
                        }
 
485
 
 
486
                        TableRow clickedEditor = null;
 
487
                        foreach (var r in GetAllRows (true).Where (r => !r.IsCategory)) {
 
488
                                if (r.EditorBounds.Contains ((int)evnt.X, (int)evnt.Y)) {
 
489
                                        clickedEditor = r;
 
490
                                        break;
 
491
                                }
 
492
                        }
 
493
                        if (clickedEditor != null && clickedEditor.Enabled)
 
494
                                StartEditing (clickedEditor);
 
495
                        else {
 
496
                                EndEditing ();
 
497
                                GrabFocus ();
 
498
                        }
 
499
 
 
500
                        return base.OnButtonPressEvent (evnt);
 
501
                }
 
502
 
 
503
                protected override bool OnButtonReleaseEvent (EventButton evnt)
 
504
                {
 
505
                        if (draggingDivider) {
 
506
                                draggingDivider = false;
 
507
                                QueueResize ();
 
508
                        }
 
509
                        return base.OnButtonReleaseEvent (evnt);
 
510
                }
 
511
 
 
512
                protected override bool OnMotionNotifyEvent (EventMotion evnt)
 
513
                {
 
514
                        if (draggingDivider) {
 
515
                                var px = evnt.X;
 
516
                                if (px < 10)
 
517
                                        px = 10;
 
518
                                else if (px > Allocation.Width - 10)
 
519
                                        px = Allocation.Width - 10;
 
520
                                dividerPosition = px / (double) Allocation.Width;
 
521
                                QueueResize ();
 
522
                                return true;
 
523
                        }
 
524
 
 
525
                        var cat = rows.FirstOrDefault (r => r.IsCategory && r.EditorBounds.Contains ((int)evnt.X, (int)evnt.Y));
 
526
                        if (cat != null) {
 
527
                                GdkWindow.Cursor = handCursor;
 
528
                                return true;
 
529
                        }
 
530
 
 
531
                        int dx = (int)((double)Allocation.Width * dividerPosition);
 
532
                        if (Math.Abs (dx - evnt.X) < 4) {
 
533
                                GdkWindow.Cursor = resizeCursor;
 
534
                                return true;
 
535
                        }
 
536
                        ShowTooltip (evnt);
 
537
                        GdkWindow.Cursor = null;
 
538
                        return base.OnMotionNotifyEvent (evnt);
 
539
                }
 
540
 
 
541
                uint tooltipTimeout;
 
542
                TooltipPopoverWindow tooltipWindow;
 
543
 
 
544
                void ShowTooltip (EventMotion evnt)
 
545
                {
 
546
                        HideTooltip ();
 
547
                        tooltipTimeout = GLib.Timeout.Add (500, delegate {
 
548
                                ShowTooltipWindow ((int)evnt.X, (int)evnt.Y);
 
549
                                return false;
 
550
                        });
 
551
                }
 
552
 
 
553
                void HideTooltip ()
 
554
                {
 
555
                        if (tooltipTimeout != 0) {
 
556
                                GLib.Source.Remove (tooltipTimeout);
 
557
                                tooltipTimeout = 0;
 
558
                        }
 
559
                        if (tooltipWindow != null) {
 
560
                                tooltipWindow.Destroy ();
 
561
                                tooltipWindow = null;
 
562
                        }
 
563
                }
 
564
 
 
565
                void ShowTooltipWindow (int x, int y)
 
566
                {
 
567
                        tooltipTimeout = 0;
 
568
                        int dx = (int)((double)Allocation.Width * dividerPosition);
 
569
                        if (x >= dx)
 
570
                                return;
 
571
                        var row = GetAllRows (true).FirstOrDefault (r => !r.IsCategory && y >= r.EditorBounds.Y && y <= r.EditorBounds.Bottom);
 
572
                        if (row != null) {
 
573
                                tooltipWindow = new TooltipPopoverWindow ();
 
574
                                tooltipWindow.ShowArrow = true;
 
575
                                var s = "<b>" + row.Property.DisplayName + "</b>\n\n";
 
576
                                s += GLib.Markup.EscapeText (row.Property.Description);
 
577
                                tooltipWindow.Markup = s;
 
578
                                tooltipWindow.ShowPopup (this, new Gdk.Rectangle (0, row.EditorBounds.Y, Allocation.Width, row.EditorBounds.Height), PopupPosition.Right);
 
579
                        }
 
580
                }
 
581
 
 
582
                protected override void OnUnrealized ()
 
583
                {
 
584
                        HideTooltip ();
 
585
                        base.OnUnrealized ();
 
586
                }
 
587
 
 
588
                protected override bool OnLeaveNotifyEvent (EventCrossing evnt)
 
589
                {
 
590
                        HideTooltip ();
 
591
                        return base.OnLeaveNotifyEvent (evnt);
 
592
                }
 
593
 
 
594
                void StartExpandAnimation (TableRow row)
 
595
                {
 
596
                        EndEditing ();
 
597
                        if (row.AnimatingExpand) {
 
598
                                GLib.Source.Remove (row.AnimationHandle);
 
599
                        } else
 
600
                                row.AnimationHeight = 0;
 
601
 
 
602
                        row.AnimatingExpand = true;
 
603
                        row.AnimationHandle = GLib.Timeout.Add (animationTimeSpan, delegate {
 
604
                                row.AnimationHeight += animationStepSize;
 
605
                                QueueResize ();
 
606
                                if (row.AnimationHeight >= row.ChildrenHeight) {
 
607
                                        row.AnimatingExpand = false;
 
608
                                        return false;
 
609
                                }
 
610
                                return true;
 
611
                        });
 
612
                }
 
613
 
 
614
                void StartCollapseAnimation (TableRow row)
 
615
                {
 
616
                        EndEditing ();
 
617
                        if (row.AnimatingExpand) {
 
618
                                GLib.Source.Remove (row.AnimationHandle);
 
619
                        } else {
 
620
                                row.AnimationHeight = row.ChildrenHeight;
 
621
                        }
 
622
                        row.AnimatingExpand = true;
 
623
                        row.AnimationHandle = GLib.Timeout.Add (animationTimeSpan, delegate {
 
624
                                row.AnimationHeight -= animationStepSize;
 
625
                                QueueResize ();
 
626
                                if (row.AnimationHeight <= 0) {
 
627
                                        row.AnimatingExpand = false;
 
628
                                        return false;
 
629
                                }
 
630
                                return true;
 
631
                        });
 
632
                }
 
633
 
 
634
                void StopAllAnimations ()
 
635
                {
 
636
                        foreach (var r in GetAllRows (false)) {
 
637
                                if (r.AnimatingExpand) {
 
638
                                        GLib.Source.Remove (r.AnimationHandle);
 
639
                                        r.AnimatingExpand = false;
 
640
                                }
 
641
                        }
 
642
                }
 
643
 
 
644
                protected override void OnDragLeave (DragContext context, uint time_)
 
645
                {
 
646
                        if (!draggingDivider)
 
647
                                GdkWindow.Cursor = null;
 
648
                        base.OnDragLeave (context, time_);
 
649
                }
 
650
 
 
651
                void EndEditing ()
 
652
                {
 
653
                        if (editSession != null) {
 
654
                                Remove (currentEditor);
 
655
                                currentEditor.Destroy ();
 
656
                                editSession.Dispose ();
 
657
                                editSession = null;
 
658
                                currentEditorRow = null;
 
659
                                QueueDraw ();
 
660
                        }
 
661
                }
 
662
 
 
663
                void StartEditing (TableRow row)
 
664
                {
 
665
                        EndEditing ();
 
666
                        currentEditorRow = row;
 
667
                        var cell = GetCell (row);
 
668
                        editSession = cell.StartEditing (row.EditorBounds, State);
 
669
                        currentEditor = (Gtk.Widget) editSession.Editor;
 
670
                        Add (currentEditor);
 
671
                        SetAllocation (currentEditor, row.EditorBounds);
 
672
                        currentEditor.Show ();
 
673
                        currentEditor.CanFocus = true;
 
674
                        currentEditor.GrabFocus ();
 
675
                        ConnectTabEvent (currentEditor);
 
676
                        editSession.Changed += delegate {
 
677
                                if (Changed != null)
 
678
                                        Changed (this, EventArgs.Empty);
 
679
                        };
 
680
                        var vs = ((Gtk.Viewport)Parent).Vadjustment;
 
681
                        if (row.EditorBounds.Top < vs.Value)
 
682
                                vs.Value = row.EditorBounds.Top;
 
683
                        else if (row.EditorBounds.Bottom > vs.Value + vs.PageSize)
 
684
                                vs.Value = row.EditorBounds.Bottom - vs.PageSize;
 
685
                        QueueDraw ();
 
686
                }
 
687
 
 
688
                void ConnectTabEvent (Gtk.Widget w)
 
689
                {
 
690
                        w.KeyPressEvent += HandleKeyPressEvent;
 
691
                        if (w is Gtk.Container) {
 
692
                                foreach (var c in ((Gtk.Container)w).Children)
 
693
                                        ConnectTabEvent (c);
 
694
                        }
 
695
                }
 
696
 
 
697
                [GLib.ConnectBefore]
 
698
                void HandleKeyPressEvent (object o, KeyPressEventArgs args)
 
699
                {
 
700
                        if (args.Event.Key == Gdk.Key.Tab || args.Event.Key == Gdk.Key.ISO_Left_Tab || args.Event.Key == Gdk.Key.KP_Tab) {
 
701
                                var r = args.Event.State == ModifierType.ShiftMask ? GetPreviousRow (currentEditorRow) : GetNextRow (currentEditorRow);
 
702
                                if (r != null) {
 
703
                                        Gtk.Application.Invoke (delegate {
 
704
                                                StartEditing (r);
 
705
                                        });
 
706
                                        args.RetVal = true;
 
707
                                }
 
708
                        }
 
709
                }
 
710
 
 
711
                TableRow GetNextRow (TableRow row)
 
712
                {
 
713
                        bool found = false;
 
714
                        foreach (var r in GetAllRows (true)) {
 
715
                                if (r.IsCategory || !r.Enabled)
 
716
                                        continue;
 
717
                                if (found)
 
718
                                        return r;
 
719
                                if (r == row)
 
720
                                        found = true;
 
721
                        }
 
722
                        return null;
 
723
                }
 
724
 
 
725
                TableRow GetPreviousRow (TableRow row)
 
726
                {
 
727
                        TableRow prev = null;
 
728
                        foreach (var r in GetAllRows (true)) {
 
729
                                if (r.IsCategory || !r.Enabled)
 
730
                                        continue;
 
731
                                if (r == row)
 
732
                                        return prev;
 
733
                                prev = r;
 
734
                        }
 
735
                        return null;
 
736
                }
 
737
        }
 
738
 
 
739
        class InstanceData 
 
740
        {
 
741
                public InstanceData (object instance) 
 
742
                {
 
743
                        Instance = instance;
 
744
                }
 
745
                
 
746
                public object Instance;
 
747
        }
 
748
}
 
749