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

//<file>
//
// Name:        priority.C
//
// Purpose:     implementation of class PriorityChooser
//
// Created:      8 Feb 94   Michael Pichler
//
// Changed:     13 Apr 95   Michael Pichler
//
//
//</file>



#include "priority.h"
#include "wtranslate.h"
#include "glyphutil.h"
#include "msgbox.h"


#include <InterViews/action.h>
#include <InterViews/background.h>
#include <InterViews/canvas.h>
#include <InterViews/cursor.h>
#include <InterViews/color.h>
#include <InterViews/display.h>
#include <InterViews/event.h>
#include <InterViews/font.h>
#include <InterViews/geometry.h>
#include <InterViews/handler.h>
#include <InterViews/hit.h>
#include <InterViews/input.h>
#include <InterViews/layout.h>
#include <InterViews/patch.h>
#include <InterViews/polyglyph.h>
#include <InterViews/session.h>
#include <InterViews/style.h>
#include <InterViews/target.h>
#include <InterViews/window.h>
#include <IV-look/choice.h>
#include <IV-look/kit.h>
#include <hyperg/OS/string.h>

#include <X11/keysym.h>

#include <stdio.h>



class SelInputHandler;

/*** PriorityChooserImpl ***/
// the "real" priority chooser


class PriorityChooserImpl
{
  public:
    PriorityChooserImpl ();
    ~PriorityChooserImpl ();

    int runDialog (
      PolyGlyph* sel,
      PolyGlyph* unsel,
      HgLanguage::Language language,
      int mustselectone,
      const char* wintitle,
      const char* headline,
      const char* selboxtitle,
      const char* unselboxtitle,
      const char* buttonall,
      const char* buttonnone,
      Action* helpaction
    );

    void confirmAction ();
    void cancelAction ();
    void helpAction ();

  private:
    void createWindow (
      HgLanguage::Language language,
      const char* wintitle,
      const char* headline,
      const char* selboxtitle,
      const char* unselboxtitle,
      const char* buttonall,
      const char* bottonnone
    );

    void checkEnable ();
    void done (int);  // quit event loop
    void redrawPatch ();
    void selectAll ();
    void deselectAll ();

    int done_;
    int mustselone_;
    HgLanguage::Language language_;
    TelltaleState* okbuttonstate_;
    ManagedWindow* win_;
    InputHandler* inputhandler_;
    PolyGlyph* callersel_;
    PolyGlyph* callerunsel_;
    PolyGlyph* vboxsel_;
    PolyGlyph* vboxunsel_;
    BoxGlyph* selglyph_;
    BoxGlyph* unselglyph_;
    Patch* patch_;
    Action* helpaction_;

  friend class SelInputHandler;
};  // PriorityChooserImpl



/*** implementation class PriorityChooser ***/


// run
// create an instance of PriorityChooser and run the dialog

int PriorityChooser::run (
  PolyGlyph* sel,
  PolyGlyph* unsel,
  HgLanguage::Language language,
  int mustselectone,
  const char* wintitle,
  const char* headline,
  const char* selboxtitle,
  const char* unselboxtitle,
  const char* buttonall,
  const char* buttonnone,
  Action* helpaction
)
{
  PriorityChooserImpl* pci = new PriorityChooserImpl ();

  int retval = pci->runDialog (sel, unsel, language, mustselectone, wintitle, headline,
    selboxtitle, unselboxtitle, buttonall, buttonnone, helpaction);

  delete pci;

  return retval;
}



/*** BoxGlyph ***/
// MonoGlyph with
// - changeable requirements
// - pick returns a hit of component -1 if glyph is hit, but no component

class BoxGlyph: public MonoGlyph
{
  public:
    BoxGlyph (PolyGlyph* body);

    void requirements (                 // set requirements
      const Requirement& rx,            //   in x
      const Requirement& ry             //   and y
    );                                  //   for subsequent calls of request

    void request (Requisition& req) const;

    void pick (Canvas*, const Allocation&, int depth, Hit&);

  private:
    Requirement rx_;
    Requirement ry_;
};


BoxGlyph::BoxGlyph (PolyGlyph* body)
: MonoGlyph (body)
{
}


void BoxGlyph::requirements (const Requirement& rx, const Requirement& ry)
{
  rx_ = rx;
  ry_ = ry;
}


void BoxGlyph::request (Requisition& req) const
{
  req.require (Dimension_X, rx_);
  req.require (Dimension_Y, ry_);
}


void BoxGlyph::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 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, -1);  // report a hit for component -1
  }
}



