/*****************************************************************************
#   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.
#
#*****************************************************************************/
//
// textbox.cpp
//
// implementation for TextBox object
//
// chrisa 28-feb-95, re-written dsg
//



//#include <iostream.h>
//#include <strstream.h>

#include <Xm/Xm.h>
#include <Xm/Text.h>
#include <Xm/Form.h>
#include <Xm/DialogS.h>
#include <Xm/ScrolledW.h>
#include <Xm/PushB.h>
#include <Xm/Label.h>

#include "assert.h"
#include "guiapp.h"
#include "textbox.h"
#include "hp_exception_kludge.h"
#include <stdlib.h>
#include <stdarg.h>
#include    "handleWindowManagerDelete2.h"
#include    "popupErrorMessage.h"




static void cbDismiss( Widget wid,
                       XtPointer pClientData,
                       XtPointer pCallData ) {


   TextBox* pTextBox = (TextBox*) pClientData;

   TRY_CATCH_WRAPPER( delete pTextBox );
}



static void cbFindFirst( Widget wid,
                         XtPointer pClientData,
                         XtPointer pCallData ) {

   TextBox* pTextBox = (TextBox*) pClientData;

   TRY_CATCH_WRAPPER( pTextBox->findFirst() );
}




static void cbFindNext( Widget wid,
                        XtPointer pClientData,
                        XtPointer pCallData ) {

   TextBox* pTextBox = (TextBox*) pClientData;
   
   TRY_CATCH_WRAPPER( pTextBox->findNext() );
}



static void cbSaveToFile( Widget wid,
                          XtPointer pClientData,
                          XtPointer pCallData ) {
   TextBox* pTextBox = (TextBox*) pClientData;

   TRY_CATCH_WRAPPER( pTextBox->saveToFile() );
}





TextBox::TextBox(const char* szTitle,
                 const int nNumberOfRowsVisible,
                 const RWCString& soMessage ) :
   soMessage_( soMessage ),
   nLastFoundPosition_( RW_NPOS ),
   pToRunWhenDismissWindow_( NULL )
                 
