/****************************************************************************
** Dooble - The Secure Internet Web Browser
**
** Copyright (c) 2008, 2009, 2010, 2011, 2012 Alexis Megas,
** Gunther van Dooble, and the Dooble Team.
** All rights reserved.
**
** License: GPL2 only:
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; version 2 of the License only.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
** or see here: http://www.gnu.org/licenses/gpl.html
**
** For the WebKit library, please see: http://webkit.org.
**
** THE CODE IS PROVIDED BY THE AUTHORS ''AS IS'' AND ANY
** EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
** PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
** ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
** GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
** IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
** ARISING IN ANY WAY OUT OF THE USE OF THIS APPLICATION, EVEN IF ADVISED
** OF THE POSSIBILITY OF SUCH DAMAGE.
**
** Please report all praise, requests, bugs, and problems to the project
** team and administrators: http://sf.net/projects/dooble.
**
** You can find us listed at our project page. New team members are welcome.
** The name of the authors should not be used to endorse or promote products
** derived from Dooble without specific prior written permission.
** If you use this code for other projects, please let us know.
**
** Web sites:
**   http://sf.net/projects/dooble
**   http://dooble.sf.net
****************************************************************************/

#include <QDir>
#include <QUrl>
#include <QIcon>
#include <QMenu>
#include <QTimer>
#include <QBuffer>
#include <QKeyEvent>
#include <QSettings>
#include <QSqlQuery>
#include <QClipboard>
#include <QScrollBar>
#include <QSqlRecord>
#include <QProgressBar>

#include "dmisc.h"
#include "dooble.h"
#include "dhistory.h"

/*
** m_contentsList is used primarily for re-encoding.
** One cannot rely on the model since it may not
** represent the user's complete history.
*/

