Logo Search packages:      
Sourcecode: ugene version File versions

AnnotationsTreeView.cpp

/*****************************************************************
* Unipro UGENE - Integrated Bioinformatics Suite
* Copyright (C) 2008 Unipro, Russia (http://ugene.unipro.ru)
* All Rights Reserved
* 
*     This source code is distributed under the terms of the
*     GNU General Public License. See the files COPYING and LICENSE
*     for details.
*****************************************************************/

#include "AnnotationsTreeView.h"

#include "AnnotatedDNAView.h"
#include "ADVConstants.h"

#include <core_api/AppContext.h>
#include <core_api/DocumentModel.h>
#include <core_api/Settings.h>
#include <core_api/DBXRefRegistry.h>

#include <document_format/GenbankLocationParser.h>
#include <gobjects/AnnotationSettings.h>
#include <gobjects/AnnotationTableObject.h>
#include <gobjects/GObjectTypes.h>
#include <gobjects/DNASequenceObject.h>
#include <selection/AnnotationSelection.h>
#include <util_gui/ProjectTreeController.h>
#include <util_gui/ProjectTreeItemSelectorDialog.h>
#include <util_gui/GUIUtils.h>

#include <QtCore/QFileInfo>
#include <QtGui/QVBoxLayout>
#include <QtGui/QPainter>
#include <QtGui/QMenu>
#include <QtGui/QClipboard>
#include <QtGui/QToolTip>
#include <QtGui/QMessageBox>
#include <QtGui/QHeaderView>

/* TRANSLATOR GB2::AnnotationsTreeView */

