xref: /haiku/src/kits/interface/OptionPopUp.cpp (revision 3cb015b1ee509d69c643506e8ff573808c86dcfc)
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 	newItem->SetTarget(this);
170 
171 	// We didnt' have any items before, so select the newly added one
172 	if (numItems == 0)
173 		SetValue(value);
174 
175 	return B_OK;
176 }
177 
178 
179 /*! \brief Called to take special actions when the child views are attached.
180 	It's used to set correctly the divider for the BMenuField.
181 */
182 void
183 BOptionPopUp::AllAttached()
184 {
185 	BMenu *menu = _mField->Menu();
186 	if (menu != NULL) {
187 		float labelWidth = _mField->StringWidth(_mField->Label());
188 		_mField->SetDivider(labelWidth + kLabelSpace);
189 		menu->SetTargetForItems(this);
190 	}
191 }
192 
193 
194 void
195 BOptionPopUp::MessageReceived(BMessage *message)
196 {
197 	BOptionControl::MessageReceived(message);
198 }
199 
200 
201 /*! \brief Set the label of the control.
202 	\param text The new label of the control.
203 */
204 void
205 BOptionPopUp::SetLabel(const char *text)
206 {
207 	BControl::SetLabel(text);
208 	_mField->SetLabel(text);
209 	// We are not sure the menu can keep the whole
210 	// string as label, so we ask it what label it's got
211 	float newWidth = _mField->StringWidth(_mField->Label());
212 	_mField->SetDivider(newWidth + kLabelSpace);
213 }
214 
215 
216 /*! \brief Set the control's value.
217 	\param value The new value of the control.
218 	Selects the option which has the given value.
219 */
220 void
221 BOptionPopUp::SetValue(int32 value)
222 {
223 	BControl::SetValue(value);
224 	BMenu *menu = _mField->Menu();
225 	if (menu == NULL)
226 		return;
227 
228 	int32 numItems = menu->CountItems();
229 	for (int32 i = 0; i < numItems; i++) {
230 		BMenuItem *item = menu->ItemAt(i);
231 		if (item && item->Message()) {
232 			int32 itemValue;
233 			item->Message()->FindInt32("be:value", &itemValue);
234 			if (itemValue == value) {
235 				item->SetMarked(true);
236 
237 #if BEHAVE_LIKE_R5
238 				item->SetMarked(false);
239 #endif
240 
241 				break;
242 			}
243 		}
244 	}
245 }
246 
247 
248 /*! \brief Enables or disables the control.
249 	\param state The new control's state.
250 */
251 void
252 BOptionPopUp::SetEnabled(bool state)
253 {
254 	BOptionControl::SetEnabled(state);
255 }
256 
257 
258 /*! \brief Gets the preferred size for the control.
259 	\param width A pointer to a float which will held the control's
260 		preferred width.
261 	\param height A pointer to a float which will held the control's
262 		preferred height.
263 */
264 void
265 BOptionPopUp::GetPreferredSize(float* _width, float* _height)
266 {
267 	// Calculate control's height, looking at the BMenuField font's height
268 	if (_height != NULL) {
269 		font_height fontHeight;
270 		_mField->GetFontHeight(&fontHeight);
271 
272 		*_height = fontHeight.ascent + fontHeight.descent
273 			+ fontHeight.leading + kHeightModifier;
274 	}
275 
276 	if (_width != NULL) {
277 		float maxWidth = 0;
278 		BMenu *menu = _mField->Menu();
279 		if (menu == NULL)
280 			return;
281 
282 		// Iterate over all the entries in the control,
283 		// and take the maximum width.
284 		// TODO: Should we call BMenuField::GetPreferredSize() instead ?
285 		int32 numItems = menu->CountItems();
286 		for (int32 i = 0; i < numItems; i++) {
287 			BMenuItem *item = menu->ItemAt(i);
288 			if (item != NULL) {
289 				float stringWidth = menu->StringWidth(item->Label());
290 				maxWidth = max_c(maxWidth, stringWidth);
291 			}
292 		}
293 
294 		maxWidth += _mField->StringWidth(BControl::Label()) + kLabelSpace + kWidthModifier;
295 		*_width = maxWidth;
296 	}
297 }
298 
299 
300 /*! \brief Resizes the control to its preferred size.
301 */
302 void
303 BOptionPopUp::ResizeToPreferred()
304 {
305 	// TODO: Some more work is needed either here or in GetPreferredSize(),
306 	// since the control doesn't always resize as it should.
307 	// It looks like if the font height is too big, the control gets "cut".
308 	float width, height;
309 	GetPreferredSize(&width, &height);
310 	ResizeTo(width, height);
311 
312 	float newWidth = _mField->StringWidth(BControl::Label());
313 	_mField->SetDivider(newWidth + kLabelSpace);
314 }
315 
316 
317 /*! \brief Gets the currently selected option.
318 	\param outName A pointer to a string which will held the option's name.
319 	\param outValue A pointer to an integer which will held the option's value.
320 	\return The index of the selected option.
321 */
322 int32
323 BOptionPopUp::SelectedOption(const char **outName, int32 *outValue) const
324 {
325 	BMenu *menu = _mField->Menu();
326 	if (menu != NULL) {
327 		BMenuItem *marked = menu->FindMarked();
328 		if (marked != NULL) {
329 			if (outName != NULL)
330 				*outName = marked->Label();
331 			if (outValue != NULL)
332 				marked->Message()->FindInt32("be:value", outValue);
333 
334 			return menu->IndexOf(marked);
335 		}
336 	}
337 
338 	return B_ERROR;
339 }
340 
341 
342 // Private Unimplemented
343 BOptionPopUp::BOptionPopUp()
344 	:
345 	BOptionControl(BRect(), "", "", NULL)
346 {
347 }
348 
349 
350 BOptionPopUp::BOptionPopUp(const BOptionPopUp &clone)
351 	:
352 	BOptionControl(clone.Frame(), "", "", clone.Message())
353 {
354 }
355 
356 
357 BOptionPopUp &
358 BOptionPopUp::operator=(const BOptionPopUp & clone)
359 {
360 	return *this;
361 }
362 
363 
364 // FBC Stuff
365 status_t BOptionPopUp::_Reserved_OptionControl_0(void *, ...) { return B_ERROR; }
366 status_t BOptionPopUp::_Reserved_OptionControl_1(void *, ...) { return B_ERROR; }
367 status_t BOptionPopUp::_Reserved_OptionControl_2(void *, ...) { return B_ERROR; }
368 status_t BOptionPopUp::_Reserved_OptionControl_3(void *, ...) { return B_ERROR; }
369 status_t BOptionPopUp::_Reserved_OptionPopUp_0(void *, ...) { return B_ERROR; }
370 status_t BOptionPopUp::_Reserved_OptionPopUp_1(void *, ...) { return B_ERROR; }
371 status_t BOptionPopUp::_Reserved_OptionPopUp_2(void *, ...) { return B_ERROR; }
372 status_t BOptionPopUp::_Reserved_OptionPopUp_3(void *, ...) { return B_ERROR; }
373 status_t BOptionPopUp::_Reserved_OptionPopUp_4(void *, ...) { return B_ERROR; }
374 status_t BOptionPopUp::_Reserved_OptionPopUp_5(void *, ...) { return B_ERROR; }
375 status_t BOptionPopUp::_Reserved_OptionPopUp_6(void *, ...) { return B_ERROR; }
376 status_t BOptionPopUp::_Reserved_OptionPopUp_7(void *, ...) { return B_ERROR; }
377