dhistory::dhistory(void):QMainWindow()
{
  ui.setupUi(this);
#ifdef Q_WS_MAC
  setAttribute(Qt::WA_MacMetalStyle, false);
  statusBar()->setSizeGripEnabled(false);
#endif
  m_populateContentsList = false;
  m_model = new QStandardItemModel(this);
  m_model->setSortRole(Qt::UserRole);

  QStringList list;

  list << tr("Title") << tr("Host")
       << tr("Location") << tr("Last Time Visited")
       << tr("Visits") << "Description";
  m_model->setHorizontalHeaderLabels(list);
  ui.history->setModel(m_model);
  ui.history->setColumnHidden(m_model->columnCount() - 1, true);
  ui.history->setSelectionMode(QAbstractItemView::ExtendedSelection);
  ui.history->setSortingEnabled(true);
  ui.history->horizontalHeader()->setDefaultAlignment
    (Qt::AlignLeft);
  ui.history->horizontalHeader()->setSortIndicator(0, Qt::AscendingOrder);
  ui.history->horizontalHeader()->setSortIndicatorShown(true);
  ui.history->horizontalHeader()->setStretchLastSection(true);

  for(int i = 0; i < ui.history->horizontalHeader()->count() - 2; i++)
    ui.history->resizeColumnToContents(i);

  ui.history->horizontalHeader()->setResizeMode(QHeaderView::Interactive);
  ui.searchLineEdit->setPlaceholderText(tr("Search History"));
  slotSetIcons();
  connect(ui.history->horizontalHeader(),
	  SIGNAL(sortIndicatorChanged(int, Qt::SortOrder)),
	  this,
	  SLOT(slotSort(int, Qt::SortOrder)));
  connect(ui.deletePushButton,
	  SIGNAL(clicked(void)),
	  this,
	  SLOT(slotDeletePage(void)));
  connect(ui.deleteAllPushButton,
	  SIGNAL(clicked(void)),
	  this,
	  SLOT(slotDeleteAll(void)));
  connect(ui.treeWidget,
	  SIGNAL(itemSelectionChanged(void)),
	  this,
	  SLOT(slotItemSelectionChanged(void)));
  connect(this,
	  SIGNAL(iconsChanged(void)),
	  ui.searchLineEdit,
	  SLOT(slotSetIcons(void)));
  connect(ui.searchLineEdit, SIGNAL(textEdited(const QString &)),
	  this, SLOT(slotTextChanged(const QString &)));
  connect(ui.history,
	  SIGNAL(doubleClicked(const QModelIndex &)),
	  this,
	  SLOT(slotItemDoubleClicked(const QModelIndex &)));
  connect(ui.closePushButton,
	  SIGNAL(clicked(void)),
	  this,
	  SLOT(close(void)));
  connect(ui.history->selectionModel(),
	  SIGNAL(selectionChanged(const QItemSelection &,
				  const QItemSelection &)),
	  this,
	  SLOT(slotItemsSelected(const QItemSelection &,
				 const QItemSelection &)));
  ui.history->setContextMenuPolicy(Qt::CustomContextMenu);
  connect(ui.history, SIGNAL(customContextMenuRequested(const QPoint &)),
	  SLOT(slotShowContextMenu(const QPoint &)));

  m_timer = new QTimer(this);
  m_timer->setInterval(2500);
  connect(m_timer,
	  SIGNAL(timeout(void)),
	  this,
	  SLOT(slotTimeout(void)));
  m_searchTimer = new QTimer(this);
  m_searchTimer->setInterval(750);
  m_searchTimer->setSingleShot(true);
  connect(m_searchTimer,
	  SIGNAL(timeout(void)),
	  this,
	  SLOT(slotPopulate(void)));
  ui.splitter->setStretchFactor(0, 0);
  ui.splitter->setStretchFactor(1, 1);

  QTreeWidgetItem *item1 = 0;
  QList<QTreeWidgetItem *> items;

  item1 = new QTreeWidgetItem((QTreeWidget *) 0,
			      QStringList(tr("All")));
  item1->setData(0, Qt::UserRole, "");
  items << item1;
  item1 = new QTreeWidgetItem((QTreeWidget *) 0,
			      QStringList(tr("Today")));
  item1->setData
    (0, Qt::UserRole, QDate::currentDate().toString(Qt::ISODate));
  items << item1;
  item1 = new QTreeWidgetItem((QTreeWidget *) 0,
			      QStringList(tr("Yesterday")));
  item1->setData
    (0, Qt::UserRole, QDate::currentDate().addDays(-1).
     toString(Qt::ISODate));
  items << item1;

  QDate currentMonth(QDate::currentDate());
  QTreeWidgetItem *item2 = new QTreeWidgetItem
    ((QTreeWidget *) 0,
     QStringList(currentMonth.toString("MMMM").toUtf8().constData()));

  item2->setData
    (0, Qt::UserRole, currentMonth.toString(Qt::ISODate).mid(0, 7));
  items << item2;

  QDate lastMonth(QDate::currentDate().addMonths(-1));
  QTreeWidgetItem *item3 = new QTreeWidgetItem
    ((QTreeWidget *) 0,
     QStringList(tr(lastMonth.toString("MMMM").toUtf8().constData())));

  item3->setData
    (0, Qt::UserRole, lastMonth.toString(Qt::ISODate).mid(0, 7));
  items << item3;
  ui.treeWidget->insertTopLevelItems(0, items);

  for(int i = 0; i < currentMonth.daysInMonth(); i++)
    {
      QDate date;

      date.setDate(currentMonth.year(), currentMonth.month(), i + 1);
      item1 = new QTreeWidgetItem
	(item2, QStringList(tr(QString::number(i + 1).toAscii().constData())));
      item1->setData(0, Qt::UserRole, date.toString(Qt::ISODate));
    }

  for(int i = 0; i < lastMonth.daysInMonth(); i++)
    {
      QDate date;

      date.setDate(lastMonth.year(), lastMonth.month(), i + 1);
      item1 = new QTreeWidgetItem
	(item3, QStringList(tr(QString::number(i + 1).toAscii().constData())));
      item1->setData(0, Qt::UserRole, date.toString(Qt::ISODate));
    }

  if(items.size() > 1)
    items.at(1)->setSelected(true);

  ui.treeWidget->setFocus();

  if(dooble::s_settings.contains("historyWindow/splitterState"))
    ui.splitter->restoreState
      (dooble::s_settings.value("historyWindow/splitterState",
				"").toByteArray());

  if(dooble::s_settings.contains("historyWindow/tableColumnsState6"))
    if(!ui.history->horizontalHeader()->restoreState
       (dooble::s_settings.value("historyWindow/tableColumnsState6",
				 "").toByteArray()))
      {
	ui.history->horizontalHeader()->setDefaultAlignment
	  (Qt::AlignLeft);
	ui.history->horizontalHeader()->setSortIndicator(0,
							 Qt::AscendingOrder);
	ui.history->horizontalHeader()->setSortIndicatorShown(true);
	ui.history->horizontalHeader()->setStretchLastSection(true);
	ui.history->horizontalHeader()->setResizeMode
	  (QHeaderView::Interactive);
      }

  ui.history->horizontalHeader()->setMovable(true);
}

dhistory::~dhistory()
{
  saveState();

  if(!dmisc::passphraseWasAuthenticated())
    {
      {
	QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE", "history");

	db.setDatabaseName(dooble::s_homePath +
			   QDir::separator() + "history.db");

	if(db.open())
	  {
	    QSqlQuery query(db);

	    query.exec("PRAGMA synchronous = OFF");
	    query.exec("DELETE FROM history WHERE temporary = 1");
	    query.exec("VACUUM");
	  }

	db.close();
      }

      QSqlDatabase::removeDatabase("history");
    }
  else
    purge();
}

