/////////////////////////////////////////////////////////////////////////////
// Name:        dbentity.cpp
// Purpose:     Database Objects
// Author:      Daniel Horak
// Modified by:
// RCS-ID:      $Id: dbentity.cc,v 1.7 2004/01/04 18:32:16 horakdan Exp $
// Copyright:   (c) Daniel Horak
// Licence:     GPL
/////////////////////////////////////////////////////////////////////////////

// ============================================================================
// declarations
// ============================================================================

// ----------------------------------------------------------------------------
// headers
// ----------------------------------------------------------------------------

// For compilers that support precompilation, includes "wx/wx.h".
#include <wx/wxprec.h>

#ifdef __BORLANDC__
    #pragma hdrstop
#endif

// for all others, include the necessary headers (this file is usually all you
// need because it includes almost all "standard" wxWindows headers
#ifndef WX_PRECOMP
#include <wx/wx.h>
#endif

#include <wx/notebook.h>
#include <wx/ogl/ogl.h>
#include "config.h"
#include "xml.h"
#include "dbobject.h"
#include "dbentity.h"
#include "dbattribute.h"
#include "dbindex.h"
#include "dbtrigger.h"
#include "dbconstraint.h"
#include "dbsimpleattr.h"
#include "dbmodelentity.h"
#include "dbrelation.h"
#include "schema.h"
#include "project.h"


DBEntity::DBEntity(DataDesignerProject *project, DataDesignerContainer *container)
	:DBObject(DBEntityType, "entity", project, container),
	m_attrs(NULL), m_indexes(NULL), m_triggers(NULL), m_constraints(NULL),
	m_placed(FALSE), m_xpos(100), m_ypos(100)
{
}

DBEntity::~DBEntity()
{
	if (m_attrs) {
		m_project->DeleteChildren(m_attrs->GetTreeItemId());
		delete m_attrs;
	}
	if (m_indexes) {
		m_project->DeleteChildren(m_indexes->GetTreeItemId());
		delete m_indexes;
	}
	if (m_triggers) {
		m_project->DeleteChildren(m_triggers->GetTreeItemId());
		delete m_triggers;
	}
	if (m_constraints) {
		m_project->DeleteChildren(m_constraints->GetTreeItemId());
		delete m_constraints;
	}
	
	wxDBObjectListNode	*node;
	
	// must delete all modelentities derived from this real entity
	node = m_modelentities.GetFirst();
	while (node) {
		delete node->GetData();
		node = node->GetNext();
	}
}

void DBEntity::LoadXmlNode(wxXmlNode *node)
{
	wxXmlNode	*child;
	wxString	number;

	if (node->GetName() == m_typestr) {
		DBObject::LoadXmlNode(node);
		
		number = node->GetPropVal("xpos", "100");
		m_xpos = wxAtoi(number);
		number = node->GetPropVal("ypos", "100");
		m_ypos = wxAtoi(number);
		
		child = node->GetChildren();
		while (child) {
			if (child->GetName() == "attributes") {
				m_attrs->LoadXmlNode(child);
			} else if (child->GetName() == "indexes") {
				m_indexes->LoadXmlNode(child);
			} else if (child->GetName() == "triggers") {
				m_triggers->LoadXmlNode(child);
			} else if (child->GetName() == "constraints") {
				m_constraints->LoadXmlNode(child);
			}
			child = child->GetNext();
		}
	} else {
		wxLogMessage("wrong type '%s'", node->GetName().c_str());
	}
}

wxTreeItemId DBEntity::AppendItem()
{
	DataDesignerSchema	*schema;
	
	if (! m_appended) {
		m_treeitemid = m_project->AppendItem(m_container->GetTreeItemId(), m_name, -1, -1, new DataDesignerItemData(this));
	
		m_attrs		= new DBAttributeContainer(m_project, m_project->AppendItem(m_treeitemid, _("Attributes")));
		m_indexes	= new DBIndexContainer(m_project, m_project->AppendItem(m_treeitemid, _("Indexes")));
		m_triggers	= new DBTriggerContainer(m_project, m_project->AppendItem(m_treeitemid, _("Triggers")));
		m_constraints	= new DBConstraintContainer(m_project, m_project->AppendItem(m_treeitemid, _("Constraints")));
		
		if ((schema = m_project->GetSchema()) != NULL) {
			schema->AddObject(this);
			schema->Refresh();
		}
		
		m_appended = TRUE;
	}
	
	return m_treeitemid;
}

