1 | // Copyright (C) 2023 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include "qdeclarativetexttospeech_p.h" |
5 | #include "qvoiceselectorattached_p.h" |
6 | |
7 | #include <QtCore/qregularexpression.h> |
8 | |
9 | QT_BEGIN_NAMESPACE |
10 | |
11 | using namespace Qt::StringLiterals; |
12 | |
13 | /*! |
14 | \internal |
15 | |
16 | Constructor that delays the initialization of the text-to-speech |
17 | engine until the QML engine sets the engine. |
18 | */ |
19 | QDeclarativeTextToSpeech::QDeclarativeTextToSpeech(QObject *parent) |
20 | : QTextToSpeech(u"none"_s , parent) |
21 | {} |
22 | |
23 | /*! |
24 | Intercept the calls to QTextToSpeech::engine/setEngine so that we can |
25 | delay the setting of the engine until the component is completely parsed. |
26 | */ |
27 | QString QDeclarativeTextToSpeech::engine() const |
28 | { |
29 | if (!m_engine.isEmpty()) |
30 | return m_engine; |
31 | return QTextToSpeech::engine(); |
32 | } |
33 | |
34 | void QDeclarativeTextToSpeech::setEngine(const QString &engine) |
35 | { |
36 | if (m_engine == engine) |
37 | return; |
38 | |
39 | m_engine = engine; |
40 | if (m_complete) |
41 | QTextToSpeech::setEngine(engine: m_engine, params: m_engineParameters); |
42 | emit engineChanged(m_engine); |
43 | } |
44 | |
45 | /*! |
46 | \qmlproperty map TextToSpeech::engineParameters |
47 | \brief This property holds engine-specific parameters. |
48 | |
49 | \sa engine |
50 | */ |
51 | QVariantMap QDeclarativeTextToSpeech::engineParameters() const |
52 | { |
53 | return m_engineParameters; |
54 | } |
55 | |
56 | void QDeclarativeTextToSpeech::setEngineParameters(const QVariantMap ¶meters) |
57 | { |
58 | if (m_engineParameters == parameters) |
59 | return; |
60 | |
61 | m_engineParameters = parameters; |
62 | // if changed after initialization, then we need to recreate the engine |
63 | if (m_complete) |
64 | QTextToSpeech::setEngine(engine: QTextToSpeech::engine(), params: m_engineParameters); |
65 | emit engineParametersChanged(); |
66 | } |
67 | |
68 | void QDeclarativeTextToSpeech::classBegin() |
69 | { |
70 | } |
71 | |
72 | void QDeclarativeTextToSpeech::componentComplete() |
73 | { |
74 | m_complete = true; |
75 | QTextToSpeech::setEngine(engine: m_engine, params: m_engineParameters); |
76 | selectVoice(); |
77 | } |
78 | |
79 | void QDeclarativeTextToSpeech::selectVoice() |
80 | { |
81 | if (!m_complete || !m_voiceSelector) |
82 | return; |
83 | |
84 | if (state() != QTextToSpeech::Ready) { |
85 | // for asynchronously initialized engines we have to wait for it to be ready |
86 | connect(sender: this, signal: &QTextToSpeech::stateChanged, context: this, slot: &QDeclarativeTextToSpeech::selectVoice, |
87 | type: Qt::SingleShotConnection); |
88 | } else { |
89 | auto voices = findVoices(criteria: m_voiceSelector->selectionCriteria()); |
90 | if (!voices.isEmpty()) |
91 | setVoice(voices.first()); |
92 | } |
93 | } |
94 | |
95 | |
96 | QList<QVoice> QDeclarativeTextToSpeech::findVoices(const QVariantMap &criteria) const |
97 | { |
98 | const QLocale *plocale = nullptr; |
99 | // if we limit by locale, then limit the search to the voices for that |
100 | if (const auto &it = criteria.find(key: QLatin1String("locale" )); it != criteria.end()) { |
101 | if (it->metaType() == QMetaType::fromType<QLocale>()) |
102 | plocale = static_cast<const QLocale *>(it->constData()); |
103 | } |
104 | QList<QVoice> voices = allVoices(locale: plocale); |
105 | |
106 | voices.removeIf(pred: [&criteria](const QVoice &voice){ |
107 | const QMetaObject &mo = QVoice::staticMetaObject; |
108 | for (const auto &[key, value] : criteria.asKeyValueRange()) { |
109 | const int propertyIndex = mo.indexOfProperty(name: key.toUtf8().constData()); |
110 | if (propertyIndex < 0) { |
111 | qWarning(msg: "QVoice doesn't have a property %s!" , qPrintable(key)); |
112 | } else { |
113 | const QMetaProperty prop = mo.property(index: propertyIndex); |
114 | const QVariant voiceValue = prop.readOnGadget(gadget: &voice); |
115 | if (voiceValue.metaType() == QMetaType::fromType<QLocale::Language>()) { |
116 | if (voiceValue.value<QLocale::Language>() != value.toLocale().language()) |
117 | return true; |
118 | } else if (value.metaType() == QMetaType::fromType<QRegularExpression>()) { |
119 | if (!value.value<QRegularExpression>().match(subject: voiceValue.toString()).hasMatch()) |
120 | return true; |
121 | } else if (voiceValue != value) { |
122 | return true; |
123 | } |
124 | } |
125 | } |
126 | return false; |
127 | }); |
128 | |
129 | return voices; |
130 | } |
131 | |
132 | |
133 | QT_END_NAMESPACE |
134 | |