├── .excludes ├── .gitignore ├── .tx └── config ├── CONTRIBUTORS.md ├── LICENSE ├── README.archive.md ├── README.md ├── config └── liri-config.json ├── deployment.pri ├── get_libs.py ├── icons ├── liri-browser-ubuntu.png ├── liri-browser.icns ├── liri-browser.ico └── liri-browser.png ├── liri-browser-ubuntu.pro ├── liri-browser.desktop ├── liri-browser.pro ├── plugins ├── omniplet-bookmarks │ ├── main.js │ └── manifest.json ├── omniplet-calculator │ ├── main.js │ └── manifest.json ├── omniplet-duckduckgo │ ├── main.js │ └── manifest.json ├── omniplet-history │ ├── main.js │ └── manifest.json └── omniplet-weather │ ├── main.js │ └── manifest.json ├── screenshots ├── screenshot_01.png └── screenshot_02.png ├── src ├── TextFieldDarkThemed.qml ├── clipboardadapter.cpp ├── clipboardadapter.h ├── config.cpp ├── config.h ├── cursor │ ├── cursor.cpp │ └── cursor.h ├── main.cpp ├── plugins │ ├── api.cpp │ ├── api.h │ ├── plugin.cpp │ ├── plugin.h │ ├── pluginsengine.cpp │ ├── pluginsengine.h │ ├── urlopener.cpp │ └── urlopener.h ├── qml.qrc ├── qml │ ├── AwesomeActionButton.qml │ ├── BaseApplication.qml │ ├── BaseBrowserView.qml │ ├── BookmarkItem.qml │ ├── BookmarksBar.qml │ ├── BookmarksDrawer.qml │ ├── BrowserOxideWebView.qml │ ├── BrowserPage.qml │ ├── BrowserTabBar.qml │ ├── BrowserToolbar.qml │ ├── BrowserView.qml │ ├── BrowserWebView.qml │ ├── BrowserWindow.qml │ ├── ColorChooser.qml │ ├── ColorPicker.qml │ ├── DesktopApplication.qml │ ├── DownloadsDrawer.qml │ ├── FullscreenBar.qml │ ├── HistoryDrawer.qml │ ├── LoadingIndicator.qml │ ├── MaterialWindow.qml │ ├── MenuFieldThemed.qml │ ├── NewTabPage.qml │ ├── Omnibox.qml │ ├── PlayerPage.qml │ ├── QuickSearches.qml │ ├── QuickSearchesList.qml │ ├── QuickSearchesPage.qml │ ├── QuickSearchesView.qml │ ├── ResizeArea.qml │ ├── RightDrawer.qml │ ├── ScrollbarThemed.qml │ ├── SearchSuggestions.qml │ ├── Settings.qml │ ├── SettingsDrawer.qml │ ├── SettingsPage.qml │ ├── SettingsView.qml │ ├── ShadowOverlay.qml │ ├── ShortcutActions.qml │ ├── SitesColors.qml │ ├── SitesColorsList.qml │ ├── SitesColorsPage.qml │ ├── SitesColorsView.qml │ ├── SystemBar.qml │ ├── SystemButtons.qml │ ├── TabBarItemDelegate.qml │ ├── TabsList.qml │ ├── TabsListPage.qml │ ├── TextFieldThemed.qml │ ├── UbuntuOmniboxOverlay.qml │ ├── oxide-user.js │ └── ubuntu │ │ ├── Main.qml │ │ └── UbuntuApplication.qml ├── sourcecodeviewer │ └── viewer.html ├── translations │ ├── ar_MA.qm │ ├── ar_MA.ts │ ├── de_DE.qm │ ├── de_DE.ts │ ├── es_CR.qm │ ├── es_CR.ts │ ├── es_ES.qm │ ├── es_ES.ts │ ├── fr_FR.qm │ ├── fr_FR.ts.bak │ ├── ko_KR.qm │ ├── ko_KR.ts.bak │ ├── liri-browser.qm │ ├── liri-browser.ts │ ├── pt_BR.qm │ ├── pt_BR.ts │ ├── pt_PT.qm │ ├── pt_PT.ts │ ├── ru_RU.qm │ └── ru_RU.ts └── ubuntu │ └── main.cpp ├── translations ├── .gitignore ├── ar_MA.qm ├── ar_MA.ts ├── de_DE.qm ├── de_DE.ts ├── es_CR.qm ├── es_CR.ts ├── es_ES.qm ├── es_ES.ts ├── fr_FR.qm ├── fr_FR.ts ├── ko_KR.qm ├── ko_KR.ts ├── pt_BR.qm ├── pt_BR.ts ├── pt_PT.qm ├── pt_PT.ts ├── ru_RU.qm └── ru_RU.ts └── ubuntu ├── Setup.md ├── liri-browser.timsueberkrueb.apparmor ├── liri-browser.timsueberkrueb.desktop └── manifest.json /.excludes: -------------------------------------------------------------------------------- 1 | Makefile 2 | *.tmp 3 | .bzr 4 | .git 5 | po 6 | 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # DS_Store 2 | 3 | .DS_Store 4 | 5 | # Build directory 6 | 7 | build 8 | 9 | # C++ objects and libs 10 | 11 | *.slo 12 | *.lo 13 | *.o 14 | *.a 15 | *.la 16 | *.lai 17 | *.so 18 | *.dll 19 | *.dylib 20 | 21 | # Qt-es 22 | 23 | /.qmake.cache 24 | /.qmake.stash 25 | *.pro.user 26 | *.pro.user.* 27 | *.qbs.user 28 | *.qbs.user.* 29 | *.moc 30 | moc_*.cpp 31 | qrc_*.cpp 32 | ui_*.h 33 | Makefile* 34 | *-build-* 35 | 36 | # QtCreator 37 | 38 | *.autosave 39 | 40 | #QtCtreator Qml 41 | *.qmlproject.user 42 | *.qmlproject.user.* 43 | 44 | # Executables 45 | *.exe 46 | *.out 47 | *.app 48 | liri-browser 49 | 50 | # Visual Studio 51 | /Win32 52 | /debug 53 | /release 54 | liri-browser.sdf 55 | liri-browser.v12.suo 56 | liri-browser.vcxproj 57 | liri-browser.vcxproj.filters 58 | liri-browser.vcxproj.user 59 | liri-browser.opensdf 60 | 61 | # Compiled Object files 62 | *.slo 63 | *.lo 64 | *.o 65 | *.obj 66 | 67 | moc_* 68 | *.pro.user* 69 | qml_qrc.cpp 70 | 71 | # Precompiled Headers 72 | *.gch 73 | *.pch 74 | 75 | # Compiled Dynamic libraries 76 | *.so 77 | *.dylib 78 | *.dll 79 | 80 | # Fortran module files 81 | *.mod 82 | 83 | # Compiled Static libraries 84 | *.lai 85 | *.la 86 | *.a 87 | *.lib 88 | 89 | # Executables 90 | *.exe 91 | *.out 92 | *.app 93 | 94 | # Ubuntu 95 | .ubuntu-sdk-deploy 96 | *.click 97 | lib/ 98 | 99 | # Oxide 100 | TransportSecurity 101 | -------------------------------------------------------------------------------- /.tx/config: -------------------------------------------------------------------------------- 1 | [main] 2 | host = https://www.transifex.com 3 | 4 | [liri-browser.liri-browserts] 5 | file_filter = src/translations/.ts 6 | source_lang = en 7 | type = QT 8 | 9 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | # Contributors 2 | 3 | The list of contributors is sorted alphabetical based on the last names. 4 | 5 | ## Code 6 | * Ilya87 (https://github.com/Ilya87) 7 | * Daniel França (https://github.com/danielfranca) 8 | * Pierre Jacquier (https://github.com/pierremtb) 9 | * Corbin Matschull (https://github.com/Polyg0n) 10 | * Alexandre Oliveira (https://github.com/RockyTV) 11 | * Andrew Penkrat (https://github.com/Aldrog) 12 | * Michael Spencer (https://github.com/iBeliever) 13 | * Tim Süberkrüb (https://github.com/tim-sueberkrueb) 14 | * Balázs Szücs (https://github.com/kuglisb) 15 | * VirusKa (https://github.com/VirusKA) 16 | * Tim Wang (https://github.com/timwangdev) 17 | 18 | The list of languages is sorted alphabetical. 19 | 20 | ## Translations 21 | Thanks to all of you who contributed via transifex! 22 | https://www.transifex.com/liri-browser/teams/ 23 | * Arabic 24 | * Fortas Abdeldjalil (https://github.com/Fcmam5) 25 | * Karim Oulad Chalha (https://github.com/karim88) 26 | * Brazilian Portuguese 27 | * Alexandre Oliveira (https://github.com/RockyTV) 28 | * French 29 | * Pierre Jacquier (https://github.com/pierremtb) 30 | * Fortas Abdeldjalil (https://github.com/Fcmam5) 31 | * German 32 | * Tim Süberkrüb (https://github.com/tim-sueberkrueb) 33 | * Korean 34 | * Eunmin Cho (https://github.com/Eunmin-Cho) 35 | * Russian 36 | * Andrew Penkrat (https://github.com/Aldrog) 37 | * Spanish 38 | * Ozkar L. Garcell (https://github.com/codeshard) 39 | -------------------------------------------------------------------------------- /README.archive.md: -------------------------------------------------------------------------------- 1 | # Liri Browser 2 | A minimalistic material design web browser written for Papyros (https://github.com/papyros/) 3 | 4 | See http://papyros.io for more information about Papyros. 5 | 6 | [![GitHub license](https://img.shields.io/github/license/liri-browser/liri-browser.svg)](https://github.com/liri-browser/liri-browser/blob/master/LICENSE) 7 | [![](https://img.shields.io/github/issues-raw/liri-browser/liri-browser.svg)](https://github.com/liri-browser/liri-browser/issues) 8 | [![GitHub release](https://img.shields.io/badge/release-0.3-red.svg)](https://github.com/liri-browser/liri-browser/releases) 9 | [![Join the chat at https://gitter.im/tim-sueberkrueb/material-browser](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/liri-browser/liri-browser?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 10 | 11 | ## About 12 | Our goal is it to create a minimalistic and simple, yet unique webbrowser that gets out of your way and lets you concentrate on the content. 13 | Liri's user interface is designed according to Google's Material Design which leads to a clean and beautiful interface. 14 | 15 | The name "liri" comes from the Albanian word for freedom. Liri Browser was formerly named "Material Browser". 16 | 17 | ## Screenshots 18 | ![Screenshot](screenshots/screenshot_01.png) 19 | ![Screenshot](screenshots/screenshot_02.png) 20 | 21 | ## Translations 22 | Please help us translating this application on [Transifex](https://www.transifex.com/liri-browser/liri-browser/)! 23 | Alternatively you can follow [this guide](https://github.com/liri-browser/liri-browser/wiki/Translations 24 | ) to get started. 25 | 26 | # Downloads 27 | Check out the [downloads section](https://github.com/liri-browser/liri-browser/releases) and download the package for your platform. 28 | 29 | ## Linux 30 | 31 | ### Ubuntu & Debian (64 bit only) 32 | Download and install our .deb-package. 33 | 34 | ### Arch Linux 35 | You get Liri Browser from the AUR: 36 | * https://aur.archlinux.org/packages/liri-browser/ 37 | * https://aur.archlinux.org/packages/liri-browser-git/ 38 | 39 | ### Ubuntu Touch 40 | Liri Browser is available as early preview in the Ubuntu Store: 41 | 42 | https://uappexplorer.com/app/liri-browser.timsueberkrueb 43 | 44 | Manual Installation 45 | * Download `liri-browser.timsueberkrueb_X.X_multi.click` on your device 46 | * Open the terminal app or connect remotely by using adb shell or SSH 47 | * `cd` into your Downloads directory 48 | * Run `pkcon --allow-untrusted install-local liri-browser.timsueberkrueb_X.X_multi.click` 49 | 50 | ### Any Linux (64 bit only) 51 | * Download `liri-browser-X.X-linux-portable.zip` 52 | * Extract the files 53 | * Run `run-liri.sh` inside the extracted directory 54 | 55 | ### Windows & Mac OS X 56 | Just download and extract the ZIP-file for your platform and run the executable. 57 | 58 | *Note for Windows users:* Expect some performance issues. 59 | 60 | ## Build on Linux 61 | 62 | ### Simple Installation Script 63 | If you just want to try Liri Browser out you can use our simple installation script. It will download and install Qt 5.5 and all necessary dependencies: 64 | 65 | https://gist.github.com/tim-sueberkrueb/bdaae352cc6dcaca19b3 66 | 67 | ### Dependencies 68 | * Qt 5.5 and QtWebEngine 1.1 (http://qt.io) 69 | * qml-material (https://github.com/papyros/qml-material) 70 | 71 | ### Instructions 72 | * Install Qt 5.5 (https://www.qt.io) 73 | * Install qml-material 74 | * git clone https://github.com/papyros/qml-material.git 75 | * cd qml-material 76 | * qmake 77 | * make 78 | * make check # Optional, make sure everything is working correctly 79 | * sudo make install 80 | 81 | ### Build and Run 82 | * git clone --recursive https://github.com/liri-browser/liri-browser.git 83 | * cd liri-browser 84 | * qmake 85 | * make 86 | * run liri-browser executable 87 | 88 | ## Copyright and License 89 | (C) Copyright 2015 by Tim Süberkrüb and contributors 90 | 91 | See CONTRIBUTORS.md for a full list of contributors. 92 | 93 | This application is free software: you can redistribute it and/or modify 94 | it under the terms of the GNU General Public License as published by 95 | the Free Software Foundation, either version 3 of the License, or 96 | (at your option) any later version. 97 | 98 | See LICENSE for more information. 99 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Liri Browser 2 | 3 | __Liri Browser development has moved to [LiriOS/browser](https://github.com/lirios/browser).__ 4 | 5 | Effort has begun there to create the next generation of the browser. 6 | A rewrite from scratch was necessary because we wanted to adopt new technologies like 7 | QtQuick Controls 2. We're also aiming to archieve a better code structure and better cross-platform support. 8 | 9 | This repository remains as __read-only archive__. Please __don't open any new issues or pull-requests__. 10 | 11 | Many thanks to all the wonderful contributors: 12 | 13 | __Code__ 14 | * Ilya87 (https://github.com/Ilya87) 15 | * Daniel França (https://github.com/danielfranca) 16 | * Pierre Jacquier (https://github.com/pierremtb) 17 | * Corbin Matschull (https://github.com/Polyg0n) 18 | * Alexandre Oliveira (https://github.com/RockyTV) 19 | * Andrew Penkrat (https://github.com/Aldrog) 20 | * Michael Spencer (https://github.com/iBeliever) 21 | * Tim Süberkrüb (https://github.com/tim-sueberkrueb) 22 | * Balázs Szücs (https://github.com/kuglisb) 23 | * VirusKa (https://github.com/VirusKA) 24 | * Tim Wang (https://github.com/timwangdev) 25 | 26 | __Translations__ 27 | 28 | Thanks to all of you who contributed via transifex! 29 | https://www.transifex.com/liri-browser/teams/ 30 | * Arabic 31 | * Fortas Abdeldjalil (https://github.com/Fcmam5) 32 | * Karim Oulad Chalha (https://github.com/karim88) 33 | * Brazilian Portuguese 34 | * Alexandre Oliveira (https://github.com/RockyTV) 35 | * French 36 | * Pierre Jacquier (https://github.com/pierremtb) 37 | * Fortas Abdeldjalil (https://github.com/Fcmam5) 38 | * German 39 | * Tim Süberkrüb (https://github.com/tim-sueberkrueb) 40 | * Korean 41 | * Eunmin Cho (https://github.com/Eunmin-Cho) 42 | * Russian 43 | * Andrew Penkrat (https://github.com/Aldrog) 44 | * Spanish 45 | * Ozkar L. Garcell (https://github.com/codeshard) 46 | 47 | __Thank you so much!__ 48 | 49 | Read the [original README.md here](README.archive.md). 50 | 51 | -------------------------------------------------------------------------------- /config/liri-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": { 3 | "omniplet-calculator": { 4 | "permissions": { 5 | "omniplet": true 6 | } 7 | }, 8 | 9 | "omniplet-weather": { 10 | "permissions": { 11 | "omniplet": true, 12 | "network": true 13 | } 14 | }, 15 | 16 | "omniplet-duckduckgo": { 17 | "permissions": { 18 | "omniplet": true, 19 | "network": true 20 | } 21 | }, 22 | 23 | "omniplet-history": { 24 | "permissions": { 25 | "omniplet": true, 26 | "history": true 27 | } 28 | }, 29 | 30 | "omniplet-bookmarks": { 31 | "permissions": { 32 | "omniplet": true, 33 | "bookmarks": true 34 | } 35 | } 36 | 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /deployment.pri: -------------------------------------------------------------------------------- 1 | android-no-sdk { 2 | target.path = /data/user/qt 3 | export(target.path) 4 | INSTALLS += target 5 | } else:android { 6 | x86 { 7 | target.path = /libs/x86 8 | } else: armeabi-v7a { 9 | target.path = /libs/armeabi-v7a 10 | } else { 11 | target.path = /libs/armeabi 12 | } 13 | export(target.path) 14 | INSTALLS += target 15 | } else:unix { 16 | isEmpty(target.path) { 17 | qnx { 18 | target.path = /tmp/$${TARGET}/bin 19 | } else { 20 | target.path = /opt/$${TARGET}/bin 21 | } 22 | export(target.path) 23 | } 24 | INSTALLS += target 25 | } 26 | 27 | export(INSTALLS) 28 | -------------------------------------------------------------------------------- /get_libs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # this is a modified version of get-libs.py which is part of checkbox. 3 | # See http://bazaar.launchpad.net/~checkbox-dev/checkbox/trunk/view/head:/checkbox-touch/get-libs 4 | # 5 | # copyright 2015 by Tim Süberkrüb 6 | # copyright 2014 canonical ltd. 7 | # written by: 8 | # maciej kisielewski 9 | # 10 | # checkbox is free software: you can redistribute it and/or modify 11 | # it under the terms of the gnu general public license version 3, 12 | # as published by the free software foundation. 13 | # 14 | # checkbox is distributed in the hope that it will be useful, 15 | # but without any warranty; without even the implied warranty of 16 | # merchantability or fitness for a particular purpose. see the 17 | # gnu general public license for more details. 18 | # 19 | # you should have received a copy of the gnu general public license 20 | # along with checkbox. if not, see . 21 | """ 22 | Download and extract .deb packages necessary to run checkbox-touch 23 | Extraction is done to specific directories as required by click package 24 | 25 | For typical development iteration, after hacking something in plainbox, 26 | run ./get-libs --plainbox-only 27 | """ 28 | 29 | import apt 30 | import apt_pkg 31 | import argparse 32 | import collections 33 | import os 34 | import shutil 35 | import subprocess 36 | import sys 37 | import tarfile 38 | import tempfile 39 | import urllib.request 40 | import zipfile 41 | 42 | # arch_list contains list of architectures for which the packages should be 43 | # downloaded 44 | arch_list = ['i386', 'amd64', 'armhf'] 45 | # multiarch_list contains full architecture name as used in the directoriesroot.app. 46 | # in contents of click package 47 | multiarch_list = ["i386-linux-gnu", "x86_64-linux-gnu", "arm-linux-gnueabihf"] 48 | 49 | def copy_tree(src, dest, preserve_symlinks=False): 50 | """ 51 | copy files from src to dest using rsync 52 | """ 53 | links_option = "-l" if preserve_symlinks else "-L" 54 | parent_dir = os.path.split(os.path.abspath(dest))[0] 55 | # adding trailing slash if it's not already there 56 | src = os.path.join(src, '') 57 | if not os.path.exists(parent_dir): 58 | os.makedirs(parent_dir) 59 | subprocess.check_call(["rsync", "-a", links_option, src, dest]) 60 | 61 | 62 | def prepare_uris(): 63 | """ 64 | prepare_uris function builds caches for architectures defined in arch_list 65 | and builds a dictionary of URLs indexed by (package_name, arch) tuple. 66 | It returns that dictionary. 67 | """ 68 | 69 | packages = ['qml-module-qtquick-controls'] 70 | 71 | # uris will serve as a database of uris from which to download packages 72 | uris = dict() 73 | Source = collections.namedtuple('Source', ['uri', 'repositories']) 74 | sources = { 75 | 'armhf': [Source('http://ports.ubuntu.com/ubuntu-ports', 76 | 'main restricted universe'), 77 | Source('http://ppa.launchpad.net/checkbox-dev/ppa/ubuntu', 78 | 'main')], 79 | 'i386': [Source('http://archive.ubuntu.com/ubuntu', 80 | 'main restricted universe'), 81 | Source('http://ppa.launchpad.net/checkbox-dev/ppa/ubuntu', 82 | 'main')], 83 | 'amd64': [Source('http://archive.ubuntu.com/ubuntu', 84 | 'main restricted universe'), 85 | Source('http://ppa.launchpad.net/checkbox-dev/ppa/ubuntu', 86 | 'main')], 87 | } 88 | for arch in arch_list: 89 | print('Getting information about packages for {0} arch.'.format(arch)) 90 | # prepare sources.list for apt 91 | with tempfile.TemporaryDirectory() as tmp: 92 | new_etc_apt = os.path.join(tmp, 'etc', 'apt') 93 | os.makedirs(new_etc_apt) 94 | # copy over trusted.gpg 95 | shutil.copyfile('/etc/apt/trusted.gpg', 96 | os.path.join(new_etc_apt, 'trusted.gpg')) 97 | # copy over additional keyrings 98 | if os.path.exists('/etc/apt/trusted.gpg.d'): 99 | shutil.copytree('/etc/apt/trusted.gpg.d', 100 | os.path.join(new_etc_apt, 'trusted.gpg.d')) 101 | sources_list = open(os.path.join(new_etc_apt, 'sources.list'), "w") 102 | for source in sources[arch]: 103 | sources_list.write( 104 | "deb [arch={arch}] {uri} wily {repositories}\n" 105 | .format(arch=arch, uri=source.uri, 106 | repositories=source.repositories)) 107 | sources_list.close() 108 | apt_pkg.config["Apt::Architecture"] = arch 109 | cache = apt.Cache(rootdir=tmp) 110 | cache.update() 111 | cache.open(None) 112 | for pkg in packages: 113 | if pkg not in cache or len(cache[pkg].versions) < 1: 114 | # package not found 115 | raise Exception('Package {0} not found for arch {1}' 116 | .format(pkg, arch)) 117 | # use first uri available 118 | uris[pkg, arch] = cache[pkg].versions[0].uri 119 | # return filled dictionary 120 | return uris 121 | 122 | 123 | def get_package_from_url_and_extract(url, target_dir): 124 | filename = os.path.join(target_dir, url.split('/')[-1]) 125 | print('retrieving {0}'.format(url)) 126 | urllib.request.urlretrieve(url, filename) 127 | subprocess.check_call(["dpkg", "-x", filename, target_dir]) 128 | 129 | 130 | def main(): 131 | uris = prepare_uris() 132 | # libs_urls contains list of .deb packages that will be downloaded and 133 | # extracted. After extraction contents of ./usr/lib are copied to ./lib 134 | libs_urls = [uris['qml-module-qtquick-controls', arch] for arch in arch_list] 135 | for lib in libs_urls: 136 | with tempfile.TemporaryDirectory() as tmp: 137 | get_package_from_url_and_extract(lib, tmp) 138 | # TODO: remove unwanted files from the extracted tree (e.g. *.h) 139 | copy_tree( 140 | os.path.join(tmp, 'usr', 'lib'), 'lib', 141 | preserve_symlinks=1) 142 | # python3_libs_urls contains list of .deb packages that will be downloaded 143 | # and extracted. After extraction contents of 144 | # ./usr/lib/python3/dist-packages are copied to ./lib/py. 145 | python3_libs = [] 146 | 147 | python3_libs_urls = [ 148 | uris[lib, arch] for arch in arch_list for lib in python3_libs] 149 | for pylib in python3_libs_urls: 150 | with tempfile.TemporaryDirectory() as tmp: 151 | get_package_from_url_and_extract(pylib, tmp) 152 | copy_tree( 153 | os.path.join(tmp, 'usr', 'lib', 'python3', 154 | 'dist-packages'), 155 | os.path.join('lib', 'py'), 156 | preserve_symlinks=1) 157 | 158 | # Remove the python3.4 directory 159 | # currently it only holds a few config-directories with symlinks 160 | # and those are not used by anything from this location 161 | if os.path.exists('lib/python3.4'): 162 | shutil.rmtree('lib/python3.4') 163 | 164 | 165 | if __name__ == "__main__": 166 | main() 167 | -------------------------------------------------------------------------------- /icons/liri-browser-ubuntu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liri-project/liri-browser/372cf1ea84b0ee7f8f2fa1b56d5b598e5c0569b1/icons/liri-browser-ubuntu.png -------------------------------------------------------------------------------- /icons/liri-browser.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liri-project/liri-browser/372cf1ea84b0ee7f8f2fa1b56d5b598e5c0569b1/icons/liri-browser.icns -------------------------------------------------------------------------------- /icons/liri-browser.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liri-project/liri-browser/372cf1ea84b0ee7f8f2fa1b56d5b598e5c0569b1/icons/liri-browser.ico -------------------------------------------------------------------------------- /icons/liri-browser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liri-project/liri-browser/372cf1ea84b0ee7f8f2fa1b56d5b598e5c0569b1/icons/liri-browser.png -------------------------------------------------------------------------------- /liri-browser-ubuntu.pro: -------------------------------------------------------------------------------- 1 | # Ubuntu SDK 2 | TEMPLATE = app 3 | 4 | #load Ubuntu specific features 5 | load(ubuntu-click) 6 | 7 | 8 | TEMPLATE = app 9 | 10 | QT += qml quick widgets svg xml multimedia core network 11 | QTPLUGIN += qsvg 12 | 13 | SOURCES += src/ubuntu/main.cpp \ 14 | src/config.cpp \ 15 | src/cursor/cursor.cpp \ 16 | src/clipboardadapter.cpp \ 17 | src/plugins/pluginsengine.cpp \ 18 | src/plugins/plugin.cpp \ 19 | src/plugins/api.cpp \ 20 | src/plugins/urlopener.cpp 21 | 22 | HEADERS += \ 23 | src/config.h \ 24 | src/cursor/cursor.h \ 25 | src/plugins/pluginsengine.h \ 26 | src/plugins/plugin.h \ 27 | src/plugins/api.h \ 28 | src/plugins/urlopener.h 29 | 30 | CONFIG += c++11 31 | 32 | TRANSLATIONS += src/translations/liri-browser.ts \ 33 | src/translations/de_DE.ts \ 34 | src/translations/ru_RU.ts \ 35 | src/translations/fr_FR.ts \ 36 | src/translations/es_CR.ts \ 37 | src/translations/es_ES.ts \ 38 | src/translations/pt_BR.ts \ 39 | src/translations/pt_PT.ts 40 | 41 | lupdate_only{ 42 | SOURCES = *.qml \ 43 | *.js \ 44 | src/qml/* \ 45 | } 46 | 47 | # Additional import path used to resolve QML modules in Qt Creator's code model 48 | QML_IMPORT_PATH = 49 | 50 | # Default rules for deployment. 51 | include(deployment.pri) 52 | 53 | DISTFILES += liri-browser.timsueberkrueb.desktop 54 | 55 | # Windows icon 56 | RC_ICONS = icons/liri-browser.ico 57 | 58 | # OS X icon 59 | ICON = icons/liri-browser.icns 60 | 61 | HEADERS += \ 62 | src/clipboardadapter.h 63 | 64 | 65 | # manifest 66 | UBUNTU_MANIFEST_FILE=ubuntu/manifest.json 67 | 68 | TARGET = liri-browser.timsueberkrueb 69 | 70 | RESOURCES += src/qml.qrc 71 | 72 | QML_FILES += $$files(*.qml,true) \ 73 | $$files(*.js,true) 74 | 75 | CONF_FILES += ubuntu/liri-browser.timsueberkrueb.apparmor \ 76 | icons/liri-browser.timsueberkrueb.png 77 | 78 | ICON_FILES = $$files(icons/*.png,true) 79 | 80 | LIB_FILES = $$files(lib/*,true) 81 | 82 | OTHER_FILES += $${CONF_FILES} \ 83 | $${QML_FILES} \ 84 | $${AP_TEST_FILES} \ 85 | ubuntu/liri-browser.timsueberkrueb.desktop \ 86 | icons/liri-browser-ubuntu.png 87 | 88 | # icon 89 | icon_files.path = /icons 90 | icon_files.files += $${ICON_FILES} 91 | 92 | # lib files 93 | lib_files.path = /lib 94 | lib_files.files += $${LIB_FILES} 95 | 96 | # qml/js files 97 | qml_files.path = /src/qml 98 | qml_files.files += $${QML_FILES} 99 | 100 | # config files 101 | config_files.path = /ubuntu 102 | config_files.files += $${CONF_FILES} 103 | 104 | # .desktop file 105 | desktop_file.path = /ubuntu 106 | desktop_file.files = ubuntu/liri-browser.timsueberkrueb.desktop 107 | desktop_file.CONFIG += no_check_exist 108 | 109 | INSTALLS+=config_files qml_files desktop_file lib_files icon_files 110 | -------------------------------------------------------------------------------- /liri-browser.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Version=0.4 3 | Type=Application 4 | Name=Liri Browser 5 | Comment=The miminal, open-source and MD inspired web browser 6 | Exec=/opt/liri-browser-build/liri-browser %U 7 | Icon=browser 8 | Actions=Internet 9 | Categories=Network;WebBrowser; 10 | MimeType=text/html;text/xml;application/xhtml_xml;image/webp;x-scheme-handler/http;x-scheme-handler/https;x-scheme-handler/ftp; 11 | 12 | -------------------------------------------------------------------------------- /liri-browser.pro: -------------------------------------------------------------------------------- 1 | # Copy translation files 2 | 3 | #copydata.commands = $(COPY_DIR) $$PWD/translations $$OUT_PWD 4 | #first.depends = $(first) copydata 5 | #export(first.depends) 6 | #export(copydata.commands) 7 | #QMAKE_EXTRA_TARGETS += first copydata 8 | 9 | TEMPLATE = app 10 | 11 | QT += qml quick widgets svg xml webengine multimedia core network webenginewidgets 12 | QTPLUGIN += qsvg 13 | 14 | SOURCES += src/main.cpp \ 15 | src/config.cpp \ 16 | src/cursor/cursor.cpp \ 17 | src/clipboardadapter.cpp \ 18 | src/plugins/pluginsengine.cpp \ 19 | src/plugins/plugin.cpp \ 20 | src/plugins/api.cpp \ 21 | src/plugins/urlopener.cpp 22 | 23 | HEADERS += \ 24 | src/config.h \ 25 | src/cursor/cursor.h \ 26 | src/plugins/pluginsengine.h \ 27 | src/plugins/plugin.h \ 28 | src/plugins/api.h \ 29 | src/plugins/urlopener.h 30 | 31 | RESOURCES += \ 32 | src/qml.qrc 33 | 34 | 35 | CONFIG += c++11 36 | 37 | TRANSLATIONS += src/translations/liri-browser.ts \ 38 | src/translations/de_DE.ts \ 39 | src/translations/ru_RU.ts \ 40 | src/translations/fr_FR.ts \ 41 | src/translations/es_CR.ts \ 42 | src/translations/es_ES.ts \ 43 | src/translations/pt_BR.ts \ 44 | src/translations/pt_PT.ts 45 | 46 | lupdate_only{ 47 | SOURCES = *.qml \ 48 | *.js \ 49 | src/qml/* \ 50 | } 51 | 52 | # Additional import path used to resolve QML modules in Qt Creator's code model 53 | QML_IMPORT_PATH = 54 | 55 | # Default rules for deployment. 56 | include(deployment.pri) 57 | 58 | DISTFILES += 59 | 60 | # Windows icon 61 | RC_ICONS = icons/liri-browser.ico 62 | 63 | # OS X icon 64 | ICON = icons/liri-browser.icns 65 | 66 | HEADERS += \ 67 | src/clipboardadapter.h 68 | 69 | linux-g++{ 70 | target.path=/opt/liri-browser-build 71 | target.files+=liri-browser 72 | target.files+=liri-browser.desktop 73 | } 74 | -------------------------------------------------------------------------------- /plugins/omniplet-bookmarks/main.js: -------------------------------------------------------------------------------- 1 | /* Bookmarks Omniplet */ 2 | 3 | var bookmarksModel; 4 | 5 | function onOmniBoxSearch(text){ 6 | var count = bookmarksModel.length, temp, i, current=0, temp2 7 | for(i=0;i", 7 | "api": "0.1", 8 | "features": [ 9 | "omniplet", 10 | "bookmarks" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /plugins/omniplet-calculator/main.js: -------------------------------------------------------------------------------- 1 | /* Calculator Omniplet */ 2 | 3 | function search(expr,a,b) { 4 | var i = 0 5 | while (i != -1) { 6 | i = expr.indexOf(a,i); 7 | if (i>=0) { 8 | if (i==0) { 9 | expr = expr.substring(0,i)+b+expr.substring(i+a.length); 10 | i += b.length 11 | } else { 12 | if (expr.substring(i-1,i) != "a") { 13 | expr = expr.substring(0,i)+b+expr.substring(i+a.length); 14 | i += b.length 15 | } else {i++} 16 | } 17 | 18 | } 19 | } 20 | return expr 21 | } 22 | 23 | function calculate(f) { 24 | var expr = f; 25 | expr = search(expr,'cos','Math.cos'); 26 | expr = search(expr,'sin','Math.sin'); 27 | expr = search(expr,'tan','Math.tan'); 28 | expr = search(expr,'acos','Math.acos'); 29 | expr = search(expr,'asin','Math.asin'); 30 | expr = search(expr,'atan','Math.atan'); 31 | expr = search(expr,'ln','Math.log'); 32 | expr = search(expr,'exp','Math.exp'); 33 | expr = search(expr,'pow','Math.pow'); 34 | expr = search(expr,'sqrt','Math.sqrt'); 35 | expr = search(expr,'pi','Math.PI'); 36 | return eval(expr); 37 | } 38 | 39 | function onOmniBoxSearch(text){ 40 | if(text.substring(0,1) === "=") { 41 | liri.appendSearchSuggestion("Result : " + calculate(text.substring(1,text.length)), "action/code", "start"); 42 | } 43 | } 44 | 45 | function onLoad(){ 46 | liri.print("Calculator Omniplet loaded"); 47 | } 48 | 49 | liri.on("load", onLoad); 50 | liri.on("omnibox.search", onOmniBoxSearch); 51 | -------------------------------------------------------------------------------- /plugins/omniplet-calculator/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "omniplet-calculator", 3 | "title": "Calculator Omniplet", 4 | "description": "Omniplet for simple calculations", 5 | "version": "0.1", 6 | "maintainer": "Tim Süberkrüb ", 7 | "api": "0.1", 8 | "features": [ 9 | "omniplet" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /plugins/omniplet-duckduckgo/main.js: -------------------------------------------------------------------------------- 1 | /* DuckDuckGo Omniplet */ 2 | 3 | function onOmniBoxSearch(text){ 4 | liri.fetchURL("https://duckduckgo.com/ac/?q=" + text, function(reply) { 5 | var data = JSON.parse(reply); 6 | liri.appendSearchSuggestion(text, "action/search"); 7 | for(var i in data) 8 | liri.appendSearchSuggestion(data[i].phrase,"action/search"); 9 | }); 10 | } 11 | 12 | function onLoad(){ 13 | liri.print("DuckDuckGo Omniplet loaded"); 14 | } 15 | 16 | liri.on("load", onLoad); 17 | liri.on("omnibox.search", onOmniBoxSearch); 18 | -------------------------------------------------------------------------------- /plugins/omniplet-duckduckgo/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "omniplet-duckduckgo", 3 | "title": "DuckDuckGo Omniplet", 4 | "description": "Omniplet that provides DuckDuckGo search suggestions", 5 | "version": "0.1", 6 | "maintainer": "Tim Süberkrüb ", 7 | "api": "0.1", 8 | "features": [ 9 | "omniplet", 10 | "network" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /plugins/omniplet-history/main.js: -------------------------------------------------------------------------------- 1 | /* History Omniplet */ 2 | 3 | var historyModel; 4 | 5 | function onOmniBoxSearch(text){ 6 | var count = historyModel.length, current = 0, temp, temp2 7 | for(var i=0; i", 7 | "api": "0.1", 8 | "features": [ 9 | "omniplet", 10 | "history" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /plugins/omniplet-weather/main.js: -------------------------------------------------------------------------------- 1 | /* Weather Omniplet */ 2 | 3 | function onOmniBoxSearch(text){ 4 | if(text.substring(0,8) === "weather ") { 5 | liri.fetchURL("http://api.openweathermap.org/data/2.5/weather?q=" + text.substring(8,text.length) + "&APPID=7d2c3897b58a06210476db2ba6ae39d2", function(reply) { 6 | var data = JSON.parse(reply); 7 | if (typeof(data.message) === "string" && data.message.indexOf("Error") === 0) { 8 | liri.print(data.message); 9 | return; 10 | } 11 | liri.appendSearchSuggestion(data.name + ": " + data.weather[0].main + " - " + parseInt(data.main.temp - 273) + " °C", "image/wb_sunny", "start"); 12 | }); 13 | } 14 | } 15 | 16 | function onLoad(){ 17 | liri.print("Weather Omniplet loaded"); 18 | } 19 | 20 | liri.on("load", onLoad); 21 | liri.on("omnibox.search", onOmniBoxSearch); 22 | -------------------------------------------------------------------------------- /plugins/omniplet-weather/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "omniplet-weather", 3 | "title": "Weather Omniplet", 4 | "description": "Omniplet that provides weather information", 5 | "version": "0.1", 6 | "maintainer": "Tim Süberkrüb ", 7 | "api": "0.1", 8 | "features": [ 9 | "omniplet", 10 | "network" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /screenshots/screenshot_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liri-project/liri-browser/372cf1ea84b0ee7f8f2fa1b56d5b598e5c0569b1/screenshots/screenshot_01.png -------------------------------------------------------------------------------- /screenshots/screenshot_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liri-project/liri-browser/372cf1ea84b0ee7f8f2fa1b56d5b598e5c0569b1/screenshots/screenshot_02.png -------------------------------------------------------------------------------- /src/TextFieldDarkThemed.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | 3 | Item { 4 | 5 | } 6 | 7 | -------------------------------------------------------------------------------- /src/clipboardadapter.cpp: -------------------------------------------------------------------------------- 1 | #include "clipboardadapter.h" 2 | 3 | #include 4 | 5 | ClipBoardAdapter::ClipBoardAdapter(QObject *parent) : QObject(parent){ 6 | clip = QApplication::clipboard(); 7 | } 8 | 9 | void ClipBoardAdapter::copyText(QString text){ 10 | clip->setText(text, QClipboard::Clipboard); 11 | clip->setText(text, QClipboard::Selection); 12 | } 13 | -------------------------------------------------------------------------------- /src/clipboardadapter.h: -------------------------------------------------------------------------------- 1 | #ifndef CLIPBOARDADAPTER_H 2 | #define CLIPBOARDADAPTER_H 3 | 4 | #include 5 | #include 6 | 7 | class ClipBoardAdapter : public QObject{ 8 | Q_OBJECT 9 | public: 10 | explicit ClipBoardAdapter(QObject *parent = 0); 11 | Q_INVOKABLE void copyText(QString text); 12 | 13 | private: 14 | QClipboard *clip; 15 | }; 16 | 17 | #endif // CLIPBOARDADAPTER_H 18 | -------------------------------------------------------------------------------- /src/config.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "config.h" 8 | 9 | Config::Config(QObject *parent) : QObject(parent){ 10 | 11 | } 12 | 13 | 14 | bool Config::load(){ 15 | QFile configFile("config/liri-config.json"); 16 | 17 | if (!configFile.open(QIODevice::ReadOnly)) { 18 | qWarning("Couldn't open config file."); 19 | return false; 20 | } 21 | 22 | QByteArray jsonData = configFile.readAll(); 23 | QJsonDocument configDoc (QJsonDocument::fromJson(jsonData)); 24 | 25 | auto json = configDoc.object(); 26 | QJsonObject pluginsObject = json["plugins"].toObject(); 27 | this->plugins = pluginsObject.toVariantMap(); 28 | 29 | return true; 30 | } 31 | 32 | 33 | bool Config::save(){ 34 | return false; 35 | } 36 | 37 | 38 | QVariantMap Config::getPluginPermissions(QString pluginName) { 39 | return this->plugins[pluginName].toMap()["permissions"].toMap(); 40 | } 41 | 42 | 43 | bool Config::getPluginIsPermitted(QString pluginName, QString feature){ 44 | QVariantMap permissions = this->getPluginPermissions(pluginName); 45 | if (permissions.keys().contains(feature)) 46 | return permissions[feature].toBool(); 47 | else 48 | return false; 49 | } 50 | -------------------------------------------------------------------------------- /src/config.h: -------------------------------------------------------------------------------- 1 | #ifndef CONFIG_H 2 | #define CONFIG_H 3 | 4 | #include 5 | #include 6 | 7 | class Config : public QObject{ 8 | Q_OBJECT 9 | public: 10 | bool load(); 11 | bool save(); 12 | 13 | QVariantMap getPluginPermissions(QString pluginName); 14 | bool getPluginIsPermitted(QString pluginName, QString feature); 15 | 16 | explicit Config(QObject *parent = 0); 17 | private: 18 | QVariantMap plugins; 19 | 20 | }; 21 | 22 | #endif // CONFIG_H 23 | -------------------------------------------------------------------------------- /src/cursor/cursor.cpp: -------------------------------------------------------------------------------- 1 | #include "cursor.h" 2 | 3 | Cursor::Cursor(QObject *parent) : 4 | QObject(parent) 5 | { 6 | } 7 | -------------------------------------------------------------------------------- /src/cursor/cursor.h: -------------------------------------------------------------------------------- 1 | #ifndef CURSOR_H 2 | #define CURSOR_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | class Cursor : public QObject 10 | { 11 | Q_OBJECT 12 | public: 13 | explicit Cursor(QObject *parent = 0); 14 | 15 | signals: 16 | 17 | public slots: 18 | QPoint pos(){return QCursor::pos();} 19 | }; 20 | 21 | #endif // CURSOR_H 22 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | //#include 2 | //#include 3 | #ifndef QT_NO_WIDGETS 4 | #include 5 | typedef QApplication Application; 6 | #else 7 | #include 8 | typedef QGuiApplication Application; 9 | #endif 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include "cursor/cursor.h" 16 | #include "clipboardadapter.h" 17 | #include "plugins/pluginsengine.h" 18 | #include "config.h" 19 | 20 | int main(int argc, char **argv) 21 | { 22 | Application app(argc, argv); 23 | 24 | // Load configuration 25 | Config * config = new Config(); 26 | config->load(); 27 | 28 | // Set domain 29 | app.setOrganizationName("liri-project"); 30 | app.setOrganizationDomain("liri-project.me"); 31 | app.setApplicationName("liri-browser"); 32 | 33 | // Load Translations 34 | QTranslator qtTranslator; 35 | qtTranslator.load("qt_" + QLocale::system().name(), 36 | QLibraryInfo::location(QLibraryInfo::TranslationsPath)); 37 | app.installTranslator(&qtTranslator); 38 | 39 | QTranslator translator; 40 | translator.load(":/translations/" + QLocale::system().name()); 41 | app.installTranslator(&translator); 42 | 43 | qputenv("QTWEBENGINE_REMOTE_DEBUGGING", "0.0.0.0:9992"); 44 | 45 | // Initialize QtWebEngine (and some attempts to activate pepper plugins, but it doesn't work) 46 | //QWebEngineSettings::globalSettings()->setAttribute(QWebEngineSettings::PluginsEnabled, true); 47 | QtWebEngine::initialize(); 48 | //QWebEngineSettings::globalSettings()->setAttribute(QWebEngineSettings::JavascriptEnabled, false); 49 | /*QWebEngineSettings *defaultSettings = QWebEngineSettings::globalSettings(); 50 | defaultSettings->setAttribute(QWebEngineSettings::PluginsEnabled, true); 51 | QWebEngineSettings::globalSettings()->setAttribute(QWebEngineSettings::PluginsEnabled, true);*/ 52 | 53 | QQmlApplicationEngine * appEngine = new QQmlApplicationEngine(); 54 | //appEngine.rootContext()->setContextProperty("utils", &utils); 55 | qmlRegisterType("Clipboard", 1, 0, "Clipboard"); 56 | appEngine->load(QUrl("qrc:/qml/DesktopApplication.qml")); 57 | QMetaObject::invokeMethod(appEngine->rootObjects().first(), "load"); 58 | appEngine->rootContext()->setContextProperty("G_Cursor",new Cursor); 59 | 60 | // Load plugins 61 | PluginsEngine * pluginsEngine = new PluginsEngine(appEngine, config); 62 | appEngine->rootContext()->setContextProperty("PluginsEngine", pluginsEngine); 63 | pluginsEngine->loadPlugins(); 64 | pluginsEngine->trigger(QString("load")); 65 | 66 | return app.exec(); 67 | } 68 | -------------------------------------------------------------------------------- /src/plugins/api.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "api.h" 11 | #include "urlopener.h" 12 | 13 | QStringList PluginAPI::events = QStringList() << QString("load") 14 | << QString("omnibox.search"); // QJSValue text 15 | 16 | QMap PluginAPI::eventFeatures = QMap(); 17 | 18 | 19 | PluginAPI::PluginAPI(QString pluginName, QVariantList features, QQmlApplicationEngine *appEngine, Config *config, QObject *parent) : QObject(parent){ 20 | eventFeatures["load"] = QVariant("common"); 21 | eventFeatures["omnibox.search"] = QVariant("omniplet"); 22 | 23 | this->pluginName = pluginName; 24 | this->appEngine = appEngine; 25 | this->config = config; 26 | this->features = features; 27 | 28 | for (int i=0; ievents.length(); i++) { 29 | eventsMap.insert(this->events[i], QList()); 30 | 31 | } 32 | } 33 | 34 | bool PluginAPI::hasFeature(QVariant feature) { 35 | return feature == "common" || this->features.contains(feature); 36 | } 37 | 38 | bool PluginAPI::hasPermission(QVariant feature){ 39 | return feature == "common" || ( hasFeature(feature) && this->config->getPluginIsPermitted(this->pluginName, feature.toString()) ); 40 | } 41 | 42 | const QString PluginAPI::print(QString text) { 43 | qDebug() << "plugin" << this->pluginName << ":" << text; 44 | return text; 45 | } 46 | 47 | bool PluginAPI::on(QString event, QJSValue callback) { 48 | if (events.contains(event)) { 49 | if (this->hasPermission(this->eventFeatures[event])) { 50 | eventsMap[event].append(callback); 51 | qDebug() << "plugin" << this->pluginName << "successfully registered callback for event" << event; 52 | return true; 53 | } 54 | else { 55 | qWarning() << "plugin" << this->pluginName << "tried to register callback for event" << event << "without permission"; 56 | return false; 57 | } 58 | } 59 | else { 60 | qWarning() << "plugin" << this->pluginName << "tries to set callback for event" << event << "without permission"; 61 | return false; 62 | } 63 | } 64 | 65 | bool PluginAPI::trigger(QString event, QJSValueList args) { 66 | if (!events.contains(event)) { 67 | qWarning() << "Trying to trigger non-existing event" << event; 68 | return false; 69 | } 70 | QList events = eventsMap[event]; 71 | for (int i=0; ifetch(url, callback); 82 | return true; 83 | } 84 | else { 85 | qWarning() << "blocked network access for plugin" << this->pluginName; 86 | return false; 87 | } 88 | } 89 | 90 | bool PluginAPI::appendSearchSuggestion(QJSValue text, QJSValue icon, QJSValue insert) { 91 | if (hasPermission("omniplet")) { 92 | QObject *rootObject = this->appEngine->rootObjects().first(); 93 | QObject *qmlObject = rootObject->findChild("searchSuggestionsModel"); 94 | QMetaObject::invokeMethod(qmlObject, 95 | "appendSuggestion", 96 | Q_ARG(QVariant, text.toVariant()), 97 | Q_ARG(QVariant, icon.toVariant()), 98 | Q_ARG(QVariant, insert.toVariant()) 99 | ); 100 | return true; 101 | } 102 | else { 103 | qWarning() << "blocked omniplet access for plugin" << this->pluginName; 104 | return false; 105 | } 106 | } 107 | 108 | 109 | QVariant PluginAPI::getHistory(){ 110 | if (hasPermission("history")) { 111 | QVariant retValue; 112 | QMetaObject::invokeMethod(this->appEngine->rootObjects().first(), "getHistoryArray", 113 | Q_RETURN_ARG(QVariant, retValue)); 114 | return QVariant::fromValue(retValue.toList()); 115 | } 116 | else { 117 | qWarning() << "blocked history access for plugin" << this->pluginName; 118 | return QVariant(false); 119 | } 120 | } 121 | 122 | 123 | QVariant PluginAPI::getBookmarks(){ 124 | if (hasPermission("bookmarks")) { 125 | QVariant retValue; 126 | QMetaObject::invokeMethod(this->appEngine->rootObjects().first(), "getBookmarksArray", 127 | Q_RETURN_ARG(QVariant, retValue)); 128 | return QVariant::fromValue(retValue.toList()); 129 | } 130 | else { 131 | qWarning() << "blocked bookmarks access for plugin" << this->pluginName; 132 | return QVariant(false); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/plugins/api.h: -------------------------------------------------------------------------------- 1 | #ifndef API_H 2 | #define API_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "../config.h" 12 | 13 | class PluginAPI : public QObject{ 14 | Q_OBJECT 15 | public: 16 | explicit PluginAPI(QString pluginName, QVariantList features, QQmlApplicationEngine *appEngine, Config *config, QObject *parent = 0); 17 | 18 | static QStringList events; 19 | static QMap eventFeatures; 20 | 21 | bool trigger(QString event, QJSValueList args); 22 | 23 | /* API invokable functions */ 24 | 25 | // Basic console output 26 | Q_INVOKABLE const QString print(QString text); 27 | 28 | // Register callbacks on events 29 | Q_INVOKABLE bool on(QString event, QJSValue callback); 30 | 31 | // Network functionality 32 | Q_INVOKABLE bool fetchURL(QUrl url, QJSValue callback); 33 | 34 | // Search Suggestions 35 | Q_INVOKABLE bool appendSearchSuggestion(QJSValue text, QJSValue icon, QJSValue insert=QJSValue("end")); 36 | 37 | // Browser history 38 | Q_INVOKABLE QVariant getHistory(); 39 | 40 | // Browser bookmarks 41 | Q_INVOKABLE QVariant getBookmarks(); 42 | private: 43 | const QString version = QString("0.1"); 44 | QString pluginName; 45 | QVariantList features; 46 | 47 | QMap> eventsMap; 48 | QQmlApplicationEngine *appEngine; 49 | Config *config; 50 | 51 | bool hasFeature(QVariant feature); 52 | bool hasPermission(QVariant feature); 53 | }; 54 | 55 | #endif // API_H 56 | -------------------------------------------------------------------------------- /src/plugins/plugin.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "plugin.h" 9 | #include "api.h" 10 | 11 | Plugin::Plugin(QString name, QString path, QQmlApplicationEngine *appEngine, Config *config, QObject *parent) : QObject(parent){ 12 | this->name = name; 13 | this->path = path; 14 | this->appEngine = appEngine; 15 | this->config = config; 16 | } 17 | 18 | 19 | void Plugin::load() { 20 | loadManifest(); 21 | bool success = loadEngine(); 22 | if (success) 23 | loadScript(); 24 | } 25 | 26 | 27 | bool Plugin::loadEngine(){ 28 | qDebug() << "Plugin API version:" << this->apiVersion; 29 | if (this->apiVersion == "0.1") { 30 | PluginAPI * apiObject = new PluginAPI(name, this->features, this->appEngine, this->config); 31 | QJSValue jsApi = engine.newQObject(apiObject); 32 | this->api = apiObject; 33 | engine.globalObject().setProperty("liri", jsApi); 34 | } 35 | else { 36 | qWarning() << "Plugin uses unknown API version. Cancel loading."; 37 | return false; 38 | } 39 | return true; 40 | } 41 | 42 | 43 | void Plugin::loadScript(){ 44 | QFile scriptFile(path + "/main.js"); 45 | if (!scriptFile.open(QIODevice::ReadOnly)) 46 | qWarning("Couldn't open script file."); 47 | QTextStream stream(&scriptFile); 48 | QString contents = stream.readAll(); 49 | scriptFile.close(); 50 | engine.evaluate(contents, path); 51 | 52 | } 53 | 54 | 55 | bool Plugin::loadManifest() { 56 | QFile manifestFile(path + "/manifest.json"); 57 | 58 | if (!manifestFile.open(QIODevice::ReadOnly)) { 59 | qWarning("Couldn't open manifest file."); 60 | return false; 61 | } 62 | 63 | QByteArray jsonData = manifestFile.readAll(); 64 | QJsonDocument manifestDoc (QJsonDocument::fromJson(jsonData)); 65 | 66 | auto json = manifestDoc.object(); 67 | // TODO: Validate 68 | 69 | this->title = json["title"].toString(); 70 | this->description = json["description"].toString(); 71 | this->maintainer = json["maintainer"].toString(); 72 | this->version = json["version"].toString(); 73 | this->apiVersion = json["api"].toString(); 74 | this->features = json["features"].toArray().toVariantList(); 75 | 76 | return true; 77 | } 78 | 79 | 80 | bool Plugin::trigger(QString event, QJSValueList args){ 81 | return this->api->trigger(event, args); 82 | } 83 | -------------------------------------------------------------------------------- /src/plugins/plugin.h: -------------------------------------------------------------------------------- 1 | #ifndef PLUGIN_H 2 | #define PLUGIN_H 3 | 4 | #include 5 | #include 6 | #include "api.h" 7 | #include 8 | #include "../config.h" 9 | 10 | class Plugin : public QObject{ 11 | Q_OBJECT 12 | public: 13 | explicit Plugin(QString name, QString path, QQmlApplicationEngine *appEngine, Config *config, QObject *parent = 0); 14 | 15 | void load(); 16 | bool trigger(QString event, QJSValueList args=QJSValueList()); 17 | 18 | private: 19 | void createEngine(); 20 | bool loadManifest(); 21 | bool loadEngine(); 22 | void loadScript(); 23 | 24 | QJSEngine engine; 25 | QString name; 26 | QString path; 27 | QString title; 28 | QString description; 29 | QString maintainer; 30 | QString apiVersion; 31 | QString version; 32 | QVariantList features; 33 | 34 | PluginAPI * api; 35 | 36 | QQmlApplicationEngine *appEngine; 37 | Config *config; 38 | }; 39 | 40 | #endif // PLUGIN_H 41 | -------------------------------------------------------------------------------- /src/plugins/pluginsengine.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "pluginsengine.h" 5 | #include "plugin.h" 6 | 7 | PluginsEngine::PluginsEngine(QQmlApplicationEngine *appEngine, Config *config, QObject *parent) : QObject(parent){ 8 | this->appEngine = appEngine; 9 | this->config = config; 10 | } 11 | 12 | void PluginsEngine::loadPlugin(QString name, QString path) { 13 | qDebug() << "Loading plugin" << name << path; 14 | Plugin *plugin = new Plugin(name, path, this->appEngine, this->config); 15 | this->plugins[name] = plugin; 16 | plugin->load(); 17 | } 18 | 19 | void PluginsEngine::loadPlugins(){ 20 | qDebug() << "Loading plugins"; 21 | QDir pluginsDirectory; 22 | pluginsDirectory.setCurrent("plugins/"); 23 | pluginsDirectory.setFilter(QDir::Dirs | QDir::NoDotAndDotDot); 24 | for (unsigned int i=0; iplugins.values().length(); i++){ 34 | this->plugins.values()[i]->trigger(event, this->newArgsList(args)); 35 | } 36 | return true; 37 | } 38 | 39 | bool PluginsEngine::trigger(QJSValue event, QJSValueList args){ 40 | return PluginsEngine::trigger(event.toString(), args); 41 | } 42 | 43 | bool PluginsEngine::trigger(QJSValue event, QString text){ 44 | return PluginsEngine::trigger(event.toString(), QJSValueList() << QJSValue(text)); 45 | } 46 | 47 | QJSValueList PluginsEngine::newArgsList(QJSValueList args){ 48 | // Make sure there's one list created for each plugin 49 | QJSValueList newList; 50 | for (int i=0; i 5 | #include 6 | #include 7 | #include "plugin.h" 8 | #include "../config.h" 9 | 10 | class PluginsEngine : public QObject{ 11 | Q_OBJECT 12 | public: 13 | explicit PluginsEngine(QQmlApplicationEngine *appEngine, Config *config, QObject *parent = 0); 14 | 15 | QMap plugins; 16 | 17 | void loadPlugins(); 18 | void loadPlugin(QString name, QString path); 19 | 20 | bool trigger(QString event, QJSValueList args=QJSValueList()); 21 | Q_INVOKABLE bool trigger(QJSValue event, QJSValueList args=QJSValueList()); 22 | Q_INVOKABLE bool trigger(QJSValue event, QString text); 23 | 24 | private: 25 | QJSValueList newArgsList(QJSValueList args); 26 | 27 | QQmlApplicationEngine *appEngine; 28 | Config *config; 29 | }; 30 | 31 | #endif // PLUGINSENGINE_H 32 | -------------------------------------------------------------------------------- /src/plugins/urlopener.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "urlopener.h" 3 | 4 | void URLOpener::replyFinished(QNetworkReply *reply) 5 | { 6 | this->callback.call(QJSValueList() << QJSValue(QString(reply->readAll()))); 7 | } 8 | 9 | void URLOpener::fetch(QUrl url, QJSValue callback) 10 | { 11 | this->callback = callback; 12 | manager = new QNetworkAccessManager(this); 13 | connect(manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(replyFinished(QNetworkReply*))); 14 | manager->get(QNetworkRequest(url)); 15 | } 16 | -------------------------------------------------------------------------------- /src/plugins/urlopener.h: -------------------------------------------------------------------------------- 1 | #ifndef URLOPENER_H 2 | #define URLOPENER_H 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | class URLOpener : public QObject 12 | { 13 | Q_OBJECT 14 | QNetworkAccessManager *manager; 15 | 16 | private slots: 17 | void replyFinished(QNetworkReply *); 18 | public: 19 | void fetch(QUrl url, QJSValue callback); 20 | private: 21 | QJSValue callback; 22 | }; 23 | 24 | 25 | #endif // URLOPENER_H 26 | -------------------------------------------------------------------------------- /src/qml.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | qml/LoadingIndicator.qml 4 | qml/BrowserWebView.qml 5 | qml/BookmarkItem.qml 6 | qml/ShortcutActions.qml 7 | qml/BrowserWindow.qml 8 | qml/DownloadsDrawer.qml 9 | qml/ColorPicker.qml 10 | qml/HistoryDrawer.qml 11 | qml/BookmarksDrawer.qml 12 | qml/NewTabPage.qml 13 | qml/SettingsView.qml 14 | qml/ColorChooser.qml 15 | qml/TabBarItemDelegate.qml 16 | qml/BrowserPage.qml 17 | qml/BrowserTabBar.qml 18 | qml/BrowserToolbar.qml 19 | qml/BookmarksBar.qml 20 | qml/FullscreenBar.qml 21 | qml/Omnibox.qml 22 | qml/BaseApplication.qml 23 | qml/Settings.qml 24 | qml/SettingsPage.qml 25 | qml/DesktopApplication.qml 26 | qml/TabsList.qml 27 | qml/TabsListPage.qml 28 | qml/SitesColors.qml 29 | qml/SitesColorsList.qml 30 | qml/MaterialWindow.qml 31 | qml/ResizeArea.qml 32 | qml/SystemBar.qml 33 | qml/SystemButtons.qml 34 | translations/ar_MA.qm 35 | translations/de_DE.qm 36 | translations/es_CR.qm 37 | translations/es_ES.qm 38 | translations/fr_FR.qm 39 | translations/pt_BR.qm 40 | translations/pt_PT.qm 41 | translations/ru_RU.qm 42 | qml/SitesColorsPage.qml 43 | qml/QuickSearches.qml 44 | qml/QuickSearchesList.qml 45 | qml/QuickSearchesPage.qml 46 | qml/MenuFieldThemed.qml 47 | qml/TextFieldThemed.qml 48 | qml/ScrollbarThemed.qml 49 | qml/PlayerPage.qml 50 | qml/AwesomeActionButton.qml 51 | qml/SearchSuggestions.qml 52 | qml/BrowserView.qml 53 | qml/BaseBrowserView.qml 54 | qml/QuickSearchesView.qml 55 | qml/SitesColorsView.qml 56 | qml/RightDrawer.qml 57 | qml/ShadowOverlay.qml 58 | 59 | 60 | -------------------------------------------------------------------------------- /src/qml/AwesomeActionButton.qml: -------------------------------------------------------------------------------- 1 | // This is a modified version of: 2 | /* 3 | * QML Material - An application framework implementing Material Design. 4 | * Copyright (C) 2015 Michael Spencer 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as 8 | * published by the Free Software Foundation, either version 2.1 of the 9 | * License, or (at your option) any later version. 10 | * 11 | * This program 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 Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program. If not, see . 18 | */ 19 | import QtQuick 2.0 20 | import QtQuick.Controls 1.2 as Controls 21 | import QtQuick.Controls.Styles 1.2 as ControlStyles 22 | import Material 0.2 23 | import QtGraphicalEffects 1.0 24 | 25 | /*! 26 | \qmltype ActionButton 27 | \inqmlmodule Material 0.1 28 | 29 | \brief A floating action button. 30 | 31 | An ActionButton is a floating action button that provides a primary action 32 | on the current page. 33 | */ 34 | Controls.Button { 35 | id: button 36 | 37 | /*! 38 | The color of the action button. By default, this is the accent color of your 39 | app as defined by \l Theme::accentColor. 40 | */ 41 | property color backgroundColor: Theme.accentColor 42 | 43 | /*! 44 | \internal 45 | The elevation of the icon. This will be higher for a white background color. 46 | */ 47 | property int elevation: backgroundColor == "white" ? 0 : 1 48 | 49 | /*! 50 | The color of the icon displayed on the action button. By default, this is 51 | automatically selected based on the \l backgroundColor. 52 | */ 53 | property color iconColor: Theme.lightDark(button.backgroundColor, 54 | Theme.light.iconColor, 55 | Theme.dark.iconColor) 56 | 57 | /*! 58 | The name of the icon to display in the action button, selected from the Material 59 | Design icon collection by Google. 60 | */ 61 | property string iconName 62 | 63 | /*! 64 | Floating action buttons come in two sizes: 65 | 66 | \list 67 | \li \b {Default size} - for most use cases 68 | \li \b {Mini size} - only used to create visual continuity with other screen elements 69 | \endlist 70 | */ 71 | property bool isMiniSize: false 72 | 73 | style: ControlStyles.ButtonStyle { 74 | padding { 75 | left: 0 76 | right: 0 77 | top: 0 78 | bottom: 0 79 | } 80 | 81 | background: Item { 82 | RectangularGlow { 83 | 84 | anchors.centerIn: parent 85 | anchors.verticalCenterOffset: elevation == 1 ? Units.dp(1.5) 86 | : Units.dp(1) 87 | 88 | width: parent.width 89 | height: parent.height 90 | 91 | glowRadius: elevation == 1 ? Units.dp(0.75) : Units.dp(0.3) 92 | opacity: elevation == 1 ? 0.6 : 0.3 93 | spread: elevation == 1 ? 0.7 : 0.85 94 | color: "black" 95 | cornerRadius: height/2 96 | } 97 | 98 | View { 99 | anchors.fill: parent 100 | radius: width/2 101 | 102 | backgroundColor: button.backgroundColor 103 | 104 | tintColor: control.pressed || 105 | (control.focus && !button.elevation) || 106 | (control.hovered && !button.elevation) ? 107 | Qt.rgba(0,0,0, control.pressed ? 0.1 : 0.05) : "transparent" 108 | 109 | Ink { 110 | id: mouseArea 111 | anchors.fill: parent 112 | Connections { 113 | target: control.__behavior 114 | onPressed: mouseArea.onPressed(mouse) 115 | onCanceled: mouseArea.onCanceled() 116 | onReleased: mouseArea.onReleased(mouse) 117 | } 118 | 119 | circular: true 120 | } 121 | } 122 | } 123 | label: Item { 124 | implicitHeight: isMiniSize ? Units.dp(40) : Units.dp(56) 125 | implicitWidth: implicitHeight 126 | AwesomeIcon { 127 | id: icon 128 | anchors.centerIn: parent 129 | name: control.iconName 130 | color: button.iconColor 131 | size: Units.dp(24) 132 | } 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/qml/BaseBrowserView.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | 3 | Item { 4 | 5 | property var icon: Qt.resolvedUrl("") 6 | property string title: "New tab" 7 | property string url: "" 8 | property bool loading: false 9 | property real loadProgress: 1.0 10 | property bool reloadable: false 11 | 12 | property bool canGoBack: false 13 | property bool canGoForward: false 14 | property bool secureConnection: false 15 | 16 | property var customColor: false 17 | property var customColorLight: false 18 | property var customTextColor: false 19 | property bool hasCloseButton: true 20 | 21 | } 22 | 23 | -------------------------------------------------------------------------------- /src/qml/BookmarkItem.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Layouts 1.0 3 | import Material 0.2 4 | import Material.ListItems 0.1 as ListItem 5 | 6 | 7 | Item { 8 | id: item 9 | property int maximumWidth: Units.dp(148) 10 | 11 | property color color: "white" 12 | 13 | height: bookmarksBar.height 14 | width: row.childrenRect.width 15 | property int maximumTextWidth: maximumWidth - favicon.width - Units.dp(16) 16 | 17 | states: [ 18 | State { 19 | name: "default" 20 | }, 21 | 22 | State { 23 | name: "dragging"; when: mouseArea.draggingId === uid 24 | PropertyChanges { 25 | target: row 26 | x: mouseArea.mouseX-row.width/2; 27 | z: 10; 28 | parent: bookmarkView 29 | anchors.fill: null 30 | y: item.y 31 | width: item.width 32 | height: item.height 33 | } 34 | } 35 | ] 36 | 37 | Row { 38 | id: row 39 | spacing: Units.dp(5) 40 | anchors.fill: parent 41 | 42 | Image { 43 | id: favicon 44 | source: faviconUrl 45 | width: Units.dp(18) 46 | height: Units.dp(18) 47 | anchors.verticalCenter: parent.verticalCenter 48 | } 49 | 50 | Text { 51 | id: lblTitle 52 | text: title 53 | color: root.currentIconColor 54 | elide: Text.ElideRight 55 | smooth: true 56 | clip: true 57 | font.family: root.fontFamily 58 | 59 | anchors.verticalCenter: parent.verticalCenter 60 | 61 | Component.onCompleted: { 62 | if (paintedWidth > maximumTextWidth) { 63 | width = maximumTextWidth; 64 | } 65 | else { 66 | width = paintedWidth; 67 | } 68 | } 69 | } 70 | 71 | } 72 | 73 | MouseArea { 74 | anchors.fill: parent 75 | acceptedButtons: Qt.LeftButton | Qt.RightButton 76 | onClicked: { 77 | if(mouse.button & Qt.RightButton) { 78 | contextMenu.open(parent, contextMenu.width-parent.width) 79 | } 80 | else 81 | root.addTab(url); 82 | } 83 | } 84 | 85 | Dropdown { 86 | id: contextMenu 87 | 88 | width: Units.dp(250) 89 | height: columnView.height + Units.dp(16) 90 | 91 | ColumnLayout { 92 | id: columnView 93 | width: parent.width 94 | anchors.centerIn: parent 95 | 96 | ListItem.Standard { 97 | text: qsTr("Edit") 98 | iconName: "image/edit" 99 | onClicked: editDialog.open(item); 100 | } 101 | 102 | ListItem.Standard { 103 | text: qsTr("Add to dash") 104 | iconName: "action/dashboard" 105 | onClicked: root.addToDash(url, title, color); 106 | } 107 | 108 | ListItem.Standard { 109 | text: qsTr("Delete") 110 | iconName: "action/delete" 111 | onClicked: { 112 | root.removeBookmark(url); 113 | contextMenu.close(); 114 | } 115 | } 116 | 117 | } 118 | } 119 | 120 | Popover { 121 | id: editDialog 122 | 123 | width: Units.dp(400) 124 | height: col.childrenRect.height + Units.dp(24) 125 | 126 | dismissOnTap: true 127 | 128 | Column { 129 | id: col 130 | anchors.fill: parent 131 | anchors.margins: Units.dp(24) 132 | spacing: Units.dp(24) 133 | 134 | Item { 135 | width: parent.width 136 | height: Units.dp(24) 137 | Text { 138 | id: name 139 | anchors.verticalCenter: parent.verticalCenter 140 | font.family: root.fontFamily 141 | text: qsTr("Edit bookmark") 142 | } 143 | 144 | IconButton { 145 | anchors.verticalCenter: parent.verticalCenter 146 | anchors.right: parent.right 147 | iconName: "navigation/close" 148 | onClicked: editDialog.close() 149 | } 150 | 151 | } 152 | 153 | TextField { 154 | id: txtEditTitle 155 | placeholderText: qsTr("Title") 156 | floatingLabel: true 157 | text: title 158 | width: parent.width 159 | } 160 | 161 | TextField { 162 | id: txtEditUrl 163 | placeholderText: qsTr("URL") 164 | floatingLabel: true 165 | text: url 166 | width: parent.width 167 | 168 | } 169 | 170 | TextField { 171 | id: txtEditFaviconUrl 172 | placeholderText: qsTr("Icon URL") 173 | floatingLabel: true 174 | text: faviconUrl 175 | width: parent.width 176 | } 177 | 178 | } 179 | 180 | Button { 181 | id: btnEditCancel 182 | anchors.bottom: parent.bottom 183 | anchors.bottomMargin: Units.dp(24) 184 | anchors.right: btnEditApply.left 185 | 186 | text: qsTr("Cancel") 187 | 188 | onClicked: { 189 | txtEditTitle.text = title; 190 | txtEditUrl.text = url; 191 | txtEditFaviconUrl.text = faviconUrl; 192 | editDialog.close(); 193 | } 194 | } 195 | 196 | Button { 197 | id: btnEditApply 198 | 199 | anchors.bottom: parent.bottom 200 | anchors.bottomMargin: Units.dp(24) 201 | anchors.rightMargin: Units.dp(24) 202 | anchors.right: parent.right 203 | 204 | text: qsTr("Apply") 205 | 206 | onClicked: { 207 | root.changeBookmark(url, txtEditTitle.text, txtEditUrl.text, txtEditFaviconUrl.text); 208 | editDialog.close(); 209 | } 210 | } 211 | 212 | 213 | } 214 | 215 | 216 | } 217 | -------------------------------------------------------------------------------- /src/qml/BookmarksBar.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | import Material 0.2 3 | import Material.ListItems 0.1 as ListItem 4 | import QtQuick.Layouts 1.0 5 | import QtQuick.Controls 1.2 as Controls 6 | 7 | Rectangle { 8 | id: bookmarksBar 9 | color: toolbar.backgroundColor 10 | height: visible ? Units.dp(48) : 0 11 | 12 | anchors { 13 | left: parent.left 14 | right: parent.right 15 | } 16 | 17 | ListView { 18 | id: bookmarkView 19 | boundsBehavior: Flickable.StopAtBounds 20 | anchors.leftMargin: Units.dp(20) 21 | anchors.rightMargin: Units.dp(20) 22 | anchors.fill: parent 23 | model: root.app.bookmarksModel 24 | orientation: ListView.Horizontal 25 | spacing: Units.dp(20) 26 | delegate: BookmarkItem {} 27 | 28 | interactive: mouseArea.draggingId === -1 29 | 30 | MouseArea { 31 | id: mouseArea 32 | 33 | anchors.fill: parent 34 | 35 | hoverEnabled: true 36 | property int index: bookmarkView.indexAt(mouseX + bookmarkView.contentX, mouseY) 37 | property int draggingId: -1 38 | property int activeIndex 39 | propagateComposedEvents: true 40 | 41 | onClicked: mouse.accepted = false; 42 | 43 | onPressAndHold: { 44 | var item = bookmarkView.itemAt(mouseX + bookmarkView.contentX, mouseY); 45 | if(item !== null) { 46 | bookmarkView.model.get(activeIndex=index).state = "dragging"; 47 | draggingId = bookmarkView.model.get(activeIndex=index).uid; 48 | } 49 | 50 | } 51 | onReleased: { 52 | draggingId = -1 53 | mouse.accepted = false; 54 | } 55 | onPositionChanged: { 56 | if (draggingId != -1 && index != -1 && index != activeIndex) { 57 | bookmarkView.model.move(activeIndex, activeIndex = index, 1); 58 | root.app.saveBookmarks(); 59 | } 60 | mouse.accepted = false; 61 | 62 | } 63 | onDoubleClicked: { 64 | mouse.accepted = false; 65 | } 66 | 67 | onWheel: { 68 | bookmarkView.flick(wheel.angleDelta.y*10, 0); 69 | } 70 | } 71 | 72 | } 73 | 74 | Behavior on height { 75 | NumberAnimation { 76 | duration: 200 77 | easing.type: Easing.InOutQuad 78 | } 79 | } 80 | } 81 | 82 | -------------------------------------------------------------------------------- /src/qml/BookmarksDrawer.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 1.2 3 | import Material 0.2 4 | import Material.ListItems 0.1 as ListItem 5 | import QtQuick.Layouts 1.0 6 | 7 | RightDrawer { 8 | id: drawer 9 | 10 | View { 11 | id: bookmarksTitle 12 | height: Units.dp(56) 13 | width: parent.width 14 | elevation: listView.contentY > 0 ? 1 : 0 15 | backgroundColor: "white" 16 | z: 1 17 | 18 | Label { 19 | id: label 20 | anchors { 21 | left: parent.left 22 | right: parent.right 23 | leftMargin: Units.dp(16) 24 | rightMargin: Units.dp(16) 25 | verticalCenter: parent.verticalCenter 26 | } 27 | text: qsTr("Bookmarks") 28 | style: "title" 29 | } 30 | 31 | } 32 | 33 | ScrollView { 34 | anchors { 35 | left: parent.left 36 | right: parent.right 37 | top: bookmarksTitle.bottom 38 | bottom: parent.bottom 39 | } 40 | 41 | ListView { 42 | id: listView 43 | 44 | //bottomMargin: Units.dp(8) 45 | interactive: count > 0 46 | model: root.app.bookmarksModel 47 | delegate: bookmarksItemDelegate 48 | 49 | Column { 50 | visible: listView.count == 0 51 | anchors.centerIn: parent 52 | spacing: Units.dp(8) 53 | 54 | Icon { 55 | name: "action/bookmark_border" 56 | size: Units.dp(48) 57 | anchors.horizontalCenter: parent.horizontalCenter 58 | } 59 | 60 | Label { 61 | style: "subheading" 62 | color: Theme.light.subTextColor 63 | text: qsTr("No bookmarks found") 64 | font.pixelSize: Units.dp(17) 65 | anchors.horizontalCenter: parent.horizontalCenter 66 | } 67 | } 68 | } 69 | } 70 | 71 | Component { 72 | id: bookmarksItemDelegate 73 | 74 | Item { 75 | height: childrenRect.height 76 | 77 | anchors { 78 | left: parent.left 79 | right: parent.right 80 | } 81 | 82 | ListItem.Standard { 83 | text: title 84 | action: [ 85 | Image { 86 | id: favImage 87 | anchors.centerIn: parent 88 | source: faviconUrl ? faviconUrl : "" 89 | height: Units.dp(20) 90 | width: Units.dp(20) 91 | }, 92 | Icon { 93 | anchors.centerIn: parent 94 | name: "social/public" 95 | size: Units.dp(22) 96 | visible: favImage.status !== Image.Ready 97 | } 98 | ] 99 | onClicked: root.addTab(url) 100 | IconButton { 101 | iconName: "action/settings" 102 | size: Units.dp(20) 103 | anchors { 104 | right: parent.right 105 | verticalCenter: parent.verticalCenter 106 | margins: Units.dp(15) 107 | } 108 | onClicked: { 109 | contextMenu.open(parent, 0) 110 | } 111 | } 112 | Dropdown { 113 | id: contextMenu 114 | 115 | width: Units.dp(250) 116 | height: columnView.height + Units.dp(16) 117 | 118 | ColumnLayout { 119 | id: columnView 120 | width: parent.width 121 | anchors.centerIn: parent 122 | 123 | ListItem.Standard { 124 | text: qsTr("Edit") 125 | iconName: "image/edit" 126 | onClicked: editDialog.open(bookmarksTitle); 127 | } 128 | 129 | ListItem.Standard { 130 | text: qsTr("Add to dash") 131 | iconName: "action/dashboard" 132 | onClicked: root.addToDash(url, title, color); 133 | } 134 | 135 | ListItem.Standard { 136 | text: qsTr("Delete") 137 | iconName: "action/delete" 138 | onClicked: { 139 | root.removeBookmark(url); 140 | contextMenu.close(); 141 | } 142 | } 143 | 144 | } 145 | } 146 | 147 | Popover { 148 | id: editDialog 149 | 150 | width: Units.dp(400) 151 | height: col.childrenRect.height + Units.dp(24) 152 | 153 | dismissOnTap: true 154 | 155 | Column { 156 | id: col 157 | anchors.fill: parent 158 | anchors.margins: Units.dp(24) 159 | spacing: Units.dp(24) 160 | 161 | Item { 162 | width: parent.width 163 | height: Units.dp(24) 164 | Text { 165 | id: name 166 | anchors.verticalCenter: parent.verticalCenter 167 | font.family: root.fontFamily 168 | text: qsTr("Edit bookmark") 169 | } 170 | 171 | IconButton { 172 | anchors.verticalCenter: parent.verticalCenter 173 | anchors.right: parent.right 174 | iconName: "navigation/close" 175 | onClicked: editDialog.close() 176 | } 177 | 178 | } 179 | 180 | TextField { 181 | id: txtEditTitle 182 | placeholderText: qsTr("Title") 183 | floatingLabel: true 184 | text: title 185 | width: parent.width 186 | } 187 | 188 | TextField { 189 | id: txtEditUrl 190 | placeholderText: qsTr("URL") 191 | floatingLabel: true 192 | text: url 193 | width: parent.width 194 | 195 | } 196 | 197 | TextField { 198 | id: txtEditFaviconUrl 199 | placeholderText: qsTr("Icon URL") 200 | floatingLabel: true 201 | text: faviconUrl 202 | width: parent.width 203 | } 204 | 205 | } 206 | 207 | Button { 208 | id: btnEditCancel 209 | anchors.bottom: parent.bottom 210 | anchors.bottomMargin: Units.dp(24) 211 | anchors.right: btnEditApply.left 212 | 213 | text: qsTr("Cancel") 214 | 215 | onClicked: { 216 | txtEditTitle.text = title; 217 | txtEditUrl.text = url; 218 | txtEditFaviconUrl.text = faviconUrl; 219 | editDialog.close(); 220 | } 221 | } 222 | 223 | Button { 224 | id: btnEditApply 225 | 226 | anchors.bottom: parent.bottom 227 | anchors.bottomMargin: Units.dp(24) 228 | anchors.rightMargin: Units.dp(24) 229 | anchors.right: parent.right 230 | 231 | text: qsTr("Apply") 232 | 233 | onClicked: { 234 | root.changeBookmark(url, txtEditTitle.text, txtEditUrl.text, txtEditFaviconUrl.text); 235 | editDialog.close(); 236 | } 237 | } 238 | 239 | 240 | } 241 | } 242 | } 243 | } 244 | 245 | 246 | } 247 | -------------------------------------------------------------------------------- /src/qml/BrowserTabBar.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | import Material 0.2 3 | import Material.ListItems 0.1 as ListItem 4 | import QtQuick.Layouts 1.0 5 | import QtQuick.Controls 1.2 as Controls 6 | 7 | Rectangle { 8 | id: tabBar 9 | 10 | height: root.tabHeight 11 | property color chosenColor: root.activeTab.view.customColor ? root.activeTab.view.customColor : root.app.lightThemeColor 12 | color: root.app.elevatedToolbar ? shadeColor(chosenColor,-0.1) : "transparent" 13 | anchors { 14 | left: parent.left 15 | rightMargin: root.app.customFrame ? Units.dp(100) : 0 16 | right: parent.right 17 | } 18 | 19 | property alias listView: listView 20 | 21 | ListView { 22 | id: listView 23 | 24 | anchors { 25 | top: parent.top 26 | bottom: parent.bottom 27 | left: parent.left 28 | } 29 | 30 | width: contentWidth < (tabBar.width - toolbarIntegrated.width) ? contentWidth : parent.width - toolbarIntegrated.width 31 | 32 | orientation: ListView.Horizontal 33 | boundsBehavior: Flickable.StopAtBounds 34 | spacing: Units.dp(1) 35 | interactive: mouseArea.draggingId == -1 36 | 37 | model: tabsModel 38 | 39 | delegate: TabBarItemDelegate {} 40 | 41 | clip: true 42 | 43 | MouseArea { 44 | id: mouseArea 45 | anchors { 46 | top: parent.top 47 | bottom: parent.bottom 48 | left: parent.left 49 | right: parent.right 50 | } 51 | 52 | hoverEnabled: true 53 | property int index: listView.indexAt(mouseX + listView.contentX, mouseY) 54 | property int draggingId: -1 55 | property int activeIndex 56 | propagateComposedEvents: true 57 | 58 | onClicked: mouse.accepted = false; 59 | 60 | onPressed: { 61 | if (root.activeTabInEditMode) { 62 | mouse.accepted = false; 63 | } 64 | } 65 | 66 | onPressAndHold: { 67 | if (root.activeTabInEditMode) { 68 | mouse.accepted = false; 69 | } else { 70 | var item = listView.itemAt(mouseX + listView.contentX, mouseY); 71 | if(item !== null) { 72 | draggingId = listView.model.get(activeIndex=index).uid; 73 | } 74 | } 75 | 76 | } 77 | onReleased: { 78 | draggingId = -1 79 | mouse.accepted = false; 80 | } 81 | onPositionChanged: { 82 | if (draggingId != -1 && index != -1 && index != activeIndex) { 83 | listView.model.move(activeIndex, activeIndex = index, 1); 84 | } 85 | mouse.accepted = false; 86 | 87 | } 88 | onDoubleClicked: { 89 | mouse.accepted = false; 90 | } 91 | 92 | onWheel: { 93 | listView.flick(wheel.angleDelta.y*10, 0); 94 | } 95 | } 96 | } 97 | 98 | IconButton { 99 | id: btnAddTabFloating 100 | visible: listView.width + toolbarIntegrated.width + width + Units.dp(24) < tabBar.width && mouseArea.draggingId === -1 && !listView.moving 101 | anchors.verticalCenter: parent.verticalCenter 102 | anchors.left: listView.right 103 | anchors.margins:if (root.app.integratedAddressbars) { Units.dp(24) } else { 12 } 104 | color: root.currentIconColor 105 | iconName: "content/add" 106 | 107 | onClicked: addTab(); 108 | } 109 | 110 | Rectangle { 111 | id: toolbarIntegrated 112 | color: "transparent" 113 | anchors.top: parent.top 114 | anchors.bottom: parent.bottom 115 | anchors.right: parent.right 116 | width: root.app.integratedAddressbars ? btnAddTab.width + btnDownloadsIntegrated.width + 117 | btnMenuIntegrated.width + 4 * Units.dp(24) 118 | : Units.dp(48) 119 | 120 | IconButton { 121 | id: btnAddTab 122 | z:30 123 | visible: !btnAddTabFloating.visible 124 | width: visible ? Units.dp(24) : 0 125 | anchors.verticalCenter: parent.verticalCenter 126 | anchors.right: btnDownloadsIntegrated.left 127 | anchors.margins: root.app.integratedAddressbars ? Units.dp(24) : root.app.customFrame ? 3 : 12 128 | anchors.rightMargin: root.app.integratedAddressbars ? Units.dp(24) : root.app.customFrame ? 2 : 12 129 | color: root.currentIconColor 130 | iconName: "content/add" 131 | 132 | onClicked: addTab(); 133 | } 134 | 135 | IconButton { 136 | id: btnDownloadsIntegrated 137 | iconName: "file/file_download" 138 | visible: root.app.webEngine === "qtwebengine" && !mobile && downloadsModel.hasDownloads && root.app.integratedAddressbars 139 | width: visible ? Units.dp(24) : 0 140 | color: root.app.webEngine === "qtwebengine" && downloadsModel.hasActiveDownloads 141 | ? Theme.lightDark(toolbar.color, Theme.accentColor, Theme.dark.iconColor) 142 | : root.currentIconColor 143 | anchors.right: btnMenuIntegrated.left 144 | anchors.verticalCenter: parent.verticalCenter 145 | anchors.margins: visible ? Units.dp(24) : 0 146 | onClicked: downloadsDrawer.open() 147 | } 148 | 149 | IconButton { 150 | id: btnMenuIntegrated 151 | visible: root.app.integratedAddressbars 152 | anchors.right: parent.right 153 | anchors.verticalCenter: parent.verticalCenter 154 | anchors.margins: root.app.integratedAddressbars ? Units.dp(24) : 0 155 | width: root.app.integratedAddressbars ? Units.dp(24) : 0 156 | color: root.currentIconColor 157 | iconName: "navigation/more_vert" 158 | onClicked: overflowMenu.open(btnMenuIntegrated) 159 | } 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/qml/BrowserToolbar.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | import Material 0.2 3 | import Material.ListItems 0.1 as ListItem 4 | import QtQuick.Layouts 1.0 5 | import QtQuick.Controls 1.2 as Controls 6 | 7 | View { 8 | id: toolbar 9 | elevation:0 10 | property color chosenColor: root.activeTab.view.customColor ? root.activeTab.view.customColor : root.app.lightThemeColor 11 | backgroundColor: root.app.elevatedToolbar ? chosenColor : "transparent" 12 | visible: !root.app.integratedAddressbars 13 | 14 | height: root.mobile ? Units.dp(64) : Units.dp(56) 15 | 16 | anchors { 17 | left: parent.left 18 | right: parent.right 19 | } 20 | 21 | property var ubuntuOmniboxOverlay 22 | property alias omnibox: omnibox 23 | property int leftIconsCount: goBackButton.activeInt + goForwardButton.activeInt + refreshButton.activeInt 24 | 25 | RowLayout { 26 | anchors.fill: parent 27 | anchors.leftMargin: spacing 28 | anchors.rightMargin: spacing 29 | z:20 30 | spacing: Units.dp(24) 31 | 32 | Layout.alignment: Qt.AlignVCenter 33 | 34 | IconButton { 35 | id: goBackButton 36 | enabled: root.activeTab.view.canGoBack 37 | property int activeInt: visible ? 1 : 0 38 | onClicked: root.activeTab.view.goBack() 39 | color: root.currentIconColor 40 | Behavior on color { ColorAnimation { duration : 500 }} 41 | action: Action { 42 | iconName: "navigation/arrow_back" 43 | name: root.mobile ? "" : qsTr("Go Back") 44 | } 45 | } 46 | 47 | IconButton { 48 | id: goForwardButton 49 | enabled: root.activeTab.view.canGoForward 50 | onClicked: root.activeTab.view.goForward() 51 | color: root.currentIconColor 52 | property int activeInt: visible ? 1 : 0 53 | Behavior on color { ColorAnimation { duration : 500 }} 54 | visible: root.activeTab.view.canGoForward || !mobile 55 | action: Action { 56 | iconName: "navigation/arrow_forward" 57 | name: root.mobile ? "" : qsTr("Go Forward") 58 | } 59 | } 60 | 61 | IconButton { 62 | hoverAnimation: true 63 | id: refreshButton 64 | color: root.currentIconColor 65 | property int activeInt: visible ? 1 : 0 66 | visible: root.activeTab.view.reloadable 67 | Behavior on color { ColorAnimation { duration : 500 }} 68 | onClicked: !activeTab.view.loading ? activeTab.view.reload() : activeTab.view.stop() 69 | action: Action { 70 | iconName: !activeTab.view.loading ? "navigation/refresh" : "navigation/close" 71 | name:root.mobile ? "" : !activeTab.view.loading ? qsTr("Refresh") : qsTr("Stop") 72 | } 73 | } 74 | 75 | Omnibox { 76 | id: omnibox 77 | 78 | Layout.fillWidth: true 79 | Layout.preferredHeight: parent.height - Units.dp(16) 80 | } 81 | 82 | IconButton { 83 | color: root.currentIconColor 84 | onClicked: {pageStack.push(tabsListPage);root.tabsListIsOpened = true} 85 | visible: mobile 86 | action: Action { 87 | iconName: "action/tab" 88 | name: root.mobile ? "" : qsTr("Tabs") 89 | } 90 | } 91 | 92 | IconButton { 93 | color: root.currentIconColor 94 | Behavior on color { ColorAnimation { duration : 500 }} 95 | onClicked: addTab() 96 | visible: !mobile && (tabsModel.count == 1) 97 | action: Action { 98 | iconName: "content/add" 99 | name: root.mobile ? "" : qsTr("Add a tab") 100 | } 101 | } 102 | 103 | IconButton { 104 | id: bookmarkButton 105 | color: root.currentIconColor 106 | Behavior on color { ColorAnimation { duration : 500 }} 107 | onClicked: toggleActiveTabBookmark() 108 | visible: !mobile 109 | action: Action { 110 | iconName: "action/bookmark_border" 111 | name: qsTr("Bookmark this page") 112 | } 113 | } 114 | 115 | IconButton { 116 | id: downloadsButton 117 | color: root.app.webEngine === "qtwebengine" && downloadsModel.hasActiveDownloads 118 | ? Theme.lightDark(toolbar.color, Theme.accentColor, Theme.dark.iconColor) 119 | : root.currentIconColor 120 | onClicked: downloadsDrawer.open(downloadsButton) 121 | visible: root.app.webEngine === "qtwebengine" && !mobile && downloadsModel.hasDownloads 122 | action: Action { 123 | iconName: "file/file_download" 124 | name: root.mobile ? "" : qsTr("Downloads") 125 | } 126 | 127 | ProgressCircle { 128 | anchors.centerIn: parent 129 | width: parent.width + Units.dp(16) 130 | height: width 131 | z: -1 132 | 133 | color: downloadsButton.color 134 | 135 | indeterminate: false 136 | value: downloadsModel.overallProgress 137 | visible: downloadsModel.hasActiveDownloads 138 | } 139 | } 140 | 141 | IconButton { 142 | id: overflowButton 143 | color: root.currentIconColor 144 | onClicked: overflowMenu.open(overflowButton) 145 | action: Action { 146 | iconName: "navigation/more_vert" 147 | name: root.mobile ? "" : qsTr("Menu") 148 | } 149 | } 150 | } 151 | 152 | Component.onCompleted: { 153 | if (root.app.platform === "converged/ubuntu") { 154 | var overlayComponent = Qt.createComponent("UbuntuOmniboxOverlay.qml"); 155 | ubuntuOmniboxOverlay = overlayComponent.createObject(toolbar, {}) 156 | } 157 | } 158 | 159 | } 160 | -------------------------------------------------------------------------------- /src/qml/BrowserView.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import Material 0.2 3 | 4 | Item { 5 | id: browserView 6 | 7 | anchors.fill: parent 8 | 9 | visible: false 10 | 11 | property string viewType: "unknown" 12 | property string viewName: "unknown" 13 | 14 | property bool isWebView: viewType === "built-in" && viewName === "webview" 15 | property bool isDash: viewType === "built-in" && viewName === "dash" 16 | property bool isSettings: viewType === "built-in" && viewName === "settings" 17 | property bool isQuickSearchesSettings: viewType === "built-in" && viewName === "quick-searches-settings" 18 | property bool isSitesColorsSettings: viewType === "built-in" && viewName === "sites-colors-settings" 19 | 20 | 21 | /* View Properties */ 22 | 23 | property var icon: browserView.ready ? browserView.item.icon : null 24 | property string title: browserView.ready ? browserView.item.title : "Loading ... " 25 | property var url: browserView.ready ? browserView.item.url : null 26 | property var profile 27 | property bool loading: browserView.ready ? browserView.item.loading : true 28 | property real loadProgress: browserView.ready ? browserView.item.loadProgress : loader.progress 29 | property bool reloadable: browserView.ready ? browserView.item.reloadable : false 30 | 31 | property bool canGoBack: browserView.ready ? browserView.item.canGoBack : false 32 | property bool canGoForward: browserView.ready ? browserView.item.canGoForward : false 33 | property bool secureConnection: browserView.ready ? browserView.item.secureConnection : false 34 | 35 | property var customColor: browserView.ready ? browserView.item.customColor : false 36 | property var customColorLight: browserView.ready ? browserView.item.customColorLight : false 37 | property var customTextColor: browserView.ready ? browserView.item.customTextColor : false 38 | property bool hasCloseButton: true 39 | 40 | property var item: loader.item 41 | 42 | property var ready: true 43 | 44 | /* Loading */ 45 | 46 | function load (url, webview) { 47 | // Dirty workaround because Oxide webviews may be created directly without the use of a loader (see loadWebview) 48 | browserView.item = Qt.binding(function(){return loader.item}); 49 | browserView.ready = Qt.binding(function(){return loader.ready}); 50 | 51 | // Ensure url is valid 52 | if (typeof(url) === "undefined") { 53 | if (root.app.newTabPage) 54 | url = "liri://dash"; 55 | else 56 | url = app.homeUrl; 57 | } 58 | if (url.toString().lastIndexOf("liri://", 0) === 0) { 59 | // Built-in Liri specific URL handling 60 | if (url.toString().lastIndexOf("liri://dash", 0) === 0) { 61 | loadNewTabPage (url); 62 | } 63 | else if (url.toString().lastIndexOf("liri://settings", 0) === 0) { 64 | 65 | if (url.toString().lastIndexOf("liri://settings/quick-searches", 0) === 0) { 66 | loadQuickSearchesSettings(url); 67 | } 68 | else if (url.toString().lastIndexOf("liri://settings/sites-colors", 0) === 0) { 69 | loadSitesColorsSettings(url); 70 | } 71 | else { 72 | loadSettings (url); 73 | } 74 | } 75 | } 76 | // TODO: Handle plugins 77 | else { 78 | // Default WebView 79 | if(url.indexOf("file://") >= 0){ 80 | url = url; 81 | }else{ 82 | url = getValidUrl(url); 83 | } 84 | 85 | 86 | loadWebView(url, webview); 87 | } 88 | 89 | } 90 | 91 | function loadWebView (url, webview) { 92 | if (browserView.ready && isWebView) { 93 | browserView.item.url = url; 94 | } 95 | else { 96 | if (root.app.webEngine === "qtwebengine" || !webview) 97 | loader.sourceComponent = root.webviewComponent; 98 | else { 99 | // Dirty workaround: Directly override the loader's data. 100 | // The problem with this is that Oxide webview sometimes *must* be created within onNewViewRequested. 101 | loader.data = webview; 102 | browserView.item = webview; 103 | browserView.ready = true; 104 | } 105 | if (browserView.ready) { 106 | if (!webview) 107 | browserView.item.url = url; 108 | if (root.app.webEngine === "qtwebengine") 109 | browserView.item.profile = root.app.defaultProfile; 110 | viewType = "built-in"; 111 | viewName = "webview"; 112 | } 113 | else { 114 | console.log("Warning, loader is not ready!"); 115 | } 116 | } 117 | } 118 | 119 | function loadNewTabPage (url) { 120 | if (!(browserView.ready && isDash)) { 121 | loader.sourceComponent = root.newTabPageComponent; 122 | if (browserView.ready) { 123 | viewType = "built-in"; 124 | viewName = "dash"; 125 | } 126 | else { 127 | console.log("Warning, loader is not ready!"); 128 | } 129 | } 130 | } 131 | 132 | function loadSettings () { 133 | if (!(browserView.ready && isSettings)) { 134 | loader.sourceComponent = root.settingsViewComponent; 135 | if (browserView.ready) { 136 | viewType = "built-in"; 137 | viewName = "settings"; 138 | } 139 | else { 140 | console.log("Warning, loader is not ready!"); 141 | } 142 | } 143 | } 144 | 145 | function loadQuickSearchesSettings () { 146 | if (!(browserView.ready && isQuickSearchesSettings)) { 147 | loader.sourceComponent = root.quickSearchesSettingsViewComponent; 148 | if (browserView.ready) { 149 | viewType = "built-in"; 150 | viewName = "quick-searches-settings"; 151 | } 152 | else { 153 | console.log("Warning, loader is not ready!"); 154 | } 155 | } 156 | } 157 | 158 | function loadSitesColorsSettings () { 159 | if (!(browserView.ready && isSitesColorsSettings)) { 160 | loader.sourceComponent = root.sitesColorsSettingsViewComponent; 161 | if (browserView.ready) { 162 | viewType = "built-in"; 163 | viewName = "sites-colors-settings"; 164 | } 165 | else { 166 | console.log("Warning, loader is not ready!"); 167 | } 168 | } 169 | } 170 | 171 | 172 | function loadPluginView () { 173 | 174 | } 175 | 176 | /* WebView functionality */ 177 | 178 | function goBack() { 179 | if (isWebView) 180 | browserView.item.goBack(); 181 | } 182 | 183 | function goForward() { 184 | if (isWebView) 185 | browserView.item.goForward(); 186 | } 187 | 188 | function runJavaScript(arg1, arg2) { 189 | if (isWebView) 190 | browserView.item.runJavaScript(arg1, arg2); 191 | } 192 | 193 | function reload() { 194 | if (isWebView) 195 | browserView.item.reload(); 196 | } 197 | 198 | function zoomIn() { 199 | if (isWebView) 200 | browserView.item.zoomFactor += 0.25; 201 | } 202 | 203 | function zoomOut() { 204 | if (isWebView) 205 | browserView.item.zoomFactor -= 0.25 206 | } 207 | 208 | function zoomReset() { 209 | if (isWebView) 210 | browserView.item.zoomFactor = 1.0 211 | } 212 | 213 | function stop() { 214 | if (isWebView) 215 | browserView.item.stop(); 216 | } 217 | 218 | function findText (text, backward, callback){ 219 | if (isWebView) 220 | browserView.item.findText(text, backward, callback); 221 | } 222 | 223 | 224 | Loader { 225 | id: loader 226 | anchors.fill: parent 227 | 228 | property bool ready: status === Loader.Ready 229 | 230 | onStatusChanged: { 231 | switch (loader.status) { 232 | case loader.ready: 233 | console.log("Loaded"); 234 | break; 235 | case Loader.Loading: 236 | break; 237 | case Loader.Error: 238 | console.log("Error"); 239 | break; 240 | } 241 | } 242 | } 243 | } 244 | 245 | -------------------------------------------------------------------------------- /src/qml/ColorChooser.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import Material 0.2 3 | 4 | 5 | Item { 6 | id: colorChooser 7 | property color color 8 | property string title 9 | property Item colorPicker 10 | property bool dark: false 11 | property bool light: false 12 | 13 | width: Units.dp(300) 14 | height: Units.dp(196) 15 | 16 | Label { 17 | id: lblTitle 18 | style: "dialog" 19 | font.family: root.fontFamily 20 | text: title || qsTr("Choose a color") 21 | anchors.top: parent.top 22 | anchors.left: parent.left 23 | anchors.margins: Units.dp(24) 24 | 25 | } 26 | 27 | Grid { 28 | columns: 7 29 | spacing: Units.dp(8) 30 | id: grid 31 | anchors.top: lblTitle.bottom 32 | anchors.topMargin: Units.dp(24) 33 | anchors.horizontalCenter: parent.horizontalCenter 34 | 35 | Repeater { 36 | model: [ 37 | "red", "pink", "purple", "deepPurple", "indigo", 38 | "blue", "lightBlue", "cyan", "teal", "green", 39 | "lightGreen", "lime", "yellow", "amber", "orange", 40 | "deepOrange", "grey", "blueGrey", "brown", "black", 41 | "white" 42 | ] 43 | 44 | Rectangle { 45 | width: Units.dp(30) 46 | height: Units.dp(30) 47 | radius: Units.dp(2) 48 | color: colorChooser.dark ? Palette.colors[modelData]["900"] : colorChooser.light ? Palette.colors[modelData]["100"] : Palette.colors[modelData]["500"] 49 | border.width: if (color === colorChooser.color) { Units.dp(2) } else { 0 } 50 | border.color: Theme.alpha("#000", 0.26) 51 | 52 | Ink { 53 | anchors.fill: parent 54 | 55 | onPressed: { 56 | colorChooser.color = parent.color; 57 | if (colorPicker) colorPicker.close(); 58 | } 59 | } 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/qml/ColorPicker.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import Material 0.2 3 | import Material.ListItems 0.1 as ListItem 4 | 5 | 6 | Dropdown { 7 | id: colorPicker 8 | property alias color: colorChooser.color 9 | property alias titel: colorChooser.title 10 | property alias dark: colorChooser.dark 11 | property alias light: colorChooser.light 12 | 13 | width: Units.dp(300) 14 | height: Units.dp(250) 15 | 16 | Column { 17 | id: column 18 | width: parent.width 19 | 20 | ColorChooser { 21 | id: colorChooser 22 | colorPicker: colorPicker 23 | width: parent.width 24 | height: Units.dp(196) 25 | } 26 | 27 | ListItem.Standard { 28 | width: parent.width 29 | TextField { 30 | placeholderText: qsTr("Custom color: (hit enter when finished)") 31 | id: customColor 32 | floatingLabel: true 33 | text: "#F0F0F0" 34 | width: parent.width - 30 35 | anchors { 36 | verticalCenter: parent.verticalCenter 37 | left: parent.left 38 | leftMargin: 15 39 | } 40 | font.pixelSize: Units.dp(16) 41 | enabled: chbCustomColor.checked 42 | onAccepted: { 43 | colorChooser.color = text 44 | colorPicker.close() 45 | } 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/qml/DesktopApplication.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.1 2 | import QtWebEngine 1.1 3 | import Qt.labs.settings 1.0 4 | import Material.Extras 0.1 5 | 6 | BaseApplication { 7 | id: application 8 | 9 | property QtObject defaultProfile: WebEngineProfile { 10 | storageName: "Default" 11 | 12 | onDownloadRequested: { 13 | downloadsModel.insert(0, { modelData: download }) 14 | // TODO: Should we confirm the download and location first? 15 | download.accept() 16 | download.stateChanged.connect(downloadsModel.downloadsChanged) 17 | download.receivedBytesChanged.connect(downloadsModel.progressChanged) 18 | 19 | downloadsModel.downloadsChanged() 20 | 21 | console.log("Starting download to " + download.path) 22 | } 23 | } 24 | 25 | property Component browserWindowComponent: BrowserWindow { 26 | app: application 27 | } 28 | 29 | property ListModel downloadsModel: ListModel { 30 | id: downloadsModel 31 | dynamicRoles: true 32 | 33 | property bool hasActiveDownloads 34 | property bool hasDownloads: count > 0 35 | 36 | property real overallProgress 37 | 38 | function loadHistory() { 39 | if (application.settings.downloads) { 40 | downloadsModel.clear() 41 | 42 | for (var i = 0; i < application.settings.downloads.length; i++) { 43 | var download = application.settings.downloads[i] 44 | 45 | downloadsModel.append({ modelData: download }) 46 | } 47 | } 48 | } 49 | 50 | function saveHistory(teardown) { 51 | // Save a history of downloads 52 | var list = []; 53 | for (var i = 0; i < downloadsModel.count; i++) { 54 | var download = downloadsModel.get(i).modelData 55 | var state = download.state === WebEngineDownloadItem.DownloadInProgress || 56 | download.state === WebEngineDownloadItem.DownloadRequested 57 | ? WebEngineDownloadItem.DownloadCancelled 58 | : download.state 59 | 60 | list.push({ 61 | "path": download.path, 62 | "state": state, 63 | "receivedBytes": download.receivedBytes, 64 | "totalBytes": download.totalBytes 65 | }) 66 | 67 | if (teardown && download.hasOwnProperty("cancel")) 68 | download.cancel() 69 | } 70 | application.settings.downloads = list; 71 | } 72 | 73 | function downloadsChanged() { 74 | hasActiveDownloads = ListUtils.filteredCount(downloadsModel, function(download) { 75 | return download.state === WebEngineDownloadItem.DownloadInProgress 76 | }) > 0 77 | 78 | saveHistory() 79 | } 80 | 81 | function progressChanged() { 82 | var activeDownloads = ListUtils.filter(downloadsModel, function(download) { 83 | return download.state === WebEngineDownloadItem.DownloadInProgress 84 | }) 85 | 86 | var receivedBytes = ListUtils.sum(activeDownloads, "receivedBytes") 87 | var totalBytes = ListUtils.sum(activeDownloads, "totalBytes") 88 | 89 | overallProgress = receivedBytes/totalBytes 90 | } 91 | 92 | function clearFinished() { 93 | for (var i = 0; i < downloadsModel.count;) { 94 | var download = downloadsModel.get(i).modelData 95 | 96 | if (download.state !== WebEngineDownloadItem.DownloadInProgress) { 97 | remove(i) 98 | } else { 99 | i++ 100 | } 101 | } 102 | 103 | saveHistory() 104 | } 105 | } 106 | 107 | function createWindow() { 108 | var newWindow = browserWindowComponent.createObject(application) 109 | return newWindow 110 | } 111 | 112 | function createDialog(request) { 113 | var newDialog = browserWindowComponent.createObject(application) 114 | var tab = newDialog.addTab("about:blank") 115 | request.openIn(tab.view.item.view) 116 | return newDialog 117 | } 118 | 119 | function load() { 120 | var browserWindow = createWindow() 121 | } 122 | 123 | Component.onCompleted: { 124 | downloadsModel.loadHistory() 125 | } 126 | 127 | Component.onDestruction: { 128 | downloadsModel.saveHistory(true); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/qml/DownloadsDrawer.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 1.2 3 | import Material 0.2 4 | import Material.ListItems 0.1 as ListItem 5 | import QtWebEngine 1.1 6 | import QtQuick.Layouts 1.1 7 | 8 | RightDrawer { 9 | id: drawer 10 | 11 | View { 12 | id: downloadsTitle 13 | height: Units.dp(56) 14 | width: parent.width 15 | elevation: listView.contentY > 0 ? 1 : 0 16 | backgroundColor: "white" 17 | z: 1 18 | 19 | RowLayout { 20 | anchors { 21 | left: parent.left 22 | leftMargin: Units.dp(16) 23 | verticalCenter: parent.verticalCenter 24 | } 25 | 26 | spacing: Units.dp(8) 27 | 28 | Label { 29 | Layout.fillWidth: true 30 | text: qsTr("Downloads") 31 | style: "title" 32 | elide: Text.ElideRight 33 | } 34 | 35 | IconButton { 36 | iconName: "action/done_all" 37 | onClicked: { 38 | downloadsModel.clearFinished() 39 | if (downloadsModel.count == 0) 40 | downloadsDrawer.close() 41 | } 42 | } 43 | } 44 | } 45 | 46 | ScrollView { 47 | anchors { 48 | left: parent.left 49 | right: parent.right 50 | top: downloadsTitle.bottom 51 | bottom: parent.bottom 52 | } 53 | 54 | // NOTE: Now empty state placeholder is necessary because the drawer can't be opened 55 | // unless there is at least one download 56 | ListView { 57 | id: listView 58 | 59 | bottomMargin: Units.dp(8) 60 | interactive: count > 0 61 | model: downloadsModel 62 | delegate: ListItem.Subtitled { 63 | id: listItem 64 | 65 | interactive: download.state === WebEngineDownloadItem.DownloadCompleted 66 | 67 | property var download: downloadsModel.get(index).modelData 68 | 69 | text: { 70 | var index = download.path.lastIndexOf("/") 71 | return download.path.substring(index + 1) 72 | } 73 | subText: { 74 | if (listItem.download.state === WebEngineDownloadItem.DownloadRequested) { 75 | return qsTr("Awaiting confirmation") 76 | } else if (listItem.download.state === WebEngineDownloadItem.DownloadInProgress) { 77 | return qsTr("Downloading...") 78 | } else if (listItem.download.state === WebEngineDownloadItem.DownloadCompleted) { 79 | return qsTr("Completed") 80 | } else if (listItem.download.state === WebEngineDownloadItem.DownloadCancelled) { 81 | return qsTr("Cancelled") 82 | } else if (listItem.download.state === WebEngineDownloadItem.DownloadInterrupted) { 83 | return qsTr("Download failed") 84 | } else { 85 | return qsTr("Unknown state!") 86 | } 87 | } 88 | 89 | content: ProgressBar { 90 | id: progressBar 91 | 92 | anchors.verticalCenter: parent.verticalCenter 93 | width: parent.width 94 | 95 | property bool completed: value == maximumValue 96 | 97 | value: listItem.download.receivedBytes 98 | maximumValue: listItem.download.totalBytes 99 | visible: listItem.download.state === WebEngineDownloadItem.DownloadInProgress 100 | } 101 | secondaryItem: IconButton { 102 | anchors.centerIn: parent 103 | iconName: "navigation/cancel" 104 | visible: listItem.download.state === WebEngineDownloadItem.DownloadInProgress 105 | onClicked: { 106 | listItem.download.cancel(); 107 | } 108 | } 109 | 110 | onClicked: { 111 | Qt.openUrlExternally("file://" + download.path) 112 | } 113 | } 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/qml/FullscreenBar.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | import Material 0.2 3 | import Material.ListItems 0.1 as ListItem 4 | import QtQuick.Layouts 1.0 5 | import QtQuick.Controls 1.2 as Controls 6 | 7 | Rectangle { 8 | id: fullscreenBar 9 | 10 | z: 5 11 | visible: root.fullscreen 12 | height: Units.dp(48) 13 | 14 | anchors { 15 | top: parent.top 16 | left: parent.left 17 | right: parent.right 18 | } 19 | 20 | Behavior on opacity { 21 | NumberAnimation { duration: 300 } 22 | } 23 | 24 | Row { 25 | anchors.fill: parent 26 | anchors.leftMargin: Units.dp(24) 27 | anchors.rightMargin: Units.dp(24) 28 | spacing: Units.dp(24) 29 | 30 | Image { 31 | source: root.activeTab.view.icon 32 | width: Units.dp(18) 33 | height: Units.dp(18) 34 | anchors.verticalCenter: parent.verticalCenter 35 | 36 | } 37 | 38 | Text { 39 | text: root.activeTab.view.title 40 | anchors.verticalCenter: parent.verticalCenter 41 | font.family: root.fontFamily 42 | } 43 | 44 | } 45 | 46 | IconButton { 47 | anchors.verticalCenter: parent.verticalCenter 48 | anchors.right: parent.right 49 | anchors.rightMargin: Units.dp(7) 50 | iconName: "navigation/fullscreen_exit" 51 | onClicked: { 52 | root.endFullscreenMode(); 53 | } 54 | } 55 | 56 | MouseArea { 57 | anchors.fill: parent 58 | hoverEnabled: true 59 | propagateComposedEvents: true 60 | 61 | onEntered: { 62 | parent.opacity = 1.0; 63 | } 64 | 65 | onExited: { 66 | parent.opacity = 0.0; 67 | } 68 | } 69 | 70 | onVisibleChanged: { 71 | if (visible) { 72 | var timer = Qt.createQmlObject("import QtQuick 2.0; Timer {}", parent); 73 | timer.interval = 1500; 74 | timer.repeat = false; 75 | timer.triggered.connect(function () { 76 | opacity = 0 77 | }); 78 | 79 | timer.start(); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/qml/HistoryDrawer.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 1.2 3 | import Material 0.2 4 | import Material.ListItems 0.1 as ListItem 5 | 6 | RightDrawer { 7 | id: drawer 8 | 9 | View { 10 | id: historyTitle 11 | height: Units.dp(56) 12 | width: parent.width 13 | elevation: listView.contentY > 0 ? 1 : 0 14 | backgroundColor: "white" 15 | z: 1 16 | 17 | Label { 18 | id: label 19 | anchors { 20 | left: parent.left 21 | leftMargin: Units.dp(16) 22 | verticalCenter: parent.verticalCenter 23 | } 24 | text: qsTr("History") 25 | style: "title" 26 | } 27 | 28 | IconButton { 29 | id: clearHistory 30 | 31 | anchors { 32 | left: label.right 33 | leftMargin: Units.dp(16) 34 | verticalCenter: parent.verticalCenter 35 | } 36 | iconName: "action/delete" 37 | 38 | onClicked: { 39 | root.app.historyModel.clear() 40 | root.app.saveHistory(); 41 | } 42 | } 43 | 44 | } 45 | 46 | ScrollView { 47 | anchors { 48 | left: parent.left 49 | right: parent.right 50 | top: historyTitle.bottom 51 | bottom: parent.bottom 52 | } 53 | 54 | ListView { 55 | id: listView 56 | 57 | bottomMargin: Units.dp(8) 58 | interactive: count > 0 59 | model: root.app.historyModel 60 | delegate: historyItemDelegate 61 | 62 | Column { 63 | visible: listView.count == 0 64 | anchors.centerIn: parent 65 | spacing: Units.dp(8) 66 | 67 | Icon { 68 | name: "action/history" 69 | size: Units.dp(48) 70 | anchors.horizontalCenter: parent.horizontalCenter 71 | } 72 | 73 | Label { 74 | style: "subheading" 75 | color: Theme.light.subTextColor 76 | text: qsTr("No browser history found") 77 | font.pixelSize: Units.dp(17) 78 | anchors.horizontalCenter: parent.horizontalCenter 79 | } 80 | } 81 | } 82 | } 83 | 84 | Component { 85 | id: historyItemDelegate 86 | 87 | Item { 88 | height: childrenRect.height 89 | 90 | anchors { 91 | left: parent.left 92 | right: parent.right 93 | } 94 | 95 | ListItem.Subheader { 96 | text: title 97 | visible: type == "date" 98 | } 99 | 100 | ListItem.Standard { 101 | visible: type == "entry" 102 | text: title 103 | action: [ 104 | Image { 105 | id: favImage 106 | anchors.centerIn: parent 107 | source: faviconUrl ? faviconUrl : "" 108 | height: Units.dp(20) 109 | width: Units.dp(20) 110 | }, 111 | Icon { 112 | anchors.centerIn: parent 113 | name: "social/public" 114 | size: Units.dp(22) 115 | visible: favImage.status !== Image.Ready 116 | } 117 | ] 118 | onClicked: root.addTab(url) 119 | } 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/qml/LoadingIndicator.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | import Material 0.2 3 | 4 | ProgressCircle { 5 | id: indicator 6 | color: "white" 7 | 8 | SequentialAnimation { 9 | running: !root.app.darkTheme 10 | loops: Animation.Infinite 11 | 12 | ColorAnimation { 13 | from: "red" 14 | to: "blue" 15 | target: indicator 16 | properties: "color" 17 | easing.type: Easing.InOutQuad 18 | duration: 2400 19 | } 20 | 21 | ColorAnimation { 22 | from: "blue" 23 | to: "green" 24 | target: indicator 25 | properties: "color" 26 | easing.type: Easing.InOutQuad 27 | duration: 1560 28 | } 29 | 30 | ColorAnimation { 31 | from: "green" 32 | to: "#FFCC00" 33 | target: indicator 34 | properties: "color" 35 | easing.type: Easing.InOutQuad 36 | duration: 840 37 | } 38 | 39 | ColorAnimation { 40 | from: "#FFCC00" 41 | to: "red" 42 | target: indicator 43 | properties: "color" 44 | easing.type: Easing.InOutQuad 45 | duration: 1200 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/qml/MaterialWindow.qml: -------------------------------------------------------------------------------- 1 | // Code modified from QML-Material 2 | 3 | import QtQuick 2.0 4 | import QtQuick.Controls 1.2 as Controls 5 | import QtQuick.Window 2.0 6 | import Material 0.2 7 | import Material.Extras 0.1 8 | import QtGraphicalEffects 1.0 9 | 10 | Controls.ApplicationWindow { 11 | id: __window 12 | property bool clientSideDecorations 13 | flags: root.app.customFrame ? Qt.FramelessWindowHint : Qt.Window 14 | color: "transparent" 15 | property alias initialPage: __pageStack.initialItem 16 | property alias pageStack: __pageStack 17 | property alias theme: __theme 18 | 19 | AppTheme { 20 | id: __theme 21 | } 22 | 23 | ResizeArea{ 24 | id:resizeArea 25 | anchors.fill: parent 26 | dragHeight: systemBar.height + 50 27 | anchors.margins: root.app.customFrame ? 5 : 0 28 | target: __window 29 | minSize: Qt.size(100,100) 30 | enabled: true 31 | 32 | RectangularGlow { 33 | id: outGlow 34 | width: __pageStack.width - parent.anchors.margins * 2 35 | height: __pageStack.height + __toolbar.height - parent.anchors.margins * 2 36 | x: 10 37 | y: 10 38 | visible: root.app.customFrame 39 | glowRadius: 10 40 | spread: 0.1 41 | color: "#A0000000" 42 | cornerRadius: glowRadius 43 | 44 | 45 | 46 | } 47 | } 48 | 49 | 50 | PageStack { 51 | id: __pageStack 52 | anchors { 53 | left: parent.left 54 | right: parent.right 55 | top: root.mobile ? __toolbar.bottom : parent.top 56 | bottom: parent.bottom 57 | margins: root.app.customFrame ? 10 : 0 58 | topMargin: root.app.customFrame && !root.mobile ? 10 : 0 59 | } 60 | 61 | onPushed: __toolbar.push(page) 62 | onPopped: { 63 | __toolbar.pop(page) 64 | root.tabsListIsOpened = false 65 | root.customSitesColorsIsOpened = false 66 | } 67 | onReplaced: __toolbar.replace(page) 68 | } 69 | 70 | Toolbar { 71 | id: __toolbar 72 | clientSideDecorations: false 73 | anchors.margins : root.app.customFrame ? 10 : 0 74 | } 75 | 76 | 77 | OverlayLayer { 78 | id: dialogOverlayLayer 79 | objectName: "dialogOverlayLayer" 80 | } 81 | 82 | OverlayLayer { 83 | id: tooltipOverlayLayer 84 | objectName: "tooltipOverlayLayer" 85 | } 86 | 87 | OverlayLayer { 88 | id: overlayLayer 89 | } 90 | 91 | width: Units.dp(800) 92 | height: Units.dp(600) 93 | 94 | SystemBar { 95 | id: systemBar 96 | visible: root.app.customFrame 97 | anchors.margins: 0 98 | z:2 99 | color: "transparent" 100 | } 101 | 102 | 103 | 104 | SystemButtons { 105 | id: sysbuttons 106 | z:90 107 | color: "transparent" 108 | onShowMinimized: root.showMinimized(); 109 | onShowMaximized: root.showMaximized(); 110 | onShowNormal: root.showNormal(); 111 | onClose: root.close(); 112 | visible: root.app.customFrame 113 | anchors { 114 | right: parent.right 115 | top: parent.top 116 | margins: 10 117 | } 118 | } 119 | 120 | Dialog { 121 | id: errorDialog 122 | 123 | property var promise 124 | 125 | positiveButtonText: "Retry" 126 | 127 | onAccepted: { 128 | promise.resolve() 129 | promise = null 130 | } 131 | 132 | onRejected: { 133 | promise.reject() 134 | promise = null 135 | } 136 | } 137 | 138 | 139 | function showError(title, text, secondaryButtonText, retry) { 140 | if (errorDialog.promise) { 141 | errorDialog.promise.reject() 142 | errorDialog.promise = null 143 | } 144 | 145 | errorDialog.negativeButtonText = secondaryButtonText ? secondaryButtonText : "Close" 146 | errorDialog.positiveButton.visible = retry || false 147 | 148 | errorDialog.promise = new Promises.Promise() 149 | errorDialog.title = title 150 | errorDialog.text = text 151 | errorDialog.open() 152 | 153 | return errorDialog.promise 154 | } 155 | 156 | Component.onCompleted: { 157 | 158 | Units.pixelDensity = Qt.binding(function() { 159 | return Screen.pixelDensity 160 | }); 161 | 162 | Device.type = Qt.binding(function () { 163 | var diagonal = Math.sqrt(Math.pow((Screen.width/Screen.pixelDensity), 2) + 164 | Math.pow((Screen.height/Screen.pixelDensity), 2)) * 0.039370; 165 | 166 | if (diagonal >= 3.5 && diagonal < 5) { //iPhone 1st generation to phablet 167 | Units.multiplier = 1; 168 | return Device.phone; 169 | } else if (diagonal >= 5 && diagonal < 6.5) { 170 | Units.multiplier = 1; 171 | return Device.phablet; 172 | } else if (diagonal >= 6.5 && diagonal < 10.1) { 173 | Units.multiplier = 1; 174 | return Device.tablet; 175 | } else if (diagonal >= 10.1 && diagonal < 29) { 176 | return Device.desktop; 177 | } else if (diagonal >= 29 && diagonal < 92) { 178 | return Device.tv; 179 | } else { 180 | return Device.unknown; 181 | } 182 | }); 183 | 184 | Units.gridUnit = Qt.binding(function() { 185 | return Device.type === Device.phone || Device.type === Device.phablet 186 | ? Units.dp(48) : Device.type == Device.tablet ? Units.dp(56) : Units.dp(64) 187 | }) 188 | } 189 | 190 | Item{ 191 | state:__window.visibility 192 | states: [ 193 | State { 194 | name: "2" 195 | PropertyChanges { target: resizeArea; anchors.margins: root.app.customFrame && !root.snappedRight && !root.snappedLeft ? 5 : 0; enabled: true } 196 | PropertyChanges { target: __pageStack; anchors.margins: root.app.customFrame && !root.snappedRight && !root.snappedLeft ? 10 : 0 } 197 | PropertyChanges { target: __toolbar; anchors.margins: root.app.customFrame && !root.snappedRight && !root.snappedLeft ? 10 : 0 } 198 | PropertyChanges { target: systemBar; anchors.margins: root.app.customFrame && !root.snappedRight && !root.snappedLeft ? 10 : 0 } 199 | PropertyChanges { target: sysbuttons; anchors.margins: 10 } 200 | PropertyChanges { target: outGlow; visible: true } 201 | }, 202 | State { 203 | name: "4" 204 | PropertyChanges { target: resizeArea; anchors.margins: 0; enabled: false } 205 | PropertyChanges { target: __pageStack; anchors.margins: 0; anchors.topMargin: 0} 206 | PropertyChanges { target: systemBar; anchors.margins: 0 } 207 | PropertyChanges { target: sysbuttons; anchors.margins: 0 } 208 | PropertyChanges { target: __toolbar; anchors.margins: 0 } 209 | PropertyChanges { target: outGlow; visible: false } 210 | } 211 | ] 212 | } 213 | 214 | function colorLuminance(hex, lum) { 215 | hex = String(hex).replace(/[^0-9a-f]/gi, ''); 216 | if (hex.length < 6) { 217 | hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2]; 218 | } 219 | lum = lum || 0; 220 | var rgb = "#", c, i; 221 | for (i = 0; i < 3; i++) { 222 | c = parseInt(hex.substr(i*2,2), 16); 223 | c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16); 224 | rgb += ("00"+c).substr(c.length); 225 | } 226 | 227 | return rgb; 228 | } 229 | 230 | 231 | } 232 | -------------------------------------------------------------------------------- /src/qml/MenuFieldThemed.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * QML Material - An application framework implementing Material Design. 3 | * Copyright (C) 2015 Michael Spencer 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as 7 | * published by the Free Software Foundation, either version 2.1 of the 8 | * License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this program. If not, see . 17 | */ 18 | import QtQuick 2.2 19 | import QtQuick.Layouts 1.1 20 | 21 | import Material 0.2 22 | import Material.ListItems 0.1 23 | 24 | /*! 25 | \qmltype MenuField 26 | \inqmlmodule Material 0.1 27 | 28 | \brief A input field similar to a text field but that opens a dropdown menu. 29 | */ 30 | Item { 31 | id: field 32 | 33 | implicitHeight: hasHelperText ? helperTextLabel.y + helperTextLabel.height + Units.dp(4) 34 | : underline.y + Units.dp(8) 35 | implicitWidth: spinBoxContents.implicitWidth 36 | 37 | activeFocusOnTab: true 38 | 39 | property color accentColor: Theme.accentColor 40 | property color errorColor: "#F44336" 41 | property color textColor 42 | property color helperColor 43 | property color linesColor 44 | property alias model: listView.model 45 | 46 | property string textRole 47 | 48 | readonly property string selectedText: (listView.currentItem) ? listView.currentItem.text : "" 49 | 50 | property alias selectedIndex: listView.currentIndex 51 | property int maxVisibleItems: 4 52 | 53 | property alias placeholderText: fieldPlaceholder.text 54 | property alias helperText: helperTextLabel.text 55 | 56 | property bool floatingLabel: false 57 | property bool hasError: false 58 | property bool hasHelperText: helperText.length > 0 59 | 60 | readonly property rect inputRect: Qt.rect(spinBox.x, spinBox.y, spinBox.width, spinBox.height) 61 | 62 | signal itemSelected(int index) 63 | 64 | Ink { 65 | anchors.fill: parent 66 | onClicked: { 67 | listView.positionViewAtIndex(listView.currentIndex, ListView.Center) 68 | var offset = listView.currentItem.itemLabel.mapToItem(menu, 0, 0) 69 | menu.open(label, 0, -offset.y) 70 | } 71 | } 72 | 73 | Item { 74 | id: spinBox 75 | 76 | height: Units.dp(24) 77 | width: parent.width 78 | 79 | y: { 80 | if(!floatingLabel) 81 | return Units.dp(16) 82 | if(floatingLabel && !hasHelperText) 83 | return Units.dp(40) 84 | return Units.dp(28) 85 | } 86 | 87 | RowLayout { 88 | id: spinBoxContents 89 | 90 | height: parent.height 91 | width: parent.width + Units.dp(5) 92 | 93 | Label { 94 | id: label 95 | 96 | Layout.fillWidth: true 97 | Layout.alignment: Qt.AlignVCenter 98 | 99 | text: (listView.currentItem) ? listView.currentItem.text : "" 100 | style: "subheading" 101 | elide: Text.ElideRight 102 | color: field.textColor 103 | } 104 | 105 | Icon { 106 | id: dropDownIcon 107 | color: field.linesColor 108 | Layout.alignment: Qt.AlignVCenter | Qt.AlignRight 109 | Layout.preferredWidth: Units.dp(24) 110 | Layout.preferredHeight: Units.dp(24) 111 | 112 | name: "navigation/arrow_drop_down" 113 | size: Units.dp(24) 114 | } 115 | } 116 | 117 | Dropdown { 118 | id: menu 119 | 120 | anchor: Item.TopLeft 121 | 122 | width: spinBox.width 123 | 124 | //If there are more than max items, show an extra half item so 125 | // it's clear the user can scroll 126 | height: Math.min(maxVisibleItems*Units.dp(48) + Units.dp(24), listView.contentHeight) 127 | 128 | ListView { 129 | id: listView 130 | 131 | width: menu.width 132 | height: count > 0 ? menu.height : 0 133 | 134 | interactive: true 135 | 136 | delegate: Standard { 137 | id: delegateItem 138 | 139 | text: textRole ? model[textRole] : modelData 140 | 141 | onClicked: { 142 | itemSelected(index) 143 | listView.currentIndex = index 144 | menu.close() 145 | } 146 | } 147 | } 148 | 149 | Scrollbar { 150 | flickableItem: listView 151 | } 152 | } 153 | } 154 | 155 | Label { 156 | id: fieldPlaceholder 157 | 158 | text: field.placeholderText 159 | visible: floatingLabel 160 | 161 | font.pixelSize: Units.dp(12) 162 | 163 | anchors.bottom: spinBox.top 164 | anchors.bottomMargin: Units.dp(8) 165 | 166 | color: Theme.light.hintColor 167 | } 168 | 169 | Rectangle { 170 | id: underline 171 | 172 | color: field.linesColor 173 | 174 | height: field.activeFocus ? Units.dp(2) : Units.dp(1) 175 | 176 | anchors { 177 | left: parent.left 178 | right: parent.right 179 | top: spinBox.bottom 180 | topMargin: Units.dp(8) 181 | } 182 | 183 | Behavior on height { 184 | NumberAnimation { duration: 200 } 185 | } 186 | 187 | Behavior on color { 188 | ColorAnimation { duration: 200 } 189 | } 190 | } 191 | 192 | Label { 193 | id: helperTextLabel 194 | 195 | anchors { 196 | left: parent.left 197 | right: parent.right 198 | top: underline.top 199 | topMargin: Units.dp(4) 200 | } 201 | 202 | visible: hasHelperText 203 | font.pixelSize: Units.dp(12) 204 | color: field.helperColor 205 | 206 | Behavior on color { 207 | ColorAnimation { duration: 200 } 208 | } 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /src/qml/Omnibox.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | import Material 0.2 3 | import Material.ListItems 0.1 as ListItem 4 | import QtQuick.Layouts 1.0 5 | import QtQuick.Controls 1.2 as Controls 6 | 7 | View { 8 | id: omnibox 9 | 10 | radius: Units.dp(2) 11 | backgroundColor: "white" 12 | elevation: 1 13 | opacity: 1 14 | 15 | property alias txtUrl: txtUrl 16 | 17 | /*om 18 | * Loading progress indicator 19 | * 20 | * The outer item provides the actual shape for the progress bar. It is clipped, and has a child rectangle that 21 | * is slightly bigger. This is so the progress bar is curved where it intersects the corners of the omnibox. 22 | */ 23 | 24 | Item { 25 | id: progressBar 26 | anchors { 27 | left: parent.left 28 | bottom: parent.bottom 29 | } 30 | 31 | clip: true 32 | height: omnibox.radius 33 | width: 0 34 | opacity: loading ? 1 : 0 35 | 36 | property bool loading: root.activeTab.view.loading 37 | property bool enableBehavior 38 | 39 | // When loading, we first disable the behavior and reset the width to 0 40 | // So the progress bar resets and doesn't animate backwards 41 | onLoadingChanged: { 42 | if (loading) { 43 | enableBehavior = false 44 | width = 0 45 | enableBehavior = true 46 | width = Qt.binding(function () { return omnibox.width * root.activeTab.view.loadProgress }) 47 | } 48 | } 49 | 50 | Behavior on width { 51 | enabled: progressBar.enableBehavior 52 | SmoothedAnimation { 53 | reversingMode: SmoothedAnimation.Sync 54 | } 55 | } 56 | 57 | Behavior on opacity { 58 | NumberAnimation { duration: 300 } 59 | } 60 | 61 | Rectangle { 62 | radius: omnibox.radius 63 | 64 | anchors { 65 | left: parent.left 66 | bottom: parent.bottom 67 | } 68 | 69 | /* 70 | * Make this wider than the clip rect, so the left edge is rounded to match the ominbox, but the right 71 | * edge is squared off. We limit the width to the ominbox width, though, so as the progress bar gets to 72 | * the edge, the clip moves off and the progress bar stops, so we see the right end of the progress bar 73 | * match the corner of the omnibox 74 | */ 75 | width: Math.min(parent.width + radius, omnibox.width) 76 | height: radius * 2 77 | color: Theme.accentColor 78 | } 79 | } 80 | 81 | Icon { 82 | id: connectionTypeIcon 83 | 84 | property bool searchIcon: false 85 | name: root.privateNav ? "awesome/binoculars" : searchIcon ? "editor/mode_edit" : root.activeTab.view.secureConnection ? "action/lock" : "social/public" 86 | color: root.activeTab.view.secureConnection ? "green" : "gray" 87 | onColorChanged: { 88 | 89 | } 90 | 91 | anchors { 92 | left: parent.left 93 | verticalCenter: parent.verticalCenter 94 | leftMargin: Units.dp(16) 95 | } 96 | } 97 | 98 | Label { 99 | id: quickSearchIndicator 100 | anchors{ 101 | left: connectionTypeIcon.right 102 | leftMargin: txtUrl.quickSearch != "" ? Units.dp(16) : 0 103 | verticalCenter: parent.verticalCenter 104 | } 105 | 106 | text: txtUrl.quickSearch 107 | visible: txtUrl.quickSearch != "" 108 | } 109 | 110 | TextField { 111 | id: txtUrl 112 | objectName: "txtUrl" 113 | anchors { 114 | left: quickSearchIndicator.right 115 | right: parent.right 116 | top: parent.top 117 | bottom: parent.bottom 118 | leftMargin: Units.dp(16) 119 | rightMargin: Units.dp(16) 120 | } 121 | property string quickSearch: "" 122 | property string quickSearchURL: "" 123 | showBorder: false 124 | visible: quickSearch == "" 125 | text: addingSearch ? searchesText : root.activeTab.view.url 126 | property var searchesText 127 | property bool addingSearch: false 128 | placeholderText: mobile ? qsTr("Search") : qsTr("Search or enter website name") 129 | opacity: 1 130 | textColor: root.tabTextColorActive 131 | onTextChanged: { 132 | root.app.searchSuggestionsModel.clear(); 133 | if(isASearchQuery(text)) { 134 | // Trigger omniplet plugins 135 | PluginsEngine.trigger("omnibox.search", text); 136 | } 137 | 138 | else { 139 | connectionTypeIcon.searchIcon = false; 140 | } 141 | 142 | } 143 | onAccepted: { 144 | if(isASearchQuery(text)) { 145 | setActiveTabURL(root.app.searchSuggestionsModel.get(root.selectedQueryIndex).suggestion) 146 | } 147 | else { 148 | setActiveTabURL(text) 149 | } 150 | root.app.searchSuggestionsModel.clear() 151 | quickSearch = "" 152 | quickSearchURL = "" 153 | root.selectedQueryIndex = 0 154 | addingSearch = false 155 | } 156 | 157 | Keys.onTabPressed: { 158 | if(text.length == 3) { 159 | var item = getInfosOfQuickSearch(text) 160 | quickSearch = item.name + "" 161 | quickSearchURL = item.url + "" 162 | txtUrlQuickSearches.forceActiveFocus() 163 | } 164 | if(isASearchQuery(text)) { 165 | addingSearch = true 166 | searchesText = root.app.searchSuggestionsModel.get(root.selectedQueryIndex).suggestion 167 | root.selectedQueryIndex = 0 168 | } 169 | } 170 | Keys.onBacktabPressed: { 171 | quickSearch = ""; 172 | placeholderText = qsTr("Search or enter website name") 173 | } 174 | Keys.onDownPressed: { 175 | if(root.selectedQueryIndex < root.app.searchSuggestionsModel.count - 1) 176 | root.selectedQueryIndex += 1 177 | } 178 | Keys.onUpPressed: { 179 | if(root.selectedQueryIndex >= 1) 180 | root.selectedQueryIndex -= 1 181 | } 182 | MouseArea { 183 | anchors.fill: parent 184 | propagateComposedEvents: true 185 | 186 | onPressed: { 187 | if (root.app.platform !== "converged/ubuntu" || !root.mobile) 188 | mouse.accepted = false; 189 | } 190 | 191 | onClicked: { 192 | if (root.app.platform === "converged/ubuntu" && root.mobile) 193 | ubuntuOmniboxOverlay.show(); 194 | } 195 | } 196 | 197 | } 198 | 199 | TextField { 200 | id: txtUrlQuickSearches 201 | anchors { 202 | left: quickSearchIndicator.right 203 | right: parent.right 204 | top: parent.top 205 | bottom: parent.bottom 206 | leftMargin: Units.dp(16) 207 | rightMargin: Units.dp(16) 208 | } 209 | visible: txtUrl.quickSearch != "" 210 | showBorder: false 211 | text: "" 212 | placeholderText: qsTr("Search...") 213 | opacity: 1 214 | textColor: root.tabTextColorActive 215 | onTextChanged: isASearchQuery(text) ? connectionTypeIcon.searchIcon = true : connectionTypeIcon.searchIcon = false; 216 | onAccepted: { 217 | if(txtUrl.quickSearch == "" && root.app.quickSearches) 218 | console.log(1) 219 | else 220 | setActiveTabURL(txtUrl.quickSearchURL + text) 221 | txtUrl.quickSearch = "" 222 | txtUrl.quickSearchURL = "" 223 | } 224 | Keys.onTabPressed:{ 225 | if(text.length == 3) { 226 | var item = getInfosOfQuickSearch(text) 227 | txtUrl.quickSearch = item.name + "" 228 | txtUrl.quickSearchURL = item.url + "" 229 | txtUrl.placeholderText = qsTr("Search...") 230 | } 231 | } 232 | Keys.onEscapePressed: { 233 | Keys.onBacktabPressed(event) 234 | } 235 | Keys.onBacktabPressed: { 236 | txtUrl.quickSearch = ""; 237 | txtUrl.placeholderText = qsTr("Search or enter website name") 238 | } 239 | Keys.onPressed: { 240 | if (event.key == Qt.Key_Backspace) { 241 | txtUrl.quickSearch = ""; 242 | txtUrl.placeholderText = qsTr("Search or enter website name") 243 | } 244 | } 245 | 246 | MouseArea { 247 | anchors.fill: parent 248 | propagateComposedEvents: true 249 | 250 | onPressed: { 251 | if (root.app.platform !== "converged/ubuntu" || !root.mobile) 252 | mouse.accepted = false; 253 | } 254 | 255 | onClicked: { 256 | if (root.app.platform === "converged/ubuntu" && root.mobile) 257 | ubuntuOmniboxOverlay.show(); 258 | } 259 | } 260 | 261 | } 262 | 263 | } 264 | -------------------------------------------------------------------------------- /src/qml/PlayerPage.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import Material 0.2 3 | 4 | Page { 5 | // TO BE REFACTORED 6 | // TODO: refactor page and tab system 7 | title: qsTr("Player") 8 | visible: false 9 | backgroundColor: "black" 10 | 11 | //property alias player: playerRoot.player 12 | 13 | /*PlayerRoot { 14 | id: playerRoot 15 | property ListModel currentPlaylistModel: ListModel {} 16 | property var currentMedia 17 | anchors.fill: parent 18 | property bool isAudio 19 | property bool noMedia: false 20 | // Component.onCompleted: player.mrl = "http://download.blender.org/peach/bigbuckbunny_movies/big_buck_bunny_480p_stereo.avi" 21 | }*/ 22 | 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/qml/QuickSearches.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import Material 0.2 3 | import QtQuick.Controls.Styles.Material 0.1 4 | 5 | 6 | Rectangle { 7 | id: quickSearchesRoot 8 | anchors.fill: parent 9 | property bool mobileMode: width < Units.dp(640) 10 | property color textColor: root.app.darkTheme ? Theme.dark.textColor : Theme.alpha(Theme.light.textColor,1) 11 | property color linesColor: Theme.alpha(textColor, 0.6) 12 | color: root.app.darkTheme ? root.app.darkThemeColor : "white" 13 | z: -20 14 | View { 15 | id: view 16 | height: label.height + Units.dp(30) 17 | width: parent.width 18 | Label { 19 | id: label 20 | anchors { 21 | left: parent.left 22 | leftMargin: 10 23 | right: parent.right 24 | bottom: parent.bottom 25 | } 26 | text: qsTr("Customize sites colors (if no theme-color)") 27 | style: "title" 28 | color: quickSearchesRoot.textColor 29 | font.pixelSize: Units.dp(30) 30 | } 31 | } 32 | QuickSearchesList { 33 | anchors.fill: parent 34 | anchors.topMargin: view.height + Units.dp(10) 35 | textColor: parent.textColor 36 | color: parent.color 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/qml/QuickSearchesPage.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import Material 0.2 3 | 4 | Page { 5 | title: qsTr("Sites Colors") 6 | visible: false 7 | 8 | 9 | QuickSearches { 10 | anchors.fill: parent 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/qml/QuickSearchesView.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | 3 | BaseBrowserView { 4 | title: "Quick searches" 5 | icon: "action/settings" 6 | url: "liri://settings/quick-searches" 7 | 8 | QuickSearches { 9 | anchors.fill: parent 10 | } 11 | } 12 | 13 | -------------------------------------------------------------------------------- /src/qml/ResizeArea.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Window 2.2 3 | 4 | MouseArea{ 5 | id: mouseArea 6 | hoverEnabled: true 7 | property var target : mouseArea 8 | property point oPoint 9 | property string rFlag 10 | property size minSize : Qt.size(50,50) 11 | property int dragHeight: 0 12 | property rect oGeometry 13 | state: "normal" 14 | onPressed: { 15 | if(Qt.LeftButton === mouse.button){ 16 | oPoint = G_Cursor.pos() 17 | oGeometry = Qt.rect(target.x,target.y,target.width,target.height) 18 | } 19 | } 20 | onReleased:{ 21 | if(!(pressedButtons&Qt.LeftButton)){ 22 | state = "normal"; 23 | } 24 | } 25 | 26 | onDoubleClicked:{ 27 | if(root.visibility == 2) 28 | root.showMaximized(); 29 | else 30 | root.showNormal(); 31 | } 32 | 33 | onPositionChanged: { 34 | if(Qt.LeftButton & pressedButtons){ 35 | if("active" === state){ 36 | var xChange = G_Cursor.pos().x - oPoint.x; 37 | var yChange = G_Cursor.pos().y - oPoint.y; 38 | var geometry = Qt.rect(target.x,target.y,target.width,target.height) 39 | switch(rFlag[0]){ 40 | case "l": 41 | xChange = Math.min(oGeometry.width-minSize.width,xChange) 42 | geometry.x = oGeometry.x + xChange 43 | geometry.width = oGeometry.width - xChange 44 | break; 45 | case "r": 46 | xChange = Math.max(minSize.width-oGeometry.width,xChange) 47 | geometry.width = oGeometry.width + xChange 48 | break; 49 | default: break; 50 | } 51 | switch(rFlag[1]){ 52 | case "t": 53 | yChange = Math.min(oGeometry.height-minSize.height,yChange) 54 | geometry.y = oGeometry.y + yChange 55 | geometry.height = oGeometry.height - yChange 56 | break; 57 | case "b": 58 | yChange = Math.max(minSize.height-oGeometry.height,yChange) 59 | geometry.height = oGeometry.height + yChange; 60 | break; 61 | default: break; 62 | } 63 | if("md" == rFlag){ 64 | geometry.x = oGeometry.x + xChange; 65 | geometry.y = oGeometry.y + yChange; 66 | root.snappedRight = false; 67 | root.snappedLeft = false; 68 | if(geometry.y < (-3*systemBar.height/4)) { 69 | root.visibility = 4; 70 | } 71 | if(geometry.x < -100) { 72 | geometry.width = Screen.desktopAvailableWidth / 2; 73 | geometry.height = Screen.desktopAvailableHeight; 74 | root.snappedLeft = true; 75 | } 76 | if(geometry.x > Screen.desktopAvailableWidth - geometry.width + 100) { 77 | geometry.width = Screen.desktopAvailableWidth / 2; 78 | geometry.height = Screen.desktopAvailableHeight; 79 | root.snappedRight = true; 80 | } 81 | } 82 | 83 | target.x = geometry.x; 84 | target.y = geometry.y; 85 | target.width = geometry.width; 86 | target.height = geometry.height; 87 | }else{ 88 | if(Math.abs(mouse.x-oPoint.x)>3 || Math.abs(mouse.y-oPoint.y)>3){ 89 | state = "active" 90 | } 91 | } 92 | }else if(!pressedButtons){ 93 | if(mouse.x < 8) rFlag = "l" 94 | else if(mouse.x > width-8) rFlag = "r" 95 | else rFlag = "m" 96 | if(mouse.y < 8) rFlag += "t" 97 | else if(mouse.y > height-8) rFlag += "b" 98 | else if(mouse.y < dragHeight) rFlag += "d" 99 | else rFlag += "m" 100 | 101 | switch(rFlag){ 102 | case "lt": 103 | case "rb": cursorShape = Qt.SizeFDiagCursor; break; 104 | case "lb": 105 | case "rt": cursorShape = Qt.SizeBDiagCursor; break; 106 | case "ld": 107 | case "rd": 108 | case "lm": 109 | case "rm": cursorShape = Qt.SizeHorCursor; break; 110 | case "mt": 111 | case "mb": cursorShape = Qt.SizeVerCursor; break; 112 | default: cursorShape = Qt.PointingHandCursor; break; 113 | } 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/qml/RightDrawer.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 1.2 3 | import Material 0.2 4 | import Material.ListItems 0.1 as ListItem 5 | import QtQuick.Layouts 1.0 6 | 7 | View { 8 | id: drawer 9 | z:25 10 | backgroundColor: "white" 11 | elevation: 2 12 | property bool shadowIsHere: root.shadow.visible 13 | property bool toOpen: false 14 | Connections { 15 | target: root.shadow 16 | onVisibleChanged: { 17 | if(root.shadow.visible) 18 | drawer.toOpen = false 19 | } 20 | } 21 | 22 | width: Units.dp(350) 23 | anchors { 24 | right: parent.right 25 | top: parent.top 26 | bottom: parent.bottom 27 | rightMargin: toOpen && shadowIsHere ? 0 : -width - 20 28 | } 29 | 30 | children: parent.content 31 | 32 | Behavior on anchors.rightMargin { 33 | NumberAnimation { 34 | duration: 400 35 | easing.type: Easing.InOutCubic 36 | } 37 | } 38 | 39 | function open() { 40 | root.shadow.visible = true 41 | toOpen = true 42 | } 43 | 44 | function close() { 45 | root.shadow.visible = false 46 | toOpen = false 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/qml/ScrollbarThemed.qml: -------------------------------------------------------------------------------- 1 | //Code modified from : 2 | /* 3 | * QML Material - An application framework implementing Material Design. 4 | * Copyright (C) 2014 Michael Spencer 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as 8 | * published by the Free Software Foundation, either version 2.1 of the 9 | * License, or (at your option) any later version. 10 | * 11 | * This program 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 Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program. If not, see . 18 | */ 19 | import QtQuick 2.0 20 | 21 | /*! 22 | \qmltype Scrollbar 23 | \inqmlmodule Material 0.1 24 | 25 | \brief Scrollbars show scrolling progress for listviews and flickables. 26 | */ 27 | Item { 28 | id: root 29 | 30 | property Flickable flickableItem 31 | property int orientation: Qt.Vertical 32 | property int thickness: 5 33 | property bool moving: flickableItem.moving 34 | 35 | property color color 36 | property int hideTime : 500 37 | width: thickness 38 | height: thickness 39 | clip: true 40 | smooth: true 41 | visible: orientation === Qt.Vertical ? flickableItem.contentHeight > flickableItem.height 42 | : flickableItem.contentWidth > flickableItem.width 43 | 44 | anchors { 45 | top: orientation === Qt.Vertical ? flickableItem.top : undefined 46 | bottom: flickableItem.bottom 47 | left: orientation === Qt.Horizontal ? flickableItem.left : undefined 48 | right: flickableItem.right 49 | margins: 2 50 | } 51 | 52 | Component.onCompleted: hideAnimation.start() 53 | 54 | onMovingChanged: { 55 | if (moving) { 56 | hideAnimation.stop() 57 | showAnimation.start() 58 | } else { 59 | hideAnimation.start() 60 | showAnimation.stop() 61 | } 62 | } 63 | 64 | NumberAnimation { 65 | id: showAnimation 66 | target: scrollBar; 67 | property: "opacity"; 68 | to: 0.3; 69 | duration: 200; 70 | easing.type: Easing.InOutQuad 71 | } 72 | 73 | SequentialAnimation { 74 | id: hideAnimation 75 | 76 | NumberAnimation { duration: root.hideTime } 77 | NumberAnimation { 78 | target: scrollBar; 79 | property: "opacity"; 80 | to: 0; 81 | duration: 500; 82 | easing.type: Easing.InOutQuad 83 | } 84 | } 85 | 86 | onOrientationChanged: { 87 | if (orientation == Qt.Vertical) { 88 | width = thickness 89 | } else { 90 | height = thickness 91 | } 92 | } 93 | 94 | Rectangle { 95 | id: scrollBar 96 | property int length: orientation == Qt.Vertical ? root.height 97 | : root.width; 98 | property int targetLength: orientation == Qt.Vertical ? flickableItem.height 99 | : flickableItem.width; 100 | property int contentStart: orientation == Qt.Vertical ? flickableItem.contentY 101 | : flickableItem.contentX; 102 | property int contentLength: orientation == Qt.Vertical ? flickableItem.contentHeight 103 | : flickableItem.contentWidth; 104 | property int start: Math.max(0, length * contentStart/contentLength); 105 | property int end: Math.min(length, 106 | length * (contentStart + targetLength)/contentLength) 107 | 108 | color: root.color 109 | opacity: 0.3 110 | radius: thickness/2 111 | width: Math.max(orientation == Qt.Horizontal ? end - start : 0, thickness) 112 | height: Math.max(orientation == Qt.Vertical ? end - start : 0, thickness) 113 | x: orientation == Qt.Horizontal ? start : 0 114 | y: orientation == Qt.Vertical ? start : 0 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/qml/SearchSuggestions.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | import Material 0.2 3 | import Material.ListItems 0.1 as ListItem 4 | import QtQuick.Layouts 1.0 5 | import QtQuick.Controls 1.2 as Controls 6 | import Material.Extras 0.1 7 | 8 | 9 | View { 10 | width: root.app.integratedAddressbars ? parent.width / 4 : toolbar.omnibox.width 11 | height: if (toolbar.omnibox.txtUrl.activeFocus && toolbar.omnibox.txtUrl.text.length > 0) { 12 | root.app.searchSuggestionsModel.count * Units.dp(48) > Units.dp(400) ? Units.dp(400) : root.app.searchSuggestionsModel.count * Units.dp(48) 13 | } 14 | else { 15 | 0 16 | } 17 | anchors { 18 | topMargin: searchSuggestionsView.count == 0 && bookmarksBar.visible ? -bookmarksBar.height - Units.dp(30) : searchSuggestionsView.count == 0 ? -Units.dp(30) : bookmarksBar.visible ? -bookmarksBar.height : 0 19 | top: titlebar.bottom 20 | left: parent.left 21 | leftMargin: root.app.integratedAddressbars ? parent.width/4 * 1.5 : Units.dp(24) * toolbar.leftIconsCount + (toolbar.leftIconsCount + 1) * Units.dp(27) 22 | } 23 | radius: toolbar.omnibox.radius 24 | elevation: searchSuggestionsView.count == 0 ? 0 : 2 25 | visible: height > 0 26 | z:20 27 | 28 | Behavior on height { 29 | NumberAnimation { duration: 400; easing.type: Easing.InOutCubic } 30 | } 31 | 32 | Behavior on anchors.topMargin { 33 | NumberAnimation { duration: 400; easing.type: Easing.InOutCubic } 34 | } 35 | 36 | Behavior on elevation { 37 | NumberAnimation { duration: 400; easing.type: Easing.InOutCubic } 38 | } 39 | 40 | Behavior on width { 41 | NumberAnimation { duration: 400; easing.type: Easing.InOutCubic } 42 | } 43 | 44 | ListView { 45 | id: searchSuggestionsView 46 | width: parent.width 47 | property int currentpos: root.selectedQueryIndex 48 | height: parent.height 49 | boundsBehavior: Flickable.StopAtBounds 50 | model: root.app.searchSuggestionsModel 51 | onCurrentposChanged: { 52 | positionViewAtIndex(currentpos,ListView.End) 53 | } 54 | 55 | delegate: ListItem.Standard { 56 | text: suggestion 57 | iconName: icon 58 | backgroundColor: root.selectedQueryIndex == index && (icon == "action/search" || icon == "action/bookmark" || icon == "action/history")? Qt.rgba(0,0,0,0.05) : "transparent" 59 | onClicked: { 60 | setActiveTabURL(suggestion) 61 | root.app.searchSuggestionsModel.clear() 62 | } 63 | } 64 | } 65 | ScrollbarThemed { 66 | flickableItem: searchSuggestionsView 67 | color: Qt.rgba(0,0,0,0.5) 68 | hideTime: 1000 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/qml/SettingsDrawer.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import Material 0.2 3 | import Material.ListItems 0.1 as ListItem 4 | 5 | 6 | NavigationDrawer { 7 | id:drawer 8 | z:25 9 | mode: "right" 10 | width:Units.dp(350) 11 | dismissOnTap: true 12 | 13 | Column { 14 | width: parent.width 15 | spacing: Units.dp(0) 16 | View { 17 | id: view 18 | height: label.height + Units.dp(30) 19 | width: parent.width 20 | Label { 21 | id: label 22 | anchors { 23 | left: parent.left 24 | right: parent.right 25 | bottom: parent.bottom 26 | leftMargin: Units.dp(16) 27 | rightMargin: Units.dp(16) 28 | centerIn:parent 29 | } 30 | text: qsTr("Settings") 31 | style: "title" 32 | font.pixelSize: Units.dp(30) 33 | } 34 | } 35 | 36 | Item { 37 | height: Units.dp(48) 38 | width: parent.width 39 | Label { 40 | style: "title" 41 | text: qsTr("General") 42 | anchors.centerIn: parent 43 | } 44 | } 45 | 46 | ListItem.Standard { 47 | text: "" 48 | height:60 49 | TextField { 50 | id: txtHomeUrl 51 | width: parent.width * 0.9 52 | text: root.app.homeUrl 53 | placeholderText: qsTr("Start page") 54 | floatingLabel: true 55 | anchors.centerIn: parent 56 | } 57 | } 58 | 59 | ListItem.Standard { 60 | text: "" 61 | height:60 62 | MenuField { 63 | id: menuSearchEngine 64 | property string selectedEngine: model[selectedIndex].toLowerCase(); 65 | width: parent.width * 0.9 66 | model: getListedSearchEngines() 67 | helperText: "Search Engine" 68 | anchors.centerIn: parent 69 | 70 | function getListedSearchEngines() { 71 | if(root.app.searchEngine == "duckduckgo") 72 | return ["DuckDuckGo", "Google", "Yahoo"] 73 | else if(root.app.searchEngine == "yahoo") 74 | return ["Yahoo", "Google", "DuckDuckGo"] 75 | else 76 | return ["Google", "DuckDuckGo", "Yahoo"] 77 | } 78 | } 79 | anchors.bottomMargin: 30 80 | } 81 | 82 | Item { 83 | height: Units.dp(48) 84 | width: parent.width 85 | Label { 86 | style: "title" 87 | text: qsTr("Appearance") 88 | anchors.centerIn: parent 89 | } 90 | } 91 | 92 | ListItem.Standard { 93 | Row { 94 | anchors.fill: parent 95 | spacing: Units.dp(12) 96 | CheckBox { 97 | id: chbDashboard 98 | checked: root.app.newTabPage 99 | anchors.verticalCenter: parent.verticalCenter 100 | } 101 | Label { 102 | text: qsTr("Enable dashboard") 103 | anchors.verticalCenter: parent.verticalCenter 104 | font.pixelSize: Units.dp(16) 105 | } 106 | } 107 | onClicked: { 108 | chbDashboard.checked = !chbDashboard.checked 109 | } 110 | } 111 | 112 | ListItem.Standard { 113 | Row { 114 | anchors.fill: parent 115 | spacing: Units.dp(12) 116 | CheckBox { 117 | id: chbIntegratedAddressbars 118 | checked: root.app.integratedAddressbars 119 | anchors.verticalCenter: parent.verticalCenter 120 | } 121 | Label { 122 | text: qsTr("Integrated addressbars") 123 | anchors.verticalCenter: parent.verticalCenter 124 | font.pixelSize: Units.dp(16) 125 | } 126 | } 127 | onClicked: { 128 | chbIntegratedAddressbars.checked = !chbIntegratedAddressbars.checked 129 | } 130 | } 131 | 132 | ListItem.Standard { 133 | Row { 134 | anchors.fill: parent 135 | spacing: Units.dp(12) 136 | CheckBox { 137 | id: chbTabsEntirelyColorized 138 | checked: root.app.tabsEntirelyColorized 139 | anchors.verticalCenter: parent.verticalCenter 140 | } 141 | Label { 142 | text: qsTr("Colorize the entire tab and toolbar") 143 | anchors.verticalCenter: parent.verticalCenter 144 | font.pixelSize: Units.dp(16) 145 | } 146 | } 147 | onClicked: { 148 | chbTabsEntirelyColorized.checked = !chbTabsEntirelyColorized.checked 149 | } 150 | } 151 | 152 | Item { 153 | height: Units.dp(48) 154 | width: parent.width 155 | Label { 156 | style: "title" 157 | text: qsTr("Theme") 158 | anchors.centerIn: parent 159 | } 160 | } 161 | 162 | ListItem.Standard { 163 | id:primaryChooser 164 | text: qsTr('Primary Color') 165 | Rectangle { 166 | id: primarycolorSample 167 | width:30 168 | height:30 169 | radius: width*0.5 170 | color: primaryColorPicker.color 171 | anchors { 172 | top: parent.top 173 | right: parent.right 174 | rightMargin:20 175 | topMargin:5 176 | } 177 | MouseArea { 178 | anchors.fill: parent 179 | onPressed: primaryColorPicker.open(primarycolorSample, Units.dp(4), Units.dp(-4)) 180 | } 181 | } 182 | } 183 | 184 | ListItem.Standard { 185 | id:accentChooser 186 | text: qsTr('Accent Color') 187 | Rectangle { 188 | id: accentcolorSample 189 | width:30 190 | height:30 191 | radius: width*0.5 192 | color: accentColorPicker.color 193 | anchors { 194 | top: parent.top 195 | right: parent.right 196 | rightMargin:20 197 | topMargin:5 198 | } 199 | MouseArea { 200 | anchors.fill: parent 201 | onPressed: accentColorPicker.open(accentcolorSample, Units.dp(4), Units.dp(-4)) 202 | } 203 | } 204 | } 205 | 206 | ColorPicker { 207 | id: primaryColorPicker 208 | color: theme.primaryColor 209 | } 210 | 211 | ColorPicker { 212 | id: accentColorPicker 213 | color: theme.accentColor 214 | } 215 | 216 | } 217 | 218 | Item { 219 | anchors { 220 | left: parent.left 221 | right: parent.right 222 | bottom: parent.bottom 223 | margins: Units.dp(16) 224 | } 225 | height: Units.dp(50) 226 | Row { 227 | spacing:10 228 | anchors.horizontalCenter: parent.horizontalCenter 229 | Button { 230 | text: qsTr("Save") 231 | elevation: 1 232 | backgroundColor: Theme.accentColor 233 | onClicked: { 234 | theme.primaryColor = primaryColorPicker.color; 235 | theme.accentColor = accentColorPicker.color; 236 | root.app.homeUrl = txtHomeUrl.text; 237 | root.app.searchEngine = menuSearchEngine.selectedEngine; 238 | root.app.integratedAddressbars = chbIntegratedAddressbars.checked; 239 | root.app.tabsEntirelyColorized = chbTabsEntirelyColorized.checked; 240 | root.app.newTabPage = chbDashboard.checked; 241 | drawer.close(); 242 | } 243 | } 244 | Button { 245 | text: qsTr("Abort") 246 | elevation: 1 247 | onClicked: { 248 | accentColorPicker.color = theme.accentColor; 249 | primaryColorPicker.color = theme.primaryColor; 250 | txtHomeUrl.text = root.app.homeUrl; 251 | chbDashboard.checked = root.app.newTabPage; 252 | chbIntegratedAddressbars.checked = root.app.integratedAddressbars; 253 | chbTabsEntirelyColorized.checked = root.app.tabsEntirelyColorized; 254 | drawer.close(); 255 | } 256 | } 257 | } 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /src/qml/SettingsPage.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import Material 0.2 3 | 4 | Page { 5 | title: qsTr("Settings") 6 | visible: false 7 | 8 | 9 | Settings { 10 | anchors.fill: parent 11 | } 12 | 13 | } 14 | 15 | -------------------------------------------------------------------------------- /src/qml/SettingsView.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import Material 0.2 3 | 4 | BaseBrowserView { 5 | 6 | title: "Settings" 7 | icon: "action/settings" 8 | Component.onCompleted: console.log(icon) 9 | url: "liri://settings" 10 | 11 | Settings { 12 | anchors.fill: parent 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/qml/ShadowOverlay.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | 3 | Rectangle { 4 | id: shadow 5 | signal clicked 6 | color: Qt.rgba(0,0,0,0.1) 7 | anchors.fill: parent 8 | z:19 9 | visible: false 10 | MouseArea { 11 | anchors.fill: parent 12 | onClicked: { 13 | shadow.clicked() 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/qml/ShortcutActions.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 1.2 as Controls 3 | 4 | 5 | Item { 6 | Controls.Action { 7 | shortcut: "Ctrl+D" 8 | onTriggered: { 9 | downloadsDrawer.open(); 10 | } 11 | } 12 | Controls.Action { 13 | shortcut: "Ctrl+H" 14 | onTriggered: { 15 | historyDrawer.open(); 16 | } 17 | } 18 | Controls.Action { 19 | shortcut: "Ctrl+B" 20 | onTriggered: { 21 | bookmarksDrawer.open(); 22 | } 23 | } 24 | Controls.Action { 25 | shortcut: "Ctrl+Alt+S" 26 | onTriggered: { 27 | addTab("liri://settings"); 28 | } 29 | } 30 | Controls.Action { 31 | shortcut: "Ctrl+F" 32 | onTriggered: { 33 | if (root.activeTab.view.isWebView) 34 | root.showSearchOverlay() 35 | } 36 | } 37 | Controls.Action { 38 | id: focus 39 | shortcut: "Ctrl+L" 40 | onTriggered: { 41 | if (root.app.integratedAddressbars && !root.activeTabInEditMode){ 42 | root.activeTabItem.editModeActive = true; 43 | } 44 | else { 45 | toolbar.omnibox.txtUrl.forceActiveFocus(); 46 | toolbar.omnibox.txtUrl.selectAll(); 47 | } 48 | } 49 | } 50 | Controls.Action { 51 | shortcut: "Ctrl+K" 52 | onTriggered: { 53 | if (root.app.integratedAddressbars && !root.activeTabInEditMode){ 54 | root.activeTabItem.editModeActive = true; 55 | } 56 | else { 57 | toolbar.omnibox.txtUrl.forceActiveFocus(); 58 | toolbar.omnibox.txtUrl.selectAll(); 59 | } 60 | } 61 | } 62 | Controls.Action { 63 | shortcut: "Ctrl+R" 64 | onTriggered: { 65 | root.activeTab.view.reload(); 66 | } 67 | } 68 | Controls.Action { 69 | shortcut: 19 70 | onTriggered: { 71 | root.addTab() 72 | toolbar.omnibox.txtUrl.forceActiveFocus(); 73 | toolbar.omnibox.txtUrl.selectAll(); 74 | } 75 | } 76 | Controls.Action { 77 | shortcut: "Ctrl+SHIFT+T" 78 | onTriggered: { 79 | root.reopenLastClosedTab() 80 | } 81 | } 82 | Controls.Action { 83 | shortcut: "Ctrl+W" 84 | onTriggered: { 85 | root.removeTab(activeTab.uid); 86 | } 87 | } 88 | Controls.Action { 89 | shortcut: "Escape" 90 | onTriggered: { 91 | if (root.app.integratedAddressbars && root.activeTabInEditMode){ 92 | root.activeTabItem.editModeActive = false; 93 | } 94 | else if (root.txtSearch.visible){ 95 | root.hideSearchOverlay(); 96 | } 97 | else if (root.fullscreen){ 98 | root.endFullscreenMode(); 99 | } 100 | } 101 | } 102 | Controls.Action { 103 | shortcut: "Ctrl+0" 104 | onTriggered: root.activeTab.view.zoomReset(); 105 | } 106 | Controls.Action { 107 | shortcut: 17 108 | onTriggered: root.activeTab.view.zoomOut(); 109 | } 110 | Controls.Action { 111 | shortcut: 16 112 | onTriggered: root.activeTab.view.zoomIn(); 113 | } 114 | Controls.Action { 115 | shortcut: "Ctrl+Tab" 116 | onTriggered: { 117 | var currentIndex = root.getTabModelIndexByUID(root.activeTab.uid); 118 | if (currentIndex === root.tabsModel.count-1) 119 | root.activeTab = tabsModel.get(0); 120 | else 121 | root.activeTab = tabsModel.get(currentIndex+1); 122 | } 123 | } 124 | Controls.Action { 125 | shortcut: "Ctrl+SHIFT+Tab" 126 | onTriggered: { 127 | var currentIndex = root.getTabModelIndexByUID(root.activeTab.uid); 128 | if (currentIndex === 0) 129 | root.activeTab = tabsModel.get(root.tabsModel.count-1); 130 | else 131 | root.activeTab = tabsModel.get(currentIndex-1); 132 | } 133 | } 134 | Controls.Action { 135 | shortcut: "F5" 136 | onTriggered: { 137 | root.activeTab.view.reload() 138 | } 139 | } 140 | Controls.Action { 141 | shortcut: "F6" 142 | onTriggered: { 143 | if (root.app.integratedAddressbars && !root.activeTabInEditMode){ 144 | root.activeTabItem.editModeActive = true; 145 | } 146 | else { 147 | toolbar.omnibox.txtUrl.forceActiveFocus(); 148 | toolbar.omnibox.txtUrl.selectAll(); 149 | } 150 | } 151 | } 152 | Controls.Action { 153 | shortcut: "F11" 154 | onTriggered: { 155 | if (!root.fullscreen){ 156 | root.startFullscreenMode(); 157 | } 158 | else { 159 | root.endFullscreenMode(); 160 | } 161 | } 162 | } 163 | Controls.Action { 164 | shortcut: "SHIFT+Backspace" 165 | onTriggered: { 166 | if (root.activeTab.view.canGoForward) 167 | root.activeTab.view.goForward(); 168 | } 169 | } 170 | /* 171 | Controls.Action { 172 | shortcut: "Backspace" 173 | onTriggered: { 174 | if (root.activeTab.view.canGoBack) 175 | root.activeTab.view.goBack(); 176 | } 177 | }*/ 178 | } 179 | -------------------------------------------------------------------------------- /src/qml/SitesColors.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import Material 0.2 3 | 4 | 5 | Rectangle { 6 | id: sitesColorsRoot 7 | anchors.fill: parent 8 | property color textColor: root.app.darkTheme ? Theme.dark.textColor : Theme.alpha(Theme.light.textColor,1) 9 | property color linesColor: Theme.alpha(textColor, 0.6) 10 | color: root.app.darkTheme ? root.app.darkThemeColor : "white" 11 | z: -20 12 | View { 13 | id: view 14 | height: label.height + Units.dp(30) 15 | width: parent.width 16 | Label { 17 | id: label 18 | anchors { 19 | left: parent.left 20 | leftMargin: 10 21 | right: parent.right 22 | bottom: parent.bottom 23 | } 24 | text: qsTr("Customize sites colors (if no theme-color)") 25 | style: "title" 26 | color: sitesColorsRoot.textColor 27 | font.pixelSize: Units.dp(30) 28 | } 29 | } 30 | SitesColorsList { 31 | anchors.fill: parent 32 | anchors.topMargin: view.height + Units.dp(10) 33 | textColor: parent.textColor 34 | color: parent.color 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/qml/SitesColorsList.qml: -------------------------------------------------------------------------------- 1 | 2 | import QtQuick 2.0 3 | import Material 0.2 4 | import Material.ListItems 0.1 as ListItem 5 | import QtQuick.Controls.Styles 1.3 6 | import QtQuick.Layouts 1.1 7 | import QtQuick.Controls.Styles.Material 0.1 8 | 9 | Item { 10 | id: item 11 | property string color 12 | property string textColor 13 | Rectangle { 14 | id: flickable 15 | color: item.color 16 | anchors.fill: parent 17 | Row { 18 | spacing: 10 19 | Column { 20 | width: flickable.width / 2 - 20 21 | Item { 22 | height: Units.dp(60) 23 | width: parent.width 24 | Label { 25 | style: "title" 26 | text: qsTr("Presets") 27 | anchors.verticalCenter: parent.verticalCenter 28 | anchors.left: parent.left 29 | anchors.margins: Units.dp(16) 30 | color: item.textColor 31 | } 32 | } 33 | ListView { 34 | id: listViewPresets 35 | width: parent.width 36 | height: flickable.height - Units.dp(60) 37 | model: root.app.presetSitesColorsModel 38 | anchors.left:parent.left 39 | anchors.margins: Units.dp(16) 40 | delegate: ListItem.Standard { 41 | 42 | Row { 43 | spacing: Units.dp(10) 44 | anchors{ 45 | margins: Units.dp(10) 46 | left: parent.left 47 | verticalCenter: parent.verticalCenter 48 | } 49 | 50 | TextField { 51 | text: listViewPresets.model.get(index).domain 52 | placeholderText: qsTr("Domain") 53 | floatingLabel: true 54 | anchors{ 55 | verticalCenter: parent.verticalCenter 56 | } 57 | onTextChanged: listViewPresets.model.set(index, {"domain": text}) 58 | style: TextFieldThemed { 59 | helperNotFocusedColor: sitesColorsRoot.linesColor 60 | textColor: sitesColorsRoot.textColor 61 | } 62 | } 63 | 64 | TextField { 65 | text: listViewPresets.model.get(index).color 66 | onTextChanged: listViewPresets.model.set(index, {"color": text}) 67 | placeholderText: qsTr("Color") 68 | floatingLabel: true 69 | anchors{ 70 | verticalCenter: parent.verticalCenter 71 | } 72 | style: TextFieldThemed { 73 | helperNotFocusedColor: sitesColorsRoot.linesColor 74 | textColor: sitesColorsRoot.textColor 75 | } 76 | } 77 | } 78 | } 79 | } 80 | 81 | } 82 | 83 | Column { 84 | width: flickable.width / 2 - 20 85 | Item { 86 | height: Units.dp(60) 87 | width: parent.width 88 | Label { 89 | style: "title" 90 | text: qsTr("Custom (can overwrite presets)") 91 | anchors.verticalCenter: parent.verticalCenter 92 | anchors.margins: Units.dp(16) 93 | anchors.left: parent.left 94 | color: item.textColor 95 | } 96 | } 97 | ListView { 98 | id: listView 99 | width: parent.width 100 | height: flickable.height - Units.dp(60) 101 | model: root.app.customSitesColorsModel 102 | anchors.margins: Units.dp(16) 103 | anchors.left: parent.left 104 | 105 | delegate: ListItem.Standard { 106 | 107 | Row { 108 | spacing: Units.dp(10) 109 | anchors{ 110 | margins: Units.dp(10) 111 | left: parent.left 112 | verticalCenter: parent.verticalCenter 113 | } 114 | 115 | TextField { 116 | text: listView.model.get(index).domain 117 | placeholderText: qsTr("Domain") 118 | floatingLabel: true 119 | anchors{ 120 | verticalCenter: parent.verticalCenter 121 | } 122 | onTextChanged: { 123 | listView.model.set(index, {"domain": text}) 124 | root.app.saveSitesColors(); 125 | } 126 | style: TextFieldThemed { 127 | helperNotFocusedColor: sitesColorsRoot.linesColor 128 | textColor: sitesColorsRoot.textColor 129 | } 130 | } 131 | 132 | TextField { 133 | text: listView.model.get(index).color 134 | placeholderText: qsTr("Color") 135 | floatingLabel: true 136 | onTextChanged: { 137 | listView.model.set(index, {"color": text}) 138 | root.app.saveSitesColors(); 139 | } 140 | anchors{ 141 | verticalCenter: parent.verticalCenter 142 | } 143 | style: TextFieldThemed { 144 | helperNotFocusedColor: sitesColorsRoot.linesColor 145 | textColor: sitesColorsRoot.textColor 146 | } 147 | } 148 | 149 | IconButton { 150 | iconName: "action/delete" 151 | color: item.textColor 152 | size: Units.dp(15) 153 | onClicked: { 154 | listView.model.remove(index) 155 | root.app.saveSitesColors(); 156 | } 157 | anchors{ 158 | verticalCenter: parent.verticalCenter 159 | } 160 | } 161 | } 162 | } 163 | } 164 | 165 | 166 | 167 | } 168 | 169 | } 170 | 171 | } 172 | ActionButton { 173 | anchors.right: parent.right 174 | anchors.bottom: parent.bottom 175 | anchors.margins: Units.dp( 48 ) 176 | iconName: "content/add" 177 | text: qsTr("Add new color") 178 | onClicked: { 179 | root.app.customSitesColorsModel.append({"domain":"", "color": ""}) 180 | root.app.saveSitesColors(); 181 | } 182 | } 183 | 184 | } 185 | -------------------------------------------------------------------------------- /src/qml/SitesColorsPage.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import Material 0.2 3 | 4 | Page { 5 | title: qsTr("Sites Colors") 6 | visible: false 7 | 8 | 9 | SitesColors { 10 | anchors.fill: parent 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/qml/SitesColorsView.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | 3 | BaseBrowserView { 4 | title: "Sites Colors" 5 | icon: "action/settings" 6 | url: "liri://settings/sites-colors" 7 | 8 | SitesColors { 9 | anchors.fill: parent 10 | } 11 | } 12 | 13 | -------------------------------------------------------------------------------- /src/qml/SystemBar.qml: -------------------------------------------------------------------------------- 1 | import Material 0.2 2 | import QtQuick 2.2 3 | 4 | Rectangle { 5 | 6 | anchors { 7 | top: parent.top; 8 | left: parent.left; 9 | right: parent.right; 10 | margins: 0 11 | bottomMargin: 0 12 | } 13 | 14 | height: tabsModel.count > 1 || root.app.integratedAddressbars ? root.tabHeight : Units.dp(30) 15 | property color chosenColor: root.activeTab.view.customColor ? root.activeTab.view.customColor : root.app.lightThemeColor 16 | color: root.app.elevatedToolbar ? shadeColor(chosenColor,-0.1) : "transparent" 17 | } 18 | -------------------------------------------------------------------------------- /src/qml/SystemButtons.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | import Material 0.2 3 | import Material.ListItems 0.1 as ListItem 4 | import QtQuick.Layouts 1.0 5 | import QtQuick.Controls 1.2 as Controls 6 | 7 | Rectangle { 8 | id:_back 9 | width:_row.implicitWidth 10 | height: tabsModel.count == 1 ? _row.implicitHeight: Units.dp(38) 11 | signal showMinimized; 12 | signal showMaximized; 13 | signal showFullScreen; 14 | signal showNormal; 15 | signal close; 16 | 17 | RowLayout { 18 | id: _row 19 | anchors{ 20 | right: parent.right 21 | top: parent.top 22 | rightMargin: Units.dp(5) 23 | topMargin: tabsModel.count == 1 ? Units.dp(4) : Units.dp(8) 24 | } 25 | 26 | spacing: Units.dp(10) 27 | property string iconsColor: root.shadow.visible ? "black" : root.currentIconColor 28 | 29 | Rectangle { 30 | width:1 31 | color: "transparent" 32 | } 33 | IconButton { 34 | iconName: "navigation/expand_more" 35 | width: Units.dp(20) 36 | height: width 37 | color: parent.iconsColor 38 | Behavior on color { ColorAnimation { duration : 500 }} 39 | onClicked: _back.showMinimized() 40 | } 41 | 42 | IconButton { 43 | iconName: root.visibility == 4 ? "image/crop_7_5" : "image/crop_3_2" 44 | width: Units.dp(20) 45 | id: sysbtn_max 46 | height: width 47 | color: parent.iconsColor 48 | Behavior on color { ColorAnimation { duration : 500 }} 49 | onClicked: { 50 | if(root.visibility == 2) 51 | _back.showMaximized(); 52 | else 53 | _back.showNormal(); 54 | } 55 | } 56 | 57 | IconButton { 58 | iconName: "navigation/close" 59 | width: Units.dp(20) 60 | height: width 61 | color: parent.iconsColor 62 | Behavior on color { ColorAnimation { duration : 500 }} 63 | onClicked: _back.close() 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/qml/TabsList.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import Material 0.2 3 | 4 | Item { 5 | 6 | ListView { 7 | id: listView 8 | anchors.fill: parent 9 | model: root.tabsModel 10 | anchors.margins: Units.gu(1) 11 | spacing: Units.dp(16) 12 | 13 | delegate: Card { 14 | property int uid: (index >= 0) ? listView.model.get(index).uid : -1 15 | 16 | property color backgroundColor: root.defaultBackgroundColor 17 | property color defaultTextColor: root.defaultForegroundColor 18 | property color textColor: defaultTextColor 19 | 20 | width: parent.width 21 | height: Units.dp(48) 22 | 23 | Rectangle { 24 | color: backgroundColor 25 | anchors.fill: parent 26 | 27 | Row { 28 | id: row 29 | anchors.left: parent.left 30 | anchors.top: parent.top 31 | anchors.bottom: parent.bottom 32 | anchors.right: closeButton.left 33 | spacing: Units.dp(16) 34 | anchors.margins: Units.dp(16) 35 | 36 | Image { 37 | id: icon 38 | visible: isAFavicon && !model.view.loading && typeof(model.view.icon) !== 'string' 39 | width: view.loading ? 0 : Units.dp(20) 40 | height: Units.dp(20) 41 | anchors.verticalCenter: parent.verticalCenter 42 | source: model.view.icon 43 | property bool isAFavicon: true 44 | onStatusChanged: { 45 | if (icon.status == Image.Error || icon.status == Image.Null) 46 | isAFavicon = false; 47 | else 48 | isAFavicon = true; 49 | } 50 | } 51 | 52 | Icon { 53 | id: iconNoFavicon 54 | color: item.foregroundColor 55 | Behavior on color { ColorAnimation { duration : 500 }} 56 | name: "action/description" 57 | visible: !icon.isAFavicon && !model.view.loading && typeof(model.view.icon) !== 'string' 58 | anchors.verticalCenter: parent.verticalCenter 59 | } 60 | 61 | Icon { 62 | color: item.foregroundColor 63 | Behavior on color { ColorAnimation { duration : 500 }} 64 | visible: typeof(model.view.icon) === 'string' && !iconNoFavicon.visible 65 | name: model.view.icon 66 | anchors.verticalCenter: parent.verticalCenter 67 | } 68 | 69 | LoadingIndicator { 70 | id: prgLoading 71 | visible: model.view.loading 72 | width: model.view.loading ? Units.dp(24) : 0 73 | height: Units.dp(24) 74 | anchors.verticalCenter: parent.verticalCenter 75 | } 76 | 77 | Text { 78 | text: model.view.title 79 | color: textColor 80 | width: parent.width - closeButton.width - icon.width - prgLoading.width - Units.dp(16) 81 | elide: Text.ElideRight 82 | smooth: true 83 | clip: true 84 | anchors.verticalCenter: parent.verticalCenter 85 | font.family: root.fontFamily 86 | } 87 | 88 | } 89 | 90 | MouseArea { 91 | x: row.x 92 | y: row.y 93 | width: row.width 94 | height: row.height 95 | 96 | onClicked: { 97 | setActiveTab(uid, true); 98 | page.pop(); 99 | } 100 | } 101 | 102 | IconButton { 103 | id: closeButton 104 | color: textColor 105 | anchors.right: parent.right 106 | anchors.rightMargin: Units.dp(16) 107 | anchors.verticalCenter: parent.verticalCenter 108 | visible: model.hasCloseButton 109 | iconName: model.closeButtonIconName 110 | onClicked: { 111 | console.log("Close tab") 112 | root.removeTab(uid); 113 | } 114 | } 115 | 116 | } 117 | 118 | } 119 | 120 | } 121 | 122 | ActionButton { 123 | anchors.right: parent.right 124 | anchors.bottom: parent.bottom 125 | anchors.margins: Units.dp( 48 ) 126 | iconName: "content/add" 127 | text: qsTr("Add tab") 128 | onClicked: root.addTab(); 129 | } 130 | 131 | } 132 | -------------------------------------------------------------------------------- /src/qml/TabsListPage.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import Material 0.2 3 | 4 | 5 | Page { 6 | id: page 7 | title: qsTr("Tabs") 8 | visible: false 9 | TabsList { 10 | anchors.fill: parent 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/qml/TextFieldThemed.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * QML Material - An application framework implementing Material Design. 3 | * Copyright (C) 2015 Ricardo Vieira 4 | * 2015 Michael Spencer 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as 8 | * published by the Free Software Foundation, either version 2.1 of the 9 | * License, or (at your option) any later version. 10 | * 11 | * This program 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 Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program. If not, see . 18 | */ 19 | import QtQuick 2.0 20 | import QtQuick.Controls.Styles 1.3 21 | import QtQuick.Layouts 1.1 22 | import Material 0.2 23 | 24 | TextFieldStyle { 25 | id: style 26 | 27 | padding { 28 | left: 0 29 | right: 0 30 | top: 0 31 | bottom: 0 32 | } 33 | 34 | font { 35 | family: "Roboto" 36 | pixelSize: Units.dp(16) 37 | } 38 | 39 | renderType: Text.QtRendering 40 | property color helperNotFocusedColor 41 | placeholderTextColor: "transparent" 42 | selectedTextColor: "white" 43 | selectionColor: control.hasOwnProperty("color") ? control.color : Theme.accentColor 44 | textColor: Theme.light.textColor 45 | 46 | background : Item { 47 | id: background 48 | 49 | property color color: control.hasOwnProperty("color") ? control.color : Theme.accentColor 50 | property color errorColor: control.hasOwnProperty("errorColor") 51 | ? control.errorColor : Palette.colors["red"]["500"] 52 | property string helperText: control.hasOwnProperty("helperText") ? control.helperText : "" 53 | property bool floatingLabel: control.hasOwnProperty("floatingLabel") ? control.floatingLabel : "" 54 | property bool hasError: control.hasOwnProperty("hasError") 55 | ? control.hasError : characterLimit && control.length > characterLimit 56 | property int characterLimit: control.hasOwnProperty("characterLimit") ? control.characterLimit : 0 57 | property bool showBorder: control.hasOwnProperty("showBorder") ? control.showBorder : true 58 | 59 | Rectangle { 60 | id: underline 61 | color: background.hasError ? background.errorColor 62 | : control.activeFocus ? background.color 63 | : helperNotFocusedColor 64 | 65 | height: control.activeFocus ? Units.dp(2) : Units.dp(1) 66 | visible: background.showBorder 67 | 68 | anchors { 69 | left: parent.left 70 | right: parent.right 71 | bottom: parent.bottom 72 | } 73 | 74 | Behavior on height { 75 | NumberAnimation { duration: 200 } 76 | } 77 | 78 | Behavior on color { 79 | ColorAnimation { duration: 200 } 80 | } 81 | } 82 | 83 | 84 | Label { 85 | id: fieldPlaceholder 86 | 87 | anchors.verticalCenter: parent.verticalCenter 88 | text: control.placeholderText 89 | font.pixelSize: Units.dp(16) 90 | anchors.margins: -Units.dp(12) 91 | color: background.color 92 | 93 | states: [ 94 | State { 95 | name: "floating" 96 | when: control.displayText.length > 0 && background.floatingLabel 97 | AnchorChanges { 98 | target: fieldPlaceholder 99 | anchors.verticalCenter: undefined 100 | anchors.top: parent.top 101 | } 102 | PropertyChanges { 103 | target: fieldPlaceholder 104 | font.pixelSize: Units.dp(12) 105 | } 106 | }, 107 | State { 108 | name: "hidden" 109 | when: control.displayText.length > 0 && !background.floatingLabel 110 | PropertyChanges { 111 | target: fieldPlaceholder 112 | visible: false 113 | } 114 | } 115 | ] 116 | 117 | transitions: [ 118 | Transition { 119 | id: floatingTransition 120 | enabled: false 121 | AnchorAnimation { 122 | duration: 200 123 | } 124 | NumberAnimation { 125 | duration: 200 126 | property: "font.pixelSize" 127 | } 128 | } 129 | ] 130 | 131 | Component.onCompleted: floatingTransition.enabled = true 132 | } 133 | 134 | RowLayout { 135 | anchors { 136 | left: parent.left 137 | right: parent.right 138 | top: underline.top 139 | topMargin: Units.dp(4) 140 | } 141 | 142 | Label { 143 | id: helperTextLabel 144 | visible: background.helperText && background.showBorder 145 | text: background.helperText 146 | font.pixelSize: Units.dp(12) 147 | color: style.helperNotFocusedColor ? style.helperNotFocusedColor : "white" 148 | 149 | Behavior on color { 150 | ColorAnimation { duration: 200 } 151 | } 152 | 153 | property string helperText: control.hasOwnProperty("helperText") 154 | ? control.helperText : "" 155 | } 156 | 157 | Label { 158 | id: charLimitLabel 159 | Layout.alignment: Qt.AlignVCenter | Qt.AlignRight 160 | visible: background.characterLimit && background.showBorder 161 | text: control.length + " / " + background.characterLimit 162 | font.pixelSize: Units.dp(12) 163 | color: background.hasError ? background.errorColor : Theme.light.hintColor 164 | horizontalAlignment: Text.AlignLeft 165 | 166 | Behavior on color { 167 | ColorAnimation { duration: 200 } 168 | } 169 | } 170 | } 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/qml/UbuntuOmniboxOverlay.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import Material 0.2 as Material 3 | import Ubuntu.Components 1.3 4 | 5 | 6 | Rectangle { 7 | id: view 8 | anchors.fill: parent 9 | visible: false 10 | opacity: visible ? 1.0 : 0 11 | color: root.currentBackgroundColor 12 | 13 | function show() { 14 | textField.text = root.activeTab.view.url; 15 | visible = true; 16 | textField.forceActiveFocus(); 17 | textField.selectAll(); 18 | } 19 | 20 | function hide() { 21 | visible = false; 22 | } 23 | 24 | Material.Icon { 25 | id: connectionIcon 26 | anchors.verticalCenter: parent.verticalCenter 27 | anchors.left: parent.left 28 | anchors.leftMargin: Material.Units.dp(15) 29 | color: root.currentIconColor 30 | name: "action/search" 31 | } 32 | 33 | TextField { 34 | id: textField 35 | 36 | text: root.activeTab.view.url 37 | placeholderText: qsTr("Search or enter website name") 38 | 39 | anchors.left: connectionIcon.right 40 | anchors.verticalCenter: parent.verticalCenter 41 | anchors.right: hideButton.left 42 | anchors.leftMargin: Material.Units.dp(15) 43 | anchors.rightMargin: Material.Units.dp(15) 44 | 45 | inputMethodHints: Qt.ImhUrlCharactersOnly 46 | 47 | onTextChanged: { 48 | isASearchQuery(text) ? connectionIcon.name = "action/search" : connectionIcon.name = "social/public"; 49 | } 50 | onAccepted: { 51 | setActiveTabURL(text); 52 | hide(); 53 | } 54 | onActiveFocusChanged: { 55 | if(!activeFocus) 56 | hide(); 57 | } 58 | } 59 | 60 | Material.IconButton { 61 | id: hideButton 62 | iconName: "hardware/keyboard_return" 63 | anchors.verticalCenter: parent.verticalCenter 64 | anchors.right: parent.right 65 | anchors.rightMargin: Material.Units.dp(15) 66 | onClicked: { 67 | hide(); 68 | } 69 | color: root.currentIconColor 70 | } 71 | 72 | Behavior on opacity { 73 | NumberAnimation { 74 | duration: 200 75 | easing.type: Easing.InOutQuad 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/qml/oxide-user.js: -------------------------------------------------------------------------------- 1 | 2 | oxide.addMessageHandler("GET_HTML", function (msg) { 3 | var event = new CustomEvent("QMLmessage", {detail: msg.args}); 4 | document.dispatchEvent(event); 5 | msg.reply({html: document.documentElement.innerHTML}); 6 | }); 7 | 8 | oxide.addMessageHandler("RUN_JAVASCRIPT", function (msg) { 9 | var event = new CustomEvent("QMLmessage", {detail: msg.args}); 10 | document.dispatchEvent(event); 11 | msg.reply({result: eval(msg.args["script"])}); 12 | }); 13 | 14 | oxide.addMessageHandler("SET_SOURCE", function (msg) { 15 | var event = new CustomEvent("QMLmessage", {detail: msg.args}); 16 | document.dispatchEvent(event); 17 | 18 | var e = new CustomEvent("SetSource", {detail: {theme: msg.args["theme"], temp: msg.args["temp"]}}); 19 | document.dispatchEvent(e); 20 | 21 | msg.reply({result: true}); 22 | }); 23 | -------------------------------------------------------------------------------- /src/qml/ubuntu/Main.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | import Material 0.2 3 | import QtQuick.Window 2.0 4 | import QtSystemInfo 5.0 5 | 6 | import ".." 7 | 8 | BrowserWindow { 9 | id: root 10 | 11 | ScreenSaver { 12 | screenSaverEnabled: !Qt.application.active 13 | } 14 | 15 | app: UbuntuApplication { 16 | 17 | } 18 | 19 | function fixDensity() { 20 | // BQ Devices 21 | var bqAquarisE45 = 22 | (Screen.width == 540) && 23 | (Screen.height == 960) && 24 | (Screen.pixelDensity.toFixed(2) == 3.94) && 25 | (Screen.logicalPixelDensity.toFixed(2) == 3.94) 26 | if (bqAquarisE45) { 27 | Units.multiplier = 2; 28 | } 29 | 30 | var bqAquarisE5 = 31 | (Screen.width == 720) && 32 | (Screen.height == 1280) && 33 | (Screen.pixelDensity.toFixed(2) == 3.94) && 34 | (Screen.logicalPixelDensity.toFixed(2) == 3.94) 35 | if (bqAquarisE5) { 36 | Units.multiplier = 3.03 37 | } 38 | 39 | // Meizu Devices 40 | var meizuMX4 = 41 | (Screen.width == 1152) && 42 | (Screen.height == 1920) && 43 | (Screen.pixelDensity.toFixed(2) == 3.94) && 44 | (Screen.logicalPixelDensity.toFixed(2) == 3.94) 45 | if (meizuMX4) { 46 | Units.multiplier = 4.11 47 | } 48 | 49 | // Google Nexus Devices 50 | var googleNexus4 = 51 | (Screen.width == 768) && 52 | (Screen.height == 1280) && 53 | (Screen.pixelDensity.toFixed(2) == 3.94) && 54 | (Screen.logicalPixelDensity.toFixed(2) == 3.94) 55 | if (googleNexus4) { 56 | Units.multiplier = 3.23 57 | } 58 | 59 | var googleNexus5 = 60 | (Screen.width == 1080) && 61 | (Screen.height == 1920) && 62 | (Screen.pixelDensity.toFixed(2) == 3.94) && 63 | (Screen.logicalPixelDensity.toFixed(2) == 3.94) 64 | if (googleNexus5) { 65 | Units.multiplier = 4.11 66 | } 67 | 68 | var googleNexus7 = 69 | (Screen.width == 1200) && 70 | (Screen.height == 1920) && 71 | (Screen.pixelDensity.toFixed(2) == 3.94) && 72 | (Screen.logicalPixelDensity.toFixed(2) == 3.94) 73 | if (googleNexus7) { 74 | Units.multiplier = 3.23 75 | } 76 | 77 | } 78 | 79 | Component.onCompleted: { 80 | fixDensity(); 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/qml/ubuntu/UbuntuApplication.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.1 2 | import Qt.labs.settings 1.0 3 | import ".." 4 | 5 | BaseApplication { 6 | id: application 7 | 8 | property string webEngine: "oxide" 9 | property string platform: "converged/ubuntu" 10 | property bool enableShortCuts: false 11 | property bool enableNewWindowAction: false 12 | property bool newTabPage: false 13 | 14 | property string homeUrl: "http://start.ubuntu.com/" 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/sourcecodeviewer/viewer.html: -------------------------------------------------------------------------------- 1 | test 2 | -------------------------------------------------------------------------------- /src/translations/ar_MA.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liri-project/liri-browser/372cf1ea84b0ee7f8f2fa1b56d5b598e5c0569b1/src/translations/ar_MA.qm -------------------------------------------------------------------------------- /src/translations/de_DE.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liri-project/liri-browser/372cf1ea84b0ee7f8f2fa1b56d5b598e5c0569b1/src/translations/de_DE.qm -------------------------------------------------------------------------------- /src/translations/es_CR.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liri-project/liri-browser/372cf1ea84b0ee7f8f2fa1b56d5b598e5c0569b1/src/translations/es_CR.qm -------------------------------------------------------------------------------- /src/translations/es_ES.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liri-project/liri-browser/372cf1ea84b0ee7f8f2fa1b56d5b598e5c0569b1/src/translations/es_ES.qm -------------------------------------------------------------------------------- /src/translations/fr_FR.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liri-project/liri-browser/372cf1ea84b0ee7f8f2fa1b56d5b598e5c0569b1/src/translations/fr_FR.qm -------------------------------------------------------------------------------- /src/translations/ko_KR.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liri-project/liri-browser/372cf1ea84b0ee7f8f2fa1b56d5b598e5c0569b1/src/translations/ko_KR.qm -------------------------------------------------------------------------------- /src/translations/liri-browser.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liri-project/liri-browser/372cf1ea84b0ee7f8f2fa1b56d5b598e5c0569b1/src/translations/liri-browser.qm -------------------------------------------------------------------------------- /src/translations/pt_BR.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liri-project/liri-browser/372cf1ea84b0ee7f8f2fa1b56d5b598e5c0569b1/src/translations/pt_BR.qm -------------------------------------------------------------------------------- /src/translations/pt_PT.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liri-project/liri-browser/372cf1ea84b0ee7f8f2fa1b56d5b598e5c0569b1/src/translations/pt_PT.qm -------------------------------------------------------------------------------- /src/translations/ru_RU.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liri-project/liri-browser/372cf1ea84b0ee7f8f2fa1b56d5b598e5c0569b1/src/translations/ru_RU.qm -------------------------------------------------------------------------------- /src/ubuntu/main.cpp: -------------------------------------------------------------------------------- 1 | //#include 2 | //#include 3 | #ifndef QT_NO_WIDGETS 4 | #include 5 | typedef QApplication Application; 6 | #else 7 | #include 8 | typedef QGuiApplication Application; 9 | #endif 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include "../cursor/cursor.h" 16 | #include "../clipboardadapter.h" 17 | #include "../plugins/pluginsengine.h" 18 | #include "../config.h" 19 | 20 | int main(int argc, char **argv) 21 | { 22 | Application app(argc, argv); 23 | 24 | // Load configuration 25 | Config * config = new Config(); 26 | config->load(); 27 | 28 | // Set domain 29 | app.setOrganizationName("liri-browser.timsueberkrueb"); 30 | app.setOrganizationDomain("liri-browser.timsueberkrueb"); 31 | app.setApplicationName("liri-browser.timsueberkrueb"); 32 | 33 | // Load Translations 34 | QTranslator qtTranslator; 35 | qtTranslator.load("qt_" + QLocale::system().name(), 36 | QLibraryInfo::location(QLibraryInfo::TranslationsPath)); 37 | app.installTranslator(&qtTranslator); 38 | 39 | QTranslator translator; 40 | translator.load(":/translations/" + QLocale::system().name()); 41 | app.installTranslator(&translator); 42 | 43 | QQmlApplicationEngine * appEngine = new QQmlApplicationEngine(); 44 | //appEngine.rootContext()->setContextProperty("utils", &utils); 45 | qmlRegisterType("Clipboard", 1, 0, "Clipboard"); 46 | appEngine->load(QUrl("src/qml/Main.qml")); 47 | //QMetaObject::invokeMethod(appEngine->rootObjects().first(), "load"); 48 | //appEngine->rootContext()->setContextProperty("G_Cursor",new Cursor); 49 | 50 | // Load plugins 51 | //PluginsEngine * pluginsEngine = new PluginsEngine(appEngine, config); 52 | //appEngine->rootContext()->setContextProperty("PluginsEngine", pluginsEngine); 53 | //pluginsEngine->loadPlugins(); 54 | //pluginsEngine->trigger(QString("load")); 55 | 56 | return app.exec(); 57 | } 58 | -------------------------------------------------------------------------------- /translations/.gitignore: -------------------------------------------------------------------------------- 1 | # DS_Store 2 | 3 | .DS_Store 4 | 5 | # Build directory 6 | 7 | build 8 | 9 | # C++ objects and libs 10 | 11 | *.slo 12 | *.lo 13 | *.o 14 | *.a 15 | *.la 16 | *.lai 17 | *.so 18 | *.dll 19 | *.dylib 20 | 21 | # Qt-es 22 | 23 | /.qmake.cache 24 | /.qmake.stash 25 | *.pro.user 26 | *.pro.user.* 27 | *.qbs.user 28 | *.qbs.user.* 29 | *.moc 30 | moc_*.cpp 31 | qrc_*.cpp 32 | ui_*.h 33 | Makefile* 34 | *-build-* 35 | 36 | # QtCreator 37 | 38 | *.autosave 39 | 40 | #QtCtreator Qml 41 | *.qmlproject.user 42 | *.qmlproject.user.* 43 | 44 | # Executables 45 | *.exe 46 | *.out 47 | *.app 48 | liri-browser 49 | 50 | # Visual Studio 51 | /Win32 52 | /debug 53 | /release 54 | liri-browser.sdf 55 | liri-browser.v12.suo 56 | liri-browser.vcxproj 57 | liri-browser.vcxproj.filters 58 | liri-browser.vcxproj.user 59 | liri-browser.opensdf 60 | 61 | # Ubuntu 62 | .ubuntu-sdk-deploy 63 | *.click 64 | lib/ 65 | 66 | # Oxide 67 | TransportSecurity 68 | -------------------------------------------------------------------------------- /translations/ar_MA.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liri-project/liri-browser/372cf1ea84b0ee7f8f2fa1b56d5b598e5c0569b1/translations/ar_MA.qm -------------------------------------------------------------------------------- /translations/de_DE.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liri-project/liri-browser/372cf1ea84b0ee7f8f2fa1b56d5b598e5c0569b1/translations/de_DE.qm -------------------------------------------------------------------------------- /translations/es_CR.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liri-project/liri-browser/372cf1ea84b0ee7f8f2fa1b56d5b598e5c0569b1/translations/es_CR.qm -------------------------------------------------------------------------------- /translations/es_ES.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liri-project/liri-browser/372cf1ea84b0ee7f8f2fa1b56d5b598e5c0569b1/translations/es_ES.qm -------------------------------------------------------------------------------- /translations/fr_FR.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liri-project/liri-browser/372cf1ea84b0ee7f8f2fa1b56d5b598e5c0569b1/translations/fr_FR.qm -------------------------------------------------------------------------------- /translations/ko_KR.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liri-project/liri-browser/372cf1ea84b0ee7f8f2fa1b56d5b598e5c0569b1/translations/ko_KR.qm -------------------------------------------------------------------------------- /translations/pt_BR.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liri-project/liri-browser/372cf1ea84b0ee7f8f2fa1b56d5b598e5c0569b1/translations/pt_BR.qm -------------------------------------------------------------------------------- /translations/pt_PT.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liri-project/liri-browser/372cf1ea84b0ee7f8f2fa1b56d5b598e5c0569b1/translations/pt_PT.qm -------------------------------------------------------------------------------- /translations/ru_RU.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liri-project/liri-browser/372cf1ea84b0ee7f8f2fa1b56d5b598e5c0569b1/translations/ru_RU.qm -------------------------------------------------------------------------------- /ubuntu/Setup.md: -------------------------------------------------------------------------------- 1 | # Build for Ubuntu Touch 2 | 3 | More to come soon ... 4 | -------------------------------------------------------------------------------- /ubuntu/liri-browser.timsueberkrueb.apparmor: -------------------------------------------------------------------------------- 1 | { 2 | "policy_groups": [ 3 | "networking", 4 | "webview", 5 | "connectivity", 6 | "audio", 7 | "keep-display-on" 8 | ], 9 | "policy_version": 1.3 10 | } 11 | -------------------------------------------------------------------------------- /ubuntu/liri-browser.timsueberkrueb.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=Liri Browser 3 | Exec=opt/liri-browser-ubuntu/bin/liri-browser.timsueberkrueb 4 | Icon=icons/liri-browser-ubuntu.png 5 | Terminal=false 6 | Type=Application 7 | X-Ubuntu-Touch=true 8 | 9 | -------------------------------------------------------------------------------- /ubuntu/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "liri-browser.timsueberkrueb", 3 | "description": "A minimalistic, material-design webbrowser", 4 | "architecture": [ 5 | "armhf", 6 | "i386", 7 | "amd64" 8 | ], 9 | "title": "Liri Browser", 10 | "hooks": { 11 | "liri-browser.timsueberkrueb": { 12 | "apparmor": "ubuntu/liri-browser.timsueberkrueb.apparmor", 13 | "desktop": "ubuntu/liri-browser.timsueberkrueb.desktop" 14 | } 15 | }, 16 | "version": "0.4", 17 | "maintainer": "Tim Süberkrüb ", 18 | "framework": "ubuntu-sdk-15.04.1" 19 | } 20 | --------------------------------------------------------------------------------