//<copyright>
// 
// Copyright (c) 1994
// Institute for Information Processing and Computer Supported New Media (IICM),
// Graz University of Technology, Austria.
// original version:
// Copyright (c) 1991 Stanford University
// Copyright (c) 1991 Silicon Graphics, Inc.
// 
//</copyright>
/*
 * Permission to use, copy, modify, distribute, and sell this software and 
 * its documentation for any purpose is hereby granted without fee, provided
 * that (i) the above copyright notices and this permission notice appear in
 * all copies of the software and related documentation, and (ii) the names of
 * Stanford and Silicon Graphics may not be used in any advertising or
 * publicity relating to the software without the specific, prior written
 * permission of Stanford and Silicon Graphics.
 * 
 * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, 
 * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY 
 * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.  
 *
 * IN NO EVENT SHALL STANFORD OR SILICON GRAPHICS BE LIABLE FOR
 * ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND,
 * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
 * WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF 
 * LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE 
 * OF THIS SOFTWARE.
 */

/*
 * WFileBrowser -- browse a directory
 */


//<file>
//
// File:        fbrowser.C - implementation of class WFileBrowser
//
// Created:      1 Jul 94   Michael Pichler
//
// Changed:      5 Jul 94   Michael Pichler
//
//
//</file>



/* key bindings (emacs and vi compatible as far as possible)

   arrow up/^p, arrow down/^n, pgup/^b, pgdn/^f/space,
   ^pgup/^home, ^pgdn/^end, home/^a/<, end/^e/>,
   ^l: recenter, j/k: scroll down/up, ^u/^d: half pg. up/down.
*/


#include "fbrowser.h"

#include <Dispatch/dispatcher.h>
#include <Dispatch/iocallback.h>
#include <IV-look/choice.h>
//#include <IV-look/fbrowser.h>
#include <IV-look/kit.h>
#include <InterViews/canvas.h>
#include <InterViews/font.h>
#include <InterViews/event.h>
#include <InterViews/layout.h>
#include <InterViews/scrbox.h>
#include <InterViews/style.h>
#include <InterViews/target.h>
#include <InterViews/telltale.h>
#include <InterViews/window.h>
#include <OS/list.h>

#include <X11/keysym.h>
#include <iostream.h>


// old IV keytable:
// static WFileBrowserKeyInfo default_key_map[] =
// {
//   { '\r', "open", &WFileBrowserImpl::open },
//   { '\007', "cancel", &WFileBrowserImpl::cancel },
//   { '\033', "cancel", &WFileBrowserImpl::cancel },
//   { 'g', "scroll-to-first", &WFileBrowserImpl::scroll_to_first },
//   { 'G', "scroll-to-last", &WFileBrowserImpl::scroll_to_last },
//   { 'a', "select-all", &WFileBrowserImpl::select_all },
//   { '\177', "unselect-all", &WFileBrowserImpl::unselect_all },
//   { '\010', "unselect-all", &WFileBrowserImpl::unselect_all },
//   { '\t', "next-focus", &WFileBrowserImpl::next_focus },
//   { 'p', "select-previous", &WFileBrowserImpl::select_previous },
//   { 'n', "select-next", &WFileBrowserImpl::select_next },
//   { '<', "select-top", &WFileBrowserImpl::select_top },
//   { '>', "select-bottom", &WFileBrowserImpl::select_bottom },
//   { 'j', "scroll-down", &WFileBrowserImpl::scroll_down },
//   { 'k', "scroll-up", &WFileBrowserImpl::scroll_up },
//   { ' ', "page-down", &WFileBrowserImpl::page_down },
//   { 'b', "page-up", &WFileBrowserImpl::page_up },
//   { 'd', "half-page-down", &WFileBrowserImpl::half_page_down },
//   { 'u', "half-page-up", &WFileBrowserImpl::half_page_up },
//   { 0, nil }
// };


declareIOCallback(WFileBrowser)
implementIOCallback(WFileBrowser)


WFileBrowser::WFileBrowser (WidgetKit* kit,
                            Action* accept, Action* cancel, Action* select,
                            boolean multiselect)
: Browser (nil, kit->style(), accept, cancel, multiselect)
{
  kit_ = kit;
  selected_ = -1;
  box_ = new TBScrollBox;
  const Font* f = kit->font ();
  FontBoundingBox bbox;
  f->font_bbox (bbox);
  scale_ = 1.0 / (bbox.ascent () + bbox.descent ());
  save_cursor_ = nil;
  rate_handler_ = new IOCallback(WFileBrowser) (
    this, &WFileBrowser::rate_scroll_timer
  );
  Style* s = kit->style();
  long milliseconds = 75;
  s->find_attribute("scrollRate", milliseconds);
  usec_rate_ = 1000 * milliseconds;
  body (box_);

  select_ = select;
  Resource::ref(select_);
}

WFileBrowser::~WFileBrowser ()
{
  delete rate_handler_;
  Resource::unref(select_);
}

