/*

    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, "@(#)Composer.c	3.14 7/28/92");

extern "C" {
#include	<ctype.h>
}

#include	"Class.h"
#include	"Alert.h"
#include	"Query.h"
#include	"Window.h"
#include	"Scroller.h"
#include	"Splitter.h"
#include	"Expander.h"
#include	"Menu.h"
#include	"PopupItem.h"
#include	"BackgroundItem.h"
#include	"Filler.h"
#ifdef ET25
#include	"Fields.h"
#include	"Look.h"
#include	"Stream.h"
#else
#include	"EditTextItem.h"
#endif
#include	"ImageItem.h"
#include	"CheapText.h"
#include	"OneOfCluster.h"
#include	"Buttons.h"
#include	"String.h"
#include	"OrdColl.h"

#include	"Composer.h"
#include	"QuelInfo.h"
#include	"QueryDialog.h"
#include	"DrawDocument.h"
#include	"OverView.h"


extern bool	GEOSIMPLE;

const int	cIdSpawn=	cIdFirstUser + 0,
		cIdEdit=	cIdFirstUser + 2,
		cIdDelete=	cIdFirstUser + 3,
		cIdCollapse=	cIdFirstUser + 4,
		cIdPromote=	cIdFirstUser + 5,
		cIdUndo=	cIdFirstUser + 6,
		cIdDeleteTree=	cIdFirstUser + 7,
		cIdAnd=		cIdFirstUser + 10,
		cIdOr=		cIdFirstUser + 11,
		cIdNot=		cIdFirstUser + 12,
		cIdReplAndOr=	cIdFirstUser + 13,
		// cIdReplOr=	cIdFirstUser + 14,
		cIdProc=	cIdFirstUser + 20,
		cIdOperator=	cIdFirstUser + 21,
		cIdConst=	cIdFirstUser + 22,
		cIdTAttr=	cIdFirstUser + 23,
		cIdAttr=	cIdFirstUser + 24,
		cIdPredef=	cIdFirstUser + 25,
		cIdViewRect=	cIdFirstUser + 26,
		cIdQItem=	cIdFirstUser + 100,
		cIdLast=	cIdFirstUser + 1000;


enum QNmode {
	eNot,
	eAnd,
	eOr,
	eProc,
	eBinOpr,
	eConst,
	eAttr,
	eLeaf,
	eUnLOpr,
	eUnROpr,
	eUnknownOpr,
	eEdit,
	eViewRect
};


class CommandView: public DialogView {
  class ComposerView	*treeView;
  VObject		*cl;
  class PopupShadow	*opr_but,
			*attr_but,
			*tattr_but;
  ActionButton		*const_but,
			*edit_but,
			*deltree_but,
			*del_but,
			*and_but,
			*or_but,
			*not_but,
			*replace_but;
  
public:
  CommandView(ComposerView *tv): (0) { treeView= tv; };
  ~CommandView();

  VObject *DoCreateDialog();
  void Control(int id, int part, void *v);

  void CopyState();
};


class PredefDialog: public Dialog {
#ifdef ET25
  TextField	*name;
#else
  EditTextItem	*name;
#endif
  int		type;
  QNode		*tree;
public:
  MetaDef(PredefDialog);

  PredefDialog(int t, QNode *tr);

  VObject *DoCreateDialog();
  void Control(int id, int part, void *v);
};


class ConstantDialog: public Dialog {
#ifdef ET25
  TextField	*val;
#else
  EditTextItem	*val;
#endif
  int		type;
  bool		cancelled;
  ComposerView	*cv;

public:
  MetaDef(ConstantDialog);

  ConstantDialog(ComposerView *, int type);
  ~ConstantDialog();

  VObject *DoCreateDialog();
  void Control(int id, int part, void *v);

  char *GetWhere();
  char *GetLabel();
  bool IsCancelled() { return cancelled; }
  bool IsValid() { return !cancelled; }

#ifdef ET25
  IStream &Load(IStream &);
  OStream &Save(OStream &);
#else
  istream &Load(istream &);
  ostream &Save(ostream &);
#endif
};

class AttrDialog: public Dialog {
  int	nfields;
  char	**attr;

  Menu		*am, *om;
#ifdef ET25
  PopupButton	*ap, *op;
#else
  PopupItem	*ap, *op;
#endif
#ifdef ET25
  TextField	*val;
#else
  EditTextItem	*val;
#endif
  BorderItem	*valbd;
  char		*table_name;
  char		**attr_types;
  bool		cancelled;
  ComposerView	*cv;

  int		oldai, oldoi;
  char		*oldstr;
  bool		has_saved;
public:
  MetaDef(AttrDialog);

  AttrDialog(ComposerView *, char *tn, int, char **, char **);
  ~AttrDialog();

  VObject *DoCreateDialog();
  void DoSave();
  void DoRestore();
  void Control(int id, int part, void *v);

  char *GetWhere();
  char *GetLabel();
  bool IsValid();

#ifdef ET25
  IStream &Load(IStream &);
  OStream &Save(OStream &);
#else
  istream &Load(istream &);
  ostream &Save(ostream &);
#endif
};


#ifdef EXP
bool	EXP_MANUAL;
#endif

#ifdef ET25
#else
class DoneDialogView: public DialogView {
  class ComposerView	*treeView;
  VObject		*t1, *t2, *tindent;
  ToggleButton		*q_active,
			*man_edit;
  
public:
  DoneDialogView(ComposerView *tv): (0) { treeView= tv; };

  VObject *DoCreateDialog();
  void Control(int id, int p, void *v);
  void DoSetup();

#ifdef EXP
  bool IsActive() { return TRUE; }
  void SetActive(bool b) {}

  bool GetManEdit() { return EXP_MANUAL; }
  void SetManEdit(bool b) {}
#else
  bool IsActive() { return q_active->GetValue(); }
  void SetActive(bool b) { q_active->SetState(b); }

  // GetManEdit() is called before DoCreateDialog, so man_edit may be nil:
  bool GetManEdit() { return man_edit ? man_edit->GetValue(): FALSE; }
  void SetManEdit(bool b) { man_edit->SetState(b); }
#endif
};
#endif


typedef	class QNode	&QNoderef;

class QNode: public Object {
  char			*name;
  QNmode		mode;
  OrdCollection 	*c;
  QNode			*parent;
  ComposerView		*cv;
  ConstantDialog	*cd;
  class AttrDialog	*ad;
  char			*oprcode;
  int			first_arg_type;
  int			pgtype;

  void AddParent(QNode *parent);
public:
  MetaDef(QNode);

  QNode(ComposerView *c, char *n= 0, QNode *parent= 0, QNmode= eLeaf);
  ~QNode();

  QNoderef operator= (QNode &qn);

  char *GetName() { return name; };
  char *SetName(char *n) { SafeDelete(name); name= strsave(n); };
  void SetType(int t, bool set_name= TRUE);
  int GetType() { return pgtype; }
  void SetFirstArgType(int t) { first_arg_type= t; }
  int GetFirstArgType() { return first_arg_type ? first_arg_type: BOOL_TYPE; }

  AttrDialog *GetAd() { return ad; }
  void SetAd(AttrDialog *d) { ad= d; }
  void ClearAd() { SafeDelete(ad); }
  ConstantDialog *GetCd() { return cd; }
  void SetCd(ConstantDialog *d) { cd= d; mode= eConst; }

  bool Delete(bool del_tree= FALSE, bool dont_save= FALSE);
  void DeleteTree();
  void Clear();

  void AddNot();
  void AddAnd(bool replace= FALSE);
  void AddOr(bool replace= FALSE);
  void AddChild(QNode *child) { c->Add(child); child->parent= this; }
  void AddUnOpr();

  void AddOperator(int);

  char *GetOprStr();
  char *GetOprCode() { return oprcode; }
  void SetOprCode(char *c) { SafeDelete(oprcode); oprcode= strsave(c); }

  OrdCollection *GetChilds() { return c; }
  int		NrChilds() { return c->Size(); }
  QNode		*GetParent() { return parent; }
  QNode		*GetChild(int nr= 0) {
	return c && c->Size() > nr ? (QNode*) c->At(nr): 0; }
  void		SetParent(QNode *qp) { parent= qp; }
  QNmode	GetMode() { return mode; }
  void		SetMode(QNmode m) { mode= m; }
  bool		IsValid();
  bool		IsEmpty() { return !cd && !ad && c->Size() == 0; }

#ifdef ET25
  OStream &PrintOn(OStream &os);
  IStream &ReadFrom(IStream &is);
#else
  ostream &PrintOn(ostream &os);
  istream &ReadFrom(istream &is);
#endif
};

typedef QNode	*QP;


class QItem: public TextItem {
  QNode	*n;
public:
  MetaDef(QItem);

  QItem(char *s, QNode *qn): (cIdQItem, s) { n= qn; };
  QNode *GetNode() { return n; }
};

typedef QItem	*IP;

MetaImpl0(QItem);


#ifdef ET25
OStream & SaveETI(OStream &os, TextField *eti)
{
  return os.PrintString(eti->AsString()) NL;
}

IStream & LoadETI(IStream &is, TextField *eti)
{
  char *s;
  is.ReadString(&s);
  eti->SetString(s);
  delete s;
  return is;
}
#else
ostream & SaveETI(ostream &os, EditTextItem *eti)
{
  return PrintString(os, eti->GetText()->AsString()) NL;
}

istream & LoadETI(istream &is, EditTextItem *eti)
{
  char *s;
  ReadString(is, &s);
  eti->SetString(s);
  delete s;
  return is;
}
#endif


MetaImpl(QNode,(
  TP(name),TE(mode),TP(c),TP(parent),TP(cv),TP(cd),TP(ad),T(pgtype),
  0)
);


QNode::QNode(ComposerView *v, char *n, QNode *p, QNmode m)
{
  cv= v;
  if (n) {
    mode= m;
    name= strsave(n);
    SetType(BOOL_TYPE, FALSE);
  } else
    Clear();

  c= new OrdCollection;
  parent= p;
}


QNode::~QNode()
{
  SafeDelete(ad);
  SafeDelete(cd);
  SafeDelete(oprcode);
  delete name;
  c->FreeAll();
  delete c;
}


QNoderef QNode::operator= (QNode &qn)
{
  if (!Delete(TRUE)) {
    delete &qn;
    return *this;
  }

  SetName(qn.GetName());
  mode= qn.mode;
  c->AddAll(qn.c);
  qn.c->Empty();

  Iter	next(c);
  QP	p;
  while (p= (QP) next())
	p->parent= this;

  cd= qn.cd; qn.cd= 0;
  ad= qn.ad; qn.ad= 0;
  pgtype= qn.pgtype;

  delete &qn;

  return *this;
}


static ComposerView	*gActiveCV;
static bool		gSaveAD;

#ifdef ET25
OStream &QNode::PrintOn(OStream &os)
#else
ostream &QNode::PrintOn(ostream &os)
#endif
{
  bool has_cd;
  bool has_ad= FALSE;
  char *sname= name;
  QNmode smode= mode;
  char *typstr= GetInfo(cv->GetDbName())->GetTypeStr(pgtype);

  has_cd= (mode == eConst && cd);
  if (mode == eEdit && ad) {
    if (gSaveAD)
	has_ad= TRUE;
    else {
        sname= typstr;
	smode= eLeaf;
    }
  }
  
  Object::PrintOn(os);
#ifdef ET25
  os.PrintString(sname);
#else
  PrintString(os, sname);
#endif
  os NL;

  os << smode SP << c NL << parent SP;
#ifdef ET25
  os.PrintString(typstr ? typstr: "???");
#else
  PrintString(os, typstr ? typstr: "???");
#endif
  os NL;
  os << has_cd SP << has_ad NL;

  if (mode == eUnLOpr || mode == eUnROpr) {
#ifdef ET25
    os.PrintString(oprcode);
#else
    PrintString(os, oprcode);
#endif
    os NL;
  }

  if (has_cd)
    cd->Save(os);
  if (has_ad)
    ad->Save(os);

  return os;
}


#ifdef ET25
IStream &QNode::ReadFrom(IStream &is)
#else
istream &QNode::ReadFrom(istream &is)
#endif
{
  bool has_cd;
  bool has_ad;

  Object::ReadFrom(is);
  cv= gActiveCV;
#ifdef ET25
  is.ReadString(&name);
#else
  ReadString(is, &name);
#endif

  is >> Enum(mode) >> c >> parent;
  if (geo_version > 134 || gSaveAD) {
    char *typstr;
#ifdef ET25
    is.ReadString(&typstr);
#else
    ReadString(is, &typstr);
#endif
    pgtype= GetInfo(cv->GetDbName())->GetTypeOid(typstr);
    SafeDelete(typstr);
  } else
    is >> pgtype;

  is >> Bool(has_cd) >> Bool(has_ad);
  if (c->Size() >= 1)
    first_arg_type= QP(c->At(0))->GetType();

  if (mode == eUnLOpr || mode == eUnROpr)
#ifdef ET25
    is.ReadString(&oprcode);
#else
    ReadString(is, &oprcode);
#endif

  if (has_cd) {
    cd= new ConstantDialog(cv, pgtype);
    cd->Load(is);
  }
  if (has_ad) {
    ad= new AttrDialog(cv, cv->GetTableName(), cv->GetNfields(),
	cv->GetAttr(), cv->GetAttrTypes());
    ad->Load(is);
    mode= eEdit; // Old save files had no eEdit
  }
    
  return is;
}

void QNode::Clear()
{
  SafeDelete(ad);
  SafeDelete(cd);
  mode= eLeaf;
  // SetName("New");
  // pgtype= BOOL_TYPE;
  SetType(BOOL_TYPE);
}


bool QNode::IsValid()
{
  switch (GetMode()) {
	case eConst:
	    if (cd)
		return cd->IsValid();
	    break;
	case eEdit:
	    if (ad)
		return ad->IsValid();
	    break;
	case eAttr:
	case eViewRect:
	    return TRUE;
	default:
	    ;
  }
  return FALSE;
}


void QNode::SetType(int t, bool set_name)
{
  pgtype= t;
  if (set_name) {
    SafeDelete(name);
    name= strsave(GetInfo(cv->GetDbName())->GetTypeStr(t));
  }
};


void QNode::AddParent(QNode *p)
{
  p->c->Add(this);
  if (parent) {
#ifdef ET25
    parent->c->AddBefore(p, this);
#else
    parent->c->InsertBefore(this, p);
#endif
    parent->c->Remove(this);
  } else {
    cv->SetQTree(p);
  }
  parent= p;
  cv->NewQTree();
}

void QNode::DeleteTree()
{
  SafeDelete(ad);
  SafeDelete(cd);
  c->FreeAll();
}

bool QNode::Delete(bool del_tree, bool dont_save)
{
  bool	del_node= FALSE;

  if (!del_tree)
    del_node= (c->Size() == 1 && GetMode() != eUnknownOpr)
      || (c->Size() == 2 && (QP(c->At(0))->IsEmpty() ^ QP(c->At(1))->IsEmpty()));

  if (!del_node && c->Size() > 0 && ShowAlert(eAlertCaution,
    "Do you really want to delete\nthis Query Tree ?") != cIdYes)
	return FALSE;

  if (!dont_save)
    cv->SaveOldTree();
  if (del_node) {
    QNode *cp= c->Size() == 2
      && QP(c->At(0))->IsEmpty() ? QP(c->At(1)): QP(c->At(0));

    if (parent) {
#ifdef ET25
      parent->c->AddBefore(cp, this);
#else
      parent->c->InsertBefore(this, cp);
#endif
      parent->c->Remove(this);
    } else {
      cv->SetQTree(cp);
    }
    cp->parent= parent;
    c->Remove(cp);	// Needed cause 'delete this' does c->FreeAll()
  } else {
    int oldtype= GetType();
    DeleteTree();
    Clear();
    if (parent)
      SetType(oldtype);
  }

  //cv->NewQTree();

  if (del_node)
    delete this;

  return TRUE;
}


char * QNode::GetOprStr()
{
  return GetName();
}


void QNode::AddOperator(int i)
{
  OprInfo	*oi= GetInfo(cv->GetDbName())->GetOprInfo(i);
  int		nchilds;
  bool		do_replace= FALSE;
  bool		saved= FALSE;
  char		kind= oi->GetKind();

  if (GetMode() == eUnknownOpr) {
    cv->SaveOldTree();
    saved= TRUE;
    if (oi->GetNrArgs() == 2) {
      QNode *n= new QNode(cv);
      n->SetType(oi->GetArgR());
      SetFirstArgType(oi->GetArgR());
      AddChild(n);
      mode= eBinOpr;
    } else if (kind == 'l') {
      mode= eUnLOpr;
      SetOprCode(oi->Oprcode());
      SetFirstArgType(oi->GetArgR());
    } else {
      mode= eUnROpr;
      SetOprCode(oi->Oprcode());
      SetFirstArgType(oi->GetArgL());
    }
    if (oi->GetRetType() != GetType()) {
      SetType(oi->GetRetType());
      AddUnOpr();
    }
  }

  // First we check for a possible replace (new operator matches
  // returntype and arguments of old operator)

  if ((nchilds= NrChilds()) == oi->GetNrArgs()) {
    Iter	next(c);
    QNode	*qn;

    for (int i= 0; qn= (QNode*) next(); i++) {
      if (qn->GetType() !=
      ((i == 0 && kind != 'l') ? oi->GetArgL(): oi->GetArgR()))
	  break;
    }
    if (i == nchilds) {	// Replace the selected operator
      do_replace= TRUE;
    }
  }

  if (!do_replace) {
    // Delete tree and insert new operator

    if (!Delete(TRUE, saved))
      return;

    if (kind == 'b') {
      mode= eBinOpr;
      QNode *n1= new QNode(cv, "", this, eLeaf);
      n1->SetType(oi->GetArgL());
      c->Add(n1);
      SetFirstArgType(oi->GetArgL());
    } else if (kind == 'l') {
      mode= eUnLOpr;
      SetOprCode(oi->Oprcode());
      SetFirstArgType(oi->GetArgR());
    } else {
      mode= eUnROpr;
      SetOprCode(oi->Oprcode());
      SetFirstArgType(oi->GetArgL());
    }

    QNode *n2= new QNode(cv, "", this, eLeaf);
    n2->SetType(kind == 'r' ? oi->GetArgL(): oi->GetArgR());
    c->Add(n2);

  } else if (!saved)
    cv->SaveOldTree();

  SetName(oi->Str());
  if (do_replace && kind != 'b')
    SetOprCode(oi->Oprcode());
  cv->NewQTree();
}


void QNode::AddAnd(bool replace)
{
  cv->SaveOldTree();
  if (replace) {
    SetMode(eAnd);
    SetName("AND");
    cv->NewQTree();
  } else {
    QNode *n= new QNode(cv, "AND", parent, eAnd);

    n->c->Add(new QNode(cv, 0, n));
    AddParent(n);
  }
}

void QNode::AddOr(bool replace)
{
  cv->SaveOldTree();
  if (replace) {
    SetMode(eOr);
    SetName("OR");
    cv->NewQTree();
  } else {
    QNode *n= new QNode(cv, "OR", parent, eOr);

    n->c->Add(new QNode(cv, 0, n));
    AddParent(n);
  }
}

void QNode::AddNot()
{
  cv->SaveOldTree();

  QNode *n= new QNode(cv, "NOT", parent, eNot);
  AddParent(n);
}



const int		cIdPDName=	cIdFirstUser + 0;

MetaImpl0(PredefDialog);

PredefDialog::PredefDialog(int t, QNode *tr):
#ifdef ET25
  ("Save (sub)tree")
#else
  ("Save (sub)tree", eBWinBlock)
#endif
{
  type= t;
  tree= tr;
}


VObject *PredefDialog::DoCreateDialog()
{
  BorderItem *namebd= new BorderItem("Name of (sub)tree",
#ifdef ET25
	name= new TextField(cIdPDName, 25));
#else
	name= new EditTextItem(cIdPDName, "", 250));
#endif

  return
  new Cluster(cIdNone, eVObjHLeft, 5,
    new Cluster(cIdNone, eVObjVCenter, 5,
	namebd,
	0),
    new Cluster(cIdNone, eVObjVCenter, Point(10),
	new ActionButton(cIdOk, "Save", TRUE),
	new ActionButton(cIdCancel, "Cancel"),
	0),
    0);
}

void PredefDialog::Control(int id, int part, void *v)
{
    switch (id) {
    case cIdOk:
#ifdef ET25
	char *n= name->AsString();
#else
	char *n= name->GetText()->AsString();
#endif
	if (strlen(n)) {
	  QNode *oldp= tree->GetParent();
	  tree->SetParent(0);
	  GetInfo()->SavePredef(n, type, tree);
	  tree->SetParent(oldp);
	} else {
          ShowAlert(eAlertNote,
	    "You have not entered a name!\nEnter a name or choose `@BCancel@P'");
	  return;
	}
	break;
    }
    Dialog::Control(id, part, v);
}



const int		cIdCVal=	cIdFirstUser + 0;

MetaImpl(ConstantDialog,(TP(val),T(type),T(cancelled),0));

ConstantDialog::ConstantDialog(ComposerView *cview, int t):
#ifdef ET25
  ("Create Constant")
#else
  ("Create Constant", eBWinBlock)
#endif
{
  cv= cview;
  type= t;
}


ConstantDialog::~ConstantDialog()
{};



VObject *ConstantDialog::DoCreateDialog()
{
  BorderItem *valbd= new BorderItem("Value",
#ifdef ET25
	val= new TextField(cIdCVal, 25));
#else
	val= new EditTextItem(cIdCVal, "", 250));
#endif

  return
  new Cluster(cIdNone, eVObjHLeft, 5,
    new Cluster(cIdNone, eVObjVCenter, 5,
	valbd,
	0),
    new Cluster(cIdNone, eVObjVCenter, Point(10),
	new ActionButton(cIdOk, "Done", TRUE),
	new ActionButton(cIdCancel, "Cancel"),
	0),
    0);
}

#ifdef ET25
OStream &ConstantDialog::Save(OStream &os)
#else
ostream &ConstantDialog::Save(ostream &os)
#endif
{
  return SaveETI(os, val);
}

#ifdef ET25
IStream &ConstantDialog::Load(IStream &is)
#else
istream &ConstantDialog::Load(istream &is)
#endif
{
#ifdef ET25
#else
  Update();	// calls CreateDialog
#endif
  return LoadETI(is, val);
}

void ConstantDialog::Control(int id, int part, void *v)
{
    switch (id) {
    case cIdOk:
	if (! GetInfo(cv->GetDbName())->TypeMatchRE(GetLabel(), type)) {
#ifdef EXP
LogExp("Entered incorrect constant: %s\n", GetLabel());
#endif
	  return;
	}
        cancelled= FALSE;
#ifdef EXP
LogExp("Entered constant: %s\n", GetLabel());
#endif
	break;
    case cIdCancel:
        cancelled= TRUE;
#ifdef EXP
LogExp("Cancelled constant: %s\n", GetLabel());
#endif
	break;
    }
    Dialog::Control(id, part, v);
}


static char cwhere[1000];

char *ConstantDialog::GetLabel()
{
#ifdef ET25
  return val->AsString();
#else
  return val->GetText()->AsString();
#endif
}


char *ConstantDialog::GetWhere()
{
  sprintf(cwhere, "\"%s\"::%s",
#ifdef ET25
    val->AsString(),
#else
    val->GetText()->AsString(),
#endif
    GetInfo(cv->GetDbName())->GetTypeStr(type));
  return cwhere;
}


const int	cIdADAttr=	cIdFirstUser + 0,
		cIdVal=		cIdFirstUser + 2,
		cIdOp=		cIdFirstUser + 10,
		cIdNE=		cIdFirstUser + 10,
		cIdLT=		cIdFirstUser + 11,
		cIdLE=		cIdFirstUser + 12,
		cIdEQ=		cIdFirstUser + 13,
		cIdGE=		cIdFirstUser + 14,
		cIdGT=		cIdFirstUser + 15,
		cIdLast2=	cIdFirstUser + 1000;

MetaImpl(AttrDialog, (TP(am),TP(om),TP(ap),TP(op),TP(val),
TP(valbd),TP(table_name),TVP(attr_types,nfields),
T(cancelled),0));

AttrDialog::AttrDialog(ComposerView *c, char *tn, int nf, char **a, char **at):
#ifdef ET25
  ("Attribute Sub Query Composer")
#else
  ("Attribute Sub Query Composer", eBWinBlock)
#endif
{
  cv= c;
  nfields= nf;
  attr= a;
  table_name= tn;
  attr_types= at;
}

AttrDialog::~AttrDialog()
{
  SafeDelete(oldstr);
};


struct OpPair {
	char *s; int c;
} ops[]= {
    "!=", cIdNE,
    "<", cIdLT,
    "<=", cIdLE,
    "=", cIdEQ,
    ">=", cIdGE,
    ">", cIdGT
};

VObject *AttrDialog::DoCreateDialog()
{
  am= new Menu("Attributes:", TRUE);
  for (int i= 0; i < nfields; i++)
	am->AppendItem(Expand(attr[i]), i);

  om= new Menu("Operators:", TRUE);
  for (i= 0; i < sizeof(ops)/sizeof(OpPair); i++)
	om->AppendItem(Expand(ops[i].s), ops[i].c);

#ifdef ET25
  ap= new PopupButton(cIdADAttr, 0, am);
  op= new PopupButton(cIdOp, cIdEQ, om);
#else
  ap= new PopupItem(cIdADAttr, 0, "Attribute:", am);
  op= new PopupItem(cIdOp, cIdEQ, "Operator:", om);
#endif

  valbd= new BorderItem("Value",
#ifdef ET25
	val= new TextField(cIdVal, 25));
#else
	val= new EditTextItem(cIdVal, "", 250));
#endif

  VObject *vcl= new Cluster(cIdNone, eVObjHLeft, 5,
    new Cluster(cIdNone, eVObjVCenter, 5,
#ifdef ET25
	new TextItem("Attribute:"),
	ap,
	new TextItem("Operator:"),
	op,
#else
	ap,
	op,
#endif
	valbd,
	0),
    new Cluster(cIdNone, eVObjVCenter, Point(10),
	new ActionButton(cIdOk, "Done", TRUE),
	new ActionButton(cIdCancel, "Cancel"),
	0),
    0);

#ifdef ET25
#else
  SetKbdFocus(val);
#endif
  return vcl;
}


void AttrDialog::DoSave()
{
#ifdef ET25
  oldai= ap->GetValue();
  oldoi= op->GetValue();
#else
  oldai= ap->GetSelectedItem();
  oldoi= op->GetSelectedItem();
#endif
  SafeDelete(oldstr);
#ifdef ET25
  oldstr= strsave(val->AsString());
#else
  oldstr= strsave(val->GetText()->AsString());
#endif
}

void AttrDialog::DoRestore()
{
#ifdef ET25
  ap->SetValue(oldai);
  op->SetValue(oldoi);
#else
  ap->SetSelectedItem(oldai);
  op->SetSelectedItem(oldoi);
#endif
  val->SetString(oldstr);
}


#ifdef ET25
OStream &AttrDialog::Save(OStream &os)
#else
ostream &AttrDialog::Save(ostream &os)
#endif
{
#ifdef ET25
  os << ap->GetValue() SP << op->GetValue() NL;
#else
  os << ap->GetSelectedItem() SP << op->GetSelectedItem() NL;
#endif
  return SaveETI(os, val);
}

#ifdef ET25
IStream &AttrDialog::Load(IStream &is)
#else
istream &AttrDialog::Load(istream &is)
#endif
{
  int	ai;
  int	oi;

#ifdef ET25
#else
  Update();	// calls CreateDialog
#endif
  is >> ai >> oi;
#ifdef ET25
  ap->SetValue(ai);
  op->SetValue(oi);
#else
  ap->SetSelectedItem(ai);
  op->SetSelectedItem(oi);
#endif
  return LoadETI(is, val);
}


void AttrDialog::Control(int id, int part, void *v)
{
    switch (id) {
    case cIdOk:
#ifdef ET25
	int sel_attr= ap->GetValue();
#else
	int sel_attr= ap->GetSelectedItem();
#endif

	if (! GetInfo(cv->GetDbName())->TypeMatchRE(
#ifdef ET25
	  val->AsString(), attr_types[sel_attr])) {
#else
	  val->GetText()->AsString(), attr_types[sel_attr])) {
#endif
#ifdef EXP
LogExp("Entered incorrect attribute constant: %s\n", val->GetText()->AsString());
#endif
		return;
	}
	cancelled= FALSE;
	has_saved= TRUE;
#ifdef EXP
LogExp("Entered attribute\n");
#endif
	break;
    case cIdCancel:
        cancelled= !has_saved;
#ifdef EXP
LogExp("Cancelled attribute dialog: %s\n", GetLabel());
#endif
	break;
    }
    Dialog::Control(id, part, v);
}


static char * QuelCast(char *type, char *a)
{
  static char s[1000];

  strcpy(s, "\"");
  strcat(s, a);
  strcat(s, "\"::");
  strcat(s, type);
  return s;
}


static char where[1000];

char *AttrDialog::GetLabel()
{
  if (cancelled)
	return "Cancelled";

#ifdef ET25
  int sel_attr= ap->GetValue();
#else
  int sel_attr= ap->GetSelectedItem();
#endif

  sprintf(where, "%s.%s %s %s",
    table_name,
    attr[sel_attr],
#ifdef ET25
    ops[op->GetValue() - cIdOp].s,
    val->AsString()
#else
    ops[op->GetSelectedItem() - cIdOp].s,
    val->GetText()->AsString()
#endif
    );
  return where;
}


char *AttrDialog::GetWhere()
{
#ifdef ET25
  int sel_attr= ap->GetValue();
#else
  int sel_attr= ap->GetSelectedItem();
#endif

  sprintf(where, "(%s.%s %s %s)",
    table_name,
    attr[sel_attr],
#ifdef ET25
    ops[op->GetValue() - cIdOp].s,
    QuelCast(attr_types[sel_attr], val->AsString())
#else
    ops[op->GetSelectedItem() - cIdOp].s,
    QuelCast(attr_types[sel_attr], val->GetText()->AsString())
#endif
    );
  return where;
}

bool AttrDialog::IsValid()
{
  return !cancelled;
}


MetaImpl(ComposerView, (TP(oprmenu),0));

#ifdef ET25
ComposerView::ComposerView(Composer *cmp, QueryDialog *q, char *db, char *tn,
	int nf, char **a, char **at):
	  composer(cmp), (0, eTLLeftRight, eTCDiagonal)
#else
ComposerView::ComposerView(QueryDialog *q, char *db, char *tn,
	int nf, char **a, char **at): (0, eTLLeftRight, eTCDiagonal)
#endif
{
  old_selection= 0;
  qd= q;
  table_name= tn;
  db_name= db;
  nfields= nf;
  attr= a;
  attr_types= at;
  menu= new NoDefMenu("Operations", FALSE,
    GEOBROWSE ? 14: GEOSIMPLE ? 11: 15, 0);
  menu->SetFlag(eMenuNoScroll);
#ifdef ET25
#else
  menu->SetGap(Point(2,2));
#endif
  midmenu= new Menu("Undo", FALSE, 0, 1, FALSE);
  midmenu->AppendItem("Undo last change", cIdUndo);
  qtree= new QNode(this);
  NewQTree();

  // Force CreateMenu cause this takes a while and we don't
  // want the user to wait when he presses the right (menu) button
  // the first time

  Menu *m= GetMenu();
  DoCreateMenu(m);
#ifdef ET25
#else
  m->ResetFlag(eMenuIsNew);
#endif
}


ComposerView::~ComposerView()
{
  menu->FreeAll();
  SafeDelete(menu);
  SafeDelete(qtree);
  SafeDelete(oldqtree);
}


#ifdef ET23
Command *ComposerView::DispatchEvents(Point lp, Token &t, Clipper *vf)
#else
Command *ComposerView::DispatchEvents(Point lp, Token t, Clipper *vf)
#endif
{
    VObject *v= 0;
    if (t.Code == eEvtLeftButton /* && t.Flags == eFlgButDown */ && dialogRoot) {
	Iter	next(((CompositeVObject*) dialogRoot)->GetList());

	while (v= (VObject*) next()) {
	  if (v->ContainsPoint(lp))
	    break;
	}

	if (!v && old_selection) {
	  SetSelection(0);
	  old_selection= 0;
	  CompStatus();
	  cmdv->CopyState();

	  return gNoChanges;
	}
    }

    if (! (t.Code == eEvtMiddleButton || t.Code == eEvtRightButton)
    // && dialogRoot && dialogRoot->ContainsPoint(lp)) {
    && dialogRoot) {
	Command *cmd= dialogRoot->Input(lp, t, vf);

	// Selected other VObject ?
	if (v && old_selection != GetSelection()) {
	    old_selection= GetSelection();
	    CompStatus();
	    cmdv->CopyState();
	}

	return cmd;
    }

    return View::DispatchEvents(lp, t, vf);
}


