<commit_before>/******************************************************************************

  This source file is part of the tomviz project.

  Copyright Kitware, Inc.

  This source code is released under the New BSD License, (the "License").

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.

******************************************************************************/
#include "RecentFilesMenu.h"

#include "ActiveObjects.h"
#include "DataSource.h"
#include "LoadDataReaction.h"
#include "SaveLoadStateReaction.h"
#include "Utilities.h"

#include <pqSettings.h>

#include <QDebug>
#include <QMenu>
#include <QMessageBox>

#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>

#include <string>

namespace tomviz {

static const int MAX_ITEMS = 10;

namespace{
void get_settings(pugi::xml_document& doc)
{
  QSettings* settings = pqApplicationCore::instance()->settings();
  QString recent = settings->value("recentFiles").toString();
  if (recent.isEmpty() || !doc.load(recent.toUtf8().data()) ||
      !doc.child("tomvizRecentFilesMenu")) {
    doc.append_child("tomvizRecentFilesMenu");
  }
}

QJsonObject loadSettings()
{
  // Load the recent files from our saved state.
  auto settings = pqApplicationCore::instance()->settings();
  auto recent = settings->value("recentFiles").toByteArray();
  auto doc = QJsonDocument::fromJson(recent);
  if (!doc.isNull() && doc.isObject()) {
    return doc.object();
  } else {
    QJsonObject json;
    json["readers"] = QJsonArray();
    return QJsonObject();
  }
}

void saveSettings(QJsonObject json)
{
  // Save recent file list to our saved state, prune lists to be <= 10 entries.
  QJsonArray readers;
  if (json.contains("readers") && json["readers"].isArray()) {
    readers = json["readers"].toArray();
  }
  QJsonArray states;
  if (json.contains("states") && json["states"].isArray()) {
    states = json["states"].toArray();
  }
  if (readers.size() > 10) {
    // We need to prune the list to 10.
    while (readers.size() > 10) {
      readers.removeLast();
    }
  }
  if (states.size() > 10) {
    // We need to prune the list to 10.
    while (states.size() > 10) {
      states.removeLast();
    }
  }
  QJsonDocument doc(json);

  auto settings = pqApplicationCore::instance()->settings();
  settings->setValue("recentFiles", doc.toJson(QJsonDocument::Compact));
}

void save_settings(pugi::xml_document& doc)
{
/*
  // trim the list.
  pugi::xml_node root = doc.root();
  std::vector<pugi::xml_node> to_remove;
  int counter = 0;
  for (pugi::xml_node node = root.child("DataReader"); node;
       node = node.next_sibling("DataReader"), counter++) {
    if (counter >= MAX_ITEMS) {
      to_remove.push_back(node);
    }
  }
  counter = 0;
  for (pugi::xml_node node = root.child("State"); node;
       node = node.next_sibling("State"), counter++) {
    if (counter >= MAX_ITEMS) {
      to_remove.push_back(node);
    }
  }
  for (size_t cc = 0; cc < to_remove.size(); cc++) {
    root.remove_child(to_remove[cc]);
  }

  std::ostringstream stream;
  doc.save(stream);
  QSettings* settings = pqApplicationCore::instance()->settings();
  settings->setValue("recentFiles", stream.str().c_str());
*/
}

}

RecentFilesMenu::RecentFilesMenu(QMenu& menu, QObject* p)
  : QObject(p)
{
  connect(&menu, SIGNAL(aboutToShow()), SLOT(aboutToShowMenu()));
}

RecentFilesMenu::~RecentFilesMenu() = default;

void RecentFilesMenu::pushDataReader(DataSource* dataSource)
{
  // Add non-proxy based readers separately.
  auto settings = loadSettings();
  auto readerList = settings["readers"].toArray();
  QJsonObject readerJson;
  readerJson["fileName"] = dataSource->fileName();
  readerJson["stack"] = dataSource->isImageStack();
  if (!dataSource->pvReaderXml().isEmpty()) {
    readerJson["pvXml"] = dataSource->pvReaderXml();
  }
  readerList.push_front(readerJson);
  settings["readers"] = readerList;
  saveSettings(settings);
}

void RecentFilesMenu::pushStateFile(const QString& filename)
{
  pugi::xml_document settings;
  get_settings(settings);

  pugi::xml_node root = settings.root();
  for (pugi::xml_node node = root.child("State"); node;
       node = node.next_sibling("State")) {
    if (filename == node.attribute("filename").as_string("")) {
      root.remove_child(node);
      break;
    }
  }

  pugi::xml_node node = root.prepend_child("State");
  node.append_attribute("filename").set_value(filename.toLatin1().data());
  save_settings(settings);
}

void RecentFilesMenu::aboutToShowMenu()
{
  auto menu = qobject_cast<QMenu*>(sender());
  Q_ASSERT(menu);
  menu->clear();

  auto json = loadSettings();

  // There are no entries, make it clear in the recent files menu.
  if (!json.contains("readers") && !json.contains("states")) {
    auto actn = menu->addAction("Empty");
    actn->setEnabled(false);
    return;
  }

  // We have something, let's populate the recent files and/or state files.
  bool headerAdded = false;
  int index = 0;
  foreach(QJsonValue file, json["readers"].toArray()) {
    if (file.isObject()) {
      auto object = file.toObject();
      if (headerAdded == false) {
        auto actn = menu->addAction("Data files");
        actn->setEnabled(false);
        headerAdded = true;
      }
      bool stack = object["stack"].toBool(false);
      auto actn =
        menu->addAction(QIcon(":/pqWidgets/Icons/pqInspect22.png"),
                        object["fileName"].toString("<bug>"));
      actn->setData(index);
      connect(actn, &QAction::triggered, [this, actn, stack]() {
        dataSourceTriggered(actn, stack);
      });
      ++index;
    }
  }

  headerAdded = false;
  foreach(QJsonValue file, json["states"].toArray()) {
    qDebug() << "File:" << file;
    if (file.isObject()) {
      auto object = file.toObject();
      if (headerAdded == false) {
        auto actn = menu->addAction("State files");
        actn->setEnabled(false);
        headerAdded = true;
      }
      auto actn =
        menu->addAction(QIcon(":/icons/tomviz.png"),
                        object["fileName"].toString("<bug>"));
      actn->setData(object["fileName"].toString("<bug>"));
      connect(actn, SIGNAL(triggered()), SLOT(stateTriggered()));
    }
  }
}

void RecentFilesMenu::dataSourceTriggered(QAction* actn, bool stack)
{
  int index = actn->data().toInt();
  auto json = loadSettings();
  QJsonArray readers;
  if (json["readers"].isArray()) {
    readers = json["readers"].toArray();
  } else {
    return;
  }

  QJsonObject file;
  if (index < readers.size() && readers.at(index).isObject()) {
    file = readers.at(index).toObject();
    // Check the file actually exists, remove it if not.
    if (!QFileInfo::exists(file["fileName"].toString()) && !stack) {
      QMessageBox::warning(
        tomviz::mainWidget(), "Error",
        QString("The file '%1' does not exist").arg(actn->iconText()));
      readers.removeAt(index);
      json["readers"] = readers;
      saveSettings(json);
      return;
    }
    // Otherwise attempt to load the file, and bump it to the top of the list
    // if it is loaded successfully.
    auto ds = LoadDataReaction::loadData(file["fileName"].toString(), true,
                                         false, false);
    readers.removeAt(index);
    if (ds) {
      readers.prepend(file);
    }
    json["readers"] = readers;
    saveSettings(json);
  }
}

void RecentFilesMenu::stateTriggered()
{
  auto actn = qobject_cast<QAction*>(sender());
  Q_ASSERT(actn);

  QString filename = actn->data().toString();

  if (QFileInfo::exists(actn->iconText())) {
    if (SaveLoadStateReaction::loadState(filename)) {
      // the above call will ensure that the file name moves to top of the list
      // since it calls pushStateFile() on success.
      return;
    }
  } else {
    // // If the user tried to open a recent file that no longer exists, remove
    // it from the recent files
    QMessageBox::warning(
      tomviz::mainWidget(), "Error",
      QString("The file '%1' does not exist").arg(actn->iconText()));
  }

  // remove the item from the recent state files list.
  pugi::xml_document settings;
  get_settings(settings);
  pugi::xml_node root = settings.root();
  for (pugi::xml_node node = root.child("State"); node;
       node = node.next_sibling("State")) {
    if (filename == node.attribute("filename").as_string()) {
      root.remove_child(node);
      save_settings(settings);
      break;
    }
  }
}
}
<commit_msg>Fixed two warnings<commit_after>/******************************************************************************

  This source file is part of the tomviz project.

  Copyright Kitware, Inc.

  This source code is released under the New BSD License, (the "License").

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.

******************************************************************************/
#include "RecentFilesMenu.h"

#include "ActiveObjects.h"
#include "DataSource.h"
#include "LoadDataReaction.h"
#include "SaveLoadStateReaction.h"
#include "Utilities.h"

#include <pqSettings.h>

#include <QDebug>
#include <QMenu>
#include <QMessageBox>

#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>

#include <string>

