/**************************************************************************** * NxWidgets/libnxwidgets/src/cnxwidget.cxx * * Copyright (C) 2012 Gregory Nutt. All rights reserved. * Author: Gregory Nutt * * 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 #include #include #include "cnxwidget.hxx" #include "cgraphicsport.hxx" #include "cwidgeteventhandler.hxx" #include "cnxfont.hxx" #include "cwidgetstyle.hxx" #include "cwidgeteventargs.hxx" #include "crectcache.hxx" #include "cwidgetcontrol.hxx" #include "singletons.hxx" /**************************************************************************** * Pre-Processor Definitions ****************************************************************************/ #define DOUBLE_CLICK_BOUNDS 10 /**************************************************************************** * Method Implementations ****************************************************************************/ using namespace NXWidgets; /** * Constructor. * * @param pWidgetControl The controllwing 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; m_flags.modal = 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 other default values m_refcon = 0; // Set border size to 1 line m_borderSize.top = 1; m_borderSize.right = 1; m_borderSize.bottom = 1; m_borderSize.left = 1; m_closeType = CLOSE_TYPE_CLOSE; m_rectCache = new CRectCache(this); 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_rectCache; delete m_widgetEventHandlers; } /** * Get the x coordinate of the widget in "Widget space". * * @return Widget space x coordinate. */ const 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. */ const 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. */ const 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. */ const 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. */ const 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. */ const 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. */ const 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. */ const 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; } /** * Is the widget modal? Only true if the Widget singleton is also modal. * * @return True if the widget is modal. */ const bool CNxWidget::isModal(void) const { return m_widgetControl->isModal() & m_flags.modal; } /** * 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()); } /** * Clips the supplied rect to the boundaries defined by this widget and * this widget's parents. * * @param rect Reference to a rect to populate with data. */ void CNxWidget::getRectClippedToHierarchy(CRect &rect) const { // Copy the widget's properties into the rect. NOTE that if this is // a child widget, getX() and getY() will return the actual X and Y // positions (and not the parent-relative X and Y positions). rect.setX(getX()); rect.setY(getY()); rect.setWidth(getWidth()); rect.setHeight(getHeight()); // And clip it clipRectToHierarchy(rect); } /** * Gets a pointer to the vector of all of the visible regions of this widget, * including any covered by children. * * @return A pointer to a vector of all visible regions. */ TNxArray *CNxWidget::getForegroundRegions(void) { return m_rectCache->getForegroundRegions(); } /** * Sets this widget's reference constant. This should be unique, * at least amongst this widget's siblings. * * @param refcon The reference constant. */ uint32_t CNxWidget::setRefcon(uint32_t refcon) { uint32_t old = m_refcon; m_refcon = refcon; return old; } /** * Sets this widget's border state. * * @param isBorderless The border state. */ void CNxWidget::setBorderless(bool borderless) { m_flags.borderless = borderless; invalidateVisibleRectCache(); } /** * 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()) { cacheVisibleRects(); if (m_rectCache->getBackgroundRegions()->size() > 0) { // Get the graphics port for drawing in this window CGraphicsPort *port = m_widgetControl->getGraphicsPort(); // Draw all visible rectangles for (int i = 0; i < m_rectCache->getBackgroundRegions()->size(); i++) { drawBorder(port); drawContents(port); } } // Remember that the widget is no longer erased m_flags.erased = false; drawChildren(); } } /** * Erases the visible regions of the widget by redrawing the widgets * behind it. */ void CNxWidget::erase(void) { if (!m_flags.erased) { cacheVisibleRects(); if (m_parent != (CNxWidget *)NULL) { m_parent->eraseWidget(this); } // Remember that the widget has been erased m_flags.erased = true; invalidateVisibleRectCache(); } } /** * 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()); } // Ensure the widget isn't running modally stopModal(); erase(); 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; // Ensure that widgets behind this do not draw over the // top of the newly-visible widget m_parent->invalidateLowerWidgetsVisibleRectCache(this); 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; // Ensure the widget isn't running modally stopModal(); m_widgetEventHandlers->raiseHideEvent(); erase(); 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; } } // Ensure that the click has occurred on a region of this widget // not obscured by its siblings if (!checkCollisionWithForegroundRects(x, y)) { return false; } // 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; } } // Ensure that the click has occurred on a region of this widget // not obscured by its siblings if (!checkCollisionWithForegroundRects(x, y)) { return false; } 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; } 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)) { onRelease(x, y); // Release occurred within widget; raise release m_widgetEventHandlers->raiseReleaseEvent(x, y); } else { onReleaseOutside(x, y); // Release occurred outside widget; raise release 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)) { erase(); 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; erase(); disableDrawing(); m_rect.setWidth(width); m_rect.setHeight(height); // Handle visible region caching if (m_parent != (CNxWidget *)NULL) { m_parent->invalidateLowerWidgetsVisibleRectCache(this); } 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); } /** * Raises the widget to the top of its parent's widget stack. * * @return True if the raise was successful. */ bool CNxWidget::raiseToTop(void) { if (m_parent != (CNxWidget *)NULL) { if (m_parent->raiseWidgetToTop(this)) { m_widgetEventHandlers->raiseMoveForwardEvent(); return true; } } return false; } /** * Lowers the widget to the bottom of its parent's widget stack. * * @return True if the lower was successful. */ bool CNxWidget::lowerToBottom(void) { if (m_parent != (CNxWidget *)NULL) { if (m_parent->lowerWidgetToBottom(this)) { m_widgetEventHandlers->raiseMoveBackwardEvent(); return true; } } return false; } /** * Raises the supplied widget to the top of this widget's child stack. * The supplied widget pointer must be a child of this widget. * * @param widget A pointer to the child widget to raise. * @return True if the raise was successful. */ bool CNxWidget::raiseWidgetToTop(CNxWidget *widget) { // Locate widget in the stack int index = getWidgetIndex(widget); if ((index > -1) && (index < m_children.size() - 1)) { m_children.erase(index); m_children.push_back(widget); widget->invalidateVisibleRectCache(); // Invalidate all widgets that collide with the depth-swapped widget for (int i = 0; i < m_children.size(); i++) { if (m_children[i]->checkCollision(widget)) { m_children[i]->invalidateVisibleRectCache(); } } widget->redraw(); return true; } return false; } /** * Lowers the supplied widget to the bottom of this widget's child stack. * The supplied widget pointer must be a child of this widget. * * @param widget A pointer to the child widget to lower. * @return True if the lower was successful. */ bool CNxWidget::lowerWidgetToBottom(CNxWidget *widget) { // Locate widget in the stack int index = getWidgetIndex(widget); if (index > 0) { widget->erase(); // Handle visible region caching widget->invalidateVisibleRectCache(); invalidateLowerWidgetsVisibleRectCache(widget); m_children.erase(index); m_children.insert(0, widget); widget->redraw(); return true; } return false; } /** * 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; getRectClippedToHierarchy(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; getRectClippedToHierarchy(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->getRectClippedToHierarchy(rect); return rect.intersects(m_rect); } /** * Invalidate the visible region cache for all widgets below the supplied * widget in this widget's child stack. This will cause those widgets to * * recalculate their visible regions next time they try to draw themselves. * @param widget A pointer to a child widget. */ void CNxWidget::invalidateLowerWidgetsVisibleRectCache(CNxWidget *widget) { // Find the widget int widgetIndex = getWidgetIndex(widget); // Invalidate lower widgets for (int i = widgetIndex - 1; i > -1; i--) { if (m_children[i]->checkCollision(widget)) { m_children[i]->invalidateVisibleRectCache(); } } } /** * 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(); invalidateVisibleRectCache(); 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(); invalidateVisibleRectCache(); widget->redraw(); } } /** * Rebuild the list of this widget's visible regions */ void CNxWidget::cacheVisibleRects(void) const { m_rectCache->cache(); } /** * Mark this widget's visible region cache as invalid, and do the same * to its child widgets. */ void CNxWidget::invalidateVisibleRectCache(void) { m_rectCache->invalidate(); // Invalidate child cache for (int i = 0; i < m_children.size(); i++) { m_children[i]->invalidateVisibleRectCache(); } } /** * Erase a child widget by drawing the widgets behind it. * * @param widget The child widget to erase. */ void CNxWidget::eraseWidget(CNxWidget *widget) { // Locate the widget int widgetIndex = getWidgetIndex(widget); // Ensure rect cache is up to date widget->cacheVisibleRects(); // Order all lower widgets to redraw themselves based on the erased widget's // visible rect cache for (int i = widgetIndex - 1; i > -1; i--) { m_children[i]->redrawDirty(widget->getForegroundRegions(), widget); } // Order this widget to redraw itself based on any remaining rectangles // in the erased widget's rect cache redrawDirty(widget->getForegroundRegions(), widget); invalidateVisibleRectCache(); } /** * Redraw any visible regions of this widget that have become corrupted. * * @param invalidRects A list of corrupt regions. * @param sender A pointer to the widget that corrupted the regions. */ void CNxWidget::redrawDirty(TNxArray *invalidRects, CNxWidget *sender) { if (isDrawingEnabled()) { // Draw any children first redrawDirtyChildren(invalidRects, sender); // Create an array that will contain all of the rects from the // original array that overlap this widget TNxArray *overlappingRects = new TNxArray(); // Remove any non-overlapping rectangles from dirty vector and add to // overlapping vector m_rectCache->splitRectangles(invalidRects, overlappingRects, sender); // Create an array that will contain all of the rects that overlap this // widget clipped to its parent TNxArray *rectsToDraw = new TNxArray(); // Split from overlappingRects into rectsToDraw, giving us an array // of rects that overlap only the visible portions of this widget m_rectCache->splitRectangles(overlappingRects, rectsToDraw, sender); // Get the graphics port for drawing in this window CGraphicsPort *port = m_widgetControl->getGraphicsPort(); // Draw the dirty rects if (rectsToDraw->size() > 0) { for (int i = 0; i < rectsToDraw->size(); i++) { drawBorder(port); drawContents(port); } } // Copy all of the overlapping rects we didn't draw back to the main // array of rects that need to be drawn by another widget for (int i = 0; i < overlappingRects->size(); i++) { invalidRects->push_back(overlappingRects->at(i)); } // Clean up delete overlappingRects; delete rectsToDraw; } } /** * Clips a rectangular region to the dimensions of this widget and its ancestors. * * @param rect The region that needs to be clipped. */ void CNxWidget::clipRectToHierarchy(CRect &rect) const { const CNxWidget *parent = m_parent; const CNxWidget *widget = this; CRect thisRect; while (parent != NULL) { // Standard widgets can draw into client space parent->getClientRect(thisRect); // Adjust rect to screen space thisRect.offset(parent->getX(), parent->getY()); rect.clipToIntersect(thisRect); // Send up to parent widget = parent; parent = parent->getParent(); } } /** * Swaps the depth of the supplied child widget. * * @param widget A pointer to the child widget that needs to swap depths. * @return True if the swap was successful. */ bool CNxWidget::swapWidgetDepth(CNxWidget *widget) { // Can we swap? if (m_children.size() > 1) { int widgetSource = 0; int widgetDest = 0; // Locate the widget in the vector widgetSource = getWidgetIndex(widget); // Attempt to raise up int i = getHigherVisibleWidget(widgetSource); if (i > -1) { // Raise widgetDest = i; } else { // Lower to bottom of stack widgetDest = 0; } // Erase the widget from the screen eraseWidget(widget); // Swap CNxWidget *tmp = m_children[widgetSource]; m_children[widgetSource] = m_children[widgetDest]; m_children[widgetDest] = tmp; // Invalidate the widgets below the top affected widget if (widgetSource < widgetDest) { // Source lower; invalidate from dest down m_children[widgetDest]->invalidateVisibleRectCache(); invalidateLowerWidgetsVisibleRectCache(m_children[widgetDest]); } else { // Dest lower; invalidate from source down m_children[widgetSource]->invalidateVisibleRectCache(); invalidateLowerWidgetsVisibleRectCache(m_children[widgetSource]); } // Redraw the widget widget->redraw(); return true; } return false; } /** * Swap the depth of this widget. * * @return True if the swap was successful. */ bool CNxWidget::swapDepth(void) { if (m_parent != (CNxWidget *)NULL) { return m_parent->swapWidgetDepth(this); } return false; } /** * 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; } /** * Run the widget modally. */ void CNxWidget::goModal(void) { // Remember that we're running modally m_flags.modal = true; // Steal focus focus(); // Loop until no longer modal while (isModal()) { // Process pending events bool interestingEvent = m_widgetControl->pollEvents(this); // Did any interesting events occur? if (!interestingEvent) { // No, give up the CPU until something interesting happens. m_widgetControl->waitForModalEvent(); } } } /** * Get the index of the specified child widget. * * @param widget The widget to get the index of. * @return The index of the widget. -1 if the widget is not found. */ const int CNxWidget::getWidgetIndex(const CNxWidget *widget) const { for (int i = 0; i < m_children.size(); i++) { if (m_children[i] == widget) { return i; } } return -1; } /** * 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(const 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; } /** * Checks if the supplied coordinates collide with a portion of this widget * that is not obscured by its siblings, but that may be obscured by * its children. * * @param x X coordinate of the click. * @param y Y coordinate of the click. * @return True if a collision occurred; false if not. */ bool CNxWidget::checkCollisionWithForegroundRects(nxgl_coord_t x, nxgl_coord_t y) const { if (isHidden()) { return false; } cacheVisibleRects(); CRect *rect; for (int i = 0; i < m_rectCache->getForegroundRegions()->size(); ++i) { rect = &(m_rectCache->getForegroundRegions()->at(i)); if (rect->contains(x, y)) { return true; } } return false; } /** * 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(); } } /** * Erase and 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); } /** * Redraws all regions of child widgets that fall within the invalidRects * regions. * * @param invalidRects List of invalid regions that need to be redrawn. * @param sender Pointer to the widget that initiated the redraw. */ void CNxWidget::redrawDirtyChildren(TNxArray *invalidRects, CNxWidget *sender) { for (int i = m_children.size() - 1; i > -1 ; i--) { if (invalidRects->size() > 0) { if (m_children.at(i) != sender) { m_children[i]->redrawDirty(invalidRects, sender); } } else { break; } } } /** * Get the index of the next visible widget higher up the z-order. * * @param startIndex The starting index. * @return The index of the next highest visible widget. */ const int CNxWidget::getHigherVisibleWidget(const int startIndex) const { for (int i = startIndex; i < m_children.size(); i++) { if (!m_children[i]->isHidden()) { return i; } } return -1; } /** * Get the index of the next visible widget lower down the z-order. * * @param startIndex The starting index. * @return The index of the next lowest visible widget. */ const int CNxWidget::getLowerVisibleWidget(const int startIndex) const { for (int i = startIndex; i > -1; i--) { if (!m_children[i]->isHidden()) { return i; } } return -1; } /** * 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); } }