Example #1
0
class MFHistory(QWidget):
    _signal0 = pyqtSignal()
    _signal1 = pyqtSignal(object)
    _signal2 = pyqtSignal(object, str)
    on_lock = pyqtSignal(object)
    off_lock = pyqtSignal(object)
    set_hint = pyqtSignal(object, str)
    set_progress = pyqtSignal(object, float)

    def __init__(self, parent, base_path, mf_exec):
        super().__init__(parent)
        self.parent = parent
        self.base_path = base_path
        self.mf_exec = mf_exec
        #
        self.filter = None
        self.worker = None
        self.stp = None
        self.time_type, self.time_anchor = 0, 0
        self.styleHelper()
        #
        _disp = self.w_topbar.hint_label
        self.on_lock.connect(_disp.getLock)
        self.off_lock.connect(_disp.releaseLock)
        self.set_hint.connect(_disp.setHint)
        self.set_progress.connect(_disp.setProgressHint)
        pass

    def styleHelper(self):
        # set main window layout as grid
        self.grid = QGridLayout()
        self.grid.setSpacing(0)
        self.grid.setContentsMargins(0, 0, 0, 0)
        # add widget into main layout
        self.w_topbar = TopbarManager(self)
        self.w_history_list = MFHistoryList(self, self.base_path)
        self.grid.addWidget(self.w_topbar, 0, 0)
        self.grid.addWidget(self.w_history_list, 1, 0)
        self.setLayout(self.grid)
        self.setFixedSize(*CFG.SIZE_HISTORY_MAIN())
        self.setVisible(False)
        self.setStyleSheet(CFG.STYLESHEET(self))
        pass

    def hideEvent(self, e):
        super().hideEvent(e)
        if not self.isVisible():
            self.w_history_list.clear()
        pass

    def setFocus(self):
        self.parent.setFocus()

    def setFilter(self, _filter):
        self.filter = _filter
        self.updateHistory(0, 0)
        pass

    def updateHistory(self, type_delta, anchor_delta, relative=False):
        if not self.isVisible(): return
        if self.worker and self.worker.isRunning():
            self.worker.terminate()
            self.worker.thread.wait()
        #
        mf_type = (self.time_type +
                   type_delta) if type_delta is not None else 0
        mf_anchor = (self.time_anchor +
                     anchor_delta) if anchor_delta is not None else 0
        if mf_type >= 4:  #reset to today
            mf_type, mf_anchor = 0, 0
            relative = False
        if mf_anchor > 0: return  #no future history
        if relative:
            self.stp.update_type(mf_type, mf_anchor)
        else:
            self.stp = TextStamp(mf_type, mf_anchor)
        #
        self.w_topbar.hint_label.setDateHint(self.stp, "(Loading...)")
        self.worker = MFWorker(self._updateHistory,
                               args=(mf_type, mf_anchor, relative))
        self.worker.start()
        return mf_type

    def _updateHistory(self, mf_type, mf_anchor, relative):
        self.items = self.mf_exec.mf_fetch(mf_type,
                                           mf_anchor,
                                           None,
                                           stp=self.stp,
                                           locate_flag=True)
        if relative:
            self.time_type, self.time_anchor = mf_type, self.stp.diff_time(
                mf_type)  # relative update
        else:
            self.time_type, self.time_anchor = mf_type, mf_anchor  # iteratively update
        _postfix = '(filtered)' if self.filter is not None else ''
        signal_emit(self._signal2, self.w_topbar.hint_label.setDateHint,
                    (self.stp, _postfix))
        signal_emit(self._signal1, self.renderHistory, (self.items, ))
        pass

    @pyqtSlot(object)
    def renderHistory(self, items):
        self.w_history_list.clear()

        for item in items:
            (_user, _stp,
             _text) = item[1]  #TODO: match user/timestamp/content/<theme>
            if (self.filter is None) or self.filter.search(_text):
                w_item = QListWidgetItem(self.w_history_list)
                w_item_widget = MFHistoryItem(self, w_item, self.base_path,
                                              item)
                size_hint = QSize(0,
                                  w_item_widget.sizeHint().height() +
                                  CFG.SIZE_ITEM_MARGIN('MFHistoryItem')
                                  )  # do not adjust width
                w_item.setSizeHint(size_hint)
                self.w_history_list.addItem(w_item)
                self.w_history_list.setItemWidget(w_item, w_item_widget)
            pass
        pass

    def showHint(self, hint, show_ms):
        self.on_lock.emit(self)
        self.set_hint.emit(self, hint)
        if show_ms >= 0:
            QThread.msleep(show_ms)
            self.off_lock.emit(self)
        pass

    def dumpHistory(self, disp):
        self.on_lock.emit(self)
        self.set_progress.emit(self, 0.0)
        #
        _file = 'MFExport %s.md' % self.stp.hint
        if Path('~/Desktop').expanduser().is_dir():
            _path = Path('~/Desktop', _file).expanduser()
        else:  #bypass some Windows OneDrive
            _path = Path('~/', _file).expanduser()
        _total = self.w_history_list.count()
        with open(str(_path), 'w+', encoding='utf-8') as f:
            f.write('# Mind Flash Export - %s\n' % self.stp.hint)
            for i in range(_total):
                w_item = self.w_history_list.itemWidget(
                    self.w_history_list.item(i))
                raw_item, uri = w_item.item, w_item.uri
                # dump the history item
                (_user, _time, _) = raw_item
                _date = datetime.fromtimestamp(
                    int(_time), tz=tzlocal()).strftime('%Y-%m-%d %H:%M:%S')
                _text = ''
                _rich_text = w_item.rich_text.copy()
                for _idx in range(len(w_item.plain_text)):
                    if w_item.plain_text[_idx] == '':
                        if len(_rich_text) == 0: continue
                        _item = _rich_text.pop(0)
                        if _item[1] == 'img':
                            _file = Path(self.base_path, _item[2])
                            _text += '\n![](%s)\n' % (POSIX(_file))
                        elif _item[1] == 'file':
                            _file = Path(self.base_path, _item[2])
                            _text += '\n[%s](%s)\n' % (_file.name,
                                                       POSIX(_file))
                        else:
                            _text += '[%s](%s)' % (_item[1], _item[2])
                    else:
                        _text += '\n' + w_item.plain_text[_idx].strip()
                    pass
                f.write("**`{user}`** `{date}`\n{text}\n\n------\n".format(
                    date=_date, user=_user, text=_text))
                self.set_progress.emit(self, (i + 1) / _total)
            pass
        #
        QThread.msleep(1000)
        self.off_lock.emit(self)
        pass

    pass
