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