class SelectNames(QDialog):  # {{{

    def __init__(self, names, txt, parent=None):
        QDialog.__init__(self, parent)
        self.l = l = QVBoxLayout(self)
        self.setLayout(l)

        self.la = la = QLabel(_('Create a Virtual library based on %s') % txt)
        l.addWidget(la)

        self._names = QListWidget(self)
        self._names.addItems(sorted(names, key=sort_key))
        self._names.setSelectionMode(self._names.MultiSelection)
        l.addWidget(self._names)

        self._or = QRadioButton(_('Match any of the selected %s')%txt)
        self._and = QRadioButton(_('Match all of the selected %s')%txt)
        self._or.setChecked(True)
        l.addWidget(self._or)
        l.addWidget(self._and)

        self.bb = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
        self.bb.accepted.connect(self.accept)
        self.bb.rejected.connect(self.reject)
        l.addWidget(self.bb)

        self.resize(self.sizeHint())

    @property
    def names(self):
        for item in self._names.selectedItems():
            yield unicode_type(item.data(Qt.DisplayRole) or '')

    @property
    def match_type(self):
        return ' and ' if self._and.isChecked() else ' or '
class SelectNames(QDialog):  # {{{

    def __init__(self, names, txt, parent=None):
        QDialog.__init__(self, parent)
        self.l = l = QVBoxLayout(self)
        self.setLayout(l)

        self.la = la = QLabel(_('Create a Virtual Library based on %s') % txt)
        l.addWidget(la)

        self._names = QListWidget(self)
        self._names.addItems(sorted(names, key=sort_key))
        self._names.setSelectionMode(self._names.ExtendedSelection)
        l.addWidget(self._names)

        self._or = QRadioButton(_('Match any of the selected %s names')%txt)
        self._and = QRadioButton(_('Match all of the selected %s names')%txt)
        self._or.setChecked(True)
        l.addWidget(self._or)
        l.addWidget(self._and)

        self.bb = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
        self.bb.accepted.connect(self.accept)
        self.bb.rejected.connect(self.reject)
        l.addWidget(self.bb)

        self.resize(self.sizeHint())

    @property
    def names(self):
        for item in self._names.selectedItems():
            yield unicode(item.data(Qt.DisplayRole) or '')

    @property
    def match_type(self):
        return ' and ' if self._and.isChecked() else ' or '
Beispiel #3
0
class SyncWidget(QWidget):
    def __init__(self, gui, do_user_config, selected_book_ids,
                 is_sync_selected):
        QWidget.__init__(self, gui)

        api.build_request('/limits')

        self.logger = Logger(
            path.join(gui.current_db.library_path, 'bookfusion_sync.log'))
        self.logger.info(
            'Open sync dialog: selected_book_ids={}; is_sync_selected={}'.
            format(selected_book_ids, is_sync_selected))

        if len(selected_book_ids) == 0:
            is_sync_selected = False

        self.worker_thread = None

        self.do_user_config = do_user_config
        self.db = gui.current_db.new_api

        self.selected_book_ids = selected_book_ids

        self.l = QVBoxLayout()
        self.l.setContentsMargins(0, 0, 0, 0)
        self.setLayout(self.l)

        self.radio_layout = QVBoxLayout()
        self.l.addLayout(self.radio_layout)

        self.sync_all_radio = QRadioButton('Sync all books')
        self.sync_all_radio.setChecked(not is_sync_selected)
        self.radio_layout.addWidget(self.sync_all_radio)

        sync_selected_radio_label = 'Sync selected books'
        if len(selected_book_ids) > 0:
            sync_selected_radio_label = 'Sync {} selected {}'.format(
                len(selected_book_ids),
                'book' if len(selected_book_ids) == 1 else 'books')
        self.sync_selected_radio = QRadioButton(sync_selected_radio_label)
        self.sync_selected_radio.toggled.connect(self.toggle_sync_selected)
        self.sync_selected_radio.setChecked(is_sync_selected)
        self.sync_selected_radio.setEnabled(len(selected_book_ids) > 0)
        self.radio_layout.addWidget(self.sync_selected_radio)

        self.reupload_possible = len(selected_book_ids) > 0 and len(
            selected_book_ids) <= 100
        if self.reupload_possible:
            for book_id in selected_book_ids:
                identifiers = self.db.get_proxy_metadata(book_id).identifiers
                if not identifiers.get('bookfusion'):
                    self.reupload_possible = False

        self.reupload_checkbox = QCheckBox('Re-upload book files', self)
        self.reupload_checkbox.setVisible(is_sync_selected
                                          and self.reupload_possible)
        self.radio_layout.addWidget(self.reupload_checkbox)

        self.btn_layout = QHBoxLayout()
        self.l.addLayout(self.btn_layout)

        self.config_btn = QPushButton('Configure')
        self.config_btn.clicked.connect(self.config)
        self.btn_layout.addWidget(self.config_btn)

        self.btn_layout.addStretch()

        self.start_btn = QPushButton('Start')
        self.start_btn.clicked.connect(self.start)
        self.btn_layout.addWidget(self.start_btn)

        self.cancel_btn = QPushButton('Cancel')
        self.cancel_btn.clicked.connect(self.cancel)
        self.cancel_btn.hide()
        self.btn_layout.addWidget(self.cancel_btn)

        self.info = QHBoxLayout()
        self.info.setContentsMargins(0, 0, 0, 0)
        self.l.addLayout(self.info)
        self.msg = QLabel()
        self.info.addWidget(self.msg)
        self.info.addStretch()
        self.log_btn = QLabel('<a href="#">Log</a>')
        self.log_btn.linkActivated.connect(self.toggle_log)
        self.log_btn.hide()
        self.info.addWidget(self.log_btn)

        self.log = QTableWidget(0, 2)
        self.log.setHorizontalHeaderLabels(['Book', 'Message'])
        self.log.horizontalHeader().setStretchLastSection(True)
        self.log.hide()
        self.l.addWidget(self.log)

        self.apply_config()

    def __del__(self):
        if self.worker_thread:
            self.worker_thread.quit()
            self.worker_thread.terminate()

    def config(self):
        self.do_user_config(parent=self)
        self.apply_config()

    def apply_config(self):
        configured = bool(prefs['api_key'])
        self.start_btn.setEnabled(configured)

    def toggle_sync_selected(self, is_sync_selected):
        if hasattr(self, 'reupload_checkbox'):
            self.reupload_checkbox.setVisible(is_sync_selected
                                              and self.reupload_possible)

    def start(self):
        if self.sync_selected_radio.isChecked(
        ) and self.reupload_checkbox.isChecked():
            reply = QMessageBox.question(
                self, 'BookFusion Sync',
                'Re-uploading book files can potentially result in previous highlights or bookmarks no longer working.\n\nPreviously uploaded files will be overwritten. Are you sure you want to re-upload?',
                QMessageBox.No | QMessageBox.Yes, QMessageBox.Yes)
            if reply != QMessageBox.Yes:
                return

        self.worker = None
        self.valid_book_ids = None
        self.book_log_map = {}
        self.book_progress_map = {}

        if self.sync_selected_radio.isChecked():
            book_ids = list(self.selected_book_ids)
        else:
            book_ids = list(self.db.all_book_ids())

        self.logger.info('Start sync: sync_selected={}; book_ids={}'.format(
            self.sync_selected_radio.isChecked(), book_ids))

        self.in_progress = True
        self.total = len(book_ids)
        self.update_progress(None)
        self.start_btn.hide()
        self.cancel_btn.show()
        self.config_btn.setEnabled(False)
        self.sync_all_radio.setEnabled(False)
        self.sync_selected_radio.setEnabled(False)

        self.worker_thread = QThread(self)

        self.worker = CheckWorker(self.db, self.logger, book_ids)
        self.worker.finished.connect(self.finish_check)
        self.worker.finished.connect(self.worker_thread.quit)
        self.worker.progress.connect(self.update_progress)
        self.worker.limitsAvailable.connect(self.apply_limits)
        self.worker.resultsAvailable.connect(self.apply_results)
        self.worker.aborted.connect(self.abort)
        self.worker.moveToThread(self.worker_thread)

        self.worker_thread.started.connect(self.worker.start)
        self.worker_thread.start()

    def apply_limits(self, limits):
        self.logger.info('Limits: {}'.format(limits))
        self.limits = limits

    def apply_results(self, books_count, valid_ids):
        self.logger.info('Check results: books_count={}; valid_ids={}'.format(
            books_count, valid_ids))
        self.valid_book_ids = valid_ids
        self.books_count = books_count

    def finish_check(self):
        if self.valid_book_ids:
            is_filesize_exceeded = len(self.valid_book_ids) < self.books_count
            is_total_books_exceeded = self.limits[
                'total_books'] and self.books_count > self.limits['total_books']

            if is_filesize_exceeded or is_total_books_exceeded:
                if self.limits['message']:
                    msg_box = QMessageBox(self)
                    msg_box.setWindowTitle('BookFusion Sync')
                    msg_box.addButton(QMessageBox.No)
                    msg_box.addButton(QMessageBox.Yes)
                    msg_box.setText(self.limits['message'])
                    msg_box.setDefaultButton(QMessageBox.Yes)
                    reply = msg_box.exec_()
                    if reply == QMessageBox.Yes:
                        self.start_sync()
                    else:
                        self.in_progress = False
                        self.msg.setText('Canceled.')
                        self.finish_sync()
                else:
                    self.start_sync()
            else:
                self.start_sync()
        else:
            if self.in_progress:
                self.in_progress = False
                self.msg.setText('No supported books selected.')
            self.finish_sync()

    def start_sync(self):
        self.log_btn.show()
        self.log.setRowCount(0)
        self.log.show()

        self.worker_thread = QThread(self)

        book_ids = self.valid_book_ids
        if self.limits['total_books']:
            book_ids = book_ids[:self.limits['total_books']]

        self.total = len(book_ids)

        self.worker = UploadManager(
            self.db, self.logger, book_ids,
            self.sync_selected_radio.isChecked()
            and self.reupload_checkbox.isChecked())
        self.worker.finished.connect(self.finish_sync)
        self.worker.finished.connect(self.worker_thread.quit)
        self.worker.progress.connect(self.update_progress)
        self.worker.uploadProgress.connect(self.update_upload_progress)
        self.worker.started.connect(self.log_start)
        self.worker.skipped.connect(self.log_skip)
        self.worker.failed.connect(self.log_fail)
        self.worker.uploaded.connect(self.log_upload)
        self.worker.updated.connect(self.log_update)
        self.worker.aborted.connect(self.abort)
        self.worker.moveToThread(self.worker_thread)

        self.worker_thread.started.connect(self.worker.start)
        self.worker_thread.start()

    def finish_sync(self):
        if self.in_progress:
            self.msg.setText('Done.')
        self.cancel_btn.hide()
        self.cancel_btn.setEnabled(True)
        self.start_btn.show()
        self.config_btn.setEnabled(True)
        self.sync_all_radio.setEnabled(True)
        self.sync_selected_radio.setEnabled(len(self.selected_book_ids) > 0)

    def abort(self, error):
        self.in_progress = False
        self.msg.setText(error)

    def cancel(self):
        self.in_progress = False
        self.msg.setText('Canceled.')
        self.cancel_btn.setEnabled(False)
        self.worker.cancel()

    def update_progress(self, progress):
        if self.in_progress:
            if isinstance(self.worker, UploadManager):
                msg = 'Synchronizing...'
            else:
                msg = 'Preparing...'
            if progress:
                msg += ' {} of {}'.format(progress + 1, self.total)
            self.msg.setText(msg)

    def update_upload_progress(self, book_id, sent, total):
        if not book_id in self.book_progress_map:
            return

        progress = self.book_progress_map[book_id]

        if sent < total:
            progress.setValue(sent)
            progress.setMaximum(total)
        else:
            progress.setMaximum(0)

    def log_start(self, book_id):
        self.update_log(book_id, None)

    def log_fail(self, book_id, msg):
        self.update_log(book_id, msg)

    def log_skip(self, book_id):
        self.update_log(book_id, 'skipped')

    def log_upload(self, book_id):
        self.update_log(book_id, 'uploaded')

    def log_update(self, book_id):
        self.update_log(book_id, 'updated')

    def toggle_log(self, _):
        self.log.setVisible(not self.log.isVisible())

    def update_log(self, book_id, msg):
        if book_id in self.book_log_map:
            index = self.book_log_map[book_id]
        else:
            index = self.log.rowCount()
            self.book_log_map[book_id] = index

            self.log.insertRow(index)

            title = self.db.get_proxy_metadata(book_id).title
            title_item = QTableWidgetItem(title)
            title_item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled
                                | Qt.ItemNeverHasChildren)
            self.log.setItem(index, 0, title_item)

            progress = QProgressBar()
            progress.setMaximum(0)
            self.log.setCellWidget(index, 1, progress)
            self.book_progress_map[book_id] = progress

        if not msg is None:
            del (self.book_progress_map[book_id])
            self.log.setCellWidget(index, 1, None)

            msg_item = QTableWidgetItem(msg)
            msg_item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled
                              | Qt.ItemNeverHasChildren)
            self.log.setItem(index, 1, msg_item)

    def maybe_cancel(self):
        if self.worker_thread and self.worker_thread.isRunning():
            reply = QMessageBox.question(
                self, 'BookFusion Sync',
                'Are you sure you want to cancel the currently running process?',
                QMessageBox.No | QMessageBox.Yes, QMessageBox.Yes)
            if reply == QMessageBox.Yes:
                self.cancel()
            else:
                return False
        return True
