<commit_before>// Copyright 2013 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/media/webrtc/native_desktop_media_list.h"

#include <map>
#include <set>
#include <sstream>

#include "base/hash.h"
#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/sequenced_worker_pool.h"
#include "chrome/browser/media/webrtc/desktop_media_list_observer.h"
#include "content/public/browser/browser_thread.h"
#include "media/base/video_util.h"
#include "third_party/libyuv/include/libyuv/scale_argb.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/webrtc/modules/desktop_capture/desktop_frame.h"
#include "third_party/webrtc/modules/desktop_capture/desktop_capturer.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/skia_util.h"

using content::BrowserThread;
using content::DesktopMediaID;

namespace {

// Update the list every second.
const int kDefaultUpdatePeriod = 1000;

// Returns a hash of a DesktopFrame content to detect when image for a desktop
// media source has changed.
uint32_t GetFrameHash(webrtc::DesktopFrame* frame) {
  int data_size = frame->stride() * frame->size().height();
  return base::SuperFastHash(reinterpret_cast<char*>(frame->data()), data_size);
}

gfx::ImageSkia ScaleDesktopFrame(std::unique_ptr<webrtc::DesktopFrame> frame,
                                 gfx::Size size) {
  gfx::Rect scaled_rect = media::ComputeLetterboxRegion(
      gfx::Rect(0, 0, size.width(), size.height()),
      gfx::Size(frame->size().width(), frame->size().height()));

  SkBitmap result;
  result.allocN32Pixels(scaled_rect.width(), scaled_rect.height(), true);
  result.lockPixels();

  uint8* pixels_data = reinterpret_cast<uint8*>(result.getPixels());
  libyuv::ARGBScale(frame->data(), frame->stride(),
                    frame->size().width(), frame->size().height(),
                    pixels_data, result.rowBytes(),
                    scaled_rect.width(), scaled_rect.height(),
                    libyuv::kFilterBilinear);

  // Set alpha channel values to 255 for all pixels.
  // TODO(sergeyu): Fix screen/window capturers to capture alpha channel and
  // remove this code. Currently screen/window capturers (at least some
  // implementations) only capture R, G and B channels and set Alpha to 0.
  // crbug.com/264424
  for (int y = 0; y < result.height(); ++y) {
    for (int x = 0; x < result.width(); ++x) {
      pixels_data[result.rowBytes() * y + x * result.bytesPerPixel() + 3] =
          0xff;
    }
  }

  result.unlockPixels();

  return gfx::ImageSkia::CreateFrom1xBitmap(result);
}

}  // namespace

NativeDesktopMediaList::SourceDescription::SourceDescription(
    DesktopMediaID id,
    const base::string16& name)
    : id(id),
      name(name) {
}

class NativeDesktopMediaList::Worker
    : public webrtc::DesktopCapturer::Callback {
 public:
  Worker(base::WeakPtr<NativeDesktopMediaList> media_list,
         std::unique_ptr<webrtc::DesktopCapturer> screen_capturer,
         std::unique_ptr<webrtc::DesktopCapturer> window_capturer);
  ~Worker() override;

  void Refresh(const gfx::Size& thumbnail_size,
               content::DesktopMediaID::Id view_dialog_id);

 private:
  typedef std::map<DesktopMediaID, uint32> ImageHashesMap;

  // webrtc::DesktopCapturer::Callback interface.
  void OnCaptureResult(webrtc::DesktopCapturer::Result result,
                       std::unique_ptr<webrtc::DesktopFrame> frame) override;

  base::WeakPtr<NativeDesktopMediaList> media_list_;

  std::unique_ptr<webrtc::DesktopCapturer> screen_capturer_;
  std::unique_ptr<webrtc::DesktopCapturer> window_capturer_;

  std::unique_ptr<webrtc::DesktopFrame> current_frame_;

  ImageHashesMap image_hashes_;

  DISALLOW_COPY_AND_ASSIGN(Worker);
};

NativeDesktopMediaList::Worker::Worker(
    base::WeakPtr<NativeDesktopMediaList> media_list,
    std::unique_ptr<webrtc::DesktopCapturer> screen_capturer,
    std::unique_ptr<webrtc::DesktopCapturer> window_capturer)
    : media_list_(media_list),
      screen_capturer_(std::move(screen_capturer)),
      window_capturer_(std::move(window_capturer)) {
  if (screen_capturer_)
    screen_capturer_->Start(this);
  if (window_capturer_)
    window_capturer_->Start(this);
}

NativeDesktopMediaList::Worker::~Worker() {}

void NativeDesktopMediaList::Worker::Refresh(
    const gfx::Size& thumbnail_size,
    content::DesktopMediaID::Id view_dialog_id) {
  std::vector<SourceDescription> sources;

  if (screen_capturer_) {
    webrtc::DesktopCapturer::SourceList screens;
    if (screen_capturer_->GetSourceList(&screens)) {
      bool mutiple_screens = screens.size() > 1;
      base::string16 title;
      for (size_t i = 0; i < screens.size(); ++i) {
        if (mutiple_screens) {
          title = base::UTF8ToUTF16("Screen " + base::IntToString(i+1));
        } else {
          title = base::UTF8ToUTF16("Entire screen");
        }
        sources.push_back(SourceDescription(DesktopMediaID(
            DesktopMediaID::TYPE_SCREEN, screens[i].id), title));
      }
    }
  }

  if (window_capturer_) {
    webrtc::DesktopCapturer::SourceList windows;
    if (window_capturer_->GetSourceList(&windows)) {
      for (webrtc::DesktopCapturer::SourceList::iterator it = windows.begin();
           it != windows.end(); ++it) {
        // Skip the picker dialog window.
        if (it->id != view_dialog_id) {
          sources.push_back(SourceDescription(
              DesktopMediaID(DesktopMediaID::TYPE_WINDOW, it->id),
              base::UTF8ToUTF16(it->title)));
        }
      }
    }
  }
  // Update list of windows before updating thumbnails.
  BrowserThread::PostTask(
      BrowserThread::UI, FROM_HERE,
      base::Bind(&NativeDesktopMediaList::OnSourcesList,
                 media_list_, sources));

  ImageHashesMap new_image_hashes;

  // Get a thumbnail for each source.
  for (size_t i = 0; i < sources.size(); ++i) {
    SourceDescription& source = sources[i];
    switch (source.id.type) {
      case DesktopMediaID::TYPE_SCREEN:
        if (!screen_capturer_->SelectSource(source.id.id))
          continue;
        screen_capturer_->CaptureFrame();
        break;

      case DesktopMediaID::TYPE_WINDOW:
        if (!window_capturer_->SelectSource(source.id.id))
          continue;
        window_capturer_->CaptureFrame();
        break;

      default:
        NOTREACHED();
    }

    // Expect that DesktopCapturer to always captures frames synchronously.
    // |current_frame_| may be NULL if capture failed (e.g. because window has
    // been closed).
    if (current_frame_) {
      uint32_t frame_hash = GetFrameHash(current_frame_.get());
      new_image_hashes[source.id] = frame_hash;

      // Scale the image only if it has changed.
      ImageHashesMap::iterator it = image_hashes_.find(source.id);
      if (it == image_hashes_.end() || it->second != frame_hash) {
        gfx::ImageSkia thumbnail =
            ScaleDesktopFrame(std::move(current_frame_), thumbnail_size);
        BrowserThread::PostTask(
            BrowserThread::UI, FROM_HERE,
            base::Bind(&NativeDesktopMediaList::OnSourceThumbnail,
                        media_list_, i, thumbnail));
      }
    }
  }

  image_hashes_.swap(new_image_hashes);

  BrowserThread::PostTask(
      BrowserThread::UI, FROM_HERE,
      base::Bind(&NativeDesktopMediaList::OnRefreshFinished, media_list_));
}

void NativeDesktopMediaList::Worker::OnCaptureResult(
    webrtc::DesktopCapturer::Result result,
    std::unique_ptr<webrtc::DesktopFrame> frame) {
  current_frame_ = std::move(frame);
}

NativeDesktopMediaList::NativeDesktopMediaList(
    std::unique_ptr<webrtc::DesktopCapturer> screen_capturer,
    std::unique_ptr<webrtc::DesktopCapturer> window_capturer)
    : screen_capturer_(std::move(screen_capturer)),
      window_capturer_(std::move(window_capturer)),
      update_period_(base::TimeDelta::FromMilliseconds(kDefaultUpdatePeriod)),
      thumbnail_size_(100, 100),
      view_dialog_id_(-1),
      observer_(NULL),
      weak_factory_(this) {
  base::SequencedWorkerPool* worker_pool = BrowserThread::GetBlockingPool();
  capture_task_runner_ = worker_pool->GetSequencedTaskRunner(
      worker_pool->GetSequenceToken());
}

NativeDesktopMediaList::~NativeDesktopMediaList() {
  capture_task_runner_->DeleteSoon(FROM_HERE, worker_.release());
}

void NativeDesktopMediaList::SetUpdatePeriod(base::TimeDelta period) {
  DCHECK(!observer_);
  update_period_ = period;
}

void NativeDesktopMediaList::SetThumbnailSize(
    const gfx::Size& thumbnail_size) {
  thumbnail_size_ = thumbnail_size;
}

void NativeDesktopMediaList::SetViewDialogWindowId(
    content::DesktopMediaID::Id dialog_id) {
  view_dialog_id_ = dialog_id;
}

void NativeDesktopMediaList::StartUpdating(DesktopMediaListObserver* observer) {
  DCHECK(!observer_);
  DCHECK(screen_capturer_ || window_capturer_);

  observer_ = observer;

  worker_.reset(new Worker(weak_factory_.GetWeakPtr(),
                           std::move(screen_capturer_),
                           std::move(window_capturer_)));
  Refresh();
}

int NativeDesktopMediaList::GetSourceCount() const {
  return sources_.size();
}

const DesktopMediaList::Source& NativeDesktopMediaList::GetSource(
    int index) const {
  return sources_[index];
}

std::vector<DesktopMediaList::Source> NativeDesktopMediaList::GetSources() const {
  return sources_;
}

void NativeDesktopMediaList::Refresh() {
  capture_task_runner_->PostTask(
      FROM_HERE, base::Bind(&Worker::Refresh, base::Unretained(worker_.get()),
                            thumbnail_size_, view_dialog_id_));
}

void NativeDesktopMediaList::OnSourcesList(
    const std::vector<SourceDescription>& new_sources) {
  typedef std::set<content::DesktopMediaID> SourceSet;
  SourceSet new_source_set;
  for (size_t i = 0; i < new_sources.size(); ++i) {
    new_source_set.insert(new_sources[i].id);
  }
  // Iterate through the old sources to find the removed sources.
  for (size_t i = 0; i < sources_.size(); ++i) {
    if (new_source_set.find(sources_[i].id) == new_source_set.end()) {
      observer_->OnSourceRemoved(i);
      sources_.erase(sources_.begin() + i);
      --i;
    }
  }
  // Iterate through the new sources to find the added sources.
  if (new_sources.size() > sources_.size()) {
    SourceSet old_source_set;
    for (size_t i = 0; i < sources_.size(); ++i) {
      old_source_set.insert(sources_[i].id);
    }

    for (size_t i = 0; i < new_sources.size(); ++i) {
      if (old_source_set.find(new_sources[i].id) == old_source_set.end()) {
        sources_.insert(sources_.begin() + i, Source());
        sources_[i].id = new_sources[i].id;
        sources_[i].name = new_sources[i].name;
        observer_->OnSourceAdded(i);
      }
    }
  }
  DCHECK_EQ(new_sources.size(), sources_.size());

  // Find the moved/changed sources.
  size_t pos = 0;
  while (pos < sources_.size()) {
    if (!(sources_[pos].id == new_sources[pos].id)) {
      // Find the source that should be moved to |pos|, starting from |pos + 1|
      // of |sources_|, because entries before |pos| should have been sorted.
      size_t old_pos = pos + 1;
      for (; old_pos < sources_.size(); ++old_pos) {
        if (sources_[old_pos].id == new_sources[pos].id)
          break;
      }
      DCHECK(sources_[old_pos].id == new_sources[pos].id);

      // Move the source from |old_pos| to |pos|.
      Source temp = sources_[old_pos];
      sources_.erase(sources_.begin() + old_pos);
      sources_.insert(sources_.begin() + pos, temp);

      observer_->OnSourceMoved(old_pos, pos);
    }

    if (sources_[pos].name != new_sources[pos].name) {
      sources_[pos].name = new_sources[pos].name;
      observer_->OnSourceNameChanged(pos);
    }
    ++pos;
  }
}

