/* kopetehistorydialog.cpp - Kopete History Dialog Copyright (c) 2002 by Richard Stellingwerff Copyright (c) 2004 by Stefan Gehn Kopete (c) 2002-2004 by the Kopete developers ************************************************************************* * * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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(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 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 = ""; mHtmlPart->begin(); htmlCode = "" + fontStyle + ""; 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 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 dayList = logger.getDaysForMonth(pair.date()); for (unsigned int i=0; idateListView, c2Date, pair.metaContact()); } mMainWidget->searchProgress->advance(1); QTimer::singleShot(0,this,SLOT(slotLoadDays())); } void HistoryDialog::init(Kopete::MetaContact *mc) { QPtrList contacts=mc->contacts(); QPtrListIterator 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(it); if (!item) return; QDate chosenDate = item->date(); HistoryLogger logger(item->metaContact(), this); QValueList msgs=logger.readMessages(chosenDate); setMessages(msgs); } void HistoryDialog::setMessages(QValueList 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::iterator it = msgs.begin(); QString accountLabel; QString resultHTML = "" + (*it).timestamp().date().toString() + "
"; 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 += "


"; resultHTML += "" + (*it).from()->account()->accountLabel() + "
"; } 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(), "" + mMainWidget->searchLine->text() + "", false); } resultHTML += "(" + (*it).timestamp().time().toString() + ") " + ((*it).direction() == Kopete::Message::Outbound ? "textColor().dark().name() + "\">> " : "textColor().light(200).name() + "\">< ") + body + "
"; 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(mMainWidget->dateListView->firstChild()); while (item != 0) { item->setVisible(s); item = static_cast(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("^ ([^<]*)<"); QMap > monthsSearched; QMap > 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(mMainWidget->dateListView->firstChild()); curItem != 0; curItem = static_cast(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 contacts = curItem->metaContact()->contacts(); for(QPtrListIterator 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"