
/* Gnome Encfs Manager (GEncfsM)
 * Copyright (C) 2011 Moritz Molch <mail@moritzmolch.de>
 
 * 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 2 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, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

using Gtk;
using GLib;
using Encfs;
using Gee;
using GnomeEncfsManager;

namespace GnomeEncfsManager {
    public class Manager {
        private static Window manager;
        private static Builder builder;
        private static TreeView treeview;
        private static TreeView sg_treeview;
        private static Stash selected_stash;
        
        private static string default_message;
        private static string current_message = "";
        private static Label status_label;
        private static Gtk.ListStore sg_model;
        private static Gtk.ListStore filtered_model;
        
        private static bool row_added;
        
        private static bool spinnerActive = false;
    
        public static void show() {
            if (manager != null) {
            	update();
                return;
            }
        
            try {
                row_added = false;
                builder = new Builder();
                builder.add_from_file(get_glade_file("manager.ui"));
                builder.connect_signals(null);
                manager = (Window)builder.get_object("manager");
                ((Widget)builder.get_object("sg_remove")).set_sensitive(false);
                
                treeview = (TreeView)builder.get_object("treeview1");
                treeview.drag_motion.connect( (widget, context, x, y, etime) => {
                    if (dragged_stash != null) {
                        TreePath path;
                        TreeViewDropPosition pos;
                        if (!treeview.get_dest_row_at_pos(x, y, out path, out pos)) return false;
                        if (pos == TreeViewDropPosition.INTO_OR_BEFORE) {
                            treeview.set_drag_dest_row(path, TreeViewDropPosition.BEFORE);
                        } else if (pos == TreeViewDropPosition.INTO_OR_AFTER) {
                            treeview.set_drag_dest_row(path, TreeViewDropPosition.AFTER);
                        }
                        Gdk.drag_status(context, context.get_suggested_action(), etime);
                        return true;
                    }
                    return true;
                });
                
                status_label = (Label)builder.get_object("label_info");
                default_message = status_label.label;
                
                treeview.set_model(Stash.get_model());
                treeview.set_rules_hint(true);
                
                var col = treeview.get_column(0);
                var cell = new CellRendererText();
                col.pack_start(cell, true);
                col.add_attribute(cell, "text", 0);
                col.add_attribute(cell, "sensitive", 2);
                
                col = treeview.get_column(1);
                cell = new CellRendererText();
                col.pack_start(cell, true);
                col.add_attribute(cell, "text", 3);
                col.add_attribute(cell, "sensitive", 2);
                
                col = treeview.get_column(2);
                var cellT = new CellRendererToggle();
                col.pack_start(cellT, true);
                col.add_attribute(cellT, "active", 1);
                col.add_attribute(cellT, "sensitive", 2);
                col.add_attribute(cellT, "activatable", 2);
                cellT.toggled.connect(mount_toggled);
                
                treeview.drag_begin.connect(treeview_drag_begin_cb);
                treeview.drag_end.connect(treeview_drag_end_cb);
                treeview.row_activated.connect( (treeview, path, column) => {
                	configure();
                });
                
                // begin stash groups ============================================================================================
                sg_treeview = (TreeView)builder.get_object("sg_treeview");
                sg_model = new Gtk.ListStore(3, typeof(string), typeof(bool), typeof(string));
                sg_treeview.set_model(sg_model);
                sg_treeview.set_rules_hint(true);
                filtered_model = new Gtk.ListStore(5, typeof(string), typeof(bool), typeof(bool), typeof(string), typeof(Stash));
                
                const TargetEntry[] targets_add = {{ "GTK_TREE_MODEL_ROW", TargetFlags.SAME_APP, 0 }};
                drag_dest_set(sg_treeview, DestDefaults.ALL, targets_add, Gdk.DragAction.MOVE |
                                                                          Gdk.DragAction.COPY);
                drag_source_set(sg_treeview, Gdk.ModifierType.BUTTON1_MASK, targets_add, Gdk.DragAction.MOVE);
                
                col = sg_treeview.get_column(0);
                cell = new CellRendererText();
                cell.edited.connect( (path, new_text) => {
                    if (new_text[0] == '<') return;
                    
                    TreeIter iter;
                    sg_model.get_iter_from_string( out iter, path );
                    string group;
                    sg_model.get(iter, 0, out group, -1);
                    sg_model.set(iter, 0, new_text, -1);
                    
                    sg_save();
                    
		            stashes_mutex.lock();
		            foreach (Stash s in stashes) {
		                if (s.options.get_string("stash_group") == group) {
		                    s.options.set_string("stash_group", new_text);
		                }
		            }
		            stashes_mutex.unlock();
		            save_stashes();
		            TrayIcon.update();
                });
                col.pack_start(cell, true);
                col.add_attribute(cell, "text", 0);
                col.add_attribute(cell, "editable", 1);
                
                // update filtered list when necessary
                Stash.get_model().row_changed.connect( (path, iter) => {
                    if (treeview == null) return;
                    if (row_added) {
                        sg_changed();
                        row_added = false;
                    }
                    if (treeview.get_model() == Stash.get_model()) {
                        return;
                    }
                    
                    Stash stash;
                    Stash.get_model().get(iter, 4, out stash, -1);
                    
                    TreeIter sg_iter;
                    Stash s;
                    filtered_model.get_iter_first(out sg_iter);
                    do {
                        if (!filtered_model.iter_is_valid(sg_iter)) { return; }
                        filtered_model.get(sg_iter, 4, out s, -1);
                        if (s == stash) {
                            filtered_model.set(sg_iter, 0, s.mountDir, 1, s.isMounted, 2, s.isAvailable, 3, s.cryptDir, 4, s, -1);
                            return;
                        }
                    } while (filtered_model.iter_next(ref sg_iter));
                });
                
                Stash.get_model().row_inserted.connect( (path, iter) => {
                    if (treeview == null) return;
                    if (treeview.get_model() == Stash.get_model()) {
                        return;
                    }
                    row_added = true;
                });
                
                Stash.get_model().row_deleted.connect( (path, iter) => {
                    if (treeview == null) return;
                    if (treeview.get_model() == Stash.get_model()) {
                        return;
                    }
                    sg_changed();
                });
                
                
                string dragged_stash_group = null;
                TreeIter? dragged_stash_group_iter = null;
                
                sg_treeview.drag_motion.connect( (widget, context, x, y, etime) => {
                    drag_unhighlight(widget);
                    if (dragged_stash == null) {
                        if ((dragged_stash_group != null) && (dragged_stash_group[0] == '<')) return true;
                        TreePath path;
                        TreeViewDropPosition pos;
                        if (!sg_treeview.get_dest_row_at_pos(x, y, out path, out pos)) return true;
                        if (int.parse(path.to_string()) <= 1) return true;
                        if ((int.parse(path.to_string()) == 2) && (pos == TreeViewDropPosition.BEFORE)) return true;
                        
                        if (pos == TreeViewDropPosition.INTO_OR_BEFORE) {
                            sg_treeview.set_drag_dest_row(path, TreeViewDropPosition.BEFORE);
                        } else if (pos == TreeViewDropPosition.INTO_OR_AFTER) {
                            sg_treeview.set_drag_dest_row(path, TreeViewDropPosition.AFTER);
                        }
                        Gdk.drag_status(context, Gdk.DragAction.MOVE, etime);
                        return true;
                    } else {
                        TreePath path;
                        TreeViewDropPosition pos;
                        if (!sg_treeview.get_dest_row_at_pos(x, y, out path, out pos)) return true;
                        if (int.parse(path.to_string()) == 0) return true;
                        if (pos == TreeViewDropPosition.BEFORE) {
                            sg_treeview.set_drag_dest_row(path, TreeViewDropPosition.INTO_OR_BEFORE);
                        } else if (pos == TreeViewDropPosition.AFTER) {
                            sg_treeview.set_drag_dest_row(path, TreeViewDropPosition.INTO_OR_AFTER);
                        }
                        Gdk.drag_status(context, Gdk.DragAction.COPY, etime);
                        return true;
                    }
                });
                

                sg_treeview.drag_begin.connect((context) => {
                    if (sg_treeview.get_selection() != null) {
		                if (sg_treeview.get_selection().get_selected(null, out dragged_stash_group_iter)) {
		                    sg_model.get(dragged_stash_group_iter, 0, out dragged_stash_group, -1);
		                }
		            }
		            /*if ((dragged_stash_group!= null) && (dragged_stash_group[0] == '<')) {
		                Gdk.drag_abort( context, Gtk.get_current_event_time());
		                //dragged_stash_group = null;
		            }*/
                });
                
                sg_treeview.drag_end.connect(() => {
                    GLib.stdout.printf("sg_treeview.drag_end\n");
                    dragged_stash_group = null;
                });
                
                sg_treeview.drag_drop.connect( (context, x, y, time) => {
                    //GLib.stdout.printf("drag_drop\n");
                    stash_reordered = false;
                    
                    TreePath sg_path;
                    TreeViewDropPosition sg_pos;
                    if (!sg_treeview.get_dest_row_at_pos(x, y, out sg_path, out sg_pos)) {
                        sg_changed();
                        return true;
                    }
                    
                    if (dragged_stash_group != null) {
                        if (dragged_stash_group[0] == '<') {sg_changed(); return true;}
                        if (int.parse(sg_path.to_string()) == 0) {sg_changed(); return true;}
                        if ((int.parse(sg_path.to_string()) == 1) && (sg_pos == TreeViewDropPosition.BEFORE)) {sg_changed(); return true;}

                        TreeIter iter;
                       
                        if (sg_pos == TreeViewDropPosition.BEFORE) {
                             sg_model.insert(out iter, int.parse(sg_path.to_string()));
                        } else {//if (sg_pos == TreeViewDropPosition.AFTER) {
                            sg_model.insert(out iter, int.parse(sg_path.to_string())+1);
                        }
                        sg_model.set(iter, 0, dragged_stash_group, 1, true, 2, "", -1);
                        #if VALA_0_36
                            sg_model.remove(ref dragged_stash_group_iter);
                        #else
                            sg_model.remove(dragged_stash_group_iter);
                        #endif
                        if (sg_treeview.get_selection() != null) {
	                        sg_treeview.get_selection().select_iter(iter);
	                    }
                        dragged_stash_group = null;
                        sg_save();
                        sg_changed();
                        TrayIcon.update();
                    }
                    return true;
                });
                
                sg_treeview.drag_data_received.connect( (context, x, y, selection_data, info, time) => {
                    //GLib.stdout.printf("sg_treeview.drag_data_received\n");
                    stash_reordered = false;
                    TreePath sg_path;
                    TreeViewDropPosition sg_pos;
                    TreeModel model;
                    TreePath path;
                    tree_get_row_drag_data( selection_data, out model, out path );

                    if (!sg_treeview.get_dest_row_at_pos(x, y, out sg_path, out sg_pos)) {
                        if (Stash.model == treeview.get_model()) {
                            TreeIter drag_iter;
                            model.get_iter( out drag_iter, path );
                            if (!((Gtk.ListStore)model).iter_is_valid(drag_iter)) return;
                            Stash s;
                            model.get(drag_iter, 4, out s, -1);
                            Stash.model.insert_after(out s.iter, drag_iter);
                            Stash.model.set(s.iter, 0, s.mountDir, 1, s.isMounted, 2, s.isAvailable, 3, s.cryptDir, 4, s, -1);
                        }
                        sg_changed();
                        return;
                    }

                    if (model == sg_model) {
                        //TreeIter iter;
                        //sg_model.insert(out iter, int.parse(sg_path.to_string()));
                        //sg_model.set(iter, 0, dragged_stash_group, 1, true, 2, "", -1);

                    } else if (model == treeview.get_model()) {
                        TreeIter drag_iter;
                        TreeIter drop_iter;
                        model.get_iter( out drag_iter, path );
                        if (!((Gtk.ListStore)model).iter_is_valid(drag_iter)) return;
                        sg_model.get_iter( out drop_iter, sg_path );
                        if (!sg_model.iter_is_valid(drop_iter)) return;
                        
                        string sg;
                        string sg_meta;
                        Stash s;
                        sg_model.get(drop_iter, 0, out sg, 2, out sg_meta, -1);
                        if (sg_meta == "") {
                            model.get(drag_iter, 4, out s, -1);
                            if (s.options.get_string("stash_group") == sg) {
                                return;
                            }
                            s.options.set_string("stash_group", sg);
                            sg_changed();
                            TrayIcon.update();
                            Manager.update();
                            save_stashes();
                        } else if (sg_meta == "default") {
                            model.get(drag_iter, 4, out s, -1);
                            if (s.options.get_string("stash_group") == "") {
                                return;
                            }
                            s.options.set_string("stash_group", "");
                            sg_changed();
                            TrayIcon.update();
                            Manager.update();
                            save_stashes();
                        } else if (sg_meta == "all") {
                            if (Stash.model == treeview.get_model()) {
                                model.get(drag_iter, 4, out s, -1);
                                Stash.model.insert_after(out s.iter, drag_iter);
                                Stash.model.set(s.iter, 0, s.mountDir, 1, s.isMounted, 2, s.isAvailable, 3, s.cryptDir, 4, s, -1);
                            } else {
                                sg_changed();
                            }
                        }
                    }
                });

                sg_model.clear();
                TreeIter iter;
                sg_model.append(out iter);
                sg_model.set(iter, 0, "< %s >".printf(_("All")), 1, false, 2, "all", -1);
                if (sg_treeview.get_selection() != null) {
		            sg_treeview.get_selection().select_iter(iter);
		        }
                sg_model.append(out iter);
                sg_model.set(iter, 0, "< %s >".printf(_("Default")), 1, false, 2, "default", -1);

                foreach (string key in settings.get_strv("stash-groups")) {
                    if (key == "") continue;
                    sg_model.append(out iter);
                    sg_model.set(iter, 0, key, 1, true, 2, "", -1);
                }
                
                
                // end stash groups ==============================================================================================
                
                ((Gtk.Spinner)builder.get_object("spinner")).active = true;
                update();
                ((Window)manager).resize(settings.get_int("width"), settings.get_int("height"));
                ((Window)manager).show_all();
                ((Window)manager).present();
                TrayIcon.update();
                
                // workaround for compiz on quantal
                /*
                {
		            
		            Timeout.add (100, () => {
		            	try {
		            		Process.spawn_command_line_async("wmctrl -a \"" + ((Window)manager).get_title() + "\"");
		            	} catch (Error e) {
		            	}
		            	return false;
		            });
		        }
		        */
            } catch (Error e) {
                stderr.printf("Could not load UI: %s\n", e.message);
            }
        }
        
        public static void show_stash_groups_toggled(ToggleAction action) {
            settings.set_boolean("show-stash-groups", action.active);
        }
        
        public static void show_stash_groups(bool active) {
            if (sg_treeview == null) return;
            if (!active) {
                TreeIter iter;
	            bool valid = sg_model.get_iter_first(out iter);
	            if (valid) {
	                string group;
			        do {
			            sg_model.get(iter, 2, out group, -1);
                        if (group == "all") {
                            break;
                        }
			        } while (sg_model.iter_next(ref iter));
			        
                    if (sg_treeview.get_selection() != null) {
	                    sg_treeview.get_selection().select_iter(iter);
	                }
			    }
            }
            sg_changed();
            update();
        }
        
        public static void sg_add_cb() {
            //GLib.stdout.printf("sg_add_cb\n");
            TreeIter iter;
            sg_model.append(out iter);
            sg_model.set(iter, 0, _("Unnamed"), 1, true, 2, "", -1);
            
            sg_save();
        }
        
        public static void sg_remove_cb() {
            //GLib.stdout.printf("sg_remove_cb\n");
            TreeIter? iter = null;
            if (sg_treeview.get_selection() != null) {
		        sg_treeview.get_selection().get_selected(null, out iter);
		    }
		    if (iter == null) {
		        return;
		    }
		    string group;
		    sg_model.get(iter, 0, out group, -1);
		    if (group[0] == '<') {
		        return;
		    }
            #if VALA_0_36
                sg_model.remove(ref iter);
            #else
                sg_model.remove(iter);
            #endif
		    sg_save();
		    
		    stashes_mutex.lock();
		    foreach (Stash s in stashes) {
		        if (s.options.get_string("stash_group") == group) {
		            s.options.set_string("stash_group", "");
		        }
		    }
		    stashes_mutex.unlock();
		    sg_changed();
		    save_stashes();
		    TrayIcon.update();
        }
        
        public static void sg_changed() {
            if (sg_treeview == null) return;
            TreeIter? iter = null;
            if (sg_treeview.get_selection() != null) {
		        sg_treeview.get_selection().get_selected(null, out iter);
		    }
		    if (iter == null) {
		        return;
		    }
		    
		    if (!sg_model.iter_is_valid(iter)) {
		        return;
		    }
		    
		    string group;
		    sg_model.get(iter, 2, out group, -1);
		    if (group == "all") {
		        treeview.set_model(Stash.get_model());
		        ((Widget)builder.get_object("sg_remove")).set_sensitive(false);
		        return;
		    } else if (group == "default") {
		        group = "";
		        ((Widget)builder.get_object("sg_remove")).set_sensitive(false);
		    } else {
		        sg_model.get(iter, 0, out group, -1);
		        ((Widget)builder.get_object("sg_remove")).set_sensitive(true);
		    }
		    
		    
	        filtered_model.clear();
	        Stash stash;
	        TreeIter iter_stashes;
	        TreeIter iter_new;
            Stash.get_model().get_iter_first(out iter_stashes);
            do {
                if (!((Gtk.ListStore)Stash.get_model()).iter_is_valid(iter_stashes)) break;
            
                Stash.get_model().get(iter_stashes, 4, out stash, -1);
                if (stash != null) {
		            if (stash.options.get_string("stash_group") == group) {
                        filtered_model.append(out iter_new);
                        filtered_model.set(iter_new, 0, stash.mountDir, 1, stash.isMounted, 2, stash.isAvailable, 3, stash.cryptDir, 4, stash, -1);   
		            }
		        }
            } while (Stash.get_model().iter_next(ref iter_stashes));

	        treeview.set_model(filtered_model);
        }
        
        public static void sg_save() {
            TreeIter iter;
            ArrayList<string> groups = new ArrayList<string>();
            string group;
	        bool valid = sg_model.get_iter_first(out iter);
	        if (valid) {
			    do {
			        sg_model.get(iter, 0, out group, -1);
			        if (group[0] != '<') {
			            groups.add(group);
			        }
			    } while (sg_model.iter_next(ref iter));
			}
			
			settings.set_strv("stash-groups", arrayList2Array(groups));
        }
        
        public static void showSpinner(bool state) {
        	spinnerActive = state;
            if (manager == null)
                return;
                
            ((Gtk.Spinner)builder.get_object("spinner")).active = true;

			if (state) {
		        ((Gtk.Spinner)builder.get_object("spinner")).visible = true;
		    }
		    else {
		        ((Gtk.Spinner)builder.get_object("spinner")).visible = false;
		    }
        }
        

        public static void showStatus( string message ) {
        	current_message = message;
            if (manager == null)
                return;
        
        	if (message == "") {
        		status_label.label = default_message;
        	} else {
        		status_label.label = message;
        	}
        }

        public static void update() {
            if (manager == null)
                return;

            ((Gtk.Action)builder.get_object("UpdateAll")).set_sensitive( stashes.size > 0 );
            ((ToggleAction)builder.get_object("ViewStashGroups")).set_active(settings.get_boolean("show-stash-groups"));
            ((Widget)builder.get_object("stash_group_pane")).set_visible(settings.get_boolean("show-stash-groups"));
			showSpinner(spinnerActive);
        	if (current_message == "" || current_message == null) {
        		status_label.label = default_message;
        	} else {
        		status_label.label = current_message;
        	}

            TreeIter ti;
            selected_stash = null;
            if (treeview.get_selection() != null) {
		        if (treeview.get_selection().get_selected(null, out ti)) {
		            treeview.get_model().get(ti, 4, out selected_stash, -1);
		        }
		    }


            ((Gtk.Action)builder.get_object("Mount")).set_sensitive(false);
            ((Gtk.Action)builder.get_object("Unmount")).set_sensitive(false);
            ((Gtk.Action)builder.get_object("Information")).set_sensitive(false);
            ((Gtk.Action)builder.get_object("Configure")).set_sensitive(false);
            ((Gtk.Action)builder.get_object("Change password")).set_sensitive(false);
            ((Gtk.Action)builder.get_object("Remove")).set_sensitive(false);

             
            if ((selected_stash != null) && (selected_stash.isAvailable)){
                if (selected_stash.isMounted) { 
                    //((Gtk.Action)builder.get_object("Mount")).set_visible(false);
                    //((Gtk.Action)builder.get_object("Unmount")).set_visible(true);
                    ((Gtk.Action)builder.get_object("Unmount")).set_sensitive(true);
                }
                else {
                    //((Gtk.Action)builder.get_object("Mount")).set_visible(true);
                    ((Gtk.Action)builder.get_object("Mount")).set_sensitive(true);
                    //((Gtk.Action)builder.get_object("Unmount")).set_visible(false);
                }
                ((Gtk.Action)builder.get_object("Information")).set_sensitive(true);
                ((Gtk.Action)builder.get_object("Configure")).set_sensitive(true);
                ((Gtk.Action)builder.get_object("Change password")).set_sensitive(true);
                ((Gtk.Action)builder.get_object("Remove")).set_sensitive(true);
            }
            else if ((selected_stash != null) && (!selected_stash.isAvailable)){
                ((Gtk.Action)builder.get_object("Configure")).set_sensitive(true);
                ((Gtk.Action)builder.get_object("Remove")).set_sensitive(true);
            }

        }
        
        public static bool is_visible() {
            if (manager == null) {
                return false;
            }
            return manager.get_visible();
        }

        public static void mount_toggled(CellRendererToggle renderer, string path) {
            TreeIter ti;
            treeview.get_model().get_iter(out ti, new TreePath.from_string(path));
            treeview.get_model().get(ti, 4, out selected_stash, -1);
            
            if (selected_stash.isMounted) {
                selected_stash.unmount();
            } else if ((MountDialog.queue.size > 0) && (MountDialog.queue.first() == selected_stash)) {
                selected_stash.mount_cancel();
            }
            else {
                MountDialog.mount(selected_stash);
            }
        }

        public static void new_stash() {
            AddStash.show();
        }
        
        public static void mount() {
            if (selected_stash == null) 
                return;
        
            MountDialog.mount(selected_stash);
        }
        
        public static void unmount() {
            if (selected_stash == null) 
                return;
                
            selected_stash.unmount();
        }
        
        public static void configure() {
            if (selected_stash == null) 
                return;
        
            ConfigureStash.show(selected_stash);
        }

        public static void change_password() {
            if (selected_stash == null) 
                return;
                
            ChangePassword.show(selected_stash);
        }        
        
        public static void remove() {
            if (selected_stash == null) 
                return;
                
            RemoveStash.show(selected_stash);
        }
        
        public static void information() {
            if (selected_stash == null) 
                return;
                
            InformationDialog.show(selected_stash);
        }
        
        public static void quit() {
            GnomeEncfsManager.quit();
        }
        
        public static void close() {
        	int width, height;
        	((Window)manager).get_size( out width, out height );
        	settings.set_int("width", width);
        	settings.set_int("height", height);
        
            if (TrayIcon.is_visible()) {
                manager.destroy();
                TrayIcon.update();
                manager = (Window)null;
            }
            else {
                GnomeEncfsManager.quit();
            }
        }
        
        public static void about() {
            About.show();
        }

        public static void help() {
        	string uri = Path.build_filename( get_data_path(), "doc", "locale", "C", "index.page" );
        	foreach (string i in Intl.get_language_names()) {
        		string tmp = Path.build_filename( get_data_path(), "doc", "locale", i.split("_")[0], "index.page" );
        		if (FileUtils.test( tmp, FileTest.EXISTS )) {
					uri = tmp;
					break;
				}
        	}

			try {
				Process.spawn_command_line_async( "yelp \"" + uri + "\"");
			}
			catch (Error e) {
				stderr.printf("%s\n", e.message);
			}
        }
        
        public static void help_online() {
			try {
				Gtk.show_uri(null, "https://answers.launchpad.net/gencfsm", Gdk.CURRENT_TIME);
			}
			catch (Error e) {
				stderr.printf("%s\n", e.message);
			}
        }
        
        public static void translate() {
			try {
				Gtk.show_uri(null, "https://translations.launchpad.net/gencfsm", Gdk.CURRENT_TIME);
			}
			catch (Error e) {
				stderr.printf("%s\n", e.message);
			}
        }
        
        public static void donate() {
			try {
				Gtk.show_uri(null, "http://www.libertyzero.com/GEncfsM/donate", Gdk.CURRENT_TIME);
			}
			catch (Error e) {
				stderr.printf("%s\n", e.message);
			}
        }
        
        public static void preferences() {
            Preferences.show();
        }
        
        private static Stash dragged_stash = null;
        private static bool stash_reordered = false;
        public static void treeview_drag_begin_cb (Gdk.DragContext context) {
            TreeIter ti;
            if (treeview.get_selection() != null) {
		        if (treeview.get_selection().get_selected(null, out ti)) {
		            treeview.get_model().get(ti, 4, out dragged_stash, -1);
		        }
		    }
		    stash_reordered = true;
        }
        
        public static void treeview_drag_end_cb(Widget            widget,
                            Gdk.DragContext   context) {
            
            if (stash_reordered) {
                stash_reordered = false;
            } else {
                dragged_stash = null;
                return;
            }
            
            TreeIter iter;
            Stash stash = null;
            
            // (re)select dragged stash
            {
                treeview.get_model().get_iter_first(out iter);
                do {
                    if (!((Gtk.ListStore)treeview.get_model()).iter_is_valid(iter)) break;
                    treeview.get_model().get(iter, 4, out stash, -1);
                    if (stash == dragged_stash) {
                        if (treeview.get_selection() != null) {
	                        treeview.get_selection().select_iter(iter);
	                    }
                        break;
                    }
                } while (treeview.get_model().iter_next(ref iter));
                
            }

            if (treeview.get_model() == Stash.get_model()) {

            } else if (treeview.get_model() == filtered_model) {
                bool found = false;
                TreeIter? iter_new = null;
                Stash stash_before = null;
                Stash stash_after = null;
                filtered_model.get_iter_first(out iter);
                do {
                    if (!filtered_model.iter_is_valid(iter)) { dragged_stash = null; return; }
                    stash_before = stash;
                    filtered_model.get(iter, 4, out stash, -1);
                    if (stash == dragged_stash) {
                        found = true;
                        break;
                    }
                } while (filtered_model.iter_next(ref iter));
                if (!found) {
                    dragged_stash = null;
                    return;
                }
                filtered_model.iter_next(ref iter);
                
                if (filtered_model.iter_is_valid(iter)) {
                    filtered_model.get(iter, 4, out stash_after, -1);
                    
                    Stash.get_model().get_iter_first(out iter);
                    do {
                        if (!Stash.get_model().iter_is_valid(iter)) { dragged_stash = null; return; }
                        Stash.get_model().get(iter, 4, out stash, -1);
                        if (stash == stash_after) {
                            Stash.get_model().insert_before( out iter_new, iter );
                            break;
                        }
                    } while (Stash.get_model().iter_next(ref iter));
                    
                    Stash.get_model().get_iter_first(out iter);
                    do {
                        if (!Stash.get_model().iter_is_valid(iter)) { dragged_stash = null; return; }
                        Stash.get_model().get(iter, 4, out stash, -1);
                        if (stash == dragged_stash) {
                            Stash.get_model().swap( iter, iter_new );
                            #if VALA_0_36
                                Stash.get_model().remove( ref iter_new );
                            #else
                                Stash.get_model().remove( iter_new );
                            #endif
                            break;
                        }
                    } while (Stash.get_model().iter_next(ref iter));
                    
                } else {
                    // The stash is at the end of the filtered list
                    Stash.get_model().get_iter_first(out iter);
                    do {
                        if (!Stash.get_model().iter_is_valid(iter)) { dragged_stash = null; return; }
                        Stash.get_model().get(iter, 4, out stash, -1);
                        if (stash == dragged_stash) {
                            Stash.get_model().append( out iter_new );
                            Stash.get_model().swap( iter, iter_new );
                            #if VALA_0_36
                                Stash.get_model().remove( ref iter_new );
                            #else
                                Stash.get_model().remove( iter_new );
                            #endif
                            break;
                        }
                    } while (Stash.get_model().iter_next(ref iter));
                }

                
            }
            
            stashes.clear();
            Stash.get_model().get_iter_first(out iter);
            do {
                if (!Stash.get_model().iter_is_valid(iter)) { dragged_stash = null; return; }
                Stash.get_model().get(iter, 4, out stash, -1);
                stash.iter = iter;
                stashes.add(stash);
            } while (Stash.get_model().iter_next(ref iter));
            
            TrayIcon.update();
            Manager.update();
            save_stashes();
            
            dragged_stash = null;
        }
        
    }
}
