1 /*************************************************************************** 2 * Copyright (C) 2007-2008 by Harm van Eersel * 3 * Copyright (C) 2009 Tim Vandermeersch * 4 * * 5 * This program is free software; you can redistribute it and/or modify * 6 * it under the terms of the GNU General Public License as published by * 7 * the Free Software Foundation; either version 2 of the License, or * 8 * (at your option) any later version. * 9 * * 10 * This program is distributed in the hope that it will be useful, * 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 13 * GNU General Public License for more details. * 14 * * 15 * You should have received a copy of the GNU General Public License * 16 * along with this program; if not, write to the * 17 * Free Software Foundation, Inc., * 18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * 19 ***************************************************************************/ 20 21 #include <QtGlobal> 22 #include <QPainter> 23 #include <QMenu> 24 #include <QAction> 25 #include <QGraphicsSceneMouseEvent> 26 27 #include <math.h> 28 29 #include "qtdeprecations.h" 30 #include "bond.h" 31 32 #include "atom.h" 33 #include "element.h" 34 #include "molscene.h" 35 #include "math2d.h" 36 #include "molecule.h" 37 #include <actions/bondtypeaction.h> 38 #include <actions/flipbondaction.h> 39 #include <actions/flipstereobondsaction.h> 40 #include "scenesettings.h" 41 #include "settingsitem.h" 42 #include <QDebug> 43 44 #define CHECKFORATOMS if (!m_beginAtom || !m_endAtom || !molecule()) 45 46 namespace Molsketch { 47 orderFromType(const Bond::BondType & type)48 int Bond::orderFromType(const Bond::BondType &type) { 49 return type / 10; 50 } 51 simpleTypeFromOrder(const int & order)52 Bond::BondType Bond::simpleTypeFromOrder(const int &order) 53 { 54 switch (order) 55 { 56 case 1: return Single; 57 case 2: return DoubleLegacy; 58 case 3: return Triple; 59 default: return Invalid; 60 } 61 } 62 Bond(Atom * atomA,Atom * atomB,Bond::BondType type,QGraphicsItem * parent)63 Bond::Bond(Atom* atomA, Atom* atomB, Bond::BondType type, QGraphicsItem* parent) 64 : graphicsItem (parent), 65 m_bondType(type), 66 m_beginAtom(0), 67 m_endAtom(0) 68 { 69 setAtoms(atomA, atomB); 70 71 MolScene* molScene = dynamic_cast<MolScene*>(scene()); 72 if (molScene) 73 setColor(molScene->settings()->defaultColor()->get()); 74 else 75 setColor(QColor(0, 0, 0)); 76 77 setZValue(2); 78 } 79 Bond(const Bond & other,Atom * atomA,Atom * atomB)80 Bond::Bond(const Bond &other, Atom* atomA, Atom* atomB) 81 : graphicsItem(other), 82 m_bondType(other.m_bondType), 83 m_beginAtom(0), 84 m_endAtom(0) 85 { 86 setAtoms(atomA, atomB); 87 } 88 ~Bond()89 Bond::~Bond() {} 90 boundingRect() const91 QRectF Bond::boundingRect() const 92 { 93 CHECKFORATOMS return QRect() ; 94 qreal w = m_endAtom->x() - m_beginAtom->x(); 95 qreal h = m_endAtom->y() - m_beginAtom->y(); 96 return QRectF(mapFromParent(m_beginAtom->pos()) - QPointF(5,5), QSizeF(w+10,h+10)).normalized(); 97 } 98 bondAngle(const Atom * origin) const99 qreal Bond::bondAngle(const Atom *origin) const 100 { 101 CHECKFORATOMS return 0 ; 102 return Molecule::toDegrees(bondAxis().angle() + (origin == endAtom()) * 180.) ; 103 } 104 bondAxis() const105 QLineF Bond::bondAxis() const 106 { 107 CHECKFORATOMS return QLineF(); 108 return QLineF(mapFromParent(m_beginAtom->pos()), 109 mapFromParent(m_endAtom->pos())) ; 110 } 111 limitLineToExtents(const QLineF & line,qreal beginExtent,qreal endExtent)112 QLineF limitLineToExtents(const QLineF& line, qreal beginExtent, qreal endExtent) { 113 return QLineF{line.pointAt(beginExtent), line.pointAt(endExtent)}; 114 } 115 limitLinesToExtents(const QPair<QLineF,QLineF> & lines,qreal beginExtent,qreal endExtent)116 QPair<QLineF, QLineF> limitLinesToExtents(const QPair<QLineF, QLineF> &lines, qreal beginExtent, qreal endExtent) { 117 return qMakePair(limitLineToExtents(lines.first, beginExtent, endExtent), 118 limitLineToExtents(lines.second, beginExtent, endExtent)); 119 } 120 getWedgeBondPath(const QPair<QLineF,QLineF> & outerLines)121 QPainterPath getWedgeBondPath(const QPair<QLineF, QLineF> &outerLines) { 122 QPainterPath path(outerLines.first.p1()); 123 path.lineTo(outerLines.first.p2()); 124 path.lineTo(outerLines.second.p2()); 125 path.lineTo(outerLines.second.p1()); 126 path.closeSubpath(); 127 return path; 128 } 129 drawHashBond() const130 QPainterPath Bond::drawHashBond() const 131 { 132 auto outerLines = getOuterLimitsOfStereoBond(); 133 auto beginExtent = getExtentForStereoBond(beginAtom(), outerLines, false), 134 endExtent = getExtentForStereoBond(endAtom(), outerLines, true); 135 136 auto outerLinesToDraw = limitLinesToExtents(outerLines, beginExtent, endExtent); 137 138 QVector<QPair<qreal, qreal> > sections { 139 {0., .08}, 140 {.23, .31}, 141 {.46, .54}, 142 {.69, .77}, 143 {.92, 1.} 144 }; 145 146 QPainterPath path; 147 for (auto range : sections) 148 path.addPath(getWedgeBondPath(limitLinesToExtents(outerLinesToDraw, range.first, range.second))); 149 150 return path; 151 } 152 getOuterLimitsOfStereoBond() const153 QPair<QLineF, QLineF> Bond::getOuterLimitsOfStereoBond() const { 154 auto axis = bondAxis(); 155 auto normalVector = axis.normalVector().unitVector(); 156 if (MolScene* s = qobject_cast<MolScene*>(scene())) 157 normalVector.setLength(s->settings()->bondWedgeWidth()->get()/2.); 158 normalVector.translate(axis.dx(), axis.dy()); 159 160 return qMakePair(QLineF{axis.p1(), normalVector.p2()}, 161 QLineF{axis.p1(), normalVector.pointAt(-1)}); 162 } 163 mapOuterLineToAtom(const Atom * atom,const QLineF & line,bool reverse) const164 QLineF Bond::mapOuterLineToAtom(const Atom *atom, const QLineF& line, bool reverse) const { 165 return QLineF(mapToItem(atom, reverse ? line.p2() : line.p1()), 166 mapToItem(atom, reverse ? line.p1() : line.p2())); 167 } 168 getExtentForStereoBond(const Atom * atom,const QPair<QLineF,QLineF> & outerLines,bool reverse) const169 qreal Bond::getExtentForStereoBond(const Atom *atom, const QPair<QLineF, QLineF> &outerLines, bool reverse) const { 170 if (reverse) return 1 - atom->getBondExtent(mapOuterLineToAtom(atom, outerLines.first, reverse), 171 mapOuterLineToAtom(atom, outerLines.second, reverse), 172 lineWidth()); 173 return atom->getBondExtent(mapOuterLineToAtom(atom, outerLines.first, reverse), 174 mapOuterLineToAtom(atom, outerLines.second, reverse), 175 lineWidth()); 176 } 177 drawWedgeBond() const178 QPainterPath Bond::drawWedgeBond() const 179 { 180 auto outerLines = getOuterLimitsOfStereoBond(); 181 auto beginExtent = getExtentForStereoBond(beginAtom(), outerLines, false), 182 endExtent = getExtentForStereoBond(endAtom(), outerLines, true); 183 auto outerLinesToDraw = limitLinesToExtents(outerLines, beginExtent, endExtent); 184 return getWedgeBondPath(outerLinesToDraw); 185 } 186 shiftAndElongate(const QLineF & line,const QPointF & shift,const QPointF & elongation)187 QLineF shiftAndElongate(const QLineF &line, const QPointF &shift, const QPointF &elongation) { 188 auto newLine = line.translated(shift); 189 newLine.setPoints(newLine.p1() - elongation, newLine.p2() + elongation); 190 return newLine; 191 } 192 getWedgeBondShape() const193 QPainterPath Bond::getWedgeBondShape() const { 194 auto outerLines = getOuterLimitsOfStereoBond(); 195 auto beginExtent = getExtentForStereoBond(beginAtom(), outerLines, false), 196 endExtent = getExtentForStereoBond(endAtom(), outerLines, true); 197 198 auto outerLinesToDraw = limitLinesToExtents(outerLines, beginExtent, endExtent); 199 200 auto axis = bondAxis(); 201 axis.setLength(bondShapeGap()); 202 QPointF bondShift{axis.dx(), axis.dy()}; 203 QPointF normalShift{axis.dy(), -axis.dx()}; 204 205 auto shiftedOuterLines = qMakePair(shiftAndElongate(outerLinesToDraw.first, normalShift, bondShift), 206 shiftAndElongate(outerLinesToDraw.second, -normalShift, bondShift)); 207 208 return getWedgeBondPath(shiftedOuterLines); 209 } 210 minimumAngle(const Bond * reference,const QSet<Bond * > & others,const Atom * origin,bool clockwise)211 double minimumAngle(const Bond* reference, const QSet<Bond*>& others, const Atom* origin, bool clockwise) 212 { 213 double minAngle = 361; 214 for (auto b : others) 215 { 216 double angle = b->bondAngle(origin) - reference->bondAngle(origin); 217 if (clockwise) angle = 360 - angle; 218 angle = Molecule::toDegrees(angle); 219 minAngle = qMin(minAngle, angle); 220 } 221 return minAngle; 222 } 223 determineDoubleBondOrientation()224 void Bond::determineDoubleBondOrientation() 225 { 226 if (m_bondType != DoubleLegacy) return; 227 m_bondType = DoubleSymmetric; 228 auto beginBondList = m_beginAtom->bonds(); 229 auto beginBonds = toSet(beginBondList); 230 beginBonds -= this; 231 auto endBondList = m_endAtom->bonds(); 232 auto endBonds = toSet(endBondList); 233 endBonds -= this; 234 // no other bonds: symmetric 235 if (beginBonds.empty() && endBonds.empty()) return; 236 // other bonds: add up angles upper and lower 237 double sumUpperAngles = minimumAngle(this, beginBonds, m_beginAtom, false) 238 + minimumAngle(this, endBonds, m_endAtom, true); 239 double sumLowerAngles = minimumAngle(this, beginBonds, m_beginAtom, true) 240 + minimumAngle(this, endBonds, m_endAtom, false); 241 // equal (float tolerance): symmetric 242 if (qAbs(sumUpperAngles - sumLowerAngles) < 1e-7) return; 243 // else: unsymmetric 244 m_bondType = DoubleAsymmetric; 245 // consider swapping atoms 246 if (sumUpperAngles > sumLowerAngles) qSwap(m_beginAtom, m_endAtom); 247 } 248 effectiveBondLine(const Bond * b,const Atom * a)249 QLineF effectiveBondLine(const Bond* b, const Atom* a) 250 { 251 QLineF bl(b->bondAxis()); 252 if (b->beginAtom() != a) 253 return QLineF(bl.p2(), bl.p1()); 254 return bl; 255 } 256 findIdealAngle(const Atom * atom,const Bond * bond,bool inverted)257 qreal findIdealAngle(const Atom* atom, const Bond* bond, bool inverted) 258 { 259 qreal angle = 120; // normal angle (e.g. in benzene) 260 QLineF bondLine(effectiveBondLine(bond, atom)); 261 // TODO skip if we have a displayed label for the beginAtom 262 foreach (const Bond* otherBond, atom->bonds()) 263 { 264 if (otherBond == bond) continue; 265 QLineF otherBondAxis(effectiveBondLine(otherBond, atom)); 266 angle = qMin(angle, (inverted 267 ? otherBondAxis.angleTo(bondLine) 268 : bondLine.angleTo(otherBondAxis))); 269 } 270 return angle*M_PI/360.; // includes division by 2 271 } 272 bondPath() const273 QPainterPath Bond::bondPath() const { 274 // Get beginning and end 275 QPointF begin = determineBondDrawingStart(m_beginAtom, m_endAtom); 276 QPointF end = determineBondDrawingStart(m_endAtom, m_beginAtom); 277 QPointF vb = end - begin; 278 QPointF uvb = vb / sqrt(vb.x()*vb.x() + vb.y()*vb.y()); 279 if (MolScene* s = qobject_cast<MolScene*>(scene())) 280 uvb *= s->settings()->bondSeparation()->get(); 281 QPointF normalVector(uvb.y(), -uvb.x()); 282 283 QPainterPath result; 284 switch ( m_bondType ) // TODO beautify 285 { 286 case Bond::Single: 287 case Bond::DativeDot: 288 case Bond::DativeDash: 289 case Bond::WedgeOrHash: 290 result.moveTo(begin); 291 result.lineTo(end); 292 break; 293 case Bond::Wedge: 294 return drawWedgeBond(); 295 case Bond::Hash: 296 return drawHashBond(); 297 case Bond::DoubleSymmetric: 298 { 299 QPointF offset = .5*normalVector; 300 result.moveTo(begin + offset); 301 result.lineTo(end + offset); 302 result.moveTo(begin - offset); 303 result.lineTo(end - offset); 304 break; 305 } 306 case Bond::CisOrTrans: 307 { 308 QPointF offset = .5*normalVector; 309 result.moveTo(begin + offset); 310 result.lineTo(end - offset); 311 result.moveTo(begin - offset); 312 result.lineTo(end + offset); 313 break; 314 } 315 case Bond::DoubleAsymmetric: 316 { 317 result.moveTo(begin); 318 result.lineTo(end); 319 // now the double part 320 QPointF offset = normalVector; 321 qreal beginAngle = findIdealAngle(beginAtom(), this, false), 322 endAngle = findIdealAngle(endAtom(), this, true), 323 limitAngle = atan(2*QLineF(QPointF(0,0),uvb).length()/QLineF(begin, end).length()); 324 beginAngle = qMax(beginAngle, limitAngle); 325 endAngle = qMax(endAngle, limitAngle); 326 result.moveTo(begin + uvb/tan(beginAngle) + offset); 327 result.lineTo(end - uvb/tan(endAngle) + offset); 328 break; 329 } 330 case Bond::Triple: 331 { 332 result.moveTo(begin); 333 result.lineTo(end); 334 QPointF offset = normalVector; 335 result.moveTo(begin + offset); 336 result.lineTo(end + offset); 337 result.moveTo(begin - offset); 338 result.lineTo(end - offset); 339 break; 340 } 341 case TripleAsymmetric: 342 { 343 result.moveTo(begin); 344 result.lineTo(end); 345 // now the double part 346 QPointF offset = normalVector; 347 qreal beginAngle = findIdealAngle(beginAtom(), this, false), 348 endAngle = findIdealAngle(endAtom(), this, true), 349 limitAngle = atan(2*QLineF(QPointF(0,0),uvb).length()/QLineF(begin, end).length()); 350 beginAngle = qMax(beginAngle, limitAngle); 351 endAngle = qMax(endAngle, limitAngle); 352 result.moveTo(begin + uvb/tan(beginAngle) + offset); 353 result.lineTo(end - uvb/tan(endAngle) + offset); 354 beginAngle = findIdealAngle(beginAtom(), this, true); 355 endAngle = findIdealAngle(endAtom(), this, false); 356 beginAngle = qMax(beginAngle, limitAngle); 357 endAngle = qMax(endAngle, limitAngle); 358 result.moveTo(begin + uvb/tan(beginAngle) - offset); 359 result.lineTo(end - uvb/tan(endAngle) - offset); 360 break; 361 } 362 default: ; 363 } 364 return result; 365 } 366 determineBondDrawingStart(Atom * start,Atom * end) const367 QPointF Bond::determineBondDrawingStart(Atom *start, Atom *end) const { 368 return mapFromScene(start->bondDrawingStart(end, lineWidth())); 369 } 370 coveringBonds() const371 QList<Bond *> Bond::coveringBonds() const 372 { 373 QList<Bond*> result; 374 auto sc = scene(); 375 if (!sc) return result; 376 for (auto item : sc->items()) 377 if (auto bond = dynamic_cast<Bond*>(item)) 378 if (bond->zValue() > zValue() && collidesWithItem(bond)) 379 result << bond; 380 return result; 381 } 382 brokenBondIndicator(const QPointF & point,const QPointF & bondVector,const QPointF & normalVector)383 QPainterPath brokenBondIndicator(const QPointF &point, const QPointF &bondVector, const QPointF &normalVector) { 384 QPointF bondUnitVector(bondVector/QLineF(QPointF(0,0), bondVector).length()); 385 const qreal decorationScale = 0.2; // TODO modifiable - from wedge bond width! 386 QPointF 387 x = normalVector*decorationScale, 388 y = -8*bondUnitVector*decorationScale; 389 QPainterPath path; 390 path.moveTo(-7*x); 391 path.quadTo(-7*x+y, -6*x+y); 392 path.cubicTo(-5*x+y, -5*x, -4*x); 393 path.cubicTo(-3*x, -3*x+y, -2*x+y); 394 path.cubicTo(-x+y, -x, 0*x); 395 path.cubicTo(x, x+y, 2*x+y); 396 path.cubicTo(3*x+y, 3*x, 4*x); 397 path.cubicTo(5*x, 5*x+y, 6*x+y); 398 path.quadTo(7*x+y, 7*x); 399 path.translate(point + 0.3 * bondVector); 400 401 return path; 402 } 403 getBrokenBondIndicatorsPath(const QPointF & begin,const QPointF & end,const QPointF & normalVector) const404 QPainterPath Bond::getBrokenBondIndicatorsPath(const QPointF &begin, const QPointF &end, const QPointF &normalVector) const { 405 QPointF bondUnitVector((end-begin)/QLineF(end, begin).length()); 406 const qreal decorationScale = 0.2; // TODO modifiable - from wedge bond width! 407 QPointF 408 x = normalVector*decorationScale, 409 y = 8*bondUnitVector*decorationScale, 410 xl = normalVector/QLineF(QPointF(), normalVector).length() * bondShapeGap() / 1.5, 411 yl = bondUnitVector * bondShapeGap() / 1.5; 412 QPainterPath path; 413 path.moveTo(-7*x - xl - y - yl); 414 path.lineTo(+7*x + xl - y - yl); 415 path.lineTo(+7*x + xl + yl); 416 path.lineTo(-7*x - xl + yl); 417 path.closeSubpath(); 418 return path.translated(begin + 0.3 * (end - begin)); 419 } 420 421 clipBrokenBondIndicator(const QPointF & point,const QPointF & otherAtom,const QPointF & normalVector) const422 QPainterPath Bond::clipBrokenBondIndicator(const QPointF& point, 423 const QPointF& otherAtom, 424 const QPointF& normalVector) const 425 { 426 QPointF bondVector = otherAtom - point; 427 QPointF bondVectorOffset = bondVector / QLineF(QPointF(), bondVector).length() * 2 * lineWidth(); 428 QPainterPath path(brokenBondIndicator(point, bondVector, normalVector)); 429 path.lineTo(point + 1.4 * normalVector - bondVectorOffset); // TODO get from wedge bond width + linewidth 430 path.lineTo(point - 1.4 * normalVector - bondVectorOffset); // TODO get from wedge bond width + linewidth 431 path.closeSubpath(); 432 return path; 433 } 434 drawBrokenIndicator(QPainter * painter,const QPainterPath & path)435 void drawBrokenIndicator(QPainter* painter, 436 const QPainterPath& path) 437 { 438 QPen subPen(painter->pen()); 439 subPen.setWidthF(subPen.widthF()* 0.75); // TODO fix shape box accordingly 440 painter->save(); 441 painter->setPen(subPen); 442 painter->drawPath(path); 443 painter->restore(); 444 } 445 paintBrokenBondIndicators(QPainter * painter,const QPointF & begin,const QPointF & end,const QPointF & vb,const QPointF & normalVector)446 void Bond::paintBrokenBondIndicators(QPainter *painter, const QPointF &begin, const QPointF &end, const QPointF &vb, const QPointF &normalVector) { 447 if (m_beginAtom->element().isEmpty()) drawBrokenIndicator(painter, brokenBondIndicator(begin, vb, normalVector)); 448 if (m_endAtom->element().isEmpty()) drawBrokenIndicator(painter, brokenBondIndicator(end, -vb, normalVector)); 449 } 450 paint(QPainter * painter,const QStyleOptionGraphicsItem * option,QWidget * widget)451 void Bond::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) 452 { 453 Q_UNUSED(option); 454 Q_UNUSED(widget); 455 CHECKFORATOMS return ; 456 457 if (m_bondType == DoubleLegacy) determineDoubleBondOrientation(); 458 459 QRectF startRect = m_beginAtom->mapRectToItem(this, m_beginAtom->boundingRect()), 460 endRect = m_endAtom->mapRectToItem(this, m_endAtom->boundingRect()); 461 if (startRect.intersects(endRect) && m_beginAtom->isDrawn() && m_endAtom->isDrawn()) return; // TODO should not be necessary anymore 462 463 464 // Get beginning and end 465 QPointF begin = mapFromParent(m_beginAtom->pos()); 466 QPointF end = mapFromParent(m_endAtom->pos()); 467 QPointF vb = end - begin; 468 QPointF uvb = vb / sqrt(vb.x()*vb.x() + vb.y()*vb.y()); 469 if (MolScene* s = qobject_cast<MolScene*>(scene())) 470 uvb *= s->settings()->bondSeparation()->get(); 471 QPointF normalVector(uvb.y(), -uvb.x()); 472 473 begin = determineBondDrawingStart(m_beginAtom, m_endAtom); 474 end = determineBondDrawingStart(m_endAtom, m_beginAtom); 475 // TODO this collision detection rests on the factor used in Atom for the determination of the starting point. Improve! 476 if (m_beginAtom->contains(mapToItem(m_beginAtom, end 477 + 0.75 * lineWidth() * QLineF(QPointF(), m_beginAtom->pos() - m_endAtom->pos()).unitVector().p2())) 478 || m_endAtom->contains(mapToItem(m_endAtom, begin 479 + 0.75* lineWidth() * QLineF(QPointF(), m_endAtom->pos() - m_beginAtom->pos()).unitVector().p2()))) 480 return; 481 482 QPen pen; 483 484 painter->save(); 485 pen.setWidthF(lineWidth()); 486 pen.setCapStyle(Qt::RoundCap); 487 pen.setJoinStyle(Qt::RoundJoin); 488 pen.setColor(getColor()); 489 painter->setPen(pen); 490 491 QPainterPath coveringShapes; 492 for (auto coveringBond : coveringBonds()) 493 coveringShapes += mapFromItem(coveringBond, coveringBond->shape()); 494 495 begin = mapFromParent(m_beginAtom->pos()); 496 end = mapFromParent(m_endAtom->pos()); 497 painter->setClipPath(shape().subtracted(coveringShapes)); 498 paintBrokenBondIndicators(painter, begin, end, vb, normalVector); 499 if (m_beginAtom->element().isEmpty()) 500 painter->setClipPath(painter->clipPath().subtracted(clipBrokenBondIndicator(begin, end, normalVector))); 501 if (m_endAtom->element().isEmpty()) 502 painter->setClipPath(painter->clipPath().subtracted(clipBrokenBondIndicator(end, begin, normalVector))); 503 504 switch ( m_bondType ) // TODO beautify 505 { 506 case Bond::DativeDot: 507 pen.setStyle(Qt::DotLine); 508 break; 509 case Bond::DativeDash: 510 pen.setStyle(Qt::DashLine); 511 break; 512 case Bond::WedgeOrHash: 513 pen.setDashPattern(QVector<qreal>() << 2 << 5); 514 break; 515 case Bond::Wedge: 516 case Bond::Hash: 517 painter->setBrush( QBrush(getColor()) ); 518 default: ; 519 } 520 painter->setPen(pen); 521 QPainterPath path = bondPath(); 522 painter->drawPath(path); 523 524 painter->setClipping(false); 525 painter->setBrush(Qt::NoBrush); 526 527 if (isSelected()) { 528 painter->setPen(Qt::blue); 529 painter->drawPath(shape()); 530 } 531 painter->restore(); 532 533 graphicsItem::paint(painter, option, widget); 534 } 535 itemChange(GraphicsItemChange change,const QVariant & value)536 QVariant Bond::itemChange(GraphicsItemChange change, const QVariant &value) 537 { 538 if (change == ItemPositionChange && parentItem()) parentItem()->update(); 539 return graphicsItem::itemChange(change, value); 540 } 541 shape() const542 QPainterPath Bond::shape() const 543 { 544 CHECKFORATOMS return QPainterPath() ; 545 return outline(); 546 } 547 setType(const BondType & t)548 void Bond::setType(const BondType &t) 549 { 550 m_bondType = t; 551 if (Molecule *m = molecule()) { 552 m->updateElectronSystems(); 553 m->updateTooltip(); 554 } 555 update(); 556 } 557 bondOrder() const558 int Bond::bondOrder() const 559 { 560 return orderFromType(bondType()); 561 } 562 bondType() const563 Bond::BondType Bond::bondType() const 564 { 565 return m_bondType; 566 } 567 beginAtom() const568 Atom* Bond::beginAtom() const 569 { 570 return m_beginAtom; 571 } 572 endAtom() const573 Atom* Bond::endAtom() const 574 { 575 return m_endAtom; 576 } 577 hasAtom(const Atom * atom) const578 bool Bond::hasAtom(const Atom* atom) const 579 { 580 return m_beginAtom == atom || m_endAtom == atom; 581 } 582 otherAtom(const Atom * atom) const583 Atom* Bond::otherAtom(const Atom *atom) const 584 { 585 return (atom == m_beginAtom) ? m_endAtom : m_beginAtom; 586 } 587 setAtoms(Atom * A,Atom * B)588 void Bond::setAtoms(Atom *A, Atom *B) 589 { 590 m_beginAtom = A ; 591 m_endAtom = B ; 592 if (m_beginAtom) { 593 m_beginAtom->updateShape(); 594 setPos(m_beginAtom->scenePos()) ; 595 } 596 if (m_endAtom) m_endAtom->updateShape(); 597 } 598 setAtoms(const QPair<Atom *,Atom * > & atoms)599 void Bond::setAtoms(const QPair<Atom *, Atom *> &atoms) { 600 setAtoms(atoms.first, atoms.second); 601 } 602 atoms() const603 QPair<Atom *, Atom *> Bond::atoms() const { 604 return qMakePair(beginAtom(), endAtom()); 605 } 606 molecule() const607 Molecule* Bond::molecule() const 608 { 609 return dynamic_cast<Molecule*>(this->parentItem()); 610 } 611 shiftVector(const QLineF & vector,qreal shift)612 QLineF Bond::shiftVector(const QLineF &vector, qreal shift) // Shifts a vector on the perpendicular axis 613 { 614 qreal rx1 = vector.x1() + shift*(vector.unitVector().y2()-vector.unitVector().y1()); 615 qreal ry1 = vector.y1() + shift*-(vector.unitVector().x2()-vector.unitVector().x1()); 616 qreal rx2 = vector.x2() + shift*(vector.unitVector().y2()-vector.unitVector().y1()); 617 qreal ry2 = vector.y2() + shift*-(vector.unitVector().x2()-vector.unitVector().x1()); 618 return QLineF(rx1,ry1,rx2,ry2); 619 } 620 xmlName() const621 QString Bond::xmlName() const { return xmlClassName(); } 622 xmlClassName()623 QString Bond::xmlClassName() { return "bond" ; } 624 setCoordinates(const QVector<QPointF> & c)625 void Bond::setCoordinates(const QVector<QPointF> &c) 626 { 627 if (c.size() != 2) return ; 628 CHECKFORATOMS return; 629 m_beginAtom->setCoordinates(c.mid(0,1)) ; 630 m_endAtom->setCoordinates(c.mid(1,1)); 631 } 632 coordinates() const633 QPolygonF Bond::coordinates() const 634 { 635 CHECKFORATOMS return QVector<QPointF>(); 636 return QVector<QPointF>() 637 << m_beginAtom->coordinates() 638 << m_endAtom->coordinates() ; 639 } 640 graphicAttributes() const641 QXmlStreamAttributes Bond::graphicAttributes() const 642 { 643 QXmlStreamAttributes attributes ; 644 attributes.append("atomRefs2", m_beginAtom->index() + " " + m_endAtom->index()) ; 645 attributes.append("type", QString::number(m_bondType)); 646 return attributes ; 647 } 648 readGraphicAttributes(const QXmlStreamAttributes & attributes)649 void Bond::readGraphicAttributes(const QXmlStreamAttributes &attributes) 650 { 651 QStringList atomIndexes = attributes.value("atomRefs2").toString().split(" ") ; 652 if (atomIndexes.size() != 2) return ; 653 654 if (auto mol = molecule()) 655 setAtoms(mol->atom(atomIndexes.first()), 656 mol->atom(atomIndexes.last())) ; 657 m_bondType = (BondType) (attributes.value("type").toString().toInt()); 658 if (attributes.hasAttribute("order")) 659 m_bondType = (BondType) (10 *attributes.value("order").toInt()); 660 } 661 prepareContextMenu(QMenu * contextMenu)662 void Bond::prepareContextMenu(QMenu *contextMenu) // TODO simply use a function that returns the scene's actions pertaining to the item 663 { 664 // Prepare bond menu 665 MolScene *sc = qobject_cast<MolScene*>(scene()); 666 if (sc) 667 { 668 QList<QAction*> actions; 669 actions << sc->findChild<bondTypeAction*>() 670 << sc->findChild<flipBondAction*>() 671 << sc->findChild<flipStereoBondsAction*>(); 672 foreach(QAction* action, actions) 673 { 674 if (!action) continue; 675 QObject::connect(action, SIGNAL(triggered()), contextMenu, SLOT(close())); // TODO check if "changed()" event can accomplish this. Only required for bondType action 676 contextMenu->addAction(action); 677 } 678 } 679 graphicsItem::prepareContextMenu(contextMenu); 680 } 681 682 class LegacyBondStereo : public XmlObjectInterface { 683 Bond *bond; 684 public: LegacyBondStereo(Bond * bond)685 LegacyBondStereo(Bond *bond) : bond(bond) {} readXml(QXmlStreamReader & in)686 QXmlStreamReader& readXml(QXmlStreamReader &in) override { 687 auto text = in.readElementText(); 688 if ("H" == text) bond->setType(Bond::Hash); 689 if ("W" == text) bond->setType(Bond::Wedge); 690 return in; 691 } 692 writeXml(QXmlStreamWriter & out) const693 QXmlStreamWriter& writeXml(QXmlStreamWriter &out) const override { 694 return out; 695 } 696 }; 697 produceChild(const QString & name,const QXmlStreamAttributes & attributes)698 XmlObjectInterface *Bond::produceChild(const QString &name, const QXmlStreamAttributes &attributes) { 699 if (name != "bondStereo" || !attributes.isEmpty()) return nullptr; 700 XmlObjectInterface *helper = new LegacyBondStereo(this); 701 helpers << helper; 702 return helper; 703 } 704 afterReadFinalization()705 void Bond::afterReadFinalization() 706 { 707 for (auto helper : helpers) delete helper; 708 helpers.clear(); 709 } 710 bondShapeGap() const711 qreal Bond::bondShapeGap() const { 712 return lineWidth(); // TODO make this configurable! 713 } 714 outline() const715 QPainterPath Bond::outline() const { 716 // Get beginning and end (taken from bondPath() 717 QPointF begin = determineBondDrawingStart(m_beginAtom, m_endAtom); 718 QPointF end = determineBondDrawingStart(m_endAtom, m_beginAtom); 719 QPointF vb = end - begin; 720 QPointF uvb = vb / sqrt(vb.x()*vb.x() + vb.y()*vb.y()); 721 QPointF bondUnitVector(uvb); 722 QPointF bondNormalVector(uvb.y(), -uvb.x()); 723 if (MolScene* s = qobject_cast<MolScene*>(scene())) 724 uvb *= s->settings()->bondSeparation()->get(); 725 QPointF normalVector(uvb.y(), -uvb.x()); 726 727 QPainterPath result; 728 729 qreal gap = bondShapeGap(); 730 731 switch (m_bondType) { 732 case Bond::Single: 733 case Bond::DativeDot: 734 case Bond::DativeDash: 735 case WedgeOrHash: 736 result.moveTo(begin + gap * (-bondUnitVector + bondNormalVector)); 737 result.lineTo(begin + gap * (-bondUnitVector - bondNormalVector)); 738 result.lineTo(end + gap * (bondUnitVector - bondNormalVector)); 739 result.lineTo(end + gap * (bondUnitVector + bondNormalVector)); 740 result.closeSubpath(); 741 break; 742 case Bond::Wedge: 743 case Bond::Hash: 744 result = getWedgeBondShape(); 745 break; 746 case DoubleSymmetric: 747 { 748 QPointF offset = .5*normalVector; 749 result.moveTo(begin + offset + gap * (-bondUnitVector + bondNormalVector)); 750 result.lineTo(end + offset + gap * (bondUnitVector + bondNormalVector)); 751 result.lineTo(end - offset + gap * (bondUnitVector - bondNormalVector)); 752 result.lineTo(begin - offset + gap * (-bondUnitVector - bondNormalVector)); 753 result.closeSubpath(); 754 break; 755 } 756 case DoubleAsymmetric: 757 { // TODO add more gap 758 result.moveTo(begin + gap * (-bondUnitVector - bondNormalVector)); 759 result.lineTo(end + gap * (bondUnitVector - bondNormalVector)); 760 // now the double part 761 QPointF offset = normalVector; 762 qreal beginAngle = findIdealAngle(beginAtom(), this, false), 763 endAngle = findIdealAngle(endAtom(), this, true), 764 limitAngle = atan(2*QLineF(QPointF(0,0),uvb).length()/QLineF(begin, end).length()); 765 beginAngle = qMax(beginAngle, limitAngle); 766 endAngle = qMax(endAngle, limitAngle); 767 result.lineTo(end - uvb/tan(endAngle) + offset + gap * (bondUnitVector + bondNormalVector)); 768 result.lineTo(begin + uvb/tan(beginAngle) + offset + gap * (-bondUnitVector + bondNormalVector)); 769 result.closeSubpath(); 770 break; 771 } 772 case CisOrTrans: 773 { 774 QPointF offset = .5*normalVector; 775 result.moveTo(begin + offset + gap * (-bondUnitVector + bondNormalVector)); 776 result.lineTo((begin + end)/2 + gap * bondNormalVector); 777 result.lineTo(end + offset + gap * (bondUnitVector + bondNormalVector)); 778 result.lineTo(end - offset + gap * (bondUnitVector - bondNormalVector)); 779 result.lineTo((begin + end)/2 + gap * -bondNormalVector); 780 result.lineTo(begin - offset + gap * (-bondUnitVector - bondNormalVector)); 781 result.closeSubpath(); 782 break; 783 } 784 case Bond::Triple: 785 { 786 QPointF offset = normalVector; 787 result.moveTo(begin + offset + gap * (-bondUnitVector + bondNormalVector)); 788 result.lineTo(end + offset + gap * (bondUnitVector + bondNormalVector)); 789 result.lineTo(end - offset + gap * (bondUnitVector - bondNormalVector)); 790 result.lineTo(begin - offset + gap * (-bondUnitVector - bondNormalVector)); 791 result.closeSubpath(); 792 break; 793 } 794 case Bond::TripleAsymmetric: 795 { // TODO add more gap to central line 796 result.moveTo(begin + gap * -bondUnitVector); 797 // now the double part 798 QPointF offset = normalVector; 799 qreal beginAngle = findIdealAngle(beginAtom(), this, false), 800 endAngle = findIdealAngle(endAtom(), this, true), 801 limitAngle = atan(2*QLineF(QPointF(0,0),uvb).length()/QLineF(begin, end).length()); 802 beginAngle = qMax(beginAngle, limitAngle); 803 endAngle = qMax(endAngle, limitAngle); 804 result.lineTo(begin + uvb/tan(beginAngle) + offset + gap * (-bondUnitVector + bondNormalVector)); 805 result.lineTo(end - uvb/tan(endAngle) + offset + gap * (bondUnitVector + bondNormalVector)); 806 result.lineTo(end + gap * bondUnitVector); 807 beginAngle = findIdealAngle(beginAtom(), this, true); 808 endAngle = findIdealAngle(endAtom(), this, false); 809 beginAngle = qMax(beginAngle, limitAngle); 810 endAngle = qMax(endAngle, limitAngle); 811 result.lineTo(end - uvb/tan(endAngle) - offset + gap * (bondUnitVector - bondNormalVector)); 812 result.lineTo(begin + uvb/tan(beginAngle) - offset + gap * (-bondUnitVector - bondNormalVector)); 813 result.closeSubpath(); 814 break; 815 } 816 default: ; 817 } 818 if (m_beginAtom->element().isEmpty()) 819 (result -= clipBrokenBondIndicator(mapFromParent(m_beginAtom->pos()), mapFromParent(m_endAtom->pos()), normalVector)) 820 += getBrokenBondIndicatorsPath(mapFromParent(m_beginAtom->pos()), mapFromParent(m_endAtom->pos()), normalVector); 821 if (m_endAtom->element().isEmpty()) 822 (result -= clipBrokenBondIndicator(mapFromParent(m_endAtom->pos()), mapFromParent(m_beginAtom->pos()), normalVector)) 823 += getBrokenBondIndicatorsPath(mapFromParent(m_endAtom->pos()), mapFromParent(m_beginAtom->pos()), normalVector); 824 return result; 825 } 826 } // namespace 827