root/trunk/qt4-gui/src/widgets/mlview.cpp

Revision 6450, 10.8 kB (checked in by flynd, 5 months ago)

Added configuration to set history font separately.

  • Property svn:eol-style set to native
Line 
1// -*- c-basic-offset: 2 -*-
2/*
3 * This file is part of Licq, an instant messaging client for UNIX.
4 * Copyright (C) 2002-2006 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// written by Graham Roff <graham@licq.org>
22// contributions by Dirk A. Mueller <dirk@licq.org>
23
24#include "mlview.h"
25
26#include "config.h"
27
28#include <QApplication>
29#include <QClipboard>
30#include <QContextMenuEvent>
31#include <QTextDocumentFragment>
32#include <QMenu>
33#include <QRegExp>
34
35#include "config/emoticons.h"
36#include "config/general.h"
37#include "core/licqgui.h"
38
39using namespace LicqQtGui;
40/* TRANSLATOR LicqQtGui::MLView */
41
42MLView::MLView(QWidget* parent)
43  : QTextBrowser(parent),
44    m_handleLinks(true)
45{
46  setLineWrapMode(QTextEdit::WidgetWidth);
47  setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
48
49  updateFont();
50  connect(Config::General::instance(), SIGNAL(fontChanged()), SLOT(updateFont()));
51}
52
53void MLView::appendNoNewLine(const QString& s)
54{
55  QTextCursor tc = textCursor();
56  tc.movePosition(QTextCursor::End);
57  tc.insertHtml(s);
58}
59
60QString MLView::toRichText(const QString& s, bool highlightURLs, bool useHTML, QRegExp highlight)
61{
62  // Expressions to match URIs and Mail addresses
63  // If no matching should be done, they will be left empty
64  QRegExp reURL;
65  QRegExp reMail;
66
67  // We must hightlight URLs at this step, before we convert
68  // linebreaks to richtext tags and such.  Also, check to make sure
69  // that the text is not prepared to be highlighted already (by AIM).
70  QRegExp reAHREF("<a href", Qt::CaseInsensitive);
71  if (highlightURLs && s.indexOf(reAHREF) == -1)
72  {
73    reURL.setPattern(
74      "(?:(https?|ftp)://(.+(:.+)?@)?|www\\d?\\.)"  // protocoll://[user[:password]@] or www[digit].
75      "[a-z0-9.-]+\\.([a-z]+|[0-9]+)"               // hostname.tld or ip address
76      "(:[0-9]+)?"                                  // optional port
77      "(/(([-\\w%{}|\\\\^~`;/?:@=&$_.+!*'(),#]|\\[|\\])*[^.,:;?!\\s])*)?");
78    reURL.setMinimal(false);
79    reURL.setCaseSensitivity(Qt::CaseInsensitive);
80
81    reMail.setPattern(
82      "(mailto:)?"
83      "[a-z9-0._%+-]+"
84      "@"
85      "[a-z0-9.-]+\\.(?:[a-z]+|[0-9]+)");
86    reMail.setMinimal(false);
87    reMail.setCaseSensitivity(Qt::CaseInsensitive);
88  }
89
90  // The following will parse through the string adding <a> tags to URIs and
91  // Mail addresses while highlighting anything matching the highlight regexp.
92  // If a highlight crosses any <a> or </a> the <span> is closed and reopened.
93  // If URI and mail matches overlap, only the first one will be tagged.
94
95  // Variables to keep track of positions in string while parsing
96  // *Pos and *Len is position and length of next match. *End is end of current
97  // match. *Pos=-2 is used to mark that next match should be found. *End is >0
98  // when a tag is currently open
99  int urlPos = -2;
100  int urlLen = 0;
101  int urlEnd = 0;
102  int mailPos = -2;
103  int mailLen = 0;
104  int mailEnd = 0;
105  int hlPos = -2;
106  int hlLen = 0;
107  int hlEnd = 0;
108
109  // New string build while parsing
110  QString text;
111  // Anything before lastpos has already been copied from input string (s) to text.
112  int lastpos = 0;
113
114  const QString highlightStart = "<span style=\"background-color: yellow; color: black\">";
115  const QString highlightEnd = "</span>";
116
117  do
118  {
119    // Find next match of a regexp but only if it has a pattern defined
120    if (hlPos == -2 && !highlight.isEmpty())
121    {
122      hlPos = s.indexOf(highlight, qMax(hlEnd, lastpos));
123      hlLen = highlight.matchedLength();
124    }
125    if (urlPos == -2 && !reURL.isEmpty())
126    {
127      urlPos = s.indexOf(reURL, qMax(urlEnd, lastpos));
128      urlLen = reURL.matchedLength();
129    }
130    if (mailPos == -2 && !reMail.isEmpty())
131    {
132      mailPos = s.indexOf(reMail, qMax(mailEnd, lastpos));
133      mailLen = reMail.matchedLength();
134    }
135
136    // Next value for lastpos. Data between newpos and lastpos can be copied as is
137    int newpos;
138    // Tags to add after next block of text has been copied
139    QString tags;
140    // Does a highlight need to be closed and reopened to allow other tag to open/close
141    bool breakhl = false;
142
143    if (hlEnd > 0 &&
144        (urlPos < 0 || hlEnd <= urlPos) && (urlEnd == 0 || hlEnd <= urlEnd) &&
145        (mailPos < 0 || hlEnd <= mailPos) && (mailEnd == 0 || hlEnd <= mailEnd))
146    {
147      // End of highlight
148      tags = highlightEnd;
149      newpos = hlEnd;
150      hlEnd = 0;
151    }
152    else if (hlEnd == 0 && hlPos > -1 &&
153        (urlPos < 0 || hlPos < urlPos) && (urlEnd == 0 || hlPos < urlEnd) &&
154        (mailPos < 0 || hlPos < mailPos) && (mailEnd == 0 || hlPos < mailEnd))
155    {
156      // Start of highlight
157      tags = highlightStart;
158      newpos = hlPos;
159      hlEnd = hlPos + hlLen;
160      hlPos = -2; // Trigger search to continue
161    }
162    else if (urlEnd > 0)
163    {
164      // End of URI
165      tags = "</a>";
166      breakhl = true;
167      newpos = urlEnd;
168      urlEnd = 0;
169      mailPos = -2; // Make sure we don't have overlapping URL and mail
170    }
171    else if (mailEnd > 0)
172    {
173      // End of mail
174      tags = "</a>";
175      breakhl = true;
176      newpos = mailEnd;
177      mailEnd = 0;
178      urlPos = -2; // Make sure we don't have overlapping URL and mail
179    }
180    else if (urlPos > -1 &&
181        (mailPos == -1 || urlPos <= mailPos))
182    {
183      // Start of URI
184      QString url = reURL.cap();
185      QString fullurl = (reURL.cap(1).isEmpty() ? QString("http://%1").arg(url) : url);
186      tags = "<a href=\"" + fullurl + "\">";
187      breakhl = true;
188      newpos = urlPos;
189      urlEnd = urlPos + urlLen;
190      urlPos = -2;
191    }
192    else if (mailPos > -1)
193    {
194      // Start of mail
195      QString mail = reMail.cap();
196      QString fullmail = (reMail.cap(1).isEmpty() ? QString("mailto:%1").arg(mail) : mail);
197      tags = "<a href=\"" + fullmail + "\">";
198      breakhl = true;
199      newpos = mailPos;
200      mailEnd = mailPos + mailLen;
201      mailPos = -2;
202    }
203    else
204    {
205      // Nothing more to do, just get the remainder of the string
206      newpos = s.length();
207    }
208
209    // Get next block of text that can be copied from input
210    QString rawtext = s.mid(lastpos, newpos - lastpos);
211    text.append(useHTML ? rawtext : Qt::escape(rawtext));
212
213    // Add tags applicable for this position in the string
214    if (breakhl && hlEnd > 0)
215      tags = highlightEnd + tags + highlightStart;
216    text.append(tags);
217
218    lastpos = newpos;
219  }
220  while (urlEnd > 0 || mailEnd > 0 || hlEnd > 0 || lastpos < s.length());
221
222  Emoticons::self()->parseMessage(text, Emoticons::NormalMode);
223
224  // convert linebreaks to <br>
225  text.replace(QRegExp("\n"), "<br>\n");
226  // We keep the first space character as-is (to allow line wrapping)
227  // and convert the next characters to &nbsp;s (to preserve multiple
228  // spaces).
229  QRegExp longSpaces(" ([ ]+)");
230  QString cap;
231  int pos = 0;
232  while ((pos = longSpaces.indexIn(text)) > -1)
233  {
234    cap = longSpaces.cap(1);
235    cap.replace(QRegExp(" "), "&nbsp;");
236    text.replace(pos+1, longSpaces.matchedLength()-1, cap);
237  }
238  text.replace(QRegExp("\t"), " &nbsp;&nbsp;&nbsp;");
239
240  return text;
241}
242
243void MLView::GotoHome()
244{
245  QTextCursor tc = textCursor();
246  tc.movePosition(QTextCursor::Start);
247  setTextCursor(tc);
248}
249
250void MLView::GotoEnd()
251{
252  QTextCursor tc = textCursor();
253  tc.movePosition(QTextCursor::End);
254  setTextCursor(tc);
255}
256
257void MLView::setBackground(const QColor& c)
258{
259  QPalette pal = palette();
260
261  pal.setColor(QPalette::Active, QPalette::Base, c);
262  pal.setColor(QPalette::Inactive, QPalette::Base, c);
263
264  setPalette(pal);
265}
266
267/** @brief Adds "Copy URL" to the popup menu if the user right clicks on a URL. */
268void MLView::contextMenuEvent(QContextMenuEvent* event)
269{
270  QMenu* menu = createStandardContextMenu();
271
272  m_url = anchorAt(event->pos());
273  if (!m_url.isNull() && !m_url.isEmpty())
274    menu->addAction(tr("Copy URL"), this, SLOT(slotCopyUrl()));
275  if (hasMarkedText())
276    menu->addAction(tr("Quote"), this, SLOT(makeQuote()));
277
278  menu->exec(event->globalPos());
279  delete menu;
280}
281
282/** @brief Adds the contents of m_url to the clipboard. */
283void MLView::slotCopyUrl()
284{
285  if (!m_url.isNull() && !m_url.isEmpty())
286  {
287    // This copies m_url to both the normal clipboard (Ctrl+C/V/X)
288    // and the selection clipboard (paste with middle mouse button).
289    QClipboard *cb = QApplication::clipboard();
290    cb->setText(m_url, QClipboard::Clipboard);
291    if (cb->supportsSelection())
292      cb->setText(m_url, QClipboard::Selection);
293  }
294}
295
296QMimeData* MLView::createMimeDataFromSelection() const
297{
298  QMimeData* result = QTextEdit::createMimeDataFromSelection();
299
300  if (result->hasHtml())
301  {
302    QString html = result->html();
303    Emoticons::unparseMessage(html);
304    QTextDocumentFragment fragment =
305      QTextDocumentFragment::fromHtml(html, document());
306    result->setText(fragment.toPlainText());
307  }
308
309  return result;
310}
311
312void MLView::makeQuote()
313{
314  QTextCursor cr = textCursor();
315  if (!cr.hasSelection())
316    return;
317
318  QString html = cr.selection().toHtml();
319
320  Emoticons::unparseMessage(html);
321
322  QString text = QTextDocumentFragment::fromHtml(html).toPlainText();
323
324  text.insert(0, "> ");
325  text.replace("\n", "\n> ");
326
327  emit quote(text);
328}
329
330void MLView::setForeground(const QColor& c)
331{
332  QPalette pal;
333  pal.setColor(QPalette::WindowText, c);
334  setPalette(pal);
335}
336
337void MLView::setHandleLinks(bool enable)
338{
339  m_handleLinks = enable;
340}
341
342void MLView::setSource(const QUrl& url)
343{
344  if (m_handleLinks && !url.scheme().isEmpty())
345    LicqGui::instance()->viewUrl(url.toString());
346}
347
348bool MLView::hasMarkedText() const
349{
350  return textCursor().hasSelection();
351}
352
353QString MLView::markedText() const
354{
355  return textCursor().selectedText();
356}
357
358void MLView::updateFont()
359{
360  setFont(Config::General::instance()->historyFont());
361
362  // Get height of current font
363  myFontHeight = fontMetrics().height();
364
365  // Set minimum height of text area to one line of text.
366  setMinimumHeight(heightForLines(1));
367}
368
369int MLView::heightForLines(int lines) const
370{
371  // We need to add frame width and the added height of the scroll area as
372  // we're calculating height for the widget, not the viewport.
373  return lines*myFontHeight + height() - viewport()->height() + 2 * frameWidth();
374}
375
376void MLView::setSizeHintLines(int lines)
377{
378  myLinesHint = lines;
379}
380
381QSize MLView::sizeHint() const
382{
383  QSize s = QTextBrowser::sizeHint();
384  if (myLinesHint > 0)
385    s.setHeight(heightForLines(myLinesHint));
386  return s;
387}
Note: See TracBrowser for help on using the browser.