////////////////////////////////////////////////////////////////////////// // // pgAdmin III - PostgreSQL Tools // // Copyright (C) 2002 - 2016, The pgAdmin Development Team // This software is released under the PostgreSQL Licence // // gqbView.cpp - View implementation for MVC Pattern of GQB // ////////////////////////////////////////////////////////////////////////// // 1. READ MODEL STATE FROM gqbModel TO CREATE THE GRAPHIC REPRESENTATION OF THE QUERY // 2. USE THE CONTROLLER TO CHANGE THE MODEL WITH THE USER INPUT #include "pgAdmin3.h" // wxWindows headers #include #include #include #include #include // App headers #include "gqb/gqbModel.h" #include "gqb/gqbEvents.h" #include "gqb/gqbViewController.h" #include "gqb/gqbQueryObjs.h" #include "gqb/gqbGraphSimple.h" #include "gqb/gqbViewPanels.h" #include "gqb/gqbObject.h" #include "gqb/gqbObjectCollection.h" // Image #include "images/gqbJoinCursor.pngc" BEGIN_EVENT_TABLE(gqbView, wxScrolledWindow) EVT_SIZE(gqbView::OnSize) EVT_PAINT(gqbView::onPaint) EVT_MOTION(gqbView::onMotion) EVT_LEFT_DOWN(gqbView::onMotion) EVT_RIGHT_DOWN(gqbView::onRightClick) EVT_LEFT_UP(gqbView::onMotion) EVT_LEFT_DCLICK(gqbView::onDoubleClick) EVT_ERASE_BACKGROUND(gqbView::onEraseBackGround) //This erase flicker create by wxStaticText when erasing background but this is not needed EVT_KEY_DOWN(gqbView::OnKeyDown) EVT_MENU(GQB_RMJ_DELETE, gqbView::OnMenuJoinDelete) EVT_MENU(GQB_RMT_DELETE, gqbView::OnMenuTableDelete) EVT_MENU(GQB_RMT_SETALIAS, gqbView::OnMenuTableSetAlias) EVT_MENU(GQB_REFRESH, gqbView::OnRefresh) END_EVENT_TABLE() gqbView::gqbView(wxWindow *gqbParent, ctlAuiNotebook *gridParent, wxSize size, gqbController *controller, gqbModel *model) : wxScrolledWindow(gqbParent, wxID_ANY, wxPoint(201, 0), size, wxHSCROLL | wxVSCROLL | wxBORDER | wxRETAINED) { this->controller = controller; this->model = model; pressed = -1; selected = -1; changeTOpressed = false; canvasSize = size; collectionSelected = NULL; joinSelected = NULL; joinSource = NULL; joinDest = NULL; joinSCol = NULL; joinDCol = NULL; refreshRate = 3; iterator = NULL; mode = pt_normal; joinCursorImage = *gqbJoinCursor_png_img; joinCursor = wxCursor(joinCursorImage); m_rightJoins = NULL; m_rightTables = NULL; m_gqbPopup = NULL; jTempSelected = NULL; cTempSelected = NULL; // Assing kind of join Options joinTypeChoices.Add(wxString(wxT(" = "))); joinTypeChoices.Add(wxString(wxT(" > "))); joinTypeChoices.Add(wxString(wxT(" < "))); joinTypeChoices.Add(wxString(wxT(" >= "))); joinTypeChoices.Add(wxString(wxT(" <= "))); // Assign default graphic behavior [skin of forms inside model] this->graphBehavior = new gqbGraphSimple(); // Create Projection Panel // GQB-TODO: move model to grid panel constructor this->gridTable = new gqbGridProjTable(this->model->getOrderedColumns(), this->model->getColumnsParents(), this->model->getColumnsAlias()); this->projectionPanel = new gqbGridPanel(controller->getTabs(), -1, gridTable); // Create Restrictions Panel this->restrictionsGridTable = new gqbGridRestTable(model->getRestrictions()); this->criteriaPanel = new gqbCriteriaPanel(controller->getTabs(), model, restrictionsGridTable); // Create Joins Panel this->joinsGridTable = new gqbGridJoinTable(this->controller); this->joinsPanel = new gqbJoinsPanel(controller->getTabs(), model, joinsGridTable, controller); // Create Order by Panel this->orderByLGridTable = new gqbGridOrderTable(1, model->getOrdByAvailColumns(), model->getOrdByAvailParents(), NULL); this->orderByRGridTable = new gqbGridOrderTable(2, model->getOrdByColumns(), model->getOrdByParents(), model->getOrdByKind()); this->orderPanel = new gqbOrderPanel(controller->getTabs(), orderByLGridTable, orderByRGridTable); #if !wxCHECK_VERSION(2, 9, 0) // does nothing in 2.9+ SetVirtualSizeHints(size); #endif } gqbView::~gqbView() { if(graphBehavior) delete graphBehavior; if(iterator) delete iterator; if(m_rightTables) delete m_rightTables; if(m_rightJoins) delete m_rightJoins; if (m_gqbPopup) delete m_gqbPopup; if(orderByRGridTable) delete orderByRGridTable; if(orderByLGridTable) delete orderByLGridTable; } // Overwrite and disable onEraseBackground Event to avoid Flicker void gqbView::onEraseBackGround(wxEraseEvent &event) { } // Detect when should be drawn the canvas with the model information void gqbView::onPaint(wxPaintEvent &event) { wxPaintDC dcc(this); // Prepare Context for Buffered Draw wxBufferedDC dc(&dcc, canvasSize); drawAll(dc, true); // Call Function to draw all } // GQB-TODO: remove all possible modification to model from here to controller. void gqbView::onRightClick(wxMouseEvent &event) { // GQB-TODO: Validate Alias gqbObject *anySelected = NULL; wxPoint pdc = event.GetPosition(); pdc.x = event.GetPosition().x; pdc.y = event.GetPosition().y; this->CalcUnscrolledPosition(pdc.x, pdc.y, &pdc.x, &pdc.y); anySelected = controller->getModelSelected(pdc, cTempSelected, jTempSelected, false); if(anySelected) { if(anySelected->getType() == GQB_QUERYOBJ) { if(!m_rightTables) { m_rightTables = new wxMenu; m_rightTables->Append(GQB_RMT_SETALIAS, _("&Set Alias for table")); m_rightTables->Append(GQB_RMT_DELETE, _("&Delete Table")); m_rightTables->AppendSeparator(); m_rightTables->Append(GQB_REFRESH, _("&Refresh")); } cTempSelected = (gqbQueryObject *) (gqbObjectCollection *) anySelected; jTempSelected = NULL; PopupMenu(m_rightTables, event.GetPosition()); } if(anySelected->getType() == GQB_JOIN) { if(!m_rightJoins) { m_rightJoins = new wxMenu; m_rightJoins->Append(GQB_RMJ_DELETE, _("&Delete Join")); m_rightJoins->AppendSeparator(); m_rightJoins->Append(GQB_REFRESH, _("&Refresh")); } cTempSelected = NULL; jTempSelected = (gqbQueryJoin *) anySelected;; PopupMenu(m_rightJoins, event.GetPosition()); } } else { if(!m_gqbPopup) { m_gqbPopup = new wxMenu; m_gqbPopup->Append(GQB_REFRESH, _("&Refresh")); } PopupMenu(m_gqbPopup, event.GetPosition()); } } void gqbView::OnMenuJoinDelete(wxCommandEvent &WXUNUSED(event)) { if(jTempSelected) { this->joinsGridTable->removeJoin(jTempSelected); controller->removeJoin(jTempSelected); jTempSelected = NULL; this->Refresh(); } } void gqbView::OnMenuTableDelete(wxCommandEvent &WXUNUSED(event)) { if(cTempSelected) { joinsGridTable->removeJoins(cTempSelected); controller->removeTableFromModel(cTempSelected, gridTable, orderByLGridTable, orderByRGridTable); cTempSelected = NULL; this->Refresh(); } } void gqbView::OnMenuTableSetAlias(wxCommandEvent &event) { if(cTempSelected) { // Because a bug that scrolled automatically the panel of the view if this dialog is called, then assign // as his parent the main container of the view, and void the bug wxTextEntryDialog dialog(controller->getDialogParent(), wxString::Format(_("Enter an alias for table %s"), cTempSelected->getName().c_str()), _("Please enter an alias for the table."), wxT(""), wxOK | wxCANCEL | wxCENTRE); dialog.SetValue(cTempSelected->getAlias()); if (dialog.ShowModal() == wxID_OK) { cTempSelected->setAlias(dialog.GetValue()); joinsPanel->Refresh(); } cTempSelected = NULL; this->Refresh(); } } void gqbView::onDoubleClick(wxMouseEvent &event) { // GQB-TODO: Validate Alias gqbObject *anySelected = NULL; wxPoint pdc = event.GetPosition(); pdc.x = event.GetPosition().x; pdc.y = event.GetPosition().y; this->CalcUnscrolledPosition(pdc.x, pdc.y, &pdc.x, &pdc.y); anySelected = controller->getModelSelected(pdc, cTempSelected, jTempSelected, false); if(anySelected) { if(anySelected->getType() == GQB_QUERYOBJ) { gqbQueryObject *t = (gqbQueryObject *) (gqbObjectCollection *) anySelected; // Because a bug that scrolled automatically the panel of the view if this dialog is called, then assign // as his parent the main container of the view, and void the bug wxTextEntryDialog dialog(controller->getDialogParent(), wxString::Format(_("Enter an alias for table %s"), t->getName().c_str()), _("Please enter an alias for the table."), wxT(""), wxOK | wxCANCEL | wxCENTRE); dialog.SetValue(t->getAlias()); if (dialog.ShowModal() == wxID_OK) { t->setAlias(dialog.GetValue()); joinsPanel->Refresh(); // hack to avoid misplaced joins anchors after insert an alias that trigger a table graph resize (bigger) this->Refresh(); this->Update(); //force refresh graphBehavior->UpdatePosObject(t, t->position.x, t->position.y, 0); } } else if(anySelected->getType() == GQB_JOIN) { gqbQueryJoin *j = (gqbQueryJoin *) anySelected; controller->getTabs()->ChangeSelection(ti_joinsPanel); gqbJoinsPanel *jPanel = wxDynamicCast( joinsPanel, gqbJoinsPanel ); jPanel->selectJoin(j); } } this->Refresh(); } // Manages user input [Mouse click, drag & drop] over the Canvas void gqbView::onMotion(wxMouseEvent &event) { static int refresh = 1; // refresh counter, everytime this values reaches // "refreshRate" value then Refresh while dragging // Discover area where event ocurrs pos.x = event.GetPosition().x; pos.y = event.GetPosition().y; this->CalcUnscrolledPosition(pos.x, pos.y, &pos.x, &pos.y); gqbObject *anySelected = NULL; // Button Down Event is triggered if(event.ButtonDown() && !changeTOpressed) { this->SetFocus(); // Which kind of button down was? join creation [click on any column at the // right of checkbox and drag & drop] or table moving [click on title and drag & drop] anySelected = controller->getModelSelected(pos, collectionSelected, joinSelected, false); if(anySelected) { // Anything before just forget about it changeTOpressed = false; joinSource = NULL; joinSCol = NULL; joinDCol = NULL; joinDest = NULL; jpos.x = 0; jpos.y = 0; if(anySelected->getType() == GQB_QUERYOBJ) { gqbQueryObject *t = (gqbQueryObject *) (gqbObjectCollection *) anySelected; // If click on the title area AND don't click on the columns selection checkbox if( (pos.y - t->position.y <= graphBehavior->getTitleRowHeight())) controller->setPointerMode(pt_normal); else if(pos.x - t->position.x <= 17) controller->setPointerMode(pt_normal); else controller->setPointerMode(pt_join); } } else { anySelected = NULL; mode = pt_normal; } if(mode == pt_normal) // pointer is used to move tables & select/unselect columns { // getSelected Item [Mark it as selected if possible] anySelected = controller->getModelSelected(pos, collectionSelected, joinSelected, true); changeTOpressed = true; // Do conversion of type object if any found if(anySelected) { if(anySelected->getType() == GQB_QUERYOBJ) { collectionSelected = (gqbQueryObject *) (gqbObjectCollection *) anySelected; joinSelected = NULL; } else if(anySelected->getType() == GQB_JOIN) { joinSelected = (gqbQueryJoin *) anySelected; collectionSelected = NULL; } } else { collectionSelected = NULL; joinSelected = NULL; } if(!collectionSelected) { // none selected temp unselect all items controller->unsetModelSelected(true); } else { gqbColumn *col = graphBehavior->getColumnAtPosition(&pos, collectionSelected); if(col) { // Add or remove column from model & observers (ex: Grid) (projection part of SQL sentence) controller->processColumnInModel(collectionSelected, col, gridTable); } } if(!joinSelected) { controller->unsetModelSelected(false); } } // Pointer is used to add joins else if(mode == pt_join) { anySelected = controller->getModelSelected(pos, collectionSelected, joinSelected, false); // Even if I get an object check that it isn't a join if( (anySelected) && anySelected->getType() == GQB_QUERYOBJ) joinSource = (gqbQueryObject *)(gqbObjectCollection *) anySelected; else joinSource = NULL; if(!joinSource) { // creation of join starts joinSCol = NULL; joinDCol = NULL; jpos.x = 0; jpos.y = 0; } else { joinSCol = graphBehavior->getColumnAtPosition(&pos, joinSource, joinSource->getWidth()); jpos = pos; // GQB-TODO then draw line between column & pointer } } this->Refresh(); } // Button Up Event is triggered if(event.ButtonUp()) { // Pointer is used to move tables & select/unselect columns if(mode == pt_normal) { changeTOpressed = false; anySelected = controller->getModelSelected(pos, collectionSelected, joinSelected, false); if (anySelected && anySelected->getType() == GQB_JOIN) { gqbJoinsPanel *jPanel = wxDynamicCast( joinsPanel, gqbJoinsPanel ); jPanel->selectJoin((gqbQueryJoin *)anySelected); } } // Pointer is used to add joins else if(mode == pt_join) { anySelected = controller->getModelSelected(pos, collectionSelected, joinSelected, false); // Even if I get an object check that it isn't a join if( (anySelected) && anySelected->getType() == GQB_QUERYOBJ) { joinDest = (gqbQueryObject *)(gqbObjectCollection *) anySelected; // Validate not self joins [in this version tables can be duplicated to create same effect] if(joinDest == joinSource) { joinDest = NULL; } } else joinDest = NULL; // Creation of join starts if(!joinDest) { joinSource = NULL; joinSCol = NULL; joinDCol = NULL; joinDest = NULL; jpos.x = 0; jpos.y = 0; } else { joinDCol = graphBehavior->getColumnAtPosition(&pos, joinDest, joinDest->getWidth()); if(joinDCol) { // GQB-TODO: Allow other type of joins gqbQueryJoin *qj = controller->addJoin(joinSource, joinSCol, joinDest, joinDCol, _equally); graphBehavior->calcAnchorPoint(qj); this->joinsGridTable->AppendJoin(qj); } // Let the temporary join line to be draw again [Don't destroy anything because all object where own by other objects this are just pointers] joinSource = NULL; joinSCol = NULL; joinDest = NULL; joinDCol = NULL; jpos.x = 0; jpos.y = 0; } } controller->setPointerMode(pt_normal); //when button is up, pointer mode should be only normal this->Refresh(); } // Mouse is Dragged while mouse button is down if (event.Dragging() && pressed) { if(mode == pt_normal) { if(collectionSelected) { // GQB-TODO: same as gqbGraphBehavior.h [find a way to not hard code the 17 default value] if((pos.x > collectionSelected->position.x + 17) || (pos.x < collectionSelected->position.x) ) { graphBehavior->UpdatePosObject(collectionSelected, pos.x, pos.y, 40); } // Don't draw too much when dragging table around canvas [lower cpu use] if(refresh % refreshRate == 0) { this->Refresh(); refresh = 1; } else refresh++; } } else if(mode == pt_join) { if(joinSource && !joinDest) { this->Refresh(); } } } } void gqbView::OnKeyDown(wxKeyEvent &event) { if(event.GetKeyCode() == WXK_DELETE) { if(collectionSelected) { this->joinsGridTable->removeJoins(collectionSelected); controller->removeTableFromModel(collectionSelected, gridTable, orderByLGridTable, orderByRGridTable); collectionSelected = NULL; this->Refresh(); } if(joinSelected) { this->joinsGridTable->removeJoin(joinSelected); controller->removeJoin(joinSelected); joinSelected = NULL; this->Refresh(); } } } void gqbView::drawAll(wxMemoryDC &bdc, bool adjustScrolling) { bdc.Clear(); if(!iterator) // Get an iterator for the objects (tables/views) in the model. iterator = this->model->createQueryIterator(); else iterator->ResetIterator(); // First Draw Tables while(iterator->HasNext()) { gqbQueryObject *tmp = (gqbQueryObject *)iterator->Next(); wxPoint pt = wxPoint(tmp->position); // Use a copy because I don't want to store the modified // version of point after CalcScrolledPosition was called if (adjustScrolling) { // adjust coordinates this->CalcScrolledPosition(pt.x, pt.y, &pt.x, &pt.y); } graphBehavior->drawTable(bdc, &pt, tmp); // graph table } // Later Draw Joins over Tables iterator->ResetIterator(); while(iterator->HasNext()) { gqbQueryObject *tmp = (gqbQueryObject *)iterator->Next(); if(tmp->getHaveJoins()) { gqbIteratorBase *joinsIterator = tmp->createJoinsIterator(); while(joinsIterator->HasNext()) { gqbQueryJoin *join = (gqbQueryJoin *) joinsIterator->Next(); wxPoint o = join->getSourceAnchor(); wxPoint d = join->getDestAnchor(); if (adjustScrolling) { // adjust coordinates origin this->CalcScrolledPosition(o.x, o.y, &o.x, &o.y); // adjust coordinates destination this->CalcScrolledPosition(d.x, d.y, &d.x, &d.y); } graphBehavior->drawJoin(bdc, o, d, join->getAnchorsUsed(), join->getSelected(), join->getKindofJoin()); } delete joinsIterator; } } // This iterator is delete at destroyer for reuse purposes if(joinSource) { // Draw temporary line while creating a join wxPoint source = jpos; wxPoint destination = pos; if(adjustScrolling) { this->CalcScrolledPosition(source.x, source.y, &source.x, &source.y); this->CalcScrolledPosition(destination.x, destination.y, &destination.x, &destination.y); } graphBehavior->drawTempJoinLine(bdc, source, destination); } } void gqbView::setPointerMode(pointerMode pm) { mode = pm; if(mode == pt_join) this->SetCursor(joinCursor); else this->SetCursor(wxNullCursor); } bool gqbView::clickOnJoin (gqbQueryJoin *join, wxPoint &pt, wxPoint &origin, wxPoint &dest) { return graphBehavior->clickOnJoin(join, pt, origin, dest); } void gqbView::emptyPanelsData() { gridTable->emptyTableData(); this->joinsGridTable->emptyTableData(); } void gqbView::newTableAdded(gqbQueryObject *item) { // Refresh Order By Panel's Left Grid if (orderByLGridTable->GetView() ) { wxGridTableMessage msg( orderByLGridTable, wxGRIDTABLE_NOTIFY_ROWS_INSERTED, orderByLGridTable->GetNumberRows() - 1, item->parent->countCols() ); orderByLGridTable->GetView()->ProcessTableMessage( msg ); } } void gqbView::updateTable(gqbQueryObject *queryTable) { if (queryTable->getHaveJoins()) { gqbIteratorBase *j = queryTable->createJoinsIterator(); while (j->HasNext()) { gqbQueryJoin *tmp = (gqbQueryJoin *)j->Next(); graphBehavior->calcAnchorPoint(tmp); } delete j; } // Update position of anchor points of Joins that come from others tables if (queryTable->getHaveRegJoins()) { gqbIteratorBase *r = queryTable->createRegJoinsIterator(); while (r->HasNext()) { gqbQueryJoin *tmp = (gqbQueryJoin *)r->Next(); graphBehavior->calcAnchorPoint(tmp); } delete r; } this->Refresh(); } void gqbView::OnRefresh(wxCommandEvent &ev) { updateModelSize(NULL, true); this->Update(); } /* * updateModelSize * - Update the model size. * - Calculate the maximum width and maximum height of the model * * When removed a table/view from model, the obj parameter must be null, * and update parameter should be true, otherwise update parameter should * be false (Dragging event) */ void gqbView::updateModelSize(gqbQueryObject *obj, bool updateAnyWay) { static int callCount = 0; callCount++; if (!obj) { // Do not update model size, everytime it gets called // Update the size once in 10 times // Update the size only if update flag is true if (callCount < 10 && !updateAnyWay) return; callCount = 0; // Figure out the actual model size. // Remove table int w = 0, h = 0, maxW = 0, maxH = 0; if(!iterator) // Get an iterator for the objects (tables/views) in the model. iterator = this->model->createQueryIterator(); else iterator->ResetIterator(); while (iterator->HasNext()) { gqbQueryObject *tmp = (gqbQueryObject *)iterator->Next();; w = tmp->position.x + tmp->getWidth(); h = tmp->position.y + tmp->getHeight(); if (maxW < w) maxW = w; if (maxH < h) maxH = h; } // Reset Model size modelSize.Set(maxW, maxH); } else { int w = 0, h = 0; w = obj->position.x + obj->getWidth(); h = obj->position.y + obj->getHeight(); if (w > modelSize.GetWidth()) modelSize.SetWidth(w); if (h > modelSize.GetHeight()) modelSize.SetHeight(h); } bool updateView = false; int viewW, viewH; GetSize(&viewW, &viewH); if (viewW < GQB_MIN_WIDTH) viewW = GQB_MIN_WIDTH; if (viewH < GQB_MIN_HEIGHT) viewH = GQB_MIN_HEIGHT; if ((modelSize.GetWidth() > viewW || canvasSize.GetWidth() > viewW) && modelSize.GetWidth() != canvasSize.GetWidth()) { canvasSize.SetWidth((modelSize.GetWidth() > viewW ? modelSize.GetWidth() : viewW)); updateView = true; } if ((modelSize.GetHeight() > viewH || canvasSize.GetHeight() > viewH ) && modelSize.GetHeight() != canvasSize.GetHeight()) { canvasSize.SetHeight((modelSize.GetHeight() > viewH ? modelSize.GetHeight() : viewH)); updateView = true; } if (canvasSize.GetWidth() < viewW) { canvasSize.SetWidth(viewW); updateView = true; } if (canvasSize.GetHeight() < viewH) { canvasSize.SetHeight(viewH); updateView = true; } if (updateView) { SetVirtualSize(canvasSize); } FitInside(); Refresh(); } void gqbView::OnSize(wxSizeEvent &event) { updateModelSize(NULL, true); } bool gqbView::canSaveAsImage() { updateModelSize(NULL, true); return !(modelSize.GetWidth() == 0 || modelSize.GetHeight() == 0); } void gqbView::SaveAsImage(const wxString &fileName, wxBitmapType imgType) { updateModelSize(NULL, true); if (modelSize.GetWidth() == 0 || modelSize.GetHeight() == 0) { wxMessageBox(_("Nothing to be saved!"), _("Save As an image"), wxOK | wxICON_INFORMATION); return; } int width = 0, height = 0; GetVirtualSize(&width, &height); /* * Create the bitmap from the Explain window */ wxMemoryDC memDC; wxBitmap tempBitmap(width, height); memDC.SelectObject(tempBitmap); memDC.Clear(); // Draw the diagram on the bitmap (Memory Device Context) drawAll(memDC, false); memDC.SelectObject(wxNullBitmap); if (!tempBitmap.SaveFile(fileName, imgType)) { wxLogError(_("Could not write the file %s: Errcode=%d."), fileName.c_str(), wxSysErrorCode()); } }