예제 #1
1
파일: progress.py 프로젝트: zwlistu/calibre
class BlockingBusy(QDialog):

    def __init__(self, msg, parent=None, window_title=_('Working')):
        QDialog.__init__(self, parent)

        self._layout = QVBoxLayout()
        self.setLayout(self._layout)
        self.msg = QLabel(msg)
        # self.msg.setWordWrap(True)
        self.font = QFont()
        self.font.setPointSize(self.font.pointSize() + 8)
        self.msg.setFont(self.font)
        self.pi = ProgressIndicator(self)
        self.pi.setDisplaySize(100)
        self._layout.addWidget(self.pi, 0, Qt.AlignHCenter)
        self._layout.addSpacing(15)
        self._layout.addWidget(self.msg, 0, Qt.AlignHCenter)
        self.start()
        self.setWindowTitle(window_title)
        self.resize(self.sizeHint())

    def start(self):
        self.pi.startAnimation()

    def stop(self):
        self.pi.stopAnimation()

    def accept(self):
        self.stop()
        return QDialog.accept(self)

    def reject(self):
        pass  # Cannot cancel this dialog
예제 #2
0
파일: save.py 프로젝트: Gondulf/calibre
class SaveWidget(QWidget):
    def __init__(self, parent=None):
        QWidget.__init__(self, parent)
        self.l = l = QHBoxLayout(self)
        self.setLayout(l)
        self.label = QLabel('')
        self.pi = ProgressIndicator(self, 24)
        l.addWidget(self.label)
        l.addWidget(self.pi)
        l.setContentsMargins(0, 0, 0, 0)
        self.pi.setVisible(False)
        self.stop()

    def start(self):
        self.pi.setVisible(True)
        self.pi.startAnimation()
        self.label.setText(_('Saving...'))

    def stop(self):
        self.pi.setVisible(False)
        self.pi.stopAnimation()
        self.label.setText(_('Saved'))
예제 #3
0
파일: save.py 프로젝트: Gondulf/calibre
class SaveWidget(QWidget):

    def __init__(self, parent=None):
        QWidget.__init__(self, parent)
        self.l = l = QHBoxLayout(self)
        self.setLayout(l)
        self.label = QLabel('')
        self.pi = ProgressIndicator(self, 24)
        l.addWidget(self.label)
        l.addWidget(self.pi)
        l.setContentsMargins(0, 0, 0, 0)
        self.pi.setVisible(False)
        self.stop()

    def start(self):
        self.pi.setVisible(True)
        self.pi.startAnimation()
        self.label.setText(_('Saving...'))

    def stop(self):
        self.pi.setVisible(False)
        self.pi.stopAnimation()
        self.label.setText(_('Saved'))
예제 #4
0
class MyBlockingBusy(QDialog):

    NORMAL = 0
    REQUESTED = 1
    ACKNOWLEDGED = 2

    def __init__(self,
                 gui,
                 msg,
                 size=100,
                 window_title='Marvin XD',
                 show_cancel=False,
                 on_top=False):
        flags = Qt.FramelessWindowHint
        if on_top:
            flags = Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint
        QDialog.__init__(self, gui, flags)

        self._layout = QVBoxLayout()
        self.setLayout(self._layout)
        self.cancel_status = 0
        self.is_running = False

        # Add the spinner
        self.pi = ProgressIndicator(self)
        self.pi.setDisplaySize(size)
        self._layout.addSpacing(15)
        self._layout.addWidget(self.pi, 0, Qt.AlignHCenter)
        self._layout.addSpacing(15)

        # Fiddle with the message
        self.msg = QLabel(msg)
        #self.msg.setWordWrap(True)
        self.font = QFont()
        self.font.setPointSize(self.font.pointSize() + 2)
        self.msg.setFont(self.font)
        self._layout.addWidget(self.msg, 0, Qt.AlignHCenter)
        sp = QSizePolicy()
        sp.setHorizontalStretch(True)
        sp.setVerticalStretch(False)
        sp.setHeightForWidth(False)
        self.msg.setSizePolicy(sp)
        self.msg.setMinimumHeight(self.font.pointSize() + 8)

        self._layout.addSpacing(15)

        if show_cancel:
            self.bb = QDialogButtonBox()
            self.cancel_button = QPushButton(QIcon(I('window-close.png')),
                                             'Cancel')
            self.bb.addButton(self.cancel_button, self.bb.RejectRole)
            self.bb.clicked.connect(self.button_handler)
            self._layout.addWidget(self.bb)

        self.setWindowTitle(window_title)
        self.resize(self.sizeHint())

    def accept(self):
        self.stop()
        return QDialog.accept(self)

    def button_handler(self, button):
        '''
        Only change cancel_status from NORMAL to REQUESTED
        '''
        if self.bb.buttonRole(button) == QDialogButtonBox.RejectRole:
            if self.cancel_status == self.NORMAL:
                self.cancel_status = self.REQUESTED
                self.cancel_button.setEnabled(False)

    def reject(self):
        '''
        Cannot cancel this dialog manually
        '''
        pass

    def set_text(self, text):
        self.msg.setText(text)

    def start(self):
        self.is_running = True
        self.pi.startAnimation()

    def stop(self):
        self.is_running = False
        self.pi.stopAnimation()
예제 #5
0
class MyBlockingBusy(QDialog):  # {{{

    all_done = pyqtSignal()

    def __init__(self, args, ids, db, refresh_books, cc_widgets, s_r_func, do_sr, sr_calls, parent=None, window_title=_('Working')):
        QDialog.__init__(self, parent)

        self._layout =  l = QVBoxLayout()
        self.setLayout(l)

        self.msg = QLabel(_('Processing %d books, please wait...') % len(ids))
        self.font = QFont()
        self.font.setPointSize(self.font.pointSize() + 8)
        self.msg.setFont(self.font)
        self.pi = ProgressIndicator(self)
        self.pi.setDisplaySize(100)
        self._layout.addWidget(self.pi, 0, Qt.AlignHCenter)
        self._layout.addSpacing(15)
        self._layout.addWidget(self.msg, 0, Qt.AlignHCenter)
        self.setWindowTitle(window_title + '...')
        self.setMinimumWidth(200)
        self.resize(self.sizeHint())
        self.error = None
        self.all_done.connect(self.on_all_done, type=Qt.QueuedConnection)
        self.args, self.ids = args, ids
        self.db, self.cc_widgets = db, cc_widgets
        self.s_r_func = FunctionDispatcher(s_r_func)
        self.do_sr = do_sr
        self.sr_calls = sr_calls
        self.refresh_books = refresh_books

    def accept(self):
        pass

    def reject(self):
        pass

    def on_all_done(self):
        if not self.error:
            # The cc widgets can only be accessed in the GUI thread
            try:
                for w in self.cc_widgets:
                    w.commit(self.ids)
            except Exception as err:
                import traceback
                self.error = (err, traceback.format_exc())
        self.pi.stopAnimation()
        QDialog.accept(self)

    def exec_(self):
        self.thread = Thread(target=self.do_it)
        self.thread.start()
        self.pi.startAnimation()
        return QDialog.exec_(self)

    def do_it(self):
        try:
            self.do_all()
        except Exception as err:
            import traceback
            try:
                err = unicode(err)
            except:
                err = repr(err)
            self.error = (err, traceback.format_exc())

        self.all_done.emit()

    def do_all(self):
        cache = self.db.new_api
        args = self.args

        # Title and authors
        if args.do_swap_ta:
            title_map = cache.all_field_for('title', self.ids)
            authors_map = cache.all_field_for('authors', self.ids)
            def new_title(authors):
                ans = authors_to_string(authors)
                return titlecase(ans) if args.do_title_case else ans
            new_title_map = {bid:new_title(authors) for bid, authors in authors_map.iteritems()}
            new_authors_map = {bid:string_to_authors(title) for bid, title in title_map.iteritems()}
            cache.set_field('authors', new_authors_map)
            cache.set_field('title', new_title_map)

        if args.do_title_case and not args.do_swap_ta:
            title_map = cache.all_field_for('title', self.ids)
            cache.set_field('title', {bid:titlecase(title) for bid, title in title_map.iteritems()})

        if args.do_title_sort:
            lang_map = cache.all_field_for('languages', self.ids)
            title_map = cache.all_field_for('title', self.ids)
            def get_sort(book_id):
                if args.languages:
                    lang = args.languages[0]
                else:
                    try:
                        lang = lang_map[book_id][0]
                    except (KeyError, IndexError, TypeError, AttributeError):
                        lang = 'eng'
                return title_sort(title_map[book_id], lang=lang)
            cache.set_field('sort', {bid:get_sort(bid) for bid in self.ids})

        if args.au:
            authors = string_to_authors(args.au)
            cache.set_field('authors', {bid:authors for bid in self.ids})

        if args.do_auto_author:
            aus_map = cache.author_sort_strings_for_books(self.ids)
            cache.set_field('author_sort', {book_id:' & '.join(aus_map[book_id]) for book_id in aus_map})

        if args.aus and args.do_aus:
            cache.set_field('author_sort', {bid:args.aus for bid in self.ids})

        # Covers
        if args.cover_action == 'remove':
            cache.set_cover({bid:None for bid in self.ids})
        elif args.cover_action == 'generate':
            from calibre.ebooks import calibre_cover
            from calibre.ebooks.metadata import fmt_sidx
            from calibre.gui2 import config
            for book_id in self.ids:
                mi = self.db.get_metadata(book_id, index_is_id=True)
                series_string = None
                if mi.series:
                    series_string = _('Book %(sidx)s of %(series)s')%dict(
                        sidx=fmt_sidx(mi.series_index,
                        use_roman=config['use_roman_numerals_for_series_number']),
                        series=mi.series)

                cdata = calibre_cover(mi.title, mi.format_field('authors')[-1],
                        series_string=series_string)
                cache.set_cover({book_id:cdata})
        elif args.cover_action == 'fromfmt':
            for book_id in self.ids:
                fmts = cache.formats(book_id, verify_formats=False)
                if fmts:
                    covers = []
                    for fmt in fmts:
                        fmtf = cache.format(book_id, fmt, as_file=True)
                        if fmtf is None:
                            continue
                        cdata, area = get_cover_data(fmtf, fmt)
                        if cdata:
                            covers.append((cdata, area))
                    covers.sort(key=lambda x: x[1])
                    if covers:
                        cache.set_cover({book_id:covers[-1][0]})
        elif args.cover_action == 'trim':
            from calibre.utils.magick import Image
            for book_id in self.ids:
                cdata = cache.cover(book_id)
                if cdata:
                    im = Image()
                    im.load(cdata)
                    im.trim(tweaks['cover_trim_fuzz_value'])
                    cdata = im.export('jpg')
                    cache.set_cover({book_id:cdata})
        elif args.cover_action == 'clone':
            cdata = None
            for book_id in self.ids:
                cdata = cache.cover(book_id)
                if cdata:
                    break
            if cdata:
                cache.set_cover({bid:cdata for bid in self.ids if bid != book_id})

        # Formats
        if args.do_remove_format:
            cache.remove_formats({bid:(args.remove_format,) for bid in self.ids})

        if args.restore_original:
            for book_id in self.ids:
                formats = cache.formats(book_id)
                originals = tuple(x.upper() for x in formats if x.upper().startswith('ORIGINAL_'))
                for ofmt in originals:
                    cache.restore_original_format(book_id, ofmt)

        # Various fields
        if args.rating != -1:
            cache.set_field('rating', {bid:args.rating*2 for bid in self.ids})

        if args.clear_pub:
            cache.set_field('publisher', {bid:'' for bid in self.ids})

        if args.pub:
            cache.set_field('publisher', {bid:args.pub for bid in self.ids})

        if args.clear_series:
            cache.set_field('series', {bid:'' for bid in self.ids})

        if args.pubdate is not None:
            cache.set_field('pubdate', {bid:args.pubdate for bid in self.ids})

        if args.adddate is not None:
            cache.set_field('timestamp', {bid:args.adddate for bid in self.ids})

        if args.do_series:
            sval = args.series_start_value if args.do_series_restart else cache.get_next_series_num_for(args.series, current_indices=True)
            cache.set_field('series', {bid:args.series for bid in self.ids})
            if not args.series:
                cache.set_field('series_index', {bid:1.0 for bid in self.ids})
            else:
                def next_series_num(bid, i):
                    if args.do_series_restart:
                        return sval + i
                    next_num = _get_next_series_num_for_list(sorted(sval.itervalues()), unwrap=False)
                    sval[bid] = next_num
                    return next_num

                smap = {bid:next_series_num(bid, i) for i, bid in enumerate(self.ids)}
                if args.do_autonumber:
                    cache.set_field('series_index', smap)
                elif tweaks['series_index_auto_increment'] != 'no_change':
                    cache.set_field('series_index', {bid:1.0 for bid in self.ids})

        if args.comments is not null:
            cache.set_field('comments', {bid:args.comments for bid in self.ids})

        if args.do_remove_conv:
            cache.delete_conversion_options(self.ids)

        if args.clear_languages:
            cache.set_field('languages', {bid:() for bid in self.ids})
        elif args.languages:
            cache.set_field('languages', {bid:args.languages for bid in self.ids})

        if args.remove_all:
            cache.set_field('tags', {bid:() for bid in self.ids})
        if args.add or args.remove:
            self.db.bulk_modify_tags(self.ids, add=args.add, remove=args.remove)

        if self.do_sr:
            for book_id in self.ids:
                self.s_r_func(book_id)
            if self.sr_calls:
                for field, book_id_val_map in self.sr_calls.iteritems():
                    self.refresh_books.update(self.db.new_api.set_field(field, book_id_val_map))
