//<copyright>
//
// Copyright (c) 1994
// Institute for Information Processing and Computer Supported New Media (IICM),
// Graz University of Technology, Austria.
//
//</copyright>
/*
 parts of code (for AdjValue, ColourDialog, ColorViewer, OColor)
 got from various sources by:
 o shawn@virginia.edu - Shawn M. E. Carnell, Graduate Student
 o IV examples idemo, bvalue
 o Dave Sternlicht with lots of help from Mark Linton
*/


//<file>
//
// Name:        coldlg.C
//
// Purpose:     implementation of color dialog
//
// Created:      6 Apr 93   Michael Pichler
//
// Changed:     31 May 94   Michael Pichler
//
// Changed:     23 May 95   Bernhard Marschall
//
//
//</file>



#include "coldlg.h"

#include "ocolor.h"
#include "colorviewer.h"
#include "colselect.h"
#include "wtranslate.h"
#include "glyphutil.h"
#include "fbrowser.h"
#include "fieldb.h"

#include <InterViews/background.h>
#include <InterViews/color.h>
#include <InterViews/event.h>
#include <InterViews/font.h>
#include <InterViews/handler.h>
#include <InterViews/layout.h>
#include <InterViews/session.h>
#include <InterViews/style.h>
#include <InterViews/target.h>
#include <InterViews/telltale.h>
#include <InterViews/window.h>
#include <IV-look/choice.h>
#include <IV-look/kit.h>

#include <hyperg/OS/list.h>

#include <X11/keysym.h>

#include <iostream.h>



declareActionCallback (ColourDialog)
implementActionCallback (ColourDialog)

/* small class to store one colour */

class RGBColor {
public:
  RGBColor(float r, float g, float b)
  { r_ = r; g_ = g; b_ = b; modified_ = false; }
  void color(float r, float g, float b)
  { r_ = r; g_ = g; b_ = b; modified_ = false; }
  float r_, g_, b_;
  boolean modified_;
};

declarePtrList(ColorList, RGBColor)
implementPtrList(ColorList, RGBColor)

/* ColFileBrowser */

class ColourFileBrowser : public WFileBrowser {
public:
  ColourFileBrowser(WidgetKit*, Action*, Action*, Action*);
  virtual ~ColourFileBrowser() {};

  virtual void focus_out() {};   // WFileBrowser unhighlights selection in focus_out;
                                 // I do NOT want this in the ColourDialog
};

ColourFileBrowser::ColourFileBrowser(
  WidgetKit* kit, Action* accept, Action* cancel, Action* select
)
: WFileBrowser(kit, accept, cancel, select)
{}


/* OrigColorViewer */

class OrigColorViewer : public Patch {
public:
  OrigColorViewer(float r, float g, float b);
  virtual ~OrigColorViewer();
  void color(float r, float g, float b);
  virtual void request(Requisition&) const;
  virtual void allocate(Canvas*, const Allocation&, Extension&);
  virtual void draw(Canvas*, const Allocation&) const;

private:
  const Color* color_;
};

OrigColorViewer::OrigColorViewer(float r, float g, float b)
: Patch(nil)
{
  color_ = new Color(r, g, b);
  Resource::ref(color_);
}

OrigColorViewer::~OrigColorViewer()
{
  Resource::unref(color_);
}

void OrigColorViewer::color(float r, float g, float b)
{
  Resource::unref(color_);
  color_ = new Color(r, g, b);
  Resource::ref(color_);
  redraw();
}

void OrigColorViewer::request(Requisition& req) const
{
  req.require_x(Requirement(93.0, fil, 93.0, 0.0));
  // don't bother about y-requirement
}

void OrigColorViewer::allocate(Canvas* c, const Allocation& a, Extension& ext)
{
  ext.set(c, a);
  Patch::allocate(c, a, ext);
}

void OrigColorViewer::draw(Canvas* c, const Allocation& a) const
{
  if (color_)
    c->fill_rect(a.left(), a.bottom(), a.right(), a.top(), color_);
}


/* ColDlgInputHandler */

class ColDlgInputHandler: public InputHandler
{
  public:
    ColDlgInputHandler (Glyph* body, Style* style, ColourDialog* cdlgi);

