UPDATE_TIMER=25                 # Poll timer (ms) for terminal output queue PDF_ORIENTATION_PORTRAIT=0 PILCONFIG=  cls_pilconfig() def charconv(s, charset):
   return convert_to_unicode[charset][ord(s)] def barrconv(barr, charset):
   slist= []
   for c in barr:
      slist.append(convert_to_unicode[charset][c])
   return "".join(slist) CHARSET_HP2225=4 class cls_pildevbase:

   def __init__(self):

      self.__aid__ = 0x00          # accessory id 
      self.__defaddr__ = 0         # default address alter AAU
      self.__did__ = ""            # device id
      self.__status__ = 0          # HP-IL status
      self.__addr__ = 0            # HP-IL primary address (by TAD,LAD)
                                   # bits 0-5=AAD or AEP, bit 7=1 means
                                   # auto address taken
      self.__addr2nd__ = 0         # HP-IL secondary address (by SAD)
                                   # bits 0-5=AES, bit 7 means auto addr taken
      self.__ilstate__ =0          # state machine flag
                                   # bit 7, bit 6, bit5, bit 4
                                   # 0      0      0     0 idle
                                   # 0      0      1     0 addressed listener in
                                   #                       second. add. mode
                                   # 0      0      0     1 addressed talker in
                                   #                       second. add. mode
                                   # 1      0      0     0 addressed listener
                                   # 0      1      0     0 addressed talker 
                                   # bit 0 or bit 1 set    active takler
                                   # bit 1: SDA, SDI
                                   # bit 0: SST, SDI, SAI, NRD
      self.__talker_frame__=0      # frame sent as talker
      self.__ptsdi__ = 0           # output pointer for device id
      self.__status_len__=1        # length of device status in bytes
      self.__ptssi__ = 0           # output pointer for hp-il status
      self.__isactive__= False     # device active in loop
      self.__addr_framecounter__=0 # framecounter when device got an address
      self.__threadobject__=None   # reference to the thread object
      self.__status_lock__= threading.Lock()
      self.__islocked__= False
      self.__access_lock__= threading.Lock()
#
# --- public functions ---
#
#  set device active/inactive. If the device becomes active check if an AAU,
#  AAD, AEP or AES happened in the mean time. In this case reset the device
#  address
#
   def setactive(self, active):
      if not self.__isactive__ and active:
         if self.__addr_framecounter__ != self.__threadobject__.get_addr_framecounter():
            self.__addr__=0
            self.__addr2nd__=0
      self.__isactive__= active
#
#  set object reference to thread object
#
   def setThreadObject(self,obj):
      self.__threadobject__= obj
#
#  set local and update globel addr_framecounter
#
   def update_addr_framecounter(self):
      self.__addr_framecounter__= self.__threadobject__.get_framecounter()
      self.__threadobject__.update_addr_framecounter(self.__addr_framecounter__)
#
#  return device status
#
   def getstatus(self):
      self.__access_lock__.acquire()
      status="idle"
      if self.__ilstate__ & 0x03:
         status="act. talker"
      else:
         if self.__ilstate__ & 0xA0:
            status="addr. listener"
         elif self.__ilstate__ & 0x50:
            status="addr. talker"

      ret= [self.__isactive__, self.__did__, self.__aid__, self.__addr__, self.__addr2nd__, status]
      self.__access_lock__.release()
      return ret

#
#  lock device, all output is disabled
#
   def setlocked(self,locked):
      self.__access_lock__.acquire()
      self.__islocked__= locked
      self.__access_lock__.release()

#
#  Process device
#
   def process(self,frame):

#
#     if device is not active, return
#
      if not self.__isactive__:
         return(frame)
#
#     process frames
#
      if (frame & 0x400) == 0:
         frame= self.__do_doe__(frame)
      elif (frame & 0x700) == 0x400:
         frame= self.__do_cmd__(frame)
      elif (frame & 0x700) == 0x500:
         frame= self.__do_rdy__(frame)