{
   Arg aArg[20];
   int nArgs;

   assert (szTitle);

   widPopupShell_ = XtVaCreatePopupShell(     
                              szTitle,
                              topLevelShellWidgetClass,
                              GuiApp::pGetGuiApp()->widGetTopLevel(),
                              XmNtitle, szTitle,
                              XmNnoResize, False,
                              XmNautoUnmanage, False, // what is this for?
                              XmNtransient, False, // what is this for?
                              XmNdeleteResponse, XmDO_NOTHING,
                              NULL );

   handleWindowManagerDelete2( widPopupShell_, cbDismiss, this );


   Widget widForm = XtVaCreateManagedWidget( 
                              "form",
                              xmFormWidgetClass,
                              widPopupShell_,
                              XmNshadowThickness, 0,
                              XmNborderWidth, 0,
                              NULL );

   Widget widDismissButton = XtVaCreateManagedWidget( "Dismiss",
        xmPushButtonWidgetClass,
        widForm,
       XmNbottomAttachment, XmATTACH_FORM,
       XmNbottomOffset, 10,
       XmNtopOffset, 10,
       XmNleftAttachment, XmATTACH_POSITION,
       XmNleftPosition, 40,
       XmNrightAttachment, XmATTACH_POSITION,
       XmNrightPosition, 60,
       XmNtraversalOn, False,
       NULL );                                                 


   XtAddCallback( widDismissButton,
                  XmNactivateCallback,
                  cbDismiss,
                  this );


   Widget widSearchLabel = XtVaCreateManagedWidget(
       "Find (case insensitive):",
       xmLabelWidgetClass,
       widForm,
       XmNbottomAttachment, XmATTACH_WIDGET,
       XmNbottomWidget, widDismissButton,
       XmNleftAttachment, XmATTACH_FORM,
       XmNtraversalOn, False,
       NULL );

   widSearchText_ = XtVaCreateManagedWidget(
       "searchText",
       xmTextWidgetClass,
       widForm,
       XmNbottomAttachment, XmATTACH_OPPOSITE_WIDGET,
       XmNbottomWidget, widSearchLabel,
       XmNleftAttachment, XmATTACH_WIDGET,
       XmNleftWidget, widSearchLabel,
       XmNtraversalOn, True,
       XmNcolumns, 20,
       NULL );

   XtAddCallback( widSearchText_,
                  XmNactivateCallback,
                  cbFindFirst,
                  this );


   Widget widFindFirstButton = XtVaCreateManagedWidget(
       "Find First",
       xmPushButtonWidgetClass,
       widForm,
       XmNbottomAttachment, XmATTACH_OPPOSITE_WIDGET,
       XmNbottomWidget, widSearchLabel,
       XmNleftAttachment, XmATTACH_WIDGET,
       XmNleftWidget, widSearchText_,
       XmNtraversalOn, False,
       NULL );

   XtAddCallback( widFindFirstButton,
                  XmNactivateCallback,
                  cbFindFirst,
                  this );

   
   Widget widFindNextButton = XtVaCreateManagedWidget(
       "Find Next",
       xmPushButtonWidgetClass,
       widForm, 
       XmNbottomAttachment, XmATTACH_OPPOSITE_WIDGET,
       XmNbottomWidget, widSearchLabel,
       XmNleftAttachment, XmATTACH_WIDGET,
       XmNleftWidget, widFindFirstButton,
       XmNtraversalOn, False,
       NULL );

   XtAddCallback( widFindNextButton,
                  XmNactivateCallback,
                  cbFindNext,
                  this );

   
   Widget widSaveToFileButton = XtVaCreateManagedWidget(
       "Save to File",
       xmPushButtonWidgetClass,
       widForm,
       XmNbottomAttachment, XmATTACH_OPPOSITE_WIDGET,
       XmNbottomWidget, widSearchLabel,
       XmNleftAttachment, XmATTACH_WIDGET,
       XmNleftWidget, widFindNextButton,
       XmNtraversalOn, False,
       NULL );

   XtAddCallback( widSaveToFileButton,
                  XmNactivateCallback,
                  cbSaveToFile,
                  this );


   const int nDefTextBoxCols = 80;

   // create the scrolled text widget
   nArgs = 0;
   XtSetArg(aArg[nArgs], XmNtopAttachment, XmATTACH_FORM); nArgs++;
   XtSetArg(aArg[nArgs], XmNleftAttachment, XmATTACH_FORM); nArgs++;
   XtSetArg(aArg[nArgs], XmNrightAttachment, XmATTACH_FORM); nArgs++;
   XtSetArg( aArg[nArgs], XmNbottomAttachment, XmATTACH_WIDGET ); nArgs++;
   XtSetArg( aArg[nArgs], XmNbottomWidget, widSearchText_ ); nArgs++;
   XtSetArg(aArg[nArgs], XmNtopOffset,4); nArgs++;
   XtSetArg(aArg[nArgs], XmNleftOffset,4); nArgs++;
   XtSetArg(aArg[nArgs], XmNrightOffset,4); nArgs++;
   XtSetArg(aArg[nArgs], XmNscrollBarDisplayPolicy, XmAS_NEEDED); nArgs++;
   //   XtSetArg(aArg[nArgs], XmNscrollingPolicy, XmAUTOMATIC); nArgs++;
   XtSetArg(aArg[nArgs], XmNrows, nNumberOfRowsVisible ); nArgs++;
   XtSetArg(aArg[nArgs], XmNcolumns, nDefTextBoxCols); nArgs++;
   XtSetArg(aArg[nArgs], XmNeditMode, XmMULTI_LINE_EDIT); nArgs++;
   XtSetArg(aArg[nArgs], XmNeditable, False); nArgs++;
   XtSetArg(aArg[nArgs], XmNtraversalOn, False); nArgs++;
   XtSetArg(aArg[nArgs], XmNcursorPositionVisible, False); nArgs++;


   if ( !soMessage.isNull() ) {
      XtSetArg( aArg[nArgs], XmNvalue, 
                (const char*) soMessage ); ++nArgs;
   }

   widScrolledText_ = XmCreateScrolledText(widForm,
                                           "textBox", 
                                           aArg, 
                                           nArgs);
   XtManageChild(widScrolledText_);


   // there are 2 ways to bring up this dialog--either pass all data to
   // it at creation or else create it, add data, and the makeVisible
   if ( !soMessage.isNull() ) {
      XtPopup( widPopupShell_, XtGrabNone );
   }

}


// set the text box value to the current contents of the
// strstream buf and manage the dibox
void TextBox::makeVisible() { 
   Arg aArg[10];

   int nArgs = 0;
   XtSetArg(aArg[nArgs], XmNvalue, (char*) soMessage_.data()); nArgs++;
   XtSetValues(widScrolledText_, aArg, nArgs);

   XtPopup( widPopupShell_, XtGrabNone ); 
}


TextBox :: ~TextBox() {

   if ( pToRunWhenDismissWindow_ ) {
      (*pToRunWhenDismissWindow_)( pParameterToRunWhenDismissWindow_ );
   }


   XtPopdown( widPopupShell_ );
   XtDestroyWidget( widPopupShell_ );
}




void TextBox :: append( const RWCString& soAdditionalMessage ) {
   soMessage_ += soAdditionalMessage;
}


void TextBox :: appendWithArgs( char* szFormat, ... ) {

   char     szAdditionalMessage[10000];

   va_list args;
   va_start( args, szFormat );
   vsprintf( szAdditionalMessage, szFormat, args );
   va_end( args );

   soMessage_ += szAdditionalMessage;
}