    void keystroke (const Event&);

  private:
    ColourDialog* cdlgi_;
};


ColDlgInputHandler::ColDlgInputHandler (Glyph* body, Style* style, ColourDialog* cdlgi)
: InputHandler (body, style)
{
  cdlgi_ = cdlgi;
}


void ColDlgInputHandler::keystroke (const Event& e)
{
  unsigned long keysym = e.keysym ();
  unsigned char key = (unsigned char) (keysym & 0xff);

//cerr << "ColDlgInputHandler got key " << keysym << endl;

  if (key == '\r' || keysym == XK_KP_Enter)
    cdlgi_->confirmAction ();
  else if (key == '\x1b' || keysym == XK_Cancel)
    cdlgi_->cancelAction ();

  // currently no help available

} // keystroke



/*** ColDeleteHandler ***/
// deleting window is equivalent to "cancel"

class ColDeleteHandler: public Handler
{
  public:
    ColDeleteHandler (ColourDialog* dlg)
    { dlg_ = dlg; }

    boolean event (Event&);

  private:
    ColourDialog* dlg_;
};


boolean ColDeleteHandler::event (Event&)
{
  dlg_->cancelAction ();
  return 1;
}



/*
int ColorDialog::run (float& r, float& g, float& b,
                      const char* wintitle, const char* title
)
{
  // run a modal color dialog

  ColourDialog* dialog = new ColorDialogImpl (HgLanguage::Default, r, g, b, wintitle, title);

  int change = dialog->runDialog ();

  if (change)
    dialog->getRGB (r, g, b);

  delete dialog;
  return change;

} // run
*/


/* implementation of ColourDialog */


ColourDialog::ColourDialog (
  HgLanguage::Language lang,
  float r, float g, float b,
  const char* wintitle,
  const char* title,
  ColourDlgAction* action
)
{
  init(action);

  num_ = 1;
  current_ = 0;

  origcol_ = new ColorList(1);
  currentcol_ = new ColorList(1);
  RGBColor* col = new RGBColor(r, g, b);
  origcol_->append(col);
  col = new RGBColor(r, g, b);
  currentcol_->append(col);

  ocolor_ = nil;

  createWindow (lang, wintitle, title, true);
  ocolor_->setrgb (r, g, b);
}


ColourDialog::ColourDialog(
  HgLanguage::Language lang,
  const char* wintitle,
  ColourDlgAction* action
)
{
  init(action);

  num_ = 0;
  current_ = -1;

  origcol_ = new ColorList();
  currentcol_ = new ColorList();

  createWindow (lang, wintitle, nil, false);
}

// common part of both constructors
void ColourDialog::init(ColourDlgAction* action)
{
  action_ = action;
  Resource::ref (action);

  ocolor_ = new OColor ();

  didapply_ = 0;
  done_ = 0;
  win_ = nil;
  mainhbox_ = nil;
  mainvbox_ = nil;
  colsel_ = nil;
  fbrowser_ = nil;
}

ColourDialog::~ColourDialog ()
{
  Resource::unref (action_);
  Resource::unref (mainhbox_);
  Resource::unref (colsel_);

  while (origcol_->count()) {
    RGBColor* col = origcol_->item(0);
    origcol_->remove(0);
    delete col;
  }
  delete origcol_;

  while (currentcol_->count()) {
    RGBColor* col = currentcol_->item(0);
    currentcol_->remove(0);
    delete col;
  }
  delete currentcol_;

  delete ocolor_;
  delete win_;
}


void ColourDialog::setRGB (float r, float g, float b, int index)
{
  RGBColor* col = origcol_->item(index);
  col->color(r, g, b);
  col = currentcol_->item(index);
  col->color(r, g, b);

  if (index == current_) {
    ocolor_->setrgb (r, g, b);
    origColViewer_->color(r, g, b);
  }
}


void ColourDialog::setColor (const Color* c, int index)
{
  float r, g, b;
  Display* dis = Session::instance ()->default_display ();
  c->intensities (dis, r, g, b);

  setRGB (r, g, b, index);
}


