Files
kmess/src/dialogs/chathistorydialog.cpp
T
2026-07-06 09:37:59 +02:00

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 &currentHandle( 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>&quot;%1&quot;</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;
}