Enable/Disable cut-copy-paste actions as required

Cut: focus widget has a selection, a 'cut' slot and 'canCut'
Copy: focus widget has a selection and a 'copy' slot
Paste: focus widget has a 'paste' slot and can accept the clipboard item
This commit is contained in:
Srivats P 2020-03-16 22:26:16 +05:30
parent 7227dd734b
commit 12f81a1dba
5 changed files with 199 additions and 53 deletions

View File

@ -19,10 +19,19 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "clipboardhelper.h" #include "clipboardhelper.h"
#include "xtableview.h"
#include <QAction> #include <QAction>
#include <QApplication> #include <QApplication>
#include <QClipboard> #include <QClipboard>
#include <QIcon> #include <QIcon>
#include <QItemSelection>
#if 0 // change 0 to 1 for debug
#define xDebug(...) qDebug(__VA_ARGS__)
#else
#define xDebug(...)
#endif
ClipboardHelper::ClipboardHelper(QObject *parent) ClipboardHelper::ClipboardHelper(QObject *parent)
: QObject(parent) : QObject(parent)
@ -43,10 +52,13 @@ ClipboardHelper::ClipboardHelper(QObject *parent)
connect(actionCopy_, SIGNAL(triggered()), SLOT(actionTriggered())); connect(actionCopy_, SIGNAL(triggered()), SLOT(actionTriggered()));
connect(actionPaste_, SIGNAL(triggered()), SLOT(actionTriggered())); connect(actionPaste_, SIGNAL(triggered()), SLOT(actionTriggered()));
#if 0 // FIXME: doesn't work correctly yet connect(qApp, SIGNAL(focusChanged(QWidget*, QWidget*)),
connect(qGuiApp, SIGNAL(focusChanged(QWidget*, QWidget*)), SLOT(updateCutCopyStatus(QWidget*, QWidget*)));
SLOT(updateClipboardActions()));
#endif connect(qApp, SIGNAL(focusChanged(QWidget*, QWidget*)),
SLOT(updatePasteStatus()));
connect(QGuiApplication::clipboard(), SIGNAL(dataChanged()),
SLOT(updatePasteStatus()));
} }
QList<QAction*> ClipboardHelper::actions() QList<QAction*> ClipboardHelper::actions()
@ -57,7 +69,7 @@ QList<QAction*> ClipboardHelper::actions()
void ClipboardHelper::actionTriggered() void ClipboardHelper::actionTriggered()
{ {
QWidget *focusWidget = QApplication::focusWidget(); QWidget *focusWidget = qApp->focusWidget();
if (!focusWidget) if (!focusWidget)
return; return;
@ -66,7 +78,7 @@ void ClipboardHelper::actionTriggered()
QString action = sender()->objectName() QString action = sender()->objectName()
.remove("action").append("()").toLower(); .remove("action").append("()").toLower();
if (focusWidget->metaObject()->indexOfSlot(qPrintable(action)) < 0) { if (focusWidget->metaObject()->indexOfSlot(qPrintable(action)) < 0) {
qDebug("%s slot not found for object %s:%s ", xDebug("%s slot not found for object %s:%s ",
qPrintable(action), qPrintable(action),
qPrintable(focusWidget->objectName()), qPrintable(focusWidget->objectName()),
focusWidget->metaObject()->className()); focusWidget->metaObject()->className());
@ -78,32 +90,126 @@ void ClipboardHelper::actionTriggered()
Qt::DirectConnection); Qt::DirectConnection);
} }
void ClipboardHelper::updateActionStatus() void ClipboardHelper::updateCutCopyStatus(QWidget *old, QWidget *now)
{ {
QWidget *focusWidget = QApplication::focusWidget(); xDebug("In %s", __FUNCTION__);
if (!focusWidget)
return;
qDebug("In %s", __FUNCTION__); const XTableView *view = dynamic_cast<XTableView*>(old);
if (view) {
disconnect(view->selectionModel(),
SIGNAL(selectionChanged(const QItemSelection&,
const QItemSelection&)),
this,
SLOT(focusWidgetSelectionChanged(const QItemSelection&,
const QItemSelection&)));
disconnect(view->model(), SIGNAL(modelReset()),
this, SLOT(focusWidgetModelReset()));
}
if (!now) {
xDebug("No focus widget to copy from");
actionCut_->setEnabled(false);
actionCopy_->setEnabled(false);
return;
}
const QMetaObject *meta = now->metaObject();
if (meta->indexOfSlot("copy()") < 0) {
xDebug("Focus Widget (%s) doesn't have a copy slot",
qPrintable(now->objectName()));
actionCut_->setEnabled(false);
actionCopy_->setEnabled(false);
return;
}
view = dynamic_cast<XTableView*>(now);
if (view) {
connect(view->selectionModel(),
SIGNAL(selectionChanged(const QItemSelection&,
const QItemSelection&)),
SLOT(focusWidgetSelectionChanged(const QItemSelection&,
const QItemSelection&)));
connect(view->model(), SIGNAL(modelReset()),
this, SLOT(focusWidgetModelReset()));
if (!view->hasSelection()) {
xDebug("%s doesn't have anything selected to copy",
qPrintable(view->objectName()));
actionCut_->setEnabled(false);
actionCopy_->setEnabled(false);
return;
}
}
xDebug("%s model can cut: %d", qPrintable(view->objectName()),
view->canCut());
actionCut_->setEnabled(view->canCut());
xDebug("%s has a selection and copy slot: copy possible",
qPrintable(now->objectName()));
actionCopy_->setEnabled(true);
}
void ClipboardHelper::focusWidgetSelectionChanged(
const QItemSelection &selected, const QItemSelection &/*deselected*/)
{
xDebug("In %s", __FUNCTION__);
// Selection changed in the XTableView that has focus
const XTableView *view = dynamic_cast<XTableView*>(qApp->focusWidget());
xDebug("canCut:%d empty:%d", view->canCut(), selected.indexes().isEmpty());
actionCut_->setEnabled(!selected.indexes().isEmpty()
&& view && view->canCut());
actionCopy_->setEnabled(!selected.indexes().isEmpty());
}
void ClipboardHelper::updatePasteStatus()
{
xDebug("In %s", __FUNCTION__);
QWidget *focusWidget = qApp->focusWidget();
if (!focusWidget) {
xDebug("No focus widget to paste into");
actionPaste_->setEnabled(false);
return;
}
const QMimeData *item = QGuiApplication::clipboard()->mimeData();
if (!item || item->formats().isEmpty()) {
xDebug("Nothing on clipboard to paste");
actionPaste_->setEnabled(false);
return;
}
const QMetaObject *meta = focusWidget->metaObject(); const QMetaObject *meta = focusWidget->metaObject();
// FIXME: we should check if the mimeData's mimeType can be pasted in if (meta->indexOfSlot("paste()") < 0) {
// the focusWidget xDebug("Focus Widget (%s) doesn't have a paste slot",
actionPaste_->setEnabled(qGuiApp->clipboard()->mimeData() qPrintable(focusWidget->objectName()));
&& (meta->indexOfSlot("paste()") >= 0)); actionPaste_->setEnabled(false);
return;
bool hasSelection = false;
if (meta->indexOfProperty("hasSelectedText") >= 0)
hasSelection |= focusWidget->property("hasSelectedText").toBool();
bool ret = false;
if (meta->indexOfMethod("hasSelection()") >= 0) {
if (QMetaObject::invokeMethod(focusWidget, "hasSelection",
Qt::DirectConnection, Q_RETURN_ARG(bool, ret)))
hasSelection |= ret;
} }
actionCut_->setEnabled(hasSelection && (meta->indexOfSlot("cut") >= 0)); const XTableView *view = dynamic_cast<XTableView*>(focusWidget);
actionCopy_->setEnabled(hasSelection && (meta->indexOfSlot("copy") >= 0)); if (view && !view->canPaste(item)) {
xDebug("%s cannot accept this item (%s)",
qPrintable(view->objectName()),
qPrintable(item->formats().join("|")));
actionPaste_->setEnabled(false);
return;
} }
xDebug("%s can accept this item (%s): paste possible",
qPrintable(focusWidget->objectName()),
qPrintable(item->formats().join("|")));
actionPaste_->setEnabled(true);
}
void ClipboardHelper::focusWidgetModelReset()
{
xDebug("In %s", __FUNCTION__);
QWidget *focusWidget = qApp->focusWidget();
updateCutCopyStatus(focusWidget, focusWidget); // re-eval cut/copy status
}
#undef xDebug