void WFileBrowser::append_item(Glyph* glyph)
{
  Glyph* target = new Target (
    LayoutKit::instance()->h_margin (glyph, 3.0, 0.0, 0.0, 15.0, fil, 0.0),
    TargetPrimitiveHit
  );
  TelltaleState* t = new TelltaleState (TelltaleState::is_enabled);
  append_selectable (t);
  append (new ChoiceItem (t, target, WidgetKit::instance()->bright_inset_frame (target)));
}

void WFileBrowser::remove_item(GlyphIndex gi)
{
  remove_selectable(gi);
  remove(gi);
}

void WFileBrowser::press (const Event& e)
{
  EventButton b = e.pointer_button();
  Window* w = canvas()->window();
  switch (b)
  {
    case Event::left:
      Browser::press(e);
      mode_ = WFileBrowser::selecting;
    break;
    case Event::middle:
      mode_ = WFileBrowser::grab_scrolling;
      save_cursor_ = w->cursor();
      start_scroll_pointer_ = e.pointer_y();
      start_scroll_pos_ = box_->cur_lower (Dimension_Y);
      w->cursor(kit_->hand_cursor());
    break;
    case Event::right:
      mode_ = WFileBrowser::rate_scrolling;
      start_scroll_pointer_ = e.pointer_y();
      start_scroll_pos_ = box_->cur_lower (Dimension_Y);
      save_cursor_ = w->cursor();
    break;
    default:
    break;
  }
}

void WFileBrowser::drag(const Event& e)
{
  WidgetKit& kit = *kit_;
  Coord delta;
  Window* w = canvas()->window();
  switch (mode_)
  {
    case WFileBrowser::selecting:
      Browser::drag(e);
    break;
    case WFileBrowser::grab_scrolling:
      delta = e.pointer_y() - start_scroll_pointer_;
      box_->scroll_to(
        Dimension_Y, start_scroll_pos_ - delta * scale_
      );
    break;
    case WFileBrowser::rate_scrolling:
      cur_scroll_pointer_ = e.pointer_y();
      if (cur_scroll_pointer_ > start_scroll_pointer_) {
        w->cursor(kit.ufast_cursor());
      } else {
        w->cursor(kit.dfast_cursor());
      }
      Dispatcher::instance().stopTimer(rate_handler_);
      rate_scroll_timer (0, 0);
    break;
  }
}

void WFileBrowser::release(const Event& e)
{
  Window* w = canvas()->window();
  Coord delta;
  switch (mode_)
  {
    case WFileBrowser::selecting:
      Browser::release(e);
    break;
    case WFileBrowser::grab_scrolling:
      delta = e.pointer_y() - start_scroll_pointer_;
      box_->scroll_to(
        Dimension_Y, start_scroll_pos_ - delta * scale_
      );
      w->cursor(save_cursor_);
    break;
    case WFileBrowser::rate_scrolling:
      Dispatcher::instance().stopTimer(rate_handler_);
      w->cursor(save_cursor_);
    break;
  }
}

void WFileBrowser::keystroke (const Event& e)
{
  unsigned long keysym = e.keysym ();
  char key = '\0';
  e.mapkey (&key, 1);

  int ctrl = e.control_is_down ();

  switch (keysym)  // cursor keys
  {
    case XK_Up:
      select_previous ();
    break;
    case XK_Down:
      select_next ();
    break;
    case XK_Prior:
      if (ctrl)
        scroll_to_first ();
      else
        page_up ();
    break;
    case XK_Next:
      if (ctrl)
        scroll_to_last ();
      else
        page_down ();
    break;
    case XK_Home:
      if (ctrl)
        scroll_to_first ();
      else
        select_top ();
    break;
    case XK_End:
      if (ctrl)
        scroll_to_last ();
      else
        select_bottom ();
    break;

    default:
      switch (key)
      {
        case '\t':
          if (e.shift_is_down ())
            prev_focus ();
          else
            next_focus ();
        break;
        case '\r':
          open ();
        break;
        case '\007':  // ^g
        case '\033':  // ESC
          cancel ();
        break;
        case 'j':
          scroll_down ();
        break;
        case 'k':
          scroll_up ();
        break;
        case 14:  // ^n
          select_next ();
        break;
        case 16:  // ^p
          select_previous ();
        break;
        case 2:  // ^b
          page_up ();
        break;
        case 6:  // ^f
        case ' ':
          page_down ();
        break;
        case 21:  // ^u
          half_page_up ();
        break;
        case 4:  // ^d
          half_page_down ();
        break;
        case 1:  // ^a
        case '<':
          select_top ();
        break;
        case 5:  // ^e
        case '>':
          select_bottom ();
        break;
        case 12:  // ^l
          recenter ();
        break;
      } // switch key

  } // switch keysym
} // keystroke

InputHandler* WFileBrowser::focus_in ()
{
  // bmarsch: 290695: do not destroy selections in multiselect mode
  // (because multiselect is done via the TelltaleStates
  if (!multiselect_) {
  // restore selection when getting back focus
    if (selected_ == -1)
      select_top ();
    else
      Browser::select (selected_);
  }
  return Browser::focus_in ();
}

