Exemplo n.º 1
0
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):
        ...
Exemplo n.º 3
0
# ---------------------------
# 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_())
Exemplo n.º 4
0
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))