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
MenuView(uint32 resizingMode)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
MessageReceived(BMessage * message)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
AllAttached(void)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
PopulateUserMenu(BMenu * pMenu,int32 index)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
AddMenu(BMessage * message)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
DeleteMenu(BMessage * message)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
AddMenuItem(BMessage * message)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
MenuSelectionChanged(BMessage * message)407 void MenuView::MenuSelectionChanged(BMessage* message)
408 {
409 SetButtonState();
410 }
411
412
413
414 //--------------------------------------------------------------------
415 // MenuView implementation member functions
416
BuildMenuItems(BMenu * pMenu,BListItem * pSuperitem,BOutlineListView * pView)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
IsSeparator(const char * text) const455 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
SetButtonState(void)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
Valid(void)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