Page 1 of 1

Possible bug with UPDATE option, Possible Bug in my code

Posted: Sun Jun 15, 2008 11:22 am
by xProgrammer
Hi Antonio

I have extended my PRINTERS class to include a DIALOG to select a printer. I decided to include an option in the dialog to refresh the list of available printers. However if I try to use that option I get an error. The error appears to be that one (at least) of the controls on my dialog doesn't have a logical value in its update property. At least that's my guess from looking at error.log.

I have reproduced the fault with the following cut down code:

Code: Select all

#include "hbclass.ch"
#include "FiveLinux.ch"

STATIC oWnd

FUNCTION Main( xHomeDir, xDataDir)

PUBLIC oPRINTERS
oPRINTERS := PRINTERS():New()
DEFINE WINDOW oWnd TITLE ( "Test Dialog Update" ) MENU BldInMenu()
ACTIVATE WINDOW oWnd MAXIMIZED

RETURN nil

FUNCTION BldInMenu()

LOCAL lomMenu

MENU lomMenu
   MENUITEM "_File"
      MENU
         MENUITEM "_Get Printers" ACTION TestPrinters()
         MENUITEM "e_Xit" ACTION oWnd:End()
      ENDMENU
ENDMENU

RETURN lomMenu

FUNCTION TestPrinters()

LOCAL sPrinter
LOCAL sPort

IF oPRINTERS:Select( "Test Document", @sPrinter, @sPort )
   MsgInfo( "Print on " + sPrinter + " on port " + sPort )
  ELSE
   MsgInfo( "Printing cancelled" )
ENDIF

RETURN nil


CLASS PRINTERS

DATA aNames           /** array of printer names */
DATA aPorts           /** array of printer ports */
DATA iCount           /** number of printers installed */
DATA sDefault         /** name of default printer */
DATA iDefault         /** number of default printer - index into aNames and aPorts */
DATA lDefault         /** is there a default printer */
DATA lExists          /** is any printer defined */
DATA lOSQueried       /** has the OS been queried about available printers */
DATA brw_PRINTER_LIST /** Printer List BROWSE object on Printer Selection DIALOG */
DATA btn_PRINT        /** Print button on Printer Selection DIALOG */

METHOD New( lGetList ) CONSTRUCTOR
METHOD GetList()
METHOD Select( psJobName, psPrinter, psPort )

ENDCLASS


METHOD New( plGetList ) CLASS PRINTERS

/** _syntax: PRINTERS():New( [<lGetList>] )
    _arguments: 
                  <lGetList> a logical value  
                      If .T. the operating system is queried and printer lists and defaults are set as part of the object's initialisation.  
                      Defaults to .T.
    _returns:     the created PRINTERS object
    _description: see METHOD GetList() for a description of how the operating system is queried  */

IF PCOUNT() < 1
   lGetList := .T.
  ELSE
   lGetList := plGetList
ENDIF
::lOSQueried := .F.
IF lGetList
   ::GetList()
ENDIF

RETURN self


METHOD GetList() CLASS PRINTERS

/** _syntax:      <PRINTERS_object>:GetList()
    _arguments:   none
    _returns:     nil
    _description: Obtains information on installed printers from the operating system by RUNning lpstat -s > lpstat.txt.
                  Opens the redirected output of the above command and parses that file building a list of printernames (in ::aNames)
                      and ports (in ::aPorts).
                  Retrieves the name of the default printer (in ::sDefault) and determines its position (in ::iDefault)
                  If there is no default printer ::lDefault is set to .F.
                  Determines if there are any printers installed (in ::lExists) and how many are installed (in ::iCount) */

LOCAL iFileHandle    /** file handle for reading output of lpstat command */
LOCAL sPrinterLine   /** line buffer for reading output of lpstat command */
LOCAL sTest          /** buffer for testing format of lines of lpstat command output */
LOCAL iEndOffset     /** work variable for splitting out printer name and port data */
LOCAL lDefaultFound  /** work variable for flagging that default printer name has been found */
LOCAL ii             /** work variable loop counter */

::aNames := ARRAY( 0 )
::aPorts := ARRAY( 0 )
::iCount := 0
::sDefault := ""
::iDefault := 0
::lDefault := .F.
::lExists := .F.

