//////////////////////////////////////////////////////////////////////
//
//  DATADLG.PRG
//
//  Copyright:
//      Alaska Software GmbH, (c) 1997-1999. Alle Rechte vorbehalten.         
//  
//  Inhalt:
//      Klassen DataDialog und DataDialogMenu
//////////////////////////////////////////////////////////////////////

#include "Gra.ch"
#include "Xbp.ch"
#include "Dmlb.ch"
#include "Common.ch"
#include "Appevent.ch"
#include "Mdidemo.ch"

/*
 * Klassendeklaration 
 */
CLASS DataDialog FROM XbpDialog
   PROTECTED:
     VAR appendMode              // Wird ein neuer Datensatz angelegt?
     VAR editControls            // XBPs, die Daten „ndern
     VAR appendControls          // XBPs, die nur bei APPEND
                                 // aktiv sind
     VAR keyControls             // Liste von XBPs fr Tastaturnavigation
     VAR keyboardBlock           // Codeblock fr xbeP_Keyboard Ereignisse
   EXPORTED:
     VAR area      READONLY      // aktueller Arbeitsbereich
     VAR newTitle                // Codeblock um Titel zu „ndern
     VAR contextMenu             // Kontextmen fr Datendialog
     VAR windowMenu              // Dynamisches Fenstermen
                                 // im Anwendungsfenster
     VAR currentControl          // Aktuelles Edit Control
     VAR seekOrder               // Daten fr die Suche im
     VAR seekExpr                // SeekDialog
     VAR seekFields
     VAR seekTitle

     METHOD init                 // Lebenszyklus
     METHOD create
     METHOD configure
     METHOD destroy
     METHOD addEditControl       // XBP fr Edit registrieren
     METHOD addAppendControl     // XBP fr Append registrieren
     METHOD addKeyControl        // XBP fr Keyboard handling registrieren
     METHOD notify               // Nachricht von DBO verarbeiten
     METHOD readData             // Daten einlesen
     METHOD validateAll          // Daten validieren
     METHOD writeData            // Daten schreiben
     METHOD isIndexUnique        // Indexwert prfen
     METHOD setDisplayFocus      // Dialog hat Fokus bekommen
     METHOD keyHandler           // Tastatursteuerung fr Edit controls
ENDCLASS



/*
 * Datendialog initialisieren
 */
METHOD DataDialog:init( oParent, oOwner , ;
                        aPos   , aSize  , ;
                        aPParam, lVisible )
   LOCAL aPSize

   DEFAULT lVisible TO .F., ;
           oParent  TO SetAppWindow()

  /*
   * Ohne Angabe einer Position wird das Fenster zentriert angezeigt
   */
   IF aPos == NIL
      aPSize := oParent:currentSize()
      aPos   := { (aPSize[1] - aSize[1]) / 2, ;
                  (aPSize[2] - aSize[2]) / 2  }
   ENDIF

   ::xbpDialog:init( oParent, oOwner, ;
                     aPos   , aSize , ;
                     aPParam, lVisible )

   ::maxButton       := .F.
   ::area            := 0
   ::border          := XBPDLG_THINBORDER
   ::editControls    := {}
   ::appendControls  := {}
   ::keyControls     := {}
   ::keyboardBlock   := {|nKey,x,obj| ::keyHandler( nKey, obj ) }
   ::appendMode      := .F.
   ::newTitle        := {|obj| obj:getTitle() }

RETURN self



/*
 * Systemresourcen anfordern
 * und DataDialog im aktuellen Arbeitsbereich registrieren
 */
METHOD DataDialog:create( oParent, oOwner , ;
                          aPos   , aSize  , ;
                          aPParam, lVisible )

   ::xbpDialog:create( oParent, oOwner , ;
                       aPos   , aSize  , ;
                       aPParam, lVisible )

   ::appendMode      := Eof()
   ::area            := Select()

   ::close           := {|mp1,mp2,obj| obj:destroy() }

   DbRegisterClient( self )

RETURN self



/*
 * Systemresourcen konfigurieren
 * und Dialog ggf. in neuem Arbeitsbereich registrieren
 */
METHOD DataDialog:configure( oParent, oOwner , ;
                             aPos   , aSize  , ;
                             aPParam, lVisible )
   LOCAL lRegister := (::area <> Select())

   ::xbpDialog:configure( oParent, oOwner , ;
                          aPos   , aSize  , ;
                          aPParam, lVisible )
   IF lRegister
      (::area)->( DbDeRegisterClient( self ) )
   ENDIF

   ::area       := Select()
   ::appendMode := Eof()

   IF lRegister
      DbRegisterClient( self )
   ENDIF

