Beispiel #1
0
class DirectoryWidget(QToolButton):
    Sg_double_clicked = Signal(str)

    def __init__(self):
        super(DirectoryWidget, self).__init__()
        self.timer = QTimer()
        self.timer.setSingleShot(True)
        self.clicked.connect(self.Sl_check_double_click)

        self.setAccessibleName('Directory')

        self.setIcon(QIcon(resource_path("icons/Cartella.png")))
        self.setIconSize(QSize(45, 45))
        self.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)

    @Slot()
    def Sl_check_double_click(self):
        success = False
        if self.timer.isActive():
            time = self.timer.remainingTime()
            if time > 0:
                self.double_clicked_action()
                success = True
                self.timer.stop()
            if time <= 0:
                self.timer.start(250)

        if not self.timer.isActive() and not success:
            self.timer.start(250)

    def double_clicked_action(self) -> None:
        pass
Beispiel #2
0
class FileWidget(QToolButton):
    Sg_double_clicked = Signal()

    def __init__(self, file: File):
        super(FileWidget, self).__init__()

        self.timer = QTimer()
        self.timer.setSingleShot(True)

        self.clicked.connect(self.check_double_click)
        self.Sg_double_clicked.connect(self.Sl_on_double_click)

        self.setAccessibleName('File')

        self.name = file.get_name()
        self.creation_date = file.get_creation_date()
        self.last_modified_date = file.get_last_modified_date()

        self.extension = self.get_extension()

        self.set_icon()
        self.setText(self.name)

    def get_extension(self) -> str:
        if self.name.find('.') != -1:
            e = self.name.split(".")
            return e[-1]
        else:
            return "no"

    def set_icon(self):
        self.setIconSize(QSize(45, 45))
        self.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
        if self.extension in ["txt", "xml", "json", "docx", "xlsx"]:
            self.setIcon(QIcon(resource_path('icons/Txt.png')))
        elif self.extension in ["mp4", "avi", "mpeg", "wmv"]:
            self.setIcon(QIcon(resource_path('icons/Video.png')))
        elif self.extension in ["jpg", "png", "gif"]:
            self.setIcon(QIcon(resource_path('icons/Immagine.png')))
        elif self.extension in ["mp3", "wav", "ogg"]:
            self.setIcon(QIcon(resource_path('icons/Audio.png')))
        else:
            self.setIcon(QIcon(resource_path('icons/DocGenerico.png')))

    def check_double_click(self):
        if self.timer.isActive():
            time = self.timer.remainingTime()
            if time > 0:
                self.Sg_double_clicked.emit()
            self.timer.stop()
            if time <= 0:
                self.timer.start(250)

        if self.timer.isActive() is False:
            self.timer.start(250)

    @Slot()
    def Sl_on_double_click(self):
        pass
Beispiel #3
0
 def test_clustering_analyzer(self):
     dataset = random_dataset(**SIMPLE_PRESET, n_samples=200)
     main = ClusteringAnalyzer()
     main.show()
     main.on_dataset_loaded(dataset)
     timer = QTimer()
     timer.setSingleShot(True)
     timer.timeout.connect(main.save_result)
     timer.start(2000)
     app.exec()
Beispiel #4
0
class EntropyWidget(QWidget):
    def __init__(self, parent, view, data):
        super(EntropyWidget, self).__init__(parent)
        self.view = view
        self.data = data
        self.raw_data = data.file.raw

        self.block_size = (len(self.raw_data) / 4096) + 1
        if self.block_size < 1024:
            self.block_size = 1024
        self.width = int(len(self.raw_data) / self.block_size)
        self.image = QImage(self.width, 1, QImage.Format_ARGB32)
        self.image.fill(QColor(0, 0, 0, 0))

        self.thread = EntropyThread(self.raw_data, self.image, self.block_size)
        self.started = False

        self.timer = QTimer()
        self.timer.timeout.connect(self.timerEvent)
        self.timer.setInterval(100)
        self.timer.setSingleShot(False)
        self.timer.start()

        self.setMinimumHeight(UIContext.getScaledWindowSize(32, 32).height())

    def paintEvent(self, event):
        p = QPainter(self)
        p.drawImage(self.rect(), self.image)
        p.drawRect(self.rect())

    def sizeHint(self):
        return QSize(640, 32)

    def timerEvent(self):
        if not self.started:
            self.thread.start()
            self.started = True
        if self.thread.updated:
            self.thread.updated = False
            self.update()

    def mousePressEvent(self, event):
        if event.button() != Qt.LeftButton:
            return
        frac = float(event.x()) / self.rect().width()
        offset = int(frac * self.width * self.block_size)
        self.view.navigateToFileOffset(offset)
class TimerQT(TimerBase):
    def __init__(self, *args, **kwargs):
        self._timer = QTimer()
        self._timer.timeout.connect(self._on_timer)
        TimerBase.__init__(self, *args, **kwargs)

    def __del__(self):
        self._timer_stop()

    def _timer_set_single_shot(self):
        self._timer.setSingleShot(self._single)

    def _timer_set_interval(self):
        self._timer.setInterval(self._interval)

    def _timer_start(self):
        self._timer.start()

    def _timer_stop(self):
        self._timer.stop()
