/*

    GEO, Postgres GIS (Geografic Information System) frontend

    Copyright (C) 1991  TNO Institute for Perception, The Netherlands
			Written by: Tom Vijlbrief

    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 1, 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., 675 Mass Ave, Cambridge, MA 02139, USA.


Note that parts of this distribution are taken from the ET++
distribution. See the et2.2/ subdirectories for details.

===============================================================================
Tom Vijlbrief
TNO Institute for Perception		Phone: +31 34 63 562 11
P.O. Box 23				Fax:   +31 34 63 539 77
3769 ZG  Soesterberg			E-mail: tom@izf.tno.nl
The Netherlands				or: uunet!hp4nl.nluug.nl!tnosoes!tom
===============================================================================

*/

static char *sccs_id= (sccs_id, "@(#)TableDialog.c	3.10 7/28/92");

#ifdef __GNUG__
#pragma implementation "AdjTextItem.h"
#pragma implementation "TableDialog.h"
#endif

#include	"Class.h"
#include	"Dialog.h"
#include	"Alert.h"
#include	"DialogItems.h"
#include	"BorderItems.h"
#include	"Scroller.h"
#include	"Splitter.h"
#include	"Window.h"
#include	"CollView.h"
#include	"OrdColl.h"
#ifdef ET25
#include	"Fields.h"
#include	"Filler.h"
#else
#include	"FileType.h"
#include	"BlankWin.h"
#endif
#include	"CheapText.h"
#include	"ClipBoard.h"
#include	"Menu.h"
#include	"DrawView.h"
#include	"Set.h"

#include	"Quel.h"
#include	"QuelInfo.h"
#include	"QueryDialog.h"
#include	"TableDialog.h"
#include	"AdjTextItem.h"

extern bool	GEOBROWSE;

const int	AtCutLen= 200;

const int	cEDITDONE= cUSERCMD + 0,
		cEDITNEXT= cUSERCMD + 1;


#ifdef ET23
inline void AllWait(bool) {}
#else
#endif

static void AddDots(char *s, char *r, int len= AtCutLen)
{
  strncpy(r, s, len);
  r[len-3]= '.';
  r[len-2]= '.';
  r[len-1]= '.';
  r[len]= '\0';
}

static char ** SaveArray(char **a, int n)
{
  char **r= new char * [n+1];

  r[n]= 0;

  for (int i= 0; i < n; i++)
	r[i]= strsave(a[i]);
  return r;
}

static void DelArray(char **a)
{
  for (int i= 0; a[i]; i++)
	delete a[i];
  delete a;
}



class LinkedSplitter: public Splitter {
  Clipper	*linkobj;
public:
  LinkedSplitter(Clipper *l, VObject *v= 0, Point e= gPoint0, int id= cIdNone):
    (v,e,id), linkobj(l) { }

  void Control(int id, int part, void *val);
  Clipper *SetLink(Clipper *c)
	{ return linkobj= c; }
};



void LinkedSplitter::Control(int id, int part, void *val)
{
    switch (part) {
    case cPartScrollPage:
    case cPartScrollRel:
    case cPartScrollStep:
    case cPartScrollAbs:
    case cPartScrollVAbs:
    case cPartScrollHAbs:
        linkobj->Scroll(part, *((Point*)val));
        break;
    }
    Splitter::Control(id, part, val);
}
 

class LinkedScroller: public Scroller {
  Clipper	*linkobj;
public:
  LinkedScroller(Clipper *l, VObject *v= 0, Point e= gPoint0, int id= cIdNone,
    ScrollDir sd= eScrollDefault):
    (v,e,id,sd), linkobj(l) { }

  void Control(int id, int part, void *val);
  Clipper *SetLink(Clipper *c)
	{ return linkobj= c; }
};


void LinkedScroller::Control(int id, int part, void *val)
{
    switch (part) {
    case cPartScrollPage:
    case cPartScrollRel:
    case cPartScrollStep:
    case cPartScrollAbs:
    case cPartScrollVAbs:
    case cPartScrollHAbs:
        linkobj->Scroll(part, *((Point*)val));
        break;
    }
    Scroller::Control(id, part, val);
}


MetaImpl(AdjTextItem, (T(adjwidth), 0));

Metric AdjTextItem::GetMinSize()
{
  Metric m= TextItem::GetMinSize();
  if (adjwidth) {
	m.extent.x= adjwidth;
  }
  return m;
};


MetaImpl0(TupleEditDialog);