void NativeDesktopMediaList::OnSourceThumbnail(
    int index,
    const gfx::ImageSkia& image) {
  DCHECK_LT(index, static_cast<int>(sources_.size()));
  sources_[index].thumbnail = image;
  observer_->OnSourceThumbnailChanged(index);
}

void NativeDesktopMediaList::OnRefreshFinished() {
  // Give a chance to the observer to stop the refresh work.
  bool is_continue = observer_->OnRefreshFinished();
  if (is_continue) {
    BrowserThread::PostDelayedTask(
        BrowserThread::UI, FROM_HERE,
        base::Bind(&NativeDesktopMediaList::Refresh,
                   weak_factory_.GetWeakPtr()),
        update_period_);
  }
}
<commit_msg>SkBitmap no longer needs lock/unlock<commit_after>// Copyright 2013 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/media/webrtc/native_desktop_media_list.h"

#include <map>
#include <set>
#include <sstream>

#include "base/hash.h"
#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/sequenced_worker_pool.h"
#include "chrome/browser/media/webrtc/desktop_media_list_observer.h"
#include "content/public/browser/browser_thread.h"
#include "media/base/video_util.h"
#include "third_party/libyuv/include/libyuv/scale_argb.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/webrtc/modules/desktop_capture/desktop_frame.h"
#include "third_party/webrtc/modules/desktop_capture/desktop_capturer.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/skia_util.h"

using content::BrowserThread;
using content::DesktopMediaID;

namespace {

// Update the list every second.
const int kDefaultUpdatePeriod = 1000;

// Returns a hash of a DesktopFrame content to detect when image for a desktop
// media source has changed.
uint32_t GetFrameHash(webrtc::DesktopFrame* frame) {
  int data_size = frame->stride() * frame->size().height();
  return base::SuperFastHash(reinterpret_cast<char*>(frame->data()), data_size);
}

gfx::ImageSkia ScaleDesktopFrame(std::unique_ptr<webrtc::DesktopFrame> frame,
                                 gfx::Size size) {
  gfx::Rect scaled_rect = media::ComputeLetterboxRegion(
      gfx::Rect(0, 0, size.width(), size.height()),
      gfx::Size(frame->size().width(), frame->size().height()));

  SkBitmap result;
  result.allocN32Pixels(scaled_rect.width(), scaled_rect.height(), true);

  uint8* pixels_data = reinterpret_cast<uint8*>(result.getPixels());
  libyuv::ARGBScale(frame->data(), frame->stride(),
                    frame->size().width(), frame->size().height(),
                    pixels_data, result.rowBytes(),
                    scaled_rect.width(), scaled_rect.height(),
                    libyuv::kFilterBilinear);

  // Set alpha channel values to 255 for all pixels.
  // TODO(sergeyu): Fix screen/window capturers to capture alpha channel and
  // remove this code. Currently screen/window capturers (at least some
  // implementations) only capture R, G and B channels and set Alpha to 0.
  // crbug.com/264424
  for (int y = 0; y < result.height(); ++y) {
    for (int x = 0; x < result.width(); ++x) {
      pixels_data[result.rowBytes() * y + x * result.bytesPerPixel() + 3] =
          0xff;
    }
  }

  return gfx::ImageSkia::CreateFrom1xBitmap(result);
}

}  // namespace

NativeDesktopMediaList::SourceDescription::SourceDescription(
    DesktopMediaID id,
    const base::string16& name)
    : id(id),
      name(name) {
}

class NativeDesktopMediaList::Worker
    : public webrtc::DesktopCapturer::Callback {
 public:
  Worker(base::WeakPtr<NativeDesktopMediaList> media_list,
         std::unique_ptr<webrtc::DesktopCapturer> screen_capturer,
         std::unique_ptr<webrtc::DesktopCapturer> window_capturer);
  ~Worker() override;

  void Refresh(const gfx::Size& thumbnail_size,
               content::DesktopMediaID::Id view_dialog_id);

 private:
  typedef std::map<DesktopMediaID, uint32> ImageHashesMap;

  // webrtc::DesktopCapturer::Callback interface.
  void OnCaptureResult(webrtc::DesktopCapturer::Result result,
                       std::unique_ptr<webrtc::DesktopFrame> frame) override;

  base::WeakPtr<NativeDesktopMediaList> media_list_;

  std::unique_ptr<webrtc::DesktopCapturer> screen_capturer_;
  std::unique_ptr<webrtc::DesktopCapturer> window_capturer_;

  std::unique_ptr<webrtc::DesktopFrame> current_frame_;

  ImageHashesMap image_hashes_;

  DISALLOW_COPY_AND_ASSIGN(Worker);
};

NativeDesktopMediaList::Worker::Worker(
    base::WeakPtr<NativeDesktopMediaList> media_list,
    std::unique_ptr<webrtc::DesktopCapturer> screen_capturer,
    std::unique_ptr<webrtc::DesktopCapturer> window_capturer)
    : media_list_(media_list),
      screen_capturer_(std::move(screen_capturer)),
      window_capturer_(std::move(window_capturer)) {
  if (screen_capturer_)
    screen_capturer_->Start(this);
  if (window_capturer_)
    window_capturer_->Start(this);
}

NativeDesktopMediaList::Worker::~Worker() {}

void NativeDesktopMediaList::Worker::Refresh(
    const gfx::Size& thumbnail_size,
    content::DesktopMediaID::Id view_dialog_id) {
  std::vector<SourceDescription> sources;

  if (screen_capturer_) {
    webrtc::DesktopCapturer::SourceList screens;
    if (screen_capturer_->GetSourceList(&screens)) {
      bool mutiple_screens = screens.size() > 1;
      base::string16 title;
      for (size_t i = 0; i < screens.size(); ++i) {
        if (mutiple_screens) {
          title = base::UTF8ToUTF16("Screen " + base::IntToString(i+1));
        } else {
          title = base::UTF8ToUTF16("Entire screen");
        }
        sources.push_back(SourceDescription(DesktopMediaID(
            DesktopMediaID::TYPE_SCREEN, screens[i].id), title));
      }
    }
  }

  if (window_capturer_) {
    webrtc::DesktopCapturer::SourceList windows;
    if (window_capturer_->GetSourceList(&windows)) {
      for (webrtc::DesktopCapturer::SourceList::iterator it = windows.begin();
           it != windows.end(); ++it) {
        // Skip the picker dialog window.
        if (it->id != view_dialog_id) {
          sources.push_back(SourceDescription(
              DesktopMediaID(DesktopMediaID::TYPE_WINDOW, it->id),
              base::UTF8ToUTF16(it->title)));
        }
      }
    }
  }
  // Update list of windows before updating thumbnails.
  BrowserThread::PostTask(
      BrowserThread::UI, FROM_HERE,
      base::Bind(&NativeDesktopMediaList::OnSourcesList,
                 media_list_, sources));

  ImageHashesMap new_image_hashes;

  // Get a thumbnail for each source.
  for (size_t i = 0; i < sources.size(); ++i) {
    SourceDescription& source = sources[i];
    switch (source.id.type) {
      case DesktopMediaID::TYPE_SCREEN:
        if (!screen_capturer_->SelectSource(source.id.id))
          continue;
        screen_capturer_->CaptureFrame();
        break;

      case DesktopMediaID::TYPE_WINDOW:
        if (!window_capturer_->SelectSource(source.id.id))
          continue;
        window_capturer_->CaptureFrame();
        break;

      default:
        NOTREACHED();
    }

    // Expect that DesktopCapturer to always captures frames synchronously.
    // |current_frame_| may be NULL if capture failed (e.g. because window has
    // been closed).
    if (current_frame_) {
      uint32_t frame_hash = GetFrameHash(current_frame_.get());
      new_image_hashes[source.id] = frame_hash;

      // Scale the image only if it has changed.
      ImageHashesMap::iterator it = image_hashes_.find(source.id);
      if (it == image_hashes_.end() || it->second != frame_hash) {
        gfx::ImageSkia thumbnail =
            ScaleDesktopFrame(std::move(current_frame_), thumbnail_size);
        BrowserThread::PostTask(
            BrowserThread::UI, FROM_HERE,
            base::Bind(&NativeDesktopMediaList::OnSourceThumbnail,
                        media_list_, i, thumbnail));
      }
    }
  }

  image_hashes_.swap(new_image_hashes);

  BrowserThread::PostTask(
      BrowserThread::UI, FROM_HERE,
      base::Bind(&NativeDesktopMediaList::OnRefreshFinished, media_list_));
}

void NativeDesktopMediaList::Worker::OnCaptureResult(
    webrtc::DesktopCapturer::Result result,
    std::unique_ptr<webrtc::DesktopFrame> frame) {
  current_frame_ = std::move(frame);
}

NativeDesktopMediaList::NativeDesktopMediaList(
    std::unique_ptr<webrtc::DesktopCapturer> screen_capturer,
    std::unique_ptr<webrtc::DesktopCapturer> window_capturer)
    : screen_capturer_(std::move(screen_capturer)),
      window_capturer_(std::move(window_capturer)),
      update_period_(base::TimeDelta::FromMilliseconds(kDefaultUpdatePeriod)),
      thumbnail_size_(100, 100),
      view_dialog_id_(-1),
      observer_(NULL),
      weak_factory_(this) {
  base::SequencedWorkerPool* worker_pool = BrowserThread::GetBlockingPool();
  capture_task_runner_ = worker_pool->GetSequencedTaskRunner(
      worker_pool->GetSequenceToken());
}

NativeDesktopMediaList::~NativeDesktopMediaList() {
  capture_task_runner_->DeleteSoon(FROM_HERE, worker_.release());
}

void NativeDesktopMediaList::SetUpdatePeriod(base::TimeDelta period) {
  DCHECK(!observer_);
  update_period_ = period;
}

void NativeDesktopMediaList::SetThumbnailSize(
    const gfx::Size& thumbnail_size) {
  thumbnail_size_ = thumbnail_size;
}

void NativeDesktopMediaList::SetViewDialogWindowId(
    content::DesktopMediaID::Id dialog_id) {
  view_dialog_id_ = dialog_id;
}

void NativeDesktopMediaList::StartUpdating(DesktopMediaListObserver* observer) {
  DCHECK(!observer_);
  DCHECK(screen_capturer_ || window_capturer_);

  observer_ = observer;

  worker_.reset(new Worker(weak_factory_.GetWeakPtr(),
                           std::move(screen_capturer_),
                           std::move(window_capturer_)));
  Refresh();
}

int NativeDesktopMediaList::GetSourceCount() const {
  return sources_.size();
}

const DesktopMediaList::Source& NativeDesktopMediaList::GetSource(
    int index) const {
  return sources_[index];
}

std::vector<DesktopMediaList::Source> NativeDesktopMediaList::GetSources() const {
  return sources_;
}

void NativeDesktopMediaList::Refresh() {
  capture_task_runner_->PostTask(
      FROM_HERE, base::Bind(&Worker::Refresh, base::Unretained(worker_.get()),
                            thumbnail_size_, view_dialog_id_));
}

void NativeDesktopMediaList::OnSourcesList(
    const std::vector<SourceDescription>& new_sources) {
  typedef std::set<content::DesktopMediaID> SourceSet;
  SourceSet new_source_set;
  for (size_t i = 0; i < new_sources.size(); ++i) {
    new_source_set.insert(new_sources[i].id);
  }
  // Iterate through the old sources to find the removed sources.
  for (size_t i = 0; i < sources_.size(); ++i) {
    if (new_source_set.find(sources_[i].id) == new_source_set.end()) {
      observer_->OnSourceRemoved(i);
      sources_.erase(sources_.begin() + i);
      --i;
    }
  }
  // Iterate through the new sources to find the added sources.
  if (new_sources.size() > sources_.size()) {
    SourceSet old_source_set;
    for (size_t i = 0; i < sources_.size(); ++i) {
      old_source_set.insert(sources_[i].id);
    }

    for (size_t i = 0; i < new_sources.size(); ++i) {
      if (old_source_set.find(new_sources[i].id) == old_source_set.end()) {
        sources_.insert(sources_.begin() + i, Source());
        sources_[i].id = new_sources[i].id;
        sources_[i].name = new_sources[i].name;
        observer_->OnSourceAdded(i);
      }
    }
  }
  DCHECK_EQ(new_sources.size(), sources_.size());

  // Find the moved/changed sources.
  size_t pos = 0;
  while (pos < sources_.size()) {
    if (!(sources_[pos].id == new_sources[pos].id)) {
      // Find the source that should be moved to |pos|, starting from |pos + 1|
      // of |sources_|, because entries before |pos| should have been sorted.
      size_t old_pos = pos + 1;
      for (; old_pos < sources_.size(); ++old_pos) {
        if (sources_[old_pos].id == new_sources[pos].id)
          break;
      }
      DCHECK(sources_[old_pos].id == new_sources[pos].id);

      // Move the source from |old_pos| to |pos|.
      Source temp = sources_[old_pos];
      sources_.erase(sources_.begin() + old_pos);
      sources_.insert(sources_.begin() + pos, temp);

      observer_->OnSourceMoved(old_pos, pos);
    }

    if (sources_[pos].name != new_sources[pos].name) {
      sources_[pos].name = new_sources[pos].name;
      observer_->OnSourceNameChanged(pos);
    }
    ++pos;
  }
}

