[education/cantor] src: Improve worksheet usability
Alexander Semke <[email protected]>
| Newsgroups | gmane.comp.kde.cvs |
|---|---|
| Message-ID | <[email protected]> |
Git commit 4b0bfeb229303ae101bef5d28563dea707f6611b by Alexander Semke, on behalf of Nanhao Lv.
Committed on 26/07/2026 at 19:50.
Pushed by asemke into branch 'master'.
Improve worksheet usability
M +20 -19 src/hierarchyentry.cpp
M +302 -53 src/worksheet.cpp
M +17 -3 src/worksheet.h
M +5 -0 src/worksheetcontrolitem.cpp
M +1 -0 src/worksheetcontrolitem.h
M +110 -69 src/worksheetentry.cpp
M +16 -0 src/worksheettexteditoritem.cpp
M +17 -0 src/worksheettextitem.cpp
M +2 -15 src/worksheetview.cpp
https://invent.kde.org/education/cantor/-/commit/4b0bfeb229303ae101bef5d28563dea707f6611b
diff --git a/src/hierarchyentry.cpp b/src/hierarchyentry.cpp
index 87c4d8a8..91982c3f 100644
--- a/src/hierarchyentry.cpp
+++ b/src/hierarchyentry.cpp
@@ -10,12 +10,10 @@
#include <QJsonObject>
#include <QRegularExpression>
-#include <QDrag>
-#include <QBitmap>
-#include <QMimeData>
#include <QPainter>
#include <QDebug>
#include <QActionGroup>
+#include <QtMath>
#include <QUuid>
#include <KLocalizedString>
@@ -475,35 +473,35 @@ void HierarchyEntry::startDrag(QPointF grabPos)
worksheet()->resetEntryCursor();
- auto* drag = new QDrag(worksheetView());
const qreal scale = worksheet()->renderer()->scale();
- const QRectF hierarchyBound(boundingRect().x(), boundingRect().y(), boundingRect().width(), m_controlElement.boundingRect().height());
- const QSizeF hierarchyZoneSize(size().width(), m_controlElement.boundingRect().height());
-
- QPixmap pixmap((hierarchyZoneSize * scale).toSize());
+ const qreal hierarchyHeight = m_controlElement.rect().height();
+ const QRectF hierarchyContentBound(boundingRect().x(), boundingRect().y(), boundingRect().width(), hierarchyHeight);
+ const QRectF hierarchyBound = hierarchyContentBound.united(
+ m_controlElement.mapRectToParent(m_controlElement.boundingRect()));
+ const QSizeF hierarchyZoneSize(size().width(), hierarchyHeight);
+ constexpr qreal previewMargin = 2.0;
+ const QRectF previewRect = hierarchyBound.adjusted(-previewMargin, -previewMargin, previewMargin, previewMargin);
+ const QSizeF previewPixelSize = previewRect.size() * scale;
+
+ QPixmap pixmap(qCeil(previewPixelSize.width()), qCeil(previewPixelSize.height()));
pixmap.fill(QColor(255, 255, 255, 0));
QPainter painter(&pixmap);
- const QRectF sceneRect = mapRectToScene(hierarchyBound);
- worksheet()->render(&painter, pixmap.rect(), sceneRect);
+ const QRectF sceneRect = mapRectToScene(previewRect);
+ worksheet()->render(&painter, QRectF(QPointF(), previewPixelSize), sceneRect);
painter.end();
- const QBitmap mask = pixmap.createMaskFromColor(QColor(255, 255, 255), Qt::MaskInColor);
-
- pixmap.setMask(mask);
- drag->setPixmap(pixmap);
-
+ QPoint hotSpot;
if (grabPos.isNull())
{
const QPointF scenePos = worksheetView()->sceneCursorPos();
- drag->setHotSpot((mapFromScene(scenePos) * scale).toPoint());
+ hotSpot = ((mapFromScene(scenePos) - previewRect.topLeft()) * scale).toPoint();
}
else
- drag->setHotSpot((grabPos * scale).toPoint());
- drag->setMimeData(new QMimeData());
+ hotSpot = ((grabPos - previewRect.topLeft()) * scale).toPoint();
- worksheet()->startDragWithHierarchy(this, drag, hierarchyZoneSize);
+ worksheet()->startDragWithHierarchy(this, pixmap, hotSpot, hierarchyZoneSize);
}
void HierarchyEntry::updateFonts(bool force)
@@ -613,5 +611,8 @@ void HierarchyEntry::handleControlElementDoubleClick()
void HierarchyEntry::updateAfterSettingsChanges()
{
+ WorksheetEntry::updateAfterSettingsChanges();
+ m_hierarchyLevelItem->updateThemeColors();
+ m_textItem->updateThemeColors();
updateFonts();
}
diff --git a/src/worksheet.cpp b/src/worksheet.cpp
index efbd9cf2..da3ec37b 100644
--- a/src/worksheet.cpp
+++ b/src/worksheet.cpp
@@ -30,17 +30,21 @@
#include <QApplication>
#include <QBuffer>
#include <QByteArray>
-#include <QDrag>
+#include <QEventLoop>
+#include <QGraphicsPixmapItem>
#include <QGraphicsSceneMouseEvent>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
+#include <QMouseEvent>
#include <QPrinter>
#include <QRegularExpression>
+#include <QSet>
#include <QTimer>
#include <QActionGroup>
#include <QFile>
#include <QScopedValueRollback>
+#include <QWheelEvent>
#include <KMessageBox>
#include <KActionCollection>
@@ -62,6 +66,13 @@ const double Worksheet::TopMargin = 12;
const double Worksheet::EntryCursorLength = 30;
const double Worksheet::EntryCursorWidth = 2;
+namespace
+{
+constexpr int DragScrollMargin = 48;
+constexpr int DragScrollInterval = 50;
+constexpr int DragScrollStep = 4;
+}
+
Worksheet::Worksheet(Cantor::Backend* backend, QWidget* parent, bool useDefaultWorksheetParameters)
: QGraphicsScene(parent),
m_cursorItemTimer(new QTimer(this)),
@@ -611,12 +622,21 @@ void Worksheet::invalidateLastEntry()
WorksheetEntry* Worksheet::entryAt(qreal x, qreal y)
{
- auto* item = itemAt(x, y, QTransform());
- while (item && (item->type() <= QGraphicsItem::UserType ||
- item->type() >= QGraphicsItem::UserType + 100))
- item = item->parentItem();
- if (item)
- return qobject_cast<WorksheetEntry*>(item->toGraphicsObject());
+ const auto sceneItems = items(QPointF(x, y));
+ for (auto* sceneItem : sceneItems)
+ {
+ auto* item = sceneItem;
+ while (item && (item->type() <= QGraphicsItem::UserType ||
+ item->type() >= QGraphicsItem::UserType + 100))
+ item = item->parentItem();
+
+ if (item)
+ {
+ if (auto* entry = qobject_cast<WorksheetEntry*>(item->toGraphicsObject()))
+ return entry;
+ }
+ }
+
return nullptr;
}
@@ -626,10 +646,24 @@ WorksheetEntry* Worksheet::entryAt(QPointF p)
}
void Worksheet::focusEntry(WorksheetEntry* entry)
+{
+ focusEntry(entry, WorksheetTextEditorItem::TopLeft);
+}
+
+void Worksheet::focusEntry(WorksheetEntry* entry, int pos, qreal xCoord)
{
if (!entry)
return;
- entry->focusEntry();
+
+ if (entry->focusEntry(pos, xCoord))
+ {
+ const KWorksheetCursor cursor = worksheetCursor();
+ if (cursor.isValid())
+ makeVisible(cursor);
+ else
+ makeVisible(entry);
+ }
+
resetEntryCursor();
//bool rt = entry->acceptRichText();
//setActionsEnabled(rt);
@@ -637,14 +671,49 @@ void Worksheet::focusEntry(WorksheetEntry* entry)
//ensureCursorVisible();
}
-void Worksheet::startDrag(WorksheetEntry* entry, QDrag* drag)
+bool Worksheet::execEntryDrag(const QPixmap& pixmap, const QPoint& hotSpot)
+{
+ const qreal scale = renderer()->scale();
+ m_dragPixmapItem = addPixmap(pixmap);
+ m_dragPixmapItem->setOpacity(0.75);
+ m_dragPixmapItem->setScale(scale > 0 ? 1.0 / scale : 1.0);
+ m_dragPixmapItem->setZValue(1000);
+ m_dragHotSpot = scale > 0 ? QPointF(hotSpot) / scale : QPointF(hotSpot);
+
+ QEventLoop eventLoop;
+ m_entryDragEventLoop = &eventLoop;
+ m_dragAccepted = false;
+
+ qApp->installEventFilter(this);
+ QApplication::setOverrideCursor(Qt::ClosedHandCursor);
+ updateDragPosition(worksheetView()->sceneCursorPos());
+ eventLoop.exec();
+ QApplication::restoreOverrideCursor();
+ qApp->removeEventFilter(this);
+
+ m_entryDragEventLoop = nullptr;
+
+ if (m_dragScrollTimer)
+ {
+ delete m_dragScrollTimer;
+ m_dragScrollTimer = nullptr;
+ }
+
+ delete m_dragPixmapItem;
+ m_dragPixmapItem = nullptr;
+
+ return m_dragAccepted;
+}
+
+void Worksheet::startDrag(WorksheetEntry* entry, const QPixmap& pixmap, const QPoint& hotSpot)
{
- if (m_readOnly || !entry || !drag)
+ if (m_readOnly || !entry || pixmap.isNull())
return;
resetEntryCursor();
m_dragEntry = entry;
+ m_dragWithHierarchy = false;
WorksheetEntry* originalPrevious = entry->previous();
WorksheetEntry* originalNext = entry->next();
@@ -667,11 +736,11 @@ void Worksheet::startDrag(WorksheetEntry* entry, QDrag* drag)
m_dragEntry->hide();
- const Qt::DropAction action = drag->exec();
+ const bool accepted = execEntryDrag(pixmap, hotSpot);
bool positionChanged = false;
- if (action == Qt::MoveAction && m_placeholderEntry)
+ if (accepted && m_placeholderEntry)
{
previous = m_placeholderEntry->previous();
next = m_placeholderEntry->next();
@@ -694,33 +763,33 @@ void Worksheet::startDrag(WorksheetEntry* entry, QDrag* drag)
setLastEntry(m_dragEntry);
m_dragEntry->show();
- const bool hierarchyMoved = m_dragEntry->type() == HierarchyEntry::Type;
-
- m_dragEntry->focusEntry();
+ WorksheetEntry* draggedEntry = m_dragEntry;
const QPointF scenePosition = worksheetView()->sceneCursorPos();
- if (entryAt(scenePosition) != m_dragEntry)
- m_dragEntry->hideActionBar();
+ if (entryAt(scenePosition) != draggedEntry)
+ draggedEntry->hideActionBar();
m_dragEntry = nullptr;
- if (hierarchyMoved && positionChanged)
+ if (positionChanged)
updateHierarchyLayout();
updateLayout();
+ focusEntry(draggedEntry);
if (positionChanged)
setModified();
}
-void Worksheet::startDragWithHierarchy(HierarchyEntry* entry, QDrag* drag, QSizeF responsibleZoneSize)
+void Worksheet::startDragWithHierarchy(HierarchyEntry* entry, const QPixmap& pixmap, const QPoint& hotSpot, QSizeF responsibleZoneSize)
{
- if (m_readOnly || !entry || !drag)
+ if (m_readOnly || !entry || pixmap.isNull())
return;
resetEntryCursor();
m_dragEntry = entry;
+ m_dragWithHierarchy = true;
m_hierarchySubentriesDrag = hierarchySubelements(entry);
m_hierarchyDragSize = responsibleZoneSize;
@@ -754,11 +823,11 @@ void Worksheet::startDragWithHierarchy(HierarchyEntry* entry, QDrag* drag, QSize
for (auto* subentry : m_hierarchySubentriesDrag)
subentry->hide();
- const Qt::DropAction action = drag->exec();
+ const bool accepted = execEntryDrag(pixmap, hotSpot);
bool positionChanged = false;
- if (action == Qt::MoveAction && m_placeholderEntry)
+ if (accepted && m_placeholderEntry)
{
previous = m_placeholderEntry->previous();
next = m_placeholderEntry->next();
@@ -791,11 +860,11 @@ void Worksheet::startDragWithHierarchy(HierarchyEntry* entry, QDrag* drag, QSize
for (auto* subentry : m_hierarchySubentriesDrag)
subentry->show();
- m_dragEntry->focusEntry();
+ WorksheetEntry* draggedEntry = m_dragEntry;
const QPointF scenePosition = worksheetView()->sceneCursorPos();
- if (entryAt(scenePosition) != m_dragEntry)
- m_dragEntry->hideActionBar();
+ if (entryAt(scenePosition) != draggedEntry)
+ draggedEntry->hideActionBar();
#ifndef NDEBUG
for (auto* current = firstEntry(); current; current = current->next())
@@ -804,9 +873,11 @@ void Worksheet::startDragWithHierarchy(HierarchyEntry* entry, QDrag* drag, QSize
m_hierarchySubentriesDrag.clear();
m_dragEntry = nullptr;
+ m_dragWithHierarchy = false;
updateHierarchyLayout();
updateLayout();
+ focusEntry(draggedEntry);
if (positionChanged)
setModified();
@@ -820,7 +891,7 @@ void Worksheet::evaluate()
loginToSession();
// evaluate the worksheet if the login was successful
- if (m_session && m_session->status() == Cantor::Session::Done) {
+ if (m_session && m_session->status() == Cantor::Session::Done && firstEntry()) {
firstEntry()->evaluate(WorksheetEntry::EvaluateNext);
setModified();
}
@@ -2094,7 +2165,27 @@ void Worksheet::mousePressEvent(QGraphicsSceneMouseEvent* event)
void Worksheet::keyPressEvent(QKeyEvent* event)
{
if (m_readOnly)
+ {
+ const bool navigationKey = event->key() == Qt::Key_Left
+ || event->key() == Qt::Key_Right
+ || event->key() == Qt::Key_Up
+ || event->key() == Qt::Key_Down
+ || event->key() == Qt::Key_Home
+ || event->key() == Qt::Key_End
+ || event->key() == Qt::Key_PageUp
+ || event->key() == Qt::Key_PageDown
+ || event->key() == Qt::Key_Tab
+ || event->key() == Qt::Key_Backtab
+ || event->key() == Qt::Key_Escape;
+ const bool safeModifiers = !(event->modifiers() & (Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier));
+ const bool safeShortcut = event->matches(QKeySequence::Copy) || event->matches(QKeySequence::SelectAll);
+
+ if ((navigationKey && safeModifiers) || safeShortcut)
+ QGraphicsScene::keyPressEvent(event);
+ else
+ event->ignore();
return;
+ }
if ((event->modifiers() & Qt::ControlModifier) && (event->key() == Qt::Key_1))
worksheetView()->actualSize();
@@ -2104,6 +2195,75 @@ void Worksheet::keyPressEvent(QKeyEvent* event)
QGraphicsScene::keyPressEvent(event);
}
+bool Worksheet::eventFilter(QObject* watched, QEvent* event)
+{
+ if (m_dragEntry && m_entryDragEventLoop)
+ {
+ if (event->type() == QEvent::MouseMove && watched->isWidgetType())
+ {
+ updateDragPosition(worksheetView()->sceneCursorPos());
+ event->accept();
+ return true;
+ }
+
+ if (event->type() == QEvent::Wheel)
+ {
+ auto* wheelEvent = static_cast<QWheelEvent*>(event);
+ int delta = wheelEvent->pixelDelta().y();
+ if (delta == 0)
+ delta = wheelEvent->angleDelta().y() / 2;
+
+ if (delta != 0)
+ {
+ worksheetView()->scrollBy(-delta);
+ updateDragPosition(worksheetView()->sceneCursorPos());
+ }
+
+ wheelEvent->accept();
+ return true;
+ }
+
+ if (event->type() == QEvent::MouseButtonRelease && watched->isWidgetType())
+ {
+ auto* mouseEvent = static_cast<QMouseEvent*>(event);
+ if (mouseEvent->button() == Qt::LeftButton)
+ {
+ m_dragAccepted = true;
+ m_entryDragEventLoop->quit();
+ mouseEvent->accept();
+ return true;
+ }
+ }
+
+ if (event->type() == QEvent::KeyPress)
+ {
+ auto* keyEvent = static_cast<QKeyEvent*>(event);
+ if (keyEvent->key() == Qt::Key_Escape)
+ {
+ m_dragAccepted = false;
+ m_entryDragEventLoop->quit();
+ }
+
+ keyEvent->accept();
+ return true;
+ }
+
+ if (event->type() == QEvent::ShortcutOverride || event->type() == QEvent::KeyRelease)
+ {
+ event->accept();
+ return true;
+ }
+
+ if (event->type() == QEvent::ApplicationDeactivate)
+ {
+ m_dragAccepted = false;
+ m_entryDragEventLoop->quit();
+ }
+ }
+
+ return QGraphicsScene::eventFilter(watched, event);
+}
+
void Worksheet::setActionCollection(KActionCollection* collection)
{
m_collection = collection;
@@ -2654,14 +2814,14 @@ void Worksheet::dragLeaveEvent(QGraphicsSceneDragDropEvent* event)
updateLayout();
}
-void Worksheet::dragMoveEvent(QGraphicsSceneDragDropEvent* event)
+void Worksheet::updateDragPosition(const QPointF& pos)
{
- if (!m_dragEntry) {
- QGraphicsScene::dragMoveEvent(event);
+ if (!m_dragEntry)
return;
- }
- QPointF pos = event->scenePos();
+ if (m_dragPixmapItem)
+ m_dragPixmapItem->setPos(pos - m_dragHotSpot);
+
auto* entry = entryAt(pos);
WorksheetEntry* prev = nullptr;
WorksheetEntry* next = nullptr;
@@ -2681,8 +2841,6 @@ void Worksheet::dragMoveEvent(QGraphicsSceneDragDropEvent* event)
}
}
- const bool dragWithHierarchy = !m_hierarchySubentriesDrag.empty();
-
if (m_placeholderEntry)
{
if (prev == m_placeholderEntry)
@@ -2694,7 +2852,7 @@ void Worksheet::dragMoveEvent(QGraphicsSceneDragDropEvent* event)
if (prev || next)
{
- const QSizeF placeholderSize = dragWithHierarchy ? m_hierarchyDragSize : m_dragEntry->size();
+ const QSizeF placeholderSize = m_dragWithHierarchy ? m_hierarchyDragSize : m_dragEntry->size();
if (!m_placeholderEntry)
m_placeholderEntry = new PlaceHolderEntry(this, QSizeF(0, 0));
@@ -2737,16 +2895,27 @@ void Worksheet::dragMoveEvent(QGraphicsSceneDragDropEvent* event)
const QPoint viewPos = worksheetView()->mapFromScene(pos);
const int viewHeight = worksheetView()->viewport()->height();
- if ((viewPos.y() < 10 || viewPos.y() > viewHeight - 10) &&
+ if ((viewPos.y() < DragScrollMargin || viewPos.y() > viewHeight - DragScrollMargin) &&
!m_dragScrollTimer) {
m_dragScrollTimer = new QTimer(this);
m_dragScrollTimer->setSingleShot(true);
- m_dragScrollTimer->setInterval(100);
+ m_dragScrollTimer->setInterval(DragScrollInterval);
connect(m_dragScrollTimer, SIGNAL(timeout()), this,
SLOT(updateDragScrollTimer()));
m_dragScrollTimer->start();
}
+}
+
+void Worksheet::dragMoveEvent(QGraphicsSceneDragDropEvent* event)
+{
+ if (!m_dragEntry)
+ {
+ QGraphicsScene::dragMoveEvent(event);
+ return;
+ }
+
+ updateDragPosition(event->scenePos());
event->accept();
}
@@ -2766,16 +2935,18 @@ void Worksheet::updateDragScrollTimer()
const QWidget* viewport = worksheetView()->viewport();
const int viewHeight = viewport->height();
if (!m_dragEntry || !(viewport->rect().contains(viewPos)) ||
- (viewPos.y() >= 10 && viewPos.y() <= viewHeight - 10)) {
+ (viewPos.y() >= DragScrollMargin && viewPos.y() <= viewHeight - DragScrollMargin)) {
delete m_dragScrollTimer;
m_dragScrollTimer = nullptr;
return;
}
- if (viewPos.y() < 10)
- worksheetView()->scrollBy(-10*(10 - viewPos.y()));
+ if (viewPos.y() < DragScrollMargin)
+ worksheetView()->scrollBy(-DragScrollStep * (DragScrollMargin - viewPos.y()));
else
- worksheetView()->scrollBy(10*(viewHeight - viewPos.y()));
+ worksheetView()->scrollBy(DragScrollStep * (viewPos.y() - (viewHeight - DragScrollMargin)));
+
+ updateDragPosition(worksheetView()->sceneCursorPos());
m_dragScrollTimer->start();
}
@@ -2995,41 +3166,119 @@ void Worksheet::selectionEvaluate()
// run entries in worksheet order: from top to down
for (auto* entry = firstEntry(); entry; entry = entry->next())
if (m_selectedEntries.indexOf(entry) != -1)
- entry->evaluate();
+ entry->evaluate(WorksheetEntry::DoNothing);
}
void Worksheet::selectionMoveUp()
{
- bool moveHierarchyEntry = false;
+ bool moved = false;
+ WorksheetEntry* focusTarget = nullptr;
+ QSet<WorksheetEntry*> entriesMovedWithHierarchy;
+ for (auto* selectedEntry : m_selectedEntries)
+ {
+ if (selectedEntry->type() != HierarchyEntry::Type)
+ continue;
+
+ const auto subentries = hierarchySubelements(static_cast<HierarchyEntry*>(selectedEntry));
+ for (auto* subentry : subentries)
+ entriesMovedWithHierarchy.insert(subentry);
+ }
+
// movement up should have an order from top to down.
for(auto* entry = firstEntry(); entry; entry = entry->next())
if(m_selectedEntries.indexOf(entry) != -1)
- if (entry->previous() && m_selectedEntries.indexOf(entry->previous()) == -1)
+ {
+ if (entriesMovedWithHierarchy.contains(entry))
+ continue;
+
+ if (!focusTarget)
+ focusTarget = entry;
+ WorksheetEntry* previousEntry = entry->previous();
+ bool previousBlockSelected = previousEntry && m_selectedEntries.indexOf(previousEntry) != -1;
+ if (!previousBlockSelected && entry->type() == HierarchyEntry::Type)
+ {
+ auto* hierarchyEntry = static_cast<HierarchyEntry*>(entry);
+ for (auto* selectedEntry : m_selectedEntries)
+ {
+ if (selectedEntry->type() != HierarchyEntry::Type)
+ continue;
+
+ auto* selectedHierarchy = static_cast<HierarchyEntry*>(selectedEntry);
+ if (selectedHierarchy->level() != hierarchyEntry->level())
+ continue;
+
+ const auto subentries = hierarchySubelements(selectedHierarchy);
+ WorksheetEntry* selectedBlockLast = subentries.empty() ? selectedEntry : subentries.back();
+ if (selectedBlockLast == previousEntry)
+ {
+ previousBlockSelected = true;
+ break;
+ }
+ }
+ }
+
+ if (previousEntry && !previousBlockSelected)
{
+ WorksheetEntry* oldPrevious = previousEntry;
entry->moveToPrevious(false);
- if (entry->type() == HierarchyEntry::Type)
- moveHierarchyEntry = true;
+ moved = moved || entry->previous() != oldPrevious;
}
- if (moveHierarchyEntry)
+ }
+
+ if (moved)
+ {
updateHierarchyLayout();
- updateLayout();
+ updateLayout();
+ makeVisible(focusTarget);
+ }
}
void Worksheet::selectionMoveDown()
{
- bool moveHierarchyEntry = false;
+ bool moved = false;
+ WorksheetEntry* focusTarget = nullptr;
+ QSet<WorksheetEntry*> entriesMovedWithHierarchy;
+ for (auto* selectedEntry : m_selectedEntries)
+ {
+ if (selectedEntry->type() != HierarchyEntry::Type)
+ continue;
+
+ const auto subentries = hierarchySubelements(static_cast<HierarchyEntry*>(selectedEntry));
+ for (auto* subentry : subentries)
+ entriesMovedWithHierarchy.insert(subentry);
+ }
+
// movement up should have an order from down to top.
for(auto* entry = lastEntry(); entry; entry = entry->previous())
if(m_selectedEntries.indexOf(entry) != -1)
- if (entry->next() && m_selectedEntries.indexOf(entry->next()) == -1)
+ {
+ if (entriesMovedWithHierarchy.contains(entry))
+ continue;
+
+ if (!focusTarget)
+ focusTarget = entry;
+ WorksheetEntry* nextEntry = entry->next();
+ if (entry->type() == HierarchyEntry::Type)
{
+ const auto subentries = hierarchySubelements(static_cast<HierarchyEntry*>(entry));
+ if (!subentries.empty())
+ nextEntry = subentries.back()->next();
+ }
+
+ if (nextEntry && m_selectedEntries.indexOf(nextEntry) == -1)
+ {
+ WorksheetEntry* oldPrevious = entry->previous();
entry->moveToNext(false);
- if (entry->type() == HierarchyEntry::Type)
- moveHierarchyEntry = true;
+ moved = moved || entry->previous() != oldPrevious;
}
- if (moveHierarchyEntry)
+ }
+
+ if (moved)
+ {
updateHierarchyLayout();
- updateLayout();
+ updateLayout();
+ makeVisible(focusTarget);
+ }
}
void Worksheet::notifyEntryFocus(WorksheetEntry* entry)
diff --git a/src/worksheet.h b/src/worksheet.h
index 7f6bed38..d2af0fd0 100644
--- a/src/worksheet.h
+++ b/src/worksheet.h
@@ -37,9 +37,11 @@ class PlaceHolderEntry;
class WorksheetTextItem;
class QAction;
-class QDrag;
+class QEventLoop;
class QGraphicsObject;
+class QGraphicsPixmapItem;
class QMenu;
+class QPixmap;
class QPrinter;
class QSyntaxHighlighter;
class KActionCollection;
@@ -84,8 +86,8 @@ class Worksheet : public QGraphicsScene
void setModified();
- void startDrag(WorksheetEntry*, QDrag*);
- void startDragWithHierarchy(HierarchyEntry*, QDrag*, QSizeF responsibleZoneSize);
+ void startDrag(WorksheetEntry*, const QPixmap&, const QPoint& hotSpot);
+ void startDragWithHierarchy(HierarchyEntry*, const QPixmap&, const QPoint& hotSpot, QSizeF responsibleZoneSize);
void setActionCollection(KActionCollection*);
QMenu* createContextMenu();
@@ -197,6 +199,7 @@ class Worksheet : public QGraphicsScene
void print(QPrinter*);
void paste();
void focusEntry(WorksheetEntry*);
+ void focusEntry(WorksheetEntry*, int pos, qreal xCoord = 0);
void evaluate();
void evaluateCurrentEntry();
@@ -320,8 +323,12 @@ class Worksheet : public QGraphicsScene
bool isValidEntry(WorksheetEntry*);
private:
+ friend class WorksheetEntry;
+
friend class WorksheetHierarchyManager;
+ bool eventFilter(QObject*, QEvent*) override;
+
private Q_SLOTS:
//void checkEntriesForSanity();
@@ -341,6 +348,8 @@ class Worksheet : public QGraphicsScene
WorksheetEntry* entryAt(qreal x, qreal y);
WorksheetEntry* entryAt(QPointF);
WorksheetEntry* entryAt(int row);
+ bool execEntryDrag(const QPixmap&, const QPoint& hotSpot);
+ void updateDragPosition(const QPointF&);
void removeDragPlaceholder();
void updateEntryCursor(QGraphicsSceneMouseEvent*);
void addEntryFromEntryCursor();
@@ -370,6 +379,11 @@ class Worksheet : public QGraphicsScene
WorksheetEntry* m_firstEntry{nullptr};
WorksheetEntry* m_lastEntry{nullptr};
WorksheetEntry* m_dragEntry{nullptr};
+ QEventLoop* m_entryDragEventLoop{nullptr};
+ QGraphicsPixmapItem* m_dragPixmapItem{nullptr};
+ QPointF m_dragHotSpot;
+ bool m_dragAccepted{false};
+ bool m_dragWithHierarchy{false};
std::vector<WorksheetEntry*> m_hierarchySubentriesDrag;
QSizeF m_hierarchyDragSize;
WorksheetEntry* m_choosenCursorEntry{nullptr};
diff --git a/src/worksheetcontrolitem.cpp b/src/worksheetcontrolitem.cpp
index 311d230d..823fd8b7 100644
--- a/src/worksheetcontrolitem.cpp
+++ b/src/worksheetcontrolitem.cpp
@@ -21,6 +21,11 @@ WorksheetControlItem::WorksheetControlItem(Worksheet* worksheet, WorksheetEntry*
setFlags(flags() | QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsFocusable);
}
+QRectF WorksheetControlItem::boundingRect() const
+{
+ return rect().normalized().adjusted(-1, -1, 1, 1);
+}
+
void WorksheetControlItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
{
Q_UNUSED(option);
diff --git a/src/worksheetcontrolitem.h b/src/worksheetcontrolitem.h
index d7fb6181..133e40fc 100644
--- a/src/worksheetcontrolitem.h
+++ b/src/worksheetcontrolitem.h
@@ -16,6 +16,7 @@ class WorksheetControlItem: public QObject, public QGraphicsRectItem
Q_OBJECT
public:
WorksheetControlItem(Worksheet* worksheet, WorksheetEntry* parent);
+ QRectF boundingRect() const override;
Q_SIGNALS:
void doubleClick();
diff --git a/src/worksheetentry.cpp b/src/worksheetentry.cpp
index cee2c97b..70f8ec39 100644
--- a/src/worksheetentry.cpp
+++ b/src/worksheetentry.cpp
@@ -19,18 +19,16 @@
#include "worksheettoolbutton.h"
#include "worksheetview.h"
-#include <QDrag>
#include <QIcon>
#include <QPropertyAnimation>
#include <QParallelAnimationGroup>
#include <QMetaMethod>
-#include <QMimeData>
#include <QGraphicsProxyWidget>
-#include <QBitmap>
#include <QJsonArray>
#include <QJsonObject>
#include <QPainter>
#include <QGraphicsSceneMouseEvent>
+#include <QtMath>
#include <KColorScheme>
#include <KLocalizedString>
@@ -278,29 +276,26 @@ void WorksheetEntry::startDrag(QPointF grabPos)
// We need reset entry cursor manually, because otherwise the entry cursor will be visible on draggable item
worksheet()->resetEntryCursor();
- QDrag* drag = new QDrag(worksheetView());
- qDebug() << size();
const qreal scale = worksheet()->renderer()->scale();
- QPixmap pixmap((size()*scale).toSize());
+ constexpr qreal previewMargin = 2.0;
+ const QRectF previewRect = boundingRect().adjusted(-previewMargin, -previewMargin, previewMargin, previewMargin);
+ const QSizeF previewPixelSize = previewRect.size() * scale;
+ QPixmap pixmap(qCeil(previewPixelSize.width()), qCeil(previewPixelSize.height()));
pixmap.fill(QColor(255, 255, 255, 0));
QPainter painter(&pixmap);
- const QRectF sceneRect = mapRectToScene(boundingRect());
- worksheet()->render(&painter, pixmap.rect(), sceneRect);
+ const QRectF sceneRect = mapRectToScene(previewRect);
+ worksheet()->render(&painter, QRectF(QPointF(), previewPixelSize), sceneRect);
painter.end();
- QBitmap mask = pixmap.createMaskFromColor(QColor(255, 255, 255),
- Qt::MaskInColor);
- pixmap.setMask(mask);
- drag->setPixmap(pixmap);
+ QPoint hotSpot;
if (grabPos.isNull()) {
const QPointF scenePos = worksheetView()->sceneCursorPos();
- drag->setHotSpot((mapFromScene(scenePos) * scale).toPoint());
+ hotSpot = ((mapFromScene(scenePos) - previewRect.topLeft()) * scale).toPoint();
} else {
- drag->setHotSpot((grabPos * scale).toPoint());
+ hotSpot = ((grabPos - previewRect.topLeft()) * scale).toPoint();
}
- drag->setMimeData(new QMimeData());
- worksheet()->startDrag(this, drag);
+ worksheet()->startDrag(this, pixmap, hotSpot);
}
@@ -509,7 +504,7 @@ void WorksheetEntry::evaluateNext(EvaluationOption opt)
entry->evaluate(EvaluateNext);
} else if (opt == FocusNext) {
worksheet()->setModified();
- entry->focusEntry(WorksheetTextEditorItem::BottomRight);
+ worksheet()->focusEntry(entry, WorksheetTextEditorItem::BottomRight);
}
else
worksheet()->setModified();
@@ -517,7 +512,7 @@ void WorksheetEntry::evaluateNext(EvaluationOption opt)
if (!worksheet()->isLoadingFromFile() && (!isEmpty() || type() != CommandEntry::Type))
worksheet()->appendCommandEntry();
else
- focusEntry();
+ worksheet()->focusEntry(this);
worksheet()->setModified();
}
}
@@ -762,12 +757,6 @@ void WorksheetEntry::startRemoving(bool warn)
return;
}
- if (!worksheet()->animationsEnabled()) {
- m_aboutToBeRemoved = true;
- remove();
- return;
- }
-
if (m_aboutToBeRemoved)
return;
@@ -775,17 +764,22 @@ void WorksheetEntry::startRemoving(bool warn)
if (!next()) {
if (previous() && previous()->isEmpty() &&
!previous()->aboutToBeRemoved()) {
- previous()->focusEntry();
+ worksheet()->focusEntry(previous());
} else {
WorksheetEntry* next = worksheet()->appendCommandEntry();
setNext(next);
- next->focusEntry();
}
} else {
- next()->focusEntry();
+ worksheet()->focusEntry(next());
}
}
+ if (!worksheet()->animationsEnabled()) {
+ m_aboutToBeRemoved = true;
+ remove();
+ return;
+ }
+
if (m_animation) {
endAnimation();
}
@@ -1025,66 +1019,113 @@ void WorksheetEntry::setCellSelected(bool val)
void WorksheetEntry::moveToNext(bool updateLayout)
{
- WorksheetEntry* next = this->next();
- if (next)
+ WorksheetEntry* blockLast = this;
+ if (type() == HierarchyEntry::Type)
{
- if (next->next())
- {
- next->next()->setPrevious(this);
- this->setNext(next->next());
- }
- else
- {
- worksheet()->setLastEntry(this);
- this->setNext(nullptr);
- }
+ const auto subentries = worksheet()->hierarchySubelements(static_cast<HierarchyEntry*>(this));
+ if (!subentries.empty())
+ blockLast = subentries.back();
+ }
- next->setPrevious(this->previous());
- next->setNext(this);
+ WorksheetEntry* nextBlockFirst = blockLast->next();
+ if (!nextBlockFirst)
+ return;
- this->setPrevious(next);
- if (next->previous())
- next->previous()->setNext(next);
- else
- worksheet()->setFirstEntry(next);
+ WorksheetEntry* nextBlockLast = nextBlockFirst;
+ if (type() == HierarchyEntry::Type && nextBlockFirst->type() == HierarchyEntry::Type)
+ {
+ const auto subentries = worksheet()->hierarchySubelements(static_cast<HierarchyEntry*>(nextBlockFirst));
+ if (!subentries.empty())
+ nextBlockLast = subentries.back();
+ }
- if (updateLayout)
- worksheet()->updateLayout();
+ WorksheetEntry* before = previous();
+ WorksheetEntry* after = nextBlockLast->next();
- worksheet()->setModified();
+ if (before)
+ before->setNext(nextBlockFirst);
+ else
+ worksheet()->setFirstEntry(nextBlockFirst);
+ nextBlockFirst->setPrevious(before);
+
+ nextBlockLast->setNext(this);
+ setPrevious(nextBlockLast);
+
+ blockLast->setNext(after);
+ if (after)
+ after->setPrevious(blockLast);
+ else
+ worksheet()->setLastEntry(blockLast);
+
+ if (updateLayout)
+ {
+ worksheet()->updateHierarchyLayout();
+ worksheet()->updateLayout();
}
+
+ worksheet()->setModified();
}
void WorksheetEntry::moveToPrevious(bool updateLayout)
{
- WorksheetEntry* previous = this->previous();
- if (previous)
+ WorksheetEntry* previousBlockLast = previous();
+ if (!previousBlockLast)
+ return;
+
+ WorksheetEntry* blockLast = this;
+ WorksheetEntry* previousBlockFirst = previousBlockLast;
+
+ if (type() == HierarchyEntry::Type)
{
- if (previous->previous())
- {
- previous->previous()->setNext(this);
- this->setPrevious(previous->previous());
- }
- else
+ auto* hierarchyEntry = static_cast<HierarchyEntry*>(this);
+ const auto subentries = worksheet()->hierarchySubelements(hierarchyEntry);
+ if (!subentries.empty())
+ blockLast = subentries.back();
+
+ for (auto* candidate = previousBlockLast; candidate; candidate = candidate->previous())
{
- worksheet()->setFirstEntry(this);
- this->setPrevious(nullptr);
+ if (candidate->type() != HierarchyEntry::Type)
+ continue;
+
+ auto* candidateHierarchy = static_cast<HierarchyEntry*>(candidate);
+ if (candidateHierarchy->level() < hierarchyEntry->level())
+ break;
+ if (candidateHierarchy->level() != hierarchyEntry->level())
+ continue;
+
+ const auto candidateSubentries = worksheet()->hierarchySubelements(candidateHierarchy);
+ WorksheetEntry* candidateLast = candidateSubentries.empty() ? candidate : candidateSubentries.back();
+ if (candidateLast == previousBlockLast)
+ previousBlockFirst = candidate;
+ break;
}
+ }
- previous->setNext(this->next());
- previous->setPrevious(this);
+ WorksheetEntry* before = previousBlockFirst->previous();
+ WorksheetEntry* after = blockLast->next();
- this->setNext(previous);
- if (previous->next())
- previous->next()->setPrevious(previous);
- else
- worksheet()->setLastEntry(previous);
+ if (before)
+ before->setNext(this);
+ else
+ worksheet()->setFirstEntry(this);
+ setPrevious(before);
- if (updateLayout)
- worksheet()->updateLayout();
+ blockLast->setNext(previousBlockFirst);
+ previousBlockFirst->setPrevious(blockLast);
- worksheet()->setModified();
+ previousBlockLast->setNext(after);
+ if (after)
+ after->setPrevious(previousBlockLast);
+ else
+ worksheet()->setLastEntry(previousBlockLast);
+
+ if (updateLayout)
+ {
+ worksheet()->updateHierarchyLayout();
+ worksheet()->updateLayout();
}
+
+ worksheet()->setModified();
}
void WorksheetEntry::recalculateControlGeometry()
diff --git a/src/worksheettexteditoritem.cpp b/src/worksheettexteditoritem.cpp
index 31713322..6c843326 100644
--- a/src/worksheettexteditoritem.cpp
+++ b/src/worksheettexteditoritem.cpp
@@ -1002,6 +1002,22 @@ void WorksheetTextEditorItem::keyPressEvent(QKeyEvent* event)
if (!m_view->hasFocus())
m_view->setFocus();
+ if (!worksheet()->isReadOnly() && modifiers == Qt::ControlModifier
+ && (key == Qt::Key_Up || key == Qt::Key_Down))
+ {
+ auto* entry = qobject_cast<WorksheetEntry*>(parentObject());
+ if (entry)
+ {
+ if (key == Qt::Key_Up)
+ entry->moveToPrevious();
+ else
+ entry->moveToNext();
+ }
+
+ event->accept();
+ return;
+ }
+
QString actionName;
const KTextEditor::Cursor cursor = m_view->cursorPosition();
const bool isSimpleNav = (modifiers == Qt::NoModifier) || ((modifiers & Qt::ShiftModifier) && !(modifiers & ~Qt::ShiftModifier));
diff --git a/src/worksheettextitem.cpp b/src/worksheettextitem.cpp
index 9fb9fcb0..19c6abc9 100644
--- a/src/worksheettextitem.cpp
+++ b/src/worksheettextitem.cpp
@@ -404,6 +404,23 @@ Cantor::Session* WorksheetTextItem::session()
void WorksheetTextItem::keyPressEvent(QKeyEvent* event)
{
worksheet()->updateFocusedTextItem(this);
+
+ if (!worksheet()->isReadOnly() && event->modifiers() == Qt::ControlModifier
+ && (event->key() == Qt::Key_Up || event->key() == Qt::Key_Down))
+ {
+ auto* entry = qobject_cast<WorksheetEntry*>(parentObject());
+ if (entry)
+ {
+ if (event->key() == Qt::Key_Up)
+ entry->moveToPrevious();
+ else
+ entry->moveToNext();
+ }
+
+ event->accept();
+ return;
+ }
+
switch (event->key()) {
case Qt::Key_Left:
if (event->modifiers() == Qt::NoModifier && textCursor().atStart()) {
diff --git a/src/worksheetview.cpp b/src/worksheetview.cpp
index 13760b0a..55b14974 100644
--- a/src/worksheetview.cpp
+++ b/src/worksheetview.cpp
@@ -186,21 +186,8 @@ void WorksheetView::scrollBy(int dy)
if (!verticalScrollBar())
return;
- int ny = verticalScrollBar()->value() + dy;
- if (ny < 0)
- ny = 0;
- else if (ny > verticalScrollBar()->maximum())
- ny = verticalScrollBar()->maximum();
-
- int x;
- if (horizontalScrollBar())
- x = horizontalScrollBar()->value();
- else
- x = 0;
-
- const qreal w = viewport()->width() / m_scale;
- const qreal h = viewport()->height() / m_scale;
- makeVisible(QRectF(x, ny, w, h));
+ endAnimation();
+ verticalScrollBar()->setValue(verticalScrollBar()->value() + dy);
}
void WorksheetView::endAnimation()