void dhistory::show(QWidget *parent)
{
  m_parent = parent;
  disconnect(m_parent,
	     SIGNAL(destroyed(void)),
	     this,
	     SLOT(slotParentDestroyed(void)));
  connect(m_parent,
	  SIGNAL(destroyed(void)),
	  this,
	  SLOT(slotParentDestroyed(void)));
  disconnect(this, SIGNAL(open(const QUrl &)), 0, 0);
  disconnect(this, SIGNAL(createTab(const QUrl &)), 0, 0);
  disconnect(this, SIGNAL(openInNewWindow(const QUrl &)), 0, 0);
  connect(this, SIGNAL(open(const QUrl &)),
	  m_parent, SLOT(slotLoadPage(const QUrl &)));
  connect(this, SIGNAL(createTab(const QUrl &)),
	  m_parent, SLOT(slotOpenLinkInNewTab(const QUrl &)));
  connect(this, SIGNAL(openInNewWindow(const QUrl &)),
	  m_parent, SLOT(slotOpenLinkInNewWindow(const QUrl &)));

  if(!isVisible())
    populate();

  if(!isVisible())
   {
     /*
     ** Don't annoy the user.
     */

     if(dooble::s_settings.contains("historyWindow/geometry"))
       {
	 if(dmisc::isGnome())
	   setGeometry(dooble::s_settings.value("historyWindow/geometry",
						QRect(100, 100, 800, 600)).
		       toRect());
	 else
	   {
	     QByteArray g(dooble::s_settings.value("historyWindow/geometry").
			  toByteArray());

	     if(!restoreGeometry(g))
	       setGeometry(100, 100, 800, 600);
	   }
       }
     else
       setGeometry(100, 100, 800, 600);
   }

  showNormal();
  raise();

  if(!m_timer->isActive())
    m_timer->start();
}

void dhistory::purge(void)
{
  {
    QSqlDatabase db = QSqlDatabase::addDatabase
      ("QSQLITE", "history_window_purge_all");

    db.setDatabaseName(dooble::s_homePath +
		       QDir::separator() + "history.db");

    if(db.open())
      {
	/*
	** Delete expired history items and items having invalid
	** URLs.
	*/

	int temporary = dmisc::passphraseWasAuthenticated() ? 0 : 1;
	QSqlQuery query(db);

	if(query.exec(QString("SELECT url, last_visited FROM history "
			      "WHERE temporary = %1").
		      arg(temporary)))
	  {
	    QDate date(QDate::currentDate());
	    QSqlQuery deleteQuery(db);

	    date = date.addDays
	      (-dooble::s_settings.value("settingsWindow/historyDays",
					 8).toInt());
	    deleteQuery.exec
	      ("PRAGMA synchronous = OFF");

	    while(query.next())
	      {
	        QUrl url
		  (QUrl::fromEncoded
		   (dmisc::decodedString
		    (QByteArray::fromBase64
		     (query.value(0).toByteArray())),
		    QUrl::StrictMode));

		if(url.isValid())
		  {
		    QDate urlDate
		      (QDate::fromString
		       (QString::fromUtf8
			(dmisc::decodedString
			 (QByteArray::fromBase64
			  (query.value(1).toByteArray()))),
			Qt::ISODate));

		    if(urlDate <= date)
		      {
			deleteQuery.prepare
			  ("DELETE FROM history WHERE "
			   "url = ? AND "
			   "temporary = ?");
			deleteQuery.bindValue(0, query.value(0));
			deleteQuery.bindValue(1, temporary);
			deleteQuery.exec();
		      }
		  }
		else
		  {
		    deleteQuery.prepare("DELETE FROM history WHERE "
					"url = ? AND temporary = ?");
		    deleteQuery.bindValue(0, query.value(0));
		    deleteQuery.bindValue(1, temporary);
		    deleteQuery.exec();
		  }
	      }
	  }

	if(dmisc::passphraseWasAuthenticated())
	  {
	    /*
	    ** Delete session-based history items.
	    */

	    QSqlQuery deleteQuery(db);

	    deleteQuery.exec
	      ("PRAGMA synchronous = OFF");
	    deleteQuery.exec
	      ("DELETE FROM history WHERE temporary = 1");
	    deleteQuery.exec("VACUUM");
	  }
      }

    db.close();
  }

  QSqlDatabase::removeDatabase("history_window_purge_all");
  statusBar()->showMessage(QString(tr("%1 Item(s) / %2 Item(s) Selected")).
			   arg(m_model->rowCount()).
			   arg(ui.history->selectionModel()->
			       selectedRows(0).size()));
}

void dhistory::slotSort(int column, Qt::SortOrder order)
{
  m_model->sort(column, order);
}

