/*****************************************************************************
#   Copyright (C) 1994-2008 by David Gordon.
#   All rights reserved.                           
#                                                                           
#   This software is part of a beta-test version of the Consed/Autofinish
#   package.  It should not be redistributed or
#   used for any commercial purpose, including commercially funded
#   sequencing, without written permission from the author and the
#   University of Washington.
#   
#   This software is provided ``AS IS'' and any express or implied
#   warranties, including, but not limited to, the implied warranties of
#   merchantability and fitness for a particular purpose, are disclaimed.
#   In no event shall the authors or the University of Washington be
#   liable for any direct, indirect, incidental, special, exemplary, or
#   consequential damages (including, but not limited to, procurement of
#   substitute goods or services; loss of use, data, or profits; or
#   business interruption) however caused and on any theory of liability,
#   whether in contract, strict liability, or tort (including negligence
#   or otherwise) arising in any way out of the use of this software, even
#   if advised of the possibility of such damage.
#
#   Building Consed from source is error prone and not simple which is
#   why I provide executables.  Due to time limitations I cannot
#   provide any assistance in building Consed.  Even if you do not
#   modify the source, you may introduce errors due to using a
#   different version of the compiler, a different version of motif,
#   different versions of other libraries than I used, etc.  For this
#   reason, if you discover Consed bugs, I can only offer help with
#   those bugs if you first reproduce those bugs with an executable
#   provided by me--not an executable you have built.
# 
#   Modifying Consed is also difficult.  Although Consed is modular,
#   some modules are used by many other modules.  Thus making a change
#   in one place can have unforeseen effects on many other features.
#   It may takes months for you to notice these other side-effects
#   which may not seen connected at all.  It is not feasable for me to
#   provide help with modifying Consed sources because of the
#   potentially huge amount of time involved.
#
#*****************************************************************************/
//
// guihighlight.cpp
//
// X-specific part of highlighting bases
//
// gordon 11-May-1995
//




#include <ctype.h>
#include <X11/Xlib.h>
#include <X11/keysym.h>

#include    <Xm/Xm.h>

#include    <iostream.h>

#include    "guiteditor.h"
#include    "guitedwin.h"
#include    "tedwin.h"
#include    "hp_exception_kludge.h"
#include    "consedParameters.h"
#include    "consed.h"
#include    "teditor.h"



void guiTedWinConsButtonPressXtEventHandler(Widget, 
                                            XtPointer  client_data,
                                            XEvent*    pEvent,
                                            Boolean* ) {

   GuiTedWin   *pGuiTedWin = (GuiTedWin*) client_data;

   int  nPixelX = pEvent->xbutton.x;
   int  nY = pEvent->xbutton.y;
   unsigned int uButton = pEvent->xbutton.button;

   if (pEvent->xbutton.button == Button1) {
      TRY_CATCH_WRAPPER( pGuiTedWin->guiSetConsCursor(nPixelX));
   }
}


void guiTedWinFragButtonPressXtEventHandler( Widget, 
                                            XtPointer  client_data,
                                            XEvent*    pEvent,
                                            Boolean* ) {

   GuiTedWin   *pGuiTedWin = (GuiTedWin*) client_data;

   int  nPixelX = pEvent->xbutton.x;
   int  nY = pEvent->xbutton.y;
   unsigned int uButton = pEvent->xbutton.button;

   // is this button 1 or 2?
   if (pEvent->xbutton.button == Button2) {
      TRY_CATCH_WRAPPER(pGuiTedWin->guiStartHighlight( nPixelX ));
   }
   else if (pEvent->xbutton.button == Button1) {
      TRY_CATCH_WRAPPER(pGuiTedWin->guiSetFragCursor(nPixelX));
   }
   else if (pEvent->xbutton.button == Button3) {
      TRY_CATCH_WRAPPER( pGuiTedWin->pTedWinGet()->popupTags( nPixelX ) );
   }
}



void guiTedWinButtonReleaseXtEventHandler( Widget, 
                                         XtPointer  client_data,
                                         XEvent*    pEvent,
                                         Boolean* ) {
   

   int  nX = pEvent->xbutton.x;
   int  nY = pEvent->xbutton.y;
   unsigned int uButton = pEvent->xbutton.button;

   // if this button 1 ignore
   if (pEvent->xbutton.button != Button2) {
      return;
   }

   GuiTedWin   *pGuiTedWin = (GuiTedWin*) client_data;
   TRY_CATCH_WRAPPER(pGuiTedWin->guiContinueHighlight( nX );pGuiTedWin->guiHighlightFinished());
}


void guiTedWinButtonMotionXtEventHandler(Widget, 
                                         XtPointer  client_data,
                                         XEvent*    pEvent,
                                         Boolean* ) {
   int  nX = pEvent->xmotion.x;
   int  nY = pEvent->xmotion.y;
   unsigned long ulSerial = pEvent->xmotion.serial;

   GuiTedWin *pGuiTedWin = (GuiTedWin*) client_data;
   TRY_CATCH_WRAPPER(pGuiTedWin->guiContinueHighlight( nX ));

}


