/** -*- Mode: C++; tab-width: 4 -*-
 * vim: sw=4 ts=4:
 *
 * Gnome Apt options dialog
 *
 * 	(C) 1998 Havoc Pennington <hp@pobox.com>
 * 	    2002, 2003 Filip Van Raemdonck <mechanix@debian.org>
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 *
 * 	$Id$
 *
 **/

#include "drawtree.h"
#include "pkgtree.h"
#include "preferences.h"

class OrderEdit
{
public:
  OrderEdit(gint nitems, Preferences* p);

  GtkWidget* widget() { return box_; }

  void add(const char* name, gint current_ordinal);
  
  void get_order(vector<gint> & ordinals);

private:
  GtkWidget* box_;
  
  vector<GtkWidget*> optionmenus_;

  gint nitems_;

  Preferences* preferences_;

  GtkWidget* make_om();

  static void activate_cb(GtkWidget* mi, gpointer data);
  void recalc_order(GtkWidget* pivotal_om, gint ordinal);
};

OrderEdit::OrderEdit(gint nitems, Preferences* p) :
  box_(0), nitems_(nitems), preferences_(p) 
{
  box_ = gtk_vbox_new(FALSE,0);

  GtkWidget* hbox = gtk_hbox_new(FALSE,0);

  GtkWidget* label = gtk_label_new(_("Column:"));

  gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);

  gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);

  label = gtk_label_new(_("Position:"));

  gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);

	gtk_widget_set_size_request (label, 90, -1); // match option menus
	gtk_box_pack_end (GTK_BOX (hbox), label, FALSE, FALSE, 0);
	gtk_box_pack_start (GTK_BOX (box_), hbox, TRUE, TRUE, GAPT_PAD);
}

//  Why there's no gtk_menu_get_index() is beyond me.  So instead
//  we'll waste a bunch of RAM and have horrible code.

static void
set_active_item (GtkWidget* om, gint item) {
	g_object_set_data (G_OBJECT (om), "gnome_apt::active_item", GINT_TO_POINTER (item));
}

static gint
get_active_item (GtkWidget* om) {
	return GPOINTER_TO_INT (g_object_get_data (G_OBJECT (om), "gnome_apt::active_item"));
}

static void
set_ordinal (GtkWidget* mi, gint ord) {
	g_object_set_data (G_OBJECT (mi), "gnome_apt::ordinal", GINT_TO_POINTER (ord));
}

static gint
get_ordinal (GtkWidget* mi) {
	return GPOINTER_TO_INT (g_object_get_data (G_OBJECT (mi), "gnome_apt::ordinal"));
}

void
OrderEdit::activate_cb(GtkWidget* mi, gpointer data)
{
  OrderEdit* oe = static_cast<OrderEdit*>(data);

  // one hideous hack after another.
  GtkWidget* om = 
    gtk_menu_get_attach_widget(GTK_MENU(GTK_WIDGET(mi)->parent));

  gint ordinal = get_ordinal(mi);

  oe->recalc_order(om, ordinal);
}

#ifdef GNOME_ENABLE_DEBUG
static void 
spew(vector<GtkWidget*> & optionmenus)
{
  ga_debug("Order: ");

  set<gint> already;
  
  vector<GtkWidget*>::iterator i = optionmenus.begin();
  while (i != optionmenus.end())
    {
      gint ord = get_active_item(*i);

      ga_debug("%d ", ord);

      set<gint>::iterator j = already.find(ord);
      if (j != already.end()) g_warning("%d found twice!", ord);

      already.insert(ord);

      ++i;
    }
  
  ga_debug("\n");
}
#endif

void
OrderEdit::recalc_order(GtkWidget* pivotal_om, gint ordinal)
{
#ifdef GNOME_ENABLE_DEBUG
  spew(optionmenus_);
#endif

  g_return_if_fail(GTK_IS_OPTION_MENU(pivotal_om));

  gint pivot = get_active_item(pivotal_om);

#ifdef GNOME_ENABLE_DEBUG
  ga_debug("moving %d to %d\n", pivot, ordinal);
#endif

  g_return_if_fail(pivot != ordinal);

  set_active_item(pivotal_om, ordinal);

  bool moved_upward = pivot < ordinal;

  // All items "outside" the two "swapped" items do not move in the
  // order. Items between them have their position either incremented
  // or decremented, depending on the swap direction.
  // (the swap is not literal; hard to explain)

  gint min = MIN(pivot, ordinal);
  gint max = MAX(pivot, ordinal);

  vector<GtkWidget*>::iterator i = optionmenus_.begin();
  while (i != optionmenus_.end())
    {
      if (*i != pivotal_om) 
        {
          gint ord = get_active_item(*i);

          if (ord < min || ord > max)
            {
              ;
            }
          else
            {
              // We know we aren't going to bump an item off
              //  the end (< first or > last ordinal) because
              //  the end items can only move in one direction
              gint new_ord;
              if (moved_upward)
                {
                  new_ord = ord - 1;
                }
              else 
                {
                  new_ord = ord + 1;
                }
              
              gtk_option_menu_set_history(GTK_OPTION_MENU(*i), new_ord);
              set_active_item(*i, new_ord);
            }
        }

      ++i;
    }

#ifdef GNOME_ENABLE_DEBUG
  spew(optionmenus_);
#endif

	preferences_->apply();
}

