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

989 statements  

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 

139 

140# 

141# Tab classes ------------------------------------------------------------------ 

142# 

143class cls_tabdrivegeneric(cls_tabgeneric): 

144 

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() 

169 

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

191 

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

223 

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

243 

244 DEV_CASS=0 

245 DEV_DISK=1 

246 DEV_HDRIVE1=2 

247 DEV_FDRIVE1=3 

248 

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] 

254 

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 

264 

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] 

274 

275# 

276# Raw drive widget class ----------------------------------------------------- 

277# 

278class cls_RawDriveWidget(cls_GenericDriveWidget): 

279 

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) 

330 

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 

368 

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) 

388 

389 

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) 

398 

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) 

409 

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() 

428 

429 def reconfigure(self): 

430 return 

431 

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 

437 

438 def disable(self): 

439 return 

440 

441 def toggle_active(self): 

442 return 

443 

444 def becomes_visible(self): 

445 return 

446 

447 def becomes_invisible(self): 

448 return 

449 

450# 

451# Drive widget class -------------------------------------------------------- 

452# 

453class cls_DriveWidget(cls_GenericDriveWidget): 

454 

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 

469 

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) 

485 

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) 

506 

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) 

533 

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) 

539 

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) 

604 

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 

612 

613 def toggle_active(self): 

614 self.toggle_controls() 

615 

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) 

671 

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() 

683 

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() 

711 

712 def do_pack(self): 

713 cls_lifpack.execute(self.filename) 

714 self.lifdir.refresh() 

715 

716 def do_import(self): 

717 workdir=PILCONFIG.get('pyilper','workdir') 

718 cls_lifimport.execute(self.filename, workdir) 

719 self.lifdir.refresh() 

720 

721 def do_label(self): 

722 oldlabel=self.lifdir.getLabel() 

723 cls_liflabel.execute(self.filename, oldlabel) 

724 self.lifdir.refresh() 

725 

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() 

749 

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() 

761 

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 

777 

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

824 

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] 

859 

860 

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 

873 

874 def sortOrder(self): 

875 return self._sort_order 

876 

877 def sort(self, column, order): 

878 self._sort_order = order 

879 super().sort(column, order) 

880 

881class DirTableView(QtWidgets.QTableView): 

882 

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") 

956 

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() 

976 

977class cls_LifDirWidget(QtWidgets.QWidget): 

978 

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) 

1030 

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__) 

1042 

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() 

1053 

1054 def getModel(self): 

1055 return(self.__model__) 

1056 

1057 def getFilename(self): 

1058 return(self.__filename__) 

1059 

1060 def getLabel(self): 

1061 return(self.__label__) 

1062 

1063 def getRowCount(self): 

1064 return(self.__rowcount__) 

1065 

1066 def getMediumSummary(self): 

1067 return(self.__labelMedium__.text()) 

1068 

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() 

1077 

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 

1089 

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: 

1115 

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

1120 

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) 

1143 

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# 

1212 

1213 

1214class cls_pildrive(cls_pildevbase): 

1215 

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__() 

1222 

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 

1244 

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 

1253 

1254 self.__isWindows__= isWindows # true, if Windows platform 

1255 self.__isRawDevice__= isRawDevice # true, if drive is used as raw device 

1256 

1257# 

1258# public ------------ 

1259# 

1260 

1261# 

1262# enable/disable (do nothing) 

1263# 

1264 def enable(self): 

1265 return 

1266 

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() 

1286 

1287# 

1288# release device 

1289# 

1290 def releaseaccesslock(self): 

1291 self.__access_lock__.release() 

1292 

1293 

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 

1303 

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 

1345 

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# 

1392 

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 

1416 

1417# 

1418# directory length fix 

1419# 

1420 if self.__buf0__[0x12] & 0x40 != 0: 

1421 self.__buf0__[0x12] &= ~0x40 

1422 

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 

1456 

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 

1465 

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 

1506 

1507 

1508# 

1509# receive data to disc according to DDL command 

1510# 

1511 def __indata__(self,n): 

1512 

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 

1527 

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 

1533 

1534 elif self.__devl__== 3: 

1535 self.__oc__= n & 255 

1536 

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 

1559 

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 

1567 

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 

1574 

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 

1587 

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 

1594 

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 

1604 

1605 else: 

1606 frame= 0x540 

1607 

1608 return (frame) 

1609 

1610# 

1611# extended DDL/DDT commands 

1612# 

1613 def __cmd_ext__(self,frame): 

1614 n= frame & 0xff 

1615 t= n >> 5 

1616 

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__() 

1646 

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)