/* 
 * Copyright (c) 2009, 2011, Oracle and/or its affiliates. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; version 2 of the
 * License.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301  USA
 */

#include "stdafx.h"


#define _USE_MATH_DEFINES
#include <math.h>
#include <vector>

using namespace std;

using namespace mforms;
using namespace MySQL::Geometry;

// Animation timer. One timer for all widget animations.
static int animation_timer_refcount= 0;
static GStaticMutex animation_timer_mutex = G_STATIC_MUTEX_INIT;
static int animation_timer_id;
static vector<BaseWidget*> animated_widgets;

#ifdef __APPLE__
  #define WIDGET_FONT "Lucida Grande"
  #define WIDGET_SMALL_FONT "Lucida Grande"

  // The HTML style color is for mforms control, the components used in cairo.
  // Both must be the same color otherwise display will be wrong.
  #define BACKGROUND_COLOR "#DBD9D9"
  #define BK_RED 219 / 255.0
  #define BK_GREEN 217 / 255.0
  #define BK_BLUE 217 / 255.0
#elif _WIN32
  #define WIDGET_FONT "Tahoma"
  #define WIDGET_SMALL_FONT "Arial"

  #define BACKGROUND_COLOR "#FFFFFF"
  #define BK_RED 1
  #define BK_GREEN 1
  #define BK_BLUE 1
#else
  #define WIDGET_FONT "Helvetica"
  #define WIDGET_SMALL_FONT "Helvetica"

  #define BACKGROUND_COLOR "#DBD9D9"
  #define BK_RED 219 / 255.0
  #define BK_GREEN 217 / 255.0
  #define BK_BLUE 217 / 255.0
#endif

#define WIDGET_NORMAL_FONT_SIZE 11
#define WIDGET_DESCRIPTION_FONT_SIZE 9
#define WIDGET_WARNING_FONT_SIZE 8

//--------------------------------------------------------------------------------------------------

/**
 * Animation timer callback. Triggers all registered step() methods.
 */
static bool on_timer(int task_id, void* owner)
{
  g_static_mutex_lock(&animation_timer_mutex);
  
  for (vector<BaseWidget*>::const_iterator iterator= animated_widgets.begin(); iterator != animated_widgets.end(); iterator++)
    (*iterator)->step();
  
  g_static_mutex_unlock(&animation_timer_mutex);
  
  return false;
}

//--------------------------------------------------------------------------------------------------

/**
 * Starts the animation timer if not yet done and increases the ref count for it, so it does not
 * get freed before the last consumer is gone.
 */
static void start_animation_timer_for(BaseWidget* widget)
{
  g_static_mutex_lock(&animation_timer_mutex);

  animated_widgets.push_back(widget);
  if (animation_timer_refcount == 0)
    animation_timer_id= ThreadedTimer::add_task(TimerFrequency, 30, false, on_timer, NULL);
  animation_timer_refcount++;
  
  g_static_mutex_unlock(&animation_timer_mutex);
}

//--------------------------------------------------------------------------------------------------

/**
 * Decreases the animation timer ref count and frees the timer if no consumer is left.
 */
static void stop_animation_timer_for(BaseWidget* widget)
{
  g_static_mutex_lock(&animation_timer_mutex);
  
  for (vector<BaseWidget*>::iterator iterator= animated_widgets.begin(); iterator != animated_widgets.end(); iterator++)
    if (*iterator == widget)
    {
      animated_widgets.erase(iterator);
      break;
    }
  
  if (animation_timer_refcount <= 0)
    g_warning("Unbalanced feedback timer deactivation in LineDiagramWidget.");
  animation_timer_refcount--;
  if (animation_timer_refcount <= 0)
    ThreadedTimer::remove_task(animation_timer_id);
  
  g_static_mutex_unlock(&animation_timer_mutex);
}

//--------------------------------------------------------------------------------------------------

BaseWidget::BaseWidget()
: _background(NULL), _description(""), _description_height(0)
{
  g_static_mutex_init(&_lock);
  _lower_limit= 0;
  _upper_limit= 1;
  _auto_scale= false;
  _layout_width= 100;
  _layout_height= 50;
  _layout_surface= NULL;
  _layout_context= NULL;
}

//--------------------------------------------------------------------------------------------------

BaseWidget::~BaseWidget()
{
  destroy_background();
  g_static_mutex_free(&_lock);
}

//--------------------------------------------------------------------------------------------------

void BaseWidget::lock()
{
  g_static_mutex_lock(&_lock);
}

//--------------------------------------------------------------------------------------------------

void BaseWidget::unlock()
{
  g_static_mutex_unlock(&_lock);
}

//--------------------------------------------------------------------------------------------------

void BaseWidget::destroy_background()
{
  if (_background != NULL)
  {
    cairo_surface_destroy(_background);
    _background= NULL;
  }
  if (_layout_surface != NULL)
  {
    cairo_surface_destroy(_layout_surface);
    _layout_surface= NULL;
  }
  if (_layout_context != NULL)
  {
    cairo_destroy(_layout_context);
    _layout_context= NULL;
  }
}

//--------------------------------------------------------------------------------------------------

/**
 * Returns the usable area for the core widget area, i.e. where descendants can draw into.
 * It excludes the area needed to draw the description.
 */
Rect BaseWidget::get_diagram_area()
{
  create_context_for_layout();
  layout(_layout_context);
  
  Rect result(0, 0, get_width(), get_height());
  result.size.height -= _description_height;
  
  return result;
}

//--------------------------------------------------------------------------------------------------

/**
 * Normalizes the input value to the range [0..1] depending on the set input value range.
 */
double BaseWidget::normalize(double input)
{
  if (_upper_limit - _lower_limit == 0)
    return 0;
  
  if (input < _lower_limit)
    input= _lower_limit;
  if (input > _upper_limit)
    input= _upper_limit;
  return (input - _lower_limit) / (_upper_limit - _lower_limit);
}

//--------------------------------------------------------------------------------------------------

/**
 * Computes new value ranges for the widget if necessary.
 *
 * @result True if the ranges were updated, otherwise false.
 */
