xref: /haiku/src/tests/kits/interface/menu/menuworld/MenuView.cpp (revision 3f28966548baf455f231941a6fbc83552119821d)
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