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