/*
 *  $Id: resource-editor.c 30072 2026-06-16 13:50:16Z yeti-dn $
 *  Copyright (C) 2005-2025 David Necas (Yeti), Petr Klapetek.
 *  E-mail: yeti@gwyddion.net, klapetek@gwyddion.net.
 *
 *  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.
 */

#include "config.h"
#include <string.h>
#include <glib/gi18n-lib.h>
#include <gtk/gtk.h>

#include "libgwyddion/macros.h"
#include "libgwyui/option-menus.h"
#include "libgwyui/inventory-store.h"
#include "libgwyui/icons.h"
#include "libgwyui/utils.h"

#include "libgwyapp/menu.h"
#include "libgwyapp/settings.h"
#include "libgwyapp/app.h"
#include "libgwyapp/resource-editor.h"
#include "libgwyapp/sanity.h"

enum {
    COMMIT_TIMEOUT = 120
};

enum {
    SELECTED_ANY      = 1 << 0,
    SELECTED_EDITABLE = 1 << 1,
    SELECTED_MASK     = 0x03
};

struct _GwyResourceEditorPrivate {
    GtkWidget *vbox;
    GtkWidget *treeview;
    GwySensitivityGroup *sensgroup;

    GtkWidget *edit_window;
    GString *edited_resource;
    guint commit_id;
};

static void         finalize         (GObject *object);
static void         render_name      (GtkTreeViewColumn *column,
                                      GtkCellRenderer *renderer,
                                      GtkTreeModel *model,
                                      GtkTreeIter *iter,
                                      gpointer data);
static void         selection_changed(GtkTreeSelection *selection,
                                      GwyResourceEditor *editor);
static void         unmap            (GtkWidget *widget);
static void         destroy          (GtkWidget *widget);
static void         new_item         (GwyResourceEditor *editor);
static void         duplicate_item   (GwyResourceEditor *editor);
static void         copy_item        (GwyResourceEditor *editor,
                                      const gchar *name,
                                      const gchar *newname);
static void         delete_item      (GwyResourceEditor *editor);
static void         set_default      (GwyResourceEditor *editor);
static void         edit_item        (GwyResourceEditor *editor);
static void         row_activated    (GwyResourceEditor *editor,
                                      GtkTreePath *path,
                                      GtkTreeViewColumn *column);
static void         edit_resource    (GwyResourceEditor *editor,
                                      const gchar *name);
static void         editor_closed    (GwyResourceEditor *editor);
static void         name_edited      (GwyResourceEditor *editor,
                                      const gchar *strpath,
                                      const gchar *text);
static gboolean     save_resource    (GwyResourceEditor *editor,
                                      const gchar *name);
static void         update_title     (GwyResourceEditor *editor);
static GwyResource* get_active       (GwyResourceEditor *editor,
                                      GtkTreeModel **model,
                                      GtkTreeIter *iter,
                                      const gchar *warnwhat);

static GtkWindowClass *parent_class = NULL;

G_DEFINE_ABSTRACT_TYPE_WITH_CODE(GwyResourceEditor, gwy_resource_editor, GTK_TYPE_WINDOW,
                                 G_ADD_PRIVATE(GwyResourceEditor))

static void
gwy_resource_editor_class_init(GwyResourceEditorClass *klass)
{
    GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);

    parent_class = gwy_resource_editor_parent_class;

    gobject_class->finalize = finalize;

    widget_class->destroy = destroy;
    widget_class->unmap = unmap;
}

static void
finalize(GObject *object)
{
    GwyResourceEditorClass *klass = GWY_RESOURCE_EDITOR_GET_CLASS(object);
    klass->instance = NULL;

    G_OBJECT_CLASS(parent_class)->finalize(object);
}

static void
gwy_resource_editor_init(GwyResourceEditor *editor)
{
    editor->priv = gwy_resource_editor_get_instance_private(editor);
}

/**
 * gwy_resource_editor_setup:
 * @editor: A resource editor.
 *
 * Sets up particular resource editor.
 *
 * Helper method only intended for resource editor implementation. To be called in particular resource initialization
 * methods.
 **/