void NativeDesktopMediaList::OnSourceThumbnail(
    int index,
    const gfx::ImageSkia& image) {
  DCHECK_LT(index, static_cast<int>(sources_.size()));
  sources_[index].thumbnail = image;
  observer_->OnSourceThumbnailChanged(index);
}

void NativeDesktopMediaList::OnRefreshFinished() {
  // Give a chance to the observer to stop the refresh work.
  bool is_continue = observer_->OnRefreshFinished();
  if (is_continue) {
    BrowserThread::PostDelayedTask(
        BrowserThread::UI, FROM_HERE,
        base::Bind(&NativeDesktopMediaList::Refresh,
                   weak_factory_.GetWeakPtr()),
        update_period_);
  }
}
<|endoftext|>


<commit_before>/**
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * 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.
 */
#ifndef __STOUT_DURATION_HPP__
#define __STOUT_DURATION_HPP__

#include <ctype.h> // For 'isdigit'.
#include <limits.h> // For 'LLONG_(MAX|MIN)'

#include <iomanip>
#include <iostream>
#include <string>

#include "error.hpp"
#include "numify.hpp"
#include "try.hpp"

class Duration
{
public:
  static Try<Duration> parse(const std::string& s)
  {
    // TODO(benh): Support negative durations (i.e., starts with '-').
    size_t index = 0;
    while (index < s.size()) {
      if (isdigit(s[index]) || s[index] == '.') {
        index++;
        continue;
      }

      Try<double> value = numify<double>(s.substr(0, index));

      if (value.isError()) {
        return Error(value.error());
      }

      const std::string& unit = s.substr(index);

      if (unit == "ns") {
        return Duration(value.get(), NANOSECONDS);
      } else if (unit == "us") {
        return Duration(value.get(), MICROSECONDS);
      } else if (unit == "ms") {
        return Duration(value.get(), MILLISECONDS);
      } else if (unit == "secs") {
        return Duration(value.get(), SECONDS);
      } else if (unit == "mins") {
        return Duration(value.get(), MINUTES);
      } else if (unit == "hrs") {
        return Duration(value.get(), HOURS);
      } else if (unit == "days") {
        return Duration(value.get(), DAYS);
      } else if (unit == "weeks") {
        return Duration(value.get(), WEEKS);
      } else {
        return Error("Unknown duration unit '" + unit + "'");
      }
    }
    return Error("Invalid duration '" + s + "'");
  }

  static Try<Duration> create(double seconds);

  Duration() : nanos(0) {}

  int64_t ns() const   { return nanos; }
  double us() const    { return static_cast<double>(nanos) / MICROSECONDS; }
  double ms() const    { return static_cast<double>(nanos) / MILLISECONDS; }
  double secs() const  { return static_cast<double>(nanos) / SECONDS; }
  double mins() const  { return static_cast<double>(nanos) / MINUTES; }
  double hrs() const   { return static_cast<double>(nanos) / HOURS; }
  double days() const  { return static_cast<double>(nanos) / DAYS; }
  double weeks() const { return static_cast<double>(nanos) / WEEKS; }

  bool operator <  (const Duration& d) const { return nanos <  d.nanos; }
  bool operator <= (const Duration& d) const { return nanos <= d.nanos; }
  bool operator >  (const Duration& d) const { return nanos >  d.nanos; }
  bool operator >= (const Duration& d) const { return nanos >= d.nanos; }
  bool operator == (const Duration& d) const { return nanos == d.nanos; }
  bool operator != (const Duration& d) const { return nanos != d.nanos; }

  Duration& operator += (const Duration& that)
  {
    nanos += that.nanos;
    return *this;
  }

  Duration& operator -= (const Duration& that)
  {
    nanos -= that.nanos;
    return *this;
  }

  Duration& operator *= (double multiplier)
  {
    nanos = static_cast<int64_t>(nanos * multiplier);
    return *this;
  }

  Duration& operator /= (double divisor)
  {
    nanos = static_cast<int64_t>(nanos / divisor);
    return *this;
  }

  Duration operator + (const Duration& that) const
  {
    Duration sum = *this;
    sum += that;
    return sum;
  }

  Duration operator - (const Duration& that) const
  {
    Duration diff = *this;
    diff -= that;
    return diff;
  }

  Duration operator * (double multiplier) const
  {
    Duration product = *this;
    product *= multiplier;
    return product;
  }

  Duration operator / (double divisor) const
  {
    Duration quotient = *this;
    quotient /= divisor;
    return quotient;
  }

  // TODO(xujyan): Use constexpr for the following variables after
  // switching to C++11.
  // A constant holding the maximum value a Duration can have.
  static Duration max();
  // A constant holding the minimum (negative) value a Duration can
  // have.
  static Duration min();
  // A constant holding a Duration of a "zero" value.
  static Duration zero() { return Duration(); }

protected:
  static const int64_t NANOSECONDS  = 1;
  static const int64_t MICROSECONDS = 1000 * NANOSECONDS;
  static const int64_t MILLISECONDS = 1000 * MICROSECONDS;
  static const int64_t SECONDS      = 1000 * MILLISECONDS;
  static const int64_t MINUTES      = 60 * SECONDS;
  static const int64_t HOURS        = 60 * MINUTES;
  static const int64_t DAYS         = 24 * HOURS;
  static const int64_t WEEKS        = 7 * DAYS;

  // For the Seconds, Minutes, Hours, Days & Weeks constructor.
  Duration(int32_t value, int64_t unit)
    : nanos(value * unit) {}

  // For the Nanoseconds, Microseconds, Milliseconds constructor.
  Duration(int64_t value, int64_t unit)
    : nanos(value * unit) {}

private:
  // Used only by "parse".
  Duration(double value, int64_t unit)
    : nanos(static_cast<int64_t>(value * unit)) {}

  int64_t nanos;

  friend std::ostream& operator << (
    std::ostream& stream,
    const Duration& duration);
};

class Nanoseconds : public Duration
{
public:
  explicit Nanoseconds(int64_t nanoseconds)
    : Duration(nanoseconds, NANOSECONDS) {}

  Nanoseconds(const Duration& d) : Duration(d) {}
};

class Microseconds : public Duration
{
public:
  explicit Microseconds(int64_t microseconds)
    : Duration(microseconds, MICROSECONDS) {}

  Microseconds(const Duration& d) : Duration(d) {}
};

class Milliseconds : public Duration
{
public:
  explicit Milliseconds(int64_t milliseconds)
    : Duration(milliseconds, MILLISECONDS) {}

  Milliseconds(const Duration& d) : Duration(d) {}
};

class Seconds : public Duration
{
public:
  explicit Seconds(int64_t seconds)
    : Duration(seconds, SECONDS) {}

  Seconds(const Duration& d) : Duration(d) {}
};

class Minutes : public Duration
{
public:
  explicit Minutes(int32_t minutes)
    : Duration(minutes, MINUTES) {}

  Minutes(const Duration& d) : Duration(d) {}
};

class Hours : public Duration
{
public:
  explicit Hours(int32_t hours)
    : Duration(hours, HOURS) {}

  Hours(const Duration& d) : Duration(d) {}
};

class Days : public Duration
{
public:
  explicit Days(int32_t days)
    : Duration(days, DAYS) {}

  Days(const Duration& d) : Duration(d) {}
};

class Weeks : public Duration
{
public:
  explicit Weeks(int32_t value) : Duration(value, WEEKS) {}

  Weeks(const Duration& d) : Duration(d) {}
};

inline std::ostream& operator << (
    std::ostream& stream,
    const Duration& duration_)
{
  long precision = stream.precision();

  // Output the duration in full double precision.
  stream.precision(std::numeric_limits<double>::digits10);

  // Parse the duration as the sign and the absolute value.
  Duration duration = duration_;
  if (duration_ < Duration::zero()) {
    stream << "-";

    // Duration::min() may not be representable as a positive Duration.
    if (duration_ == Duration::min()) {
      duration = Duration::max();
    } else {
      duration = duration_ * -1;
    }
  }

  // First determine which bucket of time unit the duration falls into
  // then check whether the duration can be represented as a whole
  // number with this time unit or a smaller one.
  // e.g. 1.42857142857143weeks falls into the 'Weeks' bucket but
  // reads better with a smaller unit: '10days'. So we use 'days'
  // instead of 'weeks' to output the duration.
  int64_t nanoseconds = duration.ns();
  if (duration < Microseconds(1)) {
    stream << duration.ns() << "ns";
  } else if (duration < Milliseconds(1)) {
    if (nanoseconds % Duration::MICROSECONDS != 0) {
      // We can't get a whole number using this unit but we can at
      // one level down.
      stream << duration.ns() << "ns";
    } else {
      stream << duration.us() << "us";
    }
  } else if (duration < Seconds(1)) {
    if (nanoseconds % Duration::MILLISECONDS != 0 &&
        nanoseconds % Duration::MICROSECONDS == 0) {
      stream << duration.us() << "us";
    } else {
      stream << duration.ms() << "ms";
    }
  } else if (duration < Minutes(1)) {
    if (nanoseconds % Duration::SECONDS != 0 &&
        nanoseconds % Duration::MILLISECONDS == 0) {
      stream << duration.ms() << "ms";
    } else {
      stream << duration.secs() << "secs";
    }
  } else if (duration < Hours(1)) {
    if (nanoseconds % Duration::MINUTES != 0 &&
        nanoseconds % Duration::SECONDS == 0) {
      stream << duration.secs() << "secs";
    } else {
      stream << duration.mins() << "mins";
    }
  } else if (duration < Days(1)) {
    if (nanoseconds % Duration::HOURS != 0 &&
        nanoseconds % Duration::MINUTES == 0) {
      stream << duration.mins() << "mins";
    } else {
      stream << duration.hrs() << "hrs";
    }
  } else if (duration < Weeks(1)) {
    if (nanoseconds % Duration::DAYS != 0 &&
        nanoseconds % Duration::HOURS == 0) {
      stream << duration.hrs() << "hrs";
    } else {
      stream << duration.days() << "days";
    }
  } else {
    if (nanoseconds % Duration::WEEKS != 0 &&
        nanoseconds % Duration::DAYS == 0) {
      stream << duration.days() << "days";
    } else {
      stream << duration.weeks() << "weeks";
    }
  }

  // Return the stream to original formatting state.
  stream.precision(precision);

  return stream;
}

inline Try<Duration> Duration::create(double seconds)
{
  if (seconds * SECONDS > LLONG_MAX || seconds * SECONDS < LLONG_MIN) {
    return Error("Argument out of the range that a Duration can represent due "
                 "to int64_t's size limit");
  }

  return Nanoseconds(static_cast<int64_t>(seconds * SECONDS));
}

inline Duration Duration::max() { return Nanoseconds(LLONG_MAX); }

inline Duration Duration::min() { return Nanoseconds(LLONG_MIN); }

#endif // __STOUT_DURATION_HPP__
<commit_msg>Add 'value' and 'units' methods to Duration subclasses.<commit_after>/**
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * 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.
 */
#ifndef __STOUT_DURATION_HPP__
#define __STOUT_DURATION_HPP__

#include <ctype.h> // For 'isdigit'.
#include <limits.h> // For 'LLONG_(MAX|MIN)'

#include <iomanip>
#include <iostream>
#include <string>

