1 /*
2 SPDX-FileCopyrightText: 2002 Frerich Raabe <raabe@kde.org>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
7 #include "history.h"
8 #include "view.h"
9 #include "khc_debug.h"
10
11 #include <QAction>
12 #include <QApplication>
13 #include <QIcon>
14 #include <QMenu>
15 #include <QTimer>
16
17 #include <KActionCollection>
18 #include <KStandardGuiItem>
19 #include <KStringHandler>
20 #include <KToolBarPopupAction>
21 #include <KXMLGUIFactory>
22 #include <KXmlGuiWindow>
23
24 using namespace KHC;
25
26 // TODO: Needs complete redo!
27 // TODO: oh yeah
28
29 History *History::m_instance = nullptr;
30
self()31 History &History::self()
32 {
33 if ( !m_instance )
34 m_instance = new History;
35 return *m_instance;
36 }
37
History()38 History::History() : QObject(),
39 m_goBuffer( 0 )
40 {
41 m_entries_current = m_entries.end();
42 }
43
~History()44 History::~History()
45 {
46 qDeleteAll(m_entries);
47 }
48
setupActions(KActionCollection * coll)49 void History::setupActions( KActionCollection *coll )
50 {
51 QPair<KGuiItem, KGuiItem> backForward = KStandardGuiItem::backAndForward();
52
53 m_backAction = new KToolBarPopupAction( QIcon::fromTheme( backForward.first.iconName() ), backForward.first.text(), this );
54 coll->addAction( QStringLiteral("back"), m_backAction );
55 coll->setDefaultShortcuts(m_backAction, KStandardShortcut::back());
56
57 connect(m_backAction, &KToolBarPopupAction::triggered, this, &History::back);
58
59 connect( m_backAction->menu(), &QMenu::triggered, this, &History::backActivated );
60
61 connect( m_backAction->menu(), &QMenu::aboutToShow, this, &History::fillBackMenu );
62
63 m_backAction->setEnabled( false );
64
65 m_forwardAction = new KToolBarPopupAction( QIcon::fromTheme( backForward.second.iconName() ), backForward.second.text(), this );
66 coll->addAction( QStringLiteral("forward"), m_forwardAction );
67 coll->setDefaultShortcuts(m_forwardAction, KStandardShortcut::forward());
68
69 connect(m_forwardAction, &KToolBarPopupAction::triggered, this, &History::forward);
70
71 connect( m_forwardAction->menu(), &QMenu::triggered, this, &History::forwardActivated );
72
73 connect( m_forwardAction->menu(), &QMenu::aboutToShow, this, &History::fillForwardMenu );
74
75 m_forwardAction->setEnabled( false );
76 }
77
installMenuBarHook(KXmlGuiWindow * mainWindow)78 void History::installMenuBarHook( KXmlGuiWindow *mainWindow )
79 {
80 QMenu *goMenu = dynamic_cast<QMenu *>(
81 mainWindow->guiFactory()->container( QStringLiteral("go_web"), mainWindow ) );
82 if ( goMenu )
83 {
84 connect(goMenu, &QMenu::aboutToShow, this, &History::fillGoMenu);
85
86 connect(goMenu, &QMenu::triggered, this, &History::goMenuActivated);
87
88 m_goMenuIndex = goMenu->actions().count();
89 }
90 }
91
createEntry()92 void History::createEntry()
93 {
94 qCDebug(KHC_LOG) << "History::createEntry()";
95
96 // First, remove any forward history
97 if (m_entries_current!=m_entries.end())
98 {
99
100 m_entries.erase(m_entries.begin(),m_entries_current);
101
102 // If current entry is empty reuse it.
103 if ( !(*m_entries_current)->view ) {
104 return;
105 }
106 }
107 // Append a new entry
108 m_entries_current = m_entries.insert(m_entries_current, new Entry ); // made current
109 }
110
updateCurrentEntry(View * view)111 void History::updateCurrentEntry( View *view )
112 {
113 if ( m_entries.isEmpty() )
114 return;
115
116 QUrl url = view->url();
117
118 Entry *current = *m_entries_current;
119
120 QDataStream stream( ¤t->buffer, QIODevice::WriteOnly );
121 view->browserExtension()->saveState( stream );
122
123 current->view = view;
124
125 if ( url.isEmpty() ) {
126 qCDebug(KHC_LOG) << "History::updateCurrentEntry(): internal url";
127 url = view->internalUrl();
128 }
129
130 qCDebug(KHC_LOG) << "History::updateCurrentEntry(): " << view->title()
131 << " (URL: " << url.url() << ")";
132
133 current->url = url;
134 current->title = view->title();
135
136 current->search = view->state() == View::Search;
137 }
138
updateActions()139 void History::updateActions()
140 {
141 m_backAction->setEnabled( canGoBack() );
142 m_forwardAction->setEnabled( canGoForward() );
143 }
144
back()145 void History::back()
146 {
147 qCDebug(KHC_LOG) << "History::back()";
148 goHistoryActivated( -1 );
149 }
150
backActivated(QAction * action)151 void History::backActivated( QAction *action )
152 {
153 int id = action->data().toInt();
154 qCDebug(KHC_LOG) << "History::backActivated(): id = " << id;
155 goHistoryActivated( -( id + 1 ) );
156 }
157
forward()158 void History::forward()
159 {
160 qCDebug(KHC_LOG) << "History::forward()";
161 goHistoryActivated( 1 );
162 }
163
forwardActivated(QAction * action)164 void History::forwardActivated( QAction *action )
165 {
166 int id = action->data().toInt();
167 qCDebug(KHC_LOG) << "History::forwardActivated(): id = " << id;
168 goHistoryActivated( id + 1 );
169 }
170
goHistoryActivated(int steps)171 void History::goHistoryActivated( int steps )
172 {
173 qCDebug(KHC_LOG) << "History::goHistoryActivated(): m_goBuffer = " << m_goBuffer;
174 if ( m_goBuffer )
175 return;
176 m_goBuffer = steps;
177 QTimer::singleShot( 0, this, &History::goHistoryDelayed );
178 }
179
goHistoryDelayed()180 void History::goHistoryDelayed()
181 {
182 qCDebug(KHC_LOG) << "History::goHistoryDelayed(): m_goBuffer = " << m_goBuffer;
183 if ( !m_goBuffer )
184 return;
185 int steps = m_goBuffer;
186 m_goBuffer = 0;
187 goHistory( steps );
188 }
189
goHistory(int steps)190 void History::goHistory( int steps )
191 {
192 qCDebug(KHC_LOG) << "History::goHistory(): " << steps;
193
194 // If current entry is empty remove it.
195 Entry *current = *m_entries_current;
196 if ( current && !current->view ) m_entries_current = m_entries.erase(m_entries_current);
197
198 EntryList::iterator newPos = m_entries_current - steps;
199
200 current = *newPos;
201 if ( !current ) {
202 qCWarning(KHC_LOG) << "No History entry at position " << newPos - m_entries.begin();
203 return;
204 }
205
206 if ( !current->view ) {
207 qCWarning(KHC_LOG) << "Empty history entry." ;
208 return;
209 }
210
211 m_entries_current = newPos;
212
213 if ( current->search ) {
214 qCDebug(KHC_LOG) << "History::goHistory(): search";
215 current->view->lastSearch();
216 return;
217 }
218
219 if ( current->url.scheme() == QLatin1String("khelpcenter") ) {
220 qCDebug(KHC_LOG) << "History::goHistory(): internal";
221 Q_EMIT goInternalUrl( current->url );
222 return;
223 }
224
225
226 Q_EMIT goUrl( current->url );
227
228 Entry h( *current );
229 h.buffer.detach();
230
231 QDataStream stream( h.buffer );
232
233 h.view->closeUrl();
234 updateCurrentEntry( h.view );
235 h.view->browserExtension()->restoreState( stream );
236
237
238 updateActions();
239 }
240
fillBackMenu()241 void History::fillBackMenu()
242 {
243 QMenu *menu = m_backAction->menu();
244 menu->clear();
245 fillHistoryPopup( menu, true, false, false );
246 }
247
fillForwardMenu()248 void History::fillForwardMenu()
249 {
250 QMenu *menu = m_forwardAction->menu();
251 menu->clear();
252 fillHistoryPopup( menu, false, true, false );
253 }
254
fillGoMenu()255 void History::fillGoMenu()
256 {
257 KXmlGuiWindow *mainWindow = static_cast<KXmlGuiWindow *>( qApp->activeWindow() );
258 QMenu *goMenu = dynamic_cast<QMenu *>( mainWindow->guiFactory()->container( QStringLiteral( "go" ), mainWindow ) );
259 if ( !goMenu || m_goMenuIndex == -1 )
260 return;
261
262 for ( int i = goMenu->actions().count() - 1 ; i >= m_goMenuIndex; i-- )
263 goMenu->removeAction( goMenu->actions()[i] );
264
265 // TODO perhaps smarter algorithm (rename existing items, create new ones only if not enough) ?
266
267 // Ok, we want to show 10 items in all, among which the current url...
268
269 if ( m_entries.count() <= 9 )
270 {
271 // First case: limited history in both directions -> show it all
272 m_goMenuHistoryStartPos = m_entries.count() - 1; // Start right from the end
273 } else
274 // Second case: big history, in one or both directions
275 {
276 // Assume both directions first (in this case we place the current URL in the middle)
277 m_goMenuHistoryStartPos = (m_entries_current - m_entries.begin()) + 4;
278
279 // Forward not big enough ?
280 if ( m_goMenuHistoryStartPos > (int) m_entries.count() - 4 )
281 m_goMenuHistoryStartPos = m_entries.count() - 1;
282 }
283 Q_ASSERT( m_goMenuHistoryStartPos >= 0 && (int) m_goMenuHistoryStartPos < m_entries.count() );
284 m_goMenuHistoryCurrentPos = m_entries_current - m_entries.begin(); // for slotActivated
285 fillHistoryPopup( goMenu, false, false, true, m_goMenuHistoryStartPos );
286 }
287
goMenuActivated(QAction * action)288 void History::goMenuActivated( QAction* action )
289 {
290 KXmlGuiWindow *mainWindow = static_cast<KXmlGuiWindow *>( qApp->activeWindow() );
291 QMenu *goMenu = dynamic_cast<QMenu *>( mainWindow->guiFactory()->container( QStringLiteral( "go" ), mainWindow ) );
292 if ( !goMenu )
293 return;
294
295 // 1 for first item in the list, etc.
296 int index = goMenu->actions().indexOf(action) - m_goMenuIndex + 1;
297 if ( index > 0 )
298 {
299 qCDebug(KHC_LOG) << "Item clicked has index " << index;
300 // -1 for one step back, 0 for don't move, +1 for one step forward, etc.
301 int steps = ( m_goMenuHistoryStartPos+1 ) - index - m_goMenuHistoryCurrentPos; // make a drawing to understand this :-)
302 qCDebug(KHC_LOG) << "Emit activated with steps = " << steps;
303 goHistory( steps );
304 }
305 }
306
fillHistoryPopup(QMenu * popup,bool onlyBack,bool onlyForward,bool checkCurrentItem,uint startPos)307 void History::fillHistoryPopup( QMenu *popup, bool onlyBack, bool onlyForward, bool checkCurrentItem, uint startPos )
308 {
309 Q_ASSERT ( popup ); // kill me if this 0... :/
310
311 Entry * current = *m_entries_current;
312 QList<Entry*>::iterator it = m_entries.begin();
313 if (onlyBack || onlyForward)
314 {
315 it = m_entries_current; // Jump to current item
316 // And move off it
317 if ( !onlyForward ) {
318 if ( it != m_entries.end() ) ++it;
319 } else {
320 if ( it != m_entries.begin() ) --it;
321 }
322 } else if ( startPos )
323 it += startPos; // Jump to specified start pos
324
325 uint i = 0;
326 while ( it != m_entries.end() )
327 {
328 QString text = (*it)->title;
329 text = KStringHandler::csqueeze(text, 50); //CT: squeeze
330 text.replace( QLatin1Char('&'), QStringLiteral("&&") );
331 QAction *action = popup->addAction( text );
332 action->setData( i );
333 if ( checkCurrentItem && *it == current )
334 {
335 action->setChecked( true ); // no pixmap if checked
336 }
337 if ( ++i > 10 )
338 break;
339 if ( !onlyForward ) {
340 ++it;
341 } else {
342 if ( it == m_entries.begin() ) {
343 it = m_entries.end();
344 } else {
345 --it;
346 }
347 }
348 }
349 }
350
canGoBack() const351 bool History::canGoBack() const
352 {
353 return m_entries.size()>1 && EntryList::const_iterator(m_entries_current) != (m_entries.begin()+(m_entries.size()-1));
354 }
355
canGoForward() const356 bool History::canGoForward() const
357 {
358 return EntryList::const_iterator(m_entries_current) != m_entries.constBegin() && m_entries.size() > 1;
359 }
360
dumpHistory() const361 void History::dumpHistory() const {
362 for(EntryList::const_iterator it = m_entries.constBegin() ; it!=m_entries.constEnd() ; ++it) {
363 qCDebug(KHC_LOG) << (*it)->title << (*it)->url << (it==EntryList::const_iterator(m_entries_current) ? "current" : "" ) ;
364 }
365
366 }
367
368 // vim:ts=2:sw=2:et
369