├── platforms ├── __init__.py ├── harmattan.py ├── bb10.py ├── base_platform.py ├── pc.py └── maemo5_autorotation.py ├── gui ├── __init__.py ├── qml │ ├── i18n │ │ ├── qml_cs.qm │ │ ├── qml_de.qm │ │ ├── qml_el.qm │ │ ├── qml_en.qm │ │ ├── qml_es.qm │ │ ├── qml_fi.qm │ │ ├── qml_fr.qm │ │ ├── qml_nl.qm │ │ ├── qml_ru.qm │ │ ├── qml_vi.qm │ │ ├── qml_spa.qm │ │ ├── qml_zh_CN.qm │ │ ├── push_transifex.sh │ │ ├── .tx │ │ │ └── config │ │ ├── pull_transifex.sh │ │ ├── all_pull_transifex.sh │ │ └── update.sh │ ├── sailfish_specific │ │ ├── SailfishScreen.qml │ │ ├── SailfishKeepAlive.qml │ │ └── SailfishMediaKeys.qml │ ├── universal_components │ │ ├── silica │ │ │ └── UC │ │ │ │ ├── Label.qml │ │ │ │ ├── Screen.qml │ │ │ │ ├── Slider.qml │ │ │ │ ├── Switch.qml │ │ │ │ ├── Button.qml │ │ │ │ ├── Dialog.qml │ │ │ │ ├── TextArea.qml │ │ │ │ ├── Menu.qml │ │ │ │ ├── MenuItem.qml │ │ │ │ ├── ProgressBar.qml │ │ │ │ ├── TextField.qml │ │ │ │ ├── TextSwitch.qml │ │ │ │ ├── PlatformListView.qml │ │ │ │ ├── PlatformFlickable.qml │ │ │ │ ├── SearchField.qml │ │ │ │ ├── VerticalScrollDecorator.qml │ │ │ │ ├── Page.qml │ │ │ │ ├── PageStatus.js │ │ │ │ ├── IconButton.qml │ │ │ │ ├── PageHeader.qml │ │ │ │ ├── TopMenu.qml │ │ │ │ ├── BackgroundRectangle.qml │ │ │ │ ├── qmldir │ │ │ │ ├── ComboBox.qml │ │ │ │ ├── ApplicationWindow.qml │ │ │ │ └── Popup.qml │ │ ├── controls │ │ │ └── UC │ │ │ │ ├── Menu.qml │ │ │ │ ├── Dialog.qml │ │ │ │ ├── Label.qml │ │ │ │ ├── TextArea.qml │ │ │ │ ├── TextField.qml │ │ │ │ ├── ProgressBar.qml │ │ │ │ ├── PlatformFlickable.qml │ │ │ │ ├── PlatformListView.qml │ │ │ │ ├── Switch.qml │ │ │ │ ├── TopMenu.qml │ │ │ │ ├── SearchField.qml │ │ │ │ ├── Slider.qml │ │ │ │ ├── Screen.qml │ │ │ │ ├── Button.qml │ │ │ │ ├── PageStatus.js │ │ │ │ ├── IconButton.qml │ │ │ │ ├── MenuItem.qml │ │ │ │ ├── VerticalScrollDecorator.qml │ │ │ │ ├── Page.qml │ │ │ │ ├── BackgroundRectangle.qml │ │ │ │ ├── style.js │ │ │ │ ├── TextSwitch.qml │ │ │ │ ├── qmldir │ │ │ │ ├── ApplicationWindow.qml │ │ │ │ ├── PageHeader.qml │ │ │ │ ├── Popup.qml │ │ │ │ ├── ComboBox.qml │ │ │ │ └── menu.svg │ │ ├── sync_qmldir.sh │ │ ├── main_qmldir │ │ ├── glacier │ │ │ └── UC │ │ │ │ └── qmldir │ │ ├── LICENSE │ │ └── README.md │ ├── modrana_components │ │ ├── ThemedBackgroundRectangle.qml │ │ ├── PageHeaderBackground.qml │ │ ├── SmartGrid.qml │ │ ├── TIcon.qml │ │ ├── ContentColumn.qml │ │ ├── TextButton.qml │ │ ├── BasePage.qml │ │ ├── PayPalButton.qml │ │ ├── FlattrButton.qml │ │ ├── BitcoinButton.qml │ │ ├── BitcoinPage.qml │ │ ├── GratipayButton.qml │ │ ├── KeyTextSwitch.qml │ │ ├── PythonLog.qml │ │ ├── KeyComboBox.qml │ │ ├── MIconButton.qml │ │ ├── HeaderPage.qml │ │ ├── IconGridPage.qml │ │ ├── IconGridButton.qml │ │ └── SearchPage.qml │ ├── backend │ │ ├── DynamicTimer.qml │ │ ├── pdb.qml │ │ ├── OptProp.qml │ │ ├── Screen.qml │ │ ├── Platform.qml │ │ ├── Actions.qml │ │ ├── Cron.qml │ │ └── KeepAlive.qml │ ├── SwitchWithText.qml │ ├── LineText.qml │ ├── PayPalButton.qml │ ├── FlattrButton.qml │ ├── SelectorButtonWithText.qml │ ├── PageFitSelector.qml │ ├── DonationDialog.qml │ ├── FileSelectorWrapper.qml │ ├── HeaderDialog.qml │ ├── WhatsNewDialog.qml │ ├── HistoryPage.qml │ ├── main.qml │ ├── BitcoinButton.qml │ ├── OptionsPage.qml │ ├── FileSelector.qml │ └── InfoPage.qml ├── qml_page.py └── gui.py ├── .gitignore ├── icons ├── back.png ├── forward.png ├── mieru.png ├── switch.png ├── view-normal.png ├── mieru_150x150.png ├── qrcode_bitcoin.png ├── page_unreadable.png ├── view-fullscreen.png ├── mieru_no_background.svg └── mieru.svg ├── data └── desktop_files │ ├── mieru_harmattan.desktop │ └── mieru_fremantle.desktop ├── TODO.TXT ├── OLD_TODO.TXT ├── release_notes.txt ├── README.md └── providers └── progressive_download.py /platforms/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /gui/__init__.py: -------------------------------------------------------------------------------- 1 | from gui import getGui -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .idea 3 | providers/test 4 | -------------------------------------------------------------------------------- /icons/back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/M4rtinK/mieru/HEAD/icons/back.png -------------------------------------------------------------------------------- /icons/forward.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/M4rtinK/mieru/HEAD/icons/forward.png -------------------------------------------------------------------------------- /icons/mieru.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/M4rtinK/mieru/HEAD/icons/mieru.png -------------------------------------------------------------------------------- /icons/switch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/M4rtinK/mieru/HEAD/icons/switch.png -------------------------------------------------------------------------------- /gui/qml/i18n/qml_cs.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/M4rtinK/mieru/HEAD/gui/qml/i18n/qml_cs.qm -------------------------------------------------------------------------------- /gui/qml/i18n/qml_de.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/M4rtinK/mieru/HEAD/gui/qml/i18n/qml_de.qm -------------------------------------------------------------------------------- /gui/qml/i18n/qml_el.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/M4rtinK/mieru/HEAD/gui/qml/i18n/qml_el.qm -------------------------------------------------------------------------------- /gui/qml/i18n/qml_en.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/M4rtinK/mieru/HEAD/gui/qml/i18n/qml_en.qm -------------------------------------------------------------------------------- /gui/qml/i18n/qml_es.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/M4rtinK/mieru/HEAD/gui/qml/i18n/qml_es.qm -------------------------------------------------------------------------------- /gui/qml/i18n/qml_fi.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/M4rtinK/mieru/HEAD/gui/qml/i18n/qml_fi.qm -------------------------------------------------------------------------------- /gui/qml/i18n/qml_fr.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/M4rtinK/mieru/HEAD/gui/qml/i18n/qml_fr.qm -------------------------------------------------------------------------------- /gui/qml/i18n/qml_nl.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/M4rtinK/mieru/HEAD/gui/qml/i18n/qml_nl.qm -------------------------------------------------------------------------------- /gui/qml/i18n/qml_ru.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/M4rtinK/mieru/HEAD/gui/qml/i18n/qml_ru.qm -------------------------------------------------------------------------------- /gui/qml/i18n/qml_vi.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/M4rtinK/mieru/HEAD/gui/qml/i18n/qml_vi.qm -------------------------------------------------------------------------------- /gui/qml/sailfish_specific/SailfishScreen.qml: -------------------------------------------------------------------------------- 1 | import Sailfish.Media 1.0 2 | 3 | ScreenBlank {} -------------------------------------------------------------------------------- /gui/qml/universal_components/silica/UC/Label.qml: -------------------------------------------------------------------------------- 1 | import Sailfish.Silica 1.0 2 | Label { 3 | } -------------------------------------------------------------------------------- /gui/qml/universal_components/silica/UC/Screen.qml: -------------------------------------------------------------------------------- 1 | import Sailfish.Silica 1.0 2 | Screen{ 3 | } -------------------------------------------------------------------------------- /gui/qml/universal_components/silica/UC/Slider.qml: -------------------------------------------------------------------------------- 1 | import Sailfish.Silica 1.0 2 | Slider{ 3 | } -------------------------------------------------------------------------------- /gui/qml/universal_components/silica/UC/Switch.qml: -------------------------------------------------------------------------------- 1 | import Sailfish.Silica 1.0 2 | Switch{ 3 | } -------------------------------------------------------------------------------- /icons/view-normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/M4rtinK/mieru/HEAD/icons/view-normal.png -------------------------------------------------------------------------------- /gui/qml/i18n/qml_spa.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/M4rtinK/mieru/HEAD/gui/qml/i18n/qml_spa.qm -------------------------------------------------------------------------------- /gui/qml/universal_components/controls/UC/Menu.qml: -------------------------------------------------------------------------------- 1 | import QtQuick.Controls 1.0 2 | 3 | Menu{ 4 | } -------------------------------------------------------------------------------- /gui/qml/universal_components/silica/UC/Button.qml: -------------------------------------------------------------------------------- 1 | import Sailfish.Silica 1.0 2 | 3 | Button {} -------------------------------------------------------------------------------- /gui/qml/universal_components/silica/UC/Dialog.qml: -------------------------------------------------------------------------------- 1 | import Sailfish.Silica 1.0 2 | Dialog { 3 | } -------------------------------------------------------------------------------- /gui/qml/universal_components/silica/UC/TextArea.qml: -------------------------------------------------------------------------------- 1 | import Sailfish.Silica 1.0 2 | TextArea{ 3 | } -------------------------------------------------------------------------------- /icons/mieru_150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/M4rtinK/mieru/HEAD/icons/mieru_150x150.png -------------------------------------------------------------------------------- /icons/qrcode_bitcoin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/M4rtinK/mieru/HEAD/icons/qrcode_bitcoin.png -------------------------------------------------------------------------------- /gui/qml/i18n/qml_zh_CN.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/M4rtinK/mieru/HEAD/gui/qml/i18n/qml_zh_CN.qm -------------------------------------------------------------------------------- /gui/qml/universal_components/controls/UC/Dialog.qml: -------------------------------------------------------------------------------- 1 | import QtQuick.Controls 1.0 2 | 3 | Dialog { 4 | } -------------------------------------------------------------------------------- /gui/qml/universal_components/controls/UC/Label.qml: -------------------------------------------------------------------------------- 1 | import QtQuick.Controls 1.0 2 | 3 | Label { 4 | } -------------------------------------------------------------------------------- /gui/qml/universal_components/silica/UC/Menu.qml: -------------------------------------------------------------------------------- 1 | import Sailfish.Silica 1.0 2 | 3 | ContextMenu{ 4 | } -------------------------------------------------------------------------------- /gui/qml/universal_components/silica/UC/MenuItem.qml: -------------------------------------------------------------------------------- 1 | import Sailfish.Silica 1.0 2 | 3 | MenuItem{ 4 | } -------------------------------------------------------------------------------- /gui/qml/universal_components/silica/UC/ProgressBar.qml: -------------------------------------------------------------------------------- 1 | import Sailfish.Silica 1.0 2 | ProgressBar{ 3 | } -------------------------------------------------------------------------------- /gui/qml/universal_components/silica/UC/TextField.qml: -------------------------------------------------------------------------------- 1 | import Sailfish.Silica 1.0 2 | TextField{ 3 | } -------------------------------------------------------------------------------- /icons/page_unreadable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/M4rtinK/mieru/HEAD/icons/page_unreadable.png -------------------------------------------------------------------------------- /icons/view-fullscreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/M4rtinK/mieru/HEAD/icons/view-fullscreen.png -------------------------------------------------------------------------------- /gui/qml/i18n/push_transifex.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # push local changes to transifex 4 | tx push -s -t 5 | -------------------------------------------------------------------------------- /gui/qml/universal_components/controls/UC/TextArea.qml: -------------------------------------------------------------------------------- 1 | import QtQuick.Controls 1.0 2 | 3 | TextArea{ 4 | } -------------------------------------------------------------------------------- /gui/qml/universal_components/controls/UC/TextField.qml: -------------------------------------------------------------------------------- 1 | import QtQuick.Controls 1.0 2 | 3 | TextField{ 4 | } -------------------------------------------------------------------------------- /gui/qml/universal_components/silica/UC/TextSwitch.qml: -------------------------------------------------------------------------------- 1 | import Sailfish.Silica 1.0 2 | 3 | TextSwitch{ 4 | } -------------------------------------------------------------------------------- /gui/qml/universal_components/controls/UC/ProgressBar.qml: -------------------------------------------------------------------------------- 1 | import QtQuick.Controls 1.0 2 | 3 | ProgressBar{ 4 | } -------------------------------------------------------------------------------- /gui/qml/universal_components/silica/UC/PlatformListView.qml: -------------------------------------------------------------------------------- 1 | import Sailfish.Silica 1.0 2 | SilicaListView { 3 | } -------------------------------------------------------------------------------- /gui/qml/universal_components/controls/UC/PlatformFlickable.qml: -------------------------------------------------------------------------------- 1 | import QtQuick.Controls 1.0 2 | 3 | Flickable { 4 | } -------------------------------------------------------------------------------- /gui/qml/universal_components/controls/UC/PlatformListView.qml: -------------------------------------------------------------------------------- 1 | import QtQuick.Controls 1.0 2 | 3 | ListView { 4 | } -------------------------------------------------------------------------------- /gui/qml/universal_components/controls/UC/Switch.qml: -------------------------------------------------------------------------------- 1 | import QtQuick.Controls 1.1 // needs Qt 5.2 2 | 3 | Switch{ 4 | } -------------------------------------------------------------------------------- /gui/qml/universal_components/silica/UC/PlatformFlickable.qml: -------------------------------------------------------------------------------- 1 | import Sailfish.Silica 1.0 2 | SilicaFlickable { 3 | } -------------------------------------------------------------------------------- /gui/qml/universal_components/controls/UC/TopMenu.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 1.0 3 | 4 | Menu { 5 | } -------------------------------------------------------------------------------- /gui/qml/universal_components/controls/UC/SearchField.qml: -------------------------------------------------------------------------------- 1 | //SearchField.qml 2 | 3 | import QtQuick.Controls 1.0 4 | 5 | TextField{ 6 | } -------------------------------------------------------------------------------- /gui/qml/universal_components/controls/UC/Slider.qml: -------------------------------------------------------------------------------- 1 | import QtQuick.Controls 1.0 2 | 3 | Slider{ 4 | property string valueText : "" 5 | } -------------------------------------------------------------------------------- /gui/qml/universal_components/silica/UC/SearchField.qml: -------------------------------------------------------------------------------- 1 | //SearchField.qml 2 | 3 | import Sailfish.Silica 1.0 4 | 5 | SearchField{ 6 | } -------------------------------------------------------------------------------- /gui/qml/universal_components/silica/UC/VerticalScrollDecorator.qml: -------------------------------------------------------------------------------- 1 | import Sailfish.Silica 1.0 2 | 3 | VerticalScrollDecorator{ 4 | } 5 | -------------------------------------------------------------------------------- /gui/qml/sailfish_specific/SailfishKeepAlive.qml: -------------------------------------------------------------------------------- 1 | // provided by the libkeepalive package 2 | 3 | import org.nemomobile.keepalive 1.1 4 | 5 | KeepAlive {} -------------------------------------------------------------------------------- /gui/qml/universal_components/controls/UC/Screen.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 1.0 3 | 4 | Item{ 5 | width : 800 6 | height : 600 7 | } -------------------------------------------------------------------------------- /gui/qml/universal_components/controls/UC/Button.qml: -------------------------------------------------------------------------------- 1 | import QtQuick.Controls 1.0 2 | import QtQuick.Controls.Styles 1.0 3 | 4 | Button{ 5 | style: ButtonStyle {} 6 | } -------------------------------------------------------------------------------- /gui/qml/universal_components/controls/UC/PageStatus.js: -------------------------------------------------------------------------------- 1 | var Inactive = 0 2 | var Activating = 1 3 | var Active = 2 4 | var Deactivating = 3 5 | var foo = ".harmattan/" 6 | -------------------------------------------------------------------------------- /gui/qml/universal_components/controls/UC/IconButton.qml: -------------------------------------------------------------------------------- 1 | import QtQuick.Controls 1.0 2 | import QtQuick.Controls.Styles 1.0 3 | 4 | Button{ 5 | style: ButtonStyle {} 6 | } -------------------------------------------------------------------------------- /gui/qml/universal_components/controls/UC/MenuItem.qml: -------------------------------------------------------------------------------- 1 | import QtQuick.Controls 1.0 2 | 3 | MenuItem{ 4 | signal clicked 5 | onTriggered : { 6 | clicked() 7 | } 8 | } -------------------------------------------------------------------------------- /gui/qml/universal_components/controls/UC/VerticalScrollDecorator.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 1.0 3 | 4 | Item { 5 | //TODO: implement scroll decorator 6 | } 7 | -------------------------------------------------------------------------------- /gui/qml/universal_components/controls/UC/Page.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 1.0 3 | 4 | Item { 5 | // TODO: page active tracking 6 | property bool isActive : true 7 | } -------------------------------------------------------------------------------- /gui/qml/universal_components/silica/UC/Page.qml: -------------------------------------------------------------------------------- 1 | import Sailfish.Silica 1.0 2 | 3 | Page { 4 | allowedOrientations : Orientation.All 5 | property bool isActive : status == PageStatus.Active 6 | } -------------------------------------------------------------------------------- /gui/qml/i18n/.tx/config: -------------------------------------------------------------------------------- 1 | [main] 2 | host = https://www.transifex.net 3 | type = QT 4 | 5 | [mieru.master] 6 | file_filter = qml_.ts 7 | source_file = qml_en.ts 8 | source_lang = en 9 | 10 | -------------------------------------------------------------------------------- /gui/qml/i18n/pull_transifex.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # pull changes from transifex, 4 | # for existing translation files 5 | # -> it is probably a good idea 6 | # to first push any local cnages 7 | tx pull 8 | -------------------------------------------------------------------------------- /gui/qml/i18n/all_pull_transifex.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # pull all changes from transifex, 4 | # including new languages 5 | # -> it is probably a good idea 6 | # to first push any local changes 7 | tx pull -a 8 | -------------------------------------------------------------------------------- /gui/qml/universal_components/silica/UC/PageStatus.js: -------------------------------------------------------------------------------- 1 | var Inactive = 0 2 | var Activating = 1 3 | var Active = 2 4 | var Deactivating = 3 5 | //TODO: check if the values are 6 | // match the Silica page status 7 | -------------------------------------------------------------------------------- /gui/qml/modrana_components/ThemedBackgroundRectangle.qml: -------------------------------------------------------------------------------- 1 | // ThemedBackgroundRectangle.qml 2 | // A background rectangle that respects the modRana theme. 3 | import UC 1.0 4 | 5 | BackgroundRectangle { 6 | normalColor : rWin.theme.color.main_fill 7 | } -------------------------------------------------------------------------------- /gui/qml/i18n/update.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # update l18n to reflect source code changes 3 | 4 | # NOTE: adding a language 5 | 6 | # update the .ts files 7 | lupdate ../*.qml -locations absolute -ts *.ts 8 | 9 | # regenerate the .qml files 10 | lrelease *.ts 11 | -------------------------------------------------------------------------------- /gui/qml/universal_components/silica/UC/IconButton.qml: -------------------------------------------------------------------------------- 1 | import Sailfish.Silica 1.0 2 | 3 | IconButton { 4 | property string iconSource : "" 5 | property bool checkable : false 6 | property bool checked : false 7 | onIconSourceChanged : { 8 | icon.source = iconSource 9 | } 10 | } -------------------------------------------------------------------------------- /gui/qml/modrana_components/PageHeaderBackground.qml: -------------------------------------------------------------------------------- 1 | //PageHeader.qml 2 | // 3 | //modRana page header 4 | 5 | import QtQuick 2.0 6 | 7 | Rectangle { 8 | id : header 9 | color : rWin.theme.color.main_fill 10 | anchors.top : parent.top 11 | anchors.left : parent.left 12 | anchors.right : parent.right 13 | height : rWin.headerHeight 14 | } -------------------------------------------------------------------------------- /gui/qml/universal_components/silica/UC/PageHeader.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import Sailfish.Silica 1.0 3 | 4 | PageHeader { 5 | id : pageHeader 6 | property color color 7 | property real headerHeight 8 | property alias menu : gridView.children 9 | 10 | SilicaGridView { 11 | id : gridView 12 | anchors.fill : parent 13 | } 14 | } -------------------------------------------------------------------------------- /gui/qml/backend/DynamicTimer.qml: -------------------------------------------------------------------------------- 1 | // QtQuick timer designed for dynamic runtime creation and triggering 2 | import QtQuick 2.0 3 | 4 | Timer { 5 | id : dynamicTimer 6 | property int timerId 7 | running : true 8 | repeat : true 9 | 10 | onTriggered : { 11 | rWin.python.call("modrana.gui.modules.cron._timerTriggered", [dynamicTimer.timerId]) 12 | } 13 | } -------------------------------------------------------------------------------- /gui/qml/modrana_components/SmartGrid.qml: -------------------------------------------------------------------------------- 1 | // SmartGrid.qml 2 | // Smart Grid is a smart Grid element that shows one column in portrait 3 | // and two columns in landscape. 4 | 5 | import QtQuick 2.0 6 | 7 | Grid { 8 | property real cellWidth : parent.width/columns 9 | // 2 columns in landscape, 1 in portrait 10 | columns : rWin.inPortrait ? 1 : 2 11 | spacing : rWin.c.style.main.spacing 12 | } -------------------------------------------------------------------------------- /gui/qml/modrana_components/TIcon.qml: -------------------------------------------------------------------------------- 1 | //TIcon.qml 2 | // an automatically themed icon 3 | 4 | import QtQuick 2.0 5 | 6 | Image { 7 | property string iconName : "" 8 | // TODO: proper slash,backslash,qUrl handling ? 9 | 10 | // handle place-holders 11 | source : iconName == "" ? "" : "image://python/icon/" + rWin.theme.id + "/" + iconName 12 | fillMode : Image.PreserveAspectFit 13 | smooth : true 14 | } -------------------------------------------------------------------------------- /data/desktop_files/mieru_harmattan.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Encoding=UTF-8 3 | Version=1.0 4 | Type=Application 5 | Name=Mieru 6 | Exec=invoker --single-instance --splash /opt/mieru/icons/harmattan_splash_portrait.png --type=e /opt/mieru/mieru.py -p harmattan -u harmattan 7 | Icon=/usr/share/icons/hicolor/80x80/apps/mieru.png 8 | Categories=Graphics; 9 | X-Window-Icon= 10 | X-HildonDesk-ShowInToolbar=true 11 | X-Osso-Type=application/x-executable 12 | -------------------------------------------------------------------------------- /gui/qml/modrana_components/ContentColumn.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | 3 | Column { 4 | anchors.top : parent.top 5 | anchors.left : parent.left 6 | anchors.right : parent.right 7 | anchors.topMargin : rWin.c.style.main.spacing 8 | anchors.leftMargin : rWin.c.style.main.spacing 9 | anchors.rightMargin : rWin.c.style.main.spacing 10 | spacing : rWin.c.style.main.spacingBig * 2 11 | width : parent.width 12 | } -------------------------------------------------------------------------------- /gui/qml/universal_components/silica/UC/TopMenu.qml: -------------------------------------------------------------------------------- 1 | import Sailfish.Silica 1.0 2 | 3 | PullDownMenu { 4 | 5 | // The popup function does nothing and is only there 6 | // for API compatibility - the Controls TopMenu needs 7 | // popup() to be called if used without a PageHeader 8 | // to open the menu. So we also provide a dummy popup() 9 | // function here with silica so that code that expects 10 | // that TopMenu has a popup() method does not break. 11 | function popup() {} 12 | } -------------------------------------------------------------------------------- /data/desktop_files/mieru_fremantle.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Encoding=UTF-8 3 | Version=1.0.0 4 | Type=Application 5 | Name=Mieru 6 | Exec=mieru 7 | Icon=/usr/share/icons/hicolor/80x80/apps/mieru.png 8 | Categories=Graphics; 9 | X-Window-Icon= 10 | X-HildonDesk-ShowInToolbar=true 11 | X-Osso-Type=application/x-executable 12 | X-Icon-path=/usr/share/icons/hicolor/80x80/ 13 | X-Window-Icon=mieru 14 | Terminal=false 15 | GenericName=Mieru manga and comics reader 16 | Comment=A manga and comics reader 17 | -------------------------------------------------------------------------------- /TODO.TXT: -------------------------------------------------------------------------------- 1 | TODO 2 | 3 | Qt 5 & Python 3 port 4 | ==================== 5 | * port from PySide to PyOtherSide 6 | * port from QtQuick 1.1 to QtQuick 2.0 7 | * port from QtComponents to Universal Components 8 | * drop the Qt 4 & Clutter GUIs 9 | * finish porting of the Python code to Python 3 10 | * no need to maintain Python 2 compatibility as 11 | PyOtherSide is Python 3 only 12 | * use proper logging, based on modRana code 13 | * packaging 14 | * Sailfish OS 15 | * Android 16 | * desktop Linux 17 | * write a porting TODO DONE ;-) -------------------------------------------------------------------------------- /gui/qml/universal_components/silica/UC/BackgroundRectangle.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import Sailfish.Silica 1.0 3 | MouseArea { 4 | id : bMouse 5 | property color highlightedColor: Theme.rgba(Theme.highlightBackgroundColor, Theme.highlightBackgroundOpacity) 6 | property string normalColor : "transparent" 7 | implicitHeight: Theme.itemSizeSmall 8 | Rectangle { 9 | anchors.fill : parent 10 | property bool clickable : false 11 | color: bMouse.pressed ? highlightedColor : normalColor 12 | } 13 | } -------------------------------------------------------------------------------- /gui/qml/universal_components/sync_qmldir.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # The universal components provide 3 | # a set of platform specific compatibility modules 4 | # that all need to have the exact same qmldir file. 5 | # This script copies the main qmldir to the all the 6 | # platform specific modules, so they don't have to be 7 | # kept in sync manually. 8 | # 9 | # 10 | # Note: If you add a new compatibility module, 11 | # add it here. 12 | 13 | cp main_qmldir controls/UC/qmldir 14 | cp main_qmldir silica/UC/qmldir 15 | cp main_qmldir glacier/UC/qmldir -------------------------------------------------------------------------------- /gui/qml/universal_components/controls/UC/BackgroundRectangle.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 1.0 3 | import "style.js" as S 4 | 5 | MouseArea { 6 | id : bMouse 7 | property string highlightedColor: "darkblue" 8 | property string normalColor : "#92aaf3" 9 | implicitHeight: S.style.dialog.item.height 10 | Rectangle { 11 | anchors.fill : parent 12 | property bool clickable : false 13 | color: bMouse.pressed ? highlightedColor : normalColor 14 | radius : S.style.listView.cornerRadius 15 | } 16 | } -------------------------------------------------------------------------------- /gui/qml/backend/pdb.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import io.thp.pyotherside 1.0 3 | 4 | Rectangle { 5 | color : "green" 6 | width : 640 7 | height : 480 8 | Text { 9 | text : "hello PDB!" 10 | } 11 | 12 | Python { 13 | id : python 14 | Component.onCompleted: { 15 | importModule('pdb', function() { 16 | call('pdb.set_trace()') 17 | }) 18 | } 19 | onError: { 20 | // when an exception is raised, this error handler will be called 21 | console.log('python error: ' + traceback); 22 | 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /gui/qml/modrana_components/TextButton.qml: -------------------------------------------------------------------------------- 1 | //TextButton.qml 2 | 3 | import QtQuick 2.0 4 | import UC 1.0 5 | 6 | ThemedBackgroundRectangle { 7 | id : textButton 8 | property alias text : tbLabel.text 9 | Label { 10 | id : tbLabel 11 | anchors.verticalCenter : parent.verticalCenter 12 | anchors.left : parent.left 13 | anchors.leftMargin : rWin.c.style.main.spacing 14 | anchors.right : parent.right 15 | anchors.rightMargin : rWin.c.style.main.spacing 16 | elide : Text.ElideRight 17 | horizontalAlignment : Text.AlignHCenter 18 | } 19 | onClicked : { 20 | textButton.clicked() 21 | } 22 | } -------------------------------------------------------------------------------- /gui/qml/SwitchWithText.qml: -------------------------------------------------------------------------------- 1 | //SwitchWithText.qml 2 | import QtQuick 2.0 3 | import UC 1.0 4 | 5 | Item { 6 | id: container 7 | 8 | height: label.height 9 | width : parent.width 10 | 11 | property alias text: label.text 12 | property alias checked: switcher.checked 13 | 14 | Label { 15 | id: label 16 | anchors { 17 | top: parent.top 18 | left: parent.left 19 | right: switcher.left 20 | rightMargin: 16 21 | } 22 | } 23 | 24 | Switch { 25 | id: switcher 26 | anchors { 27 | right: parent.right 28 | verticalCenter: parent.verticalCenter 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /gui/qml/LineText.qml: -------------------------------------------------------------------------------- 1 | //LineText.qml 2 | import QtQuick 2.0 3 | import UC 1.0 4 | 5 | Item { 6 | id: lineTextMain 7 | 8 | height: label.height 9 | 10 | property alias text: label.text 11 | 12 | Rectangle { 13 | id: line 14 | color : "black" 15 | height : 2 16 | 17 | anchors { 18 | left: parent.left 19 | right : label.left 20 | verticalCenter: parent.verticalCenter 21 | } 22 | } 23 | 24 | Label { 25 | id: label 26 | 27 | anchors { 28 | top: parent.top 29 | //left: parent.left 30 | right: parent.right 31 | leftMargin: 16 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /gui/qml/universal_components/controls/UC/style.js: -------------------------------------------------------------------------------- 1 | .pragma library 2 | 3 | var hiDPI = 0 4 | var style = getStyle(hiDPI) 5 | 6 | function getStyle(i) { 7 | return { 8 | "m" : [1, 2][i], // approximate size multiplier 9 | "main" : { 10 | "multiplier" : [1, 2][i], 11 | "spacing" : [8, 16][i], 12 | "spacingBig" : [16, 32][i] 13 | }, 14 | "dialog" : { 15 | "item" : { 16 | "height" : [80, 160][i] 17 | } 18 | }, 19 | "listView" : { 20 | "spacing" : [8, 16][i], 21 | "cornerRadius" : [8, 16][i], 22 | "itemBorder" : [20, 40][i], 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /OLD_TODO.TXT: -------------------------------------------------------------------------------- 1 | TODO 2 | 3 | Format support 4 | * folders DONE 5 | * archives cointaining just images DONE 6 | * archives containing folder with images DONE 7 | * archives with nested folders (eq a volume archive that contains per-chapter folders) DONE 8 | 9 | Unpacking DONE 10 | 11 | Options storage DONE 12 | 13 | Hildon integration 14 | * notifications DONE 15 | * options menu DONE 16 | 17 | Maintenance 18 | * packaging 19 | 20 | Harmattan QML GUI 21 | * history 22 | * options 23 | * info menu 24 | * current manga info 25 | * path 26 | * current page number/page number 27 | * size 28 | * delete ? 29 | * rename ? 30 | * reading statistics 31 | * about 32 | 33 | * page centering for pages that are smaller than the viewport -------------------------------------------------------------------------------- /gui/qml/universal_components/controls/UC/TextSwitch.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 1.0 3 | import "style.js" as S 4 | 5 | Item { 6 | id: container 7 | 8 | height: label.height 9 | width : parent.width 10 | 11 | property alias text: label.text 12 | property alias checked: switcher.checked 13 | 14 | Label { 15 | id: label 16 | anchors { 17 | top: parent.top 18 | left: parent.left 19 | right: switcher.left 20 | rightMargin: S.style.main.spacingBig 21 | } 22 | } 23 | 24 | Switch { 25 | id: switcher 26 | anchors { 27 | right: parent.right 28 | verticalCenter: parent.verticalCenter 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /gui/qml/modrana_components/BasePage.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import UC 1.0 3 | 4 | /* base modRana page, includes: 5 | * a header, with a 6 | * back button 7 | * page name 8 | * optional tools button 9 | useful for things like track logging menus, 10 | about menus, compass pages, point pages, etc. 11 | */ 12 | 13 | HeaderPage { 14 | id : searchPage 15 | property alias headerTextColor : headerLabel.color 16 | property alias headerText: headerLabel.title 17 | headerContent : PageHeader { 18 | id : headerLabel 19 | // override the default header height with a dynamic 20 | // (depends on back button being shown) modRana specific value 21 | headerHeight : rWin.headerHeight 22 | color : rWin.theme.color.page_header_text 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /gui/qml/modrana_components/PayPalButton.qml: -------------------------------------------------------------------------------- 1 | //PayPalButton.qml 2 | 3 | import QtQuick 2.0 4 | import UC 1.0 5 | 6 | Rectangle { 7 | id : ppButton 8 | color : ppMA.pressed ? "yellow" : "gold" 9 | radius : 30 10 | width : 210 * rWin.c.style.m 11 | height : rWin.c.style.button.generic.height 12 | property string url : "" 13 | 14 | Label { 15 | anchors.horizontalCenter : parent.horizontalCenter 16 | anchors.verticalCenter : parent.verticalCenter 17 | text : "PayPal" 18 | font.pixelSize : 32 * rWin.c.style.m 19 | } 20 | MouseArea { 21 | id : ppMA 22 | anchors.fill : parent 23 | onClicked : { 24 | rWin.log.info('PayPal button clicked') 25 | Qt.openUrlExternally(url) 26 | } 27 | } 28 | } 29 | 30 | 31 | -------------------------------------------------------------------------------- /gui/qml/PayPalButton.qml: -------------------------------------------------------------------------------- 1 | //PayPalButton.qml 2 | 3 | import QtQuick 2.0 4 | import UC 1.0 5 | 6 | Rectangle { 7 | id : ppButton 8 | color : ppMA.pressed ? "yellow" : "gold" 9 | smooth : true 10 | radius : 30 11 | width : 210 12 | height : 60 13 | property string url : "" 14 | 15 | Label { 16 | anchors.horizontalCenter : parent.horizontalCenter 17 | anchors.verticalCenter : parent.verticalCenter 18 | text : "