RUN lpstat -s > lpstat.txt
iFileHandle := FOpen( "lpstat.txt" )
DO WHILE HB_FReadLine( iFileHandle, @sPrinterLine, CHR(10) ) = 0
   sTest := SUBSTR( sPrinterLine, 1, 11 )
   IF sTest = "device for "
      sTest := SUBSTR( sPrinterLine, 12 )
      iEndOffset := AT( ": ", sTest )
      IF iEndOffset > 0
         AAdd( ::aNames, ALLTRIM( SUBSTR( sTest, 1, iEndOffset - 1 ) ) )
         AAdd( ::aPorts, SUBSTR( sTest, iEndOffset + 2 ) )
         ::iCount += 1
         ::lExists := .T.
      ENDIF
     ELSE
      sTest := SUBSTR( sPrinterLine, 1, 27 )
      IF sTest = "system default destination:"
         ::sDefault := ALLTRIM( SUBSTR( sPrinterLine, 28 ) )
         ::lDefault := .T.         
      ENDIF
   ENDIF
ENDDO
FClose( iFileHandle )
IF ::lDefault
   FOR ii = 1 TO ::iCount
      IF ::aNames[ii] = ::sDefault
         ::iDefault := ii
         EXIT
      ENDIF
   NEXT
ENDIF
::lOSQueried := .T.

RETURN nil


METHOD Select( psJobName, psPrinter, psPort )

/** _syntax:      <PRINTERS_object>:Select( <JobName>, @<Printer, @<Port> )
    _parameters:  <JobName> a string
                      Name of the job(s) to be printed.
                      Gets displayed in the title of the dialog
                  <Printer> a string (passed by reference)
                      This gets updated with the name of the selected printer
                  <Port> a string (passed by reference)
                      This gets updated with the port of the selected printer
    _returns:     a logical value.
                      .T. indicates that the user has selected a printer and wishes to proceed
                      .F. indicates that the user wishes to cancel
    _description: a dialog enabling the user to select a printer for (a) print job(s) or to cancel the print request
                      A printer can be selected by either: 
                          double left clicking on it, 
                          or highlighting it and clicking on the print button
                      The function gives an informational message if no printers are installed.    
                      The function checks that a printer list has been created, if not it invokes ::GetList()
                      The user can get the printer list refreshed by clicking on the Refresh button  */

LOCAL dlg_SELECT_PRINTER   /** Select Printer Dialog */
LOCAL sTitle               /** Dialog Title */            
LOCAL lRetCode             /** Function Return Code */

IF !::lOSQueried
   ::GetList()
ENDIF
sTitle := "Select Printer for Printing " + psJobName 
DEFINE DIALOG dlg_SELECT_PRINTER TITLE sTitle SIZE 800, 500
IF ::iCount < 1
   @  30,  10 SAY "No printers currently installed"
  ELSE
   @  3, 1 BROWSE ::brw_PRINTER_LIST ;
      FIELDS ::aNames[::brw_PRINTER_LIST:nAt], ::aPorts[::brw_PRINTER_LIST:nAt] ;
      HEADERS "Printer", "Port" SIZE 550, 400 OF dlg_SELECT_PRINTER UPDATE
   ::brw_PRINTER_LIST:bLDblClick := { | nRow, nCol | ::btn_PRINT:Click() }
   BrwSetColWidths( ::brw_PRINTER_LIST , { 200, 350 } )
   ::brw_PRINTER_LIST:SetArray( ::aNames )
ENDIF
@  30, 600 BUTTON ::btn_PRINT PROMPT "Print" WHEN ::lExists ;
   ACTION ( psPrinter := ::aNames[::brw_PRINTER_LIST:nAt], psPort := ::aPorts[::brw_PRINTER_LIST:nAt], lRetCode := .T., dlg_SELECT_PRINTER:End() ) ;
   PIXEL OF dlg_SELECT_PRINTER
@  90, 600 BUTTON "Refresh" ACTION ( ::GetList(), dlg_SELECT_PRINTER:Update() ) PIXEL OF dlg_SELECT_PRINTER
@ 240, 600 BUTTON "Cancel" ACTION ( lRetCode := .F., dlg_SELECT_PRINTER:End() ) PIXEL OF dlg_SELECT_PRINTER
ACTIVATE DIALOG dlg_SELECT_PRINTER CENTERED

