/* This file is part of Cinaest. * * Copyright (C) 2009 Philipp Zabel * * Cinaest is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Cinaest 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Cinaest. If not, see . */ using Gtk; using Hildon; public class MovieListView : PannableArea { public MovieListStore store; TreeView tree; IconView icons; private bool more_movies_available; private CellRendererText title_renderer; private CellRendererText secondary_renderer; private CellRendererText rating_renderer; private CellRendererText date_renderer; private MoviePoster.Factory poster_factory; private int last_a = -1; private int last_b = -1; private bool poster_mode_; public bool poster_mode { get { return poster_mode_; } set { if (value & !poster_mode_) { remove (tree); tree.set_model (null); icons.set_model (store); store.view = (Widget) icons; add (icons); } else if (!value & poster_mode_) { remove (icons); icons.set_model (null); tree.set_model (store); store.view = (Widget) tree; add (tree); } if (value != poster_mode_) { poster_mode_ = value; last_a = -1; last_b = -1; // FIXME - just calling on_adjustment_value_changed () here doesn't work // because icons.get_visible_range () returns wrong paths. check_posters (0, 10); jump_to (0, 0); } } } public signal void movie_activated (Movie movie); private Gtk.TreeView create_treeview (Gtk.Window window, bool show_date) { // Tree View var tree = (TreeView) Hildon.gtk_tree_view_new_with_model (UIMode.NORMAL, store); tree.set_headers_visible (false); tree.set_rules_hint (true); // Tree selection object var selection = tree.get_selection (); selection.set_mode (SelectionMode.SINGLE); // Poster column var poster_column = new TreeViewColumn (); poster_column.set_title (_("Poster")); // Add poster icon to column var pixbuf_renderer = new CellRendererPixbuf (); pixbuf_renderer.width = Poster.ICON_WIDTH + Hildon.MARGIN_DEFAULT; pixbuf_renderer.xalign = 0.0f; poster_column.pack_start (pixbuf_renderer, false); poster_column.add_attribute (pixbuf_renderer, "pixbuf", MovieListStore.Columns.ICON); tree.append_column (poster_column); // Title column with secondary text var title_column = new TreeViewColumn (); title_column.set_title (_("Movie")); title_column.set_sort_column_id (MovieListStore.Columns.TITLE); title_column.set_reorderable (false); title_column.set_sizing (TreeViewColumnSizing.AUTOSIZE); title_column.set_expand (true); // Add text to column var vbox_renderer = new CellRendererVBox (); title_renderer = new CellRendererText (); title_renderer.yalign = 1.0f; title_renderer.ellipsize = Pango.EllipsizeMode.END; vbox_renderer.append (title_renderer, true); // Add secondary text to column (Genres, Director, etc.) secondary_renderer = new CellRendererText (); secondary_renderer.yalign = 0; secondary_renderer.ellipsize = Pango.EllipsizeMode.END; secondary_renderer.attributes = get_attributes (window, "SmallSystemFont", "SecondaryTextColor"); vbox_renderer.append (secondary_renderer, true); title_column.pack_start (vbox_renderer, true); title_column.set_cell_data_func (vbox_renderer, title_data_func); tree.append_column (title_column); // Year column var year_column = new TreeViewColumn (); year_column.set_title (_("Year")); year_column.set_sort_column_id (MovieListStore.Columns.YEAR); year_column.set_reorderable (false); year_column.set_sort_order (SortType.DESCENDING); tree.append_column (year_column); // Rating column var rating_column = new TreeViewColumn (); rating_column.set_title (_("Rating")); rating_column.set_sort_column_id (MovieListStore.Columns.RATING); rating_column.set_reorderable (false); rating_column.set_sort_order (SortType.DESCENDING); rating_column.xalign = 1.0f; vbox_renderer = new CellRendererVBox (); rating_renderer = new CellRendererText (); rating_renderer.xalign = 1.0f; if (show_date) rating_renderer.yalign = 1.0f; vbox_renderer.append (rating_renderer, true); date_renderer = new CellRendererText (); date_renderer.yalign = 0; date_renderer.attributes = get_attributes (window, "SmallSystemFont", "SecondaryTextColor"); if (show_date) vbox_renderer.append (date_renderer, true); rating_column.pack_start (vbox_renderer, true); rating_column.set_cell_data_func (vbox_renderer, rating_data_func); tree.append_column (rating_column); tree.show (); return tree; } private Gtk.IconView create_iconview () { var iconview = (Gtk.IconView) Hildon.gtk_icon_view_new (Hildon.UIMode.NORMAL); iconview.set_column_spacing (0); iconview.set_pixbuf_column (MovieListStore.Columns.POSTER); iconview.margin = 0; iconview.item_width = Poster.SMALL_WIDTH; iconview.column_spacing = Hildon.MARGIN_HALF; iconview.row_spacing = Hildon.MARGIN_HALF; iconview.show (); return iconview; } public MovieListView (Gtk.Window window, bool show_date = false) { store = new MovieListStore (); // Sort by title store.set_sort_column_id (MovieListStore.Columns.TITLE, SortType.ASCENDING); Gdk.Color color; window.ensure_style (); if (window.style.lookup_color ("SecondaryTextColor", out color)) { store.year_markup = "(%%d)".printf (color.to_string ()); } tree = create_treeview (window, show_date); store.view = (Widget) tree; icons = create_iconview (); add (tree); // Connect signals get_vadjustment ().value_changed.connect (on_adjustment_value_changed); tree.row_activated.connect (on_row_activated); icons.item_activated.connect (on_item_activated); store.row_changed.connect (on_row_changed); store.row_inserted.connect (on_row_changed); store.search_finished.connect (on_search_finished); } construct { hscrollbar_policy = Gtk.PolicyType.NEVER; poster_factory = MoviePoster.Factory.get_instance (); } public void set_hildon_ui_mode (UIMode mode) { var selection = tree.get_selection (); if (mode == UIMode.NORMAL) { selection.set_mode (SelectionMode.NONE); icons.set_selection_mode (SelectionMode.NONE); } Hildon.gtk_tree_view_set_ui_mode (tree, mode); Hildon.gtk_icon_view_set_ui_mode (icons, mode); if (mode == UIMode.EDIT) { selection.set_mode (SelectionMode.MULTIPLE); icons.set_selection_mode (SelectionMode.MULTIPLE); } } private Pango.AttrList get_attributes (Gtk.Window window, string font_name, string color_name) { Pango.AttrList attr_list = new Pango.AttrList (); var style = Gtk.rc_get_style_by_paths (Gtk.Settings.get_default (), font_name, null, typeof (void)); if (style != null) { var attr_font_desc = new Pango.AttrFontDesc (style.font_desc.copy ()); attr_list.insert ((owned) attr_font_desc); } Gdk.Color color; window.ensure_style (); if (window.style.lookup_color (color_name, out color)) { Pango.Attribute attr_color = Pango.attr_foreground_new (color.red, color.green, color.blue); attr_list.insert ((owned) attr_color); } return attr_list; } public void unselect_all () { tree.get_selection ().unselect_all (); icons.unselect_all (); } public List get_selected_movies () { var movies = new List (); List paths; if (poster_mode_) paths = icons.get_selected_items (); else paths = tree.get_selection ().get_selected_rows (null); // get selected movies from the store foreach (TreePath path in paths) { TreeIter iter; if (store.get_iter (out iter, path)) { Movie movie; store.get (iter, MovieListStore.Columns.MOVIE, out movie); if (movie != null) { movies.append (movie); } } } return movies; } // TODO: after scrolling down 80% of the list, load more // results if available. private void on_adjustment_value_changed () { if (more_movies_available) { var vadj = get_vadjustment (); if (vadj.value > 0.8 * vadj.upper) { Banner.show_information (this, null, _("More results available - refine search to reduce the dataset")); more_movies_available = false; } } TreePath a_; TreePath b_; bool range; if (poster_mode_) { range = icons.get_visible_range (out a_, out b_); } else { range = tree.get_visible_range (out a_, out b_); } if (range) { // We know the list store is flat int a = a_.get_indices ()[0]; int b = b_.get_indices ()[0]; assert (a <= b); if (a == last_a && b == last_b) return; if (a > last_b || b < last_a) { check_posters (a, b); } else if (a >= last_a && b > last_b) { check_posters (last_b + 1, b); } else if (b <= last_b && a < last_a) { check_posters (a, last_a - 1); } last_a = a; last_b = b; } } private void check_posters (int a, int b) { for (int i = a; i <= b; i++) { var path = new TreePath.from_indices (i); TreeIter iter; if (!store.get_iter (out iter, path)) continue; Movie movie; store.get (iter, MovieListStore.Columns.MOVIE, out movie); if (movie == null) continue; if (poster_mode_) { if (movie.poster == null || movie.poster.small == null) try { poster_factory.queue (movie, Poster.SMALL_WIDTH, Poster.SMALL_HEIGHT, true, receive_poster_small); } catch (Error e) { warning ("Failed to queue poster request: %s\n", e.message); } } else { if (movie.poster == null || movie.poster.icon == null) try { poster_factory.queue (movie, Poster.ICON_WIDTH, Poster.ICON_HEIGHT, true, receive_poster_icon); } catch (Error e) { warning ("Failed to queue poster request: %s\n", e.message); } } } } private void on_row_activated (TreeView tree, TreePath path, TreeViewColumn column) { on_item_activated (path); } private void on_item_activated (TreePath path) { TreeIter iter; if (store.get_iter (out iter, path)) { Movie movie; store.get (iter, MovieListStore.Columns.MOVIE, out movie); movie_activated (movie); } } private void on_row_changed (TreePath path, TreeIter iter) { TreePath a; TreePath b; bool range; if (poster_mode_) { range = icons.get_visible_range (out a, out b); } else { range = tree.get_visible_range (out a, out b); } if (!range || (range && path.compare (a) >= 0 && path.compare (b) <= 0)) { Movie movie; store.get (iter, MovieListStore.Columns.MOVIE, out movie); if (movie == null) return; int i = path.get_indices ()[0]; check_posters (i, i); } } private void receive_poster_small (Gdk.Pixbuf pixbuf, Movie movie) { Poster poster; if (movie.poster != null) poster = movie.poster; else poster = new Poster (); poster.small = pixbuf; // Notify store of the poster change movie.poster = poster; } private void receive_poster_icon (Gdk.Pixbuf pixbuf, Movie movie) { Poster poster; if (movie.poster != null) poster = movie.poster; else poster = new Poster (); poster.icon = pixbuf; // Notify store of the poster change movie.poster = poster; } private void on_search_finished (int movies) { more_movies_available = (movies > 100); // FIXME } private void title_data_func (CellLayout cell_layout, CellRenderer cell, TreeModel model, TreeIter iter) { Movie movie; string markup; model.get (iter, MovieListStore.Columns.MOVIE, out movie, MovieListStore.Columns.MARKUP, out markup); title_renderer.markup = markup; secondary_renderer.text = movie.secondary; } private void rating_data_func (CellLayout cell_layout, CellRenderer cell, TreeModel model, TreeIter iter) { string rating; Movie movie; model.get (iter, MovieListStore.Columns.RATING, out rating, MovieListStore.Columns.MOVIE, out movie); rating_renderer.text = rating; if (movie.julian_date != 0) { var date = Date (); date.set_julian (movie.julian_date); var s = new char[64]; date.strftime (s, "%x"); date_renderer.text = (string) s; } else { date_renderer.text = ""; } } }