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 | |
38 | using namespace KHC; |
39 | |
40 | // TODO: Needs complete redo! |
41 | // TODO: oh yeah |
42 | |
43 | History *History::m_instance = 0; |
44 | |
45 | History &History::self() |
46 | { |
47 | if ( !m_instance ) |
48 | m_instance = new History; |
49 | return *m_instance; |
50 | } |
51 | |
52 | History::History() : QObject(), |
53 | m_goBuffer( 0 ) |
54 | { |
55 | m_entries_current = m_entries.end(); |
56 | } |
57 | |
58 | History::~History() |
59 | { |
60 | qDeleteAll(m_entries); |
61 | } |
62 | |
63 | void 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 | |
96 | void History::( KXmlGuiWindow *mainWindow ) |
97 | { |
98 | QMenu * = 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 | |
111 | void 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 | |
130 | void 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( ¤t->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 | |
158 | void History::updateActions() |
159 | { |
160 | m_backAction->setEnabled( canGoBack() ); |
161 | m_forwardAction->setEnabled( canGoForward() ); |
162 | } |
163 | |
164 | void History::back() |
165 | { |
166 | kDebug( 1400 ) << "History::back()" ; |
167 | goHistoryActivated( -1 ); |
168 | } |
169 | |
170 | void History::backActivated( QAction *action ) |
171 | { |
172 | int id = action->data().toInt(); |
173 | kDebug( 1400 ) << "History::backActivated(): id = " << id; |
174 | goHistoryActivated( -( id + 1 ) ); |
175 | } |
176 | |
177 | void History::forward() |
178 | { |
179 | kDebug( 1400 ) << "History::forward()" ; |
180 | goHistoryActivated( 1 ); |
181 | } |
182 | |
183 | void History::forwardActivated( QAction *action ) |
184 | { |
185 | int id = action->data().toInt(); |
186 | kDebug( 1400 ) << "History::forwardActivated(): id = " << id; |
187 | goHistoryActivated( id + 1 ); |
188 | } |
189 | |
190 | void 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 | |
199 | void 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 | |
209 | void 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 | |
261 | void History::() |
262 | { |
263 | QMenu * = m_backAction->menu(); |
264 | menu->clear(); |
265 | fillHistoryPopup( menu, true, false, false ); |
266 | } |
267 | |
268 | void History::() |
269 | { |
270 | QMenu * = m_forwardAction->menu(); |
271 | menu->clear(); |
272 | fillHistoryPopup( menu, false, true, false ); |
273 | } |
274 | |
275 | void History::() |
276 | { |
277 | KXmlGuiWindow *mainWindow = static_cast<KXmlGuiWindow *>( kapp->activeWindow() ); |
278 | QMenu * = 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 | |
308 | void History::( QAction* action ) |
309 | { |
310 | KXmlGuiWindow *mainWindow = static_cast<KXmlGuiWindow *>( kapp->activeWindow() ); |
311 | QMenu * = 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 | |
327 | void History::( QMenu *, 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 | |
371 | bool 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 | |
376 | bool History::canGoForward() const |
377 | { |
378 | return EntryList::const_iterator(m_entries_current) != m_entries.constBegin() && m_entries.size() > 1; |
379 | } |
380 | |
381 | void 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 | |