summaryrefslogtreecommitdiffstats
path: root/kopete/plugins/history
diff options
context:
space:
mode:
Diffstat (limited to 'kopete/plugins/history')
-rw-r--r--kopete/plugins/history/Makefile.am26
-rw-r--r--kopete/plugins/history/converter.cpp341
-rw-r--r--kopete/plugins/history/historychatui.rc17
-rw-r--r--kopete/plugins/history/historyconfig.kcfg35
-rw-r--r--kopete/plugins/history/historyconfig.kcfgc7
-rw-r--r--kopete/plugins/history/historydialog.cpp613
-rw-r--r--kopete/plugins/history/historydialog.h146
-rw-r--r--kopete/plugins/history/historyguiclient.cpp115
-rw-r--r--kopete/plugins/history/historyguiclient.h55
-rw-r--r--kopete/plugins/history/historylogger.cpp851
-rw-r--r--kopete/plugins/history/historylogger.h217
-rw-r--r--kopete/plugins/history/historyplugin.cpp194
-rw-r--r--kopete/plugins/history/historyplugin.h106
-rw-r--r--kopete/plugins/history/historypreferences.cpp88
-rw-r--r--kopete/plugins/history/historypreferences.h48
-rw-r--r--kopete/plugins/history/historyprefsui.ui187
-rw-r--r--kopete/plugins/history/historyui.rc12
-rw-r--r--kopete/plugins/history/historyviewer.ui347
-rw-r--r--kopete/plugins/history/kopete_history.desktop139
-rw-r--r--kopete/plugins/history/kopete_history_config.desktop141
20 files changed, 3685 insertions, 0 deletions
diff --git a/kopete/plugins/history/Makefile.am b/kopete/plugins/history/Makefile.am
new file mode 100644
index 00000000..765e5197
--- /dev/null
+++ b/kopete/plugins/history/Makefile.am
@@ -0,0 +1,26 @@
+METASOURCES = AUTO
+
+INCLUDES = $(KOPETE_INCLUDES) $(all_includes)
+
+kde_module_LTLIBRARIES = kopete_history.la kcm_kopete_history.la
+
+kopete_history_la_SOURCES = historyplugin.cpp historydialog.cpp historyviewer.ui\
+ historylogger.cpp converter.cpp historyguiclient.cpp historyconfig.kcfgc
+
+kopete_history_la_LDFLAGS = -module -no-undefined $(KDE_PLUGIN) $(all_libraries)
+kopete_history_la_LIBADD = ../../libkopete/libkopete.la
+
+kcm_kopete_history_la_SOURCES = historyprefsui.ui historypreferences.cpp historyconfig.kcfgc
+kcm_kopete_history_la_LDFLAGS = -module -no-undefined $(KDE_PLUGIN) $(all_libraries)
+kcm_kopete_history_la_LIBADD = ../../libkopete/libkopete.la $(LIB_KUTILS)
+
+service_DATA = kopete_history.desktop
+servicedir = $(kde_servicesdir)
+
+mydatadir = $(kde_datadir)/kopete_history
+mydata_DATA = historyui.rc historychatui.rc
+
+kcm_DATA = kopete_history_config.desktop
+kcmdir = $(kde_servicesdir)/kconfiguredialog
+
+kde_kcfg_DATA = historyconfig.kcfg
diff --git a/kopete/plugins/history/converter.cpp b/kopete/plugins/history/converter.cpp
new file mode 100644
index 00000000..22f662bc
--- /dev/null
+++ b/kopete/plugins/history/converter.cpp
@@ -0,0 +1,341 @@
+//Olivier Goffart <ogoffart @ kde.org>
+// 2003 06 26
+
+#include "historyplugin.h" //just needed because we are a member of this class
+ // we don't use any history function here
+
+/**-----------------------------------------------------------
+ * CONVERTER from the old kopete history.
+ * it port history from kopete 0.6, 0.5 and above the actual
+ * this should be placed in a perl script handled by KConf_update
+ * but i need to acess to some info i don't have with perl, like
+ * the accountId, to know each protocol id, and more
+ *-----------------------------------------------------------*/
+
+#include "kopetepluginmanager.h"
+#include "kopeteaccount.h"
+#include "kopeteaccountmanager.h"
+#include "kopetecontact.h"
+#include "kopetemessage.h"
+#include "kopeteprotocol.h"
+#include "kopeteuiglobal.h"
+
+#include <kconfig.h>
+#include <kdebug.h>
+#include <klocale.h>
+#include <kstandarddirs.h>
+#include <kmessagebox.h>
+#include <kprogress.h>
+#include <kapplication.h>
+#include <ksavefile.h>
+#include <qdir.h>
+#include <qdom.h>
+#include <qregexp.h>
+
+#define CBUFLENGTH 512 // buffer length for fgets()
+
+void HistoryPlugin::convertOldHistory()
+{
+ bool deleteFiles= KMessageBox::questionYesNo( Kopete::UI::Global::mainWidget(),
+ i18n( "Would you like to remove old history files?" ) , i18n( "History Converter" ), KStdGuiItem::del(), i18n("Keep") ) == KMessageBox::Yes;
+
+ KProgressDialog *progressDlg=new KProgressDialog(Kopete::UI::Global::mainWidget() , "history_progress_dlg" , i18n( "History converter" ) ,
+ QString::null , true); //modal to make sure the user will not doing stupid things (we have a kapp->processEvents())
+ progressDlg->setAllowCancel(false); //because i am too lazy to allow to cancel
+
+
+ QString kopetedir=locateLocal( "data", QString::fromLatin1( "kopete"));
+ QDir d( kopetedir ); //d should point to ~/.kde/share/apps/kopete/
+
+ d.setFilter( QDir::Dirs );
+
+ const QFileInfoList *list = d.entryInfoList();
+ QFileInfoListIterator it( *list );
+ QFileInfo *fi;
+ while ( (fi = it.current()) != 0 )
+ {
+ QString protocolId;
+ QString accountId;
+
+ if( Kopete::Protocol *p = dynamic_cast<Kopete::Protocol *>( Kopete::PluginManager::self()->plugin( fi->fileName() ) ) )
+ {
+ protocolId=p->pluginId();
+ QDictIterator<Kopete::Account> it(Kopete::AccountManager::self()->accounts(p));
+ Kopete::Account *a = it.current();
+ if(a)
+ accountId=a->accountId();
+ }
+
+ if(accountId.isNull() || protocolId.isNull())
+ {
+ if(fi->fileName() == "MSNProtocol" || fi->fileName() == "msn_logs" )
+ {
+ protocolId="MSNProtocol";
+ KGlobal::config()->setGroup("MSN");
+ accountId=KGlobal::config()->readEntry( "UserID" );
+ }
+ else if(fi->fileName() == "ICQProtocol" || fi->fileName() == "icq_logs" )
+ {
+ protocolId="ICQProtocol";
+ KGlobal::config()->setGroup("ICQ");
+ accountId=KGlobal::config()->readEntry( "UIN" );
+ }
+ else if(fi->fileName() == "AIMProtocol" || fi->fileName() == "aim_logs" )
+ {
+ protocolId="AIMProtocol";
+ KGlobal::config()->setGroup("AIM");
+ accountId=KGlobal::config()->readEntry( "UserID" );
+ }
+ else if(fi->fileName() == "OscarProtocol" )
+ {
+ protocolId="AIMProtocol";
+ KGlobal::config()->setGroup("OSCAR");
+ accountId=KGlobal::config()->readEntry( "UserID" );
+ }
+ else if(fi->fileName() == "JabberProtocol" || fi->fileName() == "jabber_logs")
+ {
+ protocolId="JabberProtocol";
+ KGlobal::config()->setGroup("Jabber");
+ accountId=KGlobal::config()->readEntry( "UserID" );
+ }
+ //TODO: gadu, wp
+ }
+
+ if(!protocolId.isEmpty() || !accountId.isEmpty())
+ {
+ QDir d2( fi->absFilePath() );
+ d2.setFilter( QDir::Files );
+ d2.setNameFilter("*.log");
+ const QFileInfoList *list = d2.entryInfoList();
+ QFileInfoListIterator it2( *list );
+ QFileInfo *fi2;
+
+ progressDlg->progressBar()->reset();
+ progressDlg->progressBar()->setTotalSteps(d2.count());
+ progressDlg->setLabel(i18n("Parsing old history in %1").arg(fi->fileName()));
+ progressDlg->show(); //if it was not already showed...
+
+ while ( (fi2 = it2.current()) != 0 )
+ {
+ //we assume that all "-" are dots. (like in hotmail.com)
+ QString contactId=fi2->fileName().replace(".log" , QString::null).replace("-" , ".");
+
+ if(!contactId.isEmpty() )
+ {
+ progressDlg->setLabel(i18n("Parsing old history in %1:\n%2").arg(fi->fileName()).arg(contactId));
+ kapp->processEvents(0); //make sure the text is updated in the progressDlg
+
+ int month=0;
+ int year=0;
+ QDomDocument doc;
+ QDomElement docElem;
+
+ QDomElement msgelement;
+ QDomNode node;
+ QDomDocument xmllist;
+ Kopete::Message::MessageDirection dir;
+ QString body, date, nick;
+ QString buffer, msgBlock;
+ char cbuf[CBUFLENGTH]; // buffer for the log file
+
+ QString logFileName = fi2->absFilePath();
+
+ // open the file
+ FILE *f = fopen(QFile::encodeName(logFileName), "r");
+
+ // create a new <message> block
+ while ( ! feof( f ) )
+ {
+ fgets(cbuf, CBUFLENGTH, f);
+ buffer = QString::fromUtf8(cbuf);
+
+ while ( strchr(cbuf, '\n') == NULL && !feof(f) )
+ {
+ fgets( cbuf, CBUFLENGTH, f );
+ buffer += QString::fromUtf8(cbuf);
+ }
+
+ if( buffer.startsWith( QString::fromLatin1( "<message " ) ) )
+ {
+ msgBlock = buffer;
+
+ // find the end of the message block
+ while( !feof( f ) && buffer != QString::fromLatin1( "</message>\n" ) /*strcmp("</message>\n", cbuf )*/ )
+ {
+ fgets(cbuf, CBUFLENGTH, f);
+ buffer = QString::fromUtf8(cbuf);
+
+ while ( strchr(cbuf, '\n') == NULL && !feof(f) )
+ {
+ fgets( cbuf, CBUFLENGTH, f );
+ buffer += QString::fromUtf8(cbuf);
+ }
+ msgBlock.append(buffer);
+ }
+
+ // now let's work on this new block
+ xmllist.setContent(msgBlock, false);
+ msgelement = xmllist.documentElement();
+ node = msgelement.firstChild();
+
+ if( msgelement.attribute( QString::fromLatin1( "direction" ) ) == QString::fromLatin1( "inbound" ) )
+ dir = Kopete::Message::Inbound;
+ else
+ dir = Kopete::Message::Outbound;
+
+ // Read all the elements.
+ QString tagname;
+ QDomElement element;
+
+ while ( ! node.isNull() )
+ {
+ if ( node.isElement() )
+ {
+ element = node.toElement();
+ tagname = element.tagName();
+
+ if( tagname == QString::fromLatin1( "srcnick" ) )
+ nick = element.text();
+
+ else if( tagname == QString::fromLatin1( "date" ) )
+ date = element.text();
+ else if( tagname == QString::fromLatin1( "body" ) )
+ body = element.text().stripWhiteSpace();
+ }
+
+ node = node.nextSibling();
+ }
+ //FIXME!! The date in logs writed with kopete running with QT 3.0 is Localised.
+ // so QT can't parse it correctly.
+ QDateTime dt=QDateTime::fromString(date);
+ if(dt.date().month() != month || dt.date().year() != year)
+ {
+ if(!docElem.isNull())
+ {
+ QDate date(year,month,1);
+ QString name = protocolId.replace( QRegExp( QString::fromLatin1( "[./~?*]" ) ), QString::fromLatin1( "-" ) ) +
+ QString::fromLatin1( "/" ) +
+ contactId.replace( QRegExp( QString::fromLatin1( "[./~?*]" ) ), QString::fromLatin1( "-" ) ) +
+ date.toString(".yyyyMM");
+ KSaveFile file( locateLocal( "data", QString::fromLatin1( "kopete/logs/" ) + name+ QString::fromLatin1( ".xml" ) ) );
+ if( file.status() == 0 )
+ {
+ QTextStream *stream = file.textStream();
+ //stream->setEncoding( QTextStream::UnicodeUTF8 ); //???? oui ou non?
+ doc.save( *stream , 1 );
+ file.close();
+ }
+ }
+
+
+ month=dt.date().month();
+ year=dt.date().year();
+ docElem=QDomElement();
+ }
+
+ if(docElem.isNull())
+ {
+ doc=QDomDocument("Kopete-History");
+ docElem= doc.createElement( "kopete-history" );
+ docElem.setAttribute ( "version" , "0.7" );
+ doc.appendChild( docElem );
+ QDomElement headElem = doc.createElement( "head" );
+ docElem.appendChild( headElem );
+ QDomElement dateElem = doc.createElement( "date" );
+ dateElem.setAttribute( "year", QString::number(year) );
+ dateElem.setAttribute( "month", QString::number(month) );
+ headElem.appendChild(dateElem);
+ QDomElement myselfElem = doc.createElement( "contact" );
+ myselfElem.setAttribute( "type", "myself" );
+ myselfElem.setAttribute( "contactId", accountId );
+ headElem.appendChild(myselfElem);
+ QDomElement contactElem = doc.createElement( "contact" );
+ contactElem.setAttribute( "contactId", contactId );
+ headElem.appendChild(contactElem);
+ QDomElement importElem = doc.createElement( "imported" );
+ importElem.setAttribute( "from", fi->fileName() );
+ importElem.setAttribute( "date", QDateTime::currentDateTime().toString() );
+ headElem.appendChild(importElem);
+ }
+ QDomElement msgElem = doc.createElement( "msg" );
+ msgElem.setAttribute( "in", dir==Kopete::Message::Outbound ? "0" : "1" );
+ msgElem.setAttribute( "from", dir==Kopete::Message::Outbound ? accountId : contactId );
+ msgElem.setAttribute( "nick", nick ); //do we have to set this?
+ msgElem.setAttribute( "time", QString::number(dt.date().day()) + " " + QString::number(dt.time().hour()) + ":" + QString::number(dt.time().minute()) );
+ QDomText msgNode = doc.createTextNode( body.stripWhiteSpace() );
+ docElem.appendChild( msgElem );
+ msgElem.appendChild( msgNode );
+ }
+ }
+
+ fclose( f );
+ if(deleteFiles)
+ d2.remove(fi2->fileName() , false);
+
+ if(!docElem.isNull())
+ {
+ QDate date(year,month,1);
+ QString name = protocolId.replace( QRegExp( QString::fromLatin1( "[./~?*]" ) ), QString::fromLatin1( "-" ) ) +
+ QString::fromLatin1( "/" ) +
+ contactId.replace( QRegExp( QString::fromLatin1( "[./~?*]" ) ), QString::fromLatin1( "-" ) ) +
+ date.toString(".yyyyMM");
+ KSaveFile file( locateLocal( "data", QString::fromLatin1( "kopete/logs/" ) + name+ QString::fromLatin1( ".xml" ) ) );
+ if( file.status() == 0 )
+ {
+ QTextStream *stream = file.textStream();
+ //stream->setEncoding( QTextStream::UnicodeUTF8 ); //???? oui ou non?
+ doc.save( *stream ,1 );
+ file.close();
+ }
+ }
+
+ }
+ progressDlg->progressBar()->setProgress(progressDlg->progressBar()->progress()+1);
+ ++it2;
+ }
+ }
+ ++it;
+ }
+ delete progressDlg;
+
+}
+
+
+bool HistoryPlugin::detectOldHistory()
+{
+ KGlobal::config()->setGroup("History Plugin");
+ QString version=KGlobal::config()->readEntry( "Version" ,"0.6" );
+
+ if(version != "0.6")
+ return false;
+
+
+ QDir d( locateLocal( "data", QString::fromLatin1( "kopete/logs")) );
+ d.setFilter( QDir::Dirs );
+ if(d.count() >= 3) // '.' and '..' are included
+ return false; //the new history already exists
+
+ QDir d2( locateLocal( "data", QString::fromLatin1( "kopete")) );
+ d2.setFilter( QDir::Dirs );
+ const QFileInfoList *list = d2.entryInfoList();
+ QFileInfoListIterator it( *list );
+ QFileInfo *fi;
+ while ( (fi = it.current()) != 0 )
+ {
+ if( dynamic_cast<Kopete::Protocol *>( Kopete::PluginManager::self()->plugin( fi->fileName() ) ) )
+ return true;
+
+ if(fi->fileName() == "MSNProtocol" || fi->fileName() == "msn_logs" )
+ return true;
+ else if(fi->fileName() == "ICQProtocol" || fi->fileName() == "icq_logs" )
+ return true;
+ else if(fi->fileName() == "AIMProtocol" || fi->fileName() == "aim_logs" )
+ return true;
+ else if(fi->fileName() == "OscarProtocol" )
+ return true;
+ else if(fi->fileName() == "JabberProtocol" || fi->fileName() == "jabber_logs")
+ return true;
+ ++it;
+ }
+ return false;
+}
diff --git a/kopete/plugins/history/historychatui.rc b/kopete/plugins/history/historychatui.rc
new file mode 100644
index 00000000..2f49392f
--- /dev/null
+++ b/kopete/plugins/history/historychatui.rc
@@ -0,0 +1,17 @@
+<!DOCTYPE kpartgui>
+<kpartgui version="19" name="kopetechatwindow">
+ <MenuBar>
+ <Menu name="tools" >
+ <text>&amp;Tools</text>
+ <Action name="historyPrevious" />
+ <Action name="historyNext" />
+ <Action name="historyLast" />
+ </Menu>
+ </MenuBar>
+
+ <ToolBar name="mainToolBar" fullWidth="true">
+ <Action name="historyPrevious" />
+ <Action name="historyNext" />
+ </ToolBar>
+
+</kpartgui>
diff --git a/kopete/plugins/history/historyconfig.kcfg b/kopete/plugins/history/historyconfig.kcfg
new file mode 100644
index 00000000..58e6c9d2
--- /dev/null
+++ b/kopete/plugins/history/historyconfig.kcfg
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Author: Stefan Gehn -->
+<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0
+ http://www.kde.org/standards/kcfg/1.0/kcfg.xsd" >
+ <kcfgfile name="kopeterc"/>
+
+ <group name="History Plugin">
+ <entry name="Auto_chatwindow" type="Bool">
+ <label>Show previous messages in new chats.</label>
+ <default>false</default>
+ </entry>
+
+ <entry name="Number_Auto_chatwindow" type="UInt">
+ <label>Number of messages to show.</label>
+ <default>7</default>
+ </entry>
+
+ <entry name="Number_ChatWindow" type="UInt">
+ <label>Number of messages per page</label>
+ <default>20</default>
+ </entry>
+
+ <entry name="History_color" type="Color">
+ <label>Color of messages</label>
+ <default>170, 170, 127</default>
+ </entry>
+
+ <entry name="BrowserStyle" type="Path">
+ <label>Style to use in history-browser.</label>
+ </entry>
+
+ </group>
+</kcfg>
diff --git a/kopete/plugins/history/historyconfig.kcfgc b/kopete/plugins/history/historyconfig.kcfgc
new file mode 100644
index 00000000..1e985622
--- /dev/null
+++ b/kopete/plugins/history/historyconfig.kcfgc
@@ -0,0 +1,7 @@
+# Code generation options for kconfig_compiler
+File=historyconfig.kcfg
+ClassName=HistoryConfig
+Singleton=true
+Mutators=true
+MemberVariables=private
+GlobalEnums=true
diff --git a/kopete/plugins/history/historydialog.cpp b/kopete/plugins/history/historydialog.cpp
new file mode 100644
index 00000000..4dd98fee
--- /dev/null
+++ b/kopete/plugins/history/historydialog.cpp
@@ -0,0 +1,613 @@
+/*
+ kopetehistorydialog.cpp - Kopete History Dialog
+
+ Copyright (c) 2002 by Richard Stellingwerff <remenic@linuxfromscratch.org>
+ Copyright (c) 2004 by Stefan Gehn <metz AT gehn.net>
+
+ Kopete (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org>
+
+ *************************************************************************
+ * *
+ * 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 "historydialog.h"
+#include "historylogger.h"
+#include "historyviewer.h"
+#include "kopetemetacontact.h"
+#include "kopeteprotocol.h"
+#include "kopeteaccount.h"
+#include "kopetecontactlist.h"
+#include "kopeteprefs.h"
+
+#include <dom/dom_doc.h>
+#include <dom/dom_element.h>
+#include <dom/html_document.h>
+#include <dom/html_element.h>
+#include <khtml_part.h>
+#include <khtmlview.h>
+
+#include <qpushbutton.h>
+#include <qlineedit.h>
+#include <qcheckbox.h>
+#include <qlayout.h>
+#include <qdir.h>
+#include <qdatetime.h>
+#include <qheader.h>
+#include <qlabel.h>
+#include <qclipboard.h>
+
+#include <kapplication.h>
+#include <kdebug.h>
+#include <kiconloader.h>
+#include <klocale.h>
+#include <krun.h>
+#include <kstandarddirs.h>
+#include <klistview.h>
+#include <klistviewsearchline.h>
+#include <kprogress.h>
+#include <kiconloader.h>
+#include <kcombobox.h>
+#include <kpopupmenu.h>
+#include <kstdaction.h>
+#include <kaction.h>
+
+class KListViewDateItem : public KListViewItem
+{
+public:
+ KListViewDateItem(KListView* parent, QDate date, Kopete::MetaContact *mc);
+ QDate date() { return mDate; }
+ Kopete::MetaContact *metaContact() { return mMetaContact; }
+
+public:
+ int compare(QListViewItem *i, int col, bool ascending) const;
+private:
+ QDate mDate;
+ Kopete::MetaContact *mMetaContact;
+};
+
+
+
+KListViewDateItem::KListViewDateItem(KListView* parent, QDate date, Kopete::MetaContact *mc)
+ : KListViewItem(parent, date.toString(Qt::ISODate), mc->displayName())
+{
+ mDate = date;
+ mMetaContact = mc;
+}
+
+int KListViewDateItem::compare(QListViewItem *i, int col, bool ascending) const
+{
+ if (col)
+ return QListViewItem::compare(i, col, ascending);
+
+ //compare dates - do NOT use ascending var here
+ KListViewDateItem* item = static_cast<KListViewDateItem*>(i);
+ if ( mDate < item->date() )
+ return -1;
+ return ( mDate > item->date() );
+}
+
+
+HistoryDialog::HistoryDialog(Kopete::MetaContact *mc, QWidget* parent,
+ const char* name) : KDialogBase(parent, name, false,
+ i18n("History for %1").arg(mc->displayName()), 0), mSearching(false)
+{
+ QString fontSize;
+ QString htmlCode;
+ QString fontStyle;
+
+ kdDebug(14310) << k_funcinfo << "called." << endl;
+ setWFlags(Qt::WDestructiveClose); // send SIGNAL(closing()) on quit
+
+ // FIXME: Allow to show this dialog for only one contact
+ mMetaContact = mc;
+
+
+
+ // Widgets initializations
+ mMainWidget = new HistoryViewer(this, "HistoryDialog::mMainWidget");
+ mMainWidget->searchLine->setFocus();
+ mMainWidget->searchLine->setTrapReturnKey (true);
+ mMainWidget->searchLine->setTrapReturnKey(true);
+ mMainWidget->searchErase->setPixmap(BarIcon("locationbar_erase"));
+
+ mMainWidget->contactComboBox->insertItem(i18n("All"));
+ mMetaContactList = Kopete::ContactList::self()->metaContacts();
+ QPtrListIterator<Kopete::MetaContact> it(mMetaContactList);
+ for(; it.current(); ++it)
+ {
+ mMainWidget->contactComboBox->insertItem((*it)->displayName());
+ }
+
+ if (mMetaContact)
+ mMainWidget->contactComboBox->setCurrentItem(mMetaContactList.find(mMetaContact)+1);
+
+ mMainWidget->dateSearchLine->setListView(mMainWidget->dateListView);
+ mMainWidget->dateListView->setSorting(0, 0); //newest-first
+
+ setMainWidget(mMainWidget);
+
+ // Initializing HTML Part
+ mMainWidget->htmlFrame->setFrameStyle(QFrame::WinPanel | QFrame::Sunken);
+ QVBoxLayout *l = new QVBoxLayout(mMainWidget->htmlFrame);
+ mHtmlPart = new KHTMLPart(mMainWidget->htmlFrame, "htmlHistoryView");
+
+ //Security settings, we don't need this stuff
+ mHtmlPart->setJScriptEnabled(false);
+ mHtmlPart->setJavaEnabled(false);
+ mHtmlPart->setPluginsEnabled(false);
+ mHtmlPart->setMetaRefreshEnabled(false);
+ mHtmlPart->setOnlyLocalReferences(true);
+
+ mHtmlView = mHtmlPart->view();
+ mHtmlView->setMarginWidth(4);
+ mHtmlView->setMarginHeight(4);
+ mHtmlView->setFocusPolicy(NoFocus);
+ mHtmlView->setSizePolicy(
+ QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding));
+ l->addWidget(mHtmlView);
+
+ QTextOStream( &fontSize ) << KopetePrefs::prefs()->fontFace().pointSize();
+ fontStyle = "<style>.hf { font-size:" + fontSize + ".0pt; font-family:" + KopetePrefs::prefs()->fontFace().family() + "; color: " + KopetePrefs::prefs()->textColor().name() + "; }</style>";
+
+ mHtmlPart->begin();
+ htmlCode = "<html><head>" + fontStyle + "</head><body class=\"hf\"></body></html>";
+ mHtmlPart->write( QString::fromLatin1( htmlCode.latin1() ) );
+ mHtmlPart->end();
+
+
+ connect(mHtmlPart->browserExtension(), SIGNAL(openURLRequestDelayed(const KURL &, const KParts::URLArgs &)),
+ this, SLOT(slotOpenURLRequest(const KURL &, const KParts::URLArgs &)));
+ connect(mMainWidget->dateListView, SIGNAL(clicked(QListViewItem*)), this, SLOT(dateSelected(QListViewItem*)));
+ connect(mMainWidget->searchButton, SIGNAL(clicked()), this, SLOT(slotSearch()));
+ connect(mMainWidget->searchLine, SIGNAL(returnPressed()), this, SLOT(slotSearch()));
+ connect(mMainWidget->searchLine, SIGNAL(textChanged(const QString&)), this, SLOT(slotSearchTextChanged(const QString&)));
+ connect(mMainWidget->searchErase, SIGNAL(clicked()), this, SLOT(slotSearchErase()));
+ connect(mMainWidget->contactComboBox, SIGNAL(activated(int)), this, SLOT(slotContactChanged(int)));
+ connect(mMainWidget->messageFilterBox, SIGNAL(activated(int)), this, SLOT(slotFilterChanged(int )));
+ connect(mHtmlPart, SIGNAL(popupMenu(const QString &, const QPoint &)), this, SLOT(slotRightClick(const QString &, const QPoint &)));
+
+ //initActions
+ KActionCollection* ac = new KActionCollection(this);
+ mCopyAct = KStdAction::copy( this, SLOT(slotCopy()), ac );
+ mCopyURLAct = new KAction( i18n( "Copy Link Address" ), QString::fromLatin1( "editcopy" ), 0, this, SLOT( slotCopyURL() ), ac );
+
+ resize(650, 700);
+ centerOnScreen(this);
+
+ // show the dialog before people get impatient
+ show();
+
+ // Load history dates in the listview
+ init();
+}
+
+HistoryDialog::~HistoryDialog()
+{
+ mSearching = false;
+}
+
+void HistoryDialog::init()
+{
+ if(mMetaContact)
+ {
+ HistoryLogger logger(mMetaContact, this);
+ init(mMetaContact);
+ }
+ else
+ {
+ QPtrListIterator<Kopete::MetaContact> it(mMetaContactList);
+ for(; it.current(); ++it)
+ {
+ HistoryLogger logger(*it, this);
+ init(*it);
+ }
+
+ }
+
+ initProgressBar(i18n("Loading..."),mInit.dateMCList.count());
+ QTimer::singleShot(0,this,SLOT(slotLoadDays()));
+}
+
+void HistoryDialog::slotLoadDays()
+{
+ if(mInit.dateMCList.isEmpty())
+ {
+ if (!mMainWidget->searchLine->text().isEmpty())
+ QTimer::singleShot(0, this, SLOT(slotSearch()));
+ doneProgressBar();
+ return;
+ }
+
+ DMPair pair(mInit.dateMCList.first());
+ mInit.dateMCList.pop_front();
+ HistoryLogger logger(pair.metaContact(), this);
+ QValueList<int> dayList = logger.getDaysForMonth(pair.date());
+ for (unsigned int i=0; i<dayList.count(); i++)
+ {
+ QDate c2Date(pair.date().year(),pair.date().month(),dayList[i]);
+ if (mInit.dateMCList.find(pair) == mInit.dateMCList.end())
+ new KListViewDateItem(mMainWidget->dateListView, c2Date, pair.metaContact());
+ }
+ mMainWidget->searchProgress->advance(1);
+ QTimer::singleShot(0,this,SLOT(slotLoadDays()));
+
+
+}
+
+void HistoryDialog::init(Kopete::MetaContact *mc)
+{
+ QPtrList<Kopete::Contact> contacts=mc->contacts();
+ QPtrListIterator<Kopete::Contact> it( contacts );
+
+ for( ; it.current(); ++it )
+ {
+ init(*it);
+ }
+}
+
+void HistoryDialog::init(Kopete::Contact *c)
+{
+ // Get year and month list
+ QRegExp rx( "\\.(\\d\\d\\d\\d)(\\d\\d)" );
+ const QString contact_in_filename=c->contactId().replace( QRegExp( QString::fromLatin1( "[./~?*]" ) ), QString::fromLatin1( "-" ) );
+ QFileInfo *fi;
+
+
+ // BEGIN check if there are Kopete 0.7.x
+ QDir d1(locateLocal("data",QString("kopete/logs/")+
+ c->protocol()->pluginId().replace( QRegExp(QString::fromLatin1("[./~?*]")),QString::fromLatin1("-"))
+ ));
+ d1.setFilter( QDir::Files | QDir::NoSymLinks );
+ d1.setSorting( QDir::Name );
+
+ const QFileInfoList *list1 = d1.entryInfoList();
+ if ( list1 != 0 )
+ {
+ QFileInfoListIterator it1( *list1 );
+ while ( (fi = it1.current()) != 0 )
+ {
+ if(fi->fileName().contains(contact_in_filename))
+ {
+ rx.search(fi->fileName());
+
+ QDate cDate = QDate(rx.cap(1).toInt(), rx.cap(2).toInt(), 1);
+
+ DMPair pair(cDate, c->metaContact());
+ mInit.dateMCList.append(pair);
+
+ }
+ ++it1;
+ }
+ }
+ // END of kopete 0.7.x check
+
+ QString logDir = locateLocal("data",QString("kopete/logs/")+
+ c->protocol()->pluginId().replace( QRegExp(QString::fromLatin1("[./~?*]")),QString::fromLatin1("-")) +
+ QString::fromLatin1( "/" ) +
+ c->account()->accountId().replace( QRegExp( QString::fromLatin1( "[./~?*]" ) ), QString::fromLatin1( "-" ) )
+ );
+ QDir d(logDir);
+ d.setFilter( QDir::Files | QDir::NoSymLinks );
+ d.setSorting( QDir::Name );
+ const QFileInfoList *list = d.entryInfoList();
+ if ( list != 0 )
+ {
+ QFileInfoListIterator it( *list );
+ while ( (fi = it.current()) != 0 )
+ {
+ if(fi->fileName().contains(contact_in_filename))
+ {
+
+ rx.search(fi->fileName());
+
+ // We search for an item in the list view with the same year. If then we add the month
+ QDate cDate = QDate(rx.cap(1).toInt(), rx.cap(2).toInt(), 1);
+
+ DMPair pair(cDate, c->metaContact());
+ mInit.dateMCList.append(pair);
+ }
+ ++it;
+ }
+ }
+}
+
+void HistoryDialog::dateSelected(QListViewItem* it)
+{
+ KListViewDateItem *item = static_cast<KListViewDateItem*>(it);
+
+ if (!item) return;
+
+ QDate chosenDate = item->date();
+
+ HistoryLogger logger(item->metaContact(), this);
+ QValueList<Kopete::Message> msgs=logger.readMessages(chosenDate);
+
+ setMessages(msgs);
+}
+
+void HistoryDialog::setMessages(QValueList<Kopete::Message> msgs)
+{
+ // Clear View
+ DOM::HTMLElement htmlBody = mHtmlPart->htmlDocument().body();
+ while(htmlBody.hasChildNodes())
+ htmlBody.removeChild(htmlBody.childNodes().item(htmlBody.childNodes().length() - 1));
+ // ----
+
+ QString dir = (QApplication::reverseLayout() ? QString::fromLatin1("rtl") :
+ QString::fromLatin1("ltr"));
+
+ QValueList<Kopete::Message>::iterator it = msgs.begin();
+
+
+ QString accountLabel;
+ QString resultHTML = "<b><font color=\"red\">" + (*it).timestamp().date().toString() + "</font></b><br/>";
+ DOM::HTMLElement newNode = mHtmlPart->document().createElement(QString::fromLatin1("span"));
+ newNode.setAttribute(QString::fromLatin1("dir"), dir);
+ newNode.setInnerHTML(resultHTML);
+ mHtmlPart->htmlDocument().body().appendChild(newNode);
+
+ // Populating HTML Part with messages
+ for ( it = msgs.begin(); it != msgs.end(); ++it )
+ {
+ if ( mMainWidget->messageFilterBox->currentItem() == 0
+ || ( mMainWidget->messageFilterBox->currentItem() == 1 && (*it).direction() == Kopete::Message::Inbound )
+ || ( mMainWidget->messageFilterBox->currentItem() == 2 && (*it).direction() == Kopete::Message::Outbound ) )
+ {
+ resultHTML = "";
+
+ if (accountLabel.isEmpty() || accountLabel != (*it).from()->account()->accountLabel())
+ // If the message's account is new, just specify it to the user
+ {
+ if (!accountLabel.isEmpty())
+ resultHTML += "<br/><br/><br/>";
+ resultHTML += "<b><font color=\"blue\">" + (*it).from()->account()->accountLabel() + "</font></b><br/>";
+ }
+ accountLabel = (*it).from()->account()->accountLabel();
+
+ QString body = (*it).parsedBody();
+
+ if (!mMainWidget->searchLine->text().isEmpty())
+ // If there is a search, then we hightlight the keywords
+ {
+ body = body.replace(mMainWidget->searchLine->text(), "<span style=\"background-color:yellow\">" + mMainWidget->searchLine->text() + "</span>", false);
+ }
+
+ resultHTML += "(<b>" + (*it).timestamp().time().toString() + "</b>) "
+ + ((*it).direction() == Kopete::Message::Outbound ?
+ "<font color=\"" + KopetePrefs::prefs()->textColor().dark().name() + "\"><b>&gt;</b></font> "
+ : "<font color=\"" + KopetePrefs::prefs()->textColor().light(200).name() + "\"><b>&lt;</b></font> ")
+ + body + "<br/>";
+
+ newNode = mHtmlPart->document().createElement(QString::fromLatin1("span"));
+ newNode.setAttribute(QString::fromLatin1("dir"), dir);
+ newNode.setInnerHTML(resultHTML);
+
+ mHtmlPart->htmlDocument().body().appendChild(newNode);
+ }
+ }
+}
+
+void HistoryDialog::slotFilterChanged(int /* index */)
+{
+ dateSelected(mMainWidget->dateListView->currentItem());
+}
+
+void HistoryDialog::slotOpenURLRequest(const KURL &url, const KParts::URLArgs &/*args*/)
+{
+ kdDebug(14310) << k_funcinfo << "url=" << url.url() << endl;
+ new KRun(url, 0, false); // false = non-local files
+}
+
+// Disable search button if there is no search text
+void HistoryDialog::slotSearchTextChanged(const QString& searchText)
+{
+ if (searchText.isEmpty())
+ {
+ mMainWidget->searchButton->setEnabled(false);
+ slotSearchErase();
+ }
+ else
+ {
+ mMainWidget->searchButton->setEnabled(true);
+ }
+}
+
+void HistoryDialog::listViewShowElements(bool s)
+{
+ KListViewDateItem* item = static_cast<KListViewDateItem*>(mMainWidget->dateListView->firstChild());
+ while (item != 0)
+ {
+ item->setVisible(s);
+ item = static_cast<KListViewDateItem*>(item->nextSibling());
+ }
+}
+
+// Erase the search line, show all date/metacontacts items in the list (accordint to the
+// metacontact selected in the combobox)
+void HistoryDialog::slotSearchErase()
+{
+ mMainWidget->searchLine->clear();
+ listViewShowElements(true);
+}
+
+/*
+* How does the search work
+* ------------------------
+* We do the search respecting the current metacontact filter item. To do this, we iterate over the
+* elements in the KListView (KListViewDateItems) and, for each one, we iterate over its subcontacts,
+* manually searching the log files of each one. To avoid searching files twice, the months that have
+* been searched already are stored in searchedMonths. The matches are placed in the matches QMap.
+* Finally, the current date item is checked in the matches QMap, and if it is present, it is shown.
+*
+* Keyword highlighting is done in setMessages() : if the search field isn't empty, we highlight the
+* search keyword.
+*
+* The search is _not_ case sensitive
+*/
+void HistoryDialog::slotSearch()
+{
+ if (mMainWidget->dateListView->childCount() == 0) return;
+
+ QRegExp rx("^ <msg.*time=\"(\\d+) \\d+:\\d+:\\d+\" >([^<]*)<");
+ QMap<QDate, QValueList<Kopete::MetaContact*> > monthsSearched;
+ QMap<QDate, QValueList<Kopete::MetaContact*> > matches;
+
+ // cancel button pressed
+ if (mSearching)
+ {
+ listViewShowElements(true);
+ goto searchFinished;
+ }
+
+ listViewShowElements(false);
+
+ initProgressBar(i18n("Searching..."), mMainWidget->dateListView->childCount());
+ mMainWidget->searchButton->setText(i18n("&Cancel"));
+ mSearching = true;
+
+ // iterate over items in the date list widget
+ for(KListViewDateItem *curItem = static_cast<KListViewDateItem*>(mMainWidget->dateListView->firstChild());
+ curItem != 0;
+ curItem = static_cast<KListViewDateItem *>(curItem->nextSibling())
+ )
+ {
+ qApp->processEvents();
+ if (!mSearching) return;
+
+ QDate month(curItem->date().year(),curItem->date().month(),1);
+ // if we haven't searched the relevant history logs, search them now
+ if (!monthsSearched[month].contains(curItem->metaContact()))
+ {
+ monthsSearched[month].push_back(curItem->metaContact());
+ QPtrList<Kopete::Contact> contacts = curItem->metaContact()->contacts();
+ for(QPtrListIterator<Kopete::Contact> it( contacts ); it.current(); ++it)
+ {
+ // get filename and open file
+ QString filename(HistoryLogger::getFileName(*it, curItem->date()));
+ if (!QFile::exists(filename)) continue;
+ QFile file(filename);
+ file.open(IO_ReadOnly);
+ if (!file.isOpen())
+ {
+ kdWarning(14310) << k_funcinfo << "Error opening " <<
+ file.name() << ": " << file.errorString() << endl;
+ continue;
+ }
+
+ QTextStream stream(&file);
+ QString textLine;
+ while(!stream.atEnd())
+ {
+ textLine = stream.readLine();
+ if (textLine.contains(mMainWidget->searchLine->text(), false))
+ {
+ if(rx.search(textLine) != -1)
+ {
+ // only match message body
+ if (rx.cap(2).contains(mMainWidget->searchLine->text()))
+ matches[QDate(curItem->date().year(),curItem->date().month(),rx.cap(1).toInt())].push_back(curItem->metaContact());
+ }
+ // this will happen when multiline messages are searched, properly
+ // parsing the files would fix this
+ else { }
+ }
+ qApp->processEvents();
+ if (!mSearching) return;
+ }
+ file.close();
+ }
+ }
+
+ // relevant logfiles have been searched now, check if current date matches
+ if (matches[curItem->date()].contains(curItem->metaContact()))
+ curItem->setVisible(true);
+
+ // Next date item
+ mMainWidget->searchProgress->advance(1);
+ }
+
+searchFinished:
+ mMainWidget->searchButton->setText(i18n("Se&arch"));
+ mSearching = false;
+ doneProgressBar();
+}
+
+
+
+// When a contact is selected in the combobox. Item 0 is All contacts.
+void HistoryDialog::slotContactChanged(int index)
+{
+ mMainWidget->dateListView->clear();
+ if (index == 0)
+ {
+ setCaption(i18n("History for All Contacts"));
+ mMetaContact = 0;
+ init();
+ }
+ else
+ {
+ mMetaContact = mMetaContactList.at(index-1);
+ setCaption(i18n("History for %1").arg(mMetaContact->displayName()));
+ init();
+ }
+}
+
+void HistoryDialog::initProgressBar(const QString& text, int nbSteps)
+{
+ mMainWidget->searchProgress->setTotalSteps(nbSteps);
+ mMainWidget->searchProgress->setProgress(0);
+ mMainWidget->searchProgress->show();
+ mMainWidget->statusLabel->setText(text);
+}
+
+void HistoryDialog::doneProgressBar()
+{
+ mMainWidget->searchProgress->hide();
+ mMainWidget->statusLabel->setText(i18n("Ready"));
+}
+
+void HistoryDialog::slotRightClick(const QString &url, const QPoint &point)
+{
+ KPopupMenu *chatWindowPopup = 0L;
+ chatWindowPopup = new KPopupMenu();
+
+ if ( !url.isEmpty() )
+ {
+ mURL = url;
+ mCopyURLAct->plug( chatWindowPopup );
+ chatWindowPopup->insertSeparator();
+ }
+ mCopyAct->setEnabled( mHtmlPart->hasSelection() );
+ mCopyAct->plug( chatWindowPopup );
+
+ connect( chatWindowPopup, SIGNAL( aboutToHide() ), chatWindowPopup, SLOT( deleteLater() ) );
+ chatWindowPopup->popup(point);
+}
+
+void HistoryDialog::slotCopy()
+{
+ QString qsSelection;
+ qsSelection = mHtmlPart->selectedText();
+ if ( qsSelection.isEmpty() ) return;
+
+ disconnect( kapp->clipboard(), SIGNAL( selectionChanged()), mHtmlPart, SLOT(slotClearSelection()));
+ QApplication::clipboard()->setText(qsSelection, QClipboard::Clipboard);
+ QApplication::clipboard()->setText(qsSelection, QClipboard::Selection);
+ connect( kapp->clipboard(), SIGNAL( selectionChanged()), mHtmlPart, SLOT(slotClearSelection()));
+}
+
+void HistoryDialog::slotCopyURL()
+{
+ disconnect( kapp->clipboard(), SIGNAL( selectionChanged()), mHtmlPart, SLOT(slotClearSelection()));
+ QApplication::clipboard()->setText( mURL, QClipboard::Clipboard);
+ QApplication::clipboard()->setText( mURL, QClipboard::Selection);
+ connect( kapp->clipboard(), SIGNAL( selectionChanged()), mHtmlPart, SLOT(slotClearSelection()));
+}
+
+#include "historydialog.moc"
diff --git a/kopete/plugins/history/historydialog.h b/kopete/plugins/history/historydialog.h
new file mode 100644
index 00000000..cf26037d
--- /dev/null
+++ b/kopete/plugins/history/historydialog.h
@@ -0,0 +1,146 @@
+/*
+ kopetehistorydialog.h - Kopete History Dialog
+
+ Copyright (c) 2002 by Richard Stellingwerff <remenic@linuxfromscratch.org>
+ Copyright (c) 2004 by Stefan Gehn <metz AT gehn.net>
+
+ Kopete (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org>
+
+ *************************************************************************
+ * *
+ * 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. *
+ * *
+ *************************************************************************
+*/
+
+#ifndef _HISTORYDIALOG_H
+#define _HISTORYDIALOG_H
+
+#include <qfile.h>
+#include <qstringlist.h>
+
+#include <kdialogbase.h>
+#include <klistview.h>
+
+#include "kopetemessage.h"
+
+class HistoryViewer;
+
+//class HistoryWidget;
+namespace Kopete { class MetaContact; }
+namespace Kopete { class XSLT; }
+class HistoryLogger;
+class KHTMLView;
+class KHTMLPart;
+
+class KURL;
+namespace KParts { struct URLArgs; class Part; }
+
+
+class KListViewDateItem;
+
+class DMPair
+{
+ public:
+ DMPair() {md = QDate(0, 0, 0); mc = 0; }
+ DMPair(QDate d, Kopete::MetaContact *c) { md = d; mc =c; }
+ QDate date() const { return md; }
+ Kopete::MetaContact* metaContact() const { return mc; }
+ bool operator==(const DMPair p1) const { return p1.date() == this->date() && p1.metaContact() == this->metaContact(); }
+ private:
+ QDate md;
+ Kopete::MetaContact *mc;
+};
+
+/**
+ * @author Richard Stellingwerff <remenic@linuxfromscratch.org>
+ * @author Stefan Gehn <metz AT gehn.net>
+ */
+class HistoryDialog : public KDialogBase
+{
+ Q_OBJECT
+
+ public:
+ HistoryDialog(Kopete::MetaContact *mc, QWidget* parent=0,
+ const char* name="HistoryDialog");
+ ~HistoryDialog();
+
+ /**
+ * Calls init(Kopete::Contact *c) for each subcontact of the metacontact
+ */
+
+
+ signals:
+ void closing();
+
+ private slots:
+ void slotOpenURLRequest(const KURL &url, const KParts::URLArgs &/*args*/);
+
+ // Called when a date is selected in the treeview
+ void dateSelected(QListViewItem *);
+
+ void slotSearch();
+
+ // Reinitialise search
+ void slotSearchErase();
+ void slotSearchTextChanged(const QString& txt); // To enable/disable search button
+ void slotContactChanged(int index);
+ void slotFilterChanged(int index);
+
+ void init();
+ void slotLoadDays();
+
+ void slotRightClick(const QString &url, const QPoint &point);
+ void slotCopy();
+ void slotCopyURL();
+
+ private:
+ enum Disabled { Prev=1, Next=2 };
+ void refreshEnabled( /*Disabled*/ uint disabled );
+
+ void initProgressBar(const QString& text, int nbSteps);
+ void doneProgressBar();
+ void init(Kopete::MetaContact *mc);
+ void init(Kopete::Contact *c);
+
+ /**
+ * Show the messages in the HTML View
+ */
+ void setMessages(QValueList<Kopete::Message> m);
+
+ void listViewShowElements(bool s);
+
+ /**
+ * Search if @param item already has @param text child
+ */
+ bool hasChild(KListViewItem* item, int month);
+
+ /**
+ * We show history dialog to look at the log for a metacontact. Here is this metacontact.
+ */
+ Kopete::MetaContact *mMetaContact;
+
+ QPtrList<Kopete::MetaContact> mMetaContactList;
+
+ // History View
+ KHTMLView *mHtmlView;
+ KHTMLPart *mHtmlPart;
+ HistoryViewer *mMainWidget;
+ Kopete::XSLT *mXsltParser;
+
+ struct Init
+ {
+ QValueList<DMPair> dateMCList; // mc for MetaContact
+ } mInit;
+
+ bool mSearching;
+
+ KAction *mCopyAct;
+ KAction *mCopyURLAct;
+ QString mURL;
+};
+
+#endif
diff --git a/kopete/plugins/history/historyguiclient.cpp b/kopete/plugins/history/historyguiclient.cpp
new file mode 100644
index 00000000..133e50a3
--- /dev/null
+++ b/kopete/plugins/history/historyguiclient.cpp
@@ -0,0 +1,115 @@
+/*
+ historyguiclient.cpp
+
+ Copyright (c) 2003-2004 by Olivier Goffart <ogoffart @ kde.org>
+ Kopete (c) 2003-2004 by the Kopete developers <kopete-devel@kde.org>
+
+ *************************************************************************
+ * *
+ * 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 "historyguiclient.h"
+#include "historylogger.h"
+#include "historyconfig.h"
+
+#include "kopetechatsession.h"
+#include "kopetecontact.h"
+#include "kopeteview.h"
+
+#include <kaction.h>
+#include <klocale.h>
+#include <kgenericfactory.h>
+
+class HistoryPlugin;
+
+HistoryGUIClient::HistoryGUIClient(Kopete::ChatSession *parent, const char *name)
+ : QObject(parent, name), KXMLGUIClient(parent)
+{
+ setInstance(KGenericFactory<HistoryPlugin>::instance());
+
+ m_manager = parent;
+
+ // Refuse to build this client, it is based on wrong parameters
+ if(!m_manager || m_manager->members().isEmpty())
+ deleteLater();
+
+ QPtrList<Kopete::Contact> mb=m_manager->members();
+ m_logger=new HistoryLogger( mb.first() , this );
+
+ actionLast=new KAction( i18n("History Last" ), QString::fromLatin1( "finish" ), 0, this, SLOT(slotLast()), actionCollection() , "historyLast" );
+ actionPrev = KStdAction::back( this, SLOT(slotPrevious()), actionCollection() , "historyPrevious" );
+ actionNext = KStdAction::forward( this, SLOT(slotNext()), actionCollection() , "historyNext" );
+
+ // we are generally at last when begining
+ actionPrev->setEnabled(true);
+ actionNext->setEnabled(false);
+ actionLast->setEnabled(false);
+
+ setXMLFile("historychatui.rc");
+}
+
+
+HistoryGUIClient::~HistoryGUIClient()
+{
+}
+
+
+void HistoryGUIClient::slotPrevious()
+{
+ KopeteView *m_currentView = m_manager->view(true);
+ m_currentView->clear();
+
+ QPtrList<Kopete::Contact> mb = m_manager->members();
+ QValueList<Kopete::Message> msgs = m_logger->readMessages(
+ HistoryConfig::number_ChatWindow(), /*mb.first()*/ 0L,
+ HistoryLogger::AntiChronological, true);
+
+ actionPrev->setEnabled(msgs.count() == HistoryConfig::number_ChatWindow());
+ actionNext->setEnabled(true);
+ actionLast->setEnabled(true);
+
+ m_currentView->appendMessages(msgs);
+}
+
+void HistoryGUIClient::slotLast()
+{
+ KopeteView *m_currentView = m_manager->view(true);
+ m_currentView->clear();
+
+ QPtrList<Kopete::Contact> mb = m_manager->members();
+ m_logger->setPositionToLast();
+ QValueList<Kopete::Message> msgs = m_logger->readMessages(
+ HistoryConfig::number_ChatWindow(), /*mb.first()*/ 0L,
+ HistoryLogger::AntiChronological, true);
+
+ actionPrev->setEnabled(true);
+ actionNext->setEnabled(false);
+ actionLast->setEnabled(false);
+
+ m_currentView->appendMessages(msgs);
+}
+
+
+void HistoryGUIClient::slotNext()
+{
+ KopeteView *m_currentView = m_manager->view(true);
+ m_currentView->clear();
+
+ QPtrList<Kopete::Contact> mb = m_manager->members();
+ QValueList<Kopete::Message> msgs = m_logger->readMessages(
+ HistoryConfig::number_ChatWindow(), /*mb.first()*/ 0L,
+ HistoryLogger::Chronological, false);
+
+ actionPrev->setEnabled(true);
+ actionNext->setEnabled(msgs.count() == HistoryConfig::number_ChatWindow());
+ actionLast->setEnabled(msgs.count() == HistoryConfig::number_ChatWindow());
+
+ m_currentView->appendMessages(msgs);
+}
+
+#include "historyguiclient.moc"
diff --git a/kopete/plugins/history/historyguiclient.h b/kopete/plugins/history/historyguiclient.h
new file mode 100644
index 00000000..420795e0
--- /dev/null
+++ b/kopete/plugins/history/historyguiclient.h
@@ -0,0 +1,55 @@
+/*
+ historyguiclient.h
+
+ Copyright (c) 2003 by Olivier Goffart <ogoffart @ kde.org>
+ Kopete (c) 2003 by the Kopete developers <kopete-devel@kde.org>
+
+ *************************************************************************
+ * *
+ * 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. *
+ * *
+ *************************************************************************
+*/
+#ifndef HISTORYGUICLIENT_H
+#define HISTORYGUICLIENT_H
+
+#include <qobject.h>
+#include <kxmlguiclient.h>
+
+namespace Kopete { class ChatSession; }
+class HistoryLogger;
+class KAction;
+
+/**
+ *@author Olivier Goffart
+ */
+class HistoryGUIClient : public QObject , public KXMLGUIClient
+{
+Q_OBJECT
+public:
+ HistoryGUIClient(Kopete::ChatSession *parent = 0, const char *name = 0);
+ ~HistoryGUIClient();
+
+ HistoryLogger *logger() const { return m_logger; }
+
+private slots:
+ void slotPrevious();
+ void slotLast();
+ void slotNext();
+
+private:
+ HistoryLogger *m_logger;
+ Kopete::ChatSession *m_manager;
+ //bool m_autoChatWindow;
+ //int m_nbAutoChatWindow;
+ //unsigned int m_nbChatWindow;
+
+ KAction *actionPrev;
+ KAction *actionNext;
+ KAction *actionLast;
+};
+
+#endif
diff --git a/kopete/plugins/history/historylogger.cpp b/kopete/plugins/history/historylogger.cpp
new file mode 100644
index 00000000..7848136f
--- /dev/null
+++ b/kopete/plugins/history/historylogger.cpp
@@ -0,0 +1,851 @@
+/*
+ historylogger.cpp
+
+ Copyright (c) 2003-2004 by Olivier Goffart <ogoffart @ kde.org>
+
+ Kopete (c) 2003-2004 by the Kopete developers <kopete-devel@kde.org>
+
+ *************************************************************************
+ * *
+ * 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 "historylogger.h"
+#include "historyconfig.h"
+
+#include <qregexp.h>
+#include <qfile.h>
+#include <qdir.h>
+#include <qdatetime.h>
+#include <qdom.h>
+#include <qtimer.h>
+
+#include <kdebug.h>
+#include <kstandarddirs.h>
+#include <ksavefile.h>
+
+#include "kopeteglobal.h"
+#include "kopetecontact.h"
+#include "kopeteprotocol.h"
+#include "kopeteaccount.h"
+#include "kopetemetacontact.h"
+#include "kopetechatsession.h"
+
+// -----------------------------------------------------------------------------
+HistoryLogger::HistoryLogger( Kopete::MetaContact *m, QObject *parent, const char *name )
+ : QObject(parent, name)
+{
+ m_saveTimer=0L;
+ m_saveTimerTime=0;
+ m_metaContact=m;
+ m_hideOutgoing=false;
+ m_cachedMonth=-1;
+ m_realMonth=QDate::currentDate().month();
+ m_oldSens=Default;
+
+ //the contact may be destroyed, for example, if the contact changes its metacontact
+ connect(m_metaContact , SIGNAL(destroyed(QObject *)) , this , SLOT(slotMCDeleted()));
+
+ setPositionToLast();
+}
+
+
+HistoryLogger::HistoryLogger( Kopete::Contact *c, QObject *parent, const char *name )
+ : QObject(parent, name)
+{
+ m_saveTimer=0L;
+ m_saveTimerTime=0;
+ m_cachedMonth=-1;
+ m_metaContact=c->metaContact();
+ m_hideOutgoing=false;
+ m_realMonth=QDate::currentDate().month();
+ m_oldSens=Default;
+
+ //the contact may be destroyed, for example, if the contact changes its metacontact
+ connect(m_metaContact , SIGNAL(destroyed(QObject *)) , this , SLOT(slotMCDeleted()));
+
+ setPositionToLast();
+}
+
+
+HistoryLogger::~HistoryLogger()
+{
+ if(m_saveTimer && m_saveTimer->isActive())
+ saveToDisk();
+}
+
+
+void HistoryLogger::setPositionToLast()
+{
+ setCurrentMonth(0);
+ m_oldSens = AntiChronological;
+ m_oldMonth=0;
+ m_oldElements.clear();
+}
+
+
+void HistoryLogger::setPositionToFirst()
+{
+ setCurrentMonth( getFirstMonth() );
+ m_oldSens = Chronological;
+ m_oldMonth=m_currentMonth;
+ m_oldElements.clear();
+}
+
+
+void HistoryLogger::setCurrentMonth(int month)
+{
+ m_currentMonth = month;
+ m_currentElements.clear();
+}
+
+
+QDomDocument HistoryLogger::getDocument(const Kopete::Contact *c, unsigned int month , bool canLoad , bool* contain)
+{
+ if(m_realMonth!=QDate::currentDate().month())
+ { //We changed month, our indice are not correct anymore, clean memory.
+ // or we will see what i called "the 31 midnight bug"(TM) :-) -Olivier
+ m_documents.clear();
+ m_cachedMonth=-1;
+ m_currentMonth++; //Not usre it's ok, but should work;
+ m_oldMonth++; // idem
+ m_realMonth=QDate::currentDate().month();
+ }
+
+ if(!m_metaContact)
+ { //this may happen if the contact has been moved, and the MC deleted
+ if(c && c->metaContact())
+ m_metaContact=c->metaContact();
+ else
+ return QDomDocument();
+ }
+
+ if(!m_metaContact->contacts().contains(c))
+ {
+ if(contain)
+ *contain=false;
+ return QDomDocument();
+ }
+
+ QMap<unsigned int , QDomDocument> documents = m_documents[c];
+ if (documents.contains(month))
+ return documents[month];
+
+
+ QDomDocument doc = getDocument(c, QDate::currentDate().addMonths(0-month), canLoad, contain);
+
+ documents.insert(month, doc);
+ m_documents[c]=documents;
+
+ return doc;
+
+}
+
+QDomDocument HistoryLogger::getDocument(const Kopete::Contact *c, const QDate date , bool canLoad , bool* contain)
+{
+ if(!m_metaContact)
+ { //this may happen if the contact has been moved, and the MC deleted
+ if(c && c->metaContact())
+ m_metaContact=c->metaContact();
+ else
+ return QDomDocument();
+ }
+
+ if(!m_metaContact->contacts().contains(c))
+ {
+ if(contain)
+ *contain=false;
+ return QDomDocument();
+ }
+
+ if(!canLoad)
+ {
+ if(contain)
+ *contain=false;
+ return QDomDocument();
+ }
+
+ QString FileName = getFileName(c, date);
+
+ QDomDocument doc( "Kopete-History" );
+
+ QFile file( FileName );
+ if ( !file.open( IO_ReadOnly ) )
+ {
+ if(contain)
+ *contain=false;
+ return doc;
+ }
+ if ( !doc.setContent( &file ) )
+ {
+ file.close();
+ if(contain)
+ *contain=false;
+ return doc;
+ }
+ file.close();
+
+ if(contain)
+ *contain=true;
+
+ return doc;
+}
+
+
+void HistoryLogger::appendMessage( const Kopete::Message &msg , const Kopete::Contact *ct )
+{
+ if(!msg.from())
+ return;
+
+ // If no contact are given: If the manager is availiable, use the manager's
+ // first contact (the channel on irc, or the other contact for others protocols
+ const Kopete::Contact *c = ct;
+ if(!c && msg.manager() )
+ {
+ QPtrList<Kopete::Contact> mb=msg.manager()->members() ;
+ c = mb.first();
+ }
+ if(!c) //If the contact is still not initialized, use the message author.
+ c = msg.direction()==Kopete::Message::Outbound ? msg.to().first() : msg.from() ;
+
+
+ if(!m_metaContact)
+ { //this may happen if the contact has been moved, and the MC deleted
+ if(c && c->metaContact())
+ m_metaContact=c->metaContact();
+ else
+ return;
+ }
+
+
+ if(!c || !m_metaContact->contacts().contains(c) )
+ {
+ /*QPtrList<Kopete::Contact> contacts= m_metaContact->contacts();
+ QPtrListIterator<Kopete::Contact> it( contacts );
+ for( ; it.current(); ++it )
+ {
+ if( (*it)->protocol()->pluginId() == msg.from()->protocol()->pluginId() )
+ {
+ c=*it;
+ break;
+ }
+ }*/
+ //if(!c)
+
+ kdWarning(14310) << k_funcinfo << "No contact found in this metacontact to" <<
+ " append in the history" << endl;
+ return;
+ }
+
+ QDomDocument doc=getDocument(c,0);
+ QDomElement docElem = doc.documentElement();
+
+ if(docElem.isNull())
+ {
+ docElem= doc.createElement( "kopete-history" );
+ docElem.setAttribute ( "version" , "0.9" );
+ doc.appendChild( docElem );
+ QDomElement headElem = doc.createElement( "head" );
+ docElem.appendChild( headElem );
+ QDomElement dateElem = doc.createElement( "date" );
+ dateElem.setAttribute( "year", QString::number(QDate::currentDate().year()) );
+ dateElem.setAttribute( "month", QString::number(QDate::currentDate().month()) );
+ headElem.appendChild(dateElem);
+ QDomElement myselfElem = doc.createElement( "contact" );
+ myselfElem.setAttribute( "type", "myself" );
+ myselfElem.setAttribute( "contactId", c->account()->myself()->contactId() );
+ headElem.appendChild(myselfElem);
+ QDomElement contactElem = doc.createElement( "contact" );
+ contactElem.setAttribute( "contactId", c->contactId() );
+ headElem.appendChild(contactElem);
+ }
+
+ QDomElement msgElem = doc.createElement( "msg" );
+ msgElem.setAttribute( "in", msg.direction()==Kopete::Message::Outbound ? "0" : "1" );
+ msgElem.setAttribute( "from", msg.from()->contactId() );
+ msgElem.setAttribute( "nick", msg.from()->property( Kopete::Global::Properties::self()->nickName() ).value().toString() ); //do we have to set this?
+ msgElem.setAttribute( "time", msg.timestamp().toString("d h:m:s") );
+
+ QDomText msgNode = doc.createTextNode( msg.plainBody() );
+ docElem.appendChild( msgElem );
+ msgElem.appendChild( msgNode );
+
+
+ // I'm temporizing the save.
+ // On hight-traffic channel, saving can take lots of CPU. (because the file is big)
+ // So i wait a time proportional to the time needed to save..
+
+ const QString filename=getFileName(c,QDate::currentDate());
+ if(!m_toSaveFileName.isEmpty() && m_toSaveFileName != filename)
+ { //that mean the contact or the month has changed, save it now.
+ saveToDisk();
+ }
+
+ m_toSaveFileName=filename;
+ m_toSaveDocument=doc;
+
+ if(!m_saveTimer)
+ {
+ m_saveTimer=new QTimer(this);
+ connect( m_saveTimer, SIGNAL( timeout() ) , this, SLOT(saveToDisk()) );
+ }
+ if(!m_saveTimer->isActive())
+ m_saveTimer->start( m_saveTimerTime, true /*singleshot*/ );
+}
+
+void HistoryLogger::saveToDisk()
+{
+ if(m_saveTimer)
+ m_saveTimer->stop();
+ if(m_toSaveFileName.isEmpty() || m_toSaveDocument.isNull())
+ return;
+
+ QTime t;
+ t.start(); //mesure the time needed to save.
+
+ KSaveFile file( m_toSaveFileName );
+ if( file.status() == 0 )
+ {
+ QTextStream *stream = file.textStream();
+ //stream->setEncoding( QTextStream::UnicodeUTF8 ); //???? oui ou non?
+ m_toSaveDocument.save( *stream, 1 );
+ file.close();
+
+ m_saveTimerTime=QMIN(t.elapsed()*1000, 300000);
+ //a time 1000 times supperior to the time needed to save. but with a upper limit of 5 minutes
+ //on a my machine, (2.4Ghz, but old HD) it should take about 10 ms to save the file.
+ // So that would mean save every 10 seconds, which seems to be ok.
+ // But it may take 500 ms if the file to save becomes too big (1Mb).
+ kdDebug(14310) << k_funcinfo << m_toSaveFileName << " saved in " << t.elapsed() << " ms " <<endl ;
+
+ m_toSaveFileName=QString::null;
+ m_toSaveDocument=QDomDocument();
+ }
+ else
+ kdError(14310) << k_funcinfo << "impossible to save the history file " << m_toSaveFileName << endl;
+
+}
+
+QValueList<Kopete::Message> HistoryLogger::readMessages(QDate date)
+{
+ QRegExp rxTime("(\\d+) (\\d+):(\\d+)($|:)(\\d*)"); //(with a 0.7.x compatibility)
+ QValueList<Kopete::Message> messages;
+
+
+ QPtrList<Kopete::Contact> ct=m_metaContact->contacts();
+ QPtrListIterator<Kopete::Contact> it( ct );
+
+ for( ; it.current(); ++it )
+ {
+ QDomDocument doc=getDocument(*it,date, true, 0L);
+ QDomElement docElem = doc.documentElement();
+ QDomNode n = docElem.firstChild();
+
+ while(!n.isNull())
+ {
+ QDomElement msgElem2 = n.toElement();
+ if( !msgElem2.isNull() && msgElem2.tagName()=="msg")
+ {
+ rxTime.search(msgElem2.attribute("time"));
+ QDateTime dt( QDate(date.year() , date.month() , rxTime.cap(1).toUInt()), QTime( rxTime.cap(2).toUInt() , rxTime.cap(3).toUInt(), rxTime.cap(5).toUInt() ) );
+
+ if (dt.date() != date)
+ {
+ n = n.nextSibling();
+ continue;
+ }
+
+ Kopete::Message::MessageDirection dir = (msgElem2.attribute("in") == "1") ?
+ Kopete::Message::Inbound : Kopete::Message::Outbound;
+
+ if(!m_hideOutgoing || dir != Kopete::Message::Outbound)
+ { //parse only if we don't hide it
+
+ QString f=msgElem2.attribute("from" );
+ const Kopete::Contact *from=f.isNull()? 0L : (*it)->account()->contacts()[f];
+
+ if(!from)
+ from= dir==Kopete::Message::Inbound ? (*it) : (*it)->account()->myself();
+
+ Kopete::ContactPtrList to;
+ to.append( dir==Kopete::Message::Inbound ? (*it)->account()->myself() : *it );
+
+ Kopete::Message msg(dt, from, to, msgElem2.text(), dir);
+ msg.setBody( QString::fromLatin1("<span title=\"%1\">%2</span>")
+ .arg( dt.toString(Qt::LocalDate), msg.escapedBody() ),
+ Kopete::Message::RichText);
+
+
+ // We insert it at the good place, given its date
+ QValueListIterator<Kopete::Message> msgIt;
+
+ for (msgIt = messages.begin(); msgIt != messages.end(); ++msgIt)
+ {
+ if ((*msgIt).timestamp() > msg.timestamp())
+ break;
+ }
+ messages.insert(msgIt, msg);
+ }
+ }
+
+ n = n.nextSibling();
+ } // end while on messages
+
+ }
+ return messages;
+}
+
+QValueList<Kopete::Message> HistoryLogger::readMessages(unsigned int lines,
+ const Kopete::Contact *c, Sens sens, bool reverseOrder, bool colorize)
+{
+ //QDate dd = QDate::currentDate().addMonths(0-m_currentMonth);
+
+ QValueList<Kopete::Message> messages;
+
+ // A regexp useful for this function
+ QRegExp rxTime("(\\d+) (\\d+):(\\d+)($|:)(\\d*)"); //(with a 0.7.x compatibility)
+
+ if(!m_metaContact)
+ { //this may happen if the contact has been moved, and the MC deleted
+ if(c && c->metaContact())
+ m_metaContact=c->metaContact();
+ else
+ return messages;
+ }
+
+ if(c && !m_metaContact->contacts().contains(c) )
+ return messages;
+
+ if(sens ==0 ) //if no sens are selected, just continue in the previous sens
+ sens = m_oldSens ;
+ if( m_oldSens != 0 && sens != m_oldSens )
+ { //we changed our sens! so retrieve the old position to fly in the other way
+ m_currentElements= m_oldElements;
+ m_currentMonth=m_oldMonth;
+ }
+ else
+ {
+ m_oldElements=m_currentElements;
+ m_oldMonth=m_currentMonth;
+ }
+ m_oldSens=sens;
+
+ //getting the color for messages:
+ QColor fgColor = HistoryConfig::history_color();
+
+ //Hello guest!
+
+ //there are two algoritms:
+ // - if a contact is given, or the metacontact contain only one contact, just read the history.
+ // - else, merge the history
+
+ //the merging algoritm is the following:
+ // we see what contact we have to read first, and we look at the firt date before another contact
+ // has a message with a bigger date.
+
+ QDateTime timeLimit;
+ const Kopete::Contact *currentContact=c;
+ if(!c && m_metaContact->contacts().count()==1)
+ currentContact=m_metaContact->contacts().first();
+ else if(!c && m_metaContact->contacts().count()== 0)
+ {
+ return messages;
+ }
+
+ while(messages.count() < lines)
+ {
+ timeLimit=QDateTime();
+ QDomElement msgElem; //here is the message element
+ QDateTime timestamp; //and the timestamp of this message
+
+ if(!c && m_metaContact->contacts().count()>1)
+ { //we have to merge the differents subcontact history
+ QPtrList<Kopete::Contact> ct=m_metaContact->contacts();
+ QPtrListIterator<Kopete::Contact> it( ct );
+ for( ; it.current(); ++it )
+ { //we loop over each contact. we are searching the contact with the next message with the smallest date,
+ // it will becomes our current contact, and the contact with the mext message with the second smallest
+ // date, this date will bocomes the limit.
+
+ QDomNode n;
+ if(m_currentElements.contains(*it))
+ n=m_currentElements[*it];
+ else //there is not yet "next message" register, so we will take the first (for the current month)
+ {
+ QDomDocument doc=getDocument(*it,m_currentMonth);
+ QDomElement docElem = doc.documentElement();
+ n= (sens==Chronological)?docElem.firstChild() : docElem.lastChild();
+
+ //i can't drop the root element
+ workaround.append(docElem);
+ }
+ while(!n.isNull())
+ {
+ QDomElement msgElem2 = n.toElement();
+ if( !msgElem2.isNull() && msgElem2.tagName()=="msg")
+ {
+ rxTime.search(msgElem2.attribute("time"));
+ QDate d=QDate::currentDate().addMonths(0-m_currentMonth);
+ QDateTime dt( QDate(d.year() , d.month() , rxTime.cap(1).toUInt()), QTime( rxTime.cap(2).toUInt() , rxTime.cap(3).toUInt(), rxTime.cap(5).toUInt() ) );
+ if(!timestamp.isValid() || ((sens==Chronological )? dt < timestamp : dt > timestamp) )
+ {
+ timeLimit=timestamp;
+ timestamp=dt;
+ msgElem=msgElem2;
+ currentContact=*it;
+
+ }
+ else if(!timeLimit.isValid() || ((sens==Chronological) ? timeLimit > dt : timeLimit < dt) )
+ {
+ timeLimit=dt;
+ }
+ break;
+ }
+ n=(sens==Chronological)? n.nextSibling() : n.previousSibling();
+ }
+ }
+ }
+ else //we don't have to merge the history. just take the next item in the contact
+ {
+ if(m_currentElements.contains(currentContact))
+ msgElem=m_currentElements[currentContact];
+ else
+ {
+ QDomDocument doc=getDocument(currentContact,m_currentMonth);
+ QDomElement docElem = doc.documentElement();
+ QDomNode n= (sens==Chronological)?docElem.firstChild() : docElem.lastChild();
+ msgElem=QDomElement();
+ while(!n.isNull()) //continue until we get a msg
+ {
+ msgElem=n.toElement();
+ if( !msgElem.isNull() && msgElem.tagName()=="msg")
+ {
+ m_currentElements[currentContact]=msgElem;
+ break;
+ }
+ n=(sens==Chronological)? n.nextSibling() : n.previousSibling();
+ }
+
+ //i can't drop the root element
+ workaround.append(docElem);
+ }
+ }
+
+
+ if(msgElem.isNull()) //we don't find ANY messages in any contact for this month. so we change the month
+ {
+ if(sens==Chronological)
+ {
+ if(m_currentMonth <= 0)
+ break; //there are no other messages to show. break even if we don't have nb messages
+ setCurrentMonth(m_currentMonth-1);
+ }
+ else
+ {
+ if(m_currentMonth >= getFirstMonth(c))
+ break; //we don't have any other messages to show
+ setCurrentMonth(m_currentMonth+1);
+ }
+ continue; //begin the loop from the bottom, and find currentContact and timeLimit again
+ }
+
+ while(
+ (messages.count() < lines) &&
+ !msgElem.isNull() &&
+ (!timestamp.isValid() || !timeLimit.isValid() ||
+ ((sens==Chronological) ? timestamp <= timeLimit : timestamp >= timeLimit)
+ ))
+ {
+ // break this loop, if we have reached the correct number of messages,
+ // if there are no more messages for this contact, or if we reached
+ // the timeLimit msgElem is the next message, still not parsed, so
+ // we parse it now
+
+ Kopete::Message::MessageDirection dir = (msgElem.attribute("in") == "1") ?
+ Kopete::Message::Inbound : Kopete::Message::Outbound;
+
+ if(!m_hideOutgoing || dir != Kopete::Message::Outbound)
+ { //parse only if we don't hide it
+
+ if( m_filter.isNull() || ( m_filterRegExp? msgElem.text().contains(QRegExp(m_filter,m_filterCaseSensitive)) : msgElem.text().contains(m_filter,m_filterCaseSensitive) ))
+ {
+ QString f=msgElem.attribute("from" );
+ const Kopete::Contact *from=(f.isNull() || !currentContact) ? 0L : currentContact->account()->contacts()[f];
+
+ if(!from)
+ from= dir==Kopete::Message::Inbound ? currentContact : currentContact->account()->myself();
+
+ Kopete::ContactPtrList to;
+ to.append( dir==Kopete::Message::Inbound ? currentContact->account()->myself() : currentContact );
+
+ if(!timestamp.isValid())
+ {
+ //parse timestamp only if it was not already parsed
+ rxTime.search(msgElem.attribute("time"));
+ QDate d=QDate::currentDate().addMonths(0-m_currentMonth);
+ timestamp=QDateTime( QDate(d.year() , d.month() , rxTime.cap(1).toUInt()), QTime( rxTime.cap(2).toUInt() , rxTime.cap(3).toUInt() , rxTime.cap(5).toUInt() ) );
+ }
+
+ Kopete::Message msg(timestamp, from, to, msgElem.text(), dir);
+ if (colorize)
+ {
+ msg.setBody( QString::fromLatin1("<span style=\"color:%1\" title=\"%2\">%3</span>")
+ .arg( fgColor.name(), timestamp.toString(Qt::LocalDate), msg.escapedBody() ),
+ Kopete::Message::RichText
+ );
+ msg.setFg( fgColor );
+ }
+ else
+ {
+ msg.setBody( QString::fromLatin1("<span title=\"%1\">%2</span>")
+ .arg( timestamp.toString(Qt::LocalDate), msg.escapedBody() ),
+ Kopete::Message::RichText
+ );
+ }
+
+ if(reverseOrder)
+ messages.prepend(msg);
+ else
+ messages.append(msg);
+ }
+ }
+
+ //here is the point of workaround. If i drop the root element, this crashes
+ //get the next message
+ QDomNode node = ( (sens==Chronological) ? msgElem.nextSibling() :
+ msgElem.previousSibling() );
+
+ msgElem = QDomElement(); //n.toElement();
+ while (!node.isNull() && msgElem.isNull())
+ {
+ msgElem = node.toElement();
+ if (!msgElem.isNull())
+ {
+ if (msgElem.tagName() == "msg")
+ {
+ if (!c && (m_metaContact->contacts().count() > 1))
+ {
+ // In case of hideoutgoing messages, it is faster to do
+ // this, so we don't parse the date if it is not needed
+ QRegExp rx("(\\d+) (\\d+):(\\d+):(\\d+)");
+ rx.search(msgElem.attribute("time"));
+
+ QDate d = QDate::currentDate().addMonths(0-m_currentMonth);
+ timestamp = QDateTime(
+ QDate(d.year(), d.month(), rx.cap(1).toUInt()),
+ QTime( rx.cap(2).toUInt(), rx.cap(3).toUInt() ) );
+ }
+ else
+ timestamp = QDateTime(); //invalid
+ }
+ else
+ msgElem = QDomElement();
+ }
+
+ node = (sens == Chronological) ? node.nextSibling() :
+ node.previousSibling();
+ }
+ m_currentElements[currentContact]=msgElem; //this is the next message
+ }
+ }
+
+ if(messages.count() < lines)
+ m_currentElements.clear(); //current elements are null this can't be allowed
+
+ return messages;
+}
+
+QString HistoryLogger::getFileName(const Kopete::Contact* c, QDate date)
+{
+
+ QString name = c->protocol()->pluginId().replace( QRegExp( QString::fromLatin1( "[./~?*]" ) ), QString::fromLatin1( "-" ) ) +
+ QString::fromLatin1( "/" ) +
+ c->account()->accountId().replace( QRegExp( QString::fromLatin1( "[./~?*]" ) ), QString::fromLatin1( "-" ) ) +
+ QString::fromLatin1( "/" ) +
+ c->contactId().replace( QRegExp( QString::fromLatin1( "[./~?*]" ) ), QString::fromLatin1( "-" ) ) +
+ date.toString(".yyyyMM");
+
+ QString filename=locateLocal( "data", QString::fromLatin1( "kopete/logs/" ) + name+ QString::fromLatin1( ".xml" ) ) ;
+
+ //Check if there is a kopete 0.7.x file
+ QFileInfo fi(filename);
+ if(!fi.exists())
+ {
+ name = c->protocol()->pluginId().replace( QRegExp( QString::fromLatin1( "[./~?*]" ) ), QString::fromLatin1( "-" ) ) +
+ QString::fromLatin1( "/" ) +
+ c->contactId().replace( QRegExp( QString::fromLatin1( "[./~?*]" ) ), QString::fromLatin1( "-" ) ) +
+ date.toString(".yyyyMM");
+
+ QString filename2=locateLocal( "data", QString::fromLatin1( "kopete/logs/" ) + name+ QString::fromLatin1( ".xml" ) ) ;
+
+ QFileInfo fi2(filename2);
+ if(fi2.exists())
+ return filename2;
+ }
+
+ return filename;
+
+}
+
+unsigned int HistoryLogger::getFirstMonth(const Kopete::Contact *c)
+{
+ if(!c)
+ return getFirstMonth();
+
+ QRegExp rx( "\\.(\\d\\d\\d\\d)(\\d\\d)" );
+ QFileInfo *fi;
+
+ // BEGIN check if there are Kopete 0.7.x
+ QDir d1(locateLocal("data",QString("kopete/logs/")+
+ c->protocol()->pluginId().replace( QRegExp(QString::fromLatin1("[./~?*]")),QString::fromLatin1("-"))
+ ));
+ d1.setFilter( QDir::Files | QDir::NoSymLinks );
+ d1.setSorting( QDir::Name );
+
+ const QFileInfoList *list1 = d1.entryInfoList();
+ QFileInfoListIterator it1( *list1 );
+
+ while ( (fi = it1.current()) != 0 )
+ {
+ if(fi->fileName().contains(c->contactId().replace( QRegExp( QString::fromLatin1( "[./~?*]" ) ), QString::fromLatin1( "-" ) )))
+ {
+ rx.search(fi->fileName());
+ int result = 12*(QDate::currentDate().year() - rx.cap(1).toUInt()) +QDate::currentDate().month() - rx.cap(2).toUInt();
+
+ if(result < 0)
+ {
+ kdWarning(14310) << k_funcinfo << "Kopete only found log file from Kopete 0.7.x made in the future. Check your date!" << endl;
+ break;
+ }
+ return result;
+ }
+ ++it1;
+ }
+ // END of kopete 0.7.x check
+
+
+ QDir d(locateLocal("data",QString("kopete/logs/")+
+ c->protocol()->pluginId().replace( QRegExp(QString::fromLatin1("[./~?*]")),QString::fromLatin1("-")) +
+ QString::fromLatin1( "/" ) +
+ c->account()->accountId().replace( QRegExp( QString::fromLatin1( "[./~?*]" ) ), QString::fromLatin1( "-" ) )
+ ));
+
+ d.setFilter( QDir::Files | QDir::NoSymLinks );
+ d.setSorting( QDir::Name );
+
+ const QFileInfoList *list = d.entryInfoList();
+ QFileInfoListIterator it( *list );
+ while ( (fi = it.current()) != 0 )
+ {
+ if(fi->fileName().contains(c->contactId().replace( QRegExp( QString::fromLatin1( "[./~?*]" ) ), QString::fromLatin1( "-" ) )))
+ {
+ rx.search(fi->fileName());
+ int result = 12*(QDate::currentDate().year() - rx.cap(1).toUInt()) +QDate::currentDate().month() - rx.cap(2).toUInt();
+ if(result < 0)
+ {
+ kdWarning(14310) << k_funcinfo << "Kopete only found log file made in the future. Check your date!" << endl;
+ break;
+ }
+ return result;
+ }
+ ++it;
+ }
+ return 0;
+}
+
+unsigned int HistoryLogger::getFirstMonth()
+{
+ if(m_cachedMonth!=-1)
+ return m_cachedMonth;
+
+ if(!m_metaContact)
+ return 0;
+
+ int m=0;
+ QPtrList<Kopete::Contact> contacts=m_metaContact->contacts();
+ QPtrListIterator<Kopete::Contact> it( contacts );
+ for( ; it.current(); ++it )
+ {
+ int m2=getFirstMonth(*it);
+ if(m2>m) m=m2;
+ }
+ m_cachedMonth=m;
+ return m;
+}
+
+void HistoryLogger::setHideOutgoing(bool b)
+{
+ m_hideOutgoing = b;
+}
+
+void HistoryLogger::slotMCDeleted()
+{
+ m_metaContact = 0;
+}
+
+void HistoryLogger::setFilter(const QString& filter, bool caseSensitive , bool isRegExp)
+{
+ m_filter=filter;
+ m_filterCaseSensitive=caseSensitive;
+ m_filterRegExp=isRegExp;
+}
+
+QString HistoryLogger::filter() const
+{
+ return m_filter;
+}
+
+bool HistoryLogger::filterCaseSensitive() const
+{
+ return m_filterCaseSensitive;
+}
+
+bool HistoryLogger::filterRegExp() const
+{
+ return m_filterRegExp;
+}
+
+QValueList<int> HistoryLogger::getDaysForMonth(QDate date)
+{
+ QRegExp rxTime("time=\"(\\d+) \\d+:\\d+(:\\d+)?\""); //(with a 0.7.x compatibility)
+
+ QValueList<int> dayList;
+
+ QPtrList<Kopete::Contact> contacts = m_metaContact->contacts();
+ QPtrListIterator<Kopete::Contact> it(contacts);
+
+ int lastDay=0;
+ for(; it.current(); ++it)
+ {
+// kdDebug() << getFileName(*it, date) << endl;
+ QFile file(getFileName(*it, date));
+ if(!file.open(IO_ReadOnly))
+ {
+ continue;
+ }
+ QTextStream stream(&file);
+ QString fullText = stream.read();
+ file.close();
+
+ int pos = 0;
+ while( (pos = rxTime.search(fullText, pos)) != -1)
+ {
+ pos += rxTime.matchedLength();
+ int day=rxTime.capturedTexts()[1].toInt();
+
+ if ( day !=lastDay && dayList.find(day) == dayList.end()) // avoid duplicates
+ {
+ dayList.append(rxTime.capturedTexts()[1].toInt());
+ lastDay=day;
+ }
+ }
+ }
+ return dayList;
+}
+
+#include "historylogger.moc"
diff --git a/kopete/plugins/history/historylogger.h b/kopete/plugins/history/historylogger.h
new file mode 100644
index 00000000..85cdbdd7
--- /dev/null
+++ b/kopete/plugins/history/historylogger.h
@@ -0,0 +1,217 @@
+/*
+ historylogger.cpp
+
+ Copyright (c) 2003-2004 by Olivier Goffart <ogoffart @ kde.org>
+ Kopete (c) 2003-2004 by the Kopete developers <kopete-devel@kde.org>
+
+ *************************************************************************
+ * *
+ * 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. *
+ * *
+ *************************************************************************
+*/
+
+#ifndef HISTORYLOGGER_H
+#define HISTORYLOGGER_H
+
+#include <qobject.h>
+#include "kopetemessage.h" //TODO: REMOVE
+
+namespace Kopete { class Contact; }
+namespace Kopete { class MetaContact; }
+class QFile;
+class QDomDocument;
+class QTimer;
+
+/**
+ * One hinstance of this class is opened for every Kopete::ChatSession,
+ * or for the history dialog
+ *
+ * @author Olivier Goffart
+ */
+class HistoryLogger : public QObject
+{
+Q_OBJECT
+public:
+
+ /**
+ * - Chronological: messages are read from the first to the last, in the time order
+ * - AntiChronological: messages are read from the last to the first, in the time reversed order
+ */
+ enum Sens { Default , Chronological , AntiChronological };
+
+ /**
+ * Constructor, takes the contact, and the color of messages
+ */
+ HistoryLogger( Kopete::MetaContact *m , QObject *parent = 0, const char *name = 0);
+ HistoryLogger( Kopete::Contact *c , QObject *parent = 0, const char *name = 0);
+
+
+ ~HistoryLogger();
+
+ /**
+ * return or setif yes or no outgoing message are hidden (and not parsed)
+ */
+ bool hideOutgoing() const { return m_hideOutgoing; }
+ void setHideOutgoing(bool);
+
+ /**
+ * set a searching filter
+ * @param filter is the string to search
+ * @param caseSensitive say if the case is important
+ * @param isRegExp say if the filter is a QRegExp, or a simle string
+ */
+ void setFilter(const QString& filter, bool caseSensitive=false , bool isRegExp=false);
+ QString filter() const;
+ bool filterCaseSensitive() const ;
+ bool filterRegExp() const;
+
+
+
+ //----------------------------------
+
+ /**
+ * log a message
+ * @param c add a presision to the contact to use, if null, autodetect.
+ */
+ void appendMessage( const Kopete::Message &msg , const Kopete::Contact *c=0L );
+
+ /**
+ * read @param lines message from the current position
+ * from Kopete::Contact @param c in the given @param sens
+ */
+ QValueList<Kopete::Message> readMessages(unsigned int lines,
+ const Kopete::Contact *c=0, Sens sens=Default,
+ bool reverseOrder=false, bool colorize=true);
+
+ /**
+ * Same as the following, but for one date. I did'nt reuse the above function
+ * because its structure is really different.
+ * Read all the messages for the given @param date
+ */
+ QValueList<Kopete::Message> readMessages(QDate date);
+
+
+ /**
+ * The pausition is set to the last message
+ */
+ void setPositionToLast();
+
+ /**
+ * The position is set to the first message
+ */
+ void setPositionToFirst();
+
+ /**
+ * Set the current month (in number of month since the actual month)
+ */
+ void setCurrentMonth(int month);
+
+ /**
+ * @return The list of the days for which there is a log for m_metaContact for month of * @param date (don't care of the day)
+ */
+ QValueList<int> getDaysForMonth(QDate date);
+
+ /**
+ * Get the filename of the xml file which contains the history from the
+ * contact in the specified @param date. Specify @param date in order to get the filename for
+ * the given date.year() date.month().
+ */
+ static QString getFileName(const Kopete::Contact* , QDate date);
+
+private:
+ bool m_hideOutgoing;
+ bool m_filterCaseSensitive;
+ bool m_filterRegExp;
+ QString m_filter;
+
+
+ /*
+ *contais all QDomDocument, for a KC, for a specified Month
+ */
+ QMap<const Kopete::Contact*,QMap<unsigned int , QDomDocument> > m_documents;
+
+ /**
+ * Contains the current message.
+ * in fact, this is the next, still not showed
+ */
+ QMap<const Kopete::Contact*, QDomElement> m_currentElements;
+
+ /**
+ * Get the document, open it is @param canload is true, contain is set to false if the document
+ * is not already contained
+ */
+ QDomDocument getDocument(const Kopete::Contact *c, unsigned int month , bool canLoad=true , bool* contain=0L);
+
+ QDomDocument getDocument(const Kopete::Contact *c, const QDate date, bool canLoad=true, bool* contain=0L);
+
+ /**
+ * look over files to get the last month for this contact
+ */
+ unsigned int getFirstMonth(const Kopete::Contact *c);
+ unsigned int getFirstMonth();
+
+
+ /*
+ * the current month
+ */
+ unsigned int m_currentMonth;
+
+ /*
+ * the cached getFirstMonth
+ */
+ int m_cachedMonth;
+
+
+
+ /*
+ * the metacontact we are using
+ */
+ Kopete::MetaContact *m_metaContact;
+
+ /*
+ * keep the old position in memory, so if we change the sens, we can begin here
+ */
+ QMap<const Kopete::Contact*, QDomElement> m_oldElements;
+ unsigned int m_oldMonth;
+ Sens m_oldSens;
+
+ /**
+ * the timer used to save the file
+ */
+ QTimer *m_saveTimer;
+ QDomDocument m_toSaveDocument;
+ QString m_toSaveFileName;
+ unsigned int m_saveTimerTime; //time in ms between each save
+
+ /**
+ * workaround for the 31 midnight bug.
+ * it contains the number of the current month.
+ */
+ int m_realMonth;
+
+ /*
+ * FIXME:
+ * WORKAROUND
+ * due to a bug in QT, i have to keep the document element in the memory to
+ * prevent crashes
+ */
+ QValueList<QDomElement> workaround;
+
+private slots:
+ /**
+ * the metacontact has been deleted
+ */
+ void slotMCDeleted();
+
+ /**
+ * save the current month's document on the disk.
+ * connected to the m_saveTimer signal
+ */
+ void saveToDisk();
+};
+
+#endif
diff --git a/kopete/plugins/history/historyplugin.cpp b/kopete/plugins/history/historyplugin.cpp
new file mode 100644
index 00000000..bf8d70b4
--- /dev/null
+++ b/kopete/plugins/history/historyplugin.cpp
@@ -0,0 +1,194 @@
+/*
+ historyplugin.cpp
+
+ Copyright (c) 2003-2004 by Olivier Goffart <ogoffart @ kde.org>
+ (c) 2003 by Stefan Gehn <metz AT gehn.net>
+ Kopete (c) 2003-2004 by the Kopete developers <kopete-devel@kde.org>
+
+ *************************************************************************
+ * *
+ * 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 <kgenericfactory.h>
+#include <kaboutdata.h>
+#include <kaction.h>
+#include <kmessagebox.h>
+//#include <kconfig.h>
+#include <kplugininfo.h>
+#include <kdeversion.h>
+
+#include "kopetechatsessionmanager.h"
+#include "kopetemetacontact.h"
+#include "kopeteview.h"
+#include "kopetecontactlist.h"
+#include "kopeteuiglobal.h"
+#include "kopetemessageevent.h"
+#include "kopeteviewplugin.h"
+
+#include "historydialog.h"
+#include "historyplugin.h"
+#include "historylogger.h"
+#include "historyguiclient.h"
+#include "historyconfig.h"
+
+typedef KGenericFactory<HistoryPlugin> HistoryPluginFactory;
+static const KAboutData aboutdata("kopete_history", I18N_NOOP("History") , "1.0" );
+K_EXPORT_COMPONENT_FACTORY( kopete_history, HistoryPluginFactory( &aboutdata ) )
+
+HistoryPlugin::HistoryPlugin( QObject *parent, const char *name, const QStringList & /* args */ )
+: Kopete::Plugin( HistoryPluginFactory::instance(), parent, name ), m_loggerFactory( this )
+{
+ KAction *viewMetaContactHistory = new KAction( i18n("View &History" ),
+ QString::fromLatin1( "history" ), 0, this, SLOT(slotViewHistory()),
+ actionCollection(), "viewMetaContactHistory" );
+ viewMetaContactHistory->setEnabled(
+ Kopete::ContactList::self()->selectedMetaContacts().count() == 1 );
+
+ connect(Kopete::ContactList::self(), SIGNAL(metaContactSelected(bool)),
+ viewMetaContactHistory, SLOT(setEnabled(bool)));
+
+ connect(Kopete::ChatSessionManager::self(), SIGNAL(viewCreated(KopeteView*)),
+ this, SLOT(slotViewCreated(KopeteView*)));
+
+ connect(this, SIGNAL(settingsChanged()), this, SLOT(slotSettingsChanged()));
+
+ setXMLFile("historyui.rc");
+ if(detectOldHistory())
+ {
+ if(
+ KMessageBox::questionYesNo(Kopete::UI::Global::mainWidget(),
+ i18n( "Old history files from Kopete 0.6.x or older has been detected.\n"
+ "Do you want to import and convert it to the new history format?" ),
+ i18n( "History Plugin" ), i18n("Import && Convert"), i18n("Do Not Import") ) == KMessageBox::Yes )
+ {
+ convertOldHistory();
+ }
+ }
+
+ // Add GUI action to all existing kmm objects
+ // (Needed if the plugin is enabled while kopete is already running)
+ QValueList<Kopete::ChatSession*> sessions = Kopete::ChatSessionManager::self()->sessions();
+ for (QValueListIterator<Kopete::ChatSession*> it= sessions.begin(); it!=sessions.end() ; ++it)
+ {
+ if(!m_loggers.contains(*it))
+ {
+ m_loggers.insert(*it, new HistoryGUIClient( *it ) );
+ connect( *it, SIGNAL(closing(Kopete::ChatSession*)),
+ this, SLOT(slotKMMClosed(Kopete::ChatSession*)));
+ }
+ }
+}
+
+
+HistoryPlugin::~HistoryPlugin()
+{
+}
+
+
+void HistoryMessageLogger::handleMessage( Kopete::MessageEvent *event )
+{
+ history->messageDisplayed( event->message() );
+ MessageHandler::handleMessage( event );
+}
+
+void HistoryPlugin::messageDisplayed(const Kopete::Message &m)
+{
+ if(m.direction()==Kopete::Message::Internal || !m.manager())
+ return;
+
+ if(!m_loggers.contains(m.manager()))
+ {
+ m_loggers.insert(m.manager() , new HistoryGUIClient( m.manager() ) );
+ connect(m.manager(), SIGNAL(closing(Kopete::ChatSession*)),
+ this, SLOT(slotKMMClosed(Kopete::ChatSession*)));
+ }
+
+ HistoryLogger *l=m_loggers[m.manager()]->logger();
+ if(l)
+ {
+ QPtrList<Kopete::Contact> mb=m.manager()->members();
+ l->appendMessage(m,mb.first());
+ }
+
+ m_lastmessage=m;
+}
+
+
+void HistoryPlugin::slotViewHistory()
+{
+ Kopete::MetaContact *m=Kopete::ContactList::self()->selectedMetaContacts().first();
+ if(m)
+ {
+ int lines = HistoryConfig::number_ChatWindow();
+
+ // TODO: Keep track of open dialogs and raise instead of
+ // opening a new (duplicated) one
+ new HistoryDialog(m);
+ }
+}
+
+
+void HistoryPlugin::slotViewCreated( KopeteView* v )
+{
+ if(v->plugin()->pluginInfo()->pluginName() != QString::fromLatin1("kopete_chatwindow") )
+ return; //Email chat windows are not supported.
+
+ bool autoChatWindow = HistoryConfig::auto_chatwindow();
+ int nbAutoChatWindow = HistoryConfig::number_Auto_chatwindow();
+
+ KopeteView *m_currentView = v;
+ Kopete::ChatSession *m_currentChatSession = v->msgManager();
+ QPtrList<Kopete::Contact> mb = m_currentChatSession->members();
+
+ if(!m_currentChatSession)
+ return; //i am sorry
+
+ if(!m_loggers.contains(m_currentChatSession))
+ {
+ m_loggers.insert(m_currentChatSession , new HistoryGUIClient( m_currentChatSession ) );
+ connect( m_currentChatSession, SIGNAL(closing(Kopete::ChatSession*)),
+ this , SLOT(slotKMMClosed(Kopete::ChatSession*)));
+ }
+
+ if(!autoChatWindow || nbAutoChatWindow == 0)
+ return;
+
+ HistoryLogger *logger = m_loggers[m_currentChatSession]->logger();
+
+ logger->setPositionToLast();
+
+ QValueList<Kopete::Message> msgs = logger->readMessages(nbAutoChatWindow,
+ /*mb.first()*/ 0L, HistoryLogger::AntiChronological, true, true);
+
+ // make sure the last message is not the one which will be appened right
+ // after the view is created (and which has just been logged in)
+ if(
+ (msgs.last().plainBody() == m_lastmessage.plainBody()) &&
+ (m_lastmessage.manager() == m_currentChatSession))
+ {
+ msgs.remove(msgs.fromLast());
+ }
+
+ m_currentView->appendMessages( msgs );
+}
+
+
+void HistoryPlugin::slotKMMClosed( Kopete::ChatSession* kmm)
+{
+ m_loggers[kmm]->deleteLater();
+ m_loggers.remove(kmm);
+}
+
+void HistoryPlugin::slotSettingsChanged()
+{
+ kdDebug(14310) << k_funcinfo << "RELOADING CONFIG" << endl;
+ HistoryConfig::self()->readConfig();
+}
+
+#include "historyplugin.moc"
diff --git a/kopete/plugins/history/historyplugin.h b/kopete/plugins/history/historyplugin.h
new file mode 100644
index 00000000..63e2c87b
--- /dev/null
+++ b/kopete/plugins/history/historyplugin.h
@@ -0,0 +1,106 @@
+/*
+ historyplugin.h
+
+ Copyright (c) 2003-2005 by Olivier Goffart <ogoffart at kde.org>
+ (c) 2003 by Stefan Gehn <metz AT gehn.net>
+ Kopete (c) 2003-2004 by the Kopete developers <kopete-devel@kde.org>
+
+ *************************************************************************
+ * *
+ * 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. *
+ * *
+ *************************************************************************
+*/
+
+#ifndef HISTORYPLUGIN_H
+#define HISTORYPLUGIN_H
+
+#include <qobject.h>
+#include <qmap.h>
+#include <qstring.h>
+
+#include "kopeteplugin.h"
+
+#include "kopetemessage.h"
+#include "kopetemessagehandler.h"
+
+class KopeteView;
+class KActionCollection;
+
+namespace Kopete
+{
+class MetaContact;
+class ChatSession;
+}
+
+class HistoryPreferences;
+class HistoryGUIClient;
+class HistoryPlugin;
+
+/**
+ * @author Richard Smith
+ */
+class HistoryMessageLogger : public Kopete::MessageHandler
+{
+ HistoryPlugin *history;
+public:
+ HistoryMessageLogger( HistoryPlugin *history ) : history(history) {}
+ void handleMessage( Kopete::MessageEvent *event );
+};
+
+class HistoryMessageLoggerFactory : public Kopete::MessageHandlerFactory
+{
+ HistoryPlugin *history;
+public:
+ HistoryMessageLoggerFactory( HistoryPlugin *history ) : history(history) {}
+ Kopete::MessageHandler *create( Kopete::ChatSession * /*manager*/, Kopete::Message::MessageDirection direction )
+ {
+ if( direction != Kopete::Message::Inbound )
+ return 0;
+ return new HistoryMessageLogger(history);
+ }
+ int filterPosition( Kopete::ChatSession *, Kopete::Message::MessageDirection )
+ {
+ return Kopete::MessageHandlerFactory::InStageToSent+5;
+ }
+};
+
+/**
+ * @author Olivier Goffart
+ */
+class HistoryPlugin : public Kopete::Plugin
+{
+ Q_OBJECT
+ public:
+ HistoryPlugin( QObject *parent, const char *name, const QStringList &args );
+ ~HistoryPlugin();
+
+ /**
+ * convert the Kopete 0.6 / 0.5 history to the new format
+ */
+ static void convertOldHistory();
+ /**
+ * return true if an old history has been detected, and no new ones
+ */
+ static bool detectOldHistory();
+
+ void messageDisplayed(const Kopete::Message &msg);
+
+ private slots:
+ void slotViewCreated( KopeteView* );
+ void slotViewHistory();
+ void slotKMMClosed( Kopete::ChatSession* );
+ void slotSettingsChanged();
+
+ private:
+ HistoryMessageLoggerFactory m_loggerFactory;
+ QMap<Kopete::ChatSession*,HistoryGUIClient*> m_loggers;
+ Kopete::Message m_lastmessage;
+};
+
+#endif
+
+
diff --git a/kopete/plugins/history/historypreferences.cpp b/kopete/plugins/history/historypreferences.cpp
new file mode 100644
index 00000000..61fce469
--- /dev/null
+++ b/kopete/plugins/history/historypreferences.cpp
@@ -0,0 +1,88 @@
+/*
+ historypreferences.cpp
+
+ Copyright (c) 2003 by Olivier Goffart <ogoffart @ kde.org>
+ (c) 2003 by Stefan Gehn <metz AT gehn.net>
+ Kopete (c) 2003-2004 by the Kopete developers <kopete-devel@kde.org>
+
+ *************************************************************************
+ * *
+ * 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 "historypreferences.h"
+#include "historyconfig.h"
+#include "historyprefsui.h"
+
+#include <kgenericfactory.h>
+#include <qlayout.h>
+#include <qgroupbox.h>
+#include <kcolorbutton.h>
+#include <knuminput.h>
+#include <qcheckbox.h>
+
+typedef KGenericFactory<HistoryPreferences> HistoryConfigFactory;
+K_EXPORT_COMPONENT_FACTORY( kcm_kopete_history, HistoryConfigFactory( "kcm_kopete_history" ) )
+
+HistoryPreferences::HistoryPreferences(QWidget *parent, const char*/*name*/, const QStringList &args)
+ : KCModule(HistoryConfigFactory::instance(), parent, args)
+{
+ kdDebug(14310) << k_funcinfo << "called." << endl;
+ (new QVBoxLayout(this))->setAutoAdd(true);
+ p = new HistoryPrefsUI(this);
+
+ connect(p->chkShowPrevious, SIGNAL(toggled(bool)), this, SLOT(slotShowPreviousChanged(bool)));
+ connect(p->Number_Auto_chatwindow, SIGNAL(valueChanged(int)),
+ this, SLOT(slotModified()));
+ connect(p->Number_ChatWindow, SIGNAL(valueChanged(int)),
+ this, SLOT(slotModified()));
+ connect(p->History_color, SIGNAL(changed(const QColor&)),
+ this, SLOT(slotModified()));
+ load();
+}
+
+HistoryPreferences::~HistoryPreferences()
+{
+ kdDebug(14310) << k_funcinfo << "called." << endl;
+}
+
+void HistoryPreferences::load()
+{
+ kdDebug(14310) << k_funcinfo << "called." << endl;
+ HistoryConfig::self()->readConfig();
+ p->chkShowPrevious->setChecked(HistoryConfig::auto_chatwindow());
+ slotShowPreviousChanged(p->chkShowPrevious->isChecked());
+ p->Number_Auto_chatwindow->setValue(HistoryConfig::number_Auto_chatwindow());
+ p->Number_ChatWindow->setValue(HistoryConfig::number_ChatWindow());
+ p->History_color->setColor(HistoryConfig::history_color());
+ //p-> HistoryConfig::browserStyle();
+ emit KCModule::changed(false);
+}
+
+void HistoryPreferences::save()
+{
+ kdDebug(14310) << k_funcinfo << "called." << endl;
+ HistoryConfig::setAuto_chatwindow(p->chkShowPrevious->isChecked());
+ HistoryConfig::setNumber_Auto_chatwindow(p->Number_Auto_chatwindow->value());
+ HistoryConfig::setNumber_ChatWindow(p->Number_ChatWindow->value());
+ HistoryConfig::setHistory_color(p->History_color->color());
+ HistoryConfig::self()->writeConfig();
+ emit KCModule::changed(false);
+}
+
+void HistoryPreferences::slotModified()
+{
+ emit KCModule::changed(true);
+}
+
+void HistoryPreferences::slotShowPreviousChanged(bool on)
+{
+ emit KCModule::changed(true);
+}
+
+#include "historypreferences.moc"
diff --git a/kopete/plugins/history/historypreferences.h b/kopete/plugins/history/historypreferences.h
new file mode 100644
index 00000000..247e2bc8
--- /dev/null
+++ b/kopete/plugins/history/historypreferences.h
@@ -0,0 +1,48 @@
+/*
+ historypreferences.h
+
+ Copyright (c) 2003 by Olivier Goffart <ogoffart @ kde.org>
+ (c) 2003 by Stefan Gehn <metz AT gehn.net>
+ Kopete (c) 2003-2004 by the Kopete developers <kopete-devel@kde.org>
+
+ *************************************************************************
+ * *
+ * 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. *
+ * *
+ *************************************************************************
+*/
+
+#ifndef HISTORYPREFERENCES_H
+#define HISTORYPREFERENCES_H
+
+#include <kcmodule.h>
+#include <qstring.h>
+
+class HistoryPrefsUI;
+
+/**
+ * @author Stefan Gehn
+ */
+class HistoryPreferences : public KCModule
+{
+ Q_OBJECT
+ public:
+ HistoryPreferences(QWidget *parent=0, const char* name=0,
+ const QStringList &args = QStringList());
+ ~HistoryPreferences();
+
+ virtual void save();
+ virtual void load();
+
+ private slots:
+ void slotModified();
+ void slotShowPreviousChanged(bool);
+
+ private:
+ HistoryPrefsUI *p;
+};
+
+#endif
diff --git a/kopete/plugins/history/historyprefsui.ui b/kopete/plugins/history/historyprefsui.ui
new file mode 100644
index 00000000..5942a07a
--- /dev/null
+++ b/kopete/plugins/history/historyprefsui.ui
@@ -0,0 +1,187 @@
+<!DOCTYPE UI><UI version="3.3" stdsetdef="1">
+<class>HistoryPrefsUI</class>
+<author>Olivier Goffart</author>
+<widget class="QWidget">
+ <property name="name">
+ <cstring>HistoryPrefsWidget</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>363</width>
+ <height>212</height>
+ </rect>
+ </property>
+ <property name="caption">
+ <string>HistoryPrefsWidget</string>
+ </property>
+ <vbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QGroupBox">
+ <property name="name">
+ <cstring>grpChatHistory</cstring>
+ </property>
+ <property name="title">
+ <string>Chat History</string>
+ </property>
+ <grid>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QLabel" row="3" column="0">
+ <property name="name">
+ <cstring>lblNoLinesPerPage</cstring>
+ </property>
+ <property name="text">
+ <string>Number of messages per page:</string>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>The number of messages that are shown when browsing history in the chat window</string>
+ </property>
+ </widget>
+ <widget class="KIntSpinBox" row="3" column="1">
+ <property name="name">
+ <cstring>Number_ChatWindow</cstring>
+ </property>
+ <property name="maxValue">
+ <number>32768</number>
+ </property>
+ <property name="minValue">
+ <number>1</number>
+ </property>
+ <property name="value">
+ <number>10</number>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>The number of message that are shown when borwsing history in the chat window</string>
+ </property>
+ </widget>
+ <widget class="QLabel" row="2" column="0">
+ <property name="name">
+ <cstring>colorLabel</cstring>
+ </property>
+ <property name="text">
+ <string>Color of messages:</string>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring>History_color</cstring>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>Color of history messages in the chat window</string>
+ </property>
+ </widget>
+ <widget class="KColorButton" row="2" column="1">
+ <property name="name">
+ <cstring>History_color</cstring>
+ </property>
+ <property name="text">
+ <string></string>
+ </property>
+ <property name="color">
+ <color>
+ <red>170</red>
+ <green>170</green>
+ <blue>127</blue>
+ </color>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>Color of history messages in the chat window</string>
+ </property>
+ </widget>
+ <widget class="KIntSpinBox" row="1" column="1">
+ <property name="name">
+ <cstring>Number_Auto_chatwindow</cstring>
+ </property>
+ <property name="maxValue">
+ <number>32768</number>
+ </property>
+ <property name="minValue">
+ <number>1</number>
+ </property>
+ <property name="value">
+ <number>7</number>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>This is the number of messages that will be added automatically in the chat window when opening a new chat.</string>
+ </property>
+ </widget>
+ <widget class="QLabel" row="1" column="0">
+ <property name="name">
+ <cstring>numberLabel</cstring>
+ </property>
+ <property name="text">
+ <string>Number of messages to show:</string>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring>Number_Auto_chatwindow</cstring>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>This is the number of messages that will be added automatically in the chat window when opening a new chat.</string>
+ </property>
+ </widget>
+ <widget class="QCheckBox" row="0" column="0" rowspan="1" colspan="2">
+ <property name="name">
+ <cstring>chkShowPrevious</cstring>
+ </property>
+ <property name="text">
+ <string>Show chat history in new chats</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>When a new chat is opened, automatically add the last few messages between you and that contact.</string>
+ </property>
+ </widget>
+ </grid>
+ </widget>
+ <spacer>
+ <property name="name">
+ <cstring>spacer2</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Expanding</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>31</width>
+ <height>90</height>
+ </size>
+ </property>
+ </spacer>
+ </vbox>
+</widget>
+<customwidgets>
+</customwidgets>
+<connections>
+ <connection>
+ <sender>chkShowPrevious</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>numberLabel</receiver>
+ <slot>setEnabled(bool)</slot>
+ </connection>
+ <connection>
+ <sender>chkShowPrevious</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>Number_Auto_chatwindow</receiver>
+ <slot>setEnabled(bool)</slot>
+ </connection>
+</connections>
+<tabstops>
+ <tabstop>chkShowPrevious</tabstop>
+ <tabstop>Number_Auto_chatwindow</tabstop>
+ <tabstop>History_color</tabstop>
+</tabstops>
+<layoutdefaults spacing="6" margin="11"/>
+<includehints>
+ <includehint>knuminput.h</includehint>
+ <includehint>kcolorbutton.h</includehint>
+ <includehint>knuminput.h</includehint>
+</includehints>
+</UI>
diff --git a/kopete/plugins/history/historyui.rc b/kopete/plugins/history/historyui.rc
new file mode 100644
index 00000000..5f72b22c
--- /dev/null
+++ b/kopete/plugins/history/historyui.rc
@@ -0,0 +1,12 @@
+<!DOCTYPE kpartgui>
+<kpartgui name="kopete_history" version="1">
+ <MenuBar>
+ <Menu name="edit">
+ <text>&amp;Edit</text>
+ <Action name="viewMetaContactHistory" />
+ </Menu>
+ </MenuBar>
+ <Menu name="contact_popup">
+ <Action name="viewMetaContactHistory" />
+ </Menu>
+</kpartgui>
diff --git a/kopete/plugins/history/historyviewer.ui b/kopete/plugins/history/historyviewer.ui
new file mode 100644
index 00000000..4cef647e
--- /dev/null
+++ b/kopete/plugins/history/historyviewer.ui
@@ -0,0 +1,347 @@
+<!DOCTYPE UI><UI version="3.3" stdsetdef="1">
+<class>HistoryViewer</class>
+<widget class="QWidget">
+ <property name="name">
+ <cstring>HistoryViewer</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>682</width>
+ <height>634</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>5</hsizetype>
+ <vsizetype>5</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>300</width>
+ <height>200</height>
+ </size>
+ </property>
+ <grid>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <widget class="QLayoutWidget" row="3" column="0">
+ <property name="name">
+ <cstring>layout3</cstring>
+ </property>
+ <hbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QLabel">
+ <property name="name">
+ <cstring>statusLabel</cstring>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>32767</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>Ready</string>
+ </property>
+ </widget>
+ <widget class="KProgress">
+ <property name="name">
+ <cstring>searchProgress</cstring>
+ </property>
+ </widget>
+ </hbox>
+ </widget>
+ <widget class="QLayoutWidget" row="2" column="0">
+ <property name="name">
+ <cstring>layout8</cstring>
+ </property>
+ <hbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QPushButton">
+ <property name="name">
+ <cstring>searchErase</cstring>
+ </property>
+ <property name="text">
+ <string></string>
+ </property>
+ <property name="accel">
+ <string></string>
+ </property>
+ </widget>
+ <widget class="QLabel">
+ <property name="name">
+ <cstring>textLabel2</cstring>
+ </property>
+ <property name="text">
+ <string>Search:</string>
+ </property>
+ </widget>
+ <widget class="KLineEdit">
+ <property name="name">
+ <cstring>searchLine</cstring>
+ </property>
+ </widget>
+ <widget class="QPushButton">
+ <property name="name">
+ <cstring>searchButton</cstring>
+ </property>
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>1</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>70</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>150</width>
+ <height>32767</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>Se&amp;arch</string>
+ </property>
+ </widget>
+ </hbox>
+ </widget>
+ <widget class="QSplitter" row="1" column="0">
+ <property name="name">
+ <cstring>splitter2</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Horizontal</enum>
+ </property>
+ <widget class="QLayoutWidget">
+ <property name="name">
+ <cstring>layout5</cstring>
+ </property>
+ <vbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <widget class="KListViewSearchLine">
+ <property name="name">
+ <cstring>dateSearchLine</cstring>
+ </property>
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>5</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>140</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>32767</width>
+ <height>32767</height>
+ </size>
+ </property>
+ </widget>
+ <widget class="KListView">
+ <column>
+ <property name="text">
+ <string>Date</string>
+ </property>
+ <property name="clickable">
+ <bool>true</bool>
+ </property>
+ <property name="resizable">
+ <bool>true</bool>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Contact</string>
+ </property>
+ <property name="clickable">
+ <bool>true</bool>
+ </property>
+ <property name="resizable">
+ <bool>true</bool>
+ </property>
+ </column>
+ <property name="name">
+ <cstring>dateListView</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>5</hsizetype>
+ <vsizetype>7</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>32767</width>
+ <height>32767</height>
+ </size>
+ </property>
+ <property name="rootIsDecorated">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </vbox>
+ </widget>
+ <widget class="QFrame">
+ <property name="name">
+ <cstring>htmlFrame</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>5</hsizetype>
+ <vsizetype>5</vsizetype>
+ <horstretch>10</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="frameShape">
+ <enum>WinPanel</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>Sunken</enum>
+ </property>
+ </widget>
+ </widget>
+ <widget class="QLayoutWidget" row="0" column="0">
+ <property name="name">
+ <cstring>layout11</cstring>
+ </property>
+ <hbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QLabel">
+ <property name="name">
+ <cstring>textLabel1</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>1</hsizetype>
+ <vsizetype>5</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Contact:</string>
+ </property>
+ </widget>
+ <widget class="KComboBox">
+ <property name="name">
+ <cstring>contactComboBox</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>7</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ <widget class="QLabel">
+ <property name="name">
+ <cstring>textLabel1_2</cstring>
+ </property>
+ <property name="text">
+ <string>Message Filter:</string>
+ </property>
+ </widget>
+ <widget class="QComboBox">
+ <item>
+ <property name="text">
+ <string>All messages</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Only incoming</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Only outgoing</string>
+ </property>
+ </item>
+ <property name="name">
+ <cstring>messageFilterBox</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>200</width>
+ <height>0</height>
+ </size>
+ </property>
+ </widget>
+ </hbox>
+ </widget>
+ </grid>
+</widget>
+<customwidgets>
+</customwidgets>
+<layoutdefaults spacing="6" margin="11"/>
+<includehints>
+ <includehint>kprogress.h</includehint>
+ <includehint>klineedit.h</includehint>
+ <includehint>klistviewsearchline.h</includehint>
+ <includehint>klistview.h</includehint>
+ <includehint>kcombobox.h</includehint>
+</includehints>
+</UI>
diff --git a/kopete/plugins/history/kopete_history.desktop b/kopete/plugins/history/kopete_history.desktop
new file mode 100644
index 00000000..5f14aee0
--- /dev/null
+++ b/kopete/plugins/history/kopete_history.desktop
@@ -0,0 +1,139 @@
+[Desktop Entry]
+Type=Service
+X-Kopete-Version=1000900
+Icon=history
+ServiceTypes=Kopete/Plugin
+X-KDE-Library=kopete_history
+X-KDE-PluginInfo-Author=Olivier Goffart
+X-KDE-PluginInfo-Email=ogoffart@tiscalinet.be
+X-KDE-PluginInfo-Name=kopete_history
+X-KDE-PluginInfo-Version=0.8.0
+X-KDE-PluginInfo-Website=http://kopete.kde.org
+X-KDE-PluginInfo-Category=Plugins
+X-KDE-PluginInfo-Depends=
+X-KDE-PluginInfo-License=GPL
+X-KDE-PluginInfo-EnabledByDefault=true
+Name=History
+Name[ar]=محفوظات
+Name[az]=Keçmiş
+Name[be]=Гісторыя
+Name[bg]=История
+Name[bn]=ইতিহাস
+Name[br]=Istor
+Name[bs]=Historija
+Name[ca]=Historial
+Name[cs]=Historie
+Name[cy]=Hanes
+Name[da]=Historik
+Name[de]=Verlauf
+Name[el]=Ιστορικό
+Name[eo]=Historio
+Name[es]=Historia
+Name[et]=Ajalugu
+Name[eu]=Historia
+Name[fa]=تاریخچه
+Name[fi]=Historia
+Name[fr]=Historique
+Name[ga]=Stair
+Name[gl]=Historial
+Name[he]=היסטוריה
+Name[hi]=इतिहास
+Name[hr]=Povijest
+Name[hu]=Üzenetnapló
+Name[id]=Sejarah
+Name[is]=Ferill
+Name[it]=Cronologia
+Name[ja]=履歴
+Name[ka]=ისტორია
+Name[kk]=Журнал
+Name[km]=ប្រវត្តិ
+Name[lt]=Istorija
+Name[lv]=Vēsture
+Name[mk]=Историја
+Name[mt]=Kronoloġija
+Name[nb]=Historie
+Name[nds]=Vörgeschicht
+Name[ne]=इतिहास
+Name[nl]=Geschiedenis
+Name[nn]=Historie
+Name[pa]=ਅਤੀਤ
+Name[pl]=Historia
+Name[pt]=Histórico
+Name[pt_BR]=História
+Name[ro]=Istoric
+Name[ru]=Журнал разговоров
+Name[rw]=Amateka
+Name[se]=Historihkka
+Name[sk]=História
+Name[sl]=Zgodovina
+Name[sr]=Историја
+Name[sr@Latn]=Istorija
+Name[sv]=Historik
+Name[ta]=வரலாறு
+Name[tg]=Номнависи сӯҳбатҳо
+Name[th]=ประวัติการใช้งาน
+Name[tr]=Geçmiş
+Name[uk]=Історія
+Name[uz]=Tarix
+Name[uz@cyrillic]=Тарих
+Name[ven]=Divhazwakale
+Name[wa]=Istwere
+Name[xh]=Imbali
+Name[zh_CN]=历史
+Name[zh_HK]=歷程紀錄
+Name[zh_TW]=歷史
+Name[zu]=Umlando
+Comment=Log all messages to keep track of your conversations
+Comment[ar]=سجل جميع الرسائل للمحافظة على محادثاتك
+Comment[be]=Запісваць усе паведамленні для стварэння дзённікаў гутарак
+Comment[bg]=Запис на всички съобщения с цел преглед и търсене в тях в бъдеще
+Comment[bn]=আপনার কথোপকথনের খতিয়ান রাখতে সব বার্তা কার্যবিবরণীতে লিখে রাখে
+Comment[bs]=Zapiši sve poruke u historiju
+Comment[ca]=Registra tots els missatges per seguir les vostres converses
+Comment[cs]=Záznam konverzace
+Comment[cy]=Cofnodi pob neges er mwyn cadw trefn ar eich sgwrsiau
+Comment[da]=Log alle beskeder for at holde styr på dine konversationer
+Comment[de]=Protokolliert alle Nachrichten der eigenen Gespräche
+Comment[el]=Καταγράψτε όλα τα μηνύματά σας για να διατηρήσετε αρχείο με τις συζητήσεις σας
+Comment[es]=Registra todos los mensajes para guardar sus conversaciones
+Comment[et]=Kõigi sõnumite logimine, et neil ka hiljem silm peal hoida
+Comment[eu]=Gorde mezu guztiak zure elkarrizketak jarrai ahal ditzazun
+Comment[fa]=برای ردگیری مکالمات خود همۀ پیامها را ثبت کنید
+Comment[fi]=Laita kaikki viestisi lokiin
+Comment[fr]=Enregistrer tous les messages pour conserver une trace de vos discussions
+Comment[gl]=Rexitra tódolas mensajex para gardar conversacións
+Comment[he]=שומר תיעוד מסודר שלך כל שיחותיך
+Comment[hi]=आपके वार्तालाप की जानकारी बनाए रखने के लिए सभी संदेशों को लॉग करें
+Comment[hr]=Upisuje u dnevnik sve poruke kako biste vodili evidenciju o svojim razgovorima
+Comment[hu]=Az üzenetek archiválása
+Comment[is]=Halda til haga samskiptaannál
+Comment[it]=Effettua il log di tutti i messaggi in modo da avere traccia delle tue conversazioni
+Comment[ja]=会話を残すためにメッセージのログを取る
+Comment[ka]=ყველა შეტყობინების ჟურნალირება თქვენი საუბრების ჩასაწერად
+Comment[kk]=Хабарласу барысын журналға жазып отыру
+Comment[km]=ចុះ​កំណត់​ហេតុ​សារ​ទាំងអស់ ដើម្បី​តាមដាន​ការ​សន្ទនា​របស់​អ្នក
+Comment[lt]=Įrašinėti visas žinutes ir vesti pokalbių žurnalą
+Comment[mk]=Ги зачувува сите пораки за да ги следите вашите разговори
+Comment[nb]=Logg alle meldinger for å ta vare på samtalene dine
+Comment[nds]=All Narichten för't Nakieken na't Logbook schrieven
+Comment[ne]=तपाईँको वार्तालापको ट्रयाक राख्न सबै सन्देश लग गर्नुहोस्
+Comment[nl]=Bewaar alle berichten in een logboek om uw conversaties later opnieuw te kunnen bekijken
+Comment[nn]=Logg alle meldingar for å ta vare på samtalane dine
+Comment[pl]=Zapisuje wszystkie wiadomości, aby trzymać historię Twoich rozmów
+Comment[pt]=Regista todas as mensagens para manter um registo da sua conversa
+Comment[pt_BR]=Registra todas as mensagens para manter o histórico de suas conversações
+Comment[ru]=Делать записи ваших разговоров в журнале
+Comment[se]=Vurke buot dieđáhusaid vai oaidnit du ságastallamiid
+Comment[sk]=Záznam všetkých správ, aby ste mohli sledovať vaše rozhovory
+Comment[sl]=Beleži vsa sporočila za hranjenje vaših pogovorov
+Comment[sr]=Уписује у дневник све поруке да би сте водили евиденцију о својим разговорима
+Comment[sr@Latn]=Upisuje u dnevnik sve poruke da bi ste vodili evidenciju o svojim razgovorima
+Comment[sv]=Logga alla meddelanden för att hålla ordning på samtalen
+Comment[ta]=உங்கள் உரையாடலை கவனிக்க அனைத்து செய்திகளையும் புகுபதி
+Comment[tg]=Сабти ҳамаи пайёмҳо барои пайгардии ҳамаи сӯҳбатҳои шумо
+Comment[tr]=Konuşmalarınızın kaydedildiği bütün günlük mesajları
+Comment[uk]=Робити записи в журналі для слідкування за вашими розмовами
+Comment[wa]=Wårder on djournå di tos vos messaedjes, po vos poleur rivey li conversåcion
+Comment[zh_CN]=记录您对话的全部消息
+Comment[zh_HK]=記錄所有訊息,讓您能追查您的對話紀錄
+Comment[zh_TW]=紀錄所有對話訊息
diff --git a/kopete/plugins/history/kopete_history_config.desktop b/kopete/plugins/history/kopete_history_config.desktop
new file mode 100644
index 00000000..5ee2d6b2
--- /dev/null
+++ b/kopete/plugins/history/kopete_history_config.desktop
@@ -0,0 +1,141 @@
+[Desktop Entry]
+Icon=history
+Type=Service
+ServiceTypes=KCModule
+
+X-KDE-ModuleType=Library
+X-KDE-Library=kopete_history
+X-KDE-FactoryName=HistoryConfigFactory
+X-KDE-ParentApp=kopete_history
+X-KDE-ParentComponents=kopete_history
+
+Name=History
+Name[ar]=محفوظات
+Name[az]=Keçmiş
+Name[be]=Гісторыя
+Name[bg]=История
+Name[bn]=ইতিহাস
+Name[br]=Istor
+Name[bs]=Historija
+Name[ca]=Historial
+Name[cs]=Historie
+Name[cy]=Hanes
+Name[da]=Historik
+Name[de]=Verlauf
+Name[el]=Ιστορικό
+Name[eo]=Historio
+Name[es]=Historia
+Name[et]=Ajalugu
+Name[eu]=Historia
+Name[fa]=تاریخچه
+Name[fi]=Historia
+Name[fr]=Historique
+Name[ga]=Stair
+Name[gl]=Historial
+Name[he]=היסטוריה
+Name[hi]=इतिहास
+Name[hr]=Povijest
+Name[hu]=Üzenetnapló
+Name[id]=Sejarah
+Name[is]=Ferill
+Name[it]=Cronologia
+Name[ja]=履歴
+Name[ka]=ისტორია
+Name[kk]=Журнал
+Name[km]=ប្រវត្តិ
+Name[lt]=Istorija
+Name[lv]=Vēsture
+Name[mk]=Историја
+Name[mt]=Kronoloġija
+Name[nb]=Historie
+Name[nds]=Vörgeschicht
+Name[ne]=इतिहास
+Name[nl]=Geschiedenis
+Name[nn]=Historie
+Name[pa]=ਅਤੀਤ
+Name[pl]=Historia
+Name[pt]=Histórico
+Name[pt_BR]=História
+Name[ro]=Istoric
+Name[ru]=Журнал разговоров
+Name[rw]=Amateka
+Name[se]=Historihkka
+Name[sk]=História
+Name[sl]=Zgodovina
+Name[sr]=Историја
+Name[sr@Latn]=Istorija
+Name[sv]=Historik
+Name[ta]=வரலாறு
+Name[tg]=Номнависи сӯҳбатҳо
+Name[th]=ประวัติการใช้งาน
+Name[tr]=Geçmiş
+Name[uk]=Історія
+Name[uz]=Tarix
+Name[uz@cyrillic]=Тарих
+Name[ven]=Divhazwakale
+Name[wa]=Istwere
+Name[xh]=Imbali
+Name[zh_CN]=历史
+Name[zh_HK]=歷程紀錄
+Name[zh_TW]=歷史
+Name[zu]=Umlando
+Comment=History Plugin
+Comment[ar]=توصيلة المحفوظات
+Comment[be]=Модуль гісторыі
+Comment[bg]=Приставка за историята
+Comment[bn]=ইতিহাস প্লাগিন
+Comment[br]=Lugent an istorig
+Comment[bs]=Dodatak za historiju
+Comment[ca]=Connector de l'historial
+Comment[cs]=Modul historie
+Comment[cy]=Ategyn Hanes
+Comment[da]=Historik-plugin
+Comment[de]=Verlaufsmodul
+Comment[el]=Πρόσθετο ιστορικού
+Comment[eo]=Historio-kromaĵo
+Comment[es]=Complemento de Historial
+Comment[et]=Ajalooplugin
+Comment[eu]=Historia plugin-a
+Comment[fa]=وصلۀ تاریخچه
+Comment[fi]=Historia-liitännäinen
+Comment[fr]=Module d'historique
+Comment[ga]=Breiseán Staire
+Comment[gl]=Plugin de historial
+Comment[he]=תוסף ההיסטוריה
+Comment[hi]=इतिहास प्लगइन
+Comment[hr]=Umetak za povijest
+Comment[hu]=Előzmények bővítőmodul
+Comment[is]=Ferilsíforrit
+Comment[it]=Plugin cronologia
+Comment[ja]=履歴プラグイン
+Comment[ka]=ისტორიის მოდული
+Comment[kk]=Журнал плагин модулі
+Comment[km]=កម្មវិធី​ជំនួយ​ប្រវត្តិ
+Comment[lt]=Istorijos įskiepis
+Comment[mk]=Приклучок за историја
+Comment[nb]=Programtillegg for historie
+Comment[nds]=Vörgeschichtmoduul
+Comment[ne]=इतिहास प्लगइन
+Comment[nl]=Geschiedenis-plugin
+Comment[nn]=Programtillegg for historie
+Comment[pl]=Wtyczka historii
+Comment[pt]='Plugin' de Historial
+Comment[pt_BR]=Plugin de Histórico
+Comment[ro]=Modul istoric
+Comment[ru]=Модуль журналирования
+Comment[se]=Historihkkalassemoduvla
+Comment[sk]=Modul histórie
+Comment[sl]=Vstavek Zgodovina
+Comment[sr]=Прикључак за историјат
+Comment[sr@Latn]=Priključak za istorijat
+Comment[sv]=Historikinsticksprogram
+Comment[ta]=வரலாற்று செருகல்
+Comment[tg]=Модули Номнависи сӯҳбатҳо
+Comment[tr]=Geçmiş Eklentisi
+Comment[uk]=Втулок історії
+Comment[uz]=Tarix plagini
+Comment[uz@cyrillic]=Тарих плагини
+Comment[wa]=Tchôke-divins del istwere
+Comment[zh_CN]=历史插件
+Comment[zh_HK]=歷程紀錄插件
+Comment[zh_TW]=歷史外掛程式