#
#     set service request bit if data available status bit set
#
      if self.__getstatus__() & 0x40:
         if (frame & 0x700) == 0x000:  # data 00x xxxx xxxx -> 001 xxxx xxxx
            frame= frame | 0x100
         if (frame & 0x700) == 0x200:  # end  01x xxxx xxxx -> 011 xxxx xxxx
            frame= frame | 0x100
         if (frame & 0x700) == 0x600:  # idy  11x xxxx xxxx -> 111 xxxx xxxx
            frame= frame | 0x100
      return(frame)

#
#  --- private ---
#
#
#  get status byte from device thread safe
#
   def __getstatus__(self):
      self.__status_lock__.acquire()
      status= self.__status__
      self.__status_lock__.release()
      return(status)
#
# set status byte of device thread safe
#
   def __setstatus__(self,status):
      self.__status_lock__.acquire()
      self.__status__= status
      self.__status_lock__.release()
#
#  output data stub
#
   def __outdata__(self,frame):
      return frame

#
#  input data stub
#
   def __indata__(self,frame):
      return

#
#  device clear stub
#
   def __clear_device__(self):
      # reset service request bit
      s= self.__getstatus__()
      s &= ~ 0x40
      self.__setstatus__(s)
      return
#
#  stub for extended sad commands
#
   def __cmd_sad_ext__(self,frame):
      return frame

#
# stub for extended commands
#
   def __cmd_ext__(self,frame):
      return frame

#
#  manage HPIL data frames, returns the returned frame
#
   def __do_doe__(self,frame):
      talker_error= False

      if (self.__ilstate__ & 0xC0) == 0x40: # addressed talker?

         if (self.__ilstate__ & 0x03) != 0: # active talker?

#           compare last talker frame with actual frame without SRQ bit
            talker_error = ((frame & 0x6FF) != (self.__talker_frame__ &0x6FF))

#           data (SDA) status (SST) or accessory ID (SDI)
            if (not talker_error) and (self.__ilstate__ & 0x02 !=0): # talker

#              save current SRQ bit
               SrqBit= frame & 0x100 

#              status (SST) or accessory ID (SDI)
               if (self.__ilstate__ & 0x01) != 0: 

#                 0x43: active talker (multibyte status)
                  if self.__ptssi__ > 0:   # SST
                     self.__ptssi__= self.__ptssi__-1
                     if self.__ptssi__ > 0:
                        frame= (self.__getstatus__() >> (( self.__status_len__-self.__ptssi__ ) * 8)) & 0xFF
                  if self.__ptsdi__ > 0:   # SDI
                     if self.__ptsdi__ == len(self.__did__):
                        frame=0
                        self.__ptsdi__=0
                     else: # SDI
                        frame= ord(self.__did__ [self.__ptsdi__])
                        self.__ptsdi__= self.__ptsdi__+1
                  if self.__ptssi__ == 0 and self.__ptsdi__ == 0 : 
#                    EOT for SST and SDI
                     frame= 0x540
               else: # 0x42 active talker (data)
                  frame=self.__outdata__(frame) # SDA
#              a set SRQ bit doesn't matter on ready class frames
               frame |= SrqBit
            else: # 0x41 active talker (single byte status)
               frame= 0x540 # end of SAI or NRD or talker error

         if frame == 0x540: # EOT
#           check for error and set ETE frame
            if talker_error:
               frame= 0x541 # ETE
            self.__ilstate__ &= ~ 0x03 # delete active talker
         self.__talker_frame__= frame

      if (self.__ilstate__ & 0xC0)==0x80:  # listener
            self.__indata__(frame)
      return(frame)
