Optionally use system locale for chat timestamp
authorShane Synan <digitalcircuit36939@gmail.com>
Sat, 3 Dec 2016 02:05:20 +0000 (20:05 -0600)
committerManuel Nickschas <sputnick@quassel-irc.org>
Wed, 12 Apr 2017 20:51:18 +0000 (22:51 +0200)
Using QLocale::system().timeFormat(), check if the AM/PM designator
exists; if so, assume a 12-hour style timestamp, otherwise assume the
previous 24-hour style timestamp.

Generate a timestamp of either " hh:mm:ss" or " h:mm:ss ap" in order
to include seconds (QLocale::ShortFormat does not specify seconds).

Add new setting UseCustomTimestampFormat to switch between system
or user timestamp string.  Bump settings version minor to keep old
behavior for upgrades.

src/qtui/chatscene.cpp
src/qtui/chatscene.h
src/qtui/chatviewsettings.h
src/qtui/qtuiapplication.cpp
src/qtui/qtuistyle.cpp
src/qtui/qtuistyle.h
src/qtui/settingspages/chatviewsettingspage.ui
src/uisupport/uistyle.cpp
src/uisupport/uistyle.h

index 188eba6..dbe4220 100644 (file)
@@ -134,6 +134,9 @@ ChatScene::ChatScene(QAbstractItemModel *model, const QString &idString, qreal w
     _showSenderBrackets = defaultSettings.showSenderBrackets();
     defaultSettings.notify("ShowSenderBrackets", this, SLOT(showSenderBracketsChanged()));
 
+    _useCustomTimestampFormat = defaultSettings.useCustomTimestampFormat();
+    defaultSettings.notify("UseCustomTimestampFormat", this, SLOT(useCustomTimestampFormatChanged()));
+
     _timestampFormatString = defaultSettings.timestampFormatString();
     defaultSettings.notify("TimestampFormat", this, SLOT(timestampFormatStringChanged()));
     updateTimestampHasBrackets();
@@ -1346,6 +1349,13 @@ void ChatScene::showSenderBracketsChanged()
     _showSenderBrackets = settings.showSenderBrackets();
 }
 
