history.cpp 9.97 KB
Newer Older
1
/*
2 3 4 5 6
    SPDX-FileCopyrightText: 2002 Frerich Raabe <raabe@kde.org>

    SPDX-License-Identifier: GPL-2.0-or-later
*/

7 8
#include "history.h"
#include "view.h"
9
#include "khc_debug.h"
David Stephen Hubner's avatar
David Stephen Hubner committed
10

Laurent Montel's avatar
Laurent Montel committed
11
#include <QAction>
Yuri Chornoivan's avatar
Yuri Chornoivan committed
12
#include <QApplication>
Laurent Montel's avatar
Laurent Montel committed
13
#include <QIcon>
Yuri Chornoivan's avatar
Yuri Chornoivan committed
14
#include <QMenu>
Pino Toscano's avatar
Pino Toscano committed
15 16
#include <QTimer>

Yuri Chornoivan's avatar
Yuri Chornoivan committed
17
#include <KActionCollection>
David Stephen Hubner's avatar
David Stephen Hubner committed
18 19 20
#include <KStandardGuiItem>
#include <KStringHandler>
#include <KToolBarPopupAction>
Yuri Chornoivan's avatar
Yuri Chornoivan committed
21 22
#include <KXMLGUIFactory>
#include <KXmlGuiWindow>
23 24 25

using namespace KHC;

David Stephen Hubner's avatar
David Stephen Hubner committed
26
// TODO: Needs complete redo!
27
// TODO: oh yeah
David Stephen Hubner's avatar
David Stephen Hubner committed
28

Laurent Montel's avatar
Laurent Montel committed
29
History *History::m_instance = nullptr;
30 31 32

History &History::self()
{
Cornelius Schumacher's avatar
Cornelius Schumacher committed
33 34 35
  if  ( !m_instance )
    m_instance = new History;
  return *m_instance;
36 37 38
}

History::History() : QObject(),
Cornelius Schumacher's avatar
Cornelius Schumacher committed
39
  m_goBuffer( 0 )
40
{
41
  m_entries_current = m_entries.end();
42 43 44 45
}

History::~History()
{
46
  qDeleteAll(m_entries);
47 48 49 50
}

void History::setupActions( KActionCollection *coll )
{
Aaron J. Seigo's avatar
build  
Aaron J. Seigo committed
51
  QPair<KGuiItem, KGuiItem> backForward = KStandardGuiItem::backAndForward();
Cornelius Schumacher's avatar
Cornelius Schumacher committed
52

Laurent Montel's avatar
Laurent Montel committed
53
  m_backAction = new KToolBarPopupAction( QIcon::fromTheme( backForward.first.iconName() ), backForward.first.text(), this );
54
  coll->addAction( QStringLiteral("back"), m_backAction );
Laurent Montel's avatar
Laurent Montel committed
55
  coll->setDefaultShortcuts(m_backAction, KStandardShortcut::back());
56
  
Laurent Montel's avatar
Laurent Montel committed
57
  connect(m_backAction, &KToolBarPopupAction::triggered, this, &History::back);
Tobias Koenig's avatar
Tobias Koenig committed
58

59
  connect( m_backAction->menu(), &QMenu::triggered, this, &History::backActivated );
60
  
61
  connect( m_backAction->menu(), &QMenu::aboutToShow, this, &History::fillBackMenu );
62
  
Cornelius Schumacher's avatar
Cornelius Schumacher committed
63 64
  m_backAction->setEnabled( false );

Laurent Montel's avatar
Laurent Montel committed
65
  m_forwardAction = new KToolBarPopupAction( QIcon::fromTheme( backForward.second.iconName() ), backForward.second.text(), this );
Laurent Montel's avatar
Laurent Montel committed
66
  coll->addAction( QStringLiteral("forward"), m_forwardAction );
Laurent Montel's avatar
Laurent Montel committed
67
  coll->setDefaultShortcuts(m_forwardAction, KStandardShortcut::forward());
68
  
Laurent Montel's avatar
Laurent Montel committed
69
  connect(m_forwardAction, &KToolBarPopupAction::triggered, this, &History::forward);
Tobias Koenig's avatar
Tobias Koenig committed
70

71
  connect( m_forwardAction->menu(), &QMenu::triggered, this, &History::forwardActivated );
72
  
73
  connect( m_forwardAction->menu(), &QMenu::aboutToShow, this, &History::fillForwardMenu );
74
  
Waldo Bastian's avatar
Waldo Bastian committed
75
  m_forwardAction->setEnabled( false );
76
}
77

78
void History::installMenuBarHook( KXmlGuiWindow *mainWindow )
79
{
80
  QMenu *goMenu = dynamic_cast<QMenu *>(
Laurent Montel's avatar
Laurent Montel committed
81
      mainWindow->guiFactory()->container( QStringLiteral("go_web"), mainWindow ) );
82 83
  if ( goMenu ) 
  {
Laurent Montel's avatar
Laurent Montel committed
84
    connect(goMenu, &QMenu::aboutToShow, this, &History::fillGoMenu);
85
    
Laurent Montel's avatar
Laurent Montel committed
86
    connect(goMenu, &QMenu::triggered, this, &History::goMenuActivated);
87
    
Tobias Koenig's avatar
Tobias Koenig committed
88
    m_goMenuIndex = goMenu->actions().count();
Cornelius Schumacher's avatar
Cornelius Schumacher committed
89
  }
90 91 92 93
}

void History::createEntry()
{
94
  qCDebug(KHC_LOG) << "History::createEntry()";
95

Cornelius Schumacher's avatar
Cornelius Schumacher committed
96
  // First, remove any forward history
97
  if (m_entries_current!=m_entries.end())
Cornelius Schumacher's avatar
Cornelius Schumacher committed
98
  {
99
  
100
    m_entries.erase(m_entries.begin(),m_entries_current);
101
    
102 103 104 105
    // If current entry is empty reuse it.
    if ( !(*m_entries_current)->view ) { 
      return;
    }
Cornelius Schumacher's avatar
Cornelius Schumacher committed
106 107
  }
  // Append a new entry
108
  m_entries_current = m_entries.insert(m_entries_current, new Entry ); // made current
109 110 111 112
}

void History::updateCurrentEntry( View *view )
{
Cornelius Schumacher's avatar
Cornelius Schumacher committed
113 114
  if ( m_entries.isEmpty() )
    return;
115

Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
116
  QUrl url = view->url();
117

118
  Entry *current = *m_entries_current;
119

120
  QDataStream stream( &current->buffer, QIODevice::WriteOnly );
Cornelius Schumacher's avatar
Cornelius Schumacher committed
121
  view->browserExtension()->saveState( stream );
122

Cornelius Schumacher's avatar
Cornelius Schumacher committed
123
  current->view = view;
124

125
  if ( url.isEmpty() ) {
126
    qCDebug(KHC_LOG) << "History::updateCurrentEntry(): internal url";
127 128 129
    url = view->internalUrl();
  }

130
  qCDebug(KHC_LOG) << "History::updateCurrentEntry(): " << view->title()
131
            << " (URL: " << url.url() << ")";
132 133

  current->url = url;
Cornelius Schumacher's avatar
Cornelius Schumacher committed
134
  current->title = view->title();
135

136
  current->search = view->state() == View::Search;
137 138 139 140
}

void History::updateActions()
{
Cornelius Schumacher's avatar
Cornelius Schumacher committed
141 142
  m_backAction->setEnabled( canGoBack() );
  m_forwardAction->setEnabled( canGoForward() );
143 144 145 146
}

void History::back()
{
147
  qCDebug(KHC_LOG) << "History::back()";
Cornelius Schumacher's avatar
Cornelius Schumacher committed
148
  goHistoryActivated( -1 );
149 150
}

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 158 159
}

void History::forward()
{
160
  qCDebug(KHC_LOG) << "History::forward()";
Cornelius Schumacher's avatar
Cornelius Schumacher committed
161
  goHistoryActivated( 1 );
162 163
}

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 171 172
}

void History::goHistoryActivated( int steps )
{
173
  qCDebug(KHC_LOG) << "History::goHistoryActivated(): m_goBuffer = " << m_goBuffer;
Cornelius Schumacher's avatar
Cornelius Schumacher committed
174 175 176
  if ( m_goBuffer )
    return;
  m_goBuffer = steps;
Laurent Montel's avatar
Laurent Montel committed
177
  QTimer::singleShot( 0, this, &History::goHistoryDelayed );
178 179 180 181
}

void History::goHistoryDelayed()
{
182
  qCDebug(KHC_LOG) << "History::goHistoryDelayed(): m_goBuffer = " << m_goBuffer;
Cornelius Schumacher's avatar
Cornelius Schumacher committed
183 184 185 186 187
  if ( !m_goBuffer )
    return;
  int steps = m_goBuffer;
  m_goBuffer = 0;
  goHistory( steps );
188 189 190 191
}

void History::goHistory( int steps )
{
192
  qCDebug(KHC_LOG) << "History::goHistory(): " << steps;
193

194
  // If current entry is empty remove it.
195 196
  Entry *current = *m_entries_current;
  if ( current && !current->view ) m_entries_current = m_entries.erase(m_entries_current);
197

198 199 200
  EntryList::iterator newPos = m_entries_current - steps;
  
  current = *newPos;
201
  if ( !current ) {
202
    qCWarning(KHC_LOG) << "No History entry at position " << newPos - m_entries.begin();
203 204
    return;
  }
205

206
  if ( !current->view ) {
207
    qCWarning(KHC_LOG) << "Empty history entry." ;
208 209
    return;
  }
210 211
  
  m_entries_current = newPos;
212

Cornelius Schumacher's avatar
Cornelius Schumacher committed
213
  if ( current->search ) {
214
    qCDebug(KHC_LOG) << "History::goHistory(): search";
Cornelius Schumacher's avatar
Cornelius Schumacher committed
215 216 217
    current->view->lastSearch();
    return;
  }
218

Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
219
  if ( current->url.scheme() == QLatin1String("khelpcenter") ) {
220
    qCDebug(KHC_LOG) << "History::goHistory(): internal";
Laurent Montel's avatar
Laurent Montel committed
221
    Q_EMIT goInternalUrl( current->url );
222 223 224
    return;
  }

225

Laurent Montel's avatar
Laurent Montel committed
226
  Q_EMIT goUrl( current->url );
227

Cornelius Schumacher's avatar
Cornelius Schumacher committed
228 229
  Entry h( *current );
  h.buffer.detach();
230

231
  QDataStream stream( h.buffer );
232

233
  h.view->closeUrl();
Cornelius Schumacher's avatar
Cornelius Schumacher committed
234 235
  updateCurrentEntry( h.view );
  h.view->browserExtension()->restoreState( stream );
236
  
237

Cornelius Schumacher's avatar
Cornelius Schumacher committed
238
  updateActions();
239 240 241 242
}

void History::fillBackMenu()
{
Tobias Koenig's avatar
Tobias Koenig committed
243
  QMenu *menu = m_backAction->menu();
Cornelius Schumacher's avatar
Cornelius Schumacher committed
244 245
  menu->clear();
  fillHistoryPopup( menu, true, false, false );
246 247 248 249
}

void History::fillForwardMenu()
{
Tobias Koenig's avatar
Tobias Koenig committed
250
  QMenu *menu = m_forwardAction->menu();
Cornelius Schumacher's avatar
Cornelius Schumacher committed
251 252
  menu->clear();
  fillHistoryPopup( menu, false, true, false );
253 254 255 256
}

void History::fillGoMenu()
{
Pino Toscano's avatar
Pino Toscano committed
257
  KXmlGuiWindow *mainWindow = static_cast<KXmlGuiWindow *>( qApp->activeWindow() );
Laurent Montel's avatar
Laurent Montel committed
258
  QMenu *goMenu = dynamic_cast<QMenu *>( mainWindow->guiFactory()->container( QStringLiteral( "go" ), mainWindow ) );
Cornelius Schumacher's avatar
Cornelius Schumacher committed
259 260 261
  if ( !goMenu || m_goMenuIndex == -1 )
    return;

Tobias Koenig's avatar
Tobias Koenig committed
262
  for ( int i = goMenu->actions().count() - 1 ; i >= m_goMenuIndex; i-- )
Harald Sitter's avatar
Harald Sitter committed
263
    goMenu->removeAction( goMenu->actions()[i] );
Cornelius Schumacher's avatar
Cornelius Schumacher committed
264 265 266 267 268 269 270 271 272 273 274 275 276

  // TODO perhaps smarter algorithm (rename existing items, create new ones only if not enough) ?

  // Ok, we want to show 10 items in all, among which the current url...

  if ( m_entries.count() <= 9 )
  {
    // First case: limited history in both directions -> show it all
    m_goMenuHistoryStartPos = m_entries.count() - 1; // Start right from the end
  } else
    // Second case: big history, in one or both directions
  {
    // 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;
Cornelius Schumacher's avatar
Cornelius Schumacher committed
278 279

    // Forward not big enough ?
280
    if ( m_goMenuHistoryStartPos > (int) m_entries.count() - 4 )
Cornelius Schumacher's avatar
Cornelius Schumacher committed
281 282
      m_goMenuHistoryStartPos = m_entries.count() - 1;
  }
Christoph Feck's avatar
Christoph Feck committed
283
  Q_ASSERT( m_goMenuHistoryStartPos >= 0 && (int) m_goMenuHistoryStartPos < m_entries.count() );
284
  m_goMenuHistoryCurrentPos = m_entries_current - m_entries.begin(); // for slotActivated
Cornelius Schumacher's avatar
Cornelius Schumacher committed
285
  fillHistoryPopup( goMenu, false, false, true, m_goMenuHistoryStartPos );
286 287
}