예제 #6
0
class MyBlockingBusy(QDialog):

    NORMAL = 0
    REQUESTED = 1
    ACKNOWLEDGED = 2

    def __init__(self, gui, msg, size=100, window_title="Marvin XD", show_cancel=False, on_top=False):
        flags = Qt.FramelessWindowHint
        if on_top:
            flags = Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint
        QDialog.__init__(self, gui, flags)

        self._layout = QVBoxLayout()
        self.setLayout(self._layout)
        self.cancel_status = 0
        self.is_running = False

        # Add the spinner
        self.pi = ProgressIndicator(self)
        self.pi.setDisplaySize(size)
        self._layout.addSpacing(15)
        self._layout.addWidget(self.pi, 0, Qt.AlignHCenter)
        self._layout.addSpacing(15)

        # Fiddle with the message
        self.msg = QLabel(msg)
        # self.msg.setWordWrap(True)
        self.font = QFont()
        self.font.setPointSize(self.font.pointSize() + 2)
        self.msg.setFont(self.font)
        self._layout.addWidget(self.msg, 0, Qt.AlignHCenter)
        sp = QSizePolicy()
        sp.setHorizontalStretch(True)
        sp.setVerticalStretch(False)
        sp.setHeightForWidth(False)
        self.msg.setSizePolicy(sp)
        self.msg.setMinimumHeight(self.font.pointSize() + 8)

        self._layout.addSpacing(15)

        if show_cancel:
            self.bb = QDialogButtonBox()
            self.cancel_button = QPushButton(QIcon(I("window-close.png")), "Cancel")
            self.bb.addButton(self.cancel_button, self.bb.RejectRole)
            self.bb.clicked.connect(self.button_handler)
            self._layout.addWidget(self.bb)

        self.setWindowTitle(window_title)
        self.resize(self.sizeHint())

    def accept(self):
        self.stop()
        return QDialog.accept(self)

    def button_handler(self, button):
        """
        Only change cancel_status from NORMAL to REQUESTED
        """
        if self.bb.buttonRole(button) == QDialogButtonBox.RejectRole:
            if self.cancel_status == self.NORMAL:
                self.cancel_status = self.REQUESTED
                self.cancel_button.setEnabled(False)

    def reject(self):
        """
        Cannot cancel this dialog manually
        """
        pass

    def set_text(self, text):
        self.msg.setText(text)

    def start(self):
        self.is_running = True
        self.pi.startAnimation()

    def stop(self):
        self.is_running = False
        self.pi.stopAnimation()
예제 #7
0
파일: job.py 프로젝트: AtulKumar2/calibre
class BlockingJob(QWidget):

    def __init__(self, parent):
        QWidget.__init__(self, parent)
        l = QVBoxLayout()
        self.setLayout(l)
        l.addStretch(10)
        self.pi = ProgressIndicator(self, 128)
        l.addWidget(self.pi, alignment=Qt.AlignHCenter)
        self.dummy = QLabel('<h2>\xa0')
        l.addSpacing(10)
        l.addWidget(self.dummy, alignment=Qt.AlignHCenter)
        l.addStretch(10)
        self.setVisible(False)
        self.text = ''
        self.setFocusPolicy(Qt.NoFocus)

    def start(self):
        self.setGeometry(0, 0, self.parent().width(), self.parent().height())
        self.setVisible(True)
        # Prevent any actions from being triggerred by key presses
        self.parent().setEnabled(False)
        self.raise_()
        self.setFocus(Qt.OtherFocusReason)
        self.pi.startAnimation()
        QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))

    def stop(self):
        QApplication.restoreOverrideCursor()
        self.pi.stopAnimation()
        self.setVisible(False)
        self.parent().setEnabled(True)
        # The following line is needed on OS X, because of this bug:
        # https://bugreports.qt-project.org/browse/QTBUG-34371 it causes
        # keyboard events to no longer work
        self.parent().setFocus(Qt.OtherFocusReason)

    def job_done(self, callback, job):
        del job.callback
        self.stop()
        callback(job)

    def paintEvent(self, ev):
        br = ev.region().boundingRect()
        p = QPainter(self)
        p.setOpacity(0.2)
        p.fillRect(br, QBrush(self.palette().text()))
        p.end()
        QWidget.paintEvent(self, ev)
        p = QPainter(self)
        p.setClipRect(br)
        f = p.font()
        f.setBold(True)
        f.setPointSize(20)
        p.setFont(f)
        p.setPen(Qt.SolidLine)
        r = QRect(0, self.dummy.geometry().top() + 10, self.geometry().width(), 150)
        p.drawText(r, Qt.AlignHCenter | Qt.AlignTop | Qt.TextSingleLine, self.text)
        p.end()

    def set_msg(self, text):
        self.text = text

    def __call__(self, name, user_text, callback, function, *args, **kwargs):
        ' Run a job that blocks the GUI providing some feedback to the user '
        self.set_msg(user_text)
        job = LongJob(name, user_text, Dispatcher(partial(self.job_done, callback)), function, *args, **kwargs)
        job.start()
        self.start()
예제 #8
0
파일: jobs.py 프로젝트: ziyuyouming/calibre
class JobsButton(QWidget):  # {{{

    tray_tooltip_updated = pyqtSignal(object)

    def __init__(self, parent=None):
        QWidget.__init__(self, parent)
        self.num_jobs = 0
        self.mouse_over = False
        self.pi = ProgressIndicator(
            self,
            self.style().pixelMetric(QStyle.PixelMetric.PM_ToolBarIconSize))
        self._jobs = QLabel('')
        self._jobs.mouseReleaseEvent = self.mouseReleaseEvent
        self.update_label()
        self.shortcut = 'Alt+Shift+J'

        self.l = l = QHBoxLayout(self)
        l.setSpacing(3)
        l.addWidget(self.pi)
        l.addWidget(self._jobs)
        m = self.style().pixelMetric(QStyle.PixelMetric.PM_DefaultFrameWidth)
        self.layout().setContentsMargins(m, m, m, m)
        self._jobs.setSizePolicy(QSizePolicy.Policy.Minimum,
                                 QSizePolicy.Policy.Minimum)
        self.setCursor(Qt.CursorShape.PointingHandCursor)
        b = _('Click to see list of jobs')
        self.setToolTip(b + ' [%s]' % self.shortcut)
        self.action_toggle = QAction(b, parent)
        parent.addAction(self.action_toggle)
        self.action_toggle.triggered.connect(self.toggle)
        if hasattr(parent, 'keyboard'):
            parent.keyboard.register_shortcut('toggle jobs list',
                                              _('Show/hide the Jobs List'),
                                              default_keys=(self.shortcut, ),
                                              action=self.action_toggle)

    def update_label(self):
        n = self.jobs()
        prefix = '<b>' if n > 0 else ''
        self._jobs.setText(prefix + _('Jobs:') + ' {} '.format(n))

    def event(self, ev):
        m = None
        et = ev.type()
        if et == ev.Enter:
            m = True
        elif et == ev.Leave:
            m = False
        if m is not None and m != self.mouse_over:
            self.mouse_over = m
            self.update()
        return QWidget.event(self, ev)

    def initialize(self, jobs_dialog, job_manager):
        self.jobs_dialog = jobs_dialog
        job_manager.job_added.connect(self.job_added)
        job_manager.job_done.connect(self.job_done)
        self.jobs_dialog.addAction(self.action_toggle)

    def mouseReleaseEvent(self, event):
        self.toggle()

    def toggle(self, *args):
        if self.jobs_dialog.isVisible():
            self.jobs_dialog.hide()
        else:
            self.jobs_dialog.show()

    @property
    def is_running(self):
        return self.pi.isAnimated()

    def start(self):
        self.pi.startAnimation()

    def stop(self):
        self.pi.stopAnimation()

    def jobs(self):
        return self.num_jobs

    def tray_tooltip(self, num=0):
        if num == 0:
            text = _('No running jobs')
        elif num == 1:
            text = _('One running job')
        else:
            text = _('%d running jobs') % num
        if not (islinux or isbsd):
            text = 'calibre: ' + text
        return text

    def job_added(self, nnum):
        self.num_jobs = nnum
        self.update_label()
        self.start()
        self.tray_tooltip_updated.emit(self.tray_tooltip(nnum))

    def job_done(self, nnum):
        self.num_jobs = nnum
        self.update_label()
        if nnum == 0:
            self.no_more_jobs()
        self.tray_tooltip_updated.emit(self.tray_tooltip(nnum))

    def no_more_jobs(self):
        if self.is_running:
            self.stop()
            QCoreApplication.instance().alert(self, 5000)

    def paintEvent(self, ev):
        if self.mouse_over:
            p = QPainter(self)
            tool = QStyleOption()
            tool.rect = self.rect()
            tool.state = QStyle.StateFlag.State_Raised | QStyle.StateFlag.State_Active | QStyle.StateFlag.State_MouseOver
            s = self.style()
            s.drawPrimitive(QStyle.PrimitiveElement.PE_PanelButtonTool, tool,
                            p, self)
            p.end()
        QWidget.paintEvent(self, ev)
