1 /* 2 * Copyright 2001-2013 Haiku, Inc. All rights reserved. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Stephan Aßmus, superstippi@gmx.de 7 * Marc Flerackers, mflerackers@androme.be 8 * John Scipione, jcipione@gmail.com 9 */ 10 11 12 #include <BMCPrivate.h> 13 14 #include <algorithm> 15 16 #include <ControlLook.h> 17 #include <LayoutUtils.h> 18 #include <MenuField.h> 19 #include <MenuItem.h> 20 #include <Message.h> 21 #include <MessageRunner.h> 22 #include <Window.h> 23 24 25 static const float kPopUpIndicatorWidth = 13.0f; 26 27 28 #if __GNUC__ == 2 29 30 31 // This is kept only for binary compatibility with BeOS R5. This class was 32 // used in their BMenuField implementation and we may come across some archived 33 // BMenuField that needs it. 34 class _BMCItem_: public BMenuItem { 35 public: 36 _BMCItem_(BMessage* data); 37 static BArchivable* Instantiate(BMessage *data); 38 }; 39 40 41 _BMCItem_::_BMCItem_(BMessage* data) 42 : BMenuItem(data) 43 { 44 45 } 46 47 48 /*static*/ BArchivable* 49 _BMCItem_::Instantiate(BMessage *data) { 50 if (validate_instantiation(data, "_BMCItem_")) 51 return new _BMCItem_(data); 52 53 return NULL; 54 } 55 56 57 #endif 58 59 60 // #pragma mark - _BMCFilter_ 61 62 63 _BMCFilter_::_BMCFilter_(BMenuField* menuField, uint32 what) 64 : 65 BMessageFilter(B_ANY_DELIVERY, B_ANY_SOURCE, what), 66 fMenuField(menuField) 67 { 68 } 69 70 71 _BMCFilter_::~_BMCFilter_() 72 { 73 } 74 75 76 filter_result 77 _BMCFilter_::Filter(BMessage* message, BHandler** handler) 78 { 79 if (message->what == B_MOUSE_DOWN) { 80 if (BView* view = dynamic_cast<BView*>(*handler)) { 81 BPoint point; 82 message->FindPoint("be:view_where", &point); 83 view->ConvertToParent(&point); 84 message->ReplacePoint("be:view_where", point); 85 *handler = fMenuField; 86 } 87 } 88 89 return B_DISPATCH_MESSAGE; 90 } 91 92 93 // #pragma mark - _BMCMenuBar_ 94 95 96 _BMCMenuBar_::_BMCMenuBar_(BRect frame, bool fixedSize, BMenuField* menuField) 97 : 98 BMenuBar(frame, "_mc_mb_", B_FOLLOW_LEFT | B_FOLLOW_TOP, B_ITEMS_IN_ROW, 99 !fixedSize), 100 fMenuField(menuField), 101 fFixedSize(fixedSize), 102 fShowPopUpMarker(true) 103 { 104 _Init(); 105 } 106 107 108 _BMCMenuBar_::_BMCMenuBar_(BMenuField* menuField) 109 : 110 BMenuBar("_mc_mb_", B_ITEMS_IN_ROW), 111 fMenuField(menuField), 112 fFixedSize(true), 113 fShowPopUpMarker(true) 114 { 115 _Init(); 116 } 117 118 119 _BMCMenuBar_::_BMCMenuBar_(BMessage* data) 120 : 121 BMenuBar(data), 122 fMenuField(NULL), 123 fFixedSize(true), 124 fShowPopUpMarker(true) 125 { 126 SetFlags(Flags() | B_FRAME_EVENTS); 127 128 bool resizeToFit; 129 if (data->FindBool("_rsize_to_fit", &resizeToFit) == B_OK) 130 fFixedSize = !resizeToFit; 131 } 132 133 134 _BMCMenuBar_::~_BMCMenuBar_() 135 { 136 } 137 138 139 // #pragma mark - _BMCMenuBar_ public methods 140 141 142 BArchivable* 143 _BMCMenuBar_::Instantiate(BMessage* data) 144 { 145 if (validate_instantiation(data, "_BMCMenuBar_")) 146 return new _BMCMenuBar_(data); 147 148 return NULL; 149 } 150 151 152 void 153 _BMCMenuBar_::AttachedToWindow() 154 { 155 fMenuField = static_cast<BMenuField*>(Parent()); 156 157 // Don't cause the KeyMenuBar to change by being attached 158 BMenuBar* menuBar = Window()->KeyMenuBar(); 159 BMenuBar::AttachedToWindow(); 160 Window()->SetKeyMenuBar(menuBar); 161 162 if (fFixedSize && (Flags() & B_SUPPORTS_LAYOUT) == 0) 163 SetResizingMode(B_FOLLOW_LEFT_RIGHT | B_FOLLOW_TOP); 164 165 if (Parent() != NULL) 166 SetLowColor(Parent()->LowColor()); 167 else 168 SetLowColor(ui_color(B_MENU_BACKGROUND_COLOR)); 169 170 fPreviousWidth = Bounds().Width(); 171 } 172 173 174 void 175 _BMCMenuBar_::Draw(BRect updateRect) 176 { 177 if (fFixedSize) { 178 // Set the width of the menu bar because the menu bar bounds may have 179 // been expanded by the selected menu item. 180 ResizeTo(fMenuField->_MenuBarWidth(), Bounds().Height()); 181 } else { 182 // For compatability with BeOS R5: 183 // - Set to the minimum of the menu bar width set by the menu frame 184 // and the selected menu item width. 185 // - Set the height to the preferred height ignoring the height of the 186 // menu field. 187 float height; 188 BMenuBar::GetPreferredSize(NULL, &height); 189 ResizeTo(std::min(Bounds().Width(), fMenuField->_MenuBarWidth()), 190 height); 191 } 192 193 BRect rect(Bounds()); 194 rgb_color base = ui_color(B_MENU_BACKGROUND_COLOR); 195 uint32 flags = 0; 196 if (!IsEnabled()) 197 flags |= BControlLook::B_DISABLED; 198 if (IsFocus()) 199 flags |= BControlLook::B_FOCUSED; 200 201 be_control_look->DrawMenuFieldBackground(this, rect, 202 updateRect, base, fShowPopUpMarker, flags); 203 204 _DrawItems(updateRect); 205 } 206 207 208 void 209 _BMCMenuBar_::FrameResized(float width, float height) 210 { 211 // we need to take care of cleaning up the parent menu field 212 float diff = width - fPreviousWidth; 213 fPreviousWidth = width; 214 215 if (Window() != NULL && diff != 0) { 216 BRect dirty(fMenuField->Bounds()); 217 if (diff > 0) { 218 // clean up the dirty right border of 219 // the menu field when enlarging 220 dirty.right = Frame().right + kVMargin; 221 dirty.left = dirty.right - diff - kVMargin * 2; 222 fMenuField->Invalidate(dirty); 223 } else if (diff < 0) { 224 // clean up the dirty right line of 225 // the menu field when shrinking 226 dirty.left = Frame().right - kVMargin; 227 fMenuField->Invalidate(dirty); 228 } 229 } 230 231 BMenuBar::FrameResized(width, height); 232 } 233 234 235 void 236 _BMCMenuBar_::MakeFocus(bool focused) 237 { 238 if (IsFocus() == focused) 239 return; 240 241 BMenuBar::MakeFocus(focused); 242 } 243 244 245 void 246 _BMCMenuBar_::MessageReceived(BMessage* message) 247 { 248 switch (message->what) { 249 case 'TICK': 250 { 251 BMenuItem* item = ItemAt(0); 252 253 if (item && item->Submenu() && item->Submenu()->Window()) { 254 BMessage message(B_KEY_DOWN); 255 256 message.AddInt8("byte", B_ESCAPE); 257 message.AddInt8("key", B_ESCAPE); 258 message.AddInt32("modifiers", 0); 259 message.AddInt8("raw_char", B_ESCAPE); 260 261 Window()->PostMessage(&message, this, NULL); 262 } 263 } 264 // fall through 265 default: 266 BMenuBar::MessageReceived(message); 267 break; 268 } 269 } 270 271 272 void 273 _BMCMenuBar_::SetMaxContentWidth(float width) 274 { 275 float left; 276 float right; 277 GetItemMargins(&left, NULL, &right, NULL); 278 279 BMenuBar::SetMaxContentWidth(width - (left + right)); 280 } 281 282 283 void 284 _BMCMenuBar_::SetEnabled(bool enabled) 285 { 286 fMenuField->SetEnabled(enabled); 287 288 BMenuBar::SetEnabled(enabled); 289 } 290 291 292 BSize 293 _BMCMenuBar_::MinSize() 294 { 295 BSize size; 296 BMenuBar::GetPreferredSize(&size.width, &size.height); 297 if (fShowPopUpMarker) { 298 // account for popup indicator + a few pixels margin 299 size.width += kPopUpIndicatorWidth; 300 } 301 302 return BLayoutUtils::ComposeSize(ExplicitMinSize(), size); 303 } 304 305 306 BSize 307 _BMCMenuBar_::MaxSize() 308 { 309 // The maximum width of a normal BMenuBar is unlimited, but we want it 310 // limited. 311 BSize size; 312 BMenuBar::GetPreferredSize(&size.width, &size.height); 313 314 return BLayoutUtils::ComposeSize(ExplicitMaxSize(), size); 315 } 316 317 318 // #pragma mark - _BMCMenuBar_ private methods 319 320 321 void 322 _BMCMenuBar_::_Init() 323 { 324 SetFlags(Flags() | B_FRAME_EVENTS | B_FULL_UPDATE_ON_RESIZE); 325 SetBorder(B_BORDER_CONTENTS); 326 327 float left, top, right, bottom; 328 GetItemMargins(&left, &top, &right, &bottom); 329 330 #if 0 331 // TODO: Better fix would be to make BMenuItem draw text properly 332 // centered 333 font_height fontHeight; 334 GetFontHeight(&fontHeight); 335 top = ceilf((Bounds().Height() - ceilf(fontHeight.ascent) 336 - ceilf(fontHeight.descent)) / 2) + 1; 337 bottom = top - 1; 338 #else 339 // TODO: Fix content location properly. This is just a quick fix to 340 // make the BMenuField label and the super-item of the BMenuBar 341 // align vertically. 342 top++; 343 bottom--; 344 #endif 345 346 if (be_control_look != NULL) 347 left = right = be_control_look->DefaultLabelSpacing(); 348 349 SetItemMargins(left, top, 350 right + fShowPopUpMarker ? kPopUpIndicatorWidth : 0, bottom); 351 } 352