483 lines
15 KiB
C++
483 lines
15 KiB
C++
/***************************************************************************
|
|
chathistorydialog.cpp - chat logs browser
|
|
-------------------
|
|
begin : Sun Feb 22 2009
|
|
copyright : (C) 2009 by Dario Freddi
|
|
email : drf54321@gmail.com
|
|
***************************************************************************/
|
|
|
|
/***************************************************************************
|
|
* *
|
|
* 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; either version 2 of the License, or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
***************************************************************************/
|
|
|
|
#include "chathistorydialog.h"
|
|
|
|
#include "../chat/chathistorymanager.h"
|
|
#include "../chat/chatmessagestyle.h"
|
|
#include "../chat/chatmessageview.h"
|
|
#include "../utils/kmessconfig.h"
|
|
#include "../utils/kmessshared.h"
|
|
#include "../accountsmanager.h"
|
|
#include "../currentaccount.h"
|
|
#include "../kmess.h"
|
|
#include "../kmessapplication.h"
|
|
#include "../kmessdebug.h"
|
|
|
|
#include <QDir>
|
|
#include <QLayout>
|
|
#include <QTimer>
|
|
#include <QStandardItemModel>
|
|
#include <QSortFilterProxyModel>
|
|
#include <QtXml/QDomDocument>
|
|
#include <QtXml/QDomNode>
|
|
#include <QClipboard>
|
|
#include <QTextCharFormat>
|
|
|
|
#include <QApplication>
|
|
#include <QIcon>
|
|
#include <KIconLoader>
|
|
#include <KMessageBox>
|
|
#include <KLocalizedString>
|
|
#include <QMenu>
|
|
#include <QAction>
|
|
#include <KStandardAction>
|
|
|
|
|
|
#ifdef KMESSDEBUG_CHATHISTORYDIALOG
|
|
#define KMESSDEBUG_CHATHISTORYDIALOG_VERBOSE
|
|
#endif
|
|
|
|
|
|
// Constructor
|
|
ChatHistoryDialog::ChatHistoryDialog( QWidget *parent )
|
|
: KMessDialog( parent )
|
|
, model_( new QStandardItemModel( this ) )
|
|
, proxyModel_( new QSortFilterProxyModel( this ) )
|
|
{
|
|
// Set up the dialog
|
|
QWidget *mainWidget = new QWidget( this );
|
|
setupUi( mainWidget );
|
|
setMainWidget( mainWidget );
|
|
setButtons( Help | Close );
|
|
setHelp( "using-chat-history" );
|
|
setWindowTitle( i18nc( "Dialog window title", "Chat History" ) );
|
|
loadingLabel_->hide();
|
|
loadingLabel_->setIcon( QIcon::fromTheme( "process-working" ) );
|
|
|
|
// Let the dialog destroy itself when it's done
|
|
setAttribute( Qt::WA_DeleteOnClose );
|
|
|
|
// Set a nice icon
|
|
setWindowIcon( KMessShared::drawIconOverlay( KIconLoader::global()->loadIcon( "kmess", KIconLoader::Panel ), QIcon::fromTheme( "chronometer" ).pixmap( 20, 20 ) ) );
|
|
|
|
// Create the chat view to show the chat logs
|
|
chatView_ = new ChatMessageView( mainWidget );
|
|
connect( chatView_, SIGNAL( popupMenu(const QString&,const QPoint&) ),
|
|
this, SLOT ( slotShowContextMenu(const QString&,const QPoint&) ) );
|
|
|
|
chatView_->updateChatStyle();
|
|
chatView_->getStyle()->setAllowEmoticonLinks( false );
|
|
frame_->layout()->addWidget( chatView_->widget() );
|
|
|
|
// Make the splitter assign more space to the view than to the controls,
|
|
// and set its minimum size
|
|
QSizePolicy sizePolicy( QSizePolicy::Preferred, QSizePolicy::Expanding );
|
|
sizePolicy.setVerticalStretch( 100 );
|
|
chatView_->widget()->setMinimumSize( 300, 200 );
|
|
chatView_->widget()->setSizePolicy( sizePolicy );
|
|
|
|
// Fill the accounts list
|
|
accountComboBox_->insertItems( 0, KMessConfig::instance()->getAccountsList() );
|
|
|
|
// If no accounts are available, disable the dialog
|
|
if( accountComboBox_->count() == 0 )
|
|
{
|
|
mainWidget->setEnabled( false );
|
|
}
|
|
else
|
|
{
|
|
KMessApplication *kmessApp = static_cast<KMessApplication *>(QApplication::instance());
|
|
|
|
// When disconnected or using a guest account, the accounts list will default to the
|
|
// first available account
|
|
if( ! kmessApp->getContactListWindow()->isConnected()
|
|
|| CurrentAccount::instance()->isGuestAccount() )
|
|
{
|
|
const QString ¤tHandle( accountComboBox_->itemText( 0 ) );
|
|
accountComboBox_->setCurrentItem( currentHandle );
|
|
}
|
|
else
|
|
{
|
|
accountComboBox_->setCurrentItem( CurrentAccount::instance()->getHandle() );
|
|
}
|
|
}
|
|
|
|
// Initialize the model for the list of contacts
|
|
proxyModel_->setSourceModel( model_ );
|
|
proxyModel_->setDynamicSortFilter( true );
|
|
proxyModel_->setFilterCaseSensitivity( Qt::CaseInsensitive );
|
|
|
|
contactListView_->setModel( proxyModel_ );
|
|
|
|
QItemSelectionModel *selectionModel = contactListView_->selectionModel();
|
|
|
|
// Attach the signals
|
|
connect( searchEdit_, SIGNAL( textChanged(const QString&) ),
|
|
proxyModel_, SLOT ( setFilterRegExp(const QString&) ) );
|
|
connect( selectionModel, SIGNAL( currentChanged(const QModelIndex&,const QModelIndex&) ),
|
|
this, SLOT ( showAvailableDates(const QModelIndex&) ) );
|
|
connect( datePicker_, SIGNAL( activated(const QDate&) ),
|
|
this, SLOT ( showLogs(const QDate&) ) );
|
|
connect( datePicker_, SIGNAL( clicked(const QDate&) ),
|
|
this, SLOT ( showLogs(const QDate&) ) );
|
|
connect( accountComboBox_, SIGNAL( currentIndexChanged(int) ),
|
|
this, SLOT ( slotAccountChanged(int) ) );
|
|
|
|
// Save the dialog and splitters sizes
|
|
KConfigGroup group = KMessConfig::instance()->getGlobalConfig( "ChatHistoryDialog" );
|
|
restoreDialogSize( group );
|
|
splitter_ ->setSizes( group.readEntry( "mainSplitterSizes", QList<int>() << 1 << 100 ) );
|
|
|
|
// Normally the focus would be set on the Search box, thus making the
|
|
// preset explanation text to disappear.
|
|
contactListView_->setFocus();
|
|
|
|
searchEdit_->setEnabled( false );
|
|
contactListView_->setEnabled( false );
|
|
datePicker_->setEnabled( false );
|
|
}
|
|
|
|
|
|
|
|
// Destructor
|
|
ChatHistoryDialog::~ChatHistoryDialog()
|
|
{
|
|
// Save the dialog and splitters size
|
|
KConfigGroup group = KMessConfig::instance()->getGlobalConfig( "ChatHistoryDialog" );
|
|
saveDialogSize( group );
|
|
group.writeEntry( "mainSplitterSizes", splitter_ ->sizes() );
|
|
}
|
|
|
|
|
|
|
|
// Show on which dates there are logged chats with a certain contact
|
|
void ChatHistoryDialog::showAvailableDates( const QModelIndex &index )
|
|
{
|
|
const QModelIndex contactIndex( proxyModel_->mapToSource( index ) );
|
|
if( ! contactIndex.isValid() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Initially the list view emits a currentChanged() signal, but the list
|
|
// doesn't bother to highlight the selected item
|
|
contactListView_->setCurrentIndex( index );
|
|
|
|
// Block the UI while we're loading
|
|
setLoading( true );
|
|
|
|
const QString handle( model_->itemFromIndex( contactIndex )->text() );
|
|
|
|
ChatHistoryManager::setHandle( handle );
|
|
const ConversationList timestamps( ChatHistoryManager::timestamps() );
|
|
|
|
switch( ChatHistoryManager::result() )
|
|
{
|
|
case ChatHistoryManager::RESULT_OK:
|
|
break;
|
|
|
|
case ChatHistoryManager::RESULT_NON_EXISTING_DIRECTORY:
|
|
case ChatHistoryManager::RESULT_UNREADABLE_LOGS:
|
|
{
|
|
#ifdef KMESSDEBUG_CHATHISTORYDIALOG
|
|
kmDebug() << "Unable to read logs: error" << ChatHistoryManager::result();
|
|
#endif
|
|
setLoading( false );
|
|
QDir logsDir( KMessConfig::instance()->getAccountDirectory( ChatHistoryManager::account() ) + "/chatlogs" );
|
|
KMessageBox::error( this, i18nc( "Dialog box text",
|
|
"There has been an error while opening your logs. This "
|
|
"is commonly a permission problem, check if you have "
|
|
"read/write access to directory <i>"%1"</i>. "
|
|
"Otherwise, your logs may be corrupted.",
|
|
logsDir.absolutePath() ),
|
|
i18nc( "Dialog box title", "Could not open chat history" ) );
|
|
return;
|
|
}
|
|
|
|
default:
|
|
setLoading( false );
|
|
kmWarning() << "Unexpected result from the chat history manager: code" << ChatHistoryManager::result();
|
|
return;
|
|
}
|
|
|
|
// Highlight the days where conversations were logged
|
|
QTextCharFormat enabledDateFormat;
|
|
enabledDateFormat.setFontPointSize( QApplication::font().pointSizeF() + 1 );
|
|
enabledDateFormat.setFontWeight( QFont::Black );
|
|
enabledDateFormat.setFontUnderline( true );
|
|
|
|
// Mark the dates with available chats in the calendar
|
|
QList<quint64> list = timestamps.keys();
|
|
qSort( list );
|
|
|
|
QDate lastDate;
|
|
foreach( quint64 timestamp, list )
|
|
{
|
|
lastDate = QDateTime::fromSecsSinceEpoch( timestamp ).date();
|
|
|
|
// Set the calendar's first and last available dates, while we're at it
|
|
if( timestamp == list.first() )
|
|
{
|
|
datePicker_->setMinimumDate( lastDate );
|
|
}
|
|
else if( timestamp == list.last() )
|
|
{
|
|
datePicker_->setMaximumDate( lastDate );
|
|
}
|
|
|
|
datePicker_->setDateTextFormat( lastDate, enabledDateFormat );
|
|
}
|
|
|
|
#ifdef KMESSDEBUG_CHATHISTORYDIALOG_VERBOSE
|
|
if( timestamps.isEmpty() )
|
|
{
|
|
kmDebug() << "No conversations available.";
|
|
}
|
|
else
|
|
{
|
|
kmDebug() << timestamps.count() << "conversations were recorded.";
|
|
}
|
|
#endif
|
|
|
|
// Change the date picker view to display the last date, and show the last log
|
|
if( lastDate.isValid() )
|
|
{
|
|
datePicker_->setCurrentPage( lastDate.year(), lastDate.month() );
|
|
datePicker_->setSelectedDate( lastDate );
|
|
showLogs( lastDate );
|
|
}
|
|
|
|
setLoading( false );
|
|
}
|
|
|
|
|
|
|
|
// Change the account for which to show logs
|
|
void ChatHistoryDialog::slotAccountChanged( int index )
|
|
{
|
|
setLoading( true );
|
|
|
|
ChatHistoryManager::setAccount( accountComboBox_->itemText( index ) );
|
|
|
|
datePicker_->setDateTextFormat( QDate(), QTextCharFormat() );
|
|
|
|
// Clear the contacts list
|
|
model_->clear();
|
|
|
|
const QStringList &list = ChatHistoryManager::contactsList();
|
|
foreach( QString handle, list )
|
|
{
|
|
model_->appendRow( new QStandardItem( handle ) );
|
|
}
|
|
|
|
setLoading( false );
|
|
|
|
// Disable the listview if there are no contacts
|
|
if( list.isEmpty() )
|
|
{
|
|
searchEdit_->setEnabled( false );
|
|
contactListView_->setEnabled( false );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Load and show the selected chats
|
|
void ChatHistoryDialog::showLogs( const QDate& day )
|
|
{
|
|
// Don't reload the same chat
|
|
if( lastSelectedDate_.isValid() && lastSelectedDate_ == day )
|
|
{
|
|
return;
|
|
}
|
|
|
|
lastSelectedDate_ = day;
|
|
|
|
if( ChatHistoryManager::timestamps().isEmpty() )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATHISTORYDIALOG
|
|
kmWarning() << "No conversations to load!";
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
setLoading( true );
|
|
|
|
#ifdef KMESSDEBUG_CHATHISTORYDIALOG
|
|
kmDebug() << "Loading...";
|
|
#endif
|
|
|
|
// The text stream is used to dump the DOM nodes to an XML byte array.
|
|
QString xmlData;
|
|
QList<quint64> timestamps;
|
|
|
|
// Find all chat log timestamps in the specified day
|
|
|
|
// Get the initial and final timestamps of the range
|
|
const quint64 startingDate = QDateTime( day, QTime( 0, 0 ) ).toSecsSinceEpoch();
|
|
const quint64 endingDate = QDateTime( day, QTime( 23, 59 ) ).toSecsSinceEpoch();
|
|
|
|
|
|
#ifdef KMESSDEBUG_CHATHISTORYDIALOG_VERBOSE
|
|
kmDebug() << "Filtering out conversations before timestamp" << startingDate << "and after" << endingDate;
|
|
#endif
|
|
|
|
ConversationList list( ChatHistoryManager::timestamps() );
|
|
ConversationList::iterator startIt = list.lowerBound( startingDate );
|
|
ConversationList::iterator endIt = list.upperBound( endingDate );
|
|
|
|
// How could the user have made it until here?
|
|
if( startIt == list.end() )
|
|
{
|
|
kmWarning() << "No conversations were logged in this date!";
|
|
setLoading( false );
|
|
return;
|
|
}
|
|
|
|
// Add the matching timestamps to a list
|
|
do
|
|
{
|
|
timestamps << startIt.key();
|
|
startIt++;
|
|
}
|
|
while( startIt != endIt );
|
|
|
|
// chatLog() takes a list of chat log times and joins all conversations together
|
|
QString xmlString( ChatHistoryManager::chatLog( timestamps, true, false ) );
|
|
|
|
// The string must be wrapped into a single tag, to have a valid XML tree even when
|
|
// there is more than one <conversation> tag.
|
|
chatView_->setXml( "<messageRoot>" + xmlString + "</messageRoot>" );
|
|
|
|
setLoading( false );
|
|
|
|
#ifdef KMESSDEBUG_CHATHISTORYDIALOG
|
|
kmDebug() << "Chat log loading returned" << ChatHistoryManager::result();
|
|
kmDebug() << "Reload done.";
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Set the contact for whom the logs will be initially shown
|
|
*
|
|
* @param handle the account to search logs in
|
|
* @param account the handle to get contact history for
|
|
* @returns whether there was history for the given contact
|
|
*/
|
|
bool ChatHistoryDialog::setInitialContact( const QString &account, const QString &handle )
|
|
{
|
|
if( account.isEmpty() )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATHISTORYDIALOG
|
|
kmDebug() << "Account is empty, selecting nothing.";
|
|
#endif
|
|
contactListView_->setCurrentIndex( QModelIndex() );
|
|
return false;
|
|
}
|
|
|
|
#ifdef KMESSDEBUG_CHATHISTORYDIALOG
|
|
kmDebug() << "Pre-selecting account" << account;
|
|
#endif
|
|
|
|
// Select the account first (calling the slot directly, or no change signal would be fired)
|
|
slotAccountChanged( accountComboBox_->findText( account ) );
|
|
|
|
if( handle.isEmpty() )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATHISTORYDIALOG
|
|
kmDebug() << "Handle is empty, not selecting a contact.";
|
|
#endif
|
|
contactListView_->setCurrentIndex( QModelIndex() );
|
|
return false;
|
|
}
|
|
|
|
// Then select the contact handle
|
|
QList<QStandardItem*> items = model_->findItems( handle, Qt::MatchContains );
|
|
|
|
if( items.isEmpty() )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATHISTORYDIALOG
|
|
kmDebug() << handle << "has no chat history in account" << account;
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
#ifdef KMESSDEBUG_CHATHISTORYDIALOG
|
|
kmDebug() << "Pre-selecting handle" << handle;
|
|
#endif
|
|
|
|
contactListView_->setCurrentIndex( proxyModel_->mapFromSource( items.first()->index() ) );
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
// Set whether the dialog is loading chats or not
|
|
void ChatHistoryDialog::setLoading( bool isLoading )
|
|
{
|
|
if( isLoading )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATHISTORYDIALOG_VERBOSE
|
|
kmDebug() << "Now loading...";
|
|
#endif
|
|
|
|
// Show the loading animation
|
|
loadingLabel_->show();
|
|
loadingLabel_->start();
|
|
|
|
searchEdit_->setEnabled( false );
|
|
contactListView_->setEnabled( false );
|
|
datePicker_->setEnabled( false );
|
|
|
|
// Clear the view
|
|
chatView_->clearView( true );
|
|
}
|
|
else
|
|
{
|
|
// Hide the loading animation
|
|
loadingLabel_->stop();
|
|
loadingLabel_->hide();
|
|
|
|
searchEdit_->setEnabled( ! ChatHistoryManager::contactsList().isEmpty() );
|
|
contactListView_->setEnabled( ! ChatHistoryManager::contactsList().isEmpty() );
|
|
datePicker_->setEnabled( ! ChatHistoryManager::timestamps().isEmpty() );
|
|
datePicker_->setFocus();
|
|
|
|
#ifdef KMESSDEBUG_CHATHISTORYDIALOG_VERBOSE
|
|
kmDebug() << "...loading ended";
|
|
#endif
|
|
}
|
|
|
|
accountComboBox_->setEnabled( ! isLoading );
|
|
}
|
|
|
|
|
|
|
|
// The user right clicked at the KHTMLPart to show a popup.
|
|
void ChatHistoryDialog::slotShowContextMenu( const QString &clickedUrl, const QPoint &point )
|
|
{
|
|
Q_UNUSED( clickedUrl )
|
|
|
|
QMenu *popup = chatView_->popupMenu();
|
|
popup->exec( point );
|
|
delete popup;
|
|
}
|