/*** SelChoiceItem ***/
// simple choice item for selections
// when activated, the glyph is drawn in a bright inset frame,
// when deactivated, it is drawn without any decoration;
// for purposes of lists, function original returns the "original" item
// (does not use IV's Observable-Observer protocol)

class SelChoiceItem: public MonoGlyph
{
  public:
    SelChoiceItem (WidgetKit& kit, Glyph* normal, Glyph* original);
    virtual ~SelChoiceItem ();

    void activate ()    { body (active_); }
    void deactivate ()  { body (normal_); }

    Glyph* original ()  { return original_; }

  private:
    Glyph* normal_;
    Glyph* active_;
    Glyph* original_;
};


SelChoiceItem::SelChoiceItem (WidgetKit& kit, Glyph* normal, Glyph* original)
: MonoGlyph (normal)
{
  normal_ = normal;
  active_ = kit.bright_inset_frame (normal);
  original_ = original;
  Resource::ref (normal_);
  Resource::ref (active_);
  Resource::ref (original_);
}


SelChoiceItem::~SelChoiceItem ()
{
  Resource::unref (normal_);
  Resource::unref (active_);
  Resource::unref (original_);
}



/*** SelInputHandler ***/
// inputhandler that manages the selections

class SelInputHandler: public InputHandler
{
  public:
    SelInputHandler (Glyph*, Style*, PriorityChooserImpl*);

    void press (const Event&);
    void release (const Event&);
    void double_click (const Event&);

  private:
    PriorityChooserImpl* prchooser_;
    GlyphIndex srcbox_;
    GlyphIndex srcitem_;
    Glyph* srcelement_;
    Cursor* origcursor_;
};


SelInputHandler::SelInputHandler (Glyph* g, Style* s, PriorityChooserImpl* pc)
: InputHandler (g, s)
{
  prchooser_ = pc;
  // setting of selbox and selitem irrelevant as release always follows drag
  origcursor_ = nil;
}


// press
// store selection
// assert: all items of vboxsel_ and vboxunsel_ are SelChoiceItems

void SelInputHandler::press (const Event& e)
{
  Hit hit (&e);
  repick (0, hit);  // pick (canvas (), allocation (), 0, hit);

  srcitem_ = hit.index (1);
  srcbox_ = hit.index (0);

  if (srcitem_ >= 0)  // highlight item to be dragged
  { PolyGlyph* srcglyph = srcbox_ ? prchooser_->vboxunsel_ : prchooser_->vboxsel_;
    srcelement_ = srcglyph->component (srcitem_);
    ((SelChoiceItem*) srcelement_)->activate ();
    // show hand cursor
    origcursor_ = prchooser_->win_->cursor ();
    prchooser_->win_->cursor (WidgetKit::instance ()->hand_cursor ());
    prchooser_->redrawPatch ();
  }
  else
    srcelement_ = nil;
}


// release
// perform movement of an item
// assert: at level 0 selected items are in the box with index 0,
//         and unselected items are in the box with index 1

void SelInputHandler::release (const Event& e)
{
  Hit hit (&e);
  repick (0, hit);  // pick (canvas (), allocation (), 0, hit);

  if (srcelement_)  // unhighlight dragged item
  { ((SelChoiceItem*) srcelement_)->deactivate ();
    prchooser_->win_->cursor (origcursor_);
  }
  else
    return;

  if (hit.any ())
  {
    GlyphIndex dstbox = hit.index (0);
    GlyphIndex dstitem = hit.index (1);

    if (srcitem_ >= 0 &&  // something to move
        !(srcbox_ == dstbox && srcitem_ == dstitem)
       )
    { // move srcelement_ to position dstitem of dstbox

      PolyGlyph* srcglyph = srcbox_ ? prchooser_->vboxunsel_ : prchooser_->vboxsel_;
      PolyGlyph* dstglyph = dstbox ? prchooser_->vboxunsel_ : prchooser_->vboxsel_;

      if (dstitem < 0)
        dstglyph->append (srcelement_);  // srcglyph->component (srcitem_)
      else
        dstglyph->insert (dstitem, srcelement_);  // srcglyph->component (srcitem_)

      if (srcbox_ == dstbox && dstitem < srcitem_ && dstitem >= 0)  // original item slided down
        srcglyph->remove (srcitem_ + 1);
      else
        srcglyph->remove (srcitem_);

      prchooser_->checkEnable ();

      srcitem_ = -1;  // do not handle double click when mouse slided over items
    }
  }  

  prchooser_->redrawPatch ();

} // release



// double_click
// move selected item to end of other list

