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