#include "error.hpp"
#include "numify.hpp"
#include "try.hpp"

class Duration
{
public:
  static Try<Duration> parse(const std::string& s)
  {
    // TODO(benh): Support negative durations (i.e., starts with '-').
    size_t index = 0;
    while (index < s.size()) {
      if (isdigit(s[index]) || s[index] == '.') {
        index++;
        continue;
      }

      Try<double> value = numify<double>(s.substr(0, index));

      if (value.isError()) {
        return Error(value.error());
      }

      const std::string& unit = s.substr(index);

      if (unit == "ns") {
        return Duration(value.get(), NANOSECONDS);
      } else if (unit == "us") {
        return Duration(value.get(), MICROSECONDS);
      } else if (unit == "ms") {
        return Duration(value.get(), MILLISECONDS);
      } else if (unit == "secs") {
        return Duration(value.get(), SECONDS);
      } else if (unit == "mins") {
        return Duration(value.get(), MINUTES);
      } else if (unit == "hrs") {
        return Duration(value.get(), HOURS);
      } else if (unit == "days") {
        return Duration(value.get(), DAYS);
      } else if (unit == "weeks") {
        return Duration(value.get(), WEEKS);
      } else {
        return Error("Unknown duration unit '" + unit + "'");
      }
    }
    return Error("Invalid duration '" + s + "'");
  }

  static Try<Duration> create(double seconds);

  Duration() : nanos(0) {}

  int64_t ns() const   { return nanos; }
  double us() const    { return static_cast<double>(nanos) / MICROSECONDS; }
  double ms() const    { return static_cast<double>(nanos) / MILLISECONDS; }
  double secs() const  { return static_cast<double>(nanos) / SECONDS; }
  double mins() const  { return static_cast<double>(nanos) / MINUTES; }
  double hrs() const   { return static_cast<double>(nanos) / HOURS; }
  double days() const  { return static_cast<double>(nanos) / DAYS; }
  double weeks() const { return static_cast<double>(nanos) / WEEKS; }

  bool operator <  (const Duration& d) const { return nanos <  d.nanos; }
  bool operator <= (const Duration& d) const { return nanos <= d.nanos; }
  bool operator >  (const Duration& d) const { return nanos >  d.nanos; }
  bool operator >= (const Duration& d) const { return nanos >= d.nanos; }
  bool operator == (const Duration& d) const { return nanos == d.nanos; }
  bool operator != (const Duration& d) const { return nanos != d.nanos; }

  Duration& operator += (const Duration& that)
  {
    nanos += that.nanos;
    return *this;
  }

  Duration& operator -= (const Duration& that)
  {
    nanos -= that.nanos;
    return *this;
  }

  Duration& operator *= (double multiplier)
  {
    nanos = static_cast<int64_t>(nanos * multiplier);
    return *this;
  }

  Duration& operator /= (double divisor)
  {
    nanos = static_cast<int64_t>(nanos / divisor);
    return *this;
  }

  Duration operator + (const Duration& that) const
  {
    Duration sum = *this;
    sum += that;
    return sum;
  }

  Duration operator - (const Duration& that) const
  {
    Duration diff = *this;
    diff -= that;
    return diff;
  }

  Duration operator * (double multiplier) const
  {
    Duration product = *this;
    product *= multiplier;
    return product;
  }

  Duration operator / (double divisor) const
  {
    Duration quotient = *this;
    quotient /= divisor;
    return quotient;
  }

  // TODO(xujyan): Use constexpr for the following variables after
  // switching to C++11.
  // A constant holding the maximum value a Duration can have.
  static Duration max();
  // A constant holding the minimum (negative) value a Duration can
  // have.
  static Duration min();
  // A constant holding a Duration of a "zero" value.
  static Duration zero() { return Duration(); }

protected:
  static const int64_t NANOSECONDS  = 1;
  static const int64_t MICROSECONDS = 1000 * NANOSECONDS;
  static const int64_t MILLISECONDS = 1000 * MICROSECONDS;
  static const int64_t SECONDS      = 1000 * MILLISECONDS;
  static const int64_t MINUTES      = 60 * SECONDS;
  static const int64_t HOURS        = 60 * MINUTES;
  static const int64_t DAYS         = 24 * HOURS;
  static const int64_t WEEKS        = 7 * DAYS;

  // For the Seconds, Minutes, Hours, Days & Weeks constructor.
  Duration(int32_t value, int64_t unit)
    : nanos(value * unit) {}

  // For the Nanoseconds, Microseconds, Milliseconds constructor.
  Duration(int64_t value, int64_t unit)
    : nanos(value * unit) {}

private:
  // Used only by "parse".
  Duration(double value, int64_t unit)
    : nanos(static_cast<int64_t>(value * unit)) {}

  int64_t nanos;

  friend std::ostream& operator << (
    std::ostream& stream,
    const Duration& duration);
};

class Nanoseconds : public Duration
{
public:
  explicit Nanoseconds(int64_t nanoseconds)
    : Duration(nanoseconds, NANOSECONDS) {}

  Nanoseconds(const Duration& d) : Duration(d) {}

  double value() const { return static_cast<double>(this->ns()); }

  static std::string units() { return "ns"; }
};

class Microseconds : public Duration
{
public:
  explicit Microseconds(int64_t microseconds)
    : Duration(microseconds, MICROSECONDS) {}

  Microseconds(const Duration& d) : Duration(d) {}

  double value() const { return this->us(); }

  static std::string units() { return "us"; }
};

class Milliseconds : public Duration
{
public:
  explicit Milliseconds(int64_t milliseconds)
    : Duration(milliseconds, MILLISECONDS) {}

  Milliseconds(const Duration& d) : Duration(d) {}

  double value() const { return this->ms(); }

  static std::string units() { return "ms"; }
};

class Seconds : public Duration
{
public:
  explicit Seconds(int64_t seconds)
    : Duration(seconds, SECONDS) {}

  Seconds(const Duration& d) : Duration(d) {}

  double value() const { return this->secs(); }

  static std::string units() { return "secs"; }
};

class Minutes : public Duration
{
public:
  explicit Minutes(int32_t minutes)
    : Duration(minutes, MINUTES) {}

  Minutes(const Duration& d) : Duration(d) {}

  double value() const { return this->mins(); }

  static std::string units() { return "mins"; }
};

class Hours : public Duration
{
public:
  explicit Hours(int32_t hours)
    : Duration(hours, HOURS) {}

  Hours(const Duration& d) : Duration(d) {}

  double value() const { return this->hrs(); }

  static std::string units() { return "hrs"; }
};

class Days : public Duration
{
public:
  explicit Days(int32_t days)
    : Duration(days, DAYS) {}

  Days(const Duration& d) : Duration(d) {}

  double value() const { return this->days(); }

  static std::string units() { return "days"; }
};

class Weeks : public Duration
{
public:
  explicit Weeks(int32_t value) : Duration(value, WEEKS) {}

  Weeks(const Duration& d) : Duration(d) {}

  double value() const { return this->weeks(); }

  static std::string units() { return "weeks"; }
};

inline std::ostream& operator << (
    std::ostream& stream,
    const Duration& duration_)
{
  long precision = stream.precision();

  // Output the duration in full double precision.
  stream.precision(std::numeric_limits<double>::digits10);

  // Parse the duration as the sign and the absolute value.
  Duration duration = duration_;
  if (duration_ < Duration::zero()) {
    stream << "-";

    // Duration::min() may not be representable as a positive Duration.
    if (duration_ == Duration::min()) {
      duration = Duration::max();
    } else {
      duration = duration_ * -1;
    }
  }

  // First determine which bucket of time unit the duration falls into
  // then check whether the duration can be represented as a whole
  // number with this time unit or a smaller one.
  // e.g. 1.42857142857143weeks falls into the 'Weeks' bucket but
  // reads better with a smaller unit: '10days'. So we use 'days'
  // instead of 'weeks' to output the duration.
  int64_t nanoseconds = duration.ns();
  if (duration < Microseconds(1)) {
    stream << duration.ns() << Nanoseconds::units();
  } else if (duration < Milliseconds(1)) {
    if (nanoseconds % Duration::MICROSECONDS != 0) {
      // We can't get a whole number using this unit but we can at
      // one level down.
      stream << duration.ns() << Nanoseconds::units();
    } else {
      stream << duration.us() << Microseconds::units();
    }
  } else if (duration < Seconds(1)) {
    if (nanoseconds % Duration::MILLISECONDS != 0 &&
        nanoseconds % Duration::MICROSECONDS == 0) {
      stream << duration.us() << Microseconds::units();
    } else {
      stream << duration.ms() << Milliseconds::units();
    }
  } else if (duration < Minutes(1)) {
    if (nanoseconds % Duration::SECONDS != 0 &&
        nanoseconds % Duration::MILLISECONDS == 0) {
      stream << duration.ms() << Milliseconds::units();
    } else {
      stream << duration.secs() << Seconds::units();
    }
  } else if (duration < Hours(1)) {
    if (nanoseconds % Duration::MINUTES != 0 &&
        nanoseconds % Duration::SECONDS == 0) {
      stream << duration.secs() << Seconds::units();
    } else {
      stream << duration.mins() << Minutes::units();
    }
  } else if (duration < Days(1)) {
    if (nanoseconds % Duration::HOURS != 0 &&
        nanoseconds % Duration::MINUTES == 0) {
      stream << duration.mins() << Minutes::units();
    } else {
      stream << duration.hrs() << Hours::units();
    }
  } else if (duration < Weeks(1)) {
    if (nanoseconds % Duration::DAYS != 0 &&
        nanoseconds % Duration::HOURS == 0) {
      stream << duration.hrs() << Hours::units();
    } else {
      stream << duration.days() << Days::units();
    }
  } else {
    if (nanoseconds % Duration::WEEKS != 0 &&
        nanoseconds % Duration::DAYS == 0) {
      stream << duration.days() << Days::units();
    } else {
      stream << duration.weeks() << Weeks::units();
    }
  }

  // Return the stream to original formatting state.
  stream.precision(precision);

  return stream;
}

inline Try<Duration> Duration::create(double seconds)
{
  if (seconds * SECONDS > LLONG_MAX || seconds * SECONDS < LLONG_MIN) {
    return Error("Argument out of the range that a Duration can represent due "
                 "to int64_t's size limit");
  }

  return Nanoseconds(static_cast<int64_t>(seconds * SECONDS));
}

inline Duration Duration::max() { return Nanoseconds(LLONG_MAX); }

inline Duration Duration::min() { return Nanoseconds(LLONG_MIN); }

#endif // __STOUT_DURATION_HPP__
<|endoftext|>


<commit_before>#include "qhextheme.h"
#include <QGuiApplication>
#include <QPalette>
#include <QWidget>
#include <QFont>

#ifdef Q_OS_MAC
    #define MONOSPACE_FONT "Courier"
#else
    #define MONOSPACE_FONT "Monospace"
#endif

QHexTheme::QHexTheme(QObject *parent): QObject(parent)
{
    QWidget* container = static_cast<QWidget*>(parent);

    QFont f(MONOSPACE_FONT, qApp->font().pointSize());
    f.setStyleHint(QFont::TypeWriter); // Use monospace fonts

    container->setFont(f);
    container->setBackgroundRole(QPalette::Base);

    this->_selectedcursor = QColor(Qt::lightGray);
    this->_addressbackground = QColor(0xF0, 0xF0, 0xFE);
    this->_addressforeground = QColor(Qt::darkBlue);
    this->_alternateaddressforeground = QColor(Qt::red);
    this->_alternatelinecolor = QColor(0xF0, 0xF0, 0xFE);
    this->_linecolor = QColor(0xFF, 0xFF, 0xA0);
}

QColor QHexTheme::baseColor() const
{
    return this->_basecolor;
}

QColor QHexTheme::selectedCursor() const
{
    return this->_selectedcursor;
}

QColor QHexTheme::addressBackground() const
{
    return this->_addressbackground;
}

QColor QHexTheme::addressForeground() const
{
    return this->_addressforeground;
}

QColor QHexTheme::alternateAddressForeground() const
{
    return this->_alternateaddressforeground;
}

QColor QHexTheme::alternateLineColor() const
{
    return this->_alternatelinecolor;
}

QColor QHexTheme::lineColor() const
{
    return this->_linecolor;
}

void QHexTheme::setBaseColor(const QColor &color)
{
    if(this->_basecolor == color)
        return;

    this->_basecolor = color;
    emit themeChanged();
}

