qss: Introduce new message label "hovered"
[quassel.git] / src / uisupport / uistyle.h
1 /***************************************************************************
2  *   Copyright (C) 2005-2018 by the Quassel Project                        *
3  *   devel@quassel-irc.org                                                 *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) version 3.                                           *
9  *                                                                         *
10  *   This program 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         *
13  *   GNU General Public License for more details.                          *
14  *                                                                         *
15  *   You should have received a copy of the GNU General Public License     *
16  *   along with this program; if not, write to the                         *
17  *   Free Software Foundation, Inc.,                                       *
18  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
19  ***************************************************************************/
20
21 #pragma once
22
23 #include <utility>
24 #include <vector>
25
26 #include <QColor>
27 #include <QDataStream>
28 #include <QFontMetricsF>
29 #include <QHash>
30 #include <QIcon>
31 #include <QTextCharFormat>
32 #include <QTextLayout>
33 #include <QPalette>
34 #include <QVector>
35
36 #include "bufferinfo.h"
37 #include "message.h"
38 #include "networkmodel.h"
39 #include "settings.h"
40
41 class UiStyle : public QObject
42 {
43     Q_OBJECT
44     Q_ENUMS(SenderPrefixModes)
45
46 public:
47     UiStyle(QObject *parent = 0);
48     virtual ~UiStyle();
49
50     //! This enumerates the possible formats a text element may have. */
51     /** These formats are ordered on increasing importance, in cases where a given property is specified
52      *  by multiple active formats.
53      *  \NOTE: Do not change/add values here without also adapting the relevant
54      *         methods in this class (in particular mergedFormat())!
55      *         Also, we _do_ rely on certain properties of these values in styleString() and friends!
56      */
57     enum class FormatType : quint32 {
58         Base            = 0x00000000,
59         Invalid         = 0xffffffff,
60
61         // Message Formats (mutually exclusive!)
62         PlainMsg        = 0x00000001,
63         NoticeMsg       = 0x00000002,
64         ActionMsg       = 0x00000003,
65         NickMsg         = 0x00000004,
66         ModeMsg         = 0x00000005,
67         JoinMsg         = 0x00000006,
68         PartMsg         = 0x00000007,
69         QuitMsg         = 0x00000008,
70         KickMsg         = 0x00000009,
71         KillMsg         = 0x0000000a,
72         ServerMsg       = 0x0000000b,
73         InfoMsg         = 0x0000000c,
74         ErrorMsg        = 0x0000000d,
75         DayChangeMsg    = 0x0000000e,
76         TopicMsg        = 0x0000000f,
77         NetsplitJoinMsg = 0x00000010,
78         NetsplitQuitMsg = 0x00000020,
79         InviteMsg       = 0x00000030,
80
81         // Standard Formats
82         Bold            = 0x00000100,
83         Italic          = 0x00000200,
84         Underline       = 0x00000400,
85         Strikethrough   = 0x00000800,
86
87         // Individual parts of a message
88         Timestamp       = 0x00001000,
89         Sender          = 0x00002000,
90         Contents        = 0x00004000,
91         Nick            = 0x00008000,
92         Hostmask        = 0x00010000,
93         ChannelName     = 0x00020000,
94         ModeFlags       = 0x00040000,
95
96         // URL is special, we want that to take precedence over the rest...
97         Url             = 0x00080000
98
99                           // mIRC Colors - we assume those to be present only in plain contents
100                           // foreground: 0x0.400000
101                           // background: 0x.0800000
102     };
103
104     enum class MessageLabel : quint32 {
105         None            = 0x00000000,
106         OwnMsg          = 0x00000001,
107         Highlight       = 0x00000002,
108         Selected        = 0x00000004,
109         Hovered         = 0x00000008,
110         Last            = Hovered
111     };
112
113     enum class ItemFormatType : quint32 {
114         None              = 0x00000000,
115
116         BufferViewItem    = 0x00000001,
117         NickViewItem      = 0x00000002,
118
119         NetworkItem       = 0x00000010,
120         ChannelBufferItem = 0x00000020,
121         QueryBufferItem   = 0x00000040,
122         IrcUserItem       = 0x00000080,
123         UserCategoryItem  = 0x00000100,
124
125         InactiveBuffer    = 0x00001000,
126         ActiveBuffer      = 0x00002000,
127         UnreadBuffer      = 0x00004000,
128         HighlightedBuffer = 0x00008000,
129         UserAway          = 0x00010000,
130
131         Invalid           = 0xffffffff
132     };
133
134     enum class FormatProperty {
135         AllowForegroundOverride = QTextFormat::UserProperty,
136         AllowBackgroundOverride
137     };
138
139     enum class ColorRole {
140         MarkerLine,
141         // Sender colors (16 + self)
142         // These aren't used directly to avoid having storing all of the sender color options in the
143         // rendering routine of each item.  Also, I couldn't figure out how to do that.
144         // It would be nice to have the UseSenderColors preference also toggle sender colors set by
145         // themes, so hopefully this can be extended in the future.
146         // Furthermore, using this palette directly would mean separate sets of colors couldn't be
147         // used for different message types.
148         SenderColorSelf,
149         SenderColor00,
150         SenderColor01,
151         SenderColor02,
152         SenderColor03,
153         SenderColor04,
154         SenderColor05,
155         SenderColor06,
156         SenderColor07,
157         SenderColor08,
158         SenderColor09,
159         SenderColor0a,
160         SenderColor0b,
161         SenderColor0c,
162         SenderColor0d,
163         SenderColor0e,
164         SenderColor0f,
165         NumRoles // must be last!
166     };
167
168     /// Display of sender prefix modes
169 #if QT_VERSION >= 0x050000
170     enum class SenderPrefixMode {
171 #else
172     enum SenderPrefixMode {
173 #endif
174         NoModes = 0,      ///< Hide sender modes
175         HighestMode = 1,  ///< Show the highest active sender mode
176         AllModes = 2      ///< Show all active sender modes
177     };
178     // Do not change SenderPrefixMode numbering without also adjusting
179     // ChatViewSettingsPage::initSenderPrefixComboBox() and chatviewsettingspage.ui "defaultValue"
180
181     struct Format {
182         FormatType type;
183         QColor foreground;
184         QColor background;
185     };
186
187     using FormatList = std::vector<std::pair<quint16, Format>>;
188
189     struct StyledString {
190         QString plainText;
191         FormatList formatList; // starting pos, ftypes
192     };
193
194     class StyledMessage;
195
196     /**
197      * List of default sender colors
198      *
199      * In order from 1 - 16, matching the Sender## format in the settings file.
200      * Don't change the length or values of the colors without updating the UI and color roles, too.
201      *
202      * @see ../qtui/settingspages/chatviewsettingspage.ui
203      * @see UiStyle::ColorRole
204      */
205     const QList<QColor> defaultSenderColors = QList<QColor> {
206         QColor(204,   0,   0),  ///< Sender00
207         QColor(  0, 108, 173),  ///< Sender01
208         QColor( 77, 153,   0),  ///< Sender02
209         QColor(102,   0, 204),  ///< Sender03
210         QColor(166, 125,   0),  ///< Sender04
211         QColor(  0, 153,  39),  ///< Sender05
212         QColor(  0,  48, 192),  ///< Sender06
213         QColor(204,   0, 154),  ///< Sender07
214         QColor(185,  70,   0),  ///< Sender08
215         QColor(134, 153,   0),  ///< Sender09
216         QColor( 20, 153,   0),  ///< Sender10
217         QColor(  0, 153,  96),  ///< Sender11
218         QColor(  0, 108, 173),  ///< Sender12
219         QColor(  0, 153, 204),  ///< Sender13
220         QColor(179,   0, 204),  ///< Sender14
221         QColor(204,   0,  77),  ///< Sender15
222     };
223     // Explicitly declare QList<QColor> type for defaultSenderColors, otherwise error C2797
224     // "list initialization inside member initializer list" will occur in Windows builds with Visual
225     // Studio's compiler.
226     //
227     // See https://blogs.msdn.microsoft.com/vcblog/2014/08/19/the-future-of-non-static-data-member-initialization/
228     // Note: Qt Creator flags this as invalid unless you set Clang in
229     // Settings -> C++ -> Code Model -> Code Completion and Semantic Highlighting -> C
230     //
231     // See https://bugreports.qt.io/browse/QTCREATORBUG-1902
232
233     /**
234      * Default sender color for sent messages
235      */
236     const QColor defaultSenderColorSelf = QColor(0, 0, 0);
237
238     static FormatType formatType(Message::Type msgType);
239     static StyledString styleString(const QString &string, FormatType baseFormat = FormatType::Base);
240     static QString mircToInternal(const QString &);
241
242     /**
243      * Gets if a custom timestamp format is used.
244      *
245      * @return True if custom timestamp format used, otherwise false
246      */
247     static inline bool useCustomTimestampFormat() { return _useCustomTimestampFormat; }
248
249     /**
250      * Gets the format string for chat log timestamps according to the system locale.
251      *
252      * This will return " hh:mm:ss" for system locales with 24-hour time or " h:mm:ss AP" for
253      * systems with 12-hour time.
254      *
255      * @return String representing timestamp format according to system locale, e.g. " hh:mm:ss"
256      */
257     static QString systemTimestampFormatString();
258
259     /**
260      * Gets the format string for chat log timestamps, either system locale or custom.
261      *
262      * Depending on useCustomTimestampFormat(), this will return either the system locale based
263      * time format, or the custom user-specified string.
264      *
265      * @return String representing timestamp format, e.g. "[hh:mm:ss]" or " hh:mm:ss"
266      */
267     static QString timestampFormatString();
268
269     QTextCharFormat format(const Format &format, MessageLabel messageLabel) const;
270     QFontMetricsF *fontMetrics(FormatType formatType, MessageLabel messageLabel) const;
271
272     QList<QTextLayout::FormatRange> toTextLayoutList(const FormatList &, int textLength, MessageLabel messageLabel) const;
273
274     inline const QBrush &brush(ColorRole role) const { return _uiStylePalette.at((int)role); }
275     inline void setBrush(ColorRole role, const QBrush &brush) { _uiStylePalette[(int)role] = brush; }
276
277     QVariant bufferViewItemData(const QModelIndex &networkModelIndex, int role) const;
278     QVariant nickViewItemData(const QModelIndex &networkModelIndex, int role) const;
279
280 public slots:
281     void reload();
282
283 signals:
284     void changed();
285
286 protected:
287     void loadStyleSheet();
288     QString loadStyleSheet(const QString &name, bool shouldExist = false);
289
290     QTextCharFormat parsedFormat(quint64 key) const;
291     QTextCharFormat cachedFormat(const Format &format, MessageLabel messageLabel) const;
292     void setCachedFormat(const QTextCharFormat &charFormat, const Format &format, MessageLabel messageLabel) const;
293     void mergeFormat(QTextCharFormat &charFormat, const Format &format, MessageLabel messageLabel) const;
294     void mergeSubElementFormat(QTextCharFormat &charFormat, FormatType formatType, MessageLabel messageLabel) const;
295     void mergeColors(QTextCharFormat &charFormat, const Format &format, MessageLabel messageLabel) const;
296
297     static FormatType formatType(const QString &code);
298     static QString formatCode(FormatType);
299
300     /**
301      * Cache the system locale timestamp format string
302      *
303      * Based on whether or not AM/PM designators are used in the QLocale::system().timeFormat(),
304      * this extends the system locale timestamp format string to include seconds.
305      *
306      * @see UiStyle::systemTimestampFormatString()
307      */
308     static void updateSystemTimestampFormat();
309
310     /**
311      * Updates the local setting cache of whether or not to use the custom timestamp format
312      *
313      * @param[in] enabled  If true, custom timestamp format used, otherwise false
314      */
315     static void setUseCustomTimestampFormat(bool enabled);
316
317     /**
318      * Updates the local setting cache of the timestamp format string
319      *
320      * @param[in] format   Timestamp format string
321      */
322     static void setTimestampFormatString(const QString &format);
323     /**
324      * Updates the local setting cache of how to display sender prefix modes
325      *
326      * @param[in] mode  Display format for sender prefix modes
327      */
328     static void setSenderPrefixDisplay(UiStyle::SenderPrefixMode mode);
329
330     /**
331      * Updates the local setting cache of whether or not to show sender brackets
332      *
333      * @param[in] enabled  If true, sender brackets are enabled, otherwise false.
334      */
335     static void enableSenderBrackets(bool enabled);
336
337     QVariant itemData(int role, const QTextCharFormat &format) const;
338
339 private slots:
340     void allowMircColorsChanged(const QVariant &);
341     void showItemViewIconsChanged(const QVariant &);
342
343 private:
344     QVector<QBrush> _uiStylePalette;
345     QBrush _markerLineBrush;
346     QHash<quint64, QTextCharFormat> _formats;
347     mutable QHash<QString, QTextCharFormat> _formatCache;
348     mutable QHash<quint64, QFontMetricsF *> _metricsCache;
349     QHash<UiStyle::ItemFormatType, QTextCharFormat> _listItemFormats;
350     static QHash<QString, FormatType> _formatCodes;
351     static bool _useCustomTimestampFormat;        ///< If true, use the custom timestamp format
352     static QString _systemTimestampFormatString;  ///< Cached copy of system locale timestamp format
353     static QString _timestampFormatString;        ///< Timestamp format string
354     static UiStyle::SenderPrefixMode _senderPrefixDisplay; ///< Prefix mode display before sender
355     static bool _showSenderBrackets;              ///< If true, show brackets around sender names
356
357     QIcon _channelJoinedIcon;
358     QIcon _channelPartedIcon;
359     QIcon _userOfflineIcon;
360     QIcon _userOnlineIcon;
361     QIcon _userAwayIcon;
362     QIcon _categoryOpIcon;
363     QIcon _categoryVoiceIcon;
364     int _opIconLimit;
365     int _voiceIconLimit;
366     bool _showNickViewIcons;
367     bool _showBufferViewIcons;
368     bool _allowMircColors;
369 };
370
371
372 class UiStyle::StyledMessage : public Message
373 {
374     Q_DECLARE_TR_FUNCTIONS(UiStyle::StyledMessage)
375
376 public:
377     explicit StyledMessage(const Message &message);
378
379     QString decoratedTimestamp() const;
380     QString plainSender() const;           //!< Nickname (no decorations) for Plain and Notice, empty else
381     QString decoratedSender() const;
382     const QString &plainContents() const;
383
384     const FormatList &contentsFormatList() const;
385
386     quint8 senderHash() const;
387
388 protected:
389     void style() const;
390
391 private:
392     mutable StyledString _contents;
393     mutable quint8 _senderHash;
394 };
395
396 #if QT_VERSION < 0x050000
397 uint qHash(UiStyle::ItemFormatType key);
398 #else
399 uint qHash(UiStyle::ItemFormatType key, uint seed);
400 #endif
401
402 // ---- Operators for dealing with enums ----------------------------------------------------------
403
404 UiStyle::FormatType operator|(UiStyle::FormatType lhs, UiStyle::FormatType rhs);
405 UiStyle::FormatType& operator|=(UiStyle::FormatType &lhs, UiStyle::FormatType rhs);
406 UiStyle::FormatType operator|(UiStyle::FormatType lhs, quint32 rhs);
407 UiStyle::FormatType& operator|=(UiStyle::FormatType &lhs, quint32 rhs);
408 UiStyle::FormatType operator&(UiStyle::FormatType lhs, UiStyle::FormatType rhs);
409 UiStyle::FormatType& operator&=(UiStyle::FormatType &lhs, UiStyle::FormatType rhs);
410 UiStyle::FormatType operator&(UiStyle::FormatType lhs, quint32 rhs);
411 UiStyle::FormatType& operator&=(UiStyle::FormatType &lhs, quint32 rhs);
412 UiStyle::FormatType& operator^=(UiStyle::FormatType &lhs, UiStyle::FormatType rhs);
413
414 UiStyle::MessageLabel operator|(UiStyle::MessageLabel lhs, UiStyle::MessageLabel rhs);
415 UiStyle::MessageLabel& operator|=(UiStyle::MessageLabel &lhs, UiStyle::MessageLabel rhs);
416 UiStyle::MessageLabel operator&(UiStyle::MessageLabel lhs, quint32 rhs);
417 UiStyle::MessageLabel& operator&=(UiStyle::MessageLabel &lhs, UiStyle::MessageLabel rhs);
418
419 // Shifts the label into the upper half of the return value
420 quint64 operator|(UiStyle::FormatType lhs, UiStyle::MessageLabel rhs);
421
422 UiStyle::ItemFormatType operator|(UiStyle::ItemFormatType lhs, UiStyle::ItemFormatType rhs);
423 UiStyle::ItemFormatType& operator|=(UiStyle::ItemFormatType &lhs, UiStyle::ItemFormatType rhs);
424
425 // ---- Allow for FormatList in QVariant ----------------------------------------------------------
426
427 QDataStream &operator<<(QDataStream &out, const UiStyle::FormatList &formatList);
428 QDataStream &operator>>(QDataStream &in, UiStyle::FormatList &formatList);
429
430 Q_DECLARE_METATYPE(UiStyle::FormatList)
431 Q_DECLARE_METATYPE(UiStyle::MessageLabel)
432 Q_DECLARE_METATYPE(UiStyle::SenderPrefixMode)