/*
 * Java-Gnome Bindings Library
 *
 * Copyright 1998-2004 the Java-Gnome Team, all rights reserved.
 *
 * The Java-Gnome bindings library is free software distributed under
 * the terms of the GNU Library General Public License version 2.
 */

package org.gnu.gtk;

import java.util.Vector;

import org.gnu.glib.EventMap;
import org.gnu.glib.EventType;
import org.gnu.glib.GObject;
import org.gnu.glib.Type;
import org.gnu.gtk.event.TreeSelectionEvent;
import org.gnu.gtk.event.TreeSelectionListener;
import org.gnu.glib.Handle;

/**
 * The TreeSelection object is a helper object to manage the selection for a
 * {@link TreeView} widget. The TreeSelection object is automatically created
 * when a newTreeView widget is created, and cannot exist independentally of
 * this widget. The primary reason the TreeSelection objects exists is for
 * cleanliness of code and API. That is, there is no conceptual reason all these
 * functions could not be methods on the TreeView widget instead of a separate
 * function.
 * 
 * <p>
 * The TreeSelection object is gotten from a TreeView by calling
 * {@link TreeView#getSelection()}. It can be manipulated to check the
 * selection status of the tree, as well as select and deselect individual rows.
 * Selection is done completely view side. As a result, multiple views of the
 * same model can have completely different selections. Additionally, you cannot
 * change the selection of a row on the model that is not currently displayed by
 * the view without expanding its parents first.
 * 
 * <p>
 * One of the important things to remember when monitoring the selection of a
 * view is that the {@link org.gnu.gtk.event.TreeSelectionEvent.Type#CHANGED CHANGED}
 * event is mostly a hint. That is, it may only emit one signal when a range of
 * rows is selected. Additionally, it may on occasion emit a "changed" event
 * when nothing has happened (mostly as a result of programmers calling
 * selectRow on an already selected row).
 * 
 * @see TreeView
 */
public class TreeSelection extends GObject {
    /**
     * constructs a new selection using handle from a native function..
     */
    protected TreeSelection(Handle handle) {
        super(handle);
    }

    /**
     * constructs a new selection using handle from a native function..
     */
    protected static TreeSelection getTreeSelection(Handle handle) {
        if (handle == null)
            return null;

        TreeSelection obj = (TreeSelection) getGObjectFromHandle(handle);
        if (obj == null)
            obj = new TreeSelection(handle);

        return obj;
    }

    /**
     * Calls the forEach method of the specified class for every selected value.
     */
    public void forEachSelected(TreeSelectionForEach implementor) {
        curForEach = implementor;
        startForEach(getHandle(), this);
    }

    private TreeSelectionForEach curForEach;

    /** This is called from the jni code to operate the foreach method */
    private void doEach(Handle model, Handle path, Handle iter) {
        TreeModel m = TreeModel.getTreeModel(model);
        curForEach.forEach(m, TreePath.getTreePath(path), TreeIter.getTreeIter(
                iter, m));
    }

    /**
     * Sets the selection mode of the selection. If the previous type was
     * SelectionMode.MULTIPLE, then the anchor is kept selected, if it was
     * previously selected.
     * 
     * @param mode
     *            The selection mode to use.
     */
    public void setMode(SelectionMode mode) {
        gtk_tree_selection_set_mode(getHandle(), mode.getValue());
    }

    public SelectionMode getMode() {
        return SelectionMode.intern(gtk_tree_selection_get_mode(getHandle()));
    }

    /**
     * Returns the {@link TreeView} associated with the selection.
     * 
     * @return Associated widget
     */
    public TreeView getTreeView() {
        Handle hndl = gtk_tree_selection_get_tree_view(getHandle());
        return TreeView.getTreeView(hndl);
    }

    /**
     * Select the row at path.
     * 
     * @param path
     *            The path to be selected.
     */
    public void select(TreePath path) {
        gtk_tree_selection_select_path(getHandle(), path.getHandle());
    }

    /**
     * Unselects the row at path.
     * 
     * @param path
     *            The path to be deselected
     */
    public void unselect(TreePath path) {
        gtk_tree_selection_unselect_path(getHandle(), path.getHandle());
    }

    /**
     * Returns TRUE if the row pointed to by path is currently selected. If path
     * does not point to a valid location, FALSE is returned
     * 
     * @param path
     *            A patch to check the selection on
     * @return True if the path is selected.
     */
    public boolean getSelected(TreePath path) {
        return gtk_tree_selection_path_is_selected(getHandle(), path
                .getHandle());
    }

    /**
     * Selects the specified iterator.
     * 
     * @param iter
     *            The {@link TreeIter} to be selected.
     */
    public void select(TreeIter iter) {
        gtk_tree_selection_select_iter(getHandle(), iter.getHandle());
    }

    /**
     * Unselects the specified iterator.
     * 
     * @param iter
     *            The {@link TreeIter} to be unselected.
     */
    public void unselect(TreeIter iter) {
        gtk_tree_selection_unselect_iter(getHandle(), iter.getHandle());
    }

    /**
     * Returns TRUE if the row pointed to by iter is currently selected.
     * 
     * @param iter
     *            The iter to test the selection of
     * @return true if the iter is selected.
     */
    public boolean getSelected(TreeIter iter) {
        return gtk_tree_selection_iter_is_selected(getHandle(), iter
                .getHandle());
    }

    /**
     * Selects all the nodes. Mode must be set to SelectionMode.MULTIPLE
     */
    public void selectAll() {
        gtk_tree_selection_select_all(getHandle());
    }