PayPal

" 19 | } 20 | MouseArea { 21 | id : ppMA 22 | anchors.fill : parent 23 | onClicked : { 24 | console.log('PayPal button clicked') 25 | rootWindow.notify(qsTr("Opening PayPal donation page, thanks!")) 26 | Qt.openUrlExternally(url) 27 | } 28 | } 29 | } 30 | 31 | 32 | -------------------------------------------------------------------------------- /gui/qml/FlattrButton.qml: -------------------------------------------------------------------------------- 1 | //FlattrButton.qml 2 | 3 | import QtQuick 2.0 4 | import UC 1.0 5 | 6 | Rectangle { 7 | id : flattrButton 8 | color : flattrMA.pressed ? "limegreen" : "green" 9 | smooth : true 10 | radius : 5 11 | width : 210 12 | height : 45 13 | property string url : "" 14 | 15 | Label { 16 | anchors.horizontalCenter : parent.horizontalCenter 17 | anchors.verticalCenter : parent.verticalCenter 18 | text : "

Flattr this !

" 19 | color : "white" 20 | } 21 | MouseArea { 22 | id : flattrMA 23 | anchors.fill : parent 24 | onClicked : { 25 | console.log('Flattr button clicked') 26 | rootWindow.notify(qsTr("Opening Flattr donation page, thanks!")) 27 | Qt.openUrlExternally(url) 28 | } 29 | } 30 | } 31 | 32 | 33 | -------------------------------------------------------------------------------- /gui/qml/universal_components/main_qmldir: -------------------------------------------------------------------------------- 1 | module UC 2 | Button 1.0 Button.qml 3 | IconButton 1.0 IconButton.qml 4 | ApplicationWindow 1.0 ApplicationWindow.qml 5 | Page 1.0 Page.qml 6 | PageHeader 1.0 PageHeader.qml 7 | Screen 1.0 Screen.qml 8 | ProgressBar 1.0 ProgressBar.qml 9 | Slider 1.0 Slider.qml 10 | Switch 1.0 Switch.qml 11 | TextSwitch 1.0 TextSwitch.qml 12 | Label 1.0 Label.qml 13 | TextArea 1.0 TextArea.qml 14 | TextField 1.0 TextField.qml 15 | SearchField 1.0 SearchField.qml 16 | Dialog 1.0 Dialog.qml 17 | PageStatus 1.0 PageStatus.js 18 | ComboBox 1.0 ComboBox.qml 19 | Menu 1.0 Menu.qml 20 | TopMenu 1.0 TopMenu.qml 21 | MenuItem 1.0 MenuItem.qml 22 | BackgroundRectangle 1.0 BackgroundRectangle.qml 23 | VerticalScrollDecorator 1.0 VerticalScrollDecorator.qml 24 | Popup 1.0 Popup.qml 25 | PlatformFlickable 1.0 PlatformFlickable.qml 26 | PlatformListView 1.0 PlatformListView.qml 27 | -------------------------------------------------------------------------------- /gui/qml/universal_components/controls/UC/qmldir: -------------------------------------------------------------------------------- 1 | module UC 2 | Button 1.0 Button.qml 3 | IconButton 1.0 IconButton.qml 4 | ApplicationWindow 1.0 ApplicationWindow.qml 5 | Page 1.0 Page.qml 6 | PageHeader 1.0 PageHeader.qml 7 | Screen 1.0 Screen.qml 8 | ProgressBar 1.0 ProgressBar.qml 9 | Slider 1.0 Slider.qml 10 | Switch 1.0 Switch.qml 11 | TextSwitch 1.0 TextSwitch.qml 12 | Label 1.0 Label.qml 13 | TextArea 1.0 TextArea.qml 14 | TextField 1.0 TextField.qml 15 | SearchField 1.0 SearchField.qml 16 | Dialog 1.0 Dialog.qml 17 | PageStatus 1.0 PageStatus.js 18 | ComboBox 1.0 ComboBox.qml 19 | Menu 1.0 Menu.qml 20 | TopMenu 1.0 TopMenu.qml 21 | MenuItem 1.0 MenuItem.qml 22 | BackgroundRectangle 1.0 BackgroundRectangle.qml 23 | VerticalScrollDecorator 1.0 VerticalScrollDecorator.qml 24 | Popup 1.0 Popup.qml 25 | PlatformFlickable 1.0 PlatformFlickable.qml 26 | PlatformListView 1.0 PlatformListView.qml 27 | -------------------------------------------------------------------------------- /gui/qml/universal_components/glacier/UC/qmldir: -------------------------------------------------------------------------------- 1 | module UC 2 | Button 1.0 Button.qml 3 | IconButton 1.0 IconButton.qml 4 | ApplicationWindow 1.0 ApplicationWindow.qml 5 | Page 1.0 Page.qml 6 | PageHeader 1.0 PageHeader.qml 7 | Screen 1.0 Screen.qml 8 | ProgressBar 1.0 ProgressBar.qml 9 | Slider 1.0 Slider.qml 10 | Switch 1.0 Switch.qml 11 | TextSwitch 1.0 TextSwitch.qml 12 | Label 1.0 Label.qml 13 | TextArea 1.0 TextArea.qml 14 | TextField 1.0 TextField.qml 15 | SearchField 1.0 SearchField.qml 16 | Dialog 1.0 Dialog.qml 17 | PageStatus 1.0 PageStatus.js 18 | ComboBox 1.0 ComboBox.qml 19 | Menu 1.0 Menu.qml 20 | TopMenu 1.0 TopMenu.qml 21 | MenuItem 1.0 MenuItem.qml 22 | BackgroundRectangle 1.0 BackgroundRectangle.qml 23 | VerticalScrollDecorator 1.0 VerticalScrollDecorator.qml 24 | Popup 1.0 Popup.qml 25 | PlatformFlickable 1.0 PlatformFlickable.qml 26 | PlatformListView 1.0 PlatformListView.qml 27 | -------------------------------------------------------------------------------- /gui/qml/universal_components/silica/UC/qmldir: -------------------------------------------------------------------------------- 1 | module UC 2 | Button 1.0 Button.qml 3 | IconButton 1.0 IconButton.qml 4 | ApplicationWindow 1.0 ApplicationWindow.qml 5 | Page 1.0 Page.qml 6 | PageHeader 1.0 PageHeader.qml 7 | Screen 1.0 Screen.qml 8 | ProgressBar 1.0 ProgressBar.qml 9 | Slider 1.0 Slider.qml 10 | Switch 1.0 Switch.qml 11 | TextSwitch 1.0 TextSwitch.qml 12 | Label 1.0 Label.qml 13 | TextArea 1.0 TextArea.qml 14 | TextField 1.0 TextField.qml 15 | SearchField 1.0 SearchField.qml 16 | Dialog 1.0 Dialog.qml 17 | PageStatus 1.0 PageStatus.js 18 | ComboBox 1.0 ComboBox.qml 19 | Menu 1.0 Menu.qml 20 | TopMenu 1.0 TopMenu.qml 21 | MenuItem 1.0 MenuItem.qml 22 | BackgroundRectangle 1.0 BackgroundRectangle.qml 23 | VerticalScrollDecorator 1.0 VerticalScrollDecorator.qml 24 | Popup 1.0 Popup.qml 25 | PlatformFlickable 1.0 PlatformFlickable.qml 26 | PlatformListView 1.0 PlatformListView.qml 27 | -------------------------------------------------------------------------------- /gui/qml/modrana_components/FlattrButton.qml: -------------------------------------------------------------------------------- 1 | //FlattrButton.qml 2 | 3 | import QtQuick 2.0 4 | import UC 1.0 5 | 6 | Rectangle { 7 | id : flattrButton 8 | color : flattrMA.pressed ? "limegreen" : "green" 9 | radius : 5 10 | width : 210 * rWin.c.style.m 11 | // height should be slightly smaller than for the flattr button 12 | height : rWin.c.style.button.generic.height * 0.75 13 | property string url : "" 14 | 15 | Label { 16 | anchors.horizontalCenter : parent.horizontalCenter 17 | anchors.verticalCenter : parent.verticalCenter 18 | text : "Flattr this !" 19 | color : "white" 20 | font.pixelSize : 28 * rWin.c.style.m 21 | } 22 | MouseArea { 23 | id : flattrMA 24 | anchors.fill : parent 25 | onClicked : { 26 | rWin.log.info('Flattr button clicked') 27 | Qt.openUrlExternally(url) 28 | } 29 | } 30 | } 31 | 32 | 33 | -------------------------------------------------------------------------------- /gui/qml/SelectorButtonWithText.qml: -------------------------------------------------------------------------------- 1 | //SelectorButtonWithText.qml 2 | import QtQuick 2.0 3 | import UC 1.0 4 | 5 | Item { 6 | id: container 7 | 8 | height: label.height 9 | width : parent.width 10 | 11 | property alias text : label.text 12 | property alias buttonText : pfsButton.text 13 | property alias iconSource : pfsButton.iconSource 14 | property alias enabled : pfsButton.enabled 15 | property Item selector 16 | 17 | Label { 18 | id: label 19 | 20 | anchors { 21 | top: parent.top 22 | left: parent.left 23 | right: pfsButton.left 24 | rightMargin: 16 25 | } 26 | } 27 | 28 | Button { 29 | id : pfsButton 30 | iconSource : "image://theme/icon-m-common-combobox-arrow" 31 | width : 200 32 | anchors { 33 | right: parent.right 34 | verticalCenter: parent.verticalCenter 35 | } 36 | onClicked : { 37 | selector.open() 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /gui/qml_page.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """qml_page.py - a manga/comix book page based on an QML image""" 3 | 4 | import page 5 | 6 | 7 | class QMLPage(page.Page): 8 | def __init__(self, image, gui): 9 | page.Page.__init__(self) 10 | self.image=image 11 | self.gui = gui 12 | """as the image data is actually managed by QML, 13 | the page object just stores an image id""" 14 | 15 | def activate(self): 16 | """start reacting on dragging and stage resize 17 | - page is not activated by default""" 18 | pass 19 | 20 | def deactivate(self): 21 | """stop reacting on dragging and stage resize""" 22 | pass 23 | 24 | def show(self): 25 | pass 26 | 27 | def getSize(self): 28 | """return image resolution""" 29 | return 0,0 30 | 31 | def free(self): 32 | """quickly release all resources""" 33 | pass 34 | 35 | def popImage(self): 36 | """return the corresponding image file object and 37 | remove local references to it""" 38 | output = self.image 39 | self.image = None 40 | return output 41 | -------------------------------------------------------------------------------- /gui/qml/backend/OptProp.qml: -------------------------------------------------------------------------------- 1 | //OptProp.qml (OptionsProperty) 2 | import QtQuick 2.0 3 | 4 | Item { 5 | id : optProp 6 | property string key : "" 7 | property var value : null 8 | property bool initialized : false 9 | 10 | // if the value changes, save it to the persistent options 11 | // dictionary - but only once we are initialized 12 | // (this should prevent use from overwriting the given key 13 | // by the default value) 14 | onValueChanged : { 15 | if (optProp.initialized && optProp.key) { 16 | rWin.set(optProp.key, optProp.value) 17 | } 18 | } 19 | 20 | // once the key is set (this also happens when a key value is 21 | // provided when the object ins instantiated) fetch the value 22 | // from the persistent options dictionary and report that 23 | // we are ready once done 24 | onKeyChanged : { 25 | optProp.initialized = false 26 | rWin.get(optProp.key, optProp.value, function(v){ 27 | optProp.value = v 28 | optProp.initialized = true 29 | }) 30 | } 31 | } -------------------------------------------------------------------------------- /platforms/harmattan.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Mieru hildon UI (for Maemo 5@N900) 4 | """ 5 | 6 | from base_platform import BasePlatform 7 | 8 | class Harmattan(BasePlatform): 9 | def __init__(self, mieru): 10 | BasePlatform.__init__(self) 11 | self.mieru = mieru 12 | 13 | def getIDString(self): 14 | return "harmattan" 15 | 16 | def getName(self): 17 | return "MeeGo 1.2 Harmattan" 18 | 19 | def getDeviceName(self): 20 | return "Nokia N9 or N950" 21 | 22 | def notify(self, message, icon=""): 23 | self.mieru.gui._notify(message, icon) 24 | 25 | def getDefaultFileSelectorPath(self): 26 | """we default to the MyDocs folder as this is where most 27 | users will store their mangas and comic books""" 28 | return "/home/user/MyDocs/" 29 | 30 | def showQuitButton(self): 31 | """Swype handles window closing""" 32 | return False 33 | 34 | def showMinimiseButton(self): 35 | """Swype handles window switching""" 36 | return False 37 | 38 | def getScreenWH(self): 39 | return 854,480 40 | 41 | def startInFullscreen(self): 42 | return True -------------------------------------------------------------------------------- /gui/qml/sailfish_specific/SailfishMediaKeys.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import Sailfish.Media 1.0 3 | import org.nemomobile.policy 1.0 4 | 5 | Item { 6 | id : mediaKeys 7 | enabled : false 8 | 9 | signal up 10 | signal down 11 | 12 | MediaKey { 13 | id: mediaUp 14 | enabled: mediaKeysAccessResource.acquired 15 | key: Qt.Key_VolumeUp 16 | onPressed : { 17 | mediaKeys.up() 18 | } 19 | } 20 | MediaKey { 21 | id: mediaDown 22 | enabled: mediaUp.enabled 23 | key: Qt.Key_VolumeDown 24 | onPressed : { 25 | mediaKeys.down() 26 | } 27 | } 28 | 29 | Permissions { 30 | enabled: mediaKeys.enabled 31 | autoRelease: true 32 | // for some reason we need to be a camera or this 33 | // does not work - inner platform effect in action, 34 | // wooooo ! :D 35 | applicationClass: "camera" 36 | 37 | Resource { 38 | id: mediaKeysAccessResource 39 | type: Resource.ScaleButton 40 | optional: true 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /gui/qml/modrana_components/BitcoinButton.qml: -------------------------------------------------------------------------------- 1 | //BitcoinButton.qml 2 | 3 | import QtQuick 2.0 4 | import UC 1.0 5 | 6 | Rectangle { 7 | id : bitcoinButton 8 | color : bitcoinMA.pressed ? "silver" : "black" 9 | radius : 25 10 | border.width : 2 11 | border.color : "white" 12 | width : 210 * rWin.c.style.m 13 | height : rWin.c.style.button.generic.height * 0.85 14 | property string url : "" 15 | 16 | Label { 17 | anchors.horizontalCenter : parent.horizontalCenter 18 | anchors.verticalCenter : parent.verticalCenter 19 | font.family: "Arial" 20 | font.pixelSize : 24 * rWin.c.style.m 21 | text : "Bitcoin" 22 | color : bitcoinMA.pressed ? "black" : "white" 23 | width : parent.width 24 | horizontalAlignment : Text.AlignHCenter 25 | verticalAlignment : Text.AlignVCenter 26 | } 27 | MouseArea { 28 | id : bitcoinMA 29 | anchors.fill : parent 30 | onClicked : { 31 | rWin.log.info('Bitcoin button clicked') 32 | var bitcoinPage = Qt.createComponent("BitcoinPage.qml") 33 | rWin.pushPage(bitcoinPage, {url : bitcoinButton.url}, rWin.animate) 34 | } 35 | } 36 | } 37 | 38 | 39 | -------------------------------------------------------------------------------- /gui/qml/modrana_components/BitcoinPage.qml: -------------------------------------------------------------------------------- 1 | //BitcoinPage.qml 2 | 3 | import QtQuick 2.0 4 | import UC 1.0 5 | 6 | BasePage { 7 | id : bitcoinPage 8 | width : parent.width - 30 * rWin.c.style.m 9 | headerText: "Bitcoin address" 10 | property string url : "" 11 | content : ContentColumn { 12 | id: dialogContent 13 | Image { 14 | id : bitcoinQrCode 15 | anchors.horizontalCenter : parent.horizontalCenter 16 | source : "image://python/icon/" + rWin.theme.id + "/qrcode_bitcoin.png" 17 | } 18 | TextInput { 19 | id : urlField 20 | anchors.horizontalCenter : parent.horizontalCenter 21 | font.pointSize : 20 * rWin.c.style.m 22 | height : 48 * rWin.c.style.m 23 | text : bitcoinPage.url 24 | onTextChanged : { 25 | selectAll() 26 | } 27 | } 28 | Button { 29 | anchors.horizontalCenter : parent.horizontalCenter 30 | text: qsTr("Copy to clipboard") 31 | onClicked: { 32 | urlField.selectAll() 33 | urlField.copy() 34 | rWin.notify("Bitcoin address copied to clipboard", 3000) 35 | } 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /gui/qml/universal_components/silica/UC/ComboBox.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import Sailfish.Silica 1.0 3 | 4 | ComboBox { 5 | id : cBox 6 | labelMargin : 0 7 | // selected item, only assigned if user 8 | // clicks on an item in the context menu, 9 | // not if changing the current item index 10 | property var item 11 | 12 | menu : ContextMenu { 13 | id : cMenu 14 | Repeater { 15 | id : cRepeater 16 | model : cBox.model 17 | MenuItem { 18 | text : model.text 19 | onClicked : { 20 | cBox.currentItem = model 21 | } 22 | } 23 | } 24 | } 25 | property var model 26 | // how does this work ? 27 | // 28 | // Menu items are added with a ListModel to the 29 | // model property, which dynamically adds them to the 30 | // context menu. Once an item is clicked, its underlying 31 | // ListElement is returned so onCurrentItemChanged 32 | // is triggered. 33 | 34 | onCurrentIndexChanged: { 35 | // assign selected item to the item 36 | // property, so that the onItemChanged 37 | // signal is triggered 38 | cBox.item = cBox.model.get(currentIndex) 39 | } 40 | } -------------------------------------------------------------------------------- /gui/qml/universal_components/controls/UC/ApplicationWindow.qml: -------------------------------------------------------------------------------- 1 | import QtQuick.Controls 1.0 2 | 3 | import "style.js" as S 4 | 5 | ApplicationWindow { 6 | id : appWindow 7 | // for now, we are in landscape when using Controls 8 | property bool inPortrait : appWindow.width < appWindow.height 9 | //property bool inPortrait : false 10 | 11 | //property alias initialPage : pageStack.initialItem 12 | property alias pageStack : pageStack 13 | 14 | property int hiDPI : 0 15 | 16 | StackView { 17 | anchors.fill : parent 18 | id : pageStack 19 | } 20 | 21 | function pushPage(pageInstance, pageProperties, animate) { 22 | // the Controls page stack disables animations when 23 | // false is passed as the third argument, but we want to 24 | // have a more logical interface, so just invert the value 25 | // before passing it to the page stack 26 | pageStack.push(pageInstance, pageProperties, !animate) 27 | return pageInstance 28 | } 29 | 30 | // reload the style table if the hiDPI setting is changed 31 | // NOTE: this should probably happen before the elements 32 | // start using the style table 33 | onHiDPIChanged : { 34 | S.style = S.getStyle(appWindow.hiDPI) 35 | } 36 | } -------------------------------------------------------------------------------- /gui/qml/modrana_components/GratipayButton.qml: -------------------------------------------------------------------------------- 1 | //GratipayButton.qml 2 | 3 | import QtQuick 2.0 4 | import UC 1.0 5 | 6 | Rectangle { 7 | id : gpButton 8 | color : "#0D4035" 9 | gradient: Gradient { 10 | GradientStop { position: 0.0; color : "#2A8F79" } 11 | GradientStop { position: 1.0; color : "#0D4035" } 12 | } 13 | radius : 5 14 | smooth : true 15 | width : 180 * rWin.c.style.m 16 | // height should be slightly smaller than for the flattr button 17 | height : rWin.c.style.button.generic.height * 0.8 18 | property string url : "" 19 | Rectangle { 20 | id : clickedBg 21 | anchors.fill : parent 22 | radius : parent.radius 23 | visible : gpMA.pressed 24 | color : "#0D4035" 25 | smooth : true 26 | } 27 | Label { 28 | anchors.horizontalCenter : parent.horizontalCenter 29 | anchors.verticalCenter : parent.verticalCenter 30 | text : "Gratipay" 31 | color : "white" 32 | font.pixelSize : 32 * rWin.c.style.m 33 | } 34 | MouseArea { 35 | id : gpMA 36 | anchors.fill : parent 37 | onClicked : { 38 | rWin.log.info('Gratipay button clicked') 39 | Qt.openUrlExternally(url) 40 | } 41 | } 42 | } 43 | 44 | 45 | -------------------------------------------------------------------------------- /gui/qml/backend/Screen.qml: -------------------------------------------------------------------------------- 1 | // defines various actions and registers their triggers 2 | import QtQuick 2.0 3 | 4 | Item { 5 | id : screen 6 | 7 | property bool keepScreenOn : false 8 | 9 | property var screenInstance : null 10 | 11 | onKeepScreenOnChanged : { 12 | if (screenInstance != null) { 13 | screenInstance.suspend = keepScreenOn 14 | if (keepScreenOn) { 15 | rWin.log.info("screen blanking enabled") 16 | } else { 17 | rWin.log.info("screen blanking disabled") 18 | } 19 | } 20 | } 21 | 22 | Component.onCompleted : { 23 | initScreen() 24 | } 25 | 26 | function initScreen() { 27 | // the screen control module might not be available on 28 | // all platforms so we need to handle import failure 29 | // (real conditional imports would be nice, wouldn't they ;)) 30 | // TODO: handle also other platforms than Sailfish OS 31 | var sailfishScreenInstance = rWin.loadQMLFile("sailfish_specific/SailfishScreen.qml", true) 32 | if (sailfishScreenInstance) { 33 | rWin.log.info("Screen: screen blanking control initialized") 34 | screen.screenInstance = sailfishScreenInstance 35 | } else { 36 | rWin.log.info("Screen: screen blanking control is not available") 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /gui/qml/PageFitSelector.qml: -------------------------------------------------------------------------------- 1 | //PageFitSelector.qml 2 | import QtQuick 1.0 3 | import UC 1.0 4 | 5 | SelectionDialog { 6 | id: pageFitSelector 7 | property Style platformStyle: SelectionDialogStyle {} 8 | property string pageFitMode : "original" 9 | titleText : qsTr("Page fit mode") 10 | onSelectedIndexChanged : { 11 | pageFitMode = model.get(selectedIndex).key 12 | accept() 13 | } 14 | model : ListModel { 15 | id : modesModel 16 | // append list items here in place of single ListElements in order to support translation 17 | Component.onCompleted : { 18 | append({"name" : qsTr("1:1 - original size"), "key" : "original"}) 19 | append({"name" : qsTr("fit to width"), "key" : "width"}) 20 | append({"name" : qsTr("fit to height"), "key" : "height"}) 21 | append({"name" : qsTr("fit to screen"), "key" : "screen"}) 22 | append({"name" : qsTr("custom - remember scale"), "key" : "custom"}) 23 | append({"name" : qsTr("orientation specific"), "key" : "orient"}) 24 | append({"name" : qsTr("show the most"), "key" : "most"}) 25 | } 26 | } 27 | function addNop() { 28 | modesModel.insert(0, {"name" : qsTr("no action"), "key" : "no action"}) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /gui/qml/backend/Platform.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | 3 | Item { 4 | property bool valid : false 5 | property string modRanaVersion : "unknown" 6 | property bool showQuitButton : true 7 | property bool fullscreenOnly : false 8 | property bool shouldStartInFullscreen : false 9 | property bool needsBackButton : true 10 | property bool needsPageBackground : false 11 | // location specific 12 | property var lastKnownPos : null 13 | property bool gpsEnabled : true 14 | property var posFromFile : null 15 | property var nmeaFilePath : null 16 | property string themesFolderPath : "unknown_path" 17 | property bool sailfish : false 18 | 19 | function setValuesFromPython(values) { 20 | modRanaVersion = values.modRanaVersion 21 | showQuitButton = values.showQuitButton 22 | fullscreenOnly = values.fullscreenOnly 23 | shouldStartInFullscreen = values.shouldStartInFullscreen 24 | needsBackButton = values.needsBackButton 25 | needsPageBackground = values.needsPageBackground 26 | lastKnownPos = values.lastKnownPos 27 | gpsEnabled = values.gpsEnabled 28 | posFromFile = values.posFromFile 29 | nmeaFilePath = values.nmeaFilePath 30 | themesFolderPath = values.themesFolderPath 31 | sailfish = values.sailfish 32 | // done, we now have the values from Python we needed 33 | valid = true 34 | } 35 | } -------------------------------------------------------------------------------- /gui/qml/DonationDialog.qml: -------------------------------------------------------------------------------- 1 | //DonationDialog.qml 2 | 3 | import QtQuick 2.0 4 | import UC 1.0 5 | 6 | HeaderDialog { 7 | id : donationDialog 8 | titleText : qsTr("Choose a donation method:") 9 | content:Item { 10 | id: dialogContent 11 | width : parent.width 12 | Row { 13 | id : ppFlattrRow 14 | anchors.top : parent.top 15 | anchors.horizontalCenter : parent.horizontalCenter 16 | anchors.topMargin : 24 17 | spacing : 32 18 | PayPalButton { 19 | id : ppButton 20 | anchors.verticalCenter : parent.verticalCenter 21 | url : "https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=martin%2ekolman%40gmail%2ecom&lc=GB&item_name=Mieru%20project¤cy_code=EUR&bn=PP%2dDonationsBF%3abtn_donate_LG%2egif%3aNonHosted" 22 | } 23 | FlattrButton { 24 | id : flattrButton 25 | anchors.verticalCenter : parent.verticalCenter 26 | url : "http://flattr.com/thing/830372/Mieru-flexible-manga-and-comic-book-reader" 27 | } 28 | } 29 | BitcoinButton { 30 | id : bitcoinButton 31 | anchors.top : ppFlattrRow.bottom 32 | anchors.topMargin : 24 33 | anchors.horizontalCenter : parent.horizontalCenter 34 | url : "1PPnoD4SyeQYgvhJ6L5xkjZ4qE4WMMCe1k" 35 | showBorder : true 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /gui/qml/universal_components/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2014, Martin Kolman 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 8 | 9 | 2. 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. 10 | 11 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 12 | 13 | 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. 14 | -------------------------------------------------------------------------------- /gui/qml/modrana_components/KeyTextSwitch.qml: -------------------------------------------------------------------------------- 1 | //KeyComboBox.qml 2 | // 3 | // ModRana uses an persistent dictionary 4 | // to configure a lot of stuff and there is a lot 5 | // of text switches comboboxes in options, most of them 6 | // mapped on a key in the persistent dict & it's 7 | // current value. 8 | // KeyTextSwitch makes it possible to just assign the 9 | // key property and the text switch will asynchronously 10 | // fetch the value of the key from Python and set the 11 | // checked property automatically. 12 | 13 | import QtQuick 2.0 14 | import UC 1.0 15 | 16 | TextSwitch { 17 | property string key 18 | property bool defaultValue : null 19 | 20 | id : keyTextSwitch 21 | 22 | onKeyChanged : { 23 | // asynchronously set initial value based 24 | // on the modRana persistent dictionary 25 | // value 26 | if (keyTextSwitch.defaultValue == null) { 27 | rWin.log.warning("KeyTextSwitch for " + keyTextSwitch.key + " has no default value") 28 | } 29 | rWin.get(keyTextSwitch.key, keyTextSwitch.defaultValue, function(value) { 30 | if (value) { 31 | keyTextSwitch.checked = true 32 | } else { 33 | keyTextSwitch.checked = false 34 | } 35 | }) 36 | } 37 | 38 | onCheckedChanged : { 39 | if (keyTextSwitch.key) { 40 | // set the value of the persistent dictionary key 41 | // to the value of the selected item 42 | rWin.set(keyTextSwitch.key, keyTextSwitch.checked) 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /release_notes.txt: -------------------------------------------------------------------------------- 1 | # Mieru Markdown formatted release notes 2 | # syntax: http://en.wikipedia.org/wiki/Markdown 3 | # 4 | # NOTE: the version for the notes might not 100% correspond 5 | # to the version a feature was added, it basically means 6 | # what was added since & including version X 7 | ["metadata"] 8 | file_version = 1 9 | 10 | ["release_notes_section"] 11 | 12 | [[2.4.1]] 13 | notes=''' 14 | * manga mode 15 | * can be enabled in __Options__ 16 | * improved paging feedback 17 | * localization support 18 | * full __Czech__, __German__, __Russian__, __Chinese__, __Finnish__, __Vietnamese__, __Dutch__ and __French__ translations 19 | * improved donation support 20 | * __Flattr__ and __Bitcoin__ were added 21 | * new packages for _Maemo@N900_ 22 | * _screen middle_ click & doubleclick support 23 | * only active with _screen edges paging_ 24 | * independent actions for _click_ and _doubleclick_ 25 | * can be used to activate temporary page fitting modes 26 | * history improvements 27 | * can now be enabled/disabled from _Options_ 28 | * is now loaded when opening known files 29 | * __many thanks__ to: 30 | * __Tempura-san__ (l18n support, German translation, manga mode, paging feedback) 31 | * __ZoG__ (Russian translation) 32 | * __cjz__ (Chinese translation) 33 | * __mve__ (Finnish translation) 34 | * __hispaf__ (Vietnamese translation) 35 | * __Ade__ (Dutch translation) 36 | * __darodi__(French translation) 37 | * fixes: 38 | * fix previous/next chapters opening on wrong page 39 | * fix a history related crash at startup 40 | ''' -------------------------------------------------------------------------------- /gui/qml/modrana_components/PythonLog.qml: -------------------------------------------------------------------------------- 1 | //PythonLog.qml 2 | // A QML element that provides Python style logging to QML 3 | // a forwards the log messages to the Python log. 4 | 5 | import QtQuick 2.0 6 | 7 | Item { 8 | id : pythonLogger 9 | property bool backendAvailable : false 10 | 11 | function debug(message) { 12 | if (pythonLogger.backendAvailable) { 13 | rWin.python.call("modrana.gui.qml_log.debug", ["" + message]) 14 | } else { 15 | console.log("DEBUG: " + message) 16 | } 17 | } 18 | 19 | function info(message) { 20 | if (pythonLogger.backendAvailable) { 21 | rWin.python.call("modrana.gui.qml_log.info", ["" + message]) 22 | } else { 23 | console.log("INFO: " + message) 24 | } 25 | } 26 | 27 | function warning(message) { 28 | if (pythonLogger.backendAvailable) { 29 | rWin.python.call("modrana.gui.qml_log.warning", ["" + message]) 30 | } else { 31 | console.log("WARNING: " + message) 32 | } 33 | } 34 | 35 | function error(message) { 36 | if (pythonLogger.backendAvailable) { 37 | rWin.python.call("modrana.gui.qml_log.error", ["" + message]) 38 | } else { 39 | console.log("ERROR: " + message) 40 | } 41 | } 42 | 43 | function critical(message) { 44 | if (pythonLogger.backendAvailable) { 45 | rWin.python.call("modrana.gui.qml_log.critical", ["" + message]) 46 | } else { 47 | console.log("CRITICAL: " + message) 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /platforms/bb10.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Mieru hildon UI (for Maemo 5@N900) 4 | """ 5 | import glob 6 | import os 7 | from platforms.base_platform import BasePlatform 8 | 9 | 10 | class BB10(BasePlatform): 11 | def __init__(self, mieru): 12 | BasePlatform.__init__(self) 13 | self.mieru = mieru 14 | self._cleanCoreDumps() 15 | 16 | def getIDString(self): 17 | return "bb10" 18 | 19 | def getName(self): 20 | return "BlackBerry 10" 21 | 22 | def getDeviceName(self): 23 | return "BlackBerry 10 device" 24 | 25 | def notify(self, message, icon=""): 26 | self.mieru.gui._notify(message, icon) 27 | 28 | def getDefaultFileSelectorPath(self): 29 | """we default to the MyDocs folder as this is where most 30 | users will store their mangas and comic books""" 31 | return "/accounts/1000/shared/downloads" 32 | 33 | def showQuitButton(self): 34 | """Swype handles window closing""" 35 | return False 36 | 37 | def showMinimiseButton(self): 38 | """Swype handles window switching""" 39 | return False 40 | 41 | def getScreenWH(self): 42 | return 720, 1280 43 | 44 | def startInFullscreen(self): 45 | return True 46 | 47 | def _cleanCoreDumps(self): 48 | """due to experimental OpenGL support when using QtQuick with QBB_USE_OPENGL, 49 | there might sometimes be creates python3.2 core dumps even though the program cleanly exits 50 | -> this function cleans them on startup""" 51 | 52 | corePath = os.path.join(self.mieru.originalCWD, 'logs', '*.core') 53 | for core in glob.glob(corePath): 54 | try: 55 | os.remove(core) 56 | except Exception as e: 57 | print("removing core-dump failed") 58 | print(e) -------------------------------------------------------------------------------- /gui/qml/FileSelectorWrapper.qml: -------------------------------------------------------------------------------- 1 | //FileSelectorWrapper.qml 2 | // -> only loads the file selector dialog & it's model once needed 3 | // -> it should also get around the limitations of the FolderListModel, 4 | // that won't notice new files unless recreated 5 | 6 | import QtQuick 2.0 7 | 8 | Item { 9 | id : wrapper 10 | property bool dialogNeeded : false 11 | property string selectedFile 12 | property string initialPath : "." 13 | signal accepted(string selectedFile) 14 | Loader { 15 | id : fileSelectorLoader 16 | source : dialogNeeded ? "FileSelector.qml" : "" 17 | onLoaded : { 18 | // set the initial path & open the dialog 19 | item.down(initialPath) 20 | console.log("file selector loaded on: " + initialPath) 21 | item.open() 22 | } 23 | } 24 | Connections { 25 | target : fileSelectorLoader.item 26 | onAccepted : { 27 | console.log("wrapped file-selector accepted on path:") 28 | console.log(fileSelectorLoader.item.selectedFile) 29 | // trigger the onAccepted signal 30 | wrapper.accepted(fileSelectorLoader.item.selectedFile) 31 | // unload the dialog 32 | wrapper.dialogNeeded = false 33 | } 34 | onRejected : { 35 | console.log("wrapped file-selector rejected") 36 | // unload the dialog 37 | wrapper.dialogNeeded = false 38 | } 39 | 40 | } 41 | 42 | // remember the starting path 43 | function down(path) { 44 | initialPath = path 45 | } 46 | 47 | // open the dialog on the starting path 48 | function open() { 49 | dialogNeeded = true 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /gui/qml/universal_components/controls/UC/PageHeader.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 1.0 3 | import "style.js" as S 4 | 5 | Label { 6 | id : headerLabel 7 | property string title : "" 8 | text : title 9 | property int headerHeight : height/8.0 10 | property real backButtonW : menu ? headerHeight * 0.8 : headerHeight * 0.8 * 2 11 | property bool _fitsIn : (paintedWidth <= (parent.width-backButtonW+(40 * S.style.m))) 12 | anchors.verticalCenter : parent.verticalCenter 13 | x: _fitsIn ? 0 : backButtonW + 24 * S.style.m 14 | width : _fitsIn ? parent.width : parent.width - backButtonW - 40 * S.style.m 15 | anchors.right : parent.right 16 | anchors.topMargin : S.style.main.spacing 17 | anchors.bottomMargin : S.style.main.spacing 18 | font.pixelSize : 48 * S.style.m 19 | textFormat : Text.StyledText 20 | wrapMode : Text.NoWrap 21 | horizontalAlignment : _fitsIn ? Text.AlignHCenter : Text.AlignLeft 22 | property var menu : null 23 | signal _openMenu 24 | Button { 25 | visible : headerLabel.menu 26 | anchors.right : parent.right 27 | anchors.rightMargin : 8 * S.style.m 28 | anchors.verticalCenter : parent.verticalCenter 29 | width : backButtonW 30 | height : backButtonW 31 | Image { 32 | smooth : true 33 | source : "menu.svg" 34 | anchors.verticalCenter : parent.verticalCenter 35 | anchors.horizontalCenter : parent.horizontalCenter 36 | width : backButtonW * 0.6 37 | height : backButtonW * 0.6 38 | } 39 | onClicked : { 40 | if (headerLabel.menu) { 41 | headerLabel.menu.popup() 42 | } 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /gui/qml/universal_components/silica/UC/ApplicationWindow.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import Sailfish.Silica 1.0 3 | ApplicationWindow{ 4 | property bool inPortrait : _setOrientation(deviceOrientation) 5 | 6 | onDeviceOrientationChanged : { 7 | _setOrientation(deviceOrientation) 8 | } 9 | 10 | cover : null 11 | 12 | /* 13 | cover: CoverBackground { 14 | CoverPlaceholder { 15 | text: "modRana" 16 | } 17 | }*/ 18 | 19 | // this property is provided for API compatibility 20 | // as the Silica UC backend uses the Silica built-in 21 | // element sizing 22 | property int hiDPI : 0 23 | 24 | function _setOrientation(dOrient) { 25 | if (dOrient == Orientation.Portrait || 26 | dOrient == Orientation.PortraitInverted) { 27 | inPortrait = true 28 | } else { 29 | inPortrait = false 30 | } 31 | 32 | console.log("device orientation changed: " + deviceOrientation) 33 | 34 | /* BTW, other orientations are: 35 | Orientation.Landscape 36 | Orientation.LandscapeInverted 37 | */ 38 | } 39 | 40 | // the Silica ApplicationWindow 41 | // does not inherit the Window element, 42 | // so we need to add some properties 43 | // for a common API with Controls 44 | property string title 45 | property var visibility : 5 46 | 47 | function pushPage(pageInstance, pageProperties, animate) { 48 | var animateFlag 49 | if (animate) { 50 | animateFlag = PageStackAction.Animated 51 | } else { 52 | animateFlag = PageStackAction.Immediate 53 | } 54 | pageStack.push(pageInstance, pageProperties, animateFlag) 55 | return pageInstance 56 | } 57 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Mieru 2 | 3 | Mieru is a flexible manga and comic book reader. 4 | 5 | Feature overview: 6 | * touch friendly interface combined with handy key shortcuts 7 | * simple and intuitive interface layout 8 | * kinetic scrolling 9 | * all common formats (zip,rar,folder with images, ...) are supported 10 | * resume last open + history 11 | * automatic loading of next/previous chapters/volumes 12 | * multiple page fitting modes 13 | * powerful paging dialog 14 | * easily accessible configuration options 15 | 16 | Mieru@Maemo: 17 | * since version 1.0 18 | * uses Hildon widgets + Clutter page view 19 | * uses native notifications 20 | * portrait and autorotation support 21 | 22 | Mieru@Harmattan (N9, N950): 23 | * since version 2.0 24 | * uses QML and Qt Components 25 | * pinch zoom support 26 | * improved Open-file dialog (based on Cacheme QML) 27 | 28 | Note: 29 | The mobile interface is currently the most advanced one - the desktop interface is currently very simple, but many features can still be accessed using key shortcuts. 30 | 31 | 32 | Mieru + Qt/QML GUI 33 | 34 | Dependencies: 35 | Python 2.5+, Qt Components 1.0+, python-pyside.qtgui, python-pyside.qtdeclarative, python-magic, libmagic, rar and zip 36 | 37 | Installing 38 | * install all dependencies 39 | * run mieru.py -u "harmattan" 40 | * the -u parameter specifies the UI, "harmattan" is used due to the Harmattan GUI being the QML one 41 | 42 | 43 | Mieru + GTK/Clutter GUI 44 | 45 | Dependencies: 46 | Python 2.5+, clutter, PyClutter, PyClutter-GTK, libmagic, rar and zip 47 | 48 | Installing: 49 | * install all dependencies 50 | * run mieru.py 51 | * using the -u parameter, you can specify the UI, "pc" = desktop ui, "hildon" = Maemo 5 UI 52 | 53 | Package availability 54 | * Harmattan(N9/50): 55 | * Apps for Meego (http://apps.formeego.com) 56 | * Ovi store (pending QA) -------------------------------------------------------------------------------- /gui/qml/backend/Actions.qml: -------------------------------------------------------------------------------- 1 | // defines various actions and registers their triggers 2 | import QtQuick 2.0 3 | 4 | Item { 5 | id : actions 6 | 7 | property bool mediaKeysEnabled : false 8 | 9 | signal zoomUp 10 | signal zoomDown 11 | 12 | property var mediaKeys : null 13 | 14 | // we need to handle the case where the MediaKeys element 15 | // is not available so we use the Connections a nd Binding 16 | // elements to work with it 17 | 18 | Connections { 19 | // this condition setting null prevents warnings if the media 20 | // keys element is not available 21 | target : actions.mediaKeys ? actions.mediaKeys : null 22 | 23 | onUp : { 24 | actions.zoomUp() 25 | } 26 | 27 | onDown : { 28 | actions.zoomDown() 29 | } 30 | } 31 | 32 | Binding { 33 | // this condition setting null prevents warnings if the media 34 | // keys element is not available 35 | target : actions.mediaKeys ? actions.mediaKeys : null 36 | property : "enabled" 37 | value : actions.mediaKeysEnabled && Qt.application.active 38 | } 39 | 40 | Component.onCompleted : { 41 | initMediaKeys() 42 | } 43 | 44 | function initMediaKeys() { 45 | // the media keys module might not be available on 46 | // all platforms so we need to handle import failure 47 | // (real conditional imports would be nice, wouldn't they ;)) 48 | var mediaKeysInstance = rWin.loadQMLFile("sailfish_specific/SailfishMediaKeys.qml", true) 49 | if (mediaKeysInstance) { 50 | rWin.log.info("Actions: Sailfish media keys initialized") 51 | actions.mediaKeys = mediaKeysInstance 52 | } else { 53 | rWin.log.info("Actions: media keys not available") 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /gui/qml/universal_components/silica/UC/Popup.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import Sailfish.Silica 1.0 3 | 4 | MouseArea { 5 | id: popup 6 | anchors.top: parent.top 7 | anchors.horizontalCenter: parent.horizontalCenter 8 | width: parent.width 9 | height: message.paintedHeight + (Theme.paddingLarge * 2) 10 | property alias title: message.text 11 | property alias timeout: hideTimer.interval 12 | property alias background: bg.color 13 | visible: opacity > 0 14 | opacity: 0.0 15 | 16 | Behavior on opacity { 17 | FadeAnimation {} 18 | } 19 | 20 | Rectangle { 21 | id: bg 22 | anchors.fill: parent 23 | } 24 | 25 | Timer { 26 | id: hideTimer 27 | triggeredOnStart: false 28 | repeat: false 29 | interval: 5000 30 | onTriggered: popup.hide() 31 | } 32 | 33 | function hide() { 34 | if (hideTimer.running) 35 | hideTimer.stop() 36 | popup.opacity = 0.0 37 | } 38 | 39 | function show() { 40 | popup.opacity = 1.0 41 | hideTimer.restart() 42 | } 43 | 44 | function notify(text, color) { 45 | popup.title = text 46 | if (color && (typeof(color) != "undefined")) 47 | bg.color = color 48 | else 49 | bg.color = Theme.rgba(Theme.secondaryHighlightColor, 0.9) 50 | show() 51 | } 52 | 53 | Label { 54 | id: message 55 | anchors.verticalCenter: popup.verticalCenter 56 | font.pixelSize: 32 57 | anchors.left: parent.left 58 | anchors.leftMargin: Theme.paddingLarge 59 | anchors.right: parent.right 60 | anchors.rightMargin: Theme.paddingRight 61 | horizontalAlignment: Text.AlignHCenter 62 | elide: Text.ElideRight 63 | wrapMode: Text.Wrap 64 | } 65 | 66 | onClicked: hide() 67 | } -------------------------------------------------------------------------------- /gui/qml/backend/Cron.qml: -------------------------------------------------------------------------------- 1 | // Runtime timer management for use from Python 2 | // 3 | // Python does not have an easy access to the Qt 5 main loop, so provide 4 | // an interface for it to easy register timer on the QML side. 5 | // The timers can be added, removed or their timeout changed. 6 | // When a timer triggers, it sends a signal with ist timer id to Python 7 | // over PyOtherSide, which is then handled by the modRana cron module and 8 | // translated to the Python callback that was set when the timer was registered 9 | // on the Python side. 10 | 11 | import QtQuick 2.0 12 | 13 | Item { 14 | id : cron 15 | property var timers : new Object() 16 | 17 | function addTimer(timerId, timeout) { 18 | // add a timer with the given timeout, start it and add it to the 19 | // timer tracking dictionary 20 | var newTimer = Qt.createQmlObject('import QtQuick 2.0; DynamicTimer {}', cron) 21 | newTimer.timerId = timerId 22 | newTimer.interval = timeout 23 | cron.timers[timerId] = newTimer 24 | rWin.log.debug("cron: timer added, timeout: " + timeout + " id: " + timerId) 25 | } 26 | 27 | function removeTimer(timerId) { 28 | // stop a timer and remove it from the timer tracking dictionary 29 | cron.timers[timerId].stop() 30 | delete cron.timers[timerId] 31 | rWin.log.debug("cron: timer removed: " + timerId) 32 | } 33 | 34 | function modifyTimerTimeout(self, timeoutId, newTimeout) { 35 | // modify the duration of a timer in progress""" 36 | cron.timers[timerId].interval = newTimeout 37 | rWin.log.debug("cron: timer " + timerId + " has new timeout: " + newTimeout) 38 | } 39 | 40 | Component.onCompleted : { 41 | rWin.python.setHandler("addTimer", addTimer) 42 | rWin.python.setHandler("removeTimer", removeTimer) 43 | rWin.python.setHandler("modifyTimerTimeout", modifyTimerTimeout) 44 | } 45 | } -------------------------------------------------------------------------------- /providers/progressive_download.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | downloading with progress information 4 | based on: 5 | http://stackoverflow.com/questions/2028517/python-urllib2-progress-hook 6 | """ 7 | import sys 8 | 9 | try: # Python 2 10 | from urllib2 import urlopen, HTTPError, URLError 11 | except ImportError: # Python 3 12 | from urllib.request import urlopen 13 | from urllib.error import HTTPError, URLError 14 | 15 | def _chunk_report(bytes_so_far, chunk_size, total_size): 16 | if total_size: 17 | percent = float(bytes_so_far) / total_size 18 | percent = round(percent*100, 2) 19 | sys.stdout.write("Downloaded %d of %d bytes (%0.2f%%)\r" % 20 | (bytes_so_far, total_size, percent)) 21 | 22 | if bytes_so_far >= total_size: 23 | sys.stdout.write('\n') 24 | else: 25 | sys.stdout.write("Downloaded %d bytes\r" % bytes_so_far) 26 | 27 | 28 | def _chunk_download(url, path, chunk_size=8192, report_hook=None): 29 | """ 30 | response hook = None -> select response hook automatically 31 | response hook = False -> do not use response hook 32 | """ 33 | response = urlopen(url) 34 | total_size = response.info()['Content-Length'] 35 | if total_size: 36 | total_size = total_size.strip() 37 | total_size = int(total_size) 38 | bytes_so_far = 0 39 | 40 | f = open(path, "wb") 41 | 42 | while 1: 43 | chunk = response.read(chunk_size) 44 | bytes_so_far += len(chunk) 45 | f.write(chunk) 46 | 47 | if not chunk: 48 | break 49 | 50 | if report_hook is None: 51 | _chunk_report(bytes_so_far, chunk_size, total_size) 52 | f.close() 53 | 54 | return bytes_so_far 55 | 56 | def download(url, path, report_hook=None, verbose=True): 57 | if verbose: 58 | print(url) 59 | _chunk_download(url, path, report_hook=None) 60 | 61 | 62 | if __name__ == '__main__': 63 | download("http://www.modrana.org/om2012/mobile_os_om2012.odp", "test.odp") 64 | 65 | -------------------------------------------------------------------------------- /gui/qml/universal_components/controls/UC/Popup.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import "style.js" as S 3 | 4 | MouseArea { 5 | id: popup 6 | anchors.top: parent.top 7 | anchors.horizontalCenter: parent.horizontalCenter 8 | width: parent.width 9 | height: message.paintedHeight + (S.style.main.spacingBig * 1) 10 | property alias title: message.text 11 | property alias timeout: hideTimer.interval 12 | property alias background: bg.color 13 | visible: opacity > 0 14 | opacity: 0.0 15 | 16 | property color _defaultColor : "orange" 17 | 18 | Behavior on opacity { 19 | // the FadeAnimation silica equals to this 20 | NumberAnimation { 21 | duration: 200 22 | easing.type: Easing.InOutQuad 23 | property: "opacity" 24 | } 25 | } 26 | 27 | Rectangle { 28 | id: bg 29 | anchors.fill: parent 30 | } 31 | 32 | Timer { 33 | id: hideTimer 34 | triggeredOnStart: false 35 | repeat: false 36 | interval: 5000 37 | onTriggered: popup.hide() 38 | } 39 | 40 | function hide() { 41 | if (hideTimer.running) 42 | hideTimer.stop() 43 | popup.opacity = 0.0 44 | } 45 | 46 | function show() { 47 | popup.opacity = 1.0 48 | hideTimer.restart() 49 | } 50 | 51 | function notify(text, color) { 52 | popup.title = text 53 | if (color && (typeof(color) != "undefined")) 54 | bg.color = color 55 | else 56 | bg.color = Qt.rgba(_defaultColor.r, _defaultColor.g, _defaultColor.b, 0.9) 57 | show() 58 | } 59 | 60 | Label { 61 | id: message 62 | anchors.verticalCenter: popup.verticalCenter 63 | font.pixelSize: 32 64 | anchors.left: parent.left 65 | anchors.leftMargin: S.style.spacingBig 66 | anchors.right: parent.right 67 | anchors.rightMargin: S.style.spacing 68 | horizontalAlignment: Text.AlignHCenter 69 | elide: Text.ElideRight 70 | wrapMode: Text.Wrap 71 | } 72 | 73 | onClicked: hide() 74 | } 75 | -------------------------------------------------------------------------------- /gui/qml/HeaderDialog.qml: -------------------------------------------------------------------------------- 1 | //HeaderDialog.qml 2 | 3 | import QtQuick 2.0 4 | import UC 1.0 5 | 6 | Dialog { 7 | id : headerDialog 8 | width : parent.width - 30 9 | property Style platformStyle: SelectionDialogStyle {} 10 | property string titleText: "Bitcoin address" 11 | title: Item { 12 | id: header 13 | height: headerDialog.platformStyle.titleBarHeight 14 | anchors.left: parent.left 15 | anchors.right: parent.right 16 | anchors.top: parent.top 17 | anchors.bottom: parent.bottom 18 | Item { 19 | id: labelField 20 | anchors.fill: parent 21 | Item { 22 | id: labelWrapper 23 | anchors.left: parent.left 24 | anchors.right: closeButton.left 25 | anchors.bottom: parent.bottom 26 | anchors.bottomMargin: headerDialog.platformStyle.titleBarLineMargin 27 | height: titleLabel.height 28 | Label { 29 | id: titleLabel 30 | x: headerDialog.platformStyle.titleBarIndent 31 | width: parent.width - closeButton.width 32 | font: headerDialog.platformStyle.titleBarFont 33 | color: headerDialog.platformStyle.commonLabelColor 34 | elide: headerDialog.platformStyle.titleElideMode 35 | text: headerDialog.titleText 36 | } 37 | } 38 | Image { 39 | id: closeButton 40 | anchors.verticalCenter : labelWrapper.verticalCenter 41 | anchors.right: labelField.right 42 | opacity: closeButtonArea.pressed ? 0.5 : 1.0 43 | source: "image://theme/icon-m-common-dialog-close" 44 | MouseArea { 45 | id: closeButtonArea 46 | anchors.fill: parent 47 | onClicked: {headerDialog.reject();} 48 | } 49 | } 50 | } 51 | Rectangle { 52 | id: headerLine 53 | anchors.left: parent.left 54 | anchors.right: parent.right 55 | anchors.bottom: header.bottom 56 | height: 1 57 | color: "#4D4D4D" 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /gui/qml/universal_components/controls/UC/ComboBox.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 1.0 3 | import "style.js" as S 4 | 5 | Column { 6 | id : comboColumn 7 | property alias label : comboLabel.text 8 | property alias model : cBox.model 9 | property alias item : cBox.item 10 | property alias currentItem : cBox.currentItem 11 | property alias currentIndex : cBox.currentIndex 12 | property string description : "" 13 | width : parent.width 14 | 15 | Row { 16 | // no spacing if the label is empty, so that the combobox 17 | // can be used as a standalone label-less combobox 18 | spacing : comboLabel.text ? S.style.main.spacing : 0 19 | property var bar : S.foo 20 | Label { 21 | id : comboLabel 22 | } 23 | ComboBox { 24 | id : cBox 25 | anchors.verticalCenter : comboLabel.verticalCenter 26 | // selected item, only assigned if user 27 | // clicks on an item in the context menu, 28 | // not if changing the current item index 29 | property var item 30 | // changes active item 31 | // without triggering the 32 | // the on current item changed signal 33 | property int currentItem 34 | 35 | property bool _skipNext : false 36 | 37 | onModelChanged : { 38 | _skipNext = true 39 | } 40 | 41 | onCurrentItemChanged : { 42 | // skip next onCurrentIndexChanged 43 | _skipNext = true 44 | currentIndex = currentItem 45 | } 46 | 47 | onCurrentIndexChanged: { 48 | // currentIndex is changed if a new model 49 | // is assigned, so we need to ignore the signal 50 | // once every time a new model is assigned 51 | if (_skipNext) { 52 | _skipNext = false 53 | } else { 54 | // assign selected item to the item 55 | // property, so that the onItemChanged 56 | // signal is triggered 57 | cBox.item = cBox.model.get(currentIndex) 58 | } 59 | } 60 | } 61 | } 62 | Label { 63 | text : comboColumn.description 64 | wrapMode : Text.Wrap 65 | width : parent.width 66 | font.italic : true 67 | } 68 | } -------------------------------------------------------------------------------- /gui/qml/modrana_components/KeyComboBox.qml: -------------------------------------------------------------------------------- 1 | //KeyComboBox.qml 2 | // 3 | // ModRana uses an persistent dictionary 4 | // to configure a lot of stuff, so there will 5 | // be a lot of comboboxes in options, most of them 6 | // mapped on a key in the persistent dict & it's 7 | // current value. 8 | // KeyComboBox makes it possible to just assign the 9 | // key property and the combobox will asynchronously 10 | // fetch the value if the key from Python, 11 | // go over all ListElements in the ListModel and set 12 | // itemIndex based on the index of the found element. 13 | // If no element is found, currentIndex is set to null 14 | // Also, if setValue is true, set the persistent dictionary 15 | // key to the value of the clicked item. 16 | // 17 | // NOTE: The the ListItems used need to have an attribute 18 | // named "value" for both these features to work. 19 | 20 | import QtQuick 2.0 21 | import UC 1.0 22 | 23 | ComboBox { 24 | property string key 25 | property string value 26 | property var defaultValue 27 | property bool setValue : true 28 | 29 | id : keyCombo 30 | 31 | onKeyChanged : { 32 | // asynchronously set initial value based 33 | // on the modRana persistent dictionary 34 | // value 35 | // TODO: skip the for loop if no key is found ? 36 | // (will probably need something like get_exists 37 | // function in GUI module & rWin, that only runs the callback 38 | // if the key exists in the dictionary) 39 | rWin.get(keyCombo.key, keyCombo.defaultValue, function(value) { 40 | for(var i = 0; keyCombo.model.count; i++) { 41 | // check if the element has the 42 | // value we got from the persistent dict 43 | if (keyCombo.model.get(i).value == value) { 44 | // matching value found, set index & return 45 | keyCombo.currentIndex = i 46 | keyCombo.value = value 47 | break 48 | return 49 | } 50 | } 51 | // we went over all elements without finding a match, 52 | // set currentIndex to null 53 | keyCombo.currentIndex = null 54 | }) 55 | } 56 | 57 | onItemChanged : { 58 | if (keyCombo.setValue && keyCombo.key) { 59 | // set the value of the persistent dictionary key 60 | // to the value of the selected item 61 | rWin.set(keyCombo.key, item.value) 62 | keyCombo.value = item.value 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /gui/qml/modrana_components/MIconButton.qml: -------------------------------------------------------------------------------- 1 | //IconButton.qml 2 | // A simple button with an icon in the middle. 3 | 4 | import QtQuick 2.0 5 | import UC 1.0 6 | 7 | Rectangle { 8 | id : icb 9 | height : iconSize 10 | width : iconSize 11 | property real iconSize : rWin.c.style.button.icon.size 12 | property real margin : rWin.c.style.main.spacing 13 | property alias iconName : themedIcon.iconName 14 | property color normalColor : rWin.theme.color.icon_button_normal 15 | property color toggledColor : rWin.theme.color.icon_button_toggled 16 | property color notEnabledColor : "lightgray" 17 | property bool checkable : false 18 | property bool checked : false 19 | property alias text : ibLabel.text 20 | 21 | color : normalColor 22 | 23 | // "#c6d1f3" QML toggled 24 | // modRana theme: 25 | // #3c60fa outline 26 | // "#92aaf3" fill 27 | // "#00004d" main text 28 | radius : 10 29 | smooth : true 30 | signal clicked 31 | signal pressAndHold 32 | 33 | onEnabledChanged : { 34 | if (icb.enabled) { 35 | checked ? icb.color = toggledColor : icb.color = normalColor 36 | } else { 37 | icb.color = notEnabledColor 38 | } 39 | } 40 | 41 | onCheckedChanged : { 42 | if (icb.enabled) { 43 | checked ? icb.color = toggledColor : icb.color = normalColor 44 | } 45 | } 46 | 47 | TIcon { 48 | id: themedIcon 49 | anchors.horizontalCenter : parent.horizontalCenter 50 | anchors.top : parent.top 51 | anchors.bottom : parent.bottom 52 | anchors.topMargin : icb.margin 53 | anchors.bottomMargin : icb.margin 54 | width : parent.width-icb.margin 55 | height : parent.height-icb.margin 56 | } 57 | 58 | Label { 59 | id : ibLabel 60 | anchors.verticalCenter : parent.verticalCenter 61 | anchors.left : parent.left 62 | anchors.leftMargin : rWin.c.style.main.spacing 63 | anchors.right : parent.right 64 | anchors.rightMargin : rWin.c.style.main.spacing 65 | elide : Text.ElideRight 66 | horizontalAlignment : Text.AlignHCenter 67 | } 68 | 69 | MouseArea { 70 | anchors.fill : parent 71 | enabled : icb.enabled 72 | onClicked: { 73 | icb.clicked() 74 | if (icb.checkable) { 75 | icb.checked = !icb.checked 76 | } 77 | } 78 | 79 | onPressedChanged : { 80 | pressed ? icb.color = toggledColor : icb.color = normalColor 81 | } 82 | 83 | onPressAndHold : { 84 | icb.pressAndHold() 85 | } 86 | 87 | } 88 | 89 | } -------------------------------------------------------------------------------- /gui/qml/backend/KeepAlive.qml: -------------------------------------------------------------------------------- 1 | // Keep alive control - keep device awake even if it would normally suspend. 2 | // This functionality is needed so that features like track logging, batch tile downloading 3 | // or turn by turn navigation work correctly. 4 | 5 | import QtQuick 2.0 6 | 7 | Item { 8 | id : stillAlive 9 | 10 | property var keepAliveInstance : null 11 | property var _wakeLocks : [] 12 | property bool keepAlive : false 13 | property bool available : keepAliveInstance != null 14 | 15 | onKeepAliveChanged : { 16 | if (stillAlive.keepAliveInstance != null) { 17 | stillAlive.keepAliveInstance.enabled = keepAlive 18 | if (keepAlive) { 19 | rWin.log.info("KeepAlive: keep alive enabled") 20 | } else { 21 | rWin.log.info("KeepAlive: keep alive disabled") 22 | } 23 | } 24 | } 25 | 26 | Component.onCompleted : { 27 | initKeepAlive() 28 | } 29 | 30 | function initKeepAlive() { 31 | // the keep-alive control module might not be available on 32 | // all platforms so we need to handle import failure 33 | // (real conditional imports would be nice, wouldn't they ;)) 34 | // TODO: handle also other platforms than Sailfish OS 35 | var sailfishKeepAliveInstance = rWin.loadQMLFile("sailfish_specific/SailfishKeepAlive.qml", true) 36 | if (sailfishKeepAliveInstance) { 37 | rWin.log.info("KeepAlive: keep alive control initialized") 38 | stillAlive.keepAliveInstance = sailfishKeepAliveInstance 39 | } else { 40 | rWin.log.info("KeepAlive: keep alive control is not available") 41 | } 42 | } 43 | 44 | function addWakeLock(wakeLockId) { 45 | rWin.log.info("KeepAlive: adding wake lock: " + wakeLockId) 46 | // check if the wake lock has already been registered 47 | var wakeLockIndex = stillAlive._wakeLocks.indexOf(wakeLockId) 48 | if (wakeLockIndex != -1) { 49 | rWin.log.warning("KeepAlive: wake lock " + wakeLockId + "already registered") 50 | } else { 51 | stillAlive._wakeLocks.push(wakeLockId) 52 | if (!stillAlive.keepAlive) { 53 | stillAlive.keepAlive = true 54 | } 55 | } 56 | } 57 | 58 | function removeWakeLock(wakeLockId) { 59 | var wakeLockIndex = _wakeLocks.indexOf(wakeLockId) 60 | if (wakeLockIndex == -1) { 61 | rWin.log.warning("KeepAlive: can't remove unknown wake lock: " + wakeLockId) 62 | } else { 63 | // lets hope this is always only called from a single thread ;-) 64 | stillAlive._wakeLocks.splice(wakeLockIndex,1) 65 | // disable keep alive in no wake locks remain 66 | if (stillAlive._wakeLocks.length == 0) { 67 | stillAlive.keepAlive = false 68 | } 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /gui/gui.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """a GUI chooser""" 3 | 4 | class GUI: 5 | def __init__(self, mieru): 6 | self.mieru = mieru 7 | 8 | def resize(self, w, h): 9 | """resize the GUI to given width and height""" 10 | pass 11 | 12 | def getWindow(self): 13 | """return the main window""" 14 | pass 15 | 16 | def getViewport(self): 17 | """return a (x,y,w,h) tuple""" 18 | pass 19 | 20 | def setWindowTitle(self, title): 21 | """set the window title to a given string""" 22 | pass 23 | 24 | def getToolkit(self): 25 | """report which toolkit the current GUI uses""" 26 | return 27 | 28 | def getAccel(self): 29 | """report if current GUI supports acceleration""" 30 | pass 31 | 32 | def toggleFullscreen(self): 33 | pass 34 | 35 | def startMainLoop(self): 36 | """start the main loop or its equivalent""" 37 | pass 38 | 39 | def stopMainLoop(self): 40 | """stop the main loop or its equivalent""" 41 | pass 42 | 43 | def showPreview(self, page, direction, onPressAction): 44 | """show a preview for a page""" 45 | pass 46 | 47 | def hidePreview(self): 48 | """hide any visible previews""" 49 | pass 50 | 51 | def getPage(self, flObject, name="", fitOnStart=True): 52 | """create a page from a file like object""" 53 | pass 54 | 55 | def showPage(self, page, mangaInstance=None, id=None): 56 | """show a page on the stage""" 57 | pass 58 | 59 | def getCurrentPage(self): 60 | """return the page that is currently shown 61 | if there is no page, return None""" 62 | pass 63 | 64 | def pageShownNotify(self, cb): 65 | """call the callback once a page is shown 66 | -> some large jpeg pages can take while to load""" 67 | pass 68 | 69 | def clearStage(self): 70 | pass 71 | 72 | def idleAdd(self, callback, *args): 73 | pass 74 | 75 | def statusReport(self): 76 | """report current status of the gui""" 77 | return "It works!" 78 | 79 | def newActiveManga(self, manga): 80 | """this is a new manga instance reporting that it has been loaded 81 | NOTE: this can be the first manga loaded at startup or a new manga instance 82 | replacing a previous one""" 83 | pass 84 | 85 | def getScale(self): 86 | """get current scale""" 87 | return None 88 | 89 | def getUpperLeftShift(self): 90 | return None 91 | 92 | def _destroyed(self): 93 | self.mieru.destroy() 94 | 95 | def _keyPressed(self, keyName): 96 | self.mieru.keyPressed(keyName) 97 | 98 | 99 | def getGui(mieru, type="QML",accel=True, size=(800,480)): 100 | """return a GUI object""" 101 | if type=="hildon" and accel: 102 | import cluttergtk 103 | import clutter_gui 104 | return clutter_gui.ClutterGTKGUI(mieru, type, size) 105 | elif type=="QML" and accel: 106 | import qml_gui 107 | return qml_gui.QMLGUI(mieru, type, size) 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /gui/qml/modrana_components/HeaderPage.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import UC 1.0 3 | 4 | /* base page, includes: 5 | * a header, with a 6 | * back button 7 | * page name 8 | * optional tools button 9 | useful for things like track logging menus, 10 | about menus, compass pages, point pages, etc. 11 | */ 12 | 13 | Page { 14 | property alias content : contentField.children 15 | property alias contentParent : contentField 16 | property alias headerContent : hContent.children 17 | property alias headerWidth : header.width 18 | property alias headerOpacity : header.opacity 19 | property alias backButtonWidth : backButton.width 20 | property int headerHeight : rWin.headerHeight 21 | property int bottomPadding : 0 22 | property real availableHeight : parent.height - bottomPadding - headerHeight 23 | property alias isFlickable : pageFlickable.interactive 24 | // TODO: reenable scroll decorator 25 | /* 26 | ScrollDecorator { 27 | id: scrolldecorator 28 | flickableItem: pageFlickable 29 | } 30 | */ 31 | 32 | /* 33 | Rectangle { 34 | id : background 35 | color : "white" 36 | anchors.fill : parent 37 | //visible : false 38 | } 39 | */ 40 | 41 | Flickable { 42 | id : pageFlickable 43 | anchors.fill: parent 44 | contentWidth: parent.width 45 | contentHeight: (headerHeight + contentField.childrenRect.height + bottomPadding) 46 | //flickableDirection: Flickable.VerticalFlick 47 | VerticalScrollDecorator {} 48 | Item { 49 | id : contentField 50 | anchors.top : header.bottom 51 | //height : childrenRect.height 52 | anchors.bottom : parent.bottom 53 | anchors.left : parent.left 54 | anchors.right : parent.right 55 | } 56 | PageHeaderBackground { 57 | id : header 58 | Item { 59 | id : hContent 60 | width : rWin.platform.needsBackButton ? 61 | headerWidth - backButton.width - rWin.c.style.main.spacingBig : 62 | headerWidth 63 | anchors.right : parent.right 64 | anchors.top : parent.top 65 | height : headerHeight 66 | } 67 | } 68 | } 69 | MIconButton { 70 | id : backButton 71 | width : headerHeight * 0.8 72 | height : headerHeight * 0.8 73 | anchors.top : parent.top 74 | anchors.left : parent.left 75 | anchors.topMargin : rWin.c.style.main.spacing 76 | anchors.leftMargin : rWin.c.style.main.spacingBig 77 | iconName : "left_thin.png" 78 | //iconSource : "image://icons/"+ rWin.theme_id +"/back_small.png" 79 | opacity : pageFlickable.atYBeginning ? 1.0 : 0.55 80 | visible : rWin.showBackButton 81 | onClicked : { 82 | rWin.pageStack.pop(undefined, !rWin.animate) 83 | } 84 | 85 | onPressAndHold : { 86 | rWin.pageStack.pop(null, !rWin.animate) 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /platforms/base_platform.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """this is a "abstract" class defining the API for platform modules 3 | NOTE: this is not just API, some multi-platform implementations are there too 4 | """ 5 | 6 | class BasePlatform: 7 | def __init__(self): 8 | pass 9 | 10 | def guiModuleLoaded(self): 11 | """notify the device module that the GUI module 12 | has been loaded""" 13 | pass 14 | 15 | def getName(self): 16 | return "unknown platform" 17 | 18 | def getDeviceName(self): 19 | return "unknown device" 20 | 21 | def getIDString(self): 22 | """ 23 | get a unique string identifier for a platform module 24 | """ 25 | return None 26 | 27 | def getScreenWH(self): 28 | return 800, 480 29 | 30 | def hasPagingKeys(self): 31 | """report if the device has has some buttons usable for paging""" 32 | return False 33 | 34 | def startChooser(self, type): 35 | """start a file/folder chooser dialog""" 36 | pass 37 | 38 | def handleKeyPress(self, keyName): 39 | """handle a key press event and return True if the key was "consumed" or 40 | "False" if it wasn't""" 41 | return False 42 | 43 | def notify(self, message, icon): 44 | """show a notification, if possible""" 45 | pass 46 | 47 | def showPagingDialogCB(self, button): 48 | """for showing paging dialog from button CB""" 49 | self.showPagingDialog() 50 | 51 | def showPagingDialog(self): 52 | pass 53 | 54 | def pagingDialogBeforeOpen(self): 55 | """do something before opening the paging dialog""" 56 | pass 57 | 58 | def showInfo(self): 59 | """show/witch to the options info""" 60 | pass 61 | 62 | def showOptions(self): 63 | """show/witch to the options window""" 64 | pass 65 | 66 | def minimize(self): 67 | """minimize the main window""" 68 | pass 69 | 70 | def showMinimiseButton(self): 71 | """ 72 | report if a window minimise button needs to be shown somewhere in the 73 | application managed UI 74 | """ 75 | return True 76 | 77 | def showQuitButton(self): 78 | """ 79 | report if a quit button needs to be shown somewhere in the 80 | application managed UI 81 | """ 82 | return True 83 | 84 | def getDefaultFileSelectorPath(self): 85 | """a fail-safe path for the file/folder selector on its first opening""" 86 | return '/' 87 | 88 | # GTK specific 89 | 90 | def startInFullscreen(self): 91 | """"should Mieru start in fullscreen on this platform ?""" 92 | return True 93 | 94 | def getSupportedGUIModuleIds(self): 95 | """ 96 | supported GUI module IDs, ordered by preference from left to right 97 | (the most-preferred should be on the left) 98 | """ 99 | return ["QML"] # as default try GTK first and then QML 100 | 101 | # def Button(self, label=""): 102 | # """return classic GTK button""" 103 | # return gtk.Button(label) 104 | # 105 | # def CheckButton(self, label=""): 106 | # """return classic GTK check button""" 107 | # return gtk.CheckButton(label) 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /gui/qml/WhatsNewDialog.qml: -------------------------------------------------------------------------------- 1 | //whatsNewDialog.qml 2 | 3 | import QtQuick 2.0 4 | import UC 1.0 5 | 6 | HeaderDialog { 7 | id : whatsNewDialog 8 | titleText : "What's new in Mieru " + readingState.getNumericVersionString() 9 | property string releaseNotesText : "" 10 | content:Item { 11 | id: dialogContent 12 | height : rootWindow.inPortrait ? rootWindow.width * 0.90 : rootWindow.height * 0.85 13 | anchors.left : parent.left 14 | anchors.right : parent.right 15 | Flickable { 16 | id : notesFlickable 17 | anchors.top : parent.top 18 | anchors.topMargin : 8 19 | width : parent.width 20 | anchors.bottom : lowerBlock.top 21 | anchors.bottomMargin : 8 22 | clip : true 23 | contentHeight : releaseNotes.height 24 | contentWidth : parent.width 25 | interactive : releaseNotes.height > height 26 | Label { 27 | id : releaseNotes 28 | anchors.left : parent.left 29 | anchors.leftMargin : 4 30 | width : parent.width-12 31 | text : releaseNotesText 32 | wrapMode : Text.WordWrap 33 | color : "white" 34 | onLinkActivated : { 35 | rootWindow.notify(qsTr("Opening link")) 36 | Qt.openUrlExternally(link) 37 | } 38 | } 39 | } 40 | ScrollDecorator { 41 | flickableItem: notesFlickable 42 | } 43 | Item { 44 | id : lowerBlock 45 | height : childrenRect.height 46 | anchors.left : parent.left 47 | anchors.right : parent.right 48 | anchors.bottom : parent.bottom 49 | anchors.horizontalCenter : parent.horizontalCenter 50 | Rectangle { 51 | id: line 52 | color : "#4D4D4D" 53 | height : 1 54 | anchors { 55 | left: parent.left 56 | right : parent.right 57 | bottom : donationLabel.top 58 | bottomMargin : 8 59 | } 60 | } 61 | Label { 62 | id : donationLabel 63 | anchors.bottom : donationButton.top 64 | anchors.bottomMargin : 16 65 | anchors.horizontalCenter : parent.horizontalCenter 66 | text : qsTr("Do you like Mieru ? Donate !") 67 | color : "white" 68 | } 69 | Button { 70 | id : donationButton 71 | anchors.bottom : hideButton.top 72 | anchors.bottomMargin : 24 73 | anchors.horizontalCenter : parent.horizontalCenter 74 | text : qsTr("How to donate ?") 75 | onClicked : { 76 | whatsNewDialog.close() 77 | donationDialog.open() 78 | } 79 | } 80 | Button { 81 | id : hideButton 82 | //anchors.top : donationButton.bottom 83 | anchors.bottom : parent.bottom 84 | anchors.horizontalCenter : parent.horizontalCenter 85 | text : qsTr("Don't show again") 86 | onClicked : { 87 | whatsNewDialog.close() 88 | readingState.disableReleaseNotesForCurrentVersion() 89 | } 90 | } 91 | } 92 | } 93 | DonationDialog { 94 | id : donationDialog 95 | } 96 | } -------------------------------------------------------------------------------- /platforms/pc.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """an ui/device/platform module targeted on desktop PCs""" 3 | 4 | import gtk 5 | 6 | from base_platform import BasePlatform 7 | 8 | class PC(BasePlatform): 9 | def __init__(self, mieru): 10 | BasePlatform.__init__(self) 11 | self.mieru = mieru 12 | # self.mb = self._addMenu() 13 | 14 | def hasPagingKeys(self): 15 | """keyboard support""" 16 | return True 17 | 18 | def startInFullscreen(self): 19 | """Mieru should start in window on desktop""" 20 | return False 21 | 22 | def startChooser(self, type): 23 | if type == "folder": 24 | t = gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER 25 | else: # type == "file" 26 | t = gtk.FILE_CHOOSER_ACTION_OPEN 27 | 28 | buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK) 29 | dialog = gtk.FileChooserDialog("Open file",self.mieru.window, t, buttons=buttons) 30 | lastFolder = self.mieru.get('lastChooserFolder', None) 31 | currentFolder = None 32 | selectedPath = None 33 | if lastFolder: 34 | dialog.set_current_folder(lastFolder) 35 | status = dialog.run() 36 | dialog.hide() 37 | if status == gtk.RESPONSE_OK: 38 | currentFolder = dialog.get_current_folder() 39 | selectedPath = dialog.get_filename() 40 | dialog.destroy() 41 | if currentFolder is not None: 42 | self.mieru.set('lastChooserFolder', currentFolder) 43 | if selectedPath: 44 | print("open") 45 | self.mieru.openManga(selectedPath) 46 | 47 | def startChooserCB(self, button, type): 48 | self.startChooser(type) 49 | 50 | def _addMenu(self): 51 | """add main menu""" 52 | 53 | if self.mieru.gui.getToolkit() == "GTK": 54 | mvbox = self.mieru.gui.getVbox() 55 | window = self.mieru.gui.getWindow() 56 | mb = gtk.MenuBar() 57 | filemenu = gtk.Menu() 58 | filem = gtk.MenuItem("_File") 59 | filem.set_submenu(filemenu) 60 | 61 | agr = gtk.AccelGroup() 62 | window.add_accel_group(agr) 63 | 64 | openm = gtk.ImageMenuItem(gtk.STOCK_OPEN, agr) 65 | key, mod = gtk.accelerator_parse("O") 66 | openm.add_accelerator("activate", agr, key, 67 | mod, gtk.ACCEL_VISIBLE) 68 | filemenu.append(openm) 69 | openm.connect("activate", self.startChooserCB, "file") 70 | 71 | sep = gtk.SeparatorMenuItem() 72 | filemenu.append(sep) 73 | 74 | exit = gtk.ImageMenuItem(gtk.STOCK_QUIT, agr) 75 | key, mod = gtk.accelerator_parse("Q") 76 | exit.add_accelerator("activate", agr, key, 77 | mod, gtk.ACCEL_VISIBLE) 78 | 79 | exit.connect("activate", gtk.main_quit) 80 | 81 | filemenu.append(exit) 82 | 83 | mb.append(filem) 84 | 85 | mvbox.pack_start(mb, False, False, 0) 86 | 87 | # hide the menu bar when in fullscreen 88 | 89 | # connect window state CB 90 | window.connect('window-state-event', self._fullscreenCB) 91 | 92 | mvbox.show_all() 93 | 94 | return mb 95 | 96 | 97 | def _fullscreenCB(self, window, event): 98 | """hide the menu bar when in fullscreen""" 99 | if event.changed_mask & gtk.gdk.WINDOW_STATE_FULLSCREEN: 100 | fullscreen = bool(event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN) 101 | if fullscreen: 102 | if self.mb: 103 | self.mb.hide() 104 | else: 105 | if self.mb: 106 | self.mb.show() 107 | 108 | def minimize(self): 109 | """minimize the main window""" 110 | self.mieru.getWindow().iconify() 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /gui/qml/modrana_components/IconGridPage.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import UC 1.0 3 | 4 | Page { 5 | id : iconGP 6 | //signal buttonClicked(string menu) 7 | property alias model : gridView.model 8 | property int headerHeight : rWin.platform.needsBackButton ? 0 : 100 9 | property int hIcons : rWin.inPortrait ? 2 : 4 10 | property double iconMargin : width/(hIcons*10) 11 | property double iconSize : (width-2)/hIcons 12 | property alias isMockup : mockup.visible 13 | property bool hasBackButton : false 14 | // search, routes, POI, mode, options, info 15 | 16 | // page background 17 | Rectangle { 18 | anchors.fill : parent 19 | color : rWin.theme.color.page_background 20 | visible : rWin.platform.needsPageBackground 21 | } 22 | 23 | function getPage(menu) { 24 | return rWin.getPage(menu) 25 | } 26 | 27 | // main flickable with icon grid 28 | GridView { 29 | id : gridView 30 | anchors.fill : parent 31 | anchors.topMargin : iconGP.iconMargin/4.0 + iconGP.headerHeight 32 | //anchors.margins : iconGP.iconMargin 33 | cellHeight : iconGP.iconSize 34 | cellWidth : iconGP.iconSize 35 | 36 | // default empty list model 37 | model : ListModel { 38 | } 39 | 40 | 41 | delegate : IconGridButton { 42 | // handle place-holders 43 | visible : icon != "" 44 | iconName : icon 45 | text : caption 46 | iconSize : iconGP.iconSize 47 | margin : iconGP.iconMargin 48 | onClicked : { 49 | //send the button clicked signal 50 | //iconGP.buttonClicked.send(menu) 51 | rWin.pushPageInstance(iconGP.getPage(menu)) 52 | } 53 | } 54 | 55 | //insert the back arrow 56 | Component.onCompleted: { 57 | if (rWin.showBackButton && !iconGP.hasBackButton) { 58 | iconGP.model.insert(0, {"caption": "", "icon":"", "menu":""}) 59 | iconGP.hasBackButton = true 60 | } 61 | } 62 | 63 | Connections { 64 | target : rWin 65 | onShowBackButtonChanged : { 66 | if (rWin.showBackButton && !iconGP.hasBackButton) { 67 | // add back button 68 | iconGP.model.insert(0, {"caption": "", "icon":"", "menu":""}) 69 | iconGP.hasBackButton = true 70 | } 71 | if (!rWin.showBackButton && iconGP.hasBackButton) { 72 | // remove the back buttons 73 | iconGP.model.remove(0) 74 | iconGP.hasBackButton = false 75 | } 76 | 77 | } 78 | } 79 | } 80 | 81 | // main "escape" button 82 | 83 | IconGridButton { 84 | iconSize : iconGP.iconSize 85 | margin : iconGP.iconMargin 86 | anchors.top : gridView.top 87 | anchors.left : parent.left 88 | iconName : "left_thin.png" 89 | text : "back" 90 | opacity : gridView.atYBeginning ? 1.0 : 0.55 91 | visible : rWin.showBackButton 92 | onClicked : { 93 | rWin.pageStack.pop(undefined,!rWin.animate) 94 | } 95 | onPressAndHold : { 96 | rWin.pageStack.pop(null,!rWin.animate) 97 | } 98 | } 99 | 100 | Rectangle { 101 | anchors.verticalCenter : parent.verticalCenter 102 | anchors.horizontalCenter : parent.horizontalCenter 103 | id : mockup 104 | visible : false 105 | opacity : 0.7 106 | color: "grey" 107 | Text { 108 | anchors.verticalCenter : parent.verticalCenter 109 | anchors.horizontalCenter : parent.horizontalCenter 110 | font.pixelSize : 64 * rWin.c.style.m 111 | text : "MOCKUP" 112 | color:"white" 113 | } 114 | } 115 | } -------------------------------------------------------------------------------- /gui/qml/universal_components/README.md: -------------------------------------------------------------------------------- 1 | Universal Components 2 | ==================== 3 | 4 | The Universal Components provide a QtQuick 2.0 component set 5 | with a single interface that can use different backends. 6 | 7 | Like application GUI code can be used written once and used 8 | everywhere where one of the supported backends is available. 9 | 10 | The backends take care of a native look, so the application 11 | should not only run on the given platform but also look 12 | reasonably good. 13 | 14 | Backends 15 | -------- 16 | 17 | * QtQuick Controls - fully supported 18 | 19 | The QtQuick Controls are part of Qt 5 since 5.1 and are a 20 | fully supported UC backend. Therefore any application 21 | using UC should run with just Qt 5.1+ being available. 22 | 23 | * Sailfish Silica - fully supported 24 | 25 | The default component set of Sailfish OS by Jolla. 26 | 27 | * Nemo Mobile Glacier - planed 28 | 29 | 30 | How does it work 31 | ---------------- 32 | 33 | Universal Components provide a unified interface with all supported backends. 34 | This is represented by a qmldir file and the elements themselves. 35 | 36 | All individual backends have the same qmldirs and the same elements that 37 | also provide the same properties and function. 38 | 39 | All platform specific functionality that is deemed worthwhile & doable 40 | is reimplemented in the other backends so that it can be part of the 41 | unified interface. 42 | 43 | 44 | Usage 45 | ----- 46 | 47 | First you need to make sure the UC directory you want to use is in your 48 | QML plugin search path. Uhen just import the UC plugin and use the provided 49 | elements: 50 | 51 | import UC 1.0 52 | 53 | If you can't manipulate your QML plugin import path, you can also import the 54 | UC plugin directly: 55 | 56 | import "./UC" 57 | 58 | 59 | Component usage notes 60 | --------------------- 61 | 62 | **PlatformFlickable** and **PlatformListView** 63 | 64 | These two components provide access to enhanced platform specific Flickables 65 | and ListViews (SilicaListView has fast scroll support, etc.). 66 | With backends that don't have such enhancements a normal Flickable or ListView 67 | is used. 68 | 69 | **TopMenu** 70 | 71 | The TopMenu element provides a multi platform menu that will generally be shown 72 | somewhere at the top of a Page using the appropriate native presentation method. 73 | Currently this translates to a PullDownMenu with with the Silica backend and to 74 | a Menu in popup mode with Controls. In the future the advanced Glacier pull down 75 | menu should also be supported. 76 | 77 | The easiest way to use the TopMenu is to place PageHeader on top of your Page 78 | and assing the TopMenu into its *menu* property: 79 | 80 | ```QML 81 | import UC 1.0 82 | Page { 83 | PageHeader { 84 | anchors.top : parent.top 85 | menu : TopMenu { 86 | MenuItem { 87 | text : "option 1" 88 | onClicked : {console.log("1 clicked!")} 89 | } 90 | MenuItem { 91 | text : "option 2" 92 | onClicked : {console.log("2 clicked!")} 93 | } 94 | } 95 | } 96 | } 97 | ``` 98 | 99 | The top menu makes sure that the TopMenu can be activated when needed, either in a 100 | platform specific way (pull down gesture with Silica) or by showing a button 101 | (with Controls). 102 | 103 | The TopMenu can be also used inside the PlatformFlickable or PlatformListView, 104 | but users will need to provide custom triggering for the TopMenu (calling its popup() method) 105 | when not using the Silica backend. 106 | 107 | 108 | Applications using Universal Components 109 | ------------------------------------- 110 | 111 | * [modRana flexible navigation system](https://github.com/M4rtinK/modrana) 112 | 113 | 114 | TODO 115 | ---- 116 | 117 | * Glacier backend 118 | 119 | 120 | LICENSE 121 | ------- 122 | 123 | Universal Components are distributed under the terms of the [3-clause BSD license](http://opensource.org/licenses/BSD-3-Clause). 124 | -------------------------------------------------------------------------------- /gui/qml/HistoryPage.qml: -------------------------------------------------------------------------------- 1 | //HistoryPage.qml 2 | import QtQuick 2.0 3 | import UC 1.0 4 | 5 | Page { 6 | id : historyPage 7 | property bool deleteModeEnabled : false 8 | 9 | // update the history list model at startup 10 | Component.onCompleted : readingState.updateHistoryListModel() 11 | 12 | tools: ToolBarLayout { 13 | ToolIcon { 14 | id : histBack 15 | iconId : "toolbar-back" 16 | onClicked : pageStack.pop() 17 | } 18 | ToolButton { 19 | text : qsTr("Delete") 20 | visible : deleteModeEnabled 21 | // FIXME: buttons are not aligned vertically in toolbar 22 | anchors.verticalCenter : histBack.verticalCenter 23 | onClicked: { 24 | historyListModel.removeChecked() 25 | historyPage.deleteModeEnabled = false 26 | readingState.updateHistoryListModel() 27 | } 28 | } 29 | ToolButton { 30 | text: qsTr("Cancel") 31 | visible : deleteModeEnabled 32 | // FIXME: buttons are not aligned vertically in toolbar 33 | anchors.verticalCenter : histBack.verticalCenter 34 | onClicked: { 35 | rootWindow.abortnotify() 36 | historyListModel.uncheckAll() 37 | historyPage.deleteModeEnabled = false 38 | readingState.updateHistoryListModel() 39 | } 40 | } 41 | ToolIcon { 42 | id : histMenu 43 | iconId : "toolbar-view-menu" 44 | onClicked : historyMenu.open() 45 | } 46 | } 47 | 48 | Menu { 49 | id : historyMenu 50 | 51 | MenuLayout { 52 | MenuItem { 53 | text : historyPage.deleteModeEnabled ? qsTr("Do not delete items") : qsTr("Delete items") 54 | onClicked : { 55 | historyPage.deleteModeEnabled = !historyPage.deleteModeEnabled 56 | rootWindow.notify(qsTr("Select items to delete")) 57 | } 58 | } 59 | MenuItem { 60 | text : qsTr("Erase history") 61 | onClicked : { 62 | eraseHistoryDialog.open() 63 | } 64 | } 65 | } 66 | } 67 | 68 | ListView { 69 | id: historyList 70 | anchors.fill : parent 71 | Label { 72 | visible : (historyList.count == 0) ? true : false 73 | text : "" + qsTr("no entries") + "" 74 | horizontalAlignment : Text.AlignHCenter 75 | anchors.verticalCenter : historyList.verticalCenter 76 | width : parent.width 77 | height : 80 78 | } 79 | model: historyListModel 80 | 81 | delegate: Component { 82 | Rectangle { 83 | width: historyList.width 84 | height: 80 85 | color: model.thing.checked?"#00B8F5":(index%2?"#eee":"#ddd") 86 | Label { 87 | id: title 88 | elide: Text.ElideRight 89 | text: model.thing.name 90 | color: (model.thing.checked?"white":"black") 91 | font.bold: true 92 | anchors.leftMargin: 10 93 | anchors.fill: parent 94 | verticalAlignment: Text.AlignVCenter 95 | } 96 | MouseArea { 97 | anchors.fill: parent 98 | onClicked: { 99 | if (historyPage.deleteModeEnabled) { 100 | historyListController.toggled(historyListModel, model.thing) 101 | } 102 | else { 103 | pageStack.pop() 104 | historyListController.thingSelected(model.thing) 105 | } 106 | } 107 | } 108 | } 109 | } 110 | } 111 | 112 | QueryDialog { 113 | id : eraseHistoryDialog 114 | titleText : qsTr("Erase history") 115 | message : qsTr("Do you want to erase the history of all mangas and comic books opened by Mieru?") 116 | acceptButtonText : qsTr("Erase") 117 | rejectButtonText : qsTr("Cancel") 118 | onAccepted : { 119 | readingState.eraseHistory() 120 | readingState.updateHistoryListModel() 121 | } 122 | } 123 | } -------------------------------------------------------------------------------- /gui/qml/modrana_components/IconGridButton.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import UC 1.0 3 | 4 | Item { 5 | id : icgb 6 | property real margin : 0 7 | property real iconSize : rWin.c.style.button.iconGrid.size 8 | property alias iconName : themedIcon.iconName 9 | property color normalColor : rWin.theme.color.main_fill 10 | property color toggledColor : rWin.theme.color.icon_grid_toggled 11 | property alias sensitive : mouseArea.enabled 12 | property string text : "" 13 | signal clicked 14 | signal pressAndHold 15 | 16 | //scale : mouseArea.pressed ? 0.9 : 1.0 17 | 18 | width : iconSize 19 | height : iconSize 20 | 21 | state: "RELEASED" 22 | 23 | // background 24 | Rectangle { 25 | id : background 26 | anchors.horizontalCenter : icgb.horizontalCenter 27 | anchors.margins : icgb.margin/2.0 28 | width : icgb.iconSize-icgb.margin/2.0 29 | height : icgb.iconSize-icgb.margin/2.0 30 | // TODO: get color from theme 31 | // TODO: slightly darker (themable ?) pressed color ? 32 | //property real darking : mouseArea.pressed ? 1.5 : 1.0 33 | //color : Qt.darker("#92aaf3", darking) 34 | radius : rWin.c.style.button.iconGrid.radius 35 | smooth : true 36 | // icon 37 | TIcon { 38 | id: themedIcon 39 | anchors.horizontalCenter : parent.horizontalCenter 40 | anchors.top : parent.top 41 | anchors.bottom : iconLabel.top 42 | iconName : icon 43 | anchors.topMargin : icgb.margin 44 | anchors.bottomMargin : icgb.margin/2.0 45 | width : parent.width-icgb.margin*1.5 46 | height : parent.height-icgb.margin*1.5 47 | } 48 | // caption 49 | Label { 50 | smooth : true 51 | id : iconLabel 52 | text : icgb.text 53 | color : rWin.theme.color.icon_button_text 54 | font.pixelSize : rWin.inPortrait ? 42 * rWin.c.style.m : 36 * rWin.c.style.m 55 | anchors.horizontalCenter : parent.horizontalCenter 56 | anchors.bottom : parent.bottom 57 | anchors.bottomMargin : icgb.margin/2 58 | property int desiredWidth : background.width-icgb.margin/2 59 | scale: paintedWidth > desiredWidth ? (desiredWidth / paintedWidth) : 1 60 | //TODO: find out why the scaled text looks kinda blurry 61 | } 62 | } 63 | MouseArea { 64 | id: mouseArea 65 | anchors.fill: parent 66 | onClicked: icgb.clicked() 67 | //TODO: investigate onPressed transitions 68 | // e.q. precludes consistent back button behaviour 69 | //onPressed: icgb.clicked() 70 | onPressedChanged: { 71 | pressed ? icgb.state = "PRESSED" : icgb.state = "RELEASED" 72 | } 73 | onPressAndHold : { 74 | icgb.pressAndHold() 75 | } 76 | //onReleased: icgb.state = "RELEASED" 77 | } 78 | 79 | // pressed/released animation 80 | states: [ 81 | State { 82 | name: "PRESSED" 83 | PropertyChanges { target: background; color: toggledColor; scale : 0.9} 84 | PropertyChanges { target: iconLabel; font.bold : true} 85 | }, 86 | State { 87 | name: "RELEASED" 88 | PropertyChanges { target: background; color: normalColor; scale : 1.0} 89 | PropertyChanges { target: iconLabel; font.bold : false} 90 | } 91 | ] 92 | 93 | transitions: [ 94 | Transition { 95 | from: "PRESSED" 96 | to: "RELEASED" 97 | ColorAnimation { target: background; duration: 100*rWin.animate } 98 | NumberAnimation { properties : "scale"; easing.type : Easing.InOutQuad; duration : 100*rWin.animate } 99 | }, 100 | Transition { 101 | from: "RELEASED" 102 | to: "PRESSED" 103 | ColorAnimation { target: background; duration: 100*rWin.animate } 104 | NumberAnimation { properties : "scale"; easing.type : Easing.InOutQuad; duration : 100*rWin.animate } 105 | } 106 | ] 107 | } -------------------------------------------------------------------------------- /gui/qml/main.qml: -------------------------------------------------------------------------------- 1 | //main.qml 2 | import QtQuick 2.0 3 | import UC 1.0 4 | 5 | PageStackWindow { 6 | id : rootWindow 7 | anchors.fill : parent 8 | initialPage : MainView { id : mainView } 9 | 10 | property bool enableMangaMode : options.get("QMLMangaMode", false) 11 | property string statsText : "" 12 | 13 | // disable switch & close buttons on Fremantle 14 | // -> has no effect on other platforms 15 | property bool allowSwitch : false 16 | property bool allowClose : false 17 | 18 | // TODO: replace hardcoded value with actual status bar height 19 | property int statusBarHeight : 36 20 | 21 | showStatusBar : options.get("QMLShowStatusBar", false) 22 | showToolBar : options.get("QMLRememberToolbarState", false) ? options.get("QMLToolbarState", true) : true 23 | 24 | function showPage(path, pageId) { 25 | mainView.showPage(path, pageId) 26 | } 27 | 28 | function setPageNumber(pageNumber) { 29 | mainView.pageNumber = pageNumber 30 | } 31 | 32 | function setMaxPageNumber(maxPageNumber) { 33 | mainView.maxPageNumber = maxPageNumber 34 | } 35 | 36 | // open a page and push it in the stack 37 | function openFile(file) { 38 | // create the Qt component based on the file/qml page to load. 39 | var component = Qt.createComponent(file) 40 | 41 | // if the page is ready to be managed it is pushed onto the stack 42 | if (component.status == Component.Ready) 43 | pageStack.push(component); 44 | else 45 | console.log("Error loading: " + component.errorString()) 46 | } 47 | 48 | // handle Mieru shutdown 49 | function shutdown() { 50 | mainView.shutdown() 51 | } 52 | 53 | // Open a dialog with information about how to turn pages 54 | function openFirstStartDialog() { 55 | firstStartDialog.open() 56 | } 57 | 58 | // Open a dialog with information about what's new 59 | function openReleaseNotesDialog() { 60 | // load release notes text 61 | whatsNewDialog.releaseNotesText = readingState.getReleaseNotes() 62 | // open the dialog 63 | whatsNewDialog.open() 64 | } 65 | 66 | FileSelectorWrapper { 67 | id : fileSelector 68 | onAccepted : { 69 | readingState.openManga(selectedFile) 70 | } 71 | } 72 | 73 | PageFitSelector { 74 | id : pageFitSelector 75 | onAccepted : mainView.setPageFitMode(pageFitMode) 76 | } 77 | 78 | PageFitSelector { 79 | id : tempPageFitSelectorClick 80 | onAccepted : mainView.setPageFitModeTemp(pageFitMode, "click") 81 | Component.onCompleted : addNop() 82 | } 83 | PageFitSelector { 84 | id : tempPageFitSelectorDoubleclick 85 | onAccepted : mainView.setPageFitModeTemp(pageFitMode, "doubleclick") 86 | Component.onCompleted : addNop() 87 | } 88 | function notify(text) { 89 | console.log("notification: " % text) 90 | notification.text = text; 91 | notification.show() 92 | } 93 | function abortnotify() { 94 | notification.hide() 95 | } 96 | 97 | // First start dialog 98 | QueryDialog { 99 | id : firstStartDialog 100 | icon : "image://icons/mieru.svg" 101 | titleText : qsTr("How to turn pages") 102 | message : qsTr("Tap the right half of the screen to go to the next page.") + "

