changeset 1824:1cd161242250

Merge
author Chris Cannam
date Tue, 28 Jan 2020 14:34:13 +0000
parents 07a8793a0388 (current diff) 0bc6caf87658 (diff)
children 029d7d3f0a3a
files
diffstat 29 files changed, 336 insertions(+), 28 deletions(-) [+]
line wrap: on
line diff
--- a/base/DataExportOptions.h	Tue Jan 28 14:32:34 2020 +0000
+++ b/base/DataExportOptions.h	Tue Jan 28 14:34:13 2020 +0000
@@ -18,8 +18,36 @@
 enum DataExportOption
 {
     DataExportDefaults = 0x0,
+
+    /**
+     * Export sparse event-based models as if they were dense models,
+     * writing an event at every interval of the model's
+     * resolution. Where no event is present in the actual model, a
+     * constant "fill event" is interpolated instead.
+     */
     DataExportFillGaps = 0x1,
-    DataExportOmitLevels = 0x2,
+
+    /**
+     * Omit the level attribute from exported events.
+     */
+    DataExportOmitLevel = 0x2,
+
+    /**
+     * Always include a timestamp in the first column. Otherwise
+     * timestamps will only be included in sparse models.
+     */
+    DataExportAlwaysIncludeTimestamp = 0x4,
+
+    /**
+     * Use sample frames rather than seconds for time and duration
+     * values.
+     */
+    DataExportWriteTimeInFrames = 0x8,
+    
+    /**
+     * Write a header row before any data rows.
+     */
+    DataExportIncludeHeader = 0x10
 };
 
 typedef int DataExportOptions;
--- a/base/Event.h	Tue Jan 28 14:32:34 2020 +0000
+++ b/base/Event.h	Tue Jan 28 14:34:13 2020 +0000
@@ -366,25 +366,71 @@
         return n;
     }
 
+    QString getDelimitedDataHeaderLine(QString delimiter,
+                                       DataExportOptions opts,
+                                       ExportNameOptions nameOpts) const {
+
+        QStringList list;
+
+        // These are considered API rather than human-readable text -
+        // they shouldn't be translated
+
+        if (opts & DataExportWriteTimeInFrames) {
+            list << "frame";
+        } else {
+            list << "time";
+        }
+
+        if (m_haveValue) {
+            list << nameOpts.valueAttributeName;
+        }
+        
+        if (m_haveDuration) {
+            list << "duration";
+        }
+        
+        if (m_haveLevel) {
+            if (!(opts & DataExportOmitLevel)) {
+                list << nameOpts.levelAttributeName;
+            }
+        }
+        
+        if (m_uri != "") {
+            list << nameOpts.uriAttributeName;
+        }
+        
+        list << "label";
+        
+        return list.join(delimiter).toUpper();
+    }
+    
     QString toDelimitedDataString(QString delimiter,
                                   DataExportOptions opts,
                                   sv_samplerate_t sampleRate) const {
         QStringList list;
 
-        list << RealTime::frame2RealTime(m_frame, sampleRate)
-            .toString().c_str();
+        if (opts & DataExportWriteTimeInFrames) {
+            list << QString("%1").arg(m_frame);
+        } else {
+            list << RealTime::frame2RealTime(m_frame, sampleRate)
+                .toString().c_str();
+        }
         
         if (m_haveValue) {
             list << QString("%1").arg(m_value);
         }
         
         if (m_haveDuration) {
-            list << RealTime::frame2RealTime(m_duration, sampleRate)
-                .toString().c_str();
+            if (opts & DataExportWriteTimeInFrames) {
+                list << QString("%1").arg(m_duration);
+            } else {
+                list << RealTime::frame2RealTime(m_duration, sampleRate)
+                    .toString().c_str();
+            }
         }
         
         if (m_haveLevel) {
-            if (!(opts & DataExportOmitLevels)) {
+            if (!(opts & DataExportOmitLevel)) {
                 list << QString("%1").arg(m_level);
             }
         }
--- a/base/EventSeries.cpp	Tue Jan 28 14:32:34 2020 +0000
+++ b/base/EventSeries.cpp	Tue Jan 28 14:34:13 2020 +0000
@@ -598,6 +598,20 @@
 }
 
 QString
+EventSeries::getDelimitedDataHeaderLine(QString delimiter,
+                                        DataExportOptions opts,
+                                        Event::ExportNameOptions nopts) const
+{
+    if (m_events.empty()) {
+        return QString();
+    } else {
+        return m_events.begin()->getDelimitedDataHeaderLine(delimiter,
+                                                            opts,
+                                                            nopts);
+    }
+}
+
+QString
 EventSeries::toDelimitedDataString(QString delimiter,
                                    DataExportOptions options,
                                    sv_frame_t startFrame,
--- a/base/EventSeries.h	Tue Jan 28 14:32:34 2020 +0000
+++ b/base/EventSeries.h	Tue Jan 28 14:34:13 2020 +0000
@@ -221,6 +221,14 @@
                Event::ExportNameOptions) const;
 
     /**
+     * Emit a label for each column that would be written by
+     * toDelimitedDataString, separated by the given delimiter.
+     */
+    QString getDelimitedDataHeaderLine(QString delimiter,
+                                       DataExportOptions options,
+                                       Event::ExportNameOptions) const;
+    
+    /**
      * Emit events starting within the given range to a delimited
      * (e.g. comma-separated) data format.
      */
--- a/base/Selection.cpp	Tue Jan 28 14:32:34 2020 +0000
+++ b/base/Selection.cpp	Tue Jan 28 14:34:13 2020 +0000
@@ -227,3 +227,15 @@
     stream << indent << "</selections>\n";
 }
 
