File indexing completed on 2024-04-28 05:52:37

0001 /*
0002     This file is part of the Okteta Gui library, made within the KDE community.
0003 
0004     SPDX-FileCopyrightText: 2008-2009 Friedrich W. H. Kossebau <kossebau@kde.org>
0005 
0006     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0007 */
0008 
0009 #include "mousenavigator.hpp"
0010 
0011 // lib
0012 #include <abstractbytearrayview.hpp>
0013 #include <bytearraytableranges.hpp>
0014 #include <bytearraytablecursor.hpp>
0015 #include <bytearraytablelayout.hpp>
0016 // Okteta core
0017 #include <Okteta/TextByteArrayAnalyzer>
0018 // Qt
0019 #include <QApplication>
0020 #include <QClipboard>
0021 #include <QMouseEvent>
0022 #include <QDrag>
0023 #include <QTimer>
0024 
0025 namespace Okteta {
0026 static constexpr int DefaultScrollTimerPeriod = 100;
0027 
0028 MouseNavigator::MouseNavigator(AbstractByteArrayView* view, AbstractMouseController* parent)
0029     : AbstractMouseController(view, parent)
0030     , mLMBPressed(false)
0031     , mInLMBDoubleClick(false)
0032     , mDragStartPossible(false)
0033 {
0034     mScrollTimer = new QTimer(this);
0035     mDragStartTimer = new QTimer(this);
0036     mTrippleClickTimer = new QTimer(this);
0037 
0038     connect(mScrollTimer,      &QTimer::timeout, this, &MouseNavigator::autoScrollTimerDone);
0039     connect(mDragStartTimer,   &QTimer::timeout, this, &MouseNavigator::startDrag);
0040     mDragStartTimer->setSingleShot(true);
0041     mTrippleClickTimer->setSingleShot(true);
0042 }
0043 
0044 MouseNavigator::~MouseNavigator() = default;
0045 
0046 bool MouseNavigator::handleMousePressEvent(QMouseEvent* mouseEvent)
0047 {
0048     bool eventUsed = false;
0049 
0050     if (mouseEvent->button() == Qt::LeftButton) {
0051         ByteArrayTableCursor* tableCursor = mView->tableCursor();
0052         ByteArrayTableRanges* tableRanges = mView->tableRanges();
0053         ByteArrayTableLayout* tableLayout = mView->layout();
0054 
0055         mView->pauseCursor();
0056         mView->finishByteEdit();
0057 
0058         mLMBPressed = true;
0059 
0060         // select whole line?
0061         if (mTrippleClickTimer->isActive()
0062             && (mouseEvent->globalPos() - mDoubleClickPoint).manhattanLength() < QApplication::startDragDistance()) {
0063             mTrippleClickTimer->stop();
0064             const Address indexAtFirstDoubleClickLinePosition = tableLayout->indexAtFirstLinePosition(mDoubleClickLine);
0065             tableRanges->setSelectionStart(indexAtFirstDoubleClickLinePosition);
0066             tableCursor->gotoIndex(indexAtFirstDoubleClickLinePosition);
0067             tableCursor->gotoLineEnd();
0068             tableRanges->setSelectionEnd(mView->cursorPosition());
0069 
0070             mView->updateChanged();
0071         } else {
0072             // TODO: pos() is now, not at the moment of the event, use globalPos() for that,.says dox
0073             const QPoint mousePoint = mView->viewportToColumns(mouseEvent->pos());
0074 
0075             // start of a drag perhaps?
0076             if (tableRanges->hasSelection() && tableRanges->selectionIncludes(mView->indexByPoint(mousePoint))) {
0077                 mDragStartPossible = true;
0078                 mDragStartTimer->start(QApplication::startDragTime());
0079                 mDragStartPoint = mousePoint;
0080             } else {
0081                 mView->placeCursor(mousePoint);
0082                 mView->ensureCursorVisible();
0083 
0084                 const Address realIndex = tableCursor->realIndex();
0085                 if (tableRanges->selectionStarted()) {
0086                     if (mouseEvent->modifiers() & Qt::SHIFT) {
0087                         tableRanges->setSelectionEnd(realIndex);
0088                     } else {
0089                         tableRanges->removeSelection();
0090                         tableRanges->setSelectionStart(realIndex);
0091                     }
0092                 } else {   // start of a new selection possible
0093                     tableRanges->setSelectionStart(realIndex);
0094 
0095                     if (!mView->isReadOnly() && (mouseEvent->modifiers() & Qt::SHIFT)) { // TODO: why only for readwrite?
0096                         tableRanges->setSelectionEnd(realIndex);
0097                     }
0098                 }
0099 
0100                 tableRanges->removeFurtherSelections();
0101             }
0102 
0103             if (tableRanges->isModified()) {
0104                 mView->updateChanged();
0105                 mView->viewport()->setCursor(mView->isReadOnly() ? Qt::ArrowCursor : Qt::IBeamCursor);
0106             }
0107         }
0108 
0109         mView->unpauseCursor();
0110         mView->emitSelectionSignals();
0111 
0112         eventUsed = true;
0113     }
0114 
0115     return eventUsed ? true : AbstractMouseController::handleMousePressEvent(mouseEvent);
0116 }
0117 
0118 bool MouseNavigator::handleMouseMoveEvent(QMouseEvent* mouseEvent)
0119 {
0120     bool eventUsed = false;
0121 
0122     if (mouseEvent->buttons() == Qt::LeftButton) {
0123         const QPoint movePoint = mView->viewportToColumns(mouseEvent->pos());
0124 
0125         if (mLMBPressed) {
0126             if (mDragStartPossible) {
0127                 mDragStartTimer->stop();
0128                 // moved enough for a drag?
0129                 if ((movePoint - mDragStartPoint).manhattanLength() > QApplication::startDragDistance()) {
0130                     startDrag();
0131                 }
0132                 if (!mView->isReadOnly()) {
0133                     mView->viewport()->setCursor(Qt::IBeamCursor);
0134                 }
0135             } else {
0136                 // selecting
0137                 handleMouseMove(movePoint);
0138             }
0139         } else if (!mView->isReadOnly()) {
0140             ByteArrayTableRanges* tableRanges = mView->tableRanges();
0141 
0142             // visual feedback for possible dragging
0143             const bool InSelection =
0144                 tableRanges->hasSelection() && tableRanges->selectionIncludes(mView->indexByPoint(movePoint));
0145             mView->viewport()->setCursor(InSelection ? Qt::ArrowCursor : Qt::IBeamCursor);
0146         }
0147         eventUsed = true;
0148     }
0149 
0150     return eventUsed ? true : AbstractMouseController::handleMouseMoveEvent(mouseEvent);
0151 }
0152 
0153 bool MouseNavigator::handleMouseReleaseEvent(QMouseEvent* mouseEvent)
0154 {
0155     bool eventUsed = false;
0156 
0157     if (mouseEvent->button() == Qt::LeftButton) {
0158         ByteArrayTableRanges* tableRanges = mView->tableRanges();
0159 
0160 //         const QPoint releasePoint = mView->viewportToColumns( mouseEvent->pos() );
0161 
0162         // this is not the release of a doubleclick so we need to process it?
0163         if (!mInLMBDoubleClick) {
0164 //             const int line = mView->lineAt( releasePoint.y() );
0165 //             const int pos = mActiveColumn->linePositionOfX( releasePoint.x() ); // TODO: can we be sure here about the active column?
0166 //             const Address index = tableLayout->indexAtCCoord( Coord(pos,line) ); // TODO: can this be another index than the one of the cursor???
0167 //             Q_EMIT mView->clicked( index ); // TODO: who needs this?
0168         }
0169 
0170         if (mLMBPressed) {
0171             mLMBPressed = false;
0172 
0173             if (mScrollTimer->isActive()) {
0174                 mScrollTimer->stop();
0175             }
0176 
0177             // was only click inside selection, nothing dragged?
0178             if (mDragStartPossible) {
0179                 mView->selectAll(false);
0180                 mDragStartTimer->stop();
0181                 mDragStartPossible = false;
0182 
0183                 mView->placeCursor(mDragStartPoint);
0184                 mView->ensureCursorVisible();
0185 
0186                 mView->unpauseCursor();
0187             }
0188             // was end of selection operation?
0189             else if (tableRanges->hasSelection()) {
0190                 if (QApplication::clipboard()->supportsSelection()) {
0191                     mView->copyToClipboard(QClipboard::Selection);
0192                 }
0193             }
0194         }
0195 
0196         Q_EMIT mView->cursorPositionChanged(mView->cursorPosition());
0197 
0198         mInLMBDoubleClick = false;
0199 
0200         if (tableRanges->selectionJustStarted()) {
0201             tableRanges->removeSelection();
0202         }
0203 
0204         mView->emitSelectionSignals();
0205         eventUsed = true;
0206     }
0207 
0208     return eventUsed ? true : AbstractMouseController::handleMouseReleaseEvent(mouseEvent);
0209 }
0210 
0211 bool MouseNavigator::handleMouseDoubleClickEvent(QMouseEvent* mouseEvent)
0212 {
0213     bool eventUsed = false;
0214 
0215     if (mouseEvent->button() == Qt::LeftButton) {
0216         ByteArrayTableCursor* tableCursor = mView->tableCursor();
0217 
0218         mDoubleClickLine = tableCursor->line();
0219 
0220         const Address index = tableCursor->validIndex();
0221 
0222         if (mView->activeCoding() == AbstractByteArrayView::CharCodingId) {
0223             mView->selectWord(index);
0224 
0225             // as we already have a doubleclick maybe it is a tripple click
0226             mTrippleClickTimer->start(qApp->doubleClickInterval());
0227             mDoubleClickPoint = mouseEvent->globalPos();
0228         }
0229         //  else
0230         //    mValueEditor->goInsideByte(); TODO: make this possible again
0231 
0232         mInLMBDoubleClick = true; //
0233         mLMBPressed = true;
0234 
0235         Q_EMIT mView->doubleClicked(index);
0236         eventUsed = true;
0237     }
0238 
0239     return eventUsed ? true : AbstractMouseController::handleMouseDoubleClickEvent(mouseEvent);
0240 }
0241 
0242 void MouseNavigator::autoScrollTimerDone()
0243 {
0244     if (mLMBPressed) {
0245         handleMouseMove(mView->viewportToColumns(mView->viewport()->mapFromGlobal(QCursor::pos())));
0246     }
0247 }
0248 
0249 void MouseNavigator::handleMouseMove(QPoint point)   // handles the move of the mouse with pressed buttons
0250 {
0251     ByteArrayTableCursor* tableCursor = mView->tableCursor();
0252     ByteArrayTableRanges* tableRanges = mView->tableRanges();
0253 
0254     const int yOffset = mView->yOffset();
0255     const int behindLastYOffset = yOffset + mView->visibleHeight();
0256     // scrolltimer but inside of viewport?
0257     if (mScrollTimer->isActive()) {
0258         if (yOffset <= point.y() && point.y() < behindLastYOffset) {
0259             mScrollTimer->stop();
0260         }
0261     }
0262     // no scrolltimer and outside of viewport?
0263     else {
0264         if (point.y() < yOffset || behindLastYOffset <= point.y()) {
0265             mScrollTimer->start(DefaultScrollTimerPeriod);
0266         }
0267     }
0268     mView->pauseCursor();
0269 
0270     mView->placeCursor(point);
0271     mView->ensureCursorVisible();
0272 
0273     // do wordwise selection?
0274     if (mInLMBDoubleClick && tableRanges->hasFirstWordSelection()) {
0275         Address newIndex = tableCursor->realIndex();
0276         const AddressRange firstWordSelection = tableRanges->firstWordSelection();
0277         const TextByteArrayAnalyzer textAnalyzer(mView->byteArrayModel(), mView->charCodec());
0278         // are we before the selection?
0279         if (firstWordSelection.startsBehind(newIndex)) {
0280             tableRanges->ensureWordSelectionForward(false);
0281             newIndex = textAnalyzer.indexOfLeftWordSelect(newIndex);
0282         }
0283         // or behind?
0284         else if (firstWordSelection.endsBefore(newIndex)) {
0285             tableRanges->ensureWordSelectionForward(true);
0286             newIndex = textAnalyzer.indexOfRightWordSelect(newIndex);
0287         }
0288         // or inside?
0289         else {
0290             tableRanges->ensureWordSelectionForward(true);
0291             newIndex = firstWordSelection.nextBehindEnd();
0292         }
0293 
0294         tableCursor->gotoIndex(newIndex);
0295     }
0296 
0297     if (tableRanges->selectionStarted()) {
0298         tableRanges->setSelectionEnd(mView->cursorPosition());
0299     }
0300 
0301     mView->updateChanged();
0302     mView->unpauseCursor();
0303     mView->emitSelectionSignals();
0304 }
0305 
0306 void MouseNavigator::startDrag()
0307 {
0308     // reset states
0309     mLMBPressed = false;
0310     mInLMBDoubleClick = false;
0311     mDragStartPossible = false;
0312 
0313     // create data
0314     QMimeData* dragData = mView->selectionAsMimeData();
0315     if (!dragData) {
0316         return;
0317     }
0318 
0319     auto* drag = new QDrag(mView);
0320     drag->setMimeData(dragData);
0321 
0322     Qt::DropActions request = (mView->isReadOnly() || mView->isOverwriteMode()) ? Qt::CopyAction : Qt::CopyAction | Qt::MoveAction;
0323     Qt::DropAction dropAction = drag->exec(request);
0324 
0325     if (dropAction == Qt::MoveAction) {
0326         auto* targetByteArrayView = qobject_cast<AbstractByteArrayView*>(drag->target());
0327         // Not inside this widget itself?
0328         if (!targetByteArrayView
0329             || targetByteArrayView->byteArrayModel() != mView->byteArrayModel()) {
0330             mView->removeSelectedData();
0331         }
0332     }
0333 }
0334 
0335 }
0336 
0337 #include "moc_mousenavigator.cpp"