1
1
__license__ = 'GPL v3'
2
2
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
3
import os, re, time, textwrap, sys, cStringIO
4
from binascii import hexlify, unhexlify
3
import os, re, time, textwrap
6
5
from PyQt4.Qt import QDialog, QMessageBox, QListWidgetItem, QIcon, \
7
6
QDesktopServices, QVBoxLayout, QLabel, QPlainTextEdit, \
8
7
QStringListModel, QAbstractItemModel, QFont, \
9
8
SIGNAL, QTimer, Qt, QSize, QVariant, QUrl, \
10
QModelIndex, QInputDialog, QAbstractTableModel
9
QModelIndex, QInputDialog, QAbstractTableModel, \
10
QDialogButtonBox, QTabWidget, QBrush, QLineEdit
12
12
from calibre.constants import islinux, iswindows
13
13
from calibre.gui2.dialogs.config_ui import Ui_Dialog
14
from calibre.gui2.dialogs.test_email_ui import Ui_Dialog as TE_Dialog
15
14
from calibre.gui2 import qstring_to_unicode, choose_dir, error_dialog, config, \
16
15
ALL_COLUMNS, NONE, info_dialog, choose_files
17
16
from calibre.utils.config import prefs
18
17
from calibre.gui2.widgets import FilenamePattern
19
18
from calibre.gui2.library import BooksModel
20
19
from calibre.ebooks import BOOK_EXTENSIONS
21
from calibre.ebooks.epub.iterator import is_supported
20
from calibre.ebooks.oeb.iterator import is_supported
22
21
from calibre.library import server_config
23
22
from calibre.customize.ui import initialized_plugins, is_disabled, enable_plugin, \
24
23
disable_plugin, customize_plugin, \
25
plugin_customization, add_plugin, remove_plugin
24
plugin_customization, add_plugin, \
25
remove_plugin, all_input_formats, \
26
input_format_plugins, \
27
output_format_plugins, available_output_formats
26
28
from calibre.utils.smtp import config as smtp_prefs
29
from calibre.gui2.convert.look_and_feel import LookAndFeelWidget
30
from calibre.gui2.convert.page_setup import PageSetupWidget
31
from calibre.gui2.convert.structure_detection import StructureDetectionWidget
32
from calibre.ebooks.conversion.plumber import Plumber
33
from calibre.utils.logging import Log
34
from calibre.gui2.convert.toc import TOCWidget
36
class ConfigTabs(QTabWidget):
38
def __init__(self, parent):
39
QTabWidget.__init__(self, parent)
43
self.plumber = Plumber('dummy.epub', 'dummy.epub', log, dummy=True)
45
def widget_factory(cls):
46
return cls(self, self.plumber.get_option_by_name,
47
self.plumber.get_option_help, None, None)
49
lf = widget_factory(LookAndFeelWidget)
50
ps = widget_factory(PageSetupWidget)
51
sd = widget_factory(StructureDetectionWidget)
52
toc = widget_factory(TOCWidget)
54
self.widgets = [lf, ps, sd, toc]
56
for plugin in input_format_plugins():
57
name = plugin.name.lower().replace(' ', '_')
59
input_widget = __import__('calibre.gui2.convert.'+name,
61
pw = input_widget.PluginWidget
62
pw.ICON = ':/images/forward.svg'
63
self.widgets.append(widget_factory(pw))
67
for plugin in output_format_plugins():
68
name = plugin.name.lower().replace(' ', '_')
70
output_widget = __import__('calibre.gui2.convert.'+name,
72
pw = output_widget.PluginWidget
73
pw.ICON = ':/images/forward.svg'
74
self.widgets.append(widget_factory(pw))
78
for i, widget in enumerate(self.widgets):
79
self.addTab(widget, widget.TITLE.replace('\n', ' ').replace('&',
81
self.setTabToolTip(i, widget.HELP if widget.HELP else widget.TITLE)
82
self.setUsesScrollButtons(True)
85
for widget in self.widgets:
86
if not widget.pre_commit_check():
88
widget.commit(save_defaults=True)
28
92
class PluginModel(QAbstractItemModel):
30
94
def __init__(self, *args):
31
95
QAbstractItemModel.__init__(self, *args)
32
96
self.icon = QVariant(QIcon(':/images/plugins.svg'))
97
p = QIcon(self.icon).pixmap(32, 32, QIcon.Disabled, QIcon.On)
98
self.disabled_icon = QVariant(QIcon(p))
35
102
def populate(self):
46
113
return QModelIndex()
48
115
if parent.isValid():
49
return self.createIndex(row, column, parent.row())
116
return self.createIndex(row, column, 1+parent.row())
51
return self.createIndex(row, column, -1)
118
return self.createIndex(row, column, 0)
53
120
def parent(self, index):
54
if not index.isValid() or index.internalId() == -1:
121
if not index.isValid() or index.internalId() == 0:
55
122
return QModelIndex()
56
return self.createIndex(index.internalId(), 0, -1)
123
return self.createIndex(index.internalId()-1, 0, 0)
58
125
def rowCount(self, parent):
59
126
if not parent.isValid():
60
127
return len(self.categories)
61
if parent.internalId() == -1:
128
if parent.internalId() == 0:
62
129
category = self.categories[parent.row()]
63
130
return len(self._data[category])
135
204
return self.icons[index.row()]
136
205
return QStringListModel.data(self, index, role)
138
class TestEmail(QDialog, TE_Dialog):
140
def __init__(self, accounts, parent):
141
QDialog.__init__(self, parent)
142
TE_Dialog.__init__(self)
144
opts = smtp_prefs().parse()
145
self.test_func = parent.test_email_settings
146
self.connect(self.test_button, SIGNAL('clicked(bool)'), self.test)
147
self.from_.setText(unicode(self.from_.text())%opts.from_)
149
self.to.setText(list(accounts.keys())[0])
151
self.label.setText(_('Using: %s:%s@%s:%s and %s encryption')%
152
(opts.relay_username, unhexlify(opts.relay_password),
153
opts.relay_host, opts.relay_port, opts.encryption))
156
self.log.setPlainText(_('Sending...'))
157
self.test_button.setEnabled(False)
159
tb = self.test_func(unicode(self.to.text()))
161
tb = _('Mail successfully sent')
162
self.log.setPlainText(tb)
164
self.test_button.setEnabled(True)
166
207
class EmailAccounts(QAbstractTableModel):
168
209
def __init__(self, accounts):
297
338
self.connect(self.browse_button, SIGNAL('clicked(bool)'), self.browse)
298
339
self.connect(self.compact_button, SIGNAL('clicked(bool)'), self.compact)
341
input_map = prefs['input_format_order']
343
for fmt in all_input_formats():
344
all_formats.add(fmt.upper())
345
for format in input_map + list(all_formats.difference(input_map)):
346
item = QListWidgetItem(format, self.input_order)
347
item.setData(Qt.UserRole, QVariant(format))
348
item.setFlags(Qt.ItemIsEnabled|Qt.ItemIsSelectable)
350
self.connect(self.input_up, SIGNAL('clicked()'), self.up_input)
351
self.connect(self.input_down, SIGNAL('clicked()'), self.down_input)
300
353
dirs = config['frequently_used_directories']
301
354
rn = config['use_roman_numerals_for_series_number']
302
355
self.timeout.setValue(prefs['network_timeout'])
353
409
self.pdf_metadata.setChecked(prefs['read_file_metadata'])
356
for ext in self.book_exts:
412
for ext in BOOK_EXTENSIONS:
357
413
ext = ext.lower()
358
414
ext = re.sub(r'(x{0,1})htm(l{0,1})', 'html', ext)
359
415
if ext == 'lrf' or is_supported('book.'+ext):
360
if ext == 'html' and added_html:
362
self.viewer.addItem(ext.upper())
363
self.viewer.item(self.viewer.count()-1).setFlags(Qt.ItemIsEnabled|Qt.ItemIsUserCheckable)
364
self.viewer.item(self.viewer.count()-1).setCheckState(Qt.Checked if ext.upper() in config['internally_viewed_formats'] else Qt.Unchecked)
365
added_html = ext == 'html'
418
for ext in sorted(exts):
419
self.viewer.addItem(ext.upper())
420
self.viewer.item(self.viewer.count()-1).setFlags(Qt.ItemIsEnabled|Qt.ItemIsUserCheckable)
421
self.viewer.item(self.viewer.count()-1).setCheckState(Qt.Checked if ext.upper() in config['internally_viewed_formats'] else Qt.Unchecked)
366
422
self.viewer.sortItems()
367
423
self.start.setEnabled(not getattr(self.server, 'is_running', False))
368
424
self.test.setEnabled(not self.start.isEnabled())
400
457
self.delete_news.setEnabled(bool(self.sync_news.isChecked()))
401
458
self.connect(self.sync_news, SIGNAL('toggled(bool)'),
402
459
self.delete_news.setEnabled)
460
self.setup_conversion_options()
462
def setup_conversion_options(self):
463
self.conversion_options = ConfigTabs(self)
464
self.stackedWidget.insertWidget(2, self.conversion_options)
404
466
def setup_email_page(self):
405
opts = smtp_prefs().parse()
407
self.email_from.setText(opts.from_)
468
if self._email_accounts.account_order:
469
return self._email_accounts.account_order[0]
470
self.send_email_widget.initialize(x)
471
opts = self.send_email_widget.smtp_opts
408
472
self._email_accounts = EmailAccounts(opts.accounts)
409
473
self.email_view.setModel(self._email_accounts)
411
self.relay_host.setText(opts.relay_host)
412
self.relay_port.setValue(opts.relay_port)
413
if opts.relay_username:
414
self.relay_username.setText(opts.relay_username)
415
if opts.relay_password:
416
self.relay_password.setText(unhexlify(opts.relay_password))
417
(self.relay_tls if opts.encryption == 'TLS' else self.relay_ssl).setChecked(True)
418
self.connect(self.relay_use_gmail, SIGNAL('clicked(bool)'),
419
self.create_gmail_relay)
420
self.connect(self.relay_show_password, SIGNAL('stateChanged(int)'),
422
state:self.relay_password.setEchoMode(self.relay_password.Password if
423
state == 0 else self.relay_password.Normal))
424
475
self.connect(self.email_add, SIGNAL('clicked(bool)'),
425
476
self.add_email_account)
426
477
self.connect(self.email_make_default, SIGNAL('clicked(bool)'),
427
478
lambda c: self._email_accounts.make_default(self.email_view.currentIndex()))
428
479
self.email_view.resizeColumnsToContents()
429
self.connect(self.test_email_button, SIGNAL('clicked(bool)'),
431
480
self.connect(self.email_remove, SIGNAL('clicked()'),
432
481
self.remove_email_account)
441
490
idx = self.email_view.currentIndex()
442
491
self._email_accounts.remove(idx)
444
def create_gmail_relay(self, *args):
445
self.relay_username.setText('@gmail.com')
446
self.relay_password.setText('')
447
self.relay_host.setText('smtp.gmail.com')
448
self.relay_port.setValue(587)
449
self.relay_tls.setChecked(True)
451
info_dialog(self, _('Finish gmail setup'),
452
_('Dont forget to enter your gmail username and password')).exec_()
453
self.relay_username.setFocus(Qt.OtherFocusReason)
454
self.relay_username.setCursorPosition(0)
456
493
def set_email_settings(self):
457
from_ = unicode(self.email_from.text()).strip()
458
if self._email_accounts.accounts and not from_:
459
error_dialog(self, _('Bad configuration'),
460
_('You must set the From email address')).exec_()
462
username = unicode(self.relay_username.text()).strip()
463
password = unicode(self.relay_password.text()).strip()
464
host = unicode(self.relay_host.text()).strip()
465
if host and not (username and password):
466
error_dialog(self, _('Bad configuration'),
467
_('You must set the username and password for '
468
'the mail server.')).exec_()
494
to_set = bool(self._email_accounts.accounts)
495
if not self.send_email_widget.set_email_settings(to_set):
470
497
conf = smtp_prefs()
471
conf.set('from_', from_)
472
498
conf.set('accounts', self._email_accounts.accounts)
473
conf.set('relay_host', host if host else None)
474
conf.set('relay_port', self.relay_port.value())
475
conf.set('relay_username', username if username else None)
476
conf.set('relay_password', hexlify(password))
477
conf.set('encryption', 'TLS' if self.relay_tls.isChecked() else 'SSL')
480
def test_email(self, *args):
481
if self.set_email_settings():
482
TestEmail(self._email_accounts.accounts, self).exec_()
484
def test_email_settings(self, to):
485
opts = smtp_prefs().parse()
486
from calibre.utils.smtp import sendmail, create_mail
487
buf = cStringIO.StringIO()
488
oout, oerr = sys.stdout, sys.stderr
489
sys.stdout = sys.stderr = buf
492
msg = create_mail(opts.from_, to, 'Test mail from calibre',
493
'Test mail from calibre')
494
sendmail(msg, from_=opts.from_, to=[to],
495
verbose=3, timeout=30, relay=opts.relay_host,
496
username=opts.relay_username,
497
password=unhexlify(opts.relay_password),
498
encryption=opts.encryption, port=opts.relay_port)
501
tb = traceback.format_exc()
502
tb += '\n\nLog:\n' + buf.getvalue()
504
sys.stdout, sys.stderr = oout, oerr
507
502
def add_plugin(self):
508
503
path = unicode(self.plugin_path.text())
540
535
info_dialog(self, _('Plugin not customizable'),
541
536
_('Plugin: %s does not need customization')%plugin.name).exec_()
543
help = plugin.customization_help()
544
text, ok = QInputDialog.getText(self, _('Customize %s')%plugin.name,
547
customize_plugin(plugin, unicode(text))
538
if hasattr(plugin, 'config_widget'):
539
config_dialog = QDialog(self)
540
button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
542
config_dialog.connect(button_box, SIGNAL('accepted()'), config_dialog.accept)
543
config_dialog.connect(button_box, SIGNAL('rejected()'), config_dialog.reject)
545
config_widget = plugin.config_widget()
546
v = QVBoxLayout(config_dialog)
547
v.addWidget(config_widget)
548
v.addWidget(button_box)
549
config_dialog.exec_()
551
if config_dialog.result() == QDialog.Accepted:
552
plugin.save_settings(config_widget)
553
self._plugin_model.refresh_plugin(plugin)
555
help = plugin.customization_help()
556
sc = plugin_customization(plugin)
560
text, ok = QInputDialog.getText(self, _('Customize %s')%plugin.name,
561
help, QLineEdit.Normal, sc)
563
customize_plugin(plugin, unicode(text).strip())
548
564
self._plugin_model.refresh_plugin(plugin)
549
565
if op == 'remove':
550
566
if remove_plugin(plugin):