+QString
+MultiSelection::toString() const
+{
+    QStringList list;
+    for (SelectionList::iterator i = m_selections.begin();
+         i != m_selections.end(); ++i) {
+        list << QString("(%1,%2)")
+            .arg(i->getStartFrame())
+            .arg(i->getEndFrame());
+    }
+    return "(" + list.join(",") + ")";
+}
--- a/base/Selection.h	Tue Jan 28 14:32:34 2020 +0000
+++ b/base/Selection.h	Tue Jan 28 14:34:13 2020 +0000
@@ -75,18 +75,22 @@
     void clearSelections();
 
     void getExtents(sv_frame_t &startFrame, sv_frame_t &endFrame) const;
-
+    
     /**
      * Return the selection that contains a given frame.
      * If defaultToFollowing is true, and if the frame is not in a
      * selected area, return the next selection after the given frame.
      * Return the empty selection if no appropriate selection is found.
      */
-    Selection getContainingSelection(sv_frame_t frame, bool defaultToFollowing) const;
+    Selection getContainingSelection(sv_frame_t frame,
+                                     bool defaultToFollowing) const;
 
     void toXml(QTextStream &stream, QString indent = "",
-                       QString extraAttributes = "") const override;
+               QString extraAttributes = "") const override;
 
+    // Debug only, not interop
+    QString toString() const;
+    
 protected:
     SelectionList m_selections;
 };
--- a/data/fileio/CSVFileWriter.cpp	Tue Jan 28 14:32:34 2020 +0000
+++ b/data/fileio/CSVFileWriter.cpp	Tue Jan 28 14:34:13 2020 +0000
@@ -85,6 +85,11 @@
     
         QTextStream out(&file);
 
+        if (m_options & DataExportIncludeHeader) {
+            out << m_model->getDelimitedDataHeaderLine(m_delimiter, m_options)
+                << endl;
+        }
+        
         sv_frame_t blockSize = 65536;
 
         if (m_model->isSparse()) {
--- a/data/fileio/MIDIFileReader.cpp	Tue Jan 28 14:32:34 2020 +0000
+++ b/data/fileio/MIDIFileReader.cpp	Tue Jan 28 14:34:13 2020 +0000
@@ -934,7 +934,7 @@
     }
 
     if (!model) {
-        model = new NoteModel(m_mainModelSampleRate, 1, 0.0, 0.0, false);
+        model = new NoteModel(m_mainModelSampleRate, 1, false);
         model->setValueQuantization(1.0);
         model->setObjectName(QFileInfo(m_path).fileName());
     }
--- a/data/fileio/MIDIFileWriter.h	Tue Jan 28 14:32:34 2020 +0000
+++ b/data/fileio/MIDIFileWriter.h	Tue Jan 28 14:34:13 2020 +0000
@@ -48,12 +48,12 @@
                    const NoteExportable *exportable, 
                    sv_samplerate_t sampleRate, // used to convert exportable sample timings
                    float tempo = 120.f);
-    virtual ~MIDIFileWriter();
+    ~MIDIFileWriter();
 
-    virtual bool isOK() const;
-    virtual QString getError() const;
+    bool isOK() const;
+    QString getError() const;
 
