⛏️ index : haiku.git

//--------------------------------------------------------------------
//	
//	MenuView.cpp
//
//	Written by: Owen Smith
//	
//--------------------------------------------------------------------

/*
	Copyright 1999, Be Incorporated.   All Rights Reserved.
	This file may be used under the terms of the Be Sample Code License.
*/

#include <Alert.h>
#include <Button.h>
#include <CheckBox.h>
#include <Menu.h>
#include <MenuItem.h>
#include <OutlineListView.h>
#include <ScrollView.h>
#include <TextControl.h>
#include <List.h>
#include <string.h>

#include "constants.h"
#include "MenuView.h"
#include "MenuWindow.h"
#include "PostDispatchInvoker.h"
#include "stddlg.h"
#include "ViewLayoutFactory.h"


//====================================================================
//	MenuView Implementation


//--------------------------------------------------------------------
//	MenuView constructors, destructors, operators

MenuView::MenuView(uint32 resizingMode)
	: BView(BRect(0, 0, 0, 0), "Menu View", resizingMode,
		B_WILL_DRAW)
{
	ViewLayoutFactory aFactory; // for semi-intelligent layout
	
	SetViewColor(BKG_GREY);

	// "hide user menus" check box
	float fCheck_x = 20.0f;
	float fCheck_y = 100.0f;
	m_pHideUserCheck = aFactory.MakeCheckBox("Hide User Menus",
		STR_HIDE_USER_MENUS, MSG_WIN_HIDE_USER_MENUS,
		BPoint(fCheck_x, fCheck_y));
	m_pHideUserCheck->SetValue(B_CONTROL_OFF);
	AddChild(m_pHideUserCheck);
	
	// "large test icons" check box
	fCheck_y = m_pHideUserCheck->Frame().bottom + 10;
	m_pLargeTestIconCheck = aFactory.MakeCheckBox("Large Test Icons",
		STR_LARGE_TEST_ICONS, MSG_WIN_LARGE_TEST_ICONS,
		BPoint(fCheck_x, fCheck_y));
	m_pLargeTestIconCheck->SetValue(B_CONTROL_OFF);
	AddChild(m_pLargeTestIconCheck);
	
	// "add menu", "add item", "delete" buttons
	BList buttons;
	float fButton_x = m_pHideUserCheck->Frame().right + 15;
	float fButton_y = m_pHideUserCheck->Frame().top;
	
	m_pAddMenuButton = aFactory.MakeButton("Add Menu Bar", STR_ADD_MENU,
		MSG_VIEW_ADD_MENU, BPoint(fButton_x, fButton_y));
	AddChild(m_pAddMenuButton);
	buttons.AddItem(m_pAddMenuButton);
	
	// for purposes of size calculation, use the longest piece
	// of text we're going to stuff into the button
	const char* addItemText;
	float itemLen, sepLen;
	itemLen = be_plain_font->StringWidth(STR_ADD_ITEM);
	sepLen = be_plain_font->StringWidth(STR_ADD_SEP);
	addItemText = (itemLen > sepLen) ? STR_ADD_ITEM : STR_ADD_SEP;
	m_pAddItemButton = aFactory.MakeButton("Add Item To Menu",
		addItemText, MSG_VIEW_ADD_ITEM,
		BPoint(fButton_x, fButton_y));
	m_pAddItemButton->SetEnabled(false);
	AddChild(m_pAddItemButton);
	buttons.AddItem(m_pAddItemButton);
	
	m_pDelButton = aFactory.MakeButton("Delete Menu Bar", STR_DELETE_MENU,
		MSG_VIEW_DELETE_MENU, BPoint(fButton_x, fButton_y));
	m_pDelButton->SetEnabled(false);
	AddChild(m_pDelButton);
	buttons.AddItem(m_pDelButton);
	
	// now resize list of buttons to max width
	aFactory.ResizeToListMax(buttons, RECT_WIDTH);
	
	// now align buttons along left side
	aFactory.Align(buttons, ALIGN_LEFT,
		m_pAddItemButton->Frame().Height() + 15);
		
	// now make add menu the default, AFTER we've
	// laid out the buttons, since the button bloats
	// when it's the default
	m_pAddMenuButton->MakeDefault(true);

	// item "label" control
	float fEdit_left = 20.0f, fEdit_bottom = m_pHideUserCheck->Frame().top - 20;
	float fEdit_right = m_pAddItemButton->Frame().right;
	
	m_pLabelCtrl = aFactory.MakeTextControl("Menu Bar Control",
		STR_LABEL_CTRL, NULL, BPoint(fEdit_left, fEdit_bottom),
		fEdit_right - fEdit_left, CORNER_BOTTOMLEFT);
	AddChild(m_pLabelCtrl);
	
	// menu outline view
	BRect r;
	r.left = m_pAddItemButton->Frame().right + 30;
	r.top = 20.0f;
	r.right = r.left + 200 - B_V_SCROLL_BAR_WIDTH;
	// API quirk here: <= 12 (hscroll height - 2) height
	// results in scrollbar drawing error
	r.bottom = r.top + 100 - B_H_SCROLL_BAR_HEIGHT;

	m_pMenuOutlineView = new BOutlineListView(r, "Menu Outline",
		B_SINGLE_SELECTION_LIST, B_FOLLOW_ALL);
	m_pMenuOutlineView->SetSelectionMessage(
		new BMessage(MSG_MENU_OUTLINE_SEL));
	
	// wrap outline view in scroller
	m_pScrollView = new BScrollView("Menu Outline Scroller",
		m_pMenuOutlineView, B_FOLLOW_LEFT | B_FOLLOW_TOP, 0,
		true, true);
	m_pScrollView->SetViewColor(BKG_GREY);
	AddChild(m_pScrollView);
	
}