Beispiel #4
0
class LLT_MC(QMainWindow):
    def __init__(self, dicTitle, dicList):
        super(LLT_MC, self).__init__()
        self.w = QWidget()
        self.setCentralWidget(self.w)

        self.title = dicTitle
        self.wordList = dicList

        self.setWindowTitle("Multiple Choice")
        self.setGeometry(0, 0, 300, 400)

        self.currentWordS = random.sample(self.wordList, 3)
        self.currentQWord = self.currentWordS[0][0]
        self.currentAWord = self.currentWordS[0][1]

        self.topLabel = QLabel("Multiple Choice: " + str(dicTitle))
        self.wordLabel = QLabel()
        self.wordLabel.setFont(QFont('Times New Roman', 16))
        self.wordLabel.setAlignment(Qt.AlignCenter)
        self.resultLabel = QLabel()
        self.resultLabel.setAlignment(Qt.AlignCenter)

        self.aRad = QRadioButton()
        self.bRad = QRadioButton()
        self.cRad = QRadioButton()
        self.group = QButtonGroup()
        self.group.addButton(self.aRad)
        self.group.addButton(self.bRad)
        self.group.addButton(self.cRad)

        self.checkBut = QPushButton("Check")
        self.checkBut.setMinimumWidth(100)
        self.checkBut.setMinimumHeight(70)
        self.checkBut.clicked.connect(self.check)
        self.nextBut = QPushButton("Next Word")
        self.nextBut.setMinimumWidth(100)
        self.nextBut.setMinimumHeight(70)
        self.nextBut.clicked.connect(self.next)
        self.exitBut = QPushButton("Exit")
        self.exitBut.setMinimumWidth(130)
        self.exitBut.setMinimumHeight(70)
        self.exitBut.clicked.connect(self.exit)

        self.hbox = QHBoxLayout()
        self.hbox.addWidget(self.checkBut)
        self.hbox.addWidget(self.nextBut)
        self.vbox = QVBoxLayout()
        self.vbox.addWidget(self.topLabel)
        self.vbox.addWidget(self.wordLabel)
        self.vbox.addWidget(self.aRad)
        self.vbox.addWidget(self.bRad)
        self.vbox.addWidget(self.cRad)
        self.vbox.addWidget(self.resultLabel)
        self.vbox.addLayout(self.hbox)
        self.vbox.addWidget(self.exitBut)

        self.w.setLayout(self.vbox)
        self.setWord()

    #define methods

    def setWord(self):
        newText = []
        for item in self.currentWordS:
            newText.append(item[1])

        random.shuffle(newText)

        self.group.setExclusive(False)
        self.aRad.setText(newText[0])
        self.bRad.setText(newText[1])
        self.cRad.setText(newText[2])
        self.wordLabel.setText(self.currentQWord)
        self.group.setExclusive(True)

    def check(self):
        if self.aRad.isChecked() and self.aRad.text() == self.currentAWord:
            self.resultLabel.setText("CORRECT")
        elif self.aRad.isChecked() and self.aRad.text() != self.currentAWord:
            self.resultLabel.setText("Try again")
        if self.bRad.isChecked() and self.bRad.text() == self.currentAWord:
            self.resultLabel.setText("CORRECT")
        elif self.bRad.isChecked() and self.bRad.text() != self.currentAWord:
            self.resultLabel.setText("Try again")
        if self.cRad.isChecked() and self.cRad.text() == self.currentAWord:
            self.resultLabel.setText("CORRECT")
        elif self.cRad.isChecked() and self.cRad.text() != self.currentAWord:
            self.resultLabel.setText("Try again")

    def next(self):
        self.currentWordS = random.sample(self.wordList, 3)
        self.currentQWord = self.currentWordS[0][0]
        self.currentAWord = self.currentWordS[0][1]

        self.wordLabel.setText(self.currentQWord)
        self.resultLabel.setText('')

        self.group.setExclusive(False)
        self.aRad.setChecked(False)
        self.bRad.setChecked(False)
        self.cRad.setChecked(False)
        self.group.setExclusive(True)

        newText = []
        for item in self.currentWordS:
            newText.append(item[1])
        random.shuffle(newText)
        self.aRad.setText(newText[0])
        self.bRad.setText(newText[1])
        self.cRad.setText(newText[2])

    def exit(self):
        confirm = QMessageBox.question(self.w, 'Quit',
                                       'Are you sure you want to exit?',
                                       QMessageBox.Yes | QMessageBox.No)
        if confirm == QMessageBox.Yes:
            self.close()
        else:
            pass
Beispiel #5
0
class SchedulerDialog(QDialog):

    SCHEDULE_TYPES = OrderedDict([
            ('days_of_week', DaysOfWeek),
            ('days_of_month', DaysOfMonth),
            ('every_x_days', EveryXDays),
    ])

    download = pyqtSignal(object)

    def __init__(self, recipe_model, parent=None):
        QDialog.__init__(self, parent)
        self.commit_on_change = True
        self.previous_urn = None

        self.setWindowIcon(QIcon(I('scheduler.png')))
        self.setWindowTitle(_("Schedule news download"))
        self.l = l = QGridLayout(self)

        # Left panel
        self.h = h = QHBoxLayout()
        l.addLayout(h, 0, 0, 1, 1)
        self.search = s = SearchBox2(self)
        self.search.initialize('scheduler_search_history')
        self.search.setMinimumContentsLength(15)
        self.go_button = b = QToolButton(self)
        b.setText(_("Go"))
        b.clicked.connect(self.search.do_search)
        h.addWidget(s), h.addWidget(b)
        self.recipes = RecipesView(self)
        l.addWidget(self.recipes, 1, 0, 1, 1)
        self.recipe_model = recipe_model
        self.recipe_model.do_refresh()
        self.recipes.setModel(self.recipe_model)
        self.recipes.setFocus(Qt.OtherFocusReason)
        self.count_label = la = QLabel(_('%s news sources') % self.recipe_model.showing_count)
        la.setAlignment(Qt.AlignCenter)
        l.addWidget(la, 2, 0, 1, 1)
        self.search.search.connect(self.recipe_model.search)
        self.recipe_model.searched.connect(self.search.search_done, type=Qt.QueuedConnection)
        self.recipe_model.searched.connect(self.search_done)

        # Right Panel
        self.scroll_area_contents = sac = QWidget(self)
        self.l.addWidget(sac, 0, 1, 2, 1)
        sac.v = v = QVBoxLayout(sac)
        v.setContentsMargins(0, 0, 0, 0)
        self.detail_box = QTabWidget(self)
        self.detail_box.setVisible(False)
        self.detail_box.setCurrentIndex(0)
        v.addWidget(self.detail_box)
        v.addItem(QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding))

        # First Tab (scheduling)
        self.tab = QWidget()
        self.detail_box.addTab(self.tab, _("&Schedule"))
        self.tab.v = vt = QVBoxLayout(self.tab)
        vt.setContentsMargins(0, 0, 0, 0)
        self.blurb = la = QLabel('blurb')
        la.setWordWrap(True), la.setOpenExternalLinks(True)
        vt.addWidget(la)
        self.frame = f = QFrame(self.tab)
        vt.addWidget(f)
        f.setFrameShape(f.StyledPanel)
        f.setFrameShadow(f.Raised)
        f.v = vf = QVBoxLayout(f)
        self.schedule = s = QCheckBox(_("&Schedule for download:"), f)
        self.schedule.stateChanged[int].connect(self.toggle_schedule_info)
        vf.addWidget(s)
        f.h = h = QHBoxLayout()
        vf.addLayout(h)
        self.days_of_week = QRadioButton(_("&Days of  week"), f)
        self.days_of_month = QRadioButton(_("Da&ys of month"), f)
        self.every_x_days = QRadioButton(_("Every &x days"), f)
        self.days_of_week.setChecked(True)
        h.addWidget(self.days_of_week), h.addWidget(self.days_of_month), h.addWidget(self.every_x_days)
        self.schedule_stack = ss = QStackedWidget(f)
        self.schedule_widgets = []
        for key in reversed(self.SCHEDULE_TYPES):
            self.schedule_widgets.insert(0, self.SCHEDULE_TYPES[key](self))
            self.schedule_stack.insertWidget(0, self.schedule_widgets[0])
        vf.addWidget(ss)
        self.last_downloaded = la = QLabel(f)
        la.setWordWrap(True)
        vf.addWidget(la)
        self.account = acc = QGroupBox(self.tab)
        acc.setTitle(_("&Account"))
        vt.addWidget(acc)
        acc.g = g = QGridLayout(acc)
        acc.unla = la = QLabel(_("&Username:"******"&Password:"******"&Show password"), self.account)
        spw.stateChanged[int].connect(self.set_pw_echo_mode)
        g.addWidget(spw, 2, 0, 1, 2)
        self.rla = la = QLabel(_("For the scheduling to work, you must leave calibre running."))
        vt.addWidget(la)
        for b, c in iteritems(self.SCHEDULE_TYPES):
            b = getattr(self, b)
            b.toggled.connect(self.schedule_type_selected)
            b.setToolTip(textwrap.dedent(c.HELP))

        # Second tab (advanced settings)
        self.tab2 = t2 = QWidget()
        self.detail_box.addTab(self.tab2, _("&Advanced"))
        self.tab2.g = g = QGridLayout(t2)
        g.setContentsMargins(0, 0, 0, 0)
        self.add_title_tag = tt = QCheckBox(_("Add &title as tag"), t2)
        g.addWidget(tt, 0, 0, 1, 2)
        t2.la = la = QLabel(_("&Extra tags:"))
        self.custom_tags = ct = QLineEdit(self)
        la.setBuddy(ct)
        g.addWidget(la), g.addWidget(ct, 1, 1)
        t2.la2 = la = QLabel(_("&Keep at most:"))
        la.setToolTip(_("Maximum number of copies (issues) of this recipe to keep.  Set to 0 to keep all (disable)."))
        self.keep_issues = ki = QSpinBox(t2)
        tt.toggled['bool'].connect(self.keep_issues.setEnabled)
        ki.setMaximum(100000), la.setBuddy(ki)
        ki.setToolTip(_(
            "<p>When set, this option will cause calibre to keep, at most, the specified number of issues"
            " of this periodical. Every time a new issue is downloaded, the oldest one is deleted, if the"
            " total is larger than this number.\n<p>Note that this feature only works if you have the"
            " option to add the title as tag checked, above.\n<p>Also, the setting for deleting periodicals"
            " older than a number of days, below, takes priority over this setting."))
        ki.setSpecialValueText(_("all issues")), ki.setSuffix(_(" issues"))
        g.addWidget(la), g.addWidget(ki, 2, 1)
        si = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
        g.addItem(si, 3, 1, 1, 1)

        # Bottom area
        self.hb = h = QHBoxLayout()
        self.l.addLayout(h, 2, 1, 1, 1)
        self.labt = la = QLabel(_("Delete downloaded &news older than:"))
        self.old_news = on = QSpinBox(self)
        on.setToolTip(_(
            "<p>Delete downloaded news older than the specified number of days. Set to zero to disable.\n"
            "<p>You can also control the maximum number of issues of a specific periodical that are kept"
            " by clicking the Advanced tab for that periodical above."))
        on.setSpecialValueText(_("never delete")), on.setSuffix(_(" days"))
        on.setMaximum(1000), la.setBuddy(on)
        on.setValue(gconf['oldest_news'])
        h.addWidget(la), h.addWidget(on)
        self.download_all_button = b = QPushButton(QIcon(I('news.png')), _("Download &all scheduled"), self)
        b.setToolTip(_("Download all scheduled news sources at once"))
        b.clicked.connect(self.download_all_clicked)
        self.l.addWidget(b, 3, 0, 1, 1)
        self.bb = bb = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel, self)
        bb.accepted.connect(self.accept), bb.rejected.connect(self.reject)
        self.download_button = b = bb.addButton(_('&Download now'), bb.ActionRole)
        b.setIcon(QIcon(I('arrow-down.png'))), b.setVisible(False)
        b.clicked.connect(self.download_clicked)
        self.l.addWidget(bb, 3, 1, 1, 1)

        geom = gprefs.get('scheduler_dialog_geometry')
        if geom is not None:
            QApplication.instance().safe_restore_geometry(self, geom)

    def sizeHint(self):
        return QSize(800, 600)

    def set_pw_echo_mode(self, state):
        self.password.setEchoMode(self.password.Normal
                if state == Qt.Checked else self.password.Password)

    def schedule_type_selected(self, *args):
        for i, st in enumerate(self.SCHEDULE_TYPES):
            if getattr(self, st).isChecked():
                self.schedule_stack.setCurrentIndex(i)
                break

    def keyPressEvent(self, ev):
        if ev.key() not in (Qt.Key_Enter, Qt.Key_Return):
            return QDialog.keyPressEvent(self, ev)

    def break_cycles(self):
        try:
            self.recipe_model.searched.disconnect(self.search_done)
            self.recipe_model.searched.disconnect(self.search.search_done)
            self.search.search.disconnect()
            self.download.disconnect()
        except:
            pass
        self.recipe_model = None

    def search_done(self, *args):
        if self.recipe_model.showing_count < 10:
            self.recipes.expandAll()

    def toggle_schedule_info(self, *args):
        enabled = self.schedule.isChecked()
        for x in self.SCHEDULE_TYPES:
            getattr(self, x).setEnabled(enabled)
        self.schedule_stack.setEnabled(enabled)
        self.last_downloaded.setVisible(enabled)

    def current_changed(self, current, previous):
        if self.previous_urn is not None:
            self.commit(urn=self.previous_urn)
            self.previous_urn = None

        urn = self.current_urn
        if urn is not None:
            self.initialize_detail_box(urn)
        self.recipes.scrollTo(current)

    def accept(self):
        if not self.commit():
            return False
        self.save_geometry()
        return QDialog.accept(self)

    def reject(self):
        self.save_geometry()
        return QDialog.reject(self)

    def save_geometry(self):
        gprefs.set('scheduler_dialog_geometry', bytearray(self.saveGeometry()))

    def download_clicked(self, *args):
        self.commit()
        if self.commit() and self.current_urn:
            self.download.emit(self.current_urn)

    def download_all_clicked(self, *args):
        if self.commit() and self.commit():
            self.download.emit(None)

    @property
    def current_urn(self):
        current = self.recipes.currentIndex()
        if current.isValid():
            return getattr(current.internalPointer(), 'urn', None)

    def commit(self, urn=None):
        urn = self.current_urn if urn is None else urn
        if not self.detail_box.isVisible() or urn is None:
            return True

        if self.account.isVisible():
            un, pw = map(unicode_type, (self.username.text(), self.password.text()))
            un, pw = un.strip(), pw.strip()
            if not un and not pw and self.schedule.isChecked():
                if not getattr(self, 'subscription_optional', False):
                    error_dialog(self, _('Need username and password'),
                            _('You must provide a username and/or password to '
                                'use this news source.'), show=True)
                    return False
            if un or pw:
                self.recipe_model.set_account_info(urn, un, pw)
            else:
                self.recipe_model.clear_account_info(urn)

        if self.schedule.isChecked():
            schedule_type, schedule = \
                    self.schedule_stack.currentWidget().schedule
            self.recipe_model.schedule_recipe(urn, schedule_type, schedule)
        else:
            self.recipe_model.un_schedule_recipe(urn)

        add_title_tag = self.add_title_tag.isChecked()
        keep_issues = '0'
        if self.keep_issues.isEnabled():
            keep_issues = unicode_type(self.keep_issues.value())
        custom_tags = unicode_type(self.custom_tags.text()).strip()
        custom_tags = [x.strip() for x in custom_tags.split(',')]
        self.recipe_model.customize_recipe(urn, add_title_tag, custom_tags, keep_issues)
        return True

    def initialize_detail_box(self, urn):
        self.previous_urn = urn
        self.detail_box.setVisible(True)
        self.download_button.setVisible(True)
        self.detail_box.setCurrentIndex(0)
        recipe = self.recipe_model.recipe_from_urn(urn)
        try:
            schedule_info = self.recipe_model.schedule_info_from_urn(urn)
        except:
            # Happens if user does something stupid like unchecking all the
            # days of the week
            schedule_info = None
        account_info = self.recipe_model.account_info_from_urn(urn)
        customize_info = self.recipe_model.get_customize_info(urn)

        ns = recipe.get('needs_subscription', '')
        self.account.setVisible(ns in ('yes', 'optional'))
        self.subscription_optional = ns == 'optional'
        act = _('Account')
        act2 = _('(optional)') if self.subscription_optional else \
                _('(required)')
        self.account.setTitle(act+' '+act2)
        un = pw = ''
        if account_info is not None:
            un, pw = account_info[:2]
            if not un:
                un = ''
            if not pw:
                pw = ''
        self.username.setText(un)
        self.password.setText(pw)
        self.show_password.setChecked(False)

        self.blurb.setText('''
        <p>
        <b>%(title)s</b><br>
        %(cb)s %(author)s<br/>
        %(description)s
        </p>
        '''%dict(title=recipe.get('title'), cb=_('Created by: '),
            author=recipe.get('author', _('Unknown')),
            description=recipe.get('description', '')))
        self.download_button.setToolTip(
                _('Download %s now')%recipe.get('title'))
        scheduled = schedule_info is not None
        self.schedule.setChecked(scheduled)
        self.toggle_schedule_info()
        self.last_downloaded.setText(_('Last downloaded: never'))
        ld_text = _('never')
        if scheduled:
            typ, sch, last_downloaded = schedule_info
            d = utcnow() - last_downloaded

            def hm(x):
                return (x-x%3600)//3600, (x%3600 - (x%3600)%60)//60
            hours, minutes = hm(d.seconds)
            tm = _('%(days)d days, %(hours)d hours'
                    ' and %(mins)d minutes ago')%dict(
                            days=d.days, hours=hours, mins=minutes)
            if d < timedelta(days=366):
                ld_text = tm
        else:
            typ, sch = 'day/time', (-1, 6, 0)
        sch_widget = {'day/time': 0, 'days_of_week': 0, 'days_of_month':1,
                'interval':2}[typ]
        rb = getattr(self, list(self.SCHEDULE_TYPES)[sch_widget])
        rb.setChecked(True)
        self.schedule_stack.setCurrentIndex(sch_widget)
        self.schedule_stack.currentWidget().initialize(typ, sch)
        add_title_tag, custom_tags, keep_issues = customize_info
        self.add_title_tag.setChecked(add_title_tag)
        self.custom_tags.setText(', '.join(custom_tags))
        self.last_downloaded.setText(_('Last downloaded:') + ' ' + ld_text)
        try:
            keep_issues = int(keep_issues)
        except:
            keep_issues = 0
        self.keep_issues.setValue(keep_issues)
        self.keep_issues.setEnabled(self.add_title_tag.isChecked())
