diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -79,6 +79,7 @@ mathrender.cpp mathrendertask.cpp extended_document.cpp + worksheetcontrolitem.cpp ) ki18n_wrap_ui(cantor_PART_SRCS imagesettings.ui) diff --git a/src/actionbar.cpp b/src/actionbar.cpp --- a/src/actionbar.cpp +++ b/src/actionbar.cpp @@ -31,7 +31,7 @@ m_pos = 0; m_height = 0; QPointF p = worksheet()->worksheetView()->viewRect().topRight(); - qreal w = qMin(parent->size().width(), + qreal w = qMin(parent->size().width() - WorksheetEntry::RightMargin, parent->mapFromScene(p).x()); setPos(w, 0); connect(worksheet()->worksheetView(), SIGNAL(viewRectChanged(QRectF)), @@ -64,7 +64,7 @@ if (!parentEntry()) return; QPointF p = worksheet()->worksheetView()->viewRect().topRight(); - qreal w = qMin(parentEntry()->size().width(), + qreal w = qMin(parentEntry()->size().width() - WorksheetEntry::RightMargin, parentEntry()->mapFromScene(p).x()); setPos(w, 0); const qreal scale = worksheet()->renderer()->scale(); diff --git a/src/commandentry.cpp b/src/commandentry.cpp --- a/src/commandentry.cpp +++ b/src/commandentry.cpp @@ -98,6 +98,8 @@ m_promptItem->setDoubleClickBehaviour(WorksheetTextItem::DoubleClickEventBehaviour::Simple); connect(m_promptItem, &WorksheetTextItem::doubleClick, this, &CommandEntry::changeResultCollapsingAction); + connect(&m_controlElement, &WorksheetControlItem::doubleClick, this, &CommandEntry::changeResultCollapsingAction); + connect(m_commandItem, &WorksheetTextItem::tabPressed, this, &CommandEntry::showCompletion); connect(m_commandItem, &WorksheetTextItem::backtabPressed, this, &CommandEntry::selectPreviousCompletion); connect(m_commandItem, &WorksheetTextItem::applyCompletion, this, &CommandEntry::applySelectedCompletion); @@ -751,6 +753,9 @@ for (ResultItem* item: m_resultItems) item->update(); } + + m_controlElement.isCollapsable = m_resultItems.size() > 0; + animateSizeChange(); } @@ -1234,34 +1239,36 @@ if (w == size().width() && !force) return; - m_promptItem->setPos(0,0); + m_promptItem->setPos(0, 0); double x = 0 + m_promptItem->width() + HorizontalSpacing; double y = 0; double width = 0; - m_commandItem->setGeometry(x,y, w-x); - width = qMax(width, m_commandItem->width()); + const qreal margin = worksheet()->isPrinting() ? 0 : RightMargin; + + m_commandItem->setGeometry(x, y, w - x - margin); + width = qMax(width, m_commandItem->width()+margin); y += qMax(m_commandItem->height(), m_promptItem->height()); foreach(WorksheetTextItem* information, m_informationItems) { y += VerticalSpacing; - y += information->setGeometry(x,y,w-x); - width = qMax(width, information->width()); + y += information->setGeometry(x, y, w - x - margin); + width = qMax(width, information->width() + margin); } if (m_errorItem) { y += VerticalSpacing; - y += m_errorItem->setGeometry(x,y,w-x); - width = qMax(width, m_errorItem->width()); + y += m_errorItem->setGeometry(x,y,w - x - margin); + width = qMax(width, m_errorItem->width() + margin); } for (auto* resultItem : m_resultItems) { if (!resultItem || !resultItem->graphicsObject()->isVisible()) continue; y += VerticalSpacing; - y += resultItem->setGeometry(x, y, w-x); - width = qMax(width, resultItem->width()); + y += resultItem->setGeometry(x, y, w - x - margin); + width = qMax(width, resultItem->width() + margin); } y += VerticalMargin; @@ -1301,6 +1308,7 @@ else setHidePrompt(); + m_controlElement.isCollapsed = true; animateSizeChange(); } @@ -1321,6 +1329,7 @@ else this->updatePrompt(); + m_controlElement.isCollapsed = false; animateSizeChange(); } diff --git a/src/imageentry.cpp b/src/imageentry.cpp --- a/src/imageentry.cpp +++ b/src/imageentry.cpp @@ -358,16 +358,19 @@ if (size().width() == w && !force) return; + //TODO somethinkg wrong with geometry and control element: control element appears in wrong place + const qreal margin = worksheet()->isPrinting() ? 0 : RightMargin; + double width; if (m_imageItem && m_imageItem->isVisible()) { - m_imageItem->setGeometry(0, 0, w, true); + m_imageItem->setGeometry(0, 0, w - margin, true); width = m_imageItem->width(); } else { - m_textItem->setGeometry(0, 0, w, true); + m_textItem->setGeometry(0, 0, w - margin, true); width = m_textItem->width(); } - setSize(QSizeF(width, height() + VerticalMargin)); + setSize(QSizeF(width + margin, height() + VerticalMargin)); } bool ImageEntry::wantToEvaluate() diff --git a/src/latexentry.cpp b/src/latexentry.cpp --- a/src/latexentry.cpp +++ b/src/latexentry.cpp @@ -503,8 +503,10 @@ if (size().width() == w && !force) return; - m_textItem->setGeometry(0, 0, w); - setSize(QSizeF(m_textItem->width(), m_textItem->height() + VerticalMargin)); + const qreal margin = worksheet()->isPrinting() ? 0 : RightMargin; + + m_textItem->setGeometry(0, 0, w - margin); + setSize(QSizeF(m_textItem->width() + margin, m_textItem->height() + VerticalMargin)); } bool LatexEntry::wantToEvaluate() diff --git a/src/markdownentry.cpp b/src/markdownentry.cpp --- a/src/markdownentry.cpp +++ b/src/markdownentry.cpp @@ -444,8 +444,10 @@ if (size().width() == w && !force) return; - m_textItem->setGeometry(0, 0, w); - setSize(QSizeF(m_textItem->width(), m_textItem->height() + VerticalMargin)); + const qreal margin = worksheet()->isPrinting() ? 0 : RightMargin; + + m_textItem->setGeometry(0, 0, w - margin); + setSize(QSizeF(m_textItem->width() + margin, m_textItem->height() + VerticalMargin)); } bool MarkdownEntry::eventFilter(QObject* object, QEvent* event) diff --git a/src/pagebreakentry.cpp b/src/pagebreakentry.cpp --- a/src/pagebreakentry.cpp +++ b/src/pagebreakentry.cpp @@ -133,10 +133,12 @@ if (size().width() == w && !force) return; + const qreal margin = worksheet()->isPrinting() ? 0 : RightMargin; + if (m_msgItem->isVisible()) { - m_msgItem->setGeometry(0, 0, w, true); + m_msgItem->setGeometry(0, 0, w - margin, true); - setSize(QSizeF(m_msgItem->width(), m_msgItem->height() + VerticalMargin)); + setSize(QSizeF(m_msgItem->width() + margin, m_msgItem->height() + VerticalMargin)); } else { setSize(QSizeF(w, 0)); } diff --git a/src/placeholderentry.cpp b/src/placeholderentry.cpp --- a/src/placeholderentry.cpp +++ b/src/placeholderentry.cpp @@ -26,6 +26,7 @@ PlaceHolderEntry::PlaceHolderEntry(Worksheet* worksheet, QSizeF s) : WorksheetEntry(worksheet) { + m_controlElement.hide(); setSize(s); } diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -26,6 +26,7 @@ ../mathrender.cpp ../mathrendertask.cpp ../extended_document.cpp + ../worksheetcontrolitem.cpp worksheet_test.cpp) ki18n_wrap_ui(worksheettest_SRCS ../imagesettings.ui) diff --git a/src/textentry.cpp b/src/textentry.cpp --- a/src/textentry.cpp +++ b/src/textentry.cpp @@ -490,8 +490,10 @@ if (size().width() == w && !force) return; - m_textItem->setGeometry(0, 0, w); - setSize(QSizeF(m_textItem->width(), m_textItem->height() + VerticalMargin)); + const qreal margin = worksheet()->isPrinting() ? 0 : RightMargin; + + m_textItem->setGeometry(0, 0, w - margin); + setSize(QSizeF(0 + m_textItem->width() + margin, m_textItem->height() + VerticalMargin)); } bool TextEntry::wantToEvaluate() diff --git a/src/worksheet.h b/src/worksheet.h --- a/src/worksheet.h +++ b/src/worksheet.h @@ -22,11 +22,14 @@ #ifndef WORKSHEET_H #define WORKSHEET_H + #include #include #include #include #include +#include +#include #include #include @@ -127,6 +130,8 @@ void setType(Worksheet::Type type); Worksheet::Type type() const; + void notifyEntryFocus(WorksheetEntry* entry); + // richtext struct RichTextInfo { bool bold; @@ -258,14 +263,22 @@ QJsonDocument toJupyterJson(); + bool isValidEntry(WorksheetEntry*); + private Q_SLOTS: void showCompletion(); //void checkEntriesForSanity(); WorksheetEntry* appendEntry(int type, bool focus = true); WorksheetEntry* insertEntry(int type, WorksheetEntry* current = nullptr); WorksheetEntry* insertEntryBefore(int type, WorksheetEntry* current = nullptr); + //Actions for selection + void selectionRemove(); + void selectionEvaluate(); + void selectionMoveUp(); + void selectionMoveDown(); + void animateEntryCursor(); private: @@ -335,6 +348,9 @@ QString m_backendName; QJsonObject* m_jupyterMetadata{nullptr}; + + QVector m_selectedEntries; + QQueue m_circularFocusBuffer; }; #endif // WORKSHEET_H diff --git a/src/worksheet.cpp b/src/worksheet.cpp --- a/src/worksheet.cpp +++ b/src/worksheet.cpp @@ -1536,81 +1536,93 @@ void Worksheet::populateMenu(QMenu *menu, QPointF pos) { - WorksheetEntry* entry = entryAt(pos); - if (entry && !entry->isAncestorOf(m_lastFocusedTextItem)) { - WorksheetTextItem* item = - qgraphicsitem_cast(itemAt(pos, QTransform())); - if (item && item->isEditable()) - m_lastFocusedTextItem = item; - } + // Two menu: for particular entry and for selection (multiple entry) + if (m_selectedEntries.isEmpty()) + { + WorksheetEntry* entry = entryAt(pos); + if (entry && !entry->isAncestorOf(m_lastFocusedTextItem)) { + WorksheetTextItem* item = + qgraphicsitem_cast(itemAt(pos, QTransform())); + if (item && item->isEditable()) + m_lastFocusedTextItem = item; + } - if (!isRunning()) - menu->addAction(QIcon::fromTheme(QLatin1String("system-run")), i18n("Evaluate Worksheet"), - this, SLOT(evaluate()), 0); + if (!isRunning()) + menu->addAction(QIcon::fromTheme(QLatin1String("system-run")), i18n("Evaluate Worksheet"), + this, SLOT(evaluate()), 0); + else + menu->addAction(QIcon::fromTheme(QLatin1String("process-stop")), i18n("Interrupt"), this, + SLOT(interrupt()), 0); + menu->addSeparator(); + + if (entry) { + QMenu* convertTo = new QMenu(menu); + QMenu* insert = new QMenu(menu); + QMenu* insertBefore = new QMenu(menu); + + convertTo->addAction(QIcon::fromTheme(QLatin1String("run-build")), i18n("Command Entry"), entry, &WorksheetEntry::convertToCommandEntry); + convertTo->addAction(QIcon::fromTheme(QLatin1String("draw-text")), i18n("Text Entry"), entry, &WorksheetEntry::convertToTextEntry); + #ifdef Discount_FOUND + convertTo->addAction(QIcon::fromTheme(QLatin1String("text-x-markdown")), i18n("Markdown Entry"), entry, &WorksheetEntry::convertToMarkdownEntry); + #endif + #ifdef WITH_EPS + convertTo->addAction(QIcon::fromTheme(QLatin1String("text-x-tex")), i18n("LaTeX Entry"), entry, &WorksheetEntry::convertToLatexEntry); + #endif + convertTo->addAction(QIcon::fromTheme(QLatin1String("image-x-generic")), i18n("Image"), entry, &WorksheetEntry::convertToImageEntry); + convertTo->addAction(QIcon::fromTheme(QLatin1String("go-next-view-page")), i18n("Page Break"), entry, &WorksheetEntry::converToPageBreakEntry); + + insert->addAction(QIcon::fromTheme(QLatin1String("run-build")), i18n("Command Entry"), entry, SLOT(insertCommandEntry())); + insert->addAction(QIcon::fromTheme(QLatin1String("draw-text")), i18n("Text Entry"), entry, SLOT(insertTextEntry())); + #ifdef Discount_FOUND + insert->addAction(QIcon::fromTheme(QLatin1String("text-x-markdown")), i18n("Markdown Entry"), entry, SLOT(insertMarkdownEntry())); + #endif + #ifdef WITH_EPS + insert->addAction(QIcon::fromTheme(QLatin1String("text-x-tex")), i18n("LaTeX Entry"), entry, SLOT(insertLatexEntry())); + #endif + insert->addAction(QIcon::fromTheme(QLatin1String("image-x-generic")), i18n("Image"), entry, SLOT(insertImageEntry())); + insert->addAction(QIcon::fromTheme(QLatin1String("go-next-view-page")), i18n("Page Break"), entry, SLOT(insertPageBreakEntry())); + + insertBefore->addAction(QIcon::fromTheme(QLatin1String("run-build")), i18n("Command Entry"), entry, SLOT(insertCommandEntryBefore())); + insertBefore->addAction(QIcon::fromTheme(QLatin1String("draw-text")), i18n("Text Entry"), entry, SLOT(insertTextEntryBefore())); + #ifdef Discount_FOUND + insertBefore->addAction(QIcon::fromTheme(QLatin1String("text-x-markdown")), i18n("Markdown Entry"), entry, SLOT(insertMarkdownEntryBefore())); + #endif + #ifdef WITH_EPS + insertBefore->addAction(QIcon::fromTheme(QLatin1String("text-x-tex")), i18n("LaTeX Entry"), entry, SLOT(insertLatexEntryBefore())); + #endif + insertBefore->addAction(QIcon::fromTheme(QLatin1String("image-x-generic")), i18n("Image"), entry, SLOT(insertImageEntryBefore())); + insertBefore->addAction(QIcon::fromTheme(QLatin1String("go-next-view-page")), i18n("Page Break"), entry, SLOT(insertPageBreakEntryBefore())); + + convertTo->setTitle(i18n("Convert Entry To")); + convertTo->setIcon(QIcon::fromTheme(QLatin1String("gtk-convert"))); + insert->setTitle(i18n("Insert Entry After")); + insert->setIcon(QIcon::fromTheme(QLatin1String("edit-table-insert-row-below"))); + insertBefore->setTitle(i18n("Insert Entry Before")); + insertBefore->setIcon(QIcon::fromTheme(QLatin1String("edit-table-insert-row-above"))); + + menu->addMenu(convertTo); + menu->addMenu(insert); + menu->addMenu(insertBefore); + } else { + menu->addAction(QIcon::fromTheme(QLatin1String("run-build")), i18n("Insert Command Entry"), this, SLOT(appendCommandEntry())); + menu->addAction(QIcon::fromTheme(QLatin1String("draw-text")), i18n("Insert Text Entry"), this, SLOT(appendTextEntry())); + #ifdef Discount_FOUND + menu->addAction(QIcon::fromTheme(QLatin1String("text-x-markdown")), i18n("Insert Markdown Entry"), this, SLOT(appendMarkdownEntry())); + #endif + #ifdef WITH_EPS + menu->addAction(QIcon::fromTheme(QLatin1String("text-x-tex")), i18n("Insert LaTeX Entry"), this, SLOT(appendLatexEntry())); + #endif + menu->addAction(QIcon::fromTheme(QLatin1String("image-x-generic")), i18n("Insert Image"), this, SLOT(appendImageEntry())); + menu->addAction(QIcon::fromTheme(QLatin1String("go-next-view-page")), i18n("Insert Page Break"), this, SLOT(appendPageBreakEntry())); + } + } else - menu->addAction(QIcon::fromTheme(QLatin1String("process-stop")), i18n("Interrupt"), this, - SLOT(interrupt()), 0); - menu->addSeparator(); - - if (entry) { - QMenu* convertTo = new QMenu(menu); - QMenu* insert = new QMenu(menu); - QMenu* insertBefore = new QMenu(menu); - - convertTo->addAction(QIcon::fromTheme(QLatin1String("run-build")), i18n("Command Entry"), entry, &WorksheetEntry::convertToCommandEntry); - convertTo->addAction(QIcon::fromTheme(QLatin1String("draw-text")), i18n("Text Entry"), entry, &WorksheetEntry::convertToTextEntry); -#ifdef Discount_FOUND - convertTo->addAction(QIcon::fromTheme(QLatin1String("text-x-markdown")), i18n("Markdown Entry"), entry, &WorksheetEntry::convertToMarkdownEntry); -#endif -#ifdef WITH_EPS - convertTo->addAction(QIcon::fromTheme(QLatin1String("text-x-tex")), i18n("LaTeX Entry"), entry, &WorksheetEntry::convertToLatexEntry); -#endif - convertTo->addAction(QIcon::fromTheme(QLatin1String("image-x-generic")), i18n("Image"), entry, &WorksheetEntry::convertToImageEntry); - convertTo->addAction(QIcon::fromTheme(QLatin1String("go-next-view-page")), i18n("Page Break"), entry, &WorksheetEntry::converToPageBreakEntry); - - insert->addAction(QIcon::fromTheme(QLatin1String("run-build")), i18n("Command Entry"), entry, SLOT(insertCommandEntry())); - insert->addAction(QIcon::fromTheme(QLatin1String("draw-text")), i18n("Text Entry"), entry, SLOT(insertTextEntry())); -#ifdef Discount_FOUND - insert->addAction(QIcon::fromTheme(QLatin1String("text-x-markdown")), i18n("Markdown Entry"), entry, SLOT(insertMarkdownEntry())); -#endif -#ifdef WITH_EPS - insert->addAction(QIcon::fromTheme(QLatin1String("text-x-tex")), i18n("LaTeX Entry"), entry, SLOT(insertLatexEntry())); -#endif - insert->addAction(QIcon::fromTheme(QLatin1String("image-x-generic")), i18n("Image"), entry, SLOT(insertImageEntry())); - insert->addAction(QIcon::fromTheme(QLatin1String("go-next-view-page")), i18n("Page Break"), entry, SLOT(insertPageBreakEntry())); - - insertBefore->addAction(QIcon::fromTheme(QLatin1String("run-build")), i18n("Command Entry"), entry, SLOT(insertCommandEntryBefore())); - insertBefore->addAction(QIcon::fromTheme(QLatin1String("draw-text")), i18n("Text Entry"), entry, SLOT(insertTextEntryBefore())); -#ifdef Discount_FOUND - insertBefore->addAction(QIcon::fromTheme(QLatin1String("text-x-markdown")), i18n("Markdown Entry"), entry, SLOT(insertMarkdownEntryBefore())); -#endif -#ifdef WITH_EPS - insertBefore->addAction(QIcon::fromTheme(QLatin1String("text-x-tex")), i18n("LaTeX Entry"), entry, SLOT(insertLatexEntryBefore())); -#endif - insertBefore->addAction(QIcon::fromTheme(QLatin1String("image-x-generic")), i18n("Image"), entry, SLOT(insertImageEntryBefore())); - insertBefore->addAction(QIcon::fromTheme(QLatin1String("go-next-view-page")), i18n("Page Break"), entry, SLOT(insertPageBreakEntryBefore())); - - convertTo->setTitle(i18n("Convert Entry To")); - convertTo->setIcon(QIcon::fromTheme(QLatin1String("gtk-convert"))); - insert->setTitle(i18n("Insert Entry After")); - insert->setIcon(QIcon::fromTheme(QLatin1String("edit-table-insert-row-below"))); - insertBefore->setTitle(i18n("Insert Entry Before")); - insertBefore->setIcon(QIcon::fromTheme(QLatin1String("edit-table-insert-row-above"))); - - menu->addMenu(convertTo); - menu->addMenu(insert); - menu->addMenu(insertBefore); - } else { - menu->addAction(QIcon::fromTheme(QLatin1String("run-build")), i18n("Insert Command Entry"), this, SLOT(appendCommandEntry())); - menu->addAction(QIcon::fromTheme(QLatin1String("draw-text")), i18n("Insert Text Entry"), this, SLOT(appendTextEntry())); -#ifdef Discount_FOUND - menu->addAction(QIcon::fromTheme(QLatin1String("text-x-markdown")), i18n("Insert Markdown Entry"), this, SLOT(appendMarkdownEntry())); -#endif -#ifdef WITH_EPS - menu->addAction(QIcon::fromTheme(QLatin1String("text-x-tex")), i18n("Insert LaTeX Entry"), this, SLOT(appendLatexEntry())); -#endif - menu->addAction(QIcon::fromTheme(QLatin1String("image-x-generic")), i18n("Insert Image"), this, SLOT(appendImageEntry())); - menu->addAction(QIcon::fromTheme(QLatin1String("go-next-view-page")), i18n("Insert Page Break"), this, SLOT(appendPageBreakEntry())); + { + menu->clear(); + menu->addAction(QIcon::fromTheme(QLatin1String("go-up")), i18n("Move Entries Up"), this, SLOT(selectionMoveUp()), 0); + menu->addAction(QIcon::fromTheme(QLatin1String("go-down")), i18n("Move Entries Down"), this, SLOT(selectionMoveDown()), 0); + menu->addAction(QIcon::fromTheme(QLatin1String("media-playback-start")), i18n("Evaluate Entries"), this, SLOT(selectionEvaluate()), 0); + menu->addAction(QIcon::fromTheme(QLatin1String("edit-delete")), i18n("Remove Entries"), this, SLOT(selectionRemove()), 0); } } @@ -1633,14 +1645,62 @@ void Worksheet::mousePressEvent(QGraphicsSceneMouseEvent* event) { - QGraphicsScene::mousePressEvent(event); /* if (event->button() == Qt::LeftButton && !focusItem() && lastEntry() && event->scenePos().y() > lastEntry()->y() + lastEntry()->size().height()) lastEntry()->focusEntry(WorksheetTextItem::BottomRight); */ - if (!m_readOnly) - updateEntryCursor(event); + + if (!m_readOnly && event->buttons() & Qt::LeftButton) + { + WorksheetEntry* selectedEntry = entryAt(event->scenePos()); + if (event->modifiers() & Qt::ControlModifier) + { + clearFocus(); + resetEntryCursor(); + + if (selectedEntry) + { + selectedEntry->setCellSelected(!selectedEntry->isCellSelected()); + selectedEntry->update(); + + WorksheetEntry* lastSelectedEntry = m_circularFocusBuffer.size() > 0 ? m_circularFocusBuffer.last() : nullptr; + if (lastSelectedEntry) + { + lastSelectedEntry->setCellSelected(!lastSelectedEntry->isCellSelected()); + lastSelectedEntry->update(); + m_circularFocusBuffer.clear(); + } + + for (WorksheetEntry* entry : {selectedEntry, lastSelectedEntry}) + if (entry) + { + if (entry->isCellSelected()) + m_selectedEntries.append(entry); + else if (!entry->isCellSelected()) + m_selectedEntries.removeOne(entry); + } + } + } + else + { + for (WorksheetEntry* entry : m_selectedEntries) + { + if(isValidEntry(entry)) + { + entry->setCellSelected(false); + entry->update(); + } + } + m_selectedEntries.clear(); + + if (selectedEntry) + notifyEntryFocus(selectedEntry); + + updateEntryCursor(event); + } + } + QGraphicsScene::mousePressEvent(event); } void Worksheet::keyPressEvent(QKeyEvent *keyEvent) @@ -2359,3 +2419,62 @@ m_animationsEnabled = animation_state; } } + +bool Worksheet::isValidEntry(WorksheetEntry* entry) +{ + for (WorksheetEntry* iter = firstEntry(); iter; iter = iter->next()) + if (entry == iter) + return true; + + return false; +} + +void Worksheet::selectionRemove() +{ + for (WorksheetEntry* entry : m_selectedEntries) + if (isValidEntry(entry)) + entry->startRemoving(); + + m_selectedEntries.clear(); +} + +void Worksheet::selectionEvaluate() +{ + // run entries in worksheet order: from top to down + for (WorksheetEntry* entry = firstEntry(); entry; entry = entry->next()) + if (m_selectedEntries.indexOf(entry) != -1) + entry->evaluate(); +} + +void Worksheet::selectionMoveUp() +{ + // movement up should have an order from top to down. + for(WorksheetEntry* entry = firstEntry(); entry; entry = entry->next()) + if(m_selectedEntries.indexOf(entry) != -1) + if (entry->previous() && m_selectedEntries.indexOf(entry->previous()) == -1) + entry->moveToPrevious(false); + updateLayout(); +} + +void Worksheet::selectionMoveDown() +{ + // movement up should have an order from down to top. + for(WorksheetEntry* entry = lastEntry(); entry; entry = entry->previous()) + if(m_selectedEntries.indexOf(entry) != -1) + if (entry->next() && m_selectedEntries.indexOf(entry->next()) == -1) + entry->moveToNext(false); + updateLayout(); +} + +void Worksheet::notifyEntryFocus(WorksheetEntry* entry) +{ + if (entry) + { + m_circularFocusBuffer.enqueue(entry); + + if (m_circularFocusBuffer.size() > 2) + m_circularFocusBuffer.dequeue(); + } + else + m_circularFocusBuffer.clear(); +} diff --git a/src/worksheetcontrolitem.h b/src/worksheetcontrolitem.h new file mode 100644 --- /dev/null +++ b/src/worksheetcontrolitem.h @@ -0,0 +1,56 @@ +/* + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + --- + Copyright (C) 2020 Sirgienko Nikita + */ +#ifndef WORKSHEETCONTROLITEM_H +#define WORKSHEETCONTROLITEM_H + +#include +#include + +class WorksheetEntry; +class Worksheet; + +class WorksheetControlItem: public QObject, public QGraphicsRectItem +{ + Q_OBJECT + public: + WorksheetControlItem(Worksheet* worksheet, WorksheetEntry* parent); + + Q_SIGNALS: + void doubleClick(); + void drag(const QPointF, const QPointF); + + private: + void hoverEnterEvent(QGraphicsSceneHoverEvent * event) override; + void hoverLeaveEvent(QGraphicsSceneHoverEvent * event) override; + void mouseMoveEvent(QGraphicsSceneMouseEvent* event) override; + void mouseDoubleClickEvent(QGraphicsSceneMouseEvent * event) override; + void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget = nullptr) override; + + public: + bool isSelected{false}; + bool isCollapsable{false}; + bool isCollapsed{false}; + + private: + Worksheet* m_worksheet{nullptr}; + bool m_isHovered{false}; +}; + +#endif // WORKSHEETCONTROLITEM_H diff --git a/src/worksheetcontrolitem.cpp b/src/worksheetcontrolitem.cpp new file mode 100644 --- /dev/null +++ b/src/worksheetcontrolitem.cpp @@ -0,0 +1,122 @@ +/* + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + --- + Copyright (C) 2020 Sirgienko Nikita + */ + +#include "worksheetcontrolitem.h" + +#include +#include +#include + +#include "worksheet.h" +#include "worksheetentry.h" + +WorksheetControlItem::WorksheetControlItem(Worksheet* worksheet, WorksheetEntry* parent): QGraphicsRectItem(parent) +{ + setAcceptDrops(true); + setAcceptHoverEvents(true); + setFlags(flags() | QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsFocusable); + m_worksheet = worksheet; +} + +void WorksheetControlItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) +{ + Q_UNUSED(option); + Q_UNUSED(widget); + + if (m_worksheet->isPrinting()) + return; + + painter->setViewTransformEnabled(true); + + if (m_isHovered) + painter->setPen(QPen(Qt::black, 2)); + else + painter->setPen(QPen(Qt::black, 1)); + + qreal x = rect().x(); + qreal y = rect().y(); + qreal w = rect().width(); + qreal h = rect().height(); + + painter->drawLine(x, y, x+w, y); + painter->drawLine(x+w, y, x+w, y+h); + painter->drawLine(x, y+h, x+w, y+h); + + //For collabsable entries draw "collapsing triangle" (form will depends from collapse's state) + if (isCollapsable) + { + if (isCollapsed) + { + QBrush brush = painter->brush(); + brush.setStyle(Qt::SolidPattern); + painter->setBrush(brush); + + QPolygon triangle; + triangle << QPoint(x, y) << QPoint(x+w, y) << QPoint(x+w, y+w); + + painter->drawPolygon(triangle); + } + else + painter->drawLine(x, y, x+w, y+w); + } + + if (isSelected) + { + //Use theme colour for selection, but with transparent + QColor color = QApplication::palette().color(QPalette::Highlight); + color.setAlpha(192); + + painter->fillRect(rect(), color); + } +} + +void WorksheetControlItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event) +{ + emit doubleClick(); + QGraphicsItem::mouseDoubleClickEvent(event); +} + +void WorksheetControlItem::mouseMoveEvent(QGraphicsSceneMouseEvent* event) +{ + if (event->buttons() != Qt::LeftButton) + return; + + const QPointF buttonDownPos = event->buttonDownPos(Qt::LeftButton); + if (contains(buttonDownPos) && (event->pos() - buttonDownPos).manhattanLength() >= QApplication::startDragDistance()) + { + ungrabMouse(); + emit drag(mapToParent(buttonDownPos), mapToParent(event->pos())); + event->accept(); + } +} + +void WorksheetControlItem::hoverEnterEvent(QGraphicsSceneHoverEvent* event) +{ + Q_UNUSED(event); + m_isHovered = true; + update(); +} + +void WorksheetControlItem::hoverLeaveEvent(QGraphicsSceneHoverEvent* event) +{ + Q_UNUSED(event); + m_isHovered = false; + update(); +} diff --git a/src/worksheetentry.h b/src/worksheetentry.h --- a/src/worksheetentry.h +++ b/src/worksheetentry.h @@ -22,11 +22,13 @@ #define WORKSHEETENTRY_H #include +#include #include #include "worksheet.h" #include "worksheettextitem.h" #include "worksheetcursor.h" +#include "worksheetcontrolitem.h" class TextEntry; class MarkdownEntry; @@ -111,6 +113,9 @@ QTextDocument::FindFlags qt_flags, const WorksheetCursor& pos = WorksheetCursor()); + bool isCellSelected(); + void setCellSelected(bool); + public Q_SLOTS: virtual bool evaluate(WorksheetEntry::EvaluationOption evalOp = FocusNext) = 0; virtual bool evaluateCurrentItem(); @@ -156,6 +161,9 @@ void startDrag(QPointF grabPos = QPointF()); + void moveToNext(bool updateLayout = true); + void moveToPrevious(bool updateLayout = true); + Q_SIGNALS: void aboutToBeDeleted(); @@ -184,13 +192,21 @@ QJsonObject jupyterMetadata() const; void setJupyterMetadata(QJsonObject metadata); + void recalculateControlGeometry(); + protected Q_SLOTS: virtual void remove(); void deleteActionBar(); void deleteActionBarAnimation(); - protected: + public: static const qreal VerticalMargin; + static const qreal ControlElementWidth; + static const qreal ControlElementBorder; + static const qreal RightMargin; + + protected: + WorksheetControlItem m_controlElement; private: QSizeF m_size; @@ -202,6 +218,7 @@ QPropertyAnimation* m_actionBarAnimation; bool m_aboutToBeRemoved; QJsonObject* m_jupyterMetadata; + bool m_isCellSelected{false}; }; #endif // WORKSHEETENTRY_H diff --git a/src/worksheetentry.cpp b/src/worksheetentry.cpp --- a/src/worksheetentry.cpp +++ b/src/worksheetentry.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include #include @@ -54,8 +55,11 @@ }; const qreal WorksheetEntry::VerticalMargin = 4; +const qreal WorksheetEntry::ControlElementWidth = 12; +const qreal WorksheetEntry::ControlElementBorder = 4; +const qreal WorksheetEntry::RightMargin = ControlElementWidth + 2*ControlElementBorder; -WorksheetEntry::WorksheetEntry(Worksheet* worksheet) : QGraphicsObject() +WorksheetEntry::WorksheetEntry(Worksheet* worksheet) : QGraphicsObject(), m_controlElement(worksheet, this) { m_next = nullptr; m_prev = nullptr; @@ -66,6 +70,8 @@ m_jupyterMetadata = nullptr; setAcceptHoverEvents(true); worksheet->addItem(this); + + connect(&m_controlElement, &WorksheetControlItem::drag, this, &WorksheetEntry::startDrag); } WorksheetEntry::~WorksheetEntry() @@ -354,6 +360,8 @@ void WorksheetEntry::populateMenu(QMenu* menu, QPointF pos) { + menu->addAction(QIcon::fromTheme(QLatin1String("go-up")), i18n("Move Up"), this, SLOT(moveToPrevious()), 0); + menu->addAction(QIcon::fromTheme(QLatin1String("go-down")), i18n("Move Down"), this, SLOT(moveToNext()), 0); if (!worksheet()->isRunning() && wantToEvaluate()) menu->addAction(QIcon::fromTheme(QLatin1String("media-playback-start")), i18n("Evaluate Entry"), this, SLOT(evaluate()), 0); @@ -405,15 +413,21 @@ { setPos(x, y); layOutForWidth(w); + + recalculateControlGeometry(); + return size().height(); } void WorksheetEntry::recalculateSize() { qreal height = size().height(); layOutForWidth(size().width(), true); if (height != size().height()) + { + recalculateControlGeometry(); worksheet()->updateEntrySize(this); + } } QPropertyAnimation* WorksheetEntry::sizeChangeAnimation(QSizeF s) @@ -440,6 +454,7 @@ void WorksheetEntry::sizeAnimated() { + recalculateControlGeometry(); worksheet()->updateEntrySize(this); } @@ -862,3 +877,82 @@ worksheet()->updateLayout(); deleteLater(); } + +bool WorksheetEntry::isCellSelected() +{ + return m_controlElement.isSelected; +} + +void WorksheetEntry::setCellSelected(bool val) +{ + m_controlElement.isSelected = val; +} + +void WorksheetEntry::moveToNext(bool updateLayout) +{ + WorksheetEntry* next = this->next(); + if (next) + { + if (next->next()) + { + next->next()->setPrevious(this); + this->setNext(next->next()); + } + else + { + worksheet()->setLastEntry(this); + this->setNext(nullptr); + } + + next->setPrevious(this->previous()); + next->setNext(this); + + this->setPrevious(next); + if (next->previous()) + next->previous()->setNext(next); + else + worksheet()->setFirstEntry(next); + + if (updateLayout) + worksheet()->updateLayout(); + } +} + +void WorksheetEntry::moveToPrevious(bool updateLayout) +{ + WorksheetEntry* previous = this->previous(); + if (previous) + { + if (previous->previous()) + { + previous->previous()->setNext(this); + this->setPrevious(previous->previous()); + } + else + { + worksheet()->setFirstEntry(this); + this->setPrevious(nullptr); + } + + previous->setNext(this->next()); + previous->setPrevious(this); + + this->setNext(previous); + if (previous->next()) + previous->next()->setPrevious(previous); + else + worksheet()->setLastEntry(previous); + + if (updateLayout) + worksheet()->updateLayout(); + } +} + +void WorksheetEntry::recalculateControlGeometry() +{ + m_controlElement.setRect( + size().width() - ControlElementWidth - ControlElementBorder, 0, // x,y + ControlElementWidth, size().height() - VerticalMargin // w,h + ); + m_controlElement.update(); +}