//<copyright>
// 
// Copyright (c) 1994
// Institute for Information Processing and Computer Supported New Media (IICM),
// Graz University of Technology, Austria.
// 
//</copyright>

//<file>
//
// Name:        glyphutil.C
//
// Purpose:     implementation of glyph utilities
//
// Created:     24 Mar 94   Michael Pichler
//
// Changed:     12 Oct 94   Michael Pichler
//
//
//</file>

char HgWidgetsWhat[] = "@(#)[HG-LIB] HgWidgets\t1.0 [Harmony GUI Widgets] [Michael Pichler, Bernhard Marschall]";


#include "glyphutil.h"

#include <InterViews/align.h>
#include <InterViews/box.h>
#include <InterViews/color.h>
#include <InterViews/hit.h>
#include <InterViews/label.h>
#include <InterViews/polyglyph.h>
#include <InterViews/style.h>
#include <InterViews/superpose.h>
#include <InterViews/tile.h>
#include <IV-look/button.h>
#include <IV-look/choice.h>
#include <IV-look/kit.h>

#include <OS/math.h>
#include <OS/string.h>

#include <string.h>
#include <iostream.h>



// multiLineLabel
// split a string of several lines
// returns a hbox of centered labels

Glyph* multiLineLabel (const char* msg_in)
{
  WidgetKit& kit = *WidgetKit::instance ();
  // default: use font and foreground of kit.instance
  return multiLineLabel (msg_in, kit.font (), kit.foreground ());
}


Glyph* multiLineLabel (const char* msg_in, const Font* font, const Color* color)
{
  LayoutKit& layout = *LayoutKit::instance ();

  char* line = new char [strlen (msg_in) + 1];  // space for one line

  Glyph* label = layout.vbox ();
    
  const char* src = msg_in;
  char* dst = line;
  for (;;)
  {
    if (*src == '\n' || !*src)
    {
      *dst = '\0';  // complete string
      label->append (layout.hbox (
        layout.hglue (),
        new Label (line, font, color),
        layout.hglue ()
      ));

      if (!*src++)  // end of string
        break;
      dst = line;  // next line
    }
    else
      *dst++ = *src++;
  } // for src

  delete line;
  return label;

} // multiLineLabel


// clearPolyGlyph
// removes all components of a PolyGlyph

void clearPolyGlyph (PolyGlyph* pg)
{
  GlyphIndex n = pg->count ();
  while (n)
    pg->remove (--n);
}


// strcpy_new (little helper)
// create new char array, copy string src onto it, return address
// null pointer creates an emty string

char* strcpy_new (const char* src)
{
  if (src)
    return strcpy (new char [strlen (src) + 1], src);
  else
    return new char ('\0');  // create ""
}


// lookupColor
// lookup for a colour under different names
// tries names in order, returns nil if no colour can be found

const Color* lookupColor (
  const Style* style,
  Display* dis,
  const char* name0, const char* name1,
  const char* name2, const char* name3
)
{
  String colorstr;
  const Color* color;

  if (name0 && style->find_attribute (name0, colorstr) && (color = Color::lookup (dis, colorstr)) != 0)
    return color;
  if (name1 && style->find_attribute (name1, colorstr) && (color = Color::lookup (dis, colorstr)) != 0)
    return color;
  if (name2 && style->find_attribute (name2, colorstr) && (color = Color::lookup (dis, colorstr)) != 0)
    return color;
  if (name3 && style->find_attribute (name3, colorstr) && (color = Color::lookup (dis, colorstr)) != 0)
    return color;

  return nil;  // no colour found
}


// glyphWithScrollbars
// adds scrollbars to an adjustable glyph (horicontal and vertical)

Glyph* glyphWithScrollbars (Glyph* g, Adjustable* xadj, Adjustable* yadj)
{
  WidgetKit& kit = *WidgetKit::instance ();
  const LayoutKit& layout = *LayoutKit::instance ();

  Glyph* hscrollbar = kit.hscroll_bar (xadj);
  Glyph* vscrollbar = kit.vscroll_bar (yadj);

  Requisition req;
  vscrollbar->request (req);
  const Requirement& xreq = req.x_requirement ();
  float scrollbarwidth = xreq.natural ();

  Glyph* buttonfiller = layout.hfixed (
    kit.outset_frame (
      layout.flexible (nil)
    ),
    scrollbarwidth
  );

  return layout.vbox (
    layout.hbox (
      layout.vcenter (g, 1.0),  // jschipf, 19950316
      vscrollbar
    ),
    layout.hbox (
      hscrollbar,
      buttonfiller
    )
  );
} // glyphWithScrollbars


