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

//<file>
//
// Name:        msgbox.C
//
// Purpose:     implementation of class MessageBox
//
// Created:     19 Jan 94   Michael Pichler
//
// $Id: msgbox.C,v 1.9 1997/01/31 14:16:50 bmarsch Exp $
//
//</file>
//
// $Log: msgbox.C,v $
// Revision 1.9  1997/01/31 14:16:50  bmarsch
// New variant with glyph instead of string
//
// Revision 1.8  1997/01/17 13:40:18  bmarsch
// Instrumentation
//
// Revision 1.7  1996/08/16 08:06:28  bmarsch
// Changed order of labels Help and UserBack
//
// Revision 1.6  1996/06/27 14:35:36  bmarsch
// Call help action on pressing help button
//
// Revision 1.5  1996/03/12 13:42:13  bmarsch
// Bugfix: button release events are handled even if pointer
// is outside the message box
//
// Revision 1.4  1996/03/04 15:17:20  bmarsch
// Removed underlined label for Cancel and Help
//
// Revision 1.3  1996/02/14 14:40:47  bmarsch
// Added keyboard shortcuts for all buttons
//
// Revision 1.2  1996/01/25 08:36:07  bmarsch
// Added Retry and Restore
//


#include <iostream.h>

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

#include <IV-look/kit.h>

#include <InterViews/action.h>
#include <InterViews/background.h>
#include <InterViews/event.h>
#include <InterViews/handler.h>
#include <InterViews/input.h>
#include <InterViews/layout.h>
#include <InterViews/session.h>
#include <InterViews/style.h>
#include <InterViews/window.h>
#include <InterViews/hit.h>

#include <IV-X11/xevent.h>
#include <IV-X11/xwindow.h>

#include <X11/keysym.h>

#include <string.h>

#ifdef INSTRUMENTED
#include <InterViews/logging.h>
#endif


// static members

WTranslate::StrSym MessageBox::label_[MessageBox::NumDefined] =
{
  WTranslate::OK,
  WTranslate::OK, WTranslate::YES, WTranslate::NO,
  WTranslate::RETRY, WTranslate::RESTORE,
  WTranslate::CANCEL, WTranslate::HELP/*dummy!*/, WTranslate::HELP
};

char MessageBox::hotKey_[MessageBox::NumDefined] =
{ 0, 'O', 'Y', 'N', 'R', 's', 0, 0, 0};

/* class MsgBoxDelete: delete handler for messagebox */
class MsgBoxDelete: public Handler {
public:
  virtual boolean event (Event& e);  // called when attemting to delete window
};

boolean MsgBoxDelete::event (Event&)
{
  return 0;  // ignore window delete
}



/* class MsgBoxAction: Action for message box buttons */
class MsgBoxAction: public Action {
public:
  MsgBoxAction (int& button,  // where to store which button was pressed
                int value);     // the number of the button

  virtual void execute ();

private:
  int& button_;
  int value_;
};


declareActionCallback(MsgBoxAction)
implementActionCallback(MsgBoxAction)


MsgBoxAction::MsgBoxAction (int& button, int value)
: button_ (button)
{
  value_ = value;
}


void MsgBoxAction::execute ()
{
  if (value_ == MessageBox::Help)  // call a help without closing the dialog box
    MessageBox::sorryNotImplemented (HgLanguage::Default, 0, "Help");
  else                             // report the pressed button
    button_ = value_;
}



/* class MsgBoxInput: InputHandler for message boxes */

class MsgBoxInput: public InputHandler {
public:
  MsgBoxInput (Glyph*, Style*,
               int buttons,       // which buttons are valid
               int& button,       // where to store which key was pressed
               int defbutton,     // the default button (return)
               char front,        // char for front button
               char back);        // char for back button

  virtual void keystroke (const Event&);

private:
  int buttons_;
  int& button_;
  int defbutton_;
  char front_;
  char back_;
};


MsgBoxInput::MsgBoxInput(Glyph* g, Style* s,
                         int buttons, int& button, int defbutton,
                         char front, char back)