View File

@ -24,6 +24,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include <QList> #include <QList>
class QAction; class QAction;
class QItemSelection;
class ClipboardHelper : public QObject class ClipboardHelper : public QObject
{ {
@ -35,7 +36,11 @@ public:
private slots: private slots:
void actionTriggered(); void actionTriggered();
void updateActionStatus(); void updateCutCopyStatus(QWidget *old, QWidget *now);
void focusWidgetSelectionChanged(const QItemSelection &selected,
const QItemSelection &deselected);
void focusWidgetModelReset();
void updatePasteStatus();
private: private:
QAction *actionCut_{nullptr}; QAction *actionCut_{nullptr};

View File

@ -109,6 +109,11 @@ QVariant LogsModel::data(const QModelIndex &index, int role) const
return QVariant(); return QVariant();
} }
Qt::DropActions LogsModel::supportedDropActions() const
{
return Qt::IgnoreAction; // read-only model, doesn't accept any data
}
QStringList LogsModel::mimeTypes() const QStringList LogsModel::mimeTypes() const
{ {
return QStringList() << "text/plain"; return QStringList() << "text/plain";

View File

@ -43,6 +43,8 @@ public:
QVariant headerData(int section, Qt::Orientation orientation, QVariant headerData(int section, Qt::Orientation orientation,
int role = Qt::DisplayRole) const; int role = Qt::DisplayRole) const;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
Qt::DropActions supportedDropActions() const;
QStringList mimeTypes() const; QStringList mimeTypes() const;
QMimeData* mimeData(const QModelIndexList &indexes) const; QMimeData* mimeData(const QModelIndexList &indexes) const;

View File

@ -22,6 +22,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include <QTableView> #include <QTableView>
#include "devicegroupmodel.h"
#include "streammodel.h"
#include <QApplication> #include <QApplication>
#include <QClipboard> #include <QClipboard>
#include <QKeyEvent> #include <QKeyEvent>
@ -36,36 +39,33 @@ public:
XTableView(QWidget *parent) : QTableView(parent) {} XTableView(QWidget *parent) : QTableView(parent) {}
virtual ~XTableView() {} virtual ~XTableView() {}
#if 0 void setModel(QAbstractItemModel *model)
Q_INVOKABLE bool hasSelection() const {
// XXX: yes, this is hacky; but there's no way to figure out
// if a model allows removeRows() or not
if (dynamic_cast<StreamModel*>(model)
|| dynamic_cast<DeviceGroupModel*>(model))
_modelAllowsRemove = true;
else
_modelAllowsRemove = false;
QTableView::setModel(model);
}
bool hasSelection() const
{ {
return !selectionModel()->selectedIndexes().isEmpty(); return !selectionModel()->selectedIndexes().isEmpty();
} }
#endif
protected: bool canCut() const
virtual void paintEvent(QPaintEvent *event)
{ {
if (!model()->hasChildren()) { return _modelAllowsRemove;
QPainter painter(viewport());
style()->drawItemText(&painter, viewport()->rect(),
layoutDirection() | Qt::AlignCenter, palette(),
true, whatsThis(), QPalette::WindowText);
}
else
QTableView::paintEvent(event);
} }
virtual void keyPressEvent(QKeyEvent *event) bool canPaste(const QMimeData *data) const
{ {
if (event->matches(QKeySequence::Cut)) { return model()->canDropMimeData(data, Qt::CopyAction,
cut(); 0, 0, QModelIndex());
} else if (event->matches(QKeySequence::Copy)) {
copy();
} else if (event->matches(QKeySequence::Paste)) {
paste();
} else
QTableView::keyPressEvent(event);
} }
public slots: public slots:
@ -102,7 +102,7 @@ public slots:
{ {
const QMimeData *mimeData = qApp->clipboard()->mimeData(); const QMimeData *mimeData = qApp->clipboard()->mimeData();
if (!mimeData) if (!mimeData || mimeData->formats().isEmpty())
return; return;
if (selectionModel()->hasSelection() if (selectionModel()->hasSelection()
@ -128,6 +128,34 @@ public slots:
model()->dropMimeData(mimeData, Qt::CopyAction, model()->dropMimeData(mimeData, Qt::CopyAction,
row, column, QModelIndex()); row, column, QModelIndex());
} }
protected:
virtual void paintEvent(QPaintEvent *event)
{
if (!model()->hasChildren()) {
QPainter painter(viewport());
style()->drawItemText(&painter, viewport()->rect(),
layoutDirection() | Qt::AlignCenter, palette(),
true, whatsThis(), QPalette::WindowText);
}
else
QTableView::paintEvent(event);
}
virtual void keyPressEvent(QKeyEvent *event)
{
if (event->matches(QKeySequence::Cut)) {
cut();
} else if (event->matches(QKeySequence::Copy)) {
copy();
} else if (event->matches(QKeySequence::Paste)) {
paste();
} else
QTableView::keyPressEvent(event);
}
private:
bool _modelAllowsRemove{false};
}; };
#endif #endif