int SELECTION_TOOL::Main( const TOOL_EVENT& aEvent ) { // Main loop: keep receiving events while( OPT_TOOL_EVENT evt = Wait() ) { // Should selected items be added to the current selection or // become the new selection (discarding previously selected items) m_additive = evt->Modifier( MD_SHIFT ); // single click? Select single object if( evt->IsClick( BUT_LEFT ) ) { if( evt->Modifier( MD_CTRL ) && !m_editModules ) { highlightNet( evt->Position() ); } else { if( !m_additive ) clearSelection(); selectCursor( evt->Position() ); } } // right click? if there is any object - show the context menu else if( evt->IsClick( BUT_RIGHT ) ) { bool emptySelection = m_selection.Empty(); if( emptySelection ) selectCursor( evt->Position() ); CONTEXT_MENU& contextMenu = m_menu.Generate( m_selection ); if( contextMenu.GetMenuItemCount() > 0 ) SetContextMenu( &contextMenu, CMENU_NOW ); m_preliminary = emptySelection; } // double click? Display the properties window else if( evt->IsDblClick( BUT_LEFT ) ) { if( m_selection.Empty() ) selectCursor( evt->Position() ); m_toolMgr->RunAction( COMMON_ACTIONS::properties ); } // drag with LMB? Select multiple objects (or at least draw a selection box) or drag them else if( evt->IsDrag( BUT_LEFT ) ) { if( m_additive ) { m_preliminary = false; selectMultiple(); } else if( m_selection.Empty() ) { m_preliminary = false; // There is nothing selected, so try to select something if( !selectCursor( getView()->ToWorld( getViewControls()->GetMousePosition() ), false ) ) { // If nothings has been selected or user wants to select more // draw the selection box selectMultiple(); } else { m_toolMgr->InvokeTool( "pcbnew.InteractiveEdit" ); } } else { // Check if dragging has started within any of selected items bounding box if( selectionContains( evt->Position() ) ) { // Yes -> run the move tool and wait till it finishes m_toolMgr->InvokeTool( "pcbnew.InteractiveEdit" ); } else { // No -> clear the selection list clearSelection(); } } } else if( evt->IsAction( &COMMON_ACTIONS::selectionCursor ) ) { // GetMousePosition() is used, as it is independent of snapping settings selectCursor( getView()->ToWorld( getViewControls()->GetMousePosition() ) ); } else if( evt->IsAction( &COMMON_ACTIONS::find ) ) { find( *evt ); } else if( evt->IsAction( &COMMON_ACTIONS::findMove ) ) { findMove( *evt ); } else if( evt->IsAction( &COMMON_ACTIONS::selectItem ) ) { SelectItem( *evt ); } else if( evt->IsAction( &COMMON_ACTIONS::unselectItem ) ) { UnselectItem( *evt ); } else if( evt->IsCancel() || evt->Action() == TA_UNDO_REDO || evt->IsAction( &COMMON_ACTIONS::selectionClear ) ) { clearSelection(); } else if( evt->IsAction( &COMMON_ACTIONS::selectConnection ) ) { selectConnection( *evt ); } else if( evt->IsAction( &COMMON_ACTIONS::selectCopper ) ) { selectCopper( *evt ); } else if( evt->IsAction( &COMMON_ACTIONS::selectNet ) ) { selectNet( *evt ); } else if( evt->Action() == TA_CONTEXT_MENU_CLOSED ) { if( m_preliminary ) clearSelection(); } } // This tool is supposed to be active forever assert( false ); return 0; }
/* * TODO: * ==== * - display value of a port (nice to have) * - triangle port (nice to have) * - mouse over (over a node or port) (nice to have) * - round shaped nodes (nice to have) */ namespace Magus { //****************************************************************************/ QtNodeEditor::QtNodeEditor(QWidget* parent) : QWidget(parent) { QVBoxLayout* mainLayout = new QVBoxLayout; mView = new QGraphicsView(this); mScene = new QtNodeGraphicsScene(); mScene->installEventFilter(this); mView->setScene(mScene); mView->setRenderHint(QPainter::Antialiasing, true); mView->setInteractive(true); mView->setMouseTracking(true); mainLayout->addWidget(mView); mLastRemovedNode = 0; mRubberBand = 0; mZoom = 1.0f; mFisheyeViewEnabled = false; mFisheyeMaxZoom = 1.0f; mFisheyeSteps = 5; mHeaderTitleIcon = NODE_HEADER_COMPOUND_ICON; mAction1Icon = NODE_HEADER_ACTION1_ICON; mAction2Icon = NODE_HEADER_ACTION2_ICON; mCompoundNodeDropped = 0; mRubberbandSelection = false; mContextMenuEnabled = true; mContextMenu = new QMenu(this); mContextMenu->addAction(new QAction(NODE_ACTION_DELETE, this)); mContextMenu->addAction(new QAction(NODE_ACTION_CENTER, this)); mZoomSubMenu = mContextMenu->addMenu(NODE_ACTION_ZOOM); QAction* action; QActionGroup actionGroupZoom(mZoomSubMenu); actionGroupZoom.setExclusive(true); action = new QAction(NODE_ACTION_ZOOM_10, this); action->setCheckable(true); actionGroupZoom.addAction(action); action = new QAction(NODE_ACTION_ZOOM_25, this); action->setCheckable(true); actionGroupZoom.addAction(action); action = new QAction(NODE_ACTION_ZOOM_50, this); action->setCheckable(true); actionGroupZoom.addAction(action); action = new QAction(NODE_ACTION_ZOOM_75, this); action->setCheckable(true); actionGroupZoom.addAction(action); action = new QAction(NODE_ACTION_ZOOM_90, this); action->setCheckable(true); actionGroupZoom.addAction(action); action = new QAction(NODE_ACTION_ZOOM_100, this); action->setCheckable(true); action->setChecked(true); actionGroupZoom.addAction(action); action = new QAction(NODE_ACTION_ZOOM_150, this); action->setCheckable(true); actionGroupZoom.addAction(action); action = new QAction(NODE_ACTION_ZOOM_200, this); action->setCheckable(true); actionGroupZoom.addAction(action); action = new QAction(NODE_ACTION_ZOOM_250, this); action->setCheckable(true); actionGroupZoom.addAction(action); action = new QAction(NODE_ACTION_ZOOM_300, this); action->setCheckable(true); actionGroupZoom.addAction(action); mZoomSubMenu->addActions(actionGroupZoom.actions()); mFisheyeViewSubMenu = mContextMenu->addMenu(NODE_ACTION_FISHEY_VIEW); QActionGroup actionGroupFisheye(mFisheyeViewSubMenu); actionGroupFisheye.setExclusive(true); action = new QAction(NODE_ACTION_FISHEYE_DISABLED, this); action->setCheckable(true); action->setChecked(true); actionGroupFisheye.addAction(action); action = new QAction(NODE_ACTION_FISHEYE_NORMAL, this); action->setCheckable(true); actionGroupFisheye.addAction(action); action = new QAction(NODE_ACTION_FISHEYE_NORMAL_SUBTLE, this); action->setCheckable(true); actionGroupFisheye.addAction(action); action = new QAction(NODE_ACTION_FISHEYE_LARGE, this); action->setCheckable(true); actionGroupFisheye.addAction(action); action = new QAction(NODE_ACTION_FISHEYE_LARGE_SUBTLE, this); action->setCheckable(true); actionGroupFisheye.addAction(action); mFisheyeViewSubMenu->addActions(actionGroupFisheye.actions()); mContextMenu->addAction(new QAction(NODE_ACTION_SELECTED_TO_COMPOUND, this)); mContextMenu->addAction(new QAction(NODE_ACTION_COLLAPSE_ALL, this)); mContextMenu->addAction(new QAction(NODE_ACTION_EXPAND_ALL, this)); mContextMenu->addAction(new QAction(NODE_ACTION_EXPAND_COMPOUNDS, this)); mContextMenu->addAction(new QAction(NODE_ACTION_CENTER, this)); setMenuZoomEnabled(true); setMenuSelectionToCompoundEnabled(true); setMenuCollapseExpandEnabled(true); setMenuExpandCompoundsEnabled(true); setMenuFisheyeViewEnabled(true); setContextMenuPolicy(Qt::CustomContextMenu); connect(mContextMenu, SIGNAL(triggered(QAction*)), this, SLOT(contextMenuItemSelected(QAction*))); setLayout(mainLayout); } //****************************************************************************/ QtNodeEditor::~QtNodeEditor(void) { } //****************************************************************************/ void QtNodeEditor::setContextMenuEnabled(bool enabled) { mContextMenuEnabled = enabled; } //****************************************************************************/ void QtNodeEditor::setCompoundHeaderTitleIcon(const QString& fileNameIcon) { mHeaderTitleIcon = fileNameIcon; } //****************************************************************************/ void QtNodeEditor::setCompoundAction1Icon(const QString& fileNameIcon) { mAction1Icon = fileNameIcon; } //****************************************************************************/ void QtNodeEditor::setCompoundAction2Icon(const QString& fileNameIcon) { mAction2Icon = fileNameIcon; } //****************************************************************************/ bool QtNodeEditor::isContextMenuEnabled(void) { return mContextMenuEnabled; } //****************************************************************************/ void QtNodeEditor::setMenuZoomEnabled(bool enabled) { mMenuZoomEnabled = enabled; QAction* action = getActionFromContextMenu(NODE_ACTION_ZOOM); if (action) action->setVisible(enabled); } //****************************************************************************/ bool QtNodeEditor::isMenuZoomEnabled(void) { return mMenuZoomEnabled; } //****************************************************************************/ void QtNodeEditor::setMenuSelectionToCompoundEnabled(bool enabled) { mMenuSelectionToCompoundEnabled = enabled; QAction* action = getActionFromContextMenu(NODE_ACTION_SELECTED_TO_COMPOUND); if (action) action->setVisible(enabled); } //****************************************************************************/ bool QtNodeEditor::isMenuSelectionToCompoundEnabled(void) { return mMenuSelectionToCompoundEnabled; } //****************************************************************************/ void QtNodeEditor::setMenuCollapseExpandEnabled(bool enabled) { mMenuCollapseExpandEnabled = enabled; QAction* action = getActionFromContextMenu(NODE_ACTION_EXPAND_ALL); if (action) action->setVisible(enabled); action = getActionFromContextMenu(NODE_ACTION_COLLAPSE_ALL); if (action) action->setVisible(enabled); } //****************************************************************************/ bool QtNodeEditor::isMenuCollapseExpandEnabled(void) { return mMenuCollapseExpandEnabled; } //****************************************************************************/ void QtNodeEditor::setMenuExpandCompoundsEnabled(bool enabled) { mMenuExpandCompoundsEnabled = enabled; QAction* action = getActionFromContextMenu(NODE_ACTION_EXPAND_COMPOUNDS); if (action) action->setVisible(enabled); } //****************************************************************************/ bool QtNodeEditor::isMenuExpandCompoundsEnabled(void) { return mMenuExpandCompoundsEnabled; } //****************************************************************************/ void QtNodeEditor::setMenuFisheyeViewEnabled(bool enabled) { mMenuFisheyeViewEnabled = enabled; QAction* action = getActionFromContextMenu(NODE_ACTION_FISHEY_VIEW); if (action) action->setVisible(enabled); } //****************************************************************************/ bool QtNodeEditor::isMenuFisheyeViewEnabled(void) { return mMenuFisheyeViewEnabled; } //****************************************************************************/ void QtNodeEditor::setFisheyeView(bool enabled, qreal maxZoom, unsigned int steps) { mFisheyeViewEnabled = enabled; mFisheyeMaxZoom = maxZoom; mFisheyeSteps = steps; } //****************************************************************************/ QGraphicsItem* QtNodeEditor::itemAtExceptActiveConnection(const QPointF& pos) { QList<QGraphicsItem*> items = mScene->items(QRectF(pos - QPointF(1,1), QSize(3,3))); bool isActive = isActiveConnection(); foreach(QGraphicsItem* item, items) { // If there is an active connection, it is not returned as a selected item // Finalized (established) connections are returned if (item->isVisible()) { if (isConnection(item)) { if (!isActive) return item; } else return item; } } return 0; } //****************************************************************************/ QtCompoundNode* QtNodeEditor::nodeOverCompound(QtNode* node) { if (!node) return 0; QtCompoundNode* compound; QList<QGraphicsItem*> items = mScene->items(); int subType; qreal halfWidth; foreach(QGraphicsItem* item, items) { if (isCompoundNode(item) && item->isVisible()) { compound = static_cast<QtCompoundNode*>(item); if (node != compound && !compound->isSelected()) { halfWidth = 0.5 * compound->getWidth(); if (node->scenePos().x() > compound->scenePos().x() - halfWidth && node->scenePos().x() < compound->scenePos().x() + halfWidth && node->scenePos().y() > compound->scenePos().y() && node->scenePos().y() < compound->scenePos().y() + compound->getHeigth()) { return compound; } } } } return 0; } //****************************************************************************/ bool QtNodeEditor::eventFilter(QObject* object, QEvent* event) { QGraphicsSceneMouseEvent* mouseEvent = (QGraphicsSceneMouseEvent*) event; QGraphicsItem* item = 0; switch ((int) event->type()) { case QEvent::GraphicsSceneMousePress: mouseClickHandler(mouseEvent); break; case QEvent::GraphicsSceneMouseDoubleClick: mouseDoubleClickHandler(mouseEvent); break; case QEvent::GraphicsSceneMouseMove: { mouseMoveHandler(mouseEvent); } break; case QEvent::GraphicsSceneMouseRelease: { mouseReleaseHandler(mouseEvent); } break; } return QObject::eventFilter(object, event); } //****************************************************************************/ bool QtNodeEditor::mouseClickHandler(QGraphicsSceneMouseEvent* mouseEvent) { switch ((int) mouseEvent->button()) { case Qt::LeftButton: { QGraphicsItem* item = itemAtExceptActiveConnection(mouseEvent->scenePos()); if (!item) { // Left-click on the canvas, but no item clicked, so deselect nodes and connections deselectAll(); mRubberbandSelection = true; mLastMousePosition.setX(mouseEvent->lastScenePos().x()); mLastMousePosition.setY(mouseEvent->lastScenePos().y()); return true; } mRubberbandSelection = false; // Delegate to the node; either the node itself is clicked, one of its children or a connection QtNode* node; int type = 0; if (item->data(NODE_KEY_GRAPHIC_ITEM_TYPE).isValid()) { type = item->data(NODE_KEY_GRAPHIC_ITEM_TYPE).toInt(); if (NODE_VALUE_TYPE_CONNECTION == type) { // ======================= Handle selected connection ======================= QtConnection* connection = static_cast<QtConnection*>(item); selectConnection(connection); } else if (NODE_VALUE_TYPE_NODE == type) { // ======================= The node itself is clicked ======================= node = static_cast<QtNode*>(item); selectNode(node, mouseEvent); } else if (NODE_VALUE_TYPE_HEADER_ICON == type || NODE_VALUE_TYPE_HEADER_TITLE == type) { // ======================= The header title or header icon is clicked ======================= node = static_cast<QtNode*>(item->parentItem()); selectNode(node, mouseEvent); } else { // A child item of the node is clicked deselectNodes(); deselectConnections(); node = static_cast<QtNode*>(item->parentItem()); if (NODE_VALUE_TYPE_PORT == type) { // ======================= Port is clicked ======================= // Either make a connection to another port, or create a new connection QtNode* baseNode = getNodeWithActiveConnection(); if (baseNode == 0) { // There is no active connection, so start one node->mouseLeftClickHandler(mouseEvent, item, NODE_ACTION_BASE); setCursor(Qt::ClosedHandCursor); } else if (baseNode != node) { // There is an active connection and the selected port is not part of the baseNode, // so try to establish a connection with the other node if (node->mouseLeftClickHandler(mouseEvent, item, NODE_ACTION_TARGET, baseNode->mActiveConnection)) { // The connection was established, so the active connection on the basenode can be set to 0 baseNode->mActiveConnection = 0; setCursor(Qt::ArrowCursor); } } } else { node->mouseLeftClickHandler(mouseEvent, item); // Don't do anything with the node after this; it may be deleted } } return true; } } break; case Qt::RightButton: { if (mContextMenuEnabled) { QPoint pos; pos.setX(mouseEvent->lastScreenPos().x()); pos.setY(mouseEvent->lastScreenPos().y()); showContextMenu(pos); } else deselectAll(); return true; } break; } //mouseEvent->accept(); return true; }