/*** TransparentTarget ***/

void TransparentTarget::pick (Canvas* c, const Allocation& a, int depth, Hit& hit)
{
  body ()->pick (c, a, depth, hit);

  if (!hit.any ())  // if no component was hit
  { // check if glyph allocation was hit
    Coord x = hit.left ();
    Coord y = hit.bottom ();
    if (x >= a.left () && x < a.right () && y >= a.bottom () && y < a.top ())
      hit.target (depth, this, 0);
  }
}


/*** ShapeOfMaster ***/

ShapeOfMaster::ShapeOfMaster (Glyph* master, Glyph* body)
: MonoGlyph (body)
{
  Resource::ref (master);
  master_ = master;
}


ShapeOfMaster::~ShapeOfMaster ()
{
  Resource::unref (master_);
}


void ShapeOfMaster::master (Glyph* glyph)
{
  Resource::ref (glyph);
  Resource::unref (master_);
  master_ = glyph;
}


void ShapeOfMaster::request (Requisition& r) const
{
  // some glyphs behave strange without request,
  // so always call request of body (if set)
  MonoGlyph::request (r);
  // override requisition by master's
  if (master_)
    master_->request (r);
  // see also Placement::request for margins of layout kit
}


/*** SimplePaletteButtonLook ***/

class SimplePaletteButtonLook: public ChoiceItem
{
  public:
    SimplePaletteButtonLook (Glyph*, TelltaleState*, WidgetKit&);

#ifdef __GNUC__
    void update (Observable*);  // bug of gnuCC: does not check virtual function
#endif
};


SimplePaletteButtonLook::SimplePaletteButtonLook (Glyph* g, TelltaleState* state, WidgetKit& kit)
: ChoiceItem (state)
{
  // what's the problem: palette_buttons of SGI motif look are not
  // really nice, so use a pressed push button in state is_chosen and
  // otherwise an ordinary push button; state is_active is set for
  // menu items and better matches button state visible; of course, it
  // should still be possible to disable the buttons
/*
  look (TelltaleState::is_chosen, 0, kit.inset_frame (g));
  look (0, TelltaleState::is_chosen, kit.outset_frame (g));
*/
  // basic telltale states: enabled, visible, active, chosen
  Glyph* disabled = kit.push_button_look (g, new TelltaleState (0));
  Glyph* enabled  = kit.push_button_look (g, new TelltaleState (TelltaleState::is_enabled));
  Glyph* hilit    = kit.push_button_look (g, new TelltaleState (TelltaleState::is_enabled_visible));
  Glyph* chosen   = kit.push_button_look (g, new TelltaleState (TelltaleState::is_enabled_visible_active));
  // must always set visible state (hilit) for chosen because of motif look

  look (0, TelltaleState::is_enabled, disabled);
  look (TelltaleState::is_enabled, TelltaleState::is_visible_active_chosen, enabled);
  look (TelltaleState::is_enabled_visible, TelltaleState::is_chosen, hilit);
  look (TelltaleState::is_enabled_active, TelltaleState::is_chosen, hilit);
  look (TelltaleState::is_enabled_chosen, 0, chosen);
}

#ifdef __GNUC__
void SimplePaletteButtonLook::update (Observable* gnuCCbug)
{
  ChoiceItem::update (gnuCCbug);
}
#endif


/*** WWidgetKit ***/

Button* WWidgetKit::push_button (Glyph* g, Action* a, TelltaleState* t)
{
  if (!t)
    t = new TelltaleState (TelltaleState::is_enabled);

  WidgetKit& kit = *WidgetKit::instance ();
  kit.begin_style ("PushButton", "Button");
  Button* button = new Button (kit.push_button_look (g, t), kit.style (), t, a);

  kit.end_style ();  // "PushButton"
  return button;
}


Button* WWidgetKit::push_button (const char* str, Action* a, TelltaleState* t)
{
  if (!t)
    t = new TelltaleState (TelltaleState::is_enabled);

  WidgetKit& kit = *WidgetKit::instance ();
  kit.begin_style ("PushButton", "Button");  // label must created after begin_style
  Button* button = new Button (kit.push_button_look (kit.label (str), t), kit.style (), t, a);

  kit.end_style ();  // "PushButton"
  return button;
}


