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