/****************************************************************************
* NxWidgets/libnxwidgets/src/cnxwidget.cxx
*
* Copyright (C) 2012 Gregory Nutt. All rights reserved.
* Author: Gregory Nutt <gnutt@nuttx.org>
*
* 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, NxWidgets, nor the names of its contributors
* me 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.
*
****************************************************************************
*
* Portions of this package derive from Woopsi (http://woopsi.org/) and
* portions are original efforts. It is difficult to determine at this
* point what parts are original efforts and which parts derive from Woopsi.
* However, in any event, the work of Antony Dzeryn will be acknowledged
* in most NxWidget files. Thanks Antony!
*
* Copyright (c) 2007-2011, Antony Dzeryn
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the names "Woopsi", "Simian Zombie" 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 Antony Dzeryn ``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 Antony Dzeryn 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 <cstdint>
#include <cstdbool>
#include "cnxwidget.hxx"
#include "cgraphicsport.hxx"
#include "cwidgeteventhandler.hxx"
#include "cnxfont.hxx"
#include "cwidgetstyle.hxx"
#include "cwidgeteventargs.hxx"
#include "cwidgetcontrol.hxx"
#include "singletons.hxx"
/****************************************************************************
* Pre-Processor Definitions
****************************************************************************/
#define DOUBLE_CLICK_BOUNDS 10
/****************************************************************************
* Method Implementations
****************************************************************************/
using namespace NXWidgets;
/**
* Constructor.
*
* @param pWidgetControl The controlling widget for the display
* @param x The x coordinate of the widget.
* @param y The y coordinate of the widget.
* @param width The width of the widget.
* @param height The height of the widget.
* @param flags Bitmask specifying some set-up values for the widget.
* @param style The style that the button should use. If this is not
* specified, the button will use the global default widget
* style.
* @see WidgetFlagType.
*/
CNxWidget::CNxWidget(CWidgetControl *pWidgetControl,
nxgl_coord_t x, nxgl_coord_t y,
nxgl_coord_t width, nxgl_coord_t height,
uint32_t flags, const CWidgetStyle *style)
{
// Save the controlling widget
m_widgetControl = pWidgetControl;
// Set properties from parameters. If this is a child widget, then
// this coordinates are relative to the parent widget.
m_rect.setX(x);
m_rect.setY(y);
m_rect.setWidth(width);
m_rect.setHeight(height);
// Do we need to fetch the default style?
if (style == (CWidgetStyle *)NULL)
{
// Get the style from the controlling widget. This allows different
// widgets within a window to have the same style, unique to the window.
pWidgetControl->getWidgetStyle(&m_style);
}
else
{
// Use specified style
useWidgetStyle(style);
}
// Add ourself to the list of controlled widgets
pWidgetControl->addControlledWidget(this);
// Mask flags against bitmasks and logical NOT twice to obtain boolean values
m_flags.borderless = (!(!(flags & WIDGET_BORDERLESS)));
m_flags.draggable = (!(!(flags & WIDGET_DRAGGABLE)));
m_flags.permeable = (!(!(flags & WIDGET_PERMEABLE)));
m_flags.doubleClickable = (!(!(flags & WIDGET_DOUBLE_CLICKABLE)));
// Dragging values
m_grabPointX = 0;
m_grabPointY = 0;
m_newX = 0;
m_newY = 0;
// Set initial flag values
m_flags.clicked = false;
m_flags.dragging = false;
m_flags.hasFocus = false;
m_flags.deleted = false;
m_flags.drawingEnabled = false;
m_flags.enabled = true;
m_flags.erased = true;
m_flags.visibleRegionCacheInvalid = true;
m_flags.hidden = false;
// Set hierarchy pointers
m_parent = (CNxWidget *)NULL;
m_focusedChild = (CNxWidget *)NULL;
// Double-click
clock_gettime(CLOCK_REALTIME, &m_lastClickTime);
m_lastClickX = 0;
m_lastClickY = 0;
m_doubleClickBounds = DOUBLE_CLICK_BOUNDS;
// Set border size to 1 line
m_borderSize.top = 1;
m_borderSize.right = 1;
m_borderSize.bottom = 1;
m_borderSize.left = 1;
m_widgetEventHandlers = new CWidgetEventHandlerList(this);
}
/**
* Destructor.
*/
CNxWidget::~CNxWidget(void)
{
if (!m_flags.deleted)
{
m_flags.deleted = true;
// Unset the clicked pointer if necessary
if (m_widgetControl->getClickedWidget() == this)
{
m_widgetControl->setClickedWidget((CNxWidget *)NULL);
}
}
if (m_parent != (CNxWidget *)NULL)
{
m_parent->removeChild(this);
}
// Delete children
while (m_children.size() > 0)
{
m_children[0]->destroy();
}
// Remove ourselve from the controlled widget list
m_widgetControl->removeControlledWidget(this);
// Delete instances. NOTE that we do not delete the controlling
// widget. It persists until the window is closed.
delete m_widgetEventHandlers;
}
/**
* Get the x coordinate of the widget in "Widget space".
*
* @return Widget space x coordinate.
*/
nxgl_coord_t CNxWidget::getX(void) const
{
if (m_parent != (CNxWidget *)NULL)
{
return m_parent->getX() + m_rect.getX();
}
return m_rect.getX();
}
/**
* Get the y coordinate of the widget in "Widget space".
*
* @return Widget space y coordinate.
*/
nxgl_coord_t CNxWidget::getY(void) const
{
if (m_parent != (CNxWidget *)NULL)
{
return m_parent->getY() + m_rect.getY();
}
return m_rect.getY();
}
/**
* Get the x coordinate of the widget relative to its parent.
*
* @return Parent-space x coordinate.
*/
nxgl_coord_t CNxWidget::getRelativeX(void) const
{
return m_rect.getX();
}
/**
* Get the y coordinate of the widget relative to its parent.
*
* @return Parent-space y coordinate.
*/
nxgl_coord_t CNxWidget::getRelativeY(void) const
{
return m_rect.getY();
}
/**
* Has the widget been marked for deletion? This function recurses up the widget
* hierarchy and only returns true if all of the widgets in the ancestor
* chain are not deleted.
*
* Widgets marked for deletion are automatically deleted and should not be
* interacted with.
*
* @return True if marked for deletion.
*/
bool CNxWidget::isDeleted(void) const
{
if (m_parent != (CNxWidget *)NULL)
{
if (m_parent->isDeleted())
{
return true;
}
}
return m_flags.deleted;
}
/**
* Is the widget allowed to draw? This function recurses up the widget
* hierarchy and only returns true if all of the widgets in the ancestor
* chain are visible.
*
* @return True if drawing is enabled.
*/
bool CNxWidget::isDrawingEnabled(void) const
{
if (m_parent != (CNxWidget *)NULL)
{
if (m_parent->isDrawingEnabled())
{
// Drawing is enabled if the widget is drawable and not deleted
return (m_flags.drawingEnabled && (!m_flags.deleted) && (!m_flags.hidden));
}
}
else
{
return (m_flags.drawingEnabled && (!m_flags.deleted) && (!m_flags.hidden));
}
return false;
}
/**
* Is the widget hidden? This function recurses up the widget
* hierarchy and returns true if any of the widgets in the ancestor
* chain are hidden.
*
* @return True if hidden.
*/
bool CNxWidget::isHidden(void) const
{
if (m_parent != (CNxWidget *)NULL)
{
if (!m_parent->isHidden())
{
// Hidden if the widget is deleted or hidden
return (m_flags.deleted || m_flags.hidden);
}
}
else
{
return (m_flags.deleted || m_flags.hidden);
}
return true;
}
/**
* Is the widget enabled? This function recurses up the widget
* hierarchy and only returns true if all of the widgets in the ancestor
* chain are enabled.
*
* @return True if enabled.
*/
bool CNxWidget::isEnabled() const
{
if (m_parent != (CNxWidget *)NULL)
{
if (m_parent->isEnabled())
{
// Enabled if the widget is enabled, not deleted and not hidden
return (m_flags.enabled && (!m_flags.deleted) && (!m_flags.hidden));
}
}
else
{
return (m_flags.enabled && (!m_flags.deleted) && (!m_flags.hidden));
}
return false;
}
/**
* Insert the dimensions that this widget wants to have into the rect
* passed in as a parameter. All coordinates are relative to the widget's
* parent.
*
* @param rect Reference to a rect to populate with data.
*/
void CNxWidget::getPreferredDimensions(CRect &rect) const
{
rect = m_rect;
}
/**
* Insert the properties of the space within this widget that is available
* for children into the rect passed in as a parameter.
* All coordinates are relative to this widget.
*
* @param rect Reference to a rect to populate with data.
*/
void CNxWidget::getClientRect(CRect &rect) const
{
if (m_flags.borderless)
{
rect.setX(0);
rect.setY(0);
rect.setWidth(getWidth());
rect.setHeight(getHeight());
}
else
{
rect.setX(m_borderSize.left);
rect.setY(m_borderSize.top);
rect.setWidth(getWidth() - (m_borderSize.left + m_borderSize.right));
rect.setHeight(getHeight() - (m_borderSize.top + m_borderSize.bottom));
}
}
/**
* Insert the properties of the space within this widget that is
* available for children into the rect passed in as a parameter.
* Identical to getClientRect() except that all coordinates are
* absolute positions within the window.
*
* @param rect Reference to a rect to populate with data.
*/
void CNxWidget::getRect(CRect &rect) const
{
getClientRect(rect);
rect.setX(rect.getX() + getX());
rect.setY(rect.getY() + getY());
}
/**
* Sets this widget's border state.
*
* @param isBorderless The border state.
*/
void CNxWidget::setBorderless(bool borderless)
{
m_flags.borderless = borderless;
}
/**
* Sets the font.
*
* @param font A pointer to the font to use.
*/
void CNxWidget::setFont(CNxFont *font)
{
m_style.font = font;
}
/**
* Draws the visible regions of the widget and the widget's child widgets.
*/
void CNxWidget::redraw(void)
{
if (isDrawingEnabled())
{
// Get the graphics port needed to draw on this window
CGraphicsPort *port = m_widgetControl->getGraphicsPort();
// Draw the Widget
drawBorder(port);
drawContents(port);
// Remember that the widget is no longer erased
m_flags.erased = false;
// Draw the children of the widget
drawChildren();
}
}
/**
* Enables the widget.
*
* @return True if the widget was enabled.
*/
bool CNxWidget::enable(void)
{
if (!m_flags.enabled)
{
m_flags.enabled = true;
onEnable();
redraw();
m_widgetEventHandlers->raiseEnableEvent();
return true;
}
return false;
}
/**
* Disabled the widget.
*
* @return True if the widget was disabled.
*/
bool CNxWidget::disable(void)
{
if (m_flags.enabled)
{
m_flags.enabled = false;
onDisable();
redraw();
m_widgetEventHandlers->raiseDisableEvent();
return true;
}
return false;
}
/**
* Erases the widget, marks it as deleted, and moves it to the CNxWidget
* deletion queue. Widgets are automatically deleted by the framework and
* should not be deleted externally.
*/
void CNxWidget::close(void)
{
if (!m_flags.deleted)
{
m_widgetEventHandlers->raiseCloseEvent();
m_widgetEventHandlers->disable();
m_flags.deleted = true;
m_flags.drawingEnabled = false;
// Unset clicked widget if necessary
CNxWidget *clickedWidget = m_widgetControl->getClickedWidget();
if (clickedWidget == this)
{
release(clickedWidget->getX(), clickedWidget->getY());
}
if (m_parent != (CNxWidget *)NULL)
{
m_parent->closeChild(this);
}
}
}
/**
* Draws the widget and makes it visible.
* Does not steal focus from other widgets.
*
* @return True if the widget was shown.
* @see hide()
*/
bool CNxWidget::show(void)
{
if (m_flags.hidden)
{
m_flags.hidden = false;
m_widgetEventHandlers->raiseShowEvent();
redraw();
return true;
}
return false;
}
/**
* Erases the widget and makes it invisible.
* Does not re-assign focus to another widget.
*
* @return True if the widget was hidden.
* @see show()
*/
bool CNxWidget::hide(void)
{
if (!m_flags.hidden)
{
m_flags.hidden = true;
m_widgetEventHandlers->raiseHideEvent();
return true;
}
return false;
}
/**
* Click this widget at the supplied coordinates. This should only be
* overridden in subclasses if the default click behaviour needs to be changed.
* If the subclassed widget should just respond to a standard click,
* the onClick() method should be overridden instead.
*
* @param x X coordinate of the click.
* @param y Y coordinate of the click.
* @return True if the click was successful.
*/
bool CNxWidget::click(nxgl_coord_t x, nxgl_coord_t y)
{
if (!isEnabled() || !checkCollision(x, y))
{
return false;
}
// Check for a double-click
if (isDoubleClick(x, y))
{
return doubleClick(x, y);
}
// Work out which child was clicked
for (int i = m_children.size() - 1; i > -1; i--)
{
if (m_children[i]->click(x, y))
{
return true;
}
}
// Handle clicks on this
m_flags.clicked = true;
// Record data for double-click
clock_gettime(CLOCK_REALTIME, &m_lastClickTime);
m_lastClickX = x;
m_lastClickY = y;
// Take focus away from child widgets
setFocusedWidget((CNxWidget *)NULL);
// Tell controlling widget that the clicked widget has changed
m_widgetControl->setClickedWidget(this);
// Run any code in the inherited class
onClick(x, y);
m_widgetEventHandlers->raiseClickEvent(x, y);
return true;
}
/**
* Check if the click is a double-click.
*
* @param x X coordinate of the click.
* @param y Y coordinate of the click.
* @return True if the click is a double-click.
*/
bool CNxWidget::isDoubleClick(nxgl_coord_t x, nxgl_coord_t y)
{
// Check for a double-click
if (m_flags.doubleClickable && hasFocus() && m_widgetControl->doubleClick())
{
// Within the allowed region?
if ((m_lastClickX > x - m_doubleClickBounds) && (m_lastClickX < x + m_doubleClickBounds))
{
if ((m_lastClickY > y - m_doubleClickBounds) && (m_lastClickY < y + m_doubleClickBounds))
{
return true;
}
}
}
return false;
}
/**
* Double-click this widget at the supplied coordinates. This
* should only be overridden in subclasses if the default
* double-click behaviour needs to be changed. If the subclassed
* widget should just respond to a standard double-click, the
* onDoubleClick() method should be overridden instead.
*
* @param x X coordinate of the click.
* @param y Y coordinate of the click.
* @return True if the click was successful.
*/
bool CNxWidget::doubleClick(nxgl_coord_t x, nxgl_coord_t y)
{
if (!isEnabled() || !checkCollision(x, y))
{
return false;
}
// Work out which child was clicked. Allow the
// child to determine if it has been double-clicked or not
// in case the second click has fallen on a different
// child to the first.
for (int i = m_children.size() - 1; i > -1; i--)
{
if (m_children[i]->click(x, y))
{
return true;
}
}
m_flags.clicked = true;
// Record data for double-click
clock_gettime(CLOCK_REALTIME, &m_lastClickTime);
m_lastClickX = x;
m_lastClickY = y;
// Take focus away from child widgets
setFocusedWidget((CNxWidget *)NULL);
// Tell controlling widget that the clicked widget has changed
m_widgetControl->setClickedWidget(this);
onDoubleClick(x, y);
m_widgetEventHandlers->raiseDoubleClickEvent(x, y);
return true;
}
/**
* Release this widget at the supplied coordinates. This should only be
* overridden in subclasses if the default release behaviour needs to be
* changed. If the subclassed widget should just respond to a standard
* release, the onRelease() method should be overridden instead.
*
* @param x X coordinate of the release.
* @param y Y coordinate of the release.
* @return True if the release was successful.
*/
bool CNxWidget::release(nxgl_coord_t x, nxgl_coord_t y)
{
if (!m_flags.clicked)
{
return false;
}
// Notify of the pre-release event. In this event, the widget is still in the
// clicked state. This event is used, for example, by button handlers so
// that the click event is really processed when the button is released
// instead of pressed. The former behavior is needed because the result
// of processing the press may obscure the button and make it impossible
// to receive the release event.
onPreRelease(x, y);
// Now mark the widget as NOT clicked and stop dragging actions.
m_flags.clicked = false;
stopDragging(x, y);
if (m_widgetControl->getClickedWidget() == this)
{
m_widgetControl->setClickedWidget((CNxWidget *)NULL);
}
// Determine which release event to fire
if (checkCollision(x, y))
{
// Notify of the release event... the widget was NOT dragged outside of
// its original bounding box
onRelease(x, y);
// Release occurred within widget; raise release
m_widgetEventHandlers->raiseReleaseEvent(x, y);
}
else
{
// Notify of the release event... the widget WAS dragged outside of
// its original bounding box
onReleaseOutside(x, y);
// Release occurred outside widget; raise release outside event
m_widgetEventHandlers->raiseReleaseOutsideEvent(x, y);
}
return true;
}
/**
* Drag the widget to the supplied coordinates.
*
* @param x The x coordinate of the mouse.
* @param y The y coordinate of the mouse.
* @param vX The horizontal distance that the mouse was dragged.
* @param vY The vertical distance that the mouse was dragged.
* @return True if the drag was successful.
*/
bool CNxWidget::drag(nxgl_coord_t x, nxgl_coord_t y, nxgl_coord_t vX, nxgl_coord_t vY)
{
if (isEnabled() && m_flags.dragging)
{
if (vX != 0 || vY != 0)
{
onDrag(x, y, vX, vY);
m_widgetEventHandlers->raiseDragEvent(x, y, vX, vY);
}
return true;
}
return false;
}
/**
* Send a keypress to the widget.
*
* @param key The keycode to send to the widget.
* @return True if the keypress was processed.
*/
bool CNxWidget::keyPress(nxwidget_char_t key)
{
if (!isEnabled())
{
return false;
}
// Raise keypress for this widget
m_widgetEventHandlers->raiseKeyPressEvent(key);
// Handle active child
if (m_focusedChild != NULL)
{
m_focusedChild->keyPress(key);
}
return true;
}
/**
* Send a cursor control event to the widget.
*
* @param cursorControl The cursor control code o send to the widget.
* @return True if the cursor control was processed.
*/
bool CNxWidget::cursorControl(ECursorControl control)
{
if (!isEnabled())
{
return false;
}
// Raise cursor control for this widget
m_widgetEventHandlers->raiseCursorControlEvent(control);
// Handle active child
if (m_focusedChild != NULL)
{
m_focusedChild->cursorControl(control);
}
return true;
}
/**
* Give the widget focus.
*
* @return True if the widget received focus correctly.
*/
bool CNxWidget::focus(void)
{
if (!isEnabled())
{
return false;
}
// Remember if the widget has focus
bool hadFocus = m_flags.hasFocus;
m_flags.hasFocus = true;
// Notify parent that this widget has focus
if (m_parent != (CNxWidget *)NULL)
{
m_parent->setFocusedWidget(this);
}
// Raise an event only if the widget did not have focus
if (!hadFocus)
{
onFocus();
m_widgetEventHandlers->raiseFocusEvent();
return true;
}
return false;
}
/**
* Remove focus from the widget.
*
* @return True if the widget lost focus correctly.
*/
bool CNxWidget::blur(void)
{
// Remember if the widget had focus
bool hadFocus = m_flags.hasFocus;
m_flags.hasFocus = false;
// Take focus away from child widgets
if (m_focusedChild != (CNxWidget *)NULL)
{
m_focusedChild->blur();
m_focusedChild = (CNxWidget *)NULL;
m_widgetControl->clearFocusedWidget(this);
}
// Raise an event only if the widget had focus
if (hadFocus)
{
onBlur();
m_widgetEventHandlers->raiseBlurEvent();
return true;
}
return false;
}
/**
* Move the widget to the new coordinates.
* Co-ordinates are relative to the parent widget.
*
* @param x The new x coordinate.
* @param y The new y coordinate.
* @return True if the move was successful.
*/
bool CNxWidget::moveTo(nxgl_coord_t x, nxgl_coord_t y)
{
// Enforce widget to stay within parent confines if necessary
if (m_parent != (CNxWidget *)NULL)
{
if (!m_parent->isPermeable())
{
CRect parentRect;
m_parent->getClientRect(parentRect);
// Check x coordinate
if (x < parentRect.getX())
{
x = parentRect.getX();
// Check width against new value
if (x + getWidth() > parentRect.getX2() + 1)
{
return false;
}
}
else if (x + getWidth() > parentRect.getX2() + 1)
{
x = (parentRect.getX() + parentRect.getX()) - getWidth();
// Check new x value
if (x < parentRect.getX())
{
return false;
}
}
// Check y coordinate
if (y < parentRect.getY())
{
y = parentRect.getY();
// Check height against new value
if (y + getHeight() > parentRect.getY2() + 1)
{
return false;
}
}
else if (y + getHeight() > parentRect.getY2() + 1)
{
y = (parentRect.getY() + parentRect.getY()) - getHeight();
// Check new y value
if (y < parentRect.getY())
{
return false;
}
}
}
}
// Perform move if necessary
if ((m_rect.getX() != x) || (m_rect.getY() != y))
{
nxgl_coord_t oldX = m_rect.getX();
nxgl_coord_t oldY = m_rect.getY();
m_rect.setX(x);
m_rect.setY(y);
redraw();
m_widgetEventHandlers->raiseMoveEvent(x, y, x - oldX, y - oldY);
return true;
}
return false;
}
/**
* Resize the widget to the new dimensions.
*
* @param width The new width.
* @param height The new height.
* @return True if the resize was successful.
*/
bool CNxWidget::resize(nxgl_coord_t width, nxgl_coord_t height)
{
// Enforce widget to stay within parent confines if necessary
if (m_parent != (CNxWidget *)NULL)
{
if (!m_parent->isPermeable())
{
CRect parentRect;
m_parent->getClientRect(parentRect);
// Check width
if (m_rect.getX() + width > parentRect.getX2() + 1)
{
width = parentRect.getX2() + 1 - m_rect.getX();
}
// Check height
if (m_rect.getY() + height > parentRect.getY2() + 1)
{
height = parentRect.getY2() + 1 - m_rect.getY();
}
}
}
if (getWidth() != width || getHeight() != height)
{
// Remember if the widget is permeable
bool wasPermeable = m_flags.permeable;
// Remember if widget was drawing
bool wasDrawEnabled = m_flags.drawingEnabled;
m_flags.permeable = true;
disableDrawing();
m_rect.setWidth(width);
m_rect.setHeight(height);
onResize(width, height);
// Reset the permeable value
m_flags.permeable = wasPermeable;
// Reset drawing value
m_flags.drawingEnabled = wasDrawEnabled;
redraw();
m_widgetEventHandlers->raiseResizeEvent(width, height);
return true;
}
return false;
}
/**
* Resize and move the widget in one operation.
* Only performs one redraw so it is faster than calling the
* two separate functions.
*
* @param x The new x coordinate.
* @param y The new y coordinate.
* @param width The new width.
* @param height The new height.
* @return True if the widget was adjusted successfully.
*/
bool CNxWidget::changeDimensions(nxgl_coord_t x, nxgl_coord_t y,
nxgl_coord_t width, nxgl_coord_t height)
{
bool wasDrawing = m_flags.drawingEnabled;
m_flags.drawingEnabled = false;
bool moved = moveTo(x, y);
m_flags.drawingEnabled = wasDrawing;
return (resize(width, height) | moved);
}
/**
* Moves the supplied child widget to the deletion queue.
* For framework use only.
*
* @param widget A pointer to the child widget.
*/
void CNxWidget::moveChildToDeleteQueue(CNxWidget *widget)
{
// Locate widget in main vector
for (int i = 0; i < m_children.size(); i++)
{
if (m_children[i] == widget)
{
// Add widget to controlling widget's delete vector
m_widgetControl->addToDeleteQueue(widget);
// Remove widget from main vector
m_children.erase(i);
break;
}
}
}
/**
* Sets the supplied widget as the focused child. The widget must
* be a child of this widget.
*
* @param widget A pointer to the child widget.
* @see getFocusedWidget()
*/
void CNxWidget::setFocusedWidget(CNxWidget *widget)
{
if (m_focusedChild != widget)
{
if (m_focusedChild != NULL)
{
// Blur the current active widget
m_focusedChild->blur();
}
}
// Remember the new active widget
m_focusedChild = widget;
// Make this widget active too
focus();
// Route keyboard input to the focused widget
m_widgetControl->setFocusedWidget(this);
}
/**
* Checks if the supplied coordinates collide with this widget.
*
* @param x The x coordinate to check.
* @param y The y coordinate to check.
* @return True if a collision occurred.
*/
bool CNxWidget::checkCollision(nxgl_coord_t x, nxgl_coord_t y) const
{
if (isHidden())
{
return false;
}
// Get the clipped rect
CRect rect;
getRect(rect);
return rect.contains(x, y);
}
/**
* Checks if the supplied rectangle definition collides with this widget.
*
* @param x The x coordinate of the rectangle to check.
* @param y The y coordinate of the rectangle to check.
* @param width The width of the rectangle to check.
* @param height The height of the rectangle to check.
* @return True if a collision occurred.
*/
bool CNxWidget::checkCollision(nxgl_coord_t x, nxgl_coord_t y,
nxgl_coord_t width, nxgl_coord_t height) const
{
if (isHidden())
{
return false;
}
// Get the clipped rect
CRect rect;
getRect(rect);
return rect.intersects(CRect(x, y, width, height));
}
/**
* Checks if the supplied widget collides with this widget.
*
* @param widget A pointer to another widget to check for collisions with.
* @return True if a collision occurred.
*/
bool CNxWidget::checkCollision(CNxWidget *widget) const
{
// Get the clipped rect
CRect rect;
widget->getRect(rect);
return rect.intersects(m_rect);
}
/**
* Adds a widget to this widget's child stack. The widget is added to the
* top of the stack. Note that the widget can only be added if it is not
* already a child of another widget.
*
* @param widget A pointer to the widget to add to the child list.
* @see insertWidget()
*/
void CNxWidget::addWidget(CNxWidget *widget)
{
if (widget->getParent() == NULL)
{
widget->setParent(this);
m_children.push_back(widget);
// Should the widget steal the focus?
if (widget->hasFocus())
{
setFocusedWidget(widget);
}
widget->enableDrawing();
widget->redraw();
}
}
/**
* Inserts a widget into this widget's child stack at the bottom of the
* stack. Note that the widget can only be added if it is not already
* a child of another widget.
*
* @param widget A pointer to the widget to add to the child list.
* @see addWidget()
*/
void CNxWidget::insertWidget(CNxWidget *widget)
{
if (widget->getParent() == NULL)
{
widget->setParent(this);
m_children.insert(0, widget);
widget->enableDrawing();
widget->redraw();
}
}
/**
* Remove this widget from the widget hierarchy. Returns
* responsibility for deleting the widget back to the developer.
* Does not unregister the widget from the VBL system.
* Does not erase the widget from the display.
*
* @return True if the widget was successfully removed.
*/
bool CNxWidget::remove(void)
{
if (m_parent != (CNxWidget *)NULL)
{
return m_parent->removeChild(this);
}
return false;
}
/**
* Remove a child widget from the widget hierarchy. Returns
* responsibility for deleting the widget back to the developer.
* Does not unregister the widget from the VBL system.
* Does not erase the widget from the display.
*
* @param widget Pointer to the widget to remove from the hierarchy.
* @return True if the widget was succesfully removed.
*/
bool CNxWidget::removeChild(CNxWidget *widget)
{
// Do we need to make another widget active?
if (m_focusedChild == widget)
{
m_focusedChild = (CNxWidget *)NULL;
m_widgetControl->clearFocusedWidget(this);
}
// Unset clicked widget if necessary
CNxWidget *clickedWidget = m_widgetControl->getClickedWidget();
if (clickedWidget == widget)
{
clickedWidget->release(clickedWidget->getX(), clickedWidget->getY());
}
// Divorce child from parent
widget->setParent((CNxWidget *)NULL);
widget->disableDrawing();
// Locate widget in main vector
for (int i = 0; i < m_children.size(); i++)
{
if (m_children[i] == widget)
{
// Remove widget from main vector
m_children.erase(i);
return true;
}
}
return false;
}
/**
* Get the child widget at the specified index.
*
* @param index Index of the child to retrieve.
* @return Pointer to the child at the specified index.
*/
const CNxWidget *CNxWidget::getChild(int index) const
{
if (index < (int)m_children.size())
{
return m_children[index];
}
return (CNxWidget *)NULL;
}
/**
* Sets the border size. The border cannot be drawn over in the
* drawContents() method.
*
* @param borderSize The new border size.
*/
void CNxWidget::setBorderSize(const WidgetBorderSize &borderSize)
{
m_borderSize.top = borderSize.top;
m_borderSize.right = borderSize.right;
m_borderSize.bottom = borderSize.bottom;
m_borderSize.left = borderSize.left;
}
/**
* Use the provided widget style
*/
void CNxWidget::useWidgetStyle(const CWidgetStyle *style)
{
m_style.colors.background = style->colors.background;
m_style.colors.selectedBackground = style->colors.selectedBackground;
m_style.colors.shineEdge = style->colors.shineEdge;
m_style.colors.shadowEdge = style->colors.shadowEdge;
m_style.colors.highlight = style->colors.highlight;
m_style.colors.disabledText = style->colors.disabledText;
m_style.colors.enabledText = style->colors.enabledText;
m_style.colors.selectedText = style->colors.selectedText;
m_style.font = style->font;
}
/**
* Draw the area of this widget that falls within the clipping region.
* Called by the redraw() function to draw all visible regions.
*
* @param port The CGraphicsPort to draw to.
* @see redraw().
*/
void CNxWidget::drawContents(CGraphicsPort* port)
{
port->drawFilledRect(getX(), getY(), getWidth(), getHeight(),
getBackgroundColor());
}
/**
* Draw all visible regions of this widget's children.
*/
void CNxWidget::drawChildren(void)
{
for (int i = 0; i < m_children.size(); i++)
{
m_children[i]->redraw();
}
}
/**
* Remove the supplied child widget from this widget and send it to
* the deletion queue.
*
* @param widget The widget to close.
* @see close().
*/
void CNxWidget::closeChild(CNxWidget *widget)
{
if (widget == NULL)
{
return;
}
// Ensure widget knows it is being closed
widget->close();
// Do we need to make another widget active?
if (m_focusedChild == widget)
{
m_focusedChild = (CNxWidget *)NULL;
m_widgetControl->clearFocusedWidget(this);
// Try to choose highest widget
for (int i = m_children.size() - 1; i > -1; i--)
{
if ((m_children[i] != widget) && (!m_children[i]->isHidden()))
{
m_focusedChild = m_children[i];
}
}
// Where should the focus go?
if (m_focusedChild != NULL)
{
// Send focus to the new active widget
m_focusedChild->focus();
// Route keyboard input to the focused widget
m_widgetControl->setFocusedWidget(this);
}
else
{
// Give focus to this
setFocusedWidget((CNxWidget *)NULL);
}
}
moveChildToDeleteQueue(widget);
}
/**
* Notify this widget that it is being dragged, and set its drag point.
*
* @param x The x coordinate of the drag position relative to this widget.
* @param y The y coordinate of the drag position relative to this widget.
*/
void CNxWidget::startDragging(nxgl_coord_t x, nxgl_coord_t y)
{
if (m_flags.draggable)
{
m_flags.dragging = true;
m_flags.clicked = true;
m_grabPointX = x - getX();
m_grabPointY = y - getY();
m_newX = m_rect.getX();
m_newY = m_rect.getY();
onDragStart();
}
}
/**
* Notify this widget that it is no longer being dragged.
*/
void CNxWidget::stopDragging(nxgl_coord_t x, nxgl_coord_t y)
{
if (m_flags.dragging)
{
onDragStop();
m_flags.dragging = false;
m_widgetEventHandlers->raiseDropEvent(x, y);
}
}