Button* WWidgetKit::palette_button (Glyph* g, Action* a, TelltaleState* t)
{
  if (!t)
    t = new TelltaleState (TelltaleState::is_enabled | TelltaleState::is_toggle);

  WidgetKit& kit = *WidgetKit::instance ();
  kit.begin_style ("PaletteButton", "Button");
  return makePaletteButton (kit, g, a, t);
}


Button* WWidgetKit::palette_button (const char* str, Action* a, TelltaleState* t)
{
  if (!t)
    t = new TelltaleState (TelltaleState::is_enabled | TelltaleState::is_toggle);

  WidgetKit& kit = *WidgetKit::instance ();
  kit.begin_style ("PaletteButton", "Button");  // label must created after begin_style
  return makePaletteButton (kit, kit.label (str), a, t);
}


Button* WWidgetKit::makePaletteButton (WidgetKit& kit, Glyph* g, Action* a, TelltaleState* t)
{
  // assert: kit.begin_style () was called
  Button* button;
  if (kit.style ()->value_is_on ("showLamp"))
    button = new Button (kit.palette_button_look (g, t), kit.style (), t, a);
  else
  {
    int enabled = t->test (TelltaleState::is_enabled);
    button = new Button (new SimplePaletteButtonLook (g, t, kit), kit.style (), t, a);
    // ChoiceItem::init always sets is_enabled to true, correct this here if necessary
    if (!enabled)
      t->set (TelltaleState::is_enabled, false);
  }

  kit.end_style ();  // "PaletteButton"
  return button;
}


void WWidgetKit::enable (Button* button)
{
  enable (button->state ());
}


Button* WWidgetKit::disable (Button* button)
{
  disable (button->state ());
  return button;  // convenient for disabling on construction
}



/*** FlexibleAlign ***/

class FlexibleAlign: public Align
{
  public:
    FlexibleAlign (DimensionName);

    // no change of request

    void allocate (
      const Allocation& given, GlyphIndex count, const Requisition*, Allocation* result
    );

  private:
    DimensionName dimens_;
};


FlexibleAlign::FlexibleAlign (DimensionName d)
: Align (d)
{
  dimens_ = d;  // Align::dimension_ is private
}


void FlexibleAlign::allocate (
  const Allocation& given,
  GlyphIndex count, const Requisition*, Allocation* result
)
{
  const Allotment& g = given.allotment (dimens_);

  for (int index = 0;  index < count;  ++index)
    result [index].allot (dimens_, g);
}


/*** ProportionalTile ***/

class ProportionalTile: public Tile
{
  public:
    ProportionalTile (
      DimensionName dimension,
      unsigned numcomponents,  // no. of components (or upper bound when prop is NULL)
      const float* prop        // array of "proportionality constants", copied by class
    );

    ProportionalTile (        // shorthand for up to ten components
      DimensionName dimension,
      float p0, float p1, float p2, float p3, float p4, float p5, float p6, float p7, float p8, float p9
    );

    ~ProportionalTile ();

    void setProp (unsigned num, float prop)  // set proportionality factor
    { // currently unused, because ProportionalTile is not visible to widget user
      if (num < maxcomp_)  prop_ [num] = prop;
    }
    float getProp (unsigned num)  // get proportionality factor
    {
      return (num < maxcomp_) ? prop_ [num] : 0;
    }

    // no chage of Tile::request

    void allocate (
      const Allocation& given, GlyphIndex count, const Requisition*, Allocation* result
    );

  private:
    DimensionName dimension_;
    unsigned maxcomp_;
    float* prop_;
};


ProportionalTile::ProportionalTile (DimensionName d, unsigned numcomponents, const float* prop)
: Tile (d)
{
  dimension_ = d;  // Tile::dimension_ is private

  if (prop && numcomponents)
  {
    maxcomp_ = numcomponents;
    prop_ = new float [numcomponents];
    for (int i = 0;  i < numcomponents;  i++)
      prop_ [i] = prop [i];
  }
  else  // reserve space
  {
    if (numcomponents < 5)
      numcomponents = 5;

    maxcomp_ = numcomponents;
    prop_ = new float [numcomponents];
    for (int i = 0;  i < numcomponents;  i++)
      prop_ [i] = 0;
  }
}