Beispiel #6
0
class ModList(QTableView):
    def __init__(self, parent: QWidget, model: Model) -> None:
        super().__init__(parent)

        settings = QSettings()

        self.hoverIndexRow = -1
        self.modmodel = model
        self.installLock = asyncio.Lock()

        self.setMouseTracking(True)
        self.setSelectionMode(QAbstractItemView.ExtendedSelection)
        self.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.setWordWrap(False)
        self.setSortingEnabled(True)
        self.setFocusPolicy(Qt.StrongFocus)
        self.setAcceptDrops(True)
        self.setEditTriggers(QTableView.EditKeyPressed
                             | QTableView.DoubleClicked)
        self.setShowGrid(False)

        self.setStyleSheet('''
            QTableView {
                gridline-color: rgba(255,255,255,1);
            }
            QTableView::item:!selected:hover {
                background-color: rgb(217, 235, 249);
            }
            ''')

        self.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel)
        self.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel)
        self.setContextMenuPolicy(Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(self.showContextMenu)

        self.verticalHeader().hide()
        self.verticalHeader().setVisible(False)
        self.setSectionSize(settings.value('compactMode', 'False') == 'True')

        self.setCornerButtonEnabled(False)
        self.horizontalHeader().setHighlightSections(False)
        self.horizontalHeader().setStretchLastSection(True)
        self.horizontalHeader().setSectionsMovable(True)

        self.listmodel = ModListModel(self, model)
        self.filtermodel = ModListFilterModel(self, self.listmodel)
        self.setModel(self.filtermodel)

        self.setItemDelegate(ModListItemDelegate(self))
        self.setSelectionModel(ModListSelectionModel(self, self.filtermodel))

        if len(model):
            self.modCountLastUpdate = len(model)
            self.resizeColumnsToContents()
        else:
            self.modCountLastUpdate = -1

        if settings.value('modlistHorizontalHeaderState'):
            self.horizontalHeader().restoreState(
                settings.value('modlistHorizontalHeaderState'))  # type: ignore

        self.horizontalHeader().sectionMoved.connect(
            lambda: self.headerChangedEvent())
        self.horizontalHeader().sectionResized.connect(
            lambda: self.headerChangedEvent())

        self.setFocus()

        self.sortByColumn(3, Qt.AscendingOrder, False)
        self.sortByColumn(2, Qt.AscendingOrder, False)
        self.sortByColumn(1, Qt.AscendingOrder, False)
        if settings.value('modlistSortColumn') is not None and \
           settings.value('modlistSortOrder') is not None:
            try:
                self.sortByColumn(
                    cast(int, settings.value('modlistSortColumn', 1,
                                             int)), Qt.DescendingOrder
                    if cast(int, settings.value('modlistSortOrder', 1, int))
                    else Qt.AscendingOrder, False)
            except Exception as e:
                logger.exception(f'could not restore sort order: {e}')
        self.horizontalHeader().sortIndicatorChanged.connect(self.sortByColumn)

        self.doubleClicked.connect(self.doubleClickEvent)
        model.updateCallbacks.append(self.modelUpdateEvent)

        # setup viewport caching to counter slow resizing with many table elements
        self.resizeTimer = QTimer(self)
        self.resizeTimer.setSingleShot(True)
        self.resizeTimer.setInterval(250)
        self.resizeTimer.timeout.connect(lambda: [
            self.resizeTimer.stop(),
            self.viewport().repaint(),
        ])
        self.viewportCache = QPixmap()
        self.viewportCacheSize = QSize(0, 0)

        # TODO: enhancement: offer option to read readme and other additional text files

    def setSectionSize(self, compact: bool) -> None:
        if compact:
            self.verticalHeader().setDefaultSectionSize(25)
        else:
            self.verticalHeader().setDefaultSectionSize(30)

    @debounce(200)
    async def headerChangedEvent(self) -> None:
        settings = QSettings()
        state = self.horizontalHeader().saveState()
        # call later to work around pyqt5 StopIteration exception
        asyncio.get_running_loop().call_later(
            25 / 1000.0,
            lambda: settings.setValue('modlistHorizontalHeaderState', state))

    def modelUpdateEvent(self, model: Model) -> None:
        if not self.modCountLastUpdate and len(self.modmodel):
            # if list was empty before, auto resize columns
            self.resizeColumnsToContents()
        self.modCountLastUpdate = len(self.modmodel)

    def mouseMoveEvent(self, event: QMouseEvent) -> None:
        self.hoverIndexRow = self.indexAt(event.pos()).row()
        return super().mouseMoveEvent(event)

    def wheelEvent(self, event: QWheelEvent) -> None:
        result = super().wheelEvent(event)
        # repaint previously hovered row on scroll avoid repaint artifacts
        index = self.model().index(self.hoverIndexRow, 0)
        self.hoverIndexRow = self.indexAt(event.position().toPoint()).row()
        rect = self.visualRect(index)
        rect.setLeft(0)
        rect.setRight(self.viewport().width())
        self.viewport().repaint(rect)
        return result

    def leaveEvent(self, event: QEvent) -> None:
        index = self.model().index(self.hoverIndexRow, 0)
        # unset row hover state and repaint previously hovered row
        self.hoverIndexRow = -1
        rect = self.visualRect(index)
        rect.setLeft(0)
        rect.setRight(self.viewport().width())
        self.viewport().repaint(rect)
        return super().leaveEvent(event)

    def doubleClickEvent(self, index: QModelIndex) -> None:
        if self.filtermodel.mapToSource(index).column() == 0:
            mod = self.modmodel[self.filtermodel.mapToSource(index).row()]
            if mod.enabled:
                asyncio.create_task(self.modmodel.disable(mod))
            else:
                asyncio.create_task(self.modmodel.enable(mod))

    def resizeEvent(self, event: QResizeEvent) -> None:
        super().resizeEvent(event)
        if not self.resizeTimer.isActive(
        ) and event.size() != self.viewportCacheSize:
            self.viewportCacheSize = event.size()
            self.viewportCache = self.viewport().grab()
            self.resizeTimer.start()

    def paintEvent(self, event: QPaintEvent) -> None:
        if self.resizeTimer.isActive():
            painter = QPainter(self.viewport())
            painter.drawPixmap(0, 0, self.viewportCache)
        else:
            super().paintEvent(event)

    def selectionChanged(self, selected: QItemSelection,
                         deselected: QItemSelection) -> None:
        super().selectionChanged(selected, deselected)

    def eventFilter(self, obj: QObject, event: QEvent) -> bool:
        return super().eventFilter(obj, event)

    def sortByColumn(self,
                     col: int,
                     order: Qt.SortOrder,
                     save: bool = True) -> None:  # type: ignore
        if save and col is not None and order is not None:
            settings = QSettings()
            settings.setValue('modlistSortColumn', col)
            settings.setValue('modlistSortOrder',
                              0 if order == Qt.AscendingOrder else 1)
        super().sortByColumn(col, order)

    def showContextMenu(self, pos: QPoint) -> None:
        mods = self.getSelectedMods()
        if not mods:
            return
        menu = QMenu(self)
        actionOpen = menu.addAction('&Open Directory')
        actionOpen.setIcon(
            QIcon(str(getRuntimePath('resources/icons/open-folder.ico'))))
        actionOpen.triggered.connect(lambda: [
            util.openDirectory(self.modmodel.getModPath(mod))  # type: ignore
            for mod in mods
        ])
        menu.addSeparator()
        actionEnable = menu.addAction('&Enable')
        actionEnable.triggered.connect(
            lambda: [asyncio.create_task(self.enableSelectedMods(True))])
        actionEnable.setEnabled(not all(mod.enabled for mod in mods))
        actionDisable = menu.addAction('&Disable')
        actionDisable.triggered.connect(
            lambda: [asyncio.create_task(self.enableSelectedMods(False))])
        actionDisable.setEnabled(not all(not mod.enabled for mod in mods))
        menu.addSeparator()
        actionUninstall = menu.addAction('&Uninstall')
        actionUninstall.triggered.connect(
            lambda: [asyncio.create_task(self.deleteSelectedMods())])
        menu.addSeparator()
        actionOpenNexus = menu.addAction('Open &Nexus Mods page')
        actionOpenNexus.setIcon(
            QIcon(str(getRuntimePath('resources/icons/browse.ico'))))
        actionOpenNexus.triggered.connect(lambda: [
            QDesktopServices.openUrl(
                QUrl(f'https://www.nexusmods.com/witcher3/mods/{modid}'))
            for modid in {mod.modid
                          for mod in mods if mod.modid > 0}
        ])
        actionOpenNexus.setEnabled(not all(mod.modid <= 0 for mod in mods))

        menu.popup(self.viewport().mapToGlobal(pos))

    def selectRowChecked(self, row: int) -> None:
        nums: int = self.filtermodel.rowCount()
        if row < nums and row >= 0:
            self.selectRow(row)
        elif nums > 0:
            self.selectRow(nums - 1)

    def getSelectedMods(self) -> List[Mod]:
        return [
            self.modmodel[self.filtermodel.mapToSource(index).row()]
            for index in self.selectionModel().selectedRows()
        ]

    async def enableSelectedMods(self, enable: bool = True) -> None:
        if not self.selectionModel().hasSelection():
            return
        mods = self.getSelectedMods()
        self.setDisabled(True)
        for mod in mods:
            try:
                if enable:
                    await self.modmodel.enable(mod)
                else:
                    await self.modmodel.disable(mod)
            except Exception as e:
                logger.bind(name=mod.filename).exception(
                    f'Could not enable/disable mod: {e}')
        self.setDisabled(False)
        self.setFocus()

    async def deleteSelectedMods(self) -> None:
        if not self.selectionModel().hasSelection():
            return
        self.setDisabled(True)
        mods = self.getSelectedMods()
        # TODO: incomplete: ask if selected mods should really be removed
        inds = self.selectedIndexes()
        self.selectionModel().clear()
        for mod in mods:
            try:
                await self.modmodel.remove(mod)
            except Exception as e:
                logger.bind(
                    name=mod.filename).exception(f'Could not delete mod: {e}')
        asyncio.get_running_loop().call_later(
            100 / 1000.0, partial(self.selectRowChecked, inds[0].row()))
        self.setDisabled(False)
        self.setFocus()

    async def updateModDetails(self, mod: Mod) -> bool:
        logger.bind(name=mod.filename,
                    dots=True).debug('Requesting details for mod')
        if not mod.md5hash:
            logger.bind(name=mod.filename).warning(
                'Could not get details for mod not installed from archive')
            return False
        try:
            details = await getModInformation(mod.md5hash)
        except Exception as e:
            logger.bind(name=mod.filename).warning(f'{e}')
            return False
        try:
            package = str(details[0]['mod']['name'])
            summary = str(details[0]['mod']['summary'])
            modid = int(details[0]['mod']['mod_id'])
            category = int(details[0]['mod']['category_id'])
            version = str(details[0]['file_details']['version'])
            fileid = int(details[0]['file_details']['file_id'])
            uploadname = str(details[0]['file_details']['name'])
            uploadtime = str(details[0]['file_details']['uploaded_time'])
            mod.package = package
            mod.summary = summary
            mod.modid = modid
            mod.category = getCategoryName(category)
            mod.version = version
            mod.fileid = fileid
            mod.uploadname = uploadname
            uploaddate = dateparser.parse(uploadtime)
            if uploaddate:
                mod.uploaddate = uploaddate.astimezone(tz=timezone.utc)
            else:
                logger.bind(name=mod.filename).debug(
                    f'Could not parse date {uploadtime} in mod information response'
                )
        except KeyError as e:
            logger.bind(name=mod.filename).exception(
                f'Could not find key "{str(e)}" in mod information response')
            return False
        try:
            await self.modmodel.update(mod)
        except Exception as e:
            logger.bind(
                name=mod.filename).exception(f'Could not update mod: {e}')
            return False
        return True

    async def updateSelectedModsDetails(self) -> None:
        if not self.selectionModel().hasSelection():
            return
        self.setDisabled(True)
        updatetime = datetime.now(tz=timezone.utc)
        mods = self.getSelectedMods()
        logger.bind(
            newline=True,
            output=False).debug(f'Requesting details for {len(mods)} mods')
        results = await asyncio.gather(
            *[self.updateModDetails(mod) for mod in mods],
            loop=asyncio.get_running_loop(),
            return_exceptions=True)
        successes = sum(results)
        errors = len(results) - successes
        message = 'Updated details for {0} mods{1}'.format(
            successes, f' ({errors} errors)' if errors else '')
        if errors:
            logger.warning(message)
        else:
            logger.success(message)
        self.modmodel.setLastUpdateTime(updatetime)
        self.setDisabled(False)
        self.setFocus()

    async def changeSelectedModsPriority(self, delta: int) -> None:
        mods = self.getSelectedMods()
        await asyncio.gather(*[
            self.modmodel.setPriority(
                mod, max(-1, min(9999, int(mod.priority + delta))))
            for mod in mods if mod.datatype in (
                'mod',
                'udf',
            )
        ],
                             loop=asyncio.get_running_loop())
        self.modmodel.setLastUpdateTime(datetime.now(tz=timezone.utc))

    def keyPressEvent(self, event: QKeyEvent) -> None:
        if event.key() == Qt.Key_Escape:
            self.selectionModel().clear()
        elif event.matches(QKeySequence.Delete):
            asyncio.create_task(self.deleteSelectedMods())
        elif event.modifiers(
        ) & Qt.ControlModifier == Qt.ControlModifier and event.key(
        ) == Qt.Key_Up:
            asyncio.create_task(self.changeSelectedModsPriority(1))
        elif event.modifiers(
        ) & Qt.ControlModifier == Qt.ControlModifier and event.key(
        ) == Qt.Key_Down:
            asyncio.create_task(self.changeSelectedModsPriority(-1))
        elif event.modifiers(
        ) & Qt.ControlModifier == Qt.ControlModifier and event.key(
        ) == Qt.Key_P:
            index = self.selectionModel().selectedRows()[0]
            index = index.sibling(index.row(), 5)
            if index.flags() & Qt.ItemIsEditable:
                self.setCurrentIndex(index)
                self.edit(index)
        else:
            super().keyPressEvent(event)

    def setFilter(self, search: str) -> None:
        self.filtermodel.setFilterRegularExpression(
            QRegularExpression(search,
                               QRegularExpression.CaseInsensitiveOption))

    async def checkInstallFromURLs(self,
                                   paths: List[Union[str, QUrl]],
                                   local: bool = True,
                                   web: bool = True) -> None:
        await self.installLock.acquire()
        installed = 0
        errors = 0
        installtime = datetime.now(tz=timezone.utc)
        # remove duplicate paths
        paths = list(set(paths))
        logger.bind(newline=True,
                    output=False).debug('Starting install from URLs')
        try:
            results = await asyncio.gather(*[
                self.installFromURL(path, local, web, installtime)
                for path in paths
            ],
                                           loop=asyncio.get_running_loop())
            for result in results:
                installed += result[0]
                errors += result[1]
        except Exception as e:
            # we should never land here, but don't lock up the UI if it happens
            logger.exception(str(e))
            errors += 1

        if installed > 0 or errors > 0:
            log = logger.bind(modlist=bool(installed))
            message = 'Installed {0} mods{1}'.format(
                installed, f' ({errors} errors)' if errors else '')
            if installed > 0 and errors > 0:
                log.warning(message)
            elif installed > 0:
                log.success(message)
            else:
                log.error(message)
        self.setDisabled(False)
        self.setFocus()
        self.installLock.release()

    async def installFromURL(
            self,
            path: Union[str, QUrl],
            local: bool = True,
            web: bool = True,
            installtime: Optional[datetime] = None) -> Tuple[int, int]:
        installed = 0
        errors = 0
        if not installtime:
            installtime = datetime.now(tz=timezone.utc)
        if isinstance(path, QUrl):
            path = path.toString()
        if web and isValidModDownloadUrl(path):
            self.setDisabled(True)
            logger.bind(dots=True, path=path).info(f'Installing mods from')
            i, e = await self.installFromFileDownload(path, installtime)
            installed += i
            errors += e
        elif local and isValidFileUrl(path):
            self.setDisabled(True)
            path = QUrl(path)
            logger.bind(dots=True, path=Path(
                path.toLocalFile())).info(f'Installing mods from')
            i, e = await self.installFromFile(Path(path.toLocalFile()),
                                              installtime)
            installed += i
            errors += e
        else:
            logger.bind(path=path).error('Could not install mods from')
        return installed, errors

    async def installFromFileDownload(
            self,
            url: str,
            installtime: Optional[datetime] = None) -> Tuple[int, int]:
        installed = 0
        errors = 0
        if not installtime:
            installtime = datetime.now(tz=timezone.utc)
        try:
            target = Path(urlparse(url).path)
            filename = re.sub(r'[^\w\-_\. ]', '_', unquote(target.name))
            target = Path(tempfile.gettempdir()).joinpath(
                'w3modmanager/download').joinpath(f'{filename}')
        except ValueError:
            logger.bind(name=url).exception('Wrong request URL')
            return 0, 1
        try:
            target.parent.mkdir(parents=True, exist_ok=True)
            logger.bind(name=url).info('Starting to download file')
            await downloadFile(url, target)
            installed, errors = await self.installFromFile(target, installtime)
        except (RequestError, ResponseError, Exception) as e:
            logger.bind(name=url).exception(f'Failed to download file: {e}')
            return 0, 1
        except Exception as e:
            logger.exception(str(e))
            return 0, 1
        finally:
            if target.is_file():
                target.unlink()
        return installed, errors

    async def installFromFile(
            self,
            path: Path,
            installtime: Optional[datetime] = None) -> Tuple[int, int]:
        originalpath = path
        installed = 0
        errors = 0
        archive = path.is_file()
        source = None
        md5hash = ''
        details = None
        detailsrequest: Optional[asyncio.Task] = None

        if not installtime:
            installtime = datetime.now(tz=timezone.utc)
        try:
            if archive:
                # unpack archive, set source and request details
                md5hash = getMD5Hash(path)
                source = path
                settings = QSettings()
                if settings.value('nexusGetInfo', 'False') == 'True':
                    logger.bind(
                        path=str(path),
                        dots=True).debug('Requesting details for archive')
                    detailsrequest = asyncio.create_task(
                        getModInformation(md5hash))
                logger.bind(path=str(path),
                            dots=True).debug('Unpacking archive')
                path = await extractMod(source)

            # validate and read mod
            valid, exhausted = containsValidMod(path, searchlimit=8)
            if not valid:
                if not exhausted and self.showContinueSearchDialog(
                        searchlimit=8):
                    if not containsValidMod(path):
                        raise InvalidPathError(path, 'Invalid mod')
                elif not exhausted:
                    raise InvalidPathError(path, 'Stopped searching for mod')
                else:
                    raise InvalidPathError(path, 'Invalid mod')
            mods = await Mod.fromDirectory(path, searchCommonRoot=not archive)

            installedMods = []
            # update mod details and add mods to the model
            for mod in mods:
                mod.md5hash = md5hash
                try:
                    # TODO: incomplete: check if mod is installed, ask if replace
                    await self.modmodel.add(mod)
                    installedMods.append(mod)
                    installed += 1
                except ModExistsError:
                    logger.bind(path=source if source else mod.source,
                                name=mod.filename).error(f'Mod exists')
                    errors += 1
                    continue

            # wait for details response if requested
            if detailsrequest:
                try:
                    details = await detailsrequest
                except (RequestError, ResponseError, Exception) as e:
                    logger.warning(
                        f'Could not get information for {source.name if source else path.name}: {e}'
                    )

            # update mod with additional information
            if source or details:
                for mod in installedMods:
                    if source:
                        # set source if it differs from the scan directory, e.g. an archive
                        mod.source = source
                    if details:
                        # set additional details if requested and available
                        try:
                            package = str(details[0]['mod']['name'])
                            summary = str(details[0]['mod']['summary'])
                            modid = int(details[0]['mod']['mod_id'])
                            category = int(details[0]['mod']['category_id'])
                            version = str(
                                details[0]['file_details']['version'])
                            fileid = int(details[0]['file_details']['file_id'])
                            uploadname = str(
                                details[0]['file_details']['name'])
                            uploadtime = str(
                                details[0]['file_details']['uploaded_time'])
                            mod.package = package
                            mod.summary = summary
                            mod.modid = modid
                            mod.category = getCategoryName(category)
                            mod.version = version
                            mod.fileid = fileid
                            mod.uploadname = uploadname
                            uploaddate = dateparser.parse(uploadtime)
                            if uploaddate:
                                mod.uploaddate = uploaddate.astimezone(
                                    tz=timezone.utc)
                            else:
                                logger.bind(name=mod.filename).debug(
                                    f'Could not parse date {uploadtime} in mod information response'
                                )
                        except KeyError as e:
                            logger.bind(name=mod.filename).exception(
                                f'Could not find key "{str(e)}" in mod information response'
                            )
                    try:
                        await self.modmodel.update(mod)
                    except Exception:
                        logger.bind(name=mod.filename).warning(
                            'Could not update mod details')

        except ModelError as e:
            logger.bind(path=e.path).error(e.message)
            errors += 1
        except InvalidPathError as e:
            # TODO: enhancement: better install error message
            logger.bind(path=e.path).error(e.message)
            errors += 1
        except FileNotFoundError as e:
            logger.bind(
                path=e.filename).error(e.strerror if e.strerror else str(e))
            errors += 1
        except OSError as e:
            logger.bind(
                path=e.filename).error(e.strerror if e.strerror else str(e))
            errors += 1
        except Exception as e:
            logger.exception(str(e))
            errors += 1
        finally:
            if detailsrequest and not detailsrequest.done():
                detailsrequest.cancel()
            if archive and not path == originalpath:
                try:
                    util.removeDirectory(path)
                except Exception:
                    logger.bind(path=path).warning(
                        'Could not remove temporary directory')
            self.modmodel.setLastUpdateTime(installtime)
            self.repaint()
        return installed, errors

    def showContinueSearchDialog(self, searchlimit: int) -> bool:
        messagebox = QMessageBox(self)
        messagebox.setWindowTitle('Unusual search depth')
        messagebox.setText(f'''
            <p>No mod detected after searching through {searchlimit} directories.</p>
            <p>Are you sure this is a valid mod?</p>
            ''')
        messagebox.setTextFormat(Qt.RichText)
        messagebox.setStandardButtons(QMessageBox.Cancel)
        yes: QPushButton = QPushButton(' Yes, continue searching ', messagebox)
        yes.setAutoDefault(True)
        yes.setDefault(True)
        messagebox.addButton(yes, QMessageBox.YesRole)
        messagebox.exec_()
        return messagebox.clickedButton() == yes

    def dropEvent(self, event: QDropEvent) -> None:
        event.accept()
        self.setDisabled(True)
        self.repaint()
        asyncio.create_task(self.checkInstallFromURLs(event.mimeData().urls()))

    def dragEnterEvent(self, event: QDragEnterEvent) -> None:
        self.setDisabled(True)
        self.repaint()
        urls = event.mimeData().urls()
        if not urls:
            self.setDisabled(False)
            self.setFocus()
            event.ignore()
            return
        for url in urls:
            try:
                parse = urlparse(url.toString())
                if parse.scheme not in ['file']:
                    self.setDisabled(False)
                    event.ignore()
                    return
                filepath = Path(url.toLocalFile())
                if isArchive(filepath) or containsValidMod(filepath,
                                                           searchlimit=8)[0]:
                    self.setDisabled(False)
                    event.accept()
                    return
            except Exception as e:
                logger.debug(str(e))
        self.setDisabled(False)
        self.setFocus()
        event.ignore()

    def dragMoveEvent(self, event: QDragMoveEvent) -> None:
        event.accept()

    def dragLeaveEvent(self, event: QDragLeaveEvent) -> None:
        event.accept()
class GameConnection(QObject, ConnectionBackend):
    Updated = Signal()

    _dt: float = 2.5
    _last_status: Any = None
    _permanent_pickups: List[Tuple[str, PickupEntry]]

    def __init__(self, executor: MemoryOperationExecutor):
        super().__init__()
        ConnectionBackend.__init__(self, executor)
        self._permanent_pickups = []

        self._timer = QTimer(self)
        self._timer.timeout.connect(self._auto_update)
        self._timer.setInterval(self._dt * 1000)
        self._timer.setSingleShot(True)
        self._notify_status()

    def set_executor(self, executor: MemoryOperationExecutor):
        self.executor = executor
        self._notify_status()

    async def start(self):
        self._timer.start()

    async def stop(self):
        self._timer.stop()

    @asyncSlot()
    async def _auto_update(self):
        try:
            await self.update(self._dt)
            self._notify_status()
        finally:
            self._timer.start()

    def _notify_status(self):
        new_status = self.current_status
        inventory = self.get_current_inventory()

        if self._last_status != (new_status, self.executor, inventory):
            self._last_status = (new_status, self.executor,
                                 copy.copy(inventory))
            self.Updated.emit()

    @property
    def pretty_current_status(self) -> str:
        return f"{self.backend_choice.pretty_text}: {self.current_status.pretty_text}"

    @property
    def current_game_name(self) -> Optional[str]:
        if self.connector is not None:
            return self.connector.game_enum.long_name

    @property
    def name(self) -> str:
        raise ValueError("bleh")

    def set_location_collected_listener(self,
                                        listener: Optional[LocationListener]):
        super(ConnectionBackend,
              self).set_location_collected_listener(listener)
        self.checking_for_collected_index = listener is not None
Beispiel #8
0
class MainWindow(QMainWindow):
    """Voice Changer main window."""
    def __init__(self, parent=None):
        super(MainWindow, self).__init__()
        self.statusBar().showMessage("Move Dial to Deform Microphone Voice !.")
        self.setWindowTitle(__doc__)
        self.setMinimumSize(240, 240)
        self.setMaximumSize(480, 480)
        self.resize(self.minimumSize())
        self.setWindowIcon(QIcon.fromTheme("audio-input-microphone"))
        self.tray = QSystemTrayIcon(self)
        self.center()
        QShortcut("Ctrl+q", self, activated=lambda: self.close())
        self.menuBar().addMenu("&File").addAction("Quit", lambda: exit())
        self.menuBar().addMenu("Sound").addAction(
            "STOP !", lambda: call('killall rec', shell=True))
        windowMenu = self.menuBar().addMenu("&Window")
        windowMenu.addAction("Hide", lambda: self.hide())
        windowMenu.addAction("Minimize", lambda: self.showMinimized())
        windowMenu.addAction("Maximize", lambda: self.showMaximized())
        windowMenu.addAction("Restore", lambda: self.showNormal())
        windowMenu.addAction("FullScreen", lambda: self.showFullScreen())
        windowMenu.addAction("Center", lambda: self.center())
        windowMenu.addAction("Top-Left", lambda: self.move(0, 0))
        windowMenu.addAction("To Mouse", lambda: self.move_to_mouse_position())
        # widgets
        group0 = QGroupBox("Voice Deformation")
        self.setCentralWidget(group0)
        self.process = QProcess(self)
        self.process.error.connect(
            lambda: self.statusBar().showMessage("Info: Process Killed", 5000))
        self.control = QDial()
        self.control.setRange(-10, 20)
        self.control.setSingleStep(5)
        self.control.setValue(0)
        self.control.setCursor(QCursor(Qt.OpenHandCursor))
        self.control.sliderPressed.connect(
            lambda: self.control.setCursor(QCursor(Qt.ClosedHandCursor)))
        self.control.sliderReleased.connect(
            lambda: self.control.setCursor(QCursor(Qt.OpenHandCursor)))
        self.control.valueChanged.connect(
            lambda: self.control.setToolTip(f"<b>{self.control.value()}"))
        self.control.valueChanged.connect(lambda: self.statusBar().showMessage(
            f"Voice deformation: {self.control.value()}", 5000))
        self.control.valueChanged.connect(self.run)
        self.control.valueChanged.connect(lambda: self.process.kill())
        # Graphic effect
        self.glow = QGraphicsDropShadowEffect(self)
        self.glow.setOffset(0)
        self.glow.setBlurRadius(99)
        self.glow.setColor(QColor(99, 255, 255))
        self.control.setGraphicsEffect(self.glow)
        self.glow.setEnabled(False)
        # Timer to start
        self.slider_timer = QTimer(self)
        self.slider_timer.setSingleShot(True)
        self.slider_timer.timeout.connect(self.on_slider_timer_timeout)
        # an icon and set focus
        QLabel(self.control).setPixmap(
            QIcon.fromTheme("audio-input-microphone").pixmap(32))
        self.control.setFocus()
        QVBoxLayout(group0).addWidget(self.control)
        self.menu = QMenu(__doc__)
        self.menu.addAction(__doc__).setDisabled(True)
        self.menu.setIcon(self.windowIcon())
        self.menu.addSeparator()
        self.menu.addAction(
            "Show / Hide", lambda: self.hide()
            if self.isVisible() else self.showNormal())
        self.menu.addAction("STOP !", lambda: call('killall rec', shell=True))
        self.menu.addSeparator()
        self.menu.addAction("Quit", lambda: exit())
        self.tray.setContextMenu(self.menu)
        self.make_trayicon()

    def run(self):
        """Run/Stop the QTimer."""
        if self.slider_timer.isActive():
            self.slider_timer.stop()
        self.glow.setEnabled(True)
        call('killall rec ; killall play', shell=True)
        self.slider_timer.start(3000)

    def on_slider_timer_timeout(self):
        """Run subprocess to deform voice."""
        self.glow.setEnabled(False)
        value = int(self.control.value()) * 100
        command = f'play -q -V0 "|rec -q -V0 -n -d -R riaa bend pitch {value} "'
        print(f"Voice Deformation Value: {value}")
        print(f"Voice Deformation Command: {command}")
        self.process.start(command)
        if self.isVisible():
            self.statusBar().showMessage("Minimizing to System TrayIcon", 3000)
            print("Minimizing Main Window to System TrayIcon now...")
            sleep(3)
            self.hide()

    def center(self):
        """Center Window on the Current Screen,with Multi-Monitor support."""
        window_geometry = self.frameGeometry()
        mousepointer_position = QApplication.desktop().cursor().pos()
        screen = QApplication.desktop().screenNumber(mousepointer_position)
        centerPoint = QApplication.desktop().screenGeometry(screen).center()
        window_geometry.moveCenter(centerPoint)
        self.move(window_geometry.topLeft())

    def move_to_mouse_position(self):
        """Center the Window on the Current Mouse position."""
        window_geometry = self.frameGeometry()
        window_geometry.moveCenter(QApplication.desktop().cursor().pos())
        self.move(window_geometry.topLeft())

    def make_trayicon(self):
        """Make a Tray Icon."""
        if self.windowIcon() and __doc__:
            self.tray.setIcon(self.windowIcon())
            self.tray.setToolTip(__doc__)
            self.tray.activated.connect(
                lambda: self.hide() if self.isVisible() else self.showNormal())
            return self.tray.show()
Beispiel #9
0
class ByteView(QAbstractScrollArea, View):
    def __init__(self, parent, data):
        QAbstractScrollArea.__init__(self, parent)
        View.__init__(self)
        View.setBinaryDataNavigable(self, True)
        self.setupView(self)
        self.data = data
        self.byte_mapping = [
            u' ', u'☺', u'☻', u'♥', u'♦', u'♣', u'♠', u'•', u'◘', u'○', u'◙',
            u'♂', u'♀', u'♪', u'♫', u'☼', u'▸', u'◂', u'↕', u'‼', u'¶', u'§',
            u'▬', u'↨', u'↑', u'↓', u'→', u'←', u'∟', u'↔', u'▴', u'▾', u' ',
            u'!', u'"', u'#', u'$', u'%', u'&', u'\'', u'(', u')', u'*', u'+',
            u',', u'-', u'.', u'/', u'0', u'1', u'2', u'3', u'4', u'5', u'6',
            u'7', u'8', u'9', u':', u';', u'<', u'=', u'>', u'?', u'@', u'A',
            u'B', u'C', u'D', u'E', u'F', u'G', u'H', u'I', u'J', u'K', u'L',
            u'M', u'N', u'O', u'P', u'Q', u'R', u'S', u'T', u'U', u'V', u'W',
            u'X', u'Y', u'Z', u'[', u'\\', u']', u'^', u'_', u'`', u'a', u'b',
            u'c', u'd', u'e', u'f', u'g', u'h', u'i', u'j', u'k', u'l', u'm',
            u'n', u'o', u'p', u'q', u'r', u's', u't', u'u', u'v', u'w', u'x',
            u'y', u'z', u'{', u'|', u'}', u'~', u'⌂', u'Ç', u'ü', u'é', u'â',
            u'ä', u'à', u'å', u'ç', u'ê', u'ë', u'è', u'ï', u'î', u'ì', u'Ä',
            u'Å', u'É', u'æ', u'Æ', u'ô', u'ö', u'ò', u'û', u'ù', u'ÿ', u'Ö',
            u'Ü', u'¢', u'£', u'¥', u'₧', u'ƒ', u'á', u'í', u'ó', u'ú', u'ñ',
            u'Ñ', u'ª', u'º', u'¿', u'⌐', u'¬', u'½', u'¼', u'¡', u'«', u'»',
            u'░', u'▒', u'▓', u'│', u'┤', u'╡', u'╢', u'╖', u'╕', u'╣', u'║',
            u'╗', u'╝', u'╜', u'╛', u'┐', u'└', u'┴', u'┬', u'├', u'─', u'┼',
            u'╞', u'╟', u'╚', u'╔', u'╩', u'╦', u'╠', u'═', u'╬', u'╧', u'╨',
            u'╤', u'╥', u'╙', u'╘', u'╒', u'╓', u'╫', u'╪', u'┘', u'┌', u'█',
            u'▄', u'▌', u'▐', u'▀', u'α', u'ß', u'Γ', u'π', u'Σ', u'σ', u'µ',
            u'τ', u'Φ', u'Θ', u'Ω', u'δ', u'∞', u'φ', u'ε', u'∩', u'≡', u'±',
            u'≥', u'≤', u'⌠', u'⌡', u'÷', u'≈', u'°', u'∙', u'·', u'√', u'ⁿ',
            u'²', u'■', u' '
        ]

        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
        self.setFocusPolicy(Qt.StrongFocus)

        self.cursorAddr = self.data.start
        self.prevCursorAddr = self.cursorAddr
        self.selectionStartAddr = self.cursorAddr
        self.topAddr = self.cursorAddr
        self.topLine = 0
        self.selectionVisible = False
        self.caretVisible = False
        self.caretBlink = True
        self.leftButtonDown = False
        self.cols = 128
        self.updatesRequired = False
        self.visibleRows = 1
        self.lines = []

        self.updateRanges()

        areaSize = self.viewport().size()
        self.adjustSize(areaSize.width(), areaSize.height())

        if self.allocatedLength > 0x7fffffff:
            self.scrollBarMultiplier = (self.allocatedLength // 0x7fffffff) + 1
        else:
            self.scrollBarMultiplier = 1
        self.wheelDelta = 0
        self.updatingScrollBar = False
        self.verticalScrollBar().setRange(0, (self.allocatedLength - 1) //
                                          self.scrollBarMultiplier)
        self.verticalScrollBar().sliderMoved.connect(self.scrollBarMoved)
        self.verticalScrollBar().actionTriggered.connect(self.scrollBarAction)

        self.cursorTimer = QTimer(self)
        self.cursorTimer.setInterval(500)
        self.cursorTimer.setSingleShot(False)
        self.cursorTimer.timeout.connect(self.cursorTimerEvent)
        self.cursorTimer.start()

        self.updateTimer = QTimer(self)
        self.updateTimer.setInterval(200)
        self.updateTimer.setSingleShot(False)
        #self.updateTimer.timeout.connect(self.updateTimerEvent)

        self.actionHandler().bindAction("Move Cursor Up",
                                        UIAction(lambda ctxt: self.up(False)))
        self.actionHandler().bindAction(
            "Move Cursor Down", UIAction(lambda ctxt: self.down(False)))
        self.actionHandler().bindAction(
            "Move Cursor Left", UIAction(lambda ctxt: self.left(1, False)))
        self.actionHandler().bindAction(
            "Move Cursor Right", UIAction(lambda ctxt: self.right(1, False)))
        self.actionHandler().bindAction(
            "Move Cursor Word Left",
            UIAction(lambda ctxt: self.left(8, False)))
        self.actionHandler().bindAction(
            "Move Cursor Word Right",
            UIAction(lambda ctxt: self.right(8, False)))
        self.actionHandler().bindAction("Extend Selection Up",
                                        UIAction(lambda ctxt: self.up(True)))
        self.actionHandler().bindAction("Extend Selection Down",
                                        UIAction(lambda ctxt: self.down(True)))
        self.actionHandler().bindAction(
            "Extend Selection Left", UIAction(lambda ctxt: self.left(1, True)))
        self.actionHandler().bindAction(
            "Extend Selection Right",
            UIAction(lambda ctxt: self.right(1, True)))
        self.actionHandler().bindAction(
            "Extend Selection Word Left",
            UIAction(lambda ctxt: self.left(8, True)))
        self.actionHandler().bindAction(
            "Extend Selection Word Right",
            UIAction(lambda ctxt: self.right(8, True)))
        self.actionHandler().bindAction(
            "Page Up", UIAction(lambda ctxt: self.pageUp(False)))
        self.actionHandler().bindAction(
            "Page Down", UIAction(lambda ctxt: self.pageDown(False)))
        self.actionHandler().bindAction(
            "Extend Selection Page Up",
            UIAction(lambda ctxt: self.pageUp(True)))
        self.actionHandler().bindAction(
            "Extend Selection Page Down",
            UIAction(lambda ctxt: self.pageDown(True)))
        self.actionHandler().bindAction(
            "Move Cursor to Start of Line",
            UIAction(lambda ctxt: self.moveToStartOfLine(False)))
        self.actionHandler().bindAction(
            "Move Cursor to End of Line",
            UIAction(lambda ctxt: self.moveToEndOfLine(False)))
        self.actionHandler().bindAction(
            "Move Cursor to Start of View",
            UIAction(lambda ctxt: self.moveToStartOfView(False)))
        self.actionHandler().bindAction(
            "Move Cursor to End of View",
            UIAction(lambda ctxt: self.moveToEndOfView(False)))
        self.actionHandler().bindAction(
            "Extend Selection to Start of Line",
            UIAction(lambda ctxt: self.moveToStartOfLine(True)))
        self.actionHandler().bindAction(
            "Extend Selection to End of Line",
            UIAction(lambda ctxt: self.moveToEndOfLine(True)))
        self.actionHandler().bindAction(
            "Extend Selection to Start of View",
            UIAction(lambda ctxt: self.moveToStartOfView(True)))
        self.actionHandler().bindAction(
            "Extend Selection to End of View",
            UIAction(lambda ctxt: self.moveToEndOfView(True)))

    def getData(self):
        return self.data

    def getStart(self):
        return self.data.start

    def getEnd(self):
        return self.data.end

    def getLength(self):
        return self.getEnd() - self.getStart()

    def getCurrentOffset(self):
        return self.cursorAddr

    def getSelectionOffsets(self):
        start = self.selectionStartAddr
        end = self.cursorAddr
        if end < start:
            t = start
            start = end
            end = t
        return (start, end)

    def updateRanges(self):
        self.ranges = self.data.allocated_ranges
        # Remove regions not backed by the file
        for i in self.data.segments:
            if i.data_length < len(i):
                self.removeRange(i.start + i.data_length, i.end)
        self.allocatedLength = 0
        for i in self.ranges:
            self.allocatedLength += i.end - i.start

    def removeRange(self, begin, end):
        newRanges = []
        for i in self.ranges:
            if (end <= i.start) or (begin >= i.end):
                newRanges.append(i)
            elif (begin <= i.start) and (end >= i.end):
                continue
            elif (begin <= i.start) and (end < i.end):
                newRanges.append(AddressRange(end, i.end))
            elif (begin > i.start) and (end >= i.end):
                newRanges.append(AddressRange(i.start, begin))
            else:
                newRanges.append(AddressRange(i.start, begin))
                newRanges.append(AddressRange(end, i.end))
        self.ranges = newRanges

    def setTopToAddress(self, addr):
        for i in self.ranges:
            if (addr >= i.start) and (addr <= i.end):
                self.topAddr = addr - ((addr - i.start) % self.cols)
                if self.topAddr < i.start:
                    self.topAddr = i.start
                return
            if i.start > addr:
                self.topAddr = i.start
                return
        self.topAddr = self.data.end

    def navigate(self, addr):
        if addr < self.getStart():
            return False
        if addr > self.getEnd():
            return False
        self.cursorAddr = self.getStart()
        for i in self.ranges:
            if i.start > addr:
                break
            if addr > i.end:
                self.cursorAddr = i.end
            elif addr >= i.start:
                self.cursorAddr = addr
            else:
                self.cursorAddr = i.start
        self.setTopToAddress(self.cursorAddr)
        self.refreshLines()
        self.showContextAroundTop()
        self.selectNone()
        self.repositionCaret()
        return True

    def updateFonts(self):
        areaSize = self.viewport().size()
        self.adjustSize(areaSize.width(), areaSize.height())

    def getFont(self):
        userFont = binaryninjaui.getMonospaceFont(self)
        if sys.platform == "darwin":
            # Some fonts aren't fixed width across all characters, use a known good one
            font = QFont("Menlo", userFont.pointSize())
            font.setKerning(False)
        else:
            font = userFont
        return font

    def createRenderContext(self):
        render = RenderContext(self)
        render.setFont(self.getFont())
        return render

    def adjustSize(self, width, height):
        self.addrWidth = max(len("%x" % self.data.end), 8)
        render = self.createRenderContext()
        cols = ((width - 4) // render.getFontWidth()) - (self.addrWidth + 2)
        if cols < 1:
            cols = 1
        if cols != self.cols:
            self.cols = cols
            if self.topLine < len(self.lines):
                self.setTopToAddress(self.lines[self.topLine].address)
            else:
                self.setTopToAddress(self.cursorAddr)
            self.refreshLines()
        self.visibleRows = (height - 4) // render.getFontHeight()
        self.verticalScrollBar().setPageStep(self.visibleRows * self.cols //
                                             self.scrollBarMultiplier)
        self.refreshAtCurrentLocation()
        self.viewport().update()

    def getContiguousOffsetForAddress(self, addr):
        offset = 0
        for i in self.ranges:
            if (addr >= i.start) and (addr <= i.end):
                offset += addr - i.start
                break
            offset += i.end - i.start
        return offset

    def getAddressForContiguousOffset(self, offset):
        cur = 0
        for i in self.ranges:
            if offset < (cur + (i.end - i.start)):
                return i.start + (offset - cur)
            cur += i.end - i.start
        return self.data.end

    def refreshLines(self):
        addr = self.topAddr
        self.lines = []
        self.topLine = 0
        self.bottomAddr = self.topAddr
        self.updateRanges()
        if self.allocatedLength > 0x7fffffff:
            self.scrollBarMultiplier = (self.allocatedLength // 0x7fffffff) + 1
        else:
            self.scrollBarMultiplier = 1
        self.updatingScrollBar = True
        self.verticalScrollBar().setRange(0, (self.allocatedLength - 1) //
                                          self.scrollBarMultiplier)
        self.verticalScrollBar().setValue(
            self.getContiguousOffsetForAddress(addr) //
            self.scrollBarMultiplier)
        self.updatingScrollBar = False
        self.updateCache()
        self.viewport().update()
        UIContext.updateStatus()

    def refreshAtCurrentLocation(self):
        if self.topLine < len(self.lines):
            self.topAddr = self.lines[self.topLine].address
        self.refreshLines()

    def createLine(self, addr, length, separator):
        if separator:
            return ByteViewLine(addr, length, u'', True)
        else:
            data = self.data.read(addr, length)
            text = u''.join([self.byte_mapping[value] for value in data])
            return ByteViewLine(addr, length, text, False)

    def cachePreviousLines(self):
        prevEnd = None
        for i in self.ranges:
            if (self.topAddr > i.start) and (self.topAddr <= i.end):
                startLine = self.topAddr - (
                    (self.topAddr - i.start) % self.cols)
                if startLine == self.topAddr:
                    startLine -= self.cols
                if startLine < i.start:
                    startLine = i.start
                line = self.createLine(startLine, self.topAddr - startLine,
                                       False)
                self.lines.insert(0, line)
                self.topLine += 1
                self.topAddr = startLine
                return True
            elif i.start >= self.topAddr:
                if prevEnd is None:
                    return False
                line = self.createLine(prevEnd, i.start - prevEnd, True)
                self.lines.insert(0, line)
                self.topLine += 1
                self.topAddr = prevEnd
                return True
            prevEnd = i.end
        if prevEnd is None:
            return False
        line = self.createLine(prevEnd, self.topAddr - prevEnd, True)
        self.lines.insert(0, line)
        self.topLine += 1
        self.topAddr = prevEnd
        return True

    def cacheNextLines(self):
        lastAddr = self.data.start
        for i in self.ranges:
            if (self.bottomAddr >= i.start) and (self.bottomAddr < i.end):
                endLine = self.bottomAddr + self.cols
                if endLine > i.end:
                    endLine = i.end
                line = self.createLine(self.bottomAddr,
                                       endLine - self.bottomAddr, False)
                self.lines.append(line)
                self.bottomAddr = endLine
                return True
            elif i.start > self.bottomAddr:
                line = self.createLine(self.bottomAddr,
                                       i.start - self.bottomAddr, True)
                self.lines.append(line)
                self.bottomAddr = i.start
                return True
            lastAddr = i.end
        if self.bottomAddr == lastAddr:
            # Ensure there is a place for the cursor at the end of the file
            if (len(self.lines) > 0) and (self.lines[-1].length != self.cols):
                return False
            line = self.createLine(lastAddr, 0, False)
            self.lines.append(line)
            self.bottomAddr += 1
            return True
        return False

    def updateCache(self):
        # Cache enough for the current page and the next page
        while (len(self.lines) - self.topLine) <= (self.visibleRows * 2):
            if not self.cacheNextLines():
                break
        # Cache enough for the previous page
        while self.topLine <= self.visibleRows:
            if not self.cachePreviousLines():
                break
        # Trim cache
        if self.topLine > (self.visibleRows * 4):
            self.lines = self.lines[self.topLine - (self.visibleRows * 4):]
            self.topLine = self.visibleRows * 4
            self.topAddr = self.lines[0].address
        if (len(self.lines) - self.topLine) > (self.visibleRows * 5):
            self.bottomAddr = self.lines[self.topLine +
                                         (self.visibleRows * 5)].address
            self.lines = self.lines[0:self.topLine + (self.visibleRows * 5)]

    def scrollLines(self, count):
        newOffset = self.topLine + count
        if newOffset < 0:
            self.topLine = 0
        elif newOffset >= len(self.lines):
            self.topLine = len(self.lines) - 1
        else:
            self.topLine = newOffset
        self.updateCache()
        self.viewport().update()
        if self.topLine < len(self.lines):
            self.updatingScrollBar = True
            addr = self.lines[self.topLine].address
            self.verticalScrollBar().setValue(
                self.getContiguousOffsetForAddress(addr) //
                self.scrollBarMultiplier)
            self.updatingScrollBar = False

    def showContextAroundTop(self):
        scroll = self.visibleRows // 4
        if scroll > self.topLine:
            self.topLine = 0
        else:
            self.topLine -= scroll
        if self.topLine < len(self.lines):
            self.updatingScrollBar = True
            addr = self.lines[self.topLine].address
            self.verticalScrollBar().setValue(
                self.getContiguousOffsetForAddress(addr) //
                self.scrollBarMultiplier)
            self.updatingScrollBar = False
        self.updateCache()

    def repositionCaret(self):
        self.updateCache()
        found = False
        for i in range(0, len(self.lines)):
            if (((self.cursorAddr >= self.lines[i].address) and
                 (self.cursorAddr <
                  (self.lines[i].address + self.lines[i].length)))
                    or (((i + 1) == len(self.lines)) and
                        (self.cursorAddr
                         == (self.lines[i].address + self.lines[i].length)))):
                if i < self.topLine:
                    self.topLine = i
                elif i > (self.topLine + self.visibleRows - 1):
                    self.topLine = i - (self.visibleRows - 1)
                self.updatingScrollBar = True
                addr = self.lines[self.topLine].address
                self.verticalScrollBar().setValue(
                    self.getContiguousOffsetForAddress(addr) //
                    self.scrollBarMultiplier)
                self.updatingScrollBar = False
                self.updateCache()
                self.viewport().update()
                found = True
                break
        if not found:
            self.setTopToAddress(self.cursorAddr)
            self.refreshLines()
            self.showContextAroundTop()
        # Force caret to be visible and repaint
        self.caretBlink = True
        self.cursorTimer.stop()
        self.cursorTimer.start()
        self.updateCaret()
        UIContext.updateStatus()

    def updateCaret(self):
        # Rerender both the old caret position and the new caret position
        render = self.createRenderContext()
        for i in range(self.topLine,
                       min(len(self.lines), self.topLine + self.visibleRows)):
            if (((self.prevCursorAddr >= self.lines[i].address) and
                 (self.prevCursorAddr <=
                  (self.lines[i].address + self.lines[i].length)))
                    or ((self.cursorAddr >= self.lines[i].address) and
                        (self.cursorAddr <=
                         (self.lines[i].address + self.lines[i].length)))):
                self.viewport().update(0, (i - self.topLine) *
                                       render.getFontHeight(),
                                       self.viewport().size().width(),
                                       render.getFontHeight() + 3)

    def resizeEvent(self, event):
        self.adjustSize(event.size().width(), event.size().height())

    def paintEvent(self, event):
        p = QPainter(self.viewport())
        render = self.createRenderContext()
        render.init(p)
        charWidth = render.getFontWidth()
        charHeight = render.getFontHeight()

        # Compute range that needs to be updated
        topY = event.rect().y()
        botY = topY + event.rect().height()
        topY = (topY - 2) // charHeight
        botY = ((botY - 2) // charHeight) + 1

        # Compute selection range
        selection = False
        selStart, selEnd = self.getSelectionOffsets()
        if selStart != selEnd:
            selection = True

        # Draw selection
        if selection:
            startY = None
            endY = None
            startX = None
            endX = None
            for i in range(0, len(self.lines)):
                if selStart >= self.lines[i].address:
                    startY = i - self.topLine
                    startX = selStart - self.lines[i].address
                    if startX > self.cols:
                        startX = self.cols
                if selEnd >= self.lines[i].address:
                    endY = i - self.topLine
                    endX = selEnd - self.lines[i].address
                    if endX > self.cols:
                        endX = self.cols

            if startY is not None and endY is not None:
                p.setPen(binaryninjaui.getThemeColor(
                    ThemeColor.SelectionColor))
                p.setBrush(
                    binaryninjaui.getThemeColor(ThemeColor.SelectionColor))
                if startY == endY:
                    p.drawRect(2 + (self.addrWidth + 2 + startX) * charWidth,
                               2 + startY * charHeight,
                               (endX - startX) * charWidth, charHeight + 1)
                else:
                    p.drawRect(2 + (self.addrWidth + 2 + startX) * charWidth,
                               2 + startY * charHeight,
                               (self.cols - startX) * charWidth,
                               charHeight + 1)
                    if endX > 0:
                        p.drawRect(2 + (self.addrWidth + 2) * charWidth,
                                   2 + endY * charHeight, endX * charWidth,
                                   charHeight + 1)
                if (endY - startY) > 1:
                    p.drawRect(2 + (self.addrWidth + 2) * charWidth,
                               2 + (startY + 1) * charHeight,
                               self.cols * charWidth,
                               ((endY - startY) - 1) * charHeight + 1)

        # Paint each line
        color = self.palette().color(QPalette.WindowText)
        for y in range(topY, botY):
            if (y + self.topLine) < 0:
                continue
            if (y + self.topLine) >= len(self.lines):
                break
            if self.lines[y + self.topLine].separator:
                render.drawLinearDisassemblyLineBackground(
                    p,
                    LinearDisassemblyLineType.NonContiguousSeparatorLineType,
                    QRect(0, 2 + y * charHeight,
                          event.rect().width(), charHeight), 0)
                continue

            lineStartAddr = self.lines[y + self.topLine].address
            addrStr = "%.8x" % lineStartAddr
            length = self.lines[y + self.topLine].length
            text = self.lines[y + self.topLine].text

            cursorCol = None
            if (((self.cursorAddr >= lineStartAddr) and
                 (self.cursorAddr < (lineStartAddr + length)))
                    or (((y + self.topLine + 1) >= len(self.lines)) and
                        (self.cursorAddr == (lineStartAddr + length)))):
                cursorCol = self.cursorAddr - lineStartAddr

            render.drawText(
                p, 2, 2 + y * charHeight,
                binaryninjaui.getThemeColor(ThemeColor.AddressColor), addrStr)
            render.drawText(p, 2 + (self.addrWidth + 2) * charWidth,
                            2 + y * charHeight, color, text)

            if self.caretVisible and self.caretBlink and not selection and cursorCol is not None:
                p.setPen(Qt.NoPen)
                p.setBrush(self.palette().color(QPalette.WindowText))
                p.drawRect(2 + (self.addrWidth + 2 + cursorCol) * charWidth,
                           2 + y * charHeight, charWidth, charHeight + 1)
                caretTextColor = self.palette().color(QPalette.Base)
                byteValue = self.data.read(lineStartAddr + cursorCol, 1)
                if len(byteValue) == 1:
                    byteStr = self.byte_mapping[byteValue[0]]
                    render.drawText(
                        p, 2 + (self.addrWidth + 2 + cursorCol) * charWidth,
                        2 + y * charHeight, caretTextColor, byteStr)

    def wheelEvent(self, event):
        if event.orientation() == Qt.Horizontal:
            return
        self.wheelDelta -= event.delta()
        if (self.wheelDelta <= -40) or (self.wheelDelta >= 40):
            lines = self.wheelDelta // 40
            self.wheelDelta -= lines * 40
            self.scrollLines(lines)

    def scrollBarMoved(self, value):
        if self.updatingScrollBar:
            return
        self.wheelDelta = 0
        addr = self.getAddressForContiguousOffset(value *
                                                  self.scrollBarMultiplier)
        self.setTopToAddress(addr)
        self.refreshLines()
        for i in range(1, len(self.lines)):
            if (self.lines[i].address + self.lines[i].length) > addr:
                self.topLine = i - 1
                break
        self.updateCache()

    def scrollBarAction(self, action):
        if action == QAbstractSlider.SliderSingleStepAdd:
            self.wheelDelta = 0
            self.scrollLines(1)
        elif action == QAbstractSlider.SliderSingleStepSub:
            self.wheelDelta = 0
            self.scrollLines(-1)
        elif action == QAbstractSlider.SliderPageStepAdd:
            self.wheelDelta = 0
            self.scrollLines(self.visibleRows)
        elif action == QAbstractSlider.SliderPageStepSub:
            self.wheelDelta = 0
            self.scrollLines(-self.visibleRows)
        elif action == QAbstractSlider.SliderToMinimum:
            self.wheelDelta = 0
            self.setTopToAddress(self.getStart())
            self.verticalScrollBar().setValue(
                self.getContiguousOffsetForAddress(self.topAddr) //
                self.scrollBarMultiplier)
            self.refreshLines()
        elif action == QAbstractSlider.SliderToMaximum:
            self.wheelDelta = 0
            self.setTopToAddress(self.getEnd())
            self.verticalScrollBar().setValue(
                self.getContiguousOffsetForAddress(self.topAddr) //
                self.scrollBarMultiplier)
            self.refreshLines()

    def cursorTimerEvent(self):
        self.caretBlink = not self.caretBlink
        self.updateCaret()

    def focusInEvent(self, event):
        self.caretVisible = True
        self.updateCaret()

    def focusOutEvent(self, event):
        self.caretVisible = False
        self.leftButtonDown = False
        self.updateCaret()

    def selectNone(self):
        for i in self.lines:
            if (self.cursorAddr >=
                    i.address) and (self.cursorAddr <
                                    (i.address + i.length)) and i.separator:
                self.cursorAddr = i.address + i.length
                break
        self.selectionStartAddr = self.cursorAddr
        if self.selectionVisible:
            self.viewport().update()
        self.repositionCaret()
        UIContext.updateStatus()

    def selectAll(self):
        self.selectionStartAddr = self.getStart()
        self.cursorAddr = self.getEnd()
        self.viewport().update()
        UIContext.updateStatus()

    def adjustAddressAfterBackwardMovement(self):
        lastAddr = self.getStart()
        for i in self.ranges:
            if (self.cursorAddr >= i.start) and (self.cursorAddr < i.end):
                break
            if i.start > self.cursorAddr:
                self.cursorAddr = lastAddr
                break
            lastAddr = i.end - 1

    def adjustAddressAfterForwardMovement(self):
        for i in self.ranges:
            if (self.cursorAddr >= i.start) and (self.cursorAddr < i.end):
                break
            if i.start > self.cursorAddr:
                self.cursorAddr = i.start
                break

    def left(self, count, selecting):
        if self.cursorAddr > (self.getStart() + count):
            self.cursorAddr -= count
        else:
            self.cursorAddr = self.getStart()
        self.adjustAddressAfterBackwardMovement()
        if not selecting:
            self.selectNone()
        self.repositionCaret()
        if self.selectionVisible or selecting:
            self.viewport().update()

    def right(self, count, selecting):
        if self.cursorAddr <= (self.getEnd() - count):
            self.cursorAddr += count
        else:
            self.cursorAddr = self.getEnd()
        self.adjustAddressAfterForwardMovement()
        if not selecting:
            self.selectNone()
        self.repositionCaret()
        if self.selectionVisible or selecting:
            self.viewport().update()

    def up(self, selecting):
        self.left(self.cols, selecting)

    def down(self, selecting):
        self.right(self.cols, selecting)

    def pageUp(self, selecting):
        for i in range(0, len(self.lines)):
            if (((self.cursorAddr >= self.lines[i].address) and
                 (self.cursorAddr <
                  (self.lines[i].address + self.lines[i].length)))
                    or (((i + 1) == len(self.lines)) and
                        (self.cursorAddr
                         == (self.lines[i].address + self.lines[i].length)))):
                if i < self.visibleRows:
                    self.cursorAddr = self.getStart()
                else:
                    lineOfs = self.cursorAddr - self.lines[i].address
                    self.cursorAddr = self.lines[
                        i - self.visibleRows].address + lineOfs
                    if self.cursorAddr < self.lines[i -
                                                    self.visibleRows].address:
                        self.cursorAddr = self.lines[i -
                                                     self.visibleRows].address
                    elif self.cursorAddr >= (
                            self.lines[i - self.visibleRows].address +
                            self.lines[i - self.visibleRows].length):
                        self.cursorAddr = self.lines[
                            i - self.visibleRows].address + self.lines[
                                i - self.visibleRows].length - 1
                    break
        self.adjustAddressAfterBackwardMovement()
        if self.topLine > self.visibleRows:
            self.topLine -= self.visibleRows
        else:
            self.topLine = 0
        if self.topLine < len(self.lines):
            self.updatingScrollBar = True
            addr = self.lines[self.topLine].address
            self.verticalScrollBar().setValue(
                self.getContiguousOffsetForAddress(addr) //
                self.scrollBarMultiplier)
            self.updatingScrollBar = False
        if not selecting:
            self.selectNone()
        self.repositionCaret()
        self.viewport().update()

    def pageDown(self, selecting):
        for i in range(0, len(self.lines)):
            if (((self.cursorAddr >= self.lines[i].address) and
                 (self.cursorAddr <
                  (self.lines[i].address + self.lines[i].length)))
                    or (((i + 1) == len(self.lines)) and
                        (self.cursorAddr
                         == (self.lines[i].address + self.lines[i].length)))):
                if i >= (len(self.lines) - self.visibleRows):
                    self.cursorAddr = self.getEnd()
                else:
                    lineOfs = self.cursorAddr - self.lines[i].address
                    self.cursorAddr = self.lines[
                        i + self.visibleRows].address + lineOfs
                    if self.cursorAddr < self.lines[i +
                                                    self.visibleRows].address:
                        self.cursorAddr = self.lines[i +
                                                     self.visibleRows].address
                    elif self.cursorAddr >= (
                            self.lines[i + self.visibleRows].address +
                            self.lines[i + self.visibleRows].length):
                        self.cursorAddr = self.lines[
                            i + self.visibleRows].address + self.lines[
                                i + self.visibleRows].length - 1
                    break
        self.adjustAddressAfterForwardMovement()
        if (self.topLine + self.visibleRows) < len(self.lines):
            self.topLine += self.visibleRows
        elif len(self.lines) > 0:
            self.topLine = len(self.lines) - 1
        if self.topLine < len(self.lines):
            self.updatingScrollBar = True
            addr = self.lines[self.topLine].address
            self.verticalScrollBar().setValue(
                self.getContiguousOffsetForAddress(addr) //
                self.scrollBarMultiplier)
            self.updatingScrollBar = False
        if not selecting:
            self.selectNone()
        self.repositionCaret()
        self.viewport().update()

    def moveToStartOfLine(self, selecting):
        for i in self.lines:
            if (self.cursorAddr >= i.address) and (self.cursorAddr <
                                                   (i.address + i.length)):
                self.cursorAddr = i.address
                break
        if not selecting:
            self.selectNone()
        self.repositionCaret()
        if self.selectionVisible or selecting:
            self.viewport().update()

    def moveToEndOfLine(self, selecting):
        for i in self.lines:
            if (self.cursorAddr >= i.address) and (self.cursorAddr <
                                                   (i.address + i.length)):
                self.cursorAddr = i.address + i.length - 1
                break
        if not selecting:
            self.selectNone()
        self.repositionCaret()
        if self.selectionVisible or selecting:
            self.viewport().update()

    def moveToStartOfView(self, selecting):
        self.cursorAddr = self.getStart()
        if not selecting:
            self.selectNone()
        self.repositionCaret()
        if self.selectionVisible or selecting:
            self.viewport().update()

    def moveToEndOfView(self, selecting):
        self.cursorAddr = self.getEnd()
        if not selecting:
            self.selectNone()
        self.repositionCaret()
        if self.selectionVisible or selecting:
            self.viewport().update()

    def addressFromLocation(self, x, y):
        if y < 0:
            y = 0
        if x < 0:
            x = 0
        if x > self.cols:
            x = self.cols
        if (y + self.topLine) >= len(self.lines):
            return self.getEnd()
        if self.lines[y + self.topLine].separator:
            return self.lines[y + self.topLine].address - 1
        result = self.lines[y + self.topLine].address + x
        if result >= (self.lines[y + self.topLine].address +
                      self.lines[y + self.topLine].length):
            if (y + self.topLine) == (len(self.lines) - 1):
                return self.getEnd()
            else:
                return self.lines[y + self.topLine].address + self.lines[
                    y + self.topLine].length - 1
        return result

    def mousePressEvent(self, event):
        if event.button() != Qt.LeftButton:
            return
        render = self.createRenderContext()
        x = (event.x() - 2) // render.getFontWidth() - (self.addrWidth + 2)
        y = (event.y() - 2) // render.getFontHeight()
        self.lastMouseX = x
        self.lastMouseY = y
        self.cursorAddr = self.addressFromLocation(x, y)
        if (event.modifiers() & Qt.ShiftModifier) == 0:
            self.selectNone()
        self.repositionCaret()
        if (event.modifiers() & Qt.ShiftModifier) != 0:
            self.viewport().update()
        self.leftButtonDown = True

    def mouseMoveEvent(self, event):
        if not self.leftButtonDown:
            return
        render = self.createRenderContext()
        x = (event.x() - 2) // render.getFontWidth() - (self.addrWidth + 2)
        y = (event.y() - 2) // render.getFontHeight()
        if (x == self.lastMouseX) and (y == self.lastMouseY):
            return
        self.lastMouseX = x
        self.lastMouseY = y
        self.cursorAddr = self.addressFromLocation(x, y)
        self.repositionCaret()
        self.viewport().update()

    def mouseReleaseEvent(self, event):
        if event.button() != Qt.LeftButton:
            return
        self.leftButtonDown = False
Beispiel #10
0
class PomoTimer(QWidget, BaseTimer):
    """
    PomoTimer: Pomodoro タイマーを提供する。

    1 pomodoro: 25min
    short break: 5min
    long break: 15min
    long break after 4pomodoro.
    """

    paused = Signal()

    def __init__(self):
        super().__init__()
        self.simple_timer = SimpleTimer(self)

        self.settings = {
            'pomo': 25,
            'short_break': 5,
            'long_break': 15,
            'long_break_after': 4
        }
        self.state_dict = {
            'work': 'Work!',
            'break': 'Break.',
            'pause': 'Pause',
            'wait': "Let's start."
        }
        self.work_timer = QTimer(self)
        self.work_timer.setSingleShot(True)
        self.break_timer = QTimer(self)
        self.break_timer.setSingleShot(True)

        self.pomo_count = 0
        self.pomo_count_lbl = QLabel(self)
        self.pomo_count_lbl.setText(f'{self.pomo_count} pomodoro finished.')
        self.state_lbl = QLabel(self)
        self.state_lbl.setText(self.state_dict['wait'])
        self.estimate_pomo = 0
        self.estimate_label = QLabel(self)
        self.estimate_label.setText('Estimate: ')
        self.estimate_pomo_widget = QSpinBox(self)
        self.estimate_pomo_widget.setValue(4)
        self.estimate_pomo_widget.setSuffix('  pomo')
        self.estimate_pomo_widget.setRange(1, 20)

        self.set_ui()
        self.set_connection()

    def set_ui(self):
        layout = self.simple_timer.layout()
        i = layout.indexOf(self.simple_timer.timer_edit)
        layout.takeAt(i)
        hlayout = QHBoxLayout()
        hlayout.addWidget(self.estimate_label)
        hlayout.addWidget(self.estimate_pomo_widget)

        vlayout = QVBoxLayout()
        vlayout.addLayout(hlayout)
        vlayout.addWidget(self.pomo_count_lbl)
        vlayout.addWidget(self.state_lbl)
        vlayout.addWidget(self.simple_timer)
        self.setLayout(vlayout)

    def set_connection(self):
        self.work_timer.timeout.connect(self.timeout)
        self.break_timer.timeout.connect(self.start_work)

    def start(self):
        self.estimate_pomo = self.estimate_pomo_widget.value()
        self.start_work()

    def start_work(self):
        """
        Start timer for working on the task.
        :return:
        """
        self.state_lbl.setText(self.state_dict['work'])
        self.simple_timer.timer = self.work_timer
        self.simple_timer.timer_edit.setValue(self.settings['pomo'])
        self.simple_timer.start()
        self.started.emit()

    def start_break(self):
        """
        Start timer for working on the rest.
        Short break is normal break, long break comes every some tasks(default 4).
        :return:
        """
        self.state_lbl.setText(self.state_dict['break'])
        self.simple_timer.timer = self.break_timer
        if self.pomo_count % self.settings['long_break_after']:
            self.simple_timer.timer_edit.setValue(self.settings['short_break'])
        else:
            self.simple_timer.timer_edit.setValue(self.settings['long_break'])
        self.simple_timer.start()
        self.started.emit()

    def abort(self):
        self.reset()
        self.aborted.emit()

    def pause(self):
        self.state_lbl.setText(self.state_dict['pause'])
        self.simple_timer.pause()
        self.paused.emit()

    def resume(self):
        self.state_lbl.setText(self.state_dict['work'])
        self.simple_timer.resume()
        self.started.emit()

    def timeout(self):
        self.pomo_count += 1
        if self.pomo_count >= self.estimate_pomo:
            self.reset()
            self.finished.emit()
        else:
            self.start_break()

    def reset(self):
        self.pomo_count = 0
        self.simple_timer.reset()
        self.work_timer.stop()
        self.break_timer.stop()
        self.state_lbl.setText(self.state_dict['wait'])

    def get_notify_message(self):
        return ''

    @property
    def name(self):
        return 'Pomo Timer'

    def fake_start(self):
        self.simple_timer.setting_time = self.simple_timer.timer_edit.value()
        self.simple_timer.timer.start(self.simple_timer.setting_time * 1000)
        self.simple_timer.set_remain_update()
        self.simple_timer.started.emit()
Beispiel #11
0
class EvelynDesktop(QStackedWidget):
    INTERVAL_SECS = 30
    ALERT_SECS = 5

    signal_get_ping = Signal()
    signal_post_history = Signal(int, QDateTime)

    def __init__(
            self,
            config_file: str
    ) -> None:
        super().__init__()
        # load config
        try:
            self.config = Config(config_file)
        except Exception as e:
            QMessageBox.critical(self, 'Config error', str(e))
            QTimer.singleShot(0, self.close)
            return
        # load settings
        self.settings = Settings()
        # state
        self.state_key: Optional[int] = None
        # label widget
        self.label_ping = ClickableLabel('Loading ...', self.post_history)
        self.label_ping.setTextFormat(Qt.RichText)
        self.label_ping.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
        layout_ping = QGridLayout()
        layout_ping.setContentsMargins(0, 0, 0, 0)
        layout_ping.addWidget(self.label_ping)
        self.widget_ping = QWidget()
        self.widget_ping.setLayout(layout_ping)
        self.addWidget(self.widget_ping)
        # alert widget
        self.label_alert = QLabel()
        self.label_alert.setWordWrap(True)
        self.label_alert.setAlignment(Qt.AlignCenter)
        self.label_alert.setStyleSheet(f'background: #dddddd;')
        self.addWidget(self.label_alert)
        # context menu
        self.action_report_done = QAction('Report done ...')
        self.action_report_done.triggered.connect(self.report_done)
        self.action_exit = QAction('Exit')
        self.action_exit.triggered.connect(self.close)
        self.action_frameless = QAction('Frameless window')
        self.action_frameless.setCheckable(True)
        self.action_frameless.triggered.connect(self.set_frameless_window)
        self.action_homepage = QAction('Open homepage')
        self.action_homepage.triggered.connect(self.open_homepage)
        self.context_menu = QMenu()
        self.context_menu.addAction(self.action_report_done)
        self.context_menu.addAction(self.action_exit)
        self.context_menu.addAction(self.action_frameless)
        self.context_menu.addAction(self.action_homepage)
        # threads
        self.thread_communication = QThread()
        self.thread_communication.start()
        # workers
        self.worker_communication = CommunicationWorker(
            netloc=self.config.netloc,
            base_path=self.config.base_path,
            api_key=self.config.api_key,
            guild=self.config.guild,
            member=self.config.member)
        self.worker_communication.moveToThread(self.thread_communication)
        # signals
        self.worker_communication.signal_get_ping_done.connect(self.get_ping_done)
        self.worker_communication.signal_post_history_done.connect(self.post_history_done)
        self.signal_get_ping.connect(self.worker_communication.get_ping)
        self.signal_post_history.connect(self.worker_communication.post_history)
        # get ping timer
        QTimer.singleShot(0, self.get_ping)
        self.timer_ping = QTimer()
        self.timer_ping.timeout.connect(self.get_ping)
        self.timer_ping.setTimerType(Qt.VeryCoarseTimer)
        self.timer_ping.start(self.INTERVAL_SECS * 1000)
        # switch label timer
        self.timer_label = QTimer()
        self.timer_label.timeout.connect(lambda: self.setCurrentWidget(self.widget_ping))
        self.timer_label.setSingleShot(True)
        self.timer_label.setTimerType(Qt.CoarseTimer)
        # window attributes
        size = self.settings.get('window', 'size', type_=QSize)
        if size is not None:
            self.resize(size)
        pos = self.settings.get('window', 'pos', type_=QPoint)
        if pos is not None:
            self.move(pos)
        frameless = self.settings.get('window', 'frameless', type_=bool)
        if frameless is not None and frameless:
            QTimer.singleShot(100, self.action_frameless.trigger)
        self.setWindowFlag(Qt.WindowStaysOnTopHint, self.config.window_stays_on_top)
        self.setAttribute(Qt.WA_TranslucentBackground)
        self.setWindowTitle('Evelyn Reminder')

    def closeEvent(
            self,
            event: QCloseEvent
    ) -> None:
        # save settings
        with suppress_and_log_exception():
            self.settings.set('window', 'size', self.size())
            self.settings.set('window', 'pos', self.pos())
            self.settings.set('window', 'frameless', bool(self.windowFlags() & Qt.FramelessWindowHint))
        # stop communication thread
        with suppress_and_log_exception():
            self.thread_communication.quit()
            self.thread_communication.wait()
        # done
        super().closeEvent(event)

    def contextMenuEvent(
            self,
            event: QContextMenuEvent
    ) -> None:
        self.context_menu.exec_(event.globalPos())

    @Slot()
    def get_ping(self) -> None:
        logging.info('Get ping ...')
        self.signal_get_ping.emit()

    @Slot(int, str, str)
    def get_ping_done(
            self,
            key: int,
            text: str,
            color: str
    ) -> None:
        logging.info('Get ping done')
        if key == -1:
            self.state_key = None
            self.label_ping.setWordWrap(True)
        else:
            self.state_key = key
            self.label_ping.setWordWrap(False)
        self.label_ping.setText(text)
        self.widget_ping.setStyleSheet(f'background : {color}; ')

    @Slot()
    def post_history(
            self,
            date_time: QDateTime = QDateTime()
    ) -> None:
        # this method is called as Slot by ClickableLabel.mouseReleaseEvent() without arguments
        # this method is called directly by EvelynDesktop.report_done() with a date_time
        if self.state_key is None:
            return
        logging.info('Post history ...')
        self.label_alert.setText('Sending ...')
        self.label_alert.setStyleSheet(f'background: #dddddd;')
        self.setCurrentWidget(self.label_alert)
        self.signal_post_history.emit(self.state_key, date_time)

    @Slot(str, bool)
    def post_history_done(
            self,
            text: str,
            error: bool
    ) -> None:
        logging.info('Post history done')
        self.label_alert.setText(text)
        if error:
            self.label_alert.setStyleSheet(f'background: #dd4b4b;')
        self.timer_label.start(self.ALERT_SECS * 1000)
        # trigger instant ping update to avoid outdated info
        self.timer_ping.stop()
        self.timer_ping.start(self.INTERVAL_SECS * 1000)
        self.get_ping()

    @Slot()
    def report_done(self) -> None:
        self.timer_ping.stop()  # stop ping update while dialog is open
        report_done_dialog = ReportDoneDialog(self)
        response = report_done_dialog.exec()
        if response != QDialog.Accepted:
            self.timer_ping.start(self.INTERVAL_SECS * 1000)
            self.get_ping()
            return
        date_time = report_done_dialog.get_date_time()
        self.post_history(date_time)

    @Slot(bool)
    def set_frameless_window(
            self,
            value: bool
    ) -> None:
        pos = self.pos()
        self.setWindowFlag(Qt.FramelessWindowHint, value)
        # workaround: window goes invisible otherwise
        self.setVisible(True)
        # workaround: window would move up otherwise
        if value:
            QTimer.singleShot(100, lambda: self.move(pos))

    @Slot()
    def open_homepage(self) -> None:
        webbrowser.open('https://github.com/stefs/evelyn-reminder')
Beispiel #12
0
class SimpleTimer(QWidget, BaseTimer):

    paused = Signal()

    def __init__(self, parent=None):

        super().__init__(parent)
        self.timer = QTimer(self)
        self.timer.setSingleShot(True)
        self.update_timer = QTimer(self)
        self.setting_time = 0
        self.remaining = 0

        self.remain_label = QLabel('00:00', self)
        self.remain_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        self.remain_label.setScaledContents(True)
        font = QFont()
        font.setPointSize(86)
        self.remain_label.setFont(font)
        self.timer_edit = QSpinBox(self)
        self.timer_edit.setRange(1, 99)
        self.timer_edit.setValue(25)

        self.set_ui()
        self.set_connection()

        self.show()

    def set_ui(self):
        vlayout = QVBoxLayout()
        vlayout.addWidget(self.remain_label, alignment=Qt.AlignHCenter)
        vlayout.addWidget(self.timer_edit)
        self.setLayout(vlayout)

    def set_connection(self):
        self.timer.timeout.connect(self.stop)
        self.update_timer.timeout.connect(self.remain_update)

    def start(self):
        self.setting_time = self.timer_edit.value()
        self.timer.start(self.setting_time * 60 * 1000)
        self.set_remain_update()
        self.started.emit()

    def set_remain_update(self):
        self.remain_update()
        self.update_timer.start(250)

    def stop(self):
        self.reset()
        self.finished.emit()

    def abort(self):
        self.reset()
        self.timer.stop()
        self.aborted.emit()

    def pause(self):
        self.update_timer.stop()
        self.remaining = self.timer.remainingTime()
        self.timer.stop()
        self.paused.emit()

    def resume(self):
        self.timer.start(self.remaining)
        self.set_remain_update()
        self.started.emit()

    def reset(self):
        self.timer.stop()
        self.update_timer.stop()
        self.remain_label.setText('00:00')
        self.remaining = 0

    def get_notify_message(self):
        remaining = self.timer.remainingTime() / 1000
        return '{0} minutes have passed.'.format(self.setting_time - int(remaining/60))

    def remain_update(self):
        remaining = self.timer.remainingTime() / 1000
        self.remain_label.setText('{min:02}:{sec:02}'.format(
            min=int(remaining / 60), sec=int(remaining % 60)))

    @property
    def name(self):
        return 'Simple Timer'
Beispiel #13
0
class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        self.art_scene = QGraphicsScene(self)
        self.art_scene.addText('Open an image file.')
        self.ui.art_view.setScene(self.art_scene)
        self.symbols_scene = QGraphicsScene(self)
        self.ui.symbols_view.setScene(self.symbols_scene)
        self.ui.action_exit.triggered.connect(self.close)
        self.ui.action_open_art.triggered.connect(self.open_image)
        self.ui.action_open_words.triggered.connect(self.open_words)
        self.ui.action_save.triggered.connect(self.save_pdf)
        self.ui.action_save_png.triggered.connect(self.save_png)
        self.ui.action_shuffle.triggered.connect(self.shuffle)
        self.ui.action_sort.triggered.connect(self.sort)
        self.ui.rows.valueChanged.connect(self.on_options_changed)
        self.ui.columns.valueChanged.connect(self.on_options_changed)
        self.ui.word_clues_radio.toggled.connect(self.on_options_changed)
        self.ui.symbol_clues_radio.toggled.connect(self.on_options_changed)

        self.word_layout = QGridLayout(self.ui.word_content)
        self.ui.word_scroll.setWidgetResizable(True)
        self.word_labels: typing.Dict[str, QLabel] = {}
        self.word_shuffler = WordShuffler([])

        self.clues = None

        self.pixmap = self.scaled_pixmap = self.mini_pixmap = None
        self.sliced_pixmap_item: typing.Optional[QGraphicsPixmapItem] = None
        self.sliced_image: typing.Optional[QImage] = None
        self.selection_grid: typing.Optional[SelectionGrid] = None
        self.cells = []
        self.art_shuffler: typing.Optional[ArtShuffler] = None
        self.symbols_source_pixmap_item: typing.Optional[
            QGraphicsPixmapItem] = None
        self.symbols_pixmap_item: typing.Optional[QGraphicsPixmapItem] = None
        self.symbols_image: typing.Optional[QImage] = None
        self.symbols_shuffler: typing.Optional[ArtShuffler] = None
        self.selected_row: typing.Optional[int] = None
        self.selected_column: typing.Optional[int] = None
        self.settings = QSettings()
        self.image_path: typing.Optional[str] = self.settings.value(
            'image_path')
        self.words_path: typing.Optional[str] = self.settings.value(
            'words_path')

        self.dirty_letters = set()
        self.timer = QTimer()
        self.timer.setInterval(500)
        self.timer.setSingleShot(True)
        # noinspection PyUnresolvedReferences
        self.timer.timeout.connect(self.on_dirty)

        self.row_count = self.column_count = 0
        self.clue_type = ClueType.words
        self.ui.rows.setValue(self.settings.value('row_count', 6, int))
        self.ui.columns.setValue(self.settings.value('column_count', 4, int))
        clue_type_name = self.settings.value('clue_type', ClueType.words.name)
        try:
            clue_type = ClueType[clue_type_name]
        except KeyError:
            clue_type = ClueType.words
        if clue_type == ClueType.words:
            self.ui.word_clues_radio.setChecked(True)
        else:
            self.ui.symbol_clues_radio.setChecked(True)
        self.row_clues: typing.List[QPixmap] = []
        self.column_clues: typing.List[QPixmap] = []
        self.on_options_changed()

    def on_dirty(self):
        for letter in self.dirty_letters:
            self.word_labels[letter].setText(
                self.word_shuffler.make_display(letter))
            self.settings.setValue(f'word_{letter}',
                                   self.word_shuffler[letter])
        if self.dirty_letters:
            self.clues = self.word_shuffler.make_clues()
            self.art_shuffler.clues = dict(self.clues)
            self.dirty_letters.clear()
            self.on_selection_moved()

        if self.pixmap is not None:
            x, y, width, height = self.get_selected_fraction()
            self.settings.setValue('x', x)
            self.settings.setValue('y', y)
            self.settings.setValue('width', width)
            self.settings.setValue('height', height)

        new_rows = self.ui.rows.value()
        new_columns = self.ui.columns.value()
        if self.ui.word_clues_radio.isChecked():
            new_clue_type = ClueType.words
        else:
            new_clue_type = ClueType.symbols
        if (new_rows, new_columns,
                new_clue_type) == (self.row_count, self.column_count,
                                   self.clue_type):
            return
        self.settings.setValue('row_count', new_rows)
        self.settings.setValue('column_count', new_columns)
        self.settings.setValue('clue_type', new_clue_type.name)
        self.row_count, self.column_count = new_rows, new_columns
        self.clue_type = new_clue_type

        word_count = (self.row_count * self.column_count)
        while self.word_layout.count():
            layout_item = self.word_layout.takeAt(0)
            layout_item.widget().deleteLater()
        self.word_labels.clear()

        self.row_clues.clear()
        self.column_clues.clear()
        if self.image_path is not None:
            self.load_image(self.image_path)

        if self.words_path is not None:
            self.load_words(self.words_path)

        letters = [chr(65 + i) for i in range(word_count)]
        if self.word_shuffler.needs_blank:
            letters.insert(0, '')
        word_fields = {}
        for i, letter in enumerate(letters):
            word_field = QLineEdit()
            self.word_layout.addWidget(word_field, i, 0)
            # noinspection PyUnresolvedReferences
            word_field.textEdited.connect(partial(self.on_word_edited, letter))

            word_label = QLabel()
            self.word_layout.addWidget(word_label, i, 1)
            self.word_labels[letter] = word_label
            word_fields[letter] = word_field
        for i, letter in enumerate(letters):
            word = self.settings.value(f'word_{letter}', '')
            self.word_shuffler[letter] = word
            self.dirty_letters.add(letter)
            word_fields[letter].setText(word)

    def on_options_changed(self, *_):
        self.timer.start()

    def shuffle(self):
        self.clues = self.word_shuffler.make_clues()
        if self.art_shuffler is not None:
            self.art_shuffler.shuffle()
            self.on_selection_moved()

    def sort(self):
        if self.art_shuffler is not None:
            self.art_shuffler.sort()
            self.on_selection_moved()

    def open_words(self):
        word_filter = 'Text files (*.txt)'
        if self.words_path is None:
            words_folder = None
        else:
            words_folder = str(Path(self.words_path).parent)
        file_name, _ = QFileDialog.getOpenFileName(self,
                                                   "Open a words file.",
                                                   dir=words_folder,
                                                   filter=word_filter)
        if not file_name:
            return
        self.settings.setValue('words_path', file_name)
        self.load_words(file_name)

    def load_words(self, words_path):
        with open(words_path) as f:
            choice = 0
            if choice == 0:
                self.word_shuffler = WordShuffler(f)
            else:
                self.word_shuffler = WordStripper(f)

    def open_image(self):
        formats = QImageReader.supportedImageFormats()
        patterns = (f'*.{fmt.data().decode()}' for fmt in formats)
        image_filter = f'Images ({" ".join(patterns)})'
        if self.image_path is None:
            image_folder = None
        else:
            image_folder = str(Path(self.image_path).parent)
        file_name, _ = QFileDialog.getOpenFileName(self,
                                                   "Open an image file.",
                                                   dir=image_folder,
                                                   filter=image_filter)
        if not file_name:
            return
        self.settings.setValue('image_path', file_name)
        self.load_image(file_name)

    def load_image(self, image_path):
        self.pixmap = QPixmap(image_path)
        if self.pixmap.isNull():
            self.pixmap = None
        self.image_path = image_path
        self.scale_image()

    def scale_image(self):
        if self.pixmap is None:
            return

        if self.selection_grid is None:
            x = self.settings.value('x', 0.0, float)
            y = self.settings.value('y', 0.0, float)
            width = self.settings.value('width', 1.0, float)
            height = self.settings.value('height', 1.0, float)
        else:
            x, y, width, height = self.get_selected_fraction()
        self.art_scene.clear()
        self.cells.clear()
        view_size = self.ui.art_view.maximumViewportSize()
        if view_size.width() == 0:
            return
        self.art_scene.setSceneRect(0, 0, view_size.width(),
                                    view_size.height())
        display_size = QSize(view_size.width() * 0.99 / 2,
                             view_size.height() * 0.99)
        self.scaled_pixmap = self.pixmap.scaled(
            display_size, aspectMode=Qt.AspectRatioMode.KeepAspectRatio)
        self.art_scene.addPixmap(self.scaled_pixmap)
        scaled_size = self.scaled_pixmap.size()
        self.selection_grid = SelectionGrid(scaled_size.width() * x,
                                            scaled_size.height() * y,
                                            scaled_size.width() * width,
                                            scaled_size.height() * height,
                                            row_count=self.row_count,
                                            column_count=self.column_count)
        self.selection_grid.on_moved = self.on_selection_moved
        self.art_scene.addItem(self.selection_grid)
        self.sliced_image = QImage(display_size,
                                   QImage.Format.Format_ARGB32_Premultiplied)
        self.check_clues()
        self.art_shuffler = ArtShuffler(self.selection_grid.row_count,
                                        self.selection_grid.column_count,
                                        self.sliced_image,
                                        QRect(0, 0, display_size.width(),
                                              display_size.height()),
                                        clues=self.clues,
                                        row_clues=self.row_clues,
                                        column_clues=self.column_clues)
        self.sliced_pixmap_item = self.art_scene.addPixmap(
            QPixmap.fromImage(self.sliced_image))
        self.sliced_pixmap_item.setPos(display_size.width(), 0)

        self.symbols_scene.clear()
        self.symbols_source_pixmap_item = self.symbols_scene.addPixmap(
            self.scaled_pixmap)
        self.symbols_image = QImage(display_size,
                                    QImage.Format.Format_ARGB32_Premultiplied)
        if self.symbols_shuffler is not None:
            selected_row = self.symbols_shuffler.selected_row
            selected_column = self.symbols_shuffler.selected_column
        else:
            selected_row = 0
            selected_column = None
        self.symbols_shuffler = ArtShuffler(self.selection_grid.row_count,
                                            self.selection_grid.column_count,
                                            self.symbols_image,
                                            QRect(0, 0, display_size.width(),
                                                  display_size.height()),
                                            row_clues=self.row_clues,
                                            column_clues=self.column_clues)
        self.symbols_shuffler.selected_row = selected_row
        self.symbols_shuffler.selected_column = selected_column
        self.symbols_pixmap_item = ClickablePixmapItem(
            QPixmap.fromImage(self.symbols_image))
        self.symbols_pixmap_item.on_click = self.on_symbols_clicked
        self.symbols_scene.addItem(self.symbols_pixmap_item)

        self.symbols_pixmap_item.setPos(display_size.width(), 0)

        self.on_selection_moved()

    def on_symbols_clicked(self, event: QGraphicsSceneMouseEvent):
        self.symbols_scene.clearSelection()
        self.symbols_shuffler.select_clue(event.pos().toPoint())
        self.on_selection_moved()

    def on_word_edited(self, letter, word):
        self.word_shuffler[letter] = word
        self.dirty_letters.add(letter)
        self.timer.start()

    def get_selected_fraction(self):
        selection_rect = self.selection_grid.rect()
        selection_pos = self.selection_grid.pos()
        size = self.scaled_pixmap.size()
        x = (selection_pos.x() + selection_rect.x()) / size.width()
        width = selection_rect.width() / size.width()
        y = (selection_pos.y() + selection_rect.y()) / size.height()
        height = selection_rect.height() / size.height()
        return x, y, width, height

    def on_selection_moved(self):
        selected_pixmap = self.get_selected_pixmap()
        self.art_shuffler.draw(selected_pixmap)
        self.sliced_pixmap_item.setPixmap(QPixmap.fromImage(self.sliced_image))

        selected_pixmap = self.get_selected_pixmap()
        cell_width = (selected_pixmap.width() /
                      self.selection_grid.column_count)
        cell_height = (selected_pixmap.height() /
                       self.selection_grid.row_count)
        self.row_clues.clear()
        self.column_clues.clear()
        for i in range(self.selection_grid.row_count):
            clue_image = selected_pixmap.copy(0, i * cell_height, cell_width,
                                              cell_height)
            self.row_clues.append(clue_image)
        for j in range(self.selection_grid.column_count):
            clue_image = selected_pixmap.copy(j * cell_width, 0, cell_width,
                                              cell_height)
            self.column_clues.append(clue_image)
        self.symbols_shuffler.row_clues = self.row_clues
        self.symbols_shuffler.column_clues = self.column_clues

        self.symbols_shuffler.draw_grid(selected_pixmap)
        self.symbols_pixmap_item.setPixmap(
            QPixmap.fromImage(self.symbols_image))
        self.timer.start()

    def get_selected_pixmap(self) -> QPixmap:
        x, y, width, height = self.get_selected_fraction()
        original_size = self.pixmap.size()
        selected_pixmap = self.pixmap.copy(x * original_size.width(),
                                           y * original_size.height(),
                                           width * original_size.width(),
                                           height * original_size.height())
        return selected_pixmap

    def resizeEvent(self, event: QResizeEvent):
        super().resizeEvent(event)
        self.scale_image()

    def save_pdf(self):
        pdf_folder = self.settings.value('pdf_folder')
        file_name, _ = QFileDialog.getSaveFileName(self,
                                                   "Save a PDF file.",
                                                   dir=pdf_folder,
                                                   filter='Documents (*.pdf)')
        if not file_name:
            return
        self.settings.setValue('pdf_folder', os.path.dirname(file_name))
        writer = QPdfWriter(file_name)
        writer.setPageSize(QPageSize(QPageSize.Letter))
        writer.setTitle('Sliced Art Puzzle')
        writer.setCreator('Don Kirkby')
        self.paint_puzzle(writer)

    def save_png(self):
        pdf_folder = self.settings.value('pdf_folder')
        file_name, _ = QFileDialog.getSaveFileName(self,
                                                   "Save an image file.",
                                                   dir=pdf_folder,
                                                   filter='Images (*.png)')
        if not file_name:
            return
        writer = QPixmap(1000, 2000)
        self.paint_puzzle(writer)
        writer.save(file_name)
        self.settings.setValue('pdf_folder', os.path.dirname(file_name))

    def paint_puzzle(self, writer: QPaintDevice):
        self.check_clues()
        painter = QPainter(writer)
        try:
            print_shuffler = ArtShuffler(self.art_shuffler.rows,
                                         self.art_shuffler.cols,
                                         writer,
                                         QRect(0, 0, writer.width(),
                                               round(writer.height() / 2)),
                                         clues=self.clues,
                                         row_clues=self.row_clues,
                                         column_clues=self.column_clues)
            print_shuffler.cells = self.art_shuffler.cells[:]
            print_shuffler.is_shuffled = self.art_shuffler.is_shuffled
            selected_pixmap = self.get_selected_pixmap()
            print_shuffler.draw(selected_pixmap, painter)

            print_shuffler.rect.moveTop(writer.height() / 2)
            print_shuffler.draw_grid(selected_pixmap, painter)
        finally:
            painter.end()

    def check_clues(self):
        if self.clue_type == ClueType.words:
            if self.clues is None:
                self.clues = self.word_shuffler.make_clues()
            self.row_clues.clear()
            self.column_clues.clear()
        else:
            self.clues = None
Beispiel #14
0
class NotepadDockWidget(QWidget, DockContextHandler):
    """
    NotepadDockWidget is the - you guessed it - notepad dock widget. It is
    responsible for saving and restoring the user's notes, and providing the
    user an interface to edit their notes from.
    """

    # Tab container
    tab_container: QTabWidget

    # The actual editor widget.
    editor: JMarkdownEditor

    # Viewer/content widget
    viewer: JMarkdownViewer

    # Timer for auto-saving.
    save_timer: QTimer

    # The currently focused BinaryView.
    bv: Optional[BinaryView] = None

    def __init__(self, parent: QWidget, name: str, bv: Optional[BinaryView]):
        """
        Initialize a new NotepadDockWidget.

        :param parent: the QWidget to parent this NotepadDockWidget to
        :param name: the name to register the dock widget under
        :param bv: the currently focused BinaryView (may be None)
        """

        self.bv = bv

        QWidget.__init__(self, parent)
        DockContextHandler.__init__(self, self, name)

        # Set up the save timer to save the current notepad content on timeout.
        self.save_timer = QTimer(self)
        self.save_timer.setSingleShot(True)
        self.save_timer.timeout.connect(lambda: self.store_notes())

        # Initialize the editor and set up the text changed callback.
        self.editor = JMarkdownEditor(self)
        self.editor.textChanged.connect(self.on_editor_text_changed)

        # Create the viewer
        self.viewer = JMarkdownViewer(self, self.editor)

        # Add both widgets to a tab container
        self.tab_container = QTabWidget()
        self.tab_container.addTab(self.viewer, "View")
        self.tab_container.addTab(self.editor, "Edit")

        # Create a simple layout for the editor and set it as the root layout.
        layout = QVBoxLayout()
        layout.addWidget(self.tab_container)
        layout.setContentsMargins(0, 0, 0, 0)
        self.setLayout(layout)

        # Probably does nothing since there should be no BV when the program
        # starts, but needs confirmation, so here it will stay.
        self.query_notes()

    def on_editor_text_changed(self):
        """
        Callback triggered when the editor's text changes.

        Used is to start the save timer, which will automatically save the
        user's notes after 1 second of typing inactivity.
        """

        self.save_timer.start(1000)

    def on_binary_view_changed(self, new_bv: BinaryView):
        """
        Callback triggered when the focused BinaryView changes.

        Used to save the notes of the old BinaryView and load the stored notes
        from the new BinaryView, if there are any.
        """

        # Do nothing if the new view is the old view.
        if self.bv == new_bv:
            return

        # Do nothing if we have no BinaryView, which might happen at startup?
        if self.bv is not None:
            self.store_notes()

        # Update the internal BinaryView reference to point to the new
        # BinaryView, and attempt to load the notes stored in it. If there is no
        # new BinaryView, remove the reference to the old one.
        if new_bv is None:
            self.bv = None
        else:
            self.bv = new_bv
            self.query_notes()

    def query_notes(self):
        """
        Attempt to retrieve the saved notes from the database. Will do nothing
        if there is no active BinaryView.
        """

        # Do nothing if we have no BinaryView, which might happen at startup?
        if self.bv is None:
            return

        # Attempt to access the notes stored in the database metadata. Will
        # throw a KeyError if this is a brand new database. Block signals to
        # avoid accidentally triggering the save event.
        self.editor.blockSignals(True)
        try:
            notes = self.bv.query_metadata(METADATA_KEY)
            self.editor.setPlainText(notes)

            # Manually trigger the text changed event since we disabled signals
            self.viewer.on_editor_text_changed()
        except KeyError:
            self.editor.setPlainText("")

        self.editor.blockSignals(False)

    def store_notes(self):
        """
        Attempt to store the current notes in the database. Will do nothing if
        there is no active BinaryView.
        """

        # Do nothing if we have no BinaryView, which might happen at startup?
        if self.bv is None:
            pass

        # Store the notes in the metadata of the database.
        notes = self.editor.toPlainText()
        self.bv.store_metadata(METADATA_KEY, notes)
        self.bv.modified = True

    # -- Binary Ninja UI callbacks (camelCase shenanigans warning) --

    # Should the notepad be visible? Helps with automatically hiding the notepad
    # when it is not needed.
    def shouldBeVisible(self, vf):
        return vf is not None

    # Triggered when a view changes, vf is a ViewFrame.
    def notifyViewChanged(self, vf):
        self.on_binary_view_changed(vf.getCurrentViewInterface().getData())
Beispiel #15
0
class AlarmClock(QWidget):

    started = Signal()
    aborted = Signal()
    finished = Signal()

    def __init__(self, parent=None):

        super().__init__(parent)
        self.alarm = QTime()
        self.timer = QTimer(self)
        self.timer.setSingleShot(True)
        self.update_timer = QTimer(self)
        self.update_timer.setInterval(1000)

        self.current_label = QLabel('00:00:00')
        font = QFont()
        font.setPointSize(50)
        self.current_label.setFont(font)
        self.time_edit = QTimeEdit(self)
        self.time_edit.setDisplayFormat('H:mm')
        self.set_around_btn = QPushButton('Set around time', self)

        self.set_ui()
        self.set_connection()
        self.reset()

        self.update_timer.start()
        self.update_label()
        self.set_around_time()

    def set_ui(self):
        self.vlayout = QVBoxLayout(self)
        self.vlayout.addWidget(self.current_label)
        self.vlayout.addWidget(self.time_edit)
        self.vlayout.addWidget(self.set_around_btn)

    def set_connection(self):
        self.timer.timeout.connect(self.stop)
        self.update_timer.timeout.connect(self.update_label)
        self.set_around_btn.clicked.connect(self.set_around_time)

    def set_around_time(self):
        current = QTime.currentTime()
        if current.minute() < 29:
            self.time_edit.setTime(QTime(current.hour(), 30))
        else:
            self.time_edit.setTime(QTime(current.hour() + 1, 0))

    def update_label(self):
        current = QTime.currentTime()
        self.current_label.setText('{hour:02}:{min:02}:{sec:02}'.format(
            hour=current.hour(), min=current.minute(), sec=current.second()))

    def start(self):
        self.time_edit.setEnabled(False)
        self.alarm = self.time_edit.time()
        self.timer.start(QTime.currentTime().msecsTo(self.alarm))
        self.started.emit()

    def abort(self):
        self.reset()
        self.aborted.emit()

    def stop(self):
        self.reset()
        self.finished.emit()

    def reset(self):
        self.timer.stop()
        self.time_edit.setEnabled(True)

    def get_notify_message(self):
        return ''

    @property
    def name(self):
        return 'Alarm Clock'
Beispiel #16
0
class DebugView(QWidget, View):
	class DebugViewHistoryEntry(HistoryEntry):
		def __init__(self, memory_addr, address, is_raw):
			HistoryEntry.__init__(self)

			self.memory_addr = memory_addr
			self.address = address
			self.is_raw = is_raw

		def __repr__(self):
			if self.is_raw:
				return "<raw history: {}+{:0x} (memory: {:0x})>".format(self.address['module'], self.address['offset'], self.memory_addr)
			return "<code history: {:0x} (memory: {:0x})>".format(self.address, self.memory_addr)

	def __init__(self, parent, data):
		if not type(data) == BinaryView:
			raise Exception('expected widget data to be a BinaryView')

		self.bv = data

		self.debug_state = binjaplug.get_state(data)
		memory_view = self.debug_state.memory_view
		self.debug_state.ui.debug_view = self

		QWidget.__init__(self, parent)
		self.controls = ControlsWidget.DebugControlsWidget(self, "Controls", data, self.debug_state)
		View.__init__(self)

		self.setupView(self)

		self.current_offset = 0

		self.splitter = QSplitter(Qt.Orientation.Horizontal, self)

		frame = ViewFrame.viewFrameForWidget(self)
		self.memory_editor = LinearView(memory_view, frame)
		self.binary_editor = DisassemblyContainer(frame, data, frame)

		self.binary_text = TokenizedTextView(self, memory_view)
		self.is_raw_disassembly = False
		self.raw_address = 0

		self.is_navigating_history = False
		self.memory_history_addr = 0

		# TODO: Handle these and change views accordingly
		# Currently they are just disabled as the DisassemblyContainer gets confused
		# about where to go and just shows a bad view
		self.binary_editor.getDisassembly().actionHandler().bindAction("View in Hex Editor", UIAction())
		self.binary_editor.getDisassembly().actionHandler().bindAction("View in Linear Disassembly", UIAction())
		self.binary_editor.getDisassembly().actionHandler().bindAction("View in Types View", UIAction())

		self.memory_editor.actionHandler().bindAction("View in Hex Editor", UIAction())
		self.memory_editor.actionHandler().bindAction("View in Disassembly Graph", UIAction())
		self.memory_editor.actionHandler().bindAction("View in Types View", UIAction())

		small_font = QApplication.font()
		small_font.setPointSize(11)

		bv_layout = QVBoxLayout()
		bv_layout.setSpacing(0)
		bv_layout.setContentsMargins(0, 0, 0, 0)

		bv_label = QLabel("Loaded File")
		bv_label.setFont(small_font)
		bv_layout.addWidget(bv_label)
		bv_layout.addWidget(self.binary_editor)

		self.bv_widget = QWidget()
		self.bv_widget.setLayout(bv_layout)

		disasm_layout = QVBoxLayout()
		disasm_layout.setSpacing(0)
		disasm_layout.setContentsMargins(0, 0, 0, 0)

		disasm_label = QLabel("Raw Disassembly at PC")
		disasm_label.setFont(small_font)
		disasm_layout.addWidget(disasm_label)
		disasm_layout.addWidget(self.binary_text)

		self.disasm_widget = QWidget()
		self.disasm_widget.setLayout(disasm_layout)

		memory_layout = QVBoxLayout()
		memory_layout.setSpacing(0)
		memory_layout.setContentsMargins(0, 0, 0, 0)

		memory_label = QLabel("Debugged Process")
		memory_label.setFont(small_font)
		memory_layout.addWidget(memory_label)
		memory_layout.addWidget(self.memory_editor)

		self.memory_widget = QWidget()
		self.memory_widget.setLayout(memory_layout)

		self.splitter.addWidget(self.bv_widget)
		self.splitter.addWidget(self.memory_widget)

		# Equally sized
		self.splitter.setSizes([0x7fffffff, 0x7fffffff])

		layout = QVBoxLayout()
		layout.setContentsMargins(0, 0, 0, 0)
		layout.setSpacing(0)
		layout.addWidget(self.controls)
		layout.addWidget(self.splitter, 100)
		self.setLayout(layout)

		self.needs_update = True
		self.update_timer = QTimer(self)
		self.update_timer.setInterval(200)
		self.update_timer.setSingleShot(False)
		self.update_timer.timeout.connect(lambda: self.updateTimerEvent())

		self.add_scripting_ref()

		# set initial breakpoint when view is switched
		if self.debug_state.bv and self.debug_state.bv.entry_point:
			local_entry_offset = self.debug_state.bv.entry_point - self.debug_state.bv.start
			if not self.debug_state.breakpoints.contains_offset(self.debug_state.bv.file.original_filename, local_entry_offset):
				self.debug_state.breakpoints.add_offset(self.debug_state.bv.file.original_filename, local_entry_offset)
				if self.debug_state.ui is not None:
					self.debug_state.ui.breakpoint_tag_add(self.debug_state.bv.entry_point)
					self.debug_state.ui.update_highlights()
					self.debug_state.ui.update_breakpoints()

	def add_scripting_ref(self):
		# Hack: The interpreter is just a thread, so look through all threads
		# and assign our state to the interpreter's locals
		for thread in threading.enumerate():
			if type(thread) == PythonScriptingInstance.InterpreterThread:
				thread.locals["dbg"] = self.debug_state

	def getData(self):
		return self.bv

	def getFont(self):
		return binaryninjaui.getMonospaceFont(self)

	def getCurrentOffset(self):
		if not self.is_raw_disassembly:
			return self.binary_editor.getDisassembly().getCurrentOffset()
		return self.raw_address

	def getSelectionOffsets(self):
		if not self.is_raw_disassembly:
			return self.binary_editor.getDisassembly().getSelectionOffsets()
		return (self.raw_address, self.raw_address)

	def getCurrentFunction(self):
		if not self.is_raw_disassembly:
			return self.binary_editor.getDisassembly().getCurrentFunction()
		return None

	def getCurrentBasicBlock(self):
		if not self.is_raw_disassembly:
			return self.binary_editor.getDisassembly().getCurrentBasicBlock()
		return None

	def getCurrentArchitecture(self):
		if not self.is_raw_disassembly:
			return self.binary_editor.getDisassembly().getCurrentArchitecture()
		return None

	def getCurrentLowLevelILFunction(self):
		if not self.is_raw_disassembly:
			return self.binary_editor.getDisassembly().getCurrentLowLevelILFunction()
		return None

	def getCurrentMediumLevelILFunction(self):
		if not self.is_raw_disassembly:
			return self.binary_editor.getDisassembly().getCurrentMediumLevelILFunction()
		return None

	def getHistoryEntry(self):
		if self.is_navigating_history:
			return None
		memory_addr = self.memory_editor.getCurrentOffset()
		if memory_addr != self.memory_history_addr:
			self.memory_history_addr = memory_addr
		if self.is_raw_disassembly and self.debug_state.connected:
			rel_addr = self.debug_state.modules.absolute_addr_to_relative(self.raw_address)
			return DebugView.DebugViewHistoryEntry(memory_addr, rel_addr, True)
		else:
			address = self.binary_editor.getDisassembly().getCurrentOffset()
			return DebugView.DebugViewHistoryEntry(memory_addr, address, False)

	def navigateToFunction(self, func, offset):
		return self.navigate(offset)

	def navigateToHistoryEntry(self, entry):
		self.is_navigating_history = True
		if hasattr(entry, 'is_raw'):
			self.memory_editor.navigate(entry.memory_addr)
			if entry.is_raw:
				if self.debug_state.connected:
					address = self.debug_state.modules.relative_addr_to_absolute(entry.address)
					self.navigate_raw(address)
			else:
				self.navigate_live(entry.address)

		View.navigateToHistoryEntry(self, entry)
		self.is_navigating_history = False

	def navigate(self, addr):
		# If we're not connected we cannot even check if the address is remote
		if not self.debug_state.connected:
			return self.navigate_live(addr)

		if self.debug_state.memory_view.is_local_addr(addr):
			local_addr = self.debug_state.memory_view.remote_addr_to_local(addr)
			if self.debug_state.bv.read(local_addr, 1) and len(self.debug_state.bv.get_functions_containing(local_addr)) > 0:
				return self.navigate_live(local_addr)

		# This runs into conflicts if some other address space is mapped over
		# where the local BV is currently loaded, but this is was less likely
		# than the user navigating to a function from the UI
		if self.debug_state.bv.read(addr, 1) and len(self.debug_state.bv.get_functions_containing(addr)) > 0:
			return self.navigate_live(addr)

		return self.navigate_raw(addr)

	def navigate_live(self, addr):
		self.show_raw_disassembly(False)
		return self.binary_editor.getDisassembly().navigate(addr)

	def navigate_raw(self, addr):
		if not self.debug_state.connected:
			# Can't navigate to remote addr when disconnected
			return False
		self.raw_address = addr
		self.show_raw_disassembly(True)
		self.load_raw_disassembly(addr)
		return True

	def notifyMemoryChanged(self):
		self.needs_update = True

	def updateTimerEvent(self):
		if self.needs_update:
			self.needs_update = False

			# Refresh the editor
			if not self.debug_state.connected:
				self.memory_editor.navigate(0)
				return

			# self.memory_editor.navigate(self.debug_state.stack_pointer)

	def showEvent(self, event):
		if not event.spontaneous():
			self.update_timer.start()
			self.add_scripting_ref()

	def hideEvent(self, event):
		if not event.spontaneous():
			self.update_timer.stop()

	def shouldBeVisible(self, view_frame):
		if view_frame is None:
			return False
		else:
			return True

	def load_raw_disassembly(self, start_ip):
		# Read a few instructions from rip and disassemble them
		inst_count = 50

		arch_dis = self.debug_state.remote_arch
		rip = self.debug_state.ip

		# Assume the worst, just in case
		read_length = arch_dis.max_instr_length * inst_count
		data = self.debug_state.memory_view.read(start_ip, read_length)

		lines = []

		# Append header line
		tokens = [InstructionTextToken(InstructionTextTokenType.TextToken, "(Code not backed by loaded file, showing only raw disassembly)")]
		contents = DisassemblyTextLine(tokens, start_ip)
		line = LinearDisassemblyLine(LinearDisassemblyLineType.BasicLineType, None, None, contents)
		lines.append(line)

		total_read = 0
		for i in range(inst_count):
			line_addr = start_ip + total_read
			(insn_tokens, length) = arch_dis.get_instruction_text(data[total_read:], line_addr)

			if insn_tokens is None:
				insn_tokens = [InstructionTextToken(InstructionTextTokenType.TextToken, "??")]
				length = arch_dis.instr_alignment
				if length == 0:
					length = 1

			# terrible libshiboken workaround, see #101
			for tok in insn_tokens:
				if tok.value.bit_length() == 64:
					tok.value ^= 0x8000000000000000

			tokens = []
			color = HighlightStandardColor.NoHighlightColor
			if line_addr == rip:
				if self.debug_state.breakpoints.contains_absolute(start_ip + total_read):
					# Breakpoint & pc
					tokens.append(InstructionTextToken(InstructionTextTokenType.TagToken, self.debug_state.ui.get_breakpoint_tag_type().icon + ">", width=5))
					color = HighlightStandardColor.RedHighlightColor
				else:
					# PC
					tokens.append(InstructionTextToken(InstructionTextTokenType.TextToken, " ==> "))
					color = HighlightStandardColor.BlueHighlightColor
			else:
				if self.debug_state.breakpoints.contains_absolute(start_ip + total_read):
					# Breakpoint
					tokens.append(InstructionTextToken(InstructionTextTokenType.TagToken, self.debug_state.ui.get_breakpoint_tag_type().icon, width=5))
					color = HighlightStandardColor.RedHighlightColor
				else:
					# Regular line
					tokens.append(InstructionTextToken(InstructionTextTokenType.TextToken, "     "))
			# Address
			tokens.append(InstructionTextToken(InstructionTextTokenType.AddressDisplayToken, hex(line_addr)[2:], line_addr))
			tokens.append(InstructionTextToken(InstructionTextTokenType.TextToken, "  "))
			tokens.extend(insn_tokens)

			# Convert to linear disassembly line
			contents = DisassemblyTextLine(tokens, line_addr, color=color)
			line = LinearDisassemblyLine(LinearDisassemblyLineType.CodeDisassemblyLineType, None, None, contents)
			lines.append(line)

			total_read += length

		self.binary_text.setLines(lines)

	def show_raw_disassembly(self, raw):
		if raw != self.is_raw_disassembly:
			self.splitter.replaceWidget(0, self.disasm_widget if raw else self.bv_widget)
			self.is_raw_disassembly = raw

	def refresh_raw_disassembly(self):
		if not self.debug_state.connected:
			# Can't navigate to remote addr when disconnected
			return

		if self.is_raw_disassembly:
			self.load_raw_disassembly(self.getCurrentOffset())
Beispiel #17
0
class StatusBar(QWidget):
    def __init__(self, parent=None) -> None:
        super().__init__()
        self.parent = parent
        #
        self.message_label = QLabel(self)
        self.sep1 = QLabel("|", self)
        self.curr_pos_label = QLabel(self)
        self.sep2 = QLabel("|", self)
        self.file_name_label = QLabel(self)
        self.sep3 = QLabel("|", self)
        self.resolution_label = QLabel(self)
        self.sep4 = QLabel("|", self)
        # self.memory_label = QLabel(self)
        # self.sep5 = QLabel("|", self)
        self.mode_label = QLabel(self)
        self.sep5 = QLabel("|", self)
        self.progressbar = QProgressBar(self)
        self.progressbar.setMaximumWidth(150)
        self.progressbar.setMaximumHeight(15)
        self.progressbar.hide()
        #
        layout = QHBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        self.setLayout(layout)
        self.layout().addWidget(self.message_label)
        self.layout().addWidget(self.sep1)
        self.layout().addWidget(self.curr_pos_label)
        self.layout().addWidget(self.sep2)
        self.layout().addWidget(self.resolution_label)
        self.layout().addWidget(self.sep3)
        self.layout().addWidget(self.file_name_label)
        self.layout().addWidget(self.sep4)
        # self.layout().addWidget(self.memory_label)
        # self.layout().addWidget(self.sep5)
        self.layout().addWidget(self.mode_label)
        self.layout().addWidget(self.sep5)
        self.layout().addWidget(self.progressbar)
        #
        self.message_timer = None

    def reset(self) -> None:
        self.message_label.setText("")
        self.curr_pos_label.setText("")
        self.file_name_label.setText("")
        self.resolution_label.setText("")

    def flash_message(self,
                      msg: str,
                      wait: int = cfg.MESSAGE_FLASH_TIME_2) -> None:
        self.message_label.setText(msg)
        if self.message_timer:
            self.message_timer.stop()
            self.message_timer.deleteLater()
        self.message_timer = QTimer()
        self.message_timer.timeout.connect(
            self.delete_flashed_message)  # type: ignore
        self.message_timer.setSingleShot(True)  # type: ignore
        self.message_timer.start(wait)  # type: ignore

    def delete_flashed_message(self) -> None:
        self.message_label.setText("")