wxDialog *DBEntity::Editor(bool edit)
{
	return new DBEntityEditor(this, edit);
}

wxXmlNode *DBEntity::GetXmlNode()
{
	wxXmlNode	*node = DBObject::GetXmlNode();
	wxString	number;
	
	number.Printf("%d", m_xpos);
	node->AddProperty("xpos", number);
	number.Printf("%d", m_ypos);
	node->AddProperty("ypos", number);

	node->AddChild(m_attrs->GetXmlNode());
	node->AddChild(m_indexes->GetXmlNode());
	node->AddChild(m_triggers->GetXmlNode());
	node->AddChild(m_constraints->GetXmlNode());
	
	return node;
}

void DBEntity::CreateShape()
{
	m_shape = new DBEntityShape(this);
	m_shape->Show(TRUE);
}

int DBEntity::GetAttributeCount()
{
	return GetProject()->GetChildrenCount(m_attrs->GetTreeItemId(), FALSE);
}

DBAttribute *DBEntity::GetAttributeByName(const wxString& name)
{
	wxLogMessage("DBEntity::GetAttributeByName - not implemented");
	
	return NULL;
}

DBAttribute *DBEntity::AddAttribute(DBAttribute *attr)
{
	DBAttribute	*nattr;
	
#ifdef ENABLE_DEBUG
	wxLogMessage("DBEntity::AddAttribute");
#endif

	if (attr == NULL) {
#ifdef ENABLE_DEBUG
		wxLogMessage("DBEntity::AddAttribute - attr == NULL");
#endif
		return NULL;
	}
		
	nattr = (DBAttribute *)m_attrs->CreateObject();
	nattr->Copy(attr);
	nattr->AppendItem();
	
	return nattr;
}

void DBEntity::AddAttributeToPrimaryKey(DBAttribute *attr)
{
	DBConstraint	*pk;
	
#ifdef ENABLE_DEBUG
	wxLogMessage("DBEntity::AddAttributeToPrimaryKey");
#endif

	if (attr == NULL)
		return;

	attr->m_primarykey = TRUE;		
	pk = GetPrimaryKeyConstraint();
	if (pk == NULL) {
		// create a PK constraint
#ifdef ENABLE_DEBUG
		wxLogMessage("DBEntity::AddAttributeToPrimaryKey - creating PK constraint");
#endif
		pk = (DBConstraint *)m_constraints->CreateObject();
		pk->m_type = DBO_CONSTRAINT_TYPE_PRIMARY_KEY;
		pk->m_name = m_name + "_pk";
		pk->CreatePName();
		pk->AppendItem();
	}
	
	pk->AddAttribute(attr);
}

void DBEntity::DeleteAttributeFromPrimaryKey(DBAttribute *attr)
{
	DBConstraint	*pk;
	
	pk = GetPrimaryKeyConstraint();
	if (pk != NULL) {
		pk->DeleteAttribute(attr->GetName());
		if (pk->m_attrs->GetChildrenCount() == 0) {
			// if there is no attribute in PK then destroy whole PK constraint
			delete pk;
		}
	}
}