: InputHandler(g, s),
  button_(button)
{
  buttons_ = buttons;
  defbutton_ = defbutton;
  front_ = front;
  back_ = back;
}

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

  // RETURN, ENTER: default button
  if (defbutton_) {
    if (key == '\r' || keysym == XK_KP_Enter)
      button_ = defbutton_;
  }

  // user front
  if (buttons_ & MessageBox::UserFront) {
    if (key == front_)
      button_ = MessageBox::UserFront;
  }
  // Ok
  if (buttons_ & MessageBox::Ok) {
    if (keysym == XK_o)
      button_ = MessageBox::Ok;
  }
  // Yes
  if (buttons_ & MessageBox::Yes) {
    if (keysym == XK_y)
      button_ = MessageBox::Yes;
  }
  // No
  if (buttons_ & MessageBox::No) {
    if (keysym == XK_n)
      button_ = MessageBox::No;
  }
  // Retry
  if (buttons_ & MessageBox::Retry) {
    if (keysym == XK_r)
      button_ = MessageBox::Retry;
  }
  // Restore
  if (buttons_ & MessageBox::Restore) {
    if (keysym == XK_s)
      button_ = MessageBox::Restore;
  }
  // cancel
  if (buttons_ & MessageBox::Cancel) {
    if (key == '\x1b' || keysym == XK_Cancel || keysym == XK_c)
      button_ = MessageBox::Cancel;
  }
  // F1, HELP: help (if present)
  if (buttons_ & MessageBox::Help) {
    if (keysym == XK_F1 || keysym == XK_Help || keysym == XK_h)
      MsgBoxAction(button_, MessageBox::Help).execute();
  }
  // user back
  if (buttons_ & MessageBox::UserBack) {
    if (key == back_)
      button_ = MessageBox::UserBack;
  }

} // keystroke



/* implementation of class MessageBox */

Action* MessageBox::helpAction_ = nil;

void MessageBox::sorryNotImplemented (HgLanguage::Language language,
                                      Window* parentwin, const char* title)
{
  if (helpAction_)
    helpAction_->execute();
  else
    message (language, parentwin, "sorry, not implemented", title, Ok);
}



