1 /* 2 * Copyright 2001-2015 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 : 43 BMenuItem(data) 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 color_which which = Parent()->LowUIColor(); 167 if (which == B_NO_COLOR) 168 SetLowColor(Parent()->LowColor()); 169 else 170 SetLowUIColor(which); 171 172 } else 173 SetLowUIColor(B_MENU_BACKGROUND_COLOR); 174 175 fPreviousWidth = Bounds().Width(); 176 } 177 178 179 void 180 _BMCMenuBar_::Draw(BRect updateRect) 181 { 182 if ((Flags() & B_SUPPORTS_LAYOUT) == 0) { 183 if (fFixedSize) { 184 // Set the width of the menu bar because the menu bar bounds may have 185 // been expanded by the selected menu item. 186 ResizeTo(fMenuField->_MenuBarWidth(), Bounds().Height()); 187 } else { 188 // For compatability with BeOS R5: 189 // - Set to the minimum of the menu bar width set by the menu frame 190 // and the selected menu item width. 191 // - Set the height to the preferred height ignoring the height of the 192 // menu field. 193 float height; 194 BMenuBar::GetPreferredSize(NULL, &height); 195 ResizeTo(std::min(Bounds().Width(), fMenuField->_MenuBarWidth()), 196 height); 197 } 198 } 199 200 BRect rect(Bounds()); 201 rgb_color base = ui_color(B_MENU_BACKGROUND_COLOR); 202 uint32 flags = 0; 203 if (!IsEnabled()) 204 flags |= BControlLook::B_DISABLED; 205 if (IsFocus()) 206 flags |= BControlLook::B_FOCUSED; 207 208 be_control_look->DrawMenuFieldBackground(this, rect, 209 updateRect, base, fShowPopUpMarker, flags); 210 211 DrawItems(updateRect); 212 } 213 214 215 void 216 _BMCMenuBar_::FrameResized(float width, float height) 217 { 218 // we need to take care of cleaning up the parent menu field 219 float diff = width - fPreviousWidth; 220 fPreviousWidth = width; 221 222 if (Window() != NULL && diff != 0) { 223 BRect dirty(fMenuField->Bounds()); 224 if (diff > 0) { 225 // clean up the dirty right border of 226 // the menu field when enlarging 227 dirty.right = Frame().right + kVMargin; 228 dirty.left = dirty.right - diff - kVMargin * 2; 229 fMenuField->Invalidate(dirty); 230 } else if (diff < 0) { 231 // clean up the dirty right line of 232 // the menu field when shrinking 233 dirty.left = Frame().right - kVMargin; 234 fMenuField->Invalidate(dirty); 235 } 236 } 237 238 BMenuBar::FrameResized(width, height); 239 } 240 241 242 void 243 _BMCMenuBar_::MakeFocus(bool focused) 244 { 245 if (IsFocus() == focused) 246 return; 247 248 BMenuBar::MakeFocus(focused); 249 } 250 251 252 void 253 _BMCMenuBar_::MessageReceived(BMessage* message) 254 { 255 switch (message->what) { 256 case 'TICK': 257 { 258 BMenuItem* item = ItemAt(0); 259 260 if (item != NULL && item->Submenu() != NULL 261 && item->Submenu()->Window() != NULL) { 262 BMessage message(B_KEY_DOWN); 263 264 message.AddInt8("byte", B_ESCAPE); 265 message.AddInt8("key", B_ESCAPE); 266 message.AddInt32("modifiers", 0); 267 message.AddInt8("raw_char", B_ESCAPE); 268 269 Window()->PostMessage(&message, this, NULL); 270 } 271 } 272 // fall through 273 default: 274 BMenuBar::MessageReceived(message); 275 break; 276 } 277 } 278 279 280 void 281 _BMCMenuBar_::SetMaxContentWidth(float width) 282 { 283 float left; 284 float right; 285 GetItemMargins(&left, NULL, &right, NULL); 286 287 BMenuBar::SetMaxContentWidth(width - (left + right)); 288 } 289 290 291 void 292 _BMCMenuBar_::SetEnabled(bool enabled) 293 { 294 fMenuField->SetEnabled(enabled); 295 296 BMenuBar::SetEnabled(enabled); 297 } 298 299 300 BSize 301 _BMCMenuBar_::MinSize() 302 { 303 BSize size; 304 BMenuBar::GetPreferredSize(&size.width, &size.height); 305 if (fShowPopUpMarker) { 306 // account for popup indicator + a few pixels margin 307 size.width += kPopUpIndicatorWidth; 308 } 309 310 return BLayoutUtils::ComposeSize(ExplicitMinSize(), size); 311 } 312 313 314 BSize 315 _BMCMenuBar_::MaxSize() 316 { 317 // The maximum width of a normal BMenuBar is unlimited, but we want it 318 // limited. 319 BSize size; 320 BMenuBar::GetPreferredSize(&size.width, &size.height); 321 322 return BLayoutUtils::ComposeSize(ExplicitMaxSize(), size); 323 } 324 325 326 // #pragma mark - _BMCMenuBar_ private methods 327 328 329 void 330 _BMCMenuBar_::_Init() 331 { 332 SetFlags(Flags() | B_FRAME_EVENTS | B_FULL_UPDATE_ON_RESIZE); 333 SetBorder(B_BORDER_CONTENTS); 334 335 float left, top, right, bottom; 336 GetItemMargins(&left, &top, &right, &bottom); 337 338 #if 0 339 // TODO: Better fix would be to make BMenuItem draw text properly 340 // centered 341 font_height fontHeight; 342 GetFontHeight(&fontHeight); 343 top = ceilf((Bounds().Height() - ceilf(fontHeight.ascent) 344 - ceilf(fontHeight.descent)) / 2) + 1; 345 bottom = top - 1; 346 #else 347 // TODO: Fix content location properly. This is just a quick fix to 348 // make the BMenuField label and the super-item of the BMenuBar 349 // align vertically. 350 top++; 351 bottom--; 352 #endif 353 354 left = right = be_control_look->DefaultLabelSpacing(); 355 356 SetItemMargins(left, top, 357 right + (fShowPopUpMarker ? kPopUpIndicatorWidth : 0), bottom); 358 } 359