DBConstraint *DBEntity::GetPrimaryKeyConstraint()
{
	DBConstraint	*pk = NULL, *constr;
	long		cookie;
	wxTreeItemId	child;
	
	if (m_constraints == NULL) {
		wxLogMessage("DBEntity::GetPrimaryKeyConstraint - m_constraints == NULL");
		return NULL;
	}
	
	child = GetProject()->GetFirstChild(m_constraints->GetTreeItemId(), cookie);
	while (child.IsOk()) {
		constr = (DBConstraint *)(((DataDesignerItemData *)m_project->GetItemData(child))->GetObject());
		if (constr->m_type == DBO_CONSTRAINT_TYPE_PRIMARY_KEY) {
			pk = constr;
			break;
		}
		child = GetProject()->GetNextChild(m_constraints->GetTreeItemId(), cookie);
	}
#ifdef ENABLE_DEBUG
	wxLogMessage("DBEntity::GetPrimaryKeyConstraint - PK was %sfound", pk ? "" : "not ");
#endif
	return pk;
}

void DBEntity::AddForeignKeyConstraint(DBRelation *relation, bool identifying)
{
	DBEntity		*parent_ent;
	long			cookie;
	wxTreeItemId		child;
	DBConstraint		*parent_pk;
	DBConstraint		*fk;
	DBSimpleAttribute	*simpleattr;
	DBAttribute		*attr;

#ifdef ENABLE_DEBUG
	wxLogMessage("DBEntity::AddForeignKeyConstraint - this entity '%s'", m_name.c_str());
#endif
	
	parent_ent = (DBEntity *)(GetProject()->m_top_entities->GetObjectByName(relation->m_parent));
	if (parent_ent == NULL) {
		wxLogMessage("DBEntity::AddForeignKeyConstraint - cannot find parent entity '%s'", relation->m_parent.c_str());
		return;
	}
	
	parent_pk = parent_ent->GetPrimaryKeyConstraint();
	if (parent_pk == NULL) {
		wxLogMessage("DBEntity::AddForeignKeyConstraint - parent entity '%s' has no primary key", parent_ent->GetName().c_str());
		return;
	}
	
	fk = (DBConstraint *)m_constraints->CreateObject();
	fk->m_type = DBO_CONSTRAINT_TYPE_FOREIGN_KEY;
	fk->m_name = parent_ent->GetName() + "_fk";
	fk->m_relation = relation->GetName();
	fk->CreatePName();
	fk->AppendItem();
	
	// iterate through attributes in parent's PK
	child = GetProject()->GetFirstChild(parent_pk->GetAttributes()->GetTreeItemId(), cookie);
	while (child.IsOk()) {
		simpleattr = (DBSimpleAttribute *)(((DataDesignerItemData *)GetProject()->GetItemData(child))->GetObject());

#ifdef ENABLE_DEBUG
		wxLogMessage("DBEntity::AddForeignKeyConstraint - processing attr '%s'", simpleattr->GetName().c_str());
#endif		

		attr = AddAttribute(simpleattr->GetRealAttribute());
		if (attr == NULL) {
			wxLogFatalError("attr == NULL");
			break;
		}
		attr->m_imported = TRUE;
		attr->m_foreignkey = TRUE;
		attr->m_relation = relation->GetName();
		
		fk->AddAttribute(attr);
		
		if ((identifying == TRUE))
			AddAttributeToPrimaryKey(attr);
		
		child = GetProject()->GetNextChild(parent_pk->GetTreeItemId(), cookie);
	}
}

void DBEntity::AddParentsPrimaryKey(DBRelation *relation)
{
	DBEntity		*parent_ent;
	long			cookie;
	wxTreeItemId		child;
	DBConstraint		*parent_pk;
	DBSimpleAttribute	*simpleattr;
	DBAttribute		*attr;

#ifdef ENABLE_DEBUG
	wxLogMessage("DBEntity::AddParentKey - this entity '%s'", m_name.c_str());
#endif
	
	parent_ent = (DBEntity *)(GetProject()->m_top_entities->GetObjectByName(relation->m_parent));
	if (parent_ent == NULL) {
		wxLogMessage("DBEntity::AddParentKey - cannot find parent entity '%s'", relation->m_parent.c_str());
		return;
	}
	
	parent_pk = parent_ent->GetPrimaryKeyConstraint();
	if (parent_pk == NULL) {
		wxLogMessage("DBEntity::AddParentKey - parent entity '%s' has no primary key", parent_ent->GetName().c_str());
		return;
	}
	
	// iterate through attributes in parent's PK
	child = GetProject()->GetFirstChild(parent_pk->GetAttributes()->GetTreeItemId(), cookie);
	while (child.IsOk()) {
		simpleattr = (DBSimpleAttribute *)(((DataDesignerItemData *)GetProject()->GetItemData(child))->GetObject());

#ifdef ENABLE_DEBUG
		wxLogMessage("DBEntity::AddParentKey - processing attr '%s'", simpleattr->GetName().c_str());
#endif
		
		attr = AddAttribute(simpleattr->GetRealAttribute());
		attr->m_imported = TRUE;
		attr->m_foreignkey = TRUE;
		attr->m_relation = relation->GetName();
		
		child = GetProject()->GetNextChild(parent_pk->GetTreeItemId(), cookie);
	}
}