namespace GB2 {

class TreeSorter {
public:
    TreeSorter(QTreeWidget* t) : tree(t) {
        col = tree->sortColumn();
        tree->setSortingEnabled(false);
    }
    virtual ~TreeSorter() {
        tree->setSortingEnabled(true);
        if (col < tree->columnCount()) {
            tree->sortByColumn(col);
        }
    }
    QTreeWidget* tree;
    int col;
};

#define SETTINGS_ROOT QString("view_adv/annotations_tree_view/")
#define COLUMN_SIZES QString("columnSizes")

AnnotationsTreeView::AnnotationsTreeView(AnnotatedDNAView* _ctx) : ctx(_ctx){
    lastMB = Qt::NoButton;
    lastClickedColumn = 0;

    tree = new QTreeWidget(this);
    
    tree->setSortingEnabled(true);
    tree->sortByColumn(1);
    
    tree->setColumnCount(2);
    headerLabels << tr("annotation_name_label") << tr("qualifier_value_label");

    tree->setHeaderLabels(headerLabels);
    tree->setSelectionMode(QAbstractItemView::ExtendedSelection);
    tree->viewport()->installEventFilter(this);
    tree->setMouseTracking(true);

    connect(tree, SIGNAL(itemEntered(QTreeWidgetItem*, int)), SLOT(sl_itemEntered(QTreeWidgetItem*, int)));
    connect(tree, SIGNAL(itemClicked(QTreeWidgetItem*, int)), SLOT(sl_itemClicked(QTreeWidgetItem*, int)));
    connect(tree, SIGNAL(itemExpanded(QTreeWidgetItem*)), SLOT(sl_itemExpanded(QTreeWidgetItem*)));

    QVBoxLayout *layout = new QVBoxLayout();
    layout->setMargin(0);
    layout->addWidget(tree);
    setLayout(layout);
    
    restoreWidgetState();

    connect(ctx, SIGNAL(si_buildPopupMenu(GObjectView*, QMenu*)), SLOT(sl_onBuildPopupMenu(GObjectView*, QMenu*)));
    connect(ctx, SIGNAL(si_annotationObjectAdded(AnnotationTableObject*)), SLOT(sl_onAnnotationObjectAdded(AnnotationTableObject*)));
    connect(ctx, SIGNAL(si_annotationObjectRemoved(AnnotationTableObject*)), SLOT(sl_onAnnotationObjectRemoved(AnnotationTableObject*)));
    foreach(AnnotationTableObject* obj, ctx->getAnnotationObjects()) {
        sl_onAnnotationObjectAdded(obj);
    }
    connectAnnotationSelection();
    connectAnnotationGroupSelection();
    connect(tree, SIGNAL(itemSelectionChanged()), SLOT(sl_onItemSelectionChanged()));

    connect(AppContext::getAnnotationsSettingsRegistry(),
        SIGNAL(si_annotationSettingsChanged(const QStringList&)),
        SLOT(sl_onAnnotationSettingsChanged(const QStringList&)));


    addColumnIcon = QIcon(":core/images/add_column.png");
    removeColumnIcon = QIcon(":core/images/remove_column.png");
    
    addAnnotationObjectAction = new QAction(tr("add_annotation_object"), this);
    connect(addAnnotationObjectAction, SIGNAL(triggered()), SLOT(sl_onAddAnnotationObjectToView()));

    removeObjectsFromViewAction = new QAction(tr("remove_annotation_objects_from_view"), this);
    removeObjectsFromViewAction->setShortcut(QKeySequence(Qt::SHIFT| Qt::Key_Delete));
    removeObjectsFromViewAction->setShortcutContext(Qt::WidgetShortcut);
    connect(removeObjectsFromViewAction, SIGNAL(triggered()), SLOT(sl_onRemoveObjectsFromView()));
    tree->addAction(removeObjectsFromViewAction);

    removeAnnotationsFromDocumentAction = new QAction(tr("remove_selected_annotations_from_doc"), this);
    removeAnnotationsFromDocumentAction->setShortcut(QKeySequence(Qt::Key_Delete));
    removeAnnotationsFromDocumentAction->setShortcutContext(Qt::WindowShortcut);
    connect(removeAnnotationsFromDocumentAction, SIGNAL(triggered()), SLOT(sl_onRemoveAnnotationsFromDocument()));
    tree->addAction(removeAnnotationsFromDocumentAction);

    copyQualifierAction = new QAction(tr("copy_qualifier_value"), this);
    connect(copyQualifierAction, SIGNAL(triggered()), SLOT(sl_onCopyQualifierValue()));

    copyQualifierURLAction = new QAction(tr("copy_qualifier_url"), this);
    connect(copyQualifierURLAction, SIGNAL(triggered()), SLOT(sl_onCopyQualifierURL()));

    toggleQualifierColumnAction = new QAction(tr("toggle_qual_column"), this);
    connect(toggleQualifierColumnAction, SIGNAL(triggered()), SLOT(sl_onToggleQualifierColumn()));

    removeColumnByHeaderClickAction = new QAction(tr("hide_qual_column"), this);
    removeColumnByHeaderClickAction->setIcon(removeColumnIcon);
    connect(removeColumnByHeaderClickAction, SIGNAL(triggered()), SLOT(sl_onRemoveColumnByHeaderClick()));

    copyColumnTextAction = new QAction(tr("copy_column_text"), this);
    connect(copyColumnTextAction, SIGNAL(triggered()), SLOT(sl_onCopyColumnText()));

    copyColumnURLAction = new QAction(tr("copy_column_url"), this);
    connect(copyColumnURLAction, SIGNAL(triggered()), SLOT(sl_onCopyColumnURL()));

    updateState();
}

void AnnotationsTreeView::restoreWidgetState() { 
    QStringList geom = AppContext::getSettings()->getValue(SETTINGS_ROOT + COLUMN_SIZES, QStringList()).toStringList();
    if (geom.isEmpty()) {
        tree->setColumnWidth(0, 300);
        tree->setColumnWidth(1, 300);
    } else {
        for (int i=0;i<geom.size(); i++) {
            const QString& w = geom.at(i);
            bool ok = false;
            int width  = w.toInt(&ok);
            if (ok) {
                tree->setColumnWidth(i, width);
            }
        }
    }
}

void AnnotationsTreeView::saveWidgetState() {
    QStringList geom;
    for (int i=0; i < tree->columnCount(); i++) {
        QString s = QString::number(tree->columnWidth(i));
        geom.append(s);
    }
    AppContext::getSettings()->setValue(SETTINGS_ROOT+COLUMN_SIZES, geom);
}


AVGroupItem* AnnotationsTreeView::findGroupItem(const AnnotationGroup* g) const {
    if (g->getParentGroup() == NULL) {
        for (int i=0, n = tree->topLevelItemCount(); i<n; i++) {
            AVItem* item = (AVItem*)tree->topLevelItem(i);
            assert(item->type == AVItemType_Group);
            AVGroupItem* groupItem = (AVGroupItem*)item;
            if (groupItem->group == g) {
                return groupItem;
            }
        }
    } else {
        AVGroupItem* parentGroupItem = findGroupItem(g->getParentGroup());
        if (parentGroupItem != NULL) {
            for(int i = 0, n = parentGroupItem->childCount(); i < n; i++) {
                AVItem* item = (AVItem*)parentGroupItem->child(i);
                if (item->type != AVItemType_Group) {
                    continue;
                }
                AVGroupItem* gItem = (AVGroupItem*)item;
                if (gItem->group == g) {
                    return gItem;
                }
            }
        }
    }
    return NULL;
}

AVAnnotationItem* AnnotationsTreeView::findAnnotationItem(const AVGroupItem* groupItem, const Annotation* a) const {
    for(int i = 0, n = groupItem->childCount(); i < n; i++) {
        AVItem* item = (AVItem*)groupItem->child(i);
        if (item->type != AVItemType_Annotation) {
            continue;
        }
        AVAnnotationItem* aItem = (AVAnnotationItem*)item;
        if (aItem->annotation == a) {
            return aItem;
        }
    }
    return NULL;
}

AVAnnotationItem* AnnotationsTreeView::findAnnotationItem(const AnnotationGroup* g, const Annotation* a) const {
    AVGroupItem* groupItem = findGroupItem(g);
    if (groupItem == NULL) {
        return NULL;
    }
    return findAnnotationItem(groupItem, a);
}

QList<AVAnnotationItem*> AnnotationsTreeView::findAnnotationItems(const Annotation* a) const {
    QList<AVAnnotationItem*> res;
    foreach(AnnotationGroup* g, a->getGroups()) {
        AVGroupItem* gItem = findGroupItem(g);
        AVAnnotationItem* aItem = findAnnotationItem(gItem, a);
        res.append(aItem);
    }
    return res;
}

void AnnotationsTreeView::connectAnnotationSelection() {
    connect(ctx->getAnnotationsSelection(), 
        SIGNAL(si_selectionChanged(AnnotationSelection*, const QList<Annotation*>&, const QList<Annotation*>& )), 
        SLOT(sl_onAnnotationSelectionChanged(AnnotationSelection*, const QList<Annotation*>&, const QList<Annotation*>&)));
}

void AnnotationsTreeView::connectAnnotationGroupSelection() {
    connect(ctx->getAnnotationsGroupSelection(), 
        SIGNAL(si_selectionChanged(AnnotationGroupSelection*, const QList<AnnotationGroup*>&, const QList<AnnotationGroup*>& )), 
        SLOT(sl_onAnnotationGroupSelectionChanged(AnnotationGroupSelection*, const QList<AnnotationGroup*>&, const QList<AnnotationGroup*>&)));
}

void AnnotationsTreeView::sl_onItemSelectionChanged() {
    AnnotationSelection* as = ctx->getAnnotationsSelection();
    as->disconnect(this);
    as->clear();

    AnnotationGroupSelection* ags = ctx->getAnnotationsGroupSelection();
    ags->disconnect(this);
    ags->clear();


    QList<QTreeWidgetItem*> items = tree->selectedItems();
    foreach(QTreeWidgetItem* i, items) {
        AVItem* item  = (AVItem*)i;
        if (item->type == AVItemType_Annotation) {
            AVAnnotationItem* aItem = (AVAnnotationItem*)item;
            as->addToSelection(aItem->annotation);
        } else if (item->type == AVItemType_Group) {
            AVGroupItem* gItem = (AVGroupItem*)item;
            ags->addToSelection(gItem->group);
        }
    }
    connectAnnotationSelection();
    connectAnnotationGroupSelection();
    updateState();
}

void AnnotationsTreeView::sl_onAnnotationSelectionChanged(AnnotationSelection* s, const QList<Annotation*>& added, const QList<Annotation*>& removed) {
    Q_UNUSED(s);
    tree->disconnect(this, SIGNAL(sl_onItemSelectionChanged()));

    foreach(Annotation* a, removed) {
        foreach(AnnotationGroup* g, a->getGroups()) {
            AVAnnotationItem* item = findAnnotationItem(g, a);
            if (item->isSelected()) {
                item->setSelected(false);
            }
        }
    }
    AVAnnotationItem* toVisible = NULL;
    foreach(Annotation* a, added) {
        foreach(AnnotationGroup* g, a->getGroups()) {
            AVAnnotationItem* item = findAnnotationItem(g, a);
            if (!item->isSelected()) {
                item->setSelected(true);
                for (QTreeWidgetItem* p = item->parent(); p!=NULL; p = p->parent()) {
                    if (!p->isExpanded()) {
                        p->setExpanded(true);
                    }
                }
            }
            toVisible = item;
        }
    }
    
    connect(tree, SIGNAL(itemSelectionChanged()), SLOT(sl_onItemSelectionChanged ()));

    //make item visible if special conditions are met
    if (toVisible!=NULL && added.size() == 1) {
        tree->scrollToItem(toVisible, QAbstractItemView::EnsureVisible);
    }
    updateState();
}


void AnnotationsTreeView::sl_onAnnotationGroupSelectionChanged(AnnotationGroupSelection* s, 
                                                               const QList<AnnotationGroup*>& added, 
                                                               const QList<AnnotationGroup*>& removed) 
{
    Q_UNUSED(s);

    foreach(AnnotationGroup* g, removed) {
        AVGroupItem* item = findGroupItem(g);
        if (item->isSelected()) {
            item->setSelected(false);
        }
    }

    foreach(AnnotationGroup* g, added) {
        AVGroupItem* item = findGroupItem(g);
        if (!item->isSelected()) {
            item->setSelected(true);
        }
    }

    if (added.size() == 1) {
        AVGroupItem* item = findGroupItem(added.first());
        tree->scrollToItem(item, QAbstractItemView::EnsureVisible);
    }
}


void AnnotationsTreeView::sl_onAnnotationObjectAdded(AnnotationTableObject* obj) {
    TreeSorter ts(tree);
    
    assert(findGroupItem(obj->getRootGroup()) == NULL);
    AVGroupItem* groupItem = buildGroupTree(NULL, obj->getRootGroup());
    tree->addTopLevelItem(groupItem);
    connect(obj, SIGNAL(si_onAnnotationsAdded(const QList<Annotation*>&)), SLOT(sl_onAnnotationsAdded(const QList<Annotation*>&)));
    connect(obj, SIGNAL(si_onAnnotationsRemoved(const QList<Annotation*>&)), SLOT(sl_onAnnotationsRemoved(const QList<Annotation*>&)));
    connect(obj, SIGNAL(si_onAnnotationModified(const AnnotationModification&)), SLOT(sl_onAnnotationModified(const AnnotationModification&)));

    connect(obj, SIGNAL(si_onGroupCreated(AnnotationGroup*)), SLOT(sl_onGroupCreated(AnnotationGroup*)));
    connect(obj, SIGNAL(si_onGroupRemoving(AnnotationGroup*)), SLOT(sl_onGroupRemoving(AnnotationGroup*)));
    connect(obj, SIGNAL(si_onGroupRenamed(AnnotationGroup*, const QString& )), SLOT(sl_onGroupRenamed(AnnotationGroup*, const QString& )));
}

void AnnotationsTreeView::sl_onAnnotationObjectRemoved(AnnotationTableObject* obj) {
    TreeSorter ts(tree);

    AVGroupItem* groupItem = findGroupItem(obj->getRootGroup());
    assert(groupItem!=NULL);
    delete groupItem;
}


void AnnotationsTreeView::sl_onAnnotationsAdded(const QList<Annotation*>& as) {
    TreeSorter ts(tree);

    QSet<AVGroupItem*> toUpdate;
    foreach(Annotation* a, as) {
        foreach(AnnotationGroup* ag, a->getGroups()) {
            AVGroupItem* gi = findGroupItem(ag);
            if (gi!=NULL) {
                if (findAnnotationItem(gi, a)== NULL) {
                    buildAnnotationTree(gi, a);
                }
            } else {
                AnnotationGroup* childGroup = ag;
                while(true) {
                    gi = findGroupItem(childGroup->getParentGroup());
                    if (gi != NULL) {
                        break;
                    }
                    childGroup = childGroup->getParentGroup();
                }
                assert(gi!=NULL && childGroup!=NULL);
                buildGroupTree(gi, childGroup);
            }
            assert(gi!=NULL);
            toUpdate.insert(gi);
        }
    }
    while (!toUpdate.isEmpty()) {
        AVGroupItem* i= *toUpdate.begin();
        toUpdate.remove(i);
        i->updateVisual();
        AVGroupItem* p = (AVGroupItem*)i->parent();
        if (p!=NULL) {
            toUpdate.insert(p);
        }
    }
}

void AnnotationsTreeView::sl_onAnnotationsRemoved(const QList<Annotation*>& as) {
    TreeSorter ts(tree);

    QSet<AVGroupItem*> groupsToUpdate;
    foreach(Annotation* a, as) {
        QList<AVAnnotationItem*> aItems = findAnnotationItems(a);
        assert(!aItems.isEmpty());
        foreach(AVAnnotationItem* ai, aItems) {
            groupsToUpdate.insert((AVGroupItem*)ai->parent());
            delete ai;
        }
    }
    foreach(AVGroupItem* g, groupsToUpdate) {
        g->updateVisual();
    }
}

void AnnotationsTreeView::sl_onAnnotationModified(const AnnotationModification& md) {
    switch(md.type) {
        case AnnotationModification_NameChanged: 
        case AnnotationModification_LocationChanged:
            {
                QList<AVAnnotationItem*> aItems = findAnnotationItems(md.annotation);
                assert(!aItems.isEmpty());
                foreach(AVAnnotationItem* ai, aItems) {
                    ai->updateVisual(ATVAnnUpdateFlag_BaseColumns);
                }
            }
            break;

        case AnnotationModification_QualifierAdded:
        case AnnotationModification_QualifierRemoving:
        case AnnotationModification_QualifierValueChanged:
            //TODO: do not forget about child indicator!
            assert(0); //TODO: not implemented
            break;

        case AnnotationModification_AddedToGroup:
            {
                const AnnotationGroupModification& gmd = (const AnnotationGroupModification&)md;
                AVGroupItem* gi = findGroupItem(gmd.group);
                assert(gi);
                buildAnnotationTree(gi, gmd.annotation);
            }
            break;

        case AnnotationModification_RemovedFromGroup:
            {
                const AnnotationGroupModification& gmd = (const AnnotationGroupModification&)md;
                AVAnnotationItem* ai = findAnnotationItem(gmd.group, gmd.annotation);
                assert(ai);
                delete ai;

            }
            break;
    }
}

void AnnotationsTreeView::sl_onGroupCreated(AnnotationGroup* g) {
    AVGroupItem* pi = g->getParentGroup()== NULL ? NULL : findGroupItem(g->getParentGroup());
    buildGroupTree(pi, g);
}

void AnnotationsTreeView::sl_onGroupRemoving(AnnotationGroup* g) {
    AVGroupItem* gi = findGroupItem(g);
    assert(gi!=NULL);
    AVGroupItem* pg  = (AVGroupItem*)gi->parent();
    delete gi;

    if (pg!=NULL) {
        pg->updateVisual();
    }

}

void AnnotationsTreeView::sl_onGroupRenamed(AnnotationGroup* g, const QString& oldName) {
    Q_UNUSED(oldName);
    AVGroupItem* gi = findGroupItem(g);
    assert(gi!=NULL);
    gi->updateVisual();
}

AVGroupItem* AnnotationsTreeView::buildGroupTree(AVGroupItem* parentGroupItem, AnnotationGroup* g) {
    AVGroupItem* groupItem = new AVGroupItem(this, parentGroupItem, g);
    const QList<AnnotationGroup*>& subgroups = g->getSubgroups();
    foreach(AnnotationGroup* subgroup, subgroups) {
        buildGroupTree(groupItem, subgroup);
    }
    const QList<Annotation*>& annotations = g->getAnnotations();
    foreach(Annotation* a, annotations) {
        buildAnnotationTree(groupItem, a);
    }
    return groupItem;
}

AVAnnotationItem* AnnotationsTreeView::buildAnnotationTree(AVGroupItem* parentGroup, Annotation* a) {
    AVAnnotationItem* annotationItem = new AVAnnotationItem(parentGroup, a);
    const QList<Qualifier>& qualifiers = a->getQualifiers();
    if (!qualifiers.isEmpty()) {
        annotationItem->setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator);
    }
    return annotationItem;
}