예제 #9
0
class BlockingJob(QWidget):
    def __init__(self, parent):
        QWidget.__init__(self, parent)
        l = QVBoxLayout()
        self.setLayout(l)
        l.addStretch(10)
        self.pi = ProgressIndicator(self, 128)
        l.addWidget(self.pi, alignment=Qt.AlignmentFlag.AlignHCenter)
        self.dummy = QLabel('<h2>\xa0')
        l.addSpacing(10)
        l.addWidget(self.dummy, alignment=Qt.AlignmentFlag.AlignHCenter)
        l.addStretch(10)
        self.setVisible(False)
        self.text = ''
        self.setFocusPolicy(Qt.FocusPolicy.NoFocus)

    def start(self):
        self.setGeometry(0, 0, self.parent().width(), self.parent().height())
        self.setVisible(True)
        # Prevent any actions from being triggered by key presses
        self.parent().setEnabled(False)
        self.raise_()
        self.setFocus(Qt.FocusReason.OtherFocusReason)
        self.pi.startAnimation()
        QApplication.setOverrideCursor(QCursor(Qt.CursorShape.WaitCursor))

    def stop(self):
        QApplication.restoreOverrideCursor()
        self.pi.stopAnimation()
        self.setVisible(False)
        self.parent().setEnabled(True)
        # The following line is needed on OS X, because of this bug:
        # https://bugreports.qt-project.org/browse/QTBUG-34371 it causes
        # keyboard events to no longer work
        self.parent().setFocus(Qt.FocusReason.OtherFocusReason)

    def job_done(self, callback, job):
        del job.callback
        self.stop()
        callback(job)

    def paintEvent(self, ev):
        br = ev.region().boundingRect()
        p = QPainter(self)
        p.setOpacity(0.2)
        p.fillRect(br, QBrush(self.palette().text()))
        p.end()
        QWidget.paintEvent(self, ev)
        p = QPainter(self)
        p.setClipRect(br)
        f = p.font()
        f.setBold(True)
        f.setPointSize(20)
        p.setFont(f)
        p.setPen(Qt.PenStyle.SolidLine)
        r = QRect(0,
                  self.dummy.geometry().top() + 10,
                  self.geometry().width(), 150)
        p.drawText(
            r, Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignTop
            | Qt.TextFlag.TextSingleLine, self.text)
        p.end()

    def set_msg(self, text):
        self.text = text

    def __call__(self, name, user_text, callback, function, *args, **kwargs):
        ' Run a job that blocks the GUI providing some feedback to the user '
        self.set_msg(user_text)
        job = LongJob(name, user_text,
                      Dispatcher(partial(self.job_done, callback)), function,
                      *args, **kwargs)
        job.start()
        self.start()
예제 #10
0
파일: jobs.py 프로젝트: 089git/calibre
class JobsButton(QFrame):  # {{{

    def __init__(self, horizontal=False, size=48, parent=None):
        QFrame.__init__(self, parent)
        if horizontal:
            size = 24
        self.pi = ProgressIndicator(self, size)
        self._jobs = QLabel('<b>'+_('Jobs:')+' 0')
        self._jobs.mouseReleaseEvent = self.mouseReleaseEvent
        self.shortcut = _('Shift+Alt+J')

        if horizontal:
            self.setLayout(QHBoxLayout())
            self.layout().setDirection(self.layout().RightToLeft)
        else:
            self.setLayout(QVBoxLayout())
            self._jobs.setAlignment(Qt.AlignHCenter|Qt.AlignBottom)

        self.layout().addWidget(self.pi)
        self.layout().addWidget(self._jobs)
        if not horizontal:
            self.layout().setAlignment(self._jobs, Qt.AlignHCenter)
        self._jobs.setMargin(0)
        self.layout().setMargin(0)
        self._jobs.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
        self.setCursor(Qt.PointingHandCursor)
        b = _('Click to see list of jobs')
        self.setToolTip(b + u' (%s)'%self.shortcut)
        self.action_toggle = QAction(b, parent)
        parent.addAction(self.action_toggle)
        self.action_toggle.setShortcut(self.shortcut)
        self.action_toggle.triggered.connect(self.toggle)

    def initialize(self, jobs_dialog, job_manager):
        self.jobs_dialog = jobs_dialog
        job_manager.job_added.connect(self.job_added)
        job_manager.job_done.connect(self.job_done)
        self.jobs_dialog.addAction(self.action_toggle)

    def mouseReleaseEvent(self, event):
        self.toggle()

    def toggle(self, *args):
        if self.jobs_dialog.isVisible():
            self.jobs_dialog.hide()
        else:
            self.jobs_dialog.show()

    @property
    def is_running(self):
        return self.pi.isAnimated()

    def start(self):
        self.pi.startAnimation()

    def stop(self):
        self.pi.stopAnimation()

    def jobs(self):
        src = unicode(self._jobs.text())
        return int(re.search(r'\d+', src).group())

    def job_added(self, nnum):
        jobs = self._jobs
        src = unicode(jobs.text())
        num = self.jobs()
        text = src.replace(str(num), str(nnum))
        jobs.setText(text)
        self.start()

    def job_done(self, nnum):
        jobs = self._jobs
        src = unicode(jobs.text())
        num = self.jobs()
        text = src.replace(str(num), str(nnum))
        jobs.setText(text)
        if nnum == 0:
            self.no_more_jobs()

    def no_more_jobs(self):
        if self.is_running:
            self.stop()
            QCoreApplication.instance().alert(self, 5000)
예제 #11
0
파일: job.py 프로젝트: bibihoma/calibre
class BlockingJob(QWidget):

    def __init__(self, parent):
        QWidget.__init__(self, parent)
        l = QVBoxLayout()
        self.setLayout(l)
        l.addStretch(10)
        self.pi = ProgressIndicator(self, 128)
        l.addWidget(self.pi, alignment=Qt.AlignHCenter)
        self.dummy = QLabel('<h2>\xa0')
        l.addSpacing(10)
        l.addWidget(self.dummy, alignment=Qt.AlignHCenter)
        l.addStretch(10)
        self.setVisible(False)
        self.text = ''

    def start(self):
        self.setGeometry(0, 0, self.parent().width(), self.parent().height())
        self.setVisible(True)
        # Prevent any actions from being triggerred by key presses
        self.parent().setEnabled(False)
        self.raise_()
        self.setFocus(Qt.OtherFocusReason)
        self.pi.startAnimation()

    def stop(self):
        self.pi.stopAnimation()
        self.setVisible(False)
        self.parent().setEnabled(True)

    def job_done(self, callback, job):
        del job.callback
        self.stop()
        callback(job)

    def paintEvent(self, ev):
        br = ev.region().boundingRect()
        p = QPainter(self)
        p.setOpacity(0.2)
        p.fillRect(br, QBrush(self.palette().text()))
        p.end()
        QWidget.paintEvent(self, ev)
        p = QPainter(self)
        p.setClipRect(br)
        f = p.font()
        f.setBold(True)
        f.setPointSize(20)
        p.setFont(f)
        p.setPen(Qt.SolidLine)
        r = QRect(0, self.dummy.geometry().top() + 10, self.geometry().width(), 150)
        p.drawText(r, Qt.AlignHCenter | Qt.AlignTop | Qt.TextSingleLine, self.text)
        p.end()

    def eventFilter(self, obj, ev):
        if ev.type() in (ev.KeyPress, ev.KeyRelease):
            return True
        return QWidget.eventFilter(self, obj, ev)

    def set_msg(self, text):
        self.text = text

    def __call__(self, name, user_text, callback, function, *args, **kwargs):
        ' Run a job that blocks the GUI providing some feedback to the user '
        self.set_msg(user_text)
        job = LongJob(name, user_text, Dispatcher(partial(self.job_done, callback)), function, *args, **kwargs)
        job.start()
        self.start()