void QHexTheme::setSelectedCursor(const QColor &color)
{
    if(this->_selectedcursor == color)
        return;

    this->_selectedcursor = color;
    emit themeChanged();
}

void QHexTheme::setAddressBackground(const QColor &color)
{
    if(this->_addressbackground == color)
        return;

    this->_addressbackground = color;
    emit themeChanged();
}

void QHexTheme::setAddressForeground(const QColor &color)
{
    if(this->_addressforeground == color)
        return;

    this->_addressforeground = color;
    emit themeChanged();
}

void QHexTheme::setAlternateAddressForeground(const QColor &color)
{
    if(this->_alternateaddressforeground == color)
        return;

    this->_alternateaddressforeground = color;
    emit themeChanged();
}

void QHexTheme::setAlternateLineColor(const QColor &color)
{
    if(this->_alternatelinecolor == color)
        return;

    this->_alternatelinecolor = color;
    emit themeChanged();
}

void QHexTheme::setLineColor(const QColor &color)
{
    if(this->_linecolor == color)
        return;

    this->_linecolor = color;
    emit themeChanged();
}
<commit_msg>QHexTheme: Use system's monospace font<commit_after>#include "qhextheme.h"
#include <QGuiApplication>
#include <QFontDatabase>
#include <QPalette>
#include <QWidget>

QHexTheme::QHexTheme(QObject *parent): QObject(parent)
{
    QWidget* container = static_cast<QWidget*>(parent);

    container->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
    container->setBackgroundRole(QPalette::Base);

    this->_selectedcursor = QColor(Qt::lightGray);
    this->_addressbackground = QColor(0xF0, 0xF0, 0xFE);
    this->_addressforeground = QColor(Qt::darkBlue);
    this->_alternateaddressforeground = QColor(Qt::red);
    this->_alternatelinecolor = QColor(0xF0, 0xF0, 0xFE);
    this->_linecolor = QColor(0xFF, 0xFF, 0xA0);
}

QColor QHexTheme::baseColor() const
{
    return this->_basecolor;
}

QColor QHexTheme::selectedCursor() const
{
    return this->_selectedcursor;
}

QColor QHexTheme::addressBackground() const
{
    return this->_addressbackground;
}

QColor QHexTheme::addressForeground() const
{
    return this->_addressforeground;
}

QColor QHexTheme::alternateAddressForeground() const
{
    return this->_alternateaddressforeground;
}

QColor QHexTheme::alternateLineColor() const
{
    return this->_alternatelinecolor;
}

QColor QHexTheme::lineColor() const
{
    return this->_linecolor;
}

void QHexTheme::setBaseColor(const QColor &color)
{
    if(this->_basecolor == color)
        return;

    this->_basecolor = color;
    emit themeChanged();
}

void QHexTheme::setSelectedCursor(const QColor &color)
{
    if(this->_selectedcursor == color)
        return;

    this->_selectedcursor = color;
    emit themeChanged();
}

void QHexTheme::setAddressBackground(const QColor &color)
{
    if(this->_addressbackground == color)
        return;

    this->_addressbackground = color;
    emit themeChanged();
}

void QHexTheme::setAddressForeground(const QColor &color)
{
    if(this->_addressforeground == color)
        return;

    this->_addressforeground = color;
    emit themeChanged();
}

void QHexTheme::setAlternateAddressForeground(const QColor &color)
{
    if(this->_alternateaddressforeground == color)
        return;

    this->_alternateaddressforeground = color;
    emit themeChanged();
}

void QHexTheme::setAlternateLineColor(const QColor &color)
{
    if(this->_alternatelinecolor == color)
        return;

    this->_alternatelinecolor = color;
    emit themeChanged();
}

void QHexTheme::setLineColor(const QColor &color)
{
    if(this->_linecolor == color)
        return;

    this->_linecolor = color;
    emit themeChanged();
}
<|endoftext|>


<commit_before>#include "gui_system.h"
#include "engine/delegate.h"
#include "engine/delegate_list.h"
#include "engine/engine.h"
#include "engine/iallocator.h"
#include "engine/input_system.h"
#include "engine/lua_wrapper.h"
#include "engine/matrix.h"
#include "engine/path.h"
#include "engine/resource_manager.h"
#include "renderer/material.h"
#include "renderer/material_manager.h"
#include "renderer/pipeline.h"
#include "renderer/shader.h"

#pragma warning(disable : 4267)
#include "tb_renderer_batcher.h"
#include <animation/tb_animation.h>
#include <bgfx/bgfx.h>
#include <tb_core.h>
#include <tb_font_renderer.h>
#include <tb_language.h>
#include <tb_msg.h>
#include <tb_node_tree.h>
#include <tb_renderer.h>
#include <tb_system.h>
#include <tb_widgets.h>
#include <tb_widgets_common.h>
#include <tb_widgets_reader.h>

using namespace tb;

namespace tb
{

void TBSystem::RescheduleTimer(double fire_time) {}

}

namespace Lumix
{

struct GUISystemImpl;

struct RootWidget : public TBWidget
{
	RootWidget(GUISystemImpl& system) : gui_system(system) {}

	bool OnEvent(const TBWidgetEvent& ev) override;

	GUISystemImpl& gui_system;
};

struct LuaEvent
{
	bool invoke()
	{
		if (lua_rawgeti(state, LUA_REGISTRYINDEX, function_ref) != LUA_TFUNCTION)
		{
			ASSERT(false);
		}

		if (lua_pcall(state, 0, 0, 0) != LUA_OK)
		{
			g_log_error.log("Gui") << lua_tostring(state, -1);
			lua_pop(state, 1);
		}
		return true;
	}

	lua_State* state;
	int function_ref;
};

struct BGFXBitmap : public TBBitmap
{
	int Width() override { return w; }
	int Height() override { return h; }

	void SetData(uint32* data) override
	{
		pipeline->destroyTexture(tex);
		tex = pipeline->createTexture(w, h, data);
	}

	Pipeline* pipeline;
	bgfx::TextureHandle tex;
	int w, h;
};

struct GUIRenderer : public TBRendererBatcher
{
	GUIRenderer(Engine& engine)
		: m_pipeline(nullptr)
	{
		m_decl.begin()
			.add(bgfx::Attrib::Position, 2, bgfx::AttribType::Float)
			.add(bgfx::Attrib::TexCoord0, 2, bgfx::AttribType::Float)
			.add(bgfx::Attrib::Color0, 4, bgfx::AttribType::Uint8, true)
			.end();
		auto* material_manager = engine.getResourceManager().get(ResourceType("material"));
		auto* resource = material_manager->load(Path("pipelines/imgui/imgui.mat"));
		m_material = static_cast<Material*>(resource);
	}

	~GUIRenderer()
	{
		if (m_material) m_material->getResourceManager().unload(*m_material);
		if (m_pipeline) m_pipeline->destroyUniform(m_texture_uniform);
	}

	TBBitmap* CreateBitmap(int width, int height, uint32* data) override
	{
		if (!m_pipeline) return nullptr;

		auto* bitmap = new BGFXBitmap();
		bitmap->tex = m_pipeline->createTexture(width, height, data);
		bitmap->w = width;
		bitmap->h = height;
		bitmap->pipeline = m_pipeline;
		return bitmap;
	}

	void RenderBatch(Batch* batch) override
	{
		if (!m_material->isReady()) return;
		if (!m_pipeline->checkAvailTransientBuffers(batch->vertex_count, m_decl, batch->vertex_count)) return;
		bgfx::TransientVertexBuffer vertex_buffer;
		bgfx::TransientIndexBuffer index_buffer;
		m_pipeline->allocTransientBuffers(
			&vertex_buffer, batch->vertex_count, m_decl, &index_buffer, batch->vertex_count);

		int16* idcs = (int16*)index_buffer.data;
		struct Vertex
		{
			float x, y;
			float u, v;
			uint32 color;
		};
		Vertex* vtcs = (Vertex*)vertex_buffer.data;
		for (int i = 0; i < batch->vertex_count; ++i)
		{
			idcs[i] = i;
			vtcs[i].x = batch->vertex[i].x;
			vtcs[i].y = batch->vertex[i].y;
			vtcs[i].u = batch->vertex[i].u;
			vtcs[i].v = batch->vertex[i].v;
			vtcs[i].color = batch->vertex[i].col;
		}

		if (batch->bitmap) m_pipeline->setTexture(0, ((BGFXBitmap*)(batch->bitmap))->tex, m_texture_uniform);

		m_pipeline->render(vertex_buffer,
			index_buffer,
			Matrix::IDENTITY,
			0,
			batch->vertex_count,
			BGFX_STATE_BLEND_ALPHA,
			m_material->getShaderInstance());
	}

	void SetClipRect(const TBRect& rect) override { m_pipeline->setScissor(rect.x, rect.y, rect.w, rect.h); }

	Material* m_material;
	bgfx::VertexDecl m_decl;
	Pipeline* m_pipeline;
	bgfx::UniformHandle m_texture_uniform;
};

struct GUISystemImpl : public GUISystem
{
	GUISystemImpl(Engine& engine)
		: m_engine(engine)
		, m_renderer(engine)
		, m_interface(nullptr)
		, m_root_widget(*this)
		, m_lua_events(engine.getAllocator())
	{
		tb_core_init(&m_renderer);

		engine.getInputSystem().eventListener().bind<GUISystemImpl, &GUISystemImpl::onInputEvent>(this);
		registerLuaAPI();
	}

	~GUISystemImpl()
	{
		m_engine.getInputSystem().eventListener().unbind<GUISystemImpl, &GUISystemImpl::onInputEvent>(this);
		tb_core_shutdown();
	}

	void showGUI(bool show)
	{
		m_interface->enableCursor(show);
		m_root_widget.SetVisibility(show ? WIDGET_VISIBILITY_VISIBLE : WIDGET_VISIBILITY_GONE);
	}

	bool isGUIShown() const
	{
		return m_root_widget.GetVisibility() == WIDGET_VISIBILITY_VISIBLE;
	}

	void loadFile(const char* path)
	{
		m_root_widget.DeleteAllChildren();
		g_widgets_reader->LoadFile(&m_root_widget, path);
	}

	static int registerEvent(lua_State* L)
	{
		auto* gui = LuaWrapper::checkArg<GUISystemImpl*>(L, 1);
		EVENT_TYPE event_type = (EVENT_TYPE)LuaWrapper::checkArg<int>(L, 2);
		TBID widget_id = LuaWrapper::checkArg<const char*>(L, 3);
		LuaEvent event;
		event.state = L;
		lua_pushvalue(L, 4);
		event.function_ref = luaL_ref(L, LUA_REGISTRYINDEX);
		lua_pop(L, 1);
		uint64 event_id = (uint64)event_type << 32 | (uint32)widget_id;
		gui->m_lua_events.insert(event_id, event);
		return 0;
	}

	void unregisterEvent(int event_type, const char* widget_id)
	{
		TBID tb_widget_id(widget_id);
		uint64 event_id = (uint64)event_type << 32 | (uint32)tb_widget_id;
		m_lua_events.erase(event_id);
	}