void
gwy_resource_editor_setup(GwyResourceEditor *editor)
{
    static const struct {
        const gchar *icon_name;
        const gchar *tooltip;
        GCallback callback;
        guint sens_flags;
    }
    toolbar_buttons[] = {
        {
            GWY_ICON_GTK_EDIT,
            N_("Edit selected item"),
            G_CALLBACK(edit_item),
            SELECTED_EDITABLE,
        },
        {
            GWY_ICON_GTK_NEW,
            N_("Create a new item"),
            G_CALLBACK(new_item),
            0,
        },
        {
            GWY_ICON_GTK_COPY,
            N_("Create a new item based on selected one"),
            G_CALLBACK(duplicate_item),
            SELECTED_ANY,
        },
        {
            GWY_ICON_GTK_DELETE,
            N_("Delete selected item"),
            G_CALLBACK(delete_item),
            SELECTED_EDITABLE,
        },
        {
            GWY_ICON_FAVOURITE,
            N_("Set selected item as default"),
            G_CALLBACK(set_default),
            SELECTED_ANY,
        },
    };

    GwyResourceEditorClass *klass = GWY_RESOURCE_EDITOR_GET_CLASS(editor);
    if (klass->instance) {
        g_warning("An instance of this editor already exists.  This is not going to work.");
    }
    klass->instance = editor;

    GwyDict *settings = gwy_app_settings_get();
    const gchar *name = klass->base_resource;
    gchar *key = g_strconcat(klass->settings_prefix, "/current", NULL);
    gwy_dict_gis_string_by_name(settings, key, &name);
    g_free(key);

    GwyResourceEditorPrivate *priv = editor->priv;
    GwyResourceClass *resclass = g_type_class_ref(klass->resource_type);
    /* TODO: handle errors */
    gwy_resource_class_mkdir(resclass);
    g_type_class_unref(resclass);

    /* Window setup */
    GtkWindow *gtkwindow = GTK_WINDOW(editor);
    gtk_window_set_resizable(gtkwindow, TRUE);
    gtk_window_set_title(gtkwindow, klass->window_title);
    gtk_window_add_accel_group(gtkwindow, gwy_app_main_accel_group());
    priv->sensgroup = gwy_sensitivity_group_new();

    priv->vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
    gtk_container_add(GTK_CONTAINER(editor), priv->vbox);

    /* Treeview */
    GtkWidget *scwin = gtk_scrolled_window_new(NULL, NULL);
    gtk_box_pack_start(GTK_BOX(priv->vbox), scwin, TRUE, TRUE, 0);
    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);

    priv->treeview = klass->construct_treeview(G_CALLBACK(selection_changed), editor, NULL);
    GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(priv->treeview));
    GwyInventory *inventory = gwy_inventory_store_get_inventory(GWY_INVENTORY_STORE(model));
    GtkTreeViewColumn *column = gtk_tree_view_get_column(GTK_TREE_VIEW(priv->treeview), 2);
    GList *rlist = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(column));
    g_assert(rlist && !rlist->next);
    g_object_set(rlist->data, "editable-set", TRUE, NULL);
    gtk_tree_view_column_set_cell_data_func(column, GTK_CELL_RENDERER(rlist->data), render_name, inventory, NULL);
    g_signal_connect_swapped(rlist->data, "edited", G_CALLBACK(name_edited), editor);
    g_signal_connect_swapped(priv->treeview, "row-activated", G_CALLBACK(row_activated), editor);
    g_list_free(rlist);
    gtk_container_add(GTK_CONTAINER(scwin), priv->treeview);

    /* Controls */
    GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
    gtk_box_set_homogeneous(GTK_BOX(hbox), TRUE);
    gtk_box_pack_start(GTK_BOX(priv->vbox), hbox, FALSE, FALSE, 0);
    for (guint i = 0; i < G_N_ELEMENTS(toolbar_buttons); i++) {
        GtkWidget *button = gtk_button_new();
        GtkWidget *image = gtk_image_new_from_icon_name(toolbar_buttons[i].icon_name, GTK_ICON_SIZE_LARGE_TOOLBAR);
        gtk_container_add(GTK_CONTAINER(button), image);
        gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 0);
        gtk_widget_set_tooltip_text(button, _(toolbar_buttons[i].tooltip));
        gwy_sensitivity_group_add_widget(priv->sensgroup, button, toolbar_buttons[i].sens_flags);
        g_signal_connect_swapped(button, "clicked", toolbar_buttons[i].callback, editor);
    }
    g_object_unref(priv->sensgroup);

    gtk_window_set_default_size(gtkwindow, -1, 400);
    gwy_app_restore_window_position(gtkwindow, klass->settings_prefix, TRUE);
    gtk_widget_show_all(priv->vbox);
    gwy_resource_tree_view_set_active(priv->treeview, name);
}