void dhistory::slotSetIcons(void)
{
  QSettings settings
    (dooble::s_settings.value("iconSet").toString(), QSettings::IniFormat);

  setWindowIcon
    (QIcon(settings.value("historyWindow/windowIcon").toString()));
  ui.closePushButton->setIcon
    (QIcon(settings.value("cancelButtonIcon").toString()));
  ui.deletePushButton->setIcon
    (QIcon(settings.value("historyWindow/deleteButtonIcon").toString()));
  ui.deleteAllPushButton->setIcon
    (QIcon(settings.value("historyWindow/deleteAllButtonIcon").toString()));
  emit iconsChanged();
}

void dhistory::slotTimeout(void)
{
  QFileInfo fileInfo(dooble::s_homePath + QDir::separator() + "history.db");
  static QDateTime lastModificationTime;

  if(fileInfo.exists())
    {
      if(fileInfo.lastModified() <= lastModificationTime)
	return;
      else
	lastModificationTime = fileInfo.lastModified();
    }
  else
    lastModificationTime = QDateTime();

  populate();
}

void dhistory::slotDeletePage(void)
{
  QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));

  /*
  ** Delete an entry, or more, from the history table.
  */

  m_timer->stop();

  int removedRows = 0;
  QStringList items;
  QModelIndexList list(ui.history->selectionModel()->selectedRows(2));

  /*
  ** Sort the list by row number. If the list is not sorted,
  ** the following while-loop misbehaves.
  */

  qSort(list);

  for(int i = 0; i < list.size(); i++)
    {
      QStandardItem *item = m_model->item
	(list.at(i).row() - removedRows, 2);

      if(item)
	{
	  items.append(item->text());

	  if(m_model->removeRow(item->row()))
	    removedRows += 1;
	}
    }

  if(!items.isEmpty())
    {
      {
	QSqlDatabase db = QSqlDatabase::addDatabase
	  ("QSQLITE", "history_window_delete");

	db.setDatabaseName(dooble::s_homePath +
			   QDir::separator() + "history.db");

	if(db.open())
	  {
	    int temporary = dmisc::passphraseWasAuthenticated() ? 0 : 1;
	    QSqlQuery query(db);

	    if(query.exec(QString("SELECT url FROM history WHERE "
				  "temporary = %1").arg(temporary)))
	      while(query.next())
		{
		  QUrl url
		  (QUrl::fromEncoded
		   (dmisc::decodedString
		    (QByteArray::fromBase64
		     (query.value(0).toByteArray())),
		    QUrl::StrictMode));

		  if(!items.contains(url.toString(QUrl::StripTrailingSlash)))
		    continue;
		  else
		    items.removeOne(url.toString(QUrl::StripTrailingSlash));

		  QSqlQuery deleteQuery(db);

		  deleteQuery.exec("PRAGMA synchronous = OFF");
		  deleteQuery.prepare("DELETE FROM history WHERE "
				      "url = ? AND "
				      "temporary = ?");
		  deleteQuery.bindValue(0, query.value(0));
		  deleteQuery.bindValue(1, temporary);
		  deleteQuery.exec();

		  if(items.isEmpty())
		    break;
		}
	  }

	db.close();
      }

      QSqlDatabase::removeDatabase("history_window_delete");
    }

  if(!list.isEmpty())
    {
      if(list.first().row() >= m_model->rowCount())
	ui.history->selectRow(m_model->rowCount() - 1);
      else
	ui.history->selectRow(list.first().row());
    }

  statusBar()->showMessage(QString(tr("%1 Item(s) / %2 Item(s) Selected")).
			   arg(m_model->rowCount()).
			   arg(ui.history->selectionModel()->
			       selectedRows(0).size()));
  m_timer->start();
  QApplication::restoreOverrideCursor();
}

void dhistory::slotOpen(void)
{
  QStandardItem *item = 0;
  QModelIndexList list(ui.history->selectionModel()->selectedRows(2));

  if(!list.isEmpty())
    item = m_model->itemFromIndex(list.takeFirst());

  if(item)
    {
      QUrl url(QUrl::fromUserInput(item->text()));

      url = QUrl::fromEncoded(url.toEncoded(QUrl::StripTrailingSlash));
      emit open(url);
    }
}

void dhistory::slotCopyUrl(void)
{
  QStandardItem *item = 0;
  QModelIndexList list(ui.history->selectionModel()->selectedRows(2));

  if(!list.isEmpty())
    item = m_model->itemFromIndex(list.takeFirst());

  if(item && QApplication::clipboard())
    QApplication::clipboard()->setText(item->text());
}

void dhistory::slotOpenInNewTab(void)
{
  QModelIndexList list(ui.history->selectionModel()->selectedRows(2));

  while(!list.isEmpty())
    {
      QStandardItem *item = m_model->itemFromIndex(list.takeFirst());

      if(item)
	{
	  QUrl url(QUrl::fromUserInput(item->text()));

	  url = QUrl::fromEncoded(url.toEncoded(QUrl::StripTrailingSlash));
	  emit createTab(url);
	}
    }
}

void dhistory::slotOpenInNewWindow(void)
{
  QStandardItem *item = 0;
  QModelIndexList list(ui.history->selectionModel()->selectedRows(2));

  if(!list.isEmpty())
    item = m_model->itemFromIndex(list.takeFirst());

  if(item)
    {
      QUrl url(QUrl::fromUserInput(item->text()));

      url = QUrl::fromEncoded(url.toEncoded(QUrl::StripTrailingSlash));
      emit openInNewWindow(url);
    }
}

void dhistory::slotShowContextMenu(const QPoint &point)
{
  if(!ui.history->selectionModel()->selectedRows().isEmpty())
    {
      QMenu menu(this);

      if(ui.history->selectionModel()->selectedRows().size() == 1 &&
	 ui.history->indexAt(point).isValid())
	{
	  menu.addAction(tr("&Bookmark"),
			 this, SLOT(slotBookmark(void)));
	  menu.addSeparator();
	  menu.addAction(tr("&Copy URL"),
			 this, SLOT(slotCopyUrl(void)));
	  menu.addSeparator();
	  menu.addAction(tr("&Delete Page"),
			 this, SLOT(slotDeletePage(void)));
	  menu.addSeparator();
	  menu.addAction(tr("Open in &Current Tab"),
			 this, SLOT(slotOpen(void)));
	  menu.addAction(tr("Open in New &Tab"),
			 this, SLOT(slotOpenInNewTab(void)));
	  menu.addAction(tr("Open in &New Window"),
			 this, SLOT(slotOpenInNewWindow(void)));
	}
      else
	{
	  menu.addAction(tr("&Bookmark"),
			 this, SLOT(slotBookmark(void)));
	  menu.addSeparator();
	  menu.addAction(tr("&Delete Pages"),
			 this, SLOT(slotDeletePage(void)));
	  menu.addSeparator();
	  menu.addAction(tr("Open in &New Tabs"),
			 this, SLOT(slotOpenInNewTab(void)));
	}

      menu.exec(ui.history->mapToGlobal(point));
    }
}

void dhistory::slotItemDoubleClicked(const QModelIndex &index)
{
  QStandardItem *item1 = 0;

  item1 = m_model->itemFromIndex(index);

  if(item1)
    {
      QStandardItem *item2 = m_model->item(item1->row(), 2);

      if(item2)
	{
	  QUrl url(QUrl::fromUserInput(item2->text()));

	  url = QUrl::fromEncoded(url.toEncoded(QUrl::StripTrailingSlash));
	  emit open(url);
	}
    }
}

void dhistory::slotParentDestroyed(void)
{
  /*
  ** Locate another Dooble window (parent).
  */

  foreach(QWidget *widget, QApplication::topLevelWidgets())
    if(qobject_cast<dooble *> (widget))
      if(widget != m_parent)
	{
	  m_parent = widget;
	  connect(m_parent,
		  SIGNAL(destroyed(void)),
		  this,
		  SLOT(slotParentDestroyed(void)));
	  disconnect(this, SIGNAL(open(const QUrl &)),
		     m_parent, SLOT(slotLoadPage(const QUrl &)));
	  disconnect(this, SIGNAL(createTab(const QUrl &)),
		     m_parent, SLOT(slotOpenLinkInNewTab(const QUrl &)));
	  disconnect(this, SIGNAL(openInNewWindow(const QUrl &)),
		     m_parent, SLOT(slotOpenLinkInNewWindow(const QUrl &)));
	  connect(this, SIGNAL(open(const QUrl &)),
		  m_parent, SLOT(slotLoadPage(const QUrl &)));
	  connect(this, SIGNAL(createTab(const QUrl &)),
		  m_parent, SLOT(slotOpenLinkInNewTab(const QUrl &)));
	  connect(this, SIGNAL(openInNewWindow(const QUrl &)),
		  m_parent, SLOT(slotOpenLinkInNewWindow(const QUrl &)));
	  break;
	}
}

void dhistory::slotDeleteAll(void)
{
  QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));

  m_timer->stop();

  QStringList items;

  for(int i = m_model->rowCount() - 1; i >= 0; i--)
    {
      QStandardItem *item = m_model->item(i, 2);

      if(item)
	{
	  items.append(item->text());
	  m_model->removeRow(item->row());
	}
    }

  if(!items.isEmpty())
    {
      {
	QSqlDatabase db = QSqlDatabase::addDatabase
	  ("QSQLITE", "history_window_delete_all");

	db.setDatabaseName(dooble::s_homePath +
			   QDir::separator() + "history.db");

	if(db.open())
	  {
	    int temporary = dmisc::passphraseWasAuthenticated() ? 0 : 1;
	    QSqlQuery query(db);

	    if(query.exec(QString("SELECT url FROM history WHERE "
				  "temporary = %1").arg(temporary)))
	      while(query.next())
		{
		  QUrl url
		    (QUrl::fromEncoded
		     (dmisc::decodedString
		      (QByteArray::fromBase64
		       (query.value(0).toByteArray())),
		      QUrl::StrictMode));

		  if(!items.contains(url.toString(QUrl::StripTrailingSlash)))
		    continue;
		  else
		    items.removeOne(url.toString(QUrl::StripTrailingSlash));

		  QSqlQuery deleteQuery(db);

		  deleteQuery.exec("PRAGMA synchronous = OFF");
		  deleteQuery.prepare("DELETE FROM history "
				      "WHERE url = ? AND "
				      "temporary = ?");
		  deleteQuery.bindValue(0, query.value(0));
		  deleteQuery.bindValue(1, temporary);
		  deleteQuery.exec();

		  if(items.isEmpty())
		    break;
		}
	  }

	db.close();
      }

      QSqlDatabase::removeDatabase("history_window_delete_all");
    }

  statusBar()->showMessage(QString(tr("%1 Item(s) / %2 Item(s) Selected")).
			   arg(m_model->rowCount()).
			   arg(ui.history->selectionModel()->
			       selectedRows(0).size()));
  m_timer->start();
  QApplication::restoreOverrideCursor();
}

void dhistory::populate(void)
{
  QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));

  /*
  ** Remember the user's current selection. After the table
  ** is populated, highlight approximate selections.
  */

  int hPos = ui.history->horizontalScrollBar()->value();
  int vPos = ui.history->verticalScrollBar()->value();
  QModelIndexList list(ui.history->selectionModel()->selectedRows());

  {
    QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE", "history_window");

    db.setDatabaseName(dooble::s_homePath +
		       QDir::separator() + "history.db");

    if(db.open())
      {
	m_model->removeRows(0, m_model->rowCount());

	int temporary = dmisc::passphraseWasAuthenticated() ? 0 : 1;
	QString dateStr("");
	QString searchStr
	  (ui.searchLineEdit->text().toLower().trimmed());
	QSqlQuery query(db);
	QList<QTreeWidgetItem *> list(ui.treeWidget->selectedItems());

	if(!list.isEmpty())
	  {
	    QTreeWidgetItem *item = list.at(0);

	    dateStr = item->data(0, Qt::UserRole).toString();
	  }

	if(query.exec(QString("SELECT title, url, last_visited, "
			      "visits, icon, description "
			      "FROM history WHERE "
			      "temporary = %1").
		      arg(temporary)))
	  while(query.next())
	    {
	      QUrl url
		(QUrl::fromEncoded
		 (dmisc::decodedString
		  (QByteArray::fromBase64
		   (query.value(1).toByteArray())),
		  QUrl::StrictMode));

	      if(!dmisc::isSchemeAcceptedByDooble(url.scheme()))
		{
		  QSqlQuery deleteQuery(db);

		  deleteQuery.exec
		    ("PRAGMA synchronous = OFF");
		  deleteQuery.prepare("DELETE FROM history WHERE "
				      "url = ? AND "
				      "temporary = ?");
		  deleteQuery.bindValue(0, query.value(1));
		  deleteQuery.bindValue(1, temporary);
		  deleteQuery.exec();
		  continue;
		}

	      QString host(url.host());
	      QString title
		(QString::fromUtf8
		 (dmisc::decodedString
		  (QByteArray::fromBase64
		   (query.value(0).toByteArray()))));
	      QString visits
		(dmisc::decodedString
		 (QByteArray::fromBase64
		  (query.value(3).toByteArray())));
	      QString description
		(QString::fromUtf8
		 (dmisc::decodedString
		  (QByteArray::fromBase64
		   (query.value(5).toByteArray()))));
	      QDateTime dateTime
		(QDateTime::fromString
		 (QString::fromUtf8
		  (dmisc::decodedString
		   (QByteArray::fromBase64
		    (query.value(2).toByteArray()))),
		  Qt::ISODate));
	      QByteArray bytes
		(dmisc::decodedString
		 (QByteArray::fromBase64
		  (query.value(4).toByteArray())));

	      if(m_populateContentsList)
		{
		  QVariantList variantList;

		  variantList << url
			      << bytes
			      << dateTime
			      << title
			      << visits
			      << description;
		  m_contentsList.append(variantList);
		}

	      if(!dateStr.isEmpty())
		if(!dateTime.date().toString(Qt::ISODate).
		   contains(dateStr))
		  continue;

	      if(host.isEmpty())
		host = "localhost";

	      if(!searchStr.isEmpty())
		if(!(host.toLower().contains(searchStr) ||
		     title.toLower().contains(searchStr) ||
		     url.toString(QUrl::StripTrailingSlash).
		     toLower().contains(searchStr)))
		  continue;

	      QIcon icon;
	      QBuffer buffer;
	      QStandardItem *item = new QStandardItem(title);

	      item->setData(title, Qt::UserRole);
	      buffer.setBuffer(&bytes);
	      buffer.open(QIODevice::ReadOnly);

	      QDataStream in(&buffer);

	      in >> icon;
	      buffer.close();

	      if(icon.isNull())
		icon = dmisc::iconForUrl(url);

	      item->setIcon(icon);
	      item->setEditable(false);
	      m_model->setRowCount(m_model->rowCount() + 1);
	      m_model->setItem(m_model->rowCount() - 1, 0, item);
	      item = new QStandardItem(host);
	      item->setData(host, Qt::UserRole);
	      item->setEditable(false);
	      m_model->setItem(m_model->rowCount() - 1, 1, item);
	      item = new QStandardItem(url.toString(QUrl::StripTrailingSlash));
	      item->setData(url.toString(QUrl::StripTrailingSlash),
			    Qt::UserRole);
	      item->setEditable(false);
	      m_model->setItem(m_model->rowCount() - 1, 2, item);
	      item = new QStandardItem
		(dateTime.toString("MM/dd/yyyy hh:mm:ss AP"));

	      /*
	      ** Sorting dates is complicated enough. By
	      ** inserting dates in the below format,
	      ** we'll guarantee that the date sort will behave.
	      */

	      item->setData(dateTime.toString("yyyy/MM/dd hh:mm:ss"),
			    Qt::UserRole);
	      item->setEditable(false);
	      m_model->setItem(m_model->rowCount() - 1, 3, item);
	      item = new QStandardItem(visits);
	      item->setData(visits.toInt(), Qt::UserRole);
	      item->setEditable(false);
	      m_model->setItem(m_model->rowCount() - 1, 4, item);
	      item = new QStandardItem(description);
	      m_model->setItem(m_model->rowCount() - 1, 5, item);
	    }

	slotSort(ui.history->horizontalHeader()->
		 sortIndicatorSection(),
		 ui.history->horizontalHeader()->
		 sortIndicatorOrder());
      }

    db.close();
  }

  QSqlDatabase::removeDatabase("history_window");
  statusBar()->showMessage(QString(tr("%1 Item(s) / %2 Item(s) Selected")).
			   arg(m_model->rowCount()).
			   arg(ui.history->selectionModel()->
			       selectedRows(0).size()));
  ui.history->horizontalHeader()->setStretchLastSection(true);
  ui.history->setSelectionMode(QAbstractItemView::MultiSelection);

  while(!list.isEmpty())
    ui.history->selectRow(list.takeFirst().row());

  ui.history->setSelectionMode(QAbstractItemView::ExtendedSelection);
  ui.history->horizontalScrollBar()->setValue(hPos);
  ui.history->verticalScrollBar()->setValue(vPos);
  QApplication::restoreOverrideCursor();
}

void dhistory::slotItemSelectionChanged(void)
{
  if(isVisible())
    populate();
}

void dhistory::slotTextChanged(const QString &text)
{
  Q_UNUSED(text);
  m_searchTimer->start();
}

void dhistory::closeEvent(QCloseEvent *event)
{
  m_timer->stop();
  m_searchTimer->stop();
  m_model->removeRows(0, m_model->rowCount());
  saveState();
  QMainWindow::closeEvent(event);
}

void dhistory::saveState(void)
{
  if(isVisible())
    {
      if(dmisc::isGnome())
	dooble::s_settings["historyWindow/geometry"] = geometry();
      else
	dooble::s_settings["historyWindow/geometry"] = saveGeometry();
    }

  dooble::s_settings["historyWindow/splitterState"] =
    ui.splitter->saveState();
  dooble::s_settings["historyWindow/tableColumnsState6"] =
    ui.history->horizontalHeader()->saveState();

  QSettings settings;

  if(isVisible())
    {
      if(dmisc::isGnome())
	settings.setValue("historyWindow/geometry", geometry());
      else
	settings.setValue("historyWindow/geometry", saveGeometry());
    }

  settings.setValue("historyWindow/splitterState",
		    ui.splitter->saveState());
  settings.setValue("historyWindow/tableColumnsState6",
		    ui.history->horizontalHeader()->
		    saveState());
}

void dhistory::slotItemsSelected(const QItemSelection &selected,
				 const QItemSelection &deselected)
{
  Q_UNUSED(selected);
  Q_UNUSED(deselected);
  statusBar()->showMessage(QString(tr("%1 Item(s) / %2 Item(s) Selected")).
			   arg(m_model->rowCount()).
			   arg(ui.history->selectionModel()->
			       selectedRows(0).size()));
}

