/*
 * Dillo Widget
 *
 * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>
 *
 * 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; either version 3 of the License, or
 * (at your option) any later version.
 *
 * 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, see <http://www.gnu.org/licenses/>.
 */



#include "fltkcore.hh"
#include "fltkflatview.hh"
#include "fltkcomplexbutton.hh"
#include "dlib/dlib.h"
#include "../lout/msg.h"
#include "../lout/misc.hh"

#include <FL/Fl.H>
#include <FL/fl_draw.H>
#include <FL/Fl_Input.H>
#include <FL/Fl_Text_Editor.H>
#include <FL/Fl_Check_Button.H>
#include <FL/Fl_Round_Button.H>
#include <FL/Fl_Choice.H>
#include <FL/Fl_Browser.H>

#include <stdio.h>

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

static Fl_Color fltkui_dimmed(Fl_Color c, Fl_Color bg)
{
   return fl_color_average(c, bg, .33f);
};

//----------------------------------------------------------------------------
/*
 * Local sub classes
 */

/*
 * Used to show optional placeholder text and to enable CTRL+{a,e,d,k} in
 * form inputs (for start,end,del,cut)
 */
class CustInput2 : public Fl_Input {
public:
   CustInput2 (int x, int y, int w, int h, const char* l=0);
   ~CustInput2 () { if (placeholder) free(placeholder); };
   void set_placeholder(const char *str);
   int show_placeholder();
   int show_normal(const char *str);
   void textcolor(Fl_Color c);
   void input_type(int t);
   int value(const char* str);
   const char* value();
   int handle(int e);
private:
   char *placeholder;
   bool showing_placeholder;
   Fl_Color usual_color;
   int usual_type;
};

CustInput2::CustInput2 (int x, int y, int w, int h, const char* l) :
   Fl_Input(x,y,w,h,l)
{
   placeholder = NULL;
   showing_placeholder = false;
   usual_color = FL_BLACK;      /* just init until widget style is set */
};

/*
 * Show normal text.
 */
int CustInput2::show_normal(const char *str)
{
   showing_placeholder = false;
   Fl_Input::textcolor(usual_color);
   Fl_Input::input_type(usual_type);
   return Fl_Input::value(str);
}

/*
 * Show the placeholder text.
 */
int CustInput2::show_placeholder()
{
   int ret;

   showing_placeholder = true;
   Fl_Input::textcolor(fltkui_dimmed(usual_color, color()));
   Fl_Input::input_type(FL_NORMAL_INPUT);
   ret = Fl_Input::value(placeholder);
   position(0);
   return ret;
}

/*
 * Set the placeholder text.
 */
void CustInput2::set_placeholder(const char *str)
{
   if (placeholder)
      free(placeholder);
   placeholder = dStrdup(str);

   if ((Fl::focus() != this) && !*value()) {
      show_placeholder();
   }
}

/*
 * Set the text color.
 */
void CustInput2::textcolor(Fl_Color c)
{
   usual_color = c;
   if (showing_placeholder)
      c = fltkui_dimmed(c, color());
   Fl_Input::textcolor(c);
}

/*
 * Set the input type (normal, password, etc.)
 */
void CustInput2::input_type(int t)
{
   usual_type = t;
   Fl_Input::input_type(t);
}

/*
 * Set the value of the input.
 * NOTE that we're not being very careful with the return value, which is
 * supposed to be nonzero iff the value was changed.
 */
int CustInput2::value(const char *str)
{
   return (placeholder && (!str || !*str) && Fl::focus() != this)
          ? show_placeholder() : show_normal(str);
}

/*
 * Return the value (text) of the input.
 */
const char* CustInput2::value()
{
   return showing_placeholder ? "" : Fl_Input::value();
}

int CustInput2::handle(int e)
{
   int rc, k = Fl::event_key();

   _MSG("CustInput2::handle event=%d\n", e);

   // We're only interested in some flags
   unsigned modifier = Fl::event_state() & (FL_SHIFT | FL_CTRL | FL_ALT);

   if (e == FL_KEYBOARD) {
      if (k == FL_Page_Down || k == FL_Page_Up || k == FL_Up || k == FL_Down) {
         // Let them through for key commands and viewport motion.
         return 0;
      }
      if (modifier == FL_CTRL) {
         if (k == 'a' || k == 'e') {
            position(k == 'a' ? 0 : size());
            return 1;
         } else if (k == 'k') {
            cut(position(), size());
            return 1;
         } else if (k == 'd') {
            cut(position(), position()+1);
            return 1;
         } else if (k == 'h' || k == 'i' || k == 'j' || k == 'l' || k == 'm') {
            // Fl_Input wants to use ^H as backspace, and also "insert a few
            // selected control characters literally", but this gets in the way
            // of key commands.
            return 0;
         }
      }
   } else if (e == FL_UNFOCUS) {
      if (placeholder && !value()[0]) {
         show_placeholder();
      }
   }

   rc = Fl_Input::handle(e);

   if (rc && e == FL_FOCUS) {
      // Nonzero return from handle() should mean that focus was accepted.
      if (showing_placeholder)
         show_normal("");
   }
   return rc;
}

/*
 * Used to show optional placeholder text.
 */
class CustTextEditor : public Fl_Text_Editor {
public:
   CustTextEditor (int x, int y, int w, int h, const char* l=0);
   ~CustTextEditor ();
   void set_placeholder(const char *str);
   void show_placeholder();
   void show_normal(const char *str);
   void textcolor(Fl_Color c);
   void value(const char* str);
   char* value();
   int handle(int e);
private:
   char *placeholder;
   bool showing_placeholder;
   Fl_Color usual_color;
   char *text_copy;
};

CustTextEditor::CustTextEditor (int x, int y, int w, int h, const char* l) :
                               Fl_Text_Editor(x,y,w,h,l)
{
   placeholder = NULL;
   showing_placeholder = false;
   buffer(new Fl_Text_Buffer());
   usual_color = FL_BLACK;      /* just init until widget style is set */
   text_copy = NULL;
};

CustTextEditor::~CustTextEditor ()
{
   Fl_Text_Buffer *buf = buffer();

   buffer(NULL);
   delete buf;

   if (placeholder)
      free(placeholder);
   if (text_copy)
      free(text_copy);
}   

