From: Jakob Haufe Date: Sun, 16 Jun 2019 20:27:40 +0000 (+0000) Subject: New upstream version 3.1 X-Git-Tag: debian/3.1-1~6 X-Git-Url: https://git.sur5r.net/?p=minitube;a=commitdiff_plain;h=434d88418722fd7717038e44bd74271ca1d92771 New upstream version 3.1 --- diff --git a/.clang-format b/.clang-format deleted file mode 100644 index 2409f52..0000000 --- a/.clang-format +++ /dev/null @@ -1,11 +0,0 @@ -BasedOnStyle: LLVM -IndentWidth: 4 -AccessModifierOffset: -4 -ColumnLimit: 100 -AllowShortIfStatementsOnASingleLine: true -AllowShortFunctionsOnASingleLine: Inline -KeepEmptyLinesAtTheStartOfBlocks: false -ContinuationIndentWidth: 8 -AlignAfterOpenBracket: true -BinPackParameters: false -AllowAllParametersOfDeclarationOnNextLine: false diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 48266c8..0000000 --- a/.gitignore +++ /dev/null @@ -1,16 +0,0 @@ -build/ -Makefile* -minitube.pro.user -.settings/ -.DS_Store -.cproject -.project -local/ -*.swp -.tx -android -qtc_packaging -debian - - -*.stash diff --git a/README.md b/README.md index d238fe0..1580dc6 100644 --- a/README.md +++ b/README.md @@ -1,44 +1,46 @@ +

+ +

+ # Minitube Minitube is a YouTube desktop application. It is written in C++ using the Qt framework. Contributing is welcome, especially in the Linux desktop integration area. -## Translating Minitube to your language -Translations are done at https://www.transifex.com/projects/p/minitube/ +## Translating to your language +Translations are done at https://www.transifex.com/flaviotordini/minitube/ Just register and apply for a language team. Please don't request translation merges on GitHub. ## Google API Key Google is now requiring an API key in order to access YouTube Data web services. -Create a "Browser Key" at https://console.developers.google.com +Create a "Browser Key" at https://console.developers.google.com and enable the Youtube Data API. The key must be specified at compile time as shown below. Alternatively Minitube can read an API key from the GOOGLE_API_KEY environment variable. ## Build instructions -To compile Minitube you need at least Qt 5.0. The following Qt modules are needed: core, gui, widgets, network, sql (using the Sqlite plugin), declarative, dbus. +Clone from Github: + + git clone --recursive https://github.com/flaviotordini/minitube.git + +You need Qt >= 5.6 and MPV >= 0.29.0. The following Qt modules are needed: core, gui, widgets, network, sql (using the Sqlite plugin), declarative, dbus, x11extras. To be able to build on a Debian (or derivative) system: - $ sudo apt-get install build-essential qttools5-dev-tools qt5-qmake qtdeclarative5-dev libphonon4qt5-dev libqt5sql5-sqlite qt5-default + sudo apt install build-essential qt5-default qttools5-dev-tools qt5-qmake qtdeclarative5-dev libqt5sql5-sqlite libqt5x11extras5-dev libmpv-dev Compiling: - $ qmake "DEFINES += APP_GOOGLE_API_KEY=YourAPIKeyHere" - $ make - -Beware of the Qt 4 version of qmake! + qmake "DEFINES += APP_GOOGLE_API_KEY=YourAPIKeyHere" + make Running: - $ build/target/minitube - -Installing on Linux: + build/target/minitube - $ sudo make install +Installing on Linux: This is for packagers. End users should not install applications in this way. -## A word about Phonon on Linux -To be able to actually watch videos you need a working Phonon setup. -Please don't report bugs about this, ask for help on your distribution support channels. + sudo make install ## Legal Stuff Copyright (C) Flavio Tordini diff --git a/debian/minitube.install b/debian/minitube.install new file mode 100644 index 0000000..41367b7 --- /dev/null +++ b/debian/minitube.install @@ -0,0 +1,2 @@ +/usr/bin/* +/usr/share/* diff --git a/debian/rules b/debian/rules index ff6ea3f..7e9ef0d 100755 --- a/debian/rules +++ b/debian/rules @@ -11,6 +11,7 @@ override_dh_auto_configure:: override_dh_auto_install:: dh_auto_install --destdir=debian/tmp + chmod a-x debian/tmp/usr/share/minitube/sounds/snapshot.wav override_dh_missing:: dh_missing --fail-missing diff --git a/empty.ts b/empty.ts index ec0b121..ce7cd4a 100644 --- a/empty.ts +++ b/empty.ts @@ -27,6 +27,14 @@ Translate %1 to your native language using %2 + + Powered by %1 + + + + Open-source software + + Icon designed by %1. @@ -110,7 +118,7 @@ You have %n new video(s) - + @@ -159,6 +167,10 @@ Show Updated + + You have no subscriptions. Use the star symbol to subscribe to channels. + + All Videos @@ -179,17 +191,6 @@ There are no updated subscriptions at this time. - - You have no subscriptions. Use the star symbol to subscribe to channels. - - - - - ClearButton - - Clear - - DataUtils @@ -200,25 +201,44 @@ %n hour(s) ago - + %n day(s) ago - + - %n weeks(s) ago + %n month(s) ago - + + + K + K as in Kilo, i.e. thousands + + + + M + M stands for Millions + + + + B + B stands for Billions + + + + %1 views + + - %n month(s) ago + %n week(s) ago - + @@ -274,7 +294,7 @@ %n Download(s) - + @@ -418,6 +438,14 @@ MainWindow + + &Window + + + + &Minimize + + &Stop @@ -658,6 +686,14 @@ Hide videos that may contain inappropriate content + + Toggle &Menu Bar + + + + Menu + + &Love %1? Rate it! @@ -746,6 +782,10 @@ Remaining time: %1 + + Volume at %1% + + Volume is muted @@ -778,26 +818,10 @@ Update - - Toggle &Menu Bar - - You can still access the menu bar by pressing the ALT key - - &Window - - - - &Minimize - - - - Menu - - MediaView @@ -844,6 +868,10 @@ Subscribe to %1 + + Switched to %1 + + Unsubscribed from %1 @@ -879,10 +907,6 @@ Install Update - - Pick a video - - PasteLineEdit @@ -892,11 +916,14 @@ - PlaylistItemDelegate + PickMessage - %1 views + Pick a video + + + PlaylistItemDelegate %1 of %2 (%3) — %4 @@ -936,10 +963,6 @@ PlaylistModel - - Searching... - - Show %1 More @@ -1061,7 +1084,7 @@ - Enter + to start watching videos. @@ -1069,11 +1092,7 @@ - to start watching videos. - - - - Watch + Enter @@ -1095,6 +1114,10 @@ &Back + + &Forward + + Forward to %1 diff --git a/icons/dark/16/audio-volume-high.png b/icons/dark/16/audio-volume-high.png new file mode 100644 index 0000000..26cd8cd Binary files /dev/null and b/icons/dark/16/audio-volume-high.png differ diff --git a/icons/dark/16/audio-volume-high@2x.png b/icons/dark/16/audio-volume-high@2x.png new file mode 100644 index 0000000..1336f60 Binary files /dev/null and b/icons/dark/16/audio-volume-high@2x.png differ diff --git a/icons/dark/16/audio-volume-muted.png b/icons/dark/16/audio-volume-muted.png new file mode 100644 index 0000000..e15b3e2 Binary files /dev/null and b/icons/dark/16/audio-volume-muted.png differ diff --git a/icons/dark/16/audio-volume-muted@2x.png b/icons/dark/16/audio-volume-muted@2x.png new file mode 100644 index 0000000..e5d2436 Binary files /dev/null and b/icons/dark/16/audio-volume-muted@2x.png differ diff --git a/icons/dark/16/bookmark-new.png b/icons/dark/16/bookmark-new.png new file mode 100644 index 0000000..26faefd Binary files /dev/null and b/icons/dark/16/bookmark-new.png differ diff --git a/icons/dark/16/bookmark-new@2x.png b/icons/dark/16/bookmark-new@2x.png new file mode 100644 index 0000000..7bc2637 Binary files /dev/null and b/icons/dark/16/bookmark-new@2x.png differ diff --git a/icons/dark/16/bookmark-new_active.png b/icons/dark/16/bookmark-new_active.png new file mode 100644 index 0000000..fa7214b Binary files /dev/null and b/icons/dark/16/bookmark-new_active.png differ diff --git a/icons/dark/16/bookmark-new_active@2x.png b/icons/dark/16/bookmark-new_active@2x.png new file mode 100644 index 0000000..5283ece Binary files /dev/null and b/icons/dark/16/bookmark-new_active@2x.png differ diff --git a/icons/dark/16/bookmark-remove.png b/icons/dark/16/bookmark-remove.png new file mode 100644 index 0000000..f98a670 Binary files /dev/null and b/icons/dark/16/bookmark-remove.png differ diff --git a/icons/dark/16/bookmark-remove@2x.png b/icons/dark/16/bookmark-remove@2x.png new file mode 100644 index 0000000..5650d28 Binary files /dev/null and b/icons/dark/16/bookmark-remove@2x.png differ diff --git a/icons/dark/16/edit-find.png b/icons/dark/16/edit-find.png new file mode 100644 index 0000000..84a5f7a Binary files /dev/null and b/icons/dark/16/edit-find.png differ diff --git a/icons/dark/16/edit-find@2x.png b/icons/dark/16/edit-find@2x.png new file mode 100644 index 0000000..0300a6a Binary files /dev/null and b/icons/dark/16/edit-find@2x.png differ diff --git a/icons/dark/16/email.png b/icons/dark/16/email.png new file mode 100644 index 0000000..90f9cb2 Binary files /dev/null and b/icons/dark/16/email.png differ diff --git a/icons/dark/16/email@2x.png b/icons/dark/16/email@2x.png new file mode 100644 index 0000000..d7520a4 Binary files /dev/null and b/icons/dark/16/email@2x.png differ diff --git a/icons/dark/16/facebook.png b/icons/dark/16/facebook.png new file mode 100644 index 0000000..6e70bdd Binary files /dev/null and b/icons/dark/16/facebook.png differ diff --git a/icons/dark/16/facebook@2x.png b/icons/dark/16/facebook@2x.png new file mode 100644 index 0000000..8afb6bf Binary files /dev/null and b/icons/dark/16/facebook@2x.png differ diff --git a/icons/dark/16/go-next.png b/icons/dark/16/go-next.png new file mode 100644 index 0000000..86786ee Binary files /dev/null and b/icons/dark/16/go-next.png differ diff --git a/icons/dark/16/go-next@2x.png b/icons/dark/16/go-next@2x.png new file mode 100644 index 0000000..cf3f592 Binary files /dev/null and b/icons/dark/16/go-next@2x.png differ diff --git a/icons/dark/16/go-previous.png b/icons/dark/16/go-previous.png new file mode 100644 index 0000000..f582862 Binary files /dev/null and b/icons/dark/16/go-previous.png differ diff --git a/icons/dark/16/go-previous@2x.png b/icons/dark/16/go-previous@2x.png new file mode 100644 index 0000000..85523c8 Binary files /dev/null and b/icons/dark/16/go-previous@2x.png differ diff --git a/icons/dark/16/link.png b/icons/dark/16/link.png new file mode 100644 index 0000000..a677e34 Binary files /dev/null and b/icons/dark/16/link.png differ diff --git a/icons/dark/16/link@2x.png b/icons/dark/16/link@2x.png new file mode 100644 index 0000000..e32d826 Binary files /dev/null and b/icons/dark/16/link@2x.png differ diff --git a/icons/dark/16/mark-watched.png b/icons/dark/16/mark-watched.png new file mode 100644 index 0000000..216f855 Binary files /dev/null and b/icons/dark/16/mark-watched.png differ diff --git a/icons/dark/16/mark-watched@2x.png b/icons/dark/16/mark-watched@2x.png new file mode 100644 index 0000000..9b38fe2 Binary files /dev/null and b/icons/dark/16/mark-watched@2x.png differ diff --git a/icons/dark/16/media-playback-start.png b/icons/dark/16/media-playback-start.png new file mode 100644 index 0000000..0d1dd2a Binary files /dev/null and b/icons/dark/16/media-playback-start.png differ diff --git a/icons/dark/16/media-playback-start@2x.png b/icons/dark/16/media-playback-start@2x.png new file mode 100644 index 0000000..5a8ee8e Binary files /dev/null and b/icons/dark/16/media-playback-start@2x.png differ diff --git a/icons/dark/16/media-playback-start_checked.png b/icons/dark/16/media-playback-start_checked.png new file mode 100644 index 0000000..0d1dd2a Binary files /dev/null and b/icons/dark/16/media-playback-start_checked.png differ diff --git a/icons/dark/16/media-playback-start_checked@2x.png b/icons/dark/16/media-playback-start_checked@2x.png new file mode 100644 index 0000000..5a8ee8e Binary files /dev/null and b/icons/dark/16/media-playback-start_checked@2x.png differ diff --git a/icons/dark/16/safesearch.png b/icons/dark/16/safesearch.png new file mode 100644 index 0000000..a359701 Binary files /dev/null and b/icons/dark/16/safesearch.png differ diff --git a/icons/dark/16/safesearch@2x.png b/icons/dark/16/safesearch@2x.png new file mode 100644 index 0000000..0e2257f Binary files /dev/null and b/icons/dark/16/safesearch@2x.png differ diff --git a/icons/dark/16/search-duration.png b/icons/dark/16/search-duration.png new file mode 100644 index 0000000..9ec10d2 Binary files /dev/null and b/icons/dark/16/search-duration.png differ diff --git a/icons/dark/16/search-duration@2x.png b/icons/dark/16/search-duration@2x.png new file mode 100644 index 0000000..21cb874 Binary files /dev/null and b/icons/dark/16/search-duration@2x.png differ diff --git a/icons/dark/16/search-quality.png b/icons/dark/16/search-quality.png new file mode 100644 index 0000000..a602e45 Binary files /dev/null and b/icons/dark/16/search-quality.png differ diff --git a/icons/dark/16/search-quality@2x.png b/icons/dark/16/search-quality@2x.png new file mode 100644 index 0000000..db43613 Binary files /dev/null and b/icons/dark/16/search-quality@2x.png differ diff --git a/icons/dark/16/search-sortBy.png b/icons/dark/16/search-sortBy.png new file mode 100644 index 0000000..55f6c58 Binary files /dev/null and b/icons/dark/16/search-sortBy.png differ diff --git a/icons/dark/16/search-sortBy@2x.png b/icons/dark/16/search-sortBy@2x.png new file mode 100644 index 0000000..7e6acdd Binary files /dev/null and b/icons/dark/16/search-sortBy@2x.png differ diff --git a/icons/dark/16/search-time.png b/icons/dark/16/search-time.png new file mode 100644 index 0000000..b1cd0c3 Binary files /dev/null and b/icons/dark/16/search-time.png differ diff --git a/icons/dark/16/search-time@2x.png b/icons/dark/16/search-time@2x.png new file mode 100644 index 0000000..6b3f928 Binary files /dev/null and b/icons/dark/16/search-time@2x.png differ diff --git a/icons/dark/16/show-updated.png b/icons/dark/16/show-updated.png new file mode 100644 index 0000000..f9b36c3 Binary files /dev/null and b/icons/dark/16/show-updated.png differ diff --git a/icons/dark/16/show-updated@2x.png b/icons/dark/16/show-updated@2x.png new file mode 100644 index 0000000..c793b96 Binary files /dev/null and b/icons/dark/16/show-updated@2x.png differ diff --git a/icons/dark/16/sort.png b/icons/dark/16/sort.png new file mode 100644 index 0000000..3b47e71 Binary files /dev/null and b/icons/dark/16/sort.png differ diff --git a/icons/dark/16/sort@2x.png b/icons/dark/16/sort@2x.png new file mode 100644 index 0000000..0152e7b Binary files /dev/null and b/icons/dark/16/sort@2x.png differ diff --git a/icons/dark/16/twitter.png b/icons/dark/16/twitter.png new file mode 100644 index 0000000..f789e61 Binary files /dev/null and b/icons/dark/16/twitter.png differ diff --git a/icons/dark/16/twitter@2x.png b/icons/dark/16/twitter@2x.png new file mode 100644 index 0000000..8267aae Binary files /dev/null and b/icons/dark/16/twitter@2x.png differ diff --git a/icons/dark/16/unwatched.png b/icons/dark/16/unwatched.png new file mode 100644 index 0000000..6195ece Binary files /dev/null and b/icons/dark/16/unwatched.png differ diff --git a/icons/dark/16/unwatched@2x.png b/icons/dark/16/unwatched@2x.png new file mode 100644 index 0000000..7768f24 Binary files /dev/null and b/icons/dark/16/unwatched@2x.png differ diff --git a/icons/dark/16/video-display.png b/icons/dark/16/video-display.png new file mode 100644 index 0000000..ea291f2 Binary files /dev/null and b/icons/dark/16/video-display.png differ diff --git a/icons/dark/16/video-display@2x.png b/icons/dark/16/video-display@2x.png new file mode 100644 index 0000000..2b6dd36 Binary files /dev/null and b/icons/dark/16/video-display@2x.png differ diff --git a/icons/dark/16/worldwide.png b/icons/dark/16/worldwide.png new file mode 100644 index 0000000..cdd59e6 Binary files /dev/null and b/icons/dark/16/worldwide.png differ diff --git a/icons/dark/16/worldwide@2x.png b/icons/dark/16/worldwide@2x.png new file mode 100644 index 0000000..e8b632f Binary files /dev/null and b/icons/dark/16/worldwide@2x.png differ diff --git a/icons/dark/24/edit-find.png b/icons/dark/24/edit-find.png new file mode 100644 index 0000000..ac08a42 Binary files /dev/null and b/icons/dark/24/edit-find.png differ diff --git a/icons/dark/24/edit-find@2x.png b/icons/dark/24/edit-find@2x.png new file mode 100644 index 0000000..8bb8bfd Binary files /dev/null and b/icons/dark/24/edit-find@2x.png differ diff --git a/icons/dark/24/refine-search.png b/icons/dark/24/refine-search.png new file mode 100644 index 0000000..ac08a42 Binary files /dev/null and b/icons/dark/24/refine-search.png differ diff --git a/icons/dark/24/refine-search@2x.png b/icons/dark/24/refine-search@2x.png new file mode 100644 index 0000000..8bb8bfd Binary files /dev/null and b/icons/dark/24/refine-search@2x.png differ diff --git a/icons/dark/32/content-loading.png b/icons/dark/32/content-loading.png new file mode 100644 index 0000000..56d9991 Binary files /dev/null and b/icons/dark/32/content-loading.png differ diff --git a/icons/dark/32/content-loading@2x.png b/icons/dark/32/content-loading@2x.png new file mode 100644 index 0000000..cc80147 Binary files /dev/null and b/icons/dark/32/content-loading@2x.png differ diff --git a/icons/dark/32/document-save.png b/icons/dark/32/document-save.png new file mode 100644 index 0000000..0af10d8 Binary files /dev/null and b/icons/dark/32/document-save.png differ diff --git a/icons/dark/32/document-save@2x.png b/icons/dark/32/document-save@2x.png new file mode 100644 index 0000000..35054ee Binary files /dev/null and b/icons/dark/32/document-save@2x.png differ diff --git a/icons/dark/32/media-playback-pause.png b/icons/dark/32/media-playback-pause.png new file mode 100644 index 0000000..133fab7 Binary files /dev/null and b/icons/dark/32/media-playback-pause.png differ diff --git a/icons/dark/32/media-playback-pause@2x.png b/icons/dark/32/media-playback-pause@2x.png new file mode 100644 index 0000000..91af18a Binary files /dev/null and b/icons/dark/32/media-playback-pause@2x.png differ diff --git a/icons/dark/32/media-playback-start.png b/icons/dark/32/media-playback-start.png new file mode 100644 index 0000000..70c3c6e Binary files /dev/null and b/icons/dark/32/media-playback-start.png differ diff --git a/icons/dark/32/media-playback-start@2x.png b/icons/dark/32/media-playback-start@2x.png new file mode 100644 index 0000000..32f1318 Binary files /dev/null and b/icons/dark/32/media-playback-start@2x.png differ diff --git a/icons/dark/32/media-playback-start_checked.png b/icons/dark/32/media-playback-start_checked.png new file mode 100644 index 0000000..4b7e59e Binary files /dev/null and b/icons/dark/32/media-playback-start_checked.png differ diff --git a/icons/dark/32/media-playback-start_checked@2x.png b/icons/dark/32/media-playback-start_checked@2x.png new file mode 100644 index 0000000..55f6afb Binary files /dev/null and b/icons/dark/32/media-playback-start_checked@2x.png differ diff --git a/icons/dark/32/media-playback-stop.png b/icons/dark/32/media-playback-stop.png new file mode 100644 index 0000000..b86d233 Binary files /dev/null and b/icons/dark/32/media-playback-stop.png differ diff --git a/icons/dark/32/media-playback-stop@2x.png b/icons/dark/32/media-playback-stop@2x.png new file mode 100644 index 0000000..28de286 Binary files /dev/null and b/icons/dark/32/media-playback-stop@2x.png differ diff --git a/icons/dark/32/media-skip-forward.png b/icons/dark/32/media-skip-forward.png new file mode 100644 index 0000000..bfb0dc6 Binary files /dev/null and b/icons/dark/32/media-skip-forward.png differ diff --git a/icons/dark/32/media-skip-forward@2x.png b/icons/dark/32/media-skip-forward@2x.png new file mode 100644 index 0000000..982da70 Binary files /dev/null and b/icons/dark/32/media-skip-forward@2x.png differ diff --git a/icons/dark/32/open-menu.png b/icons/dark/32/open-menu.png new file mode 100644 index 0000000..917da50 Binary files /dev/null and b/icons/dark/32/open-menu.png differ diff --git a/icons/dark/32/open-menu@2x.png b/icons/dark/32/open-menu@2x.png new file mode 100644 index 0000000..7a95cba Binary files /dev/null and b/icons/dark/32/open-menu@2x.png differ diff --git a/icons/dark/32/view-fullscreen.png b/icons/dark/32/view-fullscreen.png new file mode 100644 index 0000000..87864c9 Binary files /dev/null and b/icons/dark/32/view-fullscreen.png differ diff --git a/icons/dark/32/view-fullscreen@2x.png b/icons/dark/32/view-fullscreen@2x.png new file mode 100644 index 0000000..48f63ac Binary files /dev/null and b/icons/dark/32/view-fullscreen@2x.png differ diff --git a/icons/dark/32/view-list.png b/icons/dark/32/view-list.png new file mode 100644 index 0000000..9508f98 Binary files /dev/null and b/icons/dark/32/view-list.png differ diff --git a/icons/dark/32/view-list@2x.png b/icons/dark/32/view-list@2x.png new file mode 100644 index 0000000..93f8421 Binary files /dev/null and b/icons/dark/32/view-list@2x.png differ diff --git a/icons/dark/32/view-restore.png b/icons/dark/32/view-restore.png new file mode 100644 index 0000000..dcde5ee Binary files /dev/null and b/icons/dark/32/view-restore.png differ diff --git a/icons/dark/32/view-restore@2x.png b/icons/dark/32/view-restore@2x.png new file mode 100644 index 0000000..3ad5590 Binary files /dev/null and b/icons/dark/32/view-restore@2x.png differ diff --git a/icons/dark/88/channels.png b/icons/dark/88/channels.png new file mode 100644 index 0000000..f472d3a Binary files /dev/null and b/icons/dark/88/channels.png differ diff --git a/icons/dark/88/channels@2x.png b/icons/dark/88/channels@2x.png new file mode 100644 index 0000000..8172783 Binary files /dev/null and b/icons/dark/88/channels@2x.png differ diff --git a/icons/dark/88/unwatched.png b/icons/dark/88/unwatched.png new file mode 100644 index 0000000..f24b750 Binary files /dev/null and b/icons/dark/88/unwatched.png differ diff --git a/icons/dark/88/unwatched@2x.png b/icons/dark/88/unwatched@2x.png new file mode 100644 index 0000000..917bd2b Binary files /dev/null and b/icons/dark/88/unwatched@2x.png differ diff --git a/icons/light/16/audio-volume-high.png b/icons/light/16/audio-volume-high.png new file mode 100644 index 0000000..96f746e Binary files /dev/null and b/icons/light/16/audio-volume-high.png differ diff --git a/icons/light/16/audio-volume-high@2x.png b/icons/light/16/audio-volume-high@2x.png new file mode 100644 index 0000000..3893e4d Binary files /dev/null and b/icons/light/16/audio-volume-high@2x.png differ diff --git a/icons/light/16/audio-volume-muted.png b/icons/light/16/audio-volume-muted.png new file mode 100644 index 0000000..37be31d Binary files /dev/null and b/icons/light/16/audio-volume-muted.png differ diff --git a/icons/light/16/audio-volume-muted@2x.png b/icons/light/16/audio-volume-muted@2x.png new file mode 100644 index 0000000..ebe59d2 Binary files /dev/null and b/icons/light/16/audio-volume-muted@2x.png differ diff --git a/icons/light/16/bookmark-new.png b/icons/light/16/bookmark-new.png new file mode 100644 index 0000000..ed1d093 Binary files /dev/null and b/icons/light/16/bookmark-new.png differ diff --git a/icons/light/16/bookmark-new@2x.png b/icons/light/16/bookmark-new@2x.png new file mode 100644 index 0000000..006432b Binary files /dev/null and b/icons/light/16/bookmark-new@2x.png differ diff --git a/icons/light/16/bookmark-new_active.png b/icons/light/16/bookmark-new_active.png new file mode 100644 index 0000000..cdf415d Binary files /dev/null and b/icons/light/16/bookmark-new_active.png differ diff --git a/icons/light/16/bookmark-new_active@2x.png b/icons/light/16/bookmark-new_active@2x.png new file mode 100644 index 0000000..9d924a1 Binary files /dev/null and b/icons/light/16/bookmark-new_active@2x.png differ diff --git a/icons/light/16/bookmark-remove.png b/icons/light/16/bookmark-remove.png new file mode 100644 index 0000000..906a7c1 Binary files /dev/null and b/icons/light/16/bookmark-remove.png differ diff --git a/icons/light/16/bookmark-remove@2x.png b/icons/light/16/bookmark-remove@2x.png new file mode 100644 index 0000000..afbf07f Binary files /dev/null and b/icons/light/16/bookmark-remove@2x.png differ diff --git a/icons/light/16/edit-find.png b/icons/light/16/edit-find.png new file mode 100644 index 0000000..128a933 Binary files /dev/null and b/icons/light/16/edit-find.png differ diff --git a/icons/light/16/edit-find@2x.png b/icons/light/16/edit-find@2x.png new file mode 100644 index 0000000..96e6a39 Binary files /dev/null and b/icons/light/16/edit-find@2x.png differ diff --git a/icons/light/16/email.png b/icons/light/16/email.png new file mode 100644 index 0000000..005123f Binary files /dev/null and b/icons/light/16/email.png differ diff --git a/icons/light/16/email@2x.png b/icons/light/16/email@2x.png new file mode 100644 index 0000000..5f7d495 Binary files /dev/null and b/icons/light/16/email@2x.png differ diff --git a/icons/light/16/facebook.png b/icons/light/16/facebook.png new file mode 100644 index 0000000..7df7fc6 Binary files /dev/null and b/icons/light/16/facebook.png differ diff --git a/icons/light/16/facebook@2x.png b/icons/light/16/facebook@2x.png new file mode 100644 index 0000000..3d51f50 Binary files /dev/null and b/icons/light/16/facebook@2x.png differ diff --git a/icons/light/16/go-next.png b/icons/light/16/go-next.png new file mode 100644 index 0000000..eb900d8 Binary files /dev/null and b/icons/light/16/go-next.png differ diff --git a/icons/light/16/go-next@2x.png b/icons/light/16/go-next@2x.png new file mode 100644 index 0000000..5d5f123 Binary files /dev/null and b/icons/light/16/go-next@2x.png differ diff --git a/icons/light/16/go-previous.png b/icons/light/16/go-previous.png new file mode 100644 index 0000000..7922ae8 Binary files /dev/null and b/icons/light/16/go-previous.png differ diff --git a/icons/light/16/go-previous@2x.png b/icons/light/16/go-previous@2x.png new file mode 100644 index 0000000..78e528b Binary files /dev/null and b/icons/light/16/go-previous@2x.png differ diff --git a/icons/light/16/link.png b/icons/light/16/link.png new file mode 100644 index 0000000..239ca25 Binary files /dev/null and b/icons/light/16/link.png differ diff --git a/icons/light/16/link@2x.png b/icons/light/16/link@2x.png new file mode 100644 index 0000000..148c5c3 Binary files /dev/null and b/icons/light/16/link@2x.png differ diff --git a/icons/light/16/mark-watched.png b/icons/light/16/mark-watched.png new file mode 100644 index 0000000..4cb43df Binary files /dev/null and b/icons/light/16/mark-watched.png differ diff --git a/icons/light/16/mark-watched@2x.png b/icons/light/16/mark-watched@2x.png new file mode 100644 index 0000000..5d232ac Binary files /dev/null and b/icons/light/16/mark-watched@2x.png differ diff --git a/icons/light/16/media-playback-start.png b/icons/light/16/media-playback-start.png new file mode 100644 index 0000000..0671d08 Binary files /dev/null and b/icons/light/16/media-playback-start.png differ diff --git a/icons/light/16/media-playback-start@2x.png b/icons/light/16/media-playback-start@2x.png new file mode 100644 index 0000000..3877ef1 Binary files /dev/null and b/icons/light/16/media-playback-start@2x.png differ diff --git a/icons/light/16/media-playback-start_checked.png b/icons/light/16/media-playback-start_checked.png new file mode 100644 index 0000000..0671d08 Binary files /dev/null and b/icons/light/16/media-playback-start_checked.png differ diff --git a/icons/light/16/media-playback-start_checked@2x.png b/icons/light/16/media-playback-start_checked@2x.png new file mode 100644 index 0000000..3877ef1 Binary files /dev/null and b/icons/light/16/media-playback-start_checked@2x.png differ diff --git a/icons/light/16/safesearch.png b/icons/light/16/safesearch.png new file mode 100644 index 0000000..8af718d Binary files /dev/null and b/icons/light/16/safesearch.png differ diff --git a/icons/light/16/safesearch@2x.png b/icons/light/16/safesearch@2x.png new file mode 100644 index 0000000..b77f576 Binary files /dev/null and b/icons/light/16/safesearch@2x.png differ diff --git a/icons/light/16/search-duration.png b/icons/light/16/search-duration.png new file mode 100644 index 0000000..16ae94c Binary files /dev/null and b/icons/light/16/search-duration.png differ diff --git a/icons/light/16/search-duration@2x.png b/icons/light/16/search-duration@2x.png new file mode 100644 index 0000000..cc38ef6 Binary files /dev/null and b/icons/light/16/search-duration@2x.png differ diff --git a/icons/light/16/search-quality.png b/icons/light/16/search-quality.png new file mode 100644 index 0000000..50cc7b4 Binary files /dev/null and b/icons/light/16/search-quality.png differ diff --git a/icons/light/16/search-quality@2x.png b/icons/light/16/search-quality@2x.png new file mode 100644 index 0000000..d4127f3 Binary files /dev/null and b/icons/light/16/search-quality@2x.png differ diff --git a/icons/light/16/search-sortBy.png b/icons/light/16/search-sortBy.png new file mode 100644 index 0000000..8fb8356 Binary files /dev/null and b/icons/light/16/search-sortBy.png differ diff --git a/icons/light/16/search-sortBy@2x.png b/icons/light/16/search-sortBy@2x.png new file mode 100644 index 0000000..7e42d90 Binary files /dev/null and b/icons/light/16/search-sortBy@2x.png differ diff --git a/icons/light/16/search-time.png b/icons/light/16/search-time.png new file mode 100644 index 0000000..a7aaa62 Binary files /dev/null and b/icons/light/16/search-time.png differ diff --git a/icons/light/16/search-time@2x.png b/icons/light/16/search-time@2x.png new file mode 100644 index 0000000..3ba32ee Binary files /dev/null and b/icons/light/16/search-time@2x.png differ diff --git a/icons/light/16/show-updated.png b/icons/light/16/show-updated.png new file mode 100644 index 0000000..8b8fb39 Binary files /dev/null and b/icons/light/16/show-updated.png differ diff --git a/icons/light/16/show-updated@2x.png b/icons/light/16/show-updated@2x.png new file mode 100644 index 0000000..c33adae Binary files /dev/null and b/icons/light/16/show-updated@2x.png differ diff --git a/icons/light/16/sort.png b/icons/light/16/sort.png new file mode 100644 index 0000000..1c22954 Binary files /dev/null and b/icons/light/16/sort.png differ diff --git a/icons/light/16/sort@2x.png b/icons/light/16/sort@2x.png new file mode 100644 index 0000000..8f84953 Binary files /dev/null and b/icons/light/16/sort@2x.png differ diff --git a/icons/light/16/twitter.png b/icons/light/16/twitter.png new file mode 100644 index 0000000..2d597d9 Binary files /dev/null and b/icons/light/16/twitter.png differ diff --git a/icons/light/16/twitter@2x.png b/icons/light/16/twitter@2x.png new file mode 100644 index 0000000..6a74c2a Binary files /dev/null and b/icons/light/16/twitter@2x.png differ diff --git a/icons/light/16/unwatched.png b/icons/light/16/unwatched.png new file mode 100644 index 0000000..e3fe022 Binary files /dev/null and b/icons/light/16/unwatched.png differ diff --git a/icons/light/16/unwatched@2x.png b/icons/light/16/unwatched@2x.png new file mode 100644 index 0000000..daaea3f Binary files /dev/null and b/icons/light/16/unwatched@2x.png differ diff --git a/icons/light/16/video-display.png b/icons/light/16/video-display.png new file mode 100644 index 0000000..f5021bd Binary files /dev/null and b/icons/light/16/video-display.png differ diff --git a/icons/light/16/video-display@2x.png b/icons/light/16/video-display@2x.png new file mode 100644 index 0000000..6daa132 Binary files /dev/null and b/icons/light/16/video-display@2x.png differ diff --git a/icons/light/16/worldwide.png b/icons/light/16/worldwide.png new file mode 100644 index 0000000..97218d0 Binary files /dev/null and b/icons/light/16/worldwide.png differ diff --git a/icons/light/16/worldwide@2x.png b/icons/light/16/worldwide@2x.png new file mode 100644 index 0000000..41d22d8 Binary files /dev/null and b/icons/light/16/worldwide@2x.png differ diff --git a/icons/light/24/edit-find.png b/icons/light/24/edit-find.png new file mode 100644 index 0000000..be603af Binary files /dev/null and b/icons/light/24/edit-find.png differ diff --git a/icons/light/24/edit-find@2x.png b/icons/light/24/edit-find@2x.png new file mode 100644 index 0000000..bdf4071 Binary files /dev/null and b/icons/light/24/edit-find@2x.png differ diff --git a/icons/light/24/refine-search.png b/icons/light/24/refine-search.png new file mode 100644 index 0000000..be603af Binary files /dev/null and b/icons/light/24/refine-search.png differ diff --git a/icons/light/24/refine-search@2x.png b/icons/light/24/refine-search@2x.png new file mode 100644 index 0000000..bdf4071 Binary files /dev/null and b/icons/light/24/refine-search@2x.png differ diff --git a/icons/light/32/content-loading.png b/icons/light/32/content-loading.png new file mode 100644 index 0000000..d7a96d9 Binary files /dev/null and b/icons/light/32/content-loading.png differ diff --git a/icons/light/32/content-loading@2x.png b/icons/light/32/content-loading@2x.png new file mode 100644 index 0000000..4b41759 Binary files /dev/null and b/icons/light/32/content-loading@2x.png differ diff --git a/icons/light/32/document-save.png b/icons/light/32/document-save.png new file mode 100644 index 0000000..222b2de Binary files /dev/null and b/icons/light/32/document-save.png differ diff --git a/icons/light/32/document-save@2x.png b/icons/light/32/document-save@2x.png new file mode 100644 index 0000000..88f62c3 Binary files /dev/null and b/icons/light/32/document-save@2x.png differ diff --git a/icons/light/32/media-playback-pause.png b/icons/light/32/media-playback-pause.png new file mode 100644 index 0000000..5aad9d5 Binary files /dev/null and b/icons/light/32/media-playback-pause.png differ diff --git a/icons/light/32/media-playback-pause@2x.png b/icons/light/32/media-playback-pause@2x.png new file mode 100644 index 0000000..b868bc0 Binary files /dev/null and b/icons/light/32/media-playback-pause@2x.png differ diff --git a/icons/light/32/media-playback-start.png b/icons/light/32/media-playback-start.png new file mode 100644 index 0000000..122b804 Binary files /dev/null and b/icons/light/32/media-playback-start.png differ diff --git a/icons/light/32/media-playback-start@2x.png b/icons/light/32/media-playback-start@2x.png new file mode 100644 index 0000000..88b679a Binary files /dev/null and b/icons/light/32/media-playback-start@2x.png differ diff --git a/icons/light/32/media-playback-start_checked.png b/icons/light/32/media-playback-start_checked.png new file mode 100644 index 0000000..d799121 Binary files /dev/null and b/icons/light/32/media-playback-start_checked.png differ diff --git a/icons/light/32/media-playback-start_checked@2x.png b/icons/light/32/media-playback-start_checked@2x.png new file mode 100644 index 0000000..6fb34cd Binary files /dev/null and b/icons/light/32/media-playback-start_checked@2x.png differ diff --git a/icons/light/32/media-playback-stop.png b/icons/light/32/media-playback-stop.png new file mode 100644 index 0000000..421075a Binary files /dev/null and b/icons/light/32/media-playback-stop.png differ diff --git a/icons/light/32/media-playback-stop@2x.png b/icons/light/32/media-playback-stop@2x.png new file mode 100644 index 0000000..d944741 Binary files /dev/null and b/icons/light/32/media-playback-stop@2x.png differ diff --git a/icons/light/32/media-skip-forward.png b/icons/light/32/media-skip-forward.png new file mode 100644 index 0000000..6283be9 Binary files /dev/null and b/icons/light/32/media-skip-forward.png differ diff --git a/icons/light/32/media-skip-forward@2x.png b/icons/light/32/media-skip-forward@2x.png new file mode 100644 index 0000000..e79b0bf Binary files /dev/null and b/icons/light/32/media-skip-forward@2x.png differ diff --git a/icons/light/32/open-menu.png b/icons/light/32/open-menu.png new file mode 100644 index 0000000..981d103 Binary files /dev/null and b/icons/light/32/open-menu.png differ diff --git a/icons/light/32/open-menu@2x.png b/icons/light/32/open-menu@2x.png new file mode 100644 index 0000000..6aa8b0d Binary files /dev/null and b/icons/light/32/open-menu@2x.png differ diff --git a/icons/light/32/view-fullscreen.png b/icons/light/32/view-fullscreen.png new file mode 100644 index 0000000..2725ed2 Binary files /dev/null and b/icons/light/32/view-fullscreen.png differ diff --git a/icons/light/32/view-fullscreen@2x.png b/icons/light/32/view-fullscreen@2x.png new file mode 100644 index 0000000..ce103b6 Binary files /dev/null and b/icons/light/32/view-fullscreen@2x.png differ diff --git a/icons/light/32/view-list.png b/icons/light/32/view-list.png new file mode 100644 index 0000000..0587ecf Binary files /dev/null and b/icons/light/32/view-list.png differ diff --git a/icons/light/32/view-list@2x.png b/icons/light/32/view-list@2x.png new file mode 100644 index 0000000..8cdc9f2 Binary files /dev/null and b/icons/light/32/view-list@2x.png differ diff --git a/icons/light/32/view-restore.png b/icons/light/32/view-restore.png new file mode 100644 index 0000000..2a9d4aa Binary files /dev/null and b/icons/light/32/view-restore.png differ diff --git a/icons/light/32/view-restore@2x.png b/icons/light/32/view-restore@2x.png new file mode 100644 index 0000000..c93a128 Binary files /dev/null and b/icons/light/32/view-restore@2x.png differ diff --git a/icons/light/88/channels.png b/icons/light/88/channels.png new file mode 100644 index 0000000..27fbc14 Binary files /dev/null and b/icons/light/88/channels.png differ diff --git a/icons/light/88/channels@2x.png b/icons/light/88/channels@2x.png new file mode 100644 index 0000000..c83867d Binary files /dev/null and b/icons/light/88/channels@2x.png differ diff --git a/icons/light/88/unwatched.png b/icons/light/88/unwatched.png new file mode 100644 index 0000000..abda7b2 Binary files /dev/null and b/icons/light/88/unwatched.png differ diff --git a/icons/light/88/unwatched@2x.png b/icons/light/88/unwatched@2x.png new file mode 100644 index 0000000..b04db9b Binary files /dev/null and b/icons/light/88/unwatched@2x.png differ diff --git a/images/audio-volume-high.png b/images/audio-volume-high.png deleted file mode 100644 index dc0141a..0000000 Binary files a/images/audio-volume-high.png and /dev/null differ diff --git a/images/audio-volume-high@2x.png b/images/audio-volume-high@2x.png deleted file mode 100644 index a4bc0de..0000000 Binary files a/images/audio-volume-high@2x.png and /dev/null differ diff --git a/images/audio-volume-muted.png b/images/audio-volume-muted.png deleted file mode 100644 index e2475ac..0000000 Binary files a/images/audio-volume-muted.png and /dev/null differ diff --git a/images/audio-volume-muted@2x.png b/images/audio-volume-muted@2x.png deleted file mode 100644 index 0f8fb55..0000000 Binary files a/images/audio-volume-muted@2x.png and /dev/null differ diff --git a/images/bookmark-new.png b/images/bookmark-new.png deleted file mode 100644 index 796499b..0000000 Binary files a/images/bookmark-new.png and /dev/null differ diff --git a/images/bookmark-new@2x.png b/images/bookmark-new@2x.png deleted file mode 100644 index c7fc497..0000000 Binary files a/images/bookmark-new@2x.png and /dev/null differ diff --git a/images/bookmark-new_active.png b/images/bookmark-new_active.png deleted file mode 100644 index ea26fa5..0000000 Binary files a/images/bookmark-new_active.png and /dev/null differ diff --git a/images/bookmark-new_active@2x.png b/images/bookmark-new_active@2x.png deleted file mode 100644 index 15781af..0000000 Binary files a/images/bookmark-new_active@2x.png and /dev/null differ diff --git a/images/bookmark-remove.png b/images/bookmark-remove.png deleted file mode 100644 index 48d46a5..0000000 Binary files a/images/bookmark-remove.png and /dev/null differ diff --git a/images/bookmark-remove@2x.png b/images/bookmark-remove@2x.png deleted file mode 100644 index 8c8d7e9..0000000 Binary files a/images/bookmark-remove@2x.png and /dev/null differ diff --git a/images/channels.png b/images/channels.png deleted file mode 100644 index 109043d..0000000 Binary files a/images/channels.png and /dev/null differ diff --git a/images/channels@2x.png b/images/channels@2x.png deleted file mode 100644 index abba86b..0000000 Binary files a/images/channels@2x.png and /dev/null differ diff --git a/images/content-loading.png b/images/content-loading.png deleted file mode 100644 index e88c4f0..0000000 Binary files a/images/content-loading.png and /dev/null differ diff --git a/images/content-loading@2x.png b/images/content-loading@2x.png deleted file mode 100644 index d68c67b..0000000 Binary files a/images/content-loading@2x.png and /dev/null differ diff --git a/images/document-save.png b/images/document-save.png deleted file mode 100644 index 4ac54e0..0000000 Binary files a/images/document-save.png and /dev/null differ diff --git a/images/document-save@2x.png b/images/document-save@2x.png deleted file mode 100644 index 360b8f0..0000000 Binary files a/images/document-save@2x.png and /dev/null differ diff --git a/images/edit-clear.png b/images/edit-clear.png deleted file mode 100644 index 42901d8..0000000 Binary files a/images/edit-clear.png and /dev/null differ diff --git a/images/edit-find.png b/images/edit-find.png deleted file mode 100644 index aabed51..0000000 Binary files a/images/edit-find.png and /dev/null differ diff --git a/images/email.png b/images/email.png deleted file mode 100644 index 11e34d7..0000000 Binary files a/images/email.png and /dev/null differ diff --git a/images/email@2x.png b/images/email@2x.png deleted file mode 100644 index ed0f958..0000000 Binary files a/images/email@2x.png and /dev/null differ diff --git a/images/facebook.png b/images/facebook.png deleted file mode 100644 index 016965f..0000000 Binary files a/images/facebook.png and /dev/null differ diff --git a/images/facebook@2x.png b/images/facebook@2x.png deleted file mode 100644 index f3eb5de..0000000 Binary files a/images/facebook@2x.png and /dev/null differ diff --git a/images/go-next.png b/images/go-next.png deleted file mode 100644 index b927522..0000000 Binary files a/images/go-next.png and /dev/null differ diff --git a/images/go-next@2x.png b/images/go-next@2x.png deleted file mode 100644 index 1e617cd..0000000 Binary files a/images/go-next@2x.png and /dev/null differ diff --git a/images/go-next_active.png b/images/go-next_active.png deleted file mode 100644 index 9521341..0000000 Binary files a/images/go-next_active.png and /dev/null differ diff --git a/images/go-next_active@2x.png b/images/go-next_active@2x.png deleted file mode 100644 index 3e3e7dc..0000000 Binary files a/images/go-next_active@2x.png and /dev/null differ diff --git a/images/go-previous.png b/images/go-previous.png deleted file mode 100644 index c62d60f..0000000 Binary files a/images/go-previous.png and /dev/null differ diff --git a/images/go-previous@2x.png b/images/go-previous@2x.png deleted file mode 100644 index fc2664c..0000000 Binary files a/images/go-previous@2x.png and /dev/null differ diff --git a/images/go-previous_active.png b/images/go-previous_active.png deleted file mode 100644 index b410707..0000000 Binary files a/images/go-previous_active.png and /dev/null differ diff --git a/images/go-previous_active@2x.png b/images/go-previous_active@2x.png deleted file mode 100644 index faf42a5..0000000 Binary files a/images/go-previous_active@2x.png and /dev/null differ diff --git a/images/go-top.png b/images/go-top.png deleted file mode 100644 index b2ea402..0000000 Binary files a/images/go-top.png and /dev/null differ diff --git a/images/go-top@2x.png b/images/go-top@2x.png deleted file mode 100644 index c814273..0000000 Binary files a/images/go-top@2x.png and /dev/null differ diff --git a/images/link.png b/images/link.png deleted file mode 100644 index 8c9687a..0000000 Binary files a/images/link.png and /dev/null differ diff --git a/images/link@2x.png b/images/link@2x.png deleted file mode 100644 index bb31be1..0000000 Binary files a/images/link@2x.png and /dev/null differ diff --git a/images/mark-watched.png b/images/mark-watched.png deleted file mode 100644 index 961d149..0000000 Binary files a/images/mark-watched.png and /dev/null differ diff --git a/images/mark-watched@2x.png b/images/mark-watched@2x.png deleted file mode 100644 index 73f8fb4..0000000 Binary files a/images/mark-watched@2x.png and /dev/null differ diff --git a/images/media-playback-pause.png b/images/media-playback-pause.png deleted file mode 100644 index 03a5d7c..0000000 Binary files a/images/media-playback-pause.png and /dev/null differ diff --git a/images/media-playback-pause@2x.png b/images/media-playback-pause@2x.png deleted file mode 100644 index c78ae2d..0000000 Binary files a/images/media-playback-pause@2x.png and /dev/null differ diff --git a/images/media-playback-start.png b/images/media-playback-start.png deleted file mode 100644 index 256686b..0000000 Binary files a/images/media-playback-start.png and /dev/null differ diff --git a/images/media-playback-start@2x.png b/images/media-playback-start@2x.png deleted file mode 100644 index 662fe6a..0000000 Binary files a/images/media-playback-start@2x.png and /dev/null differ diff --git a/images/media-playback-stop.png b/images/media-playback-stop.png deleted file mode 100644 index 6c4e66d..0000000 Binary files a/images/media-playback-stop.png and /dev/null differ diff --git a/images/media-playback-stop@2x.png b/images/media-playback-stop@2x.png deleted file mode 100644 index b3a9971..0000000 Binary files a/images/media-playback-stop@2x.png and /dev/null differ diff --git a/images/media-skip-forward.png b/images/media-skip-forward.png deleted file mode 100644 index e59e645..0000000 Binary files a/images/media-skip-forward.png and /dev/null differ diff --git a/images/media-skip-forward@2x.png b/images/media-skip-forward@2x.png deleted file mode 100644 index 5300dc8..0000000 Binary files a/images/media-skip-forward@2x.png and /dev/null differ diff --git a/images/refine-search.png b/images/refine-search.png deleted file mode 100644 index dd977aa..0000000 Binary files a/images/refine-search.png and /dev/null differ diff --git a/images/refine-search@2x.png b/images/refine-search@2x.png deleted file mode 100644 index 37340a5..0000000 Binary files a/images/refine-search@2x.png and /dev/null differ diff --git a/images/safesearch.png b/images/safesearch.png deleted file mode 100644 index eef8f60..0000000 Binary files a/images/safesearch.png and /dev/null differ diff --git a/images/safesearch@2x.png b/images/safesearch@2x.png deleted file mode 100644 index 9a5fed5..0000000 Binary files a/images/safesearch@2x.png and /dev/null differ diff --git a/images/search-duration.png b/images/search-duration.png deleted file mode 100644 index fb816df..0000000 Binary files a/images/search-duration.png and /dev/null differ diff --git a/images/search-duration@2x.png b/images/search-duration@2x.png deleted file mode 100644 index daef48d..0000000 Binary files a/images/search-duration@2x.png and /dev/null differ diff --git a/images/search-quality.png b/images/search-quality.png deleted file mode 100644 index ae97229..0000000 Binary files a/images/search-quality.png and /dev/null differ diff --git a/images/search-quality@2x.png b/images/search-quality@2x.png deleted file mode 100644 index 3035153..0000000 Binary files a/images/search-quality@2x.png and /dev/null differ diff --git a/images/search-sortBy.png b/images/search-sortBy.png deleted file mode 100644 index 6834651..0000000 Binary files a/images/search-sortBy.png and /dev/null differ diff --git a/images/search-sortBy@2x.png b/images/search-sortBy@2x.png deleted file mode 100644 index 235200c..0000000 Binary files a/images/search-sortBy@2x.png and /dev/null differ diff --git a/images/search-time.png b/images/search-time.png deleted file mode 100644 index e38fb15..0000000 Binary files a/images/search-time.png and /dev/null differ diff --git a/images/search-time@2x.png b/images/search-time@2x.png deleted file mode 100644 index aeaaa21..0000000 Binary files a/images/search-time@2x.png and /dev/null differ diff --git a/images/show-updated.png b/images/show-updated.png deleted file mode 100644 index 9af84f2..0000000 Binary files a/images/show-updated.png and /dev/null differ diff --git a/images/show-updated@2x.png b/images/show-updated@2x.png deleted file mode 100644 index 7f0b807..0000000 Binary files a/images/show-updated@2x.png and /dev/null differ diff --git a/images/sort.png b/images/sort.png deleted file mode 100644 index 95fcb55..0000000 Binary files a/images/sort.png and /dev/null differ diff --git a/images/sort@2x.png b/images/sort@2x.png deleted file mode 100644 index 7475325..0000000 Binary files a/images/sort@2x.png and /dev/null differ diff --git a/images/system-search.png b/images/system-search.png deleted file mode 100644 index 4cf77de..0000000 Binary files a/images/system-search.png and /dev/null differ diff --git a/images/system-search_active.png b/images/system-search_active.png deleted file mode 100644 index ff40df1..0000000 Binary files a/images/system-search_active.png and /dev/null differ diff --git a/images/system-search_selected.png b/images/system-search_selected.png deleted file mode 100644 index 0db8a8f..0000000 Binary files a/images/system-search_selected.png and /dev/null differ diff --git a/images/twitter.png b/images/twitter.png deleted file mode 100644 index 8ccc500..0000000 Binary files a/images/twitter.png and /dev/null differ diff --git a/images/twitter@2x.png b/images/twitter@2x.png deleted file mode 100644 index 2366d6d..0000000 Binary files a/images/twitter@2x.png and /dev/null differ diff --git a/images/unwatched.png b/images/unwatched.png deleted file mode 100644 index 63da996..0000000 Binary files a/images/unwatched.png and /dev/null differ diff --git a/images/unwatched@2x.png b/images/unwatched@2x.png deleted file mode 100644 index 573c25e..0000000 Binary files a/images/unwatched@2x.png and /dev/null differ diff --git a/images/video-display.png b/images/video-display.png deleted file mode 100644 index a32f4a6..0000000 Binary files a/images/video-display.png and /dev/null differ diff --git a/images/video-display@2x.png b/images/video-display@2x.png deleted file mode 100644 index adacbe8..0000000 Binary files a/images/video-display@2x.png and /dev/null differ diff --git a/images/view-fullscreen.png b/images/view-fullscreen.png deleted file mode 100644 index eb7cac5..0000000 Binary files a/images/view-fullscreen.png and /dev/null differ diff --git a/images/view-fullscreen@2x.png b/images/view-fullscreen@2x.png deleted file mode 100644 index 80b93f0..0000000 Binary files a/images/view-fullscreen@2x.png and /dev/null differ diff --git a/images/view-list.png b/images/view-list.png deleted file mode 100644 index 9727d29..0000000 Binary files a/images/view-list.png and /dev/null differ diff --git a/images/view-list@2x.png b/images/view-list@2x.png deleted file mode 100644 index 20ea4b1..0000000 Binary files a/images/view-list@2x.png and /dev/null differ diff --git a/images/view-more.png b/images/view-more.png deleted file mode 100644 index 6143f4b..0000000 Binary files a/images/view-more.png and /dev/null differ diff --git a/images/view-more@2x.png b/images/view-more@2x.png deleted file mode 100644 index f13d0e0..0000000 Binary files a/images/view-more@2x.png and /dev/null differ diff --git a/images/view-refresh.png b/images/view-refresh.png deleted file mode 100644 index c4603d9..0000000 Binary files a/images/view-refresh.png and /dev/null differ diff --git a/images/view-refresh_active.png b/images/view-refresh_active.png deleted file mode 100644 index 2c9a275..0000000 Binary files a/images/view-refresh_active.png and /dev/null differ diff --git a/images/view-refresh_selected.png b/images/view-refresh_selected.png deleted file mode 100644 index 3b83d8b..0000000 Binary files a/images/view-refresh_selected.png and /dev/null differ diff --git a/images/view-restore.png b/images/view-restore.png deleted file mode 100644 index 9f05eb9..0000000 Binary files a/images/view-restore.png and /dev/null differ diff --git a/images/view-restore@2x.png b/images/view-restore@2x.png deleted file mode 100644 index ecee8be..0000000 Binary files a/images/view-restore@2x.png and /dev/null differ diff --git a/images/window-close.png b/images/window-close.png deleted file mode 100644 index 7a23bfd..0000000 Binary files a/images/window-close.png and /dev/null differ diff --git a/images/window-close_active.png b/images/window-close_active.png deleted file mode 100644 index 7553fca..0000000 Binary files a/images/window-close_active.png and /dev/null differ diff --git a/images/window-close_selected.png b/images/window-close_selected.png deleted file mode 100644 index 4158542..0000000 Binary files a/images/window-close_selected.png and /dev/null differ diff --git a/images/worldwide.png b/images/worldwide.png deleted file mode 100644 index d676740..0000000 Binary files a/images/worldwide.png and /dev/null differ diff --git a/images/worldwide@2x.png b/images/worldwide@2x.png deleted file mode 100644 index 2393b38..0000000 Binary files a/images/worldwide@2x.png and /dev/null differ diff --git a/lib/http/.gitignore b/lib/http/.gitignore new file mode 100644 index 0000000..e31cfb2 --- /dev/null +++ b/lib/http/.gitignore @@ -0,0 +1,2 @@ + +*.user diff --git a/lib/http/README.md b/lib/http/README.md new file mode 100644 index 0000000..a1370ff --- /dev/null +++ b/lib/http/README.md @@ -0,0 +1,63 @@ +# A wrapper for the Qt Network Access API + +This is just a wrapper around Qt's QNetworkAccessManager and friends. I use it in my Qt apps at http://flavio.tordini.org . It allows me to add missing functionality as needed, e.g.: + +- Throttling (as required by many web APIs nowadays) +- Read timeouts (don't let your requests get stuck forever) +- Automatic retries +- User agent and request header defaults +- Partial requests +- Redirection support (now supported by Qt >= 5.6) + +It has a simpler, higher-level API that I find easier to work with. The design emerged naturally in years of practical use. + +A basic example: + +``` +QObject *reply = Http::instance().get("https://google.com/"); +connect(reply, SIGNAL(data(QByteArray)), SLOT(onSuccess(QByteArray))); +connect(reply, SIGNAL(error(QString)), SLOT(onError(QString))); + +void MyClass::onSuccess(const QByteArray &bytes) { + qDebug() << "Feel the bytes!" << bytes; +} + +void MyClass::onError(const QString &message) { + qDebug() << "Something's wrong here" << message; +} +``` + +This is a real-world example of building a Http object suitable to a web service. It throttles requests, uses a custom user agent and caches results: + +``` +Http &myHttp() { + static Http *http = [] { + Http *http = new Http; + http->addRequestHeader("User-Agent", userAgent()); + + ThrottledHttp *throttledHttp = new ThrottledHttp(*http); + throttledHttp->setMilliseconds(1000); + + CachedHttp *cachedHttp = new CachedHttp(*throttledHttp, "mycache"); + cachedHttp->setMaxSeconds(86400 * 30); + + return cachedHttp; + }(); + return *http; +} +``` + +If the full power (and complexity) of QNetworkReply is needed you can always fallback to it: + +``` +HttpRequest req; +req.url = "https://flavio.tordini.org/"; +QNetworkReply *reply = Http::instance().networkReply(req); +// Use QNetworkReply as needed... +``` + +You can use this library under the MIT license and at your own risk. If you do, you're welcome contributing your changes and fixes. + +Cheers, + +Flavio diff --git a/lib/http/http.pri b/lib/http/http.pri new file mode 100644 index 0000000..6a210c7 --- /dev/null +++ b/lib/http/http.pri @@ -0,0 +1,16 @@ +QT *= network + +INCLUDEPATH += $$PWD/src +DEPENDPATH += $$PWD/src + +HEADERS += \ + $$PWD/src/cachedhttp.h \ + $$PWD/src/http.h \ + $$PWD/src/localcache.h \ + $$PWD/src/throttledhttp.h + +SOURCES += \ + $$PWD/src/cachedhttp.cpp \ + $$PWD/src/http.cpp \ + $$PWD/src/localcache.cpp \ + $$PWD/src/throttledhttp.cpp diff --git a/lib/http/http.pro b/lib/http/http.pro new file mode 100644 index 0000000..7fc2695 --- /dev/null +++ b/lib/http/http.pro @@ -0,0 +1,2 @@ +TEMPLATE = lib +include(http.pri) diff --git a/lib/http/src/cachedhttp.cpp b/lib/http/src/cachedhttp.cpp new file mode 100644 index 0000000..3c6942d --- /dev/null +++ b/lib/http/src/cachedhttp.cpp @@ -0,0 +1,70 @@ +#include "cachedhttp.h" +#include "localcache.h" + +namespace { + +QByteArray requestHash(const HttpRequest &req) { + const char sep = '|'; + QByteArray s = req.url.toEncoded() + sep + req.body + sep + QByteArray::number(req.offset); + if (req.operation == QNetworkAccessManager::PostOperation) { + s.append(sep); + s.append("POST"); + } + return LocalCache::hash(s); +} +} // namespace + +CachedHttpReply::CachedHttpReply(const QByteArray &body, const HttpRequest &req) + : bytes(body), req(req) { + QTimer::singleShot(0, this, SLOT(emitSignals())); +} + +QByteArray CachedHttpReply::body() const { + return bytes; +} + +void CachedHttpReply::emitSignals() { + emit data(body()); + emit finished(*this); + deleteLater(); +} + +WrappedHttpReply::WrappedHttpReply(LocalCache *cache, const QByteArray &key, HttpReply *httpReply) + : HttpReply(httpReply), cache(cache), key(key), httpReply(httpReply) { + connect(httpReply, SIGNAL(data(QByteArray)), SIGNAL(data(QByteArray))); + connect(httpReply, SIGNAL(error(QString)), SIGNAL(error(QString))); + connect(httpReply, SIGNAL(finished(HttpReply)), SLOT(originFinished(HttpReply))); +} + +void WrappedHttpReply::originFinished(const HttpReply &reply) { + if (reply.isSuccessful()) cache->insert(key, reply.body()); + emit finished(reply); +} + +CachedHttp::CachedHttp(Http &http, const char *name) + : http(http), cache(LocalCache::instance(name)), cachePostRequests(false) {} + +void CachedHttp::setMaxSeconds(uint seconds) { + cache->setMaxSeconds(seconds); +} + +void CachedHttp::setMaxSize(uint maxSize) { + cache->setMaxSize(maxSize); +} + +HttpReply *CachedHttp::request(const HttpRequest &req) { + bool cacheable = req.operation == QNetworkAccessManager::GetOperation || + (cachePostRequests && req.operation == QNetworkAccessManager::PostOperation); + if (!cacheable) { + qDebug() << "Not cacheable" << req.url; + return http.request(req); + } + const QByteArray key = requestHash(req); + const QByteArray value = cache->value(key); + if (!value.isNull()) { + qDebug() << "CachedHttp HIT" << req.url; + return new CachedHttpReply(value, req); + } + qDebug() << "CachedHttp MISS" << req.url.toString(); + return new WrappedHttpReply(cache, key, http.request(req)); +} diff --git a/lib/http/src/cachedhttp.h b/lib/http/src/cachedhttp.h new file mode 100644 index 0000000..e487a17 --- /dev/null +++ b/lib/http/src/cachedhttp.h @@ -0,0 +1,57 @@ +#ifndef CACHEDHTTP_H +#define CACHEDHTTP_H + +#include "http.h" + +class LocalCache; + +class CachedHttp : public Http { +public: + CachedHttp(Http &http = Http::instance(), const char *name = "http"); + void setMaxSeconds(uint seconds); + void setMaxSize(uint maxSize); + void setCachePostRequests(bool value) { cachePostRequests = value; } + HttpReply *request(const HttpRequest &req); + +private: + Http &http; + LocalCache *cache; + bool cachePostRequests; +}; + +class CachedHttpReply : public HttpReply { + Q_OBJECT + +public: + CachedHttpReply(const QByteArray &body, const HttpRequest &req); + QUrl url() const { return req.url; } + int statusCode() const { return 200; } + QByteArray body() const; + +private slots: + void emitSignals(); + +private: + const QByteArray bytes; + const HttpRequest req; +}; + +class WrappedHttpReply : public HttpReply { + Q_OBJECT + +public: + WrappedHttpReply(LocalCache *cache, const QByteArray &key, HttpReply *httpReply); + QUrl url() const { return httpReply->url(); } + int statusCode() const { return httpReply->statusCode(); } + QByteArray body() const { return httpReply->body(); } + +private slots: + void originFinished(const HttpReply &reply); + +private: + LocalCache *cache; + QByteArray key; + HttpReply *httpReply; +}; + +#endif // CACHEDHTTP_H diff --git a/lib/http/src/http.cpp b/lib/http/src/http.cpp new file mode 100644 index 0000000..7f58d39 --- /dev/null +++ b/lib/http/src/http.cpp @@ -0,0 +1,305 @@ +#include "http.h" + +namespace { + +QNetworkAccessManager *createNetworkAccessManager() { + QNetworkAccessManager *nam = new QNetworkAccessManager(); + return nam; +} + +QNetworkAccessManager *networkAccessManager() { + static QMap nams; + QThread *t = QThread::currentThread(); + QMap::const_iterator i = nams.constFind(t); + if (i != nams.constEnd()) return i.value(); + QNetworkAccessManager *nam = createNetworkAccessManager(); + nams.insert(t, nam); + return nam; +} + +int defaultReadTimeout = 10000; +} // namespace + +Http::Http() : requestHeaders(getDefaultRequestHeaders()), readTimeout(defaultReadTimeout) {} + +void Http::setRequestHeaders(const QMap &headers) { + requestHeaders = headers; +} + +QMap &Http::getRequestHeaders() { + return requestHeaders; +} + +void Http::addRequestHeader(const QByteArray &name, const QByteArray &value) { + requestHeaders.insert(name, value); +} + +void Http::setReadTimeout(int timeout) { + readTimeout = timeout; +} + +Http &Http::instance() { + static Http i; + return i; +} + +const QMap &Http::getDefaultRequestHeaders() { + static const QMap defaultRequestHeaders = [] { + QMap h; + h.insert("Accept-Charset", "utf-8"); + h.insert("Connection", "Keep-Alive"); + return h; + }(); + return defaultRequestHeaders; +} + +void Http::setDefaultReadTimeout(int timeout) { + defaultReadTimeout = timeout; +} + +QNetworkReply *Http::networkReply(const HttpRequest &req) { + QNetworkRequest request(req.url); + + QMap &headers = requestHeaders; + if (!req.headers.isEmpty()) headers = req.headers; + + QMap::const_iterator it; + for (it = headers.constBegin(); it != headers.constEnd(); ++it) + request.setRawHeader(it.key(), it.value()); + + if (req.offset > 0) + request.setRawHeader("Range", QString("bytes=%1-").arg(req.offset).toUtf8()); + + QNetworkAccessManager *manager = networkAccessManager(); + + QNetworkReply *networkReply = nullptr; + switch (req.operation) { + case QNetworkAccessManager::GetOperation: + networkReply = manager->get(request); + break; + + case QNetworkAccessManager::HeadOperation: + networkReply = manager->head(request); + break; + + case QNetworkAccessManager::PostOperation: + networkReply = manager->post(request, req.body); + break; + + default: + qWarning() << "Unknown operation:" << req.operation; + } + + return networkReply; +} + +HttpReply *Http::request(const HttpRequest &req) { + return new NetworkHttpReply(req, *this); +} + +HttpReply *Http::request(const QUrl &url, + QNetworkAccessManager::Operation operation, + const QByteArray &body, + uint offset) { + HttpRequest req; + req.url = url; + req.operation = operation; + req.body = body; + req.offset = offset; + return request(req); +} + +HttpReply *Http::get(const QUrl &url) { + return request(url, QNetworkAccessManager::GetOperation); +} + +HttpReply *Http::head(const QUrl &url) { + return request(url, QNetworkAccessManager::HeadOperation); +} + +HttpReply *Http::post(const QUrl &url, const QMap ¶ms) { + QByteArray body; + QMapIterator i(params); + while (i.hasNext()) { + i.next(); + body += QUrl::toPercentEncoding(i.key()) + '=' + QUrl::toPercentEncoding(i.value()) + '&'; + } + HttpRequest req; + req.url = url; + req.operation = QNetworkAccessManager::PostOperation; + req.body = body; + req.headers = requestHeaders; + req.headers.insert("Content-Type", "application/x-www-form-urlencoded"); + return request(req); +} + +HttpReply *Http::post(const QUrl &url, const QByteArray &body, const QByteArray &contentType) { + HttpRequest req; + req.url = url; + req.operation = QNetworkAccessManager::PostOperation; + req.body = body; + req.headers = requestHeaders; + QByteArray cType = contentType; + if (cType.isEmpty()) cType = "application/x-www-form-urlencoded"; + req.headers.insert("Content-Type", cType); + return request(req); +} + +NetworkHttpReply::NetworkHttpReply(const HttpRequest &req, Http &http) + : http(http), req(req), retryCount(0) { + if (req.url.isEmpty()) { + qWarning() << "Empty URL"; + } + + networkReply = http.networkReply(req); + setParent(networkReply); + setupReply(); + + readTimeoutTimer = new QTimer(this); + readTimeoutTimer->setInterval(http.getReadTimeout()); + readTimeoutTimer->setSingleShot(true); + connect(readTimeoutTimer, SIGNAL(timeout()), SLOT(readTimeout()), Qt::UniqueConnection); + readTimeoutTimer->start(); +} + +void NetworkHttpReply::setupReply() { + connect(networkReply, SIGNAL(error(QNetworkReply::NetworkError)), + SLOT(replyError(QNetworkReply::NetworkError)), Qt::UniqueConnection); + connect(networkReply, SIGNAL(finished()), SLOT(replyFinished()), Qt::UniqueConnection); + connect(networkReply, SIGNAL(downloadProgress(qint64, qint64)), + SLOT(downloadProgress(qint64, qint64)), Qt::UniqueConnection); +} + +QString NetworkHttpReply::errorMessage() { + return url().toString() + QLatin1Char(' ') + QString::number(statusCode()) + QLatin1Char(' ') + + reasonPhrase(); +} + +void NetworkHttpReply::emitError() { + const QString msg = errorMessage(); +#ifndef QT_NO_DEBUG_OUTPUT + qDebug() << "Http:" << msg; + if (!req.body.isEmpty()) qDebug() << "Http:" << req.body; +#endif + emit error(msg); + emitFinished(); +} + +void NetworkHttpReply::emitFinished() { + readTimeoutTimer->stop(); + + // disconnect to avoid replyFinished() from being called + networkReply->disconnect(); + + emit finished(*this); + + // bye bye my reply + // this will also delete this object and HttpReply as the QNetworkReply is their parent + networkReply->deleteLater(); +} + +void NetworkHttpReply::replyFinished() { + QUrl redirection = networkReply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl(); + if (redirection.isValid()) { + HttpRequest redirectReq; + redirectReq.url = redirection; + redirectReq.operation = req.operation; + redirectReq.body = req.body; + redirectReq.offset = req.offset; + QNetworkReply *redirectReply = http.networkReply(redirectReq); + setParent(redirectReply); + networkReply->deleteLater(); + networkReply = redirectReply; + setupReply(); + readTimeoutTimer->start(); + return; + } + + if (isSuccessful()) { + bytes = networkReply->readAll(); + emit data(bytes); + +#ifndef QT_NO_DEBUG_OUTPUT + if (!networkReply->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool()) + qDebug() << networkReply->url().toString() << statusCode(); + else + qDebug() << "CACHE" << networkReply->url().toString(); +#endif + } + + emitFinished(); +} + +void NetworkHttpReply::replyError(QNetworkReply::NetworkError code) { + Q_UNUSED(code); + const int status = statusCode(); + if (retryCount <= 3 && status >= 500 && status < 600) { + qDebug() << "Retrying" << req.url; + networkReply->disconnect(); + networkReply->deleteLater(); + QNetworkReply *retryReply = http.networkReply(req); + setParent(retryReply); + networkReply = retryReply; + setupReply(); + retryCount++; + readTimeoutTimer->start(); + } else { + emitError(); + return; + } +} + +void NetworkHttpReply::downloadProgress(qint64 bytesReceived, qint64 /* bytesTotal */) { + // qDebug() << "Downloading" << bytesReceived << bytesTotal << networkReply->url(); + if (bytesReceived > 0 && readTimeoutTimer->isActive()) { + readTimeoutTimer->stop(); + disconnect(networkReply, SIGNAL(downloadProgress(qint64, qint64)), this, + SLOT(downloadProgress(qint64, qint64))); + } +} + +void NetworkHttpReply::readTimeout() { + if (!networkReply) return; + networkReply->disconnect(); + networkReply->abort(); + networkReply->deleteLater(); + + if (retryCount > 3 && (networkReply->operation() != QNetworkAccessManager::GetOperation && + networkReply->operation() != QNetworkAccessManager::HeadOperation)) { + emitError(); + emit finished(*this); + return; + } + + qDebug() << "Timeout" << req.url; + QNetworkReply *retryReply = http.networkReply(req); + setParent(retryReply); + networkReply = retryReply; + setupReply(); + retryCount++; + readTimeoutTimer->start(); +} + +QUrl NetworkHttpReply::url() const { + return networkReply->url(); +} + +int NetworkHttpReply::statusCode() const { + return networkReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); +} + +QString NetworkHttpReply::reasonPhrase() const { + return networkReply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString(); +} + +const QList NetworkHttpReply::headers() const { + return networkReply->rawHeaderPairs(); +} + +QByteArray NetworkHttpReply::header(const QByteArray &headerName) const { + return networkReply->rawHeader(headerName); +} + +QByteArray NetworkHttpReply::body() const { + return bytes; +} diff --git a/lib/http/src/http.h b/lib/http/src/http.h new file mode 100644 index 0000000..23f3754 --- /dev/null +++ b/lib/http/src/http.h @@ -0,0 +1,105 @@ +#ifndef HTTP_H +#define HTTP_H + +#include + +class HttpRequest { +public: + HttpRequest() : operation(QNetworkAccessManager::GetOperation), offset(0) {} + QUrl url; + QNetworkAccessManager::Operation operation; + QByteArray body; + uint offset; + QMap headers; +}; + +class HttpReply : public QObject { + Q_OBJECT + +public: + HttpReply(QObject *parent = nullptr) : QObject(parent) {} + virtual QUrl url() const = 0; + virtual int statusCode() const = 0; + int isSuccessful() const { return statusCode() >= 200 && statusCode() < 300; } + virtual QString reasonPhrase() const { return QString(); } + virtual const QList headers() const { + return QList(); + } + virtual QByteArray header(const QByteArray &headerName) const { + Q_UNUSED(headerName); + return QByteArray(); + } + + virtual QByteArray body() const = 0; + +signals: + void data(const QByteArray &bytes); + void error(const QString &message); + void finished(const HttpReply &reply); +}; + +class Http { +public: + static Http &instance(); + static const QMap &getDefaultRequestHeaders(); + static void setDefaultReadTimeout(int timeout); + + Http(); + + void setRequestHeaders(const QMap &headers); + QMap &getRequestHeaders(); + void addRequestHeader(const QByteArray &name, const QByteArray &value); + + void setReadTimeout(int timeout); + int getReadTimeout() { return readTimeout; } + + QNetworkReply *networkReply(const HttpRequest &req); + virtual HttpReply *request(const HttpRequest &req); + HttpReply * + request(const QUrl &url, + QNetworkAccessManager::Operation operation = QNetworkAccessManager::GetOperation, + const QByteArray &body = QByteArray(), + uint offset = 0); + HttpReply *get(const QUrl &url); + HttpReply *head(const QUrl &url); + HttpReply *post(const QUrl &url, const QMap ¶ms); + HttpReply *post(const QUrl &url, const QByteArray &body, const QByteArray &contentType); + +private: + QMap requestHeaders; + int readTimeout; +}; + +class NetworkHttpReply : public HttpReply { + Q_OBJECT + +public: + NetworkHttpReply(const HttpRequest &req, Http &http); + QUrl url() const; + int statusCode() const; + QString reasonPhrase() const; + const QList headers() const; + QByteArray header(const QByteArray &headerName) const; + QByteArray body() const; + +private slots: + void replyFinished(); + void replyError(QNetworkReply::NetworkError); + void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); + void readTimeout(); + +private: + void setupReply(); + QString errorMessage(); + void emitError(); + void emitFinished(); + + Http &http; + HttpRequest req; + QNetworkReply *networkReply; + QTimer *readTimeoutTimer; + int retryCount; + QByteArray bytes; +}; + +#endif // HTTP_H diff --git a/lib/http/src/localcache.cpp b/lib/http/src/localcache.cpp new file mode 100644 index 0000000..8bee99e --- /dev/null +++ b/lib/http/src/localcache.cpp @@ -0,0 +1,171 @@ +#include "localcache.h" + +LocalCache *LocalCache::instance(const char *name) { + static QMap instances; + auto i = instances.constFind(QByteArray::fromRawData(name, strlen(name))); + if (i != instances.constEnd()) return i.value(); + LocalCache *instance = new LocalCache(name); + instances.insert(instance->getName(), instance); + return instance; +} + +LocalCache::LocalCache(const QByteArray &name) + : name(name), maxSeconds(86400 * 30), maxSize(1024 * 1024 * 100), size(0), expiring(false), + insertCount(0) { + directory = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + QLatin1Char('/') + + QLatin1String(name) + QLatin1Char('/'); +#ifndef QT_NO_DEBUG_OUTPUT + hits = 0; + misses = 0; +#endif +} + +LocalCache::~LocalCache() { +#ifndef QT_NO_DEBUG_OUTPUT + debugStats(); +#endif +} + +QByteArray LocalCache::hash(const QByteArray &s) { + QCryptographicHash hash(QCryptographicHash::Sha1); + hash.addData(s); + const QByteArray h = QByteArray::number(*(qlonglong *)hash.result().constData(), 36); + static const char sep('/'); + QByteArray p; + p.reserve(h.length() + 2); + p.append(h.at(0)); + p.append(sep); + p.append(h.at(1)); + p.append(sep); + p.append(h.constData() + 2, strlen(h.constData()) - 2); // p.append(h.mid(2)); + return p; +} + +bool LocalCache::isCached(const QString &path) { + bool cached = (QFile::exists(path) && + (maxSeconds == 0 || QDateTime::currentDateTimeUtc().toTime_t() - + QFileInfo(path).created().toTime_t() < + maxSeconds)); +#ifndef QT_NO_DEBUG_OUTPUT + if (!cached) misses++; +#endif + return cached; +} + +QByteArray LocalCache::value(const QByteArray &key) { + const QString path = cachePath(key); + if (!isCached(path)) return QByteArray(); + + QFile file(path); + if (!file.open(QIODevice::ReadOnly)) { + qWarning() << __PRETTY_FUNCTION__ << file.fileName() << file.errorString(); +#ifndef QT_NO_DEBUG_OUTPUT + misses++; +#endif + return QByteArray(); + } +#ifndef QT_NO_DEBUG_OUTPUT + hits++; +#endif + return file.readAll(); +} + +void LocalCache::insert(const QByteArray &key, const QByteArray &value) { + const QueueItem item = {key, value}; + insertQueue.append(item); + QTimer::singleShot(0, [this]() { + if (insertQueue.isEmpty()) return; + for (const auto &item : insertQueue) { + const QString path = cachePath(item.key); + const QString parentDir = path.left(path.lastIndexOf('/')); + if (!QFile::exists(parentDir)) { + QDir().mkpath(parentDir); + } + QFile file(path); + if (!file.open(QIODevice::WriteOnly)) { + qWarning() << "Cannot create" << path; + continue; + } + file.write(item.value); + file.close(); + if (size > 0) size += item.value.size(); + } + insertQueue.clear(); + + // expire cache every n inserts + if (maxSize > 0 && ++insertCount % 100 == 0) { + if (size == 0 || size > maxSize) size = expire(); + } + }); +} + +bool LocalCache::clear() { +#ifndef QT_NO_DEBUG_OUTPUT + hits = 0; + misses = 0; +#endif + size = 0; + insertCount = 0; + return QDir(directory).removeRecursively(); +} + +QString LocalCache::cachePath(const QByteArray &key) const { + return directory + QLatin1String(key.constData()); +} + +qint64 LocalCache::expire() { + if (expiring) return size; + expiring = true; + + QDir::Filters filters = QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot; + QDirIterator it(directory, filters, QDirIterator::Subdirectories); + + QMultiMap cacheItems; + qint64 totalSize = 0; + while (it.hasNext()) { + QString path = it.next(); + QFileInfo info = it.fileInfo(); + cacheItems.insert(info.created(), path); + totalSize += info.size(); + qApp->processEvents(); + } + + int removedFiles = 0; + qint64 goal = (maxSize * 9) / 10; + auto i = cacheItems.constBegin(); + while (i != cacheItems.constEnd()) { + if (totalSize < goal) break; + QString name = i.value(); + QFile file(name); + qint64 size = file.size(); + file.remove(); + totalSize -= size; + ++removedFiles; + ++i; + qApp->processEvents(); + } +#ifndef QT_NO_DEBUG_OUTPUT + debugStats(); + if (removedFiles > 0) { + qDebug() << "Removed:" << removedFiles << "Kept:" << cacheItems.count() - removedFiles + << "New Size:" << totalSize; + } +#endif + + expiring = false; + + return totalSize; +} + +#ifndef QT_NO_DEBUG_OUTPUT +void LocalCache::debugStats() { + int total = hits + misses; + if (total > 0) { + qDebug() << "Cache:" << name << '\n' + << "Inserts:" << insertCount << '\n' + << "Requests:" << total << '\n' + << "Hits:" << hits << (hits * 100) / total << "%\n" + << "Misses:" << misses << (misses * 100) / total << "%"; + } +} +#endif diff --git a/lib/http/src/localcache.h b/lib/http/src/localcache.h new file mode 100644 index 0000000..ff07109 --- /dev/null +++ b/lib/http/src/localcache.h @@ -0,0 +1,52 @@ +#ifndef LOCALCACHE_H +#define LOCALCACHE_H + +#include + +/** + * @brief Not thread-safe + */ +class LocalCache { +public: + static LocalCache *instance(const char *name); + ~LocalCache(); + static QByteArray hash(const QByteArray &s); + + const QByteArray &getName() const { return name; } + + void setMaxSeconds(uint value) { maxSeconds = value; } + void setMaxSize(uint value) { maxSize = value; } + + QByteArray value(const QByteArray &key); + void insert(const QByteArray &key, const QByteArray &value); + bool clear(); + +private: + LocalCache(const QByteArray &name); + QString cachePath(const QByteArray &key) const; + bool isCached(const QString &path); + qint64 expire(); +#ifndef QT_NO_DEBUG_OUTPUT + void debugStats(); +#endif + + QByteArray name; + QString directory; + uint maxSeconds; + qint64 maxSize; + qint64 size; + bool expiring; + uint insertCount; + struct QueueItem { + QByteArray key; + QByteArray value; + }; + QVector insertQueue; + +#ifndef QT_NO_DEBUG_OUTPUT + uint hits; + uint misses; +#endif +}; + +#endif // LOCALCACHE_H diff --git a/lib/http/src/throttledhttp.cpp b/lib/http/src/throttledhttp.cpp new file mode 100644 index 0000000..7cfb3fd --- /dev/null +++ b/lib/http/src/throttledhttp.cpp @@ -0,0 +1,51 @@ +#include "throttledhttp.h" + +ThrottledHttp::ThrottledHttp(Http &http) : http(http), milliseconds(1000) { + elapsedTimer.start(); +} + +HttpReply *ThrottledHttp::request(const HttpRequest &req) { + return new ThrottledHttpReply(http, req, milliseconds, elapsedTimer); +} + +ThrottledHttpReply::ThrottledHttpReply(Http &http, + const HttpRequest &req, + int milliseconds, + QElapsedTimer &elapsedTimer) + : http(http), req(req), milliseconds(milliseconds), elapsedTimer(elapsedTimer), timer(nullptr) { + checkElapsed(); +} + +void ThrottledHttpReply::checkElapsed() { + /* + static QMutex mutex; + QMutexLocker locker(&mutex); + */ + + const qint64 elapsedSinceLastRequest = elapsedTimer.elapsed(); + if (elapsedSinceLastRequest < milliseconds) { + if (!timer) { + timer = new QTimer(this); + timer->setSingleShot(true); + timer->setTimerType(Qt::PreciseTimer); + connect(timer, SIGNAL(timeout()), SLOT(checkElapsed())); + } + qDebug() << "Throttling" << req.url + << QString("%1ms").arg(milliseconds - elapsedSinceLastRequest); + timer->setInterval(milliseconds - elapsedSinceLastRequest); + timer->start(); + return; + } + elapsedTimer.start(); + doRequest(); +} + +void ThrottledHttpReply::doRequest() { + QObject *reply = http.request(req); + connect(reply, SIGNAL(data(QByteArray)), SIGNAL(data(QByteArray))); + connect(reply, SIGNAL(error(QString)), SIGNAL(error(QString))); + connect(reply, SIGNAL(finished(HttpReply)), SIGNAL(finished(HttpReply))); + + // this will cause the deletion of this object once the request is finished + setParent(reply); +} diff --git a/lib/http/src/throttledhttp.h b/lib/http/src/throttledhttp.h new file mode 100644 index 0000000..bd78d28 --- /dev/null +++ b/lib/http/src/throttledhttp.h @@ -0,0 +1,44 @@ +#ifndef THROTTLEDHTTP_H +#define THROTTLEDHTTP_H + +#include "http.h" +#include +#include + +class ThrottledHttp : public Http { +public: + ThrottledHttp(Http &http = Http::instance()); + void setMilliseconds(int milliseconds) { this->milliseconds = milliseconds; } + HttpReply *request(const HttpRequest &req); + +private: + Http &http; + int milliseconds; + QElapsedTimer elapsedTimer; +}; + +class ThrottledHttpReply : public HttpReply { + Q_OBJECT + +public: + ThrottledHttpReply(Http &http, + const HttpRequest &req, + int milliseconds, + QElapsedTimer &elapsedTimer); + QUrl url() const { return req.url; } + int statusCode() const { return 200; } + QByteArray body() const { return QByteArray(); } + +private slots: + void checkElapsed(); + +private: + void doRequest(); + Http &http; + HttpRequest req; + int milliseconds; + QElapsedTimer &elapsedTimer; + QTimer *timer; +}; + +#endif // THROTTLEDHTTP_H diff --git a/lib/idle/README.md b/lib/idle/README.md new file mode 100644 index 0000000..a10d818 --- /dev/null +++ b/lib/idle/README.md @@ -0,0 +1,9 @@ +# Qt Idle library + +This simple Qt library manages system and display sleep on Mac, Windows and Linux. Contributions for other platforms are welcome. + +You can use this library under the MIT license and at your own risk. If you do, you're welcome contributing your changes and fixes. + +Cheers, + +Flavio \ No newline at end of file diff --git a/lib/idle/idle.pri b/lib/idle/idle.pri new file mode 100644 index 0000000..a70e586 --- /dev/null +++ b/lib/idle/idle.pri @@ -0,0 +1,13 @@ +INCLUDEPATH += $$PWD/src +DEPENDPATH += $$PWD/src + +HEADERS += $$PWD/src/idle.h + +mac { + SOURCES += $$PWD/src/idle_mac.cpp +} else:win32 { + SOURCES += $$PWD/src/idle_win.cpp +} else { + QT *= dbus + SOURCES += $$PWD/src/idle_linux.cpp +} diff --git a/lib/idle/src/idle.h b/lib/idle/src/idle.h new file mode 100644 index 0000000..4f77d0a --- /dev/null +++ b/lib/idle/src/idle.h @@ -0,0 +1,17 @@ +#ifndef IDLE_H +#define IDLE_H + +#include + +class Idle { +public: + static bool preventDisplaySleep(const QString &reason); + static bool allowDisplaySleep(); + static QString displayErrorMessage(); + + static bool preventSystemSleep(const QString &reason); + static bool allowSystemSleep(); + static QString systemErrorMessage(); +}; + +#endif // IDLE_H diff --git a/lib/idle/src/idle_linux.cpp b/lib/idle/src/idle_linux.cpp new file mode 100644 index 0000000..1992a66 --- /dev/null +++ b/lib/idle/src/idle_linux.cpp @@ -0,0 +1,68 @@ +#include "idle.h" + +#include +#include +#include +#include + +namespace { + +const QString fdDisplayService = "org.freedesktop.ScreenSaver"; +const QString fdDisplayPath = "/org/freedesktop/ScreenSaver"; +const QString fdDisplayInterface = fdDisplayService; + +const QString gnomeSystemService = "org.gnome.SessionManager"; +const QString gnomeSystemPath = "/org/gnome/SessionManager"; +const QString gnomeSystemInterface = gnomeSystemService; + +const QString inhibitMethod = "Inhibit"; +const QString uninhibitMethod = "UnInhibit"; + +quint32 cookie; +QString errorMessage; + +bool handleReply(const QDBusReply &reply) { + if (reply.isValid()) { + cookie = reply.value(); + errorMessage.clear(); + return true; + } + errorMessage = reply.error().message(); + return false; +} + +} // namespace + +bool Idle::preventDisplaySleep(const QString &reason) { + QDBusInterface dbus(fdDisplayService, fdDisplayPath, fdDisplayInterface); + QDBusReply reply = + dbus.call(inhibitMethod, QCoreApplication::applicationName(), reason); + return handleReply(reply); +} + +bool Idle::allowDisplaySleep() { + QDBusInterface dbus(fdDisplayService, fdDisplayPath, fdDisplayInterface); + dbus.call(uninhibitMethod, cookie); + return true; +} + +QString Idle::displayErrorMessage() { + return errorMessage; +} + +bool Idle::preventSystemSleep(const QString &reason) { + QDBusInterface dbus(gnomeSystemService, gnomeSystemPath, gnomeSystemInterface); + QDBusReply reply = + dbus.call(inhibitMethod, QCoreApplication::applicationName(), reason); + return handleReply(reply); +} + +bool Idle::allowSystemSleep() { + QDBusInterface dbus(gnomeSystemService, gnomeSystemPath, gnomeSystemInterface); + dbus.call(uninhibitMethod, cookie); + return true; +} + +QString Idle::systemErrorMessage() { + return errorMessage; +} diff --git a/lib/idle/src/idle_mac.cpp b/lib/idle/src/idle_mac.cpp new file mode 100644 index 0000000..ff62bff --- /dev/null +++ b/lib/idle/src/idle_mac.cpp @@ -0,0 +1,47 @@ +#include "idle.h" + +#include + +namespace { + +IOPMAssertionID displayAssertionID = 0; +IOReturn displayRes = 0; + +IOPMAssertionID systemAssertionID = 0; +IOReturn systemRes = 0; + +} // namespace + +bool Idle::preventDisplaySleep(const QString &reason) { + displayRes = IOPMAssertionCreateWithName(kIOPMAssertionTypePreventUserIdleDisplaySleep, + kIOPMAssertionLevelOn, reason.toCFString(), + &displayAssertionID); + return displayRes == kIOReturnSuccess; +} + +bool Idle::allowDisplaySleep() { + displayRes = IOPMAssertionRelease(displayAssertionID); + return displayRes == kIOReturnSuccess; +} + +QString Idle::displayErrorMessage() { + return QString(); + // return QString::fromUtf8(IOService::stringFromReturn(displayRes)); +} + +bool Idle::preventSystemSleep(const QString &reason) { + systemRes = IOPMAssertionCreateWithName(kIOPMAssertionTypePreventUserIdleSystemSleep, + kIOPMAssertionLevelOn, reason.toCFString(), + &systemAssertionID); + return systemRes == kIOReturnSuccess; +} + +bool Idle::allowSystemSleep() { + systemRes = IOPMAssertionRelease(systemAssertionID); + return systemRes == kIOReturnSuccess; +} + +QString Idle::systemErrorMessage() { + return QString(); + // return QString::fromUtf8(IOService::stringFromReturn(systemRes)); +} diff --git a/lib/idle/src/idle_win.cpp b/lib/idle/src/idle_win.cpp new file mode 100644 index 0000000..2f189ef --- /dev/null +++ b/lib/idle/src/idle_win.cpp @@ -0,0 +1,35 @@ +#include "idle.h" + +#include "windows.h" + +namespace { +EXECUTION_STATE executionState; +} + +bool Idle::preventDisplaySleep(const QString &reason) { + executionState = SetThreadExecutionState(ES_CONTINUOUS | ES_DISPLAY_REQUIRED); + return true; +} + +bool Idle::allowDisplaySleep() { + SetThreadExecutionState(ES_CONTINUOUS | executionState); + return true; +} + +QString Idle::displayErrorMessage() { + return QString(); +} + +bool Idle::preventSystemSleep(const QString &reason) { + executionState = SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED); + return true; +} + +bool Idle::allowSystemSleep() { + SetThreadExecutionState(ES_CONTINUOUS | executionState); + return true; +} + +QString Idle::systemErrorMessage() { + return QString(); +} diff --git a/lib/media/.gitignore b/lib/media/.gitignore new file mode 100644 index 0000000..e43b0f9 --- /dev/null +++ b/lib/media/.gitignore @@ -0,0 +1 @@ +.DS_Store diff --git a/lib/media/README.md b/lib/media/README.md new file mode 100644 index 0000000..4aeec30 --- /dev/null +++ b/lib/media/README.md @@ -0,0 +1,9 @@ +# Qt Media Library Abstraction + +This is a simple wrapper around a multimedia playback library. + +Define `MEDIA_QTAV` to link to QtAV or `MEDIA_MPV` to link to libmpv (>=0.29.0). + +`MEDIA_AUDIOONLY` can be defined if the application does not need video. + +You can use this library under the MIT license and at your own risk. If you do, you're welcome contributing your changes and fixes. diff --git a/lib/media/media.pri b/lib/media/media.pri new file mode 100644 index 0000000..f6b64fa --- /dev/null +++ b/lib/media/media.pri @@ -0,0 +1,37 @@ +INCLUDEPATH += $$PWD/src +DEPENDPATH += $$PWD/src + +HEADERS += $$PWD/src/media.h + +contains(DEFINES, MEDIA_QTAV) { + QT += avwidgets + INCLUDEPATH += $$PWD/src/qtav + DEPENDPATH += $$PWD/src/qtav + HEADERS += $$PWD/src/mediaqtav.h + SOURCES += $$PWD/src/mediaqtav.cpp +} + +contains(DEFINES, MEDIA_MPV) { + QT *= gui + + LIBS += -lmpv +mac { + # useful for homebrew: brew install mpv + # LIBS += -L/usr/local/lib + # INCLUDEPATH += /usr/local/include +} + + INCLUDEPATH += $$PWD/src/mpv + DEPENDPATH += $$PWD/src/mpv + HEADERS += $$PWD/src/mpv/mediampv.h + SOURCES += $$PWD/src/mpv/mediampv.cpp + + !contains(DEFINES, MEDIA_AUDIOONLY) { + QT *= widgets + unix:!mac { + QT *= x11extras + } + HEADERS += $$PWD/src/mpv/mpvwidget.h + SOURCES += $$PWD/src/mpv/mpvwidget.cpp + } +} diff --git a/lib/media/src/media.h b/lib/media/src/media.h new file mode 100644 index 0000000..a14327f --- /dev/null +++ b/lib/media/src/media.h @@ -0,0 +1,82 @@ +#ifndef MEDIA_H +#define MEDIA_H + +#include +#ifndef MEDIA_AUDIOONLY +#include +#endif + +class Media : public QObject { + Q_OBJECT + +public: + enum State { + StoppedState, + LoadingState, + BufferingState, + PlayingState, + PausedState, + ErrorState + }; + Q_ENUM(State) + + Media(QObject *parent = nullptr) : QObject(parent) { + qRegisterMetaType("Media::State"); + } + virtual void setAudioOnly(bool value) = 0; +#ifndef MEDIA_AUDIOONLY + virtual void setRenderer(const QString &name) = 0; + virtual QWidget *videoWidget() = 0; + virtual void playSeparateAudioAndVideo(const QString &video, const QString &audio) = 0; + virtual void snapshot() = 0; +#endif + virtual void init() = 0; + + virtual Media::State state() const = 0; + + virtual void play(const QString &file) = 0; + virtual void play() = 0; + virtual void pause() = 0; + virtual void stop() = 0; + virtual void seek(qint64 ms) = 0; + virtual QString file() const = 0; + + virtual void setBufferMilliseconds(qint64 value) = 0; + virtual void setUserAgent(const QString &value) = 0; + + virtual void enqueue(const QString &file) = 0; + virtual void clearQueue() = 0; + virtual bool hasQueue() const = 0; + + virtual qint64 position() const = 0; + virtual qint64 duration() const = 0; + virtual qint64 remainingTime() const = 0; + + virtual qreal volume() const = 0; + virtual void setVolume(qreal value) = 0; + + virtual bool volumeMuted() const = 0; + virtual void setVolumeMuted(bool value) = 0; + + virtual QString errorString() const = 0; + +signals: + void error(const QString &message); + void sourceChanged(); + void bufferStatus(qreal value); + void loaded(); + void started(); + void stopped(); + void paused(bool p); + void stateChanged(Media::State state); + void positionChanged(qint64 ms); + void aboutToFinish(); + void finished(); + void volumeChanged(qreal value); + void volumeMutedChanged(bool value); +#ifndef MEDIA_AUDIOONLY + void snapshotReady(const QImage &image); +#endif +}; + +#endif // MEDIA_H diff --git a/lib/media/src/mpv/mediampv.cpp b/lib/media/src/mpv/mediampv.cpp new file mode 100644 index 0000000..b077530 --- /dev/null +++ b/lib/media/src/mpv/mediampv.cpp @@ -0,0 +1,410 @@ +#include "mediampv.h" + +#include +#include + +#ifndef MEDIA_AUDIOONLY +#include "mpvwidget.h" +#endif + +namespace { +void wakeup(void *ctx) { + // This callback is invoked from any mpv thread (but possibly also + // recursively from a thread that is calling the mpv API). Just notify + // the Qt GUI thread to wake up (so that it can process events with + // mpv_wait_event()), and return as quickly as possible. + MediaMPV *mediaMPV = (MediaMPV *)ctx; + emit mediaMPV->mpvEvents(); +} + +} // namespace + +MediaMPV::MediaMPV(QObject *parent) : Media(parent), widget(nullptr) { + QThread *thread = new QThread(this); + thread->start(); + moveToThread(thread); + connect(this, &QObject::destroyed, thread, &QThread::quit); + +#ifndef Q_OS_WIN + // Qt sets the locale in the QApplication constructor, but libmpv requires + // the LC_NUMERIC category to be set to "C", so change it back. + std::setlocale(LC_NUMERIC, "C"); +#endif + + mpv = mpv_create(); + if (!mpv) qFatal("Cannot create MPV instance"); + + mpv_set_option_string(mpv, "config", "no"); + mpv_set_option_string(mpv, "audio-display", "no"); + mpv_set_option_string(mpv, "gapless-audio", "weak"); + mpv_set_option_string(mpv, "idle", "yes"); + mpv_set_option_string(mpv, "input-default-bindings", "no"); + mpv_set_option_string(mpv, "input-vo-keyboard", "no"); + mpv_set_option_string(mpv, "input-cursor", "no"); + mpv_set_option_string(mpv, "input-media-keys", "no"); + mpv_set_option_string(mpv, "ytdl", "no"); + mpv_set_option_string(mpv, "fs", "no"); + mpv_set_option_string(mpv, "osd-level", "0"); + mpv_set_option_string(mpv, "quiet", "yes"); + mpv_set_option_string(mpv, "load-scripts", "no"); + mpv_set_option_string(mpv, "audio-client-name", + QCoreApplication::applicationName().toUtf8().data()); + mpv_set_option_string(mpv, "hwdec", "auto"); + mpv_set_option_string(mpv, "vo", "libmpv"); + + mpv_set_option_string(mpv, "cache", "no"); + mpv_set_option_string(mpv, "demuxer-max-bytes", "10485760"); + mpv_set_option_string(mpv, "demuxer-max-back-bytes", "10485760"); + +#ifdef MEDIA_MPV_WID + widget = new QWidget(); + widget->setAttribute(Qt::WA_DontCreateNativeAncestors); + widget->setAttribute(Qt::WA_NativeWindow); + // If you have a HWND, use: int64_t wid = (intptr_t)hwnd; + int64_t wid = (intptr_t)widget->winId(); + mpv_set_option(mpv, "wid", MPV_FORMAT_INT64, &wid); +#endif + +#ifndef QT_NO_DEBUG_OUTPUT + // Request log messages + // They are received as MPV_EVENT_LOG_MESSAGE. + mpv_request_log_messages(mpv, "info"); +#endif + + // From this point on, the wakeup function will be called. The callback + // can come from any thread, so we use the QueuedConnection mechanism to + // relay the wakeup in a thread-safe way. + connect(this, &MediaMPV::mpvEvents, this, &MediaMPV::onMpvEvents, Qt::QueuedConnection); + mpv_set_wakeup_callback(mpv, wakeup, this); + + if (mpv_initialize(mpv) < 0) qFatal("mpv failed to initialize"); + + // Let us receive property change events with MPV_EVENT_PROPERTY_CHANGE if + // this property changes. + mpv_observe_property(mpv, 0, "time-pos", MPV_FORMAT_DOUBLE); + mpv_observe_property(mpv, 0, "duration", MPV_FORMAT_DOUBLE); + mpv_observe_property(mpv, 0, "volume", MPV_FORMAT_DOUBLE); + mpv_observe_property(mpv, 0, "mute", MPV_FORMAT_FLAG); +} + +// This slot is invoked by wakeup() (through the mpvEvents signal). +void MediaMPV::onMpvEvents() { + // Process all events, until the event queue is empty. + while (mpv) { + mpv_event *event = mpv_wait_event(mpv, 0); + if (event->event_id == MPV_EVENT_NONE) break; + handleMpvEvent(event); + } +} + +void MediaMPV::checkAboutToFinish(qint64 position) { + if (!aboutToFinishEmitted && currentState == Media::PlayingState) { + const qint64 dur = duration(); + if (dur > 0 && dur - position < 5000) { + aboutToFinishEmitted = true; + qDebug() << "aboutToFinish" << position << dur; + emit aboutToFinish(); + } + } +} + +void MediaMPV::handleMpvEvent(mpv_event *event) { + // qDebug() << event->data; + switch (event->event_id) { + case MPV_EVENT_START_FILE: + clearTrackState(); + emit sourceChanged(); + setState(Media::LoadingState); + break; + + case MPV_EVENT_SEEK: + setState(Media::BufferingState); + break; + + case MPV_EVENT_FILE_LOADED: + setState(Media::PlayingState); + break; + + case MPV_EVENT_PLAYBACK_RESTART: + case MPV_EVENT_UNPAUSE: + setState(Media::PlayingState); + break; + + case MPV_EVENT_END_FILE: { + struct mpv_event_end_file *eof_event = (struct mpv_event_end_file *)event->data; + if (eof_event->reason == MPV_END_FILE_REASON_EOF || + eof_event->reason == MPV_END_FILE_REASON_ERROR) { + qDebug() << "Finished"; + setState(Media::StoppedState); + emit finished(); + } + break; + } + + case MPV_EVENT_PAUSE: + setState(Media::PausedState); + break; + + case MPV_EVENT_PROPERTY_CHANGE: { + mpv_event_property *prop = (mpv_event_property *)event->data; + // qDebug() << prop->name << prop->data; + + if (strcmp(prop->name, "time-pos") == 0) { + if (prop->format == MPV_FORMAT_DOUBLE) { + double seconds = *(double *)prop->data; + qint64 ms = seconds * 1000.; + emit positionChanged(ms); + checkAboutToFinish(ms); + } + } + + else if (strcmp(prop->name, "volume") == 0) { + if (prop->format == MPV_FORMAT_DOUBLE) { + double vol = *(double *)prop->data; + emit volumeChanged(vol / 100.); + } + } + + else if (strcmp(prop->name, "mute") == 0) { + if (prop->format == MPV_FORMAT_FLAG) { + int mute = *(int *)prop->data; + emit volumeMutedChanged(mute == 1); + } + } + + break; + } + + case MPV_EVENT_LOG_MESSAGE: { + struct mpv_event_log_message *msg = (struct mpv_event_log_message *)event->data; + qDebug() << "[" << msg->prefix << "] " << msg->level << ": " << msg->text; + + if (msg->log_level == MPV_LOG_LEVEL_ERROR) { + lastErrorString = QString::fromUtf8(msg->text); + emit error(lastErrorString); + } + + break; + } + + case MPV_EVENT_SHUTDOWN: { + mpv_terminate_destroy(mpv); + mpv = nullptr; + break; + } + + default:; + // Unhandled events + } +} + +void MediaMPV::sendCommand(const char *args[]) { + // mpv_command_async(mpv, 0, args); + mpv_command(mpv, args); +} + +void MediaMPV::setState(Media::State value) { + if (value != currentState) { + currentState = value; + emit stateChanged(currentState); + } +} + +void MediaMPV::clearTrackState() { + lastErrorString.clear(); + aboutToFinishEmitted = false; +} + +void MediaMPV::setAudioOnly(bool value) { + Q_UNUSED(value); +} + +#ifndef MEDIA_AUDIOONLY + +void MediaMPV::setRenderer(const QString &name) { + mpv_set_option_string(mpv, "vo", name.toUtf8().data()); +} + +QWidget *MediaMPV::videoWidget() { + if (!widget) { + widget = new MpvWidget(mpv); + } + return widget; +} + +void MediaMPV::playSeparateAudioAndVideo(const QString &video, const QString &audio) { + const QByteArray fileUtf8 = video.toUtf8(); + const char *args[] = {"loadfile", fileUtf8.constData(), nullptr}; + sendCommand(args); + + qApp->processEvents(); + + const QByteArray audioUtf8 = audio.toUtf8(); + const char *args2[] = {"audio-add", audioUtf8.constData(), nullptr}; + sendCommand(args2); + + clearTrackState(); +} + +void MediaMPV::snapshot() { + if (currentState == State::StoppedState) return; + + const QVariantList args = {"screenshot-raw", "video"}; + mpv::qt::node_builder nodeBuilder(args); + mpv_node node; + const int ret = mpv_command_node(mpv, nodeBuilder.node(), &node); + if (ret < 0) { + emit error("Cannot take snapshot"); + return; + } + + mpv::qt::node_autofree auto_free(&node); + if (node.format != MPV_FORMAT_NODE_MAP) { + emit error("Cannot take snapshot"); + return; + } + + int width = 0; + int height = 0; + int stride = 0; + mpv_node_list *list = node.u.list; + uchar *data = nullptr; + + for (int i = 0; i < list->num; ++i) { + const char *key = list->keys[i]; + if (strcmp(key, "w") == 0) { + width = static_cast(list->values[i].u.int64); + } else if (strcmp(key, "h") == 0) { + height = static_cast(list->values[i].u.int64); + } else if (strcmp(key, "stride") == 0) { + stride = static_cast(list->values[i].u.int64); + } else if (strcmp(key, "data") == 0) { + data = static_cast(list->values[i].u.ba->data); + } + } + + if (data != nullptr) { + QImage img = QImage(data, width, height, stride, QImage::Format_RGB32); + img.bits(); + emit snapshotReady(img); + } +} + +#endif + +void MediaMPV::init() {} + +Media::State MediaMPV::state() const { + return currentState; +} + +void MediaMPV::play(const QString &file) { + const QByteArray fileUtf8 = file.toUtf8(); + const char *args[] = {"loadfile", fileUtf8.constData(), nullptr}; + sendCommand(args); + + clearTrackState(); + if (currentState == Media::PausedState) play(); +} + +void MediaMPV::play() { + int flag = 0; + mpv_set_property_async(mpv, 0, "pause", MPV_FORMAT_FLAG, &flag); +} + +void MediaMPV::pause() { + int flag = 1; + mpv_set_property_async(mpv, 0, "pause", MPV_FORMAT_FLAG, &flag); +} + +void MediaMPV::stop() { + const char *args[] = {"stop", nullptr}; + sendCommand(args); +} + +void MediaMPV::seek(qint64 ms) { + double seconds = ms / 1000.; + QByteArray ba = QString::number(seconds).toUtf8(); + const char *args[] = {"seek", ba.constData(), "absolute", nullptr}; + sendCommand(args); +} + +QString MediaMPV::file() const { + char *path; + mpv_get_property(mpv, "path", MPV_FORMAT_STRING, &path); + return QString::fromUtf8(path); +} + +void MediaMPV::setBufferMilliseconds(qint64 value) { + Q_UNUSED(value); + // Not implemented +} + +void MediaMPV::setUserAgent(const QString &value) { + mpv_set_option_string(mpv, "user-agent", value.toUtf8()); +} + +void MediaMPV::enqueue(const QString &file) { + const QByteArray fileUtf8 = file.toUtf8(); + const char *args[] = {"loadfile", fileUtf8.constData(), "append", nullptr}; + sendCommand(args); +} + +void MediaMPV::clearQueue() { + const char *args[] = {"playlist-clear", nullptr}; + sendCommand(args); +} + +bool MediaMPV::hasQueue() const { + mpv_node node; + int r = mpv_get_property(mpv, "playlist", MPV_FORMAT_NODE, &node); + if (r < 0) return false; + QVariant v = mpv::qt::node_to_variant(&node); + mpv_free_node_contents(&node); + QVariantList list = v.toList(); + return list.count() > 1; +} + +qint64 MediaMPV::position() const { + double seconds; + mpv_get_property(mpv, "time-pos", MPV_FORMAT_DOUBLE, &seconds); + return seconds * 1000.; +} + +qint64 MediaMPV::duration() const { + double seconds; + mpv_get_property(mpv, "duration", MPV_FORMAT_DOUBLE, &seconds); + return seconds * 1000.; +} + +qint64 MediaMPV::remainingTime() const { + double seconds; + mpv_get_property(mpv, "time-remaining", MPV_FORMAT_DOUBLE, &seconds); + return seconds * 1000.; +} + +qreal MediaMPV::volume() const { + double vol; + mpv_get_property(mpv, "volume", MPV_FORMAT_DOUBLE, &vol); + return vol / 100.; +} + +void MediaMPV::setVolume(qreal value) { + double percent = value * 100.; + mpv_set_property_async(mpv, 0, "volume", MPV_FORMAT_DOUBLE, &percent); +} + +bool MediaMPV::volumeMuted() const { + int mute; + mpv_get_property(mpv, "mute", MPV_FORMAT_FLAG, &mute); + return mute == 1; +} + +void MediaMPV::setVolumeMuted(bool value) { + int mute = value ? 1 : 0; + mpv_set_property(mpv, "mute", MPV_FORMAT_FLAG, &mute); +} + +QString MediaMPV::errorString() const { + return lastErrorString; +} diff --git a/lib/media/src/mpv/mediampv.h b/lib/media/src/mpv/mediampv.h new file mode 100644 index 0000000..1621148 --- /dev/null +++ b/lib/media/src/mpv/mediampv.h @@ -0,0 +1,64 @@ +#ifndef MEDIAMPV_H +#define MEDIAMPV_H + +#include + +#include "media.h" +#include + +class MediaMPV : public Media { + Q_OBJECT + +public: + MediaMPV(QObject *parent = nullptr); + + void setAudioOnly(bool value); +#ifndef MEDIA_AUDIOONLY + void setRenderer(const QString &name); + QWidget *videoWidget(); + void playSeparateAudioAndVideo(const QString &video, const QString &audio); + void snapshot(); +#endif + void init(); + Media::State state() const; + void play(const QString &file); + void play(); + void pause(); + void stop(); + void seek(qint64 ms); + QString file() const; + void setBufferMilliseconds(qint64 value); + void setUserAgent(const QString &value); + void enqueue(const QString &file); + void clearQueue(); + bool hasQueue() const; + qint64 position() const; + qint64 duration() const; + qint64 remainingTime() const; + qreal volume() const; + void setVolume(qreal value); + bool volumeMuted() const; + void setVolumeMuted(bool value); + QString errorString() const; + +private slots: + void onMpvEvents(); + void checkAboutToFinish(qint64 position); + +signals: + void mpvEvents(); + +private: + void handleMpvEvent(mpv_event *event); + void sendCommand(const char *args[]); + void setState(Media::State value); + void clearTrackState(); + + QWidget *widget; + mpv_handle *mpv; + Media::State currentState = Media::StoppedState; + bool aboutToFinishEmitted = false; + QString lastErrorString; +}; + +#endif // MEDIAMPV_H diff --git a/lib/media/src/mpv/mpvwidget.cpp b/lib/media/src/mpv/mpvwidget.cpp new file mode 100644 index 0000000..335012d --- /dev/null +++ b/lib/media/src/mpv/mpvwidget.cpp @@ -0,0 +1,102 @@ +#include "mpvwidget.h" +#include + +#if defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN) +#include +#endif + +static void *get_proc_address(void *ctx, const char *name) { + Q_UNUSED(ctx); + QOpenGLContext *glctx = QOpenGLContext::currentContext(); + if (!glctx) return nullptr; + return reinterpret_cast(glctx->getProcAddress(QByteArray(name))); +} + +MpvWidget::MpvWidget(mpv_handle *mpv, QWidget *parent, Qt::WindowFlags f) + : QOpenGLWidget(parent, f), mpv(mpv), mpvContext(nullptr) { + moveToThread(qApp->thread()); +} + +MpvWidget::~MpvWidget() { + makeCurrent(); + if (mpvContext) mpv_render_context_free(mpvContext); + mpv_terminate_destroy(mpv); +} + +void MpvWidget::initializeGL() { + if (mpvContext) qFatal("Already initialized"); + + QWidget *nativeParent = nativeParentWidget(); + qDebug() << "initializeGL" << nativeParent; + if (nativeParent == nullptr) qFatal("No native parent"); + + mpv_opengl_init_params gl_init_params{get_proc_address, this, nullptr}; + mpv_render_param params[]{{MPV_RENDER_PARAM_API_TYPE, (void *)MPV_RENDER_API_TYPE_OPENGL}, + {MPV_RENDER_PARAM_OPENGL_INIT_PARAMS, &gl_init_params}, + {MPV_RENDER_PARAM_INVALID, nullptr}, + {MPV_RENDER_PARAM_INVALID, nullptr}}; + +#if defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN) + const QString platformName = QGuiApplication::platformName(); + if (platformName.contains("xcb")) { + params[2].type = MPV_RENDER_PARAM_X11_DISPLAY; + params[2].data = (void *)QX11Info::display(); + qDebug() << platformName << params[2].data; + } else if (platformName.contains("wayland")) { + qWarning() << "Wayland not supported"; + } +#endif + + if (mpv_render_context_create(&mpvContext, mpv, params) < 0) + qFatal("failed to initialize mpv GL context"); + mpv_render_context_set_update_callback(mpvContext, MpvWidget::onUpdate, (void *)this); + + connect(this, &QOpenGLWidget::frameSwapped, this, &MpvWidget::onFrameSwapped); +} + +void MpvWidget::resizeGL(int w, int h) { + qreal r = devicePixelRatioF(); + glWidth = int(w * r); + glHeight = int(h * r); +} + +void MpvWidget::paintGL() { + mpv_opengl_fbo fbo{static_cast(defaultFramebufferObject()), glWidth, glHeight, 0}; + + static bool yes = true; + mpv_render_param params[] = {{MPV_RENDER_PARAM_OPENGL_FBO, &fbo}, + {MPV_RENDER_PARAM_FLIP_Y, &yes}, + {MPV_RENDER_PARAM_INVALID, nullptr}}; + // See render_gl.h on what OpenGL environment mpv expects, and + // other API details. + mpv_render_context_render(mpvContext, params); +} + +// Make Qt invoke mpv_opengl_cb_draw() to draw a new/updated video frame. +void MpvWidget::maybeUpdate() { + // If the Qt window is not visible, Qt's update() will just skip rendering. + // This confuses mpv's opengl-cb API, and may lead to small occasional + // freezes due to video rendering timing out. + // Handle this by manually redrawing. + // Note: Qt doesn't seem to provide a way to query whether update() will + // be skipped, and the following code still fails when e.g. switching + // to a different workspace with a reparenting window manager. + if (!updatesEnabled() || isHidden() || window()->isHidden() || window()->isMinimized()) { + makeCurrent(); + paintGL(); + QOpenGLContext *c = context(); + c->swapBuffers(c->surface()); + doneCurrent(); + mpv_render_context_report_swap(mpvContext); + } else { + update(); + } +} + +void MpvWidget::onFrameSwapped() { + mpv_render_context_report_swap(mpvContext); +} + +void MpvWidget::onUpdate(void *ctx) { + QMetaObject::invokeMethod((MpvWidget *)ctx, "maybeUpdate"); +} diff --git a/lib/media/src/mpv/mpvwidget.h b/lib/media/src/mpv/mpvwidget.h new file mode 100644 index 0000000..c647311 --- /dev/null +++ b/lib/media/src/mpv/mpvwidget.h @@ -0,0 +1,36 @@ +#ifndef PLAYERWINDOW_H +#define PLAYERWINDOW_H + +#include +#include +#include +#include + +class MpvWidget Q_DECL_FINAL : public QOpenGLWidget { + Q_OBJECT + +public: + MpvWidget(mpv_handle *mpv, QWidget *parent = nullptr, Qt::WindowFlags f = nullptr); + ~MpvWidget() Q_DECL_OVERRIDE; + + QSize sizeHint() const Q_DECL_OVERRIDE { return QSize(480, 270); } + +protected: + void initializeGL() Q_DECL_OVERRIDE; + void resizeGL(int w, int h) Q_DECL_OVERRIDE; + void paintGL() Q_DECL_OVERRIDE; + +private slots: + void maybeUpdate(); + void onFrameSwapped(); + +private: + static void onUpdate(void *ctx); + + mpv_handle *mpv; + mpv_render_context *mpvContext; + int glWidth; + int glHeight; +}; + +#endif // PLAYERWINDOW_H diff --git a/lib/media/src/qtav/mediaqtav.cpp b/lib/media/src/qtav/mediaqtav.cpp new file mode 100644 index 0000000..c5a095b --- /dev/null +++ b/lib/media/src/qtav/mediaqtav.cpp @@ -0,0 +1,369 @@ +#include "mediaqtav.h" + +namespace { + +#if defined(Q_OS_ANDROID) || defined(Q_OS_MAC) +const QtAV::VideoRendererId defaultRenderer = QtAV::VideoRendererId_OpenGLWidget; +#else +const QtAV::VideoRendererId defaultRenderer = QtAV::VideoRendererId_GLWidget2; +#endif + +#ifndef MEDIA_AUDIOONLY +QtAV::VideoRendererId rendererIdFor(const QString &name) { + static const struct { + const char *name; + QtAV::VideoRendererId id; + } renderers[] = {{"opengl", QtAV::VideoRendererId_OpenGLWidget}, + {"gl", QtAV::VideoRendererId_GLWidget2}, + {"d2d", QtAV::VideoRendererId_Direct2D}, + {"gdi", QtAV::VideoRendererId_GDI}, + {"xv", QtAV::VideoRendererId_XV}, + {"x11", QtAV::VideoRendererId_X11}, + {"qt", QtAV::VideoRendererId_Widget}}; + + for (int i = 0; renderers[i].name; ++i) { + if (name == QLatin1String(renderers[i].name)) return renderers[i].id; + } + + return defaultRenderer; +} +#endif + +Media::State stateFor(QtAV::AVPlayer::State state) { + static const QMap map{ + {QtAV::AVPlayer::StoppedState, Media::StoppedState}, + {QtAV::AVPlayer::PlayingState, Media::PlayingState}, + {QtAV::AVPlayer::PausedState, Media::PausedState}}; + return map[state]; +} + +} // namespace + +MediaQtAV::MediaQtAV(QObject *parent) + : Media(parent), player1(nullptr), player2(nullptr), currentPlayer(nullptr) { +#ifndef MEDIA_AUDIOONLY + rendererId = defaultRenderer; +#endif +} + +#ifndef MEDIA_AUDIOONLY +void MediaQtAV::setRenderer(const QString &name) { + rendererId = rendererIdFor(name); +} +#endif + +void MediaQtAV::setAudioOnly(bool value) { + audioOnly = value; +} + +void MediaQtAV::init() { +#ifndef QT_NO_DEBUG_OUTPUT + QtAV::setLogLevel(QtAV::LogAll); +#endif + +#ifndef MEDIA_AUDIOONLY + QtAV::Widgets::registerRenderers(); +#endif + player1 = createPlayer(audioOnly); + setCurrentPlayer(player1); +} + +#ifndef MEDIA_AUDIOONLY +QWidget *MediaQtAV::videoWidget() { + return currentPlayer->renderer()->widget(); +} +#endif + +Media::State MediaQtAV::state() const { + if (currentPlayer->mediaStatus() == QtAV::LoadingMedia) return LoadingState; + if (currentPlayer->bufferProgress() > 0. && currentPlayer->bufferProgress() < 1.) + return BufferingState; + return stateFor(currentPlayer->state()); +} + +void MediaQtAV::play(const QString &file) { + if (currentPlayer->isPlaying()) { + smoothSourceChange(file, QString()); + return; + } + +#ifndef MEDIA_AUDIOONLY + if (!currentPlayer->externalAudio().isEmpty()) currentPlayer->setExternalAudio(QString()); +#endif + currentPlayer->play(file); + aboutToFinishEmitted = false; + lastErrorString.clear(); + clearQueue(); +} + +#ifndef MEDIA_AUDIOONLY +void MediaQtAV::playSeparateAudioAndVideo(const QString &video, const QString &audio) { + if (currentPlayer->isPlaying()) { + smoothSourceChange(video, audio); + return; + } + currentPlayer->stop(); + currentPlayer->setExternalAudio(audio); + currentPlayer->play(video); + aboutToFinishEmitted = false; + lastErrorString.clear(); + clearQueue(); +} + +void MediaQtAV::snapshot() { + auto videoCapture = currentPlayer->videoCapture(); + connect(videoCapture, &QtAV::VideoCapture::imageCaptured, this, &Media::snapshotReady); + connect(videoCapture, &QtAV::VideoCapture::failed, this, + [this] { emit snapshotReady(QImage()); }); + videoCapture->capture(); +} +#endif + +void MediaQtAV::play() { + if (currentPlayer->isPaused()) + currentPlayer->togglePause(); + else + currentPlayer->play(); +} + +QString MediaQtAV::file() const { + return currentPlayer->file(); +} + +void MediaQtAV::setBufferMilliseconds(qint64 value) { + currentPlayer->setBufferValue(value); +} + +void MediaQtAV::setUserAgent(const QString &value) { + qDebug() << "Setting user agent to" << value; + auto options = currentPlayer->optionsForFormat(); + options.insert(QStringLiteral("user_agent"), value); + currentPlayer->setOptionsForFormat(options); +} + +void MediaQtAV::enqueue(const QString &file) { + queue << file; + if (queue.size() == 1) { + qDebug() << "Preloading" << file; + auto nextPlayer = player1; + if (currentPlayer == player1) { + if (player2 == nullptr) player2 = createPlayer(audioOnly); + nextPlayer = player2; + } + nextPlayer->setFile(file); + nextPlayer->load(); + } +} + +void MediaQtAV::clearQueue() { + queue.clear(); +} + +bool MediaQtAV::hasQueue() const { + return !queue.isEmpty(); +} + +qint64 MediaQtAV::position() const { + return currentPlayer->position(); +} + +qint64 MediaQtAV::duration() const { + return currentPlayer->duration(); +} + +qint64 MediaQtAV::remainingTime() const { + return currentPlayer->duration() - currentPlayer->position(); +} + +qreal MediaQtAV::volume() const { + return currentPlayer->audio()->volume(); +} + +void MediaQtAV::setVolume(qreal value) { + auto audio = currentPlayer->audio(); + if (!audio->isOpen()) audio->open(); + audio->setVolume(value); +} + +bool MediaQtAV::volumeMuted() const { + return currentPlayer->audio()->isMute(); +} + +void MediaQtAV::setVolumeMuted(bool value) { + currentPlayer->audio()->setMute(value); +} + +QString MediaQtAV::errorString() const { + return lastErrorString; +} + +void MediaQtAV::checkAboutToFinish(qint64 position) { + if (!aboutToFinishEmitted && currentPlayer->isPlaying() && + duration() - position < currentPlayer->bufferValue()) { + aboutToFinishEmitted = true; + emit aboutToFinish(); + } +} + +void MediaQtAV::onMediaStatusChange(QtAV::MediaStatus status) { + qDebug() << QVariant(status).toString(); + + switch (status) { + case QtAV::LoadingMedia: + emit stateChanged(LoadingState); + break; + case QtAV::BufferedMedia: + if (currentPlayer->state() == QtAV::AVPlayer::PlayingState) emit stateChanged(PlayingState); + break; + case QtAV::BufferingMedia: + emit stateChanged(BufferingState); + break; + case QtAV::EndOfMedia: + if (queue.isEmpty()) + emit finished(); + else { + auto nextPlayer = currentPlayer == player1 ? player2 : player1; + if (nextPlayer->isLoaded()) { + qDebug() << "Preloaded"; + setCurrentPlayer(nextPlayer); + nextPlayer->play(); + aboutToFinishEmitted = false; + lastErrorString.clear(); + emit sourceChanged(); + queue.dequeue(); + } else { + qDebug() << "Not preloaded"; + currentPlayer->play(queue.dequeue()); + } + } + break; + case QtAV::InvalidMedia: + emit stateChanged(Media::ErrorState); + break; + default: + qDebug() << "Unhandled" << QVariant(status).toString(); + } +} + +void MediaQtAV::onAVError(const QtAV::AVError &e) { + lastErrorString = e.string(); + qDebug() << lastErrorString; + emit error(lastErrorString); +} + +void MediaQtAV::pause() { + currentPlayer->pause(); +} + +void MediaQtAV::stop() { + currentPlayer->stop(); +} + +void MediaQtAV::seek(qint64 ms) { + currentPlayer->setPosition(ms); +} + +QtAV::AVPlayer *MediaQtAV::createPlayer(bool audioOnly) { + QtAV::AVPlayer *p = new QtAV::AVPlayer(this); + +#ifndef MEDIA_AUDIOONLY + if (!audioOnly) { + if (currentPlayer) { + p->setRenderer(currentPlayer->renderer()); + p->setVideoDecoderPriority(currentPlayer->videoDecoderPriority()); + } else { + QtAV::VideoRenderer *renderer = QtAV::VideoRenderer::create(rendererId); + if (!renderer || !renderer->isAvailable() || !renderer->widget()) { + qFatal("No QtAV video renderer"); + } + p->setRenderer(renderer); + p->setVideoDecoderPriority( + {"CUDA", "D3D11", "DXVA", "VAAPI", "VideoToolbox", "FFmpeg"}); + } + } +#endif + + p->setBufferMode(QtAV::BufferTime); + + if (currentPlayer) { + p->setBufferValue(currentPlayer->bufferValue()); + p->setOptionsForFormat(currentPlayer->optionsForFormat()); + } + + return p; +} + +void MediaQtAV::connectPlayer(QtAV::AVPlayer *player) { + connect(player, &QtAV::AVPlayer::error, this, &MediaQtAV::onAVError); + + connect(player, &QtAV::AVPlayer::sourceChanged, this, &Media::sourceChanged); + connect(player, &QtAV::AVPlayer::sourceChanged, this, [this] { aboutToFinishEmitted = false; }); + + connect(player, &QtAV::AVPlayer::bufferProgressChanged, this, &Media::bufferStatus); + connect(player, &QtAV::AVPlayer::started, this, &Media::started); + connect(player, &QtAV::AVPlayer::stopped, this, &Media::stopped); + connect(player, &QtAV::AVPlayer::paused, this, &Media::paused); + + connect(player, &QtAV::AVPlayer::positionChanged, this, &Media::positionChanged); + connect(player, &QtAV::AVPlayer::positionChanged, this, &MediaQtAV::checkAboutToFinish); + + connect(player, &QtAV::AVPlayer::stateChanged, this, [this](QtAV::AVPlayer::State state) { + const State s = stateFor(state); + if (s != PlayingState) { + emit stateChanged(s); + } else if (currentPlayer->mediaStatus() == QtAV::BufferedMedia) { + // needed when resuming from pause + emit stateChanged(s); + } + }); + connect(player, &QtAV::AVPlayer::mediaStatusChanged, this, &MediaQtAV::onMediaStatusChange); + + connect(player->audio(), &QtAV::AudioOutput::volumeChanged, this, &Media::volumeChanged); + connect(player->audio(), &QtAV::AudioOutput::muteChanged, this, &Media::volumeMutedChanged); +} + +void MediaQtAV::setCurrentPlayer(QtAV::AVPlayer *player) { + if (currentPlayer) { + currentPlayer->disconnect(this); + player->audio()->setVolume(currentPlayer->audio()->volume()); + player->audio()->setMute(currentPlayer->audio()->isMute()); + player->setBufferValue(currentPlayer->bufferValue()); + } + currentPlayer = player; + connectPlayer(currentPlayer); +} + +void MediaQtAV::smoothSourceChange(const QString &file, const QString &externalAudio) { + qDebug() << "smoothSourceChange"; + auto nextPlayer = player1; + if (currentPlayer == player1) { + if (player2 == nullptr) player2 = createPlayer(audioOnly); + nextPlayer = player2; + } + QObject *context = new QObject(); + connect(nextPlayer, &QtAV::AVPlayer::loaded, context, [this, nextPlayer, context] { + qDebug() << "smoothSourceChange preloaded"; + setCurrentPlayer(nextPlayer); + + aboutToFinishEmitted = false; + lastErrorString.clear(); + clearQueue(); + emit sourceChanged(); + context->deleteLater(); + + QObject *context2 = new QObject(); + connect(nextPlayer, &QtAV::AVPlayer::mediaStatusChanged, context2, + [this, context2](QtAV::MediaStatus mediaStatus) { + if (mediaStatus == QtAV::BufferedMedia) { + qDebug() << "smoothSourceChange playing"; + auto oldPlayer = currentPlayer == player1 ? player2 : player1; + oldPlayer->stop(); + context2->deleteLater(); + } + }); + currentPlayer->play(); + }); + nextPlayer->setExternalAudio(externalAudio); + nextPlayer->setFile(file); + nextPlayer->load(); +} diff --git a/lib/media/src/qtav/mediaqtav.h b/lib/media/src/qtav/mediaqtav.h new file mode 100644 index 0000000..d343790 --- /dev/null +++ b/lib/media/src/qtav/mediaqtav.h @@ -0,0 +1,79 @@ +#ifndef MEDIAQTAV_H +#define MEDIAQTAV_H + +#include "media.h" + +#include +#ifndef MEDIA_AUDIOONLY +#include +#include +#endif + +class MediaQtAV : public Media { + Q_OBJECT + +public: + MediaQtAV(QObject *parent = nullptr); +#ifndef MEDIA_AUDIOONLY + void setRenderer(const QString &name); + QWidget *videoWidget(); + void playSeparateAudioAndVideo(const QString &video, const QString &audio); + void snapshot(); +#endif + void setAudioOnly(bool value); + void init(); + + Media::State state() const; + + void play(const QString &file); + void play(); + void pause(); + void stop(); + void seek(qint64 ms); + QString file() const; + + void setBufferMilliseconds(qint64 value); + void setUserAgent(const QString &value); + + void enqueue(const QString &file); + void clearQueue(); + bool hasQueue() const; + + qint64 position() const; + qint64 duration() const; + qint64 remainingTime() const; + + qreal volume() const; + void setVolume(qreal value); + + bool volumeMuted() const; + void setVolumeMuted(bool value); + + QString errorString() const; + +private slots: + void checkAboutToFinish(qint64 position); + void onMediaStatusChange(QtAV::MediaStatus status); + void onAVError(const QtAV::AVError &e); + +private: + QtAV::AVPlayer *createPlayer(bool audioOnly); + void connectPlayer(QtAV::AVPlayer *player); + void setCurrentPlayer(QtAV::AVPlayer *player); + void smoothSourceChange(const QString &file, const QString &externalAudio); + + QtAV::AVPlayer *player1; + QtAV::AVPlayer *player2; + QtAV::AVPlayer *currentPlayer; + + QQueue queue; + bool aboutToFinishEmitted = false; + QString lastErrorString; + +#ifndef MEDIA_AUDIOONLY + QtAV::VideoRendererId rendererId; +#endif + bool audioOnly = false; +}; + +#endif // MEDIAQTAV_H diff --git a/locale/ar.ts b/locale/ar.ts index 7964978..fc2a950 100644 --- a/locale/ar.ts +++ b/locale/ar.ts @@ -25,6 +25,14 @@ Translate %1 to your native language using %2 ترجم %1 إلى لغتك الأم بإستعمال %2 + + Powered by %1 + + + + Open-source software + + Icon designed by %1. صمم الايقونة %1. @@ -155,6 +163,10 @@ Show Updated أظهر التّحديثات + + You have no subscriptions. Use the star symbol to subscribe to channels. + لا يوجد لديك اشتراكات. استخدم رمز النجمة للإشتراك في القنوات. + All Videos جميع الفيديوهات @@ -175,17 +187,6 @@ There are no updated subscriptions at this time. لا يوجد تحديثات لقوائم اشتراكاتك حاليًا - - You have no subscriptions. Use the star symbol to subscribe to channels. - لا يوجد لديك اشتراكات. استخدم رمز النجمة للإشتراك في القنوات. - - - - ClearButton - - Clear - مسح - DataUtils @@ -202,11 +203,30 @@ - %n weeks(s) ago + %n month(s) ago + + K + K as in Kilo, i.e. thousands + + + + M + M stands for Millions + + + + B + B stands for Billions + + + + %1 views + %1 مشاهدة + - %n month(s) ago + %n week(s) ago @@ -251,22 +271,6 @@ DownloadManager - - This is just the demo version of %1. - هذه ليست سوى النسخة التجريبية من %1. - - - It can only download videos shorter than %1 minutes so you can test the download functionality. - يمكن تحميل الفيديو في أقل من %1 دقيقة بحيث يمكنك اختبار وظيفة التحميل. - - - Continue - متابعة - - - Get the full version - احصل على النسخة الكاملة - %1 downloaded in %2 %1 حمل في %2 @@ -632,10 +636,6 @@ &Float on Top &اطفو علي القمة - - &Adjust Window Size - &ضبط حجم النافذة - &Stop After This Video &أوقف العرض التلقائي بعد الفيديو الحالي @@ -672,6 +672,14 @@ Hide videos that may contain inappropriate content + + Toggle &Menu Bar + + + + Menu + + &Love %1? Rate it! أت&حبّ %1؟ قيّمه! @@ -796,6 +804,10 @@ Update تحديث + + You can still access the menu bar by pressing the ALT key + + MediaView @@ -811,22 +823,6 @@ The link will be valid only for a limited time. الرابط سيكون صالحا لمدة محدودة. - - This is just the demo version of %1. - هذه ليست سوى النسخة التجريبية من %1. - - - It allows you to test the application and see if it works for you. - انها تتيح لك تجربة البرنامج. - - - Get the full version - احصل على النسخة الكاملة - - - Continue - متابعة - Downloading %1 جاري تحميل %1 @@ -858,6 +854,10 @@ Subscribe to %1 اشترك في %1 + + Switched to %1 + + Unsubscribed from %1 تم إلغاء متابعتك ل %1 @@ -902,11 +902,14 @@ - PlaylistItemDelegate + PickMessage - %1 views - %1 مشاهدة + Pick a video + + + + PlaylistItemDelegate %1 of %2 (%3) — %4 1% من 2% (3%) — 4% @@ -946,10 +949,6 @@ PlaylistModel - - Searching... - جاري البحث... - Show %1 More اظهر %1 المزيد @@ -1071,25 +1070,16 @@ مرحبا بك في <a href='%1'>%2</a>, - Enter - "Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos" - أدخل + to start watching videos. + لبدء مشاهدة أشرطة الفيديو a keyword كلمة مفتاح - a channel - قناة - - - to start watching videos. - لبدء مشاهدة أشرطة الفيديو - - - Watch - شاهد + Enter + أدخل Recent keywords @@ -1110,6 +1100,10 @@ &Back &عودة + + &Forward + + Forward to %1 تقدّم إلى %1 @@ -1163,13 +1157,6 @@ - - Video - - Cannot get video stream for %1 - لا يمكن الحصول على دفق الفيديو %1 - - YTRegions @@ -1365,4 +1352,11 @@ العالم + + YTVideo + + Cannot get video stream for %1 + لا يمكن الحصول على دفق الفيديو %1 + + \ No newline at end of file diff --git a/locale/ast.ts b/locale/ast.ts index 2a18063..bf7f858 100644 --- a/locale/ast.ts +++ b/locale/ast.ts @@ -25,6 +25,14 @@ Translate %1 to your native language using %2 Traduz %1 a la to llingua nativa usando %2 + + Powered by %1 + + + + Open-source software + + Icon designed by %1. Iconu diseñáu por %1. @@ -155,6 +163,10 @@ Show Updated + + You have no subscriptions. Use the star symbol to subscribe to channels. + + All Videos @@ -175,17 +187,6 @@ There are no updated subscriptions at this time. - - You have no subscriptions. Use the star symbol to subscribe to channels. - - - - - ClearButton - - Clear - Llimpiar - DataUtils @@ -202,11 +203,30 @@ - %n weeks(s) ago + %n month(s) ago + + K + K as in Kilo, i.e. thousands + + + + M + M stands for Millions + + + + B + B stands for Billions + + + + %1 views + %1 reproducciones + - %n month(s) ago + %n week(s) ago @@ -251,22 +271,6 @@ DownloadManager - - This is just the demo version of %1. - Esto ye namái la versión demo de %1. - - - It can only download videos shorter than %1 minutes so you can test the download functionality. - Namái pues descargar vídeos de duración menor que %1 minutos pa que puedas probar la función de descarga. - - - Continue - Siguir - - - Get the full version - Consigui la versión completa - %1 downloaded in %2 %1 descargáu en %2 @@ -632,10 +636,6 @@ &Float on Top &Flotar na parte superior - - &Adjust Window Size - - &Stop After This Video &Detener tres d'esti videu @@ -672,6 +672,14 @@ Hide videos that may contain inappropriate content + + Toggle &Menu Bar + + + + Menu + + &Love %1? Rate it! ¿&Préstate %1? ¡Puntúalo! @@ -796,6 +804,10 @@ Update Anovar + + You can still access the menu bar by pressing the ALT key + + MediaView @@ -811,22 +823,6 @@ The link will be valid only for a limited time. Esto ye namái la versión de prueba de %1. - - This is just the demo version of %1. - Esto ye namái la versión demo de %1. - - - It allows you to test the application and see if it works for you. - Déxate probar l'aplicación y ver si te funciona. - - - Get the full version - Consigui la versión completa - - - Continue - Siguir - Downloading %1 Descargando %1 @@ -858,6 +854,10 @@ Subscribe to %1 + + Switched to %1 + + Unsubscribed from %1 @@ -902,11 +902,14 @@ - PlaylistItemDelegate + PickMessage - %1 views - %1 reproducciones + Pick a video + + + + PlaylistItemDelegate %1 of %2 (%3) — %4 %1 de %2 (%3) — %4 @@ -946,10 +949,6 @@ PlaylistModel - - Searching... - Guetando... - Show %1 More Amosar %1 más @@ -1071,25 +1070,16 @@ Bienveníu a <a href='%1'>%2</a>, - Enter - "Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos" - Escribi + to start watching videos. + pa entamar a ver vídeos. a keyword una pallabra clave - a channel - una canal - - - to start watching videos. - pa entamar a ver vídeos. - - - Watch - ver + Enter + Escribi Recent keywords @@ -1110,6 +1100,10 @@ &Back &Atrás + + &Forward + + Forward to %1 Dir a %1 @@ -1163,13 +1157,6 @@ - - Video - - Cannot get video stream for %1 - Nun pue obtenese'l fluxu de videu pa %1 - - YTRegions @@ -1365,4 +1352,11 @@ Tol mundu + + YTVideo + + Cannot get video stream for %1 + Nun pue obtenese'l fluxu de videu pa %1 + + \ No newline at end of file diff --git a/locale/be.ts b/locale/be.ts index 64ed283..a887a34 100644 --- a/locale/be.ts +++ b/locale/be.ts @@ -25,6 +25,14 @@ Translate %1 to your native language using %2 Перакладайце %1 на родную мову праз %2 + + Powered by %1 + + + + Open-source software + + Icon designed by %1. Дызайн іконкі выканаў %1. @@ -155,6 +163,10 @@ Show Updated Паказаць абноўленыя + + You have no subscriptions. Use the star symbol to subscribe to channels. + Няма жаднай падпіскі. Ужывайце зорачку, каб падпісвацца на каналы. + All Videos Усе відэа @@ -175,17 +187,6 @@ There are no updated subscriptions at this time. Падпіскі пакуль не абнаўляліся. - - You have no subscriptions. Use the star symbol to subscribe to channels. - Няма жаднай падпіскі. Ужывайце зорачку, каб падпісвацца на каналы. - - - - ClearButton - - Clear - Ачысціць - DataUtils @@ -202,11 +203,30 @@ - %n weeks(s) ago + %n month(s) ago + + K + K as in Kilo, i.e. thousands + + + + M + M stands for Millions + + + + B + B stands for Billions + + + + %1 views + %1 праглядаў + - %n month(s) ago + %n week(s) ago @@ -251,22 +271,6 @@ DownloadManager - - This is just the demo version of %1. - Гэта дэма-версія %1. - - - It can only download videos shorter than %1 minutes so you can test the download functionality. - Можна пампаваць толькі відэа, каротшыя за %1 хв., што дастаткова для тэсціравання. - - - Continue - Працягнуць - - - Get the full version - Атрымаць поўную версію - %1 downloaded in %2 %1 сцягнута ў %2 @@ -632,10 +636,6 @@ &Float on Top &Заставацца па-над усімі вокнамі - - &Adjust Window Size - - &Stop After This Video &Спыніцца пасля гэтага відэа @@ -672,6 +672,14 @@ Hide videos that may contain inappropriate content + + Toggle &Menu Bar + + + + Menu + + &Love %1? Rate it! &Падабаецца %1? Ацані яго! @@ -796,6 +804,10 @@ Update Абнаўленне + + You can still access the menu bar by pressing the ALT key + + MediaView @@ -811,22 +823,6 @@ The link will be valid only for a limited time. Спасылка будзе заставацца слушнай абмежаваны час. - - This is just the demo version of %1. - Гэта дэма-версія %1. - - - It allows you to test the application and see if it works for you. - Яна дазваляе пратэсціраваць праграму і праверыць на адпаведнасць вашым задачам. - - - Get the full version - Атрымаць поўную версію - - - Continue - Працягнуць - Downloading %1 Сцягваецца %1 @@ -858,6 +854,10 @@ Subscribe to %1 Падпісацца на %1 + + Switched to %1 + + Unsubscribed from %1 @@ -902,11 +902,14 @@ - PlaylistItemDelegate + PickMessage - %1 views - %1 праглядаў + Pick a video + + + + PlaylistItemDelegate %1 of %2 (%3) — %4 %1 з %2 (%3) — %4 @@ -946,10 +949,6 @@ PlaylistModel - - Searching... - Пошук... - Show %1 More Паказаць яшчэ %1 @@ -1071,25 +1070,16 @@ Ласкава запрашаем у <a href='%1'>%2</a> - Enter - "Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos" - Задайце + to start watching videos. + , каб пачаць глядзець відэа. a keyword ключавое слова - a channel - канал - - - to start watching videos. - , каб пачаць глядзець відэа. - - - Watch - Глядзець + Enter + Задайце Recent keywords @@ -1110,6 +1100,10 @@ &Back &Назад + + &Forward + + Forward to %1 Наперад да %1 @@ -1163,13 +1157,6 @@ - - Video - - Cannot get video stream for %1 - Не ўдалося атрымаць відэапаток для %1 - - YTRegions @@ -1365,4 +1352,11 @@ Увесь свет + + YTVideo + + Cannot get video stream for %1 + Не ўдалося атрымаць відэа-паток для %1 + + \ No newline at end of file diff --git a/locale/bg_BG.ts b/locale/bg_BG.ts index 7fe65e0..1a9264f 100644 --- a/locale/bg_BG.ts +++ b/locale/bg_BG.ts @@ -25,6 +25,14 @@ Translate %1 to your native language using %2 Преведи &1 на твоя роден език използвайки &2 + + Powered by %1 + + + + Open-source software + + Icon designed by %1. Иконите са изработени от %1. @@ -155,6 +163,10 @@ Show Updated Покажи обновени + + You have no subscriptions. Use the star symbol to subscribe to channels. + Нямате абонаменти. Изполвай звезда за абониране към канали + All Videos Всички видео клипове @@ -175,17 +187,6 @@ There are no updated subscriptions at this time. Няма обновени абонаменти - - You have no subscriptions. Use the star symbol to subscribe to channels. - Нямате абонаменти. Изполвай звезда за абониране към канали - - - - ClearButton - - Clear - Изчисти - DataUtils @@ -202,11 +203,30 @@ - %n weeks(s) ago + %n month(s) ago + + K + K as in Kilo, i.e. thousands + + + + M + M stands for Millions + + + + B + B stands for Billions + + + + %1 views + %1 гледания + - %n month(s) ago + %n week(s) ago @@ -251,22 +271,6 @@ DownloadManager - - This is just the demo version of %1. - Това е демо верция на %1 - - - It can only download videos shorter than %1 minutes so you can test the download functionality. - Може да изтегляте видео клипове по-къси от &1 минута, за да изпробвате функцията за изтегляне. - - - Continue - Продължи - - - Get the full version - Пълна версия - %1 downloaded in %2 %1 изтеглено за %2 @@ -632,10 +636,6 @@ &Float on Top &Залепи най отгоре - - &Adjust Window Size - - &Stop After This Video &Спри след този видео клип @@ -672,6 +672,14 @@ Hide videos that may contain inappropriate content + + Toggle &Menu Bar + + + + Menu + + &Love %1? Rate it! @@ -796,6 +804,10 @@ Update Обнови + + You can still access the menu bar by pressing the ALT key + + MediaView @@ -811,22 +823,6 @@ The link will be valid only for a limited time. Линка ще е валиден само за определено време. - - This is just the demo version of %1. - Това е демо верция на %1 - - - It allows you to test the application and see if it works for you. - Позволява ви да изпробвате програмата, за да проверите дали работи добре при вас. - - - Get the full version - Пълна версия - - - Continue - Продължи - Downloading %1 изтеглане %1 @@ -858,6 +854,10 @@ Subscribe to %1 Абонирай се за %1 + + Switched to %1 + + Unsubscribed from %1 @@ -902,11 +902,14 @@ - PlaylistItemDelegate + PickMessage - %1 views - %1 гледания + Pick a video + + + + PlaylistItemDelegate %1 of %2 (%3) — %4 %1 от %2 (%3) — %4 @@ -946,10 +949,6 @@ PlaylistModel - - Searching... - Търся... - Show %1 More Покажи %1 повече @@ -1071,25 +1070,16 @@ Добре дошли в <a href='%1'>%2</a>, - Enter - "Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos" - Напишете + to start watching videos. + за гледане на видео клипове a keyword ключова дума - a channel - канал - - - to start watching videos. - за гледане на видео клипове - - - Watch - Гледай + Enter + Напишете Recent keywords @@ -1110,6 +1100,10 @@ &Back &Назад + + &Forward + + Forward to %1 Напред до %1 @@ -1163,13 +1157,6 @@ - - Video - - Cannot get video stream for %1 - - - YTRegions @@ -1365,4 +1352,11 @@ На световно ниво + + YTVideo + + Cannot get video stream for %1 + + + \ No newline at end of file diff --git a/locale/ca.ts b/locale/ca.ts index 1f8fade..f3d56f3 100644 --- a/locale/ca.ts +++ b/locale/ca.ts @@ -25,6 +25,14 @@ Translate %1 to your native language using %2 Traduïu el %1 al vostre idioma natal utilitzant %2 + + Powered by %1 + Creat per %1 + + + Open-source software + Programari de codi obert + Icon designed by %1. Icona dissenyada per %1. @@ -107,7 +115,7 @@ You have %n new video(s) - + Teniu %n vídeo(s) nou(s)Teniu %n vídeos nous @@ -155,6 +163,10 @@ Show Updated Mostra actualitzats + + You have no subscriptions. Use the star symbol to subscribe to channels. + No teniu subscripcions. Feu servir el símbol de l'estrella per subscríure-us als canals. + All Videos Tots els vídeos @@ -175,17 +187,6 @@ There are no updated subscriptions at this time. No hi han subscripcions actualitzades. - - You have no subscriptions. Use the star symbol to subscribe to channels. - No teniu subscripcions. Feu servir el símbol de l'estrella per subscríure-us als canals. - - - - ClearButton - - Clear - Neteja - DataUtils @@ -195,19 +196,38 @@ %n hour(s) ago - + fa %n horesfa %n hores %n day(s) ago - + fa %n diesfa %n dies - %n weeks(s) ago - + %n month(s) ago + fa %n mesosfa %n mesos + + + K + K as in Kilo, i.e. thousands + + + + M + M stands for Millions + + + + B + B stands for Billions + + + + %1 views + %1 visualitzacions - %n month(s) ago - + %n week(s) ago + fa %n setmanesfa %n setmanes @@ -251,22 +271,6 @@ DownloadManager - - This is just the demo version of %1. - Aquesta només és la versió de demostració del %1. - - - It can only download videos shorter than %1 minutes so you can test the download functionality. - Només pot baixar vídeos de menys de %1 minuts per tal que en pugui provar aquesta funció. - - - Continue - Continua - - - Get the full version - Aconsegueix la versió completa - %1 downloaded in %2 %1 descarregat en %2 @@ -277,7 +281,7 @@ %n Download(s) - + %n baixades%n baixades @@ -632,10 +636,6 @@ &Float on Top Manté a &sobre - - &Adjust Window Size - &Ajusta la mida de la finestra - &Stop After This Video &Atura després d'aquest vídeo @@ -666,11 +666,19 @@ Restricted Mode - + Mode restringit Hide videos that may contain inappropriate content - + Amaga vídeos amb contingut no apropiat + + + Toggle &Menu Bar + A&maga la barra de menú + + + Menu + Menú &Love %1? Rate it! @@ -796,6 +804,10 @@ Update Actualitza + + You can still access the menu bar by pressing the ALT key + Encara podreu accedir a les opcions del menú mitjançant la tecla ALT + MediaView @@ -811,22 +823,6 @@ The link will be valid only for a limited time. L'enllaç només serà vàlid durant un temps limitat. - - This is just the demo version of %1. - Aquesta només és la versió de demostració del %1. - - - It allows you to test the application and see if it works for you. - Us permet probar l'aplicació i veure si us va bé. - - - Get the full version - Aconseguiu la versió completa - - - Continue - Continua - Downloading %1 Baixant %1 @@ -858,6 +854,10 @@ Subscribe to %1 Subscriu-me a %1 + + Switched to %1 + S'ha canviat a %1 + Unsubscribed from %1 De-subscrit de %1 @@ -902,11 +902,14 @@ - PlaylistItemDelegate + PickMessage - %1 views - %1 visualitzacions + Pick a video + Trieu un vídeo + + + PlaylistItemDelegate %1 of %2 (%3) — %4 %1 de %2 (%3) — %4 @@ -946,10 +949,6 @@ PlaylistModel - - Searching... - Cercant... - Show %1 More Mostra %1 Més @@ -1071,25 +1070,16 @@ Benvinguts al <a href='%1'>%2</a>, - Enter - "Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos" - Introdueix + to start watching videos. + per comencar a veure vídeos. a keyword una paraula - a channel - un canal - - - to start watching videos. - per comencar a veure vídeos. - - - Watch - Veure + Enter + Introdueix Recent keywords @@ -1110,6 +1100,10 @@ &Back &Enrere + + &Forward + E&ndavant + Forward to %1 Avança a %1 @@ -1163,13 +1157,6 @@ Descarregant %1... - - Video - - Cannot get video stream for %1 - No es pot obtenir flux de vídeo per %1 - - YTRegions @@ -1365,4 +1352,11 @@ Global + + YTVideo + + Cannot get video stream for %1 + No es pot obtenir flux de vídeo per %1 + + \ No newline at end of file diff --git a/locale/ca_ES.ts b/locale/ca_ES.ts index 11f3677..300c0f8 100644 --- a/locale/ca_ES.ts +++ b/locale/ca_ES.ts @@ -25,6 +25,14 @@ Translate %1 to your native language using %2 Traduïu el %1 al vostre idioma natal utilitzant %2 + + Powered by %1 + + + + Open-source software + + Icon designed by %1. Icona dissenyada per %1. @@ -155,6 +163,10 @@ Show Updated Mostra actualitzats + + You have no subscriptions. Use the star symbol to subscribe to channels. + No teniu subscripcions. Feu servir el símbol de l'estrella per subscríure-us als canals. + All Videos Tots els vídeos @@ -175,17 +187,6 @@ There are no updated subscriptions at this time. No hi han subscripcions actualitzades. - - You have no subscriptions. Use the star symbol to subscribe to channels. - No teniu subscripcions. Feu servir el símbol de l'estrella per subscríure-us als canals. - - - - ClearButton - - Clear - Neteja - DataUtils @@ -202,11 +203,30 @@ - %n weeks(s) ago + %n month(s) ago + + K + K as in Kilo, i.e. thousands + + + + M + M stands for Millions + + + + B + B stands for Billions + + + + %1 views + %1 visualitzacions + - %n month(s) ago + %n week(s) ago @@ -251,22 +271,6 @@ DownloadManager - - This is just the demo version of %1. - Aquesta només és la versió de demostració del %1. - - - It can only download videos shorter than %1 minutes so you can test the download functionality. - Només pot baixar vídeos de menys de %1 minuts per tal que en pugui provar aquesta funció. - - - Continue - Continua - - - Get the full version - Aconsegueix la versió completa - %1 downloaded in %2 %1 descarregat en %2 @@ -632,10 +636,6 @@ &Float on Top Manté a &sobre - - &Adjust Window Size - - &Stop After This Video &Atura després d'aquest vídeo @@ -672,6 +672,14 @@ Hide videos that may contain inappropriate content + + Toggle &Menu Bar + + + + Menu + + &Love %1? Rate it! T'&Agrada %1? Puntua'l! @@ -796,6 +804,10 @@ Update Actualitza + + You can still access the menu bar by pressing the ALT key + + MediaView @@ -811,22 +823,6 @@ The link will be valid only for a limited time. L'enllaç només serà vàlid durant un temps limitat. - - This is just the demo version of %1. - Aquesta només és la versió de demostració del %1. - - - It allows you to test the application and see if it works for you. - Us permet probar l'aplicació i veure si us va bé. - - - Get the full version - Aconseguiu la versió completa - - - Continue - Continua - Downloading %1 Baixant %1 @@ -858,6 +854,10 @@ Subscribe to %1 Subscriu-me a %1 + + Switched to %1 + + Unsubscribed from %1 @@ -902,11 +902,14 @@ - PlaylistItemDelegate + PickMessage - %1 views - %1 visualitzacions + Pick a video + + + + PlaylistItemDelegate %1 of %2 (%3) — %4 %1 de %2 (%3) — %4 @@ -946,10 +949,6 @@ PlaylistModel - - Searching... - Cercant... - Show %1 More Mostra %1 Més @@ -1071,25 +1070,16 @@ Benvinguts al <a href='%1'>%2</a>, - Enter - "Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos" - Introdueix + to start watching videos. + per comencar a veure vídeos. a keyword una paraula - a channel - un canal - - - to start watching videos. - per comencar a veure vídeos. - - - Watch - Veure + Enter + Introdueix Recent keywords @@ -1110,6 +1100,10 @@ &Back &Enrere + + &Forward + + Forward to %1 Avança a %1 @@ -1163,13 +1157,6 @@ Descarregant %1... - - Video - - Cannot get video stream for %1 - No es pot obtenir flux de vídeo per %1 - - YTRegions @@ -1365,4 +1352,11 @@ Global + + YTVideo + + Cannot get video stream for %1 + No es pot obtenir flux de vídeo per %1 + + \ No newline at end of file diff --git a/locale/cs_CZ.ts b/locale/cs_CZ.ts index 3af0d90..816e0d2 100644 --- a/locale/cs_CZ.ts +++ b/locale/cs_CZ.ts @@ -25,6 +25,14 @@ Translate %1 to your native language using %2 Přeložte %1 do vaÅ¡eho mateřského jazyka pomocí %2 + + Powered by %1 + + + + Open-source software + + Icon designed by %1. Autor ikony: %1. @@ -107,7 +115,7 @@ You have %n new video(s) - + @@ -155,6 +163,10 @@ Show Updated Zobrazit aktualizace + + You have no subscriptions. Use the star symbol to subscribe to channels. + Nemáte žádné odběry. Použijte hvězdičku k přihlásení odběru kanálů. + All Videos VÅ¡echna videa @@ -175,17 +187,6 @@ There are no updated subscriptions at this time. Nyní nejsou k dispozici žádné aktualizace odběrů. - - You have no subscriptions. Use the star symbol to subscribe to channels. - Nemáte žádné odběry. Použijte hvězdičku k přihlásení odběru kanálů. - - - - ClearButton - - Clear - Odstranit vÅ¡e - DataUtils @@ -195,19 +196,38 @@ %n hour(s) ago - + %n day(s) ago - + - %n weeks(s) ago - + %n month(s) ago + + + + K + K as in Kilo, i.e. thousands + + + + M + M stands for Millions + + + + B + B stands for Billions + + + + %1 views + %1 zobrazení - %n month(s) ago - + %n week(s) ago + @@ -251,22 +271,6 @@ DownloadManager - - This is just the demo version of %1. - Toto je pouze demoverze %1. - - - It can only download videos shorter than %1 minutes so you can test the download functionality. - Umí stahovat pouze videa délky do %1 minut, abyste mohli funkci stahování vyzkouÅ¡et - - - Continue - Pokračovat - - - Get the full version - Získat plnou verzi - %1 downloaded in %2 %1 staženo v %2 @@ -277,7 +281,7 @@ %n Download(s) - + @@ -632,10 +636,6 @@ &Float on Top &Plovoucí navrchu - - &Adjust Window Size - &Přizpůsobit velikost okna - &Stop After This Video &Zastavit po tomto videu @@ -672,6 +672,14 @@ Hide videos that may contain inappropriate content Skrýt videa, která by mohla obsahovat nevhodný obsah + + Toggle &Menu Bar + Přepnout pruh s &nabídkou + + + Menu + Nabídka + &Love %1? Rate it! &Líbí se %1? Ohodnotit! @@ -796,6 +804,10 @@ Update Aktualizovat + + You can still access the menu bar by pressing the ALT key + Stále jeÅ¡tě můžete přistupovat k pruhu s nabídkou stisknutím klávesy Alt + MediaView @@ -811,22 +823,6 @@ The link will be valid only for a limited time. Tento odkaz platí jen po omezenou dobu. - - This is just the demo version of %1. - Toto je %1 -- demoverze. - - - It allows you to test the application and see if it works for you. - Umožňuje vyzkouÅ¡et aplikaci, abyste ověřili, jestli pro vás funguje. - - - Get the full version - Stáhnout plnou verzi - - - Continue - Pokračovat - Downloading %1 Je stahováno %1 @@ -858,6 +854,10 @@ Subscribe to %1 Přihlásit k %1 + + Switched to %1 + + Unsubscribed from %1 Odhlášen z odběru %1 @@ -902,11 +902,14 @@ - PlaylistItemDelegate + PickMessage - %1 views - %1 zobrazení + Pick a video + + + + PlaylistItemDelegate %1 of %2 (%3) — %4 %1 z %2 (%3) — %4 @@ -946,10 +949,6 @@ PlaylistModel - - Searching... - Hledá se... - Show %1 More Zobrazit dalších %1 @@ -1071,25 +1070,16 @@ Vítejte v <a href='%1'>%2</a> - Enter - "Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos" - Vložit + to start watching videos. + pro sledování videí. a keyword klíčové slovo - a channel - kanál - - - to start watching videos. - pro sledování videí. - - - Watch - Sledovat + Enter + Vložit Recent keywords @@ -1110,6 +1100,10 @@ &Back &Zpět + + &Forward + + Forward to %1 Předat k %1 @@ -1163,13 +1157,6 @@ Stahování %1... - - Video - - Cannot get video stream for %1 - Nelze získat video stream pro %1 - - YTRegions @@ -1365,4 +1352,11 @@ Celosvětově + + YTVideo + + Cannot get video stream for %1 + Nelze získat video stream pro %1 + + \ No newline at end of file diff --git a/locale/da.ts b/locale/da.ts index e336189..db5efdc 100644 --- a/locale/da.ts +++ b/locale/da.ts @@ -25,6 +25,14 @@ Translate %1 to your native language using %2 Oversæt %1 til din modersmÃ¥l ved hjælp af %2 + + Powered by %1 + + + + Open-source software + + Icon designed by %1. Ikon designet af %1. @@ -96,7 +104,7 @@ AppWidget Download - + Hent @@ -155,6 +163,10 @@ Show Updated Show opdateret + + You have no subscriptions. Use the star symbol to subscribe to channels. + Du har ingen abonnementer. Brug stjernetegnet til at abonnere pÃ¥ kanaler. + All Videos Alle videoer @@ -175,17 +187,6 @@ There are no updated subscriptions at this time. Der er ingen opdateringer i de abonnerede. - - You have no subscriptions. Use the star symbol to subscribe to channels. - Du har ingen abonnementer. Brug stjernetegnet til at abonnere pÃ¥ kanaler. - - - - ClearButton - - Clear - Fjern - DataUtils @@ -202,11 +203,30 @@ - %n weeks(s) ago + %n month(s) ago + + K + K as in Kilo, i.e. thousands + + + + M + M stands for Millions + + + + B + B stands for Billions + + + + %1 views + %1 visninger + - %n month(s) ago + %n week(s) ago @@ -251,22 +271,6 @@ DownloadManager - - This is just the demo version of %1. - Dette er kun demoversionen af %1. - - - It can only download videos shorter than %1 minutes so you can test the download functionality. - Det kan kun hente videoer kortere end %1 minut, sÃ¥ du kan teste downloadfunktionaliteten. - - - Continue - Forsæt - - - Get the full version - Hent den fulde version - %1 downloaded in %2 %1 downloaded pÃ¥ %2 @@ -633,10 +637,6 @@ Kopiér &URL'en til videostrømmen &Float on Top &Behold øverst - - &Adjust Window Size - &Juster vinduesstørrelse - &Stop After This Video &Stop efter denne video @@ -667,10 +667,18 @@ Kopiér &URL'en til videostrømmen Restricted Mode - + Begrænset-MÃ¥de Hide videos that may contain inappropriate content + Gem videoer der muligvis indeholder stødene indhold. + + + Toggle &Menu Bar + + + + Menu @@ -797,6 +805,10 @@ Kopiér &URL'en til videostrømmen Update Opdatér + + You can still access the menu bar by pressing the ALT key + + MediaView @@ -812,22 +824,6 @@ Kopiér &URL'en til videostrømmen The link will be valid only for a limited time. Linket vil kun være gyldigt i en begrænset periode. - - This is just the demo version of %1. - Dette er kun demoversionen af %1. - - - It allows you to test the application and see if it works for you. - Det giver dig mulighed for at teste programmet og se om det virker for dig. - - - Get the full version - Hent den fulde version - - - Continue - Forsæt - Downloading %1 Downloader %1 @@ -859,6 +855,10 @@ Kopiér &URL'en til videostrømmen Subscribe to %1 Abonner pÃ¥ %1 + + Switched to %1 + + Unsubscribed from %1 Abonnerer ikke længere pÃ¥ %1 @@ -903,11 +903,14 @@ Kopiér &URL'en til videostrømmen - PlaylistItemDelegate + PickMessage - %1 views - %1 visninger + Pick a video + + + + PlaylistItemDelegate %1 of %2 (%3) — %4 %1 af %2 (%3) — %4 @@ -947,10 +950,6 @@ Kopiér &URL'en til videostrømmen PlaylistModel - - Searching... - Søger... - Show %1 More Vis %1 mere @@ -1072,25 +1071,16 @@ Kopiér &URL'en til videostrømmen Velkommen til <a href='%1'>%2</a>, - Enter - "Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos" - Indtast + to start watching videos. + for at begynde at se video. a keyword et nøgleord - a channel - en kanal - - - to start watching videos. - for at begynde at se video. - - - Watch - Afspil + Enter + Indtast Recent keywords @@ -1111,6 +1101,10 @@ Kopiér &URL'en til videostrømmen &Back &Tilbage + + &Forward + + Forward to %1 Frem til %1 @@ -1161,14 +1155,7 @@ Kopiér &URL'en til videostrømmen Downloading %1... - - - - - Video - - Cannot get video stream for %1 - Kan ikke hente videostrøm for %1 + Henter %1... @@ -1366,4 +1353,11 @@ Kopiér &URL'en til videostrømmen Hele verden + + YTVideo + + Cannot get video stream for %1 + Kan ikke hente videostrøm for %1 + + \ No newline at end of file diff --git a/locale/de_DE.ts b/locale/de_DE.ts index 6802af7..ec338db 100644 --- a/locale/de_DE.ts +++ b/locale/de_DE.ts @@ -25,6 +25,14 @@ Translate %1 to your native language using %2 Übersetzen Sie %1 in Ihre Muttersprache mit %2 + + Powered by %1 + + + + Open-source software + + Icon designed by %1. Icons wurden gestaltet von %1. @@ -155,6 +163,10 @@ Show Updated Zeige aktualisierte + + You have no subscriptions. Use the star symbol to subscribe to channels. + Du hast keine Abonnements. Benutze das Stern-Symbol, um einen Kanal zu abonnieren. + All Videos Alle Videos @@ -175,17 +187,6 @@ There are no updated subscriptions at this time. Zurzeit gibt es nichts Neues bei den Abonnements. - - You have no subscriptions. Use the star symbol to subscribe to channels. - Du hast keine Abonnements. Benutze das Stern-Symbol, um einen Kanal zu abonnieren. - - - - ClearButton - - Clear - Säubern - DataUtils @@ -202,11 +203,30 @@ - %n weeks(s) ago + %n month(s) ago + + K + K as in Kilo, i.e. thousands + + + + M + M stands for Millions + + + + B + B stands for Billions + + + + %1 views + %1 mal betrachtet + - %n month(s) ago + %n week(s) ago @@ -251,22 +271,6 @@ DownloadManager - - This is just the demo version of %1. - Dies ist nur die Demoversion von %1. - - - It can only download videos shorter than %1 minutes so you can test the download functionality. - Sie kann nur Videos herunterladen, die kürzer als %1 Minuten sind, um die Funktion zum Herunterladen zu testen. - - - Continue - Fortfahren - - - Get the full version - Die Vollversion kaufen - %1 downloaded in %2 %1 heruntergeladen nach %2 @@ -632,10 +636,6 @@ &Float on Top Im Vordergrund &bleiben - - &Adjust Window Size - &Fenstergröße anpassen - &Stop After This Video Nach diesem Video &anhalten @@ -672,6 +672,14 @@ Hide videos that may contain inappropriate content Verstecke Videos die unpassende Inhalte enthalten können + + Toggle &Menu Bar + + + + Menu + + &Love %1? Rate it! &Gefällt Ihnen %1? Bewertung abgeben! @@ -796,6 +804,10 @@ Update Aktualisierung + + You can still access the menu bar by pressing the ALT key + + MediaView @@ -811,22 +823,6 @@ The link will be valid only for a limited time. Der Link wird nur eine beschränkte Zeit gültig sein. - - This is just the demo version of %1. - Dies ist nur die Demoversion von %1. - - - It allows you to test the application and see if it works for you. - Sie erlaubt es Ihnen, die Anwendung zu testen und zu schauen, ob sie bei Ihnen läuft. - - - Get the full version - Die Vollversion kaufen - - - Continue - Fortfahren - Downloading %1 %1 herunterladen @@ -858,6 +854,10 @@ Subscribe to %1 Abonnieren von %1 + + Switched to %1 + + Unsubscribed from %1 Abonnement von %1 wurde aufgehoben. @@ -902,11 +902,14 @@ - PlaylistItemDelegate + PickMessage - %1 views - %1 mal betrachtet + Pick a video + + + + PlaylistItemDelegate %1 of %2 (%3) — %4 %1 von %2 (%3) – %4 @@ -946,10 +949,6 @@ PlaylistModel - - Searching... - Suche... - Show %1 More Weitere %1 zeigen @@ -1071,25 +1070,16 @@ Willkommen bei <a href='%1'>%2</a>, - Enter - "Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos" - Eingeben + to start watching videos. + um die Wiedergabe zu starten. a keyword ein Suchbegriff - a channel - ein Kanal - - - to start watching videos. - um die Wiedergabe zu starten. - - - Watch - Anschauen + Enter + Eingeben Recent keywords @@ -1110,6 +1100,10 @@ &Back &Zurück + + &Forward + + Forward to %1 Weiter zu %1 @@ -1163,13 +1157,6 @@ heruntergeladen %1 - - Video - - Cannot get video stream for %1 - Videostream für %1 konnte nicht geöffnet werden - - YTRegions @@ -1365,4 +1352,11 @@ Weltweit + + YTVideo + + Cannot get video stream for %1 + Videostream für %1 konnte nicht geöffnet werden + + \ No newline at end of file diff --git a/locale/el.ts b/locale/el.ts index 8ecd850..330b6c0 100644 --- a/locale/el.ts +++ b/locale/el.ts @@ -25,6 +25,14 @@ Translate %1 to your native language using %2 Μεταφράστε το %1 στη γλώσσα σας με χρήση του %2 + + Powered by %1 + + + + Open-source software + + Icon designed by %1. Σχεδιασμός εικονιδίου από %1. @@ -155,6 +163,10 @@ Show Updated Εμφάνιση ενημερωμένων + + You have no subscriptions. Use the star symbol to subscribe to channels. + Δεν έχετε συνδρομές. Χρησιμοποιήστε το αστέρι για να κάνετε συνδρομή σε κανάλια. + All Videos Όλα τα βίντεο @@ -175,17 +187,6 @@ There are no updated subscriptions at this time. Δεν υπάρχουν ενημερωμένες συδρομές αυτήν την στιγμή. - - You have no subscriptions. Use the star symbol to subscribe to channels. - Δεν έχετε συνδρομές. Χρησιμοποιήστε το αστέρι για να κάνετε συνδρομή σε κανάλια. - - - - ClearButton - - Clear - Εκκαθάριση - DataUtils @@ -202,11 +203,30 @@ - %n weeks(s) ago + %n month(s) ago + + K + K as in Kilo, i.e. thousands + + + + M + M stands for Millions + + + + B + B stands for Billions + + + + %1 views + %1 προβολές + - %n month(s) ago + %n week(s) ago @@ -251,22 +271,6 @@ DownloadManager - - This is just the demo version of %1. - Αυτή είναι απλά η δοκιμαστική έκδοση του %1. - - - It can only download videos shorter than %1 minutes so you can test the download functionality. - Μπορεί να κάνει λήψη βίντεο μικρότερα από %1 λεπτά ώστε να δοκιμάσετε τη λειτουργία λήψης. - - - Continue - Συνέχεια - - - Get the full version - Αποκτήστε την πλήρη έκδοση - %1 downloaded in %2 %1 λήφθηκε σε %2 @@ -632,10 +636,6 @@ &Float on Top &Διατήρηση στην κορυφή - - &Adjust Window Size - &Προσαρμογή του μεγέθους του παραθύρου - &Stop After This Video &Διακοπή μετά από αυτό το βίντεο @@ -672,6 +672,14 @@ Hide videos that may contain inappropriate content + + Toggle &Menu Bar + + + + Menu + + &Love %1? Rate it! &Λατρεύετε το %1; Βαθμολογήστε το! @@ -796,6 +804,10 @@ Update Ενημέρωση + + You can still access the menu bar by pressing the ALT key + + MediaView @@ -811,22 +823,6 @@ The link will be valid only for a limited time. Ο σύνδεσμος θα είναι έγκυρος για περιορισμένο χρονικό διάστημα. - - This is just the demo version of %1. - Αυτή είναι απλά μια δοκιμαστική έκδοση του %1. - - - It allows you to test the application and see if it works for you. - Σαε επιτρέπει να δοκιμάσετε την εφαρμογή και να δείτε αν σας κάνει. - - - Get the full version - Αποκτήστε τη πλήρη έκδοση - - - Continue - Συνέχεια - Downloading %1 Λήψη %1 @@ -858,6 +854,10 @@ Subscribe to %1 Εγγραφή στο %1 + + Switched to %1 + + Unsubscribed from %1 Διεγράφη από το %1 @@ -902,11 +902,14 @@ - PlaylistItemDelegate + PickMessage - %1 views - %1 προβολές + Pick a video + + + + PlaylistItemDelegate %1 of %2 (%3) — %4 %1 από %2 (%3) — %4 @@ -946,10 +949,6 @@ PlaylistModel - - Searching... - Αναζήτηση... - Show %1 More Εμφάνιση %1 ακόμα @@ -1071,25 +1070,16 @@ Καλωσορίσατε στο <a href='%1'>%2</a>, - Enter - "Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos" - Εισάγετε + to start watching videos. + για να αρχίσετε να βλέπετε βίντεο. a keyword μια λέξη-κλειδί - a channel - ένα κανάλι - - - to start watching videos. - για να αρχίσετε να βλέπετε βίντεο. - - - Watch - Παρακολουθήστε + Enter + Εισάγετε Recent keywords @@ -1110,6 +1100,10 @@ &Back &Επιστροφή + + &Forward + + Forward to %1 Προώθηση σε %1 @@ -1163,13 +1157,6 @@ Λήψη %1... - - Video - - Cannot get video stream for %1 - Αδυναμία λήψης της ροής βίντεο για το %1 - - YTRegions @@ -1365,4 +1352,11 @@ Παγκοσμίως + + YTVideo + + Cannot get video stream for %1 + Αδυναμία λήψης της ροής βίντεο για το %1 + + \ No newline at end of file diff --git a/locale/en.ts b/locale/en.ts index 9a66576..9e662e3 100644 --- a/locale/en.ts +++ b/locale/en.ts @@ -7,7 +7,7 @@ You have %n new video(s) - You have one new video + You have a new video You have %n new videos @@ -61,7 +61,7 @@ %n Download(s) - One Download + 1 Download %n Downloads diff --git a/locale/en_GB.ts b/locale/en_GB.ts new file mode 100644 index 0000000..f029b05 --- /dev/null +++ b/locale/en_GB.ts @@ -0,0 +1,1362 @@ + + + AboutView + + There's life outside the browser! + There's life outside the browser! + + + Version %1 + Version %1 + + + Licensed to: %1 + Licensed to: %1 + + + %1 is Free Software but its development takes precious time. + %1 is Free Software but its development takes precious time. + + + Please <a href='%1'>donate</a> to support the continued development of %2. + Please <a href='%1'>donate</a> to support the continued development of %2. + + + Translate %1 to your native language using %2 + Translate %1 to your native language using %2 + + + Powered by %1 + Powered by %1 + + + Open-source software + Open-source software + + + Icon designed by %1. + Icon designed by %1. + + + Released under the <a href='%1'>GNU General Public License</a> + Released under the <a href='%1'>GNU General Public License</a> + + + &Close + &Close + + + About + About + + + + ActivationDialog + + Enter your License Details + Enter your Licence Details + + + &Email: + &Email: + + + &Code: + &Code: + + + + ActivationView + + Please license %1 + Please license %1 + + + This demo has expired. + This demo has expired. + + + The full version allows you to watch videos without interruptions. + The full version allows you to watch videos without interruptions. + + + Without a license, the application will expire in %1 days. + Without a licence, the application will expire in %1 days. + + + By purchasing the full version, you will also support the hard work I put into creating %1. + By purchasing the full version, you will also support the hard work I put into creating %1. + + + Use Demo + Use Demo + + + Enter License + Enter Licence + + + Buy License + Buy Licence + + + + AppWidget + + Download + Download + + + + ChannelAggregator + + By %1 + By %1 + + + You have %n new video(s) + You have a new videoYou have %n new videos + + + + ChannelItemDelegate + + All Videos + All Videos + + + Unwatched Videos + Unwatched Videos + + + + ChannelView + + Name + Name + + + Last Updated + Last Updated + + + Last Added + Last Added + + + Last Watched + Last Watched + + + Most Watched + Most Watched + + + Sort by + Sort by + + + Mark all as watched + Mark all as watched + + + Show Updated + Show Updated + + + You have no subscriptions. Use the star symbol to subscribe to channels. + You have no subscriptions. Use the star symbol to subscribe to channels. + + + All Videos + All Videos + + + Unwatched Videos + Unwatched Videos + + + Mark as Watched + Mark as Watched + + + Unsubscribe + Unsubscribe + + + There are no updated subscriptions at this time. + There are no updated subscriptions at this time. + + + + DataUtils + + Just now + Just now + + + %n hour(s) ago + An hour ago%n hours ago + + + %n day(s) ago + Yesterday%n days ago + + + %n month(s) ago + A month ago%n month(s) ago + + + K + K as in Kilo, i.e. thousands + K + + + M + M stands for Millions + M + + + B + B stands for Billions + B + + + %1 views + %1 views + + + %n week(s) ago + A week ago%n weeks ago + + + + DownloadItem + + bytes + bytes + + + KB + KB + + + MB + MB + + + bytes/sec + bytes/sec + + + KB/sec + KB/sec + + + MB/sec + MB/sec + + + seconds + seconds + + + minutes + minutes + + + %4 %5 remaining + %4 %5 remaining + + + + DownloadManager + + %1 downloaded in %2 + %1 downloaded in %2 + + + Download finished + Download finished + + + %n Download(s) + 1 Download%n Downloads + + + + DownloadSettings + + Change location... + Change location... + + + Choose the download location + Choose the download location + + + Download location changed. + Download location changed. + + + Current downloads will still go in the previous location. + Current downloads will still go into the previous location. + + + Downloading to: %1 + Downloading to: %1 + + + + DownloadView + + Downloads + Downloads + + + + Extra + + The executable file has been tempered with, maybe by a virus. + The executable file has been tampered with, maybe by a virus. + + + %1 will not run. Try installing again. + %1 will not run. Try installing again. + + + Quit + Quit + + + Reinstall + Reinstall + + + + GlobalShortcuts + + Play + Play + + + Pause + Pause + + + Play/Pause + Play/Pause + + + Stop + Stop + + + Stop playing after current track + Stop playing after current track + + + Next track + Next track + + + Previous track + Previous track + + + Increase volume + Increase volume + + + Decrease volume + Decrease volume + + + Mute + Mute + + + Seek forward + Seek forward + + + Seek backward + Seek backward + + + + HomeView + + Search + Search + + + Find videos and channels by keyword + Find videos and channels by keyword + + + Browse + Browse + + + Browse videos by category + Browse videos by category + + + Subscriptions + Subscriptions + + + Channel subscriptions + Channel subscriptions + + + Make yourself comfortable + Make yourself comfortable + + + + LoadingWidget + + Error + Error + + + + MainWindow + + &Window + &Window + + + &Minimize + &Minimise + + + &Stop + &Stop + + + Stop playback and go back to the search view + Stop playback and go back to the search view + + + P&revious + &Previous + + + Go back to the previous track + Go back to the previous track + + + S&kip + &Skip + + + Skip to the next video + Skip to the next video + + + &Play + &Play + + + Resume playback + Resume playback + + + &Full Screen + &Full Screen + + + Go full screen + Go full screen + + + &Compact Mode + &Compact Mode + + + Hide the playlist and the toolbar + Hide the playlist and the toolbar + + + Open the &YouTube Page + Open the &YouTube Page + + + Go to the YouTube video page and pause playback + Go to the YouTube video page and pause playback + + + Copy the YouTube &Link + Copy the YouTube &Link + + + Copy the current video YouTube link to the clipboard + Copy the current video YouTube link to the clipboard + + + Copy the Video Stream &URL + Copy the Video Stream &URL + + + Copy the current video stream URL to the clipboard + Copy the current video stream URL to the clipboard + + + Find Video &Parts + Find Video &Parts + + + Find other video parts hopefully in the right order + Find other video parts hopefully in the right order + + + &Remove + &Remove + + + Remove the selected videos from the playlist + Remove the selected videos from the playlist + + + Move &Up + Move &Up + + + Move up the selected videos in the playlist + Move up the selected videos in the playlist + + + Move &Down + Move &Down + + + Move down the selected videos in the playlist + Move down the selected videos in the playlist + + + &Clear Recent Searches + &Clear Recent Searches + + + Clear the search history. Cannot be undone. + Clear the search history. Cannot be undone. + + + &Quit + &Quit + + + Bye + Bye + + + &Website + &Website + + + %1 on the Web + %1 on the Web + + + Make a &Donation + Make a &Donation + + + Please support the continued development of %1 + Please support the continued development of %1 + + + &About + &About + + + Info about %1 + Info about %1 + + + Search + Search + + + Mute volume + Mute volume + + + &Manually Start Playing + &Manually Start Playing + + + Manually start playing videos + Manually start playing videos + + + &Downloads + &Downloads + + + Show details about video downloads + Show details about video downloads + + + &Download + &Download + + + Download the current video + Download the current video + + + Take &Snapshot + Take &Snapshot + + + &Subscribe to Channel + &Subscribe to Channel + + + Share the current video using %1 + Share the current video using %1 + + + &Email + &Email + + + Email + Email + + + &Close + &Close + + + &Float on Top + &Float on Top + + + &Stop After This Video + &Stop After This Video + + + &Report an Issue... + &Report an Issue... + + + &Refine Search... + &Refine Search... + + + More... + More... + + + &Related Videos + &Related Videos + + + Watch videos related to the current one + Watch videos related to the current one + + + Open in &Browser... + Open in &Browser... + + + Restricted Mode + Restricted Mode + + + Hide videos that may contain inappropriate content + Hide videos that may contain inappropriate content + + + Toggle &Menu Bar + Toggle &Menu Bar + + + Menu + Menu + + + &Love %1? Rate it! + &Love %1? Rate it! + + + Buy %1... + Buy %1... + + + &Application + &Application + + + &Playback + &Playback + + + &Playlist + &Playlist + + + &Video + &Video + + + &Share + &Share + + + &View + &View + + + &Help + &Help + + + Press %1 to raise the volume, %2 to lower it + Press %1 to raise the volume, %2 to lower it + + + Choose your content location + Choose your content location + + + Opening %1 + Opening %1 + + + Do you want to exit %1 with a download in progress? + Do you want to exit %1 with a download in progress? + + + If you close %1 now, this download will be cancelled. + If you close %1 now, this download will be cancelled. + + + Close and cancel download + Close and cancel download + + + Wait for download to finish + Wait for download to finish + + + Error: %1 + Error: %1 + + + &Pause + &Pause + + + Pause playback + Pause playback + + + &Loading... + &Loading... + + + Leave &Full Screen + Leave &Full Screen + + + Remaining time: %1 + Remaining time: %1 + + + Volume at %1% + Volume at %1% + + + Volume is muted + Volume is muted + + + Volume is unmuted + Volume is unmuted + + + Maximum video definition set to %1 + Maximum video definition set to %1 + + + Your privacy is now safe + Your privacy is now safe + + + Downloads complete + Downloads complete + + + %1 version %2 is now available. + %1 version %2 is now available. + + + Remind me later + Remind me later + + + Update + Update + + + You can still access the menu bar by pressing the ALT key + You can still access the menu bar by pressing the ALT key + + + + MediaView + + You can now paste the YouTube link into another application + You can now paste the YouTube link into another application + + + You can now paste the video stream URL into another application + You can now paste the video stream URL into another application + + + The link will be valid only for a limited time. + The link will be valid for a limited time only. + + + Downloading %1 + Downloading %1 + + + of + Used in video parts, as in '2 of 3' + of + + + part + This is for video parts, as in 'Cool video - part 1' + part + + + episode + This is for video parts, as in 'Cool series - episode 1' + episode + + + Sent from %1 + Sent from %1 + + + Unsubscribe from %1 + Unsubscribe from %1 + + + Subscribe to %1 + Subscribe to %1 + + + Switched to %1 + Switched to %1 + + + Unsubscribed from %1 + Unsubscribed from %1 + + + Subscribed to %1 + Subscribed to %1 + + + + MessageWidget + + A new version of %1 is available! + A new version of %1 is available! + + + %1 %2 is now available. You have %3. + %1 %2 is now available. You have %3. + + + Would you like to download it now? + Would you like to download it now? + + + Skip This Version + Skip This Version + + + Remind Me Later + Remind Me Later + + + Install Update + Install Update + + + + PasteLineEdit + + Paste + Paste + + + + PickMessage + + Pick a video + Pick a video + + + + PlaylistItemDelegate + + %1 of %2 (%3) — %4 + %1 of %2 (%3) — %4 + + + Preparing + Preparing + + + Failed + Failed + + + Completed + Completed + + + Stopped + Stopped + + + Stop downloading + Stop downloading + + + Show in %1 + Show in %1 + + + Open parent folder + Open parent folder + + + Restart downloading + Restart downloading + + + + PlaylistModel + + Show %1 More + Show %1 More + + + No videos + No videos + + + No more videos + No more videos + + + + RefineSearchWidget + + Sort by + Sort by + + + Relevance + Relevance + + + Date + Date + + + View Count + View Count + + + Rating + Rating + + + Anytime + Any time + + + Today + Today + + + 7 Days + 7 Days + + + 30 Days + 30 Days + + + Duration + Duration + + + All + All + + + Short + Short + + + Medium + Medium + + + Long + Long + + + Less than 4 minutes + Less than 4 minutes + + + Between 4 and 20 minutes + Between 4 and 20 minutes + + + Longer than 20 minutes + Longer than 20 minutes + + + Quality + Quality + + + High Definition + High Definition + + + 720p or higher + 720p or higher + + + Done + Done + + + + RegionsView + + Done + Done + + + + SearchLineEdit + + Search + Search + + + + SearchView + + Welcome to <a href='%1'>%2</a>, + Welcome to <a href='%1'>%2</a>, + + + to start watching videos. + to start watching videos. + + + a keyword + a keyword + + + Enter + Enter + + + Recent keywords + Recent keywords + + + Recent channels + Recent channels + + + Get the full version + Get the full version + + + + SidebarHeader + + &Back + &Back + + + &Forward + &Forward + + + Forward to %1 + Forward to %1 + + + Back to %1 + Back to %1 + + + + SidebarWidget + + Refine Search + Refine Search + + + Did you mean: %1 + Did you mean: %1? + + + + SnapshotSettings + + Change location... + Change location... + + + Snapshot saved to %1 + Snapshot saved to %1 + + + Snapshots location changed. + Snapshot location changed. + + + + StandardFeedsView + + Most Popular + Most Popular + + + + UpdateDialog + + Downloading update... + Downloading update... + + + Downloading %1... + Downloading %1... + + + + YTRegions + + Algeria + Algeria + + + Argentina + Argentina + + + Australia + Australia + + + Belgium + Belgium + + + Brazil + Brazil + + + Canada + Canada + + + Chile + Chile + + + Colombia + Colombia + + + Czech Republic + Czech Republic + + + Egypt + Egypt + + + France + France + + + Germany + Germany + + + Ghana + Ghana + + + Greece + Greece + + + Hong Kong + Hong Kong + + + Hungary + Hungary + + + India + India + + + Indonesia + Indonesia + + + Ireland + Ireland + + + Israel + Israel + + + Italy + Italy + + + Japan + Japan + + + Jordan + Jordan + + + Kenya + Kenya + + + Malaysia + Malaysia + + + Mexico + Mexico + + + Morocco + Morocco + + + Netherlands + Netherlands + + + New Zealand + New Zealand + + + Nigeria + Nigeria + + + Peru + Peru + + + Philippines + Philippines + + + Poland + Poland + + + Russia + Russia + + + Saudi Arabia + Saudi Arabia + + + Singapore + Singapore + + + South Africa + South Africa + + + South Korea + South Korea + + + Spain + Spain + + + Sweden + Sweden + + + Taiwan + Taiwan + + + Tunisia + Tunisia + + + Turkey + Turkey + + + Uganda + Uganda + + + United Arab Emirates + United Arab Emirates + + + United Kingdom + United Kingdom + + + Yemen + Yemen + + + Worldwide + World Wide + + + + YTVideo + + Cannot get video stream for %1 + Cannot retrieve video stream for %1 + + + \ No newline at end of file diff --git a/locale/es.ts b/locale/es.ts index c0d34c4..0018053 100644 --- a/locale/es.ts +++ b/locale/es.ts @@ -23,7 +23,15 @@ Translate %1 to your native language using %2 - Traduzca %1 a su idioma natal usando %2 + Traduzca %1 a su idioma usando %2 + + + Powered by %1 + + + + Open-source software + Icon designed by %1. @@ -155,6 +163,10 @@ Show Updated Mostrar actualizados + + You have no subscriptions. Use the star symbol to subscribe to channels. + No se ha suscrito a ningún canal. Use el símbolo de la estrella para suscribirse a los canales. + All Videos Todos los vídeos @@ -175,17 +187,6 @@ There are no updated subscriptions at this time. No hay suscripciones actualizadas en este momento. - - You have no subscriptions. Use the star symbol to subscribe to channels. - No se ha suscrito a ningún canal. Use el símbolo de la estrella para suscribirse a los canales. - - - - ClearButton - - Clear - Vaciar - DataUtils @@ -202,11 +203,30 @@ - %n weeks(s) ago + %n month(s) ago + + K + K as in Kilo, i.e. thousands + + + + M + M stands for Millions + + + + B + B stands for Billions + + + + %1 views + %1 reproducciones + - %n month(s) ago + %n week(s) ago @@ -251,22 +271,6 @@ DownloadManager - - This is just the demo version of %1. - Esta es solo la versión de prueba de %1. - - - It can only download videos shorter than %1 minutes so you can test the download functionality. - Solo puede descargar vídeos de duración menor que %1 minutos para que pueda probar la función de descarga. - - - Continue - Continuar - - - Get the full version - Obtener la versión completa - %1 downloaded in %2 %1 descargados en %2 @@ -632,10 +636,6 @@ &Float on Top &Flotar en la parte superior - - &Adjust Window Size - &Ajustar tamaño de la ventana - &Stop After This Video &Detener tras este vídeo @@ -672,6 +672,14 @@ Hide videos that may contain inappropriate content Ocultar videos que puedan contener contenido inapropiado + + Toggle &Menu Bar + Apagar &Barra de menú + + + Menu + Menú + &Love %1? Rate it! ¿&Le gusta %1? ¡Valórelo! @@ -797,6 +805,10 @@ Update Actualizar + + You can still access the menu bar by pressing the ALT key + Por abrir la barra de menú puede presionar la tecla Alt. + MediaView @@ -812,22 +824,6 @@ The link will be valid only for a limited time. El enlace es válido solo por un tiempo limitado. - - This is just the demo version of %1. - Esto es solo la versión de prueba de %1. - - - It allows you to test the application and see if it works for you. - Le permite probar la aplicación y ver si le funciona. - - - Get the full version - Obtener la versión completa - - - Continue - Continuar - Downloading %1 Descargando %1 @@ -859,6 +855,10 @@ Subscribe to %1 Suscribirse a %1 + + Switched to %1 + + Unsubscribed from %1 Desuscrito de %1 @@ -903,11 +903,14 @@ - PlaylistItemDelegate + PickMessage - %1 views - %1 reproducciones + Pick a video + + + + PlaylistItemDelegate %1 of %2 (%3) — %4 %1 de %2 (%3) — %4 @@ -947,10 +950,6 @@ PlaylistModel - - Searching... - Buscando… - Show %1 More Mostrar %1 más @@ -1072,25 +1071,16 @@ Bienvenido a <a href='%1'>%2</a>, - Enter - "Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos" - Escriba + to start watching videos. + para empezar a ver vídeos. a keyword una palabra clave - a channel - un canal - - - to start watching videos. - para empezar a ver vídeos. - - - Watch - Ver + Enter + Escriba Recent keywords @@ -1111,6 +1101,10 @@ &Back &Atrás + + &Forward + + Forward to %1 Reenviar a %1 @@ -1164,13 +1158,6 @@ Descargando %1... - - Video - - Cannot get video stream for %1 - No se puede obtener el flujo de vídeo para %1 - - YTRegions @@ -1366,4 +1353,11 @@ Todo el mundo + + YTVideo + + Cannot get video stream for %1 + No se puede obtener el stream de vídeo para %1 + + \ No newline at end of file diff --git a/locale/es_AR.ts b/locale/es_AR.ts index 7945c35..5f9e59c 100644 --- a/locale/es_AR.ts +++ b/locale/es_AR.ts @@ -25,6 +25,14 @@ Translate %1 to your native language using %2 Traduce %1 a tu idioma natal usando %2 + + Powered by %1 + + + + Open-source software + + Icon designed by %1. Iconos diseñados por %1. @@ -155,6 +163,10 @@ Show Updated Show actualizado + + You have no subscriptions. Use the star symbol to subscribe to channels. + No tiene suscripciones. Haga click en el ícono de la estrella para suscribirse a un canal + All Videos Todos los videos @@ -175,17 +187,6 @@ There are no updated subscriptions at this time. No hay actualizaciones a sus suscripciones en este momento - - You have no subscriptions. Use the star symbol to subscribe to channels. - No tiene suscripciones. Haga click en el ícono de la estrella para suscribirse a un canal - - - - ClearButton - - Clear - Limpiar - DataUtils @@ -202,11 +203,30 @@ - %n weeks(s) ago + %n month(s) ago + + K + K as in Kilo, i.e. thousands + + + + M + M stands for Millions + + + + B + B stands for Billions + + + + %1 views + %1 visitas + - %n month(s) ago + %n week(s) ago @@ -251,22 +271,6 @@ DownloadManager - - This is just the demo version of %1. - Esta es sólo una versión de demostración de %1. - - - It can only download videos shorter than %1 minutes so you can test the download functionality. - Sólo se pueden bajar videos de menos de %1 minutos, para probar la funcionalidad de descarga. - - - Continue - Continuar - - - Get the full version - Consigue la versión completa - %1 downloaded in %2 %1 descargado en %2 @@ -632,10 +636,6 @@ &Float on Top &Siempre Visible - - &Adjust Window Size - Ajustar el tamaño de la ventana - &Stop After This Video &Finalizar Después de este Video @@ -672,6 +672,14 @@ Hide videos that may contain inappropriate content Ocultar videos que puedan tener contenido inapropiado + + Toggle &Menu Bar + + + + Menu + + &Love %1? Rate it! ¿Amas %1? ¡Califícalo! @@ -796,6 +804,10 @@ Update Actualizar + + You can still access the menu bar by pressing the ALT key + + MediaView @@ -811,22 +823,6 @@ The link will be valid only for a limited time. El enlace va a ser válido sólo por un tiempo limitado. - - This is just the demo version of %1. - Esta es sólo la versión de demostración de %1. - - - It allows you to test the application and see if it works for you. - Te permite probar la aplicación y ver si te funciona. - - - Get the full version - Conseguir la versión completa - - - Continue - Continuar - Downloading %1 Descargando %1 @@ -858,6 +854,10 @@ Subscribe to %1 Suscribirse a %1 + + Switched to %1 + + Unsubscribed from %1 Desubscripto de %1 @@ -902,11 +902,14 @@ - PlaylistItemDelegate + PickMessage - %1 views - %1 visitas + Pick a video + + + + PlaylistItemDelegate %1 of %2 (%3) — %4 %1 of %2 (%3) — %4 @@ -946,10 +949,6 @@ PlaylistModel - - Searching... - Buscando... - Show %1 More Mostrar %1 más @@ -1071,25 +1070,16 @@ Bienvenido a <a href='%1'>%2</a>, - Enter - "Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos" - Escribir + to start watching videos. + para empezar a ver videos. a keyword una palabra clave - a channel - un canal - - - to start watching videos. - para empezar a ver videos. - - - Watch - Ver + Enter + Escribir Recent keywords @@ -1110,6 +1100,10 @@ &Back &Atrás + + &Forward + + Forward to %1 Avanzar a %1 @@ -1163,13 +1157,6 @@ Descargando %1... - - Video - - Cannot get video stream for %1 - No puedo obtener el stream de video de %1 - - YTRegions @@ -1365,4 +1352,11 @@ Todo el mundo + + YTVideo + + Cannot get video stream for %1 + No puedo obtener el stream de video de %1 + + \ No newline at end of file diff --git a/locale/es_ES.ts b/locale/es_ES.ts index a06f5ca..5dea213 100644 --- a/locale/es_ES.ts +++ b/locale/es_ES.ts @@ -25,6 +25,14 @@ Translate %1 to your native language using %2 Traducir %1 a tu idioma utilizando %2 + + Powered by %1 + + + + Open-source software + + Icon designed by %1. Icono diseñado por %1. @@ -155,6 +163,10 @@ Show Updated Mostrar actualizados + + You have no subscriptions. Use the star symbol to subscribe to channels. + No tienes ninguna subscripción. Usa la estrella para subscribirte a los canales. + All Videos Todos los videos @@ -175,17 +187,6 @@ There are no updated subscriptions at this time. No existen subscripciones actualizadas en este momento. - - You have no subscriptions. Use the star symbol to subscribe to channels. - No tienes ninguna subscripción. Usa la estrella para subscribirte a los canales. - - - - ClearButton - - Clear - Limpiar - DataUtils @@ -202,11 +203,30 @@ - %n weeks(s) ago + %n month(s) ago + + K + K as in Kilo, i.e. thousands + + + + M + M stands for Millions + + + + B + B stands for Billions + + + + %1 views + %1 vistas + - %n month(s) ago + %n week(s) ago @@ -251,22 +271,6 @@ DownloadManager - - This is just the demo version of %1. - Esta es la versión de prueba de %1. - - - It can only download videos shorter than %1 minutes so you can test the download functionality. - Sólo se pueden descargar vídeos inferiores a %1 minutos. Así podrá probar la funcionalidad de descarga. - - - Continue - Continuar - - - Get the full version - Obtener la versión completa - %1 downloaded in %2 %1 descargado en %2 @@ -632,10 +636,6 @@ &Float on Top &Siempre Visible - - &Adjust Window Size - &Ajustar el tamaños de la ventana - &Stop After This Video &Detener después de este vídeo @@ -672,6 +672,14 @@ Hide videos that may contain inappropriate content Ocultar video que puedan poseer contenido inapropiado + + Toggle &Menu Bar + + + + Menu + + &Love %1? Rate it! &¿Te encanta %1? ¡Puntúalo! @@ -796,6 +804,10 @@ Update Actualizar + + You can still access the menu bar by pressing the ALT key + + MediaView @@ -811,22 +823,6 @@ The link will be valid only for a limited time. El enlace será válido sólo por un plazo de tiempo limitado. - - This is just the demo version of %1. - Esta es la versión de prueba de %1. - - - It allows you to test the application and see if it works for you. - Esta versión le permite probar la aplicación y ver si le sirve. - - - Get the full version - Obtener la versión completa - - - Continue - Continuar - Downloading %1 Descargando %1 @@ -858,6 +854,10 @@ Subscribe to %1 Subscribirse a %1 + + Switched to %1 + + Unsubscribed from %1 Desubscripto de %1 @@ -902,11 +902,14 @@ - PlaylistItemDelegate + PickMessage - %1 views - %1 vistas + Pick a video + + + + PlaylistItemDelegate %1 of %2 (%3) — %4 %1 de %2 (%3) — %4 @@ -946,10 +949,6 @@ PlaylistModel - - Searching... - Buscando... - Show %1 More Mostrar %1 más @@ -1071,25 +1070,16 @@ Bienvenidos a <a href='%1'>%2</a>, - Enter - "Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos" - Introducir + to start watching videos. + para empezar a ver vídeos a keyword una palabra clave - a channel - un canal - - - to start watching videos. - para empezar a ver vídeos - - - Watch - Ver + Enter + Introducir Recent keywords @@ -1110,6 +1100,10 @@ &Back &Volver + + &Forward + + Forward to %1 Hacia adelante %1 @@ -1163,13 +1157,6 @@ Descargando %1... - - Video - - Cannot get video stream for %1 - No se puede obtener el flujo de vídeo para %1 - - YTRegions @@ -1365,4 +1352,11 @@ Mundial + + YTVideo + + Cannot get video stream for %1 + No se puede obtener el flujo de vídeo para %1 + + \ No newline at end of file diff --git a/locale/es_MX.ts b/locale/es_MX.ts index 337e4ac..80e6566 100644 --- a/locale/es_MX.ts +++ b/locale/es_MX.ts @@ -25,6 +25,14 @@ Translate %1 to your native language using %2 Traducir %1 a tu idioma nativo usando %2 + + Powered by %1 + + + + Open-source software + + Icon designed by %1. Ícono diseñado por %1 @@ -54,7 +62,7 @@ &Code: - &Códico + &Código @@ -155,6 +163,10 @@ Show Updated Mostrar actualizados + + You have no subscriptions. Use the star symbol to subscribe to channels. + No tienes suscripciones. Usa el símbolo de estrella para suscribirte a los canales. + All Videos Todos los vídeos @@ -169,23 +181,12 @@ Unsubscribe - Quitar suscripción + Cancelar suscripción There are no updated subscriptions at this time. No hay suscripciones actualizadas - - You have no subscriptions. Use the star symbol to subscribe to channels. - No tienes suscripciones. Usa el símbolo de estrella para suscribirte a los canales. - - - - ClearButton - - Clear - Limpiar - DataUtils @@ -202,11 +203,30 @@ - %n weeks(s) ago + %n month(s) ago + + K + K as in Kilo, i.e. thousands + + + + M + M stands for Millions + + + + B + B stands for Billions + + + + %1 views + %1 vistas + - %n month(s) ago + %n week(s) ago @@ -251,22 +271,6 @@ DownloadManager - - This is just the demo version of %1. - Esta es la versión demostrativa de %1 - - - It can only download videos shorter than %1 minutes so you can test the download functionality. - Sólo puede descargar videos con duración menor a %1 minutos asi que puedes probar la funcionalidad de descarga - - - Continue - Continuar - - - Get the full version - Obtener la versión completa - %1 downloaded in %2 Descargados %1 en %2 @@ -630,11 +634,7 @@ &Float on Top - %Mantener arriba - - - &Adjust Window Size - &Ajustar tamaño de ventana + &Mantener arriba &Stop After This Video @@ -672,6 +672,14 @@ Hide videos that may contain inappropriate content Ocultar vídeos que pueden tener contenido inapropiado + + Toggle &Menu Bar + + + + Menu + + &Love %1? Rate it! &Amas %1? Calíficalo! @@ -796,6 +804,10 @@ Update Actualizar + + You can still access the menu bar by pressing the ALT key + + MediaView @@ -811,22 +823,6 @@ The link will be valid only for a limited time. El vínculo será válido sólo por un tiempo limitado - - This is just the demo version of %1. - Esta es la versión demostrativa de %1 - - - It allows you to test the application and see if it works for you. - Le permite probar la aplicación y ver si funciona para ti. - - - Get the full version - Obtener la versión completa - - - Continue - Continuar - Downloading %1 Descargando %1 @@ -858,6 +854,10 @@ Subscribe to %1 Suscribir a %1 + + Switched to %1 + + Unsubscribed from %1 Desuscribir de %1 @@ -902,11 +902,14 @@ - PlaylistItemDelegate + PickMessage - %1 views - %1 vistas + Pick a video + + + + PlaylistItemDelegate %1 of %2 (%3) — %4 %1 de %2 (%3) - %4 @@ -946,10 +949,6 @@ PlaylistModel - - Searching... - Buscando... - Show %1 More Mostrar %1 más @@ -1071,25 +1070,16 @@ Bienvenido a <a href='%1'>%2</a>, - Enter - "Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos" - Introduzca + to start watching videos. + para ver vídeos a keyword una palabra clave - a channel - un canal - - - to start watching videos. - para ver vídeos - - - Watch - Ver + Enter + Introduzca Recent keywords @@ -1110,6 +1100,10 @@ &Back Retroceder + + &Forward + + Forward to %1 Avanzar a %1 @@ -1163,13 +1157,6 @@ Descargando %1... - - Video - - Cannot get video stream for %1 - No se puede obtener el vídeo para %1 - - YTRegions @@ -1365,4 +1352,11 @@ Mundial + + YTVideo + + Cannot get video stream for %1 + No se puede obtener el vídeo para %1 + + \ No newline at end of file diff --git a/locale/fi.ts b/locale/fi.ts index 2a33153..37b42f9 100644 --- a/locale/fi.ts +++ b/locale/fi.ts @@ -25,6 +25,14 @@ Translate %1 to your native language using %2 Käännä %1 äidinkielellesi käyttämällä %2 + + Powered by %1 + + + + Open-source software + Avoimen lähdekoodin ohjelmisto + Icon designed by %1. Kuvakkeen suunnitteli %1. @@ -107,7 +115,7 @@ You have %n new video(s) - + Sinulle on %n uusi videoSinulle on %n uutta videota @@ -155,6 +163,10 @@ Show Updated Näytä päivitetyt + + You have no subscriptions. Use the star symbol to subscribe to channels. + Sinulla ei ole tilauksia. Käytä tähtisymbolia tilataksesi kanavia. + All Videos Kaikki videot @@ -175,17 +187,6 @@ There are no updated subscriptions at this time. Päivitettyjä tilauksia ei ole tällä hetkellä. - - You have no subscriptions. Use the star symbol to subscribe to channels. - Sinulla ei ole tilauksia. Käytä tähtisymbolia tilataksesi kanavia. - - - - ClearButton - - Clear - Tyhjennä - DataUtils @@ -195,19 +196,38 @@ %n hour(s) ago - + %n tunti sitten%n tuntia sitten %n day(s) ago - + %n päivä sitten%n päivää sitten - %n weeks(s) ago - + %n month(s) ago + %n kuukausi sitten%n kuukautta sitten + + + K + K as in Kilo, i.e. thousands + + + + M + M stands for Millions + + + + B + B stands for Billions + + + + %1 views + Katsottu %1 kertaa - %n month(s) ago - + %n week(s) ago + %n viikko sitten%n viikkoa sitten @@ -251,22 +271,6 @@ DownloadManager - - This is just the demo version of %1. - Tämä on vain %1-kokeiluversio. - - - It can only download videos shorter than %1 minutes so you can test the download functionality. - Voit ladata vain videoita jotka ovat lyhyempiä kuin %1 minuuttia, jotta voit testata latausominaisuutta. - - - Continue - Jatka - - - Get the full version - Hanki täysi versio - %1 downloaded in %2 %1 ladattu ajassa %2 @@ -277,7 +281,7 @@ %n Download(s) - + %n lataus%n latausta @@ -632,10 +636,6 @@ &Float on Top &Pysy päällimmäisenä - - &Adjust Window Size - &Muuta ikkunan kokoa - &Stop After This Video Py&säytä toisto tämän videon jälkeen @@ -666,11 +666,19 @@ Restricted Mode - + Rajoitettu tila Hide videos that may contain inappropriate content - + Piilota videot, jotka saattavat sisältää soveltumatonta sisältöä + + + Toggle &Menu Bar + Näytä/piilota &valikkopalkki + + + Menu + Valikko &Love %1? Rate it! @@ -796,6 +804,10 @@ Update Päivitä + + You can still access the menu bar by pressing the ALT key + Saat valikkopalkin näkyviin painamalla ALT-näppäintä + MediaView @@ -811,22 +823,6 @@ The link will be valid only for a limited time. Osoite on käytössä vain rajoitetun ajan. - - This is just the demo version of %1. - Tämä on vain %1n kokeiluversio. - - - It allows you to test the application and see if it works for you. - Voit kokeilla ohjelmaa nähdäksesi, toimiiko se. - - - Get the full version - Hanki täysi versio - - - Continue - Jatka - Downloading %1 Ladataan %1ta/tä @@ -858,6 +854,10 @@ Subscribe to %1 Tilaa kanava %1 + + Switched to %1 + + Unsubscribed from %1 Kohteen %1 tilaus lopetettu @@ -902,11 +902,14 @@ - PlaylistItemDelegate + PickMessage - %1 views - Katsottu %1 kertaa + Pick a video + Valitse video + + + PlaylistItemDelegate %1 of %2 (%3) — %4 %1 / %2 (%3) — %4 @@ -946,10 +949,6 @@ PlaylistModel - - Searching... - Etsitään... - Show %1 More Näytä %1 lisää @@ -1071,25 +1070,16 @@ Tervetuloa <a href='%1'>%2en</a> - Enter - "Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos" - Syötä + to start watching videos. + aloittaaksesi videoiden katselu. a keyword hakusana - a channel - kanava - - - to start watching videos. - aloittaaksesi videoiden katselu. - - - Watch - Katso + Enter + Syötä Recent keywords @@ -1110,6 +1100,10 @@ &Back &Takaisin + + &Forward + &Eteenpäin + Forward to %1 Eteenpäin kohteeseen %1 @@ -1163,13 +1157,6 @@ Ladataan %1... - - Video - - Cannot get video stream for %1 - Videostriimiä ei saada kohteelle %1 - - YTRegions @@ -1365,4 +1352,11 @@ Maailmanlaajuinen + + YTVideo + + Cannot get video stream for %1 + Videostriimiä ei saada kohteelle %1 + + \ No newline at end of file diff --git a/locale/fi_FI.ts b/locale/fi_FI.ts index 9af8f00..7dd0a99 100644 --- a/locale/fi_FI.ts +++ b/locale/fi_FI.ts @@ -25,6 +25,14 @@ Translate %1 to your native language using %2 Käännä %1 äidinkielellesi käyttämällä %2 + + Powered by %1 + + + + Open-source software + + Icon designed by %1. Kuvakkeen suunnitteli %1. @@ -96,7 +104,7 @@ AppWidget Download - + Lataa @@ -155,6 +163,10 @@ Show Updated Näytä päivitetyt + + You have no subscriptions. Use the star symbol to subscribe to channels. + Sinulla ei ole tilauksia. Käytä tähtisymbolia tilataksesi kanavia. + All Videos Kaikki videot @@ -175,17 +187,6 @@ There are no updated subscriptions at this time. Päivitettyjä tilauksia ei ole tällä hetkellä. - - You have no subscriptions. Use the star symbol to subscribe to channels. - Sinulla ei ole tilauksia. Käytä tähtisymbolia tilataksesi kanavia. - - - - ClearButton - - Clear - Tyhjennä - DataUtils @@ -202,11 +203,30 @@ - %n weeks(s) ago + %n month(s) ago + + K + K as in Kilo, i.e. thousands + + + + M + M stands for Millions + + + + B + B stands for Billions + + + + %1 views + Katsottu %1 kertaa + - %n month(s) ago + %n week(s) ago @@ -251,22 +271,6 @@ DownloadManager - - This is just the demo version of %1. - Tämä on vain %1-kokeiluversio. - - - It can only download videos shorter than %1 minutes so you can test the download functionality. - Voit ladata vain videoita jotka ovat lyhyempiä kuin %1 minuuttia, jotta voit testata latausominaisuutta. - - - Continue - Jatka - - - Get the full version - Hanki täysi versio - %1 downloaded in %2 %1 ladattu ajassa %2 @@ -632,10 +636,6 @@ &Float on Top &Pysy päällimmäisenä - - &Adjust Window Size - &Muuta ikkunan kokoa - &Stop After This Video Py&säytä toisto tämän videon jälkeen @@ -672,6 +672,14 @@ Hide videos that may contain inappropriate content + + Toggle &Menu Bar + Näytä/piilota &valikkopalkki + + + Menu + Valikko + &Love %1? Rate it! &Pidätkö %1sta? Arvostele se! @@ -796,6 +804,10 @@ Update Päivitä + + You can still access the menu bar by pressing the ALT key + Saat valikkopalkin näkyviin painamalla ALT-näppäintä + MediaView @@ -811,22 +823,6 @@ The link will be valid only for a limited time. Osoite on käytössä vain rajoitetun ajan. - - This is just the demo version of %1. - Tämä on vain %1n kokeiluversio. - - - It allows you to test the application and see if it works for you. - Voit kokeilla ohjelmaa nähdäksesi, toimiiko se. - - - Get the full version - Hanki täysi versio - - - Continue - Jatka - Downloading %1 Ladataan %1ta/tä @@ -858,6 +854,10 @@ Subscribe to %1 Tilaa kanava %1 + + Switched to %1 + + Unsubscribed from %1 Lopeta kohteen %1 tilaaminen @@ -902,11 +902,14 @@ - PlaylistItemDelegate + PickMessage - %1 views - Katsottu %1 kertaa + Pick a video + + + + PlaylistItemDelegate %1 of %2 (%3) — %4 %1 / %2 (%3) — %4 @@ -946,10 +949,6 @@ PlaylistModel - - Searching... - Etsitään... - Show %1 More Näytä %1 lisää @@ -1071,25 +1070,16 @@ Tervetuloa <a href='%1'>%2en</a> - Enter - "Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos" - Syötä + to start watching videos. + aloittaaksesi videoiden katselu. a keyword hakusana - a channel - kanava - - - to start watching videos. - aloittaaksesi videoiden katselu. - - - Watch - Katso + Enter + Syötä Recent keywords @@ -1110,6 +1100,10 @@ &Back &Takaisin + + &Forward + + Forward to %1 Eteenpäin kohteeseen %1 @@ -1160,14 +1154,7 @@ Downloading %1... - - - - - Video - - Cannot get video stream for %1 - Videostriimiä ei saada kohteelle %1 + Ladataan %1... @@ -1365,4 +1352,11 @@ Maailmanlaajuinen + + YTVideo + + Cannot get video stream for %1 + Videostriimiä ei saada kohteelle %1 + + \ No newline at end of file diff --git a/locale/fr.ts b/locale/fr.ts index 3392dd0..978bb23 100644 --- a/locale/fr.ts +++ b/locale/fr.ts @@ -25,6 +25,14 @@ Translate %1 to your native language using %2 Traduisez %1 dans votre langue native en utilisant %2 + + Powered by %1 + + + + Open-source software + Logiciel open-source + Icon designed by %1. Icône dessinée par %1. @@ -107,7 +115,7 @@ You have %n new video(s) - + Vous avez %n nouvelle vidéoVous avez %n nouvelles vidéos @@ -155,6 +163,10 @@ Show Updated Afficher les mises à jours + + You have no subscriptions. Use the star symbol to subscribe to channels. + Vous n'avez pas d'abonnements. Utilisez le symbole en forme d'étoile pour vous abonner à des chaines, + All Videos Toutes les vidéos @@ -175,17 +187,6 @@ There are no updated subscriptions at this time. Il n'y a pas d'abonnements mis à jour en ce moment. - - You have no subscriptions. Use the star symbol to subscribe to channels. - Vous n'avez pas d'abonnements. Utilisez le symbole en forme d'étoile pour vous abonner à des chaines, - - - - ClearButton - - Clear - Nettoyer - DataUtils @@ -195,19 +196,38 @@ %n hour(s) ago - + Il y a une heureIl y a %n heures %n day(s) ago - + Il y a %n jourIl y a %n jours - %n weeks(s) ago - + %n month(s) ago + Il y a %n moisIl y a %n mois + + + K + K as in Kilo, i.e. thousands + K + + + M + M stands for Millions + M + + + B + B stands for Billions + B + + + %1 views + %1 vues - %n month(s) ago - + %n week(s) ago + Il y a %n semaineIl y a %n semaines @@ -251,22 +271,6 @@ DownloadManager - - This is just the demo version of %1. - Il s'agit seulement de la version de démonstration de %1. - - - It can only download videos shorter than %1 minutes so you can test the download functionality. - Vous ne pouvez télécharger que des vidéos plus courtes que %1 minutes de sorte que vous puissiez tester la fonctionnalité de téléchargement. - - - Continue - Continuer - - - Get the full version - Obtenir la version complète - %1 downloaded in %2 %1 téléchargé sur %2 @@ -277,7 +281,7 @@ %n Download(s) - + %n téléchargement%n téléchargements @@ -632,10 +636,6 @@ &Float on Top &Laisser au dessus - - &Adjust Window Size - &Ajuster la taille de la fenètre - &Stop After This Video &Arrêter après cette vidéo @@ -672,6 +672,14 @@ Hide videos that may contain inappropriate content Masquer les vidéos qui peuvent contenir un contenu inapproprié + + Toggle &Menu Bar + Bascule la barre de &Menu + + + Menu + Menu + &Love %1? Rate it! &Aimer %1? Notez-le ! @@ -796,6 +804,10 @@ Update Mettre à jour + + You can still access the menu bar by pressing the ALT key + Vous pouvez continuer d'accéder à la barre de menu en appuyant sur la touche ALT + MediaView @@ -811,22 +823,6 @@ The link will be valid only for a limited time. Le lien ne sera valide que pour un temps limité. - - This is just the demo version of %1. - C'est juste la version démo de %1. - - - It allows you to test the application and see if it works for you. - Cela vous permet de tester l'application et voir si cela fonctionne pour vous. - - - Get the full version - Obtenir la version complète - - - Continue - Continuer - Downloading %1 %1 Téléchargement @@ -858,6 +854,10 @@ Subscribe to %1 S'abonner à %1 + + Switched to %1 + + Unsubscribed from %1 Se désabonner de %1 @@ -902,11 +902,14 @@ - PlaylistItemDelegate + PickMessage - %1 views - %1 vues + Pick a video + + + + PlaylistItemDelegate %1 of %2 (%3) — %4 %1 de %2 (%3) — %4 @@ -946,10 +949,6 @@ PlaylistModel - - Searching... - Recherche en cours… - Show %1 More Afficher %1 de plus @@ -1071,25 +1070,16 @@ Bienvenue sur <a href='%1'>%2</a>, - Enter - "Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos" - Entrer + to start watching videos. + pour commencer à regarder des vidéos. a keyword un mot-clé - a channel - une chaîne - - - to start watching videos. - pour commencer à regarder des vidéos. - - - Watch - Regarder + Enter + Entrer Recent keywords @@ -1110,6 +1100,10 @@ &Back &Retour + + &Forward + + Forward to %1 Continuer à %1 @@ -1163,13 +1157,6 @@ Téléchargement de %1... - - Video - - Cannot get video stream for %1 - Impossible d'obtenir le flux vidéo de %1 - - YTRegions @@ -1365,4 +1352,11 @@ Monde entier + + YTVideo + + Cannot get video stream for %1 + Impossible d'obtenir le flux vidéo de %1 + + \ No newline at end of file diff --git a/locale/gl.ts b/locale/gl.ts index ae4941f..9bd8a11 100644 --- a/locale/gl.ts +++ b/locale/gl.ts @@ -25,6 +25,14 @@ Translate %1 to your native language using %2 Traducir %1 ao seu idioma empregando %2 + + Powered by %1 + Coa tecnoloxía de %1 + + + Open-source software + Software de fontes abertas + Icon designed by %1. Icona deseñada por %1. @@ -96,7 +104,7 @@ AppWidget Download - + Descarga @@ -107,7 +115,7 @@ You have %n new video(s) - + Dispón dun novo vídeoDispón de %n novos vídeos @@ -155,6 +163,10 @@ Show Updated Amosar a actualización + + You have no subscriptions. Use the star symbol to subscribe to channels. + Non ten subscricións. Utilice o símbolo da estrela para subscribirse ás canles. + All Videos Todos os vídeos @@ -175,39 +187,47 @@ There are no updated subscriptions at this time. Neste momento non ten subscricións de actualización. - - You have no subscriptions. Use the star symbol to subscribe to channels. - Non ten subscricións. Utilice o símbolo da estrela para subscribirse ás canles. - - - - ClearButton - - Clear - Limpar - DataUtils Just now - + Agora mesmo %n hour(s) ago - + Hai %n horaHai %n horas %n day(s) ago - + Hai %n diaHai %n dias - %n weeks(s) ago - + %n month(s) ago + Hai %n mesHai %n meses + + + K + K as in Kilo, i.e. thousands + K + + + M + M stands for Millions + M + + + B + B stands for Billions + B + + + %1 views + %1 vistas - %n month(s) ago - + %n week(s) ago + Hai %n semanaHai %n semanas @@ -251,22 +271,6 @@ DownloadManager - - This is just the demo version of %1. - Isto é só a versión demo de %1. - - - It can only download videos shorter than %1 minutes so you can test the download functionality. - Só se poden descargar vídeos curtos de menos de %1 minutos para que poida probar a utilidade de descargas. - - - Continue - Continuar - - - Get the full version - Obter a versión completa - %1 downloaded in %2 %1 descargado en %2 @@ -277,7 +281,7 @@ %n Download(s) - + %n descarga%n descargas @@ -314,19 +318,19 @@ Extra The executable file has been tempered with, maybe by a virus. - + O ficheiro executable foi manipulado, quizais por un virus. %1 will not run. Try installing again. - + %1 non se executará. Probe a instalalo de novo. Quit - + Saír Reinstall - + Volver instalar @@ -594,7 +598,7 @@ Show details about video downloads - Mostrar os detalles sobre as descargas de vídeo + Amosar os detalles sobre as descargas de vídeo &Download @@ -632,10 +636,6 @@ &Float on Top &Flotante e arriba - - &Adjust Window Size - - &Stop After This Video &Deter despois deste vídeo @@ -666,11 +666,19 @@ Restricted Mode - + Modo restrinxido Hide videos that may contain inappropriate content - + Agochar os vídeos que poidan conter contido inadecuado + + + Toggle &Menu Bar + Alternar a barra do &menú + + + Menu + Menú &Love %1? Rate it! @@ -750,7 +758,7 @@ &Loading... - + &Cargando... Leave &Full Screen @@ -796,6 +804,10 @@ Update Actualizar + + You can still access the menu bar by pressing the ALT key + Aínda pode acceder á barra de menús premendo a tecla ALT + MediaView @@ -811,22 +823,6 @@ The link will be valid only for a limited time. A ligazón ten validez só por un tempo limitado. - - This is just the demo version of %1. - Isto é só a versión demo de %1. - - - It allows you to test the application and see if it works for you. - Permítelle probar o aplicativo e comprobar se vai ao seu xeito. - - - Get the full version - Obter a versión completa - - - Continue - Continuar - Downloading %1 Descargando %1 @@ -839,7 +835,7 @@ part This is for video parts, as in 'Cool video - part 1' - peza + parte episode @@ -848,7 +844,7 @@ Sent from %1 - Enviado desde %1 + Enviado dende %1 Unsubscribe from %1 @@ -858,13 +854,17 @@ Subscribe to %1 Subscribirse a %1 + + Switched to %1 + Cambiado a %1 + Unsubscribed from %1 - + Non está subscrito a %1 Subscribed to %1 - + Subscrito a %1 @@ -879,15 +879,15 @@ Would you like to download it now? - Queres descargala agora? + Quere descargala agora? Skip This Version - Saltar esta versión + Omitir esta versión Remind Me Later - Acórdamo máis adiante + Lembremo máis adiante Install Update @@ -902,11 +902,14 @@ - PlaylistItemDelegate + PickMessage - %1 views - %1 vistas + Pick a video + Deleccione un vídeo + + + PlaylistItemDelegate %1 of %2 (%3) — %4 %1 de %2 (%3) — %4 @@ -933,7 +936,7 @@ Show in %1 - Mostrar en %1 + Amosar en %1 Open parent folder @@ -946,13 +949,9 @@ PlaylistModel - - Searching... - Buscando... - Show %1 More - Mostrar %1 máis + Amosar %1 máis No videos @@ -1071,25 +1070,16 @@ Benvido a <a href='%1'>%2</a, - Enter - "Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos" - Introduza + to start watching videos. + para comezar a ver vídeos. a keyword unha palabra clave - a channel - unha canle - - - to start watching videos. - para comezar a ver vídeos. - - - Watch - Ver + Enter + Introduza Recent keywords @@ -1110,6 +1100,10 @@ &Back &Atrás + + &Forward + A&diante + Forward to %1 Ir a %1 @@ -1160,14 +1154,7 @@ Downloading %1... - - - - - Video - - Cannot get video stream for %1 - Non é posíbel obter o fluxo de vídeo de %1 + Descargando %1... @@ -1365,4 +1352,11 @@ Todo o mundo + + YTVideo + + Cannot get video stream for %1 + Non é posíbel obter o fluxo de vídeo de %1 + + \ No newline at end of file diff --git a/locale/he_IL.ts b/locale/he_IL.ts index 9dfd611..fffa966 100644 --- a/locale/he_IL.ts +++ b/locale/he_IL.ts @@ -25,6 +25,14 @@ Translate %1 to your native language using %2 ניתן לתרגם את %1 לשפת אמך באמצעות %2 + + Powered by %1 + מופעל על ידי %1 + + + Open-source software + תכנה בקוד פתוח + Icon designed by %1. הסמל עוצב על ידי %1. @@ -96,7 +104,7 @@ AppWidget Download - + הורד @@ -107,7 +115,7 @@ You have %n new video(s) - + יש לך סרטון חדשיש לך שני סרטונים חדשיםיש לך %n סרטונים חדשיםיש לך %n סרטונים חדשים @@ -155,6 +163,10 @@ Show Updated הצג עדכונים + + You have no subscriptions. Use the star symbol to subscribe to channels. + אין לך מנויים. השתמש בסמל הכוכב על מנת להיות מנוי לערוצים. + All Videos כל הסרטונים @@ -175,39 +187,47 @@ There are no updated subscriptions at this time. אין עדכונים בערוצים שאתה מנוי עליהם כרגע. - - You have no subscriptions. Use the star symbol to subscribe to channels. - אין לך מנויים. השתמש בסמל הכוכב על מנת להיות מנוי לערוצים. - - - - ClearButton - - Clear - מחיקה - DataUtils Just now - + זה עתה %n hour(s) ago - + לפני שעהלפני שעתייםלפני %n שעותלפני %n שעות %n day(s) ago - + אתמולשלשוםלפני %n ימיםלפני %n ימים - %n weeks(s) ago - + %n month(s) ago + לפני חודשלפני חודשייםלפני %n חודשיםלפני %n חודשים + + + K + K as in Kilo, i.e. thousands + ק׳ + + + M + M stands for Millions + מ׳ + + + B + B stands for Billions + ב׳ + + + %1 views + %1 צפיות - %n month(s) ago - + %n week(s) ago + לפני שבועלפני שבועייםלפני %n שבועותלפני %n שבועות @@ -251,22 +271,6 @@ DownloadManager - - This is just the demo version of %1. - זוהי רק גרסת ההדגמה של %1. - - - It can only download videos shorter than %1 minutes so you can test the download functionality. - באמצעות גרסה זו ניתן להוריד קטעי וידאו שאורכם אינו עולה על %1 דקות כדי שתהיה באפשרותך לבחור את אפשרות ההורדה. - - - Continue - המשך - - - Get the full version - קבלת הגרסה המלאה - %1 downloaded in %2 %1 התקבל במהירות של %2 @@ -277,7 +281,7 @@ %n Download(s) - + הורדה אחתשתי הורדות%n הורדות%n הורדות @@ -314,19 +318,19 @@ Extra The executable file has been tempered with, maybe by a virus. - + הקובץ שונה,אולי על ידי וירוס. %1 will not run. Try installing again. - + %1 לא ניתן להפעלה,נסה להתקין מחדש. Quit - + יציאה Reinstall - + התקן מחדש @@ -632,10 +636,6 @@ &Float on Top &ציפה מלמעלה - - &Adjust Window Size - - &Stop After This Video ל&עצור לאחר וידאו זה @@ -666,11 +666,19 @@ Restricted Mode - + מצב מוגבל Hide videos that may contain inappropriate content - + הסתר סרטונים שעשויים להכיל תוכן לא הולם + + + Toggle &Menu Bar + החלפת מ&צב סרגל תפריט + + + Menu + תפריט &Love %1? Rate it! @@ -750,7 +758,7 @@ &Loading... - + %טוען... Leave &Full Screen @@ -796,6 +804,10 @@ Update עדכון + + You can still access the menu bar by pressing the ALT key + עדיין ניתן לגשת לסרגל התפריט עם לחיצה על ALT + MediaView @@ -811,22 +823,6 @@ The link will be valid only for a limited time. הקישור יהיה תקף לזמן מוגבל בלבד. - - This is just the demo version of %1. - זוהי רק גרסת ההדגמה של %1. - - - It allows you to test the application and see if it works for you. - גרסה זו מאפשרת לך לבחון את היישום ולראות האם הוא מתאים לצרכיך. - - - Get the full version - קבלת הגרסה המלאה - - - Continue - המשך - Downloading %1 %1 מתקבל @@ -858,13 +854,17 @@ Subscribe to %1 מנוי ל %1 + + Switched to %1 + הוחלף אל %1 + Unsubscribed from %1 - + בוטלה הרשמה מ%1 Subscribed to %1 - + בוצע רישום ל%1 @@ -902,11 +902,14 @@ - PlaylistItemDelegate + PickMessage - %1 views - %1 צפיות + Pick a video + נא לבחור סרטון + + + PlaylistItemDelegate %1 of %2 (%3) — %4 %1 מתוך %2 (%3) — %4 @@ -946,10 +949,6 @@ PlaylistModel - - Searching... - בהליכי חיפוש... - Show %1 More הצגת %1 נוספים @@ -1071,25 +1070,16 @@ ברוך בואך אל <a href='%1'>%2</a>, - Enter - "Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos" - הזנה + to start watching videos. + כדי להתחיל לצפות בסרטונים. a keyword מילת מפתח - a channel - ערוץ - - - to start watching videos. - כדי להתחיל לצפות בסרטונים. - - - Watch - צפייה + Enter + הזנה Recent keywords @@ -1110,6 +1100,10 @@ &Back הקודם + + &Forward + ה&עברה + Forward to %1 העבר אל %1 @@ -1160,14 +1154,7 @@ Downloading %1... - - - - - Video - - Cannot get video stream for %1 - לא ניתן לקבל את תזרים הווידאו עבור %1 + מוריד %1... @@ -1365,4 +1352,11 @@ כל העולם + + YTVideo + + Cannot get video stream for %1 + לא ניתן לקבל את תזרים הווידאו עבור %1 + + \ No newline at end of file diff --git a/locale/hr.ts b/locale/hr.ts index 0ef5015..ce41669 100644 --- a/locale/hr.ts +++ b/locale/hr.ts @@ -25,6 +25,14 @@ Translate %1 to your native language using %2 Prevedite %1 na svoj jezik koristeći %2 + + Powered by %1 + + + + Open-source software + + Icon designed by %1. Dizajn ikone %1. @@ -155,6 +163,10 @@ Show Updated Prikaži ažurirano + + You have no subscriptions. Use the star symbol to subscribe to channels. + Niste pretplaćeni na ni jedan kanal. Za pretplatu kliknite na zvijezdicu. + All Videos Svi videozapisi @@ -175,17 +187,6 @@ There are no updated subscriptions at this time. Trenutno nema dostupnih ažuriranja pretplata - - You have no subscriptions. Use the star symbol to subscribe to channels. - Niste pretplaćeni na ni jedan kanal. Za pretplatu kliknite na zvijezdicu. - - - - ClearButton - - Clear - ObriÅ¡i - DataUtils @@ -202,11 +203,30 @@ - %n weeks(s) ago + %n month(s) ago + + K + K as in Kilo, i.e. thousands + + + + M + M stands for Millions + + + + B + B stands for Billions + + + + %1 views + %1 pregleda + - %n month(s) ago + %n week(s) ago @@ -251,22 +271,6 @@ DownloadManager - - This is just the demo version of %1. - Ovo je samo probna verzija %1. - - - It can only download videos shorter than %1 minutes so you can test the download functionality. - Može preuzeti samo video kraći od %1 minuta tako da možete testirati mogućnost preuzimanja. - - - Continue - Nastavi - - - Get the full version - Preuzmi punu verziju - %1 downloaded in %2 %1 preuzet u %2 @@ -632,10 +636,6 @@ &Float on Top &Budi na vrhu - - &Adjust Window Size - - &Stop After This Video &Stani nakon ovog videa @@ -672,6 +672,14 @@ Hide videos that may contain inappropriate content + + Toggle &Menu Bar + + + + Menu + + &Love %1? Rate it! @@ -796,6 +804,10 @@ Update Ažuriraj + + You can still access the menu bar by pressing the ALT key + + MediaView @@ -811,22 +823,6 @@ The link will be valid only for a limited time. Link će biti valjan samo ograničeno vrijeme. - - This is just the demo version of %1. - Ovo je samo demo verzija %1. - - - It allows you to test the application and see if it works for you. - Omogućava Vam da testirate program i vidite da li Vam odgovara. - - - Get the full version - Preuzmi punu verziju - - - Continue - Nastavi - Downloading %1 Preuzimam %1 @@ -858,6 +854,10 @@ Subscribe to %1 Pretplata na %1 + + Switched to %1 + + Unsubscribed from %1 @@ -902,11 +902,14 @@ - PlaylistItemDelegate + PickMessage - %1 views - %1 pregleda + Pick a video + + + + PlaylistItemDelegate %1 of %2 (%3) — %4 %1 od %2 (%3) - %4 @@ -946,10 +949,6 @@ PlaylistModel - - Searching... - Pretražujem... - Show %1 More Prikaži %1 viÅ¡e @@ -1071,25 +1070,16 @@ DobrodoÅ¡li u <a href='%1'>%2</a>, - Enter - "Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos" - Unesi + to start watching videos. + da počnete gledati video. a keyword ključna riječ - a channel - kanal - - - to start watching videos. - da počnete gledati video. - - - Watch - Gledaj + Enter + Unesi Recent keywords @@ -1110,6 +1100,10 @@ &Back &Nazad + + &Forward + + Forward to %1 Naprijed na %1 @@ -1163,13 +1157,6 @@ - - Video - - Cannot get video stream for %1 - Ne mogu naći video stream za %1 - - YTRegions @@ -1365,4 +1352,11 @@ Å irom svijeta + + YTVideo + + Cannot get video stream for %1 + Ne mogu naći video stream za %1 + + \ No newline at end of file diff --git a/locale/hu.ts b/locale/hu.ts index 46e53e9..9d60117 100644 --- a/locale/hu.ts +++ b/locale/hu.ts @@ -25,6 +25,14 @@ Translate %1 to your native language using %2 Fordítsa le a %1 programot az anyanyelvére a következővel: %2 + + Powered by %1 + + + + Open-source software + + Icon designed by %1. Ikon tervezője: %1 @@ -96,7 +104,7 @@ AppWidget Download - + Letöltés @@ -155,6 +163,10 @@ Show Updated Frissítések mutatása + + You have no subscriptions. Use the star symbol to subscribe to channels. + Nincsenek feliratkozások. A csillag szimbólumot kell használni a csatornákra való feliratkozáshoz. + All Videos Összes videó @@ -175,17 +187,6 @@ There are no updated subscriptions at this time. Nincs frissítés a feliratkozott csatornákon. - - You have no subscriptions. Use the star symbol to subscribe to channels. - Nincsenek feliratkozások. A csillag szimbólumot kell használni a csatornákra való feliratkozáshoz. - - - - ClearButton - - Clear - Törlés - DataUtils @@ -202,11 +203,30 @@ - %n weeks(s) ago + %n month(s) ago + + K + K as in Kilo, i.e. thousands + + + + M + M stands for Millions + + + + B + B stands for Billions + + + + %1 views + %1 néző + - %n month(s) ago + %n week(s) ago @@ -251,22 +271,6 @@ DownloadManager - - This is just the demo version of %1. - Ez csak a demó verziója a %1 programnak. - - - It can only download videos shorter than %1 minutes so you can test the download functionality. - Csak %1 percnél rövidebb videók tölthetők le vele a letöltési funkciók teszteléséhez. - - - Continue - Folytatás - - - Get the full version - Teljes verzió beszerzése - %1 downloaded in %2 %1 letöltve ide: %2 @@ -632,10 +636,6 @@ &Float on Top &Többi ablak fölött - - &Adjust Window Size - &Ablak méretének beállítása - &Stop After This Video &Videó után leállítás @@ -666,10 +666,18 @@ Restricted Mode - + Korlátozott mód Hide videos that may contain inappropriate content + Esetleg nem megfelelő tartalmú videók elrejtése + + + Toggle &Menu Bar + + + + Menu @@ -796,6 +804,10 @@ Update Frissítés + + You can still access the menu bar by pressing the ALT key + + MediaView @@ -811,22 +823,6 @@ The link will be valid only for a limited time. A hivatkozás csak korlátozott ideig lesz érvényben. - - This is just the demo version of %1. - Ez csak a demó verziója a %1 programnak. - - - It allows you to test the application and see if it works for you. - Kipróbálhatja az alkalmazást, hogy megfelel-e az igényeinek. - - - Get the full version - Teljes verzió beszerzése - - - Continue - Folytatás - Downloading %1 Letöltés: %1 @@ -858,6 +854,10 @@ Subscribe to %1 Feliratkozás az alábbi csatornára: %1 + + Switched to %1 + + Unsubscribed from %1 Leiratkozva %1-ról/ről. @@ -902,11 +902,14 @@ - PlaylistItemDelegate + PickMessage - %1 views - %1 néző + Pick a video + + + + PlaylistItemDelegate %1 of %2 (%3) — %4 %1 %2 közül (%3) — %4 @@ -946,10 +949,6 @@ PlaylistModel - - Searching... - Keresés... - Show %1 More További %1 elem megjelenítése @@ -1071,25 +1070,16 @@ Üdvözli a <a href='%1'>%2</a> program, - Enter - "Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos" - Írjon be + to start watching videos. + a videók megtekintéséhez. a keyword egy kulcsszót - a channel - egy csatornát - - - to start watching videos. - a videók megtekintéséhez. - - - Watch - Megtekintés + Enter + Írjon be Recent keywords @@ -1110,6 +1100,10 @@ &Back &Vissza + + &Forward + + Forward to %1 Tovább a %1-re @@ -1160,14 +1154,7 @@ Downloading %1... - - - - - Video - - Cannot get video stream for %1 - Nem található videó adatfolyam a következőhöz: %1 + Letöltés %1... @@ -1365,4 +1352,11 @@ Világszerte + + YTVideo + + Cannot get video stream for %1 + Nem található videó adatfolyam a következőhöz: %1 + + \ No newline at end of file diff --git a/locale/id.ts b/locale/id.ts index 9daf0b8..2672b63 100644 --- a/locale/id.ts +++ b/locale/id.ts @@ -25,6 +25,14 @@ Translate %1 to your native language using %2 Terjemahkan %1 ke bahasa aslimu dengan menggunakan %2 + + Powered by %1 + + + + Open-source software + + Icon designed by %1. Desain ikon oleh %1. @@ -155,6 +163,10 @@ Show Updated Acara Terbarui + + You have no subscriptions. Use the star symbol to subscribe to channels. + Anda tidak punya langganan. Gunakan simbol bintang untuk berlangganan ke saluran. + All Videos Semua video @@ -175,17 +187,6 @@ There are no updated subscriptions at this time. Tidak ada langganan yang diperbarui saat ini - - You have no subscriptions. Use the star symbol to subscribe to channels. - Anda tidak punya langganan. Gunakan simbol bintang untuk berlangganan ke saluran. - - - - ClearButton - - Clear - Bersih - DataUtils @@ -202,11 +203,30 @@ - %n weeks(s) ago + %n month(s) ago + + K + K as in Kilo, i.e. thousands + + + + M + M stands for Millions + + + + B + B stands for Billions + + + + %1 views + %1 tampilan + - %n month(s) ago + %n week(s) ago @@ -251,22 +271,6 @@ DownloadManager - - This is just the demo version of %1. - Ini adalah hanya versi demo dari %1. - - - It can only download videos shorter than %1 minutes so you can test the download functionality. - Itu bisanya hanya download video yang pendek daripada %1 menit sehingga kamu bisa menguji fungsinya downloadnya. - - - Continue - Teruskan - - - Get the full version - Dapatkan versi komplitnya - %1 downloaded in %2 %1 diunduh dalam %2 @@ -632,10 +636,6 @@ &Float on Top &Mengambang ke puncak - - &Adjust Window Size - - &Stop After This Video &berhenti setelah video ini @@ -672,6 +672,14 @@ Hide videos that may contain inappropriate content + + Toggle &Menu Bar + + + + Menu + + &Love %1? Rate it! &Love %1? Beri Nilai! @@ -796,6 +804,10 @@ Update Pembaruan data + + You can still access the menu bar by pressing the ALT key + + MediaView @@ -811,22 +823,6 @@ The link will be valid only for a limited time. Link akan berlaku hanya untuk waktu yang terbatas. - - This is just the demo version of %1. - This is just the demo version of %1. - - - It allows you to test the application and see if it works for you. - Ini memungkinkan Anda untuk menguji aplikasi dan melihat apakah ia bekerja untuk Anda. - - - Get the full version - Get the full version - - - Continue - Continue - Downloading %1 Downloading %1 @@ -858,6 +854,10 @@ Subscribe to %1 Berlangganan ke %1 + + Switched to %1 + + Unsubscribed from %1 @@ -902,11 +902,14 @@ - PlaylistItemDelegate + PickMessage - %1 views - %1 tampilan + Pick a video + + + + PlaylistItemDelegate %1 of %2 (%3) — %4 %1 dari %2 (%3) --- %4 @@ -946,10 +949,6 @@ PlaylistModel - - Searching... - Sedang mencari... - Show %1 More Tampilkan lebih banyak %1 @@ -1071,25 +1070,16 @@ Selamat datang ke <a href='%1'>%2</a>, - Enter - "Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos" - Masukkan + to start watching videos. + to start watching videos. a keyword sebuah kata kunci - a channel - a channel - - - to start watching videos. - to start watching videos. - - - Watch - Watch + Enter + Masukkan Recent keywords @@ -1110,6 +1100,10 @@ &Back &Kembali + + &Forward + + Forward to %1 Maju sampai %1 @@ -1163,13 +1157,6 @@ Mengunduh %1... - - Video - - Cannot get video stream for %1 - Tidak bisa mendapatkan video stream untuk %1 - - YTRegions @@ -1365,4 +1352,11 @@ Dunia + + YTVideo + + Cannot get video stream for %1 + Tidak bisa mendapatkan video stream untuk %1 + + \ No newline at end of file diff --git a/locale/it.ts b/locale/it.ts index 8625647..3445da6 100644 --- a/locale/it.ts +++ b/locale/it.ts @@ -25,6 +25,14 @@ Translate %1 to your native language using %2 Traduci %1 nella tua lingua usando %2 + + Powered by %1 + Utilizza %1 + + + Open-source software + Software open-source + Icon designed by %1. Icona disegnata da %1. @@ -107,7 +115,7 @@ You have %n new video(s) - + C'è un nuovo videoCi sono %n nuovi video @@ -155,6 +163,10 @@ Show Updated Mostra aggiornati + + You have no subscriptions. Use the star symbol to subscribe to channels. + Non hai iscrizioni. Usa il simbolo della stella per sottoscrivere i canali. + All Videos Tutti i video @@ -175,17 +187,6 @@ There are no updated subscriptions at this time. Non ci sono iscrizioni aggiornate in questo momento. - - You have no subscriptions. Use the star symbol to subscribe to channels. - Non hai iscrizioni. Usa il simbolo della stella per sottoscrivere i canali. - - - - ClearButton - - Clear - Cancella - DataUtils @@ -195,19 +196,38 @@ %n hour(s) ago - + Un'ora fa%n ore fa %n day(s) ago - + Ieri%n giorni fa - %n weeks(s) ago - + %n month(s) ago + Un mese fa%n mesi fa + + + K + K as in Kilo, i.e. thousands + K + + + M + M stands for Millions + M + + + B + B stands for Billions + B + + + %1 views + %1 visualizzazioni - %n month(s) ago - + %n week(s) ago + Una settimana fa%n settimane fa @@ -251,22 +271,6 @@ DownloadManager - - This is just the demo version of %1. - Questa è solo la versione demo di %1. - - - It can only download videos shorter than %1 minutes so you can test the download functionality. - Puoi scaricare solo video più corti di %1 minuti, così puoi testare la funzionalità dei download. - - - Continue - Continua - - - Get the full version - Compra la versione completa - %1 downloaded in %2 %1 scaricato in %2 @@ -277,7 +281,7 @@ %n Download(s) - + Un download%n download @@ -632,10 +636,6 @@ &Float on Top &Fluttua in alto - - &Adjust Window Size - &Adatta le dimensioni della finestra - &Stop After This Video &Ferma dopo questo video @@ -672,6 +672,14 @@ Hide videos that may contain inappropriate content Nascondi contenuti inappropriati + + Toggle &Menu Bar + Mostra/Nascondi &Menu + + + Menu + Menu + &Love %1? Rate it! Ti piace %1? @@ -796,6 +804,10 @@ Update Aggiorna + + You can still access the menu bar by pressing the ALT key + Puoi accedere di nuovo al menu premendo ALT + MediaView @@ -811,22 +823,6 @@ The link will be valid only for a limited time. Il link rimarrà valido per un periodo di tempo limitato. - - This is just the demo version of %1. - Questa è solo la versione demo di %1. - - - It allows you to test the application and see if it works for you. - Ti permette di testare l'applicazione e verificare che funzioni sul tuo computer. - - - Get the full version - Compra la versione completa - - - Continue - Continua - Downloading %1 Scarica in: %1 @@ -858,6 +854,10 @@ Subscribe to %1 Iscriviti a %1 + + Switched to %1 + Passato a %1 + Unsubscribed from %1 Iscrizione a %1 annullata @@ -902,11 +902,14 @@ - PlaylistItemDelegate + PickMessage - %1 views - %1 visualizzazioni + Pick a video + Scegli un video + + + PlaylistItemDelegate %1 of %2 (%3) — %4 %1 di %2 (%3) — %4 @@ -946,10 +949,6 @@ PlaylistModel - - Searching... - Ricerca... - Show %1 More Mostra altri %1 @@ -1071,25 +1070,16 @@ Benvenuto su <a href="%1">%2</a>, - Enter - "Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos" - Scrivi + to start watching videos. + per iniziare a guardare i video. a keyword una parola chiave - a channel - un canale - - - to start watching videos. - per iniziare a guardare i video. - - - Watch - Guarda + Enter + Scrivi Recent keywords @@ -1110,6 +1100,10 @@ &Back &Indietro + + &Forward + &Avanti + Forward to %1 Avanza a %1 @@ -1163,13 +1157,6 @@ Download di %1 - - Video - - Cannot get video stream for %1 - Impossibile ottenere il flusso video per %1 - - YTRegions @@ -1365,4 +1352,11 @@ Tutto il mondo + + YTVideo + + Cannot get video stream for %1 + Impossibile ottenere il flusso video per %1 + + \ No newline at end of file diff --git a/locale/ja_JP.ts b/locale/ja_JP.ts index 40a6fc7..ed2e914 100644 --- a/locale/ja_JP.ts +++ b/locale/ja_JP.ts @@ -25,6 +25,14 @@ Translate %1 to your native language using %2 %2を使って、%1をあなたの言語に翻訳してください + + Powered by %1 + + + + Open-source software + + Icon designed by %1. アイコンは%1さんのデザインです。 @@ -96,7 +104,7 @@ AppWidget Download - + ダウンロード @@ -155,6 +163,10 @@ Show Updated 動画の更新を確認 + + You have no subscriptions. Use the star symbol to subscribe to channels. + 購読済みのものがありません。チャンネルを購読するには星マークをクリックします。 + All Videos 全ての動画 @@ -175,17 +187,6 @@ There are no updated subscriptions at this time. 購読したチャンネルに更新はありません。 - - You have no subscriptions. Use the star symbol to subscribe to channels. - 購読済みのものがありません。チャンネルを購読するには星マークをクリックします。 - - - - ClearButton - - Clear - クリア - DataUtils @@ -202,11 +203,30 @@ - %n weeks(s) ago + %n month(s) ago + + K + K as in Kilo, i.e. thousands + + + + M + M stands for Millions + + + + B + B stands for Billions + + + + %1 views + %1回 閲覧 + - %n month(s) ago + %n week(s) ago @@ -251,22 +271,6 @@ DownloadManager - - This is just the demo version of %1. - これは%1 の試用版です。 - - - It can only download videos shorter than %1 minutes so you can test the download functionality. - 動画を%1分程度でダウンロードできる機能を試すことができます。 - - - Continue - 続ける - - - Get the full version - 製品版を入手する - %1 downloaded in %2 %1を%2にダウンロード中 @@ -632,10 +636,6 @@ &Float on Top 最前面に表示(&F) - - &Adjust Window Size - &ウィンドウの大きさを調節 - &Stop After This Video 再生終了後に停止(&S) @@ -672,6 +672,14 @@ Hide videos that may contain inappropriate content + + Toggle &Menu Bar + + + + Menu + + &Love %1? Rate it! %1を評価(&L) @@ -796,6 +804,10 @@ Update 更新 + + You can still access the menu bar by pressing the ALT key + + MediaView @@ -811,22 +823,6 @@ The link will be valid only for a limited time. リンクは制限時間内のみ有効です。 - - This is just the demo version of %1. - これは%1 の試用版です。 - - - It allows you to test the application and see if it works for you. - アプリケーションのテストや動作確認にご利用いただけます。 - - - Get the full version - 製品版を入手する - - - Continue - 続ける - Downloading %1 %1をダウンロード中 @@ -858,6 +854,10 @@ Subscribe to %1 %1を購読する + + Switched to %1 + + Unsubscribed from %1 %1の購読を解除しました @@ -902,11 +902,14 @@ - PlaylistItemDelegate + PickMessage - %1 views - %1回 閲覧 + Pick a video + + + + PlaylistItemDelegate %1 of %2 (%3) — %4 %1は%2(%3)%4 @@ -946,10 +949,6 @@ PlaylistModel - - Searching... - 検索中... - Show %1 More さらに%1件表示 @@ -1071,25 +1070,16 @@ ようこそ<a href='%1'>%2</a>へ - Enter - "Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos" - キーワードを入力して + to start watching videos. + を検索する。 a keyword 動画 - a channel - チャンネル - - - to start watching videos. - を検索する。 - - - Watch - 検索 + Enter + キーワードを入力して Recent keywords @@ -1110,6 +1100,10 @@ &Back 戻る(&B) + + &Forward + + Forward to %1 %1 に進む @@ -1163,13 +1157,6 @@ - - Video - - Cannot get video stream for %1 - %1の動画を取得できませんでした - - YTRegions @@ -1365,4 +1352,11 @@ 全世界 + + YTVideo + + Cannot get video stream for %1 + %1の動画を取得できませんでした + + \ No newline at end of file diff --git a/locale/ko_KR.ts b/locale/ko_KR.ts index 87ec5f9..1240d49 100644 --- a/locale/ko_KR.ts +++ b/locale/ko_KR.ts @@ -19,12 +19,20 @@ Please <a href='%1'>donate</a> to support the continued development of %2. - %2의 계속된 개발을 위해 <a href='%1'>기부</a>룰 해주요... + %2의 계속된 개발을 위해 <a href='%1'>기부</a>룰 해주세요. Translate %1 to your native language using %2 %2을(를) 사용해서 %1를 사용자의 언어로 번역 하세요. + + Powered by %1 + + + + Open-source software + + Icon designed by %1. 아이콘 디자인: %1 @@ -65,7 +73,7 @@ This demo has expired. - 데모 만료! + 데모버전이 만료되었습니다! The full version allows you to watch videos without interruptions. @@ -77,7 +85,7 @@ By purchasing the full version, you will also support the hard work I put into creating %1. - 구입하면, 제가 %1를 만드는데 드는 노력을 지원 합니다. + 구입하면, 개발자가 %1를 만드는데 드는 소중한 노력을 지원 합니다. Use Demo @@ -96,7 +104,7 @@ AppWidget Download - + 다운로드 @@ -118,7 +126,7 @@ Unwatched Videos - 안 본 비디오 + 시청하지 않은 비디오 @@ -149,23 +157,27 @@ Mark all as watched - 모두 본 걸로 표시 + 모두 시청함으로 표시 Show Updated 업데이트 표시 + + You have no subscriptions. Use the star symbol to subscribe to channels. + 구독 항목이 없습니다. 채널을 구독 하려면 별 아이콘을 사용 하세요. + All Videos 모든 비디오 Unwatched Videos - 안 본 비디오 + 시청하지 않은 비디오 Mark as Watched - 본 비디오로 마크 + 모두 시청함으로 표시 Unsubscribe @@ -175,23 +187,12 @@ There are no updated subscriptions at this time. 현재 업데이트된 구독이 없습니다. - - You have no subscriptions. Use the star symbol to subscribe to channels. - 구독 항목이 없습니다. 채널을 구독 하려면 별 아이콘을 사용 하세요. - - - - ClearButton - - Clear - 지우기 - DataUtils Just now - + 지금 %n hour(s) ago @@ -202,11 +203,30 @@ - %n weeks(s) ago + %n month(s) ago + + K + K as in Kilo, i.e. thousands + + + + M + M stands for Millions + + + + B + B stands for Billions + + + + %1 views + %1 조회수 + - %n month(s) ago + %n week(s) ago @@ -251,22 +271,6 @@ DownloadManager - - This is just the demo version of %1. - %1의 데모 버전 입니다. - - - It can only download videos shorter than %1 minutes so you can test the download functionality. - 사용자가 다운로드 기능을 테스트 할수 있도록 %1보다 짧은 비디오만 다운로드 됩니다. - - - Continue - 계속 - - - Get the full version - 풀 버전 구입 - %1 downloaded in %2 %1 downloaded in %2 @@ -314,19 +318,19 @@ Extra The executable file has been tempered with, maybe by a virus. - + 실행 파일이 바이러스 또는 다른 외부 원인에 의해 손상되었습니다. %1 will not run. Try installing again. - + %1을 실행할 수 없습니다. 다시 설치하시기 바랍니다. Quit - + 종료 Reinstall - + 재설치 @@ -369,15 +373,15 @@ Mute - 무음 + 음소거 Seek forward - 앞으로찾기 + 되감기 Seek backward - 뒤로찾기 + 빨리감기 @@ -422,11 +426,11 @@ MainWindow &Window - + ì°½ (&W) &Minimize - + 최소화 (&M) &Stop @@ -434,7 +438,7 @@ Stop playback and go back to the search view - 재생을 멈추고 검색 보기로 이동 + 재생을 멈추고 검색 창로 이동 P&revious @@ -442,7 +446,7 @@ Go back to the previous track - 이전트랙으로 이동 + 이전 트랙으로 이동 S&kip @@ -526,7 +530,7 @@ Move &Down - 아래로이동(&D) + 아래로 이동(&D) Move down the selected videos in the playlist @@ -546,7 +550,7 @@ Bye - 안녕... + 안녕히 가세요. &Website @@ -632,10 +636,6 @@ &Float on Top 위에 떠있기(&F) - - &Adjust Window Size - - &Stop After This Video 이 비디오 재생 후 정지(&S) @@ -666,10 +666,18 @@ Restricted Mode - + 제한된 보기 Hide videos that may contain inappropriate content + 부적절한 내용을 포함한 동영상 숨기기 + + + Toggle &Menu Bar + + + + Menu @@ -750,7 +758,7 @@ &Loading... - + 로딩중 (&L) Leave &Full Screen @@ -766,11 +774,11 @@ Volume is muted - 볼륨 소거됨 + 볼륨 음소거됨 Volume is unmuted - 볼륨 소거 해제됨 + 볼륨 응소거 해제됨 Maximum video definition set to %1 @@ -796,6 +804,10 @@ Update 업데이트 + + You can still access the menu bar by pressing the ALT key + + MediaView @@ -811,22 +823,6 @@ The link will be valid only for a limited time. 해당 링크는 제한된 시간 동안만 유효 합니다. - - This is just the demo version of %1. - %1의 데모 버전 입니다.%1의 데모 버전 입니다. - - - It allows you to test the application and see if it works for you. - 이 버전으로 프로그램이 사용자 필요에 맞는지 테스트 할수 있습니다. - - - Get the full version - 풀 버전 구입 - - - Continue - 계속 - Downloading %1 %1 다운로드 @@ -834,7 +830,7 @@ of Used in video parts, as in '2 of 3' - / + of part @@ -859,12 +855,16 @@ %1 구독 - Unsubscribed from %1 + Switched to %1 + + Unsubscribed from %1 + %1 구독 해지됨 + Subscribed to %1 - + %1 구독됨 @@ -902,11 +902,14 @@ - PlaylistItemDelegate + PickMessage - %1 views - %1 views + Pick a video + + + + PlaylistItemDelegate %1 of %2 (%3) — %4 %1 of %2 (%3) — %4 @@ -946,10 +949,6 @@ PlaylistModel - - Searching... - 검색중... - Show %1 More %1 추가 보기 @@ -1071,25 +1070,16 @@ <a href='%1'>%2</a>에 오신걸 환영 합니다! - Enter - "Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos" - 언터 + to start watching videos. + 비디오 보기 시작 a keyword 키워드 - a channel - 채널 - - - to start watching videos. - 비디오 보기 시작 - - - Watch - 감상 + Enter + 언터 Recent keywords @@ -1110,6 +1100,10 @@ &Back 뒤로(&B) + + &Forward + + Forward to %1 %1(으)로 이동 @@ -1160,14 +1154,7 @@ Downloading %1... - - - - - Video - - Cannot get video stream for %1 - %1의 비디오 가져올수 없음 + %1 다운로드중 @@ -1362,7 +1349,14 @@ Worldwide - 전세계적 + 전세계 + + + + YTVideo + + Cannot get video stream for %1 + %1의 비디오 가져올수 없음 \ No newline at end of file diff --git a/locale/ky.ts b/locale/ky.ts index e379eef..be95ecc 100644 --- a/locale/ky.ts +++ b/locale/ky.ts @@ -25,6 +25,14 @@ Translate %1 to your native language using %2 %2 аркылуу %1'ду өз эне тилиңизге которуңуз + + Powered by %1 + + + + Open-source software + + Icon designed by %1. Иконканын автору %1. @@ -155,6 +163,10 @@ Show Updated Жаңыланганын көрсөтүү + + You have no subscriptions. Use the star symbol to subscribe to channels. + Сизде жазылуулар жок. Каналдарга жазылуу үчүн жылдызча символун колдонуңуз. + All Videos Бардык видеолор @@ -175,17 +187,6 @@ There are no updated subscriptions at this time. Учурда жаңыланган жазылуулар жок. - - You have no subscriptions. Use the star symbol to subscribe to channels. - Сизде жазылуулар жок. Каналдарга жазылуу үчүн жылдызча символун колдонуңуз. - - - - ClearButton - - Clear - Тазалоо - DataUtils @@ -202,11 +203,30 @@ - %n weeks(s) ago + %n month(s) ago + + K + K as in Kilo, i.e. thousands + + + + M + M stands for Millions + + + + B + B stands for Billions + + + + %1 views + %1 көрүү + - %n month(s) ago + %n week(s) ago @@ -251,22 +271,6 @@ DownloadManager - - This is just the demo version of %1. - Бул жөн эле %1'дун демо-версиясы. - - - It can only download videos shorter than %1 minutes so you can test the download functionality. - Жүктөө функционалдуулугун текшерүү үчүн, бул %1 минутадан кыскараак видеолорду гана жүктөп бере алат. - - - Continue - Улантуу - - - Get the full version - Толук жоромолун алуу - %1 downloaded in %2 %2 жерине %1 файлы жүктөлдү @@ -632,10 +636,6 @@ &Float on Top Үстүнөн &калкытуу - - &Adjust Window Size - - &Stop After This Video Бул видеодон кийин &токтотуу @@ -672,6 +672,14 @@ Hide videos that may contain inappropriate content + + Toggle &Menu Bar + + + + Menu + + &Love %1? Rate it! @@ -796,6 +804,10 @@ Update Жаңылоо + + You can still access the menu bar by pressing the ALT key + + MediaView @@ -811,22 +823,6 @@ The link will be valid only for a limited time. Чакан убакытка чейин гана шилтеме анык болот. - - This is just the demo version of %1. - Бул жөн эле %1'дун демо-версиясы. - - - It allows you to test the application and see if it works for you. - Бул тиркемени сынап көргөнгө мүмкүндүк берет. - - - Get the full version - Толук версиясын алуу - - - Continue - Улантуу - Downloading %1 %1 жүктөп алынууда @@ -858,6 +854,10 @@ Subscribe to %1 %1 каналына жазылуу + + Switched to %1 + + Unsubscribed from %1 @@ -902,11 +902,14 @@ - PlaylistItemDelegate + PickMessage - %1 views - %1 көрүү + Pick a video + + + + PlaylistItemDelegate %1 of %2 (%3) — %4 %1 / %2 (%3) — %4 @@ -946,10 +949,6 @@ PlaylistModel - - Searching... - Изделүүдө... - Show %1 More Дагы %1 видеону көрсөтүү @@ -1071,25 +1070,16 @@ <a href='%1'>%2</a>'га кош келиңиз, - Enter - "Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos" - Видеолорду + to start watching videos. + менен табып көрүү. a keyword ачкыч сөз - a channel - канал - - - to start watching videos. - менен табып көрүү. - - - Watch - Көрүү + Enter + Видеолорду Recent keywords @@ -1110,6 +1100,10 @@ &Back &Артка + + &Forward + + Forward to %1 %1 алга @@ -1163,13 +1157,6 @@ - - Video - - Cannot get video stream for %1 - %1 үчүн видео агымын алуу мүмкүн эмес - - YTRegions @@ -1365,4 +1352,11 @@ Бүткүл дүйнө + + YTVideo + + Cannot get video stream for %1 + %1 үчүн видео агымын алуу мүмкүн эмес + + \ No newline at end of file diff --git a/locale/locale.pri b/locale/locale.pri index edf9282..a5f367c 100644 --- a/locale/locale.pri +++ b/locale/locale.pri @@ -5,7 +5,7 @@ DEPENDPATH += $$PWD VPATH += $$PWD # ls -1 *.ts | tr '\n' ' ' -TRANSLATIONS += ar.ts ast.ts be.ts bg_BG.ts ca.ts ca_ES.ts cs_CZ.ts da.ts de_DE.ts el.ts en.ts es.ts es_AR.ts es_ES.ts es_MX.ts fi.ts fi_FI.ts fr.ts gl.ts he_IL.ts hr.ts hu.ts id.ts it.ts ja_JP.ts ko_KR.ts ky.ts ms_MY.ts nb.ts nl.ts nn.ts pl.ts pl_PL.ts pt.ts pt_BR.ts ro.ts ru.ts sk.ts sl.ts sq.ts sr.ts sv_SE.ts th.ts tr.ts uk.ts uk_UA.ts vi.ts zh_CN.ts zh_TW.ts +TRANSLATIONS += ar.ts ast.ts be.ts bg_BG.ts ca.ts ca_ES.ts cs_CZ.ts da.ts de_DE.ts el.ts en.ts en_GB.ts es.ts es_AR.ts es_ES.ts es_MX.ts fi.ts fi_FI.ts fr.ts gl.ts he_IL.ts hr.ts hu.ts id.ts it.ts ja_JP.ts ko_KR.ts ky.ts ms_MY.ts nb.ts nl.ts nn.ts pl.ts pl_PL.ts pt.ts pt_BR.ts pt_PT.ts ro.ts ru.ts sk.ts sl.ts sq.ts sr.ts sv_SE.ts th.ts tr.ts uk.ts uk_UA.ts vi.ts zh_CN.ts zh_TW.ts isEmpty(QMAKE_LRELEASE) { win32:QMAKE_LRELEASE = $$[QT_INSTALL_BINS]\lrelease.exe else:QMAKE_LRELEASE = $$[QT_INSTALL_BINS]/lrelease diff --git a/locale/ms_MY.ts b/locale/ms_MY.ts index 0aa3a61..9d6756b 100644 --- a/locale/ms_MY.ts +++ b/locale/ms_MY.ts @@ -25,6 +25,14 @@ Translate %1 to your native language using %2 Terjemah %1 kepada bahasa ibunda anda menggunakan %2 + + Powered by %1 + Diperkasakan oleh %1 + + + Open-source software + Perisian sumber-terbuka + Icon designed by %1. Ikon direka oleh %1. @@ -107,7 +115,7 @@ You have %n new video(s) - + Anda mempunyai %n video baharu @@ -155,6 +163,10 @@ Show Updated Papar Dikemaskini + + You have no subscriptions. Use the star symbol to subscribe to channels. + Anda tidak mempunyai langganan. Gunakan simbol bintang untuk melanggan saluran. + All Videos Semua Video @@ -175,17 +187,6 @@ There are no updated subscriptions at this time. Tiada langganan dikemaskini buat masa ini. - - You have no subscriptions. Use the star symbol to subscribe to channels. - Anda tidak mempunyai langganan. Gunakan simbol bintang untuk melanggan saluran. - - - - ClearButton - - Clear - Kosongkan - DataUtils @@ -195,19 +196,38 @@ %n hour(s) ago - + %n jam yang lalu %n day(s) ago - + %n hari yang lalu - %n weeks(s) ago - + %n month(s) ago + %n bulan yang lalu + + + K + K as in Kilo, i.e. thousands + K + + + M + M stands for Millions + M + + + B + B stands for Billions + B + + + %1 views + ditonton %1 kali - %n month(s) ago - + %n week(s) ago + %n minggu yang lalu @@ -251,22 +271,6 @@ DownloadManager - - This is just the demo version of %1. - Ini hanyalah versi demo %1. - - - It can only download videos shorter than %1 minutes so you can test the download functionality. - Ia hanya boleh muat turun video kurang daripada %1 minit supaya anda boleh menguji kefungsian muat turunnya. - - - Continue - Teruskan - - - Get the full version - Dapatkan versi penuh - %1 downloaded in %2 %1 dimuat turun dalam %2 @@ -277,7 +281,7 @@ %n Download(s) - + %n Muat Turun @@ -632,10 +636,6 @@ &Float on Top Te&rapung Diatas - - &Adjust Window Size - &Laras Saiz Tetingkap - &Stop After This Video &Henti Selepas Video Ini @@ -672,6 +672,14 @@ Hide videos that may contain inappropriate content Sembunyi video yang mengandungi kandungan tidak senonoh + + Toggle &Menu Bar + Togol Palang &Menu + + + Menu + Menu + &Love %1? Rate it! &Suka %1? Beri penarafan! @@ -796,6 +804,10 @@ Update Kemaskini + + You can still access the menu bar by pressing the ALT key + Anda masih boleh mencapai palang menu dengan menekan kekunci ALT + MediaView @@ -811,22 +823,6 @@ The link will be valid only for a limited time. Pautan hanya sah untuk masa yang terhad. - - This is just the demo version of %1. - Ini hanyalah versi demo %1. - - - It allows you to test the application and see if it works for you. - Ia membolehkan anda uji aplikasi dan lihat jika ia berfungsi untuk anda. - - - Get the full version - Dapatkan versi penuh - - - Continue - Teruskan - Downloading %1 Memuat turun %1 @@ -858,6 +854,10 @@ Subscribe to %1 Langgan ke %1 + + Switched to %1 + Beralih ke %1 + Unsubscribed from %1 Nyahlanggan daripada %1 @@ -902,11 +902,14 @@ - PlaylistItemDelegate + PickMessage - %1 views - ditonton %1 kali + Pick a video + Pilih satu video + + + PlaylistItemDelegate %1 of %2 (%3) — %4 %1 daripada %2 (%3) — %4 @@ -946,10 +949,6 @@ PlaylistModel - - Searching... - Menggelintar... - Show %1 More Papar %1 Lagi @@ -1071,25 +1070,16 @@ Selamat datang ke <a href='%1'>%2</a>, - Enter - "Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos" - Masukkan + to start watching videos. + untuk menonton video. a keyword kata kunci - a channel - saluran - - - to start watching videos. - untuk menonton video. - - - Watch - Tonton + Enter + Masukkan Recent keywords @@ -1110,6 +1100,10 @@ &Back &Undur + + &Forward + &Maju + Forward to %1 Maju ke %1 @@ -1163,13 +1157,6 @@ Memuat turun %1... - - Video - - Cannot get video stream for %1 - Tidak dapat strim video untuk %1 - - YTRegions @@ -1365,4 +1352,11 @@ Seluruh Dunia + + YTVideo + + Cannot get video stream for %1 + Tidak dapat strim video untuk %1 + + \ No newline at end of file diff --git a/locale/nb.ts b/locale/nb.ts index 8e6ae67..c0ebc30 100644 --- a/locale/nb.ts +++ b/locale/nb.ts @@ -25,6 +25,14 @@ Translate %1 to your native language using %2 Oversett %1 til ditt morsmÃ¥l ved hjelp av %2 + + Powered by %1 + + + + Open-source software + + Icon designed by %1. Ikonet er designet av %1. @@ -155,6 +163,10 @@ Show Updated Program Oppdatert + + You have no subscriptions. Use the star symbol to subscribe to channels. + Du har ingen abonnement. Bruk stjernesymbolet for Ã¥ abonnemere pÃ¥ kanaler. + All Videos Alle Videoer @@ -175,17 +187,6 @@ There are no updated subscriptions at this time. Det er ingen oppdaterte abonnement for øyeblikket - - You have no subscriptions. Use the star symbol to subscribe to channels. - Du har ingen abonnement. Bruk stjernesymbolet for Ã¥ abonnemere pÃ¥ kanaler. - - - - ClearButton - - Clear - Nullstill - DataUtils @@ -202,11 +203,30 @@ - %n weeks(s) ago + %n month(s) ago + + K + K as in Kilo, i.e. thousands + + + + M + M stands for Millions + + + + B + B stands for Billions + + + + %1 views + %1 visninger + - %n month(s) ago + %n week(s) ago @@ -251,22 +271,6 @@ DownloadManager - - This is just the demo version of %1. - Dette er kun demo-versjonen av %1. - - - It can only download videos shorter than %1 minutes so you can test the download functionality. - Den kan kun laste ned videoer pÃ¥ under %1 minutter, for at du skal kunne prøve ut nedlastingsfunksjonen. - - - Continue - Fortsett - - - Get the full version - Kjøp fullversjonen - %1 downloaded in %2 %1 nedlastet pÃ¥ %2 @@ -632,10 +636,6 @@ &Float on Top &Vis over andre - - &Adjust Window Size - &Tilpass Vindusstørrelse - &Stop After This Video &Stopp etter denne videoen @@ -672,6 +672,14 @@ Hide videos that may contain inappropriate content Skjul videoer som kan inneholde upassende innhold + + Toggle &Menu Bar + + + + Menu + + &Love %1? Rate it! &Liker du %1? Ranger den! @@ -796,6 +804,10 @@ Update Oppdater + + You can still access the menu bar by pressing the ALT key + + MediaView @@ -811,22 +823,6 @@ The link will be valid only for a limited time. Denne linken vil kun være gyldig i en begrenset tid. - - This is just the demo version of %1. - Dette er kun demoversjonen av %1. - - - It allows you to test the application and see if it works for you. - Dette gir deg muligheten til Ã¥ prøve ut applikasjonen og se om du den er noe for deg. - - - Get the full version - Kjøp fullversjonen - - - Continue - Fortsett - Downloading %1 Nedlasting %1 @@ -858,6 +854,10 @@ Subscribe to %1 Abonnér pÃ¥ %1 + + Switched to %1 + + Unsubscribed from %1 Du er utmeldt fra %1 @@ -902,11 +902,14 @@ - PlaylistItemDelegate + PickMessage - %1 views - %1 visninger + Pick a video + + + + PlaylistItemDelegate %1 of %2 (%3) — %4 %1 av %2 (%3) — %4 @@ -946,10 +949,6 @@ PlaylistModel - - Searching... - Søker... - Show %1 More Vis %1 Mer @@ -1071,25 +1070,16 @@ Velkommen til <a href='%1'>%2</a>, - Enter - "Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos" - Skriv + to start watching videos. + for Ã¥ begynne avspilling av video a keyword ett nøkkelord - a channel - en kanal - - - to start watching videos. - for Ã¥ begynne avspilling av video - - - Watch - Se + Enter + Skriv Recent keywords @@ -1110,6 +1100,10 @@ &Back &Tilbake + + &Forward + + Forward to %1 Fremover til %1 @@ -1163,13 +1157,6 @@ Laster ned %1... - - Video - - Cannot get video stream for %1 - Kan ikke hente mediastrøm for %1 - - YTRegions @@ -1365,4 +1352,11 @@ Over hele verden + + YTVideo + + Cannot get video stream for %1 + Kan ikke hente mediastrøm for %1 + + \ No newline at end of file diff --git a/locale/nl.ts b/locale/nl.ts index f4666af..3f2cbeb 100644 --- a/locale/nl.ts +++ b/locale/nl.ts @@ -25,6 +25,14 @@ Translate %1 to your native language using %2 Vertaal %1 naar uw moedertaal met behulp van %2 + + Powered by %1 + + + + Open-source software + + Icon designed by %1. Pictogram ontworpen door %1. @@ -96,7 +104,7 @@ AppWidget Download - + Downloaden @@ -155,6 +163,10 @@ Show Updated Toon bijgewerkte + + You have no subscriptions. Use the star symbol to subscribe to channels. + U heeft geen abonnementen. Gebruik het ster-symbool om te abonneren op kanalen. + All Videos Alle video's @@ -175,17 +187,6 @@ There are no updated subscriptions at this time. Er zijn op dit moment geen bijgewerkte abonnementen. - - You have no subscriptions. Use the star symbol to subscribe to channels. - U heeft geen abonnementen. Gebruik het ster-symbool om te abonneren op kanalen. - - - - ClearButton - - Clear - Wis - DataUtils @@ -202,11 +203,30 @@ - %n weeks(s) ago + %n month(s) ago + + K + K as in Kilo, i.e. thousands + + + + M + M stands for Millions + + + + B + B stands for Billions + + + + %1 views + %1 bekeken + - %n month(s) ago + %n week(s) ago @@ -251,22 +271,6 @@ DownloadManager - - This is just the demo version of %1. - Dit is slechts de demoversie van %1. - - - It can only download videos shorter than %1 minutes so you can test the download functionality. - Het kan alleen maar videos downloaden korter dan %1 minuten zodat u de downloadfunctionaliteit kunt testen. - - - Continue - Ga door - - - Get the full version - Verkrijg de volledige versie - %1 downloaded in %2 %1 gedownload in %2 @@ -314,11 +318,11 @@ Extra The executable file has been tempered with, maybe by a virus. - + Het uitvoerbare bestand is aangepast, misschien door een virus. %1 will not run. Try installing again. - + %1 kan niet starten. Probeer opnieuw te installeren. Quit @@ -632,10 +636,6 @@ &Float on Top &Zweef erboven - - &Adjust Window Size - - &Stop After This Video &Stop na deze video @@ -672,6 +672,14 @@ Hide videos that may contain inappropriate content + + Toggle &Menu Bar + &Menubalk weergeven/verbergen + + + Menu + Menu + &Love %1? Rate it! Vindt u %1 te &gek? Waardeer het! @@ -796,6 +804,10 @@ Update Werk bij + + You can still access the menu bar by pressing the ALT key + U kunt de menubalk nog steeds benaderen door op ALT te drukken + MediaView @@ -811,22 +823,6 @@ The link will be valid only for a limited time. De link zal maar een beperkte tijd geldig zijn. - - This is just the demo version of %1. - Dit is slechts de demoversie van %1. - - - It allows you to test the application and see if it works for you. - Het biedt de mogelijkheid de applicatie te testen en te beoordelen. - - - Get the full version - Verkrijg de volledige versie - - - Continue - Ga door - Downloading %1 Bezig met downloaden van %1 @@ -858,6 +854,10 @@ Subscribe to %1 Abonneer op %1 + + Switched to %1 + + Unsubscribed from %1 @@ -902,11 +902,14 @@ - PlaylistItemDelegate + PickMessage - %1 views - %1 bekeken + Pick a video + + + + PlaylistItemDelegate %1 of %2 (%3) — %4 %1 van %2 (%3) — %4 @@ -946,10 +949,6 @@ PlaylistModel - - Searching... - Bezig met zoeken... - Show %1 More Toon %1 meer @@ -1071,25 +1070,16 @@ Welkom bij <a href='%1'>%2</a>, - Enter - "Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos" - Typ + to start watching videos. + om te beginnen met het bekijken van video's. a keyword een zoekwoord - a channel - een kanaal - - - to start watching videos. - om te beginnen met het bekijken van video's. - - - Watch - Bekijk + Enter + Typ Recent keywords @@ -1110,6 +1100,10 @@ &Back &Terug + + &Forward + + Forward to %1 Spoel vooruit naar %1 @@ -1160,14 +1154,7 @@ Downloading %1... - - - - - Video - - Cannot get video stream for %1 - Kan de videostream niet verkrijgen voor %1 + Downloaden van %1... @@ -1365,4 +1352,11 @@ Wereldwijd + + YTVideo + + Cannot get video stream for %1 + Kan de videostream niet verkrijgen voor %1 + + \ No newline at end of file diff --git a/locale/nn.ts b/locale/nn.ts index 94c5c64..40af51a 100644 --- a/locale/nn.ts +++ b/locale/nn.ts @@ -25,6 +25,14 @@ Translate %1 to your native language using %2 Omset %1 til morsmÃ¥let ditt med %2 + + Powered by %1 + + + + Open-source software + + Icon designed by %1. Ikonet er utforma av %1. @@ -155,6 +163,10 @@ Show Updated Vis oppdaterte + + You have no subscriptions. Use the star symbol to subscribe to channels. + Du har ingen tingingar. Bruk stjernesymbolet for Ã¥ tinga kanalar. + All Videos Alle videoar @@ -175,17 +187,6 @@ There are no updated subscriptions at this time. Ingen oppdaterte tingingar enno. - - You have no subscriptions. Use the star symbol to subscribe to channels. - Du har ingen tingingar. Bruk stjernesymbolet for Ã¥ tinga kanalar. - - - - ClearButton - - Clear - Nullstill - DataUtils @@ -202,11 +203,30 @@ - %n weeks(s) ago + %n month(s) ago + + K + K as in Kilo, i.e. thousands + + + + M + M stands for Millions + + + + B + B stands for Billions + + + + %1 views + %1 visingar + - %n month(s) ago + %n week(s) ago @@ -251,22 +271,6 @@ DownloadManager - - This is just the demo version of %1. - Dette er berre demoutgÃ¥va av %1. - - - It can only download videos shorter than %1 minutes so you can test the download functionality. - Han kan berre lasta ned videoar pÃ¥ under %1 minutt, for at du skal kunna prøva ut nedlastingsfunksjonen. - - - Continue - Hald fram - - - Get the full version - Kjøp fullversjonen - %1 downloaded in %2 %1 lasta ned pÃ¥ %2 @@ -632,10 +636,6 @@ &Float on Top &Vis over andre - - &Adjust Window Size - - &Stop After This Video &Stopp etter denne videoen @@ -672,6 +672,14 @@ Hide videos that may contain inappropriate content + + Toggle &Menu Bar + + + + Menu + + &Love %1? Rate it! @@ -796,6 +804,10 @@ Update Oppdater + + You can still access the menu bar by pressing the ALT key + + MediaView @@ -811,22 +823,6 @@ The link will be valid only for a limited time. Denne lenkja vil berre vera gyldig i ei avgrensa tid. - - This is just the demo version of %1. - Dette er berre demoutgÃ¥va av %1. - - - It allows you to test the application and see if it works for you. - Dette lèt prøva ut programmet og sjÃ¥ om det er noko for deg. - - - Get the full version - Kjøp fullversjonen - - - Continue - Hald fram - Downloading %1 Lastar ned %1 @@ -858,6 +854,10 @@ Subscribe to %1 Ting %1 + + Switched to %1 + + Unsubscribed from %1 @@ -902,11 +902,14 @@ - PlaylistItemDelegate + PickMessage - %1 views - %1 visingar + Pick a video + + + + PlaylistItemDelegate %1 of %2 (%3) — %4 %1 av %2 (%3) — %4 @@ -946,10 +949,6 @@ PlaylistModel - - Searching... - Søkjer … - Show %1 More Vis %1 til @@ -1071,25 +1070,16 @@ Velkomen til <a href='%1'>%2</a>, - Enter - "Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos" - Skriv + to start watching videos. + for Ã¥ Ã¥ sjÃ¥ videoar. a keyword eit nøkkelord - a channel - ein kanal - - - to start watching videos. - for Ã¥ Ã¥ sjÃ¥ videoar. - - - Watch - Snurr film + Enter + Skriv Recent keywords @@ -1110,6 +1100,10 @@ &Back &Tilbake + + &Forward + + Forward to %1 Fram til %1 @@ -1163,13 +1157,6 @@ - - Video - - Cannot get video stream for %1 - Kan ikkje henta videostraumen til %1 - - YTRegions @@ -1365,4 +1352,11 @@ Heile verda + + YTVideo + + Cannot get video stream for %1 + Kan ikkje henta videostraumen til %1 + + \ No newline at end of file diff --git a/locale/pl.ts b/locale/pl.ts index 6b1aac8..760f2d1 100644 --- a/locale/pl.ts +++ b/locale/pl.ts @@ -25,6 +25,14 @@ Translate %1 to your native language using %2 Przetłumacz %1 na swój język używając %2 + + Powered by %1 + + + + Open-source software + + Icon designed by %1. Ikony zaprojektowane przez %1. @@ -155,6 +163,10 @@ Show Updated Pokaż nowości + + You have no subscriptions. Use the star symbol to subscribe to channels. + Nie masz żadnych subskrypcji. Użyj symbolu gwiazdy do subskrybowania kanałów. + All Videos Wszystkie filmy @@ -175,17 +187,6 @@ There are no updated subscriptions at this time. Obecnie nie ma żadnych aktualizacji subskrypcji. - - You have no subscriptions. Use the star symbol to subscribe to channels. - Nie masz żadnych subskrypcji. Użyj symbolu gwiazdy do subskrybowania kanałów. - - - - ClearButton - - Clear - Wyczyść - DataUtils @@ -202,11 +203,30 @@ - %n weeks(s) ago + %n month(s) ago + + K + K as in Kilo, i.e. thousands + + + + M + M stands for Millions + + + + B + B stands for Billions + + + + %1 views + Wyświetleń: %1 + - %n month(s) ago + %n week(s) ago @@ -251,22 +271,6 @@ DownloadManager - - This is just the demo version of %1. - To jest jedynie wersja demonstracyjna %1. - - - It can only download videos shorter than %1 minutes so you can test the download functionality. - Funkcja testowa - można pobierać filmy krótsze niż %1 minut. - - - Continue - Kontynuuj - - - Get the full version - Pobierz pełną wersję - %1 downloaded in %2 %1 pobrane w %2 @@ -632,10 +636,6 @@ &Float on Top &Zawsze na wierzchu - - &Adjust Window Size - Dostosuj wielkość okn&a - &Stop After This Video Zatrzymaj odtwarzanie, po obejrzeniu tego filmu @@ -672,6 +672,14 @@ Hide videos that may contain inappropriate content Ukryj filmy mogące zawierać nieodpowiednie treści + + Toggle &Menu Bar + Przełącz pasek &menu + + + Menu + + &Love %1? Rate it! &Kochasz %1? Oceń to! @@ -796,6 +804,10 @@ Update Aktualizuj + + You can still access the menu bar by pressing the ALT key + Wciąz masz dostęp do paska menu poprzez przyciśnięcie klawisza ALT + MediaView @@ -811,22 +823,6 @@ The link will be valid only for a limited time. Link będzie ważny tylko przez ograniczony czas. - - This is just the demo version of %1. - To jest jedynie wersja demonstracyjna %1. - - - It allows you to test the application and see if it works for you. - Pozwala ci to na testowanie i sprawdzenie działania aplikacji. - - - Get the full version - Uzyskaj pełną wersję - - - Continue - Kontynuuj - Downloading %1 Pobieranie %1 @@ -858,6 +854,10 @@ Subscribe to %1 Subskrybuj %1 + + Switched to %1 + + Unsubscribed from %1 Zakończono subskrypcję %1 @@ -902,11 +902,14 @@ - PlaylistItemDelegate + PickMessage - %1 views - Wyświetleń: %1 + Pick a video + + + + PlaylistItemDelegate %1 of %2 (%3) — %4 %1 z %2 (%3) — %4 @@ -946,10 +949,6 @@ PlaylistModel - - Searching... - Wyszukiwanie ... - Show %1 More Pokaż kolejne %1 @@ -1071,25 +1070,16 @@ Witaj w <a href='%1'>%2</a>, - Enter - "Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos" - Zatwierdź + to start watching videos. + aby rozpocząć oglądanie a keyword słowo kluczowe - a channel - kanał - - - to start watching videos. - aby rozpocząć oglądanie - - - Watch - Oglądaj + Enter + Zatwierdź Recent keywords @@ -1110,6 +1100,10 @@ &Back &Wstecz + + &Forward + + Forward to %1 Przewiń do %1 @@ -1163,13 +1157,6 @@ Pobieranie %1... - - Video - - Cannot get video stream for %1 - Nie można uzyskać dostępu do %1 - - YTRegions @@ -1365,4 +1352,11 @@ Ogólnoświatowy + + YTVideo + + Cannot get video stream for %1 + Nie można uzyskać dostępu do %1 + + \ No newline at end of file diff --git a/locale/pl_PL.ts b/locale/pl_PL.ts index f541c97..826dfb7 100644 --- a/locale/pl_PL.ts +++ b/locale/pl_PL.ts @@ -25,6 +25,14 @@ Translate %1 to your native language using %2 Przetłumacz %1 na swój język używając %2 + + Powered by %1 + + + + Open-source software + + Icon designed by %1. Ikony zaprojektowane przez %1. @@ -155,6 +163,10 @@ Show Updated Pokaż zaktualizowane + + You have no subscriptions. Use the star symbol to subscribe to channels. + Nie masz żadnej subskrypcji. Użyj symbolu gwiazdki, aby subskrybować kanały. + All Videos Wszystkie filmy @@ -175,17 +187,6 @@ There are no updated subscriptions at this time. Brak zaktualizowanych subskrypcji. - - You have no subscriptions. Use the star symbol to subscribe to channels. - Nie masz żadnej subskrypcji. Użyj symbolu gwiazdki, aby subskrybować kanały. - - - - ClearButton - - Clear - Wyczyść - DataUtils @@ -202,11 +203,30 @@ - %n weeks(s) ago + %n month(s) ago + + K + K as in Kilo, i.e. thousands + + + + M + M stands for Millions + + + + B + B stands for Billions + + + + %1 views + %1widziany + - %n month(s) ago + %n week(s) ago @@ -251,22 +271,6 @@ DownloadManager - - This is just the demo version of %1. - To jest tylko wersja demo %1. - - - It can only download videos shorter than %1 minutes so you can test the download functionality. - Może pobierać jedynie filmy krótsze niż %1 minut, dla przetestowania funkcji pobierania. - - - Continue - Dalej - - - Get the full version - Pobierz pełną wersję - %1 downloaded in %2 %1 ściągnięte w %2 @@ -632,10 +636,6 @@ &Float on Top &Ustaw na wierzchu - - &Adjust Window Size - &Dopasuj rozmiar okna - &Stop After This Video &Zatrzymaj po tym filmie @@ -672,6 +672,14 @@ Hide videos that may contain inappropriate content Ukryj filmy mogące zawierać nieodpowiednie treści + + Toggle &Menu Bar + Przełącz pasek &menu + + + Menu + + &Love %1? Rate it! Uwie&lbiasz %1? Oceń to! @@ -796,6 +804,10 @@ Update Zaktualizuj + + You can still access the menu bar by pressing the ALT key + Dostęp do paska menu można uzyskać naciskając klawisz ALT + MediaView @@ -811,22 +823,6 @@ The link will be valid only for a limited time. Link będzie ważny tylko przez ograniczony czas. - - This is just the demo version of %1. - To jest tylko wersja demo %1. - - - It allows you to test the application and see if it works for you. - Pozwala przetestować aplikację, i zobaczyć czy Ci odpowiada. - - - Get the full version - Zdobądź pełną wersję - - - Continue - Dalej - Downloading %1 Pobieranie %1 @@ -858,6 +854,10 @@ Subscribe to %1 Subskrybuj %1 + + Switched to %1 + + Unsubscribed from %1 Zaprzestano subskrybować %1 @@ -902,11 +902,14 @@ - PlaylistItemDelegate + PickMessage - %1 views - %1widziany + Pick a video + + + + PlaylistItemDelegate %1 of %2 (%3) — %4 %1 z %2 (%3) — %4 @@ -946,10 +949,6 @@ PlaylistModel - - Searching... - Szukanie... - Show %1 More Pokaż kolejne %1 @@ -1071,25 +1070,16 @@ Witaj w <a href='%1'>%2</a>, - Enter - "Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos" - Zatwierdź + to start watching videos. + aby rozpocząć oglądanie a keyword słowo kluczowe - a channel - kanał - - - to start watching videos. - aby rozpocząć oglądanie - - - Watch - Oglądaj + Enter + Zatwierdź Recent keywords @@ -1110,6 +1100,10 @@ &Back &Wstecz + + &Forward + + Forward to %1 Idź do %1 @@ -1163,13 +1157,6 @@ Pobieranie %1... - - Video - - Cannot get video stream for %1 - Strumieniowanie %1 nie powiodło się - - YTRegions @@ -1365,4 +1352,11 @@ Ogólnoświatowy + + YTVideo + + Cannot get video stream for %1 + Nie można uzyskać dostępu do %1 + + \ No newline at end of file diff --git a/locale/pt.ts b/locale/pt.ts index 8cd19c1..eb05ba1 100644 --- a/locale/pt.ts +++ b/locale/pt.ts @@ -25,6 +25,14 @@ Translate %1 to your native language using %2 Ajude a traduzir o %1 através do %2 + + Powered by %1 + + + + Open-source software + + Icon designed by %1. Ícone criado por %1. @@ -155,6 +163,10 @@ Show Updated Mostrar atualizados + + You have no subscriptions. Use the star symbol to subscribe to channels. + Ainda não possui subscrições. Utilize a estrela para subscrever os canais. + All Videos Todos os vídeos @@ -175,17 +187,6 @@ There are no updated subscriptions at this time. Não existem atualizações de subscrições. - - You have no subscriptions. Use the star symbol to subscribe to channels. - Ainda não possui subscrições. Utilize a estrela para subscrever os canais. - - - - ClearButton - - Clear - Limpar - DataUtils @@ -202,11 +203,30 @@ - %n weeks(s) ago + %n month(s) ago + + K + K as in Kilo, i.e. thousands + + + + M + M stands for Millions + + + + B + B stands for Billions + + + + %1 views + %1 visualizações + - %n month(s) ago + %n week(s) ago @@ -251,22 +271,6 @@ DownloadManager - - This is just the demo version of %1. - Esta é uma versão de demonstração do %1. - - - It can only download videos shorter than %1 minutes so you can test the download functionality. - Apenas pode transferir vídeos mais curtos que %1 minuto(s) de forma a testar a funcionalidade de transferência. - - - Continue - Continuar - - - Get the full version - Obter a versão completa - %1 downloaded in %2 %1 transferência em %2 @@ -632,10 +636,6 @@ &Float on Top &Flutuante na frente - - &Adjust Window Size - &Ajuste o tamanho da janela - &Stop After This Video Parar após es&te vídeo @@ -672,6 +672,14 @@ Hide videos that may contain inappropriate content + + Toggle &Menu Bar + + + + Menu + + &Love %1? Rate it! &Gosta? %1? Avalie! @@ -796,6 +804,10 @@ Update Atualizar + + You can still access the menu bar by pressing the ALT key + + MediaView @@ -811,22 +823,6 @@ The link will be valid only for a limited time. A ligação será válida por tempo limitado. - - This is just the demo version of %1. - Esta é uma versão de demonstração do %1. - - - It allows you to test the application and see if it works for you. - Permite-lhe testar ea aplicação e verificar se é do seu agrado. - - - Get the full version - Obter a versão completa - - - Continue - Continuar - Downloading %1 Transferência: %1 @@ -858,6 +854,10 @@ Subscribe to %1 Subscrever %1 + + Switched to %1 + + Unsubscribed from %1 Não subscrito de %1 @@ -902,11 +902,14 @@ - PlaylistItemDelegate + PickMessage - %1 views - %1 visualizações + Pick a video + + + + PlaylistItemDelegate %1 of %2 (%3) — %4 %1 de %2 (%3) — %4 @@ -946,10 +949,6 @@ PlaylistModel - - Searching... - Procura... - Show %1 More Mostrar mais %1 @@ -1071,25 +1070,16 @@ Bem-vindo ao <a href='%1'>%2</a> - Enter - "Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos" - Introduza + to start watching videos. + para começar a visualizar os vídeos. a keyword uma palavra-chave - a channel - um canal - - - to start watching videos. - para começar a visualizar os vídeos. - - - Watch - Ver + Enter + Introduza Recent keywords @@ -1110,6 +1100,10 @@ &Back &Recuar + + &Forward + + Forward to %1 Avançar para %1 @@ -1163,13 +1157,6 @@ - - Video - - Cannot get video stream for %1 - Não é possível obter a emissão de %1 - - YTRegions @@ -1365,4 +1352,11 @@ Global + + YTVideo + + Cannot get video stream for %1 + Não é possível obter a emissão de %1 + + \ No newline at end of file diff --git a/locale/pt_BR.ts b/locale/pt_BR.ts index 589af55..2330982 100644 --- a/locale/pt_BR.ts +++ b/locale/pt_BR.ts @@ -25,6 +25,14 @@ Translate %1 to your native language using %2 Traduza %1 para seu idioma nativo usando %2 + + Powered by %1 + Produzido por %1 + + + Open-source software + Software open-source + Icon designed by %1. Ícone desenhado por %1. @@ -107,7 +115,7 @@ You have %n new video(s) - + Você tem %n vídeo(s) novo(s)Você tem %n vídeo(s) novo(s) @@ -155,6 +163,10 @@ Show Updated Mostrar Atualização + + You have no subscriptions. Use the star symbol to subscribe to channels. + Você não tem assinaturas. Use o símbolo da estrela para assinar canais. + All Videos Todos Os Vídeos @@ -175,17 +187,6 @@ There are no updated subscriptions at this time. Não há assinaturas atualizadas neste momento. - - You have no subscriptions. Use the star symbol to subscribe to channels. - Você não tem assinaturas. Use o símbolo da estrela para assinar canais. - - - - ClearButton - - Clear - Limpar - DataUtils @@ -195,19 +196,38 @@ %n hour(s) ago - + %n hora(s) atrás%n hora(s) atrás %n day(s) ago - + %n dia(s) atrás%n dia(s) atrás - %n weeks(s) ago - + %n month(s) ago + %n mes(es) atrás%n mes(es) atrás + + + K + K as in Kilo, i.e. thousands + K + + + M + M stands for Millions + M + + + B + B stands for Billions + B + + + %1 views + %1 visualizações - %n month(s) ago - + %n week(s) ago + %n semana(s) atrás%n semana(s) atrás @@ -251,22 +271,6 @@ DownloadManager - - This is just the demo version of %1. - Esta é apenas a versão demonstração de %1. - - - It can only download videos shorter than %1 minutes so you can test the download functionality. - Só pode fazer download de vídeos menores que %1 minutos para que você possa testar a funcionalidade de download. - - - Continue - Continuar - - - Get the full version - Obter a versão completa - %1 downloaded in %2 %1 baixados em %2 @@ -277,7 +281,7 @@ %n Download(s) - + %n Downloads%n Download(s) @@ -326,7 +330,7 @@ Reinstall - Reinstale + Reinstalar @@ -632,10 +636,6 @@ &Float on Top &Sempre Acima - - &Adjust Window Size - &Ajuste o tamanho da janela - &Stop After This Video &Parar Após Este Vídeo @@ -670,7 +670,15 @@ Hide videos that may contain inappropriate content - Ocultar vídeos com conteúdo inapropriado + Ocultar vídeos que podem conter conteúdo inapropriado + + + Toggle &Menu Bar + Mostrar Barra De &Menu + + + Menu + Menu &Love %1? Rate it! @@ -796,6 +804,10 @@ Update Atualizar + + You can still access the menu bar by pressing the ALT key + Você ainda pode acessar a barra de menu pressionando a tecla ALT + MediaView @@ -811,22 +823,6 @@ The link will be valid only for a limited time. O link só será válido por um tempo limitado. - - This is just the demo version of %1. - Esta é apenas a versão demonstração de %1. - - - It allows you to test the application and see if it works for you. - Ele permite que você teste o aplicativo e veja se ele funciona para você. - - - Get the full version - Obter a versão completa - - - Continue - Continuar - Downloading %1 Baixando %1 @@ -858,9 +854,13 @@ Subscribe to %1 Assinar %1 + + Switched to %1 + Mudar para %1 + Unsubscribed from %1 - Desinscrito de %1 + Não subscrito de %1 Subscribed to %1 @@ -902,11 +902,14 @@ - PlaylistItemDelegate + PickMessage - %1 views - %1 visualizações + Pick a video + Escolher um vídeo + + + PlaylistItemDelegate %1 of %2 (%3) — %4 %1 de %2 (%3) — %4 @@ -946,10 +949,6 @@ PlaylistModel - - Searching... - Pesquisando... - Show %1 More Mostrar Mais %1 @@ -1071,25 +1070,16 @@ Bem-vindo ao <a href='%1'>%2</a>, - Enter - "Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos" - Digite + to start watching videos. + para começar a assistir vídeos. a keyword uma palavra-chave - a channel - um canal - - - to start watching videos. - para começar a assistir vídeos. - - - Watch - Assistir + Enter + Digite Recent keywords @@ -1110,6 +1100,10 @@ &Back &Voltar + + &Forward + &Avançar + Forward to %1 Avançar para %1 @@ -1163,13 +1157,6 @@ Baixando %1... - - Video - - Cannot get video stream for %1 - Não foi possível obter stream de vídeo de %1 - - YTRegions @@ -1365,4 +1352,11 @@ Mundial + + YTVideo + + Cannot get video stream for %1 + Não foi possível obter stream de vídeo de %1 + + \ No newline at end of file diff --git a/locale/pt_PT.ts b/locale/pt_PT.ts new file mode 100644 index 0000000..6caf996 --- /dev/null +++ b/locale/pt_PT.ts @@ -0,0 +1,1362 @@ + + + AboutView + + There's life outside the browser! + Existe vida para além do browser! + + + Version %1 + Versão %1 + + + Licensed to: %1 + Licenciado a: + + + %1 is Free Software but its development takes precious time. + %1 é Software Gratuito mas o seu desenvolvimento leva tempo precioso. + + + Please <a href='%1'>donate</a> to support the continued development of %2. + Por favor <a href='%1'>doe</a> para suportar o desenvolvimento continuado do %2. + + + Translate %1 to your native language using %2 + Traduza %1 para a sua língua utilizando %2 + + + Powered by %1 + + + + Open-source software + + + + Icon designed by %1. + Ícone desenhado por %1. + + + Released under the <a href='%1'>GNU General Public License</a> + Lançado nos termos da <a href='"%1'>GNU General Public License</a> + + + &Close + &Fechar + + + About + Sobre + + + + ActivationDialog + + Enter your License Details + Introduza os detalhes da sua Licença + + + &Email: + &Email: + + + &Code: + &Código: + + + + ActivationView + + Please license %1 + Por favor licencie %1 + + + This demo has expired. + Esta demonstração expirou. + + + The full version allows you to watch videos without interruptions. + A versão completa permite-lhe ver vídeos sem interrupções. + + + Without a license, the application will expire in %1 days. + Sem uma licença, a aplicação irá expirar em %1 dias. + + + By purchasing the full version, you will also support the hard work I put into creating %1. + Ao comprar a versão completa, também irá apoiar o trabalho árduo que pus %1. + + + Use Demo + Usar Demo + + + Enter License + Introduzir Licença + + + Buy License + Comprar Licença + + + + AppWidget + + Download + Descarregar + + + + ChannelAggregator + + By %1 + Por %1 + + + You have %n new video(s) + + + + + ChannelItemDelegate + + All Videos + Todos os Vídeos + + + Unwatched Videos + Vídeos não visualizados + + + + ChannelView + + Name + Nome + + + Last Updated + Actualizado ultimamente + + + Last Added + Adicionado ultimamente + + + Last Watched + Visualizado ultimamente + + + Most Watched + Mais visualizados + + + Sort by + Ordenar por + + + Mark all as watched + Marcar todos com vistos + + + Show Updated + Mostrar actualizados + + + You have no subscriptions. Use the star symbol to subscribe to channels. + Você não tem subscrições. Use o símbolo estrela para subscrever canais. + + + All Videos + Todos os Vídeos + + + Unwatched Videos + Vídeos não visualizados + + + Mark as Watched + Marcar como visto + + + Unsubscribe + Anular subscrição + + + There are no updated subscriptions at this time. + Não há subscrições actualizadas de momento. + + + + DataUtils + + Just now + Agora mesmo + + + %n hour(s) ago + + + + %n day(s) ago + + + + %n month(s) ago + + + + K + K as in Kilo, i.e. thousands + + + + M + M stands for Millions + + + + B + B stands for Billions + + + + %1 views + %1 visualizações + + + %n week(s) ago + + + + + DownloadItem + + bytes + bytes + + + KB + KB + + + MB + MB + + + bytes/sec + bytes/seg + + + KB/sec + KB/seg + + + MB/sec + MB/seg + + + seconds + segundos + + + minutes + minutos + + + %4 %5 remaining + %4 %5 restante + + + + DownloadManager + + %1 downloaded in %2 + %1 transferido em %2 + + + Download finished + Transferência concluída + + + %n Download(s) + + + + + DownloadSettings + + Change location... + Mudar local... + + + Choose the download location + Escolha o local de downloads + + + Download location changed. + Local de downloads mudado + + + Current downloads will still go in the previous location. + Transferências actuais ainda irão para o local prévio + + + Downloading to: %1 + A transferir para: %1 + + + + DownloadView + + Downloads + Transferências + + + + Extra + + The executable file has been tempered with, maybe by a virus. + O ficheiro executável foi alterado ilegalmente, provavelmente por um vírus. + + + %1 will not run. Try installing again. + %1 não irá correr. Tente instalar outra vez. + + + Quit + Sair + + + Reinstall + Reinstalar + + + + GlobalShortcuts + + Play + Reproduzir + + + Pause + Pausa + + + Play/Pause + Reproduzir/Pausa + + + Stop + Parar + + + Stop playing after current track + Parar reprodução após faixa actual + + + Next track + Próxima faixa + + + Previous track + Faixa anterior + + + Increase volume + Aumentar volume + + + Decrease volume + Baixar volume + + + Mute + Desactivar som + + + Seek forward + Avançar + + + Seek backward + Retroceder + + + + HomeView + + Search + Pesquisar + + + Find videos and channels by keyword + Encontrar vídeos e canais por palavra-chave + + + Browse + Navegar + + + Browse videos by category + Navegar vídeos por categoria + + + Subscriptions + Subscrições + + + Channel subscriptions + Subscrições de canais + + + Make yourself comfortable + Sinta-se confortável + + + + LoadingWidget + + Error + Erro + + + + MainWindow + + &Window + &Janela + + + &Minimize + &Minimizar + + + &Stop + &Parar + + + Stop playback and go back to the search view + Parar reprodução e voltar para à vista de pesquisa + + + P&revious + A&nterior + + + Go back to the previous track + Retroceder para a faixa anterior + + + S&kip + S&altar + + + Skip to the next video + Saltar para o próximo vídeo + + + &Play + &Reproduzir + + + Resume playback + Retomar reprodução + + + &Full Screen + &Ecrã Inteiro + + + Go full screen + Ir para ecrã inteiro + + + &Compact Mode + &Modo compacto + + + Hide the playlist and the toolbar + Esconder a lista de reprodução e a barra de ferramentas + + + Open the &YouTube Page + Abrir a &Página do YouTube + + + Go to the YouTube video page and pause playback + Ir para a página de vídeos do YouTube e pausar reprodução + + + Copy the YouTube &Link + Copiar a &ligação do YouTube + + + Copy the current video YouTube link to the clipboard + Copiar o URL do video actual do YouTube para a área de transferência + + + Copy the Video Stream &URL + Copiar o &URL da Stream do Vídeo + + + Copy the current video stream URL to the clipboard + Copiar o URL da emissão de vídeo actual para a área de transferência + + + Find Video &Parts + Encontrar &Partes do Vídeo + + + Find other video parts hopefully in the right order + Procurar outras partes do video, com esperança de estarem na ordenação correcta + + + &Remove + &Remover + + + Remove the selected videos from the playlist + Remover os vídeos selecionados da lista de reprodução + + + Move &Up + Mover &Para cima + + + Move up the selected videos in the playlist + Mover para cima os vídeos selecionados na lista de reprodução + + + Move &Down + Mover &Para baixo + + + Move down the selected videos in the playlist + Mover para baixo os vídeos selecionados na lista de reprodução + + + &Clear Recent Searches + &Limpar as Pesquisas Recentes + + + Clear the search history. Cannot be undone. + Limpar o histórico de pesquisa. Não poderá ser desfeito. + + + &Quit + &Sair + + + Bye + Adeus + + + &Website + &Website + + + %1 on the Web + %1 na Web + + + Make a &Donation + Faça um &Donativo + + + Please support the continued development of %1 + Por favor apoie o desenvolvimento contínuo do %1 + + + &About + &Sobre + + + Info about %1 + Info sobre %1 + + + Search + Procurar + + + Mute volume + Desactivar som + + + &Manually Start Playing + &Começar a Reproduzir Manualmente + + + Manually start playing videos + Manualmente começar a reproduzir vídeos + + + &Downloads + &Transferências + + + Show details about video downloads + Mostrar detalhes sobre transferências de vídeos + + + &Download + &Download + + + Download the current video + Transferir o vídeo actual + + + Take &Snapshot + &Captura de ecrã + + + &Subscribe to Channel + &Subscrever ao Canal + + + Share the current video using %1 + Partilhar o video actual usando %1 + + + &Email + &Email + + + Email + Email + + + &Close + &Fechar + + + &Float on Top + &Flutuar no Topo + + + &Stop After This Video + &Parar Após Este Vídeo + + + &Report an Issue... + &Comunicar um Problema... + + + &Refine Search... + &Redefinir Pesquisa... + + + More... + Mais... + + + &Related Videos + &Vídeos Relacionados + + + Watch videos related to the current one + Ver vídeos relacionados com o actual + + + Open in &Browser... + Abrir no &Browser + + + Restricted Mode + Modo Restrito + + + Hide videos that may contain inappropriate content + Ocultar vídeos que possam conter conteúdo inapropriado + + + Toggle &Menu Bar + + + + Menu + + + + &Love %1? Rate it! + &Gosta do %1? Classifique-o! + + + Buy %1... + Comprar %1... + + + &Application + &Aplicação + + + &Playback + &Reprodução + + + &Playlist + &Lista de reprodução + + + &Video + &Vídeo + + + &Share + &Partilhar + + + &View + &Visualizar + + + &Help + &Ajuda + + + Press %1 to raise the volume, %2 to lower it + Pressione %1 para aumentar o volume, %2 para baixá-lo + + + Choose your content location + Escolha o local do seu conteúdo + + + Opening %1 + A abrir %1 + + + Do you want to exit %1 with a download in progress? + Deseja sair %1 com uma transferência activa? + + + If you close %1 now, this download will be cancelled. + Se fechar %1 agora, a transferência será cancelada. + + + Close and cancel download + Fechar e cancelar transferência + + + Wait for download to finish + Aguarde para que a transferência termine + + + Error: %1 + Erro: %1 + + + &Pause + &Pausar + + + Pause playback + Pausar reprodução + + + &Loading... + &A carregar... + + + Leave &Full Screen + Sair do &Ecrã Completo + + + Remaining time: %1 + Tempo restante: %1 + + + Volume at %1% + Volume a %1% + + + Volume is muted + O volume está silenciado + + + Volume is unmuted + O volume não está silenciado + + + Maximum video definition set to %1 + Definição de vídeo máxima definida para %1 + + + Your privacy is now safe + A sua privacidade está agora segura + + + Downloads complete + Transferência completa + + + %1 version %2 is now available. + %1 versão %2 está agora disponível. + + + Remind me later + Relembrar-me mais tarde + + + Update + Actualizar + + + You can still access the menu bar by pressing the ALT key + + + + + MediaView + + You can now paste the YouTube link into another application + Pode agora colar a ligação do Youtube para outra aplicação + + + You can now paste the video stream URL into another application + Agora você pode colar o URL do vídeo noutra aplicação + + + The link will be valid only for a limited time. + A ligação será válida apenas por um tempo limitado. + + + Downloading %1 + A transferir %1 + + + of + Used in video parts, as in '2 of 3' + de + + + part + This is for video parts, as in 'Cool video - part 1' + parte + + + episode + This is for video parts, as in 'Cool series - episode 1' + episódio + + + Sent from %1 + Enviado de %1 + + + Unsubscribe from %1 + Anular subscrição de %1 + + + Subscribe to %1 + Subscrever %1 + + + Switched to %1 + + + + Unsubscribed from %1 + Anular subscrição de %1 + + + Subscribed to %1 + Subscrito a %1 + + + + MessageWidget + + A new version of %1 is available! + Uma nova versão do %1 está disponível! + + + %1 %2 is now available. You have %3. + %1 %2 está agora disponível. Você tem %3. + + + Would you like to download it now? + Gostaria de descarregar agora? + + + Skip This Version + Saltar Esta Versão + + + Remind Me Later + Relembrar-me Mais Tarde + + + Install Update + Instalar Actualização + + + + PasteLineEdit + + Paste + Colar + + + + PickMessage + + Pick a video + + + + + PlaylistItemDelegate + + %1 of %2 (%3) — %4 + %1 de %2 (%3) — %4 + + + Preparing + A preparar + + + Failed + Falhou + + + Completed + Completado + + + Stopped + Parado + + + Stop downloading + Parar de transferir + + + Show in %1 + Mostrar em %1 + + + Open parent folder + Abrir a pasta-mãe + + + Restart downloading + Recomeçar a transferir + + + + PlaylistModel + + Show %1 More + Mostrar Mais %1 + + + No videos + Sem vídeos + + + No more videos + Sem mais vídeos + + + + RefineSearchWidget + + Sort by + Ordenar por + + + Relevance + Relevância + + + Date + Data + + + View Count + Contador de visualizações + + + Rating + Classificação + + + Anytime + Qualquer altura + + + Today + Hoje + + + 7 Days + 7 Dias + + + 30 Days + 30 Dias + + + Duration + Duração + + + All + Todos + + + Short + Curto + + + Medium + Médio + + + Long + Longo + + + Less than 4 minutes + Menos de 4 minutos + + + Between 4 and 20 minutes + Entre 4 a 20 minutos + + + Longer than 20 minutes + Mais de 20 minutos + + + Quality + Qualidade + + + High Definition + Alta-Definição + + + 720p or higher + 720p ou mais alto + + + Done + Feito + + + + RegionsView + + Done + Feito + + + + SearchLineEdit + + Search + Pesquisar + + + + SearchView + + Welcome to <a href='%1'>%2</a>, + Bem-vindo ao <a href='%1'>%2</a>, + + + to start watching videos. + para começar a ver vídeos. + + + a keyword + uma palava-chave + + + Enter + Insira + + + Recent keywords + Palavras-chave recentes + + + Recent channels + Canais recentes + + + Get the full version + Obter a versão completa + + + + SidebarHeader + + &Back + &Atrás + + + &Forward + + + + Forward to %1 + Avançar até %1 + + + Back to %1 + Retroceder até %1 + + + + SidebarWidget + + Refine Search + Redefinir Pesquisa + + + Did you mean: %1 + Queria dizer: %1 + + + + SnapshotSettings + + Change location... + Mudar local... + + + Snapshot saved to %1 + Captura de ecrã guardada em %1 + + + Snapshots location changed. + Local de capturas de ecrã mudado. + + + + StandardFeedsView + + Most Popular + Mais Popular + + + + UpdateDialog + + Downloading update... + A transferir actualização... + + + Downloading %1... + A transferir %1... + + + + YTRegions + + Algeria + Argélia + + + Argentina + Argentina + + + Australia + Austrália + + + Belgium + Bélgica + + + Brazil + Brasil + + + Canada + Canadá + + + Chile + Chile + + + Colombia + Colômbia + + + Czech Republic + República Checa + + + Egypt + Egipto + + + France + França + + + Germany + Alemanha + + + Ghana + Gana + + + Greece + Grécia + + + Hong Kong + Hong Kong + + + Hungary + Hungria + + + India + Índia + + + Indonesia + Indonésia + + + Ireland + Irlanda + + + Israel + Israel + + + Italy + Itália + + + Japan + Japão + + + Jordan + Jordânia + + + Kenya + Quénia + + + Malaysia + Malásia + + + Mexico + México + + + Morocco + Marrocos + + + Netherlands + Holanda + + + New Zealand + Nova Zelândia + + + Nigeria + Nigéria + + + Peru + Perú + + + Philippines + Filipinas + + + Poland + Polónia + + + Russia + Rússia + + + Saudi Arabia + Arábia Saudita + + + Singapore + Singapura + + + South Africa + África do Sul + + + South Korea + Coreia do Sul + + + Spain + Espanha + + + Sweden + Suécia + + + Taiwan + Ilha Formosa + + + Tunisia + Tunísia + + + Turkey + Turquia + + + Uganda + Uganda + + + United Arab Emirates + Emirados Árabes Unidos + + + United Kingdom + Reino Unido + + + Yemen + Iémen + + + Worldwide + Mundial + + + + YTVideo + + Cannot get video stream for %1 + Não é possível obter a stream do video para %1 + + + \ No newline at end of file diff --git a/locale/ro.ts b/locale/ro.ts index fd0155c..6f62bb5 100644 --- a/locale/ro.ts +++ b/locale/ro.ts @@ -25,6 +25,14 @@ Translate %1 to your native language using %2 Tradu %1 în limba proprie folosind %2 + + Powered by %1 + + + + Open-source software + + Icon designed by %1. Iconița a fost concepută de %1. @@ -96,7 +104,7 @@ AppWidget Download - + Descarcă @@ -155,6 +163,10 @@ Show Updated Arata actualizeazările + + You have no subscriptions. Use the star symbol to subscribe to channels. + Nu esti abonat la nimic. Ca sa te abonezi la canale, foloseste simbolul stea. + All Videos Toate videoclipurile @@ -175,17 +187,6 @@ There are no updated subscriptions at this time. Nici unul din canalele la care esti abonat nu are actualizari momentan. - - You have no subscriptions. Use the star symbol to subscribe to channels. - Nu esti abonat la nimic. Ca sa te abonezi la canale, foloseste simbolul stea. - - - - ClearButton - - Clear - Șterge - DataUtils @@ -202,11 +203,30 @@ - %n weeks(s) ago + %n month(s) ago + + K + K as in Kilo, i.e. thousands + + + + M + M stands for Millions + + + + B + B stands for Billions + + + + %1 views + %1 vizionări + - %n month(s) ago + %n week(s) ago @@ -251,22 +271,6 @@ DownloadManager - - This is just the demo version of %1. - Aceasta este doar o versiune demo a %1. - - - It can only download videos shorter than %1 minutes so you can test the download functionality. - Poate să descarce doar videoclipurile mai mici de %1 minute astfel încât să puteți testa funcționalitatea de descărcare. - - - Continue - Continuă - - - Get the full version - ObțineIa versiunea integrală - %1 downloaded in %2 %1 descărcat în %2 @@ -632,10 +636,6 @@ &Float on Top &Detașează - - &Adjust Window Size - &Ajustați dimensiunea ferestrei - &Stop After This Video &Oprește După Acest Videoclip @@ -666,10 +666,18 @@ Restricted Mode - + Mod restricționat Hide videos that may contain inappropriate content + Ascundeți videoclipuri care pot conține conținut neadecvat + + + Toggle &Menu Bar + + + + Menu @@ -796,6 +804,10 @@ Update Actualizează + + You can still access the menu bar by pressing the ALT key + + MediaView @@ -811,22 +823,6 @@ The link will be valid only for a limited time. Adresa va fi validă doar pentru o perioadă limitată de timp. - - This is just the demo version of %1. - Aceasta este doar o versiune demo a %1. - - - It allows you to test the application and see if it works for you. - Vă permite să testați aplicația și să vedeți dacă funcționează. - - - Get the full version - Obține versiunea integrală - - - Continue - Continuă - Downloading %1 Descărcare %1 @@ -858,6 +854,10 @@ Subscribe to %1 Aboneaza-te de la %1 + + Switched to %1 + + Unsubscribed from %1 Dezabonează-te de la %1 @@ -902,11 +902,14 @@ - PlaylistItemDelegate + PickMessage - %1 views - %1 vizionări + Pick a video + + + + PlaylistItemDelegate %1 of %2 (%3) — %4 %1 din %2 (%3) — %4 @@ -946,10 +949,6 @@ PlaylistModel - - Searching... - Căutare... - Show %1 More Afișează încă %1 @@ -1071,25 +1070,16 @@ Bine ați venit la <a href='%1'>%2</a>, - Enter - "Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos" - Introduceți + to start watching videos. + pentru a începe să vizionați videoclipuri. a keyword un cuvânt cheie - a channel - un canal - - - to start watching videos. - pentru a începe să vizionați videoclipuri. - - - Watch - Urmărește + Enter + Introduceți Recent keywords @@ -1110,6 +1100,10 @@ &Back Î&napoi + + &Forward + + Forward to %1 Avanseaza la %1 @@ -1142,7 +1136,7 @@ Snapshots location changed. - + Locația instantaneelor a fost modificată. @@ -1160,14 +1154,7 @@ Downloading %1... - - - - - Video - - Cannot get video stream for %1 - Nu poate fi accesat fluxul video pentru %1 + Se descarcă %1 @@ -1365,4 +1352,11 @@ Global + + YTVideo + + Cannot get video stream for %1 + Nu poate fi accesat fluxul video pentru %1 + + \ No newline at end of file diff --git a/locale/ru.ts b/locale/ru.ts index a810188..179bbb5 100644 --- a/locale/ru.ts +++ b/locale/ru.ts @@ -25,6 +25,14 @@ Translate %1 to your native language using %2 Перевести %1 на ваш родной язык с помощью %2 + + Powered by %1 + + + + Open-source software + + Icon designed by %1. Автор значка %1. @@ -156,6 +164,10 @@ Show Updated Показать обновленные + + You have no subscriptions. Use the star symbol to subscribe to channels. + У вас нет подписок. Используйте символ звездочки чтобы подписаться на каналы. + All Videos Все видео @@ -176,17 +188,6 @@ There are no updated subscriptions at this time. В настоящее время нет обновлений подписок. - - You have no subscriptions. Use the star symbol to subscribe to channels. - У вас нет подписок. Используйте символ звездочки чтобы подписаться на каналы. - - - - ClearButton - - Clear - Очистить - DataUtils @@ -203,11 +204,30 @@ - %n weeks(s) ago + %n month(s) ago + + K + K as in Kilo, i.e. thousands + + + + M + M stands for Millions + + + + B + B stands for Billions + + + + %1 views + %1 просмотров + - %n month(s) ago + %n week(s) ago @@ -252,22 +272,6 @@ DownloadManager - - This is just the demo version of %1. - Это всего лишь демо версия %1. - - - It can only download videos shorter than %1 minutes so you can test the download functionality. - Можно загружать только видео не длиннее %1 минут, для проверки функциональности загрузчика. - - - Continue - Продолжить - - - Get the full version - Получить полную версию - %1 downloaded in %2 %1 загружен в %2 @@ -633,10 +637,6 @@ &Float on Top &Поверх всех окон - - &Adjust Window Size - &Подстраивать размер окна - &Stop After This Video Ост&ановить после этого видео @@ -673,6 +673,14 @@ Hide videos that may contain inappropriate content Скрыть видео, содержащие недопустимый контент + + Toggle &Menu Bar + + + + Menu + + &Love %1? Rate it! &Нравится %1? Оцени! @@ -797,6 +805,10 @@ Update Обновление + + You can still access the menu bar by pressing the ALT key + + MediaView @@ -812,22 +824,6 @@ The link will be valid only for a limited time. Адрес будет существовать ограниченное время. - - This is just the demo version of %1. - Данная программа является демо-версией %1. - - - It allows you to test the application and see if it works for you. - Она позволяет вам оценить приложение. - - - Get the full version - Купить полную версию - - - Continue - Продолжить - Downloading %1 Загружаю %1 @@ -859,6 +855,10 @@ Subscribe to %1 Подписаться на %1 + + Switched to %1 + + Unsubscribed from %1 Отписаны от %1 @@ -903,11 +903,14 @@ - PlaylistItemDelegate + PickMessage - %1 views - %1 просмотров + Pick a video + + + + PlaylistItemDelegate %1 of %2 (%3) — %4 %1 из %2 (%3) — %4 @@ -947,10 +950,6 @@ PlaylistModel - - Searching... - Идет поиск... - Show %1 More Показать ещё %1 @@ -1072,25 +1071,16 @@ Добро пожаловать в <a href='%1'>%2</a>, - Enter - "Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos" - Введите + to start watching videos. + чтобы начать просмотр. a keyword запрос - a channel - канал - - - to start watching videos. - чтобы начать просмотр. - - - Watch - Смотреть + Enter + Введите Recent keywords @@ -1111,6 +1101,10 @@ &Back Н&азад + + &Forward + + Forward to %1 Вперед к %1 @@ -1164,13 +1158,6 @@ Загрузка %1... - - Video - - Cannot get video stream for %1 - Не удалось получить видео поток для %1 - - YTRegions @@ -1366,4 +1353,11 @@ Во всем мире + + YTVideo + + Cannot get video stream for %1 + Не удалось получить видео поток для %1 + + \ No newline at end of file diff --git a/locale/sk.ts b/locale/sk.ts index d393d1b..61d4fd2 100644 --- a/locale/sk.ts +++ b/locale/sk.ts @@ -25,6 +25,14 @@ Translate %1 to your native language using %2 Prelož %1 do svojho materinského jazyka cez %2 + + Powered by %1 + + + + Open-source software + + Icon designed by %1. Ikonu nadizajnoval %1. @@ -107,7 +115,7 @@ You have %n new video(s) - + @@ -155,6 +163,10 @@ Show Updated ZobraziÅ¥ aktualizované + + You have no subscriptions. Use the star symbol to subscribe to channels. + Nemáš žiadne subskripcie. Použi symbol hviezdy pre odoberanie kanálov. + All Videos VÅ¡etky videá @@ -175,17 +187,6 @@ There are no updated subscriptions at this time. Nie sú k dispozícii žiadne aktualizované subskripcie. - - You have no subscriptions. Use the star symbol to subscribe to channels. - Nemáš žiadne subskripcie. Použi symbol hviezdy pre odoberanie kanálov. - - - - ClearButton - - Clear - VyčisiÅ¥ - DataUtils @@ -195,19 +196,38 @@ %n hour(s) ago - + %n day(s) ago - + - %n weeks(s) ago - + %n month(s) ago + + + + K + K as in Kilo, i.e. thousands + + + + M + M stands for Millions + + + + B + B stands for Billions + + + + %1 views + %1 prezretí - %n month(s) ago - + %n week(s) ago + @@ -251,22 +271,6 @@ DownloadManager - - This is just the demo version of %1. - Ide o demoverziu %1. - - - It can only download videos shorter than %1 minutes so you can test the download functionality. - Umožní ti stiahnuÅ¥ iba videý kratÅ¡ie ako %1 minút, takže aspoň môžeÅ¡ otestovaÅ¥ túto funkcionalitu. - - - Continue - Pokračuj - - - Get the full version - ZískaÅ¥ plnú verziu - %1 downloaded in %2 %1 stiahnuté za %2 @@ -277,7 +281,7 @@ %n Download(s) - + @@ -632,10 +636,6 @@ &Float on Top &Vždy na vrchu - - &Adjust Window Size - &UpraviÅ¥ veľkosÅ¥ okna - &Stop After This Video &Zastav po tomto videu @@ -672,6 +672,14 @@ Hide videos that may contain inappropriate content + + Toggle &Menu Bar + + + + Menu + + &Love %1? Rate it! %Páči sa vám %1? ohodnoÅ¥te ho! @@ -796,6 +804,10 @@ Update Aktualizácia + + You can still access the menu bar by pressing the ALT key + + MediaView @@ -811,22 +823,6 @@ The link will be valid only for a limited time. Odkaz bude platný len obmedzenú dobu. - - This is just the demo version of %1. - Ide o demoverziu %1. - - - It allows you to test the application and see if it works for you. - Umožní ti aplikáciu vyskúšaÅ¥ a pohodlne otestovaÅ¥. - - - Get the full version - ZískaÅ¥ plnú verziu - - - Continue - Pokračuj - Downloading %1 SÅ¥ahujem %1. @@ -858,6 +854,10 @@ Subscribe to %1 OdoberaÅ¥ z %1 + + Switched to %1 + + Unsubscribed from %1 Bol zruÅ¡ený odber kanála %1 @@ -902,11 +902,14 @@ - PlaylistItemDelegate + PickMessage - %1 views - %1 prezretí + Pick a video + + + + PlaylistItemDelegate %1 of %2 (%3) — %4 %1 z %2 (%3) — %4 @@ -946,10 +949,6 @@ PlaylistModel - - Searching... - Hľadám... - Show %1 More Zobraz %1 viac @@ -1071,25 +1070,16 @@ Vitaj v aplikácii <a href='%1>%2</a>, - Enter - "Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos" - Vlož + to start watching videos. + pre spustenie sledovania. a keyword kľúčové slovo - a channel - názov kanálu - - - to start watching videos. - pre spustenie sledovania. - - - Watch - PozeraÅ¥ + Enter + Vlož Recent keywords @@ -1110,6 +1100,10 @@ &Back &Späť + + &Forward + + Forward to %1 Vpred k %1 @@ -1163,13 +1157,6 @@ - - Video - - Cannot get video stream for %1 - Nedostupný video stream pre %1 - - YTRegions @@ -1365,4 +1352,11 @@ Celosvetovo + + YTVideo + + Cannot get video stream for %1 + Nedostupný video stream pre %1 + + \ No newline at end of file diff --git a/locale/sl.ts b/locale/sl.ts index bfa3343..3f5ea59 100644 --- a/locale/sl.ts +++ b/locale/sl.ts @@ -25,6 +25,14 @@ Translate %1 to your native language using %2 Prevedite %1 v vaÅ¡ jezik z uporabo programa %2 + + Powered by %1 + + + + Open-source software + + Icon designed by %1. Ikone je izrisal %1. @@ -96,7 +104,7 @@ AppWidget Download - + Prenesi @@ -155,6 +163,10 @@ Show Updated Pokaži posodobitve + + You have no subscriptions. Use the star symbol to subscribe to channels. + Nimate naročnin. Uporabite zvezdo da se naročite na kanale. + All Videos Vsi videoposnetki @@ -175,17 +187,6 @@ There are no updated subscriptions at this time. Za zdaj ni nobenih novih posnetkov, na katere ste prijavljeni - - You have no subscriptions. Use the star symbol to subscribe to channels. - Nimate naročnin. Uporabite zvezdo da se naročite na kanale. - - - - ClearButton - - Clear - Počisti - DataUtils @@ -202,11 +203,30 @@ - %n weeks(s) ago + %n month(s) ago + + K + K as in Kilo, i.e. thousands + + + + M + M stands for Millions + + + + B + B stands for Billions + + + + %1 views + %1 predvajanj + - %n month(s) ago + %n week(s) ago @@ -251,22 +271,6 @@ DownloadManager - - This is just the demo version of %1. - To je samo preizkusna različica programa %1. - - - It can only download videos shorter than %1 minutes so you can test the download functionality. - Prenese lahko samo posnetke krajÅ¡e od %1 minut, da lahko preverite delovanje funkcije prenosa. - - - Continue - Nadaljuj - - - Get the full version - Pridobi popolno različico - %1 downloaded in %2 %1 preneseno v %2 @@ -632,10 +636,6 @@ &Float on Top Lebdeče na vrhu - - &Adjust Window Size - &Prilagodi velikost okna - &Stop After This Video U&stavi za tem posnetkom @@ -666,10 +666,18 @@ Restricted Mode - + Omejeni način Hide videos that may contain inappropriate content + Skriti videoposnetki lahko vsebujejo neprimerno vsebino. + + + Toggle &Menu Bar + + + + Menu @@ -796,6 +804,10 @@ Update Posodobitev + + You can still access the menu bar by pressing the ALT key + + MediaView @@ -811,22 +823,6 @@ The link will be valid only for a limited time. Povezava bo delovala le za omejen čas. - - This is just the demo version of %1. - To je samo demo različica programa %1. - - - It allows you to test the application and see if it works for you. - Dovoli vam testiranje aplikacije in preverjanje delovanja, - - - Get the full version - Pridobi celotno različico - - - Continue - Nadaljuj - Downloading %1 PrenaÅ¡anje %1 @@ -858,6 +854,10 @@ Subscribe to %1 Naroči se na %1 + + Switched to %1 + + Unsubscribed from %1 Odjavljeni od %1 @@ -902,11 +902,14 @@ - PlaylistItemDelegate + PickMessage - %1 views - %1 predvajanj + Pick a video + + + + PlaylistItemDelegate %1 of %2 (%3) — %4 %1 od %2 (%3) — %4 @@ -946,10 +949,6 @@ PlaylistModel - - Searching... - Iskanje ... - Show %1 More Pokaži %1 več @@ -1071,25 +1070,16 @@ Pozdravljeni v <a href='%1'>%2</a>, - Enter - "Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos" - Vnesite + to start watching videos. + in začnite gledati posnetke. a keyword ključno besedo - a channel - kanal - - - to start watching videos. - in začnite gledati posnetke. - - - Watch - Glejte + Enter + Vnesite Recent keywords @@ -1110,6 +1100,10 @@ &Back Na&zaj + + &Forward + + Forward to %1 Naprej na %1 @@ -1160,14 +1154,7 @@ Downloading %1... - - - - - Video - - Cannot get video stream for %1 - Za %1 ni mogoče pridobiti video pretoka + PrenaÅ¡anje %1 ... @@ -1365,4 +1352,11 @@ Svetovno + + YTVideo + + Cannot get video stream for %1 + Za %1 ni mogoče pridobiti video pretoka + + \ No newline at end of file diff --git a/locale/sq.ts b/locale/sq.ts index 02f56b3..bebe518 100644 --- a/locale/sq.ts +++ b/locale/sq.ts @@ -26,6 +26,14 @@ Translate %1 to your native language using %2 Perkthe %1 ne gjuhen e tuaj ame duke perdorur %2 + + Powered by %1 + + + + Open-source software + + Icon designed by %1. Dizajnimi i ikones u be nga %1 @@ -156,6 +164,10 @@ Show Updated Shfaq arrnimin + + You have no subscriptions. Use the star symbol to subscribe to channels. + Nuk keni abonime . Perdor simbolin yll për tu abonuar te kanalet + All Videos T'gjitha videot @@ -176,17 +188,6 @@ There are no updated subscriptions at this time. Nuk ka abonime të freskuar për momentin - - You have no subscriptions. Use the star symbol to subscribe to channels. - Nuk keni abonime . Perdor simbolin yll për tu abonuar te kanalet - - - - ClearButton - - Clear - Paster - DataUtils @@ -203,11 +204,30 @@ - %n weeks(s) ago + %n month(s) ago + + K + K as in Kilo, i.e. thousands + + + + M + M stands for Millions + + + + B + B stands for Billions + + + + %1 views + %1 e shikimeve + - %n month(s) ago + %n week(s) ago @@ -252,22 +272,6 @@ DownloadManager - - This is just the demo version of %1. - Ky eshte vetem version per demonstrim i %1 - - - It can only download videos shorter than %1 minutes so you can test the download functionality. - Mund te shkarkoj vetem video me te shkurta se %1 minut ne menyr qe te testoni funksionimin e shkarkuesit. - - - Continue - Vazhdon - - - Get the full version - Merrni versionin e plote - %1 downloaded in %2 %1 shkarkuar në %2 @@ -633,10 +637,6 @@ &Float on Top &Nxjerr ne Krye - - &Adjust Window Size - - &Stop After This Video &Ndalo pas kesaj video @@ -673,6 +673,14 @@ Hide videos that may contain inappropriate content + + Toggle &Menu Bar + + + + Menu + + &Love %1? Rate it! @@ -797,6 +805,10 @@ Update Arrnim + + You can still access the menu bar by pressing the ALT key + + MediaView @@ -812,22 +824,6 @@ The link will be valid only for a limited time. Linku do te jet i vlefshem per nje kohe te kufizuar - - This is just the demo version of %1. - Ky eshte version vetem per demonstrim i %1. - - - It allows you to test the application and see if it works for you. - Ju lejon qe te provoni programin dhe te shifni se a funksionon per ju . - - - Get the full version - Merrni versionin e plote - - - Continue - Vazhdim - Downloading %1 Duke shkarkuar %1 @@ -859,6 +855,10 @@ Subscribe to %1 Abonohu në %1 + + Switched to %1 + + Unsubscribed from %1 @@ -903,11 +903,14 @@ - PlaylistItemDelegate + PickMessage - %1 views - %1 e shikimeve + Pick a video + + + + PlaylistItemDelegate %1 of %2 (%3) — %4 %1 e %2 (%3)--%4 @@ -947,10 +950,6 @@ PlaylistModel - - Searching... - Duke kerkuar - Show %1 More Shfaq %1 më shumë @@ -1072,25 +1071,16 @@ Mire se erdhet ne <h href="%1">%2</a>, - Enter - "Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos" - Hyr + to start watching videos. + Per te filluar shikimin e videove. a keyword Nje fjal kyqe - a channel - Nje kanal - - - to start watching videos. - Per te filluar shikimin e videove. - - - Watch - Shiko + Enter + Hyr Recent keywords @@ -1111,6 +1101,10 @@ &Back Prapa + + &Forward + + Forward to %1 Përpara në %1 @@ -1164,13 +1158,6 @@ - - Video - - Cannot get video stream for %1 - Nuk mund te merr rrjedhen e videos per %1 - - YTRegions @@ -1366,4 +1353,11 @@ Mbar Bota + + YTVideo + + Cannot get video stream for %1 + Nuk mund te merr rrjedhen e videos per %1 + + \ No newline at end of file diff --git a/locale/sr.ts b/locale/sr.ts index 8663b60..15e77de 100644 --- a/locale/sr.ts +++ b/locale/sr.ts @@ -25,6 +25,14 @@ Translate %1 to your native language using %2 Преведите %1 на ваш матерњи језик помоћу %2 + + Powered by %1 + + + + Open-source software + + Icon designed by %1. Иконицу дизајниро %1. @@ -155,6 +163,10 @@ Show Updated Прикажи ажурирано + + You have no subscriptions. Use the star symbol to subscribe to channels. + Немате претплате. Користи звезду да се претплатиш. + All Videos Сви видеи @@ -175,17 +187,6 @@ There are no updated subscriptions at this time. Нема ажурираних субскрипција тренутно - - You have no subscriptions. Use the star symbol to subscribe to channels. - Немате претплате. Користи звезду да се претплатиш. - - - - ClearButton - - Clear - Очисти - DataUtils @@ -202,11 +203,30 @@ - %n weeks(s) ago + %n month(s) ago + + K + K as in Kilo, i.e. thousands + + + + M + M stands for Millions + + + + B + B stands for Billions + + + + %1 views + %1 прегледа + - %n month(s) ago + %n week(s) ago @@ -251,22 +271,6 @@ DownloadManager - - This is just the demo version of %1. - Ово је тек демо верзија %1. - - - It can only download videos shorter than %1 minutes so you can test the download functionality. - Може преузети само снимке краће од %1 минута тако да можете испробати употребљивост преузимања. - - - Continue - Настави - - - Get the full version - Преузмите пуну верзију - %1 downloaded in %2 %1 преузето у %2 @@ -632,10 +636,6 @@ &Float on Top &Флутај на врху - - &Adjust Window Size - &Прилагоди величину прозора - &Stop After This Video Зау&стави након овог видеа @@ -672,6 +672,14 @@ Hide videos that may contain inappropriate content + + Toggle &Menu Bar + + + + Menu + + &Love %1? Rate it! &Обожавате %1? Оцените! @@ -796,6 +804,10 @@ Update Ажурирај + + You can still access the menu bar by pressing the ALT key + + MediaView @@ -811,22 +823,6 @@ The link will be valid only for a limited time. Веза је исправна само одређено време. - - This is just the demo version of %1. - Ово је тек демо верзија %1. - - - It allows you to test the application and see if it works for you. - Омогућава вам да испробате програм и увидите да ли вам одговара. - - - Get the full version - Преузмите пуну верзију - - - Continue - Настави - Downloading %1 преузимам %1 @@ -858,6 +854,10 @@ Subscribe to %1 Претплати се на %1 + + Switched to %1 + + Unsubscribed from %1 Ун-претплаћен од %1 @@ -902,11 +902,14 @@ - PlaylistItemDelegate + PickMessage - %1 views - %1 прегледа + Pick a video + + + + PlaylistItemDelegate %1 of %2 (%3) — %4 %1 од %2 (%3) — %4 @@ -946,10 +949,6 @@ PlaylistModel - - Searching... - Тражим... - Show %1 More Прикажи још %1 @@ -1071,25 +1070,16 @@ Добродошли у <a href='%1'>%2</a>, - Enter - "Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos" - унесите + to start watching videos. + да би започели преглед видео снимака. a keyword кључну реч - a channel - канал - - - to start watching videos. - да би започели преглед видео снимака. - - - Watch - Гледај + Enter + унесите Recent keywords @@ -1110,6 +1100,10 @@ &Back &Назад + + &Forward + + Forward to %1 Напред до %1 @@ -1163,13 +1157,6 @@ - - Video - - Cannot get video stream for %1 - не могу да добавим видео ток за %1 - - YTRegions @@ -1365,4 +1352,11 @@ Широм света + + YTVideo + + Cannot get video stream for %1 + не могу да добавим видео ток за %1 + + \ No newline at end of file diff --git a/locale/sv_SE.ts b/locale/sv_SE.ts index b581eb3..88d809e 100644 --- a/locale/sv_SE.ts +++ b/locale/sv_SE.ts @@ -25,6 +25,14 @@ Translate %1 to your native language using %2 Översätt %1 till ditt modersmÃ¥l med %2 + + Powered by %1 + + + + Open-source software + + Icon designed by %1. Ikon designad av %1 @@ -155,6 +163,10 @@ Show Updated Visa Uppdaterat + + You have no subscriptions. Use the star symbol to subscribe to channels. + Du har inga prenumerationer. Använd stjärnsymbolen för att prenumerera pÃ¥ kanaler. + All Videos Alla Videor @@ -175,17 +187,6 @@ There are no updated subscriptions at this time. Det finns inga uppdaterade prenumerationer just nu. - - You have no subscriptions. Use the star symbol to subscribe to channels. - Du har inga prenumerationer. Använd stjärnsymbolen för att prenumerera pÃ¥ kanaler. - - - - ClearButton - - Clear - Rensa - DataUtils @@ -202,11 +203,30 @@ - %n weeks(s) ago + %n month(s) ago + + K + K as in Kilo, i.e. thousands + + + + M + M stands for Millions + + + + B + B stands for Billions + + + + %1 views + %1 visningar + - %n month(s) ago + %n week(s) ago @@ -251,22 +271,6 @@ DownloadManager - - This is just the demo version of %1. - Detta är bara en demoversion av %1. - - - It can only download videos shorter than %1 minutes so you can test the download functionality. - Den kan bara ladda ner filmer kortare än %1 minuter sÃ¥ att du kan testa ladda-ned funktionen. - - - Continue - Fortsätt - - - Get the full version - Skaffa den fullständiga versionen - %1 downloaded in %2 %1 nedladdad i %2 @@ -632,10 +636,6 @@ &Float on Top &Flyt ovanpÃ¥ - - &Adjust Window Size - &Justera Fönster Storleken - &Stop After This Video &Stoppa efter denna video @@ -672,6 +672,14 @@ Hide videos that may contain inappropriate content + + Toggle &Menu Bar + + + + Menu + + &Love %1? Rate it! &Älskar du %1? Betygsätt den! @@ -796,6 +804,10 @@ Update Uppdatera + + You can still access the menu bar by pressing the ALT key + + MediaView @@ -811,22 +823,6 @@ The link will be valid only for a limited time. Länken kommer att gälla endast under en begränsad tid. - - This is just the demo version of %1. - Detta är bara en demoversion av %1. - - - It allows you to test the application and see if it works for you. - Den tillÃ¥ter dig att testa programmet och se om det fungerar för dig. - - - Get the full version - Skaffa den fullständiga versionen - - - Continue - Fortsätt - Downloading %1 Hämtar %1 @@ -858,6 +854,10 @@ Subscribe to %1 Prenumerera pÃ¥ %1 + + Switched to %1 + + Unsubscribed from %1 Prenumeration avbruten frÃ¥n %1 @@ -902,11 +902,14 @@ - PlaylistItemDelegate + PickMessage - %1 views - %1 visningar + Pick a video + + + + PlaylistItemDelegate %1 of %2 (%3) — %4 %1 av %2 (%3) — %4 @@ -946,10 +949,6 @@ PlaylistModel - - Searching... - Söker... - Show %1 More Visa %1 Till @@ -1071,25 +1070,16 @@ Välkommen till <a href='%1'>%2</a>, - Enter - "Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos" - Ange + to start watching videos. + för att börja titta pÃ¥ video. a keyword ett sökord - a channel - en kanal - - - to start watching videos. - för att börja titta pÃ¥ video. - - - Watch - Titta + Enter + Ange Recent keywords @@ -1110,6 +1100,10 @@ &Back &Tillbaka + + &Forward + + Forward to %1 FramÃ¥t till %1 @@ -1163,13 +1157,6 @@ - - Video - - Cannot get video stream for %1 - Kan inte fÃ¥ videoström för %1 - - YTRegions @@ -1365,4 +1352,11 @@ FrÃ¥n Hela Världen + + YTVideo + + Cannot get video stream for %1 + Kan inte fÃ¥ videoström för %1 + + \ No newline at end of file diff --git a/locale/th.ts b/locale/th.ts index 99814e2..0992c98 100644 --- a/locale/th.ts +++ b/locale/th.ts @@ -25,6 +25,14 @@ Translate %1 to your native language using %2 แปล %1 สุ่ภาษาของคุณโดยใช้ %2 + + Powered by %1 + + + + Open-source software + + Icon designed by %1. ออกแบบไอคอนโดย %1 @@ -155,6 +163,10 @@ Show Updated แสดงอัพเดต + + You have no subscriptions. Use the star symbol to subscribe to channels. + คุณไม่มีการสมัครติดตาม ใช้สัญลักษณ์ดาวเพื่อสมัครติดตามช่อง + All Videos วิดีโอทั้งหมด @@ -175,17 +187,6 @@ There are no updated subscriptions at this time. ไม่มีการสมัครติดตามที่อัพเดต ณ เวลานี้ - - You have no subscriptions. Use the star symbol to subscribe to channels. - คุณไม่มีการสมัครติดตาม ใช้สัญลักษณ์ดาวเพื่อสมัครติดตามช่อง - - - - ClearButton - - Clear - ล้าง - DataUtils @@ -202,11 +203,30 @@ - %n weeks(s) ago + %n month(s) ago + + K + K as in Kilo, i.e. thousands + + + + M + M stands for Millions + + + + B + B stands for Billions + + + + %1 views + ดู %1 ครั้ง + - %n month(s) ago + %n week(s) ago @@ -251,22 +271,6 @@ DownloadManager - - This is just the demo version of %1. - นี้เป็นแค่เวอร์ชั่นทดลองใช้ของ %1 - - - It can only download videos shorter than %1 minutes so you can test the download functionality. - สามารถดาวน์โหลดวิดึโอส้ันๆกว่า %1 นาที เพื่อทดสอบฟังชั่นดาวน์โหลด - - - Continue - ต่อไป - - - Get the full version - รับเวอร์ชั่นเต็ม - %1 downloaded in %2 %1 ถูกดาวน์โหลดใน %2 @@ -632,10 +636,6 @@ &Float on Top &วางอยู่บนสุด - - &Adjust Window Size - - &Stop After This Video &หยุดหลังจากวิดีโอนี้ @@ -672,6 +672,14 @@ Hide videos that may contain inappropriate content + + Toggle &Menu Bar + + + + Menu + + &Love %1? Rate it! &ชอบ %1 มั้ย? ให้คะแนนมันหน่อย! @@ -796,6 +804,10 @@ Update อัพเดต + + You can still access the menu bar by pressing the ALT key + + MediaView @@ -811,22 +823,6 @@ The link will be valid only for a limited time. ลิงค์จะใช้ได้ในระยะเวลาที่จำกัดเท่านั้น - - This is just the demo version of %1. - นี้เป็นแค่เวอร์ชั่นทดลองใช้ %1. - - - It allows you to test the application and see if it works for you. - มันช่วยให้คุณทดสอบโปรแกรมและดูว่ามันใช้ได้ผลกับคุณหรือเปล่า - - - Get the full version - ทำให้เป็นเวอร์ชั่นเต็ม - - - Continue - ต่อไป - Downloading %1 กำลังดาวน์โหลด %1 @@ -858,6 +854,10 @@ Subscribe to %1 สมัครติดตามสู่ %1 + + Switched to %1 + + Unsubscribed from %1 @@ -902,11 +902,14 @@ - PlaylistItemDelegate + PickMessage - %1 views - ดู %1 ครั้ง + Pick a video + + + + PlaylistItemDelegate %1 of %2 (%3) — %4 %1 ของ %2 (%3) — %4 @@ -946,10 +949,6 @@ PlaylistModel - - Searching... - กำลังค้นหา... - Show %1 More แสดง %1 เพิ่มเติม @@ -1071,25 +1070,16 @@ ยินดีต้อนรับสู่ <a href='%1'>%2</a>, - Enter - "Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos" - ตกลง + to start watching videos. + เพื่อเริ่มรับชมวิดีโอ a keyword คำสำคัญ - a channel - ช่อง - - - to start watching videos. - เพื่อเริ่มรับชมวิดีโอ - - - Watch - รับชม + Enter + ตกลง Recent keywords @@ -1110,6 +1100,10 @@ &Back &กลับ + + &Forward + + Forward to %1 เดินหน้าสู่ %1 @@ -1163,13 +1157,6 @@ - - Video - - Cannot get video stream for %1 - ไม่สามารถรับกระแสวิดีโอของ %1 - - YTRegions @@ -1365,4 +1352,11 @@ ทั่วโลก + + YTVideo + + Cannot get video stream for %1 + ไม่สามารถรับกระแสวิดีโอของ %1 + + \ No newline at end of file diff --git a/locale/tr.ts b/locale/tr.ts index d2f4462..73a4a73 100644 --- a/locale/tr.ts +++ b/locale/tr.ts @@ -25,6 +25,14 @@ Translate %1 to your native language using %2 %1'i, %2 kullanarak kendi dilinize çevirin + + Powered by %1 + % 1 tarafından desteklenmektedir + + + Open-source software + Açık kaynaklı yazılım + Icon designed by %1. Simge %1 tarafından tasarlandı. @@ -107,7 +115,7 @@ You have %n new video(s) - + %n yeni videonuz var%n yeni videonuz var @@ -155,6 +163,10 @@ Show Updated Güncellenenleri Göster + + You have no subscriptions. Use the star symbol to subscribe to channels. + Hiç aboneliğiniz yok. Kanallara abone olmak için yıldız simgesini kullanın. + All Videos Tüm Videolar @@ -175,17 +187,6 @@ There are no updated subscriptions at this time. Şu anda güncellenen abonelik yok. - - You have no subscriptions. Use the star symbol to subscribe to channels. - Hiç aboneliğiniz yok. Kanallara abone olmak için yıldız simgesini kullanın. - - - - ClearButton - - Clear - Temizle - DataUtils @@ -195,19 +196,38 @@ %n hour(s) ago - + &n saat önce&n saat önce %n day(s) ago - + &n gün önce&n gün önce - %n weeks(s) ago - + %n month(s) ago + &n ay önce&n ay önce + + + K + K as in Kilo, i.e. thousands + K + + + M + M stands for Millions + M + + + B + B stands for Billions + B + + + %1 views + %1 görüntülenme - %n month(s) ago - + %n week(s) ago + &n hafta önce&n hafta önce @@ -251,22 +271,6 @@ DownloadManager - - This is just the demo version of %1. - Bu sadece %'in demo sürümüdür. - - - It can only download videos shorter than %1 minutes so you can test the download functionality. - Bu sadece %1 dakikadan kısa videoları indirebilir, indirme özelliğini böylece test edebilirsiniz. - - - Continue - Devam - - - Get the full version - Tam sürüme geç - %1 downloaded in %2 %2 de %1 indirildi @@ -277,7 +281,7 @@ %n Download(s) - + &n Ä°ndirme&n Ä°ndirme @@ -422,11 +426,11 @@ MainWindow &Window - Pencere + &Pencere &Minimize - Küçült + &Küçült &Stop @@ -632,10 +636,6 @@ &Float on Top Üstte Sabitle - - &Adjust Window Size - &Pencere Boyutunu Ayarla - &Stop After This Video Bu Videodan &Sonra Durdur @@ -672,6 +672,14 @@ Hide videos that may contain inappropriate content Uygunsuz içerik içerebilecek videoları gizle + + Toggle &Menu Bar + &Menü Çubuğunu Aç/Kapat + + + Menu + Menü + &Love %1? Rate it! %1 &seviyor musunuz? Değerlendirin! @@ -796,6 +804,10 @@ Update Güncelle + + You can still access the menu bar by pressing the ALT key + ALT tuşuna basarak hala menü çubuğuna erişebilirsiniz + MediaView @@ -811,22 +823,6 @@ The link will be valid only for a limited time. Bağlantı kısıtlı bir süre için geçerli olacak. - - This is just the demo version of %1. - Bu sadece %1'in demo sürümüdür. - - - It allows you to test the application and see if it works for you. - Bu, uygulamayı test etmenizi ve çalışıp çalışmadığını görmenizi sağlar. - - - Get the full version - Tam sürüme geç - - - Continue - Devam - Downloading %1 Ä°ndiriliyor %1 @@ -858,6 +854,10 @@ Subscribe to %1 %1 Abone Ol + + Switched to %1 + % 1 olarak değiştirildi + Unsubscribed from %1 %1 aboneliğinden çık @@ -902,11 +902,14 @@ - PlaylistItemDelegate + PickMessage - %1 views - %1 görüntülenme + Pick a video + Bir video seç + + + PlaylistItemDelegate %1 of %2 (%3) — %4 %1 of %2 (%3) — %4 @@ -946,10 +949,6 @@ PlaylistModel - - Searching... - Aranıyor... - Show %1 More %1 Tane Daha Göster @@ -1071,25 +1070,16 @@ <a href='%1'>%2</a>'a Hoşgeldiniz - Enter - "Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos" - Giriş yapın + to start watching videos. + ve videoları izlemeye başlayın. a keyword bir anahtar kelime - a channel - bir kanal - - - to start watching videos. - ve videoları izlemeye başlayın. - - - Watch - Ä°zle + Enter + Giriş yapın Recent keywords @@ -1110,6 +1100,10 @@ &Back Geri + + &Forward + &Ä°leri + Forward to %1 %1 Yönlendir @@ -1163,13 +1157,6 @@ %1 indiriliyor... - - Video - - Cannot get video stream for %1 - %1 için video akışı alınamıyor. - - YTRegions @@ -1365,4 +1352,11 @@ Dünya Çapında + + YTVideo + + Cannot get video stream for %1 + %1 için video akışı alınamıyor. + + \ No newline at end of file diff --git a/locale/uk.ts b/locale/uk.ts index 10dae88..fccc370 100644 --- a/locale/uk.ts +++ b/locale/uk.ts @@ -25,6 +25,14 @@ Translate %1 to your native language using %2 Перекласти %1 Вашою рідною мовою за допомогою %2 + + Powered by %1 + + + + Open-source software + + Icon designed by %1. Розробник піктоґрам %1. @@ -108,7 +116,7 @@ You have %n new video(s) - + @@ -156,6 +164,10 @@ Show Updated Показати оновлені + + You have no subscriptions. Use the star symbol to subscribe to channels. + У Вас немає підписок. Використовуйте символ зірочки, аби підписуватися на канали. + All Videos Усі видива @@ -176,17 +188,6 @@ There are no updated subscriptions at this time. Наразі оновлень підписок немає. - - You have no subscriptions. Use the star symbol to subscribe to channels. - У Вас немає підписок. Використовуйте символ зірочки, аби підписуватися на канали. - - - - ClearButton - - Clear - Очистити - DataUtils @@ -196,19 +197,38 @@ %n hour(s) ago - + %n day(s) ago - + - %n weeks(s) ago - + %n month(s) ago + + + + K + K as in Kilo, i.e. thousands + + + + M + M stands for Millions + + + + B + B stands for Billions + + + + %1 views + %1 переглядів - %n month(s) ago - + %n week(s) ago + @@ -252,22 +272,6 @@ DownloadManager - - This is just the demo version of %1. - Це демонстраційна версія %1. - - - It can only download videos shorter than %1 minutes so you can test the download functionality. - Із метою тестування, Ви можете завантажити видиво тривалістю до %1 хв. - - - Continue - Продовжити - - - Get the full version - Отримати повну версію - %1 downloaded in %2 %1 завантажений до %2 @@ -278,7 +282,7 @@ %n Download(s) - + @@ -633,10 +637,6 @@ &Float on Top &Згори всіх вікон - - &Adjust Window Size - &Змінити розмір вікна - &Stop After This Video Зу&пинити після цього видиво @@ -673,6 +673,14 @@ Hide videos that may contain inappropriate content Приховати відео, які можуть містити небажаний контент + + Toggle &Menu Bar + + + + Menu + + &Love %1? Rate it! &Подобається %1? Оцініть ! @@ -797,6 +805,10 @@ Update Оновлення + + You can still access the menu bar by pressing the ALT key + + MediaView @@ -812,22 +824,6 @@ The link will be valid only for a limited time. Посилання буде дійсне лише протягом обмеженого часу. - - This is just the demo version of %1. - >Це демонстраційна версія %1. - - - It allows you to test the application and see if it works for you. - Ви маєте змогу протестувати проґраму та перевірити працездатність. - - - Get the full version - Отримати повну версію - - - Continue - Продовжити - Downloading %1 Завантаження %1 @@ -859,6 +855,10 @@ Subscribe to %1 Підписуватися на %1 + + Switched to %1 + + Unsubscribed from %1 Відписані від %1 @@ -903,11 +903,14 @@ - PlaylistItemDelegate + PickMessage - %1 views - %1 переглядів + Pick a video + Вибрати відео + + + PlaylistItemDelegate %1 of %2 (%3) — %4 %1 з %2 (%3) — %4 @@ -947,10 +950,6 @@ PlaylistModel - - Searching... - Пошук... - Show %1 More Наступні %1 @@ -1072,25 +1071,16 @@ Ласкаво просимо до <a href='%1'>%2</a>, - Enter - "Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos" - Уведіть + to start watching videos. + аби розпочати перегляд. a keyword запит - a channel - канал - - - to start watching videos. - аби розпочати перегляд. - - - Watch - Дивитися + Enter + Уведіть Recent keywords @@ -1111,6 +1101,10 @@ &Back Н&азад + + &Forward + &Вперед + Forward to %1 Уперед до %1 @@ -1164,13 +1158,6 @@ Завантаження %1... - - Video - - Cannot get video stream for %1 - Не вдалося отримати видивопотік для %1 - - YTRegions @@ -1366,4 +1353,11 @@ Світовий + + YTVideo + + Cannot get video stream for %1 + Не вдалося отримати видиво потік для %1 + + \ No newline at end of file diff --git a/locale/uk_UA.ts b/locale/uk_UA.ts index 50a6fb7..9c41c38 100644 --- a/locale/uk_UA.ts +++ b/locale/uk_UA.ts @@ -25,6 +25,14 @@ Translate %1 to your native language using %2 Перекласти %1 Вашою рідною мовою за допомогою %2 + + Powered by %1 + + + + Open-source software + + Icon designed by %1. Розробник піктоґрам %1. @@ -108,7 +116,7 @@ You have %n new video(s) - + @@ -156,6 +164,10 @@ Show Updated Показати оновлені + + You have no subscriptions. Use the star symbol to subscribe to channels. + У Вас немає підписок. Використовуйте символ зірочки, аби підписуватися на канали. + All Videos Усі видива @@ -176,17 +188,6 @@ There are no updated subscriptions at this time. Наразі оновлень підписок немає. - - You have no subscriptions. Use the star symbol to subscribe to channels. - У Вас немає підписок. Використовуйте символ зірочки, аби підписуватися на канали. - - - - ClearButton - - Clear - Очистити - DataUtils @@ -196,19 +197,38 @@ %n hour(s) ago - + %n day(s) ago - + - %n weeks(s) ago - + %n month(s) ago + + + + K + K as in Kilo, i.e. thousands + + + + M + M stands for Millions + + + + B + B stands for Billions + + + + %1 views + %1 переглядів - %n month(s) ago - + %n week(s) ago + @@ -252,22 +272,6 @@ DownloadManager - - This is just the demo version of %1. - Це демонстраційна версія %1. - - - It can only download videos shorter than %1 minutes so you can test the download functionality. - Із метою тестування, Ви можете завантажити видиво тривалістю до %1 хв. - - - Continue - Продовжити - - - Get the full version - Отримати повну версію - %1 downloaded in %2 %1 завантажений до %2 @@ -278,7 +282,7 @@ %n Download(s) - + @@ -633,10 +637,6 @@ &Float on Top &Згори всіх вікон - - &Adjust Window Size - &Змінити розмір вікна - &Stop After This Video Зу&пинити після цього видиво @@ -673,6 +673,14 @@ Hide videos that may contain inappropriate content + + Toggle &Menu Bar + + + + Menu + + &Love %1? Rate it! &Подобається %1? Оцініть ! @@ -797,6 +805,10 @@ Update Оновлення + + You can still access the menu bar by pressing the ALT key + + MediaView @@ -812,22 +824,6 @@ The link will be valid only for a limited time. Посилання буде дійсне лише протягом обмеженого часу. - - This is just the demo version of %1. - >Це демонстраційна версія %1. - - - It allows you to test the application and see if it works for you. - Ви маєте змогу протестувати проґраму та перевірити працездатність. - - - Get the full version - Отримати повну версію - - - Continue - Продовжити - Downloading %1 Завантаження %1 @@ -859,6 +855,10 @@ Subscribe to %1 Підписуватися на %1 + + Switched to %1 + + Unsubscribed from %1 Відписані від %1 @@ -903,11 +903,14 @@ - PlaylistItemDelegate + PickMessage - %1 views - %1 переглядів + Pick a video + + + + PlaylistItemDelegate %1 of %2 (%3) — %4 %1 з %2 (%3) — %4 @@ -947,10 +950,6 @@ PlaylistModel - - Searching... - Пошук... - Show %1 More Наступні %1 @@ -1072,25 +1071,16 @@ Ласкаво просимо до <a href='%1'>%2</a>, - Enter - "Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos" - Уведіть + to start watching videos. + аби розпочати перегляд. a keyword запит - a channel - канал - - - to start watching videos. - аби розпочати перегляд. - - - Watch - Дивитися + Enter + Уведіть Recent keywords @@ -1111,6 +1101,10 @@ &Back Н&азад + + &Forward + + Forward to %1 Уперед до %1 @@ -1164,13 +1158,6 @@ - - Video - - Cannot get video stream for %1 - Не вдалося отримати видивопотік для %1 - - YTRegions @@ -1366,4 +1353,11 @@ Світовий + + YTVideo + + Cannot get video stream for %1 + Не вдалося отримати видивопотік для %1 + + \ No newline at end of file diff --git a/locale/vi.ts b/locale/vi.ts index 5517eaf..dce52fb 100644 --- a/locale/vi.ts +++ b/locale/vi.ts @@ -25,6 +25,14 @@ Translate %1 to your native language using %2 Chuyển ngữ %1 sang ngôn ngữ của bạn bằng cách sá»­ dụng %2 + + Powered by %1 + + + + Open-source software + + Icon designed by %1. Biểu tượng được thiết kế bởi %1. @@ -155,6 +163,10 @@ Show Updated Hiển thị Cập Nhật + + You have no subscriptions. Use the star symbol to subscribe to channels. + Bạn không có phần đăng ký theo dõi nào. Sá»­ dụng biểu tượng ngôi sao để đăng ký theo dõi một kênh nào đó. + All Videos Tất cả các video @@ -175,17 +187,6 @@ There are no updated subscriptions at this time. Hiện không có phần theo dõi đăng ký nào được cập nhật vào thời điểm này. - - You have no subscriptions. Use the star symbol to subscribe to channels. - Bạn không có phần đăng ký theo dõi nào. Sá»­ dụng biểu tượng ngôi sao để đăng ký theo dõi một kênh nào đó. - - - - ClearButton - - Clear - Dọn dẹp phần nội dung - DataUtils @@ -202,11 +203,30 @@ - %n weeks(s) ago + %n month(s) ago + + K + K as in Kilo, i.e. thousands + + + + M + M stands for Millions + + + + B + B stands for Billions + + + + %1 views + %1 lượt xem + - %n month(s) ago + %n week(s) ago @@ -251,22 +271,6 @@ DownloadManager - - This is just the demo version of %1. - Đây là phiên bản dùng thá»­ của %1. - - - It can only download videos shorter than %1 minutes so you can test the download functionality. - Phần này có thể được tải về các video ngắn hÆ¡n %1 phút để bạn có thể thá»­ nghiệm tính năng tải về. - - - Continue - Tiếp tục - - - Get the full version - Nhận phiên bản đầy đủ - %1 downloaded in %2 %1 đã tải xuống trong %2 @@ -632,10 +636,6 @@ &Float on Top &Nổi lên trên cùng - - &Adjust Window Size - - &Stop After This Video &Dừng phát sau video này @@ -672,6 +672,14 @@ Hide videos that may contain inappropriate content + + Toggle &Menu Bar + + + + Menu + + &Love %1? Rate it! &Yêu thích %1? Hãy đánh giá! @@ -796,6 +804,10 @@ Update Cập nhật + + You can still access the menu bar by pressing the ALT key + + MediaView @@ -811,22 +823,6 @@ The link will be valid only for a limited time. Liên kết này chỉ hợp lệ trong một khoảng thời gian hạn định. - - This is just the demo version of %1. - Đây chỉ là phần dùng thá»­ của phiên bản %1. - - - It allows you to test the application and see if it works for you. - Cho phép bạn thá»­ nghiệm ứng dụng và xét độ hiệu quả của ứng dụng đối với bạn. - - - Get the full version - Nhận phiên bản đầy đủ - - - Continue - Tiếp tục - Downloading %1 Đang tải về %1 @@ -858,6 +854,10 @@ Subscribe to %1 Đăng ký theo dõi đối với %1 + + Switched to %1 + + Unsubscribed from %1 @@ -902,11 +902,14 @@ - PlaylistItemDelegate + PickMessage - %1 views - %1 lượt xem + Pick a video + + + + PlaylistItemDelegate %1 of %2 (%3) — %4 %1 của %2 (%3) — %4 @@ -946,10 +949,6 @@ PlaylistModel - - Searching... - Đang tìm kiếm... - Show %1 More Hiển thị %1 thêm @@ -1071,25 +1070,16 @@ Chào mừng đến với <a href='%1'>%2</a>, - Enter - "Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos" - Nhập vào + to start watching videos. + để bắt đầu xem các đoạn video. a keyword một từ khóa - a channel - một kênh - - - to start watching videos. - để bắt đầu xem các đoạn video. - - - Watch - Xem + Enter + Nhập vào Recent keywords @@ -1110,6 +1100,10 @@ &Back &Quay lại + + &Forward + + Forward to %1 Chuyển tới %1 @@ -1163,13 +1157,6 @@ - - Video - - Cannot get video stream for %1 - Không thể tiếp nhận luồng video từ %1 - - YTRegions @@ -1365,4 +1352,11 @@ Toàn thế giới + + YTVideo + + Cannot get video stream for %1 + Không thể tiếp nhận luồng video từ %1 + + \ No newline at end of file diff --git a/locale/zh_CN.ts b/locale/zh_CN.ts index 36d4e3e..27a7517 100644 --- a/locale/zh_CN.ts +++ b/locale/zh_CN.ts @@ -25,6 +25,14 @@ Translate %1 to your native language using %2 使用 %2 将 %1 翻译为您的母语 + + Powered by %1 + + + + Open-source software + + Icon designed by %1. 图标设计:%1。 @@ -155,6 +163,10 @@ Show Updated 显示更新 + + You have no subscriptions. Use the star symbol to subscribe to channels. + 你没有任何订阅,使用星形符号订阅频道 + All Videos 所有视频 @@ -175,17 +187,6 @@ There are no updated subscriptions at this time. 当前没有订阅更新 - - You have no subscriptions. Use the star symbol to subscribe to channels. - 你没有任何订阅,使用星形符号订阅频道 - - - - ClearButton - - Clear - 清除 - DataUtils @@ -202,11 +203,30 @@ - %n weeks(s) ago + %n month(s) ago + + K + K as in Kilo, i.e. thousands + + + + M + M stands for Millions + + + + B + B stands for Billions + + + + %1 views + %1 人次观看 + - %n month(s) ago + %n week(s) ago @@ -251,22 +271,6 @@ DownloadManager - - This is just the demo version of %1. - 这只是 %1 的演示版。 - - - It can only download videos shorter than %1 minutes so you can test the download functionality. - 本版本只能下载 %1 分钟以下的视频,仅用于测试下载功能。 - - - Continue - 继续 - - - Get the full version - 获取完整版 - %1 downloaded in %2 %1 已下载(%2) @@ -632,10 +636,6 @@ &Float on Top 窗口置顶(&F) - - &Adjust Window Size - 调整窗口大小(&A) - &Stop After This Video 该视频后停止播放(&S) @@ -672,6 +672,14 @@ Hide videos that may contain inappropriate content 隐藏可能含有不恰当内容的视频 + + Toggle &Menu Bar + + + + Menu + + &Love %1? Rate it! 喜欢 %1? 为其评分! @@ -796,6 +804,10 @@ Update 更新 + + You can still access the menu bar by pressing the ALT key + + MediaView @@ -811,22 +823,6 @@ The link will be valid only for a limited time. 此链接仅能保持短时间的有效性。 - - This is just the demo version of %1. - 这仅是 %1 的演示版。 - - - It allows you to test the application and see if it works for you. - 本版本允许您测试,以确认本应用是否适合您。 - - - Get the full version - 获取完整版 - - - Continue - 继续 - Downloading %1 正在下载 %1 @@ -858,6 +854,10 @@ Subscribe to %1 订阅 %1 + + Switched to %1 + + Unsubscribed from %1 从 %1 退订 @@ -902,11 +902,14 @@ - PlaylistItemDelegate + PickMessage - %1 views - %1 人次观看 + Pick a video + + + + PlaylistItemDelegate %1 of %2 (%3) — %4 %1 之 %2 (%3) — %4 @@ -946,10 +949,6 @@ PlaylistModel - - Searching... - 搜索中…… - Show %1 More 再多显示 %1 @@ -1071,25 +1070,16 @@ 欢迎使用<a href='%1'>%2</a>! - Enter - "Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos" - 输入 + to start watching videos. + 开始观看视频。 a keyword 关键字 - a channel - 频道名称 - - - to start watching videos. - 开始观看视频。 - - - Watch - 观看 + Enter + 输入 Recent keywords @@ -1110,6 +1100,10 @@ &Back 后退(_B) + + &Forward + + Forward to %1 前进至 %1 @@ -1163,13 +1157,6 @@ 正在下载 %1... - - Video - - Cannot get video stream for %1 - 无法获得视频流。可能原因:%1 - - YTRegions @@ -1365,4 +1352,11 @@ 全球 + + YTVideo + + Cannot get video stream for %1 + 无法获得视频流。可能原因:%1 + + \ No newline at end of file diff --git a/locale/zh_TW.ts b/locale/zh_TW.ts index 4e9424e..854e248 100644 --- a/locale/zh_TW.ts +++ b/locale/zh_TW.ts @@ -25,6 +25,14 @@ Translate %1 to your native language using %2 使用 %2 翻譯 %1 介面成為您的本國語言 + + Powered by %1 + 威力本源 %1 + + + Open-source software + 開放原始碼軟體 + Icon designed by %1. 圖示由 %1 所設計。 @@ -107,7 +115,7 @@ You have %n new video(s) - + 您有 %n 個新影片 @@ -155,6 +163,10 @@ Show Updated 顯示更新 + + You have no subscriptions. Use the star symbol to subscribe to channels. + 您暫時沒有任何訂閱。使用星星符號訂閱頻道。 + All Videos 全部影片 @@ -175,17 +187,6 @@ There are no updated subscriptions at this time. 目前沒有更新的訂閱。 - - You have no subscriptions. Use the star symbol to subscribe to channels. - 您暫時沒有任何訂閱。使用星星符號訂閱頻道。 - - - - ClearButton - - Clear - 清除 - DataUtils @@ -195,19 +196,38 @@ %n hour(s) ago - + %n 小時前 %n day(s) ago - + %n 天前 - %n weeks(s) ago - + %n month(s) ago + %n 個月前 + + + K + K as in Kilo, i.e. thousands + K + + + M + M stands for Millions + M + + + B + B stands for Billions + B + + + %1 views + 瀏覽次數:%1 次 - %n month(s) ago - + %n week(s) ago + %n 週前 @@ -251,22 +271,6 @@ DownloadManager - - This is just the demo version of %1. - 這僅僅是展示版的 %1。 - - - It can only download videos shorter than %1 minutes so you can test the download functionality. - 它只能下載影片少於 %1 分鐘,使您可以測試下載功能。 - - - Continue - 繼續 - - - Get the full version - 取得完整版 - %1 downloaded in %2 %1 下載在 %2 @@ -277,7 +281,7 @@ %n Download(s) - + %n 次下載 @@ -632,10 +636,6 @@ &Float on Top 浮在上面(&F) - - &Adjust Window Size - 調整視窗大小 (&A) - &Stop After This Video 在這個影片播完之後停止(&S) @@ -672,6 +672,14 @@ Hide videos that may contain inappropriate content 隱藏可能包含不適當內容的影片 + + Toggle &Menu Bar + 切換選單列(&M) + + + Menu + 選單 + &Love %1? Rate it! 喜歡 %1 ?為它評分!(&L) @@ -796,6 +804,10 @@ Update 更新 + + You can still access the menu bar by pressing the ALT key + 您還是可以透過按下 ALT 鍵存取選單列 + MediaView @@ -811,22 +823,6 @@ The link will be valid only for a limited time. 這個連結將只在有限的時間內有效。 - - This is just the demo version of %1. - 這僅僅是展示版的 %1。 - - - It allows you to test the application and see if it works for you. - 它可以讓您測試應用程式,看它是否適合您。 - - - Get the full version - 取得完整版 - - - Continue - 繼續 - Downloading %1 正在下載 %1 @@ -858,6 +854,10 @@ Subscribe to %1 訂閱 %1 + + Switched to %1 + 切換至 %1 + Unsubscribed from %1 從 %1 取消訂閱 @@ -902,11 +902,14 @@ - PlaylistItemDelegate + PickMessage - %1 views - 瀏覽次數:%1 次 + Pick a video + 挑選影片 + + + PlaylistItemDelegate %1 of %2 (%3) — %4 %1 / %2 (%3) — %4 @@ -946,10 +949,6 @@ PlaylistModel - - Searching... - 搜尋中... - Show %1 More 顯示再多 %1個影片 @@ -1071,25 +1070,16 @@ 歡迎使用 <a href='%1'>%2</a>, - Enter - "Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos" - 輸入 + to start watching videos. + 以開始觀看影片。 a keyword 一個關鍵字 - a channel - 一個頻道 - - - to start watching videos. - 以開始觀看影片。 - - - Watch - 觀看 + Enter + 輸入 Recent keywords @@ -1110,6 +1100,10 @@ &Back 後退(&B) + + &Forward + 前進(&F) + Forward to %1 向前到 %1 @@ -1163,13 +1157,6 @@ 正在下載 %1... - - Video - - Cannot get video stream for %1 - 無法為 %1 獲得影片串流 - - YTRegions @@ -1365,4 +1352,11 @@ 全世界 + + YTVideo + + Cannot get video stream for %1 + 無法為 %1 取得影片串流 + + \ No newline at end of file diff --git a/minitube.appdata.xml b/minitube.appdata.xml index c6547b6..9789fa1 100644 --- a/minitube.appdata.xml +++ b/minitube.appdata.xml @@ -1,19 +1,20 @@ - - minitube.desktop + + Minitube + org.tordini.flavio.minitube CC0-1.0 - GPL-3.0+ + GPL-3.0+ YouTube app

- Minitube is a YouTube desktop application. + Minitube is a YouTube desktop application. It is written in C++ using the Qt framework.

http://flavio.tordini.org/minitube - http://flavio.tordini.org/files/minitube/minitube-04.jpg - http://flavio.tordini.org/files/minitube/minitube-03.jpg - http://flavio.tordini.org/files/minitube/minitube-02.jpg - http://flavio.tordini.org/files/minitube/minitube-01.jpg + http://flavio.tordini.org/files/minitube/minitube-04.jpg + http://flavio.tordini.org/files/minitube/minitube-03.jpg + http://flavio.tordini.org/files/minitube/minitube-02.jpg + http://flavio.tordini.org/files/minitube/minitube-01.jpg -
+ diff --git a/minitube.pro b/minitube.pro index 453cf15..27c5a01 100644 --- a/minitube.pro +++ b/minitube.pro @@ -1,7 +1,7 @@ -CONFIG += release c++11 -CONFIG -= rtti exceptions +CONFIG += c++14 exceptions_off rtti_off optimize_full + TEMPLATE = app -VERSION = 2.9 +VERSION = 3.1 DEFINES += APP_VERSION="$$VERSION" APP_NAME = Minitube @@ -10,8 +10,6 @@ DEFINES += APP_NAME="$$APP_NAME" APP_UNIX_NAME = minitube DEFINES += APP_UNIX_NAME="$$APP_UNIX_NAME" -DEFINES += APP_PHONON -DEFINES += APP_PHONON_SEEK DEFINES += APP_SNAPSHOT message(Building $${APP_NAME} $${VERSION}) @@ -29,12 +27,15 @@ TARGET = $${APP_UNIX_NAME} QT += widgets network sql qml +include(lib/http/http.pri) +include(lib/idle/idle.pri) + +DEFINES += MEDIA_MPV +include(lib/media/media.pri) + include(src/qtsingleapplication/qtsingleapplication.pri) -include(src/http/http.pri) -include(src/idle/idle.pri) HEADERS += src/video.h \ - src/searchlineedit.h \ src/spacer.h \ src/constants.h \ src/playlistitemdelegate.h \ @@ -43,7 +44,6 @@ HEADERS += src/video.h \ src/searchparams.h \ src/minisplitter.h \ src/loadingwidget.h \ - src/videoareawidget.h \ src/autocomplete.h \ src/videodefinition.h \ src/fontutils.h \ @@ -101,16 +101,16 @@ HEADERS += src/video.h \ src/yt3.h \ src/paginatedvideosource.h \ src/searchwidget.h \ - src/exlineedit.h \ src/channellistview.h \ src/httputils.h \ src/appwidget.h \ src/clickablelabel.h \ src/ytvideo.h \ src/toolbarmenu.h \ - src/sharetoolbar.h + src/sharetoolbar.h \ + src/videoarea.h \ + src/searchlineedit.h SOURCES += src/main.cpp \ - src/searchlineedit.cpp \ src/spacer.cpp \ src/video.cpp \ src/videomimedata.cpp \ @@ -118,7 +118,6 @@ SOURCES += src/main.cpp \ src/searchparams.cpp \ src/minisplitter.cpp \ src/loadingwidget.cpp \ - src/videoareawidget.cpp \ src/autocomplete.cpp \ src/videodefinition.cpp \ src/constants.cpp \ @@ -175,15 +174,19 @@ SOURCES += src/main.cpp \ src/ytchannel.cpp \ src/yt3.cpp \ src/paginatedvideosource.cpp \ - src/exlineedit.cpp \ src/channellistview.cpp \ src/httputils.cpp \ src/appwidget.cpp \ src/clickablelabel.cpp \ src/ytvideo.cpp \ src/toolbarmenu.cpp \ - src/sharetoolbar.cpp + src/sharetoolbar.cpp \ + src/videoarea.cpp \ + src/searchlineedit.cpp + RESOURCES += resources.qrc +RESOURCES += $$files(icons/*.png, true) + DESTDIR = build/target/ OBJECTS_DIR = build/obj/ MOC_DIR = build/moc/ @@ -192,26 +195,30 @@ RCC_DIR = build/rcc/ # Tell Qt Linguist that we use UTF-8 strings in our sources CODECFORTR = UTF-8 CODECFORSRC = UTF-8 + include(locale/locale.pri) # deploy DISTFILES += CHANGES COPYING unix:!mac { DEFINES += APP_LINUX - LIBS += -lphonon4qt5 - INCLUDEPATH += /usr/include/phonon4qt5 QT += dbus HEADERS += src/gnomeglobalshortcutbackend.h SOURCES += src/gnomeglobalshortcutbackend.cpp + isEmpty(PREFIX):PREFIX = /usr + BINDIR = $$PREFIX/bin INSTALLS += target target.path = $$BINDIR + DATADIR = $$PREFIX/share PKGDATADIR = $$DATADIR/minitube DEFINES += DATADIR=\\\"$$DATADIR\\\" \ PKGDATADIR=\\\"$$PKGDATADIR\\\" + INSTALLS += translations \ + sounds \ desktop \ appdata \ iconsvg \ @@ -225,6 +232,8 @@ unix:!mac { icon512 translations.path = $$PKGDATADIR translations.files += $$DESTDIR/locale + sounds.path = $$PKGDATADIR + sounds.files += sounds/ desktop.path = $$DATADIR/applications desktop.files += minitube.desktop appdata.path = $$DATADIR/appdata @@ -248,4 +257,5 @@ unix:!mac { icon512.path = $$DATADIR/icons/hicolor/512x512/apps icon512.files += data/512x512/minitube.png } + mac|win32|contains(DEFINES, APP_UBUNTU):include(local/local.pri) diff --git a/resources.qrc b/resources.qrc index 76556e8..9933d32 100644 --- a/resources.qrc +++ b/resources.qrc @@ -1,11 +1,6 @@ images/app.png - images/refine-search.png - images/search-time.png - images/search-sortBy.png - images/search-quality.png - images/search-duration.png flags/dz.png flags/ar.png flags/au.png @@ -54,89 +49,8 @@ flags/gb.png flags/ye.png style.css - images/worldwide.png - images/show-updated.png - images/sort.png - images/mark-watched.png - images/channels.png - images/unwatched.png - sounds/snapshot.wav images/app@2x.png - images/sort@2x.png - images/unwatched@2x.png - images/channels@2x.png - images/mark-watched@2x.png - images/show-updated@2x.png - images/worldwide@2x.png - images/refine-search@2x.png - images/search-duration@2x.png - images/search-quality@2x.png - images/search-time@2x.png - images/search-sortBy@2x.png - images/audio-volume-high.png - images/audio-volume-high@2x.png - images/audio-volume-muted.png - images/audio-volume-muted@2x.png - images/bookmark-new.png - images/bookmark-new@2x.png - images/bookmark-new_active.png - images/bookmark-new_active@2x.png - images/bookmark-remove.png - images/bookmark-remove@2x.png - images/content-loading.png - images/content-loading@2x.png - images/document-save.png - images/document-save@2x.png - images/edit-clear.png - images/edit-find.png - images/go-next.png - images/go-next@2x.png - images/go-next_active.png - images/go-next_active@2x.png - images/go-previous.png - images/go-previous@2x.png - images/go-previous_active.png - images/go-previous_active@2x.png - images/go-top.png - images/go-top@2x.png - images/media-playback-pause.png - images/media-playback-pause@2x.png - images/media-playback-start.png - images/media-playback-start@2x.png - images/media-playback-stop.png - images/media-playback-stop@2x.png - images/media-skip-forward.png - images/media-skip-forward@2x.png - images/system-search.png - images/system-search_active.png - images/system-search_selected.png - images/video-display.png - images/video-display@2x.png - images/view-fullscreen.png - images/view-fullscreen@2x.png - images/view-list.png - images/view-list@2x.png - images/view-refresh.png - images/view-refresh_active.png - images/view-refresh_selected.png - images/view-restore.png - images/view-restore@2x.png - images/window-close.png - images/window-close_active.png - images/window-close_selected.png images/64x64/app.png images/64x64/app@2x.png - images/safesearch.png - images/safesearch@2x.png - images/view-more.png - images/view-more@2x.png - images/email.png - images/email@2x.png - images/facebook.png - images/facebook@2x.png - images/link.png - images/link@2x.png - images/twitter.png - images/twitter@2x.png diff --git a/src/aboutview.cpp b/src/aboutview.cpp index 1fe723a..2c5b456 100644 --- a/src/aboutview.cpp +++ b/src/aboutview.cpp @@ -27,23 +27,22 @@ $END_LICENSE */ #include "activation.h" #endif #ifdef APP_MAC -#include "macutils.h" #include "mac_startup.h" +#include "macutils.h" #endif -#include "fontutils.h" -#include "iconutils.h" #include "appwidget.h" #include "clickablelabel.h" +#include "fontutils.h" +#include "iconutils.h" #include "mainwindow.h" AboutView::AboutView(QWidget *parent) : View(parent) { - const int padding = 30; const char *buildYear = __DATE__ + 7; - // speedup painting since we'll paint the whole background - // by ourselves anyway in paintEvent() - setAttribute(Qt::WA_OpaquePaintEvent); + setBackgroundRole(QPalette::Base); + setForegroundRole(QPalette::Text); + setAutoFillBackground(true); QBoxLayout *verticalLayout = new QVBoxLayout(this); verticalLayout->setMargin(0); @@ -55,8 +54,13 @@ AboutView::AboutView(QWidget *parent) : View(parent) { aboutlayout->setMargin(padding); aboutlayout->setSpacing(padding); - logo = new ClickableLabel(); - logo->setPixmap(IconUtils::pixmap(":/images/app.png")); + ClickableLabel *logo = new ClickableLabel(); + auto setLogoPixmap = [logo] { + logo->setPixmap(IconUtils::pixmap(":/images/app.png", logo->devicePixelRatioF())); + }; + setLogoPixmap(); + connect(window()->windowHandle(), &QWindow::screenChanged, this, setLogoPixmap); + connect(logo, &ClickableLabel::clicked, MainWindow::instance(), &MainWindow::visitSite); aboutlayout->addWidget(logo, 0, Qt::AlignTop); @@ -65,39 +69,76 @@ AboutView::AboutView(QWidget *parent) : View(parent) { layout->setSpacing(padding); aboutlayout->addLayout(layout); - QString css = "a { color: palette(text); text-decoration: none; font-weight: bold } h1 { font-weight: 300 }"; + QColor lightTextColor = palette().text().color(); +#ifdef APP_MAC + lightTextColor.setAlphaF(.75); +#endif +#ifdef APP_MAC + QColor linkColor = mac::accentColor(); +#else + QColor linkColor = palette().highlight().color(); +#endif - QString info = "" - "

" + QString(Constants::NAME) + "

" - "

" + tr("There's life outside the browser!") + "

" - "

" + tr("Version %1").arg(Constants::VERSION) + "

" - + QString("

%1

").arg(Constants::WEBSITE); + QString info = ""; + + info += "

" + QString(Constants::NAME) + + "

" + "

" + + tr("There's life outside the browser!") + + "

" + "

" + + tr("Version %1").arg(Constants::VERSION) + "

" + + QString("

%1

").arg(Constants::WEBSITE); #ifdef APP_ACTIVATION QString email = Activation::instance().getEmail(); - if (!email.isEmpty()) - info += "

" + tr("Licensed to: %1").arg("" + email + ""); + if (!email.isEmpty()) info += "

" + tr("Licensed to: %1").arg("" + email + ""); #endif #ifndef APP_EXTRA - info += "

" + tr("%1 is Free Software but its development takes precious time.").arg(Constants::NAME) + "
" - + tr("Please donate to support the continued development of %2.") - .arg(QString(Constants::WEBSITE).append("#donate"), Constants::NAME) + "

"; + info += "

" + + tr("%1 is Free Software but its development takes precious time.") + .arg(Constants::NAME) + + "
" + + tr("Please donate to support the continued development of %2.") + .arg(QString(Constants::WEBSITE).append("#donate"), Constants::NAME) + + "

"; #endif - info += "

" + tr("Translate %1 to your native language using %2").arg(Constants::NAME) - .arg("Transifex") - + "

" + info += "

" + + tr("Translate %1 to your native language using %2") + .arg(Constants::NAME) + .arg("Transifex") + + "

"; + + info += "

" + + tr("Powered by %1") + .arg("" + tr("Open-source software") + "") + + "

"; - "

" - + tr("Icon designed by %1.").arg("David Nel") - + "

" + info += "

" + + tr("Icon designed by %1.").arg("David Nel") + + "

" - #ifndef APP_EXTRA - "

" + tr("Released under the GNU General Public License") - .arg("http://www.gnu.org/licenses/gpl.html") + "

" - #endif - "

© " + buildYear + " " + Constants::ORG_NAME + "

" +#ifndef APP_EXTRA + "

" + + tr("Released under the GNU General Public License") + .arg("http://www.gnu.org/licenses/gpl.html") + + "

" +#endif + "

© " + + buildYear + " " + Constants::ORG_NAME + + "

" ""; QLabel *infoLabel = new QLabel(info, this); @@ -112,7 +153,7 @@ AboutView::AboutView(QWidget *parent) : View(parent) { closeButton->setDefault(true); closeButton->setFocus(); - connect(closeButton, SIGNAL(clicked()), parent, SLOT(goBack())); + connect(closeButton, SIGNAL(clicked()), MainWindow::instance(), SLOT(goBack())); buttonLayout->addWidget(closeButton); layout->addLayout(buttonLayout); @@ -122,22 +163,4 @@ AboutView::AboutView(QWidget *parent) : View(parent) { void AboutView::appear() { closeButton->setFocus(); - connect(window()->windowHandle(), SIGNAL(screenChanged(QScreen*)), SLOT(screenChanged()), Qt::UniqueConnection); -} - -void AboutView::paintEvent(QPaintEvent *event) { - QWidget::paintEvent(event); - QBrush brush; - if (window()->isActiveWindow()) { - brush = Qt::white; - } else { - brush = palette().window(); - } - QPainter painter(this); - painter.fillRect(0, 0, width(), height(), brush); - painter.end(); -} - -void AboutView::screenChanged() { - logo->setPixmap(IconUtils::pixmap(":/images/app.png")); } diff --git a/src/aboutview.h b/src/aboutview.h index 1f2a0fe..4d06db0 100644 --- a/src/aboutview.h +++ b/src/aboutview.h @@ -26,8 +26,6 @@ $END_LICENSE */ #include "constants.h" #include "view.h" -class ClickableLabel; - class AboutView : public View { Q_OBJECT @@ -39,14 +37,7 @@ public: return s; } -protected: - void paintEvent(QPaintEvent *e); - -private slots: - void screenChanged(); - private: - ClickableLabel *logo; QPushButton *closeButton; }; #endif diff --git a/src/appwidget.cpp b/src/appwidget.cpp index 43b3b58..295388c 100644 --- a/src/appwidget.cpp +++ b/src/appwidget.cpp @@ -41,7 +41,7 @@ void AppsWidget::paintEvent(QPaintEvent *e) { AppWidget::AppWidget(const QString &name, const QString &code, QWidget *parent) : QWidget(parent), icon(0), name(name), downloadButton(0) { const QString unixName = code.left(code.lastIndexOf('.')); - const QString baseUrl = QLatin1String("http://") + Constants::ORG_DOMAIN; + const QString baseUrl = QLatin1String("https://") + Constants::ORG_DOMAIN; const QString filesUrl = baseUrl + QLatin1String("/files/"); url = filesUrl + unixName + QLatin1String("/") + code; webPage = baseUrl + QLatin1String("/") + unixName; diff --git a/src/autocomplete.cpp b/src/autocomplete.cpp index 50f4e72..2c8e522 100644 --- a/src/autocomplete.cpp +++ b/src/autocomplete.cpp @@ -33,22 +33,25 @@ $END_LICENSE */ #ifndef QT_NO_DEBUG_OUTPUT /// Gives human-readable event type information. -QDebug operator<<(QDebug str, const QEvent * ev) { +QDebug operator<<(QDebug str, const QEvent *ev) { static int eventEnumIndex = QEvent::staticMetaObject.indexOfEnumerator("Type"); str << "QEvent"; if (ev) { QString name = QEvent::staticMetaObject.enumerator(eventEnumIndex).valueToKey(ev->type()); - if (!name.isEmpty()) str << name; else str << ev->type(); + if (!name.isEmpty()) + str << name; + else + str << ev->type(); } else { - str << (void*)ev; + str << (void *)ev; } return str.maybeSpace(); } #endif -AutoComplete::AutoComplete(SearchWidget *buddy, QLineEdit *lineEdit): - QObject(lineEdit), buddy(buddy), lineEdit(lineEdit), enabled(true), suggester(0), itemHovering(false) { - +AutoComplete::AutoComplete(SearchWidget *buddy, QLineEdit *lineEdit) + : QObject(lineEdit), buddy(buddy), lineEdit(lineEdit), enabled(true), suggester(0), + itemHovering(false) { popup = new QListWidget(); popup->setWindowFlags(Qt::Popup); popup->setFocusProxy(buddy->toWidget()); @@ -62,10 +65,10 @@ AutoComplete::AutoComplete(SearchWidget *buddy, QLineEdit *lineEdit): popup->setWindowOpacity(.9); popup->setProperty("suggest", true); - connect(popup, SIGNAL(itemClicked(QListWidgetItem*)), SLOT(acceptSuggestion())); - connect(popup, SIGNAL(currentItemChanged(QListWidgetItem*, QListWidgetItem*)), - SLOT(currentItemChanged(QListWidgetItem*))); - connect(popup, SIGNAL(itemEntered(QListWidgetItem*)), SLOT(itemEntered(QListWidgetItem *))); + connect(popup, SIGNAL(itemClicked(QListWidgetItem *)), SLOT(acceptSuggestion())); + connect(popup, SIGNAL(currentItemChanged(QListWidgetItem *, QListWidgetItem *)), + SLOT(currentItemChanged(QListWidgetItem *))); + connect(popup, SIGNAL(itemEntered(QListWidgetItem *)), SLOT(itemEntered(QListWidgetItem *))); timer = new QTimer(this); timer->setSingleShot(true); @@ -75,7 +78,6 @@ AutoComplete::AutoComplete(SearchWidget *buddy, QLineEdit *lineEdit): } bool AutoComplete::eventFilter(QObject *obj, QEvent *ev) { - if (obj != popup) { switch (ev->type()) { case QEvent::Move: @@ -104,7 +106,7 @@ bool AutoComplete::eventFilter(QObject *obj, QEvent *ev) { if (ev->type() == QEvent::KeyPress) { bool consumed = false; - QKeyEvent *keyEvent = static_cast(ev); + QKeyEvent *keyEvent = static_cast(ev); // qWarning() << keyEvent->text(); switch (keyEvent->key()) { case Qt::Key_Enter: @@ -165,10 +167,9 @@ void AutoComplete::showSuggestions(const QVector &suggestions) { QListWidgetItem *item = new QListWidgetItem(popup); Suggestion *s = suggestions[i]; item->setText(s->value); - if (!s->type.isEmpty()) - item->setIcon(QIcon(":/images/" + s->type + ".png")); + if (!s->type.isEmpty()) item->setIcon(QIcon(":/images/" + s->type + ".png")); } - popup->setCurrentItem(0); + popup->setCurrentItem(nullptr); int h = popup->frameWidth() * 2; for (int i = 0; i < suggestions.count(); ++i) h += popup->sizeHintForRow(i); @@ -187,13 +188,14 @@ void AutoComplete::showSuggestions(const QVector &suggestions) { void AutoComplete::acceptSuggestion() { int index = popup->currentIndex().row(); if (index >= 0 && index < suggestions.size()) { - Suggestion* suggestion = suggestions.at(index); + Suggestion *suggestion = suggestions.at(index); buddy->setText(suggestion->value); emit suggestionAccepted(suggestion); emit suggestionAccepted(suggestion->value); originalText.clear(); hideSuggestions(); - } else qWarning() << "No suggestion for index" << index; + } else + qWarning() << "No suggestion for index" << index; } void AutoComplete::preventSuggest() { @@ -206,16 +208,17 @@ void AutoComplete::enableSuggest() { enabled = true; } -void AutoComplete::setSuggester(Suggester* suggester) { +void AutoComplete::setSuggester(Suggester *suggester) { if (this->suggester) this->suggester->disconnect(); this->suggester = suggester; - connect(suggester, SIGNAL(ready(QVector)), SLOT(suggestionsReady(QVector))); + connect(suggester, SIGNAL(ready(QVector)), + SLOT(suggestionsReady(QVector))); } void AutoComplete::suggest() { if (!enabled) return; - popup->setCurrentItem(0); + popup->setCurrentItem(nullptr); popup->clearSelection(); originalText = buddy->text(); diff --git a/src/channelitemdelegate.cpp b/src/channelitemdelegate.cpp index 664efc4..76a5012 100644 --- a/src/channelitemdelegate.cpp +++ b/src/channelitemdelegate.cpp @@ -19,30 +19,28 @@ along with Minitube. If not, see . $END_LICENSE */ #include "channelitemdelegate.h" +#include "channelaggregator.h" #include "channelmodel.h" -#include "ytchannel.h" #include "fontutils.h" -#include "channelaggregator.h" -#include "painterutils.h" #include "iconutils.h" +#include "painterutils.h" +#include "ytchannel.h" static const int ITEM_WIDTH = 150; static const int ITEM_HEIGHT = 150; static const int THUMB_WIDTH = 88; static const int THUMB_HEIGHT = 88; -ChannelItemDelegate::ChannelItemDelegate(QObject *parent) : QStyledItemDelegate(parent) { +ChannelItemDelegate::ChannelItemDelegate(QObject *parent) : QStyledItemDelegate(parent) {} -} - -QSize ChannelItemDelegate::sizeHint(const QStyleOptionViewItem& /*option*/, - const QModelIndex& /*index*/ ) const { +QSize ChannelItemDelegate::sizeHint(const QStyleOptionViewItem & /*option*/, + const QModelIndex & /*index*/) const { return QSize(ITEM_WIDTH, ITEM_HEIGHT); } -void ChannelItemDelegate::paint( QPainter* painter, - const QStyleOptionViewItem& option, - const QModelIndex& index ) const { +void ChannelItemDelegate::paint(QPainter *painter, + const QStyleOptionViewItem &option, + const QModelIndex &index) const { const int itemType = index.data(ChannelModel::ItemTypeRole).toInt(); if (itemType == ChannelModel::ItemChannel) paintChannel(painter, option, index); @@ -54,37 +52,37 @@ void ChannelItemDelegate::paint( QPainter* painter, QStyledItemDelegate::paint(painter, option, index); } -void ChannelItemDelegate::paintAggregate(QPainter* painter, - const QStyleOptionViewItem& option, - const QModelIndex& index) const { +void ChannelItemDelegate::paintAggregate(QPainter *painter, + const QStyleOptionViewItem &option, + const QModelIndex &index) const { Q_UNUSED(index); painter->save(); - painter->translate(option.rect.topLeft()); const QRect line(0, 0, option.rect.width(), option.rect.height()); - static const QPixmap thumbnail = IconUtils::pixmap(":/images/channels.png"); + static QPixmap thumbnail = IconUtils::icon("channels").pixmap(88, 88); + connect(qApp, &QGuiApplication::paletteChanged, this, + [] { thumbnail = IconUtils::icon("channels").pixmap(88, 88); }); QString name = tr("All Videos"); - drawItem(painter, line, thumbnail, name); - painter->restore(); } -void ChannelItemDelegate::paintUnwatched(QPainter* painter, - const QStyleOptionViewItem& option, - const QModelIndex& index) const { +void ChannelItemDelegate::paintUnwatched(QPainter *painter, + const QStyleOptionViewItem &option, + const QModelIndex &index) const { Q_UNUSED(index); painter->save(); painter->translate(option.rect.topLeft()); const QRect line(0, 0, option.rect.width(), option.rect.height()); - static const QPixmap thumbnail = IconUtils::pixmap(":/images/unwatched.png"); + static QPixmap thumbnail = IconUtils::icon("unwatched").pixmap(88, 88); + connect(qApp, &QGuiApplication::paletteChanged, this, + [] { thumbnail = IconUtils::icon("unwatched").pixmap(88, 88); }); QString name = tr("Unwatched Videos"); - drawItem(painter, line, thumbnail, name); int notifyCount = ChannelAggregator::instance()->getUnwatchedCount(); @@ -94,9 +92,9 @@ void ChannelItemDelegate::paintUnwatched(QPainter* painter, painter->restore(); } -void ChannelItemDelegate::paintChannel(QPainter* painter, - const QStyleOptionViewItem& option, - const QModelIndex& index) const { +void ChannelItemDelegate::paintChannel(QPainter *painter, + const QStyleOptionViewItem &option, + const QModelIndex &index) const { YTChannel *channel = index.data(ChannelModel::DataObjectRole).value().data(); if (!channel) return; @@ -120,16 +118,15 @@ void ChannelItemDelegate::paintChannel(QPainter* painter, drawItem(painter, line, thumbnail, name); int notifyCount = channel->getNotifyCount(); - if (notifyCount > 0) - paintBadge(painter, line, QString::number(notifyCount)); + if (notifyCount > 0) paintBadge(painter, line, QString::number(notifyCount)); painter->restore(); } void ChannelItemDelegate::drawItem(QPainter *painter, - const QRect &line, - const QPixmap &thumbnail, - const QString &name) const { + const QRect &line, + const QPixmap &thumbnail, + const QString &name) const { painter->drawPixmap((line.width() - THUMB_WIDTH) / 2, 8, thumbnail); QRect nameBox = line; @@ -138,12 +135,10 @@ void ChannelItemDelegate::drawItem(QPainter *painter, bool tooBig = false; QRect textBox = painter->boundingRect(nameBox, - Qt::AlignTop | Qt::AlignHCenter | Qt::TextWordWrap, - name); + Qt::AlignTop | Qt::AlignHCenter | Qt::TextWordWrap, name); if (textBox.height() > nameBox.height() || textBox.width() > nameBox.width()) { painter->setFont(FontUtils::small()); - textBox = painter->boundingRect(nameBox, - Qt::AlignTop | Qt::AlignHCenter | Qt::TextWordWrap, + textBox = painter->boundingRect(nameBox, Qt::AlignTop | Qt::AlignHCenter | Qt::TextWordWrap, name); if (textBox.height() > nameBox.height()) { painter->setClipRect(nameBox); @@ -157,12 +152,12 @@ void ChannelItemDelegate::drawItem(QPainter *painter, } void ChannelItemDelegate::paintBadge(QPainter *painter, - const QRect &line, - const QString &text) const { + const QRect &line, + const QString &text) const { const int topLeft = (line.width() + THUMB_WIDTH) / 2; painter->save(); painter->translate(topLeft, 0); painter->setClipping(false); - PainterUtils::paintBadge(painter, text, true); + PainterUtils::paintBadge(painter, text, true, QColor(230, 36, 41), true); painter->restore(); } diff --git a/src/channellistview.cpp b/src/channellistview.cpp index f791f71..7fa5fa9 100644 --- a/src/channellistview.cpp +++ b/src/channellistview.cpp @@ -22,7 +22,6 @@ $END_LICENSE */ #include "painterutils.h" ChannelListView::ChannelListView() { - setSelectionMode(QAbstractItemView::NoSelection); // layout @@ -38,20 +37,10 @@ ChannelListView::ChannelListView() { setFrameShape(QFrame::NoFrame); setAttribute(Qt::WA_MacShowFocusRect, false); - QPalette p = palette(); - /* - p.setColor(QPalette::Base, p.window().color()); - p.setColor(QPalette::Text, p.windowText().color()); - */ - p.setColor(QPalette::Disabled, QPalette::Base, p.base().color()); - p.setColor(QPalette::Disabled, QPalette::Text, p.text().color()); - setPalette(p); - verticalScrollBar()->setPageStep(3); verticalScrollBar()->setSingleStep(1); setMouseTracking(true); - } void ChannelListView::mousePressEvent(QMouseEvent *event) { @@ -64,8 +53,10 @@ void ChannelListView::mousePressEvent(QMouseEvent *event) { void ChannelListView::mouseMoveEvent(QMouseEvent *event) { QWidget::mouseMoveEvent(event); const QModelIndex index = indexAt(event->pos()); - if (index.isValid()) setCursor(Qt::PointingHandCursor); - else unsetCursor(); + if (index.isValid()) + setCursor(Qt::PointingHandCursor); + else + unsetCursor(); } void ChannelListView::paintEvent(QPaintEvent *event) { diff --git a/src/channellistview.h b/src/channellistview.h index ebb4d8b..dcbc8bf 100644 --- a/src/channellistview.h +++ b/src/channellistview.h @@ -24,7 +24,6 @@ $END_LICENSE */ #include class ChannelListView : public QListView { - Q_OBJECT public: @@ -42,7 +41,6 @@ protected: private: QString errorMessage; - }; #endif // CHANNELLISTVIEW_H diff --git a/src/channelview.cpp b/src/channelview.cpp index d6dcd46..eb6ccf9 100644 --- a/src/channelview.cpp +++ b/src/channelview.cpp @@ -19,30 +19,27 @@ along with Minitube. If not, see . $END_LICENSE */ #include "channelview.h" -#include "ytchannel.h" -#include "ytsearch.h" -#include "searchparams.h" -#include "channelmodel.h" +#include "aggregatevideosource.h" +#include "channelaggregator.h" #include "channelitemdelegate.h" +#include "channelmodel.h" #include "database.h" -#include "channelaggregator.h" -#include "aggregatevideosource.h" -#include "mainwindow.h" #include "iconutils.h" +#include "mainwindow.h" +#include "searchparams.h" +#include "ytchannel.h" +#include "ytsearch.h" #ifdef APP_EXTRA #include "extra.h" #endif #include "channellistview.h" namespace { -static const QString sortByKey = "subscriptionsSortBy"; -static const QString showUpdatedKey = "subscriptionsShowUpdated"; -} - -ChannelView::ChannelView(QWidget *parent) : View(parent), - showUpdated(false), - sortBy(SortByName) { +const QString sortByKey = "subscriptionsSortBy"; +const QString showUpdatedKey = "subscriptionsShowUpdated"; +} // namespace +ChannelView::ChannelView(QWidget *parent) : View(parent), showUpdated(false), sortBy(SortByName) { QBoxLayout *layout = new QVBoxLayout(this); layout->setMargin(0); layout->setSpacing(0); @@ -53,7 +50,8 @@ ChannelView::ChannelView(QWidget *parent) : View(parent), channelsModel = new ChannelModel(this); listView->setModel(channelsModel); - connect(listView, SIGNAL(clicked(const QModelIndex &)), SLOT(itemActivated(const QModelIndex &))); + connect(listView, SIGNAL(clicked(const QModelIndex &)), + SLOT(itemActivated(const QModelIndex &))); connect(listView, SIGNAL(contextMenu(QPoint)), SLOT(showContextMenu(QPoint))); connect(listView, SIGNAL(viewportEntered()), channelsModel, SLOT(clearHover())); @@ -61,8 +59,8 @@ ChannelView::ChannelView(QWidget *parent) : View(parent), setupActions(); - connect(ChannelAggregator::instance(), SIGNAL(channelChanged(YTChannel*)), - channelsModel, SLOT(updateChannel(YTChannel*))); + connect(ChannelAggregator::instance(), SIGNAL(channelChanged(YTChannel *)), channelsModel, + SLOT(updateChannel(YTChannel *))); connect(ChannelAggregator::instance(), SIGNAL(unwatchedCountChanged(int)), SLOT(unwatchedCountChanged(int))); @@ -114,7 +112,7 @@ void ChannelView::setupActions() { QToolButton *sortButton = new QToolButton(this); sortButton->setText(tr("Sort by")); - sortButton->setIcon(IconUtils::icon("sort")); + IconUtils::setIcon(sortButton, "sort"); sortButton->setIconSize(QSize(16, 16)); sortButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); sortButton->setPopupMode(QToolButton::InstantPopup); @@ -124,16 +122,16 @@ void ChannelView::setupActions() { widgetAction->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_O)); statusActions << widgetAction; - markAsWatchedAction = new QAction( - IconUtils::icon("mark-watched"), tr("Mark all as watched"), this); + markAsWatchedAction = new QAction(tr("Mark all as watched"), this); + IconUtils::setIcon(markAsWatchedAction, "mark-watched"); markAsWatchedAction->setEnabled(false); markAsWatchedAction->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_W)); connect(markAsWatchedAction, SIGNAL(triggered()), SLOT(markAllAsWatched())); statusActions << markAsWatchedAction; showUpdated = settings.value(showUpdatedKey, false).toBool(); - QAction *showUpdatedAction = new QAction( - IconUtils::icon("show-updated"), tr("Show Updated"), this); + QAction *showUpdatedAction = new QAction(tr("Show Updated"), this); + IconUtils::setIcon(showUpdatedAction, "show-updated"); showUpdatedAction->setCheckable(true); showUpdatedAction->setChecked(showUpdated); showUpdatedAction->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_U)); @@ -142,26 +140,24 @@ void ChannelView::setupActions() { for (QAction *action : statusActions) { window()->addAction(action); - IconUtils::setupAction(action); + MainWindow::instance()->setupAction(action); } } QString ChannelView::noSubscriptionsMessage() { return tr("You have no subscriptions. " - "Use the star symbol to subscribe to channels."); + "Use the star symbol to subscribe to channels."); } void ChannelView::appear() { updateQuery(); - for (QAction* action : statusActions) - MainWindow::instance()->showActionInStatusBar(action, true); + MainWindow::instance()->showActionsInStatusBar(statusActions, true); setFocus(); ChannelAggregator::instance()->start(); } void ChannelView::disappear() { - for (QAction* action : statusActions) - MainWindow::instance()->showActionInStatusBar(action, false); + MainWindow::instance()->showActionsInStatusBar(statusActions, false); } void ChannelView::itemActivated(const QModelIndex &index) { @@ -200,22 +196,23 @@ void ChannelView::showContextMenu(const QPoint &point) { QMenu menu; if (channel->getNotifyCount() > 0) { - QAction *markAsWatchedAction = menu.addAction(tr("Mark as Watched"), channel, SLOT(updateWatched())); - connect(markAsWatchedAction, SIGNAL(triggered()), - ChannelAggregator::instance(), SLOT(updateUnwatchedCount())); + QAction *markAsWatchedAction = + menu.addAction(tr("Mark as Watched"), channel, SLOT(updateWatched())); + connect(markAsWatchedAction, SIGNAL(triggered()), ChannelAggregator::instance(), + SLOT(updateUnwatchedCount())); menu.addSeparator(); } /* // TODO - QAction *notificationsAction = menu.addAction(tr("Receive Notifications"), user, SLOT(unsubscribe())); - notificationsAction->setCheckable(true); + QAction *notificationsAction = menu.addAction(tr("Receive Notifications"), user, + SLOT(unsubscribe())); notificationsAction->setCheckable(true); notificationsAction->setChecked(true); */ QAction *unsubscribeAction = menu.addAction(tr("Unsubscribe"), channel, SLOT(unsubscribe())); - connect(unsubscribeAction, SIGNAL(triggered()), - ChannelAggregator::instance(), SLOT(updateUnwatchedCount())); + connect(unsubscribeAction, SIGNAL(triggered()), ChannelAggregator::instance(), + SLOT(updateUnwatchedCount())); menu.exec(mapToGlobal(point)); } @@ -237,8 +234,7 @@ void ChannelView::updateQuery(bool transition) { } QString sql = "select user_id from subscriptions"; - if (showUpdated) - sql += " where notify_count>0"; + if (showUpdated) sql += " where notify_count>0"; switch (sortBy) { case SortByUpdated: @@ -259,8 +255,7 @@ void ChannelView::updateQuery(bool transition) { } #ifdef APP_EXTRA - if (transition) - Extra::fadeInWidget(this, this); + if (transition) Extra::fadeInWidget(this, this); #endif channelsModel->setQuery(sql, Database::instance().getConnection()); diff --git a/src/channelview.h b/src/channelview.h index 35c709b..6285fc3 100644 --- a/src/channelview.h +++ b/src/channelview.h @@ -30,12 +30,11 @@ class ChannelModel; class ChannelListView; class ChannelView : public View { - Q_OBJECT public: - ChannelView(QWidget *parent = 0); - + ChannelView(QWidget *parent = nullptr); + signals: void activated(VideoSource *videoSource); @@ -72,11 +71,10 @@ private: ChannelListView *listView; ChannelModel *channelsModel; - QVector statusActions; + QVector statusActions; bool showUpdated; SortBy sortBy; QAction *markAsWatchedAction; - }; #endif // CHANNELSVIEW_H diff --git a/src/clickablelabel.cpp b/src/clickablelabel.cpp index d1f6914..bc07301 100644 --- a/src/clickablelabel.cpp +++ b/src/clickablelabel.cpp @@ -4,6 +4,20 @@ ClickableLabel::ClickableLabel(QWidget *parent) : QLabel(parent) { setCursor(Qt::PointingHandCursor); } +ClickableLabel::ClickableLabel(const QString &text, QWidget *parent) : QLabel(text, parent) { + setCursor(Qt::PointingHandCursor); +} + void ClickableLabel::mouseReleaseEvent(QMouseEvent *e) { - if (rect().contains(e->pos())) emit clicked(); + if (e->button() == Qt::LeftButton && rect().contains(e->pos())) emit clicked(); +} + +void ClickableLabel::leaveEvent(QEvent *e) { + emit hovered(false); + QLabel::leaveEvent(e); +} + +void ClickableLabel::enterEvent(QEvent *e) { + emit hovered(true); + QLabel::enterEvent(e); } diff --git a/src/clickablelabel.h b/src/clickablelabel.h index 747c861..9a24a5c 100644 --- a/src/clickablelabel.h +++ b/src/clickablelabel.h @@ -4,18 +4,20 @@ #include class ClickableLabel : public QLabel { - Q_OBJECT public: - explicit ClickableLabel(QWidget *parent = 0); + explicit ClickableLabel(QWidget *parent = nullptr); + explicit ClickableLabel(const QString &text, QWidget *parent = nullptr); signals: void clicked(); + void hovered(bool value); protected: void mouseReleaseEvent(QMouseEvent *e); - + void enterEvent(QEvent *e); + void leaveEvent(QEvent *e); }; #endif // CLICKABLELABEL_H diff --git a/src/constants.cpp b/src/constants.cpp index 0a54292..a9bfb0b 100644 --- a/src/constants.cpp +++ b/src/constants.cpp @@ -28,5 +28,5 @@ const char *Constants::NAME = STRINGIFY(APP_NAME); const char *Constants::UNIX_NAME = STRINGIFY(APP_UNIX_NAME); const char *Constants::ORG_NAME = "Flavio Tordini"; const char *Constants::ORG_DOMAIN = "flavio.tordini.org"; -const char *Constants::WEBSITE = "http://flavio.tordini.org/minitube"; +const char *Constants::WEBSITE = "https://flavio.tordini.org/minitube"; const char *Constants::EMAIL = "flavio.tordini@gmail.com"; diff --git a/src/datautils.cpp b/src/datautils.cpp index d35fc13..3101880 100644 --- a/src/datautils.cpp +++ b/src/datautils.cpp @@ -72,7 +72,7 @@ QString DataUtils::formatDateTime(const QDateTime &dt) { s = QCoreApplication::translate("DataUtils", "%n day(s) ago", Q_NULLPTR, n); } else if (seconds < (f = 60 * 60 * 24 * 30)) { int n = seconds / (60 * 60 * 24 * 7); - s = QCoreApplication::translate("DataUtils", "%n weeks(s) ago", Q_NULLPTR, n); + s = QCoreApplication::translate("DataUtils", "%n week(s) ago", Q_NULLPTR, n); } else if (seconds < (f = 60 * 60 * 24 * 365)) { int n = seconds / (60 * 60 * 24 * 30); s = QCoreApplication::translate("DataUtils", "%n month(s) ago", Q_NULLPTR, n); @@ -93,3 +93,27 @@ QString DataUtils::formatDuration(uint secs) { if (hours == 0) return res.sprintf("%d:%02d", minutes, seconds); return res.sprintf("%d:%02d:%02d", hours, minutes, seconds); } + +QString DataUtils::formatCount(int c) { + QString s; + int f = 1; + if (c < 1) { + return s; + } else if (c < (f *= 1000)) { + s = QString::number(c); + } else if (c < (f *= 1000)) { + int n = c / 1000; + s = QString::number(n) + + QCoreApplication::translate("DataUtils", "K", "K as in Kilo, i.e. thousands"); + } else if (c < (f *= 1000)) { + int n = c / (1000 * 1000); + s = QString::number(n) + + QCoreApplication::translate("DataUtils", "M", "M stands for Millions"); + } else { + int n = c / (1000 * 1000 * 1000); + s = QString::number(n) + + QCoreApplication::translate("DataUtils", "B", "B stands for Billions"); + } + + return QCoreApplication::translate("DataUtils", "%1 views").arg(s); +} diff --git a/src/datautils.h b/src/datautils.h index ad6d203..f41adf1 100644 --- a/src/datautils.h +++ b/src/datautils.h @@ -4,7 +4,6 @@ #include class DataUtils { - public: static QString stringToFilename(const QString &s); static QString regioneCode(const QLocale &locale); @@ -12,10 +11,10 @@ public: static uint parseIsoPeriod(const QString &isoPeriod); static QString formatDateTime(const QDateTime &dt); static QString formatDuration(uint secs); + static QString formatCount(int c); private: - DataUtils() { } - + DataUtils() {} }; #endif // DATAUTILS_H diff --git a/src/exlineedit.cpp b/src/exlineedit.cpp deleted file mode 100644 index 1984eb6..0000000 --- a/src/exlineedit.cpp +++ /dev/null @@ -1,186 +0,0 @@ -#include "exlineedit.h" -#include "iconutils.h" - -ClearButton::ClearButton(QWidget *parent) : QAbstractButton(parent), hovered(false), mousePressed(false) { - setCursor(Qt::ArrowCursor); - setToolTip(tr("Clear")); - setVisible(false); - setFocusPolicy(Qt::NoFocus); -} - -void ClearButton::paintEvent(QPaintEvent *e) { - Q_UNUSED(e); - QPainter painter(this); - const int h = height(); - int iconSize = 16; - if (h > 30) iconSize = 22; - QIcon::Mode iconMode = QIcon::Normal; - if (mousePressed) iconMode = QIcon::Active; - QPixmap p = IconUtils::icon("edit-clear").pixmap(iconSize, iconSize, iconMode); - int x = (width() - p.width()) / 2; - int y = (h - p.height()) / 2; - painter.drawPixmap(x, y, p); -} - -void ClearButton::textChanged(const QString &text) { - setVisible(!text.isEmpty()); -} - -void ClearButton::enterEvent(QEvent *e) { - hovered = true; - QAbstractButton::enterEvent(e); -} - -void ClearButton::leaveEvent(QEvent *e) { - hovered = false; - QAbstractButton::leaveEvent(e); -} - -void ClearButton::mousePressEvent(QMouseEvent *e) { - mousePressed = true; - QAbstractButton::mousePressEvent(e); -} - -void ClearButton::mouseReleaseEvent(QMouseEvent *e) { - mousePressed = false; - QAbstractButton::mouseReleaseEvent(e); -} - -ExLineEdit::ExLineEdit(QWidget *parent) - : QWidget(parent) - , m_leftWidget(0) - , m_lineEdit(new QLineEdit(this)) - , m_clearButton(new ClearButton(this)) { - setFocusPolicy(m_lineEdit->focusPolicy()); - setAttribute(Qt::WA_InputMethodEnabled); - setSizePolicy(m_lineEdit->sizePolicy()); - setBackgroundRole(m_lineEdit->backgroundRole()); - setMouseTracking(true); - setAcceptDrops(true); - setAttribute(Qt::WA_MacShowFocusRect, true); - QPalette p = m_lineEdit->palette(); - setPalette(p); - - // line edit - m_lineEdit->setFrame(false); - m_lineEdit->setFocusProxy(this); - m_lineEdit->setAttribute(Qt::WA_MacShowFocusRect, false); - m_lineEdit->setStyleSheet("background:transparent"); - QPalette clearPalette = m_lineEdit->palette(); - clearPalette.setBrush(QPalette::Base, QBrush(Qt::transparent)); - m_lineEdit->setPalette(clearPalette); - - // clearButton - connect(m_clearButton, SIGNAL(clicked()), m_lineEdit, SLOT(clear())); - connect(m_lineEdit, SIGNAL(textChanged(const QString&)), m_clearButton, SLOT(textChanged(const QString&))); -} - -void ExLineEdit::setFont(const QFont &font) { - m_lineEdit->setFont(font); - updateGeometries(); -} - -void ExLineEdit::setLeftWidget(QWidget *widget) { - m_leftWidget = widget; -} - -QWidget *ExLineEdit::leftWidget() const { - return m_leftWidget; -} - -void ExLineEdit::clear() { - m_lineEdit->clear(); -} - -QString ExLineEdit::text() { - return m_lineEdit->text(); -} - -void ExLineEdit::resizeEvent(QResizeEvent *e) { - Q_ASSERT(m_leftWidget); - updateGeometries(); - QWidget::resizeEvent(e); -} - -void ExLineEdit::updateGeometries() { - QStyleOptionFrame panel; - initStyleOption(&panel); - QRect rect = style()->subElementRect(QStyle::SE_LineEditContents, &panel, this); - - int padding = 3; - // int height = rect.height() + padding*2; - int width = rect.width(); - - // int m_leftWidgetHeight = m_leftWidget->height(); - m_leftWidget->setGeometry(rect.x() + 2, 0, - m_leftWidget->width(), m_leftWidget->height()); - - int clearButtonWidth = this->height(); - m_lineEdit->setGeometry(m_leftWidget->x() + m_leftWidget->width(), padding, - width - clearButtonWidth - m_leftWidget->width(), this->height() - padding*2); - - m_clearButton->setGeometry(this->width() - clearButtonWidth, 0, - clearButtonWidth, this->height()); -} - -void ExLineEdit::initStyleOption(QStyleOptionFrame *option) const { - option->initFrom(this); - option->rect = contentsRect(); - option->lineWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, option, this); - option->midLineWidth = 0; - option->state |= QStyle::State_Sunken; - if (m_lineEdit->isReadOnly()) - option->state |= QStyle::State_ReadOnly; -#ifdef QT_KEYPAD_NAVIGATION - if (hasEditFocus()) - option->state |= QStyle::State_HasEditFocus; -#endif - option->features = QStyleOptionFrame::None; -} - -QSize ExLineEdit::sizeHint() const { - m_lineEdit->setFrame(true); - QSize size = m_lineEdit->sizeHint(); - m_lineEdit->setFrame(false); - size = size + QSize(3, 3); - return size; -} - -void ExLineEdit::focusInEvent(QFocusEvent *e) { - m_lineEdit->event(e); - QWidget::focusInEvent(e); -} - -void ExLineEdit::focusOutEvent(QFocusEvent *e) { - m_lineEdit->event(e); - - if (m_lineEdit->completer()) { - connect(m_lineEdit->completer(), SIGNAL(activated(QString)), - m_lineEdit, SLOT(setText(QString))); - connect(m_lineEdit->completer(), SIGNAL(highlighted(QString)), - m_lineEdit, SLOT(_q_completionHighlighted(QString))); - } - QWidget::focusOutEvent(e); -} - -void ExLineEdit::keyPressEvent(QKeyEvent *e) { - if (e->key() == Qt::Key_Escape && !m_lineEdit->text().isEmpty()) { - m_lineEdit->clear(); - } - m_lineEdit->event(e); - QWidget::keyPressEvent(e); -} - -bool ExLineEdit::event(QEvent *e) { - if (e->type() == QEvent::ShortcutOverride || e->type() == QEvent::InputMethod) - m_lineEdit->event(e); - return QWidget::event(e); -} - -void ExLineEdit::paintEvent(QPaintEvent *e) { - Q_UNUSED(e); - QPainter p(this); - QStyleOptionFrame panel; - initStyleOption(&panel); - style()->drawPrimitive(QStyle::PE_PanelLineEdit, &panel, &p, this); -} diff --git a/src/exlineedit.h b/src/exlineedit.h deleted file mode 100644 index 94b3f46..0000000 --- a/src/exlineedit.h +++ /dev/null @@ -1,58 +0,0 @@ -#ifndef EXLINEEDIT_H -#define EXLINEEDIT_H - -#include - -class ClearButton : public QAbstractButton { - - Q_OBJECT - -public: - ClearButton(QWidget *parent = 0); - -public slots: - void textChanged(const QString &text); - -protected: - void paintEvent(QPaintEvent *e); - void enterEvent(QEvent *e); - void leaveEvent(QEvent *e); - void mousePressEvent(QMouseEvent *e); - void mouseReleaseEvent(QMouseEvent *e); - -private: - bool hovered; - bool mousePressed; -}; - -class ExLineEdit : public QWidget { - - Q_OBJECT - -public: - ExLineEdit(QWidget *parent = 0); - QLineEdit *lineEdit() const { return m_lineEdit; } - void setLeftWidget(QWidget *widget); - QWidget *leftWidget() const; - void clear(); - QString text(); - QSize sizeHint() const; - void updateGeometries(); - void setFont(const QFont &font); - -protected: - void focusInEvent(QFocusEvent *e); - void focusOutEvent(QFocusEvent *e); - void keyPressEvent(QKeyEvent *e); - void paintEvent(QPaintEvent *e); - void resizeEvent(QResizeEvent *e); - bool event(QEvent *e); - void initStyleOption(QStyleOptionFrame *option) const; - - QWidget *m_leftWidget; - QLineEdit *m_lineEdit; - ClearButton *m_clearButton; -}; - -#endif // EXLINEEDIT_H - diff --git a/src/fontutils.cpp b/src/fontutils.cpp index c9bf5c2..505e8d9 100644 --- a/src/fontutils.cpp +++ b/src/fontutils.cpp @@ -35,12 +35,11 @@ QFont createFont(bool isBold, double sizeScale) { QFont createFontWithMinSize(bool isBold, double sizeScale) { const int minPixels = 11; QFont font = createFont(isBold, sizeScale); - if (font.pixelSize() < minPixels) - font.setPixelSize(minPixels); + if (font.pixelSize() < minPixels) font.setPixelSize(minPixels); return font; } -} +} // namespace const QFont &FontUtils::small() { static const QFont font = createFontWithMinSize(false, .9); @@ -53,12 +52,12 @@ const QFont &FontUtils::smallBold() { } const QFont &FontUtils::medium() { - static const QFont font = createFont(false, 1.1); + static const QFont font = createFont(false, 1.15); return font; } const QFont &FontUtils::mediumBold() { - static const QFont font = createFont(true, 1.1); + static const QFont font = createFont(true, 1.15); return font; } diff --git a/src/gridwidget.cpp b/src/gridwidget.cpp index 3e445aa..032fdba 100644 --- a/src/gridwidget.cpp +++ b/src/gridwidget.cpp @@ -20,10 +20,7 @@ $END_LICENSE */ #include "gridwidget.h" -GridWidget::GridWidget(QWidget *parent) : - QWidget(parent), - hovered(false), - pressed(false) { +GridWidget::GridWidget(QWidget *parent) : QWidget(parent), hovered(false), pressed(false) { setCursor(Qt::PointingHandCursor); setFocusPolicy(Qt::StrongFocus); } @@ -60,6 +57,5 @@ void GridWidget::enterEvent(QEvent *event) { } void GridWidget::keyReleaseEvent(QKeyEvent *event) { - if (event->key() == Qt::Key_Return) - emit activated(); + if (event->key() == Qt::Key_Return) emit activated(); } diff --git a/src/gridwidget.h b/src/gridwidget.h index 28462fb..c23a785 100644 --- a/src/gridwidget.h +++ b/src/gridwidget.h @@ -24,15 +24,14 @@ $END_LICENSE */ #include class GridWidget : public QWidget { - Q_OBJECT public: - GridWidget(QWidget *parent = 0); - + GridWidget(QWidget *parent = nullptr); + signals: void activated(); - + protected: void mouseMoveEvent(QMouseEvent *event); void mousePressEvent(QMouseEvent *event); @@ -40,7 +39,7 @@ protected: void enterEvent(QEvent *event); void leaveEvent(QEvent *event); void keyReleaseEvent(QKeyEvent *event); - + bool hovered; bool pressed; }; diff --git a/src/homeview.cpp b/src/homeview.cpp index a8f061b..8ee3241 100644 --- a/src/homeview.cpp +++ b/src/homeview.cpp @@ -19,23 +19,21 @@ along with Minitube. If not, see . $END_LICENSE */ #include "homeview.h" -#include "segmentedcontrol.h" -#include "searchview.h" -#include "standardfeedsview.h" +#include "channelaggregator.h" #include "channelview.h" +#include "iconutils.h" #include "mainwindow.h" #include "mediaview.h" +#include "searchview.h" +#include "segmentedcontrol.h" +#include "standardfeedsview.h" #include "ytstandardfeed.h" -#include "iconutils.h" -#include "channelaggregator.h" #ifdef APP_MAC #include "macutils.h" #endif -HomeView::HomeView(QWidget *parent) : View(parent), - standardFeedsView(0), - channelsView(0) { - +HomeView::HomeView(QWidget *parent) + : View(parent), searchView(nullptr), standardFeedsView(nullptr), channelsView(nullptr) { QBoxLayout *layout = new QVBoxLayout(this); layout->setMargin(0); layout->setSpacing(0); @@ -45,11 +43,6 @@ HomeView::HomeView(QWidget *parent) : View(parent), stackedWidget = new QStackedWidget(); layout->addWidget(stackedWidget); - - searchView = new SearchView(this); - connect(searchView, SIGNAL(search(SearchParams*)), - MainWindow::instance(), SLOT(showMedia(SearchParams*))); - stackedWidget->addWidget(searchView); } void HomeView::setupBar() { @@ -76,31 +69,29 @@ void HomeView::setupBar() { connect(ChannelAggregator::instance(), SIGNAL(unwatchedCountChanged(int)), SLOT(unwatchedCountChanged(int))); - const auto a = bar->actions(); - for (QAction* action : a) { + const auto &a = bar->actions(); + for (QAction *action : a) { addAction(action); - IconUtils::setupAction(action); + MainWindow::instance()->setupAction(action); } } void HomeView::showWidget(QWidget *widget) { - QWidget* currentWidget = stackedWidget->currentWidget(); - if (currentWidget == widget) return; - QMetaObject::invokeMethod(currentWidget, "disappear"); - currentWidget->setEnabled(false); + QWidget *currentWidget = stackedWidget->currentWidget(); + if (currentWidget && currentWidget != widget) { + QMetaObject::invokeMethod(currentWidget, "disappear"); + currentWidget->setEnabled(false); + } stackedWidget->setCurrentWidget(widget); widget->setEnabled(true); - QMetaObject::invokeMethod(widget, "appear"); - QTimer::singleShot(0, widget, SLOT(setFocus())); - -#ifdef APP_MAC - // Workaround cursor bug on macOS - window()->unsetCursor(); -#endif + QMetaObject::invokeMethod(widget, "appear", Qt::QueuedConnection); } void HomeView::appear() { - QMetaObject::invokeMethod(stackedWidget->currentWidget(), "appear", Qt::QueuedConnection); + if (stackedWidget->count() == 0) + showSearch(); + else + QMetaObject::invokeMethod(stackedWidget->currentWidget(), "appear", Qt::QueuedConnection); } void HomeView::disappear() { @@ -108,6 +99,12 @@ void HomeView::disappear() { } void HomeView::showSearch() { + if (!searchView) { + searchView = new SearchView(this); + connect(searchView, SIGNAL(search(SearchParams *)), MainWindow::instance(), + SLOT(showMedia(SearchParams *))); + stackedWidget->addWidget(searchView); + } showWidget(searchView); bar->setCheckedAction(0); } @@ -115,9 +112,8 @@ void HomeView::showSearch() { void HomeView::showStandardFeeds() { if (!standardFeedsView) { standardFeedsView = new StandardFeedsView(); - connect(standardFeedsView, SIGNAL(activated(VideoSource*)), - MainWindow::instance(), - SLOT(showMedia(VideoSource*))); + connect(standardFeedsView, SIGNAL(activated(VideoSource *)), MainWindow::instance(), + SLOT(showMedia(VideoSource *))); stackedWidget->addWidget(standardFeedsView); } showWidget(standardFeedsView); @@ -127,9 +123,8 @@ void HomeView::showStandardFeeds() { void HomeView::showChannels() { if (!channelsView) { channelsView = new ChannelView(); - connect(channelsView, SIGNAL(activated(VideoSource*)), - MainWindow::instance(), - SLOT(showMedia(VideoSource*))); + connect(channelsView, SIGNAL(activated(VideoSource *)), MainWindow::instance(), + SLOT(showMedia(VideoSource *))); stackedWidget->addWidget(channelsView); } showWidget(channelsView); diff --git a/src/http/README.md b/src/http/README.md deleted file mode 100644 index a1370ff..0000000 --- a/src/http/README.md +++ /dev/null @@ -1,63 +0,0 @@ -# A wrapper for the Qt Network Access API - -This is just a wrapper around Qt's QNetworkAccessManager and friends. I use it in my Qt apps at http://flavio.tordini.org . It allows me to add missing functionality as needed, e.g.: - -- Throttling (as required by many web APIs nowadays) -- Read timeouts (don't let your requests get stuck forever) -- Automatic retries -- User agent and request header defaults -- Partial requests -- Redirection support (now supported by Qt >= 5.6) - -It has a simpler, higher-level API that I find easier to work with. The design emerged naturally in years of practical use. - -A basic example: - -``` -QObject *reply = Http::instance().get("https://google.com/"); -connect(reply, SIGNAL(data(QByteArray)), SLOT(onSuccess(QByteArray))); -connect(reply, SIGNAL(error(QString)), SLOT(onError(QString))); - -void MyClass::onSuccess(const QByteArray &bytes) { - qDebug() << "Feel the bytes!" << bytes; -} - -void MyClass::onError(const QString &message) { - qDebug() << "Something's wrong here" << message; -} -``` - -This is a real-world example of building a Http object suitable to a web service. It throttles requests, uses a custom user agent and caches results: - -``` -Http &myHttp() { - static Http *http = [] { - Http *http = new Http; - http->addRequestHeader("User-Agent", userAgent()); - - ThrottledHttp *throttledHttp = new ThrottledHttp(*http); - throttledHttp->setMilliseconds(1000); - - CachedHttp *cachedHttp = new CachedHttp(*throttledHttp, "mycache"); - cachedHttp->setMaxSeconds(86400 * 30); - - return cachedHttp; - }(); - return *http; -} -``` - -If the full power (and complexity) of QNetworkReply is needed you can always fallback to it: - -``` -HttpRequest req; -req.url = "https://flavio.tordini.org/"; -QNetworkReply *reply = Http::instance().networkReply(req); -// Use QNetworkReply as needed... -``` - -You can use this library under the MIT license and at your own risk. If you do, you're welcome contributing your changes and fixes. - -Cheers, - -Flavio diff --git a/src/http/http.pri b/src/http/http.pri deleted file mode 100644 index 6a210c7..0000000 --- a/src/http/http.pri +++ /dev/null @@ -1,16 +0,0 @@ -QT *= network - -INCLUDEPATH += $$PWD/src -DEPENDPATH += $$PWD/src - -HEADERS += \ - $$PWD/src/cachedhttp.h \ - $$PWD/src/http.h \ - $$PWD/src/localcache.h \ - $$PWD/src/throttledhttp.h - -SOURCES += \ - $$PWD/src/cachedhttp.cpp \ - $$PWD/src/http.cpp \ - $$PWD/src/localcache.cpp \ - $$PWD/src/throttledhttp.cpp diff --git a/src/http/src/cachedhttp.cpp b/src/http/src/cachedhttp.cpp deleted file mode 100644 index b49f5b8..0000000 --- a/src/http/src/cachedhttp.cpp +++ /dev/null @@ -1,70 +0,0 @@ -#include "cachedhttp.h" -#include "localcache.h" - -namespace { - -QByteArray requestHash(const HttpRequest &req) { - const char sep = '|'; - QByteArray s = req.url.toEncoded() + sep + req.body + sep + QByteArray::number(req.offset); - if (req.operation == QNetworkAccessManager::PostOperation) { - s.append(sep); - s.append("POST"); - } - return LocalCache::hash(s); -} -} - -CachedHttpReply::CachedHttpReply(const QByteArray &body, const HttpRequest &req) - : bytes(body), req(req) { - QTimer::singleShot(0, this, SLOT(emitSignals())); -} - -QByteArray CachedHttpReply::body() const { - return bytes; -} - -void CachedHttpReply::emitSignals() { - emit data(body()); - emit finished(*this); - deleteLater(); -} - -WrappedHttpReply::WrappedHttpReply(LocalCache *cache, const QByteArray &key, QObject *httpReply) - : QObject(httpReply), cache(cache), key(key), httpReply(httpReply) { - connect(httpReply, SIGNAL(data(QByteArray)), SIGNAL(data(QByteArray))); - connect(httpReply, SIGNAL(error(QString)), SIGNAL(error(QString))); - connect(httpReply, SIGNAL(finished(HttpReply)), SLOT(originFinished(HttpReply))); -} - -void WrappedHttpReply::originFinished(const HttpReply &reply) { - if (reply.isSuccessful()) cache->insert(key, reply.body()); - emit finished(reply); -} - -CachedHttp::CachedHttp(Http &http, const char *name) - : http(http), cache(LocalCache::instance(name)), cachePostRequests(false) {} - -void CachedHttp::setMaxSeconds(uint seconds) { - cache->setMaxSeconds(seconds); -} - -void CachedHttp::setMaxSize(uint maxSize) { - cache->setMaxSize(maxSize); -} - -QObject *CachedHttp::request(const HttpRequest &req) { - bool cacheable = req.operation == QNetworkAccessManager::GetOperation || - (cachePostRequests && req.operation == QNetworkAccessManager::PostOperation); - if (!cacheable) { - qDebug() << "Not cacheable" << req.url; - return http.request(req); - } - const QByteArray key = requestHash(req); - const QByteArray value = cache->value(key); - if (!value.isNull()) { - qDebug() << "CachedHttp HIT" << req.url; - return new CachedHttpReply(value, req); - } - qDebug() << "CachedHttp MISS" << req.url.toString(); - return new WrappedHttpReply(cache, key, http.request(req)); -} diff --git a/src/http/src/cachedhttp.h b/src/http/src/cachedhttp.h deleted file mode 100644 index 8758f24..0000000 --- a/src/http/src/cachedhttp.h +++ /dev/null @@ -1,59 +0,0 @@ -#ifndef CACHEDHTTP_H -#define CACHEDHTTP_H - -#include "http.h" - -class LocalCache; - -class CachedHttp : public Http { -public: - CachedHttp(Http &http = Http::instance(), const char *name = "http"); - void setMaxSeconds(uint seconds); - void setMaxSize(uint maxSize); - void setCachePostRequests(bool value) { cachePostRequests = value; } - QObject *request(const HttpRequest &req); - -private: - Http &http; - LocalCache *cache; - bool cachePostRequests; -}; - -class CachedHttpReply : public HttpReply { - Q_OBJECT - -public: - CachedHttpReply(const QByteArray &body, const HttpRequest &req); - QUrl url() const { return req.url; } - int statusCode() const { return 200; } - QByteArray body() const; - -private slots: - void emitSignals(); - -private: - const QByteArray bytes; - const HttpRequest &req; -}; - -class WrappedHttpReply : public QObject { - Q_OBJECT - -public: - WrappedHttpReply(LocalCache *cache, const QByteArray &key, QObject *httpReply); - -signals: - void data(const QByteArray &bytes); - void error(const QString &message); - void finished(const HttpReply &reply); - -private slots: - void originFinished(const HttpReply &reply); - -private: - LocalCache *cache; - QByteArray key; - QObject *httpReply; -}; - -#endif // CACHEDHTTP_H diff --git a/src/http/src/http.cpp b/src/http/src/http.cpp deleted file mode 100644 index 2326e66..0000000 --- a/src/http/src/http.cpp +++ /dev/null @@ -1,305 +0,0 @@ -#include "http.h" - -namespace { - -QNetworkAccessManager *createNetworkAccessManager() { - QNetworkAccessManager *nam = new QNetworkAccessManager(); - return nam; -} - -QNetworkAccessManager *networkAccessManager() { - static QMap nams; - QThread *t = QThread::currentThread(); - QMap::const_iterator i = nams.constFind(t); - if (i != nams.constEnd()) return i.value(); - QNetworkAccessManager *nam = createNetworkAccessManager(); - nams.insert(t, nam); - return nam; -} - -static int defaultReadTimeout = 10000; -} - -Http::Http() : requestHeaders(getDefaultRequestHeaders()), readTimeout(defaultReadTimeout) {} - -void Http::setRequestHeaders(const QMap &headers) { - requestHeaders = headers; -} - -QMap &Http::getRequestHeaders() { - return requestHeaders; -} - -void Http::addRequestHeader(const QByteArray &name, const QByteArray &value) { - requestHeaders.insert(name, value); -} - -void Http::setReadTimeout(int timeout) { - readTimeout = timeout; -} - -Http &Http::instance() { - static Http *i = new Http(); - return *i; -} - -const QMap &Http::getDefaultRequestHeaders() { - static const QMap defaultRequestHeaders = [] { - QMap h; - h.insert("Accept-Charset", "utf-8"); - h.insert("Connection", "Keep-Alive"); - return h; - }(); - return defaultRequestHeaders; -} - -void Http::setDefaultReadTimeout(int timeout) { - defaultReadTimeout = timeout; -} - -QNetworkReply *Http::networkReply(const HttpRequest &req) { - QNetworkRequest request(req.url); - - QMap &headers = requestHeaders; - if (!req.headers.isEmpty()) headers = req.headers; - - QMap::const_iterator it; - for (it = headers.constBegin(); it != headers.constEnd(); ++it) - request.setRawHeader(it.key(), it.value()); - - if (req.offset > 0) - request.setRawHeader("Range", QString("bytes=%1-").arg(req.offset).toUtf8()); - - QNetworkAccessManager *manager = networkAccessManager(); - - QNetworkReply *networkReply = 0; - switch (req.operation) { - case QNetworkAccessManager::GetOperation: - networkReply = manager->get(request); - break; - - case QNetworkAccessManager::HeadOperation: - networkReply = manager->head(request); - break; - - case QNetworkAccessManager::PostOperation: - networkReply = manager->post(request, req.body); - break; - - default: - qWarning() << "Unknown operation:" << req.operation; - } - - return networkReply; -} - -QObject *Http::request(const HttpRequest &req) { - return new NetworkHttpReply(req, *this); -} - -QObject *Http::request(const QUrl &url, - QNetworkAccessManager::Operation operation, - const QByteArray &body, - uint offset) { - HttpRequest req; - req.url = url; - req.operation = operation; - req.body = body; - req.offset = offset; - return request(req); -} - -QObject *Http::get(const QUrl &url) { - return request(url, QNetworkAccessManager::GetOperation); -} - -QObject *Http::head(const QUrl &url) { - return request(url, QNetworkAccessManager::HeadOperation); -} - -QObject *Http::post(const QUrl &url, const QMap ¶ms) { - QByteArray body; - QMapIterator i(params); - while (i.hasNext()) { - i.next(); - body += QUrl::toPercentEncoding(i.key()) + '=' + QUrl::toPercentEncoding(i.value()) + '&'; - } - HttpRequest req; - req.url = url; - req.operation = QNetworkAccessManager::PostOperation; - req.body = body; - req.headers = requestHeaders; - req.headers.insert("Content-Type", "application/x-www-form-urlencoded"); - return request(req); -} - -QObject *Http::post(const QUrl &url, const QByteArray &body, const QByteArray &contentType) { - HttpRequest req; - req.url = url; - req.operation = QNetworkAccessManager::PostOperation; - req.body = body; - req.headers = requestHeaders; - QByteArray cType = contentType; - if (cType.isEmpty()) cType = "application/x-www-form-urlencoded"; - req.headers.insert("Content-Type", cType); - return request(req); -} - -NetworkHttpReply::NetworkHttpReply(const HttpRequest &req, Http &http) - : http(http), req(req), retryCount(0) { - if (req.url.isEmpty()) { - qWarning() << "Empty URL"; - } - - networkReply = http.networkReply(req); - setParent(networkReply); - setupReply(); - - readTimeoutTimer = new QTimer(this); - readTimeoutTimer->setInterval(http.getReadTimeout()); - readTimeoutTimer->setSingleShot(true); - connect(readTimeoutTimer, SIGNAL(timeout()), SLOT(readTimeout()), Qt::UniqueConnection); - readTimeoutTimer->start(); -} - -void NetworkHttpReply::setupReply() { - connect(networkReply, SIGNAL(error(QNetworkReply::NetworkError)), - SLOT(replyError(QNetworkReply::NetworkError)), Qt::UniqueConnection); - connect(networkReply, SIGNAL(finished()), SLOT(replyFinished()), Qt::UniqueConnection); - connect(networkReply, SIGNAL(downloadProgress(qint64, qint64)), - SLOT(downloadProgress(qint64, qint64)), Qt::UniqueConnection); -} - -QString NetworkHttpReply::errorMessage() { - return url().toString() + QLatin1Char(' ') + QString::number(statusCode()) + QLatin1Char(' ') + - reasonPhrase(); -} - -void NetworkHttpReply::emitError() { - const QString msg = errorMessage(); -#ifndef QT_NO_DEBUG_OUTPUT - qDebug() << "Http:" << msg; - if (!req.body.isEmpty()) qDebug() << "Http:" << req.body; -#endif - emit error(msg); - emitFinished(); -} - -void NetworkHttpReply::emitFinished() { - readTimeoutTimer->stop(); - - // disconnect to avoid replyFinished() from being called - networkReply->disconnect(); - - emit finished(*this); - - // bye bye my reply - // this will also delete this object and HttpReply as the QNetworkReply is their parent - networkReply->deleteLater(); -} - -void NetworkHttpReply::replyFinished() { - QUrl redirection = networkReply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl(); - if (redirection.isValid()) { - HttpRequest redirectReq; - redirectReq.url = redirection; - redirectReq.operation = req.operation; - redirectReq.body = req.body; - redirectReq.offset = req.offset; - QNetworkReply *redirectReply = http.networkReply(redirectReq); - setParent(redirectReply); - networkReply->deleteLater(); - networkReply = redirectReply; - setupReply(); - readTimeoutTimer->start(); - return; - } - - if (isSuccessful()) { - bytes = networkReply->readAll(); - emit data(bytes); - -#ifndef QT_NO_DEBUG_OUTPUT - if (!networkReply->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool()) - qDebug() << networkReply->url().toString() << statusCode(); - else - qDebug() << "CACHE" << networkReply->url().toString(); -#endif - } - - emitFinished(); -} - -void NetworkHttpReply::replyError(QNetworkReply::NetworkError code) { - Q_UNUSED(code); - const int status = statusCode(); - if (retryCount <= 3 && status >= 500 && status < 600) { - qDebug() << "Retrying" << req.url; - networkReply->disconnect(); - networkReply->deleteLater(); - QNetworkReply *retryReply = http.networkReply(req); - setParent(retryReply); - networkReply = retryReply; - setupReply(); - retryCount++; - readTimeoutTimer->start(); - } else { - emitError(); - return; - } -} - -void NetworkHttpReply::downloadProgress(qint64 bytesReceived, qint64 /* bytesTotal */) { - // qDebug() << "Downloading" << bytesReceived << bytesTotal << networkReply->url(); - if (bytesReceived > 0 && readTimeoutTimer->isActive()) { - readTimeoutTimer->stop(); - disconnect(networkReply, SIGNAL(downloadProgress(qint64, qint64)), this, - SLOT(downloadProgress(qint64, qint64))); - } -} - -void NetworkHttpReply::readTimeout() { - if (!networkReply) return; - networkReply->disconnect(); - networkReply->abort(); - networkReply->deleteLater(); - - if (retryCount > 3 && (networkReply->operation() != QNetworkAccessManager::GetOperation && - networkReply->operation() != QNetworkAccessManager::HeadOperation)) { - emitError(); - emit finished(*this); - return; - } - - qDebug() << "Timeout" << req.url; - QNetworkReply *retryReply = http.networkReply(req); - setParent(retryReply); - networkReply = retryReply; - setupReply(); - retryCount++; - readTimeoutTimer->start(); -} - -QUrl NetworkHttpReply::url() const { - return networkReply->url(); -} - -int NetworkHttpReply::statusCode() const { - return networkReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); -} - -QString NetworkHttpReply::reasonPhrase() const { - return networkReply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString(); -} - -const QList NetworkHttpReply::headers() const { - return networkReply->rawHeaderPairs(); -} - -QByteArray NetworkHttpReply::header(const QByteArray &headerName) const { - return networkReply->rawHeader(headerName); -} - -QByteArray NetworkHttpReply::body() const { - return bytes; -} diff --git a/src/http/src/http.h b/src/http/src/http.h deleted file mode 100644 index 02c9695..0000000 --- a/src/http/src/http.h +++ /dev/null @@ -1,104 +0,0 @@ -#ifndef HTTP_H -#define HTTP_H - -#include - -class HttpRequest { -public: - HttpRequest() : operation(QNetworkAccessManager::GetOperation), offset(0) {} - QUrl url; - QNetworkAccessManager::Operation operation; - QByteArray body; - uint offset; - QMap headers; -}; - -class Http { -public: - static Http &instance(); - static const QMap &getDefaultRequestHeaders(); - static void setDefaultReadTimeout(int timeout); - - Http(); - - void setRequestHeaders(const QMap &headers); - QMap &getRequestHeaders(); - void addRequestHeader(const QByteArray &name, const QByteArray &value); - - void setReadTimeout(int timeout); - int getReadTimeout() { return readTimeout; } - - QNetworkReply *networkReply(const HttpRequest &req); - virtual QObject *request(const HttpRequest &req); - QObject *request(const QUrl &url, - QNetworkAccessManager::Operation operation = QNetworkAccessManager::GetOperation, - const QByteArray &body = QByteArray(), - uint offset = 0); - QObject *get(const QUrl &url); - QObject *head(const QUrl &url); - QObject *post(const QUrl &url, const QMap ¶ms); - QObject *post(const QUrl &url, const QByteArray &body, const QByteArray &contentType); - -private: - QMap requestHeaders; - int readTimeout; -}; - -class HttpReply : public QObject { - Q_OBJECT - -public: - HttpReply(QObject *parent = 0) : QObject(parent) {} - virtual QUrl url() const = 0; - virtual int statusCode() const = 0; - int isSuccessful() const { return statusCode() >= 200 && statusCode() < 300; } - virtual QString reasonPhrase() const { return QString(); } - virtual const QList headers() const { - return QList(); - } - virtual QByteArray header(const QByteArray &headerName) const { - Q_UNUSED(headerName); - return QByteArray(); - } - - virtual QByteArray body() const = 0; - -signals: - void data(const QByteArray &bytes); - void error(const QString &message); - void finished(const HttpReply &reply); -}; - -class NetworkHttpReply : public HttpReply { - Q_OBJECT - -public: - NetworkHttpReply(const HttpRequest &req, Http &http); - QUrl url() const; - int statusCode() const; - QString reasonPhrase() const; - const QList headers() const; - QByteArray header(const QByteArray &headerName) const; - QByteArray body() const; - -private slots: - void replyFinished(); - void replyError(QNetworkReply::NetworkError); - void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); - void readTimeout(); - -private: - void setupReply(); - QString errorMessage(); - void emitError(); - void emitFinished(); - - Http &http; - HttpRequest req; - QNetworkReply *networkReply; - QTimer *readTimeoutTimer; - int retryCount; - QByteArray bytes; -}; - -#endif // HTTP_H diff --git a/src/http/src/localcache.cpp b/src/http/src/localcache.cpp deleted file mode 100644 index 1d38262..0000000 --- a/src/http/src/localcache.cpp +++ /dev/null @@ -1,171 +0,0 @@ -#include "localcache.h" - -LocalCache *LocalCache::instance(const char *name) { - static QMap instances; - auto i = instances.constFind(QByteArray::fromRawData(name, strlen(name))); - if (i != instances.constEnd()) return i.value(); - LocalCache *instance = new LocalCache(name); - instances.insert(instance->getName(), instance); - return instance; -} - -LocalCache::LocalCache(const QByteArray &name) - : name(name), maxSeconds(86400 * 30), maxSize(1024 * 1024 * 100), size(0), expiring(false), - insertCount(0) { - directory = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + QLatin1Char('/') + - QLatin1String(name) + QLatin1Char('/'); -#ifndef QT_NO_DEBUG_OUTPUT - hits = 0; - misses = 0; -#endif -} - -LocalCache::~LocalCache() { -#ifndef QT_NO_DEBUG_OUTPUT - debugStats(); -#endif -} - -QByteArray LocalCache::hash(const QByteArray &s) { - QCryptographicHash hash(QCryptographicHash::Sha1); - hash.addData(s); - const QByteArray h = QByteArray::number(*(qlonglong *)hash.result().constData(), 36); - static const char sep('/'); - QByteArray p; - p.reserve(h.length() + 2); - p.append(h.at(0)); - p.append(sep); - p.append(h.at(1)); - p.append(sep); - p.append(h.constData() + 2, strlen(h.constData()) - 2); // p.append(h.mid(2)); - return p; -} - -bool LocalCache::isCached(const QString &path) { - bool cached = (QFile::exists(path) && - (maxSeconds == 0 || - QDateTime::currentDateTime().toTime_t() - QFileInfo(path).created().toTime_t() < - maxSeconds)); -#ifndef QT_NO_DEBUG_OUTPUT - if (!cached) misses++; -#endif - return cached; -} - -QByteArray LocalCache::value(const QByteArray &key) { - const QString path = cachePath(key); - if (!isCached(path)) return QByteArray(); - - QFile file(path); - if (!file.open(QIODevice::ReadOnly)) { - qWarning() << __PRETTY_FUNCTION__ << file.fileName() << file.errorString(); -#ifndef QT_NO_DEBUG_OUTPUT - misses++; -#endif - return QByteArray(); - } -#ifndef QT_NO_DEBUG_OUTPUT - hits++; -#endif - return file.readAll(); -} - -void LocalCache::insert(const QByteArray &key, const QByteArray &value) { - const QueueItem item = {key, value}; - insertQueue.append(item); - QTimer::singleShot(0, [this]() { - if (insertQueue.isEmpty()) return; - for (const auto &item : insertQueue) { - const QString path = cachePath(item.key); - const QString parentDir = path.left(path.lastIndexOf('/')); - if (!QFile::exists(parentDir)) { - QDir().mkpath(parentDir); - } - QFile file(path); - if (!file.open(QIODevice::WriteOnly)) { - qWarning() << "Cannot create" << path; - continue; - } - file.write(item.value); - file.close(); - if (size > 0) size += item.value.size(); - } - insertQueue.clear(); - - // expire cache every n inserts - if (maxSize > 0 && ++insertCount % 100 == 0) { - if (size == 0 || size > maxSize) size = expire(); - } - }); -} - -bool LocalCache::clear() { -#ifndef QT_NO_DEBUG_OUTPUT - hits = 0; - misses = 0; -#endif - size = 0; - insertCount = 0; - return QDir(directory).removeRecursively(); -} - -QString LocalCache::cachePath(const QByteArray &key) const { - return directory + QLatin1String(key.constData()); -} - -qint64 LocalCache::expire() { - if (expiring) return size; - expiring = true; - - QDir::Filters filters = QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot; - QDirIterator it(directory, filters, QDirIterator::Subdirectories); - - QMultiMap cacheItems; - qint64 totalSize = 0; - while (it.hasNext()) { - QString path = it.next(); - QFileInfo info = it.fileInfo(); - cacheItems.insert(info.created(), path); - totalSize += info.size(); - qApp->processEvents(); - } - - int removedFiles = 0; - qint64 goal = (maxSize * 9) / 10; - auto i = cacheItems.constBegin(); - while (i != cacheItems.constEnd()) { - if (totalSize < goal) break; - QString name = i.value(); - QFile file(name); - qint64 size = file.size(); - file.remove(); - totalSize -= size; - ++removedFiles; - ++i; - qApp->processEvents(); - } -#ifndef QT_NO_DEBUG_OUTPUT - debugStats(); - if (removedFiles > 0) { - qDebug() << "Removed:" << removedFiles << "Kept:" << cacheItems.count() - removedFiles - << "New Size:" << totalSize; - } -#endif - - expiring = false; - - return totalSize; -} - -#ifndef QT_NO_DEBUG_OUTPUT -void LocalCache::debugStats() { - int total = hits + misses; - if (total > 0) { - qDebug() << "Cache:" << name << '\n' - << "Inserts:" << insertCount << '\n' - << "Requests:" << total << '\n' - << "Hits:" << hits << (hits * 100) / total << "%\n" - << "Misses:" << misses << (misses * 100) / total << "%"; - } -} -#endif diff --git a/src/http/src/localcache.h b/src/http/src/localcache.h deleted file mode 100644 index ff07109..0000000 --- a/src/http/src/localcache.h +++ /dev/null @@ -1,52 +0,0 @@ -#ifndef LOCALCACHE_H -#define LOCALCACHE_H - -#include - -/** - * @brief Not thread-safe - */ -class LocalCache { -public: - static LocalCache *instance(const char *name); - ~LocalCache(); - static QByteArray hash(const QByteArray &s); - - const QByteArray &getName() const { return name; } - - void setMaxSeconds(uint value) { maxSeconds = value; } - void setMaxSize(uint value) { maxSize = value; } - - QByteArray value(const QByteArray &key); - void insert(const QByteArray &key, const QByteArray &value); - bool clear(); - -private: - LocalCache(const QByteArray &name); - QString cachePath(const QByteArray &key) const; - bool isCached(const QString &path); - qint64 expire(); -#ifndef QT_NO_DEBUG_OUTPUT - void debugStats(); -#endif - - QByteArray name; - QString directory; - uint maxSeconds; - qint64 maxSize; - qint64 size; - bool expiring; - uint insertCount; - struct QueueItem { - QByteArray key; - QByteArray value; - }; - QVector insertQueue; - -#ifndef QT_NO_DEBUG_OUTPUT - uint hits; - uint misses; -#endif -}; - -#endif // LOCALCACHE_H diff --git a/src/http/src/throttledhttp.cpp b/src/http/src/throttledhttp.cpp deleted file mode 100644 index 1438a3a..0000000 --- a/src/http/src/throttledhttp.cpp +++ /dev/null @@ -1,58 +0,0 @@ -#include "throttledhttp.h" - -namespace { - -QElapsedTimer initElapsedTimer() { - QElapsedTimer timer; - timer.start(); - return timer; -} -} - -ThrottledHttp::ThrottledHttp(Http &http) : http(http), elapsedTimer(initElapsedTimer()) {} - -QObject *ThrottledHttp::request(const HttpRequest &req) { - return new ThrottledHttpReply(http, req, milliseconds, elapsedTimer); -} - -ThrottledHttpReply::ThrottledHttpReply(Http &http, - const HttpRequest &req, - int milliseconds, - QElapsedTimer &elapsedTimer) - : http(http), req(req), milliseconds(milliseconds), elapsedTimer(elapsedTimer), timer(0) { - checkElapsed(); -} - -void ThrottledHttpReply::checkElapsed() { - /* - static QMutex mutex; - QMutexLocker locker(&mutex); - */ - - const qint64 elapsedSinceLastRequest = elapsedTimer.elapsed(); - if (elapsedSinceLastRequest < milliseconds) { - if (!timer) { - timer = new QTimer(this); - timer->setSingleShot(true); - timer->setTimerType(Qt::PreciseTimer); - connect(timer, SIGNAL(timeout()), SLOT(checkElapsed())); - } - qDebug() << "Throttling" << req.url - << QString("%1ms").arg(milliseconds - elapsedSinceLastRequest); - timer->setInterval(milliseconds - elapsedSinceLastRequest); - timer->start(); - return; - } - elapsedTimer.start(); - doRequest(); -} - -void ThrottledHttpReply::doRequest() { - QObject *reply = http.request(req); - connect(reply, SIGNAL(data(QByteArray)), SIGNAL(data(QByteArray))); - connect(reply, SIGNAL(error(QString)), SIGNAL(error(QString))); - connect(reply, SIGNAL(finished(HttpReply)), SIGNAL(finished(HttpReply))); - - // this will cause the deletion of this object once the request is finished - setParent(reply); -} diff --git a/src/http/src/throttledhttp.h b/src/http/src/throttledhttp.h deleted file mode 100644 index 4173b37..0000000 --- a/src/http/src/throttledhttp.h +++ /dev/null @@ -1,44 +0,0 @@ -#ifndef THROTTLEDHTTP_H -#define THROTTLEDHTTP_H - -#include "http.h" -#include -#include - -class ThrottledHttp : public Http { -public: - ThrottledHttp(Http &http = Http::instance()); - void setMilliseconds(int milliseconds) { this->milliseconds = milliseconds; } - QObject *request(const HttpRequest &req); - -private: - Http &http; - int milliseconds; - QElapsedTimer elapsedTimer; -}; - -class ThrottledHttpReply : public HttpReply { - Q_OBJECT - -public: - ThrottledHttpReply(Http &http, - const HttpRequest &req, - int milliseconds, - QElapsedTimer &elapsedTimer); - QUrl url() const { return req.url; } - int statusCode() const { return 200; } - QByteArray body() const { return QByteArray(); } - -private slots: - void checkElapsed(); - -private: - void doRequest(); - Http &http; - HttpRequest req; - int milliseconds; - QElapsedTimer &elapsedTimer; - QTimer *timer; -}; - -#endif // THROTTLEDHTTP_H diff --git a/src/httputils.cpp b/src/httputils.cpp index fe007cb..d522f6e 100644 --- a/src/httputils.cpp +++ b/src/httputils.cpp @@ -1,9 +1,9 @@ #include "httputils.h" +#include "cachedhttp.h" #include "constants.h" #include "http.h" -#include "throttledhttp.h" -#include "cachedhttp.h" #include "localcache.h" +#include "throttledhttp.h" Http &HttpUtils::notCached() { static Http *h = [] { @@ -47,14 +47,15 @@ void HttpUtils::clearCaches() { const QByteArray &HttpUtils::userAgent() { static const QByteArray ua = [] { - return QString(QLatin1String(Constants::NAME) - + QLatin1Char('/') + QLatin1String(Constants::VERSION) - + QLatin1String(" ( ") + Constants::WEBSITE + QLatin1String(" )")).toUtf8(); + return QString(QLatin1String(Constants::NAME) + QLatin1Char('/') + + QLatin1String(Constants::VERSION) + QLatin1String(" ( ") + + Constants::WEBSITE + QLatin1String(" )")) + .toUtf8(); }(); return ua; } const QByteArray &HttpUtils::stealthUserAgent() { - static const QByteArray ua = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.95 Safari/537.36"; + static const QByteArray ua = "curl/7.37.0"; return ua; } diff --git a/src/iconutils.cpp b/src/iconutils.cpp index b09d400..6b53645 100644 --- a/src/iconutils.cpp +++ b/src/iconutils.cpp @@ -19,44 +19,61 @@ along with Minitube. If not, see . $END_LICENSE */ #include "iconutils.h" -#include #include "mainwindow.h" +#include + +namespace { +void addIconFile(QIcon &icon, + const QString &filename, + int size, + QIcon::Mode mode = QIcon::Normal, + QIcon::State state = QIcon::Off) { + if (QFile::exists(filename)) { + icon.addFile(filename, QSize(size, size), mode, state); + } +} +} // namespace QIcon IconUtils::fromTheme(const QString &name) { - const QLatin1String symbolic("-symbolic"); + static const QLatin1String symbolic("-symbolic"); if (name.endsWith(symbolic)) return QIcon::fromTheme(name); QIcon icon = QIcon::fromTheme(name + symbolic); if (icon.isNull()) return QIcon::fromTheme(name); return icon; } -QIcon IconUtils::fromResources(const QString &name) { - QLatin1String path(":/images/"); - QLatin1String ext(".png"); - const QString pathAndName = path + name; - QIcon icon = QIcon(pathAndName + ext); - if (!icon.isNull()) { - QLatin1String active("_active"); - QLatin1String selected("_selected"); - QLatin1String disabled("_disabled"); - QLatin1String checked("_checked"); - QLatin1String twoX("@2x"); - - icon.addPixmap(QPixmap(pathAndName + active + ext), QIcon::Active); - icon.addPixmap(QPixmap(pathAndName + selected + ext), QIcon::Selected); - icon.addPixmap(QPixmap(pathAndName + disabled + ext), QIcon::Disabled); - icon.addPixmap(QPixmap(pathAndName + checked + ext), QIcon::Normal, QIcon::On); - - const QString twoXAndExt = twoX + ext; - icon.addPixmap(QPixmap(pathAndName + active + twoXAndExt), QIcon::Active); - icon.addPixmap(QPixmap(pathAndName + selected + twoXAndExt), QIcon::Selected); - icon.addPixmap(QPixmap(pathAndName + disabled + twoXAndExt), QIcon::Disabled); - icon.addPixmap(QPixmap(pathAndName + checked + twoXAndExt), QIcon::Normal, QIcon::On); +QIcon IconUtils::fromResources(const char *name) { + static const QLatin1String active("_active"); + static const QLatin1String selected("_selected"); + static const QLatin1String disabled("_disabled"); + static const QLatin1String checked("_checked"); + static const QLatin1String ext(".png"); + + QString path(":/icons/"); + + if (MainWindow::instance()->palette().window().color().value() > 128) + path += QLatin1String("light/"); + else + path += QLatin1String("dark/"); + + QIcon icon; + + // WARN keep these sizes updated with what we really use + for (int size : {16, 24, 32, 88}) { + const QString pathAndName = path + QString::number(size) + '/' + name; + QString iconFilename = pathAndName + ext; + if (QFile::exists(iconFilename)) { + addIconFile(icon, iconFilename, size); + addIconFile(icon, pathAndName + active + ext, size, QIcon::Active); + addIconFile(icon, pathAndName + selected + ext, size, QIcon::Selected); + addIconFile(icon, pathAndName + disabled + ext, size, QIcon::Disabled); + addIconFile(icon, pathAndName + checked + ext, size, QIcon::Normal, QIcon::On); + } } return icon; } -QIcon IconUtils::icon(const QString &name) { +QIcon IconUtils::icon(const char *name) { #ifdef APP_LINUX QIcon icon = fromTheme(name); if (icon.isNull()) icon = fromResources(name); @@ -66,29 +83,42 @@ QIcon IconUtils::icon(const QString &name) { #endif } -QIcon IconUtils::icon(const QStringList &names) { +QIcon IconUtils::icon(const QVector &names) { QIcon icon; - for (const QString &name : names) { + for (auto name : names) { icon = IconUtils::icon(name); if (!icon.availableSizes().isEmpty()) break; } return icon; } -QIcon IconUtils::tintedIcon(const QString &name, const QColor &color, QList sizes) { +QPixmap IconUtils::iconPixmap(const char *name, + int size, + const QColor &background, + const qreal pixelRatio) { + QString path(":/icons/"); + if (background.value() > 128) + path += "light/"; + else + path += "dark/"; + path += QString::number(size) + '/' + name + QLatin1String(".png"); + return IconUtils::pixmap(path, pixelRatio); +} + +QIcon IconUtils::tintedIcon(const char *name, const QColor &color, const QVector &sizes) { QIcon i = IconUtils::icon(name); QIcon t; - if (sizes.isEmpty()) sizes = i.availableSizes(); + // if (sizes.isEmpty()) sizes = i.availableSizes(); for (const QSize &size : sizes) { QPixmap pixmap = i.pixmap(size); - QImage tintedImage = tinted(pixmap.toImage(), color); - t.addPixmap(QPixmap::fromImage(tintedImage)); + tint(pixmap, color); + t.addPixmap(pixmap); } return t; } -QIcon IconUtils::tintedIcon(const QString &name, const QColor &color, const QSize &size) { - return IconUtils::tintedIcon(name, color, QList() << size); +QIcon IconUtils::tintedIcon(const char *name, const QColor &color, const QSize &size) { + return IconUtils::tintedIcon(name, color, QVector() << size); } QImage IconUtils::grayscaled(const QImage &image) { @@ -102,8 +132,7 @@ QImage IconUtils::grayscaled(const QImage &image) { return img; } -QImage IconUtils::tinted(const QImage &image, const QColor &color, - QPainter::CompositionMode mode) { +QImage IconUtils::tinted(const QImage &image, const QColor &color, QPainter::CompositionMode mode) { QImage img(image.size(), QImage::Format_ARGB32_Premultiplied); QPainter painter(&img); painter.drawImage(0, 0, grayscaled(image)); @@ -114,26 +143,18 @@ QImage IconUtils::tinted(const QImage &image, const QColor &color, return img; } -void IconUtils::setupAction(QAction *action) { - // never autorepeat. - // unexperienced users tend to keep keys pressed for a "long" time - action->setAutoRepeat(false); - - // show keyboard shortcuts in the status bar - if (!action->shortcut().isEmpty()) - action->setStatusTip(action->statusTip() + - QLatin1String(" (") + - action->shortcut().toString(QKeySequence::NativeText) + - QLatin1String(")")); +void IconUtils::tint(QPixmap &pixmap, const QColor &color, QPainter::CompositionMode mode) { + QPainter painter(&pixmap); + painter.setCompositionMode(mode); + painter.fillRect(pixmap.rect(), color); } -QPixmap IconUtils::pixmap(const QString &name) { +QPixmap IconUtils::pixmap(const QString &filename, const qreal pixelRatio) { // Check if a "@2x" file exists - const qreal pixelRatio = IconUtils::pixelRatio(); if (pixelRatio > 1.0) { - int dotIndex = name.lastIndexOf(QLatin1Char('.')); + int dotIndex = filename.lastIndexOf(QLatin1Char('.')); if (dotIndex != -1) { - QString at2xfileName = name; + QString at2xfileName = filename; at2xfileName.insert(dotIndex, QLatin1String("@2x")); if (QFile::exists(at2xfileName)) { QPixmap pixmap(at2xfileName); @@ -142,13 +163,5 @@ QPixmap IconUtils::pixmap(const QString &name) { } } } - return QPixmap(name); -} - -qreal IconUtils::pixelRatio() { -#if QT_VERSION >= 0x050600 - return MainWindow::instance()->devicePixelRatioF(); -#else - return MainWindow::instance()->devicePixelRatio(); -#endif + return QPixmap(filename); } diff --git a/src/iconutils.h b/src/iconutils.h index 2fbe0b2..52d915c 100644 --- a/src/iconutils.h +++ b/src/iconutils.h @@ -24,26 +24,39 @@ $END_LICENSE */ #include class IconUtils { - public: static QIcon fromTheme(const QString &name); - static QIcon fromResources(const QString &name); - static QIcon icon(const QString &name); - static QIcon icon(const QStringList &names); - static QIcon tintedIcon(const QString &name, const QColor &color, - QList sizes = QList()); - static QIcon tintedIcon(const QString &name, const QColor &color, const QSize &size); - static void setupAction(QAction *action); + static QIcon fromResources(const char *name); + + template static void setIcon(T *obj, const char *name) { + QIcon i = icon(name); + obj->setIcon(i); + obj->connect(qApp, &QGuiApplication::paletteChanged, obj, [obj, name] { + QIcon i = icon(name); + obj->setIcon(i); + }); + } + static QIcon icon(const char *name); + static QIcon icon(const QVector &names); + + static QPixmap + iconPixmap(const char *name, int size, const QColor &background, const qreal pixelRatio); + + static QIcon tintedIcon(const char *name, const QColor &color, const QVector &sizes); + static QIcon tintedIcon(const char *name, const QColor &color, const QSize &size); // HiDPI stuff - static QPixmap pixmap(const QString &name); - static qreal maxSupportedPixelRatio() { return 2.0; } - static qreal pixelRatio(); + static QPixmap pixmap(const QString &filename, const qreal pixelRatio); + + static void tint(QPixmap &pixmap, + const QColor &color, + QPainter::CompositionMode mode = QPainter::CompositionMode_SourceIn); private: - IconUtils() { } + IconUtils() {} static QImage grayscaled(const QImage &image); - static QImage tinted(const QImage &image, const QColor &color, + static QImage tinted(const QImage &image, + const QColor &color, QPainter::CompositionMode mode = QPainter::CompositionMode_Screen); }; diff --git a/src/idle/idle.pri b/src/idle/idle.pri deleted file mode 100644 index a70e586..0000000 --- a/src/idle/idle.pri +++ /dev/null @@ -1,13 +0,0 @@ -INCLUDEPATH += $$PWD/src -DEPENDPATH += $$PWD/src - -HEADERS += $$PWD/src/idle.h - -mac { - SOURCES += $$PWD/src/idle_mac.cpp -} else:win32 { - SOURCES += $$PWD/src/idle_win.cpp -} else { - QT *= dbus - SOURCES += $$PWD/src/idle_linux.cpp -} diff --git a/src/idle/src/idle.h b/src/idle/src/idle.h deleted file mode 100644 index 6a004af..0000000 --- a/src/idle/src/idle.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef IDLE_H -#define IDLE_H - -#include - -class Idle { - -public: - static bool preventDisplaySleep(const QString &reason); - static bool allowDisplaySleep(); - static QString displayErrorMessage(); - - static bool preventSystemSleep(const QString &reason); - static bool allowSystemSleep(); - static QString systemErrorMessage(); - -}; - -#endif // IDLE_H diff --git a/src/idle/src/idle_linux.cpp b/src/idle/src/idle_linux.cpp deleted file mode 100644 index 80c9e8c..0000000 --- a/src/idle/src/idle_linux.cpp +++ /dev/null @@ -1,65 +0,0 @@ -#include "idle.h" - -#include -#include -#include -#include - -namespace { - -const char *fdDisplayService = "org.freedesktop.ScreenSaver"; -const char *fdDisplayPath = "/org/freedesktop/ScreenSaver"; -const char *fdDisplayInterface = fdDisplayService; - -const char *gnomeSystemService = "org.gnome.SessionManager"; -const char *gnomeSystemPath = "/org/gnome/SessionManager"; -const char *gnomeSystemInterface = gnomeSystemService; - -quint32 cookie; -QString errorMessage; - -bool handleReply(const QDBusReply &reply) { - if (reply.isValid()) { - cookie = reply.value(); - qDebug() << "Success!" << cookie; - errorMessage.clear(); - return true; - } - errorMessage = reply.error().message(); - return false; -} - -} - -bool Idle::preventDisplaySleep(const QString &reason) { - QDBusInterface dbus(fdDisplayService, fdDisplayPath, fdDisplayInterface); - QDBusReply reply = dbus.call("Inhibit", QCoreApplication::applicationName(), reason); - return handleReply(reply); -} - -bool Idle::allowDisplaySleep() { - QDBusInterface dbus(fdDisplayService, fdDisplayPath, fdDisplayInterface); - dbus.call("UnInhibit", cookie); - return true; -} - -QString Idle::displayErrorMessage() { - return errorMessage; -} - -bool Idle::preventSystemSleep(const QString &reason) { - QDBusInterface dbus(gnomeSystemService, gnomeSystemPath, gnomeSystemInterface); - QDBusReply reply = dbus.call("Inhibit", QCoreApplication::applicationName(), reason); - return handleReply(reply); -} - -bool Idle::allowSystemSleep() { - QDBusInterface dbus(gnomeSystemService, gnomeSystemPath, gnomeSystemInterface); - dbus.call("UnInhibit", cookie); - return true; -} - -QString Idle::systemErrorMessage() { - return errorMessage; -} - diff --git a/src/idle/src/idle_mac.cpp b/src/idle/src/idle_mac.cpp deleted file mode 100644 index 315cdef..0000000 --- a/src/idle/src/idle_mac.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include "idle.h" - -#include - -namespace { - -IOPMAssertionID displayAssertionID = 0; -IOReturn displayRes = 0; - -IOPMAssertionID systemAssertionID = 0; -IOReturn systemRes = 0; - -} - -bool Idle::preventDisplaySleep(const QString &reason) { - displayRes = IOPMAssertionCreateWithName(kIOPMAssertionTypePreventUserIdleDisplaySleep, - kIOPMAssertionLevelOn, reason.toCFString(), &displayAssertionID); - return displayRes == kIOReturnSuccess; -} - -bool Idle::allowDisplaySleep() { - displayRes = IOPMAssertionRelease(displayAssertionID); - return displayRes == kIOReturnSuccess; -} - -QString Idle::displayErrorMessage() { - return QString(); - // return QString::fromUtf8(IOService::stringFromReturn(displayRes)); -} - -bool Idle::preventSystemSleep(const QString &reason) { - systemRes = IOPMAssertionCreateWithName(kIOPMAssertionTypePreventUserIdleSystemSleep, - kIOPMAssertionLevelOn, reason.toCFString(), &systemAssertionID); - return systemRes == kIOReturnSuccess; -} - -bool Idle::allowSystemSleep() { - systemRes = IOPMAssertionRelease(systemAssertionID); - return systemRes == kIOReturnSuccess; -} - -QString Idle::systemErrorMessage() { - return QString(); - // return QString::fromUtf8(IOService::stringFromReturn(systemRes)); -} diff --git a/src/idle/src/idle_win.cpp b/src/idle/src/idle_win.cpp deleted file mode 100644 index 2f189ef..0000000 --- a/src/idle/src/idle_win.cpp +++ /dev/null @@ -1,35 +0,0 @@ -#include "idle.h" - -#include "windows.h" - -namespace { -EXECUTION_STATE executionState; -} - -bool Idle::preventDisplaySleep(const QString &reason) { - executionState = SetThreadExecutionState(ES_CONTINUOUS | ES_DISPLAY_REQUIRED); - return true; -} - -bool Idle::allowDisplaySleep() { - SetThreadExecutionState(ES_CONTINUOUS | executionState); - return true; -} - -QString Idle::displayErrorMessage() { - return QString(); -} - -bool Idle::preventSystemSleep(const QString &reason) { - executionState = SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED); - return true; -} - -bool Idle::allowSystemSleep() { - SetThreadExecutionState(ES_CONTINUOUS | executionState); - return true; -} - -QString Idle::systemErrorMessage() { - return QString(); -} diff --git a/src/jsfunctions.cpp b/src/jsfunctions.cpp index e63f14e..d0cef41 100644 --- a/src/jsfunctions.cpp +++ b/src/jsfunctions.cpp @@ -19,17 +19,18 @@ along with Minitube. If not, see . $END_LICENSE */ #include "jsfunctions.h" -#include "http.h" #include "constants.h" +#include "http.h" #include -JsFunctions* JsFunctions::instance() { +JsFunctions *JsFunctions::instance() { static JsFunctions *i = new JsFunctions(QLatin1String(Constants::WEBSITE) + "-ws/functions.js"); return i; } -JsFunctions::JsFunctions(const QString &url, QObject *parent) : QObject(parent), url(url), engine(0) { +JsFunctions::JsFunctions(const QString &url, QObject *parent) + : QObject(parent), url(url), engine(nullptr) { QFile file(jsPath()); if (file.exists()) { if (file.open(QIODevice::ReadOnly | QIODevice::Text)) @@ -37,7 +38,8 @@ JsFunctions::JsFunctions(const QString &url, QObject *parent) : QObject(parent), else qWarning() << "Cannot open" << file.errorString() << file.fileName(); QFileInfo info(file); - bool stale = info.size() == 0 || info.lastModified().toTime_t() < QDateTime::currentDateTime().toTime_t() - 1800; + bool stale = info.size() == 0 || info.lastModified().toTime_t() < + QDateTime::currentDateTime().toTime_t() - 1800; if (stale) loadJs(); } else { QFile resFile(QLatin1String(":/") + jsFilename()); @@ -73,7 +75,7 @@ void JsFunctions::loadJs() { QUrlQuery q; q.addQueryItem("v", Constants::VERSION); url.setQuery(q); - QObject* reply = Http::instance().get(url); + QObject *reply = Http::instance().get(url); connect(reply, SIGNAL(data(QByteArray)), SLOT(gotJs(QByteArray))); connect(reply, SIGNAL(error(QString)), SLOT(errorJs(QString))); } @@ -101,10 +103,8 @@ void JsFunctions::errorJs(const QString &message) { QJSValue JsFunctions::evaluate(const QString &js) { if (!engine) return QString(); QJSValue value = engine->evaluate(js); - if (value.isUndefined()) - qWarning() << "Undefined result for" << js; - if (value.isError()) - qWarning() << "Error in" << js << value.toString(); + if (value.isUndefined()) qWarning() << "Undefined result for" << js; + if (value.isError()) qWarning() << "Error in" << js << value.toString(); return value; } @@ -163,6 +163,10 @@ QString JsFunctions::signatureFunctionNameRE() { return string("signatureFunctionNameRE()"); } +QStringList JsFunctions::signatureFunctionNameREs() { + return stringArray("signatureFunctionNameREs()"); +} + QStringList JsFunctions::apiKeys() { return stringArray("apiKeys()"); } diff --git a/src/jsfunctions.h b/src/jsfunctions.h index 3cb0d45..4bc369a 100644 --- a/src/jsfunctions.h +++ b/src/jsfunctions.h @@ -21,18 +21,17 @@ $END_LICENSE */ #ifndef JSFUNCTIONS_H #define JSFUNCTIONS_H -#include -#include #include #include +#include +#include class JsFunctions : public QObject { - Q_OBJECT public: - static JsFunctions* instance(); - JsFunctions(const QString &url, QObject *parent = 0); + static JsFunctions *instance(); + JsFunctions(const QString &url, QObject *parent = nullptr); QJSValue evaluate(const QString &js); QString string(const QString &js); QStringList stringArray(const QString &js); @@ -48,6 +47,7 @@ public: QString ageGateRE(); QString jsPlayerRE(); QString signatureFunctionNameRE(); + QStringList signatureFunctionNameREs(); QStringList apiKeys(); signals: diff --git a/src/loadingwidget.cpp b/src/loadingwidget.cpp index 13e7cb6..66ec79d 100644 --- a/src/loadingwidget.cpp +++ b/src/loadingwidget.cpp @@ -75,7 +75,7 @@ void LoadingWidget::setVideo(Video *video) { titleLabel->setText(title); titleLabel->setVisible(window()->height() > 100); - const int maxDescLength = 400; + const int maxDescLength = 500; QString videoDesc = video->getDescription(); if (videoDesc.length() > maxDescLength) { @@ -106,8 +106,9 @@ void LoadingWidget::setError(const QString &message) { progressBar->setValue(0); } -void LoadingWidget::bufferStatus(int percent) { - if (startTime.elapsed() > 2000 && percent > progressBar->value()) +void LoadingWidget::bufferStatus(qreal value) { + int percent = value * 100.; + if (startTime.elapsed() > 1000 && percent > progressBar->value()) progressBar->setValue(percent); } @@ -122,8 +123,9 @@ void LoadingWidget::adjustFontSize() { layout()->setMargin(spacing); titleLabel->setFont(f); - f.setPixelSize(f.pixelSize() / 2); - descriptionLabel->setFont(f); + QFont descFont = descriptionLabel->font(); + descFont.setPixelSize(f.pixelSize() / 2); + descriptionLabel->setFont(descFont); } void LoadingWidget::clear() { diff --git a/src/loadingwidget.h b/src/loadingwidget.h index ca19c06..ad4c60a 100644 --- a/src/loadingwidget.h +++ b/src/loadingwidget.h @@ -26,7 +26,6 @@ $END_LICENSE */ #include "video.h" class LoadingWidget : public QWidget { - Q_OBJECT public: @@ -39,7 +38,7 @@ protected: void resizeEvent(QResizeEvent *e); public slots: - void bufferStatus(int); + void bufferStatus(qreal value); private: void adjustFontSize(); @@ -48,7 +47,6 @@ private: QLabel *descriptionLabel; QProgressBar *progressBar; QTime startTime; - }; #endif // LOADINGWIDGET_H diff --git a/src/main.cpp b/src/main.cpp index 3a0873f..717179e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -18,14 +18,14 @@ along with Minitube. If not, see . $END_LICENSE */ -#include #include +#include -#include #include "constants.h" +#include "iconutils.h" #include "mainwindow.h" #include "searchparams.h" -#include "iconutils.h" +#include #ifdef APP_EXTRA #include "extra.h" #endif @@ -33,19 +33,20 @@ $END_LICENSE */ #include "mac_startup.h" #endif -void showWindow(QtSingleApplication &app, const QString &dataDir) { +void showWindow(QtSingleApplication &app, const QString &pkgDataDir) { MainWindow *mainWin = new MainWindow(); #ifndef APP_MAC QIcon appIcon; - if (QDir(dataDir).exists()) { + if (!pkgDataDir.isEmpty()) { appIcon = IconUtils::icon(Constants::UNIX_NAME); } else { QString dataDir = qApp->applicationDirPath() + "/data"; - const int iconSizes [] = { 16, 22, 32, 48, 64, 128, 256, 512 }; + const int iconSizes[] = {16, 22, 32, 48, 64, 128, 256, 512}; for (int i = 0; i < 8; i++) { QString size = QString::number(iconSizes[i]); - QString png = dataDir + "/" + size + "x" + size + "/" + Constants::UNIX_NAME + ".png"; + QString png = dataDir + '/' + size + 'x' + size + '/' + Constants::UNIX_NAME + + QLatin1String(".png"); appIcon.addFile(png, QSize(iconSizes[i], iconSizes[i])); } } @@ -53,8 +54,8 @@ void showWindow(QtSingleApplication &app, const QString &dataDir) { mainWin->setWindowIcon(appIcon); #endif - mainWin->connect(&app, SIGNAL(messageReceived(const QString &)), - mainWin, SLOT(messageReceived(const QString &))); + mainWin->connect(&app, SIGNAL(messageReceived(const QString &)), mainWin, + SLOT(messageReceived(const QString &))); app.setActivationWindow(mainWin, true); mainWin->show(); @@ -68,18 +69,30 @@ int main(int argc, char **argv) { // Seed random number generator qsrand(QDateTime::currentDateTime().toTime_t()); +#ifdef MEDIA_MPV + QSurfaceFormat format = QSurfaceFormat::defaultFormat(); +#ifdef APP_MAC + format.setMajorVersion(4); + format.setMinorVersion(1); +#endif + format.setProfile(QSurfaceFormat::CoreProfile); + QSurfaceFormat::setDefaultFormat(format); +#endif + #ifdef Q_OS_MAC mac::MacMain(); #endif QtSingleApplication app(argc, argv); - QString message = app.arguments().size() > 1 ? app.arguments().at(1) : QString(); - if (message == QLatin1String("--help")) { - MainWindow::printHelp(); - return 0; + QString message; + if (app.arguments().size() > 1) { + message = app.arguments().at(1); + if (message == QLatin1String("--help")) { + MainWindow::printHelp(); + return 0; + } } - if (app.sendMessage(message)) - return 0; + if (app.sendMessage(message)) return 0; app.setApplicationName(Constants::NAME); app.setOrganizationName(Constants::ORG_NAME); @@ -96,6 +109,7 @@ int main(int argc, char **argv) { cssFile.open(QFile::ReadOnly); QString styleSheet = QLatin1String(cssFile.readAll()); app.setStyleSheet(styleSheet); + cssFile.close(); #endif // qt translations @@ -104,28 +118,28 @@ int main(int argc, char **argv) { QLibraryInfo::location(QLibraryInfo::TranslationsPath)); app.installTranslator(&qtTranslator); - // app translations + QString pkgDataDir; #ifdef PKGDATADIR - QString dataDir = QLatin1String(PKGDATADIR); -#else - QString dataDir = ""; + pkgDataDir = QLatin1String(PKGDATADIR); #endif + + // app translations #ifdef APP_MAC QString localeDir = qApp->applicationDirPath() + QLatin1String("/../Resources/locale"); #else QString localeDir = qApp->applicationDirPath() + QLatin1String("/locale"); #endif if (!QDir(localeDir).exists()) { - localeDir = dataDir + QLatin1String("/locale"); + localeDir = pkgDataDir + QLatin1String("/locale"); } - // qDebug() << "Using locale dir" << localeDir << locale; + qDebug() << "Using locale dir" << localeDir << QLocale::system(); QTranslator translator; translator.load(QLocale::system(), QString(), QString(), localeDir); app.installTranslator(&translator); QNetworkProxyFactory::setUseSystemConfiguration(true); - showWindow(app, dataDir); + showWindow(app, pkgDataDir); return app.exec(); } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 3fcf2fe..4a3a480 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -60,6 +60,7 @@ $END_LICENSE */ #endif #include #ifdef APP_EXTRA +#include "compositefader.h" #include "extra.h" #include "updatedialog.h" #endif @@ -74,12 +75,19 @@ $END_LICENSE */ #include "seekslider.h" #include "sidebarwidget.h" #include "toolbarmenu.h" -#include "videoareawidget.h" +#include "videoarea.h" #include "yt3.h" #include "ytregions.h" +#ifdef MEDIA_QTAV +#include "mediaqtav.h" +#endif +#ifdef MEDIA_MPV +#include "mediampv.h" +#endif + namespace { -static MainWindow *mainWindowInstance; +MainWindow *mainWindowInstance; } MainWindow *MainWindow::instance() { @@ -87,26 +95,23 @@ MainWindow *MainWindow::instance() { } MainWindow::MainWindow() - : updateChecker(0), aboutView(0), downloadView(0), regionsView(0), mainToolBar(0), -#ifdef APP_PHONON - mediaObject(0), audioOutput(0), -#endif - fullScreenActive(false), compactModeActive(false), initialized(false), toolbarMenu(0) { - + : aboutView(nullptr), downloadView(nullptr), regionsView(nullptr), mainToolBar(nullptr), + fullScreenActive(false), compactModeActive(false), initialized(false), toolbarMenu(nullptr), + media(nullptr) { mainWindowInstance = this; // views mechanism views = new QStackedWidget(); - views->hide(); setCentralWidget(views); #ifdef APP_EXTRA Extra::windowSetup(this); #endif - messageLabel = new QLabel(); - messageLabel->setWindowFlags(Qt::ToolTip | Qt::FramelessWindowHint); - messageLabel->setStyleSheet("padding:5px;border:1px solid #808080;background:palette(window)"); + messageLabel = new QLabel(this); + messageLabel->setWordWrap(false); + messageLabel->setStyleSheet("padding:5px;border:0;background:palette(window)"); + messageLabel->setAlignment(Qt::AlignCenter); messageLabel->hide(); adjustMessageLabelPosition(); messageTimer = new QTimer(this); @@ -126,7 +131,7 @@ MainWindow::MainWindow() // build ui createActions(); createMenus(); - createToolBars(); + createToolBar(); hideToolbar(); createStatusBar(); @@ -146,25 +151,18 @@ MainWindow::MainWindow() views->widget(i)->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); setMinimumWidth(0); - views->show(); - #ifdef APP_ACTIVATION Activation::instance().initialCheck(); #else showHome(); #endif - QTimer::singleShot(100, this, &MainWindow::lazyInit); + QTimer::singleShot(1000, this, &MainWindow::lazyInit); } void MainWindow::lazyInit() { -#ifdef APP_PHONON - initPhonon(); -#endif mediaView->initialize(); -#ifdef APP_PHONON - mediaView->setMediaObject(mediaObject); -#endif + initMedia(); qApp->processEvents(); // CLI @@ -229,6 +227,12 @@ void MainWindow::changeEvent(QEvent *e) { getAction("minimize")->setEnabled(!isMinimized()); } #endif + if (messageLabel->isVisible()) { + if (e->type() == QEvent::ActivationChange || e->type() == QEvent::WindowStateChange || + e->type() == QEvent::WindowDeactivate || e->type() == QEvent::ApplicationStateChange) { + hideMessage(); + } + } QMainWindow::changeEvent(e); } @@ -303,27 +307,35 @@ bool MainWindow::eventFilter(QObject *obj, QEvent *e) { toolbarMenu->move(mapToGlobal(p)); } + if (obj == this && t == QEvent::StyleChange) { + qDebug() << "Style change detected"; + qApp->paletteChanged(qApp->palette()); + return false; + } + // standard event processing return QMainWindow::eventFilter(obj, e); } void MainWindow::createActions() { - stopAct = new QAction(IconUtils::icon("media-playback-stop"), tr("&Stop"), this); + stopAct = new QAction(tr("&Stop"), this); + IconUtils::setIcon(stopAct, "media-playback-stop"); stopAct->setStatusTip(tr("Stop playback and go back to the search view")); - stopAct->setShortcuts(QList() << QKeySequence(Qt::Key_Escape) - << QKeySequence(Qt::Key_MediaStop)); + stopAct->setShortcuts(QList() + << QKeySequence(Qt::Key_Escape) << QKeySequence(Qt::Key_MediaStop)); stopAct->setEnabled(false); actionMap.insert("stop", stopAct); connect(stopAct, SIGNAL(triggered()), SLOT(stop())); - skipBackwardAct = new QAction(IconUtils::icon("media-skip-backward"), tr("P&revious"), this); + skipBackwardAct = new QAction(tr("P&revious"), this); skipBackwardAct->setStatusTip(tr("Go back to the previous track")); skipBackwardAct->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_Left)); skipBackwardAct->setEnabled(false); actionMap.insert("previous", skipBackwardAct); connect(skipBackwardAct, SIGNAL(triggered()), mediaView, SLOT(skipBackward())); - skipAct = new QAction(IconUtils::icon("media-skip-forward"), tr("S&kip"), this); + skipAct = new QAction(tr("S&kip"), this); + IconUtils::setIcon(skipAct, "media-skip-forward"); skipAct->setStatusTip(tr("Skip to the next video")); skipAct->setShortcuts(QList() << QKeySequence(Qt::CTRL + Qt::Key_Right) << QKeySequence(Qt::Key_MediaNext)); @@ -331,15 +343,17 @@ void MainWindow::createActions() { actionMap.insert("skip", skipAct); connect(skipAct, SIGNAL(triggered()), mediaView, SLOT(skip())); - pauseAct = new QAction(IconUtils::icon("media-playback-start"), tr("&Play"), this); + pauseAct = new QAction(tr("&Play"), this); + IconUtils::setIcon(pauseAct, "media-playback-start"); pauseAct->setStatusTip(tr("Resume playback")); - pauseAct->setShortcuts(QList() << QKeySequence(Qt::Key_Space) - << QKeySequence(Qt::Key_MediaPlay)); + pauseAct->setShortcuts(QList() + << QKeySequence(Qt::Key_Space) << QKeySequence(Qt::Key_MediaPlay)); pauseAct->setEnabled(false); actionMap.insert("pause", pauseAct); connect(pauseAct, SIGNAL(triggered()), mediaView, SLOT(pause())); - fullscreenAct = new QAction(IconUtils::icon("view-fullscreen"), tr("&Full Screen"), this); + fullscreenAct = new QAction(tr("&Full Screen"), this); + IconUtils::setIcon(fullscreenAct, "view-fullscreen"); fullscreenAct->setStatusTip(tr("Go full screen")); QList fsShortcuts; #ifdef APP_MAC @@ -355,11 +369,7 @@ void MainWindow::createActions() { compactViewAct = new QAction(tr("&Compact Mode"), this); compactViewAct->setStatusTip(tr("Hide the playlist and the toolbar")); -#ifdef APP_MAC - compactViewAct->setShortcut(QKeySequence(Qt::CTRL + Qt::META + Qt::Key_C)); -#else compactViewAct->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_C)); -#endif compactViewAct->setCheckable(true); compactViewAct->setChecked(false); compactViewAct->setEnabled(false); @@ -373,7 +383,8 @@ void MainWindow::createActions() { actionMap.insert("webpage", webPageAct); connect(webPageAct, SIGNAL(triggered()), mediaView, SLOT(openWebPage())); - copyPageAct = new QAction(IconUtils::icon("link"), tr("Copy the YouTube &Link"), this); + copyPageAct = new QAction(tr("Copy the YouTube &Link"), this); + IconUtils::setIcon(copyPageAct, "link"); copyPageAct->setStatusTip(tr("Copy the current video YouTube link to the clipboard")); copyPageAct->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_L)); copyPageAct->setEnabled(false); @@ -396,8 +407,8 @@ void MainWindow::createActions() { removeAct = new QAction(tr("&Remove"), this); removeAct->setStatusTip(tr("Remove the selected videos from the playlist")); - removeAct->setShortcuts(QList() << QKeySequence("Del") - << QKeySequence("Backspace")); + removeAct->setShortcuts(QList() + << QKeySequence("Del") << QKeySequence("Backspace")); removeAct->setEnabled(false); actionMap.insert("remove", removeAct); connect(removeAct, SIGNAL(triggered()), mediaView, SLOT(removeSelected())); @@ -475,24 +486,47 @@ void MainWindow::createActions() { addAction(volumeDownAct); volumeMuteAct = new QAction(this); - volumeMuteAct->setIcon(IconUtils::icon("audio-volume-high")); + IconUtils::setIcon(volumeMuteAct, "audio-volume-high"); volumeMuteAct->setStatusTip(tr("Mute volume")); - volumeMuteAct->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_K)); + volumeMuteAct->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_M)); actionMap.insert("volumeMute", volumeMuteAct); - connect(volumeMuteAct, SIGNAL(triggered()), SLOT(volumeMute())); + connect(volumeMuteAct, SIGNAL(triggered()), SLOT(toggleVolumeMute())); addAction(volumeMuteAct); - QAction *definitionAct = new QAction(this); - definitionAct->setIcon(IconUtils::icon("video-display")); + QToolButton *definitionButton = new QToolButton(this); + definitionButton->setText(YT3::instance().maxVideoDefinition().getName()); + IconUtils::setIcon(definitionButton, "video-display"); + definitionButton->setIconSize(QSize(16, 16)); + definitionButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + definitionButton->setPopupMode(QToolButton::InstantPopup); + QMenu *definitionMenu = new QMenu(this); + QActionGroup *group = new QActionGroup(this); + for (auto &defName : VideoDefinition::getDefinitionNames()) { + QAction *a = new QAction(defName); + a->setCheckable(true); + a->setActionGroup(group); + a->setChecked(defName == YT3::instance().maxVideoDefinition().getName()); + connect(a, &QAction::triggered, this, [this, defName, definitionButton] { + setDefinitionMode(defName); + definitionButton->setText(defName); + }); + connect(&YT3::instance(), &YT3::maxVideoDefinitionChanged, this, + [defName, definitionButton](const QString &name) { + if (defName == name) definitionButton->setChecked(true); + }); + definitionMenu->addAction(a); + } + definitionButton->setMenu(definitionMenu); + QWidgetAction *definitionAct = new QWidgetAction(this); + definitionAct->setDefaultWidget(definitionButton); definitionAct->setShortcuts(QList() << QKeySequence(Qt::CTRL + Qt::Key_D)); actionMap.insert("definition", definitionAct); - connect(definitionAct, SIGNAL(triggered()), SLOT(toggleDefinitionMode())); addAction(definitionAct); QAction *action; - action = new QAction(IconUtils::icon("media-playback-start"), tr("&Manually Start Playing"), - this); + action = new QAction(tr("&Manually Start Playing"), this); + IconUtils::setIcon(action, "media-playback-start"); action->setStatusTip(tr("Manually start playing videos")); action->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_T)); action->setCheckable(true); @@ -500,17 +534,17 @@ void MainWindow::createActions() { actionMap.insert("manualplay", action); action = new QAction(tr("&Downloads"), this); + IconUtils::setIcon(action, "document-save"); action->setStatusTip(tr("Show details about video downloads")); action->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_J)); action->setCheckable(true); - action->setIcon(IconUtils::icon("document-save")); connect(action, SIGNAL(toggled(bool)), SLOT(toggleDownloads(bool))); actionMap.insert("downloads", action); action = new QAction(tr("&Download"), this); + IconUtils::setIcon(action, "document-save"); action->setStatusTip(tr("Download the current video")); action->setShortcut(QKeySequence::Save); - action->setIcon(IconUtils::icon("document-save")); action->setEnabled(false); action->setVisible(false); action->setPriority(QAction::LowPriority); @@ -531,23 +565,26 @@ void MainWindow::createActions() { action->setEnabled(false); connect(action, SIGNAL(triggered()), mediaView, SLOT(toggleSubscription())); actionMap.insert("subscribeChannel", action); - mediaView->updateSubscriptionAction(0, false); + mediaView->updateSubscriptionActionForVideo(0, false); QString shareTip = tr("Share the current video using %1"); - action = new QAction(IconUtils::icon("twitter"), "&Twitter", this); + action = new QAction("&Twitter", this); + IconUtils::setIcon(action, "twitter"); action->setStatusTip(shareTip.arg("Twitter")); action->setEnabled(false); actionMap.insert("twitter", action); connect(action, SIGNAL(triggered()), mediaView, SLOT(shareViaTwitter())); - action = new QAction(IconUtils::icon("facebook"), "&Facebook", this); + action = new QAction("&Facebook", this); + IconUtils::setIcon(action, "facebook"); action->setStatusTip(shareTip.arg("Facebook")); action->setEnabled(false); actionMap.insert("facebook", action); connect(action, SIGNAL(triggered()), mediaView, SLOT(shareViaFacebook())); - action = new QAction(IconUtils::icon("email"), tr("&Email"), this); + action = new QAction(tr("&Email"), this); + IconUtils::setIcon(action, "email"); action->setStatusTip(shareTip.arg(tr("Email"))); action->setEnabled(false); actionMap.insert("email", action); @@ -563,13 +600,14 @@ void MainWindow::createActions() { actionMap.insert("restore", action); connect(action, SIGNAL(triggered()), SLOT(restore())); - action = new QAction(IconUtils::icon("go-top"), tr("&Float on Top"), this); + action = new QAction(tr("&Float on Top"), this); + IconUtils::setIcon(action, "go-top"); action->setCheckable(true); actionMap.insert("ontop", action); connect(action, SIGNAL(toggled(bool)), SLOT(floatOnTop(bool))); - action = - new QAction(IconUtils::icon("media-playback-stop"), tr("&Stop After This Video"), this); + action = new QAction(tr("&Stop After This Video"), this); + IconUtils::setIcon(action, "media-playback-stop"); action->setShortcut(QKeySequence(Qt::SHIFT + Qt::Key_Escape)); action->setCheckable(true); action->setEnabled(false); @@ -595,7 +633,8 @@ void MainWindow::createActions() { action = new QAction(tr("More..."), this); actionMap.insert("moreRegion", action); - action = new QAction(IconUtils::icon("view-list"), tr("&Related Videos"), this); + action = new QAction(tr("&Related Videos"), this); + IconUtils::setIcon(action, "view-list"); action->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_R)); action->setStatusTip(tr("Watch videos related to the current one")); action->setEnabled(false); @@ -609,7 +648,8 @@ void MainWindow::createActions() { actionMap.insert("openInBrowser", action); connect(action, SIGNAL(triggered()), mediaView, SLOT(openInBrowser())); - action = new QAction(IconUtils::icon("safesearch"), tr("Restricted Mode"), this); + action = new QAction(tr("Restricted Mode"), this); + IconUtils::setIcon(action, "safesearch"); action->setStatusTip(tr("Hide videos that may contain inappropriate content")); action->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_K)); action->setCheckable(true); @@ -619,7 +659,8 @@ void MainWindow::createActions() { connect(action, SIGNAL(triggered()), SLOT(toggleMenuVisibilityWithMessage())); actionMap.insert("toggleMenu", action); - action = new QAction(IconUtils::icon("view-more"), tr("Menu"), this); + action = new QAction(tr("Menu"), this); + IconUtils::setIcon(action, "open-menu"); connect(action, SIGNAL(triggered()), SLOT(toggleToolbarMenu())); actionMap.insert("toolbarMenu", action); @@ -634,11 +675,11 @@ void MainWindow::createActions() { #endif // common action properties - for (QAction *action : actionMap) { + for (QAction *action : qAsConst(actionMap)) { // add actions to the MainWindow so that they work // when the menu is hidden addAction(action); - IconUtils::setupAction(action); + MainWindow::instance()->setupAction(action); } } @@ -709,8 +750,10 @@ void MainWindow::createMenus() { menuMap.insert("view", viewMenu); viewMenu->addAction(getAction("ontop")); viewMenu->addAction(compactViewAct); -#ifndef APP_MAC + viewMenu->addSeparator(); viewMenu->addAction(fullscreenAct); +#ifndef APP_MAC + viewMenu->addSeparator(); viewMenu->addAction(getAction("toggleMenu")); #endif @@ -733,46 +776,16 @@ void MainWindow::createMenus() { #endif } -void MainWindow::createToolBars() { +void MainWindow::createToolBar() { // Create widgets + currentTimeLabel = new QLabel("00:00", this); - currentTimeLabel = new QLabel("00:00"); - currentTimeLabel->setFont(FontUtils::small()); - -#ifdef APP_PHONON_SEEK - seekSlider = new Phonon::SeekSlider(); -#if APP_LINUX + seekSlider = new SeekSlider(this); + seekSlider->setEnabled(false); seekSlider->setTracking(false); -#else - seekSlider->setTracking(true); -#endif - // Phonon freezes the application with streaming videos if - // tracking is set to true and the seek slider is dragged. - seekSlider->setIconVisible(false); - seekSlider->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); -#else - slider = new SeekSlider(this); - slider->setEnabled(false); - slider->setTracking(false); - slider->setMaximum(1000); - slider->setOrientation(Qt::Horizontal); - slider->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); -#endif - -#ifdef APP_PHONON - volumeSlider = new Phonon::VolumeSlider(); - volumeSlider->setMuteVisible(false); - // qDebug() << volumeSlider->children(); - // status tip for the volume slider - QSlider *volumeQSlider = volumeSlider->findChild(); - if (volumeQSlider) - volumeQSlider->setStatusTip( - tr("Press %1 to raise the volume, %2 to lower it") - .arg(volumeUpAct->shortcut().toString(QKeySequence::NativeText), - volumeDownAct->shortcut().toString(QKeySequence::NativeText))); - // this makes the volume slider smaller - volumeSlider->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); -#endif + seekSlider->setMaximum(1000); + volumeSlider = new SeekSlider(this); + volumeSlider->setValue(volumeSlider->maximum()); #if defined(APP_MAC_SEARCHFIELD) && !defined(APP_MAC_QMACTOOLBAR) SearchWrapper *searchWrapper = new SearchWrapper(this); @@ -781,13 +794,14 @@ void MainWindow::createToolBars() { toolbarSearch = new SearchLineEdit(this); #endif toolbarSearch->setMinimumWidth(toolbarSearch->fontInfo().pixelSize() * 15); + toolbarSearch->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); toolbarSearch->setSuggester(new YTSuggester(this)); connect(toolbarSearch, SIGNAL(search(const QString &)), SLOT(search(const QString &))); connect(toolbarSearch, SIGNAL(suggestionAccepted(Suggestion *)), SLOT(suggestionAccepted(Suggestion *))); toolbarSearch->setStatusTip(searchFocusAct->statusTip()); -// Add widgets to toolbar + // Add widgets to toolbar #ifdef APP_MAC_QMACTOOLBAR currentTimeLabel->hide(); @@ -826,32 +840,45 @@ void MainWindow::createToolBars() { mainToolBar->addWidget(new Spacer()); + currentTimeLabel->setFont(FontUtils::small()); + currentTimeLabel->setMinimumWidth(currentTimeLabel->fontInfo().pixelSize() * 4); + currentTimeLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); mainToolBar->addWidget(currentTimeLabel); - mainToolBar->addWidget(new Spacer(this, currentTimeLabel->sizeHint().height() / 2)); -#ifdef APP_PHONON_SEEK - mainToolBar->addWidget(seekSlider); -#else - mainToolBar->addWidget(slider); + +#ifdef APP_WIN + mainToolBar->addWidget(new Spacer(nullptr, 10)); #endif - /* - mainToolBar->addWidget(new Spacer()); - totalTime = new QLabel(mainToolBar); - totalTime->setFont(smallerFont); - mainToolBar->addWidget(totalTime); - */ + seekSlider->setOrientation(Qt::Horizontal); + seekSlider->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); + seekSlider->setFocusPolicy(Qt::NoFocus); + mainToolBar->addWidget(seekSlider); mainToolBar->addWidget(new Spacer()); + mainToolBar->addAction(volumeMuteAct); -#ifdef APP_LINUX +#ifndef APP_MAC_QMACTOOLBAR QToolButton *volumeMuteButton = qobject_cast(mainToolBar->widgetForAction(volumeMuteAct)); - volumeMuteButton->setIcon(volumeMuteButton->icon().pixmap(16)); + volumeMuteButton->setIconSize(QSize(16, 16)); + auto fixVolumeMuteIconSize = [volumeMuteButton] { + volumeMuteButton->setIcon(volumeMuteButton->icon().pixmap(16)); + }; + fixVolumeMuteIconSize(); + volumeMuteButton->connect(volumeMuteAct, &QAction::changed, volumeMuteButton, + fixVolumeMuteIconSize); #endif -#ifdef APP_PHONON + volumeSlider->setStatusTip( + tr("Press %1 to raise the volume, %2 to lower it") + .arg(volumeUpAct->shortcut().toString(QKeySequence::NativeText), + volumeDownAct->shortcut().toString(QKeySequence::NativeText))); + + volumeSlider->setOrientation(Qt::Horizontal); + // this makes the volume slider smaller + volumeSlider->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + volumeSlider->setFocusPolicy(Qt::NoFocus); mainToolBar->addWidget(volumeSlider); -#endif mainToolBar->addWidget(new Spacer()); @@ -888,9 +915,8 @@ void MainWindow::createStatusBar() { regionMenu->addAction(moreRegionsAction); connect(moreRegionsAction, SIGNAL(triggered()), SLOT(showRegionsView())); regionAction->setMenu(regionMenu); - } else { - connect(regionAction, SIGNAL(triggered()), SLOT(showRegionsView())); } + connect(regionAction, SIGNAL(triggered()), SLOT(showRegionsView())); /* Stupid code that generates the QRC items foreach(YTRegion r, YTRegions::list()) @@ -903,31 +929,39 @@ void MainWindow::createStatusBar() { void MainWindow::showStopAfterThisInStatusBar(bool show) { QAction *action = getAction("stopafterthis"); - showActionInStatusBar(action, show); + showActionsInStatusBar({action}, show); } -void MainWindow::showActionInStatusBar(QAction *action, bool show) { +void MainWindow::showActionsInStatusBar(const QVector &actions, bool show) { #ifdef APP_EXTRA Extra::fadeInWidget(statusBar(), statusBar()); #endif - if (show) { - if (statusToolBar->actions().contains(action)) return; - if (statusToolBar->actions().isEmpty()) { - statusToolBar->addAction(action); + for (auto action : actions) { + if (show) { + if (statusToolBar->actions().contains(action)) continue; + if (statusToolBar->actions().isEmpty()) { + statusToolBar->addAction(action); + } else { + statusToolBar->insertAction(statusToolBar->actions().at(0), action); + } } else { - statusToolBar->insertAction(statusToolBar->actions().at(0), action); + statusToolBar->removeAction(action); } + } + + if (show) { if (statusBar()->isHidden() && !fullScreenActive) setStatusBarVisibility(true); } else { - statusToolBar->removeAction(action); if (statusBar()->isVisible() && !needStatusBar()) setStatusBarVisibility(false); } } void MainWindow::setStatusBarVisibility(bool show) { - statusBar()->setVisible(show); - if (views->currentWidget() == mediaView) - QTimer::singleShot(0, mediaView, SLOT(adjustWindowSize())); + if (statusBar()->isVisible() != show) { + statusBar()->setVisible(show); + if (views->currentWidget() == mediaView) + QTimer::singleShot(0, mediaView, SLOT(adjustWindowSize())); + } } void MainWindow::adjustStatusBarVisibility() { @@ -952,17 +986,18 @@ void MainWindow::showToolbar() { void MainWindow::readSettings() { QSettings settings; - if (settings.contains("geometry")) { - restoreGeometry(settings.value("geometry").toByteArray()); + QByteArray geometrySettings = settings.value("geometry").toByteArray(); + if (!geometrySettings.isEmpty()) { + restoreGeometry(geometrySettings); } else { - const QRect desktopSize = qApp->desktop()->availableGeometry(); + const QRect desktopSize = QGuiApplication::primaryScreen()->availableGeometry(); int w = desktopSize.width() * .9; int h = qMin(w / 2, desktopSize.height()); setGeometry( QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, QSize(w, h), desktopSize)); } - const VideoDefinition &firstDefinition = VideoDefinition::getDefinitions().at(0); - setDefinitionMode(settings.value("definition", firstDefinition.getName()).toString()); + setDefinitionMode(settings.value("definition", YT3::instance().maxVideoDefinition().getName()) + .toString()); getAction("manualplay")->setChecked(settings.value("manualplay", false).toBool()); getAction("safeSearch")->setChecked(settings.value("safeSearch", false).toBool()); #ifndef APP_MAC @@ -975,7 +1010,7 @@ void MainWindow::writeSettings() { if (!isReallyFullScreen()) { settings.setValue("geometry", saveGeometry()); - mediaView->saveSplitterState(); + if (mediaView) mediaView->saveSplitterState(); } settings.setValue("manualplay", getAction("manualplay")->isChecked()); @@ -988,15 +1023,19 @@ void MainWindow::writeSettings() { void MainWindow::goBack() { if (history.size() > 1) { history.pop(); - QWidget *widget = history.pop(); - showWidget(widget); + showView(history.pop()); } } -void MainWindow::showWidget(QWidget *widget, bool transition) { - Q_UNUSED(transition); +void MainWindow::showView(View *view, bool transition) { + if (!history.isEmpty() && view == history.top()) { + qDebug() << "Attempting to show same view" << view; + return; + } - setUpdatesEnabled(false); +#ifdef APP_MAC + if (transition && !history.isEmpty()) CompositeFader::go(this, this->grab()); +#endif if (compactViewAct->isChecked()) compactViewAct->toggle(); @@ -1004,70 +1043,38 @@ void MainWindow::showWidget(QWidget *widget, bool transition) { View *oldView = qobject_cast(views->currentWidget()); if (oldView) { oldView->disappear(); - views->currentWidget()->setEnabled(false); + oldView->setEnabled(false); + oldView->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); } else - qDebug() << "Cannot cast view"; + qDebug() << "Cannot cast old view"; - const bool isMediaView = widget == mediaView; + view->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + view->setEnabled(true); + views->setCurrentWidget(view); + view->appear(); + QString title = view->getTitle(); + if (title.isEmpty()) + title = Constants::NAME; + else + title += QLatin1String(" - ") + Constants::NAME; + setWindowTitle(title); + + const bool isMediaView = view == mediaView; stopAct->setEnabled(isMediaView); compactViewAct->setEnabled(isMediaView); - toolbarSearch->setEnabled(widget == homeView || isMediaView || widget == downloadView); - - aboutAct->setEnabled(widget != aboutView); - getAction("downloads")->setChecked(widget == downloadView); - - QWidget *oldWidget = views->currentWidget(); - if (oldWidget) oldWidget->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); - - views->setCurrentWidget(widget); - widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - - // call show method on the new view - View *newView = qobject_cast(widget); - if (newView) { - widget->setEnabled(true); - - QString title = newView->getTitle(); - if (title.isEmpty()) - title = Constants::NAME; - else - title += QLatin1String(" - ") + Constants::NAME; - setWindowTitle(title); - - statusToolBar->setUpdatesEnabled(false); - - // dynamic view actions - /* Not currently used by any view - foreach (QAction* action, viewActions) - showActionInStatusBar(action, false); - viewActions = newView->getViewActions(); - foreach (QAction* action, viewActions) - showActionInStatusBar(action, true); - */ - - adjustStatusBarVisibility(); - messageLabel->hide(); - - newView->appear(); - - statusToolBar->setUpdatesEnabled(true); - - /* - QString desc = metadata.value("description").toString(); - if (!desc.isEmpty()) showMessage(desc); - */ - } - - setUpdatesEnabled(true); - -#ifdef APP_MAC - // Workaround cursor bug on macOS - unsetCursor(); - mac::uncloseWindow(winId()); -#endif + toolbarSearch->setEnabled(isMediaView); + aboutAct->setEnabled(view != aboutView); + getAction("downloads")->setChecked(view == downloadView); + + // dynamic view actions + /* Not currently used by any view + showActionsInStatusBar(viewActions, false); + viewActions = newView->getViewActions(); + showActionsInStatusBar(viewActions, true); + */ - history.push(widget); + history.push(view); emit viewChanged(); } @@ -1076,7 +1083,7 @@ void MainWindow::about() { aboutView = new AboutView(this); views->addWidget(aboutView); } - showWidget(aboutView); + showView(aboutView); } void MainWindow::visitSite() { @@ -1092,7 +1099,7 @@ void MainWindow::donate() { } void MainWindow::reportIssue() { - QUrl url("http://flavio.tordini.org/forums/forum/minitube-forums/minitube-troubleshooting"); + QUrl url("https://flavio.tordini.org/forums/forum/minitube-forums/minitube-troubleshooting"); QDesktopServices::openUrl(url); } @@ -1104,6 +1111,9 @@ void MainWindow::quit() { #endif // do not save geometry when in full screen or in compact mode if (!fullScreenActive && !compactViewAct->isChecked()) { +#ifdef APP_MAC + hideToolbar(); +#endif writeSettings(); } // mediaView->stop(); @@ -1111,6 +1121,7 @@ void MainWindow::quit() { ChannelAggregator::instance()->stop(); ChannelAggregator::instance()->cleanup(); Database::shutdown(); + HttpUtils::clearCaches(); qApp->quit(); } @@ -1139,7 +1150,7 @@ void MainWindow::showEvent(QShowEvent *e) { bool MainWindow::confirmQuit() { if (DownloadManager::instance()->activeItems() > 0) { QMessageBox msgBox(this); - msgBox.setIconPixmap(IconUtils::pixmap(":/images/64x64/app.png")); + msgBox.setIconPixmap(IconUtils::pixmap(":/images/64x64/app.png", devicePixelRatioF())); msgBox.setText( tr("Do you want to exit %1 with a download in progress?").arg(Constants::NAME)); msgBox.setInformativeText( @@ -1162,12 +1173,13 @@ bool MainWindow::confirmQuit() { } void MainWindow::showHome() { - showWidget(homeView); + showView(homeView); currentTimeLabel->clear(); + seekSlider->setValue(0); } void MainWindow::showMedia(SearchParams *searchParams) { - showWidget(mediaView); + showView(mediaView); if (getAction("safeSearch")->isChecked()) searchParams->setSafeSearch(SearchParams::Strict); else @@ -1176,71 +1188,59 @@ void MainWindow::showMedia(SearchParams *searchParams) { } void MainWindow::showMedia(VideoSource *videoSource) { - showWidget(mediaView); + showView(mediaView); mediaView->setVideoSource(videoSource); } -#ifdef APP_PHONON -void MainWindow::stateChanged(Phonon::State newState, Phonon::State /* oldState */) { - // qDebug() << "Phonon state: " << newState; +void MainWindow::stateChanged(Media::State newState) { + qDebug() << newState; + + seekSlider->setEnabled(newState != Media::StoppedState); switch (newState) { - case Phonon::ErrorState: - if (mediaObject->errorType() == Phonon::FatalError) { - // Do not display because we try to play incomplete video files and sometimes trigger - // this - // We retry automatically (in MediaView) so no need to show it - // showMessage(tr("Fatal error: %1").arg(mediaObject->errorString())); - } else { - showMessage(tr("Error: %1").arg(mediaObject->errorString())); - } + case Media::ErrorState: + showMessage(tr("Error: %1").arg(media->errorString())); break; - case Phonon::PlayingState: + case Media::PlayingState: pauseAct->setEnabled(true); pauseAct->setIcon(IconUtils::icon("media-playback-pause")); pauseAct->setText(tr("&Pause")); pauseAct->setStatusTip(tr("Pause playback") + " (" + pauseAct->shortcut().toString(QKeySequence::NativeText) + ")"); - // stopAct->setEnabled(true); break; - case Phonon::StoppedState: + case Media::StoppedState: pauseAct->setEnabled(false); pauseAct->setIcon(IconUtils::icon("media-playback-start")); pauseAct->setText(tr("&Play")); pauseAct->setStatusTip(tr("Resume playback") + " (" + pauseAct->shortcut().toString(QKeySequence::NativeText) + ")"); - // stopAct->setEnabled(false); break; - case Phonon::PausedState: + case Media::PausedState: pauseAct->setEnabled(true); pauseAct->setIcon(IconUtils::icon("media-playback-start")); pauseAct->setText(tr("&Play")); pauseAct->setStatusTip(tr("Resume playback") + " (" + pauseAct->shortcut().toString(QKeySequence::NativeText) + ")"); - // stopAct->setEnabled(true); break; - case Phonon::BufferingState: + case Media::BufferingState: pauseAct->setEnabled(false); pauseAct->setIcon(IconUtils::icon("content-loading")); pauseAct->setText(tr("&Loading...")); pauseAct->setStatusTip(QString()); break; - case Phonon::LoadingState: + case Media::LoadingState: pauseAct->setEnabled(false); currentTimeLabel->clear(); - // totalTime->clear(); - // stopAct->setEnabled(true); break; default:; } } -#endif void MainWindow::stop() { showHome(); @@ -1264,16 +1264,11 @@ void MainWindow::resizeEvent(QResizeEvent *e) { #endif #ifdef APP_MAC_QMACTOOLBAR int moreButtonWidth = 40; - toolbarSearch->move(width() - toolbarSearch->width() - moreButtonWidth - 7, -38); + toolbarSearch->move(width() - toolbarSearch->width() - moreButtonWidth - 7, -34); #endif hideMessage(); } -void MainWindow::moveEvent(QMoveEvent *e) { - Q_UNUSED(e); - hideMessage(); -} - void MainWindow::enterEvent(QEvent *e) { Q_UNUSED(e); #ifdef APP_MAC @@ -1320,7 +1315,7 @@ void MainWindow::toggleFullscreen() { #endif } else { -// Exit full screen + // Exit full screen #ifdef APP_MAC MacSupport::exitFullScreen(this, views); @@ -1387,8 +1382,8 @@ void MainWindow::updateUIForFullscreen() { if (fullScreenActive) { stopAct->setShortcuts(QList() << QKeySequence(Qt::Key_MediaStop)); } else { - stopAct->setShortcuts(QList() << QKeySequence(Qt::Key_Escape) - << QKeySequence(Qt::Key_MediaStop)); + stopAct->setShortcuts(QList() + << QKeySequence(Qt::Key_Escape) << QKeySequence(Qt::Key_MediaStop)); } #ifdef Q_OS_MAC @@ -1418,23 +1413,29 @@ bool MainWindow::isReallyFullScreen() { } void MainWindow::missingKeyWarning() { + static bool shown = false; + if (shown) return; + shown = true; QMessageBox msgBox(this); - msgBox.setIconPixmap(IconUtils::pixmap(":/images/64x64/app.png")); + msgBox.setIconPixmap(IconUtils::pixmap(":/images/64x64/app.png", devicePixelRatioF())); msgBox.setText(QString("%1 was built without a Google API key.").arg(Constants::NAME)); msgBox.setInformativeText(QString("It won't work unless you enter one." "

In alternative you can get %1 from the developer site.") .arg(Constants::NAME)); msgBox.setModal(true); msgBox.setWindowModality(Qt::WindowModal); + msgBox.addButton(QMessageBox::Close); QPushButton *enterKeyButton = msgBox.addButton(QString("Enter API key..."), QMessageBox::AcceptRole); QPushButton *devButton = msgBox.addButton(QString("Get from %1").arg(Constants::WEBSITE), QMessageBox::AcceptRole); QPushButton *helpButton = msgBox.addButton(QMessageBox::Help); + msgBox.exec(); + if (msgBox.clickedButton() == helpButton) { - QDesktopServices::openUrl(QUrl( - "https://github.com/flaviotordini/minitube/blob/master/README.md#google-api-key")); + QDesktopServices::openUrl(QUrl("https://github.com/flaviotordini/minitube/blob/master/" + "README.md#google-api-key")); } else if (msgBox.clickedButton() == enterKeyButton) { bool ok; QString text = QInputDialog::getText(this, QString(), "Google API key:", QLineEdit::Normal, @@ -1447,9 +1448,12 @@ void MainWindow::missingKeyWarning() { } else if (msgBox.clickedButton() == devButton) { QDesktopServices::openUrl(QUrl(Constants::WEBSITE)); } + shown = false; } void MainWindow::compactView(bool enable) { + setUpdatesEnabled(false); + compactModeActive = enable; static QList compactShortcuts; @@ -1468,7 +1472,7 @@ void MainWindow::compactView(bool enable) { if (settings.contains(key)) restoreGeometry(settings.value(key).toByteArray()); else - resize(320, 180); + resize(480, 270); #ifdef APP_MAC_QMACTOOLBAR mac::showToolBar(winId(), !enable); @@ -1492,12 +1496,14 @@ void MainWindow::compactView(bool enable) { mediaView->setFocus(); } else { + settings.setValue(key, saveGeometry()); + // unset minimum size setMinimumSize(0, 0); + #ifdef Q_OS_MAC mac::SetupFullScreenWindow(winId()); #endif - settings.setValue(key, saveGeometry()); #ifdef APP_MAC_QMACTOOLBAR mac::showToolBar(winId(), !enable); #else @@ -1525,6 +1531,8 @@ void MainWindow::compactView(bool enable) { menuBar()->setVisible(menuVisibleBeforeCompactMode); } #endif + + setUpdatesEnabled(true); } void MainWindow::toggleToolbarMenu() { @@ -1540,72 +1548,79 @@ void MainWindow::searchFocus() { toolbarSearch->setFocus(); } -#ifdef APP_PHONON -void MainWindow::initPhonon() { - // Phonon initialization - if (mediaObject) delete mediaObject; - if (audioOutput) delete audioOutput; - mediaObject = new Phonon::MediaObject(this); - mediaObject->setTickInterval(100); - connect(mediaObject, SIGNAL(stateChanged(Phonon::State, Phonon::State)), - SLOT(stateChanged(Phonon::State, Phonon::State))); - connect(mediaObject, SIGNAL(tick(qint64)), SLOT(tick(qint64))); - connect(mediaObject, SIGNAL(totalTimeChanged(qint64)), SLOT(totalTimeChanged(qint64))); - - audioOutput = new Phonon::AudioOutput(Phonon::VideoCategory, this); - connect(audioOutput, SIGNAL(mutedChanged(bool)), SLOT(volumeMutedChanged(bool))); - Phonon::createPath(mediaObject, audioOutput); - volumeSlider->setAudioOutput(audioOutput); - -#ifdef APP_PHONON_SEEK - seekSlider->setMediaObject(mediaObject); +void MainWindow::initMedia() { +#ifdef MEDIA_QTAV + qFatal("QtAV has a showstopper bug. Audio stops randomly. See bug " + "https://github.com/wang-bin/QtAV/issues/1184"); + media = new MediaQtAV(this); +#elif defined MEDIA_MPV + media = new MediaMPV(); +#else + qFatal("No media backend defined"); #endif + media->init(); + media->setUserAgent(HttpUtils::stealthUserAgent()); QSettings settings; - audioOutput->setVolume(settings.value("volume", 1.).toReal()); - // audioOutput->setMuted(settings.value("volumeMute").toBool()); - - mediaObject->stop(); + qreal volume = settings.value("volume", 1.).toReal(); + media->setVolume(volume); + + connect(media, &Media::error, this, &MainWindow::handleError); + connect(media, &Media::stateChanged, this, &MainWindow::stateChanged); + connect(media, &Media::positionChanged, this, &MainWindow::tick); + + connect(seekSlider, &QSlider::sliderMoved, this, [this](int value) { + // value : maxValue = posit ion : duration + qint64 ms = (value * media->duration()) / seekSlider->maximum(); + qDebug() << "Seeking to" << ms; + media->seek(ms); + if (media->state() == Media::PausedState) media->play(); + }); + connect(seekSlider, &QSlider::sliderPressed, this, [this]() { + // value : maxValue = position : duration + qint64 ms = (seekSlider->value() * media->duration()) / seekSlider->maximum(); + media->seek(ms); + if (media->state() == Media::PausedState) media->play(); + }); + connect(media, &Media::started, this, [this]() { seekSlider->setValue(0); }); + + connect(media, &Media::volumeChanged, this, &MainWindow::volumeChanged); + connect(media, &Media::volumeMutedChanged, this, &MainWindow::volumeMutedChanged); + connect(volumeSlider, &QSlider::sliderMoved, this, [this](int value) { + qreal volume = (qreal)value / volumeSlider->maximum(); + media->setVolume(volume); + }); + connect(volumeSlider, &QSlider::sliderPressed, this, [this]() { + qreal volume = (qreal)volumeSlider->value() / volumeSlider->maximum(); + media->setVolume(volume); + }); + + mediaView->setMedia(media); } -#endif void MainWindow::tick(qint64 time) { +#ifdef APP_MAC + bool isDown = seekSlider->property("down").isValid(); +#else + bool isDown = seekSlider->isSliderDown(); +#endif + if (!isDown && media->state() == Media::PlayingState) { + // value : maxValue = position : duration + qint64 duration = media->duration(); + if (duration <= 0) return; + int value = (seekSlider->maximum() * media->position()) / duration; + seekSlider->setValue(value); + } + const QString s = formatTime(time); if (s != currentTimeLabel->text()) { currentTimeLabel->setText(s); emit currentTimeChanged(s); - } - -// remaining time -#ifdef APP_PHONON - const qint64 remainingTime = mediaObject->remainingTime(); - currentTimeLabel->setStatusTip(tr("Remaining time: %1").arg(formatTime(remainingTime))); - -#ifndef APP_PHONON_SEEK - const qint64 totalTime = mediaObject->totalTime(); - slider->blockSignals(true); - // qWarning() << totalTime << time << time * 100 / totalTime; - if (totalTime > 0 && time > 0 && !slider->isSliderDown() && - mediaObject->state() == Phonon::PlayingState) - slider->setValue(time * slider->maximum() / totalTime); - slider->blockSignals(false); -#endif -#endif -} - -void MainWindow::totalTimeChanged(qint64 time) { - if (time <= 0) { - // totalTime->clear(); - return; + // remaining time + const qint64 remainingTime = media->remainingTime(); + currentTimeLabel->setStatusTip(tr("Remaining time: %1").arg(formatTime(remainingTime))); } - // totalTime->setText(formatTime(time)); - - /* - slider->blockSignals(true); - slider->setMaximum(time/1000); - slider->blockSignals(false); - */ } QString MainWindow::formatTime(qint64 duration) { @@ -1621,31 +1636,31 @@ QString MainWindow::formatTime(qint64 duration) { } void MainWindow::volumeUp() { -#ifdef APP_PHONON - qreal newVolume = volumeSlider->audioOutput()->volume() + .1; - if (newVolume > volumeSlider->maximumVolume()) newVolume = volumeSlider->maximumVolume(); - volumeSlider->audioOutput()->setVolume(newVolume); -#endif + qreal newVolume = media->volume() + .1; + if (newVolume > 1.) newVolume = 1.; + media->setVolume(newVolume); } void MainWindow::volumeDown() { -#ifdef APP_PHONON - qreal newVolume = volumeSlider->audioOutput()->volume() - .1; - if (newVolume < 0.) newVolume = 0.; - volumeSlider->audioOutput()->setVolume(newVolume); -#endif + qreal newVolume = media->volume() - .1; + if (newVolume < 0) newVolume = 0; + media->setVolume(newVolume); } -void MainWindow::volumeMute() { -#ifdef APP_PHONON - bool muted = volumeSlider->audioOutput()->isMuted(); - volumeSlider->audioOutput()->setMuted(!muted); - qApp->processEvents(); - if (muted && volumeSlider->audioOutput()->volume() == 0.) { - volumeSlider->audioOutput()->setVolume(volumeSlider->maximumVolume()); - } - qDebug() << volumeSlider->audioOutput()->isMuted() << volumeSlider->audioOutput()->volume(); -#endif +void MainWindow::toggleVolumeMute() { + bool muted = media->volumeMuted(); + media->setVolumeMuted(!muted); +} + +void MainWindow::volumeChanged(qreal newVolume) { + // automatically unmute when volume changes + if (media->volumeMuted()) media->setVolumeMuted(false); + showMessage(tr("Volume at %1%").arg((int)(newVolume * 100))); + // newVolume : 1.0 = x : 1000 + int value = newVolume * volumeSlider->maximum(); + volumeSlider->blockSignals(true); + volumeSlider->setValue(value); + volumeSlider->blockSignals(false); } void MainWindow::volumeMutedChanged(bool muted) { @@ -1656,11 +1671,6 @@ void MainWindow::volumeMutedChanged(bool muted) { volumeMuteAct->setIcon(IconUtils::icon("audio-volume-high")); showMessage(tr("Volume is unmuted")); } -#ifdef APP_LINUX - QToolButton *volumeMuteButton = - qobject_cast(mainToolBar->widgetForAction(volumeMuteAct)); - volumeMuteButton->setIcon(volumeMuteButton->icon().pixmap(16)); -#endif } void MainWindow::setDefinitionMode(const QString &definitionName) { @@ -1670,18 +1680,15 @@ void MainWindow::setDefinitionMode(const QString &definitionName) { tr("Maximum video definition set to %1").arg(definitionAct->text()) + " (" + definitionAct->shortcut().toString(QKeySequence::NativeText) + ")"); showMessage(definitionAct->statusTip()); - QSettings settings; - settings.setValue("definition", definitionName); + YT3::instance().setMaxVideoDefinition(definitionName); + if (views->currentWidget() == mediaView) { + mediaView->reloadCurrentVideo(); + } } void MainWindow::toggleDefinitionMode() { - const QString definitionName = QSettings().value("definition").toString(); const QVector &definitions = VideoDefinition::getDefinitions(); - const VideoDefinition ¤tDefinition = VideoDefinition::forName(definitionName); - if (currentDefinition.isEmpty()) { - setDefinitionMode(definitions.at(0).getName()); - return; - } + const VideoDefinition ¤tDefinition = YT3::instance().maxVideoDefinition(); int index = definitions.indexOf(currentDefinition); if (index != definitions.size() - 1) { @@ -1689,7 +1696,6 @@ void MainWindow::toggleDefinitionMode() { } else { index = 0; } - // TODO: pass a VideoDefinition instead of QString. setDefinitionMode(definitions.at(index).getName()); } @@ -1712,7 +1718,7 @@ void MainWindow::setManualPlay(bool enabled) { if (views->currentWidget() == homeView && homeView->currentWidget() == homeView->getSearchView()) return; - showActionInStatusBar(getAction("manualplay"), enabled); + showActionsInStatusBar({getAction("manualplay")}, enabled); } void MainWindow::updateDownloadMessage(const QString &message) { @@ -1733,8 +1739,8 @@ void MainWindow::toggleDownloads(bool show) { } else { getAction("downloads") ->setShortcuts(QList() << QKeySequence(Qt::CTRL + Qt::Key_J)); - stopAct->setShortcuts(QList() << QKeySequence(Qt::Key_Escape) - << QKeySequence(Qt::Key_MediaStop)); + stopAct->setShortcuts(QList() + << QKeySequence(Qt::Key_Escape) << QKeySequence(Qt::Key_MediaStop)); } if (!downloadView) { @@ -1742,7 +1748,7 @@ void MainWindow::toggleDownloads(bool show) { views->addWidget(downloadView); } if (show) - showWidget(downloadView); + showView(downloadView); else goBack(); } @@ -1796,36 +1802,27 @@ void MainWindow::checkForUpdate() { if (secondsSinceLastCheck < 86400) return; // check it out - if (updateChecker) delete updateChecker; - updateChecker = new UpdateChecker(); - connect(updateChecker, SIGNAL(newVersion(QString)), this, SLOT(gotNewVersion(QString))); + UpdateChecker *updateChecker = new UpdateChecker(); + connect(updateChecker, &UpdateChecker::newVersion, this, + [this, updateChecker](const QString &version) { + updateChecker->deleteLater(); + QSettings settings; + QString checkedVersion = settings.value("checkedVersion").toString(); + if (checkedVersion == version) return; +#ifdef APP_SIMPLEUPDATE + simpleUpdateDialog(version); +#elif defined(APP_EXTRA) && !defined(APP_MAC) + UpdateDialog *dialog = new UpdateDialog(version, this); + dialog->show(); +#endif + }); updateChecker->checkForUpdate(); settings.setValue(updateCheckKey, unixTime); } -void MainWindow::gotNewVersion(const QString &version) { - if (updateChecker) { - delete updateChecker; - updateChecker = 0; - } - - QSettings settings; - QString checkedVersion = settings.value("checkedVersion").toString(); - if (checkedVersion == version) return; - -#ifdef APP_EXTRA -#ifndef APP_MAC - UpdateDialog *dialog = new UpdateDialog(version, this); - dialog->show(); -#endif -#else - simpleUpdateDialog(version); -#endif -} - void MainWindow::simpleUpdateDialog(const QString &version) { QMessageBox msgBox(this); - msgBox.setIconPixmap(IconUtils::pixmap(":/images/64x64/app.png")); + msgBox.setIconPixmap(IconUtils::pixmap(":/images/64x64/app.png", devicePixelRatioF())); msgBox.setText(tr("%1 version %2 is now available.").arg(Constants::NAME, version)); msgBox.setModal(true); msgBox.setWindowModality(Qt::WindowModal); @@ -1852,7 +1849,7 @@ void MainWindow::adjustMessageLabelPosition() { } void MainWindow::floatOnTop(bool onTop, bool showAction) { - if (showAction) showActionInStatusBar(getAction("ontop"), onTop); + if (showAction) showActionsInStatusBar({getAction("ontop")}, onTop); #ifdef APP_MAC mac::floatOnTop(winId(), onTop); #else @@ -1946,6 +1943,18 @@ void MainWindow::printHelp() { std::cout << msg.toLocal8Bit().data(); } +void MainWindow::setupAction(QAction *action) { + // never autorepeat. + // unexperienced users tend to keep keys pressed for a "long" time + action->setAutoRepeat(false); + + // show keyboard shortcuts in the status bar + if (!action->shortcut().isEmpty()) + action->setStatusTip(action->statusTip() + QLatin1String(" (") + + action->shortcut().toString(QKeySequence::NativeText) + + QLatin1String(")")); +} + QAction *MainWindow::getAction(const char *name) { return actionMap.value(QByteArray::fromRawData(name, strlen(name))); } @@ -1965,13 +1974,13 @@ void MainWindow::showMessage(const QString &message) { #endif if (statusBar()->isVisible()) statusBar()->showMessage(message, 60000); - else { + else if (isActiveWindow()) { messageLabel->setText(message); QSize size = messageLabel->sizeHint(); - // round width to nearest 10 to avoid flicker with fast changing messages (e.g. volume + // round width to avoid flicker with fast changing messages (e.g. volume // changes) - int w = size.width(); - const int multiple = 10; + int w = size.width() + 10; + const int multiple = 15; w = w + multiple / 2; w -= w % multiple; size.setWidth(w); @@ -1991,11 +2000,16 @@ void MainWindow::hideMessage() { } } +void MainWindow::handleError(const QString &message) { + qWarning() << message; + showMessage(message); +} + #ifdef APP_ACTIVATION void MainWindow::showActivationView() { - QWidget *activationView = ActivationView::instance(); + View *activationView = ActivationView::instance(); views->addWidget(activationView); - if (views->currentWidget() != activationView) showWidget(activationView); + if (views->currentWidget() != activationView) showView(activationView); } #endif @@ -2006,5 +2020,5 @@ void MainWindow::showRegionsView() { SLOT(load())); views->addWidget(regionsView); } - showWidget(regionsView); + showView(regionsView); } diff --git a/src/mainwindow.h b/src/mainwindow.h index e377a2f..e7d80b6 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -22,56 +22,47 @@ $END_LICENSE */ #define MAINWINDOW_H #include -#ifdef APP_PHONON -#include -#include -#include -#include -#endif +#include "media.h" + +class View; class HomeView; class MediaView; class DownloadView; class SearchLineEdit; -class UpdateChecker; class SearchParams; class VideoSource; class Suggestion; class ToolbarMenu; class MainWindow : public QMainWindow { - Q_OBJECT public: - static MainWindow* instance(); + static MainWindow *instance(); MainWindow(); -#ifdef APP_PHONON_SEEK - Phonon::SeekSlider* getSeekSlider() { return seekSlider; } -#else - QSlider* getSlider() { return slider; } -#endif -#ifdef APP_PHONON - Phonon::AudioOutput* getAudioOutput() { return audioOutput; } - Phonon::VolumeSlider *getVolumeSlider() { return volumeSlider; } -#endif + + QSlider *getSeekSlider() { return seekSlider; } + QSlider *getVolumeSlider() { return volumeSlider; } + QLabel *getCurrentTimeLabel() { return currentTimeLabel; } void readSettings(); void writeSettings(); static void printHelp(); QStackedWidget *getViews() { return views; } - MediaView* getMediaView() { return mediaView; } - HomeView* getHomeView() { return homeView; } - QAction* getRegionAction() { return regionAction; } + MediaView *getMediaView() { return mediaView; } + HomeView *getHomeView() { return homeView; } + QAction *getRegionAction() { return regionAction; } SearchLineEdit *getToolbarSearch() { return toolbarSearch; } + void setupAction(QAction *action); QAction *getAction(const char *name); void addNamedAction(const QByteArray &name, QAction *action); QMenu *getMenu(const char *name); - void showActionInStatusBar(QAction*, bool show); + void showActionsInStatusBar(const QVector &actions, bool show); void setStatusBarVisibility(bool show); void adjustStatusBarVisibility(); @@ -96,10 +87,12 @@ public slots: void goBack(); void showMessage(const QString &message); void hideMessage(); + void handleError(const QString &message); bool isReallyFullScreen(); bool isCompact() { return compactModeActive; } void missingKeyWarning(); void visitSite(); + void setDefinitionMode(const QString &definitionName); signals: void currentTimeChanged(const QString &s); @@ -113,14 +106,12 @@ protected: void dragEnterEvent(QDragEnterEvent *e); void dropEvent(QDropEvent *e); void resizeEvent(QResizeEvent *e); - void moveEvent(QMoveEvent *e); void leaveEvent(QEvent *e); void enterEvent(QEvent *e); private slots: void lazyInit(); void checkForUpdate(); - void gotNewVersion(const QString &version); void donate(); void reportIssue(); void about(); @@ -128,19 +119,18 @@ private slots: void updateUIForFullscreen(); void compactView(bool enable); void stop(); -#ifdef APP_PHONON - void stateChanged(Phonon::State newState, Phonon::State oldState); -#endif void searchFocus(); - void tick(qint64 time); - void totalTimeChanged(qint64 time); - void setDefinitionMode(const QString &definitionName); void toggleDefinitionMode(); void clearRecentKeywords(); + // media + void stateChanged(Media::State state); + void tick(qint64 time); + void volumeUp(); void volumeDown(); - void volumeMute(); + void toggleVolumeMute(); + void volumeChanged(qreal newVolume); void volumeMutedChanged(bool muted); void updateDownloadMessage(const QString &); @@ -161,36 +151,31 @@ private slots: #endif private: -#ifdef APP_PHONON - void initPhonon(); -#endif + void initMedia(); void createActions(); void createMenus(); - void createToolBars(); + void createToolBar(); void createStatusBar(); - void showWidget(QWidget*, bool transition = true); + void showView(View *view, bool transition = false); static QString formatTime(qint64 duration); bool confirmQuit(); void simpleUpdateDialog(const QString &version); bool needStatusBar(); void adjustMessageLabelPosition(); - UpdateChecker *updateChecker; - - QHash actionMap; - QHash menuMap; + QHash actionMap; + QHash menuMap; // view mechanism QStackedWidget *views; - QStack history; - // QVector viewActions; + QStack history; // view widgets HomeView *homeView; MediaView *mediaView; - QWidget *aboutView; - QWidget *downloadView; - QWidget *regionsView; + View *aboutView; + View *downloadView; + View *regionsView; // actions QAction *backAct; @@ -233,18 +218,8 @@ private: SearchLineEdit *toolbarSearch; QToolBar *statusToolBar; QAction *regionAction; - - // phonon -#ifdef APP_PHONON -#ifdef APP_PHONON_SEEK - Phonon::SeekSlider *seekSlider; -#else - QSlider *slider; -#endif - Phonon::VolumeSlider *volumeSlider; - Phonon::MediaObject *mediaObject; - Phonon::AudioOutput *audioOutput; -#endif + QSlider *seekSlider; + QSlider *volumeSlider; QLabel *currentTimeLabel; bool fullScreenActive; @@ -260,6 +235,8 @@ private: ToolbarMenu *toolbarMenu; QToolButton *toolbarMenuButton; + + Media *media; }; #endif diff --git a/src/mediaview.cpp b/src/mediaview.cpp index 286d56f..e6343ab 100644 --- a/src/mediaview.cpp +++ b/src/mediaview.cpp @@ -20,7 +20,6 @@ $END_LICENSE */ #include "mediaview.h" #include "constants.h" -#include "downloaditem.h" #include "downloadmanager.h" #include "http.h" #include "loadingwidget.h" @@ -32,7 +31,7 @@ $END_LICENSE */ #include "sidebarheader.h" #include "sidebarwidget.h" #include "temporary.h" -#include "videoareawidget.h" +#include "videoarea.h" #ifdef APP_ACTIVATION #include "activation.h" #endif @@ -51,6 +50,7 @@ $END_LICENSE */ #endif #include "datautils.h" #include "idle.h" +#include "videodefinition.h" MediaView *MediaView::instance() { static MediaView *i = new MediaView(); @@ -58,10 +58,10 @@ MediaView *MediaView::instance() { } MediaView::MediaView(QWidget *parent) - : View(parent), stopped(false), downloadItem(0) + : View(parent), splitter(nullptr), stopped(false) #ifdef APP_SNAPSHOT , - snapshotSettings(0) + snapshotSettings(nullptr) #endif , pauseTime(0) { @@ -79,10 +79,11 @@ void MediaView::initialize() { playlistView = new PlaylistView(); playlistView->setParent(this); connect(playlistView, SIGNAL(activated(const QModelIndex &)), - SLOT(itemActivated(const QModelIndex &))); + SLOT(onItemActivated(const QModelIndex &))); playlistModel = new PlaylistModel(); - connect(playlistModel, SIGNAL(activeRowChanged(int)), SLOT(activeRowChanged(int))); + connect(playlistModel, &PlaylistModel::activeVideoChanged, this, + &MediaView::activeVideoChanged); // needed to restore the selection after dragndrop connect(playlistModel, SIGNAL(needSelectionFor(QVector

").arg(cssColor) + - tr("Welcome to %2,") - .replace(""); + QLabel *welcomeLabel = new QLabel(); + auto setupWelcomeLabel = [this, welcomeLabel] { + QColor titleColor = palette().color(QPalette::WindowText); + titleColor.setAlphaF(.75); + int r, g, b, a; + titleColor.getRgb(&r, &g, &b, &a); + QString cssColor = QString::asprintf("rgba(%d,%d,%d,%d)", r, g, b, a); + QString text = + QString("

").arg(cssColor) + + tr("Welcome to %2,") + .replace(""; + welcomeLabel->setText(text); + }; + setupWelcomeLabel(); + connect(qApp, &QGuiApplication::paletteChanged, this, setupWelcomeLabel); welcomeLabel->setOpenExternalLinks(true); - welcomeLabel->setProperty("heading", true); - welcomeLabel->setFont(FontUtils::light(welcomeLabel->font().pointSize() * 1.25)); + welcomeLabel->setFont(FontUtils::light(welcomeLabel->font().pointSize())); layout->addWidget(welcomeLabel); layout->addSpacing(padding / 2); -#ifndef APP_MAC - const QFont &biggerFont = FontUtils::big(); -#endif - //: "Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos" // QLabel *tipLabel = new QLabel(tr("Enter"), this); - QString tip; if (qApp->layoutDirection() == Qt::RightToLeft) { tip = tr("to start watching videos.") + " " + tr("a keyword") + " " + tr("Enter"); } else { tip = tr("Enter") + " " + tr("a keyword") + " " + tr("to start watching videos."); } - QLabel *tipLabel = new QLabel(tip); - -#ifndef APP_MAC - tipLabel->setFont(biggerFont); -#endif - layout->addWidget(tipLabel); - - /* - typeCombo = new QComboBox(this); - typeCombo->addItem(tr("a keyword")); - typeCombo->addItem(tr("a channel")); -#ifndef APP_MAC - typeCombo->setFont(biggerFont); -#endif - connect(typeCombo, SIGNAL(currentIndexChanged(int)), SLOT(searchTypeChanged(int))); - tipLayout->addWidget(typeCombo); - - tipLabel = new QLabel(tr("to start watching videos."), this); -#ifndef APP_MAC - tipLabel->setFont(biggerFont); -#endif - tipLayout->addWidget(tipLabel); - */ layout->addSpacing(padding / 2); - QHBoxLayout *searchLayout = new QHBoxLayout(); + QBoxLayout *searchLayout = new QHBoxLayout(); searchLayout->setAlignment(Qt::AlignVCenter); #ifdef APP_MAC_SEARCHFIELD @@ -148,34 +131,31 @@ SearchView::SearchView(QWidget *parent) : View(parent) { setFocusProxy(slem); #else SearchLineEdit *sle = new SearchLineEdit(this); - sle->setFont(biggerFont); + sle->setFont(FontUtils::medium()); + int tipWidth = sle->fontMetrics().size(Qt::TextSingleLine, tip).width(); + sle->setMinimumWidth(tipWidth + sle->fontMetrics().width('m') * 6); + sle->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); queryEdit = sle; #endif - connect(queryEdit->toWidget(), SIGNAL(search(const QString&)), SLOT(watch(const QString&))); - connect(queryEdit->toWidget(), SIGNAL(textChanged(const QString &)), SLOT(textChanged(const QString &))); - connect(queryEdit->toWidget(), SIGNAL(textEdited(const QString &)), SLOT(textChanged(const QString &))); - connect(queryEdit->toWidget(), SIGNAL(suggestionAccepted(Suggestion*)), SLOT(suggestionAccepted(Suggestion*))); + connect(queryEdit->toWidget(), SIGNAL(search(const QString &)), SLOT(watch(const QString &))); + connect(queryEdit->toWidget(), SIGNAL(textChanged(const QString &)), + SLOT(textChanged(const QString &))); + connect(queryEdit->toWidget(), SIGNAL(textEdited(const QString &)), + SLOT(textChanged(const QString &))); + connect(queryEdit->toWidget(), SIGNAL(suggestionAccepted(Suggestion *)), + SLOT(suggestionAccepted(Suggestion *))); + queryEdit->setPlaceholderText(tip); youtubeSuggest = new YTSuggester(this); channelSuggest = new ChannelSuggest(this); - connect(channelSuggest, SIGNAL(ready(QVector)), SLOT(onChannelSuggestions(QVector))); + connect(channelSuggest, SIGNAL(ready(QVector)), + SLOT(onChannelSuggestions(QVector))); searchTypeChanged(0); searchLayout->addWidget(queryEdit->toWidget(), 0, Qt::AlignBaseline); - searchLayout->addSpacing(padding); - watchButton = new QPushButton(tr("Watch")); -#ifndef APP_MAC - watchButton->setFont(biggerFont); -#endif - watchButton->setDefault(true); - watchButton->setEnabled(false); - watchButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - connect(watchButton, SIGNAL(clicked()), this, SLOT(watch())); - searchLayout->addWidget(watchButton, 0, Qt::AlignBaseline); - - layout->addItem(searchLayout); + layout->addLayout(searchLayout); layout->addSpacing(padding); @@ -188,9 +168,9 @@ SearchView::SearchView(QWidget *parent) : View(parent) { recentKeywordsLayout->setSpacing(0); recentKeywordsLayout->setAlignment(Qt::AlignTop | Qt::AlignLeft); recentKeywordsLabel = new QLabel(tr("Recent keywords")); - recentKeywordsLabel->setEnabled(false); recentKeywordsLabel->setProperty("recentHeader", true); recentKeywordsLabel->hide(); + recentKeywordsLabel->setEnabled(false); recentKeywordsLayout->addWidget(recentKeywordsLabel); recentLayout->addLayout(recentKeywordsLayout); @@ -199,9 +179,9 @@ SearchView::SearchView(QWidget *parent) : View(parent) { recentChannelsLayout->setSpacing(0); recentChannelsLayout->setAlignment(Qt::AlignTop | Qt::AlignLeft); recentChannelsLabel = new QLabel(tr("Recent channels")); - recentChannelsLabel->setEnabled(false); recentChannelsLabel->setProperty("recentHeader", true); recentChannelsLabel->hide(); + recentChannelsLabel->setEnabled(false); recentChannelsLayout->addWidget(recentChannelsLabel); recentLayout->addLayout(recentChannelsLayout); @@ -213,39 +193,30 @@ SearchView::SearchView(QWidget *parent) : View(parent) { #ifdef APP_ACTIVATION if (!Activation::instance().isActivated()) - vLayout->addWidget(ActivationView::buyButton(tr("Get the full version")), 0, Qt::AlignRight); + vLayout->addWidget(ActivationView::buyButton(tr("Get the full version")), 0, + Qt::AlignRight); #endif } void SearchView::appear() { MainWindow *w = MainWindow::instance(); - w->showActionInStatusBar(w->getAction("manualplay"), true); - w->showActionInStatusBar(w->getAction("safeSearch"), true); - w->showActionInStatusBar(w->getAction("definition"), true); + w->showActionsInStatusBar( + {w->getAction("manualplay"), w->getAction("safeSearch"), w->getAction("definition")}, + true); updateRecentKeywords(); updateRecentChannels(); queryEdit->selectAll(); queryEdit->enableSuggest(); - if (!queryEdit->toWidget()->hasFocus()) queryEdit->toWidget()->setFocus(); - - connect(window()->windowHandle(), SIGNAL(screenChanged(QScreen*)), SLOT(screenChanged()), Qt::UniqueConnection); - - qApp->processEvents(); - update(); - -#ifdef APP_MAC - // Workaround cursor bug on macOS - window()->unsetCursor(); -#endif + QTimer::singleShot(0, queryEdit->toWidget(), SLOT(setFocus())); } void SearchView::disappear() { MainWindow *w = MainWindow::instance(); - w->showActionInStatusBar(w->getAction("safeSearch"), false); - w->showActionInStatusBar(w->getAction("definition"), false); - w->showActionInStatusBar(w->getAction("manualplay"), false); + w->showActionsInStatusBar( + {w->getAction("manualplay"), w->getAction("safeSearch"), w->getAction("definition")}, + false); } void SearchView::updateRecentKeywords() { @@ -257,7 +228,8 @@ void SearchView::updateRecentKeywords() { // cleanup QLayoutItem *item; - while ((item = recentKeywordsLayout->takeAt(1)) != 0) { + while (recentKeywordsLayout->count() - 1 > recentKeywords.size() && + (item = recentKeywordsLayout->takeAt(1)) != nullptr) { item->widget()->close(); delete item; } @@ -267,37 +239,66 @@ void SearchView::updateRecentKeywords() { const int maxDisplayLength = 25; +#ifdef APP_MAC + QPalette p = palette(); + p.setColor(QPalette::Highlight, mac::accentColor()); +#endif + + int counter = 1; for (const QString &keyword : keywords) { QString link = keyword; QString display = keyword; - if (keyword.startsWith(QLatin1String("http://")) || keyword.startsWith(QLatin1String("https://"))) { + if (keyword.startsWith(QLatin1String("http://")) || + keyword.startsWith(QLatin1String("https://"))) { int separator = keyword.indexOf('|'); if (separator > 0 && separator + 1 < keyword.length()) { link = keyword.left(separator); - display = keyword.mid(separator+1); + display = keyword.mid(separator + 1); } } - bool needStatusTip = false; - if (display.length() > maxDisplayLength) { + bool needStatusTip = display.length() > maxDisplayLength; + if (needStatusTip) { display.truncate(maxDisplayLength); display.append(QStringLiteral("\u2026")); - needStatusTip = true; } - QPushButton *itemButton = new QPushButton(display); - itemButton->setAttribute(Qt::WA_DeleteOnClose); - itemButton->setProperty("recentItem", true); - itemButton->setCursor(Qt::PointingHandCursor); - itemButton->setFocusPolicy(Qt::TabFocus); - itemButton->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); + + ClickableLabel *item; + if (recentKeywordsLayout->count() - 1 >= counter) { + item = qobject_cast(recentKeywordsLayout->itemAt(counter)->widget()); + + } else { + item = new ClickableLabel(); +#ifdef APP_MAC + item->setPalette(p); +#endif + item->setAttribute(Qt::WA_DeleteOnClose); + item->setProperty("recentItem", true); + item->setFocusPolicy(Qt::TabFocus); + connect(item, &ClickableLabel::hovered, this, [item, this](bool value) { + item->setForegroundRole(value ? QPalette::Highlight : QPalette::WindowText); + if (value) { + for (int i = 1; i < recentKeywordsLayout->count(); ++i) { + QWidget *w = recentKeywordsLayout->itemAt(i)->widget(); + if (w != item) { + w->setForegroundRole(QPalette::WindowText); + } + } + } + }); + recentKeywordsLayout->addWidget(item); + } + + item->setText(display); if (needStatusTip) - itemButton->setStatusTip(link); - connect(itemButton, &QPushButton::clicked, [this,link]() { - watchKeywords(link); - }); + item->setStatusTip(link); + else + item->setStatusTip(QString()); - recentKeywordsLayout->addWidget(itemButton); - } + disconnect(item, &ClickableLabel::clicked, nullptr, nullptr); + connect(item, &ClickableLabel::clicked, this, [this, link]() { watchKeywords(link); }); + counter++; + } } void SearchView::updateRecentChannels() { @@ -309,13 +310,17 @@ void SearchView::updateRecentChannels() { // cleanup QLayoutItem *item; - while ((item = recentChannelsLayout->takeAt(1)) != 0) { + while ((item = recentChannelsLayout->takeAt(1)) != nullptr) { item->widget()->close(); delete item; } recentChannelsLabel->setVisible(!keywords.isEmpty()); - // TODO MainWindow::instance()->getAction("clearRecentKeywords")->setEnabled(!keywords.isEmpty()); + +#ifdef APP_MAC + QPalette p = palette(); + p.setColor(QPalette::Highlight, mac::accentColor()); +#endif for (const QString &keyword : keywords) { QString link = keyword; @@ -323,18 +328,21 @@ void SearchView::updateRecentChannels() { int separator = keyword.indexOf('|'); if (separator > 0 && separator + 1 < keyword.length()) { link = keyword.left(separator); - display = keyword.mid(separator+1); + display = keyword.mid(separator + 1); } - QPushButton *itemButton = new QPushButton(display); - itemButton->setAttribute(Qt::WA_DeleteOnClose); - itemButton->setProperty("recentItem", true); - itemButton->setCursor(Qt::PointingHandCursor); - itemButton->setFocusPolicy(Qt::TabFocus); - itemButton->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); - connect(itemButton, &QPushButton::clicked, [this,link]() { - watchChannel(link); + + ClickableLabel *item = new ClickableLabel(display); +#ifdef APP_MAC + item->setPalette(p); +#endif + item->setAttribute(Qt::WA_DeleteOnClose); + item->setProperty("recentItem", true); + item->setFocusPolicy(Qt::TabFocus); + connect(item, &ClickableLabel::clicked, [this, link]() { watchChannel(link); }); + connect(item, &ClickableLabel::hovered, item, [item](bool value) { + item->setForegroundRole(value ? QPalette::Highlight : QPalette::WindowText); }); - recentChannelsLayout->addWidget(itemButton); + recentChannelsLayout->addWidget(item); } } @@ -344,7 +352,6 @@ void SearchView::watch() { } void SearchView::textChanged(const QString &text) { - watchButton->setEnabled(!text.simplified().isEmpty()); lastChannelSuggestions.clear(); } @@ -357,14 +364,6 @@ void SearchView::watch(const QString &query) { return; } - /* - if (typeCombo->currentIndex() == 1) { - // Channel search - MainWindow::instance()->channelSearch(q); - return; - } - */ - SearchParams *searchParams = new SearchParams(); searchParams->setKeywords(q); @@ -401,10 +400,7 @@ void SearchView::watchKeywords(const QString &query) { return; } - // if (typeCombo->currentIndex() == 0) { - queryEdit->setText(q); - watchButton->setEnabled(true); - // } + queryEdit->setText(q); SearchParams *searchParams = new SearchParams(); searchParams->setKeywords(q); @@ -413,18 +409,6 @@ void SearchView::watchKeywords(const QString &query) { emit search(searchParams); } -void SearchView::paintEvent(QPaintEvent *event) { - QWidget::paintEvent(event); - QBrush brush; - if (window()->isActiveWindow()) { - brush = palette().base(); - } else { - brush = palette().window(); - } - QPainter painter(this); - painter.fillRect(0, 0, width(), height(), brush); -} - void SearchView::searchTypeChanged(int index) { if (index == 0) { queryEdit->setSuggester(youtubeSuggest); @@ -438,11 +422,8 @@ void SearchView::searchTypeChanged(int index) { void SearchView::suggestionAccepted(Suggestion *suggestion) { if (suggestion->type == QLatin1String("channel")) { watchChannel(suggestion->userData); - } else watch(suggestion->value); -} - -void SearchView::screenChanged() { - logo->setPixmap(IconUtils::pixmap(":/images/app.png")); + } else + watch(suggestion->value); } void SearchView::onChannelSuggestions(const QVector &suggestions) { diff --git a/src/searchview.h b/src/searchview.h index 172b0ca..d6841d2 100644 --- a/src/searchview.h +++ b/src/searchview.h @@ -18,8 +18,8 @@ along with Minitube. If not, see . $END_LICENSE */ -#ifndef __SEARCHVIEW_H__ -#define __SEARCHVIEW_H__ +#ifndef SEARCHVIEW_H +#define SEARCHVIEW_H #include @@ -33,11 +33,10 @@ class Suggestion; class ClickableLabel; class SearchView : public View { - Q_OBJECT public: - SearchView(QWidget *parent = 0); + SearchView(QWidget *parent = nullptr); void updateRecentKeywords(); void updateRecentChannels(); @@ -49,38 +48,32 @@ public slots: void watchKeywords(const QString &query); signals: - void search(SearchParams*); - -protected: - void paintEvent(QPaintEvent *); + void search(SearchParams *); private slots: void watch(); void textChanged(const QString &text); void searchTypeChanged(int index); void suggestionAccepted(Suggestion *suggestion); - void screenChanged(); - void onChannelSuggestions(const QVector &suggestions); + void onChannelSuggestions(const QVector &suggestions); private: YTSuggester *youtubeSuggest; ChannelSuggest *channelSuggest; - QComboBox *typeCombo; SearchWidget *queryEdit; QLabel *recentKeywordsLabel; QBoxLayout *recentKeywordsLayout; QLabel *recentChannelsLabel; QBoxLayout *recentChannelsLayout; QLabel *message; - QPushButton *watchButton; QStringList recentKeywords; QStringList recentChannels; - QVector lastChannelSuggestions; + QVector lastChannelSuggestions; ClickableLabel *logo; }; -#endif // __SEARCHVIEW_H__ +#endif // SEARCHVIEW_H diff --git a/src/searchwidget.h b/src/searchwidget.h index dd42b70..b429d38 100644 --- a/src/searchwidget.h +++ b/src/searchwidget.h @@ -9,7 +9,6 @@ class Suggestion; class AutoComplete; class SearchWidget { - public: virtual QMenu *menu() const = 0; virtual void setMenu(QMenu *menu) = 0; @@ -17,7 +16,7 @@ public: virtual void preventSuggest() = 0; virtual void selectAll() = 0; virtual void setSuggester(Suggester *suggester) = 0; - virtual void setInactiveText(const QString &text) = 0; + virtual void setPlaceholderText(const QString &text) = 0; virtual void setText(const QString &text) = 0; virtual AutoComplete *getAutoComplete() = 0; virtual void emitTextChanged(const QString &text) = 0; @@ -31,7 +30,6 @@ signals: void textEdited(const QString &text); void search(const QString &text); void suggestionAccepted(Suggestion *suggestion); - }; #endif // SEARCHWIDGET diff --git a/src/seekslider.cpp b/src/seekslider.cpp index 281673f..81fb00a 100644 --- a/src/seekslider.cpp +++ b/src/seekslider.cpp @@ -2,14 +2,20 @@ class MyProxyStyle : public QProxyStyle { public: - int styleHint(StyleHint hint, const QStyleOption *option = 0, - const QWidget *widget = 0, QStyleHintReturn *returnData = 0) const { - if (hint == SH_Slider_AbsoluteSetButtons) - return Qt::LeftButton; - return QProxyStyle::styleHint(hint, option, widget, returnData); - } + int styleHint(StyleHint hint, + const QStyleOption *option = nullptr, + const QWidget *widget = nullptr, + QStyleHintReturn *returnData = nullptr) const; }; +int MyProxyStyle::styleHint(QStyle::StyleHint hint, + const QStyleOption *option, + const QWidget *widget, + QStyleHintReturn *returnData) const { + if (hint == SH_Slider_AbsoluteSetButtons) return Qt::LeftButton; + return QProxyStyle::styleHint(hint, option, widget, returnData); +} + SeekSlider::SeekSlider(QWidget *parent) : QSlider(parent) { setStyle(new MyProxyStyle()); } diff --git a/src/seekslider.h b/src/seekslider.h index a56b6c0..52835f9 100644 --- a/src/seekslider.h +++ b/src/seekslider.h @@ -4,12 +4,10 @@ #include class SeekSlider : public QSlider { - Q_OBJECT public: - SeekSlider(QWidget *parent = 0); - + SeekSlider(QWidget *parent = nullptr); }; #endif // SEEKSLIDER_H diff --git a/src/segmentedcontrol.cpp b/src/segmentedcontrol.cpp index 1af157e..e763f12 100644 --- a/src/segmentedcontrol.cpp +++ b/src/segmentedcontrol.cpp @@ -19,30 +19,25 @@ along with Minitube. If not, see . $END_LICENSE */ #include "segmentedcontrol.h" -#include "mainwindow.h" #include "fontutils.h" #include "iconutils.h" +#include "mainwindow.h" #include "painterutils.h" -SegmentedControl::SegmentedControl (QWidget *parent) : QWidget(parent) { +SegmentedControl::SegmentedControl(QWidget *parent) : QWidget(parent) { setAttribute(Qt::WA_OpaquePaintEvent); setMouseTracking(true); - hoveredAction = 0; - checkedAction = 0; - pressedAction = 0; + hoveredAction = nullptr; + checkedAction = nullptr; + pressedAction = nullptr; -#ifdef APP_WIN - selectedColor = palette().color(QPalette::Base); -#else - selectedColor = palette().color(QPalette::Window); -#endif - int darkerFactor = 105; - backgroundColor = selectedColor.darker(darkerFactor); - borderColor = backgroundColor; - hoveredColor = backgroundColor.darker(darkerFactor); - pressedColor = hoveredColor.darker(darkerFactor); + setupColors(); + connect(qApp, &QGuiApplication::paletteChanged, this, [this] { + setupColors(); + update(); + }); } QAction *SegmentedControl::addAction(QAction *action) { @@ -54,10 +49,10 @@ QAction *SegmentedControl::addAction(QAction *action) { bool SegmentedControl::setCheckedAction(int index) { if (index < 0) { - checkedAction = 0; + checkedAction = nullptr; return true; } - QAction* newCheckedAction = actionList.at(index); + QAction *newCheckedAction = actionList.at(index); return setCheckedAction(newCheckedAction); } @@ -65,20 +60,19 @@ bool SegmentedControl::setCheckedAction(QAction *action) { if (checkedAction == action) { return false; } - if (checkedAction) - checkedAction->setChecked(false); + if (checkedAction) checkedAction->setChecked(false); checkedAction = action; checkedAction->setChecked(true); update(); return true; } -QSize SegmentedControl::minimumSizeHint (void) const { +QSize SegmentedControl::minimumSizeHint() const { int itemsWidth = calculateButtonWidth() * actionList.size() * 1.2; - return(QSize(itemsWidth, QFontMetrics(font()).height() * 1.8)); + return (QSize(itemsWidth, QFontMetrics(font()).height() * 1.8)); } -void SegmentedControl::paintEvent (QPaintEvent * /*event*/) { +void SegmentedControl::paintEvent(QPaintEvent * /*event*/) { const int height = rect().height(); const int width = rect().width(); @@ -87,8 +81,7 @@ void SegmentedControl::paintEvent (QPaintEvent * /*event*/) { // Calculate Buttons Size & Location const int buttonWidth = width / actionList.size(); - const qreal pixelRatio = IconUtils::pixelRatio(); - + const qreal pixelRatio = devicePixelRatioF(); QPen pen(borderColor); const qreal penWidth = 1. / pixelRatio; pen.setWidthF(penWidth); @@ -101,22 +94,20 @@ void SegmentedControl::paintEvent (QPaintEvent * /*event*/) { QAction *action = actionList.at(i); if (i + 1 == actionCount) { // last button - rect.setWidth(width - buttonWidth * (actionCount-1)); + rect.setWidth(width - buttonWidth * (actionCount - 1)); paintButton(&p, rect, action); } else { paintButton(&p, rect, action); rect.moveLeft(rect.x() + rect.width()); } } - const qreal y = height - penWidth; - p.drawLine(QPointF(0, y), QPointF(width, y)); } -void SegmentedControl::mouseMoveEvent (QMouseEvent *event) { +void SegmentedControl::mouseMoveEvent(QMouseEvent *event) { QAction *action = findHoveredAction(event->pos()); if (!action && hoveredAction) { - hoveredAction = 0; + hoveredAction = nullptr; update(); } else if (action && action != hoveredAction) { hoveredAction = action; @@ -138,7 +129,7 @@ void SegmentedControl::mousePressEvent(QMouseEvent *event) { void SegmentedControl::mouseReleaseEvent(QMouseEvent *event) { QWidget::mouseReleaseEvent(event); - pressedAction = 0; + pressedAction = nullptr; if (hoveredAction) { bool changed = setCheckedAction(hoveredAction); if (changed) hoveredAction->trigger(); @@ -149,22 +140,37 @@ void SegmentedControl::leaveEvent(QEvent *event) { QWidget::leaveEvent(event); // status tip MainWindow::instance()->statusBar()->clearMessage(); - hoveredAction = 0; - pressedAction = 0; + hoveredAction = nullptr; + pressedAction = nullptr; update(); } -QAction *SegmentedControl::findHoveredAction(const QPoint& pos) const { +void SegmentedControl::setupColors() { + selectedColor = palette().color(QPalette::Base); + if (selectedColor.value() > 128) { + int factor = 105; + backgroundColor = selectedColor.darker(factor); + borderColor = backgroundColor; + hoveredColor = backgroundColor.darker(factor); + pressedColor = hoveredColor.darker(factor); + } else { + int factor = 130; + backgroundColor = selectedColor.lighter(factor); + borderColor = backgroundColor; + hoveredColor = backgroundColor.lighter(factor); + pressedColor = hoveredColor.lighter(factor); + } +} + +QAction *SegmentedControl::findHoveredAction(const QPoint &pos) const { const int w = width(); - if (pos.y() <= 0 || pos.x() >= w || pos.y() >= height()) - return 0; + if (pos.y() <= 0 || pos.x() >= w || pos.y() >= height()) return nullptr; int buttonWidth = w / actionList.size(); int buttonIndex = pos.x() / buttonWidth; - if (buttonIndex >= actionList.size()) - return 0; + if (buttonIndex >= actionList.size()) return nullptr; return actionList[buttonIndex]; } @@ -178,7 +184,7 @@ int SegmentedControl::calculateButtonWidth() const { return itemWidth; } -void SegmentedControl::paintButton(QPainter *painter, const QRect& rect, const QAction *action) { +void SegmentedControl::paintButton(QPainter *painter, const QRect &rect, const QAction *action) { painter->save(); painter->translate(rect.topLeft()); @@ -203,11 +209,9 @@ void SegmentedControl::paintButton(QPainter *painter, const QRect& rect, const Q painter->drawText(0, 0, width, height, Qt::AlignCenter, text); if (action->property("notifyCount").isValid()) { - QRect textBox = painter->boundingRect(rect, - Qt::AlignCenter, - text); + QRect textBox = painter->boundingRect(rect, Qt::AlignCenter, text); painter->translate((width + textBox.width()) / 2 + 10, (height - textBox.height()) / 2); - PainterUtils::paintBadge(painter, action->property("notifyCount").toString(), false, QColor(0,0,0,64)); + PainterUtils::paintBadge(painter, action->property("notifyCount").toString(), false, c); } painter->restore(); diff --git a/src/segmentedcontrol.h b/src/segmentedcontrol.h index 0639146..8e9813f 100644 --- a/src/segmentedcontrol.h +++ b/src/segmentedcontrol.h @@ -24,18 +24,17 @@ $END_LICENSE */ #include class SegmentedControl : public QWidget { - Q_OBJECT public: - SegmentedControl(QWidget *parent = 0); + SegmentedControl(QWidget *parent = nullptr); QAction *addAction(QAction *action); bool setCheckedAction(int index); bool setCheckedAction(QAction *action); QSize minimumSizeHint(void) const; signals: - void checkedActionChanged(QAction & action); + void checkedActionChanged(QAction &action); protected: void paintEvent(QPaintEvent *event); @@ -44,11 +43,12 @@ protected: void mouseReleaseEvent(QMouseEvent *event); void leaveEvent(QEvent *event); +private slots: + void setupColors(); + private: - void paintButton(QPainter *painter, - const QRect& rect, - const QAction *action); - QAction *findHoveredAction(const QPoint& pos) const; + void paintButton(QPainter *painter, const QRect &rect, const QAction *action); + QAction *findHoveredAction(const QPoint &pos) const; int calculateButtonWidth() const; QVector actionList; @@ -61,7 +61,6 @@ private: QColor selectedColor; QColor hoveredColor; QColor pressedColor; - }; #endif /* !SEGMENTEDCONTROL_H */ diff --git a/src/sharetoolbar.cpp b/src/sharetoolbar.cpp index e8d1b0d..84f232f 100644 --- a/src/sharetoolbar.cpp +++ b/src/sharetoolbar.cpp @@ -13,6 +13,6 @@ ShareToolbar::ShareToolbar(QWidget *parent) : QToolBar(parent) { } void ShareToolbar::setLeftMargin(int value) { - setStyleSheet("border:0;margin-left:" + QString::number(value) + "px"); - disconnect(sender(), 0, this, 0); + setStyleSheet("QToolButton {border:0;margin-left:" + QString::number(value) + "px}"); + disconnect(sender(), nullptr, this, nullptr); } diff --git a/src/sidebarheader.cpp b/src/sidebarheader.cpp index 399d65f..150db79 100644 --- a/src/sidebarheader.cpp +++ b/src/sidebarheader.cpp @@ -19,12 +19,13 @@ along with Minitube. If not, see . $END_LICENSE */ #include "sidebarheader.h" +#include "fontutils.h" #include "iconutils.h" +#include "mainwindow.h" #include "mediaview.h" #include "videosource.h" -#include "fontutils.h" -SidebarHeader::SidebarHeader(QWidget *parent) : QToolBar(parent) { } +SidebarHeader::SidebarHeader(QWidget *parent) : QToolBar(parent) {} void SidebarHeader::setup() { static bool isSetup = false; @@ -33,24 +34,22 @@ void SidebarHeader::setup() { setIconSize(QSize(16, 16)); - backAction = new QAction( - IconUtils::icon("go-previous"), - tr("&Back"), this); + backAction = new QAction(tr("&Back"), this); + IconUtils::setIcon(backAction, "go-previous"); backAction->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_Left)); connect(backAction, SIGNAL(triggered()), MediaView::instance(), SLOT(goBack())); addAction(backAction); - forwardAction = new QAction( - IconUtils::icon("go-next"), - tr("&Back"), this); + forwardAction = new QAction(tr("&Forward"), this); + IconUtils::setIcon(forwardAction, "go-next"); forwardAction->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_Right)); connect(forwardAction, SIGNAL(triggered()), MediaView::instance(), SLOT(goForward())); addAction(forwardAction); const auto a = actions(); - for (QAction* action : a) { + for (QAction *action : a) { window()->addAction(action); - IconUtils::setupAction(action); + MainWindow::instance()->setupAction(action); } QWidget *spacerWidget = new QWidget(this); @@ -65,7 +64,7 @@ QSize SidebarHeader::minimumSizeHint() const { void SidebarHeader::updateInfo() { setup(); - const QVector &history = MediaView::instance()->getHistory(); + const QVector &history = MediaView::instance()->getHistory(); int currentIndex = MediaView::instance()->getHistoryIndex(); bool canGoForward = MediaView::instance()->canGoForward(); @@ -73,11 +72,9 @@ void SidebarHeader::updateInfo() { forwardAction->setEnabled(canGoForward); if (canGoForward) { VideoSource *nextVideoSource = history.at(currentIndex + 1); - forwardAction->setStatusTip( - tr("Forward to %1") - .arg(nextVideoSource->getName()) - + " (" + forwardAction->shortcut().toString(QKeySequence::NativeText) + ")" - ); + forwardAction->setStatusTip(tr("Forward to %1").arg(nextVideoSource->getName()) + " (" + + forwardAction->shortcut().toString(QKeySequence::NativeText) + + ")"); } bool canGoBack = MediaView::instance()->canGoBack(); @@ -86,16 +83,13 @@ void SidebarHeader::updateInfo() { backAction->setEnabled(canGoBack); if (canGoBack) { VideoSource *previousVideoSource = history.at(currentIndex - 1); - backAction->setStatusTip( - tr("Back to %1") - .arg(previousVideoSource->getName()) - + " (" + backAction->shortcut().toString(QKeySequence::NativeText) + ")" - ); + backAction->setStatusTip(tr("Back to %1").arg(previousVideoSource->getName()) + " (" + + backAction->shortcut().toString(QKeySequence::NativeText) + ")"); } VideoSource *currentVideoSource = history.at(currentIndex); - connect(currentVideoSource, SIGNAL(nameChanged(QString)), - SLOT(updateTitle(QString)), Qt::UniqueConnection); + connect(currentVideoSource, SIGNAL(nameChanged(QString)), SLOT(updateTitle(QString)), + Qt::UniqueConnection); setTitle(currentVideoSource->getName()); } @@ -108,20 +102,19 @@ void SidebarHeader::setTitle(const QString &title) { this->title = title; update(); - QVector history = MediaView::instance()->getHistory(); + QVector history = MediaView::instance()->getHistory(); int currentIndex = MediaView::instance()->getHistoryIndex(); VideoSource *currentVideoSource = history.at(currentIndex); - for (QAction* action : videoSourceActions) + for (QAction *action : videoSourceActions) removeAction(action); videoSourceActions = currentVideoSource->getActions(); addActions(videoSourceActions); } void SidebarHeader::paintEvent(QPaintEvent *event) { - QToolBar::paintEvent(event); if (title.isEmpty()) return; QPainter p(this); - p.setPen(Qt::white); + p.setPen(palette().windowText().color()); const QRect r = rect(); @@ -129,12 +122,10 @@ void SidebarHeader::paintEvent(QPaintEvent *event) { QRect textBox = p.boundingRect(r, Qt::AlignCenter, t); int i = 1; const int margin = forwardAction->isVisible() ? 50 : 25; - while (textBox.width() > r.width() - margin*2 && t.length() > 3) { + while (textBox.width() > r.width() - margin * 2 && t.length() > 3) { t = t.left(t.length() - i).trimmed() + QStringLiteral("\u2026"); textBox = p.boundingRect(r, Qt::AlignCenter, t); i++; } p.drawText(r, Qt::AlignCenter, t); - - } diff --git a/src/sidebarwidget.cpp b/src/sidebarwidget.cpp index 27283d7..2b1d546 100644 --- a/src/sidebarwidget.cpp +++ b/src/sidebarwidget.cpp @@ -19,22 +19,24 @@ along with Minitube. If not, see . $END_LICENSE */ #include "sidebarwidget.h" +#include "mainwindow.h" #include "refinesearchbutton.h" #include "refinesearchwidget.h" #include "sidebarheader.h" -#include "mainwindow.h" #ifdef APP_EXTRA #include "extra.h" #endif -SidebarWidget::SidebarWidget(QWidget *parent) : - QWidget(parent), playlistWidth(0) { - playlist = 0; +SidebarWidget::SidebarWidget(QWidget *parent) : QWidget(parent), playlistWidth(0) { + playlist = nullptr; QBoxLayout *layout = new QVBoxLayout(this); layout->setSpacing(0); layout->setMargin(0); + setBackgroundRole(QPalette::Base); + setAutoFillBackground(true); + sidebarHeader = new SidebarHeader(); layout->addWidget(sidebarHeader); @@ -46,11 +48,9 @@ SidebarWidget::SidebarWidget(QWidget *parent) : messageLabel->setAutoFillBackground(true); messageLabel->setWordWrap(true); messageLabel->setTextFormat(Qt::RichText); - messageLabel->setTextInteractionFlags( - Qt::LinksAccessibleByKeyboard | - Qt::LinksAccessibleByMouse); - connect(messageLabel, SIGNAL(linkActivated(QString)), - SIGNAL(suggestionAccepted(QString))); + messageLabel->setTextInteractionFlags(Qt::LinksAccessibleByKeyboard | + Qt::LinksAccessibleByMouse); + connect(messageLabel, SIGNAL(linkActivated(QString)), SIGNAL(suggestionAccepted(QString))); messageLabel->hide(); layout->addWidget(messageLabel); @@ -62,8 +62,9 @@ SidebarWidget::SidebarWidget(QWidget *parent) : void SidebarWidget::setup() { refineSearchButton = new RefineSearchButton(this); - refineSearchButton->setStatusTip(tr("Refine Search") - + " (" + QKeySequence(Qt::CTRL + Qt::Key_R).toString(QKeySequence::NativeText) + ")"); + refineSearchButton->setStatusTip( + tr("Refine Search") + " (" + + QKeySequence(Qt::CTRL + Qt::Key_R).toString(QKeySequence::NativeText) + ")"); refineSearchButton->hide(); connect(refineSearchButton, SIGNAL(clicked()), SLOT(showRefineSearchWidget())); @@ -115,20 +116,20 @@ void SidebarWidget::hideRefineSearchWidget() { } void SidebarWidget::toggleRefineSearch(bool show) { - if (show) showRefineSearchWidget(); - else hideRefineSearchWidget(); + if (show) + showRefineSearchWidget(); + else + hideRefineSearchWidget(); } void SidebarWidget::resizeEvent(QResizeEvent *event) { QWidget::resizeEvent(event); - refineSearchButton->move( - playlist->viewport()->width() - refineSearchButton->minimumWidth(), - height() - refineSearchButton->minimumHeight()); + refineSearchButton->move(playlist->viewport()->width() - refineSearchButton->minimumWidth(), + height() - refineSearchButton->minimumHeight()); } void SidebarWidget::enterEvent(QEvent *) { - if (stackedWidget->currentWidget() != refineSearchWidget) - showRefineSearchButton(); + if (stackedWidget->currentWidget() != refineSearchWidget) showRefineSearchButton(); } void SidebarWidget::leaveEvent(QEvent *) { @@ -154,9 +155,8 @@ void SidebarWidget::handleMouseMove() { void SidebarWidget::showRefineSearchButton() { if (!refineSearchWidget->isEnabled()) return; - refineSearchButton->move( - playlist->viewport()->width() - refineSearchButton->minimumWidth(), - height() - refineSearchButton->minimumHeight()); + refineSearchButton->move(playlist->viewport()->width() - refineSearchButton->minimumWidth(), + height() - refineSearchButton->minimumHeight()); refineSearchButton->show(); } @@ -169,13 +169,12 @@ void SidebarWidget::showSuggestions(const QStringList &suggestions) { } message = message.arg(suggestionLinks); - QString html = - "" - "" - "%1" - ""; + QString html = "" + "" + "%1" + ""; html = html.arg(message); messageLabel->setText(html); messageLabel->show(); diff --git a/src/sidebarwidget.h b/src/sidebarwidget.h index f15e4da..aa66ee2 100644 --- a/src/sidebarwidget.h +++ b/src/sidebarwidget.h @@ -28,16 +28,15 @@ class RefineSearchWidget; class SidebarHeader; class SidebarWidget : public QWidget { - Q_OBJECT public: - SidebarWidget(QWidget *parent = 0); + SidebarWidget(QWidget *parent = nullptr); QListView *getPlaylist() { return playlist; } void setPlaylist(QListView *playlist); void showPlaylist(); - RefineSearchWidget* getRefineSearchWidget() { return refineSearchWidget; } - SidebarHeader* getHeader() { return sidebarHeader; } + RefineSearchWidget *getRefineSearchWidget() { return refineSearchWidget; } + SidebarHeader *getHeader() { return sidebarHeader; } void hideSuggestions(); public slots: diff --git a/src/snapshotpreview.cpp b/src/snapshotpreview.cpp index 3662648..373c0d6 100644 --- a/src/snapshotpreview.cpp +++ b/src/snapshotpreview.cpp @@ -21,14 +21,17 @@ $END_LICENSE */ #include "snapshotpreview.h" #include "mainwindow.h" -SnapshotPreview::SnapshotPreview(QWidget *parent) : QWidget(parent), -#ifdef APP_PHONON - mediaObject(0), - audioOutput(0) +#ifdef MEDIA_QTAV +#include "mediaqtav.h" #endif - { +#ifdef MEDIA_MPV +#include "mediampv.h" +#endif + +SnapshotPreview::SnapshotPreview(QWidget *parent) : QWidget(parent), mediaObject(nullptr) { + setWindowFlags(Qt::FramelessWindowHint | Qt::NoDropShadowWindowHint | + Qt::WindowTransparentForInput | Qt::WindowDoesNotAcceptFocus); setAttribute(Qt::WA_ShowWithoutActivating); - setWindowFlags(Qt::FramelessWindowHint | Qt::WindowDoesNotAcceptFocus); setAttribute(Qt::WA_StaticContents); setAttribute(Qt::WA_OpaquePaintEvent); setAttribute(Qt::WA_NoSystemBackground); @@ -49,15 +52,29 @@ SnapshotPreview::SnapshotPreview(QWidget *parent) : QWidget(parent), } void SnapshotPreview::start(QWidget *widget, const QPixmap &pixmap, bool soundOnly) { -#ifdef APP_PHONON if (!mediaObject) { - mediaObject = new Phonon::MediaObject(this); - audioOutput = new Phonon::AudioOutput(Phonon::NotificationCategory, this); - Phonon::createPath(mediaObject, audioOutput); +#ifdef MEDIA_QTAV + mediaObject = new MediaQtAV(this); +#elif defined MEDIA_MPV + mediaObject = new MediaMPV(this); +#else + qFatal("No media backend defined"); +#endif + if (mediaObject) { + mediaObject->setAudioOnly(true); + mediaObject->init(); + } } - mediaObject->setCurrentSource(QUrl("qrc:///sounds/snapshot.wav")); - mediaObject->play(); + +#ifdef APP_MAC + QString soundPath = QCoreApplication::applicationDirPath() + "/../Resources"; +#elif defined PKGDATADIR + QString soundPath = QLatin1String(PKGDATADIR) + "/sounds"; +#else + QString soundPath = QCoreApplication::applicationDirPath() + "/sounds"; #endif + + if (mediaObject) mediaObject->play(soundPath + "/snapshot.wav"); if (soundOnly) return; resize(pixmap.size()); @@ -79,8 +96,10 @@ void SnapshotPreview::start(QWidget *widget, const QPixmap &pixmap, bool soundOn timeLine->start(); #endif timer->start(); - if (isVisible()) update(); - else show(); + if (isVisible()) + update(); + else + show(); } void SnapshotPreview::paintEvent(QPaintEvent *e) { diff --git a/src/snapshotpreview.h b/src/snapshotpreview.h index 7bfab08..2039c0d 100644 --- a/src/snapshotpreview.h +++ b/src/snapshotpreview.h @@ -23,18 +23,13 @@ $END_LICENSE */ #include -#ifdef APP_PHONON -#include -#include -#include -#endif +#include "media.h" class SnapshotPreview : public QWidget { - Q_OBJECT public: - SnapshotPreview(QWidget *parent = 0); + SnapshotPreview(QWidget *parent = nullptr); void start(QWidget *widget, const QPixmap &pixmap, bool soundOnly); signals: @@ -51,10 +46,7 @@ private: QTimeLine *timeLine; QPoint offset; QTimer *timer; -#ifdef APP_PHONON - Phonon::MediaObject *mediaObject; - Phonon::AudioOutput *audioOutput; -#endif + Media *mediaObject; }; #endif // SNAPSHOTPREVIEW_H diff --git a/src/spacer.h b/src/spacer.h index cb87b18..5b33b2e 100644 --- a/src/spacer.h +++ b/src/spacer.h @@ -24,9 +24,8 @@ $END_LICENSE */ #include class Spacer : public QWidget { - public: - Spacer(QWidget *parent = 0, int minWidth = 60); + Spacer(QWidget *parent = nullptr, int minWidth = 60); protected: QSize sizeHint() const; diff --git a/src/standardfeedsview.cpp b/src/standardfeedsview.cpp index 3e83208..9080f1c 100644 --- a/src/standardfeedsview.cpp +++ b/src/standardfeedsview.cpp @@ -27,9 +27,7 @@ $END_LICENSE */ #include "ytstandardfeed.h" StandardFeedsView::StandardFeedsView(QWidget *parent) : View(parent), layout(0) { - QPalette p = palette(); - p.setBrush(QPalette::Window, Qt::black); - setPalette(p); + setBackgroundRole(QPalette::Base); setAutoFillBackground(true); connect(MainWindow::instance()->getAction("worldwideRegion"), SIGNAL(triggered()), @@ -95,7 +93,7 @@ void StandardFeedsView::removeVideoSourceWidget(VideoSourceWidget *videoSourceWi } const int itemCount = items.size(); - const int cols = itemCount / 3; + const int cols = 4; // itemCount / 3; for (int i = itemCount - 1; i >= 0; i--) { QLayoutItem *item = items.at(i); int index = itemCount - 1 - i; @@ -114,7 +112,7 @@ void StandardFeedsView::resetLayout() { layout = new QGridLayout(this); layout->setMargin(0); - layout->setSpacing(1); + layout->setSpacing(0); } YTStandardFeed * @@ -134,12 +132,12 @@ void StandardFeedsView::appear() { load(); } QAction *regionAction = MainWindow::instance()->getRegionAction(); - MainWindow::instance()->showActionInStatusBar(regionAction, true); + MainWindow::instance()->showActionsInStatusBar({regionAction}, true); } void StandardFeedsView::disappear() { QAction *regionAction = MainWindow::instance()->getRegionAction(); - MainWindow::instance()->showActionInStatusBar(regionAction, false); + MainWindow::instance()->showActionsInStatusBar({regionAction}, false); } void StandardFeedsView::selectWorldwideRegion() { diff --git a/src/toolbarmenu.cpp b/src/toolbarmenu.cpp index e03dbac..da26af2 100644 --- a/src/toolbarmenu.cpp +++ b/src/toolbarmenu.cpp @@ -1,6 +1,8 @@ #include "toolbarmenu.h" #include "mainwindow.h" #include "sharetoolbar.h" +#include "videodefinition.h" +#include "yt3.h" ToolbarMenu::ToolbarMenu(QWidget *parent) : QMenu(parent) { MainWindow *w = MainWindow::instance(); @@ -22,16 +24,46 @@ ToolbarMenu::ToolbarMenu(QWidget *parent) : QMenu(parent) { widgetAction->setDefaultWidget(shareToolbar); addAction(widgetAction); addSeparator(); + addAction(w->getAction("compactView")); addAction(w->getAction("ontop")); addSeparator(); + + QToolBar *definitionToolbar = new QToolBar(); + definitionToolbar->setStyleSheet("QToolButton { padding: 0}"); + definitionToolbar->setToolButtonStyle(Qt::ToolButtonTextOnly); + definitionToolbar->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + QActionGroup *definitionGroup = new QActionGroup(this); + const VideoDefinition &preferredDefinition = YT3::instance().maxVideoDefinition(); + int counter = 0; + for (auto defName : VideoDefinition::getDefinitionNames()) { + QAction *a = new QAction(defName); + a->setCheckable(true); + a->setChecked(preferredDefinition.getName() == defName); + connect(a, &QAction::triggered, this, [this, defName] { + MainWindow::instance()->setDefinitionMode(defName); + close(); + }); + definitionGroup->addAction(a); + definitionToolbar->addAction(a); + if (counter == 0) { + QWidget *w = definitionToolbar->widgetForAction(a); + w->setProperty("first", true); + counter++; + } + } + QWidgetAction *definitionAction = new QWidgetAction(this); + definitionAction->setDefaultWidget(definitionToolbar); + addAction(definitionAction); + addSeparator(); + addAction(w->getAction("clearRecentKeywords")); #ifndef APP_MAC addSeparator(); addAction(w->getAction("toggleMenu")); -#endif addSeparator(); addMenu(w->getMenu("help")); +#endif } void ToolbarMenu::showEvent(QShowEvent *e) { @@ -40,9 +72,10 @@ void ToolbarMenu::showEvent(QShowEvent *e) { QStyleOptionMenuItem option; initStyleOption(&option, a); int leftMargin = option.maxIconWidth; -#ifndef APP_MAC - // On Win & Linux the value is wrong +#ifdef APP_WIN leftMargin *= 1.5; #endif + setStyleSheet("QToolBar > QToolButton[first] {margin-left:" + QString::number(leftMargin) + + "px}"); emit leftMarginChanged(leftMargin); } diff --git a/src/toolbarmenu.h b/src/toolbarmenu.h index ed76ba8..fa8ddb0 100644 --- a/src/toolbarmenu.h +++ b/src/toolbarmenu.h @@ -7,7 +7,7 @@ class ToolbarMenu : public QMenu { Q_OBJECT public: - ToolbarMenu(QWidget *parent = 0); + ToolbarMenu(QWidget *parent = nullptr); signals: void leftMarginChanged(int value); diff --git a/src/video.cpp b/src/video.cpp index 52432b8..05d9761 100644 --- a/src/video.cpp +++ b/src/video.cpp @@ -23,12 +23,13 @@ $END_LICENSE */ #include "http.h" #include "httputils.h" #include "jsfunctions.h" +#include "playlistitemdelegate.h" #include "videodefinition.h" #include "ytvideo.h" Video::Video() : duration(0), viewCount(-1), license(LicenseYouTube), definitionCode(0), - loadingThumbnail(false), ytVideo(0) {} + loadingThumbnail(false), ytVideo(nullptr) {} Video::~Video() { qDebug() << "Deleting" << id; @@ -50,6 +51,7 @@ Video *Video::clone() { clone->published = published; clone->formattedPublished = formattedPublished; clone->viewCount = viewCount; + clone->formattedViewCount = formattedViewCount; clone->id = id; clone->definitionCode = definitionCode; return clone; @@ -89,6 +91,11 @@ void Video::setDuration(int value) { formattedDuration = DataUtils::formatDuration(duration); } +void Video::setViewCount(int value) { + viewCount = value; + formattedViewCount = DataUtils::formatCount(viewCount); +} + void Video::setPublished(const QDateTime &value) { published = value; formattedPublished = DataUtils::formatDateTime(published); @@ -98,19 +105,20 @@ void Video::setThumbnail(const QByteArray &bytes) { qreal ratio = qApp->devicePixelRatio(); thumbnail.loadFromData(bytes); thumbnail.setDevicePixelRatio(ratio); - const int thumbWidth = 160 * ratio; + const int thumbWidth = PlaylistItemDelegate::thumbWidth * ratio; if (thumbnail.width() > thumbWidth) thumbnail = thumbnail.scaledToWidth(thumbWidth, Qt::SmoothTransformation); emit gotThumbnail(); loadingThumbnail = false; } -void Video::streamUrlLoaded(const QUrl &streamUrl) { +void Video::streamUrlLoaded(const QString &streamUrl, const QString &audioUrl) { + qDebug() << "Streams loaded"; definitionCode = ytVideo->getDefinitionCode(); this->streamUrl = streamUrl; - emit gotStreamUrl(this->streamUrl); - delete ytVideo; - ytVideo = 0; + emit gotStreamUrl(streamUrl, audioUrl); + ytVideo->deleteLater(); + ytVideo = nullptr; } void Video::loadStreamUrl() { @@ -120,7 +128,18 @@ void Video::loadStreamUrl() { } ytVideo = new YTVideo(id, this); connect(ytVideo, &YTVideo::gotStreamUrl, this, &Video::streamUrlLoaded); - connect(ytVideo, &YTVideo::errorStreamUrl, this, &Video::errorStreamUrl); - connect(ytVideo, &YTVideo::errorStreamUrl, ytVideo, &QObject::deleteLater); + connect(ytVideo, &YTVideo::errorStreamUrl, this, [this](const QString &msg) { + emit errorStreamUrl(msg); + ytVideo->deleteLater(); + ytVideo = nullptr; + }); ytVideo->loadStreamUrl(); } + +void Video::abortLoadStreamUrl() { + if (ytVideo) { + ytVideo->disconnect(this); + ytVideo->deleteLater(); + ytVideo = nullptr; + } +} diff --git a/src/video.h b/src/video.h index e0c8e44..723be19 100644 --- a/src/video.h +++ b/src/video.h @@ -69,7 +69,8 @@ public: const QString &getFormattedDuration() const { return formattedDuration; } int getViewCount() const { return viewCount; } - void setViewCount(int value) { viewCount = value; } + void setViewCount(int value); + const QString &getFormattedViewCount() const { return formattedViewCount; } const QDateTime &getPublished() const { return published; } void setPublished(const QDateTime &value); @@ -78,7 +79,9 @@ public: int getDefinitionCode() const { return definitionCode; } void loadStreamUrl(); - const QUrl &getStreamUrl() { return streamUrl; } + const QString &getStreamUrl() { return streamUrl; } + bool isLoadingStreamUrl() const { return ytVideo != nullptr; } + void abortLoadStreamUrl(); const QString &getId() const { return id; } void setId(const QString &value) { id = value; } @@ -90,12 +93,12 @@ signals: void gotThumbnail(); void gotMediumThumbnail(const QByteArray &bytes); void gotLargeThumbnail(const QByteArray &bytes); - void gotStreamUrl(const QUrl &streamUrl); + void gotStreamUrl(const QString &videoUrl, const QString &audioUrl); void errorStreamUrl(const QString &message); private slots: void setThumbnail(const QByteArray &bytes); - void streamUrlLoaded(const QUrl &streamUrl); + void streamUrlLoaded(const QString &streamUrl, const QString &audioUrl); private: QString title; @@ -103,7 +106,7 @@ private: QString channelTitle; QString channelId; QString webpage; - QUrl streamUrl; + QString streamUrl; QPixmap thumbnail; QString thumbnailUrl; QString mediumThumbnailUrl; @@ -114,6 +117,7 @@ private: QDateTime published; QString formattedPublished; int viewCount; + QString formattedViewCount; License license; QString id; int definitionCode; diff --git a/src/videoarea.cpp b/src/videoarea.cpp new file mode 100644 index 0000000..1b496ca --- /dev/null +++ b/src/videoarea.cpp @@ -0,0 +1,147 @@ +/* $BEGIN_LICENSE + +This file is part of Minitube. +Copyright 2009, Flavio Tordini + +Minitube is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Minitube is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Minitube. If not, see . + +$END_LICENSE */ + +#include "videoarea.h" +#include "loadingwidget.h" +#include "mainwindow.h" +#include "playlistmodel.h" +#include "video.h" +#include "videomimedata.h" +#ifdef Q_OS_MAC +#include "macutils.h" +#endif +#include "fontutils.h" +#include "snapshotpreview.h" + +namespace { + +class PickMessage : public QWidget { +public: + PickMessage(QWidget *parent = nullptr) : QWidget(parent) { + setAutoFillBackground(true); + + QBoxLayout *l = new QHBoxLayout(this); + l->setMargin(32); + l->setSpacing(32); + l->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); + + QLabel *arrowLabel = new QLabel("←"); + arrowLabel->setFont(FontUtils::light(64)); + l->addWidget(arrowLabel); + + QLabel *msgLabel = new QLabel(tr("Pick a video")); + msgLabel->setFont(FontUtils::light(32)); + l->addWidget(msgLabel); + } +}; +} // namespace + +VideoArea::VideoArea(QWidget *parent) + : QWidget(parent), videoWidget(nullptr), messageWidget(nullptr) { + setAttribute(Qt::WA_OpaquePaintEvent); + + QBoxLayout *layout = new QVBoxLayout(this); + layout->setMargin(0); + layout->setSpacing(0); + + stackedLayout = new QStackedLayout(); + layout->addLayout(stackedLayout); + +#ifdef APP_SNAPSHOT + snapshotPreview = new SnapshotPreview(); + connect(stackedLayout, SIGNAL(currentChanged(int)), snapshotPreview, SLOT(hide())); +#endif + + setAcceptDrops(true); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + + setContextMenuPolicy(Qt::CustomContextMenu); + connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), + SLOT(showContextMenu(const QPoint &))); +} + +void VideoArea::setVideoWidget(QWidget *videoWidget) { + this->videoWidget = videoWidget; + stackedLayout->addWidget(videoWidget); +} + +void VideoArea::setLoadingWidget(LoadingWidget *loadingWidget) { + this->loadingWidget = loadingWidget; + stackedLayout->addWidget(loadingWidget); + stackedLayout->setCurrentWidget(loadingWidget); +} + +void VideoArea::showVideo() { + if (videoWidget) stackedLayout->setCurrentWidget(videoWidget); + loadingWidget->clear(); +} + +void VideoArea::showPickMessage() { + if (!messageWidget) { + messageWidget = new PickMessage(); + stackedLayout->addWidget(messageWidget); + } + stackedLayout->setCurrentWidget(messageWidget); +} + +void VideoArea::showLoading(Video *video) { + loadingWidget->setVideo(video); + stackedLayout->setCurrentWidget(loadingWidget); +} + +#ifdef APP_SNAPSHOT +void VideoArea::showSnapshotPreview(const QPixmap &pixmap) { + bool soundOnly = MainWindow::instance()->isReallyFullScreen(); + snapshotPreview->start(videoWidget, pixmap, soundOnly); +} +#endif + +void VideoArea::clear() { + loadingWidget->clear(); + stackedLayout->setCurrentWidget(loadingWidget); +} + +void VideoArea::mouseDoubleClickEvent(QMouseEvent *event) { + if (event->button() == Qt::LeftButton) emit doubleClicked(); +} + +void VideoArea::dragEnterEvent(QDragEnterEvent *event) { + // qDebug() << event->mimeData()->formats(); + if (event->mimeData()->hasFormat("application/x-minitube-video")) { + event->acceptProposedAction(); + } +} + +void VideoArea::dropEvent(QDropEvent *event) { + const VideoMimeData *videoMimeData = qobject_cast(event->mimeData()); + if (!videoMimeData) return; + + QVector