void ColourDialog::map ()
{
  win_->map ();
}


void ColourDialog::unmap ()
{
  win_->unmap ();
  win_->unbind ();  // IV "feature" (bug)
}


int ColourDialog::ismapped ()
{
  return win_->is_mapped ();
}


void ColourDialog::confirmAction ()
{
  // read ocolor_
  acceptSelection();

  // save current colours
  int i;  // gcc keeps loop-vars local
  for (i = 0; i < num_; i++) {
    RGBColor* orig = origcol_->item(i);
    RGBColor* cur = currentcol_->item(i);
    orig->r_ = cur->r_;
    orig->g_ = cur->g_;
    orig->b_ = cur->b_;
  }
  RGBColor* orig = origcol_->item(current_);
  origColViewer_->color(orig->r_, orig->g_, orig->b_);

  if (action_)
  {
    action_->apply (this);

    // reset modified flags
    for (i = 0; i < num_; i++)
      currentcol_->item(i)->modified_ = false;

    action_->close (this);
  }
  // be careful, close() action is allowed to delete this class
  else  // pathologic
    delete this;

} // confirmAction


void ColourDialog::applyAction ()
{
  // to read ocolor_
  acceptSelection();

  if (!action_)
    return;

  action_->apply (this);
  didapply_ = 1;

  // reset modified flags
  for (int i = 0; i < num_; i++)
    currentcol_->item(i)->modified_ = false;
} // applyAction


void ColourDialog::cancelAction ()
{
  if (action_)
  {
    if (didapply_)  // restore original colour after applying others
    {
      // restore original colours
      int i;  // gcc keeps loop-vars local
      for (i = 0; i < num_; i++) {
        RGBColor* cur = currentcol_->item(i);
        RGBColor* orig = origcol_->item(i);
        if (cur->r_ != orig->r_ || cur->g_ != orig->g_ || cur->b_ != orig->b_) {
          cur->r_ = orig->r_;
          cur->g_ = orig->g_;
          cur->b_ = orig->b_;
          cur->modified_ = true;
        }
      }
      RGBColor* cur = currentcol_->item(current_);
      ocolor_->setrgb(cur->r_, cur->g_, cur->b_);

      action_->apply (this);

      // reset modified flags
      for (i = 0; i < num_; i++)
        currentcol_->item(i)->modified_ = false;
    }
    action_->close (this);
  }
  else  // pathologic
    delete this;

} // cancelAction


void ColourDialog::acceptSelection()
{
  // save current ocolor_
  RGBColor* col = currentcol_->item(current_);
  float r = col->r_;
  float g = col->g_;
  float b = col->b_;
  col->r_ = ocolor_->getval(OColor::R);
  col->g_ = ocolor_->getval(OColor::G);
  col->b_ = ocolor_->getval(OColor::B);
  if (r != col->r_ || g != col->g_ || b != col->b_)
    col->modified_ = true;

  // set ocolor_ to new color
  if (fbrowser_)
    current_ = (int) fbrowser_->selected();
  col = currentcol_->item(current_);
  ocolor_->setrgb(col->r_, col->g_, col->b_);
  col = origcol_->item(current_);
  origColViewer_->color(col->r_, col->g_, col->b_);
}


void ColourDialog::getRGB (float& r, float& g, float& b, int index) const
{
  RGBColor* col = currentcol_->item(index);
  if (index == current_) {
    col->r_ = ocolor_->getval(OColor::R);
    col->g_ = ocolor_->getval(OColor::G);
    col->b_ = ocolor_->getval(OColor::B);
  }

  r = col->r_;
  g = col->g_;
  b = col->b_;
}


const char* ColourDialog::getHexRGB (int index) const
{
  float r, g, b;
  getRGB(r, g, b, index);
  return OColor::rgbtohex(r, g, b);
}


/*
void ColourDialog::toggleColorTable ()
{
  csmapped_ = !csmapped_;

  if (csmapped_)
    mainhbox_->replace (1, colsel_);
  else
    mainhbox_->replace (1, LayoutKit::instance ()->vspace (0));

  mainvbox_->change (0);  // reallocate
  win_->resize ();
  coldlginput_->redraw ();
}
*/


