Changeset View
Changeset View
Standalone View
Standalone View
view.cpp
Show All 19 Lines | |||||
20 | */ | 20 | */ | ||
21 | 21 | | |||
22 | #include "view.h" | 22 | #include "view.h" | ||
23 | 23 | | |||
24 | #include "grantleeformatter.h" | 24 | #include "grantleeformatter.h" | ||
25 | #include "history.h" | 25 | #include "history.h" | ||
26 | #include "khc_debug.h" | 26 | #include "khc_debug.h" | ||
27 | 27 | | |||
28 | #include <dom/html_document.h> | | |||
29 | #include <dom/html_head.h> | | |||
30 | #include <dom/html_misc.h> | | |||
31 | | ||||
32 | #include <KActionCollection> | 28 | #include <KActionCollection> | ||
33 | #include <KHTMLSettings> | | |||
34 | #include <KHTMLView> | | |||
35 | #include <KToolBarPopupAction> | 29 | #include <KToolBarPopupAction> | ||
36 | #include <KSharedConfig> | 30 | #include <KSharedConfig> | ||
31 | #include <KLocalizedString> | ||||
37 | #include <docbookxslt.h> | 32 | #include <docbookxslt.h> | ||
33 | #include <KIO/TransferJob> | ||||
38 | 34 | | |||
39 | #include <QClipboard> | 35 | #include <QClipboard> | ||
36 | #include <QWebEngineScript> | ||||
37 | #include <QWebEngineScriptCollection> | ||||
38 | #include <QWebEngineSettings> | ||||
39 | #include <QWebEngineProfile> | ||||
40 | #include <QWebEnginePage> | ||||
41 | #include <QWebEngineUrlSchemeHandler> | ||||
42 | #include <QWebEngineUrlRequestJob> | ||||
40 | #include <QDir> | 43 | #include <QDir> | ||
41 | #include <QFileInfo> | 44 | #include <QFileInfo> | ||
42 | #include <QGuiApplication> | 45 | #include <QGuiApplication> | ||
43 | #include <QKeyEvent> | 46 | #include <QKeyEvent> | ||
44 | #include <QMenu> | 47 | #include <QMenu> | ||
45 | #include <QScrollBar> | 48 | #include <QScrollBar> | ||
46 | #include <QStandardPaths> | 49 | #include <QStandardPaths> | ||
47 | #include <QTextStream> | 50 | #include <QTextStream> | ||
48 | #include <QWhatsThis> | 51 | #include <QWhatsThis> | ||
52 | #include <QBuffer> | ||||
53 | | ||||
54 | | ||||
49 | 55 | | |||
50 | using namespace KHC; | 56 | using namespace KHC; | ||
51 | 57 | | |||
52 | View::View( QWidget *parentWidget, QObject *parent, KHTMLPart::GUIProfile prof, KActionCollection *col ) | 58 | class HelpUrlSchemeHandler : public QWebEngineUrlSchemeHandler { | ||
53 | : KHTMLPart( parentWidget, parent, prof ), mState( Docu ), mActionCollection(col) | 59 | void requestStarted(QWebEngineUrlRequestJob* job) { | ||
54 | { | 60 | qCDebug(KHC_LOG) << job->requestUrl(); | ||
55 | setJScriptEnabled(false); | 61 | KIO::TransferJob* kiojob = KIO::get(job->requestUrl(), KIO::NoReload); | ||
56 | setJavaEnabled(false); | 62 | QByteArray result; | ||
57 | setPluginsEnabled(false); | 63 | connect(kiojob, &KIO::TransferJob::data, this, [&result](KJob*, QByteArray data) { | ||
64 | result += data; | ||||
65 | }); | ||||
66 | kiojob->exec(); | ||||
67 | auto buffer = new QBuffer(job); | ||||
68 | buffer->setData(result); | ||||
69 | job->reply("text/html", buffer); | ||||
58 | 70 | | |||
59 | mGrantleeFormatter = new GrantleeFormatter; | | |||
60 | 71 | | |||
61 | m_fontScaleStepping = 10; | 72 | } | ||
73 | }; | ||||
74 | | ||||
75 | View::View( QWidget *parentWidget, KActionCollection *col ) | ||||
76 | : QWebEngineView( parentWidget), mState( Docu ), mActionCollection(col) | ||||
77 | { | ||||
78 | page()->settings()->setUnknownUrlSchemePolicy(QWebEngineSettings::AllowAllUnknownUrlSchemes); | ||||
79 | page()->settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled,true); | ||||
80 | | ||||
81 | page()->settings()->setAttribute(QWebEngineSettings::PluginsEnabled, false); | ||||
82 | page()->settings()->setAttribute(QWebEngineSettings::JavascriptEnabled, false); | ||||
83 | | ||||
84 | page()->profile()->installUrlSchemeHandler("help",new HelpUrlSchemeHandler); | ||||
85 | page()->profile()->installUrlSchemeHandler("khelpcenter",new HelpUrlSchemeHandler); | ||||
86 | page()->profile()->installUrlSchemeHandler("man",new HelpUrlSchemeHandler); | ||||
87 | page()->profile()->installUrlSchemeHandler("file",new HelpUrlSchemeHandler); | ||||
88 | | ||||
62 | 89 | | |||
63 | connect(this, &View::setWindowCaption, this, &View::setTitle); | 90 | mGrantleeFormatter = new GrantleeFormatter; | ||
64 | connect( this, QOverload<const QString &, const QPoint &>::of(&View::popupMenu), this, &View::showMenu ); | | |||
65 | 91 | | |||
66 | QString css = langLookup(QStringLiteral("kdoctools5-common/kde-default.css")); | 92 | QString css = langLookup(QStringLiteral("kdoctools5-common/kde-default.css")); | ||
67 | if (!css.isEmpty()) | 93 | if (!css.isEmpty()) | ||
68 | { | 94 | { | ||
69 | QFile css_file(css); | 95 | QFile css_file(css); | ||
70 | if (css_file.open(QIODevice::ReadOnly)) | 96 | if (css_file.open(QIODevice::ReadOnly)) | ||
71 | { | 97 | { | ||
72 | QTextStream s(&css_file); | 98 | QTextStream stream(&css_file); | ||
73 | QString stylesheet = s.readAll(); | 99 | QString stylesheet = stream.readAll().replace(QLatin1Char('"'),QLatin1Char(' ')); | ||
74 | preloadStyleSheet(QStringLiteral("help:/kdoctools5-common/kde-default.css"), stylesheet); | 100 | QWebEngineScript script; | ||
101 | QString s = QString::fromLatin1("(function() {"\ | ||||
102 | " css = document.createElement('style');"\ | ||||
103 | " css.type = 'text/css';"\ | ||||
104 | " css.id = '%1';"\ | ||||
105 | " document.head.appendChild(css);"\ | ||||
106 | " css.innerText = \"%2\";"\ | ||||
107 | "})()").arg(QStringLiteral("default")).arg(stylesheet.simplified()); | ||||
108 | script.setName(QStringLiteral("default")); | ||||
109 | script.setSourceCode(s); | ||||
110 | script.setInjectionPoint(QWebEngineScript::DocumentReady); | ||||
111 | script.setRunsOnSubFrames(true); | ||||
112 | script.setWorldId(QWebEngineScript::ApplicationWorld); | ||||
113 | page()->scripts().insert(script); | ||||
75 | } | 114 | } | ||
76 | } | 115 | } | ||
77 | 116 | | |||
78 | view()->installEventFilter( this ); | 117 | page()->installEventFilter( this ); | ||
79 | } | 118 | } | ||
80 | 119 | | |||
81 | View::~View() | 120 | View::~View() | ||
82 | { | 121 | { | ||
83 | delete mGrantleeFormatter; | 122 | delete mGrantleeFormatter; | ||
84 | } | 123 | } | ||
85 | 124 | | |||
86 | void View::copySelectedText() | 125 | void View::copySelectedText() | ||
87 | { | 126 | { | ||
88 | qobject_cast<QGuiApplication*>(qApp)->clipboard()->setText( selectedText() ); | 127 | qobject_cast<QGuiApplication*>(qApp)->clipboard()->setText( selectedText() ); | ||
89 | } | 128 | } | ||
90 | 129 | | |||
91 | bool View::openUrl( const QUrl &url ) | | |||
92 | { | | |||
93 | mState = Docu; | | |||
94 | return KHTMLPart::openUrl( url ); | | |||
95 | } | | |||
96 | | ||||
97 | void View::saveState( QDataStream &stream ) | | |||
98 | { | | |||
99 | stream << mState; | | |||
100 | if ( mState == Docu ) | | |||
101 | KHTMLPart::saveState( stream ); | | |||
102 | } | | |||
103 | | ||||
104 | void View::restoreState( QDataStream &stream ) | | |||
105 | { | | |||
106 | stream >> mState; | | |||
107 | if ( mState == Docu ) | | |||
108 | KHTMLPart::restoreState( stream ); | | |||
109 | } | | |||
110 | | ||||
111 | QString View::langLookup( const QString &fname ) | 130 | QString View::langLookup( const QString &fname ) | ||
112 | { | 131 | { | ||
113 | QStringList search; | 132 | QStringList search; | ||
114 | 133 | | |||
115 | // retrieve the local search path | 134 | // retrieve the local search path | ||
116 | const QStringList localDoc = KDocTools::documentationDirs(); | 135 | const QStringList localDoc = KDocTools::documentationDirs(); | ||
117 | 136 | | |||
118 | QStringList langs = KLocalizedString::languages(); | 137 | QStringList langs = KLocalizedString::languages(); | ||
Show All 29 Lines | 163 | } | |||
148 | if (info.exists() && info.isFile() && info.isReadable()) { | 167 | if (info.exists() && info.isFile() && info.isReadable()) { | ||
149 | return *it; | 168 | return *it; | ||
150 | } | 169 | } | ||
151 | } | 170 | } | ||
152 | 171 | | |||
153 | return QString(); | 172 | return QString(); | ||
154 | } | 173 | } | ||
155 | 174 | | |||
156 | void View::setTitle( const QString &title ) | | |||
157 | { | | |||
158 | mTitle = title; | | |||
159 | } | | |||
160 | | ||||
161 | void View::beginSearchResult() | 175 | void View::beginSearchResult() | ||
162 | { | 176 | { | ||
163 | mState = Search; | 177 | mState = Search; | ||
164 | 178 | | |||
165 | begin( QUrl( QStringLiteral( "khelpcenter:search/result" ) ) ); | | |||
166 | mSearchResult = QString(); | 179 | mSearchResult = QString(); | ||
167 | } | 180 | } | ||
168 | 181 | | |||
169 | void View::writeSearchResult( const QString &str ) | 182 | void View::writeSearchResult( const QString &str ) | ||
170 | { | 183 | { | ||
171 | write( str ); | | |||
172 | mSearchResult += str; | 184 | mSearchResult += str; | ||
173 | } | 185 | } | ||
174 | 186 | | |||
175 | void View::endSearchResult() | 187 | void View::endSearchResult() | ||
176 | { | 188 | { | ||
177 | end(); | 189 | setInternalHtml(mSearchResult, QUrl( QStringLiteral( "khelpcenter:search/result" ) ) ); | ||
178 | if ( !mSearchResult.isEmpty() ) emit searchResultCacheAvailable(); | 190 | if ( !mSearchResult.isEmpty() ) emit searchResultCacheAvailable(); | ||
179 | } | 191 | } | ||
180 | 192 | | |||
181 | void View::beginInternal( const QUrl &url ) | 193 | void View::setInternalHtml(const QString& data, const QUrl &url ) | ||
182 | { | 194 | { | ||
183 | mInternalUrl = url; | 195 | mInternalUrl = url; | ||
184 | begin( mInternalUrl ); | 196 | setHtml(data, url); | ||
185 | } | 197 | } | ||
186 | 198 | | |||
187 | QUrl View::internalUrl() const | 199 | QUrl View::internalUrl() const | ||
188 | { | 200 | { | ||
189 | return mInternalUrl; | 201 | return mInternalUrl; | ||
190 | } | 202 | } | ||
191 | 203 | | |||
192 | void View::lastSearch() | 204 | void View::lastSearch() | ||
193 | { | 205 | { | ||
194 | if ( mSearchResult.isEmpty() ) return; | 206 | if ( mSearchResult.isEmpty() ) return; | ||
195 | 207 | | |||
196 | mState = Search; | 208 | mState = Search; | ||
197 | 209 | | |||
198 | begin( QUrl( QStringLiteral( "khelpcenter:search/last" ) ) ); | 210 | setHtml( mSearchResult, QUrl( QStringLiteral( "khelpcenter:search/last" ) ) ); | ||
199 | write( mSearchResult ); | | |||
200 | end(); | | |||
201 | } | | |||
202 | | ||||
203 | void View::slotIncFontSizes() | | |||
204 | { | | |||
205 | setFontScaleFactor( fontScaleFactor() + m_fontScaleStepping ); | | |||
206 | } | | |||
207 | | ||||
208 | void View::slotDecFontSizes() | | |||
209 | { | | |||
210 | setFontScaleFactor( fontScaleFactor() - m_fontScaleStepping ); | | |||
211 | } | | |||
212 | | ||||
213 | void View::showMenu( const QString& url, const QPoint& pos) | | |||
mlaurent: QAction *QWebEngineView::pageAction(QWebEnginePage::WebAction action) const
can generate some… | |||||
214 | { | | |||
215 | QMenu pop(view()); | | |||
216 | | ||||
217 | if (url.isEmpty()) | | |||
218 | { | | |||
219 | QAction *action; | | |||
220 | action = mActionCollection->action(QStringLiteral("go_home")); | | |||
221 | if (action) pop.addAction( action ); | | |||
222 | | ||||
223 | pop.addSeparator(); | | |||
224 | | ||||
225 | action = mActionCollection->action(QStringLiteral("prevPage")); | | |||
226 | if (action) pop.addAction( action ); | | |||
227 | action = mActionCollection->action(QStringLiteral("nextPage")); | | |||
228 | if (action) pop.addAction( action); | | |||
229 | | ||||
230 | pop.addSeparator(); | | |||
231 | | ||||
232 | pop.addAction( History::self().m_backAction ); | | |||
233 | pop.addAction( History::self().m_forwardAction ); | | |||
234 | } | | |||
235 | else | | |||
236 | { | | |||
237 | QAction *action = pop.addAction(i18n("Copy Link Address")); | | |||
238 | connect(action, &QAction::triggered, this, &View::slotCopyLink); | | |||
239 | | ||||
240 | mCopyURL = completeURL(url).url(); | | |||
241 | } | | |||
242 | | ||||
243 | pop.exec(pos); | | |||
244 | } | | |||
245 | | ||||
246 | void View::slotCopyLink() | | |||
247 | { | | |||
248 | QGuiApplication::clipboard()->setText(mCopyURL); | | |||
249 | } | | |||
250 | | ||||
251 | static DOM::HTMLLinkElement findLink(const DOM::NodeList& links, const char *rel) | | |||
252 | { | | |||
253 | for (unsigned i = 0; i <= links.length(); ++i) { | | |||
254 | DOM::HTMLLinkElement link(links.item(i)); | | |||
255 | if (link.isNull()) | | |||
256 | continue; | | |||
257 | | ||||
258 | if (link.rel() == rel) | | |||
259 | return link; | | |||
260 | } | | |||
261 | return DOM::HTMLLinkElement(); | | |||
262 | } | | |||
263 | | ||||
264 | bool View::prevPage(bool checkOnly) | | |||
265 | { | | |||
266 | const DOM::NodeList links = document().getElementsByTagName("link"); | | |||
267 | | ||||
268 | QUrl prevURL = urlFromLinkNode( findLink(links, "prev") ); | | |||
269 | | ||||
270 | if (!prevURL.isValid()) | | |||
271 | return false; | | |||
272 | | ||||
273 | if (!checkOnly) | | |||
274 | emit browserExtension()->openUrlRequest(prevURL); | | |||
275 | return true; | | |||
276 | } | | |||
277 | | ||||
278 | bool View::nextPage(bool checkOnly) | | |||
279 | { | | |||
280 | const DOM::NodeList links = document().getElementsByTagName("link"); | | |||
281 | | ||||
282 | QUrl nextURL = urlFromLinkNode( findLink(links, "next") ); | | |||
283 | | ||||
284 | if (!nextURL.isValid()) | | |||
285 | return false; | | |||
286 | | ||||
287 | if (!checkOnly) | | |||
288 | emit browserExtension()->openUrlRequest(nextURL); | | |||
289 | return true; | | |||
290 | } | | |||
291 | | ||||
292 | bool View::eventFilter( QObject *o, QEvent *e ) | | |||
293 | { | | |||
294 | if ( htmlDocument().links().length() == 0 ) | | |||
295 | return KHTMLPart::eventFilter( o, e ); | | |||
296 | | ||||
297 | switch ( e->type() ) { | | |||
298 | case QEvent::KeyPress: { | | |||
299 | QKeyEvent *ke = static_cast<QKeyEvent *>( e ); | | |||
300 | if ( ke->modifiers() & Qt::ShiftModifier && ke->key() == Qt::Key_Space ) { | | |||
301 | // If we're on the first page, it does not make sense to go back. | | |||
302 | if ( baseURL().path().endsWith( QLatin1String("/index.html") ) ) | | |||
303 | return KHTMLPart::eventFilter( o, e ); | | |||
304 | | ||||
305 | const QScrollBar * const scrollBar = view()->verticalScrollBar(); | | |||
306 | if ( scrollBar->value() == scrollBar->minimum() ) { | | |||
307 | if (prevPage()) | | |||
308 | return true; | | |||
309 | } | | |||
310 | } else if ( ke->key() == Qt::Key_Space ) { | | |||
311 | const QScrollBar * const scrollBar = view()->verticalScrollBar(); | | |||
312 | if ( scrollBar->value() == scrollBar->maximum() ) { | | |||
313 | if (nextPage()) | | |||
314 | return true; | | |||
315 | } | | |||
316 | } | | |||
317 | break; | | |||
318 | } | | |||
319 | case QEvent::WhatsThis: { | | |||
320 | QHelpEvent *he = static_cast<QHelpEvent *>( e ); | | |||
321 | const QString text = i18n( "<p>Read the topic documentation in this window.<br /><br />Press <b>Space</b>/<b>Shift+Space</b> to scroll, <b>%1</b> to find something, <b>Tab</b>/<b>Shift+Tab</b> to jump, and <b>Enter</b> to follow.</p>", | | |||
322 | actionCollection()->action( QStringLiteral("find") )->shortcut().toString( QKeySequence::NativeText ) ); | | |||
323 | QWhatsThis::showText( he->globalPos(), text, qobject_cast<QWidget *>( o ) ); | | |||
324 | e->accept(); | | |||
325 | return true; | | |||
326 | } | | |||
327 | case QEvent::QueryWhatsThis: | | |||
328 | e->accept(); | | |||
329 | return true; | | |||
330 | default: | | |||
331 | break; | | |||
332 | } | | |||
333 | return KHTMLPart::eventFilter( o, e ); | | |||
334 | } | | |||
335 | | ||||
336 | QUrl View::urlFromLinkNode( const DOM::HTMLLinkElement &link ) const | | |||
337 | { | | |||
338 | if ( link.isNull() ) | | |||
339 | return QUrl(); | | |||
340 | | ||||
341 | DOM::DOMString domHref = link.href(); | | |||
342 | if (domHref.isNull()) | | |||
343 | return QUrl(); | | |||
344 | | ||||
345 | const QUrl domHrefUrl( domHref.string() ); | | |||
346 | if ( !domHrefUrl.isRelative() ) | | |||
347 | return domHrefUrl; | | |||
348 | | ||||
349 | return QUrl(baseURL().toString() +QLatin1Char('/')+ domHref.string()); | | |||
350 | } | 211 | } | ||
351 | 212 | | |||
352 | void View::slotReload( const QUrl &url ) | 213 | void View::slotReload( const QUrl &url ) | ||
353 | { | 214 | { | ||
354 | const_cast<KHTMLSettings *>( settings() )->init( KSharedConfig::openConfig().data() ); | | |||
355 | KParts::OpenUrlArguments args = arguments(); | | |||
356 | args.setReload( true ); | | |||
357 | setArguments( args ); | | |||
358 | if ( url.isEmpty() ) | 215 | if ( url.isEmpty() ) | ||
359 | openUrl( baseURL() ); | 216 | reload(); | ||
360 | else | 217 | else | ||
361 | openUrl( url ); | 218 | load( url ); | ||
362 | } | 219 | } | ||
363 | 220 | | |||
364 | 221 | | |||
365 | // vim:ts=2:sw=2:et | 222 | // vim:ts=2:sw=2:et |
QAction *QWebEngineView::pageAction(QWebEnginePage::WebAction action) const
can generate some actions.