/* * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or * its licensors. * * For complete copyright and license terms please see the LICENSE at the root of this * distribution (the "License"). All use of this software is governed by the License, * or, if provided, by the license below or the license accompanying this file. Do not * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * */ // Original file Copyright Crytek GMBH or its affiliates, used under license. #include "StdAfx.h" #include "UndoDropDown.h" #include "Undo/Undo.h" #include #include #include #include #include #include #include ///////////////////////////////////////////////////////////////////////////// // Turns listener callbacks into signals UndoStackStateAdapter::UndoStackStateAdapter(QObject* parent /* = nullptr */) : QObject(parent) { GetIEditor()->GetUndoManager()->AddListener(this); } UndoStackStateAdapter::~UndoStackStateAdapter() { GetIEditor()->GetUndoManager()->RemoveListener(this); } ///////////////////////////////////////////////////////////////////////////// // The model that holds the list of undo/redo actions class UndoDropDownListModel : public QAbstractListModel , IUndoManagerListener { public: UndoDropDownListModel(CUndoManager& manager, UndoRedoDirection direction, QObject* parent = nullptr) : QAbstractListModel(parent) , m_manager(manager) , m_direction(direction) { m_stackNames = m_direction == UndoRedoDirection::Undo ? m_manager.GetUndoStackNames() : m_manager.GetRedoStackNames(); m_manager.AddListener(this); } virtual ~UndoDropDownListModel() { m_manager.RemoveListener(this); } int rowCount(const QModelIndex& parent = QModelIndex()) const override { if (parent.model() != this && parent != QModelIndex()) { return 0; } return m_stackNames.size(); } QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override { if (index.row() < 0 || index.row() >= rowCount() || role != Qt::DisplayRole) { return {}; } return m_stackNames[index.row()]; } void SignalNumUndoRedo(const unsigned int& numUndo, const unsigned int& numRedo) { std::vector fresh; if (UndoRedoDirection::Undo == m_direction && m_stackNames.size() != numUndo) { fresh = m_manager.GetUndoStackNames(); } else if (UndoRedoDirection::Redo == m_direction && m_stackNames.size() != numRedo) { fresh = m_manager.GetRedoStackNames(); } else { return; } std::reverse(fresh.begin(), fresh.end()); if (fresh.size() < m_stackNames.size()) { beginRemoveRows(createIndex(-1, -1), fresh.size(), m_stackNames.size() - 1); m_stackNames = fresh; endRemoveRows(); } else { beginInsertRows(createIndex(-1, -1), m_stackNames.size(), fresh.size() - 1); m_stackNames = fresh; endInsertRows(); } } private: CUndoManager& m_manager; UndoRedoDirection m_direction; std::vector m_stackNames; }; ///////////////////////////////////////////////////////////////////////////// // Custom selection model we use for the list of undo/redo actions void UndoStackItemSelectionModel::select(const QModelIndex& index, QItemSelectionModel::SelectionFlags command) { if (!model()) { return; } if (index.isValid()) { QItemSelectionModel::select({ model()->index(0, 0, {}), index }, command); } clearCurrentIndex(); } void UndoStackItemSelectionModel::select(const QItemSelection& selection, QItemSelectionModel::SelectionFlags command) { QPoint mouse = m_view->mapFromGlobal(QCursor::pos()); QModelIndex first = model()->index(0, 0, {}); QModelIndex last = model()->index(model()->rowCount() - 1, 0, {}); QModelIndex underMouse = m_view->indexAt(mouse); if (underMouse.isValid()) { last = underMouse; } QItemSelectionModel::select({ first, last }, command); clearCurrentIndex(); } ///////////////////////////////////////////////////////////////////////////// // CUndoDropDown dialog CUndoDropDown::CUndoDropDown(UndoRedoDirection direction, QWidget* pParent /* = nullptr */) : QDialog(pParent) , m_direction(direction) { auto layout = new QVBoxLayout(this); setLayout(layout); // Model & view m_model = new UndoDropDownListModel(*GetIEditor()->GetUndoManager(), direction, this); m_view = new QListView(this); m_view->setModel(m_model); m_view->setSelectionModel(new UndoStackItemSelectionModel(m_view, m_model)); m_view->setSelectionMode(QAbstractItemView::ContiguousSelection); layout->addWidget(m_view); // The buttons along the bottom of the dropdown auto buttonBox = new QHBoxLayout(); m_doButton = new QPushButton(this); buttonBox->addWidget(m_doButton); buttonBox->addStretch(1); auto clearButton = new QPushButton(this); clearButton->setText(tr("Clear")); buttonBox->addWidget(clearButton); layout->addLayout(buttonBox); // Connections connect(m_doButton, &QPushButton::clicked, this, &CUndoDropDown::OnUndoButton); connect(clearButton, &QPushButton::clicked, this, &CUndoDropDown::OnUndoClear); connect(m_view->selectionModel(), &QItemSelectionModel::selectionChanged, this, &CUndoDropDown::SelectionChanged); setMinimumWidth(450); } void CUndoDropDown::Prepare() { m_view->selectionModel()->select(m_model->index(0, 0, {}), QItemSelectionModel::ClearAndSelect); m_view->setFocus(); show(); } void CUndoDropDown::OnUndoButton() { int numSelected = m_view->selectionModel()->selectedIndexes().size(); if (m_direction == UndoRedoDirection::Undo) { GetIEditor()->GetUndoManager()->Undo(numSelected); } else { GetIEditor()->GetUndoManager()->Redo(numSelected); } accept(); } void CUndoDropDown::OnUndoClear() { if (m_direction == UndoRedoDirection::Undo) { GetIEditor()->GetUndoManager()->ClearUndoStack(); } else { GetIEditor()->GetUndoManager()->ClearRedoStack(); } accept(); } void CUndoDropDown::SelectionChanged(const QItemSelection& selected, const QItemSelection& deselected) { QString label = QString("%1 %2 action%3") .arg(m_direction == UndoRedoDirection::Undo ? tr("Undo") : tr("Redo")) .arg(m_view->selectionModel()->selectedIndexes().size()) .arg(m_view->selectionModel()->selectedIndexes().size() > 1 ? "s" : ""); m_doButton->setText(label); } void CUndoDropDown::contextMenuEvent(QContextMenuEvent*) { // Inhibit QDialog::contextMenuEvent() as this will trigger the "What's this" popup. // That happens because we're a child of a QMenu, and menus have Qt::WA_CustomWhatsThis // which make the popup show even if QWidget::whatsThis() text is empty. } #include