/*
 * Copyright (c) 2010, 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"

#include "starter_popup.h"
#include "mforms/utilities.h"
#include "webbrowser_view.h"

#include "base/string_utilities.h"

using namespace wb;
using namespace mforms;

#ifdef __APPLE__
#define STARTER_TITLE_FONT "Lucida Grande"
#define STARTER_DESCRIPTION_FONT "Lucida Grande"
#elif _WIN32
#define STARTER_TITLE_FONT "Tahoma"
#define STARTER_DESCRIPTION_FONT "Tahoma"
#else
#define STARTER_TITLE_FONT "Helvetica"
#define STARTER_DESCRIPTION_FONT "Helvetica"
#endif

#define STARTER_TITLE_FONT_SIZE 13
#define STARTER_DESCRIPTION_FONT_SIZE 11
#define STARTER_BUTTON_FONT_SIZE 13
#define STARTER_ACTION_BUTTON_FONT_SIZE 11

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

// The following helpers are just temporary. They will be replaced by a cairo context class.
static void delete_surface(cairo_surface_t* surface)
{
  if (surface != NULL)
    cairo_surface_destroy(surface);
}

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

static void delete_pattern(cairo_pattern_t* pattern)
{
  if (pattern != NULL)
    cairo_pattern_destroy(pattern);
}

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

static int image_width(cairo_surface_t* image)
{
  if (image != NULL)
    return cairo_image_surface_get_width(image);
  return 0;
}

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

static int image_height(cairo_surface_t* image)
{
  if (image != NULL)
    return cairo_image_surface_get_height(image);
  return 0;
}

//----------------- StarterPopup -------------------------------------------------------------------

StarterPopup::StarterPopup(WBContext* context)
  : Popup(mforms::PopupBezel), _context(context), _current_page(0), _callback(NULL)
{
  _mysql_plugin_overlay= Utilities::load_icon("wb_starter_type_mysql.png");
  _community_plugin_overlay= Utilities::load_icon("wb_starter_type_community.png");
  _legal_image= Utilities::load_icon("wb_starter_legal.png");
  _default_starter_icon= Utilities::load_icon("wb_starter_generic_68.png");

  _nav_button_parts[0]= Utilities::load_icon("wb_starter_button_left.png");
  _nav_button_parts[1]= Utilities::load_icon("wb_starter_button_middle.png");
  _nav_button_parts[2]= Utilities::load_icon("wb_starter_button_right.png");

  if (_nav_button_parts[1] != NULL)
  {
    _nav_button_pattern = cairo_pattern_create_for_surface(_nav_button_parts[1]);
    cairo_pattern_set_extend(_nav_button_pattern, CAIRO_EXTEND_REPEAT);
  }
  else
    _nav_button_pattern= NULL;

  _action_button_parts[0]= Utilities::load_icon("action_button_left.png");
  _action_button_parts[1]= Utilities::load_icon("action_button_middle.png");
  _action_button_parts[2]= Utilities::load_icon("action_button_right.png");

  if (_action_button_parts[1] != NULL)
  {
    _action_button_pattern = cairo_pattern_create_for_surface(_action_button_parts[1]);
    cairo_pattern_set_extend(_action_button_pattern, CAIRO_EXTEND_REPEAT);
  }
  else
    _action_button_pattern= NULL;

  // Action menu.
  _action_menu= mforms::manage(new mforms::Menu());
  _action_menu->set_handler(boost::bind(&StarterPopup::handle_command, this, _1));
  _action_menu->add_item(_("Start Plugin"), "start_plugin");
  _action_menu->add_item(_("Start Plugin in External Browser"), "start_plugin_external");
  _action_menu->add_separator();
  /* Bug 12613922
  _action_menu->add_item(_("Find Plugin in Web Repository"), "find_plugin");
  */
  _action_menu->add_item(_("Go to Author's Website"), "author_home");
  _action_menu->add_separator();

  _placing_menu= mforms::manage(new mforms::Menu());
  _placing_menu->set_handler(boost::bind(&StarterPopup::handle_command, this, _1));
  _placing_menu->add_item(_("Set at Position 1"), "set_1");
  _placing_menu->add_item(_("Set at Position 2"), "set_2");
  _placing_menu->add_item(_("Set at Position 3"), "set_3");
  _placing_menu->add_item(_("Set at Position 4"), "set_4");
  _placing_menu->add_item(_("Set at Position 5"), "set_5");
  _placing_menu->add_item(_("Set at Position 6"), "set_6");
  _placing_menu->add_separator();
  _placing_menu->add_item(_("Insert at Position 1"), "insert_1");
  _placing_menu->add_item(_("Insert at Position 2"), "insert_2");
  _placing_menu->add_item(_("Insert at Position 3"), "insert_3");
  _placing_menu->add_item(_("Insert at Position 4"), "insert_4");
  _placing_menu->add_item(_("Insert at Position 5"), "insert_5");
  _placing_menu->add_item(_("Insert at Position 6"), "insert_6");

  _action_menu->add_submenu("Add To Home Screen", _placing_menu);
}

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