void WFileBrowser::focus_out ()
{
  // bmarsch: 290695: do not destroy selections in multiselect mode
  // (because multiselect is done via the TelltaleStates
  if (!multiselect_) {
    // hide selection when leaving focus
    selected_ = selected ();
    Browser::select (-1);
  }
}

void WFileBrowser::select (GlyphIndex i)
{
  selected_ = i;
  Browser::select (i);
  if (select_ && i >= 0) select_->execute();
}

Adjustable* WFileBrowser::adjustable () const
{
  return box_;
}

void WFileBrowser::refresh ()
{
  // scroll to first item, but do not select it
  GlyphIndex n = box_->count ();
  box_->scroll_to (Dimension_Y, (Coord) n);
}

void WFileBrowser::open ()
{
  GlyphIndex i = selected ();
  if (i >= 0 && i < count ())
    choose (i);
}

/* scrolling functions */

// if IV's scrollbars were some more flexible ...
// so the scrollbox has 0 for the last (bottommost) item,
// and boxCount-1 for the first (topmost) item (no. 0),
// or generally the i-th item at boxCount-i-1 at bottom line
// (box is a TBScrollBox).
// selection should not go out of view on paging

void WFileBrowser::scroll_to_first ()
{
  GlyphIndex n = box_->count ();
  box_->scroll_to (Dimension_Y, (Coord) n);
  select (0);
}

void WFileBrowser::scroll_to_last ()
{
  GlyphIndex n = box_->count ();
  box_->scroll_to (Dimension_Y, (Coord) 0);
  select (n-1);
}

void WFileBrowser::select_previous ()
{
  GlyphIndex i = selected ();

  if (!box_->shown (i))  // place the selected line on last visible row
    box_->scroll_to (Dimension_Y, Coord (box_->count () - i - 1));
  if (i > 0)
  {
    i--;
    if (!box_->shown (i))  // sroll up one line when reaching top
      box_->scroll_forward (Dimension_Y);
    select (i);
  }
}

void WFileBrowser::select_next ()
{
  GlyphIndex i = selected ();

  if (!box_->shown (i))  // place selected line on first visible row
    box_->scroll_to(
      Dimension_Y, Coord (
        box_->count() - 1 - i +
        box_->first_shown () - box_->last_shown ()
      )
    );
  if (i < count () - 1)
  {
    i++;
    if (!box_->shown (i))  // scroll down one line when reaching bottom
      box_->scroll_backward (Dimension_Y);
    select (i);
  }
}

void WFileBrowser::select_top ()
{
  select (box_->first_shown ());
}

void WFileBrowser::select_bottom ()
{
  select (box_->last_shown ());
}

void WFileBrowser::scroll_down ()
{
  box_->scroll_backward (Dimension_Y);
}

void WFileBrowser::scroll_up ()
{
  box_->scroll_forward (Dimension_Y);
}

void WFileBrowser::page_down ()
{
  GlyphIndex d = selected () - box_->first_shown ();
  boolean vis = box_->shown (selected ());

// page backward usually scrolls down by last_shown - first_shown
// lines, but it need not (e.g. can not page over end of list); keep
// difference between selected and first shown item to hold selection
// bar on same position (if it was previously visible)

  box_->page_backward (Dimension_Y);
  if (vis)
    select (box_->first_shown () + d);
}

void WFileBrowser::page_up ()
{
  GlyphIndex d = selected () - box_->first_shown ();
  boolean vis = box_->shown (selected ());

  box_->page_forward (Dimension_Y);
  if (vis)
    select (box_->first_shown () + d);
}

void WFileBrowser::half_page_down ()
{
  GlyphIndex n = box_->last_shown () - box_->first_shown () + 1;
  GlyphIndex half = n >> 1;
  for (GlyphIndex i = 0; i < half; i++)
    box_->scroll_backward (Dimension_Y);  // why not scroll backward by half???
}

void WFileBrowser::half_page_up ()
{
  GlyphIndex n = box_->last_shown() - box_->first_shown () + 1;
  GlyphIndex half = n >> 1;
  for (GlyphIndex i = 0; i < half; i++)
    box_->scroll_forward(Dimension_Y);  // ditto
}

void WFileBrowser::recenter ()
{
  GlyphIndex i = selected () + (box_->last_shown () - box_->first_shown () + 1) / 2;  // new bottom

// cerr << box_->count () << " items, visible: from " << box_->first_shown () << " to "
// << box_->last_shown () << ", selected item: " << selected () << ", new bottom item: " << i << endl;

  box_->scroll_to (Dimension_Y, Coord (box_->count () - 1 - i));
}

// timer for rate scrolling

void WFileBrowser::rate_scroll_timer (long, long)
{
  Coord delta = cur_scroll_pointer_ - start_scroll_pointer_;
  box_->scroll_to (
    Dimension_Y, box_->cur_lower(Dimension_Y) + delta * scale_
  );
  Dispatcher::instance ().startTimer (0, usec_rate_, rate_handler_);
}

// void WFileBrowser::mark(GlyphIndex gi)
// {
//   Browser::mark(gi);
//   // makes troubles, when browser is not on screen yet
// //  box_->scroll_to(Dimension_Y, box_->count()-1-gi);
// }