Beispiel #6
0
class Editor(QFrame):  # {{{

    editing_done = pyqtSignal(object)

    def __init__(self, parent=None):
        QFrame.__init__(self, parent)
        self.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
        self.setAutoFillBackground(True)
        self.capture = 0

        self.setFrameShape(self.StyledPanel)
        self.setFrameShadow(self.Raised)
        self._layout = l = QGridLayout(self)
        self.setLayout(l)

        self.header = QLabel('')
        l.addWidget(self.header, 0, 0, 1, 2)

        self.use_default = QRadioButton('')
        self.use_custom = QRadioButton(_('&Custom'))
        l.addWidget(self.use_default, 1, 0, 1, 3)
        l.addWidget(self.use_custom, 2, 0, 1, 3)
        self.use_custom.toggled.connect(self.custom_toggled)

        off = 2
        for which in (1, 2):
            text = _('&Shortcut:') if which == 1 else _('&Alternate shortcut:')
            la = QLabel(text)
            la.setStyleSheet('QLabel { margin-left: 1.5em }')
            l.addWidget(la, off + which, 0, 1, 3)
            setattr(self, 'label%d' % which, la)
            button = QPushButton(_('None'), self)
            button.clicked.connect(partial(self.capture_clicked, which=which))
            button.installEventFilter(self)
            setattr(self, 'button%d' % which, button)
            clear = QToolButton(self)
            clear.setIcon(QIcon(I('clear_left.png')))
            clear.clicked.connect(partial(self.clear_clicked, which=which))
            setattr(self, 'clear%d' % which, clear)
            l.addWidget(button, off + which, 1, 1, 1)
            l.addWidget(clear, off + which, 2, 1, 1)
            la.setBuddy(button)

        self.done_button = doneb = QPushButton(_('Done'), self)
        l.addWidget(doneb, 0, 2, 1, 1)
        doneb.clicked.connect(lambda: self.editing_done.emit(self))
        l.setColumnStretch(0, 100)

        self.custom_toggled(False)

    def initialize(self, shortcut, all_shortcuts):
        self.header.setText('<b>%s: %s</b>' %
                            (_('Customize'), shortcut['name']))
        self.all_shortcuts = all_shortcuts
        self.shortcut = shortcut

        self.default_keys = [
            QKeySequence(k, QKeySequence.SequenceFormat.PortableText)
            for k in shortcut['default_keys']
        ]
        self.current_keys = list(shortcut['keys'])
        default = ', '.join([
            unicode_type(k.toString(k.NativeText)) for k in self.default_keys
        ])
        if not default:
            default = _('None')
        current = ', '.join([
            unicode_type(k.toString(k.NativeText)) for k in self.current_keys
        ])
        if not current:
            current = _('None')

        self.use_default.setText(
            _('&Default: %(deflt)s [Currently not conflicting: %(curr)s]') %
            dict(deflt=default, curr=current))

        if shortcut['set_to_default']:
            self.use_default.setChecked(True)
        else:
            self.use_custom.setChecked(True)
            for key, which in zip(self.current_keys, [1, 2]):
                button = getattr(self, 'button%d' % which)
                button.setText(key.toString(key.NativeText))

    def custom_toggled(self, checked):
        for w in ('1', '2'):
            for o in ('label', 'button', 'clear'):
                getattr(self, o + w).setEnabled(checked)

    def capture_clicked(self, which=1):
        self.capture = which
        button = getattr(self, 'button%d' % which)
        button.setText(_('Press a key...'))
        button.setFocus(Qt.FocusReason.OtherFocusReason)
        button.setStyleSheet('QPushButton { font-weight: bold}')

    def clear_clicked(self, which=0):
        button = getattr(self, 'button%d' % which)
        button.setText(_('None'))

    def eventFilter(self, obj, event):
        if self.capture and obj in (self.button1, self.button2):
            t = event.type()
            if t == QEvent.Type.ShortcutOverride:
                event.accept()
                return True
            if t == QEvent.Type.KeyPress:
                self.key_press_event(event, 1 if obj is self.button1 else 2)
                return True
        return QFrame.eventFilter(self, obj, event)

    def key_press_event(self, ev, which=0):
        if self.capture == 0:
            return QWidget.keyPressEvent(self, ev)
        sequence = keysequence_from_event(ev)
        if sequence is None:
            return QWidget.keyPressEvent(self, ev)
        ev.accept()

        button = getattr(self, 'button%d' % which)
        button.setStyleSheet('QPushButton { font-weight: normal}')
        button.setText(
            sequence.toString(QKeySequence.SequenceFormat.NativeText))
        self.capture = 0
        dup_desc = self.dup_check(sequence)
        if dup_desc is not None:
            error_dialog(self,
                         _('Already assigned'),
                         unicode_type(
                             sequence.toString(
                                 QKeySequence.SequenceFormat.NativeText)) +
                         ' ' + _('already assigned to') + ' ' + dup_desc,
                         show=True)
            self.clear_clicked(which=which)

    def dup_check(self, sequence):
        for sc in self.all_shortcuts:
            if sc is self.shortcut:
                continue
            for k in sc['keys']:
                if k == sequence:
                    return sc['name']

    @property
    def custom_keys(self):
        if self.use_default.isChecked():
            return None
        ans = []
        for which in (1, 2):
            button = getattr(self, 'button%d' % which)
            t = unicode_type(button.text())
            if t == _('None'):
                continue
            ks = QKeySequence(t, QKeySequence.SequenceFormat.NativeText)
            if not ks.isEmpty():
                ans.append(ks)
        return tuple(ans)
Beispiel #7
0
class SchedulerDialog(QDialog):

    SCHEDULE_TYPES = OrderedDict([
            ('days_of_week', DaysOfWeek),
            ('days_of_month', DaysOfMonth),
            ('every_x_days', EveryXDays),
    ])

    download = pyqtSignal(object)

    def __init__(self, recipe_model, parent=None):
        QDialog.__init__(self, parent)
        self.commit_on_change = True
        self.previous_urn = None

        self.setWindowIcon(QIcon(I('scheduler.png')))
        self.setWindowTitle(_("Schedule news download"))
        self.l = l = QGridLayout(self)

        # Left panel
        self.h = h = QHBoxLayout()
        l.addLayout(h, 0, 0, 1, 1)
        self.search = s = SearchBox2(self)
        self.search.initialize('scheduler_search_history')
        self.search.setMinimumContentsLength(15)
        self.go_button = b = QToolButton(self)
        b.setText(_("Go"))
        b.clicked.connect(self.search.do_search)
        self.clear_search_button = cb = QToolButton(self)
        self.clear_search_button.clicked.connect(self.search.clear_clicked)
        cb.setIcon(QIcon(I('clear_left.png')))
        h.addWidget(s), h.addWidget(b), h.addWidget(cb)
        self.recipes = RecipesView(self)
        l.addWidget(self.recipes, 1, 0, 1, 1)
        self.recipe_model = recipe_model
        self.recipe_model.do_refresh()
        self.recipes.setModel(self.recipe_model)
        self.recipes.setFocus(Qt.OtherFocusReason)
        self.count_label = la = QLabel(_('%s news sources') % self.recipe_model.showing_count)
        la.setAlignment(Qt.AlignCenter)
        l.addWidget(la, 2, 0, 1, 1)
        self.search.search.connect(self.recipe_model.search)
        self.recipe_model.searched.connect(self.search.search_done, type=Qt.QueuedConnection)
        self.recipe_model.searched.connect(self.search_done)

        # Right Panel
        self.scroll_area = sa = QScrollArea(self)
        self.l.addWidget(sa, 0, 1, 2, 1)
        sa.setFrameShape(QFrame.NoFrame)
        sa.setWidgetResizable(True)
        self.scroll_area_contents = sac = QWidget(self)
        sa.setWidget(sac)
        sac.v = v = QVBoxLayout(sac)
        v.setContentsMargins(0, 0, 0, 0)
        self.detail_box = QTabWidget(self)
        self.detail_box.setVisible(False)
        self.detail_box.setCurrentIndex(0)
        v.addWidget(self.detail_box)
        v.addItem(QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding))

        # First Tab (scheduling)
        self.tab = QWidget()
        self.detail_box.addTab(self.tab, _("&Schedule"))
        self.tab.v = vt = QVBoxLayout(self.tab)
        vt.setContentsMargins(0, 0, 0, 0)
        self.blurb = la = QLabel('blurb')
        la.setWordWrap(True), la.setOpenExternalLinks(True)
        vt.addWidget(la)
        vt.addItem(QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding))
        self.frame = f = QFrame(self.tab)
        vt.addWidget(f)
        f.setFrameShape(f.StyledPanel)
        f.setFrameShadow(f.Raised)
        f.v = vf = QVBoxLayout(f)
        self.schedule = s = QCheckBox(_("&Schedule for download:"), f)
        self.schedule.stateChanged[int].connect(self.toggle_schedule_info)
        vf.addWidget(s)
        f.h = h = QHBoxLayout()
        vf.addLayout(h)
        self.days_of_week = QRadioButton(_("&Days of  week"), f)
        self.days_of_month = QRadioButton(_("Da&ys of month"), f)
        self.every_x_days = QRadioButton(_("Every &x days"), f)
        self.days_of_week.setChecked(True)
        h.addWidget(self.days_of_week), h.addWidget(self.days_of_month), h.addWidget(self.every_x_days)
        self.schedule_stack = ss = QStackedWidget(f)
        self.schedule_widgets = []
        for key in reversed(self.SCHEDULE_TYPES):
            self.schedule_widgets.insert(0, self.SCHEDULE_TYPES[key](self))
            self.schedule_stack.insertWidget(0, self.schedule_widgets[0])
        vf.addWidget(ss)
        self.last_downloaded = la = QLabel(f)
        la.setWordWrap(True)
        vf.addWidget(la)
        vt.addItem(QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding))
        self.account = acc = QGroupBox(self.tab)
        acc.setTitle(_("&Account"))
        vt.addWidget(acc)
        acc.g = g = QGridLayout(acc)
        acc.unla = la = QLabel(_("&Username:"******"&Password:"******"&Show password"), self.account)
        spw.stateChanged[int].connect(self.set_pw_echo_mode)
        g.addWidget(spw, 2, 0, 1, 2)
        self.rla = la = QLabel(_("For the scheduling to work, you must leave calibre running."))
        vt.addWidget(la)
        for b, c in self.SCHEDULE_TYPES.iteritems():
            b = getattr(self, b)
            b.toggled.connect(self.schedule_type_selected)
            b.setToolTip(textwrap.dedent(c.HELP))

        # Second tab (advanced settings)
        self.tab2 = t2 = QWidget()
        self.detail_box.addTab(self.tab2, _("&Advanced"))
        self.tab2.g = g = QGridLayout(t2)
        g.setContentsMargins(0, 0, 0, 0)
        self.add_title_tag = tt = QCheckBox(_("Add &title as tag"), t2)
        g.addWidget(tt, 0, 0, 1, 2)
        t2.la = la = QLabel(_("&Extra tags:"))
        self.custom_tags = ct = QLineEdit(self)
        la.setBuddy(ct)
        g.addWidget(la), g.addWidget(ct, 1, 1)
        t2.la2 = la = QLabel(_("&Keep at most:"))
        la.setToolTip(_("Maximum number of copies (issues) of this recipe to keep.  Set to 0 to keep all (disable)."))
        self.keep_issues = ki = QSpinBox(t2)
        tt.toggled['bool'].connect(self.keep_issues.setEnabled)
        ki.setMaximum(100000), la.setBuddy(ki)
        ki.setToolTip(_(
            "<p>When set, this option will cause calibre to keep, at most, the specified number of issues"
            " of this periodical. Every time a new issue is downloaded, the oldest one is deleted, if the"
            " total is larger than this number.\n<p>Note that this feature only works if you have the"
            " option to add the title as tag checked, above.\n<p>Also, the setting for deleting periodicals"
            " older than a number of days, below, takes priority over this setting."))
        ki.setSpecialValueText(_("all issues")), ki.setSuffix(_(" issues"))
        g.addWidget(la), g.addWidget(ki, 2, 1)
        si = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
        g.addItem(si, 3, 1, 1, 1)

        # Bottom area
        self.hb = h = QHBoxLayout()
        self.l.addLayout(h, 2, 1, 1, 1)
        self.labt = la = QLabel(_("Delete downloaded &news older than:"))
        self.old_news = on = QSpinBox(self)
        on.setToolTip(_(
            "<p>Delete downloaded news older than the specified number of days. Set to zero to disable.\n"
            "<p>You can also control the maximum number of issues of a specific periodical that are kept"
            " by clicking the Advanced tab for that periodical above."))
        on.setSpecialValueText(_("never delete")), on.setSuffix(_(" days"))
        on.setMaximum(1000), la.setBuddy(on)
        on.setValue(gconf['oldest_news'])
        h.addWidget(la), h.addWidget(on)
        self.download_all_button = b = QPushButton(QIcon(I('news.png')), _("Download &all scheduled"), self)
        b.setToolTip(_("Download all scheduled news sources at once"))
        b.clicked.connect(self.download_all_clicked)
        self.l.addWidget(b, 3, 0, 1, 1)
        self.bb = bb = QDialogButtonBox(QDialogButtonBox.Save, self)
        bb.accepted.connect(self.accept), bb.rejected.connect(self.reject)
        self.download_button = b = bb.addButton(_('&Download now'), bb.ActionRole)
        b.setIcon(QIcon(I('arrow-down.png'))), b.setVisible(False)
        b.clicked.connect(self.download_clicked)
        self.l.addWidget(bb, 3, 1, 1, 1)

        geom = gprefs.get('scheduler_dialog_geometry')
        if geom is not None:
            self.restoreGeometry(geom)

    def sizeHint(self):
        return QSize(800, 600)

    def set_pw_echo_mode(self, state):
        self.password.setEchoMode(self.password.Normal
                if state == Qt.Checked else self.password.Password)

    def schedule_type_selected(self, *args):
        for i, st in enumerate(self.SCHEDULE_TYPES):
            if getattr(self, st).isChecked():
                self.schedule_stack.setCurrentIndex(i)
                break

    def keyPressEvent(self, ev):
        if ev.key() not in (Qt.Key_Enter, Qt.Key_Return):
            return QDialog.keyPressEvent(self, ev)

    def break_cycles(self):
        try:
            self.recipe_model.searched.disconnect(self.search_done)
            self.recipe_model.searched.disconnect(self.search.search_done)
            self.search.search.disconnect()
            self.download.disconnect()
        except:
            pass
        self.recipe_model = None

    def search_done(self, *args):
        if self.recipe_model.showing_count < 10:
            self.recipes.expandAll()

    def toggle_schedule_info(self, *args):
        enabled = self.schedule.isChecked()
        for x in self.SCHEDULE_TYPES:
            getattr(self, x).setEnabled(enabled)
        self.schedule_stack.setEnabled(enabled)
        self.last_downloaded.setVisible(enabled)

    def current_changed(self, current, previous):
        if self.previous_urn is not None:
            self.commit(urn=self.previous_urn)
            self.previous_urn = None

        urn = self.current_urn
        if urn is not None:
            self.initialize_detail_box(urn)
        self.recipes.scrollTo(current)

    def accept(self):
        if not self.commit():
            return False
        self.save_geometry()
        return QDialog.accept(self)

    def reject(self):
        self.save_geometry()
        return QDialog.reject(self)

    def save_geometry(self):
        gprefs.set('scheduler_dialog_geometry', bytearray(self.saveGeometry()))

    def download_clicked(self, *args):
        self.commit()
        if self.commit() and self.current_urn:
            self.download.emit(self.current_urn)

    def download_all_clicked(self, *args):
        if self.commit() and self.commit():
            self.download.emit(None)

    @property
    def current_urn(self):
        current = self.recipes.currentIndex()
        if current.isValid():
            return getattr(current.internalPointer(), 'urn', None)

    def commit(self, urn=None):
        urn = self.current_urn if urn is None else urn
        if not self.detail_box.isVisible() or urn is None:
            return True

        if self.account.isVisible():
            un, pw = map(unicode, (self.username.text(), self.password.text()))
            un, pw = un.strip(), pw.strip()
            if not un and not pw and self.schedule.isChecked():
                if not getattr(self, 'subscription_optional', False):
                    error_dialog(self, _('Need username and password'),
                            _('You must provide a username and/or password to '
                                'use this news source.'), show=True)
                    return False
            if un or pw:
                self.recipe_model.set_account_info(urn, un, pw)
            else:
                self.recipe_model.clear_account_info(urn)

        if self.schedule.isChecked():
            schedule_type, schedule = \
                    self.schedule_stack.currentWidget().schedule
            self.recipe_model.schedule_recipe(urn, schedule_type, schedule)
        else:
            self.recipe_model.un_schedule_recipe(urn)

        add_title_tag = self.add_title_tag.isChecked()
        keep_issues = u'0'
        if self.keep_issues.isEnabled():
            keep_issues = unicode(self.keep_issues.value())
        custom_tags = unicode(self.custom_tags.text()).strip()
        custom_tags = [x.strip() for x in custom_tags.split(',')]
        self.recipe_model.customize_recipe(urn, add_title_tag, custom_tags, keep_issues)
        return True

    def initialize_detail_box(self, urn):
        self.previous_urn = urn
        self.detail_box.setVisible(True)
        self.download_button.setVisible(True)
        self.detail_box.setCurrentIndex(0)
        recipe = self.recipe_model.recipe_from_urn(urn)
        try:
            schedule_info = self.recipe_model.schedule_info_from_urn(urn)
        except:
            # Happens if user does something stupid like unchecking all the
            # days of the week
            schedule_info = None
        account_info = self.recipe_model.account_info_from_urn(urn)
        customize_info = self.recipe_model.get_customize_info(urn)

        ns = recipe.get('needs_subscription', '')
        self.account.setVisible(ns in ('yes', 'optional'))
        self.subscription_optional = ns == 'optional'
        act = _('Account')
        act2 = _('(optional)') if self.subscription_optional else \
                _('(required)')
        self.account.setTitle(act+' '+act2)
        un = pw = ''
        if account_info is not None:
            un, pw = account_info[:2]
            if not un:
                un = ''
            if not pw:
                pw = ''
        self.username.setText(un)
        self.password.setText(pw)
        self.show_password.setChecked(False)

        self.blurb.setText('''
        <p>
        <b>%(title)s</b><br>
        %(cb)s %(author)s<br/>
        %(description)s
        </p>
        '''%dict(title=recipe.get('title'), cb=_('Created by: '),
            author=recipe.get('author', _('Unknown')),
            description=recipe.get('description', '')))
        self.download_button.setToolTip(
                _('Download %s now')%recipe.get('title'))
        scheduled = schedule_info is not None
        self.schedule.setChecked(scheduled)
        self.toggle_schedule_info()
        self.last_downloaded.setText(_('Last downloaded: never'))
        ld_text = _('never')
        if scheduled:
            typ, sch, last_downloaded = schedule_info
            d = utcnow() - last_downloaded
            def hm(x):
                return (x-x%3600)//3600, (x%3600 - (x%3600)%60)//60
            hours, minutes = hm(d.seconds)
            tm = _('%(days)d days, %(hours)d hours'
                    ' and %(mins)d minutes ago')%dict(
                            days=d.days, hours=hours, mins=minutes)
            if d < timedelta(days=366):
                ld_text = tm
        else:
            typ, sch = 'day/time', (-1, 6, 0)
        sch_widget = {'day/time': 0, 'days_of_week': 0, 'days_of_month':1,
                'interval':2}[typ]
        rb = getattr(self, list(self.SCHEDULE_TYPES)[sch_widget])
        rb.setChecked(True)
        self.schedule_stack.setCurrentIndex(sch_widget)
        self.schedule_stack.currentWidget().initialize(typ, sch)
        add_title_tag, custom_tags, keep_issues = customize_info
        self.add_title_tag.setChecked(add_title_tag)
        self.custom_tags.setText(u', '.join(custom_tags))
        self.last_downloaded.setText(_('Last downloaded:') + ' ' + ld_text)
        try:
            keep_issues = int(keep_issues)
        except:
            keep_issues = 0
        self.keep_issues.setValue(keep_issues)
        self.keep_issues.setEnabled(self.add_title_tag.isChecked())