void SelInputHandler::double_click (const Event&)
{
  // currently release is responsible for unhighlighting etc.
  // ("singleClick" did not work well)

  if (srcitem_ >= 0)  // move item to other box
  {
    PolyGlyph* srcglyph;
    PolyGlyph* dstglyph;
    if (srcbox_)  // deselect
    { srcglyph = prchooser_->vboxunsel_;
      dstglyph = prchooser_->vboxsel_;
    }
    else  // select
    { srcglyph = prchooser_->vboxsel_;
      dstglyph = prchooser_->vboxunsel_;
    }

    dstglyph->append (srcelement_);  // srcglyph->component (srcitem_)
    srcglyph->remove (srcitem_);

    prchooser_->checkEnable ();
  }

} // double_click



/*** SelKeyEventHandler ***/
// manage key strokes
// SelKeyEventHandler recieves key strokes of whole window,
// not only atop the two boxes

class SelKeyEventHandler: public InputHandler  // MonoGlyph
{
  public:
    SelKeyEventHandler (Glyph* body, Style* style, PriorityChooserImpl*);

//    void pick (Canvas*, const Allocation&, int depth, Hit&);
    void keystroke (const Event&);

  private:
    PriorityChooserImpl* prchooser_;
};


SelKeyEventHandler::SelKeyEventHandler (Glyph* body, Style* style, PriorityChooserImpl* pc)
: InputHandler (body, style)
{
  prchooser_ = pc;
}



void SelKeyEventHandler::keystroke (const Event& e)
{
    unsigned long keysym = e.keysym ();
    unsigned char key = (unsigned char) (keysym & 0xff);
  
    if (key == '\r' || keysym == XK_KP_Enter)
      prchooser_->confirmAction ();
    else if (key == '\x1b' || keysym == XK_Cancel)
      prchooser_->cancelAction ();
    else if (keysym == XK_F1 || keysym == XK_Help)
      prchooser_->helpAction ();
}



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

class SelDeleteHandler: public Handler
{
  public:
    SelDeleteHandler (PriorityChooserImpl* pc)
    { pc_ = pc; }

    boolean event (Event&);

  private:
    PriorityChooserImpl* pc_;
};


boolean SelDeleteHandler::event (Event&)
{
  pc_->cancelAction ();
  return 1;
}



/*** implementation of PriorityChooserImpl */


declareActionCallback (PriorityChooserImpl)
implementActionCallback (PriorityChooserImpl)


PriorityChooserImpl::PriorityChooserImpl ()
{
  done_ = 0;
  win_ = 0;
  vboxsel_ = vboxunsel_ = 0;
  selglyph_ = unselglyph_ = 0;
  helpaction_ = nil;
}


PriorityChooserImpl::~PriorityChooserImpl ()
{
  delete win_;  // delete whole window
  Resource::unref(helpaction_);
}


// update_requisition
// checks the requisition of Glyph comp
// updates maxx (maximal x requirement) and sumy (sum of y requirements)
// (see also compute_tile_request in InterViews/tile.c)

static void update_requisition (const Glyph* comp, float& maxx, float& sumy)
{
  Requisition requisition;
  comp->request (requisition);
  const Requirement& xreq = requisition.x_requirement ();
  const Requirement& yreq = requisition.y_requirement ();
  float xnat = xreq.natural ();
  if (xnat > maxx)
    maxx = xnat;
  sumy += yreq.natural ();
}


// runDialog