void DBEntity::AddModelEntity(DBModelEntity *ent)
{
	m_modelentities.Append(ent);
}

void DBEntity::DeleteModelEntity(DBModelEntity *ent)
{
	if (m_modelentities.IndexOf(ent) != wxNOT_FOUND) {
		m_modelentities.DeleteObject(ent);
	}
}

/*
 * Editor
 */
BEGIN_EVENT_TABLE(DBEntityEditor, DBObjectEditor)
	EVT_BUTTON(wxID_APPLY,	DBEntityEditor::OnApply)
END_EVENT_TABLE()

DBEntityEditor::DBEntityEditor(DBObject *object, bool edit)
	: DBObjectEditor(_("Entity"), wxSize(500,400), object, edit)
{
	m_page_attributes	= new wxPanel(m_notebook);
	m_page_constraints	= new wxPanel(m_notebook);
	m_page_indexes		= new wxPanel(m_notebook);
	m_page_triggers		= new wxPanel(m_notebook);
	
	m_list_attrs		= new DBAttributeListCtrl	(m_page_attributes,	((DBEntity *)GetObject())->m_attrs);
	m_list_constraints	= new DBConstraintListCtrl	(m_page_constraints,	((DBEntity *)GetObject())->m_constraints);
	m_list_indexes		= new DBIndexListCtrl		(m_page_indexes, 	((DBEntity *)GetObject())->m_indexes);
	m_list_triggers		= new DBTriggerListCtrl		(m_page_triggers, 	((DBEntity *)GetObject())->m_triggers);

	if (m_edit) {
		((DBEntity *)GetObject())->m_attrs->SetList(m_list_attrs);
		((DBEntity *)GetObject())->m_attrs->AddObjectsToList();
	
		((DBEntity *)GetObject())->m_constraints->SetList(m_list_constraints);
		((DBEntity *)GetObject())->m_constraints->AddObjectsToList();
	
		((DBEntity *)GetObject())->m_indexes->SetList(m_list_indexes);
		((DBEntity *)GetObject())->m_indexes->AddObjectsToList();

		((DBEntity *)GetObject())->m_triggers->SetList(m_list_triggers);
		((DBEntity *)GetObject())->m_triggers->AddObjectsToList();
	}
	
	wxLayoutConstraints *c = new wxLayoutConstraints;
	c->top.SameAs	(m_page_attributes, wxTop);
	c->left.SameAs	(m_page_attributes, wxLeft);
	c->right.SameAs	(m_page_attributes, wxRight);
	c->bottom.SameAs(m_page_attributes, wxBottom);
	m_list_attrs->SetConstraints(c);
	
	c = new wxLayoutConstraints;
	c->top.SameAs	(m_page_constraints, wxTop);
	c->left.SameAs	(m_page_constraints, wxLeft);
	c->right.SameAs	(m_page_constraints, wxRight);
	c->bottom.SameAs(m_page_constraints, wxBottom);
	m_list_constraints->SetConstraints(c);
	
	c = new wxLayoutConstraints;
	c->top.SameAs	(m_page_indexes, wxTop);
	c->left.SameAs	(m_page_indexes, wxLeft);
	c->right.SameAs	(m_page_indexes, wxRight);
	c->bottom.SameAs(m_page_indexes, wxBottom);
	m_list_indexes->SetConstraints(c);
	
	c = new wxLayoutConstraints;
	c->top.SameAs	(m_page_triggers, wxTop);
	c->left.SameAs	(m_page_triggers, wxLeft);
	c->right.SameAs	(m_page_triggers, wxRight);
	c->bottom.SameAs(m_page_triggers, wxBottom);
	m_list_triggers->SetConstraints(c);
	
	m_page_attributes->SetAutoLayout(TRUE);
	m_page_constraints->SetAutoLayout(TRUE);
	m_page_indexes->SetAutoLayout(TRUE);
	m_page_triggers->SetAutoLayout(TRUE);
	
	m_notebook->InsertPage(m_notebook->GetPageCount() - 1, m_page_attributes, _("Attributes"));
	m_notebook->InsertPage(m_notebook->GetPageCount() - 1, m_page_constraints, _("Constraints"));
	m_notebook->InsertPage(m_notebook->GetPageCount() - 1, m_page_indexes, _("Indexes"));
	m_notebook->InsertPage(m_notebook->GetPageCount() - 1, m_page_triggers, _("Triggers"));

	m_button_apply	= AddButton(wxID_APPLY, _("Apply"), wxSize(60,-1));

	if (m_edit) {
		m_button_ok->SetDefault();
	} else {
		m_button_apply->SetDefault();

		m_page_attributes->Disable();
		m_page_constraints->Disable();
		m_page_indexes->Disable();
		m_page_triggers->Disable();
	}
}

