Gregory Nutt 601659ff59 Squashed commit of the following:
graphics/twm4nx/src/ciconmgr.cxx:  Integrate use of CButtonArray; implement first cut at event handling.

    graphics/nxwidgets/src/cbuttonarray.cxx:  Add a method to CButtonArray that will allow us to dynamically resize the array (at the cost of losing all button labels).

    graphics/twm4nx/src/ciconmgr.cxx:  Add some fragments of CButtonArray logic.

    graphics/twm4nx/src/cwindow.cxx:  Finishes first cut at window event management.
2019-04-26 12:50:35 -06:00

882 lines
21 KiB
C++

/////////////////////////////////////////////////////////////////////////////
// apps/graphics/twm4nx/src/cmenus.cxx
// twm menu code
//
// Copyright (C) 2019 Gregory Nutt. All rights reserved.
// Author: Gregory Nutt <gnutt@nuttx.org>
//
// Largely an original work but derives from TWM 1.0.10 in many ways:
//
// Copyright 1989,1998 The Open Group
// Copyright 1988 by Evans & Sutherland Computer Corporation,
//
// Please refer to apps/twm4nx/COPYING for detailed copyright information.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in
// the documentation and/or other materials provided with the
// distribution.
// 3. Neither the name NuttX nor the names of its contributors may be
// used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
// OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
// Included Files
/////////////////////////////////////////////////////////////////////////////
#include <nuttx/config.h>
#include <cstdio>
#include <cstring>
#include <cassert>
#include <cerrno>
#include <fcntl.h>
#include <debug.h>
#include <nuttx/version.h>
#include "graphics/nxwidgets/cnxfont.hxx"
#include "graphics/nxwidgets/clistbox.hxx"
#include "graphics/twm4nx/ctwm4nx.hxx"
#include "graphics/twm4nx/cmenus.hxx"
#include "graphics/twm4nx/cresize.hxx"
#include "graphics/twm4nx/cfonts.hxx"
#include "graphics/twm4nx/cicon.hxx"
#include "graphics/twm4nx/ciconmgr.hxx"
#include "graphics/twm4nx/cwindow.hxx"
#include "graphics/twm4nx/cwindowfactory.hxx"
#include "graphics/twm4nx/cwindowevent.hxx"
#include "graphics/twm4nx/twm4nx_widgetevents.hxx"
#include "graphics/twm4nx/cmenus.hxx"
////////////////////////////////////////////////////////////////////////////
// Class Implementations
/////////////////////////////////////////////////////////////////////////////
using namespace Twm4Nx;
/**
* CMenus Constructor
*/
CMenus::CMenus(CTwm4Nx *twm4nx)
{
// Save the Twm4Nx session
m_twm4nx = twm4nx; // Save the Twm4Nx session
// Menus
m_popUpMenu = (FAR CMenus *)0; // No pop-up menu
m_activeItem = (FAR struct SMenuItem *)0; // No active menu item
m_nMenuItems = 0; // No menu items yet
m_menuDepth = 0; // No menus up
m_entryHeight = 0; // Menu entry height
m_menuPull = false; // No pull right entry
// Windows
m_menuWindow = (FAR NXWidgets::CNxTkWindow *)0; // The menu window
// Widgets
m_menuListBox = (FAR NXWidgets::CListBox *)0; //The menu list box
// Functions
m_funcKeyHead = (FAR struct SFuncKey *)0;
}
/**
* CMenus Destructor
*/
CMenus::~CMenus(void)
{
cleanup();
}
/**
* CMenus Initializer. Performs the parts of the CMenus construction
* that may fail.
*
* @result True is returned on success
*/
bool CMenus::initialize(FAR const char *name)
{
// Open a message queue to NX events.
FAR const char *mqname = m_twm4nx->getEventQueueName();
m_eventq = mq_open(mqname, O_WRONLY | O_NONBLOCK);
if (m_eventq == (mqd_t)-1)
{
gerr("ERROR: Failed open message queue '%s': %d\n",
mqname, errno);
}
// Save the menu name
m_menuName = strdup(name);
if (m_menuName == (FAR char *)0)
{
return false;
}
// Create the menu window
if (!createMenuWindow())
{
gerr("ERROR: Failed to create menu window\n");
cleanup();
return false;
}
// Create the menu list box
if (!createMenuListBox())
{
gerr("ERROR: Failed to create menu list box\n");
cleanup();
return false;
}
return true;
}
/**
* Add an item to a menu
*
* \param text The text to appear in the menu
* \param action The string to possibly execute
* \param subMenu The menu if it is a pull-right entry
* \param func The numeric function
*/
// REVISIT: Only used internally. Was used in .twmrc parsing.
bool CMenus::addMenuItem(FAR const char *text,
FAR const char *action, FAR CMenus *subMenu,
int func)
{
ginfo("Adding menu text=\"%s\", action=%s, subMenu=%d, f=%d\n",
text, action, subMenu, func);
// Allocate a new menu item entry
FAR struct SMenuItem *item = new SMenuItem;
if (item == (FAR struct SMenuItem *)0)
{
gerr("ERROR: Failed to allocate menu item\n");
return false;
}
// Clone the item name so that we have control over its lifespan
item->text = std::strdup(text);
if (item->text == (FAR char *)0)
{
gerr("ERROR: strdup of item text failed\n");
std::free(item);
return false;
}
// Save information about the menu item
item->action = action;
item->flink = NULL;
item->subMenu = NULL;
item->func = func;
CFonts *fonts = m_twm4nx->getFonts();
FAR NXWidgets::CNxFont *menuFont = fonts->getMenuFont();
int width = menuFont->getStringWidth(text);
if (width <= 0)
{
width = 1;
}
struct nxgl_size_s menuSize;
m_menuWindow->getSize(&menuSize);
if (width > menuSize.w)
{
menuSize.w = width;
m_menuWindow->setSize(&menuSize);
}
if (subMenu != NULL)
{
item->subMenu = subMenu;
m_menuPull = true;
}
// Save the index to this item and increment the total number of menu
// items
item->index = m_nMenuItems++;
// Add the menu item to the tail of the item list
if (m_menuHead == NULL)
{
m_menuHead = item;
item->blink = (FAR struct SMenuItem *)0;
}
else
{
m_menuTail->flink = item;
item->blink = m_menuTail;
}
m_menuTail = item;
item->flink = (FAR struct SMenuItem *)0;
// Add the item text to the list box
m_menuListBox->addOption(item->text, item->index);
// Update the menu window size
setMenuWindowSize();
// Redraw the list box
m_menuListBox->enableDrawing();
m_menuListBox->setRaisesEvents(true);
m_menuListBox->redraw();
return true;
}
/**
* Handle MENU events.
*
* @param eventmsg. The received NxWidget MENU event message.
* @return True if the message was properly handled. false is
* return on any failure.
*/
bool CMenus::event(FAR struct SEventMsg *eventmsg)
{
bool success = true;
switch (eventmsg->eventID)
{
case EVENT_MENU_IDENTIFY: // Describe the window
{
identify((FAR CWindow *)eventmsg->obj);
}
break;
case EVENT_MENU_VERSION: // Show the Twm4Nx version
identify((FAR CWindow *) NULL);
break;
case EVENT_MENU_DEICONIFY: // Window icon pressed
case EVENT_MENU_ICONIFY: // Tool bar minimize button pressed
{
FAR CWindow *cwin = (FAR CWindow *)eventmsg->obj;
if (cwin->isIconified())
{
cwin->deIconify();
}
else if (eventmsg->eventID == EVENT_MENU_ICONIFY)
{
cwin->iconify();
}
}
break;
case EVENT_MENU_FUNCTION: // Perform function on unknown menu
{
FAR struct SMenuItem *item;
for (item = m_menuHead; item != NULL; item = item->flink)
{
// Send another event message to the session manager
struct SEventMsg newmsg;
newmsg.eventID = item->func;
newmsg.pos.x = eventmsg->pos.x;
newmsg.pos.y = eventmsg->pos.y;
newmsg.delta.x = 0;
newmsg.delta.y = 0;
newmsg.context = eventmsg->context;
newmsg.obj = eventmsg->obj;
// NOTE that we cannot block because we are on the same thread
// as the message reader. If the event queue becomes full then
// we have no other option but to lose events.
//
// I suppose we could recurse and call Twm4Nx::dispatchEvent at
// the risk of runawy stack usage.
int ret = mq_send(m_eventq, (FAR const char *)&newmsg,
sizeof(struct SEventMsg), 100);
if (ret < 0)
{
gerr("ERROR: mq_send failed: %d\n", ret);
success = false;
}
}
}
break;
case EVENT_MENU_TITLE: // Really an action not an event
case EVENT_MENU_ROOT: // Popup root menu, really an action not an event
default:
success = false;
break;
}
return success;
}
void CMenus::identify(FAR CWindow *cwin)
{
int n = 0;
#if CONFIG_VERSION_MAJOR != 0 || CONFIG_VERSION_MINOR != 0
std::snprintf(m_info[n], INFO_SIZE, "Twm4Nx: NuttX-" CONFIG_VERSION_STRING);
#else
std::snprintf(m_info[n], INFO_SIZE, "Twm4Nx:");
#endif
m_info[n++][0] = '\0';
if (cwin != (FAR CWindow *)0)
{
// Get the size of the window
struct nxgl_size_s windowSize;
if (!cwin->getFrameSize(&windowSize))
{
return;
}
struct nxgl_point_s windowPos;
if (!cwin->getFramePosition(&windowPos))
{
return;
}
std::snprintf(m_info[n++], INFO_SIZE, "Name = \"%s\"",
cwin->getWindowName());
m_info[n++][0] = '\0';
std::snprintf(m_info[n++], INFO_SIZE, "Geometry/root = %dx%d+%d+%d",
windowSize.w, windowSize.h, windowPos.x, windowPos.y);
}
m_info[n++][0] = '\0';
std::snprintf(m_info[n++], INFO_SIZE, "Click to dismiss....");
// Figure out the width and height of the info window
FAR CFonts *fonts = m_twm4nx->getFonts();
FAR NXWidgets::CNxFont *defaultFont = fonts->getDefaultFont();
struct nxgl_size_s menuSize;
menuSize.h = n * (defaultFont->getHeight() + 2);
menuSize.w = 1;
for (int i = 0; i < n; i++)
{
int twidth = defaultFont->getStringWidth(m_info[i]);
if (twidth > menuSize.w)
{
menuSize.w = twidth;
}
}
menuSize.w += 10; // some padding
// Make sure that the window is on the display
struct nxgl_point_s menuPos;
if (m_menuWindow->getPosition(&menuPos))
{
menuPos.x -= (menuSize.w / 2);
menuPos.y -= (menuSize.h / 3);
struct nxgl_size_s displaySize;
m_twm4nx->getDisplaySize(&displaySize);
struct nxgl_size_s frameSize;
menuToFrameSize(&menuSize, &frameSize);
if (menuPos.x + frameSize.w >= displaySize.w)
{
menuPos.x = displaySize.w - frameSize.w;
}
if (menuPos.y + frameSize.h >= displaySize.h)
{
menuPos.y = displaySize.h - frameSize.h;
}
if (menuPos.x < 0)
{
menuPos.x = 0;
}
if (menuPos.y < 0)
{
menuPos.y = 0;
}
frameToMenuSize(&frameSize, &menuSize);
}
else
{
menuPos.x = 0;
menuPos.y = 0;
}
// Set the new window size and position
if (!m_menuWindow->setPosition(&menuPos) ||
!m_menuWindow->setSize(&menuSize))
{
return;
}
// Raise it to the top of the hiearchy
m_menuWindow->raise();
}
/**
* Create the menu window
*
* @result True is returned on success
*/
bool CMenus::createMenuWindow(void)
{
// Create the menu window
// 1. Get the server instance. m_twm4nx inherits from NXWidgets::CNXServer
// so we all ready have the server instance.
// 2. Create the style, using the selected colors (REVISIT)
// 3. Create a Widget control instance for the window using the default
// style for now. CWindowEvent derives from CWidgetControl.
FAR CWindowEvent *control = new CWindowEvent(m_twm4nx);
// 4. Create the menu window
m_menuWindow = m_twm4nx->createFramedWindow(control);
if (m_menuWindow == (FAR NXWidgets::CNxTkWindow *)0)
{
delete control;
return false;
}
// 5. Open and initialize the menu window
bool success = m_menuWindow->open();
if (!success)
{
delete m_menuWindow;
m_menuWindow = (FAR NXWidgets::CNxTkWindow *)0;
return false;
}
// 6. Set the initial window size
if (!setMenuWindowSize())
{
delete m_menuWindow;
m_menuWindow = (FAR NXWidgets::CNxTkWindow *)0;
return false;
}
// 7. Set the initial window position
struct nxgl_point_s pos =
{
.x = 0,
.y = 0
};
if (!m_menuWindow->setPosition(&pos))
{
delete m_menuWindow;
m_menuWindow = (FAR NXWidgets::CNxTkWindow *)0;
return false;
}
return true;
}
/**
* Update the menu window size
*
* @result True is returned on success
*/
bool CMenus::setMenuWindowSize(void)
{
CFonts *fonts = m_twm4nx->getFonts();
FAR NXWidgets::CNxFont *menuFont = fonts->getMenuFont();
m_entryHeight = menuFont->getHeight() + 4;
// Get the length of the longest item string in in the menu
nxgl_coord_t maxstring = 0;
for (FAR struct SMenuItem *curr = m_menuHead;
curr != NULL;
curr = curr->flink)
{
if (curr->text != (FAR char *)0)
{
nxgl_coord_t stringlen = menuFont->getStringWidth(curr->text);
if (stringlen > maxstring)
{
maxstring = stringlen;
}
}
}
// Lets first size the window accordingly
struct nxgl_size_s menuSize;
menuSize.w = maxstring + 10;
unsigned int nMenuItems = m_nMenuItems > 0 ? m_nMenuItems : 1;
menuSize.h = nMenuItems * m_entryHeight;
if (m_menuPull == true)
{
menuSize.w += 16;
}
// Clip to the size of the display
struct nxgl_size_s displaySize;
m_twm4nx->getDisplaySize(&displaySize);
struct nxgl_size_s frameSize;
menuToFrameSize(&menuSize, &frameSize);
if (frameSize.w > displaySize.w)
{
frameSize.w = displaySize.w;
}
if (frameSize.h > displaySize.h)
{
frameSize.h = displaySize.h;
}
// Set the new menu window size
frameToMenuSize(&frameSize, &menuSize);
if (!m_menuWindow->setSize(&menuSize))
{
gerr("ERROR: Failed to set window size\n");
return false;
}
return true;
}
/**
* Set the position of the menu window. Supports positioning of a
* pop-up window.
*
* @param framePos The position of the menu window frame
* @result True is returned on success
*/
bool CMenus::setMenuWindowPosition(FAR struct nxgl_point_s *framePos)
{
struct nxgl_point_s menuPos;
frameToMenuPos(framePos, &menuPos);
return m_menuWindow->setPosition(&menuPos);
}
/**
* Create the menu list box
*
* @result True is returned on success
*/
bool CMenus::createMenuListBox(void)
{
// Create the menu list box
// 1. Get the server instance. m_twm4nx inherits from NXWidgets::CNXServer
// so we all ready have the server instance.
// 2. Create the style, using the selected colors (REVISIT)
// 3. Create a Widget control instance for the window using the default
// style for now. CWindowEvent derives from CWidgetControl.
FAR CWindowEvent *control = new CWindowEvent(m_twm4nx);
// 4. Create the menu list box
struct nxgl_point_s pos;
pos.x = 0;
pos.y = 0;
struct nxgl_size_s size;
pos.x = 0;
pos.y = 0;
m_menuListBox = new NXWidgets::CListBox(control, pos.x, pos.y,
size.w, size.h);
if (m_menuListBox == (FAR NXWidgets::CListBox *)0)
{
gerr("ERROR: Failed to instantiate list box\n");
delete control;
return false;
}
// Configure the list box
m_menuListBox->disable();
m_menuListBox->disableDrawing();
m_menuListBox->disableDrawing();
// Register to get events from the mouse clicks on the image
m_menuListBox->addWidgetEventHandler(this);
return true;
}
/**
* Pop up a pull down menu.
*
* @param pos Location of upper left of menu frame
*/
bool CMenus::popUpMenu(FAR struct nxgl_point_s *pos)
{
// If there is already a popup menu, then delete it. We only permit
// one popup at this level.
if (m_popUpMenu != (FAR CMenus *)0)
{
delete m_popUpMenu;
}
// Create and initialize a new menu
m_popUpMenu = new CMenus(m_twm4nx);
if (m_popUpMenu == (FAR CMenus *)0)
{
gerr("ERROR: Failed to create popup menu.\n");
return false;
}
if (!m_popUpMenu->initialize(m_activeItem->text))
{
gerr("ERROR: Failed to intialize popup menu.\n");
delete m_popUpMenu;
m_popUpMenu = (FAR CMenus *)0;
return false;
}
m_popUpMenu->addMenuItem("TWM Windows", (FAR const char *)0,
(FAR CMenus *)0, 0);
FAR CWindowFactory *factory = m_twm4nx->getWindowFactory();
int nWindowNames;
FAR struct SWindow *swin;
for (swin = factory->windowHead(), nWindowNames = 0;
swin != NULL; swin = swin->flink)
{
nWindowNames++;
}
if (nWindowNames != 0)
{
FAR CWindow **windowNames =
(FAR CWindow **)std::malloc(sizeof(FAR CWindow *) * nWindowNames);
if (windowNames == (FAR CWindow **)0)
{
gerr("ERROR: Failed to allocat window name\n");
return false;
}
swin = factory->windowHead();
windowNames[0] = swin->cwin;
for (nWindowNames = 1;
swin != NULL;
swin = swin->flink, nWindowNames++)
{
FAR CWindow *tmpcwin1 = swin->cwin;
for (int i = 0; i < nWindowNames; i++)
{
FAR const char *windowName1 = tmpcwin1->getWindowName();
FAR const char *windowName2 = windowNames[i]->getWindowName();
if (std::strcmp(windowName1, windowName2) < 0)
{
FAR CWindow *tmpcwin2;
tmpcwin2 = tmpcwin1;
tmpcwin1 = windowNames[i];
windowNames[i] = tmpcwin2;
}
}
windowNames[nWindowNames] = tmpcwin1;
}
for (int i = 0; i < nWindowNames; i++)
{
m_popUpMenu->addMenuItem(windowNames[i]->getWindowName(),
(FAR const char *)0, (FAR CMenus *)0,
EVENT_WINDOW_POPUP);
}
std::free(windowNames);
}
if (m_nMenuItems == 0)
{
delete m_popUpMenu;
m_popUpMenu = (FAR CMenus *)0;
return false;
}
// Clip to screen
struct nxgl_size_s displaySize;
m_twm4nx->getDisplaySize(&displaySize);
struct nxgl_size_s menuSize;
m_menuWindow->getSize(&menuSize);
struct nxgl_size_s frameSize;
menuToFrameSize(&menuSize, &frameSize);
if (pos->x + frameSize.w > displaySize.w)
{
pos->x = displaySize.w - frameSize.w;
}
if (pos->x < 0)
{
pos->x = 0;
}
if (pos->y + frameSize.h > displaySize.h)
{
pos->y = displaySize.h - frameSize.h;
}
if (pos->y < 0)
{
pos->y = 0;
}
DEBUGASSERT(m_menuDepth < UINT8_MAX);
m_menuDepth++;
if (!m_popUpMenu->setMenuWindowPosition(pos))
{
delete m_popUpMenu;
m_popUpMenu = (FAR CMenus *)0;
return false;
}
m_popUpMenu->raiseMenuWindow();
m_menuWindow->synchronize();
return m_popUpMenu;
}
/**
* Cleanup or initialization error or on deconstruction.
*/
void CMenus::cleanup(void)
{
// Close the NxWidget event message queue
if (m_eventq != (mqd_t)-1)
{
(void)mq_close(m_eventq);
m_eventq = (mqd_t)-1;
}
// Free any popup menus
if (m_popUpMenu != (FAR CMenus *)0)
{
delete m_popUpMenu;
}
// Free the menu window
if (m_menuWindow != (FAR NXWidgets::CNxTkWindow *)0)
{
delete m_menuWindow;
m_menuWindow = (FAR NXWidgets::CNxTkWindow *)0;
}
// Free each menu item
FAR struct SMenuItem *curr;
FAR struct SMenuItem *next;
for (curr = m_menuHead; curr != (FAR struct SMenuItem *)0; curr = next)
{
next = curr->flink;
// Free the menu item text
if (curr->text != (FAR char *)0)
{
std::free(curr->text);
}
// Free any subMenu
if (curr->subMenu != (FAR CMenus *)0)
{
delete curr->subMenu;
}
// Free the menu item
delete curr;
}
// Free allocated memory
if (m_menuName != (FAR char *)0)
{
std::free(m_menuName);
m_menuName = (FAR char *)0;
}
}