/********************************************************************** PeriodicTableView - Periodic Table Graphics View for Avogadro Copyright (C) 2007-2009 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 Lesser General Public License as published by the Free Software Foundation; either version 2.1 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 "periodictableview.h" #include #include #include #include #include #include #include #include namespace OpenBabel{ OBElementTable etab; } namespace Avogadro { ElementItem::ElementItem(int elementNumber) : m_width(26), m_height(26), m_element(elementNumber) { // Want these items to be selectable setFlags(QGraphicsItem::ItemIsSelectable); m_symbol = OpenBabel::etab.GetSymbol(m_element); std::vector color = OpenBabel::etab.GetRGB(m_element); m_color = new QColor(); m_color->setRgbF(color[0], color[1], color[2]); // Set some custom data to make it easy to figure out which element we are setData(0, m_element); } ElementItem::~ElementItem() { delete m_color; } QRectF ElementItem::boundingRect() const { return QRectF(-m_width/2, -m_height/2, m_width, m_height); } QPainterPath ElementItem::shape() const { QPainterPath path; path.addRect(-m_width/2, -m_height/2, m_width, m_height); return path; } void ElementItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) { // Fill the rectangle with the element colour painter->setBrush(*m_color); // Handle the case where the item is selected // if (m_color->value() > 240) { if (m_element == 1 || m_element == 2 || m_element == 9 || m_element == 47 || m_element == 78) { if (isSelected()) painter->setPen(Qt::darkGray); else painter->setPen(Qt::black); } else { if (isSelected()) painter->setPen(Qt::white); else painter->setPen(Qt::black); } QRectF rect(-m_width/2, -m_height/2, m_width, m_height); painter->drawRect(rect); painter->drawText(rect, Qt::AlignCenter, m_symbol); } ElementDetail::ElementDetail(int elementNumber) : m_width(100), m_height(70), m_element(elementNumber) { } QRectF ElementDetail::boundingRect() const { return QRectF(-m_width/2, -m_height/2, m_width, m_height); } QPainterPath ElementDetail::shape() const { QPainterPath path; path.addRect(-m_width/2, -m_height/2, m_width, m_height); return path; } void ElementDetail::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) { // Set up a font object and get its height QFont font("sans-serif"); font.setPixelSize(12); painter->setFont(font); QFontMetrics fm(font); int pixelHeight = fm.height(); QString symbol = OpenBabel::etab.GetSymbol(m_element); QString name(ElementTranslator::name(m_element)); QString mass = QString::number(OpenBabel::etab.GetMass(m_element), 'f', 3); std::vector color = OpenBabel::etab.GetRGB(m_element); QColor m_color; m_color.setRgbF(color[0], color[1], color[2]); // Draw the element detail border and fill with the element colour painter->setBrush(m_color); painter->setPen(Qt::black); QRectF rect(-m_width/2, -m_height/2, m_width, m_height); painter->drawRect(rect); // Draw the element symbol bigger than everything else font.setPixelSize(24); QFontMetrics fm2(font); pixelHeight = fm2.height(); int pixelWidth = fm2.width(symbol); painter->setFont(font); QRectF symbolRect(-10, -m_height/2 + 8, pixelWidth, pixelHeight); painter->drawText(symbolRect, Qt::AlignCenter, symbol); // Reduce the font size to draw the other parts font.setPixelSize(12); int pixelHeight2 = fm.height(); painter->setFont(font); // I don't seem to be able to get a nice, cross platform layout working here // I would really like to figure out how to make this more portable - ideas? #ifdef Q_WS_MAC // Draw the proton number QRectF protonNumberRect(-m_width/2 - 10, -m_height/2 + 8, m_width/2, pixelHeight2); painter->drawText(protonNumberRect, Qt::AlignRight, QString::number(m_element)); // Draw the mass QRectF massNumberRect(-m_width/2, -m_height/2 + 8 + pixelHeight*1.1, m_width, pixelHeight2); painter->drawText(massNumberRect, Qt::AlignCenter, mass); // Finally the full element name QRectF nameRect(-m_width/2, -m_height/2 + 4 + pixelHeight*1.1 + pixelHeight2, m_width, pixelHeight); painter->drawText(nameRect, Qt::AlignCenter, name); #else // Draw the proton number QRectF protonNumberRect(-m_width/2 - 10, -m_height/2 + 16, m_width/2, pixelHeight2); painter->drawText(protonNumberRect, Qt::AlignRight, QString::number(m_element)); // Draw the mass QRectF massNumberRect(-m_width/2, -m_height/2 + 4 + pixelHeight, m_width, pixelHeight2); painter->drawText(massNumberRect, Qt::AlignCenter, mass); // Finally the full element name QRectF nameRect(-m_width/2, -m_height/2 + pixelHeight + 0.8 * pixelHeight2, m_width, pixelHeight); painter->drawText(nameRect, Qt::AlignCenter, name); #endif } void ElementDetail::elementChanged(int element) { m_element = element; emit update(); } PeriodicTableScene::PeriodicTableScene(QObject *parent) : QGraphicsScene(parent) { int width = 26; int height = 26; ElementDetail *detail = new ElementDetail(1); detail->setPos(6.5 * width, 0.75 * height); addItem(detail); // Connect the slot and the signal... connect(this, SIGNAL(elementChanged(int)), detail, SLOT(elementChanged(int))); ElementItem *item = new ElementItem(1); item->setPos( 0 * width, 0 * height); addItem(item); item = new ElementItem(2); item->setPos(17 * width, 0 * height); addItem(item); item = new ElementItem(3); item->setPos( 0 * width, 1 * height); addItem(item); item = new ElementItem(4); item->setPos( 1 * width, 1 * height); addItem(item); item = new ElementItem(5); item->setPos(12 * width, 1 * height); addItem(item); item = new ElementItem(6); item->setPos(13 * width, 1 * height); addItem(item); item = new ElementItem(7); item->setPos(14 * width, 1 * height); addItem(item); item = new ElementItem(8); item->setPos(15 * width, 1 * height); addItem(item); item = new ElementItem(9); item->setPos(16 * width, 1 * height); addItem(item); item = new ElementItem(10); item->setPos(17 * width, 1 * height); addItem(item); item = new ElementItem(11); item->setPos( 0 * width, 2 * height); addItem(item); item = new ElementItem(12); item->setPos( 1 * width, 2 * height); addItem(item); item = new ElementItem(13); item->setPos(12 * width, 2 * height); addItem(item); item = new ElementItem(14); item->setPos(13 * width, 2 * height); addItem(item); item = new ElementItem(15); item->setPos(14 * width, 2 * height); addItem(item); item = new ElementItem(16); item->setPos(15 * width, 2 * height); addItem(item); item = new ElementItem(17); item->setPos(16 * width, 2 * height); addItem(item); item = new ElementItem(18); item->setPos(17 * width, 2 * height); addItem(item); int element = 19; for (int i = 3; i < 5; i++) { for (int j = 0; j < 18; j++) { item = new ElementItem(element++); item->setPos(j * width, i * height); addItem(item); } } item = new ElementItem(element++); item->setPos( 0 * width, 5 * height); addItem(item); item = new ElementItem(element++); item->setPos( 1 * width, 5 * height); addItem(item); element = 71; for (int i = 2; i < 18; ++i) { item = new ElementItem(element++); item->setPos(i * width, 5 * height); addItem(item); } item = new ElementItem(element++); item->setPos( 0 * width, 6 * height); addItem(item); item = new ElementItem(element++); item->setPos( 1 * width, 6 * height); addItem(item); element = 103; for (int i = 2; i < 16; ++i) { item = new ElementItem(element++); item->setPos(i * width, 6 * height); addItem(item); } // Now for the weird ones at the bottom... element = 57; for (int i = 2; i < 16; ++i) { item = new ElementItem(element++); item->setPos(i * width, 7.5 * height); addItem(item); } element = 89; for (int i = 2; i < 16; ++i) { item = new ElementItem(element++); item->setPos(i * width, 8.5 * height); addItem(item); } } void PeriodicTableScene::mousePressEvent(QGraphicsSceneMouseEvent *event) { if (event->button() != Qt::LeftButton) return; QGraphicsItem *item = QGraphicsScene::itemAt(event->scenePos()); if (item->data(0).toInt() > 0 && item->data(0).toInt() < 119) emit(elementChanged(item->data(0).toInt())); QGraphicsScene::mousePressEvent(event); } void PeriodicTableScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { QGraphicsScene::mouseMoveEvent(event); } void PeriodicTableScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { QGraphicsScene::mouseReleaseEvent(event); } PeriodicTableView::PeriodicTableView(QWidget *parent) : QGraphicsView(parent) { // Use a small title bar (Qt::Tool) with no minimize or maximise buttons setWindowFlags(Qt::Dialog | Qt::Tool); PeriodicTableScene *table = new PeriodicTableScene; table->setSceneRect(-20, -20, 480, 260); table->setItemIndexMethod(QGraphicsScene::NoIndex); table->setBackgroundBrush(Qt::white); setScene(table); setRenderHint(QPainter::Antialiasing); setWindowTitle(tr("Periodic Table")); resize(490, 270); setFixedSize(490, 270); connect(table, SIGNAL(elementChanged(int)), this, SLOT(elementClicked(int))); } PeriodicTableView::~PeriodicTableView() { delete scene(); } void PeriodicTableView::elementClicked(int id) { emit(elementChanged(id)); } void PeriodicTableView::mouseDoubleClickEvent(QMouseEvent *) { close(); } } // End namespace Avogadro #include "periodictableview.moc"