void AnnotationsTreeView::populateAnnotationQualifiers(AVAnnotationItem* ai){
    assert(ai->childIndicatorPolicy() == QTreeWidgetItem::ShowIndicator);
    assert(ai->childCount() == 0);
    const QList<Qualifier>& qualifiers = ai->annotation->getQualifiers();
    foreach(const Qualifier& q, qualifiers) {
        AVQualifierItem* curQualifierItem = new AVQualifierItem(ai, q);
        Q_UNUSED(curQualifierItem);
    }
    ai->setChildIndicatorPolicy(QTreeWidgetItem::DontShowIndicatorWhenChildless);
}

void AnnotationsTreeView::sl_onAnnotationSettingsChanged(const QStringList& changedSettings) {
    foreach (const QString& name , changedSettings) {
        // clear icons cache with color associated with this name
        AVAnnotationItem::getIconsCache().remove(name);

        //update all top-level groups with the same annotation name -> track visible state
        for (int i=0; i<tree->topLevelItemCount(); i++) {
            AVGroupItem* top = (AVGroupItem*)tree->topLevelItem(i);
            for (int j = 0; j < top->childCount(); j++) {
                AVItem* item = (AVItem*)top->child(j);
                if (item->type == AVItemType_Group) {
                    AVGroupItem* level1 = (AVGroupItem*)item;
                    if (level1->group->getGroupName() == name) {
                        level1->updateVisual();
                    }
                }
            }
            top->updateAnnotations(name, ATVAnnUpdateFlag_BaseColumns);
        }

        //update all annotations with this name -> track visible state and color
    }
}

