//<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 <hyperg/Dispatch/dispatcher.h>
#include <hyperg/Dispatch/iocallback.h>
#include <InterViews/enter-scope.h>
#include <IV-look/choice.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 <hyperg/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;
  box_ = new TBScrollBox;
  const Font* f = kit->font ();
  FontBoundingBox bbox;
  f->font_bbox (bbox);
  scale_ = 1.0 / (bbox.ascent () + bbox.descent ());
  save_cursor_ = nil;
  save_window_ = 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_);

  focus_ = false;
}

WFileBrowser::~WFileBrowser ()
{
  // bmarsch 19950822: stop timer before destroying handler
  Dispatcher::instance().stopTimer(rate_handler_);

  // bmarsch 19950905: restore cursor
  if (save_cursor_ && save_window_) save_window_->cursor(save_cursor_);

  delete rate_handler_;
  Resource::unref(select_);
}

void WFileBrowser::append_item(Glyph* glyph,
                               Coord lborder, Coord lstretch, Coord lshrink,
                               Coord rborder, Coord rstretch, Coord rshrink)
{
  Glyph* target = new Target (
    LayoutKit::instance()->h_margin(
      glyph,
      lborder, lstretch, lshrink,
      rborder, rstretch, rshrink
    ),
    TargetTransparentHit
  );
  TelltaleFlags flags = focus_ ? TelltaleState::is_enabled_visible
                               : TelltaleState::is_enabled;
  TelltaleState* t = new TelltaleState (flags);
  append_selectable (t);
  ChoiceItem* choice = new ChoiceItem(t);
  // visible == has focus; active == is selected
  choice->look(
    0, TelltaleState::is_active,
    target
  );
  choice->look(
    TelltaleState::is_enabled_active, TelltaleState::is_visible,
    kit_->narrow_inset_frame(target)
  );
  choice->look(
    TelltaleState::is_enabled_visible_active, 0,
    kit_->bright_inset_frame(target)
  );
  append(choice);
  box_->notify_all();
}

void WFileBrowser::remove_item(GlyphIndex gi)
{
  remove_selectable(gi);
  remove(gi);
  box_->notify_all();
}

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();
      save_window_ = w;
      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();
      save_window_ = w;
    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:
      if (select_) select_->execute();
      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_);
      // bmarsch 19950905: cursor has been restored
      save_window_ = nil;
    break;
    case WFileBrowser::rate_scrolling:
      Dispatcher::instance().stopTimer(rate_handler_);
      w->cursor(save_cursor_);
      // bmarsch 19950905: cursor has been restored
      save_window_ = nil;
    break;
  }
}

void WFileBrowser::keystroke (const Event& e)
{
  // bmarsch 951006: pass the event to the focus handler, if there is
  // one !!! (this is important if you have nested InputHandlers
  if (focus_handling()) {
    InputHandler::keystroke(e);
    return;
  }

  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 ()
{
  focus_ = true;

  // set TelltaleState::is_visible (flag indicates focus)
  int i = selectable_count();
  while (i--) {
    // see Browser::active()
    TelltaleState* t = state(i);
    t->attach(this);
    t->set(TelltaleState::is_visible, true);
    t->detach(this);
  }
  return Browser::focus_in ();
}

void WFileBrowser::focus_out ()
{
  focus_ = false;

  // clear TelltaleState::is_visible (flag indicates focus)
  int i = selectable_count();
  while (i--) {
    // see Browser::active()
    TelltaleState* t = state(i);
    t->attach(this);
    t->set(TelltaleState::is_visible, false);
    t->detach(this);
  }

  // bmarsch 951006: tell base class about focus out to handle
  // hierarchical structures correctly
  Browser::focus_out();
}


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);
  if (select_)
    select_->execute();
}

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

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);
    if (select_)
      select_->execute();
  }
}

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);
    if (select_)
      select_->execute();
  }
}

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

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

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);
    if (select_)
      select_->execute();
  }
}

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);
    if (select_)
      select_->execute();
  }
}

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

  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_);
}