namespace tomviz {

namespace{
void get_settings(pugi::xml_document& doc)
{
  QSettings* settings = pqApplicationCore::instance()->settings();
  QString recent = settings->value("recentFiles").toString();
  if (recent.isEmpty() || !doc.load(recent.toUtf8().data()) ||
      !doc.child("tomvizRecentFilesMenu")) {
    doc.append_child("tomvizRecentFilesMenu");
  }
}

QJsonObject loadSettings()
{
  // Load the recent files from our saved state.
  auto settings = pqApplicationCore::instance()->settings();
  auto recent = settings->value("recentFiles").toByteArray();
  auto doc = QJsonDocument::fromJson(recent);
  if (!doc.isNull() && doc.isObject()) {
    return doc.object();
  } else {
    QJsonObject json;
    json["readers"] = QJsonArray();
    return QJsonObject();
  }
}

void saveSettings(QJsonObject json)
{
  // Save recent file list to our saved state, prune lists to be <= 10 entries.
  QJsonArray readers;
  if (json.contains("readers") && json["readers"].isArray()) {
    readers = json["readers"].toArray();
  }
  QJsonArray states;
  if (json.contains("states") && json["states"].isArray()) {
    states = json["states"].toArray();
  }
  if (readers.size() > 10) {
    // We need to prune the list to 10.
    while (readers.size() > 10) {
      readers.removeLast();
    }
  }
  if (states.size() > 10) {
    // We need to prune the list to 10.
    while (states.size() > 10) {
      states.removeLast();
    }
  }
  QJsonDocument doc(json);

  auto settings = pqApplicationCore::instance()->settings();
  settings->setValue("recentFiles", doc.toJson(QJsonDocument::Compact));
}

void save_settings(pugi::xml_document& doc)
{
  Q_UNUSED(doc)
/*
  // trim the list.
  pugi::xml_node root = doc.root();
  std::vector<pugi::xml_node> to_remove;
  int counter = 0;
  for (pugi::xml_node node = root.child("DataReader"); node;
       node = node.next_sibling("DataReader"), counter++) {
    if (counter >= MAX_ITEMS) {
      to_remove.push_back(node);
    }
  }
  counter = 0;
  for (pugi::xml_node node = root.child("State"); node;
       node = node.next_sibling("State"), counter++) {
    if (counter >= MAX_ITEMS) {
      to_remove.push_back(node);
    }
  }
  for (size_t cc = 0; cc < to_remove.size(); cc++) {
    root.remove_child(to_remove[cc]);
  }

  std::ostringstream stream;
  doc.save(stream);
  QSettings* settings = pqApplicationCore::instance()->settings();
  settings->setValue("recentFiles", stream.str().c_str());
*/
}

}

RecentFilesMenu::RecentFilesMenu(QMenu& menu, QObject* p)
  : QObject(p)
{
  connect(&menu, SIGNAL(aboutToShow()), SLOT(aboutToShowMenu()));
}

RecentFilesMenu::~RecentFilesMenu() = default;

void RecentFilesMenu::pushDataReader(DataSource* dataSource)
{
  // Add non-proxy based readers separately.
  auto settings = loadSettings();
  auto readerList = settings["readers"].toArray();
  QJsonObject readerJson;
  readerJson["fileName"] = dataSource->fileName();
  readerJson["stack"] = dataSource->isImageStack();
  if (!dataSource->pvReaderXml().isEmpty()) {
    readerJson["pvXml"] = dataSource->pvReaderXml();
  }
  readerList.push_front(readerJson);
  settings["readers"] = readerList;
  saveSettings(settings);
}

void RecentFilesMenu::pushStateFile(const QString& filename)
{
  pugi::xml_document settings;
  get_settings(settings);

  pugi::xml_node root = settings.root();
  for (pugi::xml_node node = root.child("State"); node;
       node = node.next_sibling("State")) {
    if (filename == node.attribute("filename").as_string("")) {
      root.remove_child(node);
      break;
    }
  }

  pugi::xml_node node = root.prepend_child("State");
  node.append_attribute("filename").set_value(filename.toLatin1().data());
  save_settings(settings);
}

void RecentFilesMenu::aboutToShowMenu()
{
  auto menu = qobject_cast<QMenu*>(sender());
  Q_ASSERT(menu);
  menu->clear();

  auto json = loadSettings();

  // There are no entries, make it clear in the recent files menu.
  if (!json.contains("readers") && !json.contains("states")) {
    auto actn = menu->addAction("Empty");
    actn->setEnabled(false);
    return;
  }

  // We have something, let's populate the recent files and/or state files.
  bool headerAdded = false;
  int index = 0;
  foreach(QJsonValue file, json["readers"].toArray()) {
    if (file.isObject()) {
      auto object = file.toObject();
      if (headerAdded == false) {
        auto actn = menu->addAction("Data files");
        actn->setEnabled(false);
        headerAdded = true;
      }
      bool stack = object["stack"].toBool(false);
      auto actn =
        menu->addAction(QIcon(":/pqWidgets/Icons/pqInspect22.png"),
                        object["fileName"].toString("<bug>"));
      actn->setData(index);
      connect(actn, &QAction::triggered, [this, actn, stack]() {
        dataSourceTriggered(actn, stack);
      });
      ++index;
    }
  }

  headerAdded = false;
  foreach(QJsonValue file, json["states"].toArray()) {
    qDebug() << "File:" << file;
    if (file.isObject()) {
      auto object = file.toObject();
      if (headerAdded == false) {
        auto actn = menu->addAction("State files");
        actn->setEnabled(false);
        headerAdded = true;
      }
      auto actn =
        menu->addAction(QIcon(":/icons/tomviz.png"),
                        object["fileName"].toString("<bug>"));
      actn->setData(object["fileName"].toString("<bug>"));
      connect(actn, SIGNAL(triggered()), SLOT(stateTriggered()));
    }
  }
}

void RecentFilesMenu::dataSourceTriggered(QAction* actn, bool stack)
{
  int index = actn->data().toInt();
  auto json = loadSettings();
  QJsonArray readers;
  if (json["readers"].isArray()) {
    readers = json["readers"].toArray();
  } else {
    return;
  }

  QJsonObject file;
  if (index < readers.size() && readers.at(index).isObject()) {
    file = readers.at(index).toObject();
    // Check the file actually exists, remove it if not.
    if (!QFileInfo::exists(file["fileName"].toString()) && !stack) {
      QMessageBox::warning(
        tomviz::mainWidget(), "Error",
        QString("The file '%1' does not exist").arg(actn->iconText()));
      readers.removeAt(index);
      json["readers"] = readers;
      saveSettings(json);
      return;
    }
    // Otherwise attempt to load the file, and bump it to the top of the list
    // if it is loaded successfully.
    auto ds = LoadDataReaction::loadData(file["fileName"].toString(), true,
                                         false, false);
    readers.removeAt(index);
    if (ds) {
      readers.prepend(file);
    }
    json["readers"] = readers;
    saveSettings(json);
  }
}

void RecentFilesMenu::stateTriggered()
{
  auto actn = qobject_cast<QAction*>(sender());
  Q_ASSERT(actn);

  QString filename = actn->data().toString();

  if (QFileInfo::exists(actn->iconText())) {
    if (SaveLoadStateReaction::loadState(filename)) {
      // the above call will ensure that the file name moves to top of the list
      // since it calls pushStateFile() on success.
      return;
    }
  } else {
    // // If the user tried to open a recent file that no longer exists, remove
    // it from the recent files
    QMessageBox::warning(
      tomviz::mainWidget(), "Error",
      QString("The file '%1' does not exist").arg(actn->iconText()));
  }

  // remove the item from the recent state files list.
  pugi::xml_document settings;
  get_settings(settings);
  pugi::xml_node root = settings.root();
  for (pugi::xml_node node = root.child("State"); node;
       node = node.next_sibling("State")) {
    if (filename == node.attribute("filename").as_string()) {
      root.remove_child(node);
      save_settings(settings);
      break;
    }
  }
}
}
<|endoftext|>


<commit_before>// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/io_thread.h"
#include "base/command_line.h"
#include "base/leak_tracker.h"
#include "base/logging.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chrome_thread.h"
#include "chrome/browser/gpu_process_host.h"
#include "chrome/browser/net/chrome_net_log.h"
#include "chrome/browser/net/predictor_api.h"
#include "chrome/browser/net/passive_log_collector.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/net/url_fetcher.h"
#include "net/base/mapped_host_resolver.h"
#include "net/base/host_cache.h"
#include "net/base/host_resolver.h"
#include "net/base/host_resolver_impl.h"
#include "net/base/net_util.h"
#include "net/http/http_auth_filter.h"
#include "net/http/http_auth_handler_factory.h"
#include "net/http/http_auth_handler_negotiate.h"

namespace {

net::HostResolver* CreateGlobalHostResolver() {
  const CommandLine& command_line = *CommandLine::ForCurrentProcess();
  net::HostResolver* global_host_resolver = net::CreateSystemHostResolver();

  // Determine if we should disable IPv6 support.
  if (!command_line.HasSwitch(switches::kEnableIPv6)) {
    if (command_line.HasSwitch(switches::kDisableIPv6)) {
      global_host_resolver->SetDefaultAddressFamily(net::ADDRESS_FAMILY_IPV4);
    } else {
      net::HostResolverImpl* host_resolver_impl =
          global_host_resolver->GetAsHostResolverImpl();
      if (host_resolver_impl != NULL) {
        // (optionally) Use probe to decide if support is warranted.
        bool use_ipv6_probe = true;

#if defined(OS_WIN)
        // Measure impact of probing to allow IPv6.
        // Some users report confused OS handling of IPv6, leading to large
        // latency.  If we can show that IPv6 is not supported, then disabliing
        // it will work around such problems. This is the test of the probe.
        const FieldTrial::Probability kDivisor = 100;
        const FieldTrial::Probability kProbability = 50;  // 50% probability.
        FieldTrial* trial = new FieldTrial("IPv6_Probe", kDivisor);
        int skip_group = trial->AppendGroup("_IPv6_probe_skipped",
                                            kProbability);
        trial->AppendGroup("_IPv6_probe_done",
                           FieldTrial::kAllRemainingProbability);
        use_ipv6_probe = (trial->group() != skip_group);
#endif

        if (use_ipv6_probe)
          host_resolver_impl->ProbeIPv6Support();
      }
    }
  }

  // If hostname remappings were specified on the command-line, layer these
  // rules on top of the real host resolver. This allows forwarding all requests
  // through a designated test server.
  if (!command_line.HasSwitch(switches::kHostResolverRules))
    return global_host_resolver;

  net::MappedHostResolver* remapped_resolver =
      new net::MappedHostResolver(global_host_resolver);
  remapped_resolver->SetRulesFromString(
      command_line.GetSwitchValueASCII(switches::kHostResolverRules));
  return remapped_resolver;
}

class LoggingNetworkChangeObserver
    : public net::NetworkChangeNotifier::Observer {
 public:
  // |net_log| must remain valid throughout our lifetime.
  explicit LoggingNetworkChangeObserver(net::NetLog* net_log)
      : net_log_(net_log) {
    net::NetworkChangeNotifier::AddObserver(this);
  }

  ~LoggingNetworkChangeObserver() {
    net::NetworkChangeNotifier::RemoveObserver(this);
  }

  virtual void OnIPAddressChanged() {
    LOG(INFO) << "Observed a change to the network IP addresses";

    net::NetLog::Source global_source;

    // TODO(eroman): We shouldn't need to assign an ID to this source, since
    //               conceptually it is the "global event stream". However
    //               currently the javascript does a grouping on source id, so
    //               the display will look weird if we don't give it one.
    global_source.id = net_log_->NextID();

    net_log_->AddEntry(net::NetLog::TYPE_NETWORK_IP_ADDRESSSES_CHANGED,
                       base::TimeTicks::Now(),
                       global_source,
                       net::NetLog::PHASE_NONE,
                       NULL);
  }

 private:
  net::NetLog* net_log_;
  DISALLOW_COPY_AND_ASSIGN(LoggingNetworkChangeObserver);
};

}  // namespace

// The IOThread object must outlive any tasks posted to the IO thread before the
// Quit task.
DISABLE_RUNNABLE_METHOD_REFCOUNT(IOThread);

IOThread::IOThread()
    : BrowserProcessSubThread(ChromeThread::IO),
      globals_(NULL),
      speculative_interceptor_(NULL),
      prefetch_observer_(NULL),
      predictor_(NULL) {}

IOThread::~IOThread() {
  // We cannot rely on our base class to stop the thread since we want our
  // CleanUp function to run.
  Stop();
  DCHECK(!globals_);
}

IOThread::Globals* IOThread::globals() {
  DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO));
  return globals_;
}

void IOThread::InitNetworkPredictor(
    bool prefetching_enabled,
    base::TimeDelta max_dns_queue_delay,
    size_t max_concurrent,
    const chrome_common_net::UrlList& startup_urls,
    ListValue* referral_list,
    bool preconnect_enabled) {
  DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
  message_loop()->PostTask(
      FROM_HERE,
      NewRunnableMethod(
          this,
          &IOThread::InitNetworkPredictorOnIOThread,
          prefetching_enabled, max_dns_queue_delay, max_concurrent,
          startup_urls, referral_list, preconnect_enabled));
}

void IOThread::ChangedToOnTheRecord() {
  DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
  message_loop()->PostTask(
      FROM_HERE,
      NewRunnableMethod(
          this,
          &IOThread::ChangedToOnTheRecordOnIOThread));
}

