/* 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, 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(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").arg(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 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->setSizePolicy( TQSizePolicy(TQSizePolicy::Expanding, TQSizePolicy::Expanding)); l->addWidget(mHtmlView); TQTextOStream( &fontSize ) << KopetePrefs::prefs()->fontFace().pointSize(); fontStyle = ""; mHtmlPart->begin(); htmlCode = "" + fontStyle + ""; mHtmlPart->write( TQString::fromLatin1( 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::fromLatin1( "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 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 dayList = logger.getDaysForMonth(pair.date()); for (unsigned int i=0; idateListView, c2Date, pair.metaContact()); } mMainWidget->searchProgress->advance(1); TQTimer::singleShot(0,this,TQT_SLOT(slotLoadDays())); } void HistoryDialog::init(Kopete::MetaContact *mc) { TQPtrList contacts=mc->contacts(); TQPtrListIterator 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::fromLatin1( "[./~?*]" ) ), TQString::fromLatin1( "-" ) ); TQFileInfo *fi; // BEGIN check if there are Kopete 0.7.x TQDir d1(locateLocal("data",TQString("kopete/logs/")+ c->protocol()->pluginId().replace( TQRegExp(TQString::fromLatin1("[./~?*]")),TQString::fromLatin1("-")) )); 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::fromLatin1("[./~?*]")),TQString::fromLatin1("-")) + TQString::fromLatin1( "/" ) + c->account()->accountId().replace( TQRegExp( TQString::fromLatin1( "[./~?*]" ) ), TQString::fromLatin1( "-" ) ) ); 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(it); if (!item) return; TQDate chosenDate = item->date(); HistoryLogger logger(item->metaContact(), TQT_TQOBJECT(this)); TQValueList msgs=logger.readMessages(chosenDate); setMessages(msgs); } void HistoryDialog::setMessages(TQValueList 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::fromLatin1("rtl") : TQString::fromLatin1("ltr")); TQValueList::iterator it = msgs.begin(); TQString accountLabel; TQString resultHTML = "" + (*it).timestamp().date().toString() + "
"; DOM::HTMLElement newNode = mHtmlPart->document().createElement(TQString::fromLatin1("span")); newNode.setAttribute(TQString::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(); 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(), "" + 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(TQString::fromLatin1("span")); newNode.setAttribute(TQString::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 TQString& 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 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("^ ([^<]*)<"); TQMap > monthsSearched; TQMap > 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()) ) { 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 contacts = curItem->metaContact()->contacts(); for(TQPtrListIterator 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").arg(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::clipboard()->setText(qsSelection, TQClipboard::Clipboard); TQApplication::clipboard()->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::clipboard()->setText( mURL, TQClipboard::Clipboard); TQApplication::clipboard()->setText( mURL, TQClipboard::Selection); connect( kapp->clipboard(), TQT_SIGNAL( selectionChanged()), mHtmlPart, TQT_SLOT(slotClearSelection())); } #include "historydialog.moc"