bool BaseWidget::compute_scale(double min, double max)
{
  double new_upper= _upper_limit;
  double new_lower= _lower_limit;

  // Find the lowest threshold which is greater than the currently highest value.
  // This will become the new upper limit. Similar for the lower limit.
  for (ThresholdList::const_iterator iterator= _upper_thresholds.begin();
       iterator != _upper_thresholds.end(); iterator++)
    if (*iterator > max)
    {
      new_upper= *iterator;
      break;
    }
  for (ThresholdList::const_iterator iterator= _lower_thresholds.begin();
       iterator != _lower_thresholds.end(); iterator++)
    if (*iterator < min)
    {
      new_lower= *iterator;
      break;
    }
  
  bool result= (new_upper != _upper_limit) || (new_lower != _lower_limit);
  if (result)
    set_value_range(new_lower, new_upper);

  return result;
}

//--------------------------------------------------------------------------------------------------

/**
 * Updates the value range depending on the auto scale mode and what is the
 * currently largest value in the widget.
 */
void BaseWidget::auto_scale(double value)
{
  if (_auto_scale)
  {
    double min, max; 
    get_minmax_values(&min, &max);
    min= min * (_upper_limit - _lower_limit) - _lower_limit;
    max= max * (_upper_limit - _lower_limit) - _lower_limit;
    if (value > max)
      max= value;
    if (value < min)
      min= value;
    compute_scale(min, max);
  }
}

//--------------------------------------------------------------------------------------------------

void BaseWidget::get_minmax_values(double* min, double* max)
{
  *min= 0;
  *max= 0;
}

//--------------------------------------------------------------------------------------------------

void BaseWidget::enable_auto_scale(bool enable)
{
  lock();
  _auto_scale= enable; // Change will be visible when next value comes in.
  unlock();
}

//--------------------------------------------------------------------------------------------------

/**
 * Allows to specify in what range input values are to be expected. This range will be used to 
 * normalize input values and to clamp them if they are outside it.
 *
 * @param low The lower bound of the input value range. Can be less than zero. Must be less than the high bound.
 * @param high The upper bound of the input value range. Can be less than zero. Must be more than the lower bound.
 */
void BaseWidget::set_value_range(double low, double high)
{
  if (low <= high && (low != _lower_limit || high != _upper_limit))
  {
    // Precompute transformation factors for recomputing existing values.
    // To compute the original value we have:
    //   (I)   O = Vo * (Uo - Lo) + Lo    (value-old, upper-old, lower-old).
    // To compute the new value from the original value we do (see also normalize()):
    //   (II)  Vn = (O - Ln) / (Un - Ln)   (value-new, lower-new, upper-new).
    // Inserting (I) into (II) gives the two factors we will pass on.
    double scale= (low == high) ? 0 : (_upper_limit - _lower_limit) / (high - low);
    double offset= (low == high) ? 0 : (_lower_limit - low) / (high - low);

    _lower_limit= low;
    _upper_limit= high;
   
    range_updated(scale, offset);
    
    set_needs_repaint();
  }
}

//--------------------------------------------------------------------------------------------------

void BaseWidget::set_thresholds(ThresholdList lower_thresholds, ThresholdList upper_thresholds)
{
  // Changes in thresholds are visible on next auto scale and repaint.
  _lower_thresholds= lower_thresholds;
  _upper_thresholds= upper_thresholds;
  set_needs_repaint();
}

//--------------------------------------------------------------------------------------------------

double BaseWidget::get_upper_range() const
{
  return _upper_limit;
}

//--------------------------------------------------------------------------------------------------

void BaseWidget::set_description(const std::string& description)
{
  if (_description != description)
  {
    _description= description;
    create_context_for_layout();
    if (layout(_layout_context))
    {
      set_layout_dirty(true);
      relayout();
    }
    set_needs_repaint();
  }
}

//--------------------------------------------------------------------------------------------------

void BaseWidget::repaint(cairo_t *cr, int areax, int areay, int areaw, int areah)
{
  if (is_layout_dirty())
    layout(cr);
  
  prepare_background();
  
  lock();

  // Now draw background graphics created by the widgets.
  if (_background != NULL)
  {
    cairo_set_source_surface(cr, _background, 0, 0);
    cairo_paint(cr);
  }

  // Erase background behind the description.
//  cairo_set_source_rgb(cr, BK_RED, BK_GREEN, BK_BLUE); // Same as the widget box.
//  cairo_rectangle(cr, 0, get_height() - _description_height, get_width(), _description_height);
//  cairo_fill(cr);

  if (_description != "")
  {
    cairo_select_font_face(cr, WIDGET_SMALL_FONT, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);
    cairo_set_font_size(cr, WIDGET_DESCRIPTION_FONT_SIZE);

    cairo_set_source_rgb(cr, 66 / 255.0, 73 / 255.0, 79 / 255.0);
    cairo_move_to(cr, 0, get_height() - 6); // Small extra offset for bottom spacing.
    cairo_show_text(cr, _description.c_str());
    cairo_stroke(cr);
  }
  unlock();
}

//--------------------------------------------------------------------------------------------------

/**
 * Returns the computed size for this widget.
 */
void BaseWidget::get_layout_size(int* w, int* h)
{
  if (is_layout_dirty())
  {
    create_context_for_layout();
    layout(_layout_context);
  }
  
  *w= _layout_width;
  *h= _layout_height;
}

//--------------------------------------------------------------------------------------------------

/**
 * Computes the layout of the widget description, determining so its size.
 * 
 * @result Returns true if size did change, otherwise false.
 */
bool BaseWidget::layout(cairo_t *cr)
{
  lock();
  
  bool result= false;
  if (_description != "")
  {
    cairo_select_font_face(cr, WIDGET_SMALL_FONT, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);
    cairo_set_font_size(cr, WIDGET_DESCRIPTION_FONT_SIZE);
    
    cairo_text_extents_t extents;
    cairo_text_extents(cr, _description.c_str(), &extents);
    int new_height= (int) ceil(extents.height + 8);
    if (new_height != _description_height)
    {
      _description_height= new_height;
      result= true;
    }
    
    int new_width= (int) (extents.x_advance + extents.x_bearing);
    if (new_width > _layout_width)
    {
      _layout_width= new_width; // Grow only, otherwise the layout might constantly change.
      result= true;
    }
  }
  unlock();
  
  return result;
}

//--------------------------------------------------------------------------------------------------

