nuttx-apps/graphics/twm4nx/apps/cclock.cxx

660 lines
17 KiB
C++
Raw Normal View History

/////////////////////////////////////////////////////////////////////////////
// apps/graphics/twm4nx/src/cnxterm.cxx
// Clock window
//
// Copyright (C) 2019 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 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 <ctime>
#include <cstring>
#include <csched>
#include <cassert>
#include <semaphore.h>
#include <debug.h>
#include <nuttx/semaphore.h>
#include "nshlib/nshlib.h"
#include "graphics/nxwidgets/cwidgetcontrol.hxx"
#include "graphics/nxglyphs.hxx"
#include "graphics/twm4nx/twm4nx_config.hxx"
#include "graphics/twm4nx/ctwm4nx.hxx"
#include "graphics/twm4nx/cwindow.hxx"
#include "graphics/twm4nx/cwindowfactory.hxx"
#include "graphics/twm4nx/cmainmenu.hxx"
#include "graphics/slcd.hxx"
#include "graphics/twm4nx/apps/clock_config.hxx"
#include "graphics/twm4nx/apps/cclock.hxx"
/////////////////////////////////////////////////////////////////////////////
// Private Types
/////////////////////////////////////////////////////////////////////////////
namespace Twm4Nx
{
/////////////////////////////////////////////////////////////////////////////
// Private Types
/////////////////////////////////////////////////////////////////////////////
/**
* This structure is used to pass start up parameters to the Clock task
* and to assure the the Clock is successfully started.
*/
struct SClock
{
FAR CClock *that; /**< The CClock 'this' pointer */
sem_t exclSem; /**< Sem that gives exclusive access to this structure */
sem_t waitSem; /**< Sem that posted when the task is initialized */
bool success; /**< True if successfully initialized */
};
/////////////////////////////////////////////////////////////////////////////
// Private Data
/////////////////////////////////////////////////////////////////////////////
/**
* This global data structure is used to pass start parameters to Clock
* task and to assure that the Clock is successfully started.
*/
static struct SClock GClockVars;
}
/////////////////////////////////////////////////////////////////////////////
// CClock Method Implementations
/////////////////////////////////////////////////////////////////////////////
using namespace Twm4Nx;
/**
* CClock constructor
*
* @param twm4nx. The Twm4Nx session instance
*/
CClock::CClock(FAR CTwm4Nx *twm4nx)
{
// Save/initialize the context data
m_twm4nx = twm4nx;
m_window = (FAR CWindow *)0;
m_slcd = (FAR SLcd::CSLcd *)0;
// The clock task is not running
m_pid = (pid_t)-1;
// This is un-neccessary but helpful in debugging to have a known value
std::memset(m_digits, 0, CLOCK_NDIGITS * sizeof(struct SClockDigit));
}
/**
* CClock destructor
*/
CClock::~CClock(void)
{
// There would be a problem if we were stopped with the Clock task
// running... that should never happen but we'll check anyway:
stop();
// Destroy the CSLcd instance
if (m_window != (FAR CWindow *)0)
{
delete m_window;
}
// Destroy the CSLcd instance
if (m_slcd != (FAR SLcd::CSLcd *)0)
{
delete m_slcd;
}
}
/**
* CClock initializers. Perform miscellaneous post-construction
* initialization that may fail (and hence is not appropriate to be
* done in the constructor)
*
* @return True if the Clock application was successfully initialized.
*/
bool CClock::initialize(void)
{
// Call CWindowFactory::createWindow() to create a window for the Clock
// application. Customizations:
//
// Flags:
// WFLAGS_NO_MENU_BUTTON No menu button in the toolbar
// WFLAGS_NO_RESIZE_BUTTON No resize button in the toolbar
// WFLAGS_HIDDEN Window is initially hidden
//
// Null Icon manager means to use the system, common Icon Manager
NXWidgets::CNxString name("Clock");
uint8_t wflags = (WFLAGS_NO_MENU_BUTTON | WFLAGS_NO_RESIZE_BUTTON |
WFLAGS_HIDDEN);
FAR CWindowFactory *factory = m_twm4nx->getWindowFactory();
m_window = factory->createWindow(name, &CONFIG_TWM4NX_CLOCK_ICON,
(FAR CIconMgr *)0, wflags);
if (m_window == (FAR CWindow *)0)
{
twmerr("ERROR: Failed to create CWindow\n");
return false;
}
// Get the minimum tool bar
// Configure events needed by the Clock applications
struct SAppEvents events;
events.eventObj = (FAR void *)this;
events.redrawEvent = EVENT_CLOCK_REDRAW;
events.resizeEvent = EVENT_CLOCK_RESIZE;
events.mouseEvent = EVENT_CLOCK_XYINPUT;
events.kbdEvent = EVENT_CLOCK_KBDINPUT;
events.closeEvent = EVENT_CLOCK_CLOSE;
events.deleteEvent = EVENT_CLOCK_DELETE;
bool success = m_window->configureEvents(events);
if (!success)
{
delete m_window;
m_window = (FAR CWindow *)0;
return false;
}
// Create an instance of the segment LCD emulation
m_slcd = new SLcd::CSLcd(m_window->getNxWindow(),
CONFIG_TWM4NX_CLOCK_HEIGHT);
if (m_slcd == (FAR SLcd::CSLcd *)0)
{
twmerr("ERROR: Failed to create SLcd::CSLcd instance\n");
delete m_window;
m_window = (FAR CWindow *)0;
return false;
}
// Set the correct size and position of the window based on the SLCD
// character height.
nxgl_coord_t segmentWidth = m_slcd->getWidth();
nxgl_coord_t gapWidth = segmentWidth / 2;
struct nxgl_size_s windowSize;
windowSize.w = 4 * (segmentWidth + CONFIG_TWM4NX_CLOCK_HSPACING) + gapWidth;
windowSize.h = m_slcd->getHeight() + 2 * CONFIG_TWM4NX_CLOCK_HSPACING;
// Check against the minimum toolbar width
nxgl_coord_t minWidth = minimumToolbarWidth(m_twm4nx, name, wflags);
nxgl_coord_t xOffset = CONFIG_TWM4NX_CLOCK_HSPACING;
if (windowSize.w < minWidth)
{
xOffset += (minWidth - windowSize.w) / 2;
windowSize.w = minWidth;
}
// Resize and position the frame. The window is initially position in
// the upper left hand corner of the display.
struct nxgl_size_s frameSize;
m_window->windowToFrameSize(&windowSize, &frameSize);
struct nxgl_point_s framePos;
framePos.x = 0;
framePos.y = 0;
if (!m_window->resizeFrame(&frameSize, &framePos))
{
delete m_window;
m_window = (FAR CWindow *)0;
delete m_slcd;
m_slcd = (FAR SLcd::CSLcd *)0;
return false;
}
// Initialize the SLCD digit horizontal positions
m_digits[0].xOffset = xOffset;
m_digits[1].xOffset = m_digits[0].xOffset + segmentWidth +
CONFIG_TWM4NX_CLOCK_HSPACING;
m_digits[2].xOffset = m_digits[1].xOffset + segmentWidth +
gapWidth;
m_digits[3].xOffset = m_digits[2].xOffset + segmentWidth +
CONFIG_TWM4NX_CLOCK_HSPACING;
// Display the initial time
redraw();
// Now we can show the completed window
return m_window->showWindow();
}
/**
* Start the application (perhaps in the minimized state).
*
* @return True if the application was successfully started.
*/
bool CClock::run(void)
{
// Some sanity checking
if (m_pid >= 0)
{
twmerr("ERROR: All ready running?\n");
return false;
}
// Get exclusive access to the global data structure
if (sem_wait(&GClockVars.exclSem) != 0)
{
// This might fail if a signal is received while we are waiting. Or,
// perhaps if the task is canceled.
twmerr("ERROR: Failed to get semaphore\n");
return false;
}
// Initialize the rest of parameter passing area for temporary use to
// start the new Clock task
sem_init(&GClockVars.waitSem, 0, 0);
sem_setprotocol(&GClockVars.waitSem, SEM_PRIO_NONE);
GClockVars.that = this;
GClockVars.success = false;
// Start the Clock task
sched_lock();
m_pid = task_create("Clock", CONFIG_TWM4NX_CLOCK_PRIO,
CONFIG_TWM4NX_CLOCK_STACKSIZE, clock,
(FAR char * const *)0);
// Did we successfully start the Clock task?
bool success = true;
if (m_pid < 0)
{
twmerr("ERROR: Failed to create the Clock task\n");
success = false;
}
else
{
// Wait for up to two seconds for the task to initialize
struct timespec abstime;
clock_gettime(CLOCK_REALTIME, &abstime);
abstime.tv_sec += 2;
int ret = sem_timedwait(&GClockVars.waitSem, &abstime);
sched_unlock();
if (ret < 0 || !GClockVars.success)
{
// sem_timedwait failed OR the Clock task reported a
// failure. Stop the application
twmerr("ERROR: Failed start the Clock task\n");
stop();
success = false;
}
}
sem_destroy(&GClockVars.waitSem);
sem_post(&GClockVars.exclSem);
return success;
}
/**
* This is the close window event handler. It will stop the Clock
* application thread.
*/
void CClock::stop(void)
{
// Delete the Clock task if it is still running (this could strand
// resources).
if (m_pid >= 0)
{
pid_t pid = m_pid;
m_pid = (pid_t)-1;
// Then delete the NSH task, possibly stranding resources
std::task_delete(pid);
}
}
/**
* This is the Clock task.
*/
int CClock::clock(int argc, char *argv[])
{
// Get the 'this' pointer
FAR CClock *This = GClockVars.that;
// Inform the parent thread that we successfully initialized
GClockVars.success = true;
sem_post(&GClockVars.waitSem);
// Loop forever. When the window is deleted, this task will be brutally
// terminated via task_delete()
for (; ; )
{
// Update the clock
This->update();
// Then sleep for a minute. REVISIT: It might make sense to check
// the time at a much higher rate?
sleep(60);
}
return EXIT_FAILURE; // Never get here
}
/**
* Handle Twm4Nx events. This overrides a method from CTwm4NXEvent
*
* @param eventmsg. The received NxWidget WINDOW event message.
* @return True if the message was properly handled. false is
* return on any failure.
*/
bool CClock::event(FAR struct SEventMsg *eventmsg)
{
bool success = true;
switch (eventmsg->eventID)
{
case EVENT_CLOCK_REDRAW: // Redraw event (doesn't happen)
redraw(); // Redraw the whole window
break;
case EVENT_CLOCK_CLOSE: // Window close event
stop(); // Stop the Clock thread
break;
default:
success = false;
break;
}
return success;
}
/**
* Update the Clock.
*/
void CClock::update(void)
{
// Get the current time.
FAR struct std::timespec ts;
int ret = std::clock_gettime(CLOCK_REALTIME, &ts);
if (ret < 0)
{
twmerr("ERROR: clock_gettime() failed\n");
return;
}
// Break out the time
//
// tm_hour - The number of hours past midnight, in the range 0 to 23.
// tm_min - The number of minutes after the hour, in the range 0 to 59.
struct std::tm tm;
if (std::gmtime_r(&ts.tv_sec, &tm) == (FAR struct std::tm *)0)
{
twmerr("ERROR: gmtime_r() failed\n");
return;
}
// Convert hours and minutes into SLCD segment codes
//
// Conversion of 24 hour clock (0-23) to twelve hour (1-12)
// 0 -> 12 6 -> 6 12 -> 12 18 -> 6
// 1 -> 1 7 -> 7 13 -> 1 19 -> 7
// 2 -> 2 8 -> 8 14 -> 2 20 -> 8
// 3 -> 3 9 -> 9 15 -> 3 21 -> 9
// 4 -> 4 10 -> 10 16 -> 4 22 -> 10
// 5 -> 5 11 -> 11 17 -> 5 23 -> 11
unsigned int hour = tm.tm_hour; /* Range 0-23 */
if (hour == 0)
{
hour = 12;
}
else if (hour > 12) /* Range 1-23 */
{
hour -= 12; /* Range 1-11 */
}
uint8_t codes[CLOCK_NDIGITS];
char ascii = (hour > 9) ? '1' : ' ';
m_slcd->convert(ascii, codes[0]);
ascii = (hour % 10) + '0';
m_slcd->convert(ascii, codes[1]);
ascii = (tm.tm_min / 10) + '0';
m_slcd->convert(ascii, codes[2]);
ascii = (tm.tm_min % 10) + '0';
m_slcd->convert(ascii, codes[3]);
struct nxgl_point_s pos;
pos.y = CONFIG_TWM4NX_CLOCK_VSPACING;
// Then show each segment LCD
2019-09-29 20:53:39 +02:00
// There might be a more efficient way to do this than to erase the entire
// SLCD on each update.
for (int i = 0; i < CLOCK_NDIGITS; i++)
{
if (m_digits[i].segments != codes[i])
{
pos.x = m_digits[i].xOffset;
m_slcd->erase(pos);
m_slcd->show(codes[i], pos);
m_digits[i].segments = codes[i];
}
}
}
/**
* Redraw the entire clock.
*/
void CClock::redraw(void)
{
// Get the size of the window
struct nxgl_size_s windowSize;
m_window->getWindowSize(&windowSize);
// Create a bounding box
struct nxgl_rect_s windowRect;
windowRect.pt1.x = 0;
windowRect.pt1.y = 0;
windowRect.pt2.x = windowSize.w - 1;
windowRect.pt2.y = windowSize.h - 1;
// Fill the entire window with the clock background color
FAR NXWidgets::INxWindow *nxWindow = m_window->getNxWindow();
if (!nxWindow->fill(&windowRect, SLCD_BACKGROUND))
{
twmerr("ERROR: Failed to fill window");
return;
}
// Reset the saved codes to force a complete update
for (int i = 0; i < CLOCK_NDIGITS; i++)
{
m_digits[i].segments = 0;
}
// Then update the Clock window
update();
}
/////////////////////////////////////////////////////////////////////////////
// CClockFactory Method Implementations
/////////////////////////////////////////////////////////////////////////////
/**
* CClockFactory Initializer. Performs parts of the instance
* construction that may fail. In this implementation, it will
* initialize the NSH library and register an menu item in the
* Main Menu.
*/
bool CClockFactory::initialize(FAR CTwm4Nx *twm4nx)
{
// Save the session instance
m_twm4nx = twm4nx;
// Initialize the parameter passing area. Other fields are initialized
// by CClock thread-specific logic.
sem_init(&GClockVars.exclSem, 0, 1);
// Register an entry with the Main menu. When selected, this will
// Case the start
FAR CMainMenu *cmain = twm4nx->getMainMenu();
return cmain->addApplication(this);
}
/**
* Handle CClockFactory events. This overrides a method from
* CTwm4NXEvent
*
* @param eventmsg. The received NxWidget WINDOW event message.
* @return True if the message was properly handled. false is
* return on any failure.
*/
bool CClockFactory::event(FAR struct SEventMsg *eventmsg)
{
bool success = true;
switch (eventmsg->eventID)
{
case EVENT_CLOCK_START: // Main menu selection
startFunction(); // Create a new Clock instance
break;
default:
success = false;
break;
}
return success;
}
/**
* Create and start a new instance of CClock.
*/
bool CClockFactory::startFunction(void)
{
// Instantiate the Nxterm application, providing only the session session
// instance to the constructor
FAR CClock *clock = new CClock(m_twm4nx);
if (clock == (FAR CClock *)0)
{
twmerr("ERROR: Failed to instantiate CClock\n");
return false;
}
// Initialize the Clock application
if (!clock->initialize())
{
twmerr("ERROR: Failed to initialize CClock instance\n");
delete clock;
return false;
}
// Start the Clock application instance
if (!clock->run())
{
twmerr("ERROR: Failed to start the Clock application\n");
delete clock;
return false;
}
return true;
}