//--------------------------------------------------------------------
//	MenuView virtual function overrides

void MenuView::MessageReceived(BMessage* message)
{
	switch (message->what) {
	case MSG_VIEW_ADD_MENU:
		AddMenu(message);
		break;
	case MSG_VIEW_DELETE_MENU:
		DeleteMenu(message);
		break;
	case MSG_VIEW_ADD_ITEM:
		AddMenuItem(message);
		break;
	case MSG_MENU_OUTLINE_SEL:
		MenuSelectionChanged(message);
		break;
	case MSG_LABEL_EDIT:
		SetButtonState();
		break;
	default:
		BView::MessageReceived(message);
		break;
	}
}

void MenuView::AllAttached(void)
{
	if (! Valid()) {
		return;
	}

	// Set button and view targets (now that window looper is available).
	m_pAddMenuButton->SetTarget(this);
	m_pDelButton->SetTarget(this);
	m_pAddItemButton->SetTarget(this);
	m_pMenuOutlineView->SetTarget(this);
	
	// Set button's initial state
	SetButtonState();
	
	// Wrap a message filter/invoker around the label control's text field
	// that will post us B_LABEL_EDIT msg after the text field processes a
	// B_KEY_DOWN message.
	m_pLabelCtrl->TextView()->AddFilter(
		new PostDispatchInvoker(B_KEY_DOWN, new BMessage(MSG_LABEL_EDIT), this));
	
	// Get one item's height by adding a dummy item to
	// the BOutlineListView and calculating its height.
	m_pMenuOutlineView->AddItem(new BStringItem("Dummy"));
	float itemHeight = m_pMenuOutlineView->ItemFrame(0).Height();
	itemHeight++;	// account for 1-pixel offset between items
					// which eliminates overlap
	delete m_pMenuOutlineView->RemoveItem((int32)0);
	
	// Resize outline list view to integral item height.
	// fudge factor of 4 comes from 2-pixel space between
	// scrollview and outline list view on top & bottom
	float viewHeight = 16*itemHeight;
	m_pScrollView->ResizeTo(m_pScrollView->Frame().Width(),
		viewHeight + B_H_SCROLL_BAR_HEIGHT + 4);
	BScrollBar *pBar = m_pScrollView->ScrollBar(B_HORIZONTAL);
	if (pBar) {
		pBar->SetRange(0, 300);
	}
	
	// Resize view to surround contents.
	ViewLayoutFactory aFactory;
	aFactory.ResizeAroundChildren(*this, BPoint(20,20));	
}