예제 #12
0
파일: search.py 프로젝트: BobPyron/calibre
class SearchDialog(QDialog, Ui_Dialog):

    def __init__(self, gui, parent=None, query=''):
        QDialog.__init__(self, parent)
        self.setupUi(self)

        self.config = JSONConfig('store/search')
        self.search_title.initialize('store_search_search_title')
        self.search_author.initialize('store_search_search_author')
        self.search_edit.initialize('store_search_search')

        # Loads variables that store various settings.
        # This needs to be called soon in __init__ because
        # the variables it sets up are used later.
        self.load_settings()

        self.gui = gui

        # Setup our worker threads.
        self.search_pool = SearchThreadPool(self.search_thread_count)
        self.cache_pool = CacheUpdateThreadPool(self.cache_thread_count)
        self.results_view.model().cover_pool.set_thread_count(self.cover_thread_count)
        self.results_view.model().details_pool.set_thread_count(self.details_thread_count)
        self.results_view.setCursor(Qt.PointingHandCursor)

        # Check for results and hung threads.
        self.checker = QTimer()
        self.progress_checker = QTimer()
        self.hang_check = 0

        # Update store caches silently.
        for p in self.gui.istores.values():
            self.cache_pool.add_task(p, self.timeout)

        self.store_checks = {}
        self.setup_store_checks()

        # Set the search query
        if isinstance(query, (str, unicode)):
            self.search_edit.setText(query)
        elif isinstance(query, dict):
            if 'author' in query:
                self.search_author.setText(query['author'])
            if 'title' in query:
                self.search_title.setText(query['title'])
        # Title
        self.search_title.setSizeAdjustPolicy(QComboBox.AdjustToMinimumContentsLengthWithIcon)
        self.search_title.setMinimumContentsLength(25)
        # Author
        self.search_author.setSizeAdjustPolicy(QComboBox.AdjustToMinimumContentsLengthWithIcon)
        self.search_author.setMinimumContentsLength(25)
        # Keyword
        self.search_edit.setSizeAdjustPolicy(QComboBox.AdjustToMinimumContentsLengthWithIcon)
        self.search_edit.setMinimumContentsLength(25)

        # Create and add the progress indicator
        self.pi = ProgressIndicator(self, 24)
        self.button_layout.takeAt(0)
        self.button_layout.setAlignment(Qt.AlignCenter)
        self.button_layout.insertWidget(0, self.pi, 0, Qt.AlignCenter)

        self.adv_search_button.setIcon(QIcon(I('search.png')))
        self.configure.setIcon(QIcon(I('config.png')))

        self.adv_search_button.clicked.connect(self.build_adv_search)
        self.search.clicked.connect(self.do_search)
        self.checker.timeout.connect(self.get_results)
        self.progress_checker.timeout.connect(self.check_progress)
        self.results_view.activated.connect(self.result_item_activated)
        self.results_view.download_requested.connect(self.download_book)
        self.results_view.open_requested.connect(self.open_store)
        self.results_view.model().total_changed.connect(self.update_book_total)
        self.select_all_stores.clicked.connect(self.stores_select_all)
        self.select_invert_stores.clicked.connect(self.stores_select_invert)
        self.select_none_stores.clicked.connect(self.stores_select_none)
        self.configure.clicked.connect(self.do_config)
        self.finished.connect(self.dialog_closed)

        self.progress_checker.start(100)

        self.restore_state()

    def setup_store_checks(self):
        first_run = self.config.get('first_run', True)

        # Add check boxes for each store so the user
        # can disable searching specific stores on a
        # per search basis.
        existing = {}
        for n in self.store_checks:
            existing[n] = self.store_checks[n].isChecked()

        self.store_checks = {}

        stores_check_widget = QWidget()
        store_list_layout = QGridLayout()
        stores_check_widget.setLayout(store_list_layout)

        icon = QIcon(I('donate.png'))
        for i, x in enumerate(sorted(self.gui.istores.keys(), key=lambda x: x.lower())):
            cbox = QCheckBox(x)
            cbox.setChecked(existing.get(x, first_run))
            store_list_layout.addWidget(cbox, i, 0, 1, 1)
            if self.gui.istores[x].base_plugin.affiliate:
                iw = QLabel(self)
                iw.setToolTip('<p>' + _('Buying from this store supports the calibre developer: %s</p>') % self.gui.istores[x].base_plugin.author + '</p>')
                iw.setPixmap(icon.pixmap(16, 16))
                store_list_layout.addWidget(iw, i, 1, 1, 1)
            self.store_checks[x] = cbox
        store_list_layout.setRowStretch(store_list_layout.rowCount(), 10)
        self.store_list.setWidget(stores_check_widget)

        self.config['first_run'] = False

    def build_adv_search(self):
        adv = AdvSearchBuilderDialog(self)
        if adv.exec_() == QDialog.Accepted:
            self.search_edit.setText(adv.search_string())

    def resize_columns(self):
        total = 600
        # Cover
        self.results_view.setColumnWidth(0, 85)
        total = total - 85
        # Title / Author
        self.results_view.setColumnWidth(1,int(total*.40))
        # Price
        self.results_view.setColumnWidth(2,int(total*.12))
        # DRM
        self.results_view.setColumnWidth(3, int(total*.15))
        # Store / Formats
        self.results_view.setColumnWidth(4, int(total*.25))
        # Download
        self.results_view.setColumnWidth(5, 20)
        # Affiliate
        self.results_view.setColumnWidth(6, 20)

    def do_search(self):
        # Stop all running threads.
        self.checker.stop()
        self.search_pool.abort()
        # Clear the visible results.
        self.results_view.model().clear_results()

        # Don't start a search if there is nothing to search for.
        query = []
        if self.search_title.text():
            query.append(u'title2:"~%s"' % unicode(self.search_title.text()).replace('"', ' '))
        if self.search_author.text():
            query.append(u'author2:"%s"' % unicode(self.search_author.text()).replace('"', ' '))
        if self.search_edit.text():
            query.append(unicode(self.search_edit.text()))
        query = " ".join(query)
        if not query.strip():
            error_dialog(self, _('No query'),
                        _('You must enter a title, author or keyword to'
                          ' search for.'), show=True)
            return
        # Give the query to the results model so it can do
        # futher filtering.
        self.results_view.model().set_query(query)

        # Plugins are in random order that does not change.
        # Randomize the ord of the plugin names every time
        # there is a search. This way plugins closer
        # to a don't have an unfair advantage over
        # plugins further from a.
        store_names = self.store_checks.keys()
        if not store_names:
            return
        # Remove all of our internal filtering logic from the query.
        query = self.clean_query(query)
        shuffle(store_names)
        # Add plugins that the user has checked to the search pool's work queue.
        self.gui.istores.join(4.0) # Wait for updated plugins to load
        for n in store_names:
            if self.store_checks[n].isChecked():
                self.search_pool.add_task(query, n, self.gui.istores[n], self.max_results, self.timeout)
        self.hang_check = 0
        self.checker.start(100)
        self.pi.startAnimation()

    def clean_query(self, query):
        query = query.lower()
        # Remove control modifiers.
        query = query.replace('\\', '')
        query = query.replace('!', '')
        query = query.replace('=', '')
        query = query.replace('~', '')
        query = query.replace('>', '')
        query = query.replace('<', '')
        # Remove the prefix.
        for loc in ('all', 'author', 'author2', 'authors', 'title', 'title2'):
            query = re.sub(r'%s:"(?P<a>[^\s"]+)"' % loc, '\g<a>', query)
            query = query.replace('%s:' % loc, '')
        # Remove the prefix and search text.
        for loc in ('cover', 'download', 'downloads', 'drm', 'format', 'formats', 'price', 'store'):
            query = re.sub(r'%s:"[^"]"' % loc, '', query)
            query = re.sub(r'%s:[^\s]*' % loc, '', query)
        # Remove logic.
        query = re.sub(r'(^|\s)(and|not|or|a|the|is|of)(\s|$)', ' ', query)
        # Remove "
        query = query.replace('"', '')
        # Remove excess whitespace.
        query = re.sub(r'\s{2,}', ' ', query)
        query = query.strip()
        return query.encode('utf-8')

    def save_state(self):
        self.config['geometry'] = bytearray(self.saveGeometry())
        self.config['store_splitter_state'] = bytearray(self.store_splitter.saveState())
        self.config['results_view_column_width'] = [self.results_view.columnWidth(i) for i in range(self.results_view.model().columnCount())]
        self.config['sort_col'] = self.results_view.model().sort_col
        self.config['sort_order'] = self.results_view.model().sort_order
        self.config['open_external'] = self.open_external.isChecked()

        store_check = {}
        for k, v in self.store_checks.items():
            store_check[k] = v.isChecked()
        self.config['store_checked'] = store_check

    def restore_state(self):
        geometry = self.config.get('geometry', None)
        if geometry:
            self.restoreGeometry(geometry)

        splitter_state = self.config.get('store_splitter_state', None)
        if splitter_state:
            self.store_splitter.restoreState(splitter_state)

        results_cwidth = self.config.get('results_view_column_width', None)
        if results_cwidth:
            for i, x in enumerate(results_cwidth):
                if i >= self.results_view.model().columnCount():
                    break
                self.results_view.setColumnWidth(i, x)
        else:
            self.resize_columns()

        self.open_external.setChecked(self.should_open_external)

        store_check = self.config.get('store_checked', None)
        if store_check:
            for n in store_check:
                if n in self.store_checks:
                    self.store_checks[n].setChecked(store_check[n])

        self.results_view.model().sort_col = self.config.get('sort_col', 2)
        self.results_view.model().sort_order = self.config.get('sort_order', Qt.AscendingOrder)
        self.results_view.header().setSortIndicator(self.results_view.model().sort_col, self.results_view.model().sort_order)

    def load_settings(self):
        # Seconds
        self.timeout = self.config.get('timeout', 75)
        # Milliseconds
        self.hang_time = self.config.get('hang_time', 75) * 1000

        self.max_results = self.config.get('max_results', 15)
        self.should_open_external = self.config.get('open_external', True)

        # Number of threads to run for each type of operation
        self.search_thread_count = self.config.get('search_thread_count', 4)
        self.cache_thread_count = self.config.get('cache_thread_count', 2)
        self.cover_thread_count = self.config.get('cover_thread_count', 2)
        self.details_thread_count = self.config.get('details_thread_count', 4)

    def do_config(self):
        # Save values that need to be synced between the dialog and the
        # search widget.
        self.config['open_external'] = self.open_external.isChecked()

        # Create the config dialog. It's going to put two config widgets
        # into a QTabWidget for displaying all of the settings.
        d = QDialog(self)
        button_box = QDialogButtonBox(QDialogButtonBox.Close)
        v = QVBoxLayout(d)
        button_box.accepted.connect(d.accept)
        button_box.rejected.connect(d.reject)
        d.setWindowTitle(_('Customize get books search'))

        tab_widget = QTabWidget(d)
        v.addWidget(tab_widget)
        v.addWidget(button_box)

        chooser_config_widget = StoreChooserWidget()
        search_config_widget = StoreConfigWidget(self.config)

        tab_widget.addTab(chooser_config_widget, _('Choose stores'))
        tab_widget.addTab(search_config_widget, _('Configure search'))

        # Restore dialog state.
        geometry = self.config.get('config_dialog_geometry', None)
        if geometry:
            d.restoreGeometry(geometry)
        else:
            d.resize(800, 600)
        tab_index = self.config.get('config_dialog_tab_index', 0)
        tab_index = min(tab_index, tab_widget.count() - 1)
        tab_widget.setCurrentIndex(tab_index)

        d.exec_()

        # Save dialog state.
        self.config['config_dialog_geometry'] = bytearray(d.saveGeometry())
        self.config['config_dialog_tab_index'] = tab_widget.currentIndex()

        search_config_widget.save_settings()
        self.config_changed()
        self.gui.load_store_plugins()
        self.setup_store_checks()

    def config_changed(self):
        self.load_settings()

        self.open_external.setChecked(self.should_open_external)
        self.search_pool.set_thread_count(self.search_thread_count)
        self.cache_pool.set_thread_count(self.cache_thread_count)
        self.results_view.model().cover_pool.set_thread_count(self.cover_thread_count)
        self.results_view.model().details_pool.set_thread_count(self.details_thread_count)

    def get_results(self):
        # We only want the search plugins to run
        # a maximum set amount of time before giving up.
        self.hang_check += 1
        if self.hang_check >= self.hang_time:
            self.search_pool.abort()
            self.checker.stop()
        else:
            # Stop the checker if not threads are running.
            if not self.search_pool.threads_running() and not self.search_pool.has_tasks():
                self.checker.stop()

        while self.search_pool.has_results():
            res, store_plugin = self.search_pool.get_result()
            if res:
                self.results_view.model().add_result(res, store_plugin)

        if not self.search_pool.threads_running() and not self.results_view.model().has_results():
            info_dialog(self, _('No matches'), _('Couldn\'t find any books matching your query.'), show=True, show_copy_button=False)

    def update_book_total(self, total):
        self.total.setText('%s' % total)

    def result_item_activated(self, index):
        result = self.results_view.model().get_result(index)

        if result.downloads:
            self.download_book(result)
        else:
            self.open_store(result)

    def download_book(self, result):
        d = ChooseFormatDialog(self, _('Choose format to download to your library.'), result.downloads.keys())
        if d.exec_() == d.Accepted:
            ext = d.format()
            fname = result.title[:60] + '.' + ext.lower()
            fname = ascii_filename(fname)
            self.gui.download_ebook(result.downloads[ext], filename=fname)

    def open_store(self, result):
        self.gui.istores[result.store_name].open(self, result.detail_item, self.open_external.isChecked())

    def check_progress(self):
        if not self.search_pool.threads_running() and not self.results_view.model().cover_pool.threads_running() and not self.results_view.model().details_pool.threads_running():
            self.pi.stopAnimation()
        else:
            if not self.pi.isAnimated():
                self.pi.startAnimation()

    def stores_select_all(self):
        for check in self.store_checks.values():
            check.setChecked(True)

    def stores_select_invert(self):
        for check in self.store_checks.values():
            check.setChecked(not check.isChecked())

    def stores_select_none(self):
        for check in self.store_checks.values():
            check.setChecked(False)

    def dialog_closed(self, result):
        self.results_view.model().closing()
        self.search_pool.abort()
        self.cache_pool.abort()
        self.save_state()

    def exec_(self):
        if unicode(self.search_edit.text()).strip() or unicode(self.search_title.text()).strip() or unicode(self.search_author.text()).strip():
            self.do_search()
        return QDialog.exec_(self)