Beispiel #8
0
class Editor(QFrame):  # {{{

    editing_done = pyqtSignal(object)

    def __init__(self, parent=None):
        QFrame.__init__(self, parent)
        self.setFocusPolicy(Qt.StrongFocus)
        self.setAutoFillBackground(True)
        self.capture = 0

        self.setFrameShape(self.StyledPanel)
        self.setFrameShadow(self.Raised)
        self._layout = l = QGridLayout(self)
        self.setLayout(l)

        self.header = QLabel('')
        l.addWidget(self.header, 0, 0, 1, 2)

        self.use_default = QRadioButton('')
        self.use_custom = QRadioButton(_('Custom'))
        l.addWidget(self.use_default, 1, 0, 1, 3)
        l.addWidget(self.use_custom, 2, 0, 1, 3)
        self.use_custom.toggled.connect(self.custom_toggled)

        off = 2
        for which in (1, 2):
            text = _('&Shortcut:') if which == 1 else _('&Alternate shortcut:')
            la = QLabel(text)
            la.setStyleSheet('QLabel { margin-left: 1.5em }')
            l.addWidget(la, off+which, 0, 1, 3)
            setattr(self, 'label%d'%which, la)
            button = QPushButton(_('None'), self)
            button.clicked.connect(partial(self.capture_clicked, which=which))
            button.keyPressEvent = partial(self.key_press_event, which=which)
            setattr(self, 'button%d'%which, button)
            clear = QToolButton(self)
            clear.setIcon(QIcon(I('clear_left.png')))
            clear.clicked.connect(partial(self.clear_clicked, which=which))
            setattr(self, 'clear%d'%which, clear)
            l.addWidget(button, off+which, 1, 1, 1)
            l.addWidget(clear, off+which, 2, 1, 1)
            la.setBuddy(button)

        self.done_button = doneb = QPushButton(_('Done'), self)
        l.addWidget(doneb, 0, 2, 1, 1)
        doneb.clicked.connect(lambda : self.editing_done.emit(self))
        l.setColumnStretch(0, 100)

        self.custom_toggled(False)

    def initialize(self, shortcut, all_shortcuts):
        self.header.setText('<b>%s: %s</b>'%(_('Customize'), shortcut['name']))
        self.all_shortcuts = all_shortcuts
        self.shortcut = shortcut

        self.default_keys = [QKeySequence(k, QKeySequence.PortableText) for k
                in shortcut['default_keys']]
        self.current_keys = list(shortcut['keys'])
        default = ', '.join([unicode(k.toString(k.NativeText)) for k in
                    self.default_keys])
        if not default:
            default = _('None')
        current = ', '.join([unicode(k.toString(k.NativeText)) for k in
                    self.current_keys])
        if not current:
            current = _('None')

        self.use_default.setText(_('Default: %(deflt)s [Currently not conflicting: %(curr)s]')%
                dict(deflt=default, curr=current))

        if shortcut['set_to_default']:
            self.use_default.setChecked(True)
        else:
            self.use_custom.setChecked(True)
            for key, which in zip(self.current_keys, [1,2]):
                button = getattr(self, 'button%d'%which)
                button.setText(key.toString(key.NativeText))

    def custom_toggled(self, checked):
        for w in ('1', '2'):
            for o in ('label', 'button', 'clear'):
                getattr(self, o+w).setEnabled(checked)

    def capture_clicked(self, which=1):
        self.capture = which
        button = getattr(self, 'button%d'%which)
        button.setText(_('Press a key...'))
        button.setFocus(Qt.OtherFocusReason)
        button.setStyleSheet('QPushButton { font-weight: bold}')

    def clear_clicked(self, which=0):
        button = getattr(self, 'button%d'%which)
        button.setText(_('None'))

    def key_press_event(self, ev, which=0):
        if self.capture == 0:
            return QWidget.keyPressEvent(self, ev)
        sequence = keysequence_from_event(ev)
        if sequence is None:
            return QWidget.keyPressEvent(self, ev)
        ev.accept()

        button = getattr(self, 'button%d'%which)
        button.setStyleSheet('QPushButton { font-weight: normal}')
        button.setText(sequence.toString(QKeySequence.NativeText))
        self.capture = 0
        dup_desc = self.dup_check(sequence)
        if dup_desc is not None:
            error_dialog(self, _('Already assigned'),
                    unicode(sequence.toString(QKeySequence.NativeText)) + ' ' +
                    _('already assigned to') + ' ' + dup_desc, show=True)
            self.clear_clicked(which=which)

    def dup_check(self, sequence):
        for sc in self.all_shortcuts:
            if sc is self.shortcut:
                continue
            for k in sc['keys']:
                if k == sequence:
                    return sc['name']

    @property
    def custom_keys(self):
        if self.use_default.isChecked():
            return None
        ans = []
        for which in (1, 2):
            button = getattr(self, 'button%d'%which)
            t = unicode(button.text())
            if t == _('None'):
                continue
            ks = QKeySequence(t, QKeySequence.NativeText)
            if not ks.isEmpty():
                ans.append(ks)
        return tuple(ans)
Beispiel #9
0
    def request_octo_init_settings(self, wizard, method):
        vbox = QVBoxLayout()
        next_enabled = True
        label = QLabel(_("Enter a label to name your device:"))
        name = QLineEdit()
        hl = QHBoxLayout()
        hl.addWidget(label)
        hl.addWidget(name)
        hl.addStretch(1)
        vbox.addLayout(hl)

        def clean_text(widget):
            text = widget.toPlainText().strip()
            return ' '.join(text.split())

        if method in [TIM_NEW, TIM_RECOVER]:
            gb = QGroupBox()
            hbox1 = QHBoxLayout()
            gb.setLayout(hbox1)
            vbox.addWidget(gb)
            gb.setTitle(_("Select your seed length:"))
            bg_numwords = QButtonGroup()
            for i, count in enumerate([12, 18, 24]):
                rb = QRadioButton(gb)
                rb.setText(_("%d words") % count)
                bg_numwords.addButton(rb)
                bg_numwords.setId(rb, i)
                hbox1.addWidget(rb)
                rb.setChecked(True)
            cb_pin = QCheckBox(_('Enable PIN protection'))
            cb_pin.setChecked(True)
        else:
            text = QTextEdit()
            text.setMaximumHeight(60)
            if method == TIM_MNEMONIC:
                msg = _("Enter your BIP39 mnemonic:")
            else:
                msg = _("Enter the master private key beginning with xprv:")

                def set_enabled():
                    from electrum.keystore import is_xprv
                    wizard.next_button.setEnabled(is_xprv(clean_text(text)))

                text.textChanged.connect(set_enabled)
                next_enabled = False

            vbox.addWidget(QLabel(msg))
            vbox.addWidget(text)
            pin = QLineEdit()
            pin.setValidator(QRegExpValidator(QRegExp('[1-9]{0,9}')))
            pin.setMaximumWidth(100)
            hbox_pin = QHBoxLayout()
            hbox_pin.addWidget(QLabel(_("Enter your PIN (digits 1-9):")))
            hbox_pin.addWidget(pin)
            hbox_pin.addStretch(1)

        if method in [TIM_NEW, TIM_RECOVER]:
            vbox.addWidget(WWLabel(RECOMMEND_PIN))
            vbox.addWidget(cb_pin)
        else:
            vbox.addLayout(hbox_pin)

        passphrase_msg = WWLabel(PASSPHRASE_HELP_SHORT)
        passphrase_warning = WWLabel(PASSPHRASE_NOT_PIN)
        passphrase_warning.setStyleSheet("color: red")
        cb_phrase = QCheckBox(_('Enable passphrases'))
        cb_phrase.setChecked(False)
        vbox.addWidget(passphrase_msg)
        vbox.addWidget(passphrase_warning)
        vbox.addWidget(cb_phrase)

        # ask for recovery type (random word order OR matrix)
        if method == TIM_RECOVER:
            gb_rectype = QGroupBox()
            hbox_rectype = QHBoxLayout()
            gb_rectype.setLayout(hbox_rectype)
            vbox.addWidget(gb_rectype)
            gb_rectype.setTitle(_("Select recovery type:"))
            bg_rectype = QButtonGroup()

            rb1 = QRadioButton(gb_rectype)
            rb1.setText(_('Scrambled words'))
            bg_rectype.addButton(rb1)
            bg_rectype.setId(rb1, RECOVERY_TYPE_SCRAMBLED_WORDS)
            hbox_rectype.addWidget(rb1)
            rb1.setChecked(True)

            rb2 = QRadioButton(gb_rectype)
            rb2.setText(_('Matrix'))
            bg_rectype.addButton(rb2)
            bg_rectype.setId(rb2, RECOVERY_TYPE_MATRIX)
            hbox_rectype.addWidget(rb2)
        else:
            bg_rectype = None

        wizard.exec_layout(vbox, next_enabled=next_enabled)

        if method in [TIM_NEW, TIM_RECOVER]:
            item = bg_numwords.checkedId()
            pin = cb_pin.isChecked()
            recovery_type = bg_rectype.checkedId() if bg_rectype else None
        else:
            item = ' '.join(str(clean_text(text)).split())
            pin = str(pin.text())
            recovery_type = None

        return (item, name.text(), pin, cb_phrase.isChecked(), recovery_type)