RETURN lRetCode

FUNCTION BrwSetColWidths( oBrw, aWidths )

iLastWidth := LEN( aWidths )
FOR iCount = 1 TO iLastWidth
   oBrw:aColumns[iCount]:nWidth := aWidths[iCount]
NEXT

RETURN nil
Herewith the error.log:

Code: Select all

Application
===========
   Path and name: ./PRINTERS (32 bits)
   Time from start: 0 hours 0 mins 16 secs 
   Error occurred at: 06/15/08, 21:06:19
   Error description: Error BASE/1066
   Argument error: conditional
   Args:
     [   1] = U   nil

Stack Calls
===========
   Called from (b)TWINDOW(79)
   Called from AEVAL(0)
   Called from (b)TWINDOW:TWINDOW(79)
   Called from TDIALOG:UPDATE(0)
   Called from (b)PRINTERS:SELECT(186)
   Called from TBUTTON:CLICK(149)
   Called from TBUTTON:HANDLEEVENT(158)
   Called from _FLH(241)
   Called from WINRUN(0)
   Called from TDIALOG:ACTIVATE(71)
   Called from PRINTERS:SELECT(188)
   Called from TESTPRINTERS(34)
   Called from (b)BLDINMENU(22)
   Called from TMENU:COMMAND(60)
   Called from TWINDOW:HANDLEEVENT(182)
   Called from _FLH(241)
   Called from WINRUN(0)
   Called from TWINDOW:ACTIVATE(134)
   Called from MAIN(11)

Variables in use
================
   Procedure     Type   Value
   ==========================
   (b)TWINDOW
     Param   1:    O    Class: TSCROLLBAR
     Param   2:    N    2
     Local   1:    U    nil
     Local   2:    U    nil
     Local   3:    N    0
   AEVAL
     Param   1:    A    Len:    6
     Param   2:    B    {|| ... }
   (b)TWINDOW:TWINDOW
     Param   1:    O    Class: TDIALOG
   TDIALOG:UPDATE
   (b)PRINTERS:SELECT
     Param   1:    O    Class: TBUTTON
   TBUTTON:CLICK
     Local   1:    O    Class: TBUTTON
   TBUTTON:HANDLEEVENT
     Param   1:    N    1
     Param   2:    N    0
     Param   3:    N    0
     Local   1:    O    Class: TBUTTON
   _FLH
     Param   1:    N    1
     Param   2:    N    0
     Param   3:    N    0
     Param   4:    N    21
     Local   1:    O    Class: TBUTTON
   WINRUN
     Local   1:    U    nil
     Local   2:    U    nil
     Local   3:    U    nil
     Local   4:    U    nil
   TDIALOG:ACTIVATE
     Param   1:    L    .T.
     Param   2:    U    nil
     Param   3:    U    nil
     Param   4:    U    nil
     Param   5:    L    .T.
     Param   6:    U    nil
     Local   1:    O    Class: TDIALOG
     Local   2:    N    7
   PRINTERS:SELECT
     Param   1:    C    "Test Document"
     Param   2:    U    nil
     Param   3:    U    nil
     Local   1:    O    Class: PRINTERS
     Local   2:    O    Class: TDIALOG
     Local   3:    C    "Select Printer for Printing Test Document"
     Local   4:    U    nil
   TESTPRINTERS
     Local   1:    U    nil
     Local   2:    U    nil
   (b)BLDINMENU
     Param   1:    O    Class: TMENUITEM
   TMENU:COMMAND
     Param   1:    N    135782784
     Local   1:    O    Class: TMENU
     Local   2:    O    Class: TMENUITEM
   TWINDOW:HANDLEEVENT
     Param   1:    N    3
     Param   2:    N    135782784
     Param   3:    N    0
     Local   1:    O    Class: TWINDOW
     Local   2:    U    nil
   _FLH
     Param   1:    N    3
     Param   2:    N    135782784
     Param   3:    N    0
     Param   4:    N    1
     Local   1:    O    Class: TWINDOW
   WINRUN
     Local   1:    U    nil
     Local   2:    U    nil
     Local   3:    U    nil
     Local   4:    U    nil
     Local   5:    U    nil
     Local   6:    U    nil
     Local   7:    U    nil
   TWINDOW:ACTIVATE
     Param   1:    U    nil
     Param   2:    U    nil
     Param   3:    U    nil
     Param   4:    L    .T.
     Param   5:    U    nil
     Local   1:    O    Class: TWINDOW
   MAIN
     Local   1:    U    nil
     Local   2:    U    nil

