5
// Mike KrĆ¼ger <mkrueger@xamarin.com>
7
// Copyright (c) 2012 Xamarin Inc. (http://xamarin.com)
9
// Permission is hereby granted, free of charge, to any person obtaining a copy
10
// of this software and associated documentation files (the "Software"), to deal
11
// in the Software without restriction, including without limitation the rights
12
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
// copies of the Software, and to permit persons to whom the Software is
14
// furnished to do so, subject to the following conditions:
16
// The above copyright notice and this permission notice shall be included in
17
// all copies or substantial portions of the Software.
19
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27
using System.Diagnostics;
29
using MonoDevelop.Components;
31
using MonoDevelop.Ide;
32
using MonoDevelop.Ide.Gui;
33
using MonoDevelop.Ide.Tasks;
34
using System.Collections.Generic;
35
using MonoDevelop.Ide.CodeCompletion;
36
using MonoDevelop.Core;
37
using MonoDevelop.Ide.Gui.Components;
39
using StockIcons = MonoDevelop.Ide.Gui.Stock;
41
namespace MonoDevelop.Components.MainToolbar
43
class StatusArea : EventBox, StatusBar, Animatable
51
public Message (IconId icon, string text, bool markup)
59
public struct RenderArg
61
public Gdk.Rectangle Allocation { get; set; }
62
public float BuildAnimationProgress { get; set; }
63
public float BuildAnimationOpacity { get; set; }
64
public Gdk.Rectangle ChildAllocation { get; set; }
65
public Gdk.Pixbuf CurrentPixbuf { get; set; }
66
public string CurrentText { get; set; }
67
public bool CurrentTextIsMarkup { get; set; }
68
public float ErrorAnimationProgress { get; set; }
69
public float HoverProgress { get; set; }
70
public string LastText { get; set; }
71
public bool LastTextIsMarkup { get; set; }
72
public Gdk.Pixbuf LastPixbuf { get; set; }
73
public Gdk.Point MousePosition { get; set; }
74
public Pango.Context Pango { get; set; }
75
public float ProgressBarAlpha { get; set; }
76
public float ProgressBarFraction { get; set; }
77
public bool ShowProgressBar { get; set; }
78
public float TextAnimationProgress { get; set; }
81
StatusAreaTheme theme;
84
HBox contentBox = new HBox (false, 8);
86
StatusAreaSeparator statusIconSeparator;
87
Gtk.Widget buildResultWidget;
89
readonly HBox messageBox = new HBox ();
90
internal readonly HBox statusIconBox = new HBox ();
97
AnimatedIcon iconAnimation;
100
IDisposable currentIconAnimation;
102
bool errorAnimPending;
104
MainStatusBarContextImpl mainContext;
105
StatusBarContextImpl activeContext;
106
bool progressBarVisible;
108
Queue<Message> messageQueue;
110
public StatusBar MainContext {
111
get { return mainContext; }
114
public int MaxWidth { get; set; }
118
theme = new StatusAreaTheme ();
119
renderArg = new RenderArg ();
121
mainContext = new MainStatusBarContextImpl (this);
122
activeContext = mainContext;
123
contexts.Add (mainContext);
125
VisibleWindow = false;
127
WidgetFlags |= Gtk.WidgetFlags.AppPaintable;
129
statusIconBox.BorderWidth = 0;
130
statusIconBox.Spacing = 3;
132
Action<bool> animateProgressBar =
133
showing => this.Animate ("ProgressBarFade",
134
easing: Easing.CubicInOut,
135
start: renderArg.ProgressBarAlpha,
136
end: showing ? 1.0f : 0.0f,
137
callback: val => renderArg.ProgressBarAlpha = val);
139
ProgressBegin += delegate {
140
renderArg.ShowProgressBar = true;
141
// StartBuildAnimation ();
142
renderArg.ProgressBarFraction = 0;
144
animateProgressBar (true);
147
ProgressEnd += delegate {
148
renderArg.ShowProgressBar = false;
149
// StopBuildAnimation ();
151
animateProgressBar (false);
154
ProgressFraction += delegate(object sender, FractionEventArgs e) {
155
renderArg.ProgressBarFraction = (float)e.Work;
159
contentBox.PackStart (messageBox, true, true, 0);
160
contentBox.PackEnd (statusIconBox, false, false, 0);
161
contentBox.PackEnd (statusIconSeparator = new StatusAreaSeparator (), false, false, 0);
162
contentBox.PackEnd (buildResultWidget = CreateBuildResultsWidget (Orientation.Horizontal), false, false, 0);
164
mainAlign = new Alignment (0, 0.5f, 1, 0);
165
mainAlign.LeftPadding = 12;
166
mainAlign.RightPadding = 8;
167
mainAlign.Add (contentBox);
170
mainAlign.ShowAll ();
171
statusIconBox.Hide ();
172
statusIconSeparator.Hide ();
173
buildResultWidget.Hide ();
176
this.ButtonPressEvent += delegate {
177
if (sourcePad != null)
178
sourcePad.BringToFront (true);
181
statusIconBox.Shown += delegate {
185
statusIconBox.Hidden += delegate {
189
messageQueue = new Queue<Message> ();
191
tracker = new MouseTracker(this);
192
tracker.MouseMoved += (sender, e) => QueueDraw ();
193
tracker.HoveredChanged += (sender, e) => {
194
this.Animate ("Hovered",
195
easing: Easing.SinInOut,
196
start: renderArg.HoverProgress,
197
end: tracker.Hovered ? 1.0f : 0.0f,
198
callback: x => renderArg.HoverProgress = x);
201
IdeApp.FocusIn += delegate {
202
// If there was an error while the application didn't have the focus,
203
// trigger the error animation again when it gains the focus
204
if (errorAnimPending) {
205
errorAnimPending = false;
206
TriggerErrorAnimation ();
211
protected override void OnDestroyed ()
218
void StartBuildAnimation ()
220
this.Animate ("Build",
221
val => renderArg.BuildAnimationProgress = val,
225
this.Animate ("BuildOpacity",
226
start: renderArg.BuildAnimationOpacity,
228
callback: x => renderArg.BuildAnimationOpacity = x);
231
void StopBuildAnimation ()
233
this.Animate ("BuildOpacity",
234
start: renderArg.BuildAnimationOpacity,
236
callback: x => renderArg.BuildAnimationOpacity = x,
237
finished: (val, aborted) => { if (!aborted) this.AbortAnimation ("Build"); });
240
protected override void OnSizeAllocated (Gdk.Rectangle allocation)
242
if (MaxWidth > 0 && allocation.Width > MaxWidth) {
243
allocation = new Gdk.Rectangle (allocation.X + (allocation.Width - MaxWidth) / 2, allocation.Y, MaxWidth, allocation.Height);
245
base.OnSizeAllocated (allocation);
248
void TriggerErrorAnimation ()
250
/* Hack for a compiler error - csc crashes on this:
251
this.Animate (name: "statusAreaError",
253
callback: val => renderArg.ErrorAnimationProgress = val);
255
this.Animate ("statusAreaError",
256
val => renderArg.ErrorAnimationProgress = val,
260
void UpdateSeparators ()
262
statusIconSeparator.Visible = statusIconBox.Visible && buildResultWidget.Visible;
265
public Widget CreateBuildResultsWidget (Orientation orientation)
267
EventBox ebox = new EventBox ();
270
if (orientation == Orientation.Horizontal)
276
Gdk.Pixbuf errorIcon = ImageService.GetPixbuf (StockIcons.Error, IconSize.Menu);
277
Gdk.Pixbuf noErrorIcon = ImageService.MakeGrayscale (errorIcon); // creates a new pixbuf instance
278
Gdk.Pixbuf warningIcon = ImageService.GetPixbuf (StockIcons.Warning, IconSize.Menu);
279
Gdk.Pixbuf noWarningIcon = ImageService.MakeGrayscale (warningIcon); // creates a new pixbuf instance
281
Gtk.Image errorImage = new Gtk.Image (errorIcon);
282
Gtk.Image warningImage = new Gtk.Image (warningIcon);
284
box.PackStart (errorImage, false, false, 0);
285
Label errors = new Gtk.Label ();
286
box.PackStart (errors, false, false, 0);
288
box.PackStart (warningImage, false, false, 0);
289
Label warnings = new Gtk.Label ();
290
box.PackStart (warnings, false, false, 0);
291
box.NoShowAll = true;
294
TaskEventHandler updateHandler = delegate {
296
foreach (Task t in TaskService.Errors) {
297
if (t.Severity == TaskSeverity.Error)
299
else if (t.Severity == TaskSeverity.Warning)
302
errors.Visible = ec > 0;
303
errors.Text = ec.ToString ();
304
errorImage.Visible = ec > 0;
306
warnings.Visible = wc > 0;
307
warnings.Text = wc.ToString ();
308
warningImage.Visible = wc > 0;
309
ebox.Visible = ec > 0 || wc > 0;
313
updateHandler (null, null);
315
TaskService.Errors.TasksAdded += updateHandler;
316
TaskService.Errors.TasksRemoved += updateHandler;
318
box.Destroyed += delegate {
319
noErrorIcon.Dispose ();
320
noWarningIcon.Dispose ();
321
TaskService.Errors.TasksAdded -= updateHandler;
322
TaskService.Errors.TasksRemoved -= updateHandler;
325
ebox.VisibleWindow = false;
328
ebox.ButtonReleaseEvent += delegate {
329
var pad = IdeApp.Workbench.GetPad<MonoDevelop.Ide.Gui.Pads.ErrorListPad> ();
333
errors.Visible = false;
334
errorImage.Visible = false;
335
warnings.Visible = false;
336
warningImage.Visible = false;
341
protected override void OnRealized ()
344
ModifyText (StateType.Normal, Styles.StatusBarTextColor.ToGdkColor ());
345
ModifyFg (StateType.Normal, Styles.StatusBarTextColor.ToGdkColor ());
348
protected override void OnSizeRequested (ref Requisition requisition)
350
requisition.Height = 32;
351
base.OnSizeRequested (ref requisition);
354
protected override bool OnExposeEvent (Gdk.EventExpose evnt)
356
using (var context = Gdk.CairoHelper.Create (evnt.Window)) {
357
renderArg.Allocation = Allocation;
358
renderArg.ChildAllocation = messageBox.Allocation;
359
renderArg.MousePosition = tracker.MousePosition;
360
renderArg.Pango = PangoContext;
362
theme.Render (context, renderArg);
364
return base.OnExposeEvent (evnt);
368
#region StatusBar implementation
370
public void ShowCaretState (int line, int column, int selectedChars, bool isInInsertMode)
372
throw new NotImplementedException ();
375
public void ClearCaretState ()
377
throw new NotImplementedException ();
380
public StatusBarIcon ShowStatusIcon (Gdk.Pixbuf pixbuf)
382
DispatchService.AssertGuiThread ();
383
StatusIcon icon = new StatusIcon (this, pixbuf);
384
statusIconBox.PackEnd (icon.box);
385
statusIconBox.ShowAll ();
389
void HideStatusIcon (StatusIcon icon)
391
statusIconBox.Remove (icon.EventBox);
392
if (statusIconBox.Children.Length == 0)
393
statusIconBox.Hide ();
394
icon.EventBox.Destroy ();
397
List<StatusBarContextImpl> contexts = new List<StatusBarContextImpl> ();
398
public StatusBarContext CreateContext ()
400
StatusBarContextImpl ctx = new StatusBarContextImpl (this);
405
public void ShowReady ()
410
public void SetMessageSourcePad (Pad pad)
415
public bool HasResizeGrip {
420
public class StatusIcon : StatusBarIcon
422
StatusArea statusBar;
423
internal EventBox box;
432
TooltipPopoverWindow tooltipWindow;
435
public StatusIcon (StatusArea statusBar, Gdk.Pixbuf icon)
437
this.statusBar = statusBar;
439
box = new EventBox ();
440
box.VisibleWindow = false;
441
image = new Image (icon);
442
image.SetPadding (0, 0);
444
box.Events |= Gdk.EventMask.EnterNotifyMask | Gdk.EventMask.LeaveNotifyMask;
445
box.EnterNotifyEvent += HandleEnterNotifyEvent;
446
box.LeaveNotifyEvent += HandleLeaveNotifyEvent;
450
void HandleLeaveNotifyEvent (object o, LeaveNotifyEventArgs args)
457
void HandleEnterNotifyEvent (object o, EnterNotifyEventArgs args)
465
if (!string.IsNullOrEmpty (tip)) {
467
tooltipWindow = new TooltipPopoverWindow ();
468
tooltipWindow.ShowArrow = true;
469
tooltipWindow.Text = tip;
470
tooltipWindow.ShowPopup (box, PopupPosition.Top);
476
if (tooltipWindow != null) {
477
tooltipWindow.Destroy ();
478
tooltipWindow = null;
482
public void Dispose ()
485
statusBar.HideStatusIcon (this);
486
if (images != null) {
487
foreach (Gdk.Pixbuf img in images) {
491
if (animation != 0) {
492
GLib.Source.Remove (animation);
497
public string ToolTip {
501
if (tooltipWindow != null) {
502
if (!string.IsNullOrEmpty (tip))
503
tooltipWindow.Text = value;
506
} else if (!string.IsNullOrEmpty (tip) && mouseOver)
511
public EventBox EventBox {
515
public Gdk.Pixbuf Image {
523
public void SetAlertMode (int seconds)
526
alertEnd = DateTime.Now.AddSeconds (seconds);
529
GLib.Source.Remove (animation);
531
animation = GLib.Timeout.Add (60, new GLib.TimeoutHandler (AnimateIcon));
533
if (images == null) {
534
images = new Gdk.Pixbuf [10];
535
for (int n=0; n<10; n++)
536
images [n] = ImageService.MakeTransparent (icon, ((double)(9-n))/10.0);
542
if (DateTime.Now >= alertEnd && astep == 0) {
548
image.Pixbuf = images [astep];
550
image.Pixbuf = images [20 - astep - 1];
552
astep = (astep + 1) % 20;
559
#region StatusBarContextBase implementation
561
public void ShowError (string error)
563
ShowMessage (StockIcons.StatusError, error);
566
public void ShowWarning (string warning)
568
DispatchService.AssertGuiThread ();
569
ShowMessage (StockIcons.StatusWarning, warning);
572
public void ShowMessage (string message)
574
ShowMessage (null, message, false);
577
public void ShowMessage (string message, bool isMarkup)
579
ShowMessage (null, message, isMarkup);
582
public void ShowMessage (IconId image, string message)
584
ShowMessage (image, message, false);
587
public void ShowMessage (IconId image, string message, bool isMarkup)
589
if (this.AnimationIsRunning("Text") || animPauseHandle > 0) {
590
messageQueue.Clear ();
591
messageQueue.Enqueue (new Message (image, message, isMarkup));
593
ShowMessageInner (image, message, isMarkup);
597
void ShowMessageInner (IconId image, string message, bool isMarkup)
599
DispatchService.AssertGuiThread ();
601
if (image == StockIcons.StatusError) {
602
// If the application doesn't have the focus, trigger the animation
603
// again when it gains the focus
604
if (!IdeApp.CommandService.ApplicationHasFocus)
605
errorAnimPending = true;
606
TriggerErrorAnimation ();
609
LoadText (message, isMarkup);
611
/* Hack for a compiler error - csc crashes on this:
612
this.Animate ("Text", easing: Easing.SinInOut,
613
callback: x => renderArg.TextAnimationProgress = x,
614
finished: x => { animPauseHandle = GLib.Timeout.Add (1000, () => {
615
if (messageQueue.Count > 0) {
616
Message m = messageQueue.Dequeue();
617
ShowMessageInner (m.Icon, m.Text, m.IsMarkup);
624
this.Animate ("Text",
625
x => renderArg.TextAnimationProgress = x,
626
easing: Easing.SinInOut,
627
finished: (x, b) => { animPauseHandle = GLib.Timeout.Add (1000, () => {
628
if (messageQueue.Count > 0) {
629
Message m = messageQueue.Dequeue();
630
ShowMessageInner (m.Icon, m.Text, m.IsMarkup);
638
if (renderArg.CurrentText == renderArg.LastText)
639
this.AbortAnimation ("Text");
644
void LoadText (string message, bool isMarkup)
646
if (string.IsNullOrEmpty(message))
647
message = BrandingService.ApplicationName;
648
message = message ?? "";
650
renderArg.LastText = renderArg.CurrentText;
651
renderArg.CurrentText = message.Replace (System.Environment.NewLine, " ").Replace ("\n", " ").Trim ();
653
renderArg.LastTextIsMarkup = renderArg.CurrentTextIsMarkup;
654
renderArg.CurrentTextIsMarkup = isMarkup;
657
static bool iconLoaded = false;
658
void LoadPixbuf (IconId image)
660
// We dont need to load the same image twice
661
if (currentIcon == image && iconLoaded)
665
iconAnimation = null;
667
// clean up previous running animation
668
if (currentIconAnimation != null) {
669
currentIconAnimation.Dispose ();
670
currentIconAnimation = null;
673
// if we have nothing, use the default icon
674
if (image == IconId.Null)
675
image = "md-status-steady";
678
if (ImageService.IsAnimation (image, Gtk.IconSize.Menu)) {
679
iconAnimation = ImageService.GetAnimatedIcon (image, Gtk.IconSize.Menu);
680
renderArg.CurrentPixbuf = iconAnimation.FirstFrame;
681
currentIconAnimation = iconAnimation.StartAnimation (delegate (Gdk.Pixbuf p) {
682
renderArg.CurrentPixbuf = p;
686
renderArg.CurrentPixbuf = ImageService.GetPixbuf (image, Gtk.IconSize.Menu);
694
#region Progress Monitor implementation
695
public static event EventHandler ProgressBegin, ProgressEnd, ProgressPulse;
696
public static event EventHandler<FractionEventArgs> ProgressFraction;
698
public sealed class FractionEventArgs : EventArgs
700
public double Work { get; private set; }
702
public FractionEventArgs (double work)
708
static void OnProgressBegin (EventArgs e)
710
var handler = ProgressBegin;
715
static void OnProgressEnd (EventArgs e)
717
var handler = ProgressEnd;
722
static void OnProgressPulse (EventArgs e)
724
var handler = ProgressPulse;
729
static void OnProgressFraction (FractionEventArgs e)
731
var handler = ProgressFraction;
736
public void BeginProgress (string name)
739
if (!progressBarVisible) {
740
progressBarVisible = true;
741
OnProgressBegin (EventArgs.Empty);
745
public void BeginProgress (IconId image, string name)
747
ShowMessage (image, name);
748
if (!progressBarVisible) {
749
progressBarVisible = true;
750
OnProgressBegin (EventArgs.Empty);
754
public void SetProgressFraction (double work)
756
DispatchService.AssertGuiThread ();
757
OnProgressFraction (new FractionEventArgs (work));
760
public void EndProgress ()
762
if (!progressBarVisible)
765
progressBarVisible = false;
766
OnProgressEnd (EventArgs.Empty);
772
DispatchService.AssertGuiThread ();
773
OnProgressPulse (EventArgs.Empty);
776
uint autoPulseTimeoutId;
777
public bool AutoPulse {
778
get { return autoPulseTimeoutId != 0; }
780
DispatchService.AssertGuiThread ();
782
if (autoPulseTimeoutId == 0) {
783
autoPulseTimeoutId = GLib.Timeout.Add (100, delegate {
789
if (autoPulseTimeoutId != 0) {
790
GLib.Source.Remove (autoPulseTimeoutId);
791
autoPulseTimeoutId = 0;
798
internal bool IsCurrentContext (StatusBarContextImpl ctx)
800
return ctx == activeContext;
803
internal void Remove (StatusBarContextImpl ctx)
805
if (ctx == mainContext)
808
StatusBarContextImpl oldActive = activeContext;
809
contexts.Remove (ctx);
810
UpdateActiveContext ();
811
if (oldActive != activeContext) {
812
// Removed the active context. Update the status bar.
813
activeContext.Update ();
817
internal void UpdateActiveContext ()
819
for (int n = contexts.Count - 1; n >= 0; n--) {
820
StatusBarContextImpl ctx = contexts [n];
821
if (ctx.StatusChanged) {
822
if (ctx != activeContext) {
824
activeContext.Update ();
829
throw new InvalidOperationException (); // There must be at least the main context
833
class StatusAreaSeparator: HBox
835
protected override bool OnExposeEvent (Gdk.EventExpose evnt)
837
using (var ctx = Gdk.CairoHelper.Create (this.GdkWindow)) {
838
var alloc = Allocation;
839
//alloc.Inflate (0, -2);
840
ctx.Rectangle (alloc.X, alloc.Y, 1, alloc.Height);
841
using (Cairo.LinearGradient gr = new LinearGradient (alloc.X, alloc.Y, alloc.X, alloc.Y + alloc.Height)) {
842
gr.AddColorStop (0, new Cairo.Color (0, 0, 0, 0));
843
gr.AddColorStop (0.5, new Cairo.Color (0, 0, 0, 0.2));
844
gr.AddColorStop (1, new Cairo.Color (0, 0, 0, 0));
852
protected override void OnSizeRequested (ref Requisition requisition)
854
base.OnSizeRequested (ref requisition);
855
requisition.Width = 1;