/*****************************************************************************
#   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.
#
#*****************************************************************************/
//
// guiinput.cpp
//
// handles user's mouse clicks and character inputs
//
// gordon 7-Mar-95
//




#include <X11/keysym.h>
#include <Xm/Xm.h>
#include <Xm/Form.h>
#include <Xm/DrawingA.h>
#include <Xm/ScrolledW.h>
#include <Xm/ScrollBar.h>
#include <Xm/RowColumn.h>

#include <iostream.h>
#include <string.h>
#include <stdio.h>

#include "motifutils.h"
#include "guiapp.h"
#include "guicontigwin.h"
#include "mbt_errors.h"
#include "numutil.h"
#include "contigwin.h"
#include "consed.h"
#include "hp_exception_kludge.h"


// catch drawing window input events, translate to char coords
// and pass it on to the GuiContigWin's callback
void inputXtCb(Widget widCw,
               GuiContigWin* pGcw,
               XmDrawingAreaCallbackStruct *pCb) {

   TRY_CATCH_WRAPPER(pGcw->GuiInputCallback( pCb ));
}


////////////////////////////////////////////////////////////
//
// PURPOSE: low level (pixel) processing of Mouse and Char press events 
//
// HOW TO USE IT: called from a non-class callback
//
// REV: (David Gordon) Feb. 28, 1995
//
////////////////////////////////////////////////////////////

