1 //////////////////////////////////////////////////////////////////////////
2 //
3 // pgAdmin III - PostgreSQL Tools
4 //
5 // Copyright (C) 2002 - 2016, The pgAdmin Development Team
6 // This software is released under the PostgreSQL Licence
7 //
8 // gqbGraphsimple.cpp - A simple Implementation of the Graphic Interface for GQB
9 //
10 //////////////////////////////////////////////////////////////////////////
11 
12 // App headers
13 #include "pgAdmin3.h"
14 
15 // wxWindows headers
16 #include <wx/wx.h>
17 #include <wx/dcbuffer.h>
18 
19 // App headers
20 #include "utils/sysSettings.h"
21 #include "gqb/gqbGraphSimple.h"
22 #include "gqb/gqbQueryObjs.h"
23 // GQB Images
24 #include "images/gqbColNotSel.pngc"
25 #include "images/gqbColSel.pngc"
26 
gqbGraphSimple()27 gqbGraphSimple::gqbGraphSimple()
28 {
29 	normalFont = settings->GetSystemFont();
30 	TableTitleFont = settings->GetSystemFont();
31 	TableTitleFont.SetWeight(wxFONTWEIGHT_BOLD);
32 	BackgroundLayer1 = wxBrush(wxColour(112, 112, 112), wxSOLID);
33 	BackgroundLayer2 = wxBrush (wxColour(208, 208, 208), wxSOLID);
34 	BackgroundTitle = wxBrush (wxColour(245, 245, 245), wxSOLID);
35 	minTableWidth = 80;
36 	minTableHeight = 54;
37 	rowHeight = 0;                                //By default but this it's replaced by font metrics value
38 	rowLeftMargin = 14;
39 	rowRightMargin = 5;
40 	rowTopMargin = 1;
41 	lineClickThreshold = 7;
42 	selectedPen = wxPen(wxColour(0, 146, 195), 2, wxSOLID);
43 	selectedBrush = wxBrush(wxColour(0, 146, 195), wxSOLID);
44 	imgSelBoxEmpty = *gqbColNotSel_png_bmp;
45 	imgSelBoxSelected = *gqbColSel_png_bmp;
46 
47 }
48 
49 
50 // NOTES:(1) store values of width & height at queryTable.
51 // (2)Need to set a font for the device context before get font metrics with GetTextExtent
drawTable(wxMemoryDC & bdc,wxPoint * origin,gqbQueryObject * queryTable)52 void gqbGraphSimple::drawTable(wxMemoryDC &bdc, wxPoint *origin, gqbQueryObject *queryTable)
53 {
54 
55 #if wxCHECK_VERSION(2, 9, 0)
56 	wxCoord  w = 0, h = 0, height = 0, width = 0, margin = 5;
57 #else
58 	long  w = 0, h = 0, height = 0, width = 0, margin = 5;
59 #endif
60 
61 	// Get Value for row Height
62 	if(!rowHeight)
63 	{
64 		bdc.SetFont(TableTitleFont);
65 		bdc.GetTextExtent(wxT("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxtz"), &w, &h);
66 		rowHeight = h;
67 	}
68 
69 	// Get Title Metrics
70 	bdc.SetFont(TableTitleFont);
71 	height += rowHeight + rowTopMargin;
72 
73 	// Calculate font metrics for table title with/without alias
74 	if(queryTable->getAlias().length() > 0)
75 		bdc.GetTextExtent(queryTable->getName() + wxT(" (") + queryTable->getAlias() + wxT(")"), &w, &h);
76 	else
77 		bdc.GetTextExtent(queryTable->getName(), &w, &h);
78 	width = rowLeftMargin + w + rowRightMargin;
79 
80 	// Get Columns Metrics
81 	bdc.SetFont(normalFont);
82 
83 	// Don't use h value from font metrics to get consistency between columns vertical separation (height)
84 	height += rowHeight * queryTable->parent->countCols() + rowTopMargin * queryTable->parent->countCols();
85 	gqbIteratorBase *iterator = queryTable->parent->createColumnsIterator();
86 	while(iterator->HasNext())
87 	{
88 		gqbColumn *tmp = (gqbColumn *)iterator->Next();
89 		bdc.GetTextExtent(tmp->getName(), &w, &h);
90 		if((rowLeftMargin + w + rowRightMargin) > width)
91 			width = rowLeftMargin + w + rowRightMargin;
92 	}
93 
94 	//Don't delete iterator because will be use below;
95 
96 	// Set table Size in ObjectModel (Temporary Values for object representation,
97 	// and for this reason the view can modified model without using the controller
98 	// because this values are used by controller when use object's size in internal operations)
99 	if( (height + 2) < minTableHeight) // +2 from BackgroundLayers addition
100 	{
101 		queryTable->setHeight(minTableHeight);
102 		height = minTableHeight;
103 	}
104 	else
105 		queryTable->setHeight(height + 2);
106 
107 	if( (width + 2) < minTableWidth)
108 	{
109 		queryTable->setWidth(minTableWidth);
110 		width = minTableWidth;
111 	}
112 	else
113 		queryTable->setWidth(width + 2);
114 
115 	//Decorate Table
116 	bdc.SetPen(*wxTRANSPARENT_PEN);
117 
118 	//draw second Layer
119 	bdc.SetBrush(BackgroundLayer2);
120 	bdc.DrawRectangle(wxRect(wxPoint(origin->x, origin->y), wxSize(width + 2, height + 2)));
121 
122 	//draw third Layer
123 	bdc.SetBrush(BackgroundLayer1);
124 	bdc.DrawRectangle(wxRect(wxPoint(origin->x, origin->y), wxSize(width + 1, height + 1)));
125 
126 	//draw real frame layer
127 	bdc.SetBrush(*wxWHITE_BRUSH);
128 	if(queryTable->getSelected())
129 	{
130 		bdc.SetPen(selectedPen);
131 	}
132 	else
133 	{
134 		bdc.SetPen(*wxBLACK_PEN);
135 	}
136 	bdc.DrawRectangle(wxRect(wxPoint(origin->x, origin->y), wxSize(width, height)));
137 
138 	//draw title layer
139 	bdc.SetBrush(BackgroundTitle);
140 	bdc.DrawRectangle(wxRect(wxPoint(origin->x, origin->y), wxSize(width, rowHeight + rowTopMargin)));
141 	bdc.SetFont(TableTitleFont);
142 	if(queryTable->getAlias().length() > 0)
143 		bdc.DrawText(queryTable->getName() + wxT(" (") + queryTable->getAlias() + wxT(")"), origin->x + margin, origin->y + rowTopMargin);
144 	else
145 		bdc.DrawText(queryTable->getName(), origin->x + margin, origin->y + rowTopMargin);
146 	bdc.SetFont(normalFont);
147 
148 	// GQB-TODO: in a future reuse a little more the iterator creating it inside the Query or Table Object
149 	// and only delete it when delete the query object.
150 
151 	// Draw Columns
152 	height = rowHeight + rowTopMargin;
153 	iterator->ResetIterator();
154 	while(iterator->HasNext())
155 	{
156 		gqbColumn *tmp = (gqbColumn *)iterator->Next();
157 		if(queryTable->existsColumn(tmp))
158 		{
159 			bdc.SetTextForeground(* wxRED);
160 			bdc.SetFont(normalFont);
161 			bdc.DrawBitmap(imgSelBoxSelected, origin->x + 3, origin->y + height, true);
162 		}
163 		else
164 		{
165 			bdc.SetFont(normalFont);
166 			bdc.DrawBitmap(imgSelBoxEmpty, origin->x + 3, origin->y + height, true);
167 		}
168 		bdc.DrawText(tmp->getName(), origin->x + rowLeftMargin, origin->y + height);
169 		bdc.SetTextForeground( *wxBLACK);
170 		height += rowHeight + rowTopMargin;
171 	}
172 	delete iterator;                              //now if delete because it's not needed anymore
173 
174 }
175 
176 
177 // return a column when a user click on a checkbox [0->16] x point
getColumnAtPosition(wxPoint * clickPoint,gqbQueryObject * queryTable,int sensibility)178 gqbColumn *gqbGraphSimple::getColumnAtPosition(wxPoint *clickPoint, gqbQueryObject *queryTable, int sensibility)
179 {
180 	int countCols = queryTable->parent->countCols(), colPos = -1;
181 	if(countCols > 0)                             //exists any column
182 	{
183 		colPos = (clickPoint->y - queryTable->position.y) / (rowHeight + rowTopMargin);
184 	}
185 
186 	int x = clickPoint->x - queryTable->position.x;
187 	if( (x > 0 && x < sensibility) && colPos > 0)
188 
189 		// Because 0 is title
190 		return queryTable->parent->getColumnAtIndex(colPos - 1);
191 	else
192 		return NULL;
193 }
194 
195 
drawTempJoinLine(wxMemoryDC & bdc,wxPoint & origin,wxPoint & end)196 void gqbGraphSimple::drawTempJoinLine(wxMemoryDC &bdc, wxPoint &origin, wxPoint &end)
197 {
198 	wxPoint anchorsUsed = wxPoint(0, 0);
199 
200 	if(origin.x < end.x)
201 	{
202 		anchorsUsed.x = 1;
203 		anchorsUsed.y = -1;
204 	}
205 	else
206 	{
207 		anchorsUsed.x = -1;
208 		anchorsUsed.y = 1;
209 	}
210 
211 	drawJoin(bdc, origin, end, anchorsUsed, true, _equally);
212 }
213 
214 
drawJoin(wxMemoryDC & bdc,wxPoint & origin,wxPoint & dest,wxPoint & anchorUsed,bool selected=false,type_Join joinKind=_equally)215 void gqbGraphSimple::drawJoin(wxMemoryDC &bdc, wxPoint &origin, wxPoint &dest, wxPoint &anchorUsed, bool selected = false, type_Join joinKind = _equally)
216 {
217 	wxPoint origin2 = origin;
218 	wxPoint dest2 = dest;
219 
220 	if(selected)
221 	{
222 		bdc.SetPen(selectedPen);
223 		bdc.SetBrush(selectedBrush);
224 	}
225 	else
226 	{
227 		bdc.SetPen(*wxBLACK_PEN);
228 		bdc.SetBrush(*wxBLACK_BRUSH);
229 	}
230 
231 	// GQB-TODO: optimize this if possible, I know one other can be the same?
232 
233 	// getAnchorsUsed() [-1==left]   [1==right] x->origin y->destination
234 	if(anchorUsed.x == 1)
235 	{
236 		bdc.DrawRectangle(origin.x, origin.y - 4, 8, 8);
237 		origin2.x += 20;
238 	}
239 	else
240 	{
241 		bdc.DrawRectangle(origin.x - 8, origin.y - 4, 8, 8);
242 		origin2.x -= 20;
243 	}
244 
245 	if(anchorUsed.y == 1)
246 	{
247 		bdc.DrawRectangle(dest.x, dest.y - 4, 8, 8);
248 		dest2.x += 20;
249 	}
250 	else
251 	{
252 		bdc.DrawRectangle(dest.x - 8, dest.y - 4, 8, 8);
253 		dest2.x -= 20;
254 	}
255 
256 	bdc.DrawLine(origin, origin2);
257 	bdc.DrawLine(dest, dest2);
258 	bdc.DrawLine(origin2, dest2);
259 
260 	// Draw type of join
261 	switch(joinKind)
262 	{
263 		case _equally:
264 			bdc.DrawText(wxT("="), findLineMiddle(origin2, dest2));
265 			break;
266 		case _lesser:
267 			bdc.DrawText(wxT("<"), findLineMiddle(origin2, dest2));
268 			break;
269 		case _greater:
270 			bdc.DrawText(wxT(">"), findLineMiddle(origin2, dest2));
271 			break;
272 		case _equlesser:
273 			bdc.DrawText(wxT("<="), findLineMiddle(origin2, dest2));
274 			break;
275 		case _equgreater:
276 			bdc.DrawText(wxT(">="), findLineMiddle(origin2, dest2));
277 			break;
278 	};
279 }
280 
281 
282 // Return true if pt click over a threshold of the join, false if not
clickOnJoin(gqbQueryJoin * join,wxPoint & pt,wxPoint & origin,wxPoint & dest)283 bool gqbGraphSimple::clickOnJoin(gqbQueryJoin *join, wxPoint &pt, wxPoint &origin, wxPoint &dest)
284 {
285 
286 	wxPoint origin2 = origin;
287 	wxPoint dest2 = dest;
288 
289 	if(join->getAnchorsUsed().x == 1)
290 	{
291 		origin2.x += 20;
292 	}
293 	else
294 	{
295 		origin2.x -= 20;
296 	}
297 
298 	if(join->getAnchorsUsed().y == 1)
299 	{
300 		dest2.x += 20;
301 	}
302 	else
303 	{
304 		dest2.x -= 20;
305 	}
306 
307 	// Check origin anchor
308 	bool value1 = insideLine(pt, origin, origin2, lineClickThreshold);
309 
310 	// Check dest anchor
311 	bool value2 = insideLine(pt, dest, dest2, lineClickThreshold);
312 
313 	// Check line between both tables
314 	bool value3 = insideLine(pt, origin2, dest2, lineClickThreshold);
315 
316 	if(value1 || value2 || value3)
317 		return true;
318 	else
319 		return false;
320 }
321 
322 
insideLine(wxPoint & pt,wxPoint & p1,wxPoint & p2,int threshold=7)323 bool gqbGraphSimple::insideLine(wxPoint &pt, wxPoint &p1, wxPoint &p2, int threshold = 7)
324 {
325 	bool value = false;
326 	if(distanceToLine(pt, p1, p2) < threshold)
327 	{
328 		value = true;
329 	}
330 	return value;
331 }
332 
333 
findLineMiddle(wxPoint p1,wxPoint p2)334 wxPoint gqbGraphSimple::findLineMiddle(wxPoint p1, wxPoint p2)
335 {
336 	int middleX = -1, middleY = -1;
337 
338 	int dx =  p2.x - p1.x;
339 	if(dx > 0)                                    // p1 at left
340 	{
341 		middleX = dx / 2 + p1.x;
342 	}                                             // p1 at right
343 	else
344 	{
345 		middleX = p1.x + dx / 2;
346 	}
347 
348 	int dy =  p2.y - p1.y;
349 	if(dy > 0)                                    // p1 is above
350 	{
351 		middleY = dy / 2 + p1.y;
352 	}                                             // p1 is below
353 	else
354 	{
355 		middleY = p1.y + dy / 2;
356 	}
357 
358 	if(dy == 0)
359 		middleY = p1.y;
360 	if(dx == 0)
361 		middleX = p1.x;
362 
363 	return wxPoint(middleX, middleY);
364 }
365 
366 
distanceToLine(wxPoint pt,wxPoint p1,wxPoint p2)367 double gqbGraphSimple::distanceToLine(wxPoint pt, wxPoint p1, wxPoint p2)
368 {
369 	p2.x -= p1.x;
370 	p2.y -= p1.y;
371 
372 	pt.x -= p1.x;
373 	pt.y -= p1.y;
374 
375 	double dprod = pt.x * p2.x + pt.y * p2.y;
376 	double pLenSq;
377 
378 	if(dprod <= 0.0)
379 	{
380 		pLenSq = 0.0;
381 	}
382 	else
383 	{
384 		pt.x = p2.x - pt.x;
385 		pt.y = p2.y - pt.y;
386 		dprod = pt.x * p2.x + pt.y * p2.y;
387 		if(dprod <= 0.0)
388 		{
389 			pLenSq = 0.0;
390 		}
391 		else
392 		{
393 			pLenSq = dprod * dprod / (p2.x * p2.x + p2.y * p2.y);
394 		}
395 	}
396 
397 	double lengthSq = pt.x * pt.x + pt.y * pt.y - pLenSq;
398 
399 	if(lengthSq < 0)
400 	{
401 		lengthSq = 0;
402 	}
403 
404 	double length = sqrt(lengthSq);
405 
406 	return length;
407 }
408 
409 
410 // Set the anchors points [source, destination] for a join
calcAnchorPoint(gqbQueryJoin * join)411 void gqbGraphSimple::calcAnchorPoint(gqbQueryJoin *join)
412 {
413 	int index, x, y;
414 	wxPoint use;                                  // [-1==left]   [1==right] x->origin y->destination
415 	int sx = join->getSourceQTable()->position.x;
416 	int sy = join->getSourceQTable()->position.y;
417 	int dx = join->getDestQTable()->position.x;
418 	int dy = join->getDestQTable()->position.y;
419 
420 	// Source
421 	index = join->getSourceQTable()->getColumnIndex(join->getSCol()) + 1;
422 	if(sx < dx)
423 	{
424 		x = sx + join->getSourceQTable()->getWidth();
425 		use.x = 1;
426 	}
427 	else
428 	{
429 		x = sx;
430 		use.x = -1;
431 	}
432 	y = sy + index * (rowHeight + rowTopMargin) + ((rowHeight + rowTopMargin) / 2);
433 	join->setSourceAnchor(wxPoint(x, y));
434 
435 	// Destination
436 	index = join->getDestQTable()->getColumnIndex(join->getDCol()) + 1;
437 	if(dx < sx)
438 	{
439 		x = dx + join->getDestQTable()->getWidth();
440 		use.y = 1;
441 	}
442 	else
443 	{
444 		x = dx;
445 		use.y = -1;
446 	}
447 	y = dy + index * (rowHeight + rowTopMargin) + ((rowHeight + rowTopMargin) / 2);
448 	join->setDestAnchor(wxPoint(x, y));
449 
450 	join->setAnchorsUsed(use);
451 }
452 
453 
454 // Update position of Object in the query if move table & adjust all other items like joins (own & registered)
UpdatePosObject(gqbQueryObject * queryTable,int x,int y,int cursorAdjustment)455 void  gqbGraphSimple::UpdatePosObject(gqbQueryObject *queryTable, int x, int y, int cursorAdjustment)
456 {
457 	x -= cursorAdjustment;                        // Move Pointer to a better Position;
458 	y -= rowHeight / 2;
459 
460 	// Update position of table
461 	// Do not allow table/view moved/repositioned less than (0, 0) cordinates
462 	queryTable->position.x = x > 0 ? x : 0;
463 	queryTable->position.y = y > 0 ? y : 0;
464 
465 	// Update position of anchor points of Joins that origin from this table
466 	if(queryTable->getHaveJoins())
467 	{
468 		gqbIteratorBase *j = queryTable->createJoinsIterator();
469 		while(j->HasNext())
470 		{
471 			gqbQueryJoin *tmp = (gqbQueryJoin *)j->Next();
472 			calcAnchorPoint(tmp);
473 		}
474 		delete j;
475 	}
476 
477 	// Update position of anchor points of Joins that come from others tables
478 	if(queryTable->getHaveRegJoins())
479 	{
480 		gqbIteratorBase *r = queryTable->createRegJoinsIterator();
481 		while(r->HasNext())
482 		{
483 			gqbQueryJoin *tmp = (gqbQueryJoin *)r->Next();
484 			calcAnchorPoint(tmp);
485 		}
486 		delete r;
487 	}
488 }
489 
490 
getTitleRowHeight()491 int gqbGraphSimple::getTitleRowHeight()
492 {
493 	return rowHeight;
494 }
495