Example #2
0
class MFGui(QWidget):
    _signal1 = pyqtSignal(str)
    _signal2 = pyqtSignal(object, str)

    def __init__(self):
        super().__init__()
        self.mf_exec = mf_exec
        self.w_todo = MFTodoWidget(self, MF_DIR, sync=False)
        self.w_history = MFHistory(self, MF_DIR, mf_exec)
        self.w_editor = MFTextEdit(self, self.w_history, self.w_todo)
        # set main window layout as grid
        self.grid = QGridLayout()
        self.grid.setSpacing(0)
        self.grid.setContentsMargins(0, 0, 0, 0)
        self.grid.addWidget(self.w_todo, 0, 0)
        self.grid.addWidget(self.w_editor, 1, 0)
        self.grid.setSizeConstraint(QLayout.SetFixedSize)
        self.setLayout(self.grid)
        self.resize(self.sizeHint())
        # register global shortcuts
        self.keysFn = KeysReactor(self, 'MFGui')
        self.registerGlobalKeys()
        # move window to desktop center
        qr = self.frameGeometry()
        cp = QDesktopWidget().availableGeometry().center()
        qr.moveCenter(cp)
        self.move(qr.topLeft())
        # set window style
        self.setWindowTitle(MF_NAME)
        self.setWindowIcon(QIcon('./res/icons/pulse_heart.png'))
        self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
        self.setAttribute(Qt.WA_InputMethodEnabled)
        self.setAttribute(Qt.WA_TranslucentBackground, True)
        self.setContentsMargins(5, 5, 5, 5)
        self.setGraphicsEffect(
            QGraphicsDropShadowEffect(blurRadius=5, xOffset=3, yOffset=3))
        self.setFocus()
        self.show()
        # check update
        self.worker = MFWorker(self.checkUpdate)
        self.worker.start()
        pass

    def registerGlobalKeys(self):
        ### Escape / Ctrl+W ###
        self.keysFn.register(CFG.KEYS_CLOSE(), lambda: self.close())
        self.keysFn.register(CFG.KEYS_CLOSE(self), lambda: self.close())
        ### Ctrl+L ###
        self.keysFn.register(CFG.KEYS_TO_EDIT(self), lambda: self.setFocus())

        ### Ctrl+F ###
        def mf_search_binding():
            if self.w_history.isVisible():
                _topbar = self.w_history.w_topbar
                _topbar.switch(_topbar.input_box)
                _topbar.input_box.setFocus()
            pass

        self.keysFn.register(CFG.KEYS_SEARCH(self), mf_search_binding)
        ### Alt+V ###
        self.keysFn.register(
            CFG.KEYS_RANGE_SWITCH(),
            lambda: self.w_history.updateHistory(+1, None, True))
        ### Alt+J ###
        self.keysFn.register(CFG.KEYS_JUMP_FORWARD(),
                             lambda: self.w_history.updateHistory(0, +1))
        ### Alt+K ###
        self.keysFn.register(CFG.KEYS_JUMP_BACKWARD(),
                             lambda: self.w_history.updateHistory(0, -1))
        ### Alt+H ###
        self.keysFn.register(CFG.KEYS_TOGGLE(),
                             lambda: self.w_history.toggleHistoryWidget())
        pass

    def checkUpdate(self):
        QThread.sleep(5)
        try:
            res = url_request.urlopen(MF_STATUS).read().decode('utf-8')
            res = json.loads(res)
            _latest = res[0]['name'][1:]
            if _latest != MF_VERSION and self.w_history.isVisible():
                _hint = '<a href="{url}/releases/tag/v{ver}">(v{ver} Available)</a>'.format(
                    url=MF_WEBSITE, ver=_latest)
                signal_emit(self._signal2,
                            self.w_history.w_topbar.hint_label.setDateHint,
                            (None, _hint))
                signal_emit(
                    self._signal1,
                    self.w_history.w_topbar.tool_bar.items['_'].setText,
                    (_hint, ))
        except Exception as e:
            print('Check Update Failed: ', e)
        pass

    def setFocus(self):
        self.w_editor.showCaret(force=True)
        self.w_editor.setFocus()
        pass

    pass
