Reader View based on Readability.js (inspired and code partially borrowed from harbour-webpirate project (https://github.com/Dax89/harbour-webpirate)
4
sailfish/images/z1.0/.directory
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
[Dolphin]
|
||||
PreviewsShown=true
|
||||
Timestamp=2016,7,22,5,51,26
|
||||
Version=3
|
||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 868 B |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.1 KiB |
4
sailfish/images/z1.25/.directory
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
[Dolphin]
|
||||
PreviewsShown=true
|
||||
Timestamp=2016,7,23,11,59,38
|
||||
Version=3
|
||||
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 1.3 KiB |
4
sailfish/images/z1.5-large/.directory
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
[Dolphin]
|
||||
PreviewsShown=true
|
||||
Timestamp=2016,7,23,11,59,29
|
||||
Version=3
|
||||
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 924 B |
|
Before Width: | Height: | Size: 2 KiB After Width: | Height: | Size: 1.2 KiB |
4
sailfish/images/z1.5/.directory
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
[Dolphin]
|
||||
PreviewsShown=true
|
||||
Timestamp=2016,7,23,11,59,15
|
||||
Version=3
|
||||
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 1.5 KiB |
4
sailfish/images/z1.75/.directory
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
[Dolphin]
|
||||
PreviewsShown=true
|
||||
Timestamp=2016,7,23,11,59,54
|
||||
Version=3
|
||||
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 1.8 KiB |
4
sailfish/images/z2.0/.directory
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
[Dolphin]
|
||||
PreviewsShown=true
|
||||
Timestamp=2016,7,23,12,0,7
|
||||
Version=3
|
||||
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 2.1 KiB |
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
28
sailfish/qml/js/MessageListener.js
Normal 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();
|
||||
29
sailfish/qml/js/ObjectOverrider.js
Normal 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
|
||||
}
|
||||
1856
sailfish/qml/js/Readability.js
Normal file
126
sailfish/qml/js/ReaderModeHandler.js
Normal 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
|
|
@ -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();
|
||||
|
|
@ -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"
|
||||
|
|
@ -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
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
@ -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_ */
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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_ */
|
||||