void guiTedWinLeaveNotifyXtEventHandler( Widget, 
                                         XtPointer,
                                         XEvent*,
                                         Boolean*) {
   //   cerr << "Left window" << endl;
}


void guiTedWinKeyPressXtEventHandler(Widget, 
                                     XtPointer  client_data,
                                     XEvent*    pEvent,
                                     Boolean*) {
   // not concerned with anything other than keypress
   // just in case one made it here by mistake
   if (pEvent->type != KeyPress) return;

   // get this pointer to guitedwin from client data
   GuiTedWin* pGuiTedWin = (GuiTedWin* )client_data;

   // translate the event
   const int nBufSize = 20;
   char szBuf[nBufSize];
   KeySym keySym;
   XComposeStatus xcsStatus ;  // unused through R4 sez O'Reilly
   int nRetLength = XLookupString((XKeyEvent* )pEvent,
                                  szBuf,
                                  nBufSize,
                                  &keySym,
                                  &xcsStatus);

   char c = szBuf[0];
   // printf( "keysym = %x\n", keySym );

   // cout << "keysym = " << (void*) ks << endl; cout.flush();

   //
   // keysym constants are defined in <X11/keysym.h>
   // note that modifiers are ignored.  if we ever want to care
   // about Shift, Ctrl, etc.  the translation has to be done
   // more carefully.
   //
   if ((keySym == XK_Delete)) {
         TRY_CATCH_WRAPPER(pGuiTedWin->pTedWinGet()->userOverstruckNtide('*'));
   }
   else if ( keySym == XK_BackSpace ) {
      GuiApp::popupErrorMessage( "To delete a base, overstrike with a *" );
      return;
   }
   else if ((keySym == XK_Shift_L) || (keySym == XK_Shift_R) ||
            (keySym == XK_Control_L) || (keySym == XK_Control_R) ) {
      // ignore
   }
   else if ( (keySym == XK_Left ) || (keySym == XK_Right ) ) {
      TRY_CATCH_WRAPPER( pGuiTedWin->pTedWinGet()->userPushedArrowKey( keySym == XK_Left ) );
   }
   else if ( keySym == XK_Up || keySym == XK_Down ) {
      TRY_CATCH_WRAPPER( pGuiTedWin->pTeditorGet()->userPushedUpDownArrowKey( keySym == XK_Up ) );
   }
   else {

      switch (c) {
       case 'A':
       case 'C':
       case 'G':
       case 'T':
       case 'N':
       case 'X':
       case 'a':
       case 'c':
       case 'g':
       case 't':
       case 'n':
       case 'x':
       case '*':
       case 'R':
       case 'r':
       case 'Y':
       case 'y':
       case 'M':
       case 'm':
       case 'K':
       case 'k':
       case 'S':
       case 's':
       case 'W':
       case 'w':
       case 'D':
       case 'd':
       case 'H':
       case 'h':
       case 'B':
       case 'b':
       case 'V':
       case 'v':
         // cout << "overstrike with char " << c << endl;

         // pass the overstrike event to the TedWin.
         // note that WHICH char to strike is determined
         // by the current location of the EditCursor
         TRY_CATCH_WRAPPER(pGuiTedWin->pTedWinGet()->userOverstruckNtide(c));
         break;
       case ' ':
         // cout << "space bar interpreted as insert *" << endl;
         TRY_CATCH_WRAPPER(pGuiTedWin->pTedWinGet()->insertColumnOfPads());
         break;
       case '>':
          TRY_CATCH_WRAPPER(pGuiTedWin->pTedWinGet()->userPressedHotKey(
                eChangeToNsToRight ) );
          break;
       case '<':
         TRY_CATCH_WRAPPER(pGuiTedWin->pTedWinGet()->userPressedHotKey(
                eChangeToNsToLeft ) );
          break;
       case '\x0C':  // control-L
         TRY_CATCH_WRAPPER(pGuiTedWin->pTedWinGet()->userPressedHotKey(
                eMakeLowQualityToLeft ) );
          break;
       case '\x12':  // control-R
         TRY_CATCH_WRAPPER(pGuiTedWin->pTedWinGet()->userPressedHotKey(
                eMakeLowQualityToRight ) );
         break;
       case '\x10':  // control-P
         TRY_CATCH_WRAPPER(pGuiTedWin->pTedWinGet()->userPressedHotKey(
                eAddPolymorphismTag ) );
         break;
       default:
          TRY_CATCH_WRAPPER(
          bool bFoundUserDefinedKey = false;
          for( int n = 0; n < pCP->aUserDefinedKeys_.length(); ++n ) {
             if ( c == pCP->aUserDefinedKeys_[ n ] ) {
                RWCString soProgram = pCP->aProgramsForUserDefinedKeys_[ n ];
                RWCString soArgument1 = pCP->aArgumentsToPassToUserDefinedPrograms_[ n];
                RWCString soTagWithUserDefinedKey = pCP->aTagsToApplyWithUserDefinedKeys_[n];
                pGuiTedWin->pTedWinGet()->userPressedUserDefinedKey( 
                                soProgram,
                                soArgument1,
                                soTagWithUserDefinedKey );

                bFoundUserDefinedKey = true;
                break;
             }
          }

          if ( bFoundUserDefinedKey )
             break;

         GuiApp::beep();
         break;
         );
      }
   }

}


