1#!/usr/local/bin/python3.8
2# vim:fileencoding=utf-8
3# License: GPL v3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
4
5from qt.core import QDialogButtonBox, QObject, QVBoxLayout, pyqtSignal, QDialog
6
7from calibre.gui2 import error_dialog
8from calibre.gui2.viewer.config import get_pref_group, vprefs
9from calibre.gui2.widgets2 import Dialog
10
11
12class Config(Dialog):
13
14    def __init__(self, tts_client, ui_settings, backend_settings, parent):
15        self.tts_client = tts_client
16        self.ui_settings = ui_settings
17        self.backend_settings = backend_settings
18        Dialog.__init__(self, _('Configure Read aloud'), 'read-aloud-config', parent, prefs=vprefs)
19
20    def setup_ui(self):
21        self.l = l = QVBoxLayout(self)
22        self.config_widget = self.tts_client.config_widget(self.backend_settings, self)
23        l.addWidget(self.config_widget)
24        l.addWidget(self.bb)
25        self.config_widget.restore_to_defaults
26        b = self.bb.addButton(QDialogButtonBox.StandardButton.RestoreDefaults)
27        b.clicked.connect(self.restore_to_defaults)
28        self.config_widget.restore_state(vprefs)
29
30    def save_state(self):
31        self.config_widget.save_state(vprefs)
32
33    def restore_to_defaults(self):
34        self.config_widget.restore_to_defaults()
35
36    def accept(self):
37        self.backend_settings = self.config_widget.backend_settings
38        return super().accept()
39
40
41class TTS(QObject):
42
43    dispatch_on_main_thread_signal = pyqtSignal(object)
44    event_received = pyqtSignal(object, object)
45    settings_changed = pyqtSignal(object)
46
47    def __init__(self, parent=None):
48        QObject.__init__(self, parent)
49        self._tts_client = None
50        self.dispatch_on_main_thread_signal.connect(self.dispatch_on_main_thread)
51
52    def dispatch_on_main_thread(self, func):
53        try:
54            func()
55        except Exception:
56            import traceback
57            traceback.print_exc()
58
59    @property
60    def tts_client_class(self):
61        from calibre.gui2.tts.implementation import Client
62        return Client
63
64    @property
65    def tts_client(self):
66        if self._tts_client is None:
67            settings = self.backend_settings
68            self._tts_client = self.tts_client_class(settings, self.dispatch_on_main_thread_signal.emit)
69            if self._tts_client.settings != settings:
70                self.backend_settings = self._tts_client.settings
71        return self._tts_client
72
73    def shutdown(self):
74        if self._tts_client is not None:
75            self._tts_client.shutdown()
76            self._tts_client = None
77
78    def speak_simple_text(self, text):
79        from calibre.gui2.tts.errors import TTSSystemUnavailable
80        try:
81            self.tts_client.speak_simple_text(text)
82        except TTSSystemUnavailable as err:
83            return error_dialog(self.parent(), _('Text-to-Speech unavailable'), str(err), show=True)
84
85    def action(self, action, data):
86        from calibre.gui2.tts.errors import TTSSystemUnavailable
87        try:
88            getattr(self, action)(data)
89        except TTSSystemUnavailable as err:
90            return error_dialog(self.parent(), _('Text-to-Speech unavailable'), str(err), show=True)
91
92    def play(self, data):
93        self.tts_client.speak_marked_text(data['marked_text'], self.callback)
94
95    def pause(self, data):
96        self.tts_client.pause()
97
98    def resume(self, data):
99        self.tts_client.resume()
100
101    def resume_after_configure(self, data):
102        self.tts_client.resume_after_configure()
103
104    def callback(self, event):
105        data = event.data
106        if event.type is event.type.mark:
107            data = int(data)
108        self.event_received.emit(event.type.name, data)
109
110    def stop(self, data):
111        self.tts_client.stop()
112
113    @property
114    def backend_settings(self):
115        key = 'tts_' + self.tts_client_class.name
116        return vprefs.get(key) or {}
117
118    @backend_settings.setter
119    def backend_settings(self, val):
120        key = 'tts_' + self.tts_client_class.name
121        vprefs.set(key, val or {})
122
123    def configure(self, data):
124        ui_settings = get_pref_group('tts').copy()
125        d = Config(self.tts_client, ui_settings, self.backend_settings, parent=self.parent())
126        if d.exec() == QDialog.DialogCode.Accepted:
127            s = d.backend_settings
128            self.backend_settings = s
129            self.tts_client.apply_settings(s)
130            self.settings_changed.emit(d.ui_settings)
131        else:
132            self.settings_changed.emit(None)
133        d.save_state()
134
135    def slower(self, data):
136        settings = self.tts_client.change_rate(steps=-1)
137        if settings is not None:
138            self.backend_settings = settings
139
140    def faster(self, data):
141        settings = self.tts_client.change_rate(steps=1)
142        if settings is not None:
143            self.backend_settings = settings
144