1 /*
2 This file is part of the Okteta Kasten module, made within the KDE community.
3
4 SPDX-FileCopyrightText: 2007-2009, 2012 Friedrich W. H. Kossebau <kossebau@kde.org>
5
6 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
7 */
8
9 #include "bookmarkscontroller.hpp"
10
11 // controller
12 #include "bookmarkeditpopup.hpp"
13 // Okteta Kasten gui
14 #include <Kasten/Okteta/ByteArrayView>
15 // Okteta Kasten core
16 #include <Kasten/Okteta/ByteArrayDocument>
17 // Kasten core
18 #include <Kasten/AbstractModel>
19 // Okteta gui
20 #include <Okteta/OffsetFormat>
21 // Okteta core
22 #include <Okteta/TextByteArrayAnalyzer>
23 #include <Okteta/CharCodec>
24 #include <Okteta/Bookmarkable>
25 #include <Okteta/BookmarksConstIterator>
26 #include <Okteta/Bookmark>
27 #include <Okteta/AbstractByteArrayModel>
28 // KF
29 #include <KXMLGUIClient>
30 #include <KLocalizedString>
31 #include <KActionCollection>
32 #include <KStandardAction>
33 // Qt
34 #include <QAction>
35
36 namespace Kasten {
37
38 static constexpr char BookmarkListActionListId[] = "bookmark_list";
39
40 // TODO: Sortieren nach Offset oder Zeit
41
BookmarksController(KXMLGUIClient * guiClient)42 BookmarksController::BookmarksController(KXMLGUIClient* guiClient)
43 : mGuiClient(guiClient)
44 {
45 KActionCollection* actionCollection = mGuiClient->actionCollection();
46
47 mCreateAction = KStandardAction::addBookmark(this, &BookmarksController::createBookmark, this);
48
49 mDeleteAction = new QAction(QIcon::fromTheme(QStringLiteral("bookmark-remove")),
50 i18nc("@action:inmenu", "Remove Bookmark"), this);
51 mDeleteAction->setObjectName(QStringLiteral("bookmark_remove"));
52 connect(mDeleteAction, &QAction::triggered, this, &BookmarksController::deleteBookmark);
53 actionCollection->setDefaultShortcut(mDeleteAction, Qt::CTRL | Qt::SHIFT | Qt::Key_B);
54
55 mDeleteAllAction = new QAction(QIcon::fromTheme(QStringLiteral("bookmark-remove")),
56 i18nc("@action:inmenu", "Remove All Bookmarks"), this);
57 mDeleteAllAction->setObjectName(QStringLiteral("bookmark_remove_all"));
58 connect(mDeleteAllAction, &QAction::triggered, this, &BookmarksController::deleteAllBookmarks);
59 // actionCollection->setDefaultShortcut( mDeleteAllAction, Qt::CTRL | Qt::Key_G );
60
61 mGotoNextBookmarkAction = new QAction(QIcon::fromTheme(QStringLiteral("go-next")),
62 i18nc("@action:inmenu", "Go to Next Bookmark"), this);
63 mGotoNextBookmarkAction->setObjectName(QStringLiteral("bookmark_next"));
64 connect(mGotoNextBookmarkAction, &QAction::triggered, this, &BookmarksController::gotoNextBookmark);
65 actionCollection->setDefaultShortcut(mGotoNextBookmarkAction, Qt::ALT | Qt::Key_Down);
66
67 mGotoPreviousBookmarkAction = new QAction(QIcon::fromTheme(QStringLiteral("go-previous")),
68 i18nc("@action:inmenu", "Go to Previous Bookmark"), this);
69 mGotoPreviousBookmarkAction->setObjectName(QStringLiteral("bookmark_previous"));
70 connect(mGotoPreviousBookmarkAction, &QAction::triggered, this, &BookmarksController::gotoPreviousBookmark);
71 actionCollection->setDefaultShortcut(mGotoPreviousBookmarkAction, Qt::ALT | Qt::Key_Up);
72
73 mBookmarksActionGroup = new QActionGroup(this); // TODO: do we use this only for the signal mapping?
74 // mBookmarksActionGroup->setExclusive( true );
75 connect(mBookmarksActionGroup, &QActionGroup::triggered,
76 this, &BookmarksController::onBookmarkTriggered);
77
78 actionCollection->addActions({
79 mCreateAction,
80 mDeleteAction,
81 mDeleteAllAction,
82 mGotoNextBookmarkAction,
83 mGotoPreviousBookmarkAction
84 });
85
86 setTargetModel(nullptr);
87 }
88
89 BookmarksController::~BookmarksController() = default;
90
setTargetModel(AbstractModel * model)91 void BookmarksController::setTargetModel(AbstractModel* model)
92 {
93 if (mByteArrayView) {
94 mByteArrayView->disconnect(this);
95 }
96 if (mByteArray) {
97 mByteArray->disconnect(this);
98 }
99
100 mByteArrayView = model ? model->findBaseModel<ByteArrayView*>() : nullptr;
101
102 ByteArrayDocument* document =
103 mByteArrayView ? qobject_cast<ByteArrayDocument*>(mByteArrayView->baseModel()) : nullptr;
104 mByteArray = document ? document->content() : nullptr;
105 mBookmarks = (mByteArray && mByteArrayView) ? qobject_cast<Okteta::Bookmarkable*>(mByteArray) : nullptr;
106
107 const bool hasViewWithBookmarks = (mBookmarks != nullptr);
108 int bookmarksCount = 0;
109 if (hasViewWithBookmarks) {
110 bookmarksCount = mBookmarks->bookmarksCount();
111 connect(mByteArray, SIGNAL(bookmarksAdded(QVector<Okteta::Bookmark>)),
112 SLOT(onBookmarksAdded(QVector<Okteta::Bookmark>)));
113 connect(mByteArray, SIGNAL(bookmarksRemoved(QVector<Okteta::Bookmark>)),
114 SLOT(onBookmarksRemoved(QVector<Okteta::Bookmark>)));
115 connect(mByteArray, SIGNAL(bookmarksModified(QVector<int>)),
116 SLOT(updateBookmarks()));
117 connect(mByteArrayView, &ByteArrayView::cursorPositionChanged,
118 this, &BookmarksController::onCursorPositionChanged);
119 connect(mByteArrayView, &ByteArrayView::offsetCodingChanged,
120 this, &BookmarksController::updateBookmarks);
121 }
122
123 updateBookmarks();
124
125 const bool hasBookmarks = hasViewWithBookmarks && (bookmarksCount != 0);
126 if (hasViewWithBookmarks) {
127 onCursorPositionChanged(mByteArrayView->cursorPosition());
128 } else {
129 mCreateAction->setEnabled(false);
130 mDeleteAction->setEnabled(false);
131 }
132 mDeleteAllAction->setEnabled(hasBookmarks);
133 mGotoNextBookmarkAction->setEnabled(hasBookmarks);
134 mGotoPreviousBookmarkAction->setEnabled(hasBookmarks);
135 }
136
updateBookmarks()137 void BookmarksController::updateBookmarks()
138 {
139 mGuiClient->unplugActionList(QLatin1String(BookmarkListActionListId));
140
141 qDeleteAll(mBookmarksActionGroup->actions());
142
143 if (!mBookmarks) {
144 return;
145 }
146
147 const int startOffset = mByteArrayView->startOffset();
148 Okteta::OffsetFormat::print printFunction =
149 Okteta::OffsetFormat::printFunction((Okteta::OffsetFormat::Format)mByteArrayView->offsetCoding());
150
151 char codedOffset[Okteta::OffsetFormat::MaxFormatWidth + 1];
152
153 constexpr int firstWithNumericShortCut = 1;
154 constexpr int lastWithNumericShortCut = 9;
155 int b = firstWithNumericShortCut;
156
157 Okteta::BookmarksConstIterator bit = mBookmarks->createBookmarksConstIterator();
158 while (bit.hasNext()) {
159 const Okteta::Bookmark& bookmark = bit.next();
160 printFunction(codedOffset, startOffset + bookmark.offset());
161 QString title = i18nc("@item description of bookmark", "%1: %2", QString::fromUtf8(codedOffset), bookmark.name());
162 if (b <= lastWithNumericShortCut) {
163 title = QStringLiteral("&%1 %2").arg(b).arg(title);
164 // = KStringHandler::rsqueeze( view->title(), MaxEntryLength );
165 ++b;
166 }
167 auto* action = new QAction(title, mBookmarksActionGroup);
168
169 action->setData(bookmark.offset());
170 mBookmarksActionGroup->addAction(action);
171 }
172 mGuiClient->plugActionList(QString::fromUtf8(BookmarkListActionListId),
173 mBookmarksActionGroup->actions());
174 }
175
onBookmarksAdded(const QVector<Okteta::Bookmark> & bookmarks)176 void BookmarksController::onBookmarksAdded(const QVector<Okteta::Bookmark>& bookmarks)
177 {
178 Q_UNUSED(bookmarks)
179 const int currentPosition = mByteArrayView->cursorPosition();
180 onCursorPositionChanged(currentPosition);
181
182 const int bookmarksCount = mBookmarks->bookmarksCount();
183 const bool hasBookmarks = (bookmarksCount != 0);
184
185 mDeleteAllAction->setEnabled(hasBookmarks);
186
187 updateBookmarks();
188 }
189
onBookmarksRemoved(const QVector<Okteta::Bookmark> & bookmarks)190 void BookmarksController::onBookmarksRemoved(const QVector<Okteta::Bookmark>& bookmarks)
191 {
192 Q_UNUSED(bookmarks)
193 const int currentPosition = mByteArrayView->cursorPosition();
194 onCursorPositionChanged(currentPosition);
195
196 const int bookmarksCount = mBookmarks->bookmarksCount();
197 const bool hasBookmarks = (bookmarksCount != 0);
198
199 mDeleteAllAction->setEnabled(hasBookmarks);
200
201 updateBookmarks();
202 }
203
onCursorPositionChanged(Okteta::Address newPosition)204 void BookmarksController::onCursorPositionChanged(Okteta::Address newPosition)
205 {
206 const int bookmarksCount = mBookmarks->bookmarksCount();
207 const bool hasBookmarks = (bookmarksCount != 0);
208 const bool isInsideByteArray = (newPosition < mByteArray->size());
209 bool isAtBookmark;
210 bool hasPrevious;
211 bool hasNext;
212 if (hasBookmarks) {
213 isAtBookmark = mBookmarks->containsBookmarkFor(newPosition);
214 Okteta::BookmarksConstIterator bookmarksIterator = mBookmarks->createBookmarksConstIterator();
215 hasPrevious = bookmarksIterator.findPreviousFrom(newPosition);
216 hasNext = bookmarksIterator.findNextFrom(newPosition);
217 } else {
218 isAtBookmark = false;
219 hasPrevious = false;
220 hasNext = false;
221 }
222
223 mCreateAction->setEnabled(!isAtBookmark && isInsideByteArray);
224 mDeleteAction->setEnabled(isAtBookmark);
225 mGotoNextBookmarkAction->setEnabled(hasNext);
226 mGotoPreviousBookmarkAction->setEnabled(hasPrevious);
227 }
228
createBookmark()229 void BookmarksController::createBookmark()
230 {
231 const int cursorPosition = mByteArrayView->cursorPosition();
232
233 // search for text at cursor
234 const Okteta::CharCodec* charCodec = Okteta::CharCodec::createCodec(mByteArrayView->charCodingName());
235 const Okteta::TextByteArrayAnalyzer textAnalyzer(mByteArray, charCodec);
236 QString bookmarkName = textAnalyzer.text(cursorPosition, cursorPosition + MaxBookmarkNameSize - 1);
237 delete charCodec;
238
239 if (bookmarkName.isEmpty()) {
240 bookmarkName = i18nc("default name of a bookmark", "Bookmark"); // %1").arg( 0 ) ); // TODO: use counter like with new file, globally
241
242 }
243 auto* bookmarkEditPopup = new BookmarkEditPopup(mByteArrayView->widget());
244 QPoint popupPoint = mByteArrayView->cursorRect().topLeft();
245 // popupPoint.ry() += mSlider->height() / 2;
246 popupPoint = mByteArrayView->widget()->mapToGlobal(popupPoint);
247
248 bookmarkEditPopup->setPosition(popupPoint);
249 bookmarkEditPopup->setName(bookmarkName);
250 bookmarkEditPopup->setCursorPosition(cursorPosition);
251 connect(bookmarkEditPopup, &BookmarkEditPopup::bookmarkAccepted,
252 this, &BookmarksController::addBookmark);
253 bookmarkEditPopup->open();
254 }
255
addBookmark(int cursorPosition,const QString & name)256 void BookmarksController::addBookmark(int cursorPosition, const QString& name)
257 {
258 Okteta::Bookmark bookmark(cursorPosition);
259 bookmark.setName(name);
260
261 const QVector<Okteta::Bookmark> bookmarks { bookmark };
262 mBookmarks->addBookmarks(bookmarks);
263 }
264
deleteBookmark()265 void BookmarksController::deleteBookmark()
266 {
267 const int cursorPosition = mByteArrayView->cursorPosition();
268 const QVector<Okteta::Bookmark> bookmarks { cursorPosition };
269 mBookmarks->removeBookmarks(bookmarks);
270 }
271
deleteAllBookmarks()272 void BookmarksController::deleteAllBookmarks()
273 {
274 mBookmarks->removeAllBookmarks();
275 }
276
gotoNextBookmark()277 void BookmarksController::gotoNextBookmark()
278 {
279 const int currentPosition = mByteArrayView->cursorPosition();
280
281 Okteta::BookmarksConstIterator bookmarksIterator = mBookmarks->createBookmarksConstIterator();
282 const bool hasNext = bookmarksIterator.findNextFrom(currentPosition);
283 if (hasNext) {
284 const int newPosition = bookmarksIterator.next().offset();
285 mByteArrayView->setCursorPosition(newPosition);
286 }
287 }
288
gotoPreviousBookmark()289 void BookmarksController::gotoPreviousBookmark()
290 {
291 const int currentPosition = mByteArrayView->cursorPosition();
292
293 Okteta::BookmarksConstIterator bookmarksIterator = mBookmarks->createBookmarksConstIterator();
294 const bool hasPrevious = bookmarksIterator.findPreviousFrom(currentPosition);
295 if (hasPrevious) {
296 const int newPosition = bookmarksIterator.previous().offset();
297 mByteArrayView->setCursorPosition(newPosition);
298 }
299 }
300
onBookmarkTriggered(QAction * action)301 void BookmarksController::onBookmarkTriggered(QAction* action)
302 {
303 const int newPosition = action->data().toInt();
304 mByteArrayView->setCursorPosition(newPosition);
305 }
306
307 }
308