Example #3
0
class QLabelWrapper(QLabel):
    def __init__(self, type, text='', pixmap='', alt='', parent=None):
        super().__init__(text)
        self.parent = parent
        self.type = type
        self.alt = alt
        if pixmap: self.setPixmap(pixmap)
        self.styleHelper()
        pass

    def styleHelper(self):
        self.setWordWrap(True)
        if self.type == 'item_hint':
            self.setFont(CFG.FONT_ITEM_HINT('MFHistoryItem'))
            self.setStyleSheet(CFG.STYLE_HINT('MFHistoryItem'))
            self.setFixedHeight(
                QFontMetrics(self.font()).height() +
                CFG.SIZE_HINT_HEIGHT_FIX('MFHistoryItem'))
            #
            _user, _date, _time = self.text().split()
            t_rng = int(_time.split(':')[0])
            if t_rng in range(0, 6):
                _time_color = CFG.COLOR_LATE_NIGHT()
            elif t_rng in range(6, 12):
                _time_color = CFG.COLOR_MORNING()
            elif t_rng in range(12, 18):
                _time_color = CFG.COLOR_AFTERNOON()
            else:
                _time_color = CFG.COLOR_NIGHT()
            #
            self.setText('''
                        <a style="color:{date_color}">{date}</a>
                        <a style="color:{time_color}">{time}</a>
                        <a style="color:{user_color}">@ {user}</a>
                        '''.format(
                user=_user,
                user_color=CFG.COLOR_HINT_USER('MFHistoryItem'),
                date=_date,
                date_color=CFG.COLOR_HINT_DATE('MFHistoryItem'),
                time_color=_time_color,
                time=_time))
            self.setToolTip(self.alt)
            pass
        elif self.type == 'item_text':
            self.setFont(CFG.FONT_ITEM_TEXT('MFHistoryItem'))
            self.setAlignment(Qt.AlignLeft | Qt.AlignTop)
            self.setTextFormat(Qt.RichText)
            self.setTextInteractionFlags(Qt.TextBrowserInteraction)
            self.setOpenExternalLinks(True)
            self.setStyleSheet(CFG.STYLE_TEXT('MFHistoryItem'))
            pass
        elif self.type == 'img_label':
            self.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
            self.setToolTip("View: Left-Click\nCopy: Right-Click")
            pass
        elif self.type == 'file_label':
            _pixmap = self.getFileIcon()
            _icon_size = CFG.SIZE_FILE_ICON('MFHistoryItem')
            self.setPixmap(
                _pixmap.scaledToWidth(_icon_size[0], Qt.SmoothTransformation))
            self.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
            pass
        else:
            raise Exception
            pass
        pass

    def getFileIcon(self):
        #
        _text = Path(self.alt).name
        _suffix = Path(self.alt).suffix.upper()
        _suffix = _suffix[1:] if _suffix else "(Unknown)"
        _pixmap = QPixmap(120, 120)
        _pixmap.fill(QColor(0, 0, 0, 0))
        _painter = QPainter(_pixmap)
        _painter.setRenderHints(QPainter.Antialiasing
                                | QPainter.TextAntialiasing)
        # draw rounded rect
        _pen = _painter.pen()
        _pen.setWidth(2)
        _painter.setPen(_pen)
        _painter.drawRoundedRect(QRect(1, 1, 118, 118), 15, 15)
        # draw suffix text
        _rect = QRect(8, 10, 108, 35)  #100*25
        _painter.setPen(QPen(QColor("#0f59a4")))
        _painter.setFont(CFG.FONT_ICON_SUFFIX('MFHistoryItem'))
        _painter.drawText(_rect, Qt.AlignHCenter | Qt.TextSingleLine, _suffix)
        _painter.setPen(_pen)
        # draw splitter
        _painter.drawLine(1, 40, 118, 40)
        # draw suffix text
        _rect = QRect(8, 45, 108, 110)  #100*65
        _painter.setFont(CFG.FONT_ICON_NAME('MFHistoryItem'))
        _fm = QFontMetrics(_painter.font())
        # _elided_text = _fm.elidedText(_text, Qt.ElideMiddle, _rect.width(), Qt.TextWrapAnywhere)
        _painter.drawText(_rect, Qt.AlignHCenter | Qt.TextWrapAnywhere, _text)
        del _painter
        return _pixmap

    def mousePressEvent(self, ev):
        if self.type == 'img_label' and ev.buttons() & Qt.RightButton:
            _clipboard = QApplication.clipboard()
            if self.alt:
                _clipboard.setPixmap(self.alt)
            else:
                _clipboard.setPixmap(self.pixmap())
            try:
                w_history = self.parent.parent
                _text = "Image copied."
                self.worker = MFWorker(w_history.showHint,
                                       args=(_text,
                                             int(CFG.CFG_HINT_SHOW_MS())))
                self.worker.start()
            except Exception:
                pass
            pass
        elif self.type == 'img_label' and ev.buttons() & Qt.LeftButton:
            _pixmap = self.alt if self.alt else self.pixmap()
            w_preview = MFImagePreviewer(self, _pixmap)
            pass
        elif self.type == 'file_label' and ev.buttons() & Qt.RightButton:
            _clipboard = QApplication.clipboard()
            _mimeData = QMimeData()
            _mimeData.setUrls([QUrl.fromLocalFile(str(self.alt))])
            _clipboard.setMimeData(_mimeData)
            try:
                w_history = self.parent.parent
                _text = "File <u>%s</u> copied." % self.alt.name
                self.worker = MFWorker(w_history.showHint,
                                       args=(_text,
                                             int(CFG.CFG_HINT_SHOW_MS())))
                self.worker.start()
            except:
                pass
            pass
        elif self.type == 'file_label' and ev.buttons() & Qt.LeftButton:
            QDesktopServices.openUrl(QUrl.fromLocalFile(POSIX(
                self.alt.parent)))
            pass
        return super().mousePressEvent(ev)

    pass