// Taken from VObject.C but without menu->DisableAll() / DoSetupMenu
// and simplified a little...

Command * ComposerView::DoRightButtonDownCommand(Point lp, Token, int, Clipper *vf)
{
    if (vf) {
	int cmdno;
#ifdef ET25
        if ((cmdno= menu->Pulldown(lp, vf)) >= 0) {     // show menu in window
#else
        if ((cmdno= menu->Show(lp, vf)) >= 0) {     // show menu in window
#endif
            vf->Focus();
            return DoMenuCommand(cmdno);
        }
    }
    return gNoChanges;
}

Command * ComposerView::DoMiddleButtonDownCommand(Point p, Token t, int i)
{
  midmenu->EnableItem(cIdUndo, oldqtree != 0);
#ifdef ET25
  if (midmenu->Pulldown(p, this) == cIdUndo) {
#else
  if (midmenu->Show(p, this) == cIdUndo) {
#endif
	UndoLastChange();
	SetTv();

	CompStatus();
	cmdv->CopyState();
	old_selection= 0;
  }
  return gNoChanges;
}


#ifdef ET25
OStream &Composer::Save(OStream &o)
#else
ostream &ComposerView::Save(ostream &o)
#endif
{
#ifdef ET25
  o << IsActive() NL;
  o << GetManEdit() NL;
  if (GetManEdit())
    o.PrintString(cv->tv->GetText()->AsString());
#else
  o << dv->IsActive() NL;
  o << dv->GetManEdit() NL;
  if (dv->GetManEdit())
    PrintString(o, tv->GetText()->AsString());
#endif

  gSaveAD= TRUE;
#ifdef ET25
  o << cv->qtree NL;
#else
  o << qtree NL;
#endif
  gSaveAD= FALSE;
  return o;
}

#ifdef ET25
IStream &Composer::Load(IStream &i)
#else
istream &ComposerView::Load(istream &i)
#endif
{
#ifdef ET25
  gActiveCV= cv;
#else
  gActiveCV= this;
  Update();
#endif

  bool is_active, is_manual;

  i >> Bool(is_active);
  if (geo_version > 102) {
    i >> Bool(is_manual);
    if (is_manual)
#ifdef ET25
      i.ReadString(&cv->qtext);
#else
      ReadString(i, &qtext);
#endif
  } else
    is_manual= FALSE;

#ifdef ET25
  SetActive(is_active);
  SetManEdit(is_manual);
  SafeDelete(cv->oldqtree);
  SafeDelete(cv->qtree);
  i >> cv->qtree;
  cv->NewQTree();
#else
  dv->Update();  // calls CreateDialog
  dv->SetActive(is_active);
  dv->SetManEdit(is_manual);
  SafeDelete(oldqtree);
  SafeDelete(qtree);
  i >> qtree;
  NewQTree();
#endif

  if (is_manual) {
#ifdef ET25
    cv->tv->SetString(cv->qtext, strlen(cv->qtext));
    delete cv->qtext;
  } else
    cv->SetTv();
#else
    tv->SetString(qtext, strlen(qtext));
    delete qtext;
  } else
    SetTv();
#endif
  SetManEdit(is_manual);

  return i;
}


void ComposerView::NewQTree()
{
  SetSelection(0);
  InstallTree(GetQTree());
}

void ComposerView::UndoLastChange()
{
  QNode *t= oldqtree;
  oldqtree= qtree;
  qtree= t;
  NewQTree();
  SetTv();
}


void ComposerView::SaveOldTree()
{
  SafeDelete(oldqtree);
  gActiveCV= this;
  gSaveAD= TRUE;
  oldqtree= (QNode*) qtree->DeepClone();
  gSaveAD= FALSE;
}

void ComposerView::SetQTree(QNode *n)
{
  qtree= n;
}

Menu *ComposerView::GetMenu()
{
    return menu;
}
 

void ComposerView::DoCreateMenu(Menu *menu)
{
    if (!GEOSIMPLE) {
      menu->AppendItem(Expand("Undo last change",18), cIdUndo);
      menu->AppendItem(Expand("Collapse",18), cIdCollapse);
      menu->AppendItem("-",0);
    }
    menu->AppendItem(Expand("Edit...",18),	cIdEdit);
    menu->AppendItem(Expand("Delete node",18), cIdDelete);
    menu->AppendItem(Expand("Delete tree",18), cIdDeleteTree);
    menu->AppendItem("-",0);
    menu->AppendItem(Expand("Insert AND",18), cIdAnd);
    menu->AppendItem(Expand("Insert OR",18), cIdOr);
    menu->AppendItem(Expand("Insert NOT",18), cIdNot);
    menu->AppendItem("-",0);
    menu->AppendItem(Expand("Replace with AND",18), cIdReplAndOr);
    menu->AppendItem("-",0);
    menu->AppendItem(Expand("Constant...",18), cIdConst);
    if (!GEOBROWSE && !GEOSIMPLE)
      menu->AppendItem(Expand("Viewed Rect",18), cIdViewRect);

    QuelInfo *qi= GetInfo(GetDbName());

    menu->AppendMenu(attrmenu= qi->GetAttrMenu(table_name), cIdAttr);
    menu->AppendMenu(tattrmenu= qi->GetTablesAttrMenu(
	strncmp(table_name, "pg_", 3) != 0), cIdTAttr);
    menu->AppendMenu(predefmenu=
	GetInfo()->GetPredefMenu(), cIdPredef);
    menu->AppendMenu(oprmenu=
	qi->GetOprMenu(oldoprtype= BOOL_TYPE, 0), cIdOperator);
}


TreeNode *ComposerView::FindTreeNode(VObject *vop)
{
    return (TreeNode*)vop->FindNextHandlerOfClass(Meta(TreeNode));
}


void ComposerView::CompStatus()
{
  VObject *sel;

  menu->DisableAll();

  if (oldqtree)
    menu->EnableItem(cIdUndo);

  if (not_all_disabled= bool(sel= GetSelection())) {

    TreeNode	*tn= FindTreeNode(sel);
    QNode	*qn= IP(sel->FindItem(cIdQItem))->GetNode();
    QuelInfo	*qi= GetInfo(GetDbName());
    char	*current;

    if (tn->Collapsed())
	current= "Expand";
    else
	current= "Collapse";
    menu->ReplaceItem(cIdCollapse, current);

    bool unkn_op= FALSE;
    if ((unkn_op= (qn->GetMode() == eUnknownOpr))
    || oldoprtype != qn->GetType()
    || (qn->GetChild() && oldchildtype != qn->GetChild()->GetType())
    || (!qn->GetChild() && oldchildtype)) {

        oldoprtype= qn->GetType();
	MenuItem *m= Guard(menu->RemoveItem(cIdOperator), MenuItem);
	m->FreeAll();
	delete m;

	// int childtype= !unkn_op ? 0: qn->GetChild()->GetType();
	oldchildtype= qn->GetChild() ? qn->GetChild()->GetType(): 0;
        menu->AppendMenu(oprmenu=
	  qi->GetOprMenu(oldoprtype, oldchildtype), cIdOperator);
    }
    GetInfo()->SetPredefMenu(predefmenu, oldoprtype);

    int nv= qi->SetAttrMenu(attrmenu, table_name,
      oldoprtype, qn->GetMode() == eLeaf);
    menu->EnableItem(cIdAttr, attr_state= GEOSIMPLE ? (nv > 0): TRUE);

    qi->SetTablesAttrMenu(tattrmenu, strncmp(table_name, "pg_", 3) != 0,
	oldoprtype, qn->GetMode() == eLeaf);

    if (unkn_op)
      oldoprtype= 0;

    if (qn->NrChilds() == 0) {
      menu->EnableItem(cIdConst);
      menu->EnableItem(cIdViewRect);
      const_state= viewrect_state= TRUE;
    } else {
      const_state= viewrect_state= FALSE;
    }
    menu->EnableItem(cIdPredef);
    predefmenu->EnableItem(cIdSavePredef);

    if (oprmenu->GetCollection()->Size() == 0) {
	  menu->DisableItem(cIdOperator);
	  opr_state= FALSE;
    } else {
	  menu->EnableItem(cIdOperator);
          MenuEnableAll(oprmenu);
	  opr_state= TRUE;
    }

    menu->EnableItems(cIdCollapse, cIdProc, cIdTAttr, 0);
    if (not_state= andor_state= (qn->GetType() == BOOL_TYPE))
      menu->EnableItems(cIdNot, cIdAnd, cIdOr, 0);

    if (qn->GetMode() == eAnd || qn->GetMode() == eOr) {
      if (qn->GetMode() == eAnd)
	replace_str= "Replace with OR";
      else
	replace_str= "Replace with AND";
      menu->ReplaceItem(cIdReplAndOr, replace_str);
      menu->EnableItem(cIdReplAndOr);
    } else
      replace_str= 0;

    QNode *par;
    if ((par= qn->GetParent()) && par->GetMode() == eNot
    || qn->GetMode() == eNot) {
	menu->DisableItem(cIdNot);
	not_state= FALSE;
    }

    if ((qn->GetMode() == eLeaf || qn->GetMode() == eEdit)
    && qn->GetType() == BOOL_TYPE) {
	menu->EnableItem(cIdEdit);
	edit_state= TRUE;
        menu->DisableItem(cIdConst);
	const_state= FALSE;
    } else
	edit_state= FALSE;

    menu->EnableItems(cIdDelete, cIdDeleteTree, 0);

    if (qn->GetMode() == eNot || qn->GetMode() == eAnd || qn->GetMode() == eOr) {
      menu->DisableItem(cIdConst);
      const_state= FALSE;
    }
  }
}


void ComposerView::DoSetupMenu(Menu *menu)
{
#if 0
  VObject *sel;

  if (oldqtree)
    menu->EnableItem(cIdUndo);

  if (sel= GetSelection()) {
    TreeNode	*tn= FindTreeNode(sel);
    QNode	*qn= IP(sel->FindItem(cIdQItem))->GetNode();
    QuelInfo	*qi= GetInfo(GetDbName());
    char	*current;

    if (tn->Collapsed())
	current= "Expand";
    else
	current= "Collapse";
    menu->ReplaceItem(cIdCollapse, current);

    bool unkn_op= FALSE;
    if ((unkn_op= (qn->GetMode() == eUnknownOpr))
    || oldoprtype != qn->GetType()
    || (qn->GetChild() && oldchildtype != qn->GetChild()->GetType())) {

        oldoprtype= qn->GetType();
	MenuItem *m= Guard(menu->RemoveItem(cIdOperator), MenuItem);
	m->FreeAll();
	delete m;

	// int childtype= !unkn_op ? 0: qn->GetChild()->GetType();
	oldchildtype= qn->GetChild() ? qn->GetChild()->GetType(): 0;
        menu->AppendMenu(oprmenu=
	  qi->GetOprMenu(oldoprtype, oldchildtype), cIdOperator);
    }
    GetInfo()->SetPredefMenu(predefmenu, oldoprtype);

    int nv= qi->SetAttrMenu(attrmenu, table_name,
      oldoprtype, qn->GetMode() == eLeaf);
    menu->EnableItem(cIdAttr, GEOSIMPLE ? (nv > 0): TRUE);

    qi->SetTablesAttrMenu(tattrmenu, strncmp(table_name, "pg_", 3) != 0,
	oldoprtype, qn->GetMode() == eLeaf);

    if (unkn_op)
      oldoprtype= 0;

    if (qn->NrChilds() == 0) {
      menu->EnableItem(cIdConst);
      menu->EnableItem(cIdViewRect);
    }
    menu->EnableItem(cIdPredef);
    predefmenu->EnableItem(cIdSavePredef);

    if (oprmenu->GetCollection()->Size() == 0) {
	  menu->DisableItem(cIdOperator);
    } else {
	  menu->EnableItem(cIdOperator);
          MenuEnableAll(oprmenu);
    }

    // menu->EnableItems(cIdCollapse, cIdProc, cIdAttr, cIdTAttr, 0);
    menu->EnableItems(cIdCollapse, cIdProc, cIdTAttr, 0);
    if (qn->GetType() == BOOL_TYPE)
      menu->EnableItems(cIdNot, cIdAnd, cIdOr, 0);

    if (qn->GetMode() == eAnd || qn->GetMode() == eOr) {
      if (qn->GetMode() == eAnd)
	current= "Replace with OR";
      else
	current= "Replace with AND";
      menu->ReplaceItem(cIdReplAndOr, current);
      menu->EnableItem(cIdReplAndOr);
    }

    QNode *par;
    if ((par= qn->GetParent()) && par->GetMode() == eNot
    || qn->GetMode() == eNot)
	menu->DisableItem(cIdNot);

    if ((qn->GetMode() == eLeaf || qn->GetMode() == eEdit)
    && qn->GetType() == BOOL_TYPE) {
	menu->EnableItem(cIdEdit);
        menu->DisableItem(cIdConst);
    }
    menu->EnableItems(cIdDelete, cIdDeleteTree, 0);

    if (qn->GetMode() == eNot || qn->GetMode() == eAnd || qn->GetMode() == eOr)
      menu->DisableItem(cIdConst);
  }
#else
#endif
}

Command *ComposerView::DoMenuCommand(int cmd)
{
  VObject *sel= GetSelection();

  if (cmd == cIdUndo) {
	UndoLastChange();
	SetTv();
	return TreeView::DoMenuCommand(cmd);
  }

  if (!sel)          
	return TreeView::DoMenuCommand(cmd);

  QItem *qi= Guard(selection->FindItem(cIdQItem), QItem);
  QNode *qn= qi->GetNode();

  switch (cmd) {
  case cIdCollapse:
	Collapse(FindTreeNode(sel));
        break;
  // case cIdPromote:
  case cIdEdit:
	DoEdit(qn);
	break;
  case cIdDeleteTree:
  case cIdDelete:
	qn->Delete(cmd == cIdDeleteTree);
	NewQTree();
	break;
  case cIdAnd:
	qn->AddAnd();
	break;
  case cIdOr:
	qn->AddOr();
	break;
  case cIdReplAndOr:
	if (qn->GetMode() == eAnd)
	  qn->AddOr(TRUE);
	else
	  qn->AddAnd(TRUE);
	break;
#if 0
  case cIdReplOr:
	qn->AddOr(TRUE);
	break;
#endif
  case cIdNot:
	qn->AddNot();
	break;
  case cIdConst:
	ChangeConst(qn);
	break;
  case cIdViewRect:
	ViewRect(qn);
	break;
  case cIdSavePredef:
	PredefDialog	*pd= new PredefDialog(qn->GetType(), qn);

	pd->Show();
	delete pd;
	delete(menu->RemoveItem(cIdPredef));
        menu->AppendMenu(predefmenu=
	  GetInfo()->GetPredefMenu(), cIdPredef);
	break;
  default:
	if (cmd >= cIdFirstTable) {
	  ChangeAttr(qn, cmd);
	  break;
	} else if (cmd >= cIdFirstPredef) {
	  // SetSelection(0);
	  gActiveCV= this;
	  *qn= *(QNode*) GetInfo()->LoadPredef(cmd);
	  NewQTree();
	  break;
	} else if (cmd >= cIdFirstOperator) {
	  qn->AddOperator(cmd - cIdFirstOperator);
	  break;
	} else if (cmd >= cIdFirstType) {
	;
	} else if (cmd >= cIdFirstProc) {
	;
	} else
	  return TreeView::DoMenuCommand(cmd);
  }
  SetTv();

  CompStatus();
  cmdv->CopyState();
  old_selection= 0;

  return TreeView::DoMenuCommand(cmd);
}

Command *ComposerView::NodeSelected(VObject *v, int cl)
{
  if (cl == 2) {
    QNode *qn= IP(v->FindItem(cIdQItem))->GetNode();
    switch (qn->GetMode()) {
      case eEdit:
	DoEdit(qn);
	break;
      case eLeaf:
	if (qn->GetType() == BOOL_TYPE) {
	  DoEdit(qn);
	  break;
	}
      case eConst:
	ChangeConst(qn);
	break;
      default:
	;
    }
    SetTv();
  }
  return gNoChanges;
}

char *ComposerView::GetWhere(bool incomplete)
{
  static char	s[4000];

  if (IsManual()) {
    char	w[8000];

    strcpy(w, tv->GetText()->AsString());
    for (char *c= w; *c && isspace(*c); c++)
      ;
    if (!(strncmp(c, "where", 5) == 0 || strncmp(c, "from", 4) == 0))
      strcpy(s, "where ");
    else
      s[0]= '\0';
    strcat(s, w);
    return s;
  } else
    return IsValid() || incomplete ?
      GetWhere(s, GetQTree()): "";
}


bool ComposerView::IsManual()
{
#ifdef ET25
  return composer->GetManEdit();
#else
  return dv->GetManEdit();
#endif
}


char *ComposerView::GetWhere(char *s, QNode *qn)
{
  Collection *c= qn->GetChilds();
  
  if (c->Size() == 0) {
	char	*w= " <???> ";

	switch (qn->GetMode()) {
	  case eConst:
	    if (qn->GetCd())
		w= qn->GetCd()->GetWhere();
	    break;
	  case eEdit:
	    if (qn->GetAd() && qn->GetAd()->IsValid())
		w= qn->GetAd()->GetWhere();
	    break;
	  case eAttr:
	    w= qn->GetName();
	    break;
	  case eViewRect:
	    MapView *mv= qd->GetDrawView();
	    if (mv)
	      w= mv->GetQBbox();
	    else
	      w= "? ? ? ?";
	    break;
	  default:
	    ;
	}
	return strcpy(s, w);
  }

  Iter		next(c->MakeIterator());
  QNode		*n;
  bool		first= TRUE;
  char		locs[1000];
  QNmode	mode= qn->GetMode();

  strcpy(s, "(");

  while (n= QP(next())) {
    char	s2[1000];

    if (!first || mode == eNot || mode == eUnLOpr || mode == eUnROpr
    || mode == eUnknownOpr) {
	char *os;
	switch (mode) {
	  case eAnd: os= " and "; break;
	  case eOr: os= " or "; break;
	  case eNot: os= "not "; break;
	  case eUnknownOpr: os= "? OPR ? "; break;
	  case eUnLOpr:
	  case eUnROpr:
		sprintf(s2, " %s(", qn->GetOprCode());
		os= s2;
		break;
	  case eBinOpr:
		sprintf(s2, " %s ", qn->GetOprStr());
		os= s2;
		break;
	  default: abort();
	}
	strcat(s, os);
    } else
	first= FALSE;
    strcat(s, GetWhere(locs, n));
  }
  if (mode == eUnROpr || mode == eUnLOpr)
    strcat(s, ")");

  strcat(s, ")");

  return s;
}

bool ComposerView::IsValid(QNode *qn)
{
  Collection *c= qn->GetChilds();
  
  if (c->Size() == 0)
	return qn->IsValid();

  Iter next(c->MakeIterator());
  QNode	*n;

  while (n= QP(next())) {
    if (!IsValid(n))
	return FALSE; 
  }
  return TRUE;
}

bool ComposerView::IsValid()
{
  return IsManual() || IsValid(qtree);
}

#ifdef ET25
#else
bool ComposerView::IsActive()
{
  return dv->IsActive();
}
#endif

void ComposerView::SetTv()
{
  char *s= GetWhere(TRUE);
  tv->SetString((byte*)s, strlen(s));
  tv->ForceRedraw();
  tv->UpdateEvent();
  is_changed= TRUE;
#ifdef EXP
LogExp("Query: %s\n", s);
#endif
}

void ComposerView::SetManEdit(bool b)
{
  tv->SetReadOnly(!b);
  dialogRoot->Enable(!b);
  if (!b)
    SetTv();
}

void QNode::AddUnOpr()
{
  QNode *par;
  QNode *op= new QNode(cv, "? OPR ?", par= GetParent(), eUnknownOpr);
  op->SetFirstArgType(GetType());
  if (par)
    op->SetType(par->GetFirstArgType(), FALSE);
  AddParent(op);
}


void ComposerView::ChangeAttr(QNode *qn, int aindex)
{
  SaveOldTree();

  char		*tabname;
  QuelInfo	*qi= GetInfo(GetDbName());
  AttrInfo	*ai= qi->GetAttrInfo(aindex, &tabname);
  char		s[1000];
  int		oldtype= qn->GetType();
  int		newtype= ai->GetType();

  if (newtype == qi->GetTypeOid("dt"))
    newtype= qi->GetTypeOid("abstime");

  qn->DeleteTree();
  qn->Clear();

  qn->SetType(newtype, FALSE);
  sprintf(s, "%s.%s", tabname, ai->Str());
  qn->SetName(s);
  qn->SetMode(eAttr);
  if (oldtype != newtype)
    qn->AddUnOpr();
  else
    NewQTree();
}


void ComposerView::ViewRect(QNode *qn)
{
  int		oldtype= qn->GetType();
  QuelInfo	*qi= GetInfo(GetDbName());
  int		newtype= qi->GetTypeOid("box");

  SaveOldTree();
  qn->DeleteTree();
  qn->Clear();

  qn->SetName("Viewed Rect");
  qn->SetMode(eViewRect);
  qn->SetType(newtype, FALSE);

  if (oldtype != newtype)
    qn->AddUnOpr();
  else
    NewQTree();
}

void ComposerView::ChangeConst(QNode *qn)
{
  ConstantDialog *cd;

  SaveOldTree();
  qn->DeleteTree();
  qn->ClearAd();

  if (!(cd= qn->GetCd()))
	qn->SetCd(cd= new ConstantDialog(this, qn->GetType()));
  cd->Show();
  if (!cd->IsCancelled()) {
    qn->SetName(cd->GetLabel());
    qn->SetMode(eConst);
  } else {
    int ntype= qn->GetType();
    qn->Clear();
    qn->SetType(ntype);
  }
  NewQTree();
}


void ComposerView::DoEdit(QNode *qn)
{
  AttrDialog *d;

  SaveOldTree();
  if (!(d= qn->GetAd()))
    qn->SetAd(d= new AttrDialog(this, table_name, nfields, attr, attr_types));
  d->Show();
  qn->SetName(d->GetLabel());
  qn->SetMode(eEdit);
  NewQTree();
}


VObject *ComposerView::NodeAsVObject(Object *op)
{
    QItem *t= new QItem(Guard(op,QNode)->GetName(), QP(op));
    return new BackgroundItem(ePatWhite, t);
}

Iterator *ComposerView::MakeChildrenIter(Object *op)
{
    return Guard(op,QNode)->GetChilds()->MakeIterator();
}


const int		cIdLayout=	cIdFirstUser + 200,
			cIdConn=	cIdFirstUser + 300,
			cIdActive=	cIdFirstUser + 400,
			cIdManual=	cIdFirstUser + 401,
			cIdUpdate=	cIdFirstUser + 402;

static u_short Bits1[]= {
#   include "images/1.im"
};

static u_short Bits2[]= {
#   include "images/2.im"
};

static u_short Bits3[]= {
#   include "images/3.im"
};

static u_short Bits5[]= {
#   include "images/5.im"
};

static u_short Bits6[]= {
#   include "images/6.im"
};

static u_short Bits7[]= {
#   include "images/7.im"
};

#ifdef ET25
VObject *Composer::MakeDoneDialog()
#else
VObject *DoneDialogView::DoCreateDialog()
#endif
{
#ifdef EXP
    return new Cluster(cIdNone, eVObjVCenter, Point(10),
	      new ActionButton(cIdOk, "Done", TRUE),
	      new ActionButton(cIdCancel, "Cancel"),
	      0);
#else
    static const Point cIconSize1(40, 28);
    static Bitmap *B1, *B2, *B3, *B5, *B6, *B7;
 
    if (B1 == 0) {
        B1= new Bitmap(cIconSize1, Bits1);
        B2= new Bitmap(cIconSize1, Bits2);
        B3= new Bitmap(cIconSize1, Bits3);
        B5= new Bitmap(cIconSize1, Bits5);
        B6= new Bitmap(cIconSize1, Bits6);
        B7= new Bitmap(cIconSize1, Bits7);
    }  

    VObject *the_vo= new BorderItem(
      new Cluster(cIdNone, eVObjHLeft, Point(10),
        new Cluster(cIdNone, eVObjVCenter, Point(10),
#ifdef ET25
	  new BorderItem(q_active= new ToggleButton(cIdActive,
	    "Query Active")),
	  new BorderItem(man_edit= new ToggleButton(cIdManual,
	    "Manual Edit")),
#else
	  new BorderItem(new LabeledButton(cIdActive,
	    q_active= new ToggleButton,
	    new TextItem("Query Active"))),
	  new BorderItem(new LabeledButton(cIdManual,
	    man_edit= new ToggleButton,
	    new TextItem("Manual Edit"))),
#endif
	0),
        new Cluster(cIdNone, eVObjVCenter, Point(10),
            new Cluster(cIdNone, eVObjVCenter, Point(10),
	      new ActionButton(cIdOk, "Done", TRUE),
	      new ActionButton(cIdCancel, "Cancel"),
	      !GEOBROWSE ?
	        new ActionButton(cIdUpdate, "Update"):
		new Filler(gPoint0),
	      0)
	    ,
	    GEOSIMPLE ?
	    0:
            new BorderItem("Lines",
                new OneOfCluster(cIdNone, eVObjHLeft, Point(4),
#ifdef ET25
		    t1=  new RadioButton(cIdLayout+eTCDiagonal,
			new ImageItem(B1)),
		    new RadioButton(cIdLayout+eTCPerpendicular,
			new ImageItem(B2)),
		    t2= new RadioButton(cIdLayout+eTCDiagonal2,
		        new ImageItem(B3)),
		    new RadioButton(cIdLayout+eTCNone, "none"),
#else
                    t1= new LabeledButton(cIdLayout+eTCDiagonal,
                        new RadioButton,
                        new ImageItem(B1), gPoint10, eVObjVCenter),
                    new LabeledButton(cIdLayout+eTCPerpendicular,
                        new RadioButton,
                        new ImageItem(B2), gPoint10, eVObjVCenter),
                    t2= new LabeledButton(cIdLayout+eTCDiagonal2,
                        new RadioButton,
                        new ImageItem(B3), gPoint10, eVObjVCenter),
                    new LabeledButton(cIdLayout+eTCNone, "none", TRUE),
#endif
                    0
                )
            ),   
            new BorderItem("Layout",
                new OneOfCluster(cIdNone, eVObjHLeft, Point(4),
#ifdef ET25
                    new RadioButton(cIdConn+eTLLeftRight,
                        new ImageItem(B7)),
                    tindent= new RadioButton(cIdConn+eTLIndented,
                        new ImageItem(B5)),
                    new RadioButton(cIdConn+eTLTopDown,
                        new ImageItem(B6)),
#else
                    new LabeledButton(cIdConn+eTLLeftRight,
                        new RadioButton,
                        new ImageItem(B7), gPoint10, eVObjVCenter),
                    tindent= new LabeledButton(cIdConn+eTLIndented,
                        new RadioButton,
                        new ImageItem(B5), gPoint10, eVObjVCenter),
                    new LabeledButton(cIdConn+eTLTopDown,
                        new RadioButton,
                        new ImageItem(B6), gPoint10, eVObjVCenter),
#endif
                    0
                )
            ),
	0),
      0)
  );

  return the_vo;
#endif
}

#ifdef ET25
void Composer::DoSetup()
#else
void DoneDialogView::DoSetup()
#endif
{
    if (tindent)
      tindent->Enable(FALSE);
}

#ifdef ET25
void Composer::Control(int id, int part, void *v)
#else
void DoneDialogView::Control(int id, int part, void *v)
#endif
{
#ifdef ET25
    ComposerView *treeView= cv;
#endif
    switch (id) {
    case cIdOk:
	if (IsActive() && !treeView->IsValid()) {
          ShowAlert(eAlertNote,
	    "Your query is incomplete!\nComplete it or choose `@BCancel@P'");
	  return;
	}
#ifdef ET25
#else
        GetWindow()->Close();
#endif
	treeView->Send(cCOMPOSE_DONE);
	treeView->ClearChanged();
#ifdef EXP
LogExp("Composer done\n");
#endif
#ifdef ET25
	Dialog::Control(id, part, v);
#endif
	break;
    case cIdActive:
	treeView->SetChanged();
	break;
    case cIdCancel:
#ifndef EXP
#ifdef ET25
	q_active->SetValue(FALSE);
#else
	q_active->SetState(FALSE);
#endif
#else
LogExp("Composer cancelled\n");
#endif
        GetWindow()->Close();
	// Fall through !
    case cIdUpdate:
	treeView->Send(cCOMPOSE_UPDATE);
	treeView->ClearChanged();
	break;
    case cIdManual:
	treeView->SetManEdit(GetManEdit());
	break;
    }

    switch (part) {
    case cIdLayout+eTCPerpendicular:
    case cIdLayout+eTCNone:
    case cIdLayout+eTCDiagonal:
    case cIdLayout+eTCDiagonal2:
	tindent->Enable(part == cIdLayout+eTCPerpendicular
		|| part == cIdLayout+eTCNone);
        treeView->SetConnType(TreeConnection(part-cIdLayout));
        break;  

    case cIdConn+eTLIndented:
        t1->Enable(FALSE);
        t2->Enable(FALSE);
        treeView->SetLayout(TreeLayout(part-cIdConn));
        break;  

    case cIdConn+eTLTopDown:
    case cIdConn+eTLLeftRight:
        t1->Enable(TRUE);
        t2->Enable(TRUE);
        treeView->SetLayout(TreeLayout(part-cIdConn));
        break;
    }

#ifdef ET25
    Dialog::Control(id, part, v);
#else
    DialogView::Control(id, part, v);
#endif
}


#ifdef ET25
class PopupShadow: public PopupButton {
  TextItem	*label;
public:
  PopupShadow(int id, char *l, Menu *m): label(new TextItem(l)),
	(cIdNone, cIdNone, m)
    {};
  ~PopupShadow() { delete label; }
  void SetPopupMenu(Menu *m) { SetCollection(m->GetCollection()); };
  void Draw(Rectangle r);
};

void PopupShadow::Draw(Rectangle r)
{
  label->SetOrigin(GetOrigin()+gPoint2);
  label->SetExtent(GetExtent()-gPoint4);
  label->DrawAll(r);
  gLook->PopupButtonLayout()->Adorn(this, r, Enabled() ? 0 : 2);
}
#else
class PopupLabel: public Button {
  TextItem	*ti;
  Menu		*menu;
public:
  PopupLabel(int id, char *l, Menu *m): (id), menu(m) {
    SetAt(0, ti= new TextItem(l));
  };
  ~PopupLabel() { delete ti; };

  Metric GetMinSize() { return ti->GetMinSize(); };
  void DoOnItem(int m, VObject*, Point where) {
    if (m == 3 && menu) {
#ifdef ET25
      int id= menu->Pulldown(where, this);
#else
      int id= menu->Show(where, this);
#endif
      if (id != cIdNone)
	Control(GetId(), id, 0);
    }
  }
  void SetPopupMenu(Menu *m) { menu= m; };
};


class PopupShadow: public ShadowItem {
  PopupLabel	*pl;
public:
  PopupShadow(int id, char *l, Menu *m): (cIdNone, pl= new PopupLabel(id, l, m))
    {};
  ~PopupShadow() { delete pl; };
  void SetPopupMenu(Menu *m) { pl->SetPopupMenu(m); };
};
#endif


CommandView::~CommandView()
{
}


VObject *CommandView::DoCreateDialog()
{
  cl= new BorderItem("Operations",
      new Cluster(cIdNone, eVObjHLeft, Point(6),
	new BorderItem("Replace with:",
          new Cluster(cIdNone, eVObjHLeft, gPoint2,
	    attr_but= new PopupShadow(cIdAttr,
	    	treeView->GetTableName(), treeView->GetAttrMenu()),
	    tattr_but= new PopupShadow(cIdTAttr, "Tables",
		treeView->GetTAttrMenu()),
	    const_but= new ActionButton(cIdConst, "Constant..."),
	    opr_but= new PopupShadow(cIdOperator, "Operators",
		treeView->GetOprMenu()),
	    edit_but= new ActionButton(cIdEdit, "Edit..."),
	    0)),
	new BorderItem("Insert boolean:",
          new Cluster(cIdNone, eVObjHLeft, gPoint2,
            new Cluster(cIdNone, eVObjVCenter, gPoint2,
	      and_but= new ActionButton(cIdAnd, "AND"),
	      or_but= new ActionButton(cIdOr, "OR"),
	      0),
	    not_but= new ActionButton(cIdNot, "NOT"),
	    replace_but= new ActionButton(cIdReplAndOr, "Replace with AND"),
	    0)),
	new BorderItem("Delete:",
          new Cluster(cIdNone, eVObjVCenter, gPoint2,
	    del_but= new ActionButton(cIdDelete, "Node"),
	    deltree_but= new ActionButton(cIdDeleteTree, "Tree"),
	    0)),
	0));
  return cl;
}


void CommandView::CopyState()
{
#ifdef EXP
  return;
#endif

  bool some_enabled= ! treeView->AllDisabled();

  opr_but->SetPopupMenu(treeView->GetOprMenu());

  tattr_but->Enable(some_enabled);
  del_but->Enable(some_enabled);
  deltree_but->Enable(some_enabled);

  if (some_enabled && treeView->ReplaceStr()) {
    replace_but->Enable();
    ((TextItem*) replace_but->At(0))->SetString(treeView->ReplaceStr(), TRUE);
  } else
    replace_but->Disable();

  attr_but->Enable(some_enabled && treeView->AttrState());
  opr_but->Enable(some_enabled && treeView->OprState());
  const_but->Enable(some_enabled && treeView->ConstState());
  and_but->Enable(some_enabled && treeView->AndOrState());
  or_but->Enable(some_enabled && treeView->AndOrState());
  not_but->Enable(some_enabled && treeView->NotState());
  edit_but->Enable(some_enabled && treeView->EditState());

  UpdateEvent();
}


void CommandView::Control(int id, int part, void *v)
{
  treeView->DoMenuCommand(part == cPartAction ? id: part);
}


MetaImpl0(Composer);

#ifdef ET25
Composer::Composer(QueryDialog *q, char *db, char *tn,
	int nf, char **a, char **at):
	(tn, eWinFixed)
{
  cv= new ComposerView(this, q, db, tn, nf, a, at);
}

VObject *Composer::DoCreateDialog()
{
  Expander *exp= new Expander(0, eHor, gPoint4,
	    new Scroller(cv),
	    new Clipper(cv->cmdv= new CommandView(cv)),
	  0);
  Scroller *text_scr= new Scroller(cv->tv= new TextView(0, Rectangle(350,400),
		new CheapText(), cWordWrap, eTextViewReadOnly));

  return new Expander(0, eVert, gPoint4,
	  exp,
	  text_scr,
	  MakeDoneDialog(),
	  0
	);
}
#else
Composer::Composer(QueryDialog *q, char *db, char *tn,
	int nf, char **a, char **at):
	(q, db, tn, nf, a, at)
{
  Expander *exp= new Expander(0, eHor, gPoint4,
	    new Scroller(this),
	    new Clipper(cmdv= new CommandView(this)),
	  0);
  Scroller *text_scr= new Scroller(tv= new TextView(0, Rectangle(350,400),
		new CheapText(), eLeft, eOne, TRUE, eTextViewReadOnly));

#ifdef EXP
  w= new Window(this, Point(GEOSIMPLE ? 600: 500, 300),
#else
  w= new Window(this, Point(GEOSIMPLE ? 600: 500, 600),
#endif
	(WindowFlags)(eWinDefault),
	new Expander(0, eVert, gPoint4,
#ifdef EXP
	  EXP_MANUAL ? text_scr : exp,
#else
	  exp,
	  text_scr,
#endif
	  new Clipper(dv= new DoneDialogView(this)),
	  0
	),
	tn);

#ifdef EXP
  tv->SetReadOnly(!EXP_MANUAL);
#endif
}
#endif

#ifdef ET25
#else
Composer::~Composer()
{
  if (w->IsOpen())
    w->Close();
  delete w;
}

void Composer::Show()
{
  SetTv();
  w->OpenAt(gWindow->GetExtent().Half() - Point(200,300), gWindow);
  dv->DoSetup();
  CompStatus();
  cmdv->CopyState();
}
#endif

char *Composer::GetWhere()
{
#ifdef ET25
  if (IsOpen()) {
    cv->SetTv();
#else
  if (w->IsOpen()) {
    SetTv();
    UpdateEvent();
#endif
  }
    
#ifdef ET25
  return cv->GetWhere();
#else
  return ComposerView::GetWhere();
#endif
}