static void
render_name(G_GNUC_UNUSED GtkTreeViewColumn *column,
            GtkCellRenderer *renderer,
            GtkTreeModel *model,
            GtkTreeIter *iter,
            gpointer data)
{
    gpointer defitem = gwy_inventory_get_default_item(GWY_INVENTORY(data));
    GwyResource *item;
    gtk_tree_model_get(model, iter, 0, &item, -1);
    g_object_set(renderer,
                 "editable", gwy_resource_is_modifiable(item),
                 "weight", (item == defitem) ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL,
                 NULL);
}

static void
selection_changed(GtkTreeSelection *selection,
                  GwyResourceEditor *editor)
{
    GwyResource *resource;
    GtkTreeModel *model;
    GtkTreeIter iter;
    guint state = 0;

    if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
        state |= SELECTED_ANY;
        gtk_tree_model_get(model, &iter, 0, &resource, -1);
        if (gwy_resource_is_modifiable(resource))
            state |= SELECTED_EDITABLE;
    }
    gwy_sensitivity_group_set_state(editor->priv->sensgroup, SELECTED_MASK, state);
}

static void
unmap(GtkWidget *widget)
{
    GwyResourceEditorClass *klass = GWY_RESOURCE_EDITOR_GET_CLASS(widget);
    GwyResourceEditor *editor = GWY_RESOURCE_EDITOR(widget);

    gwy_app_save_window_position(GTK_WINDOW(widget), klass->settings_prefix, FALSE, TRUE);

    GwyResource *resource;
    if ((resource = get_active(editor, NULL, NULL, NULL))) {
        GwyDict *settings = gwy_app_settings_get();
        gchar *key = g_strconcat(klass->settings_prefix, "/current", NULL);
        gwy_dict_pass_string_by_name(settings, key, g_strdup(gwy_resource_get_name(resource)));
        g_free(key);
    }

    GTK_WIDGET_CLASS(parent_class)->unmap(widget);
}

static void
destroy(GtkWidget *widget)
{
    GwyResourceEditor *editor = GWY_RESOURCE_EDITOR(widget);
    GwyResourceEditorPrivate *priv = editor->priv;

    if (priv->edit_window)
        gtk_widget_destroy(priv->edit_window);

    gwy_resource_editor_commit(editor);
    if (priv->edited_resource) {
        g_string_free(priv->edited_resource, TRUE);
        priv->edited_resource = NULL;
    }

    GTK_WIDGET_CLASS(parent_class)->destroy(widget);
}

static void
set_default(GwyResourceEditor *editor)
{
    GtkTreeModel *model;
    GwyResource *resource = get_active(editor, &model, NULL, "Set Default");
    GwyInventory *inventory = gwy_inventory_store_get_inventory(GWY_INVENTORY_STORE(model));
    gwy_inventory_set_default_item_name(inventory, gwy_resource_get_name(resource));
}

static void
new_item(GwyResourceEditor *editor)
{
    GwyResourceEditorClass *klass = GWY_RESOURCE_EDITOR_GET_CLASS(editor);
    copy_item(editor, klass->base_resource, _("Untitled"));
}

