graphics/twm4nx/src/cwindow.cxx (1,022 lines of code) (raw):

///////////////////////////////////////////////////////////////////////////// // apps/graphics/twm4nx/src/cwindow.cxx // // SPDX-License-Identifier: Apache-2.0 // // Licensed to the Apache Software Foundation (ASF) under one or more // contributor license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright ownership. The // ASF licenses this file to you under the Apache License, Version 2.0 (the // "License"); you may not use this file except in compliance with the // License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations // under the License. // ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// // // 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. // Although not listed as a copyright holder, thanks and recognition need // to go to Tom LaStrange, the original author of TWM. // ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// // Included Files ///////////////////////////////////////////////////////////////////////////// #include <nuttx/config.h> #include <cstring> #include <cassert> #include <cerrno> #include <fcntl.h> #include <mqueue.h> #include <nuttx/nx/nxglib.h> #include <nuttx/nx/nxbe.h> #include "graphics/nxglyphs.hxx" #include "graphics/nxwidgets/cnxtkwindow.hxx" #include "graphics/nxwidgets/cnxtoolbar.hxx" #include "graphics/nxwidgets/crlepalettebitmap.hxx" #include "graphics/nxwidgets/cscaledbitmap.hxx" #include "graphics/nxwidgets/cimage.hxx" #include "graphics/nxwidgets/clabel.hxx" #include "graphics/nxwidgets/cnxfont.hxx" #include "graphics/nxwidgets/singletons.hxx" #include "graphics/nxwidgets/cwidgetstyle.hxx" #include "graphics/twm4nx/twm4nx_config.hxx" #include "graphics/twm4nx/ctwm4nx.hxx" #include "graphics/twm4nx/cfonts.hxx" #include "graphics/twm4nx/cresize.hxx" #include "graphics/twm4nx/cbackground.hxx" #include "graphics/twm4nx/ciconwidget.hxx" #include "graphics/twm4nx/ciconmgr.hxx" #include "graphics/twm4nx/cwindowevent.hxx" #include "graphics/twm4nx/cwindow.hxx" #include "graphics/twm4nx/cwindowfactory.hxx" #include "graphics/twm4nx/ctwm4nxevent.hxx" #include "graphics/twm4nx/twm4nx_events.hxx" #include "graphics/twm4nx/twm4nx_cursor.hxx" ///////////////////////////////////////////////////////////////////////////// // Private Types ///////////////////////////////////////////////////////////////////////////// using namespace Twm4Nx; struct SToolbarInfo { FAR const NXWidgets::SRlePaletteBitmap *bitmap; /**< Bitmap configured for button */ bool rightSide; /**< True: Button packed on the right */ uint16_t event; /**< Event when button released */ }; ///////////////////////////////////////////////////////////////////////////// // Private Data ///////////////////////////////////////////////////////////////////////////// // This array provides a static description of the toolbar buttons struct SToolbarInfo GToolBarInfo[NTOOLBAR_BUTTONS] = { { &CONFIG_TWM4NX_MENU_IMAGE, false, EVENT_TOOLBAR_MENU }, { &CONFIG_TWM4NX_TERMINATE_IMAGE, true, EVENT_TOOLBAR_TERMINATE }, { &CONFIG_TWM4NX_RESIZE_IMAGE, true, EVENT_RESIZE_BUTTON }, { &CONFIG_TWM4NX_MINIMIZE_IMAGE, true, EVENT_TOOLBAR_MINIMIZE } }; ///////////////////////////////////////////////////////////////////////////// // CWindow Implementation ///////////////////////////////////////////////////////////////////////////// /** * CWindow Constructor * * @param twm4nx. Twm4Nx session */ CWindow::CWindow(CTwm4Nx *twm4nx) { m_twm4nx = twm4nx; // Save the Twm4Nx session m_eventq = (mqd_t)-1; // No widget message queue yet // Windows m_nxWin = (FAR NXWidgets::CNxTkWindow *)0; m_toolbar = (FAR NXWidgets::CNxToolbar *)0; m_windowEvent = (FAR CWindowEvent *)0; m_minWidth = 1; m_modal = false; // Events m_appEvents.eventObj = (FAR void *)0; m_appEvents.redrawEvent = EVENT_SYSTEM_NOP; // Redraw event ID m_appEvents.mouseEvent = EVENT_SYSTEM_NOP; // Mouse/touchscreen event ID m_appEvents.kbdEvent = EVENT_SYSTEM_NOP; // Keyboard event ID m_appEvents.closeEvent = EVENT_SYSTEM_NOP; // Window close event ID m_appEvents.deleteEvent = EVENT_SYSTEM_NOP; // Window delete event ID // Toolbar m_tbTitle = (FAR NXWidgets::CLabel *)0; m_tbHeight = 0; // Height of the toolbar m_tbLeftX = 0; // Offset to end of left buttons m_tbRightX = 0; // Offset to start of right buttons m_tbFlags = 0; // No customizations m_tbDisables = 0; // No buttons disabled // Style for the toolbar widgets. It is the same as the default // widget style, but using the color assigned to the toolbar background. m_tbStyle = *NXWidgets::g_defaultWidgetStyle; m_tbStyle.colors.background = CONFIG_TWM4NX_DEFAULT_TOOLBARCOLOR; m_tbStyle.colors.selectedBackground = CONFIG_TWM4NX_DEFAULT_TOOLBARCOLOR; // Icons/Icon Manager m_iconBitMap = (FAR NXWidgets::CRlePaletteBitmap *)0; m_iconWidget = (FAR CIconWidget *)0; m_iconMgr = (FAR CIconMgr *)0; m_iconified = false; // Dragging m_clicked = false; m_dragging = false; m_dragPos.x = 0; m_dragPos.y = 0; m_dragCSize.w = 0; m_dragCSize.h = 0; // Toolbar buttons std::memset(m_tbButtons, 0, NTOOLBAR_BUTTONS * sizeof(NXWidgets::CImage *)); } /** * CWindow Destructor */ CWindow::~CWindow(void) { cleanup(); } /** * CWindow Initializer (unlike the constructor, this may fail) * * The window is initialized with all application events disabled. * The CWindows::configureEvents() method may be called as a second * initialization step in order to enable application events. * * @param name The the name of the window (and its icon) * @param pos The initial position of the window * @param size The initial size of the window * @param sbitmap The Icon bitmap image. null if no icon. * @param iconMgr Pointer to icon manager instance. To support * multiple Icon Managers. * @param flags Toolbar customizations see WFLAGS_NO_* definition * @return True if the window was successfully initialize; false on * any failure, */ bool CWindow::initialize(FAR const NXWidgets::CNxString &name, FAR const struct nxgl_point_s *pos, FAR const struct nxgl_size_s *size, FAR const struct NXWidgets::SRlePaletteBitmap *sbitmap, FAR CIconMgr *iconMgr, uint8_t flags) { // Open a message queue to send fully digested NxWidget 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; } // If no Icon Manager was provided, we will use the standard Icon Manager if (iconMgr == (FAR CIconMgr *)0) { m_iconMgr = m_twm4nx->getIconMgr(); } else { m_iconMgr = iconMgr; } if (name.getLength() == 0) { m_name.setText(GNoName); } else { m_name.setText(name); } // Get the minimum window size. We need this minimum later for resizing. // If there is no toolbar, leave the minimum at one pixel as it was set by // the constructor. if (WFLAGS_HAVE_TOOLBAR(flags)) { m_minWidth = minimumToolbarWidth(m_twm4nx, m_name, flags); } // Do initial clip to the maximum window size struct nxgl_size_s maxWindow; m_twm4nx->maxWindowSize(&maxWindow); struct nxgl_size_s winsize; winsize.w = size->w; if (winsize.w > maxWindow.w) { winsize.w = maxWindow.w; } winsize.h = size->h; if (winsize.h > maxWindow.h) { winsize.h = maxWindow.h; } // Create the window m_nxWin = (FAR NXWidgets::CNxTkWindow *)0; m_toolbar = (FAR NXWidgets::CNxToolbar *)0; // Create the main window if (!createMainWindow(&winsize, pos, flags)) { twmerr("ERROR: createMainWindow() failed\n"); cleanup(); return false; } m_tbHeight = 0; m_tbFlags = flags; m_toolbar = (FAR NXWidgets::CNxToolbar *)0; if (WFLAGS_HAVE_TOOLBAR(flags)) { // Toolbar height should be based on size of images and fonts. if (!getToolbarHeight(name)) { twmerr("ERROR: getToolbarHeight() failed\n"); cleanup(); return false; } // Create the toolbar if (!createToolbar()) { twmerr("ERROR: createToolbar() failed\n"); cleanup(); return false; } // Add buttons to the toolbar if (!createToolbarButtons(flags)) { twmerr("ERROR: createToolbarButtons() failed\n"); cleanup(); return false; } // Add the title to the toolbar if (!createToolbarTitle(name)) { twmerr("ERROR: createToolbarTitle() failed\n"); cleanup(); return false; } } // Create and initialize the icon widget if a bitmap was provided if (sbitmap != (FAR const struct NXWidgets::SRlePaletteBitmap *)0) { // Create the icon image instance m_iconBitMap = new NXWidgets::CRlePaletteBitmap(sbitmap); if (m_iconBitMap == (NXWidgets::CRlePaletteBitmap *)0) { twmerr("ERROR: Failed to create icon image\n"); cleanup(); return false; } // Create a style for the Icon widget. It is the same as the default // widget style, but using the color assigned to the background as the // widget background color. NXWidgets::CWidgetStyle style = *NXWidgets::g_defaultWidgetStyle; style.colors.background = CONFIG_TWM4NX_DEFAULT_BACKGROUNDCOLOR; style.colors.selectedBackground = CONFIG_TWM4NX_DEFAULT_BACKGROUNDCOLOR; // Get the widget control instance from the background. This is needed // to force the icon widgets to be drawn on the background FAR CBackground *background = m_twm4nx->getBackground(); FAR NXWidgets::CWidgetControl *control = background->getWidgetControl(); m_iconWidget = new CIconWidget(m_twm4nx, control, pos->x, pos->y, &style); if (m_iconWidget == (FAR CIconWidget *)0) { twmerr("ERROR: Failed to create the icon widget\n"); cleanup(); return false; } if (!m_iconWidget->initialize(this, m_iconBitMap, m_name)) { twmerr("ERROR: Failed to initialize the icon widget\n"); cleanup(); return false; } // Initialize the icon widget m_iconWidget->disable(); m_iconWidget->disableDrawing(); m_iconWidget->setRaisesEvents(true); } // Re-enable toolbar widgets enableToolbarWidgets(); return true; } /** * Configure application window events. * * @param events Describes the application event configuration * @return True is returned on success */ bool CWindow::configureEvents(FAR const struct SAppEvents &events) { m_appEvents.eventObj = events.eventObj; // Event object m_appEvents.redrawEvent = events.redrawEvent; // Redraw event ID m_appEvents.resizeEvent = events.resizeEvent; // Resize event ID m_appEvents.mouseEvent = events.mouseEvent; // Mouse/touchscreen event ID m_appEvents.kbdEvent = events.kbdEvent; // Keyboard event ID m_appEvents.closeEvent = events.closeEvent; // Window close event ID m_appEvents.deleteEvent = events.deleteEvent; // Window delete event ID return m_windowEvent->configureEvents(events); } /** * Get the raw window size (including toolbar and frame) * * @param framesize Location to return the window frame size */ bool CWindow::getFrameSize(FAR struct nxgl_size_s *framesize) { // Get the window size struct nxgl_size_s winsize; bool success = getWindowSize(&winsize); if (success) { // Convert the window size to the frame size windowToFrameSize(&winsize, framesize); } return success; } /** * Update the window frame after a resize operation (includes the toolbar * and user window) * * @param frameSize The new window frame size * @param framePos The frame location which may also have changed */ bool CWindow::resizeFrame(FAR const struct nxgl_size_s *frameSize, FAR const struct nxgl_point_s *framePos) { // Account for toolbar and border struct nxgl_size_s delta; delta.w = 2 * CONFIG_NXTK_BORDERWIDTH; delta.h = m_tbHeight + 2 * CONFIG_NXTK_BORDERWIDTH; // Don't set the window size smaller than the minimum window size struct nxgl_size_s winsize; if (frameSize->w <= m_minWidth + delta.w) { winsize.w = m_minWidth; } else { winsize.w = frameSize->w - delta.w; } if (frameSize->h <= delta.h) { winsize.h = 1; } else { winsize.h = frameSize->h - delta.h; } // Set the usable window size bool success = m_nxWin->setSize(&winsize); if (!success) { twmerr("ERROR: Failed to setSize()\n"); return false; } if (framePos != (FAR const struct nxgl_point_s *)0) { // Set the new frame position (in case it changed too) success = setFramePosition(framePos); if (!success) { twmerr("ERROR: Failed to setFramePosition()\n"); return false; } } // Synchronize with the NX server to make sure that the new geometry is // truly in effect. m_nxWin->synchronize(); // Then update the toolbar layout (if there is one) success = updateToolbarLayout(); if (!success) { twmerr("ERROR: updateToolbarLayout() failed\n"); return false; } // Check if the application using this window is interested in resize // events if (m_appEvents.resizeEvent != EVENT_SYSTEM_NOP) { twminfo("Close event...\n"); // Send the application specific [pre-]close event struct SEventMsg outmsg; outmsg.eventID = m_appEvents.resizeEvent; outmsg.obj = (FAR void *)this; outmsg.pos.x = 0; outmsg.pos.y = 0; outmsg.context = EVENT_CONTEXT_WINDOW; outmsg.handler = m_appEvents.eventObj; int ret = mq_send(m_eventq, (FAR const char *)&outmsg, sizeof(struct SEventMsg), 100); if (ret < 0) { twmerr("ERROR: mq_send failed: %d\n", errno); return false; } } return true; } /** * Get the window frame position (accounting for toolbar and frame) * * @param size Location to return the window frame position */ bool CWindow::getFramePosition(FAR struct nxgl_point_s *framepos) { // Get the window position struct nxgl_point_s winpos; bool success = m_nxWin->getPosition(&winpos); if (success) { // Convert the window position to a frame position windowToFramePos(&winpos, framepos); } return success; } /** * Set the window frame position (accounting for toolbar and frame) * * @param size The new raw window position */ bool CWindow::setFramePosition(FAR const struct nxgl_point_s *framepos) { // Convert the frame position to the contained, primary window positio struct nxgl_point_s winpos; frameToWindowPos(framepos, &winpos); // And set the window position return m_nxWin->setPosition(&winpos); } /** * Minimize (iconify) the window * * @return True if the operation was successful */ bool CWindow::iconify(void) { if (!isIconified()) { // Make sure to exit any modal state before minimizing m_modal = false; m_nxWin->modal(false); // Hide the main window m_nxWin->hide(); // Menu windows don't have an icon if (hasIcon()) { // Enable the widget m_iconWidget->enable(); // Pick a position for icon struct nxgl_point_s iconPos; m_iconWidget->getPos(iconPos); FAR CWindowFactory *factory = m_twm4nx->getWindowFactory(); if (factory->placeIcon(this, iconPos, iconPos)) { m_iconWidget->moveTo(iconPos.x, iconPos.y); } // Redraw the icon widget m_iconWidget->enableDrawing(); m_iconWidget->redraw(); } m_iconified = true; m_nxWin->synchronize(); } return true; } /** * De-iconify the window * * @return True if the operation was successful */ bool CWindow::deIconify(void) { // De-iconify the window if (isIconified()) { // Raise and the main window m_iconified = false; m_nxWin->show(); if (hasIcon()) { // Disable the icon widget m_iconWidget->disableDrawing(); m_iconWidget->disable(); // Redraw the background window in the rectangle previously // occupied by the widget. struct nxgl_size_s size; m_iconWidget->getSize(size); struct nxgl_rect_s rect; m_iconWidget->getPos(rect.pt1); rect.pt2.x = rect.pt1.x + size.w - 1; rect.pt2.y = rect.pt1.y + size.h - 1; FAR CBackground *backgd = m_twm4nx->getBackground(); if (!backgd->redrawBackgroundWindow(&rect, false)) { twmerr("ERROR: redrawBackgroundWindow() failed\n"); } } // Make sure everything is in sync m_nxWin->synchronize(); } return true; } /** * Handle WINDOW events. * * @param eventmsg. The received NxWidget WINDOW event message. * @return True if the message was properly handled. false is * return on any failure. */ bool CWindow::event(FAR struct SEventMsg *eventmsg) { bool success = true; switch (eventmsg->eventID) { case EVENT_WINDOW_RAISE: // Raise window to the top of the hierarchy m_nxWin->raise(); // Could be the main or the icon window break; case EVENT_WINDOW_LOWER: // Lower window to the bottom of the hierarchy m_nxWin->lower(); // Could be the main or the icon window break; case EVENT_WINDOW_DEICONIFY: // De-iconify and raise the main window { success = deIconify(); } break; case EVENT_TOOLBAR_MENU: // Toolbar menu button released { // REVISIT: Not yet implemented (but don't raise an error) } break; case EVENT_TOOLBAR_MINIMIZE: // Toolbar minimize button released { // Minimize (iconify) the window success = iconify(); } break; case EVENT_TOOLBAR_TERMINATE: // Toolbar terminate button pressed if (isIconMgr()) { // Don't terminate the Icon manager, just hide it m_iconMgr->hide(); } else { // Inform the application that the window is disappearing if (m_appEvents.closeEvent != EVENT_SYSTEM_NOP) { twminfo("Close event...\n"); // Send the application specific [pre-]close event struct SEventMsg outmsg; outmsg.eventID = m_appEvents.closeEvent; outmsg.obj = (FAR void *)this; outmsg.pos.x = eventmsg->pos.x; outmsg.pos.y = eventmsg->pos.y; outmsg.context = eventmsg->context; outmsg.handler = m_appEvents.eventObj; int ret = mq_send(m_eventq, (FAR const char *)&outmsg, sizeof(struct SEventMsg), 100); if (ret < 0) { twmerr("ERROR: mq_send failed: %d\n", errno); } } // Close the window... but not yet. Send the blocked message. // The actual termination will no occur until the NX server // drains all of the message events. We will get the // EVENT_WINDOW_DELETE event at that point NXWidgets::CWidgetControl *control = m_nxWin->getWidgetControl(); nxtk_block(control->getWindowHandle(), (FAR void *)m_nxWin); } break; case EVENT_WINDOW_DELETE: // Toolbar terminate button pressed { // Poll for pending events before closing. FAR CWindow *cwin = (FAR CWindow *)eventmsg->obj; success = cwin->pollToolbarEvents(); FAR CWindowFactory *factory = m_twm4nx->getWindowFactory(); factory->destroyWindow(cwin); } break; case EVENT_TOOLBAR_GRAB: /* Left click on title widget. Start drag */ success = toolbarGrab(eventmsg); break; case EVENT_WINDOW_DRAG: /* Mouse movement while clicked */ success = windowDrag(eventmsg); break; case EVENT_TOOLBAR_UNGRAB: /* Left click release while dragging. */ success = toolbarUngrab(eventmsg); break; default: success = false; break; } return success; } /** * Create the main window * * Initially, the application window will generate no window-related events * (redraw, mouse/touchscreen, keyboard input, etc.). After creating the * window, the user may call the configureEvents() method to select the * eventIDs of the events to be generated. * * @param winsize The initial window size * @param winpos The initial window position * @param flags Toolbar customizations see WFLAGS_NO_* definitions */ bool CWindow::createMainWindow(FAR const nxgl_size_s *winsize, FAR const nxgl_point_s *winpos, uint8_t flags) { // 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. // Setup the the CWindowEvent instance to use our inherited drag event // handler m_windowEvent = new CWindowEvent(m_twm4nx, (FAR void *)this, m_appEvents); m_windowEvent->installEventTap(this, (uintptr_t)1); // 4. Create the window. Handling provided flags. NOTE: that menu windows // are always created hidden and in the iconified state (although they // have no icons) uint8_t cflags = NXBE_WINDOW_RAMBACKED; if (WFLAGS_IS_HIDDEN(flags) | WFLAGS_IS_MENU(flags)) { cflags |= NXBE_WINDOW_HIDDEN; } m_nxWin = m_twm4nx->createFramedWindow(m_windowEvent, cflags); if (m_nxWin == (FAR NXWidgets::CNxTkWindow *)0) { delete m_windowEvent; m_windowEvent = (FAR CWindowEvent *)0; return false; } // 5. Open and initialize the window bool success = m_nxWin->open(); if (!success) { return false; } // 6. Set the initial window size if (!m_nxWin->setSize(winsize)) { return false; } // 7. Set the initial window position if (!m_nxWin->setPosition(winpos)) { return false; } // Menu windows are always created hidden and in the iconified state // (although they have no icons) m_iconified = WFLAGS_IS_MENU(flags); return true; } /** * Calculate the height of the tool bar */ bool CWindow::getToolbarHeight(FAR const NXWidgets::CNxString &name) { // The tool bar height is the largest of the toolbar button heights or the // title text font // Check if there is a title. If so, get the font height. m_tbHeight = 0; if (name.getLength() != 0) { FAR CFonts *fonts = m_twm4nx->getFonts(); FAR NXWidgets::CNxFont *titleFont = fonts->getTitleFont(); m_tbHeight = titleFont->getHeight(); } // Now compare this to the height of each toolbar image for (int btindex = 0; btindex < NTOOLBAR_BUTTONS; btindex++) { nxgl_coord_t btnHeight = GToolBarInfo[btindex].bitmap->height; if (btnHeight > m_tbHeight) { m_tbHeight = btnHeight; } } // Plus some lines for good separation m_tbHeight += CONFIG_TWM4NX_TOOLBAR_VSPACING; return true; } /** * Create the tool bar */ bool CWindow::createToolbar(void) { // Create the toolbar // 1. Create the style, using the selected colors (REVISIT) // 2. Create a Widget control instance for the window using the default // style for now. CWindowEvent derives from CWidgetControl. struct SAppEvents events; events.eventObj = (FAR void *)this; events.redrawEvent = EVENT_SYSTEM_NOP; events.resizeEvent = EVENT_SYSTEM_NOP; events.mouseEvent = EVENT_TOOLBAR_XYINPUT; events.kbdEvent = EVENT_SYSTEM_NOP; events.closeEvent = EVENT_SYSTEM_NOP; events.deleteEvent = EVENT_WINDOW_DELETE; FAR CWindowEvent *control = new CWindowEvent(m_twm4nx, (FAR void *)this, events); control->installEventTap(this, (uintptr_t)0); // 3. Get the toolbar sub-window from the framed window m_toolbar = m_nxWin->openToolbar(m_tbHeight, control); if (m_toolbar == (FAR NXWidgets::CNxToolbar *)0) { delete control; return false; } // 4. Open and initialize the tool bar if (!m_toolbar->open()) { delete m_toolbar; m_toolbar = (FAR NXWidgets::CNxToolbar *)0; return false; } // 5. Fill the entire tool bar with the background color from the // current widget style. if (!fillToolbar()) { delete m_toolbar; m_toolbar = (FAR NXWidgets::CNxToolbar *)0; return false; } return true; } /** * Fill the toolbar background color */ bool CWindow::fillToolbar(void) { // Get the graphics port for drawing on the toolbar FAR NXWidgets::CWidgetControl *control = m_toolbar->getWidgetControl(); NXWidgets::CGraphicsPort *port = control->getGraphicsPort(); // Get the size of the window struct nxgl_size_s windowSize; if (!m_toolbar->getSize(&windowSize)) { twmerr("ERROR: Failed to get the size of the toolbar\n"); return false; } // Fill the toolbar with the background color of the current widget style // (which is always the default widget style for now). port->drawFilledRect(0, 0, windowSize.w, windowSize.h, NXWidgets::g_defaultWidgetStyle->colors.background); return true; } /** * Update the toolbar layout, resizing the title text window and * repositioning all windows on the toolbar. */ bool CWindow::updateToolbarLayout(void) { // Disable toolbar widget drawing and events while we do this disableToolbarWidgets(); // Reposition all right buttons. Change the width of the // toolbar does not effect the left side spacing. struct nxgl_size_s winsize; if (!getWindowSize(&winsize)) { twmerr("ERROR: Failed to get window size\n"); return false; } // Set up the toolbar horizontal spacing m_tbRightX = winsize.w; for (int btindex = 0; btindex < NTOOLBAR_BUTTONS; btindex++) { if (m_tbButtons[btindex] != (FAR NXWidgets::CImage *)0 && GToolBarInfo[btindex].rightSide) { FAR NXWidgets::CImage *cimage = m_tbButtons[btindex]; // Set the position of the Icon image in the toolbar struct nxgl_size_s windowSize; getWindowSize(&windowSize); // Center image vertically struct nxgl_point_s pos; pos.y = (m_tbHeight - cimage->getHeight()) / 2; // Pack on the right horizontally m_tbRightX -= (cimage->getWidth() + CONFIG_TWM4NX_TOOLBAR_HSPACING); pos.x = m_tbRightX; if (!cimage->moveTo(pos.x, pos.y)) { twmerr("ERROR: Failed to move button image\n"); return false; } } } // Vertical size of the title window is selected to fill the entire // toolbar. This really needs to be only the font height. However, // this improves the click-ability of the widget for small fonts. // // The Horizontal size of the title widget is determined by the available // space between m_tbLeftX and m_tbRightX. struct nxgl_size_s titleSize; titleSize.h = m_tbHeight; titleSize.w = m_tbRightX - m_tbLeftX - CONFIG_TWM4NX_TOOLBAR_HSPACING + 1; if (!m_tbTitle->resize(titleSize.w, titleSize.h)) { twmerr("ERROR: Failed to resize title\n"); return false; } // Fill the entire tool bar with the background color from the current // widget style. if (!fillToolbar()) { twmerr("ERROR: Failed to fill the toolbar\n"); return false; } // Enable and re-draw all of the toolbar widgets enableToolbarWidgets(); return true; } /** * Disable toolbar widget drawing and widget events. */ bool CWindow::disableToolbarWidgets(void) { for (int btindex = 0; btindex < NTOOLBAR_BUTTONS; btindex++) { FAR NXWidgets::CImage *cimage = m_tbButtons[btindex]; if (cimage != (FAR NXWidgets::CImage *)0) { cimage->disableDrawing(); cimage->setRaisesEvents(false); } } m_tbTitle->disableDrawing(); m_tbTitle->setRaisesEvents(false); return true; } /** * Enable and redraw toolbar widget drawing and widget events. */ bool CWindow::enableToolbarWidgets(void) { for (int btindex = 0; btindex < NTOOLBAR_BUTTONS; btindex++) { FAR NXWidgets::CImage *cimage = m_tbButtons[btindex]; if (cimage != (FAR NXWidgets::CImage *)0) { cimage->enableDrawing(); cimage->setRaisesEvents(true); cimage->redraw(); } } m_tbTitle->enableDrawing(); m_tbTitle->setRaisesEvents(true); m_tbTitle->redraw(); return true; } /** * Create all toolbar buttons * * @param flags Toolbar customizations see WFLAGS_NO_* definitions * @return True if the window was successfully initialize; false on * any failure, */ bool CWindow::createToolbarButtons(uint8_t flags) { struct nxgl_size_s winsize; if (!getWindowSize(&winsize)) { return false; } // Create the title bar windows // Set up the toolbar horizontal spacing m_tbRightX = winsize.w; m_tbLeftX = 0; for (int btindex = 0; btindex < NTOOLBAR_BUTTONS; btindex++) { // Check if this button is omitted by toolbar customizations if ((m_tbFlags & (1 << btindex)) != 0) { // Omitted.. skip to the next button continue; } // Create the bitmap instance FAR const NXWidgets::SRlePaletteBitmap *sbitmap = GToolBarInfo[btindex].bitmap; #ifdef CONFIG_TWM4NX_TOOLBAR_ICONSCALE // Create a CScaledBitmap to scale the bitmap icon struct nxgl_size_s iconSize; iconSize.w = CONFIG_TWM4NX_TOOLBAR_ICONWIDTH; iconSize.h = CONFIG_TWM4NX_TOOLBAR_ICONHEIGHT; FAR NXWidgets::CScaledBitmap *scaler = new NXWidgets::CScaledBitmap(sbitmap, iconSize); if (scaler == (FAR NXWidgets::CScaledBitmap *)0) { twmerr("ERROR: Failed to created scaled bitmap\n"); return false; } #endif // Create the image. The image will serve as button since it // can detect clicks and release just like a button. m_tbButtons[btindex] = (FAR NXWidgets::CImage *)0; nxgl_coord_t w = 1; nxgl_coord_t h = 1; // Get the toolbar CWdigetControl instance. This will force all // widget drawing to go to the toolbar. NXWidgets::CWidgetControl *control = m_toolbar->getWidgetControl(); #ifdef CONFIG_TWM4NX_TOOLBAR_ICONSCALE w = scaler->getWidth(); h = scaler->getHeight(); m_tbButtons[btindex] = new NXWidgets::CImage(control, 0, 0, w, h, scaler, &m_tbStyle); if (m_tbButtons[btindex] == (FAR NXWidgets::CImage *)0) { twmerr("ERROR: Failed to create image\n"); delete scalar; return false; } #else FAR NXWidgets::CRlePaletteBitmap *cbitmap = new NXWidgets::CRlePaletteBitmap(sbitmap); if (cbitmap == (FAR NXWidgets::CRlePaletteBitmap *)0) { twmerr("ERROR: Failed to create CrlPaletteBitmap\n"); return false; } w = cbitmap->getWidth(); h = cbitmap->getHeight(); m_tbButtons[btindex] = new NXWidgets::CImage(control, 0, 0, w, h, cbitmap, &m_tbStyle); if (m_tbButtons[btindex] == (FAR NXWidgets::CImage *)0) { twmerr("ERROR: Failed to create image\n"); delete cbitmap; return false; } #endif // Configure the image, disabling drawing for now FAR NXWidgets::CImage *cimage = m_tbButtons[btindex]; cimage->setBorderless(true); cimage->disableDrawing(); cimage->setRaisesEvents(false); // Register to get events from the mouse clicks on the image cimage->addWidgetEventHandler(this); // Set the position of the Icon image in the toolbar struct nxgl_size_s windowSize; getWindowSize(&windowSize); // Center image vertically struct nxgl_point_s pos; pos.y = (m_tbHeight - cimage->getHeight()) / 2; // Pack on the left or right horizontally if (GToolBarInfo[btindex].rightSide) { m_tbRightX -= (cimage->getWidth() + CONFIG_TWM4NX_TOOLBAR_HSPACING); pos.x = m_tbRightX; } else { m_tbLeftX += CONFIG_TWM4NX_TOOLBAR_HSPACING; pos.x = m_tbLeftX; m_tbLeftX += cimage->getWidth(); } if (!cimage->moveTo(pos.x, pos.y)) { delete m_tbButtons[btindex]; m_tbButtons[btindex] = (FAR NXWidgets::CImage *)0; return false; } } return true; } /** * Add buttons and title widgets to the tool bar * * @param name The name to use for the toolbar title */ bool CWindow::createToolbarTitle(FAR const NXWidgets::CNxString &name) { // Is there a title? if (name.getLength() == 0) { // No.. then there is nothing to be done here return true; } // Vertical size of the title window is selected to fill the entire // toolbar. This really needs to be only the font height. However, // this improves the click-ability of the widget for small fonts. // // The Horizontal size of the title widget is determined by the available // space between m_tbLeftX and m_tbRightX. struct nxgl_size_s titleSize; titleSize.h = m_tbHeight; titleSize.w = m_tbRightX - m_tbLeftX - 2 * CONFIG_TWM4NX_TOOLBAR_HSPACING + 1; // Position the title. Packed to the left horizontally, positioned at the // top of the toolbar. struct nxgl_point_s titlePos; titlePos.x = m_tbLeftX + CONFIG_TWM4NX_TOOLBAR_HSPACING; titlePos.y = 0; // Get the Widget control instance from the toolbar window. This // will force all widget drawing to go to the toolbar. FAR NXWidgets:: CWidgetControl *control = m_toolbar->getWidgetControl(); if (control == (FAR NXWidgets:: CWidgetControl *)0) { // Should not fail return false; } // Create the toolbar title widget m_tbTitle = new NXWidgets::CLabel(control, titlePos.x, titlePos.y, titleSize.w, titleSize.h, name, &m_tbStyle); if (m_tbTitle == (FAR NXWidgets::CLabel *)0) { twmerr("ERROR: Failed to construct tool bar title widget\n"); return false; } // Configure the title widget FAR CFonts *fonts = m_twm4nx->getFonts(); FAR NXWidgets::CNxFont *titleFont = fonts->getTitleFont(); m_tbTitle->setFont(titleFont); m_tbTitle->setBorderless(true); m_tbTitle->disableDrawing(); m_tbTitle->setTextAlignmentHoriz(NXWidgets::CLabel::TEXT_ALIGNMENT_HORIZ_LEFT); m_tbTitle->setTextAlignmentVert(NXWidgets::CLabel::TEXT_ALIGNMENT_VERT_CENTER); m_tbTitle->setRaisesEvents(false); // Register to get events from the mouse clicks on the image m_tbTitle->addWidgetEventHandler(this); return true; } /** * After the toolbar was grabbed, it may be dragged then dropped, or it * may be simply "un-grabbed". Both cases are handled here. * * NOTE: Unlike the other event handlers, this does NOT override any * virtual event handling methods. It just combines some common event- * handling logic. * * @param x The mouse/touch X position. * @param y The mouse/touch y position. */ void CWindow::handleUngrabEvent(nxgl_coord_t x, nxgl_coord_t y) { // Generate the un-grab event struct SEventMsg msg; msg.eventID = EVENT_TOOLBAR_UNGRAB; msg.obj = (FAR void *)this; msg.pos.x = x; msg.pos.y = y; msg.context = EVENT_CONTEXT_TOOLBAR; msg.handler = (FAR void *)0; // 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", errno); } } /** * Handle a mouse click event. * * @param e The event data. */ void CWindow::handleClickEvent(const NXWidgets::CWidgetEventArgs &e) { // We are interested only the the press event on the title box and // only if we are not already dragging the window if (!m_dragging && m_tbTitle->isClicked()) { // Generate the event struct SEventMsg msg; msg.eventID = EVENT_TOOLBAR_GRAB; msg.obj = (FAR void *)this; msg.pos.x = e.getX(); msg.pos.y = e.getY(); msg.context = EVENT_CONTEXT_TOOLBAR; msg.handler = (FAR void *)0; // 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", errno); } } } /** * Override the virtual CWidgetEventHandler::handleReleaseEvent. This * event will fire when the title widget is released. isClicked() * will return false for the title widget. * * @param e The event data. */ void CWindow::handleReleaseEvent(const NXWidgets::CWidgetEventArgs &e) { // Handle the case where a release event was received, but the // window was not dragged. if (m_dragging && !m_tbTitle->isClicked()) { // A click with no drag should raise the window. m_nxWin->raise(); // Handle the non-drag drop event handleUngrabEvent(e.getX(), e.getY()); } } /** * Override the virtual CWidgetEventHandler::handleActionEvent. This * event will fire when the image is released but before it has been * has been drawn. isClicked() will return true for the appropriate * images. * * @param e The event data. */ void CWindow::handleActionEvent(const NXWidgets::CWidgetEventArgs &e) { // We are are interested in the pre-release event for any of the // toolbar buttons. for (int btindex = 0; btindex < NTOOLBAR_BUTTONS; btindex++) { // Check if this button is omitted by toolbar customizations or if the // button is temporarily disabled. if (((m_tbFlags | m_tbDisables) & (1 << btindex)) != 0) { continue; } // Check if the widget is clicked if (m_tbButtons[btindex] != (FAR NXWidgets::CImage *)0 && m_tbButtons[btindex]->isClicked()) { // Yes.. generate the event struct SEventMsg msg; msg.eventID = GToolBarInfo[btindex].event; msg.obj = (FAR void *)this; msg.pos.x = e.getX(); msg.pos.y = e.getY(); msg.context = EVENT_CONTEXT_TOOLBAR; msg.handler = (FAR void *)0; // 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", errno); } } } } /** * This function is called when there is any movement of the mouse or * touch position that would indicate that the object is being moved. * * This function overrides the virtual IEventTap::moveEvent method. * * @param pos The current mouse/touch X/Y position in toolbar relative * coordinates. * @return True: if the drag event was processed; false it was * ignored. The event should be ignored if there is not actually * a drag event in progress */ bool CWindow::moveEvent(FAR const struct nxgl_point_s &pos, uintptr_t arg) { twminfo("m_dragging=%u pos=(%d,%d)\n", m_dragging, pos.x, pos.y); // We are interested only the drag event while we are in the dragging // state. if (m_dragging) { // arg == 0 means that this a toolbar event vs s main window event. // Since the position is relative in both cases, we need a fix-up in // the height to keep the same toolbar relative position in all cases. nxgl_coord_t yIncr = ((arg == 0) ? 0 : m_tbHeight); // Generate the event struct SEventMsg msg; msg.eventID = EVENT_WINDOW_DRAG; msg.obj = (FAR void *)this; msg.pos.x = pos.x; msg.pos.y = pos.y + yIncr; msg.context = EVENT_CONTEXT_TOOLBAR; msg.handler = (FAR void *)0; // 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", errno); } return true; } return false; } /** * This function is called if the mouse left button is released or * if the touchscreen touch is lost. This indicates that the * dragging sequence is complete. * * This function overrides the virtual IEventTap::dropEvent method. * * @param pos The last mouse/touch X/Y position in toolbar relative * coordinates. * @return True: If the drag event was processed; false it was * ignored. The event should be ignored if there is not actually * a drag event in progress */ bool CWindow::dropEvent(FAR const struct nxgl_point_s &pos, uintptr_t arg) { twminfo("m_dragging=%u pos=(%d,%d)\n", m_dragging, pos.x, pos.y); // We are interested only the the drag drop event on the title box while we // are in the dragging state. // // When the Drop Event is received, both isClicked and isBeingDragged() // will return false. It is sufficient to verify that the isClicked() is // not true to exit the drag. if (m_dragging) { // Yes.. handle the drop event // arg == 0 means that this a tooolbar event vs s main window event. // Since the position is relative in both cases, we need a fix-up in // the height to keep the same toolbar relative position in all cases. nxgl_coord_t yIncr = ((arg == 0) ? 0 : m_tbHeight); handleUngrabEvent(pos.x, pos.y + yIncr); return true; } return false; } /** * Is dragging enabled? * * @param arg The user-argument provided that accompanies the callback * @return True: If the dragging is enabled. */ bool CWindow::isActive(uintptr_t arg) { return m_clicked; } /** * Enable/disable dragging * * True is provided when (1) isActive() returns false, but (2) a mouse * report with a left-click is received. * False is provided when (1) isActive() returns true, but (2) a mouse * report without a left-click is received. * * In the latter is redundant since dropEvent() will be called immediately * afterward. * * @param pos. The mouse position at the time of the click or release * @param enable. True: Enable dragging * @param arg The user-argument provided that accompanies the callback */ void CWindow::enableMovement(FAR const struct nxgl_point_s &pos, bool enable, uintptr_t arg) { m_clicked = enable; } /** * Handle the TOOLBAR_GRAB event. That corresponds to a left * mouse click on the title widget in the toolbar * * @param eventmsg. The received NxWidget event message. * @return True if the message was properly handled. false is * return on any failure. */ bool CWindow::toolbarGrab(FAR struct SEventMsg *eventmsg) { twminfo("GRAB (%d,%d)\n", eventmsg->pos.x, eventmsg->pos.y); // Override application mouse events while dragging. This is necessary to // to handle cases where the drag that starts in the toolbar is moved // into the application window area. struct SAppEvents events; events.eventObj = (FAR void *)this; events.redrawEvent = EVENT_SYSTEM_NOP; events.resizeEvent = EVENT_SYSTEM_NOP; events.mouseEvent = EVENT_TOOLBAR_XYINPUT; events.kbdEvent = EVENT_SYSTEM_NOP; events.closeEvent = m_appEvents.closeEvent; events.deleteEvent = m_appEvents.deleteEvent; bool success = m_windowEvent->configureEvents(events); if (!success) { return false; } // Promote the window to a modal window m_modal = true; m_nxWin->modal(true); // Indicate that dragging has started. m_dragging = true; // Get the frame position. struct nxgl_point_s framePos; getFramePosition(&framePos); twminfo("Position (%d,%d)\n", framePos.x, framePos.y); // Save the toolbar-relative mouse position in order to detect the amount // of movement in the next drag event. m_dragPos.x = eventmsg->pos.x; m_dragPos.y = eventmsg->pos.y; #ifdef CONFIG_TWM4NX_MOUSE // Select the grab cursor image m_twm4nx->setCursorImage(&CONFIG_TWM4NX_GBCURSOR_IMAGE); // Remember the grab cursor size m_dragCSize.w = CONFIG_TWM4NX_GBCURSOR_IMAGE.size.w; m_dragCSize.h = CONFIG_TWM4NX_GBCURSOR_IMAGE.size.h; #else // Fudge a value for the case where we are using a touchscreen. m_dragCSize.w = 16; m_dragCSize.h = 16; #endif return true; } /** * Handle the WINDOW_DRAG event. That corresponds to a mouse * movement when the window is in a grabbed state. * * @param eventmsg. The received NxWidget event message. * @return True if the message was properly handled. false is * return on any failure. */ bool CWindow::windowDrag(FAR struct SEventMsg *eventmsg) { twminfo("DRAG (%d,%d)\n", eventmsg->pos.x, eventmsg->pos.y); if (m_dragging) { // The coordinates in the eventmsg are relative to the origin // of the toolbar. // Get the current (old) frame position struct nxgl_point_s oldPos; if (!getFramePosition(&oldPos)) { twmerr("ERROR: getFramePosition() failed\n") ; return false; } // We want to set the new frame position so that the new // mouse position is at the same relative position as it // was when the toolbar title was first grabbed. struct nxgl_point_s newPos; newPos.x = oldPos.x + eventmsg->pos.x - m_dragPos.x; newPos.y = oldPos.y + eventmsg->pos.y - m_dragPos.y; // Save the new mouse position m_dragPos.x = eventmsg->pos.x; m_dragPos.y = eventmsg->pos.y; // Keep the window on the display (at least enough of it so that we // can still grab it) struct nxgl_size_s displaySize; m_twm4nx->getDisplaySize(&displaySize); if (newPos.x < 0) { newPos.x = 0; } else if (newPos.x + m_dragCSize.w > displaySize.w) { newPos.x = displaySize.w - m_dragCSize.w; } if (newPos.y < 0) { newPos.y = 0; } else if (newPos.y + m_dragCSize.h > displaySize.h) { newPos.y = displaySize.h - m_dragCSize.h; } // Set the new window position if it has changed twminfo("Position (%d,%d)->(%d,%d)\n", oldPos.x, oldPos.y, newPos.x, newPos.y); if (newPos.x != oldPos.x || newPos.y != oldPos.y) { if (!setFramePosition(&newPos)) { twmerr("ERROR: setFramePosition failed\n"); return false; } m_nxWin->synchronize(); } return true; } return false; } /** * Handle the TOOLBAR_UNGRAB event. The corresponds to a mouse * left button release while in the grabbed state * * @param eventmsg. The received NxWidget event message in window relative * coordinates. * @return True if the message was properly handled. false is * return on any failure. */ bool CWindow::toolbarUngrab(FAR struct SEventMsg *eventmsg) { twminfo("UNGRAB (%d,%d)\n", eventmsg->pos.x, eventmsg->pos.y); // One last position update if (!windowDrag(eventmsg)) { return false; } // Indicate no longer dragging m_dragging = false; // No longer modal m_modal = false; m_nxWin->modal(false); #ifdef CONFIG_TWM4NX_MOUSE // Restore the normal cursor image m_twm4nx->setCursorImage(&CONFIG_TWM4NX_CURSOR_IMAGE); #endif // Restore normal application event handling. return m_windowEvent->configureEvents(m_appEvents); } /** * Free windows from Twm4Nx window structure. */ void CWindow::cleanup(void) { // Close the NxWidget event message queue if (m_eventq != (mqd_t)-1) { mq_close(m_eventq); m_eventq = (mqd_t)-1; } // Delete toolbar images for (int btindex = 0; btindex < NTOOLBAR_BUTTONS; btindex++) { FAR NXWidgets::CImage *cimage = m_tbButtons[btindex]; if (cimage != (NXWidgets::CImage *)0) { delete m_tbButtons[btindex]; m_tbButtons[btindex] = (NXWidgets::CImage *)0; } } if (m_tbTitle != (FAR NXWidgets::CLabel *)0) { delete m_tbTitle; m_tbTitle = (FAR NXWidgets::CLabel *)0; } // Delete the toolbar if (m_toolbar != (FAR NXWidgets::CNxToolbar *)0) { delete m_toolbar; m_toolbar = (FAR NXWidgets::CNxToolbar *)0; } // Delete the window if (m_nxWin != (FAR NXWidgets::CNxTkWindow *)0) { delete m_nxWin; m_nxWin = (FAR NXWidgets::CNxTkWindow *)0; } // Delete the Icon if (m_iconWidget != (FAR CIconWidget *)0) { delete m_iconWidget; m_iconWidget = (FAR CIconWidget *)0; } if (m_iconBitMap != (FAR NXWidgets::CRlePaletteBitmap *)0) { delete m_iconBitMap; m_iconBitMap = (FAR NXWidgets::CRlePaletteBitmap *)0; } } ///////////////////////////////////////////////////////////////////////////// // Public Functions ///////////////////////////////////////////////////////////////////////////// namespace Twm4Nx { /** * Return the minimum width of a toolbar window. If the window is * resized smaller than this width, then the items in the toolbar will * overlap. * * @param twm4nx The Twm4Nx session object * @param title The window title string * @param flags Window toolbar properties * @return The minimum recommended window width. */ nxgl_coord_t minimumToolbarWidth(FAR CTwm4Nx *twm4nx, FAR const NXWidgets::CNxString &title, uint8_t flags) { // Get the width of the title string FAR CFonts *fonts = twm4nx->getFonts(); FAR NXWidgets::CNxFont *titleFont = fonts->getTitleFont(); nxgl_coord_t tbWidth = titleFont->getStringWidth(title) + CONFIG_TWM4NX_TOOLBAR_HSPACING; for (int btindex = 0; btindex < NTOOLBAR_BUTTONS; btindex++) { // Check if this button is omitted by toolbar customizations if ((flags & (1 << btindex)) == 0) { // Add the button image width tbWidth += GToolBarInfo[btindex].bitmap->width + CONFIG_TWM4NX_TOOLBAR_HSPACING; } } return tbWidth; } }