/********************************************************************** SelectRotateTool - Selection and Rotation Tool for Avogadro Copyright (C) 2007 Donald Ephraim Curtis Copyright (C) 2007,2008 by Marcus D. Hanwell This file is part of the Avogadro molecular editor project. For more information, see Avogadro is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Avogadro is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. **********************************************************************/ #include "selectrotatetool.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; using namespace OpenBabel; using namespace Eigen; namespace Avogadro { SelectRotateTool::SelectRotateTool(QObject *parent) : Tool(parent), m_selectionBox(false), m_selectionMode(0), m_settingsWidget(0) { QAction *action = activateAction(); action->setIcon(QIcon(QString::fromUtf8(":/select/select.png"))); action->setToolTip(tr("Selection Tool (F11)\n" "Click to pick individual atoms, residues, or fragments\n" "Drag to select a range of atoms\n" "Use Ctrl to toggle the selection and shift to add to the selection")); action->setShortcut(Qt::Key_F11); } SelectRotateTool::~SelectRotateTool() { if(m_settingsWidget) { m_settingsWidget->deleteLater(); } } int SelectRotateTool::usefulness() const { return 500000; } QUndoCommand* SelectRotateTool::mousePressEvent(GLWidget *widget, QMouseEvent *event) { m_movedSinceButtonPressed = false; m_lastDraggingPosition = event->pos(); m_initialDraggingPosition = event->pos(); //! List of hits from a selection/pick m_hits = widget->hits(event->pos().x()-SEL_BOX_HALF_SIZE, event->pos().y()-SEL_BOX_HALF_SIZE, SEL_BOX_SIZE, SEL_BOX_SIZE); if (event->buttons() & Qt::LeftButton && !m_hits.size()) { m_leftButtonPressed = true; event->accept(); m_selectionBox = true; } else if (event->buttons() & Qt::LeftButton) { m_leftButtonPressed = true; } else m_leftButtonPressed = false; if(!m_selectionBox) { widget->setCursor(Qt::CrossCursor); } return 0; } QUndoCommand* SelectRotateTool::mouseReleaseEvent(GLWidget *widget, QMouseEvent *event) { // Reset the cursor widget->setCursor(Qt::ArrowCursor); Molecule *molecule = widget->molecule(); if(!molecule) { return 0; } if(!m_hits.size()) { m_selectionBox = false; } QList hitList; if (m_leftButtonPressed && !m_movedSinceButtonPressed && m_hits.size()) { event->accept(); // user didn't move the mouse -- regular picking, not selection box // we'll assemble separate "hit lists" of selected atoms and residues // (e.g., if we're in residue selection mode, picking an atom // will select the whole residue foreach (const GLHit& hit, m_hits) { if(hit.type() == Primitive::AtomType) // Atom selection { Atom *atom = molecule->atom(hit.name()); hitList.append(atom); break; } else if(hit.type() == Primitive::BondType) // Bond selection { Bond *bond = molecule->bond(hit.name()); hitList.append(bond); break; } // else if(hit.type() == Primitive::ResidueType) { // Residue *res = static_cast(molecule->GetResidue(hit.name())); // hitList.append(res); // break; } switch (m_selectionMode) { case 2: // residue foreach(Primitive *hit, hitList) { if (hit->type() == Primitive::AtomType) { Atom *atom = static_cast(hit); // If the atom is unselected, select the whole residue bool select = !widget->isSelected(atom); // Since the atom doesn't know to which residue it belongs, // we iterate over all residues and check if the atom is in // the current residue. foreach (Residue *residue, molecule->residues()) { QList atoms = residue->atoms(); if (atoms.contains(atom->id())) { QList neighborList; // add the atoms foreach (unsigned long id, atoms) neighborList.append(molecule->atomById(id)); // add the bonds foreach (unsigned long id, residue->bonds()) neighborList.append(molecule->bondById(id)); widget->setSelected(neighborList, select); } } // end for(residues) } else if (hit->type() == Primitive::BondType) { Bond *bond = static_cast(hit); // If the bond is unselected, select the whole residue bool select = !widget->isSelected(bond); // Since the bond doesn't know to which residue it belongs, // we iterate over all residues and check if the bond is in // the current residue. foreach (Residue *residue, molecule->residues()) { QList bonds = residue->bonds(); if (bonds.contains(bond->id())) { QList neighborList; // add the atoms foreach (unsigned long id, residue->atoms()) neighborList.append(molecule->atomById(id)); // add the bonds foreach (unsigned long id, bonds) neighborList.append(molecule->bondById(id)); widget->setSelected(neighborList, select); } } // end for(residues) } } // end for(hits) break; case 3: // molecule foreach(Primitive *hit, hitList) { if (hit->type() == Primitive::AtomType) { Atom *atom = static_cast(hit); // if this atom is unselected, select the whole fragment bool select = !widget->isSelected(atom); QList neighborList; // We really want the "connected fragment" since a Molecule can contain // multiple user-visible molecule fragments // we can use either BFS or DFS interators -- look for the connected fragment OpenBabel::OBMol mol = molecule->OBMol(); OpenBabel::OBMolAtomDFSIter iter(mol, atom->index() + 1); Atom *tmpNeighbor; do { tmpNeighbor = molecule->atom(iter->GetIdx() - 1); neighborList.append(tmpNeighbor); // we want to find all bonds on this site // (obviously all bonds will be in this fragment) FOR_BONDS_OF_ATOM(b, *iter) neighborList.append(molecule->bond(b->GetIdx())); } while ((iter++).next()); // this returns false when we've gone looped through the fragment widget->setSelected(neighborList, select); } else if (hit->type() == Primitive::BondType) { Bond *bond = static_cast(hit); // if this atom is unselected, select the whole fragment bool select = !widget->isSelected(bond); QList neighborList; // We really want the "connected fragment" since a Molecule can contain // multiple user-visible molecule fragments // we can use either BFS or DFS interators -- look for the connected fragment OpenBabel::OBMol mol = molecule->OBMol(); OpenBabel::OBMolAtomDFSIter iter(mol, molecule->atomById(bond->beginAtomId())->index() + 1); Atom *tmpNeighbor; do { tmpNeighbor = molecule->atom(iter->GetIdx() - 1); neighborList.append(tmpNeighbor); // we want to find all bonds on this site // (obviously all bonds will be in this fragment) FOR_BONDS_OF_ATOM(b, *iter) neighborList.append(molecule->bond(b->GetIdx())); } while ((iter++).next()); // this returns false when we've gone looped through the fragment widget->setSelected(neighborList, select); } // FIXME -- also need to handle other primitive hit types // (e.g., if we hit a residue, bond, etc.) } break; case 1: // atom/bond default: // If the Ctrl modifier is pressed toggle selection if(event->modifiers() & Qt::ControlModifier) widget->toggleSelected(hitList); else { // If the shift modifier key is not pressed clear previous selection if (!(event->modifiers() & Qt::ShiftModifier)) widget->clearSelected(); widget->setSelected(hitList, true); } break; } } else if(m_movedSinceButtonPressed && !m_hits.size()) { // Selection box picking - need to figure out which atoms were in the box int sx = qMin(m_initialDraggingPosition.x(), m_lastDraggingPosition.x()); int ex = qMax(m_initialDraggingPosition.x(), m_lastDraggingPosition.x()); int sy = qMin(m_initialDraggingPosition.y(), m_lastDraggingPosition.y()); int ey = qMax(m_initialDraggingPosition.y(), m_lastDraggingPosition.y()); int w = ex-sx; int h = ey-sy; // (sx, sy) = Upper left most position. // (ex, ey) = Bottom right most position. QList hits = widget->hits(sx, sy, w, h); // Iterate over the hits foreach(const GLHit& hit, hits) { if(hit.type() == Primitive::AtomType) // Atom selection { Atom *atom = molecule->atom(hit.name()); if(!hitList.contains(atom)) { hitList.append(atom); } } if(hit.type() == Primitive::BondType) // Bond selection { Bond *bond = molecule->bond(hit.name()); if(!hitList.contains(bond)) { hitList.append(bond); } } } // If the modifier key is not pressed clear the previous selection if (!(event->modifiers() & Qt::ShiftModifier)) widget->clearSelected(); // Set the selection widget->setSelected(hitList, true); } widget->update(); return 0; } QUndoCommand* SelectRotateTool::mouseMoveEvent(GLWidget *widget, QMouseEvent *event) { if (m_leftButtonPressed && !m_hits.size()) { event->accept(); QPoint deltaDragging = event->pos() - m_lastDraggingPosition; if( ( event->pos() - m_initialDraggingPosition ).manhattanLength() > 2 ) m_movedSinceButtonPressed = true; m_lastDraggingPosition = event->pos(); widget->update(); } else if (m_leftButtonPressed) { if((event->pos() - m_initialDraggingPosition).manhattanLength() > 2) m_movedSinceButtonPressed = true; else event->accept(); } return 0; } QUndoCommand* SelectRotateTool::wheelEvent(GLWidget*, QWheelEvent*) { return 0; } void SelectRotateTool::selectionBox(float sx, float sy, float ex, float ey) { glPushMatrix(); glLoadIdentity(); GLdouble projection[16]; glGetDoublev(GL_PROJECTION_MATRIX,projection); GLdouble modelview[16]; glGetDoublev(GL_MODELVIEW_MATRIX,modelview); GLint viewport[4]; glGetIntegerv(GL_VIEWPORT,viewport); GLdouble startPos[3]; GLdouble endPos[3]; gluUnProject(float(sx), viewport[3] - float(sy), 0.1, modelview, projection, viewport, &startPos[0], &startPos[1], &startPos[2]); gluUnProject(float(ex), viewport[3] - float(ey), 0.1, modelview, projection, viewport, &endPos[0], &endPos[1], &endPos[2]); glPushAttrib(GL_ALL_ATTRIB_BITS); glPushMatrix(); glLoadIdentity(); glEnable(GL_BLEND); glDisable(GL_LIGHTING); glDisable(GL_CULL_FACE); glColor4f(1.0, 1.0, 1.0, 0.2); glBegin(GL_POLYGON); glVertex3f(startPos[0],startPos[1],startPos[2]); glVertex3f(startPos[0],endPos[1],startPos[2]); glVertex3f(endPos[0],endPos[1],startPos[2]); glVertex3f(endPos[0],startPos[1],startPos[2]); glEnd(); startPos[2] += 0.0001; glDisable(GL_BLEND); glColor3f(1.0, 1.0, 1.0); glBegin(GL_LINE_LOOP); glVertex3f(startPos[0],startPos[1],startPos[2]); glVertex3f(startPos[0],endPos[1],startPos[2]); glVertex3f(endPos[0],endPos[1],startPos[2]); glVertex3f(endPos[0],startPos[1],startPos[2]); glEnd(); glPopMatrix(); glPopAttrib(); glPopMatrix(); } void SelectRotateTool::setSelectionMode(int i) { m_selectionMode = i; } int SelectRotateTool::selectionMode() const { return m_selectionMode; } void SelectRotateTool::selectionModeChanged( int index ) { setSelectionMode(index + 1); } QWidget *SelectRotateTool::settingsWidget() { if(!m_settingsWidget) { m_settingsWidget = new QWidget; QLabel *labelMode = new QLabel(tr("Selection Mode:")); labelMode->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); labelMode->setMaximumHeight(15); m_comboSelectionMode = new QComboBox(m_settingsWidget); m_comboSelectionMode->addItem(tr("Atom/Bond")); m_comboSelectionMode->addItem(tr("Residue")); m_comboSelectionMode->addItem(tr("Molecule")); QHBoxLayout* tmp = new QHBoxLayout; tmp->addWidget(labelMode); tmp->addWidget(m_comboSelectionMode); tmp->addStretch(1); m_layout = new QVBoxLayout(); m_layout->addLayout(tmp); m_layout->addStretch(1); m_settingsWidget->setLayout(m_layout); connect(m_comboSelectionMode, SIGNAL(currentIndexChanged(int)), this, SLOT(selectionModeChanged(int))); connect(m_settingsWidget, SIGNAL(destroyed()), this, SLOT(settingsWidgetDestroyed())); } return m_settingsWidget; } void SelectRotateTool::settingsWidgetDestroyed() { m_settingsWidget = 0; } bool SelectRotateTool::paint(GLWidget *) { if(m_selectionBox) { selectionBox(m_initialDraggingPosition.x(), m_initialDraggingPosition.y(), m_lastDraggingPosition.x(), m_lastDraggingPosition.y()); } return true; } } #include "selectrotatetool.moc" Q_EXPORT_PLUGIN2(selectrotatetool, Avogadro::SelectRotateToolFactory)