/**
 * Creates a cairo context on a small image surface, to be used for layouting.
 */
void BaseWidget::create_context_for_layout()
{
  if (_layout_surface == NULL)
    _layout_surface= cairo_image_surface_create(CAIRO_FORMAT_ARGB32, get_width(), get_height());
  if (_layout_context == NULL)
    _layout_context= cairo_create(_layout_surface);
}

//----------------- WidgetSeparator ----------------------------------------------------------------

void WidgetSeparator::repaint(cairo_t *cr, int areax, int areay, int areaw, int areah)
{
//  cairo_set_source_rgb(cr, BK_RED, BK_GREEN, BK_BLUE); // Same as the widget box.
//  cairo_rectangle(cr, 0, 0, get_width(), get_height());
//  cairo_fill(cr);

  double offset= get_width() / 2;

  cairo_set_line_width(cr, 1);

  cairo_set_source_rgb(cr, 179 / 255.0, 179 / 255.0, 179 / 255.0);
  cairo_move_to(cr, offset, 0);
  cairo_line_to(cr, offset, get_height());
  cairo_stroke(cr);

  cairo_set_source_rgb(cr, 240 / 255.0, 240 / 255.0, 240 / 255.0);
  cairo_move_to(cr, offset + 0.5, 0);
  cairo_line_to(cr, offset + 0.5, get_height());
  cairo_stroke(cr);
}

//----------------- WidgetContainer ----------------------------------------------------------------

WidgetContainer::WidgetContainer(const std::string& title)
: Box(false), _title(title), _content(true)
{
//  set_back_color(BACKGROUND_COLOR);

  set_padding(4);
  //set_spacing(4);

  _title.set_style(BoldInfoCaptionStyle);
  _title.set_wrap_text(false);
  _title.set_color("#42494F");
  add(&_title, false, true);

  _content.set_padding(4);
  _content.set_spacing(8);
  add(&_content, true, true);
}

//--------------------------------------------------------------------------------------------------

WidgetContainer::~WidgetContainer()
{
}

//--------------------------------------------------------------------------------------------------

void WidgetContainer::add_widget(BaseWidget* widget, bool expand)
{
  _content.add(widget, expand, true);
}

//--------------------------------------------------------------------------------------------------

void WidgetContainer::set_title(const std::string& title)
{
  _title.set_text(title);
}

//----------------- HeartbeatWidget ----------------------------------------------------------------

HeartbeatWidget::HeartbeatWidget()
  : BaseWidget()
{
  memset(_luminance, 0, sizeof(_luminance));
  memset(_deflection, 0, sizeof(_deflection));
  _pivot= 0;
  start_animation_timer_for(this);
}

//--------------------------------------------------------------------------------------------------

HeartbeatWidget::~HeartbeatWidget()
{
  stop_animation_timer_for(this);
}

//--------------------------------------------------------------------------------------------------

/**
 * Sets the heartbeat value at the current pivot position. The value must be in the range of 0..1.
 */
void HeartbeatWidget::set_value(double value)
{
  value= normalize(value);

  lock();
  
  // Set the deflection at the pivot point to the new value.
  _deflection[_pivot]= value;

  unlock();
}

//--------------------------------------------------------------------------------------------------

/**
 * Computes the values for the next paint operation and triggers invalidation so that
 * the widget is redrawn.
 */
void HeartbeatWidget::step()
{
  lock();
  
  // Decrease luminance each point by one step to simulate a fade-out.
  // Go backwards and stop when we reach the first 0 value.
  static double luminance_step= 1.5 / HEARTBEAT_DATA_SIZE;
  int index= _pivot - 1;
  while (true)
  {
    if (index < 0)
      index= HEARTBEAT_DATA_SIZE - 1;
    if (index == _pivot)
      break;

    _luminance[index] -= luminance_step;
    if (_luminance[index] < 0)
      _luminance[index]= 0;
    if (_luminance[index] == 0)
      break;
    index--;
  }

  _luminance[_pivot]= 1;

  // Precompute next deflection (simulate swing-out) and move pivot point.
  double current_value= _deflection[_pivot];
  _pivot++;
  if (_pivot == HEARTBEAT_DATA_SIZE)
    _pivot= 0;
  
  _deflection[_pivot]= -0.5 * current_value;

  unlock();

  set_needs_repaint();
}

//--------------------------------------------------------------------------------------------------

/**
 * Creates the static background (including the dotted graph lines) if not yet done or if the
 * size of the control changed.
 */
void HeartbeatWidget::prepare_background()
{
  Rect bounds= get_diagram_area();

  if (_background == NULL 
    || cairo_image_surface_get_width(_background) != bounds.width()
    || cairo_image_surface_get_height(_background) != bounds.height())
  {
    destroy_background();
    _background= cairo_image_surface_create(CAIRO_FORMAT_ARGB32, (int) bounds.width(), (int) bounds.height());
    cairo_t* cr= cairo_create(_background);

    // Erase background to constant color.
    cairo_set_source_rgb(cr, 48 / 255.0, 54 / 255.0, 59 / 255.0);
    cairo_paint(cr);
    
    // Top line is a gradient.
    cairo_pattern_t* gradient = cairo_pattern_create_linear(0, 0, 0, 5);
    cairo_pattern_add_color_stop_rgba(gradient, 0, 1, 1, 1, 0.2);
    cairo_pattern_add_color_stop_rgba(gradient, 1, 1, 1, 1, 0);
    
    cairo_set_source(cr, gradient);
    cairo_set_line_width(cr, 5);
    cairo_move_to(cr, 2.5, 3);
    cairo_line_to(cr, bounds.width() - 2.5, 3);
    cairo_stroke(cr);
    
    cairo_pattern_destroy(gradient);
    
    // Dotted graph lines.
    double dashes[] = {3.0, 2.0};
    
    cairo_set_dash(cr, dashes, 2, 0);
    cairo_set_source_rgb(cr, 72 / 255.0, 78 / 255.0, 83 / 255.0);
    cairo_set_line_width(cr, 1);
    
    double y= 4.5;
    while (y < bounds.height())
    {
      cairo_move_to(cr, 4.5, y);
      cairo_line_to(cr, bounds.width() - 4.5, y);
      y += 7;
    }
    cairo_stroke(cr);

    cairo_destroy(cr);
  }
}

