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