static void
duplicate_item(GwyResourceEditor *editor)
{
    GwyResource *resource = get_active(editor, NULL, NULL, "Copy");
    copy_item(editor, gwy_resource_get_name(resource), NULL);
}

static void
copy_item(GwyResourceEditor *editor, const gchar *name, const gchar *newname)
{
    gwy_debug("<%s> -> <%s>", name, newname);

    GwyResourceEditorPrivate *priv = editor->priv;
    GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(priv->treeview));
    GwyInventory *inventory = gwy_inventory_store_get_inventory(GWY_INVENTORY_STORE(model));
    GwyResource *resource = gwy_inventory_new_item(inventory, name, newname);
    gwy_resource_tree_view_set_active(priv->treeview, gwy_resource_get_name(resource));
    save_resource(editor, gwy_resource_get_name(resource));
}

static void
delete_item(GwyResourceEditor *editor)
{
    gwy_resource_editor_commit(editor);

    /* Get selected resource, and the inventory it belongs to: */
    GwyResourceEditorPrivate *priv = editor->priv;
    GtkTreeModel *model;
    GtkTreeIter iter;
    GwyResource *resource = get_active(editor, &model, &iter, "Delete");
    const gchar *name = gwy_resource_get_name(resource);
    if (priv->edited_resource && gwy_strequal(priv->edited_resource->str, name)) {
        gtk_widget_destroy(priv->edit_window);
    }
    gwy_resource_delete(resource);
}

static void
edit_item(GwyResourceEditor *editor)
{
    GwyResource *resource = get_active(editor, NULL, NULL, "Edit");
    edit_resource(editor, gwy_resource_get_name(resource));
}

static void
row_activated(GwyResourceEditor *editor,
              GtkTreePath *path,
              G_GNUC_UNUSED GtkTreeViewColumn *column)
{
    GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(editor->priv->treeview));
    GwyResource *resource;
    GtkTreeIter iter;
    gtk_tree_model_get_iter(model, &iter, path);
    gtk_tree_model_get(model, &iter, 0, &resource, -1);
    if (!gwy_resource_is_modifiable(resource))
        return;

    edit_resource(editor, gwy_resource_get_name(resource));
}

static void
edit_resource(GwyResourceEditor *editor, const gchar *name)
{
    gwy_resource_editor_commit(editor);

    GwyResourceEditorClass *klass = GWY_RESOURCE_EDITOR_GET_CLASS(editor);
    GwyResourceEditorPrivate *priv = editor->priv;

    if (!priv->edit_window) {
        priv->edited_resource = g_string_new(name);
        priv->edit_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
        update_title(editor);
        klass->construct_editor(editor, GTK_CONTAINER(priv->edit_window));
        g_signal_connect_swapped(priv->edit_window, "destroy", G_CALLBACK(editor_closed), editor);
        gtk_widget_show_all(priv->edit_window);
    }
    else {
        g_string_assign(priv->edited_resource, name);
        update_title(editor);
        klass->switch_resource(editor);
        gtk_window_present(GTK_WINDOW(priv->edit_window));
    }
}

static void
editor_closed(GwyResourceEditor *editor)
{
    gwy_resource_editor_commit(editor);
    GwyResourceEditorPrivate *priv = editor->priv;
    g_assert(priv->edited_resource);

    g_string_free(priv->edited_resource, TRUE);
    priv->edited_resource = NULL;
    priv->edit_window = NULL;
}

static void
name_edited(GwyResourceEditor *editor, const gchar *strpath, const gchar *text)
{
    gwy_resource_editor_commit(editor);

    GwyResourceEditorPrivate *priv = editor->priv;
    GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(priv->treeview));
    GwyInventory *inventory = gwy_inventory_store_get_inventory(GWY_INVENTORY_STORE(model));
    if (gwy_inventory_get_item(inventory, text))
        return;

    GtkTreePath *path = gtk_tree_path_new_from_string(strpath);
    GtkTreeIter iter;
    gtk_tree_model_get_iter(model, &iter, path);
    gtk_tree_path_free(path);
    GwyResource *resource;
    gtk_tree_model_get(model, &iter, 0, &resource, -1);
    gboolean is_being_edited = (priv->edited_resource
                                && gwy_strequal(gwy_resource_get_name(resource), priv->edited_resource->str));

    gwy_resource_rename(resource, text);

    if (is_being_edited) {
        g_string_assign(priv->edited_resource, text);
        update_title(editor);
    }
    gwy_resource_tree_view_set_active(priv->treeview, text);
}