DBEntityEditor::~DBEntityEditor()
{
}

bool DBEntityEditor::TransferDataFromWindow()
{
	DBEntity		*entity = (DBEntity *)GetObject();

	DBObjectEditor::TransferDataFromWindow();
	
	return TRUE;
}

bool DBEntityEditor::TransferDataToWindow()
{
	DBEntity		*entity = (DBEntity *)GetObject();
	
	DBObjectEditor::TransferDataToWindow();
	
	return TRUE;
}

void DBEntityEditor::OnApply(wxCommandEvent& event)
{
	DBObject	*object;

	if (Validate() == FALSE)
		return;
		
	TransferDataFromWindow();
	
	object = GetObject();
	object->AppendItem();
	
	m_list_attrs->SetContainer(((DBEntity *)GetObject())->m_attrs);
	m_list_constraints->SetContainer(((DBEntity *)GetObject())->m_constraints);
	m_list_indexes->SetContainer(((DBEntity *)GetObject())->m_indexes);
	m_list_triggers->SetContainer(((DBEntity *)GetObject())->m_triggers);

	m_page_attributes->Enable();
	m_page_constraints->Enable();
	m_page_indexes->Enable();
	m_page_triggers->Enable();
	
	m_button_apply->Disable();
	m_button_ok->SetDefault();

	m_edit = TRUE;
}

/*
 * Container
 */
DBEntityContainer::DBEntityContainer(DataDesignerProject *project, const wxTreeItemId& parent)
	: DataDesignerContainer(project, parent, "entities")
{
}

DBObject *DBEntityContainer::CreateObject()
{
	return new DBEntity(GetProject(), this);
}

void DBEntityContainer::ShowList()
{
	SetList(new DBEntityListCtrl(GetProject()->GetSplitter(), this));
	
	DataDesignerContainer::AddObjectsToListAndShow();
}

/*
 * ObjectList
 */
DBEntityListCtrl::DBEntityListCtrl(wxWindow *parent, DataDesignerContainer *container)
	: DBObjectListCtrl(parent, container)
{
	SetColumnWidth(0, 200);
	InsertColumn(1, _("Remark"));
	SetColumnWidth(1, 400);
}

DBEntityListCtrl::~DBEntityListCtrl()
{
}

void DBEntityListCtrl::SetObject(long item, DBObject *object)
{
	DBEntity *entity = (DBEntity *)object;
}