int MessageBox::message (HgLanguage::Language language, Window* parentwin,
                         const char* msg, const char* title,
                         int buttons, int defbutton,
                         const char* userfront, const char* userback,
                         char hotFront, char hotBack,
                         Action* helpAction)
{
#ifdef INSTRUMENTED
  // write message to logfile
  Instrumentation::instance()->write_prefix(log_error_message);
  Instrumentation::instance()->write_to_log(msg);
#endif

  return message(language, parentwin, multiLineLabel(msg), title,
                 buttons, defbutton, userfront, userback,
                 hotFront, hotBack, helpAction);
// #ifdef INSTRUMENTED
//   // write message to logfile
//   Instrumentation::instance()->write_prefix(log_error_message);
//   Instrumentation::instance()->write_to_log(msg);
// #endif

//   Resource::ref(helpAction);
//   helpAction_ = helpAction;
  
//   WidgetKit& kit = *WidgetKit::instance ();
//   const LayoutKit& layout = *LayoutKit::instance ();

//   // array for button handler
//   Handler* bhandler[NumDefined];

//   // push new style "MessageBox"
//   kit.begin_style ("MessageBox", "Dialog");
//   Style* style = kit.style ();

//   // set window title
//   if (title && *title) {
//     style->attribute ("name", title);
//     style->attribute ("iconName", title);
//   } // otherwise same title as parent window

//   int vspace = 10, hspace = 20;  // might use X-attribute

//   PolyGlyph* buttonbox = layout.hbox ();

//   // return value: ID of pressed button
//   int retval = 0;

//   // display at least one button
//   if (!buttons)
//     buttons = Ok;

//   // invalid default button
//   if (!(defbutton & buttons))
//     defbutton = 0;

//   buttonbox->append (layout.hglue (hspace));

//   for (int i = 0;  i < NumDefined;  i++) {
//     bhandler[i] = nil;
//     int button_i = 1 << i;
//     if (buttons & button_i) {
//       // bmarsch 19950809: UserDefined button
//       const char* str;
//       char key = 0;
//       if (button_i == UserFront)
//         str = userfront, key = hotFront;
//       else if (button_i == UserBack)
//         str = userback, key = hotBack;
//       else
//         str = WTranslate::str(label_[i], language), key = hotKey_[i];
//       if (!str) continue;
//       Action* action = new MsgBoxAction (retval, button_i);
  
//       if (button_i & defbutton) {
//         // default button
//         Button* b = kit.default_button (str, action);
//         bhandler[i] = b->handler();   // save handler
//         buttonbox->append (b);
//         defbutton = button_i;  // prevent more than one default button
//       }
//       else {
//         // begin pushbutton style
//         kit.begin_style("PushButton", "Button");
//         Button* b = kit.push_button(UnderlinedLabel::hotLabel(kit, str, key), action);
//         bhandler[i] = b->handler();
//         buttonbox->append(b);
//         // end pushbutton style
//         kit.end_style();
//       }

//       buttonbox->append (layout.hglue (hspace));
//     }
//   } // for i

//   Glyph* label = multiLineLabel (msg);
//   Glyph* vspc = layout.vglue (vspace, 0.5, 0);
//   // must allow stretching a little bit to compensate for rounding errors
//   // of nasty float coordinates (see also Tile::allocate)

//   Glyph* body = layout.vbox (
//     kit.outset_frame (layout.vbox (
//       vspc,
//       layout.hbox (
//         layout.hglue (hspace),
//         label,
//         layout.hglue (hspace)
//       ),
//       vspc
//     )),
//     kit.outset_frame (layout.vbox (
//       vspc,
//       buttonbox,
//       vspc
//     ))
//   );

//   // keyboard handler
//   MsgBoxInput* ihandler = new MsgBoxInput(body, style,
//                                           buttons, retval, defbutton,
//                                           hotFront, hotBack);

//   // window
//   TransientWindow* win = new TransientWindow(ihandler);
//   win->style(style);

//   // set delete handler
//   MsgBoxDelete* delhandler = new MsgBoxDelete;
//   win->wm_delete(delhandler);

//   if (parentwin) {
//     // center atop primary window
//     win->transient_for(parentwin);
//     win->place(parentwin->left () + 0.5 * parentwin->width (),
//                parentwin->bottom () + 0.5 * parentwin->height ());
//     win->align(0.5, 0.5);
//   }

//   win->map();

//   // event loop
//   Session* s = Session::instance();
//   Event e;
//   for (;;) {
//     s->read(e);
//     // mpichler, 19950311; bmarsch, 19960312: pushbuttons are
//     // grabbing; handle only events for only if the grabber is a
//     // pushbutton of this MessageBox (otherwise other unwanted
//     // MessageBoxes might be created)
//     if (ihandler->inside(e) || grabbing(e.grabber(), bhandler)) {
//       e.handle();
//     }
//     else if (e.type() == Event::key) {
//       ihandler->keystroke(e);
//     }
//     if (retval)      // a button or key was pressed
//       break;
//     if (s->done()) { // this happens when the application window is deleted
//       retval = Cancel;
//       break;
//     }
//   } // read events

//   // close dialog window
//   win->unmap();
//   win->unbind();
//   delete win;

//   // pop style "MessgeBox"
//   kit.end_style();

//   Resource::unref(helpAction_);

//   return retval;
}