RETURN self



/*
 * Systemresourcen freigeben und DataDialog entregistrieren
 */
METHOD DataDialog:destroy()

  /*
   * Aufruf ignorieren wenn das Fenster schon im INIT Status ist.
   */
   IF ::Status() == XBP_STAT_INIT
      RETURN self
   ENDIF

   ::writeData()
   ::hide()
   (::Area)->( DbCloseArea() )

  /*
   * Eintrag im Fenstermen l”schen
   */
   IF ! Empty( ::windowMenu )
      ::windowMenu:delItem( self ) //
      ::windowMenu := NIL
   ENDIF

  /*
   * Referenzen auf den DataDialog und das Kontextmen l”schen
   */
   IF ! Empty( ::contextMenu )
      ::contextMenu:cargo := NIL
      ::contextMenu       := NIL
   ENDIF

  /*
   * Systemresourcen freigeben und Instanzvariablen
   * auf die Werte im Zustand :init() zurcksetzen
   */
   ::xbpDialog:destroy() 
   ::Area           :=  0
   ::appendMode     := .F.
   ::editControls   := {} 
   ::appendControls := {}
   ::newTitle       := {|obj| obj:getTitle() }

RETURN self



/*
 * Notify Methode:
 *   - Vor Bewegen des Satzzeigers, Daten schreiben
 *   - Nach Bewegen des Satzzeigers, Daten neu einlesen
 */
METHOD DataDialog:notify( nEvent, mp1, mp2 )

   IF nEvent <> xbeDBO_Notify
      RETURN self                         // ** RETURN **
   ENDIF

   DbSuspendNotifications()
   DO CASE
   CASE mp1 == DBO_MOVE_PROLOG            // Satzzeiger wird bewegt
      ::writeData()

   CASE mp1 == DBO_MOVE_DONE .OR. ;       // Skip ist beendet       
        mp1 == DBO_GOBOTTOM  .OR. ;      
        mp1 == DBO_GOTOP                 
      ::readData()

      DO CASE
      CASE Empty( ::contextMenu )
      CASE Eof()                          // Eintr„ge im Kontextmen
         ::contextMenu:disableBottom()    // verfgbar machen oder
         ::contextMenu:disableEof()       // sperren, je nachdem
      CASE mp1 == DBO_GOBOTTOM            // wo der Satzzeiger sich
         ::contextMenu:enableAll()        // befindet.
         ::contextMenu:disableBottom()
      CASE mp1 == DBO_GOTOP
         ::contextMenu:enableAll()
         ::contextMenu:disableTop()
      OTHERWISE
         ::contextMenu:enableAll()
      ENDCASE

      DO CASE
      CASE Eof()                               // Eintr„ge im Bearbeiten-Men
         WinMenu():editMenu:disableBottom()    // des Fensters sperren
         WinMenu():editMenu:disableEof()       // oder verfgbar machen
      CASE mp1 == DBO_GOBOTTOM                 // je nachdem, wo sich
         WinMenu():editMenu:enableAll()        // der Satzzeiger befindet
         WinMenu():editMenu:disableBottom()
      CASE mp1 == DBO_GOTOP
         WinMenu():editMenu:enableAll()
         WinMenu():editMenu:disableTop()
      OTHERWISE
         WinMenu():editMenu:enableAll()
      ENDCASE

   ENDCASE
   DbResetNotifications()

RETURN self



/*
 * EditControl an Liste anfgen
 */
METHOD DataDialog:addEditControl( oXbp )
   IF AScan( ::editControls, oXbp ) == 0
      AAdd(  ::editControls, oXbp )
      ::addKeyControl( oXbp )
   ENDIF
RETURN self



/*
 * AppendControl an Liste anfgen
 */
METHOD DataDialog:addAppendControl( oXbp )
   IF AScan( ::appendControls, oXbp ) == 0
      AAdd(  ::appendControls, oXbp )
      ::addKeyControl( oXbp )
   ENDIF
RETURN self



/*
 * XBP an Liste fr die Tastatursteuerung anfgen. Jeder XBP bekommt
 * den gleichen :keyBoard Codeblock. Er wird nach dem Ereignis
 * xbeP_Keyboard ausgewertet und ruft einen benutzerdefinierten
 * Tastatur-Handler
 */