/*
 * Show normal text.
 */
void CustTextEditor::show_normal(const char *str)
{
   showing_placeholder = false;
   Fl_Text_Editor::textcolor(usual_color);
   buffer()->text(str);
}

/*
 * Show the placeholder text.
 */
void CustTextEditor::show_placeholder()
{
   showing_placeholder = true;
   Fl_Text_Editor::textcolor(fltkui_dimmed(usual_color, color()));
   buffer()->text(placeholder);
}

/*
 * Set the placeholder text.
 */
void CustTextEditor::set_placeholder(const char *str)
{
   if (placeholder)
      free(placeholder);
   placeholder = dStrdup(str);

   if ((Fl::focus() != this) && buffer()->length() == 0) {
      show_placeholder();
   }
}

/*
 * Set the text color.
 */
void CustTextEditor::textcolor(Fl_Color c)
{
   usual_color = c;
   if (showing_placeholder)
      c = fltkui_dimmed(c, color());
   Fl_Text_Editor::textcolor(c);
}

/*
 * Set the value of the input.
 */
void CustTextEditor::value(const char *str)
{
   if (placeholder && (!str || !*str) && Fl::focus() != this)
      show_placeholder();
   else
      show_normal(str);
}

/*
 * Return the value (text) of the input.
 */
char* CustTextEditor::value()
{
   /* FLTK-1.3 insists upon returning a new copy of the buffer text, so
    * we have to keep track of it.
    */
   if (text_copy)
      free(text_copy);
   text_copy = showing_placeholder ? dStrdup("") : buffer()->text();
   return text_copy;
}

int CustTextEditor::handle(int e)
{
   int rc;

   if (e == FL_UNFOCUS) {
      if (placeholder && buffer()->length() == 0) {
         show_placeholder();
      }
   }

   rc = Fl_Text_Editor::handle(e);

   if (rc && e == FL_FOCUS) {
      // Nonzero return from handle() should mean that focus was accepted.
      if (showing_placeholder)
         show_normal("");
   }
   return rc;
}


/*
 * Used to handle some keystrokes as shortcuts to option menuitems
 * (i.e. jump to the next menuitem whose label starts with the pressed key)
 */
class CustChoice : public Fl_Choice {
public:
   CustChoice (int x, int y, int w, int h, const char* l=0) :
      Fl_Choice(x,y,w,h,l) {};
   int handle(int e);
};

int CustChoice::handle(int e)
{
   int k = Fl::event_key();
   unsigned modifier = Fl::event_state() & (FL_SHIFT|FL_CTRL|FL_ALT|FL_META);

   _MSG("CustChoice::handle %p e=%d active=%d focus=%d\n",
       this, e, active(), (Fl::focus() == this));
   if (Fl::focus() != this) {
      ; // Not Focused, let FLTK handle it
   } else if (e == FL_KEYDOWN && modifier == 0) {
      if (k == FL_Enter || k == FL_Down) {
         return Fl_Choice::handle(FL_PUSH); // activate menu

      } else if (isalnum(k)) { // try key as shortcut to menuitem
         int t = value()+1 >= size() ? 0 : value()+1;
         while (t != value()) {
             const Fl_Menu_Item *mi = &(menu()[t]);
             if (mi->submenu()) // submenu?
                ;
             else if (mi->label() && mi->active()) { // menu item?
                if (k == tolower(mi->label()[0])) {
                   value(mi);
                   return 1; // Let FLTK know we used this key
                }
             }
             if (++t == size())
                t = 0;
         }
      }
   }

   return Fl_Choice::handle(e);
}

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

