///////////////////////////////////////////////////////////////////////////// // apps/graphics/twm4nx/src/cmenus.cxx // twm menu code // // Copyright (C) 2019 Gregory Nutt. All rights reserved. // Author: Gregory Nutt // // 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 #include #include #include #include #include #include #include #include "graphics/nxwidgets/cnxstring.hxx" #include "graphics/nxwidgets/cnxfont.hxx" #include "graphics/nxwidgets/cbuttonarray.hxx" #include "graphics/nxwidgets/cwidgeteventargs.hxx" #include "graphics/twm4nx/twm4nx_config.hxx" #include "graphics/twm4nx/ctwm4nx.hxx" #include "graphics/twm4nx/cmenus.hxx" #include "graphics/twm4nx/cresize.hxx" #include "graphics/twm4nx/cfonts.hxx" #include "graphics/twm4nx/ciconmgr.hxx" #include "graphics/twm4nx/cwindow.hxx" #include "graphics/twm4nx/cwindowfactory.hxx" #include "graphics/twm4nx/cwindowevent.hxx" #include "graphics/twm4nx/ctwm4nxevent.hxx" #include "graphics/twm4nx/twm4nx_widgetevents.hxx" #include "graphics/twm4nx/cmenus.hxx" //////////////////////////////////////////////////////////////////////////// // Pre-processor Definitions ///////////////////////////////////////////////////////////////////////////// // Window flags: // // WFLAGS_NO_MENU_BUTTON: No menu buttons on menus // WFLAGS_NO_DELETE_BUTTON: Menus cannot be deleted in this manner. // WFLAGS_NO_RESIZE_BUTTON: Menus cannot be resized // WFLAGS_MENU: Menu windows are always created in the // hidden and iconifed state. When the menu is // selected, then it should be de-iconfied to // be shown. // WFLAGS_HIDDEN: Redundant #define MENU_WINDOW_FLAGS (WFLAGS_NO_MENU_BUTTON | WFLAGS_NO_DELETE_BUTTON | \ WFLAGS_NO_RESIZE_BUTTON | WFLAGS_MENU | \ WFLAGS_HIDDEN) //////////////////////////////////////////////////////////////////////////// // Class Implementations ///////////////////////////////////////////////////////////////////////////// using namespace Twm4Nx; /** * CMenus Constructor */ CMenus::CMenus(CTwm4Nx *twm4nx) { // Save the Twm4Nx session m_twm4nx = twm4nx; // Save the Twm4Nx session m_eventq = (mqd_t)-1; // No widget message queue yet // Menus m_menuHead = (FAR struct SMenuItem *)0; // No menu items m_menuTail = (FAR struct SMenuItem *)0; // No menu items m_nMenuItems = 0; // No menu items yet m_entryHeight = 0; // Menu entry height // Windows m_menuWindow = (FAR CWindow *)0; // The menu window // Widgets m_buttons = (FAR NXWidgets::CButtonArray *)0; // The menu button array } /** * CMenus Destructor */ CMenus::~CMenus(void) { cleanup(); } /** * CMenus Initializer. Performs the parts of the CMenus construction * that may fail. The menu window is created but is not initially * visible. Use the show() method to make the menu visible. * * @param name The name of the menu * @result True is returned on success */ bool CMenus::initialize(FAR NXWidgets::CNxString &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) { twmerr("ERROR: Failed open message queue '%s': %d\n", mqname, errno); return false; } // Clone the menu name m_menuName = name; // Create the menu window if (!createMenuWindow()) { twmerr("ERROR: Failed to create menu window\n"); cleanup(); return false; } // Create the menu button array if (!createMenuButtonArray()) { twmerr("ERROR: Failed to create menu button array\n"); cleanup(); return false; } return true; } /** * Add an item to a menu * * \param text The text to appear in the menu * \param subMenu The menu if it is a pull-right entry * \param handler The application event handler. Should be null unless * the event recipient is EVENT_RECIPIENT_APP * \param event The event to generate on menu item selection */ bool CMenus::addMenuItem(FAR NXWidgets::CNxString &text, FAR CMenus *subMenu, FAR CTwm4NxEvent *handler, uint16_t event) { twminfo("Adding: subMenu=%p, event=%04x\n", subMenu, event); // Allocate a new menu item entry FAR struct SMenuItem *item = new SMenuItem; if (item == (FAR struct SMenuItem *)0) { twmerr("ERROR: Failed to allocate menu item\n"); return false; } // Clone the item name so that we have control over its lifespan item->text = text; // Save information about the menu item item->flink = NULL; item->subMenu = subMenu; item->handler = handler; item->event = event; // Increment the total number of menu items 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; // Update the menu window size setMenuWindowSize(); m_menuWindow->synchronize(); // Get the updated window size struct nxgl_size_s menuSize; m_menuWindow->getWindowSize(&menuSize); // Resize the button array nxgl_coord_t buttonHeight = menuSize.h / m_nMenuItems; if (!m_buttons->resizeArray(1, m_nMenuItems, menuSize.w, buttonHeight)) { twmerr("ERROR: CButtonArray::resizeArray failed\n"); return false; } // We have to update all button labels after resizing int index; for (index = 0, item = m_menuHead; item != (FAR struct SMenuItem *)0; index++, item = item->flink) { m_buttons->setText(0, index, item->text); } 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_XYINPUT: // Poll for button array events { // This event message is sent from CWindowEvent whenever mouse, // touchscreen, or keyboard entry events are received in the // menu application window that contains the button array. NXWidgets::CWidgetControl *control = m_menuWindow->getWidgetControl(); // Poll for button array events. // // pollEvents() returns true if any interesting event in the // button array. handleActionEvent() will be called in that // case. false is not a failure. (void)control->pollEvents(); } break; 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->event; newmsg.pos.x = eventmsg->pos.x; newmsg.pos.y = eventmsg->pos.y; newmsg.context = eventmsg->context; newmsg.handler = item->handler; 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 runaway stack usage. int ret = mq_send(m_eventq, (FAR const char *)&newmsg, sizeof(struct SEventMsg), 100); if (ret < 0) { twmerr("ERROR: mq_send failed: %d\n", ret); success = false; } } } break; case EVENT_MENU_SUBMENU: // Sub-menu selected { // Bring up the sub-menu FAR CMenus *cmenu = (FAR CMenus *)eventmsg->obj; success = cmenu->show(); } break; 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->getWindowPosition(&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->setWindowPosition(&menuPos) || !m_menuWindow->setWindowSize(&menuSize)) { return; } // Raise it to the top of the hiearchy m_menuWindow->raiseWindow(); } /** * Convert the position of a menu window to the position of * the containing frame. */ void CMenus::menuToFramePos(FAR const struct nxgl_point_s *menupos, FAR struct nxgl_point_s *framepos) { nxgl_coord_t tbheight = m_menuWindow->getToolbarHeight(); framepos->x = menupos->x - CONFIG_NXTK_BORDERWIDTH; framepos->y = menupos->y - tbheight - CONFIG_NXTK_BORDERWIDTH; } /** * Convert the position of the containing frame to the position of * the menu window. */ void CMenus::frameToMenuPos(FAR const struct nxgl_point_s *framepos, FAR struct nxgl_point_s *menupos) { nxgl_coord_t tbheight = m_menuWindow->getToolbarHeight(); menupos->x = framepos->x + CONFIG_NXTK_BORDERWIDTH; menupos->y = framepos->y + tbheight + CONFIG_NXTK_BORDERWIDTH; } /** * Convert the size of a menu window to the size of the containing * frame. */ void CMenus::menuToFrameSize(FAR const struct nxgl_size_s *menusize, FAR struct nxgl_size_s *framesize) { nxgl_coord_t tbheight = m_menuWindow->getToolbarHeight(); framesize->w = menusize->w + 2 * CONFIG_NXTK_BORDERWIDTH; framesize->h = menusize->h + tbheight + 2 * CONFIG_NXTK_BORDERWIDTH; } /** * Convert the size of a containing frame to the size of the menu * window. */ void CMenus::frameToMenuSize(FAR const struct nxgl_size_s *framesize, FAR struct nxgl_size_s *menusize) { nxgl_coord_t tbheight = m_menuWindow->getToolbarHeight(); menusize->w = framesize->w - 2 * CONFIG_NXTK_BORDERWIDTH; menusize->h = framesize->h - tbheight - 2 * CONFIG_NXTK_BORDERWIDTH; } /** * Create the menu window. Menu windows are always created in the hidden * state. When the menu is selected, then it should be shown. * * @result True is returned on success */ bool CMenus::createMenuWindow(void) { // Create the menu window CWindowFactory *factory = m_twm4nx->getWindowFactory(); m_menuWindow = factory->createWindow(m_menuName, (FAR const struct NXWidgets::SRlePaletteBitmap *)0, (FAR CIconMgr *)0, MENU_WINDOW_FLAGS); if (m_menuWindow == (FAR CWindow *)0) { twmerr("ERROR: Failed to create icon manager window"); return false; } // Configure mouse events needed by the button array. bool success = m_menuWindow->configureEvents((FAR void *)this, EVENT_SYSTEM_NOP, EVENT_MENU_XYINPUT, EVENT_SYSTEM_NOP); if (!success) { delete m_menuWindow; m_menuWindow = (FAR CWindow *)0; return false; } // Adjust the size of the window struct nxgl_size_s windowSize; getMenuWindowSize(windowSize); if (!m_menuWindow->setWindowSize(&windowSize)) { twmerr("ERROR: Failed to set window size\n"); delete m_menuWindow; m_menuWindow = (FAR CWindow *)0; return false; } return true; } /** * Calculate the optimal menu frame size * * @param frameSize The location to return the calculated frame size */ void CMenus::getMenuFrameSize(FAR struct nxgl_size_s &frameSize) { CFonts *fonts = m_twm4nx->getFonts(); FAR NXWidgets::CNxFont *menuFont = fonts->getMenuFont(); m_entryHeight = menuFont->getHeight() + CONFIG_TWM4NX_MENU_VSPACING; // Get the minimum width of the toolbar nxgl_coord_t maxWidth = minimumToolbarWidth(m_twm4nx, m_menuName, MENU_WINDOW_FLAGS); // Compare that to the length of the longest item string in in the menu for (FAR struct SMenuItem *curr = m_menuHead; curr != NULL; curr = curr->flink) { nxgl_coord_t stringlen = menuFont->getStringWidth(curr->text); if (stringlen > maxWidth) { maxWidth = stringlen; } } // Lets first size the window accordingly struct nxgl_size_s menuSize; menuSize.w = maxWidth + CONFIG_TWM4NX_MENU_HSPACING; unsigned int nMenuItems = m_nMenuItems > 0 ? m_nMenuItems : 1; menuSize.h = nMenuItems * m_entryHeight; // Clip to the size of the display struct nxgl_size_s displaySize; m_twm4nx->getDisplaySize(&displaySize); menuToFrameSize(&menuSize, &frameSize); if (frameSize.w > displaySize.w) { frameSize.w = displaySize.w; } if (frameSize.h > displaySize.h) { frameSize.h = displaySize.h; } } /** * Calculate the optimal menu window size * * @param frameSize The location to return the calculated window size */ void CMenus::getMenuWindowSize(FAR struct nxgl_size_s &size) { struct nxgl_size_s frameSize; getMenuFrameSize(frameSize); frameToMenuSize(&frameSize, &size); } /** * Update the menu window size * * @result True is returned on success */ bool CMenus::setMenuWindowSize(void) { // Get the optimal menu window size struct nxgl_size_s frameSize; getMenuFrameSize(frameSize); if (!m_menuWindow->resizeFrame(&frameSize, (FAR const struct nxgl_point_s *)0)) { twmerr("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->setWindowPosition(&menuPos); } /** * Create the menu button array * * @result True is returned on success */ bool CMenus::createMenuButtonArray(void) { // Get the width of the window struct nxgl_size_s windowSize; if (!m_menuWindow->getWindowSize(&windowSize)) { twmerr("ERROR: Failed to get window size\n"); return false; } // Create the button array uint8_t nrows = m_nMenuItems > 0 ? m_nMenuItems : 1; nxgl_coord_t buttonWidth = windowSize.w; nxgl_coord_t buttonHeight = windowSize.h / nrows; // Get the Widget control instance from the Icon Manager window. This // will force all widget drawing to go to the Icon Manager window. FAR NXWidgets:: CWidgetControl *control = m_menuWindow->getWidgetControl(); if (control == (FAR NXWidgets:: CWidgetControl *)0) { // Should not fail return false; } // Now we have enough information to create the button array // The button must be positioned at the upper left of the window m_buttons = new NXWidgets::CButtonArray(control, 0, 0, 1, nrows, buttonWidth, buttonHeight); if (m_buttons == (FAR NXWidgets::CButtonArray *)0) { twmerr("ERROR: Failed to create the button array\n"); return false; } // Configure the button array widget FAR CFonts *fonts = m_twm4nx->getFonts(); FAR NXWidgets::CNxFont *iconManagerFont = fonts->getIconManagerFont(); m_buttons->setFont(iconManagerFont); m_buttons->setBorderless(true); m_buttons->setRaisesEvents(true); // Enable and draw the button array m_buttons->enable(); m_buttons->enableDrawing(); m_buttons->redraw(); // Register to get events from the mouse clicks on the image m_buttons->addWidgetEventHandler(this); return true; } /** * Handle a widget action event, overriding the CWidgetEventHandler * method. This will indicate a button pre-release event. * * @param e The event data. */ void CMenus::handleActionEvent(const NXWidgets::CWidgetEventArgs &e) { // A button should now be clicked int column; int row; if (m_buttons->isButtonClicked(column, row) && column == 0) { // The row number is sufficent to locate the menu entry info // But we have to search through the menu items to find the // at this row. FAR struct SMenuItem *item; int index; for (item = m_menuHead, index = 0; item != (FAR struct SMenuItem *)0; item = item->flink, index++) { // When the index matches the row number, then we have // the entry. // REVISIT: Are there any race conditions we need to // concerned with here? Such as menuitems being removed // while the menu is up? if (row == index) { // Send an event to the Twm4Nx event handler struct SEventMsg msg; // If there is a subMenu, then bring the sub-menu up // now. if (item->subMenu != (FAR CMenus *)0) { msg.eventID = EVENT_MENU_SUBMENU; msg.handler = (FAR CTwm4NxEvent *)0; msg.obj = (FAR void *)item->subMenu; } // Otherwise, send the event specified for the menu item. The // handler is only used if the recipient of the event is // EVENT_RECIPIENT_APP else { msg.eventID = item->event; msg.handler = item->handler; msg.obj = (FAR void *)this; } // Fill in the remaining, common stuff msg.pos.x = e.getX(); msg.pos.y = e.getY(); msg.context = EVENT_CONTEXT_MENU; // 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 runaway stack usage. int ret = mq_send(m_eventq, (FAR const char *)&msg, sizeof(struct SEventMsg), 100); if (ret < 0) { twmerr("ERROR: mq_send failed: %d\n", ret); } return; } } twmwarn("WARNING: No matching menu at index %d\n", row); } } /** * 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 the menu window if (m_menuWindow != (FAR CWindow *)0) { delete m_menuWindow; m_menuWindow = (FAR CWindow *)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 any subMenu if (curr->subMenu != (FAR CMenus *)0) { delete curr->subMenu; } // Free the menu item delete curr; } m_menuHead = (FAR struct SMenuItem *)0; m_menuTail = (FAR struct SMenuItem *)0; // Free the button array if (m_buttons != (FAR NXWidgets::CButtonArray *)0) { delete m_buttons; m_buttons = (FAR NXWidgets::CButtonArray *)0; } }