void AMActionHistoryTreeView3::onEnteredIndex(const QModelIndex &index){ AMActionHistoryModel3 *historyModel = qobject_cast<AMActionHistoryModel3*>(model()); if(!historyModel) return; if(shiftKeyDown_){ // If shift key is down and we don't already have the forbidden cursor and we're hovering over an item that is not a sibling then we can't shift click ... go forbidden if(!hasOverriddenShiftCursor_ && (index.parent() != lastClickedIndex_.parent()) ){ hasOverriddenShiftCursor_ = true; setCursor(*forbiddenCursor_); } // Or if the shift key is down and we already have the forbidden cursor and we're hovering over an item that is a sibling then we can shift click ... go default // Also check lastClickWasDeselect ... we can never shift click if that sucker is on else if(hasOverriddenShiftCursor_ && (index.parent() == lastClickedIndex_.parent() && !lastClickWasDeselect_) ){ hasOverriddenShiftCursor_ = false; setCursor(*regularCursor_); } } if(controlKeyDown_){ ParentSelectMap selectMap = model()->data(index, AMActionHistoryModel3::ParentSelectRole).value<ParentSelectMap>(); // If control key is down and we have overridden the cursor ... go default if(hasOverriddenControlCursor_){ hasOverriddenControlCursor_ = false; setCursor(*regularCursor_); } // If control key is down and we don't already have the forbidden cursor and this viewer is in the selection map and this index isn't parentSelected // and we have a parent somewhere selected and our direct parent isn't selected one way or the other ... go forbidden // Just trying to make sure that you if you have three levels (A parents B, B parents C) if A or above is selected but B is not then you can't control-click in C or below ... what would that mean anyway if(!hasOverriddenControlCursor_ && selectMap.contains(this) && !selectMap.value(this) && hasSelectedParent(index) && !(selectionModel()->isSelected(index.parent()) || model()->data(index.parent(), AMActionHistoryModel3::ParentSelectRole).value<ParentSelectMap>().value(this)) ){ hasOverriddenControlCursor_ = true; setCursor(*forbiddenCursor_); } } }
void AMActionHistoryModel3::markIndexGroup(const QModelIndex &index, QAbstractItemView *viewer, bool selected){ // Just do the regular markParentSelected if this is a top-level action log item (parent index is invalid) if(!index.parent().isValid()){ recurseMarkParentSelected(index, viewer, selected); return; } // Check if the action log item inherited the loop action (if so, then we need to multi-select/deselect the children) AMActionLogItem3 *item = logItem(index.parent()); if(item->actionInheritedLoop()){ // Figure out your index in the original loop, and select/deselect all of your siblings with the same loop index int actionsPerLoop = rowCount(index.parent())/item->numberOfLoops(); int indexInLoop = (index.row()%actionsPerLoop); QModelIndex markingIndex; for(int x = 0; x < rowCount(index.parent()); x++){ if(x%actionsPerLoop == indexInLoop){ markingIndex = this->index(x, 0, index.parent()); recurseMarkParentSelected(markingIndex, viewer, selected); emit dataChanged(markingIndex, markingIndex); } } } else recurseMarkParentSelected(index, viewer, selected); // Check to see if there are any children left at this level. If not, then deselect the parent as well (no point selecting a list or loop when the user has removed all of the contents) int siblingsSelected = 0; for(int x = 0; x < rowCount(index.parent()); x++){ ParentSelectMap selectMap = data(this->index(x, 0, index.parent()), AMActionHistoryModel3::ParentSelectRole).value<ParentSelectMap>(); if(selectMap.contains(viewer) && selectMap.value(viewer)) siblingsSelected++; } if(siblingsSelected == 0){ // Actually deselect in the selection model if the parent was actually selected if(viewer->selectionModel()->isSelected(index.parent())){ QItemSelectionModel::SelectionFlags deselectFlag = QItemSelectionModel::Deselect; viewer->selectionModel()->select(index.parent(), deselectFlag); ParentSelectMap selectMap = data(index.parent(), AMActionHistoryModel3::ParentSelectRole).value<ParentSelectMap>(); if(selectMap.contains(viewer)){ AMActionLogItem3 *item = logItem(index.parent()); // Make sure it doesn't get parentSelected somehow item->setParentSelected(viewer, false); } emit dataChanged(index.parent(), index.parent()); } // Otherwise just use the group deselect for the parentSelected mapping else markIndexGroupAsDeselected(index.parent(), viewer); } }
QItemSelectionModel::SelectionFlags AMActionHistoryTreeView3::selectionCommand(const QModelIndex &index, const QEvent *selectionEvent) const{ // Initialize by calling the parent version QItemSelectionModel::SelectionFlags retFlags = QTreeView::selectionCommand(index, selectionEvent); bool hasShiftModifier, hasControlModifier; // If we have a valid index we have a lot of work to do (also make sure this thing is actually an AMActionHistoryModel viewing tree) if(index.isValid()){ AMActionHistoryModel3 *historyModel = qobject_cast<AMActionHistoryModel3*>(model()); // We only care about the mouse button press events otherwise return the parent class call if( !historyModel || (selectionEvent->type() != QEvent::MouseButtonPress) ) return QTreeView::selectionCommand(index, selectionEvent); QMouseEvent *mouseEvent = (QMouseEvent*)selectionEvent; // Ignore clicks on non-selectable indices // We can ignore: not enabled indexes, and not selectable indexes if(!index.flags().testFlag(Qt::ItemIsEnabled) || !index.flags().testFlag(Qt::ItemIsSelectable)) return QTreeView::selectionCommand(index, selectionEvent); // Grab keyboard shift and control modifiers Qt::KeyboardModifiers currentModifiers = mouseEvent->modifiers(); hasControlModifier = currentModifiers&Qt::ControlModifier; hasShiftModifier = currentModifiers&Qt::ShiftModifier; ParentSelectMap selectMap = model()->data(index, AMActionHistoryModel3::ParentSelectRole).value<ParentSelectMap>(); // This is pretty bad, you can tell too much is in here that doesn't belong when I need to do a const cast AMActionHistoryTreeView3 *checkViewer = const_cast<AMActionHistoryTreeView3*>(this); bool parentIsSelected = false; // Figure out if the parent is selected via the mapping or the selection model if(index.parent().isValid()){ ParentSelectMap parentsSelectMap = model()->data(index.parent(), AMActionHistoryModel3::ParentSelectRole).value<ParentSelectMap>(); if(parentsSelectMap.contains(checkViewer)) parentIsSelected |= parentsSelectMap.value(checkViewer); parentIsSelected |= selectionModel()->isSelected(index.parent()); } // On control clicks where the parent is selected and this index is as well, do the group deselect and return noUpdate in the selection flags if(hasControlModifier && index.parent().isValid() && parentIsSelected && selectMap.contains(checkViewer) && selectMap.value(checkViewer)){ historyModel->markIndexGroupAsDeselected(index, checkViewer); return QItemSelectionModel::NoUpdate; } // On control clicks where the parent is selected and this index is not, do the group select and return noUpdate in the selection flags else if(hasControlModifier && index.parent().isValid() && parentIsSelected && selectMap.contains(checkViewer) && !selectMap.value(checkViewer)){ historyModel->markIndexGroupAsSelected(index, checkViewer); return QItemSelectionModel::NoUpdate; } // Ignore regular clicks on the an index that is already actually selected if it's the only one selected if( !hasControlModifier && (actuallySelectedByClickingCount_ == 1) && (selectionModel()->selectedIndexes().contains(index)) ){ return QItemSelectionModel::NoUpdate; } } // Log some flags ... might be an easier way now that I know how to use QFlag::testFlag() bool clearFlag = (retFlags&QItemSelectionModel::Clear); bool selectFlag = (retFlags&QItemSelectionModel::Select); bool deselectFlag = (retFlags&QItemSelectionModel::Deselect); bool toggleFlag = (retFlags&QItemSelectionModel::Toggle); bool indexAlreadyOn = selectionModel()->selectedIndexes().contains(index); bool noUpdateFlag = (retFlags&QItemSelectionModel::NoUpdate); // An actual selection! Selected OR (Toggled AND NOT Currently On) OR (noUpdate AND Currently On) bool selectedTrue = selectFlag || (toggleFlag && !indexAlreadyOn) || (noUpdateFlag && indexAlreadyOn); // An actual deselection! Deselected OR (Toggled AND Currently On) OR (noUpdate AND NOT Currently On) OR (Actually selected an invalid index) bool selectedFalse = deselectFlag || (toggleFlag && indexAlreadyOn) || (noUpdateFlag && !indexAlreadyOn) || (selectedTrue && !index.isValid()); // If we actually selected and the column is 0 (we only care about that one) log the state and emit actuallySelectedByClicking if(selectedTrue && (index.column() == 0) ){ lastClickWasDeselect_ = false; lastClickedIndex_ = index; emit actuallySelectedByClicking(index, clearFlag); } // If we actually deselected and the column is 0 (we only care about that one) log the state (particularily lastClickWasDeselect) and emit actuallyDeselectedByClicking else if(selectedFalse && (index.column() == 0) ){ QModelIndexList currentSelections = selectionModel()->selection().indexes(); if(currentSelections.count() == 2){ currentSelections.removeOne(index); lastClickedIndex_ = currentSelections.at(0); lastClickWasDeselect_ = false; } else lastClickWasDeselect_ = true; emit actuallyDeselectedByClicking(index, clearFlag); } // If neither selected nor deselected but clear flag is on then emit clearedByClicking else if(clearFlag){ lastClickWasDeselect_ = false; emit clearedByClicking(); } // Return the flags we have return retFlags; }
QVariant AMActionHistoryModel3::data(const QModelIndex &index, int role) const { AMPointerTreeNode *indexNode = static_cast<AMPointerTreeNode*>(index.internalPointer()); if(!indexNode){ //NEM return QVariant(); } AMActionLogItem3 *item = static_cast<AMActionLogItem3*>(indexNode->item()); if(!item){ AMErrorMon::alert(this, AMACTIONHISTORYMODEL_MODELDATA_BAD_ITEM, QString("The action history attempted to access data with a bad item (row: %1 column: %2). Please report this problem to the Acquaman developers ").arg(index.row()).arg(index.column())); return QVariant(); } if(role == Qt::DisplayRole) { switch(index.column()) { case 0: return item->shortDescription(); case 1: return QVariant(); case 2: return item->failureMessage(); case 3: return AMDateTimeUtils::prettyDateTime(item->endDateTime()); case 4: return AMDateTimeUtils::prettyDuration(item->startDateTime(), item->endDateTime()); } } else if(role == Qt::DecorationRole) { // column 0: return the action icon. if(index.column() == 0) { QString iconFileName = item->iconFileName(); if(iconFileName.isEmpty()) iconFileName = ":/64x64/generic-action.png"; QPixmap p; if(QPixmapCache::find("AMActionLogItemIcon" % iconFileName, &p)) return p; else { p.load(iconFileName); p = p.scaledToHeight(22, Qt::SmoothTransformation); QPixmapCache::insert("AMActionLogItemIcon" % iconFileName, p); return p; } } // column 1: return the status icon else if(index.column() == 1) { switch(item->finalState()) { case AMAction3::Succeeded: return succeededIcon_; case AMAction3::Cancelled: return cancelledIcon_; case AMAction3::Failed: return failedIcon_; default: return unknownIcon_; } } else return QVariant(); } else if(role == Qt::SizeHintRole) { return QSize(-1, 32); } else if(role == Qt::ToolTipRole) { if(index.column() == 0) { // In case the item didn't (or isn't yet) in some sort of finished state if(item && item->canCopy() && !(item->finalState() == 7 || item->finalState() == 8 || item->finalState() == 9) ) return "Cannot select this item, it has not yet completed"; //In case a loop or list had no logged actions in it if(item && item->canCopy() && item->actionInheritedLoop() && (childrenCount(index) == 0) ) return "Cannot select this item, it has no children."; //In case a loop or list had no logged actions in it if(item && item->canCopy() && item->actionInheritedLoop() && (successfulChildrenCount(index) == 0) ) return "Cannot select this item, none of its children succeeded."; return item->longDescription(); } else if(index.column() == 1) { switch(item->finalState()) { case AMAction3::Succeeded: return "Succeeded"; case AMAction3::Cancelled: return "Cancelled"; case AMAction3::Failed: if(item->failureMessage().isEmpty()) return "Failed"; else return QString("Failed with message: %1").arg(item->failureMessage()); default: return "[?]"; } } else if(index.column() == 2) return QString(item->failureMessage()); } else if(role == Qt::BackgroundRole) { switch(item->finalState()) { case AMAction3::Succeeded: return QColor(126, 255, 106);// light green case AMAction3::Cancelled: return QColor(255, 176, 106);// light orange case AMAction3::Failed: return QColor(255, 104, 106);// light red default: return QVariant(); } } else if(role == AMActionHistoryModel3::ParentSelectRole) { ParentSelectMap selectMap; ParentSelectMap::const_iterator i = item->allParentSelected().constBegin(); while (i != item->allParentSelected().constEnd()) { selectMap.insert(i.key(), i.value()); ++i; } return QVariant::fromValue(selectMap); } return QVariant(); }