void AnnotationsTreeView::updateColumnContextActions(AVItem* item, int col) {
    copyColumnTextAction->setEnabled(item!=NULL && (col >= 2 || (item->type == AVItemType_Annotation && col == 1)) && !item->text(col).isEmpty());
    copyColumnURLAction->setEnabled(item!=NULL && col >= 2 && item->isColumnLinked(col));
    if (!copyColumnTextAction->isEnabled()) {
        copyColumnTextAction->setText(tr("copy_column_text"));
    } else {
        QString colName;
        if (col >= 2) {
            assert(qColumns.size() > col - 2);
            colName = qColumns[col - 2];
            copyColumnTextAction->setText(tr("copy_column_'%1'_text").arg(colName));
        } else {
            AVAnnotationItem* ai = (AVAnnotationItem*)item;
            copyColumnTextAction->setText(tr("copy_'%1'_annotation_location").arg(ai->annotation->getAnnotationName()));
        }
    }

    if (!copyColumnURLAction->isEnabled()) {
        copyColumnURLAction->setText(tr("copy_column_url"));
    } else {
        assert(qColumns.size() > col - 2);
        QString colName = qColumns[col - 2];
        copyColumnURLAction->setText(tr("copy_column_'%1'_url").arg(colName));
    }
}

void AnnotationsTreeView::sl_onBuildPopupMenu(GObjectView* v, QMenu* m) {
    Q_UNUSED(v);
    
    // Add actions that not depends on the point popup is called
    adjustMenu(m);
    
    QPoint globalPos = QCursor::pos();
    QPoint treePos = tree->mapFromGlobal(globalPos);
    if (!tree->rect().contains(treePos)) {
        return;
    }
    
    // Check is popup is called for column header
    QHeaderView* header = tree->header();
    QPoint headerPoint = header->mapFromGlobal(globalPos);
    if (header->rect().contains(headerPoint)) {
        int idx = header->logicalIndexAt(headerPoint);
        if (idx >= 2) {
            assert(idx - 2 < qColumns.size());
            lastClickedColumn = idx;
            removeColumnByHeaderClickAction->setText(tr("hide_'%1'_column").arg(qColumns[lastClickedColumn-2]));
            QAction* first = m->actions().first();
            m->insertAction(first, removeColumnByHeaderClickAction);
            m->insertSeparator(first);
        }
        return;
    }

    //Ensure that item clicked is in the tree selection. Do not destroy multi-selection if present
    QList<QTreeWidgetItem*> selItems = tree->selectedItems();
    QPoint viewportPos = tree->viewport()->mapFromGlobal(globalPos);
    if (selItems.size() <= 1) {
        QTreeWidgetItem* item = tree->itemAt(viewportPos);
        if (item!=NULL) {
            if (selItems.size() == 1 && selItems.first()!=item) {
                tree->setItemSelected(selItems.first(), false);
            }
            tree->setItemSelected(item, true);
        }
    }
    
    //Update column sensitive actions that appears only in context menu
    selItems = tree->selectedItems();
    lastClickedColumn = tree->columnAt(viewportPos.x());
    updateColumnContextActions(selItems.size() == 1 ? (AVItem*)selItems.first() : (AVItem*)NULL, lastClickedColumn);


    //Add active context actions to the top level menu
    QList<QAction*> contextActions;
    contextActions << copyQualifierAction << copyQualifierURLAction << toggleQualifierColumnAction << copyColumnTextAction << copyColumnURLAction;
    
    QMenu* copyMenu = GUIUtils::findSubMenu(m, ADV_MENU_COPY);
    foreach(QAction* a, contextActions) {
        if (a->isEnabled()) {
            copyMenu->addAction(a);
        }
    }
    int nActive = 0;
    QAction* first = m->actions().first();
    foreach(QAction* a, contextActions) {
        if (a->isEnabled()) {
            nActive++;
            m->insertAction(first, a);
        }
    }
    if (nActive > 0) {
        m->insertSeparator(first);
    }
}