void GuiContigWin :: GuiInputCallback( XmDrawingAreaCallbackStruct *pCb ) {

   // to humor people who click on the window before they have put a 
   // contig in it
   if ( !pContigWin_->pGetContig() ) return;



   int  nPixelX;
   int  nPixelY;

   if ( pCb->event->xany.type == ButtonPress ) {

      // before doing anything, clear any highlight/selection
      // from a previous ButtonPress
      pContigWin_->clearHighlight();

      // I've found that pCb->event->xbutton.state == ControlMask 
      // is not set on a button release event.  Thus there must be
      // some other method of determining if the user had pushed 
      // control on button down.

      uButtonPressState_ = pCb->event->xbutton.state;

      //      printf( "button state = %o\n", uButtonPressState_ );

      if (pCb->event->xbutton.button == Button2) {

         nPixelX = pCb->event->xbutton.x;
         nPixelY = pCb->event->xbutton.y;

         bool bControl = uButtonPressState_ & ControlMask;
         bool bShift = uButtonPressState_ & ShiftMask;


         if ( bIsInConsensusBasesArea( nPixelX, nPixelY ) ) {
            if ( ( bControl && !bShift ) || ( !bControl && !bShift ) ) 
               guiStartHighlight( nPixelX, nPixelY );
            else if ( bShift && !bControl )
               pContigWin_->popup4BestTracesByCursorPosition( 
                                   nCharXFromPixel( nPixelX ) );
         }
         else if ( bIsInFragmentBasesArea( nPixelX, nPixelY ) ) {
            if ( !bControl && !bShift ) 
               pContigWin_->popupTracesByCursorPosition(nCharXFromPixel(nPixelX), 
                                  pContigWin_->nFragLineFromPixelY(nPixelY) );
         }
      } 
      else if (pCb->event->xbutton.button == Button1) {
         
         nPixelX = pCb->event->xbutton.x;
         nPixelY = pCb->event->xbutton.y;

         pContigWin_->nButton1PressedScreenX_ = nCharXFromPixel( nPixelX );
         
         int nScreenCharX = nCharXFromPixel(nPixelX);

         if (bIsInFragmentBasesArea( nPixelX, nPixelY ) ) {

            pContigWin_->bButton1PressedInReadBasesArea_ = true;
            pContigWin_->bButton1PressedInConsensusBasesArea_ = false;

            pContigWin_->nButton1PressedReadLine_ = 
               pContigWin_->nFragLineFromPixelY(nPixelY);


            pContigWin_->moveCursorToFragChar( nScreenCharX, 
                                               pContigWin_->nButton1PressedReadLine_ );

            pContigWin_->showBaseQuality( nScreenCharX, 
                                   pContigWin_->nFragLineFromPixelY(nPixelY),
                                   true ); // read base

            guiStartHighlight( nPixelX, nPixelY );
            
            if ( pCP->bPutHorizontalLineAtCursor_ ) {
               guiDrawHorizontalLineAtCursor( pContigWin_->nButton1PressedReadLine_ );
            }
         }
         else if (bIsInConsensusBasesArea(nPixelX, nPixelY)) {
            pContigWin_->bButton1PressedInReadBasesArea_ = false;
            pContigWin_->bButton1PressedInConsensusBasesArea_ = true;
            pContigWin_->moveCursorToConsCharXPos(nCharXFromPixel(nPixelX));
            pContigWin_->showBaseQuality( nScreenCharX, 0, 
                                          false ); // consensus base
            guiStartHighlight( nPixelX, nPixelY );
         }
         else {

            pContigWin_->bButton1PressedInReadBasesArea_ = false;
            pContigWin_->bButton1PressedInConsensusBasesArea_ = false;


            if (bIsOnAReadName( nPixelX, nPixelY ) ) {
               pContigWin_->toggleHighlightOfReadName( nPixelX, nPixelY );
            }

            //cout << "not in bases area, x = " <<
            //  nPixelX << " y = " << nPixelY << endl; cout.flush();
         }
         
      }  /*       if (pCb->event->xbutton.button == Button2) */
      else if (pCb->event->xbutton.button == Button3) {

         nPixelX = pCb->event->xbutton.x;
         nPixelY = pCb->event->xbutton.y;

         nPixelXBeforePopupMenu_ = nPixelX;
         nPixelYBeforePopupMenu_ = nPixelY;

         if (pCb->event->xbutton.state == ControlMask )
            guiPopupTagInfoForTagsAtThisLocation();
         else {
            popupMenu( (XButtonPressedEvent*) pCb->event );
         }

         // set cursor
         if (bIsInFragmentBasesArea( nPixelX, nPixelY ) ) {
            int nScreenCharX = nCharXFromPixel(nPixelX);
            int nReadIndex = pContigWin_->nFragLineFromPixelY(nPixelY);

            pContigWin_->moveCursorToFragChar(nScreenCharX, nReadIndex );
         }
         else if (bIsInConsensusBasesArea(nPixelX, nPixelY)) {
            pContigWin_->moveCursorToConsCharXPos(nCharXFromPixel(nPixelX));
         }


      }  /*       if (pCb->event->xbutton.button == Button2) */
   } /*    if ( pCb->event->xany.type == ButtonPress )  */
   else if ( pCb->event->xany.type == ButtonRelease ) {

      

      nPixelX = pCb->event->xbutton.x;
      nPixelY = pCb->event->xbutton.y;

      if (pCb->event->xbutton.button == Button1 ) {
         pContigWin_->userReleasedMouseButton1( nPixelX, nPixelY );
      }
      else if (pCb->event->xbutton.button == Button2 ) {
         // I've found that pCb->event->xbutton.state == ControlMask 
         // is not set on a button release event.  Thus there must be
         // some other method of determining if the user had pushed 
         // control on button down.

         bool bControl = uButtonPressState_ & ControlMask;
         bool bShift = uButtonPressState_ & ShiftMask;

         if ( ( bControl && !bShift ) || ( !bControl && !bShift ) ) 
            pContigWin_->highlightFinished();
      }

      bMouseIsOnBasesWithButtonDown_ = false;
   }
   else if ( pCb->event->xany.type == KeyPress ) {
      // translate the key
      const int nBufSize = 20;
      char szBuf[ nBufSize];
      KeySym keySym;
      XComposeStatus xcsStatus;
      int nRetLength = XLookupString( (XKeyEvent* ) pCb->event,
                                      szBuf,
                                      nBufSize,
                                      &keySym,
                                      &xcsStatus );

      char c = szBuf[0];

      if ( (keySym == XK_Shift_L) || (keySym == XK_Shift_R) ||
           (keySym == XK_Control_L) || (keySym == XK_Control_R) ) {
         // ignore the user pushing down the control key or pushing
         // down the shift key
      }
      else if ( keySym == XK_Right ) {
         pContigWin_->userPushedRightArrow();
      }
      else if ( keySym == XK_Left ) {
         pContigWin_->userPushedLeftArrow();
      }
      else if ( keySym == XK_Down ) {
         pContigWin_->userPushedDownArrow();
      } 
      else if ( keySym == XK_Up ) {
         pContigWin_->userPushedUpArrow();
      }
      else {
         switch (c) {
         case '\x01' : // control A
            pContigWin_->userPushedControlA();
            break;
         case '\x05' : // control E
            pContigWin_->userPushedControlE();
            break;
         case '\x14' : // control T

            nPixelX = pCb->event->xbutton.x;
            nPixelY = pCb->event->xbutton.y;

            if ( bIsInFragmentBasesArea( nPixelX, nPixelY ) ) {
               pContigWin_->popupTracesByCursorPosition(
                         nCharXFromPixel(nPixelX), 
                         pContigWin_->nFragLineFromPixelY(nPixelY) 
                                                           );
            }
            
            break;
         case '<' :
            pContigWin_->userPushedLeftArrowAboveComma();
            break;
         case '>' :
            pContigWin_->userPushedRightArrowAbovePeriod();
            break;
         default:

            // is this key a user-defined key?

            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 ];
                  pContigWin_->userPressedUserDefinedKey( 
                                                         soProgram,
                                                         soArgument1,
                                                         soTagWithUserDefinedKey );

                  bFoundUserDefinedKey = true;
                  break;
               }
            }
            if ( bFoundUserDefinedKey )
               break;

            // couldn't find this key.  Could it be an overstrike with a 
            // letter or a pad?

            if ( pContigWin_->bIsEditCursorVisible() && 
                 pContigWin_->editCursor_.bCursorOnCons()  ) {

               int nConsPos = pContigWin_->editCursor_.nConsPosGet();

               char cLower = tolower(c);
               if ( ( 'a' <= cLower && cLower <= 'z' ) || ( cLower == '*' ) ) {
                  pContigWin_->userOverstrikingConsensus( nConsPos,
                                                          cLower );

                  break;
               }
            }

             
            // if got here, user pushed an illegal key

            GuiApp::beep();

         }  // switch
      } //       if ( (keySym == XK_Shift_L) || (keySym == XK_Shift_R) ... else

   } //    else if ( pCb->event->xany.type == KeyPress ) {

} // void GuiContigWin :: GuiInputCallback( XmDrawingAreaCallbackStruct *pCb )