//--------------------------------------------------------------------
//	MenuView operations

void MenuView::PopulateUserMenu(BMenu* pMenu, int32 index)
{
	if ((! pMenu) || (! Valid())) {
		return;
	}
		
	// blow away all menu items
	BMenuItem* pMenuItem = pMenu->RemoveItem((int32)0);
	while(pMenuItem) {
		delete pMenuItem;
		pMenuItem = pMenu->RemoveItem((int32)0);
	}
	
	// build menu up from outline list view	
	BListItem* pListItem = m_pMenuOutlineView->ItemUnderAt(NULL, true, index);
	
	// recursive buildup of menu items
	BuildMenuItems(pMenu, pListItem, m_pMenuOutlineView);
}



//--------------------------------------------------------------------
//	MenuView message handlers

void MenuView::AddMenu(BMessage* message)
{
	if (! Valid()) {
		return;
	}
	
	const char* menuName = m_pLabelCtrl->Text();
	if ((! menuName) || (! *menuName)) {
		BAlert* pAlert = new BAlert("Add menu alert",
			"Please specify the menu name first.", "OK");
		pAlert->Go();
		return;
	}
	
	m_pMenuOutlineView->AddItem(new BStringItem(menuName));	
	
	// add some info and pass to window
	BMessage newMsg(MSG_WIN_ADD_MENU);
	newMsg.AddString("Menu Name", menuName);
	BWindow* pWin = Window();
	if (pWin) {
		pWin->PostMessage(&newMsg);
	}
	
	// Reset the label control and buttons
	m_pLabelCtrl->SetText("");
	SetButtonState();
}

void MenuView::DeleteMenu(BMessage* message)
{
	if (! Valid())
		return;

	int32 itemCount;	
	int32 selected = m_pMenuOutlineView->CurrentSelection();
	if (selected < 0)
		return;
	
	BStringItem* pSelItem = dynamic_cast<BStringItem*>
		(m_pMenuOutlineView->ItemAt(selected));
	if (! pSelItem)
		return;
	
	if (pSelItem->OutlineLevel() == 0) {
		// get index of item among items in 0 level
		itemCount = m_pMenuOutlineView->CountItemsUnder(NULL, true);
		int32 i;
		for (i=0; i<itemCount; i++) {
			BListItem* pItem = m_pMenuOutlineView->ItemUnderAt(NULL, true, i);
			if (pItem == pSelItem) {
				break;
			}
		}
		
		// Stuff the index into the message and pass along to
		// the window.
		BMessage newMsg(MSG_WIN_DELETE_MENU);
		newMsg.AddInt32("Menu Index", i);
		BWindow* pWin = Window();
		if (pWin) {
			pWin->PostMessage(&newMsg);
		}
	}			
				
	// Find & record all subitems of selection, because we'll
	// have to delete them after they're removed from the list.
	BList subItems;
	int32 j;
	itemCount = m_pMenuOutlineView->CountItemsUnder(pSelItem, false);
	for (j=0; j<itemCount; j++) {
		BListItem* pItem = m_pMenuOutlineView->ItemUnderAt(pSelItem, false, j);
		subItems.AddItem(pItem);
	}
	
	// Note the superitem for reference just below
	BStringItem* pSuperitem = dynamic_cast<BStringItem*>
		(m_pMenuOutlineView->Superitem(pSelItem));
		
	// Remove selected item and all subitems from
	// the outline list view.
	m_pMenuOutlineView->RemoveItem(pSelItem); // removes super- and subitems
	
	// Update window status
	MenuWindow* pWin = dynamic_cast<MenuWindow*>(Window());
	if (pWin) {
		const char* itemName = pSelItem->Text();
		if (strcmp(itemName, STR_SEPARATOR)) {
			pWin->UpdateStatus(STR_STATUS_DELETE_ITEM, itemName);
		} else {
			pWin->UpdateStatus(STR_STATUS_DELETE_SEPARATOR);
		}
	}

	// Delete the selected item and all subitems	
	for (j=0; j<itemCount; j++) {
		BListItem* pItem = reinterpret_cast<BListItem*>(subItems.ItemAt(j));
		delete pItem; // deletes subitems
	}
	delete pSelItem; // deletes superitem
	
	if (pSuperitem) {
		// There's a bug in outline list view which does not
		// update the superitem correctly. The only way to do so
		// is to remove and add a brand-new superitem afresh.
		if (! m_pMenuOutlineView->CountItemsUnder(pSuperitem, true))
		{
			int32 index = m_pMenuOutlineView->FullListIndexOf(pSuperitem);
			m_pMenuOutlineView->RemoveItem(pSuperitem);
			BStringItem* pCloneItem = new BStringItem(
				pSuperitem->Text(), pSuperitem->OutlineLevel());
			m_pMenuOutlineView->AddItem(pCloneItem, index);
			delete pSuperitem;
		}
	}
}

