316 lines
10 KiB
C++
316 lines
10 KiB
C++
|
#include <QGuiApplication>
|
||
|
#include <QMenu>
|
||
|
#include <QXmlStreamWriter>
|
||
|
#include <QClipboard>
|
||
|
#include <QApplication>
|
||
|
#include "skeletonxml.h"
|
||
|
#include "motionlistwidget.h"
|
||
|
|
||
|
MotionListWidget::MotionListWidget(const SkeletonDocument *document, QWidget *parent) :
|
||
|
QTreeWidget(parent),
|
||
|
m_document(document)
|
||
|
{
|
||
|
setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
|
||
|
setFocusPolicy(Qt::NoFocus);
|
||
|
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||
|
|
||
|
setAutoScroll(false);
|
||
|
|
||
|
setHeaderHidden(true);
|
||
|
|
||
|
QPalette palette = this->palette();
|
||
|
palette.setColor(QPalette::Window, Qt::transparent);
|
||
|
palette.setColor(QPalette::Base, Qt::transparent);
|
||
|
setPalette(palette);
|
||
|
|
||
|
setStyleSheet("QTreeView {qproperty-indentation: 0;}");
|
||
|
|
||
|
setContentsMargins(0, 0, 0, 0);
|
||
|
|
||
|
connect(document, &SkeletonDocument::motionListChanged, this, &MotionListWidget::reload);
|
||
|
connect(document, &SkeletonDocument::cleanup, this, &MotionListWidget::removeAllContent);
|
||
|
|
||
|
connect(this, &MotionListWidget::removeMotion, document, &SkeletonDocument::removeMotion);
|
||
|
|
||
|
setContextMenuPolicy(Qt::CustomContextMenu);
|
||
|
connect(this, &QTreeWidget::customContextMenuRequested, this, &MotionListWidget::showContextMenu);
|
||
|
|
||
|
reload();
|
||
|
}
|
||
|
|
||
|
void MotionListWidget::motionRemoved(QUuid motionId)
|
||
|
{
|
||
|
if (m_currentSelectedMotionId == motionId)
|
||
|
m_currentSelectedMotionId = QUuid();
|
||
|
m_selectedMotionIds.erase(motionId);
|
||
|
m_itemMap.erase(motionId);
|
||
|
}
|
||
|
|
||
|
void MotionListWidget::updateMotionSelectState(QUuid motionId, bool selected)
|
||
|
{
|
||
|
auto findItemResult = m_itemMap.find(motionId);
|
||
|
if (findItemResult == m_itemMap.end()) {
|
||
|
qDebug() << "Find motion item failed:" << motionId;
|
||
|
return;
|
||
|
}
|
||
|
MotionWidget *motionWidget = (MotionWidget *)itemWidget(findItemResult->second.first, findItemResult->second.second);
|
||
|
motionWidget->updateCheckedState(selected);
|
||
|
}
|
||
|
|
||
|
void MotionListWidget::selectMotion(QUuid motionId, bool multiple)
|
||
|
{
|
||
|
if (multiple) {
|
||
|
if (!m_currentSelectedMotionId.isNull()) {
|
||
|
m_selectedMotionIds.insert(m_currentSelectedMotionId);
|
||
|
m_currentSelectedMotionId = QUuid();
|
||
|
}
|
||
|
if (m_selectedMotionIds.find(motionId) != m_selectedMotionIds.end()) {
|
||
|
updateMotionSelectState(motionId, false);
|
||
|
m_selectedMotionIds.erase(motionId);
|
||
|
} else {
|
||
|
updateMotionSelectState(motionId, true);
|
||
|
m_selectedMotionIds.insert(motionId);
|
||
|
}
|
||
|
if (m_selectedMotionIds.size() > 1) {
|
||
|
return;
|
||
|
}
|
||
|
if (m_selectedMotionIds.size() == 1)
|
||
|
motionId = *m_selectedMotionIds.begin();
|
||
|
else
|
||
|
motionId = QUuid();
|
||
|
}
|
||
|
if (!m_selectedMotionIds.empty()) {
|
||
|
for (const auto &id: m_selectedMotionIds) {
|
||
|
updateMotionSelectState(id, false);
|
||
|
}
|
||
|
m_selectedMotionIds.clear();
|
||
|
}
|
||
|
if (m_currentSelectedMotionId != motionId) {
|
||
|
if (!m_currentSelectedMotionId.isNull()) {
|
||
|
updateMotionSelectState(m_currentSelectedMotionId, false);
|
||
|
}
|
||
|
m_currentSelectedMotionId = motionId;
|
||
|
if (!m_currentSelectedMotionId.isNull()) {
|
||
|
updateMotionSelectState(m_currentSelectedMotionId, true);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void MotionListWidget::mousePressEvent(QMouseEvent *event)
|
||
|
{
|
||
|
QModelIndex itemIndex = indexAt(event->pos());
|
||
|
QTreeView::mousePressEvent(event);
|
||
|
if (event->button() == Qt::LeftButton) {
|
||
|
bool multiple = QGuiApplication::queryKeyboardModifiers().testFlag(Qt::ControlModifier);
|
||
|
if (itemIndex.isValid()) {
|
||
|
QTreeWidgetItem *item = itemFromIndex(itemIndex);
|
||
|
auto motionId = QUuid(item->data(itemIndex.column(), Qt::UserRole).toString());
|
||
|
if (QGuiApplication::queryKeyboardModifiers().testFlag(Qt::ShiftModifier)) {
|
||
|
bool startAdd = false;
|
||
|
bool stopAdd = false;
|
||
|
std::vector<QUuid> waitQueue;
|
||
|
for (const auto &childId: m_document->motionIdList) {
|
||
|
if (m_shiftStartMotionId == childId || motionId == childId) {
|
||
|
if (startAdd) {
|
||
|
stopAdd = true;
|
||
|
} else {
|
||
|
startAdd = true;
|
||
|
}
|
||
|
}
|
||
|
if (startAdd)
|
||
|
waitQueue.push_back(childId);
|
||
|
if (stopAdd)
|
||
|
break;
|
||
|
}
|
||
|
if (stopAdd && !waitQueue.empty()) {
|
||
|
if (!m_selectedMotionIds.empty()) {
|
||
|
for (const auto &id: m_selectedMotionIds) {
|
||
|
updateMotionSelectState(id, false);
|
||
|
}
|
||
|
m_selectedMotionIds.clear();
|
||
|
}
|
||
|
if (!m_currentSelectedMotionId.isNull()) {
|
||
|
m_currentSelectedMotionId = QUuid();
|
||
|
}
|
||
|
for (const auto &waitId: waitQueue) {
|
||
|
selectMotion(waitId, true);
|
||
|
}
|
||
|
}
|
||
|
return;
|
||
|
} else {
|
||
|
m_shiftStartMotionId = motionId;
|
||
|
}
|
||
|
selectMotion(motionId, multiple);
|
||
|
return;
|
||
|
}
|
||
|
if (!multiple)
|
||
|
selectMotion(QUuid());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool MotionListWidget::isMotionSelected(QUuid motionId)
|
||
|
{
|
||
|
return (m_currentSelectedMotionId == motionId ||
|
||
|
m_selectedMotionIds.find(motionId) != m_selectedMotionIds.end());
|
||
|
}
|
||
|
|
||
|
void MotionListWidget::showContextMenu(const QPoint &pos)
|
||
|
{
|
||
|
QMenu contextMenu(this);
|
||
|
|
||
|
std::set<QUuid> unorderedMotionIds = m_selectedMotionIds;
|
||
|
if (!m_currentSelectedMotionId.isNull())
|
||
|
unorderedMotionIds.insert(m_currentSelectedMotionId);
|
||
|
|
||
|
std::vector<QUuid> motionIds;
|
||
|
for (const auto &cand: m_document->motionIdList) {
|
||
|
if (unorderedMotionIds.find(cand) != unorderedMotionIds.end())
|
||
|
motionIds.push_back(cand);
|
||
|
}
|
||
|
|
||
|
QAction modifyAction(tr("Modify"), this);
|
||
|
if (motionIds.size() == 1) {
|
||
|
connect(&modifyAction, &QAction::triggered, this, [=]() {
|
||
|
emit modifyMotion(*motionIds.begin());
|
||
|
});
|
||
|
contextMenu.addAction(&modifyAction);
|
||
|
}
|
||
|
|
||
|
QAction copyAction(tr("Copy"), this);
|
||
|
if (!motionIds.empty()) {
|
||
|
connect(©Action, &QAction::triggered, this, &MotionListWidget::copy);
|
||
|
contextMenu.addAction(©Action);
|
||
|
}
|
||
|
|
||
|
QAction pasteAction(tr("Paste"), this);
|
||
|
if (m_document->hasPastableMotionsInClipboard()) {
|
||
|
connect(&pasteAction, &QAction::triggered, m_document, &SkeletonDocument::paste);
|
||
|
contextMenu.addAction(&pasteAction);
|
||
|
}
|
||
|
|
||
|
QAction deleteAction(tr("Delete"), this);
|
||
|
if (!motionIds.empty()) {
|
||
|
connect(&deleteAction, &QAction::triggered, [=]() {
|
||
|
for (const auto &motionId: motionIds)
|
||
|
emit removeMotion(motionId);
|
||
|
});
|
||
|
contextMenu.addAction(&deleteAction);
|
||
|
}
|
||
|
|
||
|
contextMenu.exec(mapToGlobal(pos));
|
||
|
}
|
||
|
|
||
|
void MotionListWidget::resizeEvent(QResizeEvent *event)
|
||
|
{
|
||
|
QTreeWidget::resizeEvent(event);
|
||
|
if (calculateColumnCount() != columnCount())
|
||
|
reload();
|
||
|
}
|
||
|
|
||
|
int MotionListWidget::calculateColumnCount()
|
||
|
{
|
||
|
if (nullptr == parentWidget())
|
||
|
return 0;
|
||
|
|
||
|
int columns = parentWidget()->width() / Theme::motionPreviewImageSize;
|
||
|
if (0 == columns)
|
||
|
columns = 1;
|
||
|
return columns;
|
||
|
}
|
||
|
|
||
|
void MotionListWidget::reload()
|
||
|
{
|
||
|
removeAllContent();
|
||
|
|
||
|
int columns = calculateColumnCount();
|
||
|
if (0 == columns)
|
||
|
return;
|
||
|
|
||
|
int columnWidth = parentWidget()->width() / columns;
|
||
|
|
||
|
//qDebug() << "parentWidth:" << parentWidget()->width() << "columnWidth:" << columnWidth << "columns:" << columns;
|
||
|
|
||
|
setColumnCount(columns);
|
||
|
for (int i = 0; i < columns; i++)
|
||
|
setColumnWidth(i, columnWidth);
|
||
|
|
||
|
decltype(m_document->motionIdList.size()) motionIndex = 0;
|
||
|
while (motionIndex < m_document->motionIdList.size()) {
|
||
|
QTreeWidgetItem *item = new QTreeWidgetItem(this);
|
||
|
item->setFlags((item->flags() | Qt::ItemIsEnabled) & ~(Qt::ItemIsSelectable) & ~(Qt::ItemIsEditable));
|
||
|
for (int col = 0; col < columns && motionIndex < m_document->motionIdList.size(); col++, motionIndex++) {
|
||
|
const auto &motionId = m_document->motionIdList[motionIndex];
|
||
|
item->setSizeHint(col, QSize(columnWidth, MotionWidget::preferredHeight() + 2));
|
||
|
item->setData(col, Qt::UserRole, motionId.toString());
|
||
|
MotionWidget *widget = new MotionWidget(m_document, motionId);
|
||
|
connect(widget, &MotionWidget::modifyMotion, this, &MotionListWidget::modifyMotion);
|
||
|
setItemWidget(item, col, widget);
|
||
|
widget->reload();
|
||
|
widget->updateCheckedState(isMotionSelected(motionId));
|
||
|
m_itemMap[motionId] = std::make_pair(item, col);
|
||
|
}
|
||
|
invisibleRootItem()->addChild(item);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void MotionListWidget::removeAllContent()
|
||
|
{
|
||
|
m_itemMap.clear();
|
||
|
clear();
|
||
|
}
|
||
|
|
||
|
bool MotionListWidget::mouseMove(QMouseEvent *event)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
bool MotionListWidget::wheel(QWheelEvent *event)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
bool MotionListWidget::mouseRelease(QMouseEvent *event)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
bool MotionListWidget::mousePress(QMouseEvent *event)
|
||
|
{
|
||
|
if (event->button() == Qt::RightButton) {
|
||
|
showContextMenu(mapFromGlobal(event->globalPos()));
|
||
|
return false;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
bool MotionListWidget::mouseDoubleClick(QMouseEvent *event)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
bool MotionListWidget::keyPress(QKeyEvent *event)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
void MotionListWidget::copy()
|
||
|
{
|
||
|
if (m_selectedMotionIds.empty() && m_currentSelectedMotionId.isNull())
|
||
|
return;
|
||
|
|
||
|
std::set<QUuid> limitMotionIds = m_selectedMotionIds;
|
||
|
if (!m_currentSelectedMotionId.isNull())
|
||
|
limitMotionIds.insert(m_currentSelectedMotionId);
|
||
|
|
||
|
std::set<QUuid> emptySet;
|
||
|
|
||
|
SkeletonSnapshot snapshot;
|
||
|
m_document->toSnapshot(&snapshot, emptySet, SkeletonDocumentToSnapshotFor::Motions,
|
||
|
emptySet, limitMotionIds);
|
||
|
QString snapshotXml;
|
||
|
QXmlStreamWriter xmlStreamWriter(&snapshotXml);
|
||
|
saveSkeletonToXmlStream(&snapshot, &xmlStreamWriter);
|
||
|
QClipboard *clipboard = QApplication::clipboard();
|
||
|
clipboard->setText(snapshotXml);
|
||
|
}
|