808 lines
25 KiB
C++
808 lines
25 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/chatmessagestyle.h"
|
|
#include "../chat/chatmessageview.h"
|
|
#include "../utils/kmessconfig.h"
|
|
#include "../utils/kmessshared.h"
|
|
#include "../currentaccount.h"
|
|
#include "../kmessdebug.h"
|
|
|
|
#include <QDir>
|
|
#include <QTimer>
|
|
#include <QStandardItemModel>
|
|
#include <QSortFilterProxyModel>
|
|
#include <QtXml/QDomDocument>
|
|
#include <QtXml/QDomNode>
|
|
#include <QClipboard>
|
|
|
|
#include <KApplication>
|
|
#include <KMessageBox>
|
|
#include <KLocalizedString>
|
|
#include <KDateTime>
|
|
#include <KMenu>
|
|
#include <KAction>
|
|
#include <KHTMLView>
|
|
#include <KStandardAction>
|
|
|
|
|
|
#ifdef KMESSDEBUG_CHATHISTORYDIALOG
|
|
// #define KMESSDEBUG_CHATHISTORYDIALOG_VERBOSE
|
|
#endif
|
|
|
|
|
|
// Constructor
|
|
ChatHistoryDialog::ChatHistoryDialog( QWidget *parent )
|
|
: KDialog( parent )
|
|
, logParser_( 0 )
|
|
, model_( new QStandardItemModel( this ) )
|
|
, proxyModel_( new QSortFilterProxyModel( this ) )
|
|
, reloadingLogs_( false )
|
|
, reloadLogsTimer_(0)
|
|
{
|
|
// 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();
|
|
|
|
// 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 ), KIcon( "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 );
|
|
rightSplitter_->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 );
|
|
|
|
loadContactsList();
|
|
|
|
// Initialize the model for the list of contacts
|
|
proxyModel_->setSourceModel( model_ );
|
|
proxyModel_->setDynamicSortFilter( true );
|
|
proxyModel_->setFilterCaseSensitivity( Qt::CaseInsensitive );
|
|
|
|
listView_->setModel( proxyModel_ );
|
|
|
|
QItemSelectionModel *selectionModel = listView_->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 ( cacheContactXml(const QModelIndex&) ) );
|
|
connect( dateRadio_, SIGNAL( toggled(bool) ),
|
|
this, SLOT ( reloadLogs() ) );
|
|
connect( fromBox_, SIGNAL( toggled(bool) ),
|
|
this, SLOT ( reloadLogs() ) );
|
|
connect( toBox_, SIGNAL( toggled(bool) ),
|
|
this, SLOT ( reloadLogs() ) );
|
|
connect( fromDate_, SIGNAL( changed(const QDate&) ),
|
|
this, SLOT ( checkDates() ) );
|
|
connect( toDate_, SIGNAL( changed(const QDate&) ),
|
|
this, SLOT ( checkDates() ) );
|
|
connect( conversationRadio_, SIGNAL( toggled(bool) ),
|
|
this, SLOT ( reloadLogs() ) );
|
|
connect( conversationComboBox_, SIGNAL( currentIndexChanged(int) ),
|
|
this, SLOT ( reloadLogs() ) );
|
|
|
|
// Register QDomElement as a meta type to allow the thread to send signals here
|
|
qRegisterMetaType<QDomElement>( "QDomElement" );
|
|
|
|
// Save the dialog and splitters sizes
|
|
KConfigGroup group = KMessConfig::instance()->getGlobalConfig( "ChatHistoryDialog" );
|
|
restoreDialogSize( group );
|
|
mainSplitter_ ->setSizes( group.readEntry( "mainSplitterSizes", QList<int>() << 1 << 100 ) );
|
|
rightSplitter_->setSizes( group.readEntry( "rightSplitterSizes", QList<int>() << 1 << 100 ) );
|
|
|
|
// Normally the focus would be set on the Search box, thus making the
|
|
// preset explanation text to disappear.
|
|
listView_->setFocus();
|
|
}
|
|
|
|
|
|
|
|
// Destructor
|
|
ChatHistoryDialog::~ChatHistoryDialog()
|
|
{
|
|
// Save the dialog and splitters size
|
|
KConfigGroup group = KMessConfig::instance()->getGlobalConfig( "ChatHistoryDialog" );
|
|
saveDialogSize( group );
|
|
group.writeEntry( "mainSplitterSizes", mainSplitter_ ->sizes() );
|
|
group.writeEntry( "rightSplitterSizes", rightSplitter_->sizes() );
|
|
}
|
|
|
|
|
|
|
|
// A conversation has been parsed by the thread
|
|
void ChatHistoryDialog::addConversation( int timestamp, const QDomElement &newConversation )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATHISTORYDIALOG_VERBOSE
|
|
kDebug() << "Adding new conversation on" << timestamp;
|
|
#endif
|
|
|
|
QMutexLocker lock( &addConversationLock_ );
|
|
const QDateTime &date = QDateTime::fromTime_t( timestamp );
|
|
|
|
// Add this conversation to the internal list
|
|
conversations_[ timestamp ] = newConversation;
|
|
|
|
// Add the chat to the chat selection combo box, keeping it sorted by date (most recent first)
|
|
// The first item (index 0) is skipped as it is a temporary item ("Loading...") which
|
|
// should remain the first
|
|
int index;
|
|
for( index = 1; index < conversationComboBox_->count(); ++index )
|
|
{
|
|
int currentTimestamp = conversationComboBox_->itemData( index, Qt::UserRole ).toUInt();
|
|
|
|
if( currentTimestamp < 1 )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATHISTORYDIALOG
|
|
kWarning() << "Invalid timestamp!";
|
|
#endif
|
|
continue;
|
|
}
|
|
|
|
if( currentTimestamp <= timestamp )
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Prevent the insertion from generating a signal which regenerates the logs
|
|
conversationComboBox_->blockSignals( true );
|
|
conversationComboBox_->insertItem( index,
|
|
KGlobal::locale()->formatDateTime( date, KLocale::ShortDate ),
|
|
timestamp );
|
|
conversationComboBox_->blockSignals( false );
|
|
|
|
#ifdef KMESSDEBUG_CHATHISTORYDIALOG_VERBOSE
|
|
kDebug() << "Added as item" << index;
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
// Caches the XML for the selected contact. Makes things a lot faster
|
|
void ChatHistoryDialog::cacheContactXml( 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
|
|
listView_->setCurrentIndex( index );
|
|
|
|
// Reset the xml loading thread
|
|
if( logParser_ != 0 )
|
|
{
|
|
logParser_->end();
|
|
logParser_ = 0;
|
|
}
|
|
|
|
// Reset the UI
|
|
xml_.clear();
|
|
conversations_.clear();
|
|
setLoading( true );
|
|
// Avoid reentering while we're still loading a contact's chats
|
|
listView_->setEnabled( false );
|
|
|
|
// Reset the cache
|
|
xml_.setContent( QByteArray( "<conversations></conversations>" ) );
|
|
QDomNode cachedConversations( xml_.firstChildElement( "conversations" ) );
|
|
|
|
const QString handle( model_->itemFromIndex( contactIndex )->text() );
|
|
QDir logsDir( KMessConfig::instance()->getAccountDirectory( CurrentAccount::instance()->getHandle() )
|
|
+ "/chatlogs" );
|
|
|
|
#ifdef KMESSDEBUG_CHATHISTORYDIALOG
|
|
kDebug() << "Loading conversations for contact:" << handle;
|
|
#endif
|
|
|
|
// Filter only to read from this contact's files, if any
|
|
logsDir.setFilter( QDir::Files );
|
|
logsDir.setNameFilters( QStringList() << ( handle + "*.xml" ) );
|
|
|
|
bool showDate = chatView_->getStyle()->getShowMessageDate();
|
|
bool showTime = chatView_->getStyle()->getShowMessageTime();
|
|
bool showSeconds = chatView_->getStyle()->getShowMessageSeconds();
|
|
|
|
const QFileInfoList &list = logsDir.entryInfoList();
|
|
foreach( const QFileInfo &entry, list )
|
|
{
|
|
QString currentHandle( entry.fileName() );
|
|
|
|
#ifdef KMESSDEBUG_CHATHISTORYDIALOG_VERBOSE
|
|
kDebug() << "Reading conversations from file:" << entry.fileName();
|
|
#endif
|
|
|
|
QFile file( entry.absoluteFilePath() );
|
|
|
|
if( ! file.open( QFile::ReadOnly | QFile::Text ) )
|
|
{
|
|
// Very unlikely, but...
|
|
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" ) );
|
|
|
|
setLoading( false );
|
|
return;
|
|
}
|
|
|
|
if( logParser_ == 0 )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATHISTORYDIALOG_VERBOSE
|
|
kDebug() << "Starting log parser thread...";
|
|
#endif
|
|
logParser_ = new XmlLogParser( showDate, showTime, showSeconds );
|
|
connect( logParser_, SIGNAL( gotConversation(int,const QDomElement&) ),
|
|
this, SLOT ( addConversation(int,const QDomElement&) ) );
|
|
connect( logParser_, SIGNAL( finished() ),
|
|
this, SLOT ( setLoading() ) );
|
|
}
|
|
|
|
#ifdef KMESSDEBUG_CHATHISTORYDIALOG_VERBOSE
|
|
kDebug() << "Adding conversation" << entry.fileName() << "to log parser thread...";
|
|
#endif
|
|
logParser_->addDocument( entry.fileName(), file.readAll() );
|
|
|
|
file.close();
|
|
}
|
|
|
|
// start the thread
|
|
if( logParser_ != 0 && !logParser_->isRunning() )
|
|
{
|
|
kDebug() << "Starting the log parser thread!";
|
|
logParser_->start( QThread::HighPriority );
|
|
}
|
|
|
|
if( list.isEmpty() )
|
|
{
|
|
setLoading( false );
|
|
|
|
#ifdef KMESSDEBUG_CHATHISTORYDIALOG_VERBOSE
|
|
kDebug() << "No conversations available.";
|
|
#endif
|
|
}
|
|
#ifdef KMESSDEBUG_CHATHISTORYDIALOG_VERBOSE
|
|
else
|
|
{
|
|
kDebug() << "Conversations are available.";
|
|
}
|
|
#endif
|
|
|
|
// Restore the UI: the view will be reenabled by reloadLogs()
|
|
listView_->setEnabled( true );
|
|
}
|
|
|
|
|
|
|
|
// Verify whether the chat date interval is valid or not
|
|
bool ChatHistoryDialog::checkDates( bool checkAndReloadLogs )
|
|
{
|
|
// When the user first clicks on the "filter by date" radio button, neither the "from"
|
|
// nor the "to" checkboxes are enabled, so show no chats.
|
|
// It's better than showing *everything*, which may mean YEARS of chats.
|
|
if( ! fromBox_->isChecked() && ! toBox_->isChecked() )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATHISTORYDIALOG_VERBOSE
|
|
kDebug() << "No date filter chosen.";
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
QDate fromDate( fromDate_->date() );
|
|
QDate toDate ( toDate_ ->date() );
|
|
|
|
// Disallow negative date intervals
|
|
if( fromDate > toDate )
|
|
{
|
|
toDate_->setDate( fromDate );
|
|
|
|
// Setting the date calls this method for another check
|
|
return false;
|
|
}
|
|
|
|
// Allow a maximum interval of one year of chats (and this may already
|
|
// be an ENORMOUS amount of data, depending on the contact...)
|
|
static const int maxDays = 365;
|
|
int daysDifference = fromDate.daysTo( toDate ) - maxDays;
|
|
if( daysDifference > 0 )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATHISTORYDIALOG_VERBOSE
|
|
kDebug() << "Date difference is too long (" << maxDays << "days +" << daysDifference << "), fixing dates.";
|
|
#endif
|
|
|
|
toDate = toDate.addDays( - daysDifference );
|
|
toDate_->setDate( toDate );
|
|
|
|
// Setting the date calls this method for another check
|
|
return false;
|
|
}
|
|
|
|
if( checkAndReloadLogs )
|
|
{
|
|
reloadLogs();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
// Load the list of contacts for which logs are available
|
|
void ChatHistoryDialog::loadContactsList()
|
|
{
|
|
// Find all XML chat log files in the logs directory
|
|
QDir logsDir( KMessConfig::instance()->getAccountDirectory( CurrentAccount::instance()->getHandle() )
|
|
+ "/chatlogs" );
|
|
|
|
logsDir.setFilter( QDir::Files );
|
|
logsDir.setNameFilters( QStringList() << "*.xml" );
|
|
|
|
// Add them to the list
|
|
const QFileInfoList &list = logsDir.entryInfoList();
|
|
QHash<QString, bool> loadedHandles;
|
|
|
|
foreach( const QFileInfo &entry, list )
|
|
{
|
|
QString handle( entry.fileName() );
|
|
handle.remove( handle.length() - 4, 4 );
|
|
|
|
// strip off the number information too.
|
|
if( handle.at( handle.length() - 1 ).isNumber() )
|
|
{
|
|
handle.truncate( handle.lastIndexOf( '.' ) );
|
|
}
|
|
|
|
// now, only add the handle if it hasn't already been loaded.
|
|
if ( loadedHandles.contains( handle ) ) continue;
|
|
|
|
loadedHandles.insert( handle, true );
|
|
model_->appendRow( new QStandardItem( handle ) );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Reload the current chat log history
|
|
void ChatHistoryDialog::reloadLogs()
|
|
{
|
|
// Never load logs immediately: wait an instant to allow the user to do more changes.
|
|
// This makes the interface zippy and the user happy
|
|
if( reloadLogsTimer_ == 0 )
|
|
{
|
|
reloadLogsTimer_ = new QTimer( this );
|
|
reloadLogsTimer_->setSingleShot( true );
|
|
connect( reloadLogsTimer_, SIGNAL( timeout() ),
|
|
this, SLOT ( reloadLogs() ) );
|
|
}
|
|
|
|
if( reloadLogsTimer_ != sender() )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATHISTORYDIALOG
|
|
kDebug() << "Scheduling reload...";
|
|
#endif
|
|
reloadLogsTimer_->start( 250 );
|
|
return;
|
|
}
|
|
|
|
// Reloading logs may be time consuming too, protect the operation from reentrancy
|
|
// QMutexLocker lock( &addConversationLock_ );
|
|
if( reloadingLogs_ )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATHISTORYDIALOG
|
|
kDebug() << "Already running, exit.";
|
|
#endif
|
|
return;
|
|
}
|
|
reloadingLogs_ = true;
|
|
|
|
chatView_->widget()->setEnabled( false );
|
|
|
|
if( conversations_.isEmpty() )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATHISTORYDIALOG
|
|
kWarning() << "No cached conversations!";
|
|
#endif
|
|
reloadingLogs_ = false;
|
|
return;
|
|
}
|
|
|
|
#ifdef KMESSDEBUG_CHATHISTORYDIALOG
|
|
kDebug() << "Reloading...";
|
|
#endif
|
|
|
|
// The text stream is used to dump the DOM nodes to an XML byte array.
|
|
QString xmlData;
|
|
QTextStream log( &xmlData );
|
|
|
|
// Show a specific chat
|
|
if( conversationRadio_->isChecked() )
|
|
{
|
|
// Get the timestamp of the requested chat from the combobox.
|
|
int timestamp = conversationComboBox_->itemData( conversationComboBox_->currentIndex(),
|
|
Qt::UserRole ).toUInt();
|
|
|
|
#ifdef KMESSDEBUG_CHATHISTORYDIALOG_VERBOSE
|
|
kDebug() << "Conversation timestamp:" << timestamp;
|
|
#endif
|
|
|
|
if( ! conversations_.contains( timestamp ) )
|
|
{
|
|
kWarning() << "Chat not found!";
|
|
reloadingLogs_ = false;
|
|
return;
|
|
}
|
|
|
|
// Add the chat to the xml output
|
|
log << conversations_[ timestamp ];
|
|
}
|
|
// There are two options only, but you *never* know.
|
|
else if( dateRadio_->isChecked() )
|
|
{
|
|
// Assume the interval of dates makes sense: checkDates() fixes the dates for us.
|
|
if( ! checkDates( false /*checkAndReloadLogs */ ) )
|
|
{
|
|
reloadingLogs_ = false;
|
|
return;
|
|
}
|
|
|
|
// All chats in the specified time interval will be merged into one.
|
|
|
|
// Add to the xml string the starting tag
|
|
log << "<messageRoot>" ;
|
|
|
|
QMapIterator<int,QDomNode> it( conversations_ );
|
|
while( it.hasNext() )
|
|
{
|
|
it.next();
|
|
const int timestamp = it.key();
|
|
const QDomNode &conversation = it.value();
|
|
const QDate &date = QDateTime::fromTime_t( timestamp ).date();
|
|
|
|
#ifdef KMESSDEBUG_CHATHISTORYDIALOG_VERBOSE
|
|
kDebug() << "Conversation date:" << date;
|
|
#endif
|
|
|
|
if( fromBox_->isChecked() && date < fromDate_->date() )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATHISTORYDIALOG_VERBOSE
|
|
kDebug() << "Filtering out conversation on:" << date << "<" << fromDate_->date();
|
|
#endif
|
|
continue;
|
|
}
|
|
else if( toBox_->isChecked() && date > toDate_->date() )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATHISTORYDIALOG_VERBOSE
|
|
kDebug() << "Filtering out conversation on:" << date << ">" << toDate_->date()
|
|
<< "and all later ones";
|
|
#endif
|
|
// We're at the last possible chat, skip the rest
|
|
break;
|
|
}
|
|
|
|
// Add all messages of this conversation to the output
|
|
const QDomNodeList &messages = conversation.childNodes();
|
|
for( int index = 0; index < messages.size(); ++index )
|
|
{
|
|
log << messages.at( index );
|
|
}
|
|
|
|
// Without this, the data won't be fully sent to the byte array by the time setXml is called.
|
|
log.flush();
|
|
}
|
|
|
|
// Add the closing tag
|
|
log << "</messageRoot>";
|
|
}
|
|
else
|
|
{
|
|
kWarning() << "UI error, no chat selection choice was active!";
|
|
reloadingLogs_ = false;
|
|
return;
|
|
}
|
|
|
|
chatView_->setXml( xmlData );
|
|
chatView_->widget()->setEnabled( true );
|
|
|
|
#ifdef KMESSDEBUG_CHATHISTORYDIALOG
|
|
kDebug() << "Reload done.";
|
|
#endif
|
|
|
|
reloadingLogs_ = false;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Set the contact for whom the logs will be initially shown
|
|
*
|
|
* @param handle the handle to get contact history for
|
|
* @returns whether there was history for the given contact contact
|
|
*/
|
|
bool ChatHistoryDialog::setContact( const QString &handle )
|
|
{
|
|
if( handle.isEmpty() )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATHISTORYDIALOG
|
|
kDebug() << "Handle is empty, pretending to have logs for the contact.";
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
QList<QStandardItem*> items = model_->findItems( handle, Qt::MatchContains );
|
|
|
|
if( items.isEmpty() )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATHISTORYDIALOG
|
|
kDebug() << handle << "has no chat history";
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
listView_->setCurrentIndex( proxyModel_->mapFromSource( items.first()->index() ) );
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
// Set whether the dialog is loading chats or not
|
|
void ChatHistoryDialog::setLoading( bool isLoading )
|
|
{
|
|
if( isLoading )
|
|
{
|
|
// Show the loading animation
|
|
loadingLabel_->show();
|
|
loadingLabel_->start();
|
|
|
|
// Clear the chooser
|
|
conversationComboBox_->clear();
|
|
conversationComboBox_->insertItem( 0, i18nc( "Combo box default item", "Loading..." ), -1 );
|
|
|
|
// Clear the view
|
|
chatView_->clearView( true );
|
|
|
|
#ifdef KMESSDEBUG_CHATHISTORYDIALOG
|
|
kDebug() << "Now loading...";
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
// Hide the loading animation
|
|
loadingLabel_->stop();
|
|
loadingLabel_->hide();
|
|
|
|
if( conversationComboBox_->count() < 1
|
|
|| conversations_.isEmpty() )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATHISTORYDIALOG
|
|
kDebug() << "No more loading - list empty.";
|
|
#endif
|
|
conversationComboBox_->clear();
|
|
conversationComboBox_->insertItem( 0, i18nc( "Combo box default item", "No logged chats" ), -1 );
|
|
}
|
|
else if( conversationComboBox_->itemData( 0, Qt::UserRole ) == -1 )
|
|
{
|
|
conversationComboBox_->removeItem( 0 );
|
|
#ifdef KMESSDEBUG_CHATHISTORYDIALOG
|
|
kDebug() << "No more loading - removed temp item.";
|
|
#endif
|
|
}
|
|
#ifdef KMESSDEBUG_CHATHISTORYDIALOG
|
|
else
|
|
{
|
|
kDebug() << "Wtf?";
|
|
}
|
|
#endif
|
|
}
|
|
|
|
chatView_->widget()->setEnabled( ! isLoading );
|
|
}
|
|
|
|
|
|
|
|
|
|
// Worker thread's constructor
|
|
XmlLogParser::XmlLogParser( bool showDate, bool showTime, bool showSeconds )
|
|
: end_( false )
|
|
, showDate_( showDate )
|
|
, showTime_( showTime )
|
|
, showSeconds_( showSeconds )
|
|
{
|
|
}
|
|
|
|
|
|
|
|
// Add another document to the worker thread's queue
|
|
void XmlLogParser::addDocument( const QString &fileName, const QByteArray &newDocument )
|
|
{
|
|
documents_[ fileName ] = newDocument;
|
|
}
|
|
|
|
|
|
|
|
// Stop the worker thread
|
|
void XmlLogParser::end()
|
|
{
|
|
disconnect( this );
|
|
|
|
end_ = true;
|
|
// quit() here makes the threads exec() return, so it effectively "detaches"
|
|
// from the main program; however, the run() method stays running until it's
|
|
// done with the current document and it notices end_ is true. If that takes
|
|
// more than the 5 seconds (in the wait() call below), it is terminated.
|
|
quit();
|
|
|
|
// Wait 5s for the thread to finish: if it's not enough, kill it
|
|
wait( 5000 );
|
|
if( isRunning() )
|
|
{
|
|
terminate();
|
|
}
|
|
|
|
deleteLater();
|
|
}
|
|
|
|
|
|
|
|
// Worker thread's method to parse XML files into conversations
|
|
void XmlLogParser::run()
|
|
{
|
|
#ifdef KMESSDEBUG_CHATHISTORYDIALOG_VERBOSE
|
|
kDebug() << "Initially present:" << documents_.count() << "documents.";
|
|
#endif
|
|
|
|
while( ! documents_.isEmpty() )
|
|
{
|
|
// take the first document from the hash for processing
|
|
QHash<const QString,QByteArray>::iterator it = documents_.begin();
|
|
const QString fileName = it.key();
|
|
const QByteArray documentBytes = documents_.take( fileName );
|
|
|
|
QDomDocument document;
|
|
document.setContent( documentBytes );
|
|
|
|
#ifdef KMESSDEBUG_CHATHISTORYDIALOG_VERBOSE
|
|
kDebug() << "Parsing document from XML file:" << fileName;
|
|
#endif
|
|
|
|
// There is no message root in this XML file
|
|
QDomNode messageRoot( document.firstChildElement( "messageRoot" ) );
|
|
if( messageRoot.isNull() )
|
|
{
|
|
kWarning() << "Invalid XML file!";
|
|
continue;
|
|
}
|
|
|
|
// Since in the chat logs only the message timestamps are saved, add to each message
|
|
// the date and time in the current format
|
|
const QDomNodeList &messages = document.elementsByTagName( "message" );
|
|
#ifdef KMESSDEBUG_CHATHISTORYDIALOG_VERBOSE
|
|
kDebug() << messages.size() << "messages to parse.";
|
|
#endif
|
|
for( int indexMsg = 0; indexMsg < messages.size(); ++indexMsg )
|
|
{
|
|
QDomElement message( messages.at( indexMsg ).toElement() );
|
|
int timestamp = message.attribute( "timestamp" ).toUInt();
|
|
const QDateTime &datetime = QDateTime::fromTime_t( timestamp );
|
|
|
|
if( ! message.hasAttribute( "timestamp" ) || timestamp == 0 )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( showDate_ )
|
|
{
|
|
message.setAttribute( "time", KGlobal::locale()->formatDateTime( datetime, KLocale::ShortDate, showSeconds_ ) );
|
|
}
|
|
else
|
|
{
|
|
message.setAttribute( "time", KGlobal::locale()->formatTime( datetime.time(), showSeconds_ ) );
|
|
}
|
|
}
|
|
#ifdef KMESSDEBUG_CHATHISTORYDIALOG_VERBOSE
|
|
kDebug() << messages.size() << "messages parsed.";
|
|
#endif
|
|
|
|
// Add all conversations from this file to the cached XML and the UI
|
|
const QDomNodeList &fileConversations = messageRoot.childNodes();
|
|
for( int index = 0; index < fileConversations.size(); ++index )
|
|
{
|
|
QDomElement conversation( fileConversations.at( index ).toElement() );
|
|
|
|
if( conversation.nodeName() != "conversation" )
|
|
{
|
|
kWarning() << "Unexpected element" << conversation.nodeName() << "found in the XML log:"
|
|
<< fileName;
|
|
continue;
|
|
}
|
|
|
|
|
|
// Check if the <conversation> has a valid timestamp
|
|
const QDomNode ×tampNode = conversation.attributes().namedItem( "timestamp" );
|
|
const int timestamp = timestampNode.nodeValue().toInt();
|
|
|
|
if( timestampNode.isNull() )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATHISTORYDIALOG
|
|
kWarning() << "Invalid XML: invalid timestamp!";
|
|
#endif
|
|
continue;
|
|
}
|
|
|
|
// Change it to a <messageRoot> so that it'll be readily usable when selected
|
|
conversation.setTagName( "messageRoot" );
|
|
// conversation.removeAttribute( "timestamp" ); // Ignored by ChatMessageView?
|
|
|
|
emit gotConversation( timestamp, conversation );
|
|
}
|
|
|
|
// if we're supposed to stop processing now, just stop.
|
|
if( end_ )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATHISTORYDIALOG_VERBOSE
|
|
kDebug() << "Caught end, closing.";
|
|
#endif
|
|
return;
|
|
}
|
|
}
|
|
|
|
#ifdef KMESSDEBUG_CHATHISTORYDIALOG_VERBOSE
|
|
kDebug() << "All documents have been parsed.";
|
|
#endif
|
|
}
|
|
|
|
|
|
// The user right clicked at the KHTMLPart to show a popup.
|
|
void ChatHistoryDialog::slotShowContextMenu( const QString &clickedUrl, const QPoint &point )
|
|
{
|
|
Q_UNUSED( clickedUrl )
|
|
|
|
KMenu *popup = chatView_->popupMenu();
|
|
popup->exec( point );
|
|
delete popup;
|
|
}
|
|
|
|
#include "chathistorydialog.moc"
|