namespace dw {
namespace fltk {
namespace ui {

enum { RELIEF_X_THICKNESS = 3, RELIEF_Y_THICKNESS = 3 };

using namespace lout::object;
using namespace lout::container::typed;

FltkResource::FltkResource (FltkPlatform *platform)
{
   DBG_OBJ_CREATE ("dw::fltk::ui::FltkResource");

   this->platform = platform;

   allocation.x = 0;
   allocation.y = 0;
   allocation.width = 1;
   allocation.ascent = 1;
   allocation.descent = 0;

   style = NULL;

   enabled = true;
}

/**
 * This is not a constructor, since it calls some virtual methods, which
 * should not be done in a C++ base constructor.
 */
void FltkResource::init (FltkPlatform *platform)
{
   view = NULL;
   widget = NULL;
   platform->attachResource (this);
}

FltkResource::~FltkResource ()
{
   platform->detachResource (this);
   if (widget) {
      if (view) {
         view->removeFltkWidget(widget);
      }
      delete widget;
   }
   if (style)
      style->unref ();

   DBG_OBJ_DELETE ();
}

void FltkResource::attachView (FltkView *view)
{
   if (this->view)
      MSG_ERR("FltkResource::attachView: multiple views!\n");

   if (view->usesFltkWidgets ()) {
      this->view = view;

      widget = createNewWidget (&allocation);
      view->addFltkWidget (widget, &allocation);
      if (style)
         setWidgetStyle (widget, style);
      if (! enabled)
         widget->deactivate ();
   }
}

void FltkResource::detachView (FltkView *view)
{
   if (this->view != view)
      MSG_ERR("FltkResource::detachView: this->view: %p view: %p\n",
              (void *) this->view, (void *) view);
   this->view = NULL;
}

void FltkResource::sizeAllocate (core::Allocation *allocation)
{
   DBG_OBJ_ENTER ("resize", 0, "sizeAllocate", "%d, %d; %d * (%d + %d)",
                  allocation->x, allocation->y, allocation->width,
                  allocation->ascent, allocation->descent);

   this->allocation = *allocation;
   view->allocateFltkWidget (widget, allocation);

   DBG_OBJ_LEAVE ();
}

void FltkResource::draw (core::View *view, core::Rectangle *area,
                         core::DrawingContext *context)
{
   FltkView *fltkView = (FltkView*)view;
   if (fltkView->usesFltkWidgets () && this->view == fltkView) {
      fltkView->drawFltkWidget (widget, area);
   }
}

void FltkResource::setStyle (core::style::Style *style)
{
   if (this->style)
      this->style->unref ();

   this->style = style;
   style->ref ();

   setWidgetStyle (widget, style);
}

void FltkResource::setWidgetStyle (Fl_Widget *widget,
                                   core::style::Style *style)
{
   FltkFont *font = (FltkFont*)style->font;
   widget->labelsize (font->size);
   widget->labelfont (font->font);

   FltkColor *bg = (FltkColor*)style->backgroundColor;
   if (bg) {
      int normal_bg = bg->colors[FltkColor::SHADING_NORMAL];

      if (style->color) {
         int style_fg = ((FltkColor*)style->color)->colors
                                                   [FltkColor::SHADING_NORMAL];
         Fl_Color fg = fl_contrast(style_fg, normal_bg);

         widget->labelcolor(fg);
         widget->selection_color(fg);
      }

      widget->color(normal_bg);
   }
}

void FltkResource::setDisplayed(bool displayed)
{
   if (displayed)
      widget->show();
   else
      widget->hide();
}

bool FltkResource::displayed()
{
   bool ret = false;

   if (widget) {
      // visible() is not the same thing as being show()n exactly, but
      // show()/hide() set it appropriately for our purposes.
      ret = widget->visible();
   }
   return ret;
}

bool FltkResource::isEnabled ()
{
   return enabled;
}

void FltkResource::setEnabled (bool enabled)
{
   this->enabled = enabled;

   if (enabled)
      widget->activate ();
   else
      widget->deactivate ();
}

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

template <class I> FltkSpecificResource<I>::FltkSpecificResource (FltkPlatform
                                                                  *platform) :
   FltkResource (platform)
{
   DBG_OBJ_CREATE ("dw::fltk::ui::FltkSpecificResource<>");
   DBG_OBJ_BASECLASS (I);
   DBG_OBJ_BASECLASS (FltkResource);
}

template <class I> FltkSpecificResource<I>::~FltkSpecificResource ()
{
   DBG_OBJ_DELETE ();
}

template <class I> void FltkSpecificResource<I>::sizeAllocate (core::Allocation
                                                               *allocation)
{
   FltkResource::sizeAllocate (allocation);
}

template <class I> void FltkSpecificResource<I>::draw (core::View *view,
                                                       core::Rectangle *area,
                                                       core::DrawingContext
                                                       *context)
{
   FltkResource::draw (view, area, context);
}

template <class I> void FltkSpecificResource<I>::setStyle (core::style::Style
                                                           *style)
{
   FltkResource::setStyle (style);
}

template <class I> bool FltkSpecificResource<I>::isEnabled ()
{
   return FltkResource::isEnabled ();
}

template <class I> void FltkSpecificResource<I>::setEnabled (bool enabled)
{
   FltkResource::setEnabled (enabled);
}

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

class EnterButton : public Fl_Button {
public:
   EnterButton (int x,int y,int w,int h, const char* label = 0) :
      Fl_Button (x,y,w,h,label) {};
   int handle(int e);
};

int EnterButton::handle(int e)
{
   if (e == FL_KEYBOARD && Fl::focus() == this && Fl::event_key() == FL_Enter){
      set_changed();
      simulate_key_action();
      do_callback();
      return 1;
   }
   return Fl_Button::handle(e);
}

FltkLabelButtonResource::FltkLabelButtonResource (FltkPlatform *platform,
                                                  const char *label):
   FltkSpecificResource <dw::core::ui::LabelButtonResource> (platform)
{
   this->label = dStrdup (label);
   init (platform);
}

FltkLabelButtonResource::~FltkLabelButtonResource ()
{
   free((char *)label);
}

Fl_Widget *FltkLabelButtonResource::createNewWidget (core::Allocation
                                                     *allocation)
{
   Fl_Button *button =
        new EnterButton (allocation->x, allocation->y, allocation->width,
                         allocation->ascent + allocation->descent, label);
   button->callback (widgetCallback, this);
   button->when (FL_WHEN_RELEASE);
   return button;
}

void FltkLabelButtonResource::sizeRequest (core::Requisition *requisition)
{
   DBG_OBJ_ENTER0 ("resize", 0, "sizeRequest");

   if (style) {
      FltkFont *font = (FltkFont*)style->font;
      fl_font(font->font,font->size);
      requisition->width =
         (int)fl_width (label, strlen (label))
         + 2 * RELIEF_X_THICKNESS;
      requisition->ascent = font->ascent + RELIEF_Y_THICKNESS;
      requisition->descent = font->descent + RELIEF_Y_THICKNESS;
   } else {
      requisition->width = 1;
      requisition->ascent = 1;
      requisition->descent = 0;
   }

   DBG_OBJ_MSGF ("resize", 1, "result: %d * (%d + %d)",
                 requisition->width, requisition->ascent, requisition->descent);
   DBG_OBJ_LEAVE ();
}

/*
 * Get FLTK state and translate to dw
 *
 * TODO: find a good home for this and the fltkviewbase.cc original.
 */
static core::ButtonState getDwButtonState ()
{
   int s1 = Fl::event_state ();
   int s2 = (core::ButtonState)0;

   if (s1 & FL_SHIFT)   s2 |= core::SHIFT_MASK;
   if (s1 & FL_CTRL)    s2 |= core::CONTROL_MASK;
   if (s1 & FL_ALT)     s2 |= core::META_MASK;
   if (s1 & FL_BUTTON1) s2 |= core::BUTTON1_MASK;
   if (s1 & FL_BUTTON2) s2 |= core::BUTTON2_MASK;
   if (s1 & FL_BUTTON3) s2 |= core::BUTTON3_MASK;

   return (core::ButtonState)s2;
}

static void setButtonEvent(dw::core::EventButton *event)
{
   event->xCanvas = Fl::event_x();
   event->yCanvas = Fl::event_y();
   event->state = getDwButtonState();
   event->button = Fl::event_button();
   event->numPressed = Fl::event_clicks() + 1;
}

void FltkLabelButtonResource::widgetCallback (Fl_Widget *widget,
                                              void *data)
{
   if (!Fl::event_button3()) {
      FltkLabelButtonResource *lbr = (FltkLabelButtonResource*) data;
      dw::core::EventButton event;
      setButtonEvent(&event);
      lbr->emitClicked(&event);
   }
}

const char *FltkLabelButtonResource::getLabel ()
{
   return label;
}


void FltkLabelButtonResource::setLabel (const char *label)
{
   free((char *)this->label);
   this->label = dStrdup (label);

   widget->label (this->label);
   queueResize (true);
}

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

FltkComplexButtonResource::FltkComplexButtonResource (FltkPlatform *platform,
                                                      dw::core::Widget
                                                      *widget, bool relief):
   FltkSpecificResource <dw::core::ui::ComplexButtonResource> (platform)
{
   flatView = topView = NULL;
   this->relief = relief;
   FltkResource::init (platform);
   ComplexButtonResource::init (widget);
}

FltkComplexButtonResource::~FltkComplexButtonResource ()
{
}

void FltkComplexButtonResource::widgetCallback (Fl_Widget *widget,
                                                void *data)
{
   FltkComplexButtonResource *res = (FltkComplexButtonResource*)data;

   if (Fl::event() == FL_RELEASE && Fl::event_button() != FL_RIGHT_MOUSE) {
      int w = widget->w(), h = widget->h();

      res->click_x = Fl::event_x() - widget->x();
      res->click_y = Fl::event_y() - widget->y();
      if (res->style) {
         res->click_x -= res->style->boxOffsetX();
         res->click_y -= res->style->boxOffsetY();
         w -= res->style->boxDiffWidth();
         h -= res->style->boxDiffHeight();
      }
      if (res->click_x >= 0 && res->click_y >= 0 &&
          res->click_x < w && res->click_y < h) {
         dw::core::EventButton event;
         setButtonEvent(&event);
         res->emitClicked(&event);
      }
   } else if (Fl::event() == FL_KEYBOARD) {
      // Simulate a click.
      dw::core::EventButton event;

      res->click_x = res->click_y = 0;
      event.xCanvas = widget->x() + res->style->boxOffsetX();
      event.yCanvas = widget->y() + res->style->boxOffsetY();
      // ButtonState doesn't have mouse button values on a release.
      event.state = (core::ButtonState) 0;
      event.button = 1;
      event.numPressed = 1;
      res->emitClicked(&event);
   }
}

dw::core::Platform *FltkComplexButtonResource::createPlatform ()
{
   return new FltkPlatform ();
}

void FltkComplexButtonResource::attachView (FltkView *view)
{
   FltkResource::attachView (view);

   if (view->usesFltkWidgets ())
      topView = view;
}

void FltkComplexButtonResource::detachView (FltkView *view)
{
   FltkResource::detachView (view);
}

void FltkComplexButtonResource::sizeAllocate (core::Allocation *allocation)
{
   FltkResource::sizeAllocate (allocation);

   DBG_OBJ_MSGF_O ("resize", 0, flatView,
                   "<b>resize</b> (%d %d, <i>%d - 2 * %d =</i> %d, "
                   "<i>%d + %d - 2 * %d =</i> %d)",
                   reliefXThickness (), reliefYThickness (),
                   allocation->width, reliefXThickness (),
                   allocation->width - 2 * reliefXThickness (),
                   allocation->ascent, allocation->descent,
                   reliefYThickness (),
                   allocation->ascent + allocation->descent
                   - 2 * reliefYThickness ());

   ((FltkFlatView*)flatView)->resize (
      reliefXThickness (), reliefYThickness (),
      allocation->width - 2 * reliefXThickness (),
      allocation->ascent + allocation->descent - 2 * reliefYThickness ());

   ((FltkFlatView*)flatView)->parent ()->init_sizes ();
}

void FltkComplexButtonResource::setLayout (dw::core::Layout *layout)
{
   layout->attachView (flatView);
}

int FltkComplexButtonResource::reliefXThickness ()
{
   return relief ? RELIEF_X_THICKNESS : 0;
}

int FltkComplexButtonResource::reliefYThickness ()
{
   return relief ? RELIEF_Y_THICKNESS : 0;
}


Fl_Widget *FltkComplexButtonResource::createNewWidget (core::Allocation
                                                            *allocation)
{
   ComplexButton *button =
      new ComplexButton (allocation->x, allocation->y, allocation->width,
                         allocation->ascent + allocation->descent);
   button->callback (widgetCallback, this);
   button->when (FL_WHEN_RELEASE);
   if (!relief)
      button->box(FL_NO_BOX);

   flatView = new FltkFlatView (allocation->x + reliefXThickness (),
                                allocation->y + reliefYThickness (),
                                allocation->width - 2 * reliefXThickness (),
                                allocation->ascent + allocation->descent
                                   - 2 * reliefYThickness ());
   button->add ((FltkFlatView *)flatView);

   if (layout)
      layout->attachView (flatView);
   return button;
}

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

FltkEntryResource::FltkEntryResource (FltkPlatform *platform, int size,
                                      bool password, const char *label,
                                      const char *placeholder):
   FltkSpecificResource <dw::core::ui::EntryResource> (platform)
{
   this->size = size;
   this->password = password;
   this->label = label ? dStrdup(label) : NULL;
   this->label_w = 0;
   this->placeholder = placeholder ? dStrdup(placeholder) : NULL;

   initText = NULL;
   editable = false;

   init (platform);
}

FltkEntryResource::~FltkEntryResource ()
{
   if (initText)
      free((char *)initText);
   if (label)
      free(label);
   if (placeholder)
      free(placeholder);
}

Fl_Widget *FltkEntryResource::createNewWidget (core::Allocation
                                                    *allocation)
{
   CustInput2 *input =
        new CustInput2(allocation->x, allocation->y, allocation->width,
                      allocation->ascent + allocation->descent);
   input->input_type(password ? FL_SECRET_INPUT : FL_NORMAL_INPUT);
   input->callback (widgetCallback, this);
   input->when (FL_WHEN_ENTER_KEY_ALWAYS);

   if (label) {
      input->label(label);
      input->align(FL_ALIGN_LEFT);
   }
   if (initText)
      input->value (initText);
   if (placeholder)
      input->set_placeholder(placeholder);

   return input;
}

void FltkEntryResource::setWidgetStyle (Fl_Widget *widget,
                                        core::style::Style *style)
{
   CustInput2 *in = (CustInput2 *)widget;

   FltkResource::setWidgetStyle(widget, style);

   in->textcolor(widget->labelcolor());
   in->cursor_color(widget->labelcolor());
   in->textsize(in->labelsize());
   in->textfont(in->labelfont());

   if (label) {
      int h;
      label_w = 0;
      widget->measure_label(label_w, h);
      label_w += RELIEF_X_THICKNESS;
   }
}

void FltkEntryResource::setDisplayed(bool displayed)
{
   FltkResource::setDisplayed(displayed);
   queueResize(true);
}

void FltkEntryResource::sizeRequest (core::Requisition *requisition)
{
   DBG_OBJ_ENTER0 ("resize", 0, "sizeRequest");

   if (displayed() && style) {
      FltkFont *font = (FltkFont*)style->font;
      fl_font(font->font,font->size);
      // WORKAROUND: A bug with fl_width(uint_t) on non-xft X was present in
      // 1.3.0 (STR #2688).
      requisition->width =
         (int)fl_width ("n")
         * (size == UNLIMITED_SIZE ? 10 : size)
         + label_w + (2 * RELIEF_X_THICKNESS);
      requisition->ascent = font->ascent + RELIEF_Y_THICKNESS;
      requisition->descent = font->descent + RELIEF_Y_THICKNESS;
   } else {
      requisition->width = 0;
      requisition->ascent = 0;
      requisition->descent = 0;
   }

   DBG_OBJ_MSGF ("resize", 1, "result: %d * (%d + %d)",
                 requisition->width, requisition->ascent, requisition->descent);
   DBG_OBJ_LEAVE ();
}

void FltkEntryResource::sizeAllocate (core::Allocation *allocation)
{
   if (!label) {
      FltkResource::sizeAllocate(allocation);
   } else {
      DBG_OBJ_MSGF ("resize", 0,
                    "<b>sizeAllocate</b> (%d, %d; %d * (%d + %d))",
                    allocation->x, allocation->y, allocation->width,
                    allocation->ascent, allocation->descent);

      this->allocation = *allocation;

      /* push the Fl_Input over to the right of the label */
      core::Allocation a = this->allocation;
      a.x += this->label_w;
      a.width -= this->label_w;
      view->allocateFltkWidget (widget, &a);
   }
}

void FltkEntryResource::widgetCallback (Fl_Widget *widget, void *data)
{
   ((FltkEntryResource*)data)->emitActivate ();
}

const char *FltkEntryResource::getText ()
{
   return ((CustInput2*)widget)->value ();
}

void FltkEntryResource::setText (const char *text)
{
   if (initText)
      free((char *)initText);
   initText = dStrdup (text);

   ((CustInput2*)widget)->value (initText);
}

bool FltkEntryResource::isEditable ()
{
   return editable;
}

void FltkEntryResource::setEditable (bool editable)
{
   this->editable = editable;
}

void FltkEntryResource::setMaxLength (int maxlen)
{
   ((Fl_Input *)widget)->maximum_size(maxlen);
}

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

static int kf_backspace_word (int c, Fl_Text_Editor *e)
{
   int p1, p2 = e->insert_position();

   e->previous_word();
   p1 = e->insert_position();
   e->buffer()->remove(p1, p2);
   e->show_insert_position();
   e->set_changed();
   if (e->when() & FL_WHEN_CHANGED)
      e->do_callback();
   return 0;
}

FltkMultiLineTextResource::FltkMultiLineTextResource (FltkPlatform *platform,
                                                      int cols, int rows,
                                                      const char *placeholder):
   FltkSpecificResource <dw::core::ui::MultiLineTextResource> (platform)
{
   editable = false;

   numCols = cols;
   numRows = rows;

   DBG_OBJ_SET_NUM ("numCols", numCols);
   DBG_OBJ_SET_NUM ("numRows", numRows);

   // Check values. Upper bound check is left to the caller.
   if (numCols < 1) {
      MSG_WARN("numCols = %d is set to 1.\n", numCols);
      numCols = 1;
   }
   if (numRows < 1) {
      MSG_WARN("numRows = %d is set to 1.\n", numRows);
      numRows = 1;
   }
   this->placeholder = placeholder ? dStrdup(placeholder) : NULL;

   init (platform);
}

FltkMultiLineTextResource::~FltkMultiLineTextResource ()
{
   if (placeholder)
      free(placeholder);
}

Fl_Widget *FltkMultiLineTextResource::createNewWidget (core::Allocation
                                                            *allocation)
{
   CustTextEditor *text =
      new CustTextEditor (allocation->x, allocation->y, allocation->width,
                          allocation->ascent + allocation->descent);
   text->wrap_mode(Fl_Text_Display::WRAP_AT_BOUNDS, 0);
   text->remove_key_binding(FL_BackSpace, FL_TEXT_EDITOR_ANY_STATE);
   text->add_key_binding(FL_BackSpace, 0, Fl_Text_Editor::kf_backspace);
   text->add_key_binding(FL_BackSpace, FL_CTRL, kf_backspace_word);
   if (placeholder)
      text->set_placeholder(placeholder);
   return text;
}

void FltkMultiLineTextResource::setWidgetStyle (Fl_Widget *widget,
                                                core::style::Style *style)
{
   CustTextEditor *ed = (CustTextEditor *)widget;

   FltkResource::setWidgetStyle(widget, style);

   ed->textcolor(widget->labelcolor());
   ed->cursor_color(widget->labelcolor());
   ed->textsize(ed->labelsize());
   ed->textfont(ed->labelfont());
}

void FltkMultiLineTextResource::sizeRequest (core::Requisition *requisition)
{
   DBG_OBJ_ENTER0 ("resize", 0, "sizeRequest");

   if (style) {
      FltkFont *font = (FltkFont*)style->font;
      fl_font(font->font,font->size);
      // WORKAROUND: A bug with fl_width(uint_t) on non-xft X was present in
      // 1.3.0 (STR #2688).
      requisition->width =
         (int)fl_width ("n") * numCols + 2 * RELIEF_X_THICKNESS;
      requisition->ascent =
         RELIEF_Y_THICKNESS + font->ascent +
         (font->ascent + font->descent) * (numRows - 1);
      requisition->descent =
         font->descent +
         RELIEF_Y_THICKNESS;
   } else {
      requisition->width = 1;
      requisition->ascent = 1;
      requisition->descent = 0;
   }

   DBG_OBJ_MSGF ("resize", 1, "result: %d * (%d + %d)",
                 requisition->width, requisition->ascent, requisition->descent);
   DBG_OBJ_LEAVE ();
}

const char *FltkMultiLineTextResource::getText ()
{
   return ((CustTextEditor*)widget)->value ();
}

void FltkMultiLineTextResource::setText (const char *text)
{
   ((CustTextEditor*)widget)->value (text);
}

bool FltkMultiLineTextResource::isEditable ()
{
   return editable;
}

void FltkMultiLineTextResource::setEditable (bool editable)
{
   this->editable = editable;
}

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

template <class I>
FltkToggleButtonResource<I>::FltkToggleButtonResource (FltkPlatform *platform,
                                                       bool activated):
   FltkSpecificResource <I> (platform)
{
   initActivated = activated;
}


template <class I>
FltkToggleButtonResource<I>::~FltkToggleButtonResource ()
{
}


template <class I>
Fl_Widget *FltkToggleButtonResource<I>::createNewWidget (core::Allocation
                                                              *allocation)
{
   Fl_Button *button = createNewButton (allocation);
   button->value (initActivated);
   return button;
}

template <class I>
void FltkToggleButtonResource<I>::setWidgetStyle (Fl_Widget *widget,
                                                  core::style::Style *style)
{
   FltkResource::setWidgetStyle(widget, style);

   widget->selection_color(FL_BLACK);
}


template <class I>
void FltkToggleButtonResource<I>::sizeRequest (core::Requisition *requisition)
{
   DBG_OBJ_ENTER0 ("resize", 0, "sizeRequest");

   FltkFont *font = (FltkFont *)
      (this->FltkResource::style ? this->FltkResource::style->font : NULL);

   if (font) {
      fl_font(font->font, font->size);
      requisition->width = font->ascent + font->descent + 2*RELIEF_X_THICKNESS;
      requisition->ascent = font->ascent + RELIEF_Y_THICKNESS;
      requisition->descent = font->descent + RELIEF_Y_THICKNESS;
   } else {
      requisition->width = 1;
      requisition->ascent = 1;
      requisition->descent = 0;
   }

   DBG_OBJ_MSGF ("resize", 1, "result: %d * (%d + %d)",
                 requisition->width, requisition->ascent, requisition->descent);
   DBG_OBJ_LEAVE ();
}


template <class I>
bool FltkToggleButtonResource<I>::isActivated ()
{
   return ((Fl_Button*)this->widget)->value ();
}


template <class I>
void FltkToggleButtonResource<I>::setActivated (bool activated)
{
   initActivated = activated;
   ((Fl_Button*)this->widget)->value (initActivated);
}

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

FltkCheckButtonResource::FltkCheckButtonResource (FltkPlatform *platform,
                                                  bool activated):
   FltkToggleButtonResource<dw::core::ui::CheckButtonResource> (platform,
                                                                activated)
{
   init (platform);
}


FltkCheckButtonResource::~FltkCheckButtonResource ()
{
}


Fl_Button *FltkCheckButtonResource::createNewButton (core::Allocation
                                                          *allocation)
{
   Fl_Check_Button *cb =
      new Fl_Check_Button (allocation->x, allocation->y, allocation->width,
                           allocation->ascent + allocation->descent);
   return cb;
}

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

bool FltkRadioButtonResource::Group::FltkGroupIterator::hasNext ()
{
   return it.hasNext ();
}

dw::core::ui::RadioButtonResource
*FltkRadioButtonResource::Group::FltkGroupIterator::getNext ()
{
   return (dw::core::ui::RadioButtonResource*)it.getNext ();
}

void FltkRadioButtonResource::Group::FltkGroupIterator::unref ()
{
   delete this;
}


FltkRadioButtonResource::Group::Group (FltkRadioButtonResource
                                       *radioButtonResource)
{
   list = new lout::container::typed::List <FltkRadioButtonResource> (false);
   connect (radioButtonResource);
}

FltkRadioButtonResource::Group::~Group ()
{
   delete list;
}

void FltkRadioButtonResource::Group::connect (FltkRadioButtonResource
                                              *radioButtonResource)
{
   list->append (radioButtonResource);
}

void FltkRadioButtonResource::Group::unconnect (FltkRadioButtonResource
                                                *radioButtonResource)
{
   list->removeRef (radioButtonResource);
   if (list->isEmpty ())
      delete this;
}


FltkRadioButtonResource::FltkRadioButtonResource (FltkPlatform *platform,
                                                  FltkRadioButtonResource
                                                  *groupedWith,
                                                  bool activated):
   FltkToggleButtonResource<dw::core::ui::RadioButtonResource> (platform,
                                                                activated)
{
   init (platform);

   if (groupedWith) {
      group = groupedWith->group;
      group->connect (this);
   } else
      group = new Group (this);
}


FltkRadioButtonResource::~FltkRadioButtonResource ()
{
   group->unconnect (this);
}

dw::core::ui::RadioButtonResource::GroupIterator
*FltkRadioButtonResource::groupIterator ()
{
   return group->groupIterator ();
}

void FltkRadioButtonResource::widgetCallback (Fl_Widget *widget,
                                              void *data)
{
   if (widget->when () & FL_WHEN_CHANGED)
      ((FltkRadioButtonResource*)data)->buttonClicked ();
}

void FltkRadioButtonResource::buttonClicked ()
{
   for (Iterator <FltkRadioButtonResource> it = group->iterator ();
        it.hasNext (); ) {
      FltkRadioButtonResource *other = it.getNext ();
      other->setActivated (other == this);
   }
}

Fl_Button *FltkRadioButtonResource::createNewButton (core::Allocation
                                                     *allocation)
{
   /*
    * Groups of Fl_Radio_Button must be added to one Fl_Group, which is
    * not possible in this context. For this, we do the grouping ourself,
    * based on FltkRadioButtonResource::Group.
    *
    * What we actually need for this, is a widget, which behaves like a
    * check button, but looks like a radio button. The first depends on the
    * type, the second on the style. Since the type is simpler to change
    * than the style, we create a radio button, and then change the type
    * (instead of creating a check button, and changing the style).
    */

   Fl_Button *button =
      new Fl_Round_Button (allocation->x, allocation->y, allocation->width,
                           allocation->ascent + allocation->descent);
   button->when (FL_WHEN_CHANGED);
   button->callback (widgetCallback, this);
   button->type (FL_TOGGLE_BUTTON);

   return button;
}

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

template <class I> dw::core::Iterator *
FltkSelectionResource<I>::iterator (dw::core::Content::Type mask, bool atEnd)
{
   /** \bug Implementation. */
   return new core::EmptyIterator (this->getEmbed (), mask, atEnd);
}

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

FltkOptionMenuResource::FltkOptionMenuResource (FltkPlatform *platform):
   FltkSelectionResource <dw::core::ui::OptionMenuResource> (platform)
{
   /* Fl_Menu_ does not like multiple menu items with the same label, and
    * insert() treats some characters specially unless escaped, so let's
    * do our own menu handling.
    */
   itemsAllocated = 0x10;
   menu = new Fl_Menu_Item[itemsAllocated];
   memset(menu, 0, itemsAllocated * sizeof(Fl_Menu_Item));
   itemsUsed = 1; // menu[0].text == NULL, which is an end-of-menu marker.

   init (platform);
}

FltkOptionMenuResource::~FltkOptionMenuResource ()
{
   for (int i = 0; i < itemsUsed; i++) {
      if (menu[i].text)
         free((char *) menu[i].text);
   }
   delete[] menu;
}

void FltkOptionMenuResource::setWidgetStyle (Fl_Widget *widget,
                                             core::style::Style *style)
{
   Fl_Choice *ch = (Fl_Choice *)widget;

   FltkResource::setWidgetStyle(widget, style);

   ch->textcolor(widget->labelcolor());
   ch->textfont(ch->labelfont());
   ch->textsize(ch->labelsize());
}

Fl_Widget *FltkOptionMenuResource::createNewWidget (core::Allocation
                                                     *allocation)
{
   Fl_Choice *choice =
      new CustChoice (allocation->x, allocation->y,
                      allocation->width,
                      allocation->ascent + allocation->descent);
   choice->menu(menu);
   return choice;
}

void FltkOptionMenuResource::widgetCallback (Fl_Widget *widget,
                                             void *data)
{
}

int FltkOptionMenuResource::getMaxItemWidth()
{
   int i, max = 0;

   for (i = 0; i < itemsUsed; i++) {
      int width = 0;
      const char *str = menu[i].text;

      if (str) {
         width = fl_width(str);
         if (width > max)
            max = width;
      }
   }
   return max;
}

void FltkOptionMenuResource::sizeRequest (core::Requisition *requisition)
{
   DBG_OBJ_ENTER0 ("resize", 0, "sizeRequest");

   if (style) {
      FltkFont *font = (FltkFont*)style->font;
      fl_font(font->font, font->size);
      int maxItemWidth = getMaxItemWidth ();
      requisition->ascent = font->ascent + RELIEF_Y_THICKNESS;
      requisition->descent = font->descent + RELIEF_Y_THICKNESS;
      requisition->width = maxItemWidth
         + (requisition->ascent + requisition->descent)
         + 2 * RELIEF_X_THICKNESS;
   } else {
      requisition->width = 1;
      requisition->ascent = 1;
      requisition->descent = 0;
   }

   DBG_OBJ_MSGF ("resize", 1, "result: %d * (%d + %d)",
                 requisition->width, requisition->ascent, requisition->descent);
   DBG_OBJ_LEAVE ();
}

void FltkOptionMenuResource::enlargeMenu ()
{
   Fl_Choice *ch = (Fl_Choice *)widget;
   int selected = ch->value();
   Fl_Menu_Item *newMenu;

   itemsAllocated += 0x10;
   newMenu = new Fl_Menu_Item[itemsAllocated];
   memcpy(newMenu, menu, itemsUsed * sizeof(Fl_Menu_Item));
   memset(newMenu + itemsUsed, 0, 0x10 * sizeof(Fl_Menu_Item));
   delete[] menu;
   menu = newMenu;
   ch->menu(menu);
   ch->value(selected);
}

Fl_Menu_Item *FltkOptionMenuResource::newItem()
{
   Fl_Menu_Item *item;

   if (itemsUsed == itemsAllocated)
      enlargeMenu();

   item = menu + itemsUsed - 1;
   itemsUsed++;

   return item;
}

void FltkOptionMenuResource::addItem (const char *str,
                                      bool enabled, bool selected)
{
   Fl_Menu_Item *item = newItem();

   item->text = dStrdup(str);

   if (enabled == false)
      item->flags = FL_MENU_INACTIVE;

   if (selected)
      ((Fl_Choice *)widget)->value(item);

   queueResize (true);
}

void FltkOptionMenuResource::setItem (int index, bool selected)
{
   if (selected)
      ((Fl_Choice *)widget)->value(menu+index);
}

void FltkOptionMenuResource::pushGroup (const char *name, bool enabled)
{
   Fl_Menu_Item *item = newItem();

   item->text = dStrdup(name);

   if (enabled == false)
      item->flags = FL_MENU_INACTIVE;

   item->flags |= FL_SUBMENU;

   queueResize (true);
}

void FltkOptionMenuResource::popGroup ()
{
   /* Item with NULL text field closes the submenu */
   newItem();
   queueResize (true);
}

bool FltkOptionMenuResource::isSelected (int index)
{
   return index == ((Fl_Choice *)widget)->value();
}

int FltkOptionMenuResource::getNumberOfItems()
{
   return ((Fl_Choice*)widget)->size();
}

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

class CustBrowser : public Fl_Browser {
public:
   CustBrowser(int x, int y, int w, int h) : Fl_Browser(x, y, w, h) {};
   int full_width() const;
   int full_height() const {return Fl_Browser::full_height();}
   int avg_height() {return size() ? Fl_Browser_::incr_height() : 0;}
};

/*
 * Fl_Browser_ has a full_width(), but it has a tendency to contain 0, so...
 */
int CustBrowser::full_width() const
{
   int max = 0;
   void *item = item_first();

   while (item) {
      int w = item_width(item);

      if (w > max)
         max = w;

      item = item_next(item);
   }
   return max;
}

FltkListResource::FltkListResource (FltkPlatform *platform,
                                    core::ui::ListResource::SelectionMode
                                    selectionMode, int rowCount):
   FltkSelectionResource <dw::core::ui::ListResource> (platform),
   currDepth(0)
{
   mode = selectionMode;
   showRows = rowCount;
   init (platform);
}

FltkListResource::~FltkListResource ()
{
}


Fl_Widget *FltkListResource::createNewWidget (core::Allocation *allocation)
{
   CustBrowser *b =
      new CustBrowser (allocation->x, allocation->y, allocation->width,
                      allocation->ascent + allocation->descent);

   b->type((mode == SELECTION_MULTIPLE) ? FL_MULTI_BROWSER : FL_HOLD_BROWSER);
   b->callback(widgetCallback, this);
   b->when(FL_WHEN_CHANGED);
   b->column_widths(colWidths);
   b->column_char('\a');   // I just chose a nonprinting character.

   return b;
}

void FltkListResource::setWidgetStyle (Fl_Widget *widget,
                                       core::style::Style *style)
{
   Fl_Browser *b = (Fl_Browser *)widget;

   FltkResource::setWidgetStyle(widget, style);

   b->textfont(widget->labelfont());
   b->textsize(widget->labelsize());
   b->textcolor(widget->labelcolor());

   colWidths[0] = b->textsize();
   colWidths[1] = colWidths[0];
   colWidths[2] = colWidths[0];
   colWidths[3] = 0;
}

void FltkListResource::widgetCallback (Fl_Widget *widget, void *data)
{
   Fl_Browser *b = (Fl_Browser *) widget;

   if (b->selected(b->value())) {
      /* If it shouldn't be selectable, deselect it again. It would be nice to
       * have a less unpleasant way to do this.
       */
      const char *inactive_code;
      if ((inactive_code = strstr(b->text(b->value()), "@N"))) {
         const char *ignore_codes = strstr(b->text(b->value()), "@.");

         if (inactive_code < ignore_codes)
            b->select(b->value(), 0);
      }
   }
}

void *FltkListResource::newItem (const char *str, bool enabled, bool selected)
{
   Fl_Browser *b = (Fl_Browser *) widget;
   int index = b->size() + 1;
   char *label = (char *)malloc(strlen(str) + 1 + currDepth + 4),
        *s = label;

   memset(s, '\a', currDepth);
   s += currDepth;
   if (!enabled) {
      // FL_INACTIVE_COLOR
      *s++ = '@';
      *s++ = 'N';
   }
   // ignore further '@' chars
   *s++ = '@';
   *s++ = '.';

   strcpy(s, str);

   b->add(label);
   free(label);

   if (selected) {
      b->select(index, selected);
      if (b->type() == FL_HOLD_BROWSER) {
         /* Left to its own devices, it sometimes has some suboptimal ideas
          * about how to scroll, and sometimes doesn't seem to show everything
          * where it thinks it is.
          */
         if (index > showRows) {
            /* bottomline() and middleline() don't work because the widget is
             * too tiny at this point for the bbox() call in
             * Fl_Browser::lineposition() to do what one would want.
             */
            b->topline(index - showRows + 1);
         } else {
            b->topline(1);
         }
      }
   }
   queueResize (true);
   return NULL;
}

void FltkListResource::addItem (const char *str, bool enabled, bool selected)
{
   // Fl_Browser_::incr_height() for item height won't do the right thing if
   // the first item doesn't have anything to it.
   if (!str || !*str)
      str = " ";
   newItem(str, enabled, selected);
}

void FltkListResource::setItem (int index, bool selected)
{
   Fl_Browser *b = (Fl_Browser *) widget;

   b->select(index + 1, selected);
}

void FltkListResource::pushGroup (const char *name, bool enabled)
{
   bool en = false;
   bool selected = false;

   // Fl_Browser_::incr_height() for item height won't do the right thing if
   // the first item doesn't have anything to it.
   if (!name || !*name)
      name = " ";

   // TODO: Proper disabling of item groups
   newItem(name, en, selected);

   if (currDepth < 3)
      currDepth++;
}

void FltkListResource::popGroup ()
{
   CustBrowser *b = (CustBrowser *) widget;

   newItem(" ", false, false);
   b->hide(b->size());

   if (currDepth)
      currDepth--;
}

int FltkListResource::getMaxItemWidth()
{
   return ((CustBrowser *) widget)->full_width();
}

void FltkListResource::sizeRequest (core::Requisition *requisition)
{
   DBG_OBJ_ENTER0 ("resize", 0, "sizeRequest");

   if (style) {
      CustBrowser *b = (CustBrowser *) widget;
      int height = b->full_height();
      requisition->width = getMaxItemWidth() + 4;

      if (showRows * b->avg_height() < height) {
         height = showRows * b->avg_height();
         b->has_scrollbar(Fl_Browser_::VERTICAL_ALWAYS);
         requisition->width += Fl::scrollbar_size();
      } else {
         b->has_scrollbar(0);
      }

      requisition->descent = style->font->descent + 2;
      requisition->ascent = height - style->font->descent + 2;
   } else {
      requisition->width = 1;
      requisition->ascent = 1;
      requisition->descent = 0;
   }

   DBG_OBJ_MSGF ("resize", 1, "result: %d * (%d + %d)",
                 requisition->width, requisition->ascent, requisition->descent);
   DBG_OBJ_LEAVE ();
}

int FltkListResource::getNumberOfItems()
{
   return ((Fl_Browser*)widget)->size();
}

bool FltkListResource::isSelected (int index)
{
   Fl_Browser *b = (Fl_Browser *) widget;

   return b->selected(index + 1) ? true : false;
}

} // namespace ui
} // namespace fltk
} // namespace dw

