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