diff --git a/build/flatpak/org.pitivi.Pitivi.json b/build/flatpak/org.pitivi.Pitivi.json index f1b33c7a7ce17c9664813440f4fe501a5e9f7cda..1094522a60c8aebbc45e63486a58c560fc321951 100644 --- a/build/flatpak/org.pitivi.Pitivi.json +++ b/build/flatpak/org.pitivi.Pitivi.json @@ -19,8 +19,13 @@ ], "sdk": "org.gnome.Sdk", "copy-icon": true, + "sdk-extensions": [ + "org.freedesktop.Sdk.Extension.rust-stable" + ], "build-options": { + "append-path": "/usr/lib/sdk/rust-stable/bin", "env": { + "CARGO_HOME": "/run/build/cargo-c/cargo", "PYTHON": "python3", "GST_PLUGIN_SYSTEM_PATH": "/app/lib/gstreamer-1.0/", "FREI0R_PATH": "/app/lib/frei0r-1/" @@ -36,6 +41,7 @@ "python3-matplotlib.json", "libcanberra/libcanberra.json", "python3-librosa.json", + "wpe/wpe.json", { "name": "gsound", "buildsystem": "meson", @@ -387,6 +393,7 @@ "-Dgst-plugins-good:doc=disabled", "-Dgst-plugins-good:dv=enabled", "-Dgst-plugins-ugly:doc=disabled", + "-Dgst-plugins-bad:wpe=enabled", "-Dgst-plugins-ugly:x264=enabled", "-Dgst-python:pygi-overrides-dir=/app/lib/python3.10/site-packages/gi/overrides/", "-Dgstreamer-vaapi:doc=disabled", @@ -419,6 +426,40 @@ } ] }, + { + "name": "cargo-c", + "buildsystem": "simple", + "build-commands": [ + "cargo install cargo-c --root /app" + ], + "build-options": { + "build-args": [ + "--share=network" + ] + }, + "cleanup": [ + "*" + ] + }, + { + "name": "gst-plugins-rs", + "buildsystem": "simple", + "sources": [ + { + "type": "git", + "url": "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs", + "branch": "0.10" + } + ], + "build-options": { + "build-args": [ + "--share=network" + ] + }, + "build-commands": [ + "cargo cinstall -p gst-plugin-gtk4 --prefix=/app" + ] + }, { "name": "pitivi", "buildsystem": "meson", diff --git a/build/flatpak/pitivi-flatpak b/build/flatpak/pitivi-flatpak index e27aa7ad8864268ad1ecf542315639611103845e..f0f4fa3780cfd0690b562b4e554c7d4775106c1a 100755 --- a/build/flatpak/pitivi-flatpak +++ b/build/flatpak/pitivi-flatpak @@ -120,6 +120,9 @@ def expand_manifest(manifest_path, outfile, basedir, gst_version, branchname): # This is the name of a file to be included containing modules. continue + if "sources" not in module: + continue + if module["sources"][0]["type"] != "git": continue diff --git a/build/flatpak/wpe/0001-Fix-ENABLE-WEB_AUDIO-build-after-262451-main.patch b/build/flatpak/wpe/0001-Fix-ENABLE-WEB_AUDIO-build-after-262451-main.patch new file mode 100644 index 0000000000000000000000000000000000000000..9b892aca9f8e435693d271f24def159c9d10813e --- /dev/null +++ b/build/flatpak/wpe/0001-Fix-ENABLE-WEB_AUDIO-build-after-262451-main.patch @@ -0,0 +1,34 @@ +From b36decf27ea91f9ed21a4b46524d6c6781076d5c Mon Sep 17 00:00:00 2001 +From: Don Olmstead +Date: Fri, 31 Mar 2023 19:46:07 -0700 +Subject: [PATCH] Fix !ENABLE(WEB_AUDIO) build after 262451@main + https://bugs.webkit.org/show_bug.cgi?id=254852 + +Unreviewed build fix. + +Add !ENABLE(WEB_AUDIO) guard. + +* Source/WebCore/page/MemoryRelease.cpp: + +Canonical link: https://commits.webkit.org/262461@main +--- + Source/WebCore/page/MemoryRelease.cpp | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/Source/WebCore/page/MemoryRelease.cpp b/Source/WebCore/page/MemoryRelease.cpp +index a4f208c17e64..438c2cb00cf2 100644 +--- a/Source/WebCore/page/MemoryRelease.cpp ++++ b/Source/WebCore/page/MemoryRelease.cpp +@@ -103,7 +103,9 @@ static void releaseCriticalMemory(Synchronous synchronous, MaintainBackForwardCa + } + + CSSValuePool::singleton().drain(); ++#if ENABLE(WEB_AUDIO) + HRTFElevation::clearCache(); ++#endif + + Page::forEachPage([](auto& page) { + page.cookieJar().clearCache(); +-- +2.34.1 + diff --git a/build/flatpak/wpe/wpe.json b/build/flatpak/wpe/wpe.json new file mode 100644 index 0000000000000000000000000000000000000000..4d58007caa8e4ef1067ceaca3e28c74b26d0d867 --- /dev/null +++ b/build/flatpak/wpe/wpe.json @@ -0,0 +1,98 @@ +{ + "name": "wpe", + "buildsystem": "simple", + "build-commands": [], + "modules": [ + { + "name": "unifdef", + "buildsystem": "simple", + "build-commands": [ + "make prefix=/app install" + ], + "sources": [ + { + "type": "archive", + "url": "https://dotat.at/prog/unifdef/unifdef-2.12.tar.gz", + "sha256": "fba564a24db7b97ebe9329713ac970627b902e5e9e8b14e19e024eb6e278d10b" + } + ] + }, + { + "name": "libyuv", + "buildsystem": "cmake", + "config-opts": [ + "-DCMAKE_INSTALL_PREFIX=/app", + "-DCMAKE_INSTALL_LIBDIR=lib" + ], + "sources": [ + { + "type": "archive", + "url": "https://github.com/lemenkov/libyuv/archive/master.tar.gz", + "sha256" : "599aed0edb5e95a582f6fe5cd4c11ff21a3dd656352601488fa8bc4cdb27d38d" + } + ] + }, + { + "name": "libavif", + "buildsystem": "cmake", + "config-opts": [ + "-DCMAKE_INSTALL_PREFIX=/app", + "-DCMAKE_INSTALL_LIBDIR=lib" + ], + "sources": [ + { + "type": "archive", + "url": "https://github.com/AOMediaCodec/libavif/archive/refs/tags/v0.9.2.tar.gz", + "sha256" : "d6607d654adc40a392da83daa72a4ff802cd750c045a68131c9305639c10fc5c" + } + ] + }, + { + "name": "libsoup", + "buildsystem": "meson", + "sources": [ + { + "type": "archive", + "url": "https://download.gnome.org/sources/libsoup/2.72/libsoup-2.72.0.tar.xz", + "sha256" : "170c3f8446b0f65f8e4b93603349172b1085fb8917c181d10962f02bb85f5387" + } + ] + }, + { + "name": "wpe-webkit", + "buildsystem": "cmake-ninja", + "config-opts": [ + "-DPORT=WPE", + "-DENABLE_UNIFIED_BUILDS=NO", + "-DCMAKE_BUILD_TYPE=Release", + "-DENABLE_API_TESTS=OFF", + "-DUSE_SOUP2=ON", + "-DENABLE_DOCUMENTATION=NO", + "-DENABLE_INTROSPECTION=NO", + "-DENABLE_BUBBLEWRAP_SANDBOX=NO", + "-DENABLE_MINIBROWSER=OFF", + "-DENABLE_TOOLS=OFF", + "-DENABLE_VIDEO=OFF", + "-DENABLE_WEB_AUDIO=OFF", + "-DDEBUG_FISSION=ON", + "-DCMAKE_INSTALL_PREFIX=/app", + "-DCMAKE_INSTALL_LIBDIR=lib" + ], + "sources": [ + { + "type": "archive", + "url": "https://wpewebkit.org/releases/wpewebkit-2.40.1.tar.xz", + "sha256": "c6b25e168b70f2121305ed078d0790e0aa4b0c73fce44e32ed42d4e5dd137ccb" + }, + { + "type": "patch", + "path": "0001-Fix-ENABLE-WEB_AUDIO-build-after-262451-main.patch" + } + ], + "env": { + "CC": "/usr/bin/cc", + "CXX": "/usr/bin/c++" + } + } + ] +} diff --git a/data/closing_credits/template.txt b/data/closing_credits/template.txt new file mode 100644 index 0000000000000000000000000000000000000000..34ef01a236a6ea1d61a60e970fe3d4d8e0ea63e8 --- /dev/null +++ b/data/closing_credits/template.txt @@ -0,0 +1,45 @@ + + + + + + + +Closing Credit Clip + + +
+
+ {content} +
+
+ + diff --git a/data/ui/alignmentprogress.ui b/data/ui/alignmentprogress.ui index 12de80929d5d601cb9536d60ec9e8a35a3104235..dff8eb7955f3f27ad60c192a00a73f2aa0736b2f 100644 --- a/data/ui/alignmentprogress.ui +++ b/data/ui/alignmentprogress.ui @@ -4,12 +4,14 @@ False - 12 + 12 + 12 + 12 + 12 Auto-Alignment Starting True center-on-parent 400 - dialog False @@ -22,27 +24,19 @@ False 0 12 + fill <b><big>Performing Auto-Alignment</big></b> True - - False - True - 0 - True False + fill Estimating... True - - False - True - 1 - diff --git a/data/ui/beatdetection.ui b/data/ui/beatdetection.ui index a735e45359ac6a6ac52b344060763019f78441ec..6bf129a9d5e237e879c5ad27ef7ba8988ead00cd 100644 --- a/data/ui/beatdetection.ui +++ b/data/ui/beatdetection.ui @@ -13,70 +13,45 @@ True False 5 + fill Detect beats True True True + fill - - False - True - 0 - True True True + fill Clear detected beats + edit-clear-all-symbolic - - - True - False - edit-clear-all-symbolic - - - - False - True - 1 - - - False - True - 0 - True False + True + fill True False + fill Detecting... - - False - True - 0 - - - True - True - 1 - diff --git a/data/ui/clipcolor.ui b/data/ui/clipcolor.ui index 0d7f82a13e5878fef3a1b7c476cb75bf02272f1b..10574ce3bdd60d812b1ec1beaf7b60e54003c463 100644 --- a/data/ui/clipcolor.ui +++ b/data/ui/clipcolor.ui @@ -6,27 +6,22 @@ True False start - 12 - 12 - 12 - 12 + 12 + 12 + 12 + 12 5 - False True True True True Pick the clip color rgb(255,255,255) + fill - - False - True - 0 - diff --git a/data/ui/clipcompositing.ui b/data/ui/clipcompositing.ui index e08850e4959e514185a4551c53278712c95461a2..abc40978796fdd9e10fb980581ef24384980e9b3 100644 --- a/data/ui/clipcompositing.ui +++ b/data/ui/clipcompositing.ui @@ -1,7 +1,6 @@ - - + 1 1 @@ -14,192 +13,162 @@ 5 - - True - False - edit-clear-all-symbolic - - - True - False - edit-clear-all-symbolic - - - True - False start start - 10 + 10 + 10 + 10 + 10 10 6 - True - False end - True - Fade-in: + 1 + Fade-in: 0 + + 0 + 0 + - - 0 - 0 - - True - False - seconds + seconds + + 2 + 0 + - - 2 - 0 - - True - True + 1 fade_in_adjustment 0.10 2 + + 1 + 0 + - - 1 - 0 - + 1 150 - True - True + 1 1 - True + 1 fade_in_adjustment 1 - False left + + 3 + 0 + - - 3 - 0 - - True - False end - True - Fade-out: + 1 + Fade-out: 0 + + 0 + 1 + - - 0 - 1 - - True - True + 1 fade_out_adjustment 0.10 2 + + 1 + 1 + - - 1 - 1 - - True - False - seconds + seconds + + 2 + 1 + - - 2 - 1 - + 1 150 - True - True - True + 1 + 1 fade_out_adjustment 1 - False + + 3 + 1 + - - 3 - 1 - - True - True - False - True - Reset fade-in - icon_reset1 - none + 1 + 0 + 1 + Reset fade-in + edit-clear-all-symbolic + 0 + + 4 + 0 + - - 4 - 0 - - True - True - False - True - Reset fade-out - icon_reset2 - none + 1 + 0 + 1 + Reset fade-out + edit-clear-all-symbolic + 0 + + 4 + 1 + - - 4 - 1 - - True - False end - Blending: + Blending: + + 0 + 2 + - - 0 - 2 - - True - False start + + 1 + 2 + - - 1 - 2 - 3 - - diff --git a/data/ui/clipmediaprops.ui b/data/ui/clipmediaprops.ui index 3db1de50b1f5b63118af8bdac070a893b60f3de9..44bfe6ba2fa562148bc2e915c02b6b0c343993e2 100644 --- a/data/ui/clipmediaprops.ui +++ b/data/ui/clipmediaprops.ui @@ -4,188 +4,126 @@ False - 5 + 5 + 5 + 5 + 5 Clip Properties - dialog True False vertical 12 - - - True - False - end - - - _Cancel - True - True - True - True - - - False - False - 0 - - - - - _Apply to project - True - True - True - True - - - False - False - 1 - - - - - False - True - end - 1 - - True False vertical + fill True False - 0 - none + True + fill - + True False + True - + True False - True + start - + True False - start - - - True - False - - - False - True - 0 - - - - - True - False - x - - - False - True - 1 - - - - - True - False - - - False - True - 2 - - - - - 1 - 0 - - - - - Size (pixels) - True - True - False - start - 0 - True - - - 0 - 0 - - - - - Frame rate - True - True - False - start - 0 - True - - - 0 - 1 - - - - - Pixel aspect ratio - True - True - False - start - 0 - True + fill - - 0 - 2 - - + True False - start + x + fill - - 1 - 2 - - + True False - start + fill - - 1 - 1 - + + 1 + 0 + + + + + + Size (pixels) + True + True + False + start + + 0 + 0 + + + + + + Frame rate + True + True + False + start + + 0 + 1 + + + + + + Pixel aspect ratio + True + True + False + start + + 0 + 2 + + + + + + True + False + start + + 1 + 2 + + + + + + True + False + start + + 1 + 1 + @@ -201,79 +139,64 @@ - - True - True - 0 - True False - 0 - none + True + fill - + True False + True - + True False - True - - - True - False - start - - - 1 - 0 - - - - - True - False - start - - - 1 - 1 - - - - - Sample rate - True - True - False - start - 0 - True - - - 0 - 1 - - - - - Channels - True - True - False - start - 0.5 - True - - - 0 - 0 - - + start + + 1 + 0 + + + + + + True + False + start + + 1 + 1 + + + + + + Sample rate + True + True + False + start + + 0 + 1 + + + + + + Channels + True + True + False + start + + 0 + 0 + @@ -289,18 +212,33 @@ - - True - True - 1 - - - False - True - 2 - + + + + True + False + fill + + + _Cancel + True + True + True + True + + + + + _Apply to project + True + True + True + True + + + diff --git a/data/ui/cliptransformation.ui b/data/ui/cliptransformation.ui index 0b48498b9741f69652eb207758b541d00c100b87..86bc522e7624dc57d63128020afb779c8f85cc07 100644 --- a/data/ui/cliptransformation.ui +++ b/data/ui/cliptransformation.ui @@ -2,23 +2,12 @@ - 1 9999999999 1 10 - - True - False - chain-connected-symbolic - - - True - False - edit-clear-all-symbolic - -9999999999 9999999999 @@ -55,41 +44,37 @@ end Y: 0 + + 0 + 1 + - - 0 - 1 - True True end - - 1 position_x_adjustment True + + 1 + 0 + - - 1 - 0 - True True end - - 1 position_y_adjustment True + + 1 + 1 + - - 1 - 1 - @@ -98,40 +83,37 @@ end Height: 0 + + 0 + 3 + - - 0 - 3 - True True end - - digits width_adjustment True + + 1 + 2 + - - 1 - 2 - True True end - height_adjustment True + + 1 + 3 + - - 1 - 3 - @@ -140,14 +122,14 @@ True Constrain Proportions True - aspect_ratio_image - none + chain-connected-symbolic + False + + 2 + 2 + 2 + - - 2 - 2 - 2 - @@ -156,18 +138,18 @@ end X: 0 + + 0 + 0 + - - 0 - 0 - - + True False center - expand + < @@ -176,13 +158,10 @@ False True Previous keyframe - none + False + True + fill - - True - True - 0 - @@ -192,13 +171,10 @@ False True Next keyframe - none + False + True + fill - - True - True - 1 - @@ -207,13 +183,10 @@ True False True - none + False + True + fill - - True - True - 2 - @@ -222,21 +195,18 @@ False True Reset to default values - icon_reset1 - none + edit-clear-all-symbolic + False + True + fill - - True - True - 3 - + + 0 + 4 + 5 + - - 0 - 4 - 5 - @@ -245,11 +215,11 @@ end Width: 0 + + 0 + 2 + - - 0 - 2 - @@ -263,12 +233,12 @@ + + 4 + 0 + 4 + - - 4 - 0 - 4 - diff --git a/data/ui/closingcreditsclipdialog.ui b/data/ui/closingcreditsclipdialog.ui new file mode 100644 index 0000000000000000000000000000000000000000..8b57f5647b2ce88a2f75142a819beab168afab0d --- /dev/null +++ b/data/ui/closingcreditsclipdialog.ui @@ -0,0 +1,86 @@ + + + + + Please enter text + + + Special Clip Settings + + + + vertical + 12 + 5 + 5 + 5 + 5 + + + + + + + True + True + + + True + True + True + True + textbuffer1 + + + + + + + + 0 + 0 + 2 + + + + + + + + true + + + + + + + + + button8 + ok_button + + + + 5 + 5 + 5 + 5 + + + Cancel + 1 + 1 + 5 + + + + + OK + 1 + 1 + + + + + + diff --git a/data/ui/customwidgets/alpha.ui b/data/ui/customwidgets/alpha.ui index c261d1fa470807c6366023b92866fbb8c13753cb..2b8d777ffa8f1b5d852835b9acbc2e9dc6839a65 100644 --- a/data/ui/customwidgets/alpha.ui +++ b/data/ui/customwidgets/alpha.ui @@ -31,31 +31,6 @@ 1 10 - - True - False - edit-clear-all-symbolic - - - True - False - edit-clear-all-symbolic - - - True - False - edit-clear-all-symbolic - - - True - False - edit-clear-all-symbolic - - - True - False - edit-clear-all-symbolic - 64 @@ -77,7 +52,10 @@ True False - 12 + 12 + 12 + 12 + 12 8 6 @@ -85,56 +63,56 @@ True True True - image1 - none + edit-clear-all-symbolic + False top + + 4 + 2 + - - 4 - 2 - True True True - image2 - none + edit-clear-all-symbolic + False top + + 4 + 3 + - - 4 - 3 - True True True - image3 - none + edit-clear-all-symbolic + False top + + 4 + 4 + - - 4 - 4 - True True True - image4 - none + edit-clear-all-symbolic + False top + + 4 + 5 + - - 4 - 5 - @@ -142,11 +120,11 @@ False Angle: 0 + + 0 + 2 + - - 0 - 2 - @@ -154,11 +132,11 @@ False Noise level: 0 + + 0 + 3 + - - 0 - 3 - @@ -166,11 +144,11 @@ False White sensitivity: 0 + + 0 + 4 + - - 0 - 4 - @@ -178,11 +156,11 @@ False Black sensitivity: 0 + + 0 + 5 + - - 0 - 5 - @@ -193,12 +171,13 @@ 1 2 left + True + + 1 + 2 + 2 + - - 1 - 2 - 2 - @@ -209,12 +188,13 @@ 1 2 left + True + + 1 + 3 + 2 + - - 1 - 3 - 2 - @@ -226,12 +206,13 @@ 2 0 left + True + + 1 + 4 + 2 + - - 1 - 4 - 2 - @@ -243,12 +224,13 @@ 2 0 left + True + + 1 + 5 + 2 + - - 1 - 5 - 2 - @@ -256,13 +238,13 @@ True True True - none + False top + + 3 + 2 + - - 3 - 2 - @@ -270,12 +252,12 @@ True True True - none + False + + 3 + 3 + - - 3 - 3 - @@ -283,12 +265,12 @@ True True True - none + False + + 3 + 4 + - - 3 - 4 - @@ -296,23 +278,23 @@ True True True - none + False + + 3 + 5 + - - 3 - 5 - True False + + 1 + 1 + 2 + - - 1 - 1 - 2 - @@ -320,11 +302,11 @@ False Alpha: 0 + + 0 + 0 + - - 0 - 0 - @@ -336,12 +318,13 @@ 1 2 left + True + + 1 + 0 + 2 + - - 1 - 0 - 2 - @@ -349,44 +332,44 @@ False Target chroma key: 0 + + 0 + 6 + - - 0 - 6 - True False Red + + 0 + 8 + - - 0 - 8 - True False Green + + 1 + 8 + - - 1 - 8 - True True blue_color_adjustment + + 2 + 7 + - - 2 - 7 - @@ -394,73 +377,71 @@ True Select a color rgb(0,255,0) + + 3 + 7 + - - 3 - 7 - True False Blue + + 2 + 8 + - - 2 - 8 - True True green_color_adjustment + + 1 + 7 + - - 1 - 7 - True True red_color_adjustment + + 0 + 7 + - - 0 - 7 - True True True - image5 - none + edit-clear-all-symbolic + False top + + 4 + 0 + - - 4 - 0 - True False - 0 - none + + 4 + 7 + - - 4 - 7 - diff --git a/data/ui/customwidgets/frei0r-filter-3-point-color-balance.ui b/data/ui/customwidgets/frei0r-filter-3-point-color-balance.ui index a694d62090a01308d308233ebc295434d9a5ecaf..e2c27d31d7ca8a29117e5ef98f8250e5fd88a9f6 100644 --- a/data/ui/customwidgets/frei0r-filter-3-point-color-balance.ui +++ b/data/ui/customwidgets/frei0r-filter-3-point-color-balance.ui @@ -48,25 +48,13 @@ 10 - - True - False - edit-clear-all-symbolic - - - True - False - edit-clear-all-symbolic - - - True - False - edit-clear-all-symbolic - True False - 8 + 8 + 8 + 8 + 8 8 8 @@ -80,39 +68,39 @@ True True adjustment7 + + 0 + 0 + - - 0 - 0 - True True adjustment8 + + 0 + 1 + - - 0 - 1 - True True adjustment9 + + 0 + 2 + - - 0 - 2 - + + 1 + 3 + - - 1 - 3 - @@ -124,39 +112,39 @@ True True adjustment4 + + 0 + 0 + - - 0 - 0 - True True adjustment5 + + 0 + 1 + - - 0 - 1 - True True adjustment6 + + 0 + 2 + - - 0 - 2 - + + 2 + 3 + - - 2 - 3 - @@ -168,50 +156,50 @@ True True adjustment1 + + 0 + 0 + - - 0 - 0 - True True adjustment2 + + 0 + 1 + - - 0 - 1 - True True adjustment3 + + 0 + 2 + - - 0 - 2 - + + 3 + 3 + - - 3 - 3 - True False + + 1 + 4 + 3 + - - 1 - 4 - 3 - @@ -224,12 +212,8 @@ False R center + fill - - False - True - 0 - @@ -237,12 +221,8 @@ False G center + fill - - False - True - 1 - @@ -250,18 +230,14 @@ False B center + fill - - False - True - 2 - + + 0 + 3 + - - 0 - 3 - @@ -275,43 +251,39 @@ True False - 0 - none + + 0 + 0 + - - 0 - 0 - True False - 0 - none True True True - image2 + edit-clear-all-symbolic top + + 1 + 0 + - - 1 - 0 - + + 1 + 2 + - - 1 - 2 - @@ -325,43 +297,39 @@ True False - 0 - none + + 0 + 0 + - - 0 - 0 - True False - 0 - none True True True - image1 + edit-clear-all-symbolic top + + 1 + 0 + - - 1 - 0 - + + 2 + 2 + - - 2 - 2 - @@ -375,76 +343,72 @@ True False - 0 - none + + 0 + 0 + - - 0 - 0 - True False - 0 - none True True True - image3 + edit-clear-all-symbolic top + + 1 + 0 + - - 1 - 0 - + + 3 + 2 + - - 3 - 2 - True False Shadows + + 1 + 0 + - - 1 - 0 - True False Midtones + + 2 + 0 + - - 2 - 0 - True False Highlights + + 3 + 0 + - - 3 - 0 - @@ -454,11 +418,11 @@ True True + + 0 + 0 + - - 0 - 0 - @@ -466,11 +430,11 @@ True True 4 + + 0 + 1 + - - 0 - 1 - @@ -479,11 +443,11 @@ start 19 Split preview + + 1 + 0 + - - 1 - 0 - @@ -493,18 +457,18 @@ 19 Source image on left 0.5 + + 1 + 1 + - - 1 - 1 - + + 1 + 6 + 3 + - - 1 - 6 - 3 - diff --git a/data/ui/customwidgets/frei0r-filter-alphaspot.ui b/data/ui/customwidgets/frei0r-filter-alphaspot.ui index 8721ad4c717a2bc8a9ba851a829269de1bd863ec..3bf5b0f58854129c873fa1c8b56747c9df5abdb6 100644 --- a/data/ui/customwidgets/frei0r-filter-alphaspot.ui +++ b/data/ui/customwidgets/frei0r-filter-alphaspot.ui @@ -50,50 +50,13 @@ 0.01 0.10000000000000001 - - True - False - edit-clear-all-symbolic - - - True - False - edit-clear-all-symbolic - - - True - False - edit-clear-all-symbolic - - - True - False - edit-clear-all-symbolic - - - True - False - edit-clear-all-symbolic - - - True - False - edit-clear-all-symbolic - - - True - False - edit-clear-all-symbolic - - - True - False - edit-clear-all-symbolic - True False - 12 + 12 + 12 + 12 + 12 8 6 @@ -101,56 +64,56 @@ True True True - image1 - none + edit-clear-all-symbolic + False top + + 4 + 3 + - - 4 - 3 - True True True - image2 - none + edit-clear-all-symbolic + False top + + 4 + 4 + - - 4 - 4 - True True True - image3 - none + edit-clear-all-symbolic + False top + + 4 + 5 + - - 4 - 5 - True True True - image4 - none + edit-clear-all-symbolic + False top + + 4 + 6 + - - 4 - 6 - @@ -162,12 +125,13 @@ 2 2 left + True + + 1 + 3 + 2 + - - 1 - 3 - 2 - @@ -179,12 +143,13 @@ 2 2 left + True + + 1 + 4 + 2 + - - 1 - 4 - 2 - @@ -196,12 +161,13 @@ 2 2 left + True + + 1 + 5 + 2 + - - 1 - 5 - 2 - @@ -213,12 +179,13 @@ 2 2 left + True + + 1 + 6 + 2 + - - 1 - 6 - 2 - @@ -226,13 +193,13 @@ True True True - none + False top + + 3 + 3 + - - 3 - 3 - @@ -240,12 +207,12 @@ True True True - none + False + + 3 + 4 + - - 3 - 4 - @@ -253,12 +220,12 @@ True True True - none + False + + 3 + 5 + - - 3 - 5 - @@ -266,156 +233,156 @@ True True True - none + False + + 3 + 6 + - - 3 - 6 - True False 328 + + 1 + 2 + 2 + - - 1 - 2 - 2 - True False + + 1 + 0 + 2 + - - 1 - 0 - 2 - True False Shape: + + 0 + 0 + - - 0 - 0 - True False x + + 0 + 3 + - - 0 - 3 - True False y + + 0 + 4 + - - 0 - 4 - True False width + + 0 + 5 + - - 0 - 5 - True False height + + 0 + 6 + - - 0 - 6 - True False Operation: + + 0 + 1 + - - 0 - 1 - True False + + 1 + 1 + 2 + - - 1 - 1 - 2 - True False tilt + + 0 + 7 + - - 0 - 7 - True False trans. width + + 0 + 8 + - - 0 - 8 - True False min alpha + + 0 + 9 + - - 0 - 9 - True False max alpha + + 0 + 10 + - - 0 - 10 - @@ -427,12 +394,13 @@ 2 2 left + True + + 1 + 7 + 2 + - - 1 - 7 - 2 - @@ -444,12 +412,13 @@ 2 2 left + True + + 1 + 8 + 2 + - - 1 - 8 - 2 - @@ -461,12 +430,13 @@ 2 2 left + True + + 1 + 9 + 2 + - - 1 - 9 - 2 - @@ -478,12 +448,13 @@ 2 2 left + True + + 1 + 10 + 2 + - - 1 - 10 - 2 - @@ -491,12 +462,12 @@ True True True - none + False + + 3 + 8 + - - 3 - 8 - @@ -504,12 +475,12 @@ True True True - none + False + + 3 + 7 + - - 3 - 7 - @@ -517,12 +488,12 @@ True True True - none + False + + 3 + 9 + - - 3 - 9 - @@ -530,68 +501,68 @@ True True True - none + False + + 3 + 10 + - - 3 - 10 - True True True - image6 - none + edit-clear-all-symbolic + False top + + 4 + 7 + - - 4 - 7 - True True True - image7 - none + edit-clear-all-symbolic + False top + + 4 + 8 + - - 4 - 8 - True True True - image9 - none + edit-clear-all-symbolic + False top + + 4 + 10 + - - 4 - 10 - True True True - image10 - none + edit-clear-all-symbolic + False top + + 4 + 9 + - - 4 - 9 - diff --git a/data/ui/customwidgets/pitivi:object_effect.ui b/data/ui/customwidgets/pitivi:object_effect.ui index 3da06b8780ed85047c9c2dc787ff27f37d3539b4..8e2aee69d32730fd84640e70924c742985e9c6d7 100644 --- a/data/ui/customwidgets/pitivi:object_effect.ui +++ b/data/ui/customwidgets/pitivi:object_effect.ui @@ -16,17 +16,12 @@ True False start + True Color: - - True - True - 0 - - False True True True @@ -38,11 +33,6 @@ rgb(255,255,255) - - False - True - 1 - diff --git a/data/ui/effectslibrary.ui b/data/ui/effectslibrary.ui index 4042145028f705391ab18f4833d02d150004b208..11dc04ae481133405ccd11a8d42e715ad537b298 100644 --- a/data/ui/effectslibrary.ui +++ b/data/ui/effectslibrary.ui @@ -1,55 +1,28 @@ - - - - True - False - False - 1 + + + - - True - False - - - Favorites - True - True - True - True - - - + + 0 + Favorites + 1 + 1 + - - False - True - - - True - False + 5 - - - True - True - True - False - edit-find-symbolic - edit-clear-symbolic - Search... - - - - + 1 + 1 + 1 + Search... + - - True - True - diff --git a/data/ui/elementsettingsdialog.ui b/data/ui/elementsettingsdialog.ui index 6e7a7e2d78d7882d237d2ed208c1317b99e68a72..cc45d14847a0e8790cf9c417a2973bf025e8d257 100644 --- a/data/ui/elementsettingsdialog.ui +++ b/data/ui/elementsettingsdialog.ui @@ -4,78 +4,59 @@ False - 12 Properties for <element> True - dialog True False vertical 12 + 12 + 12 + 12 + 12 - + True False - end + fill Reset all - False True True True + fill - - False - True - 0 - OK - False True True True False - - False - False - 1 - - - False - True - end - 0 - True True never - in + True + True + fill True False - none - - True - True - 1 - diff --git a/data/ui/filelisterrordialog.ui b/data/ui/filelisterrordialog.ui index 37c7e20b091cda6fa06dd8549f7251faa92e540e..d171747f410bb0af475a9669b0f64bea2c024043 100644 --- a/data/ui/filelisterrordialog.ui +++ b/data/ui/filelisterrordialog.ui @@ -5,8 +5,6 @@ True False - 12 - dialog @@ -15,50 +13,24 @@ False vertical 12 - - - True - False - end - - - OK - True - True - True - False - - - False - False - 0 - - - - - False - True - end - 1 - - + 12 + 12 + 12 + 12 True False 6 + fill True False dialog-warning 6 + fill - - False - True - 0 - @@ -67,19 +39,27 @@ 0 center True + True + fill + + + + + + + True + False + fill + + + OK + True + True + True + False - - True - True - 1 - - - False - True - 0 - @@ -88,6 +68,7 @@ True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + fill never @@ -114,11 +95,6 @@ - - False - True - 3 - diff --git a/data/ui/greeter.ui b/data/ui/greeter.ui index ba3819381ce7ee55ccda4ac4d0197acfd4bb4a35..ae8f0c7635f8172e8331cab6a0e8436a163f70ae 100644 --- a/data/ui/greeter.ui +++ b/data/ui/greeter.ui @@ -1,115 +1,65 @@ - - + + - True - False center 30 10 vertical - True - True + 1 center 32 35 - edit-find-symbolic - False - False - - False - True - 0 - recent_projects_labelbox - True - False recent_projects_label - True - False start - Recent Projects + Recent Projects - - False - True - 0 - - True - False end 12 - True - Updated + 1 + Updated - - False - True - 1 - - - False - True - 1 - recent_projects_listbox - True - False - - False - True - 2 - - True - False vertical - True - True + 1 never - True - + 1 + - True - False - none - + - True - False vertical - True - False - True - - - False - 6 - end + 1 + + + 0 + 16 @@ -120,16 +70,11 @@ - - False - False - 0 - - + - False - 16 + 0 + 6 @@ -140,114 +85,57 @@ - - False - False - 0 - - - False - True - 0 - - True - False vertical - - False - True - 1 - - + - + - - False - True - 0 - - True - False end - True + 1 - Remove - True - True - True + Remove + 1 + 1 - - 0 - - - False - True - 1 - - True - False center - True + 1 vertical - - - False - True - 0 - + diff --git a/data/ui/mainmenubutton.ui b/data/ui/mainmenubutton.ui index bc9869cab695864f62be94848e67967d13c9f87b..f6c2b28b2e591d22939a7cca7e129a6ed9c79e2f 100644 --- a/data/ui/mainmenubutton.ui +++ b/data/ui/mainmenubutton.ui @@ -2,240 +2,76 @@ - - False - - - True - False - 12 - 12 - 8 - 8 - vertical - - - True - True - False - Save the current project under a new name or a different location - 1 - 1 - editor.save-as - Save As... - - - False - True - 0 - - - - - True - True - False - Reload the current project - 1 - 1 - editor.revert-to-saved - Revert to saved version - - - False - True - 1 - - - - - True - True - False - Export the current project and all its media in a .tar archive - 1 - 1 - editor.export-project - Export as Archive... - - - False - True - 2 - - - - - True - False - 2 - 2 - - - False - True - 3 - - - - - True - True - False - Export the frame at the current playhead position as an image file. - 1 - 1 - editor.save-frame - Export current frame... - - - False - True - 4 - - - - - True - False - 2 - 2 - - - False - True - 5 - - - - - True - True - False - Edit the project settings - 1 - 1 - editor.project-settings - Project Settings - - - False - True - 6 - - - - - True - False - 2 - 2 - - - False - True - 7 - - - - - True - True - False - 1 - 1 - win.preferences - Preferences - - - False - True - 8 - - - - - True - True - False - 1 - 1 - app.shortcuts_window - Keyboard Shortcuts - - - False - True - 9 - - - - - True - True - False - 1 - 1 - win.help - User Manual - - - False - True - 10 - - - - - True - True - False - Start the intro to Pitivi for new users - 1 - 1 - editor.interactive-intro - Interactive Intro - - - False - True - 11 - - - - - True - True - False - 1 - 1 - win.about - About Pitivi - - - False - True - 12 - - - - - main - 1 - - - + +
+ + Save As... + editor.save-as + + + Revert to saved version + editor.revert-to-saved + + + Export as Archive... + editor.export-project + +
+
+ + Export current frame... + editor.save-frame + +
+
+ + Project Settings + editor.project-settings + +
+
+ + Preferences + win.preferences + + + Keyboard Shortcuts + app.shortcuts_window + + + User Manual + win.help + + + Interactive Intro + editor.interactive-intro + + + About Pitivi + win.about + +
+
+ +
+ + Keyboard Shortcuts + app.shortcuts_window + + + User Manual + win.help + + + About Pitivi + win.about + +
+
True True True - menu - - - True - False - open-menu-symbolic - - + open-menu-symbolic
diff --git a/data/ui/markerpopover.ui b/data/ui/markerpopover.ui index 31eccaef2f3754e935f4c19570c2ea70465ca09e..fe551a1a9d3290e9cf340346b63e09d3aee9fd14 100644 --- a/data/ui/markerpopover.ui +++ b/data/ui/markerpopover.ui @@ -22,44 +22,30 @@ True word False + fill
- - False - True - 0 -
- + True False True - start - gtk-remove + Remove True True True - True + True + fill - - False - True - 0 - - - True - True - 1 - diff --git a/data/ui/medialibrary.ui b/data/ui/medialibrary.ui index 896a59866eae4bf2b096cfd9f98099e31eb3d4e5..0e150670af31955b9ea303e3a4c7c7336749b6cb 100644 --- a/data/ui/medialibrary.ui +++ b/data/ui/medialibrary.ui @@ -1,296 +1,176 @@ - - + - True - False + center - - True - False + center center - False - 1 + - - True - False - Remove selected clips from the project + + Remove selected clips from the project medialibrary.remove-assets - _Remove from Project - True + 1 list-remove-symbolic - - - media_remove_button - - + + media_remove_button + - - False - True - - - True - False - Clip properties... - True + + Clip properties... + 1 document-properties-symbolic - - - media_props_button - - + + media_props_button + - - False - True - - - True - False - Show tags associated with selected clips - True + + Show tags associated with selected clips + 1 tag-symbolic - - - - tags_button - - + + + tags_button + - - False - True - - - True - False - Insert the selected clips at the end of the timeline + + Insert the selected clips at the end of the timeline medialibrary.insert-assets-at-end - Insert at _End of Timeline - True + 1 insert-object-symbolic - - - media_insert_button - - + + media_insert_button + - - False - True - - - False - True - 3 - - - True - False - False - 1 + + - - True - False - Add media files to your project - True - Import - True + + Add media files to your project + Import + 1 list-add-symbolic - - - media_import_button - - + + media_import_button + - - False - True - - - True - False - Show clips as a detailed list - True + + Show clips as a detailed list + 1 view-list-symbolic - - - media_listview_button - - + + media_listview_button + - - False - True - - - True - False - 5 - - - True - True - 3 - - starred-symbolic - edit-clear-symbolic - Select clips that have not been used in the project - Show all clips - Search... - - - - - media_search_entry - - - - - - - True - True - - - - - medialibrary_toolbar + + 1 + 3 + 1 + Search... + + + media_search_entry + + + medialibrary_toolbar + - True - True - False warning - True - - - False - 5 - 6 - end + 1 + + + 8 + 8 + 8 + 8 + 16 - - True - True - True + + 1 + 0 - - False - True - 0 - - - False - True - 1 - - - - False - 8 - 16 + + + 5 + 5 + 5 + 5 + 6 - - True - False - True - 0 + + 1 + 1 - - False - True - 0 - - - True - True - 0 - view_error_button - True - True - False other - - - False - 5 - vertical - 6 - end - - - - - - False - True - 1 - - - + - False - 8 + 8 + 8 + 8 + 8 16 - True - False - Add media to your project by dragging files and folders here or by using the "Import" button. - True + Add media to your project by dragging files and folders here or by using the "Import" button. + 1 0 + 1 - - False - True - 0 - - - True - True - 0 - + + + + 5 + 5 + 5 + 5 + vertical + 6 + + + + diff --git a/data/ui/pluginpreferencesrow.ui b/data/ui/pluginpreferencesrow.ui index 3c6371cb605e5418ebb623ea740996b03d8c235b..a6fd07c4a06f49c2991ccd92cce8d30d73a6ebfa 100644 --- a/data/ui/pluginpreferencesrow.ui +++ b/data/ui/pluginpreferencesrow.ui @@ -18,17 +18,15 @@ True False vertical + fill True False 0 + True + fill - - True - True - 0 - @@ -36,22 +34,14 @@ False True 0 + True + fill - - True - True - 1 - - - False - True - 0 - @@ -60,12 +50,6 @@ center 6 - - False - False - end - 1 - diff --git a/data/ui/preferences.ui b/data/ui/preferences.ui index 2aabe3436baf17de2abc5acfcecebcd372a188ca..dab3709521b07cd2f51de8f2bd3cd32e32d675c4 100644 --- a/data/ui/preferences.ui +++ b/data/ui/preferences.ui @@ -1,186 +1,116 @@ - - + - False - Preferences - False - dialog - + Preferences + 1 + 0 - + - False vertical 8 - - - False - end - - - Reset to Factory Settings - False - True - False - True - True - Reset all settings to their default values - - - - False - True - 0 - True - - - - - Revert - False - True - False - True - True - Revert all settings to the previous values (before you opened the preferences dialog) - - - - False - True - 1 - - - - - Close - False - True - True - True - - - False - True - 2 - - - - - False - True - end - 2 - - - True - False - True - False - True + 1 stack + 1 - - False - True - 0 - - True - False vertical + 1 - - False - True - 1 - - True - False - True + 1 crossfade - - False - True - 2 - - - True - True - 0 - - False + 0 warning - - - False - 5 + + + 8 + 8 + 8 + 8 + + + Some changes will not take effect until you restart Pitivi + 1 + 1 + + + + + + + 5 + 5 + 5 + 5 vertical 6 - end - - False - True - 1 - - - - False - 8 - - - True - False - Some changes will not take effect until you restart Pitivi - True - - - False - True - 0 - - + + + + + end + 5 + 5 + 5 + 5 + + + Reset to Factory Settings + 5 + 0 + 1 + 1 + Reset all settings to their default values + + + + + + Revert + 5 + 0 + 1 + 1 + Revert all settings to the previous values (before you opened the preferences dialog) + + + + + + Close + 5 + 1 + 1 - - True - True - 0 - - - False - True - 1 - diff --git a/data/ui/project_info.ui b/data/ui/project_info.ui index 4d019fbfc1d0e8d30801e4ceb9397929647c1337..cf20fac06d7f49498aca733f79fb2a8f8a5b67cd 100644 --- a/data/ui/project_info.ui +++ b/data/ui/project_info.ui @@ -11,14 +11,8 @@ False False 12 - True - True + fill - - False - True - 0 - @@ -31,35 +25,29 @@ 12 True vertical + fill - + True False center True + False - - False - True - 0 - - - False - True - 1 - True False vertical + fill True False + fill project_name_label @@ -67,11 +55,6 @@ False start - - False - True - 0 - @@ -82,18 +65,8 @@ 200 True - - False - True - 1 - - - False - True - 0 - @@ -105,18 +78,8 @@ 200 True - - False - True - 1 - - - False - True - 2 - diff --git a/data/ui/projectsettings.ui b/data/ui/projectsettings.ui index c0acb3b4d0d4b810e240ef3f5ed42276a06bdfcc..447bed648d9a1970faaa50dd75cec3c41c451798 100644 --- a/data/ui/projectsettings.ui +++ b/data/ui/projectsettings.ui @@ -1,7 +1,6 @@ - - + 1 9999 @@ -56,806 +55,490 @@ 10 - False - 5 - Project Settings - center-on-parent - dialog + Project Settings - + - True - False vertical 12 - - - True - False - end - - - Cancel - True - True - True - - - False - False - 0 - - - - - OK - True - True - True - - - False - False - 1 - - - - - False - True - end - 0 - - + 5 + 5 + 5 + 5 - - True - False 6 12 - True - False - 0 - - - True - False - 0 - 6 + + + 12 + 12 + 18 + 12 + vertical + 12 + + + end + 6 + + + start + Preset: + + + + + + + + 1 + + + + + 1 + 1 + 0 + open-menu-symbolic + + + + - - True - False - 12 + vertical 12 - - True - False - end + + vertical 6 - - True - False + start - Preset: + Size: - + - - False - False - 0 - - - - - True - False - True - - - True - - - - - False - True - 1 - - - True - True - True - none + + 6 - - True - False - open-menu-symbolic + + 1 + adjustment1 + 1 - - - False - True - 2 - - - - - False - False - 0 - - - - - True - False - vertical - 12 - - - True - False - vertical - 6 - - True - False - start - Size: - - - + + × - - False - True - 0 - - - True - False - 6 - - - True - True - - adjustment1 - True - - - False - False - 0 - - - - - True - False - × - - - False - False - 1 - - - - - True - True - - adjustment2 - True - - - False - False - 2 - - - - - True - False - pixels - - - False - True - 3 - - + + 1 + adjustment2 + 1 - - False - False - 1 - - - Constrain proportions - True - True - False - Maintain aspect ratio - start - 0.5 - True - + + pixels - - False - False - 2 - - - False - True - 0 - - - True - False + + Constrain proportions + 1 + Maintain aspect ratio start - vertical - 6 - - - True - False - start - Frame rate: - - - - - - False - True - 0 - - - - - True - False - - - - 0 - - - - - False - True - 1 - - + + + + + + + + start + vertical + 6 + + + start + Frame rate: + + + + + + + - + + + 0 + - - False - True - 1 - + + + - - False - True - 1 - - + - True - False - Video + Video + + 0 + 1 + 2 + - - 0 - 1 - 2 - - True - False - 0 - - - True - False - 6 + + + start + 12 + 12 + 18 + 12 + 6 + 6 - - - True - False - start - 12 - 6 - 6 - - - True - True - 10 - True - - - - 1 - 0 - - - - - True - False - start - Author: - - - 0 - 0 - - - - - True - False - start - Year: - - - 2 - 0 - - - - - True - True - start - 4 - - 1900 - adjustment3 - True - 1900 - - - 3 - 0 - - - - - - - - - - - - - - - - - - - - - - - - - - + + 1 + 10 + 1 + + + 1 + 0 + + + + start + Author: + + 0 + 0 + + + + + + start + Year: + + 2 + 0 + + + + + + 1 + start + 1900 + adjustment3 + 1 + 1900 + + 3 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - True - False - Info + Info + + 0 + 0 + 2 + - - 0 - 0 - 2 - - True - False - 0 - - - True - False - 6 + + + 12 + 12 + 18 + 12 + vertical + 12 - - True - False - 12 + vertical 12 - - True - False + vertical - 12 + 6 - - True - False - vertical + + start + Scaled proxies resolution: + + + + + + + 6 - - True - False - start - Scaled proxies resolution: - - - + + 1 + 1 + adjustment4 + 1 + 1 - - False - True - 0 - - - True - False - 6 - - - True - True - - 1 - adjustment4 - True - 1 - - - False - False - 0 - - - - - True - False - × - - - False - False - 1 - - - - - True - True - - 1 - adjustment5 - True - 1 - - - False - False - 2 - - - - - True - False - pixels - - - False - True - 3 - - + + × - - False - False - 1 - - - Constrain proportions - True - True - False - start - True - + + 1 + 1 + adjustment5 + 1 + 1 - - False - False - 2 - - - True - True - <a href='#'>Proxy Preferences</a> - True - False - 1 - + + pixels - - False - True - 3 - - - False - True - 0 - + + + + Constrain proportions + 1 + start + + + + + + 1 + <a href='#'>Proxy Preferences</a> + 1 + 1 + + - - False - True - 1 - - + - True - False - Proxy + Proxy + + 1 + 1 + - - 1 - 1 - - True - False - 0 - - - True - False - 12 - 12 - 18 - 12 + + + 6 + 6 + 12 + 12 + 18 + 12 - - - True - False - 6 - 6 - - - True - False - start - Title: - - - - - - 0 - 0 - 4 - - - - - True - False - start - 6 - Action: - - - - - - 0 - 2 - 4 - - - - - True - True - - 1 - adjustment8 - True - 1 - - - 0 - 3 - - - - - True - True - - 1 - adjustment9 - True - 1 - - - 2 - 3 - - - - - True - True - - 1 - adjustment6 - True - 1 - - - 0 - 1 - - - - - True - True - - 1 - adjustment7 - True - 1 - - - 2 - 1 - - - - - True - False - % - 0 - - - 3 - 1 - - - - - True - False - % - 0 - - - 3 - 3 - - - - - True - False - × - - - 1 - 1 - - - - - True - False - × - - - 1 - 3 - - + + start + Title: + + + + + 0 + 0 + 4 + + + + + + start + 6 + Action: + + + + + 0 + 2 + 4 + + + + + + 1 + 1 + adjustment8 + 1 + 1 + + 0 + 3 + + + + + + 1 + 1 + adjustment9 + 1 + 1 + + 2 + 3 + + + + + + 1 + 1 + adjustment6 + 1 + 1 + + 0 + 1 + + + + + + 1 + 1 + adjustment7 + 1 + 1 + + 2 + 1 + + + + + + % + 0 + + 3 + 1 + + + + + + % + 0 + + 3 + 3 + + + + + + × + + 1 + 1 + + + + + + × + + 1 + 3 + - + - True - False - Safe Areas + Safe Areas + + 1 + 2 + - - 1 - 2 - @@ -867,11 +550,6 @@ - - False - True - 3 - @@ -879,5 +557,28 @@ button8 ok_button + + + 5 + 5 + 5 + 5 + + + Cancel + 1 + 1 + 5 + + + + + OK + 1 + 1 + + + + diff --git a/data/ui/renderingdialog.ui b/data/ui/renderingdialog.ui index 128dd82bb55b4cfded13208739ac7b88124fe5b8..7bfbcef2e551a65c010f1e2eecb6f97be132252b 100644 --- a/data/ui/renderingdialog.ui +++ b/data/ui/renderingdialog.ui @@ -15,11 +15,6 @@ 1 - - True - False - question-round-symbolic - @@ -36,12 +31,10 @@ False - 18 Render True center-always 220 - dialog 600 @@ -49,42 +42,18 @@ False vertical 18 - - - True - False - end - - - Render - True - True - True - True - True - True - - - - False - False - 1 - - - - - False - True - end - 4 - - + 18 + 18 + 18 + 18 True False True True + fill + fill 6 12 @@ -98,13 +67,12 @@ False 0 0 - False right + + 1 + 1 + - - 1 - 1 - @@ -115,11 +83,11 @@ 6 Quality: fill + + 0 + 1 + - - 0 - 1 - @@ -130,11 +98,11 @@ 6 Preset: right + + 0 + 0 + - - 0 - 0 - @@ -150,6 +118,7 @@ True right preset_popover + fill True @@ -162,33 +131,28 @@ True - False - gtk-missing-image + False + image-missing + + 0 + 0 + - - 0 - 0 - True False Custom + + 0 + 1 + - - 0 - 1 - - - False - True - 0 - @@ -196,26 +160,15 @@ True True start - none - - - True - False - open-menu-symbolic - - + False + open-menu-symbolic - - False - True - 1 - + + 1 + 0 + - - 1 - 0 - @@ -242,11 +195,11 @@ False start Container format: + + 0 + 0 + - - 0 - 0 - @@ -266,39 +219,29 @@ - - False - True - 0 - True True True - help_icon - none - True + question-round-symbolic + False + fill - - False - True - 1 - + + 1 + 0 + - - 1 - 0 - + + 0 + 0 + - - 0 - 0 - @@ -307,7 +250,7 @@ start vertical - + Automatically render from proxy files True True @@ -317,16 +260,10 @@ This option is a good trade-off between quality of the rendered video and stability. start True - True - - False - True - 0 - - + Always render from proxy files True True @@ -334,16 +271,11 @@ This option is a good trade-off between quality of the rendered video and stabil Render all proxied clips from the proxy assets. There might be some quality loss during the rendering process. start True - True + automatically_use_proxies - - False - True - 1 - - + Never render from proxy files True True @@ -352,19 +284,14 @@ This option is a good trade-off between quality of the rendered video and stabil <i>Use at your own risk!</i> start True - True + automatically_use_proxies - - False - True - 2 - + + 0 + 2 + - - 0 - 2 - @@ -377,147 +304,101 @@ This option is a good trade-off between quality of the rendered video and stabil True False - 0 - + True False - 12 + 12 + 12 + 24 + 12 + vertical + 6 - + True False - 12 - vertical - 6 - - - True - False - start - Channels: - - - - - - False - True - 0 - - - - - True - False - start - - - - - 0 - - - - - False - False - 1 - - - - - Advanced... - True - True - True - end - 1 - - - - False - False - end - 2 - - - - - True - False - start - - - - - 0 - - - - - False - False - end - 3 - - + start + Channels: + + + + + + + + True + False + start + - - True - False - start - Codec: - - - - - - False - True - end - 4 - + + + 0 + + + + + + Advanced... + True + True + True + end + + + + + + True + False + start + - - True - False - start - - - - - 0 - - - - - False - False - end - 5 - + + + 0 + + + + + + True + False + start + Codec: + + + + + + + + True + False + start + - - True - False - start - Sample rate: - - - - - - False - True - end - 6 - + + + 0 + + + + True + False + start + Sample rate: + + + + + @@ -527,220 +408,148 @@ This option is a good trade-off between quality of the rendered video and stabil True False start - 0 True - True + + 1 + 0 + - - 1 - 0 - True False - 0 - + True False - 12 + 12 + 12 + 24 + 12 + vertical + 6 - + True False - 12 - vertical - 6 - - - True - False - start - Scale: - - - - - - False - True - 0 - - - - - True - False - start - 12 - - - True - True - start - - 1.0 - False - False - adjustment1 - 1 - True - 1 - - - - Scale - - - - - False - False - 0 - - - - - True - False - start - 1000 x 1000 - 12 - 0 - - - Height - - - - - False - True - 1 - - - - - False - False - 1 - - - - - Project Settings... - True - True - True - end - - - - False - False - 2 - - + start + Scale: + + + + + + + + True + False + start + 12 - - Advanced... + True True - True - end - - - - False - False - end - 3 - - - - - True - False start - - - - - 0 - - + 1.0 + adjustment1 + 1 + True + 1 + + + Scale + - - False - False - end - 4 - - + True False start - Codec: - - - + 1000 x 1000 + 12 + + Height + - - False - True - end - 5 - + + + + + Project Settings... + True + True + True + end + + + + + + Advanced... + True + True + True + end + + + + + + True + False + start + - - True - False - start - - - - - 0 - - - - - Framerate - - - - - False - False - end - 6 - + + + 0 + + + + + + True + False + start + Codec: + + + + + + + + True + False + start + - - True - False - start - Frame rate: - - - - - - False - True - end - 7 - + + + 0 + + + Framerate + + + + + + True + False + start + Frame rate: + + + @@ -752,23 +561,21 @@ This option is a good trade-off between quality of the rendered video and stabil True False start - 0 True - True + + 0 + 0 + - - 0 - 0 - + + 0 + 1 + - - 0 - 1 - @@ -779,11 +586,11 @@ This option is a good trade-off between quality of the rendered video and stabil Advanced + + 1 + 2 + - - 1 - 2 - @@ -792,11 +599,11 @@ This option is a good trade-off between quality of the rendered video and stabil True True + + 2 + 3 + - - 2 - 3 - @@ -805,11 +612,11 @@ This option is a good trade-off between quality of the rendered video and stabil end center File path: + + 0 + 3 + - - 0 - 3 - @@ -817,21 +624,35 @@ This option is a good trade-off between quality of the rendered video and stabil True True + + 1 + 3 + - - 1 - 3 - - - True - True - 0 - + + + + True + False + fill + + + Render + True + True + True + True + True + True + + + + diff --git a/data/ui/renderingprogress.ui b/data/ui/renderingprogress.ui index 50de7120d57572dfc389ed8eac51daf66523ce3f..0f2099c49d89b1524412d77cfde7d8afb46aeb38 100644 --- a/data/ui/renderingprogress.ui +++ b/data/ui/renderingprogress.ui @@ -4,24 +4,26 @@ False - 12 Rendering True center-on-parent 400 - dialog - + True False vertical 12 + 12 + 12 + 12 + 12 - + True False - end + fill Pause @@ -30,11 +32,6 @@ True - - False - False - 0 - @@ -44,63 +41,39 @@ True - - False - False - 1 - - - Play + + Close True True True - + fill + - - False - True - 2 - True - - - Open Folder + + Play True True True - + fill + - - True - True - 3 - True - - - Close + + Open Folder True True True - + + True + fill - - False - True - 4 - - - False - True - end - 0 - @@ -112,12 +85,8 @@ True 50 0 + fill - - False - True - 0 - @@ -125,12 +94,8 @@ False Initializing... True + fill - - False - True - 3 - @@ -143,29 +108,24 @@ False 0 + + 1 + 0 + - - 1 - 0 - False Estimated filesize: 0 + + 0 + 0 + - - 0 - 0 - - - False - True - 4 - diff --git a/data/ui/timelinetoolbar.ui b/data/ui/timelinetoolbar.ui index d5bfe99ce5993494b8ef6ba58ddeae3c8206516e..0f25a3e298f01e2c55332ebb6a864bd3979565f3 100644 --- a/data/ui/timelinetoolbar.ui +++ b/data/ui/timelinetoolbar.ui @@ -1,149 +1,96 @@ - - - - True + + + False - start - vertical - icons - 2 + center + horizontal + - - True + False Split clips at playhead position timeline.split-clips - Split - True + 1 scissors-symbolic - - False - True - - - True + False Delete clips timeline.delete-selected-clips - Delete - True + 1 edit-delete-symbolic - - False - True - - - True + False Group clips timeline.group-selected-clips - Group - True + 1 attach-audio-symbolic - - False - True - - - True + False Ungroup clips timeline.ungroup-selected-clips - Ungroup - True + 1 detach-audio-symbolic - - False - True - - - True + False Cut clips timeline.cut-selected-clips - Cut - True + 1 edit-cut-symbolic - - False - True - - - True + False Copy clips timeline.copy-selected-clips - Copy - True + 1 edit-copy-symbolic - - False - True - - - True + False Paste clips timeline.paste-clips - Paste - True + 1 edit-paste-symbolic - - False - True - - - True + False Align clips based on their soundtracks timeline.align-clips Align - True + 1 stopwatch-symbolic - - False - True - - - True + False Toggle gapless mode When enabled, adjacent clips automatically move to fill gaps. timeline.toggle-gapless-mode - Gapless mode - True + 1 align-tool-symbolic - - False - True - diff --git a/data/ui/titleeditor.ui b/data/ui/titleeditor.ui index d120565bce0e5c180b4e1a1edd1d732b1503073e..c2c1aae1ebf7f27dfb9e29b5f4132b6ec50928c5 100644 --- a/data/ui/titleeditor.ui +++ b/data/ui/titleeditor.ui @@ -24,7 +24,8 @@ True False - etched-in + True + fill True @@ -44,12 +45,8 @@ True word -1 + fill - - False - True - 0 - @@ -61,7 +58,6 @@ 10 - False True True True @@ -69,17 +65,12 @@ False Choose a font + fill - - False - True - 0 - - False True True True @@ -87,34 +78,23 @@ True Pick a text color rgb(239,41,41) + fill - - False - True - 1 - picker1 True False - 0 - none + fill - - False - True - 2 - - False True True True @@ -122,30 +102,20 @@ True Pick a background color rgb(255,255,255) + fill - - False - True - 3 - picker2 True False - 0 - none + fill - - False - True - 4 - @@ -156,37 +126,22 @@ True Pick an outline color rgba(0,0,0,0) + fill - - False - True - 5 - picker3 True False - 0 - none + fill - - False - True - 6 - - - False - True - 1 - @@ -195,15 +150,9 @@ True False 10 - True start - - False - True - 2 - @@ -217,11 +166,6 @@ 0 0 - - False - True - 3 - @@ -238,11 +182,11 @@ True False + + 1 + 0 + - - 1 - 0 - @@ -250,11 +194,11 @@ False Horizontal: 0 + + 0 + 0 + - - 0 - 0 - @@ -262,11 +206,11 @@ False 1 + + 1 + 1 + - - 1 - 1 - @@ -274,57 +218,45 @@ False Vertical: 0 + + 0 + 1 + - - 0 - 1 - True True - position_y_adj 2 True + + 2 + 1 + - - 2 - 1 - True True - position_x_adj 2 True + + 2 + 0 + - - 2 - 0 - - - False - True - 4 - - - True - True - 2 - diff --git a/data/ui/trackerperspective.ui b/data/ui/trackerperspective.ui index c357436e3103f26ffd6560f00206aadc544d97a9..075fc8c9cc9aff53b8594a079f2fd5a8cafcf967 100644 --- a/data/ui/trackerperspective.ui +++ b/data/ui/trackerperspective.ui @@ -1,29 +1,24 @@ - - + True False GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_STRUCTURE_MASK - - + + + + + + - - - - - - True - False - Pause - media-playback-pause-symbolic - - - True - False - Play - media-playback-start-symbolic + + + + + + + 100 @@ -31,16 +26,6 @@ 10 - - True - False - media-seek-backward-symbolic - - - True - False - media-seek-forward-symbolic - diff --git a/pitivi/action_search_bar.py b/pitivi/action_search_bar.py index c81897b36a32dafac78d0de24af0615f63a67c4f..67d29628004937a5f6459aef61133acdb2813094 100644 --- a/pitivi/action_search_bar.py +++ b/pitivi/action_search_bar.py @@ -36,21 +36,26 @@ class ActionSearchBar(Gtk.Window): self.set_size_request(600, 50) self.vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=PADDING) self.set_decorated(False) - self.add(self.vbox) + self.set_child(self.vbox) self.entry = Gtk.SearchEntry() self.results_window = Gtk.ScrolledWindow() self.results_window.props.can_focus = False + self.results_window.set_hexpand(True) + self.results_window.set_halign(Gtk.Align.FILL) clear_styles(self.entry) self.setup_results_window() self.entry.props.placeholder_text = _("Search Action") + self.entry.set_hexpand(True) - self.entry.connect("key-press-event", self._entry_key_press_event_cb) + eventcontroller_key = Gtk.EventControllerKey() + eventcontroller_key.connect("key-pressed", self._entry_key_press_event_cb) + self.entry.add_controller(eventcontroller_key) self.entry.connect("changed", self._entry_changed_cb) self.treeview.connect("row-activated", self._treeview_row_activated_cb) - self.vbox.pack_start(self.entry, True, True, 0) - self.vbox.pack_start(self.results_window, True, True, 0) + self.vbox.prepend(self.entry) + self.vbox.prepend(self.results_window) def setup_results_window(self): self.list_model = Gtk.ListStore(str, int, Gdk.ModifierType, Gio.SimpleAction, object, bool, bool) @@ -98,7 +103,7 @@ class ActionSearchBar(Gtk.Window): self.__select_row(self.model_filter.get_iter_first()) - self.results_window.add(self.treeview) + self.results_window.set_child(self.treeview) self.results_window.props.min_content_height = 300 def filter_func(self, model, row, data): @@ -150,23 +155,23 @@ class ActionSearchBar(Gtk.Window): row_path = self.model_filter.get_path(row_iter) self.treeview.scroll_to_cell(row_path, None, False, 0, 0) - def _entry_key_press_event_cb(self, entry, event): - if event.keyval == Gdk.KEY_Escape: + def _entry_key_press_event_cb(self, controller, keyval, keycode, state): + if keyval == Gdk.KEY_Escape: self.destroy() return True - if event.keyval == Gdk.KEY_Return: + if keyval == Gdk.KEY_Return: self.__activate_selected_action() return True - if event.keyval == Gdk.KEY_Up: + if keyval == Gdk.KEY_Up: selection = self.treeview.get_selection() model, row_iter = selection.get_selected() if row_iter: self.__select_row(model.iter_previous(row_iter)) return True - if event.keyval == Gdk.KEY_Down: + if keyval == Gdk.KEY_Down: selection = self.treeview.get_selection() model, row_iter = selection.get_selected() if row_iter: diff --git a/pitivi/check.py b/pitivi/check.py index 69e74723a433b4ed6cdf1b4f87027552ed4b0ec6..940a76b45adc4032f8c185f8a7cf40ffab5972a0 100644 --- a/pitivi/check.py +++ b/pitivi/check.py @@ -272,12 +272,12 @@ def _check_videosink(): from gi.repository import Gst global VIDEOSINK_FACTORY - # If using GdkBroadwayDisplay make sure not to try to use gtkglsink + # If using GdkBroadwayDisplay make sure not to try to use gtk4glsink # as it would segfault right away. if not VIDEOSINK_FACTORY and \ not _using_broadway_display() and \ - "gtkglsink" in os.environ.get("PITIVI_UNSTABLE_FEATURES", ""): - sink = Gst.ElementFactory.make("gtkglsink", None) + "gtk4glsink" in os.environ.get("PITIVI_UNSTABLE_FEATURES", ""): + sink = Gst.ElementFactory.make("gtk4glsink", None) if sink: res = sink.set_state(Gst.State.READY) if res == Gst.StateChangeReturn.SUCCESS: @@ -285,7 +285,7 @@ def _check_videosink(): sink.set_state(Gst.State.NULL) if not VIDEOSINK_FACTORY: - VIDEOSINK_FACTORY = Gst.ElementFactory.find("gtksink") + VIDEOSINK_FACTORY = Gst.ElementFactory.find("gtk4paintablesink") return VIDEOSINK_FACTORY @@ -355,7 +355,7 @@ def check_requirements(): if not _check_videosink(): print(_("Could not create video output sink. " - "Make sure you have a gtksink available.")) + "Make sure you have a gtk4paintablesink available.")) return False _check_hardware_decoders() @@ -388,14 +388,12 @@ def initialize_modules(): sys.exit(1) require_version("Gtk", GTK_API_VERSION) - require_version("Gdk", GTK_API_VERSION) - from gi.repository import Gdk - Gdk.init([]) from gi.repository import Gtk + Gtk.init() # Monkey patch deprecated methods to use the new variant by default - Gtk.Layout.get_vadjustment = Gtk.Scrollable.get_vadjustment - Gtk.Layout.get_hadjustment = Gtk.Scrollable.get_hadjustment + # Gtk.Layout.get_vadjustment = Gtk.Scrollable.get_vadjustment + # Gtk.Layout.get_hadjustment = Gtk.Scrollable.get_hadjustment if not gi.version_info >= (3, 11): from gi.repository import GObject @@ -456,7 +454,7 @@ def initialize_modules(): GST_API_VERSION = "1.0" GST_VERSION = "1.17.90" # FIXME Remove checks in proxy.py and utils/markers.py once we bump to >= 1.19.2 -GTK_API_VERSION = "3.0" +GTK_API_VERSION = "4.0" GLIB_API_VERSION = "2.0" HARD_DEPENDENCIES = [GICheck(version_required="3.20.0"), CairoDependency(version_required="1.10.0"), @@ -470,7 +468,7 @@ HARD_DEPENDENCIES = [GICheck(version_required="3.20.0"), GIDependency("GstVideo", apiversion=GST_API_VERSION), GtkDependency("Gtk", apiversion=GTK_API_VERSION, - version_required="3.20.0"), + version_required="4.6.0"), ClassicDependency("numpy"), GIDependency("Gio", apiversion="2.0"), GstPluginDependency("gtk"), diff --git a/pitivi/clip_properties/alignment.py b/pitivi/clip_properties/alignment.py index b890c2ab029799d7b98a0a758d90fb5bd610e7fb..ed07477c566b8eb9ce31f904d9b23315b87a6be9 100644 --- a/pitivi/clip_properties/alignment.py +++ b/pitivi/clip_properties/alignment.py @@ -16,14 +16,14 @@ # # You should have received a copy of the GNU Lesser General Public # License along with this program; if not, see . -from gi.repository import Gdk from gi.repository import GObject +from gi.repository import Graphene from gi.repository import Gtk from pitivi.utils.loggable import Loggable -class AlignmentEditor(Gtk.EventBox, Loggable): +class AlignmentEditor(Gtk.Box, Loggable): """Widget for aligning a video clip. Attributes: @@ -38,20 +38,23 @@ class AlignmentEditor(Gtk.EventBox, Loggable): } def __init__(self): - Gtk.EventBox.__init__(self) + Gtk.Box.__init__(self) Loggable.__init__(self) self._hovered_box = None - self.connect("button-release-event", self._button_release_event_cb) - self.connect("motion-notify-event", self._motion_notify_event_cb) - self.connect("leave-notify-event", self._leave_notify_event_cb) - self.add_events(Gdk.EventMask.POINTER_MOTION_MASK) + eventcontroller_click = Gtk.GestureClick() + eventcontroller_motion = Gtk.EventControllerMotion() + eventcontroller_click.connect("released", self._button_release_event_cb) + eventcontroller_motion.connect("motion", self._motion_notify_event_cb) + eventcontroller_motion.connect("leave", self._leave_notify_event_cb) + self.add_controller(eventcontroller_click) + self.add_controller(eventcontroller_motion) - def _leave_notify_event_cb(self, unused_widget, _): + def _leave_notify_event_cb(self, controller): self._hovered_box = None self.queue_draw() - def _button_release_event_cb(self, widget, event): + def _button_release_event_cb(self, controller, n_pressed, x, y): if not self._hovered_box: return self.emit("align") @@ -85,8 +88,8 @@ class AlignmentEditor(Gtk.EventBox, Loggable): return int(coordinate) - def _motion_notify_event_cb(self, widget, event): - hovered_box = self._get_box(event.x, event.y) + def _motion_notify_event_cb(self, controller, x, y): + hovered_box = self._get_box(x, y) if hovered_box != self._hovered_box: self._hovered_box = hovered_box self.queue_draw() @@ -106,9 +109,13 @@ class AlignmentEditor(Gtk.EventBox, Loggable): height = box_height * 4 + 3 return width, height, box_width, box_height - def do_draw(self, cr): + def do_snapshot(self, snapshot): width, height, box_width, box_height = self.get_used_size() + Gtk.Widget.do_snapshot(self, snapshot) + bounds = Graphene.Rect().init(0, 0, width, height) + cr = snapshot.append_cairo(bounds) + self._draw_frame(cr, width, height) # Highlight the box that the cursor is hovering over @@ -117,7 +124,7 @@ class AlignmentEditor(Gtk.EventBox, Loggable): y = height / 8 * self._hovered_box[1] cr.rectangle(x, y, box_width, box_height) - color = self.get_style_context().get_color(Gtk.StateFlags.LINK) + color = self.get_style_context().get_color() cr.set_source_rgba(color.red, color.green, color.blue, 0.7) cr.set_line_width(1) cr.fill() @@ -140,7 +147,7 @@ class AlignmentEditor(Gtk.EventBox, Loggable): cr.move_to(width - line_offset_x, 0) cr.line_to(width - line_offset_x, height) - color = self.get_style_context().get_color(Gtk.StateFlags.ACTIVE) + color = self.get_style_context().get_color() cr.set_source_rgba(color.red, color.green, color.blue, 0.6) cr.set_line_width(1) cr.stroke() diff --git a/pitivi/clip_properties/color.py b/pitivi/clip_properties/color.py index 56a445c80ebe07d31e6f9b76c72d69416c3cf48a..c466f1bdc3277c46cf61f8fcf54cc3cfc099370b 100644 --- a/pitivi/clip_properties/color.py +++ b/pitivi/clip_properties/color.py @@ -56,22 +56,19 @@ class ColorProperties(Gtk.Expander, Loggable): self._create_ui() def _create_ui(self): - self.builder = Gtk.Builder() + self.builder = Gtk.Builder(self) self.builder.add_from_file(os.path.join(get_ui_dir(), "clipcolor.ui")) - self.builder.connect_signals(self) box = self.builder.get_object("color_box") - self.add(box) + self.set_child(box) self.color_button = self.builder.get_object("color_button") self.color_picker_button = ColorPickerButton() - box.add(self.color_picker_button) + box.append(self.color_picker_button) self.color_picker_button.connect( "value-changed", self._color_picker_value_changed_cb) - self.show_all() - def _set_child_property(self, name, value): with self.app.action_log.started("Color change property", finalizing_action=CommitTimelineFinalizingAction(self.app.project_manager.current_project.pipeline), diff --git a/pitivi/clip_properties/compositing.py b/pitivi/clip_properties/compositing.py index 814f904933620bdeb25fcc4636234790117509e7..ba5ae77edba656aa4567c022545ad5ed08e07a26 100644 --- a/pitivi/clip_properties/compositing.py +++ b/pitivi/clip_properties/compositing.py @@ -56,13 +56,14 @@ class CompositingProperties(Gtk.Expander, Loggable): self.set_expanded(True) self.set_label(_("Compositing")) - builder = Gtk.Builder() + builder = Gtk.Builder(self) builder.add_from_file(os.path.join(get_ui_dir(), "clipcompositing.ui")) - builder.connect_signals(self) compositing_box = builder.get_object("compositing_box") self._fade_in_adjustment = builder.get_object("fade_in_adjustment") self._fade_out_adjustment = builder.get_object("fade_out_adjustment") + self.textbox = builder.get_object("blending_mode") + self.textbox.connect("changed", self._blending_property_changed_cb) self._video_source: Optional[GES.VideoSource] = None self._control_source: Optional[Gst.ControlSource] = None @@ -84,8 +85,7 @@ class CompositingProperties(Gtk.Expander, Loggable): ): self.blending_combo.append(value_id, text) - self.add(compositing_box) - compositing_box.show_all() + self.set_child(compositing_box) def set_source(self, video_source: GES.VideoSource) -> None: self.debug("Source set to %s", video_source) diff --git a/pitivi/clip_properties/markers.py b/pitivi/clip_properties/markers.py index fe23b04918bcc321b240ae6a5f5b08414469ca84..9da49c5d054faa8cc30661b75ad189f27125fa76 100644 --- a/pitivi/clip_properties/markers.py +++ b/pitivi/clip_properties/markers.py @@ -64,7 +64,7 @@ class ClipMarkersProperties(Gtk.Expander, Loggable): self.set_label(_("Clip markers")) self.expander_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) - self.add(self.expander_box) + self.set_child(self.expander_box) def set_clip(self, clip): if self.clip: @@ -75,7 +75,7 @@ class ClipMarkersProperties(Gtk.Expander, Loggable): disconnect_all_by_func(child.markers_manager, self._lists_modified_cb) disconnect_all_by_func(child.markers_manager, self._current_list_changed_cb) - for child in self.expander_box.get_children(): + while child := self.expander_box.get_first_child(): self.expander_box.remove(child) self.clip = clip @@ -94,47 +94,52 @@ class ClipMarkersProperties(Gtk.Expander, Loggable): continue row_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=SPACING) - row_box.set_border_width(SPACING) + row_box.set_margin_top(SPACING) + row_box.set_margin_bottom(SPACING) + row_box.set_margin_start(SPACING) + row_box.set_margin_end(SPACING) row_box.show() row_size_group = Gtk.SizeGroup(mode=Gtk.SizeGroupMode.VERTICAL) child_type = child.get_track_type() name = ClipMarkersProperties.TRACK_TYPES[child_type] - label = Gtk.Label(label=name) - row_box.pack_start(label, False, False, 0) + label = Gtk.Label.new(name) + row_box.append(label) label.show() labels_size_group.add_widget(label) row_size_group.add_widget(label) label.props.valign = Gtk.Align.START controls_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=SPACING) + controls_box.set_vexpand(True) + controls_box.set_valign(Gtk.Align.FILL) controls_box.show() selection_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=SPACING) list_store = Gtk.ListStore(str, str) list_combo = Gtk.ComboBox.new_with_model(list_store) renderer_text = Gtk.CellRendererText() - list_combo.pack_start(renderer_text, True) + # list_combo.append(renderer_text) # TODO: porting: adapt to new method list_combo.add_attribute(renderer_text, "text", 1) list_combo.set_id_column(0) - selection_box.pack_start(list_combo, False, False, 0) + selection_box.append(list_combo) list_combo.show() combos_size_group.add_widget(list_combo) row_size_group.add_widget(list_combo) snap_toggle = Gtk.CheckButton.new_with_label(_("Magnetic")) - selection_box.pack_start(snap_toggle, False, False, 0) + selection_box.append(snap_toggle) if GES_MARKERS_SNAPPABLE: snap_toggle.show() - controls_box.pack_start(selection_box, False, False, 0) + controls_box.append(selection_box) if isinstance(child, GES.AudioSource) and "librosa" not in MISSING_SOFT_DEPS: container = self._create_beat_detection_ui() - controls_box.pack_start(container, False, False, 0) + controls_box.append(container) audio_source = child - row_box.pack_start(controls_box, True, True, 0) + row_box.append(controls_box) manager = child.markers_manager list_combo.connect("changed", self._combo_changed_cb, child, snap_toggle) @@ -149,9 +154,9 @@ class ClipMarkersProperties(Gtk.Expander, Loggable): # Display audio marker settings below the video ones, # matching how they're shown on the timeline. if child_type == GES.TrackType.AUDIO: - self.expander_box.pack_end(row_box, False, False, 0) + self.expander_box.append(row_box) else: - self.expander_box.pack_start(row_box, False, False, 0) + self.expander_box.append(row_box) self._set_audio_source(audio_source) @@ -195,9 +200,8 @@ class ClipMarkersProperties(Gtk.Expander, Loggable): manager.snappable = active def _create_beat_detection_ui(self) -> Gtk.Box: - builder = Gtk.Builder() + builder = Gtk.Builder(self) builder.add_from_file(os.path.join(get_ui_dir(), "beatdetection.ui")) - builder.connect_signals(self) self._detect_button = builder.get_object("detect-button") self._clear_button = builder.get_object("clear-button") diff --git a/pitivi/clip_properties/title.py b/pitivi/clip_properties/title.py index 04aa0f43be050249863469ef01aedd3cd1280cb3..038ff1114842df471b5093b3de25109d8a90d443 100644 --- a/pitivi/clip_properties/title.py +++ b/pitivi/clip_properties/title.py @@ -75,11 +75,10 @@ class TitleProperties(Gtk.Expander, Loggable): self._create_ui() def _create_ui(self): - builder = Gtk.Builder() + builder = Gtk.Builder(self) builder.add_from_file(os.path.join(get_ui_dir(), "titleeditor.ui")) - builder.connect_signals(self) # Create UI - self.add(builder.get_object("box1")) + self.set_child(builder.get_object("box1")) self.editing_box = builder.get_object("base_table") self.textarea = builder.get_object("textview") @@ -95,19 +94,19 @@ class TitleProperties(Gtk.Expander, Loggable): self.color_picker_foreground_widget = ColorPickerButton() self.color_picker_foreground_widget.show() self.color_picker_foreground = builder.get_object("color_picker_foreground") - self.color_picker_foreground.add(self.color_picker_foreground_widget) + self.color_picker_foreground.set_child(self.color_picker_foreground_widget) self.color_picker_foreground_widget.connect("value-changed", self._color_picker_value_changed_cb, self.foreground_color_button, "color") self.color_picker_background_widget = ColorPickerButton() self.color_picker_background_widget.show() self.background_color_picker = builder.get_object("color_picker_background") - self.background_color_picker.add(self.color_picker_background_widget) + self.background_color_picker.set_child(self.color_picker_background_widget) self.color_picker_background_widget.connect("value-changed", self._color_picker_value_changed_cb, self.background_color_button, "foreground-color") self.color_picker_outline_widget = ColorPickerButton() self.color_picker_outline_widget.show() self.outline_color_picker = builder.get_object("color_picker_outline") - self.outline_color_picker.add(self.color_picker_outline_widget) + self.outline_color_picker.set_child(self.color_picker_outline_widget) self.color_picker_outline_widget.connect("value-changed", self._color_picker_value_changed_cb, self.outline_color_button, "outline-color") self.drop_shadow_checkbox = builder.get_object("check_drop_shadow") @@ -132,8 +131,6 @@ class TitleProperties(Gtk.Expander, Loggable): ("right", _("Right"))): self.halignment_combo.append(value_id, text) - self.show_all() - def _set_child_property(self, name, value, mergeable=False): with self.app.action_log.started("Title change property %s" % name, finalizing_action=CommitTimelineFinalizingAction(self.app.project_manager.current_project.pipeline), diff --git a/pitivi/clipproperties.py b/pitivi/clipproperties.py index 86281480c512f5aa96b391310d93b6dd6f913d74..3f3b04ea33c69be0d4ef46428ca4303c17d7cbbd 100644 --- a/pitivi/clipproperties.py +++ b/pitivi/clipproperties.py @@ -23,12 +23,12 @@ from typing import Dict from typing import Optional from typing import Tuple -import cairo from gi.repository import Gdk from gi.repository import GdkPixbuf from gi.repository import GES from gi.repository import GLib from gi.repository import GObject +from gi.repository import Graphene from gi.repository import Gst from gi.repository import GstController from gi.repository import Gtk @@ -39,6 +39,7 @@ from pitivi.clip_properties.color import ColorProperties from pitivi.clip_properties.compositing import CompositingProperties from pitivi.clip_properties.markers import ClipMarkersProperties from pitivi.clip_properties.title import TitleProperties +from pitivi.closingcreditsperspective import ClosingCreditsPerspective from pitivi.configure import get_pixmap_dir from pitivi.configure import get_ui_dir from pitivi.configure import in_devel @@ -54,7 +55,7 @@ from pitivi.utils.misc import disconnect_all_by_func from pitivi.utils.pipeline import PipelineError from pitivi.utils.timeline import SELECT from pitivi.utils.ui import disable_scroll -from pitivi.utils.ui import EFFECT_TARGET_ENTRY +from pitivi.utils.ui import get_listbox_children from pitivi.utils.ui import PADDING from pitivi.utils.ui import SPACING @@ -96,53 +97,57 @@ class ClipProperties(Gtk.ScrolledWindow, Loggable): self.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) viewport = Gtk.Viewport() - viewport.set_shadow_type(Gtk.ShadowType.ETCHED_IN) viewport.show() - self.add(viewport) + self.set_child(viewport) vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=SPACING) vbox.show() - viewport.add(vbox) + viewport.set_child(vbox) self.clips_box = Gtk.Box() self.clips_box.set_orientation(Gtk.Orientation.VERTICAL) self.clips_box.show() - vbox.pack_start(self.clips_box, False, False, 0) + vbox.append(self.clips_box) + + self.closing_clip_expander = ClosingClipProperties(app) + self.closing_clip_expander.set_vexpand(False) + vbox.append(self.closing_clip_expander) self.transformation_expander = TransformationProperties(app) self.transformation_expander.set_vexpand(False) - vbox.pack_start(self.transformation_expander, False, False, 0) + vbox.append(self.transformation_expander) self.speed_expander = TimeProperties(app) self.speed_expander.set_vexpand(False) if in_devel(): - vbox.pack_start(self.speed_expander, False, False, 0) + vbox.append(self.speed_expander) self.title_expander = TitleProperties(app) self.title_expander.set_vexpand(False) - vbox.pack_start(self.title_expander, False, False, 0) + vbox.append(self.title_expander) self.color_expander = ColorProperties(app) self.color_expander.set_vexpand(False) - vbox.pack_start(self.color_expander, False, False, 0) + vbox.append(self.color_expander) self.compositing_expander = CompositingProperties(app) self.compositing_expander.set_vexpand(False) - vbox.pack_start(self.compositing_expander, False, False, 0) + vbox.append(self.compositing_expander) self.effect_expander = EffectProperties(app) self.effect_expander.set_vexpand(False) - vbox.pack_start(self.effect_expander, False, False, 0) + vbox.append(self.effect_expander) self.marker_expander = ClipMarkersProperties(app) self.marker_expander.set_vexpand(False) - vbox.pack_start(self.marker_expander, False, False, 0) + vbox.append(self.marker_expander) self.helper_box = self.create_helper_box() - self.clips_box.pack_start(self.helper_box, False, False, 0) + self.clips_box.append(self.helper_box) disable_scroll(vbox) + self.closing_clip_expander.set_clip(None) self.transformation_expander.set_source(None) self.speed_expander.set_clip(None) self.title_expander.set_source(None) @@ -157,24 +162,47 @@ class ClipProperties(Gtk.ScrolledWindow, Loggable): def create_helper_box(self): """Creates the widgets to display when no clip is selected.""" box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) - box.props.margin = PADDING + box.props.margin_top = PADDING + box.props.margin_bottom = PADDING + box.props.margin_start = PADDING + box.props.margin_end = PADDING - label = Gtk.Label(label=_("Select a clip on the timeline to configure its properties and effects or create a new clip:")) - label.set_line_wrap(True) + label = Gtk.Label.new(_("Select a clip on the timeline to configure its properties and effects or create a new clip:")) + label.set_wrap(True) label.set_xalign(0) - box.pack_start(label, False, False, SPACING) + label.props.margin_top = SPACING + label.props.margin_bottom = SPACING + label.props.margin_start = SPACING + label.props.margin_end = SPACING + box.append(label) self._title_button = Gtk.Button() self._title_button.set_label(_("Create a title clip")) self._title_button.connect("clicked", self.create_title_clip_cb) - box.pack_start(self._title_button, False, False, SPACING) + self._title_button.props.margin_top = SPACING + self._title_button.props.margin_bottom = SPACING + self._title_button.props.margin_start = SPACING + self._title_button.props.margin_end = SPACING + box.append(self._title_button) self._color_button = Gtk.Button() self._color_button.set_label(_("Create a color clip")) self._color_button.connect("clicked", self.create_color_clip_cb) - box.pack_start(self._color_button, False, False, SPACING) + self._color_button.props.margin_top = SPACING + self._color_button.props.margin_bottom = SPACING + self._color_button.props.margin_start = SPACING + self._color_button.props.margin_end = SPACING + box.append(self._color_button) + + self._title_button = Gtk.Button() + self._title_button.set_label(_("Create closing credits clip")) + self._title_button.connect("clicked", self.create_closing_credits_clip_cb) + self._title_button.props.margin_top = SPACING + self._title_button.props.margin_bottom = SPACING + self._title_button.props.margin_start = SPACING + self._title_button.props.margin_end = SPACING + box.append(self._title_button) - box.show_all() return box def create_title_clip_cb(self, unused_button): @@ -214,6 +242,11 @@ class ClipProperties(Gtk.ScrolledWindow, Loggable): self.app.gui.editor.timeline_ui.insert_clips_on_first_layer([color_clip]) self._selection.set_selection([color_clip], SELECT) + def create_closing_credits_clip_cb(self, unused_widget): + dialog = ClosingCreditsPerspective(self.app.gui, self.app, None) + dialog.window.set_modal(True) + dialog.window.show() + def set_project(self, project, timeline_ui): if self._project: self._selection.disconnect_by_func(self._selection_changed_cb) @@ -240,6 +273,7 @@ class ClipProperties(Gtk.ScrolledWindow, Loggable): elif isinstance(child, GES.VideoTestSource): color_clip_source = child + self.closing_clip_expander.set_clip(ges_clip if (not title_source and not color_clip_source) else None) self.transformation_expander.set_source(video_source) self.speed_expander.set_clip(ges_clip if (not title_source and not color_clip_source) else None) self.title_expander.set_source(title_source) @@ -255,6 +289,54 @@ def is_time_effect(effect): return bool(effect.get_meta(TimeProperties.TIME_EFFECT_META)) and in_devel() +class ClosingClipProperties(Gtk.Expander, Loggable): + """Widget for configuring closing credits clip.""" + + def __init__(self, app: Gtk.Application) -> None: + Gtk.Expander.__init__(self) + Loggable.__init__(self) + + self.set_expanded(True) + self.set_label(_("Edit Closing Credits")) + self._clip = None + + self.app = app + self.project = self.app.project_manager.current_project + self.change_text_button = Gtk.Button(label="Edit Clip") + self.change_text_button.props.halign = Gtk.Align.CENTER + self.change_text_button.props.margin_top = PADDING + self.change_text_button.props.margin_bottom = PADDING + self.change_text_button.props.margin_start = PADDING + self.change_text_button.props.margin_end = PADDING + self.change_text_button.connect("clicked", self._button_clicked_cb) + + self.hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) + self.hbox.props.halign = Gtk.Align.CENTER + self.hbox.append(self.change_text_button) + + self.set_child(self.hbox) + self.hide() + + def _button_clicked_cb(self, unused_widget): + dialog = ClosingCreditsPerspective(self.app.gui, self.app, self._clip) + dialog.window.set_modal(True) + dialog.window.show() + + def set_clip(self, clip): + if clip is None: + return + + if not clip or not isinstance(clip, GES.SourceClip): + self.hide() + + if "closing_credit" not in clip.get_asset().get_meta("pitivi::tags"): + self.hide() + return + + self._clip = clip + self.show() + + class TimeProperties(Gtk.Expander, Loggable): """Widget for setting the time related properties of a clip. @@ -285,8 +367,11 @@ class TimeProperties(Gtk.Expander, Loggable): grid = Gtk.Grid.new() grid.props.row_spacing = SPACING grid.props.column_spacing = SPACING - grid.props.border_width = SPACING - self.add(grid) + grid.props.margin_top = SPACING + grid.props.margin_bottom = SPACING + grid.props.margin_start = SPACING + grid.props.margin_end = SPACING + self.set_child(grid) self._speed_adjustment = Gtk.Adjustment() self._speed_adjustment.props.lower = 1 / MAX_RATE @@ -295,6 +380,10 @@ class TimeProperties(Gtk.Expander, Loggable): self._speed_spin_button = Gtk.SpinButton.new(adjustment=self._speed_adjustment, climb_rate=2, digits=2) self._speed_spin_button.set_increments(.1, 1) self._speed_spin_button.set_numeric(True) + self._speed_spin_button.props.margin_top = PADDING + self._speed_spin_button.props.margin_bottom = PADDING + self._speed_spin_button.props.margin_start = PADDING + self._speed_spin_button.props.margin_end = PADDING self._speed_scale_adjustment = Gtk.Adjustment() self._speed_scale_adjustment.props.lower = self._rate_to_linear(1 / MAX_RATE) @@ -302,8 +391,11 @@ class TimeProperties(Gtk.Expander, Loggable): self._speed_scale_adjustment.props.value = 1 self._speed_scale = Gtk.Scale.new(Gtk.Orientation.HORIZONTAL, self._speed_scale_adjustment) self._speed_scale.set_size_request(width=200, height=-1) - self._speed_scale.props.draw_value = False self._speed_scale.props.show_fill_level = False + self._speed_scale.props.margin_top = PADDING + self._speed_scale.props.margin_bottom = PADDING + self._speed_scale.props.margin_start = PADDING + self._speed_scale.props.margin_end = PADDING linear = self._rate_to_linear(1 / 4) self._speed_scale.add_mark(linear, Gtk.PositionType.BOTTOM, "¼") @@ -314,8 +406,9 @@ class TimeProperties(Gtk.Expander, Loggable): self._speed_scale.add_mark(linear, Gtk.PositionType.BOTTOM, "{}".format(rate)) hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) - hbox.pack_start(self._speed_spin_button, False, False, PADDING) - hbox.pack_start(self._speed_scale, False, False, PADDING) + + hbox.append(self._speed_spin_button) + hbox.append(self._speed_scale) self.__add_widget_to_grid(grid, _("Speed"), hbox, self._speed_reset_button_clicked_cb, 0) self.__setting_rate = False @@ -333,12 +426,12 @@ class TimeProperties(Gtk.Expander, Loggable): def __add_widget_to_grid(self, grid: Gtk.Grid, nick: str, widget: Gtk.Widget, reset_func: Callable, y: int) -> None: text = _("%(preference_label)s:") % {"preference_label": nick} - button = Gtk.Button.new_from_icon_name("edit-clear-all-symbolic", Gtk.IconSize.MENU) + button = Gtk.Button.new_from_icon_name("edit-clear-all-symbolic") button.set_tooltip_text(_("Reset to default value")) - button.set_relief(Gtk.ReliefStyle.NONE) + button.set_has_frame(False) button.connect("clicked", reset_func) - label = Gtk.Label(label=text) + label = Gtk.Label.new(text) label.props.yalign = 0.5 grid.attach(label, 0, y, 1, 1) grid.attach(widget, 1, y, 1, 1) @@ -509,7 +602,7 @@ class TimeProperties(Gtk.Expander, Loggable): self.notify("rate_linear") self._clip.connect("deep-notify", self.__child_property_changed_cb) - self.show_all() + self.show() else: self.hide() @@ -586,56 +679,65 @@ class EffectProperties(Gtk.Expander, Loggable): self.expander_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) self.effects_listbox = Gtk.ListBox() - placeholder_label = Gtk.Label( + placeholder_label = Gtk.Label.new( _("To apply an effect to the clip, drag it from the Effect Library " "or use the button below.")) - placeholder_label.set_line_wrap(True) + placeholder_label.set_wrap(True) placeholder_label.show() self.effects_listbox.set_placeholder(placeholder_label) # Add effect popover button self.effect_popover = EffectsPopover(app) - self.add_effect_button = Gtk.MenuButton(_("Add Effect")) + self.add_effect_button = Gtk.MenuButton() + self.add_effect_button.set_label(_("Add Effect")) + self.add_effect_button.set_direction(Gtk.ArrowType.NONE) self.add_effect_button.set_popover(self.effect_popover) self.add_effect_button.props.halign = Gtk.Align.CENTER + self.add_effect_button.props.margin_top = PADDING + self.add_effect_button.props.margin_bottom = PADDING + self.add_effect_button.props.margin_start = PADDING + self.add_effect_button.props.margin_end = PADDING - self.object_tracker_box = Gtk.ButtonBox() + self.object_tracker_box = Gtk.Box() self.object_tracker_box.props.halign = Gtk.Align.CENTER + self.object_tracker_box.props.margin_top = PADDING + self.object_tracker_box.props.margin_bottom = PADDING + self.object_tracker_box.props.margin_start = PADDING + self.object_tracker_box.props.margin_end = PADDING self.cover_popover: Optional[Gtk.Popover] = None self.cover_object_button: Optional[Gtk.MenuButton] = None if "cvtracker" not in MISSING_SOFT_DEPS: - self.cover_object_button = Gtk.MenuButton(_("Cover Object")) - self.object_tracker_box.pack_start(self.cover_object_button, False, False, 0) + self.cover_object_button = Gtk.MenuButton() + self.cover_object_button.set_label(_("Cover Object")) + self.cover_object_button.set_direction(Gtk.ArrowType.NONE) + self.object_tracker_box.append(self.cover_object_button) - self.drag_dest_set(Gtk.DestDefaults.DROP, [EFFECT_TARGET_ENTRY], - Gdk.DragAction.COPY) + drop_target = Gtk.DropTarget.new(GObject.TYPE_STRING, Gdk.DragAction.COPY) - self.expander_box.pack_start(self.effects_listbox, False, False, 0) - self.expander_box.pack_start(self.add_effect_button, False, False, PADDING) - self.expander_box.pack_start(self.object_tracker_box, False, False, PADDING) + self.expander_box.append(self.effects_listbox) + self.expander_box.append(self.add_effect_button) + self.expander_box.append(self.object_tracker_box) - self.add(self.expander_box) + self.set_child(self.expander_box) # Connect all the widget signals - self.connect("drag-motion", self._drag_motion_cb) - self.connect("drag-leave", self._drag_leave_cb) - self.connect("drag-data-received", self._drag_data_received_cb) + drop_target.connect("accept", self._accept_cb) + drop_target.connect("motion", self._motion_cb) + drop_target.connect("leave", self._leave_cb) + drop_target.connect("drop", self._drop_cb, self) + self.add_controller(drop_target) - self.add_effect_button.connect("toggled", self._add_effect_button_toggled_cb) + self.add_effect_button.connect("activate", self._add_effect_button_toggled_cb) if self.cover_object_button: - self.cover_object_button.connect("toggled", self._cover_object_button_toggled_cb) - - self.show_all() + self.cover_object_button.connect("activate", self._cover_object_button_toggled_cb) def _add_effect_button_toggled_cb(self, button): # MenuButton interacts directly with the popover, bypassing our subclassed method - if button.props.active: - self.effect_popover.search_entry.set_text("") + self.effect_popover.search_entry.set_text("") def _cover_object_button_toggled_cb(self, button): - if button.props.active: - self.cover_popover.update_object_list() + self.cover_popover.update_object_list() def _create_effect_row(self, effect): if is_time_effect(effect): @@ -645,15 +747,19 @@ class EffectProperties(Gtk.Expander, Loggable): toggle = Gtk.CheckButton() toggle.props.active = effect.props.active + toggle.props.margin_top = PADDING + toggle.props.margin_bottom = PADDING + toggle.props.margin_start = PADDING + toggle.props.margin_end = PADDING effect_info = self.app.effects.get_info(effect) - effect_label = Gtk.Label(effect_info.human_name) + effect_label = Gtk.Label.new(effect_info.human_name) effect_label.set_tooltip_text(effect_info.description) # Set up revealer + expander effect_config_ui = self.effects_properties_manager.get_effect_configuration_ui(effect) config_ui_revealer = Gtk.Revealer() - config_ui_revealer.add(effect_config_ui) + config_ui_revealer.set_child(effect_config_ui) expander = Gtk.Expander() expander.set_label_widget(effect_label) @@ -663,37 +769,42 @@ class EffectProperties(Gtk.Expander, Loggable): config_ui_revealer.props.halign = Gtk.Align.CENTER expander.connect("notify::expanded", self._toggle_expander_cb, config_ui_revealer) - remove_effect_button = Gtk.Button.new_from_icon_name("window-close", - Gtk.IconSize.BUTTON) - remove_effect_button.props.margin_end = PADDING + remove_effect_button = Gtk.Button.new_from_icon_name("window-close") + remove_effect_button.props.margin_start = PADDING row_widgets_box = Gtk.Box() - row_drag_icon = Gtk.Image.new_from_pixbuf(self.drag_lines_pixbuf) - row_widgets_box.pack_start(row_drag_icon, False, False, PADDING) - row_widgets_box.pack_start(toggle, False, False, PADDING) - row_widgets_box.pack_start(expander, True, True, PADDING) - row_widgets_box.pack_end(remove_effect_button, False, False, 0) + row_drag_icon = Gtk.Picture.new_for_pixbuf(self.drag_lines_pixbuf) + row_drag_icon.props.margin_top = PADDING + row_drag_icon.props.margin_bottom = PADDING + row_drag_icon.props.margin_start = PADDING + row_drag_icon.props.margin_end = PADDING - vbox.pack_start(row_widgets_box, False, False, 0) - vbox.pack_start(config_ui_revealer, False, False, 0) + row_widgets_box.append(row_drag_icon) + row_widgets_box.append(toggle) + row_widgets_box.append(expander) + row_widgets_box.append(remove_effect_button) - event_box = Gtk.EventBox() - event_box.add(vbox) + vbox.append(row_widgets_box) + vbox.append(config_ui_revealer) + + event_box = Gtk.Box() + event_box.append(vbox) row = Gtk.ListBoxRow(selectable=False, activatable=False) row.effect = effect row.toggle = toggle - row.add(event_box) + row.set_child(event_box) # Set up drag&drop - event_box.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, - [EFFECT_TARGET_ENTRY], Gdk.DragAction.MOVE) - event_box.connect("drag-begin", self._drag_begin_cb) - event_box.connect("drag-data-get", self._drag_data_get_cb) + drag_source = Gtk.DragSource.new() + drag_source.set_actions(Gdk.DragAction.MOVE) + drag_source.connect("drag-begin", self._drag_begin_cb, event_box) + drag_source.connect("prepare", self._prepare_cb, event_box) + event_box.add_controller(drag_source) - row.drag_dest_set(Gtk.DestDefaults.ALL, [EFFECT_TARGET_ENTRY], - Gdk.DragAction.MOVE | Gdk.DragAction.COPY) - row.connect("drag-data-received", self._drag_data_received_cb) + row_drop_target = Gtk.DropTarget.new(GObject.TYPE_INT, Gdk.DragAction.MOVE) + row_drop_target.connect("drop", self._drop_cb) + row.add_controller(row_drop_target) remove_effect_button.connect("clicked", self._remove_button_cb, row) toggle.connect("toggled", self._effect_active_toggle_cb, row) @@ -701,7 +812,7 @@ class EffectProperties(Gtk.Expander, Loggable): return row def _update_listbox(self): - for row in self.effects_listbox.get_children(): + while row := self.effects_listbox.get_row_at_index(0): self.effects_listbox.remove(row) for effect in self.clip.get_top_effects(): @@ -710,15 +821,15 @@ class EffectProperties(Gtk.Expander, Loggable): effect_row = self._create_effect_row(effect) if effect_row: - self.effects_listbox.add(effect_row) + self.effects_listbox.insert(effect_row, -1) - self.effects_listbox.show_all() + self.effects_listbox.show() def _toggle_expander_cb(self, expander, unused_prop, revealer): revealer.props.reveal_child = expander.props.expanded def _get_effect_row(self, effect): - for row in self.effects_listbox.get_children(): + for row in get_listbox_children(self.effects_listbox): if row.effect == effect: return row return None @@ -728,8 +839,8 @@ class EffectProperties(Gtk.Expander, Loggable): if not row: return - self.effects_listbox.add(row) - self.effects_listbox.show_all() + self.effects_listbox.insert(row, -1) + self.effects_listbox.show() def _remove_effect_row(self, effect): row = self._get_effect_row(effect) @@ -821,31 +932,38 @@ class EffectProperties(Gtk.Expander, Loggable): self._disconnect_from_track_element(track_element) self._remove_effect_row(track_element) - def _drag_begin_cb(self, eventbox, context): + def _drag_begin_cb(self, drag_source, drag, eventbox): """Draws the drag icon.""" row = eventbox.get_parent() alloc = row.get_allocation() - surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, alloc.width, alloc.height) - ctx = cairo.Context(surface) + snapshot = Gtk.Snapshot.new() + bounds = Graphene.Rect().init(0, 0, alloc.width, alloc.height) + ctx = snapshot.append_cairo(bounds) row.draw(ctx) ctx.paint_with_alpha(0.35) - Gtk.drag_set_icon_surface(context, surface) - - def _drag_data_get_cb(self, eventbox, drag_context, selection_data, unused_info, unused_timestamp): - row = eventbox.get_parent() - effect_info = self.app.effects.get_info(row.effect) - effect_name = effect_info.human_name + paintable = snapshot.to_paintable(Graphene.Size().init(alloc.width, alloc.height)) - data = bytes(effect_name, "UTF-8") - selection_data.set(drag_context.list_targets()[0], 0, data) + drag_source.set_icon(paintable, alloc.width / 2, alloc.height / 2) - def _drag_motion_cb(self, unused_widget, unused_drag_context, unused_x, y, unused_timestamp): - """Highlights some widgets to indicate it can receive drag&drop.""" + def _prepare_cb(self, drag_source, unused_x, unused_y, eventbox): + row = eventbox.get_parent() + row_index = row.get_index() + data = GObject.Value() + data.set_int(int(row_index)) + content_provider = Gdk.ContentProvider.new_for_value(data) + drag_source.set_content(content_provider) + return content_provider + + def _accept_cb(self, drop_target, drop): self.debug( "Something is being dragged in the clip properties' effects list") + return True + + def _motion_cb(self, drop_target, x, y): + """Highlights some widgets to indicate it can receive drag&drop.""" row = self.effects_listbox.get_row_at_y(y) if row: self.effects_listbox.drag_highlight_row(row) @@ -853,7 +971,7 @@ class EffectProperties(Gtk.Expander, Loggable): else: self.effects_listbox.drag_highlight() - def _drag_leave_cb(self, unused_widget, drag_context, unused_timestamp): + def _leave_cb(self, drop_target): """Unhighlights the widgets which can receive drag&drop.""" self.debug( "The item being dragged has left the clip properties' effects list") @@ -864,21 +982,23 @@ class EffectProperties(Gtk.Expander, Loggable): def __get_time_effects(self): return [effect for effect in self.clip.get_top_effects() if is_time_effect(effect)] - def _drag_data_received_cb(self, widget, drag_context, x, y, selection_data, unused_info, timestamp): + def _drop_cb(self, drop_target, value, x, y, widget): + """Handles the drop of an effect.""" + drop = drop_target.get_drop() if not self.clip: # Indicate that a drop will not be accepted. - Gdk.drag_status(drag_context, 0, timestamp) - return + drop_target.reject() + return False if self.effects_listbox.get_row_at_y(y): - # Drop happened inside the lisbox + # Drop happened inside the listbox drop_index = widget.get_index() else: - drop_index = len(self.effects_listbox.get_children()) - 1 + drop_index = len(self.effects_listbox.get_children()) - if drag_context.get_suggested_action() == Gdk.DragAction.COPY: + if drop_target.get_action() == Gdk.DragAction.COPY: # An effect dragged probably from the effects list. - factory_name = str(selection_data.get_data(), "UTF-8") + factory_name = value top_effect_index = drop_index + len(self.__get_time_effects()) self.debug("Effect dragged at position %s - computed top effect index %s", @@ -893,15 +1013,14 @@ class EffectProperties(Gtk.Expander, Loggable): if effect: self.clip.set_top_effect_index(effect, top_effect_index) - elif drag_context.get_suggested_action() == Gdk.DragAction.MOVE: + elif drop_target.get_action() == Gdk.DragAction.MOVE: # An effect dragged from the same listbox to change its position. - source_eventbox = Gtk.drag_get_source_widget(drag_context) - source_row = source_eventbox.get_parent() - source_index = source_row.get_index() - + # Source index is passed as data + source_index = value self._move_effect(self.clip, source_index, drop_index) - drag_context.finish(True, False, timestamp) + drop.finish(drop.get_action()) + return True def _move_effect(self, clip, source_index, drop_index): # Handle edge cases @@ -946,16 +1065,17 @@ class TransformationProperties(Gtk.Expander, Loggable): alignment_editor_container = self.builder.get_object("clip_alignment") self.alignment_editor = AlignmentEditor() self.alignment_editor.connect("align", self.__alignment_editor_align_cb) - alignment_editor_container.pack_start(self.alignment_editor, True, True, 0) + self.alignment_editor.set_hexpand(True) + self.alignment_editor.set_halign(Gtk.Align.FILL) + alignment_editor_container.append(self.alignment_editor) self.__control_bindings = {} # Used to make sure self.__control_bindings_changed doesn't get called # when bindings are changed from this class self.__own_bindings_change = False - self.add(self.builder.get_object("transform_box")) + self.set_child(self.builder.get_object("transform_box")) self._init_buttons() - self.show_all() self.hide() self.app.project_manager.connect_after( @@ -984,12 +1104,12 @@ class TransformationProperties(Gtk.Expander, Loggable): self.__set_prop("posy", y) def _update_aspect_ratio_button_image(self): - image = self.builder.get_object("aspect_ratio_image") + button = self.builder.get_object("aspect_ratio_button") if self._aspect_ratio is not None: icon_name = "chain-connected-symbolic" else: icon_name = "chain-broken-symbolic" - image.props.icon_name = icon_name + button.props.icon_name = icon_name def _init_buttons(self): clear_button = self.builder.get_object("clear_button") diff --git a/pitivi/closingcreditsperspective.py b/pitivi/closingcreditsperspective.py new file mode 100644 index 0000000000000000000000000000000000000000..a51189bf70dd7f475259a8bd592ab83d55bd2e96 --- /dev/null +++ b/pitivi/closingcreditsperspective.py @@ -0,0 +1,171 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2023, Rhythm Narula +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This program 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, see . +"""Handling Closing Credits Clip.""" +import os +from gettext import gettext as _ + +from gi.repository import GES +from gi.repository import Gst +from gi.repository import Gtk + +from pitivi.configure import get_ui_dir +from pitivi.utils.loggable import Loggable + + +class ClosingCreditsPerspective(Loggable): + """Manager of a dialog for generating closing credits clip. + + Attributes: + parent_window : Window from which this widget is called. + app (Pitivi): The current app. + clip_name : Type of the clip, example - center, right or left. + clip : Clip to be modified. + """ + + def __init__(self, parent_window, app, clip): + self.app = app + self.project = self.app.project_manager.current_project + Loggable.__init__(self) + + self.proxy_aspect_ratio = Gst.Fraction(1, 0) + + self.text_entered = "" + self.placeholder_str = """

Directed By

+

Rhythm Narula

+

Screenplay By

+

Alan Silvestri

+

Produced By

+

Joanna Johnston

+

Director of Photography

+

Ken Ralston

+

Digital Artist

+

Thibault Saunier

+

Musicians

+

Rhythm Narula

+

Digital Artist

+

Alexandru Băluț

+

Costumes Designed By

+

Andy Guthenberg/p> +

Co-Producers

+

Rhythm Narula

+

+

Additional Visual Effects by WETA, LTD

+

Wellington, New Zealand

+

+

First-Assistant Camera - Tony Rivetti

+

Second-Assistant Camera - Frank D. Parish

+

Aerial Coordinator - Kevin La Rosa

+

+

Visual Effects Supervisor- Stephen Rosenbaum

+
+

Filmed in PANAVISION

+

Color and prints by TECHNICOLOR

+

Kodak Motion Picture Products

+ """ + self.clip_types = [_("Center"), _("Right"), _("Left")] + self.clip_name = self.clip_types[0] + self.clip = clip + self._create_ui() + self.window.set_transient_for(parent_window) + + def _create_ui(self): + """Creates UI of the Text Editor to enter content to be displayed on clip.""" + self.builder = Gtk.Builder(self) + self.builder.add_from_file( + os.path.join(get_ui_dir(), "closingcreditsclipdialog.ui")) + self.window = self.builder.get_object("closingcredits-settings-dialog") + self.window.set_size_request(600, 700) + + self.combo_box = self.builder.get_object("combo_box") + self._populate_combo_box() + + self.text_entry = self.builder.get_object("text_entry") + self.textbuf = self.builder.get_object("textview1").get_buffer() + if self.clip is not None: + self.placeholder_str = self.clip.get_asset().get_meta("pitivi::clip_content") + self.clip_name = self.clip.get_asset().get_meta("pitivi::clip_type") + self.textbuf.set_text(self.placeholder_str) + + def _populate_combo_box(self): + """Populates the dropdown with available clip types.""" + for clip_name in self.clip_types: + self.combo_box.append_text(clip_name) + self.combo_box.set_active(0) + + def on_combo_changed(self, combo): + self.clip_name = combo.get_active_text() + + def _response_cb(self, unused_widget, response): + """Handles the dialog being closed.""" + if response == Gtk.ResponseType.OK: + self._update_details() + self.window.destroy() + + def _update_details(self): + """Updates text entered and handles the generation/modification of clip.""" + self.text_entered = self.textbuf.get_text(self.textbuf.get_start_iter(), self.textbuf.get_end_iter(), True) + if self.clip is None: + self._generate_closing_credit_clip() + else: + self._update_closing_credit_clip() + + def _generate_closing_credit_clip(self): + """Handles the generation and addition of generated closing credit clip to project.""" + self._generate_html() + self._generate_clip("output.mp4") + filenames = [Gst.filename_to_uri(os.getcwd() + "/output.mp4")] + self.project.add_uris(filenames) + assets = GES.list_assets(GES.UriClip) + for asset in assets: + if os.path.basename(asset.get_id()) == 'output.mp4': + asset.set_meta("pitivi::tags", "closing_credit") + asset.set_meta("pitivi::clip_content", self.text_entered) + asset.set_meta("pitivi::clip_type", self.clip_name) + + def _update_closing_credit_clip(self): + """Handles the modification and reloading of the selected clip.""" + self._generate_html() + self._generate_clip(self.clip.get_uri()[7:]) + self.clip.get_asset().set_meta("pitivi::clip_content", self.text_entered) + self.clip.get_asset().set_meta("pitivi::clip_type", self.clip_name) + GES.Asset.needs_reload(GES.UriClip, self.clip.get_asset().props.id) + self.clip = None + + def _generate_html(self): + """Reads the template.txt, modifies its content and write it to a HTML file that is used to generate clip.""" + with open("data/closing_credits/template.txt", "r", encoding="utf-8") as file: + template_content = file.read() + template_content = template_content.format(type=self.clip_name, content=self.text_entered) + with open("data/closing_credits/closingcredits.html", "w", encoding="utf-8") as html_file: + html_file.write(template_content) + + def _generate_clip(self, output_clip_path): + """Generate clip using wpe package taking HTML as input and stores it in output_clip_path location.""" + file_uri = Gst.filename_to_uri(os.getcwd() + "/data/closing_credits/closingcredits.html") + pipe = Gst.Pipeline() + pipe = Gst.parse_launch("wpevideosrc num-buffers=200 location=" + file_uri + " draw-background=0 ! video/x-raw,framerate=30/1 ! videoconvert ! x264enc ! mp4mux ! filesink location=" + output_clip_path) + pipe.set_state(Gst.State.PLAYING) + bus = pipe.get_bus() + msg = bus.timed_pop_filtered(Gst.CLOCK_TIME_NONE, Gst.MessageType.ERROR | Gst.MessageType.EOS) + if msg.type == Gst.MessageType.ERROR: + err, debug = msg.parse_error() + self.log("Error received from element %s: %s" % (msg.src.get_name(), err)) + self.log("Debugging information: %s" % debug) + elif msg.type == Gst.MessageType.EOS: + self.log("End-Of-Stream reached.") + else: + self.log("Unexpected message received.") + pipe.set_state(Gst.State.NULL) diff --git a/pitivi/dialogs/about.py b/pitivi/dialogs/about.py index 1d8de793004b35aa5d4721831b4f7641ff6fe46e..6bd86fca9725a0504856b78805af22c8d8bf7aad 100644 --- a/pitivi/dialogs/about.py +++ b/pitivi/dialogs/about.py @@ -51,7 +51,7 @@ class AboutDialog(Gtk.AboutDialog): comments = ["", "GES %s" % ".".join(map(str, GES.version())), - "GTK+ %s" % ".".join(map(str, (Gtk.MAJOR_VERSION, Gtk.MINOR_VERSION))), + "GTK %s" % ".".join(map(str, (Gtk.MAJOR_VERSION, Gtk.MINOR_VERSION))), "GStreamer %s" % ".".join(map(str, Gst.version()))] self.set_comments("\n".join(comments)) @@ -80,9 +80,4 @@ class AboutDialog(Gtk.AboutDialog): self.set_documenters(documenters) self.set_license_type(Gtk.License.LGPL_2_1) self.set_logo_icon_name("org.pitivi.Pitivi") - self.connect("response", self.__about_response_cb) self.set_transient_for(app.gui) - - @staticmethod - def __about_response_cb(dialog, unused_response): - dialog.destroy() diff --git a/pitivi/dialogs/browseprojects.py b/pitivi/dialogs/browseprojects.py index e86c8879e4197ddbb1d39b5d28940aaa0da22401..4b01c28c898f9bff14a7140262877372f3d2d818 100644 --- a/pitivi/dialogs/browseprojects.py +++ b/pitivi/dialogs/browseprojects.py @@ -18,6 +18,7 @@ from gettext import gettext as _ from gi.repository import GES +from gi.repository import Gio from gi.repository import Gtk @@ -39,7 +40,8 @@ class BrowseProjectsDialog(Gtk.FileChooserDialog): _("Open"), Gtk.ResponseType.OK) self.set_default_response(Gtk.ResponseType.OK) self.set_select_multiple(False) - self.set_current_folder(app.settings.lastProjectFolder) + gfile = Gio.File.new_for_path(app.settings.lastProjectFolder) + self.set_current_folder(gfile) formatter_assets = GES.list_assets(GES.Formatter) formatter_assets.sort( key=lambda x: - x.get_meta(GES.META_FORMATTER_RANK)) @@ -51,15 +53,3 @@ class BrowseProjectsDialog(Gtk.FileChooserDialog): extension = format_.get_meta(GES.META_FORMATTER_EXTENSION) file_filter.add_pattern("*{}".format(extension)) self.add_filter(file_filter) - default = Gtk.FileFilter() - default.set_name(_("All supported formats")) - default.add_custom(Gtk.FileFilterFlags.URI, self.__can_load_uri, None) - self.add_filter(default) - - # pylint: disable=bare-except - @staticmethod - def __can_load_uri(filterinfo, unused_uri): - try: - return GES.Formatter.can_load_uri(filterinfo.uri) - except: - return False diff --git a/pitivi/dialogs/clipmediaprops.py b/pitivi/dialogs/clipmediaprops.py index b2e9b4135c4afd16864c8e7deb53df84a82b95bb..840fcdfbcf4f420cd8b8fbdf5f1b31c8618ea2e4 100644 --- a/pitivi/dialogs/clipmediaprops.py +++ b/pitivi/dialogs/clipmediaprops.py @@ -46,9 +46,8 @@ class ClipMediaPropsDialog: self.has_video = False self.is_image = False - builder = Gtk.Builder() + builder = Gtk.Builder(self) builder.add_from_file(os.path.join(get_ui_dir(), "clipmediaprops.ui")) - builder.connect_signals(self) self.dialog = builder.get_object("Import Settings") # Checkbuttons (with their own labels) in the first table column: self.size_checkbutton = builder.get_object("size_checkbutton") @@ -127,9 +126,11 @@ class ClipMediaPropsDialog: self.framerate_checkbutton.hide() self.video_header_label.set_markup("" + _("Image:") + "") - self.dialog.connect("key-press-event", self._key_press_cb) + eventcontroller_key = Gtk.EventControllerKey() + eventcontroller_key.connect("key-pressed", self._key_press_cb) + self.dialog.add_controller(eventcontroller_key) self.dialog.connect("response", self.__response_cb) - self.dialog.run() + self.dialog.show() def _apply(self): """Applies the widgets values to the project.""" @@ -155,7 +156,7 @@ class ClipMediaPropsDialog: self._apply() self.dialog.destroy() - def _key_press_cb(self, unused_widget, event): - if event.keyval in (Gdk.KEY_Escape, Gdk.KEY_Q, Gdk.KEY_q): + def _key_press_cb(self, controller, keyval, keycode, state): + if keyval in (Gdk.KEY_Escape, Gdk.KEY_Q, Gdk.KEY_q): self.dialog.destroy() return True diff --git a/pitivi/dialogs/filelisterrordialog.py b/pitivi/dialogs/filelisterrordialog.py index b7ae5c501f73fe6169cea6fb064ba8a02806b917..970daad6be09569ccd0c884cd4fa12f0ace15b8d 100644 --- a/pitivi/dialogs/filelisterrordialog.py +++ b/pitivi/dialogs/filelisterrordialog.py @@ -25,6 +25,7 @@ from gi.repository import Pango from pitivi.configure import get_ui_dir from pitivi.utils.loggable import Loggable +from pitivi.utils.ui import get_widget_children class FileListErrorDialog(GObject.Object, Loggable): @@ -37,10 +38,9 @@ class FileListErrorDialog(GObject.Object, Loggable): def __init__(self, title, headline): GObject.Object.__init__(self) Loggable.__init__(self) - self.builder = Gtk.Builder() + self.builder = Gtk.Builder(self) self.builder.add_from_file(os.path.join(get_ui_dir(), "filelisterrordialog.ui")) - self.builder.connect_signals(self) self.window = self.builder.get_object("filelisterrordialog") self.window.set_modal(False) @@ -59,10 +59,10 @@ class FileListErrorDialog(GObject.Object, Loggable): """ self.debug("Uri: %s, reason: %s, extra: %s", uri, reason, extra) exp = self.__create_file_expander(uri, reason, extra) - self.errorvbox.pack_start(exp, False, False, 0) - if len(self.errorvbox.get_children()) < 3: + self.errorvbox.prepend(exp) + # ToDo: Use size instead + if len(get_widget_children(self.errorvbox)) < 3: exp.set_expanded(True) # Let's save the user some clicks - exp.show_all() @staticmethod def __create_file_expander(uri, reason, extra=None): @@ -98,7 +98,7 @@ class FileListErrorDialog(GObject.Object, Loggable): textview = Gtk.TextView(buffer=textbuffer) textview.set_wrap_mode(Gtk.WrapMode.WORD) - exp.add(textview) + exp.set_child(textview) return exp diff --git a/pitivi/dialogs/missingasset.py b/pitivi/dialogs/missingasset.py index 0b898a3fbba296874eb57d34d1d390988b4bc888..0ff0f92fdcd5f8803cb6e145c5b5d3a19613b16c 100644 --- a/pitivi/dialogs/missingasset.py +++ b/pitivi/dialogs/missingasset.py @@ -45,7 +45,10 @@ class MissingAssetDialog(Gtk.Dialog, Loggable): self.set_modal(True) self.add_buttons(_("Cancel"), Gtk.ResponseType.CANCEL, _("Open"), Gtk.ResponseType.OK) - self.set_border_width(SPACING * 2) + self.set_margin_bottom(SPACING * 2) + self.set_margin_end(SPACING * 2) + self.set_margin_start(SPACING * 2) + self.set_margin_top(SPACING * 2) self.get_content_area().set_spacing(SPACING) self.set_transient_for(app.gui) self.set_default_response(Gtk.ResponseType.OK) @@ -57,7 +60,7 @@ class MissingAssetDialog(Gtk.Dialog, Loggable): label_start = Gtk.Label() label_start.set_markup(_("The following file could not be found:")) label_start.set_xalign(0) - vbox.pack_start(label_start, False, False, 0) + vbox.prepend(label_start) hbox = Gtk.Box() hbox.set_orientation(Gtk.Orientation.HORIZONTAL) @@ -68,27 +71,29 @@ class MissingAssetDialog(Gtk.Dialog, Loggable): label_asset_info.set_markup(beautify_missing_asset(asset)) label_asset_info.set_xalign(0) label_asset_info.set_yalign(0) - hbox.pack_start(label_asset_info, False, False, 0) + hbox.prepend(label_asset_info) unused_small_thumb, large_thumb = AssetThumbnail.get_thumbnails_from_xdg_cache(uri) if large_thumb: self.debug("A thumbnail file was found for %s", uri) - thumbnail = Gtk.Image.new_from_pixbuf(large_thumb) - hbox.pack_end(thumbnail, False, False, 0) + thumbnail = Gtk.Picture.new_for_pixbuf(large_thumb) + thumbnail.set_can_shrink(False) + hbox.append(thumbnail) - vbox.pack_start(hbox, False, False, 0) + vbox.prepend(hbox) label_end = Gtk.Label() label_end.set_markup(_("Please specify its new location:")) label_end.set_xalign(0) label_end.set_margin_top(PADDING) - vbox.pack_start(label_end, False, False, 0) + vbox.prepend(label_end) - self.get_content_area().pack_start(vbox, False, False, 0) - vbox.show_all() + self.get_content_area().prepend(vbox) self._chooser = self.__setup_file_chooser(uri, app.settings) - self.get_content_area().pack_start(self._chooser, True, True, 0) + self._chooser.set_hexpand(True) + self._chooser.set_halign(Gtk.Align.FILL) + self.get_content_area().prepend(self._chooser) self._chooser.show() # If the window is too big, the window manager will resize it so that @@ -122,7 +127,10 @@ class MissingAssetDialog(Gtk.Dialog, Loggable): def get_new_uri(self): """Returns new URI of the missing asset, if provided by the user.""" - response = self.run() + self.connect("response", self._response_cb) + self.show() + return self._chooser.get_uri() + + def _response_cb(self, unused_dialog, response): if response == Gtk.ResponseType.OK: self.log("User chose a new URI for the missing file") - return self._chooser.get_uri() diff --git a/pitivi/dialogs/prefs.py b/pitivi/dialogs/prefs.py index fc228c75f4fe10a4c2e8907fa43ea3235c60511a..24a5aace27b50e21f617db34dfea2f1a6d271a5c 100644 --- a/pitivi/dialogs/prefs.py +++ b/pitivi/dialogs/prefs.py @@ -33,7 +33,7 @@ from pitivi.settings import GlobalSettings from pitivi.utils import widgets from pitivi.utils.loggable import Loggable from pitivi.utils.ui import alter_style_class -from pitivi.utils.ui import fix_infobar +from pitivi.utils.ui import get_listbox_children from pitivi.utils.ui import PADDING from pitivi.utils.ui import PREFERENCES_CSS from pitivi.utils.ui import SPACING @@ -79,9 +79,8 @@ class PreferencesDialog(Loggable): self.action_ids = {} # Identify the widgets we'll need - builder = Gtk.Builder() + builder = Gtk.Builder(self) builder.add_from_file(os.path.join(get_ui_dir(), "preferences.ui")) - builder.connect_signals(self) self.dialog = builder.get_object("dialog1") self.sidebar = builder.get_object("sidebar") self.stack = builder.get_object("stack") @@ -102,16 +101,16 @@ class PreferencesDialog(Loggable): def run(self): """Runs the dialog.""" PreferencesDialog._instance = self - self.dialog.run() + self.dialog.show() PreferencesDialog._instance = None def __setup_css(self): css_provider = Gtk.CssProvider() css_provider.load_from_data(PREFERENCES_CSS.encode('UTF-8')) - screen = Gdk.Screen.get_default() + display = Gdk.Display.get_default() style_context = self.app.gui.get_style_context() - style_context.add_provider_for_screen(screen, css_provider, - Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION) + style_context.add_provider_for_display(display, css_provider, + Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION) # Public API @@ -231,14 +230,20 @@ class PreferencesDialog(Loggable): def _create_props_container(self, prefs): container = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) - container.set_border_width(SPACING) + container.set_margin_top(SPACING) + container.set_margin_bottom(SPACING) + container.set_margin_start(SPACING) + container.set_margin_end(SPACING) listbox = Gtk.ListBox() listbox.set_selection_mode(Gtk.SelectionMode.NONE) - listbox.props.margin = PADDING * 2 - listbox.get_style_context().add_class('prefs_list') + listbox.props.margin_top = PADDING * 2 + listbox.props.margin_bottom = PADDING * 2 + listbox.props.margin_start = PADDING * 2 + listbox.props.margin_end = PADDING * 2 + listbox.add_css_class('prefs_list') - container.pack_start(listbox, False, False, 0) + container.prepend(listbox) label_size_group = Gtk.SizeGroup(mode=Gtk.SizeGroupMode.HORIZONTAL) prop_size_group = Gtk.SizeGroup(mode=Gtk.SizeGroupMode.HORIZONTAL) @@ -246,11 +251,14 @@ class PreferencesDialog(Loggable): for y, (label, widget, revert_widget, extra_widget) in enumerate(prefs): box = Gtk.Box() - label_widget = Gtk.Label(label=label) - label_widget.set_alignment(0.0, 0.5) + label_widget = Gtk.Label.new(label) + label_widget.set_yalign(0.5) + label_widget.set_xalign(0) label_widget.props.margin_end = PADDING * 3 label_widget.props.margin_top = PADDING * 2 label_widget.props.margin_bottom = PADDING * 2 + label_widget.set_hexpand(True) + label_widget.set_halign(Gtk.Align.START) label_size_group.add_widget(label_widget) widget.props.margin_end = PADDING * 3 @@ -258,33 +266,36 @@ class PreferencesDialog(Loggable): widget.props.margin_bottom = PADDING * 2 prop_size_group.add_widget(widget) - box.pack_start(label_widget, True, True, 0) - box.pack_start(widget, False, False, 0) + box.prepend(label_widget) + box.append(widget) if revert_widget: - box.pack_start(revert_widget, False, False, 0) + box.append(revert_widget) if extra_widget: box1 = box box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) - box.pack_start(box1, False, False, 0) - box.pack_start(extra_widget, False, False, SPACING) + box.append(box1) + extra_widget.props.margin_top = SPACING + extra_widget.props.margin_bottom = SPACING + extra_widget.props.margin_start = SPACING + extra_widget.props.margin_end = SPACING + box.append(extra_widget) box.props.margin_start = PADDING * 3 row = Gtk.ListBoxRow() row.set_activatable(False) - row.add(box) + row.set_child(box) if y == 0: - row.get_style_context().add_class('first') - listbox.add(row) + row.add_css_class('first') + listbox.insert(row, -1) - container.show_all() return container def _create_revert_button(self): - revert = Gtk.Button.new_from_icon_name("edit-clear-all-symbolic", Gtk.IconSize.MENU) + revert = Gtk.Button.new_from_icon_name("edit-clear-all-symbolic") revert.set_tooltip_text(_("Reset to default value")) - revert.set_relief(Gtk.ReliefStyle.NONE) + revert.set_has_frame(False) revert.props.valign = Gtk.Align.CENTER return revert @@ -312,7 +323,7 @@ class PreferencesDialog(Loggable): def __add_plugin_manager_section(self): page = PluginPreferencesPage(self.app, self) - page.show_all() + page.show() self._add_page("__plugins", page) def __add_proxies_section(self): @@ -326,27 +337,26 @@ class PreferencesDialog(Loggable): self.proxy_height_widget.set_widget_value(self.app.settings.default_scaled_proxy_height) self.widgets["default_scaled_proxy_height"] = self.proxy_height_widget size_box = Gtk.Box(spacing=SPACING) - size_box.pack_start(self.proxy_width_widget, False, False, 0) - size_box.pack_start(Gtk.Label("×"), False, False, 0) - size_box.pack_start(self.proxy_height_widget, False, False, 0) + size_box.prepend(self.proxy_width_widget) + size_box.prepend(Gtk.Label.new("×")) + size_box.prepend(self.proxy_height_widget) size_box.set_tooltip_text(_("This resolution will be used as the" " default target resolution for new projects and projects missing" " scaled proxy meta-data.")) self.scaled_proxy_size_revert_button = self._create_revert_button() self.proxy_infobar = Gtk.InfoBar.new() - fix_infobar(self.proxy_infobar) self.proxy_infobar.set_message_type(Gtk.MessageType.WARNING) self.proxy_infobar.add_button(_("Project Settings"), Gtk.ResponseType.OK) self.scaled_proxies_infobar_label = Gtk.Label.new() - self.proxy_infobar.get_content_area().add(self.scaled_proxies_infobar_label) - self.proxy_infobar.show_all() + self.proxy_infobar.add_child(self.scaled_proxies_infobar_label) + self.proxy_infobar.set_revealed(True) prefs.append((_("Initial proxy size for new projects"), size_box, self.scaled_proxy_size_revert_button, None)) container = self._create_props_container(prefs) - container.pack_start(self.proxy_infobar, False, False, 0) + container.prepend(self.proxy_infobar) self._add_page("_proxies", container) @@ -404,22 +414,21 @@ class PreferencesDialog(Loggable): self.content_box.set_header_func(self._add_header_func, None) self.content_box.connect("row-activated", self.__row_activated_cb) self.content_box.set_selection_mode(Gtk.SelectionMode.NONE) - self.content_box.props.margin = PADDING * 3 + self.content_box.props.margin_top = PADDING * 3 + self.content_box.props.margin_bottom = PADDING * 3 + self.content_box.props.margin_start = PADDING * 3 + self.content_box.props.margin_end = PADDING * 3 self.content_box.props.halign = Gtk.Align.CENTER - self.content_box.get_style_context().add_class('prefs_list') - - viewport = Gtk.Viewport() - viewport.add(self.content_box) + self.content_box.add_css_class('prefs_list') scrolled_window = Gtk.ScrolledWindow() scrolled_window.props.hexpand = True scrolled_window.props.min_content_height = 500 scrolled_window.props.min_content_width = 600 - scrolled_window.add_with_viewport(viewport) + scrolled_window.set_child(self.content_box) outside_box = Gtk.Box() - outside_box.add(scrolled_window) - outside_box.show_all() + outside_box.append(scrolled_window) self._add_page("__shortcuts", outside_box) @@ -446,19 +455,21 @@ class PreferencesDialog(Loggable): title_label.props.margin_end = PADDING * 3 title_label.props.margin_top = PADDING * 2 title_label.props.margin_bottom = PADDING * 2 + title_label.set_hexpand(True) + title_label.set_halign(Gtk.Align.FILL) self.description_size_group.add_widget(title_label) accel_label.props.xalign = 0 accel_label.props.margin_start = PADDING * 3 accel_label.props.margin_end = PADDING * 2 + accel_label.set_hexpand(True) self.accel_size_group.add_widget(accel_label) # Add the third column with the reset button. - button = Gtk.Button.new_from_icon_name("edit-clear-all-symbolic", - Gtk.IconSize.MENU) + button = Gtk.Button.new_from_icon_name("edit-clear-all-symbolic") button.set_tooltip_text(_("Reset the shortcut to the default accelerator")) button.props.margin_end = PADDING / 2 - button.set_relief(Gtk.ReliefStyle.NONE) + button.set_has_frame(False) button.connect("clicked", self.__reset_accelerator_cb, item) button.set_sensitive(accel_changed) @@ -468,9 +479,9 @@ class PreferencesDialog(Loggable): # Pack the three widgets above into a row and add to parent_box. contents_box = Gtk.Box() - contents_box.pack_start(title_label, True, True, 0) - contents_box.pack_start(accel_label, True, False, 0) - contents_box.pack_start(button, False, False, 0) + contents_box.prepend(title_label) + contents_box.append(accel_label) + contents_box.append(button) return contents_box @@ -484,7 +495,7 @@ class PreferencesDialog(Loggable): if prev_group != group: if before is not None: - before.get_style_context().add_class("last") + before.add_css_class("last") header = Gtk.Label() header.set_use_markup(True) @@ -496,13 +507,12 @@ class PreferencesDialog(Loggable): header.props.margin_end = PADDING * 2 header.props.xalign = 0 alter_style_class("group_title", header, "font-size: small;") - header.get_style_context().add_class("group_title") + header.add_css_class("group_title") box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) - box.add(header) - box.get_style_context().add_class("background") - box.show_all() + box.append(header) + box.add_css_class("background") row.set_header(box) - row.get_style_context().add_class("first") + row.add_css_class("first") def __reset_accelerator_cb(self, unused_button, item): """Resets the accelerator to the default value.""" @@ -595,7 +605,7 @@ class ModelItem(GObject.Object): except IndexError: accels = "" - keyval, mods = Gtk.accelerator_parse(accels) + unused_bool, keyval, mods = Gtk.accelerator_parse(accels) return Gtk.accelerator_get_label(keyval, mods) @@ -623,20 +633,17 @@ class CustomShortcutDialog(Gtk.Dialog): self.set_geometry_hints(None, geometry, Gdk.WindowHints.MAX_SIZE) self.set_transient_for(self.preferences) self.get_titlebar().set_decoration_layout('close:') - self.add_events(Gdk.EventMask.KEY_PRESS_MASK) self.conflicting_action = None # Setup the widgets used in the dialog. self.apply_button = self.add_button(_("Apply"), Gtk.ResponseType.OK) - self.apply_button.get_style_context()\ - .add_class(Gtk.STYLE_CLASS_SUGGESTED_ACTION) + self.apply_button.add_css_class("suggested-action") self.apply_button.set_tooltip_text(_("Apply the accelerator to this" " shortcut.")) self.apply_button.hide() self.replace_button = self.add_button(_("Replace"), Gtk.ResponseType.OK) - self.replace_button.get_style_context().\ - add_class(Gtk.STYLE_CLASS_SUGGESTED_ACTION) + self.replace_button.add_css_class("suggested-action") self.replace_button.set_tooltip_text(_("Remove this accelerator from where " "it was used previously and set it for " "this shortcut.")) @@ -660,11 +667,14 @@ class CustomShortcutDialog(Gtk.Dialog): self.conflict_label.props.wrap = True content_area = self.get_content_area() - content_area.props.margin = PADDING * 3 - content_area.add(prompt_label) - content_area.add(self.accelerator_label) - content_area.add(self.conflict_label) - content_area.add(self.invalid_label) + content_area.props.margin_top = PADDING * 3 + content_area.props.margin_bottom = PADDING * 3 + content_area.props.margin_start = PADDING * 3 + content_area.props.margin_end = PADDING * 3 + content_area.set_child(prompt_label) + content_area.set_child(self.accelerator_label) + content_area.set_child(self.conflict_label) + content_area.set_child(self.invalid_label) def do_key_press_event(self, event): """Handles key press events and detects valid accelerators.""" @@ -789,14 +799,16 @@ class PluginsBox(Gtk.ListBox): self.set_header_func(self._add_header_func, None) self.bind_model(self.list_store, self._create_widget_func, None) - self.props.margin = PADDING * 3 + self.props.margin_bottom = PADDING * 3 + self.props.margin_start = PADDING * 3 + self.props.margin_end = PADDING * 3 self.props.margin_top = 0 - self.get_style_context().add_class('prefs_list') + self.add_css_class('prefs_list') # Activate the plugins' switches for plugins that are already loaded. loaded_plugins = self.app.plugin_manager.engine.get_loaded_plugins() - for row in self.get_children(): + for row in get_listbox_children(self): if row.plugin_info.get_module_name() in loaded_plugins: row.switch.set_active(True) @@ -807,7 +819,7 @@ class PluginsBox(Gtk.ListBox): def get_row(self, module_name): """Gets the PluginPreferencesRow linked to a given module name.""" - for row in self.get_children(): + for row in get_listbox_children(self): if row.plugin_info.get_module_name() == module_name: return row return None @@ -856,7 +868,7 @@ class PluginsBox(Gtk.ListBox): if previous_type != current_type: self._set_header(row, current_type) if before is not None: - before.get_style_context().add_class("last") + before.add_css_class("last") def _set_header(self, row, group): group_title = str(group) @@ -867,12 +879,12 @@ class PluginsBox(Gtk.ListBox): header.props.valign = Gtk.Align.CENTER header.props.halign = Gtk.Align.START alter_style_class("group_title", header, "font-size: small;") - header.get_style_context().add_class("group_title") + header.add_css_class("group_title") button = Gtk.Button.new() button.props.valign = Gtk.Align.CENTER - button.props.relief = Gtk.ReliefStyle.NONE - button.add(Gtk.Image.new_from_icon_name("folder-open-symbolic", Gtk.IconSize.MENU)) + button.set_has_frame(False) + button.set_icon_name("folder-open-symbolic") button.connect("clicked", self.__location_clicked_cb, group.get_dir()) inner_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) @@ -880,15 +892,16 @@ class PluginsBox(Gtk.ListBox): inner_box.props.margin_top = PADDING * 3 inner_box.props.margin_bottom = PADDING inner_box.props.margin_end = PADDING * 2 - inner_box.pack_start(header, True, True, 0) - inner_box.pack_start(button, False, False, 0) + inner_box.set_hexpand(True) + inner_box.set_halign(Gtk.Align.FILL) + inner_box.prepend(header) + inner_box.prepend(button) box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) - box.add(inner_box) - box.get_style_context().add_class("background") - box.show_all() + box.append(inner_box) + box.add_css_class("background") row.set_header(box) - row.get_style_context().add_class("first") + row.add_css_class("first") def __location_clicked_cb(self, unused_button, directory): uri = GLib.filename_to_uri(directory, None) @@ -910,14 +923,15 @@ class PluginPreferencesPage(Gtk.ScrolledWindow): # We could use Gtk.ListBox.set_placeholder, but it # appears bad inside the list widget. placeholder_label = Gtk.Label.new(_("No plugins available")) - placeholder_label.props.margin = 4 * PADDING + placeholder_label.props.margin_end = 4 * PADDING + placeholder_label.props.margin_start = 4 * PADDING + placeholder_label.props.margin_bottom = 4 * PADDING placeholder_label.props.margin_top = 10 * PADDING placeholder_label.show() plugins_box = placeholder_label self._infobar_revealer = Gtk.Revealer() self._infobar = Gtk.InfoBar() - fix_infobar(self._infobar) self._infobar_label = Gtk.Label() self._setup_infobar() @@ -929,21 +943,21 @@ class PluginPreferencesPage(Gtk.ScrolledWindow): note_label.props.margin_bottom = PADDING * 2 wrapper_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) - self.add(wrapper_box) + self.set_child(wrapper_box) self.set_min_content_height(500) self.set_min_content_width(600) - wrapper_box.pack_start(self._infobar_revealer, expand=False, fill=False, padding=0) - wrapper_box.pack_start(plugins_box, expand=False, fill=False, padding=0) - wrapper_box.pack_start(note_label, expand=False, fill=False, padding=0) + wrapper_box.prepend(self._infobar_revealer) + wrapper_box.append(plugins_box) + wrapper_box.append(note_label) # Helpers self._infobar_timer = None def _setup_infobar(self): - self._infobar_revealer.add(self._infobar) - self._infobar_label.set_line_wrap(True) - self._infobar.get_content_area().add(self._infobar_label) + self._infobar_revealer.set_child(self._infobar) + self._infobar_label.set_wrap(True) + self._infobar.add_child(self._infobar_label) def show_infobar(self, text, message_type, auto_hide=True): """Sets a text and a message type to the infobar to display it.""" diff --git a/pitivi/dialogs/projectsettings.py b/pitivi/dialogs/projectsettings.py index e8eaa6f761c1024a8922af142ce1f91b05172c9a..f58188fc9a344910ff4b7377979769d814bf0d0d 100644 --- a/pitivi/dialogs/projectsettings.py +++ b/pitivi/dialogs/projectsettings.py @@ -54,10 +54,9 @@ class ProjectSettingsDialog: def _create_ui(self): """Initializes the static parts of the UI.""" - self.builder = Gtk.Builder() + self.builder = Gtk.Builder(self) self.builder.add_from_file( os.path.join(get_ui_dir(), "projectsettings.ui")) - self.builder.connect_signals(self) self.window = self.builder.get_object("project-settings-dialog") self.frame_rate_combo = self.builder.get_object("frame_rate_combo") @@ -89,7 +88,9 @@ class ProjectSettingsDialog: # Add custom framerate fraction widget. frame_rate_box = self.builder.get_object("frame_rate_box") self.frame_rate_fraction_widget = FractionWidget() - frame_rate_box.pack_end(self.frame_rate_fraction_widget, True, True, 0) + self.frame_rate_fraction_widget.set_hexpand(True) + self.frame_rate_fraction_widget.set_halign(Gtk.Align.FILL) + frame_rate_box.append(self.frame_rate_fraction_widget) self.frame_rate_fraction_widget.show() # Behavior. @@ -213,7 +214,8 @@ class ProjectSettingsDialog: def _proxy_settings_label_cb(self, unused_widget, unused_parm): prefs_dialog = PreferencesDialog(self.app) prefs_dialog.stack.set_visible_child_name("_proxies") - prefs_dialog.run() + prefs_dialog.set_modal(True) + prefs_dialog.show() def update_scaled_proxy_width(self): height = int(self.scaled_proxy_height_spin.get_value()) diff --git a/pitivi/editorperspective.py b/pitivi/editorperspective.py index 8143ed59d7f28d2395ef07ad0dfcb5597c9cd8a8..f3dc74206776d22ab824b628ecc9cd7b8af55454 100644 --- a/pitivi/editorperspective.py +++ b/pitivi/editorperspective.py @@ -101,12 +101,13 @@ class EditorPerspective(Perspective, Loggable): """Sets up the UI.""" self.__setup_css() self._create_ui() - self.app.gui.connect("focus-in-event", self.__focus_in_event_cb) + eventcontroller_focus = Gtk.EventControllerFocus() + eventcontroller_focus.connect("enter", self.__focus_in_event_cb) + self.app.gui.add_controller(eventcontroller_focus) self.app.gui.connect("destroy", self._destroyed_cb) def activate_compact_mode(self): """Shrinks widgets to suit better a small screen.""" - self.medialibrary.activate_compact_mode() self.viewer.activate_compact_mode() def refresh(self): @@ -117,12 +118,12 @@ class EditorPerspective(Perspective, Loggable): def __setup_css(self): css_provider = Gtk.CssProvider() css_provider.load_from_data(EDITOR_PERSPECTIVE_CSS.encode("UTF-8")) - screen = Gdk.Screen.get_default() + display = Gdk.Display.get_default() style_context = self.app.gui.get_style_context() - style_context.add_provider_for_screen(screen, css_provider, - Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION) + style_context.add_provider_for_display(display, css_provider, + Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION) - def __focus_in_event_cb(self, unused_widget, unused_event): + def __focus_in_event_cb(self, unused_controller): ges_timeline = self.timeline_ui.timeline.ges_timeline if not ges_timeline: # Nothing to work with, Pitivi is starting up. @@ -191,8 +192,15 @@ class EditorPerspective(Perspective, Loggable): self.mainhpaned = Gtk.Paned(orientation=Gtk.Orientation.HORIZONTAL) # Separates the two sets of tabs self.secondhpaned = Gtk.Paned(orientation=Gtk.Orientation.HORIZONTAL) - self.toplevel_widget.pack1(self.mainhpaned, resize=False, shrink=False) - self.mainhpaned.pack1(self.secondhpaned, resize=True, shrink=False) + self.toplevel_widget.set_shrink_start_child(False) + self.toplevel_widget.set_shrink_end_child(False) + self.toplevel_widget.set_start_child(self.mainhpaned) + self.mainhpaned.set_start_child(self.secondhpaned) + self.mainhpaned.set_resize_start_child(True) + self.mainhpaned.set_shrink_start_child(False) + self.mainhpaned.set_shrink_end_child(False) + self.secondhpaned.set_shrink_start_child(False) + self.secondhpaned.set_shrink_end_child(False) self.toplevel_widget.show() self.secondhpaned.show() self.mainhpaned.show() @@ -202,9 +210,9 @@ class EditorPerspective(Perspective, Loggable): self.medialibrary = MediaLibraryWidget(self.app) self.effectlist = EffectListWidget(self.app) self.main_tabs.append_page("Media Library", - self.medialibrary, Gtk.Label(label=_("Media Library"))) + self.medialibrary, Gtk.Label.new(_("Media Library"))) self.main_tabs.append_page("Effect Library", - self.effectlist, Gtk.Label(label=_("Effect Library"))) + self.effectlist, Gtk.Label.new(_("Effect Library"))) self.medialibrary.connect('play', self._media_library_play_cb) self.medialibrary.show() self.effectlist.show() @@ -214,26 +222,37 @@ class EditorPerspective(Perspective, Loggable): self.clipconfig = ClipProperties(self.app) self.trans_list = TransitionsListWidget(self.app) self.context_tabs.append_page("Clip", - self.clipconfig, Gtk.Label(label=_("Clip"))) + self.clipconfig, Gtk.Label.new(_("Clip"))) self.context_tabs.append_page("Transition", - self.trans_list, Gtk.Label(label=_("Transition"))) + self.trans_list, Gtk.Label.new(_("Transition"))) # Show by default the Title tab, as the Clip and Transition tabs # are useful only when a clip or transition is selected, but # the Title tab allows adding titles. self.context_tabs.set_current_page(2) - self.secondhpaned.pack1(self.main_tabs, resize=False, shrink=False) - self.secondhpaned.pack2(self.context_tabs, resize=False, shrink=False) + self.secondhpaned.set_start_child(self.main_tabs) + self.secondhpaned.set_end_child(self.context_tabs) self.main_tabs.show() self.context_tabs.show() # Viewer self.viewer = ViewerContainer(self.app) - self.mainhpaned.pack2(self.viewer, resize=True, shrink=False) + self.mainhpaned.set_end_child(self.viewer) + self.mainhpaned.set_resize_end_child(True) # Now, the lower part: the timeline + self.timelinepaned = Gtk.Paned(orientation=Gtk.Orientation.VERTICAL) + self.timelinepaned.set_shrink_start_child(False) + self.timelinepaned.set_shrink_end_child(False) + self.timeline_ui = TimelineContainer(self.app, self.editor_state) - self.toplevel_widget.pack2(self.timeline_ui, resize=True, shrink=False) + self.mini_timeline_ui = self.timeline_ui.timeline.mini_layout_container + + self.timelinepaned.set_start_child(self.mini_timeline_ui) + self.timelinepaned.set_end_child(self.timeline_ui) + + self.toplevel_widget.set_end_child(self.timelinepaned) + self.toplevel_widget.set_resize_end_child(True) self.intro = InteractiveIntro(self.app) self.headerbar.pack_end(self.intro.intro_button) @@ -243,15 +262,15 @@ class EditorPerspective(Perspective, Loggable): # Identify widgets for AT-SPI, making our test suite easier to develop # These will show up in sniff, accerciser, etc. - self.headerbar.get_accessible().set_name("editor_headerbar") - self.menu_button.get_accessible().set_name("main menu button") - self.toplevel_widget.get_accessible().set_name("contents") - self.mainhpaned.get_accessible().set_name("upper half") - self.secondhpaned.get_accessible().set_name("tabs") - self.main_tabs.get_accessible().set_name("primary tabs") - self.context_tabs.get_accessible().set_name("secondary tabs") - self.viewer.get_accessible().set_name("viewer") - self.timeline_ui.get_accessible().set_name("timeline area") + self.headerbar.set_name("editor_headerbar") + self.menu_button.set_name("main menu button") + self.toplevel_widget.set_name("contents") + self.mainhpaned.set_name("upper half") + self.secondhpaned.set_name("tabs") + self.main_tabs.set_name("primary tabs") + self.context_tabs.set_name("secondary tabs") + self.viewer.set_name("viewer") + self.timeline_ui.set_name("timeline area") # Restore settings for position and visibility. if self.settings.mainWindowHPanePosition is None: @@ -261,7 +280,7 @@ class EditorPerspective(Perspective, Loggable): self.toplevel_widget.set_position(self.settings.mainWindowVPanePosition) def _set_default_positions(self): - window_width = self.app.gui.get_size()[0] + window_width = self.app.gui.get_width() if self.settings.mainWindowHPanePosition is None: self.settings.mainWindowHPanePosition = window_width / 3 if self.settings.mainWindowMainHPanePosition is None: @@ -299,23 +318,17 @@ class EditorPerspective(Perspective, Loggable): def focus_timeline(self): layers_representation = self.timeline_ui.timeline.layout # Check whether it has focus already, grab_focus always emits an event. - if not layers_representation.props.is_focus: + if not layers_representation.is_focus(): layers_representation.grab_focus() def __create_headerbar(self): headerbar = Gtk.HeaderBar() - headerbar.set_show_close_button(True) - undo_button = Gtk.Button.new_from_icon_name( - "edit-undo-symbolic", Gtk.IconSize.SMALL_TOOLBAR) - undo_button.set_always_show_image(True) - undo_button.set_label(_("Undo")) + undo_button = Gtk.Button.new_from_icon_name("edit-undo-symbolic") undo_button.set_action_name("app.undo") undo_button.set_use_underline(True) - redo_button = Gtk.Button.new_from_icon_name( - "edit-redo-symbolic", Gtk.IconSize.SMALL_TOOLBAR) - redo_button.set_always_show_image(True) + redo_button = Gtk.Button.new_from_icon_name("edit-redo-symbolic") redo_button.set_action_name("app.redo") redo_button.set_use_underline(True) @@ -323,9 +336,7 @@ class EditorPerspective(Perspective, Loggable): self.save_button = Gtk.Button.new_with_label(_("Save")) self.save_button.set_focus_on_click(False) - self.render_button = Gtk.Button.new_from_icon_name( - "system-run-symbolic", Gtk.IconSize.SMALL_TOOLBAR) - self.render_button.set_always_show_image(True) + self.render_button = Gtk.Button.new_from_icon_name("system-run-symbolic") self.render_button.set_label(_("Render")) self.render_button.set_tooltip_text( _("Export your project as a finished movie")) @@ -333,21 +344,22 @@ class EditorPerspective(Perspective, Loggable): self.render_button.connect("clicked", self._render_cb) undo_redo_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0) - undo_redo_box.get_style_context().add_class("linked") - undo_redo_box.pack_start(undo_button, expand=False, fill=False, padding=0) - undo_redo_box.pack_start(redo_button, expand=False, fill=False, padding=0) + undo_redo_box.add_css_class("linked") + undo_redo_box.prepend(undo_button) + undo_redo_box.append(redo_button) headerbar.pack_start(undo_redo_box) self.builder.add_from_file( os.path.join(get_ui_dir(), "mainmenubutton.ui")) self.menu_button = self.builder.get_object("menubutton") + menu_model = self.builder.get_object("editor_menu_model") + self.menu_button.set_menu_model(menu_model) self.keyboard_shortcuts_button = self.builder.get_object("menu_shortcuts") headerbar.pack_end(self.menu_button) headerbar.pack_end(self.save_button) headerbar.pack_end(self.render_button) - headerbar.show_all() return headerbar @@ -447,15 +459,7 @@ class EditorPerspective(Perspective, Loggable): self.app.project_manager.revert_to_saved_project() def __export_project_cb(self, unused_action, unused_param): - uri = self._show_export_dialog(self.app.project_manager.current_project) - result = None - if uri: - result = self.app.project_manager.export_project( - self.app.project_manager.current_project, uri) - - if not result: - self.log("Project couldn't be exported") - return result + self._show_export_dialog(self.app.project_manager.current_project) def __project_settings_cb(self, unused_action, unused_param): self.show_project_settings_dialog() @@ -463,7 +467,8 @@ class EditorPerspective(Perspective, Loggable): def show_project_settings_dialog(self): project = self.app.project_manager.current_project dialog = ProjectSettingsDialog(self.app.gui, project, self.app) - dialog.window.run() + dialog.window.set_modal(True) + dialog.window.show() self.update_title() # Project management callbacks @@ -510,8 +515,11 @@ class EditorPerspective(Perspective, Loggable): dialog.set_property("secondary-use-markup", True) dialog.set_property("secondary-text", unquote(str(exception))) dialog.set_transient_for(self.app.gui) - dialog.run() - dialog.destroy() + + def dialog_response_cb(dialog, response_id): + dialog.destroy() + dialog.connect("response", dialog_response_cb) + dialog.show() self.error("failed to save project") def _project_manager_project_saved_cb(self, unused_project_manager, unused_project, unused_uri): @@ -545,8 +553,8 @@ class EditorPerspective(Perspective, Loggable): save, Gtk.ResponseType.YES) dialog.set_default_response(Gtk.ResponseType.CANCEL) - dialog.get_accessible().set_name("unsaved changes dialog") - reject_btn.get_style_context().add_class("destructive-action") + dialog.set_name("unsaved changes dialog") + reject_btn.add_css_class("destructive-action") primary = _("Save changes to the current project before closing?") dialog.props.use_markup = True @@ -565,20 +573,25 @@ class EditorPerspective(Perspective, Loggable): dialog.props.secondary_text = message - response = dialog.run() - dialog.destroy() + res = [None] + dialog.set_modal(True) + dialog.connect("response", self._project_manager_closing_project_cb_response_cb, project, project_manager, res) + dialog.show() + + return res[0] + def _project_manager_closing_project_cb_response_cb(self, dialog, response, project, project_manager, res): if response == Gtk.ResponseType.YES: if project.uri is not None and project_manager.disable_save is False: - res = self.app.project_manager.save_project() + res[0] = self.app.project_manager.save_project() else: - res = self.save_project_as() + # FixMe: Remains from switching to a callback model for save as dialog + # res = self.save_project_as() + res[0] = True elif response == Gtk.ResponseType.REJECT: - res = True + res[0] = True else: - res = False - - return res + res[0] = False def _project_manager_project_closed_cb(self, project_manager, project): """Starts disconnecting the UI from the specified project. @@ -609,19 +622,29 @@ class EditorPerspective(Perspective, Loggable): message_type=Gtk.MessageType.WARNING, buttons=Gtk.ButtonsType.NONE, text=_("Revert to saved project version?")) - dialog.add_buttons(Gtk.STOCK_CANCEL, Gtk.ResponseType.NO, - Gtk.STOCK_REVERT_TO_SAVED, Gtk.ResponseType.YES) + dialog.add_buttons("Cancel", Gtk.ResponseType.NO, + "Revert to saved", Gtk.ResponseType.YES) dialog.set_resizable(False) dialog.set_property("secondary-text", _("This will reload the current project. All unsaved changes will be lost.")) dialog.set_default_response(Gtk.ResponseType.NO) dialog.set_transient_for(self.app.gui) - response = dialog.run() - dialog.destroy() - if response != Gtk.ResponseType.YES: - return False + + project_manager_response = [True] + dialog.set_modal(True) + dialog.connect("response", self._project_manager_reverting_to_saved_cb_response_cb, project_manager_response) + dialog.show() + + return project_manager_response[0] + return True + def _project_manager_reverting_to_saved_cb_response_cb(self, dialog, response, project_manager_response): + if response == Gtk.ResponseType.YES: + project_manager_response[0] = True + else: + project_manager_response[0] = False + def _project_manager_missing_uri_cb(self, project_manager, project, unused_error, asset): if project.at_least_one_asset_missing: # One asset is already missing so no point in spamming the user @@ -681,7 +704,6 @@ class EditorPerspective(Perspective, Loggable): chooser.set_default_response(Gtk.ResponseType.OK) chooser.set_select_multiple(False) - chooser.props.do_overwrite_confirmation = True asset = GES.Formatter.get_default() asset_extension = asset.get_meta(GES.META_FORMATTER_EXTENSION) @@ -698,26 +720,12 @@ class EditorPerspective(Perspective, Loggable): default.add_pattern("*") chooser.add_filter(default) - response = chooser.run() - if response == Gtk.ResponseType.OK: - self.log("User chose a URI to export project to") - # need to do this to work around bug in Gst.uri_construct - # which escapes all /'s in path! - uri = "file://" + chooser.get_filename() - self.log("uri: %s", uri) - ret = uri - else: - self.log("User didn't choose a URI to export project to") - ret = None + chooser.connect('response', self._export_dialog_response_cb) - chooser.destroy() - return ret + chooser.show() def save_project_as(self): - uri = self._show_save_as_dialog() - if uri is None: - return False - return self.app.project_manager.save_project(uri) + self._show_save_as_dialog() def _show_save_as_dialog(self): self.log("Save URI requested") @@ -736,38 +744,56 @@ class EditorPerspective(Perspective, Loggable): chooser.set_select_multiple(False) chooser.set_current_name(_("Untitled") + "." + asset.get_meta(GES.META_FORMATTER_EXTENSION)) - chooser.set_current_folder(self.settings.lastProjectFolder) - chooser.props.do_overwrite_confirmation = True + lastfolder = Gio.File.new_for_path(self.settings.lastProjectFolder) + chooser.set_current_folder(lastfolder) default = Gtk.FileFilter() default.set_name(_("Detect automatically")) default.add_pattern("*") chooser.add_filter(default) - response = chooser.run() + chooser.connect('response', self._save_as_dialog_response_cb) + + chooser.show() + + def __save_frame_cb(self, unused_action, unused_param): + """Exports a snapshot of the current frame as an image file.""" + res = self._show_save_screenshot_dialog() + if res: + path, mime = res[0], res[1] + self.app.project_manager.current_project.pipeline.save_thumbnail( + -1, -1, mime, path) + + def _export_dialog_response_cb(self, chooser, response): + if response == Gtk.ResponseType.OK: + self.log("User chose a URI to export project to") + # need to do this to work around bug in Gst.uri_construct + # which escapes all /'s in path! + uri = "file://" + chooser.get_current_folder().get_uri() + '/' + chooser.get_current_name() + self.log("uri: %s", uri) + result = self.app.project_manager.export_project( + self.app.project_manager.current_project, uri) + if not result: + self.log("Project couldn't be exported") + else: + self.log("User didn't choose a URI to export project to") + + chooser.destroy() + + def _save_as_dialog_response_cb(self, chooser, response): if response == Gtk.ResponseType.OK: self.log("User chose a URI to save project to") # need to do this to work around bug in Gst.uri_construct # which escapes all /'s in path! - uri = "file://" + chooser.get_filename() + uri = "file://" + chooser.get_current_folder().get_uri() + '/' + chooser.get_current_name() file_filter = chooser.get_filter().get_name() self.log("uri:%s , filter:%s", uri, file_filter) - self.settings.lastProjectFolder = chooser.get_current_folder() - ret = uri + self.settings.lastProjectFolder = chooser.get_current_folder().get_uri() + self.app.project_manager.save_project(uri) else: self.log("User didn't choose a URI to save project to") - ret = None chooser.destroy() - return ret - - def __save_frame_cb(self, unused_action, unused_param): - """Exports a snapshot of the current frame as an image file.""" - res = self._show_save_screenshot_dialog() - if res: - path, mime = res[0], res[1] - self.app.project_manager.current_project.pipeline.save_thumbnail( - -1, -1, mime, path) def _show_save_screenshot_dialog(self): """Asks the user where to save the current frame. @@ -790,7 +816,15 @@ class EditorPerspective(Perspective, Loggable): filt.set_name(image_format) filt.add_mime_type(formats.get(image_format)[0]) chooser.add_filter(filt) - response = chooser.run() + + ret = [] + chooser.set_modal(True) + chooser.connect('response', self._save_screenshot_dialog_response_cb, formats, ret) + chooser.show() + + return ret + + def _save_screenshot_dialog_response_cb(self, chooser, response, formats, ret): if response == Gtk.ResponseType.OK: chosen_format = formats.get(chooser.get_filter().get_name()) chosen_ext = chosen_format[1][0] @@ -801,7 +835,6 @@ class EditorPerspective(Perspective, Loggable): else: ret = None chooser.destroy() - return ret def update_title(self): project = self.app.project_manager.current_project @@ -809,7 +842,8 @@ class EditorPerspective(Perspective, Loggable): if project.has_unsaved_modifications(): unsaved_mark = "*" title = "%s%s — %s" % (unsaved_mark, project.name, APPNAME) - self.headerbar.set_title(title) + self.headerbar.set_title_widget(Gtk.Label.new(title)) + self.headerbar.add_css_class("title") class PreviewAssetWindow(Gtk.Window): @@ -826,23 +860,23 @@ class PreviewAssetWindow(Gtk.Window): self.app = app self.set_title(_("Preview")) - self.set_type_hint(Gdk.WindowTypeHint.UTILITY) self.set_transient_for(app.gui) self._previewer = PreviewWidget(app.settings, minimal=True) - self.add(self._previewer) + self.set_child(self._previewer) self._previewer.preview_uri(self._asset.get_id()) self._previewer.show() - self.connect("key-press-event", self._key_press_event_cb) + eventcontroller_key = Gtk.EventControllerKey() + eventcontroller_key.connect("key-pressed", self._key_press_event_cb) + self.add_controller(eventcontroller_key) def preview(self): """Shows the window and starts the playback.""" width, height = self._calculate_preview_window_size() - self.resize(width, height) + self.set_default_size(width, height) # Setting the position of the window only works if it's currently hidden # otherwise, after the resize the position will not be readjusted - self.set_position(Gtk.WindowPosition.CENTER_ON_PARENT) self.show() self._previewer.play() @@ -862,7 +896,8 @@ class PreviewAssetWindow(Gtk.Window): video = video_streams[0] img_width = video.get_natural_width() img_height = video.get_natural_height() - mainwindow_width, mainwindow_height = self.app.gui.get_size() + mainwindow_width = self.app.gui.get_width() + mainwindow_height = self.app.gui.get_height() max_width = 0.85 * mainwindow_width max_height = 0.85 * mainwindow_height @@ -876,7 +911,7 @@ class PreviewAssetWindow(Gtk.Window): new_height = max_width * img_height / img_width return int(max_width), int(new_height + controls_height) - def _key_press_event_cb(self, unused_widget, event): - if event.keyval == Gdk.KEY_Escape: + def _key_press_event_cb(self, controller, keyval, keycode, state): + if keyval == Gdk.KEY_Escape: self.destroy() return True diff --git a/pitivi/effects.py b/pitivi/effects.py index d61ae68b1cd72b74fcda05b490df7ccaa5cdc35f..b456991ec25e5b6a6ccbd28b259f77d49bc8eb79 100644 --- a/pitivi/effects.py +++ b/pitivi/effects.py @@ -35,12 +35,12 @@ from gettext import gettext as _ from typing import Optional from typing import Union -import cairo from gi.repository import Gdk from gi.repository import GdkPixbuf from gi.repository import GES from gi.repository import GLib from gi.repository import GObject +from gi.repository import Graphene from gi.repository import Gst from gi.repository import Gtk @@ -51,7 +51,8 @@ from pitivi.trackerperspective import EFFECT_TRACKED_OBJECT_ID_META from pitivi.trackerperspective import EFFECT_TRACKED_OBJECT_NAME_META from pitivi.utils.loggable import Loggable from pitivi.utils.ui import disable_scroll -from pitivi.utils.ui import EFFECT_TARGET_ENTRY +from pitivi.utils.ui import get_listbox_children +from pitivi.utils.ui import get_widget_children from pitivi.utils.ui import PADDING from pitivi.utils.ui import SPACING from pitivi.utils.widgets import FractionWidget @@ -334,6 +335,8 @@ class EffectsManager(Loggable): else: bin_description = effect + if isinstance(bin_description, list): + bin_description = bin_description[0] name = EffectInfo.name_from_bin_description(bin_description) return self._effects.get(name) @@ -390,44 +393,46 @@ class EffectListWidget(Gtk.Box, Loggable): os.path.join(get_pixmap_dir(), "star-solid.svg"), 15, 15) self.set_orientation(Gtk.Orientation.VERTICAL) - builder = Gtk.Builder() + builder = Gtk.Builder(self) builder.add_from_file(os.path.join(get_ui_dir(), "effectslibrary.ui")) - builder.connect_signals(self) - toolbar = builder.get_object("effectslibrary_toolbar") - toolbar.get_style_context().add_class(Gtk.STYLE_CLASS_INLINE_TOOLBAR) + toolbar = builder.get_object("effectslibrary_box") self.search_entry = builder.get_object("search_entry") self.fav_view_toggle = builder.get_object("favourites_toggle") - self.fav_view_toggle.set_image(Gtk.Image.new_from_pixbuf(self._star_icon_solid)) + self.fav_view_toggle.set_child(Gtk.Picture.new_for_pixbuf(self._star_icon_solid)) self.main_view = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) self.category_view = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + self.category_view.set_hexpand(True) + self.category_view.set_halign(Gtk.Align.FILL) # Used for showing search results and favourites self.search_view = Gtk.ListBox(activate_on_single_click=False) self.search_view.connect("row-activated", self.effects_listbox_row_activated_cb) - placeholder_text = Gtk.Label(_("No effects")) + placeholder_text = Gtk.Label.new(_("No effects")) placeholder_text.props.visible = True self.search_view.set_placeholder(placeholder_text) + self.search_view.set_hexpand(True) + self.search_view.set_halign(Gtk.Align.FILL) - self.main_view.pack_start(self.category_view, True, True, 0) - self.main_view.pack_start(self.search_view, True, True, 0) + self.main_view.prepend(self.category_view) + self.main_view.prepend(self.search_view) scrollwin = Gtk.ScrolledWindow() scrollwin.props.hscrollbar_policy = Gtk.PolicyType.NEVER scrollwin.props.vscrollbar_policy = Gtk.PolicyType.AUTOMATIC - scrollwin.add(self.main_view) + scrollwin.set_vexpand(True) + scrollwin.set_valign(Gtk.Align.FILL) + scrollwin.set_child(self.main_view) - self.pack_start(toolbar, False, False, 0) - self.pack_start(scrollwin, True, True, 0) + self.prepend(scrollwin) + self.prepend(toolbar) # Delay the loading of the available effects so the application # starts faster. GLib.idle_add(self._load_available_effects_cb) - scrollwin.show_all() - toolbar.show_all() self.search_view.hide() def _load_available_effects_cb(self): @@ -439,16 +444,16 @@ class EffectListWidget(Gtk.Box, Loggable): # Add category expanders for category in self.app.effects.categories: widget = self._create_category_widget(category) - self.category_view.add(widget) + self.category_view.append(widget) # Add effects to category expanders - for expander in self.category_view.get_children(): + for expander in get_widget_children(self.category_view): listbox = expander.get_child() category_name = expander.get_label() self.add_effects_to_listbox(listbox, category_name) - self.category_view.show_all() + self.category_view.show() def add_effects_to_listbox(self, listbox, category=None, only_text=False): """Adds effect rows to the given listbox.""" @@ -463,16 +468,22 @@ class EffectListWidget(Gtk.Box, Loggable): if not category or category in effect_info.categories: widget = self._create_effect_widget(name, only_text) - listbox.add(widget) + listbox.insert(widget, -1) def _create_category_widget(self, category): """Creates an expander for the given category.""" - expander = Gtk.Expander(label=category, margin=SPACING) + expander = Gtk.Expander( + label=category, + margin_top=SPACING, + margin_bottom=SPACING, + margin_start=SPACING, + margin_end=SPACING + ) listbox = Gtk.ListBox(activate_on_single_click=False) listbox.connect("row-activated", self.effects_listbox_row_activated_cb) - expander.add(listbox) + expander.set_child(listbox) return expander @@ -480,68 +491,90 @@ class EffectListWidget(Gtk.Box, Loggable): """Creates list box row for the given effect.""" effect_info = self.app.effects.get_info(effect_name) - effect_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, margin=SPACING / 2) + effect_box = Gtk.Box( + orientation=Gtk.Orientation.HORIZONTAL, + margin_top=SPACING / 2, + margin_bottom=SPACING / 2, + margin_start=SPACING / 2, + margin_end=SPACING / 2 + ) effect_box.effect_name = effect_name effect_box.set_tooltip_text(effect_info.description) - label = Gtk.Label(effect_info.human_name, xalign=0) + label = Gtk.Label.new(effect_info.human_name) + label.set_xalign(0) + label.set_halign(Gtk.Align.FILL) + label.set_hexpand(True) + + effect_box.prepend(label) if not only_text: # Show effect thumbnail - icon = Gtk.Image.new_from_pixbuf(effect_info.icon) - effect_box.pack_start(icon, False, True, SPACING / 2) + icon = Gtk.Picture.new_for_pixbuf(effect_info.icon) + icon.set_can_shrink(False) + icon.props.margin_top = SPACING / 2 + icon.props.margin_bottom = SPACING / 2 + icon.props.margin_start = SPACING / 2 + icon.props.margin_end = SPACING / 2 + icon.set_halign(Gtk.Align.FILL) + effect_box.prepend(icon) # Set up favourite button fav_button = Gtk.Button() - fav_button.props.relief = Gtk.ReliefStyle.NONE - fav_button.props.halign = Gtk.Align.CENTER + fav_button.set_has_frame(False) + fav_button.props.halign = Gtk.Align.END fav_button.props.valign = Gtk.Align.CENTER + fav_button.set_margin_top(SPACING / 2) + fav_button.set_margin_bottom(SPACING / 2) + fav_button.set_margin_start(SPACING / 2) + fav_button.set_margin_end(SPACING / 2) fav_button.set_tooltip_text(_("Add to Favorites")) starred = effect_name in self.app.settings.favourite_effects self._set_fav_button_state(fav_button, starred) fav_button.connect("clicked", self._fav_button_clicked_cb, effect_box.effect_name) - effect_box.pack_end(fav_button, False, True, SPACING / 2) - - effect_box.pack_start(label, True, True, 0) + effect_box.append(fav_button) # Set up drag behavoir - eventbox = Gtk.EventBox(visible_window=False) - eventbox.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, [EFFECT_TARGET_ENTRY], Gdk.DragAction.COPY) - eventbox.connect("drag-data-get", self._drag_data_get_cb) - eventbox.connect("drag-begin", self._drag_begin_cb) - eventbox.add(effect_box) + eventbox = Gtk.Box() + eventbox.append(effect_box) + eventbox_controller = Gtk.DragSource.new() + eventbox_controller.connect("drag-begin", self._drag_begin_cb) + eventbox_controller.connect("prepare", self._prepare_cb, effect_box) + eventbox.add_controller(eventbox_controller) row = Gtk.ListBoxRow(selectable=False) - row.add(eventbox) - row.show_all() + row.set_child(eventbox) return row - def _drag_data_get_cb(self, eventbox, drag_context, selection_data, unused_info, unused_timestamp): - effect_box = eventbox.get_child() - data = bytes(effect_box.effect_name, "UTF-8") - selection_data.set(drag_context.list_targets()[0], 0, data) + def _prepare_cb(self, drag_source, start_x, start_y, effect_box): + data = GObject.Value(GObject.TYPE_STRING) + data.set_value(str(effect_box.effect_name)) + content_provider = Gdk.ContentProvider.new_for_value(data) + drag_source.set_content(content_provider) + return content_provider - def _drag_begin_cb(self, eventbox, context): - # Draw drag-icon + def _drag_begin_cb(self, drag_source, drag): icon = self._drag_icon icon_height = icon.get_height() icon_width = icon.get_width() - surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, icon_width, icon_height) - ctx = cairo.Context(surface) + snapshot = Gtk.Snapshot.new() + bounds = Graphene.Rect().init(0, 0, 2 * icon_width, 2 * icon_height) + ctx = snapshot.append_cairo(bounds) # Center the icon around the cursor. ctx.translate(icon_width / 2, icon_height / 2) - surface.set_device_offset(-icon_width / 2, -icon_height / 2) Gdk.cairo_set_source_pixbuf(ctx, icon, 0, 0) ctx.paint_with_alpha(0.35) - Gtk.drag_set_icon_surface(context, surface) + paintable = snapshot.to_paintable(Graphene.Size().init(2 * icon_width, 2 * icon_height)) + + drag_source.set_icon(paintable, icon_width, icon_height) def effects_listbox_row_activated_cb(self, listbox, row): """Handles the activation of a row representing an effect.""" - effect_box = row.get_child().get_child() + effect_box = row.get_first_child().get_first_child() self.apply_effect(effect_box.effect_name) def apply_effect(self, effect_name): @@ -567,11 +600,11 @@ class EffectListWidget(Gtk.Box, Loggable): button.active = is_active if button.active: - image = Gtk.Image.new_from_pixbuf(self._star_icon_solid) + image = Gtk.Picture.new_for_pixbuf(self._star_icon_solid) else: - image = Gtk.Image.new_from_pixbuf(self._star_icon_regular) + image = Gtk.Picture.new_for_pixbuf(self._star_icon_regular) - button.props.image = image + button.set_child(image) def _fav_button_clicked_cb(self, clicked_button, effect): """Adds effect to favourites and syncs the state of favourite button.""" @@ -581,16 +614,16 @@ class EffectListWidget(Gtk.Box, Loggable): # Get all listboxes which contain the effect effect_info = self.app.effects.get_info(effect) all_effect_listboxes = [category_expander.get_child() - for category_expander in self.category_view.get_children() + for category_expander in get_widget_children(self.category_view) if category_expander.get_label() in effect_info.categories] all_effect_listboxes.append(self.search_view) # Find and sync state in other listboxes for listbox in all_effect_listboxes: - for row in listbox.get_children(): - effect_box = row.get_child().get_child() + for row in get_listbox_children(listbox): + effect_box = row.get_first_child().get_first_child() if effect == effect_box.effect_name: - fav_button = effect_box.get_children()[2] + fav_button = effect_box.get_last_child() # Sync the state with the clicked button self._set_fav_button_state(fav_button, clicked_button.active) @@ -604,7 +637,7 @@ class EffectListWidget(Gtk.Box, Loggable): def _favourites_filter(self, row): """Filters search_view to show favourites.""" - effect_box = row.get_child().get_child() + effect_box = row.get_first_child().get_first_child() effect_name = effect_box.effect_name return effect_name in self.app.settings.favourite_effects @@ -621,8 +654,8 @@ class EffectListWidget(Gtk.Box, Loggable): def _search_filter(self, row): """Filters search_view to show search results.""" - effect_box = row.get_child().get_child() - label = effect_box.get_children()[1] + effect_box = row.get_first_child().get_first_child() + label = get_widget_children(effect_box)[1] label_text = label.get_text().lower() search_key = self.search_entry.get_text().lower() @@ -639,16 +672,13 @@ class EffectListWidget(Gtk.Box, Loggable): else: self._switch_to_view(self.category_view) - def _search_entry_icon_release_cb(self, entry, icon_pos, event): - entry.set_text("") - def _switch_to_view(self, next_view): """Shows next_view and hides all other views.""" if next_view.props.visible: # It's already visible, no need to switch to it. return - for child_view in self.main_view.get_children(): + for child_view in get_widget_children(self.main_view): child_view.props.visible = child_view == next_view @@ -661,7 +691,13 @@ class EffectsPopover(Gtk.Popover, Loggable): self.app = app - vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, margin=PADDING) + vbox = Gtk.Box( + orientation=Gtk.Orientation.VERTICAL, + margin_top=PADDING, + margin_bottom=PADDING, + margin_start=PADDING, + margin_end=PADDING + ) self.search_entry = Gtk.SearchEntry() self.search_entry.connect("search-changed", self._search_entry_cb) @@ -670,25 +706,26 @@ class EffectsPopover(Gtk.Popover, Loggable): scroll_window.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) scroll_window.props.max_content_height = 350 scroll_window.props.propagate_natural_height = True + scroll_window.set_vexpand(True) + scroll_window.set_valign(Gtk.Align.FILL) self.listbox = Gtk.ListBox() self.listbox.connect("row-activated", self._effect_row_activate_cb) self.listbox.set_filter_func(self._search_filter) - placeholder_text = Gtk.Label(_("No effects")) + placeholder_text = Gtk.Label.new(_("No effects")) placeholder_text.props.visible = True self.listbox.set_placeholder(placeholder_text) self.app.gui.editor.effectlist.add_effects_to_listbox(self.listbox, only_text=True) - scroll_window.add(self.listbox) + scroll_window.set_child(self.listbox) - vbox.pack_start(self.search_entry, False, False, 0) - vbox.pack_end(scroll_window, True, True, 0) - vbox.show_all() + vbox.prepend(self.search_entry) + vbox.append(scroll_window) - self.add(vbox) + self.set_child(vbox) def _effect_row_activate_cb(self, listbox, row): - effect_box = row.get_child().get_child() + effect_box = row.get_first_child().get_first_child() self.app.gui.editor.effectlist.apply_effect(effect_box.effect_name) self.hide() @@ -696,8 +733,8 @@ class EffectsPopover(Gtk.Popover, Loggable): self.listbox.invalidate_filter() def _search_filter(self, row): - effect_box = row.get_child().get_child() - label = effect_box.get_children()[0] + effect_box = row.get_first_child().get_first_child() + label = effect_box.get_first_child() label_text = label.get_text().lower() search_key = self.search_entry.get_text().lower() @@ -774,7 +811,7 @@ class EffectsPropertiesManager(GObject.Object, Loggable): def _post_configuration(self, effect, effect_set_ui): effect_name = effect.get_property("bin-description") if 'aspectratiocrop' in effect.get_property("bin-description"): - for widget in effect_set_ui.get_children()[0].get_children(): + for widget in get_widget_children(effect_set_ui.get_first_child()): if isinstance(widget, FractionWidget): widget.add_presets(["4:3", "5:4", "9:3", "16:9", "16:10"]) else: diff --git a/pitivi/greeterperspective.py b/pitivi/greeterperspective.py index a575fb0befc07c0f99403e133d35959b944426d9..b83c711ce54e2c667c926a299485da91dbe85721 100644 --- a/pitivi/greeterperspective.py +++ b/pitivi/greeterperspective.py @@ -33,11 +33,10 @@ from pitivi.project import Project from pitivi.utils.ui import beautify_last_updated_timestamp from pitivi.utils.ui import beautify_project_path from pitivi.utils.ui import BinWithNaturalWidth -from pitivi.utils.ui import fix_infobar +from pitivi.utils.ui import get_listbox_children from pitivi.utils.ui import GREETER_PERSPECTIVE_CSS from pitivi.utils.ui import PADDING from pitivi.utils.ui import SPACING -from pitivi.utils.ui import URI_TARGET_ENTRY MAX_RECENT_PROJECTS = 10 @@ -56,7 +55,7 @@ class ProjectInfoRow(Gtk.ListBoxRow): builder = Gtk.Builder() builder.add_from_file(os.path.join(get_ui_dir(), "project_info.ui")) - self.add(builder.get_object("project_info_tophbox")) + self.set_child(builder.get_object("project_info_tophbox")) self.select_button = builder.get_object("project_select_button") # Hide the select button as we only want to @@ -71,10 +70,10 @@ class ProjectInfoRow(Gtk.ListBoxRow): builder.get_object("project_uri_label").set_text( beautify_project_path(recent_project_item.get_uri_display())) builder.get_object("project_last_updated_label").set_text( - beautify_last_updated_timestamp(recent_project_item.get_modified())) + beautify_last_updated_timestamp(recent_project_item.get_modified().to_unix())) def __load_thumb_cb(self): - self.__thumb.set_from_pixbuf(Project.get_thumb(self.uri)) + self.__thumb.set_paintable(Project.get_thumb(self.uri)) return False @@ -124,14 +123,16 @@ class GreeterPerspective(Perspective): builder.add_from_file(os.path.join(get_ui_dir(), "greeter.ui")) logo = builder.get_object("logo") - icon_theme = Gtk.IconTheme.get_default() - pixbuf = icon_theme.load_icon("org.pitivi.Pitivi", 256, Gtk.IconLookupFlags.FORCE_SIZE) - logo.set_from_pixbuf(pixbuf) + icon_theme = Gtk.IconTheme.get_for_display(Gdk.Display.get_default()) + paintable = icon_theme.lookup_icon("org.pitivi.Pitivi", "", 256, 1, Gtk.TextDirection.NONE, 0) + logo.set_from_paintable(paintable) + logo.set_pixel_size(256) self.toplevel_widget = builder.get_object("toplevel_vbox") - self.toplevel_widget.drag_dest_set( - Gtk.DestDefaults.ALL, [URI_TARGET_ENTRY], Gdk.DragAction.COPY) - self.toplevel_widget.connect("drag-data-received", self.__drag_data_received_cb) + + drop_target = Gtk.DropTarget.new(Gdk.FileList, Gdk.DragAction.COPY) + drop_target.connect("drop", self._drop_cb) + self.toplevel_widget.add_controller(drop_target) self.__topvbox = builder.get_object("topvbox") self.__welcome_vbox = builder.get_object("welcome_vbox") @@ -146,17 +147,19 @@ class GreeterPerspective(Perspective): self.__recent_projects_listbox.set_selection_mode(Gtk.SelectionMode.NONE) self.__recent_projects_listbox.connect( "row-activated", self.__projects_row_activated_cb) - self.__recent_projects_listbox.connect( - "button-press-event", self.__projects_button_press_cb) + + listbox_eventcontroller = Gtk.GestureClick() + listbox_eventcontroller.connect("pressed", self.__projects_button_press_cb) + listbox_eventcontroller.set_button(3) + self.__recent_projects_listbox.add_controller(listbox_eventcontroller) self.__infobar = builder.get_object("infobar") - fix_infobar(self.__infobar) - self.__infobar.hide() + self.__infobar.set_revealed(False) self.__infobar.connect("response", self.__infobar_response_cb) self.__actionbar = builder.get_object("actionbar") self.__remove_projects_button = builder.get_object("remove_projects_button") - self.__remove_projects_button.get_style_context().add_class("destructive-action") + self.__remove_projects_button.add_css_class("destructive-action") self.__remove_projects_button.connect("clicked", self.__remove_projects_clicked_cb) self.__setup_css() @@ -171,7 +174,7 @@ class GreeterPerspective(Perspective): self.__selected_projects = [] # Clear the currently displayed list of recent projects. - for child in self.__recent_projects_listbox.get_children(): + while child := self.__recent_projects_listbox.get_row_at_index(0): self.__recent_projects_listbox.remove(child) recent_items = [item for item in self.app.recent_manager.get_items() @@ -183,7 +186,7 @@ class GreeterPerspective(Perspective): recent_project_info = ProjectInfoRow(item) recent_project_info.select_button.connect( "toggled", self.__project_selected_cb, recent_project_info) - self.__recent_projects_listbox.add(recent_project_info) + self.__recent_projects_listbox.insert(recent_project_info, -1) recent_project_info.show() child = self.__recent_projects_vbox @@ -193,16 +196,15 @@ class GreeterPerspective(Perspective): child = self.__welcome_vbox self.__update_headerbar(welcome=True) - children = self.__topvbox.get_children() - if children: - current_child = children[0] + current_child = self.__topvbox.get_first_child() + if current_child: if current_child == child: child = None else: self.__topvbox.remove(current_child) if child: - self.__topvbox.pack_start(child, False, False, 0) + self.__topvbox.prepend(child) if recent_items: self.__search_entry.show() @@ -217,10 +219,10 @@ class GreeterPerspective(Perspective): def __setup_css(self): css_provider = Gtk.CssProvider() css_provider.load_from_data(GREETER_PERSPECTIVE_CSS.encode('UTF-8')) - screen = Gdk.Screen.get_default() + display = Gdk.Display.get_default() style_context = self.app.gui.get_style_context() - style_context.add_provider_for_screen(screen, css_provider, - Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION) + style_context.add_provider_for_display(display, css_provider, + Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION) def __create_headerbar(self): headerbar = Gtk.HeaderBar() @@ -233,8 +235,7 @@ class GreeterPerspective(Perspective): self.__open_project_button.set_tooltip_text(_("Open an existing project")) self.__open_project_button.set_action_name("greeter.open-project") - self.__selection_button = Gtk.Button.new_from_icon_name("object-select-symbolic", - Gtk.IconSize.BUTTON) + self.__selection_button = Gtk.Button.new_from_icon_name("object-select-symbolic") self.__selection_button.set_tooltip_text(_("Select projects for removal")) self.__selection_button.connect("clicked", self.__selection_clicked_cb) @@ -242,7 +243,7 @@ class GreeterPerspective(Perspective): self.__cancel_button.connect("clicked", self.__cancel_clicked_cb) self.__warnings_button = Gtk.MenuButton.new() - self.__warnings_button.props.image = Gtk.Image.new_from_icon_name("warning-symbolic", Gtk.IconSize.BUTTON) + self.__warnings_button.set_icon_name("warning-symbolic") self.__warnings_button.set_popover(self.__create_warnings_popover()) self.menu_button = self.__create_menu() @@ -264,18 +265,19 @@ class GreeterPerspective(Perspective): self.__cancel_button.set_visible(selection) self.__selection_button.set_visible(projects) self.menu_button.set_visible(welcome or projects) - self.headerbar.set_show_close_button(welcome or projects) + self.headerbar.set_show_title_buttons(welcome or projects) self.__warnings_button.set_visible((welcome or projects) and MISSING_SOFT_DEPS) if selection: - self.headerbar.get_style_context().add_class("selection-mode") - self.headerbar.set_title(_("Click an item to select")) + self.headerbar.add_css_class("selection-mode") + self.headerbar.set_title_widget(Gtk.Label.new(_("Click an item to select"))) else: - self.headerbar.get_style_context().remove_class("selection-mode") + self.headerbar.remove_css_class("selection-mode") if projects: - self.headerbar.set_title(_("Select a Project")) + self.headerbar.set_title_widget(Gtk.Label.new(_("Select a Project"))) else: - self.headerbar.set_title(_("Pitivi")) + self.headerbar.set_title_widget(Gtk.Label.new(_("Pitivi"))) + self.headerbar.add_css_class("title") def _create_actions(self): group = Gio.SimpleActionGroup() @@ -309,37 +311,38 @@ class GreeterPerspective(Perspective): builder.add_from_file(os.path.join(get_ui_dir(), "mainmenubutton.ui")) menu_button = builder.get_object("menubutton") # Menu options we want to display. - visible_options = ["menu_shortcuts", "menu_help", "menu_about"] - for widget in builder.get_object("menu_box").get_children(): - if Gtk.Buildable.get_name(widget) not in visible_options: - widget.hide() - else: - visible_options.remove(Gtk.Buildable.get_name(widget)) - assert not visible_options + menu_model = builder.get_object("greeter_menu_model") + menu_button.set_menu_model(menu_model) return menu_button - def __drag_data_received_cb(self, unused_widget, unused_context, unused_x, - unused_y, data, unused_info, unused_time): + def _drop_cb(self, drop_target, value, x, y): """Opens the project file dragged from Nautilus.""" - uris = data.get_uris() - if not uris: - return + files = value.get_files() + if not files: + return False + uris = [file.get_uri() for file in files] uri = uris[0] extension = os.path.splitext(uri)[1][1:] if extension in self.__project_filter: self.app.project_manager.load_project(uri) + return True + def __new_project_cb(self, unused_action, unused_param): self.app.project_manager.new_blank_project() def __open_project_cb(self, unused_action, unused_param): dialog = BrowseProjectsDialog(self.app) - response = dialog.run() - uri = dialog.get_uri() - dialog.destroy() - if response == Gtk.ResponseType.OK: + dialog.connect("response", self._browse_projects_dialog_cb) + dialog.show() + + def _browse_projects_dialog_cb(self, dialog, response_id): + gfile = dialog.get_file() + uri = gfile.get_uri() + if response_id == Gtk.ResponseType.OK: self.app.project_manager.load_project(uri) + dialog.close() def __app_version_info_received_cb(self, app, unused_version_information): """Handles new version info.""" @@ -363,14 +366,14 @@ class GreeterPerspective(Perspective): # increment counter, create infobar and show info self.app.settings.displayCounter += 1 text = _("Pitivi %s is available.") % latest_version - label = Gtk.Label(label=text) - self.__infobar.get_content_area().add(label) + label = Gtk.Label.new(text) + self.__infobar.add_child(label) self.__infobar.set_message_type(Gtk.MessageType.INFO) - self.__infobar.show_all() + self.__infobar.show() def __infobar_response_cb(self, unused_infobar, response_id): if response_id == Gtk.ResponseType.CLOSE: - self.__infobar.hide() + self.__infobar.set_revealed(False) def __projects_row_activated_cb(self, unused_listbox, row): if row.select_button.get_visible(): @@ -378,19 +381,18 @@ class GreeterPerspective(Perspective): else: self.app.project_manager.load_project(row.uri) - def __projects_button_press_cb(self, listbox, event): - if event.button == 3: - self.__start_selection_mode() - row = listbox.get_row_at_y(event.y) - if row: - row.select_button.set_active(True) + def __projects_button_press_cb(self, controller, n_pressed, x, y): + self.__start_selection_mode() + listbox = controller.get_widget() + row = listbox.get_row_at_y(y) + if row: + row.select_button.set_active(True) def __search_changed_cb(self, search_entry): search_hit = False search_text = search_entry.get_text().lower() - style_context = search_entry.get_style_context() - for recent_project_item in self.__recent_projects_listbox.get_children(): + for recent_project_item in get_listbox_children(self.__recent_projects_listbox): if search_text in recent_project_item.name.lower(): recent_project_item.show() search_hit = True @@ -398,9 +400,9 @@ class GreeterPerspective(Perspective): recent_project_item.hide() if not search_hit: - style_context.add_class("error") + search_entry.add_css_class("error") else: - style_context.remove_class("error") + search_entry.remove_css_class("error") self.__recent_projects_labelbox.set_visible(search_hit) self.__recent_projects_listbox.set_visible(search_hit) @@ -415,7 +417,7 @@ class GreeterPerspective(Perspective): self.__update_headerbar(selection=True) self.__search_entry.hide() self.__actionbar.show() - for child in self.__recent_projects_listbox.get_children(): + for child in get_listbox_children(self.__recent_projects_listbox): child.select_button.show() def __cancel_clicked_cb(self, unused_button): @@ -437,13 +439,19 @@ class GreeterPerspective(Perspective): def __create_warnings_popover(self): """Creates a popover listing missing soft dependencies.""" popover = Gtk.Popover() - box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, margin=PADDING * 3) - - label = Gtk.Label(_("To enable additional features, please install the following packages and restart Pitivi:")) + box = Gtk.Box( + orientation=Gtk.Orientation.VERTICAL, + margin_top=PADDING * 3, + margin_bottom=PADDING * 3, + margin_start=PADDING * 3, + margin_end=PADDING * 3 + ) + + label = Gtk.Label.new(_("To enable additional features, please install the following packages and restart Pitivi:")) label.props.halign = Gtk.Align.START label.props.wrap = True label.props.xalign = 0 - box.pack_start(label, False, False, 0) + box.prepend(label) grid = Gtk.Grid() grid.props.row_spacing = SPACING @@ -452,27 +460,26 @@ class GreeterPerspective(Perspective): grid.props.margin_top = SPACING * 2 for row_index, dep in enumerate(MISSING_SOFT_DEPS.values()): - name_label = Gtk.Label(dep.modulename) + name_label = Gtk.Label.new(dep.modulename) name_label.props.selectable = True name_label.props.can_focus = False name_label.props.xalign = 0 name_label.props.valign = Gtk.Align.START grid.attach(name_label, 0, row_index, 1, 1) - mdash_label = Gtk.Label("―") + mdash_label = Gtk.Label.new("―") mdash_label.props.xalign = 0 mdash_label.props.valign = Gtk.Align.START grid.attach(mdash_label, 1, row_index, 1, 1) - description_label = Gtk.Label(dep.additional_message) + description_label = Gtk.Label.new(dep.additional_message) description_label.props.wrap = True description_label.props.xalign = 0 description_label.props.yalign = Gtk.Align.START grid.attach(description_label, 2, row_index, 1, 1) - box.pack_start(grid, False, False, 0) + box.prepend(grid) wrapper_bin = BinWithNaturalWidth(box, width=500) - wrapper_bin.show_all() - popover.add(wrapper_bin) + popover.set_child(wrapper_bin) return popover diff --git a/pitivi/interactiveintro.py b/pitivi/interactiveintro.py index ef2a660859b94e3178f3fe9b2d23ecf77f7629f7..2e3860aa2df983e24d1fc9a36013a3abdc19cae0 100644 --- a/pitivi/interactiveintro.py +++ b/pitivi/interactiveintro.py @@ -38,11 +38,6 @@ INTERACTIVE_INTRO_CSS = """ margin: 10px; } - -#intro-popover { - background-color: @theme_selected_bg_color; -} - #intro-popover > box { margin: 10px; } @@ -86,18 +81,15 @@ class InteractiveIntro(GObject.Object): def __setup_css(self): css_provider = Gtk.CssProvider() css_provider.load_from_data(INTERACTIVE_INTRO_CSS.encode("UTF-8")) - screen = Gdk.Screen.get_default() + display = Gdk.Display.get_default() style_context = self.app.gui.get_style_context() - style_context.add_provider_for_screen(screen, css_provider, - Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION) + style_context.add_provider_for_display(display, css_provider, + Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION) def __create_intro_button(self): """Creates a button for controlling the intro.""" - button = Gtk.Button.new_from_icon_name( - "org.pitivi.Pitivi-symbolic", Gtk.IconSize.LARGE_TOOLBAR) - button.set_always_show_image(True) - button.props.no_show_all = True - button.props.relief = Gtk.ReliefStyle.NONE + button = Gtk.Button.new_from_icon_name("org.pitivi.Pitivi-symbolic") + button.set_has_frame(False) # We could set_action_name, but we don't know here the group # in which the action will be added, so it's simpler this way. button.connect("clicked", self._intro_button_clicked_cb) @@ -106,23 +98,30 @@ class InteractiveIntro(GObject.Object): def __create_popover(self, text): popover = Gtk.Popover() popover.set_position(Gtk.PositionType.BOTTOM) - popover.set_modal(True) + popover.set_autohide(True) popover.connect("closed", self._intro_popover_closed_cb) popover.set_name("intro-popover") label = Gtk.Label() label.set_markup(text) - label.set_line_wrap(True) + label.set_wrap(True) label.set_max_width_chars(24) + label.props.margin_top = 5 + label.props.margin_bottom = 5 + label.props.margin_start = 5 + label.props.margin_end = 5 - img = Gtk.Image.new_from_icon_name("dialog-information-symbolic", Gtk.IconSize.DIALOG) + img = Gtk.Image.new_from_icon_name("dialog-information-symbolic") + img.props.margin_top = 5 + img.props.margin_bottom = 5 + img.props.margin_start = 5 + img.props.margin_end = 5 vbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) - vbox.pack_start(img, False, False, 5) - vbox.pack_end(label, False, False, 5) - vbox.show_all() + vbox.prepend(img) + vbox.prepend(label) - popover.add(vbox) + popover.set_child(vbox) return popover @@ -152,39 +151,44 @@ class InteractiveIntro(GObject.Object): def show_control_popover(self): # pylint: disable=attribute-defined-outside-init overview_button = Gtk.Button.new_with_label(_("Start Overview")) + overview_button.props.margin_top = 10 + overview_button.props.margin_bottom = 10 + overview_button.props.margin_start = 10 + overview_button.props.margin_end = 10 + overview_button.set_halign(Gtk.Align.FILL) overview_button.connect("clicked", self._overview_button_clicked_cb) vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) - vbox.pack_start(overview_button, False, True, 10) + vbox.prepend(overview_button) self.intro_button.show() - self.intro_button.get_style_context().add_class("intro-highlight") + self.intro_button.add_css_class("intro-highlight") self.control_popover = Gtk.Popover() self.control_popover.set_name("control-popover") - self.control_popover.add(vbox) - self.control_popover.set_relative_to(self.intro_button) - self.control_popover.show_all() + self.control_popover.set_child(vbox) + self.intro_button.set_child(self.control_popover) self.control_popover.popup() self.control_popover.connect("closed", self._control_popover_closed_cb) def _control_popover_closed_cb(self, unused_widget): - self.intro_button.get_style_context().remove_class("intro-highlight") + self.intro_button.remove_css_class("intro-highlight") def set_current_widget(self, widget): """Switches the focus to a new widget, highlighting it.""" if self.widget: - self.widget.get_style_context().remove_class("intro-highlight") + self.widget.remove_css_class("intro-highlight") self.widget = None if widget: self.widget = widget - self.widget.get_style_context().add_class("intro-highlight") + self.widget.add_css_class("intro-highlight") - if isinstance(self.widget, Gtk.ModelButton): - self.parent_widget = self._find_parent(self.widget, Gtk.Popover) - if self.parent_widget: - self.parent_widget.popup() + if isinstance(self.widget, Gtk.Button): + if self.widget == self.app.gui.editor.keyboard_shortcuts_button: + self.parent_widget = self._find_parent(self.widget, Gtk.Popover) + if self.parent_widget: + self.parent_widget.popup() def _find_parent(self, widget, parent_class): while widget: @@ -243,7 +247,7 @@ class InteractiveIntro(GObject.Object): self.set_current_widget(widget) popover = self.__create_popover(text) - popover.set_relative_to(widget) + popover.set_parent(widget) if center: rect = Gdk.Rectangle() diff --git a/pitivi/mainwindow.py b/pitivi/mainwindow.py index f102e9bb6d9a317318885be53fa00dacd3221d85..ef3229dfc374451ceececc611764b3889d73db9b 100644 --- a/pitivi/mainwindow.py +++ b/pitivi/mainwindow.py @@ -19,6 +19,7 @@ import os from gettext import gettext as _ from urllib.parse import unquote +from gi.repository import Gdk from gi.repository import Gio from gi.repository import GLib from gi.repository import Gtk @@ -33,12 +34,6 @@ from pitivi.utils.loggable import Loggable from pitivi.utils.misc import show_user_manual -GlobalSettings.add_config_option('mainWindowX', - section="main-window", - key="X", default=0, type_=int) -GlobalSettings.add_config_option('mainWindowY', - section="main-window", - key="Y", default=0, type_=int) GlobalSettings.add_config_option('mainWindowWidth', section="main-window", key="width", default=-1, type_=int) @@ -91,8 +86,9 @@ class MainWindow(Gtk.ApplicationWindow, Loggable): os.environ["PULSE_PROP_media.role"] = "production" os.environ["PULSE_PROP_application.icon_name"] = "pitivi" - Gtk.IconTheme.get_default().append_search_path(get_pixmap_dir()) - Gtk.IconTheme.get_default().append_search_path(os.path.join(get_pixmap_dir(), "transitions")) + icon_theme = Gtk.IconTheme.get_for_display(Gdk.Display.get_default()) + icon_theme.add_search_path(get_pixmap_dir()) + icon_theme.add_search_path(os.path.join(get_pixmap_dir(), "transitions")) Gtk.ApplicationWindow.__init__(self) Loggable.__init__(self) @@ -146,20 +142,17 @@ class MainWindow(Gtk.ApplicationWindow, Loggable): # a bit different than opening `pitivi file.xges`. For example # connecting to the "realize" signal instead of idle_add-ing # fails to restore the position when directly loading a project. - GLib.idle_add(self.__initial_placement_cb, - self.app.settings.mainWindowX, - self.app.settings.mainWindowY, - width, height) + GLib.idle_add(self.__initial_placement_cb, width, height) self.check_screen_constraints() - self.connect("configure-event", self.__configure_cb) - self.connect("delete-event", self.__delete_cb) + self.connect("notify::default-width", self.__configure_width_cb) + self.connect("notify::default-height", self.__configure_height_cb) + self.connect("close_request", self.__delete_cb) - def __initial_placement_cb(self, x, y, width, height): + def __initial_placement_cb(self, width, height): self.__placed = True - self.resize(width, height) - self.move(x, y) + self.set_default_size(width, height) if self.__wanted_perspective: self.show_perspective(self.__wanted_perspective) self.__wanted_perspective = None @@ -179,8 +172,8 @@ class MainWindow(Gtk.ApplicationWindow, Loggable): # a way, even with client-side decorations, to tell us the exact # minimum required dimensions of a window. min_size, _ = self.get_preferred_size() - screen_width = self.get_screen().get_width() - screen_height = self.get_screen().get_height() + screen_width = self.get_width() + screen_height = self.get_height() self.debug("Minimum UI size is %sx%s", min_size.width, min_size.height) self.debug("Screen size is %sx%s", screen_width, screen_height) return min_size.width >= 0.9 * screen_width @@ -229,19 +222,20 @@ class MainWindow(Gtk.ApplicationWindow, Loggable): self.__perspective.menu_button.set_active(active) def __preferences_cb(self, unused_action, unused_param): - PreferencesDialog(self.app).run() + dialog = PreferencesDialog(self.app) + dialog.run() - def __configure_cb(self, unused_widget, unused_event): - """Saves the main window position and size.""" - position = self.get_position() - self.app.settings.mainWindowX = position.root_x - self.app.settings.mainWindowY = position.root_y + def __configure_height_cb(self, unused_widget, unused_pspec): + """Saves the main window height.""" + size_height = self.get_height() + self.app.settings.mainWindowHeight = size_height - size = self.get_size() - self.app.settings.mainWindowWidth = size.width - self.app.settings.mainWindowHeight = size.height + def __configure_width_cb(self, unused_widget, unused_pspec): + """Saves the main window width.""" + size_width = self.get_width() + self.app.settings.mainWindowWidth = size_width - def __delete_cb(self, unused_widget, unused_event): + def __delete_cb(self, unused_event): self.app.settings.mainWindowHPanePosition = self.editor.secondhpaned.get_position() self.app.settings.mainWindowMainHPanePosition = self.editor.mainhpaned.get_position() self.app.settings.mainWindowVPanePosition = self.editor.toplevel_widget.get_position() @@ -263,10 +257,13 @@ class MainWindow(Gtk.ApplicationWindow, Loggable): dialog.set_property("secondary-use-markup", True) dialog.set_property("secondary-text", unquote(str(reason))) dialog.set_transient_for(self) - dialog.run() - dialog.destroy() + dialog.connect("response", self._new_project_failed_dialog_cb) + dialog.show() self.show_perspective(self.greeter) + def _new_project_failed_dialog_cb(self, dialog, response_id): + dialog.destroy() + def __project_closed_cb(self, unused_project_manager, unused_project): self.show_perspective(self.greeter) @@ -284,12 +281,12 @@ class MainWindow(Gtk.ApplicationWindow, Loggable): # Remove the current perspective before adding the # specified perspective because we can only add one # toplevel widget to the main window at a time. - self.remove(self.__perspective.toplevel_widget) + self.set_child() self.log("Displaying perspective: %s", type(perspective).__name__) self.__perspective = perspective self.set_titlebar(perspective.headerbar) # The window must be shown only after setting the headerbar with # set_titlebar. Otherwise we get a warning things can go wrong. self.show() - self.add(perspective.toplevel_widget) + self.set_child(perspective.toplevel_widget) perspective.refresh() diff --git a/pitivi/mediafilespreviewer.py b/pitivi/mediafilespreviewer.py index a7e831c207e630052ac655072438f8f11894f8a0..20b3ae0dffeef73388bd892104615e56e1d91cde 100644 --- a/pitivi/mediafilespreviewer.py +++ b/pitivi/mediafilespreviewer.py @@ -18,7 +18,6 @@ import html from gettext import gettext as _ -from gi.repository import Gdk from gi.repository import GdkPixbuf from gi.repository import GES from gi.repository import GLib @@ -104,7 +103,7 @@ class PreviewWidget(Gtk.Grid, Loggable): self.preview_video = ViewerWidget(sink_widget) self.preview_video.props.hexpand = minimal self.preview_video.props.vexpand = minimal - self.preview_video.show_all() + self.preview_video.show() self.attach(self.preview_video, 0, 0, 1, 1) # An image for images and audio @@ -117,31 +116,35 @@ class PreviewWidget(Gtk.Grid, Loggable): # Play button self.bbox = Gtk.Box() self.bbox.set_orientation(Gtk.Orientation.HORIZONTAL) - self.play_button = Gtk.ToolButton() + self.play_button = Gtk.Button() self.play_button.set_icon_name("media-playback-start") self.play_button.connect("clicked", self._on_start_stop_clicked_cb) - self.bbox.pack_start(self.play_button, False, False, 0) + self.bbox.prepend(self.play_button) # Scale for position handling self.pos_adj = Gtk.Adjustment() self.seeker = Gtk.Scale.new(Gtk.Orientation.HORIZONTAL, self.pos_adj) - self.seeker.connect('button-press-event', self._on_seeker_press_cb) - self.seeker.connect('button-release-event', self._on_seeker_press_cb) - self.seeker.connect('motion-notify-event', self._on_motion_notify_cb) - self.seeker.set_draw_value(False) + self.seeker.set_hexpand(True) + self.seeker.set_halign(Gtk.Align.FILL) + seeker_eventcontroller_motion = Gtk.EventControllerMotion() + seeker_eventcontroller_click = Gtk.GestureClick() + seeker_eventcontroller_click.connect('pressed', self._on_seeker_press_cb) + seeker_eventcontroller_click.connect('released', self._on_seeker_release_cb) + seeker_eventcontroller_motion.connect('motion', self._on_motion_notify_cb) + self.seeker.add_controller(seeker_eventcontroller_click) + self.seeker.add_controller(seeker_eventcontroller_motion) self.seeker.show() - self.bbox.pack_start(self.seeker, True, True, 0) + self.bbox.prepend(self.seeker) # Zoom buttons - self.b_zoom_in = Gtk.ToolButton() + self.b_zoom_in = Gtk.Button() self.b_zoom_in.set_icon_name("zoom-in") self.b_zoom_in.connect("clicked", self._on_zoom_clicked_cb, 1) - self.b_zoom_out = Gtk.ToolButton() + self.b_zoom_out = Gtk.Button() self.b_zoom_out.set_icon_name("zoom-out") self.b_zoom_out.connect("clicked", self._on_zoom_clicked_cb, -1) - self.bbox.pack_start(self.b_zoom_in, False, False, 0) - self.bbox.pack_start(self.b_zoom_out, False, False, 0) - self.bbox.show_all() + self.bbox.prepend(self.b_zoom_in) + self.bbox.prepend(self.b_zoom_out) self.attach(self.bbox, 0, 2, 1, 1) # Label for metadata tags @@ -155,11 +158,13 @@ class PreviewWidget(Gtk.Grid, Loggable): vbox = Gtk.Box() vbox.set_orientation(Gtk.Orientation.VERTICAL) vbox.set_spacing(SPACING) - self.l_error = Gtk.Label(label=_("Pitivi can not preview this file.")) + self.l_error = Gtk.Label.new(_("Pitivi can not preview this file.")) + self.l_error.set_hexpand(True) + self.l_error.set_halign(Gtk.Align.FILL) self.b_details = Gtk.Button.new_with_label(_("More info")) self.b_details.connect('clicked', self._on_b_details_clicked_cb) - vbox.pack_start(self.l_error, True, True, 0) - vbox.pack_start(self.b_details, False, False, 0) + vbox.prepend(self.l_error) + vbox.prepend(self.b_details) vbox.show() self.attach(vbox, 0, 4, 1, 1) @@ -258,7 +263,7 @@ class PreviewWidget(Gtk.Grid, Loggable): video_height = video.get_natural_height() w, h = self.__get_best_size(video_width, video_height) self.preview_video.set_size_request(w, h) - self.preview_video.props.ratio = video_width / video_height + self.preview_video.ratio = video_width / video_height self.preview_video.show() self.bbox.show() self.play_button.show() @@ -280,7 +285,7 @@ class PreviewWidget(Gtk.Grid, Loggable): audio = audio[0] self.pos_adj.props.upper = duration self.preview_image.set_from_icon_name( - "audio-x-generic", Gtk.IconSize.DIALOG) + "audio-x-generic", Gtk.IconSize.LARGE) self.preview_image.show() self.preview_image.set_size_request(PREVIEW_WIDTH, PREVIEW_HEIGHT) self.description = "\n".join([ @@ -342,25 +347,26 @@ class PreviewWidget(Gtk.Grid, Loggable): self.preview_image.hide() self.preview_video.hide() - def _on_seeker_press_cb(self, widget, event): + def _on_seeker_press_cb(self, controller, n_pressed, x, y): self.slider_being_used = True - if event.type == Gdk.EventType.BUTTON_PRESS: - self.countinuous_seek = True - if self.is_playing: - self.player.set_simple_state(Gst.State.PAUSED) - elif event.type == Gdk.EventType.BUTTON_RELEASE: - self.countinuous_seek = False - value = int(widget.get_value()) - self.player.simple_seek(value) - self.at_eos = False - if self.is_playing: - self.player.set_simple_state(Gst.State.PLAYING) - # Now, allow gobject timeout to continue updating the slider pos: - self.slider_being_used = False + self.countinuous_seek = True + if self.is_playing: + self.player.set_simple_state(Gst.State.PAUSED) + + def _on_seeker_release_cb(self, controller, n_pressed, x, y): + self.slider_being_used = True + self.countinuous_seek = False + value = int(self.seeker.get_value()) + self.player.simple_seek(value) + self.at_eos = False + if self.is_playing: + self.player.set_simple_state(Gst.State.PLAYING) + # Now, allow gobject timeout to continue updating the slider pos: + self.slider_being_used = False - def _on_motion_notify_cb(self, widget, event): + def _on_motion_notify_cb(self, controller, x, y): if self.countinuous_seek: - value = int(widget.get_value()) + value = int(self.seeker.get_value()) self.player.simple_seek(value) self.at_eos = False @@ -398,9 +404,9 @@ class PreviewWidget(Gtk.Grid, Loggable): self.settings.FCpreviewWidth = int(w) self.settings.FCpreviewHeight = int(h) elif self.current_preview_type == 'image': - pixbuf = self.preview_image.get_pixbuf() - w = pixbuf.get_width() - h = pixbuf.get_height() + paintable = self.preview_image.get_paintable() + w = paintable.get_intrinsic_width() + h = paintable.get_intrinsic_height() if increment > 0: w *= 1.2 h *= 1.2 @@ -459,8 +465,11 @@ class PreviewWidget(Gtk.Grid, Loggable): text=self.error_message) dialog.set_icon_name("pitivi") dialog.set_title(_("Error while analyzing a file")) - dialog.run() - dialog.destroy() + + def dialog_response_cb(dialog, response_id): + dialog.destroy() + dialog.connect("response", dialog_response_cb) + dialog.show() def do_destroy(self): """Handles the destruction of the widget.""" diff --git a/pitivi/medialibrary.py b/pitivi/medialibrary.py index cc7a44e97b356f7afd7a93400eaa440965409fc9..eb95472296502e6f0ec9bc41f4f1b32cca6a4e75 100644 --- a/pitivi/medialibrary.py +++ b/pitivi/medialibrary.py @@ -26,15 +26,16 @@ from enum import IntEnum from gettext import gettext as _ from gettext import ngettext from hashlib import md5 +from typing import Any from typing import Set -import cairo from gi.repository import Gdk from gi.repository import GdkPixbuf from gi.repository import GES from gi.repository import Gio from gi.repository import GLib from gi.repository import GObject +from gi.repository import Graphene from gi.repository import Gst from gi.repository import GstPbutils from gi.repository import Gtk @@ -44,7 +45,6 @@ from pitivi.configure import get_pixmap_dir from pitivi.configure import get_ui_dir from pitivi.dialogs.clipmediaprops import ClipMediaPropsDialog from pitivi.dialogs.filelisterrordialog import FileListErrorDialog -from pitivi.mediafilespreviewer import PreviewWidget from pitivi.settings import GlobalSettings from pitivi.timeline.previewers import AssetPreviewer from pitivi.utils.loggable import Loggable @@ -57,15 +57,18 @@ from pitivi.utils.proxy import get_proxy_target from pitivi.utils.proxy import ProxyingStrategy from pitivi.utils.ui import beautify_asset from pitivi.utils.ui import beautify_eta -from pitivi.utils.ui import FILE_TARGET_ENTRY -from pitivi.utils.ui import filter_unsupported_media_files -from pitivi.utils.ui import fix_infobar from pitivi.utils.ui import info_name from pitivi.utils.ui import LARGE_THUMB_WIDTH from pitivi.utils.ui import PADDING from pitivi.utils.ui import SMALL_THUMB_WIDTH from pitivi.utils.ui import SPACING -from pitivi.utils.ui import URI_TARGET_ENTRY +# ToDo: Fix Import Dialog PreviewWidget and filter +# from pitivi.mediafilespreviewer import PreviewWidget + +SUPPORTED_EXTENSIONS = ["3g2", "3gp", "asf", "asx", "av1", "avi", "dv", "flv", "m2t", "m2ts", "m2v", "m4v", + "mkv", "mov", "mp2", "mp4", "mpg", "mpeg", "mts", "mxf", "ogg", "ogv", "rm", "rmvb", + "ts", "vob", "webm", "wmv", "png", "jpg", "jpeg", "gif", "bmp", "svg", "svgz", "tiff", + "tif", "ico", "xpm", "pbm", "pgm", "ppm", "rgb", "xbm", "xpm", "xwd"] class ViewType(IntEnum): @@ -113,6 +116,14 @@ class AssetStoreItem(GObject.GObject): else: self.tags = set(tags.split(",")) + def compare_alphabetical(self, other: "AssetStoreItem", _data: Any): + if self.uri < other.uri: + return -1 + elif self.uri == other.uri: + return 0 + else: + return 1 + class TagState(IntEnum): """How the tag is associated with assets under selection.""" @@ -147,7 +158,7 @@ class FileChooserExtraWidget(Gtk.Box, Loggable): self.__keep_open_check.props.valign = Gtk.Align.START self.__keep_open_check.set_tooltip_text(_("When importing files keep the dialog open")) self.__keep_open_check.set_active(not self.app.settings.closeImportDialog) - self.pack_start(self.__keep_open_check, expand=False, fill=False, padding=0) + self.prepend(self.__keep_open_check) self.hq_proxy_check = Gtk.CheckButton.new() # Translators: Create optimized media for unsupported files. @@ -158,11 +169,19 @@ class FileChooserExtraWidget(Gtk.Box, Loggable): self.hq_combo.insert_text(OptimizeOption.UNSUPPORTED_ASSETS, _("Unsupported assets")) self.hq_combo.insert_text(OptimizeOption.ALL, _("All")) self.hq_combo.props.active = OptimizeOption.UNSUPPORTED_ASSETS + self.hq_combo.props.margin_top = PADDING + self.hq_combo.props.margin_bottom = PADDING + self.hq_combo.props.margin_start = PADDING + self.hq_combo.props.margin_end = PADDING self.hq_combo.set_sensitive(False) self.help_button = Gtk.Button() self.__update_help_button() - self.help_button.props.relief = Gtk.ReliefStyle.NONE + self.help_button.set_has_frame(False) + self.help_button.props.margin_top = SPACING + self.help_button.props.margin_bottom = SPACING + self.help_button.props.margin_start = SPACING + self.help_button.props.margin_end = SPACING self.help_button.connect("clicked", self._help_button_clicked_cb) self.scaled_proxy_check = Gtk.CheckButton.new() @@ -171,24 +190,30 @@ class FileChooserExtraWidget(Gtk.Box, Loggable): self.project_settings_label = Gtk.Label() self.project_settings_label.set_markup("%s" % _("Project Settings")) + self.project_settings_label.props.margin_top = SPACING + self.project_settings_label.props.margin_bottom = SPACING + self.project_settings_label.props.margin_start = SPACING + self.project_settings_label.props.margin_end = SPACING self.project_settings_label.connect("activate-link", self._target_res_cb) proxy_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + proxy_box.props.margin_top = SPACING * 2 + proxy_box.props.margin_bottom = SPACING * 2 + proxy_box.props.margin_start = SPACING * 2 + proxy_box.props.margin_end = SPACING * 2 hq_proxy_row = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) - hq_proxy_row.pack_start(self.hq_proxy_check, expand=False, fill=False, padding=0) - hq_proxy_row.pack_start(self.hq_combo, expand=False, fill=False, padding=PADDING) - hq_proxy_row.pack_start(self.help_button, expand=False, fill=False, padding=SPACING) - proxy_box.pack_start(hq_proxy_row, expand=False, fill=False, padding=0) + hq_proxy_row.append(self.hq_proxy_check) + hq_proxy_row.append(self.hq_combo) + hq_proxy_row.append(self.help_button) + proxy_box.append(hq_proxy_row) row = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) - row.pack_start(self.scaled_proxy_check, expand=False, fill=False, padding=0) - row.pack_start(self.project_settings_label, expand=False, fill=False, padding=SPACING) - proxy_box.pack_start(row, expand=False, fill=False, padding=0) - - self.pack_start(proxy_box, expand=False, fill=False, padding=SPACING * 2) + row.append(self.scaled_proxy_check) + row.append(self.project_settings_label) + proxy_box.append(row) - self.show_all() + self.append(proxy_box) size_group = Gtk.SizeGroup(mode=Gtk.SizeGroupMode.VERTICAL) size_group.add_widget(self.__keep_open_check) @@ -219,8 +244,7 @@ class FileChooserExtraWidget(Gtk.Box, Loggable): icon = "question-round-symbolic" else: icon = "warning-symbolic" - image = Gtk.Image.new_from_icon_name(icon, Gtk.IconSize.BUTTON) - self.help_button.set_image(image) + self.help_button.set_icon_name(icon) def _help_button_clicked_cb(self, unused_button): show_user_manual("importing") @@ -427,13 +451,13 @@ class AssetThumbnail(GObject.Object, Loggable): @staticmethod def __get_icon(icon_name, size): - icon_theme = Gtk.IconTheme.get_default() + icon_theme = Gtk.IconTheme.get_for_display(Gdk.Display.get_default()) try: - icon = icon_theme.load_icon(icon_name, size, Gtk.IconLookupFlags.FORCE_SIZE) + icon = icon_theme.lookup_icon(icon_name, "", size, 1, Gtk.TextDirection.NONE, 0) except GLib.Error: # The resulting black question mark is good for light themes but # not the best for the dark ones. - icon = icon_theme.load_icon("dialog-question-symbolic", size, 0) + icon = icon_theme.lookup_icon("dialog-question-symbolic", "", size, 1, Gtk.TextDirection.NONE, 0) return icon def _set_state(self): @@ -526,56 +550,42 @@ class MediaLibraryWidget(Gtk.Box, Loggable): self._last_suggested_tags: Set[str] = set() self.set_orientation(Gtk.Orientation.VERTICAL) - builder = Gtk.Builder() + builder = Gtk.Builder(self) builder.add_from_file(os.path.join(get_ui_dir(), "medialibrary.ui")) - builder.connect_signals(self) self._welcome_infobar = builder.get_object("welcome_infobar") - fix_infobar(self._welcome_infobar) self._project_settings_infobar = Gtk.InfoBar() - self._project_settings_infobar.hide() + self._project_settings_infobar.set_revealed(False) self._project_settings_infobar.set_message_type(Gtk.MessageType.OTHER) self._project_settings_infobar.set_show_close_button(True) self._project_settings_infobar.add_button(_("Project Settings"), Gtk.ResponseType.OK) self._project_settings_infobar.connect("response", self.__project_settings_set_infobar_cb) self._project_settings_label = Gtk.Label() - self._project_settings_label.set_line_wrap(True) + self._project_settings_label.set_wrap(True) self._project_settings_label.show() - content_area = self._project_settings_infobar.get_content_area() - content_area.add(self._project_settings_label) + self._project_settings_infobar.add_child(self._project_settings_label) - fix_infobar(self._project_settings_infobar) self._import_warning_infobar = builder.get_object("warning_infobar") - fix_infobar(self._import_warning_infobar) - self._import_warning_infobar.hide() + self._import_warning_infobar.set_revealed(False) self._import_warning_infobar.connect("response", self.__warning_infobar_cb) self._warning_label = builder.get_object("warning_label") self._view_error_button = builder.get_object("view_error_button") toolbar = builder.get_object("medialibrary_toolbar") - toolbar.get_style_context().add_class(Gtk.STYLE_CLASS_INLINE_TOOLBAR) self._import_button = builder.get_object("media_import_button") self._listview_button = builder.get_object("media_listview_button") self.search_entry = builder.get_object("media_search_entry") - search_completion = Gtk.EntryCompletion() self.search_store = Gtk.ListStore(str) - search_completion.set_model(self.search_store) - search_completion.set_text_column(0) - search_completion.set_minimum_key_length(0) - search_completion.set_popup_completion(True) - search_completion.set_inline_selection(False) - self.search_entry.set_completion(search_completion) bottom_toolbar_container = builder.get_object("medialibrary_bottom_toolbar_container") - bottom_toolbar = builder.get_object("medialibrary_bottom_toolbar") - bg_color = bottom_toolbar_container.get_style_context().get_background_color(Gtk.StateFlags.NORMAL) - bottom_toolbar.override_background_color(Gtk.StateFlags.NORMAL, bg_color) self._clipprops_button = builder.get_object("media_props_button") self.tags_button = builder.get_object("tags_button") self.scrollwin = Gtk.ScrolledWindow() + self.scrollwin.set_halign(Gtk.Align.FILL) + self.scrollwin.set_hexpand(True) self.scrollwin.set_policy( Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) - self.scrollwin.get_accessible().set_name( + self.scrollwin.set_name( "media_flowbox_scrollwindow") self.store = Gio.ListStore() @@ -583,14 +593,18 @@ class MediaLibraryWidget(Gtk.Box, Loggable): self.flowbox = Gtk.FlowBox() self.flowbox.set_valign(Gtk.Align.START) + self.flowbox.set_vexpand(True) self.flowbox.bind_model(self.store, self.create_iconview_widget_func) # Setting up respective adjustments to allow automatic scrolling self.flowbox.set_vadjustment(self.scrollwin.get_vadjustment()) self.flowbox.set_hadjustment(self.scrollwin.get_hadjustment()) - self.scrollwin.add(self.flowbox) + self.scrollwin.set_child(self.flowbox) - self.flowbox.connect("button-press-event", self._flowbox_button_press_event_cb) - self.flowbox.connect("button-release-event", self._flowbox_button_release_event_cb) + flowbox_eventcontroller = Gtk.GestureClick() + flowbox_eventcontroller.connect("pressed", self._flowbox_button_press_event_cb) + flowbox_eventcontroller.connect("released", self._flowbox_button_release_event_cb) + flowbox_eventcontroller.set_button(0) + self.flowbox.add_controller(flowbox_eventcontroller) self.flowbox.set_activate_on_single_click(False) self.flowbox.connect("child-activated", self._flowbox_child_activated_cb) self.flowbox.set_selection_mode(Gtk.SelectionMode.MULTIPLE) @@ -599,6 +613,7 @@ class MediaLibraryWidget(Gtk.Box, Loggable): # The _progressbar that shows up when importing clips self._progressbar = Gtk.ProgressBar() self._progressbar.set_show_text(True) + self._progressbar.hide() # Connect to project. We must remove and reset the callbacks when # changing project. @@ -610,11 +625,10 @@ class MediaLibraryWidget(Gtk.Box, Loggable): project_manager.connect("project-closed", self._project_closed_cb) # Drag and Drop - self.drag_dest_set(Gtk.DestDefaults.DROP | Gtk.DestDefaults.MOTION, - [URI_TARGET_ENTRY, FILE_TARGET_ENTRY], - Gdk.DragAction.COPY) - self.drag_dest_add_uri_targets() - self.connect("drag_data_received", self._drag_data_received_cb) + drop_target = Gtk.DropTarget.new(Gdk.FileList, Gdk.DragAction.COPY) + drop_target.connect("enter", self._enter_cb) + drop_target.connect("drop", self._drop_cb) + self.add_controller(drop_target) self._setup_view_as_drag_and_drop_source() @@ -640,47 +654,52 @@ class MediaLibraryWidget(Gtk.Box, Loggable): # Set the state of the view mode toggle button. self._listview_button.set_active(self.clip_view == ViewType.LIST) - self.scrollwin.show_all() # Add all the child widgets. - self.pack_start(toolbar, False, False, 0) - self.pack_start(self._welcome_infobar, False, False, 0) - self.pack_start(self._project_settings_infobar, False, False, 0) - self.pack_start(self._import_warning_infobar, False, False, 0) - self.pack_start(self.scrollwin, True, True, 0) - self.pack_start(self._progressbar, False, False, 0) - self.pack_start(bottom_toolbar_container, False, False, 0) + self.prepend(toolbar) + self.append(self._welcome_infobar) + self.append(self._project_settings_infobar) + self.append(self._import_warning_infobar) + self.append(self.scrollwin) + self.append(self._progressbar) + self.append(bottom_toolbar_container) self.filter_store() def create_iconview_widget_func(self, item): box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) box.set_halign(Gtk.Align.CENTER) - box.props.margin = PADDING / 2 + box.props.margin_top = PADDING / 2 + box.props.margin_bottom = PADDING / 2 + box.props.margin_start = PADDING / 2 + box.props.margin_end = PADDING / 2 - icon = Gtk.Image.new_from_pixbuf(item.icon_128) + icon = Gtk.Picture.new_for_pixbuf(item.icon_128) + icon.set_can_shrink(False) - box.pack_start(icon, False, False, 0) + box.prepend(icon) box.set_tooltip_markup(item.infotext) - box.show_all() return box def create_listview_widget_func(self, item): box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) - box.props.margin = PADDING + box.props.margin_top = PADDING + box.props.margin_bottom = PADDING + box.props.margin_start = PADDING + box.props.margin_end = PADDING box.set_spacing(SPACING) - icon = Gtk.Image.new_from_pixbuf(item.icon_64) + icon = Gtk.Picture.new_for_pixbuf(item.icon_64) + icon.set_can_shrink(False) label = Gtk.Label() label.props.ellipsize = Pango.EllipsizeMode.START label.set_markup(item.infotext) label.set_yalign(0) - box.pack_start(icon, False, False, 0) - box.pack_start(label, False, False, 0) - box.show_all() + box.prepend(icon) + box.prepend(label) return box @@ -717,18 +736,17 @@ class MediaLibraryWidget(Gtk.Box, Loggable): return 1 def _setup_view_as_drag_and_drop_source(self): - self.flowbox.drag_source_set( - Gdk.ModifierType.BUTTON1_MASK, [URI_TARGET_ENTRY], Gdk.DragAction.COPY) - self.flowbox.drag_source_add_uri_targets() - self.flowbox.connect("drag-data-get", self._flowbox_drag_data_get_cb) - self.flowbox.connect_after("drag-begin", self._flowbox_drag_begin_cb) - self.flowbox.connect("drag-end", self._flowbox_drag_end_cb) + flowbox_controller = Gtk.DragSource.new() + flowbox_controller.connect("drag-begin", self._flowbox_drag_begin_cb) + flowbox_controller.connect("prepare", self._flowbox_prepare_cb) + flowbox_controller.connect("drag-end", self._flowbox_drag_end_cb) + self.flowbox.add_controller(flowbox_controller) def _store_items_changed_cb(self, store_model, unused_position, unused_removed, unused_added): if store_model.get_n_items() == 0: - self._welcome_infobar.show_all() + self._welcome_infobar.set_revealed(True) else: - self._welcome_infobar.hide() + self._welcome_infobar.set_revealed(False) def _import_sources_cb(self, unused_action): self.show_import_assets_dialog() @@ -767,44 +785,12 @@ class MediaLibraryWidget(Gtk.Box, Loggable): def filter_store(self): """Updates the search field suggestions and filters the assets.""" - text = self.search_entry.get_text().lower() - # Process the search text. - tags = set() - words = [] - last_token_match = None - last_token_tag = None + text = self.search_entry.get_text().lower() + words = set() for match in re.finditer(r"\S+", text): token = text[match.start():match.end()] - parts = token.split(":", 1) - last_token_tag = None - if len(parts) == 2 and parts[0] == "tag": - tag = parts[1] - if tag in self.witnessed_tags: - tags.add(tag) - last_token_tag = tag - else: - words.append(token) - last_token_match = match - - # Update the search suggestions. - # The prefix to which we append the suggestions is chosen such that - # it excludes the last token, unless there is whitespace after it, - # in which case it is included. - suggested_tags = set(tags) - if last_token_match: - if last_token_match.end() == len(text): - # Exclude the last token from the suggestions prefix. - prefix = text[:last_token_match.start()] - suggested_tags.discard(last_token_tag) - else: - # There is some whitespace after the last token, so then - # we include it in the suggestions prefix. - prefix = text - else: - # There is no token, only whitespace or nothing. - prefix = text - self._update_search_suggestions(prefix, suggested_tags) + words.add(token) # Filter the assets. @@ -813,30 +799,12 @@ class MediaLibraryWidget(Gtk.Box, Loggable): # Realistically, nobody expects to search for only one character, # and skipping that makes a huge difference in responsiveness. # We must convert to markup form to be able to search for &, ', etc. - escaped_words = [GLib.markup_escape_text(word) for word in words if len(word) > 1] + escaped_words = set(GLib.markup_escape_text(word) for word in words if len(word) > 1) for i, row_widget in enumerate(self.flowbox): - matches = not tags.difference(self.store[i].tags) - if matches: - row_text = self.store[i].infotext.lower() - matches = all(escaped_word in row_text for escaped_word in escaped_words) - row_widget.set_visible(bool(matches)) - - def _update_search_suggestions(self, prefix: str, entered_tags: Set[str]): - """Updates the suggestions for the search field.""" - tags = self.witnessed_tags - entered_tags - - if self._last_prefix == prefix and self._last_suggested_tags == tags: - # Nothing changed. - return - - self._last_prefix = prefix - self._last_suggested_tags = tags - - self.search_store.clear() - for tag in sorted(tags): - autocomplete_suggestion = "{}tag:{}".format(prefix, tag) - self.search_store.append([autocomplete_suggestion]) + row_text = self.store[i].infotext.lower() + matches = all(escaped_word in row_text for escaped_word in escaped_words.difference(self.store[i].tags)) + row_widget.set_visible(matches) def _connect_to_project(self, project): """Connects signal handlers to the specified project.""" @@ -849,46 +817,64 @@ class MediaLibraryWidget(Gtk.Box, Loggable): def show_import_assets_dialog(self): """Pops up the "Import Sources" dialog box.""" - dialog = Gtk.FileChooserDialog() + dialog = Gtk.Dialog() dialog.set_title(_("Select One or More Files")) - dialog.set_action(Gtk.FileChooserAction.OPEN) dialog.set_icon_name("pitivi") dialog.add_buttons(_("Cancel"), Gtk.ResponseType.CANCEL, _("Add"), Gtk.ResponseType.OK) - dialog.props.extra_widget = FileChooserExtraWidget(self.app) dialog.set_default_response(Gtk.ResponseType.OK) - dialog.set_select_multiple(True) dialog.set_modal(True) dialog.set_transient_for(self.app.gui) - dialog.set_current_folder(self.app.settings.lastImportFolder) - dialog.connect('response', self._import_dialog_box_response_cb) - previewer = PreviewWidget(self.app.settings) - dialog.set_preview_widget(previewer) - dialog.set_use_preview_label(False) - dialog.connect('update-preview', previewer.update_preview_cb) + # previewer = PreviewWidget(self.app.settings) + # dialog.set_preview_widget(previewer) + # dialog.set_use_preview_label(False) + # dialog.connect('update-preview', previewer.update_preview_cb) + + filechooserwidget = Gtk.FileChooserWidget() + filechooserwidget.set_hexpand(True) + filechooserwidget.set_vexpand(True) + filechooserwidget.set_action(Gtk.FileChooserAction.OPEN) + filechooserwidget.set_select_multiple(True) + lastfolder = Gio.File.new_for_path(self.app.settings.lastImportFolder) + filechooserwidget.set_current_folder(lastfolder) + + hbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) # Filter for the "known good" formats by default file_filter = Gtk.FileFilter() file_filter.set_name(_("Supported file formats")) - file_filter.add_custom(Gtk.FileFilterFlags.URI | Gtk.FileFilterFlags.MIME_TYPE, - filter_unsupported_media_files) + + for extension in SUPPORTED_EXTENSIONS: + file_filter.add_pattern("*.%s" % extension) + for formatter in GES.list_assets(GES.Formatter): for extension in formatter.get_meta("extension").split(","): if not extension: continue file_filter.add_pattern("*.%s" % extension) - dialog.add_filter(file_filter) + filechooserwidget.add_filter(file_filter) # ...and allow the user to override our whitelists default = Gtk.FileFilter() default.set_name(_("All files")) default.add_pattern("*") - dialog.add_filter(default) + filechooserwidget.add_filter(default) + + extra_widgets = FileChooserExtraWidget(self.app) + hbox.prepend(filechooserwidget) + hbox.append(extra_widgets) + + dialog.connect('response', self._import_dialog_box_response_cb, filechooserwidget, extra_widgets) + + # stuff the hbox in the dialog + content_area = dialog.get_content_area() + content_area.prepend(hbox) # Add a shortcut for the project folder (if saved) if self._project.uri: shortcut = os.path.dirname(self._project.uri) - dialog.add_shortcut_folder_uri(shortcut) + shortcut_gfile = Gio.File.new_for_uri(shortcut) + filechooserwidget.add_shortcut_folder(shortcut_gfile) dialog.show() @@ -900,7 +886,6 @@ class MediaLibraryWidget(Gtk.Box, Loggable): return self.debug("Adding asset %s", asset.props.id) - self._pending_assets.append(asset) if self._project.loaded: @@ -916,11 +901,9 @@ class MediaLibraryWidget(Gtk.Box, Loggable): for asset in self._pending_assets: thumb_decorator = AssetThumbnail(asset, self.app.proxy_manager) item = AssetStoreItem(asset, thumb_decorator) - asset.connect("notify-meta", self.asset_meta_changed_cb) self.witnessed_tags.update(item.tags) - self.store.append(item) - + self.store.insert_sorted(item, AssetStoreItem.compare_alphabetical, None) thumb_decorator.connect("thumb-updated", self.__thumb_updated_cb, asset) del self._pending_assets[:] @@ -946,7 +929,6 @@ class MediaLibraryWidget(Gtk.Box, Loggable): if asset == item.asset: pos = i break - if pos == -1: return @@ -1026,7 +1008,6 @@ class MediaLibraryWidget(Gtk.Box, Loggable): def _asset_added_cb(self, unused_project, asset): """Checks whether the asset added to the project should be shown.""" self._last_imported_uris.add(asset.props.id) - for item in self.store: if asset == item.asset: self.info("Asset %s already in!", asset.props.id) @@ -1083,7 +1064,7 @@ class MediaLibraryWidget(Gtk.Box, Loggable): def _start_importing(self): self.__last_proxying_estimate_time = _("Unknown") self.import_start_time = time.time() - self._welcome_infobar.hide() + self._welcome_infobar.set_revealed(False) self._progressbar.show() def _done_importing(self): @@ -1104,7 +1085,7 @@ class MediaLibraryWidget(Gtk.Box, Loggable): self._view_error_button.set_label(btn_text) self._warning_label.set_text(text) - self._import_warning_infobar.show_all() + self._import_warning_infobar.set_revealed(True) self._select_last_imported_uris() @@ -1113,7 +1094,7 @@ class MediaLibraryWidget(Gtk.Box, Loggable): file_name = os.path.basename(asset_path) message = _("The project settings have been set to match file '%s'") % file_name self._project_settings_label.set_text(message) - self._project_settings_infobar.show() + self._project_settings_infobar.set_revealed(True) def _select_last_imported_uris(self): if not self._last_imported_uris: @@ -1131,16 +1112,17 @@ class MediaLibraryWidget(Gtk.Box, Loggable): # Import Sources Dialog Box callbacks - def _import_dialog_box_response_cb(self, dialogbox, response): + def _import_dialog_box_response_cb(self, dialogbox, response, filechooserwidget, extra_widgets): self.debug("response: %r", response) if response == Gtk.ResponseType.OK: - lastfolder = dialogbox.get_current_folder() + lastfolder = filechooserwidget.get_current_folder().get_path() # get_current_folder() is None if file was chosen from 'Recents' if not lastfolder: - lastfolder = GLib.path_get_dirname(dialogbox.get_filename()) + lastfolder = GLib.path_get_dirname(filechooserwidget.get_filename()) self.app.settings.lastImportFolder = lastfolder - dialogbox.props.extra_widget.save_values() - filenames = dialogbox.get_uris() + extra_widgets.save_values() + gfiles = filechooserwidget.get_files() + filenames = [gfile.get_uri() for gfile in gfiles] self._project.add_uris(filenames) if self.app.settings.closeImportDialog: dialogbox.destroy() @@ -1175,7 +1157,7 @@ class MediaLibraryWidget(Gtk.Box, Loggable): def __project_settings_set_infobar_cb(self, infobar, response_id): if response_id == Gtk.ResponseType.OK: self.app.gui.editor.show_project_settings_dialog() - infobar.hide() + infobar.set_revealed(False) def _clip_properties_cb(self, unused_widget): """Shows the clip properties in a dialog. @@ -1190,17 +1172,17 @@ class MediaLibraryWidget(Gtk.Box, Loggable): asset = assets[0] dialog = ClipMediaPropsDialog(self._project, asset) dialog.dialog.set_transient_for(self.app.gui) - dialog.run() + dialog.show() def __warning_infobar_cb(self, infobar, response_id): if response_id == Gtk.ResponseType.OK: self.__show_errors() self._reset_error_list() - infobar.hide() + infobar.set_revealed(False) def _reset_error_list(self): self._errors = [] - self._import_warning_infobar.hide() + self._import_warning_infobar.set_revealed(False) def __show_errors(self): """Shows a dialog with the import errors.""" @@ -1237,7 +1219,7 @@ class MediaLibraryWidget(Gtk.Box, Loggable): def _tags_button_clicked_cb(self, unused_widget): self.tags_popover = Gtk.Popover() - popover_heading = Gtk.Label(label=_("Tag as:")) + popover_heading = Gtk.Label.new(_("Tag as:")) popover_heading.set_halign(Gtk.Align.START) paths = self.get_selected_paths() @@ -1272,6 +1254,12 @@ class MediaLibraryWidget(Gtk.Box, Loggable): tags_list = Gtk.ListBox() tags_list.bind_model(tagstore, self.create_tagslist_widget_func) tags_list.set_can_focus(False) + tags_list.set_hexpand(True) + tags_list.set_halign(Gtk.Align.FILL) + tags_list.set_margin_top(PADDING) + tags_list.set_margin_bottom(PADDING) + tags_list.set_margin_start(PADDING) + tags_list.set_margin_end(PADDING) new_entry_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) self.new_tag_entry = Gtk.Entry() @@ -1279,28 +1267,41 @@ class MediaLibraryWidget(Gtk.Box, Loggable): self.new_tag_entry.set_width_chars(12) self.new_tag_entry.connect("activate", self.new_tag_entry_activated_cb, tagstore) add_tag_button = Gtk.Button.new_with_label(_("Add")) + add_tag_button.props.margin_top = PADDING + add_tag_button.props.margin_bottom = PADDING + add_tag_button.props.margin_start = PADDING + add_tag_button.props.margin_end = PADDING add_tag_button.connect("clicked", self.add_tag_button_clicked_cb, tagstore) - new_entry_box.pack_start(self.new_tag_entry, False, False, 0) - new_entry_box.pack_start(add_tag_button, False, False, PADDING) + new_entry_box.prepend(self.new_tag_entry) + new_entry_box.prepend(add_tag_button) box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) - box.props.margin = PADDING - box.pack_start(popover_heading, False, False, SPACING) - box.pack_start(tags_list, True, True, PADDING) - box.pack_start(new_entry_box, False, False, 0) + box.props.margin_top = PADDING + box.props.margin_bottom = PADDING + box.props.margin_start = PADDING + box.props.margin_end = PADDING + popover_heading.props.margin_top = SPACING + popover_heading.props.margin_bottom = SPACING + popover_heading.props.margin_start = SPACING + popover_heading.props.margin_end = SPACING + box.append(popover_heading) + box.append(tags_list) + box.append(new_entry_box) self.tags_popover.connect("closed", self._tags_popover_closed_cb, tags_list, tagstore) - self.tags_popover.add(box) + self.tags_popover.set_child(box) self.tags_popover.set_position(Gtk.PositionType.BOTTOM) - self.tags_popover.set_relative_to(self.tags_button) - self.tags_popover.show_all() + self.tags_button.set_popover(self.tags_popover) self.tags_popover.popup() tags_list.unselect_all() def create_tagslist_widget_func(self, tag_item: TagStoreItem): """Converts a tag item to a widget.""" box = Gtk.ListBoxRow() - box.props.margin = PADDING + box.props.margin_top = PADDING + box.props.margin_bottom = PADDING + box.props.margin_start = PADDING + box.props.margin_end = PADDING checkbutton = Gtk.CheckButton.new_with_label(tag_item.name) @@ -1311,15 +1312,13 @@ class MediaLibraryWidget(Gtk.Box, Loggable): checkbutton.connect("toggled", self._tag_toggled_cb, tag_item) - box.add(checkbutton) - box.show_all() + box.set_child(checkbutton) return box def _tags_popover_closed_cb(self, popover, tags_list, tagstore): self.apply_changed_tags(tags_list, tagstore) popover.hide() - self.tags_button.set_active(False) self.tags_popover = None def _tag_toggled_cb(self, toggle_button, tag_item): @@ -1562,7 +1561,7 @@ class MediaLibraryWidget(Gtk.Box, Loggable): selected_count = len(self.flowbox.get_selected_children()) self.remove_assets_action.set_enabled(selected_count) self.insert_at_end_action.set_enabled(selected_count) - self.tags_button.set_sensitive(selected_count) + self.tags_button.set_sensitive(selected_count == 1) # Some actions can only be done on a single item at a time: self._clipprops_button.set_sensitive(selected_count == 1) @@ -1575,13 +1574,14 @@ class MediaLibraryWidget(Gtk.Box, Loggable): path = child.get_index() self.emit("play", self.store[path].asset) - def _flowbox_button_press_event_cb(self, flowbox, event): - child = flowbox.get_child_at_pos(event.x, event.y) - if event.type == Gdk.EventType.DOUBLE_BUTTON_PRESS: + def _flowbox_button_press_event_cb(self, controller, n_pressed, x, y): + child = self.flowbox.get_child_at_pos(x, y) + if n_pressed == 2: # It is possible to double-click outside of clips pass else: - if not event.get_state() & (Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.SHIFT_MASK): + # To be tested: https://amolenaar.github.io/pgi-docgen/Gtk-4.0/classes/EventController.html#Gtk.EventController.get_current_event_state + if not controller.get_current_event_state() & (Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.SHIFT_MASK): # Ctrl or Shift key is not pressed. # It is a left or right mouse click. if child: @@ -1606,15 +1606,15 @@ class MediaLibraryWidget(Gtk.Box, Loggable): # to the Timeline. return self.rubberbanding - def _flowbox_button_release_event_cb(self, flowbox, event): + def _flowbox_button_release_event_cb(self, controller, n_pressed, x, y): if self.rubberbanding: self.rubberbanding = False - res, button = event.get_button() - if not res or button != 3: + button = controller.get_button() + if button != 3: return - child = self.flowbox.get_child_at_pos(event.x, event.y) + child = self.flowbox.get_child_at_pos(x, y) if child: if not child.is_selected(): self.flowbox.unselect_all() @@ -1625,17 +1625,17 @@ class MediaLibraryWidget(Gtk.Box, Loggable): self.debug("Not showing popup menu") return - popover = Gtk.Popover.new_from_model(self.flowbox, model) + popover = Gtk.PopoverMenu.new_from_model(model) + self.flowbox.append(popover) popover.insert_action_group("assets", action_group) popover.props.position = Gtk.PositionType.BOTTOM rect = Gdk.Rectangle() - rect.x = event.x - rect.y = event.y + rect.x = x + rect.y = y rect.width = 1 rect.height = 1 popover.set_pointing_to(rect) - popover.show_all() def __disconnect_from_project(self): self._project.disconnect_by_func(self._asset_added_cb) @@ -1651,7 +1651,7 @@ class MediaLibraryWidget(Gtk.Box, Loggable): self._project = project self._reset_error_list() self.store.remove_all() - self._welcome_infobar.show_all() + self._welcome_infobar.set_revealed(True) self._connect_to_project(project) def _new_project_loaded_cb(self, project_manager, project): @@ -1664,7 +1664,7 @@ class MediaLibraryWidget(Gtk.Box, Loggable): def _project_closed_cb(self, project_manager, project): self.__disconnect_from_project() - self._project_settings_infobar.hide() + self._project_settings_infobar.set_revealed(False) self.store.remove_all() self._project = None @@ -1686,45 +1686,50 @@ class MediaLibraryWidget(Gtk.Box, Loggable): else: self._project.add_uris(uris) - def _drag_data_received_cb(self, widget, context, x, y, - selection, targettype, time_): - """Handles data being dragged onto self.""" - self.debug("targettype: %d, selection.data: %r", - targettype, selection.get_data()) - uris = selection.get_uris() - # Scan in the background what was dragged and - # import whatever can be imported. - self.app.threads.add_thread(PathWalker, uris, self.__paths_walked_cb) + def _enter_cb(self, drop_target, x, y): + return drop_target.get_actions() - def _flowbox_drag_data_get_cb(self, view, context, data, info, timestamp): - uris = [self.store[path].uri for path in self._dragged_paths] - data.set_uris(uris) + def _drop_cb(self, drop_target, value, x, y): + files = value.get_files() + uris = [file.get_uri() for file in files] + self.app.threads.add_thread(PathWalker, uris, self.__paths_walked_cb) + return True - def _flowbox_drag_begin_cb(self, view, context): + def _flowbox_prepare_cb(self, drag_source, unused_x, unused_y): self.dragged = True self._dragged_paths = self.get_selected_paths() if not self._dragged_paths: - context.drag_abort(int(time.time())) - else: - row = self.store[self._dragged_paths[0]] - icon = row.icon_128 + drag_source.drag_cancel() + + uris = [Gio.File.new_for_uri(self.store[path].uri) for path in self._dragged_paths] + file_list = Gdk.FileList.new_from_list(uris) + value = GObject.Value(Gdk.FileList, file_list) + content_provider = Gdk.ContentProvider.new_for_value(value) + drag_source.set_content(content_provider) + return content_provider - icon_height = icon.get_height() - icon_width = icon.get_width() + def _flowbox_drag_begin_cb(self, drag_source, drag): + row = self.store[self._dragged_paths[0]] + icon = row.icon_128 - surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, icon_width, icon_height) - ctx = cairo.Context(surface) - # Center the icon around the cursor. - ctx.translate(icon_width / 2, icon_height / 2) - surface.set_device_offset(-icon_width / 2, -icon_height / 2) + icon_height = icon.get_height() + icon_width = icon.get_width() - Gdk.cairo_set_source_pixbuf(ctx, icon, 0, 0) - ctx.paint_with_alpha(0.35) + snapshot = Gtk.Snapshot.new() + bounds = Graphene.Rect().init(0, 0, 2 * icon_width, 2 * icon_height) + ctx = snapshot.append_cairo(bounds) - Gtk.drag_set_icon_surface(context, surface) + ctx.translate(icon_width / 2, icon_height / 2) - def _flowbox_drag_end_cb(self, view, context): + Gdk.cairo_set_source_pixbuf(ctx, icon, 0, 0) + ctx.paint_with_alpha(0.35) + + paintable = snapshot.to_paintable(Graphene.Size().init(2 * icon_width, 2 * icon_height)) + + drag_source.set_icon(paintable, icon_width, icon_height) + + def _flowbox_drag_end_cb(self, drag_source, drag, delete_data): self.info("Drag operation ended") self.dragged = False @@ -1740,6 +1745,3 @@ class MediaLibraryWidget(Gtk.Box, Loggable): paths = self.get_selected_paths() return [self.store[path].asset for path in paths] - - def activate_compact_mode(self): - self._import_button.set_is_important(False) diff --git a/pitivi/preset.py b/pitivi/preset.py index 0adc46408840a8df79941e5624e2251efe14c4c5..af512e64d6cb4090cca72e2b48ee81d88145471a 100644 --- a/pitivi/preset.py +++ b/pitivi/preset.py @@ -113,9 +113,9 @@ class PresetManager(GObject.Object, Loggable): menu_model.append(_("Save"), "preset.%s" % action.get_name()) self.action_save = action - menu = Gtk.Menu.new_from_model(menu_model) + menu = Gtk.PopoverMenu.new_from_model(menu_model) menu.insert_action_group("preset", action_group) - button.set_popup(menu) + button.set_popover(menu) def _preset_changed_cb(self, combo): """Handles the selection of a preset.""" @@ -156,9 +156,9 @@ class PresetManager(GObject.Object, Loggable): can_save = self.is_save_button_sensitive(preset_name) self.action_save.set_enabled(can_save) if can_save: - entry.get_style_context().add_class("unsaved") + entry.add_css_class("unsaved") else: - entry.get_style_context().remove_class("unsaved") + entry.remove_css_class("unsaved") can_remove = self.is_remove_button_sensitive() self.action_remove.set_enabled(can_remove) diff --git a/pitivi/project.py b/pitivi/project.py index 9c10938ec96c669dfaa727d08599c7e2dcc0f7d4..f813c2ca90e555b74d4ab923683b85f73fce6dad 100644 --- a/pitivi/project.py +++ b/pitivi/project.py @@ -28,8 +28,10 @@ from hashlib import md5 from typing import Optional from urllib.parse import unquote +from gi.repository import Gdk from gi.repository import GdkPixbuf from gi.repository import GES +from gi.repository import Gio from gi.repository import GLib from gi.repository import GObject from gi.repository import Gst @@ -198,13 +200,15 @@ class ProjectManager(GObject.Object, Loggable): dialog.set_default_response(1) # Default to "Save as" dialog.set_icon_name("pitivi") dialog.set_modal(True) - dialog.get_accessible().set_name("pitivi died") + dialog.set_name("pitivi died") primary = Gtk.Label() - primary.set_line_wrap(True) + primary.set_wrap(True) primary.set_use_markup(True) - primary.set_alignment(0, 0.5) + primary.set_yalign(0.5) primary.props.label = message + primary.set_hexpand(True) + primary.set_halign(Gtk.Align.FILL) # These 2 lines are needed for a decent dialog width, with wrapped text: dialog.props.default_width = 700 @@ -212,27 +216,37 @@ class ProjectManager(GObject.Object, Loggable): # put the text in a vbox vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=SPACING * 2) - vbox.pack_start(primary, True, True, 0) + vbox.set_hexpand(True) + vbox.set_valign(Gtk.Align.FILL) + vbox.prepend(primary) # make the [[image] text] hbox - image = Gtk.Image.new_from_icon_name("dialog-error-symbolic", Gtk.IconSize.DIALOG) + image = Gtk.Image.new_from_icon_name("dialog-error-symbolic") + image.set_halign(Gtk.Align.FILL) hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=SPACING * 2) - hbox.pack_start(image, False, True, 0) - hbox.pack_start(vbox, True, True, 0) - hbox.set_border_width(SPACING) + hbox.prepend(image) + hbox.prepend(vbox) + hbox.set_margin_top(SPACING) + hbox.set_margin_bottom(SPACING) + hbox.set_margin_start(SPACING) + hbox.set_margin_end(SPACING) + hbox.set_hexpand(True) + hbox.set_halign(Gtk.Align.FILL) # stuff the hbox in the dialog content_area = dialog.get_content_area() - content_area.pack_start(hbox, True, True, 0) + content_area.prepend(hbox) content_area.set_spacing(SPACING * 2) - hbox.show_all() - response = dialog.run() + dialog.connect("response", self._project_pipeline_died_dialog_cb) + dialog.show() + + def _project_pipeline_died_dialog_cb(self, dialog, response_id): dialog.destroy() - if response == 1: + if response_id == 1: self.app.gui.editor.save_project_as() - elif response == 2: + elif response_id == 2: self.app.gui.editor.save_project() self.app.shutdown() @@ -286,47 +300,63 @@ class ProjectManager(GObject.Object, Loggable): """ dialog = Gtk.Dialog(title="", transient_for=self.app.gui) ignore_backup_btn = dialog.add_button(_("Ignore backup"), Gtk.ResponseType.REJECT) - ignore_backup_btn.get_style_context().add_class("destructive-action") + ignore_backup_btn.add_css_class("destructive-action") dialog.add_button(_("Restore from backup"), Gtk.ResponseType.YES) dialog.set_icon_name("pitivi") dialog.set_modal(True) dialog.set_default_response(Gtk.ResponseType.YES) - dialog.get_accessible().set_name("restore from backup dialog") + dialog.set_name("restore from backup dialog") primary = Gtk.Label() - primary.set_line_wrap(True) + primary.set_wrap(True) primary.set_use_markup(True) - primary.set_alignment(0, 0.5) + primary.set_yalign(0.5) message = _("An autosaved version of your project file was found. " "It is %s newer than the saved project.\n\n" "Would you like to load it instead?") % \ beautify_time_delta(time_diff) primary.props.label = message + primary.set_hexpand(True) + primary.set_halign(Gtk.Align.FILL) # put the text in a vbox vbox = Gtk.Box(homogeneous=False, spacing=SPACING * 2) vbox.set_orientation(Gtk.Orientation.VERTICAL) - vbox.pack_start(primary, True, True, 0) + vbox.set_vexpand(True) + vbox.set_valign(Gtk.Align.FILL) + vbox.prepend(primary) # make the [[image] text] hbox - image = Gtk.Image.new_from_icon_name( - "dialog-question-symbolic", Gtk.IconSize.DIALOG) + image = Gtk.Image.new_from_icon_name("dialog-question-symbolic") hbox = Gtk.Box(homogeneous=False, spacing=SPACING * 2) hbox.set_orientation(Gtk.Orientation.HORIZONTAL) - hbox.pack_start(image, False, False, 0) - hbox.pack_start(vbox, True, True, 0) - hbox.set_border_width(SPACING) + hbox.prepend(image) + hbox.prepend(vbox) + hbox.set_margin_top(SPACING) + hbox.set_margin_bottom(SPACING) + hbox.set_margin_start(SPACING) + hbox.set_margin_end(SPACING) + hbox.set_hexpand(True) + hbox.set_halign(Gtk.Align.FILL) # stuff the hbox in the dialog content_area = dialog.get_content_area() - content_area.pack_start(hbox, True, True, 0) + content_area.prepend(hbox) content_area.set_spacing(SPACING * 2) - hbox.show_all() - response = dialog.run() + do_restore = [True] + dialog.connect("response", self._restore_from_backup_dialog_cb, do_restore) + dialog.show() + + return do_restore[0] + + def _restore_from_backup_dialog_cb(self, dialog, response_id, do_restore): dialog.destroy() - return response == Gtk.ResponseType.YES + if response_id == Gtk.ResponseType.YES: + do_restore[0] = True + else: + do_restore[0] = False def save_project(self, uri=None, formatter_type=None, backup=False): """Saves the current project. @@ -830,17 +860,17 @@ class Project(Loggable, GES.Project): return os.path.join(thumbs_cache_dir, thumb_hash) + ".png" @classmethod - def get_thumb(cls, uri): + def get_thumb(cls, uri: str) -> Gdk.Paintable: """Gets the project thumb, if exists, else the default thumb or None.""" + file = Gio.File.new_for_path(cls.get_thumb_path(uri, SCALED_THUMB_DIR)) try: - thumb = GdkPixbuf.Pixbuf.new_from_file(cls.get_thumb_path(uri, SCALED_THUMB_DIR)) + thumb = Gdk.Texture.new_from_file(file) except GLib.Error: - # Try to get the default thumb. - try: - thumb = Gtk.IconTheme.get_default().load_icon("video-x-generic", 128, 0) - except GLib.Error: - return None - thumb = scale_pixbuf(thumb, SCALED_THUMB_WIDTH, SCALED_THUMB_HEIGHT) + thumb = Gtk.IconTheme.get_for_display( + Gdk.Display.get_default()).lookup_icon( + "video-x-generic", "", + min(SCALED_THUMB_WIDTH, SCALED_THUMB_HEIGHT), 1, + Gtk.TextDirection.NONE, 0) return thumb diff --git a/pitivi/render.py b/pitivi/render.py index c9052ebafc1cdb040738f2ea3f630d435c80abfe..14e007aaf8970bf98af482c34daeb59d7f166bb2 100644 --- a/pitivi/render.py +++ b/pitivi/render.py @@ -54,7 +54,7 @@ from pitivi.utils.widgets import GstElementSettingsDialog PITIVI_ENCODING_TARGET_CATEGORY = "user-defined" -def set_icon_and_title(icon, title, preset_item, icon_size=Gtk.IconSize.DND): +def set_icon_and_title(icon, title, preset_item, icon_size=Gtk.IconSize.LARGE): """Adds icon for the respective preset. Args: @@ -148,9 +148,9 @@ class PresetBoxRow(Gtk.ListBoxRow): icon = Gtk.Image() set_icon_and_title(icon, title, preset_item) - description = Gtk.Label(preset_item.target.get_description()) + description = Gtk.Label.new(preset_item.target.get_description()) description.set_xalign(0) - description.set_line_wrap(True) + description.set_wrap(True) description.props.max_width_chars = 30 grid.attach(title, 1, 0, 1, 1) @@ -159,8 +159,11 @@ class PresetBoxRow(Gtk.ListBoxRow): grid.set_row_spacing(6) grid.set_column_spacing(10) grid.set_row_homogeneous(False) - grid.props.margin = 6 - self.add(grid) + grid.props.margin_top = 6 + grid.props.margin_bottom = 6 + grid.props.margin_start = 6 + grid.props.margin_end = 6 + self.set_child(grid) class PresetsManager(GObject.Object, Loggable): @@ -687,10 +690,9 @@ class RenderingProgressDialog(GObject.Object): self.app = app self.main_render_dialog = parent - self.builder = Gtk.Builder() + self.builder = Gtk.Builder(self) self.builder.add_from_file( os.path.join(configure.get_ui_dir(), "renderingprogress.ui")) - self.builder.connect_signals(self) self.window = self.builder.get_object("render-progress") self.table1 = self.builder.get_object("table1") @@ -711,7 +713,7 @@ class RenderingProgressDialog(GObject.Object): self.window.set_transient_for(self.app.gui) self.window.set_icon_name("system-run-symbolic") - self.play_rendered_file_button.get_style_context().add_class("suggested-action") + self.play_rendered_file_button.add_css_class("suggested-action") # We will only show the close/play buttons when the render is done: self.play_rendered_file_button.hide() @@ -741,7 +743,7 @@ class RenderingProgressDialog(GObject.Object): self._filesize_est_label.show() self._filesize_est_value_label.show() - def _delete_event_cb(self, unused_dialog_widget, unused_event): + def _close_request_cb(self, unused_event): """Stops the rendering.""" # The user closed the window by pressing Escape. self.emit("cancel") @@ -833,7 +835,7 @@ class RenderDialog(Loggable): self._display_render_settings() - self.window.connect("delete-event", self._delete_event_cb) + self.window.connect("close_request", self._close_request_cb) self.project.connect("video-size-changed", self._project_video_size_changed_cb) self.presets_manager.connect("profile-updated", self._presets_manager_profile_updated_cb) @@ -917,10 +919,9 @@ class RenderDialog(Loggable): return True def _create_ui(self): - builder = Gtk.Builder() + builder = Gtk.Builder(self) builder.add_from_file( os.path.join(configure.get_ui_dir(), "renderingdialog.ui")) - builder.connect_signals(self) self.window = builder.get_object("render-dialog") self.video_output_checkbutton = builder.get_object( @@ -998,7 +999,7 @@ class RenderDialog(Loggable): self.preset_listbox.bind_model(self.presets_manager.model, self._create_preset_row_func) self.preset_listbox.connect("row-activated", self._preset_listbox_row_activated_cb) - self.preset_popover.add(self.preset_listbox) + self.preset_popover.set_child(self.preset_listbox) def _create_preset_row_func(self, preset_item): return PresetBoxRow(preset_item) @@ -1028,7 +1029,7 @@ class RenderDialog(Loggable): return True def _preset_selection_menubutton_clicked_cb(self, button): - self.preset_popover.show_all() + self.preset_popover.show() def _project_video_size_changed_cb(self, project): """Handles Project metadata changes.""" @@ -1330,9 +1331,11 @@ class RenderDialog(Loggable): text=primary_message) dialog.set_property("secondary-text", secondary_message) dialog.set_property("secondary-use-markup", True) - dialog.show_all() - dialog.run() - dialog.destroy() + + def dialog_response_cb(dialog, response_id): + dialog.destroy() + dialog.connect("response", dialog_response_cb) + dialog.show() def start_action(self): """Starts the render process.""" @@ -1498,13 +1501,16 @@ class RenderDialog(Loggable): shortcut = os.path.dirname(self.project.uri) chooser.add_shortcut_folder_uri(shortcut) - response = chooser.run() + chooser.connect("response", self._select_file_dialog_cb, file_filter) + chooser.show() + + def _select_file_dialog_cb(self, chooser, response, file_filter): if response == Gtk.ResponseType.DELETE_EVENT: # This happens because Gtk.FileChooserNative is confused because we # added a filter but since it's ignored it complains there is none. # Try again without the filter. chooser.remove_filter(file_filter) - response = chooser.run() + chooser.show() if response == Gtk.ResponseType.ACCEPT: self.app.settings.lastExportFolder = chooser.get_current_folder() @@ -1542,7 +1548,7 @@ class RenderDialog(Loggable): self.project.disconnect_by_func(self._rendering_settings_changed_cb) self.destroy() - def _delete_event_cb(self, unused_window, unused_event): + def _close_request_cb(self, unused_event): self.debug("Render dialog is being deleted") self.destroy() @@ -1682,7 +1688,8 @@ class RenderDialog(Loggable): def _project_settings_button_clicked_cb(self, unused_button): dialog = ProjectSettingsDialog(self.window, self.project, self.app) - dialog.window.run() + dialog.window.set_modal(True) + dialog.window.show() self._display_settings() def _audio_output_checkbutton_toggled_cb(self, unused_audio): diff --git a/pitivi/shortcuts.py b/pitivi/shortcuts.py index c9fd815bdd8a764c7b081f3af776141959d3aec6..eb4bc9cc0d8180e2a9f8ef00aab8e4a4343b5853 100644 --- a/pitivi/shortcuts.py +++ b/pitivi/shortcuts.py @@ -215,11 +215,11 @@ class ShortcutsWindow(Gtk.ShortcutsWindow): accelerator = "" short = Gtk.ShortcutsShortcut(title=title, accelerator=accelerator) short.show() - group.add(short) - section.add(group) + group.append(short) + section.append(group) # Method below must be called after the section has been populated, # otherwise the shortcuts won't show up in search. - self.add(section) + self.set_child(section) def show_shortcuts(app): diff --git a/pitivi/tabsmanager.py b/pitivi/tabsmanager.py index 2a8c332d2107cf9d63eea65d37bd003318a15e58..9584af17b7cd95f9a2461d0481e3a7a663993364 100644 --- a/pitivi/tabsmanager.py +++ b/pitivi/tabsmanager.py @@ -15,7 +15,6 @@ # You should have received a copy of the GNU Lesser General Public # License along with this program; if not, see . """Gtk.Notebook helpers.""" -from gi.repository import Gdk from gi.repository import Gtk from pitivi.settings import ConfigError @@ -36,7 +35,10 @@ class BaseTabs(Gtk.Notebook, Loggable): def __init__(self, app): Gtk.Notebook.__init__(self) Loggable.__init__(self) - self.set_border_width(SPACING) + self.set_margin_top(SPACING) + self.set_margin_bottom(SPACING) + self.set_margin_start(SPACING) + self.set_margin_end(SPACING) self.set_scrollable(True) self.settings = app.settings notebook_widget_settings = self.get_settings() @@ -56,13 +58,14 @@ class BaseTabs(Gtk.Notebook, Loggable): original_position = self.page_num(child) self.remove_page(original_position) # Add the tab to the notebook in our newly created window. - notebook.append_page(child, Gtk.Label(label=child_name)) + notebook.append_page(child, Gtk.Label.new(child_name)) child.show() def _set_child_properties(self, child, label): - self.child_set_property(child, "detachable", True) - self.child_set_property(child, "tab-expand", False) - self.child_set_property(child, "tab-fill", True) + page = self.get_page(child) + page.props.detachable = True + page.props.tab_expand = False + page.props.tab_fill = True label.props.xalign = 0.0 def __detached_window_destroyed_cb(self, window, child, @@ -71,7 +74,7 @@ class BaseTabs(Gtk.Notebook, Loggable): position = notebook.page_num(child) notebook.remove_page(position) setattr(self.settings, child_name + "docked", True) - label = Gtk.Label(label=child_name) + label = Gtk.Label.new(child_name) self.insert_page(child, label, original_position) self._set_child_properties(child, label) @@ -91,7 +94,6 @@ class BaseTabs(Gtk.Notebook, Loggable): original_position = self.page_num(child) child_name = self.get_tab_label(child).get_text() window = Gtk.Window() - window.set_type_hint(Gdk.WindowTypeHint.UTILITY) window.set_title(child_name) # Get the previous window state settings @@ -106,24 +108,22 @@ class BaseTabs(Gtk.Notebook, Loggable): window.set_default_size(width, height) notebook = Gtk.Notebook() notebook.props.show_tabs = False - window.add(notebook) - window.show_all() + window.set_child(notebook) window.move(x, y) - window.connect( - "configure-event", self.__detached_window_configure_cb, - child_name) + window.connect("notify::default-width", self.__detached_window_configure_cb, child_name) + window.connect("notify::default-height", self.__detached_window_configure_cb, child_name) window.connect( "destroy", self.__detached_window_destroyed_cb, child, original_position, child_name) return notebook - def __detached_window_configure_cb(self, window, event, child_name): + def __detached_window_configure_cb(self, window, unused_pspec, child_name): """Saves the position and size of the specified window.""" # get_position() takes the window manager's decorations into account position = window.get_position() - setattr(self.settings, child_name + "width", event.width) - setattr(self.settings, child_name + "height", event.height) + setattr(self.settings, child_name + "width", window.get_width()) + setattr(self.settings, child_name + "height", window.get_height()) setattr(self.settings, child_name + "x", position[0]) setattr(self.settings, child_name + "y", position[1]) diff --git a/pitivi/timeline/elements.py b/pitivi/timeline/elements.py index 6a47c26983b55e5b99b3aafb1ae22b780e7314ed..c4633281e78e56efe8818810e7144968a609ff2c 100644 --- a/pitivi/timeline/elements.py +++ b/pitivi/timeline/elements.py @@ -24,12 +24,13 @@ from gi.repository import Gdk from gi.repository import GdkPixbuf from gi.repository import GES from gi.repository import GObject +from gi.repository import Graphene from gi.repository import Gst from gi.repository import GstController from gi.repository import Gtk from matplotlib.axes import Axes from matplotlib.backend_bases import MouseButton -from matplotlib.backends.backend_gtk3cairo import FigureCanvasGTK3Cairo +from matplotlib.backends.backend_gtk4cairo import FigureCanvasGTK4Cairo from matplotlib.collections import PathCollection from matplotlib.figure import Figure from matplotlib.lines import Line2D @@ -40,6 +41,7 @@ from pitivi.timeline.markers import ClipMarkersBox from pitivi.timeline.markers import Marker from pitivi.timeline.previewers import AudioPreviewer from pitivi.timeline.previewers import ImagePreviewer +from pitivi.timeline.previewers import MiniPreview from pitivi.timeline.previewers import TitlePreviewer from pitivi.timeline.previewers import VideoPreviewer from pitivi.undo.timeline import CommitTimelineFinalizingAction @@ -55,7 +57,7 @@ from pitivi.utils.timeline import UNSELECT from pitivi.utils.timeline import Zoomable from pitivi.utils.ui import CURSORS from pitivi.utils.ui import DRAG_CURSOR -from pitivi.utils.ui import EFFECT_TARGET_ENTRY +from pitivi.utils.ui import get_widget_children from pitivi.utils.ui import NORMAL_CURSOR from pitivi.utils.ui import set_state_flags_recurse @@ -75,7 +77,7 @@ def get_pspec(element_factory_name, propname): return [prop for prop in element.list_properties() if prop.name == propname][0] -class KeyframeCurve(FigureCanvasGTK3Cairo, Loggable): +class KeyframeCurve(FigureCanvasGTK4Cairo, Loggable): YLIM_OVERRIDES = {} __YLIM_OVERRIDES_VALUES = [("volume", "volume", (0.0, 0.2))] @@ -94,16 +96,15 @@ class KeyframeCurve(FigureCanvasGTK3Cairo, Loggable): def __init__(self, timeline, binding, ges_elem): figure = Figure() - FigureCanvasGTK3Cairo.__init__(self, figure) + FigureCanvasGTK4Cairo.__init__(self, figure) Loggable.__init__(self) # Remove the "matplotlib-canvas" class which forces a white background. # https://github.com/matplotlib/matplotlib/commit/3c832377fb4c4b32fcbdbc60fdfedb57296bc8c0 - style_ctx = self.get_style_context() - for css_class in style_ctx.list_classes(): - style_ctx.remove_class(css_class) + for css_class in self.get_css_classes(): + self.remove_css_class(css_class) - style_ctx.add_class("KeyframeCurve") + self.add_css_class("KeyframeCurve") self._ges_elem = ges_elem self._timeline = timeline @@ -160,9 +161,9 @@ class KeyframeCurve(FigureCanvasGTK3Cairo, Loggable): self._initial_value = 0 # The initial keyframe timestamp when a keyframe is being moved. self._initial_timestamp = 0 - # The initial event.x when a keyframe is being moved. + # The initial event x when a keyframe is being moved. self._initial_x = 0 - # The initial event.y when a keyframe is being moved. + # The initial event y when a keyframe is being moved. self._initial_y = 0 # The (offset, value) of both keyframes of the clicked keyframe line. self.__clicked_line = () @@ -171,10 +172,16 @@ class KeyframeCurve(FigureCanvasGTK3Cairo, Loggable): self.__hovered = False - self.connect("motion-notify-event", self.__motion_notify_event_cb) - self.connect("event", self._event_cb) + eventcontroller_click = Gtk.GestureClick() + eventcontroller_motion = Gtk.EventControllerMotion() + eventcontroller_motion.connect("motion", self.__motion_notify_event_cb) + eventcontroller_motion.connect("leave", self._leave_notify_event_cb) self.connect("notify::height-request", self.__height_request_cb) - self.connect("button_release_event", self._button_release_event_cb) + eventcontroller_click.connect("released", self._button_release_event_cb) + eventcontroller_click.set_button(1) + + self.add_controller(eventcontroller_click) + self.add_controller(eventcontroller_motion) self.mpl_connect('button_press_event', self._mpl_button_press_event_cb) self.mpl_connect('button_release_event', self._mpl_button_release_event_cb) @@ -274,18 +281,16 @@ class KeyframeCurve(FigureCanvasGTK3Cairo, Loggable): self._update_plots() self._timeline.ges_timeline.get_parent().commit_timeline() - def __motion_notify_event_cb(self, unused_widget, unused_event): + def __motion_notify_event_cb(self, unused_controller, x, y): # We need to do this here, because Matplotlib's callbacks can't stop # signal propagation. if self.handling_motion: return True return False - def _event_cb(self, unused_element, event): - if event.type == Gdk.EventType.LEAVE_NOTIFY: - cursor = NORMAL_CURSOR - self._timeline.get_window().set_cursor(cursor) - return False + def _leave_notify_event_cb(self, controller): + cursor = NORMAL_CURSOR + self._timeline.set_cursor(cursor) def _mpl_button_press_event_cb(self, event): if event.button != MouseButton.LEFT: @@ -377,7 +382,7 @@ class KeyframeCurve(FigureCanvasGTK3Cairo, Loggable): self._update_tooltip(None) self.__hovered = False - self._timeline.get_window().set_cursor(cursor) + self._timeline.set_cursor(cursor) def _mpl_button_release_event_cb(self, event): if event.button != MouseButton.LEFT: @@ -414,10 +419,7 @@ class KeyframeCurve(FigureCanvasGTK3Cairo, Loggable): self._offset = None self.__clicked_line = () - def _button_release_event_cb(self, unused_widget, event): - if not event.get_button() == (True, 1): - return False - + def _button_release_event_cb(self, controller, n_pressed, x, y): dragged = self._dragged self._dragged = False @@ -635,7 +637,7 @@ class MultipleKeyframeCurve(KeyframeCurve): self.set_tooltip_markup(markup) -class TimelineElement(Gtk.Layout, Zoomable, Loggable): +class TimelineElement(Gtk.Fixed, Zoomable, Loggable): __gsignals__ = { # Signal the keyframes curve are being hovered "curve-enter": (GObject.SignalFlags.RUN_LAST, None, ()), @@ -644,7 +646,7 @@ class TimelineElement(Gtk.Layout, Zoomable, Loggable): } def __init__(self, element, timeline): - Gtk.Layout.__init__(self) + Gtk.Fixed.__init__(self) Zoomable.__init__(self) Loggable.__init__(self) @@ -663,20 +665,19 @@ class TimelineElement(Gtk.Layout, Zoomable, Loggable): self.previewer = self._get_previewer() if self.previewer: - self.add(self.previewer) + self.put(self.previewer, 0, 0) self.__background = self._get_background() if self.__background: - self.add(self.__background) + self.put(self.__background, 0, 0) self.markers = ClipMarkersBox(self.app, self._ges_elem) self._ges_elem.markers_manager.set_markers_box(self.markers) - self.add(self.markers) + self.put(self.markers, 0, 0) self.keyframe_curve = None self.__controlled_property = None - self.show_all() # We set up the default mixing property right here, if a binding was # already set (when loading a project), it will be added later @@ -799,19 +800,21 @@ class TimelineElement(Gtk.Layout, Zoomable, Loggable): if binding.props.name == self.__controlled_property.name: self.__ensure_keyframes(binding) - def do_draw(self, cr): - self.propagate_draw(self.__background, cr) + def do_snapshot(self, snapshot): + Gtk.Fixed.do_snapshot(self, snapshot) + + self.snapshot_child(self.__background, snapshot) if self.previewer: - self.propagate_draw(self.previewer, cr) + self.snapshot_child(self.previewer, snapshot) if self.keyframe_curve and self.keyframe_curve.is_drawable(): project = self.timeline.app.project_manager.current_project if project.pipeline.get_simple_state() != Gst.State.PLAYING: - self.propagate_draw(self.keyframe_curve, cr) + self.snapshot_child(self.keyframe_curve, snapshot) if self.markers and self.markers.is_drawable(): - self.propagate_draw(self.markers, cr) + self.snapshot_child(self.markers, snapshot) # Callbacks def __selected_changed_cb(self, unused_selected, selected): @@ -887,7 +890,7 @@ class VideoBackground(Gtk.Box): def __init__(self): Gtk.Box.__init__(self) - self.get_style_context().add_class("VideoBackground") + self.add_css_class("VideoBackground") class VideoSource(TimelineElement): @@ -1037,7 +1040,7 @@ class TitleSource(VideoSource): def _get_previewer(self): previewer = TitlePreviewer(self._ges_elem) - previewer.get_style_context().add_class("TitleSource") + previewer.add_css_class("TitleSource") return previewer def _get_default_position(self): @@ -1074,7 +1077,7 @@ class VideoUriSource(VideoSource): def __init__(self, element, timeline): VideoSource.__init__(self, element, timeline) - self.get_style_context().add_class("VideoUriSource") + self.add_css_class("VideoUriSource") def _get_previewer(self): if isinstance(self._ges_elem, GES.ImageSource): @@ -1094,7 +1097,7 @@ class AudioBackground(Gtk.Box): def __init__(self): Gtk.Box.__init__(self) - self.get_style_context().add_class("AudioBackground") + self.add_css_class("AudioBackground") class AudioUriSource(TimelineElement): @@ -1103,7 +1106,7 @@ class AudioUriSource(TimelineElement): def __init__(self, element, timeline): TimelineElement.__init__(self, element, timeline) - self.get_style_context().add_class("AudioUriSource") + self.add_css_class("AudioUriSource") def _get_previewer(self): previewer = AudioPreviewer(self._ges_elem, self.timeline.app.settings.previewers_max_cpu) @@ -1119,7 +1122,7 @@ class AudioUriSource(TimelineElement): return None -class TrimHandle(Gtk.EventBox, Loggable): +class TrimHandle(Gtk.Box, Loggable): __gtype_name__ = "PitiviTrimHandle" @@ -1128,18 +1131,18 @@ class TrimHandle(Gtk.EventBox, Loggable): PIXBUF = None def __init__(self, clip, edge): - Gtk.EventBox.__init__(self) + Gtk.Box.__init__(self) Loggable.__init__(self) self.clip = clip self.edge = edge - self.get_style_context().add_class("Trimbar") + self.add_css_class("Trimbar") if edge == GES.Edge.EDGE_END: css_class = "right" else: css_class = "left" - self.get_style_context().add_class(css_class) + self.add_css_class(css_class) self.props.valign = Gtk.Align.FILL self.shrink() @@ -1148,8 +1151,11 @@ class TrimHandle(Gtk.EventBox, Loggable): else: self.props.halign = Gtk.Align.START - def do_draw(self, cr): - Gtk.EventBox.do_draw(self, cr) + def do_snapshot(self, snapshot): + Gtk.Box.do_snapshot(self, snapshot) + bounds = Graphene.Rect().init(0, 0, self.get_width(), self.get_height()) + cr = snapshot.append_cairo(bounds) + if TrimHandle.PIXBUF is None: TrimHandle.PIXBUF = GdkPixbuf.Pixbuf.new_from_file( os.path.join(get_pixmap_dir(), "trimbar-focused.png")) @@ -1162,22 +1168,20 @@ class TrimHandle(Gtk.EventBox, Loggable): def shrink(self): self.props.width_request = TrimHandle.DEFAULT_WIDTH - if self.props.window: - self.props.window.set_cursor(NORMAL_CURSOR) + self.set_cursor(NORMAL_CURSOR) -class Clip(Gtk.EventBox, Zoomable, Loggable): +class Clip(Gtk.Box, Loggable): __gtype_name__ = "PitiviClip" def __init__(self, layer: GES.Layer, ges_clip: GES.Clip): - Gtk.EventBox.__init__(self) - Zoomable.__init__(self) + Gtk.Box.__init__(self) Loggable.__init__(self) name = ges_clip.get_name() self.set_name(name) - self.get_accessible().set_name(name) + self.set_name(name) self._elements_container = None self.left_handle = None @@ -1188,7 +1192,6 @@ class Clip(Gtk.EventBox, Zoomable, Loggable): self.app = layer.app self.ges_clip = ges_clip - self.ges_clip.ui = self self.ges_clip.selected = Selected() self.ges_clip.selected.selected = self.ges_clip in self.timeline.selection @@ -1196,7 +1199,7 @@ class Clip(Gtk.EventBox, Zoomable, Loggable): self.video_widget = None self._setup_widget() - self.__force_position_update = True + self._force_position_update = True for ges_timeline_element in self.ges_clip.get_children(False): self._add_child(ges_timeline_element) @@ -1205,8 +1208,14 @@ class Clip(Gtk.EventBox, Zoomable, Loggable): set_state_flags_recurse(self, Gtk.StateFlags.SELECTED, are_set=self.ges_clip.selected) # Connect to Widget signals. - self.connect("button-release-event", self._button_release_event_cb) - self.connect("event", self._event_cb) + eventcontroller_click = Gtk.GestureClick() + eventcontroller_motion = Gtk.EventControllerMotion() + eventcontroller_click.connect("released", self._button_release_event_cb) + eventcontroller_motion.connect("enter", self._eventcontroller_motion_enter_cb) + eventcontroller_motion.connect("leave", self._eventcontroller_motion_leave_cb) + + self.add_controller(eventcontroller_click) + self.add_controller(eventcontroller_motion) # Connect to GES signals. self.ges_clip.connect("notify::start", self._start_changed_cb) @@ -1218,37 +1227,28 @@ class Clip(Gtk.EventBox, Zoomable, Loggable): self.ges_clip.connect_after("child-removed", self._child_removed_cb) # To be able to receive effects dragged on clips. - self.drag_dest_set(0, [EFFECT_TARGET_ENTRY], Gdk.DragAction.COPY) - self.connect("drag-drop", self.__drag_drop_cb) - - @property - def layer(self): - ges_layer = self.ges_clip.props.layer - return ges_layer.ui if ges_layer else None + drag_controller = Gtk.DropTarget.new(GObject.TYPE_STRING, Gdk.DragAction.COPY) + drag_controller.connect("drop", self.__drop_cb) + self.add_controller(drag_controller) - def __drag_drop_cb(self, widget, context, x, y, timestamp): + def __drop_cb(self, drop_target, unused_value, unused_x, unused_y): success = False - - target = self.drag_dest_find_target(context, None) - if not target: - return False - - if target.name() == EFFECT_TARGET_ENTRY.target: - self.info("Adding effect %s", self.timeline.drop_data) - self.timeline.selection.set_selection([self.ges_clip], SELECT) - self.app.gui.editor.switch_context_tab(self.ges_clip) - - effect_info = self.app.effects.get_info(self.timeline.drop_data) - pipeline = self.timeline.ges_timeline.get_parent() - with self.app.action_log.started("add effect", - finalizing_action=CommitTimelineFinalizingAction(pipeline), - toplevel=True): + drop = drop_target.get_drop() + self.info("Adding effect %s", self.timeline.drop_data) + self.timeline.selection.set_selection([self.ges_clip], SELECT) + self.app.gui.editor.switch_context_tab(self.ges_clip) + + effect_info = self.app.effects.get_info(self.timeline.drop_data) + pipeline = self.timeline.ges_timeline.get_parent() + with self.app.action_log.started("add effect", + finalizing_action=CommitTimelineFinalizingAction(pipeline), + toplevel=True): + if effect_info: self.add_effect(effect_info) - self.timeline.clean_drop_data() - success = True - - Gtk.drag_finish(context, success, False, timestamp) + self.timeline.clean_drop_data() + success = True + drop.finish(drop.get_actions()) return success def add_effect(self, effect_info): @@ -1276,68 +1276,18 @@ class Clip(Gtk.EventBox, Zoomable, Loggable): return effect return None - def update_position(self): - layer = self.layer - if not layer or layer != self.get_parent(): - # Things are not settled yet. - return - - start = self.ges_clip.props.start - duration = self.ges_clip.props.duration - x = self.ns_to_pixel(start) - # The calculation of the width assumes that the start is always - # int(pixels_float). In that case, the rounding can add up and a pixel - # might be lost if we ignore the start of the clip. - width = self.ns_to_pixel(start + duration) - x - - parent_height = layer.props.height_request - y = 0 - height = parent_height - has_video = self.ges_clip.find_track_elements(None, GES.TrackType.VIDEO, GObject.TYPE_NONE) - has_audio = self.ges_clip.find_track_elements(None, GES.TrackType.AUDIO, GObject.TYPE_NONE) - if not has_video or not has_audio: - if layer.media_types == (GES.TrackType.AUDIO | GES.TrackType.VIDEO): - height = parent_height / 2 - if not has_video: - y = height - - if self.__force_position_update or \ - x != self._current_x or \ - y != self._current_y or \ - width != self._current_width or \ - parent_height != self._current_parent_height or \ - layer != self._current_parent: - - offset_px = self.ns_to_pixel(self.ges_clip.props.in_point) - - for ges_timeline_element in self.ges_clip.get_children(False): - if not ges_timeline_element.ui: - continue - - if ges_timeline_element.ui.markers: - ges_timeline_element.ui.markers.offset = offset_px - - layer.move(self, x, y) - self.set_size_request(width, height) - - elements = self._elements_container.get_children() - for child in elements: - child.set_size(width, height / len(elements)) + def _add_child(self, ges_timeline_element): + """Initializes added Clip's ges_timeline_element.""" - self.__force_position_update = False - # pylint: disable=attribute-defined-outside-init - self._current_x = x - self._current_y = y - self._current_width = width - self._current_parent_height = parent_height - self._current_parent = layer + def update_position(self): + """Updates the UI of the clip.""" def _setup_widget(self): pass def _add_trim_handles(self): overlay = Gtk.Overlay() - self.add(overlay) + self.append(overlay) self._elements_container = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0) overlay.add_overlay(self._elements_container) @@ -1356,10 +1306,10 @@ class Clip(Gtk.EventBox, Zoomable, Loggable): handle.shrink() def do_map(self): - Gtk.EventBox.do_map(self) + Gtk.Box.do_map(self) self.update_position() - def _button_release_event_cb(self, unused_widget, event): + def _button_release_event_cb(self, controller, n_pressed, x, y): self.debug("Button release event") if self.timeline.got_dragged: @@ -1368,8 +1318,8 @@ class Clip(Gtk.EventBox, Zoomable, Loggable): self.timeline.got_dragged = False return False - res, button = event.get_button() - if res and not button == 1: + button = controller.get_button() + if not button == 1: # Only the left mouse button selects. return False @@ -1379,7 +1329,7 @@ class Clip(Gtk.EventBox, Zoomable, Loggable): mode = SELECT_ADD else: mode = UNSELECT - clicked_layer, click_pos = self.timeline.get_clicked_layer_and_pos(event) + clicked_layer, click_pos = self.timeline.get_clicked_layer_and_pos(controller, x, y) self.timeline.set_selection_meta_info(clicked_layer, click_pos, mode) else: self.app.gui.editor.switch_context_tab(self.ges_clip) @@ -1412,16 +1362,23 @@ class Clip(Gtk.EventBox, Zoomable, Loggable): for handle in self.handles: handle.hide() - def _event_cb(self, element, event): + def _eventcontroller_motion_enter_cb(self, controller, unused_x, unused_y): prelight = None - if (event.type == Gdk.EventType.ENTER_NOTIFY and - event.mode == Gdk.CrossingMode.NORMAL and + event = controller.get_current_event() + if(event is not None and event.get_mode() == Gdk.CrossingMode.NORMAL and not self.timeline.scrubbing): + print("enter_notify_event_cb") prelight = True for handle in self.handles: handle.enlarge() - elif (event.type == Gdk.EventType.LEAVE_NOTIFY and - event.mode == Gdk.CrossingMode.NORMAL): + + if prelight is not None: + set_state_flags_recurse(self, Gtk.StateFlags.PRELIGHT, are_set=prelight, ignored_classes=(Marker,)) + + def _eventcontroller_motion_leave_cb(self, controller): + prelight = None + event = controller.get_current_event() + if event is not None and event.get_mode() == Gdk.CrossingMode.NORMAL: prelight = False for handle in self.handles: handle.shrink() @@ -1429,8 +1386,6 @@ class Clip(Gtk.EventBox, Zoomable, Loggable): if prelight is not None: set_state_flags_recurse(self, Gtk.StateFlags.PRELIGHT, are_set=prelight, ignored_classes=(Marker,)) - return False - def _start_changed_cb(self, clip, pspec): self.update_position() @@ -1455,13 +1410,8 @@ class Clip(Gtk.EventBox, Zoomable, Loggable): def __curve_leave_cb(self, unused_keyframe_curve): self.__show_handles() - def _add_child(self, ges_timeline_element: GES.TimelineElement): - ges_timeline_element.selected = Selected() - ges_timeline_element.selected.selected = self.ges_clip.selected.selected - ges_timeline_element.ui = None - def _child_added_cb(self, ges_clip, ges_timeline_element: GES.TimelineElement): - self.__force_position_update = True + self._force_position_update = True self._add_child(ges_timeline_element) self.__connect_to_child(ges_timeline_element) self.update_position() @@ -1470,25 +1420,164 @@ class Clip(Gtk.EventBox, Zoomable, Loggable): pass def _child_removed_cb(self, unused_ges_clip, ges_timeline_element: GES.TimelineElement): - self.__force_position_update = True + self._force_position_update = True self.__disconnect_from_child(ges_timeline_element) self._remove_child(ges_timeline_element) self.update_position() -class SourceClip(Clip): - __gtype_name__ = "PitiviSourceClip" +class FullClip(Clip, Zoomable): + """Full version of Clip(ui).""" def __init__(self, layer: GES.Layer, ges_clip: GES.Clip): + Zoomable.__init__(self) + Clip.__init__(self, layer, ges_clip) + + self.ges_clip.ui = self + + def _add_child(self, ges_timeline_element): + ges_timeline_element.selected = Selected() + ges_timeline_element.selected.selected = self.ges_clip.selected.selected + ges_timeline_element.ui = None + + def update_position(self): + ges_layer = self.ges_clip.props.layer + layer = ges_layer.ui + if not layer or layer != self.get_parent(): + # Things are not settled yet. + return + + start = self.ges_clip.props.start + duration = self.ges_clip.props.duration + x = self.ns_to_pixel(start) + # The calculation of the width assumes that the start is always + # int(pixels_float). In that case, the rounding can add up and a pixel + # might be lost if we ignore the start of the clip. + width = self.ns_to_pixel(start + duration) - x + + parent_height = layer.props.height_request + y = 0 + height = parent_height + has_video = self.ges_clip.find_track_elements(None, GES.TrackType.VIDEO, GObject.TYPE_NONE) + has_audio = self.ges_clip.find_track_elements(None, GES.TrackType.AUDIO, GObject.TYPE_NONE) + if not has_video or not has_audio: + if layer.media_types == (GES.TrackType.AUDIO | GES.TrackType.VIDEO): + height = parent_height / 2 + if not has_video: + y = height + + if self._force_position_update or \ + x != self._current_x or \ + y != self._current_y or \ + width != self._current_width or \ + parent_height != self._current_parent_height or \ + layer != self._current_parent: + + offset_px = self.ns_to_pixel(self.ges_clip.props.in_point) + + for ges_timeline_element in self.ges_clip.get_children(False): + if not ges_timeline_element.ui: + continue + + if ges_timeline_element.ui.markers: + ges_timeline_element.ui.markers.offset = offset_px + + layer.move(self, x, y) + self.set_size_request(width, height) + + elements = get_widget_children(self._elements_container) + for child in elements: + child.set_size(width, height / len(elements)) + + self._force_position_update = False + # pylint: disable=attribute-defined-outside-init + self._current_x = x + self._current_y = y + self._current_width = width + self._current_parent_height = parent_height + self._current_parent = layer + + +class MiniClip(Clip): + """Mini version of Clip(mini_ui).""" + + __gtype_name__ = "PitiviMiniClip" + + def __init__(self, layer, ges_clip): Clip.__init__(self, layer, ges_clip) + self.ges_clip.mini_ui = self + + def _add_child(self, ges_timeline_element): + ges_timeline_element.mini_ui = None + + def update_position(self): + ges_layer = self.ges_clip.props.layer + layer = ges_layer.mini_ui + if not layer or layer != self.get_parent(): + # Things are not settled yet. + return + + start = self.ges_clip.props.start + duration = self.ges_clip.props.duration + ratio = self.timeline.calc_best_zoom_ratio() + x = Zoomable.ns_to_pixel(start, zoomratio=ratio) + # The calculation of the width assumes that the start is always + # int(pixels_float). In that case, the rounding can add up and a pixel + # might be lost if we ignore the start of the clip. + width = Zoomable.ns_to_pixel(start + duration, zoomratio=ratio) - x + + parent_height = layer.props.height_request + y = 0 + + if self._force_position_update or \ + x != self._current_x or \ + y != self._current_y or \ + width != self._current_width or \ + parent_height != self._current_parent_height or \ + layer != self._current_parent: + + layer.move(self, x, y) + self.set_size_request(width, parent_height) + + self._force_position_update = False + # pylint: disable=attribute-defined-outside-init + self._current_x = x + self._current_y = y + self._current_width = width + self._current_parent_height = parent_height + self._current_parent = layer + + +class SourceClip(): + __gtype_name__ = "PitiviSourceClip" + def _setup_widget(self): self._add_trim_handles() - self.get_style_context().add_class("Clip") + self.add_css_class("Clip") + + def _remove_child(self, ges_timeline_element): + if ges_timeline_element.ui: + self._elements_container.remove(ges_timeline_element.ui) + ges_timeline_element.ui = None + + if ges_timeline_element.mini_ui: + self._elements_container.remove(ges_timeline_element.mini_ui) + ges_timeline_element.mini_ui = None + + def _create_child_widget(self, ges_source: GES.Source) -> Optional[Gtk.Widget]: + raise NotImplementedError() + + +class FullSourceClip(SourceClip, FullClip): + __gtype_name__ = "PitiviFullSourceClip" + + def __init__(self, layer, ges_clip): + FullClip.__init__(self, layer, ges_clip) def _add_child(self, ges_timeline_element: GES.TimelineElement): - Clip._add_child(self, ges_timeline_element) + FullClip._add_child(self, ges_timeline_element) # In some cases a GESEffect is added here, # so we have to limit the markers initialization to GESSources. @@ -1505,27 +1594,66 @@ class SourceClip(Clip): return ges_source.ui = widget + widget.set_hexpand(True) if ges_source.get_track_type() == GES.TrackType.VIDEO: - self._elements_container.pack_start(widget, expand=True, fill=False, padding=0) + self._elements_container.prepend(widget) else: - self._elements_container.pack_end(widget, expand=True, fill=False, padding=0) + self._elements_container.append(widget) widget.set_visible(True) def _create_child_widget(self, ges_source: GES.Source) -> Optional[Gtk.Widget]: raise NotImplementedError() - def _remove_child(self, ges_timeline_element): - if ges_timeline_element.ui: - self._elements_container.remove(ges_timeline_element.ui) - ges_timeline_element.ui = None +class MiniSourceClip(SourceClip, MiniClip): + __gtype_name__ = "PitiviMiniSourceClip" + + def __init__(self, layer, ges_clip): + MiniClip.__init__(self, layer, ges_clip) + + def _add_child(self, ges_timeline_element): + MiniClip._add_child(self, ges_timeline_element) + + ges_source: GES.Source = ges_timeline_element + + widget = self._create_child_widget(ges_source) + if not widget: + return + + ges_source.mini_ui = widget + self._elements_container.prepend(widget) + widget.set_hexpand(True) + widget.set_visible(True) + + def _create_child_widget(self, ges_source: GES.Source) -> Optional[Gtk.Widget]: + raise NotImplementedError() -class UriClip(SourceClip): + +class SimpleClip(MiniSourceClip): + __gtype_name__ = "PitiviSimpleClip" + + def __init__(self, layer, ges_clip): + MiniSourceClip.__init__(self, layer, ges_clip) + self.add_css_class("SimpleClip") + + def do_query_tooltip(self, x, y, keyboard_mode, tooltip): + tooltip.set_markup(filename_from_uri( + self.ges_clip.get_asset().props.id)) + + return True + + def _create_child_widget(self, ges_source: GES.Source) -> Optional[Gtk.Widget]: + color, tooltip = GES_TYPE_COLOR_TOOLTIP.get(self.ges_clip.__gtype__, None) + self.props.has_tooltip = tooltip + return MiniPreview(color) + + +class UriClip(FullSourceClip): __gtype_name__ = "PitiviUriClip" def __init__(self, layer: GES.Layer, ges_clip: GES.Clip): - SourceClip.__init__(self, layer, ges_clip) - self.get_style_context().add_class("UriClip") + FullSourceClip.__init__(self, layer, ges_clip) + self.add_css_class("UriClip") self.props.has_tooltip = True def do_query_tooltip(self, x, y, keyboard_mode, tooltip): @@ -1545,12 +1673,12 @@ class UriClip(SourceClip): return None -class TestClip(SourceClip): +class TestClip(FullSourceClip): __gtype_name__ = "PitiviTestClip" def __init__(self, layer: GES.Layer, ges_clip: GES.Clip): - SourceClip.__init__(self, layer, ges_clip) - self.get_style_context().add_class("TestClip") + FullSourceClip.__init__(self, layer, ges_clip) + self.add_css_class("TestClip") def _create_child_widget(self, ges_source: GES.Source) -> Optional[Gtk.Widget]: if ges_source.get_track_type() == GES.TrackType.VIDEO: @@ -1560,12 +1688,12 @@ class TestClip(SourceClip): return None -class TitleClip(SourceClip): +class TitleClip(FullSourceClip): __gtype_name__ = "PitiviTitleClip" def __init__(self, layer: GES.Layer, ges_clip: GES.Clip): - SourceClip.__init__(self, layer, ges_clip) - self.get_style_context().add_class("TitleClip") + FullSourceClip.__init__(self, layer, ges_clip) + self.add_css_class("TitleClip") def _create_child_widget(self, ges_source: GES.Source) -> Optional[Gtk.Widget]: if ges_source.get_track_type() == GES.TrackType.VIDEO: @@ -1575,21 +1703,19 @@ class TitleClip(SourceClip): return None -class TransitionClip(Clip): +class TransitionClip(): __gtype_name__ = "PitiviTransitionClip" - def __init__(self, layer: GES.Layer, ges_clip: GES.Clip): + def __init__(self): self.__has_video = False - Clip.__init__(self, layer, ges_clip) - if self.__has_video: self.z_order = 1 else: self.z_order = 0 - self.get_style_context().add_class("TransitionClip") + self.add_css_class("TransitionClip") # In the case of TransitionClips, we are the only container self._add_trim_handles() @@ -1606,8 +1732,6 @@ class TransitionClip(Clip): return True def _add_child(self, ges_timeline_element): - Clip._add_child(self, ges_timeline_element) - if not isinstance(ges_timeline_element, GES.VideoTransition): return @@ -1623,9 +1747,48 @@ class TransitionClip(Clip): self.app.gui.editor.trans_list.deactivate() +class FullTransitionClip(TransitionClip, FullClip): + + __gtype_name__ = "PitiviFullTransitionClip" + + def __init__(self, layer: GES.Layer, ges_clip: GES.Clip): + FullClip.__init__(self, layer, ges_clip) + TransitionClip.__init__(self) + + def _add_child(self, ges_timeline_element): + FullClip._add_child(self, ges_timeline_element) + TransitionClip._add_child(self, ges_timeline_element) + + +class MiniTransitionClip(TransitionClip, MiniClip): + + __gtype_name__ = "PitiviMiniTransitionClip" + + def __init__(self, layer: GES.Layer, ges_clip: GES.Clip): + MiniClip.__init__(self, layer, ges_clip) + TransitionClip.__init__(self) + + def _add_child(self, ges_timeline_element): + MiniClip._add_child(self, ges_timeline_element) + TransitionClip._add_child(self, ges_timeline_element) + + GES_TYPE_UI_TYPE = { GES.UriClip.__gtype__: UriClip, GES.TitleClip.__gtype__: TitleClip, - GES.TransitionClip.__gtype__: TransitionClip, + GES.TransitionClip.__gtype__: FullTransitionClip, GES.TestClip.__gtype__: TestClip } + +GES_TYPE_COLOR_TOOLTIP = { + GES.UriClip.__gtype__: ((0.214, 0.50, 0.39), True), + GES.TitleClip.__gtype__: ((0.819, 0.20, 0.267), False), + GES.TestClip.__gtype__: ((0.619, 0.670, 0.067), False) +} + +GES_TYPE_MINI_UI_TYPE = { + GES.UriClip.__gtype__: SimpleClip, + GES.TitleClip.__gtype__: SimpleClip, + GES.TransitionClip.__gtype__: MiniTransitionClip, + GES.TestClip.__gtype__: SimpleClip +} diff --git a/pitivi/timeline/layer.py b/pitivi/timeline/layer.py index 2b80a78c68c5001190ff01759b744b3b932e9463..ba7e5bf1d4e111a045e699ab38093b421d1b29d8 100644 --- a/pitivi/timeline/layer.py +++ b/pitivi/timeline/layer.py @@ -27,6 +27,7 @@ from pitivi.undo.timeline import CommitTimelineFinalizingAction from pitivi.utils.loggable import Loggable from pitivi.utils.timeline import Zoomable from pitivi.utils.ui import LAYER_HEIGHT +from pitivi.utils.ui import MINI_LAYER_HEIGHT from pitivi.utils.ui import PADDING from pitivi.utils.ui import SEPARATOR_HEIGHT @@ -42,26 +43,26 @@ VIDEO_ICONS = { } -class SpacedSeparator(Gtk.EventBox): +class SpacedSeparator(Gtk.Box): """A Separator with vertical spacing. - Inherits from EventBox since we want to change background color. + Inherits from Box since we want to change background color. """ def __init__(self): - Gtk.EventBox.__init__(self) + Gtk.Box.__init__(self) - self.get_style_context().add_class("SpacedSeparator") + self.add_css_class("SpacedSeparator") self.props.height_request = SEPARATOR_HEIGHT -class LayerControls(Gtk.EventBox, Loggable): +class LayerControls(Gtk.Box, Loggable): """Container with widgets for controlling a layer.""" __gtype_name__ = 'PitiviLayerControls' def __init__(self, ges_layer, app): - Gtk.EventBox.__init__(self) + Gtk.Box.__init__(self) Loggable.__init__(self) self.ges_layer = ges_layer @@ -74,38 +75,44 @@ class LayerControls(Gtk.EventBox, Loggable): self.timeline_video_tracks = [track for track in tracks if track.props.track_type == GES.TrackType.VIDEO] hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) - self.add(hbox) + self.append(hbox) vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) - hbox.pack_start(vbox, True, True, 0) + vbox.set_vexpand(True) + vbox.set_valign(Gtk.Align.FILL) + hbox.append(vbox) rightside_separator = Gtk.Separator.new(Gtk.Orientation.VERTICAL) - hbox.pack_start(rightside_separator, False, False, 0) + hbox.append(rightside_separator) name_row = Gtk.Box() name_row.set_orientation(Gtk.Orientation.HORIZONTAL) name_row.props.margin_top = PADDING name_row.props.margin_start = PADDING name_row.props.margin_end = PADDING - vbox.pack_start(name_row, False, False, 0) + vbox.append(name_row) self.menubutton = Gtk.MenuButton.new() self.menubutton.props.valign = Gtk.Align.CENTER - self.menubutton.props.relief = Gtk.ReliefStyle.NONE + self.menubutton.set_has_frame(False) model, action_group = self.__create_menu_model() - popover = Gtk.Popover.new_from_model(self.menubutton, model) + popover = Gtk.PopoverMenu.new_from_model(model) popover.insert_action_group("layer", action_group) popover.props.position = Gtk.PositionType.LEFT self.menubutton.set_popover(popover) - name_row.pack_start(self.menubutton, False, False, 0) + name_row.append(self.menubutton) self.name_entry = Gtk.Entry() - self.name_entry.get_style_context().add_class("LayerControlEntry") + self.name_entry.add_css_class("LayerControlEntry") self.name_entry.props.valign = Gtk.Align.CENTER - self.name_entry.connect("focus-out-event", self.__name_focus_out_cb) + self.name_entry.set_hexpand(True) + self.name_entry.set_halign(Gtk.Align.FILL) + eventcontroller_focus = Gtk.EventControllerFocus() + eventcontroller_focus.connect("leave", self.__name_focus_out_cb) + self.name_entry.add_controller(eventcontroller_focus) self.ges_layer.connect("notify-meta", self.__layer_rename_cb) self.__update_name() - name_row.pack_start(self.name_entry, True, True, 0) + name_row.append(self.name_entry) self.audio_button = Gtk.Button.new() self.audio_button.connect("clicked", self.__audio_button_clicked_cb) @@ -115,15 +122,15 @@ class LayerControls(Gtk.EventBox, Loggable): self.video_button.connect("clicked", self.__video_button_clicked_cb) self.__update_video_button() - control_box = Gtk.ButtonBox() - control_box.set_layout(Gtk.ButtonBoxStyle.EXPAND) - control_box.add(self.video_button) - control_box.add(self.audio_button) - name_row.pack_start(control_box, False, False, 0) + control_box = Gtk.Box() + control_box.append(self.video_button) + control_box.append(self.audio_button) + control_box.add_css_class("linked") + name_row.append(control_box) space = Gtk.Label() space.props.vexpand = True - vbox.pack_start(space, False, False, 0) + vbox.append(space) self.ges_layer.connect("notify::priority", self.__layer_priority_changed_cb) self.ges_layer.connect("active-changed", self.__layer_active_changed_cb) @@ -135,7 +142,7 @@ class LayerControls(Gtk.EventBox, Loggable): self.connect("notify::window", self.__window_set_cb) def __window_set_cb(self, unused_window, unused_pspec): - self.props.window.set_cursor(Gdk.Cursor.new(Gdk.CursorType.HAND1)) + self.props.window.set_cursor(Gdk.Cursor.new_from_name("pointer")) def __del__(self): self.name_entry.disconnect_by_func(self.__name_focus_out_cb) @@ -150,7 +157,7 @@ class LayerControls(Gtk.EventBox, Loggable): return self.__update_name() - def __name_focus_out_cb(self, unused_widget, unused_event): + def __name_focus_out_cb(self, unused_controller): self.name_entry.delete_selection() current_name = self.ges_layer.ui.get_name() name = self.name_entry.get_text() @@ -251,7 +258,7 @@ class LayerControls(Gtk.EventBox, Loggable): def __update_audio_button(self): active = self.__check_tracks_active(self.timeline_audio_tracks) icon = AUDIO_ICONS[active] - self.audio_button.set_image(Gtk.Image.new_from_icon_name(icon, Gtk.IconSize.BUTTON)) + self.audio_button.set_icon_name(icon) def __video_button_clicked_cb(self, button): self.ges_layer.set_active_for_tracks(not self.__check_tracks_active( @@ -261,7 +268,7 @@ class LayerControls(Gtk.EventBox, Loggable): def __update_video_button(self): active = self.__check_tracks_active(self.timeline_video_tracks) icon = VIDEO_ICONS[active] - self.video_button.set_image(Gtk.Image.new_from_icon_name(icon, Gtk.IconSize.BUTTON)) + self.video_button.set_icon_name(icon) def __layer_active_changed_cb(self, ges_layer, active, tracks): self.__update_video_button() @@ -287,23 +294,20 @@ class LayerControls(Gtk.EventBox, Loggable): icon = "audio-x-generic-symbolic" if icon != self.__icon: - image = Gtk.Image.new_from_icon_name(icon, Gtk.IconSize.BUTTON) - self.menubutton.props.image = image + self.menubutton.set_icon_name(icon) self.__icon = icon -class Layer(Gtk.Layout, Zoomable, Loggable): +class Layer(Gtk.Fixed, Loggable): """Container for the clips widgets of a layer.""" __gtype_name__ = "PitiviLayer" def __init__(self, ges_layer, timeline): - Gtk.Layout.__init__(self) - Zoomable.__init__(self) + Gtk.Fixed.__init__(self) Loggable.__init__(self) self.ges_layer = ges_layer - self.ges_layer.ui = self self.timeline = timeline self.app = timeline.app @@ -318,9 +322,7 @@ class Layer(Gtk.Layout, Zoomable, Loggable): self.props.valign = Gtk.Align.START self.media_types = GES.TrackType(0) - for ges_clip in ges_layer.get_clips(): - self._add_clip(ges_clip) - self.check_media_types() + self.old_media_types = GES.TrackType(0) def set_name(self, name): self.ges_layer.set_meta("video::name", name) @@ -351,12 +353,13 @@ class Layer(Gtk.Layout, Zoomable, Loggable): self.ges_layer.disconnect_by_func(self._clip_added_cb) self.ges_layer.disconnect_by_func(self._clip_removed_cb) - def check_media_types(self): + def _check_media_types(self): if self.timeline.editing_context: - self.info("Not updating media types as we are editing the timeline") + self.info("Not updating media types as" + " we are editing the timeline") return - old_media_types = self.media_types + self.old_media_types = self.media_types self.media_types: GES.TrackType = GES.TrackType(0) ges_clips = self.ges_layer.get_clips() for ges_clip in ges_clips: @@ -365,18 +368,6 @@ class Layer(Gtk.Layout, Zoomable, Loggable): # Cannot find more types than these. break - if self.media_types & GES.TrackType.AUDIO and self.media_types & GES.TrackType.VIDEO: - self.props.height_request = LAYER_HEIGHT - else: - # If the layer is empty, set layer's height to default height. - self.props.height_request = LAYER_HEIGHT // 2 - - if hasattr(self.ges_layer, "control_ui") and self.ges_layer.control_ui: - self.ges_layer.control_ui.update(self.media_types) - - if old_media_types != self.media_types: - self.update_position() - def _clip_child_added_cb(self, ges_clip, child): self.check_media_types() @@ -387,19 +378,13 @@ class Layer(Gtk.Layout, Zoomable, Loggable): self._add_clip(ges_clip) self.check_media_types() - def _add_clip(self, ges_clip): - ui_type = elements.GES_TYPE_UI_TYPE.get(ges_clip.__gtype__, None) - if ui_type is None: - self.error("Implement UI for type %s?", ges_clip.__gtype__) - return - - widget = ui_type(self, ges_clip) - self._children.append(widget) + def _add_clip_ui(self, ges_clip, clip_ui): + self._children.append(clip_ui) self._children.sort(key=lambda clip: clip.z_order) - self.put(widget, self.ns_to_pixel(ges_clip.props.start), 0) - widget.update_position() + + clip_ui.update_position() self._changed = True - widget.show_all() + clip_ui.show() ges_clip.connect_after("child-added", self._clip_child_added_cb) ges_clip.connect_after("child-removed", self._clip_child_removed_cb) @@ -408,6 +393,75 @@ class Layer(Gtk.Layout, Zoomable, Loggable): self._remove_clip(ges_clip) self.check_media_types() + def _remove_clip_ui(self, ges_clip, clip_ui): + self.remove(clip_ui) + self._children.remove(clip_ui) + self._changed = True + clip_ui.release() + clip_ui = None + + ges_clip.disconnect_by_func(self._clip_child_added_cb) + ges_clip.disconnect_by_func(self._clip_child_removed_cb) + + def update_position(self): + pass + + def do_snapshot(self, snapshot): + Gtk.Fixed.do_snapshot(self, snapshot) + + if self._changed: + self._children.sort(key=lambda clip: clip.z_order) + for child in self._children: + if isinstance(child, elements.TransitionClip): + # TODO: Replace using Xlib apis + # window = child.get_window() + # window.raise_() + pass + self._changed = False + + for child in self._children: + self.snapshot_child(child, snapshot) + + +class FullLayer(Layer, Zoomable): + """Container for the Full clips.""" + + __gtype_name__ = "PitiviFullLayer" + + def __init__(self, ges_layer, timeline): + Layer.__init__(self, ges_layer, timeline) + Zoomable.__init__(self) + + self.ges_layer.ui = self + for ges_clip in ges_layer.get_clips(): + self._add_clip(ges_clip) + self.check_media_types() + + def check_media_types(self): + Layer._check_media_types(self) + + if self.media_types & GES.TrackType.AUDIO and self.media_types & GES.TrackType.VIDEO: + self.props.height_request = LAYER_HEIGHT + else: + # If the layer is empty, set layer's height to default height. + self.props.height_request = LAYER_HEIGHT // 2 + + if hasattr(self.ges_layer, "control_ui") and self.ges_layer.control_ui: + self.ges_layer.control_ui.update(self.media_types) + + if self.old_media_types != self.media_types: + self.update_position() + + def _add_clip(self, ges_clip): + ui_type = elements.GES_TYPE_UI_TYPE.get(ges_clip.__gtype__, None) + if ui_type is None: + self.error("Implement UI for type %s?", ges_clip.__gtype__) + return + + widget = ui_type(self, ges_clip) + self.put(widget, self.ns_to_pixel(ges_clip.props.start), 0) + Layer._add_clip_ui(self, ges_clip, widget) + def _remove_clip(self, ges_clip): if not ges_clip.ui: return @@ -417,28 +471,66 @@ class Layer(Gtk.Layout, Zoomable, Loggable): self.error("Implement UI for type %s?", ges_clip.__gtype__) return - self.remove(ges_clip.ui) - self._children.remove(ges_clip.ui) - self._changed = True - ges_clip.ui.release() - ges_clip.ui = None - - ges_clip.disconnect_by_func(self._clip_child_added_cb) - ges_clip.disconnect_by_func(self._clip_child_removed_cb) + Layer._remove_clip_ui(self, ges_clip, ges_clip.ui) def update_position(self): for ges_clip in self.ges_layer.get_clips(): - if hasattr(ges_clip, "ui"): + if hasattr(ges_clip, 'ui') and ges_clip.ui: ges_clip.ui.update_position() - def do_draw(self, cr): - if self._changed: - self._children.sort(key=lambda clip: clip.z_order) - for child in self._children: - if isinstance(child, elements.TransitionClip): - window = child.get_window() - window.raise_() - self._changed = False - for child in self._children: - self.propagate_draw(child, cr) +class MiniLayer(Layer): + """Container for the Mini clips.""" + + __gtype_name__ = "PitiviMiniLayer" + + def __init__(self, ges_layer, timeline): + Layer.__init__(self, ges_layer, timeline) + + self.ges_layer.mini_ui = self + for ges_clip in ges_layer.get_clips(): + self._add_clip(ges_clip) + self.check_media_types() + + def check_media_types(self): + if self.timeline.editing_context: + self.info("Not updating media types as" + " we are editing the timeline") + return + + self.props.height_request = MINI_LAYER_HEIGHT + + def _add_clip(self, ges_clip): + ui_type = elements.GES_TYPE_MINI_UI_TYPE.get(ges_clip.__gtype__, None) + if ui_type is None: + self.error("Implement Mini UI for type %s?", ges_clip.__gtype__) + return + + widget = ui_type(self, ges_clip) + ratio = self.timeline.calc_best_zoom_ratio() + x = Zoomable.ns_to_pixel(ges_clip.props.start, zoomratio=ratio) + self.put(widget, x, 0) + Layer._add_clip_ui(self, ges_clip, widget) + + def _remove_clip(self, ges_clip): + if not ges_clip.mini_ui: + return + + ui_type = elements.GES_TYPE_MINI_UI_TYPE.get(ges_clip.__gtype__, None) + if ui_type is None: + self.error("Implement Mini UI for type %s?", ges_clip.__gtype__) + return + + Layer._remove_clip_ui(self, ges_clip, ges_clip.mini_ui) + + def update_position(self): + pass + + def do_snapshot(self, snapshot): + Layer.do_snapshot(self, snapshot) + + for layer in self.timeline.ges_timeline.get_layers(): + # pylint: disable=W0212 + for child in layer.mini_ui._children: + child.update_position() + self.timeline.mini_layout.queue_draw() diff --git a/pitivi/timeline/markers.py b/pitivi/timeline/markers.py index 674e5c3feaafe70ce964960e7221eb599322bf52..4efdf5bb2f6efae1b07628e46aab7dbf401d0869 100644 --- a/pitivi/timeline/markers.py +++ b/pitivi/timeline/markers.py @@ -25,29 +25,27 @@ from gi.repository import Gtk from pitivi.configure import get_ui_dir from pitivi.utils.loggable import Loggable from pitivi.utils.timeline import Zoomable +from pitivi.utils.ui import get_widget_children TIMELINE_MARKER_SIZE = 10 CLIP_MARKER_HEIGHT = 12 CLIP_MARKER_WIDTH = 10 -class Marker(Gtk.EventBox, Loggable): +class Marker(Gtk.Box, Loggable): """Widget representing a marker.""" def __init__(self, ges_marker, class_name, width, height): - Gtk.EventBox.__init__(self) + Gtk.Box.__init__(self) Loggable.__init__(self) - self.add_events(Gdk.EventMask.ENTER_NOTIFY_MASK | - Gdk.EventMask.LEAVE_NOTIFY_MASK) - self.ges_marker = ges_marker self.ges_marker.ui = self self.position_ns = self.ges_marker.props.position self.width = width self.height = height - self.get_style_context().add_class(class_name) + self.add_css_class(class_name) self.ges_marker.connect("notify-meta", self._notify_meta_cb) self._selected = False @@ -101,17 +99,17 @@ class Marker(Gtk.EventBox, Loggable): self.unset_state_flags(Gtk.StateFlags.SELECTED) -class MarkersBox(Gtk.EventBox, Zoomable, Loggable): +class MarkersBox(Gtk.Box, Zoomable, Loggable): """Container for displaying and managing markers.""" def __init__(self, app, hadj=None): - Gtk.EventBox.__init__(self) + Gtk.Box.__init__(self) Zoomable.__init__(self) Loggable.__init__(self) - self.layout = Gtk.Layout() - self.add(self.layout) - self.get_style_context().add_class("MarkersBox") + self.layout = Gtk.Fixed() + self.append(self.layout) + self.add_css_class("MarkersBox") self.app = app @@ -127,10 +125,6 @@ class MarkersBox(Gtk.EventBox, Zoomable, Loggable): self.marker_moving: Optional[Marker] = None self.marker_new: Optional[Marker] = None - self.add_events(Gdk.EventMask.POINTER_MOTION_MASK | - Gdk.EventMask.BUTTON_PRESS_MASK | - Gdk.EventMask.BUTTON_RELEASE_MASK) - def first_marker(self, before: Optional[int] = None, after: Optional[int] = None) -> Optional[int]: """Returns position of the closest marker found before or after the given timestamp. @@ -177,7 +171,7 @@ class MarkersBox(Gtk.EventBox, Zoomable, Loggable): @markers_container.setter def markers_container(self, ges_markers_container): if self.__markers_container: - for marker in self.layout.get_children(): + while marker := self.layout.get_first_child(): self.layout.remove(marker) self.__markers_container.disconnect_by_func(self._marker_added_cb) self.__markers_container.disconnect_by_func(self._marker_removed_cb) @@ -190,6 +184,11 @@ class MarkersBox(Gtk.EventBox, Zoomable, Loggable): self.__markers_container.connect("marker-removed", self._marker_removed_cb) self.__markers_container.connect("marker-moved", self._marker_moved_cb) + # Hacky fix for 2x calls to release() + self.__markers_container.connect("marker-added", self._marker_added_cb) + self.__markers_container.connect("marker-removed", self._marker_removed_cb) + self.__markers_container.connect("marker-moved", self._marker_moved_cb) + def release(self): if self.__markers_container: self.__markers_container.disconnect_by_func(self._marker_added_cb) @@ -223,7 +222,7 @@ class MarkersBox(Gtk.EventBox, Zoomable, Loggable): self._update_position() def _update_position(self): - for marker in self.layout.get_children(): + for marker in get_widget_children(self.layout): position = self.ns_to_pixel(marker.position) - self.offset - marker.width / 2 self.layout.move(marker, position, 0) @@ -339,8 +338,7 @@ class MarkerPopover(Gtk.Popover): self.comment_textview.get_buffer().set_text(self.marker.comment) self.set_position(Gtk.PositionType.TOP) - self.set_relative_to(self.marker) - self.show_all() + self.marker.append(self) @Gtk.Template.Callback() def remove_button_clicked_cb(self, event): @@ -375,7 +373,7 @@ class ClipMarkersBox(MarkersBox): self.props.height_request = CLIP_MARKER_HEIGHT - self.get_style_context().add_class("ClipMarkersBox") + self.add_css_class("ClipMarkersBox") def _create_marker(self, ges_marker): return Marker(ges_marker, "ClipMarker", CLIP_MARKER_WIDTH, CLIP_MARKER_HEIGHT) diff --git a/pitivi/timeline/previewers.py b/pitivi/timeline/previewers.py index 12224f4ef5165f13b8af073a7140ac306c0cef10..4c36c738a76e8f35a57cf809147c259c575a77ea 100644 --- a/pitivi/timeline/previewers.py +++ b/pitivi/timeline/previewers.py @@ -29,6 +29,7 @@ from gi.repository import GdkPixbuf from gi.repository import GES from gi.repository import GLib from gi.repository import GObject +from gi.repository import Graphene from gi.repository import Gst from gi.repository import Gtk from gi.repository import Pango @@ -47,6 +48,8 @@ from pitivi.utils.system import CPUUsageTracker from pitivi.utils.timeline import Zoomable from pitivi.utils.ui import CLIP_BORDER_WIDTH from pitivi.utils.ui import EXPANDED_SIZE +from pitivi.utils.ui import get_widget_children +from pitivi.utils.ui import MINI_LAYER_HEIGHT # Our C module optimizing waveforms rendering try: @@ -409,7 +412,7 @@ class Previewer(GObject.Object): return max(THUMB_PERIOD, quantized) -class ImagePreviewer(Gtk.Layout, Previewer, Zoomable, Loggable): +class ImagePreviewer(Gtk.Fixed, Previewer, Zoomable, Loggable): """A previewer widget drawing the same thumbnail repeatedly. Can be used for Image clips or Color clips. @@ -419,12 +422,12 @@ class ImagePreviewer(Gtk.Layout, Previewer, Zoomable, Loggable): __gsignals__ = PREVIEW_GENERATOR_SIGNALS def __init__(self, ges_elem, max_cpu_usage): - Gtk.Layout.__init__(self) + Gtk.Fixed.__init__(self) Previewer.__init__(self, GES.TrackType.VIDEO, max_cpu_usage) Zoomable.__init__(self) Loggable.__init__(self) - self.get_style_context().add_class("VideoPreviewer") + self.add_css_class("VideoPreviewer") self.ges_elem = ges_elem self.uri = get_proxy_target(ges_elem).props.id @@ -502,7 +505,7 @@ class ImagePreviewer(Gtk.Layout, Previewer, Zoomable, Loggable): self.put(thumb, x, y) thumbs[position] = thumb - thumb.set_from_pixbuf(self.__image_pixbuf) + thumb.set_pixbuf(self.__image_pixbuf) thumb.set_visible(True) for thumb in self.thumbs.values(): @@ -530,7 +533,7 @@ class ImagePreviewer(Gtk.Layout, Previewer, Zoomable, Loggable): else: opacity = 1.0 - for thumb in self.get_children(): + for thumb in get_widget_children(self): thumb.props.opacity = opacity def start_generation(self): @@ -831,7 +834,7 @@ class AssetPreviewer(Previewer, Loggable): self.pipeline.set_state(Gst.State.READY) -class VideoPreviewer(Gtk.Layout, AssetPreviewer, Zoomable): +class VideoPreviewer(Gtk.Fixed, AssetPreviewer, Zoomable): """A video previewer widget, drawing thumbnails. Attributes: @@ -843,11 +846,11 @@ class VideoPreviewer(Gtk.Layout, AssetPreviewer, Zoomable): __gsignals__ = PREVIEW_GENERATOR_SIGNALS def __init__(self, ges_elem, max_cpu_usage): - Gtk.Layout.__init__(self) + Gtk.Fixed.__init__(self) Zoomable.__init__(self) AssetPreviewer.__init__(self, get_proxy_target(ges_elem), max_cpu_usage) - self.get_style_context().add_class("VideoPreviewer") + self.add_css_class("VideoPreviewer") self.ges_elem: GES.VideoUriSource = ges_elem self.thumbs = {} @@ -864,7 +867,7 @@ class VideoPreviewer(Gtk.Layout, AssetPreviewer, Zoomable): else: opacity = 1.0 - for thumb in self.get_children(): + for thumb in get_widget_children(self): thumb.props.opacity = opacity def refresh(self): @@ -901,7 +904,7 @@ class VideoPreviewer(Gtk.Layout, AssetPreviewer, Zoomable): thumbs[position] = thumb if position in self.thumb_cache: pixbuf = self.thumb_cache[position] - thumb.set_from_pixbuf(pixbuf) + thumb.set_pixbuf(pixbuf) thumb.set_visible(True) else: if position not in self.failures and position != self.position: @@ -923,7 +926,7 @@ class VideoPreviewer(Gtk.Layout, AssetPreviewer, Zoomable): # Can happen because we don't stop the pipeline before # updating the thumbnails in _update_thumbnails. return - thumb.set_from_pixbuf(pixbuf) + thumb.set_pixbuf(pixbuf) def release(self): """Stops preview generation and cleans the object.""" @@ -948,13 +951,13 @@ class VideoPreviewer(Gtk.Layout, AssetPreviewer, Zoomable): self._update_thumbnails() -class Thumbnail(Gtk.Image): +class Thumbnail(Gtk.Picture): """Simple widget representing a Thumbnail.""" def __init__(self, width, height): - Gtk.Image.__init__(self) + Gtk.Picture.__init__(self) - self.get_style_context().add_class("Thumbnail") + self.add_css_class("Thumbnail") self.props.width_request = width self.props.height_request = height @@ -1161,18 +1164,18 @@ def get_wavefile_location_for_uri(uri): return os.path.join(cache_dir, filename) -class AudioPreviewer(Gtk.Layout, Previewer, Zoomable, Loggable): +class AudioPreviewer(Gtk.Fixed, Previewer, Zoomable, Loggable): """Audio previewer using the results from the "level" GStreamer element.""" __gsignals__ = PREVIEW_GENERATOR_SIGNALS def __init__(self, ges_elem, max_cpu_usage): - Gtk.Layout.__init__(self) + Gtk.Fixed.__init__(self) Previewer.__init__(self, GES.TrackType.AUDIO, max_cpu_usage) Zoomable.__init__(self) Loggable.__init__(self) - self.get_style_context().add_class("AudioPreviewer") + self.add_css_class("AudioPreviewer") self.pipeline = None self._wavebin = None @@ -1293,7 +1296,11 @@ class AudioPreviewer(Gtk.Layout, Previewer, Zoomable, Loggable): return True return False - def do_draw(self, context): + def do_snapshot(self, snapshot): + Gtk.Fixed.do_snapshot(self, snapshot) + bounds = Graphene.Rect().init(0, 0, self.get_width(), self.get_height()) + context = snapshot.append_cairo(bounds) + if not self.samples or not self.ges_elem.get_track() or not self.ges_elem.props.active: # Nothing to draw. return @@ -1301,7 +1308,15 @@ class AudioPreviewer(Gtk.Layout, Previewer, Zoomable, Loggable): # The area we have to refresh is this rect inside the clip. # For example rect.x is > 0 when the start of the clip is out of view. # rect.width = how many pixels of the clip are in view horizontally. - res, rect = Gdk.cairo_get_clip_rectangle(context) + + x1, y1, x2, y2 = context.clip_extents() + rect = Gdk.Rectangle() + rect.x = x1 + rect.y = y1 + rect.width = x2 - x1 + rect.height = y2 - y1 + + res = (rect.width > 0 and rect.height > 0) assert res start = self.ges_elem.props.start @@ -1392,18 +1407,18 @@ class AudioPreviewer(Gtk.Layout, Previewer, Zoomable, Loggable): Zoomable.__del__(self) -class TitlePreviewer(Gtk.Layout, Previewer, Zoomable, Loggable): +class TitlePreviewer(Gtk.Fixed, Previewer, Zoomable, Loggable): """Title Clip previewer using Pango to draw text on the clip.""" __gsignals__ = PREVIEW_GENERATOR_SIGNALS def __init__(self, ges_elem): - Gtk.Layout.__init__(self) + Gtk.Fixed.__init__(self) Previewer.__init__(self, GES.TrackType.VIDEO, None) Zoomable.__init__(self) Loggable.__init__(self) - self.get_style_context().add_class("TitlePreviewer") + self.add_css_class("TitlePreviewer") self.ges_elem = ges_elem font = Gtk.Settings.get_default().get_property("gtk-font-name") @@ -1417,7 +1432,10 @@ class TitlePreviewer(Gtk.Layout, Previewer, Zoomable, Loggable): if pspec.name == "text": self.queue_draw() - def do_draw(self, context): + def do_snapshot(self, snapshot): + Gtk.Fixed.do_snapshot(self, snapshot) + bounds = Graphene.Rect().init(0, 0, self.get_width(), self.get_height()) + context = snapshot.append_cairo(bounds) width = self.get_allocated_width() height = self.get_allocated_height() @@ -1488,3 +1506,23 @@ class TitlePreviewer(Gtk.Layout, Previewer, Zoomable, Loggable): def release(self): # Nothing to release pass + + +class MiniPreview(Gtk.Fixed): + """Mini Clip previewer to draw color filled mini clips.""" + + def __init__(self, color): + Gtk.Fixed.__init__(self) + self.add_css_class("MiniPreviewer") + self.color = color + self.props.height_request = MINI_LAYER_HEIGHT + + def do_snapshot(self, snapshot): + Gtk.Fixed.do_snapshot(self, snapshot) + bounds = Graphene.Rect().init(0, 0, self.get_width(), self.get_height()) + context = snapshot.append_cairo(bounds) + + rect = Gdk.cairo_get_clip_rectangle(context)[1] + context.set_source_rgb(*self.color) + context.rectangle(0, 0, rect.width, rect.height) + context.fill() diff --git a/pitivi/timeline/ruler.py b/pitivi/timeline/ruler.py index 4f90baacd7c6edb688683b0671f467e7388d1761..024202b2f86d67ef12851aec78c2d369b724437c 100644 --- a/pitivi/timeline/ruler.py +++ b/pitivi/timeline/ruler.py @@ -104,10 +104,6 @@ class ScaleRuler(Gtk.DrawingArea, Loggable): self._pipeline = None self.ges_timeline = None - self.add_events(Gdk.EventMask.POINTER_MOTION_MASK | - Gdk.EventMask.BUTTON_PRESS_MASK | Gdk.EventMask.BUTTON_RELEASE_MASK | - Gdk.EventMask.SCROLL_MASK) - self.pixbuf = None # all values are in pixels @@ -122,6 +118,8 @@ class ScaleRuler(Gtk.DrawingArea, Loggable): Gtk.Settings.get_default().connect("notify::gtk-theme-name", self._update_colors_cb) Gtk.Settings.get_default().connect("notify::gtk-application-prefer-dark-theme", self._update_colors_cb) + self.set_draw_func(self.draw_func) + def set_pipeline(self, pipeline): self._pipeline = pipeline self.ges_timeline = pipeline.props.timeline @@ -157,9 +155,7 @@ class ScaleRuler(Gtk.DrawingArea, Loggable): # Gtk.Widget overrides - def do_configure_event(self, unused_event): - width = self.get_allocated_width() - height = self.get_allocated_height() + def do_resize(self, width, height): self.debug("Configuring, height %d, width %d", width, height) # Destroy previous buffer @@ -173,7 +169,7 @@ class ScaleRuler(Gtk.DrawingArea, Loggable): self._set_colors() return False - def do_draw(self, context): + def draw_func(self, unused_widget, context, unused_width, unused_height): if self.pixbuf is None: self.info("No buffer to paint") return False diff --git a/pitivi/timeline/timeline.py b/pitivi/timeline/timeline.py index 8b341d67ea75a1fe6ee020d1d3cb2915cd12110d..4526626e3f5dbd4d2de7f28dc2e388aee03d74dc 100644 --- a/pitivi/timeline/timeline.py +++ b/pitivi/timeline/timeline.py @@ -24,6 +24,8 @@ from gi.repository import Gdk from gi.repository import GES from gi.repository import Gio from gi.repository import GLib +from gi.repository import GObject +from gi.repository import Graphene from gi.repository import Gst from gi.repository import Gtk @@ -37,8 +39,9 @@ from pitivi.settings import GlobalSettings from pitivi.timeline.elements import Clip from pitivi.timeline.elements import TransitionClip from pitivi.timeline.elements import TrimHandle -from pitivi.timeline.layer import Layer +from pitivi.timeline.layer import FullLayer from pitivi.timeline.layer import LayerControls +from pitivi.timeline.layer import MiniLayer from pitivi.timeline.layer import SpacedSeparator from pitivi.timeline.markers import MarkersBox from pitivi.timeline.previewers import Previewer @@ -54,8 +57,8 @@ from pitivi.utils.timeline import Selection from pitivi.utils.timeline import TimelineError from pitivi.utils.timeline import UNSELECT from pitivi.utils.timeline import Zoomable -from pitivi.utils.ui import EFFECT_TARGET_ENTRY from pitivi.utils.ui import LAYER_HEIGHT +from pitivi.utils.ui import MINI_LAYER_HEIGHT from pitivi.utils.ui import PLAYHEAD_COLOR from pitivi.utils.ui import PLAYHEAD_WIDTH from pitivi.utils.ui import SEPARATOR_HEIGHT @@ -65,7 +68,6 @@ from pitivi.utils.ui import SNAPBAR_COLOR from pitivi.utils.ui import SNAPBAR_WIDTH from pitivi.utils.ui import SPACING from pitivi.utils.ui import TOUCH_INPUT_SOURCES -from pitivi.utils.ui import URI_TARGET_ENTRY from pitivi.utils.widgets import ZoomBox @@ -135,22 +137,23 @@ class Marquee(Gtk.Box, Loggable): Args: timeline (Timeline): The timeline indirectly containing the marquee. + layout (LayersLayout): Layout containing the layers on which the marquee would be drawn. """ __gtype_name__ = "PitiviMarquee" - def __init__(self, timeline): + def __init__(self, timeline, layout): Gtk.Box.__init__(self) Loggable.__init__(self) self._timeline = timeline + self.layout = layout self.start_x, self.start_y = 0, 0 self.end_x, self.end_y = self.start_x, self.start_y - self.props.no_show_all = True self.hide() - self.get_style_context().add_class("Marquee") + self.add_css_class("Marquee") def hide(self): """Hides and resets the widget.""" @@ -159,16 +162,18 @@ class Marquee(Gtk.Box, Loggable): self.props.width_request = -1 self.set_visible(False) - def set_start_position(self, event): + def set_start_position(self, controller, event_x, event_y): """Sets the first corner of the marquee. Args: - event (Gdk.EventButton): The button pressed event which might - start a select operation. + controller (Gtk.GestureClick): The controller responsible for handling + clicks. A pressed signal by it might start a select operation. + event_x: X cordinate of the event + event_y: Y cordinate of the event """ - event_widget = Gtk.get_event_widget(event) + event_widget = Gtk.get_widget(controller) self.start_x, self.start_y = event_widget.translate_coordinates( - self._timeline.layout.layers_vbox, event.x, event.y) + self.layout.layers_vbox, event_x, event_y) self.end_x, self.end_y = self.start_x, self.start_y self.get_parent().move(self, self.start_x, self.start_y) @@ -176,16 +181,18 @@ class Marquee(Gtk.Box, Loggable): self.props.height_request = 0 self.set_visible(True) - def move(self, event): + def move(self, controller, event_x, event_y): """Sets the second corner of the marquee. Args: - event (Gdk.EventMotion): The motion event which contains - the coordinates of the second corner. + controller (Gtk.EventControllerMotion): The controller responsible for handling + motion. + event_x: X cordinate of the event + event_y: Y cordinate of the event """ - event_widget = Gtk.get_event_widget(event) + event_widget = Gtk.get_widget(controller) self.end_x, self.end_y = event_widget.translate_coordinates( - self._timeline.layout.layers_vbox, event.x, event.y) + self.layout.layers_vbox, event_x, event_y) x = min(self.start_x, self.end_x) y = min(self.start_y, self.end_y) @@ -194,29 +201,34 @@ class Marquee(Gtk.Box, Loggable): self.props.width_request = abs(self.start_x - self.end_x) self.props.height_request = abs(self.start_y - self.end_y) - def find_clips(self): + def find_clips(self, mini=False): """Finds the clips which intersect the marquee. + Args: + mini (bool): Layers layout or Mini Layers layout. + Returns: List[GES.Clip]: The clips under the marquee. """ if self.props.width_request == 0: return [] - start_layer = self._timeline.get_layer_at(self.start_y)[0] - end_layer = self._timeline.get_layer_at(self.end_y)[0] - start_pos = max(0, self._timeline.pixel_to_ns(self.start_x)) - end_pos = max(0, self._timeline.pixel_to_ns(self.end_x)) + start_layer = self._timeline.get_layer_at(self.start_y, mini=mini)[0] + end_layer = self._timeline.get_layer_at(self.end_y, mini=mini)[0] + + ratio = self._timeline.calc_best_zoom_ratio() if mini else None + start_pos = max(0, self._timeline.pixel_to_ns(self.start_x, zoomratio=ratio)) + end_pos = max(0, self._timeline.pixel_to_ns(self.end_x, zoomratio=ratio)) return self._timeline.get_clips_in_between(start_layer, end_layer, start_pos, end_pos) -class LayersLayout(Gtk.Layout, Zoomable, Loggable): +class LayersLayout(Gtk.ScrolledWindow, Loggable): """Layout for displaying scrollable layers, the playhead, snap indicator. The layers are actual widgets in a vertical Gtk.Box. - The playhead and the snap indicator are drawn on top in do_draw(). + The playhead and the snap indicator are drawn on top in do_snapshot(). Args: timeline (Timeline): The timeline indirectly containing the layout. @@ -227,54 +239,50 @@ class LayersLayout(Gtk.Layout, Zoomable, Loggable): """ def __init__(self, timeline): - Gtk.Layout.__init__(self) - Zoomable.__init__(self) + Gtk.ScrolledWindow.__init__(self) Loggable.__init__(self) + self.props.hscrollbar_policy = Gtk.PolicyType.EXTERNAL + self.props.vscrollbar_policy = Gtk.PolicyType.EXTERNAL + self.fixed = Gtk.Fixed() + self.set_child(self.fixed) self._timeline = timeline self.snap_position = 0 self.playhead_position = 0 self.layers_vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) - self.layers_vbox.get_style_context().add_class("LayersBox") - self.put(self.layers_vbox, 0, 0) + self.layers_vbox.add_css_class("LayersBox") + self.fixed.put(self.layers_vbox, 0, 0) - self.marquee = Marquee(timeline) - self.put(self.marquee, 0, 0) + self.marquee = Marquee(timeline, self) + self.fixed.put(self.marquee, 0, 0) - self.layers_vbox.connect("size-allocate", self.__size_allocate_cb) - - def zoom_changed(self): - # The width of the area/workspace changes when the zoom level changes. - self.update_width() - # Required so the playhead is redrawn. - self.queue_draw() + # TODO:Porting: find proper replacement + self.layers_vbox.connect("notify::width_request", self.__size_allocate_cb) - def do_draw(self, cr): + def do_snapshot(self, snapshot): """Draws the children and indicators.""" - Gtk.Layout.do_draw(self, cr) + Gtk.Widget.do_snapshot(self, snapshot) + bounds = Graphene.Rect().init(0, 0, self.get_width(), self.get_height()) + cr = snapshot.append_cairo(bounds) - self.__draw_playhead(cr) - self.__draw_snap_indicator(cr) + self._draw_playhead(cr) + self._draw_snap_indicator(cr) - def __draw_playhead(self, cr): - """Draws the playhead line.""" - offset = self.get_hadjustment().get_value() - position = max(0, self.playhead_position) - x = self.ns_to_pixel(position) - offset - self.__draw_vertical_bar(cr, x, PLAYHEAD_WIDTH, PLAYHEAD_COLOR) - - def __draw_snap_indicator(self, cr): - """Draws a snapping indicator line.""" - offset = self.get_hadjustment().get_value() - x = self.ns_to_pixel(self.snap_position) - offset - if x <= 0: - return + def update_width(self, width): + """Updates the width of the area and the width of the layers_vbox.""" + view_width = self.get_allocated_width() + space_at_the_end = view_width * 2 / 3 + width = width + space_at_the_end + width = max(view_width, width) - self.__draw_vertical_bar(cr, x, SNAPBAR_WIDTH, SNAPBAR_COLOR) + self.log("Updating the width_request of the layers_vbox: %s", width) + # This triggers a renegotiation of the size, meaning + # layers_vbox's "size-allocate" will be emitted, see __size_allocate_cb. + self.layers_vbox.props.width_request = width - def __draw_vertical_bar(self, cr, xpos, width, color): + def _draw_vertical_bar(self, cr, xpos, width, color): if xpos < 0: return @@ -287,29 +295,87 @@ class LayersLayout(Gtk.Layout, Zoomable, Loggable): cr.line_to(xpos, height) cr.stroke() + def __size_allocate_cb(self, layers_vbox, unused_pspec): + """Sets the size of the scrollable area to fit the layers_vbox.""" + print("size allocate") + allocation = layers_vbox.get_allocation() + self.log("The size of the layers_vbox changed: %sx%s", allocation.width, allocation.height) + # The additional space is for the 'Add layer' button. + self.set_max_content_width(allocation.width) + self.set_max_content_height(allocation.height + LAYER_HEIGHT / 2) + + +class FullLayersLayout(LayersLayout, Zoomable): + + def __init__(self, timeline): + Zoomable.__init__(self) + LayersLayout.__init__(self, timeline) + + def _draw_playhead(self, cr): + """Draws the playhead line.""" + offset = self.get_hadjustment().get_value() + position = max(0, self.playhead_position) + + x = self.ns_to_pixel(position) - offset + self._draw_vertical_bar(cr, x, PLAYHEAD_WIDTH, PLAYHEAD_COLOR) + + def _draw_snap_indicator(self, cr): + """Draws a snapping indicator line.""" + offset = self.get_hadjustment().get_value() + x = self.ns_to_pixel(self.snap_position) - offset + if x <= 0: + return + + self._draw_vertical_bar(cr, x, SNAPBAR_WIDTH, SNAPBAR_COLOR) + def update_width(self): - """Updates the width of the area and the width of the layers_vbox.""" ges_timeline = self._timeline.ges_timeline - view_width = self.get_allocated_width() - space_at_the_end = view_width * 2 / 3 duration = 0 if not ges_timeline else ges_timeline.props.duration - width = self.ns_to_pixel(duration) + space_at_the_end - width = max(view_width, width) + width = self.ns_to_pixel(duration) - self.log("Updating the width_request of the layers_vbox: %s", width) - # This triggers a renegotiation of the size, meaning - # layers_vbox's "size-allocate" will be emitted, see __size_allocate_cb. - self.layers_vbox.props.width_request = width + LayersLayout.update_width(self, width) + + def zoom_changed(self): + # The width of the area/workspace changes when the zoom level changes. + self.update_width() + # Required so the playhead is redrawn. + self.queue_draw() - def __size_allocate_cb(self, unused_widget, allocation): - """Sets the size of the scrollable area to fit the layers_vbox.""" - self.log("The size of the layers_vbox changed: %sx%s", allocation.width, allocation.height) - self.props.width = allocation.width - # The additional space is for the 'Add layer' button. - self.props.height = allocation.height + LAYER_HEIGHT / 2 +class MiniLayersLayout(LayersLayout): -class Timeline(Gtk.EventBox, Zoomable, Loggable): + def __init__(self, timeline): + LayersLayout.__init__(self, timeline) + + def _draw_playhead(self, cr): + """Draws the playhead line.""" + offset = self.get_hadjustment().get_value() + position = max(0, self.playhead_position) + + ratio = self._timeline.calc_best_zoom_ratio() + x = Zoomable.ns_to_pixel(position, zoomratio=ratio) - offset + self._draw_vertical_bar(cr, x, PLAYHEAD_WIDTH, PLAYHEAD_COLOR) + + def _draw_snap_indicator(self, cr): + """Draws a snapping indicator line.""" + offset = self.get_hadjustment().get_value() + ratio = self._timeline.calc_best_zoom_ratio() + x = Zoomable.ns_to_pixel(self.snap_position, zoomratio=ratio) - offset + if x <= 0: + return + + self._draw_vertical_bar(cr, x, SNAPBAR_WIDTH, SNAPBAR_COLOR) + + def update_width(self): + ges_timeline = self._timeline.ges_timeline + duration = 0 if not ges_timeline else ges_timeline.props.duration + ratio = self._timeline.calc_best_zoom_ratio() + width = Zoomable.ns_to_pixel(duration, zoomratio=ratio) + + LayersLayout.update_width(self, width) + + +class Timeline(Gtk.Box, Zoomable, Loggable): """Container for the layers controls and representation. Attributes: @@ -319,7 +385,7 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable): __gtype_name__ = "PitiviTimeline" def __init__(self, app, size_group, editor_state): - Gtk.EventBox.__init__(self) + Gtk.Box.__init__(self) Zoomable.__init__(self) Loggable.__init__(self) @@ -331,14 +397,22 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable): self.props.can_focus = False hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) - self.add(hbox) + self.append(hbox) - self.layout = LayersLayout(self) + self.layout = FullLayersLayout(self) self.layout.props.can_focus = True - self.layout.props.can_default = True + self.layout.set_hexpand(True) + self.layout.set_halign(Gtk.Align.FILL) self.hadj = self.layout.get_hadjustment() self.vadj = self.layout.get_vadjustment() - hbox.pack_end(self.layout, True, True, 0) + hbox.append(self.layout) + + self.mini_layout = MiniLayersLayout(self) + + self.mini_layout_container = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0) + self.mini_layout_container.append(self.mini_layout) + self.mini_layout_container.props.height_request = MINI_LAYER_HEIGHT + self.mini_layout_container.hide() self._layers_controls_vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) self._layers_controls_vbox.props.hexpand = False @@ -347,29 +421,27 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable): # Stuff the layers controls in a ScrolledWindow so they can be scrolled. # Use self.layout's hadj to scroll the controls in sync with the layers. - scrolled_window = Gtk.ScrolledWindow(vadjustment=self.vadj) + scrolled_window = Gtk.ScrolledWindow() + scrolled_window.set_vadjustment(self.vadj) scrolled_window.props.propagate_natural_width = True scrolled_window.props.vscrollbar_policy = Gtk.PolicyType.EXTERNAL - scrolled_window.add(self._layers_controls_vbox) - hbox.pack_start(scrolled_window, False, False, 0) + scrolled_window.set_child(self._layers_controls_vbox) + hbox.prepend(scrolled_window) self.add_layer_button = Gtk.Button.new_with_label(_("Add layer")) - self.add_layer_button.props.margin = SPACING + self.add_layer_button.props.margin_top = SPACING + self.add_layer_button.props.margin_bottom = SPACING + self.add_layer_button.props.margin_start = SPACING + self.add_layer_button.props.margin_end = SPACING self.add_layer_button.set_halign(Gtk.Align.CENTER) self.add_layer_button.show() self.add_layer_button.set_action_name("timeline.add-layer") - self._layers_controls_vbox.pack_end(self.add_layer_button, False, False, 0) + self._layers_controls_vbox.append(self.add_layer_button) - self.get_style_context().add_class("Timeline") - self.props.expand = True - self.get_accessible().set_name("timeline canvas") - - # A window is needed to receive BUTTON_* events. This is the reason why - # Timeline is a Gtk.EventBox subclass and not directly a Gtk.Box, - # see `hbox` above. - assert self.get_has_window() - # A lot of operations go through the handlers of these events. - self.add_events(Gdk.EventType.BUTTON_PRESS | Gdk.EventType.BUTTON_RELEASE) + self.add_css_class("Timeline") + self.set_hexpand(True) + self.set_vexpand(True) + self.set_name("timeline canvas") # Whether the entire timeline content is in view and # it should be kept that way if it makes sense. @@ -390,6 +462,10 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable): self.delayed_scroll = {} self.__next_seek_position = None + # Whether the playhead is in Locked mode + # If this is true, Playhead will center itself. + self.playhead_locked = False + # Clip selection. self.selection = Selection() # The last layer where the user clicked. @@ -433,26 +509,40 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable): self.__last_clips_on_leave = None # To be able to receive effects dragged on clips. - self.drag_dest_set(0, [EFFECT_TARGET_ENTRY], Gdk.DragAction.COPY) - # To be able to receive assets dragged from the media library. - self.drag_dest_add_uri_targets() - - self.connect("drag-motion", self._drag_motion_cb) - self.connect("drag-leave", self._drag_leave_cb) - self.connect("drag-drop", self._drag_drop_cb) - self.connect("drag-data-received", self._drag_data_received_cb) + drop_target = Gtk.DropTarget.new(GObject.TYPE_NONE, Gdk.DragAction.COPY) + drop_target.set_preload(True) + drop_target.set_gtypes([Gdk.FileList, GObject.TYPE_STRING]) + mini_layout_controller = Gtk.DropTarget.new(GObject.TYPE_NONE, Gdk.DragAction.COPY) + mini_layout_controller.set_gtypes([Gdk.FileList, GObject.TYPE_STRING]) + + drop_target.connect("accept", self._accept_cb) + drop_target.connect("notify", self._notify_cb) + drop_target.connect("motion", self._motion_cb, self) + drop_target.connect("leave", self._leave_cb) + drop_target.connect("drop", self._drop_cb) + self.add_controller(drop_target) + + mini = True + mini_layout_controller.connect("accept", self._accept_cb) + mini_layout_controller.connect("notify::preload", self._notify_cb) + mini_layout_controller.connect("motion", self._motion_cb, self.mini_layout_container, mini) + mini_layout_controller.connect("leave", self._leave_cb) + mini_layout_controller.connect("drop", self._drop_cb) + self.mini_layout_container.add_controller(mini_layout_controller) self.app.settings.connect("edgeSnapDeadbandChanged", self.__snap_distance_changed_cb) - self.layout.layers_vbox.connect_after("size-allocate", self.__size_allocate_cb) + # self.layout.layers_vbox.connect_after("size-allocate", self.__size_allocate_cb) + self.layers_size_group = Gtk.SizeGroup.new(Gtk.SizeGroupMode.VERTICAL) self.hadj.connect("value-changed", self.__hadj_value_changed_cb) - def __size_allocate_cb(self, unused_widget, unused_allocation): - """Handles the layers vbox size allocations.""" - if self.delayed_scroll: - self.scroll_to_playhead(**self.delayed_scroll) + # TODO: Find replacement for set_child_property + # def __size_allocate_cb(self, unused_widget, unused_allocation): + # """Handles the layers vbox size allocations.""" + # if self.delayed_scroll: + # self.scroll_to_playhead(**self.delayed_scroll) @property def media_types(self): @@ -479,6 +569,10 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable): self.disconnect_by_func(self._button_release_event_cb) self.disconnect_by_func(self._motion_notify_event_cb) + self.mini_layout_container.disconnect_by_func(self._button_press_event_cb) + self.mini_layout_container.disconnect_by_func(self._button_release_event_cb) + self.mini_layout_container.disconnect_by_func(self._motion_notify_event_cb) + self.ges_timeline.disconnect_by_func(self._duration_changed_cb) self.ges_timeline.disconnect_by_func(self._layer_added_cb) self.ges_timeline.disconnect_by_func(self._layer_removed_cb) @@ -506,6 +600,7 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable): for ges_layer in self.ges_timeline.get_layers(): self._add_layer(ges_layer) self.__update_layers() + self.__update_mini_timeline_height() self.ges_timeline.connect("notify::duration", self._duration_changed_cb) self.ges_timeline.connect("layer-added", self._layer_added_cb) @@ -513,14 +608,31 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable): self.ges_timeline.connect("snapping-started", self.__snapping_started_cb) self.ges_timeline.connect("snapping-ended", self.__snapping_ended_cb) - self.connect("button-press-event", self._button_press_event_cb) - self.connect("button-release-event", self._button_release_event_cb) - self.connect("motion-notify-event", self._motion_notify_event_cb) + eventcontroller_click = Gtk.GestureClick() + eventcontroller_motion = Gtk.EventControllerMotion() + eventcontroller_click.connect("pressed", self._button_press_event_cb) + eventcontroller_click.connect("released", self._button_release_event_cb) + eventcontroller_click.set_button(0) + eventcontroller_motion.connect("motion", self._motion_notify_event_cb) + self.add_controller(eventcontroller_click) + self.add_controller(eventcontroller_motion) + + mini = True + mini_eventcontroller_click = Gtk.GestureClick() + mini_eventcontroller_motion = Gtk.EventControllerMotion() + mini_eventcontroller_click.connect("pressed", self._button_press_event_cb, mini) + mini_eventcontroller_click.connect("released", self._button_release_event_cb, mini) + mini_eventcontroller_click.set_button(0) + mini_eventcontroller_motion.connect("motion", self._motion_notify_event_cb, mini) + self.mini_layout_container.add_controller(mini_eventcontroller_click) + self.mini_layout_container.add_controller(mini_eventcontroller_motion) self.layout.update_width() + self.mini_layout.update_width() def _duration_changed_cb(self, ges_timeline, pspec): self.layout.update_width() + self.mini_layout.update_width() def scroll_to_playhead(self, align=None, when_not_in_view=False, delayed=False): """Scrolls so that the playhead is in view. @@ -564,9 +676,15 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable): self.__last_position = position self.layout.playhead_position = position self.layout.queue_draw() + + self.mini_layout.playhead_position = position + self.mini_layout.queue_draw() + layout_width = self.layout.get_allocation().width x = self.ns_to_pixel(self.__last_position) - self.hadj.get_value() - if pipeline.playing() and x > layout_width - 100: + if self.playhead_locked: + self.scroll_to_playhead() + elif pipeline.playing() and x > layout_width - 100: self.scroll_to_playhead(Gtk.Align.START) if not pipeline.playing(): self.update_visible_overlays() @@ -577,6 +695,9 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable): self.layout.snap_position = position self.layout.queue_draw() + self.mini_layout.snap_position = position + self.mini_layout.queue_draw() + def __snapping_ended_cb(self, *unused_args): self.__end_snap() @@ -585,6 +706,9 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable): self.layout.snap_position = 0 self.layout.queue_draw() + self.mini_layout.snap_position = 0 + self.mini_layout.queue_draw() + def update_snapping_distance(self): """Updates the snapping distance of self.ges_timeline.""" self.ges_timeline.set_snapping_distance( @@ -630,7 +754,8 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable): # Gtk events management - def do_scroll_event(self, event): + def do_scroll_event(self, controller, dx, dy): + event = controller.get_current_event() res, delta_x, delta_y = event.get_scroll_deltas() if not res: res, direction = event.get_scroll_direction() @@ -646,7 +771,7 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable): self.error("Could not handle %s scroll event", direction) return False - event_widget = Gtk.get_event_widget(event) + event_widget = Gtk.get_widget(controller) if event.get_state() & (Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.MOD1_MASK): # Zoom. @@ -726,23 +851,23 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable): """ self.__next_seek_position = next_seek_position - def _button_press_event_cb(self, unused_widget, event): + def _button_press_event_cb(self, controller, n_pressed, x, y, mini=False): """Handles a mouse button press event.""" - self.debug("PRESSED %s", event) + self.debug("PRESSED %s", controller) self.app.gui.editor.focus_timeline() - event_widget = Gtk.get_event_widget(event) + event_widget = controller.get_widget() - res, button = event.get_button() - if res and button == 1: + button = controller.get_button() + if button == 1: self.dragging_element = self._get_parent_of_type(event_widget, Clip) if isinstance(event_widget, TrimHandle): self.__clicked_handle = event_widget self.debug("Dragging element is %s", self.dragging_element) if self.dragging_element: - self.__drag_start_x = event.x - self._on_layer = self.dragging_element.layer.ges_layer + self.__drag_start_x = x + self._on_layer = self.dragging_element.ges_clip.props.layer else: layer_controls = self._get_parent_of_type(event_widget, LayerControls) if layer_controls: @@ -751,64 +876,72 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable): finalizing_action=CommitTimelineFinalizingAction(self._project.pipeline), toplevel=True) else: - self.layout.marquee.set_start_position(event) + if not mini: + self.layout.marquee.set_start_position(controller, x, y) + else: + self.mini_layout.marquee.set_start_position(controller, x, y) - self.scrubbing = res and button == 3 + self.scrubbing = button == 3 if self.scrubbing: - self._seek(event) + self._seek(controller, x, y, mini) clip = self._get_parent_of_type(event_widget, Clip) if clip: clip.shrink_trim_handles() - self._scrolling = res and button == 2 + self._scrolling = button == 2 if self._scrolling: # pylint: disable=attribute-defined-outside-init - self._scroll_start_x = event.x - self._scroll_start_y = event.y + self._scroll_start_x = x + self._scroll_start_y = y - def _button_release_event_cb(self, unused_widget, event): - self.debug("RELEASED %s", event) + def _button_release_event_cb(self, controller, n_pressed, x, y, mini=False): + self.debug("RELEASED %s", controller) allow_seek = not self.__got_dragged - res, button = event.get_button() + button = controller.get_button() if self.dragging_element: self.drag_end() elif self.__moving_layer: self.__end_moving_layer() return False - elif self.layout.marquee.is_visible() and res and button == 1: - clips = self.layout.marquee.find_clips() - self.selection.set_selection(clips, SELECT) - self.layout.marquee.hide() + elif self.layout.marquee.is_visible() and button == 1: + if not mini: + clips = self.layout.marquee.find_clips(mini=mini) + self.selection.set_selection(clips, SELECT) + self.layout.marquee.hide() + else: + clips = self.mini_layout.marquee.find_clips(mini=mini) + self.selection.set_selection(clips, SELECT) + self.mini_layout.marquee.hide() self.scrubbing = False self._scrolling = False - if allow_seek and res and button == 1: + if allow_seek and button == 1: if self.app.settings.leftClickAlsoSeeks: if self.__next_seek_position is not None: self._project.pipeline.simple_seek(self.__next_seek_position) self.__next_seek_position = None else: - event_widget = Gtk.get_event_widget(event) + event_widget = Gtk.get_widget(controller) if event_widget and self._get_parent_of_type(event_widget, LayerControls) is None: - self._seek(event) + self._seek(controller, x, y, mini) # Allowing group clips selection by shift+clicking anywhere on the timeline. if self.get_parent().shift_mask: last_clicked_layer = self.last_clicked_layer if not last_clicked_layer: - clicked_layer, click_pos = self.get_clicked_layer_and_pos(event) + clicked_layer, click_pos = self.get_clicked_layer_and_pos(controller, x, y, mini=mini) self.set_selection_meta_info(clicked_layer, click_pos, SELECT) else: last_click_pos = self.last_click_pos - cur_clicked_layer, cur_click_pos = self.get_clicked_layer_and_pos(event) + cur_clicked_layer, cur_click_pos = self.get_clicked_layer_and_pos(controller, x, y, mini=mini) clips = self.get_clips_in_between( last_clicked_layer, cur_clicked_layer, last_click_pos, cur_click_pos) self.selection.set_selection(clips, SELECT) elif not self.get_parent().control_mask: - clicked_layer, click_pos = self.get_clicked_layer_and_pos(event) + clicked_layer, click_pos = self.get_clicked_layer_and_pos(controller, x, y, mini=mini) self.set_selection_meta_info(clicked_layer, click_pos, SELECT) self.__end_snap() @@ -824,12 +957,14 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable): self.last_clicked_layer = clicked_layer self.last_click_pos = click_pos - def get_clicked_layer_and_pos(self, event): + def get_clicked_layer_and_pos(self, controller, event_x, event_y, mini=False): """Gets layer and position in the timeline where user clicked.""" - event_widget = Gtk.get_event_widget(event) - x, y = event_widget.translate_coordinates(self.layout.layers_vbox, event.x, event.y) - clicked_layer = self.get_layer_at(y)[0] - click_pos = max(0, self.pixel_to_ns(x)) + event_widget = Gtk.get_widget(controller) + layers_box = self.layout.layers_vbox if not mini else self.mini_layout.layers_vbox + x, y = event_widget.translate_coordinates(layers_box, event_x, event_y) + clicked_layer = self.get_layer_at(y, mini=mini)[0] + ratio = self.calc_best_zoom_ratio() if mini else None + click_pos = max(0, self.pixel_to_ns(x, zoomratio=ratio)) return clicked_layer, click_pos def get_clips_in_between(self, layer1, layer2, pos1, pos2): @@ -867,7 +1002,7 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable): for clip in layer.get_clips(): yield clip - def _motion_notify_event_cb(self, unused_widget, event): + def _motion_notify_event_cb(self, controller, x, y, mini=False): if self.dragging_element: if self.dragging_group is None: self.dragging_group = self.selection.group() @@ -876,38 +1011,42 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable): # Don't allow dragging a transition. return False - state = event.get_state() + state = controller.get_current_event_state() if isinstance(state, tuple): state = state[1] if not state & Gdk.ModifierType.BUTTON1_MASK: self.drag_end() return False - if self.got_dragged or self.__past_threshold(event): - event_widget = Gtk.get_event_widget(event) - x, y = event_widget.translate_coordinates(self.layout.layers_vbox, event.x, event.y) - self.__drag_update(x, y) + if self.got_dragged or self.__past_threshold(controller, x): + event_widget = Gtk.get_widget(controller) + layers_box = self.layout.layers_vbox if not mini else self.mini_layout.layers_vbox + x, y = event_widget.translate_coordinates(layers_box, x, y) + self.__drag_update(x, y, mini) self.got_dragged = True elif self.__moving_layer: - event_widget = Gtk.get_event_widget(event) - unused_x, y = event_widget.translate_coordinates(self, event.x, event.y) + event_widget = Gtk.get_widget(controller) + unused_x, y = event_widget.translate_coordinates(self, x, y) layer, unused_on_sep = self.get_layer_at( y, prefer_ges_layer=self.__moving_layer, - past_middle_when_adjacent=True) + past_middle_when_adjacent=True, mini=mini) if layer != self.__moving_layer: priority = layer.get_priority() self.move_layer(self.__moving_layer, priority) elif self.layout.marquee.is_visible(): - self.layout.marquee.move(event) + self.layout.marquee.move(controller, x, y) + elif self.mini_layout.marquee.is_visible(): + self.mini_layout.marquee.move(controller, x, y) elif self.scrubbing: - self._seek(event) + self._seek(controller, x, y, mini) elif self._scrolling: - self.__scroll(event) + self.__scroll(x, y) return False - def __past_threshold(self, event): + def __past_threshold(self, controller, x): threshold = 0 + event = controller.get_current_event() tool = event.get_device_tool() if tool: if tool.get_tool_type() in {Gdk.DeviceToolType.PEN, @@ -918,21 +1057,24 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable): # the stylus while clicking. threshold = 3 - delta_x = abs(self.__drag_start_x - event.x) + delta_x = abs(self.__drag_start_x - x) return delta_x > threshold - def _seek(self, event): - event_widget = Gtk.get_event_widget(event) - x, unused_y = event_widget.translate_coordinates(self.layout.layers_vbox, event.x, event.y) - position = max(0, self.pixel_to_ns(x)) + def _seek(self, controller, x, y, mini=False): + event_widget = Gtk.get_widget(controller) + layers_box = self.layout.layers_vbox if not mini else self.mini_layout.layers_vbox + x2, unused_y = event_widget.translate_coordinates(layers_box, x, y) + + ratio = self.calc_best_zoom_ratio() if mini else None + position = max(0, self.pixel_to_ns(x2, zoomratio=ratio)) self._project.pipeline.simple_seek(position) - def __scroll(self, event): + def __scroll(self, x, y): # determine how much to move the canvas - x_diff = self._scroll_start_x - event.x + x_diff = self._scroll_start_x - x self.hadj.set_value(self.hadj.get_value() + x_diff) - y_diff = self._scroll_start_y - event.y + y_diff = self._scroll_start_y - y self.vadj.set_value(self.vadj.get_value() + y_diff) def __hadj_value_changed_cb(self, hadj): @@ -971,12 +1113,13 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable): duration_frames = self.ges_timeline.get_frame_at(duration) return self.ges_timeline.get_frame_time(duration_frames) - def __create_clips(self, x, y): + def __create_clips(self, x, y, mini=False): """Creates the clips for an asset drag operation. Args: x (int): The x coordinate relative to the layers box. y (int): The y coordinate relative to the layers box. + mini (bool): Event from Mini Timeline or Simple Timeline """ placement = 0 self.dragging_element = None @@ -989,9 +1132,10 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable): ges_clips = [] self.app.action_log.begin("Add clips") for asset in assets: - ges_layer, unused_on_sep = self.get_layer_at(y) + ges_layer, unused_on_sep = self.get_layer_at(y, mini=mini) if not placement: - placement = max(0, self.pixel_to_ns(x)) + ratio = self.calc_best_zoom_ratio() if mini else None + placement = max(0, self.pixel_to_ns(x, zoomratio=ratio)) self.debug("Adding %s at %s on layer %s", asset.props.id, Gst.TIME_ARGS(placement), ges_layer) self.app.action_log.begin("Add one clip") @@ -1019,7 +1163,7 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable): if ges_clips: ges_clip = ges_clips[0] - self.dragging_element = ges_clip.ui + self.dragging_element = ges_clip.ui if not mini else ges_clip.mini_ui self._on_layer = ges_layer self.dropping_clips = True @@ -1027,31 +1171,44 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable): self.dragging_group = self.selection.group() - def _drag_motion_cb(self, widget, context, x, y, timestamp): - target = self.drag_dest_find_target(context, None) - if not target: - Gdk.drag_status(context, 0, timestamp) - return True - - if not self.drop_data_ready: - # We don't know yet the details of what's being dragged. - # Ask for the details. - self.drag_get_data(context, target, timestamp) - elif target.name() == URI_TARGET_ENTRY.target: - x, y = widget.translate_coordinates(self.layout.layers_vbox, x, y) + def _accept_cb(self, drop_target, drop): + return True + + def _notify_cb(self, drop_target, pspec): + data = drop_target.props.value + if not self.drop_data_ready and data is not None: + self.__last_clips_on_leave = None + if isinstance(data, str): + # Dragging an effect from the Effect Library. + factory_name = str(data) + self.drop_data = factory_name + self.drop_data_ready = True + elif isinstance(data, Gdk.FileList): + self.drop_data = [file.get_uri() for file in data.get_files()] + self.drop_data_ready = True + + def _motion_cb(self, drop_target, x, y, widget, mini=False): + data = drop_target.get_value() + if self.drop_data_ready and isinstance(data, Gdk.FileList): + layers_box = self.layout.layers_vbox if not mini else self.mini_layout.layers_vbox + x, y = widget.translate_coordinates(layers_box, x, y) + # print(temp) + # x = 10 + # y = 10 if not self.dropping_clips: # The preview clips have not been created yet. - self.__create_clips(x, y) - self.__drag_update(x, y) - Gdk.drag_status(context, Gdk.DragAction.COPY, timestamp) - return True + self.__create_clips(x, y, mini) + self.__drag_update(x, y, mini) + + # # drop.status(drop.get_actions(), Gdk.DragAction.COPY) + return drop_target.get_actions() - def _drag_leave_cb(self, unused_widget, context, unused_timestamp): + def _leave_cb(self, drop_target): # De-highlight the separators. We still need to remember them. # See how __on_separators is used in __dragDropCb for details self._set_separators_prelight(False) - target = self.drag_dest_find_target(context, None) + data = drop_target.get_value() if self.dragging_element: self.__last_clips_on_leave = [(clip.get_layer(), clip) for clip in self.dragging_group.get_children(False)] @@ -1068,7 +1225,7 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable): self.dropping_clips = False self.dragging_group.ungroup(recursive=False) self.dragging_group = None - elif target == URI_TARGET_ENTRY.target: + elif isinstance(data, Gdk.FileList): self.clean_drop_data() def clean_drop_data(self): @@ -1076,15 +1233,14 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable): self.drop_data = None self.dropping_clips = False - def _drag_drop_cb(self, unused_widget, context, x, y, timestamp): + def _drop_cb(self, drop_target, value, x, y): # Same as in insertEnd: this value changes during insertion, snapshot # it zoom_was_fitted = self.zoomed_fitted - target = self.drag_dest_find_target(context, None).name() success = True self.clean_drop_data() - if target == URI_TARGET_ENTRY.target: + if isinstance(value, Gdk.FileList): if self.__last_clips_on_leave: pipeline = self._project.pipeline with self.app.action_log.started("add clip", @@ -1108,27 +1264,14 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable): else: success = False - Gtk.drag_finish(context, success, False, timestamp) + drop_target.get_drop().finish(drop_target.get_actions()) return success - def _drag_data_received_cb(self, unused_widget, unused_context, unused_x, - unused_y, selection_data, unused_info, timestamp): - data_type = selection_data.get_data_type().name() - if not self.drop_data_ready: - self.__last_clips_on_leave = None - if data_type == URI_TARGET_ENTRY.target: - self.drop_data = selection_data.get_uris() - self.drop_data_ready = True - elif data_type == EFFECT_TARGET_ENTRY.target: - # Dragging an effect from the Effect Library. - factory_name = str(selection_data.get_data(), "UTF-8") - self.drop_data = factory_name - self.drop_data_ready = True - # Handle layers def _layer_added_cb(self, unused_ges_timeline, ges_layer): self._add_layer(ges_layer) self.__update_layers() + self.__update_mini_timeline_height() def move_layer(self, ges_layer, index): self.debug("Moving layer %s to %s", ges_layer.props.priority, index) @@ -1141,23 +1284,31 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable): def _add_layer(self, ges_layer): """Adds widgets for controlling and showing the specified layer.""" - layer = Layer(ges_layer, self) + layer = FullLayer(ges_layer, self) ges_layer.ui = layer + mini_layer = MiniLayer(ges_layer, self) + ges_layer.mini_ui = mini_layer + if not self._separators: # Make sure the first layer has separators above it. self.__add_separators() control = LayerControls(ges_layer, self.app) - control.show_all() - self._layers_controls_vbox.pack_start(control, False, False, 0) + self._layers_controls_vbox.prepend(control) + self.layers_size_group.add_widget(control) ges_layer.control_ui = control # Check the media types so the controls are set up properly. layer.check_media_types() + mini_layer.check_media_types() - self.layout.layers_vbox.pack_start(layer, False, False, 0) + self.layout.layers_vbox.prepend(layer) + self.layers_size_group.add_widget(layer) layer.show() + self.mini_layout.layers_vbox.prepend(mini_layer) + mini_layer.show() + self.__add_separators() ges_layer.connect("notify::priority", self.__layer_priority_changed_cb) @@ -1166,13 +1317,17 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable): """Adds separators to separate layers.""" controls_separator = SpacedSeparator() controls_separator.show() - self._layers_controls_vbox.pack_start(controls_separator, False, False, 0) + self._layers_controls_vbox.prepend(controls_separator) separator = SpacedSeparator() separator.show() - self.layout.layers_vbox.pack_start(separator, False, False, 0) + self.layout.layers_vbox.prepend(separator) + + mini_separator = SpacedSeparator() + mini_separator.show() + self.mini_layout.layers_vbox.prepend(mini_separator) - self._separators.append((controls_separator, separator)) + self._separators.append((controls_separator, separator, mini_separator)) def __layer_priority_changed_cb(self, unused_ges_layer, unused_pspec): """Handles the changing of a layer's priority.""" @@ -1190,19 +1345,21 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable): self.debug("Layers still being shuffled, not updating widgets: %s", priorities) return self.debug("Updating layers widgets positions") - self.__update_separator(0) - for ges_layer in ges_layers: - self.__update_layer(ges_layer) - self.__update_separator(ges_layer.props.priority + 1) + # self.__update_separator(0) + # for ges_layer in ges_layers: + # self.__update_layer(ges_layer) + # self.__update_separator(ges_layer.props.priority + 1) def __update_separator(self, priority): """Sets the position of the separators in their parent.""" position = priority * 2 - controls_separator, layers_separator = self._separators[priority] + controls_separator, layers_separator, mini_layers_separator = self._separators[priority] vbox = self._layers_controls_vbox vbox.child_set_property(controls_separator, "position", position) vbox = self.layout.layers_vbox vbox.child_set_property(layers_separator, "position", position) + vbox = self.mini_layout.layers_vbox + vbox.child_set_property(mini_layers_separator, "position", position) def __update_layer(self, ges_layer): """Sets the position of the layer and its controls in their parent.""" @@ -1211,25 +1368,38 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable): vbox.child_set_property(ges_layer.control_ui, "position", position) vbox = self.layout.layers_vbox vbox.child_set_property(ges_layer.ui, "position", position) + vbox = self.mini_layout.layers_vbox + vbox.child_set_property(ges_layer.mini_ui, "position", position) + + def __update_mini_timeline_height(self): + ges_layers = self.ges_timeline.get_layers() + # extra space to allow drag over separators of last layer. + height = (len(ges_layers) * MINI_LAYER_HEIGHT) + (2 * MINI_LAYER_HEIGHT) + self.mini_layout_container.props.height_request = height def _remove_layer(self, ges_layer): self.info("Removing layer: %s", ges_layer.props.priority) self.layout.layers_vbox.remove(ges_layer.ui) + self.mini_layout.layers_vbox.remove(ges_layer.mini_ui) self._layers_controls_vbox.remove(ges_layer.control_ui) ges_layer.disconnect_by_func(self.__layer_priority_changed_cb) # Remove extra separators. - controls_separator, separator = self._separators.pop() + controls_separator, separator, mini_layers_separator = self._separators.pop() self.layout.layers_vbox.remove(separator) + self.mini_layout.layers_vbox.remove(mini_layers_separator) self._layers_controls_vbox.remove(controls_separator) ges_layer.ui.release() ges_layer.ui = None + ges_layer.mini_ui.release() + ges_layer.mini_ui = None ges_layer.control_ui = None def _layer_removed_cb(self, unused_ges_timeline, ges_layer): self._remove_layer(ges_layer) self.__update_layers() + self.__update_mini_timeline_height() def separator_priority(self, separator): position = self.layout.layers_vbox.child_get_property(separator, "position") @@ -1248,11 +1418,12 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable): self.update_position() self.editor_state.set_value("zoom-level", Zoomable.get_current_zoom_level()) - def set_best_zoom_ratio(self, allow_zoom_in=False): - """Sets the zoom level so that the entire timeline is in view.""" + def calc_best_zoom_ratio(self, mini=True): + """Returns the zoom ratio so that the entire timeline is in (mini)view.""" duration = 0 if not self.ges_timeline else self.ges_timeline.get_duration() - if not duration: - return + if not duration or (mini and not self.mini_layout_container.get_visible()): + # Maximum available width, the parent is TimelineContainer + return self.get_parent().get_allocated_width() # Add Gst.SECOND - 1 to the timeline duration to make sure the # last second of the timeline will be in view. @@ -1261,7 +1432,17 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable): self.debug("Adjusting zoom for a timeline duration of %s secs", timeline_duration_s) - zoom_ratio = self.layout.get_allocation().width / timeline_duration_s + layout = self.mini_layout if mini else self.layout + zoom_ratio = layout.get_allocation().width / timeline_duration_s + return zoom_ratio + + def set_best_zoom_ratio(self, allow_zoom_in=False): + """Sets the zoom level so that the entire timeline is in view.""" + duration = 0 if not self.ges_timeline else self.ges_timeline.get_duration() + if not duration: + return + + zoom_ratio = self.calc_best_zoom_ratio(mini=False) nearest_zoom_level = Zoomable.compute_zoom_level(zoom_ratio) if nearest_zoom_level >= Zoomable.get_current_zoom_level() and not allow_zoom_in: # This means if we continue we'll zoom in. @@ -1296,7 +1477,7 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable): return GES.EditMode.EDIT_TRIM return GES.EditMode.EDIT_NORMAL - def get_layer_at(self, y, prefer_ges_layer=None, past_middle_when_adjacent=False): + def get_layer_at(self, y, prefer_ges_layer=None, past_middle_when_adjacent=False, mini=False): ges_layers = self.ges_timeline.get_layers() if y < SEPARATOR_HEIGHT: # The cursor is at the top, above the first layer. @@ -1313,10 +1494,12 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable): if past_middle_when_adjacent: index_preferred = prefer_ges_layer.get_priority() - height_preferred = prefer_ges_layer.ui.get_allocation().height + height_preferred = prefer_ges_layer.ui.get_allocation().height if not mini \ + else prefer_ges_layer.mini_ui.get_allocation().height for i, ges_layer in enumerate(ges_layers): - layer_rect = ges_layer.ui.get_allocation() + layer_rect = ges_layer.ui.get_allocation() if not mini \ + else ges_layer.mini_ui.get_allocation() layer_y = layer_rect.y layer_height = layer_rect.height if layer_y <= y < layer_y + layer_height: @@ -1343,7 +1526,10 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable): # Choose a layer as close to prefer_ges_layer as possible. prefer_after = False - if layer_y + layer_height <= y < next_ges_layer.ui.get_allocation().y: + next_layer = next_ges_layer.ui if not mini \ + else next_ges_layer.mini_ui + + if layer_y + layer_height <= y < next_layer.get_allocation().y: # The cursor is between this layer and the one below. if prefer_after: ges_layer = next_ges_layer @@ -1357,12 +1543,13 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable): for sep in self.__on_separators: set_state_flags_recurse(sep, Gtk.StateFlags.PRELIGHT, are_set=prelight) - def __drag_update(self, x, y): + def __drag_update(self, x, y, mini=False): """Updates a clip or asset drag operation. Args: x (int): The x coordinate relative to the layers box. y (int): The y coordinate relative to the layers box. + mini (bool): Event from Mini Timeline or Simple Timeline """ if not self.dragging_element: return @@ -1386,13 +1573,18 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable): mode: GES.EditMode = self.__get_editing_mode() self.editing_context.set_mode(mode) + ratio = self.calc_best_zoom_ratio() if mini else None + x = x - int(self.__drag_start_x) + if self.editing_context.edge is GES.Edge.EDGE_END: - position = self.pixel_to_ns(x - int(self.__drag_start_x) + self.__clicked_handle.get_allocated_width()) + x = self.pixel_to_ns(x, zoomratio=ratio) + position = x + self.__clicked_handle.get_allocated_width() else: - position = self.pixel_to_ns(x - int(self.__drag_start_x)) + x = self.pixel_to_ns(x, zoomratio=ratio) + position = x self._set_separators_prelight(False) - res = self.get_layer_at(y, prefer_ges_layer=self._on_layer) + res = self.get_layer_at(y, prefer_ges_layer=self._on_layer, mini=mini) self._on_layer, self.__on_separators = res if (mode != GES.EditMode.EDIT_NORMAL or self.dragging_group.props.height > 1): @@ -1454,6 +1646,7 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable): for ges_layer in self.ges_timeline.get_layers(): ges_layer.ui.check_media_types() + ges_layer.mini_ui.check_media_types() self._set_separators_prelight(False) self.__on_separators = [] @@ -1485,7 +1678,7 @@ class TimelineContainer(Gtk.Grid, Zoomable, Loggable): self._create_ui() self._create_actions() - self.timeline.connect("size-allocate", self.__timeline_size_allocate_cb) + # self.timeline.connect("size-allocate", self.__timeline_size_allocate_cb) # Public API @@ -1684,20 +1877,19 @@ class TimelineContainer(Gtk.Grid, Zoomable, Loggable): # Vertical Scrollbar. It will be displayed only when needed. self.vscrollbar = Gtk.Scrollbar(orientation=Gtk.Orientation.VERTICAL, adjustment=self.timeline.vadj) - self.vscrollbar.get_style_context().add_class("background") + self.vscrollbar.add_css_class("background") hscrollbar = Gtk.Scrollbar(orientation=Gtk.Orientation.HORIZONTAL, adjustment=self.timeline.hadj) - hscrollbar.get_style_context().add_class("background") + hscrollbar.add_css_class("background") self.ruler = TimelineScaleRuler(self) self.ruler.props.hexpand = True builder = Gtk.Builder() builder.add_from_file(os.path.join(get_ui_dir(), "timelinetoolbar.ui")) - self.toolbar = builder.get_object("timeline_toolbar") - self.toolbar.get_style_context().add_class(Gtk.STYLE_CLASS_INLINE_TOOLBAR) - self.toolbar.get_accessible().set_name("timeline toolbar") + self.toolbar = builder.get_object("timeline_box") + self.toolbar.set_name("timeline box") self.gapless_button = builder.get_object("gapless_button") self.gapless_button.set_active(self._settings.timelineAutoRipple) @@ -1710,11 +1902,10 @@ class TimelineContainer(Gtk.Grid, Zoomable, Loggable): self.attach(self.timeline, 0, 2, 2, 1) self.attach(self.vscrollbar, 2, 2, 1, 1) self.attach(hscrollbar, 1, 3, 1, 1) - self.attach(self.toolbar, 3, 2, 1, 1) + self.attach(self.toolbar, 1, 4, 1, 1) self.set_margin_top(SPACING) - self.show_all() if not in_devel(): self.gapless_button.hide() @@ -1755,6 +1946,20 @@ class TimelineContainer(Gtk.Grid, Zoomable, Loggable): self.app.shortcuts.add("timeline.action-search", ["slash"], self.action_search, _("Action Search")) + # Cut Mode. + self.action_cut_mode = Gio.SimpleAction.new("action-cut-mode", None) + self.action_cut_mode.connect("activate", self.__action_cut_mode_cb) + group.add_action(self.action_cut_mode) + self.app.shortcuts.add("timeline.action-cut-mode", ["c"], + self.action_cut_mode, _("Action Cut Mode")) + + # Locked playhead mode, playhead is centered automatically. + self.action_locked_playhead_mode = Gio.SimpleAction.new("action-playhead-locked-mode", None) + self.action_locked_playhead_mode.connect("activate", self.__action_locked_playhead_mode_cb) + group.add_action(self.action_locked_playhead_mode) + self.app.shortcuts.add("timeline.action-playhead-locked-mode", ["p"], + self.action_locked_playhead_mode, _("Action Locked Playhead Mode")) + # Clips actions. self.delete_action = Gio.SimpleAction.new("delete-selected-clips", None) self.delete_action.connect("activate", self._delete_selected_cb) @@ -2181,7 +2386,8 @@ class TimelineContainer(Gtk.Grid, Zoomable, Loggable): def __add_effect_cb(self, unused_action, unused_parameter): clip = self.timeline.selection.get_single_clip() if clip: - self.effects_popover.set_relative_to(clip.ui) + # self.effects_popover.set_relative_to(clip.ui)\ + clip.ui.set_child(self.effects_popover) self.effects_popover.popup() def _align_selected_cb(self, unused_action, unused_parameter): @@ -2258,18 +2464,18 @@ class TimelineContainer(Gtk.Grid, Zoomable, Loggable): # Gtk widget virtual methods - def do_key_press_event(self, event): + def do_key_press_event(self, controller, keyval, keycode, state): # This is used both for changing the selection modes and for affecting # the seek keyboard shortcuts further below - if event.keyval == Gdk.KEY_Shift_L: + if keyval == Gdk.KEY_Shift_L: self.shift_mask = True - elif event.keyval == Gdk.KEY_Control_L: + elif keyval == Gdk.KEY_Control_L: self.control_mask = True - def do_key_release_event(self, event): - if event.keyval == Gdk.KEY_Shift_L: + def do_key_release_event(self, controller, keyval, keycode, state): + if keyval == Gdk.KEY_Shift_L: self.shift_mask = False - elif event.keyval == Gdk.KEY_Control_L: + elif keyval == Gdk.KEY_Control_L: self.control_mask = False def _seek_backward_one_second_cb(self, unused_action, unused_parameter): @@ -2341,17 +2547,17 @@ class TimelineContainer(Gtk.Grid, Zoomable, Loggable): if position is not None: clip.set_start(position) - def do_focus_in_event(self, unused_event): + def do_focus_in_event(self, unused_controller): self.log("Timeline has grabbed focus") self.update_actions() - def do_focus_out_event(self, unused_event): + def do_focus_out_event(self, unused_controller): self.log("Timeline has lost focus") self.update_actions() - def __timeline_size_allocate_cb(self, unused_widget, allocation): - fits = self.timeline.layout.props.height <= allocation.height - self.vscrollbar.set_opacity(0 if fits else 1) + # def __timeline_size_allocate_cb(self, unused_widget, allocation): + # fits = self.timeline.layout.props.height <= allocation.height + # self.vscrollbar.set_opacity(0 if fits else 1) def _zoom_in_cb(self, unused_action, unused_parameter): Zoomable.zoom_in() @@ -2378,8 +2584,6 @@ class TimelineContainer(Gtk.Grid, Zoomable, Loggable): win = ActionSearchBar(self.app) win.set_transient_for(self.app.gui) - win.show_all() - def __get_current_marker_boxes(self): # Return a list of the selected elements' marker boxes sources = self.timeline.selection.get_selected_track_elements() @@ -2448,3 +2652,16 @@ class TimelineContainer(Gtk.Grid, Zoomable, Loggable): self.app.project_manager.current_project.pipeline.simple_seek(position) self.app.gui.editor.timeline_ui.timeline.scroll_to_playhead(align=Gtk.Align.CENTER, when_not_in_view=True) + + def __action_cut_mode_cb(self, unused_action, unused_parameter): + if self.timeline.mini_layout_container.is_visible(): + self.timeline.mini_layout_container.hide() + else: + self.timeline.mini_layout_container.show() + + def __action_locked_playhead_mode_cb(self, unused_action, unused_parameter): + if self.timeline.playhead_locked: + self.timeline.playhead_locked = False + else: + self.timeline.playhead_locked = True + self.timeline.scroll_to_playhead() diff --git a/pitivi/trackerperspective.py b/pitivi/trackerperspective.py index 7f44abedc2ea2c5468918e3f188ce07c41cc0b82..ebd9d20163f1bb25291081b5a3aae1e8832315ac 100644 --- a/pitivi/trackerperspective.py +++ b/pitivi/trackerperspective.py @@ -27,7 +27,6 @@ from typing import Tuple import cairo import numpy -from gi.repository import Gdk from gi.repository import GES from gi.repository import Gio from gi.repository import GObject @@ -41,7 +40,6 @@ from pitivi.perspective import Perspective from pitivi.utils.loggable import Loggable from pitivi.utils.pipeline import AssetPipeline from pitivi.utils.pipeline import SimplePipeline -from pitivi.utils.ui import fix_infobar from pitivi.utils.ui import NORMAL_CURSOR from pitivi.utils.ui import PADDING from pitivi.utils.ui import SPACING @@ -184,13 +182,14 @@ class TrackedObjectRow(Gtk.ListBoxRow): self.object_id: str = object_id self.name: str = name - label = Gtk.Label(name) - label.props.margin = SPACING + label = Gtk.Label.new(name) + label.props.margin_top = SPACING + label.props.margin_bottom = SPACING label.props.margin_end = PADDING label.props.margin_start = PADDING label.props.halign = Gtk.Align.START label.show() - self.add(label) + self.set_child(label) @Gtk.Template(filename=os.path.join(get_ui_dir(), "trackerperspective.ui")) @@ -207,8 +206,6 @@ class ToplevelWidget(Gtk.Box, Loggable): next_frame_button = Gtk.Template.Child() object_listbox = Gtk.Template.Child() object_manager_box = Gtk.Template.Child() - pause_icon = Gtk.Template.Child() - play_icon = Gtk.Template.Child() play_pause_button = Gtk.Template.Child() pos_adj = Gtk.Template.Child() prev_frame_button = Gtk.Template.Child() @@ -268,15 +265,12 @@ class ToplevelWidget(Gtk.Box, Loggable): self.tracked_objects_store.append(TrackedObjectItem(object_id, index, name)) self.object_listbox.bind_model(self.tracked_objects_store, self.create_tracked_object_row_func) - fix_infobar(self.howto_add_infobar) - # Setup Viewer _, self.sink_widget = self.pipeline.create_sink() self.aspect_frame.set( xalign=0.5, yalign=0.5, ratio=self.source_width / self.source_height, obey_child=False) self.viewer_overlay.add(self.sink_widget) self.viewer_overlay.add_overlay(self.drawing_area) - self.viewer_overlay.show_all() # Setup Seeker self.seeker.props.adjustment.set_upper(self.asset.props.duration) @@ -285,7 +279,7 @@ class ToplevelWidget(Gtk.Box, Loggable): # Setup algorithm ComboBox cell = Gtk.CellRendererText() self.algorithm_combo_box.set_model(self.__get_tracking_algorithms()) - self.algorithm_combo_box.pack_start(cell, False) + self.algorithm_combo_box.prepend(cell) self.algorithm_combo_box.add_attribute(cell, "text", 0) self._setup(None) @@ -317,11 +311,11 @@ class ToplevelWidget(Gtk.Box, Loggable): def _pipeline_state_change_cb(self, pipeline, state, prev_state): self.log("Pipeline state %s -> %s", prev_state, state) if pipeline.playing(): - icon = self.pause_icon + icon = "media-playback-pause-symbolic" else: - icon = self.play_icon + icon = "media-playback-start-symbolic" - self.play_pause_button.set_image(icon) + self.play_pause_button.set_icon_name(icon) self.track_button.props.sensitive = False @Gtk.Template.Callback() @@ -360,41 +354,41 @@ class ToplevelWidget(Gtk.Box, Loggable): # Bounding box callbacks @Gtk.Template.Callback() - def _drawing_area_button_event_cb(self, widget, event): - res, button = event.get_button() - if not res or button != 1: + def _drawing_area_button_pressed_cb(self, controller, n_pressed, x, y): + if self.pipeline.get_simple_state() != Gst.State.PAUSED: return + self.x1 = x + self.y1 = y + self.drawing_area_width = self.drawing_area.get_allocated_width + self.drawing_area_height = self.drawing_area.get_allocated_height + + @Gtk.Template.Callback() + def _drawing_area_button_released_cb(self, controller, n_pressed, x, y): if self.pipeline.get_simple_state() != Gst.State.PAUSED: return - if event.get_event_type() == Gdk.EventType.BUTTON_PRESS: - self.x1 = event.x - self.y1 = event.y - self.drawing_area_width = self.drawing_area.get_allocated_width - self.drawing_area_height = self.drawing_area.get_allocated_height - elif event.get_event_type() == Gdk.EventType.BUTTON_RELEASE: - self.x2 = event.x - self.y2 = event.y - - # Convert the viewer coordinates to video coordinates. - factor: float = 1 / self.video_to_viewer_factor() - x = min(self.x1, self.x2) * factor - y = min(self.y1, self.y2) * factor - w = abs(self.x2 - self.x1) * factor - h = abs(self.y2 - self.y1) * factor - if w and h: - # Apply the selected area to the object. - if not self.current_object: - self._create_empty_object() - position = self.pipeline.get_position() - self.object_manager.update_object_position( - self.current_object, position, (x, y, w, h)) - - # Allow tracking. - self.track_button.props.sensitive = True - - self.__reset_selected_area() + self.x2 = x + self.y2 = y + + # Convert the viewer coordinates to video coordinates. + factor: float = 1 / self.video_to_viewer_factor() + x = min(self.x1, self.x2) * factor + y = min(self.y1, self.y2) * factor + w = abs(self.x2 - self.x1) * factor + h = abs(self.y2 - self.y1) * factor + if w and h: + # Apply the selected area to the object. + if not self.current_object: + self._create_empty_object() + position = self.pipeline.get_position() + self.object_manager.update_object_position( + self.current_object, position, (x, y, w, h)) + + # Allow tracking. + self.track_button.props.sensitive = True + + self.__reset_selected_area() def _create_empty_object(self): """Generates a new object and adds it to the object_manager.""" @@ -416,18 +410,18 @@ class ToplevelWidget(Gtk.Box, Loggable): self.drawing_area_width, self.drawing_area_height = None, None @Gtk.Template.Callback() - def _drawing_area_enter_notify_event_cb(self, widget, event): - self.app.gui.get_window().set_cursor(Gdk.Cursor(Gdk.CursorType.CROSSHAIR)) + def _drawing_area_enter_notify_event_cb(self, controller, x, y): + self.app.gui.set_cursor("crosshair") @Gtk.Template.Callback() - def _drawing_area_leave_notify_event_cb(self, widget, event): - self.app.gui.get_window().set_cursor(NORMAL_CURSOR) + def _drawing_area_leave_notify_event_cb(self, controller): + self.app.gui.set_cursor(NORMAL_CURSOR) @Gtk.Template.Callback() - def _drawing_area_motion_notify_event_cb(self, widget, event): + def _drawing_area_motion_notify_event_cb(self, controller, x, y): if self.x1 is not None: - self.x2 = event.x - self.y2 = event.y + self.x2 = x + self.y2 = y self.drawing_area.queue_draw() @Gtk.Template.Callback() @@ -543,7 +537,7 @@ class ToplevelWidget(Gtk.Box, Loggable): "uridecodebin uri={} ! videoconvert ! \ cvtracker object-initial-x={} object-initial-y={} object-initial-width={} \ object-initial-height={} algorithm={} draw-rect=true ! tee name=t ! \ - queue ! videoconvert ! gtksink name=gtksink t. \ + queue ! videoconvert ! gtk4paintablesink name=gtk4paintablesink t. \ ! fakesink name=sink signal-handoffs=TRUE" .format(self.asset.props.id, int(x), int(y), int(w), int(h), algorithm)) @@ -554,7 +548,7 @@ class ToplevelWidget(Gtk.Box, Loggable): fakesink.connect("handoff", self.__tracker_handoff_cb, self.roi_data) # Set up a widget to show the video as the object is being tracked. - video_sink = _pipeline.get_by_name("gtksink") + video_sink = _pipeline.get_by_name("gtk4paintablesink") self.tracker_sink_widget = video_sink.props.widget self.viewer_overlay.add_overlay(self.tracker_sink_widget) @@ -657,17 +651,16 @@ class TrackerPerspective(Perspective): def __create_headerbar(self): headerbar = Gtk.HeaderBar() - headerbar.set_show_close_button(False) + headerbar.set_show_title_buttons(False) - back_button = Gtk.Button.new_from_icon_name( - "go-previous-symbolic", Gtk.IconSize.SMALL_TOOLBAR) - back_button.set_always_show_image(True) + back_button = Gtk.Button.new_from_icon_name("go-previous-symbolic") back_button.set_tooltip_text(_("Go back")) back_button.connect("clicked", self.__back_button_clicked_cb) back_button.set_margin_end(4 * PADDING) headerbar.pack_start(back_button) - headerbar.props.title = os.path.basename(self.asset.props.id) - headerbar.show_all() + asset_name = os.path.basename(self.asset.props.id) + headerbar.set_title_widget(Gtk.Label.new(asset_name)) + headerbar.add_css_class("title") return headerbar @@ -704,22 +697,29 @@ class CoverObjectPopover(Gtk.Popover, Loggable): self.listbox.connect("row-activated", self.__row_activated_cb) self.scroll_window = Gtk.ScrolledWindow() - self.scroll_window.add(self.listbox) + self.scroll_window.set_child(self.listbox) self.scroll_window.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) self.scroll_window.props.max_content_height = 350 self.scroll_window.props.propagate_natural_height = True + self.scroll_window.set_hexpand(True) + self.scroll_window.set_halign(Gtk.Align.FILL) - vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, margin=PADDING) - vbox.pack_start(self.scroll_window, True, True, 0) - vbox.show_all() + vbox = Gtk.Box( + orientation=Gtk.Orientation.VERTICAL, + margin_top=PADDING, + margin_bottom=PADDING, + margin_start=PADDING, + margin_end=PADDING + ) + vbox.prepend(self.scroll_window) - self.add(vbox) + self.set_child(vbox) def update_object_list(self): """Updates the list of not yet covered objects.""" self.object_manager = ObjectManager(self.clip.asset) - for row in self.listbox.get_children(): + while row := self.listbox.get_row_at_index(0): self.listbox.remove(row) # Check which tracked objects have already been covered. @@ -732,16 +732,14 @@ class CoverObjectPopover(Gtk.Popover, Loggable): # Allow selecting the not-yet-covered objects. for _index, object_id, name in self.object_manager.objects: if object_id not in covered_objects: - self.listbox.add(TrackedObjectRow(object_id, name)) + self.listbox.insert(TrackedObjectRow(object_id, name), -1) # Allow tracking new objects. button_row = Gtk.ListBoxRow(selectable=False) track_objects_button = Gtk.Button(_("Track objects")) track_objects_button.connect("clicked", self.__track_objects_button_clicked_cb) - button_row.add(track_objects_button) - self.listbox.add(button_row) - - self.listbox.show_all() + button_row.set_child(track_objects_button) + self.listbox.insert(button_row, -1) def __row_activated_cb(self, listbox: Gtk.ListBox, row: TrackedObjectRow): self._create_effect(row.object_id, row.name) diff --git a/pitivi/transitions.py b/pitivi/transitions.py index a7510fdffce590af9dc20ee887d59da0ef714832..3a4a1e428f78be63f43f323d95cd8dde43c487fb 100644 --- a/pitivi/transitions.py +++ b/pitivi/transitions.py @@ -26,7 +26,6 @@ from gi.repository import Gtk from pitivi.configure import get_pixmap_dir from pitivi.utils.loggable import Loggable from pitivi.utils.misc import disconnect_all_by_func -from pitivi.utils.ui import fix_infobar from pitivi.utils.ui import PADDING from pitivi.utils.ui import SPACING @@ -62,28 +61,35 @@ class TransitionsListWidget(Gtk.Box, Loggable): self.searchbar = Gtk.Box() self.searchbar.set_orientation(Gtk.Orientation.HORIZONTAL) # Prevents being flush against the notebook - self.searchbar.set_border_width(3) + self.searchbar.set_margin_top(3) + self.searchbar.set_margin_bottom(3) + self.searchbar.set_margin_start(3) + self.searchbar.set_margin_end(3) self.search_entry = Gtk.Entry() self.search_entry.set_icon_from_icon_name( Gtk.EntryIconPosition.SECONDARY, "edit-clear-symbolic") self.search_entry.set_placeholder_text(_("Search...")) - self.searchbar.pack_end(self.search_entry, True, True, 0) + self.search_entry.set_hexpand(True) + self.search_entry.set_halign(Gtk.Align.FILL) + self.searchbar.append(self.search_entry) self.props_widgets = Gtk.Grid() - self.props_widgets.props.margin = PADDING + self.props_widgets.props.margin_top = PADDING + self.props_widgets.props.margin_bottom = PADDING + self.props_widgets.props.margin_start = PADDING + self.props_widgets.props.margin_end = PADDING self.props_widgets.props.column_spacing = SPACING - self.border_mode_normal = Gtk.RadioButton( + self.border_mode_normal = Gtk.CheckButton( group=None, label=_("Normal")) self.border_mode_normal.set_active(True) self.props_widgets.attach(self.border_mode_normal, 0, 0, 1, 1) - self.border_mode_loop = Gtk.RadioButton( + self.border_mode_loop = Gtk.CheckButton( group=self.border_mode_normal, label=_("Loop")) self.props_widgets.attach(self.border_mode_loop, 0, 1, 1, 1) self.border_scale = Gtk.Scale.new(Gtk.Orientation.HORIZONTAL, None) - self.border_scale.set_draw_value(False) self.props_widgets.attach(self.border_scale, 1, 0, 1, 2) self.invert_checkbox = Gtk.CheckButton(label=_("Reverse direction")) @@ -94,33 +100,34 @@ class TransitionsListWidget(Gtk.Box, Loggable): self.__update_border_scale() self.infobar = Gtk.InfoBar() - fix_infobar(self.infobar) self.infobar.props.message_type = Gtk.MessageType.OTHER txtlabel = Gtk.Label() - txtlabel.set_line_wrap(True) + txtlabel.set_wrap(True) txtlabel.set_text( _("Create a transition by overlapping two adjacent clips on the " "same layer. Click the transition on the timeline to change " "the transition type.")) - self.infobar.get_content_area().add(txtlabel) + self.infobar.add_child(txtlabel) self.storemodel = Gtk.ListStore(GES.Asset, str, str, str) # Create the filterModel for searching self.model_filter = self.storemodel.filter_new() self.iconview_scrollwin = Gtk.ScrolledWindow() - self.iconview_scrollwin.set_shadow_type(Gtk.ShadowType.ETCHED_IN) + self.iconview_scrollwin.set_has_frame(True) + self.iconview_scrollwin.set_hexpand(True) + self.iconview_scrollwin.set_halign(Gtk.Align.FILL) self.iconview = Gtk.IconView(model=self.model_filter) cell_renderer = Gtk.CellRendererPixbuf() - cell_renderer.props.stock_size = Gtk.IconSize.DIALOG - self.iconview.pack_start(cell_renderer, expand=False) + cell_renderer.props.icon_size = Gtk.IconSize.LARGE + self.iconview.pack_end(cell_renderer, False) self.iconview.add_attribute(cell_renderer, "icon-name", COL_ICON_NAME) self.iconview.set_item_width(48 + SPACING) self.iconview.set_property("has_tooltip", True) - self.iconview_scrollwin.add(self.iconview) + self.iconview_scrollwin.set_child(self.iconview) self.search_entry.connect("changed", self._search_entry_changed_cb) self.search_entry.connect("icon-press", self._search_entry_icon_press_cb) @@ -129,13 +136,12 @@ class TransitionsListWidget(Gtk.Box, Loggable): # Speed-up startup by only checking available transitions on idle GLib.idle_add(self._load_available_transitions_cb) - self.pack_start(self.infobar, False, False, 0) - self.pack_start(self.searchbar, False, False, 0) - self.pack_start(self.iconview_scrollwin, True, True, 0) - self.pack_start(self.props_widgets, False, False, 0) + self.prepend(self.infobar) + self.append(self.searchbar) + self.append(self.iconview_scrollwin) + self.append(self.props_widgets) - self.infobar.show_all() - self.iconview_scrollwin.show_all() + self.infobar.set_revealed(True) self.iconview.hide() self.props_widgets.set_sensitive(False) self.props_widgets.hide() @@ -161,8 +167,8 @@ class TransitionsListWidget(Gtk.Box, Loggable): self.iconview.connect("selection-changed", self._transition_selected_cb) self.border_scale.connect("value-changed", self._border_scale_cb) self.invert_checkbox.connect("toggled", self._invert_checkbox_cb) - self.border_mode_normal.connect("released", self._border_type_changed_cb) - self.border_mode_loop.connect("released", self._border_type_changed_cb) + self.border_mode_normal.connect("toggled", self._border_type_changed_cb) + self.border_mode_loop.connect("toggled", self._border_type_changed_cb) self.element.connect("notify::border", self.__updated_cb) self.element.connect("notify::invert", self.__updated_cb) self.element.connect("notify::transition-type", self.__updated_cb) @@ -261,14 +267,14 @@ class TransitionsListWidget(Gtk.Box, Loggable): return self.element = element self._update_ui() - self.iconview.show_all() - self.props_widgets.show_all() - self.searchbar.show_all() + self.iconview.show() + self.props_widgets.show() + self.searchbar.show() self.__connect_ui() # We REALLY want the infobar to be hidden as space is really constrained # and yet GTK 3.10 seems to be racy in showing/hiding infobars, so # this must happen *after* the tab has been made visible/switched to: - self.infobar.hide() + self.infobar.set_revealed(False) def _update_ui(self): transition_type = self.element.get_transition_type() @@ -302,7 +308,7 @@ class TransitionsListWidget(Gtk.Box, Loggable): self.iconview.hide() self.props_widgets.hide() self.searchbar.hide() - self.infobar.show() + self.infobar.set_revealed(False) def _get_icon(self, transition_nick: str) -> Optional[GdkPixbuf.Pixbuf]: """Gets an icon pixbuf for the specified transition nickname.""" @@ -313,7 +319,7 @@ class TransitionsListWidget(Gtk.Box, Loggable): return None def _iconview_query_tooltip_cb(self, view, x, y, keyboard_mode, tooltip): - is_row, x, y, model, path, iter_ = view.get_tooltip_context( + is_row, model, path, iter_ = view.get_tooltip_context( x, y, keyboard_mode) if not is_row: return False @@ -321,7 +327,7 @@ class TransitionsListWidget(Gtk.Box, Loggable): view.set_tooltip_item(tooltip, path) icon_name = model.get_value(iter_, COL_ICON_NAME) - tooltip.set_icon_from_icon_name(icon_name, Gtk.IconSize.DIALOG) + tooltip.set_icon_from_icon_name(icon_name) longname = model.get_value(iter_, COL_NAME) description = model.get_value(iter_, COL_DESCRIPTION) diff --git a/pitivi/utils/custom_effect_widgets.py b/pitivi/utils/custom_effect_widgets.py index 5c2e27157894e3670f13a8aed987c4909d090898..a28085c41aa7327cd53bcbb47b8abdf60f401048 100644 --- a/pitivi/utils/custom_effect_widgets.py +++ b/pitivi/utils/custom_effect_widgets.py @@ -103,7 +103,7 @@ def object_cover_effect_widget(effect_prop_manager, element_setting_widget, elem set_foreground_color(argb) color_picker_button = ColorPickerButton() - base_table.add(color_picker_button) + base_table.append(color_picker_button) handler_value_changed = color_picker_button.connect("value-changed", color_picker_value_changed_cb) def color_button_color_set_cb(button: Gtk.ColorButton): @@ -150,7 +150,7 @@ def create_alpha_widget(effect_prop_manager, element_setting_widget, element): color_picker = ColorPickerButton(0, 255, 0) color_picker_frame = builder.get_object("color_picker_frame") - color_picker_frame.add(color_picker) + color_picker_frame.set_child(color_picker) # Additional Setup @@ -266,9 +266,9 @@ def create_3point_color_balance_widget(effect_prop_manager, element_setting_widg midtones_color_picker_frame = builder.get_object("midtones_color_picker_frame") highlights_color_picker_frame = builder.get_object("highlights_color_picker_frame") - shadows_color_picker_frame.add(shadows_color_picker_button) - midtones_color_picker_frame.add(midtones_color_picker_button) - highlights_color_picker_frame.add(highlights_color_picker_button) + shadows_color_picker_frame.set_child(shadows_color_picker_button) + midtones_color_picker_frame.set_child(midtones_color_picker_button) + highlights_color_picker_frame.set_child(highlights_color_picker_button) # Manually handle the custom part of the UI. # 1) Connecting the color wheel widgets @@ -424,7 +424,7 @@ def create_alphaspot_widget(effect_prop_manager, element_setting_widget, element ]) shape_picker.set_model(shape_list) shape_text_renderer = Gtk.CellRendererText() - shape_picker.pack_start(shape_text_renderer, 0) + shape_picker.prepend(shape_text_renderer) shape_picker.add_attribute(shape_text_renderer, "text", 0) def get_current_shape(): @@ -465,7 +465,7 @@ def create_alphaspot_widget(effect_prop_manager, element_setting_widget, element ]) op_picker.set_model(op_list) op_text_renderer = Gtk.CellRendererText() - op_picker.pack_start(op_text_renderer, 0) + op_picker.prepend(op_text_renderer) op_picker.add_attribute(op_text_renderer, "text", 0) def get_current_op(): diff --git a/pitivi/utils/misc.py b/pitivi/utils/misc.py index 36cfd03cf1ee12f071ba09a63637778f7e9e5712..cec8df6136800a9dda3ffa6eee93975fcae922d0 100644 --- a/pitivi/utils/misc.py +++ b/pitivi/utils/misc.py @@ -282,8 +282,8 @@ def show_user_manual(page=None): " Make sure to have either the `yelp` GNOME" " documentation viewer or a web browser" " installed")) - dialog.run() - dialog.destroy() + dialog.connect("response", _dialog_response_cb) + dialog.show() def unicode_error_dialog(): @@ -298,8 +298,8 @@ def unicode_error_dialog(): text=message) dialog.set_icon_name("pitivi") dialog.set_title(_("Error while decoding a string")) - dialog.run() - dialog.destroy() + dialog.connect("response", _dialog_response_cb) + dialog.show() def intersect(v1, v2): @@ -420,7 +420,7 @@ def _get_square_width(video_info): def video_info_get_rotation(video_info): tags = video_info.get_tags() - if not tags: + if tags is None: return 0 is_rotated, rotation_string = tags.get_string(Gst.TAG_IMAGE_ORIENTATION) @@ -560,3 +560,7 @@ def is_pathname_valid(pathname: str) -> bool: # (e.g., a bug). Permit this exception to unwind the call stack. # # Did we mention this should be shipped with Python already? + + +def _dialog_response_cb(self, dialog, response): + dialog.close() diff --git a/pitivi/utils/pipeline.py b/pitivi/utils/pipeline.py index 354e8c90988e0e0ba8fe92115b8bd08b21f4c5ec..0f16c817c4ffb5554fa9424a631801926bd06571 100644 --- a/pitivi/utils/pipeline.py +++ b/pitivi/utils/pipeline.py @@ -22,6 +22,7 @@ from gi.repository import GES from gi.repository import GLib from gi.repository import GObject from gi.repository import Gst +from gi.repository import Gtk from pitivi.check import VIDEOSINK_FACTORY from pitivi.utils.loggable import Loggable @@ -106,10 +107,11 @@ class SimplePipeline(GObject.Object, Loggable): """ factory_name = VIDEOSINK_FACTORY.get_name() sink = Gst.ElementFactory.make(factory_name, None) - widget = sink.props.widget + paintable = sink.props.paintable + widget = Gtk.Picture.new_for_paintable(paintable) - if factory_name == "gtksink": - self.info("Using gtksink") + if factory_name == "gtk4paintablesink": + self.info("Using gtk4paintablesink") video_sink = sink else: self.info("Using glsinkbin around %s", VIDEOSINK_FACTORY.get_name()) diff --git a/pitivi/utils/timeline.py b/pitivi/utils/timeline.py index 89f228416cfcb96ab05471cfbed0870c1da834e5..2f90e1e67b2ba2b6df4581d5c5d7a897a2a4993f 100644 --- a/pitivi/utils/timeline.py +++ b/pitivi/utils/timeline.py @@ -115,11 +115,13 @@ class Selection(GObject.Object, Loggable): old_selection = self._clips self._clips = selection + from pitivi.utils.ui import set_state_flags_recurse for obj, selected in self.__get_selection_changes(old_selection): obj.selected.selected = selected if obj.ui: - from pitivi.utils.ui import set_state_flags_recurse set_state_flags_recurse(obj.ui, Gtk.StateFlags.SELECTED, are_set=selected) + if obj.mini_ui: + set_state_flags_recurse(obj.mini_ui, Gtk.StateFlags.SELECTED, are_set=selected) for element in obj.get_children(False): if isinstance(obj, (GES.BaseEffect, GES.TextOverlay)): @@ -457,19 +459,20 @@ class Zoomable: cls.zoom_range) ** (1.0 / 3.0)) * cls.zoom_steps) @classmethod - def pixel_to_ns(cls, pixel): + def pixel_to_ns(cls, pixel, zoomratio=None): """Returns the duration equivalent of the specified pixel.""" - return int(pixel * Gst.SECOND / cls.zoomratio) + zoomratio = cls.zoomratio if zoomratio is None else zoomratio + return int(pixel * Gst.SECOND / zoomratio) @classmethod - def ns_to_pixel(cls, duration): + def ns_to_pixel(cls, duration, zoomratio=None): """Returns the pixel equivalent of the specified duration.""" # Here, a long time ago (206f3a05), a pissed programmer said: # DIE YOU CUNTMUNCH CLOCK_TIME_NONE UBER STUPIDITY OF CRACK BINDINGS !! + zoomratio = cls.zoomratio if zoomratio is None else zoomratio if duration == Gst.CLOCK_TIME_NONE: return 0 - - return int((float(duration) / Gst.SECOND) * cls.zoomratio) + return int((float(duration) / Gst.SECOND) * zoomratio) @classmethod def ns_to_pixel_accurate(cls, duration): diff --git a/pitivi/utils/ui.py b/pitivi/utils/ui.py index cf591ddbeca082d0dabe87c5435a31f96c06a2f7..54adff35b017147bc6581e6368213dbc6019a058 100644 --- a/pitivi/utils/ui.py +++ b/pitivi/utils/ui.py @@ -57,6 +57,7 @@ PLAYHEAD_COLOR = (255, 0, 0) SNAPBAR_WIDTH = 5 SNAPBAR_COLOR = (127, 153, 204) LAYER_HEIGHT = 130 +MINI_LAYER_HEIGHT = 25 # The space between two layers. SEPARATOR_HEIGHT = 1 @@ -66,22 +67,17 @@ SMALL_THUMB_WIDTH = 64 # 128 is the normal size for thumbnails, but for *icons* it looks insane. LARGE_THUMB_WIDTH = 96 -# Drag and drop -FILE_TARGET_ENTRY = Gtk.TargetEntry.new("text/plain", 0, 0) -URI_TARGET_ENTRY = Gtk.TargetEntry.new("text/uri-list", 0, 0) -EFFECT_TARGET_ENTRY = Gtk.TargetEntry.new("pitivi/effect", 0, 0) - TOUCH_INPUT_SOURCES = (Gdk.InputSource.TOUCHPAD, Gdk.InputSource.TRACKPOINT, Gdk.InputSource.TABLET_PAD) CURSORS = { - GES.Edge.EDGE_START: Gdk.Cursor.new(Gdk.CursorType.LEFT_SIDE), - GES.Edge.EDGE_END: Gdk.Cursor.new(Gdk.CursorType.RIGHT_SIDE) + GES.Edge.EDGE_START: Gdk.Cursor.new_from_name("w-resize"), + GES.Edge.EDGE_END: Gdk.Cursor.new_from_name("e-resize") } -NORMAL_CURSOR = Gdk.Cursor.new(Gdk.CursorType.LEFT_PTR) -DRAG_CURSOR = Gdk.Cursor.new(Gdk.CursorType.HAND1) +NORMAL_CURSOR = Gdk.Cursor.new_from_name("default") +DRAG_CURSOR = Gdk.Cursor.new_from_name("pointer") def get_month_format_string(): @@ -227,6 +223,7 @@ EDITOR_PERSPECTIVE_CSS = """ .VideoPreviewer:selected, .AudioPreviewer:selected, + .MiniPreviewer:selected, .TitlePreviewer:selected { opacity: 0.15; } @@ -432,7 +429,7 @@ def format_audiochannels(channels): def gtk_style_context_get_color(context, state): context.save() context.set_state(state) - color = context.get_color(context.get_state()) + color = context.get_color() context.restore() return color @@ -769,19 +766,19 @@ def beautify_last_updated_timestamp(last_updated_timestamp): # -------------------- Gtk widget helpers ----------------------------------- # -class BinWithNaturalWidth(Gtk.Bin): +class BinWithNaturalWidth(Gtk.Widget): """A bin with a maximum width.""" def __init__(self, child, width, *args, **kwargs): - Gtk.Bin.__init__(self, *args, **kwargs) + Gtk.Widget.__init__(self, *args, **kwargs) self.natural_width = width - self.add(child) + self.insert_after(child) def do_get_request_mode(self): return Gtk.SizeRequestMode.HEIGHT_FOR_WIDTH def do_get_preferred_width(self): - minimum, _ = Gtk.Bin.do_get_preferred_width(self) + minimum, _ = Gtk.Widget.do_get_preferred_width(self) minimum = min(minimum, self.natural_width) return minimum, self.natural_width @@ -792,9 +789,8 @@ def clear_styles(widget): Args: widget (Gtk.Widget): The widget to clean up. """ - style = widget.get_style_context() - for css_class in style.list_classes(): - style.remove_class(css_class) + for css_class in widget.get_css_classes(): + widget.remove_css_class(css_class) def create_model(columns, data) -> Gtk.ListStore: @@ -881,13 +877,14 @@ def set_state_flags_recurse(widget, state_flags, are_set, ignored_classes=()): else: widget.unset_state_flags(state_flags) - if isinstance(widget, Gtk.Container): - for child in widget.get_children(): - set_state_flags_recurse(child, state_flags, are_set, ignored_classes) + # if isinstance(widget, Gtk.Container): + # for child in get_widget_children(widget): + # set_state_flags_recurse(child, state_flags, are_set, ignored_classes) -def disable_scroll_event_cb(widget, unused_event): - GObject.signal_stop_emission_by_name(widget, "scroll-event") +def disable_scroll_event_cb(controller, dx, dy): + widget = controller.get_widget() + GObject.signal_stop_emission_by_name(widget, "scroll") return False @@ -896,20 +893,33 @@ def disable_scroll(widget): Makes sure the vulnerable widgets do not react to scroll events. """ - if isinstance(widget, Gtk.Container): - widget.foreach(disable_scroll) + # if isinstance(widget, Gtk.Container): + # widget.foreach(disable_scroll) if isinstance(widget, (Gtk.ComboBox, Gtk.Scale, Gtk.SpinButton)): - widget.connect("scroll-event", disable_scroll_event_cb) - - -def fix_infobar(infobar): - # Work around https://bugzilla.gnome.org/show_bug.cgi?id=710888 - def make_sure_revealer_does_nothing(widget): - if not isinstance(widget, Gtk.Revealer): - return - widget.set_transition_type(Gtk.RevealerTransitionType.NONE) - infobar.forall(make_sure_revealer_does_nothing) + eventcontroller_scroll = Gtk.EventControllerScroll() + eventcontroller_scroll.connect("scroll", disable_scroll_event_cb) + widget.add_controller(eventcontroller_scroll) + + +def get_listbox_children(listbox): + index = 0 + child_list = [] + while child := listbox.get_row_at_index(index): + child_list.append(child) + index += 1 + return child_list + + +# ToDo: Can be further optimised and use Yield +def get_widget_children(widget, filter_func=lambda child: True): + child = widget.get_first_child() + child_list = [] + while child: + if filter_func(child): + child_list.append(child) + child = child.get_next_sibling() + return child_list AUDIO_CHANNELS = create_model((str, int), diff --git a/pitivi/utils/widgets.py b/pitivi/utils/widgets.py index 9153abf850bc1016022d648e7c8699e776da95a4..a4302046a557079a90c36e51068442bbcd53c091 100644 --- a/pitivi/utils/widgets.py +++ b/pitivi/utils/widgets.py @@ -114,25 +114,24 @@ class TextWidget(Gtk.Box, DynamicWidget): DynamicWidget.__init__(self, default) self.set_orientation(Gtk.Orientation.HORIZONTAL) - self.set_border_width(0) self.set_spacing(0) if widget is None: if choices: self.combo = Gtk.ComboBoxText.new_with_entry() self.text = self.combo.get_child() self.combo.show() - self.pack_start(self.combo, expand=False, fill=False, padding=0) + self.prepend(self.combo) for choice in choices: self.combo.append_text(choice) elif combobox: self.combo = Gtk.ComboBox.new_with_entry() self.text = self.combo.get_child() self.combo.show() - self.pack_start(self.combo, expand=False, fill=False, padding=0) + self.prepend(self.combo) else: self.text = Gtk.Entry() self.text.show() - self.pack_start(self.text, expand=False, fill=False, padding=0) + self.prepend(self.text) else: self.text = widget @@ -238,16 +237,15 @@ class NumericWidget(Gtk.Box, DynamicWidget): self.spinner = Gtk.SpinButton(adjustment=self.adjustment) if width_chars: self.spinner.props.width_chars = width_chars - self.pack_start(self.spinner, expand=False, fill=False, padding=0) + self.prepend(self.spinner) self.spinner.show() if with_slider: self.slider = Gtk.Scale.new( Gtk.Orientation.HORIZONTAL, self.adjustment) - self.pack_start(self.slider, expand=False, fill=False, padding=0) + self.prepend(self.slider) self.slider.show() self.slider.set_size_request(width=100, height=-1) - self.slider.props.draw_value = False # Abuse GTK3's progressbar "fill level" feature to provide # a visual indication of the default value on property sliders. if default is not None: @@ -311,7 +309,9 @@ class TimeWidget(TextWidget, DynamicWidget): TextWidget.__init__(self, self.VALID_REGEX) TextWidget.set_width_chars(self, 10) self._framerate = None - self.text.connect("focus-out-event", self._focus_out_cb) + eventcontroller_focus = Gtk.EventControllerFocus() + eventcontroller_focus.connect("leave", self._focus_out_cb) + self.text.add_controller(eventcontroller_focus) def get_widget_value(self): timecode = TextWidget.get_widget_value(self) @@ -342,7 +342,7 @@ class TimeWidget(TextWidget, DynamicWidget): timecode = timecode[2:] TextWidget.set_widget_value(self, timecode, send_signal=send_signal) - def _focus_out_cb(self, widget, event): + def _focus_out_cb(self, controller): """Reset the text to display the current position of the playhead.""" if self.default is not None: self.set_widget_value(self.default) @@ -351,8 +351,10 @@ class TimeWidget(TextWidget, DynamicWidget): return self.connect("activate", activate_cb) def connect_focus_events(self, focus_in_cb, focus_out_cb): - focus_in_handler_id = self.text.connect("focus-in-event", focus_in_cb) - focus_out_handler_id = self.text.connect("focus-out-event", focus_out_cb) + eventcontroller_focus = Gtk.EventControllerFocus() + focus_in_handler_id = eventcontroller_focus.connect("enter", focus_in_cb) + focus_out_handler_id = eventcontroller_focus.connect("leave", focus_out_cb) + self.text.add_controller(eventcontroller_focus) return [focus_in_handler_id, focus_out_handler_id] def set_framerate(self, framerate): @@ -448,7 +450,7 @@ class ToggleWidget(Gtk.Box, DynamicWidget): self.props.valign = Gtk.Align.CENTER if switch_button is None: self.switch_button = Gtk.Switch() - self.pack_start(self.switch_button, expand=False, fill=False, padding=0) + self.prepend(self.switch_button) self.switch_button.show() else: self.switch_button = switch_button @@ -477,7 +479,7 @@ class ChoiceWidget(Gtk.Box, DynamicWidget): self.values = None self.set_orientation(Gtk.Orientation.HORIZONTAL) self.contents = Gtk.ComboBoxText() - self.pack_start(self.contents, expand=False, fill=False, padding=0) + self.prepend(self.contents) self.set_choices(choices) self.contents.show() cell = self.contents.get_cells()[0] @@ -508,7 +510,7 @@ class ChoiceWidget(Gtk.Box, DynamicWidget): self.contents.set_sensitive(True) -class PathWidget(Gtk.FileChooserButton, DynamicWidget): +class PathWidget(Gtk.Button, DynamicWidget): """Widget for entering a path.""" __gtype_name__ = 'PathWidget' @@ -521,9 +523,9 @@ class PathWidget(Gtk.FileChooserButton, DynamicWidget): DynamicWidget.__init__(self, default) self.dialog = Gtk.FileChooserDialog(action=action) self.dialog.add_buttons( - Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_CLOSE, Gtk.ResponseType.CLOSE) + _("Cancel"), Gtk.ResponseType.CANCEL, _("Close"), Gtk.ResponseType.CLOSE) self.dialog.set_default_response(Gtk.ResponseType.OK) - Gtk.FileChooserButton.__init__(self, dialog=self.dialog) + Gtk.Button.__init__(self, dialog=self.dialog) self.dialog.connect("response", self._response_cb) self.uri = "" @@ -571,10 +573,10 @@ class FontWidget(Gtk.FontButton, DynamicWidget): self.connect("font-set", callback, *args) def set_widget_value(self, font_name): - self.set_font_name(font_name) + self.set_font(font_name) def get_widget_value(self): - return self.get_font_name() + return self.get_font() class InputValidationWidget(Gtk.Box, DynamicWidget): @@ -594,12 +596,16 @@ class InputValidationWidget(Gtk.Box, DynamicWidget): DynamicWidget.__init__(self, widget.default) self._widget = widget self._validation_function = validation_function - self._warning_sign = Gtk.Image.new_from_icon_name("dialog-warning-symbolic", Gtk.IconSize.LARGE_TOOLBAR) + self._warning_sign = Gtk.Image.new_from_icon_name("dialog-warning-symbolic") + self._warning_sign.props.margin_top = SPACING + self._warning_sign.props.margin_bottom = SPACING + self._warning_sign.props.margin_start = SPACING + self._warning_sign.props.margin_end = SPACING self.set_orientation(Gtk.Orientation.HORIZONTAL) - self.pack_start(self._widget, expand=False, fill=False, padding=0) - self.pack_start(self._warning_sign, expand=False, fill=False, padding=SPACING) - self._warning_sign.set_no_show_all(True) + self.prepend(self._widget) + self.prepend(self._warning_sign) + self._warning_sign.hide() self._widget.connect_value_changed(self._widget_value_changed_cb) @@ -696,9 +702,11 @@ class GstElementSettingsWidget(Gtk.Box, Loggable): break def show_widget(self, widget): - self.pack_start(widget, True, True, 0) + widget.set_hexpand(True) + widget.set_halign(Gtk.Align.FILL) + self.prepend(widget) disable_scroll(widget) - self.show_all() + self.show() def map_builder(self, builder): """Maps the GStreamer element's properties to corresponding widgets in @builder. @@ -798,7 +806,7 @@ class GstElementSettingsWidget(Gtk.Box, Loggable): grid.attach(widget, 0, y, 2, 1) else: text = _("%(preference_label)s:") % {"preference_label": nick} - label = Gtk.Label(label=text) + label = Gtk.Label.new(text) label.props.yalign = 0.5 grid.attach(label, 0, y, 1, 1) grid.attach(widget, 1, y, 1, 1) @@ -833,15 +841,18 @@ class GstElementSettingsWidget(Gtk.Box, Loggable): props = [prop for prop in GObject.list_properties(self.element) if prop.name not in self.ignore] if not props: - widget = Gtk.Label(label=_("No properties.")) - self.pack_start(widget, expand=False, fill=False, padding=0) + widget = Gtk.Label.new(_("No properties.")) + self.prepend(widget) widget.show() return grid = Gtk.Grid() grid.props.row_spacing = SPACING grid.props.column_spacing = SPACING - grid.props.border_width = SPACING + grid.props.margin_top = SPACING + grid.props.margin_bottom = SPACING + grid.props.margin_start = SPACING + grid.props.margin_end = SPACING element_name = None if isinstance(self.element, Gst.Element): @@ -897,8 +908,8 @@ class GstElementSettingsWidget(Gtk.Box, Loggable): except KeyError: widget = prop_widget - label = Gtk.Label(label=prop.nick) - label.set_alignment(0.0, 0.5) + label = Gtk.Label.new(prop.nick) + label.set_yalign(0.5) grid.attach(label, 0, y, 1, 1) grid.attach(widget, 1, y, 1, 1) if hasattr(prop, 'blurb'): @@ -928,8 +939,7 @@ class GstElementSettingsWidget(Gtk.Box, Loggable): grid.attach(button, 3, y, 1, 1) self.element.connect('deep-notify', self._property_changed_cb) - self.pack_start(grid, expand=False, fill=False, padding=0) - self.show_all() + self.prepend(grid) def _make_widget_from_gvalue(self, gvalue, default): if isinstance(gvalue, Gst.ValueList): @@ -959,7 +969,7 @@ class GstElementSettingsWidget(Gtk.Box, Loggable): keyframe_button = Gtk.ToggleButton() keyframe_button.props.focus_on_click = False # Avoid the ugly selection outline keyframe_button.set_tooltip_text(_("Show keyframes for this value")) - keyframe_button.set_relief(Gtk.ReliefStyle.NONE) + keyframe_button.set_has_frame(False) keyframe_button.connect('toggled', self.__keyframes_toggled_cb, prop) self.__widgets_by_keyframe_button[keyframe_button] = widget prop_binding = self.element.get_control_binding(prop.name) @@ -970,11 +980,11 @@ class GstElementSettingsWidget(Gtk.Box, Loggable): def __create_reset_to_default_button(self, unused_prop, widget, keyframe_button): icon = Gtk.Image() - icon.set_from_icon_name("edit-clear-all-symbolic", Gtk.IconSize.MENU) + icon.set_from_icon_name("edit-clear-all-symbolic") button = Gtk.Button() - button.add(icon) + button.set_child(icon) button.set_tooltip_text(_("Reset to default value")) - button.set_relief(Gtk.ReliefStyle.NONE) + button.set_has_frame(False) button.connect('clicked', self.__reset_to_default_clicked_cb, widget, keyframe_button) self.__widgets_by_reset_button[button] = widget @@ -1130,15 +1140,14 @@ class GstElementSettingsDialog(Loggable): self.properties = properties self.__caps = caps - self.builder = Gtk.Builder() + self.builder = Gtk.Builder(self) self.builder.add_from_file( os.path.join(get_ui_dir(), "elementsettingsdialog.ui")) - self.builder.connect_signals(self) self.ok_btn = self.builder.get_object("okbutton1") self.window = self.builder.get_object("dialog1") self.elementsettings = GstElementSettingsWidget(self.element, controllable=False) - self.builder.get_object("viewport1").add(self.elementsettings) + self.builder.get_object("viewport1").set_child(self.elementsettings) # set title and frame label self.window.set_title( @@ -1172,7 +1181,6 @@ class GstElementSettingsDialog(Loggable): scrolledwindow = self.builder.get_object("scrolledwindow1") scrolledwindow.set_policy( Gtk.PolicyType.NEVER, Gtk.PolicyType.NEVER) - scrolledwindow.set_shadow_type(Gtk.ShadowType.NONE) else: # If we need to scroll, set a reasonable height for the window. default_height = 600 @@ -1224,16 +1232,15 @@ class ZoomBox(Gtk.Grid, Zoomable): self.timeline = timeline zoom_fit_btn = Gtk.Button() - zoom_fit_btn.set_relief(Gtk.ReliefStyle.NONE) + zoom_fit_btn.set_has_frame(False) zoom_fit_btn.set_tooltip_text(_("Zoom Fit")) zoom_fit_btn_grid = Gtk.Grid() - zoom_fit_icon = Gtk.Image.new_from_icon_name( - "zoom-fit-best-symbolic", Gtk.IconSize.BUTTON) - zoom_fit_btn_grid.add(zoom_fit_icon) - zoom_fit_btn_label = Gtk.Label(label=_("Zoom")) - zoom_fit_btn_grid.add(zoom_fit_btn_label) + zoom_fit_icon = Gtk.Image.new_from_icon_name("zoom-fit-best-symbolic") + zoom_fit_btn_grid.attach(zoom_fit_icon, 0, 0, 1, 1) + zoom_fit_btn_label = Gtk.Label.new(_("Zoom")) + zoom_fit_btn_grid.attach(zoom_fit_btn_label, 1, 0, 1, 1) zoom_fit_btn_grid.set_column_spacing(SPACING / 2) - zoom_fit_btn.add(zoom_fit_btn_grid) + zoom_fit_btn.set_child(zoom_fit_btn_grid) zoom_fit_btn.connect("clicked", self._zoom_fit_cb) self.attach(zoom_fit_btn, 0, 0, 1, 1) @@ -1246,8 +1253,9 @@ class ZoomBox(Gtk.Grid, Zoomable): # Setting _zoom_adjustment's value must be done after we create the # zoom slider, otherwise the slider remains at the leftmost position. self._zoom_adjustment.set_value(Zoomable.get_current_zoom_level()) - zoomslider.props.draw_value = False - zoomslider.connect("scroll-event", self._zoom_slider_scroll_cb) + eventcontroller_scroll = Gtk.EventControllerScroll() + eventcontroller_scroll.connect("scroll", self._zoom_slider_scroll_cb) + zoomslider.add_controller(eventcontroller_scroll) zoomslider.connect("value-changed", self._zoom_slider_changed_cb) zoomslider.connect("query-tooltip", self._zoom_slider_query_tooltip_cb) zoomslider.set_has_tooltip(True) @@ -1257,11 +1265,10 @@ class ZoomBox(Gtk.Grid, Zoomable): self.attach(zoomslider, 1, 0, 1, 1) # Empty label so we have some spacing at the right of the zoomslider - self.attach(Gtk.Label(label=""), 2, 0, 1, 1) + self.attach(Gtk.Label.new(""), 2, 0, 1, 1) self.set_hexpand(False) self.set_column_spacing(ZOOM_SLIDER_PADDING) - self.show_all() def _zoom_slider_changed_cb(self, adjustment): Zoomable.set_zoom_level(adjustment.get_value()) @@ -1275,18 +1282,18 @@ class ZoomBox(Gtk.Grid, Zoomable): def _zoom_fit_cb(self, button): self.timeline.timeline.set_best_zoom_ratio(allow_zoom_in=True) - def _zoom_slider_scroll_cb(self, unused, event): + def _zoom_slider_scroll_cb(self, controller, dx, dy): delta = 0 + event = controller.get_current_event() if event.direction in [Gdk.ScrollDirection.UP, Gdk.ScrollDirection.RIGHT]: delta = 1 elif event.direction in [Gdk.ScrollDirection.DOWN, Gdk.ScrollDirection.LEFT]: delta = -1 elif event.direction in [Gdk.ScrollDirection.SMOOTH]: - unused_res, delta_x, delta_y = event.get_scroll_deltas() - if delta_x: - delta = math.copysign(1, delta_x) - elif delta_y: - delta = math.copysign(1, -delta_y) + if dx: + delta = math.copysign(1, dx) + elif dy: + delta = math.copysign(1, -dy) if delta: Zoomable.set_zoom_level(Zoomable.get_current_zoom_level() + delta) @@ -1384,8 +1391,7 @@ class ColorPickerButton(Gtk.Button): self.dropper_grab_widget = None self.pointer_device = None self.is_picking = False - picker_image = Gtk.Image.new_from_icon_name("gtk-color-picker", Gtk.IconSize.BUTTON) - self.set_image(picker_image) + self.set_icon_name("gtk-color-picker") self.connect("clicked", self.clicked_cb) def clicked_cb(self, button): @@ -1401,7 +1407,6 @@ class ColorPickerButton(Gtk.Button): grab_widget.resize(1, 1) grab_widget.move(-100, -100) grab_widget.show() - grab_widget.add_events(Gdk.EventMask.BUTTON_RELEASE_MASK) top_level = self.get_toplevel() if isinstance(top_level, Gtk.Window): if top_level.has_group(): @@ -1418,7 +1423,10 @@ class ColorPickerButton(Gtk.Button): return # Start tracking the mouse events self.dropper_grab_widget.grab_add() - self.dropper_grab_widget.connect("button-release-event", self.button_release_event_cb) + dropper_eventcontroller_click = Gtk.GestureClick() + dropper_eventcontroller_click.connect("released", self.button_release_event_cb) + dropper_eventcontroller_click.set_button(1) + self.dropper_grab_widget.add_controller(dropper_eventcontroller_click) def make_cursor_picker(self, screen): # Get color-picker cursor if it exists in the current theme else fallback to generating it ourself @@ -1427,20 +1435,18 @@ class ColorPickerButton(Gtk.Button): except TypeError: pixbuf = GdkPixbuf.Pixbuf.new_from_data(DROPPER_BITS, GdkPixbuf.Colorspace.RGB, True, 8, DROPPER_WIDTH, DROPPER_HEIGHT, DROPPER_WIDTH * 4) - cursor = Gdk.Cursor.new_from_pixbuf(screen.get_display(), pixbuf, DROPPER_X_HOT, DROPPER_Y_HOT) + texture = Gdk.Texture.new_for_pixbuf(pixbuf) + cursor = Gdk.Cursor.new_from_texture(texture, DROPPER_X_HOT, DROPPER_Y_HOT) return cursor - def button_release_event_cb(self, widget, event): - if event.button != Gdk.BUTTON_PRIMARY: - return - - self.grab_color_at_pointer(event.get_screen(), event.x_root, event.y_root) + def button_release_event_cb(self, controller, n_pressed, x, y): + cursor = controller.get_current_event().get_surface().get_cursor() + self.grab_color_at_pointer(cursor.get_texture(), x, y) self.emit("value-changed") self.shutdown_eyedropper() - def grab_color_at_pointer(self, screen, x, y): - root_window = screen.get_root_window() - pixbuf = Gdk.pixbuf_get_from_window(root_window, x, y, 1, 1) + def grab_color_at_pointer(self, texture, unused_x, unused_y): + pixbuf = Gdk.pixbuf_get_from_texture(texture) pixels = pixbuf.get_pixels() self.color_r = pixels[0] self.color_g = pixels[1] diff --git a/pitivi/viewer/guidelines.py b/pitivi/viewer/guidelines.py index 3b6693e073b65d5f658aff3275f8a969e9597a9e..eaf48f9505cdd301b93cb8fdc3751542303cc85e 100644 --- a/pitivi/viewer/guidelines.py +++ b/pitivi/viewer/guidelines.py @@ -87,9 +87,12 @@ class GuidelinesPopover(Gtk.Popover): grid = Gtk.Grid() grid.props.row_spacing = SPACING grid.props.column_spacing = SPACING - grid.props.margin = SPACING * 2 + grid.props.margin_top = SPACING * 2 + grid.props.margin_bottom = SPACING * 2 + grid.props.margin_start = SPACING * 2 + grid.props.margin_end = SPACING * 2 - label = Gtk.Label(_("Composition Guidelines")) + label = Gtk.Label.new(_("Composition Guidelines")) label.props.wrap = True grid.attach(label, 0, 0, 2, 1) @@ -99,7 +102,7 @@ class GuidelinesPopover(Gtk.Popover): for guideline in Guideline: row += 1 - label = Gtk.Label(guideline.label) + label = Gtk.Label.new(guideline.label) label.props.halign = Gtk.Align.START label.props.wrap = True label.props.xalign = 0 @@ -111,8 +114,7 @@ class GuidelinesPopover(Gtk.Popover): self.switches[guideline] = switch - grid.show_all() - self.add(grid) + self.set_child(grid) def toggle(self): """Toggle the visible guidelines on the managed overlay.""" @@ -151,7 +153,7 @@ class GuidelinesOverlay(Gtk.DrawingArea): self.active_guidelines = set() self.hide() - self.props.no_show_all = True + self.set_draw_func(self.draw_func) def add_guideline(self, guideline): if guideline not in self.active_guidelines: @@ -167,9 +169,7 @@ class GuidelinesOverlay(Gtk.DrawingArea): self.set_visible(False) self.queue_draw() - def do_draw(self, cr): - width = self.get_allocated_width() - height = self.get_allocated_height() + def draw_func(self, unused_drawing_area, cr, width, height): # Draw black border. cr.set_source_rgb(0, 0, 0) diff --git a/pitivi/viewer/move_scale_overlay.py b/pitivi/viewer/move_scale_overlay.py index ebbce69865d22189bfbae75f5ae16ed80f45f0e1..a9bbf2107a4e9aec2c9427c61547664b456f6a6e 100644 --- a/pitivi/viewer/move_scale_overlay.py +++ b/pitivi/viewer/move_scale_overlay.py @@ -320,6 +320,7 @@ class MoveScaleOverlay(Overlay): self._source.connect("deep-notify", self.__source_property_changed_cb) self.update_from_source() + self.set_draw_func(self.draw_func) def __get_source_property(self, prop): if self.__source_property_keyframed(prop): @@ -550,7 +551,7 @@ class MoveScaleOverlay(Overlay): self.__reset_handle_sizes() self.queue_draw() - def do_draw(self, cr): + def draw_func(self, unused_drawing_area, cr, unused_width, unused_height): selected = self._is_selected() hovered = self._is_hovered() if not selected and not hovered: diff --git a/pitivi/viewer/overlay_stack.py b/pitivi/viewer/overlay_stack.py index 9b2f4c695bce4174bb61e1c0e807373c1b184b5d..4ced1da3497587a4d4af9d63dd23c37487701551 100644 --- a/pitivi/viewer/overlay_stack.py +++ b/pitivi/viewer/overlay_stack.py @@ -41,15 +41,8 @@ class OverlayStack(Gtk.Overlay, Loggable): self.click_position = None self.hovered_overlay = None self.selected_overlay = None - self.add_events(Gdk.EventMask.BUTTON_PRESS_MASK | - Gdk.EventMask.BUTTON_RELEASE_MASK | - Gdk.EventMask.POINTER_MOTION_MASK | - Gdk.EventMask.SCROLL_MASK | - Gdk.EventMask.ENTER_NOTIFY_MASK | - Gdk.EventMask.LEAVE_NOTIFY_MASK | - Gdk.EventMask.ALL_EVENTS_MASK) - self.add(sink_widget) - self.connect("size-allocate", self.__size_allocate_cb) + self.set_child(sink_widget) + # self.connect("size-allocate", self.__size_allocate_cb) # Whether to show the percent of the size relative to the project size. # It is set to false initially because the viewer gets resized @@ -59,8 +52,8 @@ class OverlayStack(Gtk.Overlay, Loggable): # ID of resizing timeout callback, so it can be delayed. self.__resizing_id = 0 self.revealer = Gtk.Revealer(transition_type=Gtk.RevealerTransitionType.CROSSFADE) - self.resize_status = Gtk.Label(name="resize_status") - self.revealer.add(self.resize_status) + self.resize_status = Gtk.Label.new("resize_status") + self.revealer.set_child(self.resize_status) self.add_overlay(self.revealer) self.guidelines_overlay = guidelines_overlay @@ -69,7 +62,7 @@ class OverlayStack(Gtk.Overlay, Loggable): self.safe_areas_overlay = SafeAreasOverlay(self) self.add_overlay(self.safe_areas_overlay) - sink_widget.connect("size-allocate", self.__sink_widget_size_allocate_cb) + # sink_widget.connect("size-allocate", self.__sink_widget_size_allocate_cb) def __size_allocate_cb(self, widget, rectangle): self.window_size = numpy.array([rectangle.width, @@ -194,7 +187,7 @@ class OverlayStack(Gtk.Overlay, Loggable): self.app.gui.get_window().set_cursor(cursor) def reset_cursor(self): - self.app.gui.get_window().set_cursor(None) + self.app.gui.set_cursor(None) def get_drag_distance(self, cursor_position): return cursor_position - self.click_position diff --git a/pitivi/viewer/peak_meter.py b/pitivi/viewer/peak_meter.py index b0433c1f290afc00fcc3b12d8711131c1c0dee65..d7092fa61b7fe3371b2f730f1cd3a8debdc97d8c 100644 --- a/pitivi/viewer/peak_meter.py +++ b/pitivi/viewer/peak_meter.py @@ -25,7 +25,7 @@ from pitivi.utils.ui import set_cairo_color from pitivi.utils.ui import SPACING # The width for the peak meter -PEAK_METER_WIDTH = 8 +PEAK_METER_WIDTH = 10 # The maximum height for the peak meter PEAK_METER_MAX_HEIGHT = 200 # The minimum height for the peak meter @@ -47,16 +47,15 @@ class PeakMeterWidget(Gtk.DrawingArea): Gtk.DrawingArea.__init__(self) self.pixel_buffer = None - style_context = self.get_style_context() - style_context.add_class("background") - self.connect("size-allocate", self.__size_allocate_cb) + self.add_css_class("background") + self.connect("resize", self.__resize_cb) - def __size_allocate_cb(self, unused_event, allocation): + def __resize_cb(self, unused_widget, width, height): if self.pixel_buffer is not None: self.pixel_buffer.finish() self.pixel_buffer = None - self.pixel_buffer = cairo.ImageSurface(cairo.FORMAT_ARGB32, allocation.width, allocation.height) + self.pixel_buffer = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height) def draw_background(self, context, width, height): style_context = self.get_style_context() @@ -74,12 +73,13 @@ class PeakMeter(PeakMeterWidget): width = PEAK_METER_WIDTH self.set_property("width_request", width) - style_context = self.get_style_context() - style_context.add_class("frame") + self.add_css_class("frame") + + self.set_draw_func(self.draw_func) - self.connect("size-allocate", self.__size_allocate_cb) + self.connect("resize", self.__resize_cb) - def do_draw(self, context): + def draw_func(self, unused_widget, context, width, height): if self.pixel_buffer is None: return @@ -97,8 +97,8 @@ class PeakMeter(PeakMeterWidget): context.set_source_surface(pixel_buffer, 0.0, 0.0) context.paint() - def __size_allocate_cb(self, unused_event, allocation): - self.__set_gradients(allocation.width, allocation.height) + def __resize_cb(self, unused_widget, width, height): + self.__set_gradients(width, height) def __draw_bar(self, context, width, height): peak_height = self.__normalize_peak(height) @@ -152,8 +152,9 @@ class PeakMeterScale(PeakMeterWidget): PeakMeterWidget.__init__(self) width = FONT_SIZE * 2 self.set_property("width_request", width) + self.set_draw_func(self.draw_func) - def do_draw(self, context): + def draw_func(self, unused_widget, context, width, height): if self.pixel_buffer is None: return diff --git a/pitivi/viewer/safe_areas_overlay.py b/pitivi/viewer/safe_areas_overlay.py index 339ee950f1618ef4dfc402e96d5e0a7ddeeb8324..c6238fa5333e56748082a5031feba735c8bb4b36 100644 --- a/pitivi/viewer/safe_areas_overlay.py +++ b/pitivi/viewer/safe_areas_overlay.py @@ -29,7 +29,7 @@ class SafeAreasOverlay(Gtk.DrawingArea): self.__project.connect("safe-area-size-changed", self.__safe_areas_size_changed_cb) - self.props.no_show_all = True + self.set_draw_func(self.draw_func) def __safe_areas_size_changed_cb(self, project): self.queue_draw() @@ -42,9 +42,7 @@ class SafeAreasOverlay(Gtk.DrawingArea): y = (widget_height - h) / 2 return round05(x), round05(y), int(w), int(h) - def do_draw(self, cr): - width = self.get_allocated_width() - height = self.get_allocated_height() + def draw_func(self, unused_drawing_area, cr, width, height): title_rect = self._compute_rect(width, height, self.__project.title_safe_area_horizontal, self.__project.title_safe_area_vertical) action_rect = self._compute_rect(width, height, self.__project.action_safe_area_horizontal, self.__project.action_safe_area_vertical) diff --git a/pitivi/viewer/title_overlay.py b/pitivi/viewer/title_overlay.py index 2a785c8cb697a43da7f6b9426005bd81e06173f9..7104ab3f3b0622a2b4c0953a1e900178e57e9f6a 100644 --- a/pitivi/viewer/title_overlay.py +++ b/pitivi/viewer/title_overlay.py @@ -30,6 +30,7 @@ class TitleOverlay(Overlay): self.__size = None self.__click_window_position = None self.update_from_source() + self.set_draw_func(self.draw_func) stack.app.project_manager.current_project.pipeline.connect("async-done", self.on_async_done) @@ -116,7 +117,7 @@ class TitleOverlay(Overlay): self.__set_source_position(title_position) self._commit() - def do_draw(self, cr): + def draw_func(self, unused_drawing_area, cr, unused_width, unused_height): selected = self._is_selected() hovered = self._is_hovered() if not selected and not hovered: diff --git a/pitivi/viewer/viewer.py b/pitivi/viewer/viewer.py index 0ea7a183e84d23e403dc41bbeebdd06d941e5d34..66c0eb077ae009bbb3103896435776d546b92c11 100644 --- a/pitivi/viewer/viewer.py +++ b/pitivi/viewer/viewer.py @@ -134,14 +134,13 @@ class ViewerContainer(Gtk.Box, Loggable): for i in range(project.audiochannels): if i > len(self.peak_meters) - 1: new_peak_meter = PeakMeter() - new_peak_meter.set_property("valign", Gtk.Align.FILL) - new_peak_meter.set_property("halign", Gtk.Align.CENTER) + new_peak_meter.set_vexpand(True) + new_peak_meter.set_valign(Gtk.Align.FILL) + new_peak_meter.set_halign(Gtk.Align.CENTER) new_peak_meter.set_margin_bottom(SPACING) new_peak_meter.set_margin_top(SPACING) self.peak_meters.append(new_peak_meter) - self.peak_meter_box.pack_start(self.peak_meters[i], False, False, 0) - - self.peak_meter_box.show_all() + self.peak_meter_box.prepend(self.peak_meters[i]) def set_project(self, project): """Sets the displayed project. @@ -183,16 +182,23 @@ class ViewerContainer(Gtk.Box, Loggable): sink_widget, self.guidelines_popover.overlay) self.target = ViewerWidget(self.overlay_stack) + self.target.set_hexpand(True) + self.target.set_halign(Gtk.Align.FILL) self._reset_viewer_aspect_ratio(self.project) - self.viewer_row_box.pack_start(self.target, expand=True, fill=True, padding=0) + self.viewer_row_box.prepend(self.target) if self.docked: - self.pack_start(self.viewer_row_box, expand=True, fill=True, padding=0) + self.viewer_row_box.set_vexpand(True) + self.viewer_row_box.set_valign(Gtk.Align.FILL) + self.prepend(self.viewer_row_box) else: - self.external_vbox.pack_start(self.viewer_row_box, expand=True, fill=False, padding=0) - self.external_vbox.child_set(self.viewer_row_box, fill=True) + self.viewer_row_box.set_hexpand(True) + self.external_vbox.prepend(self.viewer_row_box) + # TODO:Porting: Check if this is needed + # self.external_vbox.child_set(self.viewer_row_box, fill=True) + # GObject.Object.set_property(self.viewer_row_box, "fill", True) - self.viewer_row_box.show_all() + self.target.show() # Wait for 1s to make sure that the viewer has completely realized # and then we can mark the resize status as showable. @@ -217,15 +223,18 @@ class ViewerContainer(Gtk.Box, Loggable): if active: self.emit("activate-playback-controls", True) - def _external_window_delete_cb(self, unused_window, unused_event): + def _external_window_close_cb(self, unused_event): self.dock() return True - def _external_window_configure_cb(self, unused_window, event): - self.settings.viewerWidth = event.width - self.settings.viewerHeight = event.height - self.settings.viewerX = event.x - self.settings.viewerY = event.y + # TODO: Porting: set viewerX and viewerY in the settings + def _external_window_notify_default_width_cb(self, external_window, unused_pspec): + width, unused_height = external_window.get_default_size() + self.settings.viewerWidth = width + + def _external_window_notify_default_height_cb(self, external_window, unused_pspec): + unused_width, height = external_window.get_default_size() + self.settings.viewerHeight = height def _create_ui(self): """Creates the Viewer GUI.""" @@ -235,11 +244,11 @@ class ViewerContainer(Gtk.Box, Loggable): vbox = Gtk.Box() vbox.set_orientation(Gtk.Orientation.VERTICAL) vbox.set_spacing(SPACING) - self.external_window.add(vbox) - self.external_window.connect( - "delete-event", self._external_window_delete_cb) + self.external_window.set_child(vbox) self.external_window.connect( - "configure-event", self._external_window_configure_cb) + "close_request", self._external_window_close_cb) + self.external_window.connect("notify::default-width", self._external_window_notify_default_width_cb) + self.external_window.connect("notify::default-height", self._external_window_notify_default_height_cb) self.external_vbox = vbox # This holds the viewer and the peak meters box. @@ -257,18 +266,20 @@ class ViewerContainer(Gtk.Box, Loggable): corner_size = space * lines + margin corner.set_size_request(corner_size, corner_size) corner.set_halign(Gtk.Align.START) - corner.add_events(Gdk.EventMask.ENTER_NOTIFY_MASK | - Gdk.EventMask.BUTTON_PRESS_MASK | - Gdk.EventMask.BUTTON_RELEASE_MASK | - Gdk.EventMask.POINTER_MOTION_MASK) hpane = self.app.gui.editor.mainhpaned vpane = self.app.gui.editor.toplevel_widget - corner.connect("draw", self.__corner_draw_cb, lines, space, margin) - corner.connect("enter-notify-event", self.__corner_enter_notify_cb) - corner.connect("button-press-event", self.__corner_button_press_cb, hpane, vpane) - corner.connect("button-release-event", self.__corner_button_release_cb) - corner.connect("motion-notify-event", self.__corner_motion_notify_cb, hpane, vpane) - self.pack_end(corner, False, False, 0) + + eventcontroller_click = Gtk.GestureClick() + eventcontroller_motion = Gtk.EventControllerMotion() + + corner.set_draw_func(self.__corner_draw_cb, lines, space, margin) + eventcontroller_motion.connect("enter", self.__corner_enter_notify_cb) + eventcontroller_click.connect("pressed", self.__corner_button_press_cb, hpane, vpane) + eventcontroller_click.connect("released", self.__corner_button_release_cb) + eventcontroller_motion.connect("motion", self.__corner_motion_notify_cb, hpane, vpane) + corner.add_controller(eventcontroller_click) + corner.add_controller(eventcontroller_motion) + self.append(corner) # Peak Meters self.peak_meter_box = Gtk.Box() @@ -276,111 +287,116 @@ class ViewerContainer(Gtk.Box, Loggable): self.peak_meter_box.set_margin_start(SPACING) self.peak_meter_box.set_margin_bottom(SPACING) self.peak_meter_box.set_margin_top(SPACING) - self.peak_meter_box.set_property("valign", Gtk.Align.CENTER) + self.peak_meter_box.set_vexpand(True) + self.peak_meter_box.set_valign(Gtk.Align.FILL) self.peak_meters = [] # Peak Meter Scale self.peak_meter_scale = PeakMeterScale() - self.peak_meter_scale.set_property("valign", Gtk.Align.FILL) - self.peak_meter_scale.set_property("halign", Gtk.Align.CENTER) + self.peak_meter_scale.set_halign(Gtk.Align.CENTER) + self.peak_meter_scale.set_valign(Gtk.Align.FILL) + self.peak_meter_scale.set_vexpand(True) + self.peak_meter_scale.set_hexpand(False) self.peak_meter_scale.set_margin_start(SPACING) - self.peak_meter_box.pack_end(self.peak_meter_scale, False, False, 0) - self.viewer_row_box.pack_end(self.peak_meter_box, False, False, 0) - self.peak_meter_scale.connect("configure-event", self.__peak_meter_scale_configure_event_cb) + self.peak_meter_box.append(self.peak_meter_scale) + self.viewer_row_box.append(self.peak_meter_box) + self.peak_meter_scale.connect("notify", self.__peak_meter_scale_notify_content_height_cb) # Buttons/Controls bbox = Gtk.Box() bbox.set_orientation(Gtk.Orientation.HORIZONTAL) - bbox.set_property("valign", Gtk.Align.CENTER) - bbox.set_property("halign", Gtk.Align.CENTER) + bbox.set_halign(Gtk.Align.CENTER) + bbox.set_valign(Gtk.Align.CENTER) bbox.set_margin_start(SPACING) bbox.set_margin_end(SPACING) - self.pack_end(bbox, False, False, 0) + self.append(bbox) self.guidelines_button = Gtk.MenuButton.new() - self.guidelines_button.props.image = Gtk.Image.new_from_icon_name("view-grid-symbolic", Gtk.IconSize.BUTTON) - self.guidelines_button.set_relief(Gtk.ReliefStyle.NONE) + self.guidelines_button.set_icon_name("view-grid-symbolic") + self.guidelines_button.set_has_frame(False) self.guidelines_button.set_tooltip_text(_("Select composition guidelines")) - bbox.pack_start(self.guidelines_button, False, False, 0) + bbox.append(self.guidelines_button) - self.start_button = Gtk.Button.new_from_icon_name("media-skip-backward-symbolic", - Gtk.IconSize.BUTTON) + self.start_button = Gtk.Button.new_from_icon_name("media-skip-backward-symbolic") self.start_button.connect("clicked", self._start_button_clicked_cb) - self.start_button.set_relief(Gtk.ReliefStyle.NONE) + self.start_button.set_has_frame(False) self.start_button.set_tooltip_text( _("Go to the beginning of the timeline")) self.start_button.set_sensitive(False) - bbox.pack_start(self.start_button, False, False, 0) + bbox.append(self.start_button) - self.back_button = Gtk.Button.new_from_icon_name("media-seek-backward-symbolic", - Gtk.IconSize.BUTTON) + self.back_button = Gtk.Button.new_from_icon_name("media-seek-backward-symbolic") - self.back_button.set_relief(Gtk.ReliefStyle.NONE) + self.back_button.set_has_frame(False) self.back_button.connect("clicked", self._back_cb) self.back_button.set_tooltip_text(_("Go back one second")) self.back_button.set_sensitive(False) - bbox.pack_start(self.back_button, False, False, 0) + bbox.append(self.back_button) self.playpause_button = PlayPauseButton() self.playpause_button.connect("play", self._play_button_cb) - bbox.pack_start(self.playpause_button, False, False, 0) + bbox.append(self.playpause_button) self.playpause_button.set_sensitive(False) - self.forward_button = Gtk.Button.new_from_icon_name("media-seek-forward-symbolic", - Gtk.IconSize.BUTTON) - self.forward_button.set_relief(Gtk.ReliefStyle.NONE) + self.forward_button = Gtk.Button.new_from_icon_name("media-seek-forward-symbolic") + self.forward_button.set_has_frame(False) self.forward_button.connect("clicked", self._forward_cb) self.forward_button.set_tooltip_text(_("Go forward one second")) self.forward_button.set_sensitive(False) - bbox.pack_start(self.forward_button, False, False, 0) + bbox.append(self.forward_button) - self.end_button = Gtk.Button.new_from_icon_name("media-skip-forward-symbolic", - Gtk.IconSize.BUTTON) - self.end_button.set_relief(Gtk.ReliefStyle.NONE) + self.end_button = Gtk.Button.new_from_icon_name("media-skip-forward-symbolic") + self.end_button.set_has_frame(False) self.end_button.connect("clicked", self._end_button_clicked_cb) self.end_button.set_tooltip_text( _("Go to the end of the timeline")) self.end_button.set_sensitive(False) - bbox.pack_start(self.end_button, False, False, 0) + bbox.append(self.end_button) self.timecode_entry = TimeWidget() self.timecode_entry.set_widget_value(0) self.timecode_entry.set_tooltip_text( _('Enter a timecode or frame number\nand press "Enter" to go to that position')) + self.timecode_entry.props.margin_top = 15 + self.timecode_entry.props.margin_bottom = 15 + self.timecode_entry.props.margin_start = 15 + self.timecode_entry.props.margin_end = 15 self.timecode_entry.connect_activate_event(self._entry_activate_cb) - self.timecode_entry.connect("key_press_event", self._entry_key_press_event_cb) - bbox.pack_start(self.timecode_entry, False, False, 15) + timecode_eventcontroller_key = Gtk.EventControllerKey() + timecode_eventcontroller_key.connect("key_pressed", self._entry_key_press_event_cb) + self.timecode_entry.add_controller(timecode_eventcontroller_key) + bbox.append(self.timecode_entry) - self.undock_button = Gtk.Button.new_from_icon_name("view-restore-symbolic", - Gtk.IconSize.BUTTON) + self.undock_button = Gtk.Button.new_from_icon_name("view-restore-symbolic") - self.undock_button.set_relief(Gtk.ReliefStyle.NONE) + self.undock_button.set_has_frame(False) self.undock_button.connect("clicked", self.undock_cb) self.undock_button.set_tooltip_text( _("Detach the viewer\nYou can re-attach it by closing the newly created window.")) - bbox.pack_start(self.undock_button, False, False, 0) + bbox.append(self.undock_button) - self.show_all() + self.show() # Create a hidden container for the clip trim preview video widget. self.hidden_chest = Gtk.Frame() + self.hidden_chest.hide() # It has to be added to the window, otherwise when we add # a video widget to it, it will create a new window! - self.pack_end(self.hidden_chest, False, False, 0) + self.append(self.hidden_chest) # Identify widgets for AT-SPI, making our test suite easier to develop # These will show up in sniff, accerciser, etc. - self.start_button.get_accessible().set_name("start_button") - self.back_button.get_accessible().set_name("back_button") - self.playpause_button.get_accessible().set_name("playpause_button") - self.forward_button.get_accessible().set_name("forward_button") - self.end_button.get_accessible().set_name("end_button") - self.timecode_entry.get_accessible().set_name("timecode_entry") - self.undock_button.get_accessible().set_name("undock_button") + self.start_button.set_name("start_button") + self.back_button.set_name("back_button") + self.playpause_button.set_name("playpause_button") + self.forward_button.set_name("forward_button") + self.end_button.set_name("end_button") + self.timecode_entry.set_name("timecode_entry") + self.undock_button.set_name("undock_button") self.buttons_container = bbox - self.external_vbox.show_all() + self.external_vbox.show() def _create_actions(self): self.action_group = Gio.SimpleActionGroup() @@ -416,7 +432,7 @@ class ViewerContainer(Gtk.Box, Loggable): for count, peak in enumerate(peak_values): self.peak_meters[count].update_peakmeter(peak) - def __corner_draw_cb(self, unused_widget, cr, lines, space, margin): + def __corner_draw_cb(self, unused_widget, cr, width, height, lines, space, margin): cr.set_line_width(1) marker_color = self.app.gui.get_style_context().lookup_color("borders") @@ -430,31 +446,35 @@ class ViewerContainer(Gtk.Box, Loggable): cr.line_to(space * (lines - i), space * lines) cr.stroke() - def __corner_enter_notify_cb(self, widget, unused_event): + def __corner_enter_notify_cb(self, controller, unused_x, unused_y): + widget = controller.get_widget() if not self.__cursor: - self.__cursor = Gdk.Cursor.new(Gdk.CursorType.BOTTOM_LEFT_CORNER) - widget.get_window().set_cursor(self.__cursor) + self.__cursor = Gdk.Cursor.new_from_name("sw-resize") + widget.set_cursor(self.__cursor) - def __corner_button_press_cb(self, unused_widget, event, hpane, vpane): - if event.button == 1: + def __corner_button_press_cb(self, controller, n_pressed, x, y, hpane, vpane): + if controller.get_button() == 1: # The mouse pointer position is w.r.t the root of the screen # whereas the positions of panes is w.r.t the root of the # mainwindow. We need to find the translation that takes us # from screen coordinate system to mainwindow coordinate system. - self.__translation = (event.x_root - hpane.get_position(), - event.y_root - vpane.get_position()) - def __corner_button_release_cb(self, unused_widget, unused_event): + # ToDo - Used to be event.x_root, no proper replacement found + self.__translation = (x - hpane.get_position(), + y - vpane.get_position()) + + def __corner_button_release_cb(self, unused_controller, n_pressed, x, y): self.__translation = None - def __corner_motion_notify_cb(self, unused_widget, event, hpane, vpane): + def __corner_motion_notify_cb(self, controller, x, y, hpane, vpane): if self.__translation is None: return - hpane.set_position(event.x_root - self.__translation[0]) - vpane.set_position(event.y_root - self.__translation[1]) + # ToDo - Used to be event.x_root, no proper replacement found + hpane.set_position(x - self.__translation[0]) + vpane.set_position(y - self.__translation[1]) - def __peak_meter_scale_configure_event_cb(self, unused_widget, unused_event): + def __peak_meter_scale_notify_content_height_cb(self, unused_widget, unused_event): container_height = self.viewer_row_box.get_allocated_height() margins = self.peak_meter_box.get_allocated_height() - self.peak_meter_scale.get_bar_height() + SPACING * 4 @@ -473,9 +493,9 @@ class ViewerContainer(Gtk.Box, Loggable): self.app.gui.editor.timeline_ui.timeline.scroll_to_playhead( align=Gtk.Align.CENTER, when_not_in_view=True) - def _entry_key_press_event_cb(self, widget, event): + def _entry_key_press_event_cb(self, controller, keyval, keycode, state): """Handles the key press events in the timecode_entry widget.""" - if event.keyval == Gdk.KEY_Escape: + if keyval == Gdk.KEY_Escape: self.app.gui.editor.focus_timeline() # Active Timeline calllbacks @@ -535,25 +555,27 @@ class ViewerContainer(Gtk.Box, Loggable): self.viewer_row_box.remove(self.target) self.__create_new_viewer() self.buttons_container.set_margin_bottom(SPACING) - self.external_vbox.pack_end(self.buttons_container, False, False, 0) + self.external_vbox.append(self.buttons_container) self.undock_button.hide() self.fullscreen_button = Gtk.ToggleButton() - fullscreen_image = Gtk.Image.new_from_icon_name( - "view-fullscreen-symbolic", Gtk.IconSize.BUTTON) - self.fullscreen_button.set_image(fullscreen_image) + self.fullscreen_button.set_icon_name("view-fullscreen-symbolic") self.fullscreen_button.set_tooltip_text( _("Show this window in fullscreen")) - self.fullscreen_button.set_relief(Gtk.ReliefStyle.NONE) - self.buttons_container.pack_end( - self.fullscreen_button, expand=False, fill=False, padding=6) + self.fullscreen_button.set_has_frame(False) + self.fullscreen_button.set_margin_top(6) + self.fullscreen_button.set_margin_bottom(6) + self.fullscreen_button.set_margin_start(6) + self.fullscreen_button.set_margin_end(6) + self.buttons_container.append(self.fullscreen_button) self.fullscreen_button.show() self.fullscreen_button.connect("toggled", self._toggle_fullscreen_cb) self.external_window.show() self.hide() - self.external_window.move(self.settings.viewerX, self.settings.viewerY) - self.external_window.resize( + # TODO:Porting find replacement + # self.external_window.move(self.settings.viewerX, self.settings.viewerY) + self.external_window.set_default_size( self.settings.viewerWidth, self.settings.viewerHeight) if self.project: self.project.pipeline.pause() @@ -581,10 +603,10 @@ class ViewerContainer(Gtk.Box, Loggable): self.__create_new_viewer() self.undock_button.show() - self.fullscreen_button.destroy() + self.buttons_container.remove(self.fullscreen_button) self.external_vbox.remove(self.buttons_container) self.buttons_container.set_margin_bottom(0) - self.pack_end(self.buttons_container, False, False, 0) + self.append(self.buttons_container) self.show() self.external_window.hide() @@ -596,7 +618,6 @@ class ViewerContainer(Gtk.Box, Loggable): if widget.get_active(): self.external_window.hide() # GTK doesn't let us fullscreen utility windows - self.external_window.set_type_hint(Gdk.WindowTypeHint.NORMAL) self.external_window.show() self.external_window.fullscreen() widget.set_tooltip_text(_("Exit fullscreen mode")) @@ -604,7 +625,6 @@ class ViewerContainer(Gtk.Box, Loggable): self.external_window.unfullscreen() widget.set_tooltip_text(_("Show this window in fullscreen")) self.external_window.hide() - self.external_window.set_type_hint(Gdk.WindowTypeHint.UTILITY) self.external_window.show() def _position_cb(self, unused_pipeline, position): @@ -642,7 +662,7 @@ class ViewerContainer(Gtk.Box, Loggable): if widget: self.warning("The previous trim preview video widget should have been removed already") self.hidden_chest.remove(widget) - self.hidden_chest.add(sink_widget) + self.hidden_chest.set_child(sink_widget) sink_widget.show() self.trim_pipeline.connect("state-change", self._state_change_cb) self.trim_pipeline.set_simple_state(Gst.State.PAUSED) @@ -710,7 +730,7 @@ class ViewerContainer(Gtk.Box, Loggable): self.app.simple_uninhibit(ViewerContainer.INHIBIT_REASON) -class ViewerWidget(Gtk.AspectFrame, Loggable): +class ViewerWidget(Gtk.Frame, Loggable): """Container responsible with enforcing the aspect ratio. Args: @@ -719,38 +739,34 @@ class ViewerWidget(Gtk.AspectFrame, Loggable): """ def __init__(self, video_widget): - Gtk.AspectFrame.__init__(self, xalign=0.5, yalign=0.5, ratio=4 / 3, - border_width=SPACING, obey_child=False) + Gtk.Frame.__init__(self) Loggable.__init__(self) # The width and height used when snapping the child widget size. self.videowidth = 0 self.videoheight = 0 + self.ratio = 4 / 3 # Sequence of floats representing sizes where the viewer size snaps. # The project natural video size is 1, double size is 2, etc. self.snaps = [] - # Set the shadow to None, otherwise it will take space and the - # child widget size snapping will be a bit off. - self.set_shadow_type(Gtk.ShadowType.NONE) - - self.add(video_widget) + self.set_child(video_widget) # We keep the ViewerWidget hidden initially, or the desktop wallpaper # would show through the non-double-buffered widget! self.hide() def switch_widget(self, widget): - child = self.get_child() - if child: - self.remove(child) - self.add(widget) + # child = self.get_child() + # if child: + # self.remove(child) + self.set_child(widget) def update_aspect_ratio(self, project): """Forces the DAR of the project on the child widget.""" ratio_fraction = project.get_dar() self.debug("Updating aspect ratio to %r", ratio_fraction) - self.props.ratio = float(ratio_fraction) + self.ratio = float(ratio_fraction) self.videowidth = project.videowidth self.videoheight = project.videoheight @@ -761,22 +777,16 @@ class ViewerWidget(Gtk.AspectFrame, Loggable): self.snaps.append(1 / divisor) self.snaps += list(range(1, 10)) - def do_get_preferred_width(self): - minimum, unused_natural = Gtk.AspectFrame.do_get_preferred_width(self) - # Do not let a chance for Gtk to choose video natural size - # as we want to have full control - return minimum, minimum + 1 - - def do_get_preferred_height(self): - minimum, unused_natural = Gtk.AspectFrame.do_get_preferred_height(self) + def do_measure(self, orientation, for_size): + minimum, unused_natural, unused_minimum_baseline, unused_natural_baseline = Gtk.Widget.do_measure(self, orientation, for_size) # Do not let a chance for Gtk to choose video natural size # as we want to have full control - return minimum, minimum + 1 + return minimum, minimum + 1, -1, -1 def do_compute_child_allocation(self, allocation): """Snaps the size of the child depending on the project size.""" # Start with the max possible allocation. - Gtk.AspectFrame.do_compute_child_allocation(self, allocation) + Gtk.Frame.do_compute_child_allocation(self, allocation) if not self.videowidth: return @@ -800,8 +810,10 @@ class ViewerWidget(Gtk.AspectFrame, Loggable): allocation.width = self.videowidth * snap allocation.height = self.videoheight * snap full = self.get_allocation() - allocation.x = full.x + self.props.xalign * (full.width - allocation.width) - allocation.y = full.y + self.props.yalign * (full.height - allocation.height) + xalign = 0.5 + yalign = 0.5 + allocation.x = full.x + xalign * (full.width - allocation.width) + allocation.y = full.y + yalign * (full.height - allocation.height) class PlayPauseButton(Gtk.Button, Loggable): @@ -815,8 +827,8 @@ class PlayPauseButton(Gtk.Button, Loggable): Gtk.Button.__init__(self) Loggable.__init__(self) self.image = Gtk.Image() - self.add(self.image) - self.set_relief(Gtk.ReliefStyle.NONE) + self.set_child(self.image) + self.set_has_frame(False) self.playing = False self.set_play() @@ -831,15 +843,13 @@ class PlayPauseButton(Gtk.Button, Loggable): def set_play(self): self.log("Displaying the play image") self.playing = True - self.set_image(Gtk.Image.new_from_icon_name( - "media-playback-start-symbolic", Gtk.IconSize.BUTTON)) + self.set_icon_name("media-playback-start-symbolic") self.set_tooltip_text(_("Play")) self.playing = False def set_pause(self): self.log("Displaying the pause image") self.playing = False - self.set_image(Gtk.Image.new_from_icon_name( - "media-playback-pause-symbolic", Gtk.IconSize.BUTTON)) + self.set_icon_name("media-playback-pause-symbolic") self.set_tooltip_text(_("Pause")) self.playing = True diff --git a/plugins/console/console.py b/plugins/console/console.py index 4f897bc3cfdfffb13e57639d68e35aa20fc5e54f..6933bd7c5ac7c74c03a4c6f3fa4022291b7f171f 100644 --- a/plugins/console/console.py +++ b/plugins/console/console.py @@ -160,11 +160,11 @@ class Console(GObject.GObject, Peas.Activatable): def add_menu_item(self): """Inserts a menu item into the Pitivi menu.""" menu = self.app.gui.editor.builder.get_object("menu_box") - self.menu_item = Gtk.ModelButton.new() - self.menu_item.props.text = _("Developer Console") + self.menu_item = Gtk.Button.new() + self.menu_item.props.label = _("Developer Console") self.menu_item.set_action_name("app.open_console") - menu.add(self.menu_item) + menu.append(self.menu_item) def remove_menu_item(self): """Removes a menu item from the Pitivi menu.""" @@ -186,8 +186,8 @@ class Console(GObject.GObject, Peas.Activatable): self.window.set_default_size(600, 400) self.window.set_title(_("Pitivi Console")) - self.window.connect("delete-event", self.__delete_event_cb) - self.window.add(self.terminal) + self.window.connect("close_request", self.__close_request_cb) + self.window.set_child(self.terminal) def _create_welcome_message(self, namespace): console_plugin_info = self.app.plugin_manager.get_plugin_info("console") @@ -236,12 +236,12 @@ class Console(GObject.GObject, Peas.Activatable): self.terminal.set_font(settings.consoleFont) def __menu_item_activate_cb(self, unused_data, unused_param): - self.window.show_all() + self.window.show() self.window.set_keep_above(True) def __eof_cb(self, unused_widget): self.window.hide() return True - def __delete_event_cb(self, unused_widget, unused_data): + def __close_request_cb(self, unused_data): return self.window.hide_on_delete() diff --git a/plugins/console/widgets.py b/plugins/console/widgets.py index 4ee32ca3efc9bc25aa4350617918e2f77a72e068..3f900f985dc5bef26b43080c095e67659ea65dd6 100644 --- a/plugins/console/widgets.py +++ b/plugins/console/widgets.py @@ -62,9 +62,11 @@ class ConsoleWidget(Gtk.ScrolledWindow): buf = ConsoleBuffer(namespace, welcome_message) self._view.set_buffer(buf) self._view.set_editable(True) - self.add(self._view) + self.set_child(self._view) - self._view.connect("key-press-event", self.__key_press_event_cb) + eventcontroller_key = Gtk.EventControllerKey() + eventcontroller_key.connect("key-pressed", self.__key_press_event_cb) + self._view.add_controller(eventcontroller_key) buf.connect("mark-set", self.__mark_set_cb) buf.connect("insert-text", self.__insert_text_cb) @@ -136,30 +138,30 @@ class ConsoleWidget(Gtk.ScrolledWindow): self._view.get_buffer().error.set_property("foreground-rgba", color) # pylint: disable=too-many-return-statements - def __key_press_event_cb(self, view, event): - buf = view.get_buffer() - state = event.state & Gtk.accelerator_get_default_mod_mask() + def __key_press_event_cb(self, controller, keyval, keycode, state): + buf = controller.get_widget().get_buffer() + state = state & Gtk.accelerator_get_default_mod_mask() ctrl = state & Gdk.ModifierType.CONTROL_MASK - if event.keyval == Gdk.KEY_Return: + if keyval == Gdk.KEY_Return: buf.process_command_line() return True - if event.keyval in (Gdk.KEY_KP_Down, Gdk.KEY_Down): + if keyval in (Gdk.KEY_KP_Down, Gdk.KEY_Down): buf.history.down(buf.get_command_line()) return True - if event.keyval in (Gdk.KEY_KP_Up, Gdk.KEY_Up): + if keyval in (Gdk.KEY_KP_Up, Gdk.KEY_Up): buf.history.up(buf.get_command_line()) return True - if event.keyval in (Gdk.KEY_KP_Left, Gdk.KEY_Left, Gdk.KEY_BackSpace): + if keyval in (Gdk.KEY_KP_Left, Gdk.KEY_Left, Gdk.KEY_BackSpace): return buf.is_cursor(at=True) - if event.keyval in (Gdk.KEY_KP_Home, Gdk.KEY_Home): + if keyval in (Gdk.KEY_KP_Home, Gdk.KEY_Home): buf.place_cursor(buf.get_iter_at_mark(buf.prompt_mark)) return True - if (ctrl and event.keyval == Gdk.KEY_d) or event.keyval == Gdk.KEY_Escape: + if (ctrl and keyval == Gdk.KEY_d) or keyval == Gdk.KEY_Escape: return self.emit("eof") return False diff --git a/tests/common.py b/tests/common.py index 3a7dbe297ba7ef00c052083f5e76a45dd86b9114..1f5d970c535dd05df30ecc1b2ccffe72c7345d9d 100644 --- a/tests/common.py +++ b/tests/common.py @@ -51,6 +51,7 @@ from pitivi.utils.proxy import ProxyingStrategy from pitivi.utils.proxy import ProxyManager from pitivi.utils.timeline import Selected from pitivi.utils.timeline import Zoomable +from pitivi.utils.ui import get_widget_children def handle_uncaught_exception_func(exctype, value, trace): @@ -477,7 +478,7 @@ class TestCase(unittest.TestCase, Loggable): expect_selected) self.assertEqual(ges_clip.selected.selected, expect_selected) - for child in ges_clip.ui.get_children(): + for child in get_widget_children(ges_clip.ui): if not hasattr(child, "selected"): continue @@ -513,17 +514,24 @@ class TestCase(unittest.TestCase, Loggable): def check_priorities_and_positions(self, timeline, ges_layers, expected_priorities): layers_vbox = timeline.layout.layers_vbox + mini_layers_vbox = timeline.mini_layout.layers_vbox # Check the layers priorities. priorities = [ges_layer.props.priority for ges_layer in ges_layers] self.assertListEqual(priorities, expected_priorities) + expected_positions = [priority * 2 + 1 + for priority in expected_priorities] + # Check the positions of the Layer widgets. positions = [layers_vbox.child_get_property(ges_layer.ui, "position") for ges_layer in ges_layers] - expected_positions = [priority * 2 + 1 - for priority in expected_priorities] - self.assertListEqual(positions, expected_positions, layers_vbox.get_children()) + self.assertListEqual(positions, expected_positions, get_widget_children(layers_vbox)) + + # Check the positions of the MiniLayer widgets. + positions = [mini_layers_vbox.child_get_property(ges_layer.mini_ui, "position") + for ges_layer in ges_layers] + self.assertListEqual(positions, expected_positions, get_widget_children(mini_layers_vbox)) # Check the positions of the LayerControl widgets. controls_vbox = timeline._layers_controls_vbox @@ -534,15 +542,21 @@ class TestCase(unittest.TestCase, Loggable): # Check the number of the separators. count = len(ges_layers) + 1 self.assertEqual(len(timeline._separators), count) - controls_separators, layers_separators = list(zip(*timeline._separators)) - # Check the positions of the LayerControl separators. expected_positions = [2 * index for index in range(count)] + controls_separators, layers_separators, mini_layers_separators = list(zip(*timeline._separators)) + + # Check the positions of the Layer separators. positions = [layers_vbox.child_get_property(separator, "position") for separator in layers_separators] self.assertListEqual(positions, expected_positions) - # Check the positions of the Layer separators. + # Check the positions of the MiniLayer separators. + positions = [mini_layers_vbox.child_get_property(separator, "position") + for separator in mini_layers_separators] + self.assertListEqual(positions, expected_positions) + + # Check the positions of the LayerControl separators. positions = [controls_vbox.child_get_property(separator, "position") for separator in controls_separators] self.assertListEqual(positions, expected_positions) @@ -662,4 +676,5 @@ def create_test_clip(clip_type): clip = clip_type() clip.selected = Selected() clip.ui = None + clip.mini_ui = None return clip diff --git a/tests/plugins/test_alpha.ui b/tests/plugins/test_alpha.ui index e231be7b2e72ef3f2bf08b0c70482c180da04d68..34c5173451c9c4bbca89413cf4d7e3061f0cbea5 100644 --- a/tests/plugins/test_alpha.ui +++ b/tests/plugins/test_alpha.ui @@ -14,20 +14,13 @@ 1 10
- - True - False - edit-clear-all-symbolic - - - True - False - edit-clear-all-symbolic - True False - 6 + 6 + 6 + 6 + 6 6 6 @@ -36,11 +29,11 @@ False Alpha: 0 + + 1 + 1 + - - 1 - 1 -
@@ -52,11 +45,12 @@ 1 2 left + True + + 2 + 1 + - - 2 - 1 - @@ -64,11 +58,11 @@ False Black sensitivity: 0 + + 1 + 0 + - - 1 - 0 - @@ -80,11 +74,12 @@ 2 0 left + True + + 2 + 0 + - - 2 - 0 - @@ -92,40 +87,40 @@ True True True - none + False + + 0 + 0 + - - 0 - 0 - True True True - image1 - none + edit-clear-all-symbolic + False top + + 3 + 0 + - - 3 - 0 - True True True - image2 - none + edit-clear-all-symbolic + False top + + 3 + 1 + - - 3 - 1 - diff --git a/tests/test_medialibrary.py b/tests/test_medialibrary.py index dfd645db2fb4e7824045f83339da79c053d65683..6ffe50035062693ef085ce93fdb92040bada48db 100644 --- a/tests/test_medialibrary.py +++ b/tests/test_medialibrary.py @@ -33,6 +33,7 @@ from pitivi.project import ProjectManager from pitivi.utils.misc import ASSET_DURATION_META from pitivi.utils.misc import asset_get_duration from pitivi.utils.proxy import ProxyingStrategy +from pitivi.utils.ui import get_widget_children from pitivi.utils.validate import create_event from tests import common @@ -541,12 +542,12 @@ class TestTaggingAssets(BaseTestMediaLibrary): def get_tags_list(self): box = self.medialibrary.tags_popover.get_child() - popover_widgets = box.get_children() + popover_widgets = get_widget_children(box) return popover_widgets[1] def assert_tags_popover(self, tags_state): box = self.medialibrary.tags_popover.get_child() - popover_widgets = box.get_children() + popover_widgets = get_widget_children(box) tags_list = popover_widgets[1] for row_widget in tags_list: diff --git a/tests/test_timeline_elements.py b/tests/test_timeline_elements.py index dfb1be41c510980e83cb8ec1a43a66eead351782..bddb7ce85f8ee2a3265ee0b5bbd65c3e792e7940 100644 --- a/tests/test_timeline_elements.py +++ b/tests/test_timeline_elements.py @@ -29,6 +29,7 @@ from matplotlib.backend_bases import MouseEvent from pitivi.timeline.elements import GES_TYPE_UI_TYPE from pitivi.undo.undo import UndoableActionLog from pitivi.utils.timeline import SELECT +from pitivi.utils.timeline import UNSELECT from pitivi.utils.timeline import Zoomable from pitivi.utils.ui import LAYER_HEIGHT from tests import common @@ -513,3 +514,18 @@ class TestClip(common.TestCase): ges_object = GObject.new(gtype) widget = widget_class(ges_layer.ui, ges_object) self.assertEqual(ges_object.ui, widget, widget_class) + + def test_mini_selection(self): + """Checks whether both ui and mini_ui gets selected.""" + timeline_container = common.create_timeline_container() + timeline = timeline_container.timeline + + clip1, = self.add_clips_simple(timeline, 1) + + timeline.selection.set_selection([clip1], SELECT) + self.assertTrue(clip1.ui.get_state_flags() & Gtk.StateFlags.SELECTED) + self.assertTrue(clip1.mini_ui.get_state_flags() & Gtk.StateFlags.SELECTED) + + timeline.selection.set_selection([clip1], UNSELECT) + self.assertFalse(clip1.ui.get_state_flags() & Gtk.StateFlags.SELECTED) + self.assertFalse(clip1.mini_ui.get_state_flags() & Gtk.StateFlags.SELECTED) diff --git a/tests/test_timeline_layer.py b/tests/test_timeline_layer.py index 68aaef965ffaa76f4139755db8ad090fccf26a91..2d2485823813388394bcaf934840cbd6351bad4a 100644 --- a/tests/test_timeline_layer.py +++ b/tests/test_timeline_layer.py @@ -20,7 +20,7 @@ from unittest import mock from gi.repository import GES from pitivi.timeline.layer import AUDIO_ICONS -from pitivi.timeline.layer import Layer +from pitivi.timeline.layer import FullLayer from pitivi.timeline.layer import VIDEO_ICONS from pitivi.utils.ui import LAYER_HEIGHT from tests import common @@ -31,7 +31,7 @@ class TestLayerControl(common.TestCase): def test_name(self): timeline = mock.MagicMock() ges_layer = GES.Layer() - layer = Layer(ges_layer, timeline) + layer = FullLayer(ges_layer, timeline) self.assertEqual(layer.get_name(), "Layer 0", "Default name generation failed") ges_layer.set_meta("audio::name", "a") @@ -46,7 +46,7 @@ class TestLayerControl(common.TestCase): def test_name_meaningful(self): timeline = mock.MagicMock() ges_layer = GES.Layer() - layer = Layer(ges_layer, timeline) + layer = FullLayer(ges_layer, timeline) layer.set_name("Layer 0x") self.assertEqual(layer.get_name(), "Layer 0x") @@ -160,7 +160,7 @@ class TestLayer(common.TestCase): # the layer will use check_media_types which updates the # height of layer.control_ui, which now it should not be set. self.assertFalse(hasattr(ges_layer, "control_ui")) - unused_layer = Layer(ges_layer, timeline) + unused_layer = FullLayer(ges_layer, timeline) @common.setup_timeline def test_layer_heights(self): diff --git a/tests/test_trackerperspective.py b/tests/test_trackerperspective.py index 2a0af20a6152f344836bdc3994156e08ba9939e1..2d7246a08d7e5f32c9d7f0035b872d661df7122d 100644 --- a/tests/test_trackerperspective.py +++ b/tests/test_trackerperspective.py @@ -22,6 +22,7 @@ from gi.repository import GES from pitivi.check import MISSING_SOFT_DEPS from pitivi.trackerperspective import ObjectManager +from pitivi.utils.ui import get_listbox_children from tests import common @@ -39,7 +40,7 @@ class TestCoverObjectPopover(common.TestCase): expander.cover_object_button.clicked() self.assertTrue(expander.cover_popover.props.visible) # Only one row containing the Track Objects button should exist. - self.assertEqual(len(expander.cover_popover.listbox.get_children()), 1) + self.assertEqual(len(get_listbox_children(expander.cover_popover.listbox)), 1) expander.cover_object_button.clicked() self.assertFalse(expander.cover_popover.props.visible) @@ -54,7 +55,7 @@ class TestCoverObjectPopover(common.TestCase): expander.cover_object_button.clicked() self.assertTrue(expander.cover_popover.props.visible) # Two rows for two object and one for Track Objects. - self.assertEqual(len(expander.cover_popover.listbox.get_children()), 3) + self.assertEqual(len(get_listbox_children(expander.cover_popover.listbox)), 3) self.assertEqual(len(clip.get_top_effects()), 0) expander.cover_popover.listbox.get_row_at_index(0).emit("activate") @@ -64,7 +65,7 @@ class TestCoverObjectPopover(common.TestCase): expander.cover_object_button.clicked() self.assertTrue(expander.cover_popover.props.visible) # One row for the uncovered object and one for the Track Objects button. - self.assertEqual(len(expander.cover_popover.listbox.get_children()), 2) + self.assertEqual(len(get_listbox_children(expander.cover_popover.listbox)), 2) class TestObjectManager(common.TestCase): diff --git a/tests/test_undo_timeline.py b/tests/test_undo_timeline.py index 86eaead4e61919da22a680e07fa1cf531b08018e..1dcdf5a8b0010f7516f6146e24dfd7701dc1d93f 100644 --- a/tests/test_undo_timeline.py +++ b/tests/test_undo_timeline.py @@ -25,7 +25,7 @@ from gi.repository import Gst from gi.repository import GstController from gi.repository import Gtk -from pitivi.timeline.layer import Layer +from pitivi.timeline.layer import FullLayer from pitivi.undo.base import PropertyChangedAction from pitivi.undo.project import AssetAddedAction from pitivi.undo.timeline import ClipAdded @@ -320,7 +320,7 @@ class TestLayerObserver(common.TestCase): @common.setup_timeline def test_layer_renamed(self): - layer = Layer(self.layer, timeline=mock.Mock()) + layer = FullLayer(self.layer, timeline=mock.Mock()) self.assertIsNone(layer._name_if_set()) with self.app.action_log.started("change layer name"):