[ease/text] New Presentation window switched to IconView



commit 582cc4b0ddff07695f8102418a79a1978671a274
Author: Nate Stedman <natesm gmail com>
Date:   Wed Dec 1 05:10:48 2010 -0500

    New Presentation window switched to IconView
    
    * WelcomeWindow renamed to NewPresentationWindow.
    * IconView actually emits selection_changed

 ...elcome-window.ui => new-presentation-window.ui} |   10 +-
 ease-core/ease-icon-view.vala                      |    3 +
 ease/Makefile.am                                   |    5 +-
 ease/ease-main.vala                                |   56 ++--
 ease/ease-new-presentation-window.vala             |  398 ++++++++++++++++++
 ease/ease-slide-button-panel.vala                  |   17 +-
 ease/ease-welcome-actor.vala                       |  246 -----------
 ease/ease-welcome-window.vala                      |  437 --------------------
 8 files changed, 449 insertions(+), 723 deletions(-)
---
diff --git a/data/ui/welcome-window.ui b/data/ui/new-presentation-window.ui
similarity index 91%
rename from data/ui/welcome-window.ui
rename to data/ui/new-presentation-window.ui
index a607848..2fa41c8 100644
--- a/data/ui/welcome-window.ui
+++ b/data/ui/new-presentation-window.ui
@@ -1,4 +1,4 @@
-<?xml version="1.0"?>
+<?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <requires lib="gtk+" version="2.16"/>
   <!-- interface-naming-policy project-wide -->
@@ -32,7 +32,7 @@
               <object class="GtkSpinButton" id="horiz_spin">
                 <property name="visible">True</property>
                 <property name="can_focus">True</property>
-                <property name="invisible_char">&#x2022;</property>
+                <property name="invisible_char">â?¢</property>
                 <property name="adjustment">adjustment1</property>
               </object>
               <packing>
@@ -45,7 +45,7 @@
               <object class="GtkSpinButton" id="vert_spin">
                 <property name="visible">True</property>
                 <property name="can_focus">True</property>
-                <property name="invisible_char">&#x2022;</property>
+                <property name="invisible_char">â?¢</property>
                 <property name="adjustment">adjustment2</property>
               </object>
               <packing>
@@ -61,7 +61,7 @@
                 <property name="can_focus">True</property>
                 <property name="receives_default">True</property>
                 <property name="use_underline">True</property>
-                <signal name="clicked" handler="ease_welcome_window_create_new_document"/>
+                <signal name="clicked" handler="ease_new_presentation_window_create_new_document"/>
               </object>
               <packing>
                 <property name="expand">False</property>
@@ -79,7 +79,7 @@
                 <property name="can_focus">True</property>
                 <property name="receives_default">True</property>
                 <property name="use_stock">True</property>
-                <signal name="clicked" handler="ease_welcome_window_on_open_pres_button_clicked"/>
+                <signal name="clicked" handler="ease_new_presentation_window_on_open_pres_button_clicked"/>
               </object>
               <packing>
                 <property name="expand">False</property>
diff --git a/ease-core/ease-icon-view.vala b/ease-core/ease-icon-view.vala
index e453783..40c651e 100644
--- a/ease-core/ease-icon-view.vala
+++ b/ease-core/ease-icon-view.vala
@@ -204,6 +204,7 @@ public class Ease.IconView : Clutter.Group
 		layout.homogeneous = true;
 		row_spacing = 6;
 		column_spacing = 6;
+		item_width = 100;
 		text_color = { 0, 0, 0, 255 };
 		
 		// size the box appropriately
@@ -501,6 +502,8 @@ public class Ease.IconView : Clutter.Group
 				}
 			}
 		}