METHOD DataDialog:addKeyControl( oXbp )
   IF AScan( ::keyControls, oXbp ) == 0
      AAdd(  ::keyControls, oXbp )

      oXbp:keyBoard := ::keyboardBlock
   ENDIF
RETURN self



/*
 * Fenster hat den Fokus bekommen.
 * Workarea ausw„hlen, Eintrag im Fenstermen markieren und Fokus
 * auf das aktuelle Edit Control setzen
 */
METHOD DataDialog:setDisplayFocus

   DbSelectArea( ::area )

   IF ::windowMenu <> NIL
      ::windowMenu:checkItem( self )
   ENDIF

   IF ::currentControl <> NIL
      SetAppFocus( ::currentControl )
   ENDIF
RETURN self



/*
 * Benutzerdefinierte Tastaturverarbeitung
 */
METHOD DataDialog:keyHandler( nKey, oXbp )
   LOCAL i:=AScan( ::keyControls, oXbp )

   DO CASE
   CASE i == 0
   CASE oXbp:isDerivedFrom( "XbpMLE" ) .OR. oXbp:isDerivedFrom( "XbpListbox" )
     // Return, PageUp, PageDown werden zum Editieren von MLE und Listbox
     // verwendet. In dem Fall werden die Tasten hier ignoriert.

   CASE nKey == xbeK_RETURN
      IF ++i > Len( ::keyControls )
         i := 1
      ENDIF
      DO WHILE ! ::keyControls[i]:isenabled()
         i ++
      ENDDO
      SetAppFocus( ::keyControls[i] )

   CASE nKey == xbeK_PGDN
      IF ! Eof()
         SKIP
         IF Eof()
            DbSuspendNotifications()
            SKIP -1                    // Append-Situation vermeiden
            DbResetNotifications()
            DbGobottom()               // Notification ausl”sen
         ENDIF
      ENDIF

   CASE nKey == xbeK_PGUP
      IF ! Bof()
         SKIP -1
      ENDIF

   ENDCASE
RETURN self



/*
 * Datensatz lesen und Daten in EditControls einlesen
 */
METHOD DataDialog:readData()
   LOCAL i, imax  := Len( ::editControls )

   FOR i:=1 TO imax                       // Daten aus Datei in
      ::editControls[i]:setData()         // XBPs bertragen
   NEXT

   Eval( ::newTitle, self )               // Neuer Fenstertitel

   IF Eof()                               // XBPs aktivieren oder
      IF ! ::appendMode                   // deaktivieren, die nur
         imax  := Len( ::appendControls ) // bei APPEND zug„nglich
         FOR i:=1 TO imax                 // sein drfen
            ::appendControls[i]:enable()  // Eof() ist erreicht
         NEXT                             // XBPs entsperren
      ENDIF
      ::appendMode := .T.
   ELSEIF ::appendMode                    // Satzzeiger wurde von
      imax  := Len( ::appendControls )    // Eof() auf existierenden
      FOR i:=1 TO imax                    // Datensatz bewegt.
         ::appendControls[i]:disable()    // XBPs wieder sperren
      NEXT   
      ::appendMode := .F.   
   ENDIF   

RETURN



/*
 * Daten der EditControls in eine Datei schreiben
 */