" 103 | + qsTr("Tap the left half to go to the previous page.") 104 | acceptButtonText : qsTr("Don't show again") 105 | rejectButtonText : qsTr("OK") 106 | onAccepted : { 107 | options.set("QMLShowFirstStartDialog", false) 108 | } 109 | } 110 | 111 | // What's new dialog 112 | WhatsNewDialog { 113 | id : whatsNewDialog 114 | } 115 | 116 | InfoBanner { 117 | id : notification 118 | timerShowTime : 5000 119 | height : rootWindow.height / 5.0 120 | // add margin and prevent overlapping with status bar, if the bar is visible 121 | y : rootWindow.showStatusBar ? rootWindow.statusBarHeight + 8 : 8 122 | } 123 | 124 | } -------------------------------------------------------------------------------- /gui/qml/BitcoinButton.qml: -------------------------------------------------------------------------------- 1 | //BitcoinButton.qml 2 | 3 | import QtQuick 2.0 4 | import UC 1.0 5 | 6 | Rectangle { 7 | id : bitcoinButton 8 | color : bitcoinMA.pressed ? "silver" : "black" 9 | border.width : showBorder ? 2 : 0 10 | border.color : "white" 11 | smooth : true 12 | radius : 25 13 | width : 210 14 | height : 50 15 | property string url : "" 16 | property bool showBorder : false 17 | 18 | 19 | Label { 20 | anchors.horizontalCenter : parent.horizontalCenter 21 | anchors.verticalCenter : parent.verticalCenter 22 | font.family: "Arial" 23 | font.pixelSize : 24 24 | text : "

Bitcoin

" 25 | color : bitcoinMA.pressed ? "black" : "white" 26 | } 27 | MouseArea { 28 | id : bitcoinMA 29 | anchors.fill : parent 30 | onClicked : { 31 | console.log('Bitcoin button clicked') 32 | bitcoinDialog.open() 33 | } 34 | } 35 | Dialog { 36 | id : bitcoinDialog 37 | width : parent.width - 30 38 | property Style platformStyle : SelectionDialogStyle {} 39 | property string titleText : qsTr("Bitcoin address") 40 | title: Item { 41 | id: header 42 | height: bitcoinDialog.platformStyle.titleBarHeight 43 | anchors.left : parent.left 44 | anchors.right : parent.right 45 | anchors.top : parent.top 46 | anchors.bottom : parent.bottom 47 | Item { 48 | id: labelField 49 | anchors.fill: parent 50 | Item { 51 | id: labelWrapper 52 | anchors.left : parent.left 53 | anchors.right : closeButton.left 54 | anchors.bottom : parent.bottom 55 | anchors.bottomMargin : bitcoinDialog.platformStyle.titleBarLineMargin 56 | height : titleLabel.height 57 | Label { 58 | id: titleLabel 59 | x: bitcoinDialog.platformStyle.titleBarIndent 60 | width : parent.width - closeButton.width 61 | font : bitcoinDialog.platformStyle.titleBarFont 62 | color : bitcoinDialog.platformStyle.commonLabelColor 63 | elide : bitcoinDialog.platformStyle.titleElideMode 64 | text : bitcoinDialog.titleText 65 | } 66 | 67 | } 68 | Image { 69 | id: closeButton 70 | anchors.verticalCenter : labelWrapper.verticalCenter 71 | anchors.right : labelField.right 72 | opacity : closeButtonArea.pressed ? 0.5 : 1.0 73 | source : "image://theme/icon-m-toolbar-close-selected" 74 | width : 64 75 | height : 64 76 | MouseArea { 77 | id : closeButtonArea 78 | anchors.fill : parent 79 | onClicked : bitcoinDialog.reject() 80 | } 81 | } 82 | } 83 | Rectangle { 84 | id: headerLine 85 | anchors.left : parent.left 86 | anchors.right : parent.right 87 | anchors.bottom : header.bottom 88 | height : 1 89 | color : "#4D4D4D" 90 | } 91 | } 92 | content:Item { 93 | id: dialogContent 94 | width : parent.width 95 | height : bitcoinQrCode.height + urlField.height + 32 96 | Image { 97 | id : bitcoinQrCode 98 | anchors.top : dialogContent.top 99 | anchors.topMargin : 16 100 | anchors.horizontalCenter : parent.horizontalCenter 101 | source : "image://icons/qrcode_bitcoin.png" 102 | } 103 | TextField { 104 | id : urlField 105 | anchors.top : bitcoinQrCode.bottom 106 | anchors.topMargin : 16 107 | anchors.left : parent.left 108 | anchors.right : parent.right 109 | //anchors.horizontalCenter : parent.horizontalCenter 110 | font.pointSize : 18 111 | height : 80 112 | text : bitcoinButton.url 113 | } 114 | Button { 115 | anchors.top : urlField.bottom 116 | anchors.topMargin : 12 117 | anchors.horizontalCenter : parent.horizontalCenter 118 | text: qsTr("Copy address") 119 | iconSource : "image://theme/icon-m-toolbar-cut-paste" 120 | onClicked: { 121 | urlField.selectAll() 122 | urlField.copy() 123 | rootWindow.notify(qsTr("Bitcoin address copied to clipboard")) 124 | } 125 | } 126 | } 127 | } 128 | } 129 | 130 | 131 | -------------------------------------------------------------------------------- /gui/qml/universal_components/controls/UC/menu.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 44 | 46 | 47 | 49 | image/svg+xml 50 | 52 | 53 | 54 | 55 | 56 | 61 | 65 | 70 | 73 | 76 | 80 | 86 | 87 | 88 | 89 | 90 | 95 | 98 | 101 | 105 | 111 | 112 | 113 | 114 | 115 | 120 | 123 | 126 | 130 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | -------------------------------------------------------------------------------- /gui/qml/modrana_components/SearchPage.qml: -------------------------------------------------------------------------------- 1 | //SearchPage.qml 2 | 3 | import QtQuick 2.0 4 | import UC 1.0 5 | import ".." 6 | import "../functions.js" as F 7 | 8 | HeaderPage { 9 | id: searchPage 10 | // search type id 11 | property string searchId : "" 12 | property string pageHeader : "" 13 | property string _searchResultId : "search:result:" + searchId 14 | property string _searchStatusId : "search:status:" + searchId 15 | // persistent dict key that should be used to store and retrieve 16 | // last used search query 17 | height : rWin.c.style.button.generic.height 18 | property string lastSearchKey : "" 19 | property bool _searchInProgress : false 20 | property string _searchStatus : "" 21 | property string _searchThreadId : "" 22 | property bool showNavigationIndicator : false 23 | 24 | function search (query) { 25 | rWin.python.call("modrana.gui.search.search", [searchPage.searchId, query], function(v) { 26 | rWin.log.info("searching for: " + query + " using " + searchPage.searchId) 27 | searchPage._searchThreadId = v 28 | searchPage._searchInProgress = true 29 | }) 30 | } 31 | 32 | Component.onCompleted : { 33 | // connect to the status & result callbacks 34 | rWin.python.setHandler(searchPage._searchStatusId, function(v){ 35 | rWin.log.info("search status: " + v) 36 | searchPage._searchStatus = v 37 | }) 38 | rWin.python.setHandler(searchPage._searchResultId, function(results){ 39 | rWin.log.info("search result: " + results) 40 | // load the results into a list model 41 | // (for some reason just assigning it does not work) 42 | pointLW.model.clear() 43 | for (var i=0; i" + model.name + " (" + resultDelegate.distanceString + ")" 183 | } 184 | Label { 185 | text : model.description 186 | wrapMode : Text.WordWrap 187 | width : resultDelegate.width - rWin.c.style.main.spacingBig*2 188 | } 189 | } 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /platforms/maemo5_autorotation.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # gPodder - A media aggregator and podcast client 4 | # Copyright (c) 2005-2010 Thomas Perl and the gPodder Team 5 | # 6 | # gPodder is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # gPodder is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | # 19 | 20 | import dbus 21 | import dbus.glib 22 | 23 | import hildon 24 | import osso 25 | 26 | # Replace this with your own gettext() functionality 27 | #import gpodder 28 | #_ = gpodder.gettext 29 | #_ = "_" 30 | 31 | 32 | class FremantleRotation(object): 33 | """thp's screen rotation for Maemo 5 34 | 35 | Simply instantiate an object of this class and let it auto-rotate 36 | your StackableWindows depending on the device orientation. 37 | 38 | If you need to relayout a window, connect to its "configure-event" 39 | signal and measure the ratio of width/height and relayout for that. 40 | 41 | You can set the mode for rotation to AUTOMATIC (default), NEVER or 42 | ALWAYS with the set_mode() method. 43 | """ 44 | AUTOMATIC, NEVER, ALWAYS = range(3) 45 | 46 | # Human-readable captions for the above constants 47 | MODE_CAPTIONS = ('Automatic', 'Landscape', 'Portrait') 48 | 49 | # Privately-used constants 50 | _PORTRAIT, _LANDSCAPE = ('portrait', 'landscape') 51 | _ENABLE_ACCEL = 'req_accelerometer_enable' 52 | _DISABLE_ACCEL = 'req_accelerometer_disable' 53 | 54 | # Defined in mce/dbus-names.h 55 | _MCE_SERVICE = 'com.nokia.mce' 56 | _MCE_REQUEST_PATH = '/com/nokia/mce/request' 57 | _MCE_REQUEST_IF = 'com.nokia.mce.request' 58 | 59 | # sysfs device name for the keyboard slider switch 60 | KBD_SLIDER = '/sys/devices/platform/gpio-switch/slide/state' 61 | _KBD_OPEN = 'open' 62 | _KBD_CLOSED = 'closed' 63 | 64 | def __init__(self, app_name, main_window=None, version='1.0', mode=0): 65 | """Create a new rotation manager 66 | 67 | app_name ... The name of your application (for osso.Context) 68 | main_window ... The root window (optional, hildon.StackableWindow) 69 | version ... The version of your application (optional, string) 70 | mode ... Initial mode for this manager (default: AUTOMATIC) 71 | """ 72 | self._orientation = None 73 | self._main_window = main_window 74 | self._stack = hildon.WindowStack.get_default() 75 | self._mode = -1 76 | self._last_dbus_orientation = None 77 | self._keyboard_state = self._get_keyboard_state() 78 | app_id = '-'.join((app_name, self.__class__.__name__)) 79 | self._osso_context = osso.Context(app_id, version, False) 80 | program = hildon.Program.get_instance() 81 | program.connect('notify::is-topmost', self._on_topmost_changed) 82 | system_bus = dbus.Bus.get_system() 83 | system_bus.add_signal_receiver(self._on_orientation_signal, \ 84 | signal_name='sig_device_orientation_ind', \ 85 | dbus_interface='com.nokia.mce.signal', \ 86 | path='/com/nokia/mce/signal') 87 | 88 | 89 | system_bus.add_signal_receiver(self._on_keyboard_signal, \ 90 | signal_name='Condition', \ 91 | dbus_interface='org.freedesktop.Hal.Device', \ 92 | path='/org/freedesktop/Hal/devices/platform_slide') 93 | self.set_mode(mode) 94 | 95 | """check for current orientation - the first signal comes after and orientation change, 96 | so if the device is already in portrait when the app is launched, it won't 97 | rotate to the correct orintation before being oriented to landscape and back 98 | """ 99 | mceRequest = system_bus.get_object('com.nokia.mce','/com/nokia/mce/request') 100 | dir(mceRequest) 101 | orientationInfo = mceRequest.get_device_orientation() 102 | # send a fake CB with current orientation info 103 | self._on_orientation_signal(*orientationInfo) 104 | 105 | def get_mode(self): 106 | """Get the currently-set rotation mode 107 | 108 | This will return one of three values: AUTOMATIC, ALWAYS or NEVER. 109 | """ 110 | return self._mode 111 | 112 | def set_mode(self, new_mode): 113 | """Set the rotation mode 114 | 115 | You can set the rotation mode to AUTOMATIC (use hardware rotation 116 | info), ALWAYS (force portrait) and NEVER (force landscape). 117 | """ 118 | if new_mode not in (self.AUTOMATIC, self.ALWAYS, self.NEVER): 119 | raise ValueError('Unknown rotation mode') 120 | 121 | if self._mode != new_mode: 122 | if self._mode == self.AUTOMATIC: 123 | # Remember the current "automatic" orientation for later 124 | self._last_dbus_orientation = self._orientation 125 | # Tell MCE that we don't need the accelerometer anymore 126 | self._send_mce_request(self._DISABLE_ACCEL) 127 | 128 | if new_mode == self.NEVER: 129 | self._orientation_changed(self._LANDSCAPE) 130 | elif new_mode == self.ALWAYS and \ 131 | self._keyboard_state != self._KBD_OPEN: 132 | self._orientation_changed(self._PORTRAIT) 133 | elif new_mode == self.AUTOMATIC: 134 | # Restore the last-known "automatic" orientation 135 | self._orientation_changed(self._last_dbus_orientation) 136 | # Tell MCE that we need the accelerometer again 137 | self._send_mce_request(self._ENABLE_ACCEL) 138 | 139 | self._mode = new_mode 140 | 141 | def _send_mce_request(self, request): 142 | rpc = osso.Rpc(self._osso_context) 143 | rpc.rpc_run(self._MCE_SERVICE, \ 144 | self._MCE_REQUEST_PATH, \ 145 | self._MCE_REQUEST_IF, \ 146 | request, \ 147 | use_system_bus=True) 148 | 149 | def _on_topmost_changed(self, program, property_spec): 150 | # XXX: This seems to never get called on Fremantle(?) 151 | if self._mode == self.AUTOMATIC: 152 | if program.get_is_topmost(): 153 | self._send_mce_request(self._ENABLE_ACCEL) 154 | else: 155 | self._send_mce_request(self._DISABLE_ACCEL) 156 | 157 | def _get_main_window(self): 158 | if self._main_window: 159 | # If we have gotten the main window as parameter, return it and 160 | # don't try "harder" to find another window using the stack 161 | return self._main_window 162 | else: 163 | # The main window is at the "bottom" of the window stack, and as 164 | # the list we get with get_windows() is sorted "topmost first", we 165 | # simply take the last item of the list to get our main window 166 | windows = self._stack.get_windows() 167 | if windows: 168 | return windows[-1] 169 | else: 170 | return None 171 | 172 | def _orientation_changed(self, orientation): 173 | if self._orientation == orientation: 174 | # Ignore repeated requests 175 | return 176 | 177 | flags = 0 178 | 179 | if orientation != self._LANDSCAPE: 180 | flags |= hildon.PORTRAIT_MODE_SUPPORT 181 | 182 | if orientation == self._PORTRAIT: 183 | flags |= hildon.PORTRAIT_MODE_REQUEST 184 | 185 | window = self._get_main_window() 186 | if window is not None: 187 | hildon.hildon_gtk_window_set_portrait_flags(window, flags) 188 | 189 | self._orientation = orientation 190 | 191 | def _get_keyboard_state(self): 192 | # For sbox, if the device does not exist assume that it's closed 193 | try: 194 | return open(self.KBD_SLIDER).read().strip() 195 | except IOError: 196 | return self._KBD_CLOSED 197 | 198 | def _keyboard_state_changed(self): 199 | state = self._get_keyboard_state() 200 | 201 | if state == self._KBD_OPEN: 202 | self._orientation_changed(self._LANDSCAPE) 203 | elif state == self._KBD_CLOSED: 204 | if self._mode == self.AUTOMATIC: 205 | self._orientation_changed(self._last_dbus_orientation) 206 | elif self._mode == self.ALWAYS: 207 | self._orientation_changed(self._PORTRAIT) 208 | 209 | self._keyboard_state = state 210 | 211 | def _on_keyboard_signal(self, condition, button_name): 212 | if condition == 'ButtonPressed' and button_name == 'cover': 213 | self._keyboard_state_changed() 214 | 215 | def _on_orientation_signal(self, orientation, stand, face, x, y, z): 216 | if orientation in (self._PORTRAIT, self._LANDSCAPE): 217 | if self._mode == self.AUTOMATIC and \ 218 | self._keyboard_state != self._KBD_OPEN: 219 | # Automatically set the rotation based on hardware orientation 220 | self._orientation_changed(orientation) 221 | 222 | # Save the current orientation for "automatic" mode later on 223 | self._last_dbus_orientation = orientation 224 | 225 | -------------------------------------------------------------------------------- /gui/qml/OptionsPage.qml: -------------------------------------------------------------------------------- 1 | //OptionPage.qml 2 | import QtQuick 2.0 3 | import UC 1.0 4 | 5 | Page { 6 | id : optionsPage 7 | anchors.fill : parent 8 | anchors.topMargin : 15 9 | anchors.bottomMargin : 15 10 | anchors.leftMargin : 15 11 | anchors.rightMargin : 15 12 | 13 | property bool showReleaseNotes : options.get("showReleaseNotes", true) 14 | property bool fileSelectorCheckHistory : options.get("fileSelectorCheckHistory", true) 15 | property bool historyEnabled : options.get("historyEnabled", true) 16 | 17 | Flickable { 18 | anchors.fill : parent 19 | contentWidth : optionsPage.width 20 | contentHeight: optionsColumn.height 21 | Column { 22 | id : optionsColumn 23 | spacing : 30 24 | width : optionsPage.width 25 | 26 | LineText { 27 | width : optionsPage.width 28 | text : qsTr("Page view") 29 | } 30 | Label { 31 | text : "" + qsTr("Rotation") + "" 32 | } 33 | ButtonRow { 34 | // synchronise with current orientation lock 35 | Component.onCompleted : { 36 | if (mainView.orientationLock == PageOrientation.Automatic) { 37 | checkedButton = bAuto 38 | } else if (mainView.orientationLock == PageOrientation.LockPortrait) { 39 | checkedButton = bPortrait 40 | } else { 41 | checkedButton = bLandscape 42 | } 43 | } 44 | Button { 45 | id : bAuto 46 | text : qsTr("auto") 47 | onClicked : { 48 | options.set("QMLmainViewRotation", "auto") 49 | mainView.orientationLock = PageOrientation.Automatic 50 | } 51 | } 52 | Button { 53 | id : bPortrait 54 | text : qsTr("portrait") 55 | onClicked : { 56 | options.set("QMLmainViewRotation", "portrait") 57 | mainView.orientationLock = PageOrientation.LockPortrait 58 | } 59 | } 60 | Button { 61 | id : bLandscape 62 | text : qsTr("landscape") 63 | onClicked : { 64 | options.set("QMLmainViewRotation", "landscape") 65 | mainView.orientationLock = PageOrientation.LockLandscape 66 | } 67 | } 68 | } 69 | SwitchWithText { 70 | text : "" + qsTr("Show status bar") + "" 71 | checked : rootWindow.showStatusBar 72 | onCheckedChanged : { 73 | rootWindow.showStatusBar = checked 74 | options.set("QMLShowStatusBar", checked) 75 | } 76 | } 77 | SwitchWithText { 78 | text : "" + qsTr("Remember toolbar state") + "" 79 | checked : options.get("QMLRememberToolbarState", false) 80 | onCheckedChanged : { 81 | options.set("QMLRememberToolbarState", checked) 82 | } 83 | } 84 | Label { 85 | text : "" + qsTr("Fullscreen button opacity") + "" 86 | } 87 | Slider { 88 | value : mainView.fullscreenButtonOpacity 89 | minimumValue: 0.0 90 | maximumValue: 1.0 91 | stepSize: 0.1 92 | valueIndicatorText : Math.round(value*100) + " %" 93 | valueIndicatorVisible: true 94 | onPressedChanged : { 95 | var outputValue 96 | // completely transparent items don't receive events 97 | if (value == 0) { 98 | outputValue = 0.01 99 | } else { 100 | // round away small fractions that were created by the assured lowest value 101 | outputValue = Math.round(value * 100) / 100 102 | } 103 | // update once dragging stops 104 | options.set("QMLFullscreenButtonOpacity", outputValue) 105 | mainView.fullscreenButtonOpacity = outputValue 106 | } 107 | } 108 | 109 | LineText { 110 | width : optionsPage.width 111 | text : qsTr("Paging options") 112 | } 113 | Label { 114 | text : "" + qsTr("Paging mode") + "" 115 | } 116 | ButtonRow { 117 | Component.onCompleted : { 118 | var pm = options.get("QMLPagingMode", "screen") 119 | if (pm == "screen") { 120 | checkedButton = bPMScreen 121 | } else if (pm == "edges") { 122 | checkedButton = bPMEdges 123 | } else { 124 | checkedButton = bPMScreen 125 | } 126 | } 127 | Button { 128 | id : bPMScreen 129 | text : qsTr("Whole screen") 130 | onClicked : { 131 | options.set("QMLPagingMode", "screen") 132 | mainView.pagingMode = "screen" 133 | } 134 | } 135 | Button { 136 | id : bPMEdges 137 | text : qsTr("On edges") 138 | onClicked : { 139 | options.set("QMLPagingMode", "edges") 140 | mainView.pagingMode = "edges" 141 | } 142 | } 143 | } 144 | 145 | SelectorButtonWithText { 146 | enabled : mainView.pagingMode == "edges" 147 | text : "" + qsTr("Middle click") + "" 148 | buttonText : qsTranslate("MainView", mainView.pageFitModeClick) 149 | // FIXME: incomplete theme on Fremantle 150 | iconSource: platform.incompleteTheme() ? 151 | "image://theme/icon-m-image-edit-resize" : "image://theme/icon-m-common-combobox-arrow" 152 | selector : tempPageFitSelectorClick 153 | } 154 | 155 | SelectorButtonWithText { 156 | enabled : mainView.pagingMode == "edges" 157 | text : "" + qsTr("Middle doubleclick") + "" 158 | buttonText : qsTranslate("MainView", mainView.pageFitModeDoubleclick) 159 | // FIXME: incomplete theme on Fremantle 160 | iconSource: platform.incompleteTheme() ? 161 | "image://theme/icon-m-image-edit-resize" : "image://theme/icon-m-common-combobox-arrow" 162 | selector : tempPageFitSelectorDoubleclick 163 | } 164 | 165 | 166 | SwitchWithText { 167 | text : "" + qsTr("Show paging feedback") + "" 168 | checked : mainView.pagingFeedback 169 | onCheckedChanged : { 170 | mainView.pagingFeedback = checked 171 | options.set("QMLPagingFeedback", checked) 172 | } 173 | } 174 | 175 | LineText { 176 | width : optionsPage.width 177 | text : qsTr("Page scaling") 178 | } 179 | SelectorButtonWithText { 180 | text : "" + qsTr("Page fit mode") + "" 181 | buttonText : qsTranslate("MainView", mainView.pageFitMode) 182 | // FIXME: incomplete theme on Fremantle 183 | iconSource: platform.incompleteTheme() ? 184 | "image://theme/icon-m-image-edit-resize" : "image://theme/icon-m-common-combobox-arrow" 185 | selector : pageFitSelector 186 | } 187 | LineText { 188 | width : optionsPage.width 189 | text : qsTr("History") 190 | } 191 | SwitchWithText { 192 | text : "" + qsTr("history enabled") + "" 193 | checked : historyEnabled 194 | onCheckedChanged : { 195 | historyEnabled = checked 196 | options.set("historyEnabled", checked) 197 | } 198 | } 199 | SwitchWithText { 200 | text : "" + qsTr("use when opening files") + "" 201 | checked : fileSelectorCheckHistory 202 | onCheckedChanged : { 203 | fileSelectorCheckHistory = checked 204 | options.set("fileSelectorCheckHistory", checked) 205 | } 206 | } 207 | LineText { 208 | width : optionsPage.width 209 | text : qsTr("Miscellaneous") 210 | } 211 | SwitchWithText { 212 | text : "" + qsTr("Manga reading mode") + "" 213 | checked : rootWindow.enableMangaMode 214 | onCheckedChanged : { 215 | rootWindow.enableMangaMode = checked 216 | options.set("QMLMangaMode", checked) 217 | } 218 | } 219 | SwitchWithText { 220 | text : "" + qsTr("Show release notes") + "" 221 | checked : showReleaseNotes 222 | onCheckedChanged : { 223 | showReleaseNotes = checked 224 | options.set("showReleaseNotes", checked) 225 | } 226 | } 227 | } 228 | } 229 | 230 | tools: ToolBarLayout { 231 | ToolIcon { 232 | iconId: "toolbar-back" 233 | onClicked: pageStack.pop() 234 | } 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /gui/qml/FileSelector.qml: -------------------------------------------------------------------------------- 1 | //FileSelector.qml 2 | import QtQuick 1.0 3 | import UC 1.0 4 | import Qt.labs.folderlistmodel 1.0 5 | 6 | Dialog { 7 | id: fileSelector 8 | width : parent.width - 30 9 | property Style platformStyle: SelectionDialogStyle {} 10 | 11 | property variant filterList: [ "*" ] 12 | 13 | property string titleText: qsTr("File Selector") 14 | property variant folderModel: folderModel1 15 | property variant view: view1 16 | property string selectedFile: ""; 17 | 18 | // go down one view 19 | function down(path) { 20 | // slide current view out to the left 21 | if (folderModel == folderModel1) { 22 | view = view2 23 | folderModel = folderModel2; 24 | view1.state = "exitLeft"; 25 | } else { 26 | view = view1 27 | folderModel = folderModel1; 28 | view2.state = "exitLeft"; 29 | } 30 | 31 | // and slide new view in from right 32 | view.x = fileSelector.width; 33 | view.state = "current"; 34 | view.focus = true; 35 | folderModel.folder = path; 36 | } 37 | 38 | function up() { 39 | selectedFile = folderModel.folder; 40 | 41 | var path = folderModel.parentFolder; 42 | if (folderModel == folderModel1) { 43 | view = view2 44 | folderModel = folderModel2; 45 | view1.state = "exitRight"; 46 | } else { 47 | view = view1 48 | folderModel = folderModel1; 49 | view2.state = "exitRight"; 50 | } 51 | view.x = -fileSelector.width; 52 | view.state = "current"; 53 | view.focus = true; 54 | folderModel.folder = path; 55 | } 56 | 57 | property Component delegate: 58 | Component { 59 | id: defaultDelegate 60 | 61 | Item { 62 | id: delegateItem 63 | property bool selected: filePath == selectedFile; 64 | 65 | height: fileSelector.platformStyle.itemHeight 66 | anchors.left: parent.left 67 | anchors.right: parent.right 68 | 69 | MouseArea { 70 | id: delegateMouseArea 71 | anchors.fill: parent; 72 | onPressed: selectedFile = filePath; 73 | onClicked: { 74 | if (folderModel.isFolder(index)) 75 | down(filePath); 76 | else 77 | accept(); 78 | } 79 | } 80 | 81 | Rectangle { 82 | id: backgroundRect 83 | anchors.fill: parent 84 | color: delegateItem.selected ? fileSelector.platformStyle.itemSelectedBackgroundColor : fileSelector.platformStyle.itemBackgroundColor 85 | } 86 | 87 | BorderImage { 88 | id: background 89 | anchors.fill: parent 90 | border { left: 22; top: 2; right: 2; bottom: 22 } 91 | source: delegateMouseArea.pressed ? fileSelector.platformStyle.itemPressedBackground : 92 | delegateItem.selected ? fileSelector.platformStyle.itemSelectedBackground : 93 | fileSelector.platformStyle.itemBackground 94 | } 95 | 96 | Text { 97 | id: itemText 98 | elide: Text.ElideRight 99 | color: delegateItem.selected ? fileSelector.platformStyle.itemSelectedTextColor : fileSelector.platformStyle.itemTextColor 100 | anchors.verticalCenter: delegateItem.verticalCenter 101 | anchors.left: parent.left 102 | anchors.right: folderModel.isFolder(index)?downArrow.left:parent.right 103 | anchors.leftMargin: fileSelector.platformStyle.itemLeftMargin 104 | anchors.rightMargin: fileSelector.platformStyle.itemRightMargin 105 | text: fileName; 106 | font: fileSelector.platformStyle.itemFont 107 | } 108 | 109 | // add "right" arrow to all directories 110 | Image { 111 | id: downArrow 112 | source: "image://theme/icon-m-common-drilldown-arrow-inverse" 113 | anchors.right: parent.right; 114 | anchors.verticalCenter: parent.verticalCenter 115 | visible: folderModel.isFolder(index) 116 | } 117 | } 118 | } 119 | 120 | title: Item { 121 | id: header 122 | height: fileSelector.platformStyle.titleBarHeight 123 | 124 | anchors.left: parent.left 125 | anchors.right: parent.right 126 | anchors.top: parent.top 127 | anchors.bottom: parent.bottom 128 | 129 | Item { 130 | id: labelField 131 | 132 | anchors.fill: parent 133 | 134 | Item { 135 | id: labelWrapper 136 | anchors.left: parent.left 137 | anchors.right: closeButton.left 138 | 139 | anchors.bottom: parent.bottom 140 | anchors.bottomMargin: fileSelector.platformStyle.titleBarLineMargin 141 | 142 | height: titleLabel.height 143 | 144 | Label { 145 | id: titleLabel 146 | x: fileSelector.platformStyle.titleBarIndent 147 | width: parent.width - closeButton.width 148 | font: fileSelector.platformStyle.titleBarFont 149 | color: fileSelector.platformStyle.commonLabelColor 150 | elide: fileSelector.platformStyle.titleElideMode 151 | text: fileSelector.titleText 152 | } 153 | } 154 | 155 | Image { 156 | id: closeButton 157 | anchors.bottom: parent.bottom 158 | anchors.bottomMargin: fileSelector.platformStyle.titleBarLineMargin-6 159 | anchors.right: labelField.right 160 | 161 | opacity: closeButtonArea.pressed ? 0.5 : 1.0 162 | source: "image://theme/icon-m-common-dialog-close" 163 | 164 | MouseArea { 165 | id: closeButtonArea 166 | anchors.fill: parent 167 | onClicked: {fileSelector.reject();} 168 | } 169 | } 170 | } 171 | 172 | Rectangle { 173 | id: headerLine 174 | 175 | anchors.left: parent.left 176 | anchors.right: parent.right 177 | 178 | anchors.bottom: header.bottom 179 | 180 | height: 1 181 | 182 | color: "#4D4D4D" 183 | } 184 | } 185 | 186 | content: Item { 187 | id: contentField 188 | 189 | property int maxListViewHeight : visualParent ? visualParent.height * 0.87 190 | - fileSelector.platformStyle.titleBarHeight 191 | - fileSelector.platformStyle.contentSpacing - 50 192 | : fileSelector.parent ? fileSelector.parent.height * 0.87 193 | - fileSelector.platformStyle.titleBarHeight 194 | - fileSelector.platformStyle.contentSpacing - 50 195 | : 350 196 | 197 | height: maxListViewHeight 198 | width: fileSelector.width 199 | y : fileSelector.platformStyle.contentSpacing 200 | clip: true 201 | 202 | // we have two list views and are shifting them left and right 203 | // for nice animation 204 | 205 | Item { 206 | id: pathItem 207 | anchors.left: parent.left 208 | anchors.right: parent.right 209 | anchors.top: parent.top 210 | height: fileSelector.platformStyle.itemHeight 211 | 212 | property bool canGoUp: folderModel.parentFolder != "" && folderModel.folder != folderModel.parentFolder 213 | 214 | enabled: canGoUp 215 | MouseArea { 216 | id: backArea 217 | anchors.fill: parent 218 | onClicked: { up(); } 219 | } 220 | 221 | // add "left" arrow to go up one directory 222 | Image { 223 | id: backButton 224 | //source: "image://theme/icon-m-startup-back" 225 | // fix Fremantle CSSU icon availability 226 | source: platform.incompleteTheme() ? "image://theme/icon-m-toolbar-back-white-selected" : 227 | "image://theme/icon-m-startup-back" 228 | anchors.verticalCenter: parent.verticalCenter 229 | anchors.left: parent.left 230 | opacity: parent.canGoUp?(backArea.pressed ? 0.5 : 1.0):0.4 231 | } 232 | 233 | Label { 234 | id: pathLabel 235 | font: fileSelector.platformStyle.itemFont 236 | color: fileSelector.platformStyle.itemTextColor 237 | elide: Text.ElideLeft 238 | 239 | text: folderModel.folder 240 | 241 | anchors.verticalCenter: parent.verticalCenter 242 | anchors.left: backButton.right 243 | anchors.right: parent.right 244 | 245 | anchors.leftMargin: fileSelector.platformStyle.itemLeftMargin 246 | anchors.rightMargin: fileSelector.platformStyle.itemRightMargin 247 | } 248 | 249 | Rectangle { 250 | id: pathLine 251 | 252 | anchors.left: parent.left 253 | anchors.right: parent.right 254 | 255 | anchors.bottom: parent.bottom 256 | 257 | height: 1 258 | 259 | color: "#4D4D4D" 260 | } 261 | } 262 | 263 | Item { 264 | clip: true 265 | focus: true 266 | 267 | anchors.top: pathItem.bottom 268 | anchors.bottom: parent.bottom 269 | width: parent.width 270 | 271 | ListView { 272 | id: view1 273 | anchors.top: parent.top 274 | anchors.bottom: parent.bottom 275 | x: 0 276 | width: parent.width 277 | pressDelay: fileSelector.platformStyle.pressDelay 278 | 279 | FolderListModel { 280 | id: folderModel1 281 | nameFilters: filterList 282 | } 283 | 284 | model: folderModel1 285 | delegate: defaultDelegate 286 | 287 | states: [ 288 | State { 289 | name: "current" 290 | PropertyChanges { target: view1; x: 0 } 291 | }, 292 | State { 293 | name: "exitLeft" 294 | PropertyChanges { target: view1; x: -parent.width } 295 | }, 296 | State { 297 | name: "exitRight" 298 | PropertyChanges { target: view1; x: parent.width } 299 | } 300 | ] 301 | transitions: [ 302 | Transition { 303 | NumberAnimation { properties: "x"; duration: 250 } 304 | } 305 | ] 306 | } 307 | 308 | ListView { 309 | id: view2 310 | anchors.top: parent.top 311 | anchors.bottom: parent.bottom 312 | x: parent.width 313 | width: parent.width 314 | pressDelay: fileSelector.platformStyle.pressDelay 315 | 316 | FolderListModel { 317 | id: folderModel2 318 | nameFilters: filterList 319 | } 320 | 321 | model: folderModel2 322 | delegate: defaultDelegate 323 | 324 | states: [ 325 | State { 326 | name: "current" 327 | PropertyChanges { target: view2; x: 0 } 328 | }, 329 | State { 330 | name: "exitLeft" 331 | PropertyChanges { target: view2; x: -parent.width } 332 | }, 333 | State { 334 | name: "exitRight" 335 | PropertyChanges { target: view2; x: parent.width } 336 | } 337 | ] 338 | transitions: [ 339 | Transition { 340 | NumberAnimation { properties: "x"; duration: 250 } 341 | } 342 | ] 343 | } 344 | } 345 | 346 | MouseArea { 347 | property int xPos 348 | enabled: false 349 | 350 | anchors.fill: parent 351 | onPressed: { console.log("Pressed"); xPos = mouseX; mouse.accepted = false; } 352 | onReleased: { 353 | console.log("Swipe: " + mouseX + " " +xPos); 354 | if (mouseX - xPos > width/5) { 355 | up(); 356 | mouse.accepted = false; 357 | } 358 | } 359 | } 360 | } 361 | } 362 | -------------------------------------------------------------------------------- /icons/mieru_no_background.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 25 | 27 | 38 | 49 | 60 | 71 | 82 | 93 | 104 | 115 | 117 | 121 | 125 | 129 | 130 | 133 | 137 | 138 | 140 | 144 | 148 | 152 | 153 | 156 | 160 | 161 | 168 | 169 | 187 | 189 | 190 | 192 | image/svg+xml 193 | 195 | 196 | 197 | 198 | 199 | 204 | 209 | 218 | 223 | 236 | 245 | 250 | 255 | 260 | 261 | 262 | -------------------------------------------------------------------------------- /icons/mieru.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 25 | 27 | 38 | 49 | 60 | 71 | 82 | 93 | 104 | 115 | 117 | 121 | 125 | 129 | 130 | 133 | 137 | 138 | 140 | 144 | 148 | 152 | 153 | 156 | 160 | 161 | 168 | 169 | 187 | 189 | 190 | 192 | image/svg+xml 193 | 195 | 196 | 197 | 198 | 199 | 204 | 216 | 221 | 230 | 235 | 248 | 257 | 262 | 267 | 272 | 273 | 274 | -------------------------------------------------------------------------------- /gui/qml/InfoPage.qml: -------------------------------------------------------------------------------- 1 | //InfoPage.qml 2 | import QtQuick 2.0 3 | import UC 1.0 4 | 5 | Page { 6 | tools: ToolBarLayout { 7 | ToolIcon { 8 | iconId: "toolbar-back" 9 | onClicked: pageStack.pop() 10 | } 11 | ButtonRow { 12 | TabButton { 13 | text: qsTr("Info") 14 | tab: tab1 15 | } 16 | TabButton { 17 | text: qsTr("Stats") 18 | tab: tab2 19 | } 20 | TabButton { 21 | text: qsTr("About") 22 | tab: tab3 23 | } 24 | } 25 | } 26 | 27 | TabGroup { 28 | id : tabGroup 29 | currentTab : tab1 30 | 31 | Page { 32 | id : tab1 33 | anchors.fill : parent 34 | anchors.topMargin : 30 35 | anchors.bottomMargin : 30 36 | anchors.leftMargin : 30 37 | anchors.rightMargin : 30 38 | 39 | ScrollDecorator { 40 | flickableItem : infoFlickable 41 | } 42 | Flickable { 43 | id : infoFlickable 44 | anchors.fill : parent 45 | contentWidth : tab1.width 46 | contentHeight : infoHeadline.height + infoFirstPage.height + infoColumn.height + 30 47 | flickableDirection : Flickable.VerticalFlick 48 | 49 | Label { 50 | id : infoHeadline 51 | anchors.horizontalCenter : parent.horizontalCenter 52 | text : "

" + readingState.getPrettyName() + "

" 53 | width : tab1.width 54 | wrapMode : Text.WordWrap 55 | horizontalAlignment : Text.AlignHCenter 56 | } 57 | Image { 58 | id : infoFirstPage 59 | anchors.horizontalCenter : parent.horizontalCenter 60 | anchors.top : infoHeadline.bottom 61 | anchors.topMargin : 10 62 | source : "image://page/" + mainView.mangaPath + "|0" 63 | fillMode : Image.PreserveAspectFit 64 | width : tab1.width / 2.0 65 | height : tab1.width / 2.0 66 | smooth : true 67 | } 68 | Column { 69 | id : infoColumn 70 | anchors.top : infoFirstPage.bottom 71 | anchors.topMargin : 20 72 | spacing : 20 73 | 74 | LineText { 75 | width : tab1.width 76 | text : qsTr("Properties") 77 | } 78 | Label { 79 | text : "" + qsTr("Pages") + ": " + (mainView.maxPageNumber - 1) 80 | } 81 | Label { 82 | text : "" + qsTr("Path") + ": " + mainView.mangaPath 83 | 84 | // explicit width is needed for wrapping to work 85 | width : tab1.width 86 | wrapMode : Text.WrapAnywhere 87 | } 88 | LineText { 89 | width : tab1.width 90 | text : qsTr("Online search") 91 | } 92 | Button { 93 | text : "Google" 94 | anchors.horizontalCenter : parent.horizontalCenter 95 | onClicked : { 96 | rootWindow.notify(qsTr("Opening Google search")) 97 | Qt.openUrlExternally("http://www.google.com/search?as_q=" + readingState.getPrettyName()) 98 | } 99 | } 100 | Button { 101 | // TODO: other language mutations 102 | text : "Wikipedia" 103 | anchors.horizontalCenter : parent.horizontalCenter 104 | onClicked : { 105 | rootWindow.notify(qsTr("Opening Wikipedia search")) 106 | Qt.openUrlExternally("http://en.wikipedia.org/w/index.php?search=" + readingState.getPrettyName() + "&go=Go") 107 | } 108 | 109 | } 110 | Button { 111 | text : "Manga updates" 112 | anchors.horizontalCenter : parent.horizontalCenter 113 | onClicked : { 114 | rootWindow.notify(qsTr("Opening Manga updates search")) 115 | Qt.openUrlExternally("http://www.mangaupdates.com/search.html?search=" + readingState.getPrettyName()) 116 | } 117 | } 118 | } 119 | } 120 | } 121 | Page { 122 | id: tab2 123 | anchors.fill : parent 124 | anchors.topMargin : 30 125 | anchors.bottomMargin : 30 126 | anchors.leftMargin : 30 127 | anchors.rightMargin : 30 128 | Text { 129 | anchors.left : parent.left 130 | id : statsHeadline 131 | text : "" + qsTr("Usage Statistics") + "" 132 | font.pointSize: 24 133 | } 134 | Switch { 135 | id : statsSwitch 136 | anchors.left : statsHeadline.right 137 | anchors.leftMargin : 35 138 | 139 | onCheckedChanged : { 140 | // enable/disable stats and update statsText 141 | stats.enabled = statsSwitch.checked 142 | statsText.text = stats.statsText 143 | } 144 | 145 | // workaround for checked property binding loop 146 | Component.onCompleted : { 147 | checked = stats.enabled 148 | } 149 | } 150 | Text { 151 | id : statsText 152 | anchors.top : statsHeadline.bottom 153 | anchors.topMargin : 10 154 | text: stats.statsText 155 | font.pointSize: 24 156 | } 157 | 158 | Button { 159 | anchors.top : statsText.bottom 160 | anchors.topMargin : 50 161 | text : qsTr("Reset") 162 | onClicked : { 163 | resetStatsDialog.open() 164 | } 165 | } 166 | } 167 | 168 | Page { 169 | id: tab3 170 | anchors.fill : parent 171 | anchors.topMargin : 8 172 | anchors.bottomMargin : 8 173 | anchors.leftMargin : 8 174 | anchors.rightMargin : 8 175 | 176 | ScrollDecorator { 177 | flickableItem : aboutFlickable 178 | } 179 | Flickable { 180 | id : aboutFlickable 181 | anchors.fill : parent 182 | contentWidth : tab3.width 183 | contentHeight : aboutColumn.height + 30 184 | flickableDirection : Flickable.VerticalFlick 185 | 186 | Item { 187 | //anchors.horizontalCenter : parent.horizontalCenter 188 | width : tab3.width 189 | height : childrenRect.height 190 | id : aboutColumn 191 | Label { 192 | id : versionLabel 193 | anchors.top : parent.top 194 | anchors.horizontalCenter : parent.horizontalCenter 195 | text : "

Mieru " + readingState.getVersionString() + "

" 196 | } 197 | Image { 198 | id : mieruIcon 199 | anchors.top : versionLabel.bottom 200 | anchors.topMargin : 5 201 | anchors.horizontalCenter : parent.horizontalCenter 202 | source : "image://icons/mieru.svg" 203 | } 204 | Label { 205 | id : mieruDescription 206 | anchors.top : mieruIcon.bottom 207 | anchors.topMargin : 8 208 | anchors.horizontalCenter : parent.horizontalCenter 209 | horizontalAlignment: Text.AlignHCenter 210 | width : parent.width 211 | wrapMode : Text.WordWrap 212 | text : qsTr("Mieru is a flexible Manga and comic book reader.") 213 | } 214 | Label { 215 | id : donateLabel 216 | anchors.top : mieruDescription.bottom 217 | anchors.topMargin : 25 218 | anchors.horizontalCenter : parent.horizontalCenter 219 | horizontalAlignment: Text.AlignHCenter 220 | width : parent.width 221 | wrapMode : Text.WordWrap 222 | text : qsTr("Do you like Mieru ? Donate !") 223 | } 224 | Row { 225 | id : ppFlattrRow 226 | anchors.top : donateLabel.bottom 227 | anchors.horizontalCenter : parent.horizontalCenter 228 | anchors.topMargin : 24 229 | spacing : 32 230 | PayPalButton { 231 | id : ppButton 232 | anchors.verticalCenter : parent.verticalCenter 233 | url : "https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=martin%2ekolman%40gmail%2ecom&lc=GB&item_name=Mieru%20project¤cy_code=EUR&bn=PP%2dDonationsBF%3abtn_donate_LG%2egif%3aNonHosted" 234 | } 235 | 236 | FlattrButton { 237 | id : flattrButton 238 | anchors.verticalCenter : parent.verticalCenter 239 | url : "http://flattr.com/thing/830372/Mieru-flexible-manga-and-comic-book-reader" 240 | } 241 | } 242 | 243 | BitcoinButton { 244 | id : bitcoinButton 245 | anchors.top : ppFlattrRow.bottom 246 | anchors.topMargin : 24 247 | anchors.horizontalCenter : parent.horizontalCenter 248 | url : "1PPnoD4SyeQYgvhJ6L5xkjZ4qE4WMMCe1k" 249 | } 250 | Column { 251 | anchors.top : bitcoinButton.bottom 252 | anchors.topMargin : 25 253 | spacing : 5 254 | Label { 255 | text : "" + qsTr("main developer") + ": Martin Kolman" 256 | } 257 | Label { 258 | text : "" + qsTr("email") + ": mieru.info@gmail.com" 259 | onLinkActivated : Qt.openUrlExternally(link) 260 | } 261 | Label { 262 | text : "" + qsTr("www") + ": http://m4rtink.github.com/mieru/" 263 | onLinkActivated : Qt.openUrlExternally(link) 264 | } 265 | Label { 266 | width : tab3.width 267 | text : "" + qsTr("discussion") + ": " + "forum.meego.com" 268 | onLinkActivated : Qt.openUrlExternally(link) 269 | } 270 | } 271 | } 272 | } 273 | ScrollDecorator { 274 | flickableItem: aboutFlickable 275 | } 276 | } 277 | } 278 | 279 | QueryDialog { 280 | id : resetStatsDialog 281 | titleText : qsTr("Reset all usage statistics") 282 | message : qsTr("Do you really want to reset all usage statistics?") 283 | acceptButtonText : qsTr("Reset") 284 | rejectButtonText : qsTr("Cancel") 285 | onAccepted : { 286 | stats.reset() 287 | statsText.text = stats.statsText 288 | } 289 | } 290 | } 291 | --------------------------------------------------------------------------------