#
# manage HP-IL command frames
#
   def __do_cmd__(self,frame):

      n= frame & 0xff
      t= n >> 5
      if t == 0:
         if n == 4:   # SDC
            if (self.__ilstate__ & 0x80) != 0: # listener
               self.__clear_device__()
         elif n == 20: # DCL
             self.__clear_device__()
      elif t == 1:  # LAD
         n= n & 31
         if n == 31: # UNL, if not talker then go to idle state
             if (self.__ilstate__ & 0xA0) != 0: # not talker ?
                self.__ilstate__ &= 0x50 # idle
         else:       # if MLA go to listen state
             if (self.__ilstate__ &0x80) ==0 and  n == (self.__addr__ & 31):
                if (self.__addr2nd__ & 0x80) == 0:
                   self.__ilstate__ = 0x80 # set addressed listener
                else:
                   self.__ilstate__ = 0x20 # set addressed listener second. add mode
      elif t == 2:  # TAD  
         n= n & 31
         if n == (self.__addr__ & 31):
            if (self.__addr2nd__ & 0x80) == 0: # if MTA go to talker state
               self.__ilstate__= 0x40 # set addressed talker
            else:
               self.__ilstate__= 0x10 # set addressed talker, second. add. mode
         else:
            if ( self.__ilstate__ & 0x50) != 0: # addressed talker?
               self.__ilstate__ &= 0xA0 # idle
      elif t == 3: # SAD
         if (self.__ilstate__ & 0x30) !=0: # addressed talker or listener 2nd addr mode?
            n = n & 31
            if n == (self.__addr2nd__ & 31):
               self.__ilstate__<<=2  # switch to addressed listener/talker
            else:
               self.__ilstate__=0 # idle
         else:
            frame= self.__cmd_sad_ext__(frame)
      elif t == 4: 
         n= n & 31
         if n == 16: # IFC
            self.__ilstate__= 0x0 # idle
         elif n == 26: # AAU
            self.update_addr_framecounter()
            self.__addr__=  self.__defaddr__
            self.__addr2nd__= 0
      else:
         frame= self.__cmd_ext__(frame)
      return(frame)
            
#
# HP-IL ready frames
#
   def __do_rdy__(self,frame):

      n= frame & 0xFF
      if n <= 127:
         if ( self.__ilstate__ & 0x40) !=0:  # SOT, addressed talker?
            if  n == 66:  #NRD
               self.__ptsdi__ = 0
               self.__ptssi__ = 0
               self.__ilstate__ = 0x41 # abort transfer, clear SDA/SDI

            elif n == 96: # SDA
               frame=self.__outdata__(frame)
               if frame != 0x560: # not sda received data
                  self.__ilstate__= 0x42 # active talker, SDA/SDI
                  self.__talker_frame__= frame # last talker frame
            elif n == 97: # SST
                # reset service request bit
                s= self.__getstatus__()
                s &= ~ 0x40
                self.__setstatus__(s)
                # update IL status and return no. of status bytes
                self.__ptssi__ = self.__status_len__
                if self.__ptssi__ > 0: # response to status request
                   frame = (self.__getstatus__() >> ((self.__status_len__-self.__ptssi__) * 8)) & 0xFF
                   self.__ilstate__= 0x43 # active talker
                   self.__talker_frame__= frame # last talker frame
            elif n == 98:  # SDI
               if self.__did__ != "":
                  frame= ord(self.__did__[0])
                  self.__ptsdi__ = 1 # other 2
                  self.__ilstate__= 0x43 # active talker, SDA/SDI, SST/SDI/SAI
                  self.__talker_frame__= frame
            elif n == 99: # SAI
                  frame= self.__aid__ & 0xFF
                  self.__ilstate__= 0x41 # active talker, SST/SDI/SAI
                  self.__talker_frame__= frame
      else:
         if n < 0x80 +31: # AAD
            if ((self.__addr__ & 0x80) == 0 and self.__addr2nd__ ==0):
               # AAD, if not already an assigned address, take it
               self.update_addr_framecounter()
               self.__addr__ = n
               frame=frame+1
         elif  (n >= 0xA0 and n < 0xA0 + 31): # AEP
            # AEP, if not already an assigned address and got an AES frame,
            if ((self.__addr__ & 0x80) == 0 and (self.__addr2nd__ & 0x80) != 0):
               # take it
               self.update_addr_framecounter()
               self.__addr__= n & 0x9F
         elif (n >= 0xC0 and n < 0xC0 +31): # AES
            if (self.__addr__ & 0x80) == 0:
               self.update_addr_framecounter()
               self.__addr2nd__= n & 0x9F
               frame=frame + 1
      return (frame) class cls_tabgeneric(QtWidgets.QWidget):

   def __init__(self,parent,name):
      super().__init__()
      self.name= name
      self.active= PILCONFIG.get(self.name,"active",False)
      self.parent=parent
      self.font_name=FONT
      self.font_size=0
      self.font_width=0
      self.font_height=0
      self.width= 0
      self.height= 0
      self.guiobject= None
      self.cbLogging= None
      self.logging= False
      self.pildevice=None
      self.widget_index=1
      self.cBut= None