METHOD DataDialog:writeData()
   LOCAL i, imax
   LOCAL lLocked   := .F. , ;       // Ist Satzsperre gesetzt?
         lAppend   := .F. , ;       // Ist Datensatz neu?
         aChanged  := {}  , ;       // XBPs mit ge„nderten Daten
         nOldArea  := Select()      // Aktueller Bereich

   dbSelectArea( ::area )

   IF Eof()                         // Neuen Datensatz anlegen
      IF ::validateAll()            // Daten vor Append validieren
         APPEND BLANK
         lAppend  := .T.
         aChanged := ::editControls // Alle XBPs k”nnen ge„ndert sein
         lLocked  := ! NetErr()     // Automatische Sperre
      ELSE                        
         MsgBox("Ungltige Daten")  // Keine ungltigen Daten
         DbSelectArea( nOldArea )   // in neuen Datensatz schreiben
         RETURN .F.                 // *** ZURšCK ***
      ENDIF
   ELSE
      imax := Len( ::editControls ) // Feststellen, welche Daten
      FOR i:=1 TO imax              // ge„ndert wurden
         IF ::editControls[i]:changed
            AAdd( aChanged, ::editControls[i] )
         ENDIF
      NEXT

      IF Empty( aChanged )          // Keine Žnderung -> 
         DbSelectArea( nOldArea )   // keine Satzsperre n”tig
         RETURN .T.                 // *** ZURšCK ***
      ENDIF

      lLocked := DbRLock( Recno() ) // Datensatz sperren
   ENDIF

   IF ! lLocked
      MsgBox( "Datensatz ist zur Zeit gesperrt" )
      DbSelectArea( nOldArea )      // keine Satzsperre m”glich
      RETURN .F.                    // *** ZURšCK ***
   ENDIF

   imax := Len( aChanged )          // Schreibzugriff nur fr
   FOR i:=1 TO imax                 // ge„nderte Daten
      IF ! lAppend                  // erforderlich
         IF ! aChanged[i]:validate()
            aChanged[i]:undo()      // ungltige Daten ! 
            LOOP                    // Original wiederherstellen
         ENDIF                      // und n„chsten XBP validieren
      ENDIF   
      aChanged[i]:getData()         // Daten aus XBP holen und
   NEXT                             // in Datei schreiben    

   DbCommit()                       // Datenpuffer wegschreiben
   DbRUnlock( Recno() )             // Sperre aufheben
                                    // Dialogelemente nach Append
   IF ::appendMode                  // wieder sperren
      imax  := Len( ::appendControls )         
      FOR i:=1 TO imax
         ::appendControls[i]:disable()
      NEXT
      ::appendMode := .F.

      IF ! Empty( ::contextMenu )
         ::contextMenu:disableBottom()
         ::contextMenu:enableEof()
      ENDIF
      WinMenu():editMenu:disableBottom()
      WinMenu():editMenu:enableEof()
   ENDIF

   DbSelectArea( nOldArea )

RETURN .T.



/*
 * Daten aus allen EditControls validieren
 * Wird bei APPEND gebraucht bevor ein neuer Datensatz angefgt wird
 */
METHOD DataDialog:validateAll()
   LOCAL i := 0, imax := Len( ::editControls )
   LOCAL lValid := .T.

   DO WHILE ++i <= imax .AND. lValid
      lValid := ::editControls[i]:validate()
   ENDDO

RETURN lValid



/*
 * Prfen, ob ein Indexeintrag noch nicht vorhanden ist 
 */
METHOD DataDialog:isIndexUnique( nOrder, xIndexValue )
   LOCAL nOldOrder := OrdNumber()
   LOCAL nRecno    := Recno()
   LOCAL lUnique   := .F.

   DbSuspendNotifications()         // Keine Nachricht vom DBO
                                    // an self bei DbSeek() !!!
   OrdSetFocus( nOrder )

   lUnique := .NOT. DbSeek( xIndexValue )

   OrdSetFocus( nOldOrder )
   DbGoTo( nRecno )

   DbResetNotifications()

RETURN lUnique



/*
 * Klassendeklaration fr Kontextmen
 */
CLASS DataDialogMenu FROM XbpMenu
   PROTECTED:
      VAR disabledItems
      METHOD disable
      METHOD enable

   EXPORTED:
      VAR disableTop
      VAR disableBottom
      VAR disableEof

      METHOD init, create
      METHOD goTop, goBottom, skip, seek, append, delete
      METHOD enableAll
      METHOD enableTop
      METHOD enableBottom
      METHOD enableEof
      METHOD disableTop
      METHOD disableBottom
      METHOD disableEof
ENDCLASS



/*
 * Instanzvariablen initialisieren
 */
METHOD DataDialogMenu:init( oParent, aPresParam, lVisible )
   ::xbpMenu:init( oParent, aPresParam, lVisible )
   ::disabledItems := {}

   ::xbpMenu:title := "~Bearbeiten"

   // Meneintr„ge, die nach Bof() oder GoTop() gesperrt werden
   ::disableTop    := { 6, 9 }

   // Meneintr„ge, die nach GoBottom() gesperrt werden
   ::disableBottom := { 7, 10 }

   // Meneintr„ge, die bei Eof() gesperrt werden
   ::disableEof    := { 1, 2, 3 }

RETURN self



/*
 * Meneintr„ge initialisieren
 */
