%PDF- %PDF-
| Direktori : /proc/self/root/usr/lib/calibre/calibre/gui2/device_drivers/ |
| Current File : //proc/self/root/usr/lib/calibre/calibre/gui2/device_drivers/mtp_config.py |
#!/usr/bin/env python3
__license__ = 'GPL v3'
__copyright__ = '2012, Kovid Goyal <kovid at kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import weakref
from qt.core import (QWidget, QListWidgetItem, Qt, QToolButton, QLabel,
QTabWidget, QGridLayout, QListWidget, QIcon, QLineEdit, QVBoxLayout,
QPushButton, QGroupBox, QScrollArea, QHBoxLayout, QComboBox,
pyqtSignal, QSizePolicy, QDialog, QDialogButtonBox, QPlainTextEdit,
QApplication, QSize)
from calibre.ebooks import BOOK_EXTENSIONS
from calibre.gui2 import error_dialog
from calibre.gui2.dialogs.template_dialog import TemplateDialog
from calibre.utils.date import parse_date
from calibre.gui2.device_drivers.mtp_folder_browser import Browser, IgnoredFolders
from polyglot.builtins import iteritems
class FormatsConfig(QWidget): # {{{
def __init__(self, all_formats, format_map):
QWidget.__init__(self)
self.l = l = QGridLayout()
self.setLayout(l)
self.f = f = QListWidget(self)
l.addWidget(f, 0, 0, 3, 1)
unchecked_formats = sorted(all_formats - set(format_map))
for fmt in format_map + unchecked_formats:
item = QListWidgetItem(fmt, f)
item.setData(Qt.ItemDataRole.UserRole, fmt)
item.setFlags(Qt.ItemFlag.ItemIsEnabled|Qt.ItemFlag.ItemIsUserCheckable|Qt.ItemFlag.ItemIsSelectable)
item.setCheckState(Qt.CheckState.Checked if fmt in format_map else Qt.CheckState.Unchecked)
self.button_up = b = QToolButton(self)
b.setIcon(QIcon(I('arrow-up.png')))
l.addWidget(b, 0, 1)
b.clicked.connect(self.up)
self.button_down = b = QToolButton(self)
b.setIcon(QIcon(I('arrow-down.png')))
l.addWidget(b, 2, 1)
b.clicked.connect(self.down)
@property
def format_map(self):
return [str(self.f.item(i).data(Qt.ItemDataRole.UserRole) or '') for i in
range(self.f.count()) if self.f.item(i).checkState()==Qt.CheckState.Checked]
def validate(self):
if not self.format_map:
error_dialog(self, _('No formats selected'),
_('You must choose at least one format to send to the'
' device'), show=True)
return False
return True
def up(self):
idx = self.f.currentRow()
if idx > 0:
self.f.insertItem(idx-1, self.f.takeItem(idx))
self.f.setCurrentRow(idx-1)
def down(self):
idx = self.f.currentRow()
if idx < self.f.count()-1:
self.f.insertItem(idx+1, self.f.takeItem(idx))
self.f.setCurrentRow(idx+1)
# }}}
class TemplateConfig(QWidget): # {{{
def __init__(self, val):
QWidget.__init__(self)
self.t = t = QLineEdit(self)
t.setText(val or '')
t.setCursorPosition(0)
self.setMinimumWidth(400)
self.l = l = QGridLayout(self)
self.setLayout(l)
self.m = m = QLabel('<p>'+_('''<b>Save &template</b> to control the filename and
location of files sent to the device:'''))
m.setWordWrap(True)
m.setBuddy(t)
l.addWidget(m, 0, 0, 1, 2)
l.addWidget(t, 1, 0, 1, 1)
b = self.b = QPushButton(_('&Template editor'))
l.addWidget(b, 1, 1, 1, 1)
b.clicked.connect(self.edit_template)
@property
def template(self):
return str(self.t.text()).strip()
def edit_template(self):
t = TemplateDialog(self, self.template)
t.setWindowTitle(_('Edit template'))
if t.exec():
self.t.setText(t.rule[1])
def validate(self):
from calibre.utils.formatter import validation_formatter
tmpl = self.template
try:
validation_formatter.validate(tmpl)
return True
except Exception as err:
error_dialog(self, _('Invalid template'),
'<p>'+_('The template %s is invalid:')%tmpl +
'<br>'+str(err), show=True)
return False
# }}}
class SendToConfig(QWidget): # {{{
def __init__(self, val, device):
QWidget.__init__(self)
self.t = t = QLineEdit(self)
t.setText(', '.join(val or []))
t.setCursorPosition(0)
self.l = l = QGridLayout(self)
self.setLayout(l)
self.m = m = QLabel('<p>'+_('''A <b>list of &folders</b> on the device to
which to send e-books. The first one that exists will be used:'''))
m.setWordWrap(True)
m.setBuddy(t)
l.addWidget(m, 0, 0, 1, 2)
l.addWidget(t, 1, 0)
self.b = b = QToolButton()
l.addWidget(b, 1, 1)
b.setIcon(QIcon(I('document_open.png')))
b.clicked.connect(self.browse)
b.setToolTip(_('Browse for a folder on the device'))
self._device = weakref.ref(device)
@property
def device(self):
return self._device()
def browse(self):
b = Browser(self.device.filesystem_cache, show_files=False,
parent=self)
if b.exec() == QDialog.DialogCode.Accepted and b.current_item is not None:
sid, path = b.current_item
self.t.setText('/'.join(path[1:]))
@property
def value(self):
ans = [x.strip() for x in str(self.t.text()).strip().split(',')]
return [x for x in ans if x]
# }}}
class IgnoredDevices(QWidget): # {{{
def __init__(self, devs, blacklist):
QWidget.__init__(self)
self.l = l = QVBoxLayout()
self.setLayout(l)
self.la = la = QLabel('<p>'+_(
'''Select the devices to be <b>ignored</b>. calibre <b>will not</b>
connect to devices with a checkmark next to their names.'''))
la.setWordWrap(True)
l.addWidget(la)
self.f = f = QListWidget(self)
l.addWidget(f)
devs = [(snum, (x[0], parse_date(x[1]))) for snum, x in
iteritems(devs)]
for dev, x in sorted(devs, key=lambda x:x[1][1], reverse=True):
name = x[0]
name = '%s [%s]'%(name, dev)
item = QListWidgetItem(name, f)
item.setData(Qt.ItemDataRole.UserRole, dev)
item.setFlags(Qt.ItemFlag.ItemIsEnabled|Qt.ItemFlag.ItemIsUserCheckable|Qt.ItemFlag.ItemIsSelectable)
item.setCheckState(Qt.CheckState.Checked if dev in blacklist else Qt.CheckState.Unchecked)
@property
def blacklist(self):
return [str(self.f.item(i).data(Qt.ItemDataRole.UserRole) or '') for i in
range(self.f.count()) if self.f.item(i).checkState()==Qt.CheckState.Checked]
def ignore_device(self, snum):
for i in range(self.f.count()):
i = self.f.item(i)
c = str(i.data(Qt.ItemDataRole.UserRole) or '')
if c == snum:
i.setCheckState(Qt.CheckState.Checked)
break
# }}}
# Rules {{{
class Rule(QWidget):
remove = pyqtSignal(object)
def __init__(self, device, rule=None):
QWidget.__init__(self)
self._device = weakref.ref(device)
self.l = l = QHBoxLayout()
self.setLayout(l)
p, s = _('Send the %s format to the folder:').partition('%s')[0::2]
self.l1 = l1 = QLabel(p)
l.addWidget(l1)
self.fmt = f = QComboBox(self)
l.addWidget(f)
self.l2 = l2 = QLabel(s)
l.addWidget(l2)
self.folder = f = QLineEdit(self)
f.setPlaceholderText(_('Folder on the device'))
l.addWidget(f)
self.b = b = QToolButton()
l.addWidget(b)
b.setIcon(QIcon(I('document_open.png')))
b.clicked.connect(self.browse)
b.setToolTip(_('Browse for a folder on the device'))
self.rb = rb = QPushButton(QIcon(I('list_remove.png')),
_('&Remove rule'), self)
l.addWidget(rb)
rb.clicked.connect(self.removed)
for fmt in sorted(BOOK_EXTENSIONS):
self.fmt.addItem(fmt.upper(), fmt.lower())
self.fmt.setCurrentIndex(0)
if rule is not None:
fmt, folder = rule
idx = self.fmt.findText(fmt.upper())
if idx > -1:
self.fmt.setCurrentIndex(idx)
self.folder.setText(folder)
self.ignore = False
@property
def device(self):
return self._device()
def browse(self):
b = Browser(self.device.filesystem_cache, show_files=False,
parent=self)
if b.exec() == QDialog.DialogCode.Accepted and b.current_item is not None:
sid, path = b.current_item
self.folder.setText('/'.join(path[1:]))
def removed(self):
self.remove.emit(self)
@property
def rule(self):
folder = str(self.folder.text()).strip()
if folder:
return (
str(self.fmt.itemData(self.fmt.currentIndex()) or ''),
folder
)
return None
class FormatRules(QGroupBox):
def __init__(self, device, rules):
QGroupBox.__init__(self, _('Format specific sending'))
self._device = weakref.ref(device)
self.l = l = QVBoxLayout()
self.setLayout(l)
self.la = la = QLabel('<p>'+_(
'''You can create rules that control where e-books of a specific
format are sent to on the device. These will take precedence over
the folders specified above.'''))
la.setWordWrap(True)
l.addWidget(la)
self.sa = sa = QScrollArea(self)
sa.setWidgetResizable(True)
self.w = w = QWidget(self)
w.l = QVBoxLayout()
w.setLayout(w.l)
sa.setWidget(w)
l.addWidget(sa)
self.widgets = []
for rule in rules:
r = Rule(device, rule)
self.widgets.append(r)
w.l.addWidget(r)
r.remove.connect(self.remove_rule)
if not self.widgets:
self.add_rule()
self.b = b = QPushButton(QIcon(I('plus.png')), _('Add a &new rule'))
l.addWidget(b)
b.clicked.connect(self.add_rule)
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Ignored)
@property
def device(self):
return self._device()
def add_rule(self):
r = Rule(self.device)
self.widgets.append(r)
self.w.l.addWidget(r)
r.remove.connect(self.remove_rule)
self.sa.verticalScrollBar().setValue(self.sa.verticalScrollBar().maximum())
def remove_rule(self, rule):
rule.setVisible(False)
rule.ignore = True
@property
def rules(self):
for w in self.widgets:
if not w.ignore:
r = w.rule
if r is not None:
yield r
# }}}
class MTPConfig(QTabWidget):
def __init__(self, device, parent=None, highlight_ignored_folders=False):
QTabWidget.__init__(self, parent)
self._device = weakref.ref(device)
cd = msg = None
if device.current_friendly_name is not None:
if device.current_serial_num is None:
msg = '<p>' + (_('The <b>%s</b> device has no serial number, '
'it cannot be configured')%device.current_friendly_name)
else:
cd = 'device-'+device.current_serial_num
else:
msg = '<p>' + _('<b>No MTP device connected.</b><p>'
' You can only configure the MTP device plugin when a device'
' is connected.')
self.current_device_key = cd
if msg:
msg += '<p>' + _('If you want to un-ignore a previously'
' ignored MTP device, use the "Ignored devices" tab.')
l = QLabel(msg)
l.setWordWrap(True)
l.setStyleSheet('QLabel { margin-left: 2em }')
l.setMinimumWidth(500)
l.setMinimumHeight(400)
self.insertTab(0, l, _('Cannot configure'))
else:
self.base = QWidget(self)
self.insertTab(0, self.base, _('Configure %s')%self.device.current_friendly_name)
l = self.base.l = QGridLayout(self.base)
self.base.setLayout(l)
self.rules = r = FormatRules(self.device, self.get_pref('rules'))
self.formats = FormatsConfig(set(BOOK_EXTENSIONS),
self.get_pref('format_map'))
self.send_to = SendToConfig(self.get_pref('send_to'), self.device)
self.template = TemplateConfig(self.get_pref('send_template'))
self.base.la = la = QLabel(_(
'Choose the formats to send to the %s')%self.device.current_friendly_name)
la.setWordWrap(True)
self.base.b = b = QPushButton(QIcon(I('list_remove.png')),
_('&Ignore the %s in calibre')%device.current_friendly_name,
self.base)
b.clicked.connect(self.ignore_device)
self.config_ign_folders_button = cif = QPushButton(
QIcon(I('tb_folder.png')), _('Change scanned &folders'))
cif.setStyleSheet(
'QPushButton { font-weight: bold; }')
if highlight_ignored_folders:
cif.setIconSize(QSize(64, 64))
self.show_debug_button = bd = QPushButton(QIcon(I('debug.png')),
_('Show device information'))
bd.clicked.connect(self.show_debug_info)
cif.clicked.connect(self.change_ignored_folders)
l.addWidget(b, 0, 0, 1, 2)
l.addWidget(la, 1, 0, 1, 1)
l.addWidget(self.formats, 2, 0, 5, 1)
l.addWidget(cif, 2, 1, 1, 1)
l.addWidget(self.template, 3, 1, 1, 1)
l.addWidget(self.send_to, 4, 1, 1, 1)
l.addWidget(self.show_debug_button, 5, 1, 1, 1)
l.setRowStretch(6, 10)
l.addWidget(r, 7, 0, 1, 2)
l.setRowStretch(7, 100)
self.igntab = IgnoredDevices(self.device.prefs['history'],
self.device.prefs['blacklist'])
self.addTab(self.igntab, _('Ignored devices'))
self.current_ignored_folders = self.get_pref('ignored_folders')
self.initial_ignored_folders = self.current_ignored_folders
self.setCurrentIndex(1 if msg else 0)
def show_debug_info(self):
info = self.device.device_debug_info()
d = QDialog(self)
d.l = l = QVBoxLayout()
d.setLayout(l)
d.v = v = QPlainTextEdit()
d.setWindowTitle(self.device.get_gui_name())
v.setPlainText(info)
v.setMinimumWidth(400)
v.setMinimumHeight(350)
l.addWidget(v)
bb = d.bb = QDialogButtonBox(QDialogButtonBox.StandardButton.Close)
bb.accepted.connect(d.accept)
bb.rejected.connect(d.reject)
l.addWidget(bb)
bb.addButton(_('Copy to clipboard'), QDialogButtonBox.ButtonRole.ActionRole)
bb.clicked.connect(lambda :
QApplication.clipboard().setText(v.toPlainText()))
d.exec()
def change_ignored_folders(self):
d = IgnoredFolders(self.device,
self.current_ignored_folders, parent=self)
if d.exec() == QDialog.DialogCode.Accepted:
self.current_ignored_folders = d.ignored_folders
def ignore_device(self):
self.igntab.ignore_device(self.device.current_serial_num)
self.base.b.setEnabled(False)
self.base.b.setText(_('The %s will be ignored in calibre')%
self.device.current_friendly_name)
self.base.b.setStyleSheet('QPushButton { font-weight: bold }')
self.base.setEnabled(False)
def get_pref(self, key):
p = self.device.prefs.get(self.current_device_key, {})
if not p and self.current_device_key is not None:
self.device.prefs[self.current_device_key] = p
return self.device.get_pref(key)
@property
def device(self):
return self._device()
def validate(self):
if hasattr(self, 'formats'):
if not self.formats.validate():
return False
if not self.template.validate():
return False
return True
def commit(self):
self.device.prefs['blacklist'] = self.igntab.blacklist
p = self.device.prefs.get(self.current_device_key, {})
if hasattr(self, 'formats'):
p.pop('format_map', None)
f = self.formats.format_map
if f and f != self.device.prefs['format_map']:
p['format_map'] = f
p.pop('send_template', None)
t = self.template.template
if t and t != self.device.prefs['send_template']:
p['send_template'] = t
p.pop('send_to', None)
s = self.send_to.value
if s and s != self.device.prefs['send_to']:
p['send_to'] = s
p.pop('rules', None)
r = list(self.rules.rules)
if r and r != self.device.prefs['rules']:
p['rules'] = r
if self.current_ignored_folders != self.initial_ignored_folders:
p['ignored_folders'] = self.current_ignored_folders
if self.current_device_key is not None:
self.device.prefs[self.current_device_key] = p
class SendError(QDialog):
def __init__(self, gui, error):
QDialog.__init__(self, gui)
self.l = l = QVBoxLayout()
self.setLayout(l)
self.la = la = QLabel('<p>'+
_('You are trying to send books into the <b>%s</b> folder. This '
'folder is currently ignored by calibre when scanning the '
'device. You have to tell calibre you want this folder scanned '
'in order to be able to send books to it. Click the '
'<b>Configure</b> button below to send books to it.')%error.folder)
la.setWordWrap(True)
la.setMinimumWidth(500)
l.addWidget(la)
self.bb = bb = QDialogButtonBox(QDialogButtonBox.StandardButton.Close)
self.b = bb.addButton(_('Configure'), QDialogButtonBox.ButtonRole.AcceptRole)
bb.accepted.connect(self.accept)
bb.rejected.connect(self.reject)
l.addWidget(bb)
self.setWindowTitle(_('Cannot send to %s')%error.folder)
self.setWindowIcon(QIcon(I('dialog_error.png')))
self.resize(self.sizeHint())
def accept(self):
QDialog.accept(self)
dev = self.parent().device_manager.connected_device
dev.highlight_ignored_folders = True
self.parent().configure_connected_device()
dev.highlight_ignored_folders = False
if __name__ == '__main__':
from calibre.gui2 import Application
from calibre.devices.mtp.driver import MTP_DEVICE
from calibre.devices.scanner import DeviceScanner
s = DeviceScanner()
s.scan()
app = Application([])
dev = MTP_DEVICE(None)
dev.startup()
cd = dev.detect_managed_devices(s.devices)
dev.open(cd, 'test')
cw = dev.config_widget()
d = QDialog()
d.l = QVBoxLayout()
d.setLayout(d.l)
d.l.addWidget(cw)
bb = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok|QDialogButtonBox.StandardButton.Cancel)
d.l.addWidget(bb)
bb.accepted.connect(d.accept)
bb.rejected.connect(d.reject)
if d.exec() == QDialog.DialogCode.Accepted:
cw.commit()
dev.shutdown()