xref: /haiku/src/kits/interface/OptionPopUp.cpp (revision d3d8b26997fac34a84981e6d2b649521de2cc45a)
1 /*
2  * Copyright 2003-2005, Haiku, Inc.
3  * Distributed under the terms of the MIT license.
4  *
5  * Authors:
6  *		Stefano Ceccherini (burton666@libero.it)
7  */
8 
9 
10 #include <MenuField.h>
11 #include <MenuItem.h>
12 #include <OptionPopUp.h>
13 #include <PopUpMenu.h>
14 
15 #include <stdio.h>
16 
17 
18 // If enabled, behaves like in BeOS R5, in that when you call
19 // SelectOptionFor() or SetValue(), the selected item isn't marked, and
20 // so SelectedOption() will return -1. This is broken, IMHO.
21 #define BEHAVE_LIKE_R5 0
22 
23 const float kLabelSpace = 8.0;
24 const float kWidthModifier = 25.0;
25 const float kHeightModifier = 10.0;
26 
27 
28 /*! \brief Creates and initializes a BOptionPopUp.
29 	\param frame The frame of the control.
30 	\param name The name of the control.
31 	\param label The label which will be displayed by the control.
32 	\param message The message which the control will send when operated.
33 	\param resize Resizing flags. They will be passed to the base class.
34 	\param flags View flags. They will be passed to the base class.
35 */
36 BOptionPopUp::BOptionPopUp(BRect frame, const char *name, const char *label,
37 		BMessage *message, uint32 resize, uint32 flags)
38 	: BOptionControl(frame, name, label, message, resize, flags)
39 {
40 	BPopUpMenu *popUp = new BPopUpMenu(label, true, true);
41 	_mField = new BMenuField(Bounds(), "_menu", label, popUp);
42 	AddChild(_mField);
43 }
44 
45 
46 /*! \brief Creates and initializes a BOptionPopUp.
47 	\param frame The frame of the control.
48 	\param name The name of the control.
49 	\param label The label which will be displayed by the control.
50 	\param message The message which the control will send when operated.
51 	\param fixed It's passed to the BMenuField constructor. If it's true,
52 		the BMenuField size will never change.
53 	\param resize Resizing flags. They will be passed to the base class.
54 	\param flags View flags. They will be passed to the base class.
55 */
56 BOptionPopUp::BOptionPopUp(BRect frame, const char *name, const char *label,
57 		BMessage *message, bool fixed, uint32 resize, uint32 flags)
58 	: BOptionControl(frame, name, label, message, resize, flags)
59 {
60 	BPopUpMenu *popUp = new BPopUpMenu(label, true, true);
61 	_mField = new BMenuField(Bounds(), "_menu", label, popUp, fixed);
62 	AddChild(_mField);
63 }
64 
65 
66 BOptionPopUp::~BOptionPopUp()
67 {
68 }
69 
70 
71 /*! \brief Returns a pointer to the BMenuField used internally.
72 	\return A Pointer to the BMenuField which the class uses internally.
73 */
74 BMenuField *
75 BOptionPopUp::MenuField()
76 {
77 	return _mField;
78 }
79 
80 
81 /*! \brief Gets the option at the given index.
82 	\param index The option's index.
83 	\param outName A pointer to a string which will held the option's name,
84 		as soon as the function returns.
85 	\param outValue A pointer to an integer which will held the option's value,
86 		as soon as the funciton returns.
87 	\return \c true if The wanted option was found,
88 			\c false otherwise.
89 */
90 bool
91 BOptionPopUp::GetOptionAt(int32 index, const char **outName, int32 *outValue)
92 {
93 	bool result = false;
94 	BMenu *menu = _mField->Menu();
95 
96 	if (menu != NULL) {
97 		BMenuItem *item = menu->ItemAt(index);
98 		if (item != NULL) {
99 			if (outName != NULL)
100 				*outName = item->Label();
101 			if (outValue != NULL)
102 				item->Message()->FindInt32("be:value", outValue);
103 
104 			result = true;
105 		}
106 	}
107 
108 	return result;
109 }
110 
111 
112 /*! \brief Removes the option at the given index.
113 	\param index The index of the option to remove.
114 */
115 void
116 BOptionPopUp::RemoveOptionAt(int32 index)
117 {
118 	BMenu *menu = _mField->Menu();
119 	if (menu != NULL) {
120 		BMenuItem *item = menu->ItemAt(index);
121 		if (item != NULL) {
122 			menu->RemoveItem(item);
123 			delete item;
124 		}
125 	}
126 }
127 
128 
129 /*! \brief Returns the amount of "Options" (entries) contained in the control.
130 */
131 int32
132 BOptionPopUp::CountOptions() const
133 {
134 	BMenu *menu = _mField->Menu();
135 	return (menu != NULL) ? menu->CountItems() : 0;
136 }
137 
138 
139 /*! \brief Adds an option to the control, at the given position.
140 	\param name The name of the option to add.
141 	\param value The value of the option.
142 	\param index The index which the new option will have in the control.
143 	\return \c B_OK if the option was added succesfully,
144 		\c B_BAD_VALUE if the given index was invalid.
145 		\c B_ERROR if something else happened.
146 */
147 status_t
148 BOptionPopUp::AddOptionAt(const char *name, int32 value, int32 index)
149 {
150 	BMenu *menu = _mField->Menu();
151 	if (menu == NULL)
152 		return B_ERROR;
153 
154 	int32 numItems = menu->CountItems();
155 	if (index < 0 || index > numItems)
156 		return B_BAD_VALUE;
157 
158 	BMessage *message = MakeValueMessage(value);
159 	if (message == NULL)
160 		return B_NO_MEMORY;
161 
162 	BMenuItem *newItem = new BMenuItem(name, message);
163 	if (newItem == NULL) {
164 		delete message;
165 		return B_NO_MEMORY;
166 	}
167 
168 	menu->AddItem(newItem, index);
169 
170 	// We didnt' have any items before, so select the newly added one
171 	if (numItems == 0)
172 		SetValue(value);
173 
174 	return B_OK;
175 }
176 
177 
178 /*! \brief Called to take special actions when the child views are attached.
179 	It's used to set correctly the divider for the BMenuField.
180 */
181 void
182 BOptionPopUp::AllAttached()
183 {
184 	BMenu *menu = _mField->Menu();
185 	if (menu != NULL) {
186 		float labelWidth = _mField->StringWidth(_mField->Label());
187 		_mField->SetDivider(labelWidth + kLabelSpace);
188 	}
189 }
190 
191 
192 void
193 BOptionPopUp::MessageReceived(BMessage *message)
194 {
195 	BOptionControl::MessageReceived(message);
196 }
197 
198 
199 /*! \brief Set the label of the control.
200 	\param text The new label of the control.
201 */
202 void
203 BOptionPopUp::SetLabel(const char *text)
204 {
205 	BControl::SetLabel(text);
206 	_mField->SetLabel(text);
207 	// We are not sure the menu can keep the whole
208 	// string as label, so we ask it what label it's got
209 	float newWidth = _mField->StringWidth(_mField->Label());
210 	_mField->SetDivider(newWidth + kLabelSpace);
211 }
212 
213 
214 /*! \brief Set the control's value.
215 	\param value The new value of the control.
216 	Selects the option which has the given value.
217 */
218 void
219 BOptionPopUp::SetValue(int32 value)
220 {
221 	BControl::SetValue(value);
222 	BMenu *menu = _mField->Menu();
223 	if (menu == NULL)
224 		return;
225 
226 	int32 numItems = menu->CountItems();
227 	for (int32 i = 0; i < numItems; i++) {
228 		BMenuItem *item = menu->ItemAt(i);
229 		if (item && item->Message()) {
230 			int32 itemValue;
231 			item->Message()->FindInt32("be:value", &itemValue);
232 			if (itemValue == value) {
233 				item->SetMarked(true);
234 
235 #if BEHAVE_LIKE_R5
236 				item->SetMarked(false);
237 #endif
238 
239 				break;
240 			}
241 		}
242 	}
243 }
244 
245 
246 /*! \brief Enables or disables the control.
247 	\param state The new control's state.
248 */
249 void
250 BOptionPopUp::SetEnabled(bool state)
251 {
252 	BOptionControl::SetEnabled(state);
253 }
254 
255 
256 /*! \brief Gets the preferred size for the control.
257 	\param width A pointer to a float which will held the control's
258 		preferred width.
259 	\param height A pointer to a float which will held the control's
260 		preferred height.
261 */
262 void
263 BOptionPopUp::GetPreferredSize(float* _width, float* _height)
264 {
265 	// Calculate control's height, looking at the BMenuField font's height
266 	if (_height != NULL) {
267 		font_height fontHeight;
268 		_mField->GetFontHeight(&fontHeight);
269 
270 		*_height = fontHeight.ascent + fontHeight.descent
271 			+ fontHeight.leading + kHeightModifier;
272 	}
273 
274 	if (_width != NULL) {
275 		float maxWidth = 0;
276 		BMenu *menu = _mField->Menu();
277 		if (menu == NULL)
278 			return;
279 
280 		// Iterate over all the entries in the control,
281 		// and take the maximum width.
282 		// TODO: Should we call BMenuField::GetPreferredSize() instead ?
283 		int32 numItems = menu->CountItems();
284 		for (int32 i = 0; i < numItems; i++) {
285 			BMenuItem *item = menu->ItemAt(i);
286 			if (item != NULL) {
287 				float stringWidth = menu->StringWidth(item->Label());
288 				maxWidth = max_c(maxWidth, stringWidth);
289 			}
290 		}
291 
292 		maxWidth += _mField->StringWidth(BControl::Label()) + kLabelSpace + kWidthModifier;
293 		*_width = maxWidth;
294 	}
295 }
296 
297 
298 /*! \brief Resizes the control to its preferred size.
299 */
300 void
301 BOptionPopUp::ResizeToPreferred()
302 {
303 	// TODO: Some more work is needed either here or in GetPreferredSize(),
304 	// since the control doesn't always resize as it should.
305 	// It looks like if the font height is too big, the control gets "cut".
306 	float width, height;
307 	GetPreferredSize(&width, &height);
308 	ResizeTo(width, height);
309 
310 	float newWidth = _mField->StringWidth(BControl::Label());
311 	_mField->SetDivider(newWidth + kLabelSpace);
312 }
313 
314 
315 /*! \brief Gets the currently selected option.
316 	\param outName A pointer to a string which will held the option's name.
317 	\param outValue A pointer to an integer which will held the option's value.
318 	\return The index of the selected option.
319 */
320 int32
321 BOptionPopUp::SelectedOption(const char **outName, int32 *outValue) const
322 {
323 	BMenu *menu = _mField->Menu();
324 	if (menu != NULL) {
325 		BMenuItem *marked = menu->FindMarked();
326 		if (marked != NULL) {
327 			if (outName != NULL)
328 				*outName = marked->Label();
329 			if (outValue != NULL)
330 				marked->Message()->FindInt32("be:value", outValue);
331 
332 			return menu->IndexOf(marked);
333 		}
334 	}
335 
336 	return B_ERROR;
337 }
338 
339 
340 // Private Unimplemented
341 BOptionPopUp::BOptionPopUp()
342 	:
343 	BOptionControl(BRect(), "", "", NULL)
344 {
345 }
346 
347 
348 BOptionPopUp::BOptionPopUp(const BOptionPopUp &clone)
349 	:
350 	BOptionControl(clone.Frame(), "", "", clone.Message())
351 {
352 }
353 
354 
355 BOptionPopUp &
356 BOptionPopUp::operator=(const BOptionPopUp & clone)
357 {
358 	return *this;
359 }
360 
361 
362 // FBC Stuff
363 status_t BOptionPopUp::_Reserved_OptionControl_0(void *, ...) { return B_ERROR; }
364 status_t BOptionPopUp::_Reserved_OptionControl_1(void *, ...) { return B_ERROR; }
365 status_t BOptionPopUp::_Reserved_OptionControl_2(void *, ...) { return B_ERROR; }
366 status_t BOptionPopUp::_Reserved_OptionControl_3(void *, ...) { return B_ERROR; }
367 status_t BOptionPopUp::_Reserved_OptionPopUp_0(void *, ...) { return B_ERROR; }
368 status_t BOptionPopUp::_Reserved_OptionPopUp_1(void *, ...) { return B_ERROR; }
369 status_t BOptionPopUp::_Reserved_OptionPopUp_2(void *, ...) { return B_ERROR; }
370 status_t BOptionPopUp::_Reserved_OptionPopUp_3(void *, ...) { return B_ERROR; }
371 status_t BOptionPopUp::_Reserved_OptionPopUp_4(void *, ...) { return B_ERROR; }
372 status_t BOptionPopUp::_Reserved_OptionPopUp_5(void *, ...) { return B_ERROR; }
373 status_t BOptionPopUp::_Reserved_OptionPopUp_6(void *, ...) { return B_ERROR; }
374 status_t BOptionPopUp::_Reserved_OptionPopUp_7(void *, ...) { return B_ERROR; }
375