/* 
 * Copyright (c) 2009, 2012, 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 "mforms/mforms.h"

#define SIDE_PADDING 12
#define TAB_SIDE_PADDING 10
#define TAB_TEXT_SPACING 8

#ifdef __APPLE__
  #define TAB_FONT "Lucida Grande"
#elif _WIN32
  #define TAB_FONT "Tahoma"
#else
  #define TAB_FONT "Helvetica"
#endif
#define TITLE_FONT_SIZE 13
#define SUB_TITLE_FONT_SIZE 9
#define TITLE_TEXT_SPACING 4

#define ICON_WIDTH 32
#define ICON_HEIGHT 32

#define INITIAL_TAB_HEIGHT 58

#ifndef _WIN32
using std::max;
#endif

using namespace base;
using namespace mforms;

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

TabSwitcher::TabSwitcher()
: _tabView(0), _selected(-1), _last_clicked(-1), _needs_relayout(true)
{
  _colors[TabBackground]= Color(236 / 255.0, 242 / 255.0, 248 / 255.0, 1); // A light blue.
  _colors[TabMainCaption]= Color(0, 0, 0, 1);
  _colors[TabSubCaption]= Color(0.5, 0.5, 0.5, 1);
  _colors[TabLineColor]= Color(173 / 255.0, 173 / 255.0, 173 / 255.0, 1);
  
  _background_pattern= 0;
  _line_pattern= 0;
  set_size(-1, INITIAL_TAB_HEIGHT);
}

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

TabSwitcher::~TabSwitcher()
{
  for (std::vector<TabItem*>::iterator iter= _items.begin(); iter != _items.end(); ++iter)
  {
    if ((*iter)->icon != NULL)
      cairo_surface_destroy((*iter)->icon);
    if ((*iter)->alt_icon != NULL)
      cairo_surface_destroy((*iter)->alt_icon);

    delete *iter;
  }
  destroy_patterns();
}

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

/**
 * Prepare patterns we need for drawing.
 */
void TabSwitcher::prepare_patterns()
{
  if (_background_pattern == NULL)
  {
    _background_pattern = cairo_pattern_create_linear(0, 0, 0, get_height());
    cairo_pattern_add_color_stop_rgba(_background_pattern, 0, 1, 1, 1, 0);
    cairo_pattern_add_color_stop_rgba(_background_pattern, 1, 1, 1, 1, 1);
  }
  if (_line_pattern == NULL)
  {
    Color line_color= _colors[TabLineColor];
    
    _line_pattern = cairo_pattern_create_linear(0, 0, 0, get_height());
    cairo_pattern_add_color_stop_rgba(_line_pattern, 0, line_color.red, line_color.green,
                                      line_color.blue, 0);
    cairo_pattern_add_color_stop_rgba(_line_pattern, 1, line_color.red, line_color.green,
                                      line_color.blue, line_color.alpha);
  }
}

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

/**
 * If any pattern is allocated, destroy it.
 */
void TabSwitcher::destroy_patterns()
{
  if (_background_pattern != NULL)
    cairo_pattern_destroy(_background_pattern);
  if (_line_pattern != NULL)
    cairo_pattern_destroy(_line_pattern);
}

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

int TabSwitcher::get_preferred_height() 
{
  return INITIAL_TAB_HEIGHT; 
}

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

void TabSwitcher::attach_to_tabview(TabView *tabView)
{
  _tabView= tabView;
  set_needs_relayout();
  
  scoped_connect(_tabView->signal_tab_changed(),boost::bind(&TabSwitcher::tab_changed, this));
}

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

/**
 * Sets the color of one of the tabulator parts.
 */
void TabSwitcher::set_color(TabElementPart part, Color new_color)
{
  _colors[part]= new_color;
  if (part == TabLineColor)
    destroy_patterns();
  set_needs_repaint();
}

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

int TabSwitcher::add_item(const std::string &title, const std::string &sub_title,
                          const std::string &icon_path, const std::string &alt_icon_path)
{
  TabItem *item = new TabItem();

  item->title= title;
  item->sub_title= sub_title;
  item->icon= cairo_image_surface_create_from_png(icon_path.c_str());
  if (item->icon && cairo_surface_status(item->icon) != CAIRO_STATUS_SUCCESS)
  {
    cairo_surface_destroy(item->icon);
    item->icon= NULL;
  }
  item->alt_icon= cairo_image_surface_create_from_png(alt_icon_path.c_str());
  if (item->alt_icon && cairo_surface_status(item->alt_icon) != CAIRO_STATUS_SUCCESS)
  {
    cairo_surface_destroy(item->alt_icon);
    item->alt_icon= NULL;
  }

  _items.push_back(item);

  if (_selected == -1)
    set_selected(_items.size() - 1);
  
  set_needs_relayout();
  return _items.size() - 1;
}

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