예제 #13
0
class JobsButton(QFrame):  # {{{

    tray_tooltip_updated = pyqtSignal(object)

    def __init__(self, horizontal=False, size=48, parent=None):
        QFrame.__init__(self, parent)
        if horizontal:
            size = 24
        self.pi = ProgressIndicator(self, size)
        self._jobs = QLabel('<b>' + _('Jobs:') + ' 0')
        self._jobs.mouseReleaseEvent = self.mouseReleaseEvent
        self.shortcut = 'Shift+Alt+J'

        if horizontal:
            self.setLayout(QHBoxLayout())
            self.layout().setDirection(self.layout().RightToLeft)
        else:
            self.setLayout(QVBoxLayout())
            self._jobs.setAlignment(Qt.AlignHCenter | Qt.AlignBottom)

        self.layout().addWidget(self.pi)
        self.layout().addWidget(self._jobs)
        if not horizontal:
            self.layout().setAlignment(self._jobs, Qt.AlignHCenter)
        self._jobs.setMargin(0)
        self.layout().setContentsMargins(0, 0, 0, 0)
        self._jobs.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
        self.setCursor(Qt.PointingHandCursor)
        b = _('Click to see list of jobs')
        self.setToolTip(b + u' (%s)' % self.shortcut)
        self.action_toggle = QAction(b, parent)
        parent.addAction(self.action_toggle)
        self.action_toggle.triggered.connect(self.toggle)
        if hasattr(parent, 'keyboard'):
            parent.keyboard.register_shortcut('toggle jobs list',
                                              _('Show/hide the Jobs List'),
                                              default_keys=(self.shortcut, ),
                                              action=self.action_toggle)

    def initialize(self, jobs_dialog, job_manager):
        self.jobs_dialog = jobs_dialog
        job_manager.job_added.connect(self.job_added)
        job_manager.job_done.connect(self.job_done)
        self.jobs_dialog.addAction(self.action_toggle)

    def mouseReleaseEvent(self, event):
        self.toggle()

    def toggle(self, *args):
        if self.jobs_dialog.isVisible():
            self.jobs_dialog.hide()
        else:
            self.jobs_dialog.show()

    @property
    def is_running(self):
        return self.pi.isAnimated()

    def start(self):
        self.pi.startAnimation()

    def stop(self):
        self.pi.stopAnimation()

    def jobs(self):
        src = unicode(self._jobs.text())
        return int(re.search(r'\d+', src).group())

    def tray_tooltip(self, num=0):
        if num == 0:
            text = _('No running jobs')
        elif num == 1:
            text = _('One running job')
        else:
            text = _('%d running jobs') % num
        if not (islinux or isbsd):
            text = 'calibre: ' + text
        return text

    def job_added(self, nnum):
        jobs = self._jobs
        src = unicode(jobs.text())
        num = self.jobs()
        text = src.replace(str(num), str(nnum))
        jobs.setText(text)
        self.start()
        self.tray_tooltip_updated.emit(self.tray_tooltip(nnum))

    def job_done(self, nnum):
        jobs = self._jobs
        src = unicode(jobs.text())
        num = self.jobs()
        text = src.replace(str(num), str(nnum))
        jobs.setText(text)
        if nnum == 0:
            self.no_more_jobs()
        self.tray_tooltip_updated.emit(self.tray_tooltip(nnum))

    def no_more_jobs(self):
        if self.is_running:
            self.stop()
            QCoreApplication.instance().alert(self, 5000)
예제 #14
0
파일: jobs.py 프로젝트: artbycrunk/calibre
class JobsButton(QWidget):  # {{{

    tray_tooltip_updated = pyqtSignal(object)

    def __init__(self, parent=None):
        QWidget.__init__(self, parent)
        self.mouse_over = False
        self.pi = ProgressIndicator(self, self.style().pixelMetric(QStyle.PM_ToolBarIconSize))
        self._jobs = QLabel('<b>'+_('Jobs:')+' 0 ')
        self._jobs.mouseReleaseEvent = self.mouseReleaseEvent
        self.shortcut = 'Alt+Shift+J'

        self.l = l = QHBoxLayout(self)
        l.setSpacing(3)
        l.addWidget(self.pi)
        l.addWidget(self._jobs)
        m = self.style().pixelMetric(QStyle.PM_DefaultFrameWidth)
        self.layout().setContentsMargins(m, m, m, m)
        self._jobs.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
        self.setCursor(Qt.PointingHandCursor)
        b = _('Click to see list of jobs')
        self.setToolTip(b + u' [%s]'%self.shortcut)
        self.action_toggle = QAction(b, parent)
        parent.addAction(self.action_toggle)
        self.action_toggle.triggered.connect(self.toggle)
        if hasattr(parent, 'keyboard'):
            parent.keyboard.register_shortcut('toggle jobs list', _('Show/hide the Jobs List'), default_keys=(self.shortcut,), action=self.action_toggle)

    def event(self, ev):
        m = None
        et = ev.type()
        if et == ev.Enter:
            m = True
        elif et == ev.Leave:
            m = False
        if m is not None and m != self.mouse_over:
            self.mouse_over = m
            self.update()
        return QWidget.event(self, ev)

    def initialize(self, jobs_dialog, job_manager):
        self.jobs_dialog = jobs_dialog
        job_manager.job_added.connect(self.job_added)
        job_manager.job_done.connect(self.job_done)
        self.jobs_dialog.addAction(self.action_toggle)

    def mouseReleaseEvent(self, event):
        self.toggle()

    def toggle(self, *args):
        if self.jobs_dialog.isVisible():
            self.jobs_dialog.hide()
        else:
            self.jobs_dialog.show()

    @property
    def is_running(self):
        return self.pi.isAnimated()

    def start(self):
        self.pi.startAnimation()

    def stop(self):
        self.pi.stopAnimation()

    def jobs(self):
        src = unicode(self._jobs.text())
        return int(re.search(r'\d+', src).group())

    def tray_tooltip(self, num=0):
        if num == 0:
            text = _('No running jobs')
        elif num == 1:
            text = _('One running job')
        else:
            text = _('%d running jobs') % num
        if not (islinux or isbsd):
            text = 'calibre: ' + text
        return text

    def job_added(self, nnum):
        jobs = self._jobs
        src = unicode(jobs.text())
        num = self.jobs()
        text = src.replace(str(num), str(nnum))
        jobs.setText(text)
        self.start()
        self.tray_tooltip_updated.emit(self.tray_tooltip(nnum))

    def job_done(self, nnum):
        jobs = self._jobs
        src = unicode(jobs.text())
        num = self.jobs()
        text = src.replace(str(num), str(nnum))
        jobs.setText(text)
        if nnum == 0:
            self.no_more_jobs()
        self.tray_tooltip_updated.emit(self.tray_tooltip(nnum))

    def no_more_jobs(self):
        if self.is_running:
            self.stop()
            QCoreApplication.instance().alert(self, 5000)

    def paintEvent(self, ev):
        if self.mouse_over:
            p = QPainter(self)
            tool = QStyleOption()
            tool.rect = self.rect()
            tool.state = QStyle.State_Raised | QStyle.State_Active | QStyle.State_MouseOver
            s = self.style()
            s.drawPrimitive(QStyle.PE_PanelButtonTool, tool, p, self)
            p.end()
        QWidget.paintEvent(self, ev)