void IOThread::Init() {
  BrowserProcessSubThread::Init();

  DCHECK(!globals_);
  globals_ = new Globals;

  globals_->net_log.reset(new ChromeNetLog());

  // Add an observer that will emit network change events to the ChromeNetLog.
  // Assuming NetworkChangeNotifier dispatches in FIFO order, we should be
  // logging the network change before other IO thread consumers respond to it.
  network_change_observer_.reset(
      new LoggingNetworkChangeObserver(globals_->net_log.get()));

  globals_->host_resolver = CreateGlobalHostResolver();
  globals_->http_auth_handler_factory.reset(CreateDefaultAuthHandlerFactory(
      globals_->host_resolver));
}

void IOThread::CleanUp() {
  // This must be reset before the ChromeNetLog is destroyed.
  network_change_observer_.reset();

  // If any child processes are still running, terminate them and
  // and delete the BrowserChildProcessHost instances to release whatever
  // IO thread only resources they are referencing.
  BrowserChildProcessHost::TerminateAll();

  // Not initialized in Init().  May not be initialized.
  if (predictor_) {
    predictor_->Shutdown();

    // TODO(willchan): Stop reference counting Predictor.  It's owned by
    // IOThread now.
    predictor_->Release();
    predictor_ = NULL;
    chrome_browser_net::FreePredictorResources();
  }

  // Deletion will unregister this interceptor.
  delete speculative_interceptor_;
  speculative_interceptor_ = NULL;

  // Not initialized in Init().  May not be initialized.
  if (prefetch_observer_) {
    globals_->host_resolver->RemoveObserver(prefetch_observer_);
    delete prefetch_observer_;
    prefetch_observer_ = NULL;
  }

  // TODO(eroman): hack for http://crbug.com/15513
  if (globals_->host_resolver->GetAsHostResolverImpl()) {
    globals_->host_resolver.get()->GetAsHostResolverImpl()->Shutdown();
  }

  // We will delete the NetLog as part of CleanUpAfterMessageLoopDestruction()
  // in case any of the message loop destruction observers try to access it.
  deferred_net_log_to_delete_.reset(globals_->net_log.release());

  delete globals_;
  globals_ = NULL;

  // URLRequest instances must NOT outlive the IO thread.
  base::LeakTracker<URLRequest>::CheckForLeaks();

  BrowserProcessSubThread::CleanUp();
}

void IOThread::CleanUpAfterMessageLoopDestruction() {
  // TODO(eroman): get rid of this special case for 39723. If we could instead
  // have a method that runs after the message loop destruction obsevers have
  // run, but before the message loop itself is destroyed, we could safely
  // combine the two cleanups.
  deferred_net_log_to_delete_.reset();
  BrowserProcessSubThread::CleanUpAfterMessageLoopDestruction();
}

net::HttpAuthHandlerFactory* IOThread::CreateDefaultAuthHandlerFactory(
    net::HostResolver* resolver) {
  net::HttpAuthFilterWhitelist* auth_filter = NULL;

  // Get the whitelist information from the command line, create an
  // HttpAuthFilterWhitelist, and attach it to the HttpAuthHandlerFactory.
  const CommandLine& command_line = *CommandLine::ForCurrentProcess();

  if (command_line.HasSwitch(switches::kAuthServerWhitelist)) {
    std::string auth_server_whitelist =
        command_line.GetSwitchValueASCII(switches::kAuthServerWhitelist);

    // Create a whitelist filter.
    auth_filter = new net::HttpAuthFilterWhitelist();
    auth_filter->SetWhitelist(auth_server_whitelist);
  }

  // Set the flag that enables or disables the Negotiate auth handler.
#if defined(OS_WIN)
  static const bool kNegotiateAuthEnabledDefault = true;
#else
  static const bool kNegotiateAuthEnabledDefault = false;
#endif
  bool negotiate_auth_enabled = kNegotiateAuthEnabledDefault;
  if (command_line.HasSwitch(switches::kExperimentalEnableNegotiateAuth)) {
    std::string enable_negotiate_auth = command_line.GetSwitchValueASCII(
        switches::kExperimentalEnableNegotiateAuth);
    // Enabled if no value, or value is 'true'.  Disabled otherwise.
    negotiate_auth_enabled =
        enable_negotiate_auth.empty() ||
        (StringToLowerASCII(enable_negotiate_auth) == "true");
  }

  net::HttpAuthHandlerRegistryFactory* registry_factory =
      net::HttpAuthHandlerFactory::CreateDefault();

  globals_->url_security_manager.reset(
      net::URLSecurityManager::Create(auth_filter));

  // Add the security manager to the auth factories that need it.
  registry_factory->SetURLSecurityManager("ntlm",
                                          globals_->url_security_manager.get());
  registry_factory->SetURLSecurityManager("negotiate",
                                          globals_->url_security_manager.get());
  if (negotiate_auth_enabled) {
    // Configure the Negotiate settings for the Kerberos SPN.
    // TODO(cbentzel): Read the related IE registry settings on Windows builds.
    // TODO(cbentzel): Ugly use of static_cast here.
    net::HttpAuthHandlerNegotiate::Factory* negotiate_factory =
        static_cast<net::HttpAuthHandlerNegotiate::Factory*>(
            registry_factory->GetSchemeFactory("negotiate"));
    DCHECK(negotiate_factory);
    if (command_line.HasSwitch(switches::kDisableAuthNegotiateCnameLookup))
      negotiate_factory->set_disable_cname_lookup(true);
    if (command_line.HasSwitch(switches::kEnableAuthNegotiatePort))
      negotiate_factory->set_use_port(true);
  } else {
    // Disable the Negotiate authentication handler.
    registry_factory->RegisterSchemeFactory("negotiate", NULL);
  }
  return registry_factory;
}

void IOThread::InitNetworkPredictorOnIOThread(
    bool prefetching_enabled,
    base::TimeDelta max_dns_queue_delay,
    size_t max_concurrent,
    const chrome_common_net::UrlList& startup_urls,
    ListValue* referral_list,
    bool preconnect_enabled) {
  DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO));
  CHECK(!predictor_);

  chrome_browser_net::EnablePredictor(prefetching_enabled);

  predictor_ = new chrome_browser_net::Predictor(
      globals_->host_resolver,
      max_dns_queue_delay,
      max_concurrent,
      preconnect_enabled);
  predictor_->AddRef();

  // TODO(jar): Until connection notification and DNS observation handling are
  // properly combined into a learning model, we'll only use one observation
  // mechanism or the other.
  if (preconnect_enabled) {
    DCHECK(!speculative_interceptor_);
    speculative_interceptor_ = new chrome_browser_net::ConnectInterceptor;
  } else {
    DCHECK(!prefetch_observer_);
    prefetch_observer_ = chrome_browser_net::CreateResolverObserver();
    globals_->host_resolver->AddObserver(prefetch_observer_);
  }

  FinalizePredictorInitialization(
      predictor_, prefetch_observer_, startup_urls, referral_list);
}

void IOThread::ChangedToOnTheRecordOnIOThread() {
  DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO));

  if (predictor_) {
    // Destroy all evidence of our OTR session.
    predictor_->Predictor::DiscardAllResults();
  }

  // Clear the host cache to avoid showing entries from the OTR session
  // in about:net-internals.
  if (globals_->host_resolver->GetAsHostResolverImpl()) {
    net::HostCache* host_cache =
        globals_->host_resolver.get()->GetAsHostResolverImpl()->cache();
    if (host_cache)
      host_cache->clear();
  }
  // Clear all of the passively logged data.
  // TODO(eroman): this is a bit heavy handed, really all we need to do is
  //               clear the data pertaining to off the record context.
  globals_->net_log->passive_collector()->Clear();
}
<commit_msg>Fix a crash when using negotiate HTTP auth.<commit_after>// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/io_thread.h"
#include "base/command_line.h"
#include "base/leak_tracker.h"
#include "base/logging.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chrome_thread.h"
#include "chrome/browser/gpu_process_host.h"
#include "chrome/browser/net/chrome_net_log.h"
#include "chrome/browser/net/predictor_api.h"
#include "chrome/browser/net/passive_log_collector.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/net/url_fetcher.h"
#include "net/base/mapped_host_resolver.h"
#include "net/base/host_cache.h"
#include "net/base/host_resolver.h"
#include "net/base/host_resolver_impl.h"
#include "net/base/net_util.h"
#include "net/http/http_auth_filter.h"
#include "net/http/http_auth_handler_factory.h"
#include "net/http/http_auth_handler_negotiate.h"

namespace {

net::HostResolver* CreateGlobalHostResolver() {
  const CommandLine& command_line = *CommandLine::ForCurrentProcess();
  net::HostResolver* global_host_resolver = net::CreateSystemHostResolver();

  // Determine if we should disable IPv6 support.
  if (!command_line.HasSwitch(switches::kEnableIPv6)) {
    if (command_line.HasSwitch(switches::kDisableIPv6)) {
      global_host_resolver->SetDefaultAddressFamily(net::ADDRESS_FAMILY_IPV4);
    } else {
      net::HostResolverImpl* host_resolver_impl =
          global_host_resolver->GetAsHostResolverImpl();
      if (host_resolver_impl != NULL) {
        // (optionally) Use probe to decide if support is warranted.
        bool use_ipv6_probe = true;

#if defined(OS_WIN)
        // Measure impact of probing to allow IPv6.
        // Some users report confused OS handling of IPv6, leading to large
        // latency.  If we can show that IPv6 is not supported, then disabliing
        // it will work around such problems. This is the test of the probe.
        const FieldTrial::Probability kDivisor = 100;
        const FieldTrial::Probability kProbability = 50;  // 50% probability.
        FieldTrial* trial = new FieldTrial("IPv6_Probe", kDivisor);
        int skip_group = trial->AppendGroup("_IPv6_probe_skipped",
                                            kProbability);
        trial->AppendGroup("_IPv6_probe_done",
                           FieldTrial::kAllRemainingProbability);
        use_ipv6_probe = (trial->group() != skip_group);
#endif

        if (use_ipv6_probe)
          host_resolver_impl->ProbeIPv6Support();
      }
    }
  }

  // If hostname remappings were specified on the command-line, layer these
  // rules on top of the real host resolver. This allows forwarding all requests
  // through a designated test server.
  if (!command_line.HasSwitch(switches::kHostResolverRules))
    return global_host_resolver;

  net::MappedHostResolver* remapped_resolver =
      new net::MappedHostResolver(global_host_resolver);
  remapped_resolver->SetRulesFromString(
      command_line.GetSwitchValueASCII(switches::kHostResolverRules));
  return remapped_resolver;
}

class LoggingNetworkChangeObserver
    : public net::NetworkChangeNotifier::Observer {
 public:
  // |net_log| must remain valid throughout our lifetime.
  explicit LoggingNetworkChangeObserver(net::NetLog* net_log)
      : net_log_(net_log) {
    net::NetworkChangeNotifier::AddObserver(this);
  }

  ~LoggingNetworkChangeObserver() {
    net::NetworkChangeNotifier::RemoveObserver(this);
  }

  virtual void OnIPAddressChanged() {
    LOG(INFO) << "Observed a change to the network IP addresses";

    net::NetLog::Source global_source;

    // TODO(eroman): We shouldn't need to assign an ID to this source, since
    //               conceptually it is the "global event stream". However
    //               currently the javascript does a grouping on source id, so
    //               the display will look weird if we don't give it one.
    global_source.id = net_log_->NextID();

    net_log_->AddEntry(net::NetLog::TYPE_NETWORK_IP_ADDRESSSES_CHANGED,
                       base::TimeTicks::Now(),
                       global_source,
                       net::NetLog::PHASE_NONE,
                       NULL);
  }

 private:
  net::NetLog* net_log_;
  DISALLOW_COPY_AND_ASSIGN(LoggingNetworkChangeObserver);
};

}  // namespace