int PriorityChooserImpl::runDialog (
  PolyGlyph* sel,
  PolyGlyph* unsel,
  HgLanguage::Language language,
  int mustselectone,
  const char* wintitle,
  const char* headline,
  const char* selboxtitle,
  const char* unselboxtitle,
  const char* buttonall,
  const char* buttonnone,
  Action* helpaction
)
{
  GlyphIndex i, n;
  WidgetKit& kit = *WidgetKit::instance ();
  const LayoutKit& layout = *LayoutKit::instance ();

  kit.begin_style ("PriorityChooser");

  // keep pointers for storing dialog result
  callersel_ = sel;
  callerunsel_ = unsel;
  mustselone_ = mustselectone;
  okbuttonstate_ = 0;  // set in createWindow
  language_ = language;
  helpaction_ = helpaction;
  Resource::ref(helpaction);

  // create new dialog window
  createWindow (language, wintitle, headline, selboxtitle, unselboxtitle, buttonall, buttonnone);

  // append the list items in the two boxes
  // and calculate maximal size of the box

  float maxx = 0, sumy = 0;
  const float lmargin = 5.0;
  const float rmargin = 5.0;
  const float vglue = 15.0;

  // "selected" box
  if (sel)
  { n = sel->count ();
    for (i = 0;  i < n;  i++)
    {
      Glyph* comp = sel->component (i);
      update_requisition (comp, maxx, sumy);
      Glyph* component = new Target (  // because margin is not pickable
        layout.hmargin (comp, lmargin, 0.0, 0.0, rmargin, fil, 0.0),
        TargetTransparentHit
      );
      vboxsel_->append (new SelChoiceItem (kit, component, comp));
    }
  }

// IV style:
// vboxsel_->append (new ChoiceItem (
//   new TelltaleState (TelltaleState::is_enabled),
//   component,
//   kit.bright_inset_frame (component)
// ));


  // "unselected" box
  if (unsel)
  { n = unsel->count ();
    for (i = 0;  i < n;  i++)
    {
      Glyph* comp = unsel->component (i);
      update_requisition (comp, maxx, sumy);
      Glyph* component = new Target (
        layout.hmargin (comp, lmargin, 0.0, 0.0, rmargin, fil, 0.0),
        TargetTransparentHit
      );
      vboxunsel_->append (new SelChoiceItem (kit, component, comp));
    }
  }


  Requirement rx (maxx + lmargin + rmargin, 0, 0, 0.0);
  Requirement ry (sumy + vglue, 0, 0, 1.0);

  selglyph_->requirements (rx, ry);
  unselglyph_->requirements (rx, ry);  

  checkEnable ();  // disable button when no selected items

  // map dialog window
  win_->map ();

  kit.end_style ();  // "PriorityChooser"


  // event loop for modality

  Session* s = Session::instance ();
  Event e;
  while (!done_)
  {
    s->read (e);
    // mpichler, 19950313: pushbuttons are grabbing, so when still handling events of
    // grabbing handlers, another instance of the dialog could be created by accident
    if (/*e.grabber () ||*/ inputhandler_->inside (e))
      e.handle ();
    else if (e.type () == Event::key)
      inputhandler_->keystroke (e);
    if (s->done ())  // this happens when the application window is deleted
      done_ = PriorityChooser::Cancel;
  } // read events

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

  return done_;

} // runDialog



// checkEnable
// disable OK button when "mustselectone" and no item is selected

void PriorityChooserImpl::checkEnable ()
{
  if (mustselone_)
    okbuttonstate_->set (TelltaleState::is_enabled, (int) vboxsel_->count ());
}


// done
// quit event loop, set return value

void PriorityChooserImpl::done (int d)
{
  done_ = d;
}


void PriorityChooserImpl::redrawPatch ()
{
  patch_->redraw ();  // no resize of window
}


void PriorityChooserImpl::confirmAction ()
{
  // copy lists back
  // assert: all items of vboxsel_ and vboxunsel_ are SelChoiceItems
  // and the original (input) items are stored as original

  if (!okbuttonstate_->test (TelltaleState::is_enabled))
    return;  // accessed disabled function via key press

  clearPolyGlyph (callersel_);
  clearPolyGlyph (callerunsel_);

  GlyphIndex i, n;
  n = vboxsel_->count ();
  for (i = 0;  i < n;  i++)
  {
    SelChoiceItem* comp = (SelChoiceItem*) vboxsel_->component (i);
    callersel_->append (comp->original ());
  }
  n = vboxunsel_->count ();
  for (i = 0;  i < n;  i++)
  {
    SelChoiceItem* comp = (SelChoiceItem*) vboxunsel_->component (i);
    callerunsel_->append (comp->original ());
  }

  done (PriorityChooser::Ok);
}


void PriorityChooserImpl::cancelAction ()
{
  done (PriorityChooser::Cancel);
}



// helpAction
// currently not implemented (for screen dumps no disabled button)

void PriorityChooserImpl::helpAction ()
{
  if (helpaction_)
    helpaction_->execute();
  else
    MessageBox::sorryNotImplemented (language_, win_);
}



// selectAll
// move all items to selected polyglyph

void PriorityChooserImpl::selectAll ()
{
  GlyphIndex nunsel = vboxunsel_->count ();

  GlyphIndex n = nunsel;
  while (n--)
  { vboxsel_->append (vboxunsel_->component (0));
    vboxunsel_->remove (0);
  }

  if (nunsel)
    redrawPatch ();

  checkEnable ();
}



// deselectAll
// move all items to unselected polyglyph

void PriorityChooserImpl::deselectAll ()
{
  GlyphIndex nsel = vboxsel_->count ();

  GlyphIndex n = nsel;
  while (n--)
  { vboxunsel_->append (vboxsel_->component (0));
    vboxsel_->remove (0);
  }

  if (nsel)
    redrawPatch ();

  checkEnable ();
}