Beispiel #10
0
class ConversionDialog(Dialog):
    def __init__(self, parent, force_entire_book=False):
        self.prefs = self.prefsPrep()
        self.parent = parent
        self.force_entire_book = force_entire_book
        self.criteria = None
        Dialog.__init__(self, _('Chinese Conversion'), 'chinese_conversion_dialog', parent)

    def setup_ui(self):

        # Create layout for entire dialog
        layout = QVBoxLayout(self)
        self.setLayout(layout)

        #Create a scroll area for the top part of the dialog
        self.scrollArea = QScrollArea(self)
        self.scrollArea.setWidgetResizable(True)

        # Create widget for all the contents of the dialog except the OK and Cancel buttons
        self.scrollContentWidget = QWidget(self.scrollArea)
        self.scrollArea.setWidget(self.scrollContentWidget)
        widgetLayout = QVBoxLayout(self.scrollContentWidget)

        # Add scrollArea to dialog
        layout.addWidget(self.scrollArea)

        self.other_group_box = QGroupBox(_('Other Changes'))
        widgetLayout.addWidget(self.other_group_box)
        other_group_box_layout = QVBoxLayout()
        self.other_group_box.setLayout(other_group_box_layout)

        text_dir_layout = QHBoxLayout()
        other_group_box_layout.addLayout(text_dir_layout)
        direction_label = QLabel(_('Text Direction:'))
        text_dir_layout.addWidget(direction_label)
        self.text_dir_combo = QComboBox()
        text_dir_layout.addWidget(self.text_dir_combo)
        self.text_dir_combo.addItems([_('No Conversion'), _('Horizontal'), _('Vertical')])
        self.text_dir_combo.setToolTip(_('Select the desired text orientation'))
        self.text_dir_combo.currentIndexChanged.connect(self.update_gui)


        self.optimization_group_box = QGroupBox(_('Reader Device Optimization'))
        other_group_box_layout.addWidget(self.optimization_group_box)
        optimization_group_box_layout = QVBoxLayout()
        self.optimization_group_box.setLayout(optimization_group_box_layout)
        
        punc_group=QButtonGroup(self)
        self.text_dir_punc_none_button = QRadioButton("""No presentation optimization""")
        optimization_group_box_layout.addWidget(self.text_dir_punc_none_button)
        self.text_dir_punc_button = QRadioButton("""Optimize presentation for Readium reader""")
        self.text_dir_punc_button.setToolTip(_('Use vert/horiz punctuation presentation forms for Chrome Readium Epub3 reader'))
        optimization_group_box_layout.addWidget(self.text_dir_punc_button)
        self.text_dir_punc_kindle_button = QRadioButton("""Optimize presentation for Kindle reader""")
        self.text_dir_punc_kindle_button.setToolTip(_('Use vert/horiz puncuation presentation forms for Kindle reader'))
        optimization_group_box_layout.addWidget(self.text_dir_punc_kindle_button)
        self.text_dir_punc_none_button.toggled.connect(self.update_gui)
        self.text_dir_punc_button.toggled.connect(self.update_gui)
        self.text_dir_punc_kindle_button.toggled.connect(self.update_gui)

        source_group=QButtonGroup(self)
        self.file_source_button = QRadioButton(_('Selected File Only'))
        self.book_source_button = QRadioButton(_('Entire eBook'))
        source_group.addButton(self.file_source_button)
        source_group.addButton(self.book_source_button)
        self.source_group_box = QGroupBox(_('Source'))
        if not self.force_entire_book:
            widgetLayout.addWidget(self.source_group_box)
            source_group_box_layout = QVBoxLayout()
            self.source_group_box.setLayout(source_group_box_layout)
            source_group_box_layout.addWidget(self.file_source_button)
            source_group_box_layout.addWidget(self.book_source_button)

        layout.addSpacing(10)
        self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)

        self.button_box.accepted.connect(self._ok_clicked)
        self.button_box.rejected.connect(self.reject)
        layout.addWidget(self.button_box)

        if not self.force_entire_book:
            self.file_source_button.setChecked(self.prefs['use_html_file'])
            self.book_source_button.setChecked(self.prefs['use_entire_book'])
        else:
            self.file_source_button.setChecked(False)
            self.book_source_button.setChecked(True)

        self.text_dir_combo.setCurrentIndex(self.prefs['orientation'])
        self.text_dir_punc_none_button.setChecked(self.prefs['no_optimization'])
        self.text_dir_punc_button.setChecked(self.prefs['readium_optimization'])
        self.text_dir_punc_kindle_button.setChecked(self.prefs['kindle_optimization'])
        self.update_gui()

    def update_gui(self):

        if self.text_dir_combo.currentIndex() == 0:
            self.optimization_group_box.setEnabled(False)
            self.text_dir_punc_none_button.setEnabled(False)
            self.text_dir_punc_button.setEnabled(False)
            self.text_dir_punc_kindle_button.setEnabled(False)
        else:
            self.optimization_group_box.setEnabled(True)
            self.text_dir_punc_none_button.setEnabled(True)
            self.text_dir_punc_button.setEnabled(True)
            self.text_dir_punc_kindle_button.setEnabled(True)
            
    def _ok_clicked(self):

        optimization_mode = 0
        if self.text_dir_punc_button.isChecked():
            optimization_mode = 1    #Readium
        if self.text_dir_punc_kindle_button.isChecked():
            optimization_mode = 2    #Kindle
 
        self.criteria = (self.text_dir_combo.currentIndex(), optimization_mode)
        self.savePrefs()
        self.accept()

    def getCriteria(self):
        return self.criteria

    def prefsPrep(self):
        from calibre.utils.config import JSONConfig
        plugin_prefs = JSONConfig('plugins/{0}_ChineseConversion_settings'.format(PLUGIN_SAFE_NAME))
        plugin_prefs.defaults['use_html_file'] = True
        plugin_prefs.defaults['use_entire_book'] = True
        plugin_prefs.defaults['orientation'] = 0
        plugin_prefs.defaults['no_optimization'] = True
        plugin_prefs.defaults['readium_optimization'] = False
        plugin_prefs.defaults['kindle_optimization'] = False
        return plugin_prefs

    def savePrefs(self):
        self.prefs['use_html_file'] = self.file_source_button.isChecked()
        self.prefs['use_entire_book'] = self.book_source_button.isChecked()
        self.prefs['orientation'] = self.text_dir_combo.currentIndex()
        self.prefs['no_optimization'] = self.text_dir_punc_none_button.isChecked()
        self.prefs['readium_optimization'] = self.text_dir_punc_button.isChecked()
        self.prefs['kindle_optimization'] = self.text_dir_punc_kindle_button.isChecked()
Beispiel #11
0
class ConversionDialog(Dialog):
    def __init__(self, parent, force_entire_book=False):
        self.prefs = self.prefsPrep()
        self.parent = parent
        self.force_entire_book = force_entire_book
        self.criteria = None
        Dialog.__init__(self, _('Chinese Conversion'),
                        'chinese_conversion_dialog', parent)

    def setup_ui(self):
        self.quote_for_trad_target = _("Update quotes: "",'' -> 「」,『』")
        self.quote_for_simp_target = _("Update quotes: 「」,『』 -> "",''")

        # Create layout for entire dialog
        layout = QVBoxLayout(self)
        self.setLayout(layout)

        #Create a scroll area for the top part of the dialog
        self.scrollArea = QScrollArea(self)
        self.scrollArea.setWidgetResizable(True)

        # Create widget for all the contents of the dialog except the OK and Cancel buttons
        self.scrollContentWidget = QWidget(self.scrollArea)
        self.scrollArea.setWidget(self.scrollContentWidget)
        widgetLayout = QVBoxLayout(self.scrollContentWidget)

        # Add scrollArea to dialog
        layout.addWidget(self.scrollArea)

        self.operation_group_box = QGroupBox(_('Conversion Direction'))
        widgetLayout.addWidget(self.operation_group_box)
        operation_group_box_layout = QVBoxLayout()
        self.operation_group_box.setLayout(operation_group_box_layout)

        operation_group = QButtonGroup(self)
        self.no_conversion_button = QRadioButton(_('No Conversion'))
        operation_group.addButton(self.no_conversion_button)
        self.trad_to_simp_button = QRadioButton(_('Traditional to Simplified'))
        operation_group.addButton(self.trad_to_simp_button)
        self.simp_to_trad_button = QRadioButton(_('Simplified to Traditional'))
        operation_group.addButton(self.simp_to_trad_button)
        self.trad_to_trad_button = QRadioButton(
            _('Traditional to Traditional'))
        operation_group.addButton(self.trad_to_trad_button)
        operation_group_box_layout.addWidget(self.no_conversion_button)
        operation_group_box_layout.addWidget(self.trad_to_simp_button)
        operation_group_box_layout.addWidget(self.simp_to_trad_button)
        operation_group_box_layout.addWidget(self.trad_to_trad_button)
        self.no_conversion_button.toggled.connect(self.update_gui)
        self.trad_to_simp_button.toggled.connect(self.update_gui)
        self.simp_to_trad_button.toggled.connect(self.update_gui)
        self.trad_to_trad_button.toggled.connect(self.update_gui)

        self.style_group_box = QGroupBox(_('Language Styles'))
        widgetLayout.addWidget(self.style_group_box)
        style_group_box_layout = QVBoxLayout()
        self.style_group_box.setLayout(style_group_box_layout)

        input_layout = QHBoxLayout()
        style_group_box_layout.addLayout(input_layout)
        self.input_region_label = QLabel(_('Input:'))
        input_layout.addWidget(self.input_region_label)
        self.input_combo = QComboBox()
        input_layout.addWidget(self.input_combo)
        self.input_combo.addItems([_('Mainland'), _('Hong Kong'), _('Taiwan')])
        self.input_combo.setToolTip(_('Select the origin region of the input'))
        self.input_combo.currentIndexChanged.connect(self.update_gui)

        output_layout = QHBoxLayout()
        style_group_box_layout.addLayout(output_layout)
        self.output_region_label = QLabel(_('Output:'))
        output_layout.addWidget(self.output_region_label)
        self.output_combo = QComboBox()
        output_layout.addWidget(self.output_combo)
        self.output_combo.addItems(
            [_('Mainland'), _('Hong Kong'),
             _('Taiwan')])
        self.output_combo.setToolTip(
            _('Select the desired region of the output'))
        self.output_combo.currentIndexChanged.connect(self.update_gui)

        self.use_target_phrases = QCheckBox(
            _('Use output target phrases if possible'))
        self.use_target_phrases.setToolTip(
            _('Check to allow region specific word replacements if available'))
        style_group_box_layout.addWidget(self.use_target_phrases)
        self.use_target_phrases.stateChanged.connect(self.update_gui)

        self.quotation_group_box = QGroupBox(_('Quotation Marks'))
        widgetLayout.addWidget(self.quotation_group_box)
        quotation_group_box_layout = QVBoxLayout()
        self.quotation_group_box.setLayout(quotation_group_box_layout)

        quotation_group = QButtonGroup(self)
        self.quotation_no_conversion_button = QRadioButton(_('No Conversion'))
        quotation_group.addButton(self.quotation_no_conversion_button)
        self.quotation_trad_to_simp_button = QRadioButton(
            self.quote_for_simp_target)
        quotation_group.addButton(self.quotation_trad_to_simp_button)
        self.quotation_simp_to_trad_button = QRadioButton(
            self.quote_for_trad_target)
        quotation_group.addButton(self.quotation_simp_to_trad_button)
        quotation_group_box_layout.addWidget(
            self.quotation_no_conversion_button)
        quotation_group_box_layout.addWidget(
            self.quotation_simp_to_trad_button)
        quotation_group_box_layout.addWidget(
            self.quotation_trad_to_simp_button)
        self.quotation_no_conversion_button.toggled.connect(self.update_gui)
        self.quotation_trad_to_simp_button.toggled.connect(self.update_gui)
        self.quotation_simp_to_trad_button.toggled.connect(self.update_gui)
        self.use_smart_quotes = QCheckBox(
            """Use curved 'Smart" quotes if applicable""")
        self.use_smart_quotes.setToolTip(
            _('Use smart curved half-width quotes rather than straight full-width quotes'
              ))
        quotation_group_box_layout.addWidget(self.use_smart_quotes)
        self.use_smart_quotes.stateChanged.connect(self.update_gui)

        self.other_group_box = QGroupBox(_('Other Changes'))
        widgetLayout.addWidget(self.other_group_box)
        other_group_box_layout = QVBoxLayout()
        self.other_group_box.setLayout(other_group_box_layout)

        text_dir_layout = QHBoxLayout()
        other_group_box_layout.addLayout(text_dir_layout)
        direction_label = QLabel(_('Text Direction:'))
        text_dir_layout.addWidget(direction_label)
        self.text_dir_combo = QComboBox()
        text_dir_layout.addWidget(self.text_dir_combo)
        self.text_dir_combo.addItems(
            [_('No Conversion'),
             _('Horizontal'),
             _('Vertical')])
        self.text_dir_combo.setToolTip(
            _('Select the desired text orientation'))
        self.text_dir_combo.currentIndexChanged.connect(self.update_gui)

        self.optimization_group_box = QGroupBox(
            _('Reader Device Optimization'))
        other_group_box_layout.addWidget(self.optimization_group_box)
        optimization_group_box_layout = QVBoxLayout()
        self.optimization_group_box.setLayout(optimization_group_box_layout)

        punc_group = QButtonGroup(self)
        self.text_dir_punc_none_button = QRadioButton(
            """No presentation optimization""")
        optimization_group_box_layout.addWidget(self.text_dir_punc_none_button)
        self.text_dir_punc_button = QRadioButton(
            """Optimize presentation for Readium reader""")
        self.text_dir_punc_button.setToolTip(
            _('Use vert/horiz punctuation presentation forms for Chrome Readium Epub3 reader'
              ))
        optimization_group_box_layout.addWidget(self.text_dir_punc_button)
        self.text_dir_punc_kindle_button = QRadioButton(
            """Optimize presentation for Kindle reader""")
        self.text_dir_punc_kindle_button.setToolTip(
            _('Use vert/horiz puncuation presentation forms for Kindle reader')
        )
        optimization_group_box_layout.addWidget(
            self.text_dir_punc_kindle_button)
        self.text_dir_punc_none_button.toggled.connect(self.update_gui)
        self.text_dir_punc_button.toggled.connect(self.update_gui)
        self.text_dir_punc_kindle_button.toggled.connect(self.update_gui)

        source_group = QButtonGroup(self)
        self.file_source_button = QRadioButton(_('Selected File Only'))
        self.book_source_button = QRadioButton(_('Entire eBook'))
        source_group.addButton(self.file_source_button)
        source_group.addButton(self.book_source_button)
        self.source_group_box = QGroupBox(_('Source'))
        if not self.force_entire_book:
            widgetLayout.addWidget(self.source_group_box)
            source_group_box_layout = QVBoxLayout()
            self.source_group_box.setLayout(source_group_box_layout)
            source_group_box_layout.addWidget(self.file_source_button)
            source_group_box_layout.addWidget(self.book_source_button)

        layout.addSpacing(10)
        self.button_box = QDialogButtonBox(QDialogButtonBox.Ok
                                           | QDialogButtonBox.Cancel)

        self.button_box.accepted.connect(self._ok_clicked)
        self.button_box.rejected.connect(self.reject)
        layout.addWidget(self.button_box)

        self.input_combo.setCurrentIndex(self.prefs['input_format'])
        self.output_combo.setCurrentIndex(self.prefs['output_format'])
        self.no_conversion_button.setChecked(self.prefs['no_conversion'])
        self.trad_to_simp_button.setChecked(self.prefs['trad_to_simp'])
        self.simp_to_trad_button.setChecked(self.prefs['simp_to_trad'])
        self.trad_to_trad_button.setChecked(self.prefs['trad_to_trad'])
        if not self.force_entire_book:
            self.file_source_button.setChecked(self.prefs['use_html_file'])
            self.book_source_button.setChecked(self.prefs['use_entire_book'])
        else:
            self.file_source_button.setChecked(False)
            self.book_source_button.setChecked(True)

        self.quotation_no_conversion_button.setChecked(
            self.prefs['quote_no_conversion'])
        self.quotation_trad_to_simp_button.setChecked(
            self.prefs['quote_trad_to_simp'])
        self.quotation_simp_to_trad_button.setChecked(
            self.prefs['quote_simp_to_trad'])

        self.use_smart_quotes.setChecked(self.prefs['use_smart_quotes'])
        self.text_dir_combo.setCurrentIndex(self.prefs['orientation'])
        self.text_dir_punc_none_button.setChecked(
            self.prefs['no_optimization'])
        self.text_dir_punc_button.setChecked(
            self.prefs['readium_optimization'])
        self.text_dir_punc_kindle_button.setChecked(
            self.prefs['kindle_optimization'])
        self.update_gui()

    def update_gui(self):
        if (self.quotation_trad_to_simp_button.isChecked()):
            self.use_smart_quotes.setEnabled(True)
        else:
            self.use_smart_quotes.setEnabled(False)

        if self.text_dir_combo.currentIndex() == 0:
            self.optimization_group_box.setEnabled(False)
            self.text_dir_punc_none_button.setEnabled(False)
            self.text_dir_punc_button.setEnabled(False)
            self.text_dir_punc_kindle_button.setEnabled(False)
        else:
            self.optimization_group_box.setEnabled(True)
            self.text_dir_punc_none_button.setEnabled(True)
            self.text_dir_punc_button.setEnabled(True)
            self.text_dir_punc_kindle_button.setEnabled(True)

        if self.no_conversion_button.isChecked():
            self.input_combo.setEnabled(False)
            self.output_combo.setEnabled(False)
            self.use_target_phrases.setEnabled(False)
            self.output_region_label.setEnabled(False)
            self.input_region_label.setEnabled(False)
            self.style_group_box.setEnabled(False)

        elif self.trad_to_simp_button.isChecked():
            self.input_combo.setEnabled(True)
            #only mainland output locale for simplified output
            self.output_combo.setCurrentIndex(0)
            self.output_combo.setEnabled(False)
            self.use_target_phrases.setEnabled(True)
            self.output_region_label.setEnabled(False)
            self.input_region_label.setEnabled(True)
            self.style_group_box.setEnabled(True)

        elif self.simp_to_trad_button.isChecked():
            #only mainland input locale for simplified input
            self.input_combo.setCurrentIndex(0)
            self.input_combo.setEnabled(False)
            self.output_combo.setEnabled(True)
            self.use_target_phrases.setEnabled(True)
            self.output_region_label.setEnabled(True)
            self.input_region_label.setEnabled(False)
            self.style_group_box.setEnabled(True)

        elif self.trad_to_trad_button.isChecked():
            #Trad->Trad
            #currently only mainland input locale for Trad->Trad
            self.input_combo.setCurrentIndex(0)
            self.input_combo.setEnabled(False)
            self.output_combo.setEnabled(True)
            self.use_target_phrases.setEnabled(True)
            self.output_region_label.setEnabled(True)
            self.input_region_label.setEnabled(False)
            self.style_group_box.setEnabled(True)

        else:
            self.input_combo.setEnabled(True)
            self.output_combo.setEnabled(True)
            self.use_target_phrases.setEnabled(True)
            self.style_group_box.setEnabled(True)
            self.output_region_label.setEnabled(True)
            self.input_region_label.setEnabled(True)

    def _ok_clicked(self):
        output_mode = 0
        if self.trad_to_simp_button.isChecked():
            output_mode = 1  #trad -> simp
        if self.simp_to_trad_button.isChecked():
            output_mode = 2  #simp -> trad
        elif self.trad_to_trad_button.isChecked():
            output_mode = 3  #trad -> trad

        quote_mode = 0
        if self.quotation_trad_to_simp_button.isChecked():
            quote_mode = 1  #trad -> simp
        if self.quotation_simp_to_trad_button.isChecked():
            quote_mode = 2  #simp -> trad

        optimization_mode = 0
        if self.text_dir_punc_button.isChecked():
            optimization_mode = 1  #Readium
        if self.text_dir_punc_kindle_button.isChecked():
            optimization_mode = 2  #Kindle

        self.criteria = (self.file_source_button.isChecked(), output_mode,
                         self.input_combo.currentIndex(),
                         self.output_combo.currentIndex(),
                         self.use_target_phrases.isChecked(), quote_mode,
                         self.use_smart_quotes.isChecked(),
                         self.text_dir_combo.currentIndex(), optimization_mode)
        self.savePrefs()
        self.accept()

    def getCriteria(self):
        return self.criteria

    def prefsPrep(self):
        from calibre.utils.config import JSONConfig
        plugin_prefs = JSONConfig(
            'plugins/{0}_ChineseConversion_settings'.format(PLUGIN_SAFE_NAME))
        plugin_prefs.defaults['input_format'] = 0
        plugin_prefs.defaults['output_format'] = 0
        plugin_prefs.defaults['no_conversion'] = True
        plugin_prefs.defaults['trad_to_simp'] = False
        plugin_prefs.defaults['use_html_file'] = True
        plugin_prefs.defaults['simp_to_trad'] = False
        plugin_prefs.defaults['trad_to_trad'] = False
        plugin_prefs.defaults['use_entire_book'] = True
        plugin_prefs.defaults['use_target_phrases'] = True
        plugin_prefs.defaults['quote_no_conversion'] = True
        plugin_prefs.defaults['quote_trad_to_simp'] = False
        plugin_prefs.defaults['quote_simp_to_trad'] = False
        plugin_prefs.defaults['use_smart_quotes'] = False
        plugin_prefs.defaults['orientation'] = 0
        plugin_prefs.defaults['no_optimization'] = True
        plugin_prefs.defaults['readium_optimization'] = False
        plugin_prefs.defaults['kindle_optimization'] = False
        return plugin_prefs

    def savePrefs(self):
        self.prefs['input_format'] = self.input_combo.currentIndex()
        self.prefs['output_format'] = self.output_combo.currentIndex()
        self.prefs['no_conversion'] = self.no_conversion_button.isChecked()
        self.prefs['trad_to_simp'] = self.trad_to_simp_button.isChecked()
        self.prefs['use_html_file'] = self.file_source_button.isChecked()
        self.prefs['simp_to_trad'] = self.simp_to_trad_button.isChecked()
        self.prefs['trad_to_trad'] = self.trad_to_trad_button.isChecked()
        self.prefs['use_entire_book'] = self.book_source_button.isChecked()
        self.prefs['use_target_phrases'] = self.use_target_phrases.isChecked()
        self.prefs[
            'quote_no_conversion'] = self.quotation_no_conversion_button.isChecked(
            )
        self.prefs[
            'quote_trad_to_simp'] = self.quotation_trad_to_simp_button.isChecked(
            )
        self.prefs[
            'quote_simp_to_trad'] = self.quotation_simp_to_trad_button.isChecked(
            )
        self.prefs['use_smart_quotes'] = self.use_smart_quotes.isChecked()
        self.prefs['orientation'] = self.text_dir_combo.currentIndex()
        self.prefs[
            'no_optimization'] = self.text_dir_punc_none_button.isChecked()
        self.prefs[
            'readium_optimization'] = self.text_dir_punc_button.isChecked()
        self.prefs[
            'kindle_optimization'] = self.text_dir_punc_kindle_button.isChecked(
            )