void TextBox :: findFirst() {

   char* szSearchString = XmTextGetString( widSearchText_ );

   RWCString soSearchStringLowerCase( szSearchString );
   XtFree( szSearchString );

   soSearchStringLowerCase.toLower();
   
   if ( soMessageLowerCase_.isNull() ) {
      soMessageLowerCase_ = soMessage_;
      soMessageLowerCase_.toLower();
   }

   // highlight all matches

   int nFoundPos;
   int nStartPosition = 0;
   bool bFoundAMatch = false;
   while( ( nFoundPos = soMessageLowerCase_.index( soSearchStringLowerCase,
                                               nStartPosition ) ) 
          != RW_NPOS ) {

      if ( !bFoundAMatch ) {
         bFoundAMatch = true;
         nLastFoundPosition_ = nFoundPos;
      }

      
      XmTextSetHighlight( widScrolledText_, 
                          nFoundPos,
                          nFoundPos + soSearchStringLowerCase.length(),
                          XmHIGHLIGHT_SELECTED );
      nStartPosition = nFoundPos + 1;
   }


   if ( !bFoundAMatch ) {
      GuiApp::beep();
      return;
   }


   scrollWindowToLocation( nLastFoundPosition_ );
}



void TextBox :: findNext() {

   int nStartPosition = nLastFoundPosition_ + 1;

   char* szSearchString = XmTextGetString( widSearchText_ );

   RWCString soSearchStringLowerCase( szSearchString );
   XtFree( szSearchString );

   soSearchStringLowerCase.toLower();

   int nFoundPos = soMessageLowerCase_.index( 
                             soSearchStringLowerCase,
                             nStartPosition );

   nLastFoundPosition_ = nFoundPos;

   if ( nFoundPos == RW_NPOS ) {
      GuiApp::beep();
      return;
   }

   scrollWindowToLocation( nLastFoundPosition_ );

}


void TextBox :: scrollWindowToLocation( const int nCharacterPosition ) {

   // now we need to figure out where to set the top of the page so that 
   // there is some context before the highlighted word

   const int nLinesAboveFoundLine = 5;
   int nCRsFound = 0;
   int nPos = nCharacterPosition;

   while( nPos > 0 && nCRsFound <= nLinesAboveFoundLine ) {
      if ( soMessage_[ nPos ] == '\n' )
         ++nCRsFound;

      --nPos;
   }

   // 2 ways to leave the loop:
   // 1)  nPos == 0
   // 2) nCRsFound > nLinesAboveFoundLine
   // It the former, fine--set the top character to 0
   // If it is the latter, then nPos is pointing the \n in the diagram 
   // below:

   // line above the one we want to show\n
   // line we want to be top line

   // Thus we need to advance nPos by 2--one to get to the \n and 1 more
   // to get to the 'l' in 'line we want to be top line'

   if ( nCRsFound > nLinesAboveFoundLine ) 
      nPos = nPos + 2;

   XmTextSetTopCharacter( widScrolledText_, nPos );

}



void TextBox :: saveToFile() {

   RWCString soDirMask = "*.*";
   RWCString soTryOutFileName = "info.txt";
   
   FileName soUserOutFileName = 
      GuiApp::popupFileSelector( "Save info to file",
                                 soDirMask,
                                 soTryOutFileName );

   // if user cancelled or did not supply name, bail out now
   if ( soUserOutFileName.isNull() ) return;

   bool bGoAhead = true;
   if ( soUserOutFileName.bFileByThisNameExists() ) {
      bGoAhead = 
         GuiApp::popupDecisionMessage( "File %s exists. Save anyway?",
                                       (const char*) soUserOutFileName );

   }

   if ( ! bGoAhead ) return;

   try {
      saveToFile2( soUserOutFileName );
   }
   catch (SysRequestFailed srf) {
      popupErrorMessage( srf.szGetDesc() );
   }
}


void TextBox :: saveToFile2(const FileName& soUserOutFileName ) {
   
   FILE* pOut = fopen( (const char*) soUserOutFileName, "w" );

   if ( !pOut ) {
      PANIC_OST( ost ) << "Unable to open file " << soUserOutFileName
          << " for writing" << endl << ends;
      SysRequestFailed srf(ost.str());
      // what the hell, show 'em the errno
      srf.includeErrnoDescription();
      throw srf;
   }

   for( int n = 0; n < soMessage_.length(); ++n )
      putc( soMessage_[n], pOut );

   fclose( pOut );
}


void TextBox :: raiseWindow() {

   XtMapWidget( widPopupShell_ );

   XRaiseWindow(
                XtDisplay( widPopupShell_ ),
                XtWindow( widPopupShell_ )
                );


}