int MessageBox::message (HgLanguage::Language language, Window* parentwin,
                         Glyph* glyph, const char* title,
                         int buttons, int defbutton,
                         const char* userfront, const char* userback,
                         char hotFront, char hotBack,
                         Action* helpAction)
{
  Resource::ref(helpAction);
  helpAction_ = helpAction;
  
  WidgetKit& kit = *WidgetKit::instance ();
  const LayoutKit& layout = *LayoutKit::instance ();

  // array for button handler
  Handler* bhandler[NumDefined];

  // push new style "MessageBox"
  kit.begin_style ("MessageBox", "Dialog");
  Style* style = kit.style ();

  // set window title
  if (title && *title) {
    style->attribute ("name", title);
    style->attribute ("iconName", title);
  } // otherwise same title as parent window

  int vspace = 10, hspace = 20;  // might use X-attribute

  PolyGlyph* buttonbox = layout.hbox ();

  // return value: ID of pressed button
  int retval = 0;

  // display at least one button
  if (!buttons)
    buttons = Ok;

  // invalid default button
  if (!(defbutton & buttons))
    defbutton = 0;

  buttonbox->append (layout.hglue (hspace));

  for (int i = 0;  i < NumDefined;  i++) {
    bhandler[i] = nil;
    int button_i = 1 << i;
    if (buttons & button_i) {
      // bmarsch 19950809: UserDefined button
      const char* str;
      char key = 0;
      if (button_i == UserFront)
        str = userfront, key = hotFront;
      else if (button_i == UserBack)
        str = userback, key = hotBack;
      else
        str = WTranslate::str(label_[i], language), key = hotKey_[i];
      if (!str) continue;
      Action* action = new MsgBoxAction (retval, button_i);
  
      if (button_i & defbutton) {
        // default button
        Button* b = kit.default_button (str, action);
        bhandler[i] = b->handler();   // save handler
        buttonbox->append (b);
        defbutton = button_i;  // prevent more than one default button
      }
      else {
        // begin pushbutton style
        kit.begin_style("PushButton", "Button");
        Button* b = kit.push_button(UnderlinedLabel::hotLabel(kit, str, key), action);
        bhandler[i] = b->handler();
        buttonbox->append(b);
        // end pushbutton style
        kit.end_style();
      }

      buttonbox->append (layout.hglue (hspace));
    }
  } // for i

  Glyph* vspc = layout.vglue (vspace, 0.5, 0);
  // must allow stretching a little bit to compensate for rounding errors
  // of nasty float coordinates (see also Tile::allocate)

  Glyph* body = layout.vbox (
    kit.outset_frame (layout.vbox (
      vspc,
      layout.hbox (
        layout.hglue (hspace),
        glyph,
        layout.hglue (hspace)
      ),
      vspc
    )),
    kit.outset_frame (layout.vbox (
      vspc,
      buttonbox,
      vspc
    ))
  );

  // keyboard handler
  MsgBoxInput* ihandler = new MsgBoxInput(body, style,
                                          buttons, retval, defbutton,
                                          hotFront, hotBack);

  // window
  TransientWindow* win = new TransientWindow(ihandler);
  win->style(style);

  // set delete handler
  MsgBoxDelete* delhandler = new MsgBoxDelete;
  win->wm_delete(delhandler);

  if (parentwin) {
    // center atop primary window
    win->transient_for(parentwin);
    win->place(parentwin->left () + 0.5 * parentwin->width (),
               parentwin->bottom () + 0.5 * parentwin->height ());
    win->align(0.5, 0.5);
  }

  win->map();

  // event loop
  Session* s = Session::instance();
  Event e;
  for (;;) {
    s->read(e);
    // mpichler, 19950311; bmarsch, 19960312: pushbuttons are
    // grabbing; handle only events for only if the grabber is a
    // pushbutton of this MessageBox (otherwise other unwanted
    // MessageBoxes might be created)
    if (ihandler->inside(e) || grabbing(e.grabber(), bhandler)) {
      e.handle();
    }
    else if (e.type() == Event::key) {
      ihandler->keystroke(e);
    }
    if (retval)      // a button or key was pressed
      break;
    if (s->done()) { // this happens when the application window is deleted
      retval = Cancel;
      break;
    }
  } // read events

  // close dialog window
  win->unmap();
  win->unbind();
  delete win;

  // pop style "MessgeBox"
  kit.end_style();

  Resource::unref(helpAction_);

  return retval;
} // message

// checks if grabber is in the button handler list
boolean MessageBox::grabbing(Handler* grabber, Handler** handler)
{
  if (grabber) {
    for (int i = 0; i < NumDefined; i++)
      if (grabber == handler[i])
        return true;
  }

  return false;
}