//--------------------------------------------------------------------------------------------------

/**
 * Called when the user changed the value range. Transform existing values into that new range.
 * The given parameters are precomputed values that allow to do a simple transformation.
 */
void HeartbeatWidget::range_updated(double scale, double offset)
{
  lock();
  for (int i= 0; i < HEARTBEAT_DATA_SIZE; i++)
    _deflection[i]= _deflection[i] * scale + offset;
    
  unlock();
}

//--------------------------------------------------------------------------------------------------

void HeartbeatWidget::get_minmax_values(double* min, double* max)
{
  *min= 0;
  *max= 0;

  lock();
  for (int i= 0; i < HEARTBEAT_DATA_SIZE; i++)
  {
    if (_deflection[i] > *max)
      *max= _deflection[i];
    if (_deflection[i] < *min)
      *min= _deflection[i];
  }
  
  unlock();
}

//--------------------------------------------------------------------------------------------------

void HeartbeatWidget::repaint(cairo_t *cr, int areax, int areay, int areaw, int areah)
{
  BaseWidget::repaint(cr, areax, areay, areaw, areah);

  Rect bounds= get_diagram_area();

  // Draw the heartbeat line.
  cairo_set_line_width(cr, 2);
  cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);

  cairo_move_to(cr, 8, bounds.height() / 2);
  bounds.size.width -= 16; // Leave 8 pixels room left and right.

  lock();
  for (double pixel= 0; pixel < bounds.width(); pixel++)
  {
    // Transform pixel index into data array index. Merge values for fractions.
    int index1= (int) floor(pixel * HEARTBEAT_DATA_SIZE / bounds.width());
    int index2= index1 + 1;
    if (index2 == HEARTBEAT_DATA_SIZE)
      index2= 0;
    double fraction= (pixel * HEARTBEAT_DATA_SIZE / bounds.width()) - index1;
    
    double deflection= _deflection[index1] * (1 - fraction) + _deflection[index2] * fraction;
    double luminance= _luminance[index1] * (1 - fraction) + _luminance[index2] * fraction;
    
    double x= 8.5 + pixel;
    double y= -deflection * (bounds.height() - 8) / 2 + bounds.height() / 2;
    cairo_set_source_rgba(cr, 102 / 255.0, 171 / 255.0, 251 / 255.0, luminance);
    cairo_line_to(cr, x, y);
    cairo_stroke(cr);
    
    cairo_move_to(cr, x, y);
  }
  unlock();
}

//----------------- ServerInfoWidget ---------------------------------------------------------------

ServerInfoWidget::ServerInfoWidget()
{
  _status= -1;
  _instance= "Localhost 5.1";
  _ip_address= "127.0.0.1";
  _server= "MySQL 5.1.32 via socket";
  _image_unknown= Utilities::load_icon("admin_info_unknown.png");
  _image_running= Utilities::load_icon("admin_info_running.png");
  _image_stopped= Utilities::load_icon("admin_info_stopped.png");
}

//--------------------------------------------------------------------------------------------------

ServerInfoWidget::~ServerInfoWidget()
{
 cairo_surface_destroy(_image_unknown);
 cairo_surface_destroy(_image_running);
 cairo_surface_destroy(_image_stopped);
}

//--------------------------------------------------------------------------------------------------

void ServerInfoWidget::set_server_info(const std::string& instance, 
                                       const std::string& ip_address,
                                       const std::string& server)
{
  if (_instance != instance || _ip_address != ip_address || _server != server)
  {
    lock();
    _instance= instance;
    _ip_address= ip_address;
    _server= server;

    set_layout_dirty(true);
    set_needs_repaint();
    unlock();
  }
}

//--------------------------------------------------------------------------------------------------

void ServerInfoWidget::set_server_status(int status)
{
  // Sanity check.
  if (status < -1 || status > 1)
    status= -1;

  lock();
  if (_status != status)
  {
    _status= status;
    set_layout_dirty(true);
    set_needs_repaint();
  }
  unlock();
}

//--------------------------------------------------------------------------------------------------

#define ICON_OFFSET 15 // Distance between left border and icon.
#define SPACING 16     // Horizontal distance between icon and text.
#define TEXT_SPACING 8 // Distance between variable and fixed text.
#define LINE_SPACING 2 // Extra spacing between lines.