Example #4
0
class ToolBarIcon(QLabel):
    def __init__(self, parent, item):
        super().__init__('', parent)
        self.parent = parent
        self.topbar = parent.parent
        self.name = item[0]
        self.attr = item[1]
        self.press_pos = QPoint(0, 0)
        self.styleHelper()
        pass

    def styleHelper(self):
        if self.name == '_':
            _width = CFG.SIZE_TOPBAR_MAIN(
            )[0] - CFG.SIZE_TOPBAR_ICON()[0] * TOOL_ICON_NUM - (
                CFG.SIZE_ICON(self)[0] + 1)  #33 for one rightmost icon
            self.setFixedSize(_width, CFG.SIZE_TOPBAR_MAIN()[1])
            self.setStyleSheet('QLabel { border-width: 1px 0px 1px 0px; }')
            self.setTextFormat(Qt.RichText)
            self.setTextInteractionFlags(Qt.TextBrowserInteraction)
            self.setOpenExternalLinks(True)
            self.callback = None  #TODO: impl. MouseReactor
            return

        self.icon_name = self.name
        _icon = QIcon('./res/svg/{}.svg'.format(self.icon_name)).pixmap(
            QSize(*CFG.SIZE_ICON(self)))
        self.setPixmap(_icon)
        self.setToolTip(self.attr['hint'])
        self.callback = self.__getattribute__(self.attr['func'])
        self.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)

        if self.attr['pos'] < 0:  #leftmost position
            self.setFixedSize(*CFG.SIZE_TOPBAR_ICON())
            self.setStyleSheet('QLabel { border-width: 1px 1px 1px 1px; }')
        elif self.attr['pos'] > 0:  #rightmost position
            self.setAlignment(Qt.AlignRight | Qt.AlignTop)
            self.setStyleSheet('QLabel { border-width: 1px 1px 1px 0px; }')
        else:  #middle position
            self.setFixedSize(*CFG.SIZE_TOPBAR_ICON())
            self.setStyleSheet('QLabel { border-width: 1px 1px 1px 0px; }')
        pass

    def mousePressEvent(self, e):
        self.press_pos = e.pos()
        if e.buttons() & Qt.LeftButton:
            if self.callback: self.callback()
        return super().mousePressEvent(e)

    def mouseMoveEvent(self, e):
        if (self.callback is None) and (e.buttons() & Qt.LeftButton):
            _main_body = self.topbar.parent.parent
            _main_body.move(_main_body.mapToParent(e.pos() - self.press_pos))
            # print(self.parent.pos() - self.init_pos)
            pass
        elif e.buttons() & Qt.RightButton:
            pass
        pass

    def filterIconEvent(self):
        self.topbar.switch(self.topbar.input_box)
        self.topbar.input_box.setFocus()
        pass

    def exportIconEvent(self):
        self.topbar.switch(self.topbar.hint_label)
        self.worker = MFWorker(self.topbar.parent.dumpHistory,
                               args=(self.topbar.hint_label, ))
        self.worker.start()
        pass

    def historyIconEvent(self):
        _icons = ['history', 'history-week', 'history-month', 'history-year']
        #
        _idx = self.topbar.parent.updateHistory(+1, None, True)
        self.icon_name = _icons[_idx]
        _icon = QIcon('./res/svg/{}.svg'.format(self.icon_name)).pixmap(
            QSize(*CFG.SIZE_ICON(self)))
        self.setPixmap(_icon)
        pass

    def collapseIconEvent(self):
        self.topbar.parent.parent.w_editor.toggleHistoryWidget()
        pass

    pass