METHOD DataDialogMenu:create( oParent, aPresParam, lVisible )
   ::xbpMenu:create( oParent, aPresParam, lVisible )

   ::addItem( { "~Neu"       , {|| ::append()             } } )
   ::addItem( { "~Suchen"    , {|| ::seek()               } } )
   ::addItem( { "~L”schen"   , {|| ::delete()             } } )
   ::addItem( { "S~ichern"   , {|| WinMenu():currentWin:writeData() } } )

   ::addItem( MENUITEM_SEPARATOR )

   ::addItem( { "Zum ~Anfang", {|| ::goTop()    } } )
   ::addItem( { "Zum ~Ende"  , {|| ::gobottom() } } )

   ::addItem( MENUITEM_SEPARATOR )

   ::addItem( { "~Zurck"    , {|| ::skip(-1) } } )

   ::addItem( { "~Vor"       , {|| ::skip( 1) } } )

RETURN self



/*
 * Einige Standard-Datenbankoperationen
 */
METHOD DataDialogMenu:goTop
   (WinMenu():currentWin:area)->(DbGotop())
RETURN self

METHOD DataDialogMenu:skip( nSkip )
   LOCAL nOldArea := Select()
   LOCAL nArea    := WinMenu():currentWin:area

   DEFAULT nSkip TO 1

   DbSelectArea( nArea )
   DbSkip(nSkip)
   IF Eof()
      DbSuspendNotifications()
      SKIP -1                          // Append-Situation vermeiden
      DbResetNotifications()
      DbGobottom()                     // Notification ausl”sen
   ENDIF

   DbSelectArea( nOldArea )
RETURN self

METHOD DataDialogMenu:goBottom
   (WinMenu():currentWin:area)->(DbGobottom())
RETURN self

METHOD DataDialogMenu:seek
   SeekRecord()
RETURN self

METHOD DataDialogMenu:append
   (WinMenu():currentWin:area)->(DbGoto(Lastrec()+1))
RETURN self

METHOD DataDialogMenu:delete
   DeleteRecord()
RETURN self



/*
 * Alle Meneintr„ge wieder verfgbar machen
 */
METHOD DataDialogMenu:enableAll
   LOCAL i, imax

   IF ! Empty( ::disabledItems )
      imax := Len( ::disabledItems )

      FOR i:=1 TO imax
         ::enableItem( ::disabledItems[i] )
      NEXT
     ::disabledItems := {}
   ENDIF

RETURN self



/*
 * Bestimmte Meneintr„ge wieder verfgbar machen
 */
METHOD DataDialogMenu:enable( aItems )
   LOCAL i, imax, j, jmax

   IF ! Empty( aItems )
      imax := Len( aItems )
      jmax := Len( ::disabledItems )

      FOR i:=1 TO imax
         j := AScan( ::disabledItems, aItems[i] )

         IF j > 0
            ::enableItem( aItems[i] )
            ADel( ::disabledItems, j )
            jmax--
         ENDIF
      NEXT

      ASize( ::disabledItems, jmax )
   ENDIF

RETURN self



/*
 * Bestimmte Meneintr„ge sperren
 */
METHOD DataDialogMenu:disable( aItems )
   LOCAL i, imax

   IF ! Empty( aItems )
      imax := Len( aItems )

      FOR i:=1 TO imax
         IF AScan( ::disabledItems, aItems[i] ) == 0
            ::disableItem( aItems[i] )
            AAdd( ::disabledItems, aItems[i] )
         ENDIF
      NEXT
   ENDIF

RETURN self



/*
 * Meneintr„ge verfgbar machen, 
 * die bei nach GoTop() ausgew„hlt werden drfen
 */
METHOD DataDialogMenu:enableTop
RETURN ::enable( ::disableTop )



/*
 * Meneintr„ge verfgbar machen, 
 * die nach Gobottom() ausgew„hlt werden drfen
 */
METHOD DataDialogMenu:enableBottom
RETURN ::enable( ::disableBottom )



/*
 * Meneintr„ge verfgbar machen, 
 * die bei Eof() ausgew„hlt werden drfen
 */
METHOD DataDialogMenu:enableEof
RETURN ::enable( ::disableEof )



/*
 * Meneintr„ge sperren,
 * die nach GoTop() oder Bof() nicht ausgew„hlt werden drfen
 */
METHOD DataDialogMenu:disableTop
RETURN ::disable( ::disableTop )



/*
 * Meneintr„ge sperren,
 * die nach GoBottom() nicht ausgew„hlt werden drfen
 */
METHOD DataDialogMenu:disableBottom
RETURN ::disable( ::disableBottom )



/*
 * Meneintr„ge sperren,
 * die bei Eof() nicht ausgew„hlt werden drfen
 */
METHOD DataDialogMenu:disableEof
RETURN ::disable( ::disableEof )
