1 /* 2 * Copyright 2001-2006, Haiku, Inc. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Marc Flerackers (mflerackers@androme.be) 7 * Bill Hayden (haydentech@users.sourceforge.net) 8 * Stefano Ceccherini (burton666@libero.it) 9 * Olivier Milla 10 */ 11 12 //! Display item for BMenu class 13 14 #include <Bitmap.h> 15 #include <MenuItem.h> 16 #include <Shape.h> 17 #include <String.h> 18 #include <Window.h> 19 20 #include <string.h> 21 #include <stdlib.h> 22 23 24 const uint32 kCtrlLength = 20*11; 25 const unsigned char kCtrlBits[] = { 26 0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x14,0xff,0xff,0xff, 27 0x1d,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x17,0x14,0xff,0xff,0xff, 28 0x1d,0x1a,0x13,0x04,0x04,0x13,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x17,0x14,0xff,0xff,0xff, 29 0x1d,0x1a,0x04,0x1a,0x1a,0x04,0x1a,0x04,0x04,0x04,0x1a,0x04,0x1a,0x1a,0x1a,0x17,0x14,0xff,0xff,0xff, 30 0x1d,0x1a,0x04,0x1a,0x1a,0x1a,0x1a,0x1a,0x04,0x1a,0x1a,0x04,0x1a,0x1a,0x1a,0x17,0x14,0xff,0xff,0xff, 31 0x1d,0x1a,0x04,0x1a,0x1a,0x1a,0x1a,0x1a,0x04,0x1a,0x1a,0x04,0x1a,0x1a,0x1a,0x17,0x14,0xff,0xff,0xff, 32 0x1d,0x1a,0x04,0x1a,0x1a,0x04,0x1a,0x1a,0x04,0x1a,0x1a,0x04,0x1a,0x1a,0x1a,0x17,0x14,0xff,0xff,0xff, 33 0x1d,0x1a,0x13,0x04,0x04,0x13,0x1a,0x1a,0x04,0x1a,0x1a,0x04,0x04,0x04,0x1a,0x17,0x14,0xff,0xff,0xff, 34 0x1d,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x17,0x14,0xff,0xff,0xff, 35 0x1d,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x14,0xff,0xff,0xff, 36 0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0xff,0xff,0xff 37 /* 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 38 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 39 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 40 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 41 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff*/ 42 }; 43 44 45 const uint32 kAltLength = 20*11; 46 const unsigned char kAltBits[] = { 47 0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x14,0xff,0xff,0xff, 48 0x1d,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x17,0x14,0xff,0xff,0xff, 49 0x1d,0x1a,0x1a,0x13,0x04,0x04,0x13,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x17,0x14,0xff,0xff,0xff, 50 0x1d,0x1a,0x1a,0x04,0x1a,0x1a,0x04,0x1a,0x04,0x1a,0x1a,0x04,0x04,0x04,0x1a,0x17,0x14,0xff,0xff,0xff, 51 0x1d,0x1a,0x1a,0x04,0x1a,0x1a,0x04,0x1a,0x04,0x1a,0x1a,0x1a,0x04,0x1a,0x1a,0x17,0x14,0xff,0xff,0xff, 52 0x1d,0x1a,0x1a,0x04,0x04,0x04,0x04,0x1a,0x04,0x1a,0x1a,0x1a,0x04,0x1a,0x1a,0x17,0x14,0xff,0xff,0xff, 53 0x1d,0x1a,0x1a,0x04,0x1a,0x1a,0x04,0x1a,0x04,0x1a,0x1a,0x1a,0x04,0x1a,0x1a,0x17,0x14,0xff,0xff,0xff, 54 0x1d,0x1a,0x1a,0x04,0x1a,0x1a,0x04,0x1a,0x04,0x04,0x04,0x1a,0x04,0x1a,0x1a,0x17,0x14,0xff,0xff,0xff, 55 0x1d,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x17,0x14,0xff,0xff,0xff, 56 0x1d,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x14,0xff,0xff,0xff, 57 0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0xff,0xff,0xff 58 /* 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 59 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 60 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 61 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 62 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff*/ 63 }; 64 65 66 const uint32 kShiftLength = 24*11; 67 const unsigned char kShiftBits[] = { 68 0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x17,0xff,0xff, 69 0x1d,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x19,0x17,0xff,0xff, 70 0x1d,0x1b,0x1b,0x17,0x0d,0x0d,0x17,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x19,0x17,0xff,0xff, 71 0x1d,0x1b,0x1b,0x0d,0x19,0x19,0x0d,0x1b,0x0d,0x1b,0x0d,0x1b,0x0d,0x0d,0x0d,0x1b,0x0d,0x0d,0x0d,0x1b,0x19,0x17,0xff,0xff, 72 0x1d,0x1b,0x1b,0x17,0x0d,0x0d,0x19,0x1b,0x0d,0x1b,0x0d,0x1b,0x0d,0x1b,0x1b,0x1b,0x1b,0x0d,0x1b,0x1b,0x19,0x17,0xff,0xff, 73 0x1d,0x1b,0x1b,0x1b,0x1b,0x1b,0x0d,0x1b,0x0d,0x0d,0x0d,0x1b,0x0d,0x0d,0x1b,0x1b,0x1b,0x0d,0x1b,0x1b,0x19,0x17,0xff,0xff, 74 0x1d,0x1b,0x1b,0x0d,0x19,0x19,0x0d,0x1b,0x0d,0x1b,0x0d,0x1b,0x0d,0x1b,0x1b,0x1b,0x1b,0x0d,0x1b,0x1b,0x19,0x17,0xff,0xff, 75 0x1d,0x1b,0x1b,0x17,0x0d,0x0d,0x17,0x1b,0x0d,0x1b,0x0d,0x1b,0x0d,0x1b,0x1b,0x1b,0x1b,0x0d,0x1b,0x1b,0x19,0x17,0xff,0xff, 76 0x1d,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x19,0x17,0xff,0xff, 77 0x1d,0x19,0x19,0x19,0x19,0x19,0x19,0x19,0x19,0x19,0x19,0x19,0x19,0x19,0x19,0x19,0x19,0x19,0x19,0x19,0x19,0x17,0xff,0xff, 78 0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0xff,0xff 79 /*0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 80 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 81 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 82 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 83 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff*/ 84 }; 85 86 const float kLightBGTint = (B_LIGHTEN_1_TINT + B_LIGHTEN_1_TINT + B_NO_TINT) / 3.0; 87 88 89 BMenuItem::BMenuItem(const char *label, BMessage *message, char shortcut, 90 uint32 modifiers) 91 { 92 _InitData(); 93 if (label != NULL) 94 fLabel = strdup(label); 95 96 SetMessage(message); 97 98 fShortcutChar = shortcut; 99 100 if (shortcut != 0) 101 fModifiers = modifiers | B_COMMAND_KEY; 102 else 103 fModifiers = 0; 104 } 105 106 107 BMenuItem::BMenuItem(BMenu *menu, BMessage *message) 108 { 109 _InitData(); 110 SetMessage(message); 111 _InitMenuData(menu); 112 } 113 114 115 BMenuItem::BMenuItem(BMessage *data) 116 { 117 _InitData(); 118 119 if (data->HasString("_label")) { 120 const char *string; 121 122 data->FindString("_label", &string); 123 SetLabel(string); 124 } 125 126 bool disable; 127 if (data->FindBool("_disable", &disable) == B_OK) 128 SetEnabled(!disable); 129 130 bool marked; 131 if (data->FindBool("_marked", &marked) == B_OK) 132 SetMarked(marked); 133 134 if (data->HasInt32("_user_trig")) { 135 int32 user_trig; 136 137 data->FindInt32("_user_trig", &user_trig); 138 139 SetTrigger(user_trig); 140 } 141 142 if (data->HasInt32("_shortcut")) { 143 int32 shortcut, mods; 144 145 data->FindInt32("_shortcut", &shortcut); 146 data->FindInt32("_mods", &mods); 147 148 SetShortcut(shortcut, mods); 149 } 150 151 if (data->HasMessage("_msg")) { 152 BMessage *msg = new BMessage; 153 154 data->FindMessage("_msg", msg); 155 SetMessage(msg); 156 } 157 158 BMessage subMessage; 159 if (data->FindMessage("_submenu", &subMessage) == B_OK) { 160 BArchivable *object = instantiate_object(&subMessage); 161 162 if (object != NULL) { 163 BMenu *menu = dynamic_cast<BMenu *>(object); 164 165 if (menu != NULL) 166 _InitMenuData(menu); 167 } 168 } 169 } 170 171 172 BArchivable * 173 BMenuItem::Instantiate(BMessage *data) 174 { 175 if (validate_instantiation(data, "BMenuItem")) 176 return new BMenuItem(data); 177 178 return NULL; 179 } 180 181 182 status_t 183 BMenuItem::Archive(BMessage *data, bool deep) const 184 { 185 status_t ret = B_OK; 186 if (fLabel) 187 ret = data->AddString("_label", Label()); 188 189 if (ret == B_OK && !IsEnabled()) 190 ret = data->AddBool("_disable", true); 191 192 if (ret == B_OK && IsMarked()) 193 ret = data->AddBool("_marked", true); 194 195 if (ret == B_OK && fUserTrigger) 196 ret = data->AddInt32("_user_trig", fUserTrigger); 197 198 if (ret == B_OK && fShortcutChar) { 199 ret = data->AddInt32("_shortcut", fShortcutChar); 200 if (ret == B_OK) 201 ret = data->AddInt32("_mods", fModifiers); 202 } 203 204 if (ret == B_OK && Message()) 205 ret = data->AddMessage("_msg", Message()); 206 207 if (ret == B_OK && deep && fSubmenu) { 208 BMessage submenu; 209 210 if (fSubmenu->Archive(&submenu, true) == B_OK) 211 ret = data->AddMessage("_submenu", &submenu); 212 } 213 214 return ret; 215 } 216 217 218 BMenuItem::~BMenuItem() 219 { 220 free(fLabel); 221 delete fSubmenu; 222 } 223 224 225 void 226 BMenuItem::SetLabel(const char *string) 227 { 228 if (fLabel != NULL) { 229 free(fLabel); 230 fLabel = NULL; 231 } 232 233 if (string != NULL) 234 fLabel = strdup(string); 235 236 if (fSuper != NULL) { 237 fSuper->InvalidateLayout(); 238 239 if (fSuper->LockLooper()) { 240 fSuper->Invalidate(); 241 fSuper->UnlockLooper(); 242 } 243 } 244 } 245 246 247 void 248 BMenuItem::SetEnabled(bool state) 249 { 250 if (fEnabled == state) 251 return; 252 253 fEnabled = state; 254 255 if (fSubmenu != NULL) 256 fSubmenu->SetEnabled(state); 257 258 BMenu *menu = Menu(); 259 if (menu != NULL && menu->LockLooper()) { 260 menu->Invalidate(fBounds); 261 menu->UnlockLooper(); 262 } 263 } 264 265 266 void 267 BMenuItem::SetMarked(bool state) 268 { 269 fMark = state; 270 271 if (state && Menu() != NULL) 272 Menu()->ItemMarked(this); 273 } 274 275 276 void 277 BMenuItem::SetTrigger(char trigger) 278 { 279 fUserTrigger = trigger; 280 281 const char* pos = strchr(Label(), trigger); 282 if (pos != NULL) { 283 fAutomaticTrigger = trigger; 284 fTriggerIndex = pos - Label(); 285 } else { 286 fAutomaticTrigger = 0; 287 fTriggerIndex = -1; 288 } 289 290 if (fSuper != NULL) 291 fSuper->InvalidateLayout(); 292 } 293 294 295 void 296 BMenuItem::SetShortcut(char ch, uint32 modifiers) 297 { 298 if (fShortcutChar != 0 && (fModifiers & B_COMMAND_KEY) && fWindow) 299 fWindow->RemoveShortcut(fShortcutChar, fModifiers); 300 301 fShortcutChar = ch; 302 303 if (ch != 0) 304 fModifiers = modifiers | B_COMMAND_KEY; 305 else 306 fModifiers = 0; 307 308 if (fShortcutChar != 0 && (fModifiers & B_COMMAND_KEY) && fWindow) 309 fWindow->AddShortcut(fShortcutChar, fModifiers, this); 310 311 if (fSuper) { 312 fSuper->InvalidateLayout(); 313 314 if (fSuper->LockLooper()) { 315 fSuper->Invalidate(); 316 fSuper->UnlockLooper(); 317 } 318 } 319 } 320 321 322 const char * 323 BMenuItem::Label() const 324 { 325 return fLabel; 326 } 327 328 329 bool 330 BMenuItem::IsEnabled() const 331 { 332 if (fSubmenu) 333 return fSubmenu->IsEnabled(); 334 335 if (!fEnabled) 336 return false; 337 338 return fSuper ? fSuper->IsEnabled() : true; 339 } 340 341 342 bool 343 BMenuItem::IsMarked() const 344 { 345 return fMark; 346 } 347 348 349 char 350 BMenuItem::Trigger() const 351 { 352 return fUserTrigger; 353 } 354 355 356 char 357 BMenuItem::Shortcut(uint32 *modifiers) const 358 { 359 if (modifiers) 360 *modifiers = fModifiers; 361 362 return fShortcutChar; 363 } 364 365 366 BMenu * 367 BMenuItem::Submenu() const 368 { 369 return fSubmenu; 370 } 371 372 373 BMenu * 374 BMenuItem::Menu() const 375 { 376 return fSuper; 377 } 378 379 380 BRect 381 BMenuItem::Frame() const 382 { 383 return fBounds; 384 } 385 386 387 void 388 BMenuItem::GetContentSize(float *width, float *height) 389 { 390 fSuper->CacheFontInfo(); 391 392 fCachedWidth = fSuper->StringWidth(fLabel); 393 394 if (width) 395 *width = (float)ceil(fCachedWidth); 396 if (height) 397 *height = fSuper->fFontHeight; 398 } 399 400 401 void 402 BMenuItem::TruncateLabel(float maxWidth, char *newLabel) 403 { 404 BFont font; 405 BString string(fLabel); 406 407 font.TruncateString(&string, B_TRUNCATE_MIDDLE, maxWidth); 408 409 string.CopyInto(newLabel, 0, string.Length()); 410 newLabel[string.Length()] = '\0'; 411 } 412 413 414 void 415 BMenuItem::DrawContent() 416 { 417 fSuper->CacheFontInfo(); 418 419 fSuper->MovePenBy(0, fSuper->fAscent); 420 BPoint lineStart = fSuper->PenLocation(); 421 422 float labelWidth, labelHeight; 423 GetContentSize(&labelWidth, &labelHeight); 424 425 // truncate if needed 426 // TODO: Actually, this is still never triggered 427 if (fBounds.Width() > labelWidth) 428 fSuper->DrawString(fLabel); 429 else { 430 char *truncatedLabel = new char[strlen(fLabel) + 4]; 431 TruncateLabel(fBounds.Width(), truncatedLabel); 432 fSuper->DrawString(truncatedLabel); 433 delete[] truncatedLabel; 434 } 435 436 if (fSuper->AreTriggersEnabled() && fTriggerIndex != -1) { 437 float escapements[fTriggerIndex + 1]; 438 BFont font; 439 fSuper->GetFont(&font); 440 441 font.GetEscapements(fLabel, fTriggerIndex + 1, escapements); 442 443 for (int32 i = 0; i < fTriggerIndex; i++) 444 lineStart.x += escapements[i] * font.Size(); 445 446 lineStart.x--; 447 lineStart.y++; 448 449 BPoint lineEnd(lineStart); 450 lineEnd.x += escapements[fTriggerIndex] * font.Size(); 451 452 fSuper->StrokeLine(lineStart, lineEnd); 453 } 454 } 455 456 457 void 458 BMenuItem::Draw() 459 { 460 bool enabled = IsEnabled(); 461 bool selected = IsSelected(); 462 463 rgb_color noTint = ui_color(B_MENU_BACKGROUND_COLOR); 464 rgb_color bgColor = noTint; 465 466 // set low color and fill background if selected 467 if (selected && (enabled || Submenu()) /*&& fSuper->fRedrawAfterSticky*/) { 468 bgColor = tint_color(noTint, B_DARKEN_2_TINT); 469 fSuper->SetLowColor(bgColor); 470 fSuper->FillRect(Frame(), B_SOLID_LOW); 471 } else 472 fSuper->SetLowColor(bgColor); 473 474 // set high color 475 if (enabled) 476 fSuper->SetHighColor(ui_color(B_MENU_ITEM_TEXT_COLOR)); 477 else if (selected) 478 fSuper->SetHighColor(tint_color(noTint, 479 B_LIGHTEN_1_TINT)); 480 else 481 fSuper->SetHighColor(tint_color(noTint, 482 B_DISABLED_LABEL_TINT)); 483 484 // draw content 485 fSuper->MovePenTo(ContentLocation()); 486 DrawContent(); 487 488 // draw extra symbols 489 if (fSuper->Layout() == B_ITEMS_IN_COLUMN) { 490 if (IsMarked()) 491 _DrawMarkSymbol(bgColor); 492 493 if (fShortcutChar) 494 _DrawShortcutSymbol(); 495 496 if (Submenu()) 497 _DrawSubmenuSymbol(bgColor); 498 } 499 } 500 501 502 void 503 BMenuItem::Highlight(bool flag) 504 { 505 Menu()->Invalidate(Frame()); 506 } 507 508 509 bool 510 BMenuItem::IsSelected() const 511 { 512 return fSelected; 513 } 514 515 516 BPoint 517 BMenuItem::ContentLocation() const 518 { 519 return BPoint(fBounds.left + Menu()->fPad.left, 520 fBounds.top + Menu()->fPad.top); 521 } 522 523 524 void BMenuItem::_ReservedMenuItem1() {} 525 void BMenuItem::_ReservedMenuItem2() {} 526 void BMenuItem::_ReservedMenuItem3() {} 527 void BMenuItem::_ReservedMenuItem4() {} 528 529 530 BMenuItem::BMenuItem(const BMenuItem &) 531 { 532 } 533 534 535 BMenuItem & 536 BMenuItem::operator=(const BMenuItem &) 537 { 538 return *this; 539 } 540 541 542 void 543 BMenuItem::_InitData() 544 { 545 fLabel = NULL; 546 fSubmenu = NULL; 547 fWindow = NULL; 548 fSuper = NULL; 549 fModifiers = 0; 550 fCachedWidth = 0; 551 fTriggerIndex = -1; 552 fUserTrigger = 0; 553 fAutomaticTrigger = 0; 554 fShortcutChar = 0; 555 fMark = false; 556 fEnabled = true; 557 fSelected = false; 558 } 559 560 561 void 562 BMenuItem::_InitMenuData(BMenu *menu) 563 { 564 fSubmenu = menu; 565 fSubmenu->fSuperitem = this; 566 567 BMenuItem *item = menu->FindMarked(); 568 569 if (menu->IsRadioMode() && menu->IsLabelFromMarked() && item != NULL) 570 SetLabel(item->Label()); 571 else 572 SetLabel(menu->Name()); 573 } 574 575 576 void 577 BMenuItem::Install(BWindow *window) 578 { 579 if (fSubmenu) 580 fSubmenu->Install(window); 581 582 fWindow = window; 583 584 if (fShortcutChar != 0 && (fModifiers & B_COMMAND_KEY) && fWindow) 585 window->AddShortcut(fShortcutChar, fModifiers, this); 586 587 if (!Messenger().IsValid()) 588 SetTarget(window); 589 } 590 591 592 status_t 593 BMenuItem::Invoke(BMessage *message) 594 { 595 if (!IsEnabled()) 596 return B_ERROR; 597 598 if (fSuper->IsRadioMode()) 599 SetMarked(true); 600 601 bool notify = false; 602 uint32 kind = InvokeKind(¬ify); 603 604 BMessage clone(kind); 605 status_t err = B_BAD_VALUE; 606 607 if (!message && !notify) 608 message = Message(); 609 610 if (!message) { 611 if (!fSuper->IsWatched()) 612 return err; 613 } else 614 clone = *message; 615 616 clone.AddInt32("index", Menu()->IndexOf(this)); 617 clone.AddInt64("when", (int64)system_time()); 618 clone.AddPointer("source", this); 619 clone.AddMessenger("be:sender", BMessenger(fSuper)); 620 621 if (message) 622 err = BInvoker::Invoke(&clone); 623 624 // TODO: assynchronous messaging 625 // SendNotices(kind, &clone); 626 627 return err; 628 } 629 630 631 void 632 BMenuItem::Uninstall() 633 { 634 if (fSubmenu != NULL) 635 fSubmenu->Uninstall(); 636 637 if (Target() == fWindow) 638 SetTarget(BMessenger()); 639 640 if (fShortcutChar != 0 && (fModifiers & B_COMMAND_KEY) && fWindow != NULL) 641 fWindow->RemoveShortcut(fShortcutChar, fModifiers); 642 643 fWindow = NULL; 644 } 645 646 647 void 648 BMenuItem::SetSuper(BMenu *super) 649 { 650 if (fSuper != NULL && super != NULL) 651 debugger("Error - can't add menu or menu item to more than 1 container (either menu or menubar)."); 652 653 fSuper = super; 654 655 if (fSubmenu != NULL) 656 fSubmenu->fSuper = super; 657 } 658 659 660 void 661 BMenuItem::Select(bool selected) 662 { 663 if (fSelected == selected) 664 return; 665 666 if (Submenu() || IsEnabled()) { 667 fSelected = selected; 668 Highlight(selected); 669 } 670 } 671 672 673 void 674 BMenuItem::_DrawMarkSymbol(rgb_color bgColor) 675 { 676 fSuper->PushState(); 677 678 BRect r(fBounds); 679 r.right = r.left + r.Height(); 680 r.top += 2; 681 r.bottom -= 2; 682 r.left += 1; 683 r.right -= 5; 684 685 fSuper->SetHighColor(tint_color(bgColor, kLightBGTint)); 686 fSuper->FillRect(r); 687 688 BPoint center(floorf((r.left + r.right) / 2.0) + 0.5, 689 floorf((r.top + r.bottom) / 2.0) + 0.5); 690 // NOTE: center is on X.5, Y.5 on purpose! 691 BShape arrowShape; 692 arrowShape.MoveTo(BPoint(center.x - 4, center.y - 1.5)); 693 arrowShape.LineTo(BPoint(center.x - 1, center.y + 2.0)); 694 arrowShape.LineTo(BPoint(center.x + 4, center.y - 3.5)); 695 696 fSuper->SetDrawingMode(B_OP_OVER); 697 fSuper->SetHighColor(tint_color(bgColor, B_DARKEN_MAX_TINT)); 698 fSuper->SetPenSize(2.0); 699 // NOTE: StrokeShape() offsets the shape by the current pen position, 700 // it is not documented in the BeBook, but it is true! 701 fSuper->MovePenTo(B_ORIGIN); 702 fSuper->StrokeShape(&arrowShape); 703 704 fSuper->PopState(); 705 } 706 707 708 void 709 BMenuItem::_DrawShortcutSymbol() 710 { 711 // TODO: Review this 712 BPoint where = ContentLocation(); 713 where.x += fBounds.Width() - 28; 714 if (fSubmenu) 715 where.x -= 12; 716 717 switch (fShortcutChar) { 718 case B_DOWN_ARROW: 719 case B_UP_ARROW: 720 case B_LEFT_ARROW: 721 case B_RIGHT_ARROW: 722 case B_ENTER: 723 _DrawControlChar(fShortcutChar, where + BPoint(0, fSuper->fAscent)); 724 break; 725 726 default: 727 fSuper->DrawChar(fShortcutChar, where + BPoint(0, fSuper->fAscent)); 728 break; 729 } 730 731 where -= BPoint(20, -1); 732 733 if (fModifiers & B_COMMAND_KEY) { 734 BRect rect(0,0,16,10); 735 BBitmap control(rect, B_CMAP8); 736 737 if (BMenu::sAltAsCommandKey) 738 control.SetBits(kAltBits, kAltLength, 0, B_CMAP8); 739 else 740 control.SetBits(kCtrlBits, kCtrlLength, 0, B_CMAP8); 741 fSuper->DrawBitmap(&control, where); 742 743 where.x -= rect.Width() + 1; 744 } 745 746 if (fModifiers & B_CONTROL_KEY) { 747 BRect rect(0,0,16,10); 748 BBitmap control(rect, B_CMAP8); 749 750 if (BMenu::sAltAsCommandKey) 751 control.SetBits(kCtrlBits, kCtrlLength, 0, B_CMAP8); 752 else 753 control.SetBits(kAltBits, kAltLength, 0, B_CMAP8); 754 fSuper->DrawBitmap(&control, where); 755 756 where.x -= rect.Width() + 1; 757 } 758 759 if (fModifiers & B_SHIFT_KEY) { 760 BRect rect(0,0,21,10); 761 BBitmap shift(rect, B_CMAP8); 762 shift.SetBits(kShiftBits, kShiftLength, 0, B_CMAP8); 763 fSuper->DrawBitmap(&shift, where - BPoint(6, 0)); 764 } 765 } 766 767 768 void 769 BMenuItem::_DrawSubmenuSymbol(rgb_color bgColor) 770 { 771 fSuper->PushState(); 772 773 BRect r(fBounds); 774 r.left = r.right - r.Height(); 775 r.InsetBy(2.0, 2.0); 776 777 fSuper->SetHighColor(tint_color(bgColor, kLightBGTint)); 778 fSuper->FillRect(r); 779 780 BPoint center(floorf((r.left + r.right) / 2.0) + 0.5, 781 floorf((r.top + r.bottom) / 2.0) + 0.5); 782 // NOTE: center is on X.5, Y.5 on purpose! 783 BShape arrowShape; 784 arrowShape.MoveTo(BPoint(center.x - 1.5, center.y - 3)); 785 arrowShape.LineTo(BPoint(center.x + 2.0, center.y)); 786 arrowShape.LineTo(BPoint(center.x - 1.5, center.y + 3)); 787 788 fSuper->SetDrawingMode(B_OP_OVER); 789 fSuper->SetHighColor(tint_color(bgColor, B_DARKEN_MAX_TINT)); 790 fSuper->SetPenSize(2.0); 791 // NOTE: StrokeShape() offsets the shape by the current pen position, 792 // it is not documented in the BeBook, but it is true! 793 fSuper->MovePenTo(B_ORIGIN); 794 fSuper->StrokeShape(&arrowShape); 795 796 fSuper->PopState(); 797 } 798 799 800 void 801 BMenuItem::_DrawControlChar(char shortcut, BPoint where) 802 { 803 // TODO: If needed, take another font for the control characters 804 // (or have font overlays in the app_server!) 805 const char* symbol = " "; 806 807 switch (shortcut) { 808 case B_DOWN_ARROW: 809 symbol = "\xe2\x86\x93"; 810 break; 811 case B_UP_ARROW: 812 symbol = "\xe2\x86\x91"; 813 break; 814 case B_LEFT_ARROW: 815 symbol = "\xe2\x86\x90"; 816 break; 817 case B_RIGHT_ARROW: 818 symbol = "\xe2\x86\x92"; 819 break; 820 case B_ENTER: 821 symbol = "\xe2\x86\xb5"; 822 break; 823 } 824 825 fSuper->DrawString(symbol, where); 826 } 827 828 829 void 830 BMenuItem::SetAutomaticTrigger(char trigger) 831 { 832 fAutomaticTrigger = trigger; 833 834 const char* pos = strchr(Label(), trigger); 835 if (pos != NULL) 836 fTriggerIndex = pos - Label(); 837 else 838 fTriggerIndex = -1; 839 } 840