Linked RDDs
===========
   DBF
   DBFFPT
   DBFNTX
   DBFBLOB

DataBases in use
================

Classes in use:
===============
     1 HASHENTRY
     2 HBCLASS
     3 HBOBJECT
     4 PRINTERS
     5 TWINDOW
     6 TMENU
     7 TMENUITEM
     8 TDIALOG
     9 TCONTROL
    10 TWBROWSE
    11 TWBCOLUMN
    12 TSCROLLBAR
    13 TBUTTON
    14 ERROR

Memory Analysis
===============
      24 Static variables

   Dynamic memory consume:
      Actual  Value:          0 bytes
      Highest Value:          0 bytes
Your help would be appreciated as the UPDATE option would be very handy for the code I have to write in the near future, even though it could be removed from this particular class with little loss of utility.

Thanks
Doug
(xProgrammer)

Posted: Sun Jun 15, 2008 9:53 pm
by xProgrammer
Hi Antonio

I realise, upon reflection, that the above code would need to be changed to function as intended where upon requerying the printer list there is a change from or to no installed printers. Such a change may well use a technique other than dialog:update(). Nonetheless, the above code should be able to be made to run even if some of its output isn't exactly "correct". The technique behind it will be important for some of the code that I have to write in the very near future.

Thanks
Doug
(xProgrammer)

Posted: Sun Jun 15, 2008 10:25 pm
by xProgrammer
Hi Antonio

I have included comments in the above PRINTERS class in a format that I hope can be processed by a documentation engine. I don't know how much interest there would be in this group in developing one, but it could be very helpful. Using /** at the start of a comment to indicate that it should be included comes from the gnome documentation standard. The _syntax:, _arguments:, _return:, _description: "headers" correspond to the xHarbour and Harbour documentation.

Regards
Doug
(xProgrammer)

Posted: Wed Jun 18, 2008 10:05 am
by xProgrammer
Hi Antonio

The problem appears to be that the object constructor of the TScrollbar class doesn't initialise ::lUpdate. I added the following line to TScrollbar:New()

Code: Select all

   ::lUpdate := .F. 


and rebuilt the library and the problem disappeared.

::lUpdate is inherited from TControl. Is there anything we can do in TControl to ensure that all its children set a logical value for ::lUpdate?

::lUpdate needs to contain a logical value for all controls on a window / dialog for the Window:Update() method to work:

Code: Select all

   METHOD Update() INLINE ;
      AEval( ::aControls, { |o| If( o:lUpdate, o:Refresh(),) } )
I guess the alternative would be to change the Update() method along the following lines:

Code: Select all

   METHOD Update() INLINE ;
      AEval( ::aControls, { |o| If( (o:lUpdate = nil .OR. o:lUpdate), o:Refresh(),) } )
This would rely on the xHarbour compiler generating code that doesn't test the second half of the .OR. statement if the first half is true.

Regards
Doug
(xProgrammer)

Posted: Wed Jun 18, 2008 10:26 am
by xProgrammer
Hi all

Having diagnosed and fixed the Update() bug (I think) I have removed it from my PRINTERS Class to overcome the problem when going to or from a condition of no installed printers and processing a refresh request. The class is now coded as below:

Code: Select all

#include "hbclass.ch"
#include "FiveLinux.ch"

CLASS PRINTERS

DATA aNames           /** array of printer names */
DATA aPorts           /** array of printer ports */
DATA iCount           /** number of printers installed */
DATA sDefault         /** name of default printer */
DATA iDefault         /** number of default printer - index into aNames and aPorts */
DATA lDefault         /** is there a default printer */
DATA lExists          /** is any printer defined */
DATA lOSQueried       /** has the OS been queried about available printers */
DATA brw_PRINTER_LIST /** Printer List BROWSE object on Printer Selection DIALOG */
DATA btn_PRINT        /** Print button on Printer Selection DIALOG */