/*
 * DBEntityShape
 */
DBEntityShape::DBEntityShape(DBEntity *entity)
	: wxRectangleShape(), m_mentity(NULL)
{
	Create(entity);

	m_xpos = entity->m_xpos;
	m_ypos = entity->m_ypos;
}

DBEntityShape::DBEntityShape(DBModelEntity *mentity)
	: wxRectangleShape(), m_mentity(mentity)
{
	Create(mentity->GetRealEntity());

	m_xpos = mentity->m_xpos;
	m_ypos = mentity->m_ypos;
}

void DBEntityShape::Create(DBEntity *entity)
{
	SetClientData(entity);
	m_attrcount = entity->GetAttributeCount();

	m_pen = wxBLACK_PEN;
	m_brush = wxGREEN_BRUSH;
	m_font = g_oglNormalFont;
	m_width = 100;
	m_height = (m_attrcount + 1) * 20 + 5;
	m_fixedWidth = TRUE;
	m_fixedHeight = TRUE;
	SetDefaultRegionSize();
}

void DBEntityShape::OnDraw(wxDC& dc)
{
	DBEntity	*entity;
	wxCoord		left, top;
	wxCoord		x, y;
	int		cnt;
	wxTreeItemId	child, attrsid;
	long		cookie;
	DBAttribute	*attr;
	DataDesignerProject	*project;
	wxString	attr_txt;
	
	entity = (DBEntity *)GetClientData();
	project = entity->GetProject();
	attrsid = entity->m_attrs->GetTreeItemId();
	cnt = entity->GetAttributeCount();
	if (cnt != m_attrcount) {
		m_attrcount = cnt;
		m_height = (m_attrcount + 1) * 20 + 5;
	}
	left = (wxCoord)(m_xpos - m_width/2);
	top = (wxCoord)(m_ypos - m_height/2);

	wxRectangleShape::OnDraw(dc);

	dc.SetFont(*m_font);
	x = left + 5;
	y = top + 5;
	dc.DrawText(entity->m_name, x, y);

	y = (wxCoord)(top + 20);
	dc.DrawLine(left, y, left + (wxCoord)m_width, y);
	
	y += 5;
	child = project->GetFirstChild(attrsid, cookie);
	while (child.IsOk()) {
		attr = (DBAttribute *)(((DataDesignerItemData *)(project->GetItemData(child)))->GetObject());
		attr_txt = attr->m_name;
		if (attr->m_primarykey || attr->m_foreignkey) {
			attr_txt.Append(' ');

			if (attr->m_primarykey)
				attr_txt.Append('P');
			if (attr->m_foreignkey)
				attr_txt.Append('F');
				
			attr_txt.Append('K');
		}
		dc.DrawText(attr_txt, left + 5, y);
		y += 20;
		child = project->GetNextChild(attrsid, cookie);
	}
#if 0
	Move(dc, GetX(), GetY());		// redraw ends of relations too
#endif
}

void DBEntityShape::OnEndDragLeft(double x, double y, int keys, int attachment)
{
	DBEntity	*entity;

	wxRectangleShape::OnEndDragLeft(x, y, keys, attachment);
	
	entity = (DBEntity *)GetClientData();
	
	if (m_mentity) {
		m_mentity->m_xpos = (wxCoord)m_xpos;
		m_mentity->m_ypos = (wxCoord)m_ypos;
	} else {
		entity->m_xpos = (wxCoord)m_xpos;
		entity->m_ypos = (wxCoord)m_ypos;
	}
	
	entity->GetProject()->GetSplitter()->GetView()->GetDocument()->Modify(TRUE);
}

void DBEntityShape::OnLeftDoubleClick(double x, double y, int keys, int attachment)
{
	DBObject	*object;
	wxDialog	*editor;

	wxRectangleShape::OnLeftDoubleClick(x, y, keys, attachment);
	
	object = (DBObject *)GetClientData();
	editor = object->Editor(TRUE);
	if (editor) {
		editor->ShowModal();
		editor->Destroy();
	}
}

