Reader View based on Readability.js (inspired and code partially borrowed from harbour-webpirate project (https://github.com/Dax89/harbour-webpirate)

This commit is contained in:
Muki 2016-11-10 08:12:50 +01:00
parent 008b223474
commit c64552e0a2
36 changed files with 2551 additions and 1436 deletions

View file

@ -0,0 +1,4 @@
[Dolphin]
PreviewsShown=true
Timestamp=2016,7,22,5,51,26
Version=3

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 868 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1,4 @@
[Dolphin]
PreviewsShown=true
Timestamp=2016,7,23,11,59,38
Version=3

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -0,0 +1,4 @@
[Dolphin]
PreviewsShown=true
Timestamp=2016,7,23,11,59,29
Version=3

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 924 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1,4 @@
[Dolphin]
PreviewsShown=true
Timestamp=2016,7,23,11,59,15
Version=3

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -0,0 +1,4 @@
[Dolphin]
PreviewsShown=true
Timestamp=2016,7,23,11,59,54
Version=3

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -0,0 +1,4 @@
[Dolphin]
PreviewsShown=true
Timestamp=2016,7,23,12,0,7
Version=3

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -38,7 +38,27 @@ Page {
property int index
property int feedindex
property bool cached
property variant _settings: settings
property bool themeApply: true
property bool navigateBackPop: true
function init() {
view.loadHtml(utils.formatHtml(content, settings.offlineMode, ""))
navigateBackPop = true
themeApply = true
}
function navigateBack() {
if (view.canGoBack)
view.goBack()
else
if (navigateBackPop)
pageStack.pop()
else
init()
}
allowedOrientations: {
switch (settings.allowedOrientations) {
@ -51,13 +71,9 @@ Page {
}
Component.onCompleted: {
bar.hide();
controlbar.show();
var fontSize = getFontSize();
var style = "h1,h2,h3,div,p,pre,code{word-wrap:break-word}body{margin:"+Theme.horizontalPageMargin+";margin-bottom:"+Theme.itemSizeExtraLarge+
";background-color:"+Theme.highlightDimmerColor+";"+"color:"+Theme.primaryColor+";"+"font-size:"+fontSize+
";font-family:"+Theme.fontFamily+"}"+"a{color:"+Theme.highlightColor+"}"+"img{height:auto;max-width:100%;width:auto;}";
view.loadHtml(utils.formatHtml(content, settings.offlineMode, style));
bar.hide()
controlbar.show()
init()
}
// Workaround for 'High Power Consumption' webkit bug
@ -79,25 +95,6 @@ Page {
}
}
function navigate(url) {
var hcolor = Theme.highlightColor.toString().substr(1, 6);
var shcolor = Theme.secondaryHighlightColor.toString().substr(1, 6);
var imgWidth = settings.fontSize == 1 ? root.width/(1.5) : settings.fontSize == 2 ? root.width/(2.0) : root.width;
return url+"?fontsize=18px&width="+imgWidth+"&highlightColor="+hcolor+"&secondaryHighlightColor="+shcolor+"&margin="+Theme.paddingMedium;
}
function getFontSize() {
return (Theme.fontSizeSmall * (settings.fontSize / 10) * 0.6) * Theme.pixelRatio;
}
function updateFontSize() {
view.experimental.evaluateJavaScript(
"(function(){document.body.style.fontSize="+getFontSize()+";})()",
function(result) {
//console.log("result:",result);
});
}
function check() {
// Not allowed while Syncing
if (dm.busy || fetcher.busy || dm.removerBusy) {
@ -115,7 +112,7 @@ Page {
if (!settings.offlineMode && !dm.online) {
if (cached) {
// Entry cached
notification.show(qsTr("Network connection is unavailable.\nSwitching to Offline mode."));
notification.show(qsTr("Network connection is unavailable.\nSwitching to offline mode."));
settings.offlineMode = true;
} else {
// Entry not cached
@ -130,7 +127,7 @@ Page {
function openEntryInBrowser() {
entryModel.setData(index, "read", 1, "");
notification.show(qsTr("Launching an external browser..."));
Qt.openUrlExternally(settings.offlineMode ? navigate(offlineUrl) : onlineUrl);
Qt.openUrlExternally(settings.offlineMode ? offlineUrl : onlineUrl);
}
function openUrlEntryInBrowser(url) {
@ -139,13 +136,6 @@ Page {
}
function openEntryInViewer() {
// (!dm.online && settings.offlineMode) -> WORKAROUND for https://github.com/mkiol/kaktus/issues/14
if (!dm.online && settings.offlineMode) {
openEntryInBrowser();
return;
}
pageStack.replace(Qt.resolvedUrl("WebPreviewPage.qml"),
{"entryId": entryId,
"onlineUrl": onlineUrl,
@ -161,8 +151,23 @@ Page {
});
}
function openEntry() {
function openUrlInViewer(url) {
pageStack.replace(Qt.resolvedUrl("WebPreviewPage.qml"),
{"entryId": entryId,
"onlineUrl": url,
"offlineUrl": url,
"title": title,
"stared": stared,
"liked": liked,
"broadcast": broadcast,
"index": index,
"feedindex": feedindex,
"read" : read,
"cached" : cached
});
}
function openEntry() {
if (!check()) {
return;
}
@ -175,6 +180,45 @@ Page {
openEntryInViewer();
}
function initTheme() {
var theme = { "primaryColor": Theme.rgba(Theme.primaryColor, 1.0).toString(),
"secondaryColor": Theme.rgba(Theme.secondaryColor, 1.0).toString(),
"highlightColor": Theme.rgba(Theme.highlightColor, 1.0).toString(),
"highlightColorDark": Qt.darker(Theme.highlightColor).toString(),
"secondaryHighlightColor": Theme.rgba(Theme.secondaryHighlightColor, 1.0).toString(),
"highlightDimmerColor": Theme.rgba(Theme.highlightDimmerColor, 1.0).toString(),
"fontFamily": Theme.fontFamily,
"fontFamilyHeading": Theme.fontFamilyHeading,
"pageMargin": Theme.horizontalPageMargin/Theme.pixelRatio,
"pageMarginBottom": Theme.itemSizeMedium/Theme.pixelRatio,
"fontSize": Theme.fontSizeMedium,
"fontSizeTitle": Theme.fontSizeLarge,
"zoom": settings.zoom,
"theme": settings.readerTheme }
postMessage("theme_set", { "theme": theme })
postMessage("theme_apply")
}
function updateZoom(delta) {
var zoom = settings.zoom;
settings.zoom = ((zoom + delta) <= 0.5) || ((zoom + delta) >= 2.0) ? zoom : zoom + delta
var theme = { "zoom": settings.zoom }
postMessage("theme_set", { "theme": theme })
}
function messageReceivedHandler(message) {
if (message.type === "theme_init") {
if (root.themeApply) {
initTheme()
root.themeApply = false
}
}
}
function postMessage(message, data) {
view.experimental.postMessage(JSON.stringify({ "type": message, "data": data }));
}
Connections {
target: settings
onFontSizeChanged: updateFontSize()
@ -192,40 +236,86 @@ Page {
experimental.transparentBackground: true
experimental.overview: false
experimental.enableResizeContent: true
experimental.preferences.javascriptEnabled: true
experimental.preferences.navigatorQtObjectEnabled: true
onLoadingChanged: {
/*console.log("onLoadingChanged:")
console.log(" url: ", loadRequest.url)
console.log(" status: ", loadRequest.status)
console.log(" error string: ", loadRequest.errorString)
console.log(" error code:: ", loadRequest.errorCode)
if (loadRequest.status === WebView.LoadSucceededStatus) {
console.log(" LoadSucceededStatus")
}*/
}
experimental.userScripts: [
Qt.resolvedUrl("js/ObjectOverrider.js"),
Qt.resolvedUrl("js/Readability.js"),
Qt.resolvedUrl("js/Theme.js"),
Qt.resolvedUrl("js/ReaderModeHandler.js"),
Qt.resolvedUrl("js/MessageListener.js")]
experimental.onMessageReceived: {
console.log("onMessageReceived data:", message.data)
root.messageReceivedHandler(JSON.parse(message.data))
}
onNavigationRequested: {
if (!Qt.application.active) {
request.action = WebView.IgnoreRequest;
return;
return
}
/*var url = "" + request.url
if (url.indexOf("about:") === 0) {
request.action = WebView.IgnoreRequest
return
}*/
//console.log("request.url: " + request.url)
//console.log("onlineUrl: " + root.onlineUrl)
if (request.url == root.onlineUrl || request.url == root.offlineUrl) {
root.openEntryInViewer()
request.action = WebView.IgnoreRequest
return
}
// Offline
if (settings.offlineMode) {
if (request.navigationType === WebView.LinkClickedNavigation) {
request.action = WebView.IgnoreRequest;
request.action = WebView.IgnoreRequest
} else {
request.action = WebView.AcceptRequest
}
return;
return
}
// Online
if (request.navigationType === WebView.LinkClickedNavigation) {
if (_settings.webviewNavigation === 0) {
request.action = WebView.IgnoreRequest;
return;
request.action = WebView.IgnoreRequest
return
}
if (_settings.webviewNavigation === 1) {
request.action = WebView.IgnoreRequest;
root.openUrlEntryInBrowser(request.url);
return;
request.action = WebView.IgnoreRequest
root.openUrlEntryInBrowser(request.url)
return
}
if (_settings.webviewNavigation === 2) {
request.action = WebView.AcceptRequest
root.openUrlInViewer(request.url)
request.action = WebView.IgnoreRequest
return;
//request.action = WebView.AcceptRequest
//navigateBackPop = false
//return;
}
}
}
@ -247,7 +337,7 @@ Page {
IconBarItem {
text: qsTr("Back")
icon: "image://theme/icon-m-back"
onClicked: pageStack.pop()
onClicked: root.navigateBack()
}
IconBarItem {
@ -325,7 +415,7 @@ Page {
text: qsTr("Increase font")
icon: "image://icons/icon-m-fontup"
onClicked: {
settings.fontSize++;
root.updateZoom(0.1)
}
}
@ -333,7 +423,7 @@ Page {
text: qsTr("Decrease font")
icon: "image://icons/icon-m-fontdown"
onClicked: {
settings.fontSize--;
root.updateZoom(-0.1)
}
}

View file

@ -629,9 +629,9 @@ Page {
}
TextSwitchWithIcon {
text: qsTr("Read mode")
description: qsTr("Web pages will be reformatted into an easy to read version.")
iconSource: settings.readerMode ? "image://icons/icon-m-reader-selected" : "image://icons/icon-m-reader"
text: qsTr("Auto switch to Reader View")
description: qsTr("Reader View is a feature that strips away clutter like buttons, ads and background images, and changes the page's layout for better readability.")
iconSource: "image://icons/icon-m-reader"
onCheckedChanged: {
settings.readerMode = checked;
}
@ -640,13 +640,46 @@ Page {
}
}
TextSwitch {
text: qsTr("Show images")
onCheckedChanged: {
settings.showTabIcons = checked;
ComboBox {
width: root.width
label: qsTr("Reader View theme")
description: qsTr("Style theme which will be used to display articles in Reader View.")
currentIndex: {
if (settings.readerTheme === "dark")
return 0;
if (settings.readerTheme === "light")
return 1;
}
Component.onCompleted: {
checked = settings.showTabIcons;
menu: ContextMenu {
MenuItem { text: qsTr("Dark") }
MenuItem { text: qsTr("Light") }
}
onCurrentIndexChanged: {
switch (currentIndex) {
case 0:
settings.readerTheme = "dark";
break;
case 1:
settings.readerTheme = "light";
break;
}
}
}
Slider {
width: root.width
minimumValue: 50
maximumValue: 200
value: Math.floor(settings.zoom * 100)
label: qsTr("Viewer font size level")
valueText: value + "%"
stepSize: 10
onValueChanged: settings.zoom = value/100
onClicked: {
// Default value
value = 100;
}
}
@ -712,50 +745,7 @@ Page {
onCurrentIndexChanged: settings.allowedOrientations = currentIndex
}
ComboBox {
width: root.width
label: qsTr("Offline viewer style")
//description: qsTr("Style which will be used to display articles in the Offline mode.")
currentIndex: {
if (settings.offlineTheme === "black")
return 0;
if (settings.offlineTheme === "white")
return 1;
}
menu: ContextMenu {
MenuItem { id: blackTheme; text: qsTr("Black") }
MenuItem { id: whiteTheme; text: qsTr("White") }
}
onCurrentIndexChanged: {
switch (currentIndex) {
case 0:
settings.offlineTheme = "black";
break;
case 1:
settings.offlineTheme = "white";
break;
}
}
}
Slider {
width: root.width
minimumValue: 1
maximumValue: 10
value: settings.fontSize-10
label: qsTr("Viewer font size")
valueText: value
stepSize: 1
onValueChanged: settings.fontSize = value+10
onClicked: {
// Default value
value = 5;
}
}
SectionHeader {
/*SectionHeader {
text: qsTr("Other")
}
@ -765,7 +755,7 @@ Page {
onClicked: {
guide.show();
}
}
}*/
Item {
height: Theme.paddingMedium

View file

@ -1,5 +1,5 @@
/*
Copyright (C) 2014 Michal Kosciesza <michal@mkiol.net>
Copyright (C) 2016 Michal Kosciesza <michal@mkiol.net>
This file is part of Kaktus.
@ -17,7 +17,10 @@
along with Kaktus. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.0
// Some ideas heavily inspired and partially borrowed from
// harbour-webpirate project (https://github.com/Dax89/harbour-webpirate)
import QtQuick 2.1
import Sailfish.Silica 1.0
import QtWebKit 3.0
@ -40,75 +43,121 @@ Page {
property variant _settings: settings
property int markAsReadTime: 4000
property bool updateStyleDone: false
property int imgWidth: {
switch (settings.fontSize) {
case 1:
return view.width/(1.5);
case 2:
return view.width/(2.0);
}
return view.width;
}
property int toolbarHideTime: 4000
property bool readerMode: false
property bool readerModePossible: false
property bool autoReaderMode: settings.readerMode
function openUrlEntryInBrowser(url) {
notification.show(qsTr("Launching an external browser..."));
Qt.openUrlExternally(url);
notification.show(qsTr("Launching an external browser..."))
Qt.openUrlExternally(url)
}
function onlineDownload(url, id) {
//console.log("onlineDownload url=",url);
dm.onlineDownload(id, url);
proggressPanel.text = qsTr("Loading page content...");
proggressPanel.open = true;
dm.onlineDownload(id, url)
proggressPanel.text = qsTr("Loading page content...")
proggressPanel.open = true
}
function init() {
navigate(settings.offlineMode ? offlineUrl : onlineUrl)
}
function navigate(url) {
var hcolor = Theme.highlightColor.toString().substr(1, 6);
var shcolor = Theme.secondaryHighlightColor.toString().substr(1, 6);
view.url = url+"?fontsize=18px&width="+imgWidth+"&highlightColor="+hcolor+"&secondaryHighlightColor="+shcolor+"&margin="+Theme.paddingMedium;
if (settings.offlineMode) {
// WORKAROUND for https://github.com/mkiol/kaktus/issues/14
var xhr = new XMLHttpRequest()
xhr.onreadystatechange = function () {
if(xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
view.loadHtml(xhr.responseText)
}
}
xhr.open("GET", offlineUrl);
xhr.send()
} else {
view.url = url
}
}
function updateStyle() {
var viewport = settings.fontSize / 10;
//console.log("Theme.pixelRatio: " + Theme.pixelRatio)
viewport = viewport < 1 ? viewport.toPrecision(1) : viewport.toPrecision(2);
viewport *= Theme.pixelRatio;
function navigateBack() {
if (view.canGoBack) {
view.goBack()
root.readerModePossible = false
//view.scrollToTop()
} else {
pageStack.pop()
}
}
view.experimental.evaluateJavaScript(
"(function(){
// viewport
var viewport = document.querySelector('meta[name=\"viewport\"]');
if (viewport) {
viewport.content = 'initial-scale="+viewport+"';
} else {
document.getElementsByTagName('head')[0].appendChild('<meta name=\"viewport\" content=\"initial-scale="+viewport+"\">');
}
function initTheme() {
var theme = { "primaryColor": Theme.rgba(Theme.primaryColor, 1.0).toString(),
"secondaryColor": Theme.rgba(Theme.secondaryColor, 1.0).toString(),
"highlightColor": Theme.rgba(Theme.highlightColor, 1.0).toString(),
"highlightColorDark": Qt.darker(Theme.highlightColor).toString(),
"secondaryHighlightColor": Theme.rgba(Theme.secondaryHighlightColor, 1.0).toString(),
"highlightDimmerColor": Theme.rgba(Theme.highlightDimmerColor, 1.0).toString(),
"fontFamily": Theme.fontFamily,
"fontFamilyHeading": Theme.fontFamilyHeading,
"pageMargin": Theme.horizontalPageMargin/Theme.pixelRatio,
"pageMarginBottom": Theme.itemSizeMedium/Theme.pixelRatio,
"fontSize": Theme.fontSizeMedium,
"fontSizeTitle": Theme.fontSizeLarge,
"zoom": settings.zoom,
"theme": settings.readerTheme }
postMessage("theme_set", { "theme": theme })
}
// bottom margin
document.body.style.marginBottom=\""+Theme.itemSizeExtraLarge+"px\";
//document.body.style.maxWidth=\"100%\";
//document.body.style.width=\"100%\";
return 0;
})()",
function(result) {
//console.log("result:",result);
});
function updateZoom(delta) {
var zoom = settings.zoom;
settings.zoom = ((zoom + delta) <= 0.5) || ((zoom + delta) >= 2.0) ? zoom : zoom + delta
var theme = { "zoom": settings.zoom }
postMessage("theme_set", { "theme": theme })
}
function switchReaderMode() {
postMessage(root.readerMode ? "readermodehandler_disable" : "readermodehandler_enable");
}
function messageReceivedHandler(message) {
if (message.type === "theme_init") {
initTheme()
} else if (message.type === "readability_result") {
root.readerModePossible = message.data.possible
root.readerMode = message.data.enabled
// Auto switch to reader mode
if (!root.readerMode && root.readerModePossible &&
(root.autoReaderMode || settings.offlineMode)) {
switchReaderMode()
root.autoReaderMode = false
}
} else if (message.type === "readability_status") {
console.log("readability_status: " + message.data.enabled)
} else if (message.type === "readability_enabled") {
root.readerMode = true
view.scrollToTop()
} else if (message.type === "readability_disabled") {
root.readerMode = false
view.scrollToTop()
}
}
function postMessage(message, data) {
view.experimental.postMessage(JSON.stringify({ "type": message, "data": data }));
}
ActiveDetector {}
onForwardNavigationChanged: {
if (forwardNavigation)
forwardNavigation = false;
}
/*onBackNavigationChanged: {
if (backNavigation)
backNavigation = false;
}*/
showNavigationIndicator: false
allowedOrientations: {
@ -121,55 +170,7 @@ Page {
return Orientation.Landscape | Orientation.Portrait;
}
Component.onCompleted: {
if (settings.offlineMode) {
navigate(offlineUrl);
} else {
if (settings.readerMode) {
onlineDownload(root.onlineUrl, root.entryId);
} else {
view.url = onlineUrl;
}
}
}
Connections {
target: settings
onReaderModeChanged: {
if (settings.readerMode) {
//onlineDownload(root.onlineUrl, root.entryId);
onlineDownload(root.onlineUrl, "");
} else {
view.url = onlineUrl;
}
}
onFontSizeChanged: {
// Changing viewport in WebView
updateStyle();
}
}
Connections {
target: dm
onOnlineDownloadReady: {
//console.log("onOnlineDownloadReady url=",url);
if (id=="") {
var newUrl = cache.getUrlbyUrl(url);
//console.log("newurl=",newUrl);
navigate(newUrl);
offlineUrl = newUrl;
return;
}
navigate(offlineUrl);
entryModel.setData(index,"cached",1, "");
}
onOnlineDownloadFailed: {
notification.show(qsTr("Failed to switch to Reader mode :-("));
proggressPanel.open = false;
//settings.readerMode = false;
}
}
Component.onCompleted: init()
// Workaround for 'High Power Consumption' webkit bug
Connections {
@ -194,67 +195,51 @@ Page {
id: view
anchors { top: parent.top; left: parent.left; right: parent.right}
//height: parent.height - (controlbar.shown ? controlbar.height : 0)
height: parent.height
//overridePageStackNavigation: true
experimental.userAgent: _settings.getDmUserAgent()
experimental.transparentBackground: _settings.offlineMode || _settings.readerMode
experimental.preferences.javascriptEnabled: true
experimental.preferences.navigatorQtObjectEnabled: true
experimental.preferredMinimumContentsWidth: 980
experimental.overview: false
experimental.enableResizeContent: true
experimental.userAgent: _settings.getDmUserAgent()
onLoadProgressChanged: {
// Changing viewport on 50% load proggress in WebView to increase font size
if (loadProgress>50) {
root.updateStyle();
root.updateStyleDone = true;
}
experimental.userScripts: [
Qt.resolvedUrl("js/ObjectOverrider.js"),
Qt.resolvedUrl("js/Readability.js"),
Qt.resolvedUrl("js/Theme.js"),
Qt.resolvedUrl("js/ReaderModeHandler.js"),
Qt.resolvedUrl("js/MessageListener.js")]
experimental.onMessageReceived: {
console.log("onMessageReceived data:", message.data)
root.messageReceivedHandler(JSON.parse(message.data))
}
onLoadingChanged: {
/*console.log(">>> onLoadingChanged");
console.log("loadRequest.url=",loadRequest.url);
console.log("loadRequest.status=",loadRequest.status);
console.log("loadRequest.errorString=",loadRequest.errorString);
console.log("loadRequest.errorCode=",loadRequest.errorCode);
console.log("loadRequest.errorDomain=",loadRequest.errorDomain);*/
switch (loadRequest.status) {
case WebView.LoadStartedStatus:
proggressPanel.text = qsTr("Loading page content...");
proggressPanel.open = true;
// Reseting viewport flag
root.updateStyleDone = false;
break;
case WebView.LoadSucceededStatus:
proggressPanel.open = false;
// Changing viewport in WebView to increase font size
root.updateStyle();
// Start timer to mark as read
if (!root.read)
timer.start();
// Readability.js
postMessage("readermodehandler_check", { "title": view.canGoBack ? "" : root.title });
break;
case WebView.LoadFailedStatus:
proggressPanel.open = false;
//console.log("LoadFailedStatus");
if (_settings.offlineMode) {
notification.show(qsTr("Failed to load item from local cache :-("));
notification.show(qsTr("Failed to load page from local cache :-("));
} else {
if (_settings.readerMode) {
notification.show(qsTr("Failed to switch to Reader mode :-("));
_settings.readerMode = false;
} else {
notification.show(qsTr("Failed to load page content :-("));
}
notification.show(qsTr("Failed to load page content :-("));
}
break;
default:
@ -263,27 +248,34 @@ Page {
}
onNavigationRequested: {
//console.log("onNavigationRequested, URL:",request.url,"navigationType:",request.navigationType);
/*console.log("onNavigationRequested: ")
console.log(" url:",request.url)
console.log(" navigation type:", request.navigationType)
console.log(" navigation LinkClickedNavigation:", request.navigationType === WebView.LinkClickedNavigation)
console.log(" navigation FormSubmittedNavigation:", request.navigationType === WebView.FormSubmittedNavigation)
console.log(" navigation BackForwardNavigation:", request.navigationType === WebView.BackForwardNavigation)
console.log(" navigation ReloadNavigation:", request.navigationType === WebView.ReloadNavigation)
console.log(" navigation FormResubmittedNavigation:", request.navigationType === WebView.FormResubmittedNavigation)
console.log(" navigation OtherNavigation:", request.navigationType === WebView.OtherNavigation)
console.log(" action:", request.action);*/
if (!Qt.application.active) {
request.action = WebView.IgnoreRequest;
return;
request.action = WebView.IgnoreRequest
return
}
// Offline
if (settings.offlineMode) {
if (request.navigationType === WebView.LinkClickedNavigation) {
request.action = WebView.IgnoreRequest;
request.action = WebView.IgnoreRequest
} else {
request.action = WebView.AcceptRequest
}
return;
return
}
// Online
if (request.navigationType === WebView.LinkClickedNavigation) {
if (_settings.webviewNavigation === 0) {
request.action = WebView.IgnoreRequest;
return;
@ -296,14 +288,8 @@ Page {
}
if (_settings.webviewNavigation === 2) {
onlineUrl = request.url;
if (_settings.readerMode) {
//console.log("Reader mode: navigation request url=",request.url);
onlineDownload(request.url);
request.action = WebView.IgnoreRequest;
return;
}
request.action = WebView.AcceptRequest
//root.readerMode = false
return;
}
}
@ -316,11 +302,12 @@ Page {
id: controlbar
flickable: view
transparent: false
showable: !hideToolbarTimer.running
IconBarItem {
text: qsTr("Back")
icon: "image://theme/icon-m-back"
onClicked: pageStack.pop()
onClicked: root.navigateBack()
}
IconBarItem {
@ -353,11 +340,12 @@ Page {
}
IconBarItem {
text: qsTr("Toggle Read mode")
icon: settings.readerMode ? "image://icons/icon-m-reader-selected" : "image://icons/icon-m-reader"
enabled: !settings.offlineMode
text: qsTr("Toggle Reader View")
icon: root.readerMode ? "image://icons/icon-m-reader-selected" : "image://icons/icon-m-reader"
enabled: root.readerModePossible && !settings.offlineMode
visible: true
onClicked: {
settings.readerMode = !settings.readerMode;
root.switchReaderMode()
}
}
@ -398,7 +386,7 @@ Page {
text: qsTr("Increase font")
icon: "image://icons/icon-m-fontup"
onClicked: {
settings.fontSize++
root.updateZoom(0.1)
}
}
@ -406,7 +394,7 @@ Page {
text: qsTr("Decrease font")
icon: "image://icons/icon-m-fontdown"
onClicked: {
settings.fontSize--
root.updateZoom(-0.1)
}
}
@ -418,13 +406,21 @@ Page {
Clipboard.text = root.onlineUrl;
}
}
IconBarItem {
text: qsTr("Hide toolbar")
icon: "image://theme/icon-m-dismiss"
onClicked: {
hideToolbarTimer.start()
controlbar.hide()
}
}
}
ProgressPanel {
id: proggressPanel
transparent: false
anchors.left: parent.left
//height: isPortrait ? app.panelHeightPortrait : app.panelHeightLandscape
cancelable: true
onCloseClicked: view.stop()
@ -442,4 +438,9 @@ Page {
}
}
}
Timer {
id: hideToolbarTimer
interval: root.toolbarHideTime
}
}

View file

@ -0,0 +1,28 @@
// Code heavily inspired and partially borrowed from
// harbour-webpirate project (https://github.com/Dax89/harbour-webpirate)
window.Kaktus_MessageListenerObject = function() {
navigator.qt.onmessage = this.onMessage.bind(this);
};
window.Kaktus_MessageListenerObject.prototype.onMessage = function(message) {
var obj = JSON.parse(message.data);
var data = obj.data;
if(obj.type === "readermodehandler_enable")
Kaktus_ReaderModeHandler.switchMode(true);
else if(obj.type === "readermodehandler_disable")
Kaktus_ReaderModeHandler.switchMode(false);
else if(obj.type === "readermodehandler_check")
Kaktus_ReaderModeHandler.check(data);
else if(obj.type === "readermodehandler_status")
Kaktus_ReaderModeHandler.status();
else if(obj.type === "theme_set")
Kaktus_Theme.set(data.theme);
else if(obj.type === "theme_update_scale")
Kaktus_Theme.updateScale();
else if(obj.type === "theme_apply")
Kaktus_Theme.apply();
};
window.Kaktus_MessageListener = new window.Kaktus_MessageListenerObject();

View file

@ -0,0 +1,29 @@
// Code borrowed from harbour-webpirate project (https://github.com/Dax89/harbour-webpirate)
console.log = function(msg) {
var data = { type: "console_log",
data: { log: ((typeof msg === "object") ? msg.toString() : msg) } };
navigator.qt.postMessage(JSON.stringify(data));
}
console.error = function(msg) {
var data = { type: "console_error",
data: { log: ((typeof msg === "object") ? msg.toString() : msg) } };
navigator.qt.postMessage(JSON.stringify(data));
}
window.open = function(url) { // Popup Blocker
var data = { type: "window_open",
data: { url: url } };
navigator.qt.postMessage(JSON.stringify(data));
}
window.onerror = function(errmsg, url, line) { // Print Javascript Errors
if((url !== undefined) && url.length)
console.log(url + "(" + line + "): " + errmsg);
return false; // Ignore other errors
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,126 @@
// Code heavily inspired and partially borrowed from
// harbour-webpirate project (https://github.com/Dax89/harbour-webpirate)
window.Kaktus_ReaderModeHandlerObject = function() {
this.enabled = false;
this.orginalDoc = null;
this.readabilityDoc = null;
this.readabilityPossible = false;
};
window.Kaktus_ReaderModeHandlerObject.prototype.applyFiltering = function(doc, insert) {
var elements, i;
// kaktus img
var img = doc.getElementById("_kaktus_img");
// article element
elements = doc.getElementsByTagName("article");
if (elements.length > 0) {
var newBody = "" + insert + (img ? img.outerHTML : "");
for (i = 0; i < elements.length; i++) {
newBody += "<article>" + elements[i].innerHTML + "</article>";
}
doc.body.innerHTML = newBody;
}
// width, height, target attributes
elements = doc.querySelectorAll("[width],[height],[target],[class]");
for (i = 0; i < elements.length; i++) {
elements[i].removeAttribute("width");
elements[i].removeAttribute("height");
elements[i].removeAttribute("target");
elements[i].removeAttribute("class");
}
// img max-width
//elements = doc.getElementsByTagName("img");
//for (i = 0; i < elements.length; i++)
// elements[i].style.maxWidth = "100%";
};
window.Kaktus_ReaderModeHandlerObject.prototype.check = function(data) {
var loc = document.location;
var uri = { "spec": loc.href,
"host": loc.host,
"prePath": loc.protocol + "//" + loc.host,
"scheme": loc.protocol.substr(0, loc.protocol.indexOf(":")),
"pathBase": loc.protocol + "//" + loc.host + loc.pathname.substr(0, loc.pathname.lastIndexOf("/") + 1) };
var newDoc1 = document.implementation.createDocument('http://www.w3.org/1999/xhtml', 'html', null);
var newBody1 = document.createElementNS('http://www.w3.org/1999/xhtml', 'body');
newBody1.innerHTML = document.body.innerHTML;
newDoc1.documentElement.appendChild(newBody1);
var article = null;
try {
article = new window.Readability(uri, newDoc1).parse();
} catch (Exception) {}
this.readabilityPossible = article && article.length > 0 ? true : false;
//console.log("Readability possible: " + this.readabilityPossible)
if (this.readabilityPossible) {
var title = article.title !== "" ? article.title : data.title;
var insert = title === "" ? "" : "<h1 id='_kaktus_title'>" + title + "</h1>";
var newDoc2 = document.implementation.createDocument('http://www.w3.org/1999/xhtml', 'html', null);
var newBody2 = document.createElementNS('http://www.w3.org/1999/xhtml', 'body');
newBody2.innerHTML = insert + article.content;
newDoc2.documentElement.appendChild(newBody2);
this.applyFiltering(newDoc2, insert);
this.readabilityDoc = newDoc2.documentElement.innerHTML;
this.orginalDoc = document.documentElement.innerHTML;
//console.log("Readability: " + this.readabilityDoc);
}
var result = { type: "readability_result", data: { possible: this.readabilityPossible, enabled: this.enabled } };
navigator.qt.postMessage(JSON.stringify(result));
};
window.Kaktus_ReaderModeHandlerObject.prototype.disable = function() {
this.enabled = false;
document.documentElement.innerHTML = this.orginalDoc;
var result = { type: "readability_disabled" };
navigator.qt.postMessage(JSON.stringify(result));
window.Kaktus_Theme.updateScale();
};
window.Kaktus_ReaderModeHandlerObject.prototype.enable = function() {
this.enabled = true;
document.documentElement.innerHTML = this.readabilityDoc;
window.Kaktus_Theme.apply();
var result = { type: "readability_enabled" };
navigator.qt.postMessage(JSON.stringify(result));
};
window.Kaktus_ReaderModeHandlerObject.prototype.status = function() {
var result = { type: "readability_status", data: { enabled: this.enabled } };
navigator.qt.postMessage(JSON.stringify(result));
};
window.Kaktus_ReaderModeHandlerObject.prototype.switchMode = function(enabled) {
if (this.enabled === enabled)
return;
if (this.enabled) {
this.disable();
return;
}
if (!this.readabilityPossible) {
return;
}
this.enable();
};
window.Kaktus_ReaderModeHandler = new window.Kaktus_ReaderModeHandlerObject();

101
sailfish/qml/js/Theme.js Normal file
View file

@ -0,0 +1,101 @@
// Code heavily inspired and partially borrowed from
// harbour-webpirate project (https://github.com/Dax89/harbour-webpirate)
window.Kaktus_ThemeObject = function() {
var result = { type: "theme_init" };
navigator.qt.postMessage(JSON.stringify(result));
};
window.Kaktus_ThemeObject.prototype.set = function(theme) {
for(var prop in theme)
this[prop] = theme[prop];
this.updateScale();
};
// Hack to fix wrong device Pixel Ratio reported by Webview (thanks to llelectronics)
window.Kaktus_ThemeObject.prototype.getPixelRatio = function() {
if(window.screen.width <= 540) // Jolla devicePixelRatio: 1.5
return 1.5
if(window.screen.width > 540 && screen.width <= 768) // Nexus 4 devicePixelRatio: 2.0
return 2.0
if (window.screen.width > 768) // Nexus 5 devicePixelRatio: 3.0
return 3.0
};
window.Kaktus_ThemeObject.prototype.updateScale = function() {
var pr = this.getPixelRatio();
var _scale = this.zoom ? this.zoom * pr : pr;
var scale = Math.round((_scale <= 0.5 ? 0.5 : _scale ) * 10 ) / 10;
var viewport_ele = document.querySelector("meta[name='viewport']");
var content = "width=device-width/" + scale + ", initial-scale=" + scale;
// console.log("viewport content: " + content);
if (viewport_ele) {
viewport_ele.content = content;
} else {
var meta_ele = document.createElement("meta");
var name_att = document.createAttribute("name");
name_att.value = "viewport";
var content_att = document.createAttribute("content");
content_att.value = content;
meta_ele.setAttributeNode(name_att);
meta_ele.setAttributeNode(content_att);
document.head.appendChild(meta_ele);
}
};
window.Kaktus_ThemeObject.prototype.apply = function() {
var scale = this.getPixelRatio();
var pageMargin = Math.floor(this.pageMargin / (scale * this.zoom));
var pageMarginBottom = Math.floor(this.pageMarginBottom / (scale * this.zoom));
var fontSize = Math.floor(this.fontSize / scale);
var fontSizeTitle = Math.floor(this.fontSizeTitle / scale);
//var maxWidth = Math.floor(window.screen.width / (scale * this.zoom));
var css = "";
if (this.theme === "dark") {
css = "* { font-family: \"" + this.fontFamily + "\";\n" +
"background-color: " + this.highlightDimmerColor + " !important;\n" +
"color: " + this.primaryColor + " !important;\n }\n\n";
css += "select { color: " + this.highlightDimmerColor + " !important; }\n";
css += "a { color: " + this.highlightColor + " !important; }\n";
} else if (this.theme === "light") {
css = "* { font-family: \"" + this.fontFamily + "\";\n" +
"background-color: " + this.secondaryColor + " !important;\n" +
"color: " + this.highlightDimmerColor + " !important;\n }\n\n";
css += "select { color: " + this.highlightColorDark + " !important; }\n";
css += "a { color: " + this.highlightColorDark + " !important; }\n";
}
//css += "body { max-width: " + maxWidth + "px; \n" +
css += "body { " +
"margin: 0; \n" +
"padding: " + pageMargin + "px " + pageMargin + "px " + pageMarginBottom + "px " + pageMargin + "px; \n" +
"font-size: " + fontSize + "px; }\n";
css += "img { max-width: 100% !important; max-height:device-height !important; }\n";
css += "buttom, input, form { max-width: 100% !important; max-height:device-height !important; }\n";
css += "a, h1, h2, h3, div, p, pre, code { word-wrap: break-word; }\n";
css += "h1, h1, h3 { font-family: \"" + this.fontFamilyHeading + "\"; }\n";
//css += "#_kaktus_img { margin-bottom: 10px; }\n";
css += "#_kaktus_title { font-size: " + fontSizeTitle + "px; font-weight: bold; }";
//css += "figure { margin: 0; padding: 0; }";
//console.log(css);
var style_ele = document.getElementById("_kaktus_style");
if (style_ele) {
style_ele.innerHTML = css;
} else {
style_ele = document.createElement("style");
style_ele.id = "_kaktus_style";
style_ele.type = "text/css";
style_ele.appendChild(document.createTextNode(css));
document.head.appendChild(style_ele);
}
this.updateScale();
}
window.Kaktus_Theme = new window.Kaktus_ThemeObject();

View file

@ -1,387 +0,0 @@
/**
* Copyright (c) 2013, Kläralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* - Neither the name of Kläralvdalens Datakonsult AB nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "abstractitemmodel.hpp"
class AbstractItemModel::Private
{
public:
Private(AbstractItemModel *qq)
: q(qq)
, m_sourceModel(0)
{
}
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight);
void headerDataChanged(Qt::Orientation orientation, int first, int last);
void layoutAboutToBeChanged();
void layoutChanged();
void rowsAboutToBeInserted(const QModelIndex &parent, int first, int last);
void rowsInserted(const QModelIndex &parent, int first, int last);
void rowsAboutToBeRemoved(const QModelIndex &parent, int first, int last);
void rowsRemoved(const QModelIndex &parent, int first, int last);
void columnsAboutToBeInserted(const QModelIndex &parent, int first, int last);
void columnsInserted(const QModelIndex &parent, int first, int last);
void columnsAboutToBeRemoved(const QModelIndex &parent, int first, int last);
void columnsRemoved(const QModelIndex &parent, int first, int last);
void modelAboutToBeReset();
void modelReset();
void rowsAboutToBeMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destinationParent, int destinationRow);
void rowsMoved(const QModelIndex &parent, int start, int end, const QModelIndex &destination, int row );
void columnsAboutToBeMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destinationParent, int destinationColumn);
void columnsMoved(const QModelIndex &parent, int start, int end, const QModelIndex &destination, int column );
QModelIndex indexForPath(const QVariantList &indexPath) const;
QVariantList pathForIndex(const QModelIndex &index) const;
AbstractItemModel *q;
QPointer<QAbstractItemModel> m_sourceModel;
QString m_itemTypeRole;
QHash<int, QByteArray> m_roleNames;
QHash<QByteArray, int> m_reverseRoleNames;
};
void AbstractItemModel::Private::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
{
// we support only one column at the moment
const int column = topLeft.column();
const QModelIndex parentIndex = topLeft.parent();
for (int row = topLeft.row(); row <= bottomRight.row(); ++row) {
const QModelIndex index = m_sourceModel->index(row, column, parentIndex);
const QVariantList indexPath = pathForIndex(index);
emit q->itemUpdated(indexPath);
}
}
void AbstractItemModel::Private::headerDataChanged(Qt::Orientation orientation, int first, int last)
{
// nothing to do at the moment
Q_UNUSED(orientation)
Q_UNUSED(first)
Q_UNUSED(last)
}
void AbstractItemModel::Private::layoutAboutToBeChanged()
{
// nothing to do at the moment
}
void AbstractItemModel::Private::layoutChanged()
{
emit q->itemsChanged(bb::cascades::DataModelChangeType::Init);
}
void AbstractItemModel::Private::rowsAboutToBeInserted(const QModelIndex&, int, int)
{
// nothing to do at the moment
}
void AbstractItemModel::Private::rowsInserted(const QModelIndex &parent, int first, int last)
{
const QVariantList parentPath = pathForIndex(parent);
for (int pos = first; pos <= last; pos++) {
QVariantList indexPath = parentPath;
emit q->itemAdded(indexPath << pos);
}
emit q->countChanged();
}
void AbstractItemModel::Private::rowsAboutToBeRemoved(const QModelIndex&, int, int)
{
// nothing to do at the moment
}
void AbstractItemModel::Private::rowsRemoved(const QModelIndex &parent, int first, int last)
{
const QVariantList parentPath = pathForIndex(parent);
for (int pos = first; pos <= last; pos++) {
QVariantList indexPath = parentPath;
emit q->itemRemoved(indexPath << pos);
}
emit q->countChanged();
}
void AbstractItemModel::Private::columnsAboutToBeInserted(const QModelIndex&, int, int)
{
// nothing to do at the moment
}
void AbstractItemModel::Private::columnsInserted(const QModelIndex &parent, int first, int last)
{
//TODO: support column selection?
Q_UNUSED(parent)
Q_UNUSED(first)
Q_UNUSED(last)
}
void AbstractItemModel::Private::columnsAboutToBeRemoved(const QModelIndex&, int, int)
{
// nothing to do at the moment
}
void AbstractItemModel::Private::columnsRemoved(const QModelIndex &parent, int first, int last)
{
//TODO: support column selection?
Q_UNUSED(parent)
Q_UNUSED(first)
Q_UNUSED(last)
}
void AbstractItemModel::Private::modelAboutToBeReset()
{
// nothing to do at the moment
}
void AbstractItemModel::Private::modelReset()
{
emit q->itemsChanged(bb::cascades::DataModelChangeType::Init);
}
void AbstractItemModel::Private::rowsAboutToBeMoved(const QModelIndex&, int, int, const QModelIndex&, int)
{
// nothing to do at the moment
}
void AbstractItemModel::Private::rowsMoved(const QModelIndex&, int, int, const QModelIndex&, int)
{
emit q->itemsChanged(bb::cascades::DataModelChangeType::Init);
}
void AbstractItemModel::Private::columnsAboutToBeMoved(const QModelIndex&, int, int, const QModelIndex&, int)
{
// nothing to do at the moment
}
void AbstractItemModel::Private::columnsMoved(const QModelIndex&, int, int, const QModelIndex&, int)
{
emit q->itemsChanged(bb::cascades::DataModelChangeType::Init);
}
QModelIndex AbstractItemModel::Private::indexForPath(const QVariantList &indexPath) const
{
Q_ASSERT(m_sourceModel);
QModelIndex index;
QModelIndex parentIndex;
for (int i = 0; i < indexPath.count(); ++i) {
index = m_sourceModel->index(indexPath[i].toInt(), 0, parentIndex);
parentIndex = index;
}
return index;
}
QVariantList AbstractItemModel::Private::pathForIndex(const QModelIndex &index) const
{
QVariantList indexPath;
QModelIndex currentIndex = index;
while (currentIndex.isValid()) {
indexPath.prepend(currentIndex.row());
currentIndex = currentIndex.parent();
}
return indexPath;
}
AbstractItemModel::AbstractItemModel(QObject *parent)
: bb::cascades::DataModel(parent)
, d(new Private(this))
{
}
AbstractItemModel::~AbstractItemModel()
{
delete d;
}
QAbstractItemModel* AbstractItemModel::sourceModel() const
{
return d->m_sourceModel;
}
void AbstractItemModel::resetSourceModel()
{
//emit itemsChanged(bb::cascades::DataModelChangeType::Init);
setSourceModel(d->m_sourceModel);
}
void AbstractItemModel::setSourceModel(QAbstractItemModel *sourceModel)
{
//if (d->m_sourceModel == sourceModel)
// return;
if (d->m_sourceModel) {
disconnect(d->m_sourceModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(dataChanged(QModelIndex,QModelIndex)));
disconnect(d->m_sourceModel, SIGNAL(headerDataChanged(Qt::Orientation,int,int)), this, SLOT(headerDataChanged(Qt::Orientation,int,int)));
disconnect(d->m_sourceModel, SIGNAL(layoutAboutToBeChanged()), this, SLOT(layoutAboutToBeChanged()));
disconnect(d->m_sourceModel, SIGNAL(layoutChanged()), this, SLOT(layoutChanged()));
disconnect(d->m_sourceModel, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), this, SLOT(rowsAboutToBeInserted(QModelIndex,int,int)));
disconnect(d->m_sourceModel, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(rowsInserted(QModelIndex,int,int)));
disconnect(d->m_sourceModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), this, SLOT(rowsAboutToBeRemoved(QModelIndex,int,int)));
disconnect(d->m_sourceModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(rowsRemoved(QModelIndex,int,int)));
disconnect(d->m_sourceModel, SIGNAL(columnsAboutToBeInserted(QModelIndex,int,int)), this, SLOT(columnsAboutToBeInserted(QModelIndex,int,int)));
disconnect(d->m_sourceModel, SIGNAL(columnsInserted(QModelIndex,int,int)), this, SLOT(columnsInserted(QModelIndex,int,int)));
disconnect(d->m_sourceModel, SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int)), this, SLOT(columnsAboutToBeRemoved(QModelIndex,int,int)));
disconnect(d->m_sourceModel, SIGNAL(columnsRemoved(QModelIndex,int,int)), this, SLOT(columnsRemoved(QModelIndex,int,int)));
disconnect(d->m_sourceModel, SIGNAL(modelAboutToBeReset()), this, SLOT(modelAboutToBeReset()));
disconnect(d->m_sourceModel, SIGNAL(modelReset()), this, SLOT(modelReset()));
disconnect(d->m_sourceModel, SIGNAL(rowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)), this, SLOT(rowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)));
disconnect(d->m_sourceModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), this, SLOT(rowsMoved(QModelIndex,int,int,QModelIndex,int)));
disconnect(d->m_sourceModel, SIGNAL(columnsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)), this, SLOT(columnsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)));
disconnect(d->m_sourceModel, SIGNAL(columnsMoved(QModelIndex,int,int,QModelIndex,int)), this, SLOT(columnsMoved(QModelIndex,int,int,QModelIndex,int)));
}
d->m_sourceModel = sourceModel;
if (d->m_sourceModel) {
connect(d->m_sourceModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(dataChanged(QModelIndex,QModelIndex)));
connect(d->m_sourceModel, SIGNAL(headerDataChanged(Qt::Orientation,int,int)), this, SLOT(headerDataChanged(Qt::Orientation,int,int)));
connect(d->m_sourceModel, SIGNAL(layoutAboutToBeChanged()), this, SLOT(layoutAboutToBeChanged()));
connect(d->m_sourceModel, SIGNAL(layoutChanged()), this, SLOT(layoutChanged()));
connect(d->m_sourceModel, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), this, SLOT(rowsAboutToBeInserted(QModelIndex,int,int)));
connect(d->m_sourceModel, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(rowsInserted(QModelIndex,int,int)));
connect(d->m_sourceModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), this, SLOT(rowsAboutToBeRemoved(QModelIndex,int,int)));
connect(d->m_sourceModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(rowsRemoved(QModelIndex,int,int)));
connect(d->m_sourceModel, SIGNAL(columnsAboutToBeInserted(QModelIndex,int,int)), this, SLOT(columnsAboutToBeInserted(QModelIndex,int,int)));
connect(d->m_sourceModel, SIGNAL(columnsInserted(QModelIndex,int,int)), this, SLOT(columnsInserted(QModelIndex,int,int)));
connect(d->m_sourceModel, SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int)), this, SLOT(columnsAboutToBeRemoved(QModelIndex,int,int)));
connect(d->m_sourceModel, SIGNAL(columnsRemoved(QModelIndex,int,int)), this, SLOT(columnsRemoved(QModelIndex,int,int)));
connect(d->m_sourceModel, SIGNAL(modelAboutToBeReset()), this, SLOT(modelAboutToBeReset()));
connect(d->m_sourceModel, SIGNAL(modelReset()), this, SLOT(modelReset()));
connect(d->m_sourceModel, SIGNAL(rowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)), this, SLOT(rowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)));
connect(d->m_sourceModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), this, SLOT(rowsMoved(QModelIndex,int,int,QModelIndex,int)));
connect(d->m_sourceModel, SIGNAL(columnsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)), this, SLOT(columnsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)));
connect(d->m_sourceModel, SIGNAL(columnsMoved(QModelIndex,int,int,QModelIndex,int)), this, SLOT(columnsMoved(QModelIndex,int,int,QModelIndex,int)));
d->m_roleNames = d->m_sourceModel->roleNames();
// update reverse role names
d->m_reverseRoleNames.clear();
QHashIterator<int, QByteArray> it(d->m_roleNames);
while (it.hasNext()) {
it.next();
d->m_reverseRoleNames.insert(it.value(), it.key());
}
} else {
d->m_roleNames.clear();
d->m_reverseRoleNames.clear();
}
emit itemsChanged(bb::cascades::DataModelChangeType::Init);
emit sourceModelChanged();
}
QString AbstractItemModel::itemTypeRole() const
{
return d->m_itemTypeRole;
}
void AbstractItemModel::setItemTypeRole(const QString &roleName)
{
if (d->m_itemTypeRole == roleName)
return;
d->m_itemTypeRole = roleName;
emit itemTypeRoleChanged();
emit itemsChanged(bb::cascades::DataModelChangeType::Init);
}
int AbstractItemModel::childCount(const QVariantList &indexPath)
{
if (!d->m_sourceModel)
return 0;
return d->m_sourceModel->rowCount(d->indexForPath(indexPath));
}
bool AbstractItemModel::hasChildren(const QVariantList &indexPath)
{
if (!d->m_sourceModel)
return false;
return d->m_sourceModel->hasChildren(d->indexForPath(indexPath));
}
QString AbstractItemModel::itemType(const QVariantList &indexPath)
{
if (!d->m_sourceModel)
return QString();
if (d->m_itemTypeRole.isEmpty())
return QString();
return d->m_sourceModel->data(d->indexForPath(indexPath),
d->m_reverseRoleNames.value(d->m_itemTypeRole.toUtf8())).toString();
}
QVariant AbstractItemModel::data(const QVariantList &indexPath)
{
if (!d->m_sourceModel)
return QVariant();
QVariantMap result;
const QModelIndex index = d->indexForPath(indexPath);
QHashIterator<int, QByteArray> it(d->m_roleNames);
while (it.hasNext()) {
it.next();
result[it.value()] = index.data(it.key());
}
return result;
}
void AbstractItemModel::fetchMore(const QVariantList &indexPath)
{
if (!d->m_sourceModel)
return;
const QModelIndex index = d->indexForPath(indexPath);
if (d->m_sourceModel->canFetchMore(index))
d->m_sourceModel->fetchMore(index);
}
// Only for QAbstractListModel
int AbstractItemModel::readCount()
{
if (!d->m_sourceModel)
return 0;
return d->m_sourceModel->rowCount();
}
#include "moc_abstractitemmodel.cpp"

View file

@ -1,93 +0,0 @@
/**
* Copyright (c) 2013, Kläralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* - Neither the name of Kläralvdalens Datakonsult AB nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef ABSTRACTITEMMODEL_HPP
#define ABSTRACTITEMMODEL_HPP
#include <bb/cascades/DataModel>
#include <QAbstractItemModel>
class AbstractItemModel : public bb::cascades::DataModel
{
Q_OBJECT
Q_PROPERTY(QAbstractItemModel *sourceModel READ sourceModel WRITE setSourceModel NOTIFY sourceModelChanged)
Q_PROPERTY(QString itemTypeRole READ itemTypeRole WRITE setItemTypeRole NOTIFY itemTypeRoleChanged)
Q_PROPERTY(int count READ readCount NOTIFY countChanged)
public:
AbstractItemModel(QObject *parent = 0);
~AbstractItemModel();
QAbstractItemModel* sourceModel() const;
void setSourceModel(QAbstractItemModel *sourceModel);
Q_INVOKABLE void resetSourceModel();
QString itemTypeRole() const;
void setItemTypeRole(const QString &roleName);
int childCount(const QVariantList &indexPath);
bool hasChildren(const QVariantList &indexPath);
QString itemType(const QVariantList &indexPath);
QVariant data(const QVariantList &indexPath);
int readCount();
public Q_SLOTS:
void fetchMore(const QVariantList &indexPath);
Q_SIGNALS:
void sourceModelChanged();
void itemTypeRoleChanged();
void countChanged();
private:
class Private;
Private* const d;
Q_PRIVATE_SLOT(d, void dataChanged(const QModelIndex&, const QModelIndex&))
Q_PRIVATE_SLOT(d, void headerDataChanged(Qt::Orientation, int, int))
Q_PRIVATE_SLOT(d, void layoutAboutToBeChanged())
Q_PRIVATE_SLOT(d, void layoutChanged())
Q_PRIVATE_SLOT(d, void rowsAboutToBeInserted(const QModelIndex&, int, int))
Q_PRIVATE_SLOT(d, void rowsInserted(const QModelIndex&, int, int))
Q_PRIVATE_SLOT(d, void rowsAboutToBeRemoved(const QModelIndex&, int, int))
Q_PRIVATE_SLOT(d, void rowsRemoved(const QModelIndex&, int, int))
Q_PRIVATE_SLOT(d, void columnsAboutToBeInserted(const QModelIndex&, int, int))
Q_PRIVATE_SLOT(d, void columnsInserted(const QModelIndex&, int, int))
Q_PRIVATE_SLOT(d, void columnsAboutToBeRemoved(const QModelIndex&, int, int))
Q_PRIVATE_SLOT(d, void columnsRemoved(const QModelIndex&, int, int))
Q_PRIVATE_SLOT(d, void modelAboutToBeReset())
Q_PRIVATE_SLOT(d, void modelReset())
Q_PRIVATE_SLOT(d, void rowsAboutToBeMoved(const QModelIndex&, int, int, const QModelIndex&, int))
Q_PRIVATE_SLOT(d, void rowsMoved(const QModelIndex&, int, int, const QModelIndex&, int))
Q_PRIVATE_SLOT(d, void columnsAboutToBeMoved(const QModelIndex&, int, int, const QModelIndex&, int))
Q_PRIVATE_SLOT(d, void columnsMoved(const QModelIndex&, int, int, const QModelIndex&, int))
};
#endif

View file

@ -1,114 +0,0 @@
/*
Copyright (C) 2014 Michal Kosciesza <michal@mkiol.net>
This file is part of Kaktus.
Kaktus is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Kaktus is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Kaktus. If not, see <http://www.gnu.org/licenses/>.
*/
#include <bb/cascades/AbstractPane>
#include <bb/cascades/Application>
#include <bb/cascades/QmlDocument>
#include <bb/device/DisplayInfo>
#include <Qt/qdeclarativedebug.h>
#include <QtGui/QFileSystemModel>
#include <QLocale>
#include <QTranslator>
#include <QTimer>
#include <QDebug>
#include "databasemanager.h"
#include "downloadmanager.h"
#include "cacheserver.h"
#include "utils.h"
#include "settings.h"
#include "networkaccessmanagerfactory.h"
#include "abstractitemmodel.hpp"
#include "webimageview.h"
using namespace bb::cascades;
#ifdef KAKTUS_LIGHT
static const char *VERSION = "2.3 (light edition)";
#else
static const char *VERSION = "2.3";
#endif
static const char *AUTHOR = "Michal Kosciesza <michal@mkiol.net>";
static const char *PAGE = "https://github.com/mkiol/kaktus";
static const char *APP_NAME = "Kaktus";
Q_DECL_EXPORT int main(int argc, char **argv)
{
qmlRegisterType<QAbstractItemModel>();
qmlRegisterType<QTimer>("net.mkiol.kaktus", 1, 0, "QTimer");
qmlRegisterType <AbstractItemModel> ("com.kdab.components", 1, 0, "AbstractItemModel");
qmlRegisterType <WebImageView> ("org.labsquare", 1, 0, "WebImageView");
qRegisterMetaType < DatabaseManager::CacheItem > ("CacheItem");
Application app(argc, argv);
app.setApplicationName(APP_NAME);
app.setApplicationVersion(VERSION);
Settings* settings = Settings::instance();
QTranslator translator;
#ifdef KAKTUS_LIGHT
const QString filename = QString::fromLatin1("kaktuslight_%1").arg(
settings->getLocale()=="" ? QLocale().name() : settings->getLocale());
#else
const QString filename = QString::fromLatin1("kaktus_%1").arg(
settings->getLocale()=="" ? QLocale().name() : settings->getLocale());
#endif
if (translator.load(filename, "app/native/qm"))
app.installTranslator(&translator);
settings->qml = QmlDocument::create("asset:///main.qml");
settings->qml->documentContext()->setContextProperty("APP_NAME", APP_NAME);
settings->qml->documentContext()->setContextProperty("VERSION", VERSION);
settings->qml->documentContext()->setContextProperty("AUTHOR", AUTHOR);
settings->qml->documentContext()->setContextProperty("PAGE", PAGE);
NetworkAccessManagerFactory factory(settings->getDmUserAgent());
settings->qml->defaultDeclarativeEngine()->setNetworkAccessManagerFactory(&factory);
DatabaseManager db;
settings->db = &db;
DownloadManager dm;
settings->dm = &dm;
CacheServer cache(&db);
settings->cache = &cache;
Utils utils;
bb::device::DisplayInfo display;
QFileSystemModel *model = new QFileSystemModel(&app);
model->setRootPath("app/");
settings->qml->setContextProperty("db", &db);
settings->qml->setContextProperty("utils", &utils);
settings->qml->setContextProperty("dm", &dm);
settings->qml->setContextProperty("cache", &cache);
settings->qml->setContextProperty("settings", settings);
settings->qml->setContextProperty("_fileSystemModel", model);
settings->qml->setContextProperty("display", &display);
QObject::connect(settings->qml->defaultDeclarativeEngine(), SIGNAL(quit()),
QCoreApplication::instance(), SLOT(quit()));
AbstractPane *root = settings->qml->createRootObject<AbstractPane>();
Application::instance()->setScene(root);
return Application::exec();
}

View file

@ -215,8 +215,8 @@ void Settings::setWebviewNavigation(int value)
int Settings::getWebviewNavigation()
{
// Default is 1 - open in external browser
return settings.value("webviewnavigation", 1).toInt();
// Default is 0 - open in web view
return settings.value("webviewnavigation", 0).toInt();
}
void Settings::setShowTabIcons(bool value)
@ -630,31 +630,30 @@ QString Settings::getDmUserAgent()
return settings.value("useragent", value).toString();
}
QString Settings::getOfflineTheme()
QString Settings::getReaderTheme()
{
return settings.value("theme", "black").toString();
QString theme = settings.value("theme", "dark").toString();
return theme != "light" ? "dark" : "light";
}
void Settings::setOfflineTheme(const QString &value)
void Settings::setReaderTheme(const QString &value)
{
if (getOfflineTheme() != value) {
if (getReaderTheme() != value) {
settings.setValue("theme", value);
emit offlineThemeChanged();
emit readerThemeChanged();
}
}
int Settings::getFontSize()
{
int size = settings.value("fontsize", 15).toInt();
if (size < 10)
size = 15;
return size < 10 ? 15 : size;
int size = settings.value("fontsize", 10).toInt();
return size < 5 ? 5 : size > 50 ? 50 : size;
}
void Settings::setFontSize(int value)
{
// Min value is 10 & max value is 30
if (value > 30 || value < 10)
// Min value is 5 & max value is 50
if (value > 50 || value < 5)
return;
if (getFontSize() != value) {
@ -663,6 +662,27 @@ void Settings::setFontSize(int value)
}
}
float Settings::getZoom()
{
float size = settings.value("zoom", 1.0).toFloat();
//size = static_cast<float>(static_cast<int>(size*100+0.5))/100.0;
return size < 0.5 ? 0.5 : size > 2.0 ? 2.0 : size;
}
void Settings::setZoom(float value)
{
// Min value is 0.5 & max value is 2.0
if (value < 0.5 || value > 2.0)
return;
//value = static_cast<float>(static_cast<int>(value*100+0.5))/100.0;
if (getZoom() != value) {
settings.setValue("zoom", value);
emit zoomChanged();
}
}
int Settings::getRetentionDays()
{
// Default is 14 days

View file

@ -62,7 +62,8 @@ class Settings: public QObject
Q_PROPERTY (bool reinitDB READ getReinitDB WRITE setReinitDB)
Q_PROPERTY (QString locale READ getLocale WRITE setLocale NOTIFY localeChanged)
Q_PROPERTY (int fontSize READ getFontSize WRITE setFontSize NOTIFY fontSizeChanged)
Q_PROPERTY (QString offlineTheme READ getOfflineTheme WRITE setOfflineTheme NOTIFY offlineThemeChanged)
Q_PROPERTY (float zoom READ getZoom WRITE setZoom NOTIFY zoomChanged)
Q_PROPERTY (QString readerTheme READ getReaderTheme WRITE setReaderTheme NOTIFY readerThemeChanged)
Q_PROPERTY (bool autoDownloadOnUpdate READ getAutoDownloadOnUpdate WRITE setAutoDownloadOnUpdate NOTIFY autoDownloadOnUpdateChanged)
Q_PROPERTY (int cachingMode READ getCachingMode WRITE setCachingMode NOTIFY cachingModeChanged)
Q_PROPERTY (int theme READ getTheme WRITE setTheme NOTIFY themeChanged)
@ -152,8 +153,11 @@ public:
void setFontSize(int value);
int getFontSize();
void setOfflineTheme(const QString &value);
QString getOfflineTheme();
void setZoom(float value);
float getZoom();
void setReaderTheme(const QString &value);
QString getReaderTheme();
void setAutoDownloadOnUpdate(bool value);
bool getAutoDownloadOnUpdate();
@ -257,8 +261,9 @@ signals:
void viewModeChanged();
void helpDoneChanged();
void localeChanged();
void offlineThemeChanged();
void readerThemeChanged();
void fontSizeChanged();
void zoomChanged();
void autoDownloadOnUpdateChanged();
void themeChanged();
void cachingModeChanged();

View file

@ -1,96 +0,0 @@
// Based on: https://github.com/RileyGB/BlackBerry10-Samples
#include "standardweblistitem.h"
#include <QNetworkReply>
#include <QNetworkDiskCache>
#include <QtGui/QDesktopServices>
#include <bb/cascades/Image>
using namespace bb::cascades;
QNetworkAccessManager * StandardWebListItem::nm = new QNetworkAccessManager();
QNetworkDiskCache * StandardWebListItem::nc = new QNetworkDiskCache();
StandardWebListItem::StandardWebListItem()
{
nc->setCacheDirectory(QDesktopServices::storageLocation(QDesktopServices::CacheLocation));
nm->setCache(nc);
loading = 0;
}
StandardWebListItem::~StandardWebListItem(){}
const QUrl& StandardWebListItem::getUrl() const
{
return url;
}
void StandardWebListItem::setUrl(const QUrl& url)
{
this->url = url;
loading = 0;
resetImage();
QNetworkRequest request;
request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
request.setUrl(url);
QNetworkReply * reply = nm->get(request);
QObject::connect(reply, SIGNAL(finished()), this, SLOT(imageLoaded()));
QObject::connect(reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(dowloadProgressed(qint64,qint64)));
emit urlChanged();
}
double StandardWebListItem::getLoading() const
{
return loading;
}
void StandardWebListItem::imageLoaded()
{
QNetworkReply * reply = qobject_cast<QNetworkReply*>(sender());
if (reply->error() == QNetworkReply::NoError) {
if (isARedirectedUrl(reply)) {
setURLToRedirectedUrl(reply);
return;
} else {
QByteArray imageData = reply->readAll();
setImage(Image(imageData));
}
}
reply->deleteLater();
}
bool StandardWebListItem::isARedirectedUrl(QNetworkReply *reply)
{
QUrl redirection = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
return !redirection.isEmpty();
}
void StandardWebListItem::setURLToRedirectedUrl(QNetworkReply *reply)
{
QUrl redirectionUrl = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
QUrl baseUrl = reply->url();
QUrl resolvedUrl = baseUrl.resolved(redirectionUrl);
setUrl(resolvedUrl.toString());
}
void StandardWebListItem::clearCache()
{
nc->clear();
}
void StandardWebListItem::dowloadProgressed(qint64 bytes, qint64 total) {
loading = double(bytes) / double(total);
emit loadingChanged();
}

View file

@ -1,47 +0,0 @@
// Based on: https://github.com/RileyGB/BlackBerry10-Samples
#ifndef STANDARDWEBLISTITEM_H_
#define STANDARDWEBLISTITEM_H_
#include <bb/cascades/StandardListItem>
#include <QNetworkAccessManager>
#include <QNetworkDiskCache>
#include <QUrl>
using namespace bb::cascades;
class StandardWebListItem: public bb::cascades::StandardListItem
{
Q_OBJECT
Q_PROPERTY (QUrl url READ getUrl WRITE setUrl NOTIFY urlChanged)
Q_PROPERTY (float loading READ getLoading NOTIFY loadingChanged)
public:
StandardWebListItem();
virtual ~StandardWebListItem();
const QUrl& getUrl() const;
double getLoading() const;
public Q_SLOTS:
void setUrl(const QUrl& url);
void clearCache();
private Q_SLOTS:
void imageLoaded();
void dowloadProgressed(qint64,qint64);
signals:
void urlChanged();
void loadingChanged();
private:
static QNetworkAccessManager * nm;
static QNetworkDiskCache * nc;
QUrl url;
float loading;
bool isARedirectedUrl(QNetworkReply *reply);
void setURLToRedirectedUrl(QNetworkReply *reply);
};
#endif /* STANDARDWEBLISTITEM_H_ */

View file

@ -144,22 +144,13 @@ QString Utils::formatHtml(const QString & data, bool offline, const QString & st
QRegExp rxA("<a[^>]*></a>", Qt::CaseInsensitive);
QRegExp rxP("<p[^>]*></p>", Qt::CaseInsensitive);
/*QTextDocument doc;
doc.setDefaultStyleSheet("body{background-color:#00000;color:#fffff} h1{margin:0;padding:0} p{margin:0;padding:0} a{color:#0092CC}");
doc.setHtml(data);
QString content = doc.toPlainText().replace(QChar::ObjectReplacementCharacter,QChar(0x0020)).trimmed();
content.replace(rxHeadEnd,"<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">");
QRegExp rxEnter("\n\n", Qt::CaseInsensitive);
content = doc.toHtml();
content.remove(rxEnter);*/
QTextDocument doc; doc.setHtml(data);
if (doc.toPlainText().replace(QChar::ObjectReplacementCharacter,QChar(0x0020)).trimmed().isEmpty())
return "";
QString content = data;
if (offline) {
content.remove(rxImg);
content.remove(rxImg); content.remove("</img>", Qt::CaseInsensitive);
content.remove(rxA);
content.remove(rxP);
} else {

View file

@ -1,326 +0,0 @@
// Based on: https://github.com/RileyGB/BlackBerry10-Samples
#include "webimageview.h"
#include <QNetworkReply>
#include <QNetworkDiskCache>
#include <QtGui/QDesktopServices>
#include <QStringList>
#include <bb/cascades/Image>
#include "cacheserver.h"
using namespace bb::cascades;
QNetworkAccessManager * WebImageView::mNetManager = new QNetworkAccessManager();
QNetworkDiskCache * WebImageView::mNetworkDiskCache = new QNetworkDiskCache();
const QString WebImageView::availableColors[5] = {"green", "blue", "orange", "pink", "grey"};
const QString WebImageView::spriteMap[5][10] = {
{"plus","home","label-2","star","label","pin","sheet","power","diamond","folder"},
{"enveloppe","happy-face","rss","calc","clock","pen","bug","label-box","yen","snail"},
{"cloud","cog","vbar","pie","table","line","magnifier","potion","pound","euro"},
{"lightbulb","movie","note","camera","mobile","computer","heart","bubbles","dollars"},
{"alert","bill","funnel","eye","bubble","calendar","check","crown","plane"}
};
WebImageView::WebImageView() {
// Initialize network cache
mNetworkDiskCache->setCacheDirectory(QDesktopServices::storageLocation(QDesktopServices::CacheLocation));
// Set cache in manager
mNetManager->setCache(mNetworkDiskCache);
// Set defaults
mLoading = 0;
mIsLoaded = false;
doSizeCheck = false;
}
const QUrl& WebImageView::url() const {
return mUrl;
}
bb::ImageData WebImageView::fromQImage(const QImage &qImage)
{
bb::ImageData imageData(bb::PixelFormat::RGBA_Premultiplied, qImage.width(), qImage.height());
unsigned char *dstLine = imageData.pixels();
for (int y = 0; y < imageData.height(); y++) {
unsigned char * dst = dstLine;
for (int x = 0; x < imageData.width(); x++) {
QRgb srcPixel = qImage.pixel(x, y);
*dst++ = qRed(srcPixel) * qAlpha(srcPixel) / 255;
*dst++ = qGreen(srcPixel) * qAlpha(srcPixel) / 255;
*dst++ = qBlue(srcPixel) * qAlpha(srcPixel) / 255;
*dst++ = qAlpha(srcPixel);
}
dstLine += imageData.bytesPerLine();
}
return imageData;
}
int WebImageView::getWidth() const
{
return sourceWidth;
}
/*int WebImageView::getHeight() const
{
return sourceHeight;
}
int WebImageView::getSize() const
{
return sourceSize;
}*/
bool WebImageView::getDoSizeCheck()
{
return doSizeCheck;
}
void WebImageView::setDoSizeCheck(bool value)
{
if (doSizeCheck != value) {
doSizeCheck = value;
emit doSizeCheckChanged();
}
}
void WebImageView::setUrl(const QUrl& url)
{
//qDebug() << "url" << url << "mUrl" << mUrl << (url==mUrl);
if (url == mUrl) {
return;
}
mLoading = 0;
mIsLoaded = false;
mUrl = url;
mLoading = 0;
mIsLoaded = false;
emit isLoadedChanged();
resetImage();
if (url.isEmpty()) {
emit urlChanged();
return;
}
// Detecting if url is "asset:///"
if (url.toString().startsWith("asset:///")) {
this->setImageSource(url);
mIsLoaded = true;
emit isLoadedChanged();
emit urlChanged();
return;
}
// Detecting if url is "image://nvicons/"
if (url.toString().startsWith("image://nvicons/")) {
QStringList parts = url.toString().split('?');
QString color = parts.at(1);
parts = parts.at(0).split('/');
QString icon = parts.at(3);
setImage(Image(fromQImage(QImage("app/native/assets/sprite-icons.png").copy(getPosition(icon, color)))));
mIsLoaded = true;
emit isLoadedChanged();
emit urlChanged();
return;
}
// Detecting if url is "cache://"
if (url.toString().startsWith("cache://")) {
QStringList parts = url.toString().split('/');
QString filename = parts.at(2);
Settings *s = Settings::instance();
DatabaseManager::CacheItem item = s->db->readCacheByEntry(filename);
if (item.id == "") {
item = s->db->readCacheByFinalUrl(filename);
} else {
filename = item.finalUrl;
}
filename = s->getDmCacheDir() + "/" + filename;
if (!QFile::exists(filename)) {
emit urlChanged();
return;
}
if (item.contentType == "image/x-icon") {
// BB does not support ICO image format -> must convert
QFile file(filename);
if (!file.open(QIODevice::ReadOnly)) {
qWarning() << "Could not open" << filename << "for reading: " << file.errorString();
file.close();
emit urlChanged();
return;
}
QByteArray data;
data.append(file.readAll());
file.close();
setImage(Image(fromQImage(QImage::fromData(data))));
} else {
setImage(Image(QUrl(filename)));
}
mIsLoaded = true;
emit isLoadedChanged();
emit urlChanged();
return;
}
// Create request
QNetworkRequest request;
request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
request.setUrl(url);
// Create reply
QNetworkReply * reply = mNetManager->get(request);
// Connect to signals
QObject::connect(reply, SIGNAL(finished()), this, SLOT(imageLoaded()));
QObject::connect(reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(dowloadProgressed(qint64,qint64)));
QObject::connect(reply, SIGNAL(metaDataChanged()), this, SLOT(metaDataChanged()));
emit urlChanged();
}
void WebImageView::metaDataChanged()
{
QNetworkReply * reply = qobject_cast<QNetworkReply*>(sender());
//qDebug() << "metaDataChanged:" << reply->url();
// Memory protection fix -> not loading big images
if (reply->header(QNetworkRequest::ContentLengthHeader).isValid()) {
int length = reply->header(QNetworkRequest::ContentLengthHeader).toInt();
if (length > maxSourceSize) {
//qDebug() << "metaDataChanged, length=" << length;
reply->close();
return;
}
}
}
double WebImageView::loading() const
{
return mLoading;
}
void WebImageView::imageLoaded() {
// Get reply
QNetworkReply * reply = qobject_cast<QNetworkReply*>(sender());
//qDebug() << "error" << reply->error();
//qDebug() << "imageLoaded:" << reply->url() << reply->error();
if (reply->error() == QNetworkReply::NoError) {
if (isARedirectedUrl(reply)) {
setURLToRedirectedUrl(reply);
return;
} else {
QByteArray imageData = reply->readAll();
//qDebug() << "imageData.length" << imageData.length();
// Memory protection & Tiny image fix -> not loading big or tiny images
if (doSizeCheck &&
(imageData.length() > maxSourceSize ||
imageData.length() < minSourceSize)) {
mIsLoaded = false;
} else {
QImage img = QImage::fromData(imageData);
int width = img.width();
//int height = img.height();
//int size = imageData.length();
if (width != sourceWidth) {
sourceWidth = width;
emit widthChanged();
}
/*if (height != sourceHeight) {
sourceHeight = height;
emit heightChanged();
}
if (size != sourceSize) {
sourceSize = size;
emit sizeChanged();
}*/
//qDebug() << "ContentType:" << reply->header(QNetworkRequest::ContentTypeHeader).toString();
if (reply->header(QNetworkRequest::ContentTypeHeader).toString() == "image/x-icon") {
// BB does not support ICO image format -> must convert
setImage(Image(fromQImage(QImage::fromData(imageData))));
} else {
setImage(Image(imageData));
}
mIsLoaded = true;
}
}
} else {
mIsLoaded = false;
mUrl.clear();
}
emit isLoadedChanged();
// Memory management
reply->deleteLater();
}
bool WebImageView::isARedirectedUrl(QNetworkReply *reply) {
QUrl redirection = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
return !redirection.isEmpty();
}
bool WebImageView::isLoaded() const {
return mIsLoaded;
}
void WebImageView::setURLToRedirectedUrl(QNetworkReply *reply) {
QUrl redirectionUrl = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
QUrl baseUrl = reply->url();
QUrl resolvedUrl = baseUrl.resolved(redirectionUrl);
setUrl(resolvedUrl.toString());
}
void WebImageView::clearCache() {
mNetworkDiskCache->clear();
}
void WebImageView::dowloadProgressed(qint64 bytes, qint64 total) {
mLoading = double(bytes) / double(total);
emit loadingChanged();
}
QRect WebImageView::getPosition(const QString &icon, const QString &color)
{
int n = 16, s = 20, a = 16;
for (int i = 0; i < 5; ++i) {
for (int j = 0; j < 10; ++j) {
if (spriteMap[i][j] == icon) {
n += 100 * i;
a += j * s;
return QRect(n + getOffsetByColor(color), a, 16, 16);
}
}
}
qWarning() << "getPosition failed!";
return QRect(0,0,0,0);
}
int WebImageView::getOffsetByColor(const QString &color)
{
int index = 0;
for (int i = 0; i < 5; ++i) {
if (availableColors[i] == color) {
index = i;
break;
}
}
return index * 20;
}

View file

@ -1,83 +0,0 @@
// Based on: https://github.com/RileyGB/BlackBerry10-Samples
#ifndef WEBIMAGEVIEW_H_
#define WEBIMAGEVIEW_H_
#include <bb/cascades/ImageView>
#include <QNetworkAccessManager>
#include <QNetworkDiskCache>
#include <QUrl>
#include <QRect>
#include <bb/ImageData>
#include <QtGui/QImage>
using namespace bb::cascades;
class WebImageView: public bb::cascades::ImageView {
Q_OBJECT
Q_PROPERTY (QUrl url READ url WRITE setUrl NOTIFY urlChanged)
Q_PROPERTY (float loading READ loading NOTIFY loadingChanged)
Q_PROPERTY (bool isLoaded READ isLoaded NOTIFY isLoadedChanged)
Q_PROPERTY (int width READ getWidth NOTIFY widthChanged)
Q_PROPERTY (bool doSizeCheck READ getDoSizeCheck WRITE setDoSizeCheck NOTIFY doSizeCheckChanged)
//Q_PROPERTY (int height READ getHeight NOTIFY heightChanged)
//Q_PROPERTY (int size READ getSize NOTIFY sizeChanged)
public:
WebImageView();
const QUrl& url() const;
double loading() const;
bool isLoaded() const;
int getWidth() const;
//int getHeight() const;
//int getSize() const;
bool getDoSizeCheck();
public Q_SLOTS:
void setUrl(const QUrl& url);
void clearCache();
void setDoSizeCheck(bool value);
private Q_SLOTS:
void imageLoaded();
void dowloadProgressed(qint64,qint64);
void metaDataChanged();
signals:
void urlChanged();
void loadingChanged();
void isLoadedChanged();
void widthChanged();
void doSizeCheckChanged();
//void heightChanged();
//void sizeChanged();
private:
static QNetworkAccessManager * mNetManager;
static QNetworkDiskCache * mNetworkDiskCache;
static const int maxSourceSize = 500000; // Memory protection fix -> not loading big images
static const int minSourceSize = 2000; // Tiny images
QUrl mUrl;
float mLoading;
bool mIsLoaded;
bool doSizeCheck;
bool isARedirectedUrl(QNetworkReply *reply);
void setURLToRedirectedUrl(QNetworkReply *reply);
const static QString availableColors[5];
const static QString spriteMap[5][10];
bb::ImageData fromQImage(const QImage &qImage);
int getOffsetByColor(const QString &color);
QRect getPosition(const QString &icon, const QString &color);
int sourceWidth;
int sourceHeight;
int sourceSize;
};
#endif /* WEBIMAGEVIEW_H_ */