METHOD New( lGetList ) CONSTRUCTOR
METHOD GetList()
METHOD Select( psJobName, psPrinter, psPort )

ENDCLASS


METHOD New( plGetList ) CLASS PRINTERS

/** _syntax: PRINTERS():New( [<lGetList>] )
    _arguments: 
                  <lGetList> a logical value  
                      If .T. the operating system is queried and printer lists and defaults are set as part of the object's initialisation.  
                      Defaults to .T.
    _returns:     the created PRINTERS object
    _description: see METHOD GetList() for a description of how the operating system is queried  */

IF PCOUNT() < 1
   lGetList := .T.
  ELSE
   lGetList := plGetList
ENDIF
::lOSQueried := .F.
IF lGetList
   ::GetList()
ENDIF

RETURN self


METHOD GetList() CLASS PRINTERS

/** _syntax:      <PRINTERS_object>:GetList()
    _arguments:   none
    _returns:     nil
    _description: Obtains information on installed printers from the operating system by RUNning lpstat -s > lpstat.txt.
                  Opens the redirected output of the above command and parses that file building a list of printernames (in ::aNames)
                      and ports (in ::aPorts).
                  Retrieves the name of the default printer (in ::sDefault) and determines its position (in ::iDefault)
                  If there is no default printer ::lDefault is set to .F.
                  Determines if there are any printers installed (in ::lExists) and how many are installed (in ::iCount) */

LOCAL iFileHandle    /** file handle for reading output of lpstat command */
LOCAL sPrinterLine   /** line buffer for reading output of lpstat command */
LOCAL sTest          /** buffer for testing format of lines of lpstat command output */
LOCAL iEndOffset     /** work variable for splitting out printer name and port data */
LOCAL lDefaultFound  /** work variable for flagging that default printer name has been found */
LOCAL ii             /** work variable loop counter */

::aNames := ARRAY( 0 )
::aPorts := ARRAY( 0 )
::iCount := 0
::sDefault := ""
::iDefault := 0
::lDefault := .F.
::lExists := .F.

RUN lpstat -s > lpstat.txt
iFileHandle := FOpen( "lpstat.txt" )
DO WHILE HB_FReadLine( iFileHandle, @sPrinterLine, CHR(10) ) = 0
   sTest := SUBSTR( sPrinterLine, 1, 11 )
   IF sTest = "device for "
      sTest := SUBSTR( sPrinterLine, 12 )
      iEndOffset := AT( ": ", sTest )
      IF iEndOffset > 0
         AAdd( ::aNames, ALLTRIM( SUBSTR( sTest, 1, iEndOffset - 1 ) ) )
         AAdd( ::aPorts, SUBSTR( sTest, iEndOffset + 2 ) )
         ::iCount += 1
         ::lExists := .T.
      ENDIF
     ELSE
      sTest := SUBSTR( sPrinterLine, 1, 27 )
      IF sTest = "system default destination:"
         ::sDefault := ALLTRIM( SUBSTR( sPrinterLine, 28 ) )
         ::lDefault := .T.         
      ENDIF
   ENDIF
ENDDO
FClose( iFileHandle )
IF ::lDefault
   FOR ii = 1 TO ::iCount
      IF ::aNames[ii] = ::sDefault
         ::iDefault := ii
         EXIT
      ENDIF
   NEXT
ENDIF
::lOSQueried := .T.

RETURN nil


METHOD Select( psJobName, psPrinter, psPort )

/** _syntax:      <PRINTERS_object>:Select( <JobName>, @<Printer, @<Port> )
    _parameters:  <JobName> a string
                      Name of the job(s) to be printed.
                      Gets displayed in the title of the dialog
                  <Printer> a string (passed by reference)
                      This gets updated with the name of the selected printer
                  <Port> a string (passed by reference)
                      This gets updated with the port of the selected printer
    _returns:     a logical value.
                      .T. indicates that the user has selected a printer and wishes to proceed
                      .F. indicates that the user wishes to cancel
    _description: a dialog enabling the user to select a printer for (a) print job(s) or to cancel the print request
                      A printer can be selected by either: 
                          double left clicking on it, 
                          or highlighting it and clicking on the print button
                      The function gives an informational message if no printers are installed.    
                      The function checks that a printer list has been created, if not it invokes ::GetList()
                      The user can get the printer list refreshed by clicking on the Refresh button  */