void ColourDialog::appendColourEntry(const char* name, float r, float g, float b)
{
  WidgetKit& kit = *WidgetKit::instance();
  const LayoutKit& layout = *LayoutKit::instance();

  // append entry to file browser
  Glyph* label = layout.h_margin(kit.label(name), 5.0);
  TelltaleState* t = new TelltaleState(TelltaleState::is_enabled);
  fbrowser_->append_selectable(t);
  fbrowser_->append(new ChoiceItem(t, label, kit.bright_inset_frame(label)));
  num_++;

  // append inital colour to list of original and current colours
  RGBColor* col = new RGBColor(r, g, b);
  origcol_->append(col);
  col = new RGBColor(r, g, b);
  currentcol_->append(col);

  // select first if none is selected
  if (current_ < 0) {
    current_ = 0;
    fbrowser_->select(0);
    acceptSelection();
    ocolor_->setrgb(r, g, b);
  }
}

void ColourDialog::appendColourEntry(const char* name, const Color* color)
{
  ColorIntensity r, g, b;
  color->intensities(Session::instance()->default_display(), r, g, b);
  appendColourEntry(name, r, g, b);
}

boolean ColourDialog::modified(int i) const
{
  return currentcol_->item(i)->modified_;
}


void ColourDialog::createWindow (
  HgLanguage::Language lang,
  const char* wintitle,
  const char* title,
  boolean single
)
{
  WidgetKit& kit = *WidgetKit::instance ();
  const LayoutKit& layout = *LayoutKit::instance ();

  kit.begin_style ("ColourChooser", "ColorChooser");
  kit.style ()->alias ("Dialog");  // mgais, 19940616

  // Create an observable color with 6 adjustables in range 0..1
  ocolor_ = new OColor ();

  ColorViewer* cv = new ColorViewer (ocolor_);
  origColViewer_ = new OrigColorViewer(
    ocolor_->getval(OColor::R), 
    ocolor_->getval(OColor::G),
    ocolor_->getval(OColor::B)
  );

  const Font* font = kit.font ();
  FontBoundingBox bbox;
  font->font_bbox (bbox);
  float charwidth = bbox.width ();  // maximum character width

  kit.begin_style ("FieldEditor", "EditLabel");
  kit.font ()->char_bbox ('8', bbox);
  float digitwidth = 3 * bbox.width () + 12.0;  // width of three digits
  float hexlabelwidth = 6 * bbox.width () + 12.0;  // aabbcc
  kit.end_style ();

  // RGB and HLS labels
  Glyph* label_R = layout.vcenter (layout.hfixed (kit.label ("R"), charwidth), 0.5);
  Glyph* label_G = layout.vcenter (layout.hfixed (kit.label ("G"), charwidth), 0.5);
  Glyph* label_B = layout.vcenter (layout.hfixed (kit.label ("B"), charwidth), 0.5);
  Glyph* label_H = layout.vcenter (layout.hfixed (kit.label ("H"), charwidth), 0.5);
  Glyph* label_L = layout.vcenter (layout.hfixed (kit.label ("L"), charwidth), 0.5);
  Glyph* label_S = layout.vcenter (layout.hfixed (kit.label ("S"), charwidth), 0.5);
  Glyph* label_HEX = layout.vcenter (kit.label ("RGB (Hex)"), 0.5);
//  Glyph* label_HEX = layout.hfixed (kit.label ("h"), charwidth);
//  Glyph* comment_HEX = kit.label ("(Hex)");

  // RGB and HLS field editors
  Glyph* field_R = layout.vcenter (layout.hfixed (ocolor_->getfed (OColor::R), digitwidth), 0.5);
  Glyph* field_G = layout.vcenter (layout.hfixed (ocolor_->getfed (OColor::G), digitwidth), 0.5);
  Glyph* field_B = layout.vcenter (layout.hfixed (ocolor_->getfed (OColor::B), digitwidth), 0.5);
  Glyph* field_H = layout.vcenter (layout.hfixed (ocolor_->getfed (OColor::H), digitwidth), 0.5);
  Glyph* field_L = layout.vcenter (layout.hfixed (ocolor_->getfed (OColor::L), digitwidth), 0.5);
  Glyph* field_S = layout.vcenter (layout.hfixed (ocolor_->getfed (OColor::S), digitwidth), 0.5);
  Glyph* field_HEX = layout.vcenter (layout.hfixed (ocolor_->getfed (OColor::HEXRGB), hexlabelwidth), 0.5);

  // RGB and HLS scroll bars
  Glyph* scroll_R = layout.vcenter (kit.hscroll_bar (ocolor_->getadj (OColor::R)), 0.5);
  Glyph* scroll_G = layout.vcenter (kit.hscroll_bar (ocolor_->getadj (OColor::G)), 0.5);
  Glyph* scroll_B = layout.vcenter (kit.hscroll_bar (ocolor_->getadj (OColor::B)), 0.5);
  Glyph* scroll_H = layout.vcenter (kit.hscroll_bar (ocolor_->getadj (OColor::H)), 0.5);
  Glyph* scroll_L = layout.vcenter (kit.hscroll_bar (ocolor_->getadj (OColor::L)), 0.5);
  Glyph* scroll_S = layout.vcenter (kit.hscroll_bar (ocolor_->getadj (OColor::S)), 0.5);

  // colour selector
  ColorSelector* cs = new ColorSelector (ocolor_, kit.style ());

  colsel_ = layout.vcenter (
    layout.lmargin (
      kit.outset_frame (
        layout.hfixed (cs, 160.0)
      ),
      20.0
    ),
    1.0
  );
  Resource::ref (colsel_);
  // when changing the bodies of monoglyphs or the components of polyglyphs
  // they get ref'ed and unref'ed; when this is done dynamically care must
  // be taken that the components are not deleted on such a change

  // FileBrowser
  if (!single) {
    Action* act = new ActionCallback(ColourDialog) (this, &ColourDialog::acceptSelection);
    fbrowser_ = new ColourFileBrowser(
      &kit, act, nil, act
    );
  }

  Glyph* buttons = layout.hbox (
    layout.hglue (),
    kit.default_button (
      WTranslate::str (WTranslate::OK, lang),
      new ActionCallback(ColourDialog)(this, &ColourDialog::confirmAction)
    ),
    layout.hglue (),
    kit.push_button (
      WTranslate::str (WTranslate::APPLY, lang),
      new ActionCallback(ColourDialog)(this, &ColourDialog::applyAction)
    ),
    layout.hglue (),
    kit.push_button (
      WTranslate::str (WTranslate::CANCEL, lang),
      new ActionCallback(ColourDialog)(this, &ColourDialog::cancelAction)
    ),
    layout.hglue ()
  );

  Glyph* fbrowser_cv;
  if (single)
    fbrowser_cv = kit.outset_frame (cv);
  else
    fbrowser_cv = layout.vbox(
      layout.hbox(
        kit.inset_frame(fbrowser_),
        kit.vscroll_bar(fbrowser_->adjustable())
      ),
      layout.vspace(10.0),
      layout.fixed(
        layout.hbox(
          layout.vbox(
            layout.vglue(0),
            kit.label(WTranslate::str(WTranslate::OLDCOL, lang)),
            layout.vglue(0)
          ),
          layout.hspace(3.0),
          kit.outset_frame(origColViewer_),
          layout.hspace(10.0),
          layout.vbox(
            layout.vglue(0),
            kit.label(WTranslate::str(WTranslate::NEWCOL, lang)),
            layout.vglue(0)
          ),
          layout.hspace(3.0),
          kit.outset_frame(cv)
        ),
        260.0, 32.0
      )
    );


  // only up to ten components in constructor
  Glyph* thevbox = layout.vbox (
    // color viewer
    layout.vcenter (layout.fixed(fbrowser_cv, 260.0, 125.0), 1.0),
    layout.vspace (15.0),
    // scrollbars and field editors
    layout.hbox (label_H, layout.hspace (5.0), field_H, layout.hspace (8.0), scroll_H),
    layout.vspace (5.0),
    layout.hbox (label_L, layout.hspace (5.0), field_L, layout.hspace (8.0), scroll_L),
    layout.vspace (5.0),
    layout.hbox (label_S, layout.hspace (5.0), field_S, layout.hspace (8.0), scroll_S),
    layout.vspace (20.0)
  );
  thevbox->append (layout.hbox (label_R, layout.hspace (5.0), field_R, layout.hspace (8.0), scroll_R));
  thevbox->append (layout.vspace (5.0));
  thevbox->append (layout.hbox (label_G, layout.hspace (5.0), field_G, layout.hspace (8.0), scroll_G));
  thevbox->append (layout.vspace (5.0));
  thevbox->append (layout.hbox (label_B, layout.hspace (5.0), field_B, layout.hspace (8.0), scroll_B));
  thevbox->append (layout.vspace (10.0));
  // force RGB HEX field editor to use only natural size
  thevbox->append (WLayoutKit::phbox (WLayoutKit::AlignOrdinary,
    layout.hglue (), 1.0,
    label_HEX, 0.0,
    layout.hspace (8.0), 0.0,
    field_HEX, 0.0
//    layout.hspace (10.0), 0.0,
//    comment_HEX, 0.0,
//    layout.hglue (), 1.0
  ));
/*
  thevbox->append (layout.vspace (20.0));
  thevbox->append (
    // check box
    layout.hmargin (
      kit.check_box (
        "full colour",
        new ActionCallback(ColourDialog) (this, &ColourDialog::toggleFullColor)
      ),
      0, fil, 0, 0, fil, 0
    )
  );
*/

  mainhbox_ = layout.hbox (
    colsel_,
    layout.hmargin (
      thevbox,
      20.0
    )
  );
  Resource::ref (mainhbox_);
//  csmapped_ = 0;

  Glyph* title_label;
  if (title && *title)
    title_label = layout.margin(
        kit.label(title),
        20.0, 0.0, 0.0, 15.0
    );
  else
    title_label = layout.vspace(10);

  mainvbox_ = layout.vbox (
    // title (label)
    title_label,
    layout.vspace (15.0),
    mainhbox_,
    layout.vmargin (buttons, 20.0)
  );

  coldlginput_ = new ColDlgInputHandler (
    new Target (
      new Background (
        mainvbox_,
        kit.background ()
      ),
      TargetTransparentHit
    ),
    kit.style (), this);

  // focus management for field editors
  coldlginput_->append_input_handler (ocolor_->getfed (OColor::H));
  coldlginput_->append_input_handler (ocolor_->getfed (OColor::L));
  coldlginput_->append_input_handler (ocolor_->getfed (OColor::S));
  coldlginput_->append_input_handler (ocolor_->getfed (OColor::R));
  coldlginput_->append_input_handler (ocolor_->getfed (OColor::G));
  coldlginput_->append_input_handler (ocolor_->getfed (OColor::B));
  coldlginput_->append_input_handler (ocolor_->getfed (OColor::HEXRGB));
  coldlginput_->append_input_handler (cs);  // colour table
  if (!single)
    coldlginput_->append_input_handler (fbrowser_);
  // no initial focus (keypress for buttons)

  ManagedWindow* coldlgwin = new ApplicationWindow (coldlginput_);

  // delete handler
  coldlgwin->wm_delete (new ColDeleteHandler (this));

  Style* winstyle = new Style (kit.style ());
  winstyle->attribute ("name", wintitle);  // same "iconName"
  coldlgwin->style (winstyle);

  kit.end_style ();  // "ColourChooser"

  win_ = coldlgwin;

} // createWindow


/*
int ColourDialog::runDialog ()
{
  ocolor_->setrgb (r_, g_, b_);
  win_->map ();
  // event loop for modality

  Session* s = Session::instance ();
  Event e;
  while (!done_)
  {
    s->read (e);
    if (e.grabber () || coldlginput_->inside (e))
      e.handle ();
    else if (e.type () == Event::key)
      coldlginput_->keystroke (e);
    if (s->done ())  // this happens when the application window is deleted
      done_ = ColorDialog::Cancel;
  } // read events

  // unmap window
  win_->unmap ();
  win_->unbind ();  // IV "feature" (bug)

  return done_;
}
*/