void 
OrderEdit::add(const char* name, gint current_ordinal)
{
	GtkWidget* hbox = gtk_hbox_new (FALSE, GAPT_PAD);

	GtkWidget* label = gtk_label_new ("");
	gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);

	label = gtk_label_new (name);
  gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
	gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);

  GtkWidget* om    = make_om();

  gtk_option_menu_set_history(GTK_OPTION_MENU(om), current_ordinal);
  optionmenus_.push_back(om);
  set_active_item(om, current_ordinal);

	gtk_box_pack_end (GTK_BOX (hbox), om, FALSE, FALSE, 0);

	gtk_box_pack_start (GTK_BOX (box_), hbox, TRUE, TRUE, GAPT_PAD);
}

void 
OrderEdit::get_order(vector<gint> & ordinals)
{
  vector<GtkWidget*>::iterator i = optionmenus_.begin();
  while (i != optionmenus_.end())
    {
      gint ord = get_active_item(*i);
      
      ordinals.push_back(ord);

      ++i;
    }

  g_return_if_fail(static_cast<guint>(nitems_) == ordinals.size());
}

GtkWidget*
OrderEdit::make_om()
{
  GtkWidget* om = gtk_option_menu_new();

  GtkWidget* menu = gtk_menu_new();

  int i = 0;
  while (i < nitems_)
    {
      char buf[100];

      g_snprintf(buf, 99, "%d", i+1);
      
      GtkWidget* mi = gtk_menu_item_new_with_label(buf);
		g_signal_connect (G_OBJECT (mi), "activate", G_CALLBACK (activate_cb), this);
      set_ordinal(mi, i);
		gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi);

      ++i;
    }

  gtk_option_menu_set_menu(GTK_OPTION_MENU(om), menu);

/* FIXME: is this still true? */
  // Size allocation is broken in Gtk option menus
	gtk_widget_set_size_request (om, 80, -1);

  return om;
}

Preferences::Preferences (void) : pkglist_ (0), oe_ (0) { }

Preferences::~Preferences()
{
  if (oe_) delete oe_;
}

void 
Preferences::edit()
{
  g_return_if_fail(pkglist_ != 0);

	GtkWidget* dlg = gtk_dialog_new_with_buttons (_("Gnome Apt Preferences"),
	      NULL, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, NULL);
	gnome_apt_setup_dialog (dlg);
	g_signal_connect (G_OBJECT (dlg), "close", G_CALLBACK (close_cb), this);
	g_signal_connect (G_OBJECT (dlg), "response", G_CALLBACK (response_cb), NULL);

	GtkWidget* frame = gtk_frame_new (_("Column order"));
	gtk_container_set_border_width (GTK_CONTAINER (frame), GAPT_PAD);
	GtkWidget* vbox = gtk_vbox_new (FALSE, GAPT_PAD);
  gtk_container_set_border_width(GTK_CONTAINER(vbox), GAPT_PAD);  
  gtk_container_add(GTK_CONTAINER(frame), vbox);
  
  g_return_if_fail(oe_ == 0);

	oe_ = new OrderEdit (DrawTree::ColumnTypeEnd, this);
  int i = 0;
	while (i < DrawTree::ColumnTypeEnd) {
		DrawTree::ColumnType ct = static_cast<DrawTree::ColumnType> (i);
		gushort order = pkglist_->getView()->type_column (ct);
		oe_->add (DrawTree::get_column_name (ct, false), order);
      ++i;
    }

  gtk_box_pack_start(GTK_BOX(vbox), oe_->widget(),
                     TRUE, TRUE, 0);

	vbox = gtk_vbox_new (FALSE, GAPT_PAD);
	gtk_container_set_border_width (GTK_CONTAINER (vbox), GAPT_PAD);

	gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
  
	gtk_container_add (GTK_CONTAINER (GTK_DIALOG(dlg)->vbox), vbox);
	gtk_widget_show_all (dlg);
}

void
Preferences::response_cb (GtkWidget* wdg, gint response, gpointer data) {
	if (response == GTK_RESPONSE_CLOSE) {
		gtk_widget_destroy (wdg);
	} else {
		g_warning ("Got response: %d", response);
	}
}

void
Preferences::apply (void) {
	vector<DrawTree::ColumnType> column_order (DrawTree::ColumnTypeEnd);

  vector<gint> order;
  oe_->get_order(order);

  int c = 0;
  vector<gint>::iterator i = order.begin();
  while (i != order.end())
    {
		DrawTree::ColumnType ct = static_cast<DrawTree::ColumnType> (c);
      column_order[*i] = ct;
      ++i;
      ++c;
    }

	pkglist_->getView()->set_column_order (column_order);
}

gint 
Preferences::close_cb (GtkWidget* w, gpointer data)
{
  Preferences* p = static_cast<Preferences*>(data);

  p->close();

  return FALSE;
}
 
void 
Preferences::close()
{
  if (oe_)
    {
      delete oe_; 
      oe_ = 0;
    }
}

static Preferences* preferences = 0;

Preferences*
gnome_apt_preferences()
{
  if (preferences == 0)
    preferences = new Preferences;

  return preferences;
}

void         
gnome_apt_preferences_shutdown()
{
  if (preferences != 0)
    delete preferences;

  preferences = 0;
}
