Blob Blame History Raw
/*
  This file is part of Dconf Editor

  Dconf Editor 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.

  Dconf Editor 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 Dconf Editor.  If not, see <https://www.gnu.org/licenses/>.
*/

using Gtk;

[GtkTemplate (ui = "/ca/desrt/dconf-editor/ui/pathbar.ui")]
public class PathBar : Box
{
    [GtkChild] private PathBarItem root_button;

    public string complete_path { get; private set; default = ""; }

    construct
    {
        add_slash_label ();
    }

    /*\
    * * public calls
    \*/

    public void set_path (ViewType type, string path)
    {
        if (type == ViewType.SEARCH)
        {
            update_cursors_for_search (path, true);
            return;
        }
        update_cursors_for_search (path, false);

        activate_item (root_button, path == "/");

        complete_path = "";
        string [] split = path.split ("/", /* max tokens disabled */ 0);
        string last = split [split.length - 1];

        bool destroy_all = false;
        bool maintain_all = false;
        @foreach ((child) => {
                if (child is Label)
                {
                    if (destroy_all)
                        child.destroy ();
                    else
                        complete_path += "/";
                    return;
                }

                PathBarItem item = (PathBarItem) child;

                if (maintain_all)
                {
                    complete_path += item.text_string;
                    activate_item (item, false);
                    return;
                }

                if (item == root_button || (!destroy_all && item.text_string == split [0]))
                {
                    complete_path += split [0];
                    split = split [1:split.length];
                    if (split.length == 0 || (split.length == 1 && type == ViewType.FOLDER))
                    {
                        activate_item (item, true);
                        maintain_all = true;
                    }
                    else
                        activate_item (item, false);
                    return;
                }

                child.destroy ();
                destroy_all = true;
            });

        if (split.length > 0)
        {
            /* add one item per folder */
            if (split.length > 1)
            {
                uint index = 0;
                foreach (string item in split [0:split.length - 1])
                {
                    complete_path += item + "/";
                    add_path_bar_item (item, complete_path, true, type == ViewType.FOLDER && (index == split.length - 2));
                    add_slash_label ();
                    index++;
                }
            }

            /* if key path */
            if (type == ViewType.OBJECT)
            {
                complete_path += last;
                add_path_bar_item (last, complete_path, false, true);
            }
        }

        show_all ();
    }

    public string get_selected_child (string current_path)
    {
        if (!complete_path.has_prefix (current_path) || complete_path == current_path)
            return "";
        int index_of_last_slash = complete_path.index_of ("/", current_path.length);
        return index_of_last_slash == -1 ? complete_path : complete_path.slice (0, index_of_last_slash + 1);
    }

    public void update_ghosts (string non_ghost_path, bool is_search)
    {
        string action_target = "";
        @foreach ((child) => {
                StyleContext context = child.get_style_context ();
                if (child is PathBarItem)
                {
                    PathBarItem item = (PathBarItem) child;
                    Variant? variant = item.get_action_target_value ();
                    if (variant == null)
                        assert_not_reached ();
                    if (((!) variant).get_type_string () == "s")    // directory
                        action_target = ((!) variant).get_string ();
                    else
                    {
                        string unused;
                        ((!) variant).get ("(ss)", out action_target, out unused);
                    }

                    if (context.has_class ("active"))
                    {
                        if (is_search)
                        {
                            item.set_cursor_type (PathBarItem.CursorType.POINTER);
                            item.set_detailed_action_name (item.default_action);
                        }
                        else
                        {
                            item.set_cursor_type (PathBarItem.CursorType.CONTEXT);
                            item.set_action_name ("ui.empty");
                        }
                        context.remove_class ("inexistent");
                    }
                    else if (non_ghost_path.has_prefix (action_target))
                    {
                        item.set_cursor_type (PathBarItem.CursorType.POINTER);
                        item.set_detailed_action_name (item.default_action);
                        context.remove_class ("inexistent");
                    }
                    else
                    {
                        item.set_cursor_type (PathBarItem.CursorType.DEFAULT);
                        item.set_detailed_action_name (item.alternative_action);
                        context.add_class ("inexistent");
                    }
                }
                else if (non_ghost_path.has_prefix (action_target))
                    context.remove_class ("inexistent");
                else
                    context.add_class ("inexistent");
            });
    }