#
#     Build basic layout
#
      self.vbox= QtWidgets.QVBoxLayout()
      self.setLayout(self.vbox)
#
#     hbox1: container of GUI Object
#
      self.hbox1=QtWidgets.QHBoxLayout()
      self.hbox1.setContentsMargins(10,10,10,10)
#
#     hbox2: container of control widgets
# 
      self.hbox2= QtWidgets.QHBoxLayout()
      self.hbox2.setContentsMargins(10,3,10,3)

      self.cbActive= QtWidgets.QCheckBox('Device enabled')
      self.cbActive.setChecked(self.active)
      self.cbActive.setEnabled(False)
      self.cbActive.stateChanged.connect(self.do_cbActive)

      self.hbox2.addWidget(self.cbActive)
      self.hbox2.addStretch(1)

      self.vbox.addLayout(self.hbox1)
      self.vbox.addLayout(self.hbox2)
#
#  add the gui object to hbox1
#
   def add_guiobject(self,guiobject):
      self.guiobject= guiobject
      self.hbox1.addWidget(self.guiobject)
#
#  insert a control widget
#
   def add_controlwidget(self,widget):
      self.hbox2.insertWidget(self.widget_index,widget)
      self.widget_index+=1
#
#  add a config cascading menu: always at the end of the control box
#
   def add_configwidget(self):
      self.cBut= cls_config_tool_button(self.name,"Configuration")
      self.hbox2.addWidget(self.cBut)
#
#  insert the cbLogging widget in hbox2 after the cbActive Widget
#
   def add_logging(self):
      self.cbLogging= LogCheckboxWidget(self.name)
      self.hbox2.insertWidget(1,self.cbLogging)
      self.logging= PILCONFIG.get(self.name,"logging",False)
      self.cbLogging.setChecked(self.logging)
      self.cbLogging.setEnabled(False)
      self.cbLogging.stateChanged.connect(self.do_cbLogging)
      self.widget_index+=1
#
#  if config button present, then add log buffer option
#
      if self.cBut is not None:
         self.cBut.add_option("Log buffering","buffer_log",T_BOOLEAN,[True,False])
#
#  insert a status widget
#
   def add_statuswidget(self,statuswidget):
      self.hbox2.insertWidget(self.widget_index,statuswidget)
      self.hbox2.insertStretch(self.widget_index,1)
#
#  tab config changed, handle buffer_log change
#
   def do_tabconfig_changed(self):
     param= self.cBut.get_changed_option_name()
     if param== "buffer_log":
         self.cbLogging.set_buffering(PILCONFIG.get(self.name,"buffer_log"))
#
#  reconfigure, nothing to do here
#
   def reconfigure(self):
      return
# 
#  disable tab
#
   def disable(self):
      if self.cbLogging is not None:
         if self.logging:
            self.cbLogging.logClose()
         self.cbLogging.setEnabled(False)
      self.cbActive.setEnabled(False)
#
#  enable tab
#
   def enable(self):
      self.cbActive.setEnabled(True)
      if self.cbLogging is not None:
         if self.logging:
            self.cbLogging.logOpen()
         self.cbLogging.setEnabled(True)
#
#  toggle active/inactive
#
   def toggle_active(self):
      return
#
#  action: toogle active checkbox
#
   def do_cbActive(self):
      self.active= self.cbActive.isChecked()
      PILCONFIG.put(self.name,"active",self.active)
      self.pildevice.setactive(self.active)
      self.toggle_active()