void ServerInfoWidget::repaint(cairo_t *cr, int areax, int areay, int areaw, int areah)
{
  BaseWidget::repaint(cr, areax, areay, areaw, areah);

  lock();

  cairo_save(cr);

  // Erase background.
//  cairo_set_source_rgb(cr, BK_RED, BK_GREEN, BK_BLUE);
//  cairo_paint(cr);

  cairo_surface_t* icon;
  std::string status_text;
  switch (_status)
  {
    case 1:
      icon= _image_running;
      status_text= "Running";
      break;
    case 0:
      icon= _image_stopped;
      status_text= "Stopped";
      break;
    default:
      icon= _image_unknown;
      status_text= "Unknown";
  }

  // Variable text.
  cairo_select_font_face(cr, WIDGET_FONT, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);
  cairo_set_font_size(cr, WIDGET_NORMAL_FONT_SIZE);
  double y= _content_top;
  cairo_set_source_rgb(cr, 25 / 255.0, 25 / 255.0, 25 / 255.0);
  cairo_move_to(cr, _var_text_left, y);
  cairo_show_text(cr, _instance.c_str());
  cairo_stroke(cr);
  y += _text_y_advance;
  cairo_move_to(cr, _var_text_left, y);
  cairo_show_text(cr, _ip_address.c_str());
  cairo_stroke(cr);
  y += _text_y_advance;
  cairo_move_to(cr, _var_text_left, y);
  cairo_show_text(cr, _server.c_str());
  cairo_stroke(cr);
  y += _text_y_advance;
  cairo_move_to(cr, _var_text_left, y);
  cairo_show_text(cr, status_text.c_str());
  cairo_stroke(cr);

  // Icon.
  if (icon != NULL)
  {
    cairo_set_source_surface(cr, icon, ICON_OFFSET, 0);
    cairo_paint(cr);
  }

  // Fixed text.
  cairo_select_font_face(cr, WIDGET_FONT, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
  y= _content_top;
  cairo_set_source_rgb(cr, 98 / 255.0, 97 / 255.0, 97 / 255.0);
  cairo_move_to(cr, _instance_left, y);
  cairo_show_text(cr, "Name:");
  cairo_stroke(cr);
  y += _text_y_advance;
  cairo_move_to(cr, _ip_left, y);
  cairo_show_text(cr, "Host:");
  cairo_stroke(cr);
  y += _text_y_advance;
  cairo_move_to(cr, _server_left, y);
  cairo_show_text(cr, "Server:");
  cairo_stroke(cr);
  y += _text_y_advance;
  cairo_move_to(cr, _status_left, y);
  cairo_show_text(cr, "Status:");
  cairo_stroke(cr);

  cairo_restore(cr);
  unlock();
}

//--------------------------------------------------------------------------------------------------

/**
 * This function computes the overall layout of the widget and sets its size.
 */
bool ServerInfoWidget::layout(cairo_t* cr)
{
  BaseWidget::layout(cr);

  cairo_save(cr);
    
  cairo_select_font_face(cr, WIDGET_FONT, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);
  cairo_set_font_size(cr, WIDGET_NORMAL_FONT_SIZE);

  cairo_text_extents_t extents;
  double total_height= 4;
  double total_width= ICON_OFFSET;

  lock();
  
  // Icon and main text position.
  // Icon size for left text offset and total size.
  cairo_surface_t* icon;
  std::string status_text;
  switch (_status)
  {
    case 1:
      icon= _image_running;
      status_text= "Running";
      break;
    case 0:
      icon= _image_stopped;
      status_text= "Stopped";
      break;
    default:
      icon= _image_unknown;
      status_text= "Unknown";
  }

  double icon_width= 0;
  double icon_height= 0;
  if (icon != NULL)
  {
    icon_width= cairo_image_surface_get_width(icon);
    icon_height= cairo_image_surface_get_height(icon);
  }

  // Fixed text positions.
  cairo_select_font_face(cr, WIDGET_FONT, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
  double fixed_max_width= 0;
  double fixed_left= ICON_OFFSET + icon_width + SPACING;

  cairo_text_extents(cr, "Name:", &extents);
  double instance_width= extents.x_advance;
  if (instance_width > fixed_max_width)
    fixed_max_width= instance_width;

  _content_top= extents.height;

  cairo_text_extents(cr, "Host:", &extents);
  double ip_width= extents.x_advance;
  if (ip_width > fixed_max_width)
    fixed_max_width= ip_width;
  cairo_text_extents(cr, "Server:", &extents);
  double server_width= extents.x_advance;
  if (server_width > fixed_max_width)
    fixed_max_width= server_width;
  cairo_text_extents(cr, "Status:", &extents);
  double status_width= extents.x_advance;
  if (status_width > fixed_max_width)
    fixed_max_width= status_width;

  // Now that we have all widths and the maximum width compute the final positions.
  // The vertical offset between lines is computed for the bold, variable text (as this likely needs a bit more room).
  _instance_left= fixed_left + fixed_max_width - instance_width;
  _ip_left= fixed_left + fixed_max_width - ip_width;
  _server_left= fixed_left + fixed_max_width - server_width;
  _status_left= fixed_left + fixed_max_width - status_width;

  cairo_select_font_face(cr, WIDGET_FONT, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);
  _var_text_left= fixed_left + fixed_max_width + TEXT_SPACING;
  double var_max_width= 0;

  cairo_text_extents(cr, _instance.c_str(), &extents);
  if (extents.x_advance > var_max_width)
    var_max_width= extents.x_advance;

  if (extents.height > _content_top)
    _content_top= extents.height;

  cairo_text_extents(cr, _ip_address.c_str(), &extents);
  if (extents.x_advance > var_max_width)
    var_max_width= extents.x_advance;
  cairo_text_extents(cr, _server.c_str(), &extents);
  if (extents.x_advance > var_max_width)
    var_max_width= extents.x_advance;
  cairo_text_extents(cr, status_text.c_str(), &extents);
  if (extents.x_advance > var_max_width)
    var_max_width= extents.x_advance;

  total_width= _var_text_left + var_max_width;

  cairo_text_extents(cr, "Äg", &extents);
  _text_y_advance= extents.height + LINE_SPACING;

  if (icon_height > 4 * _text_y_advance)
    total_height += icon_height;
  else
    total_height += 4 * _text_y_advance; // 4 lines of text.
  
  cairo_restore(cr);
  _layout_width= MIN(270, (int) total_width); // For now moor the size at 270px max.
  _layout_height= (int) total_height;
  
  unlock();
  
  return true;
}

//----------------- BarGraphWidget -----------------------------------------------------------------------

#define BAR_WIDTH 31

BarGraphWidget::BarGraphWidget()
: _value(0), _value_gradient(NULL), _grid(NULL)
{
  _layout_width= BAR_WIDTH;
}

//--------------------------------------------------------------------------------------------------

BarGraphWidget::~BarGraphWidget()
{
}

//--------------------------------------------------------------------------------------------------

void BarGraphWidget::prepare_background()
{
  Rect bounds= get_diagram_area();

  if (_background == NULL 
    || cairo_image_surface_get_height(_background) != bounds.height())
  {
    destroy_background();
    _background= cairo_image_surface_create(CAIRO_FORMAT_ARGB32, BAR_WIDTH, (int) bounds.height());
    cairo_t* cr= cairo_create(_background);

    // The background is a constant color with a gradient overlay and a darker border.
    cairo_set_source_rgb(cr, 52 / 255.0, 54 / 255.0, 56 / 255.0);
    cairo_paint(cr);

    cairo_pattern_t* gradient= cairo_pattern_create_linear(0, 0, 0, bounds.height() - 2);
    cairo_pattern_add_color_stop_rgba(gradient, 0, 122 / 255.0, 140 / 255.0, 154 / 255.0, 0.4);
    cairo_pattern_add_color_stop_rgba(gradient, 0.08, 151 / 255.0, 170 / 255.0, 184 / 255.0, 0.6);
    cairo_pattern_add_color_stop_rgba(gradient, 0.09, 105 / 255.0, 122 / 255.0, 135 / 255.0, 0.6);
    cairo_pattern_add_color_stop_rgba(gradient, 1, 60 / 255.0, 66 / 255.0, 71 / 255.0, 0.6);
    
    cairo_rectangle(cr, 1, 1, BAR_WIDTH - 2, bounds.height() - 2);
    cairo_set_source(cr, gradient);
    cairo_fill(cr);
    cairo_pattern_destroy(gradient);
    
    cairo_destroy(cr);

    // Grid overlay.
    _grid= cairo_image_surface_create(CAIRO_FORMAT_ARGB32, BAR_WIDTH, (int) bounds.height());
    cr= cairo_create(_grid);
    cairo_set_source_rgba(cr, 52 / 255.0, 54 / 255.0, 56 / 255.0, 0.2);
    cairo_set_line_width(cr, 1);
    for (double y= bounds.height() - 3.5; y > 1; y -= 3)
    {
      cairo_move_to(cr, 0.5, y);
      cairo_line_to(cr, BAR_WIDTH - 0.5, y);
    }
    cairo_stroke(cr);

    cairo_move_to(cr, BAR_WIDTH / 2 + 0.5, bounds.height() - 0.5);
    cairo_line_to(cr, BAR_WIDTH / 2 + 0.5, 0.5);
    cairo_stroke(cr);

    cairo_destroy(cr);

    create_value_gradient();
  }
}

//--------------------------------------------------------------------------------------------------

void BarGraphWidget::destroy_background()
{
  BaseWidget::destroy_background();

  if (_value_gradient != NULL)
    cairo_pattern_destroy(_value_gradient);
  _value_gradient= NULL;
  if (_grid != NULL)
    cairo_surface_destroy(_grid);
  _grid= NULL;
}

//--------------------------------------------------------------------------------------------------

/**
 * Called when the user changed the value range. Transform existing values into that new range.
 * The given parameters are precomputed values that allow do a simple transformation.
 */
void BarGraphWidget::range_updated(double scale, double offset)
{
  lock();
  _value= _value * scale + offset;
  unlock();
}

//--------------------------------------------------------------------------------------------------

void BarGraphWidget::get_minmax_values(double* min, double* max)
{
  lock();
  *min= _value;
  *max= _value;
  unlock();
}

//--------------------------------------------------------------------------------------------------

/**
 * Creates the gradient for the value display, which depends on both, the control's dimension
 * as well as the actual value.
 */
void BarGraphWidget::create_value_gradient()
{
  // The value gradient is always fully shown (i.e. over the full range), regardless of the value.
  // Though the value changes, so we have to create the gradient exactly for each value to make it work as we want.
  if (_value_gradient != NULL)
    cairo_pattern_destroy(_value_gradient);
  
  Rect bounds= get_diagram_area();
  double split_point= (bounds.height() - 2) * _value;
  split_point = 3 * (split_point / 3); // Integer div.

  _value_gradient= cairo_pattern_create_linear(0, bounds.height() - 1 - split_point, 0, bounds.height() - 1);
  cairo_pattern_add_color_stop_rgb(_value_gradient, 0, 102 / 255.0, 171 / 255.0, 251 / 255.0);
  cairo_pattern_add_color_stop_rgb(_value_gradient, 1, 0 / 255.0, 119 / 255.0, 189 / 255.0);
}

//--------------------------------------------------------------------------------------------------

void BarGraphWidget::set_value(double value)
{
  value= normalize(value);
  if (_value != value)
  {
    _value= value;
    create_value_gradient();
    set_needs_repaint();
  }
}

//--------------------------------------------------------------------------------------------------


void BarGraphWidget::repaint(cairo_t *cr, int areax, int areay, int areaw, int areah)
{
  BaseWidget::repaint(cr, areax, areay, areaw, areah);

  // Fill body with thick lines in either dark gray or a blue gradient depending on the current bar value.
  Rect bounds= get_diagram_area();

  lock();

  // Compute the split point as multiple of 3 to simulate a discret bar graph.
  double split_point= (bounds.height() - 2) * _value;
  split_point = 3 * (int) (split_point / 3);

  cairo_set_source(cr, _value_gradient);
  cairo_rectangle(cr, 1, bounds.height() - 1 - split_point, BAR_WIDTH - 2, split_point);
  cairo_fill(cr);

  // Finally the grid overlay.
  cairo_set_source_surface(cr, _grid, 0, 0);
  cairo_paint(cr);
  unlock();
}

//----------------- LineDiagramWidget --------------------------------------------------------------

LineDiagramWidget::LineDiagramWidget()
{
  memset(_deflection, 0, sizeof(_deflection));
  memset(_timestamp, 0, sizeof(_timestamp));
  _time_in_view= 60; // Corresponds to a zoom factor of 100%.
  _next_value= 0;
  _value_gradient= NULL;
  _grid= NULL;
  _content_alpha= 1;
  _warning_alpha= 0;
  _warning_extents.width= 0;
  _sleep_start= 0;
  _sleep_mode= Awake;
  _last_shift= 0;
  
  _clock= g_timer_new();
  g_timer_start(_clock);
  
  start_animation_timer_for(this);
}

//--------------------------------------------------------------------------------------------------

LineDiagramWidget::~LineDiagramWidget()
{
  stop_animation_timer_for(this);
  
  lock();
  g_timer_destroy(_clock);
  unlock();
}

//--------------------------------------------------------------------------------------------------

void LineDiagramWidget::repaint(cairo_t *cr, int areax, int areay, int areaw, int areah)
{
  BaseWidget::repaint(cr, areax, areay, areaw, areah);
  
  Rect bounds= get_diagram_area();
  
  lock();

  cairo_push_group(cr); // Prepare composing.
  
  //if (_sleep_mode != Sleeping)
  {
    cairo_set_line_width(cr, 1);
    cairo_set_source(cr, _value_gradient);
    
    // Search backwards for the first value which has been measured
    // at least _time_in_view before.
    double current_time= g_timer_elapsed(_clock, NULL);
    int index= LINE_SERIES_DATA_SIZE - 1;
    while ((index > 0) && (_timestamp[index] > 0) && ((current_time - _timestamp[index]) < _time_in_view))
      index--;
    double start_time= current_time - _time_in_view;
    
    // The first point just sets a location for the first curve to start.
    // This can be outside of the current window but we need it to correctly draw the first curve part.
    double deflection=  _deflection[index];
    double x= (_timestamp[index] - start_time) * bounds.width() / _time_in_view;
    double y= (bounds.height() - 2) * (1 - deflection) + 0.5;
    cairo_move_to(cr, x, y);
    
    for (; index < LINE_SERIES_DATA_SIZE; index++)
    {
      // Compute control points for current position and the next position.
      double x1= x;
      double y1= y;
      
      deflection=  _deflection[index];
      x= (_timestamp[index] - start_time) * bounds.width() / _time_in_view;
      y= (bounds.height() - 2) * (1 - deflection) + 0.5;
      
      double x2= x;
      double y2= y;

      // Move horizontal control point location to the middle between both endpoints.
      double dX= (x2 - x1) / 2;
      x1 += dX;
      x2 -= dX;
      
      cairo_curve_to(cr, x1, y1, x2, y2, x, y);
    }
    
    // Close the figure for proper filling.
    cairo_line_to(cr, bounds.width() - 1.5, y);
    cairo_line_to(cr, bounds.width() - 1.5, bounds.height() - 1.5);
    cairo_line_to(cr, 1.5, bounds.height() - 1.5);
    
    cairo_fill(cr);
    
    // Part of the border has been overdrawn, fix it.
    cairo_set_source_rgb(cr, 52 / 255.0, 54 / 255.0, 56 / 255.0);
    cairo_rectangle(cr, 0.5, 0.5, bounds.width() - 1, bounds.height() - 1);
    cairo_stroke(cr);
  }
  
  // Finally the grid overlay.
  cairo_set_source_surface(cr, _grid, 0, 0);
  cairo_paint(cr);

  // Compose background, content and feedback.
  cairo_pop_group_to_source(cr);
  cairo_paint_with_alpha(cr, _content_alpha);
  show_feedback(cr, bounds);

  unlock();
}

//--------------------------------------------------------------------------------------------------

void LineDiagramWidget::set_value(double value)
{
  auto_scale(value);

  value= normalize(value);

  lock();
  double timestamp= g_timer_elapsed(_clock, NULL);
  
  // Move values one step to the left.
  memmove(&_deflection[0], &_deflection[1], sizeof(_deflection));
  _deflection[LINE_SERIES_DATA_SIZE - 1]= value;

  memmove(&_timestamp[0], &_timestamp[1], sizeof(_timestamp));
  _timestamp[LINE_SERIES_DATA_SIZE - 1]= timestamp;
  
  unlock();
  
}

//--------------------------------------------------------------------------------------------------

void LineDiagramWidget::prepare_background()
{
  Rect bounds= get_diagram_area();
  
  if (_background == NULL 
     || cairo_image_surface_get_height(_background) != bounds.height()
     || cairo_image_surface_get_width(_background) != bounds.width())
  {
    destroy_background();
    _background= cairo_image_surface_create(CAIRO_FORMAT_ARGB32, (int) bounds.width(), (int) bounds.height());
    cairo_t* cr= cairo_create(_background);
    
    // The background is a constant color with a gradient overlay and a darker border.
    cairo_set_source_rgb(cr, 52 / 255.0, 54 / 255.0, 56 / 255.0);
    cairo_paint(cr);
    
    cairo_pattern_t* gradient= cairo_pattern_create_linear(0, 0, 0, bounds.height() - 2);
    cairo_pattern_add_color_stop_rgba(gradient, 0, 122 / 255.0, 140 / 255.0, 154 / 255.0, 0.4);
    cairo_pattern_add_color_stop_rgba(gradient, 0.08, 151 / 255.0, 170 / 255.0, 184 / 255.0, 0.6);
    cairo_pattern_add_color_stop_rgba(gradient, 0.09, 105 / 255.0, 122 / 255.0, 135 / 255.0, 0.6);
    cairo_pattern_add_color_stop_rgba(gradient, 1, 60 / 255.0, 66 / 255.0, 71 / 255.0, 0.6);
    
    cairo_rectangle(cr, 1, 1, bounds.width() - 2, bounds.height() - 2);
    cairo_set_source(cr, gradient);
    cairo_fill(cr);
    cairo_pattern_destroy(gradient);
    
    cairo_destroy(cr);

    // Grid overlay.
    _grid= cairo_image_surface_create(CAIRO_FORMAT_ARGB32, (int) bounds.width(), (int) bounds.height());
    cr= cairo_create(_grid);
    cairo_set_source_rgba(cr, 52 / 255.0, 54 / 255.0, 56 / 255.0, 0.2);
    cairo_set_line_width(cr, 1);
    for (double y= bounds.height() - 3.5; y > 5; y -= 3)
    {
      cairo_move_to(cr, 0.5, y);
      cairo_line_to(cr, (int) bounds.width() - 0.5, y);
    }
    cairo_stroke(cr);
    
    double dX= 15 * 60 / _time_in_view; // That is: a 16 pixel wide rectangle at 1 min. total time.
    for (double x= bounds.width() - dX - 0.5; x > 1; x -= dX)
    {
      cairo_move_to(cr, x, 0.5);
      cairo_line_to(cr, x, (int) bounds.height() - 0.5);
    }
    cairo_stroke(cr);
    
    cairo_destroy(cr);
    
    _value_gradient= cairo_pattern_create_linear(0, 0, 0, bounds.height() - 2);
    cairo_pattern_add_color_stop_rgb(_value_gradient, 0, 102 / 255.0, 171 / 255.0, 251 / 255.0);
    cairo_pattern_add_color_stop_rgb(_value_gradient, 1, 0 / 255.0, 119 / 255.0, 189 / 255.0);
  }
}

//--------------------------------------------------------------------------------------------------

void LineDiagramWidget::destroy_background()
{
  BaseWidget::destroy_background();
  
  if (_value_gradient != NULL)
    cairo_pattern_destroy(_value_gradient);
  _value_gradient= NULL;
  if (_grid != NULL)
    cairo_surface_destroy(_grid);
  _grid= NULL;
}

//--------------------------------------------------------------------------------------------------

/**
 * Called when the user changed the value range. Transform existing values into that new range.
 * The given parameters are precomputed values that allow to do a simple transformation.
 */
void LineDiagramWidget::range_updated(double scale, double offset)
{
  lock();
  for (int i= 0; i < LINE_SERIES_DATA_SIZE; i++)
    _deflection[i]= _deflection[i] * scale + offset;
  
  unlock();
}

//--------------------------------------------------------------------------------------------------

/**
 * Returns the largest and smallest value in the current time range.
 */
void LineDiagramWidget::get_minmax_values(double* min, double* max)
{
  *min= 0;
  *max= 0;
  
  // Search backwards for the first value which has been measured
  // at least _time_in_view before.
  double current_time= g_timer_elapsed(_clock, NULL);
  int index= LINE_SERIES_DATA_SIZE - 1;
  while ((index > 0) && (_timestamp[index] > 0) && ((current_time - _timestamp[index]) < _time_in_view))
    index--;

  lock();
  for (; index < LINE_SERIES_DATA_SIZE; index++)
  {
    if (_deflection[index] > *max)
      *max= _deflection[index];
    if (_deflection[index] < *min)
      *min= _deflection[index];
  }
  
  unlock();
}

//--------------------------------------------------------------------------------------------------

#define DATA_TIMEOUT 15 // 15 seconds after which the diagram will go to sleep if no data comes in.

void LineDiagramWidget::step()
{
  double timestamp= g_timer_elapsed(_clock, NULL);
  
  bool needs_repaint= false;
  if (_sleep_mode == Awake && timestamp - _last_shift >= 0.5)
  {
    // Two shifts per second.
    _last_shift= timestamp;
    auto_scale(0);
    needs_repaint= true;
  }

  lock();
  
  // Determine if we have to go sleeping or awake.
  if (_sleep_mode == Awake)
  {
    // We are not sleeping or going to sleep. Check if we need to.
    if (timestamp - _timestamp[LINE_SERIES_DATA_SIZE - 1] >= DATA_TIMEOUT)
    {
      begin_sleeping(timestamp);
      needs_repaint= true;
    }
  }
  else
  {
    // We are sleeping. Check if we can awake.
    if (_sleep_mode == Sleeping && (timestamp - _timestamp[LINE_SERIES_DATA_SIZE - 1] < DATA_TIMEOUT))
    {
      end_sleeping(timestamp);
      needs_repaint= true;
    }
  }
  if (feedback_step())
    needs_repaint= true;

  if (needs_repaint)
    set_needs_repaint();
  
  unlock();
}

//--------------------------------------------------------------------------------------------------

#define WARNING_TEXT "No Data"

void LineDiagramWidget::show_feedback(cairo_t* cr, const Rect& bounds)
{
  if (_sleep_mode != Awake)
  {
    cairo_select_font_face(cr, WIDGET_SMALL_FONT, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);
    cairo_set_font_size(cr, WIDGET_WARNING_FONT_SIZE);

    if (_warning_extents.width == 0)
      cairo_text_extents(cr, WARNING_TEXT, &_warning_extents);
    
    // Place the warning in the upper left corner.
    int y= (int) (bounds.top() + 4 - _warning_extents.y_bearing);
    int x= (int) (bounds.left() + 4);

    cairo_push_group(cr); // Prepare composing.
    
    cairo_set_source_rgb(cr, 220 / 255.0, 220 / 255.0, 220 / 255.0);
    cairo_move_to(cr, x, y);
    cairo_show_text(cr, WARNING_TEXT);
    cairo_stroke(cr);

    cairo_pop_group_to_source(cr);
    cairo_paint_with_alpha(cr, _warning_alpha);
  }
}

//--------------------------------------------------------------------------------------------------

/**
 * Triggered when, after some timeout, no new data arrives.
 * Note: lock has been acquired on call already.
 */
void LineDiagramWidget::begin_sleeping(double timestamp)
{
  _sleep_mode= GoSleeping;
  _sleep_start= timestamp;
}

//--------------------------------------------------------------------------------------------------

/**
 * Triggered when, after we slept, new data arrives.
 * Note: lock has been acquired on call already.
 */
void LineDiagramWidget::end_sleeping(double timestamp)
{
  _sleep_mode= Awaking;
  _sleep_start= timestamp;
}

//--------------------------------------------------------------------------------------------------

/**
 * Used to compute the next feedback step, if there is any feedback.
 * Returns true if the widget must be repainted.
 */
bool LineDiagramWidget::feedback_step()
{
  bool result= false;
  if (_sleep_mode != Awake)
  {
    // Determine if we have to go sleeping or awake.
    double timestamp= g_timer_elapsed(_clock, NULL);

    // Normalize time point to 6 seconds for one cycle and map it to radians.
    double time_point= 2 * M_PI * (timestamp - _sleep_start) / 6;
    
    switch (_sleep_mode)
    {
      case GoSleeping:
        // Go sleeping within first 3 seconds. After that normal sleeping alone takes place.
        if (timestamp - _sleep_start < 3)
        {
          // Compute content alpha value.
          // Minimum value is 25%. Divide by 2 as we shift the coordinate system 1 up (to
          // have no negative values).
          _content_alpha= 0.25 + 0.75 * 0.5 * (1 + cos(time_point));
        }
        else
          _sleep_mode= Sleeping;

        // Same computation for warning alpha (just the opposite direction).
        _warning_alpha= 0.25 + 0.75 * 0.5 * (1 + sin(time_point - M_PI / 2));
        
        // Just in case the GoSleeping phase could not be executed (the timer thread could not
        // trigger within the first second of the animation cycle) check the content alpha value
        // and set it to its endpoint to have a proper display.
        if (_sleep_mode == Sleeping && _content_alpha > 0.25)
          _content_alpha= 0.25;
        
        result= true;
        break;
      case Awaking:
          // When awaking simply do a linear interpolation for both alpha values (duration = 1 sec). 
          // Once both have reached their end values we stop the animation.
        if (_content_alpha < 1)
          _content_alpha= min(1.0, timestamp - _sleep_start);
        if (_warning_alpha > 0)
          _warning_alpha= max(0.0, 1 - (timestamp - _sleep_start));
        if (_content_alpha == 1 && _warning_alpha == 0)
          _sleep_mode= Awake;

        result= true;
        break;
    }
  }
  return result;
}

//--------------------------------------------------------------------------------------------------

