1// -*- Mode: vala; indent-tabs-mode: nil; tab-width: 4 -*- 2/*- 3 * Copyright (c) 2016-2018 elementary LLC. (https://elementary.io) 4 * 5 * This program is free software: you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation, either version 3 of the License, or 8 * (at your option) any later version. 9 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 * Authored by: Artem Anufrij <artem.anufrij@live.de> 19 * 20 */ 21 22namespace Audience { 23 public enum LibraryItemStyle { THUMBNAIL, ROW } 24 25 public class LibraryItem : Gtk.FlowBoxChild { 26 Audience.Services.LibraryManager manager; 27 28 public Gee.ArrayList<Audience.Objects.Video> episodes { get; private set; } 29 public LibraryItemStyle item_style { get; construct set; } 30 31 private Gtk.Label title_label; 32 private Gtk.Spinner spinner; 33 private Gtk.Grid spinner_container; 34 private Gtk.Menu context_menu; 35 36 public Gtk.Image poster { get; set; } 37 public string episode_poster_path { get; construct set; } 38 public string poster_cache_file { get; construct set; } 39 public string hash { get; construct set; } 40 41 public LibraryItem (Audience.Objects.Video video, LibraryItemStyle item_style) { 42 Object (item_style: item_style); 43 episodes = new Gee.ArrayList<Audience.Objects.Video> (); 44 add_episode (video); 45 video.title_changed.connect (video_title_changed); 46 video.poster_changed.connect (video_poster_changed); 47 48 hash = GLib.Checksum.compute_for_string (ChecksumType.MD5, video.video_file.get_parent ().get_uri (), video.video_file.get_parent ().get_uri ().length); 49 episode_poster_path = Path.build_filename (video.video_file.get_parent ().get_path (), video.video_file.get_parent ().get_basename () + ".jpg"); 50 poster_cache_file = Path.build_filename (App.get_instance ().get_cache_directory (), hash + ".jpg"); 51 } 52 53 construct { 54 manager = Audience.Services.LibraryManager.get_instance (); 55 56 var grid = new Gtk.Grid (); 57 grid.valign = Gtk.Align.START; 58 grid.expand = true; 59 60 title_label = new Gtk.Label (""); 61 62 context_menu = new Gtk.Menu (); 63 64 var move_to_trash = new Gtk.MenuItem.with_label (_("Move to Trash")); 65 move_to_trash.activate.connect ( move_video_to_trash ); 66 67 if (item_style == LibraryItemStyle.THUMBNAIL) { 68 margin_bottom = 12; 69 70 title_label.set_line_wrap (true); 71 title_label.max_width_chars = 0; 72 title_label.justify = Gtk.Justification.CENTER; 73 74 grid.halign = Gtk.Align.CENTER; 75 grid.row_spacing = 12; 76 77 var new_cover = new Gtk.MenuItem.with_label (_("Set Artwork")); 78 new_cover.activate.connect ( set_new_cover ); 79 context_menu.append (new_cover); 80 context_menu.append (new Gtk.SeparatorMenuItem ()); 81 82 poster = new Gtk.Image (); 83 poster.margin_top = poster.margin_start = poster.margin_end = 12; 84 poster.get_style_context ().add_class ("card"); 85 poster.pixbuf = null; 86 poster.notify ["pixbuf"].connect (poster_visibility); 87 88 spinner_container = new Gtk.Grid (); 89 spinner_container.height_request = Audience.Services.POSTER_HEIGHT; 90 spinner_container.width_request = Audience.Services.POSTER_WIDTH; 91 spinner_container.margin_top = spinner_container.margin_start = spinner_container.margin_end = 12; 92 spinner_container.get_style_context ().add_class ("card"); 93 94 spinner = new Gtk.Spinner (); 95 spinner.expand = true; 96 spinner.active = true; 97 spinner.valign = Gtk.Align.CENTER; 98 spinner.halign = Gtk.Align.CENTER; 99 spinner.height_request = 32; 100 spinner.width_request = 32; 101 102 spinner_container.add (spinner); 103 104 grid.attach (spinner_container, 0, 0, 1, 1); 105 grid.attach (poster, 0, 0, 1, 1); 106 grid.attach (title_label, 0, 1); 107 108 map.connect (poster_visibility); 109 } else { 110 grid.halign = Gtk.Align.FILL; 111 grid.attach (title_label, 0, 0); 112 grid.margin = 12; 113 } 114 115 context_menu.append (move_to_trash); 116 context_menu.show_all (); 117 118 var event_box = new Gtk.EventBox (); 119 event_box.button_press_event.connect (show_context_menu); 120 event_box.add (grid); 121 122 add (event_box); 123 show_all (); 124 } 125 126 private void poster_visibility () { 127 if (poster.pixbuf != null) { 128 spinner.active = false; 129 spinner_container.hide (); 130 poster.show_all (); 131 } else { 132 spinner.active = true; 133 spinner_container.show (); 134 poster.hide (); 135 } 136 } 137 138 private bool show_context_menu (Gtk.Widget sender, Gdk.EventButton evt) { 139 if (evt.type == Gdk.EventType.BUTTON_PRESS && evt.button == 3) { 140 context_menu.popup_at_pointer (evt); 141 return true; 142 } 143 return false; 144 } 145 146 private void video_poster_changed (Audience.Objects.Video video) { 147 if (item_style == LibraryItemStyle.THUMBNAIL && (episodes.size == 1 || poster.pixbuf == null)) { 148 poster.pixbuf = video.poster; 149 } 150 } 151 152 private void video_title_changed (Audience.Objects.Video video) { 153 if (episodes.size == 1) { 154 title_label.label = video.title; 155 } else { 156 title_label.label = video.container; 157 } 158 title_label.show (); 159 } 160 161 private void set_new_cover () { 162 var image_filter = new Gtk.FileFilter (); 163 image_filter.set_filter_name (_("Image files")); 164 image_filter.add_mime_type ("image/*"); 165 166 var filechooser = new Gtk.FileChooserNative ( 167 _("Open"), 168 Audience.App.get_instance ().mainwindow, 169 Gtk.FileChooserAction.OPEN, 170 _("_Open"), 171 _("_Cancel") 172 ); 173 filechooser.add_filter (image_filter); 174 175 if (filechooser.run () == Gtk.ResponseType.ACCEPT) { 176 Gdk.Pixbuf? pixbuf = manager.get_poster_from_file (filechooser.get_filename ()); 177 if (pixbuf != null) { 178 try { 179 if (episodes.size == 1) { 180 pixbuf.save (episodes.first ().video_file.get_path () + ".jpg", "jpeg"); 181 episodes.first ().set_new_poster (pixbuf); 182 episodes.first ().initialize_poster.begin (); 183 } else { 184 manager.clear_cache.begin (poster_cache_file); 185 pixbuf.save (episode_poster_path, "jpeg"); 186 create_episode_poster (); 187 } 188 } catch (Error e) { 189 warning (e.message); 190 } 191 } 192 } 193 filechooser.destroy (); 194 } 195 196 private void move_video_to_trash () { 197 debug (episodes.size.to_string ()); 198 if (episodes.size == 1) { 199 var video = episodes.first (); 200 video.trashed (); 201 try { 202 video.video_file.trash (); 203 manager.deleted_items (video.video_file.get_path ()); 204 } catch (Error e) { 205 warning (e.message); 206 } 207 } else { 208 try { 209 episodes.first ().video_file.get_parent ().trash (); 210 manager.deleted_items (episodes.first ().video_file.get_parent ().get_path ()); 211 } catch (Error e) { 212 warning (e.message); 213 } 214 } 215 } 216 217 public void add_episode (Audience.Objects.Video episode) { 218 episode.trashed.connect (() => { 219 episodes.remove (episode); 220 }); 221 episodes.add (episode); 222 if (episodes.size == 1) { 223 title_label.label = episode.title; 224 } else if (episodes.size == 2) { 225 title_label.label = episode.container; 226 create_episode_poster (); 227 } 228 } 229 230 public string get_title () { 231 return title_label.label; 232 } 233 234 public void create_episode_poster () { 235 if (FileUtils.test (poster_cache_file, FileTest.EXISTS)) { 236 try { 237 poster.pixbuf = new Gdk.Pixbuf.from_file (poster_cache_file); 238 } catch (Error e) { 239 warning (e.message); 240 } 241 } else if (FileUtils.test (episode_poster_path, FileTest.EXISTS)) { 242 poster.pixbuf = manager.get_poster_from_file (episode_poster_path); 243 try { 244 poster.pixbuf.save (poster_cache_file, "jpeg"); 245 } catch (Error e) { 246 warning (e.message); 247 } 248 } 249 } 250 } 251} 252