Beispiel #12
0
class Editor(QFrame):  # {{{

    editing_done = pyqtSignal(object)

    def __init__(self, parent=None):
        QFrame.__init__(self, parent)
        self.setFocusPolicy(Qt.StrongFocus)
        self.setAutoFillBackground(True)
        self.capture = 0

        self.setFrameShape(self.StyledPanel)
        self.setFrameShadow(self.Raised)
        self._layout = l = QGridLayout(self)
        self.setLayout(l)

        self.header = QLabel('')
        l.addWidget(self.header, 0, 0, 1, 2)

        self.use_default = QRadioButton('')
        self.use_custom = QRadioButton(_('Custom'))
        l.addWidget(self.use_default, 1, 0, 1, 3)
        l.addWidget(self.use_custom, 2, 0, 1, 3)
        self.use_custom.toggled.connect(self.custom_toggled)

        off = 2
        for which in (1, 2):
            text = _('&Shortcut:') if which == 1 else _('&Alternate shortcut:')
            la = QLabel(text)
            la.setStyleSheet('QLabel { margin-left: 1.5em }')
            l.addWidget(la, off + which, 0, 1, 3)
            setattr(self, 'label%d' % which, la)
            button = QPushButton(_('None'), self)
            button.clicked.connect(partial(self.capture_clicked, which=which))
            button.keyPressEvent = partial(self.key_press_event, which=which)
            setattr(self, 'button%d' % which, button)
            clear = QToolButton(self)
            clear.setIcon(QIcon(I('clear_left.png')))
            clear.clicked.connect(partial(self.clear_clicked, which=which))
            setattr(self, 'clear%d' % which, clear)
            l.addWidget(button, off + which, 1, 1, 1)
            l.addWidget(clear, off + which, 2, 1, 1)
            la.setBuddy(button)

        self.done_button = doneb = QPushButton(_('Done'), self)
        l.addWidget(doneb, 0, 2, 1, 1)
        doneb.clicked.connect(lambda: self.editing_done.emit(self))
        l.setColumnStretch(0, 100)

        self.custom_toggled(False)

    def initialize(self, shortcut, all_shortcuts):
        self.header.setText('<b>%s: %s</b>' %
                            (_('Customize'), shortcut['name']))
        self.all_shortcuts = all_shortcuts
        self.shortcut = shortcut

        self.default_keys = [
            QKeySequence(k, QKeySequence.PortableText)
            for k in shortcut['default_keys']
        ]
        self.current_keys = list(shortcut['keys'])
        default = ', '.join(
            [unicode(k.toString(k.NativeText)) for k in self.default_keys])
        if not default:
            default = _('None')
        current = ', '.join(
            [unicode(k.toString(k.NativeText)) for k in self.current_keys])
        if not current:
            current = _('None')

        self.use_default.setText(
            _('Default: %(deflt)s [Currently not conflicting: %(curr)s]') %
            dict(deflt=default, curr=current))

        if shortcut['set_to_default']:
            self.use_default.setChecked(True)
        else:
            self.use_custom.setChecked(True)
            for key, which in zip(self.current_keys, [1, 2]):
                button = getattr(self, 'button%d' % which)
                button.setText(key.toString(key.NativeText))

    def custom_toggled(self, checked):
        for w in ('1', '2'):
            for o in ('label', 'button', 'clear'):
                getattr(self, o + w).setEnabled(checked)

    def capture_clicked(self, which=1):
        self.capture = which
        button = getattr(self, 'button%d' % which)
        button.setText(_('Press a key...'))
        button.setFocus(Qt.OtherFocusReason)
        button.setStyleSheet('QPushButton { font-weight: bold}')

    def clear_clicked(self, which=0):
        button = getattr(self, 'button%d' % which)
        button.setText(_('None'))

    def key_press_event(self, ev, which=0):
        code = ev.key()
        if self.capture == 0 or code in (0, Qt.Key_unknown, Qt.Key_Shift,
                                         Qt.Key_Control, Qt.Key_Alt,
                                         Qt.Key_Meta, Qt.Key_AltGr,
                                         Qt.Key_CapsLock, Qt.Key_NumLock,
                                         Qt.Key_ScrollLock):
            return QWidget.keyPressEvent(self, ev)
        button = getattr(self, 'button%d' % which)
        button.setStyleSheet('QPushButton { font-weight: normal}')
        mods = int(ev.modifiers()) & ~Qt.KeypadModifier
        txt = unicode(ev.text())
        if txt and txt.lower() == txt.upper():
            # We have a symbol like ! or > etc. In this case the value of code
            # already includes Shift, so remove it
            mods &= ~Qt.ShiftModifier
        sequence = QKeySequence(code | mods)
        button.setText(sequence.toString(QKeySequence.NativeText))
        self.capture = 0
        dup_desc = self.dup_check(sequence)
        if dup_desc is not None:
            error_dialog(self,
                         _('Already assigned'),
                         unicode(sequence.toString(QKeySequence.NativeText)) +
                         ' ' + _('already assigned to') + ' ' + dup_desc,
                         show=True)
            self.clear_clicked(which=which)

    def dup_check(self, sequence):
        for sc in self.all_shortcuts:
            if sc is self.shortcut:
                continue
            for k in sc['keys']:
                if k == sequence:
                    return sc['name']

    @property
    def custom_keys(self):
        if self.use_default.isChecked():
            return None
        ans = []
        for which in (1, 2):
            button = getattr(self, 'button%d' % which)
            t = unicode(button.text())
            if t == _('None'):
                continue
            ks = QKeySequence(t, QKeySequence.NativeText)
            if not ks.isEmpty():
                ans.append(ks)
        return tuple(ans)