// The IOThread object must outlive any tasks posted to the IO thread before the
// Quit task.
DISABLE_RUNNABLE_METHOD_REFCOUNT(IOThread);

IOThread::IOThread()
    : BrowserProcessSubThread(ChromeThread::IO),
      globals_(NULL),
      speculative_interceptor_(NULL),
      prefetch_observer_(NULL),
      predictor_(NULL) {}

IOThread::~IOThread() {
  // We cannot rely on our base class to stop the thread since we want our
  // CleanUp function to run.
  Stop();
  DCHECK(!globals_);
}

IOThread::Globals* IOThread::globals() {
  DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO));
  return globals_;
}

void IOThread::InitNetworkPredictor(
    bool prefetching_enabled,
    base::TimeDelta max_dns_queue_delay,
    size_t max_concurrent,
    const chrome_common_net::UrlList& startup_urls,
    ListValue* referral_list,
    bool preconnect_enabled) {
  DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
  message_loop()->PostTask(
      FROM_HERE,
      NewRunnableMethod(
          this,
          &IOThread::InitNetworkPredictorOnIOThread,
          prefetching_enabled, max_dns_queue_delay, max_concurrent,
          startup_urls, referral_list, preconnect_enabled));
}

void IOThread::ChangedToOnTheRecord() {
  DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
  message_loop()->PostTask(
      FROM_HERE,
      NewRunnableMethod(
          this,
          &IOThread::ChangedToOnTheRecordOnIOThread));
}

void IOThread::Init() {
  BrowserProcessSubThread::Init();

  DCHECK(!globals_);
  globals_ = new Globals;

  globals_->net_log.reset(new ChromeNetLog());

  // Add an observer that will emit network change events to the ChromeNetLog.
  // Assuming NetworkChangeNotifier dispatches in FIFO order, we should be
  // logging the network change before other IO thread consumers respond to it.
  network_change_observer_.reset(
      new LoggingNetworkChangeObserver(globals_->net_log.get()));

  globals_->host_resolver = CreateGlobalHostResolver();
  globals_->http_auth_handler_factory.reset(CreateDefaultAuthHandlerFactory(
      globals_->host_resolver));
}

void IOThread::CleanUp() {
  // This must be reset before the ChromeNetLog is destroyed.
  network_change_observer_.reset();

  // If any child processes are still running, terminate them and
  // and delete the BrowserChildProcessHost instances to release whatever
  // IO thread only resources they are referencing.
  BrowserChildProcessHost::TerminateAll();

  // Not initialized in Init().  May not be initialized.
  if (predictor_) {
    predictor_->Shutdown();

    // TODO(willchan): Stop reference counting Predictor.  It's owned by
    // IOThread now.
    predictor_->Release();
    predictor_ = NULL;
    chrome_browser_net::FreePredictorResources();
  }

  // Deletion will unregister this interceptor.
  delete speculative_interceptor_;
  speculative_interceptor_ = NULL;

  // Not initialized in Init().  May not be initialized.
  if (prefetch_observer_) {
    globals_->host_resolver->RemoveObserver(prefetch_observer_);
    delete prefetch_observer_;
    prefetch_observer_ = NULL;
  }

  // TODO(eroman): hack for http://crbug.com/15513
  if (globals_->host_resolver->GetAsHostResolverImpl()) {
    globals_->host_resolver.get()->GetAsHostResolverImpl()->Shutdown();
  }

  // We will delete the NetLog as part of CleanUpAfterMessageLoopDestruction()
  // in case any of the message loop destruction observers try to access it.
  deferred_net_log_to_delete_.reset(globals_->net_log.release());

  delete globals_;
  globals_ = NULL;

  // URLRequest instances must NOT outlive the IO thread.
  base::LeakTracker<URLRequest>::CheckForLeaks();

  BrowserProcessSubThread::CleanUp();
}

void IOThread::CleanUpAfterMessageLoopDestruction() {
  // TODO(eroman): get rid of this special case for 39723. If we could instead
  // have a method that runs after the message loop destruction obsevers have
  // run, but before the message loop itself is destroyed, we could safely
  // combine the two cleanups.
  deferred_net_log_to_delete_.reset();
  BrowserProcessSubThread::CleanUpAfterMessageLoopDestruction();
}

net::HttpAuthHandlerFactory* IOThread::CreateDefaultAuthHandlerFactory(
    net::HostResolver* resolver) {
  net::HttpAuthFilterWhitelist* auth_filter = NULL;

  // Get the whitelist information from the command line, create an
  // HttpAuthFilterWhitelist, and attach it to the HttpAuthHandlerFactory.
  const CommandLine& command_line = *CommandLine::ForCurrentProcess();

  if (command_line.HasSwitch(switches::kAuthServerWhitelist)) {
    std::string auth_server_whitelist =
        command_line.GetSwitchValueASCII(switches::kAuthServerWhitelist);

    // Create a whitelist filter.
    auth_filter = new net::HttpAuthFilterWhitelist();
    auth_filter->SetWhitelist(auth_server_whitelist);
  }

  // Set the flag that enables or disables the Negotiate auth handler.
#if defined(OS_WIN)
  static const bool kNegotiateAuthEnabledDefault = true;
#else
  static const bool kNegotiateAuthEnabledDefault = false;
#endif
  bool negotiate_auth_enabled = kNegotiateAuthEnabledDefault;
  if (command_line.HasSwitch(switches::kExperimentalEnableNegotiateAuth)) {
    std::string enable_negotiate_auth = command_line.GetSwitchValueASCII(
        switches::kExperimentalEnableNegotiateAuth);
    // Enabled if no value, or value is 'true'.  Disabled otherwise.
    negotiate_auth_enabled =
        enable_negotiate_auth.empty() ||
        (StringToLowerASCII(enable_negotiate_auth) == "true");
  }

  net::HttpAuthHandlerRegistryFactory* registry_factory =
      net::HttpAuthHandlerFactory::CreateDefault();

  globals_->url_security_manager.reset(
      net::URLSecurityManager::Create(auth_filter));

  // Add the security manager to the auth factories that need it.
  registry_factory->SetURLSecurityManager("ntlm",
                                          globals_->url_security_manager.get());
  registry_factory->SetURLSecurityManager("negotiate",
                                          globals_->url_security_manager.get());
  if (negotiate_auth_enabled) {
    // Configure the Negotiate settings for the Kerberos SPN.
    // TODO(cbentzel): Read the related IE registry settings on Windows builds.
    // TODO(cbentzel): Ugly use of static_cast here.
    net::HttpAuthHandlerNegotiate::Factory* negotiate_factory =
        static_cast<net::HttpAuthHandlerNegotiate::Factory*>(
            registry_factory->GetSchemeFactory("negotiate"));
    DCHECK(negotiate_factory);
    negotiate_factory->set_host_resolver(resolver);
    if (command_line.HasSwitch(switches::kDisableAuthNegotiateCnameLookup))
      negotiate_factory->set_disable_cname_lookup(true);
    if (command_line.HasSwitch(switches::kEnableAuthNegotiatePort))
      negotiate_factory->set_use_port(true);
  } else {
    // Disable the Negotiate authentication handler.
    registry_factory->RegisterSchemeFactory("negotiate", NULL);
  }
  return registry_factory;
}

void IOThread::InitNetworkPredictorOnIOThread(
    bool prefetching_enabled,
    base::TimeDelta max_dns_queue_delay,
    size_t max_concurrent,
    const chrome_common_net::UrlList& startup_urls,
    ListValue* referral_list,
    bool preconnect_enabled) {
  DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO));
  CHECK(!predictor_);

  chrome_browser_net::EnablePredictor(prefetching_enabled);

  predictor_ = new chrome_browser_net::Predictor(
      globals_->host_resolver,
      max_dns_queue_delay,
      max_concurrent,
      preconnect_enabled);
  predictor_->AddRef();

  // TODO(jar): Until connection notification and DNS observation handling are
  // properly combined into a learning model, we'll only use one observation
  // mechanism or the other.
  if (preconnect_enabled) {
    DCHECK(!speculative_interceptor_);
    speculative_interceptor_ = new chrome_browser_net::ConnectInterceptor;
  } else {
    DCHECK(!prefetch_observer_);
    prefetch_observer_ = chrome_browser_net::CreateResolverObserver();
    globals_->host_resolver->AddObserver(prefetch_observer_);
  }

  FinalizePredictorInitialization(
      predictor_, prefetch_observer_, startup_urls, referral_list);
}

void IOThread::ChangedToOnTheRecordOnIOThread() {
  DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO));

  if (predictor_) {
    // Destroy all evidence of our OTR session.
    predictor_->Predictor::DiscardAllResults();
  }

  // Clear the host cache to avoid showing entries from the OTR session
  // in about:net-internals.
  if (globals_->host_resolver->GetAsHostResolverImpl()) {
    net::HostCache* host_cache =
        globals_->host_resolver.get()->GetAsHostResolverImpl()->cache();
    if (host_cache)
      host_cache->clear();
  }
  // Clear all of the passively logged data.
  // TODO(eroman): this is a bit heavy handed, really all we need to do is
  //               clear the data pertaining to off the record context.
  globals_->net_log->passive_collector()->Clear();
}
<|endoftext|>


<commit_before>/*
 * Copyright(c) Sophist Solutions, Inc. 1990-2020.  All rights reserved
 */
#ifndef _Stroika_Foundation_Execution_WaitableEvent_inl_
#define _Stroika_Foundation_Execution_WaitableEvent_inl_ 1

/*
 ********************************************************************************
 ***************************** Implementation Details ***************************
 ********************************************************************************
 */
#include "Common.h"
#include "Finally.h"
#include "ModuleInit.h"
#include "SpinLock.h"
#include "Thread.h"

#if qExecution_WaitableEvent_SupportWaitForMultipleObjects
namespace Stroika::Foundation::Execution::Private_ {
    struct WaitableEvent_ModuleInit_ {
        SpinLock fExtraWaitableEventsMutex_;
    };
}
namespace {
    extern Stroika::Foundation::Execution::ModuleInitializer<Stroika::Foundation::Execution::Private_::WaitableEvent_ModuleInit_> _Stroika_Foundation_Execution_Private_WaitableEvent_ModuleInit_; // this object constructed for the CTOR/DTOR per-module side-effects
}
#endif

namespace Stroika::Foundation::Execution {

    /*
     ********************************************************************************
     ********************** Execution::WaitableEvent::WE_ ***************************
     ********************************************************************************
     */
    inline WaitableEvent::WE_::WE_ (ResetType resetType)
        : fResetType{resetType}
    {
    }
    inline void WaitableEvent::WE_::Reset ()
    {
        typename ConditionVariable<>::QuickLockType critSection{fConditionVariable.fMutex};
        fTriggered = false;
    }
    inline bool WaitableEvent::WE_::PeekIsSet () const noexcept
    {
        typename ConditionVariable<>::QuickLockType critSection{fConditionVariable.fMutex};
        return fTriggered;
    }
    inline void WaitableEvent::WE_::Set ()
    {
        /**
         * NOTIFY the condition variable (notify_all), but unlock first due to:                  *
         *      http://en.cppreference.com/w/cpp/thread/condition_variable/notify_all
         *
         *          The notifying thread does not need to hold the lock on the same mutex as the
         *          one held by the waiting thread(s); in fact doing so is a pessimization, since
         *          the notified thread would immediately block again, waiting for the notifying
         *          thread to release the lock.
         *
         *  \note https://stroika.atlassian.net/browse/STK-620 - helgrind workaround needed because of this unlock, but the unlock is still correct
         *
         */
        {
            typename ConditionVariable<>::QuickLockType critSection{fConditionVariable.fMutex};
            fTriggered = true;
        }
        fConditionVariable.notify_all ();
    }

