Coverage for pyilper/pildrive.py: 91%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1#!/usr/bin/python3
2# -*- coding: utf-8 -*-
3# pyILPER 1.2.1 (python) for Linux
4#
5# An emulator for virtual HP-IL devices for the PIL-Box
6# derived from ILPER 1.4.5 for Windows
7# Copyright (c) 2008-2013 Jean-Francois Garnier
8# C++ version (c) 2013 Christoph Gießelink
9# Python Version (c) 2015 Joachim Siebold
10#
11# This program is free software; you can redistribute it and/or
12# modify it under the terms of the GNU General Public License
13# as published by the Free Software Foundation; either version 2
14# of the License, or (at your option) any later version.
15#
16# This program is distributed in the hope that it will be useful,
17# but WITHOUT ANY WARRANTY; without even the implied warranty of
18# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19# GNU General Public License for more details.
20#
21# You should have received a copy of the GNU General Public License
22# along with this program; if not, write to the Free Software
23# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
24#
25# Virtual drive tab object classes -------------------------------------------
26#
27# Changelog
28# 24.09.2015 cg
29# - expanded LIF filename filter with *.lif and *.LIF extention
30# 29.11.2015 jsi
31# - working directory is default when opening lif files
32# - do not check lif medium version
33# - invalid layout if all media information are zero
34# - refresh dir list if drive has not been talker for nn seconds
35# - use device lock instead of pausing PIL-Loop
36# 01.12.2015 jsi
37# - clear dirlist if illegal medium is mounted
38# 18.12.2015 jsi
39# - added dropdown command button in drive tab
40# - added context menu for entries in directory listing
41# 31.12.2015 jsi
42# - add view option to context menu
43# 03.01.2016 jsi
44# - added label option to dropdown command menu
45# - filename and drivetype controls are disabled if the drive is enabled
46# - rename 'Help' menu entry to 'Manual'
47# 05.01.2016 jsi
48# - initialize filename an drivetype controls properly at program start
49# 08.01.2016 jsi
50# - introduced lifcore, refactoring
51# - do not lock pildevice, if pyilper is disabled
52# 10.01.2016 jsi
53# - show tooltips for disabled controls in the drive tab
54# 16.01.2016 jsi
55# - revert disabling filename and drivetype controls if the drive is enabled
56# - allow arbitrary disk layouts
57# 30.01.2016 jsi
58# - enable file management controls only if a compatible version of the LIF
59# utilities is installed
60# 31.01.2016 jsi
61# - added workdir parameter to call of cls_lifview
62# 19.02.2016 jsi:
63# - added character set combo box to cls_tabdrive
64# - put text in front of the combo boxes at the bottom of the tabs
65# 05.04.2016 jsi:
66# - issue warinings about invalid LIF headers only when mounting those files, hint by cg
67# 04.12.2016 jsi
68# - allow LIF directories not starting at record 2
69# 04.02.2016 jsi
70# - added missing argument to sortbyColumn (QT5 fix)
71# 19.02.2017 jsi
72# - font size of the directory listing of the LifDirWidget can now be
73# configured. The row height is now properly adjusted to the font height
74# 17.08.2017 jsi
75# - set did to empty string instead of none
76# 20.08.2017 jsi
77# - add create barcode to context menu
78# 28.08.2017 jsi
79# - get papersize config parameter in the constructor of the tab widget
80# 01.09.2017 jsi
81# - added output directory list to pdf to tools
82# 03.09.2017 jsi
83# - register pildevice is now method of commobject
84# 20.09.2017 jsi
85# - make directory font size reconfigurable on runtime
86# 28.10.2017 jsi
87# - lifutils_installed is now variable of cls_pyilper
88# 30.10.2017 jsi
89# - bugfix: close file in getMediumInfo
90# 08.01.2018 jsi
91# - change tool menu button behaviour to InstantPopup
92# 16.01.2018 jsi
93# - refactoring: adapt cls_tabdrive to cls_tabgeneric, create new
94# cls_DriveWidget class. Remove tool menu button in favour of push buttons
95# - implemented cascading configuration menu
96# 18.01.2018 jsi
97# - direcorycharsize is now a dual parameter
98# 24.01.2018 jsi
99# - fixed missing lifcore import
100# 28.01.2018 jsi
101# - fixed errors in referencing gui object
102# - set pushbutton autodefault property false
103# 05.02.2018 jsi
104# - allow smaller font sizes for directory listing
105# 06.11.2019 jsi
106# - changed text in drive type selection box to "HP9114B"
107# 29.04.2020 jsi
108# - initialize directory table with zero rows (prevents doing right clicks into
109# empty rows of a directory which caused a crash of the program)
110# - make all entries of the directory table read only
111# - left click on a selected row does a deselect.
112# 30.04.2020 jsi
113# - code optimization
114# 15.11.2021 jsi
115# - refactoring, created abstract classes for drives and drivetabs
116# - created classes for raw drive and raw drive tab
117# - renamed classes for LIF drive and LIF drive tab
118# 16.11.2021 jsi
119# - medium info did not update in raw drive tab
120# 23.11.2021 jsi
121# - change image file was not forwarded to the pildevice in raw drive tab
122# - lock pildevice if medium changes in raw drive tab
123# 20.12.2021 jsi
124# - open raw file: only existing files are allowed
125#
126from PySide6 import QtCore, QtGui, QtWidgets
127import time
128import threading
129import os
130from .pilcore import *
131from .pildevbase import cls_pildevbase
132from .pilwidgets import cls_tabgeneric, T_STRING, T_INTEGER, O_DEFAULT
133from .pilconfig import PilConfigError, PILCONFIG
134from .pilcharconv import CHARSET_HP71, charsets
135from .lifutils import cls_LifFile,cls_LifDir,LifError, getLifInt, putLifInt
136from .lifcore import *
137from .lifexec import cls_lifpack, cls_lifpurge, cls_lifrename, cls_lifexport, cls_lifimport, cls_lifview, cls_liflabel, check_lifutils, cls_lifbarcode
138from .pilpdf import cls_pdfprinter,cls_textItem
140#
141# Tab classes ------------------------------------------------------------------
142#
143class cls_tabdrivegeneric(cls_tabgeneric):
145 def __init__(self,parent,name):
146 super().__init__(parent,name)
147 self.name= name
148#
149# handle changes of tab config options
150#
151 def do_tabconfig_changed(self):
152 self.guiobject.reconfigure()
153 super().do_tabconfig_changed()
154#
155# reconfigure
156#
157 def reconfigure(self):
158 self.guiobject.reconfigure()
159 super().reconfigure()
160#
161# enable/disable
162#
163 def enable(self):
164 super().enable()
165 self.parent.commthread.register(self.pildevice,self.name)
166 self.pildevice.setactive(PILCONFIG.get(self.name,"active"))
167 self.pildevice.enable()
168 self.guiobject.enable()
170 def disable(self):
171 self.pildevice.disable()
172 self.guiobject.disable()
173 super().disable()
174#
175 def toggle_active(self):
176 self.guiobject.toggle_active()
177#
178# becomes visible, activate update timer
179#
180 def becomes_visible(self):
181 self.guiobject.becomes_visible()
182#
183# becomes invisible, deactivate update timer
184#
185 def becomes_invisible(self):
186 self.guiobject.becomes_invisible()
187#
188# Tab class for LIF drive
189#
190class cls_tabdrive(cls_tabdrivegeneric):
192 def __init__(self,parent,name):
193 super().__init__(parent,name)
194#
195# Set default values
196#
197 self.charset= PILCONFIG.get(self.name,"charset",CHARSET_HP71)
198 self.terminalcharsize= PILCONFIG.get(self.name,"directorycharsize",-1)
199#
200# create drive GUI object
201#
202 self.guiobject= cls_DriveWidget(self,self.name)
203#
204# add gui object to tab
205#
206 self.add_guiobject(self.guiobject)
207#
208# add cascading config menu and option(s)
209#
210 self.add_configwidget()
211 self.cBut.add_option("Character set","charset",T_STRING,charsets)
212 self.cBut.add_option("Font size","directorycharsize",T_INTEGER,[O_DEFAULT,11,12,13,14,15,16,17,18])
213#
214# create HPIL-device, notify object to drive gui object
215#
216 self.pildevice= cls_pildrive(isWINDOWS(),False)
217 self.guiobject.set_pildevice(self.pildevice)
218 self.cBut.config_changed_signal.connect(self.do_tabconfig_changed)
219#
220# Tab class for raw drive
221#
222class cls_tabrawdrive(cls_tabdrivegeneric):
224 def __init__(self,parent,name):
225 super().__init__(parent,name)
226#
227# create drive GUI object
228#
229 self.guiobject= cls_RawDriveWidget(self,self.name)
230#
231# add gui object to tab
232#
233 self.add_guiobject(self.guiobject)
234#
235# create HPIL-device, notify object to drive gui object
236#
237 self.pildevice= cls_pildrive(isWINDOWS(),True)
238 self.guiobject.set_pildevice(self.pildevice)
239#
240# Generic drive widget class (contains definitions only) -----------------------
241#
242class cls_GenericDriveWidget(QtWidgets.QWidget):
244 DEV_CASS=0
245 DEV_DISK=1
246 DEV_HDRIVE1=2
247 DEV_FDRIVE1=3
249 deviceinfo= { }
250 deviceinfo[DEV_CASS]=["",0x10]
251 deviceinfo[DEV_DISK]=["HP9114B",0x10]
252 deviceinfo[DEV_HDRIVE1]=["HDRIVE1",0x10]
253 deviceinfo[DEV_FDRIVE1]=["FDRIVE1",0x10]
255 # Medium types
256 MEDIUM_CASS=0
257 MEDIUM_DISK=1
258 MEDIUM_HDRIVE1=2
259 MEDIUM_HDRIVE2=3
260 MEDIUM_HDRIVE4=4
261 MEDIUM_HDRIVE8=5
262 MEDIUM_HDRIVE16=6
263 MEDIUM_UNKNOWN= -1
265 mediainfo= { }
266 mediainfo[MEDIUM_CASS]=['HP82161A Cassette',2,1,256]
267 mediainfo[MEDIUM_DISK]=['HP9114B double sided disk',77,2,16]
268 mediainfo[MEDIUM_HDRIVE1]=['HDRIVE1 640K disk',80,2,16]
269 mediainfo[MEDIUM_HDRIVE2]=['HDRIVE1 2MB disk',125,1,64]
270 mediainfo[MEDIUM_HDRIVE4]=['HDRIVE1 4MB disk',125,2,64]
271 mediainfo[MEDIUM_HDRIVE8]=['HDRIVE1 8MB disk',125,4,64]
272 mediainfo[MEDIUM_HDRIVE16]=['HDRIVE1 16MB disk',125,8,64]
273 mediainfo[MEDIUM_UNKNOWN]=['unknown',0,0,0]
275#
276# Raw drive widget class -----------------------------------------------------
277#
278class cls_RawDriveWidget(cls_GenericDriveWidget):
280 def __init__(self,parent,name):
281 super().__init__()
282#
283# Set default values
284#
285 self.name=name
286 self.parent=parent
287#
288# Set default values
289#
290 self.filename= PILCONFIG.get(self.name,"filename","")
291 self.medium= PILCONFIG.get(self.name,"medium",self.MEDIUM_HDRIVE1)
292 deviceName,self.tracks,self.surfaces,self.blocks=self.mediainfo[self.medium]
293 self.did=PILCONFIG.get(self.name,"did",self.deviceinfo[self.DEV_HDRIVE1][0])
294#
295# The accessory id is always 0x10
296#
297 self.aid=0x10
298 self.pildevice=None
299#
300# Build Gui, filename first
301#
302 self.hbox1= QtWidgets.QHBoxLayout()
303 self.lbltxt1=QtWidgets.QLabel("RAW Image File: ")
304 self.lblFilename=QtWidgets.QLabel()
305 self.butFilename=QtWidgets.QPushButton()
306 self.butFilename.setAutoDefault(False)
307 self.butFilename.setText("change")
308 self.hbox1.addWidget(self.lbltxt1)
309 self.hbox1.addWidget(self.lblFilename)
310 self.hbox1.addStretch(1)
311 self.hbox1.addWidget(self.butFilename)
312# self.hbox1.setContentsMargins(15,10,10,5)
313 self.hbox1.setContentsMargins(0,0,0,0)
314#
315# Combo box for medium type
316#
317 self.hbox2= QtWidgets.QHBoxLayout()
318 self.grid=QtWidgets.QGridLayout()
319 self.lbltxt2=QtWidgets.QLabel("Medium type ")
320 self.grid.addWidget(self.lbltxt2,0,0)
321 self.comboMedium=QtWidgets.QComboBox()
322 for i in self.mediainfo.keys():
323 txt=self.mediainfo[i][0]
324 if i != self.MEDIUM_UNKNOWN:
325 self.comboMedium.addItem(txt)
326 self.comboMedium.setCurrentIndex(self.medium)
327 self.grid.addWidget(self.comboMedium,0,1)
328 self.lblMediumText=QtWidgets.QLabel(self.mediumText())
329 self.grid.addWidget(self.lblMediumText,0,2)
331#
332# editable combo box for DID
333#
334 self.lbltxt3=QtWidgets.QLabel("DID ")
335 self.grid.addWidget(self.lbltxt3,1,0)
336 self.comboDID=QtWidgets.QComboBox()
337 self.comboDID.setEditable(True)
338 for i in self.deviceinfo.keys():
339 txt=self.deviceinfo[i][0]
340 self.comboDID.addItem(txt)
341 self.comboDID.setEditText(self.did)
342 self.grid.addWidget(self.comboDID,1,1)
343 self.hbox2.addLayout(self.grid)
344 self.hbox2.addStretch(1)
345#
346# assemble layouts
347#
348 self.vbox= QtWidgets.QVBoxLayout()
349 self.vbox.addLayout(self.hbox1)
350 self.vbox.addLayout(self.hbox2)
351 self.vbox.addStretch(1)
352 self.setLayout(self.vbox)
353#
354# basic initialization
355#
356 self.lblFilename.setText(self.filename)
357# self.butFilename.setEnabled(False)
358 self.lblFilename.setText(self.filename)
359 self.butFilename.clicked.connect(self.do_filenameChanged)
360 self.comboMedium.currentIndexChanged.connect(self.do_comboMediumChanged)
361# note: every change of the text triggers this callback
362 self.comboDID.currentTextChanged.connect(self.do_comboDIDChanged)
363#
364# set HP-IL device
365#
366 def set_pildevice(self,pildevice):
367 self.pildevice= pildevice
369 def mediumText(self):
370 totalblocks=self.tracks*self.surfaces*self.blocks
371 totalbytes=totalblocks*256
372 return("Medium Layout: ({}/{}/{}), Size: {} blocks ({} bytes).".format(self.tracks,self.surfaces,self.blocks,totalblocks, totalbytes))
373#
374# Callbacks
375#
376 def do_comboMediumChanged(self):
377 self.medium=self.comboMedium.currentIndex()
378 deviceName,self.tracks,self.surfaces,self.blocks=self.mediainfo[self.medium]
379 self.lblMediumText.setText(self.mediumText())
380 self.pildevice.setlocked(True)
381 self.pildevice.sethdisk(self.filename,self.tracks,self.surfaces,self.blocks)
382 self.pildevice.setlocked(False)
383 PILCONFIG.put(self.name,'medium',self.medium)
384 try:
385 PILCONFIG.save()
386 except PilConfigError as e:
387 reply=QtWidgets.QMessageBox.critical(self.parent.parent.ui,'Error',e.msg+': '+e.add_msg,QtWidgets.QMessageBox.Ok,QtWidgets.QMessageBox.Ok)
390 def do_comboDIDChanged(self):
391 self.did=self.comboDID.currentText()
392 self.pildevice.setdevice(self.did,self.aid)
393 PILCONFIG.put(self.name,'did',self.did)
394 try:
395 PILCONFIG.save()
396 except PilConfigError as e:
397 reply=QtWidgets.QMessageBox.critical(self.parent.parent.ui,'Error',e.msg+': '+e.add_msg,QtWidgets.QMessageBox.Ok,QtWidgets.QMessageBox.Ok)
399 def do_filenameChanged(self):
400 flist= self.get_rawFilename()
401 if flist is None:
402 return
403 self.filename=flist[0]
404 PILCONFIG.put(self.name,'filename',self.filename)
405 try:
406 PILCONFIG.save()
407 except PilConfigError as e:
408 reply=QtWidgets.QMessageBox.critical(self.parent.parent.ui,'Error',e.msg+': '+e.add_msg,QtWidgets.QMessageBox.Ok,QtWidgets.QMessageBox.Ok)
410 if self.pildevice is not None:
411 self.pildevice.setlocked(True)
412 self.pildevice.sethdisk(self.filename,self.tracks,self.surfaces,self.blocks)
413 self.pildevice.setlocked(False)
414 self.lblFilename.setText(self.filename)
415#
416# enter raw filename, file must exist because __wrec__ does not create a
417# non existing file
418#
419 def get_rawFilename(self):
420 dialog=QtWidgets.QFileDialog()
421 dialog.setWindowTitle("Select RAW Image File")
422 dialog.setAcceptMode(QtWidgets.QFileDialog.AcceptOpen)
423 dialog.setFileMode(QtWidgets.QFileDialog.ExistingFile)
424 dialog.setNameFilters( ["RAW Image File (*.dat *.DAT *.img *.IMG)", "All Files (*)"] )
425 dialog.setOptions(QtWidgets.QFileDialog.DontUseNativeDialog)
426 if dialog.exec():
427 return dialog.selectedFiles()
429 def reconfigure(self):
430 return
432 def enable(self):
433 deviceName,tracks,surfaces,blocks=self.mediainfo[self.medium]
434 self.pildevice.sethdisk(self.filename,tracks,surfaces,blocks)
435 self.pildevice.setdevice(self.did,self.aid)
436 return
438 def disable(self):
439 return
441 def toggle_active(self):
442 return
444 def becomes_visible(self):
445 return
447 def becomes_invisible(self):
448 return
450#
451# Drive widget class --------------------------------------------------------
452#
453class cls_DriveWidget(cls_GenericDriveWidget):
455 def __init__(self,parent,name):
456 super().__init__()
457#
458# Set default values
459#
460 self.name=name
461 self.parent=parent
462#
463# Set default values
464#
465 self.filename= PILCONFIG.get(self.name,"filename","")
466 self.drivetype= PILCONFIG.get(self.name,"drivetype",self.DEV_HDRIVE1)
467 self.papersize=PILCONFIG.get("pyilper","papersize")
468 self.pildevice=None
470#
471# Header with file name and volume information
472#
473 self.hbox1= QtWidgets.QHBoxLayout()
474 self.lbltxt1=QtWidgets.QLabel("LIF Image File: ")
475 self.lblFilename=QtWidgets.QLabel()
476 self.butFilename=QtWidgets.QPushButton()
477 self.butFilename.setAutoDefault(False)
478 self.butFilename.setText("change")
479 self.hbox1.addWidget(self.lbltxt1)
480 self.hbox1.addWidget(self.lblFilename)
481 self.hbox1.addStretch(1)
482 self.hbox1.addWidget(self.butFilename)
483# self.hbox1.setContentsMargins(15,10,10,5)
484 self.hbox1.setContentsMargins(0,0,0,0)
486#
487# drive type group box
488#
489 self.gbox = QtWidgets.QGroupBox()
490 self.gbox.setFlat(True)
491 self.gbox.setTitle("Drive Type")
492 self.vbox2= QtWidgets.QVBoxLayout()
493 self.radbutCass = QtWidgets.QRadioButton(self.gbox)
494 self.radbutCass.setText("HP82161A")
495 self.vbox2.addWidget(self.radbutCass)
496 self.radbutDisk = QtWidgets.QRadioButton(self.gbox)
497 self.radbutDisk.setText("HP9114B")
498 self.radbutHdrive1 = QtWidgets.QRadioButton(self.gbox)
499 self.vbox2.addWidget(self.radbutDisk)
500 self.radbutHdrive1.setText("HDRIVE1")
501 self.vbox2.addWidget(self.radbutHdrive1)
502 self.gbox.setLayout(self.vbox2)
503 self.gbox_buttonlist=[self.radbutCass, self.radbutDisk, self.radbutHdrive1]
504 self.vbox3= QtWidgets.QVBoxLayout()
505 self.vbox3.addWidget(self.gbox)
507#
508# Initialize file management tool buttons
509#
510 self.butPack= QtWidgets.QPushButton("Pack")
511 self.butPack.setEnabled(False)
512 self.butPack.setAutoDefault(False)
513 self.vbox3.addWidget(self.butPack)
514 self.butImport= QtWidgets.QPushButton("Import")
515 self.butImport.setEnabled(False)
516 self.butImport.setAutoDefault(False)
517 self.vbox3.addWidget(self.butImport)
518 self.butLabel= QtWidgets.QPushButton("Label")
519 self.butLabel.setEnabled(False)
520 self.butLabel.setAutoDefault(False)
521 self.vbox3.addWidget(self.butLabel)
522 self.butDirList= QtWidgets.QPushButton("Directory Listing")
523 self.butDirList.setEnabled(False)
524 self.butDirList.setAutoDefault(False)
525 self.vbox3.addWidget(self.butDirList)
526 self.vbox3.addStretch(1)
527#
528# directory widget
529#
530 self.vbox1= QtWidgets.QVBoxLayout()
531 self.lifdir=cls_LifDirWidget(self,self.name,0,FONT,self.papersize)
532 self.vbox1.addWidget(self.lifdir)
534 self.hbox2= QtWidgets.QHBoxLayout()
535 self.hbox2.addLayout(self.vbox1)
536 self.hbox2.addLayout(self.vbox3)
537# self.hbox2.setContentsMargins(10,3,10,3)
538 self.hbox2.setContentsMargins(0,0,0,0)
540#
541 self.vbox= QtWidgets.QVBoxLayout()
542 self.vbox.addLayout(self.hbox1)
543 self.vbox.addLayout(self.hbox2)
544 self.setLayout(self.vbox)
545#
546# basic initialization
547#
548 self.lblFilename.setText(self.filename)
549 self.butFilename.setEnabled(False)
550 self.setDrivetypeChecked()
551 for w in self.gbox_buttonlist:
552 w.setEnabled(False)
553 self.lblFilename.setText(self.filename)
554 self.lifdir.setFileName(self.filename)
555#
556# connect actions
557#
558 self.radbutCass.clicked.connect(self.do_drivetypeChanged)
559 self.radbutDisk.clicked.connect(self.do_drivetypeChanged)
560 self.radbutHdrive1.clicked.connect(self.do_drivetypeChanged)
561 self.butFilename.clicked.connect(self.do_filenameChanged)
562#
563 self.butPack.clicked.connect(self.do_pack)
564 self.butImport.clicked.connect(self.do_import)
565 self.butLabel.clicked.connect(self.do_label)
566 self.butDirList.clicked.connect(self.do_dirlist)
567#
568# refresh timer
569#
570 self.timer=QtCore.QTimer()
571 self.timer.timeout.connect(self.update_hdrive)
572 self.update_pending= False
573#
574# enable/disable GUI elements
575#
576 self.toggle_controls()
577#
578# set HP-IL device
579#
580 def set_pildevice(self,pildevice):
581 self.pildevice= pildevice
582#
583# reconfigure
584#
585 def reconfigure(self):
586 self.lifdir.reconfigure()
587#
588# enable/disable
589#
590 def enable(self):
591 did,aid= self.deviceinfo[self.drivetype]
592 self.pildevice.setdevice(did,aid)
593 status, tracks, surfaces, blocks= self.lifMediumCheck(self.filename,True)
594 if not status:
595 self.filename=""
596 PILCONFIG.put(self.name,'filename',self.filename)
597 try:
598 PILCONFIG.save()
599 except PilConfigError as e:
600 reply=QtWidgets.QMessageBox.critical(self.parent.parent.ui,'Error',e.msg+': '+e.add_msg,QtWidgets.QMessageBox.Ok,QtWidgets.QMessageBox.Ok)
601 self.pildevice.sethdisk(self.filename,tracks,surfaces,blocks)
602 self.lblFilename.setText(self.filename)
603 self.lifdir.setFileName(self.filename)
605 def disable(self):
606 return
607#
608# enable/disable lif image file controls:
609# - change drive type
610# - change drive type
611# - tools teardown menu
613 def toggle_active(self):
614 self.toggle_controls()
616 def toggle_controls(self):
617 self.butFilename.setEnabled(True)
618 for w in self.gbox_buttonlist:
619 w.setEnabled(True)
620 if self.parent.active:
621 self.butPack.setEnabled(False)
622 self.butImport.setEnabled(False)
623 self.butLabel.setEnabled(False)
624 self.butDirList.setEnabled(False)
625 else:
626 if self.filename != "" and self.parent.parent.lifutils_installed:
627 self.butPack.setEnabled(True)
628 self.butImport.setEnabled(True)
629 self.butLabel.setEnabled(True)
630 self.butDirList.setEnabled(True)
631#
632# set drive type checked
633#
634 def setDrivetypeChecked(self):
635 i=0
636 for w in self.gbox_buttonlist:
637 if i == self.drivetype:
638 w.setChecked(True)
639 else:
640 w.setChecked(False)
641 i+=1
642#
643# becomes visible, activate update timer
644#
645 def becomes_visible(self):
646 self.timer.start(REFRESH_RATE)
647 return
648#
649# becomes invisible, deactivate update timer
650#
651 def becomes_invisible(self):
652 self.timer.stop()
653 return
654#
655# Callbacks
656#
657 def do_filenameChanged(self):
658 flist= self.get_lifFilename()
659 if flist is None:
660 return
661 status, tracks, surfaces, blocks= self.lifMediumCheck(flist[0],False)
662 if status:
663 self.filename=flist[0]
664 else:
665 self.filename=""
666 PILCONFIG.put(self.name,'filename',self.filename)
667 try:
668 PILCONFIG.save()
669 except PilConfigError as e:
670 reply=QtWidgets.QMessageBox.critical(self.parent.parent.ui,'Error',e.msg+': '+e.add_msg,QtWidgets.QMessageBox.Ok,QtWidgets.QMessageBox.Ok)
672 if self.pildevice is not None:
673 self.pildevice.setlocked(True)
674 self.pildevice.sethdisk(self.filename,tracks,surfaces,blocks)
675 self.pildevice.setlocked(False)
676 self.lblFilename.setText(self.filename)
677 self.lifdir.setFileName(self.filename)
678 if self.filename=="":
679 self.lifdir.clear()
680 else:
681 self.lifdir.refresh()
682 self.toggle_controls()
684 def do_drivetypeChanged(self):
685 i=0
686 for w in self.gbox_buttonlist:
687 if w.isChecked():
688 self.drivetype=i
689 break
690 i+=1
691 PILCONFIG.put(self.name,'drivetype', self.drivetype)
692#
693# remove filename
694#
695 if self.filename != "":
696 self.filename=""
697 PILCONFIG.put(self.name,'filename',self.filename)
698 self.lblFilename.setText(self.filename)
699 self.lifdir.clear()
700 reply=QtWidgets.QMessageBox.warning(self.parent.parent.ui,'Warning',"Drive type changed. You have to reopen the LIF image file",QtWidgets.QMessageBox.Ok,QtWidgets.QMessageBox.Ok)
701 did,aid= self.deviceinfo[self.drivetype]
702 try:
703 PILCONFIG.save()
704 except PilConfigError as e:
705 reply=QtWidgets.QMessageBox.critical(self.parent.parent.ui,'Error',e.msg+': '+e.add_msg,QtWidgets.QMessageBox.Ok,QtWidgets.QMessageBox.Ok)
706 if self.pildevice is not None:
707 self.pildevice.setlocked(True)
708 self.pildevice.setdevice(did,aid)
709 self.pildevice.setlocked(False)
710 self.toggle_controls()
712 def do_pack(self):
713 cls_lifpack.execute(self.filename)
714 self.lifdir.refresh()
716 def do_import(self):
717 workdir=PILCONFIG.get('pyilper','workdir')
718 cls_lifimport.execute(self.filename, workdir)
719 self.lifdir.refresh()
721 def do_label(self):
722 oldlabel=self.lifdir.getLabel()
723 cls_liflabel.execute(self.filename, oldlabel)
724 self.lifdir.refresh()
726 def do_dirlist(self):
727#
728# get medium summary, return if blank or no medium
729#
730 mediumsummary= self.lifdir.getMediumSummary()
731 linebreak= mediumsummary.find("Label")
732 if linebreak == -1:
733 return
734 mediuminfo= mediumsummary[:linebreak]
735 labelinfo=mediumsummary[linebreak:]
736#
737# get output file name
738#
739 flist= cls_pdfprinter.get_pdfFilename()
740 if flist is None:
741 return
742 output_filename= flist[0]
743#
744# initialize pdf printer
745#
746 title="Directory listing of: "+self.filename
747 pdfprinter=cls_pdfprinter(self.papersize,PDF_ORIENTATION_PORTRAIT, output_filename,title,True,1)
748 pdfprinter.begin()
750 model= self.lifdir.getModel()
751 cols=6
752 rows= self.lifdir.getRowCount()
753 pdfprinter.print_item(cls_textItem(mediuminfo))
754 pdfprinter.print_item(cls_textItem(labelinfo))
755 pdfprinter.print_item(cls_textItem(self.lifdir.getDirSummary()))
756 pdfprinter.print_item(cls_textItem("{:12} {:8} {:>6}/{:6} {:7} {:7}".format("Filename","Type","Size","Space","Date","Time")))
757 for i in range (rows):
758 line="{:12} {:8} {:>6}/{:6} {:7} {:7}".format(model.item(i,0).text().strip(), model.item(i,1).text().strip(), model.item(i,2).text().strip(), model.item(i,3).text().strip(), model.item(i,4).text(), model.item(i,5).text())
759 pdfprinter.print_item(cls_textItem(line))
760 pdfprinter.end()
762#
763# Drive tab: refresh directory listing of medium
764#
765 def update_hdrive(self):
766 if self.filename=="":
767 return
768 if self.pildevice is None:
769 return
770 tm=time.time()
771 modified, timestamp= self.pildevice.ismodified()
772 self.update_pending= self.update_pending or modified
773 if self.update_pending:
774 if tm - timestamp > NOT_TALKER_SPAN:
775 self.refreshDirList()
776 self.update_pending= False
778 def refreshDirList(self):
779 if self.filename=="":
780 return
781 self.pildevice.acquireaccesslock()
782 self.lifdir.refresh()
783 self.pildevice.releaseaccesslock()
784#
785# enter lif filename
786#
787 def get_lifFilename(self):
788 dialog=QtWidgets.QFileDialog()
789 dialog.setWindowTitle("Select LIF Image File")
790 dialog.setAcceptMode(QtWidgets.QFileDialog.AcceptOpen)
791 dialog.setFileMode(QtWidgets.QFileDialog.AnyFile)
792 dialog.setNameFilters( ["LIF Image File (*.dat *.DAT *.lif *.LIF)", "All Files (*)"] )
793 dialog.setOptions(QtWidgets.QFileDialog.DontUseNativeDialog)
794# dialog.setDirectory(PILCONFIG.get('pyilper','workdir'))
795 if dialog.exec():
796 return dialog.selectedFiles()
797#
798# Check lif image file, returns status, tracks, surfaces, blocks
799# If valid LIF1 medium and medium is compatible to device:
800# return True, tracks, surfaces, blocks of medium
801# else:
802# return False and default layout of device
803#
804 def lifMediumCheck(self,filename,quiet):
805 defaultmedium= self.getDefaultMedium(self.drivetype)
806 def_name, def_tracks, def_surfaces, def_blocks= self.mediainfo[defaultmedium]
807 status, tracks, surfaces, blocks= self.getMediumInfo(filename)
808 if status ==0: # medium info found
809 return [True, tracks, surfaces, blocks]
810 elif status==1: # file dos not exist or cannot be opened
811 return [True, def_tracks, def_surfaces, def_blocks]
812 elif status==2:
813 if not quiet:
814 reply=QtWidgets.QMessageBox.critical(self.parent.parent.ui,'Error',"File does not contain a LIF type 1 medium.",QtWidgets.QMessageBox.Ok,QtWidgets.QMessageBox.Ok)
815 return [False, def_tracks, def_surfaces, def_blocks]
816 elif status==3:
817 if not quiet:
818 reply=QtWidgets.QMessageBox.warning(self.parent.parent.ui,'Warning',"File does not contain a LIF type 1 medium with valid layout information. Using default layout of current drive type.",QtWidgets.QMessageBox.Ok,QtWidgets.QMessageBox.Ok)
819 return [True, def_tracks, def_surfaces, def_blocks]
820#
821# get media info from lif header
822#
823 def getMediumInfo(self,filename):
825#
826# read lif file header
827#
828 try:
829 if isWINDOWS():
830 fd= os.open(filename,os.O_RDONLY | os.O_BINARY)
831 else:
832 fd= os.open(filename,os.O_RDONLY)
833 except OSError:
834 return [1,0,0,0] # file does not exist or cannot be opened
835 try:
836 b=os.read(fd,256)
837 os.close(fd)
838 except OSError:
839 return [1,0,0,0] # file read error
840 if len(b) < 256:
841 return [2,0,0,0] # not lif type 1 file
842#
843# do we have a LIF type 1 file
844#
845 lifmagic= getLifInt(b,0,2)
846 dirstart=getLifInt(b,8,4)
847# if not(lifmagic == 0x8000 and dirstart == 2):
848 if not(lifmagic == 0x8000):
849 return [2,0,0,0] # no lif type 1 file
850#
851# get medium layout
852#
853 tracks= getLifInt(b,24,4)
854 surfaces= getLifInt(b,28,4)
855 blocks= getLifInt(b,32,4)
856 if (tracks == surfaces) and (surfaces == blocks) :
857 return [3,0,0,0] # no valid media layout information
858 return [0, tracks, surfaces, blocks]
861 def getDefaultMedium(self,device):
862 if device== self.DEV_CASS:
863 return self.MEDIUM_CASS
864 if device== self.DEV_DISK:
865 return self.MEDIUM_DISK
866 if device== self.DEV_HDRIVE1:
867 return self.MEDIUM_HDRIVE1
868#
869# LifDir Widget -----------------------------------------------------------
870#
871class TableModel(QtGui.QStandardItemModel):
872 _sort_order = QtCore.Qt.AscendingOrder
874 def sortOrder(self):
875 return self._sort_order
877 def sort(self, column, order):
878 self._sort_order = order
879 super().sort(column, order)
881class DirTableView(QtWidgets.QTableView):
883 def __init__(self,parent,papersize):
884 super().__init__(parent)
885 self.parent=parent
886 self.papersize= papersize
887#
888# custom mouse press even. A click to a selected row unselects it
889#
890 def mousePressEvent(self, event):
891 if event.button()== QtCore.Qt.LeftButton:
892#DEPRECATED pos
893 row=self.indexAt(event.pos()).row()
894 isSelected=False
895#
896# check if the row is already selected
897#
898 i =self.selectionModel().selection().indexes()
899 if i:
900 if i[0].row()== row:
901 isSelected=True
902#
903# yes, clear
904#
905 if isSelected:
906 self.selectionModel().clear()
907 event.accept()
908 return
909#
910# no, select
911#
912 else:
913 self.selectRow(row)
914 event.accept()
915 return
916#
917# No left button, let others do the job
918#
919 event.ignore()
920#
921# context menu
922#
923 def contextMenuEvent(self, event):
924 if self.parent.parent.parent.active:
925 event.accept()
926 return
927 if not self.parent.parent.parent.parent.lifutils_installed:
928 event.accept()
929 return
930 i= self.selectionModel().selection().indexes()
931 if i:
932 row=i[0].row()
933 model=self.parent.getModel()
934 imagefile= self.parent.getFilename()
935 liffilename=model.item(row,0).text()
936 liffiletype=model.item(row,1).text()
937 menu = QtWidgets.QMenu()
938 exportAction = menu.addAction("Export")
939 purgeAction = menu.addAction("Purge")
940 renameAction = menu.addAction("Rename")
941 ft=get_finfo_name(liffiletype)
942#
943# view action
944#
945 viewAction= None
946 if ft is not None:
947 if get_finfo_type(ft)[1] != "":
948 viewAction= menu.addAction("View")
949#
950# create barcode action
951#
952 barcodeAction= None
953 if ft is not None:
954 if ft== 0xE080 or ft== 0xE0D0:
955 barcodeAction= menu.addAction("Barcode")
957 action = menu.exec(self.mapToGlobal(event.pos()))
958 if action is None:
959 event.accept()
960 return
961 workdir=PILCONFIG.get('pyilper','workdir')
962 charset=PILCONFIG.get(self.parent.parent.name,"charset")
963 if action ==exportAction:
964 cls_lifexport.execute(imagefile,liffilename,liffiletype,workdir)
965 elif action== purgeAction:
966 cls_lifpurge.execute(imagefile,liffilename)
967 self.parent.refresh()
968 elif action== renameAction:
969 cls_lifrename.execute(imagefile,liffilename)
970 self.parent.refresh()
971 elif action== viewAction:
972 cls_lifview.execute(imagefile, liffilename, liffiletype,workdir, charset)
973 elif action== barcodeAction:
974 cls_lifbarcode.execute(imagefile,liffilename,ft,self.papersize)
975 event.accept()
977class cls_LifDirWidget(QtWidgets.QWidget):
979 def __init__(self,parent,name,rows,font_name,papersize):
980 super().__init__(parent)
981 self.parent=parent
982 self.name=name
983 self.__papersize__=papersize
984 self.__font_name__= font_name
985 self.__font_size__= 13
986 self.__table__ = DirTableView(self,self.__papersize__) # Table view for dir
987 self.__table__.setSortingEnabled(False) # no sorting
988##
989 self.__table__.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
990#
991# switch off grid, no focus, no row selection
992#
993 self.__table__.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
994 self.__table__.setFocusPolicy(QtCore.Qt.NoFocus)
995 self.__table__.setShowGrid(False)
996 self.__columns__=6 # 5 rows for directory listing
997 self.__rowcount__=0 # number of rows in table
998 self.__filename__="" # LIF filename
999 self.__label__="" # Label of lif file
1000 self.__model__ = TableModel(rows, self.__columns__, self.__table__)
1001#
1002# populate header , set column size
1003#
1004 self.__model__.setHeaderData(0,QtCore.Qt.Horizontal,"File")
1005 self.__model__.setHeaderData(1,QtCore.Qt.Horizontal,"Type")
1006 self.__model__.setHeaderData(2,QtCore.Qt.Horizontal,"Size")
1007 self.__model__.setHeaderData(3,QtCore.Qt.Horizontal,"Space")
1008 self.__model__.setHeaderData(4,QtCore.Qt.Horizontal,"Date")
1009 self.__model__.setHeaderData(5,QtCore.Qt.Horizontal,"Time")
1010 self.__table__.setModel(self.__model__)
1011 self.__table__.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Stretch)
1012#
1013# handle click to header -> sorting
1014#
1015 self.__table__.horizontalHeader().sectionClicked.connect(
1016 self.handleSectionClicked)
1017#
1018# no vertical header
1019#
1020 self.__table__.verticalHeader().setVisible(False)
1021 self.__table__.verticalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Fixed)
1022#
1023# set font for directory listing, adjust row height
1024#
1025# self.__font__= QtGui.QFont(self.__font_name__)
1026 self.__font__= QtGui.QFont()
1027# self.__font__.setPixelSize(13)
1028# metrics= QtGui.QFontMetrics(self.__font__)
1029# self.__table__.verticalHeader().setDefaultSectionSize(metrics.height()+1)
1031#
1032# add labels for text information (label, medium, directory)
1033#
1034 layout = QtWidgets.QVBoxLayout(self)
1035 self.__labelMedium__= QtWidgets.QLabel()
1036 self.__labelMedium__.setText("")
1037 layout.addWidget(self.__labelMedium__)
1038 self.__labelDir__= QtWidgets.QLabel()
1039 self.__labelDir__.setText("")
1040 layout.addWidget(self.__labelDir__)
1041 layout.addWidget(self.__table__)
1043 self.reconfigure()
1044#
1045# reconfigure the font size of the directory list
1046#
1047 def reconfigure(self):
1048 self.__font_size__= PILCONFIG.get_dual(self.name,"directorycharsize")
1049 self.__font__.setPixelSize(self.__font_size__)
1050 metrics= QtGui.QFontMetrics(self.__font__)
1051 self.__table__.verticalHeader().setDefaultSectionSize(metrics.height()+1)
1052 self.refresh()
1054 def getModel(self):
1055 return(self.__model__)
1057 def getFilename(self):
1058 return(self.__filename__)
1060 def getLabel(self):
1061 return(self.__label__)
1063 def getRowCount(self):
1064 return(self.__rowcount__)
1066 def getMediumSummary(self):
1067 return(self.__labelMedium__.text())
1069 def getDirSummary(self):
1070 return(self.__labelDir__.text())
1071#
1072# connect lif data file
1073#
1074 def setFileName(self,filename):
1075 self.__filename__= filename
1076 self.refresh()
1078#
1079# clear info
1080#
1081 def clear(self):
1082 self.__labelMedium__.setText("")
1083 self.__labelDir__.setText("")
1084 if self.__rowcount__==0:
1085 return
1086 self.__model__.removeRows(0, self.__rowcount__)
1087 self.__rowcount__=0
1088 return
1090#
1091# read and display directory
1092#
1093 def refresh(self):
1094 if self.__filename__== "":
1095 return
1096 self.clear()
1097 try:
1098 lif=cls_LifFile()
1099 lif.set_filename(self.__filename__)
1100 lif.lifopen()
1101 except LifError:
1102 return
1103 lifdir= cls_LifDir(lif)
1104 lifdir.open()
1105 lifdir.rewind()
1106 dir_start, dir_length, no_tracks, no_surfaces, no_blocks, label, initdatetime=lif.getLifHeader()
1107 self.__label__= label
1108 totalblocks=no_tracks* no_surfaces* no_blocks
1109 totalbytes= totalblocks* 256
1110#
1111# handle invalid values
1112#
1113 if no_tracks> 125 or no_surfaces>8 or no_blocks > 256 or \
1114 no_tracks==0 or no_surfaces==0 or no_blocks ==0:
1116 self.__labelMedium__.setText("Medium Layout: (invalid). Label: {:6s}, formatted: {:s}".format(label, initdatetime))
1117 else:
1118 self.__labelMedium__.setText("Medium Layout: ({}/{}/{}), Size: {} blocks ({} bytes). Label: {:6s}, formatted: {:s}".format(no_tracks,no_surfaces,no_blocks,totalblocks, totalbytes, label, initdatetime))
1119 self.__labelDir__.setText("Directory size: {} entries ({} used). Last block used: {}".format(dir_length*8, lifdir.num_entries, lifdir.lastblock))
1121#
1122# populate directory listing
1123#
1124 while True:
1125 r= lifdir.getNextEntry()
1126 if r == []:
1127 break
1128 name, ftype_num, start_block, alloc_blocks, datetime, ftype, length= r
1129 x=[name,ftype ,"{:-8d}".format(length),"{:-8d}".format(alloc_blocks*256),datetime.split(sep=' ')[0],datetime.split(sep=' ')[1]]
1130 for column in range(self.__columns__):
1131 item = QtGui.QStandardItem(x[column])
1132 item.setFont(self.__font__)
1133 item.setTextAlignment(QtCore.Qt.AlignLeft)
1134 item.setFlags(item.flags() & ~QtCore.Qt.ItemIsEditable)
1135 self.__model__.setItem(self.__rowcount__, column, item)
1136 self.__rowcount__+=1
1137 lif.lifclose()
1138#
1139# go to end of scroll area
1140#
1141 self.__table__.verticalScrollBar().setRange(0,10000)
1142 self.__table__.verticalScrollBar().setValue(10000)
1144#
1145# handle click to header field and sort column
1146#
1147 def handleSectionClicked(self, index):
1148 if index >=4: # not for date/time
1149 return
1150 if not self.__table__.isSortingEnabled():
1151 self.__table__.setSortingEnabled(True)
1152 self.__table__.sortByColumn(index,self.__model__.sortOrder())
1153 self.__table__.horizontalHeader().setSortIndicator(
1154 index, self.__table__.model().sortOrder())
1155 self.__table__.verticalScrollBar().setValue(0)
1156#
1157# HP-IL virtual disc class ---------------------------------------------------
1158#
1159# Initial release derived from ILPER 1.43 for Windows
1160#
1161# Changelog
1162#
1163# 09.02.2015 improvements and chages of ILPER 1.5
1164# - renamed __fetat__ to __ilstate__
1165# - renamed __outdta__ to __outdata__
1166# - fixed increase of __ptout__ in __outdta__ (case 3: block)
1167# - delete zero __ptout__ in DDT section of do_cmd
1168# - inserte zero __ptout__ in SDA section of do_rdy
1169# - fixed __ilstate__ usage in do_cmd (LAD/SAD)
1170# 03.03.2015 windows i/o compatibility
1171# - rewritten: __rrec__, __wrec__, __format_disc__ to
1172# 11.03.2015 more improvements and changes of ILPER 1.5
1173# - fix first sector of LIF-Image
1174# - set pyhsical medium information, set id
1175# - not implemented: enable auto extended address switch
1176# 21.03.2015 more header fixes for HP-41
1177# 19.05.2015 getMediumInfo removed
1178# 30.05.2015 fixed error in handling AP, added getstatus
1179# 06.10.2015 jsi:
1180# - class statement syntax update
1181# 21.11.2015 jsi:
1182# - removed SSRQ/CSRQ approach
1183# 29.11.2015 jsi:
1184# - introduced talker activity timer
1185# - introduced device lock
1186# 30.11.2015 jsi:
1187# - fixed idle timer mechanism
1188# - fixed header of HP82161 medium when formatted with an HP-71
1189# 02.12.2015 jsi:
1190# - fixed composition of the implementation byte array (4 byt int not byte!)
1191# - removed fix header of HP82161 medium when formatted with an HP-71
1192# 19.02.2016 jsi
1193# - refactored and merged new Ildev base class of Christoph Giesselink
1194# - improved os detection
1195# 08.07.2016 jsi
1196# - refactoring: windows platform flag is constructor parameter now
1197# 19.09.2017 jsi
1198# - duplicate definition of getLifInt and putLifInt removed
1199# 28.01.2017 jsi
1200# - removed self.__islocked__ in cls_pildrive because it hides the
1201# variable of cls_pildevbase
1202# 16.02.2020 jsi
1203# - call self.__clear_device__ if medium (lif image file) was changed.
1204# Clear the content of both buffers in the device clear subroutine
1205# (hint by Christoph Gießelink)
1206# - clear disk drive status after successful reading or writing
1207# (hint by Christoph Gießelink)
1208# - call self.__setstatus__ instead of setting the status variable directly
1209# - return write protect error in wrec if write to file fails instead of
1210# error code 29
1211#
1214class cls_pildrive(cls_pildevbase):
1216#
1217# Note: if we would like to implement a true "raw" device then we must
1218# add an option to the constructor to disable header fixing
1219#
1220 def __init__(self, isWindows,isRawDevice):
1221 super().__init__()
1223#
1224# HP-IL data and variables
1225#
1226 self.__aid__ = 0x10 # accessory id = mass storage
1227 self.__defaddr__ = 2 # default address alter AAU
1228 self.__did__ = "" # device id
1229#
1230# disk management variables
1231#
1232 self.__devl__ =0 # device listener
1233 self.__devt__ =0 # device talker
1234 self.__oc__ = 0 # byte pointer
1235 self.__pe__ = 0 # record pointer
1236 self.__pe0__=0
1237 self.__fpt__ = False # flag pointer
1238 self.__flpwr__ = 0 # flag partial write
1239 self.__ptout__ = 0 # pointer out
1240 self.__modified__= False # medium modification flag
1241 self.__tracks__= 0 # no of tracks of medium
1242 self.__surfaces__= 0 # no of surfaces of medium
1243 self.__blocks__= 0 # no of blocks of medium
1245 self.__lif__= bytearray(12) # device info
1246 self.__nbe__=0 # max. number of sectors
1247 self.__buf0__= bytearray(256) # buffer 0
1248 self.__buf1__= bytearray(256) # buffer 1
1249 self.__hdiscfile__= "" # disc file
1250 self.__isactive__= False # device active in loop
1251 self.__access_lock__= threading.Lock()
1252 self.__timestamp__= time.time() # last time of beeing talker
1254 self.__isWindows__= isWindows # true, if Windows platform
1255 self.__isRawDevice__= isRawDevice # true, if drive is used as raw device
1257#
1258# public ------------
1259#
1261#
1262# enable/disable (do nothing)
1263#
1264 def enable(self):
1265 return
1267 def disable(self):
1268 return
1269#
1270# was image modified since last timestamp
1271#
1272 def ismodified(self):
1273 self.__access_lock__.acquire()
1274 if self.__modified__:
1275 self.__modified__= False
1276 self.__access_lock__.release()
1277 return (True, self.__timestamp__)
1278 else:
1279 self.__access_lock__.release()
1280 return (False, self.__timestamp__)
1281#
1282# lock device
1283#
1284 def acquireaccesslock(self):
1285 self.__access_lock__.acquire()
1287#
1288# release device
1289#
1290 def releaseaccesslock(self):
1291 self.__access_lock__.release()
1294#
1295# set new filename (disk change) and medium information
1296#
1297 def sethdisk(self,filename,tracks,surfaces,blocks):
1298 self.__hdiscfile__= filename
1299 self.__tracks__= tracks
1300 self.__surfaces__= surfaces
1301 self.__blocks__= blocks
1302 self.__nbe__= tracks*surfaces*blocks
1304 k=0
1305 for i in (24,16,8,0):
1306 self.__lif__[k]= tracks >> i & 0xFF
1307 k+=1
1308 for i in (24,16,8,0):
1309 self.__lif__[k]= surfaces >> i & 0xFF
1310 k+=1
1311 for i in (24,16,8,0):
1312 self.__lif__[k]= blocks >> i & 0xFF
1313 k+=1
1314 self.__clear_device__()
1315 self.__setstatus__(0)
1316#
1317# Note: the device status should be 23 (new media) here. This status
1318# is reset to zero after a SST was processed by the real drive which has not
1319# been implemented in pildevbase.py so far. Without that at least the
1320# HP-71B hangs on media initialization.
1321#
1322 return
1323#
1324# set aid and did of device
1325#
1326 def setdevice(self,did,aid):
1327 self.__aid__= aid
1328 if did== "":
1329 self.__did__= ""
1330 else:
1331 self.__did__=did
1332 self.__clear_device__()
1333 return
1334#
1335# private
1336#
1337#
1338# copy buffer 0 to buffer 1
1339#
1340 def __copybuf__(self):
1341 self.__oc__=0
1342 for i in range (256):
1343 self.__buf1__[i]= self.__buf0__[i]
1344 return
1346#
1347# exchange buffers
1348#
1349 def __exchbuf__(self):
1350 self.__oc__=0
1351 for i in range (256):
1352 x=self.__buf1__[i]
1353 self.__buf1__[i]= self.__buf0__[i]
1354 self.__buf0__[i]= x
1355 return
1356#
1357# read one sector n* pe (256 bytes) into buf0
1358#
1359 def __rrec__(self):
1360 self.__access_lock__.acquire()
1361 if self.__islocked__:
1362 self.__access_lock__.release()
1363 self.__setstatus__(20) # no medium error
1364 return
1365 try:
1366 if self.__isWindows__:
1367 fd= os.open(self.__hdiscfile__,os.O_RDONLY | os.O_BINARY)
1368 else:
1369 fd= os.open(self.__hdiscfile__,os.O_RDONLY)
1370 os.lseek(fd,self.__pe__ * 256, os.SEEK_SET)
1371 b=os.read(fd,256)
1372 os.close(fd)
1373 l=len(b)
1374# print("rrec record %d size %d" % (self.__pe__,l))
1375 self.__setstatus__(0) # success, clear status
1376 for i in range (l):
1377 self.__buf0__[i]= b[i]
1378 if l < 256:
1379 for i in range(l,256):
1380 self.__buf0__[i]=0x00
1381 except OSError as e:
1382 self.__setstatus__(20) # failed read always returns no medium error
1383 self.__access_lock__.release()
1384 return
1385#
1386# fix the header if record 0 (LIF header) is written
1387#
1388 def __fix_header__(self):
1389#
1390# LIF Version 1 header?
1391#
1393 if self.__buf0__[0x00]== 0x80 and self.__buf0__[0x01]== 0x00:
1394 tracks= getLifInt(self.__buf0__,24,4)
1395 surfaces=getLifInt(self.__buf0__,28,4)
1396 blocks=getLifInt(self.__buf0__,32,4)
1397#
1398# wrong media size information (HP firmware bug)?
1399#
1400 if(tracks == surfaces and surfaces == blocks):
1401 putLifInt(self.__buf0__,24,4,self.__tracks__)
1402 putLifInt(self.__buf0__,28,4,self.__surfaces__)
1403 putLifInt(self.__buf0__,32,4,self.__blocks__)
1404#
1405# LIF Version 1 fix (for HP41 initialized images)
1406#
1407 if self.__buf0__[0x14]!= 0x00 or self.__buf0__[0x15]!= 0x01:
1408 self.__buf0__[0x14]= 0x00
1409 self.__buf0__[0x15]= 0x01
1410#
1411# Fix garbage in label field (for HP41 initialized images)
1412#
1413 if self.__buf0__[0x02] != 0x20 and (self.__buf0__[0x02] < 0x41 or self.__buf0__[0x02] > 0x5A):
1414 for i in range(6):
1415 self.__buf0__[i+0x02]=0x20
1417#
1418# directory length fix
1419#
1420 if self.__buf0__[0x12] & 0x40 != 0:
1421 self.__buf0__[0x12] &= ~0x40
1423 return
1424#
1425# write buffer 0 to one sector n* pe (256 bytes)
1426#
1427 def __wrec__(self):
1428 self.__access_lock__.acquire()
1429 if self.__islocked__:
1430 self.__access_lock__.release()
1431 self.__setstatus__(20) # no medium error
1432 return
1433 try:
1434 if self.__isWindows__:
1435 fd= os.open(self.__hdiscfile__, os.O_WRONLY | os.O_BINARY)
1436 else:
1437 fd= os.open(self.__hdiscfile__, os.O_WRONLY)
1438 try:
1439 os.lseek(fd,self.__pe__ * 256, os.SEEK_SET)
1440 if self.__pe__ == 0 and (not self.__isRawDevice__) :
1441 self.__fix_header__()
1442# print("wrec record %d" % (self.__pe__))
1443 os.write(fd,self.__buf0__)
1444 self.__modified__= True
1445 self.__timestamp__= time.time()
1446 self.__setstatus__(0) # success, clear status
1447 except OSError as e:
1448 self.__setstatus__(29) # write error always returns write protect
1449 # error
1450 os.close(fd)
1451 except OSError as e:
1452 self.__setstatus__(29) # file open failed always returns write
1453 # protect error
1454 self.__access_lock__.release()
1455 return
1457#
1458# "format" a lif image file
1459#
1460 def __format_disc__(self):
1461 b= bytearray(256)
1462# print("Format disk")
1463 for i in range (0, len(b)):
1464 b[i]= 0xFF
1466 self.__access_lock__.acquire()
1467 if self.__islocked__:
1468 self.__access_lock__.release()
1469 self.__setstatus__(20) # no medium error
1470 return
1471 try:
1472 if self.__isWindows__:
1473 fd= os.open(self.__hdiscfile__, os.O_WRONLY | os.O_BINARY | os.O_TRUNC | os.O_CREAT, 0o644)
1474 else:
1475 fd= os.open(self.__hdiscfile__, os.O_WRONLY | os.O_TRUNC | os.O_CREAT, 0o644)
1476 for i in range(0,127):
1477 os.write(fd,b)
1478 os.close(fd)
1479 self.__timestamp__= time.time()
1480 self.__setstatus__(0) # success, clear status
1481 except OSError:
1482 self.__setstatus__(29) # failed file creation and initialization
1483 # always returns write protect error
1484 self.__access_lock__.release()
1485 return
1486#
1487# private (overloaded) -------------------------
1488#
1489# clear drive reset internal pointers
1490#
1491 def __clear_device__ (self):
1492 self.__fpt__= False
1493 self.__pe__ = 0
1494 self.__oc__ = 0
1495 self.__access_lock__.acquire()
1496 self.__modified__= False
1497 self.__access_lock__.release()
1498#
1499# Initialize/Invalidate buffer content. The HP-41 as controller uses
1500# buf 1 as a directory cache.
1501#
1502 for i in range (256):
1503 self.__buf0__[i]= 0x0;
1504 self.__buf1__[i]= 0x0;
1505 return
1508#
1509# receive data to disc according to DDL command
1510#
1511 def __indata__(self,n):
1513 if (self.__devl__== 0) or (self.__devl__== 2) or (self.__devl__==6):
1514 self.__buf0__[self.__oc__]= n & 255
1515 self.__oc__+=1
1516 if self.__oc__ > 255:
1517 self.__oc__= 0
1518 self.__wrec__()
1519 self.__pe__+=1
1520 if self.__flpwr__ != 0:
1521 self.__rrec__()
1522 else:
1523 if ( n & 0x200) !=0:
1524 self.__wrec__() # END
1525 if self.__flpwr__ == 0:
1526 self.__pe__+=1
1528 elif self.__devl__ == 1:
1529 self.__buf1__[self.__oc__] = n & 255
1530 self.__oc__+=1
1531 if self.__oc__ > 255:
1532 self.__oc__ =0
1534 elif self.__devl__== 3:
1535 self.__oc__= n & 255
1537 elif self.__devl__ == 4:
1538 n= n & 255
1539 if self.__fpt__:
1540 self.__pe0__= self.__pe0__ & 0xFF00
1541 self.__pe0__= self.__pe0__ | n
1542 if self.__pe0__ < self.__nbe__:
1543 self.__pe__= self.__pe0__
1544 self.__setstatus__(0)
1545 else:
1546 self.__setstatus__(28)
1547 self.__fpt__= False
1548 else:
1549 self.__pe0__= self.__pe0__ & 255
1550 self.__pe0__= self.__pe0__ | (n <<8)
1551 self.__fpt__= True
1552 return
1553#
1554# send data from disc according to DDT command
1555#
1556 def __outdata__(self,frame):
1557 if frame== 0x560 : # initial SDA
1558 self.__ptout__=0
1560 if (self.__devt__== 0) or (self.__devt__==2): # send buffer 0, read
1561 frame= self.__buf0__[self.__oc__]
1562 self.__oc__+=1
1563 if self.__oc__ > 255:
1564 self.__oc__=0
1565 self.__rrec__()
1566 self.__pe__+=1
1568 elif self.__devt__== 1: # send buffer 1
1569 frame= self.__buf1__[self.__oc__]
1570 self.__oc__+=1
1571 if self.__oc__ > 255:
1572 self.__oc__=0
1573 self.__devt__= 15 # send EOT on the next SDA
1575 elif self.__devt__ == 3: # send position
1576 if self.__ptout__ == 0:
1577 frame= self.__pe__ >> 8
1578 self.__ptout__+=1
1579 elif self.__ptout__ == 1:
1580 frame= self.__pe__ & 255
1581 self.__ptout__+=1
1582 elif self.__ptout__ == 2:
1583 frame= self.__oc__ & 255
1584 self.__ptout__+=1
1585 else:
1586 frame = 0x540 # EOT
1588 elif self.__devt__==6: # send implementation
1589 if self.__ptout__ < 12:
1590 frame= self.__lif__[self.__ptout__]
1591 self.__ptout__+=1
1592 else:
1593 frame= 0x540 # EOT
1595 elif self.__devt__ == 7: # send max record
1596 if self.__ptout__ == 0:
1597 frame= self.__nbe__ >> 8
1598 self.__ptout__+=1
1599 elif self.__ptout__ == 1:
1600 frame= self.__nbe__ & 255
1601 self.__ptout__+=1
1602 else:
1603 frame = 0x540 # EOT
1605 else:
1606 frame= 0x540
1608 return (frame)
1610#
1611# extended DDL/DDT commands
1612#
1613 def __cmd_ext__(self,frame):
1614 n= frame & 0xff
1615 t= n >> 5
1617 if t == 5: # DDL
1618 n=n & 31
1619 if (self.__ilstate__ & 0xC0) == 0x80: # are we listener?
1620 self.__devl__= n & 0xFF
1621 if n== 1:
1622 self.__flpwr__=0
1623 elif n== 2:
1624 self.__oc__= 0
1625 self.__flpwr__=0
1626 elif n==4:
1627 self.__flpwr__=0
1628 self.__fpt__= False
1629 elif n==5:
1630 self.__format_disc__()
1631 elif n == 6:
1632 self.__flpwr__= 0x80
1633 self.__rrec__()
1634 elif n == 7:
1635 self.__fpt__= False
1636 self.__pe__ = 0
1637 self.__oc__ = 0
1638 elif n == 8:
1639 self.__wrec__()
1640 if self.__flpwr__ ==0:
1641 self.__pe__+=1
1642 elif n == 9:
1643 self.__copybuf__()
1644 elif n == 10:
1645 self.__exchbuf__()
1647 elif t == 6: # DDT
1648 n= n& 31
1649 if (self.__ilstate__ & 0x40) == 0x40:
1650 self.__devt__= n & 0xFF
1651 if n== 0:
1652 self.__flpwr__=0
1653 elif n == 2:
1654 self.__rrec__()
1655 self.__oc__=0
1656 self.__flpwr__=0
1657 self.__pe__+=1
1658 elif n == 4:
1659 self.__exchbuf__()
1660 return(frame)