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