void PriorityChooserImpl::createWindow (  // create dialog window
  HgLanguage::Language language,
  const char* wintitle,
  const char* headline,
  const char* selboxtitle,
  const char* unselboxtitle,
  const char* buttonall,
  const char* buttonnone
)
{
  WidgetKit& kit = *WidgetKit::instance ();
  const LayoutKit& layout = *LayoutKit::instance ();

  Glyph* hglue = layout.hglue (15.0, fil, 0);
  Glyph* vglue = layout.vglue (15.0, 0, 0);

  Button* okbutton = 
    kit.default_button (// "Ok"
      WTranslate::str (WTranslate::OK, language),
      new ActionCallback(PriorityChooserImpl)(this, &PriorityChooserImpl::confirmAction)
  );
  okbuttonstate_ = okbutton->state ();

  Glyph* buttons = layout.hbox (
    hglue,
    okbutton,
    hglue,
    kit.push_button (// "Cancel"
      WTranslate::str (WTranslate::CANCEL, language),
      new ActionCallback(PriorityChooserImpl)(this, &PriorityChooserImpl::cancelAction)),
    hglue,
    kit.push_button (// "Help"
      WTranslate::str (WTranslate::HELP, language),
      new ActionCallback(PriorityChooserImpl)(this, &PriorityChooserImpl::helpAction)),
    hglue
  );

  Glyph* buttonbox = layout.hbox (
    layout.hmargin (
      kit.push_button (buttonall ? buttonall : WTranslate::str (WTranslate::SELECTALL, language),
        new ActionCallback(PriorityChooserImpl)(this, &PriorityChooserImpl::selectAll)),
//      20, fil, 0, 10, fil, 0
      15, fil, 0, 10, fil, 0        
    ),
    layout.hmargin (
      kit.push_button (buttonnone ? buttonnone : WTranslate::str (WTranslate::DESELECTALL, language),
        new ActionCallback(PriorityChooserImpl)(this, &PriorityChooserImpl::deselectAll)),
//      20, fil, 0, 10, fil, 0
      15, fil, 0, 10, fil, 0        
    )
  );

  Glyph* headlineglyph = headline ?
    layout.hmargin (multiLineLabel (headline), 20, fil, 0, 20, fil, 0) : nil;

  Glyph* boxtitles = layout.hbox (
    layout.hmargin (
      kit.label (selboxtitle ? selboxtitle : WTranslate::str (WTranslate::SELECTED, language)),
//      20, fil, 0, 10, fil, 0
      15, fil, 0, 15, fil, 0        
    ),
    layout.hmargin (
      kit.label (unselboxtitle ? unselboxtitle : WTranslate::str (WTranslate::UNSELECTED, language)),
//      10, fil, 0, 20, fil, 0
      15, fil, 0, 15, fil, 0        
    )
  );

  vboxsel_ = layout.vbox ();
  vboxunsel_ = layout.vbox ();
  selglyph_ = new BoxGlyph (vboxsel_);
  unselglyph_ = new BoxGlyph (vboxunsel_);

  // ensure that the two selection boxes are the first components of the hbox

  patch_ = new Patch (
    layout.hbox (
      layout.hmargin (
        kit.inset_frame (selglyph_),
//        20, fil, 0, 10, fil, 0
      15, fil, 0, 15, fil, 0        
      ),
      layout.hmargin (
        kit.inset_frame (unselglyph_),
//        10, fil, 0, 20, fil, 0
      15, fil, 0, 15, fil, 0        
      )
    )
  );

  SelInputHandler* selinputhandler = new SelInputHandler (patch_, kit.style (), this);
  Glyph* inputboxes = selinputhandler;
 
  Glyph* winbody = layout.vbox (
    layout.vmargin (headlineglyph, 20.0),
    inputboxes,  // the two boxes
    layout.vglue (10.0, 0, 0),
    boxtitles,   // selected, deselected
    layout.vglue (7.0, 0, 0),
    buttonbox,   // [de]select all
    vglue,
    kit.outset_frame (layout.hglue ()),
    layout.vmargin (buttons, 15.0)  // OK, Cancel
  );

  inputhandler_ = new SelKeyEventHandler (
    new Background (
      winbody,
      kit.background ()
    ),
    kit.style (),
    this
  );

  win_ = new ApplicationWindow (inputhandler_);
//  win_ = new TransientWindow (inputhandler_);  // mgais, 19950113

  if (wintitle)
  { Style* style = new Style (kit.style ());
    style->attribute ("name", wintitle);
    win_->style (style);
  }

  // delete handler
  win_->wm_delete (new SelDeleteHandler (this));

} // createWindow
