852690fa399e3206598d8a89d55d9b94c52294e1
[cinaest] / src / movie-list-store.vala
1 /* This file is part of Cinaest.
2  *
3  * Copyright (C) 2009 Philipp Zabel
4  *
5  * Cinaest 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  * Cinaest 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 Cinaest. If not, see <http://www.gnu.org/licenses/>.
17  */
18
19 using Gtk;
20
21 public class MovieListStore : ListStore, TreeModel {
22         public enum Columns {
23                 TITLE,
24                 YEAR,
25                 RATING,
26                 POSTER,
27                 MOVIE,
28                 MARKUP,
29                 N_COLUMNS
30         }
31         private GLib.Type[] types = {
32                 typeof (string),
33                 typeof (int),
34                 typeof (string),
35                 typeof (Gdk.Pixbuf),
36                 typeof (Movie),
37                 typeof (string)
38         };
39         private GLib.Type[] base_type = {
40                 typeof (Movie),
41                 typeof (string), // Markup: "Title (Year)"
42                 typeof (string)  // Rating
43         };
44         private Gdk.Pixbuf no_poster;
45         private MoviePoster.Factory poster_factory;
46         private MovieFilter filter;
47         public bool update_running { get; set; }
48         public string year_markup = "<span size=\"small\">[%d]</span>";
49         private Cancellable cancellable;
50
51         public signal void search_finished (int movies);
52
53         private MovieSource _source;
54         public MovieSource source {
55                 get {
56                         return _source;
57                 }
58                 set {
59                         _source = value;
60                 }
61         }
62
63         construct {
64                 set_column_types (base_type);
65                 no_poster = null;
66                 source = null;
67                 update_running = false;
68
69                 poster_factory = MoviePoster.Factory.get_instance ();
70         }
71
72         public void add (Movie movie, out TreeIter iter) {
73                 TreeIter iter1;
74                 var markup = new StringBuilder ();
75                 markup.append (Markup.escape_text (movie.title));
76                 if (movie.year > 0) {
77                         markup.append (" ");
78                         markup.append_printf (year_markup, movie.year);
79                 }
80
81                 append (out iter1);
82                 base.set (iter1, 0, movie,
83                                  1, markup.str,
84                                  2, (movie.rating >= 0) ? "%d.%d".printf (movie.rating / 10, movie.rating % 10) : null);
85
86                 movie.notify.connect (this.on_movie_changed);
87
88                 iter = iter1;
89         }
90
91         public new bool remove (Movie movie) {
92                 TreeIter iter;
93
94                 if (get_iter_from_movie (out iter, movie)) {
95                         movie.notify.disconnect (this.on_movie_changed);
96                         base.remove (iter);
97
98                         if (SourceFlags.EDITABLE in source.get_flags ()) {
99                                 source.delete_movie (movie);
100                         }
101
102                         return true;
103                 }
104
105                 return false;
106         }
107
108         private void on_movie_changed (GLib.Object source, GLib.ParamSpec spec) {
109                 var movie = (Movie) source;
110
111                 TreeIter iter;
112                 if (get_iter_from_movie (out iter, movie)) {
113                         TreePath path = get_path (iter);
114                         base.row_changed (path, iter);
115                 }
116         }
117
118         public bool get_editable () {
119                 return (SourceFlags.EDITABLE in source.get_flags ());
120         }
121
122         public bool get_iter_from_movie (out TreeIter iter, Movie movie_a) {
123                 if (get_iter_first (out iter)) {
124                         do {
125                                 Movie movie_b;
126                                 get (iter, Columns.MOVIE, out movie_b);
127                                 if (movie_a == movie_b)
128                                         return true;
129                         } while (iter_next (ref iter));
130                 }
131                 return false;
132         }
133
134         public bool start_search (MovieFilter _filter) {
135                 if (update_running) {
136                         stdout.printf ("aborting search ...\n");
137                         cancellable.cancel ();
138                         poster_factory.clear_queue ();
139                         return false;
140                 }
141                 if (cancellable == null || cancellable.is_cancelled ())
142                         cancellable = new Cancellable ();
143
144                 filter = _filter;
145                 stdout.printf ("begin search\n");
146                 search_async.begin ();
147                 update_running = true;
148                 return true;
149         }
150
151         // Asynchronous update method
152         private async void search_async () {
153                 stdout.printf ("search started: \"%s\"\n", filter.title);
154
155                 clear ();
156
157                 if (source != null) {
158                         // FIXME - arbitrary limit
159                         int n = yield source.get_movies (filter, receive_movie, 100, cancellable);
160                         search_finished (n);
161                 }
162
163                 update_running = false;
164                 if (cancellable.is_cancelled ()) {
165                         stdout.printf ("search aborted, starting new\n");
166                         cancellable.reset ();
167                         if (cancellable.is_cancelled ()) {
168                                 stdout.printf ("OW WEY\n");
169                         }
170                         start_search (filter);
171                 } else {
172                         stdout.printf ("search stopped\n");
173                 }
174         }
175
176         private void receive_movie (SList<Movie> movies) {
177                 TreeIter iter;
178
179                 if (cancellable.is_cancelled ())
180                         return;
181
182                 foreach (Movie movie in movies) {
183                         add (movie, out iter);
184                         try {
185                                 poster_factory.queue_thumbnail (movie, 64, 64, false, receive_poster_thumbnail);
186                         } catch (Error e) {
187                                 warning ("Failed to queue poster request: %s\n", e.message);
188                         }
189                 }
190         }
191
192         private void receive_poster_thumbnail (Gdk.Pixbuf pixbuf, Movie movie) {
193                 var poster = new Poster ();
194                 poster.thumbnail = pixbuf;
195                 movie.poster = poster;
196         }
197
198         // Implement TreeModel interface
199         public virtual GLib.Type get_column_type (int index_) {
200                 return_val_if_fail (index_ >= 0 && index_ < Columns.N_COLUMNS, 0);
201
202                 return types[index_];
203         }
204
205         public virtual int get_n_columns () {
206                 return Columns.N_COLUMNS;
207         }
208
209         public virtual void get_value (TreeIter iter, int column, out GLib.Value value) {
210                 Movie movie;
211
212                 // FIXME
213                 if (no_poster == null) try {
214                         no_poster = new Gdk.Pixbuf.from_file ("/usr/share/icons/hicolor/64x64/hildon/general_video.png");
215                 } catch (Error e) {
216                         critical ("Missing general_video icon: %s\n", e.message);
217                 }
218
219                 return_if_fail (column >= 0 && column < Columns.N_COLUMNS);
220
221                 // Get the Movie from our parent's storage
222                 Value val;
223                 base.get_value (iter, 0, out val);
224                 movie = (Movie) val.get_object ();
225
226                 value.init (get_column_type (column));
227
228                 switch (column) {
229                 case Columns.TITLE:
230                         if (movie != null) {
231                                 value.set_string (movie.title);
232                         } else {
233                                 value.set_string ("");
234                         }
235                         break;
236
237                 case Columns.YEAR:
238                         if (movie != null) {
239                                 value.set_int (movie.year);
240                         } else {
241                                 value.set_int (-1);
242                         }
243                         break;
244
245                 case Columns.RATING:
246                         base.get_value (iter, 2, out value);
247                         break;
248
249                 case Columns.POSTER:
250                         if ((movie.poster != null) && (movie.poster.thumbnail != null))
251                                 value.set_object (movie.poster.thumbnail);
252                         else
253                                 value.set_object (no_poster);
254                         break;
255
256                 case Columns.MOVIE:
257                         value.set_object (movie);
258                         break;
259
260                 case Columns.MARKUP:
261                         base.get_value (iter, 1, out value);
262                         break;
263
264                 default:
265                         assert_not_reached ();
266                 }
267         }
268 }