ProportionalTile::ProportionalTile (
  DimensionName d,
  float p0, float p1, float p2, float p3, float p4,
  float p5, float p6, float p7, float p8, float p9
): Tile (d)
{
  dimension_ = d;  // Tile::dimension_ is private
  maxcomp_ = 10;
  prop_ = new float [10];

  float* p = prop_;
  *p++ = p0;
  *p++ = p1;
  *p++ = p2;
  *p++ = p3;
  *p++ = p4;
  *p++ = p5;
  *p++ = p6;
  *p++ = p7;
  *p++ = p8;
  *p++ = p9;
}


ProportionalTile::~ProportionalTile ()
{
  delete[] prop_;
}


void ProportionalTile::allocate (
  const Allocation& given,
  GlyphIndex count, const Requisition* request, Allocation* result
)
{
  const Allotment& g = given.allotment (dimension_);

  // compute how much of allocation is distributed fixed: all compo-
  // nents with 0.0 proportional constant get their natural requirement;
  // the other alignments are distributed according to proportional constants

  float fixedspace = 0.0;
  float propsum = 0.0;

  int index;  // gcc keeps loop-vars local
  for (index = 0;  index < count;  index++)
  {
    const Requirement& r = request [index].requirement (dimension_);
    if (r.defined ())
    {
      float prop = getProp (index);
      if (prop > 0)
        propsum += prop;
      else
        fixedspace += r.natural ();
    }
  } // for

  Coord span = g.span () - fixedspace;  // remaining space
  if (span < 0)
    span = 0;
  Coord p = g.origin ();  // component origin

// cerr << "sum of proportionality constants: " << propsum << endl;
// cerr << "space to be shared by non-fixed components: " << span << endl;

  for (index = 0;  index < count;  index++)
  {
    const Requirement& r = request [index].requirement (dimension_);
    Allotment& a = result [index].allotment (dimension_);
    if (r.defined ())
    {
      float prop = getProp (index);

      Coord cspan;  // component span
      if (prop > 0)
        cspan = span * prop / propsum;
      else
        cspan = r.natural ();

      a.span (cspan);
      a.origin (p + r.alignment () * cspan);
      a.alignment (r.alignment ());
      p += cspan;
    }
    else  // no requirement
    { a.span (0);
      a.origin (p);
      a.alignment (0);
    }
  } // for

} // ProportionalTile::allocate


/*** WLayoutKit ***/

PolyGlyph* WLayoutKit::fhbox (AlignStrategy ya, GlyphIndex size)
{
  Layout* yl;
  if (ya == AlignFlexible)
    yl = new FlexibleAlign (Dimension_Y);
  else
    yl = new Align (Dimension_Y);

  return new Box (
    new Superpose (new Tile (Dimension_X), yl),
    size
  );
} // fhbox


PolyGlyph* WLayoutKit::fhbox (
  AlignStrategy ya,
  Glyph* g1, Glyph* g2, Glyph* g3, Glyph* g4, Glyph* g5,
  Glyph* g6, Glyph* g7, Glyph* g8, Glyph* g9, Glyph* g10
)
{
  Layout* yl;
  if (ya == AlignFlexible)
    yl = new FlexibleAlign (Dimension_Y);
  else
    yl = new Align (Dimension_Y);

  return new Box (
    new Superpose (new Tile (Dimension_X), yl),
    g1, g2, g3, g4, g5, g6, g7, g8, g9, g10
  );
} // fhbox


PolyGlyph* WLayoutKit::phbox (
  AlignStrategy ya,
  Glyph* g1, float p1, Glyph* g2, float p2, Glyph* g3, float p3, Glyph* g4, float p4, Glyph* g5, float p5,
  Glyph* g6, float p6, Glyph* g7, float p7, Glyph* g8, float p8, Glyph* g9, float p9, Glyph* g10, float p10
)
{
  Layout* yl;
  if (ya == AlignFlexible)
    yl = new FlexibleAlign (Dimension_Y);
  else
    yl = new Align (Dimension_Y);

  ProportionalTile* ptile = new ProportionalTile (Dimension_X, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10);

  return new Box (
    new Superpose (ptile, yl),
    g1, g2, g3, g4, g5, g6, g7, g8, g9, g10
  );
} // phbox


PolyGlyph* WLayoutKit::phbox (
  AlignStrategy ya,
  unsigned numcomponents,
  const float* prop
)
{
  Layout* yl;
  if (ya == AlignFlexible)
    yl = new FlexibleAlign (Dimension_Y);
  else
    yl = new Align (Dimension_Y);

  ProportionalTile* ptile = new ProportionalTile (Dimension_X, numcomponents, prop);

  return new Box (
    new Superpose (ptile, yl),
    numcomponents
  );
} // phbox
