// scroldlg.cc - implements scrollable dialog and group classes.
//    Copyright (C) 2000 Laurynas Biveinis <lauras@softhome.net>
//
//    This program is free software; you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation; either version 2 of the License, or
//    (at your option) any later version.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with this program; if not, write to the Free Software
//    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//

#include "scroldlg.h"

#define Uses_TEvent
#define Uses_TGroup
#define Uses_TKeys
#define Uses_TRect
#define Uses_TScrollBar
#include <rhtvision/tv.h>

scrollable_group::scrollable_group(const TRect & bounds) :
   TGroup(bounds),
   //scroll_group_init(&init_background),
   hsb(new TScrollBar(TRect(bounds.a.x + 1, bounds.b.y - 1, 
                                   bounds.b.x - 1, bounds.b.y))),
   vsb(new TScrollBar(TRect(bounds.b.x - 1, bounds.a.y + 1,
                                   bounds.b.x, bounds.b.y - 1)))
{
   eventMask |= evBroadcast;
   options |= ofFramed;
   delta.x = delta.y = limit.x = limit.y = 0;
   insert(new TBackground(TRect(0, 0, size.x, size.y), ' '));
}

void scrollable_group::changeBounds(const TRect & bounds)
{
   lock();
   TGroup::changeBounds(bounds);
   setLimit(limit.x, limit.y);
   unlock();
   drawView();
}

static Boolean is_view(TView * view, void * args)
{
   return Boolean(view == args);
}

void scrollable_group::handleEvent(TEvent& event)
{
   TGroup::handleEvent(event);
   if(event.what == evBroadcast)
   {
      if(event.message.command == cmScrollBarChanged &&
        (event.message.infoPtr == hsb ||
         event.message.infoPtr == vsb))
         scrollDraw();
      else if(event.message.command == cmReceivedFocus &&
              firstThat(is_view, event.message.infoPtr) != 0)
         focusSubView((TView*) event.message.infoPtr);
   }
}

static void do_scroll(TView * view, void * args)
{
   if (strcmp(view->name, "TBackground"))
   {
      TPoint dest = view->origin + (*(TPoint *)args);
      view->moveTo(dest.x, dest.y);
   }
}

void scrollable_group::scrollDraw()
{
   TPoint d;
   d.x = hsb ? hsb->value : 0;
   d.y = vsb ? vsb->value : 0;
   if (d.x != delta.x || d.y != delta.y)
   {
      TPoint info;
      info = delta - d;
      lock();
      forEach(do_scroll, &info);
      delta = d;
      unlock();
      drawView();
   }
}

void scrollable_group::scrollTo(int x, int y)
{
   lock();
   if (hsb && x != hsb->value)
      hsb->setValue(x);
   if (vsb && y != vsb->value)
      vsb->setValue(y);
   unlock();
   scrollDraw();
}

void scrollable_group::setLimit(int x, int y)
{
   limit.x = x;
   limit.y = y;
   lock();
   if (hsb)
      hsb->setParams(hsb->value, 0, x - size.x, size.x - 1, 1);
   if (vsb)
      vsb->setParams(vsb->value, 0, y - size.y, size.y - 1, 1);
   unlock();
   scrollDraw();
}

void scrollable_group::setState(ushort aState, Boolean enable)
{
   TGroup::setState(aState, enable);
   if (aState & (sfActive | sfSelected))
   {
      if (hsb)
         if (enable)
             hsb->show();
         else
             hsb->hide();
      if (vsb)
         if(enable)
             vsb->show();
         else
             vsb->hide();
   }
}

void scrollable_group::focusSubView(TView* view)
{
   TRect rview = view->getBounds();
   TRect r = getExtent();
   r.intersect(rview);
   if(r != rview)
   {
      int dx, dy;
      dx = delta.x;
      if (view->origin.x < 0)
         dx = delta.x + view->origin.x;
      else if (view->origin.x + view->size.x > size.x)
         dx = delta.x+view->origin.x+view->size.x-size.x;
      dy = delta.y;
      if(view->origin.y < 0)
         dy = delta.y + view->origin.y;
      else if(view->origin.y + view->size.y > size.y)
         dy = delta.y+view->origin.y+view->size.y-size.y;
      scrollTo(dx, dy);
   }
}

// Returns true if selecting next selectable control in given group
// in given direction wraps around - crosses first or last control in a group.
static bool next_selectable_wraps(TGroup * where, int forwards)
{
   bool rez = false;
   if (where->current)
   {
      TView * p = where->current;
      do
      {
         if (forwards)
             p = p->next;
         else
             p = p->prev();
         if ((where->last == p) || (where->first() == p))
             rez = true;
      }
      while (!((((p->state & (sfVisible + sfDisabled)) == sfVisible) &&
              (p->options & ofSelectable)) || (p == where->current)));
   }
   return rez;
}

void scroll_dialog::handleEvent(TEvent& event)
{
   // This handles proper switching order between scrollable groups and
   // normal controls. Note that TVision must be compiled *without*
   // #define NO_STREAM for this to work - it needs data member 'name'.
   // Sure, it is possible to acomplish this with RTTI, but that's overkill,
   if /* ( */ (event.what == evKeyDown) /* && */
   {
      if ((event.keyDown.keyCode == kbTab) ||
          (event.keyDown.keyCode == kbShiftTab)) /* ) */
      {
         select_next(event.keyDown.keyCode == kbShiftTab);
         clearEvent(event);
      }
      else
         TDialog::handleEvent(event);
   }
   TDialog::handleEvent(event);
}

void scroll_dialog::select_next(int direction)
{
   if (current != scroll_group)
   {
      // We are selecting away from a simple control
      selectNext(direction);
      // But maybe we have entered the group ?
      if (current == scroll_group)
         scroll_group->selectNext(direction);
   }
   else
   {
      // If selecting next member in a group will wrap around,
      // deselect the whole group and select next dialog control.
      if (next_selectable_wraps(scroll_group, direction))
         selectNext(direction);
      else
         scroll_group->selectNext(direction);
   }
}

