root/trunk/qt4-gui/src/dialogs/historydlg.cpp

Revision 6466, 16.2 kB (checked in by flynd, 4 months ago)

Use const pointer for owner objects that are fetched only for read access.

Line 
1// -*- c-basic-offset: 2 -*-
2/*
3 * This file is part of Licq, an instant messaging client for UNIX.
4 * Copyright (C) 2007 Licq developers
5 *
6 * Licq is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * Licq is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with Licq; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19 */
20
21#include "historydlg.h"
22
23#include "config.h"
24
25#include <QCheckBox>
26#include <QDialogButtonBox>
27#include <QGroupBox>
28#include <QLabel>
29#include <QLineEdit>
30#include <QHBoxLayout>
31#include <QPushButton>
32#include <QRegExp>
33#include <QShortcut>
34#include <QTextCodec>
35#include <QVBoxLayout>
36
37#include <licq_events.h>
38#include <licq_message.h>
39#include <licq_user.h>
40
41#include "config/chat.h"
42#include "core/licqgui.h"
43#include "core/signalmanager.h"
44#include "core/usermenu.h"
45#include "helpers/eventdesc.h"
46#include "helpers/support.h"
47#include "helpers/usercodec.h"
48#include "widgets/calendar.h"
49#include "widgets/historyview.h"
50
51
52using namespace LicqQtGui;
53/* TRANSLATOR LicqQtGui::HistoryDlg */
54
55HistoryDlg::HistoryDlg(QString id, unsigned long ppid, QWidget* parent)
56  : QDialog(parent),
57    myId(id),
58    myPpid(ppid)
59{
60  Support::setWidgetProps(this, "UserHistoryDialog");
61  setAttribute(Qt::WA_DeleteOnClose, true);
62
63  myIsOwner = (gUserManager.FindOwner(myId.toLatin1(), myPpid) != NULL);
64
65  QVBoxLayout* topLayout = new QVBoxLayout(this);
66
67  // Main content (everything except dialog buttons)
68  QHBoxLayout* mainLayout = new QHBoxLayout();
69  topLayout->addLayout(mainLayout);
70
71  // Left sidebar with calendar and search controls
72  QVBoxLayout* sidebarLayout = new QVBoxLayout();
73  mainLayout->addLayout(sidebarLayout);
74
75  // Calendar for navigating
76  myCalendar = new Calendar();
77  connect(myCalendar, SIGNAL(clicked(const QDate&)), SLOT(calenderClicked()));
78  sidebarLayout->addWidget(myCalendar);
79
80  // Buttons to go to previos/next day with activity
81  QHBoxLayout* navigateLayout = new QHBoxLayout();
82  sidebarLayout->addLayout(navigateLayout);
83  QPushButton* previousDateButton = new QPushButton(tr("&Previous day"));
84  connect(previousDateButton, SIGNAL(clicked()), SLOT(previousDate()));
85  navigateLayout->addWidget(previousDateButton);
86  navigateLayout->addStretch(1);
87  QPushButton* nextDateButton = new QPushButton(tr("&Next day"));
88  connect(nextDateButton, SIGNAL(clicked()), SLOT(nextDate()));
89  navigateLayout->addWidget(nextDateButton);
90
91  // Status label for showing various messages
92  myStatusLabel = new QLabel();
93  sidebarLayout->addWidget(myStatusLabel);
94
95  sidebarLayout->addStretch(1);
96
97  // Controls for searching history
98  QGroupBox* searchGroup = new QGroupBox(tr("Search"));
99  sidebarLayout->addWidget(searchGroup);
100  QVBoxLayout* searchLayout = new QVBoxLayout(searchGroup);
101
102  // Input field for searching
103  QHBoxLayout* patternLayout = new QHBoxLayout();
104  searchLayout->addLayout(patternLayout);
105  QLabel* patternLabel = new QLabel(tr("Find:"));
106  patternLayout->addWidget(patternLabel);
107  myPatternEdit = new QLineEdit();
108  patternLayout->addWidget(myPatternEdit);
109  patternLabel->setBuddy(myPatternEdit);
110
111  // Set focus to search box if user presses slash
112  QShortcut* patternShortcut = new QShortcut(Qt::Key_Slash, this);
113  connect(patternShortcut, SIGNAL(activated()), myPatternEdit, SLOT(setFocus()));
114
115  // Options to control searching
116  myMatchCaseCheck = new QCheckBox(tr("Match &case"));
117  searchLayout->addWidget(myMatchCaseCheck);
118  myRegExpSearchCheck = new QCheckBox(tr("&Regular expression"));
119  searchLayout->addWidget(myRegExpSearchCheck);
120
121  // Find button
122  QHBoxLayout* findLayout = new QHBoxLayout();
123  myFindPrevButton = new QPushButton(tr("F&ind previous"));
124  myFindPrevButton->setEnabled(false);
125  connect(myFindPrevButton, SIGNAL(clicked()), SLOT(findPrevious()));
126  findLayout->addWidget(myFindPrevButton);
127  findLayout->addStretch(1);
128  myFindNextButton = new QPushButton(tr("&Find next"));
129  myFindNextButton->setDefault(true);
130  myFindNextButton->setEnabled(false);
131  connect(myFindNextButton, SIGNAL(clicked()), SLOT(findNext()));
132  findLayout->addWidget(myFindNextButton);
133  searchLayout->addLayout(findLayout);
134  connect(myPatternEdit, SIGNAL(textChanged(const QString&)), SLOT(searchTextChanged(const QString&)));
135  myPatternChanged = true;
136
137  // Shortcuts for searching
138  QShortcut* findPrevShortcut = new QShortcut(Qt::SHIFT + Qt::Key_F3, this);
139  connect(findPrevShortcut, SIGNAL(activated()), SLOT(findPrevious()));
140  QShortcut* findNextShortcut = new QShortcut(Qt::Key_F3, this);
141  connect(findNextShortcut, SIGNAL(activated()), SLOT(findNext()));
142
143  // Widget to show history entries
144  myHistoryView = new HistoryView(true, myId, myPpid);
145  mainLayout->addWidget(myHistoryView, 1);
146
147  // Dialog buttons
148  QHBoxLayout* buttonsLayout = new QHBoxLayout();
149  topLayout->addLayout(buttonsLayout);
150  if (!myIsOwner)
151  {
152    QPushButton* menuButton = new QPushButton(tr("&Menu"));
153    connect(menuButton, SIGNAL(pressed()), SLOT(showUserMenu()));
154    menuButton->setMenu(LicqGui::instance()->userMenu());
155    buttonsLayout->addWidget(menuButton);
156  }
157  QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Close);
158  connect(buttons, SIGNAL(rejected()), SLOT(close()));
159  buttonsLayout->addWidget(buttons);
160
161  show();
162
163  const ICQUser* u = gUserManager.FetchUser(myId.toLatin1(), myPpid, LOCK_R);
164
165  QString name = tr("INVALID USER");
166  myContactCodec = QTextCodec::codecForLocale();
167
168  if (u != NULL)
169  {
170    myContactCodec = UserCodec::codecForICQUser(u);
171
172    name = myContactCodec->toUnicode(u->GetFirstName());
173    QString lastname = myContactCodec->toUnicode(u->GetLastName());
174    if ((!name.isEmpty()) && (!lastname.isEmpty()))
175      name = name + " " + lastname;
176    else
177      name = name + lastname;
178    if (!name.isEmpty())
179      name = " (" + name + ")";
180    name.prepend(QString::fromUtf8(u->GetAlias()));
181  }
182  setWindowTitle(tr("Licq - History ") + name);
183
184  bool validHistory = false;
185
186  if (u == NULL)
187  {
188    myStatusLabel->setText(tr("Invalid user requested"));
189  }
190  // Fetch list of all history entries
191  else if (!u->GetHistory(myHistoryList))
192  {
193    if (u->HistoryFile())
194      myStatusLabel->setText(tr("Error loading history file: %1\nDescription: %2")
195          .arg(u->HistoryFile()).arg(u->HistoryName()));
196    else
197      myStatusLabel->setText(tr("Sorry, history is disabled for this person"));
198  }
199  // No point in doing anything more if history is empty
200  else if (myHistoryList.size() == 0)
201  {
202    myStatusLabel->setText(tr("History is empty"));
203  }
204  else
205  {
206    // No problems that should stop us from continuing
207    validHistory = true;
208  }
209
210  if (!validHistory)
211  {
212    if (u != NULL)
213      gUserManager.DropUser(u);
214    myCalendar->setEnabled(false);
215    previousDateButton->setEnabled(false);
216    nextDateButton->setEnabled(false);
217    myPatternEdit->setEnabled(false);
218    myFindPrevButton->setEnabled(false);
219    myFindNextButton->setEnabled(false);
220    return;
221  }
222
223  myContactName = tr("server");
224  myUseHtml = false;
225
226  if (!myIsOwner)
227    myContactName = QString::fromUtf8(u->GetAlias());
228  for (int x = 0; x < myId.length(); x++)
229  {
230    if (!myId[x].isDigit())
231    {
232      myUseHtml = true;
233      break;
234    }
235  }
236  gUserManager.DropUser(u);
237
238  const ICQOwner* o = gUserManager.FetchOwner(myPpid, LOCK_R);
239  if (o != NULL)
240  {
241    myOwnerName = QString::fromUtf8(o->GetAlias());
242    gUserManager.DropOwner(o);
243  }
244
245  // Mark all dates with activity so they are easier to find
246  for (HistoryListIter item = myHistoryList.begin(); item != myHistoryList.end(); ++item)
247  {
248    QDate date = QDateTime::fromTime_t((*item)->Time()).date();
249    myCalendar->markDate(date);
250  }
251
252  // Limit calendar to dates where we have history entries
253  myCalendar->setMinimumDate(QDateTime::fromTime_t((*myHistoryList.begin())->Time()).date());
254  QDate lastDate = QDateTime::fromTime_t((*(--myHistoryList.end()))->Time()).date();
255  myCalendar->setMaximumDate(lastDate);
256  myCalendar->setSelectedDate(lastDate);
257  calenderClicked();
258
259  // Catch sent messages and add them to history
260  connect(LicqGui::instance(), SIGNAL(eventSent(const ICQEvent*)),
261      SLOT(eventSent(const ICQEvent*)));
262
263  // Catch received messages so we can add them to history
264  connect(LicqGui::instance()->signalManager(), SIGNAL(updatedUser(CICQSignal*)), SLOT(updatedUser(CICQSignal*)));
265}
266
267HistoryDlg::~HistoryDlg()
268{
269  ICQUser::ClearHistory(myHistoryList);
270}
271
272void HistoryDlg::updatedUser(CICQSignal* signal)
273{
274  if (signal->Id() != myId || signal->PPID() != myPpid)
275    return;
276
277  if (signal->SubSignal() == USER_EVENTS)
278  {
279    const ICQUser* u = gUserManager.FetchUser(myId.toLatin1(), myPpid, LOCK_R);
280    if (u == NULL)
281      return;
282
283    const CUserEvent* event = u->EventPeekId(signal->Argument());
284    gUserManager.DropUser(u);
285
286    if (event != NULL && signal->Argument() > 0 && signal->Argument() > (*(--myHistoryList.end()))->Id())
287      addMsg(event);
288  }
289}
290
291void HistoryDlg::eventSent(const ICQEvent* event)
292{
293  if (event->Id() == myId && event->PPID() == myPpid && event->UserEvent() != NULL)
294    addMsg(event->UserEvent());
295}
296
297void HistoryDlg::addMsg(const CUserEvent* event)
298{
299  CUserEvent* eventCopy = event->Copy();
300  myHistoryList.push_back(eventCopy);
301  QDate date = QDateTime::fromTime_t(event->Time()).date();
302  myCalendar->markDate(date);
303  myCalendar->setMaximumDate(date);
304}
305
306QRegExp HistoryDlg::getRegExp() const
307{
308  // Since QRegExp has a FixedString mode we can use it for normal search also
309  return QRegExp(
310      myPatternEdit->text(),
311      (myMatchCaseCheck->isChecked() ? Qt::CaseSensitive : Qt::CaseInsensitive),
312      (myRegExpSearchCheck->isChecked() ? QRegExp::RegExp2 : QRegExp::FixedString));
313}
314
315void HistoryDlg::showHistory()
316{
317  if (myHistoryList.size() == 0)
318    return;
319
320  myHistoryView->clear();
321  myHistoryView->setReverse(Config::Chat::instance()->reverseHistory());
322
323  QDateTime date;
324
325  // Go through all entries in the list
326  for (HistoryListIter item = myHistoryList.begin(); item != myHistoryList.end(); ++item)
327  {
328    date.setTime_t((*item)->Time());
329
330    // Skip those that aren't from the selected date
331    if (date.date() != myCalendar->selectedDate())
332      continue;
333
334    QString messageText;
335    if ((*item)->SubCommand() == ICQ_CMDxSUB_SMS) // SMSs are always in UTF-8
336      messageText = QString::fromUtf8((*item)->Text());
337    else
338      messageText = myContactCodec->toUnicode((*item)->Text());
339
340    QString name = (*item)->Direction() == D_RECEIVER ? myContactName : myOwnerName;
341
342    QRegExp highlight;
343
344    // Check if this is the entry we've searched for
345    if (item == mySearchPos)
346    {
347      highlight = getRegExp();
348      highlight.setMinimal(true);
349    }
350    messageText = HistoryView::toRichText(messageText, true, myUseHtml, highlight);
351
352    // Add entry to history view
353    myHistoryView->addMsg((*item)->Direction(), false,
354        ((*item)->SubCommand() == ICQ_CMDxSUB_MSG ? "" : (EventDescription(*item) + " ")),
355        date,
356        (*item)->IsDirect(),
357        (*item)->IsMultiRec(),
358        (*item)->IsUrgent(),
359        (*item)->IsEncrypted(),
360        name,
361        messageText,
362        (item == mySearchPos ? "SearchHit" : QString()));
363  }
364
365  // Tell history view to update in case it is buffered
366  myHistoryView->updateContent();
367}
368
369void HistoryDlg::calenderClicked()
370{
371  // Clear search position
372  mySearchPos = myHistoryList.end();
373
374  myStatusLabel->setText(QString());
375  showHistory();
376}
377
378void HistoryDlg::findNext()
379{
380  find(false);
381}
382
383void HistoryDlg::findPrevious()
384{
385  find(true);
386}
387
388void HistoryDlg::find(bool backwards)
389{
390  if (myPatternEdit->text().isEmpty())
391    return;
392
393  QRegExp regExp(getRegExp());
394
395  // If search pattern has changed, find all matching dates and mark them in the calendar
396  if (myPatternChanged)
397  {
398    myCalendar->clearMatches();
399
400    for (HistoryListIter i = myHistoryList.begin(); i != myHistoryList.end(); ++i)
401    {
402      QString messageText;
403      if ((*i)->SubCommand() == ICQ_CMDxSUB_SMS) // SMSs are always in UTF-8
404        messageText = QString::fromUtf8((*i)->Text());
405      else
406        messageText = myContactCodec->toUnicode((*i)->Text());
407
408      if (messageText.contains(regExp))
409      {
410        QDate date = QDateTime::fromTime_t((*i)->Time()).date();
411        myCalendar->addMatch(date);
412      }
413    }
414
415    // No need to do this again next time
416    myPatternChanged = false;
417  }
418
419  myStatusLabel->setText(QString());
420
421  // If this is first search we need to find an entry to start searching from
422  if (mySearchPos == myHistoryList.end())
423  {
424    for (mySearchPos = myHistoryList.begin(); mySearchPos != myHistoryList.end(); ++mySearchPos)
425    {
426      QDate date = QDateTime::fromTime_t((*mySearchPos)->Time()).date();
427
428      // When searching backwards, set start to first entry after current day
429      if (date > myCalendar->selectedDate())
430        break;
431
432      // When searching forwards, set start to last entry before current day
433      if (!backwards && date >= myCalendar->selectedDate())
434        break;
435    }
436
437    // Back one step to actually get entry before current day
438    if (!backwards)
439      --mySearchPos;
440  }
441
442  // Remember where we started so we can stop after checking all entries once
443  HistoryListIter startPos = mySearchPos;
444
445  while (true)
446  {
447    if (backwards)
448      --mySearchPos;
449    else
450      ++mySearchPos;
451
452    // end is outside list so don't try to match it
453    if (mySearchPos != myHistoryList.end())
454    {
455      QString messageText;
456      if ((*mySearchPos)->SubCommand() == ICQ_CMDxSUB_SMS) // SMSs are always in UTF-8
457        messageText = QString::fromUtf8((*mySearchPos)->Text());
458      else
459        messageText = myContactCodec->toUnicode((*mySearchPos)->Text());
460
461      if (messageText.contains(regExp))
462        // We have a match
463        break;
464    }
465
466    if (mySearchPos == startPos)
467    {
468      myStatusLabel->setText(tr("Search returned no matches"));
469      myPatternEdit->setStyleSheet("background: red");
470      return;
471    }
472
473    if (mySearchPos == myHistoryList.end())
474    {
475      myStatusLabel->setText(tr("Search wrapped around"));
476
477      // Iterator wraps around between begin and end so no extra handling needed
478      continue;
479    }
480  }
481
482  QDate date = QDateTime::fromTime_t((*mySearchPos)->Time()).date();
483  myCalendar->setSelectedDate(date);
484  showHistory();
485  myHistoryView->scrollToAnchor("SearchHit");
486}
487
488void HistoryDlg::searchTextChanged(const QString& text)
489{
490  // Disable search buttons if there is no text in the search box
491  myFindNextButton->setEnabled(!text.isEmpty());
492  myFindPrevButton->setEnabled(!text.isEmpty());
493
494  // Clear failed status from previous search
495  myPatternEdit->setStyleSheet("");
496
497  // Mark that pattern has changed since previous search
498  myPatternChanged = true;
499
500  // Search field is cleared so clear status message and matching dates
501  if (text.isEmpty())
502  {
503    myStatusLabel->setText(QString());
504    myCalendar->clearMatches();
505  }
506}
507
508void HistoryDlg::showUserMenu()
509{
510  LicqGui::instance()->userMenu()->setUser(myId, myPpid);
511}
512
513void HistoryDlg::nextDate()
514{
515  QDateTime date;
516  HistoryListIter item;
517
518  // Find first entry in next date
519  for (item = myHistoryList.begin(); item != myHistoryList.end(); ++item)
520  {
521    date.setTime_t((*item)->Time());
522
523    // Stop when we find an entry with date later then current
524    if (date.date() > myCalendar->selectedDate())
525      break;
526  }
527
528  // No later date found so go to oldest entry
529  if (item == myHistoryList.end())
530    date.setTime_t((*myHistoryList.begin())->Time());
531
532  myCalendar->setSelectedDate(date.date());
533  calenderClicked();
534}
535
536void HistoryDlg::previousDate()
537{
538  QDateTime date;
539  HistoryListIter item;
540
541  // Find first entry in next date
542  for (item = myHistoryList.begin(); item != myHistoryList.end(); ++item)
543  {
544    date.setTime_t((*item)->Time());
545
546    // Stop when we find an entry with date later then current
547    if (date.date() >= myCalendar->selectedDate())
548      break;
549  }
550
551  // Go back to last entry of previous day
552  --item;
553
554  // No earlier date, go to last
555  if (item == myHistoryList.end())
556    --item;
557
558  date.setTime_t((*item)->Time());
559
560  myCalendar->setSelectedDate(date.date());
561  calenderClicked();
562}
Note: See TracBrowser for help on using the browser.