void DBEntityShape::OnLeftClick(double x, double y, int keys, int attachment)
{
	DBObject	*object;
	
	wxRectangleShape::OnLeftClick(x, y, keys, attachment);

#ifdef NOT_USED_YET	
	wxClientDC dc(GetCanvas());
	GetCanvas()->PrepareDC(dc);

	object = (DBObject *)GetClientData();
	
	if (keys == 0) {
		bool selected = Selected();
		
		selected = !selected;
		Select(selected, &dc);
		
//		object->GetProject()->GetSchema()->SelectAll(FALSE);
	} else if (keys & KEY_SHIFT) {
		if (Selected()) {
			Select(FALSE, &dc);
		} else {
			Select(TRUE, &dc);
		}
	}
#endif
}

void DBEntityShape::OnBeginDragRight(double x, double y, int keys, int attachment)
{
	wxRectangleShape::OnBeginDragRight(x, y, keys, attachment);
	
	attachment = 0;

	wxClientDC dc(GetCanvas());
	GetCanvas()->PrepareDC(dc);
	
	wxPen dottedPen(wxColour(0,0,0), 1, wxDOT);
	dc.SetLogicalFunction(OGLRBLF);
	dc.SetPen(dottedPen);
	double xp, yp;
	GetAttachmentPosition(attachment, &xp, &yp);
	dc.DrawLine((wxCoord) xp, (wxCoord) yp, (wxCoord) x, (wxCoord) y);
	GetCanvas()->CaptureMouse();
	
	DBObject *object = (DBObject *)GetClientData();
}

void DBEntityShape::OnDragRight(bool draw, double x, double y, int keys, int attachment)
{
	wxRectangleShape::OnDragRight(draw, x, y, keys, attachment);

	attachment = 0;
	
	wxClientDC dc(GetCanvas());
	GetCanvas()->PrepareDC(dc);
	
        wxPen dottedPen(wxColour(0, 0, 0), 1, wxDOT);
	dc.SetLogicalFunction(OGLRBLF);
	dc.SetPen(dottedPen);
	double xp, yp;
	GetAttachmentPosition(attachment, &xp, &yp);
	dc.DrawLine((wxCoord) xp, (wxCoord) yp, (wxCoord) x, (wxCoord) y);
}

void DBEntityShape::OnEndDragRight(double x, double y, int keys, int attachment)
{
	wxRectangleShape::OnEndDragRight(x, y, keys, attachment);

	GetCanvas()->ReleaseMouse();
	
	int new_attachment;
	wxShape *other = GetCanvas()->FindFirstSensitiveShape(x, y, &new_attachment, OP_DRAG_RIGHT);
	
	if (other == NULL) {
		// there is no object on the end, so exit
		return;
	}
		
	DBObject *object_from = (DBObject *)GetClientData();
	DBObject *object_to = (DBObject *)(other->GetClientData());
	
	if (!object_to || (object_to->GetType() != DBEntityType) || (object_to == object_from)) {
#ifdef ENABLE_DEBUG
		wxLogMessage("DBEntityShape::OnEndDragRight - no end for relation");
#endif
		return;
	}
	
	DBRelation *relation = (DBRelation *)(object_from->GetProject()->m_top_relations->CreateObject());
	relation->m_parent = object_from->GetName();
	relation->m_child = object_to->GetName();
	relation->m_name = object_from->m_pname + "_" + object_to->m_pname;
	relation->AppendItem();
	wxDialog *editor = relation->Editor(TRUE);
	if (editor) {
		if (editor->ShowModal() != wxID_OK) {
			object_from->GetProject()->Delete(relation->GetTreeItemId());
			delete relation;
			relation = NULL;
		}
		editor->Destroy();
		
		if (relation) {
			// copy PK from parent as FK into child
			relation->LinkAttributes();
			
			// attach relation to model iff schema is a model
		}
	}
}


syntax highlighted by Code2HTML, v. 0.9.1