    private void update_cursors_for_search (string current_path, bool is_search)
    {
        if (is_search)
            get_style_context ().add_class ("greyed-pathbar");
        else
            get_style_context ().remove_class ("greyed-pathbar");

        @foreach ((child) => {
                if (!(child is PathBarItem))
                    return;
                StyleContext context = child.get_style_context ();
                if (!context.has_class ("active"))
                    return;
                PathBarItem item = (PathBarItem) child;
                if (is_search)
                {
                    item.set_cursor_type (PathBarItem.CursorType.POINTER);
                    item.set_detailed_action_name (item.default_action);
                }
                else
                {
                    item.set_cursor_type (PathBarItem.CursorType.CONTEXT);
                    item.set_action_name ("ui.empty");
                }
            });
    }

    /*\
    * * widgets management
    \*/

    private void add_slash_label ()
    {
        add (new Label ("/"));
    }

    private void add_path_bar_item (string label, string complete_path, bool is_folder, bool block)
    {
        PathBarItem path_bar_item;
        if (is_folder)
        {
            Variant variant = new Variant.string (complete_path);
            string _variant = variant.print (false);
            path_bar_item = new PathBarItem (label, "ui.open-folder(" + _variant + ")", "ui.notify-folder-emptied(" + _variant + ")");
        }
        else
        {
            Variant variant = new Variant ("(ss)", complete_path, "");
            string _variant = variant.print (false);
            path_bar_item = new PathBarItem (label, "ui.open-object(" + _variant + ")", "ui.notify-object-deleted(" + _variant + ")");
        }
        add (path_bar_item);
        activate_item (path_bar_item, block);   // has to be after add()
    }

    private void activate_item (PathBarItem item, bool state)   // never called when current_view is search
    {
        StyleContext context = item.get_style_context ();
        if (state == context.has_class ("active"))
            return;
        if (state)
        {
            item.set_cursor_type (PathBarItem.CursorType.CONTEXT);
            item.set_action_name ("ui.empty");
            context.add_class ("active");
        }
        else
        {
            item.set_cursor_type (PathBarItem.CursorType.POINTER);
            item.set_detailed_action_name (item.default_action);
            context.remove_class ("active");
        }
    }
}

[GtkTemplate (ui = "/ca/desrt/dconf-editor/ui/pathbar-item.ui")]
private class PathBarItem : Button
{
    public string alternative_action { get; construct; }
    public string default_action { get; construct; }
    public string text_string { get; construct; }
    [GtkChild] private Label text_label;

    public enum CursorType {
        DEFAULT,
        POINTER,
        CONTEXT
    }
    private CursorType cursor_type = CursorType.POINTER;

    private bool hover = false; // there’s probably a function for that

    construct
    {
        enter_notify_event.connect (() => { hover = true;  set_new_cursor_type (cursor_type); });
        leave_notify_event.connect (() => { hover = false; set_new_cursor_type (CursorType.DEFAULT); });
    }

    public void set_cursor_type (CursorType cursor_type)
    {
        this.cursor_type = cursor_type;
        if (hover)
            set_new_cursor_type (cursor_type);
    }

    private void set_new_cursor_type (CursorType new_cursor_type)
    {
        Gdk.Window? gdk_window = get_window ();
        Gdk.Display? display = Gdk.Display.get_default ();
        if (gdk_window == null || display == null)
            return;

        Gdk.Cursor? cursor = null;
        switch (new_cursor_type)
        {
            case CursorType.DEFAULT: cursor = null; break;
            case CursorType.POINTER: cursor = new Gdk.Cursor.from_name ((!) display, "pointer"); break;
            case CursorType.CONTEXT: cursor = new Gdk.Cursor.from_name ((!) display, "context-menu"); break;
        }
        ((!) gdk_window).set_cursor (cursor);
    }

    [GtkCallback]
    private void update_cursor ()
    {
        if (get_style_context ().has_class ("inexistent"))
            return;

        if (cursor_type != CursorType.CONTEXT)
        {
            cursor_type = CursorType.CONTEXT;
            set_new_cursor_type (cursor_type);
            return;
        }

        GLib.Menu menu = new GLib.Menu ();
        menu.append (_("Copy current path"), "ui.copy-path"); // or "app.copy(\"" + get_action_target_value ().get_string () + "\")"
        menu.freeze ();

        Popover popover_test = new Popover.from_model (this, (MenuModel) menu);
        popover_test.popup ();
    }

    public PathBarItem (string label, string _default_action, string _alternative_action)
    {
        Object (text_string: label, default_action: _default_action, alternative_action: _alternative_action);
        text_label.set_text (label);
        set_detailed_action_name (_default_action);
    }
}