/** -*- Mode: C++; tab-width: 4 -*-
 * vim: sw=4 ts=4:
 *
 * Gnome Apt package tree display
 *
 * 	(C) 1998 Havoc Pennington <hp@pobox.com>
 * 	    2002-2004 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$
 *
 **/

#ifndef GNOME_APT_DRAW_TREE_H
#define GNOME_APT_DRAW_TREE_H

#include <gtk/gtk.h>
#include <map>
#include <string>
#include <vector>

/* FIXME: this is only needed for statustype & cachecontrol */
#include "cachecontrol.h"

class GAptPkgTree;
class TreeNode;
typedef vector<TreeNode*> Nodes;

class DrawTree {
public:
	typedef class TreeNode Node;

	DrawTree (GAptPkgTree*);
	~DrawTree (void);

	GAptPkgTree* getModel (void) { return dt_model; }
	GtkWidget* widget (void) { return da_; }

	void queue_display_update (void);
	void queue_recalc_rows (bool = false);

	void change_selection (Node*, bool = false);
  bool openclose_node(Node* node);

  enum ColumnStyle {
    BoolCol,         // check item; no label drawn
    DrawCol,         // Node::draw_column is called
    TreeCol          // DrawCol which accounts for tree depth and draws expanders.
  };

	typedef enum {
		ColumnDelete, ColumnInstall,
		ColumnName,
		ColumnCurrent, ColumnAvailable,
		ColumnStatus,
		ColumnSection, ColumnPriority,
		ColumnDescription,

		ColumnTypeEnd
	} ColumnType;

	void set_visible (ColumnType, bool);
	bool is_visible (ColumnType) const;

  void set_column(gushort col, ColumnStyle style);
  
	void set_column_order (const vector<ColumnType>&);

	static const char* get_column_name (ColumnType, bool);
	gushort type_column (ColumnType) const;

	ColumnType column_type (gushort col) const {
		g_return_val_if_fail (col < dt_coltypes.size(), ColumnTypeEnd);
		return dt_coltypes[col];
	}

  // We keep these here so every item doesn't 
  //  have to have one.
  GdkGC*   gc() {
    g_return_val_if_fail(gc_,0);
    return gc_;
  }

  Node* selected_node();
	Nodes* get_selection (void);

  void set_hadjustment(GtkAdjustment* a);
  void set_vadjustment(GtkAdjustment* a);

	/* Needed by the app window to pass on events. */
	gint consider_key_event (GdkEventKey*);

	class Column {
		string name;
		ColumnType type;

	public:
	/* FIXME: don't expose internals */
		ColumnStyle style;

		/* If the window is resized larger than we need, each column gets padded,
		    but we need to save the user-defined size. */
		int width;
		int extra;
		int x;	/* cache x positions for efficiency */

		guint visible : 1;
		guint shortcut;

		Column (ColumnType, ColumnStyle, string = "", char = 0);

		const ColumnType getType (void) const { return (const ColumnType) type; }
		const char* getName (void) const { return (const char*) name.c_str(); }
		const int getWidth (void) const { return (const int) width; }
		const bool isVisible (void) const { return (const bool) visible; }
		void setVisible (bool b) { visible = b; }
		void setWidth (int w) { width = w; }
	};

	gboolean MarkRows[2]; /* broken, orphaned */
private:
  // called by static callback
  gint configure(GdkEventConfigure* event);
  static gint configure_event(GtkWidget* w, GdkEventConfigure* event, gpointer data);

  gint expose(GdkEventExpose* event);
  static gint expose_event(GtkWidget* w, GdkEventExpose* event, gpointer data);
  
  gint button(GdkEventButton* event);
  static gint button_event(GtkWidget* w, GdkEventButton* event, gpointer data);

	gint key (GdkEventKey*);

  gint motion(GdkEventMotion* event);
  static gint motion_event(GtkWidget* w, GdkEventMotion* event, gpointer data);

  gint leave(GdkEventCrossing* event);
  static gint leave_event(GtkWidget* w, GdkEventCrossing* event, gpointer data);

  static void realize_cb(GtkWidget* w, gpointer data);
  void realize();

	static gint scroll_event_cb (GtkWidget*, GdkEventScroll*, gpointer);
	gint scroll_event (GdkEventScroll*);

  // for internal use (by idle callback), use queue version
  void do_display_update();
  static gint update_idle(gpointer data);

	GAptPkgTree* dt_model;
	GtkWidget* da_;
	GdkPixmap* pixmap_;

  bool update_queued_;

  vector<Column*> columns_;
	vector<ColumnType> dt_coltypes;

	/* Nodes on screen at any given time, cached for speed */
	Nodes visible_nodes_;
  vector<gushort> depths_;

  // recalc_rows sets focus_index to the index of a node
  //  matching this one. it's a hack so we can use recalc_rows
  //  rather than writing a find_row_number function
  Node* focus_node_; 
  guint focus_index_;

  void recalc_widths();
  gint total_width_;

	bool recalc_level (Nodes::iterator&, Nodes::iterator&, gint);
  static gint recalc_rows_idle(gpointer data);
  void recalc_rows();
  bool recalc_rows_queued_;
  // row which begins current display
  guint row_; 

  // rows in the display
  guint nrows_; 

	TreeNode* selected_node_;
	TreeNode* dt_highlight;
	Nodes dt_selection;
  gint prelight_;    // -1 is magic for none

  GdkGC* gc_;

  GtkAdjustment* hadjustment_;
  GtkAdjustment* vadjustment_;

  // so we can avoid changing things if we get a configure event
  //  with the same size we already have. This prevents 
  //  flicker on pane changes
  gint pixwidth_;
  gint pixheight_; 

	/* Returns offset from current row_, or -1 for none */
  gint row_from_coords(gint x, gint y);
  // -1 for none
  gint column_from_x  (gint x); 

	gint max_rows_onscreen();
  float exact_rows_onscreen();

  // arg can be negative
  void scroll_by(gint rows);

  void draw_expander(GdkRectangle* cell_rect, bool expanded);
  void update_row(guint visible_row_number, GtkStateType state, 
                  bool copy_to_window = true);

  gint row_index(Node* n);

	typedef enum {
		SelReplace, SelAdd, SelKeep
	} SelectionMode;

	int focus_node (Node*, bool = true);
	/* Move highlight around and optionally change the selection */
	void move_selection (gint, SelectionMode = SelReplace);

  void setup_adjustments();
  static void hadjustment_changed_signal(GtkAdjustment* a, gpointer data);
  static void vadjustment_changed_signal(GtkAdjustment* a, gpointer data);
  void hadjustment_changed();
  void vadjustment_changed();

  // Column resize stuff
  class Resize {
  public:
    Resize(size_t left, size_t right) :
      left_(left), right_(right), xor_gc_(0) {}

    size_t left_;
    size_t right_;
    GdkGC* xor_gc_;
  };
  
  // Non-0 means we are resizing.
  Resize* resize_;

  guint update_idle_id_;
  guint recalc_idle_id_;

	GAptCacheControl* dt_cachectrl;

  void draw_resize_line();
	/* 'remaining' is how many more columns will be drawn (for data caching) */
	void draw_column (Node*, gushort, gushort, GtkStateType, GdkRectangle*);

	void init_columns (const vector<ColumnType>&);
	void load_column_attrs (void);

	gboolean get_bool (gpointer, gushort);
	void set_bool (gpointer, gushort, bool);
	gboolean display_bool (gpointer, gushort);
};

#endif