void MenuView::AddMenuItem(BMessage* message)
{
	if (! Valid()) {
		return;
	}		
	
	// Add item to outline list view but DON'T update the
	// window right away; the actual menu items will be
	// created dynamically later on.
	int32 selected = m_pMenuOutlineView->CurrentSelection();
	if (selected >= 0) {
		BListItem* pSelItem = m_pMenuOutlineView->ItemAt(selected);
		if (pSelItem) {		

			int32 level = pSelItem->OutlineLevel() + 1;
			int32 index = m_pMenuOutlineView->FullListIndexOf(pSelItem)
				+ m_pMenuOutlineView->CountItemsUnder(pSelItem, false) + 1;
			const char* itemName = m_pLabelCtrl->Text();
			bool bIsSeparator = IsSeparator(itemName);

			if (bIsSeparator) {
				m_pMenuOutlineView->AddItem(new BStringItem(STR_SEPARATOR, level),
					index);
			} else {
				m_pMenuOutlineView->AddItem(new BStringItem(itemName, level),
					index);
			}

			MenuWindow* pWin = dynamic_cast<MenuWindow*>(Window());
			if (pWin) {
				if (! bIsSeparator) {
					pWin->UpdateStatus(STR_STATUS_ADD_ITEM, itemName);
				} else {
					pWin->UpdateStatus(STR_STATUS_ADD_SEPARATOR);
				}
			}

			m_pMenuOutlineView->Invalidate();	// outline view doesn't
												// invalidate correctly
			
			// Reset the label control the buttons
			m_pLabelCtrl->SetText("");
			SetButtonState();
		}
	}	
}

void MenuView::MenuSelectionChanged(BMessage* message)
{
	SetButtonState();
}



//--------------------------------------------------------------------
//	MenuView implementation member functions

