2
// SearchPopupWindow.cs
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
28
using MonoDevelop.Ide;
29
using MonoDevelop.Ide.Gui;
30
using Mono.TextEditor;
33
namespace MonoDevelop.Components
35
public class PopoverWindow : Gtk.Window, Animatable
37
PopoverWindowTheme theme;
39
PopupPosition position;
40
Gtk.Alignment alignment;
42
Gdk.Rectangle currentCaret;
43
Gdk.Window targetWindow;
50
bool disableSizeCheck;
52
const int MinArrowSpacing = 5;
54
public PopoverWindow () : this(Gtk.WindowType.Popup)
58
public PopoverWindow (Gtk.WindowType type) : base(type)
60
SkipTaskbarHint = true;
63
TypeHint = WindowTypeHint.Tooltip;
64
CheckScreenColormap ();
66
alignment = new Alignment (0, 0, 1f, 1f);
70
disableSizeCheck = false;
72
SizeRequested += (object o, SizeRequestedArgs args) => {
73
if (this.AnimationIsRunning("Resize") && !disableSizeCheck) {
74
Gtk.Requisition result = new Gtk.Requisition ();
75
result.Width = Math.Max (args.Requisition.Width, Math.Max (Allocation.Width, targetSize.Width));
76
result.Height = Math.Max (args.Requisition.Height, Math.Max (Allocation.Height, targetSize.Height));
77
args.Requisition = result;
84
public PopoverWindowTheme Theme {
87
theme = new PopoverWindowTheme ();
88
theme.RedrawNeeded += OnRedrawNeeded;
96
theme.RedrawNeeded -= OnRedrawNeeded;
98
theme.RedrawNeeded += OnRedrawNeeded;
105
public bool ShowArrow {
106
get { return Theme.ShowArrow; }
107
set { Theme.ShowArrow = value; }
110
public Gtk.Alignment ContentBox {
111
get { return alignment; }
114
public void ShowPopup (Gtk.Widget widget, PopupPosition position)
116
ShowPopup (widget, null, Gdk.Rectangle.Zero, position);
119
public void ShowPopup (Gtk.Widget widget, Gdk.EventButton evt, PopupPosition position)
121
ShowPopup (widget, evt, Gdk.Rectangle.Zero, position);
124
public void ShowPopup (Gtk.Widget widget, Gdk.Rectangle caret, PopupPosition position)
126
ShowPopup (widget, null, caret, position);
129
void ShowPopup (Gtk.Widget parent, Gdk.EventButton evt, Gdk.Rectangle caret, PopupPosition position)
131
this.parent = parent;
132
this.currentCaret = caret;
133
Theme.TargetPosition = position;
136
eventProvided = true;
137
targetWindow = evt.Window;
139
targetWindow = parent.GdkWindow;
144
public void AnimatedResize ()
146
if (!GtkUtil.ScreenSupportsARGB ()) {
151
disableSizeCheck = true;
152
Gtk.Requisition sizeReq = Gtk.Requisition.Zero;
153
// use OnSizeRequested instead of SizeRequest to bypass internal GTK caching
154
OnSizeRequested (ref sizeReq);
155
disableSizeCheck = false;
157
Gdk.Size size = new Gdk.Size (sizeReq.Width, sizeReq.Height);
159
// ensure that our apint area is big enough for our padding
160
if (paintSize.Width <= 15 || paintSize.Height <= 15)
164
Gdk.Size start = paintSize;
165
Func<float, Gdk.Size> transform = x => new Gdk.Size ((int)(start.Width + (size.Width - start.Width) * x),
166
(int)(start.Height + (size.Height - start.Height) * x));
167
this.Animate ("Resize",
169
easing: Easing.SinInOut,
170
transform: transform,
171
callback: s => paintSize = s,
172
finished: (x, aborted) => { if (!aborted) MaybeReanimate(); });
176
void MaybeReanimate ()
178
disableSizeCheck = true;
179
Gtk.Requisition sizeReq = Gtk.Requisition.Zero;
180
OnSizeRequested (ref sizeReq);
181
disableSizeCheck = false;
183
if (sizeReq.Width == paintSize.Width && sizeReq.Height == paintSize.Height)
186
AnimatedResize (); //Desired size changed mid animation
190
/// Gets or sets the maximum Y top bound. The popover window will be placed below this bound.
191
/// 0 means it's not set. Default value: 0
193
public int MaximumYTopBound {
198
public void RepositionWindow ()
204
Gdk.Rectangle caret = currentCaret;
205
Gdk.Window window = targetWindow;
206
if (targetWindow == null)
208
PopupPosition position = Theme.TargetPosition;
209
this.position = Theme.TargetPosition;
212
window.GetOrigin (out x, out y);
213
var alloc = parent.Allocation;
218
caret.Width = caret.Height = 1;
220
if (caret.Equals (Gdk.Rectangle.Zero))
221
caret = new Gdk.Rectangle (0, 0, alloc.Width, alloc.Height);
222
caret = GtkUtil.ToScreenCoordinates (parent, parent.GdkWindow, caret);
225
Gtk.Requisition request = SizeRequest ();
226
var screen = parent.Screen;
227
Gdk.Rectangle geometry = GtkWorkarounds.GetUsableMonitorGeometry (screen, screen.GetMonitorAtPoint (x, y));
229
// Flip the orientation if the window doesn't fit the screen.
231
int intPos = (int) position;
232
switch ((PopupPosition)(intPos & 0x0f)) {
233
case PopupPosition.Top:
234
if (caret.Bottom + request.Height > geometry.Bottom)
235
intPos = (intPos & 0xf0) | (int)PopupPosition.Bottom;
237
case PopupPosition.Bottom:
238
if (caret.Top - request.Height < geometry.X)
239
intPos = (intPos & 0xf0) | (int)PopupPosition.Top;
241
case PopupPosition.Right:
242
if (caret.X - request.Width < geometry.X)
243
intPos = (intPos & 0xf0) | (int)PopupPosition.Left;
245
case PopupPosition.Left:
246
if (caret.Right + request.Width > geometry.Right)
247
intPos = (intPos & 0xf0) | (int)PopupPosition.Right;
251
position = (PopupPosition) intPos;
254
// Calculate base coordinate
256
switch ((PopupPosition)((int)position & 0x0f)) {
257
case PopupPosition.Top:
260
case PopupPosition.Bottom:
261
y = caret.Y - request.Height; break;
262
case PopupPosition.Right:
263
x = caret.X - request.Width; break;
264
case PopupPosition.Left:
265
x = caret.Right; break;
268
if ((position & PopupPosition.Top) != 0 || (position & PopupPosition.Bottom) != 0) {
269
if (((int)position & 0x10) != 0)
270
x = caret.X - MinArrowSpacing - Theme.ArrowWidth/2;
271
else if (((int)position & 0x20) != 0)
272
x = caret.Right - request.Width + MinArrowSpacing + Theme.ArrowWidth/2;
274
x = caret.X + (caret.Width - request.Width) / 2;
276
if (x < geometry.Left)
278
else if (x + request.Width > geometry.Right)
279
x = geometry.Right - request.Width;
281
offset = caret.X + caret.Width / 2 - x;
282
if (offset - Theme.ArrowWidth/2 < MinArrowSpacing)
283
offset = MinArrowSpacing + Theme.ArrowWidth/2;
284
if (offset > request.Width - MinArrowSpacing - Theme.ArrowWidth/2)
285
offset = request.Width - MinArrowSpacing - Theme.ArrowWidth/2;
288
if (((int)position & 0x10) != 0)
289
y = caret.Y - MinArrowSpacing - Theme.ArrowWidth/2;
290
else if (((int)position & 0x20) != 0)
291
y = caret.Bottom - request.Height + MinArrowSpacing + Theme.ArrowWidth/2;
293
y = caret.Y + (caret.Height - request.Height) / 2;
295
if (y < geometry.Top)
297
else if (y + request.Height > geometry.Bottom)
298
y = geometry.Bottom - request.Height;
299
if (MaximumYTopBound > 0)
300
y = Math.Max (MaximumYTopBound, y);
302
offset = caret.Y + caret.Height / 2 - y;
303
if (offset - Theme.ArrowWidth/2 < MinArrowSpacing)
304
offset = MinArrowSpacing + Theme.ArrowWidth/2;
305
if (offset > request.Height - MinArrowSpacing - Theme.ArrowWidth/2)
306
offset = request.Height - MinArrowSpacing - Theme.ArrowWidth/2;
308
Theme.ArrowOffset = offset;
309
this.position = position;
314
DesktopService.RemoveWindowShadow (this);
317
public bool SupportsAlpha {
322
void CheckScreenColormap ()
324
SupportsAlpha = Screen.IsComposited;
326
Colormap = Screen.RgbaColormap;
328
Colormap = Screen.RgbColormap;
332
protected override void OnScreenChanged (Gdk.Screen previous_screen)
334
base.OnScreenChanged (previous_screen);
335
CheckScreenColormap ();
338
protected override bool OnExposeEvent (Gdk.EventExpose evnt)
342
using (var context = Gdk.CairoHelper.Create (evnt.Window)) {
345
context.Operator = Cairo.Operator.Source;
346
context.SetSourceRGBA (1, 1, 1, 0);
348
context.Operator = Cairo.Operator.Over;
349
context.SetSourceRGB (1, 1, 1);
354
OnDrawContent (evnt, context); // Draw content first so we can easily clip it
355
retVal = base.OnExposeEvent (evnt);
357
changed = Theme.SetBorderPath (context, BorderAllocation, position);
358
context.Operator = Cairo.Operator.DestIn;
359
context.SetSourceRGBA (1, 1, 1, 1);
361
context.Operator = Cairo.Operator.Over;
363
// protect against overriden methods which leave in a bad state
365
if (Theme.DrawPager) {
366
Theme.RenderPager (context,
368
new Gdk.Rectangle (Allocation.X, Allocation.Y, paintSize.Width, paintSize.Height));
371
Theme.RenderBorder (context, BorderAllocation, position);
377
GtkWorkarounds.UpdateNativeShadow (this);
382
protected virtual void OnDrawContent (Gdk.EventExpose evnt, Cairo.Context context)
384
Theme.RenderBackground (context, new Gdk.Rectangle (Allocation.X, Allocation.Y, paintSize.Width, paintSize.Height));
387
void UpdatePadding ()
389
uint top,left,bottom,right;
390
top = left = bottom = right = (uint)Theme.Padding + 1;
393
if ((position & PopupPosition.Top) != 0)
394
top += (uint)Theme.ArrowLength;
395
else if ((position & PopupPosition.Bottom) != 0)
396
bottom += (uint)Theme.ArrowLength;
397
else if ((position & PopupPosition.Left) != 0)
398
left += (uint)Theme.ArrowLength;
399
else if ((position & PopupPosition.Right) != 0)
400
right += (uint)Theme.ArrowLength;
402
alignment.SetPadding (top, bottom, left, right);
405
void OnRedrawNeeded (object sender, EventArgs args)
411
protected override void OnSizeAllocated (Rectangle allocation)
413
if (!this.AnimationIsRunning ("Resize"))
414
paintSize = new Gdk.Size (allocation.Width, allocation.Height);
416
base.OnSizeAllocated (allocation);
419
protected Rectangle ChildAllocation {
421
var rect = BorderAllocation;
422
rect.Inflate (-Theme.Padding - 1, -Theme.Padding - 1);
427
Rectangle BorderAllocation {
429
var rect = new Gdk.Rectangle (Allocation.X, Allocation.Y, paintSize.Width, paintSize.Height);
431
if ((position & PopupPosition.Top) != 0) {
432
rect.Y += Theme.ArrowLength;
433
rect.Height -= Theme.ArrowLength;
435
else if ((position & PopupPosition.Bottom) != 0) {
436
rect.Height -= Theme.ArrowLength;
438
else if ((position & PopupPosition.Left) != 0) {
439
rect.X += Theme.ArrowLength;
440
rect.Width -= Theme.ArrowLength;
442
else if ((position & PopupPosition.Right) != 0) {
443
rect.Width -= Theme.ArrowLength;