TupleEditDialog::TupleEditDialog(TableDialog *tabled, bool cr, bool eq, byte *title,
	int nfields, char **f, char **t, char **c, bool block)
#ifdef ET25
	: ((char*) title, WindowFlags(block ? eWinFixed+eWinBlock: eWinFixed)),
#else
	: ((char*) title, block ? eBWinBlock: eBWinFixed),
#endif
	name((char*) title), nf(nfields),
	creating(cr), fields(f), types(t), edit_q(eq)
{
  kbdid= -1;
  td= tabled;
  do_edit_q= cancelled= deleted= replaced= added= FALSE;
#ifdef ET25
  etitems= new TextField*[nf];
#else
  etitems= new EditTextItem*[nf];
#endif
  to_long= new char*[nf];

  if (c) {
    contents= new char*[nf];
    for (int i= 0; i < nf; i++)
      contents[i]= c[i] ? strsave(c[i]): 0;
  }
}


TupleEditDialog::~TupleEditDialog()
{
  if (IsOpen())
    Close();

  // etitems->FreeAll is already done by ~DialogView
  delete etitems;

  for (int i= 0; i < nf; i++) {
    delete to_long[i];
    if (contents)
      delete contents[i];
  }
  delete to_long;
  delete contents;
}


static Point	show_pos;

void TupleEditDialog::ShowIt()
{
  if (show_pos == gPoint0)
    show_pos= Point(100,100);
  ShowAt(0, show_pos);
  show_pos+= Point(16,30);
}

void TupleEditDialog::SetPos(Point p)
{
  Point ip= GetInitialPos();
  show_pos= p + ip - Point(5,26); // Correct for external windowmanager offset
				  // This is an ET2.2 bug....
}

void TupleEditDialog::SavePos()
{
  SetPos(GetWindow()->GetRect().origin);
}


static bool ReadOnly(char *s)
{
  return
    strcmp(s, "oid") == 0 ||
    strcmp(s, "tmin") == 0 ||
    strcmp(s, "tmax") == 0;
}


bool	map_show_points;

static const int cIdDelete	= cIdFirstUser + 0,
		 cIdEditQ	= cIdFirstUser + 1,
		 cIdCreateOnMap	= cIdFirstUser + 2,
		 cIdEditOnMap	= cIdFirstUser + 3,
		 cIdNext	= cIdFirstUser + 4,
		 cIdMatch	= cIdFirstUser + 5,
		 cIdShowPoints	= cIdFirstUser + 6,
		 cIdItem	= cIdFirstUser + 1000;

