#include "GraphicsCacheItem.h"
#include "GraphicsImage.h"
#include "GraphicsParams.h"
-#include "LoaderQueue.h"
+#include "GraphicsCache.h"
-#include <boost/bind.hpp>
+#include "support/debug.h"
+#include "support/lassert.h"
+#include "support/Timeout.h"
+#include <queue>
+#include <memory>
+#include <set>
-using std::string;
-
+using namespace std;
+using namespace lyx::support;
namespace lyx {
-using support::FileName;
-
namespace graphics {
-class Loader::Impl : public boost::signals::trackable {
+
+/////////////////////////////////////////////////////////////////////
+//
+// LoaderQueue
+//
+/////////////////////////////////////////////////////////////////////
+
+class LoaderQueue {
public:
+ /// Use this to request that the item is loaded.
+ void touch(Cache::ItemPtr const & item);
+ /// Query whether the clock is ticking.
+ bool running() const;
+ ///get the and only instance of the class
+ static LoaderQueue & get();
+private:
+ /// This class is a singleton class... use LoaderQueue::get() instead
+ LoaderQueue();
+ /// The in-progress loading queue (elements are unique here).
+ list<Cache::ItemPtr> cache_queue_;
+ /// Used to make the insertion of new elements faster.
+ set<Cache::ItemPtr> cache_set_;
+ /// Newly touched elements go here. loadNext moves them to cache_queue_
+ queue<Cache::ItemPtr> bucket_;
///
- Impl();
+ Timeout timer;
+ ///
+ bool running_;
+
+ /** This is the 'threaded' method, that does the loading in the
+ * background.
+ */
+ void loadNext();
+ ///
+ void startLoader();
+ ///
+ void stopLoader();
+};
+
+
+
+//static int const s_numimages_ = 5;
+static int const s_numimages_ = 10;
+static int const s_millisecs_ = 500;
+
+
+LoaderQueue & LoaderQueue::get()
+{
+ static LoaderQueue singleton;
+ return singleton;
+}
+
+
+void LoaderQueue::loadNext()
+{
+ LYXERR(Debug::GRAPHICS, "LoaderQueue: "
+ << cache_queue_.size() << " items in the queue");
+ int counter = s_numimages_;
+ while (!cache_queue_.empty() && counter--) {
+ Cache::ItemPtr ptr = cache_queue_.front();
+ cache_set_.erase(ptr);
+ cache_queue_.pop_front();
+ if (ptr->status() == WaitingToLoad)
+ ptr->startLoading();
+ }
+ if (!cache_queue_.empty())
+ startLoader();
+ else
+ stopLoader();
+}
+
+
+LoaderQueue::LoaderQueue() : timer(s_millisecs_, Timeout::ONETIME),
+ running_(false)
+{
+ // Disconnected when this is destroyed
+ timer.timeout.connect([this](){ loadNext(); });
+}
+
+
+void LoaderQueue::startLoader()
+{
+ LYXERR(Debug::GRAPHICS, "LoaderQueue: waking up");
+ running_ = true;
+ timer.setTimeout(s_millisecs_);
+ timer.start();
+}
+
+
+void LoaderQueue::stopLoader()
+{
+ timer.stop();
+ running_ = false ;
+ LYXERR(Debug::GRAPHICS, "LoaderQueue: I'm going to sleep");
+}
+
+
+bool LoaderQueue::running() const
+{
+ return running_ ;
+}
+
+
+void LoaderQueue::touch(Cache::ItemPtr const & item)
+{
+ if (! cache_set_.insert(item).second) {
+ list<Cache::ItemPtr>::iterator
+ it = cache_queue_.begin();
+ list<Cache::ItemPtr>::iterator
+ end = cache_queue_.end();
+
+ it = find(it, end, item);
+ if (it != end)
+ cache_queue_.erase(it);
+ }
+ cache_queue_.push_front(item);
+ if (!running_)
+ startLoader();
+}
+
+
+
+/////////////////////////////////////////////////////////////////////
+//
+// GraphicsLoader
+//
+/////////////////////////////////////////////////////////////////////
+
+typedef std::shared_ptr<Image> ImagePtr;
+
+class Loader::Impl {
+ friend class Loader;
+public:
+ ///
+ Impl(FileName const & doc_file);
///
~Impl();
///
///
Params const & params() const { return params_; }
+ ///
+ FileName doc_file_;
/// The loading status of the image.
ImageStatus status_;
/** Must store a copy of the cached item to ensure that it is not
*/
Cache::ItemPtr cached_item_;
/// We modify a local copy of the image once it is loaded.
- Image::ImagePtr image_;
+ ImagePtr image_;
/// This signal is emitted when the image loading status changes.
- boost::signal<void()> signal_;
+ signals2::signal<void()> signal_;
+ /// The connection of the signal statusChanged
+ signals2::scoped_connection connection_;
+
+ double displayPixelRatio() const
+ {
+ return params_.pixel_ratio;
+ }
+ void setDisplayPixelRatio(double scale)
+ {
+ params_.pixel_ratio = scale;
+ }
private:
///
};
-Loader::Loader()
- : pimpl_(new Impl)
+Loader::Loader(FileName const & doc_file)
+ : pimpl_(new Impl(doc_file))
{}
-Loader::Loader(FileName const & file, DisplayType type)
- : pimpl_(new Impl)
+Loader::Loader(FileName const & doc_file, FileName const & file, bool display)
+ : pimpl_(new Impl(doc_file))
{
- reset(file, type);
+ reset(file, display);
}
-Loader::Loader(FileName const & file, Params const & params)
- : pimpl_(new Impl)
+Loader::Loader(FileName const & doc_file, FileName const & file, Params const & params)
+ : pimpl_(new Impl(doc_file))
{
reset(file, params);
}
+Loader::Loader(FileName const & doc_file, Loader const & other)
+ : pimpl_(new Impl(doc_file))
+{
+ Params const & params = other.pimpl_->params();
+ reset(params.filename, params);
+}
+
+
Loader::Loader(Loader const & other)
- : pimpl_(new Impl)
+ : pimpl_(new Impl(other.pimpl_->doc_file_))
{
Params const & params = other.pimpl_->params();
reset(params.filename, params);
Loader::~Loader()
-{}
+{
+ delete pimpl_;
+}
Loader & Loader::operator=(Loader const & other)
{
+ LASSERT(false, /**/);
if (this != &other) {
+ delete pimpl_;
+ pimpl_ = new Impl(other.pimpl_->doc_file_);
Params const & params = other.pimpl_->params();
reset(params.filename, params);
}
}
-void Loader::reset(FileName const & file, DisplayType type) const
+void Loader::reset(FileName const & file, bool display) const
{
Params params;
- params.display = type;
+ params.display = display;
pimpl_->resetParams(params);
pimpl_->resetFile(file);
void Loader::startLoading() const
{
- if (pimpl_->status_ != WaitingToLoad || !pimpl_->cached_item_.get())
+ if (pimpl_->status_ != WaitingToLoad || !pimpl_->cached_item_
+ || pimpl_->cached_item_->status() == Converting)
return;
pimpl_->startLoading();
}
+void Loader::reload() const
+{
+ pimpl_->cached_item_->startLoading();
+}
+
+
void Loader::startMonitoring() const
{
- if (!pimpl_->cached_item_.get())
+ if (!pimpl_->cached_item_)
return;
pimpl_->cached_item_->startMonitoring();
bool Loader::monitoring() const
{
- if (!pimpl_->cached_item_.get())
+ if (!pimpl_->cached_item_)
return false;
return pimpl_->cached_item_->monitoring();
}
-unsigned long Loader::checksum() const
+void Loader::checkModifiedAsync() const
{
- if (!pimpl_->cached_item_.get())
- return 0;
+ if (!pimpl_->cached_item_)
+ return;
- return pimpl_->cached_item_->checksum();
+ pimpl_->cached_item_->checkModifiedAsync();
}
FileName const & Loader::filename() const
{
static FileName const empty;
- return pimpl_->cached_item_.get() ?
+ return pimpl_->cached_item_ ?
pimpl_->cached_item_->filename() : empty;
}
}
-boost::signals::connection Loader::connect(slot_type const & slot) const
+double Loader::displayPixelRatio() const
+{
+ return pimpl_->displayPixelRatio();
+}
+
+
+void Loader::setDisplayPixelRatio(double scale)
+{
+ pimpl_->setDisplayPixelRatio(scale);
+}
+
+
+signals2::connection Loader::connect(slot const & slot) const
{
return pimpl_->signal_.connect(slot);
}
}
-Loader::Impl::Impl()
- : status_(WaitingToLoad)
+Loader::Impl::Impl(FileName const & doc_file)
+ : doc_file_(doc_file), status_(WaitingToLoad)
{
}
void Loader::Impl::resetFile(FileName const & file)
{
- FileName const old_file = cached_item_.get() ?
+ FileName const old_file = cached_item_ ?
cached_item_->filename() : FileName();
if (file == old_file)
if (!old_file.empty()) {
continue_monitoring = cached_item_->monitoring();
+ // cached_item_ is going to be reset, so the connected
+ // signal needs to be disconnected.
+ try {
+ // This can in theory throw a BufferException
+ connection_.disconnect();
+ } catch (...) {
+ LYXERR(Debug::GRAPHICS, "Unable to disconnect signal.");
+ }
cached_item_.reset();
- Cache::get().remove(old_file);
+ if (status_ != Converting) {
+ Cache::get().remove(old_file);
+ } else {
+ //TODO remove cache item when it is not busy any more, see #7163
+ }
}
- status_ = cached_item_.get() ? cached_item_->status() : WaitingToLoad;
+ status_ = cached_item_ ? cached_item_->status() : WaitingToLoad;
image_.reset();
- if (cached_item_.get() || file.empty())
+ if (cached_item_ || file.empty())
return;
Cache & gc = Cache::get();
if (!gc.inCache(file))
- gc.add(file);
+ gc.add(file, doc_file_);
// We /must/ make a local copy of this.
cached_item_ = gc.item(file);
if (continue_monitoring && !cached_item_->monitoring())
cached_item_->startMonitoring();
- cached_item_->connect(boost::bind(&Impl::statusChanged, this));
+ // This is a scoped connection
+ connection_ = cached_item_->connect([this](){ statusChanged(); });
}
return;
params_ = params;
- status_ = cached_item_.get() ? cached_item_->status() : WaitingToLoad;
+ status_ = cached_item_ ? cached_item_->status() : WaitingToLoad;
image_.reset();
}
void Loader::Impl::statusChanged()
{
- status_ = cached_item_.get() ? cached_item_->status() : WaitingToLoad;
+ status_ = cached_item_ ? cached_item_->status() : WaitingToLoad;
createPixmap();
signal_();
}
void Loader::Impl::createPixmap()
{
- if (!cached_item_.get() ||
- params_.display == NoDisplay || status_ != Loaded)
+ if (!params_.display || status_ != Loaded)
return;
+ if (!cached_item_) {
+ LYXERR(Debug::GRAPHICS, "pixmap not cached yet");
+ return;
+ }
+
+ if (!cached_item_->image()) {
+ // There must have been a problem reading the file.
+ LYXERR(Debug::GRAPHICS, "Graphics file not loaded.");
+ return;
+ }
+
image_.reset(cached_item_->image()->clone());
- // These do nothing if there's nothing to do
- image_->clip(params_);
- image_->rotate(params_);
- image_->scale(params_);
+ if (params_.pixel_ratio == 1.0) {
+ string filename = cached_item_->filename().absFileName();
+ size_t idx = filename.find_last_of('.');
+ if (idx != string::npos && idx > 3) {
+ if (filename.substr(idx - 3, 3) == "@2x") {
+ params_.pixel_ratio = 2.0;
+ }
+ }
+ }
bool const success = image_->setPixmap(params_);
if (status_ != WaitingToLoad)
return;
+ if (cached_item_->tryDisplayFormat()) {
+ status_ = Loaded;
+ createPixmap();
+ return;
+ }
+
LoaderQueue::get().touch(cached_item_);
}