    /*
     ********************************************************************************
     *************************** Execution::WaitableEvent ***************************
     ********************************************************************************
     */
    inline WaitableEvent::WaitableEvent (ResetType resetType)
        : fWE_{resetType}
    {
    }
    inline void WaitableEvent::Reset ()
    {
        //Debug::TraceContextBumper ctx ("WaitableEvent::Reset");
        fWE_.Reset ();
    }
    inline bool WaitableEvent::PeekIsSet () const noexcept
    {
        return fWE_.PeekIsSet ();
    }
    inline void WaitableEvent::Wait (Time::DurationSecondsType timeout)
    {
        fWE_.WaitUntil (timeout + Time::GetTickCount ());
    }
    inline bool WaitableEvent::WaitQuietly (Time::DurationSecondsType timeout)
    {
        return fWE_.WaitUntilQuietly (timeout + Time::GetTickCount ());
    }
    inline void WaitableEvent::WaitUntil (Time::DurationSecondsType timeoutAt)
    {
        fWE_.WaitUntil (timeoutAt);
    }
    inline bool WaitableEvent::WaitUntilQuietly (Time::DurationSecondsType timeoutAt)
    {
        return fWE_.WaitUntilQuietly (timeoutAt);
    }
#if qExecution_WaitableEvent_SupportWaitForMultipleObjects
    template <typename CONTAINER_OF_WAITABLE_EVENTS, typename SET_OF_WAITABLE_EVENTS_RESULT>
    inline SET_OF_WAITABLE_EVENTS_RESULT WaitableEvent::WaitForAny (CONTAINER_OF_WAITABLE_EVENTS waitableEvents, Time::DurationSecondsType timeout)
    {
        return WaitForAnyUntil (waitableEvents, timeout + Time::GetTickCount ());
    }
    template <typename ITERATOR_OF_WAITABLE_EVENTS, typename SET_OF_WAITABLE_EVENTS_RESULT>
    inline SET_OF_WAITABLE_EVENTS_RESULT WaitableEvent::WaitForAny (ITERATOR_OF_WAITABLE_EVENTS waitableEventsStart, ITERATOR_OF_WAITABLE_EVENTS waitableEventsEnd, Time::DurationSecondsType timeout)
    {
        return WaitForAnyUntil (waitableEventsStart, waitableEventsEnd, timeout + Time::GetTickCount ());
    }
    template <typename CONTAINER_OF_WAITABLE_EVENTS, typename SET_OF_WAITABLE_EVENTS_RESULT>
    inline SET_OF_WAITABLE_EVENTS_RESULT WaitableEvent::WaitForAnyUntil (CONTAINER_OF_WAITABLE_EVENTS waitableEvents, Time::DurationSecondsType timeoutAt)
    {
        return WaitForAnyUntil (begin (waitableEvents), end (waitableEvents), timeoutAt);
    }
    template <typename ITERATOR_OF_WAITABLE_EVENTS, typename SET_OF_WAITABLE_EVENTS_RESULT>
    SET_OF_WAITABLE_EVENTS_RESULT WaitableEvent::WaitForAnyUntil (ITERATOR_OF_WAITABLE_EVENTS waitableEventsStart, ITERATOR_OF_WAITABLE_EVENTS waitableEventsEnd, Time::DurationSecondsType timeoutAt)
    {
        SET_OF_WAITABLE_EVENTS_RESULT result;
        /*
            *  Create another WE as shared.
            *  Stick it onto the list for each waitablevent
            *  Wait on 'new private fake' enevt (and if it succeeeds, then check orig events and return right now)
            *  Either way - undo additions
            *
            *  <<< @todo DOCUMENT AND EXPLAIN MUTEX >>>
            */
        shared_ptr<WE_>         we      = make_shared<WE_> (eAutoReset);
        [[maybe_unused]] auto&& cleanup = Finally (
            [we, waitableEventsStart, waitableEventsEnd] () noexcept {
                Thread::SuppressInterruptionInContext suppressThreadInterrupts;
                [[maybe_unused]] auto&&               critSec = lock_guard{_Stroika_Foundation_Execution_Private_WaitableEvent_ModuleInit_.Actual ().fExtraWaitableEventsMutex_};
                for (ITERATOR_OF_WAITABLE_EVENTS i = waitableEventsStart; i != waitableEventsEnd; ++i) {
                    (*i)->fExtraWaitableEvents_.remove (we);
                }
            });
        {
            [[maybe_unused]] auto&& critSec = lock_guard{_Stroika_Foundation_Execution_Private_WaitableEvent_ModuleInit_.Actual ().fExtraWaitableEventsMutex_};
            for (ITERATOR_OF_WAITABLE_EVENTS i = waitableEventsStart; i != waitableEventsEnd; ++i) {
                (*i)->fExtraWaitableEvents_.push_front (we);
            }
        }
        while (result.empty ()) {
            we->WaitUntil (timeoutAt);
            for (ITERATOR_OF_WAITABLE_EVENTS i = waitableEventsStart; i != waitableEventsEnd; ++i) {
                WaitableEvent* we2Test = *i;
                if (we2Test->fWE_.PeekIsSet ()) {
                    if (we2Test->fWE_.fResetType == eAutoReset) {
                        we2Test->fWE_.Reset ();
                    }
                    result.insert (we2Test);
                }
            }
        }
        return result;
    }
    template <typename CONTAINER_OF_WAITABLE_EVENTS>
    inline void WaitableEvent::WaitForAll (CONTAINER_OF_WAITABLE_EVENTS waitableEvents, Time::DurationSecondsType timeout)
    {
        WaitForAllUntil (waitableEvents, timeout + Time::GetTickCount ());
    }
    template <typename ITERATOR_OF_WAITABLE_EVENTS>
    inline void WaitableEvent::WaitForAll (ITERATOR_OF_WAITABLE_EVENTS waitableEventsStart, ITERATOR_OF_WAITABLE_EVENTS waitableEventsEnd, Time::DurationSecondsType timeout)
    {
        WaitForAllUntil (waitableEventsStart, waitableEventsEnd, timeout + Time::GetTickCount ());
    }
    template <typename CONTAINER_OF_WAITABLE_EVENTS>
    inline void WaitableEvent::WaitForAllUntil (CONTAINER_OF_WAITABLE_EVENTS waitableEvents, Time::DurationSecondsType timeoutAt)
    {
        WaitForAllUntil (begin (waitableEvents), end (waitableEvents), timeoutAt);
    }
    template <typename ITERATOR_OF_WAITABLE_EVENTS>
    void WaitableEvent::WaitForAllUntil (ITERATOR_OF_WAITABLE_EVENTS waitableEventsStart, ITERATOR_OF_WAITABLE_EVENTS waitableEventsEnd, Time::DurationSecondsType timeoutAt)
    {
        /*
         *  Create another WE as shared.
         *  Stick it onto the list for each waitablevent
         *  Wait on 'new private fake' enevt (and if it succeeeds, then check orig events and return right now)
         *  Either way - undo additions
         *
         *  <<< @todo DOCUMENT AND EXPLAIN MUTEX >>>
         */
        shared_ptr<WE_>         we      = make_shared<WE_> (eAutoReset);
        [[maybe_unused]] auto&& cleanup = Finally (
            [we, waitableEventsStart, waitableEventsEnd] () noexcept {
                Thread::SuppressInterruptionInContext suppressThreadInterrupts;
                [[maybe_unused]] auto&&               critSec = lock_guard{_Stroika_Foundation_Execution_Private_WaitableEvent_ModuleInit_.Actual ().fExtraWaitableEventsMutex_};
                for (ITERATOR_OF_WAITABLE_EVENTS i = waitableEventsStart; i != waitableEventsEnd; ++i) {
                    (*i)->fExtraWaitableEvents_.remove (we);
                }
            });
        {
            [[maybe_unused]] auto&& critSec = lock_guard{_Stroika_Foundation_Execution_Private_WaitableEvent_ModuleInit_.Actual ().fExtraWaitableEventsMutex_};
            for (ITERATOR_OF_WAITABLE_EVENTS i = waitableEventsStart; i != waitableEventsEnd; ++i) {
                (*i)->fExtraWaitableEvents_.push_front (we);
            }
        }
        while (true) {
            we->WaitUntil (timeoutAt);
            bool anyStillWaiting = false;
            for (ITERATOR_OF_WAITABLE_EVENTS i = waitableEventsStart; i != waitableEventsEnd; ++i) {
                WaitableEvent* we2Test = *i;
                RequireNotNull (we2Test);
                if (not we2Test->fWE_.PeekIsSet ()) {
                    anyStillWaiting = true;
                }
            }
            if (anyStillWaiting) {
                continue;
            }
            else {
                for (ITERATOR_OF_WAITABLE_EVENTS i = waitableEventsStart; i != waitableEventsEnd; ++i) {
                    WaitableEvent* we2Test = *i;
                    RequireNotNull (we2Test);
                    if (we2Test->fWE_.fResetType == eAutoReset) {
                        Assert (we2Test->fWE_.PeekIsSet ()); // we probably need some mechanism to assure this is true but we currently have no enforcement of this!
                        we2Test->fWE_.Reset ();
                    }
                }
                return; // success
            }
        }
    }
#endif
}

#if qExecution_WaitableEvent_SupportWaitForMultipleObjects
namespace {
    Stroika::Foundation::Execution::ModuleInitializer<Stroika::Foundation::Execution::Private_::WaitableEvent_ModuleInit_> _Stroika_Foundation_Execution_Private_WaitableEvent_ModuleInit_; // this object constructed for the CTOR/DTOR per-module side-effects
}
#endif

#endif /*_Stroika_Foundation_Execution_WaitableEvent_inl_*/
<commit_msg>cosmetic<commit_after>/*
 * Copyright(c) Sophist Solutions, Inc. 1990-2020.  All rights reserved
 */
#ifndef _Stroika_Foundation_Execution_WaitableEvent_inl_
#define _Stroika_Foundation_Execution_WaitableEvent_inl_ 1

/*
 ********************************************************************************
 ***************************** Implementation Details ***************************
 ********************************************************************************
 */
#include "Common.h"
#include "Finally.h"
#include "ModuleInit.h"
#include "SpinLock.h"
#include "Thread.h"

#if qExecution_WaitableEvent_SupportWaitForMultipleObjects
namespace Stroika::Foundation::Execution::Private_ {
    struct WaitableEvent_ModuleInit_ {
        SpinLock fExtraWaitableEventsMutex_;
    };
}
namespace {
    extern Stroika::Foundation::Execution::ModuleInitializer<Stroika::Foundation::Execution::Private_::WaitableEvent_ModuleInit_> _Stroika_Foundation_Execution_Private_WaitableEvent_ModuleInit_; // this object constructed for the CTOR/DTOR per-module side-effects
}
#endif

namespace Stroika::Foundation::Execution {