예제 #15
0
class JobsButton(QFrame):  # {{{

    tray_tooltip_updated = pyqtSignal(object)

    def __init__(self, horizontal=False, size=48, parent=None):
        QFrame.__init__(self, parent)
        if horizontal:
            size = 24
        self.pi = ProgressIndicator(self, size)
        self._jobs = QLabel('<b>'+_('Jobs:')+' 0')
        self._jobs.mouseReleaseEvent = self.mouseReleaseEvent
        self.shortcut = 'Shift+Alt+J'

        if horizontal:
            self.setLayout(QHBoxLayout())
            self.layout().setDirection(self.layout().RightToLeft)
        else:
            self.setLayout(QVBoxLayout())
            self._jobs.setAlignment(Qt.AlignHCenter|Qt.AlignBottom)

        self.layout().addWidget(self.pi)
        self.layout().addWidget(self._jobs)
        if not horizontal:
            self.layout().setAlignment(self._jobs, Qt.AlignHCenter)
        self._jobs.setMargin(0)
        self.layout().setContentsMargins(0, 0, 0, 0)
        self._jobs.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
        self.setCursor(Qt.PointingHandCursor)
        b = _('Click to see list of jobs')
        self.setToolTip(b + u' (%s)'%self.shortcut)
        self.action_toggle = QAction(b, parent)
        parent.addAction(self.action_toggle)
        self.action_toggle.triggered.connect(self.toggle)
        if hasattr(parent, 'keyboard'):
            parent.keyboard.register_shortcut('toggle jobs list', _('Show/hide the Jobs List'), default_keys=(self.shortcut,), action=self.action_toggle)

    def initialize(self, jobs_dialog, job_manager):
        self.jobs_dialog = jobs_dialog
        job_manager.job_added.connect(self.job_added)
        job_manager.job_done.connect(self.job_done)
        self.jobs_dialog.addAction(self.action_toggle)

    def mouseReleaseEvent(self, event):
        self.toggle()

    def toggle(self, *args):
        if self.jobs_dialog.isVisible():
            self.jobs_dialog.hide()
        else:
            self.jobs_dialog.show()

    @property
    def is_running(self):
        return self.pi.isAnimated()

    def start(self):
        self.pi.startAnimation()

    def stop(self):
        self.pi.stopAnimation()

    def jobs(self):
        src = unicode(self._jobs.text())
        return int(re.search(r'\d+', src).group())

    def tray_tooltip(self, num=0):
        if num == 0:
            text = _('No running jobs')
        elif num == 1:
            text = _('One running job')
        else:
            text = _('%d running jobs') % num
        if not (islinux or isbsd):
            text = 'calibre: ' + text
        return text

    def job_added(self, nnum):
        jobs = self._jobs
        src = unicode(jobs.text())
        num = self.jobs()
        text = src.replace(str(num), str(nnum))
        jobs.setText(text)
        self.start()
        self.tray_tooltip_updated.emit(self.tray_tooltip(nnum))

    def job_done(self, nnum):
        jobs = self._jobs
        src = unicode(jobs.text())
        num = self.jobs()
        text = src.replace(str(num), str(nnum))
        jobs.setText(text)
        if nnum == 0:
            self.no_more_jobs()
        self.tray_tooltip_updated.emit(self.tray_tooltip(nnum))

    def no_more_jobs(self):
        if self.is_running:
            self.stop()
            QCoreApplication.instance().alert(self, 5000)
예제 #16
0
class JobsButton(QFrame):  # {{{

    def __init__(self, horizontal=False, size=48, parent=None):
        QFrame.__init__(self, parent)
        if horizontal:
            size = 24
        self.pi = ProgressIndicator(self, size)
        self._jobs = QLabel('<b>'+_('Jobs:')+' 0')
        self._jobs.mouseReleaseEvent = self.mouseReleaseEvent
        self.shortcut = 'Shift+Alt+J'

        if horizontal:
            self.setLayout(QHBoxLayout())
            self.layout().setDirection(self.layout().RightToLeft)
        else:
            self.setLayout(QVBoxLayout())
            self._jobs.setAlignment(Qt.AlignHCenter|Qt.AlignBottom)

        self.layout().addWidget(self.pi)
        self.layout().addWidget(self._jobs)
        if not horizontal:
            self.layout().setAlignment(self._jobs, Qt.AlignHCenter)
        self._jobs.setMargin(0)
        self.layout().setContentsMargins(0, 0, 0, 0)
        self._jobs.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
        self.setCursor(Qt.PointingHandCursor)
        b = _('Click to see list of jobs')
        self.setToolTip(b + u' (%s)'%self.shortcut)
        self.action_toggle = QAction(b, parent)
        parent.addAction(self.action_toggle)
        self.action_toggle.setShortcut(self.shortcut)
        self.action_toggle.triggered.connect(self.toggle)

    def initialize(self, jobs_dialog, job_manager):
        self.jobs_dialog = jobs_dialog
        job_manager.job_added.connect(self.job_added)
        job_manager.job_done.connect(self.job_done)
        self.jobs_dialog.addAction(self.action_toggle)

    def mouseReleaseEvent(self, event):
        self.toggle()

    def toggle(self, *args):
        if self.jobs_dialog.isVisible():
            self.jobs_dialog.hide()
        else:
            self.jobs_dialog.show()

    @property
    def is_running(self):
        return self.pi.isAnimated()

    def start(self):
        self.pi.startAnimation()

    def stop(self):
        self.pi.stopAnimation()

    def jobs(self):
        src = unicode(self._jobs.text())
        return int(re.search(r'\d+', src).group())

    def job_added(self, nnum):
        jobs = self._jobs
        src = unicode(jobs.text())
        num = self.jobs()
        text = src.replace(str(num), str(nnum))
        jobs.setText(text)
        self.start()

    def job_done(self, nnum):
        jobs = self._jobs
        src = unicode(jobs.text())
        num = self.jobs()
        text = src.replace(str(num), str(nnum))
        jobs.setText(text)
        if nnum == 0:
            self.no_more_jobs()

    def no_more_jobs(self):
        if self.is_running:
            self.stop()
            QCoreApplication.instance().alert(self, 5000)
예제 #17
0
class MyBlockingBusy(QDialog): # {{{

    do_one_signal = pyqtSignal()

    phases = ['',
              _('Title/Author'),
              _('Standard metadata'),
              _('Custom metadata'),
              _('Search/Replace'),
    ]

    def __init__(self, msg, args, db, ids, cc_widgets, s_r_func,
                 parent=None, window_title=_('Working')):
        QDialog.__init__(self, parent)

        self._layout = QVBoxLayout()
        self.setLayout(self._layout)
        self.msg_text = msg
        self.msg = QLabel(msg+'        ') # Ensure dialog is wide enough
        #self.msg.setWordWrap(True)
        self.font = QFont()
        self.font.setPointSize(self.font.pointSize() + 8)
        self.msg.setFont(self.font)
        self.pi = ProgressIndicator(self)
        self.pi.setDisplaySize(100)
        self._layout.addWidget(self.pi, 0, Qt.AlignHCenter)
        self._layout.addSpacing(15)
        self._layout.addWidget(self.msg, 0, Qt.AlignHCenter)
        self.setWindowTitle(window_title)
        self.resize(self.sizeHint())
        self.start()

        self.args = args
        self.series_start_value = None
        self.db = db
        self.ids = ids
        self.error = None
        self.cc_widgets = cc_widgets
        self.s_r_func = s_r_func
        self.do_one_signal.connect(self.do_one_safe, Qt.QueuedConnection)

    def start(self):
        self.pi.startAnimation()

    def stop(self):
        self.pi.stopAnimation()

    def accept(self):
        self.stop()
        return QDialog.accept(self)

    def exec_(self):
        self.current_index = 0
        self.current_phase = 1
        self.do_one_signal.emit()
        return QDialog.exec_(self)

    def do_one_safe(self):
        try:
            if self.current_index >= len(self.ids):
                self.current_phase += 1
                self.current_index = 0
                if self.current_phase > 4:
                    self.db.commit()
                    return self.accept()
            id = self.ids[self.current_index]
            percent = int((self.current_index*100)/float(len(self.ids)))
            self.msg.setText(self.msg_text.format(self.phases[self.current_phase],
                                        percent))
            self.do_one(id)
        except Exception as err:
            import traceback
            try:
                err = unicode(err)
            except:
                err = repr(err)
            self.error = (err, traceback.format_exc())
            return self.accept()

    def do_one(self, id):
        remove_all, remove, add, au, aus, do_aus, rating, pub, do_series, \
            do_autonumber, do_remove_format, remove_format, do_swap_ta, \
            do_remove_conv, do_auto_author, series, do_series_restart, \
            series_start_value, do_title_case, cover_action, clear_series, \
            pubdate, adddate, do_title_sort, languages, clear_languages, \
            restore_original = self.args


        # first loop: All changes that modify the filesystem and commit
        # immediately. We want to
        # try hard to keep the DB and the file system in sync, even in the face
        # of exceptions or forced exits.
        if self.current_phase == 1:
            title_set = False
            if do_swap_ta:
                title = self.db.title(id, index_is_id=True)
                aum = self.db.authors(id, index_is_id=True)
                if aum:
                    aum = [a.strip().replace('|', ',') for a in aum.split(',')]
                    new_title = authors_to_string(aum)
                    if do_title_case:
                        new_title = titlecase(new_title)
                    self.db.set_title(id, new_title, notify=False)
                    title_set = True
                if title:
                    new_authors = string_to_authors(title)
                    self.db.set_authors(id, new_authors, notify=False)
            if do_title_case and not title_set:
                title = self.db.title(id, index_is_id=True)
                self.db.set_title(id, titlecase(title), notify=False)
            if do_title_sort:
                title = self.db.title(id, index_is_id=True)
                if languages:
                    lang = languages[0]
                else:
                    lang = self.db.languages(id, index_is_id=True)
                    if lang:
                        lang = lang.partition(',')[0]
                self.db.set_title_sort(id, title_sort(title, lang=lang),
                        notify=False)
            if au:
                self.db.set_authors(id, string_to_authors(au), notify=False)
            if cover_action == 'remove':
                self.db.remove_cover(id)
            elif cover_action == 'generate':
                from calibre.ebooks import calibre_cover
                from calibre.ebooks.metadata import fmt_sidx
                from calibre.gui2 import config
                mi = self.db.get_metadata(id, index_is_id=True)
                series_string = None
                if mi.series:
                    series_string = _('Book %(sidx)s of %(series)s')%dict(
                        sidx=fmt_sidx(mi.series_index,
                        use_roman=config['use_roman_numerals_for_series_number']),
                        series=mi.series)

                cdata = calibre_cover(mi.title, mi.format_field('authors')[-1],
                        series_string=series_string)
                self.db.set_cover(id, cdata)
            elif cover_action == 'fromfmt':
                fmts = self.db.formats(id, index_is_id=True, verify_formats=False)
                if fmts:
                    covers = []
                    for fmt in fmts.split(','):
                        fmtf = self.db.format(id, fmt, index_is_id=True,
                                as_file=True)
                        if fmtf is None: continue
                        cdata, area = get_cover_data(fmtf, fmt)
                        if cdata:
                            covers.append((cdata, area))
                    covers.sort(key=lambda x: x[1])
                    if covers:
                        self.db.set_cover(id, covers[-1][0])
                    covers = []

            if do_remove_format:
                self.db.remove_format(id, remove_format, index_is_id=True,
                        notify=False, commit=True)

            if restore_original:
                formats = self.db.formats(id, index_is_id=True)
                formats = formats.split(',') if formats else []
                originals = [x.upper() for x in formats if
                        x.upper().startswith('ORIGINAL_')]
                for ofmt in originals:
                    fmt = ofmt.replace('ORIGINAL_', '')
                    with SpooledTemporaryFile(SPOOL_SIZE) as stream:
                        self.db.copy_format_to(id, ofmt, stream,
                                index_is_id=True)
                        stream.seek(0)
                        self.db.add_format(id, fmt, stream, index_is_id=True,
                                notify=False)
                    self.db.remove_format(id, ofmt, index_is_id=True,
                            notify=False, commit=True)

        elif self.current_phase == 2:
            # All of these just affect the DB, so we can tolerate a total rollback
            if do_auto_author:
                x = self.db.author_sort_from_book(id, index_is_id=True)
                if x:
                    self.db.set_author_sort(id, x, notify=False, commit=False)

            if aus and do_aus:
                self.db.set_author_sort(id, aus, notify=False, commit=False)

            if rating != -1:
                self.db.set_rating(id, 2*rating, notify=False, commit=False)

            if pub:
                self.db.set_publisher(id, pub, notify=False, commit=False)

            if clear_series:
                self.db.set_series(id, '', notify=False, commit=False)

            if pubdate is not None:
                self.db.set_pubdate(id, pubdate, notify=False, commit=False)

            if adddate is not None:
                self.db.set_timestamp(id, adddate, notify=False, commit=False)

            if do_series:
                if do_series_restart:
                    if self.series_start_value is None:
                        self.series_start_value = series_start_value
                    next = self.series_start_value
                    self.series_start_value += 1
                else:
                    next = self.db.get_next_series_num_for(series)
                self.db.set_series(id, series, notify=False, commit=False)
                if not series:
                    self.db.set_series_index(id, 1.0, notify=False, commit=False)
                elif do_autonumber: # is True if do_series_restart is True
                    self.db.set_series_index(id, next, notify=False, commit=False)
                elif tweaks['series_index_auto_increment'] != 'no_change':
                    self.db.set_series_index(id, 1.0, notify=False, commit=False)

            if do_remove_conv:
                self.db.delete_conversion_options(id, 'PIPE', commit=False)

            if clear_languages:
                self.db.set_languages(id, [], notify=False, commit=False)
            elif languages:
                self.db.set_languages(id, languages, notify=False, commit=False)

        elif self.current_phase == 3:
            # both of these are fast enough to just do them all
            for w in self.cc_widgets:
                w.commit(self.ids)
            if remove_all:
                self.db.remove_all_tags(self.ids)
            self.db.bulk_modify_tags(self.ids, add=add, remove=remove,
                                         notify=False)
            self.current_index = len(self.ids)
        elif self.current_phase == 4:
            self.s_r_func(id)
        # do the next one
        self.current_index += 1
        self.do_one_signal.emit()