/**
 * Replaces the icon pair for the given item with the new values.
 */
void TabSwitcher::set_icon(int index, const std::string &icon_path, const std::string &alt_icon_path)
{
  if (index >= 0 && index < (int) _items.size())
  {
    TabItem *item = _items[index];

    if (item->icon != NULL)
      cairo_surface_destroy(item->icon);
    item->icon= cairo_image_surface_create_from_png(icon_path.c_str());
    if (item->icon && cairo_surface_status(item->icon) != CAIRO_STATUS_SUCCESS)
    {
      cairo_surface_destroy(item->icon);
      item->icon= NULL;
    }

    if (item->alt_icon != NULL)
      cairo_surface_destroy(item->alt_icon);
    item->alt_icon= cairo_image_surface_create_from_png(alt_icon_path.c_str());
    if (item->alt_icon && cairo_surface_status(item->alt_icon) != CAIRO_STATUS_SUCCESS)
    {
      cairo_surface_destroy(item->alt_icon);
      item->alt_icon= NULL;
    }

    set_needs_relayout();
  }
}

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

void TabSwitcher::set_selected(int index)
{
  _selected= index;
  if (_tabView != NULL)
    _tabView->set_active_tab(index);
  set_needs_repaint();
}

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

void TabSwitcher::set_needs_relayout()
{
  _needs_relayout= true;
  destroy_patterns();
  set_needs_repaint();
}

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

void TabSwitcher::set_layout_dirty()
{
  DrawBox::set_layout_dirty(true);
  set_needs_relayout();
}

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

