You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdenetwork/kopete/plugins/history/historydialog.cpp

614 lines
20 KiB

/*
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 <tqpushbutton.h>
#include <tqlineedit.h>
#include <tqcheckbox.h>
#include <tqlayout.h>
#include <tqdir.h>
#include <tqdatetime.h>
#include <tqheader.h>
#include <tqlabel.h>
#include <tqclipboard.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, TQDate date, Kopete::MetaContact *mc);
TQDate date() { return mDate; }
Kopete::MetaContact *metaContact() { return mMetaContact; }
public:
int compare(TQListViewItem *i, int col, bool ascending) const;
private:
TQDate mDate;
Kopete::MetaContact *mMetaContact;
};
KListViewDateItem::KListViewDateItem(KListView* parent, TQDate date, Kopete::MetaContact *mc)
: KListViewItem(parent, date.toString(Qt::ISODate), mc->displayName())
{
mDate = date;
mMetaContact = mc;
}
int KListViewDateItem::compare(TQListViewItem *i, int col, bool ascending) const
{
if (col)
return TQListViewItem::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, TQWidget* parent,
const char* name) : KDialogBase(parent, name, false,
i18n("History for %1").tqarg(mc->displayName()), 0), mSearching(false)
{
TQString fontSize;
TQString htmlCode;
TQString fontStyle;
kdDebug(14310) << k_funcinfo << "called." << endl;
setWFlags(TQt::WDestructiveClose); // send TQT_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();
TQPtrListIterator<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(TQFrame::WinPanel | TQFrame::Sunken);
TQVBoxLayout *l = new TQVBoxLayout(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(TQ_NoFocus);
mHtmlView->tqsetSizePolicy(
TQSizePolicy(TQSizePolicy::Expanding, TQSizePolicy::Expanding));
l->addWidget(mHtmlView);
TQTextOStream( &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( TQString::tqfromLatin1( htmlCode.latin1() ) );
mHtmlPart->end();
connect(mHtmlPart->browserExtension(), TQT_SIGNAL(openURLRequestDelayed(const KURL &, const KParts::URLArgs &)),
this, TQT_SLOT(slotOpenURLRequest(const KURL &, const KParts::URLArgs &)));
connect(mMainWidget->dateListView, TQT_SIGNAL(clicked(TQListViewItem*)), this, TQT_SLOT(dateSelected(TQListViewItem*)));
connect(mMainWidget->searchButton, TQT_SIGNAL(clicked()), this, TQT_SLOT(slotSearch()));
connect(mMainWidget->searchLine, TQT_SIGNAL(returnPressed()), this, TQT_SLOT(slotSearch()));
connect(mMainWidget->searchLine, TQT_SIGNAL(textChanged(const TQString&)), this, TQT_SLOT(slotSearchTextChanged(const TQString&)));
connect(mMainWidget->searchErase, TQT_SIGNAL(clicked()), this, TQT_SLOT(slotSearchErase()));
connect(mMainWidget->contactComboBox, TQT_SIGNAL(activated(int)), this, TQT_SLOT(slotContactChanged(int)));
connect(mMainWidget->messageFilterBox, TQT_SIGNAL(activated(int)), this, TQT_SLOT(slotFilterChanged(int )));
connect(mHtmlPart, TQT_SIGNAL(popupMenu(const TQString &, const TQPoint &)), this, TQT_SLOT(slotRightClick(const TQString &, const TQPoint &)));
//initActions
KActionCollection* ac = new KActionCollection(this);
mCopyAct = KStdAction::copy( TQT_TQOBJECT(this), TQT_SLOT(slotCopy()), ac );
mCopyURLAct = new KAction( i18n( "Copy Link Address" ), TQString::tqfromLatin1( "editcopy" ), 0, TQT_TQOBJECT(this), TQT_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, TQT_TQOBJECT(this));
init(mMetaContact);
}
else
{
TQPtrListIterator<Kopete::MetaContact> it(mMetaContactList);
for(; it.current(); ++it)
{
HistoryLogger logger(*it, TQT_TQOBJECT(this));
init(*it);
}
}
initProgressBar(i18n("Loading..."),mInit.dateMCList.count());
TQTimer::singleShot(0,this,TQT_SLOT(slotLoadDays()));
}
void HistoryDialog::slotLoadDays()
{
if(mInit.dateMCList.isEmpty())
{
if (!mMainWidget->searchLine->text().isEmpty())
TQTimer::singleShot(0, this, TQT_SLOT(slotSearch()));
doneProgressBar();
return;
}
DMPair pair(mInit.dateMCList.first());
mInit.dateMCList.pop_front();
HistoryLogger logger(pair.metaContact(), TQT_TQOBJECT(this));
TQValueList<int> dayList = logger.getDaysForMonth(pair.date());
for (unsigned int i=0; i<dayList.count(); i++)
{
TQDate 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);
TQTimer::singleShot(0,this,TQT_SLOT(slotLoadDays()));
}
void HistoryDialog::init(Kopete::MetaContact *mc)
{
TQPtrList<Kopete::Contact> contacts=mc->contacts();
TQPtrListIterator<Kopete::Contact> it( contacts );
for( ; it.current(); ++it )
{
init(*it);
}
}
void HistoryDialog::init(Kopete::Contact *c)
{
// Get year and month list
TQRegExp rx( "\\.(\\d\\d\\d\\d)(\\d\\d)" );
const TQString contact_in_filename=c->contactId().replace( TQRegExp( TQString::tqfromLatin1( "[./~?*]" ) ), TQString::tqfromLatin1( "-" ) );
TQFileInfo *fi;
// BEGIN check if there are Kopete 0.7.x
TQDir d1(locateLocal("data",TQString("kopete/logs/")+
c->protocol()->pluginId().replace( TQRegExp(TQString::tqfromLatin1("[./~?*]")),TQString::tqfromLatin1("-"))
));
d1.setFilter( TQDir::Files | TQDir::NoSymLinks );
d1.setSorting( TQDir::Name );
const TQFileInfoList *list1 = d1.entryInfoList();
if ( list1 != 0 )
{
TQFileInfoListIterator it1( *list1 );
while ( (fi = it1.current()) != 0 )
{
if(fi->fileName().contains(contact_in_filename))
{
rx.search(fi->fileName());
TQDate cDate = TQDate(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
TQString logDir = locateLocal("data",TQString("kopete/logs/")+
c->protocol()->pluginId().replace( TQRegExp(TQString::tqfromLatin1("[./~?*]")),TQString::tqfromLatin1("-")) +
TQString::tqfromLatin1( "/" ) +
c->account()->accountId().replace( TQRegExp( TQString::tqfromLatin1( "[./~?*]" ) ), TQString::tqfromLatin1( "-" ) )
);
TQDir d(logDir);
d.setFilter( TQDir::Files | TQDir::NoSymLinks );
d.setSorting( TQDir::Name );
const TQFileInfoList *list = d.entryInfoList();
if ( list != 0 )
{
TQFileInfoListIterator 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
TQDate cDate = TQDate(rx.cap(1).toInt(), rx.cap(2).toInt(), 1);
DMPair pair(cDate, c->metaContact());
mInit.dateMCList.append(pair);
}
++it;
}
}
}
void HistoryDialog::dateSelected(TQListViewItem* it)
{
KListViewDateItem *item = static_cast<KListViewDateItem*>(it);
if (!item) return;
TQDate chosenDate = item->date();
HistoryLogger logger(item->metaContact(), TQT_TQOBJECT(this));
TQValueList<Kopete::Message> msgs=logger.readMessages(chosenDate);
setMessages(msgs);
}
void HistoryDialog::setMessages(TQValueList<Kopete::Message> msgs)
{
// Clear View
DOM::HTMLElement htmlBody = mHtmlPart->htmlDocument().body();
while(htmlBody.hasChildNodes())
htmlBody.removeChild(htmlBody.childNodes().item(htmlBody.childNodes().length() - 1));
// ----
TQString dir = (TQApplication::reverseLayout() ? TQString::tqfromLatin1("rtl") :
TQString::tqfromLatin1("ltr"));
TQValueList<Kopete::Message>::iterator it = msgs.begin();
TQString accountLabel;
TQString resultHTML = "<b><font color=\"red\">" + (*it).timestamp().date().toString() + "</font></b><br/>";
DOM::HTMLElement newNode = mHtmlPart->document().createElement(TQString::tqfromLatin1("span"));
newNode.setAttribute(TQString::tqfromLatin1("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();
TQString 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(TQString::tqfromLatin1("span"));
newNode.setAttribute(TQString::tqfromLatin1("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 TQString& 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 TQMap.
* Finally, the current date item is checked in the matches TQMap, 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;
TQRegExp rx("^ <msg.*time=\"(\\d+) \\d+:\\d+:\\d+\" >([^<]*)<");
TQMap<TQDate, TQValueList<Kopete::MetaContact*> > monthsSearched;
TQMap<TQDate, TQValueList<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())
)
{
tqApp->processEvents();
if (!mSearching) return;
TQDate 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());
TQPtrList<Kopete::Contact> contacts = curItem->metaContact()->contacts();
for(TQPtrListIterator<Kopete::Contact> it( contacts ); it.current(); ++it)
{
// get filename and open file
TQString filename(HistoryLogger::getFileName(*it, curItem->date()));
if (!TQFile::exists(filename)) continue;
TQFile file(filename);
file.open(IO_ReadOnly);
if (!file.isOpen())
{
kdWarning(14310) << k_funcinfo << "Error opening " <<
file.name() << ": " << TQString(file.errorString()).ascii() << endl;
continue;
}
TQTextStream stream(&file);
TQString 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[TQDate(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 { }
}
tqApp->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").tqarg(mMetaContact->displayName()));
init();
}
}
void HistoryDialog::initProgressBar(const TQString& 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 TQString &url, const TQPoint &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, TQT_SIGNAL( aboutToHide() ), chatWindowPopup, TQT_SLOT( deleteLater() ) );
chatWindowPopup->popup(point);
}
void HistoryDialog::slotCopy()
{
TQString qsSelection;
qsSelection = mHtmlPart->selectedText();
if ( qsSelection.isEmpty() ) return;
disconnect( kapp->clipboard(), TQT_SIGNAL( selectionChanged()), mHtmlPart, TQT_SLOT(slotClearSelection()));
TQApplication::tqclipboard()->setText(qsSelection, TQClipboard::Clipboard);
TQApplication::tqclipboard()->setText(qsSelection, TQClipboard::Selection);
connect( kapp->clipboard(), TQT_SIGNAL( selectionChanged()), mHtmlPart, TQT_SLOT(slotClearSelection()));
}
void HistoryDialog::slotCopyURL()
{
disconnect( kapp->clipboard(), TQT_SIGNAL( selectionChanged()), mHtmlPart, TQT_SLOT(slotClearSelection()));
TQApplication::tqclipboard()->setText( mURL, TQClipboard::Clipboard);
TQApplication::tqclipboard()->setText( mURL, TQClipboard::Selection);
connect( kapp->clipboard(), TQT_SIGNAL( selectionChanged()), mHtmlPart, TQT_SLOT(slotClearSelection()));
}
#include "historydialog.moc"