void dhistory::keyPressEvent(QKeyEvent *event)
{
  if(event)
    {
      if(event->key() == Qt::Key_Escape)
	close();
      else if(event && event->key() == Qt::Key_Delete)
	slotDeletePage();
      else if(event->key() == Qt::Key_F &&
	      event->modifiers() == Qt::ControlModifier)
	{
	  ui.searchLineEdit->setFocus();
	  ui.searchLineEdit->selectAll();
	}
    }

  QMainWindow::keyPressEvent(event);
}

void dhistory::reencode(QProgressBar *progress)
{
  if(!dmisc::passphraseWasAuthenticated())
    return;

  /*
  ** This method assumes that the respective container
  ** is as current as possible. It will use the container's
  ** contents to create new database entries using the newly-encoded
  ** data. Obsolete data will be deleted via the purge method.
  */

  m_timer->stop();

  if(progress)
    {
      progress->setMaximum(-1);
      progress->setVisible(true);
    }

  {
    QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE",
						"history_reencode");

    db.setDatabaseName(dooble::s_homePath +
		       QDir::separator() + "history.db");

    if(db.open())
      {
	QSqlQuery query(db);

	query.exec("PRAGMA synchronous = OFF");
	query.exec("DELETE FROM history");
	query.exec("VACUUM");

	if(progress)
	  {
	    progress->setMaximum(m_contentsList.size());
	    progress->update();
	  }

	int i = 1;

	while(!m_contentsList.isEmpty())
	  {
	    QVariantList list(m_contentsList.takeFirst());

	    if(list.size() < 6)
	      {
		if(progress)
		  progress->setValue(i);

		i += 1;
		continue;
	      }

	    query.prepare
	      ("INSERT OR REPLACE INTO history "
	       "(url, icon, last_visited, title, visits, "
	       "temporary, description) "
	       "VALUES (?, ?, ?, ?, ?, ?, ?)");
	    query.bindValue
	      (0,
	       dmisc::encodedString
	       (list.at(0).toUrl().toEncoded(QUrl::StripTrailingSlash),
		true).toBase64());
	    query.bindValue
	      (1,
	       dmisc::encodedString(list.at(1).toByteArray(),
				    true).toBase64());
	    query.bindValue
	      (2,
	       dmisc::encodedString(list.at(2).toDateTime().
				    toString(Qt::ISODate).
				    toUtf8(),
				    true).toBase64());

	    if(list.at(3).toString().trimmed().isEmpty())
	      query.bindValue
		(3,
		 dmisc::encodedString(list.at(0).toUrl().
				      toEncoded(QUrl::StripTrailingSlash),
				      true).toBase64());
	    else
	      query.bindValue
		(3,
		 dmisc::encodedString(list.at(3).toString().toUtf8(),
				      true).toBase64());

	    query.bindValue
	      (4,
	       dmisc::encodedString(list.at(4).toString().toAscii(),
				    true).toBase64());

	    int temporary = dmisc::passphraseWasAuthenticated() ? 0 : 1;

	    query.bindValue(5, temporary);
	    query.bindValue
	      (6,
	       dmisc::encodedString(list.at(5).toString().toUtf8(),
				    true).toBase64());
	    query.exec();

	    if(progress)
	      progress->setValue(i);

	    i += 1;
	  }
      }

    db.close();
  }

  QSqlDatabase::removeDatabase("history_reencode");
  m_contentsList.clear();

  if(progress)
    progress->setVisible(false);

  m_timer->start();
}

void dhistory::slotBookmark(void)
{
  QDateTime now(QDateTime::currentDateTime());
  QModelIndexList list(ui.history->selectionModel()->selectedRows(0));

  while(!list.isEmpty())
    {
      QUrl url;
      QIcon icon;
      QString title("");
      QString description("");
      QStandardItem *item = m_model->item(list.first().row(), 0);

      if(item)
	{
	  icon = item->icon();
	  title = item->text();
	}

      item = m_model->item(list.first().row(), 2);

      if(item)
	{
	  url = QUrl::fromUserInput(item->text());
	  url = QUrl::fromEncoded(url.toEncoded(QUrl::StripTrailingSlash));
	}

      item = m_model->item(list.first().row(), 5);

      if(item)
	description = item->text();

      if(icon.isNull())
	icon = dmisc::iconForUrl(url);

      emit bookmark(url, icon, title, description, now, now);
      list.takeFirst();
    }
}

void dhistory::prepareHistoryList(void)
{
  m_populateContentsList = true;
  populate();
  m_populateContentsList = false;
}

void dhistory::slotPopulate(void)
{
  populate();
}