void TabSwitcher::layout(cairo_t *cr)
{
  if (_needs_relayout)
  {
    _needs_relayout= false;

    cairo_save(cr);
    
    cairo_select_font_face(cr, TAB_FONT, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
    
    int new_height= 0;
    int new_width= 2 * SIDE_PADDING;
    for (std::vector<TabItem*>::iterator iter = _items.begin(); iter != _items.end(); ++iter)
    {
      int title_width= 0;
      int sub_title_width= 0;
      int height= 0;
      cairo_text_extents_t extents;
      
      if ((*iter)->title != "")
      {
        cairo_set_font_size(cr, TITLE_FONT_SIZE);
        cairo_text_extents(cr, (*iter)->title.c_str(), &extents);
        title_width= (int) extents.x_advance;
        height= (int) (extents.y_advance - extents.y_bearing);
        (*iter)->text_y_advance= height + TITLE_TEXT_SPACING;
      }
      
      if ((*iter)->sub_title != "")
      {
        cairo_set_font_size(cr, SUB_TITLE_FONT_SIZE);
        cairo_text_extents(cr, (*iter)->sub_title.c_str(), &extents);
        sub_title_width = (int) extents.x_advance;
        height += (int) extents.y_advance;
      }
      
      (*iter)->text_height= height;
      (*iter)->text_width= max(title_width, sub_title_width);
      (*iter)->width= (*iter)->text_width + 2 * TAB_SIDE_PADDING;
      if ((*iter)->icon != NULL)
      {
        (*iter)->width += ICON_WIDTH + TAB_TEXT_SPACING;
        if (height < ICON_HEIGHT)
          height= ICON_HEIGHT;
      }
      if (height > new_height)
        new_height= height;
      new_width += (*iter)->width;
    }

    // Grow only.
    if (new_width < get_width())
      new_width= get_width();
    if (new_height < get_height())
      new_height= get_height();
    set_size(new_width, new_height);
    
    cairo_restore(cr);
  }
}

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

void TabSwitcher::mouse_down(int button, int x, int y)
{
  DrawBox::mouse_down(button, x, y);
  
  // For now ignore which button was pressed.
  _last_clicked= index_from_point(x, y);
}

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

void TabSwitcher::mouse_up(int button, int x, int y)
{
  DrawBox::mouse_up(button, x, y);
}

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

void TabSwitcher::mouse_click(int button, int x, int y)
{
  DrawBox::mouse_click(button, x, y);
  
  // Don't change anything if the user clicked outside of any tab.
  if (_last_clicked > -1 && _last_clicked == index_from_point(x, y))
  {
    set_selected(_last_clicked);
    _signal_changed();
  }
}

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

void TabSwitcher::repaint(cairo_t *cr, int areax, int areay, int areaw, int areah)
{
  layout(cr);
  prepare_patterns();

  cairo_save(cr);
  
  // Font selection for both the main caption and the sub caption.
  cairo_select_font_face(cr, TAB_FONT, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
  
  // Erase background.
  Color background= _colors[TabBackground];
  cairo_set_source_rgba(cr, background.red, background.green, background.blue, background.alpha);
  cairo_paint(cr);

  // For Cairo we rather use coordinates "half into" a pixel for sharper drawing.
  double width= get_width() + 0.5;
  double height= get_height() + 0.5;
  
  // Draw bottom line.
  cairo_set_source_rgba(cr, _colors[TabLineColor].red, _colors[TabLineColor].green, 
                        _colors[TabLineColor].blue, _colors[TabLineColor].alpha);
  cairo_set_line_width(cr, 1);
  cairo_move_to(cr, 0.5, height - 1);
  cairo_line_to(cr, width - 1, height - 1);
  cairo_stroke(cr);

  // Now go for each tab and paint it.
  // Keep in mind for sharp lines we need to move them to the middle of two pixels.
  // Not so for image and text drawing!
  double x= SIDE_PADDING;
  double x_line= x + 0.5;
  int i= 0;
  for (std::vector<TabItem*>::iterator iter = _items.begin(); iter != _items.end(); ++iter, ++i)
  {
    // Background gradient if this is the selected tab.
    if (i == _selected)
    {
      cairo_set_source(cr, _background_pattern);
      cairo_rectangle(cr, x_line + 1, 0, (*iter)->width - 1, height);
      cairo_fill(cr);

      // The bottom line for a selected tab is notably lighter, so draw this separately.
      cairo_set_source_rgba(cr, 0, 0, 0, 0.1);
      cairo_move_to(cr, x_line + 1, height - 1);
      cairo_line_to(cr, x_line + (*iter)->width - 1, height - 1);
      cairo_stroke(cr);
      
      // Left border, also a gradient.
      cairo_set_source(cr, _line_pattern);
      cairo_move_to(cr, x_line, 0);
      cairo_line_to(cr, x_line, height);
      cairo_stroke(cr);
    }
    
    // Icon.
    x += TAB_SIDE_PADDING;
    x_line += TAB_SIDE_PADDING;
    cairo_surface_t* icon= (i == _selected) ? (*iter)->icon : (*iter)->alt_icon;
    if (icon != NULL)
    {
      cairo_set_source_surface(cr, icon, x, (height - ICON_HEIGHT) / 2);
      if (i == _selected)
        cairo_paint(cr);
      else
        cairo_paint_with_alpha(cr, 0.75);
      x += ICON_WIDTH + TAB_TEXT_SPACING;
      x_line += ICON_WIDTH + TAB_TEXT_SPACING;
    }
    
    // Text.
    if ((*iter)->text_width > 0)
    {
      double text_offset= (height - (*iter)->text_height) / 2;
      Color text_color;
      
      if ((*iter)->title != "")
      {
        cairo_set_font_size(cr, TITLE_FONT_SIZE);
        text_color= _colors[TabMainCaption];
        
        cairo_set_source_rgba(cr, text_color.red, text_color.green, text_color.blue, text_color.alpha);
        cairo_move_to(cr, x, text_offset);
        cairo_show_text(cr, (*iter)->title.c_str());
        cairo_stroke(cr);
        
        text_offset += (*iter)->text_y_advance;
      }
      
      if ((*iter)->sub_title != "")
      {
        cairo_set_font_size(cr, SUB_TITLE_FONT_SIZE);
        text_color= _colors[TabSubCaption];
        cairo_set_source_rgba(cr, text_color.red, text_color.green, text_color.blue, text_color.alpha);
        cairo_move_to(cr, x, text_offset);
        cairo_show_text(cr, (*iter)->sub_title.c_str());
        cairo_stroke(cr);
      }
      
      x += (*iter)->text_width;
      x_line += (*iter)->text_width;
    }
    
    x += TAB_SIDE_PADDING;
    x_line += TAB_SIDE_PADDING;
    
    // Right border (only for the selected item).
    if (i == _selected)
    {
      cairo_set_source(cr, _line_pattern);
      cairo_move_to(cr, x_line, 0);
      cairo_line_to(cr, x_line, height);
      cairo_stroke(cr);
    }
  }
    
  cairo_restore(cr);
}

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

/**
 * Returns the index of the tab item at the given position or -1 if there is none.
 *
 * The coordinates must be given in local space.
 */
int TabSwitcher::index_from_point(int x, int y)
{
  if (_items.size() == 0 || x < 0 || x > get_width() || y < 0 || y > get_height())
    return -1;
  
  float offset= 0.5 + SIDE_PADDING;
  if (x < offset)
    return -1;
  
  int i= 0;
  for (std::vector<TabItem*>::iterator iter = _items.begin(); iter != _items.end(); ++iter, ++i)
  {
    if (offset <= x && x <= offset + (*iter)->width)
      return i;
    offset += (*iter)->width;
  }
  return -1;
}

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

void TabSwitcher::tab_changed()
{
  _selected = _tabView->get_active_tab();
  set_needs_repaint();
}