Beispiel #13
0
class OnePlot(QWidget):
    ''' Widget de tracé d'une courbe Y=f(X)'''

    Ylabels = {
        "position": ("x [pixel]", "y [pixel]"),
        "position_mm": ("x [mm]", "y [mm]")
    }

    def __init__(self, mainWindow):

        # call the base class constructor:
        QWidget.__init__(self, mainWindow)

        self.mw = mainWindow  # remember he application main windwos

        # Attributes (persistant data)
        self.__figure = Figure()  # the plot figure
        self.__axes = None  # axis system
        self.__canvas = None  # area for matplotlib plot
        self.__toolbar = None  # plot tool bar
        self.__xlim = None  # xmin, xmay of the plo
        self.__ylim = None  # ymin, ymax of the plot
        self.__axes_aspect = 'equal'  # 'equal' or 'auto'

        self.btn_imageSize = QRadioButton("ImageSize", self)
        self.btn_autoSize = QRadioButton("AutoSize", self)
        self.btn_axesEqual = QRadioButton("Equal", self)
        self.btn_axesAuto = QRadioButton("Auto", self)

        group = QButtonGroup(self)  # pour les 2 boutons imageSize, autoSize
        group.addButton(self.btn_imageSize)
        group.addButton(self.btn_autoSize)

        group = QButtonGroup(self)
        group.addButton(self.btn_axesEqual)
        group.addButton(self.btn_axesAuto)

        self.__initUI()  # Initialisation de l'interface utilisateur

    def __initUI(self):
        '''To initialize or configure all the widgets on the screen.'''
        self.__figure.subplots_adjust(left=0.1,
                                      right=0.98,
                                      bottom=0.1,
                                      top=0.95)
        self.__axes = self.__figure.add_subplot(111)
        self.__canvas = FigureCanvas(self.__figure)
        self.__toolbar = NavigationToolbar(self.__canvas, self)

        self.btn_axesEqual.toggled.connect(lambda: self.__SetAspect("equal"))
        self.btn_axesEqual.setEnabled(False)
        texte = "Tracé dans des axes orthonormés"
        self.btn_axesEqual.setStatusTip(texte)
        self.btn_axesEqual.setChecked(True)

        self.btn_axesAuto.toggled.connect(lambda: self.__SetAspect("auto"))
        self.btn_axesAuto.setEnabled(False)
        texte = "Tracé dans des axes non orthonormés"
        self.btn_axesAuto.setStatusTip(texte)

        self.btn_imageSize.toggled.connect(self.__ImageSizePlotXYLim)
        self.btn_imageSize.setEnabled(False)
        texte = "Tracé avec les bornes min et max de l'image"
        self.btn_imageSize.setStatusTip(texte)
        self.btn_imageSize.setChecked(True)

        self.btn_autoSize.toggled.connect(self.__AutoSizePlotXYLim)
        self.btn_autoSize.setEnabled(False)
        texte = "Tracé avec les bornes min et max de la "
        texte += "trajectoire calculée"
        self.btn_autoSize.setStatusTip(texte)

        vbox = QVBoxLayout()
        self.setLayout(vbox)
        vbox.addWidget(self.__canvas)
        # last raw of the display :
        #  HBox[toolbar <<<strech>>> VBox [ HBOX[Equal Auto]        ] ]
        #                                 [ HBox[ImageSize AutoSize]]
        hbox = QHBoxLayout()
        hbox.addWidget(self.__toolbar)

        vb = QVBoxLayout()
        hbox.addStretch()
        hbox.addLayout(vb)

        hb = QHBoxLayout()
        hb.addWidget(self.btn_axesEqual)
        hb.addWidget(self.btn_axesAuto)
        vb.addLayout(hb)
        hb = QHBoxLayout()
        hb.addWidget(self.btn_imageSize)
        hb.addWidget(self.btn_autoSize)
        vb.addLayout(hb)

        vbox.addLayout(hbox)

    def __SetAspect(self, aspect):
        self.__axes_aspect = aspect
        self.__axes.set_aspect(aspect)
        self.__canvas.draw()

    def __AutoSizePlotXYLim(self):

        if self.mw.target_pos is None: return

        xlabel, ylabel = "X [pixels]", "Y [pixels]"
        scale = self.mw.imageTab.pix_to_mm_coeff

        X, Y = self.mw.target_pos[0], self.mw.target_pos[1]
        self.__xlim = np.array([np.nanmin(X), np.nanmax(X)]) * scale
        self.__ylim = np.array([np.nanmin(Y), np.nanmax(Y)]) * scale

        if self.mw.imageTab.valid_scale:
            xlabel, ylabel = "X [mm]", "Y [mm]"

        offset = (self.__ylim[1] - self.__ylim[0]) / 10
        self.__ylim += np.array([-offset, offset])

        self.__axes.set_xlim(*self.__xlim)
        self.__axes.set_ylim(*self.__ylim)
        self.__axes.set_xlabel(xlabel)
        self.__axes.set_ylabel(ylabel)

        self.__axes.set_aspect(self.__axes_aspect)
        self.__canvas.draw()

    def __ImageSizePlotXYLim(self):

        if self.mw.imageTab.video_size is None: return

        xlabel, ylabel = "X [pixels]", "Y [pixels]"
        w, h = self.mw.imageTab.video_size
        scale = self.mw.imageTab.pix_to_mm_coeff

        self.__xlim = np.array([0, w - 1], dtype=float) * scale
        self.__ylim = np.array([0, h - 1], dtype=float) * scale

        if self.mw.imageTab.valid_scale:
            xlabel, ylabel = "X [mm]", "Y [mm]"

        self.__axes.set_xlim(*self.__xlim)
        self.__axes.set_ylim(*self.__ylim)
        self.__axes.set_xlabel(xlabel)
        self.__axes.set_ylabel(ylabel)

        self.__axes.set_aspect(self.__axes_aspect)
        self.__canvas.draw()

    def ClearAxes(self):
        self.__axes.clear()
        self.__canvas.draw()

    def Plot(self):

        target_pos = self.mw.target_pos
        X, Y, I = target_pos
        scale = self.mw.imageTab.pix_to_mm_coeff

        self.btn_imageSize.setEnabled(True)
        self.btn_autoSize.setEnabled(True)
        self.btn_axesEqual.setEnabled(True)
        self.btn_axesAuto.setEnabled(True)

        # Effacement automatique si demandé à chaque nouveau tracé :
        if self.mw.flags["autoClearTraj"]: self.__axes.clear()

        # Récupération du nom de l'alagorithme de traitement :
        algo = self.mw.imageTab.btn_algo.currentText()

        # AutoSize an EqualSize plot
        self.__ImageSizePlotXYLim()
        self.__SetAspect("equal")

        # tracé de courbe paramétrée (x(t),y(t)) :
        color = 'b' if self.mw.target_RGB is None else self.mw.target_RGB / 255
        self.__axes.plot(X * scale,
                         Y * scale,
                         color=color,
                         marker='o',
                         markersize=2,
                         linewidth=.4,
                         label="Trajectoire XY / algo : {}".format(algo))
        self.__axes.grid(True)
        self.__axes.legend(loc='best', fontsize=10)
        self.__axes.set_aspect(self.__axes_aspect)
        self.__canvas.draw()
Beispiel #14
0
class TwoPlots(QWidget):
    ''' Widget to plot 2 curves x(t) & y(t), or Vx(t) & Vy(t)'''

    Ylabels = {
        "position": ("X [pixel]", "Y [pixel]"),
        "velocity": ("VX [pixel/s]", "VY [pixel/s]"),
        "position_mm": ("X [mm]", "Y [mm]"),
        "velocity_mm": ("VX [mm/s]", "VY [mm/s]")
    }

    CurveLabels = {
        "position": ("X(t) [{}]", "Y(t) [{}]"),
        "velocity": ("VX(t) {}", "VY(t) {}")
    }

    def __init__(self, mainWindow, quantity):

        # appel du constructeur de la classe de base :
        QWidget.__init__(self, mainWindow)

        self.mw = mainWindow  # la fenêtre de l'application principale

        # Attributs (objets persistants)
        self.__quantity = quantity  # "position" or "velocity"
        self.__data1 = None  # data for the first plot
        self.__data2 = None  # data for tthe second
        self.__figure = None  # figure tracé
        self.__axes1 = None  # système d'axes tracé 1
        self.__axes2 = None  # système d'axes tracé 2
        self.__canvas = None  # pour le tracé matplot.lib
        self.__toolbar = None  # barre d'outils tracé
        self.__time = None  # abcissa values for plot
        self.__xlim = None  # xmin, xmay tracé
        self.__xlabel = None  # étiquette axe X (n° image ou temps [s])
        self.__ylim1 = None  # ymin, ymax tracé x(t)
        self.__ylim2 = None  # ymin, ymax tracé y(t)

        self.btn_imageSize = QRadioButton("ImageSize", self)
        self.btn_autoSize = QRadioButton("AutoSize", self)

        self.btn_smooth_Vx = QCheckBox("Lissage Vx", self)
        self.btn_smooth_Vy = QCheckBox("Lissage Vy", self)
        self.x_mav_nb_pts = QSpinBox(parent=self)  # X velocity moving average
        self.y_mav_nb_pts = QSpinBox(parent=self)  # Y velocity moving average

        self.__initUI()  # Initialisation de l'interface utilisateur

    def __initUI(self):

        if self.__quantity == "position":

            for w in (self.btn_smooth_Vx, self.btn_smooth_Vy,
                      self.x_mav_nb_pts, self.y_mav_nb_pts):
                w.setVisible(False)
                w.setEnabled(False)

            group = QButtonGroup(self)
            group.addButton(self.btn_imageSize)
            group.addButton(self.btn_autoSize)

            self.btn_imageSize.toggled.connect(self.__ImageSizePlotXYLim)
            self.btn_imageSize.setEnabled(False)
            texte = "Tracé avec les bornes min et max de l'image"
            self.btn_imageSize.setStatusTip(texte)
            self.btn_imageSize.setChecked(True)

            self.btn_autoSize.toggled.connect(self.__AutoSizePlotXYLim)
            self.btn_autoSize.setEnabled(False)
            texte = "Tracé avec les bornes min et max de la "
            texte += "trajectoire calculée"
            self.btn_autoSize.setStatusTip(texte)

        elif self.__quantity == "velocity":

            for w in (self.btn_imageSize, self.btn_autoSize):
                w.setVisible(False)
                w.setEnabled(False)

            self.btn_smooth_Vx.stateChanged.connect(self.__smooth_Vx_wanted)
            self.btn_smooth_Vy.stateChanged.connect(self.__smooth_Vy_wanted)

            self.x_mav_nb_pts.setEnabled(False)
            self.y_mav_nb_pts.setEnabled(False)
            self.x_mav_nb_pts.setRange(3, 100)
            self.y_mav_nb_pts.setRange(3, 100)
            self.x_mav_nb_pts.setSingleStep(2)
            self.y_mav_nb_pts.setSingleStep(2)
            self.x_mav_nb_pts.valueChanged.connect(self.__smooth_vX)
            self.y_mav_nb_pts.valueChanged.connect(self.__smooth_vY)

        vbox = QVBoxLayout()
        self.setLayout(vbox)

        # Ligne 1 : tracé de l'image
        self.setLayout(vbox)
        self.__figure = Figure()
        self.__axes1 = self.__figure.add_subplot(211)
        self.__axes2 = self.__figure.add_subplot(212)
        self.__figure.subplots_adjust(left=0.120,
                                      right=0.99,
                                      bottom=0.11,
                                      top=0.98)
        self.__canvas = FigureCanvas(self.__figure)
        self.__toolbar = NavigationToolbar(self.__canvas, self)
        #self.__toolbar.setMinimumWidth(450)
        vbox.addWidget(self.__canvas)

        hbox = QHBoxLayout()
        hbox.addWidget(self.__toolbar)
        hbox.addStretch()

        if self.__quantity == "position":
            hbox.addWidget(self.btn_imageSize)
            hbox.addWidget(self.btn_autoSize)

        elif self.__quantity == "velocity":
            vb = QVBoxLayout()
            hb = QHBoxLayout()
            hb.addWidget(self.btn_smooth_Vx)
            hb.addWidget(self.x_mav_nb_pts)
            vb.addLayout(hb)
            hb = QHBoxLayout()
            hb.addWidget(self.btn_smooth_Vy)
            hb.addWidget(self.y_mav_nb_pts)
            vb.addLayout(hb)
            hbox.addLayout(vb)

        vbox.addLayout(hbox)

    def reset(self):
        if self.__quantity == "velocity":
            for w in (self.btn_smooth_Vx, self.btn_smooth_Vy,
                      self.x_mav_nb_pts, self.y_mav_nb_pts):
                w.setVisible(True)
                w.setEnabled(True)
            self.x_mav_nb_pts.setValue(self.x_mav_nb_pts.minimum())
            self.y_mav_nb_pts.setValue(self.y_mav_nb_pts.minimum())
            self.btn_smooth_Vx.setCheckState(Qt.Unchecked)
            self.btn_smooth_Vy.setCheckState(Qt.Unchecked)

    def __smooth_Vx_wanted(self, checked):
        if checked:
            self.x_mav_nb_pts.setEnabled(True)
        else:
            self.x_mav_nb_pts.setEnabled(False)
        self.Plot()

    def __smooth_Vy_wanted(self, checked):
        if checked:
            self.y_mav_nb_pts.setEnabled(True)
        else:
            self.y_mav_nb_pts.setEnabled(False)
        self.Plot()

    def __smooth_vX(self, nb_pts):
        if self.btn_smooth_Vx.isChecked():
            self.Plot()
        else:
            pass

    def __smooth_vY(self, nb_pts):
        if self.btn_smooth_Vy.isChecked():
            self.Plot()
        else:
            pass

    def __compute_velocity(self, U, plot_id):
        """Computes the velocity with the centered finite difference of order 1 :
           V[i] = (U[i+1] - U[i-1])/(T[i+1] - T[i-1])  for i in [1,N-1]
           V[0] = (U[1] - U[0])/(T[1] - T[0])
           V[-1] = (U[-1] - U[-2])/(T[-1] - T[-2])
        """
        V = U.copy()
        T = self.__time
        V[0] = (U[1] - U[0]) / (T[1] - T[0])
        V[-1] = (U[-1] - U[-2]) / (T[-1] - T[-2])
        V[1:-1] = (U[2:] - U[:-2]) / (T[2:] - T[:-2])

        if plot_id == "x":
            if self.btn_smooth_Vx.isChecked():
                N = self.x_mav_nb_pts.value()
                V = self.__smooth_data(V, N)
        elif plot_id == "y":
            if self.btn_smooth_Vy.isChecked():
                N = self.y_mav_nb_pts.value()
                V = self.__smooth_data(V, N)
        return V

    def __smooth_data(self, U, nb_pts):
        """Computes the nb_pts moving average on U."""
        N = nb_pts
        V = U.copy()
        V.fill(np.nan)
        mav = deque(maxlen=N)
        # initialize the mav (moving average)
        for e in U[:N]:
            mav.append(e)
        # move!
        index, count = N // 2, 0
        while count < V.shape[0] - N:
            V[index] = np.mean(mav)
            mav.append(U[N + count])
            count += 1
            index += 1

        return V

    def Plot(self):

        target_pos = self.mw.target_pos
        if target_pos is None:
            return
        else:
            self.__data1, self.__data2, I = target_pos
        scale = self.mw.imageTab.pix_to_mm_coeff

        if self.__quantity == "position":
            self.btn_imageSize.setEnabled(True)
            self.btn_autoSize.setEnabled(True)
        elif self.__quantity == "velocity":
            pass

        # Effacement automatiqe si demandé à chaque nouveau tracé :
        if self.mw.flags["autoClearTraj"]:
            if self.__axes1 is not None: self.__axes1.clear()
            if self.__axes2 is not None: self.__axes2.clear()

        # Récupération du nom de l'alagorithme de traitement :
        algo = self.mw.imageTab.btn_algo.currentText()

        # Récupération de la valeur de FP (Frame per seconde) pour calcul
        # du pas de temps et des abscisses :
        deltaT = None
        if self.mw.imageTab.video_FPS is not None:
            deltaT = 1. / self.mw.imageTab.video_FPS
            self.__time = np.array(I) * deltaT
            self.__xlabel = "temps [s]"
        else:
            self.__time = np.array(I)
            self.__xlabel = "image #"

        if self.__quantity == "velocity":
            if deltaT is not None:
                self.__data1 = self.__compute_velocity(self.__data1, "x")
                self.__data2 = self.__compute_velocity(self.__data2, "y")
                if self.btn_smooth_Vx.isChecked():
                    N = self.x_mav_nb_pts.value()
                if self.btn_smooth_Vy.isChecked():
                    N = self.y_mav_nb_pts.value()
                self.__AutoSizePlotXYLim()
            else:
                self.__data1, self.__data2 = None, None
            self.mw.target_veloc = np.array([self.__data1, self.__data2])
        else:
            self.__ImageSizePlotXYLim()

        if self.__data1 is None or self.__data2 is None: return

        curveLabelX, curveLabelY = TwoPlots.CurveLabels[self.__quantity]
        if self.__quantity == "position":
            Xlabel, Ylabel = curveLabelX.format(algo), curveLabelY.format(algo)
        else:
            Xlabel, Ylabel = curveLabelX.format(""), curveLabelY.format("")

        color = 'b' if self.mw.target_RGB is None else self.mw.target_RGB / 255

        # tracé de courbe x(t)
        self.__axes1.plot(self.__time,
                          self.__data1 * scale,
                          color=color,
                          marker='o',
                          markersize=2,
                          linewidth=.4,
                          label=Xlabel)
        self.__axes1.grid(True)
        #self.__axes1.legend(fontsize=9, framealpha=0.7,
        #                   bbox_to_anchor=(-0.1, 1.1), loc='upper left')
        self.__axes1.legend(loc='best', fontsize=10)

        # tracé de courbe y(t)
        self.__axes2.plot(self.__time,
                          self.__data2 * scale,
                          color=color,
                          marker='o',
                          markersize=2,
                          linewidth=.4,
                          label=Ylabel)
        self.__axes2.grid(True)
        #self.__axes2.legend(fontsize=9, framealpha=0.7,
        #                   bbox_to_anchor=(1.1, 1.1), loc='upper right')
        self.__axes2.legend(loc='best', fontsize=10)

        self.__canvas.draw()

    def __AutoSizePlotXYLim(self):

        if self.mw.target_pos is None: return

        y1label, y2label = TwoPlots.Ylabels[self.__quantity]
        self.__xlim = np.array(
            [np.nanmin(self.__time),
             np.nanmax(self.__time)])

        scale = self.mw.imageTab.pix_to_mm_coeff

        if not self.btn_smooth_Vx.isChecked():
            self.__ylim1 = np.array(
                [np.nanmin(self.__data1),
                 np.nanmax(self.__data1)]) * scale
            offset = (self.__ylim1[1] - self.__ylim1[0]) / 10
            self.__ylim1 += np.array([-offset, offset])
        else:
            #self.__ylim1 = self.__axes1.get_ylim()
            pass

        if not self.btn_smooth_Vy.isChecked():
            self.__ylim2 = np.array(
                [np.nanmin(self.__data2),
                 np.nanmax(self.__data2)]) * scale
            offset = (self.__ylim2[1] - self.__ylim2[0]) / 10
            self.__ylim2 += np.array([-offset, offset])
        else:
            #self.__ylim2 = self.__axes2.get_ylim()
            pass

        if self.mw.imageTab.valid_scale:
            y1label, y2label = TwoPlots.Ylabels[self.__quantity + "_mm"]

        self.__axes1.set_xlim(*self.__xlim)
        self.__axes2.set_xlim(*self.__xlim)
        self.__axes1.set_ylim(*self.__ylim1)
        self.__axes2.set_ylim(*self.__ylim2)
        self.__axes1.set_ylabel(y1label)
        self.__axes2.set_ylabel(y2label)
        self.__axes2.set_xlabel(self.__xlabel)

        self.__canvas.draw()

    def __ImageSizePlotXYLim(self):

        if self.mw.target_pos is None: return

        scale = self.mw.imageTab.pix_to_mm_coeff

        y1label, y2label = TwoPlots.Ylabels[self.__quantity]
        w, h = self.mw.imageTab.video_size
        self.__xlim = np.array(
            [np.nanmin(self.__time),
             np.nanmax(self.__time)])
        self.__ylim1 = np.array([0, w - 1], dtype=float) * scale
        self.__ylim2 = np.array([0, h - 1], dtype=float) * scale

        if self.mw.imageTab.valid_scale:
            y1label, y2label = TwoPlots.Ylabels[self.__quantity + "_mm"]

        self.__axes1.set_xlim(*self.__xlim)
        self.__axes2.set_xlim(*self.__xlim)
        self.__axes1.set_ylim(*self.__ylim1)
        self.__axes2.set_ylim(*self.__ylim2)
        self.__axes1.set_ylabel(y1label)
        self.__axes2.set_ylabel(y2label)
        self.__axes2.set_xlabel(self.__xlabel)

        self.__canvas.draw()

    def ClearAxes(self):
        if self.__axes1 is not None: self.__axes1.clear()
        if self.__axes2 is not None: self.__axes2.clear()
        self.__canvas.draw()