+void ChatScene::useCustomTimestampFormatChanged()
+{
+    ChatViewSettings settings;
+    _useCustomTimestampFormat = settings.useCustomTimestampFormat();
+    updateTimestampHasBrackets();
+}
+
 void ChatScene::timestampFormatStringChanged()
 {
     ChatViewSettings settings;
@@ -1357,23 +1367,29 @@ void ChatScene::updateTimestampHasBrackets()
 {
     // Calculate these parameters only as needed, rather than on-demand
 
-    // Does the timestamp format contain brackets?  For example:
-    // Classic: "[hh:mm:ss]"
-    // Modern:  " hh:mm:ss"
-    //
-    // Match groups of any opening or closing brackets - (), {}, [], <>, (>, {], etc:
-    //   ^\s*[({[<].+[)}\]>]\s*$
-    //   [...]    is a character group containing ...
-    //   ^        matches start of string
-    //   \s*      matches any amount of whitespace
-    //   [({[<]   matches (, {, [, or <
-    //   .+       matches one or more characters
-    //   [)}\]>]  matches ), }, ], or >, escaping the ]
-    //   $        matches end of string
-    // Alternatively, if opening and closing brackets must be in pairs, use this:
-    //   (^\s*\(.+\)\s*$)|(^\s*\{.+\}\s*$)|(^\s*\[.+\]\s*$)|(^\s*<.+>\s*$)
-    // Note that '\' must be escaped as '\\'
-    // Helpful interactive website for debugging and explaining:  https://regex101.com/
-    const QRegExp regExpMatchBrackets("^\\s*[({[<].+[)}\\]>]\\s*$");
-    _timestampHasBrackets = regExpMatchBrackets.exactMatch(_timestampFormatString);
+    if (!_useCustomTimestampFormat) {
+        // The default timestamp format string does not have brackets, no need to check.
+        // If UiStyle::updateSystemTimestampFormat() has brackets added, change this, too.
+        _timestampHasBrackets = false;
+    } else {
+        // Does the timestamp format contain brackets?  For example:
+        // Classic: "[hh:mm:ss]"
+        // Modern:  " hh:mm:ss"
+        //
+        // Match groups of any opening or closing brackets - (), {}, [], <>, (>, {], etc:
+        //   ^\s*[({[<].+[)}\]>]\s*$
+        //   [...]    is a character group containing ...
+        //   ^        matches start of string
+        //   \s*      matches any amount of whitespace
+        //   [({[<]   matches (, {, [, or <
+        //   .+       matches one or more characters
+        //   [)}\]>]  matches ), }, ], or >, escaping the ]
+        //   $        matches end of string
+        // Alternatively, if opening and closing brackets must be in pairs, use this:
+        //   (^\s*\(.+\)\s*$)|(^\s*\{.+\}\s*$)|(^\s*\[.+\]\s*$)|(^\s*<.+>\s*$)
+        // Note that '\' must be escaped as '\\'
+        // Helpful interactive website for debugging and explaining:  https://regex101.com/
+        const QRegExp regExpMatchBrackets("^\\s*[({[<].+[)}\\]>]\\s*$");
+        _timestampHasBrackets = regExpMatchBrackets.exactMatch(_timestampFormatString);
+    }
 }
index e06d22d..4e90951 100644 (file)
@@ -185,6 +185,11 @@ private slots:
      */
     void showSenderBracketsChanged();
 
+    /**
+     * Updates the local setting cache of whether or not to use the custom timestamp format
+     */
+    void useCustomTimestampFormatChanged();
+
     /**
      * Updates the local setting cache of the timestamp format string
      */
@@ -247,6 +252,7 @@ private:
 
     bool _showSenderBrackets;  /// If true, show brackets around sender names
 
+    bool _useCustomTimestampFormat; /// If true, use the custom timestamp format
     QString _timestampFormatString; /// Format of the timestamp string
     bool _timestampHasBrackets;     /// If true, timestamp format has [brackets] of some sort
 
index cb57b68..b727ab8 100644 (file)
@@ -46,7 +46,20 @@ public:
     inline void enableWebPreview(bool enabled) { setLocalValue("ShowWebPreview", enabled); }
 
     /**
-     * Gets the format string for chat log timestamps
+     * Gets if a custom timestamp format is used.
+     *
+     * @returns True if custom timestamp format used, otherwise false
+     */
+    inline bool useCustomTimestampFormat() { return localValue("UseCustomTimestampFormat", false).toBool(); }
+    /**
+     * Sets whether a custom timestamp format is used.
+     *
+     * @param[in] enabled True if custom timestamp format used, otherwise false
+     */
+    inline void setUseCustomTimestampFormat(bool enabled) { setLocalValue("UseCustomTimestampFormat", enabled); }
+
+    /**
+     * Gets the format string for chat log timestamps.
      *
      * @returns String representing timestamp format, e.g. "[hh:mm:ss]" or " hh:mm:ss"
      */
index 36bb1a4..b77d0af 100644 (file)
@@ -210,7 +210,7 @@ bool QtUiApplication::migrateSettings()
     // --------
     // Check minor settings version, handling upgrades/downgrades as needed
     // Current minor version
-    const uint VERSION_MINOR_CURRENT = 3;
+    const uint VERSION_MINOR_CURRENT = 4;
     // Stored minor version
     uint versionMinor = s.versionMinor();
 
@@ -270,10 +270,29 @@ bool QtUiApplication::applySettingsMigration(QtUiSettings settings, const uint n
     // Each missed version will be called in sequence.  E.g. to upgrade from '1' to '3', this
     // function will be called with '2', then '3'.
     // Use explicit scope via { ... } to avoid cross-initialization
+    //
+    // In most cases, the goal is to preserve the older default values for keys that haven't been
+    // saved.  Exceptions will be noted below.
+    case 4:
+    {
+        // New default changes: system locale used to generate a timestamp format string, deciding
+        // 24-hour or 12-hour timestamp.
+
+        // --------
+        // ChatView settings
+        const QString useCustomTimestampFormatId = "ChatView/__default__/UseCustomTimestampFormat";
+        if (!settings.valueExists(useCustomTimestampFormatId)) {
+            // New default value is false, preserve previous behavior by setting to true
+            settings.setValue(useCustomTimestampFormatId, true);
+        }
+        // --------
+
+        // Migration complete!
+        return true;
+    }
     case 3:
     {
-        // New default changes: per-chat history and line wrapping enabled by default.  Preserve
-        // the older default values for keys that haven't been saved.
+        // New default changes: per-chat history and line wrapping enabled by default.
 
         // --------
         // InputWidget settings
@@ -297,7 +316,7 @@ bool QtUiApplication::applySettingsMigration(QtUiSettings settings, const uint n
     case 2:
     {
         // New default changes: sender <nick> brackets disabled, sender colors and sender CTCP
-        // colors enabled.  Preserve the older default values for keys that haven't been saved.
+        // colors enabled.
 
         // --------
         // ChatView settings
index ebf1947..1e8cc35 100644 (file)
@@ -27,6 +27,8 @@
 QtUiStyle::QtUiStyle(QObject *parent) : UiStyle(parent)
 {
     ChatViewSettings s;
+    s.notify("UseCustomTimestampFormat", this, SLOT(updateUseCustomTimestampFormat()));
+    updateUseCustomTimestampFormat();
     s.notify("TimestampFormat", this, SLOT(updateTimestampFormatString()));
     updateTimestampFormatString();
     s.notify("ShowSenderBrackets", this, SLOT(updateShowSenderBrackets()));
@@ -39,6 +41,12 @@ QtUiStyle::QtUiStyle(QObject *parent) : UiStyle(parent)
 
 QtUiStyle::~QtUiStyle() {}
 
+void QtUiStyle::updateUseCustomTimestampFormat()
+{
+    ChatViewSettings s;
+    setUseCustomTimestampFormat(s.useCustomTimestampFormat());
+}
+
 void QtUiStyle::updateTimestampFormatString()
 {
     ChatViewSettings s;
index d824740..2aaceb6 100644 (file)
@@ -50,7 +50,16 @@ public slots:
     void generateSettingsQss() const;
 
 private slots:
+    /**
+     * Updates knowledge of whether or not to use the custom timestamp format
+     */
+    void updateUseCustomTimestampFormat();
+
+    /**
+     * Updates knowledge of the current timestamp format
+     */
     void updateTimestampFormatString();
+
     /**
      * Updates knowledge of whether or not to show sender brackets
      */
index b68c872..80c6cff 100644 (file)
    <item>
     <layout class="QHBoxLayout" name="horizontalLayout">
      <item>
-      <widget class="QLabel" name="label_13">
+      <widget class="QCheckBox" name="customTimestampFormat">
+       <property name="toolTip">
+        <string>Use a custom format for the timestamp</string>
+       </property>
        <property name="text">
-        <string>Timestamp format:</string>
+        <string>Custom timestamp format:</string>
+       </property>
+       <property name="defaultValue" stdset="0">
+        <bool>false</bool>
+       </property>
+       <property name="settingsKey" stdset="0">
+        <string notr="true">UseCustomTimestampFormat</string>
        </property>
       </widget>
      </item>
      <item>
       <widget class="QLineEdit" name="timestampFormat">
+       <property name="enabled">
+        <bool>false</bool>
+       </property>
        <property name="toolTip">
         <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Usage examples:&lt;/p&gt;
 &lt;table cellpadding=&quot;2&quot;&gt;
   </customwidget>
  </customwidgets>
  <tabstops>
+  <tabstop>customTimestampFormat</tabstop>
   <tabstop>timestampFormat</tabstop>
   <tabstop>showSenderBrackets</tabstop>
   <tabstop>customChatViewFont</tabstop>
     </hint>
    </hints>
   </connection>
+  <connection>
+   <sender>customTimestampFormat</sender>
+   <signal>toggled(bool)</signal>
+   <receiver>timestampFormat</receiver>
+   <slot>setEnabled(bool)</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>116</x>
+     <y>22</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>301</x>
+     <y>23</y>
+    </hint>
+   </hints>
+  </connection>
  </connections>
 </ui>
index f55b183..fd2bee5 100644 (file)
 #include "util.h"
 
 QHash<QString, UiStyle::FormatType> UiStyle::_formatCodes;
-QString UiStyle::_timestampFormatString; /// Timestamp format
-bool UiStyle::_showSenderBrackets;       /// If true, show brackets around sender names
+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),
@@ -70,6 +72,7 @@ UiStyle::UiStyle(QObject *parent)
     // 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);
 
@@ -168,7 +171,52 @@ QString UiStyle::loadStyleSheet(const QString &styleSheet, bool shouldExist)
     return ss;
 }
 
+
+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) {
@@ -663,6 +711,26 @@ 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)
index b179147..0b4589a 100644 (file)
@@ -178,7 +178,33 @@ public:
     static FormatType formatType(Message::Type msgType);
     static StyledString styleString(const QString &string, quint32 baseFormat = Base);
     static QString mircToInternal(const QString &);
-    static inline QString timestampFormatString() { return _timestampFormatString; }
+
+    /**
+     * Gets if a custom timestamp format is used.
+     *
+     * @return True if custom timestamp format used, otherwise false
+     */
+    static inline bool useCustomTimestampFormat() { return _useCustomTimestampFormat; }
+
+    /**
+     * Gets the format string for chat log timestamps according to the system locale.
+     *
+     * This will return " hh:mm:ss" for system locales with 24-hour time or " h:mm:ss AP" for
+     * systems with 12-hour time.
+     *
+     * @return String representing timestamp format according to system locale, e.g. " hh:mm:ss"
+     */
+    static QString systemTimestampFormatString();
+
+    /**
+     * Gets the format string for chat log timestamps, either system locale or custom.
+     *
+     * Depending on useCustomTimestampFormat(), this will return either the system locale based
+     * time format, or the custom user-specified string.
+     *
+     * @return String representing timestamp format, e.g. "[hh:mm:ss]" or " hh:mm:ss"
+     */
+    static QString timestampFormatString();
 
     QTextCharFormat format(quint32 formatType, quint32 messageLabel) const;
     QFontMetricsF *fontMetrics(quint32 formatType, quint32 messageLabel) const;
@@ -209,7 +235,31 @@ protected:
 
     static FormatType formatType(const QString &code);
     static QString formatCode(FormatType);
+
+    /**
+     * Cache the system locale timestamp format string
+     *
+     * Based on whether or not AM/PM designators are used in the QLocale::system().timeFormat(),
+     * this extends the system locale timestamp format string to include seconds.
+     *
+     * @see UiStyle::systemTimestampFormatString()
+     */
+    static void updateSystemTimestampFormat();
+
+    /**
+     * Updates the local setting cache of whether or not to use the custom timestamp format
+     *
+     * @param[in] enabled  If true, custom timestamp format used, otherwise false
+     */
+    static void setUseCustomTimestampFormat(bool enabled);
+
+    /**
+     * Updates the local setting cache of the timestamp format string
+     *
+     * @param[in] format   Timestamp format string
+     */
     static void setTimestampFormatString(const QString &format);
+
     /**
      * Updates the local setting cache of whether or not to show sender brackets
      *
@@ -231,8 +281,10 @@ private:
     mutable QHash<quint64, QFontMetricsF *> _metricsCache;
     QHash<quint32, QTextCharFormat> _listItemFormats;
     static QHash<QString, FormatType> _formatCodes;
-    static QString _timestampFormatString;
-    static bool _showSenderBrackets;  /// If true, show brackets around sender names
+    static bool _useCustomTimestampFormat;        /// If true, use the custom timestamp format
+    static QString _systemTimestampFormatString;  /// Cached copy of system locale timestamp format
+    static QString _timestampFormatString;        /// Timestamp format string
+    static bool _showSenderBrackets;              /// If true, show brackets around sender names
 
     QIcon _channelJoinedIcon;
     QIcon _channelPartedIcon;