	void registerLuaAPI()
	{
		lua_State* L = m_engine.getState();

		#define REGISTER_FUNCTION(name) \
			do {\
				auto f = &LuaWrapper::wrapMethod<GUISystemImpl, decltype(&GUISystemImpl::name), \
					&GUISystemImpl::name>; \
				LuaWrapper::createSystemFunction(L, "Gui", #name, f); \
			} while(false) \

		REGISTER_FUNCTION(showGUI);
		REGISTER_FUNCTION(isGUIShown);
		REGISTER_FUNCTION(loadFile);
		REGISTER_FUNCTION(unregisterEvent);

		LuaWrapper::createSystemFunction(L, "Gui", "registerEvent", &GUISystemImpl::registerEvent);

		LuaWrapper::createSystemVariable(L, "Gui", "instance", this);
		LuaWrapper::createSystemVariable(L, "Gui", "EVENT_TYPE_CLICK", EVENT_TYPE_CLICK);

		#undef REGISTER_FUNCTION
	}

	void setInterface(Interface* interface) override
	{
		// For safe typecasting
		if (m_renderer.m_pipeline)
		{
			m_renderer.m_pipeline->destroyUniform(m_renderer.m_texture_uniform);
		}

		m_renderer.m_pipeline = nullptr;

		m_interface = interface;

		if (!m_interface) return;

		auto* pipeline = m_interface->getPipeline();
		pipeline->addCustomCommandHandler("renderIngameGUI")
			.callback.bind<GUISystemImpl, &GUISystemImpl::pipelineCallback>(this);
		m_renderer.m_pipeline = m_interface->getPipeline();
		m_renderer.m_texture_uniform = m_renderer.m_pipeline->createTextureUniform("u_texture");

		void register_stb_font_renderer();
		register_stb_font_renderer();

		g_font_manager->AddFontInfo("gui/vera.ttf", "Vera");
		TBFontDescription fd;
		fd.SetID(TBIDC("Vera"));
		fd.SetSize(g_tb_skin->GetDimensionConverter()->DpToPx(14));
		g_font_manager->SetDefaultFontDescription(fd);

		TBFontFace* font = g_font_manager->CreateFontFace(g_font_manager->GetDefaultFontDescription());
		if (font)
			font->RenderGlyphs(
				" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz");

		g_tb_lng->Load("gui/language/lng_en.tb.txt");
		g_tb_skin->Load("gui/skin/skin.tb.txt");
	}

	void pipelineCallback()
	{
		int w = (int)m_interface->getSize().x;
		int h = (int)m_interface->getSize().y;
		m_renderer.BeginPaint(w, h);

		Lumix::Matrix ortho;
		m_root_widget.SetSize(w, h);
		ortho.setOrtho(0.0f, (float)w, (float)h, 0.0f, -1.0f, 1.0f, false);
		m_interface->getPipeline()->setViewProjection(ortho, w, h);

		m_root_widget.InvokePaint(TBWidget::PaintProps());
		m_renderer.EndPaint();
	}

	void onInputEvent(InputSystem::InputEvent& event)
	{
		Vec2 mouse_pos = m_engine.getInputSystem().getMousePos() - m_interface->getPos();
		switch (event.type)
		{
			case InputSystem::InputEvent::POINTER_DOWN:
				m_root_widget.InvokePointerDown((int)mouse_pos.x, (int)mouse_pos.y, 1, TB_MODIFIER_NONE, false);
				break;
			case InputSystem::InputEvent::POINTER_UP:
				m_root_widget.InvokePointerUp((int)mouse_pos.x, (int)mouse_pos.y, TB_MODIFIER_NONE, false);
				break;
			case InputSystem::InputEvent::POINTER_MOVE:
				m_root_widget.InvokePointerMove((int)mouse_pos.x, (int)mouse_pos.y, TB_MODIFIER_NONE, false);
				break;
			case InputSystem::InputEvent::KEY_DOWN:
				m_root_widget.InvokeKey(event.key.sym, TB_KEY_UNDEFINED, TB_MODIFIER_NONE, true);
				break;
			case InputSystem::InputEvent::KEY_UP:
				m_root_widget.InvokeKey(event.key.sym, TB_KEY_UNDEFINED, TB_MODIFIER_NONE, false);
				break;
		}
	}

	void update(float) override
	{
		float dx = m_engine.getInputSystem().getMouseXMove();
		float dy = m_engine.getInputSystem().getMouseYMove();
		Vec2 mouse_pos = m_engine.getInputSystem().getMousePos() - m_interface->getPos();
		if (dx != 0 || dy != 0)
		{
			m_root_widget.InvokePointerMove((int)mouse_pos.x, (int)mouse_pos.y, TB_MODIFIER_NONE, false);
		}

		TBAnimationManager::Update();
		m_root_widget.InvokeProcessStates();
		m_root_widget.InvokeProcess();
		TBMessageHandler::ProcessMessages();
	}

	void stopGame() override { m_lua_events.clear(); }

	const char* getName() const override { return "gui"; }

	Engine& m_engine;
	RootWidget m_root_widget;
	GUIRenderer m_renderer;
	Interface* m_interface;
	HashMap<uint64, LuaEvent> m_lua_events;
};

bool RootWidget::OnEvent(const TBWidgetEvent& ev)
{
	uint64 event_id = (uint64)ev.type << 32 | (uint32)ev.target->GetID();
	auto iter = gui_system.m_lua_events.find(event_id);
	if (!iter.isValid()) return false;

	return iter.value().invoke();
}

LUMIX_PLUGIN_ENTRY(gui)
{
	return LUMIX_NEW(engine.getAllocator(), GUISystemImpl)(engine);
}

} // namespace Lumix
<commit_msg>ingame gui's slider value can be accessed from Lua<commit_after>#include "gui_system.h"
#include "engine/delegate.h"
#include "engine/delegate_list.h"
#include "engine/engine.h"
#include "engine/iallocator.h"
#include "engine/input_system.h"
#include "engine/lua_wrapper.h"
#include "engine/matrix.h"
#include "engine/path.h"
#include "engine/resource_manager.h"
#include "renderer/material.h"
#include "renderer/material_manager.h"
#include "renderer/pipeline.h"
#include "renderer/shader.h"

#pragma warning(disable : 4267)
#include "tb_renderer_batcher.h"
#include <animation/tb_animation.h>
#include <bgfx/bgfx.h>
#include <tb_core.h>
#include <tb_font_renderer.h>
#include <tb_language.h>
#include <tb_msg.h>
#include <tb_node_tree.h>
#include <tb_renderer.h>
#include <tb_system.h>
#include <tb_widgets.h>
#include <tb_widgets_common.h>
#include <tb_widgets_reader.h>

using namespace tb;

namespace tb
{

void TBSystem::RescheduleTimer(double fire_time) {}

}

namespace Lumix
{

struct GUISystemImpl;

struct RootWidget : public TBWidget
{
	RootWidget(GUISystemImpl& system) : gui_system(system) {}

	bool OnEvent(const TBWidgetEvent& ev) override;

	GUISystemImpl& gui_system;
};

struct LuaEvent
{
	bool invoke()
	{
		if (lua_rawgeti(state, LUA_REGISTRYINDEX, function_ref) != LUA_TFUNCTION)
		{
			ASSERT(false);
		}

		if (lua_pcall(state, 0, 0, 0) != LUA_OK)
		{
			g_log_error.log("Gui") << lua_tostring(state, -1);
			lua_pop(state, 1);
		}
		return true;
	}

	lua_State* state;
	int function_ref;
};

struct BGFXBitmap : public TBBitmap
{
	int Width() override { return w; }
	int Height() override { return h; }

	void SetData(uint32* data) override
	{
		pipeline->destroyTexture(tex);
		tex = pipeline->createTexture(w, h, data);
	}

	Pipeline* pipeline;
	bgfx::TextureHandle tex;
	int w, h;
};

struct GUIRenderer : public TBRendererBatcher
{
	GUIRenderer(Engine& engine)
		: m_pipeline(nullptr)
	{
		m_decl.begin()
			.add(bgfx::Attrib::Position, 2, bgfx::AttribType::Float)
			.add(bgfx::Attrib::TexCoord0, 2, bgfx::AttribType::Float)
			.add(bgfx::Attrib::Color0, 4, bgfx::AttribType::Uint8, true)
			.end();
		auto* material_manager = engine.getResourceManager().get(ResourceType("material"));
		auto* resource = material_manager->load(Path("pipelines/imgui/imgui.mat"));
		m_material = static_cast<Material*>(resource);
	}

	~GUIRenderer()
	{
		if (m_material) m_material->getResourceManager().unload(*m_material);
		if (m_pipeline) m_pipeline->destroyUniform(m_texture_uniform);
	}

	TBBitmap* CreateBitmap(int width, int height, uint32* data) override
	{
		if (!m_pipeline) return nullptr;

		auto* bitmap = new BGFXBitmap();
		bitmap->tex = m_pipeline->createTexture(width, height, data);
		bitmap->w = width;
		bitmap->h = height;
		bitmap->pipeline = m_pipeline;
		return bitmap;
	}

	void RenderBatch(Batch* batch) override
	{
		if (!m_material->isReady()) return;
		if (!m_pipeline->checkAvailTransientBuffers(batch->vertex_count, m_decl, batch->vertex_count)) return;
		bgfx::TransientVertexBuffer vertex_buffer;
		bgfx::TransientIndexBuffer index_buffer;
		m_pipeline->allocTransientBuffers(
			&vertex_buffer, batch->vertex_count, m_decl, &index_buffer, batch->vertex_count);

		int16* idcs = (int16*)index_buffer.data;
		struct Vertex
		{
			float x, y;
			float u, v;
			uint32 color;
		};
		Vertex* vtcs = (Vertex*)vertex_buffer.data;
		for (int i = 0; i < batch->vertex_count; ++i)
		{
			idcs[i] = i;
			vtcs[i].x = batch->vertex[i].x;
			vtcs[i].y = batch->vertex[i].y;
			vtcs[i].u = batch->vertex[i].u;
			vtcs[i].v = batch->vertex[i].v;
			vtcs[i].color = batch->vertex[i].col;
		}

		if (batch->bitmap) m_pipeline->setTexture(0, ((BGFXBitmap*)(batch->bitmap))->tex, m_texture_uniform);

		m_pipeline->render(vertex_buffer,
			index_buffer,
			Matrix::IDENTITY,
			0,
			batch->vertex_count,
			BGFX_STATE_BLEND_ALPHA,
			m_material->getShaderInstance());
	}

	void SetClipRect(const TBRect& rect) override { m_pipeline->setScissor(rect.x, rect.y, rect.w, rect.h); }

	Material* m_material;
	bgfx::VertexDecl m_decl;
	Pipeline* m_pipeline;
	bgfx::UniformHandle m_texture_uniform;
};

struct GUISystemImpl : public GUISystem
{
	GUISystemImpl(Engine& engine)
		: m_engine(engine)
		, m_renderer(engine)
		, m_interface(nullptr)
		, m_root_widget(*this)
		, m_lua_events(engine.getAllocator())
	{
		tb_core_init(&m_renderer);

		engine.getInputSystem().eventListener().bind<GUISystemImpl, &GUISystemImpl::onInputEvent>(this);
		registerLuaAPI();
	}

	~GUISystemImpl()
	{
		m_engine.getInputSystem().eventListener().unbind<GUISystemImpl, &GUISystemImpl::onInputEvent>(this);
		tb_core_shutdown();
	}

	void showGUI(bool show)
	{
		m_interface->enableCursor(show);
		m_root_widget.SetVisibility(show ? WIDGET_VISIBILITY_VISIBLE : WIDGET_VISIBILITY_GONE);
	}

	bool isGUIShown() const
	{
		return m_root_widget.GetVisibility() == WIDGET_VISIBILITY_VISIBLE;
	}

	void loadFile(const char* path)
	{
		m_root_widget.DeleteAllChildren();
		g_widgets_reader->LoadFile(&m_root_widget, path);
	}

	int getSliderValue(const char* id)
	{
		auto* slider = m_root_widget.GetWidgetByID(tb::TBID(id));
		return slider->GetValue();
	}

	void setSliderValue(const char* id, int value)
	{
		auto* slider = m_root_widget.GetWidgetByID(tb::TBID(id));
		slider->SetValue(value);
	}

	static int registerEvent(lua_State* L)
	{
		auto* gui = LuaWrapper::checkArg<GUISystemImpl*>(L, 1);
		EVENT_TYPE event_type = (EVENT_TYPE)LuaWrapper::checkArg<int>(L, 2);
		TBID widget_id = LuaWrapper::checkArg<const char*>(L, 3);
		LuaEvent event;
		event.state = L;
		lua_pushvalue(L, 4);
		event.function_ref = luaL_ref(L, LUA_REGISTRYINDEX);
		lua_pop(L, 1);
		uint64 event_id = (uint64)event_type << 32 | (uint32)widget_id;
		gui->m_lua_events.insert(event_id, event);
		return 0;
	}

	void unregisterEvent(int event_type, const char* widget_id)
	{
		TBID tb_widget_id(widget_id);
		uint64 event_id = (uint64)event_type << 32 | (uint32)tb_widget_id;
		m_lua_events.erase(event_id);
	}