Beispiel #15
0
class MakeBrickDialog(QDialog):
    def __init__(self, parent, modal=True, flags=Qt.WindowFlags()):
        QDialog.__init__(self, parent, flags)
        self.model = None
        self._model_dir = None
        self.setModal(modal)
        self.setWindowTitle("Convert sources to FITS brick")
        lo = QVBoxLayout(self)
        lo.setContentsMargins(10, 10, 10, 10)
        lo.setSpacing(5)
        # file selector
        self.wfile = FileSelector(self,
                                  label="FITS filename:",
                                  dialog_label="Output FITS file",
                                  default_suffix="fits",
                                  file_types="FITS files (*.fits *.FITS)",
                                  file_mode=QFileDialog.ExistingFile)
        lo.addWidget(self.wfile)
        # reference frequency
        lo1 = QHBoxLayout()
        lo.addLayout(lo1)
        lo1.setContentsMargins(0, 0, 0, 0)
        label = QLabel("Frequency, MHz:", self)
        lo1.addWidget(label)
        tip = """<P>If your sky model contains spectral information (such as spectral indices), then a brick may be generated
    for a specific frequency. If a frequency is not specified here, the reference frequency of the model sources will be assumed.</P>"""
        self.wfreq = QLineEdit(self)
        self.wfreq.setValidator(QDoubleValidator(self))
        label.setToolTip(tip)
        self.wfreq.setToolTip(tip)
        lo1.addWidget(self.wfreq)
        # beam gain
        lo1 = QHBoxLayout()
        lo.addLayout(lo1)
        lo1.setContentsMargins(0, 0, 0, 0)
        self.wpb_apply = QCheckBox("Apply primary beam expression:", self)
        self.wpb_apply.setChecked(True)
        lo1.addWidget(self.wpb_apply)
        tip = """<P>If this option is specified, a primary power beam gain will be applied to the sources before inserting
    them into the brick. This can be any valid Python expression making use of the variables 'r' (corresponding
    to distance from field centre, in radians) and 'fq' (corresponding to frequency.)</P>"""
        self.wpb_exp = QLineEdit(self)
        self.wpb_apply.setToolTip(tip)
        self.wpb_exp.setToolTip(tip)
        lo1.addWidget(self.wpb_exp)
        # overwrite or add mode
        lo1 = QHBoxLayout()
        lo.addLayout(lo1)
        lo1.setContentsMargins(0, 0, 0, 0)
        self.woverwrite = QRadioButton("overwrite image", self)
        self.woverwrite.setChecked(True)
        lo1.addWidget(self.woverwrite)
        self.waddinto = QRadioButton("add into image", self)
        lo1.addWidget(self.waddinto)
        # add to model
        self.wadd = QCheckBox(
            "Add resulting brick to sky model as a FITS image component", self)
        lo.addWidget(self.wadd)
        lo1 = QHBoxLayout()
        lo.addLayout(lo1)
        lo1.setContentsMargins(0, 0, 0, 0)
        self.wpad = QLineEdit(self)
        self.wpad.setValidator(QDoubleValidator(self))
        self.wpad.setText("1.1")
        lab = QLabel("...with padding factor:", self)
        lab.setToolTip(
            """<P>The padding factor determines the amount of null padding inserted around the image during
      the prediction stage. Padding alleviates the effects of tapering and detapering in the uv-brick, which can show
      up towards the edges of the image. For a factor of N, the image will be padded out to N times its original size.
      This increases memory use, so if you have no flux at the edges of the image anyway, then a pad factor of 1 is
      perfectly fine.</P>""")
        self.wpad.setToolTip(lab.toolTip())
        self.wadd.toggled[bool].connect(self.wpad.setEnabled)
        self.wadd.toggled[bool].connect(lab.setEnabled)
        self.wpad.setEnabled(False)
        lab.setEnabled(False)
        lo1.addStretch(1)
        lo1.addWidget(lab, 0)
        lo1.addWidget(self.wpad, 1)
        self.wdel = QCheckBox(
            "Remove from the sky model sources that go into the brick", self)
        lo.addWidget(self.wdel)
        # OK/cancel buttons
        lo.addSpacing(10)
        lo2 = QHBoxLayout()
        lo.addLayout(lo2)
        lo2.setContentsMargins(5, 5, 5, 5)
        self.wokbtn = QPushButton("OK", self)
        self.wokbtn.setMinimumWidth(128)
        self.wokbtn.clicked.connect(self.accept)
        self.wokbtn.setEnabled(False)
        cancelbtn = QPushButton("Cancel", self)
        cancelbtn.setMinimumWidth(128)
        cancelbtn.clicked.connect(self.reject)
        lo2.addWidget(self.wokbtn)
        lo2.addStretch(1)
        lo2.addWidget(cancelbtn)
        self.setMinimumWidth(384)
        # signals
        self.wfile.filenameSelected.connect(self._fileSelected)
        # internal state
        self.qerrmsg = QErrorMessage(self)

    def setModel(self, model):
        self.model = model
        pb = self.model.primaryBeam()
        if pb:
            self.wpb_exp.setText(pb)
        else:
            self.wpb_apply.setChecked(False)
            self.wpb_exp.setText("")
        if model.filename():
            self._model_dir = os.path.dirname(os.path.abspath(
                model.filename()))
        else:
            self._model_dir = os.path.abspath('.')
        self.wfile.setDirectory(self._model_dir)
        self._fileSelected(self.wfile.filename(), quiet=True)

    def _fileSelected(self, filename, quiet=False):
        self.wokbtn.setEnabled(False)
        if not filename:
            return None
        # check that filename matches model
        if not os.path.samefile(self._model_dir, os.path.dirname(filename)):
            self.wfile.setFilename('')
            if not quiet:
                QMessageBox.warning(
                    self, "Directory mismatch",
                    """<P>The FITS file must reside in the same directory
          as the current sky model.</P>""")
            self.wfile.setDirectory(self._model_dir)
            return None
        # read fits file
        busy = BusyIndicator()
        try:
            input_hdu = pyfits.open(filename)[0]
            hdr = input_hdu.header
            # get frequency, if specified
            for axis in range(1, hdr['NAXIS'] + 1):
                if hdr['CTYPE%d' % axis].upper() == 'FREQ':
                    self.wfreq.setText(str(hdr['CRVAL%d' % axis] / 1e+6))
                    break
        except Exception as err:
            busy.reset_cursor()
            self.wfile.setFilename('')
            if not quiet:
                QMessageBox.warning(
                    self, "Error reading FITS",
                    "Error reading FITS file %s: %s" % (filename, str(err)))
            return None
        self.wokbtn.setEnabled(True)
        # if filename is not in model already, enable the "add to model" control
        for src in self.model.sources:
            if isinstance(getattr(src, 'shape', None), ModelClasses.FITSImage) \
                    and os.path.exists(src.shape.filename) and os.path.exists(filename) \
                    and os.path.samefile(src.shape.filename, filename):
                self.wadd.setChecked(True)
                self.wadd.setEnabled(False)
                self.wadd.setText("image already in sky model")
                break
        else:
            self.wadd.setText("add image to sky model")
        busy.reset_cursor()
        return filename

    def accept(self):
        """Tries to make a brick, and closes the dialog if successful."""
        sources = [
            src for src in self.model.sources
            if src.selected and src.typecode == 'pnt'
        ]
        filename = self.wfile.filename()
        if not self._fileSelected(filename):
            return
        # get PB expression
        pbfunc = None
        if self.wpb_apply.isChecked():
            pbexp = str(self.wpb_exp.text())
            try:
                pbfunc = eval("lambda r,fq:" + pbexp)
            except Exception as err:
                QMessageBox.warning(
                    self, "Error parsing PB experssion",
                    "Error parsing primary beam expression %s: %s" %
                    (pbexp, str(err)))
                return
        # get frequency
        freq = str(self.wfreq.text())
        freq = float(freq) * 1e+6 if freq else None
        # get pad factor
        pad = str(self.wpad.text())
        pad = max(float(pad), 1) if pad else 1
        # read fits file
        busy = BusyIndicator()
        try:
            input_hdu = pyfits.open(filename)[0]
        except Exception as err:
            busy.reset_cursor()
            QMessageBox.warning(
                self, "Error reading FITS",
                "Error reading FITS file %s: %s" % (filename, str(err)))
            return
        # reset data if asked to
        if self.woverwrite.isChecked():
            input_hdu.data[...] = 0
        # insert sources
        Imaging.restoreSources(input_hdu,
                               sources,
                               0,
                               primary_beam=pbfunc,
                               freq=freq)
        # save fits file
        try:
            # pyfits seems to produce an exception:
            #         TypeError: formatwarning() takes exactly 4 arguments (5 given)
            # when attempting to overwrite a file. As a workaround, remove the file first.
            if os.path.exists(filename):
                os.remove(filename)
            input_hdu.writeto(filename)
        except Exception as err:
            traceback.print_exc()
            busy.reset_cursor()
            QMessageBox.warning(
                self, "Error writing FITS",
                "Error writing FITS file %s: %s" % (filename, str(err)))
            return
        changed = False
        sources = self.model.sources
        # remove sources from model if asked to
        if self.wdel.isChecked():
            sources = [
                src for src in sources
                if not (src.selected and src.typecode == 'pnt')
            ]
            changed = True
        # add image to model if asked to
        if self.wadd.isChecked():
            hdr = input_hdu.header
            # get image parameters
            max_flux = float(input_hdu.data.max())
            wcs = WCS(hdr, mode='pyfits')
            # Get reference pixel coordinates
            # wcs.getCentreWCSCoords() doesn't work, as that gives us the middle of the image
            # So scan the header to get the CRPIX values
            ra0 = dec0 = 1
            for iaxis in range(hdr['NAXIS']):
                axs = str(iaxis + 1)
                name = hdr.get('CTYPE' + axs, axs).upper()
                if name.startswith("RA"):
                    ra0 = hdr.get('CRPIX' + axs, 1) - 1
                elif name.startswith("DEC"):
                    dec0 = hdr.get('CRPIX' + axs, 1) - 1
            # convert pixel to degrees
            ra0, dec0 = wcs.pix2wcs(ra0, dec0)
            ra0 *= DEG
            dec0 *= DEG
            sx, sy = wcs.getHalfSizeDeg()
            sx *= DEG
            sy *= DEG
            nx, ny = input_hdu.data.shape[-1:-3:-1]
            # check if this image is already contained in the model
            for src in sources:
                if isinstance(getattr(src, 'shape', None),
                              ModelClasses.FITSImage) and os.path.samefile(
                                  src.shape.filename, filename):
                    # update source parameters
                    src.pos.ra, src.pos.dec = ra0, dec0
                    src.flux.I = max_flux
                    src.shape.ex, src.shape.ey = sx, sy
                    src.shape.nx, src.shape.ny = nx, ny
                    src.shape.pad = pad
                    break
            # not contained, make new source object
            else:
                pos = ModelClasses.Position(ra0, dec0)
                flux = ModelClasses.Flux(max_flux)
                shape = ModelClasses.FITSImage(sx,
                                               sy,
                                               0,
                                               os.path.basename(filename),
                                               nx,
                                               ny,
                                               pad=pad)
                img_src = SkyModel.Source(os.path.splitext(
                    os.path.basename(filename))[0],
                                          pos,
                                          flux,
                                          shape=shape)
                sources.append(img_src)
            changed = True
        if changed:
            self.model.setSources(sources)
            self.model.emitUpdate(SkyModel.SkyModel.UpdateAll, origin=self)
        self.parent().showMessage("Wrote %d sources to FITS file %s" %
                                  (len(sources), filename))
        busy.reset_cursor()
        return QDialog.accept(self)