/**************************************************************************** * NxWidgets/nxwm/src/ccalibration.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. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include "nxwmconfig.hxx" #include "nxwmglyphs.hxx" #include "ctouchscreen.hxx" #include "ccalibration.hxx" /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ /**************************************************************************** * Configuration */ #ifndef CONFIG_NX # error "NX is not enabled (CONFIG_NX)" #endif /** * Positional/size data for the calibration lines and circles */ #define CALIBRATION_LEFTX 40 #define CALIBRATION_RIGHTX (windowSize.w - 41) #define CALIBRATION_TOPY 40 #define CALIBRATION_BOTTOMY (windowSize.h - 41) #define CALIBRATION_CIRCLE_RADIUS 16 #define CALIBRATION_LINE_THICKNESS 2 /**************************************************************************** * CCalibration Implementation Classes ****************************************************************************/ using namespace NxWM; /** * CCalibration Constructor * * @param taskbar. The taskbar instance used to terminate calibration * @param window. The window to use for the calibration display * @param touchscreen. An instance of the class that wraps the touchscreen * device. */ CCalibration::CCalibration(CTaskbar *taskbar, CFullScreenWindow *window, CTouchscreen *touchscreen) { // Initialize state data m_taskbar = taskbar; m_window = window; m_touchscreen = touchscreen; m_thread = 0; m_calthread = CALTHREAD_NOTRUNNING; m_calphase = CALPHASE_NOT_STARTED; m_touched = false; } /** * CCalibration Destructor */ CCalibration::~CCalibration(void) { // Make sure that the application is not running (it should already // have been stopped) stop(); // Although we did not create the window, the rule is that I have to dispose // of it delete m_window; } /** * Each implementation of IApplication must provide a method to recover * the contained IApplicationWindow instance. */ IApplicationWindow *CCalibration::getWindow(void) const { return static_cast(m_window); } /** * Get the icon associated with the application * * @return An instance if IBitmap that may be used to rend the * application's icon. This is an new IBitmap instance that must * be deleted by the caller when it is no long needed. */ NXWidgets::IBitmap *CCalibration::getIcon(void) { NXWidgets::CRlePaletteBitmap *bitmap = new NXWidgets::CRlePaletteBitmap(&CONFIG_NXWM_CALIBRATION_ICON); return bitmap; } /** * Get the name string associated with the application * * @return A copy if CNxString that contains the name of the application. */ NXWidgets::CNxString CCalibration::getName(void) { return NXWidgets::CNxString("Touchscreen Calibration"); } /** * Start the application (perhaps in the minimized state). * * @return True if the application was successfully started. */ bool CCalibration::run(void) { gvdbg("Starting calibration: m_calthread=%d\n", (int)m_calthread); return startCalibration(CALTHREAD_STARTED); } /** * Stop the application. */ void CCalibration::stop(void) { gvdbg("Stopping calibration: m_calthread=%d\n", (int)m_calthread); // Was the calibration thread created? if (m_thread != 0) { // Is the calibration thread running? if (isRunning()) { // The main thread is stuck waiting for the next touchscreen input... // We can signal that we would like the thread to stop, but we will be // stuck here until the next touch m_calthread = CALTHREAD_STOPREQUESTED; // Try to wake up the calibration thread so that it will see our // terminatin request gvdbg("Stopping calibration: m_calthread=%d\n", (int)m_calthread); (void)pthread_kill(m_thread, CONFIG_NXWM_CALIBRATION_SIGNO); } } } /** * Destroy the application and free all of its resources. This method * will initiate blocking of messages from the NX server. The server * will flush the window message queue and reply with the blocked * message. When the block message is received by CWindowMessenger, * it will send the destroy message to the start window task which * will, finally, safely delete the application. */ void CCalibration::destroy(void) { // Make sure that the application is stopped (should already be stopped) stop(); // Block any further window messages m_window->block(this); } /** * The application window is hidden (either it is minimized or it is * maximized, but it is not at the top of the hierarchy) */ void CCalibration::hide(void) { gvdbg("Entry\n"); // Is the calibration thread running? if (m_calthread == CALTHREAD_RUNNING || m_calthread == CALTHREAD_SHOW) { // Ask the calibration thread to hide the display m_calthread = CALTHREAD_HIDE; (void)pthread_kill(m_thread, CONFIG_NXWM_CALIBRATION_SIGNO); } } /** * Redraw the entire window. The application has been maximized or * otherwise moved to the top of the hierarchy. This method is called from * CTaskbar when the application window must be displayed */ void CCalibration::redraw(void) { gvdbg("Entry\n"); // Is the calibration thread running? We might have to restart it if // we have completed the calibration early but are being brought to // top of the display again // Is the calibration thread running? if (!isRunning()) { gvdbg("Starting calibration: m_calthread=%d\n", (int)m_calthread); (void)startCalibration(CALTHREAD_SHOW); } // The calibration thread is running. Make sure that is is not // already processing a redraw else if (m_calthread != CALTHREAD_SHOW) { // Ask the calibration thread to restart the calibration and redraw the display m_calthread = CALTHREAD_SHOW; (void)pthread_kill(m_thread, CONFIG_NXWM_CALIBRATION_SIGNO); } } /** * Report of this is a "normal" window or a full screen window. The * primary purpose of this method is so that window manager will know * whether or not it show draw the task bar. * * @return True if this is a full screen window. */ bool CCalibration::isFullScreen(void) const { return m_window->isFullScreen(); } /** * Accept raw touchscreen input. * * @param sample Touchscreen input sample */ void CCalibration::touchscreenInput(struct touch_sample_s &sample) { // Is this a new touch event? Or is it a drag event? if ((sample.point[0].flags & (TOUCH_DOWN|TOUCH_MOVE)) != 0) { // Yes.. but ignore drag events if we did not see the matching // touch down event if ((sample.point[0].flags & TOUCH_DOWN) != 0 || (m_touched && sample.point[0].id == m_touchId)) { // Yes.. save the touch position and wait for the TOUCH_UP report m_touchPos.x = sample.point[0].x; m_touchPos.y = sample.point[0].y; gvdbg("Touch id: %d flags: %02x x: %d y: %d h: %d w: %d pressure: %d\n", sample.point[0].id, sample.point[0].flags, sample.point[0].x, sample.point[0].y, sample.point[0].h, sample.point[0].w, sample.point[0].pressure); // Show calibration screen again, changing the color of the circle to // make it clear that the touch has been noticed. if (!m_touched) { m_screenInfo.circleFillColor = CONFIG_NXWM_CALIBRATION_TOUCHEDCOLOR; showCalibration(); m_touched = true; } // Remember the ID of the touch down event m_touchId = sample.point[0].id; } } // Was the touch released? else if ((sample.point[0].flags & TOUCH_UP) != 0) { // Yes.. did we see the pen down event? if (m_touched) { // Yes.. For the matching touch ID? if (sample.point[0].id == m_touchId) { // Yes.. invoke the state machine. gvdbg("State: %d Screen x: %d y: %d Touch x: %d y: %d\n", m_calphase, m_screenInfo.pos.x, m_screenInfo.pos.y, m_touchPos.x, m_touchPos.y); stateMachine(); } else { // No... restore the un-highlighted circle m_screenInfo.circleFillColor = CONFIG_NXWM_CALIBRATION_CIRCLECOLOR; showCalibration(); } } // In any event, the screen is not touched m_touched = false; } } /** * Start the calibration thread. * * @param initialState. The initial state of the calibration thread * @return True if the thread was successfully started. */ bool CCalibration::startCalibration(enum ECalThreadState initialState) { // Verify that the thread is not already running if (isRunning()) { gdbg("The calibration thread is already running\n"); return false; } // Configure the calibration thread pthread_attr_t attr; (void)pthread_attr_init(&attr); struct sched_param param; param.sched_priority = CONFIG_NXWM_CALIBRATION_LISTENERPRIO; (void)pthread_attr_setschedparam(&attr, ¶m); (void)pthread_attr_setstacksize(&attr, CONFIG_NXWM_CALIBRATION_LISTENERSTACK); // Set the initial state of the thread m_calthread = initialState; // Start the thread that will perform the calibration process int ret = pthread_create(&m_thread, &attr, calibration, (FAR void *)this); if (ret != 0) { gdbg("pthread_create failed: %d\n", ret); return false; } // Detach from the pthread so that we do not have any memory leaks (void)pthread_detach(m_thread); gvdbg("Calibration thread m_calthread=%d\n", (int)m_calthread); return true; } /** * The calibration thread. This is the entry point of a thread that provides the * calibration displays, waits for input, and collects calibration data. * * @param arg. The CCalibration 'this' pointer cast to a void*. * @return This function always returns NULL when the thread exits */ FAR void *CCalibration::calibration(FAR void *arg) { CCalibration *This = (CCalibration *)arg; bool stalled = true; // The calibration thread is now running This->m_calthread = CALTHREAD_RUNNING; This->m_calphase = CALPHASE_NOT_STARTED; gvdbg("Started: m_calthread=%d\n", (int)This->m_calthread); // Loop until calibration completes or we have been requested to terminate while (This->m_calthread != CALTHREAD_STOPREQUESTED && This->m_calphase != CALPHASE_COMPLETE) { // Check for state changes due to display order changes if (This->m_calthread == CALTHREAD_HIDE) { // This state is set by hide() when our display is no longer visible This->m_calthread = CALTHREAD_RUNNING; This->m_calphase = CALPHASE_NOT_STARTED; stalled = true; } else if (This->m_calthread == CALTHREAD_SHOW) { // This state is set by redraw() when our display has become visible This->m_calthread = CALTHREAD_RUNNING; This->m_calphase = CALPHASE_NOT_STARTED; stalled = false; This->stateMachine(); } // The calibration thread will stall if has been asked to hide the // display. While stalled, we will just sleep for a bit abd test // the state again. If we are re-awakened by a redraw(), then we // will be given a signal which will wake us up immediately. // // Note that stalled is also initially true so we have to receive // redraw() before we attempt to draw anything if (stalled) { // Sleep for a while (or until we receive a signal) std::usleep(500*1000); } else { // Wait for the next raw touchscreen input (or possibly a signal) struct touch_sample_s sample; while (!This->m_touchscreen->waitRawTouchData(&sample) && This->m_calthread == CALTHREAD_RUNNING); // Then process the raw touchscreen input if (This->m_calthread == CALTHREAD_RUNNING) { This->touchscreenInput(sample); } } } // Perform the final steps of calibration This->finishCalibration(); gvdbg("Terminated: m_calthread=%d\n", (int)This->m_calthread); return (FAR void *)0; } /** * This is the calibration state machine. It is called initially and then * as new touchscreen data is received. */ void CCalibration::stateMachine(void) { gvdbg("Old m_calphase=%d\n", m_calphase); // Recover the window instance contained in the full screen window NXWidgets::INxWindow *window = m_window->getWindow(); // Get the size of the fullscreen window struct nxgl_size_s windowSize; if (!window->getSize(&windowSize)) { return; } switch (m_calphase) { default: case CALPHASE_NOT_STARTED: { // Clear the entire screen // Get the widget control associated with the full screen window NXWidgets::CWidgetControl *control = window->getWidgetControl(); // Get the CCGraphicsPort instance for this window NXWidgets::CGraphicsPort *port = control->getGraphicsPort(); // Fill the entire window with the background color port->drawFilledRect(0, 0, windowSize.w, windowSize.h, CONFIG_NXWM_DEFAULT_BACKGROUNDCOLOR); // Then draw the first calibration screen m_screenInfo.pos.x = CALIBRATION_LEFTX; m_screenInfo.pos.y = CALIBRATION_TOPY; m_screenInfo.lineColor = CONFIG_NXWM_CALIBRATION_LINECOLOR; m_screenInfo.circleFillColor = CONFIG_NXWM_CALIBRATION_CIRCLECOLOR; showCalibration(); // Then set up the current state m_calphase = CALPHASE_UPPER_LEFT; } break; case CALPHASE_UPPER_LEFT: { // A touch has been received while in the CALPHASE_UPPER_LEFT state. // Save the touch data and set up the next calibration display m_calibData[CALIB_UPPER_LEFT_INDEX].x = m_touchPos.x; m_calibData[CALIB_UPPER_LEFT_INDEX].y = m_touchPos.y; // Clear the previous screen by re-drawing it using the backgro9und // color. That is much faster than clearing the whole display m_screenInfo.lineColor = CONFIG_NXWM_CALIBRATION_BACKGROUNDCOLOR; m_screenInfo.circleFillColor = CONFIG_NXWM_CALIBRATION_BACKGROUNDCOLOR; showCalibration(); // Then draw the next calibration screen m_screenInfo.pos.x = CALIBRATION_RIGHTX; m_screenInfo.pos.y = CALIBRATION_TOPY; m_screenInfo.lineColor = CONFIG_NXWM_CALIBRATION_LINECOLOR; m_screenInfo.circleFillColor = CONFIG_NXWM_CALIBRATION_CIRCLECOLOR; showCalibration(); // Then set up the current state m_calphase = CALPHASE_UPPER_RIGHT; } break; case CALPHASE_UPPER_RIGHT: { // A touch has been received while in the CALPHASE_UPPER_RIGHT state. // Save the touch data and set up the next calibration display m_calibData[CALIB_UPPER_RIGHT_INDEX].x = m_touchPos.x; m_calibData[CALIB_UPPER_RIGHT_INDEX].y = m_touchPos.y; // Clear the previous screen by re-drawing it using the backgro9und // color. That is much faster than clearing the whole display m_screenInfo.lineColor = CONFIG_NXWM_CALIBRATION_BACKGROUNDCOLOR; m_screenInfo.circleFillColor = CONFIG_NXWM_CALIBRATION_BACKGROUNDCOLOR; showCalibration(); // Then draw the next calibration screen m_screenInfo.pos.x = CALIBRATION_RIGHTX; m_screenInfo.pos.y = CALIBRATION_BOTTOMY; m_screenInfo.lineColor = CONFIG_NXWM_CALIBRATION_LINECOLOR; m_screenInfo.circleFillColor = CONFIG_NXWM_CALIBRATION_CIRCLECOLOR; showCalibration(); // Then set up the current state m_calphase = CALPHASE_LOWER_RIGHT; } break; case CALPHASE_LOWER_RIGHT: { // A touch has been received while in the CALPHASE_LOWER_RIGHT state. // Save the touch data and set up the next calibration display m_calibData[CALIB_LOWER_RIGHT_INDEX].x = m_touchPos.x; m_calibData[CALIB_LOWER_RIGHT_INDEX].y = m_touchPos.y; // Clear the previous screen by re-drawing it using the backgro9und // color. That is much faster than clearing the whole display m_screenInfo.lineColor = CONFIG_NXWM_CALIBRATION_BACKGROUNDCOLOR; m_screenInfo.circleFillColor = CONFIG_NXWM_CALIBRATION_BACKGROUNDCOLOR; showCalibration(); // Then draw the next calibration screen m_screenInfo.pos.x = CALIBRATION_LEFTX; m_screenInfo.pos.y = CALIBRATION_BOTTOMY; m_screenInfo.lineColor = CONFIG_NXWM_CALIBRATION_LINECOLOR; m_screenInfo.circleFillColor = CONFIG_NXWM_CALIBRATION_CIRCLECOLOR; showCalibration(); // Then set up the current state m_calphase = CALPHASE_LOWER_LEFT; } break; case CALPHASE_LOWER_LEFT: { // A touch has been received while in the CALPHASE_LOWER_LEFT state. // Save the touch data and set up the next calibration display m_calibData[CALIB_LOWER_LEFT_INDEX].x = m_touchPos.x; m_calibData[CALIB_LOWER_LEFT_INDEX].y = m_touchPos.y; // Clear the previous screen by re-drawing it using the backgro9und // color. That is much faster than clearing the whole display m_screenInfo.lineColor = CONFIG_NXWM_CALIBRATION_BACKGROUNDCOLOR; m_screenInfo.circleFillColor = CONFIG_NXWM_CALIBRATION_BACKGROUNDCOLOR; showCalibration(); // Inform any waiter that calibration is complete m_calphase = CALPHASE_COMPLETE; } break; case CALPHASE_COMPLETE: // Might happen... do nothing if it does break; } gvdbg("New m_calphase=%d Screen x: %d y: %d\n", m_calphase, m_screenInfo.pos.x, m_screenInfo.pos.y); } /** * Presents the next calibration screen * * @param screenInfo Describes the next calibration screen */ void CCalibration::showCalibration(void) { // Recover the window instance contained in the full screen window NXWidgets::INxWindow *window = m_window->getWindow(); // Get the widget control associated with the full screen window NXWidgets::CWidgetControl *control = window->getWidgetControl(); // Get the CCGraphicsPort instance for this window NXWidgets::CGraphicsPort *port = control->getGraphicsPort(); // Get the size of the fullscreen window struct nxgl_size_s windowSize; if (!window->getSize(&windowSize)) { return; } // Draw the circle at the center of the touch position port->drawFilledCircle(&m_screenInfo.pos, CALIBRATION_CIRCLE_RADIUS, m_screenInfo.circleFillColor); /* Draw horizontal line */ port->drawFilledRect(0, m_screenInfo.pos.y, windowSize.w, CALIBRATION_LINE_THICKNESS, m_screenInfo.lineColor); /* Draw vertical line */ port->drawFilledRect(m_screenInfo.pos.x, 0, CALIBRATION_LINE_THICKNESS, windowSize.h, m_screenInfo.lineColor); } /** * Finish calibration steps and provide the calibration data to the * touchscreen driver. */ void CCalibration::finishCalibration(void) { // Did we finish calibration successfully? if (m_calphase == CALPHASE_COMPLETE) { // Yes... Get the final Calibration data struct SCalibrationData caldata; if (createCalibrationData(caldata)) { // And provide this to the touchscreen, enabling touchscreen processing m_touchscreen->setEnabled(false); m_touchscreen->setCalibrationData(caldata); m_touchscreen->setEnabled(true); } } // Remove the touchscreen application from the taskbar m_taskbar->stopApplication(this); // And set the terminated stated m_calthread = CALTHREAD_TERMINATED; } /** * Given the raw touch data collected by the calibration thread, create the * massaged calibration data needed by CTouchscreen. * * @param data. A reference to the location to save the calibration data * @return True if the calibration data was successfully created. */ bool CCalibration::createCalibrationData(struct SCalibrationData &data) { // Recover the window instance contained in the full screen window NXWidgets::INxWindow *window = m_window->getWindow(); // Get the size of the fullscreen window struct nxgl_size_s windowSize; if (!window->getSize(&windowSize)) { gdbg("NXWidgets::INxWindow::getSize failed\n"); return false; } // Calculate the calibration parameters // // (scaledX - LEFTX) / (rawX - leftX) = (RIGHTX - LEFTX) / (rightX - leftX) // scaledX = (rawX - leftX) * (RIGHTX - LEFTX) / (rightX - leftX) + LEFTX // = rawX * xSlope + (LEFTX - leftX * xSlope) // = rawX * xSlope + xOffset // // where: // xSlope = (RIGHTX - LEFTX) / (rightX - leftX) // xOffset = (LEFTX - leftX * xSlope) b16_t leftX = (m_calibData[CALIB_UPPER_LEFT_INDEX].x + m_calibData[CALIB_LOWER_LEFT_INDEX].x) << 15; b16_t rightX = (m_calibData[CALIB_UPPER_RIGHT_INDEX].x + m_calibData[CALIB_LOWER_RIGHT_INDEX].x) << 15; data.xSlope = b16divb16(itob16(CALIBRATION_RIGHTX - CALIBRATION_LEFTX), (rightX - leftX)); data.xOffset = itob16(CALIBRATION_LEFTX) - b16mulb16(leftX, data.xSlope); gdbg("New xSlope: %08x xOffset: %08x\n", data.xSlope, data.xOffset); // Similarly for Y // // (scaledY - TOPY) / (rawY - topY) = (BOTTOMY - TOPY) / (bottomY - topY) // scaledY = (rawY - topY) * (BOTTOMY - TOPY) / (bottomY - topY) + TOPY // = rawY * ySlope + (TOPY - topY * ySlope) // = rawY * ySlope + yOffset // // where: // ySlope = (BOTTOMY - TOPY) / (bottomY - topY) // yOffset = (TOPY - topY * ySlope) b16_t topY = (m_calibData[CALIB_UPPER_LEFT_INDEX].y + m_calibData[CALIB_UPPER_RIGHT_INDEX].y) << 15; b16_t bottomY = (m_calibData[CALIB_LOWER_LEFT_INDEX].y + m_calibData[CALIB_LOWER_RIGHT_INDEX].y) << 15; data.ySlope = b16divb16(itob16(CALIBRATION_BOTTOMY - CALIBRATION_TOPY), (bottomY - topY)); data.yOffset = itob16(CALIBRATION_TOPY) - b16mulb16(topY, data.ySlope); gdbg("New ySlope: %08x yOffset: %08x\n", data.ySlope, data.yOffset); return true; } /** * CCalibrationFactory Constructor * * @param taskbar. The taskbar instance used to terminate calibration * @param touchscreen. An instance of the class that wraps the * touchscreen device. */ CCalibrationFactory::CCalibrationFactory(CTaskbar *taskbar, CTouchscreen *touchscreen) { m_taskbar = taskbar; m_touchscreen = touchscreen; } /** * Create a new instance of an CCalibration (as IApplication). */ IApplication *CCalibrationFactory::create(void) { // Call CTaskBar::openFullScreenWindow to create a full screen window for // the calibation application CFullScreenWindow *window = m_taskbar->openFullScreenWindow(); if (!window) { gdbg("ERROR: Failed to create CFullScreenWindow\n"); return (IApplication *)0; } // Open the window (it is hot in here) if (!window->open()) { gdbg("ERROR: Failed to open CFullScreenWindow \n"); delete window; return (IApplication *)0; } // Instantiate the application, providing the window to the application's // constructor CCalibration *calibration = new CCalibration(m_taskbar, window, m_touchscreen); if (!calibration) { gdbg("ERROR: Failed to instantiate CCalibration\n"); delete window; return (IApplication *)0; } return static_cast(calibration); } /** * Get the icon associated with the application * * @return An instance if IBitmap that may be used to rend the * application's icon. This is an new IBitmap instance that must * be deleted by the caller when it is no long needed. */ NXWidgets::IBitmap *CCalibrationFactory::getIcon(void) { NXWidgets::CRlePaletteBitmap *bitmap = new NXWidgets::CRlePaletteBitmap(&CONFIG_NXWM_CALIBRATION_ICON); return bitmap; }