+		
+		selection_changed(this);
 	}
 	
 	/**
diff --git a/ease/Makefile.am b/ease/Makefile.am
index 37b2b37..d57588e 100644
--- a/ease/Makefile.am
+++ b/ease/Makefile.am
@@ -20,13 +20,12 @@ ease_SOURCES = \
 	ease-inspector-transition-pane.vala \
 	ease-inspector.vala \
 	ease-main.vala \
+	ease-new-presentation-window.vala \
 	ease-player.vala \
 	ease-selection-rectangle.vala \
 	ease-slide-actor.vala \
 	ease-slide-button-panel.vala \
-	ease-slide-sorter.vala \
-	ease-welcome-actor.vala \
-	ease-welcome-window.vala
+	ease-slide-sorter.vala
 
 # compiler flags
 ease_VALAFLAGS = \
diff --git a/ease/ease-main.vala b/ease/ease-main.vala
index 35f0a66..72747f8 100644
--- a/ease/ease-main.vala
+++ b/ease/ease-main.vala
@@ -18,7 +18,7 @@
 internal class Ease.Main : GLib.Object
 {
 	private static Gee.ArrayList<EditorWindowInfo> windows;
-	private static WelcomeWindow welcome;
+	private static NewPresentationWindow new_presentation;
 	private static Unique.App app;
 	
 	// options
@@ -50,8 +50,8 @@ internal class Ease.Main : GLib.Object
 	 * Start Ease to edit files.
 	 * 
 	 * If the user runs Ease with a filename as a parameter, this function
-	 * will open an { link EditorWindow}. Otherwise, a { link WelcomeWindow}
-	 * will be opened.
+	 * will open an { link EditorWindow}. Otherwise, a
+	 * { link NewPresentationWindow} will be opened.
 	 *
 	 * @param args Program arguments.
 	 */
@@ -107,7 +107,7 @@ internal class Ease.Main : GLib.Object
 						play_file(data.get_filename(), false);
 						return Unique.Response.OK;
 					case UniqueCommand.SHOW_WELCOME:
-						show_welcome();
+						show_new_presentation();
 						return Unique.Response.OK;
 				}
 				
@@ -169,7 +169,7 @@ internal class Ease.Main : GLib.Object
 		// if no files are given, show the new presentation window
 		if (filenames == null && play_filename == null)
 		{
-			if (!running) show_welcome();
+			if (!running) show_new_presentation();
 			else app.send_message(UniqueCommand.SHOW_WELCOME, null);
 		}
 		
@@ -269,9 +269,10 @@ internal class Ease.Main : GLib.Object
 	 * Removes an { link EditorWindow} from Ease's internal store of windows.
 	 * 
 	 * Ease tracks the current windows in order to properly quit when there
-	 * are no { link EditorWindow}s on screen and the { link WelcomeWindow} is
-	 * hidden. This function will quit Ease if the removed window is the final
-	 * window and the { link WelcomeWindow} is hidden.
+	 * are no { link EditorWindow}s on screen and the
+	 * { link NewPresentationWindow} is hidden. This function will quit Ease
+	 * if the removed window is the final window and the
+	 * { link NewPresentationWindow} is hidden.
 	 *
 	 * @param win The { link EditorWindow}.
 	 */
@@ -288,7 +289,7 @@ internal class Ease.Main : GLib.Object
 		win.play.disconnect(on_play);
 		win.close.disconnect(on_close);
 		
-		if (windows.size == 0 && welcome == null)
+		if (windows.size == 0 && new_presentation == null)
 		{
 			Gtk.main_quit();
 		}
@@ -298,8 +299,8 @@ internal class Ease.Main : GLib.Object
 	 * Adds an { link EditorWindow} to Ease's internal store of windows.
 	 * 
 	 * Ease tracks the current windows in order to properly quit when there
-	 * are no { link EditorWindow}s on screen and the { link WelcomeWindow} is
-	 * hidden. 
+	 * are no { link EditorWindow}s on screen and the
+	 * { link NewPresentationWindow} is hidden. 
 	 *
 	 * @param win The { link EditorWindow}.
 	 */
@@ -346,37 +347,38 @@ internal class Ease.Main : GLib.Object
 	}
 
 	/**
-	 * Shows the { link WelcomeWindow}
+	 * Shows the { link NewPresentationWindow}
 	 * 
-	 * Shows the { link WelcomeWindow}, or raises it to the top if it is not
-	 * already displayed.
+	 * Shows the { link NewPresentationWindow}, or raises it to the top if it
+	 * is already displayed.
 	 *
 	 */