/**
 * gwy_resource_editor_queue_commit:
 * @editor: A resource editor.
 *
 * Queues commit of resource changes, marking the currently edited resource `dirty'.
 *
 * Call this method in particular resource editor subclass whenever user changes some editor property.
 *
 * To flush pending commit, call gwy_resource_editor_commit().  To immediately commit a change, call this method and
 * then gwy_resource_editor_commit().
 **/
void
gwy_resource_editor_queue_commit(GwyResourceEditor *editor)
{
    g_return_if_fail(GWY_IS_RESOURCE_EDITOR(editor));

    GwyResourceEditorPrivate *priv = editor->priv;
    if (priv->commit_id)
        g_source_remove(priv->commit_id);

    priv->commit_id = g_timeout_add_full(G_PRIORITY_LOW, COMMIT_TIMEOUT,
                                         (GSourceFunc)&gwy_resource_editor_commit, editor, NULL);
}

/**
 * gwy_resource_editor_commit:
 * @editor: A resource editor.
 *
 * Commits pending resource changes, if there are any.
 *
 * It calls @apply_changes method first (if it exists), then saves resource to disk.
 *
 * Changes are always immediately committed (if there are any pending): before the editor is destroyed, when
 * a resource stops being edited, before a resource is deleted, before a resource is renamed. When a resource is newly
 * created, it is immediately created on disk too.
 *
 * Returns: Always %FALSE (to be usable as #GSourceFunc).
 **/
gboolean
gwy_resource_editor_commit(GwyResourceEditor *editor)
{
    g_return_val_if_fail(GWY_IS_RESOURCE_EDITOR(editor), FALSE);
    GwyResourceEditorPrivate *priv = editor->priv;
    gwy_debug("%u", priv->commit_id);

    if (!priv->commit_id)
        return FALSE;

    g_return_val_if_fail(priv->edited_resource && *priv->edited_resource->str, FALSE);

    GwyResourceEditorClass *klass = GWY_RESOURCE_EDITOR_GET_CLASS(editor);
    if (klass->apply_changes)
        klass->apply_changes(editor);

    save_resource(editor, priv->edited_resource->str);
    priv->commit_id = 0;

    return FALSE;
}

static gboolean
save_resource(GwyResourceEditor *editor, const gchar *name)
{
    gwy_debug("<%s>", name);

    GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(editor->priv->treeview));
    GwyInventory *inventory = gwy_inventory_store_get_inventory(GWY_INVENTORY_STORE(model));
    GwyResource *resource = gwy_inventory_get_item(inventory, name);
    if (!resource) {
        g_warning("Trying to save `%s' that isn't in inventory", name);
        return FALSE;
    }

    GError *error = NULL;
    if (!gwy_resource_save(resource, &error)) {
        /* FIXME: GUIze this */
        g_warning("Cannot save resource %s: %s", name, error->message);
        g_clear_error(&error);
        return FALSE;
    }

    return TRUE;
}

static void
update_title(GwyResourceEditor *editor)
{
    GwyResourceEditorClass *klass = GWY_RESOURCE_EDITOR_GET_CLASS(editor);
    GwyResourceEditorPrivate *priv = editor->priv;
    gchar *title = g_strdup_printf(klass->editor_title, priv->edited_resource->str);
    gtk_window_set_title(GTK_WINDOW(priv->edit_window), title);
    g_free(title);
}