VObject *TupleEditDialog::DoCreateDialog()
{
  OrdCollection	*ca= new OrdCollection;
  OrdCollection	*ct= new OrdCollection;
  OrdCollection	*cc= new OrdCollection;

  DynInfo *di= td->GetQD()->GetDyn();
  char *oidstr= di && di->TheOid() ? di->TheOid(): "oid";
  for (int i= 0; i < nf; i++) {
    etitems[i]= 0;
    bool readonly= ReadOnly(fields[i]);
    if (readonly && creating)
	continue;
#ifdef ET25
    ca->Add(new TextItem(fields[i], gSysFont, gPoint5));
    ct->Add(new TextItem(types[i], gSysFont, gPoint5));
    TextField *newet;
    cc->Add(newet= new TextField(cIdItem+i, 20));
#else
    ca->Add(new TextItem(fields[i]));
    ct->Add(new TextItem(types[i]));
    EditTextItem *newet;
    cc->Add(newet= new EditTextItem(cIdItem+i, "", 200));
#endif
    etitems[i]= newet;
    if (readonly)
#ifdef ET25
      newet->SetEditable(FALSE);
#else
      newet->Tv()->SetReadOnly(TRUE);
#endif
    SetText(i, creating || !contents[i] ? "": contents[i]);
    if (strcmp(fields[i], oidstr) == 0)
	oid= i;
  }

  VObject *v= new Cluster(cIdNone, eVObjHLeft, 10,
#ifdef ET25
    new Cluster(cIdNone, eVObjVTop, 5,
#else
    new Cluster(cIdNone, eVObjVCenter, 5,
#endif
	new BorderItem("Attribute", new Cluster(cIdNone, eVObjHLeft, 5, ca)),
	new BorderItem("Type", new Cluster(cIdNone, eVObjHLeft, 5, ct)),
	new BorderItem("Value", new Cluster(cIdNone, eVObjHLeft, 5, cc)),
	0),
    GEOBROWSE ?
     new Filler(gPoint0):
     new Cluster(cIdNone, eVObjVCenter, 5,
	new ActionButton(cIdCreateOnMap, "Create on Map"),
	new ActionButton(cIdEditOnMap, "Edit on Map"),
	new BorderItem("Nodes", new Cluster(cIdNone, eVObjVCenter, 6,
#ifdef ET25
	  match_point= new ToggleButton(cIdMatch, "Match", TRUE),
	  show_point= new ToggleButton(cIdShowPoints, "Show", map_show_points),
#else
	  new LabeledButton(cIdMatch,
	    match_point= new ToggleButton(cIdNone, TRUE),
	    new TextItem("Match"), gPoint2),
	  new LabeledButton(cIdShowPoints,
	    show_point= new ToggleButton(cIdNone, map_show_points),
	    new TextItem("Show"), gPoint2),
#endif
	  0)),
	0),
    new Cluster(cIdNone, eVObjVCenter, 5,
	new ActionButton(cIdOk, creating ? "Add": "Replace", TRUE),
	new ActionButton(cIdNext, "Next"),
	new ActionButton(cIdCancel, "Cancel"),
	creating ? 0: new ActionButton(cIdDelete, "Delete"),
	!edit_q ? 0: new ActionButton(cIdEditQ, "Edit Query..."),
	0),
    0);

  return v;
}


char *TupleEditDialog::GetText(int i)
{
#ifdef ET25
  return to_long[i] ? to_long[i]: etitems[i]->AsString();
#else
  return to_long[i] ? to_long[i]: etitems[i]->GetText()->AsString();
#endif
}
  
void TupleEditDialog::SetText(int i, char *s)
{
  const int	EditCutLen=30;

  SafeDelete(to_long[i]);
  if (strlen(s) >= AtCutLen) {
    char ds[EditCutLen+1];
    to_long[i]= strsave(s);
    AddDots(s, ds, EditCutLen);
    s= ds;
  }
  etitems[i]->SetString(s);
}

bool TupleEditDialog::AllFieldsMatch()
{
  for (int i= 0; i < nf; i++) {
	if (etitems[i] == 0 || (creating && GetText(i)[0] == '\0')
	|| (!creating && contents[i] && strcmp(contents[i], GetText(i)) == 0))
		continue;
	if (! GetInfo()->TypeMatchRE(GetText(i), types[i], name, fields[i]))
		return FALSE;
  }
  return TRUE;
}


void TupleEditDialog::ReplaceTuple()
{
  char	q[10000];
  char	f[10000];
  int	cnt= 0;

  sprintf(q, "replace %s(", name);
  bool first= TRUE;
  for (int i= 0; i < nf; i++) {
	if (etitems[i] == 0
	 || (contents[i] && strcmp(contents[i], GetText(i)) == 0)
	 || (!contents[i] && !GetText(i)[0]))
		continue;
	char escs[10000];
	EscSpecial(GetText(i), escs);
	sprintf(f, "%s%s=\"%s\"::%s", first ? "": ",", fields[i],
	  escs, types[i]);
	strcat(q, f);
	first= FALSE;
	cnt++;
  }
  if (cnt == 0) {
  	cancelled= TRUE;
	return;
  }

#if 1 /* tom@izf.tno.nl */
  DynInfo *di= td->GetQD()->GetDyn();
  sprintf(f, ") where %s.%s = \"%s\"::oid",
	name,
	di && di->TheOid() ? di->TheOid(): "oid",
	contents[oid]);
#else
  sprintf(f, ") where %s.oid = \"%s\"::oid",
	name, contents[oid]);
#endif
  strcat(q,f);

  SetDb(td->GetQD()->DataBaseName());
  QuelExec(q);
  replaced= TRUE;
}

void TupleEditDialog::AddTuple()
{
  char	q[10000];
  char	f[10000];
  int	cnt= 0;

  sprintf(q, "append %s(", name);
  bool first= TRUE;
  for (int i= 0; i < nf; i++) {
	if (etitems[i] == 0)
		continue;

#ifdef ET25
	char *ts= etitems[i]->AsString();
#else
	char *ts= etitems[i]->GetText()->AsString();
#endif
	char escs[10000];

	if (ts[0] == '\0')
	  continue;
	// EscSpecial(ts, escs);
	EscSpecial(GetText(i), escs);
	sprintf(f, "%s%s=\"%s\"::%s", first ? "": ",", fields[i],
	  escs, types[i]);
	strcat(q, f);
	first= FALSE;
	cnt++;
  }
  if (cnt == 0)
	return;
  strcat(q, ")");

  SetDb(td->GetQD()->DataBaseName());
  QuelExec(q);
  added= TRUE;
}


void TupleEditDialog::SetCreateEdit()
{
#ifdef ET25
#else
    VObject *v= GetKbdFocus();
    if (oldfocus == v)
    	return;
    	
    oldfocus= v;   
    kbdid= !v ? -1: v->GetId() - cIdItem;

    DisableItem(cIdCreateOnMap);
    DisableItem(cIdEditOnMap);
    can_create= FALSE;
    if (kbdid >= 0) {
	switch (kbdtype= GetInfo(TRUE)->GetTypeOid(types[kbdid])) {
	  case PATH_TYPE:
	    EnableItem(cIdEditOnMap);
	  case POINT_TYPE:
	    can_create= TRUE;
	    EnableItem(cIdCreateOnMap);
	    break;
	}
    }
#endif
}

#ifdef ET25
#else

#ifdef ET23
Command *TupleEditDialog::DispatchEvents(Point lp, Token &t, Clipper *vf)
#else
Command *TupleEditDialog::DispatchEvents(Point lp, Token t, Clipper *vf)
#endif
{
    Command *cmd= Dialog::DispatchEvents(lp, t, vf);
    
    SetCreateEdit();

    return cmd;
}
#endif


void TupleEditDialog::DoNext()
{
  Control(cIdNext, cPartAction, 0);
}

TupleEditDialog	*cur_next;


void TupleEditDialog::Control(int id, int p, void *v)
{
  DrawView	*dv= (DrawView*) td->GetQD()->GetDrawView();

  switch (id) {
  case cIdOk:
  case cIdNext:
	if (!AllFieldsMatch())
		return;
	if (creating) {
	    AddTuple();
	} else
	    ReplaceTuple();
	if (!creating)
	  id= cIdOk;
	if (id == cIdNext) {
	  Send(cEDITNEXT, 0, &last_rect);
	  last_rect= gRect0;
	  if (can_create)
	    Control(cIdCreateOnMap, 0, 0);
	  cur_next= this;
	  return;
	}
	cur_next= 0;
	Send(cEDITDONE);
	RemoveObserver(td); // td may be destroyed
	SavePos();
	Dialog::Control(id, p, v);
	return;
  case cIdCancel:
	cur_next= 0;
	cancelled= TRUE;
	Send(cEDITDONE);
	RemoveObserver(td); // td may be destroyed
	SavePos();
	Dialog::Control(id, p, v);
	return;
  case cIdShowPoints:
	if (p != cPartToggle)
	  return;
	map_show_points= show_point->GetValue();
	dv->ForceRedraw();
	dv->UpdateEvent();
	cur_next= this;
	return;
  case cIdDelete:
	char q[1000];

	DynInfo *di= td->GetQD()->GetDyn();
	sprintf(q, "delete %s where %s.%s = \"%s\"::oid",
		name, name,
		di && di->TheOid() ? di->TheOid(): "oid",
		contents[oid]);

	SetDb(td->GetQD()->DataBaseName());
	QuelExec(q);
	deleted= TRUE;

	Send(cEDITDONE);
	RemoveObserver(td); // td may be destroyed
	SavePos();
	cur_next= 0;
	Dialog::Control(cIdOk, p, v);
	return;
  case cIdEditQ:
	do_edit_q= TRUE;
	Send(cEDITDONE);
	RemoveObserver(td); // td may be destroyed
	SavePos();
	Dialog::Control(cIdOk, p, v);
	cur_next= 0;
	return;
  case cIdCreateOnMap:
  case cIdEditOnMap:
	char		s[9000];
	
	switch (kbdtype) {
	  case PATH_TYPE:
	    if (id == cIdCreateOnMap) {
	     dv->SetMessage("Digitise a Path (left mouse button)");
	     if (dv->MakeNewPath(s, match_point->GetValue(), &last_rect)) {
	       SetText(kbdid, s);
	       dv->SetMessage("Path has been digitised");
	     } else {
	       SetText(kbdid, "");
	       dv->SetMessage("No Path was digitised");
	     }
	    } else {
	     strcpy(s, GetText(kbdid));
	     dv->SetMessage("Change the Path on your map (left mouse button)");
	     if (dv->EditPath(s, match_point->GetValue(), &last_rect)) {
	      SetText(kbdid, s);
	      dv->SetMessage("Path has been digitised");
	    } else
	      dv->SetMessage("No Path was digitised");
	    }
	    break;
	  case POINT_TYPE:
	    dv->SetMessage("Digitise a Point (left mouse button)");
	    if (dv->MakeNewPoint(s, match_point->GetValue(), &last_rect)) {
	      SetText(kbdid, s);
	      dv->SetMessage("Point has been digitised");
	    } else
	      dv->SetMessage("No Point was digitised");
	    break;
	  default:
	    ;
	}
	cur_next= this;
	return;
  default:
	cur_next= this;
	SetCreateEdit();
  }
  Dialog::Control(id, p, v);
}



MetaImpl0(TableDialog);

static const int cTableMinWidth	= 600;
static const int cColMinWidth	= 50;

static const int cIdTable	= cIdFirstUser + 0,
		 cIdRefresh	= cIdFirstUser + 1,
		 cIdNew		= cIdFirstUser + 2,
		 cIdEditTuple	= cIdFirstUser + 3,
		 cIdPrintTable	= cIdFirstUser + 4,
		 cIdEditRefresh	= cIdFirstUser + 5;

static const int MaxTuples=	1000;


TableDialog::TableDialog(byte *title, QueryDialog *qd, char *wh)
#ifdef ET25
	: ((char *)title, eWinFixed)
#else
	: ((char *)title, eBWinFixed)
#endif
{
  AllWait(TRUE);

  q= qd;
  name= (char*) title;
  where= wh;

  edit_active= new Set;
  edit_done= new Set;
  table_col= new OrdCollection;
  header_col= new OrdCollection;

  SetDb(qd->DataBaseName());
  nfields= q->GetNrFields();

  force_fetch= first_tuple= TRUE;
  if (nfields == 0)
	return;
  hw= new int[nfields];
  colw= new int[nfields];
  hcolw= new int[nfields];

  char		*attrt[1000];
  char		*attr[1000];
  int		ac;

  char	**get_aa= q->GetAttr(TRUE);
  for (ac= 0; ac < nfields; ac++) {
    char types[100];
    GetInfo(TRUE)->GetAttrTypeStr(q->TableName(), get_aa[ac], types);
    attrt[ac]= strsave(types);
    attr[ac]= get_aa[ac];
  }

  ta= SaveArray(attrt, nfields);
  aa= SaveArray(attr, nfields);

  for (ac= 0; ac < nfields; ac++)
    delete attrt[ac];

  AllWait(FALSE);
}


TableDialog::~TableDialog()
{
  if (IsOpen())
	Close();

  edit_active->FreeAll();
  delete edit_active;
  edit_done->FreeAll();
  delete edit_done;

  if (table_view)
    delete table_view;
  else {
    table_col->FreeAll();
    delete(table_col);
  }
  if (header_view)
    delete header_view;
  else {
    header_col->FreeAll();
    delete(header_col);
  }

  if (nfields == 0)
	return;

  SafeDelete(hw);
  SafeDelete(colw);
  SafeDelete(hcolw);

  DelArray(aa);
  DelArray(ta);
}


void TableDialog::DoObserve(int id, int part, void *data, Object *o)
{
  if (id == cEDITDONE) {
    TupleEditDialog *ed= (TupleEditDialog*) o;

    q->LooseFree();

    if (ed->DoEditQ()) {
	q->SetObs();
	if (!q->IsOpen())
	  q->ShowIt();
	else
	  q->GetWindow()->Top();
    }

    if (IsLoose()) {
      if (! ed->Cancelled() && !ed->DoEditQ())
	q->FetchQuery();
      q->LooseAdd(ed);
      delete this;
    } else {
      if (! ed->Cancelled() && EditRefresh()) {
	  force_fetch= TRUE;
	  DoSetup();
      }
      if (!edit_active->RemovePtr(ed))
	abort();
      edit_done->Add(ed);
    }
  } else if (id == cEDITNEXT) {
    if (EditRefresh()) {
      force_fetch= TRUE;
      q->SetInvalRect(* (Rectangle*) data);
      DoSetup();
    }
  }
}


#ifdef ET25
#else
void TableDialog::DoSetupMenu(Menu *m)
{
  DialogView::DoSetupMenu(m);
  m->DisableItem(cCUT);
}
#endif

class TableView: public CollectionView {
public:
    MetaDef(TableView);
    
    TableView(EvtHandler *e, class SeqCollection *col,
	CollViewOptions o= eCVDefault, int r= 0, int c= 1): (e, col, o, r, c)
		{}

#ifdef ET25
#else
    void SelectionToClipboard(char * type, ostream &os);
    Command *DoMenuCommand(int cmd);
#endif
};

MetaImpl0(TableView);


#ifdef ET25
#else
void TableView::SelectionToClipboard(char * type, ostream &os)
{
    Rectangle r= GetSelection();
    VObject *vo= GetItem(r.origin.x, r.origin.y);

    if (vo) {
	CheapText *t= new CheapText(vo->AsString());
    	
#ifdef ET23
    	if (type == cDocTypeET) {
#else
    	if (strcmp(type, cDocTypeET) == 0) {
#endif
	    os << t SP;
#ifdef ET23
	} else if (type == cDocTypeAscii) {
#else
	} else if (strcmp(type, cDocTypeAscii) == 0) {
#endif
	    t->PrintOnAsPureText(os);
	}
	delete t;
    }
}
#endif


#ifdef ET25
#else
Command *TableView::DoMenuCommand(int cmd)
{
  switch(cmd) {
    case cCOPY:
#ifdef ET23
	gClipBoard->SetType(cDocTypeET);
#else
	gClipBoard->SetType((char*) cDocTypeET);
#endif
	View::DoMenuCommand(cmd);
	return gNoChanges;
    default:
	return View::DoMenuCommand(cmd);
    }
    return gNoChanges;
}
#endif


static Font	*HeaderFont;

const int	MaxRow= 25;


VObject *TableDialog::DoCreateDialog()
{
  if (!HeaderFont)
    HeaderFont= new_Font(eFontChicago, 12, eFaceBold);

  if (nfields == 0) {	// Query Error
      return new Cluster(cIdNone, eVObjVCenter, 5,
		new ActionButton(cIdOk, "Ok", TRUE),
		// new ActionButton(cIdCancel, "Cancel"),
		0);
  }

  table_view= new TableView(this, table_col, eCVGrid,
	0, nfields);

  table_view->SetMinExtent(Point(
	cTableMinWidth/nfields < cColMinWidth ? cColMinWidth: cTableMinWidth/nfields, 0));
  table_view->SetId(cIdTable);
#ifdef ET25
#else
  table_view->SetContainer(this);
#endif

  for (int i= 0; i < nfields; i++) {
    VObject	*no;
    header_col->Add(no= new AdjTextItem(aa[i], HeaderFont));
    Point ext= no->GetMinSize().extent;
    hw[i]= ext.x;
    hcolw[i]= strlen(aa[i]);
    item_h= ext.y;
  }
  
#if 0
  vnr= q->NrTuples() > MaxRow || q->NrTuples() == 0 ?
	MaxRow: q->NrTuples();
#else
  vnr= MaxRow;
#endif

#ifdef ET25
  table_scroller= new LinkedScroller(0,
#else
  table_scroller= new LinkedSplitter(0,
#endif
    table_view, Point(cTableMinWidth+nfields-1, (vnr * (item_h+1))-1), cIdTable);

  header_view= new CollectionView(this, header_col,
#ifdef ET25
	eCVGrid, 1, nfields);
#else
	CollViewOptions(eCVGrid | eCVClearSelection), 1, nfields);
#endif
  header_view->SetMinExtent(Point(
	cTableMinWidth/nfields < cColMinWidth ? cColMinWidth: cTableMinWidth/nfields, 0));
#ifdef ET25
#else
  header_view->SetContainer(this);
#endif

  VObject *v=
    new Cluster(cIdNone, eVObjHLeft, 10,
#ifdef ET25
      table_scroller->SetLink(
	new Clipper(header_view, Point(cTableMinWidth+nfields-1, item_h))),
#else
      new BorderItem(table_scroller->SetLink(
	new Clipper(header_view, Point(cTableMinWidth+nfields-1, item_h))), gPoint0),
#endif
      table_scroller,
      new Cluster(cIdNone, eVObjVCenter, 10,
		new ActionButton(cIdOk, "Ok", TRUE),
		// new ActionButton(cIdCancel, "Cancel"),
		new ActionButton(cIdRefresh, "Refresh"),
		new ActionButton(cIdNew, "Add Row..."),
		new ActionButton(cIdEditTuple, "Edit Row..."),
#ifdef ET25
		edit_refresh= new ToggleButton(cIdEditRefresh,
			"Refresh after Edits", TRUE),
#else
		new LabeledButton(cIdEditRefresh,
		  edit_refresh= new ToggleButton(cIdNone, TRUE),
		  new TextItem("Refresh after Edits")),
#endif
		new ActionButton(cIdPrintTable, "Print"),
		0),
      0);

  return v;
}


bool TableDialog::EditRefresh()
{
  return edit_refresh == 0 || edit_refresh->GetValue();
}


bool TableDialog::AddTuple(char **a)
{
  if (nfields == 0)
	return FALSE;

  if (first_tuple) {

    skipping= FALSE;
    table_col->FreeAll();

    c= new OrdCollection*[nfields];
    for (int i= 0; i < nfields; i++)
	c[i]= new OrdCollection;

    w= new int[nfields];
    tcnt= 0;

    for (i= 0; i < nfields; i++) {
	w[i]= hw[i];
	colw[i]= hcolw[i];
    }
  }

  /*
   * Fill the table ColView and adjust the header ColView so that
   * the header textitems have the correct width:
   */

  if (a) {
    if (!skipping) {
	for (int i= 0; i < nfields; i++) {
	  int  al= a[i] ? strlen(a[i]): 0;
	  bool li= al >= AtCutLen;
	  char ds[AtCutLen+1];
	  if (li)
	    AddDots(a[i], ds);

	  AdjTextItem *nt= new AdjTextItem(!li ? a[i]: ds);
	  if (li)
		nt->SetOrgStr(a[i]);
	  int sw= a[i] ? al: 0;
	  if (sw > colw[i])
	  	colw[i]= sw;
	  if (first_tuple && nt->GetMinSize().extent.x < hw[i])
		nt->SetMinWidth(hw[i]);
	  c[i]->Add(nt);
	  int ntw= nt->GetMinSize().extent.x;
	  if (ntw > w[i])
		w[i]= ntw;
	}
	first_tuple= FALSE;

	if (++tcnt == MaxTuples) {
	  if ((IsOpen() || force_fetch) /* && 0 ET bug, don't show alert yet! */)
	    ShowAlert(eAlertNote,
	      "More than %d tuples!\nI'll display the first %d...",
	      MaxTuples, MaxTuples);
	  skipping= TRUE;
	}
    }

  } else {

    first_tuple= TRUE;

    for (int i= 0; i < nfields; i++)
	((AdjTextItem*)header_col->At(i))->SetMinWidth(w[i]);
    header_view->Modified();
#ifdef ET25
    header_view->ClearSelection();
#else
    header_view->SetNoSelection();
#endif
    delete w;

    OrdCollection *new_col= new OrdCollection;
    for (i= 0; i < nfields; i++)
	new_col->AddAll(c[i]);
    table_view->SetCollection(new_col);
    table_col= new_col;

    cursel= -1;
    DisableItem(cIdEditTuple);
    // table_view->Modified();
    // table_view->SetNoSelection();

#if 0
    if (tcnt < vnr) {
      fprintf(stderr, "Resizing\n");
      table_scroller->SetContentRect(
        Point(cTableMinWidth+nfields-1, (tcnt * (item_h+1))-1));
    }
#endif

    for (i= 0; i < nfields; i++)
	delete c[i];
    delete c;
  }
  return !skipping;
}


void TableDialog::DoSetup()
{
  if (force_fetch) {
    force_fetch= FALSE;
    q->FetchQuery();
  }
}


bool TableDialog::EditShape(long oid, bool *edit_q)
{
  int	nf= nfields;
  int	tcnt= 0;
  char	**a;
  char	wh[1000];

  DynInfo *di= GetQD()->GetDyn();
  sprintf(wh, "%s.%s = \"%d\"::oid", name,
	di && di->TheOid() ? di->TheOid(): "oid",
	oid);
  SetDb(q->DataBaseName());
  QueryIter *qi= new QueryIter(name, 1, wh);
  qi->SetFields(TRUE, q->GetAttr(TRUE));

  while (a= qi->Next()) {
	for (int i= 0; i < nf; i++) {
	  AdjTextItem *nt= new AdjTextItem(a[i]);
	  table_col->Add(nt);
	}
	tcnt++;
  }
  delete qi;

  if (tcnt == 0) {
    ShowAlert(eAlertNote,
      "The tuple with OID @B%d@P does no longer exist", oid);
    *edit_q= FALSE;
    return FALSE;
  }
  
  TupleEditDialog *ed= ShowEdit(0, TRUE, FALSE);
  ed->AddObserver(this);
  q->ActiveAdd(ed);
  *edit_q= FALSE;
  return TRUE;
}


void TableDialog::ShowAddTuple()
{
  TupleEditDialog *ed= new TupleEditDialog(this, TRUE, FALSE, (byte*) name,
		nfields, aa, ta, 0, FALSE);
  // edit_active->Add(ed);
  ed->ShowIt();
  ed->AddObserver(this);
  q->ActiveAdd(ed);
}


TupleEditDialog *TableDialog::ShowEdit(int v, bool edit_q, bool block)
{
    char	**contents= new char*[nfields+1];
    int		nrows= table_view ?
			table_view->GetCollection()->Size() / nfields: 1;
    int		row= int(v) % nrows;

    for (int i= 0; i < nfields; i++) {
	AdjTextItem *ai= (AdjTextItem*) table_col->At(row+i*nrows);
	contents[i]= ai->GetOrgStr() ? ai->GetOrgStr(): ai->AsString();
    }
    contents[i]= 0;
    TupleEditDialog *ed= new TupleEditDialog(this, FALSE, edit_q, (byte*) name,
	nfields, aa, ta, contents, block);
    ed->ShowIt();
    delete contents;

    return ed;
}

void TableDialog::AscPrint()
{
  int		ir, ic;
  extern char	*GEOPRINT;
  char		*prname= GEOPRINT;
  FILE		*pf;
  
  if (!prname)
    prname= "lpr";
    
  pf= popen(prname, "w");
  if (!pf) {
  	fprintf(stderr, "Cannot print with '%s'\n", prname);
  	perror(prname);
  	return;
  }
  
  int totw= 0;
  for (ic= 0; ic < nfields; ic++) {
      char	*s= ((VObject*) header_col->At(ic))->AsString();
      fprintf(pf, "| %-*s ", colw[ic], s);
      totw+= colw[ic] + 3;
  }
  fprintf(pf, "|\n");
  for (ic= 0, totw++; ic < totw; ic++)
    fprintf(pf, "=");
  fprintf(pf, "\n");
    
  for (ir= 0; ir < tcnt; ir++) {
    for (ic= 0; ic < nfields; ic++) {
      int	oi= ic * tcnt + ir;
      char	*s= ((VObject*) table_col->At(oi))->AsString();
      fprintf(pf, "| %-*s ", colw[ic], s);
    }
    fprintf(pf, "|\n");
  }
  pclose(pf);
}


void TableDialog::Control(int id, int p, void *v)
{
  edit_done->FreeAll();

  switch (id) {
  case cIdCancel:
  case cIdOk:
	Dialog::Control(id, p, v);
	return;
  case cIdRefresh:
	force_fetch= TRUE;
	DoSetup();
	break;
  case cIdPrintTable:
  	AscPrint();
  	break;
  case cIdNew:
	TupleEditDialog *ed= new TupleEditDialog(this, TRUE, FALSE, (byte*) name,
		nfields, aa, ta, 0, FALSE);
	edit_active->Add(ed);
	ed->ShowIt();
	ed->AddObserver(this);
	return;
  case cIdEditTuple:
  	if (cursel >= 0) {
  		v= (void*) cursel;
  		p= cPartCollDoubleSelect;
  		// fall through
  	} else
  		break;
  case cIdTable:
	switch (p) {
	case cPartCollSelect:
	    cursel= (int) v;
	    EnableItem(cIdEditTuple);
	    break;
	case cPartCollDoubleSelect:
	    cursel= -1;
	    DisableItem(cIdEditTuple);
#ifdef ET25
	    table_view->ClearSelection();
#else
	    table_view->SetNoSelection();
#endif

	    TupleEditDialog *ed= ShowEdit((int)v, FALSE, FALSE);
	    edit_active->Add(ed);
	    ed->AddObserver(this);
#if 0
	    if (! ed->Cancelled()) {
	      force_fetch= TRUE;
	      DoSetup();
	      // q->UpdatePreview(TRUE);
	    }
	    delete ed;
	    cursel= -1;
	    DisableItem(cIdEditTuple);
	    table_view->SetNoSelection();
#endif
	}
	return;
  default:
	Dialog::Control(id, p, v);
  }
}
