1 | /*
|
---|
2 | * psitextview.cpp - Icon-aware QTextView subclass widget
|
---|
3 | * Copyright (C) 2003 Michail Pishchagin
|
---|
4 | *
|
---|
5 | * This library is free software; you can redistribute it and/or
|
---|
6 | * modify it under the terms of the GNU Lesser General Public
|
---|
7 | * License as published by the Free Software Foundation; either
|
---|
8 | * version 2.1 of the License, or (at your option) any later version.
|
---|
9 | *
|
---|
10 | * This library is distributed in the hope that it will be useful,
|
---|
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
---|
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
---|
13 | * Lesser General Public License for more details.
|
---|
14 | *
|
---|
15 | * You should have received a copy of the GNU Lesser General Public
|
---|
16 | * License along with this library; if not, write to the Free Software
|
---|
17 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
---|
18 | *
|
---|
19 | */
|
---|
20 |
|
---|
21 | #include "psitextview.h"
|
---|
22 |
|
---|
23 | #include <qpopupmenu.h>
|
---|
24 | #include <qapplication.h>
|
---|
25 | #include <qclipboard.h>
|
---|
26 | #include <qtooltip.h>
|
---|
27 |
|
---|
28 | #include <qstylesheet.h>
|
---|
29 | #include <private/qrichtext_p.h>
|
---|
30 |
|
---|
31 | #ifndef WIDGET_PLUGIN
|
---|
32 | #include "iconset.h"
|
---|
33 | #else
|
---|
34 | class Icon;
|
---|
35 | class Iconset;
|
---|
36 | #endif
|
---|
37 |
|
---|
38 | //----------------------------------------------------------------------------
|
---|
39 | // URLObject - helper class for handling links
|
---|
40 | //----------------------------------------------------------------------------
|
---|
41 |
|
---|
42 | class URLObject : public QObject
|
---|
43 | {
|
---|
44 | Q_OBJECT
|
---|
45 |
|
---|
46 | public:
|
---|
47 | URLObject(QObject *parent = 0)
|
---|
48 | : QObject(qApp) { Q_UNUSED(parent); }
|
---|
49 |
|
---|
50 | QPopupMenu *createPopupMenu(const QString &lnk);
|
---|
51 |
|
---|
52 | private:
|
---|
53 | QString link;
|
---|
54 |
|
---|
55 | QString copyString(QString from)
|
---|
56 | {
|
---|
57 | QString l = from;
|
---|
58 |
|
---|
59 | int colon = l.find(':');
|
---|
60 | if ( colon == -1 )
|
---|
61 | colon = 0;
|
---|
62 | QString service = l.left( colon );
|
---|
63 |
|
---|
64 | if ( service == "mailto" || service == "jabber" || service == "jid" || service == "xmpp" ) {
|
---|
65 | if ( colon > -1 )
|
---|
66 | l = l.mid( colon + 1 );
|
---|
67 |
|
---|
68 | while ( l[0] == '/' )
|
---|
69 | l = l.mid( 1 );
|
---|
70 | }
|
---|
71 |
|
---|
72 | return l;
|
---|
73 | }
|
---|
74 |
|
---|
75 | signals:
|
---|
76 | void openURL(QString);
|
---|
77 |
|
---|
78 | public slots:
|
---|
79 | void popupAction(QString lnk) {
|
---|
80 | emit openURL(lnk);
|
---|
81 | }
|
---|
82 |
|
---|
83 | void popupAction() {
|
---|
84 | popupAction(link);
|
---|
85 | }
|
---|
86 |
|
---|
87 | void popupCopy(QString lnk) {
|
---|
88 | QApplication::clipboard()->setText( copyString(lnk), QClipboard::Clipboard );
|
---|
89 | if(QApplication::clipboard()->supportsSelection())
|
---|
90 | QApplication::clipboard()->setText( copyString(lnk), QClipboard::Selection );
|
---|
91 | }
|
---|
92 |
|
---|
93 | void popupCopy() {
|
---|
94 | popupCopy(link);
|
---|
95 | }
|
---|
96 | };
|
---|
97 |
|
---|
98 | static URLObject *urlObject = 0;
|
---|
99 |
|
---|
100 | QPopupMenu *URLObject::createPopupMenu(const QString &lnk)
|
---|
101 | {
|
---|
102 | link = lnk;
|
---|
103 | if ( link.isEmpty() )
|
---|
104 | return 0;
|
---|
105 |
|
---|
106 | int colon = link.find(':');
|
---|
107 | if ( colon == -1 )
|
---|
108 | colon = 0;
|
---|
109 | QString service = link.left( colon );
|
---|
110 |
|
---|
111 | QString action = "ERROR";
|
---|
112 | QString iconName;
|
---|
113 |
|
---|
114 | if ( service == "mailto" ) {
|
---|
115 | action = URLLabel::tr("Open mail composer");
|
---|
116 | iconName = "psi/email";
|
---|
117 | }
|
---|
118 | else if ( service == "jabber" || service == "jid" || service == "xmpp" ) {
|
---|
119 | // TODO: need more actions to jabber item. Ex: "add to roster", "send message"
|
---|
120 | action = URLLabel::tr("Add to Roster");
|
---|
121 | iconName = "psi/add";
|
---|
122 | }
|
---|
123 | else { //if ( service == "http" || service == "https" || service.isEmpty() ) {
|
---|
124 | action = URLLabel::tr("Open web browser");
|
---|
125 | iconName = "psi/www";
|
---|
126 | }
|
---|
127 |
|
---|
128 | #ifndef WIDGET_PLUGIN
|
---|
129 | const Icon *icon = 0;
|
---|
130 | if ( !iconName.isEmpty() )
|
---|
131 | icon = IconsetFactory::iconPtr( iconName );
|
---|
132 | #endif
|
---|
133 |
|
---|
134 | QPopupMenu *m = new QPopupMenu;
|
---|
135 |
|
---|
136 | #ifndef WIDGET_PLUGIN
|
---|
137 | if ( icon )
|
---|
138 | m->insertItem(/*QIconSet(*/ *icon /*)*/, action, this, SLOT(popupAction()));
|
---|
139 | else
|
---|
140 | #endif
|
---|
141 | m->insertItem(action, this, SLOT(popupAction()));
|
---|
142 | m->insertItem(URLLabel::tr("Copy location"), this, SLOT(popupCopy()));
|
---|
143 |
|
---|
144 | return m;
|
---|
145 | }
|
---|
146 |
|
---|
147 | //----------------------------------------------------------------------------
|
---|
148 | // TextIcon - helper class for PsiTextView
|
---|
149 | //----------------------------------------------------------------------------
|
---|
150 |
|
---|
151 | class TextIcon : public QObject, public QTextCustomItem
|
---|
152 | {
|
---|
153 | Q_OBJECT
|
---|
154 |
|
---|
155 | public:
|
---|
156 | TextIcon(QTextDocument *p, const QMap<QString, QString> &attr, const QString &context, QMimeSourceFactory &factory);
|
---|
157 | ~TextIcon();
|
---|
158 |
|
---|
159 | void adjustToPainter(QPainter *);
|
---|
160 | void draw(QPainter *p, int x, int y, int cx, int cy, int cw, int ch, const QColorGroup &cg, bool selected);
|
---|
161 | QString richText() const;
|
---|
162 |
|
---|
163 | Placement placement() const { return place; }
|
---|
164 | int minimumWidth() const { return width; }
|
---|
165 |
|
---|
166 | private slots:
|
---|
167 | void iconUpdated(const QPixmap &);
|
---|
168 |
|
---|
169 | private:
|
---|
170 | Placement place;
|
---|
171 | int tmpwidth, tmpheight;
|
---|
172 | QMap<QString, QString> attributes;
|
---|
173 | QString imgId;
|
---|
174 | Icon *icon;
|
---|
175 | };
|
---|
176 |
|
---|
177 | TextIcon::TextIcon(QTextDocument *p, const QMap<QString, QString> &attr, const QString &context, QMimeSourceFactory &factory)
|
---|
178 | : QObject(0, 0), QTextCustomItem (p)
|
---|
179 | {
|
---|
180 | Q_UNUSED(context);
|
---|
181 | Q_UNUSED(factory);
|
---|
182 |
|
---|
183 | width = height = 0;
|
---|
184 |
|
---|
185 | icon = 0;
|
---|
186 | QString iconName = attr["name"];
|
---|
187 |
|
---|
188 | if ( iconName.isEmpty() )
|
---|
189 | iconName = attr["src"];
|
---|
190 |
|
---|
191 | if ( iconName.isEmpty() )
|
---|
192 | iconName = attr["source"];
|
---|
193 |
|
---|
194 | #ifndef WIDGET_PLUGIN
|
---|
195 | if ( !iconName.isEmpty() ) {
|
---|
196 | icon = (Icon *)IconsetFactory::iconPtr( iconName );
|
---|
197 | if ( icon )
|
---|
198 | icon = new Icon(*icon);
|
---|
199 |
|
---|
200 | if ( icon ) {
|
---|
201 | icon->activated();
|
---|
202 | connect(icon, SIGNAL(pixmapChanged(const QPixmap &)), SLOT(iconUpdated(const QPixmap &)));
|
---|
203 |
|
---|
204 | width = icon->pixmap().width();
|
---|
205 | height = icon->pixmap().height();
|
---|
206 | }
|
---|
207 | }
|
---|
208 | #endif
|
---|
209 |
|
---|
210 | if ( !icon && (width*height)==0 )
|
---|
211 | width = height = 50;
|
---|
212 |
|
---|
213 | place = PlaceInline;
|
---|
214 | if ( attr["align"] == "left" )
|
---|
215 | place = PlaceLeft;
|
---|
216 | else if ( attr["align"] == "right" )
|
---|
217 | place = PlaceRight;
|
---|
218 |
|
---|
219 | tmpwidth = width;
|
---|
220 | tmpheight = height;
|
---|
221 |
|
---|
222 | attributes = attr;
|
---|
223 | }
|
---|
224 |
|
---|
225 | TextIcon::~TextIcon()
|
---|
226 | {
|
---|
227 | #ifndef WIDGET_PLUGIN
|
---|
228 | if ( icon ) {
|
---|
229 | icon->stop();
|
---|
230 | delete icon;
|
---|
231 | }
|
---|
232 | #endif
|
---|
233 | }
|
---|
234 |
|
---|
235 | void TextIcon::adjustToPainter(QPainter *)
|
---|
236 | {
|
---|
237 | // FIXME: This class can be incorrectly printed. Check this sometime later.
|
---|
238 | }
|
---|
239 |
|
---|
240 | void TextIcon::draw(QPainter *p, int x, int y, int cx, int cy, int cw, int ch, const QColorGroup &cg, bool /*selected*/)
|
---|
241 | {
|
---|
242 | if ( placement() != PlaceInline ) {
|
---|
243 | x = xpos;
|
---|
244 | y = ypos;
|
---|
245 | }
|
---|
246 |
|
---|
247 | if ( !icon ) {
|
---|
248 | p->fillRect( x, y, width, height, cg.dark() );
|
---|
249 | return;
|
---|
250 | }
|
---|
251 |
|
---|
252 | if ( placement() != PlaceInline && !QRect( xpos, ypos, width, height ).intersects( QRect( cx, cy, cw, ch ) ) )
|
---|
253 | return;
|
---|
254 |
|
---|
255 | #ifndef WIDGET_PLUGIN
|
---|
256 | if ( placement() == PlaceInline )
|
---|
257 | p->drawPixmap( x , y, icon->pixmap() );
|
---|
258 | else
|
---|
259 | p->drawPixmap( cx , cy, icon->pixmap(), cx - x, cy - y, cw, ch );
|
---|
260 | #endif
|
---|
261 | }
|
---|
262 |
|
---|
263 | void TextIcon::iconUpdated(const QPixmap &)
|
---|
264 | {
|
---|
265 | // TODO: any ideas what it should do? ;-)
|
---|
266 | }
|
---|
267 |
|
---|
268 | QString TextIcon::richText() const
|
---|
269 | {
|
---|
270 | QString s;
|
---|
271 | s += "<icon ";
|
---|
272 | QMap<QString, QString>::ConstIterator it = attributes.begin();
|
---|
273 | for ( ; it != attributes.end(); ++it ) {
|
---|
274 | s += it.key() + "=";
|
---|
275 | if ( (*it).find( ' ' ) != -1 )
|
---|
276 | s += "\"" + *it + "\"" + " ";
|
---|
277 | else
|
---|
278 | s += *it + " ";
|
---|
279 | }
|
---|
280 | s += ">";
|
---|
281 | return s;
|
---|
282 | }
|
---|
283 |
|
---|
284 | //----------------------------------------------------------------------------
|
---|
285 | // PsiStyleSheet - helper class for PsiTextView
|
---|
286 | //----------------------------------------------------------------------------
|
---|
287 |
|
---|
288 | class PsiStyleSheet : public QStyleSheet
|
---|
289 | {
|
---|
290 | Q_OBJECT
|
---|
291 | private:
|
---|
292 | PsiStyleSheet(QObject *parent = 0, const char *name = 0)
|
---|
293 | : QStyleSheet(parent, name)
|
---|
294 | {
|
---|
295 | new QStyleSheetItem(this, QString::fromLatin1("icon"));
|
---|
296 | }
|
---|
297 |
|
---|
298 | public:
|
---|
299 | QTextCustomItem *tag(const QString &name, const QMap<QString, QString> &attr,
|
---|
300 | const QString &context,
|
---|
301 | const QMimeSourceFactory &factory,
|
---|
302 | bool emptyTag, QTextDocument *doc ) const
|
---|
303 | {
|
---|
304 | const QStyleSheetItem *style = item( name );
|
---|
305 | if ( style && style->name() == "icon" )
|
---|
306 | return new TextIcon( doc, attr, context, (QMimeSourceFactory&)factory );
|
---|
307 | return QStyleSheet::tag(name, attr, context, factory, emptyTag, doc);
|
---|
308 | }
|
---|
309 |
|
---|
310 | static PsiStyleSheet *self();
|
---|
311 |
|
---|
312 | private:
|
---|
313 | static PsiStyleSheet *psiStyleSheet;
|
---|
314 | };
|
---|
315 |
|
---|
316 | PsiStyleSheet *PsiStyleSheet::psiStyleSheet = 0;
|
---|
317 |
|
---|
318 | PsiStyleSheet *PsiStyleSheet::self()
|
---|
319 | {
|
---|
320 | if ( !psiStyleSheet ) {
|
---|
321 | psiStyleSheet = new PsiStyleSheet();
|
---|
322 | }
|
---|
323 |
|
---|
324 | return psiStyleSheet;
|
---|
325 | }
|
---|
326 |
|
---|
327 | //----------------------------------------------------------------------------
|
---|
328 | // PsiTextView
|
---|
329 | //----------------------------------------------------------------------------
|
---|
330 |
|
---|
331 | class PsiTextView::Private : public QObject
|
---|
332 | {
|
---|
333 | Q_OBJECT
|
---|
334 |
|
---|
335 | public:
|
---|
336 | Private(QObject *parent)
|
---|
337 | : QObject(parent, "PsiTextView::Private") { }
|
---|
338 | };
|
---|
339 |
|
---|
340 | PsiTextView::PsiTextView(QWidget *parent, const char *name)
|
---|
341 | : QTextEdit(parent, name)
|
---|
342 | {
|
---|
343 | d = new Private(this);
|
---|
344 |
|
---|
345 | setReadOnly(true);
|
---|
346 | setTextFormat(RichText);
|
---|
347 |
|
---|
348 | setStyleSheet( styleSheet() );
|
---|
349 | }
|
---|
350 |
|
---|
351 | QStyleSheet *PsiTextView::styleSheet()
|
---|
352 | {
|
---|
353 | return PsiStyleSheet::self();
|
---|
354 | }
|
---|
355 |
|
---|
356 | QPopupMenu *PsiTextView::createPopupMenu(const QPoint &pos)
|
---|
357 | {
|
---|
358 | QString link = anchorAt(pos);
|
---|
359 | if ( link.isEmpty() )
|
---|
360 | return QTextEdit::createPopupMenu(pos);
|
---|
361 |
|
---|
362 | if ( !urlObject )
|
---|
363 | urlObject = new URLObject();
|
---|
364 | return urlObject->createPopupMenu( link );
|
---|
365 | }
|
---|
366 |
|
---|
367 | void PsiTextView::emitHighlighted(const QString &)
|
---|
368 | {
|
---|
369 | }
|
---|
370 |
|
---|
371 | void PsiTextView::emitLinkClicked(const QString &s)
|
---|
372 | {
|
---|
373 | if ( !urlObject )
|
---|
374 | urlObject = new URLObject();
|
---|
375 | urlObject->popupAction( s );
|
---|
376 | }
|
---|
377 |
|
---|
378 | //----------------------------------------------------------------------------
|
---|
379 | // URLLabel
|
---|
380 | //----------------------------------------------------------------------------
|
---|
381 |
|
---|
382 | class URLLabel::Private
|
---|
383 | {
|
---|
384 | public:
|
---|
385 | QString url;
|
---|
386 | QString title;
|
---|
387 | };
|
---|
388 |
|
---|
389 | URLLabel::URLLabel(QWidget *parent, const char *name)
|
---|
390 | : QLabel(parent, name)
|
---|
391 | {
|
---|
392 | d = new Private;
|
---|
393 | setCursor( pointingHandCursor );
|
---|
394 | }
|
---|
395 |
|
---|
396 | URLLabel::~URLLabel()
|
---|
397 | {
|
---|
398 | delete d;
|
---|
399 | }
|
---|
400 |
|
---|
401 | const QString &URLLabel::url() const
|
---|
402 | {
|
---|
403 | return d->url;
|
---|
404 | }
|
---|
405 |
|
---|
406 | void URLLabel::setUrl(const QString &url)
|
---|
407 | {
|
---|
408 | d->url = url;
|
---|
409 | updateText();
|
---|
410 | }
|
---|
411 |
|
---|
412 | const QString &URLLabel::title() const
|
---|
413 | {
|
---|
414 | return d->title;
|
---|
415 | }
|
---|
416 |
|
---|
417 | void URLLabel::setTitle(const QString &t)
|
---|
418 | {
|
---|
419 | d->title = t;
|
---|
420 | updateText();
|
---|
421 | }
|
---|
422 |
|
---|
423 | void URLLabel::updateText()
|
---|
424 | {
|
---|
425 | setText( QString("<a href=\"%1\">%2</a>").arg(d->url).arg(d->title) );
|
---|
426 |
|
---|
427 | if ( d->url != d->title )
|
---|
428 | QToolTip::add(this, d->url);
|
---|
429 | else
|
---|
430 | QToolTip::remove(this);
|
---|
431 | }
|
---|
432 |
|
---|
433 | void URLLabel::mouseReleaseEvent (QMouseEvent *e)
|
---|
434 | {
|
---|
435 | QLabel::mouseReleaseEvent (e);
|
---|
436 |
|
---|
437 | switch ( e->button() ) {
|
---|
438 | case LeftButton:
|
---|
439 | if ( !urlObject )
|
---|
440 | urlObject = new URLObject();
|
---|
441 | urlObject->popupAction( url() );
|
---|
442 | break;
|
---|
443 |
|
---|
444 | case MidButton:
|
---|
445 | break;
|
---|
446 |
|
---|
447 | case RightButton:
|
---|
448 | {
|
---|
449 | if ( !urlObject )
|
---|
450 | urlObject = new URLObject();
|
---|
451 | QPopupMenu *m = urlObject->createPopupMenu( d->url );
|
---|
452 |
|
---|
453 | if ( m ) {
|
---|
454 | m->exec( e->globalPos() );
|
---|
455 | delete m;
|
---|
456 | }
|
---|
457 | break;
|
---|
458 | }
|
---|
459 |
|
---|
460 | default:
|
---|
461 | ; // to supress warnings
|
---|
462 | }
|
---|
463 | }
|
---|
464 |
|
---|
465 | void URLLabel::enterEvent (QEvent *e)
|
---|
466 | {
|
---|
467 | QLabel::enterEvent (e);
|
---|
468 | }
|
---|
469 |
|
---|
470 | void URLLabel::leaveEvent (QEvent *e)
|
---|
471 | {
|
---|
472 | QLabel::leaveEvent (e);
|
---|
473 | }
|
---|
474 |
|
---|
475 | void URLLabel::connectOpenURL(QObject *receiver, const char *slot)
|
---|
476 | {
|
---|
477 | if ( !urlObject )
|
---|
478 | urlObject = new URLObject();
|
---|
479 |
|
---|
480 | connect(urlObject, SIGNAL(openURL(QString)), receiver, slot);
|
---|
481 | }
|
---|
482 |
|
---|
483 | #include "psitextview.moc"
|
---|