예제 #18
0
파일: search.py 프로젝트: yzz-00/calibre
class SearchDialog(QDialog, Ui_Dialog):

    SEARCH_TEXT = _('&Search')
    STOP_TEXT = _('&Stop')

    def __init__(self, gui, parent=None, query=''):
        QDialog.__init__(self, parent)
        self.setupUi(self)
        s = self.style()
        self.close.setIcon(s.standardIcon(s.SP_DialogCloseButton))

        self.config = JSONConfig('store/search')
        self.search_title.initialize('store_search_search_title')
        self.search_author.initialize('store_search_search_author')
        self.search_edit.initialize('store_search_search')

        # Loads variables that store various settings.
        # This needs to be called soon in __init__ because
        # the variables it sets up are used later.
        self.load_settings()

        self.gui = gui

        # Setup our worker threads.
        self.search_pool = SearchThreadPool(self.search_thread_count)
        self.cache_pool = CacheUpdateThreadPool(self.cache_thread_count)
        self.results_view.model().cover_pool.set_thread_count(
            self.cover_thread_count)
        self.results_view.model().details_pool.set_thread_count(
            self.details_thread_count)
        self.results_view.setCursor(Qt.PointingHandCursor)

        # Check for results and hung threads.
        self.checker = QTimer()
        self.progress_checker = QTimer()
        self.hang_check = 0

        # Update store caches silently.
        for p in self.gui.istores.values():
            self.cache_pool.add_task(p, self.timeout)

        self.store_checks = {}
        self.setup_store_checks()

        # Set the search query
        if isinstance(query, (bytes, unicode_type)):
            self.search_edit.setText(query)
        elif isinstance(query, dict):
            if 'author' in query:
                self.search_author.setText(query['author'])
            if 'title' in query:
                self.search_title.setText(query['title'])

        # Create and add the progress indicator
        self.pi = ProgressIndicator(self, 24)
        self.button_layout.takeAt(0)
        self.button_layout.setAlignment(Qt.AlignCenter)
        self.button_layout.insertWidget(0, self.pi, 0, Qt.AlignCenter)

        self.adv_search_button.setIcon(QIcon(I('gear.png')))
        self.adv_search_button.setToolTip(_('Advanced search'))
        self.configure.setIcon(QIcon(I('config.png')))

        self.adv_search_button.clicked.connect(self.build_adv_search)
        self.search.clicked.connect(self.toggle_search)
        self.checker.timeout.connect(self.get_results)
        self.progress_checker.timeout.connect(self.check_progress)
        self.results_view.activated.connect(self.result_item_activated)
        self.results_view.download_requested.connect(self.download_book)
        self.results_view.open_requested.connect(self.open_store)
        self.results_view.model().total_changed.connect(self.update_book_total)
        self.select_all_stores.clicked.connect(self.stores_select_all)
        self.select_invert_stores.clicked.connect(self.stores_select_invert)
        self.select_none_stores.clicked.connect(self.stores_select_none)
        self.configure.clicked.connect(self.do_config)
        self.finished.connect(self.dialog_closed)
        self.searching = False

        self.progress_checker.start(100)

        self.restore_state()

    def setup_store_checks(self):
        first_run = self.config.get('first_run', True)

        # Add check boxes for each store so the user
        # can disable searching specific stores on a
        # per search basis.
        existing = {}
        for n in self.store_checks:
            existing[n] = self.store_checks[n].isChecked()

        self.store_checks = {}

        stores_check_widget = QWidget()
        store_list_layout = QGridLayout()
        stores_check_widget.setLayout(store_list_layout)

        icon = QIcon(I('donate.png'))
        for i, x in enumerate(
                sorted(self.gui.istores.keys(), key=lambda x: x.lower())):
            cbox = QCheckBox(x)
            cbox.setChecked(existing.get(x, first_run))
            store_list_layout.addWidget(cbox, i, 0, 1, 1)
            if self.gui.istores[x].base_plugin.affiliate:
                iw = QLabel(self)
                iw.setToolTip('<p>' + _(
                    'Buying from this store supports the calibre developer: %s</p>'
                ) % self.gui.istores[x].base_plugin.author + '</p>')
                iw.setPixmap(icon.pixmap(16, 16))
                store_list_layout.addWidget(iw, i, 1, 1, 1)
            self.store_checks[x] = cbox
        store_list_layout.setRowStretch(store_list_layout.rowCount(), 10)
        self.store_list.setWidget(stores_check_widget)

        self.config['first_run'] = False

    def build_adv_search(self):
        adv = AdvSearchBuilderDialog(self)
        if adv.exec_() == QDialog.Accepted:
            self.search_edit.setText(adv.search_string())

    def resize_columns(self):
        total = 600
        # Cover
        self.results_view.setColumnWidth(0, 85)
        total = total - 85
        # Title / Author
        self.results_view.setColumnWidth(1, int(total * .40))
        # Price
        self.results_view.setColumnWidth(2, int(total * .12))
        # DRM
        self.results_view.setColumnWidth(3, int(total * .15))
        # Store / Formats
        self.results_view.setColumnWidth(4, int(total * .25))
        # Download
        self.results_view.setColumnWidth(5, 20)
        # Affiliate
        self.results_view.setColumnWidth(6, 20)

    def toggle_search(self):
        if self.searching:
            self.search_pool.abort()
            m = self.results_view.model()
            m.details_pool.abort()
            m.cover_pool.abort()
            self.search.setText(self.SEARCH_TEXT)
            self.checker.stop()
            self.searching = False
        else:
            self.do_search()
        # Prevent hitting the enter key twice in quick succession causing
        # the search to start and stop
        self.search.setEnabled(False)
        QTimer.singleShot(1000, lambda: self.search.setEnabled(True))

    def do_search(self):
        # Stop all running threads.
        self.checker.stop()
        self.search_pool.abort()
        # Clear the visible results.
        self.results_view.model().clear_results()

        # Don't start a search if there is nothing to search for.
        query = []
        if self.search_title.text():
            query.append(
                u'title2:"~%s"' %
                unicode_type(self.search_title.text()).replace('"', ' '))
        if self.search_author.text():
            query.append(
                u'author2:"%s"' %
                unicode_type(self.search_author.text()).replace('"', ' '))
        if self.search_edit.text():
            query.append(unicode_type(self.search_edit.text()))
        query = " ".join(query)
        if not query.strip():
            error_dialog(self,
                         _('No query'),
                         _('You must enter a title, author or keyword to'
                           ' search for.'),
                         show=True)
            return
        self.searching = True
        self.search.setText(self.STOP_TEXT)
        # Give the query to the results model so it can do
        # futher filtering.
        self.results_view.model().set_query(query)

        # Plugins are in random order that does not change.
        # Randomize the ord of the plugin names every time
        # there is a search. This way plugins closer
        # to a don't have an unfair advantage over
        # plugins further from a.
        store_names = list(self.store_checks)
        if not store_names:
            return
        # Remove all of our internal filtering logic from the query.
        query = self.clean_query(query)
        shuffle(store_names)
        # Add plugins that the user has checked to the search pool's work queue.
        self.gui.istores.join(4.0)  # Wait for updated plugins to load
        for n in store_names:
            if self.store_checks[n].isChecked():
                self.search_pool.add_task(query, n, self.gui.istores[n],
                                          self.max_results, self.timeout)
        self.hang_check = 0
        self.checker.start(100)
        self.pi.startAnimation()

    def clean_query(self, query):
        query = query.lower()
        # Remove control modifiers.
        query = query.replace('\\', '')
        query = query.replace('!', '')
        query = query.replace('=', '')
        query = query.replace('~', '')
        query = query.replace('>', '')
        query = query.replace('<', '')
        # Remove the prefix.
        for loc in ('all', 'author', 'author2', 'authors', 'title', 'title2'):
            query = re.sub(r'%s:"(?P<a>[^\s"]+)"' % loc, r'\g<a>', query)
            query = query.replace('%s:' % loc, '')
        # Remove the prefix and search text.
        for loc in ('cover', 'download', 'downloads', 'drm', 'format',
                    'formats', 'price', 'store'):
            query = re.sub(r'%s:"[^"]"' % loc, '', query)
            query = re.sub(r'%s:[^\s]*' % loc, '', query)
        # Remove logic.
        query = re.sub(r'(^|\s|")(and|not|or|a|the|is|of)(\s|$|")', r' ',
                       query)
        # Remove "
        query = query.replace('"', '')
        # Remove excess whitespace.
        query = re.sub(r'\s+', ' ', query)
        query = query.strip()
        return query.encode('utf-8')

    def save_state(self):
        self.config['geometry'] = bytearray(self.saveGeometry())
        self.config['store_splitter_state'] = bytearray(
            self.store_splitter.saveState())
        self.config['results_view_column_width'] = [
            self.results_view.columnWidth(i)
            for i in range(self.results_view.model().columnCount())
        ]
        self.config['sort_col'] = self.results_view.model().sort_col
        self.config['sort_order'] = self.results_view.model().sort_order
        self.config['open_external'] = self.open_external.isChecked()

        store_check = {}
        for k, v in self.store_checks.items():
            store_check[k] = v.isChecked()
        self.config['store_checked'] = store_check

    def restore_state(self):
        geometry = self.config.get('geometry', None)
        if geometry:
            QApplication.instance().safe_restore_geometry(self, geometry)

        splitter_state = self.config.get('store_splitter_state', None)
        if splitter_state:
            self.store_splitter.restoreState(splitter_state)

        results_cwidth = self.config.get('results_view_column_width', None)
        if results_cwidth:
            for i, x in enumerate(results_cwidth):
                if i >= self.results_view.model().columnCount():
                    break
                self.results_view.setColumnWidth(i, x)
        else:
            self.resize_columns()

        self.open_external.setChecked(self.should_open_external)

        store_check = self.config.get('store_checked', None)
        if store_check:
            for n in store_check:
                if n in self.store_checks:
                    self.store_checks[n].setChecked(store_check[n])

        self.results_view.model().sort_col = self.config.get('sort_col', 2)
        self.results_view.model().sort_order = self.config.get(
            'sort_order', Qt.AscendingOrder)
        self.results_view.header().setSortIndicator(
            self.results_view.model().sort_col,
            self.results_view.model().sort_order)

    def load_settings(self):
        # Seconds
        self.timeout = self.config.get('timeout', 75)
        # Milliseconds
        self.hang_time = self.config.get('hang_time', 75) * 1000

        self.max_results = self.config.get('max_results', 15)
        self.should_open_external = self.config.get('open_external', True)

        # Number of threads to run for each type of operation
        self.search_thread_count = self.config.get('search_thread_count', 4)
        self.cache_thread_count = self.config.get('cache_thread_count', 2)
        self.cover_thread_count = self.config.get('cover_thread_count', 2)
        self.details_thread_count = self.config.get('details_thread_count', 4)

    def do_config(self):
        # Save values that need to be synced between the dialog and the
        # search widget.
        self.config['open_external'] = self.open_external.isChecked()

        # Create the config dialog. It's going to put two config widgets
        # into a QTabWidget for displaying all of the settings.
        d = QDialog(self)
        button_box = QDialogButtonBox(QDialogButtonBox.Close)
        v = QVBoxLayout(d)
        button_box.accepted.connect(d.accept)
        button_box.rejected.connect(d.reject)
        d.setWindowTitle(_('Customize Get books search'))

        tab_widget = QTabWidget(d)
        v.addWidget(tab_widget)
        v.addWidget(button_box)

        chooser_config_widget = StoreChooserWidget()
        search_config_widget = StoreConfigWidget(self.config)

        tab_widget.addTab(chooser_config_widget, _('Choose s&tores'))
        tab_widget.addTab(search_config_widget, _('Configure s&earch'))

        # Restore dialog state.
        geometry = self.config.get('config_dialog_geometry', None)
        if geometry:
            QApplication.instance().safe_restore_geometry(d, geometry)
        else:
            d.resize(800, 600)
        tab_index = self.config.get('config_dialog_tab_index', 0)
        tab_index = min(tab_index, tab_widget.count() - 1)
        tab_widget.setCurrentIndex(tab_index)

        d.exec_()

        # Save dialog state.
        self.config['config_dialog_geometry'] = bytearray(d.saveGeometry())
        self.config['config_dialog_tab_index'] = tab_widget.currentIndex()

        search_config_widget.save_settings()
        self.config_changed()
        self.gui.load_store_plugins()
        self.setup_store_checks()

    def config_changed(self):
        self.load_settings()

        self.open_external.setChecked(self.should_open_external)
        self.search_pool.set_thread_count(self.search_thread_count)
        self.cache_pool.set_thread_count(self.cache_thread_count)
        self.results_view.model().cover_pool.set_thread_count(
            self.cover_thread_count)
        self.results_view.model().details_pool.set_thread_count(
            self.details_thread_count)

    def get_results(self):
        # We only want the search plugins to run
        # a maximum set amount of time before giving up.
        self.hang_check += 1
        if self.hang_check >= self.hang_time:
            self.search_pool.abort()
            self.checker.stop()
        else:
            # Stop the checker if not threads are running.
            if not self.search_pool.threads_running(
            ) and not self.search_pool.has_tasks():
                self.checker.stop()

        while self.search_pool.has_results():
            res, store_plugin = self.search_pool.get_result()
            if res:
                self.results_view.model().add_result(res, store_plugin)

        if not self.search_pool.threads_running(
        ) and not self.results_view.model().has_results():
            info_dialog(self,
                        _('No matches'),
                        _('Couldn\'t find any books matching your query.'),
                        show=True,
                        show_copy_button=False)

    def update_book_total(self, total):
        self.total.setText('%s' % total)

    def result_item_activated(self, index):
        result = self.results_view.model().get_result(index)

        if result.downloads:
            self.download_book(result)
        else:
            self.open_store(result)

    def download_book(self, result):
        d = ChooseFormatDialog(self,
                               _('Choose format to download to your library.'),
                               list(result.downloads.keys()))
        if d.exec_() == d.Accepted:
            ext = d.format()
            fname = result.title[:60] + '.' + ext.lower()
            fname = ascii_filename(fname)
            show_download_info(result.title, parent=self)
            self.gui.download_ebook(result.downloads[ext],
                                    filename=fname,
                                    create_browser=result.create_browser)

    def open_store(self, result):
        self.gui.istores[result.store_name].open(
            self, result.detail_item, self.open_external.isChecked())

    def check_progress(self):
        m = self.results_view.model()
        if not self.search_pool.threads_running(
        ) and not m.cover_pool.threads_running(
        ) and not m.details_pool.threads_running():
            self.pi.stopAnimation()
            self.search.setText(self.SEARCH_TEXT)
            self.searching = False
        else:
            self.searching = True
            if unicode_type(self.search.text()) != self.STOP_TEXT:
                self.search.setText(self.STOP_TEXT)
            if not self.pi.isAnimated():
                self.pi.startAnimation()

    def stores_select_all(self):
        for check in self.store_checks.values():
            check.setChecked(True)

    def stores_select_invert(self):
        for check in self.store_checks.values():
            check.setChecked(not check.isChecked())

    def stores_select_none(self):
        for check in self.store_checks.values():
            check.setChecked(False)

    def dialog_closed(self, result):
        self.results_view.model().closing()
        self.search_pool.abort()
        self.cache_pool.abort()
        self.save_state()

    def exec_(self):
        if unicode_type(self.search_edit.text()).strip() or unicode_type(
                self.search_title.text()).strip() or unicode_type(
                    self.search_author.text()).strip():
            self.do_search()
        return QDialog.exec_(self)
