File indexing completed on 2025-01-05 05:23:27

0001 /*
0002     This file is part of the Okteta Kasten module, made within the KDE community.
0003 
0004     SPDX-FileCopyrightText: 2007-2009, 2012 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 "bookmarkscontroller.hpp"
0010 
0011 // controller
0012 #include "bookmarkeditpopup.hpp"
0013 // Okteta Kasten gui
0014 #include <Kasten/Okteta/ByteArrayView>
0015 // Okteta Kasten core
0016 #include <Kasten/Okteta/ByteArrayDocument>
0017 // Kasten core
0018 #include <Kasten/AbstractModel>
0019 // Okteta gui
0020 #include <Okteta/OffsetFormat>
0021 // Okteta core
0022 #include <Okteta/TextByteArrayAnalyzer>
0023 #include <Okteta/CharCodec>
0024 #include <Okteta/Bookmarkable>
0025 #include <Okteta/BookmarksConstIterator>
0026 #include <Okteta/Bookmark>
0027 #include <Okteta/AbstractByteArrayModel>
0028 // KF
0029 #include <KXMLGUIClient>
0030 #include <KLocalizedString>
0031 #include <KActionCollection>
0032 #include <KStandardAction>
0033 // Qt
0034 #include <QActionGroup>
0035 #include <QAction>
0036 
0037 namespace Kasten {
0038 
0039 static constexpr char BookmarkListActionListId[] = "bookmark_list";
0040 
0041 // TODO: Sortieren nach Offset oder Zeit
0042 
0043 BookmarksController::BookmarksController(KXMLGUIClient* guiClient)
0044     : mGuiClient(guiClient)
0045 {
0046     KActionCollection* actionCollection = mGuiClient->actionCollection();
0047 
0048     mCreateAction = KStandardAction::addBookmark(this, &BookmarksController::createBookmark, this);
0049 
0050     mDeleteAction = new QAction(QIcon::fromTheme(QStringLiteral("bookmark-remove")),
0051                                 i18nc("@action:inmenu", "Remove Bookmark"), this);
0052     mDeleteAction->setObjectName(QStringLiteral("bookmark_remove"));
0053     connect(mDeleteAction, &QAction::triggered, this, &BookmarksController::deleteBookmark);
0054     actionCollection->setDefaultShortcut(mDeleteAction, QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_B));
0055 
0056     mDeleteAllAction = new QAction(QIcon::fromTheme(QStringLiteral("bookmark-remove")),
0057                                    i18nc("@action:inmenu", "Remove All Bookmarks"), this);
0058     mDeleteAllAction->setObjectName(QStringLiteral("bookmark_remove_all"));
0059     connect(mDeleteAllAction, &QAction::triggered, this, &BookmarksController::deleteAllBookmarks);
0060 //     actionCollection->setDefaultShortcut(mDeleteAllAction, QKeySequence(Qt::CTRL | Qt::Key_G));
0061 
0062     mGotoNextBookmarkAction = new QAction(QIcon::fromTheme(QStringLiteral("go-next")),
0063                                           i18nc("@action:inmenu", "Go to Next Bookmark"), this);
0064     mGotoNextBookmarkAction->setObjectName(QStringLiteral("bookmark_next"));
0065     connect(mGotoNextBookmarkAction, &QAction::triggered, this, &BookmarksController::gotoNextBookmark);
0066     actionCollection->setDefaultShortcut(mGotoNextBookmarkAction, QKeySequence(Qt::ALT | Qt::Key_Down));
0067 
0068     mGotoPreviousBookmarkAction = new QAction(QIcon::fromTheme(QStringLiteral("go-previous")),
0069                                               i18nc("@action:inmenu", "Go to Previous Bookmark"), this);
0070     mGotoPreviousBookmarkAction->setObjectName(QStringLiteral("bookmark_previous"));
0071     connect(mGotoPreviousBookmarkAction, &QAction::triggered, this, &BookmarksController::gotoPreviousBookmark);
0072     actionCollection->setDefaultShortcut(mGotoPreviousBookmarkAction, QKeySequence(Qt::ALT | Qt::Key_Up));
0073 
0074     mBookmarksActionGroup = new QActionGroup(this);   // TODO: do we use this only for the signal mapping?
0075 //     mBookmarksActionGroup->setExclusive( true );
0076     connect(mBookmarksActionGroup, &QActionGroup::triggered,
0077             this, &BookmarksController::onBookmarkTriggered);
0078 
0079     actionCollection->addActions({
0080         mCreateAction,
0081         mDeleteAction,
0082         mDeleteAllAction,
0083         mGotoNextBookmarkAction,
0084         mGotoPreviousBookmarkAction
0085     });
0086 
0087     setTargetModel(nullptr);
0088 }
0089 
0090 BookmarksController::~BookmarksController() = default;
0091 
0092 void BookmarksController::setTargetModel(AbstractModel* model)
0093 {
0094     if (mByteArrayView) {
0095         mByteArrayView->disconnect(this);
0096     }
0097     if (mByteArray) {
0098         mByteArray->disconnect(this);
0099     }
0100 
0101     mByteArrayView = model ? model->findBaseModel<ByteArrayView*>() : nullptr;
0102 
0103     ByteArrayDocument* document =
0104         mByteArrayView ? qobject_cast<ByteArrayDocument*>(mByteArrayView->baseModel()) : nullptr;
0105     mByteArray = document ? document->content() : nullptr;
0106     mBookmarks = (mByteArray && mByteArrayView) ? qobject_cast<Okteta::Bookmarkable*>(mByteArray) : nullptr;
0107 
0108     const bool hasViewWithBookmarks = (mBookmarks != nullptr);
0109     int bookmarksCount = 0;
0110     if (hasViewWithBookmarks) {
0111         bookmarksCount = mBookmarks->bookmarksCount();
0112         connect(mByteArray, SIGNAL(bookmarksAdded(QVector<Okteta::Bookmark>)),
0113                 SLOT(onBookmarksAdded(QVector<Okteta::Bookmark>)));
0114         connect(mByteArray, SIGNAL(bookmarksRemoved(QVector<Okteta::Bookmark>)),
0115                 SLOT(onBookmarksRemoved(QVector<Okteta::Bookmark>)));
0116         connect(mByteArray, SIGNAL(bookmarksModified(QVector<int>)),
0117                 SLOT(updateBookmarks()));
0118         connect(mByteArrayView, &ByteArrayView::cursorPositionChanged,
0119                 this, &BookmarksController::onCursorPositionChanged);
0120         connect(mByteArrayView, &ByteArrayView::offsetCodingChanged,
0121                 this, &BookmarksController::updateBookmarks);
0122     }
0123 
0124     updateBookmarks();
0125 
0126     const bool hasBookmarks = hasViewWithBookmarks && (bookmarksCount != 0);
0127     if (hasViewWithBookmarks) {
0128         onCursorPositionChanged(mByteArrayView->cursorPosition());
0129     } else {
0130         mCreateAction->setEnabled(false);
0131         mDeleteAction->setEnabled(false);
0132         mGotoNextBookmarkAction->setEnabled(false);
0133         mGotoPreviousBookmarkAction->setEnabled(false);
0134     }
0135     mDeleteAllAction->setEnabled(hasBookmarks);
0136 }
0137 
0138 void BookmarksController::updateBookmarks()
0139 {
0140     mGuiClient->unplugActionList(QLatin1String(BookmarkListActionListId));
0141 
0142     qDeleteAll(mBookmarksActionGroup->actions());
0143 
0144     if (!mBookmarks) {
0145         return;
0146     }
0147 
0148     const int startOffset = mByteArrayView->startOffset();
0149     Okteta::OffsetFormat::print printFunction =
0150         Okteta::OffsetFormat::printFunction((Okteta::OffsetFormat::Format)mByteArrayView->offsetCoding());
0151 
0152     char codedOffset[Okteta::OffsetFormat::MaxFormatWidth + 1];
0153 
0154     constexpr int firstWithNumericShortCut = 1;
0155     constexpr int lastWithNumericShortCut = 9;
0156     int b = firstWithNumericShortCut;
0157 
0158     Okteta::BookmarksConstIterator bit = mBookmarks->createBookmarksConstIterator();
0159     while (bit.hasNext()) {
0160         const Okteta::Bookmark& bookmark = bit.next();
0161         printFunction(codedOffset, startOffset + bookmark.offset());
0162         QString title = i18nc("@item description of bookmark", "%1: %2", QString::fromUtf8(codedOffset), bookmark.name());
0163         if (b <= lastWithNumericShortCut) {
0164             title = QStringLiteral("&%1 %2").arg(b).arg(title);
0165             // = KStringHandler::rsqueeze( view->title(), MaxEntryLength );
0166             ++b;
0167         }
0168         auto* action = new QAction(title, mBookmarksActionGroup);
0169 
0170         action->setData(bookmark.offset());
0171         mBookmarksActionGroup->addAction(action);
0172     }
0173     mGuiClient->plugActionList(QString::fromUtf8(BookmarkListActionListId),
0174                                mBookmarksActionGroup->actions());
0175 }
0176 
0177 void BookmarksController::onBookmarksAdded(const QVector<Okteta::Bookmark>& bookmarks)
0178 {
0179     Q_UNUSED(bookmarks)
0180     const int currentPosition = mByteArrayView->cursorPosition();
0181     onCursorPositionChanged(currentPosition);
0182 
0183     const int bookmarksCount = mBookmarks->bookmarksCount();
0184     const bool hasBookmarks = (bookmarksCount != 0);
0185 
0186     mDeleteAllAction->setEnabled(hasBookmarks);
0187 
0188     updateBookmarks();
0189 }
0190 
0191 void BookmarksController::onBookmarksRemoved(const QVector<Okteta::Bookmark>& bookmarks)
0192 {
0193     Q_UNUSED(bookmarks)
0194     const int currentPosition = mByteArrayView->cursorPosition();
0195     onCursorPositionChanged(currentPosition);
0196 
0197     const int bookmarksCount = mBookmarks->bookmarksCount();
0198     const bool hasBookmarks = (bookmarksCount != 0);
0199 
0200     mDeleteAllAction->setEnabled(hasBookmarks);
0201 
0202     updateBookmarks();
0203 }
0204 
0205 void BookmarksController::onCursorPositionChanged(Okteta::Address newPosition)
0206 {
0207     const int bookmarksCount = mBookmarks->bookmarksCount();
0208     const bool hasBookmarks = (bookmarksCount != 0);
0209     const bool isInsideByteArray = (newPosition < mByteArray->size());
0210     bool isAtBookmark;
0211     bool hasPrevious;
0212     bool hasNext;
0213     if (hasBookmarks) {
0214         isAtBookmark = mBookmarks->containsBookmarkFor(newPosition);
0215         Okteta::BookmarksConstIterator bookmarksIterator = mBookmarks->createBookmarksConstIterator();
0216         hasPrevious = bookmarksIterator.findPreviousFrom(newPosition - 1);
0217         hasNext = bookmarksIterator.findNextFrom(newPosition + 1);
0218     } else {
0219         isAtBookmark = false;
0220         hasPrevious = false;
0221         hasNext = false;
0222     }
0223 
0224     mCreateAction->setEnabled(!isAtBookmark && isInsideByteArray);
0225     mDeleteAction->setEnabled(isAtBookmark);
0226     mGotoNextBookmarkAction->setEnabled(hasNext);
0227     mGotoPreviousBookmarkAction->setEnabled(hasPrevious);
0228 }
0229 
0230 void BookmarksController::createBookmark()
0231 {
0232     const int cursorPosition = mByteArrayView->cursorPosition();
0233 
0234     // search for text at cursor
0235     const Okteta::CharCodec* charCodec = Okteta::CharCodec::createCodec(mByteArrayView->charCodingName());
0236     const Okteta::TextByteArrayAnalyzer textAnalyzer(mByteArray, charCodec);
0237     QString bookmarkName = textAnalyzer.text(cursorPosition, cursorPosition + MaxBookmarkNameSize - 1);
0238     delete charCodec;
0239 
0240     if (bookmarkName.isEmpty()) {
0241         bookmarkName = i18nc("default name of a bookmark", "Bookmark");  // %1").arg( 0 ) ); // TODO: use counter like with new file, globally
0242 
0243     }
0244     auto* bookmarkEditPopup = new BookmarkEditPopup(mByteArrayView->widget());
0245     QPoint popupPoint = mByteArrayView->cursorRect().topLeft();
0246 //     popupPoint.ry() += mSlider->height() / 2;
0247     popupPoint = mByteArrayView->widget()->mapToGlobal(popupPoint);
0248 
0249     bookmarkEditPopup->setPosition(popupPoint);
0250     bookmarkEditPopup->setName(bookmarkName);
0251     bookmarkEditPopup->setCursorPosition(cursorPosition);
0252     connect(bookmarkEditPopup, &BookmarkEditPopup::bookmarkAccepted,
0253             this, &BookmarksController::addBookmark);
0254     bookmarkEditPopup->open();
0255 }
0256 
0257 void BookmarksController::addBookmark(int cursorPosition, const QString& name)
0258 {
0259     Okteta::Bookmark bookmark(cursorPosition);
0260     bookmark.setName(name);
0261 
0262     const QVector<Okteta::Bookmark> bookmarks { bookmark };
0263     mBookmarks->addBookmarks(bookmarks);
0264 }
0265 
0266 void BookmarksController::deleteBookmark()
0267 {
0268     const int cursorPosition = mByteArrayView->cursorPosition();
0269     const QVector<Okteta::Bookmark> bookmarks { cursorPosition };
0270     mBookmarks->removeBookmarks(bookmarks);
0271 }
0272 
0273 void BookmarksController::deleteAllBookmarks()
0274 {
0275     mBookmarks->removeAllBookmarks();
0276 }
0277 
0278 void BookmarksController::gotoNextBookmark()
0279 {
0280     const int positionAfter = mByteArrayView->cursorPosition() + 1;
0281 
0282     Okteta::BookmarksConstIterator bookmarksIterator = mBookmarks->createBookmarksConstIterator();
0283     const bool hasNext = bookmarksIterator.findNextFrom(positionAfter);
0284     if (hasNext) {
0285         const int newPosition = bookmarksIterator.next().offset();
0286         mByteArrayView->setCursorPosition(newPosition);
0287     }
0288 }
0289 
0290 void BookmarksController::gotoPreviousBookmark()
0291 {
0292     const int positionBefore = mByteArrayView->cursorPosition() - 1;
0293 
0294     Okteta::BookmarksConstIterator bookmarksIterator = mBookmarks->createBookmarksConstIterator();
0295     const bool hasPrevious = bookmarksIterator.findPreviousFrom(positionBefore);
0296     if (hasPrevious) {
0297         const int newPosition = bookmarksIterator.previous().offset();
0298         mByteArrayView->setCursorPosition(newPosition);
0299     }
0300 }
0301 
0302 void BookmarksController::onBookmarkTriggered(QAction* action)
0303 {
0304     const int newPosition = action->data().toInt();
0305     mByteArrayView->setCursorPosition(newPosition);
0306 }
0307 
0308 }
0309 
0310 #include "moc_bookmarkscontroller.cpp"