class main(QWidget): def __init__(self, parent=None): super(main, self).__init__(parent) self.setup() # connections, widgets, layouts, etc. self.blksize = 2**20 # 1 MB; must be divisible by 16 self.ext = '.enc' # extension is appended to encrypted files self.path = '' self.encrypted = [] # to highlight done files in list self.decrypted = [] self.clipboard = QApplication.clipboard() self.timeout = None # to clear message label, see setMessage # this program was just an excuse to play with QprogressBar if not hash(os.urandom(11)) % 11: QTimer().singleShot(50, self.windDown) # various random hints hints = [ 'Freshly encrypted files can be renamed in the table!', 'Clipboard is always cleared on program close!', 'Keys can contain emoji if you <em>really</em> want: \U0001f4e6', 'Keys can contain emoji if you <em>really</em> want: \U0001F511', 'This isn\'t a tip, I just wanted to say hello!', 'Keys can be anywhere from 8 to 4096 characters long!', 'This program was just an excuse to play with the progress bars!', 'Select \'Party\' in the hash button for progress bar fun!', ('Did you know you can donate one or all of your vital organs to ' 'the Aperture Science Self-Esteem Fund for Girls? It\'s true!'), ('It\'s been {:,} days since Half-Life 2: Episode ' 'Two'.format(int((time.time() - 1191988800) / 86400))), 'I\'m version {}!'.format(VERSION), 'I\'m version {}.whatever!'.format(VERSION.split('.')[0]), ('Brought to you by me, I\'m <a href="https://orthallelous.word' 'press.com/">Orthallelous!</a>'), #'Brought to you by me, I\'m Htom Sirveaux!', 'I wonder if there\'s beer on the sun', 'Raspberry World: For all your raspberry needs. Off the beltline', #'I\'ve plummented to my death and I can\'t get up', '<em>NOT</em> compatible with the older version!', ('Hello there, fellow space travellers! Until somebody gives me ' 'some new lines in KAS, that is all I can say. - Bentusi Exchange' ) ] if not hash(os.urandom(9)) % 4: self.extraLabel.setText(random.choice(hints)) def genKey(self): "generate a random key" n = self.keySizeSB.value() char = string.printable.rstrip() #map(chr, range(256)) while len(char) < n: char += char key = ''.join(random.sample(char, n)) self.keyInput.setText(key) def showKey(self, state=None): "hide/show key characters" if state is None: state = bool(self.showKeyCB.checkState()) else: state = bool(state) if state: self.keyInput.setEchoMode(QLineEdit.Normal) else: self.keyInput.setEchoMode(QLineEdit.PasswordEchoOnEdit) def getFolder(self): "open file dialog and fill file table" path = QFileDialog(directory=self.path).getExistingDirectory() if not path: return self.path = str(path) self.populateTable(self.path) self.encrypted, self.decrypted = [], [] return def resizeEvent(self, event): self.showFolder(self.path) # update how the folder is shown def splitterChanged(self, pos): self.showFolder(self.path) # likewise def showFolder(self, path): "displays current path, truncating as needed" if not path: return ell, sl = '\u2026', os.path.sep # ellipsis, slash chars lfg, rfg = Qt.ElideLeft, Qt.ElideRight lst, wdh = os.path.basename(path), self.folderLabel.width() path = path.replace(os.path.altsep or '\\', sl) self.folderLabel.setToolTip(path) # truncate folder location fnt = QFontMetrics(self.folderLabel.font()) txt = str(fnt.elidedText(path, lfg, wdh)) if len(txt) <= 1: # label is way too short self.folderLabel.setText('\u22ee' if txt != sl else txt) return # but when would this happen? # truncate some more (don't show part of a folder name) if len(txt) < len(path) and txt[1] != sl: txt = ell + sl + txt.split(sl, 1)[-1] # don't truncate remaining folder name from the left if txt[2:] != lst and len(txt[2:]) < len(lst) + 2: txt = str(fnt.elidedText(ell + sl + lst, rfg, wdh)) # you'd think len(txt) < len(lst) would work, but no; you'd be wrong self.folderLabel.setText(txt) def populateTable(self, path): "fill file table with file names" self.showFolder(path) names = [] for n in os.listdir(path): if os.path.isdir(os.path.join(path, n)): continue # folder names.append(n) self.folderTable.clearContents() self.folderTable.setRowCount(len(names)) self.folderTable.setColumnCount(1) if not names: # no files in this folder, inform user self.setMessage('This folder has no files') return self.folderTable.blockSignals(True) selEnab = Qt.ItemIsSelectable | Qt.ItemIsEnabled for i, n in enumerate(names): item = QTableWidgetItem() item.setText(n) item.setToolTip(n) item.setFlags(selEnab) # color code encrypted/decrypted files if n in self.encrypted: item.setTextColor(QColor(211, 70, 0)) # allowed encrypted filenames to be changed item.setFlags(selEnab | Qt.ItemIsEditable) if n in self.decrypted: item.setForeground(QColor(0, 170, 255)) self.folderTable.setItem(i, 0, item) if len(names) > 5: self.setMessage('{:,} files'.format(len(names)), 7) self.folderTable.blockSignals(False) return def editFileName(self, item): "change file name" new, old = str(item.text()), str(item.toolTip()) result = QMessageBox.question( self, 'Renaming?', ("<p align='center'>Do you wish to rename<br>" + '<span style="color:#d34600;">{}</span>'.format(old) + "<br>to<br>" + '<span style="color:#ef4b00;">{}</span>'.format(new) + '<br>?</p>')) self.folderTable.blockSignals(True) if any(i in new for i in '/?<>:*|"^'): self.setMessage('Invalid character in name', 7) item.setText(old) elif result == QMessageBox.Yes: oold = os.path.join(self.path, old) try: os.rename(oold, os.path.join(self.path, new)) self.encrypted.remove(old) self.encrypted.append(new) item.setToolTip(new) except Exception as err: self.setMessage(str(err), 9) item.setText(old) item.setToolTip(old) self.encrypted.remove(new) self.encrypted.append(old) else: item.setText(old) self.folderTable.blockSignals(False) def setMessage(self, message, secs=4, col=None): "show a message for a few seconds - col must be rgb triplet tuple" if self.timeout: # https://stackoverflow.com/a/21081371 self.timeout.stop() self.timeout.deleteLater() if col is None: color = 'rgb(255, 170, 127)' else: try: color = 'rgb({}, {}, {})'.format(*col) except: color = 'rgb(255, 170, 127)' self.messageLabel.setStyleSheet('background-color: {};'.format(color)) self.messageLabel.setText(message) self.messageLabel.setToolTip(message) self.timeout = QTimer() self.timeout.timeout.connect(self.clearMessage) self.timeout.setSingleShot(True) self.timeout.start(secs * 1000) def clearMessage(self): self.messageLabel.setStyleSheet('') self.messageLabel.setToolTip('') self.messageLabel.setText('') def getName(self): "return file name of selected" items = self.folderTable.selectedItems() names = [str(i.text()) for i in items] if names: return names[0] # only the first selected file else: return '' def showKeyLen(self, string): "displays a tooltip showing length of key" s = len(string) note = '{:,} character{}'.format(s, '' if s == 1 else 's') tip = QToolTip pos = self.genKeyButton.mapToGlobal(QPoint(0, 0)) if s < self.minKeyLen: note = '<span style="color:#c80000;">{}</span>'.format(note) else: note = '<span style="color:#258f22;">{}</span>'.format(note) tip.showText(pos, note) def lock(self, flag=True): "locks buttons if True" stuff = [ self.openButton, self.encryptButton, self.decryptButton, self.genKeyButton, self.hashButton, self.showKeyCB, self.copyButton, self.keyInput, self.keySizeSB, self.folderTable, ] for i in stuff: i.blockSignals(flag) i.setEnabled(not flag) return def _lerp(self, v1, v2, numPts=10): "linearly interpolate from v1 to v2\nFrom Orthallelous" if len(v1) != len(v2): raise ValueError("different dimensions") D, V, n = [], [], abs(numPts) for i, u in enumerate(v1): D.append(v2[i] - u) for i in range(n + 1): vn = [] for j, u in enumerate(v1): vn.append(u + D[j] / float(n + 2) * i) V.append(tuple(vn)) return V def weeeeeee(self): "party time" self.lock() self.setMessage('Party time!', 2.5) a, b, c = self.encryptPbar, self.decryptPbar, self.hashPbar process, sleep = app.processEvents, time.sleep am, bm, cm = a.minimum(), b.minimum(), c.minimum() ax, bx, cx = a.maximum(), b.maximum(), c.maximum() a.reset() b.reset() c.reset() loops = self._lerp((am, bm, cm), (ax, bx, cx), 100) ivops = loops[::-1] # up and up! for i in range(3): for j, k, l in loops: a.setValue(int(j)) b.setValue(int(k)) c.setValue(int(l)) process() sleep(0.01) a.setValue(ax) b.setValue(bx) c.setValue(cx) sleep(0.25) a.setValue(am) b.setValue(bm) c.setValue(cm) # snake! self.setMessage('Snake time!') self.messageLabel.setStyleSheet('background-color: rgb(127,170,255);') for i in range(2): for j, k, l in loops: a.setValue(int(j)) process() sleep(0.002) process() a.setInvertedAppearance(True) process() for j, k, l in ivops: a.setValue(int(j)) process() sleep(0.002) for j, k, l in loops: b.setValue(int(k)) process() sleep(0.002) process() b.setInvertedAppearance(False) process() for j, k, l in ivops: b.setValue(int(k)) process() sleep(0.002) for j, k, l in loops: c.setValue(int(l)) process() sleep(0.002) process() c.setInvertedAppearance(True) process() for j, k, l in ivops: c.setValue(int(l)) process() sleep(0.002) process() b.setInvertedAppearance(True) process() for j, k, l in loops: b.setValue(int(k)) process() sleep(0.002) process() b.setInvertedAppearance(False) process() for j, k, l in ivops: b.setValue(int(k)) process() sleep(0.002) process() a.setInvertedAppearance(False) b.setInvertedAppearance(True) c.setInvertedAppearance(False) for j, k, l in loops: a.setValue(int(j)) process() sleep(0.002) process() a.setInvertedAppearance(True) process() for j, k, l in ivops: a.setValue(int(j)) process() sleep(0.002) # bars sleep(0.5) self.setMessage('Bars!') process() self.messageLabel.setStyleSheet('background-color: rgb(127,255,170);') for i in range(2): a.setValue(ax) time.sleep(0.65) a.setValue(am) sleep(0.25) process() b.setValue(bx) time.sleep(0.65) b.setValue(bm) sleep(0.25) process() c.setValue(cx) time.sleep(0.65) c.setValue(cm) sleep(0.25) process() b.setValue(bx) time.sleep(0.65) b.setValue(bm) sleep(0.25) process() # okay, enough process() a.setValue(ax) b.setValue(bx) c.setValue(cx) #a.setValue(am); b.setValue(bm); c.setValue(cm) a.setInvertedAppearance(False) b.setInvertedAppearance(True) c.setInvertedAppearance(False) self.lock(False) return def windDown(self, note=None): "silly deload on load" if note is None: note = 'Loading...' self.lock() self.setMessage(note) self.messageLabel.setStyleSheet('background-color: rgb(9, 190, 130);') a, b, c = self.encryptPbar, self.decryptPbar, self.hashPbar am, bm, cm = a.minimum(), b.minimum(), c.minimum() ax, bx, cx = a.maximum(), b.maximum(), c.maximum() a.reset() b.reset() c.reset() loops = self._lerp((ax, bx, cx), (am, bm, cm), 100) for j, k, l in loops: a.setValue(int(j)) b.setValue(int(k)) c.setValue(int(l)) app.processEvents() time.sleep(0.02) a.reset() b.reset() c.reset() self.lock(False) self.clearMessage() def genHash(self, action): "generate hash of selected file and display it" name, t0 = self.getName(), time.perf_counter() # mark what hash was used in the drop-down menu for i in self.hashButton.menu().actions(): if i == action: i.setIconVisibleInMenu(True) else: i.setIconVisibleInMenu(False) if str(action.text()) == 'Party': self.weeeeeee() self.windDown('Winding down...') return if not name: self.setMessage('No file selected') return if not os.path.exists(os.path.join(self.path, name)): self.setMessage('File does not exist') return self.lock() hsh = self.hashFile(os.path.join(self.path, name), getattr(hashlib, str(action.text()))) self.lock(False) #hsh = str(action.text()) + ': ' + hsh self.hashLabel.setText(hsh) self.hashLabel.setToolTip(hsh) self.extraLabel.setText( str(action.text()) + ' hash took ' + self.secs_fmt(time.perf_counter() - t0)) def setCancel(self): "cancel operation" self._requestStop = True def showCancelButton(self, state=False): "show/hide cancel button" self.cancelButton.blockSignals(not state) self.cancelButton.setEnabled(state) if state: self.cancelButton.show() self.keyInput.hide() self.genKeyButton.hide() self.keySizeSB.hide() else: self.cancelButton.hide() self.keyInput.show() self.genKeyButton.show() self.keySizeSB.show() def hashFile(self, fn, hasher): "returns the hash value of a file" hsh, blksize = hasher(), self.blksize fsz, csz = os.path.getsize(fn), 0.0 self.hashPbar.reset() self.showCancelButton(True) prog, title = '(# {:.02%}) {}', self.windowTitle() with open(fn, 'rb') as f: while 1: blk = f.read(blksize) if not blk: break hsh.update(blk) csz += blksize self.hashPbar.setValue(int(round(csz * 100.0 / fsz))) app.processEvents() self.setWindowTitle(prog.format(csz / fsz, title)) if self._requestStop: break self.hashPbar.setValue(self.hashPbar.maximum()) self.setWindowTitle(title) self.showCancelButton(False) if self._requestStop: self.setMessage('Hashing canceled!') self.hashPbar.setValue(self.hashPbar.minimum()) self._requestStop = False return return hsh.hexdigest() def hashKey(self, key, salt=b''): "hashes a key for encrypting/decrypting file" salt = salt.encode() if type(salt) != bytes else salt key = key.encode() if type(key) != bytes else key p = app.processEvents self.setMessage('Key Hashing...', col=(226, 182, 249)) p() key = hashlib.pbkdf2_hmac('sha512', key, salt, 444401) p() self.clearMessage() p() return hashlib.sha3_256(key).digest() # AES requires a 32 char key def encrypt(self): "encrypt selected file with key" name, t0 = self.getName(), time.perf_counter() if not name: self.setMessage('No file selected') return if not os.path.exists(os.path.join(self.path, name)): self.setMessage('File does not exist') return key = str(self.keyInput.text()) if len(key) < self.minKeyLen: self.setMessage(('Key must be at least ' '{} characters long').format(self.minKeyLen)) return self.lock() gn = self.encryptFile(key, os.path.join(self.path, name)) if not gn: self.lock(False) return self.encrypted.append(os.path.basename(gn)) self.lock(False) self.populateTable(self.path) # repopulate folder list bn, tt = os.path.basename(gn), time.perf_counter() - t0 self.setMessage('Encrypted, saved "{}"'.format(bn, 13)) self.extraLabel.setText('Encrypting took ' + self.secs_fmt(tt)) def encryptFile(self, key, fn): "encrypts a file using AES (MODE_GCM)" chars = ''.join(map(chr, range(256))).encode() chk = AES.block_size sample = random.sample iv = bytes(sample(chars, chk * 2)) salt = bytes(sample(chars * 2, 256)) vault = AES.new(self.hashKey(key, salt), AES.MODE_GCM, iv) fsz = os.path.getsize(fn) del key blksize = self.blksize gn = fn + self.ext fne = os.path.basename(fn).encode() fnz = len(fne) if len(fne) % chk: fne += bytes(sample(chars, chk - len(fne) % chk)) csz = 0.0 # current processed value self.encryptPbar.reset() prog, title = '({:.02%}) {}', self.windowTitle() self.showCancelButton(True) with open(fn, 'rb') as src, open(gn, 'wb') as dst: dst.write(bytes([0] * 16)) # spacer for MAC written at end dst.write(iv) dst.write(salt) # store iv, salt # is it safe to store MAC, iv, salt plain right in file? # can't really store them encrypted, # or elsewhere in this model of single file encryption? # can't have another file for the file to lug around # store file size, file name length dst.write(vault.encrypt(struct.pack('<2Q', fsz, fnz))) dst.write(vault.encrypt(fne)) # store filename while 1: dat = src.read(blksize) if not dat: break elif len(dat) % chk: # add padding fil = chk - len(dat) % chk dat += bytes(sample(chars, fil)) dst.write(vault.encrypt(dat)) csz += blksize # show progress self.encryptPbar.setValue(int(round(csz * 100.0 / fsz))) self.setWindowTitle(prog.format(csz / fsz, title)) app.processEvents() if self._requestStop: break if not self._requestStop: stuf = random.randrange(23) # pack in more stuffing fing = b''.join(bytes(sample(chars, 16)) for i in range(stuf)) dst.write(vault.encrypt(fing)) # and for annoyance dst.seek(0) dst.write(vault.digest()) # write MAC self.hashLabel.setText('MAC: ' + vault.hexdigest()) self.encryptPbar.setValue(self.encryptPbar.maximum()) self.setWindowTitle(title) self.showCancelButton(False) if self._requestStop: self.setMessage('Encryption canceled!') self.encryptPbar.setValue(self.encryptPbar.minimum()) self._requestStop = False os.remove(gn) return return gn def decrypt(self): "encrypt selected file with key" name, t0 = self.getName(), time.perf_counter() if not name: self.setMessage('No file selected') return if not os.path.exists(os.path.join(self.path, name)): self.setMessage('File does not exist') return key = str(self.keyInput.text()) if len(key) < self.minKeyLen: self.setMessage(('Key must be at least ' '{} characters long').format(self.minKeyLen)) return self.lock() gn = self.decryptFile(key, os.path.join(self.path, name)) if not gn: self.lock(False) return self.decrypted.append(os.path.basename(gn)) self.lock(False) self.populateTable(self.path) # repopulate folder list bn, tt = os.path.basename(gn), time.perf_counter() - t0 self.setMessage('Decrypted, saved "{}"'.format(bn, 13)) self.extraLabel.setText('Decrypting took ' + self.secs_fmt(tt)) def decryptFile(self, key, fn): "decrypts a file using AES (MODE_GCM)" blksize = self.blksize gn = hashlib.md5(os.path.basename(fn).encode()).hexdigest() gn = os.path.join(self.path, gn) # temporary name if os.path.exists(gn): self.setMessage('file already exists') return self.decryptPbar.reset() csz = 0.0 # current processed value chk, fnsz = AES.block_size, os.path.getsize(fn) prog, title = '({:.02%}) {}', self.windowTitle() try: with open(fn, 'rb') as src, open(gn, 'wb') as dst: # extract iv, salt MAC = src.read(16) iv = src.read(AES.block_size * 2) salt = src.read(256) vault = AES.new(self.hashKey(key, salt), AES.MODE_GCM, iv) self.showCancelButton(True) # extract file size, file name length sizes = src.read(struct.calcsize('<2Q')) fsz, fnz = struct.unpack('<2Q', vault.decrypt(sizes)) # extract filename; round up fnz to nearest chk rnz = fnz if not fnz % chk else fnz + chk - fnz % chk rfn = vault.decrypt(src.read(rnz))[:fnz].decode() self.setMessage('Found "{}"'.format(rfn), 13, (255, 211, 127)) while 1: dat = src.read(blksize) if not dat: break dst.write(vault.decrypt(dat)) csz += blksize # show progress self.decryptPbar.setValue(int(round(csz * 100.0 / fnsz))) self.setWindowTitle(prog.format(1 - (csz / fnsz), title)) app.processEvents() if self._requestStop: break if not self._requestStop: dst.truncate(fsz) # remove padding if not self._requestStop: vault.verify(MAC) self.hashLabel.setText('') except (ValueError, KeyError) as err: os.remove(gn) self.setMessage('Invalid decryption!') self.setWindowTitle(title) self.showCancelButton(False) return except Exception as err: os.remove(gn) self.setMessage('Invalid key or file!') self.setWindowTitle(title) self.showCancelButton(False) return self.decryptPbar.setValue(self.decryptPbar.maximum()) self.setWindowTitle(title) self.showCancelButton(False) if self._requestStop: self.setMessage('Decryption canceled!') self.decryptPbar.setValue(self.decryptPbar.minimum()) self._requestStop = False os.remove(gn) return # restore original file name name, ext = os.path.splitext(rfn) count = 1 fn = os.path.join(self.path, name + ext) while os.path.exists(fn): fn = os.path.join(self.path, name + '_{}'.format(count) + ext) count += 1 os.rename(gn, fn) # restore original name return fn # saved name def copyKeyHash(self, action): "copies either the key or the hash to clipboard" act = str(action.text()).lower() if 'key' in act: txt = str(self.keyInput.text()) elif 'hash' in act: txt = str(self.hashLabel.text()) else: self.setMessage('Invalid copy selection') return if not txt: self.setMessage('Empty text; Nothing to copy') return if 'key' in act: self.setMessage('Key copied to clipboard') elif 'hash' in act: self.setMessage('Hash copied to clipboard') else: self.setMessage('Invalid copy selection') return self.clipboard.clear() self.clipboard.setText(txt) def secs_fmt(self, s): "6357 -> '1h 45m 57s'" Y, D, H, M = 31556952, 86400, 3600, 60 y = int(s // Y) s -= y * Y d = int(s // D) s -= d * D h = int(s // H) s -= h * H m = int(s // M) s -= m * M r = (str(int(s)) if int(s) == s else str(round(s, 3))) + 's' if m: r = str(m) + 'm ' + r if h: r = str(h) + 'h ' + r if d: r = str(d) + 'd ' + r if y: r = str(y) + 'y ' + r return r.strip() def closeEvent(self, event): self.clipboard.clear() def setup(self): "constructs the gui" Fixed = QSizePolicy() MinimumExpanding = QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) self.minKeyLen = 8 self.maxKeyLen = 4096 self.splitter = QSplitter(self) self.splitter.setOrientation(Qt.Horizontal) self.splitter.splitterMoved.connect(self.splitterChanged) # left column self.leftColumn = QWidget() self.vl01 = QVBoxLayout() # left column - first item (0; horizonal layout 0) self.hl00 = QHBoxLayout() self.hl00.setSpacing(5) self.openButton = QPushButton('&Open') self.openButton.setToolTip('Open folder') self.openButton.setMinimumSize(60, 20) self.openButton.setMaximumSize(60, 20) self.openButton.setSizePolicy(Fixed) self.openButton.clicked.connect(self.getFolder) #ico = self.style().standardIcon(QStyle.SP_DirIcon) #self.openButton.setIcon(ico) self.folderLabel = QLabel() self.folderLabel.setMinimumSize(135, 20) self.folderLabel.setMaximumSize(16777215, 20) self.folderLabel.setSizePolicy(MinimumExpanding) self.hl00.insertWidget(0, self.openButton) self.hl00.insertWidget(1, self.folderLabel) # left column - second item (1) self.folderTable = QTableWidget() self.folderTable.setMinimumSize(200, 32) self.folderTable.horizontalHeader().setVisible(False) self.folderTable.horizontalHeader().setStretchLastSection(True) self.folderTable.verticalHeader().setVisible(False) self.folderTable.verticalHeader().setDefaultSectionSize(15) self.folderTable.itemChanged.connect(self.editFileName) # left column - third item (2) self.extraLabel = QLabel() self.extraLabel.setMinimumSize(200, 20) self.extraLabel.setMaximumSize(16777215, 20) self.extraLabel.setSizePolicy(MinimumExpanding) self.extraLabel.setTextInteractionFlags(Qt.LinksAccessibleByMouse) # finalize left column self.vl01.insertLayout(0, self.hl00) self.vl01.insertWidget(1, self.folderTable) self.vl01.insertWidget(2, self.extraLabel) self.leftColumn.setLayout(self.vl01) # right column self.rightColumn = QWidget() self.vl02 = QVBoxLayout() # right column - first item (0) self.messageLabel = QLabel() self.messageLabel.setMinimumSize(290, 20) self.messageLabel.setMaximumSize(16777215, 20) self.messageLabel.setSizePolicy(MinimumExpanding) self.messageLabel.setAlignment(Qt.AlignCenter) # right column - second item (2; horizontal layout 1) self.hl01 = QHBoxLayout() self.hl01.setSpacing(5) self.encryptButton = QPushButton('&Encrypt') #\U0001F512 self.encryptButton.setToolTip('Encrypt selected file') self.encryptButton.setMinimumSize(60, 20) self.encryptButton.setMaximumSize(60, 20) self.encryptButton.setSizePolicy(Fixed) self.encryptButton.clicked.connect(self.encrypt) self.encryptPbar = QProgressBar() self.encryptPbar.setMinimumSize(225, 20) self.encryptPbar.setMaximumSize(16777215, 20) self.encryptPbar.setSizePolicy(MinimumExpanding) self.encryptPbar.setTextVisible(False) palette = self.encryptPbar.palette() # color of progress bar color = QColor(211, 70, 0) palette.setColor(QPalette.Highlight, color) self.encryptPbar.setPalette(palette) self.hl01.insertWidget(0, self.encryptButton) self.hl01.insertWidget(1, self.encryptPbar) # right column - third item (3; horizontal layout 2) self.hl02 = QHBoxLayout() self.hl02.setSpacing(5) self.cancelButton = QPushButton('C&ANCEL') self.cancelButton.setToolTip('Cancels current operation') self.cancelButton.setMinimumSize(70, 24) self.cancelButton.setMaximumSize(70, 24) self.cancelButton.setSizePolicy(Fixed) self.cancelButton.clicked.connect(self.setCancel) font = self.cancelButton.font() font.setBold(True) self.cancelButton.setFont(font) self.cancelButton.blockSignals(True) self.cancelButton.setEnabled(False) self.cancelButton.hide() self._requestStop = False self.keyInput = QLineEdit() self.keyInput.setMinimumSize(225, 20) self.keyInput.setMaximumSize(16777215, 20) self.keyInput.setSizePolicy(MinimumExpanding) self.keyInput.setPlaceholderText('key') self.keyInput.setMaxLength(self.maxKeyLen) self.keyInput.setAlignment(Qt.AlignCenter) self.keyInput.textEdited.connect(self.showKeyLen) self.genKeyButton = QPushButton('&Gen Key') #\U0001F511 self.genKeyButton.setToolTip('Generate a random key') self.genKeyButton.setMinimumSize(60, 20) self.genKeyButton.setMaximumSize(60, 20) self.genKeyButton.setSizePolicy(Fixed) self.genKeyButton.clicked.connect(self.genKey) self.keySizeSB = QSpinBox() self.keySizeSB.setToolTip('Length of key to generate') self.keySizeSB.setRange(32, 1024) self.keySizeSB.setMinimumSize(40, 20) self.keySizeSB.setMaximumSize(40, 20) self.keySizeSB.setSizePolicy(Fixed) self.keySizeSB.setAlignment(Qt.AlignCenter) self.keySizeSB.setButtonSymbols(QSpinBox.NoButtons) self.keySizeSB.setWrapping(True) self.hl02.insertWidget(0, self.cancelButton) self.hl02.insertWidget(1, self.keyInput) self.hl02.insertWidget(2, self.genKeyButton) self.hl02.insertWidget(3, self.keySizeSB) # right column - fourth item (4; horizontal layout 3) self.hl03 = QHBoxLayout() self.hl03.setSpacing(5) self.decryptButton = QPushButton('&Decrypt') #\U0001F513 self.decryptButton.setToolTip('Decrypt selected file') self.decryptButton.setMinimumSize(60, 20) self.decryptButton.setMaximumSize(60, 20) self.decryptButton.setSizePolicy(Fixed) self.decryptButton.clicked.connect(self.decrypt) self.decryptPbar = QProgressBar() self.decryptPbar.setMinimumSize(225, 20) self.decryptPbar.setMaximumSize(16777215, 20) self.decryptPbar.setSizePolicy(MinimumExpanding) self.decryptPbar.setTextVisible(False) self.decryptPbar.setInvertedAppearance(True) palette = self.decryptPbar.palette() # color of progress bar color = QColor(0, 170, 255) palette.setColor(QPalette.Highlight, color) self.decryptPbar.setPalette(palette) self.hl03.insertWidget(0, self.decryptButton) self.hl03.insertWidget(1, self.decryptPbar) # right column - fifth item (7; horizontal layout 4) self.hl04 = QHBoxLayout() self.hl04.setSpacing(5) self.showKeyCB = QCheckBox('&Show Key') self.showKeyCB.setToolTip('Show/Hide key value') self.showKeyCB.setMinimumSize(75, 20) self.showKeyCB.setMaximumSize(75, 20) self.showKeyCB.setSizePolicy(Fixed) self.showKeyCB.clicked.connect(self.showKey) self.showKeyCB.setChecked(True) self.hashPbar = QProgressBar() self.hashPbar.setMinimumSize(150, 20) self.hashPbar.setMaximumSize(16777215, 20) self.hashPbar.setSizePolicy(MinimumExpanding) self.hashPbar.setTextVisible(False) palette = self.hashPbar.palette() # color of progress bar color = QColor(31, 120, 73) palette.setColor(QPalette.Highlight, color) self.hashPbar.setPalette(palette) self.hashButton = QPushButton('&Hash') self.hashButton.setToolTip('Determine file hash') self.hashButton.setMinimumSize(60, 20) self.hashButton.setMaximumSize(60, 20) self.hashButton.setSizePolicy(Fixed) menu = QMenu(self.hashButton) ico = self.style().standardIcon(QStyle.SP_DialogYesButton) for alg in sorted( filter(lambda x: 'shake' not in x, hashlib.algorithms_guaranteed), key=lambda n: (len(n), sorted(hashlib.algorithms_guaranteed).index(n))): menu.addAction( ico, alg ) # drop shake algs as their .hexdigest requires an argument - the rest don't menu.addAction(ico, 'Party') for i in menu.actions(): i.setIconVisibleInMenu(False) self.hashButton.setMenu(menu) menu.triggered.connect(self.genHash) self.hl04.insertWidget(0, self.showKeyCB) self.hl04.insertWidget(1, self.hashPbar) self.hl04.insertWidget(2, self.hashButton) # right column - sixth item (8; horizontal layout 5) self.hl05 = QHBoxLayout() self.hl05.setSpacing(5) self.copyButton = QPushButton('&Copy') #\U0001F4CB self.copyButton.setToolTip('Copy key or hash to clipboard') self.copyButton.setMinimumSize(60, 20) self.copyButton.setMaximumSize(60, 20) self.copyButton.setSizePolicy(Fixed) menu2 = QMenu(self.copyButton) menu2.addAction('Copy Key') menu2.addAction('Copy Hash') self.copyButton.setMenu(menu2) menu2.triggered.connect(self.copyKeyHash) self.hashLabel = QLabel() self.hashLabel.setMinimumSize(225, 20) self.hashLabel.setMaximumSize(16777215, 20) self.hashLabel.setSizePolicy(MinimumExpanding) self.hashLabel.setTextFormat(Qt.PlainText) self.hashLabel.setAlignment(Qt.AlignCenter) self.hashLabel.setTextInteractionFlags(Qt.TextSelectableByMouse) self.hl05.insertWidget(0, self.copyButton) self.hl05.insertWidget(1, self.hashLabel) # finalize right column self.vl02.insertWidget(0, self.messageLabel) self.vl02.insertSpacerItem(1, QSpacerItem(0, 0)) self.vl02.insertLayout(2, self.hl01) self.vl02.insertLayout(3, self.hl02) self.vl02.insertLayout(4, self.hl03) self.vl02.insertSpacerItem(5, QSpacerItem(0, 0)) self.vl02.insertWidget(6, QFrame()) self.vl02.insertLayout(7, self.hl04) self.vl02.insertLayout(8, self.hl05) self.rightColumn.setLayout(self.vl02) # finalize main window self.splitter.insertWidget(0, self.leftColumn) self.splitter.insertWidget(1, self.rightColumn) layout = QHBoxLayout(self) layout.addWidget(self.splitter) self.setLayout(layout) self.setWindowTitle('Simple File Encryptor/Decryptor') self.resize(self.sizeHint())
class AddNew(QWidget): def __init__(self, main_table): super(AddNew, self).__init__() self.table = main_table self.headers = main_table.model_c.metadata_c.metadata["headers"] self.title = f'Add New - {self.table.model_c.path_c.get_file_name()}' # DESCRIPTION self.setWindowTitle(self.title) self.setWindowState(Qt.WindowMaximized) self.setStyleSheet(styles.main_dark) # MAIN LAYOUT self.main_v_layout = QVBoxLayout() # SPLITTER self.splitter = QSplitter(self) # MAIN TOOL BAR self.toolbar = QGroupBox(self) self.set_up_toolbar() # SIDE WORKSPACE self.side_widget = None # MAIN WORKSPACE main_workspace_layout = QVBoxLayout() self.main_widget = QWidget(self) self.headers_inputs = HeadersInput(self, self.headers) # MAIN WORKSPACE BUTTONS button_h_layout = QHBoxLayout() self.button_add = QPushButton("ADD", self) self.button_add.clicked.connect(self.add_new) self.button_clear = QPushButton("Clear", self) self.button_clear.clicked.connect(self.clear) button_h_layout.addWidget(self.button_add) button_h_layout.addWidget(self.button_clear) # SET UP main_workspace_layout.addWidget(self.headers_inputs) main_workspace_layout.addLayout(button_h_layout) self.main_widget.setLayout(main_workspace_layout) self.splitter.addWidget(self.main_widget) self.main_v_layout.addWidget(self.toolbar) self.main_v_layout.addWidget(self.splitter) self.setLayout(self.main_v_layout) self.show() def add_new(self): item = self.headers_inputs.get_values() if not item: return _, contains_in_self = self.table.add(item) self.open_home_table() def clear(self): self.headers_inputs.clear() def open_home_table(self): if self.side_widget is not None: self.side_widget.close() self.side_widget = SideTable(self, self.table.model_c) self.splitter.insertWidget(-1, self.side_widget) def set_up_toolbar(self): vbox = QHBoxLayout() self.toolbar.setStyleSheet(styles.tool_bar_groupbox_dark) vbox.setAlignment(Qt.AlignLeft) vbox.setContentsMargins(10, 0, 0, 0) self.toolbar.setLayout(vbox) self.toolbar.setFixedHeight(40) home_table = QPushButton(QIcon(icon.HOME_SCREEN_I()), "", self.toolbar) vbox.addWidget(home_table) parent_relation_option = (list( map( lambda relation: { "text": relation["name_of_child_table"], "parm": relation["path_of_child_table"], "icon": icon.MULTICAST_I(), "callback": self.open_parent, "tooltip": relation["name_of_relation"] }, self.table.model_c.metadata_c.metadata["sequential_info"] ["parent_relation"]))) if len(parent_relation_option) > 0: select_parent = ButtonOptions(self.toolbar, parent_relation_option, icon.MULTICAST_I()) vbox.addWidget(select_parent) child_relations_options = (list( map( lambda relation: { "text": relation["name_of_child_table"], "parm": relation["path_of_child_table"], "icon": icon.MULTICAST_I(), "callback": self.open_parent, "tooltip": relation["name_of_relation"] }, self.table.model_c.metadata_c.metadata["sequential_info"] ["child_relation"]))) if len(child_relations_options) > 0: add_child = ButtonOptions(self.toolbar, child_relations_options, icon.DOWN_BUTTON_I()) vbox.addWidget(add_child) home_table.setStatusTip(f"Open '{self.table.model_c.path_c.path}'") home_table.clicked.connect(self.open_home_table) def open_parent(self, path): if self.side_widget is not None: self.side_widget.close() self.side_widget = SideTableSelectParent( self, SQFile(Path(path), True), self.headers_inputs.set_parent, self.table.model_c.path_c) self.splitter.insertWidget(-1, self.side_widget) def open_child(self, path): ...
# --------------------------- # Splitterにwidgetをインデックスで指定した位置に追加する # --------------------------- import sys from PySide2.QtWidgets import QApplication, QSplitter, QTextEdit app = QApplication(sys.argv) qw_text_edit_left = QTextEdit() qw_text_edit_left.append('left') qw_text_edit_right = QTextEdit() qw_text_edit_right.append('right') qw_splitter = QSplitter() qw_splitter.addWidget(qw_text_edit_left) qw_splitter.addWidget(qw_text_edit_right) qw_text_edit_center = QTextEdit() qw_text_edit_center.append('center') qw_splitter.insertWidget(1, qw_text_edit_center) # index 1 の位置にwidgetを追加 print(qw_splitter.indexOf(qw_text_edit_left)) print(qw_splitter.indexOf(qw_text_edit_center)) print(qw_splitter.indexOf(qw_text_edit_right)) qw_splitter.show() sys.exit(app.exec_())
class BeeRocksAnalyzer(QMainWindow): def __init__(self, argv, parent=None): super(BeeRocksAnalyzer, self).__init__(parent) self.argv = argv self.isPlay = False self.restart = False self.restartDone = True self.log_start_pos = 0 self.wa_widget = None self.wa_widget_mod = None self.cm_widget = None self.cm_widget_mod = None self.log_widget = None self.log_widget_mod = None self.ap_mac2num = {} self.sta_mac2num = {} self.fdLog = None self.fname = "beerocks_analyzer.log" self.isMap = False self.isGraphs = False self.isMapSepWin = False self.isExtLogFile = False self.isOnline = False self.wait_for_mark = False for i in range(0, len(argv)): arg_i = argv[i].strip() if arg_i.startswith("-f="): self.fname = arg_i.split("=")[1] if arg_i.startswith("-map"): self.isMap = True if "win" in arg_i: self.isMapSepWin = True if arg_i.startswith("-graphs"): self.isGraphs = True if arg_i.startswith("-ext_log_file"): self.isExtLogFile = True if self.isExtLogFile: self.argv.remove("-ext_log_file") else: self.isOnline = True self.timer = QTimer(self) self.timer.setSingleShot(True) self.connect(self.timer, SIGNAL("timeout()"), self.timerEvent) self.timeUpdateSig = UpdateSig() self.timeUpdateSig.sig.connect(self.timeUpdateSlot) self.fileTime = 0.0 self.runTime = 0.0 self.threadExit = False self.thread = None self.main_widget = QSplitter(Qt.Horizontal) self.main_vsplitter = QSplitter(Qt.Vertical) self.main_vsplitter.addWidget(self.main_widget) self.createMainFrame() self.createMenu() self.setCentralWidget(self.main_vsplitter) self.createLoggerWidget(2) if self.isGraphs: self.createAnalyzerWidget(0) if self.isMap: self.createConnMapWidget(1) if not self.isExtLogFile: self.playPauseButtonTrig() def closeEvent(self, event): self.stopReadSampleThread() self.closeLogFiles() try: self.log_widget.close() except Exception: pass if self.isGraphs: try: self.wa_widget.close() except Exception: pass if self.isMap: try: self.cm_widget.close() except Exception: pass QMainWindow.closeEvent(self, event) def getLogFilePos(self): return self.fdLog.tell() def closeLogFiles(self): if self.fdLog is not None: self.fdLog.close() self.fdLog = None def openLogFiles(self, log_start_pos=0): self.closeLogFiles() if self.fname is not None: try: # open log file self.fdLog = open(self.fname, "rt") if log_start_pos > 0: logger.debug("openLogFiles: seek to {:d}", log_start_pos) self.fdLog.seek(log_start_pos, 0) elif log_start_pos < 0: logger.debug("openLogFiles: seek to end of file", log_start_pos) self.fdLog.seek(0, 2) except Exception: self.fdLog = None if self.fdLog is None: logger.error("Error(1): can't open file {}".format(self.fname)) sys.exit(0) def startReadSampleThread(self, update_start_time=False): self.start_time = time.time() - self.fileTime self.stopReadSampleThread() self.thread = threading.Thread(target=self.readSampleThread, args=(update_start_time, )) self.thread.start() def stopReadSampleThread(self): if self.thread is not None: if self.thread.isAlive(): self.threadExit = True try: self.thread.join(2) except Exception: pass del self.thread self.thread = None return True else: return True def readSample(self, line): param_n = [] param_v = [] param_t = -1 is_start = False is_stop = False is_map_update = False res = "" if line.startswith("#") or len(line) < 4: return [param_t, is_start, is_stop, is_map_update, res] i1 = line.find("|") if (i1 == -1): return [param_t, is_start, is_stop, is_map_update, res] param_t = float(line[0:i1]) i2 = line.find(",") if i2 == -1: # START or STOP or error try: param_m = (str(line[i1 + 1:])).strip() if param_m == "MARK": self.wait_for_mark = False self.cm_widget.increment_node_counters() if param_m == "START" or param_m == "MARK": is_start = True elif param_m == "STOP": is_stop = True else: logger.error("readSample() 1 line --> {}\n", line) except Exception as e: # TODO: too broad exception logger.error("readSample() 1 line --> {}\n{}", line, str(e)) return [param_t, is_start, is_stop, is_map_update, res] else: try: param_m = str(line[i1 + 1:i2]) # read line's key,value: param_n<-key, param_v<-value line_args = line[i2 + 1:].split(',') param_n = [] for arg in line_args: arg_val = arg.split(':') key = arg_val[0].lower().strip() param_n.append(key) if ("mac" in key) or ("bssid" in key) or ("backhaul" in key): param_v.append( arg.replace(":", "=", 1).split("=")[1].strip()) else: param_v.append(arg_val[1].strip()) except Exception as e: logger.error("readSample() 1 line --> {}".format(line)) logger.exception(e) return [param_t, is_start, is_stop, is_map_update, res] if param_v is None: return [param_t, is_start, is_stop, is_map_update, res] if not self.wait_for_mark: param_m_val = param_m.split(':') param_m_n = param_m_val[0].strip() if param_m_n != "type" and param_m_n != "Type": is_map_update = True if param_m_n == "Name": # nw_map_update try: i_state = param_n.index('state') i_mac = param_n.index('mac') i_type = param_n.index('type') except Exception as e: # TODO: too broad exception logger.error("readSample() --> {}, " "nw_map_update line does not contain state " "or mac or type or parent bssid".format(line)) logger.exception(e) return [param_t, is_start, is_stop, is_map_update, res] state = (param_v[i_state].split())[0] mac = param_v[i_mac] line_type = param_v[i_type] if "2" in line_type: # IRE try: i_backhaul = param_n.index('backhaul') mac = param_v[ i_backhaul] # for IRE, the backhaul mac is the "client" mac except Exception as e: # TODO: too broad exception logger.error("readSample() --> {}, " "nw_map_update IRE line does not contain " "a backhaul mac address\n{}".format( line, str(e))) return [param_t, is_start, is_stop, is_map_update, res] if ("2" in line_type) or ("3" in line_type): # IRE or client if state == "Connected": if mac not in self.sta_mac2num: # new sta mac addr self.sta_mac2num[mac] = len(self.sta_mac2num) res += "sta_id: {:d},".format(self.sta_mac2num[mac]) if res.endswith(','): res = res[:-1] elif param_m_n == "type" or param_m_n == "Type": param_m_v = int(param_m_val[1].strip()) try: i1 = param_n.index('mac') except Exception as e: # TODO: too broad exception logger.error( "readSample() --> {}, 'stats_update' line not contain mac address.\n{}" .format(line, e)) return [param_t, is_start, is_stop, is_map_update, res] mac = param_v[i1] i1 += 1 if param_m_v == 3: # Client stats update if mac not in self.sta_mac2num: # new sta mac addr self.sta_mac2num[mac] = len(self.sta_mac2num) res += "sta_id: %d" % self.sta_mac2num[mac] # End of readSample() return [param_t, is_start, is_stop, is_map_update, res] def readSampleThread(self, update_start_time=False): if self.isGraphs: self.wa_widget.update_start_time = True self.threadExit = False while not self.threadExit: if self.fdLog is not None: update_widgets = True line = self.fdLog.readline() if not line: time.sleep(1) continue [param_t1, is_start, is_stop, is_map_update, sta_ap_ids] = self.readSample(line) if len(sta_ap_ids) > 0: line += ", " + sta_ap_ids if is_stop: logger.info("Read stop flag, stopping") self.threadExit = True if param_t1 == -1: time.sleep(0.1) elif param_t1 == 0.000: logger.debug( "readSampleThread() --> param_t1=0.000, updating self.start_time" ) self.start_time = time.time() else: if update_start_time: logger.debug("updating start_time") self.start_time = time.time() - float(param_t1) update_start_time = False self.runTime = round(time.time() - self.start_time, 3) delta_t = self.fileTime - self.runTime if delta_t > 0.1: # if delta_t > 5: logger.debug( "readSampleThread() --> sleeping for {:.3f} seconds" .format(delta_t)) time.sleep(delta_t) elif (delta_t < -3) and (not is_map_update): logger.debug( "readSampleThread() --> (delta_t < -3) " "and (not is_map_update) -> update_widgets = False" ) update_widgets = False self.restartRuntime = False self.timeUpdateSig.sig.emit(param_t1) if update_widgets and not self.wait_for_mark: if "BML_EVENT" in line: self.log_widget.readSampleAndUpdateLogger( line, param_t1, is_start, is_stop) else: if self.isMap: logger.debug("self.isMap, calling readSample") self.cm_widget.readSample( line, param_t1, is_start, is_stop) if self.isGraphs: self.wa_widget.readSampleAndUpdateGraphs( line, param_t1, is_start, is_stop) def createAnalyzerWidget(self, widget_index=0): self.wa_widget_mod = __import__("beerocks_analyzer_widget") self.wa_widget = self.wa_widget_mod.BeeRocksAnalyzerWidget(self.argv) self.wa_widget.restartSig.sig.connect(self.resetFromLog) self.openLogFiles(self.log_start_pos) self.main_widget.insertWidget(widget_index, self.wa_widget) def createConnMapWidget(self, widget_index=0): self.cm_widget_mod = __import__("connectivity_map_widget") self.cm_widget = self.cm_widget_mod.ConnectivityMapWidget() self.cm_widget.restartSig.sig.connect(self.resetFromLog) self.openLogFiles(self.log_start_pos) if self.isMapSepWin: self.cm_widget.setWindowTitle("BeeRocks Analyzer V%s" % VERSION) self.cm_widget.setMinimumSize(800, 600) self.cm_widget.move(0, 0) self.cm_widget.show() else: self.main_widget.insertWidget(widget_index, self.cm_widget) def createLoggerWidget(self, widget_index=0): self.log_widget_mod = __import__("logger_widget") self.log_widget = self.log_widget_mod.LoggerWidget() self.log_widget.restartSig.sig.connect(self.resetFromLog) self.openLogFiles(self.log_start_pos) self.main_vsplitter.insertWidget(widget_index, self.log_widget) def resetAnalyzerWidget(self): self.wa_widget.setParent(None) self.wa_widget.close() self.wa_widget.deleteLater() def resetConnMapWidget(self): self.cm_widget.setParent(None) self.cm_widget.close() self.cm_widget.deleteLater() def resetLoggerWidget(self): self.log_widget.setParent(None) self.log_widget.close() self.log_widget.deleteLater() def timerEvent(self): if self.restart: self.restart = False del self.log_widget_mod sys.modules.pop("logger_widget") self.createLoggerWidget(2) if self.isGraphs: del self.wa_widget_mod sys.modules.pop("beerocks_analyzer_widget") self.createAnalyzerWidget(0) if self.isMap: del self.cm_widget_mod sys.modules.pop("connectivity_map_widget") self.createConnMapWidget(1) if self.isPlay: self.startReadSampleThread() self.restartDone = True def playPauseButtonTrig(self): if self.isPlay: self.stopReadSampleThread() if self.isExtLogFile: self.timeSlider.setEnabled(True) else: self.startReadSampleThread() if self.isExtLogFile: self.timeSlider.setEnabled(False) self.isPlay = not self.isPlay self.playPauseAction.setIcon( self.pauseIcon if self.isPlay else self.playIcon) def onlineOfflineButtonTrig(self): global g_marker_update self.stopReadSampleThread() self.isOnline = not self.isOnline self.createMainFrame() self.timeUpdateSig.sig.emit(self.fileTime) if self.isOnline: self.log_start_pos = -1 # end of file self.openLogFiles(self.log_start_pos) # ask for markers and only then start read sample thread g_marker_update = True self.wait_for_mark = True self.startReadSampleThread(True) elif self.isPlay: self.playPauseButtonTrig() self.onlineOfflineAction.setIcon( self.onlineIcon if self.isOnline else self.offlineIcon) def resetFromLog(self): self.log_start_pos = self.getLogFilePos() self.reset() def reset(self): if self.restartDone: self.restartDone = False self.resetLoggerWidget() if self.isGraphs: self.resetAnalyzerWidget() if self.isMap: self.resetConnMapWidget() self.ap_mac2num = {} self.sta_mac2num = {} self.restart = True self.timer.start(100) def createMenu(self): self.file_menu = self.menuBar().addMenu("&File") quit_action = self.createAction("&Quit", slot=self.close, shortcut="Ctrl+Q", tip="Close the application") self.file_menu.addAction(quit_action) def createAction(self, text, slot=None, shortcut=None, icon=None, tip=None, checkable=False, signal="triggered()"): action = QAction(text, self) if icon is not None: action.setIcon(QIcon(":/%s.png" % icon)) if shortcut is not None: action.setShortcut(shortcut) if tip is not None: action.setToolTip(tip) action.setStatusTip(tip) if slot is not None: self.connect(action, SIGNAL(signal), slot) if checkable: action.setCheckable(True) return action def createMainFrame(self): self.script_name = os.path.basename(__file__) self.scriptPath = os.path.abspath(__file__) self.scriptPath = self.scriptPath.replace(self.script_name, "") if self.scriptPath.find("\\"): self.scriptPath = self.scriptPath.replace("\\", "/") try: self.removeToolBar(self.toolbar) except Exception: pass self.toolbar = self.addToolBar('Tools') self.toolbarWidgetLayout = QHBoxLayout() self.playIcon = QIcon(self.scriptPath + "/icons/play.png") self.pauseIcon = QIcon(self.scriptPath + "/icons/pause.png") self.playPauseAction = QAction(self.playIcon, 'Play/Pause', self) self.playPauseAction.triggered.connect(self.playPauseButtonTrig) self.onlineIcon = QIcon(self.scriptPath + "/icons/online_button.png") self.offlineIcon = QIcon(self.scriptPath + "/icons/offline_button.png") self.onlineOfflineAction = QAction(self.onlineIcon, 'Online/Offline', self) self.onlineOfflineAction.triggered.connect( self.onlineOfflineButtonTrig) if not self.isExtLogFile: self.toolbar.addAction(self.onlineOfflineAction) if self.isExtLogFile or (not self.isOnline): # Play/Pause button self.toolbar.addAction(self.playPauseAction) # Slider self.getFileMarkers() self.timeSlider = QSlider() self.lastTimerValue = 0.0 self.lastTimerReleasedValue = 0.0 self.lastTimerValueChanged = False self.timeSlider.valueChanged.connect(self.timeSliderChanged) self.timeSlider.sliderReleased.connect(self.timeSliderReleased) self.timeSlider.setOrientation(Qt.Horizontal) self.timeSlider.setMinimum(0) self.timeSlider.setMaximum(len(self.file_marks) - 1) self.timeSlider.setTickInterval(1) self.timeSlider.setMaximumHeight(24) self.timeSlider.setMinimumWidth(220) self.timeSlider.setToolTip("Timeline slider") self.toolbarWidgetLayout.addWidget(self.timeSlider) # File Time self.fileTimeLabel = QLabel() self.fileTimeLabel.setText("File time:") self.fileTimeText = QTextEdit() self.fileTimeText.setEnabled(False) self.fileTimeText.setMaximumHeight(24) self.fileTimeText.setMaximumWidth(60) self.fileTimeText.setText("0.0") self.fileTimeText.setToolTip("Current Time") self.start_time = None self.toolbarWidgetLayout.addWidget(self.fileTimeLabel) self.toolbarWidgetLayout.addWidget(self.fileTimeText) self.toolbarWidgetLayout.addStretch(1) self.toolbarWidget = QWidget() self.toolbarWidget.setLayout(self.toolbarWidgetLayout) self.toolbar.addWidget(self.toolbarWidget) self.setWindowTitle("BeeRocks Analyzer V%s" % VERSION) self.setWindowIcon(QIcon(self.scriptPath + '/icons/wifi.ico')) self.setMinimumSize(800, 600) self.move(0, 0) self.showMaximized() def getFileMarkers(self): logger.info("Scanning log markers/time line...") fd = open(self.fname, "rt") line = " " self.file_marks = [] p = 0 t = "0.000" self.file_marks.append((p, t)) while True: p = fd.tell() line = fd.readline() if line == "": break i = line.find("|") t = line[0:i] if line.find("|MARK") != -1: self.file_marks.append((p, t)) fd.close() logger.info("Done.") def timeSliderChanged(self, value): self.lastTimerValueChanged = (self.lastTimerValue != value) self.lastTimerValue = value self.fileTimeText.setText(self.file_marks[self.lastTimerValue][1]) def timeSliderReleased(self): if (self.lastTimerReleasedValue != self.lastTimerValue) or (self.lastTimerValueChanged): self.lastTimerValueChanged = False self.lastTimerReleasedValue = self.lastTimerValue self.log_start_pos = self.file_marks[self.lastTimerValue][0] self.fileTime = float(self.file_marks[self.lastTimerValue][1]) self.reset() def timeUpdateSlot(self, t): self.fileTime = float(t) if self.restartRuntime or self.start_time is None: self.runTime = self.fileTime else: self.runTime = round(time.time() - self.start_time, 3) self.fileTimeText.setText(str(t))