/**********************************************************************
ManipulateTool - Manipulation Tool for Avogadro
Copyright (C) 2007 by Marcus D. Hanwell
Copyright (C) 2007 by Geoffrey R. Hutchison
Copyright (C) 2007 by Benoit Jacob
This file is part of the Avogadro molecular editor project.
For more information, see
Some code is based on Open Babel
For more information, see
This program 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 version 2 of the License.
This program 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.
***********************************************************************/
#include "manipulatetool.h"
#include "eyecandy.h"
#include
#include
#include
#include
#include
#include
#include
#include
using Eigen::Vector3d;
using Eigen::Transform3d;
using Eigen::AngleAxisd;
namespace Avogadro {
ManipulateTool::ManipulateTool(QObject *parent) : Tool(parent),
m_clickedAtom(0), m_leftButtonPressed(false), m_midButtonPressed(false),
m_rightButtonPressed(false), m_eyecandy(new Eyecandy)
{
m_eyecandy->setColor(1.0, 0.0, 0.0, 1.0);
QAction *action = activateAction();
action->setIcon(QIcon(QString::fromUtf8(":/manipulate/manipulate.png")));
action->setToolTip(tr("Manipulation Tool (F10)\n\n"
"Left Mouse: Click and drag to move atoms\n"
"Middle Mouse: Click and drag to move atoms further away or closer\n"
"Right Mouse: Click and drag to rotate selected atoms."));
action->setShortcut(Qt::Key_F10);
}
ManipulateTool::~ManipulateTool()
{
delete m_eyecandy;
}
int ManipulateTool::usefulness() const
{
return 600000;
}
void ManipulateTool::zoom(GLWidget *widget, const Eigen::Vector3d *goal,
double delta) const
{
// Set the cursor - this needs to be reset to Qt::ArrowCursor after
widget->setCursor(Qt::SizeVerCursor);
// Move the selected atom(s) in to or out of the screen
Vector3d transformedGoal = widget->camera()->modelview() * *goal;
double distanceToGoal = transformedGoal.norm();
double t = ZOOM_SPEED * delta;
const double minDistanceToGoal = 2.0 * CAMERA_NEAR_DISTANCE;
double u = minDistanceToGoal / distanceToGoal - 1.0;
if( fabs(t) > fabs(u) ) {
t = u;
}
Vector3d atomTranslation = widget->camera()->backTransformedZAxis() * t;
if (widget->selectedPrimitives().size())
foreach(Primitive *p, widget->selectedPrimitives())
if (p->type() == Primitive::AtomType)
static_cast(p)->setPos(atomTranslation + *static_cast(p)->pos());
if (m_clickedAtom && !widget->isSelected(m_clickedAtom))
m_clickedAtom->setPos(atomTranslation + *m_clickedAtom->pos());
widget->molecule()->update();
}
void ManipulateTool::translate(GLWidget *widget, const Eigen::Vector3d *what,
const QPoint &from, const QPoint &to) const
{
// Set the cursor - this needs to be reset to Qt::ArrowCursor after
// Currently, there's a Qt/Mac bug -- SizeAllCursor looks like a spreadsheet cursor
#ifdef Q_WS_MAC
widget->setCursor(Qt::CrossCursor);
#else
widget->setCursor(Qt::SizeAllCursor);
#endif
// Translate the selected atoms in the x and y sense of the view
Vector3d fromPos = widget->camera()->unProject(from, *what);
Vector3d toPos = widget->camera()->unProject(to, *what);
Vector3d atomTranslation = toPos - fromPos;
if (widget->selectedPrimitives().size())
foreach(Primitive *p, widget->selectedPrimitives())
if (p->type() == Primitive::AtomType)
static_cast(p)->setPos(atomTranslation + *static_cast(p)->pos());
if (m_clickedAtom && !widget->isSelected(m_clickedAtom))
m_clickedAtom->setPos(atomTranslation + *m_clickedAtom->pos());
widget->molecule()->update();
}
void ManipulateTool::rotate(GLWidget *widget, const Eigen::Vector3d *center,
double deltaX, double deltaY) const
{
// Set the cursor - this needs to be reset to Qt::ArrowCursor after
widget->setCursor(Qt::ClosedHandCursor);
// Rotate the selected atoms about the center
// rotate only selected primitives
Transform3d fragmentRotation;
fragmentRotation.matrix().setIdentity();
fragmentRotation.translation() = *center;
fragmentRotation.rotate(
AngleAxisd(deltaY * ROTATION_SPEED, widget->camera()->backTransformedXAxis()));
fragmentRotation.rotate(
AngleAxisd(deltaX * ROTATION_SPEED, widget->camera()->backTransformedYAxis()));
fragmentRotation.translate(- *center);
foreach(Primitive *p, widget->selectedPrimitives())
if (p->type() == Primitive::AtomType)
static_cast(p)->setPos(fragmentRotation * *static_cast(p)->pos());
widget->molecule()->update();
}
void ManipulateTool::tilt(GLWidget *widget, const Eigen::Vector3d *center,
double delta) const
{
// Tilt the selected atoms about the center
Transform3d fragmentRotation;
fragmentRotation.matrix().setIdentity();
fragmentRotation.translation() = *center;
fragmentRotation.rotate(AngleAxisd(delta * ROTATION_SPEED, widget->camera()->backTransformedZAxis()));
fragmentRotation.translate(- *center);
foreach(Primitive *p, widget->selectedPrimitives())
if (p->type() == Primitive::AtomType)
static_cast(p)->setPos(fragmentRotation * *static_cast(p)->pos());
widget->molecule()->update();
}
QUndoCommand* ManipulateTool::mousePressEvent(GLWidget *widget, QMouseEvent *event)
{
event->accept();
m_lastDraggingPosition = event->pos();
// Make sure there aren't modifier keys clicked with the left button
// If the user has a Mac and only a one-button mouse, everything
// looks like a left button
if (event->buttons() & Qt::LeftButton &&
event->modifiers() == Qt::NoModifier)
{
m_leftButtonPressed = true;
// Set the cursor - this needs to be reset to Qt::ArrowCursor after
// Currently, there's a Qt/Mac bug -- SizeAllCursor looks like a spreadsheet cursor
#ifdef Q_WS_MAC
widget->setCursor(Qt::CrossCursor);
#else
widget->setCursor(Qt::SizeAllCursor);
#endif
}
// On a Mac, click and hold the Shift key
if (event->buttons() & Qt::MidButton ||
(event->buttons() & Qt::LeftButton &&
event->modifiers() == Qt::ShiftModifier))
{
m_midButtonPressed = true;
// Set the cursor - this needs to be reset to Qt::ArrowCursor after
widget->setCursor(Qt::SizeVerCursor);
}
// On a Mac, click and hold either the Command or Control Keys
// (Control or Meta in Qt-speak)
if (event->buttons() & Qt::RightButton ||
(event->buttons() & Qt::LeftButton &&
(event->modifiers() == Qt::ControlModifier
|| event->modifiers() == Qt::MetaModifier)))
{
m_rightButtonPressed = true;
// Set the cursor - this needs to be reset to Qt::ArrowCursor after
widget->setCursor(Qt::ClosedHandCursor);
}
m_clickedAtom = widget->computeClickedAtom(event->pos());
// update eyecandy angle
m_xAngleEyecandy = 0;
m_yAngleEyecandy = 0;
widget->update();
QUndoCommand* undo = new MoveAtomCommand(widget->molecule());
return undo;
}
QUndoCommand* ManipulateTool::mouseReleaseEvent(GLWidget *widget, QMouseEvent *event)
{
Q_UNUSED(event);
m_leftButtonPressed = false;
m_midButtonPressed = false;
m_rightButtonPressed = false;
m_clickedAtom = 0;
// Set the cursor back to the default cursor
widget->setCursor(Qt::ArrowCursor);
widget->update();
QUndoCommand* undo = new MoveAtomCommand(widget->molecule());
return undo;
}
QUndoCommand* ManipulateTool::mouseMoveEvent(GLWidget *widget, QMouseEvent *event)
{
if(!widget->molecule())
return 0;
// Get the currently selected atoms from the view
PrimitiveList currentSelection = widget->selectedPrimitives();
QPoint deltaDragging = event->pos() - m_lastDraggingPosition;
// Manipulation can be performed in two ways - centred on an individual atom
// update eyecandy angle
m_xAngleEyecandy += deltaDragging.x() * ROTATION_SPEED;
m_yAngleEyecandy += deltaDragging.y() * ROTATION_SPEED;
if (m_clickedAtom) {
event->accept();
if (m_leftButtonPressed)
{
// translate the molecule following mouse movement
translate(widget, m_clickedAtom->pos(), m_lastDraggingPosition,
event->pos());
}
else if (m_midButtonPressed)
{
if (deltaDragging.y() == 0)
// Perform the rotation
tilt(widget, m_clickedAtom->pos(), deltaDragging.x());
else
// Perform the zoom toward clicked atom
zoom(widget, m_clickedAtom->pos(), deltaDragging.y());
}
else if (m_rightButtonPressed)
{
// Atom centred rotation
rotate(widget, m_clickedAtom->pos(), deltaDragging.x(),
deltaDragging.y());
}
}
else if (currentSelection.size()) {
event->accept();
// Some atoms are selected - work out where the center is
m_selectedPrimitivesCenter.setZero();
int numPrimitives = 0;
foreach(Primitive *hit, currentSelection)
{
if (hit->type() == Primitive::AtomType)
{
Atom *atom = static_cast(hit);
m_selectedPrimitivesCenter += *atom->pos();
numPrimitives++;
}
}
m_selectedPrimitivesCenter /= numPrimitives;
if (m_leftButtonPressed)
{
// translate the molecule following mouse movement
translate(widget, &m_selectedPrimitivesCenter, m_lastDraggingPosition,
event->pos());
}
else if (m_midButtonPressed)
{
// Perform the rotation
tilt(widget, &m_selectedPrimitivesCenter, deltaDragging.x());
// Perform the zoom toward molecule center
zoom(widget, &m_selectedPrimitivesCenter, deltaDragging.y());
}
else if(m_rightButtonPressed)
{
// rotation around the center of the selected atoms
rotate(widget, &m_selectedPrimitivesCenter, deltaDragging.x(), deltaDragging.y());
}
}
m_lastDraggingPosition = event->pos();
widget->update();
return 0;
}
QUndoCommand* ManipulateTool::wheelEvent(GLWidget*, QWheelEvent*)
{
return 0;
}
bool ManipulateTool::paint(GLWidget *widget)
{
int selectedSize = widget->selectedPrimitives().size();
if(m_clickedAtom)
{
if(m_leftButtonPressed)
{
m_eyecandy->drawTranslation(widget, m_clickedAtom, m_clickedAtom->pos());
}
else if(m_midButtonPressed)
{
m_eyecandy->drawZoom(widget, m_clickedAtom, m_clickedAtom->pos());
}
else if(m_rightButtonPressed && selectedSize)
{
m_eyecandy->drawRotation(widget, m_clickedAtom,
m_xAngleEyecandy, m_yAngleEyecandy, m_clickedAtom->pos());
}
}
else if(selectedSize)
{
if(m_leftButtonPressed)
{
m_eyecandy->drawTranslation(widget, &m_selectedPrimitivesCenter, 1.5, 0.);
}
else if(m_midButtonPressed)
{
m_eyecandy->drawZoom(widget, &m_selectedPrimitivesCenter, 1.5);
}
else if(m_rightButtonPressed)
{
m_eyecandy->drawRotation(widget, &m_selectedPrimitivesCenter, 3.,
m_xAngleEyecandy, m_yAngleEyecandy);
}
}
return true;
}
MoveAtomCommand::MoveAtomCommand(Molecule *molecule, QUndoCommand *parent) : QUndoCommand(parent), m_molecule(0)
{
// Store the molecule - this call won't actually move an atom
setText(QObject::tr("Manipulate Atom"));
m_moleculeCopy = *molecule;
m_molecule = molecule;
undone = false;
}
MoveAtomCommand::MoveAtomCommand(Molecule *molecule, int type, QUndoCommand *parent) : QUndoCommand(parent), m_molecule(0)
{
// Store the original molecule before any modifications are made
setText(QObject::tr("Manipulate Atom"));
m_moleculeCopy = *molecule;
m_molecule = molecule;
m_type =type;
undone = false;
}
void MoveAtomCommand::redo()
{
// Move the specified atom to the location given
if (undone)
{
Molecule newMolecule = *m_molecule;
*m_molecule = m_moleculeCopy;
m_moleculeCopy = newMolecule;
}
QUndoCommand::redo();
}
void MoveAtomCommand::undo()
{
// Restore our original molecule
Molecule newMolecule = *m_molecule;
*m_molecule = m_moleculeCopy;
m_moleculeCopy = newMolecule;
undone = true;
}
bool MoveAtomCommand::mergeWith (const QUndoCommand *)
{
// Just return true to repeated calls - we have stored the original molecule
return true;
}
int MoveAtomCommand::id() const
{
return 26011980;
}
}
#include "manipulatetool.moc"
Q_EXPORT_PLUGIN2(manipulatetool, Avogadro::ManipulateToolFactory)