-    virtual void write();
+    void write();
 
 protected:
     typedef std::vector<MIDIEvent *> MIDITrack;
--- a/data/model/AlignmentModel.h	Tue Jan 28 14:32:34 2020 +0000
+++ b/data/model/AlignmentModel.h	Tue Jan 28 14:34:13 2020 +0000
@@ -77,6 +77,10 @@
                QString indent = "",
                QString extraAttributes = "") const override;
 
+    QString getDelimitedDataHeaderLine(QString, DataExportOptions) const override {
+        return "";
+    }
+    
     QString toDelimitedDataString(QString, DataExportOptions,
                                   sv_frame_t, sv_frame_t) const override {
         return "";
--- a/data/model/BasicCompressedDenseThreeDimensionalModel.cpp	Tue Jan 28 14:32:34 2020 +0000
+++ b/data/model/BasicCompressedDenseThreeDimensionalModel.cpp	Tue Jan 28 14:34:13 2020 +0000
@@ -499,6 +499,17 @@
 }
 
 QString
+BasicCompressedDenseThreeDimensionalModel::getDelimitedDataHeaderLine(QString delimiter,
+                                                                      DataExportOptions) const
+{
+    QStringList list;
+    for (int i = 0; i < m_yBinCount; ++i) {
+        list << QString("Bin%1").arg(i+1);
+    }
+    return list.join(delimiter);
+}    
+
+QString
 BasicCompressedDenseThreeDimensionalModel::toDelimitedDataString(QString delimiter,
                                                           DataExportOptions,
                                                           sv_frame_t startFrame,
--- a/data/model/BasicCompressedDenseThreeDimensionalModel.h	Tue Jan 28 14:32:34 2020 +0000
+++ b/data/model/BasicCompressedDenseThreeDimensionalModel.h	Tue Jan 28 14:34:13 2020 +0000
@@ -181,6 +181,9 @@
 
     QString getTypeName() const override { return tr("Editable Dense 3-D"); }
 
+    QString getDelimitedDataHeaderLine(QString delimiter,
+                                       DataExportOptions opts) const override;
+
     QString toDelimitedDataString(QString delimiter,
                                   DataExportOptions options,
                                   sv_frame_t startFrame,
--- a/data/model/BoxModel.h	Tue Jan 28 14:32:34 2020 +0000
+++ b/data/model/BoxModel.h	Tue Jan 28 14:34:13 2020 +0000
@@ -348,8 +348,26 @@
         m_events.toXml(out, indent, QString("dimensions=\"2\""), options);
     }
 
+    QString getDelimitedDataHeaderLine(QString delimiter,
+                                       DataExportOptions opts) const override {
+        QStringList list;
+
+        // These are considered API rather than human-readable text -
+        // they shouldn't be translated
+
+        if (opts & DataExportWriteTimeInFrames) {
+            list << "startframe" << "endframe";
+        } else {
+            list << "start" << "end";
+        }
+
+        list << "extent start" << "extent end" << "label";
+
+        return list.join(delimiter).toUpper();
+    }
+    
     QString toDelimitedDataString(QString delimiter,
-                                  DataExportOptions,
+                                  DataExportOptions opts,
                                   sv_frame_t startFrame,
                                   sv_frame_t duration) const override {
 
@@ -363,14 +381,25 @@
 
             QStringList list;
 
-            list << RealTime::frame2RealTime
-                (e.getFrame(), getSampleRate())
-                .toString().c_str()
-                 << RealTime::frame2RealTime
-                (e.getFrame() + e.getDuration(), getSampleRate())
-                .toString().c_str()
-                 << QString("%1").arg(e.getValue())
-                 << QString("%1").arg(e.getValue() + fabsf(e.getLevel()));
+            if (opts & DataExportWriteTimeInFrames) {
+                
+                list << QString("%1").arg(e.getFrame());
+                list << QString("%1").arg(e.getFrame() + e.getDuration());
+
+            } else {
+            
+                list << RealTime::frame2RealTime
+                    (e.getFrame(), getSampleRate())
+                    .toString().c_str();
+
+                list << RealTime::frame2RealTime
+                    (e.getFrame() + e.getDuration(), getSampleRate())
+                    .toString().c_str();
+            }
+
+            list << QString("%1").arg(e.getValue());
+
+            list << QString("%1").arg(e.getValue() + fabsf(e.getLevel()));
             
             if (e.getLabel() != "") {
                 list << e.getLabel();
--- a/data/model/Dense3DModelPeakCache.h	Tue Jan 28 14:32:34 2020 +0000
+++ b/data/model/Dense3DModelPeakCache.h	Tue Jan 28 14:34:13 2020 +0000
@@ -119,6 +119,10 @@
         return source ? source->getCompletion() : 100;
     }
 
+    QString getDelimitedDataHeaderLine(QString, DataExportOptions) const override {
+        return "";
+    }
+    
     QString toDelimitedDataString(QString, DataExportOptions,
                                   sv_frame_t, sv_frame_t) const override {
         return "";
--- a/data/model/DenseTimeValueModel.cpp	Tue Jan 28 14:32:34 2020 +0000
+++ b/data/model/DenseTimeValueModel.cpp	Tue Jan 28 14:34:13 2020 +0000
@@ -16,7 +16,19 @@
 #include "DenseTimeValueModel.h"
 
 #include <QStringList>
-        
+
+QString
+DenseTimeValueModel::getDelimitedDataHeaderLine(QString delimiter,
+                                                DataExportOptions) const
+{
+    int ch = getChannelCount();
+    QStringList list;
+    for (int i = 0; i < ch; ++i) {
+        list << QString("Channel%1").arg(i+1);
+    }
+    return list.join(delimiter);
+}
+
 QString
 DenseTimeValueModel::toDelimitedDataString(QString delimiter,
                                            DataExportOptions,
--- a/data/model/DenseTimeValueModel.h	Tue Jan 28 14:32:34 2020 +0000
+++ b/data/model/DenseTimeValueModel.h	Tue Jan 28 14:34:13 2020 +0000
@@ -82,6 +82,9 @@
     bool canPlay() const override { return true; }
     QString getDefaultPlayClipId() const override { return ""; }
 
+    QString getDelimitedDataHeaderLine(QString delimiter,
+                                       DataExportOptions options) const override;
+    
     QString toDelimitedDataString(QString delimiter,
                                   DataExportOptions options,
                                   sv_frame_t startFrame,
--- a/data/model/EditableDenseThreeDimensionalModel.cpp	Tue Jan 28 14:32:34 2020 +0000
+++ b/data/model/EditableDenseThreeDimensionalModel.cpp	Tue Jan 28 14:34:13 2020 +0000
@@ -352,6 +352,17 @@
 }
 
 QString
+EditableDenseThreeDimensionalModel::getDelimitedDataHeaderLine(QString delimiter,
+                                                               DataExportOptions) const
+{
+    QStringList list;
+    for (int i = 0; i < m_yBinCount; ++i) {
+        list << QString("Bin%1").arg(i+1);
+    }
+    return list.join(delimiter);
+}    
+    
+QString
 EditableDenseThreeDimensionalModel::toDelimitedDataString(QString delimiter,
                                                           DataExportOptions,
                                                           sv_frame_t startFrame,
--- a/data/model/EditableDenseThreeDimensionalModel.h	Tue Jan 28 14:32:34 2020 +0000
+++ b/data/model/EditableDenseThreeDimensionalModel.h	Tue Jan 28 14:34:13 2020 +0000
@@ -172,6 +172,9 @@
 
     QString getTypeName() const override { return tr("Editable Dense 3-D"); }
 
+    QString getDelimitedDataHeaderLine(QString delimiter,
+                                       DataExportOptions opts) const override;
+
     QString toDelimitedDataString(QString delimiter,
                                   DataExportOptions options,
                                   sv_frame_t startFrame,
--- a/data/model/FFTModel.h	Tue Jan 28 14:32:34 2020 +0000
+++ b/data/model/FFTModel.h	Tue Jan 28 14:34:13 2020 +0000
@@ -99,6 +99,9 @@
     float getBinValue(int n) const override;
     QString getBinName(int n) const override;
 
+    QString getDelimitedDataHeaderLine(QString, DataExportOptions) const override {
+        return "";
+    }
     QString toDelimitedDataString(QString, DataExportOptions,
                                   sv_frame_t, sv_frame_t) const override {
         return "";
--- a/data/model/ImageModel.h	Tue Jan 28 14:32:34 2020 +0000
+++ b/data/model/ImageModel.h	Tue Jan 28 14:34:13 2020 +0000
@@ -275,6 +275,16 @@
         m_events.toXml(out, indent, QString("dimensions=\"1\""), options);
     }
 
+    QString getDelimitedDataHeaderLine(QString delimiter,
+                                       DataExportOptions options) const override {
+        Event::ExportNameOptions nameOpts;
+        nameOpts.uriAttributeName = "image";
+
+        return m_events.getDelimitedDataHeaderLine(delimiter,
+                                                   options,
+                                                   nameOpts);
+    }
+    
     QString toDelimitedDataString(QString delimiter,
                                   DataExportOptions options,
                                   sv_frame_t startFrame,
--- a/data/model/Model.h	Tue Jan 28 14:32:34 2020 +0000
+++ b/data/model/Model.h	Tue Jan 28 14:34:13 2020 +0000
@@ -274,6 +274,17 @@
                QString indent = "",
                QString extraAttributes = "") const override;
 
+    /**
+     * Emit a label for each column that would be written by
+     * toDelimitedDataString, separated by the given delimiter.
+     */
+    virtual QString getDelimitedDataHeaderLine(QString delimiter,
+                                               DataExportOptions options) const = 0;
+    
+    /**
+     * Emit the contents of the model within the given range to a
+     * delimited (e.g. comma-separated) data format.
+     */
     virtual QString toDelimitedDataString(QString delimiter,
                                           DataExportOptions options,
                                           sv_frame_t startFrame,
--- a/data/model/NoteModel.h	Tue Jan 28 14:32:34 2020 +0000
+++ b/data/model/NoteModel.h	Tue Jan 28 14:34:13 2020 +0000
@@ -197,6 +197,9 @@
         return m_events.getNearestEventMatching
             (startSearchAt, predicate, direction, found);
     }
+    int getIndexForEvent(const Event &e) {
+        return m_events.getIndexForEvent(e);
+    }
 
     /**
      * Editing methods.
@@ -405,6 +408,13 @@
         m_events.toXml(out, indent, QString("dimensions=\"3\""));
     }
 
+    QString getDelimitedDataHeaderLine(QString delimiter,
+                                       DataExportOptions options) const override {
+        return m_events.getDelimitedDataHeaderLine(delimiter,
+                                                   options,
+                                                   Event::ExportNameOptions());
+    }
+    
     QString toDelimitedDataString(QString delimiter,
                                   DataExportOptions options,
                                   sv_frame_t startFrame,
--- a/data/model/RegionModel.h	Tue Jan 28 14:32:34 2020 +0000
+++ b/data/model/RegionModel.h	Tue Jan 28 14:34:13 2020 +0000
@@ -340,6 +340,13 @@
         m_events.toXml(out, indent, QString("dimensions=\"3\""));
     }
 
+    QString getDelimitedDataHeaderLine(QString delimiter,
+                                       DataExportOptions options) const override {
+        return m_events.getDelimitedDataHeaderLine(delimiter,
+                                                   options,
+                                                   Event::ExportNameOptions());
+    }
+    
     QString toDelimitedDataString(QString delimiter,
                                   DataExportOptions options,
                                   sv_frame_t startFrame,
--- a/data/model/SparseOneDimensionalModel.h	Tue Jan 28 14:32:34 2020 +0000
+++ b/data/model/SparseOneDimensionalModel.h	Tue Jan 28 14:34:13 2020 +0000
@@ -308,6 +308,13 @@
         m_events.toXml(out, indent, QString("dimensions=\"1\""));
     }
 
+    QString getDelimitedDataHeaderLine(QString delimiter,
+                                       DataExportOptions options) const override {
+        return m_events.getDelimitedDataHeaderLine(delimiter,
+                                                   options,
+                                                   Event::ExportNameOptions());
+    }
+    
     QString toDelimitedDataString(QString delimiter,
                                   DataExportOptions options,
                                   sv_frame_t startFrame,
--- a/data/model/SparseTimeValueModel.h	Tue Jan 28 14:32:34 2020 +0000
+++ b/data/model/SparseTimeValueModel.h	Tue Jan 28 14:34:13 2020 +0000
@@ -347,6 +347,13 @@
         m_events.toXml(out, indent, QString("dimensions=\"2\""));
     }
 
+    QString getDelimitedDataHeaderLine(QString delimiter,
+                                       DataExportOptions options) const override {
+        return m_events.getDelimitedDataHeaderLine(delimiter,
+                                                   options,
+                                                   Event::ExportNameOptions());
+    }
+    
     QString toDelimitedDataString(QString delimiter,
                                   DataExportOptions options,
                                   sv_frame_t startFrame,
--- a/data/model/TextModel.h	Tue Jan 28 14:32:34 2020 +0000
+++ b/data/model/TextModel.h	Tue Jan 28 14:34:13 2020 +0000
@@ -278,6 +278,15 @@
         m_events.toXml(out, indent, QString("dimensions=\"2\""), options);
     }
 
+    QString getDelimitedDataHeaderLine(QString delimiter,
+                                       DataExportOptions options) const override {
+        Event::ExportNameOptions nameOpts;
+        nameOpts.valueAttributeName = "height";
+        return m_events.getDelimitedDataHeaderLine(delimiter,
+                                                   options,
+                                                   nameOpts);
+    }
+    
     QString toDelimitedDataString(QString delimiter,
                                   DataExportOptions options,
                                   sv_frame_t startFrame,
--- a/data/osc/OSCQueue.cpp	Tue Jan 28 14:32:34 2020 +0000
+++ b/data/osc/OSCQueue.cpp	Tue Jan 28 14:34:13 2020 +0000
@@ -34,8 +34,8 @@
 void
 OSCQueue::oscError(int num, const char *msg, const char *path)
 {
-    cerr << "ERROR: OSCQueue::oscError: liblo server error " << num
-              << " in path " << path << ": " << msg << endl;
+    SVCERR << "ERROR: OSCQueue::oscError: liblo server error " << num
+           << " in path " << path << ": " << msg << endl;
 }
 
 int
@@ -175,8 +175,7 @@
     OSCMessage *message = m_buffer.readOne();
     OSCMessage rmessage = *message;
     delete message;
-    SVDEBUG << "OSCQueue::readMessage: In thread "
-            << QThread::currentThreadId() << ": message follows:\n"
+    SVDEBUG << "OSCQueue::readMessage[" << QThread::currentThreadId() << "]: "
             << rmessage.toString() << endl;
     return rmessage;
 }
--- a/system/System.cpp	Tue Jan 28 14:32:34 2020 +0000
+++ b/system/System.cpp	Tue Jan 28 14:34:13 2020 +0000
@@ -27,6 +27,10 @@
 #include <unistd.h>
 #endif
 
+#ifdef _MSC_VER
+#include <winrt/Windows.UI.ViewManagement.h>
+#endif
+
 #ifdef __APPLE__
 #include <sys/param.h>
 #include <sys/sysctl.h>
@@ -334,6 +338,39 @@
     }
 }
 
+bool
+OSReportsDarkThemeActive()
+{
+    SVCERR << "OSReportsDarkThemeActive() called" << endl;
+#ifdef _MSC_VER
+    using namespace winrt::Windows::UI::ViewManagement;
+    UISettings settings;
+    auto background = settings.GetColorValue(UIColorType::Background);
+    if (int(background.R) + int(background.G) + int(background.B) < 384) {
+        return true;
+    }
+#endif
+    return false;
+}
+
+bool
+OSQueryAccentColour(int &r, int &g, int &b)
+{
+    SVCERR << "OSQueryAccentColour() called" << endl;
+#ifdef _MSC_VER
+    using namespace winrt::Windows::UI::ViewManagement;
+    bool dark = OSReportsDarkThemeActive();
+    UISettings settings;
+    auto accent = settings.GetColorValue
+        (dark ? UIColorType::AccentLight1 : UIColorType::Accent);
+    r = accent.R;
+    g = accent.G;
+    b = accent.B;
+    return true;
+#endif
+    return false;
+}
+
 double mod(double x, double y) { return x - (y * floor(x / y)); }
 float modf(float x, float y) { return x - (y * floorf(x / y)); }
 
--- a/system/System.h	Tue Jan 28 14:32:34 2020 +0000
+++ b/system/System.h	Tue Jan 28 14:34:13 2020 +0000
@@ -163,6 +163,17 @@
 // if unknown. (Hence signed return type)
 extern ssize_t GetDiscSpaceMBAvailable(const char *path);
 
+// Return true if the OS desktop is set to use a dark mode
+// theme. Return false if it is set to a light theme or if the theme
+// is unknown.
+extern bool OSReportsDarkThemeActive();
+
+// Return true if the OS desktop reports an accent colour to go with
+// the current theme; if so, also return by reference the r, g, and b
+// components of the colour (range 0-255). Return false if we can't
+// query such a thing.
+extern bool OSQueryAccentColour(int &r, int &g, int &b);
+
 extern void StoreStartupLocale();
 extern void RestoreStartupLocale();