// called by motion and button press event handlers
void GuiTedWin :: guiContinueHighlight( int nPixelX ) {

   handleMovingMouseOffOrBackIntoWindow( nPixelX );

   int  nConsPos = nConsPosFromPixelX( nPixelX );
   pTedWinGet()->continueHighlight( nConsPos );

   

   // record case of the user trying to move the point outside of the
   // window

   if ( nPixelX <= 0 || nPixelX >= ( nGetTraceWindowPixelWidth() - 1 ) ) {
      ConsEd::pGetConsEd()->pGetSelection()->bUserMovedPointerToEdgeOfWindow_ = true;
   }

}
   

void GuiTedWin :: guiStartHighlight( int nPixelX ) {
   int  nConsPos = nConsPosFromPixelX( nPixelX );
   pTedWinGet()->startHighlight( nConsPos );
}   


// handles mouse event in frag window to set cursor
void GuiTedWin::guiSetFragCursor( const int nPixelX ) {
   int  nConsPos = nConsPosFromPixelX( nPixelX );
   pTedWinGet()->setFragCursor( nConsPos );
}   


// handles mouse event in consensus window to set cursor
void GuiTedWin::guiSetConsCursor(const int nPixelX) {
   int nConsPos = nConsPosFromPixelX( nPixelX );
   pTedWinGet()->setConsCursor( nConsPos );
}   

void    GuiTedWin  ::  guiHighlightBackground( const int nLeftConsPos,
                                                const int nRightConsPos ) {

   int  nLeftPixel = nLeftPixelXFromConsPos( nLeftConsPos );
   int  nRightPixel = nRightPixelXFromConsPos( nRightConsPos );
   
   int  nWindowHeight;
   Arg  aArg[20];
   int  nArgs = 0;
   XtSetArg( aArg[ nArgs ], XtNheight, &nWindowHeight ); ++nArgs;
   XtGetValues( widFragmentBases_, aArg, nArgs );

   XFillRectangle(
                  XtDisplay( widFragmentBases_ ),
                  XtWindow(  widFragmentBases_ ),
                  pGuiColorTextHighlightBackground_->gcGet(),
                  nLeftPixel, // x origin
                  0,
                  nRightPixel - nLeftPixel + 1, // width
                  nGetTextWindowHeight( eFragmentBase )
                  );

}
   

void    GuiTedWin  ::  guiHighlightFinished() {
   stopAutomaticScrollingIfNecessary();
   pTedWinGet()->highlightFinished();
}



void GuiTedWin :: handleMovingMouseOffOrBackIntoWindow( 
                          const int nPixelX ) {

   bool bIsNowOnWindow = ( 0 <= nPixelX &&
                           nPixelX < nGetTraceWindowPixelWidth() ) ?
      true : false;

   if ( bIsNowOnWindow && !bMouseIsOnWindow_ ) {
      // change back to onto window--remove the timer

      stopAutomaticScrollingIfNecessary();
   }
   else if ( !bIsNowOnWindow && bMouseIsOnWindow_ ) {

      // just moved off the window--add the timer
      
      bAutomaticallyScrollLeftNotRight_ = 
         ( nPixelX < 0 ? true : false );

      pTedWin_->automaticallyScroll();

      resetScrollingTimer();
   }
   else if ( bIsNowOnWindow && bMouseIsOnWindow_ ) {
      return;
   }
   else if ( !bIsNowOnWindow && !bMouseIsOnWindow_ ) {
      return;
   }


   bMouseIsOnWindow_ = bIsNowOnWindow;
}


static void cbAutomaticScrollingTimer( XtPointer pClientData,
                                      XtIntervalId* pId ) {

   TedWin* pTedWin = (TedWin*) pClientData;

   TRY_CATCH_WRAPPER( 
      pTedWin->automaticScrollingTimer();
      pTedWin->pGuiTedWin_->resetScrollingTimer();
      );
}


void GuiTedWin :: resetScrollingTimer() {

   bAutomaticScrollingInProgress_ = true;

   xtidTimerForAutomaticScrolling_ = XtAppAddTimeOut(
         GAPP->ctxGetAppContext(),
         pCP->nWhenUserScrollsOffWindowMillisecondsBetweenScrolling_,
         cbAutomaticScrollingTimer,
         pTedWin_ );
}



void GuiTedWin :: stopAutomaticScrollingIfNecessary() {

   if ( bAutomaticScrollingInProgress_ ) {

      XtRemoveTimeOut( xtidTimerForAutomaticScrolling_ );
      bAutomaticScrollingInProgress_ = false;
   }
}