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 "xtableview.h"
#include <QAction>
#include <QApplication>
#include <QClipboard>
#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)
: QObject(parent)
@ -43,10 +52,13 @@ ClipboardHelper::ClipboardHelper(QObject *parent)
connect(actionCopy_, SIGNAL(triggered()), SLOT(actionTriggered()));
connect(actionPaste_, SIGNAL(triggered()), SLOT(actionTriggered()));
#if 0 // FIXME: doesn't work correctly yet
connect(qGuiApp, SIGNAL(focusChanged(QWidget*, QWidget*)),
SLOT(updateClipboardActions()));
#endif
connect(qApp, SIGNAL(focusChanged(QWidget*, QWidget*)),
SLOT(updateCutCopyStatus(QWidget*, QWidget*)));
connect(qApp, SIGNAL(focusChanged(QWidget*, QWidget*)),
SLOT(updatePasteStatus()));
connect(QGuiApplication::clipboard(), SIGNAL(dataChanged()),
SLOT(updatePasteStatus()));
}
QList<QAction*> ClipboardHelper::actions()
@ -57,7 +69,7 @@ QList<QAction*> ClipboardHelper::actions()
void ClipboardHelper::actionTriggered()
{
QWidget *focusWidget = QApplication::focusWidget();
QWidget *focusWidget = qApp->focusWidget();
if (!focusWidget)
return;
@ -66,7 +78,7 @@ void ClipboardHelper::actionTriggered()
QString action = sender()->objectName()
.remove("action").append("()").toLower();
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(focusWidget->objectName()),
focusWidget->metaObject()->className());
@ -78,32 +90,126 @@ void ClipboardHelper::actionTriggered()
Qt::DirectConnection);
}
void ClipboardHelper::updateActionStatus()
void ClipboardHelper::updateCutCopyStatus(QWidget *old, QWidget *now)
{
QWidget *focusWidget = QApplication::focusWidget();
if (!focusWidget)
return;
xDebug("In %s", __FUNCTION__);
qDebug("In %s", __FUNCTION__);
const QMetaObject *meta = focusWidget->metaObject();
// FIXME: we should check if the mimeData's mimeType can be pasted in
// the focusWidget
actionPaste_->setEnabled(qGuiApp->clipboard()->mimeData()
&& (meta->indexOfSlot("paste()") >= 0));
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;
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()));
}
actionCut_->setEnabled(hasSelection && (meta->indexOfSlot("cut") >= 0));
actionCopy_->setEnabled(hasSelection && (meta->indexOfSlot("copy") >= 0));
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();
if (meta->indexOfSlot("paste()") < 0) {
xDebug("Focus Widget (%s) doesn't have a paste slot",
qPrintable(focusWidget->objectName()));
actionPaste_->setEnabled(false);
return;
}
const XTableView *view = dynamic_cast<XTableView*>(focusWidget);
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>
class QAction;
class QItemSelection;
class ClipboardHelper : public QObject
{
@ -35,7 +36,11 @@ public:
private slots:
void actionTriggered();
void updateActionStatus();
void updateCutCopyStatus(QWidget *old, QWidget *now);
void focusWidgetSelectionChanged(const QItemSelection &selected,
const QItemSelection &deselected);
void focusWidgetModelReset();
void updatePasteStatus();
private:
QAction *actionCut_{nullptr};

View File

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

View File

@ -43,6 +43,8 @@ public:
QVariant headerData(int section, Qt::Orientation orientation,
int role = Qt::DisplayRole) const;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
Qt::DropActions supportedDropActions() const;
QStringList mimeTypes() 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 "devicegroupmodel.h"
#include "streammodel.h"
#include <QApplication>
#include <QClipboard>
#include <QKeyEvent>
@ -36,36 +39,33 @@ public:
XTableView(QWidget *parent) : QTableView(parent) {}
virtual ~XTableView() {}
#if 0
Q_INVOKABLE bool hasSelection() const
void setModel(QAbstractItemModel *model)
{
// 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();
}
#endif
protected:
virtual void paintEvent(QPaintEvent *event)
bool canCut() const
{
if (!model()->hasChildren()) {
QPainter painter(viewport());
style()->drawItemText(&painter, viewport()->rect(),
layoutDirection() | Qt::AlignCenter, palette(),
true, whatsThis(), QPalette::WindowText);
}
else
QTableView::paintEvent(event);
return _modelAllowsRemove;
}
virtual void keyPressEvent(QKeyEvent *event)
bool canPaste(const QMimeData *data) const
{
if (event->matches(QKeySequence::Cut)) {
cut();
} else if (event->matches(QKeySequence::Copy)) {
copy();
} else if (event->matches(QKeySequence::Paste)) {
paste();
} else
QTableView::keyPressEvent(event);
return model()->canDropMimeData(data, Qt::CopyAction,
0, 0, QModelIndex());
}
public slots:
@ -102,7 +102,7 @@ public slots:
{
const QMimeData *mimeData = qApp->clipboard()->mimeData();
if (!mimeData)
if (!mimeData || mimeData->formats().isEmpty())
return;
if (selectionModel()->hasSelection()
@ -128,6 +128,34 @@ public slots:
model()->dropMimeData(mimeData, Qt::CopyAction,
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