bool GuiContigWin :: bIsInFragmentBasesArea(const int nPixelX,
                                           const int nPixelY) const {

   bool b1 = 
       (nFragmentBasesStartPixelX() <= nPixelX);

   bool b2 = 
       (nForFragLineTopPixelY() <= nPixelY);

   bool b3 = 
       (nPixelY <= pContigWin_->nGetFragmentBasesMaxPixelY());

   bool b = b1 && b2 && b3;

   return( b );
}



bool GuiContigWin :: bIsOnAReadName( const int nPixelX,
                                    const int nPixelY ) {

   return ( 
           ( nPixelX < nFragmentBasesStartPixelX() ) &&
           ( nForFragLineTopPixelY() <= nPixelY ) &&
           ( nPixelY <= pContigWin_->nGetFragmentBasesMaxPixelY() )
           );
}


bool GuiContigWin :: bIsOnARead( const int nPixelY ) {

   return( 
          ( nForFragLineTopPixelY() <= nPixelY ) &&
          ( nPixelY <= pContigWin_->nGetFragmentBasesMaxPixelY() )
          );
}



void GuiContigWin :: guiStartHighlight( const int nPixelX,
                                        const int nPixelY ) {

   if  ( bIsInConsensusBasesArea( nPixelX, nPixelY ) ) {

      int nScreenCharX = nCharXFromPixel(nPixelX);

      pContigWin_->startHighlight( nScreenCharX, true,  // is on consensus
                                   0 );
   }
   else if ( bIsInFragmentBasesArea( nPixelX, nPixelY ) ) {

      int nScreenCharX = nCharXFromPixel(nPixelX);
      int nReadIndex = pContigWin_->nFragLineFromPixelY(nPixelY);

      pContigWin_->startHighlight( nScreenCharX, 
                                   false, // is not on consensus
                                   nReadIndex );
   }
}