void AnnotationsTreeView::adjustMenu(QMenu* m) const {
    QMenu* addMenu = GUIUtils::findSubMenu(m, ADV_MENU_ADD);
    assert(addMenu!=NULL);
    addMenu->addAction(addAnnotationObjectAction);

    QMenu* removeMenu = GUIUtils::findSubMenu(m, ADV_MENU_REMOVE);
    assert(removeMenu!=NULL);
    removeMenu->addAction(removeObjectsFromViewAction);
    removeMenu->addAction(removeAnnotationsFromDocumentAction);
}

void AnnotationsTreeView::sl_onAddAnnotationObjectToView() {
    ProjectTreeControllerModeSettings s;
    s.objectTypesToShow.append(GObjectTypes::ANNOTATION_TABLE);
    AnnotationTableObjectConstraints ac;
    ac.sequenceSizeToFit = 0;//TODO: ctx->getDNASequenceContext()->getSequence().length();
    s.objectConstraints.append(&ac);
    s.sorted = true;
    foreach(GObject* o, ctx->getObjects()) {
        s.excludeObjectList.append(o);
    }
    QList<GObject*> objs = ProjectTreeItemSelectorDialog::selectObjects(s);
    foreach(GObject* obj, objs) {
        assert(obj->getGObjectType() == GObjectTypes::ANNOTATION_TABLE);
        ctx->addObject(obj);
    } 
}



static QList<AVGroupItem*> selectGroupItems(const QList<QTreeWidgetItem*>& items, TriState readOnly, TriState rootOnly) {
    QList<AVGroupItem*> res;
    foreach(QTreeWidgetItem* i, items) {
        AVItem* item = (AVItem*)i;
        if (item->type == AVItemType_Group) {
            AVGroupItem* gItem = (AVGroupItem*)item;
            if (rootOnly != TriState_Unknown) {
                bool groupIsRoot = gItem->parent() == NULL;
                if ( (rootOnly == TriState_Yes && !groupIsRoot) || (rootOnly==TriState_No && groupIsRoot)) {
                    continue;
                }
            }
            if (readOnly != TriState_Unknown) {
                bool groupReadOnly = gItem->group->getGObject()->isStateLocked();
                if ( (readOnly == TriState_Yes && !groupReadOnly) || (readOnly==TriState_No && groupReadOnly)) {
                    continue;
                }
            }
            res.append(gItem);
        }
    }
    return res;
}

static QList<AVAnnotationItem*> selectAnnotationItems(const QList<QTreeWidgetItem*>& items, TriState readOnly) {
    QList<AVAnnotationItem*> res;
    foreach(QTreeWidgetItem* i, items) {
        AVItem* item = (AVItem*)i;
        if (item->type == AVItemType_Annotation) {
            AVAnnotationItem* aItem = (AVAnnotationItem*)item;
            if (readOnly != TriState_Unknown) {
                bool aReadOnly= aItem->annotation->getGObject()->isStateLocked();
                if ( (readOnly == TriState_Yes && !aReadOnly) || (readOnly==TriState_No && aReadOnly)) {
                    continue;
                }
            }
            res.append(aItem);
        }
    }
    return res;
}

void AnnotationsTreeView::sl_onRemoveObjectsFromView() {
    QList<AVGroupItem*> topLevelGroups = selectGroupItems(tree->selectedItems(), TriState_Unknown, TriState_Yes);
    QList<GObject*> objects;
    foreach(AVGroupItem* gItem, topLevelGroups) {
        objects.append(gItem->group->getGObject());
    }
    foreach(GObject* obj, objects) {
        assert(obj->getGObjectType() == GObjectTypes::ANNOTATION_TABLE);
        ctx->removeObject(obj);
    }
}

static bool groupDepthInverseComparator(const AVGroupItem* i1, const AVGroupItem* i2) {
    int depth1 = i1->group->getGroupDepth();
    int depth2 = i2->group->getGroupDepth();
    return depth1 > depth2;
}

