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

« back to all changes in this revision

Viewing changes to src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl.Views/BlameWidget.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
// BlameWidget.cs
 
3
//  
 
4
// Author:
 
5
//       Mike KrĆ¼ger <mkrueger@novell.com>
 
6
// 
 
7
// Copyright (c) 2010 Novell, Inc (http://www.novell.com)
 
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.Linq;
 
29
using Gtk;
 
30
using Gdk;
 
31
using System.Collections.Generic;
 
32
using Mono.TextEditor;
 
33
using MonoDevelop.Ide;
 
34
using System.Threading;
 
35
using MonoDevelop.Core;
 
36
using System.Text.RegularExpressions;
 
37
using System.Text;
 
38
using MonoDevelop.Components.Commands;
 
39
 
 
40
namespace MonoDevelop.VersionControl.Views
 
41
{
 
42
        public enum BlameCommands {
 
43
                CopyRevision,
 
44
                ShowDiff,
 
45
                ShowLog
 
46
        }
 
47
        
 
48
        public class BlameWidget : Bin
 
49
        {
 
50
                Adjustment vAdjustment;
 
51
                Gtk.VScrollbar vScrollBar;
 
52
                
 
53
                Adjustment hAdjustment;
 
54
                Gtk.HScrollbar hScrollBar;
 
55
                
 
56
                BlameRenderer overview;
 
57
                
 
58
                TextEditor editor;
 
59
                List<ContainerChild> children = new List<ContainerChild> ();
 
60
                
 
61
                public Adjustment Vadjustment {
 
62
                        get { return this.vAdjustment; }
 
63
                }
 
64
 
 
65
                public Adjustment Hadjustment {
 
66
                        get { return this.hAdjustment; }
 
67
                }
 
68
                
 
69
                public override ContainerChild this [Widget w] {
 
70
                        get {
 
71
                                foreach (ContainerChild info in children.ToArray ()) {
 
72
                                        if (info.Child == w)
 
73
                                                return info;
 
74
                                }
 
75
                                return null;
 
76
                        }
 
77
                }
 
78
                
 
79
                public TextEditor Editor {
 
80
                        get {
 
81
                                return this.editor;
 
82
                        }
 
83
                }
 
84
                VersionControlDocumentInfo info;
 
85
                
 
86
                public Ide.Gui.Document Document {
 
87
                        get {
 
88
                                return info.Document;
 
89
                        }
 
90
                }
 
91
                public VersionControlItem VersionControlItem {
 
92
                        get {
 
93
                                return info.Item;
 
94
                        }
 
95
                }
 
96
 
 
97
                protected BlameWidget (IntPtr ptr) : base (ptr)
 
98
                {
 
99
                }
 
100
 
 
101
                public BlameWidget (VersionControlDocumentInfo info)
 
102
                {
 
103
                        this.info = info;
 
104
                        
 
105
                        vAdjustment = new Adjustment (0, 0, 0, 0, 0, 0);
 
106
                        vAdjustment.Changed += HandleAdjustmentChanged;
 
107
                        
 
108
                        vScrollBar = new VScrollbar (vAdjustment);
 
109
                        AddChild (vScrollBar);
 
110
                        
 
111
                        hAdjustment = new Adjustment (0, 0, 0, 0, 0, 0);
 
112
                        hAdjustment.Changed += HandleAdjustmentChanged;
 
113
                        
 
114
                        hScrollBar = new HScrollbar (hAdjustment);
 
115
                        AddChild (hScrollBar);
 
116
                        
 
117
                        editor = new TextEditor (info.Document.Editor.Document, info.Document.Editor.Options);
 
118
                        AddChild (editor);
 
119
                        editor.SetScrollAdjustments (hAdjustment, vAdjustment);
 
120
                        
 
121
                        overview = new BlameRenderer (this);
 
122
                        AddChild (overview);
 
123
                        
 
124
                        this.DoubleBuffered = true;
 
125
                        editor.Painted += HandleEditorExposeEvent;
 
126
                        editor.EditorOptionsChanged += delegate {
 
127
                                overview.OptionsChanged ();
 
128
                        };
 
129
                        editor.Caret.PositionChanged += ComparisonWidget.CaretPositionChanged;
 
130
                        editor.FocusInEvent += ComparisonWidget.EditorFocusIn;
 
131
                        editor.Document.Folded += delegate {
 
132
                                QueueDraw ();
 
133
                        };
 
134
                        editor.Document.FoldTreeUpdated += delegate {
 
135
                                QueueDraw ();
 
136
                        };
 
137
                        editor.ButtonPressEvent += OnPopupMenu;
 
138
                        Show ();
 
139
                }
 
140
                
 
141
                void OnPopupMenu (object sender, Gtk.ButtonPressEventArgs args)
 
142
                {
 
143
                        if (args.Event.Button == 3) {
 
144
                                int textEditorXOffset = (int)args.Event.X - (int)editor.TextViewMargin.XOffset;
 
145
                                if (textEditorXOffset < 0)
 
146
                                        return;
 
147
                                this.menuPopupLocation = new Cairo.Point ((int)args.Event.X, (int)args.Event.Y);
 
148
                                DocumentLocation loc = editor.PointToLocation (textEditorXOffset, (int)args.Event.Y);
 
149
                                if (!editor.IsSomethingSelected || !editor.SelectionRange.Contains (editor.Document.LocationToOffset (loc)))
 
150
                                        editor.Caret.Location = loc;
 
151
                                
 
152
                                this.ShowPopup ();
 
153
                        }
 
154
                }
 
155
                
 
156
                void ShowPopup ()
 
157
                {
 
158
                        CommandEntrySet cset = IdeApp.CommandService.CreateCommandEntrySet ("/MonoDevelop/VersionControl/BlameView/ContextMenu");
 
159
                        Gtk.Menu menu = IdeApp.CommandService.CreateMenu (cset);
 
160
                        menu.Destroyed += delegate {
 
161
                                this.QueueDraw ();
 
162
                        };
 
163
                        
 
164
                        menu.Popup (null, null, new Gtk.MenuPositionFunc (PositionPopupMenu), 0, Gtk.Global.CurrentEventTime);
 
165
                }
 
166
                
 
167
                Cairo.Point menuPopupLocation;
 
168
                void PositionPopupMenu (Menu menu, out int x, out int y, out bool pushIn)
 
169
                {
 
170
                        this.GdkWindow.GetOrigin (out x, out y);
 
171
                        x += this.menuPopupLocation.X;
 
172
                        y += this.menuPopupLocation.Y;
 
173
                        Requisition request = menu.SizeRequest ();
 
174
                        Gdk.Rectangle geometry = Screen.GetMonitorGeometry (Screen.GetMonitorAtPoint (x, y));
 
175
                        
 
176
                        y = Math.Max (geometry.Top, Math.Min (y, geometry.Bottom - request.Height));
 
177
                        x = Math.Max (geometry.Left, Math.Min (x, geometry.Right - request.Width));
 
178
                        pushIn = true;
 
179
                }
 
180
                
 
181
                void HandleAdjustmentChanged (object sender, EventArgs e)
 
182
                {
 
183
                        Adjustment adjustment = (Adjustment)sender;
 
184
                        Scrollbar scrollbar = adjustment == vAdjustment ? (Scrollbar)vScrollBar : hScrollBar;
 
185
                        bool newVisible = adjustment.Upper - adjustment.Lower > adjustment.PageSize;
 
186
                        if (scrollbar.Visible != newVisible) {
 
187
                                scrollbar.Visible = newVisible;
 
188
                                QueueResize ();
 
189
                        }
 
190
                }
 
191
                
 
192
                public override GLib.GType ChildType ()
 
193
                {
 
194
                        return Gtk.Widget.GType;
 
195
                }
 
196
                
 
197
                protected override void ForAll (bool include_internals, Gtk.Callback callback)
 
198
                {
 
199
                        base.ForAll (include_internals, callback);
 
200
                        
 
201
                        if (include_internals)
 
202
                                children.ForEach (child => callback (child.Child));
 
203
                }
 
204
                
 
205
                public void AddChild (Gtk.Widget child)
 
206
                {
 
207
                        child.Parent = this;
 
208
                        children.Add (new ContainerChild (this, child));
 
209
                        child.Show ();
 
210
                }
 
211
                
 
212
                protected override void OnAdded (Widget widget)
 
213
                {
 
214
                        base.OnAdded (widget);
 
215
                        if (widget == Child)
 
216
                                widget.SetScrollAdjustments (hAdjustment, vAdjustment);
 
217
                }
 
218
                
 
219
                protected override void OnRemoved (Widget widget)
 
220
                {
 
221
                        widget.Unparent ();
 
222
                        foreach (var info in children.ToArray ()) {
 
223
                                if (info.Child == widget) {
 
224
                                        children.Remove (info);
 
225
                                        break;
 
226
                                }
 
227
                        }
 
228
                }
 
229
                
 
230
                protected override void OnDestroyed ()
 
231
                {
 
232
                        base.OnDestroyed ();
 
233
                        children.ForEach (child => child.Child.Destroy ());
 
234
                        children.Clear ();
 
235
                }
 
236
                 
 
237
                protected override void OnSizeAllocated (Rectangle allocation)
 
238
                {
 
239
                        base.OnSizeAllocated (allocation);
 
240
                        int vwidth = vScrollBar.Visible ? vScrollBar.Requisition.Width : 0;
 
241
                        int hheight = hScrollBar.Visible ? hScrollBar.Requisition.Height : 0; 
 
242
                        Rectangle childRectangle = new Rectangle (allocation.X + 1, allocation.Y + 1, allocation.Width - vwidth - 1, allocation.Height - hheight - 1);
 
243
                        
 
244
                        if (vScrollBar.Visible) {
 
245
                                int right = childRectangle.Right;
 
246
                                int vChildTopHeight = -1;
 
247
                                int v = hScrollBar.Visible ? hScrollBar.Requisition.Height : 0;
 
248
                                vScrollBar.SizeAllocate (new Rectangle (right, childRectangle.Y + vChildTopHeight, vwidth, Allocation.Height - v - vChildTopHeight - 1));
 
249
                                vScrollBar.Value = System.Math.Max (System.Math.Min (vAdjustment.Upper - vAdjustment.PageSize, vScrollBar.Value), vAdjustment.Lower);
 
250
                        }
 
251
                        int overviewWidth = overview.WidthRequest;
 
252
                        overview.SizeAllocate (new Rectangle (childRectangle.Right - overviewWidth, childRectangle.Top, overviewWidth, childRectangle.Height));
 
253
                        editor.SizeAllocate (new Rectangle (childRectangle.X, childRectangle.Top, childRectangle.Width - overviewWidth, childRectangle.Height));
 
254
                
 
255
                        if (hScrollBar.Visible) {
 
256
                                hScrollBar.SizeAllocate (new Rectangle (childRectangle.X, childRectangle.Bottom, childRectangle.Width, hheight));
 
257
                                hScrollBar.Value = System.Math.Max (System.Math.Min (hAdjustment.Upper - hAdjustment.PageSize, hScrollBar.Value), hAdjustment.Lower);
 
258
                        }
 
259
                }
 
260
                
 
261
                static double GetWheelDelta (Scrollbar scrollbar, ScrollDirection direction)
 
262
                {
 
263
                        double delta = System.Math.Pow (scrollbar.Adjustment.PageSize, 2.0 / 3.0);
 
264
                        if (direction == ScrollDirection.Up || direction == ScrollDirection.Left)
 
265
                                delta = -delta;
 
266
                        if (scrollbar.Inverted)
 
267
                                delta = -delta;
 
268
                        return delta;
 
269
                }
 
270
                
 
271
                protected override bool OnScrollEvent (EventScroll evnt)
 
272
                {
 
273
                        Scrollbar scrollWidget = (evnt.Direction == ScrollDirection.Up || evnt.Direction == ScrollDirection.Down) ? (Scrollbar)vScrollBar : hScrollBar;
 
274
                        if (scrollWidget.Visible) {
 
275
                                double newValue = scrollWidget.Adjustment.Value + GetWheelDelta (scrollWidget, evnt.Direction);
 
276
                                newValue = System.Math.Max (System.Math.Min (scrollWidget.Adjustment.Upper  - scrollWidget.Adjustment.PageSize, newValue), scrollWidget.Adjustment.Lower);
 
277
                                scrollWidget.Adjustment.Value = newValue;
 
278
                        }
 
279
                        return base.OnScrollEvent (evnt);
 
280
                }
 
281
                
 
282
                protected override void OnSizeRequested (ref Gtk.Requisition requisition)
 
283
                {
 
284
                        base.OnSizeRequested (ref requisition);
 
285
                        children.ForEach (child => child.Child.SizeRequest ());
 
286
                }
 
287
                
 
288
                void HandleEditorExposeEvent (object o, PaintEventArgs args)
 
289
                {
 
290
                        var cr = args.Context;
 
291
                        int startLine = Editor.YToLine (Editor.VAdjustment.Value);
 
292
                        double startY = Editor.LineToY (startLine);
 
293
                        double curY = startY - Editor.VAdjustment.Value;
 
294
                        int line = startLine;
 
295
                        var color = Style.Dark (State);
 
296
                        
 
297
                        while (curY < editor.Allocation.Bottom) {
 
298
                                Annotation ann = line <= overview.annotations.Count ? overview.annotations[line - 1] : null;
 
299
                                double curStart = curY;
 
300
                                do {
 
301
                                        JumpOverFoldings (ref line);
 
302
                                        line++;
 
303
                                } while (curY < editor.Allocation.Bottom && line <= overview.annotations.Count && ann != null && overview.annotations[line - 1] != null && overview.annotations[line - 1].Revision == ann.Revision);
 
304
                                curY = Editor.LineToY (line) - Editor.VAdjustment.Value;
 
305
                                
 
306
                                if (overview.highlightAnnotation != null) {
 
307
                                        if (ann != null && overview.highlightAnnotation.Revision == ann.Revision && curStart <= overview.highlightPositon && overview.highlightPositon < curY) {
 
308
                                        } else {
 
309
                                                cr.Rectangle (Editor.TextViewMargin.XOffset, curStart + cr.LineWidth, Editor.Allocation.Width - Editor.TextViewMargin.XOffset, curY - curStart - cr.LineWidth);
 
310
                                                cr.Color = new Cairo.Color (color.Red / (double)ushort.MaxValue, 
 
311
                                                        color.Green / (double)ushort.MaxValue,
 
312
                                                        color.Blue / (double)ushort.MaxValue,
 
313
                                                        0.1);
 
314
                                                cr.Fill ();
 
315
                                                
 
316
                                        }
 
317
                                }
 
318
                                if (ann != null) {
 
319
                                        cr.MoveTo (Editor.TextViewMargin.XOffset, curY + 0.5);
 
320
                                        cr.LineTo (Editor.Allocation.Width, curY + 0.5);
 
321
                                        
 
322
                                        cr.Color = new Cairo.Color (color.Red / (double)ushort.MaxValue, 
 
323
                                                                    color.Green / (double)ushort.MaxValue,
 
324
                                                                    color.Blue / (double)ushort.MaxValue,
 
325
                                                                    0.2);
 
326
                                        cr.Stroke ();
 
327
                                }
 
328
                        }
 
329
                }
 
330
                
 
331
                protected override bool OnExposeEvent (EventExpose evnt)
 
332
                {
 
333
                        Gdk.GC gc = Style.DarkGC (State);
 
334
                        evnt.Window.DrawLine (gc, Allocation.X, Allocation.Top, Allocation.X, Allocation.Bottom);
 
335
                        evnt.Window.DrawLine (gc, Allocation.Right, Allocation.Top, Allocation.Right, Allocation.Bottom);
 
336
                        
 
337
                        evnt.Window.DrawLine (gc, Allocation.Left, Allocation.Y, Allocation.Right, Allocation.Y);
 
338
                        evnt.Window.DrawLine (gc, Allocation.Left, Allocation.Bottom, Allocation.Right, Allocation.Bottom);
 
339
                        
 
340
                        
 
341
                        return base.OnExposeEvent (evnt);
 
342
                }
 
343
                
 
344
                void JumpOverFoldings (ref int line)
 
345
                {
 
346
                        int lastFold = -1;
 
347
                        foreach (FoldSegment fs in Editor.Document.GetStartFoldings (line).Where (fs => fs.IsFolded)) {
 
348
                                lastFold = System.Math.Max (fs.EndOffset, lastFold);
 
349
                        }
 
350
                        if (lastFold > 0) 
 
351
                                line = Editor.Document.OffsetToLineNumber (lastFold);
 
352
                }
 
353
 
 
354
                internal static string FormatMessage (string msg)
 
355
                {
 
356
                        StringBuilder sb = new StringBuilder ();
 
357
                        bool wasWs = false;
 
358
                        foreach (char ch in msg) {
 
359
                                if (ch == ' ' || ch == '\t') {
 
360
                                        if (!wasWs)
 
361
                                                sb.Append (' ');
 
362
                                        wasWs = true;
 
363
                                        continue;
 
364
                                }
 
365
                                wasWs = false;
 
366
                                sb.Append (ch);
 
367
                        }
 
368
                        
 
369
                        Document doc = new Document ();
 
370
                        doc.Text = sb.ToString ();
 
371
                        for (int i = 1; i <= doc.LineCount; i++) {
 
372
                                string text = doc.GetLineText (i).Trim ();
 
373
                                int idx = text.IndexOf (':');
 
374
                                if (text.StartsWith ("*") && idx >= 0 && idx < text.Length - 1) {
 
375
                                        int offset = doc.GetLine (i).EndOffset;
 
376
                                        msg = text.Substring (idx + 1) + doc.GetTextAt (offset, doc.Length - offset);
 
377
                                        break;
 
378
                                }
 
379
                        }
 
380
                        return msg.TrimStart (' ', '\t');
 
381
                }
 
382
 
 
383
                class BlameRenderer : DrawingArea 
 
384
                {
 
385
                        static readonly Annotation locallyModified = new Annotation ("", "?", DateTime.MinValue);
 
386
                        
 
387
                        BlameWidget widget;
 
388
                        internal List<Annotation> annotations;
 
389
                        Pango.Layout layout;
 
390
 
 
391
                        double dragPosition = -1;
 
392
                        public BlameRenderer (BlameWidget widget)
 
393
                        {
 
394
                                this.widget = widget;
 
395
                                widget.info.Updated += delegate { QueueDraw (); };
 
396
                                annotations = new List<Annotation> ();
 
397
                                UpdateAnnotations (null, null);
 
398
        //                      widget.Document.Saved += UpdateAnnotations;
 
399
                                widget.Editor.Document.TextReplacing += EditorDocumentTextReplacing;
 
400
                                widget.Editor.Document.LineChanged += EditorDocumentLineChanged;
 
401
                                widget.vScrollBar.ValueChanged += delegate {
 
402
                                        QueueDraw ();
 
403
                                };
 
404
                                
 
405
                                layout = new Pango.Layout (PangoContext);
 
406
                                Events |= EventMask.ButtonPressMask | EventMask.ButtonReleaseMask | EventMask.PointerMotionMask | EventMask.LeaveNotifyMask;
 
407
                                OptionsChanged ();
 
408
                                Show ();
 
409
                        }
 
410
                        
 
411
                        public void OptionsChanged ()
 
412
                        {
 
413
                                var description = Pango.FontDescription.FromString ("Tahoma " + (int)(10 * widget.Editor.Options.Zoom));
 
414
                                layout.FontDescription = description;
 
415
                                UpdateWidth ();
 
416
                        }
 
417
                        
 
418
                        protected override void OnDestroyed ()
 
419
                        {
 
420
                                base.OnDestroyed ();
 
421
//                              widget.Document.Saved -= UpdateAnnotations;
 
422
                                widget.Editor.Document.TextReplacing -= EditorDocumentTextReplacing;
 
423
                                widget.Editor.Document.LineChanged -= EditorDocumentLineChanged;
 
424
                                if (layout != null) {
 
425
                                        layout.Dispose ();
 
426
                                        layout = null;
 
427
                                }
 
428
                        }
 
429
                        
 
430
                        internal double highlightPositon;
 
431
                        internal Annotation highlightAnnotation;
 
432
                        protected override bool OnMotionNotifyEvent (EventMotion evnt)
 
433
                        {
 
434
                                TooltipText = null;
 
435
                                if (dragPosition >= 0) {
 
436
                                        int x, y;
 
437
                                        widget.GetPointer (out x, out y);
 
438
                                        int newWidthRequest = widget.Allocation.Width - x;
 
439
                                        newWidthRequest = Math.Min (widget.Allocation.Width - (int)widget.Editor.TextViewMargin.XOffset, Math.Max (leftSpacer, newWidthRequest));
 
440
                                        
 
441
                                        WidthRequest = newWidthRequest;
 
442
                                        QueueResize ();
 
443
                                }
 
444
                                int startLine = widget.Editor.YToLine (widget.Editor.VAdjustment.Value + evnt.Y);
 
445
                                var ann = startLine > 0 && startLine <= annotations.Count ? annotations[startLine - 1] : null;
 
446
                                if (ann != null)
 
447
                                        TooltipText = GetCommitMessage (startLine);
 
448
                                
 
449
                                highlightPositon = evnt.Y;
 
450
                                if (highlightAnnotation != ann) {
 
451
                                        highlightAnnotation = ann;
 
452
                                        widget.QueueDraw ();
 
453
                                }
 
454
                                
 
455
                                return base.OnMotionNotifyEvent (evnt);
 
456
                        }
 
457
                        
 
458
                        
 
459
                        protected override bool OnLeaveNotifyEvent (EventCrossing evnt)
 
460
                        {
 
461
                                highlightAnnotation = null;
 
462
                                widget.QueueDraw ();
 
463
                                return base.OnLeaveNotifyEvent (evnt);
 
464
                        }
 
465
                        
 
466
                        uint grabTime;
 
467
                        protected override bool OnButtonPressEvent (EventButton evnt)
 
468
                        {
 
469
                                if (evnt.Button == 3) {
 
470
                                        CommandEntrySet opset = new CommandEntrySet ();
 
471
                                        opset.AddItem (BlameCommands.ShowDiff);
 
472
                                        opset.AddItem (BlameCommands.ShowLog);
 
473
                                        opset.AddItem (Command.Separator);
 
474
                                        opset.AddItem (BlameCommands.CopyRevision);
 
475
                                        IdeApp.CommandService.ShowContextMenu (opset, this);
 
476
                                } else {
 
477
                                        if (evnt.X < leftSpacer) {
 
478
                                                grabTime = evnt.Time;
 
479
                                                var status = Gdk.Pointer.Grab (this.GdkWindow, false, EventMask.PointerMotionHintMask | EventMask.Button1MotionMask | EventMask.ButtonReleaseMask | EventMask.EnterNotifyMask | EventMask.LeaveNotifyMask, null, null, grabTime);
 
480
                                                if (status == GrabStatus.Success) {
 
481
                                                        dragPosition = evnt.X;
 
482
                                                }
 
483
                                        }
 
484
                                }
 
485
                                return base.OnButtonPressEvent (evnt);
 
486
                        }
 
487
                        
 
488
                        [CommandHandler (BlameCommands.CopyRevision)]
 
489
                        protected void OnCopyRevision ()
 
490
                        {
 
491
                                if (highlightAnnotation == null)
 
492
                                        return;
 
493
                                var clipboard = Clipboard.Get (Gdk.Atom.Intern ("CLIPBOARD", false));
 
494
                                clipboard.Text = highlightAnnotation.Revision.ToString ();
 
495
                                clipboard = Clipboard.Get (Gdk.Atom.Intern ("PRIMARY", false));
 
496
                                clipboard.Text = highlightAnnotation.Revision.ToString ();
 
497
                        }
 
498
                
 
499
                        [CommandHandler (BlameCommands.ShowDiff)]
 
500
                        protected void OnShowDiff ()
 
501
                        {
 
502
                                if (highlightAnnotation == null)
 
503
                                        return;
 
504
                                int i = 1;
 
505
                                foreach (var content in widget.info.Document.Window.SubViewContents) {
 
506
                                        DiffView diffView = content as DiffView;
 
507
                                        if (diffView != null) {
 
508
                                                widget.info.Document.Window.SwitchView (i);
 
509
                                                var rev = widget.info.History.FirstOrDefault (h => h.ToString () == highlightAnnotation.Revision);
 
510
                                                if (rev == null)
 
511
                                                        return;
 
512
                                                diffView.ComparisonWidget.SetRevision (diffView.ComparisonWidget.OriginalEditor, rev.GetPrevious ());
 
513
                                                diffView.ComparisonWidget.SetRevision (diffView.ComparisonWidget.DiffEditor, rev);
 
514
                                                break;
 
515
                                        }
 
516
                                        i++;
 
517
                                }
 
518
                        }
 
519
                
 
520
                        [CommandHandler (BlameCommands.ShowLog)]
 
521
                        protected void OnShowLog ()
 
522
                        {
 
523
                                if (highlightAnnotation == null)
 
524
                                        return;
 
525
                                int i = 1;
 
526
                                foreach (var content in widget.info.Document.Window.SubViewContents) {
 
527
                                        LogView logView = content as LogView;
 
528
                                        if (logView != null) {
 
529
                                                widget.info.Document.Window.SwitchView (i);
 
530
                                                var rev = widget.info.History.FirstOrDefault (h => h.ToString () == highlightAnnotation.Revision);
 
531
                                                if (rev == null)
 
532
                                                        return;
 
533
                                                logView.LogWidget.SelectedRevision = rev;
 
534
                                                break;
 
535
                                        }
 
536
                                        i++;
 
537
                                }
 
538
                        }
 
539
                
 
540
                        
 
541
                        protected override bool OnButtonReleaseEvent (EventButton evnt)
 
542
                        {
 
543
                                if (dragPosition >= 0) {
 
544
                                        Gdk.Pointer.Ungrab (grabTime);
 
545
                                        dragPosition = -1;
 
546
                                }
 
547
                                return base.OnButtonReleaseEvent (evnt);
 
548
                        }
 
549
                        DateTime minDate, maxDate;
 
550
                        /// <summary>
 
551
                        /// Reloads annotations for the current document
 
552
                        /// </summary>
 
553
                        internal void UpdateAnnotations (object sender, EventArgs e)
 
554
                        {
 
555
                                StatusBarContext ctx = IdeApp.Workbench.StatusBar.CreateContext ();
 
556
                                ctx.AutoPulse = true;
 
557
                                ctx.ShowMessage (ImageService.GetImage ("md-version-control", IconSize.Menu), GettextCatalog.GetString ("Retrieving history"));
 
558
                                
 
559
                                ThreadPool.QueueUserWorkItem (delegate {
 
560
                                        try {
 
561
                                                annotations = new List<Annotation> (widget.VersionControlItem.Repository.GetAnnotations (widget.Document.FileName));
 
562
                                                
 
563
//                                              for (int i = 0; i < annotations.Count; i++) {
 
564
//                                                      Annotation varname = annotations[i];
 
565
//                                                      System.Console.WriteLine (i + ":" + varname);
 
566
//                                              }
 
567
                                                minDate = annotations.Min (a => a.Date);
 
568
                                                maxDate = annotations.Max (a => a.Date);
 
569
                                        } catch (Exception ex) {
 
570
                                                LoggingService.LogError ("Error retrieving history", ex);
 
571
                                        }
 
572
                                        
 
573
                                        DispatchService.GuiDispatch (delegate {
 
574
                                                ctx.Dispose ();
 
575
                                                UpdateWidth ();
 
576
                                                QueueDraw ();
 
577
                                        });
 
578
                                });
 
579
                        }
 
580
        
 
581
                        /// <summary>
 
582
                        /// Marks a line as locally modified
 
583
                        /// </summary>
 
584
                        private void EditorDocumentLineChanged (object sender, LineEventArgs e)
 
585
                        {
 
586
                                int startLine = widget.Editor.Document.OffsetToLineNumber (e.Line.Offset);
 
587
                                SetAnnotation (startLine, locallyModified);
 
588
                        }
 
589
                        
 
590
                        /// <summary>
 
591
                        /// Marks necessary lines modified when text is replaced
 
592
                        /// </summary>
 
593
                        private void EditorDocumentTextReplacing (object sender, ReplaceEventArgs e)
 
594
                        {
 
595
                                int  startLine = widget.Editor.Document.OffsetToLineNumber (e.Offset),
 
596
                                     endLine = widget.Editor.Document.OffsetToLineNumber (e.Offset + Math.Max (e.Count, e.Value != null ? e.Value.Length : 0)),
 
597
                                     lineCount = 0;
 
598
                                string[] tokens = null;
 
599
                                
 
600
                                if (startLine < endLine) {
 
601
                                        // change crosses line boundary
 
602
                                        
 
603
                                        lineCount = endLine - startLine;
 
604
                                        lineCount = Math.Min (lineCount, annotations.Count - startLine);
 
605
                                        
 
606
                                        if (lineCount > 0)
 
607
                                                annotations.RemoveRange (startLine - 1, lineCount);
 
608
                                        if (!string.IsNullOrEmpty (e.Value)) {
 
609
                                                for (int i=0; i<lineCount; ++i)
 
610
                                                        annotations.Insert (startLine - 1, locallyModified);
 
611
                                        }
 
612
                                        return;
 
613
                                } else if (0 == e.Count) {
 
614
                                                // insert
 
615
                                                tokens = e.Value.Split (new string[]{Environment.NewLine}, StringSplitOptions.None);
 
616
                                                lineCount = tokens.Length - 1;
 
617
                                                for (int i=0; i<lineCount; ++i) {
 
618
                                                        annotations.Insert (Math.Min (startLine, annotations.Count), locallyModified);
 
619
                                                }
 
620
                                } else if (startLine > endLine) {
 
621
                                        // revert
 
622
                                        UpdateAnnotations (null, null);
 
623
                                        return;
 
624
                                }
 
625
                                
 
626
                                SetAnnotation (startLine, locallyModified);
 
627
                        }
 
628
                        
 
629
                        void SetAnnotation (int index, Annotation text)
 
630
                        {
 
631
                                if (index < 0)
 
632
                                        return;
 
633
                                for (int i = annotations.Count; i <= index; ++i)
 
634
                                        annotations.Add (locallyModified);
 
635
                                annotations[index] = text;
 
636
                        }
 
637
        
 
638
                        /// <summary>
 
639
                        /// Gets the commit message matching a given annotation index.
 
640
                        /// </summary>
 
641
                        internal string GetCommitMessage (int index)
 
642
                        {
 
643
                                Annotation annotation = (index < annotations.Count)? annotations[index]: null;
 
644
                                var history = widget.info.History;
 
645
                                if (null != history && annotation != null) {
 
646
                                        foreach (Revision rev in history) {
 
647
                                                if (rev.ToString () == annotation.Revision) {
 
648
                                                        return rev.Message;
 
649
                                                }
 
650
                                        }
 
651
                                }
 
652
                                return null;
 
653
                        }
 
654
                        
 
655
                        string TruncRevision (string revision)
 
656
                        {
 
657
                                return TruncRevision (revision, 8);
 
658
                        }
 
659
                        
 
660
                        /// <summary>
 
661
                        /// Truncates the revision. This is done by trying to find the shortest matching number.
 
662
                        /// </summary>
 
663
                        /// <returns>
 
664
                        /// The shortest revision number (down to a minimum length of initialLength).
 
665
                        /// </returns>
 
666
                        /// <param name='revision'>
 
667
                        /// The revision.
 
668
                        /// </param>
 
669
                        /// <param name='initalLength'>
 
670
                        /// Inital length.
 
671
                        /// </param> 
 
672
                        string TruncRevision (string revision, int initalLength)
 
673
                        {
 
674
                                if (initalLength >= revision.Length)
 
675
                                        return revision;
 
676
                                string truncated = revision.Substring (0, initalLength);
 
677
                                var history = widget.info.History;
 
678
                                if (history != null) {
 
679
                                        bool isMisleadingMatch = history.Select (r => r.ToString ()).Any (rev => rev != revision && rev.StartsWith (truncated));
 
680
                                        if (isMisleadingMatch)
 
681
                                                truncated = TruncRevision (revision, initalLength + 1);
 
682
                                }
 
683
                                return truncated;
 
684
                        }
 
685
                        
 
686
                        void UpdateWidth ()
 
687
                        {
 
688
                                int tmpwidth, height, width = 120;
 
689
                                int dateTimeLength = -1;
 
690
                                foreach (Annotation note in annotations) {
 
691
                                        if (!string.IsNullOrEmpty (note.Author)) { 
 
692
                                                if (dateTimeLength < 0 && note.HasDate) {
 
693
                                                        layout.SetText (note.Date.ToShortDateString ());
 
694
                                                        layout.GetPixelSize (out dateTimeLength, out height);
 
695
                                                }
 
696
                                                layout.SetText (note.Author + TruncRevision (note.Revision));
 
697
                                                layout.GetPixelSize (out tmpwidth, out height);
 
698
                                                width = Math.Max (width, tmpwidth);
 
699
                                        }
 
700
                                }
 
701
                                WidthRequest = dateTimeLength + width + 30 + leftSpacer + margin * 2;
 
702
                                QueueResize ();
 
703
                        }
 
704
 
 
705
                        const int leftSpacer = 4;
 
706
                        const int margin = 4;
 
707
 
 
708
                        
 
709
                        protected override bool OnExposeEvent (Gdk.EventExpose e)
 
710
                        {
 
711
                                using (Cairo.Context cr = Gdk.CairoHelper.Create (e.Window)) {
 
712
                                        cr.LineWidth = Math.Max (1.0, widget.Editor.Options.Zoom);
 
713
                                        
 
714
                                        cr.Rectangle (leftSpacer, 0, Allocation.Width, Allocation.Height);
 
715
                                        cr.Color = new Cairo.Color (0.95, 0.95, 0.95);
 
716
                                        cr.Fill ();
 
717
                                        
 
718
                                        int startLine = widget.Editor.YToLine ((int)widget.Editor.VAdjustment.Value);
 
719
                                        double startY = widget.Editor.LineToY (startLine);
 
720
                                        while (startLine > 1 && startLine < annotations.Count && annotations[startLine - 1] != null && annotations[startLine] != null && annotations[startLine - 1].Revision == annotations[startLine].Revision) {
 
721
                                                startLine--;
 
722
                                                startY -= widget.Editor.GetLineHeight (widget.Editor.Document.GetLine (startLine));
 
723
                                        }
 
724
                                        double curY = startY - widget.Editor.VAdjustment.Value;
 
725
                                        int line = startLine;
 
726
                                        while (curY < Allocation.Bottom) {
 
727
                                                double curStart = curY;
 
728
//                                              widget.JumpOverFoldings (ref line);
 
729
                                                int lineStart = line;
 
730
                                                int w = 0, w2 = 0, h = 16;
 
731
                                                Annotation ann = line <= annotations.Count ? annotations[line - 1] : null;
 
732
                                                if (ann != null) {
 
733
                                                        do {
 
734
                                                                widget.JumpOverFoldings (ref line);
 
735
                                                                line++;
 
736
                                                        } while (line <= annotations.Count && annotations[line - 1] != null && annotations[line - 1].Revision == ann.Revision);
 
737
                                                        double nextY = widget.editor.LineToY (line) - widget.editor.VAdjustment.Value;
 
738
                                                        if (highlightAnnotation != null && highlightAnnotation.Revision == ann.Revision && curStart <= highlightPositon && highlightPositon < nextY) {
 
739
                                                                cr.Rectangle (leftSpacer, curStart + cr.LineWidth, Allocation.Width - leftSpacer, nextY - curStart - cr.LineWidth);
 
740
                                                                cr.Color = new Cairo.Color (1, 1, 1);
 
741
                                                                cr.Fill ();
 
742
                                                        }
 
743
                                                        layout.SetText (ann.Author);
 
744
                                                        layout.GetPixelSize (out w, out h);
 
745
                                                        e.Window.DrawLayout (Style.BlackGC, leftSpacer + margin, (int)(curY + (widget.Editor.LineHeight - h) / 2), layout);
 
746
                                                        
 
747
                                                        
 
748
                                                        layout.SetText (TruncRevision (ann.Revision));
 
749
                                                        layout.GetPixelSize (out w2, out h);
 
750
                                                        e.Window.DrawLayout (Style.BlackGC, Allocation.Width - w2 - margin, (int)(curY + (widget.Editor.LineHeight - h) / 2), layout);
 
751
 
 
752
                                                        if (ann.HasDate) {
 
753
                                                                string dateTime = ann.Date.ToShortDateString ();
 
754
                                                                int middle = w + (Allocation.Width - margin * 2 - leftSpacer - w - w2) / 2;
 
755
                                                                layout.SetText (dateTime);
 
756
                                                                layout.GetPixelSize (out w, out h);
 
757
                                                                e.Window.DrawLayout (Style.BlackGC, leftSpacer + margin + middle - w / 2, (int)(curY + (widget.Editor.LineHeight - h) / 2), layout);
 
758
                                                        }
 
759
                                                        curY = nextY;
 
760
                                                } else {
 
761
                                                        curY += widget.Editor.GetLineHeight (line);
 
762
                                                        line++;
 
763
                                                        widget.JumpOverFoldings (ref line);
 
764
                                                }
 
765
                                                
 
766
                                                if (ann != null && line - lineStart > 1) {
 
767
                                                        string msg = GetCommitMessage (lineStart);
 
768
                                                        if (!string.IsNullOrEmpty (msg)) {
 
769
                                                                msg = FormatMessage (msg);
 
770
                                                                layout.SetText (msg);
 
771
                                                                layout.Width = (int)(Allocation.Width * Pango.Scale.PangoScale);
 
772
                                                                using (var gc = new Gdk.GC (e.Window)) {
 
773
                                                                        gc.RgbFgColor = Style.Dark (State);
 
774
                                                                        gc.ClipRectangle = new Rectangle (0, (int)curStart, Allocation.Width, (int)(curY - curStart));
 
775
                                                                        e.Window.DrawLayout (gc, (int)(leftSpacer + margin), (int)(curStart + h), layout);
 
776
                                                                }
 
777
                                                        }
 
778
                                                }
 
779
                                                
 
780
                                                cr.Rectangle (0, curStart, leftSpacer, curY - curStart);
 
781
                                                
 
782
                                                if (ann != null && ann != locallyModified && !string.IsNullOrEmpty (ann.Author)) {
 
783
                                                        double a;
 
784
                                                        
 
785
                                                        if (ann != null && (maxDate - minDate).TotalHours > 0) {
 
786
                                                                a = 1 - (ann.Date  - minDate).TotalHours / (maxDate - minDate).TotalHours;
 
787
                                                        } else {
 
788
                                                                a = 1;
 
789
                                                        }
 
790
                                                        HslColor color = new Cairo.Color (0.90, 0.90, 1);
 
791
                                                        color.L = 0.4 + a / 2;
 
792
                                                        color.S = 1 - a / 2;
 
793
                                                        cr.Color = color;
 
794
                                                } else {
 
795
                                                        cr.Color = ann != null ? new Cairo.Color (1, 1, 0) : new Cairo.Color (0.95, 0.95, 0.95);
 
796
                                                }
 
797
                                                cr.Fill ();
 
798
 
 
799
                                                if (ann != null) {
 
800
                                                        cr.MoveTo (0, curY + 0.5);
 
801
                                                        cr.LineTo (Allocation.Width, curY + 0.5);
 
802
                                                        cr.Color = new Cairo.Color (0.6, 0.6, 0.6);
 
803
                                                        cr.Stroke ();
 
804
                                                }
 
805
                                        }
 
806
                                }
 
807
                                return true;
 
808
                        }
 
809
                        
 
810
                }       
 
811
        }
 
812
}
 
813