    /*
     ********************************************************************************
     ********************** Execution::WaitableEvent::WE_ ***************************
     ********************************************************************************
     */
    inline WaitableEvent::WE_::WE_ (ResetType resetType)
        : fResetType{resetType}
    {
    }
    inline void WaitableEvent::WE_::Reset ()
    {
        typename ConditionVariable<>::QuickLockType critSection{fConditionVariable.fMutex};
        fTriggered = false;
    }
    inline bool WaitableEvent::WE_::PeekIsSet () const noexcept
    {
        typename ConditionVariable<>::QuickLockType critSection{fConditionVariable.fMutex};
        return fTriggered;
    }
    inline void WaitableEvent::WE_::Set ()
    {
        /**
         * NOTIFY the condition variable (notify_all), but unlock first due to:                  *
         *      http://en.cppreference.com/w/cpp/thread/condition_variable/notify_all
         *
         *          The notifying thread does not need to hold the lock on the same mutex as the
         *          one held by the waiting thread(s); in fact doing so is a pessimization, since
         *          the notified thread would immediately block again, waiting for the notifying
         *          thread to release the lock.
         *
         *  \note https://stroika.atlassian.net/browse/STK-620 - helgrind workaround needed because of this unlock, but the unlock is still correct
         *
         */
        {
            typename ConditionVariable<>::QuickLockType critSection{fConditionVariable.fMutex};
            fTriggered = true;
        }
        fConditionVariable.notify_all ();
    }

    /*
     ********************************************************************************
     *************************** Execution::WaitableEvent ***************************
     ********************************************************************************
     */
    inline WaitableEvent::WaitableEvent (ResetType resetType)
        : fWE_{resetType}
    {
    }
    inline void WaitableEvent::Reset ()
    {
        //Debug::TraceContextBumper ctx ("WaitableEvent::Reset");
        fWE_.Reset ();
    }
    inline bool WaitableEvent::PeekIsSet () const noexcept
    {
        return fWE_.PeekIsSet ();
    }
    inline void WaitableEvent::Wait (Time::DurationSecondsType timeout)
    {
        fWE_.WaitUntil (timeout + Time::GetTickCount ());
    }
    inline bool WaitableEvent::WaitQuietly (Time::DurationSecondsType timeout)
    {
        return fWE_.WaitUntilQuietly (timeout + Time::GetTickCount ());
    }
    inline void WaitableEvent::WaitUntil (Time::DurationSecondsType timeoutAt)
    {
        fWE_.WaitUntil (timeoutAt);
    }
    inline bool WaitableEvent::WaitUntilQuietly (Time::DurationSecondsType timeoutAt)
    {
        return fWE_.WaitUntilQuietly (timeoutAt);
    }
#if qExecution_WaitableEvent_SupportWaitForMultipleObjects
    template <typename CONTAINER_OF_WAITABLE_EVENTS, typename SET_OF_WAITABLE_EVENTS_RESULT>
    inline SET_OF_WAITABLE_EVENTS_RESULT WaitableEvent::WaitForAny (CONTAINER_OF_WAITABLE_EVENTS waitableEvents, Time::DurationSecondsType timeout)
    {
        return WaitForAnyUntil (waitableEvents, timeout + Time::GetTickCount ());
    }
    template <typename ITERATOR_OF_WAITABLE_EVENTS, typename SET_OF_WAITABLE_EVENTS_RESULT>
    inline SET_OF_WAITABLE_EVENTS_RESULT WaitableEvent::WaitForAny (ITERATOR_OF_WAITABLE_EVENTS waitableEventsStart, ITERATOR_OF_WAITABLE_EVENTS waitableEventsEnd, Time::DurationSecondsType timeout)
    {
        return WaitForAnyUntil (waitableEventsStart, waitableEventsEnd, timeout + Time::GetTickCount ());
    }
    template <typename CONTAINER_OF_WAITABLE_EVENTS, typename SET_OF_WAITABLE_EVENTS_RESULT>
    inline SET_OF_WAITABLE_EVENTS_RESULT WaitableEvent::WaitForAnyUntil (CONTAINER_OF_WAITABLE_EVENTS waitableEvents, Time::DurationSecondsType timeoutAt)
    {
        return WaitForAnyUntil (begin (waitableEvents), end (waitableEvents), timeoutAt);
    }
    template <typename ITERATOR_OF_WAITABLE_EVENTS, typename SET_OF_WAITABLE_EVENTS_RESULT>
    SET_OF_WAITABLE_EVENTS_RESULT WaitableEvent::WaitForAnyUntil (ITERATOR_OF_WAITABLE_EVENTS waitableEventsStart, ITERATOR_OF_WAITABLE_EVENTS waitableEventsEnd, Time::DurationSecondsType timeoutAt)
    {
        SET_OF_WAITABLE_EVENTS_RESULT result;
        /*
         *  Create another WE as shared.
         *  Stick it onto the list for each waitablevent
         *  Wait on 'new private fake' enevt (and if it succeeeds, then check orig events and return right now)
         *  Either way - undo additions
         *
         *  <<< @todo DOCUMENT AND EXPLAIN MUTEX >>>
         */
        shared_ptr<WE_>         we      = make_shared<WE_> (eAutoReset);
        [[maybe_unused]] auto&& cleanup = Finally (
            [we, waitableEventsStart, waitableEventsEnd] () noexcept {
                Thread::SuppressInterruptionInContext suppressThreadInterrupts;
                [[maybe_unused]] auto&&               critSec = lock_guard{_Stroika_Foundation_Execution_Private_WaitableEvent_ModuleInit_.Actual ().fExtraWaitableEventsMutex_};
                for (ITERATOR_OF_WAITABLE_EVENTS i = waitableEventsStart; i != waitableEventsEnd; ++i) {
                    (*i)->fExtraWaitableEvents_.remove (we);
                }
            });
        {
            [[maybe_unused]] auto&& critSec = lock_guard{_Stroika_Foundation_Execution_Private_WaitableEvent_ModuleInit_.Actual ().fExtraWaitableEventsMutex_};
            for (ITERATOR_OF_WAITABLE_EVENTS i = waitableEventsStart; i != waitableEventsEnd; ++i) {
                (*i)->fExtraWaitableEvents_.push_front (we);
            }
        }
        while (result.empty ()) {
            we->WaitUntil (timeoutAt);
            for (ITERATOR_OF_WAITABLE_EVENTS i = waitableEventsStart; i != waitableEventsEnd; ++i) {
                WaitableEvent* we2Test = *i;
                if (we2Test->fWE_.PeekIsSet ()) {
                    if (we2Test->fWE_.fResetType == eAutoReset) {
                        we2Test->fWE_.Reset ();
                    }
                    result.insert (we2Test);
                }
            }
        }
        return result;
    }
    template <typename CONTAINER_OF_WAITABLE_EVENTS>
    inline void WaitableEvent::WaitForAll (CONTAINER_OF_WAITABLE_EVENTS waitableEvents, Time::DurationSecondsType timeout)
    {
        WaitForAllUntil (waitableEvents, timeout + Time::GetTickCount ());
    }
    template <typename ITERATOR_OF_WAITABLE_EVENTS>
    inline void WaitableEvent::WaitForAll (ITERATOR_OF_WAITABLE_EVENTS waitableEventsStart, ITERATOR_OF_WAITABLE_EVENTS waitableEventsEnd, Time::DurationSecondsType timeout)
    {
        WaitForAllUntil (waitableEventsStart, waitableEventsEnd, timeout + Time::GetTickCount ());
    }
    template <typename CONTAINER_OF_WAITABLE_EVENTS>
    inline void WaitableEvent::WaitForAllUntil (CONTAINER_OF_WAITABLE_EVENTS waitableEvents, Time::DurationSecondsType timeoutAt)
    {
        WaitForAllUntil (begin (waitableEvents), end (waitableEvents), timeoutAt);
    }
    template <typename ITERATOR_OF_WAITABLE_EVENTS>
    void WaitableEvent::WaitForAllUntil (ITERATOR_OF_WAITABLE_EVENTS waitableEventsStart, ITERATOR_OF_WAITABLE_EVENTS waitableEventsEnd, Time::DurationSecondsType timeoutAt)
    {
        /*
         *  Create another WE as shared.
         *  Stick it onto the list for each waitablevent
         *  Wait on 'new private fake' enevt (and if it succeeeds, then check orig events and return right now)
         *  Either way - undo additions
         *
         *  <<< @todo DOCUMENT AND EXPLAIN MUTEX >>>
         */
        shared_ptr<WE_>         we      = make_shared<WE_> (eAutoReset);
        [[maybe_unused]] auto&& cleanup = Finally (
            [we, waitableEventsStart, waitableEventsEnd] () noexcept {
                Thread::SuppressInterruptionInContext suppressThreadInterrupts;
                [[maybe_unused]] auto&&               critSec = lock_guard{_Stroika_Foundation_Execution_Private_WaitableEvent_ModuleInit_.Actual ().fExtraWaitableEventsMutex_};
                for (ITERATOR_OF_WAITABLE_EVENTS i = waitableEventsStart; i != waitableEventsEnd; ++i) {
                    (*i)->fExtraWaitableEvents_.remove (we);
                }
            });
        {
            [[maybe_unused]] auto&& critSec = lock_guard{_Stroika_Foundation_Execution_Private_WaitableEvent_ModuleInit_.Actual ().fExtraWaitableEventsMutex_};
            for (ITERATOR_OF_WAITABLE_EVENTS i = waitableEventsStart; i != waitableEventsEnd; ++i) {
                (*i)->fExtraWaitableEvents_.push_front (we);
            }
        }
        while (true) {
            we->WaitUntil (timeoutAt);
            bool anyStillWaiting = false;
            for (ITERATOR_OF_WAITABLE_EVENTS i = waitableEventsStart; i != waitableEventsEnd; ++i) {
                WaitableEvent* we2Test = *i;
                RequireNotNull (we2Test);
                if (not we2Test->fWE_.PeekIsSet ()) {
                    anyStillWaiting = true;
                }
            }
            if (anyStillWaiting) {
                continue;
            }
            else {
                for (ITERATOR_OF_WAITABLE_EVENTS i = waitableEventsStart; i != waitableEventsEnd; ++i) {
                    WaitableEvent* we2Test = *i;
                    RequireNotNull (we2Test);
                    if (we2Test->fWE_.fResetType == eAutoReset) {
                        Assert (we2Test->fWE_.PeekIsSet ()); // we probably need some mechanism to assure this is true but we currently have no enforcement of this!
                        we2Test->fWE_.Reset ();
                    }
                }
                return; // success
            }
        }
    }
#endif
}

#if qExecution_WaitableEvent_SupportWaitForMultipleObjects
namespace {
    Stroika::Foundation::Execution::ModuleInitializer<Stroika::Foundation::Execution::Private_::WaitableEvent_ModuleInit_> _Stroika_Foundation_Execution_Private_WaitableEvent_ModuleInit_; // this object constructed for the CTOR/DTOR per-module side-effects
}
#endif

#endif /*_Stroika_Foundation_Execution_WaitableEvent_inl_*/
<|endoftext|>


<commit_before>/* BEGIN_COMMON_COPYRIGHT_HEADER
 * (c)LGPL2+
 *
 * LXDE-Qt - a lightweight, Qt based, desktop toolset
 * http://lxde.org/
 *
 * Copyright: 2014 LXQt team
 * Authors:
 *   Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
 *
 * This program or library is free software; you can redistribute it
 * and/or modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.

 * You should have received a copy of the GNU Lesser General
 * Public License along with this library; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA
 *
 * END_COMMON_COPYRIGHT_HEADER */