LOCAL dlg_SELECT_PRINTER   /** Select Printer Dialog */
LOCAL sTitle               /** Dialog Title */            
LOCAL lRetCode             /** Function Return Code */
LOCAL cAction              /** Controls printer query loop */

IF !::lOSQueried
   ::GetList()
ENDIF
sTitle := "Select Printer for Printing " + psJobName 
cAction := "S"
DO WHILE cAction = "S"
   DEFINE DIALOG dlg_SELECT_PRINTER TITLE sTitle SIZE 800, 500
   IF ::iCount < 1
      @  30,  10 SAY "No printers currently installed"
     ELSE
      @  3, 1 BROWSE ::brw_PRINTER_LIST ;
         FIELDS ::aNames[::brw_PRINTER_LIST:nAt], ::aPorts[::brw_PRINTER_LIST:nAt] ;
         HEADERS "Printer", "Port" SIZE 550, 400 OF dlg_SELECT_PRINTER 
      ::brw_PRINTER_LIST:bLDblClick := { | nRow, nCol | ::btn_PRINT:Click() }
      BrwSetColWidths( ::brw_PRINTER_LIST , { 200, 350 } )
      ::brw_PRINTER_LIST:SetArray( ::aNames )
   ENDIF
   @  30, 600 BUTTON ::btn_PRINT PROMPT "Print" WHEN ::lExists ;
      ACTION ( psPrinter := ::aNames[::brw_PRINTER_LIST:nAt], psPort := ::aPorts[::brw_PRINTER_LIST:nAt], lRetCode := .T., ;
      cAction := "Q", dlg_SELECT_PRINTER:End() ) ;
      PIXEL OF dlg_SELECT_PRINTER
   @  90, 600 BUTTON "Refresh" ACTION ( ::GetList(), dlg_SELECT_PRINTER:End() ) PIXEL OF dlg_SELECT_PRINTER
   @ 240, 600 BUTTON "Cancel" ACTION ( lRetCode := .F., cAction := "Q", dlg_SELECT_PRINTER:End() ) PIXEL OF dlg_SELECT_PRINTER
   ACTIVATE DIALOG dlg_SELECT_PRINTER CENTERED
ENDDO

RETURN lRetCode
Regards
xProgrammer

Posted: Wed Jun 18, 2008 9:16 pm
by Antonio Linares
Doug,

> The problem appears to be that the object constructor of the TScrollbar class doesn't initialise ::lUpdate

Thats what I thought when I saw this in the error.log file:

Code: Select all

   Argument error: conditional 
   Args: 
     [   1] = U   nil 
...
   Procedure     Type   Value 
   ========================== 
   (b)TWINDOW 
     Param   1:    O    Class: TSCROLLBAR 
     Param   2:    N    2 
     Local   1:    U    nil 
     Local   2:    U    nil 
     Local   3:    N    0 
   AEVAL 
but I have been quite busy these past days and had no time to do some tests. So glad to know that you solved it :-)

Posted: Thu Jun 19, 2008 10:12 am
by xProgrammer
Hi Antonio

I figured you must have been busy. Pretty frantic here too. As I get chances I am working on a number of bits of code to extend the xHarbour FiveLinux programming environment. The PRINTERS class was just one little bit (that grew out of the need to develop the functionality provided by GetPrinters() and GetDefaultPrinter() in the Windows version of xHarbour but missing in the Linux version.) . I have started work on a basic project "manager" that grew out of playing around with script files that were originally derived from buildx.sh. That project really requires a dialog to select a file that will apply a filter to the files (eg only list ,prg files) so an offshoot of that project is a FILES class. Then whilst I have extended FiveLinux.ch to add commands to create .dbf's with a fairly natural and concise syntax an interactive tool that would create, modify or document .dbf's would be desirable. Plus I want to have a technical documentation tool that will extract documentation from source files and data files, both explicit and implied, and format appropriately. This should tie in nicely with my basic project manager. There's no time scale but I do hope to do it little bit by bit as I do my other development jobs not to mention all the other tasks I have.

Regards

Doug