static GwyResource*
get_active(GwyResourceEditor *editor,
           GtkTreeModel **model,
           GtkTreeIter *iter,
           const gchar *warnwhat)
{
    GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(editor->priv->treeview));
    GtkTreeModel *treemodel;
    GtkTreeIter treeiter;
    if (!gtk_tree_selection_get_selected(selection, &treemodel, &treeiter)) {
        if (warnwhat)
            g_critical("Something must be selected for `%s'!", warnwhat);
        if (model)
            *model = NULL;
        return NULL;
    }

    GwyResource *resource;
    gtk_tree_model_get(treemodel, &treeiter, 0, &resource, -1);
    if (model)
        *model = treemodel;
    if (iter)
        *iter = treeiter;

    return resource;
}

/**
 * gwy_resource_editor_class_setup:
 * @klass: A resource editor class.
 *
 * Sets up particular resource editor class.
 *
 * Helper class method only intended for resource editor implementation. To be called in particular class
 * initialization methods.
 **/
void
gwy_resource_editor_class_setup(GwyResourceEditorClass *klass)
{
    g_return_if_fail(GWY_IS_RESOURCE_EDITOR_CLASS(klass));

    GwyResourceClass *resclass = g_type_class_ref(klass->resource_type);
    const gchar *name = gwy_resource_class_get_name(resclass);
    g_type_class_unref(resclass);
    klass->settings_prefix = g_strdup_printf("/app/%s/editor", name);
}

/**
 * gwy_resource_editor_get_edited:
 * @editor: A resource editor.
 *
 * Gets the currently edited resource.
 *
 * It is an error to call this method when no resource is being edited.
 *
 * Returns: The currently edited resource.
 **/
GwyResource*
gwy_resource_editor_get_edited(GwyResourceEditor *editor)
{
    g_return_val_if_fail(GWY_IS_RESOURCE_EDITOR(editor), NULL);
    GwyResourceEditorPrivate *priv = editor->priv;
    g_return_val_if_fail(priv->edited_resource, NULL);

    GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(priv->treeview));
    GwyInventory *inventory = gwy_inventory_store_get_inventory(GWY_INVENTORY_STORE(model));

    return (GwyResource*)gwy_inventory_get_item(inventory, priv->edited_resource->str);
}

/**
 * SECTION: resource-editor
 * @title: GwyResourceEditor
 * @short_description: Base/helper class for resource editors
 *
 * The base class contains all the methods that handle the resource list, set defaults, or sync resource list with
 * disk.  Particular editors (subclasses) have to override (in fact, to fill, because #GwyResourceEditor does not
 * define them) the methods and class data in their class init methods.  Then they generally only need to care about
 * widgets inside their edit windows.
 **/

/**
 * GwyResourceEditorClass:
 * @resource_type: The #GType of edited resources.  It must be a type derived from #GwyResource.
 * @base_resource: The name of vanilla, default resource.  The `New' button creates a new resource as a copy of this
 *                 one.
 * @window_title: Resource list window title.  It should be already translated.
 * @editor_title: Editor window title template.  It must contain one <literal>%s</literal> that will be replaced with
 *                edited resource name.  It should be already translated.
 * @settings_prefix: Settings prefix for saved state, filled by gwy_resource_editor_class_setup().
 * @construct_treeview: Method to create the resource list widget, it is of the gwy_gradient_tree_view_new()
 *                      signature. There are currently some hardcoded assumptions about the tree view columns, so it
 *                      cannot be completely arbitrary.
 * @construct_editor: Method to construct editor contents in provided container (the container itself is managed by
 *                    #GwyResourceEditor, its contents by a particular editor).
 * @apply_changes: Method called on commit (before resource is written to disk).  It should obtain values from the
 *                 editor widgets and actually update the resource.
 * @switch_resource: Method to update editor window widgets to display another resource.
 * @instance: Editor instance.  Resource editors are singletons so if an instance exists, pointer to it is kept here.
 *
 * The resource editor class.
 **/

/* vim: set cin columns=120 tw=118 et ts=4 sw=4 cino=>1s,e0,n0,f0,{0,}0,^0,\:1s,=0,g1s,h0,t0,+1s,c3,(0,u0 : */