    /**
     * Unselects all the nodes.
     */
    public void unselectAll() {
        gtk_tree_selection_unselect_all(getHandle());
    }

    /**
     * Selects a range of nodes, determined by startPath and endPath inclusive.
     * 
     * @param startPath
     *            The initial node of the range.
     * @param endPath
     *            The final node of the range.
     */
    public void select(TreePath startPath, TreePath endPath) {
        gtk_tree_selection_select_range(getHandle(), startPath.getHandle(),
                endPath.getHandle());
    }

    public void unselect(TreePath startPath, TreePath endPath) {
        gtk_tree_selection_unselect_range(getHandle(), startPath.getHandle(),
                endPath.getHandle());
    }

    /**
     * Returns an array of rows which are currently selected
     * 
     * @return an empty array if there aren't any rows selected; or an array
     *         filled with TreePath objects, representing the selections.
     * @since 2.2
     */
    public TreePath[] getSelectedRows() {
        Handle[] hndls = gtk_tree_selection_get_selected_rows(getHandle());
        if (null == hndls)
            return null;
        TreePath[] paths = new TreePath[hndls.length];
        for (int i = 0; i < hndls.length; i++) {
            paths[i] = TreePath.getTreePath(hndls[i]);
        }
        return paths;
    }

    /**
     * Returns the number of rows that have been selected.
     * 
     * @since 2.2
     */
    public int countRows() {
        return gtk_tree_selection_count_selected_rows(getHandle());
    }

    // /////////////////////////////////////////////////////////////////////////////
    // EVENTS - the Changed signal.
    //
    /** listeners for selection changing */
    private Vector selectionListeners = null;

    /**
     * Register an object to handle Selection change events which are emitted
     * whenever the selection has (possibly) changed
     * 
     * @see org.gnu.gtk.event.TreeSelectionListener
     */
    public void addListener(TreeSelectionListener listener) {
        // Don't add the listener a second time if it is in the Vector.
        int i = Widget.findListener(selectionListeners, listener);
        if (i == -1) {
            if (null == selectionListeners) {
                evtMap.initialize(this, TreeSelectionEvent.Type.CHANGED);
                selectionListeners = new Vector();
            }
            selectionListeners.addElement(listener);
        }
    }

    /**
     * Removes a listener
     * 
     * @see #addListener(TreeSelectionListener)
     */
    public void removeListener(TreeSelectionListener listener) {
        int i = Widget.findListener(selectionListeners, listener);
        if (i > -1) {
            selectionListeners.remove(i);
        }
        if (0 == selectionListeners.size()) {
            evtMap.uninitialize(this, TreeSelectionEvent.Type.CHANGED);
            selectionListeners = null;
        }
    }

    protected void fireSelectionEvent(TreeSelectionEvent event) {
        if (null == selectionListeners) {
            return;
        }
        int size = selectionListeners.size();
        int i = 0;
        while (i < size) {
            TreeSelectionListener bl = (TreeSelectionListener) selectionListeners
                    .elementAt(i);
            bl.selectionChangedEvent(event);
            i++;
        }
    }

    private void handleChanged() {
        fireSelectionEvent(new TreeSelectionEvent(this));
    }

    public Class getEventListenerClass(String signal) {
        Class cls = evtMap.getEventListenerClass(signal);
        if (cls == null)
            cls = super.getEventListenerClass(signal);
        return cls;
    }

    public EventType getEventType(String signal) {
        EventType et = evtMap.getEventType(signal);
        if (et == null)
            et = super.getEventType(signal);
        return et;
    }

    private static EventMap evtMap = new EventMap();
    static {
        addEvents(evtMap);
    }

    private static void addEvents(EventMap evtMap) {
        evtMap.addEvent("changed", "handleChanged",
                TreeSelectionEvent.Type.CHANGED, TreeSelectionListener.class);
    }

    /**
     * Retrieve the runtime type used by the GLib library.
     */
    public static Type getType() {
        return new Type(gtk_tree_selection_get_type());
    }

    native static final private void startForEach(Handle handle,
            TreeSelection selection);

    native static final protected int gtk_tree_selection_get_type();

    native static final protected void gtk_tree_selection_set_mode(
            Handle selection, int type);

    native static final protected int gtk_tree_selection_get_mode(
            Handle selection);

    // native static final protected int gtk_tree_selection_get_user_data(Handle
    // selection);
    native static final protected Handle gtk_tree_selection_get_tree_view(
            Handle selection);

    // native static final protected int gtk_tree_selection_get_selected(Handle
    // selection);
    native static final protected Handle[] gtk_tree_selection_get_selected_rows(
            Handle selection);

    native static final protected int gtk_tree_selection_count_selected_rows(
            Handle selection);

    native static final protected void gtk_tree_selection_select_path(
            Handle selection, Handle path);

    native static final protected void gtk_tree_selection_unselect_path(
            Handle selection, Handle path);

    native static final protected void gtk_tree_selection_select_iter(
            Handle selection, Handle iter);

    native static final protected void gtk_tree_selection_unselect_iter(
            Handle selection, Handle iter);

    native static final protected boolean gtk_tree_selection_path_is_selected(
            Handle selection, Handle path);

    native static final protected boolean gtk_tree_selection_iter_is_selected(
            Handle selection, Handle iter);

    native static final protected void gtk_tree_selection_select_all(
            Handle selection);

    native static final protected void gtk_tree_selection_unselect_all(
            Handle selection);

    native static final protected void gtk_tree_selection_select_range(
            Handle selection, Handle startPath, Handle endPath);

    native static final protected void gtk_tree_selection_unselect_range(
            Handle selection, Handle startPath, Handle endPath);
}