#
#  action: toggle log checkbox
#
   def do_cbLogging(self):
      self.cbLogging.setEnabled(False)
      self.logging= self.cbLogging.isChecked()
      self.pildevice.setlocked(True)
      if self.logging:
         self.cbLogging.logOpen()
      else:
         self.cbLogging.logClose()
      PILCONFIG.put(self.name,"logging",self.logging)
      self.pildevice.setlocked(False)
      self.cbLogging.setEnabled(True) class LogCheckboxWidget(QtWidgets.QCheckBox):
   def __init__(self,name):
      super().__init__("Log "+name)
      self.name=name
      self.filename=self.name+".log"
      self.log= None
      self.buffer_log=PILCONFIG.get(self.name,"buffer_log",True)
#
#   configure log buffering
#
   def set_buffering(self,buffer_log):
      self.buffer_log= buffer_log
#
#   open log file, output BOM only on Windows at the beginning of the file
#
   def logOpen(self):
      try:
         if isWINDOWS() and PILCONFIG.get("pyilper","usebom"):
            self.log=open(self.filename,"a",encoding="UTF-8-SIG")
         else:
            self.log=open(self.filename,"a",encoding="UTF-8")
         self.log.write("\nBegin log "+self.filename+" at ")
         self.log.write(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
         self.log.write("\n")
      except OSError as e:
         reply=QtWidgets.QMessageBox.critical(self,'Error',"Cannot open log file: "+ e.strerror,QtWidgets.QMessageBox.Ok,QtWidgets.QMessageBox.Ok)

   def logClose(self):
      try:
         self.log.write("\nEnd log "+self.filename+" at ")
         self.log.write(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
         self.log.write("\n")
         self.log.close()
         self.log= None
      except OSError as e:
         reply=QtWidgets.QMessageBox.critical(self,'Error',"Cannot close log file: "+ e.strerror,QtWidgets.QMessageBox.Ok,QtWidgets.QMessageBox.Ok)

   def logWrite(self,line):
      if self.log is None:
         return
      try:
         self.log.write(line)
      except OSError as e:
         reply=QtWidgets.QMessageBox.critical(self,'Error',"Cannot write to log file: "+ e.strerror+". Logging disabled",QtWidgets.QMessageBox.Ok,QtWidgets.QMessageBox.Ok)
         try:
            self.log.close()
         except OSError:
            pass
         self.log = None

   def logFlush(self):
      if self.log is None:
         return
      if self.buffer_log:
         return
      try:
         self.log.flush()
      except OSError as e:
         reply=QtWidgets.QMessageBox.critical(self,'Error',"Cannot flush to log file: "+ e.strerror+". Logging disabled",QtWidgets.QMessageBox.Ok,QtWidgets.QMessageBox.Ok)
         try:
            self.log.close()
         except OSError:
            pass
         self.log = None T_INTEGER=3 O_DEFAULT= -1 T_STRING=2
import copy
import queue
import threading
import re
from math import floor
from PyQt5 import QtCore, QtWidgets, QtGui, QtPrintSupport
from .pilcore import UPDATE_TIMER, PDF_ORIENTATION_PORTRAIT
from .pilconfig import PILCONFIG
from .pilcharconv import charconv, barrconv, CHARSET_HP2225
from .pildevbase import cls_pildevbase
from .pilwidgets import cls_tabgeneric, LogCheckboxWidget, T_INTEGER, O_DEFAULT, T_STRING
from .pilcore import *
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
# HP2225B virtual device classes  ---------------------------------------------
#
# Changelog
# 30.12.2018 jsi:
# - initial version
# 02.01.2018 jsi:
# - added support for ^N and ^O for bold mode on and off
# - allow ESC&kS in addition to ESC&k0S to switch to normal character pitch
# 04.01.2018 jsi:
# - support zero length graphics chunks
# - switchable print color
#

#
# constants --------------------------------------------------------------
#

PDF_LINES=70           # number of lines in pdf output
PDF_MARGINS=50         # margins (top,bot,left,right) of pdf output
PDF_MAX_COLS=3         # max number of columns in pdf output
PDF_COLUMN_SPACING=80  # spacing between columns
PDF_LINE_SPACING=0     # linespacing in (relative) pixel

# GUI commands
CMD_LF_PRESSED=     0
CMD_LF_RELEASED=    1
CMD_FF_PRESSED=     2
CMD_CLEAR=          3

# HPIL-Thread commands
REMOTECMD_CLEAR=0      # clear device
REMOTECMD_LOG=1        # log something
REMOTECMD_STATUS=2     # printer status information: 
REMOTECMD_TEXT=3       # print characters according to current status
REMOTECMD_GRAPHICS=4   # print graphics according to current status
REMOTECMD_CR=5         # carriage return
REMOTECMD_LF=6         # line feed
REMOTECMD_HLF=7        # half line feed
REMOTECMD_BS=8         # backspace
REMOTECMD_FF=9         # form feed
REMOTECMD_TERMGRAPHICS=10 # end of graphics

ELEMENT_FF=0
ELEMENT_TEXT=1
ELEMENT_GRAPHICS=2

# Printer constants
BUFFER_LINE_H=2             # number of dots for a buffer line height
PRINTER_WIDTH_HIGH= 1280    # width in dots for high resolution
PRINTER_WIDTH_LOW= 640      # width in dots for low resolution
HP2225B_FONT_PIXELSIZE=28   # pixel size of the font used
HP2225B_MAX_LINES= 69       # maximum number of lines for a page

# Font widths
# Print mode norm                 : 80 chars/line, character width 16 dots
# Print mode expand               : 40 chars/line, character width 32 dots
# Print mode compressed           : 142 chars/line, character width 9 dots
# Print mode expand/compressed    : 71 chars/line, character width 18 dots
FONT_WIDTH= [16, 32, 9, 18 ]
#
# this is a hack because Qt on Macos does not display expanded fonts correctly
#
if isMACOS():
   FONT_STRETCH= [100, 110, 56, 110]
else:
   FONT_STRETCH= [100, 200, 56, 113]

BUFFER_SIZE_NAMES=["5 Pages", "10 Pages","20 Pages","50 Pages"]
BUFFER_SIZE_VALUES=[2500, 5000, 10000, 25000]
#
# Print colors
#
HP2225_COLOR_BLACK=0
HP2225_COLOR_RED=1
HP2225_COLR_BLUE=2
HP2225_COLOR_GREEN=3
COLOR_NAMES= [ "black", "red", "blue", "green" ]
HP2225_COLORS=[QtCore.Qt.black, QtCore.Qt.red, QtCore.Qt.blue, QtCore.Qt.green]

#
# HP2225B tab widget ---------------------------------------------------------
#
class cls_tabhp2225b(cls_tabgeneric):


   def __init__(self,parent,name):
      super().__init__(parent,name)
      self.name=name
#
#     this parameter is global
#
      self.papersize=PILCONFIG.get("pyilper","papersize")
#
#     init local parameter
#
      self.screenwidth=PILCONFIG.get(self.name,"hp2225b_screenwidth",-1)
      self.scrollupbuffersize=PILCONFIG.get(self.name,"hp2225b_scrollupbuffersize",1)
      self.printcolor=PILCONFIG.get(self.name,"hp2225b_printcolor",HP2225_COLOR_BLACK)
#
#     create Printer GUI object
#
      self.guiobject=cls_hp2225bWidget(self,self.name,self.papersize)
#
#     add gui object 
#
      self.add_guiobject(self.guiobject)
#
#     add cascading config menu
#
      self.add_configwidget()
#
#     add local config option
#
      self.cBut.add_option("Screen width","hp2225b_screenwidth",T_INTEGER,[O_DEFAULT,640,960,1280])

      self.cBut.add_option("Buffer size","hp2225b_scrollupbuffersize",T_STRING,BUFFER_SIZE_NAMES)