void AnnotationsTreeView::sl_onRemoveAnnotationsFromDocument() {
    QList<AVAnnotationItem*> modAnnotations = selectAnnotationItems(tree->selectedItems(), TriState_No);
    foreach(AVAnnotationItem* aItem, modAnnotations) {
        AnnotationTableObject* ao = qobject_cast<AnnotationTableObject*>(aItem->annotation->getGObject());
        if (ao->isStateLocked()) {
            continue;
        }
        ao->removeAnnotation(aItem->annotation);
    }

    QList<AVGroupItem*> nonRootModGroups = selectGroupItems(tree->selectedItems(), TriState_No, TriState_No);
    
    //remove the deepest groups first
    qSort(nonRootModGroups.begin(), nonRootModGroups.end(), groupDepthInverseComparator);
    foreach(AVGroupItem* gItem, nonRootModGroups) {
        assert(gItem->group->getParentGroup()!=NULL && gItem->group->getGObject()!=NULL);
        AnnotationTableObject* ao = qobject_cast<AnnotationTableObject*>(gItem->group->getGObject());
        if (ao->isStateLocked()) {
            continue;
        }
        gItem->group->getParentGroup()->removeSubgroup(gItem->group);
    }
}

void AnnotationsTreeView::updateState() {
    QList<QTreeWidgetItem*> items = tree->selectedItems();
    
    QList<AVGroupItem*> topLevelGroups = selectGroupItems(items, TriState_Unknown, TriState_Yes);
    removeObjectsFromViewAction->setEnabled(!topLevelGroups.isEmpty());
    
    QList<AVGroupItem*> nonRootModGroups = selectGroupItems(items, TriState_No, TriState_No);
    QList<AVAnnotationItem*> modAnnotations = selectAnnotationItems(items, TriState_No);
    removeAnnotationsFromDocumentAction->setEnabled(!nonRootModGroups.isEmpty() || !modAnnotations.isEmpty());


    bool hasOnly1QualifierSelected = items.size() == 1 && ((AVItem*)items.first())->type == AVItemType_Qualifier;
    QString qName = hasOnly1QualifierSelected ? ((AVQualifierItem*)items.first())->qName : QString("");

    copyQualifierAction->setEnabled(hasOnly1QualifierSelected);
    copyQualifierAction->setText(hasOnly1QualifierSelected ? tr("copy_qual_'%1'_value").arg(qName) : tr("copy_qualifier_value"));
    
    bool hasOnly1QualifierSelectedWithURL = hasOnly1QualifierSelected && ((AVItem*)items.first())->isColumnLinked(1);
    copyQualifierURLAction->setEnabled(hasOnly1QualifierSelectedWithURL);
    copyQualifierURLAction->setText(hasOnly1QualifierSelectedWithURL ? tr("copy_qual_'%1'_url").arg(qName) : tr("copy_qualifier_url"));

    
    toggleQualifierColumnAction->setEnabled(hasOnly1QualifierSelected);
    bool hasColumn = qColumns.contains(qName);
    toggleQualifierColumnAction->setText(!hasOnly1QualifierSelected ? tr("toggle_qual_column")
        : (qColumns.contains(qName) ? tr("hide_'%1'_column"): tr("add_'%1'_column")).arg(qName));

    toggleQualifierColumnAction->setIcon(hasOnly1QualifierSelected ? (hasColumn ? removeColumnIcon : addColumnIcon) : QIcon());
}

bool AnnotationsTreeView::eventFilter(QObject* o, QEvent* e) {
    if (o != tree->viewport()) {
        return false;
    }
    QEvent::Type type = e->type();
    if (type == QEvent::ToolTip) {
        QHelpEvent* he = (QHelpEvent*)e;
        QPoint globalPos = he->globalPos();
        QPoint viewportPos = tree->viewport()->mapFromGlobal(globalPos);
        QTreeWidgetItem* item = tree->itemAt(viewportPos);
        if (item != NULL) {
            AVItem* avi = (AVItem*)item;
            if (avi->type == AVItemType_Annotation) {
                AVAnnotationItem* ai = (AVAnnotationItem*)avi;
                QString tip = ai->annotation->getQualifiersTip(15);
                if (!tip.isEmpty()) {
                    QToolTip::showText(he->globalPos(), tip);
                    return true;
                }
            }
        }
    }  else if (type == QEvent::MouseButtonRelease) {
        lastMB = ((QMouseEvent*)e)->button();
    }
    return false;
}

void AnnotationsTreeView::sl_itemEntered(QTreeWidgetItem * i, int column) {
    AVItem* item = (AVItem*)i;
    Qt::CursorShape newShape = Qt::ArrowCursor;
    Qt::CursorShape currentShape = tree->cursor().shape();
    if (item != NULL) {
        if (item->isColumnLinked(column)) {
            newShape = Qt::PointingHandCursor;
        }
    }
    if (newShape == Qt::PointingHandCursor || ((newShape == Qt::ArrowCursor && currentShape == Qt::PointingHandCursor))) {
        tree->setCursor(newShape);
    }
}

void AnnotationsTreeView::sl_itemClicked(QTreeWidgetItem * i, int column) {
    AVItem* item = (AVItem*)i;
    if (lastMB != Qt::LeftButton || item==NULL || !item->isColumnLinked(column)) {
        return;
    }
    GUIUtils::runWebBrowser(item->buildLinkURL(column));
}

void AnnotationsTreeView::sl_itemExpanded(QTreeWidgetItem* qi) {
    AVItem* i = (AVItem*)qi;
    if (i->type != AVItemType_Annotation) {
        return;
    }
    AVAnnotationItem* ai = (AVAnnotationItem*)i;
    if (ai->childCount() == 0 && !ai->annotation->getQualifiers().isEmpty()) {
        assert(ai->childIndicatorPolicy() == QTreeWidgetItem::ShowIndicator);
        populateAnnotationQualifiers(ai);
    } else {
        assert(ai->childIndicatorPolicy() == QTreeWidgetItem::DontShowIndicatorWhenChildless);
    }
}

