%PDF- %PDF-
| Direktori : /lib/calibre/calibre/gui2/tts/ |
| Current File : //lib/calibre/calibre/gui2/tts/linux_config.py |
#!/usr/bin/env python3
# License: GPL v3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
from contextlib import suppress
from qt.core import (
QAbstractItemView, QAbstractTableModel, QByteArray, QComboBox, QFontMetrics,
QFormLayout, QItemSelectionModel, QSlider, QSortFilterProxyModel, Qt, QTableView,
QWidget
)
from calibre.gui2.preferences.look_feel import BusyCursor
class VoicesModel(QAbstractTableModel):
system_default_voice = ('', '', '')
def __init__(self, voice_data, default_output_module, parent=None):
super().__init__(parent)
self.voice_data = voice_data
self.current_voices = voice_data[default_output_module]
self.column_headers = (_('Name'), _('Language'), _('Variant'))
def rowCount(self, parent=None):
return len(self.current_voices) + 1
def columnCount(self, parent=None):
return len(self.column_headers)
def headerData(self, section, orientation, role=Qt.ItemDataRole.DisplayRole):
if role == Qt.ItemDataRole.DisplayRole and orientation == Qt.Orientation.Horizontal:
return self.column_headers[section]
return super().headerData(section, orientation, role)
def data(self, index, role=Qt.ItemDataRole.DisplayRole):
if role == Qt.ItemDataRole.DisplayRole:
row = index.row()
with suppress(IndexError):
if row == 0:
return (_('System default'), '', '')[index.column()]
data = self.current_voices[row - 1]
ans = data[index.column()]
if not ans or ans == 'none':
ans = ''
return ans
if role == Qt.ItemDataRole.UserRole:
row = index.row()
with suppress(IndexError):
if row == 0:
return self.system_default_voice
return self.current_voices[row - 1]
def change_output_module(self, om):
self.beginResetModel()
try:
self.current_voices = self.voice_data[om]
finally:
self.endResetModel()
def index_for_voice(self, v):
r = 0
if v != self.system_default_voice:
try:
idx = self.current_voices.index(v)
except Exception:
return
r = idx + 1
return self.index(r, 0)
class Widget(QWidget):
def __init__(self, tts_client, initial_backend_settings=None, parent=None):
QWidget.__init__(self, parent)
self.l = l = QFormLayout(self)
self.tts_client = tts_client
self.speed = s = QSlider(Qt.Orientation.Horizontal, self)
s.setTickPosition(QSlider.TickPosition.TicksAbove)
s.setMinimumWidth(200)
l.addRow(_('&Speed of speech:'), s)
s.setRange(self.tts_client.min_rate, self.tts_client.max_rate)
s.setSingleStep(10)
s.setTickInterval((s.maximum() - s.minimum()) // 2)
self.output_modules = om = QComboBox(self)
with BusyCursor():
self.voice_data = self.tts_client.get_voice_data()
self.system_default_output_module = self.tts_client.system_default_output_module
om.addItem(_('System default'), self.system_default_output_module)
for x in self.voice_data:
om.addItem(x, x)
l.addRow(_('Speech s&ynthesizer:'), om)
self.voices = v = QTableView(self)
self.voices_model = VoicesModel(self.voice_data, self.system_default_output_module, parent=v)
self.proxy_model = p = QSortFilterProxyModel(self)
p.setFilterCaseSensitivity(Qt.CaseSensitivity.CaseInsensitive)
p.setSourceModel(self.voices_model)
v.setModel(p)
v.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
v.setSortingEnabled(True)
h = v.horizontalHeader()
h.resizeSection(0, QFontMetrics(self.font()).averageCharWidth() * 30)
v.verticalHeader().close()
v.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
v.sortByColumn(0, Qt.SortOrder.AscendingOrder)
om.currentIndexChanged.connect(self.output_module_changed)
l.addRow(v)
self.backend_settings = initial_backend_settings or {}
def restore_state(self, prefs):
data = prefs.get(f'{self.tts_client.name}-voice-table-state')
if data is not None:
self.voices.horizontalHeader().restoreState(QByteArray(data))
def save_state(self, prefs):
data = bytearray(self.voices.horizontalHeader().saveState())
prefs.set(f'{self.tts_client.name}-voice-table-state', data)
def restore_to_defaults(self):
self.backend_settings = {}
def sizeHint(self):
ans = super().sizeHint()
ans.setHeight(max(ans.height(), 600))
return ans
@property
def selected_voice(self):
for x in self.voices.selectedIndexes():
return x.data(Qt.ItemDataRole.UserRole)
@selected_voice.setter
def selected_voice(self, val):
val = val or VoicesModel.system_default_voice
idx = self.voices_model.index_for_voice(tuple(val))
if idx is not None:
idx = self.proxy_model.mapFromSource(idx)
self.voices.selectionModel().select(idx, QItemSelectionModel.SelectionFlag.ClearAndSelect | QItemSelectionModel.SelectionFlag.Rows)
self.voices.scrollTo(idx)
@property
def selected_output_module(self):
return self.output_modules.currentData()
@selected_output_module.setter
def selected_output_module(self, val):
if not val:
self.output_modules.setCurrentIndex(0)
return
idx = self.output_modules.findData(val)
if idx < 0:
idx = 0
self.output_modules.setCurrentIndex(idx)
def output_module_changed(self, idx):
om = self.selected_output_module
self.voices_model.change_output_module(om)
@property
def rate(self):
return self.speed.value()
@rate.setter
def rate(self, val):
val = int(val or 0)
self.speed.setValue(val)
@property
def backend_settings(self):
ans = {}
om = self.selected_output_module
if om != self.system_default_output_module:
ans['output_module'] = om
voice = self.selected_voice
if voice and voice != VoicesModel.system_default_voice:
ans['voice'] = voice
rate = self.rate
if rate:
ans['rate'] = rate
return ans
@backend_settings.setter
def backend_settings(self, val):
om = val.get('output_module') or self.system_default_output_module
self.selected_output_module = om
voice = val.get('voice') or VoicesModel.system_default_voice
self.selected_voice = voice
self.rate = val.get('rate') or 0
if __name__ == '__main__':
from calibre.gui2 import Application
from calibre.gui2.tts.implementation import Client
app = Application([])
c = Client({})
w = Widget(c, {})
w.show()
app.exec()
print(w.backend_settings)