/*
* 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;
}