void AnnotationsTreeView::sl_onCopyQualifierValue() {
    QList<QTreeWidgetItem*> items = tree->selectedItems();
    assert(items.size() == 1);
    AVItem* item = (AVItem*)items.first();
    assert(item->type == AVItemType_Qualifier);
    AVQualifierItem* qi = (AVQualifierItem*)item;
    QApplication::clipboard()->setText(qi->qValue);
}

void AnnotationsTreeView::sl_onCopyQualifierURL() {
    QList<QTreeWidgetItem*> items = tree->selectedItems();
    assert(items.size() == 1);
    AVItem* item = (AVItem*)items.first();
    if (item->isColumnLinked(1)) {
        QApplication::clipboard()->setText(item->buildLinkURL(1));
    }
}

void AnnotationsTreeView::sl_onCopyColumnText() {
    QList<QTreeWidgetItem*> items = tree->selectedItems();
    assert(items.size() == 1);
    AVItem* item = (AVItem*)items.first();
    QApplication::clipboard()->setText(item->text(lastClickedColumn));
}

void AnnotationsTreeView::sl_onCopyColumnURL() {
    QList<QTreeWidgetItem*> items = tree->selectedItems();
    assert(items.size() == 1);
    AVItem* item = (AVItem*)items.first();
    QApplication::clipboard()->setText(item->buildLinkURL(lastClickedColumn));
}

void AnnotationsTreeView::sl_onToggleQualifierColumn() {
    QList<QTreeWidgetItem*> items = tree->selectedItems();
    assert(items.size() == 1);
    AVItem* item = (AVItem*)items.first();
    assert(item->type == AVItemType_Qualifier);
    AVQualifierItem* qi = (AVQualifierItem*)item;
    if (qColumns.contains(qi->qName)) {
        removeQualifierColumn(qi->qName);
    } else {
        addQualifierColumn(qi->qName);
    }
}

void AnnotationsTreeView::sl_onRemoveColumnByHeaderClick() {
    assert(lastClickedColumn >= 2);
    assert(lastClickedColumn-2 <= qColumns.size());
    removeQualifierColumn(qColumns[lastClickedColumn-2]);
}


void AnnotationsTreeView::updateAllAnnotations(ATVAnnUpdateFlags flags) {
    QString emptyFilter;
    for(int i=0; i<tree->topLevelItemCount(); i++) {
        AVGroupItem* gi = (AVGroupItem*)tree->topLevelItem(i);
        gi->updateAnnotations(emptyFilter, flags);
    }
}

void AnnotationsTreeView::addQualifierColumn(const QString& q) {
    TreeSorter ts(tree);

    qColumns.append(q);
    int nColumns = headerLabels.size() + qColumns.size();
    tree->setColumnCount(nColumns);
    tree->setHeaderLabels(headerLabels + qColumns);
    tree->setColumnWidth(nColumns-2, nColumns - 2 == 1 ? 200 : 100);
    updateAllAnnotations(ATVAnnUpdateFlag_QualColumns);
    
    updateState();
}


void AnnotationsTreeView::removeQualifierColumn(const QString& q) {
    bool ok = qColumns.removeOne(q);
    if (!ok) {
        return;
    }

    TreeSorter ts(tree);

    tree->setColumnCount(headerLabels.size() + qColumns.size());
    tree->setHeaderLabels(headerLabels + qColumns);
    updateAllAnnotations(ATVAnnUpdateFlag_QualColumns);
    
    updateState();
}

#define COLUMN_NAMES "ATV_COLUMNS"

void AnnotationsTreeView::saveState(QVariantMap& map) const {
    map.insert(COLUMN_NAMES, QVariant(qColumns));
    
    QStringList columns = map.value(COLUMN_NAMES).toStringList();
    assert(columns == qColumns);
}

void AnnotationsTreeView::updateState(const QVariantMap& map) {
    QStringList columns = map.value(COLUMN_NAMES).toStringList();
    //QByteArray geom = map.value(COLUMNS_GEOM).toByteArray();
    
    if (columns != qColumns && !columns.isEmpty()) {
        TreeSorter ts(tree);
        foreach(QString q, qColumns) {
            removeQualifierColumn(q);
        }
        foreach(QString q, columns) {
            addQualifierColumn(q);
        }
    }
    /*if (columns == qColumns && !geom.isEmpty()) {
        tree->header()->restoreState(geom);
    }*/
}

//////////////////////////////////////////////////////////////////////////
/// Tree model
bool AVItem::processLinks(const QString& qName, const QString& qValue, int col) {
    bool linked = false;
    if (qName == "db_xref") {
        QStringList l = qValue.split(":");
        QString dbName = l[0];
        QString dbId = l.size() > 1 ? l[1] : "";
        DBXRefInfo info = AppContext::getDBXRefRegistry()->getRefByKey(dbName);
        linked = !info.url.isEmpty();
        setToolTip(col, info.comment);
        if (linked) {
            setData(col, Qt::UserRole, true);
        }
    } 

    if (linked) {
        QFont f = font(col);
        f.setUnderline(true);
        setFont(col, f);
        setForeground(col, Qt::blue);
    }
    return linked;
}

bool AVItem::isColumnLinked(int col) const {
    return data(col, Qt::UserRole).type() == QVariant::Bool;
}

QString AVItem::buildLinkURL(int col) const {
    assert(isColumnLinked(col));
    QString qValue = text(col);
    QStringList split = qValue.split(":");
    QString type = split.first();
    QString id = split.size() < 2 ? QString("") : split[1];
    QString url = AppContext::getDBXRefRegistry()->getRefByKey(type).url.arg(id);
    return url;
}

AVGroupItem::AVGroupItem(AnnotationsTreeView* _atv, AVGroupItem* parent, AnnotationGroup* g) : AVItem(parent, AVItemType_Group), group(g), atv(_atv) 
{
    updateVisual();
}

const QIcon& AVGroupItem::getGroupIcon() {
    static QIcon groupIcon(":/core/images/group_green_active.png");
    return groupIcon;
}

const QIcon& AVGroupItem::getDocumentIcon() {
    static QIcon groupIcon(":/core/images/gobject.png");
    return groupIcon;
}

