• Skip to content
  • Skip to link menu
KDE 4.2 API Reference
  • KDE API Reference
  • kdeedu
  • Sitemap
  • Contact Us
 

kalzium

bondcentrictool.cpp

Go to the documentation of this file.
00001 /**********************************************************************
00002   BondCentricTool - Bond Centric Manipulation Tool for Avogadro
00003 
00004   Copyright (C) 2007 by Shahzad Ali
00005   Copyright (C) 2007 by Ross Braithwaite
00006   Copyright (C) 2007 by James Bunt
00007   Copyright (C) 2007 by Marcus D. Hanwell
00008   Copyright (C) 2006,2007 by Benoit Jacob
00009 
00010   This file is part of the Avogadro molecular editor project.
00011   For more information, see <http://avogadro.sourceforge.net/>
00012 
00013   Avogadro is free software; you can redistribute it and/or modify
00014   it under the terms of the GNU General Public License as published by
00015   the Free Software Foundation; either version 2 of the License, or
00016   (at your option) any later version.
00017 
00018   Avogadro is distributed in the hope that it will be useful,
00019   but WITHOUT ANY WARRANTY; without even the implied warranty of
00020   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00021   GNU General Public License for more details.
00022 
00023   You should have received a copy of the GNU General Public License
00024   along with this program; if not, write to the Free Software
00025   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
00026   02110-1301, USA.
00027  **********************************************************************/
00028 
00029 #include "bondcentrictool.h"
00030 #include "quaternion.h"
00031 
00032 #ifdef WIN32
00033 #include <float.h>
00034 #include <math.h>
00035 #define isnan(x) _isnan(x)
00036 #endif
00037 
00038 #include <iostream>
00039 
00040 #include <avogadro/navigate.h>
00041 #include <avogadro/primitive.h>
00042 #include <avogadro/color.h>
00043 #include <avogadro/glwidget.h>
00044 #include <avogadro/camera.h>
00045 #include <avogadro/toolgroup.h>
00046 
00047 #include <openbabel/obiter.h>
00048 #include <openbabel/mol.h>
00049 
00050 #include <QtPlugin>
00051 #include <QString>
00052 
00053 #include <QDebug>
00054 
00055 using namespace std;
00056 using namespace OpenBabel;
00057 using namespace Eigen;
00058 
00059 namespace Avogadro {
00060 
00061   // ############################ BondCentricTool ################################
00062 
00063   // ##########  Constructor  ##########
00064 
00065   BondCentricTool::BondCentricTool(QObject *parent) : Tool(parent),
00066   m_molecule(NULL),
00067   m_settingsWidget(NULL),
00068   m_clickedAtom(NULL),
00069   m_clickedBond(NULL),
00070   m_selectedBond(NULL),
00071   m_skeleton(NULL),
00072   m_referencePoint(NULL),
00073   m_currentReference(NULL),
00074   m_directionVector(NULL),
00075   m_snapped(false),
00076   m_toolGroup(NULL),
00077   m_leftButtonPressed(false),
00078   m_midButtonPressed(false),
00079   m_rightButtonPressed(false),
00080   m_movedSinceButtonPressed(false),
00081   m_showAngles(true),
00082   m_snapToEnabled(true),
00083   m_snapToAngle(10)
00084   {
00085     QAction *action = activateAction();
00086     action->setIcon(QIcon(QString::fromUtf8(":/bondcentric/bondcentric.png")));
00087     action->setToolTip(tr("Bond Centric Manipulation Tool\n\n"
00088           "Left Mouse:   Click and drag to rotate the view\n"
00089           "Middle Mouse: Click and drag to zoom in or out\n"
00090           "Right Mouse:  Click and drag to move the view\n\n"
00091           "Left Click & drag on a Bond to set the Manipulation Plane:\n"
00092           "- Left Click & Drag one of the Atoms in the Bond to change the angle\n"
00093           "- Right Click & Drag one of the Atoms in the Bond to change the length"));
00094     //action->setShortcut(Qt::Key_F9);
00095     connect(action,SIGNAL(toggled(bool)),this,SLOT(toolChanged(bool)));
00096   }
00097 
00098   // ##########  Desctructor  ##########
00099 
00100   BondCentricTool::~BondCentricTool()
00101   {
00102     delete m_referencePoint;
00103     m_referencePoint = NULL;
00104     delete m_currentReference;
00105     m_currentReference = NULL;
00106     delete m_directionVector;
00107     m_directionVector = NULL;
00108 
00109     if (m_settingsWidget)
00110     {
00111       m_snapToAngleLabel->deleteLater();
00112       m_spacer->deleteLater();
00113       m_showAnglesBox->deleteLater();
00114       m_snapToCheckBox->deleteLater();
00115       m_snapToAngleBox->deleteLater();
00116       m_layout->deleteLater();
00117 
00118       m_settingsWidget->deleteLater();
00119     }
00120   }
00121 
00122   // ##########  clearData  ##########
00123 
00124   void BondCentricTool::clearData()
00125   {
00126     m_clickedAtom = NULL;
00127     m_clickedBond = NULL;
00128     m_selectedBond = NULL;
00129     delete m_referencePoint;
00130     m_referencePoint = NULL;
00131     delete m_currentReference;
00132     m_currentReference = NULL;
00133     delete m_directionVector;
00134     m_directionVector = NULL;
00135     m_toolGroup = NULL;
00136     m_leftButtonPressed = false;
00137     m_midButtonPressed = false;
00138     m_rightButtonPressed = false;
00139     m_movedSinceButtonPressed = false;
00140     m_snapped = false;
00141   }
00142 
00143   // ##########  moleculeChanged  ##########
00144 
00145   void BondCentricTool::setMolecule(Molecule* molecule)
00146   {
00147     if(m_molecule) {
00148       disconnect(m_molecule, 0 , this, 0);
00149     }
00150 
00151     if (molecule) {
00152       m_molecule = molecule;
00153       connect(molecule, SIGNAL(primitiveRemoved(Primitive*)), this,
00154           SLOT(primitiveRemoved(Primitive*)));
00155     }
00156 
00157     clearData();
00158   }
00159 
00160   // ##########  primitiveRemoved  ##########
00161 
00162   void BondCentricTool::primitiveRemoved(Primitive *primitive)
00163   {
00164     if (primitive == m_clickedAtom || primitive == m_clickedBond ||
00165         primitive == m_selectedBond) {
00166       clearData();
00167     }
00168   }
00169 
00170   // ##########  toolChanged  ##########
00171 
00172   void BondCentricTool::toolChanged(bool checked)
00173   {
00174     if(!checked && m_molecule)
00175     {
00176       m_molecule->update();
00177       clearData();
00178     }
00179   }
00180 
00181   // ##########  usefulness  ##########
00182 
00183   int BondCentricTool::usefulness() const
00184   {
00185     return 2000000;
00186   }
00187 
00188   // ##########  mousePress  ##########
00189 
00190   QUndoCommand* BondCentricTool::mousePress(GLWidget *widget, const QMouseEvent *event)
00191   {
00192     m_undo = 0;
00193 
00194     m_lastDraggingPosition = event->pos();
00195     m_movedSinceButtonPressed = false;
00196 
00197 #ifdef Q_WS_MAC
00198     m_leftButtonPressed = (event->buttons() & Qt::LeftButton
00199         && event->modifiers() == Qt::NoModifier);
00200     // On the Mac, either use a three-button mouse
00201     // or hold down the Shift key
00202     m_midButtonPressed = ((event->buttons() & Qt::MidButton) ||
00203         (event->buttons() & Qt::LeftButton && event->modifiers()
00204          & Qt::ShiftModifier));
00205     // Hold down the Command key (ControlModifier in Qt notation) for right button
00206     m_rightButtonPressed = ((event->buttons() & Qt::RightButton) ||
00207         (event->buttons() & Qt::LeftButton &&
00208          (event->modifiers() == Qt::ControlModifier ||
00209           event->modifiers() == Qt::MetaModifier)));
00210 #else
00211     m_leftButtonPressed = (event->buttons() & Qt::LeftButton);
00212     m_midButtonPressed = (event->buttons() & Qt::MidButton);
00213     m_rightButtonPressed = (event->buttons() & Qt::RightButton);
00214 #endif
00215 
00216     m_clickedAtom = NULL;
00217     m_clickedBond = NULL;
00218 
00219     int oldName = m_selectedBond ? m_selectedBond->GetIdx() : -1;
00220 
00221     // Check if the mouse clicked on any Atoms or Bonds.
00222     Primitive *clickedPrim = widget->computeClickedPrimitive(event->pos());
00223 
00224     if (clickedPrim && clickedPrim->type() == Primitive::AtomType)
00225     {
00226       // Atom clicked on.
00227       m_clickedAtom = (Atom*)clickedPrim;
00228 
00229       if ((m_rightButtonPressed || m_leftButtonPressed) && isAtomInBond(m_clickedAtom, m_selectedBond))
00230       {
00231         //Create an undo instance for this manipulation
00232         m_undo = new BondCentricMoveCommand(widget->molecule());
00233 
00234         // Populate the skeleton in preparation to alter the angle or length of the bond.
00235         m_skeleton = new SkeletonTree();
00236         m_skeleton->populate(m_clickedAtom, m_selectedBond, widget->molecule());
00237 
00238         if (m_leftButtonPressed)
00239         {
00240           // If the bond is to be rotated, save off a vector used in the calculation.
00241           // If the vector is calculated every mouse movement it causes the molecule to
00242           // jitter, saving it now prevents this.
00243 
00244           Atom *otherAtom;
00245           if (m_clickedAtom == static_cast<Atom*>(m_selectedBond->GetBeginAtom()))
00246             otherAtom = static_cast<Atom*>(m_selectedBond->GetEndAtom());
00247           else
00248             otherAtom = static_cast<Atom*>(m_selectedBond->GetBeginAtom());
00249 
00250           Vector3d centerProj = widget->camera()->project(otherAtom->pos());
00251           centerProj -= Vector3d(0,0,centerProj.z());
00252           Vector3d clickedProj = widget->camera()->project(m_clickedAtom->pos());
00253           clickedProj -= Vector3d(0,0,clickedProj.z());
00254 
00255           if ((clickedProj - centerProj).norm() == 0)
00256           {
00257             // Have no way of testing this as the chance of this happening is almost 0
00258             m_directionVector = new Vector3d(1, 0, 0);
00259           }
00260           else
00261           {
00262             m_directionVector = new Vector3d(clickedProj - centerProj);
00263             *m_directionVector = m_directionVector->normalized();
00264           }
00265         }
00266       }
00267       else if (m_selectedBond)
00268       {
00269         // Check if one of the atoms one bond away from the selected bond has
00270         // been clicked on in preparation to alter dihedral angles.
00271 
00272         Atom *dihedralRotCen = NULL;
00273         Bond *skeleBond = NULL;
00274 
00275         Atom *beginAtom = static_cast<Atom*>(m_selectedBond->GetBeginAtom());
00276         Atom *endAtom = static_cast<Atom*>(m_selectedBond->GetEndAtom());
00277 
00278         // Check which atom in the selected bond the atom being manipulated will
00279         // be rotated around to change its dihedral angle.
00280         if ((skeleBond = static_cast<Bond*>(m_clickedAtom->GetBond(beginAtom)))) {
00281           dihedralRotCen = beginAtom;
00282         }
00283         else if ((skeleBond = static_cast<Bond*>(m_clickedAtom->GetBond(endAtom)))) {
00284           dihedralRotCen = endAtom;
00285         }
00286 
00287         bool skeletonSet = false;
00288         if (m_rightButtonPressed && skeleBond)
00289         {
00290           //Create an undo instance for this manipulation
00291           m_undo = new BondCentricMoveCommand(widget->molecule());
00292 
00293           // Populate the skeleton in preparation to alter the dihedral angle of the
00294           // clicked atom.
00295           m_skeleton = new SkeletonTree();
00296           m_skeleton->populate(m_clickedAtom, skeleBond, widget->molecule());
00297           skeletonSet = true;
00298         }
00299         else if (m_leftButtonPressed && dihedralRotCen)
00300         {
00301           //Create an undo instance for this manipulation
00302           m_undo = new BondCentricMoveCommand(widget->molecule());
00303 
00304           // Populate the skeleton in preparation to alter the dihedral angle of all
00305           // the atoms bonded to this end of the bond (essentially twisting this end
00306           // of the bond).
00307           m_skeleton = new SkeletonTree();
00308           m_skeleton->populate(dihedralRotCen, m_selectedBond, widget->molecule());
00309           skeletonSet = true;
00310         }
00311 
00312         if (skeletonSet)
00313         {
00314           // If the dihedral angle is to be changed, save off a vector used in the
00315           // calculation. If the vector is calculated every mouse movement it causes
00316           // the molecule to jitter, saving it now prevents this.
00317 
00318           Vector3d centerProj = widget->camera()->project(dihedralRotCen->pos());
00319           centerProj -= Vector3d(0,0,centerProj.z());
00320           Vector3d clickedProj = widget->camera()->project(m_clickedAtom->pos());
00321           clickedProj -= Vector3d(0,0,clickedProj.z());
00322 
00323           if ((clickedProj - centerProj).norm() == 0)
00324           {
00325             // Have no way of testing this as the chance of this happening is almost 0
00326             m_directionVector = new Vector3d(1, 0, 0);
00327           }
00328           else
00329           {
00330             m_directionVector = new Vector3d(clickedProj - centerProj);
00331             *m_directionVector = m_directionVector->normalized();
00332           }
00333         }
00334       }
00335     }
00336     else if (clickedPrim && clickedPrim->type() == Primitive::BondType)
00337     {
00338       // Bond clicked on.
00339       m_clickedBond = (Bond*)clickedPrim;
00340 
00341       // If the Bond was clicked on with the left mouse button, set it as the
00342       // currently selected bond and reset the reference point (if the Bond has
00343       // changed).
00344       if (m_leftButtonPressed)
00345       {
00346         m_selectedBond = m_clickedBond;
00347 
00348         if ((int)m_selectedBond->GetIdx() != oldName)
00349         {
00350           delete m_referencePoint;
00351           m_referencePoint = NULL;
00352 
00353           delete m_currentReference;
00354           m_currentReference = NULL;
00355 
00356           m_snapped = false;
00357 
00358           Atom *leftAtom = static_cast<Atom*>(m_selectedBond->GetBeginAtom());
00359           Atom *rightAtom = static_cast<Atom*>(m_selectedBond->GetEndAtom());
00360 
00361           Vector3d left = leftAtom->pos();
00362           Vector3d right = rightAtom->pos();
00363           Vector3d leftToRight = right - left;
00364 
00365           Vector3d x = Vector3d(1, 0, 0);
00366           Vector3d y = Vector3d(0, 1, 0);
00367 
00368           Vector3d A = leftToRight.cross(x);
00369           Vector3d B = leftToRight.cross(y);
00370 
00371           m_referencePoint = A.norm() >= B.norm() ? new Vector3d(A) : new Vector3d(B);
00372           *m_referencePoint = m_referencePoint->normalized();
00373 
00374           Vector3d *reference = calculateSnapTo(m_selectedBond,
00375               m_referencePoint, m_snapToAngle);
00376 
00377           if (reference && m_snapToEnabled)
00378           {
00379             m_snapped = true;
00380             m_currentReference = reference;
00381             *m_currentReference = m_currentReference->normalized();
00382           }
00383           else {
00384             m_currentReference = new Vector3d(*m_referencePoint);
00385           }
00386         }
00387       }
00388     }
00389 
00390     widget->update();
00391     return 0;
00392   }
00393 
00394   // ##########  mouseRelease  ##########
00395 
00396   QUndoCommand* BondCentricTool::mouseRelease(GLWidget *widget, const QMouseEvent*)
00397   {
00398     delete m_directionVector;
00399     m_directionVector = NULL;
00400 
00401     if (!m_clickedAtom && !m_clickedBond && !m_movedSinceButtonPressed)
00402     {
00403       delete m_referencePoint;
00404       m_referencePoint = NULL;
00405       delete m_currentReference;
00406       m_currentReference = NULL;
00407       m_snapped = false;
00408       m_selectedBond = NULL;
00409     }
00410     else if (!m_movedSinceButtonPressed) {
00411       m_undo = 0;
00412     }
00413 
00414     if (m_skeleton)
00415     {
00416       delete m_skeleton;
00417       m_skeleton = NULL;
00418     }
00419 
00420     m_leftButtonPressed = false;
00421     m_midButtonPressed = false;
00422     m_rightButtonPressed = false;
00423     m_clickedAtom = NULL;
00424     m_clickedBond = NULL;
00425 
00426     widget->update();
00427     return m_undo;
00428   }
00429 
00430   // ##########  mouseMove  ##########
00431 
00432   QUndoCommand* BondCentricTool::mouseMove(GLWidget *widget, const QMouseEvent *event)
00433   {
00434     if (!m_molecule) {
00435       return 0;
00436     }
00437 
00438     QPoint deltaDragging = event->pos() - m_lastDraggingPosition;
00439 
00440     if (deltaDragging.manhattanLength() > 2) {
00441       m_movedSinceButtonPressed = true;
00442     }
00443 
00444     // Mouse navigation has two modes - atom centred when an atom is clicked
00445     // and scene if no atom has been clicked.
00446 
00447 #ifdef Q_WS_MAC
00448     if (event->buttons() & Qt::LeftButton && event->modifiers() == Qt::NoModifier)
00449 #else
00450       if (event->buttons() & Qt::LeftButton)
00451 #endif
00452       {
00453         if (m_clickedBond && m_selectedBond && m_referencePoint)
00454         {
00455           Atom *beginAtom = static_cast<Atom*>(m_selectedBond->GetBeginAtom());
00456           Atom *endAtom = static_cast<Atom*>(m_selectedBond->GetEndAtom());
00457 
00458           Vector3d rotationVector = endAtom->pos() - beginAtom->pos();
00459           rotationVector = rotationVector / rotationVector.norm();
00460 
00461           Vector3d begin = widget->camera()->project(beginAtom->pos());
00462           Vector3d end = widget->camera()->project(endAtom->pos());
00463 
00464           Vector3d zAxis = Vector3d(0, 0, 1);
00465           Vector3d beginToEnd = end - begin;
00466           beginToEnd -= Vector3d(0, 0, beginToEnd.z());
00467 
00468           Vector3d direction = zAxis.cross(beginToEnd);
00469           direction = direction / direction.norm();
00470 
00471           Vector3d mouseMoved = Vector3d(deltaDragging.x(), deltaDragging.y(), 0);
00472 
00473           double magnitude = mouseMoved.dot(direction) / direction.norm();
00474 
00475           *m_referencePoint = performRotation(magnitude * (M_PI / 180.0),
00476               rotationVector, Vector3d(0, 0, 0),
00477               *m_referencePoint);
00478 
00479           Eigen::Vector3d *reference = calculateSnapTo(m_selectedBond,
00480               m_referencePoint, m_snapToAngle);
00481           if (reference && m_snapToEnabled)
00482           {
00483             m_snapped = true;
00484             delete m_currentReference;
00485             m_currentReference = reference;
00486             *m_currentReference = m_currentReference->normalized();
00487           }
00488           else
00489           {
00490             m_snapped = false;
00491             delete m_currentReference;
00492             m_currentReference = new Vector3d(*m_referencePoint);
00493           }
00494         }
00495         else if (isAtomInBond(m_clickedAtom, m_selectedBond))
00496         {
00497           //Do atom rotation.
00498           Atom *otherAtom;
00499 
00500           if (m_clickedAtom == static_cast<Atom*>(m_selectedBond->GetBeginAtom()))
00501             otherAtom = static_cast<Atom*>(m_selectedBond->GetEndAtom());
00502           else
00503             otherAtom = static_cast<Atom*>(m_selectedBond->GetBeginAtom());
00504 
00505           Vector3d center = otherAtom->pos();
00506           Vector3d clicked = m_clickedAtom->pos();
00507 
00508           Vector3d centerProj = widget->camera()->project(center);
00509           centerProj -= Vector3d(0,0,centerProj.z());
00510           Vector3d referenceProj = widget->camera()->project(*m_currentReference + center);
00511           referenceProj -= Vector3d(0,0,referenceProj.z());
00512 
00513           Vector3d referenceVector = referenceProj - centerProj;
00514           referenceVector = referenceVector.normalized();
00515 
00516           Vector3d rotationVector = referenceVector.cross(*m_directionVector);
00517           rotationVector = rotationVector.normalized();
00518 
00519           Vector3d currMouseVector = Vector3d(event->pos().x(), event->pos().y(), 0)
00520             - centerProj;
00521           if(currMouseVector.norm() > 5)
00522           {
00523             currMouseVector = currMouseVector.normalized();
00524             double mouseAngle = acos(m_directionVector->dot(currMouseVector) /
00525                 currMouseVector.norm2());
00526 
00527             if(mouseAngle > 0)
00528             {
00529               Vector3d tester;
00530 
00531               tester = performRotation(mouseAngle, rotationVector, Vector3d(0, 0, 0),
00532                   *m_directionVector);
00533               double testAngle1 = acos(tester.dot(currMouseVector) /
00534                   currMouseVector.norm2());
00535 
00536               tester = performRotation(-mouseAngle, rotationVector, Vector3d(0, 0, 0),
00537                   *m_directionVector);
00538               double testAngle2 = acos(tester.dot(currMouseVector) /
00539                   currMouseVector.norm2());
00540 
00541               if(testAngle1 > testAngle2 || isnan(testAngle2)) {
00542                 mouseAngle = -mouseAngle;
00543               }
00544 
00545               Vector3d direction = clicked - center;
00546               if (m_skeleton)
00547               {
00548                 Vector3d currCrossDir = m_currentReference->cross(direction).normalized();
00549 
00550                 m_skeleton->skeletonRotate(mouseAngle, currCrossDir, center);
00551                 *m_referencePoint = performRotation(mouseAngle, currCrossDir,
00552                     Vector3d(0, 0, 0), *m_referencePoint);
00553                 *m_currentReference = performRotation(mouseAngle, currCrossDir,
00554                     Vector3d(0, 0, 0), *m_currentReference);
00555                 *m_directionVector = performRotation(mouseAngle, rotationVector,
00556                     Vector3d(0, 0, 0), *m_directionVector);
00557               }
00558             }
00559           }
00560         }
00561         else if (m_selectedBond && m_clickedAtom &&
00562             (m_clickedAtom->GetBond(m_selectedBond->GetBeginAtom()) ||
00563              m_clickedAtom->GetBond(m_selectedBond->GetEndAtom())))
00564         {
00565           // Do multiple dihedral rotation (twising of the bond).
00566 
00567           Atom *beginAtom = static_cast<Atom*>(m_selectedBond->GetBeginAtom());
00568           Atom *endAtom = static_cast<Atom*>(m_selectedBond->GetEndAtom());
00569 
00570           Vector3d center;
00571           Vector3d other;
00572           if (m_clickedAtom->GetBond(beginAtom))
00573           {
00574             center = beginAtom->pos();
00575             other = endAtom->pos();
00576           }
00577           else {
00578             center = endAtom->pos();
00579             other = beginAtom->pos();
00580           }
00581 
00582           Vector3d clicked = m_clickedAtom->pos();
00583 
00584           Vector3d axis = Vector3d(0, 0, ((widget->camera()->modelview() * other).z() >=
00585                 (widget->camera()->modelview() * center).z() ? -1 : 1));
00586 
00587           Vector3d centerProj = widget->camera()->project(center);
00588           centerProj -= Vector3d(0,0,centerProj.z());
00589 
00590           Vector3d currMouseVector = Vector3d(event->pos().x(), event->pos().y(), 0)
00591             - centerProj;
00592 
00593           if(currMouseVector.norm() > 5)
00594           {
00595             currMouseVector = currMouseVector.normalized();
00596             double mouseAngle = acos(m_directionVector->dot(currMouseVector) /
00597                 currMouseVector.norm2());
00598 
00599             if(mouseAngle > 0)
00600             {
00601               Vector3d tester;
00602 
00603               tester = performRotation(mouseAngle, axis, Vector3d(0, 0, 0), *m_directionVector);
00604               double testAngle1 = acos(tester.dot(currMouseVector) /
00605                   currMouseVector.norm2());
00606 
00607               tester = performRotation(-mouseAngle, axis, Vector3d(0, 0, 0), *m_directionVector);
00608               double testAngle2 = acos(tester.dot(currMouseVector) /
00609                   currMouseVector.norm2());
00610 
00611               if(testAngle1 > testAngle2 || isnan(testAngle2)) {
00612                 mouseAngle = -mouseAngle;
00613               }
00614 
00615               if (m_skeleton)
00616               {
00617                 *m_directionVector = performRotation(mouseAngle, axis,
00618                     Vector3d(0, 0, 0), *m_directionVector);
00619 
00620                 axis = (other - center).normalized();
00621                 m_skeleton->skeletonRotate(mouseAngle, axis, center);
00622               }
00623             }
00624           }
00625         }
00626         else {
00627           // rotation around the center of the molecule
00628           Navigate::rotate(widget, widget->center(), deltaDragging.x(), deltaDragging.y());
00629         }
00630       }
00631 #ifdef Q_WS_MAC
00632     // On the Mac, either use a three-button mouse
00633     // or hold down the Shift key
00634       else if ((event->buttons() & Qt::MidButton) || (event->buttons() &
00635             Qt::LeftButton && event->modifiers() & Qt::ShiftModifier))
00636 #else
00637       else if (event->buttons() & Qt::MidButton)
00638 #endif
00639       {
00640         if (m_clickedAtom)
00641         {
00642           // Perform the rotation
00643           Navigate::tilt(widget, m_clickedAtom->pos(), deltaDragging.x());
00644 
00645           // Perform the zoom toward the center of a clicked atom
00646           Navigate::zoom(widget, m_clickedAtom->pos(), deltaDragging.y());
00647         }
00648         else if (m_clickedBond)
00649         {
00650           Atom *begin = static_cast<Atom *>(m_clickedBond->GetBeginAtom());
00651           Atom *end = static_cast<Atom *>(m_clickedBond->GetEndAtom());
00652 
00653           Vector3d btoe = end->pos() - begin->pos();
00654           double newLen = btoe.norm() / 2;
00655           btoe = btoe / btoe.norm();
00656 
00657           Vector3d mid = begin->pos() + btoe * newLen;
00658 
00659           // Perform the rotation
00660           Navigate::tilt(widget, mid, deltaDragging.x());
00661 
00662           // Perform the zoom toward the centre of a clicked bond
00663           Navigate::zoom(widget, mid, deltaDragging.y());
00664         }
00665         else
00666         {
00667           // Perform the rotation
00668           Navigate::tilt(widget, widget->center(), deltaDragging.x());
00669 
00670           // Perform the zoom toward molecule center
00671           Navigate::zoom(widget, widget->center(), deltaDragging.y());
00672         }
00673       }
00674 #ifdef Q_WS_MAC
00675     // On the Mac, either use a three-button mouse
00676     // or hold down the Command key (ControlModifier in Qt notation)
00677       else if ((event->buttons() & Qt::RightButton) ||
00678           (event->buttons() & Qt::LeftButton &&
00679            (event->modifiers() == Qt::ControlModifier || event->modifiers() == Qt::MetaModifier)))
00680 #else
00681       else if (event->buttons() & Qt::RightButton)
00682 #endif
00683       {
00684         if (isAtomInBond(m_clickedAtom, m_selectedBond))
00685         {
00686           // Adjust the length of the bond following the mouse movement.
00687 
00688           Atom *otherAtom;
00689 
00690           if (m_clickedAtom == static_cast<Atom*>(m_selectedBond->GetBeginAtom()))
00691             otherAtom = static_cast<Atom*>(m_selectedBond->GetEndAtom());
00692           else
00693             otherAtom = static_cast<Atom*>(m_selectedBond->GetBeginAtom());
00694 
00695           Vector3d clicked = m_clickedAtom->pos();
00696           Vector3d other = otherAtom->pos();
00697           Vector3d direction = clicked - other;
00698 
00699           Vector3d mouseLast = widget->camera()->unProject(m_lastDraggingPosition);
00700           Vector3d mouseCurr = widget->camera()->unProject(event->pos());
00701           Vector3d mouseDir = mouseCurr - mouseLast;
00702 
00703           Vector3d component = mouseDir.dot(direction) / direction.norm2() * direction;
00704 
00705           if (m_skeleton) {
00706             m_skeleton->skeletonTranslate(component.x(), component.y(), component.z());
00707           }
00708         }
00709         else if (m_selectedBond && m_clickedAtom &&
00710             (m_clickedAtom->GetBond(m_selectedBond->GetBeginAtom()) ||
00711              m_clickedAtom->GetBond(m_selectedBond->GetEndAtom())))
00712         {
00713           // Do dihedral angle manipulation of the clicked atom.
00714 
00715           Atom *beginAtom = static_cast<Atom*>(m_selectedBond->GetBeginAtom());
00716           Atom *endAtom = static_cast<Atom*>(m_selectedBond->GetEndAtom());
00717 
00718           Vector3d center;
00719           Vector3d other;
00720           if (m_clickedAtom->GetBond(beginAtom))
00721           {
00722             center = beginAtom->pos();
00723             other = endAtom->pos();
00724           }
00725           else {
00726             center = endAtom->pos();
00727             other = beginAtom->pos();
00728           }
00729 
00730           Vector3d clicked = m_clickedAtom->pos();
00731 
00732           Vector3d axis = Vector3d(0, 0, ((widget->camera()->modelview() * other).z() >=
00733                 (widget->camera()->modelview() * center).z() ? -1 : 1));
00734 
00735           Vector3d centerProj = widget->camera()->project(center);
00736           centerProj -= Vector3d(0,0,centerProj.z());
00737 
00738           Vector3d currMouseVector = Vector3d(event->pos().x(), event->pos().y(), 0)
00739             - centerProj;
00740 
00741           if(currMouseVector.norm() > 5)
00742           {
00743             currMouseVector = currMouseVector.normalized();
00744             double mouseAngle = acos(m_directionVector->dot(currMouseVector) /
00745                 currMouseVector.norm2());
00746 
00747             if(mouseAngle > 0)
00748             {
00749               Vector3d tester;
00750 
00751               tester = performRotation(mouseAngle, axis, Vector3d(0, 0, 0), *m_directionVector);
00752               double testAngle1 = acos(tester.dot(currMouseVector) /
00753                   currMouseVector.norm2());
00754 
00755               tester = performRotation(-mouseAngle, axis, Vector3d(0, 0, 0), *m_directionVector);
00756               double testAngle2 = acos(tester.dot(currMouseVector) /
00757                   currMouseVector.norm2());
00758 
00759               if(testAngle1 > testAngle2 || isnan(testAngle2)) {
00760                 mouseAngle = -mouseAngle;
00761               }
00762 
00763               if (m_skeleton)
00764               {
00765                 *m_directionVector = performRotation(mouseAngle, axis,
00766                     Vector3d(0, 0, 0), *m_directionVector);
00767 
00768                 axis = (other - center).normalized();
00769                 m_skeleton->skeletonRotate(mouseAngle, axis, center);
00770               }
00771             }
00772           }
00773         }
00774         else {
00775           // Translate the molecule following mouse movement.
00776           Navigate::translate(widget, widget->center(), m_lastDraggingPosition, event->pos());
00777         }
00778       }
00779 
00780     m_lastDraggingPosition = event->pos();
00781     widget->update();
00782 
00783     return 0;
00784   }
00785 
00786   // ##########  wheel  ##########
00787 
00788   QUndoCommand* BondCentricTool::wheel(GLWidget *widget, const QWheelEvent *event)
00789   {
00790     m_clickedAtom = NULL;
00791     m_clickedBond = NULL;
00792 
00793     Primitive *clickedPrim = widget->computeClickedPrimitive(event->pos());
00794 
00795     if (clickedPrim && clickedPrim->type() == Primitive::AtomType)
00796     {
00797       Atom *clickedAtom = (Atom*)clickedPrim;
00798       // Perform the zoom toward clicked atom
00799       Navigate::zoom(widget, clickedAtom->pos(), - MOUSE_WHEEL_SPEED * event->delta());
00800     }
00801     else if (clickedPrim && clickedPrim->type() == Primitive::BondType)
00802     {
00803       Bond *clickedBond = (Bond*)clickedPrim;
00804 
00805       Atom *begin = static_cast<Atom *>(clickedBond->GetBeginAtom());
00806       Atom *end = static_cast<Atom *>(clickedBond->GetEndAtom());
00807 
00808       Vector3d btoe = end->pos() - begin->pos();
00809       double newLen = btoe.norm() / 2;
00810       btoe = btoe / btoe.norm();
00811 
00812       Vector3d mid = begin->pos() + btoe * newLen;
00813 
00814       // Perform the zoom toward the centre of a clicked bond
00815       Navigate::zoom(widget, mid, - MOUSE_WHEEL_SPEED * event->delta());
00816     }
00817     else {
00818       // Perform the zoom toward molecule center
00819       Navigate::zoom(widget, widget->center(), - MOUSE_WHEEL_SPEED * event->delta());
00820     }
00821 
00822     widget->update();
00823 
00824     return 0;
00825   }
00826 
00827   // ##########  paint  ##########
00828 
00829   bool BondCentricTool::paint(GLWidget *widget)
00830   {
00831     if(widget->toolGroup()->activeTool() != this) {
00832       clearData();
00833     }
00834 
00835     bool dihedralAtomClicked = false;
00836 
00837     // Draw the dihedral angles and rectangles if they apply.
00838     if ((m_leftButtonPressed || m_rightButtonPressed) && m_clickedAtom &&
00839         m_selectedBond && !isAtomInBond(m_clickedAtom, m_selectedBond))
00840     {
00841       Atom *begin = static_cast<Atom*>(m_selectedBond->GetBeginAtom());
00842       Atom *end = static_cast<Atom*>(m_selectedBond->GetEndAtom());
00843 
00844       if (m_clickedAtom->GetBond(begin) || m_clickedAtom->GetBond(end))
00845       {
00846         dihedralAtomClicked = true;
00847 
00848         if (m_rightButtonPressed) {
00849           drawSingleDihedralAngles(widget, m_clickedAtom, m_selectedBond);
00850         } else {
00851           drawDihedralAngles(widget, m_clickedAtom, m_selectedBond);
00852         }
00853       }
00854     }
00855 
00856     // The small yellow sphere that resembles the center of the molecule during rotation.
00857     if (!dihedralAtomClicked &&
00858         ((m_leftButtonPressed && !m_clickedBond && !isAtomInBond(m_clickedAtom, m_selectedBond))
00859          || (m_midButtonPressed && !m_clickedBond && !m_clickedAtom)
00860          || (m_rightButtonPressed && !isAtomInBond(m_clickedAtom, m_selectedBond))))
00861     {
00862       drawSphere(widget, widget->center(), 0.10, 1.0);
00863     }
00864 
00865     // If a single atom was clicked on, display the angles around it.
00866     if (!dihedralAtomClicked && m_leftButtonPressed && m_clickedAtom &&
00867         (!m_selectedBond || !isAtomInBond(m_clickedAtom, m_selectedBond)))
00868     {
00869       drawAtomAngles(widget, m_clickedAtom);
00870     }
00871 
00872     // Draw the manipulation rectangle and relative angles.
00873     if (m_selectedBond && !dihedralAtomClicked)
00874     {
00875       Atom *begin = static_cast<Atom*>(m_selectedBond->GetBeginAtom());
00876       Atom *end = static_cast<Atom*>(m_selectedBond->GetEndAtom());
00877 
00878       if (m_currentReference)
00879       {
00880         // Draw bond length text.
00881