예제 #19
0
class BlockingJob(QWidget):

    def __init__(self, parent):
        QWidget.__init__(self, parent)
        l = QVBoxLayout()
        self.setLayout(l)
        l.addStretch(10)
        self.pi = ProgressIndicator(self, 128)
        l.addWidget(self.pi, alignment=Qt.AlignHCenter)
        self.dummy = QLabel('<h2>\xa0')
        l.addSpacing(10)
        l.addWidget(self.dummy, alignment=Qt.AlignHCenter)
        l.addStretch(10)
        self.setVisible(False)
        self.text = ''

    def start(self):
        self.setGeometry(0, 0, self.parent().width(), self.parent().height())
        self.setVisible(True)
        # Prevent any actions from being triggerred by key presses
        self.parent().setEnabled(False)
        self.raise_()
        self.setFocus(Qt.OtherFocusReason)
        self.pi.startAnimation()
        QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))

    def stop(self):
        QApplication.restoreOverrideCursor()
        self.pi.stopAnimation()
        self.setVisible(False)
        self.parent().setEnabled(True)

    def job_done(self, callback, job):
        del job.callback
        self.stop()
        callback(job)

    def paintEvent(self, ev):
        br = ev.region().boundingRect()
        p = QPainter(self)
        p.setOpacity(0.2)
        p.fillRect(br, QBrush(self.palette().text()))
        p.end()
        QWidget.paintEvent(self, ev)
        p = QPainter(self)
        p.setClipRect(br)
        f = p.font()
        f.setBold(True)
        f.setPointSize(20)
        p.setFont(f)
        p.setPen(Qt.SolidLine)
        r = QRect(0, self.dummy.geometry().top() + 10, self.geometry().width(), 150)
        p.drawText(r, Qt.AlignHCenter | Qt.AlignTop | Qt.TextSingleLine, self.text)
        p.end()

    def eventFilter(self, obj, ev):
        if ev.type() in (ev.KeyPress, ev.KeyRelease):
            return True
        return QWidget.eventFilter(self, obj, ev)

    def set_msg(self, text):
        self.text = text

    def __call__(self, name, user_text, callback, function, *args, **kwargs):
        ' Run a job that blocks the GUI providing some feedback to the user '
        self.set_msg(user_text)
        job = LongJob(name, user_text, Dispatcher(partial(self.job_done, callback)), function, *args, **kwargs)
        job.start()
        self.start()