3
// Copyright (C) 2008 GNOME Do
5
// This program is free software: you can redistribute it and/or modify
6
// it under the terms of the GNU General Public License as published by
7
// the Free Software Foundation, either version 3 of the License, or
8
// (at your option) any later version.
10
// This program is distributed in the hope that it will be useful,
11
// but WITHOUT ANY WARRANTY; without even the implied warranty of
12
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
// GNU General Public License for more details.
15
// You should have received a copy of the GNU General Public License
16
// along with this program. If not, see <http://www.gnu.org/licenses/>.
20
using System.Collections.Generic;
21
using System.Collections.ObjectModel;
29
using Do.Interface.Wink;
30
using Do.Interface.Xlib;
33
using Docky.Utilities;
34
using Docky.Interface.Menus;
35
using Docky.Interface.Painters;
37
namespace Docky.Interface
41
internal partial class DockArea : Gtk.DrawingArea
43
public static readonly TimeSpan BaseAnimationTime = new TimeSpan (0, 0, 0, 0, 150);
45
static DateTime UpdateTimeStamp (DateTime lastStamp, TimeSpan animationLength)
47
TimeSpan delta = DateTime.UtcNow - lastStamp;
48
if (delta < animationLength)
49
return DateTime.UtcNow.Subtract (animationLength - delta);
50
return DateTime.UtcNow;
53
const uint OffDockWakeupTime = 250;
54
const uint OnDockWakeupTime = 20;
56
const int UrgentBounceHeight = 80;
57
const int LaunchBounceHeight = 30;
59
TimeSpan BounceTime = new TimeSpan (0, 0, 0, 0, 700);
60
TimeSpan InsertAnimationTime = new TimeSpan (0, 0, 0, 0, 150*5);
62
#region Private Variables
63
DateTime enter_time = new DateTime (0);
64
DateTime interface_change_time = new DateTime (0);
65
DateTime last_draw_timeout = new DateTime (0);
66
DateTime showhide_time = new DateTime (0);
67
DateTime painter_time = new DateTime (0);
69
TimeSpan painter_span;
75
DrawingService drawing_service;
79
#region Public Properties
81
/// Returns true if the cursor is over the visible part of the dock or the pixel closest to its autohide edge
83
public bool CursorIsOverDockArea { get; private set; }
86
/// The width of the docks window, but not the visible dock
88
public int Width { get; private set; }
91
/// The height of the docks window
93
public int Height { get; private set; }
96
/// Returns true if the dock is currently being overlayed by an IDockPainter
98
public bool PainterOverlayVisible { get; private set; }
101
/// The width of the visible dock
103
public int DockWidth {
104
get { return PositionProvider.DockWidth; }
108
/// The height of the visible dock
110
public int DockHeight {
111
get { return PositionProvider.DockHeight; }
114
public uint[] StrutRequest {
116
uint[] values = new uint[12];
117
Gdk.Rectangle geo = LayoutUtils.MonitorGeometry ();
119
if (DockPreferences.AutohideType != AutohideType.None)
122
switch (DockPreferences.Orientation) {
123
case DockOrientation.Bottom:
124
values [(int) Struts.Bottom] = (uint) (DockHeight + (Screen.Height - (geo.Y + geo.Height)));
125
values [(int) Struts.BottomStart] = (uint) geo.X;
126
values [(int) Struts.BottomEnd] = (uint) (geo.X + geo.Width - 1);
128
case DockOrientation.Top:
129
values [(int) Struts.Top] = (uint) (DockHeight + geo.Y);
130
values [(int) Struts.TopStart] = (uint) geo.X;
131
values [(int) Struts.TopEnd] = (uint) (geo.X + geo.Width - 1);
139
/// The current cursor as known to the dock.
141
public Gdk.Point Cursor {
143
return CursorTracker.Cursor;
147
public Gdk.Rectangle MinimumDockArea {
149
return PositionProvider.MinimumDockArea;
154
AutohideTracker AutohideTracker { get; set; }
156
CursorTracker CursorTracker { get; set; }
158
DnDTracker DnDTracker { get; set; }
160
DockAnimationState AnimationState { get; set; }
162
ItemPositionProvider PositionProvider { get; set; }
164
new DockItemMenu PopupMenu { get; set; }
166
ReadOnlyCollection<AbstractDockItem> DockItems {
167
get { return DockServices.ItemsService.DockItems; }
170
AbstractDockItem CurrentDockItem {
172
int index = PositionProvider.IndexAtPosition (Cursor);
173
if (index >= 0 && index < DockItems.Count)
174
return DockItems [index];
179
TimeSpan SummonTime {
180
get { return DockPreferences.SummonTime; }
183
bool WindowIntersectingOther {
184
get { return AutohideTracker.WindowIntersectingOther; }
187
IEnumerable<Gdk.Window> WindowStack {
190
return Screen.WindowStack;
193
return Wnck.Screen.Default.WindowsStacked.Select (wnk => Gdk.Window.ForeignNew ((uint) wnk.Xid));
201
public DockArea (DockWindow window) : base ()
203
drawing_service = new DrawingService (this);
204
DockServices.RegisterService (drawing_service);
206
this.window = window;
208
ScreenUtils.Initialize ();
209
WindowUtils.Initialize ();
211
AnimationState = new DockAnimationState ();
212
AutohideTracker = new AutohideTracker (this);
213
CursorTracker = new CursorTracker (window, OffDockWakeupTime);
214
PositionProvider = new ItemPositionProvider (this);
215
DnDTracker = new DnDTracker (this, PositionProvider, CursorTracker);
216
PopupMenu = new DockItemMenu ();
218
BuildAnimationStateEngine ();
220
this.SetCompositeColormap ();
222
AddEvents ((int) EventMask.ButtonPressMask |
223
(int) EventMask.ButtonReleaseMask |
224
(int) EventMask.ScrollMask |
225
(int) EventMask.FocusChangeMask);
228
DoubleBuffered = false;
240
geo = LayoutUtils.MonitorGeometry ();
243
Height = DockPreferences.FullIconSize + 2 * PositionProvider.VerticalBuffer + UrgentBounceHeight;
244
Height = Math.Max (150, Height);
246
SetSizeRequest (Width, Height);
249
void RegisterEvents ()
251
DockServices.ItemsService.DockItemsChanged += OnDockItemsChanged;
252
DockServices.ItemsService.ItemNeedsUpdate += HandleItemNeedsUpdate;
254
DockPreferences.MonitorChanged += HandleMonitorChanged;
255
DockPreferences.IconSizeChanged += HandleIconSizeChanged;
256
DockPreferences.OrientationChanged += HandleOrientationChanged;
258
DockServices.PainterService.PainterShowRequest += HandlePainterShowRequest;
259
DockServices.PainterService.PainterHideRequest += HandlePainterHideRequest;
261
Screen.SizeChanged += HandleSizeChanged;
263
PopupMenu.Hidden += OnDockItemMenuHidden;
264
PopupMenu.Shown += OnDockItemMenuShown;
266
Services.Core.UniverseInitialized += HandleUniverseInitialized;
268
Realized += (o, e) => SetParentInputMask ();
269
Realized += (o, a) => GdkWindow.SetBackPixmap (null, false);
271
AutohideTracker.IntersectionChanged += HandleIntersectionChanged;
272
Wnck.Screen.Default.ActiveWindowChanged += HandleActiveWindowChanged;
274
CursorTracker.CursorUpdated += HandleCursorUpdated;
276
DnDTracker.DragEnded += HandleDragEnded;
277
DnDTracker.DrawRequired += HandleDrawRequired;
279
window.KeyPressEvent += HandleKeyPressEvent;
281
StyleSet += (o, a) => {
283
GdkWindow.SetBackPixmap (null, false);
287
void UnregisterEvents ()
289
DockServices.ItemsService.DockItemsChanged -= OnDockItemsChanged;
290
DockServices.ItemsService.ItemNeedsUpdate -= HandleItemNeedsUpdate;
292
DockPreferences.MonitorChanged -= HandleMonitorChanged;
293
DockPreferences.IconSizeChanged -= HandleIconSizeChanged;
294
DockPreferences.OrientationChanged -= HandleOrientationChanged;
296
DockServices.PainterService.PainterShowRequest -= HandlePainterShowRequest;
297
DockServices.PainterService.PainterHideRequest -= HandlePainterHideRequest;
299
Screen.SizeChanged -= HandleSizeChanged;
301
PopupMenu.Hidden -= OnDockItemMenuHidden;
302
PopupMenu.Shown -= OnDockItemMenuShown;
304
Services.Core.UniverseInitialized -= HandleUniverseInitialized;
306
AutohideTracker.IntersectionChanged -= HandleIntersectionChanged;
307
Wnck.Screen.Default.ActiveWindowChanged -= HandleActiveWindowChanged;
309
CursorTracker.CursorUpdated -= HandleCursorUpdated;
311
DnDTracker.DragEnded -= HandleDragEnded;
312
DnDTracker.DrawRequired -= HandleDrawRequired;
314
window.KeyPressEvent -= HandleKeyPressEvent;
317
void BuildAnimationStateEngine ()
319
AnimationState.AddCondition (Animations.IconInsert,
320
() => DockItems.Any (di => di.TimeSinceAdd < InsertAnimationTime));
322
AnimationState.AddCondition (Animations.Zoom,
323
() => (CursorIsOverDockArea && ZoomIn != 1) || (!CursorIsOverDockArea && ZoomIn != 0));
325
AnimationState.AddCondition (Animations.Open,
326
() => DateTime.UtcNow - enter_time < BaseAnimationTime ||
327
DateTime.UtcNow - interface_change_time < BaseAnimationTime);
329
AnimationState.AddCondition (Animations.Bounce,
330
() => DockItems.Any (di => di.TimeSinceClick <= BounceTime));
332
AnimationState.AddCondition (Animations.UrgencyChanged,
333
() => DockItems.Any (di => DateTime.UtcNow - di.AttentionRequestStartTime < BounceTime));
335
AnimationState.AddCondition (Animations.InputModeChanged,
336
() => DateTime.UtcNow - interface_change_time < SummonTime ||
337
DateTime.UtcNow - showhide_time < SummonTime);
340
void HandleKeyPressEvent (Gdk.EventKey key)
342
KeyBinding clearKeyBinding = Services.Keybinder.Bindings.Where(p => p.Description == Mono.Unix.Catalog.GetString ("Clear")).FirstOrDefault();
343
if (PainterOverlayVisible &&
344
clearKeyBinding != null && Services.Keybinder.KeyEventToString (key) == clearKeyBinding.KeyString)
348
void HandleCursorUpdated (object sender, CursorUpdatedArgs args)
350
if (PopupMenu.Visible)
353
UpdateCursorIsOverDockArea ();
355
bool cursorMoveWarrantsDraw = CursorIsOverDockArea && args.OldCursor.X != args.NewCursor.X;
357
if (DnDTracker.DragResizing || cursorMoveWarrantsDraw)
361
void HandleDrawRequired(object sender, EventArgs e)
366
void HandleDragEnded(object sender, EventArgs e)
372
void HandleItemNeedsUpdate (object sender, UpdateRequestArgs args)
374
if (args.Type == UpdateRequestType.NeedsAttentionSet) {
375
SetParentInputMask ();
378
GLib.Timeout.Add ((uint) BounceTime.Milliseconds + 20, delegate {
384
if (args.Type == UpdateRequestType.IconChanged || args.Type == UpdateRequestType.NameChanged) {
385
RequestIconRender (args.Item);
387
RequestFullRender ();
392
void HandleMonitorChanged()
397
void HandleOrientationChanged()
400
window.Reposition ();
403
void HandleSizeChanged (object sender, EventArgs e)
408
public void Reconfigure ()
410
if (!Visible || !IsRealized || DnDTracker.DragResizing)
415
PositionProvider.ForceUpdate ();
416
SetParentInputMask ();
418
window.DelaySetStruts ();
422
void HandleUniverseInitialized (object sender, EventArgs e)
424
GLib.Timeout.Add (2000, delegate {
425
DockServices.ItemsService.ForceUpdate ();
431
void HandleIconSizeChanged()
437
void HandlePaintNeeded (object sender, PaintNeededArgs args)
439
if (sender != Painter && sender != LastPainter) return;
442
painter_span = args.AnimationLength;
443
painter_time = DateTime.UtcNow;
444
if (!AnimationState.Contains (Animations.Painter))
445
AnimationState.AddCondition (Animations.Painter, () => DateTime.UtcNow - painter_time < painter_span);
450
void HandlePainterHideRequest(object sender, EventArgs e)
452
IDockPainter painter = sender as IDockPainter;
453
if (Painter != painter)
457
PainterOverlayVisible = false;
458
interface_change_time = UpdateTimeStamp (interface_change_time, SummonTime);
460
SetParentInputMask ();
463
GLib.Timeout.Add (500, () => {
464
DockServices.ItemsService.ForceUpdate ();
468
window.UnpresentWindow ();
469
DnDTracker.Enable ();
472
void HandlePainterShowRequest(object sender, EventArgs e)
474
IDockPainter painter = sender as IDockPainter;
475
if (Painter == painter)
478
if (Painter == null || Painter.Interruptable) {
480
Painter.Interrupt ();
482
PainterOverlayVisible = true;
483
interface_change_time = UpdateTimeStamp (interface_change_time, SummonTime);
485
SetParentInputMask ();
488
painter.Interrupt ();
491
window.PresentWindow ();
492
DnDTracker.Disable ();
495
void ResetCursorTimer ()
497
CursorTracker.TimerLength = (CursorIsOverDockArea || DnDTracker.DragResizing) ? OnDockWakeupTime : OffDockWakeupTime;
500
void UpdateCursorIsOverDockArea ()
502
bool tmp = CursorIsOverDockArea;
504
Gdk.Rectangle dockRegion;
505
if (PainterOverlayVisible)
506
dockRegion = GetDockArea ();
508
dockRegion = MinimumDockArea;
511
dockRegion.Inflate (0, (int) (IconSize * (DockPreferences.ZoomPercent - 1)) + 22);
514
switch (DockPreferences.Orientation) {
515
case DockOrientation.Bottom:
516
dockRegion.Y += dockRegion.Height - 1;
517
dockRegion.Height = 1;
519
case DockOrientation.Top:
520
dockRegion.Height = 1;
526
if (PainterOverlayVisible)
527
CursorIsOverDockArea = dockRegion.Contains (new Gdk.Point (dockRegion.X, Cursor.Y));
529
CursorIsOverDockArea = dockRegion.Contains (Cursor);
531
if (CursorIsOverDockArea != tmp) {
533
enter_time = UpdateTimeStamp (enter_time, BaseAnimationTime);
535
switch (DockPreferences.AutohideType) {
536
case AutohideType.Autohide:
537
showhide_time = enter_time;
539
case AutohideType.Intellihide:
540
if (WindowIntersectingOther)
541
showhide_time = enter_time;
546
if (PainterOverlayVisible) {
547
GLib.Timeout.Add (500, delegate {
548
if (!CursorIsOverDockArea && PainterOverlayVisible && (DateTime.UtcNow - enter_time).TotalMilliseconds > 400)
559
if (0 < animation_timer) {
560
if ((DateTime.UtcNow - last_draw_timeout).TotalMilliseconds > 500) {
561
// honestly this should never happen. I am not sure if it does but we are going
562
// to protect against it because there are reports of rendering failing. A condition
563
// where the animation_timer is > 0 without an actual callback tied to it would
564
// forever block future animations.
565
GLib.Source.Remove (animation_timer);
572
// the presense of this queue draw has caused some confusion, so I will explain.
573
// first its here to draw the "first frame". Without it, we have a 16ms delay till that happens,
574
// however minor that is.
577
if (AnimationState.AnimationNeeded)
578
animation_timer = GLib.Timeout.Add (1000/60, OnDrawTimeoutElapsed);
581
bool OnDrawTimeoutElapsed ()
583
last_draw_timeout = DateTime.UtcNow;
587
if (AnimationState.AnimationNeeded)
590
//reset the timer to 0 so that the next time AnimatedDraw is called we fall back into
599
// this is a "protected method". We need to be sure that our input mask is okay on every frame.
600
// 99% of the time this means nothing at all will be done
601
SetParentInputMask ();
604
void OnDockItemsChanged (IEnumerable<AbstractDockItem> items)
606
DockPreferences.MaxIconSize = (int) (((double) Width / MinimumDockArea.Width) * IconSize);
609
RequestFullRender ();
610
UpdateCursorIsOverDockArea ();
614
void OnDockItemMenuHidden (object o, System.EventArgs args)
616
// While an popup menus are being showing, the dock does not recieve mouse updates. This is
617
// both a good thing and a bad thing. We must at the very least update the cursor position once the
618
// popup is no longer in view.
619
CursorTracker.Enabled = true;
624
/// Only purpose is to trigger one last redraw to eliminate the hover text
626
void OnDockItemMenuShown (object o, EventArgs args)
628
CursorTracker.Enabled = false;
632
public void ProxyButtonReleaseEvent (Gdk.EventButton evnt)
634
HandleButtonReleaseEvent (evnt);
637
protected override bool OnButtonReleaseEvent (Gdk.EventButton evnt)
639
bool result = base.OnButtonPressEvent (evnt);
640
HandleButtonReleaseEvent (evnt);
645
private void HandleButtonReleaseEvent (Gdk.EventButton evnt)
647
// lets not do anything in this case
648
if (DnDTracker.DragResizing) return;
650
if (PainterOverlayVisible) {
651
Painter.Clicked (GetDockArea (), Cursor);
652
if (PainterOverlayVisible && !GetDockArea ().Contains (Cursor)) {
656
int item = PositionProvider.IndexAtPosition ((int) evnt.X, (int) evnt.Y); //sometimes clicking is not good!
657
if (item < 0 || item >= DockItems.Count || !CursorIsOverDockArea || PainterOverlayVisible)
660
//handling right clicks for those icons which request simple right click handling
661
if (evnt.Button == 3) {
662
if (CurrentDockItem is IRightClickable && (CurrentDockItem as IRightClickable).GetMenuItems ().Any ()) {
663
PointD itemPosition_;
665
IconZoomedPosition (PositionProvider.IndexAtPosition (Cursor), out itemPosition_, out itemZoom);
667
Gdk.Point itemPosition = new Gdk.Point ((int) itemPosition_.X, (int) itemPosition_.Y);
669
itemPosition = itemPosition.RelativeMovePoint ((int) (IconSize * itemZoom * .9 * .5), RelativeMove.Inward);
670
itemPosition = itemPosition.RelativePointToRootPoint (window);
672
PopupMenu.PopUp (CurrentDockItem.Description,
673
(CurrentDockItem as IRightClickable).GetMenuItems (),
680
//send off the clicks
681
PointD relative_point = RelativePointOverItem (item);
682
DockItems [item].Clicked (evnt.Button, evnt.State, relative_point);
689
PointD RelativePointOverItem (int item)
691
PointD relative_point = new PointD (0,0);
694
IconZoomedPosition (item, out center, out zoom);
696
int left = (int) (center.X - DockItems [item].Width * zoom / 2);
697
int top = (int) (center.Y - DockItems [item].Height * zoom / 2);
698
int right = (int) (center.X + DockItems [item].Width * zoom / 2);
699
int bottom = (int) (center.Y + DockItems [item].Height * zoom / 2);
701
relative_point.X = (Cursor.X - left) / (double) (right - left);
702
relative_point.Y = (Cursor.Y - top) / (double) (bottom - top);
704
return relative_point;
707
protected override bool OnScrollEvent (Gdk.EventScroll evnt)
709
if (PainterOverlayVisible) {
710
Painter.Scrolled (evnt.Direction);
712
int item = PositionProvider.IndexAtPosition ((int) evnt.X, (int) evnt.Y);
713
if (item >= 0 && item < DockItems.Count)
714
DockItems [item].Scrolled (evnt.Direction);
716
return base.OnScrollEvent (evnt);
719
void SetIconRegions ()
721
Gdk.Rectangle pos, area;
722
window.GetPosition (out pos.X, out pos.Y);
723
window.GetSize (out pos.Width, out pos.Height);
725
// we use geo here instead of our position for the Y value because we know the parent window
726
// may offset us when hidden. This is not desired...
727
for (int i = 0; i < DockItems.Count; i++) {
728
Gdk.Point position = PositionProvider.IconUnzoomedPosition (i);
729
area = new Gdk.Rectangle (pos.X + (position.X - IconSize / 2),
730
pos.Y + (position.Y - IconSize / 2),
733
DockItems [i].SetIconRegion (area);
737
void SetParentInputMask ()
743
if (PainterOverlayVisible) {
745
} else if (CursorIsOverDockArea) {
746
offset = GetDockArea ().Height;
747
offset = offset * 2 + 10;
749
if (IsHidden && !DnDTracker.DragResizing) {
752
offset = GetDockArea ().Height;
756
int dockSize = (DnDTracker.DragResizing) ? Width : MinimumDockArea.Width;
758
switch (DockPreferences.Orientation) {
759
case DockOrientation.Bottom:
760
window.SetInputMask (new Gdk.Rectangle ((Width - dockSize) / 2,
765
case DockOrientation.Top:
766
window.SetInputMask (new Gdk.Rectangle ((Width - dockSize) / 2,
774
void InterruptPainter ()
776
if (Painter == null || !Painter.Interruptable) return;
778
Painter.Interrupt ();
780
PainterOverlayVisible = false;
781
interface_change_time = DateTime.UtcNow;
783
DnDTracker.Enable ();
784
window.UnpresentWindow ();
786
SetParentInputMask ();
790
public override void Dispose ()
792
DockServices.UnregisterService (drawing_service).Dispose ();
795
DnDTracker.Disable ();
797
AutohideTracker.Dispose ();
798
CursorTracker.Dispose ();
799
DnDTracker.Dispose ();
800
PositionProvider.Dispose ();
801
AnimationState.Dispose ();
802
PopupMenu.Destroy ();
803
PopupMenu.Dispose ();
805
PositionProvider = null;
806
AnimationState = null;
809
if (backbuffer != null)
810
backbuffer.Destroy ();
812
if (input_area_buffer != null)
813
input_area_buffer.Destroy ();
815
if (dock_icon_buffer != null)
816
dock_icon_buffer.Destroy ();
818
if (LastPainter != null) {
819
LastPainter.PaintNeeded -= HandlePaintNeeded;
822
if (Painter != null) {
823
Painter.PaintNeeded -= HandlePaintNeeded;
828
if (animation_timer > 0)
829
GLib.Source.Remove (animation_timer);