StarterPopup::~StarterPopup()
{
  delete_surface(_mysql_plugin_overlay);
  delete_surface(_community_plugin_overlay);
  delete_surface(_legal_image);
  delete_surface(_default_starter_icon);

  delete_surface(_nav_button_parts[0]);
  delete_surface(_nav_button_parts[1]);
  delete_surface(_nav_button_parts[2]);
  if (_nav_button_pattern != NULL)
    delete_pattern(_nav_button_pattern);

  delete_surface(_action_button_parts[0]);
  delete_surface(_action_button_parts[1]);
  delete_surface(_action_button_parts[2]);
  if (_action_button_pattern != NULL)
    delete_pattern(_action_button_pattern);

  _placing_menu->release();
  _action_menu->release();
}

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

/**
 * Draws a navigation or an action button (if @param action is true).
 */
void StarterPopup::draw_button(cairo_t* cr, double x, double y, double inner_width, bool action)
{
  cairo_surface_t* left_part= action ? _action_button_parts[0] : _nav_button_parts[0];
  cairo_surface_t* right_part= action ? _action_button_parts[2] : _nav_button_parts[2];
  cairo_pattern_t* pattern= action ? _action_button_pattern : _nav_button_pattern;

  if (pattern != NULL)
  {
    cairo_save(cr);
    cairo_translate(cr, x, y);
    cairo_set_source_surface(cr, left_part, 0, 0);
    cairo_paint(cr);
    
    double offset= image_width(left_part);
    cairo_set_source(cr, pattern);
    cairo_rectangle(cr, offset, 0, inner_width, image_height(left_part));
    cairo_fill(cr);
    
    offset += inner_width;
    cairo_set_source_surface(cr, right_part, offset, 0);
    cairo_paint(cr);
    cairo_restore(cr);
  }
}

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

#define STARTER_OVERLAY_OFFSET 8        // Vertical offset for the entry to make the overlay icon stand out a bit
                                        // and start at top position 0.
#define STARTER_TEXT_SPACING 12         // Horizontal distance between icon and text area.
#define STARTER_INFO_TEXT_SPACING 6     // Horizontal distance between info text parts.
#define STARTER_SPACING 20              // Spacing between adjacent starter entries.
#define STARTER_TEXT_LINE_HEIGHT 14     // Hard coded line height, to make the overall image more compact.
#define STARTER_BUTTON_OFFSET 225       // Vertical offset for the buttons.
#define STARTER_ACTION_BUTTON_PADDING 5 // Space between text and right button part (with arrow).

#define STARTER_PUBLISHER_TEXT _("Publisher:")
#define STARTER_TYPE_TEXT _("Type:")
#define STARTER_ACTION_TEXT _("Action")

