5
// Mike KrĆ¼ger <mkrueger@novell.com>
7
// Copyright (c) 2009 Novell, Inc (http://www.novell.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 System.ComponentModel;
31
namespace MonoDevelop.Components
33
[Category ("Widgets")]
35
public class DropDownBox : Gtk.Button
38
const int pixbufSpacing = 2;
39
const int leftSpacing = 2;
40
const int ySpacing = 1;
47
layout.SetText (value);
52
public DropDownBoxListWindow.IListDataProvider DataProvider {
57
public bool DrawRightBorder {
62
public Gdk.Pixbuf Pixbuf {
67
public object CurrentItem {
72
int defaultIconHeight, defaultIconWidth;
75
/// This is so that the height doesn't jump around depending whether there's an icon assigned or not.
77
public int DefaultIconHeight {
78
get { return defaultIconHeight; }
80
defaultIconHeight = value;
84
public int DefaultIconWidth {
85
get { return defaultIconWidth; }
87
defaultIconWidth = value;
93
layout = new Pango.Layout (this.PangoContext);
94
this.Events = Gdk.EventMask.KeyPressMask | Gdk.EventMask.FocusChangeMask | Gdk.EventMask.ButtonPressMask | Gdk.EventMask.ButtonReleaseMask | Gdk.EventMask.PointerMotionMask | Gdk.EventMask.LeaveNotifyMask;
99
void PositionListWindow ()
104
ParentWindow.GetOrigin (out ox, out oy);
105
int dx = ox + this.Allocation.X;
106
int dy = oy + this.Allocation.Bottom;
107
window.WidthRequest = Allocation.Width;
109
window.GetSizeRequest (out width, out height);
110
Gdk.Rectangle geometry = Screen.GetMonitorGeometry (Screen.GetMonitorAtPoint (dx, dy));
112
if (dy + height > geometry.Bottom)
113
dy = oy + this.Allocation.Y - height;
114
if (dx + width > geometry.Right)
115
dx = geometry.Right - width;
117
window.Move (dx, dy);
118
window.GetSizeRequest (out width, out height);
122
public void SetItem (string text, Gdk.Pixbuf icon, object currentItem)
124
if (currentItem != CurrentItem) {// don't update when the same item is set.
126
this.CurrentItem = currentItem;
132
ItemSet (this, EventArgs.Empty);
135
public void SetItem (int i)
137
SetItem (DataProvider.GetText (i), DataProvider.GetIcon (i), DataProvider.GetTag (i));
140
protected override void OnDestroyed ()
143
if (layout != null) {
151
protected override void OnSizeRequested (ref Requisition requisition)
154
layout.GetPixelSize (out width, out height);
156
if (Pixbuf != null) {
157
width += Pixbuf.Width + pixbufSpacing * 2;
158
height = System.Math.Max (height, Pixbuf.Height);
160
height = System.Math.Max (height, defaultIconHeight);
165
int arrowHeight = height / 2;
166
int arrowWidth = arrowHeight + 1;
168
requisition.Width = width + arrowWidth + leftSpacing;
169
requisition.Height = height + ySpacing * 2;
172
protected override bool OnFocusOutEvent (Gdk.EventFocus evnt)
175
return base.OnFocusOutEvent (evnt);
179
DropDownBoxListWindow window = null;
180
internal void DestroyWindow ()
182
if (window != null) {
189
protected override bool OnKeyPressEvent (Gdk.EventKey evnt)
191
if (evnt.Key == Gdk.Key.Escape) {
195
if (window != null && window.ProcessKey (evnt.Key, evnt.State))
197
return base.OnKeyPressEvent (evnt);
200
protected override bool OnLeaveNotifyEvent (Gdk.EventCrossing evnt)
203
return base.OnLeaveNotifyEvent (evnt);
206
protected override bool OnButtonPressEvent (Gdk.EventButton e)
211
if (e.Type == Gdk.EventType.ButtonPress) {
212
if (window != null) {
216
if (DataProvider != null) {
217
DataProvider.Reset ();
218
if (DataProvider.IconCount > 0) {
219
window = new DropDownBoxListWindow (DataProvider);
220
window.list.SelectItem += delegate {
221
SetItem (window.list.Selection);
223
PositionListWindow ();
224
window.SelectItem (CurrentItem);
229
return base.OnButtonPressEvent (e);
232
protected override void OnStateChanged (StateType previous_state)
234
base.OnStateChanged (previous_state);
237
protected override bool OnButtonReleaseEvent (Gdk.EventButton e)
239
return base.OnButtonReleaseEvent (e);
242
protected override bool OnMotionNotifyEvent (Gdk.EventMotion e)
245
return base.OnMotionNotifyEvent (e);
248
protected override bool OnExposeEvent (Gdk.EventExpose args)
250
Gdk.Drawable win = args.Window;
253
layout.GetPixelSize (out width, out height);
255
int arrowHeight = height / 2;
256
int arrowWidth = arrowHeight + 1;
257
int arrowXPos = this.Allocation.X + this.Allocation.Width - arrowWidth;
261
//HACK: don't ever draw insensitive, only active/prelight/normal, because insensitive generally looks really ugly
262
//this *might* cause some theme issues with the state of the text/arrows rendering on top of it
263
var state = window != null? StateType.Active
264
: State == StateType.Insensitive? StateType.Normal : State;
266
//HACK: paint the button background as if it were bigger, but it stays clipped to the real area,
267
// so we get the content but not the border. This might break with crazy themes.
268
//FIXME: we can't use the style's actual internal padding because GTK# hasn't wrapped GtkBorder AFAICT
269
// (default-border, inner-border, default-outside-border, etc - see http://git.gnome.org/browse/gtk+/tree/gtk/gtkbutton.c)
270
const int padding = 4;
271
Style.PaintBox (Style, args.Window, state, ShadowType.None, args.Area, this, "button",
272
Allocation.X - padding, Allocation.Y - padding, Allocation.Width + padding * 2, Allocation.Height + padding * 2);
274
int xPos = Allocation.Left;
275
if (Pixbuf != null) {
276
win.DrawPixbuf (this.Style.BaseGC (StateType.Normal), Pixbuf, 0, 0, xPos + pixbufSpacing, Allocation.Y + (Allocation.Height - Pixbuf.Height) / 2, Pixbuf.Width, Pixbuf.Height, Gdk.RgbDither.None, 0, 0);
277
xPos += Pixbuf.Width + pixbufSpacing * 2;
280
//constrain the text area so it doesn't get rendered under the arrows
281
var textArea = new Gdk.Rectangle (xPos, Allocation.Y + ySpacing, arrowXPos - xPos - 2, Allocation.Height - ySpacing);
282
Style.PaintLayout (Style, win, state, true, textArea, this, "", textArea.X, textArea.Y, layout);
284
state = Sensitive ? StateType.Normal : StateType.Insensitive;
285
Gtk.Style.PaintArrow (this.Style, win, state, ShadowType.None, args.Area, this, "", ArrowType.Up, true, arrowXPos, Allocation.Y + (Allocation.Height) / 2 - arrowHeight, arrowWidth, arrowHeight);
286
Gtk.Style.PaintArrow (this.Style, win, state, ShadowType.None, args.Area, this, "", ArrowType.Down, true, arrowXPos, Allocation.Y + (Allocation.Height) / 2, arrowWidth, arrowHeight);
288
win.DrawLine (this.Style.DarkGC (StateType.Normal), Allocation.X + Allocation.Width - 1, Allocation.Y, Allocation.X + Allocation.Width - 1, Allocation.Y + Allocation.Height);
292
public EventHandler ItemSet;