pgadmin3/ogl/canvas.cpp
2024-02-06 08:34:42 +05:00

574 lines
16 KiB
C++

//////////////////////////////////////////////////////////////////////////
//
// pgAdmin III - PostgreSQL Tools
//
// Portions Copyright (C) 1998 - 2011, Julian Smart
// Portions Copyright (C) 2011 - 2016, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
// canvas.cpp - Shape canvas class
//
//////////////////////////////////////////////////////////////////////////
#include "pgAdmin3.h"
#include <ctype.h>
#include <math.h>
#include <stdlib.h>
#include "wx/dcbuffer.h"
#include "ogl/ogl.h"
#define CONTROL_POINT_SIZE 6
// Control point types
// Rectangle and most other shapes
#define CONTROL_POINT_VERTICAL 1
#define CONTROL_POINT_HORIZONTAL 2
#define CONTROL_POINT_DIAGONAL 3
// Line
#define CONTROL_POINT_ENDPOINT_TO 4
#define CONTROL_POINT_ENDPOINT_FROM 5
#define CONTROL_POINT_LINE 6
IMPLEMENT_DYNAMIC_CLASS(wxShapeCanvas, wxScrolledWindow)
BEGIN_EVENT_TABLE(wxShapeCanvas, wxScrolledWindow)
EVT_PAINT(wxShapeCanvas::OnPaint)
EVT_ERASE_BACKGROUND(wxShapeCanvas::OnEraseBackground)
EVT_MOUSE_EVENTS(wxShapeCanvas::OnMouseEvent)
END_EVENT_TABLE()
const wxChar *wxShapeCanvasNameStr = wxT("shapeCanvas");
// Object canvas
wxShapeCanvas::wxShapeCanvas(wxWindow *parent, wxWindowID id,
const wxPoint &pos,
const wxSize &size,
long style,
const wxString &name):
wxScrolledWindow(parent, id, pos, size, style | wxVSCROLL | wxHSCROLL, name)
{
m_shapeDiagram = NULL;
m_dragState = NoDragging;
m_draggedShape = NULL;
m_oldDragX = 0;
m_oldDragY = 0;
m_firstDragX = 0;
m_firstDragY = 0;
m_checkTolerance = TRUE;
#if OGL_USE_BUFFERED_PAINT
RecreateBuffer(size);
#endif
}
wxShapeCanvas::~wxShapeCanvas()
{
if(m_shapeDiagram)
delete m_shapeDiagram;
}
#if OGL_USE_BUFFERED_PAINT
/// Recreate buffer bitmap if necessary
bool wxShapeCanvas::RecreateBuffer(const wxSize &size)
{
wxSize sz = size;
if (sz == wxDefaultSize)
sz = GetClientSize();
if (sz.x < 1 || sz.y < 1)
return false;
if (!m_bufferBitmap.Ok() || m_bufferBitmap.GetWidth() < sz.x || m_bufferBitmap.GetHeight() < sz.y)
m_bufferBitmap = wxBitmap(sz.x, sz.y);
return m_bufferBitmap.Ok();
}
#endif
void wxShapeCanvas::OnPaint(wxPaintEvent &WXUNUSED(event))
{
#if OGL_USE_BUFFERED_PAINT
RecreateBuffer();
wxBufferedPaintDC dc(this, m_bufferBitmap);
#else
wxPaintDC dc(this);
#endif
PrepareDC(dc);
DrawBackground(dc, true);
if (GetDiagram())
GetDiagram()->Redraw(dc);
// Necessary or it unscales again if there's a zoom level
dc.SetUserScale(1.0, 1.0);
}
// Draws the background
void wxShapeCanvas::DrawBackground(wxDC &dc, bool transformed)
{
dc.SetBackground(wxBrush(GetBackgroundColour(), wxBRUSHSTYLE_SOLID));
dc.Clear();
}
void wxShapeCanvas::OnEraseBackground(wxEraseEvent &event)
{
}
void wxShapeCanvas::OnMouseEvent(wxMouseEvent &event)
{
wxClientDC dc(this);
PrepareDC(dc);
wxPoint logPos(event.GetLogicalPosition(dc));
double x, y;
x = (double) logPos.x;
y = (double) logPos.y;
int keys = 0;
if (event.ShiftDown())
keys = keys | KEY_SHIFT;
if (event.ControlDown())
keys = keys | KEY_CTRL;
bool dragging = event.Dragging();
// Check if we're within the tolerance for mouse movements.
// If we're very close to the position we started dragging
// from, this may not be an intentional drag at all.
if (dragging)
{
int dx = abs(dc.LogicalToDeviceX((long) (x - m_firstDragX)));
int dy = abs(dc.LogicalToDeviceY((long) (y - m_firstDragY)));
if (m_checkTolerance && (dx <= GetDiagram()->GetMouseTolerance()) && (dy <= GetDiagram()->GetMouseTolerance()))
{
return;
}
else
// If we've ignored the tolerance once, then ALWAYS ignore
// tolerance in this drag, even if we come back within
// the tolerance range.
m_checkTolerance = FALSE;
}
// Dragging - note that the effect of dragging is left entirely up
// to the object, so no movement is done unless explicitly done by
// object.
if (dragging && m_draggedShape && m_dragState == StartDraggingLeft)
{
m_dragState = ContinueDraggingLeft;
// If the object isn't m_draggable, transfer message to canvas
if (m_draggedShape->Draggable())
m_draggedShape->GetEventHandler()->OnBeginDragLeft((double)x, (double)y, keys, m_draggedAttachment);
else
{
m_draggedShape = NULL;
OnBeginDragLeft((double)x, (double)y, keys);
}
m_oldDragX = x;
m_oldDragY = y;
}
else if (dragging && m_draggedShape && m_dragState == ContinueDraggingLeft)
{
// Continue dragging
m_draggedShape->GetEventHandler()->OnDragLeft(FALSE, m_oldDragX, m_oldDragY, keys, m_draggedAttachment);
m_draggedShape->GetEventHandler()->OnDragLeft(TRUE, (double)x, (double)y, keys, m_draggedAttachment);
m_oldDragX = x;
m_oldDragY = y;
}
else if (event.LeftUp() && m_draggedShape && m_dragState == ContinueDraggingLeft)
{
m_dragState = NoDragging;
m_checkTolerance = TRUE;
m_draggedShape->GetEventHandler()->OnDragLeft(FALSE, m_oldDragX, m_oldDragY, keys, m_draggedAttachment);
m_draggedShape->GetEventHandler()->OnEndDragLeft((double)x, (double)y, keys, m_draggedAttachment);
m_draggedShape = NULL;
}
else if (dragging && m_draggedShape && m_dragState == StartDraggingRight)
{
m_dragState = ContinueDraggingRight;
if (m_draggedShape->Draggable())
m_draggedShape->GetEventHandler()->OnBeginDragRight((double)x, (double)y, keys, m_draggedAttachment);
else
{
m_draggedShape = NULL;
OnBeginDragRight((double)x, (double)y, keys);
}
m_oldDragX = x;
m_oldDragY = y;
}
else if (dragging && m_draggedShape && m_dragState == ContinueDraggingRight)
{
// Continue dragging
m_draggedShape->GetEventHandler()->OnDragRight(FALSE, m_oldDragX, m_oldDragY, keys, m_draggedAttachment);
m_draggedShape->GetEventHandler()->OnDragRight(TRUE, (double)x, (double)y, keys, m_draggedAttachment);
m_oldDragX = x;
m_oldDragY = y;
}
else if (event.RightUp() && m_draggedShape && m_dragState == ContinueDraggingRight)
{
m_dragState = NoDragging;
m_checkTolerance = TRUE;
m_draggedShape->GetEventHandler()->OnDragRight(FALSE, m_oldDragX, m_oldDragY, keys, m_draggedAttachment);
m_draggedShape->GetEventHandler()->OnEndDragRight((double)x, (double)y, keys, m_draggedAttachment);
m_draggedShape = NULL;
}
// All following events sent to canvas, not object
else if (dragging && !m_draggedShape && m_dragState == StartDraggingLeft)
{
m_dragState = ContinueDraggingLeft;
OnBeginDragLeft((double)x, (double)y, keys);
m_oldDragX = x;
m_oldDragY = y;
}
else if (dragging && !m_draggedShape && m_dragState == ContinueDraggingLeft)
{
// Continue dragging
OnDragLeft(FALSE, m_oldDragX, m_oldDragY, keys);
OnDragLeft(TRUE, (double)x, (double)y, keys);
m_oldDragX = x;
m_oldDragY = y;
}
else if (event.LeftUp() && !m_draggedShape && m_dragState == ContinueDraggingLeft)
{
m_dragState = NoDragging;
m_checkTolerance = TRUE;
OnDragLeft(FALSE, m_oldDragX, m_oldDragY, keys);
OnEndDragLeft((double)x, (double)y, keys);
m_draggedShape = NULL;
}
else if (dragging && !m_draggedShape && m_dragState == StartDraggingRight)
{
m_dragState = ContinueDraggingRight;
OnBeginDragRight((double)x, (double)y, keys);
m_oldDragX = x;
m_oldDragY = y;
}
else if (dragging && !m_draggedShape && m_dragState == ContinueDraggingRight)
{
// Continue dragging
OnDragRight(FALSE, m_oldDragX, m_oldDragY, keys);
OnDragRight(TRUE, (double)x, (double)y, keys);
m_oldDragX = x;
m_oldDragY = y;
}
else if (event.RightUp() && !m_draggedShape && m_dragState == ContinueDraggingRight)
{
m_dragState = NoDragging;
m_checkTolerance = TRUE;
OnDragRight(FALSE, m_oldDragX, m_oldDragY, keys);
OnEndDragRight((double)x, (double)y, keys);
m_draggedShape = NULL;
}
// Non-dragging events
else if (event.IsButton())
{
m_checkTolerance = TRUE;
// Find the nearest object
int attachment = 0;
wxShape *nearest_object = FindShape(x, y, &attachment);
if (nearest_object) // Object event
{
if (event.LeftDown())
{
m_draggedShape = nearest_object;
m_draggedAttachment = attachment;
m_dragState = StartDraggingLeft;
m_firstDragX = x;
m_firstDragY = y;
}
else if (event.LeftUp())
{
// N.B. Only register a click if the same object was
// identified for down *and* up.
if (nearest_object == m_draggedShape)
nearest_object->GetEventHandler()->OnLeftClick((double)x, (double)y, keys, attachment);
m_draggedShape = NULL;
m_dragState = NoDragging;
}
else if (event.LeftDClick())
{
nearest_object->GetEventHandler()->OnLeftDoubleClick((double)x, (double)y, keys, attachment);
m_draggedShape = NULL;
m_dragState = NoDragging;
}
else if (event.RightDown())
{
m_draggedShape = nearest_object;
m_draggedAttachment = attachment;
m_dragState = StartDraggingRight;
m_firstDragX = x;
m_firstDragY = y;
}
else if (event.RightUp())
{
if (nearest_object == m_draggedShape)
nearest_object->GetEventHandler()->OnRightClick((double)x, (double)y, keys, attachment);
m_draggedShape = NULL;
m_dragState = NoDragging;
}
}
else // Canvas event (no nearest object)
{
if (event.LeftDown())
{
m_draggedShape = NULL;
m_dragState = StartDraggingLeft;
m_firstDragX = x;
m_firstDragY = y;
}
else if (event.LeftUp())
{
OnLeftClick((double)x, (double)y, keys);
m_draggedShape = NULL;
m_dragState = NoDragging;
}
else if (event.RightDown())
{
m_draggedShape = NULL;
m_dragState = StartDraggingRight;
m_firstDragX = x;
m_firstDragY = y;
}
else if (event.RightUp())
{
OnRightClick((double)x, (double)y, keys);
m_draggedShape = NULL;
m_dragState = NoDragging;
}
}
}
}
/*
* Try to find a sensitive object, working up the hierarchy of composites.
*
*/
wxShape *wxShapeCanvas::FindFirstSensitiveShape(double x, double y, int *new_attachment, int op)
{
wxShape *image = FindShape(x, y, new_attachment);
if (!image) return NULL;
wxShape *actualImage = FindFirstSensitiveShape1(image, op);
if (actualImage)
{
double dist;
// Find actual attachment
actualImage->HitTest(x, y, new_attachment, &dist);
}
return actualImage;
}
wxShape *wxShapeCanvas::FindFirstSensitiveShape1(wxShape *image, int op)
{
if (image->GetSensitivityFilter() & op)
return image;
if (image->GetParent())
return FindFirstSensitiveShape1(image->GetParent(), op);
return NULL;
}
// Helper function: TRUE if 'contains' wholly contains 'contained'.
static bool WhollyContains(wxShape *contains, wxShape *contained)
{
double xp1, yp1, xp2, yp2;
double w1, h1, w2, h2;
double left1, top1, right1, bottom1, left2, top2, right2, bottom2;
xp1 = contains->GetX();
yp1 = contains->GetY();
xp2 = contained->GetX();
yp2 = contained->GetY();
contains->GetBoundingBoxMax(&w1, &h1);
contained->GetBoundingBoxMax(&w2, &h2);
left1 = (double)(xp1 - (w1 / 2.0));
top1 = (double)(yp1 - (h1 / 2.0));
right1 = (double)(xp1 + (w1 / 2.0));
bottom1 = (double)(yp1 + (h1 / 2.0));
left2 = (double)(xp2 - (w2 / 2.0));
top2 = (double)(yp2 - (h2 / 2.0));
right2 = (double)(xp2 + (w2 / 2.0));
bottom2 = (double)(yp2 + (h2 / 2.0));
return ((left1 <= left2) && (top1 <= top2) && (right1 >= right2) && (bottom1 >= bottom2));
}
wxShape *wxShapeCanvas::FindShape(double x, double y, int *attachment, wxClassInfo *info, wxShape *notObject)
{
double nearest = 100000.0;
int nearest_attachment = 0;
wxShape *nearest_object = NULL;
// Go backward through the object list, since we want:
// (a) to have the control points drawn LAST to overlay
// the other objects
// (b) to find the control points FIRST if they exist
wxNode *current = GetDiagram()->GetShapeList()->GetLast();
while (current)
{
wxShape *object = (wxShape *)current->GetData();
double dist;
int temp_attachment;
// First pass for lines, which might be inside a container, so we
// want lines to take priority over containers. This first loop
// could fail if we clickout side a line, so then we'll
// try other shapes.
if (object->IsShown() &&
object->IsKindOf(CLASSINFO(wxLineShape)) &&
object->HitTest(x, y, &temp_attachment, &dist) &&
((info == NULL) || object->IsKindOf(info)) &&
(!notObject || !notObject->HasDescendant(object)))
{
// A line is trickier to spot than a normal object.
// For a line, since it's the diagonal of the box
// we use for the hit test, we may have several
// lines in the box and therefore we need to be able
// to specify the nearest point to the centre of the line
// as our hit criterion, to give the user some room for
// manouevre.
if (dist < nearest)
{
nearest = dist;
nearest_object = object;
nearest_attachment = temp_attachment;
}
}
if (current)
current = current->GetPrevious();
}
current = GetDiagram()->GetShapeList()->GetLast();
while (current)
{
wxShape *object = (wxShape *)current->GetData();
double dist;
int temp_attachment;
// On second pass, only ever consider non-composites or divisions. If children want to pass
// up control to the composite, that's up to them.
if (object->IsShown() && (object->IsKindOf(CLASSINFO(wxDivisionShape)) || !object->IsKindOf(CLASSINFO(wxCompositeShape)))
&& object->HitTest(x, y, &temp_attachment, &dist) && ((info == NULL) || object->IsKindOf(info)) &&
(!notObject || !notObject->HasDescendant(object)))
{
if (!object->IsKindOf(CLASSINFO(wxLineShape)))
{
// If we've hit a container, and we have already found a line in the
// first pass, then ignore the container in case the line is in the container.
// Check for division in case line straddles divisions (i.e. is not wholly contained).
if (!nearest_object || !(object->IsKindOf(CLASSINFO(wxDivisionShape)) || WhollyContains(object, nearest_object)))
{
nearest_object = object;
nearest_attachment = temp_attachment;
current = NULL;
}
}
}
if (current)
current = current->GetPrevious();
}
*attachment = nearest_attachment;
return nearest_object;
}
/*
* Higher-level events called by OnEvent
*
*/
void wxShapeCanvas::OnLeftClick(double WXUNUSED(x), double WXUNUSED(y), int WXUNUSED(keys))
{
}
void wxShapeCanvas::OnRightClick(double WXUNUSED(x), double WXUNUSED(y), int WXUNUSED(keys))
{
}
void wxShapeCanvas::OnDragLeft(bool WXUNUSED(draw), double WXUNUSED(x), double WXUNUSED(y), int WXUNUSED(keys))
{
}
void wxShapeCanvas::OnBeginDragLeft(double WXUNUSED(x), double WXUNUSED(y), int WXUNUSED(keys))
{
}
void wxShapeCanvas::OnEndDragLeft(double WXUNUSED(x), double WXUNUSED(y), int WXUNUSED(keys))
{
}
void wxShapeCanvas::OnDragRight(bool WXUNUSED(draw), double WXUNUSED(x), double WXUNUSED(y), int WXUNUSED(keys))
{
}
void wxShapeCanvas::OnBeginDragRight(double WXUNUSED(x), double WXUNUSED(y), int WXUNUSED(keys))
{
}
void wxShapeCanvas::OnEndDragRight(double WXUNUSED(x), double WXUNUSED(y), int WXUNUSED(keys))
{
}
void wxShapeCanvas::AddShape(wxShape *object, wxShape *addAfter)
{
GetDiagram()->AddShape(object, addAfter);
}
void wxShapeCanvas::InsertShape(wxShape *object)
{
GetDiagram()->InsertShape(object);
}
void wxShapeCanvas::RemoveShape(wxShape *object)
{
GetDiagram()->RemoveShape(object);
}
bool wxShapeCanvas::GetQuickEditMode()
{
return GetDiagram()->GetQuickEditMode();
}
void wxShapeCanvas::Redraw(wxDC &dc)
{
GetDiagram()->Redraw(dc);
}
void wxShapeCanvas::Snap(double *x, double *y)
{
GetDiagram()->Snap(x, y);
}
// Returns the current scroll position
wxPoint wxShapeCanvas::GetCurrentPixelScrollPosition()
{
int pixelsPerUnitX, pixelsPerUnitY;
GetScrollPixelsPerUnit(& pixelsPerUnitX, & pixelsPerUnitY);
int posX, posY;
GetViewStart(& posX, & posY);
wxPoint pt(pixelsPerUnitX * posX, pixelsPerUnitY * posY);
return pt;
}