/**
 * Draws a starter entry in the given bounds, including the trigger button (action button) if
 * this entry is "hot". As a side effect the entry's bounds are stored in our internal list
 * which is used for hit testing.
 */
void StarterPopup::draw_entry(cairo_t* cr, app_StarterRef starter, int x, int y, int w, int h)
{
  // Add the outstanding part of the action button to the entry height (only for hit testing).
  double button_height= image_height(_action_button_parts[0]);

  // First add this entry to our internal list.
  PopupEntry entry= {starter, MySQL::Geometry::Rect(x, y, w, h + button_height / 2)};
  _entries.push_back(entry);

  cairo_surface_t* icon= Utilities::load_icon(starter->largeIcon());
  if (icon == NULL)
    icon= _default_starter_icon;
  cairo_set_source_surface(cr, icon, x, y + STARTER_OVERLAY_OFFSET);
  cairo_paint(cr);
  int icon_width= image_width(icon);
  if (icon != _default_starter_icon)
    delete_surface(icon);

  if (starter->publisher() == "Oracle Corp.")
    icon= _mysql_plugin_overlay;
  else
    icon= _community_plugin_overlay;
  int overlay_width= image_width(icon);
  cairo_set_source_surface(cr, icon, x + icon_width - overlay_width + 4, y);
  cairo_paint(cr);

  double space_for_text= w - icon_width - STARTER_TEXT_SPACING - STARTER_SPACING;

  // Title.
  cairo_select_font_face(cr, STARTER_TITLE_FONT, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
  cairo_set_font_size(cr, STARTER_TITLE_FONT_SIZE);
  double text_offset= x + icon_width + STARTER_TEXT_SPACING;
  cairo_set_source_rgb(cr, 1, 1, 1);

  // Usually lines are set with the vertical distance in extents.height, however since have not much space
  // we only use the minimal height possible.
  double line1_offset= y + STARTER_OVERLAY_OFFSET + STARTER_TEXT_LINE_HEIGHT / 2;
  cairo_move_to(cr, text_offset, line1_offset);

  // We add a space char here as cairo sometimes not not fully render the last character.
  // Better this wrong "glyph" is a space char.
  std::string text= Utilities::shorten_string(cr, *(starter->title()) + " ", space_for_text);
  cairo_show_text(cr, text.c_str());
  cairo_stroke(cr);

  // Description. We display at most two lines of the description. If it is longer then we show end ellipses.
  // TODO: once Pango is available use its word wrapping abilities.
  cairo_set_source_rgba(cr, 170 / 255.0, 173 / 255.0, 181 / 255.0, 1);
  cairo_select_font_face(cr, STARTER_DESCRIPTION_FONT, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
  cairo_set_font_size(cr, STARTER_DESCRIPTION_FONT_SIZE);

  line1_offset += STARTER_TEXT_LINE_HEIGHT;
  double line2_offset = line1_offset + STARTER_TEXT_LINE_HEIGHT;
  std::string description= starter->description();
  gchar* head= (gchar*) description.c_str();
  const gchar* end;
  g_utf8_validate(head, -1, &end); // Display only the valid part of the string.
  gchar* tail= head;

  std::string line1;
  std::string line2;
  while (true)
  {
    // Get the next word.
    while (tail != end && (*tail != ' '))
      tail= g_utf8_next_char(tail);
    gchar* part= g_strndup(head, tail - head);

    // See which space the part requires.
    cairo_text_extents_t local_extents;
    cairo_text_extents(cr, part, &local_extents);
    if ((tail == end) || ceil(local_extents.width) >= space_for_text)
    {
      g_free(part);
      break;
    }

    line1= part;
    g_free(part);

    // Skip over the space char.
    tail= g_utf8_next_char(tail);
  }
  cairo_move_to(cr, text_offset, line1_offset);
  cairo_show_text(cr, line1.c_str());
  cairo_stroke(cr);

  // Get the rest of the string and determine space requirement.
  // Skip over the split space char if we found one.
  if (tail != end)
    tail= g_utf8_next_char(tail);
  gchar* part= g_strndup(tail, end - tail);
  line2= Utilities::shorten_string(cr, part, space_for_text);
  cairo_move_to(cr, text_offset, line2_offset);
  cairo_show_text(cr, line2.c_str());
  cairo_stroke(cr);

  // Additional info. Lines start with right aligned text.
  line1_offset += 2 * STARTER_TEXT_LINE_HEIGHT; // For publisher line.
  line2_offset += 2 * STARTER_TEXT_LINE_HEIGHT; // For type line.
  double info_offset;
  cairo_text_extents_t text_extents;
  cairo_text_extents(cr, STARTER_PUBLISHER_TEXT, &text_extents);
  double part1_width= text_extents.width;
  info_offset= text_extents.width;
  cairo_text_extents(cr, STARTER_TYPE_TEXT, &text_extents);
  double part2_width= text_extents.width;
  if (text_extents.width > info_offset)
    info_offset= text_extents.width;
  space_for_text -= info_offset;
  info_offset += text_offset;

  cairo_move_to(cr, info_offset - part1_width, line1_offset);
  cairo_show_text(cr, STARTER_PUBLISHER_TEXT);
  cairo_stroke(cr);

  cairo_move_to(cr, info_offset - part2_width, line2_offset);
  cairo_show_text(cr, STARTER_TYPE_TEXT);
  cairo_stroke(cr);

  cairo_set_source_rgb(cr, 1, 1, 1);

  text= Utilities::shorten_string(cr, *(starter->publisher()), space_for_text);
  cairo_move_to(cr, info_offset + STARTER_INFO_TEXT_SPACING, line1_offset);
  cairo_show_text(cr, text.c_str());
  cairo_stroke(cr);

  text= Utilities::shorten_string(cr, *(starter->type()), space_for_text);
  cairo_move_to(cr, info_offset + STARTER_INFO_TEXT_SPACING, line2_offset);
  cairo_show_text(cr, text.c_str());
  cairo_stroke(cr);

  // Draw action overlay if this entry is the hot one.
  if (starter == _hot_entry)
  {
    double trigger_left= x + 10;
    double trigger_top= y + h - button_height / 2;

    cairo_set_source_rgb(cr, 1, 1, 1);
    cairo_select_font_face(cr, STARTER_DESCRIPTION_FONT, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
    cairo_set_font_size(cr, STARTER_ACTION_BUTTON_FONT_SIZE);
    cairo_text_extents(cr, STARTER_ACTION_TEXT, &text_extents);

    draw_button(cr, trigger_left, trigger_top, text_extents.width + STARTER_ACTION_BUTTON_PADDING, true);

    // Cache the action button bounds to ease hit tests.
    _action_button_bounds.pos.x = trigger_left;
    _action_button_bounds.pos.y = trigger_top;
    _action_button_bounds.size.width = image_width(_action_button_parts[0]) + text_extents.width +
      STARTER_ACTION_BUTTON_PADDING + image_width(_action_button_parts[2]);
    _action_button_bounds.size.height = button_height;

    // Draw the button text.
    trigger_top += (button_height - text_extents.height) / 2 - text_extents.y_bearing - 1;

    cairo_move_to(cr, trigger_left + image_width(_action_button_parts[0]), trigger_top);
    cairo_set_source_rgb(cr, 1, 1, 1);
    cairo_show_text(cr, STARTER_ACTION_TEXT);
    cairo_stroke(cr);

  }
}

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

#define STARTER_NAV_BUTTON_PADDING 7              // Space left and right of the button text
#define STARTER_MORE_PLUGINS_BUTTON_MIN_WIDTH 140 // Minimal width of the "Get More Plugins.." button.
#define STARTER_NAV_BUTTON_MIN_WIDTH 80           // Minimal width of the navigation buttons.
#define STARTER_BUTTON_SPACING 8                  // Spacing between back and next button.
#define STARTER_ENTRIES_PER_PAGE 6

/**
 * Returns the starter entry whose location covers the given coordinates.
 */
app_StarterRef StarterPopup::starter_from_point(int x, int y)
{
  for (std::vector<PopupEntry>::iterator iterator= _entries.begin(); iterator != _entries.end();
    iterator++)
  {
    if (iterator->bounds.contains(x, y))
      return iterator->starter;
  }
  return app_StarterRef();
}

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

/**
 * Triggered from the action menu.
 */
void StarterPopup::handle_command(const std::string& command)
{
  if (_callback == NULL)
    return;

  StarterAction action= StarterNone;
  if (command == "start_plugin")
    action= StarterStartPlugin;
  else
    if (command == "start_plugin_external")
      action= StarterStartPluginExternal;
    else
      if (command == "find_plugin")
        action= StarterFindPlugin;
      else
        if (command == "author_home")
          action= StarterAuthorHome;
        else
          if (command == "set_1")
            action= StarterSetAt1;
          else
            if (command == "set_2")
              action= StarterSetAt2;
            else
              if (command == "set_3")
                action= StarterSetAt3;
              else
                if (command == "set_4")
                  action= StarterSetAt4;
                else
                  if (command == "set_5")
                    action= StarterSetAt5;
                  else
                    if (command == "set_6")
                      action= StarterSetAt6;
                    else
                      if (command == "insert_1")
                        action= StarterInsertAt1;
                      else
                        if (command == "insert_2")
                          action= StarterInsertAt2;
                        else
                          if (command == "insert_3")
                            action= StarterInsertAt3;
                          else
                            if (command == "insert_4")
                              action= StarterInsertAt4;
                            else
                              if (command == "insert_5")
                                action= StarterInsertAt5;
                              else
                                if (command == "insert_6")
                                  action= StarterInsertAt6;

  _callback(action, _hot_entry, _user_data);

  // This will close the popup once we return to the platform code.
  set_modal_result(1);
}

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

void StarterPopup::set_callback(starter_action_callback callback, void* user_data)
{
  _callback= callback;
  _user_data= user_data;
}

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

#define STARTER_MORE_PLUGINS_TEXT _("Get More Plugins...")
#define STARTER_BACK_TEXT _("< Back")
#define STARTER_NEXT_TEXT _("Next >")
#define STARTER_PAGE_TEXT _("Page %i of %i")

/**
 * This is the core drawing routine for the popup content.
 * Note: we take advantage of the knowledge that we draw on a background bitmap, so nothing is
 * buffered here, except for structures needed for hit testing.
 */
void StarterPopup::repaint(cairo_t *cr, int x, int y, int w, int h)
{
  // We want text anti-aliasing with gray pixels, as we render white text on a black background.
  cairo_font_options_t* options= cairo_font_options_create();
  cairo_font_options_set_antialias(options, CAIRO_ANTIALIAS_GRAY);

  // TODO: the gray setting doesn't seem to be supported everywhere. At least on my Win7 VM
  //       it has the effect to render without any anti-aliasing. Hence it is disabled for now.
  //       Might later be enabled again when it works as expected on all platforms.
  //cairo_set_font_options(cr, options);

  _entries.clear();

  int current_left= x;
  int current_top= y;
  int entry_width= (int) ceil(w / 3.0);

  // Start with the predefined starters.
  // Skip entries which don't belong to the current page.
  int counter= STARTER_ENTRIES_PER_PAGE;
  int skip_count= _current_page * STARTER_ENTRIES_PER_PAGE; 
  _page_count= (int) (ceil)(((double) _context->get_root()->starters()->predefined().count() +
    _context->get_root()->starters()->custom().count()) / STARTER_ENTRIES_PER_PAGE);

  grt::ListRef<app_Starter> starters= _context->get_root()->starters()->predefined();
  for (grt::ListRef<app_Starter>::const_iterator iterator= starters.begin();
    iterator != starters.end() && counter > 0;
    iterator++)
  {
    if (skip_count > 0)
      skip_count--;
    else
    {
      draw_entry(cr, *iterator, current_left, current_top, entry_width, 75);

      current_left += entry_width;
      if (current_left >= x + w)
      {
        // Advance to next line.
        current_left= x;
        current_top += 110;
      }
      counter--;
    }
  }

  // Now the user starters.
  starters= _context->get_root()->starters()->custom();
  for (grt::ListRef<app_Starter>::const_iterator iterator= starters.begin();
    iterator != starters.end() && counter > 0;
    iterator++)
  {
    if (skip_count > 0)
      skip_count--;
    else
    {
      draw_entry(cr, *iterator, current_left, current_top, 270 , 75);

      current_left += (int) ceil(w / 3.0);
      if (current_left >= x + w)
      {
        // Advance to next line.
        current_left= x;
        current_top += 110;
      }
      counter--;
    }
  }

  // Buttons.

  cairo_select_font_face(cr, STARTER_TITLE_FONT, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
  cairo_set_font_size(cr, STARTER_BUTTON_FONT_SIZE);

  double button_top = y + STARTER_BUTTON_OFFSET; // Same for all buttons.
  double button_left= x;
  double button_height= image_height(_nav_button_parts[0]);

  cairo_text_extents_t extents;
  cairo_text_extents(cr, STARTER_MORE_PLUGINS_TEXT, &extents);
  
  // Text position, center vertically.
  current_left= x + image_width(_nav_button_parts[0]) + STARTER_NAV_BUTTON_PADDING;
  current_top= (int) ceil(button_top + (button_height - extents.height) / 2 - extents.y_bearing);

  double button_width= ceil(extents.width) + 2 * STARTER_NAV_BUTTON_PADDING;
  double outer_offset= image_width(_nav_button_parts[0]) + image_width(_nav_button_parts[2]);
  if (button_width < STARTER_MORE_PLUGINS_BUTTON_MIN_WIDTH - outer_offset)
    button_width= STARTER_MORE_PLUGINS_BUTTON_MIN_WIDTH - outer_offset;
/*
  draw_button(cr, button_left, button_top, button_width, false);
  _more_button_bounds.size.width = button_width + outer_offset;
  _more_button_bounds.size.height = button_height;
  _more_button_bounds.pos.x = button_left;
  _more_button_bounds.pos.y = button_top;

  cairo_move_to(cr, current_left, current_top);
  cairo_set_source_rgb(cr, 1, 1, 1);
  cairo_show_text(cr, STARTER_MORE_PLUGINS_TEXT);
  cairo_stroke(cr);
*/
  // Next and back buttons are right aligned, so we need their width first. Both buttons will be set to
  // have the same width, depending on which is larger.
  cairo_text_extents(cr, STARTER_BACK_TEXT, &extents);
  button_width= ceil(extents.width);
  double back_width= button_width; // For horizontal text alignment.
  cairo_text_extents(cr, STARTER_NEXT_TEXT, &extents);
  double next_width= ceil(extents.width); // For horizontal text alignment.
  if (extents.width > next_width)
    button_width= next_width;
  button_width += 2 * STARTER_NAV_BUTTON_PADDING;
  if (button_width < STARTER_NAV_BUTTON_MIN_WIDTH - outer_offset)
    button_width= STARTER_NAV_BUTTON_MIN_WIDTH - outer_offset;
  button_left = x + w - 2 * (button_width + outer_offset) - STARTER_BUTTON_SPACING;

  cairo_push_group(cr);
  draw_button(cr, button_left, button_top, button_width, false);
  _back_button_bounds.pos.x = button_left;
  _back_button_bounds.pos.y = button_top;
  _back_button_bounds.size.width = button_width + outer_offset;
  _back_button_bounds.size.height = button_height;

  current_left= (int) ceil(button_left + (_back_button_bounds.width() - back_width) / 2);
  cairo_set_source_rgb(cr, 1, 1, 1);
  cairo_move_to(cr, current_left, current_top);
  cairo_show_text(cr, STARTER_BACK_TEXT);
  cairo_stroke(cr);
  cairo_pop_group_to_source(cr);
  if (_current_page > 0)
    cairo_paint(cr);
  else
    cairo_paint_with_alpha(cr, 0.5);

  cairo_push_group(cr);
  button_left += _back_button_bounds.width() + STARTER_BUTTON_SPACING;
  _next_button_bounds.pos.x = button_left;
  _next_button_bounds.pos.y = button_top;
  _next_button_bounds.size.width = button_width + outer_offset;
  _next_button_bounds.size.height = button_height;
  current_left= (int) ceil(button_left + (_next_button_bounds.width() - next_width) / 2);

  draw_button(cr, button_left, button_top, button_width, false);

  cairo_set_source_rgb(cr, 1, 1, 1);
  cairo_move_to(cr, current_left, current_top);
  cairo_show_text(cr, STARTER_NEXT_TEXT);
  cairo_stroke(cr);
  cairo_pop_group_to_source(cr);
  if (_current_page < _page_count - 1)
    cairo_paint(cr);
  else
    cairo_paint_with_alpha(cr, 0.5);

  // Page display.
  std::string page_text= base::strfmt(STARTER_PAGE_TEXT, _current_page + 1, _page_count);
  cairo_text_extents(cr, page_text.c_str(), &extents);
  current_left= (int) (floor)(_back_button_bounds.left() - STARTER_BUTTON_SPACING - extents.width);
  cairo_set_source_rgb(cr, 1, 1, 1);
  cairo_move_to(cr, current_left, current_top);
  cairo_show_text(cr, page_text.c_str());
  cairo_stroke(cr);

  // Legal text.
  cairo_set_source_surface(cr, _legal_image, x, y + h - image_height(_legal_image));
  cairo_paint(cr);

  // Clean up.
  cairo_font_options_destroy(options);
}

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

void StarterPopup::mouse_down(int button, int x, int y)
{
}

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

void StarterPopup::mouse_up(int button, int x, int y)
{
}

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

void StarterPopup::mouse_click(int button, int x, int y)
{
  if (button == 0) // Left mouse button.
  {
    bool handled= false;

    if (!handled && _back_button_bounds.contains(x, y))
      if (_current_page > 0)
      {
        handled= true;
        _current_page--;
        set_needs_repaint();
      }

    if (!handled && _next_button_bounds.contains(x, y))
    {
      if (_current_page < _page_count - 1)
      {
        handled= true;
        _current_page++;
        set_needs_repaint();
      }
    }
/*
    if (!handled && _more_button_bounds.contains(x, y))
    {
      handled= true;
      if (_callback != NULL)
        _callback(StarterPluginCentral, _hot_entry, _user_data);
      set_modal_result(1);
    }
*/
    if (!handled && _action_button_bounds.contains(x, y))
    {
      handled= true;

      // See if the user hit the action button and show the action menu if so.
      _action_menu->popup_at(this, (int) (_action_button_bounds.right() + 5), (int) _action_button_bounds.top());
    }

    if (!handled && _hot_entry.is_valid())
    { 
      handle_command("start_plugin");
      set_modal_result(1);
    }
  }
}

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

void StarterPopup::mouse_enter()
{
}

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

void StarterPopup::mouse_leave()
{
}

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

void StarterPopup::mouse_move(int x, int y)
{
  app_StarterRef starter= starter_from_point(x, y);
  if (_hot_entry != starter)
  {
    _hot_entry= starter;
    set_needs_repaint();
  }
}

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