	void registerLuaAPI()
	{
		lua_State* L = m_engine.getState();

		#define REGISTER_FUNCTION(name) \
			do {\
				auto f = &LuaWrapper::wrapMethod<GUISystemImpl, decltype(&GUISystemImpl::name), \
					&GUISystemImpl::name>; \
				LuaWrapper::createSystemFunction(L, "Gui", #name, f); \
			} while(false) \

		REGISTER_FUNCTION(showGUI);
		REGISTER_FUNCTION(isGUIShown);
		REGISTER_FUNCTION(loadFile);
		REGISTER_FUNCTION(getSliderValue);
		REGISTER_FUNCTION(setSliderValue);
		REGISTER_FUNCTION(unregisterEvent);

		LuaWrapper::createSystemFunction(L, "Gui", "registerEvent", &GUISystemImpl::registerEvent);

		LuaWrapper::createSystemVariable(L, "Gui", "instance", this);
		LuaWrapper::createSystemVariable(L, "Gui", "EVENT_TYPE_CLICK", EVENT_TYPE_CLICK);
		LuaWrapper::createSystemVariable(L, "Gui", "EVENT_TYPE_CHANGED", EVENT_TYPE_CHANGED);

		#undef REGISTER_FUNCTION
	}

	void setInterface(Interface* interface) override
	{
		// For safe typecasting
		if (m_renderer.m_pipeline)
		{
			m_renderer.m_pipeline->destroyUniform(m_renderer.m_texture_uniform);
		}

		m_renderer.m_pipeline = nullptr;

		m_interface = interface;

		if (!m_interface) return;

		auto* pipeline = m_interface->getPipeline();
		pipeline->addCustomCommandHandler("renderIngameGUI")
			.callback.bind<GUISystemImpl, &GUISystemImpl::pipelineCallback>(this);
		m_renderer.m_pipeline = m_interface->getPipeline();
		m_renderer.m_texture_uniform = m_renderer.m_pipeline->createTextureUniform("u_texture");

		void register_stb_font_renderer();
		register_stb_font_renderer();

		g_font_manager->AddFontInfo("gui/vera.ttf", "Vera");
		TBFontDescription fd;
		fd.SetID(TBIDC("Vera"));
		fd.SetSize(g_tb_skin->GetDimensionConverter()->DpToPx(14));
		g_font_manager->SetDefaultFontDescription(fd);

		TBFontFace* font = g_font_manager->CreateFontFace(g_font_manager->GetDefaultFontDescription());
		if (font)
			font->RenderGlyphs(
				" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz");

		g_tb_lng->Load("gui/language/lng_en.tb.txt");
		g_tb_skin->Load("gui/skin/skin.tb.txt");
	}

	void pipelineCallback()
	{
		int w = (int)m_interface->getSize().x;
		int h = (int)m_interface->getSize().y;
		m_renderer.BeginPaint(w, h);

		Lumix::Matrix ortho;
		m_root_widget.SetSize(w, h);
		ortho.setOrtho(0.0f, (float)w, (float)h, 0.0f, -1.0f, 1.0f, false);
		m_interface->getPipeline()->setViewProjection(ortho, w, h);

		m_root_widget.InvokePaint(TBWidget::PaintProps());
		m_renderer.EndPaint();
	}

	void onInputEvent(InputSystem::InputEvent& event)
	{
		Vec2 mouse_pos = m_engine.getInputSystem().getMousePos() - m_interface->getPos();
		switch (event.type)
		{
			case InputSystem::InputEvent::POINTER_DOWN:
				m_root_widget.InvokePointerDown((int)mouse_pos.x, (int)mouse_pos.y, 1, TB_MODIFIER_NONE, false);
				break;
			case InputSystem::InputEvent::POINTER_UP:
				m_root_widget.InvokePointerUp((int)mouse_pos.x, (int)mouse_pos.y, TB_MODIFIER_NONE, false);
				break;
			case InputSystem::InputEvent::POINTER_MOVE:
				m_root_widget.InvokePointerMove((int)mouse_pos.x, (int)mouse_pos.y, TB_MODIFIER_NONE, false);
				break;
			case InputSystem::InputEvent::KEY_DOWN:
				m_root_widget.InvokeKey(event.key.sym, TB_KEY_UNDEFINED, TB_MODIFIER_NONE, true);
				break;
			case InputSystem::InputEvent::KEY_UP:
				m_root_widget.InvokeKey(event.key.sym, TB_KEY_UNDEFINED, TB_MODIFIER_NONE, false);
				break;
		}
	}

	void update(float) override
	{
		float dx = m_engine.getInputSystem().getMouseXMove();
		float dy = m_engine.getInputSystem().getMouseYMove();
		Vec2 mouse_pos = m_engine.getInputSystem().getMousePos() - m_interface->getPos();
		if (dx != 0 || dy != 0)
		{
			m_root_widget.InvokePointerMove((int)mouse_pos.x, (int)mouse_pos.y, TB_MODIFIER_NONE, false);
		}

		TBAnimationManager::Update();
		m_root_widget.InvokeProcessStates();
		m_root_widget.InvokeProcess();
		TBMessageHandler::ProcessMessages();
	}

	void stopGame() override { m_lua_events.clear(); }

	const char* getName() const override { return "gui"; }

	Engine& m_engine;
	RootWidget m_root_widget;
	GUIRenderer m_renderer;
	Interface* m_interface;
	HashMap<uint64, LuaEvent> m_lua_events;
};

bool RootWidget::OnEvent(const TBWidgetEvent& ev)
{
	uint64 event_id = (uint64)ev.type << 32 | (uint32)ev.target->GetID();
	auto iter = gui_system.m_lua_events.find(event_id);
	if (!iter.isValid()) return false;

	return iter.value().invoke();
}

LUMIX_PLUGIN_ENTRY(gui)
{
	return LUMIX_NEW(engine.getAllocator(), GUISystemImpl)(engine);
}

} // namespace Lumix
<|endoftext|>


<commit_before>/*  This file is part of the Tufão project
    Copyright (C) 2012 Vinícius dos Santos Oliveira <vini.ipsmaker@gmail.com>

    This 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, see <http://www.gnu.org/licenses/>.
*/

#include "priv/httpfileserver.h"

#include <QtCore/QFileInfo>
#include <QtCore/QDateTime>
#include <QtCore/QPair>
#include <QtCore/QDir>
#include <QtCore/QUrl>

#include <QtNetwork/QAbstractSocket>

#include "httpserverrequest.h"

static qint64 bufferSize = BUFFER_SIZE;

inline static QList< QPair<qulonglong, qulonglong> >
ranges(const Tufao::Headers &headers, qulonglong fileSize)
{
    if (!headers.contains("Range"))
        return QList< QPair<qulonglong, qulonglong> >();

    QList< QPair<qulonglong, qulonglong> > ranges;

    QList<QByteArray> rangeHeaders(headers.values("Range"));
    foreach (QByteArray rangesSpecifier, rangeHeaders) {
        static const QByteArray bytesUnit("bytes=");
        if (!rangesSpecifier.startsWith(bytesUnit))
            continue;

        QList<QByteArray> rangeSet(rangesSpecifier.mid(bytesUnit.size())
                                   .split(','));
        foreach (QByteArray rangeSpec, rangeSet) {
            rangeSpec = rangeSpec.trimmed();

            if (rangeSpec.startsWith('-')) {
                bool ok;
                qulonglong nbytes = rangeSpec.mid(1).toULongLong(&ok);
                if (!ok || nbytes == 0)
                    continue;

                if (nbytes >= fileSize) {
                    ranges.push_back(QPair<qulonglong, qulonglong>
                                     (0, fileSize - 1));
                } else {
                    ranges.push_back(QPair<qulonglong, qulonglong>
                                     (fileSize - nbytes, fileSize - 1));
                }
            } else {
                int i = rangeSpec.indexOf('-');
                if (i == -1)
                    continue;

                bool ok[2];
                QPair<qulonglong, qulonglong>
                        range(rangeSpec.toULongLong(ok),
                              rangeSpec.mid(1 + i).toULongLong());

                if (!ok[1])
                    range.second = fileSize - 1;

                if (!ok[0] || range.second < range.first
                        || range.first >= fileSize)
                    continue;

                if (range.second >= fileSize)
                    range.second = fileSize;

                ranges.push_back(range);
            }
        }
    }

    return ranges;
}

namespace Tufao {

HttpFileServer::HttpFileServer(const QString &dir, QObject *parent) :
    AbstractHttpServerRequestHandler(parent),
    priv(new Priv(dir))
{
}

HttpFileServer::~HttpFileServer()
{
    delete priv;
}

// split method in check and transmit phases
// also, how the hell will this work with 404-responses?
void HttpFileServer::serveFile(const QString &fileName,
                               HttpServerRequest &request,
                               HttpServerResponse &response)
{
    // Check if method is alright
    {
        const QByteArray method(request.method());
        if (method != "GET" && method != "HEAD") {
            response.writeHead(HttpResponseStatusCode::METHOD_NOT_ALLOWED);
            response.headers().insert("Allow", "GET, HEAD");
            response.end();
            return;
        }
    }

    QFileInfo fileInfo(fileName);

    if (!fileInfo.exists()) {
        response.writeHead(HttpResponseStatusCode::NOT_FOUND);
        response.end();
        return;
    }

    // Check "If-Modified-Since" header
    if (request.headers().contains("If-Modified-Since")) {
        foreach (const QByteArray &value,
                 request.headers().values("If-Modified-Since")) {
            QDateTime date(Headers::toDateTime(value));

            // Ignore invalid values
            if (fileInfo.lastModified() < date || !date.isValid()) {
                continue;
            } else if (fileInfo.lastModified() == date) {
                response.writeHead(HttpResponseStatusCode::NOT_MODIFIED);
                response.end();
            }
        }
    }

    // Check "If-Unmodified-Since" header
    if (request.headers().contains("If-Unmodified-Since")) {
        foreach (const QByteArray &value,
                 request.headers().values("If-Unmodified-Since")) {
            QDateTime date(Headers::toDateTime(value));

            // Ignore invalid values
            if (fileInfo.lastModified() < date || !date.isValid()) {
                continue;
            } else if (fileInfo.lastModified() > date) {
                response.writeHead(HttpResponseStatusCode::PRECONDITION_FAILED);
                response.end();
            }
        }
    }

    // Check If-Range header
    if (request.headers().contains("If-Range")
            && request.headers().contains("Range")) {
        foreach (QByteArray value, request.headers().values("If-Range")) {
            // Ignore ETags
            int strSize = value.size();
            if ((strSize > 0 && value[0] == '"')
                    || (strSize > 1 && value[0] == 'W' && value[1] == '/')) {
                continue;
            } else {
                QDateTime date(Headers::toDateTime(value));

                // Ignore invalid values
                if (fileInfo.lastModified() < date || !date.isValid()) {
                    continue;
                } else if (fileInfo.lastModified() > date) {
                    // Return the entire entity using a 200 response
                    request.headers().remove("Range");
                    break;
                }
            }
        }
    }

    // All conditionals were okay, continue...

    response.headers().insert("Accept-Ranges", "bytes");
    response.headers().insert("Date", Headers
                              ::fromDateTime(QDateTime::currentDateTime()));
    response.headers().insert("Last-Modified", Headers
                              ::fromDateTime(fileInfo.lastModified()));

    if (request.method() == "HEAD") {
        response.writeHead(HttpResponseStatusCode::OK);
        response.end();
        return;
    }

    QFile file(fileName);
    file.open(QIODevice::ReadOnly);

    QList< QPair<qulonglong, qulonglong> >
            ranges(::ranges(request.headers(), fileInfo.size()));

    // Not a byterange request
    if (!ranges.size()) {
        // Not a _satisfiable_ byterange request
        if (request.headers().contains("Range")) {
            static const QByteArray bytesUnit("bytes */");

            response.writeHead(HttpResponseStatusCode
                               ::REQUESTED_RANGE_NOT_SATISFIABLE);
            response.headers().insert("Content-Range", bytesUnit
                                      + QByteArray::number(fileInfo.size()));
            response.end();
            return;
        }

        // Just send the entity
        response.writeHead(HttpResponseStatusCode::OK);

        while (!file.atEnd()) {
            response << file.read(::bufferSize);
            request.socket().flush();
        }

        response.end();
    } else if (ranges.size() == 1) {
        // ONE range
        static const QByteArray bytesUnit("bytes ");

        response.writeHead(HttpResponseStatusCode::OK);
        QPair<qulonglong, qulonglong> &range(ranges[0]);
        response.headers().insert("Content-Range", bytesUnit
                                  + QByteArray::number(range.first)
                                  + '-'
                                  + QByteArray::number(range.second)
                                  + '/'
                                  + QByteArray::number(fileInfo.size()));
        file.seek(range.first);

        qint64 remaining = 1 + range.second - range.first;
        while (remaining) {
            QByteArray chunk(file.read(qMin(remaining, ::bufferSize)));
            response << chunk;
            request.socket().flush();
            remaining -= chunk.size();
        }

        response.end();
    } else {
        // MULTIPLE ranges
        QByteArray contentType(response.headers().value("Content-Type"));
        response.headers().replace("Content-Type", "multipart/byteranges;"
                                   " boundary=THIS_STRING_SEPARATES");

        for (int i = 0;i != ranges.size();++i) {
            QPair<qulonglong, qulonglong> &range(ranges[i]);
            response << "--THIS_STRING_SEPARATES\r\n";
            if (!contentType.isEmpty())
                response << "Content-type: " << contentType << "\r\n";

            response << QByteArray("Content-range: bytes")
                     << QByteArray::number(range.first) << QByteArray(1, '-')
                     << QByteArray::number(range.second)
                     << QByteArray(1, '/')
                     << QByteArray::number(fileInfo.size()) << "\r\n"
                     << QByteArray("\r\n");

            file.seek(range.first);

            qint64 remaining = 1 + range.second - range.first;
            while (remaining) {
                QByteArray chunk(file.read(qMin(remaining, ::bufferSize)));
                response << chunk;
                request.socket().flush();
                remaining -= chunk.size();
            }
        }
        response.end("--THIS_STRING_SEPARATES--\r\n");
    }
}

bool HttpFileServer::serveFile(const QString &fileName,
                               HttpServerResponse &response,
                               HttpResponseStatusCode statusCode)
{
    QFile file(fileName);
    if (!file.open(QIODevice::ReadOnly))
        return false;

    response.writeHead(statusCode);

    qint64 remaining = file.size();
    while (remaining) {
        QByteArray chunk(file.read(qMin(remaining, ::bufferSize)));
        response << chunk;
        response.flush();
        remaining -= chunk.size();
    }
    response.end();

    return true;
}

qint64 HttpFileServer::bufferSize()
{
    return ::bufferSize;
}

void HttpFileServer::setBufferSize(qint64 size)
{
    if (size <= 0)
        return;

    ::bufferSize = size;
}

bool HttpFileServer::handleRequest(HttpServerRequest &request,
                                   HttpServerResponse &response)
{
    QString fileName{
        QDir::cleanPath(priv->rootDir
                        + QDir::toNativeSeparators(request.url()
                                                   .path(QUrl::FullyDecoded)))};
    if (!fileName.startsWith(priv->rootDir + QDir::separator()))
        return false;

    {
        QFileInfo fileInfo(fileName);
        if (!fileInfo.exists() || !fileInfo.isFile())
            return false;
    }

    serveFile(fileName, request, response);
    return true;
}

} // namespace Tufao
<commit_msg>Removed obsolete comment in HttpFileServer code<commit_after>/*  This file is part of the Tufão project
    Copyright (C) 2012 Vinícius dos Santos Oliveira <vini.ipsmaker@gmail.com>

    This 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, see <http://www.gnu.org/licenses/>.
*/

#include "priv/httpfileserver.h"

#include <QtCore/QFileInfo>
#include <QtCore/QDateTime>
#include <QtCore/QPair>
#include <QtCore/QDir>
#include <QtCore/QUrl>

#include <QtNetwork/QAbstractSocket>

#include "httpserverrequest.h"

static qint64 bufferSize = BUFFER_SIZE;

inline static QList< QPair<qulonglong, qulonglong> >
ranges(const Tufao::Headers &headers, qulonglong fileSize)
{
    if (!headers.contains("Range"))
        return QList< QPair<qulonglong, qulonglong> >();

    QList< QPair<qulonglong, qulonglong> > ranges;

    QList<QByteArray> rangeHeaders(headers.values("Range"));
    foreach (QByteArray rangesSpecifier, rangeHeaders) {
        static const QByteArray bytesUnit("bytes=");
        if (!rangesSpecifier.startsWith(bytesUnit))
            continue;

        QList<QByteArray> rangeSet(rangesSpecifier.mid(bytesUnit.size())
                                   .split(','));
        foreach (QByteArray rangeSpec, rangeSet) {
            rangeSpec = rangeSpec.trimmed();

            if (rangeSpec.startsWith('-')) {
                bool ok;
                qulonglong nbytes = rangeSpec.mid(1).toULongLong(&ok);
                if (!ok || nbytes == 0)
                    continue;

                if (nbytes >= fileSize) {
                    ranges.push_back(QPair<qulonglong, qulonglong>
                                     (0, fileSize - 1));
                } else {
                    ranges.push_back(QPair<qulonglong, qulonglong>
                                     (fileSize - nbytes, fileSize - 1));
                }
            } else {
                int i = rangeSpec.indexOf('-');
                if (i == -1)
                    continue;

                bool ok[2];
                QPair<qulonglong, qulonglong>
                        range(rangeSpec.toULongLong(ok),
                              rangeSpec.mid(1 + i).toULongLong());

                if (!ok[1])
                    range.second = fileSize - 1;

                if (!ok[0] || range.second < range.first
                        || range.first >= fileSize)
                    continue;

                if (range.second >= fileSize)
                    range.second = fileSize;

                ranges.push_back(range);
            }
        }
    }

    return ranges;
}

namespace Tufao {

HttpFileServer::HttpFileServer(const QString &dir, QObject *parent) :
    AbstractHttpServerRequestHandler(parent),
    priv(new Priv(dir))
{
}

HttpFileServer::~HttpFileServer()
{
    delete priv;
}

void HttpFileServer::serveFile(const QString &fileName,
                               HttpServerRequest &request,
                               HttpServerResponse &response)
{
    // Check if method is alright
    {
        const QByteArray method(request.method());
        if (method != "GET" && method != "HEAD") {
            response.writeHead(HttpResponseStatusCode::METHOD_NOT_ALLOWED);
            response.headers().insert("Allow", "GET, HEAD");
            response.end();
            return;
        }
    }

    QFileInfo fileInfo(fileName);

    if (!fileInfo.exists()) {
        response.writeHead(HttpResponseStatusCode::NOT_FOUND);
        response.end();
        return;
    }

    // Check "If-Modified-Since" header
    if (request.headers().contains("If-Modified-Since")) {
        foreach (const QByteArray &value,
                 request.headers().values("If-Modified-Since")) {
            QDateTime date(Headers::toDateTime(value));

            // Ignore invalid values
            if (fileInfo.lastModified() < date || !date.isValid()) {
                continue;
            } else if (fileInfo.lastModified() == date) {
                response.writeHead(HttpResponseStatusCode::NOT_MODIFIED);
                response.end();
            }
        }
    }

    // Check "If-Unmodified-Since" header
    if (request.headers().contains("If-Unmodified-Since")) {
        foreach (const QByteArray &value,
                 request.headers().values("If-Unmodified-Since")) {
            QDateTime date(Headers::toDateTime(value));

            // Ignore invalid values
            if (fileInfo.lastModified() < date || !date.isValid()) {
                continue;
            } else if (fileInfo.lastModified() > date) {
                response.writeHead(HttpResponseStatusCode::PRECONDITION_FAILED);
                response.end();
            }
        }
    }

    // Check If-Range header
    if (request.headers().contains("If-Range")
            && request.headers().contains("Range")) {
        foreach (QByteArray value, request.headers().values("If-Range")) {
            // Ignore ETags
            int strSize = value.size();
            if ((strSize > 0 && value[0] == '"')
                    || (strSize > 1 && value[0] == 'W' && value[1] == '/')) {
                continue;
            } else {
                QDateTime date(Headers::toDateTime(value));

                // Ignore invalid values
                if (fileInfo.lastModified() < date || !date.isValid()) {
                    continue;
                } else if (fileInfo.lastModified() > date) {
                    // Return the entire entity using a 200 response
                    request.headers().remove("Range");
                    break;
                }
            }
        }
    }

    // All conditionals were okay, continue...

    response.headers().insert("Accept-Ranges", "bytes");
    response.headers().insert("Date", Headers
                              ::fromDateTime(QDateTime::currentDateTime()));
    response.headers().insert("Last-Modified", Headers
                              ::fromDateTime(fileInfo.lastModified()));

    if (request.method() == "HEAD") {
        response.writeHead(HttpResponseStatusCode::OK);
        response.end();
        return;
    }

    QFile file(fileName);
    file.open(QIODevice::ReadOnly);

    QList< QPair<qulonglong, qulonglong> >
            ranges(::ranges(request.headers(), fileInfo.size()));

    // Not a byterange request
    if (!ranges.size()) {
        // Not a _satisfiable_ byterange request
        if (request.headers().contains("Range")) {
            static const QByteArray bytesUnit("bytes */");

            response.writeHead(HttpResponseStatusCode
                               ::REQUESTED_RANGE_NOT_SATISFIABLE);
            response.headers().insert("Content-Range", bytesUnit
                                      + QByteArray::number(fileInfo.size()));
            response.end();
            return;
        }

        // Just send the entity
        response.writeHead(HttpResponseStatusCode::OK);

        while (!file.atEnd()) {
            response << file.read(::bufferSize);
            request.socket().flush();
        }

        response.end();
    } else if (ranges.size() == 1) {
        // ONE range
        static const QByteArray bytesUnit("bytes ");

        response.writeHead(HttpResponseStatusCode::OK);
        QPair<qulonglong, qulonglong> &range(ranges[0]);
        response.headers().insert("Content-Range", bytesUnit
                                  + QByteArray::number(range.first)
                                  + '-'
                                  + QByteArray::number(range.second)
                                  + '/'
                                  + QByteArray::number(fileInfo.size()));
        file.seek(range.first);

        qint64 remaining = 1 + range.second - range.first;
        while (remaining) {
            QByteArray chunk(file.read(qMin(remaining, ::bufferSize)));
            response << chunk;
            request.socket().flush();
            remaining -= chunk.size();
        }

        response.end();
    } else {
        // MULTIPLE ranges
        QByteArray contentType(response.headers().value("Content-Type"));
        response.headers().replace("Content-Type", "multipart/byteranges;"
                                   " boundary=THIS_STRING_SEPARATES");

        for (int i = 0;i != ranges.size();++i) {
            QPair<qulonglong, qulonglong> &range(ranges[i]);
            response << "--THIS_STRING_SEPARATES\r\n";
            if (!contentType.isEmpty())
                response << "Content-type: " << contentType << "\r\n";

            response << QByteArray("Content-range: bytes")
                     << QByteArray::number(range.first) << QByteArray(1, '-')
                     << QByteArray::number(range.second)
                     << QByteArray(1, '/')
                     << QByteArray::number(fileInfo.size()) << "\r\n"
                     << QByteArray("\r\n");

            file.seek(range.first);

            qint64 remaining = 1 + range.second - range.first;
            while (remaining) {
                QByteArray chunk(file.read(qMin(remaining, ::bufferSize)));
                response << chunk;
                request.socket().flush();
                remaining -= chunk.size();
            }
        }
        response.end("--THIS_STRING_SEPARATES--\r\n");
    }
}

bool HttpFileServer::serveFile(const QString &fileName,
                               HttpServerResponse &response,
                               HttpResponseStatusCode statusCode)
{
    QFile file(fileName);
    if (!file.open(QIODevice::ReadOnly))
        return false;

    response.writeHead(statusCode);

    qint64 remaining = file.size();
    while (remaining) {
        QByteArray chunk(file.read(qMin(remaining, ::bufferSize)));
        response << chunk;
        response.flush();
        remaining -= chunk.size();
    }
    response.end();

    return true;
}

qint64 HttpFileServer::bufferSize()
{
    return ::bufferSize;
}

void HttpFileServer::setBufferSize(qint64 size)
{
    if (size <= 0)
        return;

    ::bufferSize = size;
}

bool HttpFileServer::handleRequest(HttpServerRequest &request,
                                   HttpServerResponse &response)
{
    QString fileName{
        QDir::cleanPath(priv->rootDir
                        + QDir::toNativeSeparators(request.url()
                                                   .path(QUrl::FullyDecoded)))};
    if (!fileName.startsWith(priv->rootDir + QDir::separator()))
        return false;

    {
        QFileInfo fileInfo(fileName);
        if (!fileInfo.exists() || !fileInfo.isFile())
            return false;
    }

    serveFile(fileName, request, response);
    return true;
}

} // namespace Tufao
<|endoftext|>