// // Copyright (C) 2008-2009 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 System.IO; using System.Collections.Generic; using System.ComponentModel; using Gdk; using Cairo; using Mono.Unix; using Mono.Addins; using Mistelix.DataModel; using Mistelix.Core; using Mistelix.Effects; namespace Mistelix.Widgets { public delegate void ShowImageSelectionEventHandler (object sender, ShowImageSelectionEventArgs e); public class ShowImageSelectionEventArgs: EventArgs { public SlideImage[] images; public ShowImageSelectionEventArgs (SlideImage[] images) { this.images = images; } } public delegate void ShowImageUpdatedElementsEventHandler (object sender, EventArgs e); // // Displays the images selected by the user when creating a slideshow. Drop target. // public class SlideShowImageView : TreeView, IDisposable, IComparer { enum ImageSortType { FileName_Ascending, FileName_Descending, Date_Ascending, Date_Descending } ListStore store; BackgroundWorker thumbnailing; int next_id = 1; bool paint_control = false; readonly int thumbnail_height; readonly int thumbnail_width; readonly string notitle; ImageSortType sort_type; Effect[] effects; Cairo.ImageSurface def_image; public const int COL_INDEX = 0; public const int COL_CAIROIMAGE = 1; public const int COL_DESCRIPTION = 2; public const int COL_OBJECT = 3; public const string EFFECT = "effect"; static TargetEntry [] tag_dest_target_table = new TargetEntry [] { new TargetEntry ("text/uri-list", 0, (uint) Gtk.TargetFlags.App), new TargetEntry ("application/x-mistelix-img", 0, (uint) 2) }; public ShowImageSelectionEventHandler ChangeEvent; public ShowImageUpdatedElementsEventHandler UpdatedElements; TaskDispatcher dispatcher; public SlideShowImageView () { Model = store = CreateStore (); dispatcher = new TaskDispatcher (); notitle = Catalog.GetString (""); thumbnail_height = ThumbnailSizeManager.Current.Height; thumbnail_width = ThumbnailSizeManager.Current.Width; Selection.Mode = SelectionMode.Multiple; DragDrop += new DragDropHandler (HandleTargetDragDrop); DragDataReceived += new DragDataReceivedHandler (HandleTargetDragDataReceived); Gtk.Drag.DestSet (this, DestDefaults.All, tag_dest_target_table, DragAction.Copy | DragAction.Move); CursorChanged += OnCursorChanged; thumbnailing = new BackgroundWorker (); thumbnailing.WorkerSupportsCancellation = true; thumbnailing.DoWork += new DoWorkEventHandler (DoWork); ButtonPressEvent += new ButtonPressEventHandler (OnButtonPressed); KeyPressEvent += OnKeyPressed; def_image = Utils.CreateNoPreviewImage (thumbnail_width, thumbnail_height); } ~SlideShowImageView () { Dispose (false); } public ListStore ListStore { get { return store; } } public override void Dispose () { Dispose (true); System.GC.SuppressFinalize (this); } protected virtual void Dispose (bool disposing) { Logger.Debug ("SlideShowImageView.Disposing"); store.Foreach (delegate (TreeModel model, TreePath path, TreeIter iter) { DataImageSurface image = (DataImageSurface) store.GetValue (iter, COL_CAIROIMAGE); if (image != null) ((IDisposable)image).Dispose (); return false; }); Logger.Debug ("SlideShowImageView.Disposed"); thumbnailing.CancelAsync (); thumbnailing.Dispose (); ((IDisposable)def_image).Dispose (); store.Dispose (); } public void LoadSlideShow (SlideShow slideshow) { Logger.Debug ("SlideShowImageView.LoadSlideShow. Loaded: " + slideshow.images.Count); CreateColumns (); paint_control = true; foreach (SlideShow.Image image in slideshow.images) { store.AppendValues (next_id.ToString (), null, image.Title == null ? notitle : image.Title, image); next_id++; } if (thumbnailing.IsBusy == false) thumbnailing.RunWorkerAsync (store); UpdateButtonSensitivity (); } void CreateColumns () { CellRendererText title_cell = new CellRendererText (); title_cell.Editable = true; title_cell.Edited += OnDescriptionCellEdited; AppendColumn (Catalog.GetString ("#"), new CellRendererText (), "text", COL_INDEX); AppendColumn (Catalog.GetString ("Image"), new CairoImageCellRenderer (thumbnail_width, thumbnail_height, def_image, RequestThumbnail), new TreeCellDataFunc (SetTreeIter)); AppendColumn (Catalog.GetString ("Description"), title_cell, "text", COL_DESCRIPTION); } void OnDescriptionCellEdited (object o, EditedArgs args) { TreeIter iter; SlideImage image; store.GetIter (out iter, new Gtk.TreePath (args.Path)); image = (SlideImage) store.GetValue (iter, COL_OBJECT); if (String.Compare (notitle, args.NewText) != 0) { if (args.NewText.Length == 0) { // Title has been clean up image.Title = null; store.SetValue (iter, COL_DESCRIPTION, notitle); } else { image.Title = args.NewText; store.SetValue (iter, COL_DESCRIPTION, image.Title); } } } // Sets the Iter element to paint into the CairoImageCellRenderer (shared by all cells) void SetTreeIter (Gtk.TreeViewColumn tree_column, Gtk.CellRenderer c, Gtk.TreeModel tree_model, Gtk.TreeIter iter) { ((CairoImageCellRenderer)c).Iter = iter; } void RequestThumbnail (object sender, RequestThumbnailEventArgs args) { Task task; task = new Task (args.Iter); task.DoEventHandler += DoThumbnail; dispatcher.AddTask (task); if (thumbnailing.IsBusy == false) thumbnailing.RunWorkerAsync (store); } void HandleTargetDragDrop (object sender, DragDropArgs args) { args.RetVal = false; } void HandleDragEnd (object sender, DragEndArgs args) { args.RetVal = true; } // Processing of dropped files from the FileView is done here void HandleTargetDragDataReceived (object sender, DragDataReceivedArgs args) { bool external = Gtk.Drag.GetSourceWidget (args.Context) == null; // null if the drag is from outside the app Logger.Debug ("ImageView.HandleTargetDragDataReceived"); Gdk.Cursor watch = new Gdk.Cursor (Gdk.CursorType.Watch); GdkWindow.Cursor = watch; GdkWindow.Display.Sync (); if (paint_control == false) { CreateColumns (); paint_control = true; } PathList list = new PathList (); if (external == true) list.FromExternalString (System.Text.Encoding.UTF8.GetString (args.SelectionData.Data)); else list.FromString (System.Text.Encoding.UTF8.GetString (args.SelectionData.Data)); foreach (string file in list) LoadFile (file, external); args.RetVal = true; UpdateButtonSensitivity (); Gtk.Drag.Finish (args.Context, true, false, args.Time); // Remove the cursor when the importing process is completed if (external == true) { Task task; task = new Task (this); task.DoEventHandler += delegate { Application.Invoke (delegate { GdkWindow.Cursor = null; QueueDraw (); }); }; dispatcher.AddTask (task); } else GdkWindow.Cursor = null; if (thumbnailing.IsBusy == false) thumbnailing.RunWorkerAsync (store); } // Adds a new dropped file into the view void LoadFile (string file, bool external) { FileInfo fi; if (external == false) { fi = new FileInfo (file); store.AppendValues (next_id.ToString (), null, notitle, new SlideImage (fi.FullName)); next_id++; return; } Task task; task = new Task (file); task.DoEventHandler += AddElement; dispatcher.AddTask (task); } // Element loading from dropped files is not asyncronous void AddElement (object obj, EventArgs args) { DataImageSurface cairo_image; SlideImage image; Uri uri; Task item; string filename; try { item = (Task) obj; filename = (string) item.Data; uri = new Uri (filename); filename = uri.AbsolutePath; image = new SlideImage (filename); cairo_image = image.GetThumbnail (thumbnail_width, thumbnail_height); Application.Invoke (delegate { store.AppendValues (next_id.ToString (), cairo_image, notitle, image); }); next_id++; } catch (Exception e) { Logger.Error ("SlideShowImageView.AddElement. Exception {0}", e); } } void HandleDropBegin (object sender, DragBeginArgs a) { a.RetVal = true; } ListStore CreateStore () { // Image number, image pixbuf, object return new ListStore (typeof (string), typeof (DataImageSurface), typeof (string), typeof (SlideImage)); } protected override void OnSizeAllocated (Gdk.Rectangle allocation) { // While showing the text we should redraw to center it again to new dimensions if (paint_control == false) QueueDraw (); base.OnSizeAllocated (allocation); } protected override bool OnExposeEvent (Gdk.EventExpose args) { if (paint_control == true) return base.OnExposeEvent (args); // See: http://www.mail-archive.com/mono-list@lists.ximian.com/msg26065.html using (Cairo.Context cr = Gdk.CairoHelper.Create (args.Window)) { int winWidth, winHeight, w, h; args.Window.GetSize (out winWidth, out winHeight); Pango.Layout layout = new Pango.Layout (this.PangoContext); layout.Width = winWidth * (int) Pango.Scale.PangoScale; layout.SetMarkup (Catalog.GetString ("To add images to this slideshow you can:\n\n * Drag images here from the right bottom pane\n * Drag images here from your desktop or file manager\n\nYou can also use the contextual menu to sort, remove or add effects to the images")); layout.GetPixelSize (out w, out h); Gdk.GC light = Style.DarkGC (StateType.Normal); args.Window.DrawLayout (light, (winWidth - w) /2, (winHeight -h) / 2, layout); layout.Dispose (); } return true; } void OnCursorChanged (object obj, EventArgs e) { TreeSelection selection = (obj as TreeView).Selection; TreeModel model; TreeIter iter; TreePath[] paths; SlideImage[] images; paths = selection.GetSelectedRows (out model); images = new SlideImage [paths.Length]; for (int i = 0; i < paths.Length; i++) { model.GetIter (out iter, paths [i]); images[i] = (SlideImage) store.GetValue (iter, COL_OBJECT); } if (ChangeEvent != null) ChangeEvent (this, new ShowImageSelectionEventArgs (images)); } bool IsIterVisible (Gtk.TreeIter iter_visible) { TreePath start, end, cur; TreeIter iter; GetVisibleRange (out start, out end); if (!store.GetIter (out iter, start)) return false; do { cur = store.GetPath (iter); if (iter.Equals (iter_visible)) return true; } while (!cur.Equals (end) && store.IterNext (ref iter)); return false; } // Task event to create the thumbnail void DoThumbnail (object sender, EventArgs e) { SlideImage image; DataImageSurface prev_image; DataImageSurface cairo_image; Gtk.TreeIter iter; iter = (Gtk.TreeIter) ((Task)sender).Data; prev_image = (DataImageSurface) store.GetValue (iter, COL_CAIROIMAGE); if (prev_image != null) { return; } // Discard not visible items if (IsIterVisible (iter) == false) return; image = (SlideImage) store.GetValue (iter, COL_OBJECT); cairo_image = image.GetThumbnail (thumbnail_width, thumbnail_height); Application.Invoke (delegate { store.SetValue (iter, COL_CAIROIMAGE, cairo_image); }); } void DoWork (object sender, DoWorkEventArgs e) { Task task; Logger.Debug ("SlideShowImageView.Dowork start"); while (true) { if (thumbnailing.CancellationPending) break; task = dispatcher.GetNextTask (); if (task == null) break; task.Do (); } Logger.Debug ("SlideShowImageView.Dowork end"); } public void MoveUpSelectedElements () { TreeIter prev = TreeIter.Zero, first, iter; TreePath[] paths; bool more; paths = Selection.GetSelectedRows (); if (paths.Length == 0) return; store.GetIter (out first, paths [0]); more = store.GetIterFirst (out iter); while (more) // Search for previous element to the first selected { if (iter.Equals (first)) break; prev = iter; more = store.IterNext (ref iter); } if (prev.Equals (TreeIter.Zero)) return; foreach (TreePath path in paths) { store.GetIter (out iter, path); store.Swap (prev, iter); } } public void MoveDownSelectedElements () { TreeIter next = TreeIter.Zero, last, iter; TreePath[] paths; bool more; paths = Selection.GetSelectedRows (); if (paths.Length == 0) return; store.GetIter (out last, paths [paths.Length - 1]); more = store.GetIterFirst (out iter); while (more) // Search for next element to the first selected { if (iter.Equals (last)) { more = store.IterNext (ref iter); if (more) next = iter; break; } more = store.IterNext (ref iter); } if (next.Equals (TreeIter.Zero)) return; for (int i = paths.Length - 1; i >= 0; i--) { store.GetIter (out iter, paths[i]); store.Swap (next, iter); } } [GLib.ConnectBefore] void OnButtonPressed (object o, ButtonPressEventArgs args) { ExtendedMenu menu; ExtensionNodeList nodelist; if (args.Event.Button != 3) return; menu = new ExtendedMenu (); nodelist = EffectManager.List; if (nodelist.Count > 0) { if (effects == null) { effects = new Effect [nodelist.Count]; int pos = 0; foreach (TypeExtensionNode node in nodelist) { Effect effect = (Effect) node.CreateInstance (); effect.UIOptionInvoked += new Effect.UIOptionEventHandler (OnEffect); effects [pos] = effect; pos++; } } foreach (Effect effect in effects) menu.AddItem (effect.DisplayName, effect.OnUIEventHandle); } menu.AddItem (Catalog.GetString ("Remove all effects"), OnRemoveEffects); menu.AddSeparator (); menu.AddItem (Catalog.GetString ("Sort by filename (Ascending)"), OnSortAscendingbyName); menu.AddItem (Catalog.GetString ("Sort by filename (Descending)"), OnSortDescendingbyName); menu.AddItem (Catalog.GetString ("Sort by date on disc (Ascending)"), OnSortAscendingbyDate); menu.AddItem (Catalog.GetString ("Sort by date on disc (Descending)"), OnSortDescendingbyDate); menu.AddSeparator (); menu.AddItem (Catalog.GetString ("Remove selected images"), OnRemoveSelectedImage); menu.AddItem (Catalog.GetString ("Remove all images"), OnRemoveImages); menu.Popup (args); args.RetVal = true; } void OnRemoveSelectedImage (object obj, EventArgs e) { TreeIter iter; TreePath[] paths; paths = Selection.GetSelectedRows (); if (paths.Length == 0) return; for (int i = paths.Length - 1; i >= 0; i--) { store.GetIter (out iter, paths[i]); store.Remove (ref iter); } UpdateButtonSensitivity (); } void OnRemoveImages (object obj, EventArgs e) { store.Clear (); UpdateButtonSensitivity (); next_id = 1; } void OnSortAscendingbyDate (object obj, EventArgs e) { sort_type = ImageSortType.Date_Ascending; DoSort (); } void OnSortDescendingbyDate (object obj, EventArgs e) { sort_type = ImageSortType.Date_Descending; DoSort (); } void OnSortAscendingbyName (object obj, EventArgs e) { sort_type = ImageSortType.FileName_Ascending; DoSort (); } void OnSortDescendingbyName (object obj, EventArgs e) { sort_type = ImageSortType.FileName_Descending; DoSort (); } // ListStore sort capabilities are for displaying proposes, we need to sort the actual elements void DoSort () { List items = new List (); store.Foreach (delegate (TreeModel model, TreePath path, TreeIter iter) { items.Add (iter); return false; }); items.Sort (this); Gtk.ListStore new_model = CreateStore (); next_id = 1; foreach (Gtk.TreeIter iter in items) { new_model.AppendValues (next_id.ToString (), store.GetValue (iter, COL_CAIROIMAGE), store.GetValue (iter, COL_DESCRIPTION), store.GetValue (iter, COL_OBJECT)); next_id++; } store.Dispose (); Model = store = new_model; if (thumbnailing.IsBusy == false) thumbnailing.RunWorkerAsync (store); } // Sorts the images in the view following the option selected in the contextual menu public int Compare (Gtk.TreeIter a, Gtk.TreeIter b) { SlideImage slide_a, slide_b; FileInfo file_a, file_b; slide_a = (SlideImage) store.GetValue (a, SlideShowImageView.COL_OBJECT); slide_b = (SlideImage) store.GetValue (b, SlideShowImageView.COL_OBJECT); if (slide_a == null || slide_b == null) return 0; file_a = new FileInfo (slide_a.image); file_b = new FileInfo (slide_b.image); switch (sort_type) { case ImageSortType.FileName_Ascending: return String.Compare (file_a.Name, file_b.Name); case ImageSortType.FileName_Descending: return String.Compare (file_b.Name, file_a.Name); case ImageSortType.Date_Ascending: return DateTime.Compare (file_a.CreationTime, file_b.CreationTime); case ImageSortType.Date_Descending: return DateTime.Compare (file_b.CreationTime, file_a.CreationTime); default: return 0; } } void UpdateButtonSensitivity () { if (UpdatedElements != null) UpdatedElements (this, EventArgs.Empty); } [GLib.ConnectBefore()] void OnKeyPressed (object sender, Gtk.KeyPressEventArgs args) { if (args.Event.Key == Gdk.Key.Delete || args.Event.Key == Gdk.Key.KP_Delete) { args.RetVal = true; OnRemoveSelectedImage (sender, EventArgs.Empty); } } void OnEffect (object sender, Effect.EffectEventArgs e) { TreeIter iter; TreePath[] paths; Effect effect; SlideImage image; DataImageSurface cairo_image, prev_image; paths = Selection.GetSelectedRows (); if (paths.Length == 0) return; effect = e.Effect; Logger.Debug ("OnEffect {0} (elements selected {1})", effect.Name, paths.Length); for (int i = 0; i < paths.Length; i++) { Effect image_effect; store.GetIter (out iter, paths[i]); prev_image = (DataImageSurface) store.GetValue (iter, COL_CAIROIMAGE); image = (SlideImage) store.GetValue (iter, COL_OBJECT); image_effect = EffectManager.CreateFromName (effect.Name); if (image.AddEffect (image_effect) == false) continue; image = image_effect.Apply (image); cairo_image = image.GetThumbnail (thumbnail_width, thumbnail_height); store.SetValue (iter, COL_CAIROIMAGE, cairo_image); if (prev_image != null) ((IDisposable)prev_image).Dispose (); } } void OnRemoveEffects (object obj, EventArgs e) { TreeIter iter; TreePath[] paths; SlideImage image; DataImageSurface cairo_image, prev_image; paths = Selection.GetSelectedRows (); if (paths.Length == 0) return; for (int i = 0; i < paths.Length; i++) { store.GetIter (out iter, paths[i]); prev_image = (DataImageSurface) store.GetValue (iter, COL_CAIROIMAGE); image = (SlideImage) store.GetValue (iter, COL_OBJECT); image.Effects = null; cairo_image = image.GetThumbnail (thumbnail_width, thumbnail_height); store.SetValue (iter, COL_CAIROIMAGE, cairo_image); if (prev_image != null) ((IDisposable)prev_image).Dispose (); } } } }