1 //-------------------------------------------------------------------- 2 // 3 // MenuView.cpp 4 // 5 // Written by: Owen Smith 6 // 7 //-------------------------------------------------------------------- 8 9 /* 10 Copyright 1999, Be Incorporated. All Rights Reserved. 11 This file may be used under the terms of the Be Sample Code License. 12 */ 13 14 #include <Alert.h> 15 #include <Button.h> 16 #include <CheckBox.h> 17 #include <Menu.h> 18 #include <MenuItem.h> 19 #include <OutlineListView.h> 20 #include <ScrollView.h> 21 #include <TextControl.h> 22 #include <List.h> 23 #include <string.h> 24 25 #include "constants.h" 26 #include "MenuView.h" 27 #include "MenuWindow.h" 28 #include "PostDispatchInvoker.h" 29 #include "stddlg.h" 30 #include "ViewLayoutFactory.h" 31 32 33 //==================================================================== 34 // MenuView Implementation 35 36 37 //-------------------------------------------------------------------- 38 // MenuView constructors, destructors, operators 39 40 MenuView::MenuView(uint32 resizingMode) 41 : BView(BRect(0, 0, 0, 0), "Menu View", resizingMode, 42 B_WILL_DRAW) 43 { 44 ViewLayoutFactory aFactory; // for semi-intelligent layout 45 46 SetViewColor(BKG_GREY); 47 48 // "hide user menus" check box 49 float fCheck_x = 20.0f; 50 float fCheck_y = 100.0f; 51 m_pHideUserCheck = aFactory.MakeCheckBox("Hide User Menus", 52 STR_HIDE_USER_MENUS, MSG_WIN_HIDE_USER_MENUS, 53 BPoint(fCheck_x, fCheck_y)); 54 m_pHideUserCheck->SetValue(B_CONTROL_OFF); 55 AddChild(m_pHideUserCheck); 56 57 // "large test icons" check box 58 fCheck_y = m_pHideUserCheck->Frame().bottom + 10; 59 m_pLargeTestIconCheck = aFactory.MakeCheckBox("Large Test Icons", 60 STR_LARGE_TEST_ICONS, MSG_WIN_LARGE_TEST_ICONS, 61 BPoint(fCheck_x, fCheck_y)); 62 m_pLargeTestIconCheck->SetValue(B_CONTROL_OFF); 63 AddChild(m_pLargeTestIconCheck); 64 65 // "add menu", "add item", "delete" buttons 66 BList buttons; 67 float fButton_x = m_pHideUserCheck->Frame().right + 15; 68 float fButton_y = m_pHideUserCheck->Frame().top; 69 70 m_pAddMenuButton = aFactory.MakeButton("Add Menu Bar", STR_ADD_MENU, 71 MSG_VIEW_ADD_MENU, BPoint(fButton_x, fButton_y)); 72 AddChild(m_pAddMenuButton); 73 buttons.AddItem(m_pAddMenuButton); 74 75 // for purposes of size calculation, use the longest piece 76 // of text we're going to stuff into the button 77 const char* addItemText; 78 float itemLen, sepLen; 79 itemLen = be_plain_font->StringWidth(STR_ADD_ITEM); 80 sepLen = be_plain_font->StringWidth(STR_ADD_SEP); 81 addItemText = (itemLen > sepLen) ? STR_ADD_ITEM : STR_ADD_SEP; 82 m_pAddItemButton = aFactory.MakeButton("Add Item To Menu", 83 addItemText, MSG_VIEW_ADD_ITEM, 84 BPoint(fButton_x, fButton_y)); 85 m_pAddItemButton->SetEnabled(false); 86 AddChild(m_pAddItemButton); 87 buttons.AddItem(m_pAddItemButton); 88 89 m_pDelButton = aFactory.MakeButton("Delete Menu Bar", STR_DELETE_MENU, 90 MSG_VIEW_DELETE_MENU, BPoint(fButton_x, fButton_y)); 91 m_pDelButton->SetEnabled(false); 92 AddChild(m_pDelButton); 93 buttons.AddItem(m_pDelButton); 94 95 // now resize list of buttons to max width 96 aFactory.ResizeToListMax(buttons, RECT_WIDTH); 97 98 // now align buttons along left side 99 aFactory.Align(buttons, ALIGN_LEFT, 100 m_pAddItemButton->Frame().Height() + 15); 101 102 // now make add menu the default, AFTER we've 103 // laid out the buttons, since the button bloats 104 // when it's the default 105 m_pAddMenuButton->MakeDefault(true); 106 107 // item "label" control 108 float fEdit_left = 20.0f, fEdit_bottom = m_pHideUserCheck->Frame().top - 20; 109 float fEdit_right = m_pAddItemButton->Frame().right; 110 111 m_pLabelCtrl = aFactory.MakeTextControl("Menu Bar Control", 112 STR_LABEL_CTRL, NULL, BPoint(fEdit_left, fEdit_bottom), 113 fEdit_right - fEdit_left, CORNER_BOTTOMLEFT); 114 AddChild(m_pLabelCtrl); 115 116 // menu outline view 117 BRect r; 118 r.left = m_pAddItemButton->Frame().right + 30; 119 r.top = 20.0f; 120 r.right = r.left + 200 - B_V_SCROLL_BAR_WIDTH; 121 // API quirk here: <= 12 (hscroll height - 2) height 122 // results in scrollbar drawing error 123 r.bottom = r.top + 100 - B_H_SCROLL_BAR_HEIGHT; 124 125 m_pMenuOutlineView = new BOutlineListView(r, "Menu Outline", 126 B_SINGLE_SELECTION_LIST, B_FOLLOW_ALL); 127 m_pMenuOutlineView->SetSelectionMessage( 128 new BMessage(MSG_MENU_OUTLINE_SEL)); 129 130 // wrap outline view in scroller 131 m_pScrollView = new BScrollView("Menu Outline Scroller", 132 m_pMenuOutlineView, B_FOLLOW_LEFT | B_FOLLOW_TOP, 0, 133 true, true); 134 m_pScrollView->SetViewColor(BKG_GREY); 135 AddChild(m_pScrollView); 136 137 } 138 139 140 141 //-------------------------------------------------------------------- 142 // MenuView virtual function overrides 143 144 void MenuView::MessageReceived(BMessage* message) 145 { 146 switch (message->what) { 147 case MSG_VIEW_ADD_MENU: 148 AddMenu(message); 149 break; 150 case MSG_VIEW_DELETE_MENU: 151 DeleteMenu(message); 152 break; 153 case MSG_VIEW_ADD_ITEM: 154 AddMenuItem(message); 155 break; 156 case MSG_MENU_OUTLINE_SEL: 157 MenuSelectionChanged(message); 158 break; 159 case MSG_LABEL_EDIT: 160 SetButtonState(); 161 break; 162 default: 163 BView::MessageReceived(message); 164 break; 165 } 166 } 167 168 void MenuView::AllAttached(void) 169 { 170 if (! Valid()) { 171 return; 172 } 173 174 // Set button and view targets (now that window looper is available). 175 m_pAddMenuButton->SetTarget(this); 176 m_pDelButton->SetTarget(this); 177 m_pAddItemButton->SetTarget(this); 178 m_pMenuOutlineView->SetTarget(this); 179 180 // Set button's initial state 181 SetButtonState(); 182 183 // Wrap a message filter/invoker around the label control's text field 184 // that will post us B_LABEL_EDIT msg after the text field processes a 185 // B_KEY_DOWN message. 186 m_pLabelCtrl->TextView()->AddFilter( 187 new PostDispatchInvoker(B_KEY_DOWN, new BMessage(MSG_LABEL_EDIT), this)); 188 189 // Get one item's height by adding a dummy item to 190 // the BOutlineListView and calculating its height. 191 m_pMenuOutlineView->AddItem(new BStringItem("Dummy")); 192 float itemHeight = m_pMenuOutlineView->ItemFrame(0).Height(); 193 itemHeight++; // account for 1-pixel offset between items 194 // which eliminates overlap 195 delete m_pMenuOutlineView->RemoveItem((int32)0); 196 197 // Resize outline list view to integral item height. 198 // fudge factor of 4 comes from 2-pixel space between 199 // scrollview and outline list view on top & bottom 200 float viewHeight = 16*itemHeight; 201 m_pScrollView->ResizeTo(m_pScrollView->Frame().Width(), 202 viewHeight + B_H_SCROLL_BAR_HEIGHT + 4); 203 BScrollBar *pBar = m_pScrollView->ScrollBar(B_HORIZONTAL); 204 if (pBar) { 205 pBar->SetRange(0, 300); 206 } 207 208 // Resize view to surround contents. 209 ViewLayoutFactory aFactory; 210 aFactory.ResizeAroundChildren(*this, BPoint(20,20)); 211 } 212 213 214 215 //-------------------------------------------------------------------- 216 // MenuView operations 217 218 void MenuView::PopulateUserMenu(BMenu* pMenu, int32 index) 219 { 220 if ((! pMenu) || (! Valid())) { 221 return; 222 } 223 224 // blow away all menu items 225 BMenuItem* pMenuItem = pMenu->RemoveItem((int32)0); 226 while(pMenuItem) { 227 delete pMenuItem; 228 pMenuItem = pMenu->RemoveItem((int32)0); 229 } 230 231 // build menu up from outline list view 232 BListItem* pListItem = m_pMenuOutlineView->ItemUnderAt(NULL, true, index); 233 234 // recursive buildup of menu items 235 BuildMenuItems(pMenu, pListItem, m_pMenuOutlineView); 236 } 237 238 239 240 //-------------------------------------------------------------------- 241 // MenuView message handlers 242 243 void MenuView::AddMenu(BMessage* message) 244 { 245 if (! Valid()) { 246 return; 247 } 248 249 const char* menuName = m_pLabelCtrl->Text(); 250 if ((! menuName) || (! *menuName)) { 251 BAlert* pAlert = new BAlert("Add menu alert", 252 "Please specify the menu name first.", "OK"); 253 pAlert->Go(); 254 return; 255 } 256 257 m_pMenuOutlineView->AddItem(new BStringItem(menuName)); 258 259 // add some info and pass to window 260 BMessage newMsg(MSG_WIN_ADD_MENU); 261 newMsg.AddString("Menu Name", menuName); 262 BWindow* pWin = Window(); 263 if (pWin) { 264 pWin->PostMessage(&newMsg); 265 } 266 267 // Reset the label control and buttons 268 m_pLabelCtrl->SetText(""); 269 SetButtonState(); 270 } 271 272 void MenuView::DeleteMenu(BMessage* message) 273 { 274 if (! Valid()) 275 return; 276 277 int32 itemCount; 278 int32 selected = m_pMenuOutlineView->CurrentSelection(); 279 if (selected < 0) 280 return; 281 282 BStringItem* pSelItem = dynamic_cast<BStringItem*> 283 (m_pMenuOutlineView->ItemAt(selected)); 284 if (! pSelItem) 285 return; 286 287 if (pSelItem->OutlineLevel() == 0) { 288 // get index of item among items in 0 level 289 itemCount = m_pMenuOutlineView->CountItemsUnder(NULL, true); 290 int32 i; 291 for (i=0; i<itemCount; i++) { 292 BListItem* pItem = m_pMenuOutlineView->ItemUnderAt(NULL, true, i); 293 if (pItem == pSelItem) { 294 break; 295 } 296 } 297 298 // Stuff the index into the message and pass along to 299 // the window. 300 BMessage newMsg(MSG_WIN_DELETE_MENU); 301 newMsg.AddInt32("Menu Index", i); 302 BWindow* pWin = Window(); 303 if (pWin) { 304 pWin->PostMessage(&newMsg); 305 } 306 } 307 308 // Find & record all subitems of selection, because we'll 309 // have to delete them after they're removed from the list. 310 BList subItems; 311 int32 j; 312 itemCount = m_pMenuOutlineView->CountItemsUnder(pSelItem, false); 313 for (j=0; j<itemCount; j++) { 314 BListItem* pItem = m_pMenuOutlineView->ItemUnderAt(pSelItem, false, j); 315 subItems.AddItem(pItem); 316 } 317 318 // Note the superitem for reference just below 319 BStringItem* pSuperitem = dynamic_cast<BStringItem*> 320 (m_pMenuOutlineView->Superitem(pSelItem)); 321 322 // Remove selected item and all subitems from 323 // the outline list view. 324 m_pMenuOutlineView->RemoveItem(pSelItem); // removes super- and subitems 325 326 // Update window status 327 MenuWindow* pWin = dynamic_cast<MenuWindow*>(Window()); 328 if (pWin) { 329 const char* itemName = pSelItem->Text(); 330 if (strcmp(itemName, STR_SEPARATOR)) { 331 pWin->UpdateStatus(STR_STATUS_DELETE_ITEM, itemName); 332 } else { 333 pWin->UpdateStatus(STR_STATUS_DELETE_SEPARATOR); 334 } 335 } 336 337 // Delete the selected item and all subitems 338 for (j=0; j<itemCount; j++) { 339 BListItem* pItem = reinterpret_cast<BListItem*>(subItems.ItemAt(j)); 340 delete pItem; // deletes subitems 341 } 342 delete pSelItem; // deletes superitem 343 344 if (pSuperitem) { 345 // There's a bug in outline list view which does not 346 // update the superitem correctly. The only way to do so 347 // is to remove and add a brand-new superitem afresh. 348 if (! m_pMenuOutlineView->CountItemsUnder(pSuperitem, true)) 349 { 350 int32 index = m_pMenuOutlineView->FullListIndexOf(pSuperitem); 351 m_pMenuOutlineView->RemoveItem(pSuperitem); 352 BStringItem* pCloneItem = new BStringItem( 353 pSuperitem->Text(), pSuperitem->OutlineLevel()); 354 m_pMenuOutlineView->AddItem(pCloneItem, index); 355 delete pSuperitem; 356 } 357 } 358 } 359 360 void MenuView::AddMenuItem(BMessage* message) 361 { 362 if (! Valid()) { 363 return; 364 } 365 366 // Add item to outline list view but DON'T update the 367 // window right away; the actual menu items will be 368 // created dynamically later on. 369 int32 selected = m_pMenuOutlineView->CurrentSelection(); 370 if (selected >= 0) { 371 BListItem* pSelItem = m_pMenuOutlineView->ItemAt(selected); 372 if (pSelItem) { 373 374 int32 level = pSelItem->OutlineLevel() + 1; 375 int32 index = m_pMenuOutlineView->FullListIndexOf(pSelItem) 376 + m_pMenuOutlineView->CountItemsUnder(pSelItem, false) + 1; 377 const char* itemName = m_pLabelCtrl->Text(); 378 bool bIsSeparator = IsSeparator(itemName); 379 380 if (bIsSeparator) { 381 m_pMenuOutlineView->AddItem(new BStringItem(STR_SEPARATOR, level), 382 index); 383 } else { 384 m_pMenuOutlineView->AddItem(new BStringItem(itemName, level), 385 index); 386 } 387 388 MenuWindow* pWin = dynamic_cast<MenuWindow*>(Window()); 389 if (pWin) { 390 if (! bIsSeparator) { 391 pWin->UpdateStatus(STR_STATUS_ADD_ITEM, itemName); 392 } else { 393 pWin->UpdateStatus(STR_STATUS_ADD_SEPARATOR); 394 } 395 } 396 397 m_pMenuOutlineView->Invalidate(); // outline view doesn't 398 // invalidate correctly 399 400 // Reset the label control the buttons 401 m_pLabelCtrl->SetText(""); 402 SetButtonState(); 403 } 404 } 405 } 406 407 void MenuView::MenuSelectionChanged(BMessage* message) 408 { 409 SetButtonState(); 410 } 411 412 413 414 //-------------------------------------------------------------------- 415 // MenuView implementation member functions 416 417 void MenuView::BuildMenuItems(BMenu* pMenu, BListItem* pSuperitem, 418 BOutlineListView* pView) 419 { 420 if ((! pMenu) || (! pSuperitem) || (! pView)) { 421 return; 422 } 423 424 int32 len = pView->CountItemsUnder(pSuperitem, true); 425 if (len == 0) { 426 BMenuItem* pEmptyItem = new BMenuItem(STR_MNU_EMPTY_ITEM, NULL); 427 pEmptyItem->SetEnabled(false); 428 pMenu->AddItem(pEmptyItem); 429 } 430 431 for (int32 i=0; i<len; i++) { 432 BStringItem* pItem = dynamic_cast<BStringItem*> 433 (pView->ItemUnderAt(pSuperitem, true, i)); 434 if (pItem) { 435 if (pView->CountItemsUnder(pItem, true) > 0) { 436 // add a submenu & fill it 437 BMenu* pNewMenu = new BMenu(pItem->Text()); 438 BuildMenuItems(pNewMenu, pItem, pView); 439 pMenu->AddItem(pNewMenu); 440 } else { 441 if (strcmp(pItem->Text(), STR_SEPARATOR)) { 442 // add a string item 443 BMessage* pMsg = new BMessage(MSG_USER_ITEM); 444 pMsg->AddString("Item Name", pItem->Text()); 445 pMenu->AddItem(new BMenuItem(pItem->Text(), pMsg)); 446 } else { 447 // add a separator item 448 pMenu->AddItem(new BSeparatorItem()); 449 } 450 } 451 } 452 } 453 } 454 455 bool MenuView::IsSeparator(const char* text) const 456 { 457 if (! text) { 458 return true; 459 } 460 461 if (! *text) { 462 return true; 463 } 464 465 int32 len = strlen(text); 466 for (int32 i = 0; i < len; i++) { 467 char ch = text[i]; 468 if ((ch != ' ') && (ch != '-')) { 469 return false; 470 } 471 } 472 473 return true; 474 } 475 476 void MenuView::SetButtonState(void) 477 { 478 if (! Valid()) { 479 return; 480 } 481 482 // See if an item in the outline list is selected 483 int32 index = m_pMenuOutlineView->CurrentSelection(); 484 bool bIsSelected = (index >= 0); 485 486 // If an item is selected, see if the selected 487 // item is a separator 488 bool bSeparatorSelected = false; 489 if (bIsSelected) { 490 BStringItem* pItem = dynamic_cast<BStringItem*> 491 (m_pMenuOutlineView->ItemAt(index)); 492 if (pItem) { 493 bSeparatorSelected = (! strcmp(pItem->Text(), STR_SEPARATOR)); 494 } 495 } 496 497 // Delete: enable if anything is selected 498 m_pDelButton->SetEnabled(bIsSelected); 499 500 // Add Item: enable if anything but a separator is selected 501 bool bEnableAddItem = bIsSelected && (! bSeparatorSelected); 502 m_pAddItemButton->SetEnabled(bEnableAddItem); 503 504 // Add Menu: enable if there's any text in the label field 505 const char* labelText = m_pLabelCtrl->Text(); 506 m_pAddMenuButton->SetEnabled(labelText && (*labelText)); 507 508 // Add Item: text says Add Separator if button is enabled 509 // and the label field contains separator text, 510 // Add Item otherwise 511 const char* itemText; 512 if (bEnableAddItem && IsSeparator(labelText)) { 513 itemText = STR_ADD_SEP; 514 } else { 515 itemText = STR_ADD_ITEM; 516 } 517 m_pAddItemButton->SetLabel(itemText); 518 519 // If add item button is enabled, it should be the default 520 if (bEnableAddItem) { 521 m_pAddItemButton->MakeDefault(true); 522 } else { 523 m_pAddMenuButton->MakeDefault(true); 524 } 525 } 526 527 bool MenuView::Valid(void) 528 { 529 if (! m_pLabelCtrl) { 530 ierror(STR_NO_LABEL_CTRL); 531 return false; 532 } 533 if (! m_pHideUserCheck) { 534 ierror(STR_NO_HIDE_USER_CHECK); 535 return false; 536 } 537 if (! m_pLargeTestIconCheck) { 538 ierror(STR_NO_LARGE_ICON_CHECK); 539 return false; 540 } 541 if (! m_pAddMenuButton) { 542 ierror(STR_NO_ADDMENU_BUTTON); 543 return false; 544 } 545 if (! m_pAddItemButton) { 546 ierror(STR_NO_ADDITEM_BUTTON); 547 return false; 548 } 549 if (! m_pDelButton) { 550 ierror(STR_NO_DELETE_BUTTON); 551 return false; 552 } 553 if (! m_pMenuOutlineView) { 554 ierror(STR_NO_MENU_OUTLINE); 555 return false; 556 } 557 if (! m_pScrollView) { 558 ierror(STR_NO_MENU_SCROLL_VIEW); 559 return false; 560 } 561 return true; 562 } 563 564