288
void History::goMenuActivated( QAction* action )
289
{
Pino Toscano's avatar
Pino Toscano committed
290
  KXmlGuiWindow *mainWindow = static_cast<KXmlGuiWindow *>( qApp->activeWindow() );
Laurent Montel's avatar
Laurent Montel committed
291
  QMenu *goMenu = dynamic_cast<QMenu *>( mainWindow->guiFactory()->container( QStringLiteral( "go" ), mainWindow ) );
Cornelius Schumacher's avatar
Cornelius Schumacher committed
292 293 294 295
  if ( !goMenu )
    return;

  // 1 for first item in the list, etc.
296
  int index = goMenu->actions().indexOf(action) - m_goMenuIndex + 1;
Cornelius Schumacher's avatar
Cornelius Schumacher committed
297 298
  if ( index > 0 )
  {
299
    qCDebug(KHC_LOG) << "Item clicked has index " << index;
Cornelius Schumacher's avatar
Cornelius Schumacher committed
300 301
    // -1 for one step back, 0 for don't move, +1 for one step forward, etc.
    int steps = ( m_goMenuHistoryStartPos+1 ) - index - m_goMenuHistoryCurrentPos; // make a drawing to understand this :-)
302
    qCDebug(KHC_LOG) << "Emit activated with steps = " << steps;
Cornelius Schumacher's avatar
Cornelius Schumacher committed
303 304
    goHistory( steps );
  }
305 306
}

307
void History::fillHistoryPopup( QMenu *popup, bool onlyBack, bool onlyForward, bool checkCurrentItem, uint startPos )
308
{
Cornelius Schumacher's avatar
Cornelius Schumacher committed
309 310
  Q_ASSERT ( popup ); // kill me if this 0... :/

311 312
  Entry * current = *m_entries_current;
  QList<Entry*>::iterator it = m_entries.begin();
Cornelius Schumacher's avatar
Cornelius Schumacher committed
313 314
  if (onlyBack || onlyForward)
  {
315
    it = m_entries_current; // Jump to current item
316 317 318 319 320 321
    // And move off it
    if ( !onlyForward ) {
        if ( it != m_entries.end() ) ++it;
    } else {
        if ( it != m_entries.begin() ) --it;
    }
Cornelius Schumacher's avatar
Cornelius Schumacher committed
322 323 324 325
  } else if ( startPos )
    it += startPos; // Jump to specified start pos

  uint i = 0;
326
  while ( it != m_entries.end() )
Cornelius Schumacher's avatar
Cornelius Schumacher committed
327
  {
328
    QString text = (*it)->title;
Cornelius Schumacher's avatar
Cornelius Schumacher committed
329
    text = KStringHandler::csqueeze(text, 50); //CT: squeeze
330
    text.replace( QLatin1Char('&'), QStringLiteral("&&") );
331 332
    QAction *action = popup->addAction( text );
    action->setData( i );
333
    if ( checkCurrentItem && *it == current )
Cornelius Schumacher's avatar
Cornelius Schumacher committed
334
    {
335 336
      action->setChecked( true ); // no pixmap if checked
    }
Cornelius Schumacher's avatar
Cornelius Schumacher committed
337 338
    if ( ++i > 10 )
      break;
339 340 341 342 343 344 345 346 347
    if ( !onlyForward ) {
        ++it;
    } else {
        if ( it == m_entries.begin() ) {
            it = m_entries.end();
        } else {
            --it;
        }
    }
Cornelius Schumacher's avatar
Cornelius Schumacher committed
348
  }
349 350 351 352
}

bool History::canGoBack() const
{
353
  return m_entries.size()>1 && EntryList::const_iterator(m_entries_current) != (m_entries.begin()+(m_entries.size()-1));
354 355 356 357
}

bool History::canGoForward() const
{
358
  return EntryList::const_iterator(m_entries_current) != m_entries.constBegin() && m_entries.size() > 1;
359 360 361
}

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