void AVGroupItem::updateVisual() {
    if (group->getParentGroup() == group->getGObject()->getRootGroup()) {
        const AnnotationSettings* as  = AppContext::getAnnotationsSettingsRegistry()->getSettings(group->getGroupName());
        Qt::ItemFlags f = flags();
        if (as->visible) {
            f|=Qt::ItemIsEnabled | Qt::ItemIsSelectable;
        } else {
            f&= (!Qt::ItemIsEnabled) | Qt::ItemIsSelectable;
        }
        setFlags(f);
    }
    if (parent() == NULL) {
        QString docShortName = QFileInfo(group->getGObject()->getDocument()->getURL()).fileName();
        if (docShortName.isEmpty()) {
            docShortName = AnnotationsTreeView::tr("not_saved");
        }
        setText(0, group->getGObject()->getGObjectName() + " ("+docShortName+")");
        setIcon(0, getDocumentIcon());
    } else {
        int ng = group->getSubgroups().size();
        int na = group->getAnnotations().size();
        QString nameString = group->getGroupName() + "  " + QString("(%1, %2)").arg(ng).arg(na);
        setText(0, nameString);
        setIcon(0, getGroupIcon());
    }
}

void AVGroupItem::updateAnnotations(const QString& nameFilter, ATVAnnUpdateFlags f) {
    bool noFilter = nameFilter.isEmpty();
    for (int j = 0; j < childCount(); j++) {
        AVItem* item = (AVItem*)child(j);
        if (item->type == AVItemType_Group) {
            AVGroupItem* level1 = (AVGroupItem*)item;
            if (noFilter || level1->group->getGroupName() == nameFilter) {
                level1->updateAnnotations(nameFilter, f);
            }
        } else {
            assert(item->type == AVItemType_Annotation);
            AVAnnotationItem* aItem= (AVAnnotationItem*)item;
            if (noFilter || aItem->annotation->getAnnotationName() == nameFilter) {
                aItem->updateVisual(f);
            }
        }
    }
}


AVAnnotationItem::AVAnnotationItem(AVGroupItem* parent, Annotation* a) : AVItem(parent, AVItemType_Annotation), annotation(a)
{
    updateVisual(ATVAnnUpdateFlags(ATVAnnUpdateFlag_BaseColumns) | ATVAnnUpdateFlag_QualColumns);    
    hasNumericQColumns = false;
}

#define MAX_ICONS_CACHE_SIZE 500

QMap<QString, QIcon>& AVAnnotationItem::getIconsCache() {
    static QMap<QString, QIcon> iconsCache;
    return iconsCache;
}

void AVAnnotationItem::updateVisual(ATVAnnUpdateFlags f) {
    if (f.testFlag(ATVAnnUpdateFlag_BaseColumns)) {
        const QString& name = annotation->getAnnotationName();

        const AnnotationSettings* as  = AppContext::getAnnotationsSettingsRegistry()->getSettings(name);
        Qt::ItemFlags f2 = flags();
        if (as->visible) {
            f2|= Qt::ItemIsEnabled | Qt::ItemIsSelectable;
        } else {
            f2&= (!Qt::ItemIsEnabled) | Qt::ItemIsSelectable;
        }
        setFlags(f2);

        QMap<QString, QIcon>& cache = getIconsCache();
        QIcon icon = cache.value(name);
        if (icon.isNull()) {
            QColor c = AppContext::getAnnotationsSettingsRegistry()->getSettings(annotation->getAnnotationName())->color;
            icon = GUIUtils::createSquareIcon(c, 9);
            if (cache.size() > MAX_ICONS_CACHE_SIZE) {
                cache.clear();
            }
            cache[name] = icon;
        }
        assert(!icon.isNull());

        setIcon(0, icon);
        setText(0, annotation->getAnnotationName());

        locationString = Genbank::LocationParser::buildLocationString(annotation->data());
        setText(1, locationString);
    }

    if (f.testFlag(ATVAnnUpdateFlag_QualColumns)) {
        //setup custom qualifiers columns
        AnnotationsTreeView* atv = getAnnotationTreeView();
        assert(atv!=NULL);
        const QStringList& colNames = atv->getQualifierColumnNames();
        hasNumericQColumns = false;
        for (int i=0, n = colNames.size(); i < n ;i++) {
            int col = 2+i;
            QString colName = colNames[i];
            QString colText = annotation->findFirstQualifierValue(colName);
            setText(2+i, colText);
            bool linked = processLinks(colName, colText,  col);
            if (!linked) {
                bool ok  = false;
                double d = colText.toDouble(&ok);
                if (ok) {
                    setData(col, Qt::UserRole, d);
                    hasNumericQColumns = true;
                }
            }
        }
    }
}

bool AVAnnotationItem::operator<(const QTreeWidgetItem & other) const {
    int col = treeWidget()->sortColumn();
    assert(((const AVItem&)other).type == AVItemType_Annotation);
    const AVAnnotationItem& ai = (const AVAnnotationItem&)other;
    if (col == 0) {
        return annotation->getAnnotationName() < ai.annotation->getAnnotationName();
    }
    if (col == 1 || (isColumnNumeric(col) && ai.isColumnNumeric(col))) {
        double oval = ai.getNumericVal(col);
        double mval = getNumericVal(col);
        return mval < oval;
    }
    return text(col) < other.text(col);
}

bool AVAnnotationItem::isColumnNumeric(int col) const {
    if (col == 0) {
        return false;
    } 
    if (col == 1) {
        return true;
    }
    if (!hasNumericQColumns) {
        return false;
    }
    return data(col, Qt::UserRole).type() == QVariant::Double;
}

double AVAnnotationItem::getNumericVal(int col) const {
    if (col == 1) {
        LRegion r = annotation->getLocation().first();
        return r.startPos;
    }
    bool ok  = false;
    double d = data(col, Qt::UserRole).toDouble(&ok);
    assert(ok);
    return d;
}

AVQualifierItem::AVQualifierItem(AVAnnotationItem* parent, const Qualifier& q) 
: AVItem(parent, AVItemType_Qualifier), qName(q.getQualifierName()), qValue(q.getQualifierValue())
{
    setText(0, qName);
    setText(1, qValue);

    processLinks(qName, qValue, 1);
}

}//namespace

Generated by  Doxygen 1.6.0   Back to index