1 //
2 // MenuButton.cs
3 //
4 // Author:
5 //   Michael Hutchinson <mhutchinson@novell.com>
6 //
7 // Copyright (C) 2008 Novell, Inc (http://www.novell.com)
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
16 //
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 //
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 //
28 
29 using System;
30 using Gtk;
31 
32 namespace MonoDevelop.Components
33 {
34 	[System.ComponentModel.Category ("MonoDevelop.Components")]
35 	[System.ComponentModel.ToolboxItem (true)]
36 	class MenuButton : Button
37 	{
38 		MenuCreator creator;
39 		Label label;
40 		Image image;
41 		Arrow arrow;
42 		bool isOpen;
43 
MenuButton()44 		public MenuButton ()
45 			: base ()
46 		{
47 			HBox box = new HBox ();
48 			box.Spacing = 6;
49 			Add (box);
50 
51 			image = new Image ();
52 			image.NoShowAll = true;
53 			box.PackStart (image, false, false, 0);
54 			label = new Label ();
55 			label.NoShowAll = true;
56 			box.PackStart (label, false, false, 0);
57 			ArrowType = Gtk.ArrowType.Down;
58 			base.Label = null;
59 		}
60 
MenuButton(IntPtr raw)61 		protected MenuButton (IntPtr raw)
62 			: base (raw)
63 		{
64 
65 		}
66 
67 		public MenuCreator MenuCreator {
68 			get { return creator; }
69 			set { creator = value; }
70 		}
71 
OnClicked()72 		protected override void OnClicked ()
73 		{
74 			base.OnClicked ();
75 
76 			if (creator != null) {
77 				Menu menu = creator (this);
78 
79 				if (menu != null) {
80 					isOpen = true;
81 
82 					//make sure the button looks depressed
83 					ReliefStyle oldRelief = this.Relief;
84 					this.Relief = ReliefStyle.Normal;
85 
86 					//clean up after the menu's done
87 					menu.Hidden += delegate {
88 						this.Relief = oldRelief ;
89 						isOpen = false;
90 						this.State = StateType.Normal;
91 
92 						//FIXME: for some reason the menu's children don't get activated if we destroy
93 						//directly here, so use a timeout to delay it
94 						GLib.Timeout.Add (100, delegate {
95 							menu.Destroy ();
96 							return false;
97 						});
98 					};
99 					menu.Popup (null, null, PositionFunc, 0, Gtk.Global.CurrentEventTime);
100 				}
101 			}
102 
103 		}
104 
OnStateChanged(StateType previous_state)105 		protected override void OnStateChanged(StateType previous_state)
106 		{
107 			base.OnStateChanged (previous_state);
108 
109 			//while the menu's open, make sure the button looks depressed
110 			if (isOpen && this.State != StateType.Active)
111 				this.State = StateType.Active;
112 		}
113 
PositionFunc(Menu mn, out int x, out int y, out bool push_in)114 		void PositionFunc (Menu mn, out int x, out int y, out bool push_in)
115 		{
116 			this.GdkWindow.GetOrigin (out x, out y);
117 			Gdk.Rectangle rect = this.Allocation;
118 			x += rect.X;
119 			y += rect.Y + rect.Height;
120 
121 			//if the menu would be off the bottom of the screen, "drop" it upwards
122 			if (y + mn.Requisition.Height > this.Screen.Height) {
123 				y -= mn.Requisition.Height;
124 				y -= rect.Height;
125 			}
126 
127 			//let GTK reposition the button if it still doesn't fit on the screen
128 			push_in = true;
129 		}
130 
131 		public ArrowType? ArrowType {
132 			get { return arrow == null? (Gtk.ArrowType?) null : arrow.ArrowType; }
133 			set {
134 				if (value == null) {
135 					if (arrow != null) {
136 						((HBox)arrow.Parent).Remove (arrow);
137 						arrow.Destroy ();
138 						arrow = null;
139 					}
140 				} else {
141 					if (arrow == null ) {
142 						arrow = new Arrow (Gtk.ArrowType.Down, ShadowType.Out);
143 						arrow.Show ();
144 						((HBox)label.Parent).PackEnd (arrow, false, false, 0);
145 					}
146 					arrow.ArrowType = value?? Gtk.ArrowType.Down;
147 				}
148 			}
149 		}
150 
OnDestroyed()151 		protected override void OnDestroyed ()
152 		{
153 			creator = null;
154 			base.OnDestroyed ();
155 		}
156 
157 		public new string Label {
158 			get { return label.Text; }
159 			set {
160 				label.Text = value;
161 				label.Visible = !string.IsNullOrEmpty (value);
162 			}
163 		}
164 
165 		public new bool UseUnderline {
166 			get { return label.UseUnderline; }
167 			set { label.UseUnderline = value; }
168 		}
169 
170 		public string StockImage {
171 			set {
172 				image.Pixbuf = RenderIcon (value, IconSize.Button, null);
173 				image.Show ();
174 			}
175 		}
176 
177 		public bool UseMarkup
178 		{
179 			get { return label.UseMarkup; }
180 			set { label.UseMarkup = value; }
181 		}
182 
183 		public string Markup {
184 			set { label.Markup = value; }
185 		}
186 	}
187 
MenuCreator(MenuButton button)188 	delegate Menu MenuCreator (MenuButton button);
189 }
190