/* * Project: MoleCuilder * Description: creates and alters molecular systems * Copyright (C) 2010-2012 University of Bonn. All rights reserved. * * * This file is part of MoleCuilder. * * MoleCuilder 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. * * MoleCuilder 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 MoleCuilder. If not, see . */ /* * QtMoleculeList.cpp * * Created on: Jan 21, 2010 * Author: crueger */ // include config.h #ifdef HAVE_CONFIG_H #include #endif #include "QtMoleculeList.hpp" #include #include #include "UIElements/Views/Qt4/MoleculeList/QtMoleculeItem.hpp" #include "UIElements/Views/Qt4/MoleculeList/QtMoleculeItemFactory.hpp" #include #include #include "CodePatterns/MemDebug.hpp" #include "CodePatterns/Log.hpp" #include "CodePatterns/Observer/Notification.hpp" #include "Atom/atom.hpp" #include "Descriptors/MoleculeIdDescriptor.hpp" #include "Formula.hpp" #include "molecule.hpp" #include "MoleculeListClass.hpp" using namespace std; const unsigned int QtMoleculeList::update_times_per_second = 20; QtMoleculeList::QtMoleculeList() : Observer("QtMoleculeList"), changing(false), ChangingChildrensVisibility(false), list_accessing(false), update_timer(NULL), callback_DirtyItems(boost::bind(&QtMoleculeList::informDirtyState, this, _1, _2)) { setColumnCount(QtMoleculeItemFactory::COLUMNCOUNT); World::getInstance().signOn(this, World::MoleculeInserted); World::getInstance().signOn(this, World::MoleculeRemoved); refill(); // qRegisterMetaType("QItemSelection"); //connect(this,SIGNAL(cellChanged(int,int)),this,SLOT(moleculeChanged(int,int))); // connect(selectionModel(),SIGNAL(selectionChanged(QItemSelection, QItemSelection)),this,SLOT(rowsSelected(QItemSelection, QItemSelection))); connect(this, SIGNAL(itemChanged(QStandardItem*)), this, SLOT(checkForVisibilityChange(QStandardItem*))); } QtMoleculeList::~QtMoleculeList() { World::getInstance().signOff(this, World::MoleculeInserted); World::getInstance().signOff(this, World::MoleculeRemoved); } QVariant QtMoleculeList::headerData(int section, Qt::Orientation orientation, int role) const { if (role == Qt::DisplayRole) { if (orientation == Qt::Horizontal) { if (section < QtMoleculeItemFactory::COLUMNTYPES_MAX) return QString(QtMoleculeItemFactory::COLUMNNAMES[section]); } } return QVariant(); } void QtMoleculeList::update(Observable *publisher) { ASSERT(0, "QtMoleculeList::update() - we did not sign up for any global updates."); } QtMoleculeItem * QtMoleculeList::MoleculeIdToItem(const moleculeId_t _molid) const { MoleculeItemBiMap_t::left_const_iterator iter = MoleculeItemBiMap.left.find(_molid); if( iter != MoleculeItemBiMap.left.end()) return iter->second; else return NULL; } const moleculeId_t QtMoleculeList::ItemToMoleculeId(const QtMoleculeItem * const _item) const { const MoleculeItemBiMap_t::right_const_iterator iter = MoleculeItemBiMap.right.find(const_cast(_item)); if (iter != MoleculeItemBiMap.right.end()) return iter->second; else return -1; } const moleculeId_t QtMoleculeList::IndexToMoleculeId(const QModelIndex &_index) const { QtMoleculeItem * const item = dynamic_cast(itemFromIndex(_index)); if (item == NULL) return -1; else return ItemToMoleculeId(item); } void QtMoleculeList::recieveNotification(Observable *publisher, Notification_ptr notification) { if (dynamic_cast(publisher) != NULL) { switch (notification->getChannelNo()) { case World::MoleculeInserted: { const molecule * const mol = World::getInstance().lastChanged(); while(list_accessing); list_accessing = true; newItems.push_back( mol ); list_accessing = false; break; } case World::MoleculeRemoved: { const molecule * const mol = World::getInstance().lastChanged(); while(list_accessing); list_accessing = true; toBeRemovedItems.push_back( mol ); // remove in any case, as we also got insert list_accessing = false; const QtMoleculeItem *mol_item = MoleculeIdToItem(mol->getId()); if (mol_item != NULL) { QStandardItem *parent_item = mol_item->parent(); if (parent_item != NULL) addToBeSetOccurrence( parent_item ); else ELOG(2, "QtMoleculeList::recieveNotification() - item to molecule " +toString(mol)+" has no parent."); } break; } default: ASSERT(0, "QtMoleculeList::recieveNotification() - cannot get here, not subscribed to channel " +toString(notification->getChannelNo())); break; } } } void QtMoleculeList::addGroupItem( QStandardItem *&mainitem, const std::string &_molecule_formula) { QList groupItems = QtMoleculeItemFactory::getInstance().createGroupItems(_molecule_formula); mainitem = groupItems.front(); formula.insert( std::make_pair(_molecule_formula, mainitem) ); invisibleRootItem()->appendRow(groupItems); } void QtMoleculeList::addToBeSetOccurrence( QStandardItem *_groupitem) { while (list_accessing); list_accessing = true; toBeSetOccurrenceItems.insert( _groupitem ); list_accessing = false; } void QtMoleculeList::addMoleculeItem( QStandardItem *_groupitem, const moleculeId_t _molid, const std::string &_molecule_formula) { QList molItems = QtMoleculeItemFactory::getInstance().createMoleculeItems(_molid, callback_DirtyItems); QtMoleculeItem *mol_item = dynamic_cast(molItems.front()); ASSERT( mol_item != NULL, "QtMoleculeList::addMoleculeItem() - item from factory was not a QtMoleculeItem?"); MoleculeItemBiMap.left.insert( std::make_pair(_molid, mol_item) ); // LOG(1, "Inserting molecule " << _mol->getId() << ": " << _mol); _groupitem->appendRow(molItems); // here update for the occurence will happen "one tick" later, but we don't // have the groupitem any time earlier addToBeSetOccurrence(_groupitem); } void QtMoleculeList::addMolecule(const molecule * const _mol) { // find group if already in list QStandardItem *groupItem = NULL; const std::string &molecule_formula = _mol->getFormula().toString(); FormulaTreeItemMap_t::const_iterator formulaiter = formula.find(molecule_formula); // new molecule type -> create new group if (formulaiter == formula.end()){ // insert new formula entry into visibility #ifndef NDEBUG std::pair< FormulaVisibilityCountMap_t::iterator, bool> visibilityinserter = #endif FormulaVisibilityCountMap.insert( std::make_pair( molecule_formula, (unsigned int)0) ); ASSERT( visibilityinserter.second, "QtMoleculeList::refill() - molecule with formula " +molecule_formula+" already in FormulaVisibilityCountMap."); // create item and place into Map with formula as key addGroupItem(groupItem, molecule_formula); } else { groupItem = formulaiter->second; } ASSERT( groupItem != NULL, "QtMoleculeList::addMolecule() - item with id "+toString(_mol->getId()) +" has not parent?"); // add molecule addMoleculeItem(groupItem, _mol->getId(), molecule_formula); } void QtMoleculeList::removeItem(QtMoleculeItem * const _item) { const QModelIndex mol_index = indexFromItem(_item); QStandardItem *groupitem = _item->parent(); const QModelIndex group_index = groupitem->index(); removeRows(mol_index.row(), 1, group_index); MoleculeItemBiMap_t::right_iterator removeiter = MoleculeItemBiMap.right.find(_item); ASSERT( removeiter != MoleculeItemBiMap.right.end(), "QtMoleculeList::removeItem() - could not find item in BiMap."); LOG(1, "Erasing molecule " << (removeiter->second)); MoleculeItemBiMap.right.erase(removeiter); // don't need to, groupitem is already present in toBeSetOccurrenceItems // addToBeSetOccurrence(_groupitem); } void QtMoleculeList::refill() { // check timer's presence if (update_timer == NULL) { update_timer = new QTimer(this); connect( update_timer, SIGNAL(timeout()), this, SLOT(checkState())); } else update_timer->stop(); changing = true; const std::vector &molecules = World::getInstance().getAllMolecules(); clear(); formula.clear(); FormulaVisibilityCountMap.clear(); // LOG(1, "Clearing list."); MoleculeItemBiMap.clear(); dirtyItems.clear(); visibilityItems.clear(); toBeMovedItems.clear(); newItems.clear(); toBeRemovedItems.clear(); toBeSetOccurrenceItems.clear(); toBeSetVisibilityItems.clear(); for (std::vector::const_iterator iter = molecules.begin(); iter != molecules.end(); iter++) addMolecule(*iter); changing = false; // activate timer update_timer->start(1000/update_times_per_second); } bool QtMoleculeList::areAnyItemsDirty() { // get whether any items are dirty while (list_accessing); list_accessing = true; bool dirty = false; dirty |= !dirtyItems.empty(); dirty |= !visibilityItems.empty(); dirty |= !newItems.empty(); dirty |= !toBeRemovedItems.empty(); dirty |= !toBeSetOccurrenceItems.empty(); dirty |= !toBeSetVisibilityItems.empty(); list_accessing = false; return dirty; } void QtMoleculeList::checkState() { const bool dirty = areAnyItemsDirty(); // update if required if (dirty) updateItemStates(); } void QtMoleculeList::subjectKilled(Observable *publisher) {} void QtMoleculeList::checkForVisibilityChange(QStandardItem* _item) { // qDebug() << "Item changed called."; if (_item->index().column() == QtMoleculeItemFactory::VISIBILITY) { // qDebug() << "visibilityItem changed: " << (_item->checkState() ? "checked" : "unchecked"); while(list_accessing); list_accessing = true; if ((_item->parent() == NULL) || (_item->parent() == invisibleRootItem())) toBeSetVisibilityItems.insert( _item ); else visibilityItems.insert( dynamic_cast(_item) ); list_accessing = false; } } void QtMoleculeList::setVisibilityForMoleculeItem(QtMoleculeItem* _item) { if (ChangingChildrensVisibility) return; const bool visible = _item->checkState(); const std::string &molecule_formula = _item->getMolecule()->getFormula().toString(); ASSERT( FormulaVisibilityCountMap.count(molecule_formula) != 0, "QtMoleculeList::setVisibilityForMoleculeItem() - molecule with formula " +molecule_formula +" is not present in FormulaVisibilityCountMap."); // get parent QStandardItem *groupItem = _item->parent(); QStandardItem *visgroupItem = invisibleRootItem()->child(groupItem->index().row(), QtMoleculeItemFactory::VISIBILITY); ASSERT( groupItem != NULL, "QtMoleculeList::setVisibilityForMoleculeItem() - item with id " +toString(_item->getMolecule()->getId())+" has not parent?"); // check whether we have to set the group item ChangingChildrensVisibility = true; if (visible) { ++(FormulaVisibilityCountMap[molecule_formula]); // compare with occurence/total number of molecules if (FormulaVisibilityCountMap[molecule_formula] == (unsigned int)(groupItem->rowCount())) visgroupItem->setCheckState(Qt::Checked); } else { --(FormulaVisibilityCountMap[molecule_formula]); // none selected anymore? if (FormulaVisibilityCountMap[molecule_formula] == 0) visgroupItem->setCheckState(Qt::Unchecked); } ChangingChildrensVisibility = false; emit moleculesVisibilityChanged(_item->getMolecule()->getId(), visible); } void QtMoleculeList::setVisibilityForGroupItem(QStandardItem* _item) { if (ChangingChildrensVisibility) return; ChangingChildrensVisibility = true; // go through all children, but don't enter for groupItem once more const bool visible = _item->checkState(); QStandardItem *groupitem = invisibleRootItem()->child( _item->index().row(), QtMoleculeItemFactory::NAME); for (int i=0;irowCount();++i) { QtMoleculeItem *molItem = dynamic_cast( groupitem->child(i, QtMoleculeItemFactory::VISIBILITY)); if (molItem->checkState() != visible) { molItem->setCheckState(visible ? Qt::Checked : Qt::Unchecked); // emit signal emit moleculesVisibilityChanged(molItem->getMolecule()->getId(), visible); } } // set current number of visible children const std::string molecule_formula = invisibleRootItem()->child(_item->row(), QtMoleculeItemFactory::NAME)->text().toStdString(); FormulaVisibilityCountMap_t::iterator countiter = FormulaVisibilityCountMap.find(molecule_formula); ASSERT( countiter != FormulaVisibilityCountMap.end(), "QtMoleculeList::setVisibilityForGroupItem() - molecules "+molecule_formula +" have no entry in visibility count map?"); countiter->second = visible ? groupitem->rowCount() : 0; ChangingChildrensVisibility = false; } void QtMoleculeList::moleculeChanged() { /*int idx = verticalHeaderItem(row)->data(Qt::UserRole).toInt(); molecule *mol = molecules->ReturnIndex(idx); string cellValue = item(row,QtMoleculeItemFactory::NAME)->text().toStdString(); if(mol->getName() != cellValue && cellValue !="") { mol->setName(cellValue); } else if(cellValue==""){ item(row,QtMoleculeItemFactory::NAME)->setText(QString(mol->getName().c_str())); }*/ } int QtMoleculeList::setOccurrence(QStandardItem * const _groupitem) { QModelIndex modelindex = _groupitem->index(); ASSERT( modelindex.isValid(), "QtMoleculeList::setOccurrence() - groupitem not associated to model anymore."); const int index = modelindex.row(); QStandardItem *parent_item = _groupitem->parent() == NULL ? invisibleRootItem() : _groupitem->parent(); ASSERT( parent_item != NULL, "QtMoleculeList::setOccurrence() - group item at "+toString(index) +" does not have a parent?"); QStandardItem *occ_item = parent_item->child(index, QtMoleculeItemFactory::OCCURRENCE); ASSERT( occ_item != NULL, "QtMoleculeList::setOccurrence() - group item at "+toString(index) +" does not have an occurrence?"); const int count = _groupitem->rowCount(); if (count == 0) { // we have to remove the group item completely const std::string molecule_formula = _groupitem->text().toStdString(); formula.erase(molecule_formula); FormulaVisibilityCountMap.erase(molecule_formula); return index; } else { occ_item->setText(QString::number(count)); return -1; } } void QtMoleculeList::readdItem(QtMoleculeItem *_molitem) { // use takeRows of molecule .. QStandardItem *groupitem = _molitem->parent(); ASSERT( groupitem != NULL, "QtMoleculeList::readdItem() - mol item at "+toString(_molitem->index().row()) +" does not have a groupitem?"); QList mol_row = _molitem->parent()->takeRow(_molitem->index().row()); // call setOccurrence on the old group later, if it's not a removal candidate if (groupitem->rowCount() != 0) addToBeSetOccurrence(groupitem); // .. and re-add where new formula fits const std::string molecule_formula = _molitem->getMolecule()->getFormula().toString(); FormulaTreeItemMap_t::iterator iter = formula.find(molecule_formula); if (iter == formula.end()) { // add new group item and formula entry addGroupItem(groupitem, molecule_formula); } else { groupitem = iter->second; } ASSERT( groupitem != NULL, "QtMoleculeList::readdItem() - failed to create a sensible new groupitem"); // finally add again groupitem->appendRow(mol_row); // call setOccurrence on the new group later addToBeSetOccurrence(groupitem); } void QtMoleculeList::informDirtyState( QtMoleculeItem *_item, const QtMoleculeItem::MoveTypes _type) { while (list_accessing); list_accessing = true; dirtyItems.insert(_item); if (_type == QtMoleculeItem::NeedsMove) { // we have to convert whatever item raised the dirty signal to the first // item in the row as otherwise multiple items in the row are selected // as to be moved, i.e. the same row is moved multiple times QStandardItem *group_item = _item->parent(); QtMoleculeItem *mol_item = dynamic_cast(group_item->child(_item->row(), 0)); toBeMovedItems.insert(mol_item); // // add group item, too // toBeSetOccurrenceItems.insert(group_item); } list_accessing = false; } void QtMoleculeList::updateItemStates() { // wait till initial refill has been executed if (changing) return; changing = true; /// copy lists such that new signals for dirty/.. may come in right away // TODO: if we had move semantics ... while (list_accessing); list_accessing = true; list_of_items_t dirtyItems_copy = dirtyItems; dirtyItems.clear(); list_of_items_t visibilityItems_copy = visibilityItems; visibilityItems.clear(); list_of_items_t toBeMovedItems_copy = toBeMovedItems; toBeMovedItems.clear(); std::vector newItems_copy = newItems; newItems.clear(); std::vector toBeRemovedItems_copy = toBeRemovedItems; toBeRemovedItems.clear(); std::set toBeSetOccurrenceItems_copy = toBeSetOccurrenceItems; toBeSetOccurrenceItems.clear(); std::set toBeSetVisibilityItems_copy = toBeSetVisibilityItems; toBeSetVisibilityItems.clear(); list_accessing = false; /// first check consistency among the sets: /// -# if we remove an item, we don't have to update it before anymore /// -# if we remove an item, we don't have to move it before anymore /// -# if we remove an item, we don't have to change its visibility /// -# don't add molecule that are also removed // // remove molecules added and removed immediately in both lists std::vector addedremoved; std::sort(newItems_copy.begin(), newItems_copy.end()); std::sort(toBeRemovedItems_copy.begin(), toBeRemovedItems_copy.end()); std::set_intersection( newItems_copy.begin(), newItems_copy.end(), toBeRemovedItems_copy.begin(), toBeRemovedItems_copy.end(), std::back_inserter(addedremoved)); { std::vector::iterator removeiter = std::set_difference( newItems_copy.begin(), newItems_copy.end(), addedremoved.begin(), addedremoved.end(), newItems_copy.begin()); newItems_copy.erase(removeiter, newItems_copy.end()); } { std::vector::iterator removeiter = std::set_difference( toBeRemovedItems_copy.begin(), toBeRemovedItems_copy.end(), addedremoved.begin(), addedremoved.end(), toBeRemovedItems_copy.begin()); toBeRemovedItems_copy.erase(removeiter, toBeRemovedItems_copy.end()); } addedremoved.clear(); for (std::vector::iterator removeiter = toBeRemovedItems_copy.begin(); removeiter != toBeRemovedItems_copy.end(); ++removeiter) { QtMoleculeItem *item = MoleculeIdToItem((*removeiter)->getId()); dirtyItems_copy.erase(item); toBeMovedItems_copy.erase(item); } for (list_of_items_t::iterator visiter = visibilityItems_copy.begin(); visiter != visibilityItems_copy.end(); ) { QtMoleculeItem * const _item = dynamic_cast( (*visiter)->parent()->child( (*visiter)->index().row(), QtMoleculeItemFactory::NAME)); const moleculeId_t molid = ItemToMoleculeId(_item); const molecule *mol = World::getInstance().getMolecule(MoleculeById(molid)); if (std::binary_search( toBeRemovedItems_copy.begin(), toBeRemovedItems_copy.end(), mol)) visibilityItems_copy.erase(visiter++); else ++visiter; } /// 1a. do the update for each dirty item for (list_of_items_t::const_iterator dirtyiter = dirtyItems_copy.begin(); dirtyiter != dirtyItems_copy.end(); ++dirtyiter) { LOG(1, "Updating item " << *dirtyiter); (*dirtyiter)->updateState(); } /// 1b. do the visibility update for each dirty item for (list_of_items_t::const_iterator visiter = visibilityItems_copy.begin(); visiter != visibilityItems_copy.end(); ++visiter) { // LOG(1, "Updating visibility of item " << *visiter); ASSERT(((*visiter)->parent() != NULL) && ((*visiter)->parent() != invisibleRootItem()), "QtMoleculeList::updateItemStates() - a group item ended up in visibilityItems."); setVisibilityForMoleculeItem(*visiter); } /// 2. move all items that need to be moved for (list_of_items_t::const_iterator moveiter = toBeMovedItems_copy.begin(); moveiter != toBeMovedItems_copy.end(); ++moveiter) { // LOG(1, "Moving item " << *moveiter); readdItem(*moveiter); } /// 3. remove all items whose molecules have been removed for (std::vector::const_iterator removeiter = toBeRemovedItems_copy.begin(); removeiter != toBeRemovedItems_copy.end(); ++removeiter) { // LOG(1, "Removing molecule " << *removeiter); // cannot access directly, molecule is gone QtMoleculeItem *item = MoleculeIdToItem((*removeiter)->getId()); if (item != NULL) removeItem(item); } /// 4. instantiate all new items for (std::vector::const_iterator moliter = newItems_copy.begin(); moliter != newItems_copy.end(); ++moliter) { // LOG(1, "Adding molecule " << (*moliter)->getName()); // check that World knows the molecule still const molecule * const mol = World::getInstance().getMolecule(MoleculeById((*moliter)->getId())); if ((mol != NULL) && (mol == *moliter)) { addMolecule(mol); } } /// 5a. update the group item's occurrence and visibility std::set RowsToRemove; for (std::set::const_iterator groupiter = toBeSetOccurrenceItems_copy.begin(); groupiter != toBeSetOccurrenceItems_copy.end(); ++groupiter) { // LOG(1, "Updating group item " << *groupiter); const int index = setOccurrence(*groupiter); if (index != -1) RowsToRemove.insert(index); } // remove all visibility updates whose row is removed for (std::set::iterator visiter = toBeSetVisibilityItems_copy.begin(); visiter != toBeSetVisibilityItems_copy.end(); ) { if (RowsToRemove.count((*visiter)->index().row()) != 0) toBeSetVisibilityItems_copy.erase(visiter++); else ++visiter; } // update visibility of all group items for (std::set::iterator visiter = toBeSetVisibilityItems_copy.begin(); visiter != toBeSetVisibilityItems_copy.end(); ++visiter) { // LOG(1, "Updating visibility of item " << *visiter); setVisibilityForGroupItem(*visiter); } /// 5b. remove all rows with 0 occurrence starting from last for (std::set::reverse_iterator riter = RowsToRemove.rbegin(); riter != RowsToRemove.rend(); ++riter) { // LOG(1, "Removing group item at row " << *riter); removeRows(*riter, 1, invisibleRootItem()->index()); } // and done changing = false; }