#include "lxqtplatformtheme.h"
#include <QVariant>
#include <QDebug>

#include <QIcon>
#include <QStandardPaths>
#include <QApplication>
#include <QWidget>

#include <QMetaObject>
#include <QMetaProperty>
#include <QMetaEnum>
#include <QToolBar>
#include <QSettings>
#include <QTimer>
#include <QDir>
#include <QFileInfo>
#include <QFileSystemWatcher>
#include <QStyle>
#include "qiconloader_p.h"

LXQtPlatformTheme::LXQtPlatformTheme():
    settingsWatcher_(NULL)
{
    // When the plugin is loaded, it seems that the app is not yet running and
    // QThread environment is not completely set up. So creating filesystem watcher
    // does not work since it uses QSocketNotifier internally which can only be
    // created within QThread thread. So let's schedule a idle handler to initialize
    // the watcher in the main event loop.
    loadSettings();
    QTimer::singleShot(0, this, SLOT(initWatch()));
}

LXQtPlatformTheme::~LXQtPlatformTheme() {
    if(settingsWatcher_)
        delete settingsWatcher_;
}

void LXQtPlatformTheme::initWatch()
{
    settingsWatcher_ = new QFileSystemWatcher();
    settingsWatcher_->addPath(settingsFile_);
    connect(settingsWatcher_, &QFileSystemWatcher::fileChanged, this, &LXQtPlatformTheme::onSettingsChanged);
}

void LXQtPlatformTheme::loadSettings() {
    // QSettings is really handy. It tries to read from /etc/xdg/lxqt/lxqt.conf
    // as a fallback if a key is missing from the user config file ~/.config/lxqt/lxqt.conf.
    // So we can customize the default values in /etc/xdg/lxqt/lxqt.conf and does
    // not necessarily to hard code the default values here.
    QSettings settings(QSettings::UserScope, "lxqt", "lxqt");
    settingsFile_ = settings.fileName();

    // icon theme
    iconTheme_ = settings.value("icon_theme", "oxygen").toString();

    // read other widget related settings form LxQt settings.
    QByteArray tb_style = settings.value("tool_button_style").toByteArray();
    // convert toolbar style name to value
    QMetaEnum me = QToolBar::staticMetaObject.property(QToolBar::staticMetaObject.indexOfProperty("toolButtonStyle")).enumerator();
    int value = me.keyToValue(tb_style.constData());
    if(value == -1)
        toolButtonStyle_ = Qt::ToolButtonTextBesideIcon;
    else
        toolButtonStyle_ = static_cast<Qt::ToolButtonStyle>(value);

    // single click activation
    singleClickActivate_ = settings.value("single_click_activate").toBool();

    // load Qt settings
    settings.beginGroup(QLatin1String("Qt"));

    // widget style
    style_ = settings.value(QLatin1String("style"), QLatin1String("fusion")).toString();

    // SystemFont
    fontStr_ = settings.value(QLatin1String("font")).toString();
    if(!fontStr_.isEmpty()) {
        if(font_.fromString(fontStr_))
            QApplication::setFont(font_); // it seems that we need to do this manually.
    }

    // FixedFont
    fixedFontStr_ = settings.value(QLatin1String("fixedFont")).toString();
    if(!fixedFontStr_.isEmpty()) {
        fixedFont_.fromString(fixedFontStr_);
    }

    // mouse
    doubleClickInterval_ = settings.value(QLatin1String("doubleClickInterval"));
    wheelScrollLines_ = settings.value(QLatin1String("wheelScrollLines"));

    // keyboard
    cursorFlashTime_ = settings.value(QLatin1String("cursorFlashTime"));
    settings.endGroup();
}

// this is called whenever the config file is changed.
void LXQtPlatformTheme::onSettingsChanged() {
    // D*mn! yet another Qt 5.4 regression!!!
    // See the bug report: https://github.com/lxde/lxqt/issues/441
    // Since Qt 5.4, QSettings uses QSaveFile to save the config files.
    // https://github.com/qtproject/qtbase/commit/8d15068911d7c0ba05732e2796aaa7a90e34a6a1#diff-e691c0405f02f3478f4f50a27bdaecde
    // QSaveFile will save the content to a new temp file, and replace the old file later.
    // Hence the existing config file is not changed. Instead, it's deleted and then replaced.
    // This new behaviour unfortunately breaks QFileSystemWatcher.
    // After file deletion, we can no longer receive any new change notifications.
    // The most ridiculous thing is, QFileSystemWatcher does not provide a
    // way for us to know if a file is deleted. WT*?
    // Luckily, I found a workaround: If the file path no longer exists
    // in the watcher's files(), this file is deleted.
    bool file_deleted = !settingsWatcher_->files().contains(settingsFile_);
    if(file_deleted) // if our config file is already deleted, reinstall a new watcher
        settingsWatcher_->addPath(settingsFile_);

    // NOTE: in Qt4, Qt monitors the change of _QT_SETTINGS_TIMESTAMP root property and
    // reload Trolltech.conf when the value is changed. Then, it automatically
    // applies the new settings.
    // Unfortunately, this is no longer the case in Qt5. Yes, yet another regression.
    // We're free to provide our own platform plugin, but Qt5 does not
    // update the settings and repaint the UI. We need to do it ourselves
    // through dirty hacks and private Qt internal APIs.
    QString oldStyle = style_;
    QString oldIconTheme = iconTheme_;
    QString oldFont = fontStr_;
    QString oldFixedFont = fixedFontStr_;

    loadSettings(); // reload the config file

    if(style_ != oldStyle) // the widget style is changed
    {
        // ask Qt5 to apply the new style
        if (qobject_cast<QApplication *>(QCoreApplication::instance()))
            QApplication::setStyle(style_);
    }

    if(iconTheme_ != oldIconTheme) { // the icon theme is changed
        QIconLoader::instance()->updateSystemTheme(); // this is a private internal API of Qt5.
    }

    // if font is changed
    if(oldFont != fontStr_ || oldFixedFont != fixedFontStr_){
        // FIXME: to my knowledge there is no way to ask Qt to reload the fonts.
        // Should we call QApplication::setFont() to override the font?
        // This does not work with QSS, though.
        //
        // After reading the source code of Qt, I think the right method to call
        // here is QApplicationPrivate::setSystemFont(). However, this is an
        // internal API and should not be used. Let's call QApplication::setFont()
        // instead since it approximately does the same thing.
        // Internally, QApplication will emit QEvent::ApplicationFontChange so
        // all of the widgets will update their fonts.
        // FIXME: should we call the internal API: QApplicationPrivate::setFont() instead?
        // QGtkStyle does this internally.
        fixedFont_.fromString(fixedFontStr_); // FIXME: how to set this to the app?
        if(font_.fromString(fontStr_))
            QApplication::setFont(font_);
    }

    // emit a ThemeChange event to all widgets
    Q_FOREACH(QWidget* widget, QApplication::allWidgets()) {
        // Qt5 added a QEvent::ThemeChange event.
        QEvent event(QEvent::ThemeChange);
        QApplication::sendEvent(widget, &event);
    }
}

bool LXQtPlatformTheme::usePlatformNativeDialog(DialogType type) const {
    return false;
}

#if 0
QPlatformDialogHelper *LXQtPlatformTheme::createPlatformDialogHelper(DialogType type) const {
    return 0;
}
#endif

const QPalette *LXQtPlatformTheme::palette(Palette type) const {

    if (type == QPlatformTheme::SystemPalette)
        // the default constructor uses the default palette
        return new QPalette;
    return QPlatformTheme::palette(type);
}

const QFont *LXQtPlatformTheme::font(Font type) const {
    if(type == SystemFont && !fontStr_.isEmpty()) {
        // NOTE: for some reason, this is not called by Qt when program startup.
        // So we do QApplication::setFont() manually.
        return &font_;
    }
    else if(type == FixedFont && !fixedFontStr_.isEmpty()) {
        return &fixedFont_;
    }
    return QPlatformTheme::font(type);
}

QVariant LXQtPlatformTheme::themeHint(ThemeHint hint) const {
    switch (hint) {
    case CursorFlashTime:
        return cursorFlashTime_;
    case KeyboardInputInterval:
        break;
    case MouseDoubleClickInterval:
        return doubleClickInterval_;
    case StartDragDistance:
        break;
    case StartDragTime:
        break;
    case KeyboardAutoRepeatRate:
        break;
    case PasswordMaskDelay:
        break;
    case StartDragVelocity:
        break;
    case TextCursorWidth:
        break;
    case DropShadow:
        return QVariant(true);
    case MaximumScrollBarDragDistance:
        break;
    case ToolButtonStyle:
        return QVariant(toolButtonStyle_);
    case ToolBarIconSize:
        break;
    case ItemViewActivateItemOnSingleClick:
        return QVariant(singleClickActivate_);
    case SystemIconThemeName:
        return iconTheme_;
    case SystemIconFallbackThemeName:
        return "hicolor";
    case IconThemeSearchPaths:
        return xdgIconThemePaths();
    case StyleNames:
        return QStringList() << style_;
    case WindowAutoPlacement:
        break;
    case DialogButtonBoxLayout:
        break;
    case DialogButtonBoxButtonsHaveIcons:
        return QVariant(true);
    case UseFullScreenForPopupMenu:
        break;
    case KeyboardScheme:
        return QVariant(X11KeyboardScheme);
    case UiEffects:
        break;
    case SpellCheckUnderlineStyle:
        break;
    case TabAllWidgets:
        break;
    case IconPixmapSizes:
        break;
    case PasswordMaskCharacter:
        break;
    case DialogSnapToDefaultButton:
        break;
    case ContextMenuOnMouseRelease:
        break;
    case MousePressAndHoldInterval:
        break;
    case MouseDoubleClickDistance:
        break;
    default:
        break;
    }
    return QPlatformTheme::themeHint(hint);
}

// Helper to return the icon theme paths from XDG.
QStringList LXQtPlatformTheme::xdgIconThemePaths() const
{
    QStringList paths;
    QStringList xdgDirs;

    // Add home directory first in search path
    const QFileInfo homeIconDir(QDir::homePath() + QStringLiteral("/.icons"));
    if (homeIconDir.isDir())
        paths.prepend(homeIconDir.absoluteFilePath());

    QString xdgDataHome = QFile::decodeName(qgetenv("XDG_DATA_HOME"));
    if (xdgDataHome.isEmpty())
        xdgDataHome = QDir::homePath() + QLatin1String("/.local/share");
    xdgDirs.append(xdgDataHome);

    QString xdgDataDirs = QFile::decodeName(qgetenv("XDG_DATA_DIRS"));
    if (xdgDataDirs.isEmpty())
        xdgDataDirs = QLatin1String("/usr/local/share/:/usr/share/");
    xdgDirs.append(xdgDataDirs);

    foreach (const QString &s, xdgDirs) {
        foreach (const QString &xdgDir, s.split(QLatin1Char(':'))) {
            const QFileInfo xdgIconsDir(xdgDir + QStringLiteral("/icons"));
            if (xdgIconsDir.isDir())
                paths.append(xdgIconsDir.absoluteFilePath());
        }
    }
    return paths;
}
<commit_msg>xdgIconThemePaths: Remove duplicated paths<commit_after>/* BEGIN_COMMON_COPYRIGHT_HEADER
 * (c)LGPL2+
 *
 * LXDE-Qt - a lightweight, Qt based, desktop toolset
 * http://lxde.org/
 *
 * Copyright: 2014 LXQt team
 * Authors:
 *   Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
 *
 * This program or library is free software; you can redistribute it
 * and/or modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.

 * You should have received a copy of the GNU Lesser General
 * Public License along with this library; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA
 *
 * END_COMMON_COPYRIGHT_HEADER */