-	internal static void show_welcome()
+	internal static void show_new_presentation()
 	{
-		if (welcome == null)
+		if (new_presentation == null)
 		{
-			welcome = new WelcomeWindow();
-			welcome.hide.connect(() => remove_welcome());
+			new_presentation = new NewPresentationWindow();
+			new_presentation.hide.connect(() => remove_new_presentation());
 		}
 		else
 		{
-			welcome.present();
+			new_presentation.present();
 		}
 	}
 
 	/**
-	 * Hides the { link WelcomeWindow}.
+	 * Hides the { link NewPresentationWindow}.
 	 * 
-	 * It's important to call this function when the { link WelcomeWindow} is
-	 * hidden, so that Ease can properly exit when all windows are closed.
-	 * When the { link WelcomeWindow} is shown via show_welcome, this function
-	 * is automatically added in that window's hide signal handler.
+	 * It's important to call this function when the
+	 * { link NewPresentationWindow} is hidden, so that Ease can properly exit
+	 * when all windows are closed. When the { link NewPresentationWindow} is
+	 * shown via show_new_presentation, this function is automatically added in
+	 * that window's hide signal handler.
 	 */
-	internal static void remove_welcome()
+	internal static void remove_new_presentation()
 	{
-		welcome.hide_all();
-		welcome = null;
+		new_presentation.hide_all();
+		new_presentation = null;
 		if (windows.size == 0)
 		{
 			Gtk.main_quit();
diff --git a/ease/ease-new-presentation-window.vala b/ease/ease-new-presentation-window.vala
new file mode 100644
index 0000000..27e3896
--- /dev/null
+++ b/ease/ease-new-presentation-window.vala
@@ -0,0 +1,398 @@
+/*  Ease, a GTK presentation application
+    Copyright (C) 2010 Nate Stedman
+
+    This program 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.
+
+    This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * The window shown when Ease starts and when the user creates a new
+ * presentation.
+ */
+internal class Ease.NewPresentationWindow : Gtk.Window
+{
+	// main buttons
+	private Gtk.Button new_pres_button;
+	private Gtk.Button open_pres_button;
+	private Gtk.ComboBox combores;
+	private Gtk.SpinButton x_res;
+	private Gtk.SpinButton y_res;
+	
+	// zoom widgets
+	private ZoomSlider zoom_slider;
+
+	// clutter view
+	private ScrolledEmbedWindow scrolled;
+	
+	// icon view that displays slide previews
+	private IconView icon_view;
+	
+	// store for the previews and slides
+	private IterableListStore model;
+	private const int THEME_COL = 0;
+	private const int TEXT_COL = 1;
+	private const int PIXBUF_COL = 2;
+	
+	/**
+	 * The currently selected Theme.
+	 */
+	private Theme selected_theme = null;
+	
+	/**
+	 * The slide identifier to display as a preview.
+	 */
+	private const string PREVIEW_SLIDE = Theme.TITLE;
+	
+	/**
+	 * The set of resolutions to display in a combo box. Of course, the user is
+	 * free to specify a custom resolution with the spin buttons.
+	 */
+	private static int[] RESOLUTIONS_X = { 640, 800, 1024, 1280, 1280, 1920 };
+	private static int[] RESOLUTIONS_Y = { 480, 600,  768,  720, 1024, 1080 };
+	
+	/**
+	 * The index of the default resolution in { link RESOLUTIONS_X} (and y).
+	 */
+	private const int DEFAULT_RESOLUTION = 2;
+	
+	/**
+	 * The number of resolutions in { link RESOLUTIONS_X} (and y).
+	 */
+	private const int RESOLUTION_COUNT = 6;
+	
+	/**
+	 * The values that the animated zoom slider will animate to when its
+	 * buttons are clicked.
+	 */
+	private int[] ZOOM_VALUES = {100, 150, 200, 250, 400};
+	
+	/**
+	 * The starting point for the zoom slider.
+	 */
+	private const int SLIDER_START = 190;
+	
+	/**
+	 * The amount of space displayed around and between items of the icon view
+	 * that displays the available themes.
+	 */
+	private const int ICON_PADDING = 15;
+	
+	/**
+	 * The slide to use for theme previews.
+	 */
+	private const string PREVIEW_ID = Theme.TITLE;
+	
+	internal NewPresentationWindow()
+	{
+		title = _("New Presentation");
+		set_default_size(640, 480);
+		
+		var builder = new Gtk.Builder();
+		try
+		{
+			string ui_path =
+				data_path(Path.build_filename(Temp.UI_DIR,
+				                              "new-presentation-window.ui"));
+			builder.add_from_file(ui_path);
+		}
+		catch (Error e)
+		{
+			critical("Unable to load UI: %s", e.message);
+		}
+
+		var vbox = builder.get_object("vbox1") as Gtk.VBox;
+		var hbox = builder.get_object("hbox1") as Gtk.HBox;
+		combores = builder.get_object("combo_resolution") as Gtk.ComboBox;
+		x_res = builder.get_object("horiz_spin") as Gtk.SpinButton;
+		y_res = builder.get_object("vert_spin") as Gtk.SpinButton;
+		new_pres_button = builder.get_object("newpres") as Gtk.Button;
+		open_pres_button = builder.get_object("openpres") as Gtk.Button;
+
+		// zoom slider
+		zoom_slider = new AnimatedZoomSlider(new Gtk.Adjustment(100, 100, 400,
+		                                     10, 50, 50), ZOOM_VALUES);
+		hbox.pack_start(zoom_slider, true, false, 0);
+		hbox.reorder_child(zoom_slider, 4);
+		zoom_slider.sliderpos = SLIDER_START;
+
+		// Resolutions combo box
+		// FIXME : not re-create it, or do it from Glade.
+		hbox.remove(combores);
+		combores = new Gtk.ComboBox.text();
+		combores.insert_text(0, _("Custom"));
+		for (var i = 0; i < RESOLUTION_COUNT; i++)
+		{
+			combores.append_text(_("%i by %i").printf(RESOLUTIONS_X[i],
+			                                          RESOLUTIONS_Y[i]));
+		}
+		
+		combores.changed.connect(() => {
+			var val = combores.get_active();
+			if (val > 0) {
+				x_res.set_value(RESOLUTIONS_X[val - 1]);
+				y_res.set_value(RESOLUTIONS_Y[val - 1]);
+			}
+		});
+
+		hbox.pack_start(combores);
+		hbox.reorder_child(combores, 0);
+
+		// set the range of the resolution spin buttons
+		x_res.set_range(RESOLUTIONS_X[0],
+		                RESOLUTIONS_X[RESOLUTION_COUNT - 1]);
+
+		y_res.set_range(RESOLUTIONS_Y[0],
+		                RESOLUTIONS_Y[RESOLUTION_COUNT - 1]);
+		
+		// rebuild the preview thumbnails when resolution changes
+		x_res.value_changed.connect(() => {
+			reconstruct_previews((int)x_res.get_value(),
+			                     (int)y_res.get_value());
+		});
+
+		y_res.value_changed.connect(() => {
+			reconstruct_previews((int)x_res.get_value(),
+			                     (int)y_res.get_value());
+		});
+		
+		// select the default slide size, +1 to avoid "Custom"
+		combores.set_active(DEFAULT_RESOLUTION + 1);
+
+		// buttons
+		new_pres_button.sensitive = false;
+		new_pres_button.image = new Gtk.Image.from_stock("gtk-new",
+														 Gtk.IconSize.BUTTON);
+		
+		// create the upper UI - the embed
+		scrolled = new ScrolledEmbedWindow(null);
+		scrolled.hscrollbar_policy = Gtk.PolicyType.NEVER;
+		(scrolled.embed.get_stage() as Clutter.Stage).use_fog = false;
+		(scrolled.embed.get_stage() as Clutter.Stage).color = { 0, 0, 0, 255 };
+		
+		// create the icon view
+		icon_view = new Ease.IconView();
+		
+		// icon view model columns
+		icon_view.pixbuf_column = PIXBUF_COL;
+		icon_view.text_column = TEXT_COL;
+		
+		// icon view styling
+		icon_view.padding = ICON_PADDING;
+		icon_view.column_spacing = ICON_PADDING;
+		icon_view.row_spacing = ICON_PADDING;
+		icon_view.item_width = (float)zoom_slider.get_value();
+		icon_view.text_color = { 255, 255, 255, 255 };
+		
+		// icon view selection (single theme, no deselecting)
+		icon_view.selection_mode = Gtk.SelectionMode.BROWSE;
+		
+		// add the icon view to the stage
+		scrolled.embed.viewport.add_actor(icon_view);
+		
+		// enable the new button once the user selects a theme
+		icon_view.selection_changed.connect(() => {
+			new_pres_button.sensitive = true;
+			
+			// set the selected theme			
+			icon_view.selected_foreach((v, path) => {
+				Gtk.TreeIter itr;
+				Theme theme;
+				icon_view.model.get_iter(out itr, path);
+				icon_view.model.get(itr, THEME_COL, out theme);
+				selected_theme = theme;
+			});
+		});
+		
+		// create a new document of the selected theme when double clicked
+		icon_view.item_activated.connect((v, path) => {
+			// get the theme
+			Gtk.TreeIter itr;
+			Theme theme;
+			icon_view.model.get_iter(out itr, path);
+			icon_view.model.get(itr, THEME_COL, out theme);
+			selected_theme = theme;
+			
+			// create the document
+			create_new_document(null);
+		});
+		
+		// add the themes
+		try
+		{
+			// locate all installed theme directories
+			var list = locate_themes();
+			
+			// create the model
+			model = new IterableListStore({ typeof(Theme),
+			                                typeof(string),
+			                                typeof(Gdk.Pixbuf) });
+			icon_view.model = model;
+			
+			// get an iterator to the start of the list
+			Gtk.TreeIter iter;
+			
+			// add each theme to the model
+			foreach (var path in list)
+			{
+				// load the theme
+				var theme = new Theme(path);
+				
+				// append a row to the model for storage
+				model.append(out iter);
+				
+				// store the theme, theme's name, and slide in the column
+				model.set(iter, THEME_COL, theme,
+				                TEXT_COL, theme.title);
+				
+				// create a pixbuf preview and store it in the model
+				construct_preview((int)x_res.get_value(),
+				                  (int)y_res.get_value(),
+				                  ref iter);				                  
+			}
+		}
+		catch (Error e)
+		{
+			error_dialog("Error loading themes: %s", e.message);
+		}
+		
+		// place the clutter embed into the window
+		vbox.pack_start(scrolled, true, true, 0);
+		vbox.reorder_child(scrolled, 0);
+		
+		// automatically resize the icon view with the embed
+		size_allocate.connect(() => {
+			scrolled.embed.viewport.width = scrolled.embed.get_stage().width;
+			icon_view.width = scrolled.embed.viewport.width;
+		});
+		
+		// change the zoom of the previews when the zoom slider is moved
+		zoom_slider.value_changed.connect(() => {
+			icon_view.item_width = (float)zoom_slider.get_value();
+		});
+
+		// connect signals and build the window
+		builder.connect_signals(this);
+		add(vbox);
+		show_all();
+	}
+
+	[CCode (instance_pos = -1)]
+	internal void on_open_pres_button_clicked(Gtk.Widget sender)
+	{
+		var filename = Dialog.open_document(this);
+		if (filename != null)
+		{
+			Main.open_file(filename);
+			hide();
+		}
+	}
+
+	[CCode (instance_pos = -1)]
+	internal void create_new_document(Gtk.Widget? sender)
+	{
+		Main.new_from_theme(selected_theme,
+		                    (int)x_res.get_value(),
+		                    (int)y_res.get_value());
+		hide();
+	}
+	
+	private void set_resolution_box(int width, int height)
+	{
+		for (var i = 0; i < RESOLUTION_COUNT; i++)
+		{
+			if (width == RESOLUTIONS_X[i] && height == RESOLUTIONS_Y[i])
+			{
+				combores.set_active(i + 1);
+				return;
+			}
+		}
+		combores.set_active(0);
+	}
+		
+	private extern const string DATA_DIR;
+	private Gee.LinkedList<string> locate_themes() throws GLib.Error
+	{
+		var list = new Gee.LinkedList<string>();
+		foreach (var item in data_contents_folder("themes"))
+		{
+			var f = File.new_for_path(Path.build_filename(item, "Theme.json"));
+			if (f.query_exists(null) && theme_not_redundant(item, list))
+			{
+				list.add(item);
+			}
+		}
+		return list;
+	}
+	
+	// TODO: this isn't a very smart method. add versions to themes, check those
+	private bool theme_not_redundant(string item, Gee.List<string> list)
+	{
+		foreach (var str in list)
+		{
+			if (File.new_for_path(str).get_basename() == 
+			    File.new_for_path(item).get_basename())
+				return false;
+		}
+		return true;
+	}
+	
+	/**
+	 * Creates a slide preview and saves it in the model's appropriate column.
+	 */
+	private void construct_preview(int width, int height, ref Gtk.TreeIter iter)
+	{
+		// get the theme
+		Theme theme;
+		model.get(iter, THEME_COL, out theme);
+		
+		// create the slide to be previewed
+		var slide = theme.create_slide(PREVIEW_SLIDE,
+		                               (int)x_res.get_value(),
+		                               (int)y_res.get_value());
+		
+		// set the special text on the slide
+		foreach (var element in slide)
+		{
+			element.has_been_edited = true;
+			switch (element.identifier)
+			{
+				case Theme.TITLE_TEXT:
+					(element as TextElement).text.clear_set(theme.title,
+					                                        null);
+					break;
+				case Theme.AUTHOR_TEXT:
+					(element as TextElement).text.clear_set(
+						Environment.get_real_name(), null);
+					break;
+			}
+		}
+		
+		// create the pixbuf
+		var pixbuf = SlideButtonPanel.pixbuf_sized(slide, 500, width, height);
+		
+		// set the image
+		model.set(iter, PIXBUF_COL, pixbuf);
+	}
+	
+	/**
+	 * Reconstructs all previews.
+	 */
+	private void reconstruct_previews(int width, int height)
+	{
+		debug("remake");
+		foreach (var iter in model)
+		{
+			construct_preview(width, height, ref iter);
+		}
+	}
+}
diff --git a/ease/ease-slide-button-panel.vala b/ease/ease-slide-button-panel.vala
index 39a27a2..b649ba3 100644
--- a/ease/ease-slide-button-panel.vala
+++ b/ease/ease-slide-button-panel.vala
@@ -177,18 +177,25 @@ internal class Ease.SlideButtonPanel : Gtk.ScrolledWindow
 	 */
 	internal static Gdk.Pixbuf? pixbuf(Slide slide, int width)
 	{
-		var height = (int)((float)width * slide.height /
-		                                  slide.width);
+		return pixbuf_sized(slide, width, slide.width, slide.height);
+	}
+	
+	internal static Gdk.Pixbuf pixbuf_sized(Slide slide, int width,
+	                                        int slide_width, int slide_height)
+	{
+		var height = (int)((float)width * slide_height /
+		                                  slide_width);
 		var surface = new Cairo.ImageSurface(Cairo.Format.RGB24, width, height);
 		
 		var context = new Cairo.Context(surface);
 		context.save();
-		context.scale((float)width / slide.width,
-		              (float)height / slide.height);
+		context.scale((float)width / slide_width,
+		              (float)height / slide_height);
 		
 		try
 		{
-			slide.cairo_render(context, width < 100);
+			slide.cairo_render_sized(context, slide_width, slide_height,
+			                         width < 100);
 		}
 		catch (GLib.Error e)
 		{



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]