void MenuView::BuildMenuItems(BMenu* pMenu, BListItem* pSuperitem,
	BOutlineListView* pView)
{
	if ((! pMenu) || (! pSuperitem) || (! pView)) {
		return;
	}
	
	int32 len = pView->CountItemsUnder(pSuperitem, true);
	if (len == 0) {
		BMenuItem* pEmptyItem = new BMenuItem(STR_MNU_EMPTY_ITEM, NULL);
		pEmptyItem->SetEnabled(false);
		pMenu->AddItem(pEmptyItem);
	}
	
	for (int32 i=0; i<len; i++) {
		BStringItem* pItem = dynamic_cast<BStringItem*>
			(pView->ItemUnderAt(pSuperitem, true, i));
		if (pItem) {
			if (pView->CountItemsUnder(pItem, true) > 0) {
				// add a submenu & fill it
				BMenu* pNewMenu = new BMenu(pItem->Text());
				BuildMenuItems(pNewMenu, pItem, pView);
				pMenu->AddItem(pNewMenu);
			} else {
				if (strcmp(pItem->Text(), STR_SEPARATOR)) {
					// add a string item
					BMessage* pMsg = new BMessage(MSG_USER_ITEM);
					pMsg->AddString("Item Name", pItem->Text());
					pMenu->AddItem(new BMenuItem(pItem->Text(), pMsg));
				} else {
					// add a separator item
					pMenu->AddItem(new BSeparatorItem());
				}
			}
		}
	}
}

bool MenuView::IsSeparator(const char* text) const
{
	if (! text) {
		return true;
	}
	
	if (! *text) {
		return true;
	}
	
	int32 len = strlen(text);
	for (int32 i = 0; i < len; i++) {
		char ch = text[i];
		if ((ch != ' ') && (ch != '-')) {
			return false;
		}
	}
	
	return true;
}

void MenuView::SetButtonState(void)
{
	if (! Valid()) {
		return;
	}

	// See if an item in the outline list is selected
	int32 index = m_pMenuOutlineView->CurrentSelection();
	bool bIsSelected = (index >= 0);
	
	// If an item is selected, see if the selected
	// item is a separator
	bool bSeparatorSelected = false;
	if (bIsSelected) {
		BStringItem* pItem = dynamic_cast<BStringItem*>
			(m_pMenuOutlineView->ItemAt(index));
		if (pItem) {
			bSeparatorSelected = (! strcmp(pItem->Text(), STR_SEPARATOR));
		}
	}

	// Delete: enable if anything is selected
	m_pDelButton->SetEnabled(bIsSelected);
	
	// Add Item: enable if anything but a separator is selected	
	bool bEnableAddItem = bIsSelected && (! bSeparatorSelected);
	m_pAddItemButton->SetEnabled(bEnableAddItem);

	// Add Menu: enable if there's any text in the label field
	const char* labelText = m_pLabelCtrl->Text();
	m_pAddMenuButton->SetEnabled(labelText && (*labelText));
		
	// Add Item: text says Add Separator if button is enabled
	// and the label field contains separator text,
	// Add Item otherwise
	const char* itemText;
	if (bEnableAddItem && IsSeparator(labelText)) {
		itemText = STR_ADD_SEP;
	} else {
		itemText = STR_ADD_ITEM;
	}
	m_pAddItemButton->SetLabel(itemText);

	// If add item button is enabled, it should be the default	
	if (bEnableAddItem) {
		m_pAddItemButton->MakeDefault(true);
	} else {
		m_pAddMenuButton->MakeDefault(true);
	}
}

bool MenuView::Valid(void)
{
	if (! m_pLabelCtrl) {
		ierror(STR_NO_LABEL_CTRL);
		return false;
	}
	if (! m_pHideUserCheck) {
		ierror(STR_NO_HIDE_USER_CHECK);
		return false;
	}
	if (! m_pLargeTestIconCheck) {
		ierror(STR_NO_LARGE_ICON_CHECK);
		return false;
	}
	if (! m_pAddMenuButton) {
		ierror(STR_NO_ADDMENU_BUTTON);
		return false;
	}
	if (! m_pAddItemButton) {
		ierror(STR_NO_ADDITEM_BUTTON);
		return false;
	}
	if (! m_pDelButton) {
		ierror(STR_NO_DELETE_BUTTON);
		return false;
	}
	if (! m_pMenuOutlineView) {
		ierror(STR_NO_MENU_OUTLINE);
		return false;
	}
	if (! m_pScrollView) {
		ierror(STR_NO_MENU_SCROLL_VIEW);
		return false;
	}
	return true;		
}