Optionally use system locale for chat timestamp
[quassel.git] / src / uisupport / uistyle.cpp
index 838cfca..fd2bee5 100644 (file)
@@ -1,5 +1,5 @@
 /***************************************************************************
- *   Copyright (C) 2005-2014 by the Quassel Project                        *
+ *   Copyright (C) 2005-2016 by the Quassel Project                        *
  *   devel@quassel-irc.org                                                 *
  *                                                                         *
  *   This program is free software; you can redistribute it and/or modify  *
 #include "util.h"
 
 QHash<QString, UiStyle::FormatType> UiStyle::_formatCodes;
-QString UiStyle::_timestampFormatString;
+bool UiStyle::_useCustomTimestampFormat;       /// If true, use the custom timestamp format
+QString UiStyle::_timestampFormatString;       /// Timestamp format
+QString UiStyle::_systemTimestampFormatString; /// Cached copy of system locale timestamp format
+bool UiStyle::_showSenderBrackets;             /// If true, show brackets around sender names
 
 UiStyle::UiStyle(QObject *parent)
     : QObject(parent),
@@ -66,7 +69,12 @@ UiStyle::UiStyle(QObject *parent)
     _formatCodes["%DM"] = ModeFlags;
     _formatCodes["%DU"] = Url;
 
-    setTimestampFormatString("[hh:mm:ss]");
+    // Initialize fallback defaults
+    // NOTE: If you change this, update qtui/chatviewsettings.h, too.  More explanations available
+    // in there.
+    setUseCustomTimestampFormat(false);
+    setTimestampFormatString(" hh:mm:ss");
+    enableSenderBrackets(true);
 
     // BufferView / NickView settings
     UiStyleSettings s;
@@ -104,8 +112,22 @@ void UiStyle::loadStyleSheet()
     QString styleSheet;
     styleSheet += loadStyleSheet("file:///" + Quassel::findDataFilePath("stylesheets/default.qss"));
     styleSheet += loadStyleSheet("file:///" + Quassel::configDirPath() + "settings.qss");
-    if (s.value("UseCustomStyleSheet", false).toBool())
-        styleSheet += loadStyleSheet("file:///" + s.value("CustomStyleSheetPath").toString(), true);
+    if (s.value("UseCustomStyleSheet", false).toBool()) {
+        QString customSheetPath(s.value("CustomStyleSheetPath").toString());
+        QString customSheet = loadStyleSheet("file:///" + customSheetPath, true);
+        if (customSheet.isEmpty()) {
+            // MIGRATION: changed default install path for data from /usr/share/apps to /usr/share
+            if (customSheetPath.startsWith("/usr/share/apps/quassel")) {
+                customSheetPath.replace(QRegExp("^/usr/share/apps"), "/usr/share");
+                customSheet = loadStyleSheet("file:///" + customSheetPath, true);
+                if (!customSheet.isEmpty()) {
+                    s.setValue("CustomStyleSheetPath", customSheetPath);
+                    qDebug() << "Custom stylesheet path migrated to" << customSheetPath;
+                }
+            }
+        }
+        styleSheet += customSheet;
+    }
     styleSheet += loadStyleSheet("file:///" + Quassel::optionValue("qss"), true);
 
     if (!styleSheet.isEmpty()) {
@@ -150,11 +172,62 @@ QString UiStyle::loadStyleSheet(const QString &styleSheet, bool shouldExist)
 }
 
 
+void UiStyle::updateSystemTimestampFormat()
+{
+    // Does the system locale use AM/PM designators?  For example:
+    // AM/PM:    h:mm AP
+    // AM/PM:    hh:mm a
+    // 24-hour:  h:mm
+    // 24-hour:  hh:mm ADD things
+    // For timestamp format, see https://doc.qt.io/qt-5/qdatetime.html#toString
+    // This won't update if the system locale is changed while Quassel is running.  If need be,
+    // Quassel could hook into notifications of changing system locale to update this.
+    //
+    // Match any AP or A designation if on a word boundary, including underscores.
+    //   .*(\b|_)(A|AP)(\b|_).*
+    //   .*         Match any number of characters
+    //   \b         Match a word boundary, i.e. "AAA.BBB", "." is matched
+    //   _          Match the literal character '_' (not considered a word boundary)
+    //   (X|Y)  Match either X or Y, exactly
+    //
+    // Note that '\' must be escaped as '\\'
+    // QRegExp does not support (?> ...), so it's replaced with standard matching, (...)
+    // Helpful interactive website for debugging and explaining:  https://regex101.com/
+    const QRegExp regExpMatchAMPM(".*(\\b|_)(A|AP)(\\b|_).*", Qt::CaseInsensitive);
+
+    if (regExpMatchAMPM.exactMatch(QLocale::system().timeFormat(QLocale::ShortFormat))) {
+        // AM/PM style used
+        _systemTimestampFormatString = " h:mm:ss ap";
+    } else {
+        // 24-hour style used
+        _systemTimestampFormatString = " hh:mm:ss";
+    }
+    // Include a space to give the timestamp a small bit of padding between the border of the chat
+    // buffer window and the numbers.  Helps with readability.
+    // If you change this to include brackets, e.g. "[hh:mm:ss]", also update
+    // ChatScene::updateTimestampHasBrackets() to true or false as needed!
+}
+
+
+// FIXME The following should trigger a reload/refresh of the chat view.
+void UiStyle::setUseCustomTimestampFormat(bool enabled)
+{
+    if (_useCustomTimestampFormat != enabled) {
+        _useCustomTimestampFormat = enabled;
+    }
+}
+
 void UiStyle::setTimestampFormatString(const QString &format)
 {
     if (_timestampFormatString != format) {
         _timestampFormatString = format;
-        // FIXME reload
+    }
+}
+
+void UiStyle::enableSenderBrackets(bool enabled)
+{
+    if (_showSenderBrackets != enabled) {
+        _showSenderBrackets = enabled;
     }
 }
 
@@ -638,14 +711,35 @@ QString UiStyle::mircToInternal(const QString &mirc_)
 }
 
 
+QString UiStyle::systemTimestampFormatString()
+{
+    if (_systemTimestampFormatString.isEmpty()) {
+        // Calculate and cache the system timestamp format string
+        updateSystemTimestampFormat();
+    }
+    return _systemTimestampFormatString;
+}
+
+
+QString UiStyle::timestampFormatString()
+{
+    if (useCustomTimestampFormat()) {
+        return _timestampFormatString;
+    } else {
+        return systemTimestampFormatString();
+    }
+}
+
+
 /***********************************************************************************/
 UiStyle::StyledMessage::StyledMessage(const Message &msg)
     : Message(msg)
 {
-    if (type() == Message::Plain)
+    if (type() == Message::Plain || type() == Message::Action)
         _senderHash = 0xff;
     else
-        _senderHash = 0x00;  // this means we never compute the hash for msgs that aren't plain
+        _senderHash = 0x00;
+    // This means we never compute the hash for msgs that aren't Plain or Action
 }
 
 
@@ -799,7 +893,11 @@ QString UiStyle::StyledMessage::decoratedSender() const
 {
     switch (type()) {
     case Message::Plain:
-        return QString("<%1>").arg(plainSender()); break;
+        if (_showSenderBrackets)
+            return QString("<%1>").arg(plainSender());
+        else
+            return QString("%1").arg(plainSender());
+        break;
     case Message::Notice:
         return QString("[%1]").arg(plainSender()); break;
     case Message::Action: