Possible bug with UPDATE option, Possible Bug in my code

Post Reply
User avatar
xProgrammer
Posts: 464
Joined: Tue May 16, 2006 7:47 am
Location: Australia

Possible bug with UPDATE option, Possible Bug in my code

Post 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 | Expand

#include "hbclass.ch"#include "FiveLinux.ch"STATIC oWndFUNCTION Main( xHomeDir, xDataDir)PUBLIC oPRINTERSoPRINTERS := PRINTERS():New()DEFINE WINDOW oWnd TITLE ( "Test Dialog Update" ) MENU BldInMenu()ACTIVATE WINDOW oWnd MAXIMIZEDRETURN nilFUNCTION BldInMenu()LOCAL lomMenuMENU lomMenu   MENUITEM "_File"      MENU         MENUITEM "_Get Printers" ACTION TestPrinters()         MENUITEM "e_Xit" ACTION oWnd:End()      ENDMENUENDMENURETURN lomMenuFUNCTION TestPrinters()LOCAL sPrinterLOCAL sPortIF oPRINTERS:Select( "Test Document", @sPrinter, @sPort )   MsgInfo( "Print on " + sPrinter + " on port " + sPort )  ELSE   MsgInfo( "Printing cancelled" )ENDIFRETURN nilCLASS PRINTERSDATA 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 ) CONSTRUCTORMETHOD GetList()METHOD Select( psJobName, psPrinter, psPort )ENDCLASSMETHOD 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 := plGetListENDIF::lOSQueried := .F.IF lGetList   ::GetList()ENDIFRETURN selfMETHOD 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.txtiFileHandle := 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   ENDIFENDDOFClose( iFileHandle )IF ::lDefault   FOR ii = 1 TO ::iCount      IF ::aNames[ii] = ::sDefault         ::iDefault := ii         EXIT      ENDIF   NEXTENDIF::lOSQueried := .T.RETURN nilMETHOD 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()ENDIFsTitle := "Select Printer for Printing " + psJobName DEFINE DIALOG dlg_SELECT_PRINTER TITLE sTitle SIZE 800, 500IF ::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_PRINTERACTIVATE DIALOG dlg_SELECT_PRINTER CENTEREDRETURN lRetCodeFUNCTION BrwSetColWidths( oBrw, aWidths )iLastWidth := LEN( aWidths )FOR iCount = 1 TO iLastWidth   oBrw:aColumns[iCount]:nWidth := aWidths[iCount]NEXTRETURN nil


Herewith the error.log:

Code: Select all | Expand

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   nilStack 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    nilLinked RDDs===========   DBF   DBFFPT   DBFNTX   DBFBLOBDataBases 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 ERRORMemory 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)
User avatar
xProgrammer
Posts: 464
Joined: Tue May 16, 2006 7:47 am
Location: Australia

Post 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)
User avatar
xProgrammer
Posts: 464
Joined: Tue May 16, 2006 7:47 am
Location: Australia

Post 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)
User avatar
xProgrammer
Posts: 464
Joined: Tue May 16, 2006 7:47 am
Location: Australia

Post 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 | Expand

   ::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 | Expand

   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 | Expand

   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)
User avatar
xProgrammer
Posts: 464
Joined: Tue May 16, 2006 7:47 am
Location: Australia

Post 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 | Expand

#include "hbclass.ch"#include "FiveLinux.ch"CLASS PRINTERSDATA 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 ) CONSTRUCTORMETHOD GetList()METHOD Select( psJobName, psPrinter, psPort )ENDCLASSMETHOD 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 := plGetListENDIF::lOSQueried := .F.IF lGetList   ::GetList()ENDIFRETURN selfMETHOD 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.txtiFileHandle := 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   ENDIFENDDOFClose( iFileHandle )IF ::lDefault   FOR ii = 1 TO ::iCount      IF ::aNames[ii] = ::sDefault         ::iDefault := ii         EXIT      ENDIF   NEXTENDIF::lOSQueried := .T.RETURN nilMETHOD 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()ENDIFsTitle := "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 CENTEREDENDDORETURN lRetCode


Regards
xProgrammer
User avatar
Antonio Linares
Site Admin
Posts: 42521
Joined: Thu Oct 06, 2005 5:47 pm
Location: Spain
Has thanked: 31 times
Been thanked: 76 times
Contact:

Post 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 | Expand

   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 :-)
regards, saludos

Antonio Linares
www.fivetechsoft.com
User avatar
xProgrammer
Posts: 464
Joined: Tue May 16, 2006 7:47 am
Location: Australia

Post 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
Post Reply