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