6e15af3786
Signed-off-by: Xiang Xiao <xiaoxiang@xiaomi.com>
661 lines
17 KiB
C++
661 lines
17 KiB
C++
/////////////////////////////////////////////////////////////////////////////
|
|
// 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 <cunistd>
|
|
|
|
#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
|
|
// 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;
|
|
}
|