// // Copyright (C) 2008-2010 Jordi Mas i Hernandez, jmas@softcatala.org // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // using System; using Gtk; using Mono.Unix; using Cairo; using System.Collections; using System.Collections.Generic; using System.Timers; using Mistelix.Widgets; using Mistelix.DataModel; using Mistelix.Transitions; using Mistelix.Core; namespace Mistelix.Dialogs { // // This class supports the execution of AddSlideDialog. When running as a Modal dialog box the Run loop exists when we change // visibility, something that we need to do when previewing the slideshow. This class encapsulates the running context // static public class AddSlideDialogRunner { static AddSlideDialog dialog; static ResponseType response; static AddSlideDialogRunner () { } static public AddSlideDialog Dialog { get {return dialog; } } static public void Create (Project project) { if (dialog != null) dialog.Destroy (); dialog = new AddSlideDialog (project); } static public ResponseType Run () { response = (ResponseType) dialog.Run (); return response; } static public void ProcessResponse () { // ResponseType.None is returned when the loop exits with no action (change dlg box visiblity) if (response == ResponseType.None) return; dialog.Destroy (); dialog = null; } } // UI interface to create new Slideshows public class AddSlideDialog : BuilderDialog { public enum ColumnsCombo // Column description within the transitions store { Column_DisplayName, Column_Data } ImagesFileView file_view; SlideShowImageView image_view; ListStore transition_store, textposition_store; SlideImage selected_image; SlideShow slide; TransitionPreview drawing_area; bool edit_mode; string audiofile; Project project; const int COL_DISPLAYNAME = 0; [GtkBeans.Builder.Object] Gtk.VBox left_vbox; [GtkBeans.Builder.Object ("scrolledwindow_files")] Gtk.ScrolledWindow scrolledwin_files; [GtkBeans.Builder.Object ("vertical_paned")] Gtk.HPaned vpaned; [GtkBeans.Builder.Object ("vpaned_horizontal")] Gtk.VPaned hpaned; [GtkBeans.Builder.Object ("scrolled_images")] Gtk.ScrolledWindow scrolled_images; // View the slide images [GtkBeans.Builder.Object] Gtk.SpinButton duration_spin; [GtkBeans.Builder.Object] Gtk.ComboBox transition_combo; [GtkBeans.Builder.Object] Gtk.HBox ctrls_hbox; [GtkBeans.Builder.Object] Gtk.Button up_button; [GtkBeans.Builder.Object] Gtk.Button down_button; [GtkBeans.Builder.Object] Gtk.Button preview_button; [GtkBeans.Builder.Object] Gtk.Button audio_button; [GtkBeans.Builder.Object] Gtk.VBox vbox_dir; [GtkBeans.Builder.Object] Gtk.ComboBox textposition_combo; // TODO: Since allows editing probably should be renamed to SlideDialog public AddSlideDialog (Project project) : base ("AddSlideDialog.ui", "addslide") { this.project = project; LoadWindowPosition (); image_view = new SlideShowImageView (); image_view.ChangeEvent = new ShowImageSelectionEventHandler (OnChangeImage); image_view.UpdatedElements = new ShowImageUpdatedElementsEventHandler (OnUpdatedImages); scrolled_images.Add (image_view); up_button.Clicked += new EventHandler (OnButtonUp); down_button.Clicked += new EventHandler (OnButtonDown); audio_button.Clicked += new EventHandler (OnButtonAudio); file_view = new ImagesFileView (); new DirectoryView (vbox_dir, new ChangeDirectoryEventHandler (OnDirectoryChanged), Mistelix.Preferences.GetStringValue (Preferences.ImagesDirectoryKey)); scrolledwin_files.Add (file_view); drawing_area = new TransitionPreview (); ctrls_hbox.Add (drawing_area); drawing_area.Visible = true; file_view.ShowAll (); left_vbox.ShowAll (); vpaned.Position = 650; // W (left) hpaned.Position = 300; // H (right) transition_store = new ListStore (typeof (string), typeof (string)); // DisplayName, Name LoadTransitionsIntoCombo (); transition_combo.Model = transition_store; CellRenderer transition_cell = new CellRendererText (); transition_combo.PackStart (transition_cell, true); transition_combo.SetCellDataFunc (transition_cell, Utils.ComboBoxCellFunc); transition_store.SetSortFunc (0, new Gtk.TreeIterCompareFunc (TransitionSort)); transition_store.SetSortColumnId (0, SortType.Ascending); textposition_store = new ListStore (typeof (string), typeof (int)); // DisplayName, int textposition_combo.Model = textposition_store; CellRenderer textposition_cell = new CellRendererText (); textposition_combo.PackStart (textposition_cell, true); textposition_combo.SetCellDataFunc (textposition_cell, Utils.ComboBoxCellFunc); LoadTextPositionsIntoCombo (); SetTransisitionToNone (); ImageControlsSensitive (false); OnUpdatedImages (this, EventArgs.Empty); duration_spin.ValueChanged += new EventHandler (TimeValueChanged); } public void LoadSlideShow (SlideShow slide) { this.slide = slide; image_view.LoadSlideShow (slide); audiofile = slide.AudioFile; edit_mode = true; } public SlideShow GenerateSlideShow () { Logger.Debug ("AddSlideDialog.GenerateSlideShow."); if (edit_mode == true) { // Reuse the same object, only store the images again slide.images.Clear (); } else { slide = new SlideShow (); } TreeIter iter; bool more; SlideImage image; more = image_view.Model.GetIterFirst (out iter); slide.AudioFile = audiofile; while (more) { image = (SlideImage) image_view.Model.GetValue (iter, SlideShowImageView.COL_OBJECT); image.Project = this.project; slide.images.Add (image); more = image_view.Model.IterNext (ref iter); } return slide; } void OnDirectoryChanged (object sender, ChangeDirectoryEventArgs args) { file_view.OnDirChanged (args.Directory); } void OnButtonUp (object sender, EventArgs args) { image_view.MoveUpSelectedElements (); } void OnButtonDown (object sender, EventArgs args) { image_view.MoveDownSelectedElements (); } void ImageControlsSensitive (bool sensitive) { transition_combo.Sensitive = sensitive; duration_spin.Sensitive = sensitive; } void SetTransisitionToNone () { TreeIter iter; transition_store.GetIterFirst (out iter); transition_combo.SetActiveIter (iter); } void LoadTransitionsIntoCombo () { foreach (TransitionManager.TransitionName name in TransitionManager.NameList) transition_store.AppendValues (name.DisplayName, name.Name); } void TimeValueChanged (object source, System.EventArgs args) { SpinButton spinner; TreeSelection selection; TreeIter iter; TreeModel model; SlideImage image; TreePath[] paths; spinner = source as SpinButton; selection = (image_view as TreeView).Selection; // Establish new time value on the selected items paths = selection.GetSelectedRows (out model); foreach (TreePath path in paths) { model.GetIter (out iter, path); image = (SlideImage) model.GetValue (iter, SlideShowImageView.COL_OBJECT); image.ShowTime = spinner.ValueAsInt; } } void OnSaveSlide (object sender, EventArgs args) { Respond (ResponseType.Ok); } // This is a particular case since the Cancel button is not part of the standard dlg button bar void OnCancel (object sender, EventArgs args) { Respond (ResponseType.Cancel); } void OnTransitionComboChanged (object sender, EventArgs args) { ComboBox combo = sender as ComboBox; TreeIter iter; TreeModel model; SlideImage image; string transition; TreeSelection selection; TreePath[] paths; Logger.Debug ("AddSlideDialog.OnTransitionComboChanged"); if (!combo.GetActiveIter (out iter)) return; // Establish new transition on the selected items transition = (string) combo.Model.GetValue (iter, (int) ColumnsCombo.Column_Data); selection = (image_view as TreeView).Selection; paths = selection.GetSelectedRows (out model); foreach (TreePath path in paths) { model.GetIter (out iter, path); image = (SlideImage) model.GetValue (iter, SlideShowImageView.COL_OBJECT); image.Transition = transition; } drawing_area.UpdateTransitionType (transition); } void OnTextPositionComboChanged (object sender, EventArgs args) { ComboBox combo = sender as ComboBox; TreeIter iter; TreeModel model; SlideImage image; TextPosition position; TreeSelection selection; TreePath[] paths; Logger.Debug ("AddSlideDialog.OnTextPositionComboChanged"); if (!combo.GetActiveIter (out iter)) return; // Establish new text position on the selected items position = (TextPosition) combo.Model.GetValue (iter, (int) ColumnsCombo.Column_Data); selection = (image_view as TreeView).Selection; paths = selection.GetSelectedRows (out model); foreach (TreePath path in paths) { model.GetIter (out iter, path); image = (SlideImage) model.GetValue (iter, SlideShowImageView.COL_OBJECT); image.Position = position; } } // Selected image has changed. Generated from SlideShowImage. void OnChangeImage (object sender, ShowImageSelectionEventArgs args) { bool more; TreeIter iter; string transition; Logger.Debug ("AddSlideDialog.OnChangeImage. Images {0}", args.images.Length); if (args.images.Length != 1) return; selected_image = args.images [0]; // Take the first since all of them will be the same ImageControlsSensitive (true); duration_spin.Value = selected_image.ShowTime; drawing_area.UpdateTransitionType (string.Empty); // Transition for this image more = transition_store.GetIterFirst (out iter); while (more) { transition = (string) transition_store.GetValue (iter, (int) ColumnsCombo.Column_Data); if (selected_image.Transition.Equals (transition)) { transition_combo.SetActiveIter (iter); break; } more = transition_store.IterNext (ref iter); } if (more == false) SetTransisitionToNone (); // Text position for this image TextPosition position; more = textposition_store.GetIterFirst (out iter); while (more) { position = (TextPosition) textposition_store.GetValue (iter, (int) ColumnsCombo.Column_Data); if (selected_image.Position == position) { textposition_combo.SetActiveIter (iter); return; } more = textposition_store.IterNext (ref iter); } } void OnSlideShowPreview (object sender, EventArgs args) { SlideShow slide_show = GenerateSlideShow (); SlideShowView slide_show_view = new SlideShowView(slide_show); Visible = false; FullScreenWindow full_screen = new FullScreenWindow (slide_show_view); } // If there are at least two elements we enable the navigation buttons void OnUpdatedImages (object sender, EventArgs args) { bool enable; TreeIter iter; enable = image_view.ListStore.GetIterFirst (out iter); preview_button.Sensitive = enable; if (enable) enable = image_view.ListStore.IterNext (ref iter); up_button.Sensitive = enable; down_button.Sensitive = enable; } public override void FreeResources () { file_view.Dispose (); image_view.Dispose (); Destroy (); } void LoadTextPositionsIntoCombo () { for (int i = 0; i < (int) TextPosition.Length; i++) { textposition_store.AppendValues ( TextPositionConverter.FromEnum ((TextPosition) i), i); } } public override void Destroy () { SaveWindowPosition (); base.Destroy (); } void LoadWindowPosition () { int x, y, width, height; x = Mistelix.Preferences.GetIntValue (Preferences.SlideWindowX); y = Mistelix.Preferences.GetIntValue (Preferences.SlideWindowY); width = Mistelix.Preferences.GetIntValue (Preferences.SlideWindowWidth); height = Mistelix.Preferences.GetIntValue (Preferences.SlideWindowHeight); if (x==-1 && y ==-1 && width == -1 && height == -1) return; Move (x, y); Resize (width, height); } void SaveWindowPosition () { int x, y, width, height; GetPosition (out x, out y); GetSize (out width, out height); Mistelix.Preferences.SetIntValue (Preferences.SlideWindowX, x); Mistelix.Preferences.SetIntValue (Preferences.SlideWindowY, y); Mistelix.Preferences.SetIntValue (Preferences.SlideWindowWidth, width); Mistelix.Preferences.SetIntValue (Preferences.SlideWindowHeight, height); Mistelix.Preferences.Save (); } void OnButtonAudio (object sender, EventArgs args) { AudioSelectionDialog dialog = new AudioSelectionDialog (); dialog.Filename = audiofile; if (dialog.RunModal () == ResponseType.Ok) { audiofile = dialog.Filename; } } // Sorts transitions alphabetically int TransitionSort (Gtk.TreeModel model, Gtk.TreeIter a, Gtk.TreeIter b) { string name_a, name_b; name_a = (string) model.GetValue (a, COL_DISPLAYNAME); name_b = (string) model.GetValue (b, COL_DISPLAYNAME); return String.Compare (name_a, name_b); } } // Transition preview public class TransitionPreview : DrawingArea { Cairo.ImageSurface img_a; Cairo.ImageSurface img_b; SlideImage slide_a; SlideImage slide_b; Transition images_transition; SlideImage img; bool next; System.Timers.Timer timer; const int PREVIEW_WIDTH = 100; const int PREVIEW_HEIGHT = 100; public TransitionPreview () { img_a = new Cairo.ImageSurface (Format.Argb32, PREVIEW_WIDTH, PREVIEW_HEIGHT); Cairo.Context gr = new Cairo.Context (img_a); // Translators: Used as an example of how a transition looks like, transitioning from 'A' to 'B' DrawTextCentered (gr, PREVIEW_WIDTH / 2, PREVIEW_HEIGHT / 2, Catalog.GetString ("A")); ((IDisposable)gr).Dispose (); img_b = new Cairo.ImageSurface (Format.Argb32, PREVIEW_WIDTH, 100); gr = new Cairo.Context (img_b); // Translators: Used as an example of how a transition looks like, transitioning from 'A' to 'B' DrawTextCentered (gr, PREVIEW_WIDTH / 2, PREVIEW_HEIGHT / 2, Catalog.GetString ("B")); ((IDisposable)gr).Dispose (); slide_a = new SlideImage (img_a); slide_b = new SlideImage (img_b); timer = new System.Timers.Timer (); timer.Elapsed += TimerUpdater; ((IDisposable)img_a).Dispose (); ((IDisposable)img_b).Dispose (); } public void UpdateTransitionType (string type) { if (type == string.Empty || type.Equals (TransitionManager.None.Name)) { timer.Enabled = false; images_transition = null; return; } images_transition = TransitionManager.CreateFromName (type); timer.Enabled = true; images_transition.Source = slide_a; images_transition.Target = slide_b; images_transition.Frames = 25 * 3; images_transition.Reset (); next = images_transition.MoveNext (); timer.Interval = 40; timer.Enabled = true; QueueDraw (); } // From a given point centers the text into it void DrawTextCentered (Cairo.Context gr, double x, double y, string str) { int w, h; Pango.Layout layout; layout = Pango.CairoHelper.CreateLayout (gr); layout.FontDescription = Pango.FontDescription.FromString ("sans 32"); layout.SetText (str); layout.SingleParagraphMode = true; layout.Width = -1; layout.GetPixelSize (out w, out h); gr.MoveTo (x - w / 2, y - h / 2); Pango.CairoHelper.ShowLayout (gr, layout); layout.Dispose (); } void TimerUpdater (object source, ElapsedEventArgs e) { next = images_transition.MoveNext (); Application.Invoke (delegate { QueueDraw (); }); if (next) { timer.Interval = 40; } else { // Preview finished, repetition starts 3 seconds images_transition.Reset (); next = images_transition.MoveNext (); timer.Interval = 3000; timer.Enabled = true; } } protected override bool OnExposeEvent (Gdk.EventExpose args) { if (!IsRealized) return false; if (images_transition == null) return base.OnExposeEvent(args); byte[] pixels; img = (SlideImage) images_transition.Current; pixels = img.Pixels; // TODO: To change to the right signature when the new Mono.Cairo is wide spread. Fixed in Mono.Cairo r120299 ImageSurface img_sur = new ImageSurface (ref pixels, Cairo.Format.Argb32, img.Width, img.Height, img.Stride); Cairo.Context cc = Gdk.CairoHelper.Create (args.Window); cc.Save (); cc.SetSourceSurface (img_sur, 0, 0); cc.Paint (); cc.Restore (); img_sur.Destroy (); ((IDisposable)cc).Dispose(); return base.OnExposeEvent(args); } protected override void OnUnrealized () { timer.Enabled = false; timer.Elapsed -= TimerUpdater; } } }