Merge lp:~artem-anufrij/audience/library-view into lp:~audience-members/audience/trunk
- library-view
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Felipe Escoto |
Approved revision: | 710 |
Merged at revision: | 657 |
Proposed branch: | lp:~artem-anufrij/audience/library-view |
Merge into: | lp:~audience-members/audience/trunk |
Diff against target: |
1071 lines (+826/-16) 13 files modified
data/org.pantheon.audience.gschema.xml (+10/-0) src/Audience.vala (+18/-1) src/CMakeLists.txt (+6/-0) src/Objects/Video.vala (+196/-0) src/Services/LibraryManager.vala (+183/-0) src/Services/Thubnailer.vala (+52/-0) src/Settings.vala (+3/-1) src/Widgets/LibraryItem.vala (+100/-0) src/Widgets/LibraryPage.vala (+120/-0) src/Widgets/NavigationButton.vala (+35/-0) src/Widgets/PlayerPage.vala (+8/-0) src/Widgets/WelcomePage.vala (+25/-2) src/Window.vala (+70/-12) |
To merge this branch: | bzr merge lp:~artem-anufrij/audience/library-view |
Related bugs: | |
Related blueprints: |
Organise movies
(Medium)
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Felipe Escoto (community) | Approve | ||
Danielle Foré | ux | Approve | |
Cody Garver | Needs Fixing | ||
Review via email: mp+306018@code.launchpad.net |
Commit message
Create a library view
Description of the change
Danielle Foré (danrabbit) wrote : | # |
Danielle Foré (danrabbit) wrote : | # |
* Instead of the magnifying glass icon, use "folder-videos"
* re: question in slack about if library >0, I think that should be the eventual behavior, but I think that would require moving the "resume last video" (and maybe "open file") items into the library so that they remain accessible even if you have a library.
Artem Anufrij (artem-anufrij) wrote : | # |
DONE: I don't think the words "Back to" are necessary. On other back buttons we just put the thing you're going back to. So "Library" and "Welcome Screen" are probably fine.
Artem Anufrij (artem-anufrij) wrote : | # |
DONE: The window title doesn't seem to update when you change screens. It should show "Videos" on the Library and Welcome pages
Danielle Foré (danrabbit) wrote : | # |
If I select "Resume last video" and then use the back button to return to the Welcome, the video will continue to play in the background (I can hear it). This doesn't happen when choosing a video from the library
Adam Bieńkowski (donadigo) wrote : | # |
I commented on some things in the code, mostly code style issues, but also some different ones about using native functions.
Artem Anufrij (artem-anufrij) wrote : | # |
@Daniel: Done
@Adam: Done
thanks for your feedback....
Danielle Foré (danrabbit) wrote : | # |
Please don't add any more features until we debug the existing ones ;)
* Keyboard navigation doesn't seem to work
* I now have a "TV Shows" heading even though I don't have any TV shows
Danielle Foré (danrabbit) wrote : | # |
* Please remove the h4 from video titles. This style class is usually used for category or section headers
* Add 12px row spacing to the library item grid
* You shouldn't have more than one h1 on a single screen, so for section titles, h2 is probably more appropriate
Danielle Foré (danrabbit) wrote : | # |
I really don't like this "tv-shows-
Danielle Foré (danrabbit) wrote : | # |
* Can we add the "card" class to cover images? this gives them a nice little box shadow
* It looks like your flowbox has valign set to fill. This causes a funny looking selection when you have a small library. this should probably be set to either CENTER or START
Danielle Foré (danrabbit) wrote : | # |
* Should probably also add row and column spacing of 12 to the flowbox to make sure item labels don't crash into each other.
Danielle Foré (danrabbit) wrote : | # |
Re-posted outstanding issues since Launchpad doesn't have cool checkboxes like GitHub:
* No arrow key navigation in the library
* Alt + left arrow should activate the back button
Danielle Foré (danrabbit) wrote : | # |
Rico wants us to be using GObject-style construction, I think we should make an effort to do this for new code https:/
Danielle Foré (danrabbit) wrote : | # |
If I rename a file in Files, Audience won't update until I close it. Since it can't find the file, clicking the item can't play it. We should probably just autoremove missing files
Danielle Foré (danrabbit) wrote : | # |
It looks like the keyboard navigation not working is a problem with other views and isn't just in this branch. Tracking it separately here: https:/
That shouldn't be a blocker on this branch
Danielle Foré (danrabbit) wrote : | # |
Let's change "Open Library" to "Browse Library" for clarity so we're not implying that a new window will appear :)
Danielle Foré (danrabbit) wrote : | # |
Getting this error:
[FATAL 14:59:35.076098] Video.vala:124: Failed to open '/home/
I don't have ~/.cache/audience. If the folder doesn't exist, we should create it
Danielle Foré (danrabbit) wrote : | # |
Hm it seems that the item is only removed when clicked. Can we use GLib.FileMonitor or something instead so that it updates live?
Danielle Foré (danrabbit) wrote : | # |
If I delete a file, it doesn't seem to clear the associated cover image from .cache
Danielle Foré (danrabbit) wrote : | # |
Made some diff comments
Danielle Foré (danrabbit) wrote : | # |
We should add margin_bottom = 12 to the label in a library item. That way the selection from flowbox will look nicer
Felipe Escoto (philip.scott) wrote : | # |
Made some comments on the diff about code style, but also:
- The library shouldn't be loaded into memory unless the user requests it. This is currently causing a long startup time when you just click on a file to open ;)
Non-blockers, but would be nice on this MR:
- Load the library async: Even if we are not loading the library on startup, clicking on it would still take a bit of time where it would look like its' frozen. Loading it async should also fix that
- HD Thumbnails. Because high res is best res
Artem Anufrij (artem-anufrij) wrote : | # |
Thank you guys for your feedback.
@ Felipe:
* how can I hide the library button (on welcome screen) if I don't check the files?
* library loads already async
public void begin_scan () {
detect_
}
poster check is async, too:
public async void initialize_poster ()
Djax (parnold-x) wrote : | # |
async still blocks if there is no yield.
Maybe just do the checking for viedo files at startup and finish the initalization with poster and stuff on entering the library.
Artem Anufrij (artem-anufrij) wrote : | # |
@Djax: Thats a good idea. I will do that.
Danielle Foré (danrabbit) wrote : | # |
Re-posting outstanding issues again:
* We should add margin_bottom = 12 to the library item. That way the selection from flowbox will look nicer and match the top
* If I delete a file, it doesn't seem to clear the associated cover image from .cache
* It seems that a missing item is only removed when clicked. Can we use GLib.FileMonitor or something instead so that it updates live?
Danielle Foré (danrabbit) wrote : | # |
The spinner should probably be packed into a grid (or another stylable widget) and have the card class and set size request on that widget so the spinner itself can be a sane size like 32px
Danielle Foré (danrabbit) wrote : | # |
I'm gonna give a UX approve. I can't really find anything else to complain about right now ;) Looks good. Thank you for your hard work!
I do have some diff comments for code style :)
Danielle Foré (danrabbit) wrote : | # |
Oops found an issue:
1. Have an empty videos folder
2. Open Audience
3. see that browse library is not available
4. Add a movie to videos folder
Expectation: The Browse Library button appears
Reality: Nothing happens
Cody Garver (codygarver) wrote : | # |
I found 2 issues:
* If you start playing a video from the library, then return to the library by clicking "Back", then hit the Play key on the keyboard, it plays the audio from the video you exited. Maybe it's not actually closing the video?
* If you start playing a video, then return to the library by clicking "Back", then play the same video, it momentarily remembers where you were at in the video but then it starts playing it from the beginning. It should always remember the playback progress.
Danielle Foré (danrabbit) : | # |
Danielle Foré (danrabbit) wrote : | # |
1. Start with something in your videos folder
2. Open Videos; See that the "Browse Library" option is available
3. Remove all items from your videos folder
Expectation: The "Browse Library" option goes away
Reality: It stays and clicking it shoes an empty library
- 704. By Artem Anufrij
-
hide welcome button if all video files were deleted
Danielle Foré (danrabbit) wrote : | # |
Alright, that seems pretty damn robust. Really having trouble finding new and exciting ways to break things. Looks good :)
Felipe Escoto (philip.scott) wrote : | # |
Thank you for doing my requested fixes :) Now i just have one blocker before merging:
If you clear your cache on ~/.cache/
Felipe Escoto (philip.scott) wrote : | # |
Also made some comments regarding code style, but overall it looks very nice! Great job :)
- 705. By Artem Anufrij
-
code style
- 706. By Artem Anufrij
-
improve multithreading
- 707. By Artem Anufrij
-
removed unused code
- 708. By Artem Anufrij
-
build_path -> build_filename
- 709. By Artem Anufrij
-
fixed: Replay button shows last played video
- 710. By Artem Anufrij
-
fixed last played video button
Felipe Escoto (philip.scott) wrote : | # |
Thank you for dealing with our requests! :) You rock man
Preview Diff
1 | === modified file 'data/org.pantheon.audience.gschema.xml' |
2 | --- data/org.pantheon.audience.gschema.xml 2016-03-14 13:10:18 +0000 |
3 | +++ data/org.pantheon.audience.gschema.xml 2016-09-22 18:15:35 +0000 |
4 | @@ -49,5 +49,15 @@ |
5 | <summary>Cause the audience window to stay on top by default when it's playing</summary> |
6 | <description>Set the option to keep the audience window above all other windows by default when audience is currently playing</description> |
7 | </key> |
8 | + <key name="library-folder" type="s"> |
9 | + <default>""</default> |
10 | + <summary>Library folder</summary> |
11 | + <description></description> |
12 | + </key> |
13 | + <key name="poster-names" type="as"> |
14 | + <default>['poster.jpg','Poster.jpg','cover.jpg','Cover.jpg']</default> |
15 | + <summary>Poster file names</summary> |
16 | + <description></description> |
17 | + </key> |
18 | </schema> |
19 | </schemalist> |
20 | |
21 | === modified file 'src/Audience.vala' |
22 | --- src/Audience.vala 2016-08-28 04:19:38 +0000 |
23 | +++ src/Audience.vala 2016-09-22 18:15:35 +0000 |
24 | @@ -65,7 +65,8 @@ |
25 | translate_url = "https://translations.launchpad.net/audience"; |
26 | |
27 | about_authors = { "Cody Garver <cody@elementaryos.org>", |
28 | - "Tom Beckmann <tom@elementaryos.org>" }; |
29 | + "Tom Beckmann <tom@elementaryos.org>", |
30 | + "Artem Anufrij <artem.anufrij@live.de>" }; |
31 | about_translators = _("translator-credits"); |
32 | about_license_type = Gtk.License.GPL_3_0; |
33 | } |
34 | @@ -89,6 +90,18 @@ |
35 | if (settings.last_folder == "-1") { |
36 | settings.last_folder = Environment.get_user_special_dir (GLib.UserDirectory.VIDEOS); |
37 | } |
38 | + if (settings.library_folder == "") { |
39 | + settings.library_folder = GLib.Environment.get_user_special_dir (GLib.UserDirectory.VIDEOS); |
40 | + } |
41 | + |
42 | + try { |
43 | + File cache = File.new_for_path (get_cache_directory ()); |
44 | + if (!cache.query_exists ()) { |
45 | + cache.make_directory (); |
46 | + } |
47 | + } catch (Error e) { |
48 | + error (e.message); |
49 | + } |
50 | |
51 | mainwindow = new Window (); |
52 | mainwindow.application = this; |
53 | @@ -96,6 +109,10 @@ |
54 | } |
55 | } |
56 | |
57 | + public string get_cache_directory () { |
58 | + return GLib.Path.build_filename(GLib.Environment.get_user_cache_dir (), exec_name); |
59 | + } |
60 | + |
61 | //the application was requested to open some files |
62 | public override void open (File[] files, string hint) { |
63 | activate (); |
64 | |
65 | === modified file 'src/CMakeLists.txt' |
66 | --- src/CMakeLists.txt 2016-08-01 18:46:23 +0000 |
67 | +++ src/CMakeLists.txt 2016-09-22 18:15:35 +0000 |
68 | @@ -57,7 +57,13 @@ |
69 | Widgets/PlaylistPopover.vala |
70 | Widgets/WelcomePage.vala |
71 | Widgets/PlayerPage.vala |
72 | + Widgets/LibraryPage.vala |
73 | + Widgets/LibraryItem.vala |
74 | + Widgets/NavigationButton.vala |
75 | Services/Inhibitor.vala |
76 | + Services/LibraryManager.vala |
77 | + Services/Thubnailer.vala |
78 | + Objects/Video.vala |
79 | PACKAGES |
80 | ${VALA_DEPS} |
81 | OPTIONS |
82 | |
83 | === added directory 'src/Objects' |
84 | === added file 'src/Objects/Video.vala' |
85 | --- src/Objects/Video.vala 1970-01-01 00:00:00 +0000 |
86 | +++ src/Objects/Video.vala 2016-09-22 18:15:35 +0000 |
87 | @@ -0,0 +1,196 @@ |
88 | +/*- |
89 | + * Copyright (c) 2016-2016 elementary LLC. |
90 | + * |
91 | + * This program is free software: you can redistribute it and/or modify |
92 | + * it under the terms of the GNU General Public License as published by |
93 | + * the Free Software Foundation, either version 3 of the License, or |
94 | + * (at your option) any later version. |
95 | + |
96 | + * This program is distributed in the hope that it will be useful, |
97 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
98 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
99 | + * GNU General Public License for more details. |
100 | + |
101 | + * You should have received a copy of the GNU General Public License |
102 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
103 | + * |
104 | + * Authored by: Artem Anufrij <artem.anufrij@live.de> |
105 | + * |
106 | + */ |
107 | + |
108 | +namespace Audience.Objects { |
109 | + public class Video : Object { |
110 | + Audience.Services.LibraryManager manager; |
111 | + |
112 | + public signal void poster_changed (); |
113 | + public signal void title_changed (); |
114 | + |
115 | + public File video_file { get; private set; } |
116 | + public string directory { get; construct set; } |
117 | + public string file { get; construct set; } |
118 | + |
119 | + public string title { get; private set; } |
120 | + public int year { get; private set; } |
121 | + |
122 | + public Gdk.Pixbuf? poster { get; private set; } |
123 | + |
124 | + public string mime_type { get; construct set; } |
125 | + public string poster_cache_file { get; private set; } |
126 | + |
127 | + public string hash { get; construct set; } |
128 | + |
129 | + public Video (string directory, string file, string mime_type) { |
130 | + Object (directory: directory, file: file, mime_type: mime_type); |
131 | + } |
132 | + |
133 | + construct { |
134 | + manager = Audience.Services.LibraryManager.get_instance (); |
135 | + manager.thumbler.finished.connect (dbus_finished); |
136 | + |
137 | + this.title = Audience.get_title (file); |
138 | + |
139 | + this.extract_metadata (); |
140 | + video_file = File.new_for_path (this.get_path ()); |
141 | + |
142 | + hash = GLib.Checksum.compute_for_string (ChecksumType.MD5, this.get_path (), this.get_path ().length); |
143 | + |
144 | + poster_cache_file = Path.build_filename (App.get_instance ().get_cache_directory (), hash + ".jpg"); |
145 | + |
146 | + notify["poster"].connect (() => { |
147 | + poster_changed (); |
148 | + }); |
149 | + notify["title"].connect (() => { |
150 | + title_changed (); |
151 | + }); |
152 | + } |
153 | + |
154 | + private void extract_metadata () { |
155 | + // exclude YEAR from Title |
156 | + MatchInfo info; |
157 | + if (manager.regex_year.match (this.title, 0, out info)) { |
158 | + this.year = int.parse (info.fetch (0).substring (1, 4)); |
159 | + this.title = this.title.replace (info.fetch (0) + ")", ""); |
160 | + } |
161 | + } |
162 | + |
163 | + public async void initialize_poster () { |
164 | + initialize_poster_thread.begin ((obj, res) => { |
165 | + this.poster = initialize_poster_thread.end (res); |
166 | + }); |
167 | + } |
168 | + |
169 | + public async Gdk.Pixbuf? initialize_poster_thread () { |
170 | + SourceFunc callback = initialize_poster_thread.callback; |
171 | + Gdk.Pixbuf? pixbuf = null; |
172 | + |
173 | + ThreadFunc<void*> run = () => { |
174 | + |
175 | + string poster_path = poster_cache_file; |
176 | + pixbuf = get_poster_from_file (poster_path); |
177 | + |
178 | + // POSTER in Cache exists |
179 | + if (pixbuf != null) { |
180 | + Idle.add ((owned) callback); |
181 | + return null; |
182 | + } |
183 | + |
184 | + // Try to find a POSTER in same folder of video file |
185 | + if (pixbuf == null) { |
186 | + poster_path = this.get_path () + ".jpg"; |
187 | + pixbuf = get_poster_from_file (poster_path); |
188 | + } |
189 | + |
190 | + if (pixbuf == null) { |
191 | + poster_path = Path.build_filename (this.directory, Audience.get_title (file) + ".jpg"); |
192 | + pixbuf = get_poster_from_file (poster_path); |
193 | + } |
194 | + |
195 | + foreach (string s in Audience.settings.poster_names) { |
196 | + if (pixbuf == null) { |
197 | + poster_path = Path.build_filename (this.directory, s); |
198 | + pixbuf = get_poster_from_file (poster_path); |
199 | + } else { |
200 | + break; |
201 | + } |
202 | + } |
203 | + |
204 | + // POSTER found |
205 | + if (pixbuf != null) { |
206 | + try { |
207 | + pixbuf.save (poster_cache_file, "jpeg"); |
208 | + } catch (Error e) { |
209 | + warning (e.message); |
210 | + } |
211 | + Idle.add ((owned) callback); |
212 | + return null; |
213 | + } |
214 | + |
215 | + // Check if THUMBNAIL exists |
216 | + string? thumbnail_path = manager.get_thumbnail_path (video_file); |
217 | + if (thumbnail_path != null) { |
218 | + pixbuf = get_poster_from_file (thumbnail_path); |
219 | + Idle.add ((owned) callback); |
220 | + return null; |
221 | + } |
222 | + |
223 | + // Call DBUS for create a new THUMBNAIL |
224 | + Gee.ArrayList<string> uris = new Gee.ArrayList<string> (); |
225 | + Gee.ArrayList<string> mimes = new Gee.ArrayList<string> (); |
226 | + |
227 | + uris.add (video_file.get_uri ()); |
228 | + mimes.add (mime_type); |
229 | + |
230 | + manager.thumbler.Instand (uris, mimes); |
231 | + |
232 | + Idle.add ((owned) callback); |
233 | + return null; |
234 | + }; |
235 | + |
236 | + try { |
237 | + new Thread<void*>.try (null, run); |
238 | + } catch (Error e) { |
239 | + error (e.message); |
240 | + } |
241 | + |
242 | + yield; |
243 | + |
244 | + return pixbuf; |
245 | + } |
246 | + |
247 | + private void dbus_finished (uint heandle) { |
248 | + if (poster == null) { |
249 | + string? thumbnail_path = manager.get_thumbnail_path (video_file); |
250 | + if (thumbnail_path != null) { |
251 | + poster = get_poster_from_file (thumbnail_path); |
252 | + } |
253 | + } |
254 | + } |
255 | + |
256 | + public string get_path () { |
257 | + return Path.build_filename (directory, file); |
258 | + } |
259 | + |
260 | + public Gdk.Pixbuf? get_poster_from_file (string poster_path) { |
261 | + Gdk.Pixbuf pixbuf = null; |
262 | + if (File.new_for_path (poster_path).query_exists ()) { |
263 | + try { |
264 | + pixbuf = new Gdk.Pixbuf.from_file_at_scale (poster_path, -1, Audience.Services.POSTER_HEIGHT, true); |
265 | + } catch (Error e) { |
266 | + warning (e.message); |
267 | + } |
268 | + |
269 | + if (pixbuf == null) { |
270 | + return null; |
271 | + } |
272 | + // Cut THUMBNAIL images |
273 | + int width = pixbuf.width; |
274 | + if (width > Audience.Services.POSTER_WIDTH) { |
275 | + int x_offset = (width - Audience.Services.POSTER_WIDTH) / 2; |
276 | + pixbuf = new Gdk.Pixbuf.subpixbuf (pixbuf, x_offset, 0, Audience.Services.POSTER_WIDTH, Audience.Services.POSTER_HEIGHT); |
277 | + } |
278 | + } |
279 | + |
280 | + return pixbuf; |
281 | + } |
282 | + } |
283 | +} |
284 | |
285 | === added file 'src/Services/LibraryManager.vala' |
286 | --- src/Services/LibraryManager.vala 1970-01-01 00:00:00 +0000 |
287 | +++ src/Services/LibraryManager.vala 2016-09-22 18:15:35 +0000 |
288 | @@ -0,0 +1,183 @@ |
289 | +// -*- Mode: vala; indent-tabs-mode: nil; tab-width: 4 -*- |
290 | +/*- |
291 | + * Copyright (c) 2016-2016 elementary LLC. |
292 | + * |
293 | + * This program is free software: you can redistribute it and/or modify |
294 | + * it under the terms of the GNU General Public License as published by |
295 | + * the Free Software Foundation, either version 3 of the License, or |
296 | + * (at your option) any later version. |
297 | + |
298 | + * This program is distributed in the hope that it will be useful, |
299 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
300 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
301 | + * GNU General Public License for more details. |
302 | + |
303 | + * You should have received a copy of the GNU General Public License |
304 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
305 | + * |
306 | + * Authored by: Artem Anufrij <artem.anufrij@live.de> |
307 | + * |
308 | + */ |
309 | + |
310 | +namespace Audience.Services { |
311 | + public const int POSTER_WIDTH = 170; |
312 | + public const int POSTER_HEIGHT = 240; |
313 | + |
314 | + public class LibraryManager : Object { |
315 | + |
316 | + public signal void video_file_detected (Audience.Objects.Video video); |
317 | + public signal void video_file_deleted (string path); |
318 | + public signal void finished (); |
319 | + |
320 | + public Regex regex_year { get; construct set; } |
321 | + public DbusThumbnailer thumbler { get; construct set; } |
322 | + |
323 | + public bool has_items { get; private set; } |
324 | + |
325 | + private Gee.ArrayList<string> poster_hash; |
326 | + private Gee.ArrayList<FileMonitor> monitoring_directories; |
327 | + |
328 | + public static LibraryManager instance = null; |
329 | + public static LibraryManager get_instance () { |
330 | + if (instance == null) { |
331 | + instance = new LibraryManager (); |
332 | + } |
333 | + |
334 | + return instance; |
335 | + } |
336 | + |
337 | + private LibraryManager () { |
338 | + } |
339 | + |
340 | + construct { |
341 | + poster_hash = new Gee.ArrayList<string> (); |
342 | + monitoring_directories = new Gee.ArrayList<FileMonitor> (); |
343 | + try { |
344 | + regex_year = new Regex ("\\(\\d\\d\\d\\d(?=(\\)$))"); |
345 | + } catch (Error e) { |
346 | + error (e.message); |
347 | + } |
348 | + thumbler = new DbusThumbnailer (); |
349 | + |
350 | + finished.connect (() => { clear_unused_cache_files.begin (); }); |
351 | + } |
352 | + |
353 | + public void begin_scan () { |
354 | + detect_video_files.begin (Audience.settings.library_folder); |
355 | + } |
356 | + |
357 | + public async void detect_video_files (string source) throws GLib.Error { |
358 | + File directory = File.new_for_path (source); |
359 | + |
360 | + FileMonitor monitor = directory.monitor (FileMonitorFlags.NONE, null); |
361 | + monitor.changed.connect ((src, dest, event) => { |
362 | + if (event == GLib.FileMonitorEvent.DELETED) { |
363 | + video_file_deleted (src.get_path ()); |
364 | + } |
365 | + else if (event == GLib.FileMonitorEvent.CHANGES_DONE_HINT) { |
366 | + FileInfo file_info; |
367 | + try { |
368 | + file_info = src.query_info (FileAttribute.STANDARD_CONTENT_TYPE + "," + FileAttribute.STANDARD_IS_HIDDEN + "," + FileAttribute.STANDARD_TYPE, 0); |
369 | + } catch (Error e) { |
370 | + warning (e.message); |
371 | + return; |
372 | + } |
373 | + if (file_info.get_file_type () == FileType.DIRECTORY) { |
374 | + detect_video_files.begin (src.get_path ()); |
375 | + } else if (is_file_valid (file_info)) { |
376 | + string src_path = src.get_path (); |
377 | + crate_video_object (file_info, Path.get_dirname (src_path), Path.get_basename (src_path)); |
378 | + } |
379 | + } |
380 | + }); |
381 | + monitoring_directories.add (monitor); |
382 | + |
383 | + var children = directory.enumerate_children (FileAttribute.STANDARD_CONTENT_TYPE + "," + FileAttribute.STANDARD_IS_HIDDEN, 0); |
384 | + |
385 | + if (children != null) { |
386 | + FileInfo file_info; |
387 | + while ((file_info = children.next_file ()) != null) { |
388 | + if (file_info.get_file_type () == FileType.DIRECTORY) { |
389 | + detect_video_files.begin (source + "/" + file_info.get_name ()); |
390 | + continue; |
391 | + } |
392 | + |
393 | + if (is_file_valid (file_info)) { |
394 | + crate_video_object (file_info, source); |
395 | + } |
396 | + } |
397 | + } |
398 | + if (directory.get_path () == Audience.settings.library_folder) { |
399 | + finished (); |
400 | + } |
401 | + } |
402 | + |
403 | + private bool is_file_valid (FileInfo file_info) { |
404 | + string mime_type = file_info.get_content_type (); |
405 | + return !file_info.get_is_hidden () && mime_type.contains ("video"); |
406 | + } |
407 | + |
408 | + private void crate_video_object (FileInfo file_info, string source, string name = "") { |
409 | + if (name == "") { |
410 | + name = file_info.get_name (); |
411 | + } |
412 | + var video = new Audience.Objects.Video (source, name, file_info.get_content_type ()); |
413 | + video_file_detected (video); |
414 | + poster_hash.add (video.hash + ".jpg"); |
415 | + has_items = true; |
416 | + } |
417 | + |
418 | + public string? get_thumbnail_path (File file) { |
419 | + if (!file.is_native ()) { |
420 | + return null; |
421 | + } |
422 | + string? path = null; |
423 | + try { |
424 | + var info = file.query_info (FileAttribute.THUMBNAIL_PATH + "," + FileAttribute.THUMBNAILING_FAILED, FileQueryInfoFlags.NONE); |
425 | + path = info.get_attribute_as_string (FileAttribute.THUMBNAIL_PATH); |
426 | + var failed = info.get_attribute_boolean (FileAttribute.THUMBNAILING_FAILED); |
427 | + |
428 | + if (failed || path == null) { |
429 | + return null; |
430 | + } |
431 | + |
432 | + path = path.replace ("normal", "large"); |
433 | + |
434 | + File large_thumbnail = File.new_for_path (path); |
435 | + if (!large_thumbnail.query_exists ()) { |
436 | + return null; |
437 | + } |
438 | + } catch (Error e) { |
439 | + warning (e.message); |
440 | + return null; |
441 | + } |
442 | + |
443 | + return path; |
444 | + } |
445 | + |
446 | + public void clear_cache (Audience.Objects.Video video) { |
447 | + File file = File.new_for_path (video.poster_cache_file); |
448 | + if (file.query_exists ()) { |
449 | + file.delete_async.begin (Priority.DEFAULT, null); |
450 | + } |
451 | + } |
452 | + |
453 | + public async void clear_unused_cache_files () { |
454 | + File directory = File.new_for_path (App.get_instance ().get_cache_directory ()); |
455 | + try { |
456 | + var children = directory.enumerate_children (FileAttribute.STANDARD_NAME, 0); |
457 | + |
458 | + if (children != null) { |
459 | + FileInfo file_info; |
460 | + while ((file_info = children.next_file ()) != null) { |
461 | + if (!poster_hash.contains (file_info.get_name ())) { |
462 | + children.get_child (file_info).delete_async.begin (); |
463 | + } |
464 | + } |
465 | + } |
466 | + } catch (Error e) { |
467 | + warning (e.message); |
468 | + } |
469 | + } |
470 | + } |
471 | +} |
472 | |
473 | === added file 'src/Services/Thubnailer.vala' |
474 | --- src/Services/Thubnailer.vala 1970-01-01 00:00:00 +0000 |
475 | +++ src/Services/Thubnailer.vala 2016-09-22 18:15:35 +0000 |
476 | @@ -0,0 +1,52 @@ |
477 | +// -*- Mode: vala; indent-tabs-mode: nil; tab-width: 4 -*- |
478 | +/*- |
479 | + * Copyright (c) 2016-2016 elementary LLC. |
480 | + * |
481 | + * This program is free software: you can redistribute it and/or modify |
482 | + * it under the terms of the GNU General Public License as published by |
483 | + * the Free Software Foundation, either version 3 of the License, or |
484 | + * (at your option) any later version. |
485 | + |
486 | + * This program is distributed in the hope that it will be useful, |
487 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
488 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
489 | + * GNU General Public License for more details. |
490 | + |
491 | + * You should have received a copy of the GNU General Public License |
492 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
493 | + * |
494 | + * Authored by: Artem Anufrij <artem.anufrij@live.de> |
495 | + * |
496 | + */ |
497 | + |
498 | +namespace Audience.Services { |
499 | + [DBus (name = "org.freedesktop.thumbnails.Thumbnailer1")] |
500 | + private interface Tumbler : GLib.Object { |
501 | + public abstract async uint Queue (string[] uris, string[] mime_types, string flavor, string sheduler, uint handle_to_dequeue) throws GLib.IOError, GLib.DBusError; |
502 | + public signal void Finished (uint handle); |
503 | + } |
504 | + |
505 | + public class DbusThumbnailer : GLib.Object { |
506 | + private Tumbler tumbler; |
507 | + private const string THUMBNAILER_IFACE = "org.freedesktop.thumbnails.Thumbnailer1"; |
508 | + private const string THUMBNAILER_SERVICE = "/org/freedesktop/thumbnails/Thumbnailer1"; |
509 | + |
510 | + public signal void finished (uint handle); |
511 | + |
512 | + public DbusThumbnailer () { |
513 | + } |
514 | + |
515 | + construct { |
516 | + try { |
517 | + tumbler = Bus.get_proxy_sync (BusType.SESSION, THUMBNAILER_IFACE, THUMBNAILER_SERVICE); |
518 | + tumbler.Finished.connect ((handle) => { finished (handle); }); |
519 | + } catch (Error e) { |
520 | + warning (e.message); |
521 | + } |
522 | + } |
523 | + |
524 | + public void Instand (Gee.ArrayList<string> uris, Gee.ArrayList<string> mimes ){ |
525 | + tumbler.Queue.begin (uris.to_array (), mimes.to_array (), "large", "default", 0); |
526 | + } |
527 | + } |
528 | +} |
529 | |
530 | === modified file 'src/Settings.vala' |
531 | --- src/Settings.vala 2016-03-14 13:10:18 +0000 |
532 | +++ src/Settings.vala 2016-09-22 18:15:35 +0000 |
533 | @@ -28,7 +28,9 @@ |
534 | public string last_folder {get; set;} |
535 | public bool playback_wait {get; set;} |
536 | public bool stay_on_top {get; set;} |
537 | - public string subtitle_font {get; set; } |
538 | + public string subtitle_font {get; set;} |
539 | + public string library_folder {get; set;} |
540 | + public string[] poster_names {get; set;} |
541 | |
542 | public Settings () { |
543 | base ("org.pantheon.audience"); |
544 | |
545 | === added file 'src/Widgets/LibraryItem.vala' |
546 | --- src/Widgets/LibraryItem.vala 1970-01-01 00:00:00 +0000 |
547 | +++ src/Widgets/LibraryItem.vala 2016-09-22 18:15:35 +0000 |
548 | @@ -0,0 +1,100 @@ |
549 | +// -*- Mode: vala; indent-tabs-mode: nil; tab-width: 4 -*- |
550 | +/*- |
551 | + * Copyright (c) 2016-2016 elementary LLC. |
552 | + * |
553 | + * This program is free software: you can redistribute it and/or modify |
554 | + * it under the terms of the GNU General Public License as published by |
555 | + * the Free Software Foundation, either version 3 of the License, or |
556 | + * (at your option) any later version. |
557 | + |
558 | + * This program is distributed in the hope that it will be useful, |
559 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
560 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
561 | + * GNU General Public License for more details. |
562 | + |
563 | + * You should have received a copy of the GNU General Public License |
564 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
565 | + * |
566 | + * Authored by: Artem Anufrij <artem.anufrij@live.de> |
567 | + * |
568 | + */ |
569 | + |
570 | +namespace Audience { |
571 | + public class LibraryItem : Gtk.FlowBoxChild { |
572 | + |
573 | + Gtk.Grid grid; |
574 | + public Audience.Objects.Video video { get; construct set; } |
575 | + |
576 | + Gtk.Image poster; |
577 | + Gtk.Label title; |
578 | + Gtk.Spinner spinner; |
579 | + Gtk.Grid spinner_container; |
580 | + |
581 | + public LibraryItem (Audience.Objects.Video video) { |
582 | + Object (video: video); |
583 | + } |
584 | + |
585 | + construct { |
586 | + margin_bottom = 12; |
587 | + |
588 | + video.poster_changed.connect (() => { |
589 | + if (video.poster != null) { |
590 | + spinner.active = false; |
591 | + spinner_container.hide (); |
592 | + if (poster == null) { |
593 | + poster = new Gtk.Image (); |
594 | + poster.margin_top = poster.margin_left = poster.margin_right = 12; |
595 | + poster.get_style_context ().add_class ("card"); |
596 | + grid.attach (poster, 0, 0, 1, 1); |
597 | + } |
598 | + |
599 | + poster.pixbuf = video.poster; |
600 | + poster.show (); |
601 | + } else { |
602 | + spinner.active = true; |
603 | + spinner_container.show (); |
604 | + if (poster != null) { |
605 | + poster.hide (); |
606 | + } |
607 | + } |
608 | + }); |
609 | + |
610 | + video.title_changed.connect (() => { |
611 | + title.label = video.title; |
612 | + title.show (); |
613 | + }); |
614 | + |
615 | + spinner_container = new Gtk.Grid (); |
616 | + spinner_container.height_request = Audience.Services.POSTER_HEIGHT; |
617 | + spinner_container.width_request = Audience.Services.POSTER_WIDTH; |
618 | + spinner_container.margin_top = spinner_container.margin_left = spinner_container.margin_right = 12; |
619 | + spinner_container.get_style_context ().add_class ("card"); |
620 | + |
621 | + spinner = new Gtk.Spinner (); |
622 | + spinner.expand = true; |
623 | + spinner.active = true; |
624 | + spinner.valign = Gtk.Align.CENTER; |
625 | + spinner.halign = Gtk.Align.CENTER; |
626 | + spinner.height_request = 32; |
627 | + spinner.width_request = 32; |
628 | + |
629 | + spinner_container.add (spinner); |
630 | + |
631 | + grid = new Gtk.Grid (); |
632 | + grid.halign = Gtk.Align.CENTER; |
633 | + grid.valign = Gtk.Align.START; |
634 | + grid.row_spacing = 12; |
635 | + |
636 | + title = new Gtk.Label (video.title); |
637 | + title.justify = Gtk.Justification.CENTER; |
638 | + title.set_line_wrap (true); |
639 | + title.max_width_chars = 0; |
640 | + |
641 | + |
642 | + grid.attach (spinner_container, 0, 0, 1, 1); |
643 | + grid.attach (title, 0, 1, 1 ,1); |
644 | + |
645 | + this.add (grid); |
646 | + } |
647 | + } |
648 | +} |
649 | |
650 | === added file 'src/Widgets/LibraryPage.vala' |
651 | --- src/Widgets/LibraryPage.vala 1970-01-01 00:00:00 +0000 |
652 | +++ src/Widgets/LibraryPage.vala 2016-09-22 18:15:35 +0000 |
653 | @@ -0,0 +1,120 @@ |
654 | +// -*- Mode: vala; indent-tabs-mode: nil; tab-width: 4 -*- |
655 | +/*- |
656 | + * Copyright (c) 2016-2016 elementary LLC. |
657 | + * |
658 | + * This program is free software: you can redistribute it and/or modify |
659 | + * it under the terms of the GNU General Public License as published by |
660 | + * the Free Software Foundation, either version 3 of the License, or |
661 | + * (at your option) any later version. |
662 | + |
663 | + * This program is distributed in the hope that it will be useful, |
664 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
665 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
666 | + * GNU General Public License for more details. |
667 | + |
668 | + * You should have received a copy of the GNU General Public License |
669 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
670 | + * |
671 | + * Authored by: Artem Anufrij <artem.anufrij@live.de> |
672 | + * |
673 | + */ |
674 | + |
675 | +namespace Audience { |
676 | + public class LibraryPage : Gtk.ScrolledWindow { |
677 | + |
678 | + Gtk.FlowBox view_movies; |
679 | + |
680 | + Audience.Services.LibraryManager manager; |
681 | + bool poster_initialized = false; |
682 | + int items_counter; |
683 | + public bool has_items { get { return items_counter > 0; } } |
684 | + |
685 | + public static LibraryPage instance = null; |
686 | + public static LibraryPage get_instance () { |
687 | + if (instance == null) { |
688 | + instance = new LibraryPage (); |
689 | + } |
690 | + |
691 | + return instance; |
692 | + } |
693 | + |
694 | + private LibraryPage () { |
695 | + } |
696 | + |
697 | + construct { |
698 | + items_counter = 0; |
699 | + view_movies = new Gtk.FlowBox (); |
700 | + view_movies.margin = 24; |
701 | + view_movies.homogeneous = true; |
702 | + view_movies.row_spacing = 12; |
703 | + view_movies.column_spacing = 12; |
704 | + view_movies.valign = Gtk.Align.START; |
705 | + view_movies.selection_mode = Gtk.SelectionMode.NONE; |
706 | + view_movies.child_activated.connect (play_video); |
707 | + |
708 | + view_movies.set_sort_func ((child1, child2) => { |
709 | + var item1 = child1 as LibraryItem; |
710 | + var item2 = child2 as LibraryItem; |
711 | + if (item1 != null && item2 != null) { |
712 | + return item1.video.file.collate (item2.video.file); |
713 | + } |
714 | + return 0; |
715 | + }); |
716 | + |
717 | + manager = Audience.Services.LibraryManager.get_instance (); |
718 | + manager.video_file_detected.connect (add_item); |
719 | + manager.video_file_deleted.connect (remove_item_from_path); |
720 | + manager.begin_scan (); |
721 | + |
722 | + this.add (view_movies); |
723 | + |
724 | + map.connect (() => { |
725 | + if (!poster_initialized) { |
726 | + poster_initialized = true; |
727 | + poster_initialisation.begin (); |
728 | + show_all (); |
729 | + } |
730 | + }); |
731 | + } |
732 | + |
733 | + private void add_item (Audience.Objects.Video video) { |
734 | + Audience.LibraryItem new_item = new Audience.LibraryItem (video); |
735 | + view_movies.add (new_item); |
736 | + if (poster_initialized) { |
737 | + new_item.show_all (); |
738 | + new_item.video.initialize_poster.begin (); |
739 | + } |
740 | + items_counter++; |
741 | + } |
742 | + |
743 | + private void play_video (Gtk.FlowBoxChild item) { |
744 | + var selected = (item as Audience.LibraryItem); |
745 | + if (selected.video.video_file.query_exists ()) { |
746 | + bool from_beginning = selected.video.video_file.get_uri () != settings.current_video; |
747 | + App.get_instance ().mainwindow.play_file (selected.video.video_file.get_uri (), from_beginning); |
748 | + } else { |
749 | + remove_item.begin (selected); |
750 | + } |
751 | + } |
752 | + |
753 | + private async void remove_item (LibraryItem item) { |
754 | + manager.clear_cache (item.video); |
755 | + item.dispose (); |
756 | + items_counter--; |
757 | + } |
758 | + |
759 | + private async void remove_item_from_path (string path ) { |
760 | + foreach (var child in view_movies.get_children ()) { |
761 | + if ((child as LibraryItem).video.video_file.get_path ().has_prefix (path)) { |
762 | + remove_item.begin (child as LibraryItem); |
763 | + } |
764 | + } |
765 | + } |
766 | + |
767 | + private async void poster_initialisation () { |
768 | + foreach (var child in view_movies.get_children ()) { |
769 | + (child as LibraryItem).video.initialize_poster.begin (); |
770 | + } |
771 | + } |
772 | + } |
773 | +} |
774 | |
775 | === added file 'src/Widgets/NavigationButton.vala' |
776 | --- src/Widgets/NavigationButton.vala 1970-01-01 00:00:00 +0000 |
777 | +++ src/Widgets/NavigationButton.vala 2016-09-22 18:15:35 +0000 |
778 | @@ -0,0 +1,35 @@ |
779 | +// -*- Mode: vala; indent-tabs-mode: nil; tab-width: 4 -*- |
780 | +/*- |
781 | + * Copyright (c) 2016-2016 elementary LLC. |
782 | + * |
783 | + * This program is free software: you can redistribute it and/or modify |
784 | + * it under the terms of the GNU General Public License as published by |
785 | + * the Free Software Foundation, either version 3 of the License, or |
786 | + * (at your option) any later version. |
787 | + |
788 | + * This program is distributed in the hope that it will be useful, |
789 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
790 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
791 | + * GNU General Public License for more details. |
792 | + |
793 | + * You should have received a copy of the GNU General Public License |
794 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
795 | + * |
796 | + * Authored by: Artem Anufrij <artem.anufrij@live.de> |
797 | + * |
798 | + */ |
799 | + |
800 | +namespace Audience { |
801 | + public class NavigationButton : Gtk.Button { |
802 | + |
803 | + public NavigationButton () { |
804 | + } |
805 | + |
806 | + construct { |
807 | + can_focus = false; |
808 | + valign = Gtk.Align.CENTER; |
809 | + vexpand = false; |
810 | + this.get_style_context ().add_class ("back-button"); |
811 | + } |
812 | + } |
813 | +} |
814 | |
815 | === modified file 'src/Widgets/PlayerPage.vala' |
816 | --- src/Widgets/PlayerPage.vala 2016-08-19 16:27:45 +0000 |
817 | +++ src/Widgets/PlayerPage.vala 2016-09-22 18:15:35 +0000 |
818 | @@ -264,9 +264,17 @@ |
819 | settings.current_video = uri; |
820 | } |
821 | |
822 | + public double get_progress () { |
823 | + return playback.progress; |
824 | + } |
825 | + |
826 | public string get_played_uri () { |
827 | return playback.uri; |
828 | } |
829 | + |
830 | + public void reset_played_uri () { |
831 | + playback.uri = null; |
832 | + } |
833 | |
834 | public void next () { |
835 | get_playlist_widget ().next (); |
836 | |
837 | === modified file 'src/Widgets/WelcomePage.vala' |
838 | --- src/Widgets/WelcomePage.vala 2016-08-19 16:27:45 +0000 |
839 | +++ src/Widgets/WelcomePage.vala 2016-09-22 18:15:35 +0000 |
840 | @@ -1,6 +1,7 @@ |
841 | namespace Audience { |
842 | public class WelcomePage : Granite.Widgets.Welcome { |
843 | private DiskManager disk_manager; |
844 | + private Services.LibraryManager library_manager; |
845 | public WelcomePage () { |
846 | base (_("No Videos Open"), _("Select a source to begin playing.")); |
847 | } |
848 | @@ -33,9 +34,22 @@ |
849 | set_item_visible (2, disk_manager.has_media_volumes ()); |
850 | }); |
851 | |
852 | + library_manager = Services.LibraryManager.get_instance (); |
853 | + library_manager.video_file_detected.connect ((vid) => { |
854 | + set_item_visible (3, true); |
855 | + this.show_all (); |
856 | + }); |
857 | + |
858 | + library_manager.video_file_deleted.connect ((vid) => { |
859 | + set_item_visible (3, LibraryPage.get_instance ().has_items); |
860 | + }); |
861 | + |
862 | append ("media-cdrom", _("Play from Disc"), _("Watch a DVD or open a file from disc")); |
863 | set_item_visible (2, disk_manager.has_media_volumes ()); |
864 | |
865 | + append ("folder-videos", _("Browse Library"), _("Watch a movie from your library")); |
866 | + set_item_visible (3, library_manager.has_items); |
867 | + |
868 | activated.connect ((index) => { |
869 | var window = App.get_instance ().mainwindow; |
870 | switch (index) { |
871 | @@ -49,6 +63,8 @@ |
872 | case 2: |
873 | window.run_open_dvd (); |
874 | break; |
875 | + case 3: |
876 | + window.show_library (); |
877 | } |
878 | }); |
879 | } |
880 | @@ -60,9 +76,16 @@ |
881 | var filename = settings.current_video; |
882 | var last_file = File.new_for_uri (filename); |
883 | |
884 | - replay_button.title = _("Replay last video"); |
885 | + if (settings.last_stopped == 0.0) { |
886 | + replay_button.title = _("Replay last video"); |
887 | + replay_button.icon.icon_name = ("media-playlist-repeat"); |
888 | + } else { |
889 | + replay_button.title = _("Resume last video"); |
890 | + replay_button.icon.icon_name = ("media-playback-start"); |
891 | + } |
892 | + |
893 | replay_button.description = get_title (last_file.get_basename ()); |
894 | - replay_button.icon.icon_name = ("media-playlist-repeat"); |
895 | + |
896 | |
897 | bool show_last_file = settings.current_video != ""; |
898 | if (last_file.query_exists () == false) { |
899 | |
900 | === modified file 'src/Window.vala' |
901 | --- src/Window.vala 2016-08-24 01:21:07 +0000 |
902 | +++ src/Window.vala 2016-09-22 18:15:35 +0000 |
903 | @@ -26,12 +26,18 @@ |
904 | private Gtk.HeaderBar header; |
905 | private PlayerPage player_page; |
906 | private WelcomePage welcome_page; |
907 | + private LibraryPage library_page; |
908 | + private NavigationButton navigation_button; |
909 | private ZeitgeistManager zeitgeist_manager; |
910 | |
911 | + // For better translation |
912 | + const string navigation_button_welcomescreen = N_("Back"); |
913 | + const string navigation_button_library = N_("Library"); |
914 | + |
915 | public signal void media_volumes_changed (); |
916 | |
917 | public Window () { |
918 | - |
919 | + |
920 | } |
921 | |
922 | construct { |
923 | @@ -43,8 +49,33 @@ |
924 | header = new Gtk.HeaderBar (); |
925 | header.set_show_close_button (true); |
926 | header.get_style_context ().add_class ("compact"); |
927 | + |
928 | + navigation_button = new NavigationButton (); |
929 | + navigation_button.clicked.connect (() => { |
930 | + double progress = player_page.get_progress (); |
931 | + if (progress > 0) { |
932 | + settings.last_stopped = progress; |
933 | + } |
934 | + player_page.playing = false; |
935 | + player_page.reset_played_uri (); |
936 | + title = App.get_instance ().program_name; |
937 | + get_window ().set_cursor (null); |
938 | + |
939 | + if (navigation_button.label == navigation_button_library) { |
940 | + navigation_button.label = navigation_button_welcomescreen; |
941 | + main_stack.set_visible_child_full ("library", Gtk.StackTransitionType.SLIDE_RIGHT); |
942 | + } else { |
943 | + navigation_button.hide (); |
944 | + main_stack.set_visible_child (welcome_page); |
945 | + } |
946 | + |
947 | + welcome_page.refresh (); |
948 | + }); |
949 | + |
950 | + header.pack_start (navigation_button); |
951 | set_titlebar (header); |
952 | |
953 | + library_page = LibraryPage.get_instance (); |
954 | welcome_page = new WelcomePage (); |
955 | |
956 | player_page = new PlayerPage (); |
957 | @@ -58,12 +89,17 @@ |
958 | }); |
959 | |
960 | main_stack = new Gtk.Stack (); |
961 | - main_stack.add (welcome_page); |
962 | - main_stack.add (player_page); |
963 | + main_stack.expand = true; |
964 | + main_stack.add_named (welcome_page, "welcome"); |
965 | + main_stack.add_named (player_page, "player"); |
966 | + main_stack.add_named (library_page, "library"); |
967 | + main_stack.transition_type = Gtk.StackTransitionType.SLIDE_LEFT_RIGHT; |
968 | |
969 | add (main_stack); |
970 | show_all (); |
971 | - main_stack.set_visible_child (welcome_page); |
972 | + |
973 | + navigation_button.hide (); |
974 | + main_stack.set_visible_child_full ("welcome", Gtk.StackTransitionType.NONE); |
975 | |
976 | Gtk.TargetEntry uris = {"text/uri-list", 0, 0}; |
977 | Gtk.drag_dest_set (this, Gtk.DestDefaults.ALL, {uris}, Gdk.DragAction.MOVE); |
978 | @@ -103,9 +139,9 @@ |
979 | /*/ FIXME: Remove comments once gala bug is fixed: https://bugs.launchpad.net/gala/+bug/1602722 |
980 | if (Gdk.WindowState.MAXIMIZED in e.changed_mask) { |
981 | bool currently_maximixed = Gdk.WindowState.MAXIMIZED in e.new_window_state; |
982 | - |
983 | + |
984 | if (main_stack.get_visible_child () == player_page && currently_maximixed) { |
985 | - fullscreen (); |
986 | + fullscreen (); |
987 | } |
988 | }*/ |
989 | |
990 | @@ -130,6 +166,12 @@ |
991 | |
992 | public override bool key_press_event (Gdk.EventKey e) { |
993 | uint keycode = e.hardware_keycode; |
994 | + |
995 | + if ((e.state & Gdk.ModifierType.MOD1_MASK) != 0 && e.keyval == Gdk.Key.Left) { |
996 | + navigation_button.clicked (); |
997 | + return true; |
998 | + } |
999 | + |
1000 | if ((e.state & Gdk.ModifierType.CONTROL_MASK) != 0) { |
1001 | if (match_keycode (Gdk.Key.o, keycode)) { |
1002 | run_open_file (); |
1003 | @@ -139,7 +181,7 @@ |
1004 | return true; |
1005 | } |
1006 | } |
1007 | - |
1008 | + |
1009 | if (main_stack.get_visible_child () == player_page) { |
1010 | if (match_keycode (Gdk.Key.p, keycode) || match_keycode (Gdk.Key.space, keycode)) { |
1011 | player_page.playing = !player_page.playing; |
1012 | @@ -225,7 +267,7 @@ |
1013 | if (clear_playlist) { |
1014 | player_page.get_playlist_widget ().clear_items (); |
1015 | } |
1016 | - |
1017 | + |
1018 | string[] videos = {}; |
1019 | foreach (var file in files) { |
1020 | if (file.query_file_type (0) == FileType.DIRECTORY) { |
1021 | @@ -242,7 +284,7 @@ |
1022 | if (videos.length == 0) { |
1023 | return; |
1024 | } |
1025 | - |
1026 | + |
1027 | if (force_play) { |
1028 | play_file (videos [0]); |
1029 | } |
1030 | @@ -260,6 +302,13 @@ |
1031 | read_first_disk.begin (); |
1032 | } |
1033 | |
1034 | + public void show_library () { |
1035 | + navigation_button.label = navigation_button_welcomescreen; |
1036 | + navigation_button.show (); |
1037 | + main_stack.set_visible_child (library_page); |
1038 | + library_page.grab_focus (); |
1039 | + } |
1040 | + |
1041 | public void run_open_file (bool clear_playlist = false, bool force_play = true) { |
1042 | var file = new Gtk.FileChooserDialog (_("Open"), this, Gtk.FileChooserAction.OPEN, |
1043 | _("_Cancel"), Gtk.ResponseType.CANCEL, _("_Open"), Gtk.ResponseType.ACCEPT); |
1044 | @@ -328,15 +377,24 @@ |
1045 | unfullscreen (); |
1046 | } |
1047 | |
1048 | - private void play_file (string uri, bool from_beginning = true) { |
1049 | - main_stack.set_visible_child (player_page); |
1050 | + public void play_file (string uri, bool from_beginning = true) { |
1051 | + if (navigation_button.visible) { |
1052 | + navigation_button.label = navigation_button_library; |
1053 | + } else { |
1054 | + navigation_button.show (); |
1055 | + navigation_button.label = navigation_button_welcomescreen; |
1056 | + } |
1057 | + |
1058 | + main_stack.set_visible_child_full ("player", Gtk.StackTransitionType.SLIDE_LEFT); |
1059 | player_page.play_file (uri, from_beginning); |
1060 | if (is_maximized) { |
1061 | fullscreen (); |
1062 | } |
1063 | - |
1064 | + |
1065 | if (settings.stay_on_top && !settings.playback_wait) { |
1066 | set_keep_above (true); |
1067 | } |
1068 | + |
1069 | + welcome_page.refresh (); |
1070 | } |
1071 | } |
* I don't think the words "Back to" are necessary. On other back buttons we just put the thing you're going back to. So "Library" and "Welcome Screen" are probably fine.
* The window title doesn't seem to update when you change screens. It should show "Videos" on the Library and Welcome pages
* I think these pages should be added to a Gtk.Stack so we can have a nice swipe animation when moving back like you see in Switchboard
* There should probably be some kind of placeholder for when Videos is unable to fetch a poster image
* The shortcut "alt + left" should activate the back button