nuttx-apps/graphics/twm4nx/src/cwindowfactory.cxx

672 lines
19 KiB
C++

/////////////////////////////////////////////////////////////////////////////
// apps/graphics/twm4nx/src/cwindowfactory.cxx
// A collection of Window Helpers: Add a new window, put the titlebar and
// other stuff around the window
//
// 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.
// Although not listed as a copyright holder, thanks and recognition need
// to go to Tom LaStrange, the original author of TWM.
//
// 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 <cstdbool>
#include <cassert>
#include <mqueue.h>
#include "graphics/nxwidgets/cwidgetcontrol.hxx"
#include "graphics/nxwidgets/cnxwindow.hxx"
#include "graphics/nxwidgets/cnxtkwindow.hxx"
#include "graphics/nxwidgets/cnxtoolbar.hxx"
#include "graphics/twm4nx/twm4nx_config.hxx"
#include "graphics/twm4nx/ctwm4nx.hxx"
#include "graphics/twm4nx/cbackground.hxx"
#include "graphics/twm4nx/cwindow.hxx"
#include "graphics/twm4nx/cwindowfactory.hxx"
#include "graphics/twm4nx/ciconmgr.hxx"
#include "graphics/twm4nx/cmainmenu.hxx"
#include "graphics/twm4nx/twm4nx_events.hxx"
/////////////////////////////////////////////////////////////////////////////
// CWindowFactory Implementation
/////////////////////////////////////////////////////////////////////////////
using namespace Twm4Nx;
/**
* CWindowFactory Constructor
*
* @param twm4nx. Twm4Nx session
*/
CWindowFactory::CWindowFactory(FAR CTwm4Nx *twm4nx)
{
m_twm4nx = twm4nx; // Cached copy of the Twm4Nx session object
m_windowHead = (FAR struct SWindow *)0; // List of all Windows
// Set up the position where we will create the initial window
m_winpos.x = 50;
m_winpos.y = 50;
}
/**
* CWindowFactory Destructor
*/
CWindowFactory::~CWindowFactory(void)
{
}
/**
* Add Icon Manager menu items to the Main menu. This is really part
* of the instance initialization, but cannot be executed until the
* Main Menu logic is ready.
*
* @return True on success
*/
bool CWindowFactory::addMenuItems(void)
{
// Add the Icon Manager entry to the Main Menu. This provides a quick
// way to de-iconfigy all windows and show a clean desktop.
FAR CMainMenu *cmain = m_twm4nx->getMainMenu();
if (!cmain->addApplication(&m_desktopItem))
{
twmerr("ERROR: Failed to add to the Main Menu\n");
return false;
}
return true;
}
/**
* Create a new window and add it to the window list.
*
* 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 window name
* @param sbitmap The Icon bitmap
* @param iconMgr Pointer to icon manager instance
* @param flags Toolbar customizations see WFLAGS_NO_* definitions
* @return Reference to the allocated CWindow instance
*/
FAR CWindow *
CWindowFactory::createWindow(FAR NXWidgets::CNxString &name,
FAR const struct NXWidgets::SRlePaletteBitmap *sbitmap,
FAR CIconMgr *iconMgr, uint8_t flags)
{
twminfo("flags=%02x\n", flags);
// Allocate a container for the Twm4NX window
FAR struct SWindow *win =
(FAR struct SWindow *)std::zalloc(sizeof(struct SWindow));
if (win == (FAR struct SWindow *)0)
{
twmerr("ERROR: Unable to allocate memory to manage window\n");
return (FAR CWindow *)0;
}
// Create and initialize the window itself
win->cwin = new CWindow(m_twm4nx);
if (win->cwin == (FAR CWindow *)0)
{
twmerr("ERROR: Failed to create CWindow\n");
std::free(win);
return (FAR CWindow *)0;
}
// Place the window at a random position
// Default size: Try a one quarter of the display.
struct nxgl_size_s displaySize;
m_twm4nx->getDisplaySize(&displaySize);
struct nxgl_size_s winsize;
winsize.w = displaySize.w / 2;
winsize.h = displaySize.h / 2;
if ((m_winpos.x + winsize.w) > displaySize.w)
{
m_winpos.x = 50;
}
if ((m_winpos.x + winsize.w) > (displaySize.w - 16))
{
winsize.w = displaySize.w - m_winpos.x - 16;
}
if ((m_winpos.y + winsize.h) > displaySize.h)
{
m_winpos.y = 50;
}
if ((m_winpos.y + winsize.h) > (displaySize.h - 16))
{
winsize.h = displaySize.h - m_winpos.y - 16;
}
twminfo("Position window at (%d,%d), size (%d,%d)\n",
m_winpos.x, m_winpos.y, winsize.w, winsize.h);
if (!win->cwin->initialize(name, &m_winpos, &winsize, sbitmap,
iconMgr, flags))
{
twmerr("ERROR: Failed to initialize CWindow\n");
delete win->cwin;
std::free(win);
return (FAR CWindow *)0;
}
// Update the position for the next window
m_winpos.x += 30;
m_winpos.y += 30;
// Add the window into the window list
addWindowContainer(win);
// Add the window to the icon manager if it is a regular window (i.e., if
// it is not the Icon Manager Window and it is not a Menu Window)
if (!WFLAGS_IS_ICONMGR(flags) && !WFLAGS_IS_MENU(flags))
{
if (iconMgr == (FAR CIconMgr *)0)
{
iconMgr = m_twm4nx->getIconMgr();
}
iconMgr->addWindow(win->cwin);
}
// Return the contained window
return win->cwin;
}
/**
* Handle the EVENT_WINDOW_DELETE event. The logic sequence is as
* follows:
*
* 1. The TERMINATE button in pressed in the Window Toolbar and
* CWindow::handleActionEvent() catches the button event on the
* event listener thread and generates the EVENT_WINDOW_TERMINATE
* 2. CWindows::event receives the widget event, EVENT_WINDOW_TERMINATE,
* on the Twm4NX manin threadand requests to halt the NX Server
* messages queues.
* 3. when server responds, the CwindowsEvent::handleBlockedEvent
* generates the EVENT_WINDOW_DELETE which is caught by
* CWindows::event() and which, in turn calls this function.
*
* @param cwin The CWindow instance. This will be deleted and its
* associated container will be freed.
*/
void CWindowFactory::destroyWindow(FAR CWindow *cwin)
{
// Find the container of the window
FAR struct SWindow *win = findWindow(cwin);
if (win == (FAR struct SWindow *)0)
{
// This should not happen.. worthy of an assertion
twmerr("ERROR: Failed to find CWindow container\n");
}
else
{
// Remove the window container from the window list
removeWindowContainer(win);
}
// Remove the window from the Icon Manager
// Add the window to the icon manager
CIconMgr *iconmgr = cwin->getIconMgr();
DEBUGASSERT(iconmgr != (CIconMgr *)0);
iconmgr->removeWindow(cwin);
// Delete the contained CWindow instance
delete cwin;
// And, finally, free the CWindow container
free(win);
}
/**
* Pick a position for a new Icon on the desktop. Tries to avoid
* collisions with other Icons and reserved areas on the background
*
* @param cwin The window being iconified.
* @param defPos The default position to use if there is no free
* region on the desktop.
* @param iconPos The selected Icon position. Might be the same as
* the default position.
* @return True is returned on success
*/
bool CWindowFactory::placeIcon(FAR CWindow *cwin,
FAR const struct nxgl_point_s &defPos,
FAR struct nxgl_point_s &iconPos)
{
// Does this window have an Icon?
bool success = false;
if (cwin->hasIcon())
{
// Get the size of the Display (i.e., the size of the background)
struct nxgl_size_s displaySize;
m_twm4nx->getDisplaySize(&displaySize);
// Get the size of the Icon
struct nxgl_size_s iconSize;
cwin->getIconWidgetSize(iconSize);
// Get the background instance
FAR CBackground *backgd = m_twm4nx->getBackground();
// Search for a free region. Start at the at the left size
struct nxgl_point_s tmpPos;
tmpPos.x = CONFIG_TWM4NX_ICON_HSPACING;
// Try each possible horizontal position until we find a free location or
// until we run out of positions to test
nxgl_coord_t iconWidth;
for (; tmpPos.x < (displaySize.w - iconSize.w); tmpPos.x += iconWidth)
{
// Start at the top of the next column
tmpPos.y = CONFIG_TWM4NX_ICON_VSPACING;
// Try each possible vertical position until we find a free
// location or until we run out of positions to test
iconWidth = 0;
nxgl_coord_t iconHeight;
for (; tmpPos.y < (displaySize.h - iconSize.h); tmpPos.y += iconHeight)
{
// Create a bounding box at this position
struct nxgl_rect_s iconBounds;
iconBounds.pt1.x = tmpPos.x;
iconBounds.pt1.y = tmpPos.y;
iconBounds.pt2.x = tmpPos.x + iconSize.w - 1;
iconBounds.pt2.y = tmpPos.y + iconSize.h - 1;
// Check if this box intersects any reserved region on the
// background.
struct nxgl_rect_s collision;
if (backgd->checkCollision(iconBounds, collision))
{
// Yes.. Set the width to some small, arbitrary non-zero
// value. This is because the actual colliding object may
// be quite wide. But we may still be able to position
// icons above or below the object.
if (iconWidth < 20)
{
iconWidth = 20;
}
// and reset the vertical search position to move past
// the collision. This may terminate the inner loop.
iconHeight = collision.pt2.y - tmpPos.y +
CONFIG_TWM4NX_ICON_VSPACING + 1;
}
// No.. check if some other icon is already occupying this
// position
else if (checkCollision(cwin, iconBounds, collision))
{
// Yes.. We need to keep track of the widest icon for the
// case where we move to the next column.
nxgl_coord_t tmpWidth = collision.pt2.x - tmpPos.x + 1;
if (tmpWidth > iconWidth)
{
iconWidth = tmpWidth;
}
// And reset the vertical search position to move past the
// collision. This may terminate the inner loop.
iconHeight = collision.pt2.y - tmpPos.y +
CONFIG_TWM4NX_ICON_VSPACING + 1;
}
// No collision.. place the icon at this position
else
{
iconPos.x = tmpPos.x;
iconPos.y = tmpPos.y;
return true;
}
}
// Add some configurable spacing to the maximum width of this
// column. The next column will skip 'right' by this width.
iconWidth += CONFIG_TWM4NX_ICON_HSPACING;
}
// No free region found, use the user provided default
iconPos.x = defPos.x;
iconPos.y = defPos.y;
success = true;
}
return success;
}
/**
* Redraw icons. The icons are drawn on the background window. When
* the background window receives a redraw request, it will call this
* method in order to redraw any effected icons drawn in the
* background.
*
* @param nxRect The region in the background to be redrawn
*/
void CWindowFactory::redrawIcons(FAR const nxgl_rect_s *nxRect)
{
twminfo("Redrawing...\n");
// Try each window
for (FAR struct SWindow *win = m_windowHead;
win != (FAR struct SWindow *)0;
win = win->flink)
{
// Check if the window has an icon and it is in the iconified state
FAR CWindow *cwin = win->cwin;
if (cwin->hasIcon() && cwin->isIconified())
{
// Yes.. Create a bounding box for the icon
struct nxgl_size_s iconSize;
cwin->getIconWidgetSize(iconSize);
struct nxgl_point_s iconPos;
cwin->getIconWidgetPosition(iconPos);
struct nxgl_rect_s iconBounds;
iconBounds.pt1.x = iconPos.x;
iconBounds.pt1.y = iconPos.y;
iconBounds.pt2.x = iconPos.x + iconSize.w - 1;
iconBounds.pt2.y = iconPos.y + iconSize.h - 1;
// Does anything within bounding box need to be redrawn?
struct nxgl_rect_s intersection;
nxgl_rectintersect(&intersection, nxRect, &iconBounds);
if (!nxgl_nullrect(&intersection))
{
// Yes.. Redraw the icon (or a portion of the icon)
twminfo("Redraw icon\n");
cwin->redrawIcon();
}
}
}
}
/**
* Check if the icon within iconBounds collides with any other icon on the
* desktop.
*
* @param cwin The window containing the Icon of interest
* @param iconBounds The candidate Icon bounding box
* @param collision The bounding box of the icon that the candidate collides
* with
* @return Returns true if there is a collision
*/
bool CWindowFactory::checkCollision(FAR CWindow *cwin,
FAR const struct nxgl_rect_s &iconBounds,
FAR struct nxgl_rect_s &collision)
{
// Try every window
for (FAR struct SWindow *win = m_windowHead;
win != (FAR struct SWindow *)0;
win = win->flink)
{
// Ignore 'this' window, any windows that are not iconified, and any
// windows that have no icons.
if (win->cwin != cwin && win->cwin->hasIcon() &&
win->cwin->isIconified())
{
// Create a bounding box for the icon
struct nxgl_size_s iconSize;
win->cwin->getIconWidgetSize(iconSize);
struct nxgl_point_s iconPos;
win->cwin->getIconWidgetPosition(iconPos);
collision.pt1.x = iconPos.x;
collision.pt1.y = iconPos.y;
collision.pt2.x = iconPos.x + iconSize.w - 1;
collision.pt2.y = iconPos.y + iconSize.h - 1;
// Return true if there is an intersection
if (nxgl_intersecting(&iconBounds, &collision))
{
return true;
}
}
}
// No collision
return false;
}
/**
* 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 CWindowFactory::event(FAR struct SEventMsg *eventmsg)
{
twminfo("eventID: %d\n", eventmsg->eventID);
bool success = true;
switch (eventmsg->eventID)
{
case EVENT_TOOLBAR_XYINPUT: // Poll for toolbar mouse/touch events
{
FAR struct SXyInputEventMsg *nxmsg =
(FAR struct SXyInputEventMsg *)eventmsg;
FAR CWindow *cwin = (FAR CWindow *)nxmsg->obj;
DEBUGASSERT(cwin != (FAR CWindow *)0);
success = cwin->pollToolbarEvents();
}
break;
case EVENT_WINDOW_DESKTOP: // Show the desktop
success = showDesktop();
break;
// Forward the event to the appropriate window
default: // All other window messages
{
FAR CWindow *cwin = (FAR CWindow *)eventmsg->obj;
DEBUGASSERT(cwin != (FAR CWindow *)0);
success = cwin->event(eventmsg);
}
break;
}
return success;
}
/**
* Add a window container to the window list.
*
* @param win. The window container to be added to the list.
*/
void CWindowFactory::addWindowContainer(FAR struct SWindow *win)
{
win->blink = (FAR struct SWindow *)0;
win->flink = m_windowHead;
if (m_windowHead != (FAR struct SWindow *)0)
{
m_windowHead->blink = win;
}
m_windowHead = win;
}
/**
* Remove a window container from the window list.
*
* @param win. The window container to be removed from the list.
*/
void CWindowFactory::removeWindowContainer(FAR struct SWindow *win)
{
FAR struct SWindow *prev = win->blink;
FAR struct SWindow *next = win->flink;
if (prev == (FAR struct SWindow *)0)
{
m_windowHead = next;
}
else
{
prev->flink = next;
}
if (next != (FAR struct SWindow *)0)
{
next->blink = prev;
}
win->flink = NULL;
win->blink = NULL;
}
/**
* Find the window container that contains the specified window.
*
* @param cwin. The window whose container is needed.
* @return On success, the container of the specific window is returned;
* NULL is returned on failure.
*/
FAR struct SWindow *CWindowFactory::findWindow(FAR CWindow *cwin)
{
for (FAR struct SWindow *win = m_windowHead;
win != (FAR struct SWindow *)0;
win = win->flink)
{
if (win->cwin == cwin)
{
return win;
}
}
return (FAR struct SWindow *)0;
}
/**
* This is the function that responds to the EVENT_WINDOW_DESKTOP. It
* iconifies all windows so that the desktop is visible.
*/
bool CWindowFactory::showDesktop(void)
{
// Add the Icon Manager entry to the Main Menu. This provides a quick
// way to de-iconfigy all windows and show a clean desktop.
for (FAR struct SWindow *win = m_windowHead;
win != (FAR struct SWindow *)0;
win = win->flink)
{
// Iconify everything: Application windows, the Icon Manage, all
// Menus, etc.
win->cwin->iconify();
}
return true;
}