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