#include "lxqtplatformtheme.h"
#include <QVariant>
#include <QDebug>

#include <QIcon>
#include <QStandardPaths>
#include <QApplication>
#include <QWidget>

#include <QMetaObject>
#include <QMetaProperty>
#include <QMetaEnum>
#include <QToolBar>
#include <QSettings>
#include <QTimer>
#include <QDir>
#include <QFileInfo>
#include <QFileSystemWatcher>
#include <QStyle>
#include "qiconloader_p.h"

LXQtPlatformTheme::LXQtPlatformTheme():
    settingsWatcher_(NULL)
{
    // When the plugin is loaded, it seems that the app is not yet running and
    // QThread environment is not completely set up. So creating filesystem watcher
    // does not work since it uses QSocketNotifier internally which can only be
    // created within QThread thread. So let's schedule a idle handler to initialize
    // the watcher in the main event loop.
    loadSettings();
    QTimer::singleShot(0, this, SLOT(initWatch()));
}

LXQtPlatformTheme::~LXQtPlatformTheme() {
    if(settingsWatcher_)
        delete settingsWatcher_;
}

void LXQtPlatformTheme::initWatch()
{
    settingsWatcher_ = new QFileSystemWatcher();
    settingsWatcher_->addPath(settingsFile_);
    connect(settingsWatcher_, &QFileSystemWatcher::fileChanged, this, &LXQtPlatformTheme::onSettingsChanged);
}

void LXQtPlatformTheme::loadSettings() {
    // QSettings is really handy. It tries to read from /etc/xdg/lxqt/lxqt.conf
    // as a fallback if a key is missing from the user config file ~/.config/lxqt/lxqt.conf.
    // So we can customize the default values in /etc/xdg/lxqt/lxqt.conf and does
    // not necessarily to hard code the default values here.
    QSettings settings(QSettings::UserScope, "lxqt", "lxqt");
    settingsFile_ = settings.fileName();

    // icon theme
    iconTheme_ = settings.value("icon_theme", "oxygen").toString();

    // read other widget related settings form LxQt settings.
    QByteArray tb_style = settings.value("tool_button_style").toByteArray();
    // convert toolbar style name to value
    QMetaEnum me = QToolBar::staticMetaObject.property(QToolBar::staticMetaObject.indexOfProperty("toolButtonStyle")).enumerator();
    int value = me.keyToValue(tb_style.constData());
    if(value == -1)
        toolButtonStyle_ = Qt::ToolButtonTextBesideIcon;
    else
        toolButtonStyle_ = static_cast<Qt::ToolButtonStyle>(value);

    // single click activation
    singleClickActivate_ = settings.value("single_click_activate").toBool();

    // load Qt settings
    settings.beginGroup(QLatin1String("Qt"));

    // widget style
    style_ = settings.value(QLatin1String("style"), QLatin1String("fusion")).toString();

    // SystemFont
    fontStr_ = settings.value(QLatin1String("font")).toString();
    if(!fontStr_.isEmpty()) {
        if(font_.fromString(fontStr_))
            QApplication::setFont(font_); // it seems that we need to do this manually.
    }

    // FixedFont
    fixedFontStr_ = settings.value(QLatin1String("fixedFont")).toString();
    if(!fixedFontStr_.isEmpty()) {
        fixedFont_.fromString(fixedFontStr_);
    }

    // mouse
    doubleClickInterval_ = settings.value(QLatin1String("doubleClickInterval"));
    wheelScrollLines_ = settings.value(QLatin1String("wheelScrollLines"));

    // keyboard
    cursorFlashTime_ = settings.value(QLatin1String("cursorFlashTime"));
    settings.endGroup();
}

// this is called whenever the config file is changed.
void LXQtPlatformTheme::onSettingsChanged() {
    // D*mn! yet another Qt 5.4 regression!!!
    // See the bug report: https://github.com/lxde/lxqt/issues/441
    // Since Qt 5.4, QSettings uses QSaveFile to save the config files.
    // https://github.com/qtproject/qtbase/commit/8d15068911d7c0ba05732e2796aaa7a90e34a6a1#diff-e691c0405f02f3478f4f50a27bdaecde
    // QSaveFile will save the content to a new temp file, and replace the old file later.
    // Hence the existing config file is not changed. Instead, it's deleted and then replaced.
    // This new behaviour unfortunately breaks QFileSystemWatcher.
    // After file deletion, we can no longer receive any new change notifications.
    // The most ridiculous thing is, QFileSystemWatcher does not provide a
    // way for us to know if a file is deleted. WT*?
    // Luckily, I found a workaround: If the file path no longer exists
    // in the watcher's files(), this file is deleted.
    bool file_deleted = !settingsWatcher_->files().contains(settingsFile_);
    if(file_deleted) // if our config file is already deleted, reinstall a new watcher
        settingsWatcher_->addPath(settingsFile_);

    // NOTE: in Qt4, Qt monitors the change of _QT_SETTINGS_TIMESTAMP root property and
    // reload Trolltech.conf when the value is changed. Then, it automatically
    // applies the new settings.
    // Unfortunately, this is no longer the case in Qt5. Yes, yet another regression.
    // We're free to provide our own platform plugin, but Qt5 does not
    // update the settings and repaint the UI. We need to do it ourselves
    // through dirty hacks and private Qt internal APIs.
    QString oldStyle = style_;
    QString oldIconTheme = iconTheme_;
    QString oldFont = fontStr_;
    QString oldFixedFont = fixedFontStr_;

    loadSettings(); // reload the config file

    if(style_ != oldStyle) // the widget style is changed
    {
        // ask Qt5 to apply the new style
        if (qobject_cast<QApplication *>(QCoreApplication::instance()))
            QApplication::setStyle(style_);
    }

    if(iconTheme_ != oldIconTheme) { // the icon theme is changed
        QIconLoader::instance()->updateSystemTheme(); // this is a private internal API of Qt5.
    }

    // if font is changed
    if(oldFont != fontStr_ || oldFixedFont != fixedFontStr_){
        // FIXME: to my knowledge there is no way to ask Qt to reload the fonts.
        // Should we call QApplication::setFont() to override the font?
        // This does not work with QSS, though.
        //
        // After reading the source code of Qt, I think the right method to call
        // here is QApplicationPrivate::setSystemFont(). However, this is an
        // internal API and should not be used. Let's call QApplication::setFont()
        // instead since it approximately does the same thing.
        // Internally, QApplication will emit QEvent::ApplicationFontChange so
        // all of the widgets will update their fonts.
        // FIXME: should we call the internal API: QApplicationPrivate::setFont() instead?
        // QGtkStyle does this internally.
        fixedFont_.fromString(fixedFontStr_); // FIXME: how to set this to the app?
        if(font_.fromString(fontStr_))
            QApplication::setFont(font_);
    }

    // emit a ThemeChange event to all widgets
    Q_FOREACH(QWidget* widget, QApplication::allWidgets()) {
        // Qt5 added a QEvent::ThemeChange event.
        QEvent event(QEvent::ThemeChange);
        QApplication::sendEvent(widget, &event);
    }
}

bool LXQtPlatformTheme::usePlatformNativeDialog(DialogType type) const {
    return false;
}

#if 0
QPlatformDialogHelper *LXQtPlatformTheme::createPlatformDialogHelper(DialogType type) const {
    return 0;
}
#endif

const QPalette *LXQtPlatformTheme::palette(Palette type) const {

    if (type == QPlatformTheme::SystemPalette)
        // the default constructor uses the default palette
        return new QPalette;
    return QPlatformTheme::palette(type);
}

const QFont *LXQtPlatformTheme::font(Font type) const {
    if(type == SystemFont && !fontStr_.isEmpty()) {
        // NOTE: for some reason, this is not called by Qt when program startup.
        // So we do QApplication::setFont() manually.
        return &font_;
    }
    else if(type == FixedFont && !fixedFontStr_.isEmpty()) {
        return &fixedFont_;
    }
    return QPlatformTheme::font(type);
}

QVariant LXQtPlatformTheme::themeHint(ThemeHint hint) const {
    switch (hint) {
    case CursorFlashTime:
        return cursorFlashTime_;
    case KeyboardInputInterval:
        break;
    case MouseDoubleClickInterval:
        return doubleClickInterval_;
    case StartDragDistance:
        break;
    case StartDragTime:
        break;
    case KeyboardAutoRepeatRate:
        break;
    case PasswordMaskDelay:
        break;
    case StartDragVelocity:
        break;
    case TextCursorWidth:
        break;
    case DropShadow:
        return QVariant(true);
    case MaximumScrollBarDragDistance:
        break;
    case ToolButtonStyle:
        return QVariant(toolButtonStyle_);
    case ToolBarIconSize:
        break;
    case ItemViewActivateItemOnSingleClick:
        return QVariant(singleClickActivate_);
    case SystemIconThemeName:
        return iconTheme_;
    case SystemIconFallbackThemeName:
        return "hicolor";
    case IconThemeSearchPaths:
        return xdgIconThemePaths();
    case StyleNames:
        return QStringList() << style_;
    case WindowAutoPlacement:
        break;
    case DialogButtonBoxLayout:
        break;
    case DialogButtonBoxButtonsHaveIcons:
        return QVariant(true);
    case UseFullScreenForPopupMenu:
        break;
    case KeyboardScheme:
        return QVariant(X11KeyboardScheme);
    case UiEffects:
        break;
    case SpellCheckUnderlineStyle:
        break;
    case TabAllWidgets:
        break;
    case IconPixmapSizes:
        break;
    case PasswordMaskCharacter:
        break;
    case DialogSnapToDefaultButton:
        break;
    case ContextMenuOnMouseRelease:
        break;
    case MousePressAndHoldInterval:
        break;
    case MouseDoubleClickDistance:
        break;
    default:
        break;
    }
    return QPlatformTheme::themeHint(hint);
}

// Helper to return the icon theme paths from XDG.
QStringList LXQtPlatformTheme::xdgIconThemePaths() const
{
    QStringList paths;
    QStringList xdgDirs;

    // Add home directory first in search path
    const QFileInfo homeIconDir(QDir::homePath() + QStringLiteral("/.icons"));
    if (homeIconDir.isDir())
        paths.prepend(homeIconDir.absoluteFilePath());

    QString xdgDataHome = QFile::decodeName(qgetenv("XDG_DATA_HOME"));
    if (xdgDataHome.isEmpty())
        xdgDataHome = QDir::homePath() + QLatin1String("/.local/share");
    xdgDirs.append(xdgDataHome);

    QString xdgDataDirs = QFile::decodeName(qgetenv("XDG_DATA_DIRS"));
    if (xdgDataDirs.isEmpty())
        xdgDataDirs = QLatin1String("/usr/local/share/:/usr/share/");
    xdgDirs.append(xdgDataDirs);

    foreach (const QString &s, xdgDirs) {
        foreach (const QString &xdgDir, s.split(QLatin1Char(':'))) {
            const QFileInfo xdgIconsDir(xdgDir + QStringLiteral("/icons"));
            if (xdgIconsDir.isDir())
                paths.append(xdgIconsDir.absoluteFilePath());
        }
    }
    paths.removeDuplicates();
    return paths;
}
<|endoftext|>