Blob Blame Raw


// System includes
#include "tsystem.h"
#include "timagecache.h"

// Geometry
#include "tgeometry.h"

// Image
#include "tiio.h"
#include "timageinfo.h"
#include "trop.h"
#include "tropcm.h"

// Sound
#include "tsop.h"
#include "tsound.h"

// Strings
#include "tconvert.h"

// File-related includes
#include "tfilepath.h"
#include "tfiletype.h"
#include "filebrowsermodel.h"
#include "fileviewerpopup.h"

// OpenGL
#include "tgl.h"
#include "tvectorgl.h"
#include "tvectorrenderdata.h"

// Qt helpers
#include "toonzqt/gutil.h"
#include "toonzqt/imageutils.h"

// App-Stage includes
#include "tapp.h"
#include "toutputproperties.h"
#include "toonz/txsheethandle.h"
#include "toonz/txshsimplelevel.h"
#include "toonz/levelproperties.h"
#include "toonz/tscenehandle.h"
#include "toonz/toonzscene.h"
#include "toonz/sceneproperties.h"
#include "toonz/txshlevelhandle.h"
#include "toonz/tcamera.h"
#include "toonz/preferences.h"
#include "toonz/tproject.h"

// Image painting
#include "toonz/imagepainter.h"

// Preview
#include "previewfxmanager.h"

// Panels
#include "pane.h"

// recent files
#include "mainwindow.h"

// Other widgets
#include "toonzqt/flipconsole.h"
#include "toonzqt/dvdialog.h"
#include "filmstripselection.h"
#include "castselection.h"
#include "histogrampopup.h"

// Qt includes
#include <QApplication>
#include <QDesktopWidget>
#include <QSettings>
#include <QPainter>
#include <QDialogButtonBox>
#include <QAbstractButton>
#include <QLabel>
#include <QRadioButton>
#include <QSlider>
#include <QButtonGroup>
#include <QToolBar>
#include <QMainWindow>
#include <QUrl>
#include <QObject>
#include <QDesktopServices>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QPushButton>

#include <stdint.h>  // for uintptr_t

#ifdef _WIN32
#include "avicodecrestrictions.h"
#endif

#include "flipbook.h"

//-----------------------------------------------------------------------------

using namespace ImageUtils;

namespace {
QString getShortcut(const char *id) {
  return QString::fromStdString(
      CommandManager::instance()->getShortcutFromId(id));
}
}  // namespace

//-----------------------------------------------------------------------------

namespace {
/* inline TRect getImageBounds(const TImageP& img)
  {
    if(TRasterImageP ri= img)
      return ri->getRaster()->getBounds();
    else if(TToonzImageP ti= img)
      return ti->getRaster()->getBounds();
    else
    {
      TVectorImageP vi= img;
      return convert(vi->getBBox());
    }
  }*/

//-----------------------------------------------------------------------------

inline TRectD getImageBoundsD(const TImageP &img) {
  if (TRasterImageP ri = img)
    return TRectD(0, 0, ri->getRaster()->getLx(), ri->getRaster()->getLy());
  else if (TToonzImageP ti = img)
    return TRectD(0, 0, ti->getSize().lx, ti->getSize().ly);
  else {
    TVectorImageP vi = img;
    return vi->getBBox();
  }
}
}  // namespace

//=============================================================================
/*! \class FlipBook
                \brief The FlipBook class provides to view level images.

                Inherits \b QWidget.

                The object is composed of grid layout \b QGridLayout which
   contains an image
                viewer \b ImageViewer and a double button bar. It is possible
   decide widget
                title and which button bar show by setting QString and bool in
   constructor.

                You can set level to show in FlipBook using \b setLevel(); and
   call
                onDrawFrame() to show FlipBook current frame. The current frame
   can be
                set directly using setCurrentFrame(int index) or using slot
   methods connected
                to button bar.

    \sa FlipBookPool class.
*/
FlipBook::FlipBook(QWidget *parent, QString viewerTitle,
                   std::vector<int> flipConsoleButtonMask, UCHAR flags,
                   bool isColorModel)  //, bool showOnlyPlayBackgroundButton)
    : QWidget(parent),
      m_viewerTitle(viewerTitle),
      m_levelNames(),
      m_levels(),
      m_playSound(false),
      m_snd(0),
      m_player(0)
      //, m_doCompare(false)
      ,
      m_currentFrameToSave(0),
      m_lw(),
      m_lr(),
      m_loadPopup(0),
      m_savePopup(0),
      m_shrink(1),
      m_isPreviewFx(false),
      m_previewedFx(0),
      m_previewXsh(0),
      m_previewUpdateTimer(this),
      m_xl(0),
      m_title1(),
      m_poolIndex(-1),
      m_freezed(false),
      m_loadbox(),
      m_dim(),
      m_loadboxes(),
      m_freezeButton(0),
      m_flags(flags) {
  setAcceptDrops(true);
  setFocusPolicy(Qt::StrongFocus);

  // flipConsoleButtonMask = flipConsoleButtonMask & ~FlipConsole::eSubCamera;

  ImageUtils::FullScreenWidget *fsWidget =
      new ImageUtils::FullScreenWidget(this);

  m_imageViewer = new ImageViewer(
      fsWidget, this,
      std::find(flipConsoleButtonMask.begin(), flipConsoleButtonMask.end(),
                FlipConsole::eHisto) == flipConsoleButtonMask.end());
  fsWidget->setWidget(m_imageViewer);

  setFocusProxy(m_imageViewer);
  m_title = m_viewerTitle;
  m_imageViewer->setIsColorModel(isColorModel);

  // layout
  QVBoxLayout *mainLayout = new QVBoxLayout(this);
  mainLayout->setMargin(0);
  mainLayout->setSpacing(0);
  {
    mainLayout->addWidget(fsWidget, 1);
    m_flipConsole = new FlipConsole(
        mainLayout, flipConsoleButtonMask, true, 0,
        (viewerTitle == "") ? "FlipConsole" : viewerTitle, this, !isColorModel);
    mainLayout->addWidget(m_flipConsole);
  }
  setLayout(mainLayout);

  // signal-slot connection
  bool ret = connect(m_flipConsole, SIGNAL(buttonPressed(FlipConsole::EGadget)),
                     this, SLOT(onButtonPressed(FlipConsole::EGadget)));

  m_flipConsole->setFrameRate(TApp::instance()
                                  ->getCurrentScene()
                                  ->getScene()
                                  ->getProperties()
                                  ->getOutputProperties()
                                  ->getFrameRate());

  mainLayout->addWidget(m_flipConsole);

  m_previewUpdateTimer.setSingleShot(true);

  ret = ret && connect(parentWidget(), SIGNAL(closeButtonPressed()), this,
                       SLOT(onCloseButtonPressed()));
  ret = ret && connect(parentWidget(), SIGNAL(doubleClick(QMouseEvent *)), this,
                       SLOT(onDoubleClick(QMouseEvent *)));
  ret = ret && connect(&m_previewUpdateTimer, SIGNAL(timeout()), this,
                       SLOT(performFxUpdate()));

  assert(ret);

  m_viewerTitle = (m_viewerTitle.isEmpty()) ? tr("Flipbook") : m_viewerTitle;
  parentWidget()->setWindowTitle(m_viewerTitle);
}

//-----------------------------------------------------------------------------
/*! add freeze button to the flipbook. called from the function
   PreviewFxManager::openFlipBook.
        this button will hide for re-use, at onCloseButtonPressed
*/
void FlipBook::addFreezeButtonToTitleBar() {
  // If there is the button already, then reuse it.
  if (m_freezeButton) {
    m_freezeButton->show();
    return;
  }

  // If there is not, then newly make it
  TPanel *panel = qobject_cast<TPanel *>(parentWidget());
  if (panel) {
    TPanelTitleBar *titleBar = panel->getTitleBar();
    m_freezeButton           = new TPanelTitleBarButton(
        titleBar, ":Resources/pane_freeze_off.svg",
        ":Resources/pane_freeze_over.svg", ":Resources/pane_freeze_on.svg");
    m_freezeButton->setToolTip("Freeze");
    titleBar->add(QPoint(-64, 0), m_freezeButton);
    connect(m_freezeButton, SIGNAL(toggled(bool)), this, SLOT(freeze(bool)));
    QPoint p(titleBar->width() - 64, 0);
    m_freezeButton->move(p);
    m_freezeButton->show();
  }
}

//-----------------------------------------------------------------------------

void FlipBook::freeze(bool on) {
  if (on)
    freezePreview();
  else
    unfreezePreview();
}

//-----------------------------------------------------------------------------

void FlipBook::focusInEvent(QFocusEvent *e) {
  m_flipConsole->makeCurrent();
  QWidget::focusInEvent(e);
}

//-----------------------------------------------------------------------------

namespace {

enum { eBegin, eIncrement, eEnd };

static DVGui::ProgressDialog *Pd = 0;

class ProgressBarMessager final : public TThread::Message {
public:
  int m_choice;
  int m_val;
  QString m_str;
  ProgressBarMessager(int choice, int val, const QString &str = "")
      : m_choice(choice), m_val(val), m_str(str) {}
  void onDeliver() override {
    switch (m_choice) {
    case eBegin:
      if (!Pd)
        Pd = new DVGui::ProgressDialog(
            QObject::tr("Saving previewed frames...."), QObject::tr("Cancel"),
            0, m_val);
      else
        Pd->setMaximum(m_val);
      Pd->show();
      break;
    case eIncrement:
      if (Pd->wasCanceled()) {
        delete Pd;
        Pd = 0;
      } else {
        // if (m_val==Pd->maximum()) Pd->hide();
        Pd->setValue(m_val);
      }
      break;
    case eEnd: {
      DVGui::info(m_str);
      delete Pd;
      Pd = 0;
    } break;
    default:
      assert(false);
    }
  }

  TThread::Message *clone() const override {
    return new ProgressBarMessager(*this);
  }
};

}  // namespace

//=============================================================================

LoadImagesPopup::LoadImagesPopup(FlipBook *flip)
    : FileBrowserPopup(tr("Load Images"), Options(WITH_APPLY_BUTTON),
                       tr("Append"), new QFrame(0))
    , m_flip(flip)
    , m_minFrame(0)
    , m_maxFrame(1000000)
    , m_step(1)
    , m_shrink(1) {
  QFrame *frameRangeFrame = (QFrame *)m_customWidget;

  frameRangeFrame->setObjectName("customFrame");
  frameRangeFrame->setFrameStyle(QFrame::StyledPanel);
  // frameRangeFrame->setFixedHeight(30);

  m_fromField   = new DVGui::LineEdit(this);
  m_toField     = new DVGui::LineEdit(this);
  m_stepField   = new DVGui::LineEdit("1", this);
  m_shrinkField = new DVGui::LineEdit("1", this);

  // Define the append/load filter types
  m_appendFilterTypes << "3gp"
                      << "mov"
                      << "jpg"
                      << "png"
                      << "tga"
                      << "tif"
                      << "tiff"
                      << "bmp"
                      << "sgi"
                      << "rgb"
                      << "nol";

#ifdef _WIN32
  m_appendFilterTypes << "avi";
#endif

  m_loadFilterTypes << "tlv"
                    << "pli" << m_appendFilterTypes;
  m_appendFilterTypes << "psd";

  // layout
  QHBoxLayout *frameRangeLayout = new QHBoxLayout();
  frameRangeLayout->setMargin(5);
  frameRangeLayout->setSpacing(5);
  {
    frameRangeLayout->addStretch(1);

    frameRangeLayout->addWidget(new QLabel(tr("From:")), 0);
    frameRangeLayout->addWidget(m_fromField, 0);

    frameRangeLayout->addSpacing(5);

    frameRangeLayout->addWidget(new QLabel(tr("To:")), 0);
    frameRangeLayout->addWidget(m_toField, 0);

    frameRangeLayout->addSpacing(10);

    frameRangeLayout->addWidget(new QLabel(tr("Step:")), 0);
    frameRangeLayout->addWidget(m_stepField, 0);

    frameRangeLayout->addSpacing(5);

    frameRangeLayout->addWidget(new QLabel(tr("Shrink:")));
    frameRangeLayout->addWidget(m_shrinkField, 0);
  }
  frameRangeFrame->setLayout(frameRangeLayout);

  // Make signal-slot connections
  bool ret = true;

  ret = ret && connect(m_fromField, SIGNAL(editingFinished()), this,
                       SLOT(onEditingFinished()));
  ret = ret && connect(m_toField, SIGNAL(editingFinished()), this,
                       SLOT(onEditingFinished()));
  ret = ret && connect(m_stepField, SIGNAL(editingFinished()), this,
                       SLOT(onEditingFinished()));
  ret = ret && connect(m_shrinkField, SIGNAL(editingFinished()), this,
                       SLOT(onEditingFinished()));

  ret = ret && connect(this, SIGNAL(filePathClicked(const TFilePath &)),
                       SLOT(onFilePathClicked(const TFilePath &)));

  assert(ret);

  setOkText(tr("Load"));

  setWindowTitle(tr("Load / Append Images"));
}

//-----------------------------------------------------------------------------

void LoadImagesPopup::onEditingFinished() {
  int val;
  val    = m_fromField->text().toInt();
  m_from = (val < m_minFrame) ? m_minFrame : val;
  val    = m_toField->text().toInt();
  m_to   = (val > m_maxFrame) ? m_maxFrame : val;

  if (m_to < m_from) m_to = m_from;

  val      = m_stepField->text().toInt();
  m_step   = (val < 1) ? 1 : val;
  val      = m_shrinkField->text().toInt();
  m_shrink = (val < 1) ? 1 : val;

  m_fromField->setText(QString::number(m_from));
  m_toField->setText(QString::number(m_to));
  m_stepField->setText(QString::number(m_step));
  m_shrinkField->setText(QString::number(m_shrink));
}

//-----------------------------------------------------------------------------

bool LoadImagesPopup::execute() { return doLoad(false); }

//-----------------------------------------------------------------------------
/*! Append images with apply button
*/
bool LoadImagesPopup::executeApply() { return doLoad(true); }

//-----------------------------------------------------------------------------

bool LoadImagesPopup::doLoad(bool append) {
  if (m_selectedPaths.empty()) return false;

  ::viewFile(*m_selectedPaths.begin(), m_from, m_to, m_step, m_shrink, 0,
             m_flip, append);

  // register recent files
  std::set<TFilePath>::const_iterator pt;
  for (pt = m_selectedPaths.begin(); pt != m_selectedPaths.end(); ++pt) {
    RecentFiles::instance()->addFilePath(
        toQString(
            TApp::instance()->getCurrentScene()->getScene()->decodeFilePath(
                (*pt))),
        RecentFiles::Flip);
  }
  return true;
}

//-----------------------------------------------------------------------------

void LoadImagesPopup::onFilePathClicked(const TFilePath &fp) {
  TLevel::Iterator it;
  TLevelP level;
  TLevelReaderP lr;

  if (fp == TFilePath()) goto clear;

  lr = TLevelReaderP(fp);
  if (!lr) goto clear;

  level = lr->loadInfo();

  if (!level || level->getFrameCount() == 0) goto clear;

  it = level->begin();
  m_to, m_from = it->first.getNumber();

  for (; it != level->end(); ++it) m_to = it->first.getNumber();

  if (m_from == -2 && m_to == -2) m_from = m_to = 1;

  m_minFrame = m_from;
  m_maxFrame = m_to;
  m_fromField->setText(QString::number(m_from));
  m_toField->setText(QString::number(m_to));
  return;

clear:

  m_minFrame = 0;
  m_maxFrame = 10000000;
  m_fromField->setText("");
  m_toField->setText("");
}

//=============================================================================

SaveImagesPopup::SaveImagesPopup(FlipBook *flip)
    : FileBrowserPopup(tr("Save Flipbook Images")), m_flip(flip) {
  setOkText(tr("Save"));
}

bool SaveImagesPopup::execute() {
  if (m_selectedPaths.empty()) return false;

  return m_flip->doSaveImages(*m_selectedPaths.begin());
}

//=============================================================================
void FlipBook::loadImages() {
  if (!m_loadPopup) {
    m_loadPopup = new LoadImagesPopup(this);  //, frameRangeFrame);
    // move the initial folder to the project root
    m_loadPopup->setFolder(
        TProjectManager::instance()->getCurrentProjectPath().getParentDir());
  }

  m_loadPopup->show();
  m_loadPopup->raise();
  m_loadPopup->activateWindow();
}

//=============================================================================

bool FlipBook::canAppend() {
  // Images can be appended if:
  //  a) There is a name (in particular, an extension) representing currently
  //  held ones.
  //  b) This flipbook is not holding a preview (inappropriate and problematic).
  //  c) The level has no palette. Otherwise, appended images may have a
  //  different palette.
  return !m_levels.empty() && !m_previewedFx && !m_palette;
}

//=============================================================================

void FlipBook::saveImages() {
  if (!m_savePopup) m_savePopup = new SaveImagesPopup(this);

  // initialize the default path every time
  TOutputProperties *op = TApp::instance()
                              ->getCurrentScene()
                              ->getScene()
                              ->getProperties()
                              ->getOutputProperties();
  m_savePopup->setFolder(op->getPath().getParentDir());
  m_savePopup->setFilename(op->getPath().withFrame().withoutParentDir());

  m_savePopup->show();
  m_savePopup->raise();
  m_savePopup->activateWindow();
}

//=============================================================================

FlipBook::~FlipBook() {
  if (m_loadPopup) delete m_loadPopup;
  if (m_savePopup) delete m_savePopup;
}

//=============================================================================

bool FlipBook::doSaveImages(TFilePath fp) {
  QStringList formats;
  TLevelWriter::getSupportedFormats(formats, true);
  Tiio::Writer::getSupportedFormats(formats, true);

  ToonzScene *scene = TApp::instance()->getCurrentScene()->getScene();
  TOutputProperties *outputSettings =
      scene->getProperties()->getOutputProperties();

  std::string ext = fp.getType();

  // Open a notice that the previewFx is rendered in 8bpc regardless of the
  // output settings.
  if (m_isPreviewFx && outputSettings->getRenderSettings().m_bpp == 64) {
    QString question =
        "Save previewed images :\nImages will be saved in 8 bit per channel "
        "with this command.\nDo you want to save images?";
    int ret =
        DVGui::MsgBox(question, QObject::tr("Save"), QObject::tr("Cancel"), 0);
    if (ret == 2 || ret == 0) return false;
  }

#ifdef _WIN32
  if (ext == "avi") {
    TPropertyGroup *props = outputSettings->getFileFormatProperties(ext);
    std::string codecName = props->getProperty(0)->getValueAsString();
    TDimension res        = scene->getCurrentCamera()->getRes();
    if (!AviCodecRestrictions::canWriteMovie(::to_wstring(codecName), res)) {
      QString msg(
          QObject::tr("The resolution of the output camera does not fit with "
                      "the options chosen for the output file format."));
      DVGui::warning(msg);
      return false;
    }
  }
#endif

  if (ext == "") {
    ext = outputSettings->getPath().getType();
    fp  = fp.withType(ext);
  }
  if (fp.getName() == "") {
    DVGui::warning(
        tr("The file name cannot be empty or contain any of the following "
           "characters:(new line)  \\ / : * ? \"  |"));
    return false;
  }

  if (!formats.contains(QString::fromStdString(ext))) {
    DVGui::warning(
        tr("It is not possible to save because the selected file format is not "
           "supported."));
    return false;
  }

  int from, to, step;
  m_flipConsole->getFrameRange(from, to, step);

  if (m_currentFrameToSave != 0) {
    DVGui::info("Already saving!");
    return true;
  }

  if (TFileType::getInfo(fp) == TFileType::RASTER_IMAGE || ext == "pct" ||
      ext == "pic" || ext == "pict")  // pct e' un formato"livello" (ha i
                                      // settings di quicktime) ma fatto di
                                      // diversi frames
    fp = fp.withFrame(TFrameId::EMPTY_FRAME);

  fp          = scene->decodeFilePath(fp);
  bool exists = TFileStatus(fp.getParentDir()).doesExist();
  if (!exists) {
    try {
      TFilePath parent = fp.getParentDir();
      TSystem::mkDir(parent);
      DvDirModel::instance()->refreshFolder(parent.getParentDir());
    } catch (TException &e) {
      DVGui::error("Cannot create " + toQString(fp.getParentDir()) + " : " +
                   QString(::to_string(e.getMessage()).c_str()));
      return false;
    } catch (...) {
      DVGui::error("Cannot create " + toQString(fp.getParentDir()));
      return false;
    }
  }

  if (TSystem::doesExistFileOrLevel(fp)) {
    QString question(tr("File %1 already exists.\nDo you want to overwrite it?")
                         .arg(toQString(fp)));
    int ret = DVGui::MsgBox(question, QObject::tr("Overwrite"),
                            QObject::tr("Cancel"));
    if (ret == 2) return false;
  }

  try {
    m_lw = TLevelWriterP(fp,
                         outputSettings->getFileFormatProperties(fp.getType()));
  } catch (...) {
    DVGui::error("It is not possible to save Flipbook content.");
    return false;
  }

  m_lw->setFrameRate(outputSettings->getFrameRate());

  m_currentFrameToSave = 1;

  ProgressBarMessager(eBegin, m_framesCount).sendBlocking();

  QTimer::singleShot(50, this, SLOT(saveImage()));
  return true;
}

//-----------------------------------------------------------------------------

void FlipBook::saveImage() {
  static int savedFrames = 0;

  assert(Pd);
  int from, to, step;

  m_flipConsole->getFrameRange(from, to, step);

  for (; m_currentFrameToSave <= m_framesCount; m_currentFrameToSave++) {
    ProgressBarMessager(eIncrement, m_currentFrameToSave).sendBlocking();
    if (!Pd) break;

    int actualFrame = from + (m_currentFrameToSave - 1) * step;
    TImageP img     = getCurrentImage(actualFrame);
    if (!img) continue;
    TImageWriterP writer = m_lw->getFrameWriter(TFrameId(actualFrame));
    bool failureOnSaving = false;
    if (!writer) continue;
    try {
      writer->save(img);
    } catch (...) {
      QString str(tr("It is not possible to save Flipbook content."));
      ProgressBarMessager(eEnd, 0, str).send();
      m_currentFrameToSave = 0;
      m_lw                 = TLevelWriterP();
      savedFrames          = 0;
      return;
    }
    savedFrames++;
    //		if (!m_pb->changeFraction(m_currentFrameToSave,
    // TApp::instance()->getCurrentXsheet()->getXsheet()->getFrameCount()))
    //			break;
    m_currentFrameToSave++;

    QTimer::singleShot(50, this, SLOT(saveImage()));
    return;
  }

  QString str = tr("Saved %1 frames out of %2 in %3")
                    .arg(std::to_string(savedFrames).c_str())
                    .arg(std::to_string(m_framesCount).c_str())
                    .arg(::to_string(m_lw->getFilePath()).c_str());

  if (!Pd) str = "Canceled! " + str;

  ProgressBarMessager(eEnd, 0, str).send();

  m_currentFrameToSave = 0;
  m_lw                 = TLevelWriterP();
  savedFrames          = 0;
}

//=============================================================================

void FlipBook::onButtonPressed(FlipConsole::EGadget button) {
  switch (button) {
  case FlipConsole::eSound:
    m_playSound = !m_playSound;
    break;

  case FlipConsole::eHisto:
    m_imageViewer->showHistogram();
    break;

  case FlipConsole::eSaveImg: {
    TRect loadbox = m_loadbox;
    m_loadbox     = TRect();
    TImageP img   = getCurrentImage(m_flipConsole->getCurrentFrame());
    m_loadbox     = loadbox;
    if (!img) {
      DVGui::warning(tr("There are no rendered images to save."));
      return;
    } else if ((TVectorImageP)img) {
      DVGui::warning(
          tr("It is not possible to take or compare snapshots for Toonz vector "
             "levels."));
      return;
    }
    TRasterImageP ri(img);
    TToonzImageP ti(img);
    TImageP clonedImg;
    if (ri)
      clonedImg = TRasterImageP(ri->getRaster()->clone());
    else
      clonedImg = TToonzImageP(ti->getRaster()->clone(), ti->getSavebox());
    TImageCache::instance()->add(QString("TnzCompareImg"), clonedImg);
    break;
  }

  case FlipConsole::eCompare:
    if ((TVectorImageP)getCurrentImage(m_flipConsole->getCurrentFrame())) {
      DVGui::warning(
          tr("It is not possible to take or compare snapshots for Toonz vector "
             "levels."));
      m_flipConsole->setChecked(FlipConsole::eCompare, false);
      return;
    }
    break;

  case FlipConsole::eSave:
    saveImages();
    break;
  }
}

//=============================================================================
// FlipBookPool
//-----------------------------------------------------------------------------

/*! \class FlipBookPool
                \brief The FlipBookPool class is used to store used flipbook
   viewers.

    Flipbooks are generally intended as temporary but friendly floating widgets,
    that gets displayed when a rendered scene or image needs to be shown.
    Since a user may require that the geometry of a flipbook is to be remembered
    between rendering tasks - perhaps even between different Toonz sessions -
    flipbooks are always stored for later use in a \b FlipBookPool class.

    This class implements the basical features to \b pop a flipbook from the
   pool
    or \b push a used one; plus, it provides the \b save and \b load functions
    for persistent storage between Toonz sessions.

    \sa FlipBook class.
*/

FlipBookPool::FlipBookPool() : m_overallFlipCount(0) {
  qRegisterMetaType<ImagePainter::VisualSettings>(
      "ImagePainter::VisualSettings");
}

//-----------------------------------------------------------------------------

FlipBookPool::~FlipBookPool() {}

//-----------------------------------------------------------------------------

FlipBookPool *FlipBookPool::instance() {
  static FlipBookPool poolInstance;
  return &poolInstance;
}

//-----------------------------------------------------------------------------

void FlipBookPool::push(FlipBook *flipbook) {
  m_pool.insert(pair<int, FlipBook *>(flipbook->getPoolIndex(), flipbook));
}

//-----------------------------------------------------------------------------

//! Extracts the first unused flipbook from the flipbook pool.
//! If all known flipbooks are shown, allocates a new flipbook with the
//! first unused flipbook geometry in a geometry pool.
//! Again, if all recorded geometry are used by some existing flipbook, a
//! default geometry is used.
FlipBook *FlipBookPool::pop() {
  FlipBook *flipbook;
  TPanel *panel;

  TMainWindow *currentRoom = TApp::instance()->getCurrentRoom();

  if (m_pool.empty()) {
    panel = TPanelFactory::createPanel(currentRoom, "FlipBook");
    panel->setFloating(true);

    flipbook = static_cast<FlipBook *>(panel->widget());

    // Set geometry
    static int x = 0, y = 0;
    if (m_geometryPool.empty()) {
      panel->setGeometry(x += 50, y += 50, 400, 300);
      flipbook->setPoolIndex(m_overallFlipCount);
      m_overallFlipCount++;
    } else {
      flipbook->setPoolIndex(m_geometryPool.begin()->first);
      QRect geometry(m_geometryPool.begin()->second);
      panel->setGeometry(geometry);
      if ((geometry & QApplication::desktop()->availableGeometry(panel))
              .isEmpty())
        panel->move(x += 50, y += 50);
      m_geometryPool.erase(m_geometryPool.begin());
    }
  } else {
    flipbook = m_pool.begin()->second;
    panel    = (TPanel *)flipbook->parent();
    m_pool.erase(m_pool.begin());
  }

  // The panel need to be added to currentRoom's layout control.
  currentRoom->addDockWidget(panel);
  panel->raise();
  panel->show();

  return flipbook;
}

//-----------------------------------------------------------------------------

//! Saves the content of this flipbook pool.
void FlipBookPool::save() const {
  QSettings history(toQString(m_historyPath), QSettings::IniFormat);
  history.clear();

  history.setValue("count", m_overallFlipCount);

  history.beginGroup("flipbooks");

  std::map<int, FlipBook *>::const_iterator it;
  for (it = m_pool.begin(); it != m_pool.end(); ++it) {
    history.beginGroup(QString::number(it->first));
    TPanel *panel = static_cast<TPanel *>(it->second->parent());
    history.setValue("geometry", panel->geometry());
    history.endGroup();
  }

  std::map<int, QRect>::const_iterator jt;
  for (jt = m_geometryPool.begin(); jt != m_geometryPool.end(); ++jt) {
    history.beginGroup(QString::number(jt->first));
    history.setValue("geometry", jt->second);
    history.endGroup();
  }

  history.endGroup();
}

//-----------------------------------------------------------------------------

//! Loads the pool from input history
void FlipBookPool::load(const TFilePath &historyPath) {
  QSettings history(toQString(historyPath), QSettings::IniFormat);
  m_historyPath = historyPath;

  m_pool.clear();
  m_geometryPool.clear();

  m_overallFlipCount = history.value("count").toInt();

  history.beginGroup("flipbooks");

  QStringList flipBooks(history.childGroups());
  QStringList::iterator it;
  for (it = flipBooks.begin(); it != flipBooks.end(); ++it) {
    history.beginGroup(*it);

    // Retrieve flipbook geometry
    QVariant geom = history.value("geometry");

    // Insert geometry
    m_geometryPool.insert(pair<int, QRect>(it->toInt(), geom.toRect()));

    history.endGroup();
  }

  history.endGroup();
}

//=============================================================================

//! Returns the level frame number corresponding to passed flipbook index
TFrameId FlipBook::Level::flipbookIndexToLevelFrame(int index) {
  TLevel::Iterator it;
  int levelPos;
  if (m_incrementalIndexing) {
    levelPos = (index - 1) * m_step;
    it       = m_level->getTable()->find(m_fromIndex);
    advance(it, levelPos);
  } else {
    levelPos = m_fromIndex + (index - 1) * m_step;
    it       = m_level->getTable()->find(levelPos);
  }
  if (it == m_level->end()) return TFrameId();
  return it->first;
}

//-----------------------------------------------------------------------------

//! Returns the number of flipbook indexes available for this level
int FlipBook::Level::getIndexesCount() {
  return m_incrementalIndexing ? (m_level->getFrameCount() - 1) / m_step + 1
                               : (m_toIndex - m_fromIndex) / m_step + 1;
}

//=============================================================================

bool FlipBook::isSavable() const {
  if (m_levels.empty()) return false;

  for (int i = 0; i < m_levels.size(); i++)
    if (m_levels[i].m_fp != TFilePath() &&
        (m_levels[i].m_fp.getType() == "tlv" ||
         m_levels[i].m_fp.getType() == "pli"))
      return false;

  return true;
}

//=============================================================================

/*! Set the level contained in \b fp to FlipBook; if level exist show in image
                viewer its first frame, set current frame to 1.
                It's possible to change level palette, in fact if \b palette is
   different
                from 0 set level palette to \b palette.
*/
void FlipBook::setLevel(const TFilePath &fp, TPalette *palette, int from,
                        int to, int step, int shrink, TSoundTrack *snd,
                        bool append, bool isToonzOutput) {
  try {
    if (!append) {
      clearCache();
      m_levelNames.clear();
      m_levels.clear();
    }
    m_snd = 0;
    m_xl  = 0;

    m_flipConsole->enableProgressBar(false);
    m_flipConsole->setProgressBarStatus(0);
    m_flipConsole->enableButton(FlipConsole::eSound, snd != 0);
    m_flipConsole->enableButton(FlipConsole::eDefineLoadBox, true);
    m_flipConsole->enableButton(FlipConsole::eUseLoadBox, true);
    if (fp == TFilePath()) return;

    m_shrink = shrink;

    if (fp.getDots() == ".." && fp.getType() != "noext")
      m_levelNames.push_back(toQString(fp.withoutParentDir().withFrame()));
    else
      m_levelNames.push_back(toQString(fp.withoutParentDir()));

    m_snd = snd;

    if (TSystem::doesExistFileOrLevel(fp))  // is a  viewfile
    {
      // m_flipConsole->enableButton(FlipConsole::eCheckBg,
      // true);//fp.getType()!="pli");

      m_lr = TLevelReaderP(fp);

      bool supportsRandomAccess = doesSupportRandomAccess(fp, isToonzOutput);
      if (supportsRandomAccess) m_lr->enableRandomAccessRead(isToonzOutput);

      bool randomAccessRead    = supportsRandomAccess && isToonzOutput;
      bool incrementalIndexing = m_isPreviewFx ? true : false;

      TLevelP level = m_lr->loadInfo();

      if (!level || level->getFrameCount() == 0) {
        if (m_flags & eDontKeepFilesOpened) m_lr = TLevelReaderP();
        return;
      }

      // For the color model, get the reference fids from palette and delete
      // unneeded from the table
      if (m_imageViewer->isColorModel() && palette) {
        std::vector<TFrameId> fids = palette->getRefLevelFids();

        // when loading a single-frame, standard raster image into the
        // ColorModel, skip here.
        // If the fid == NO_FRAME(=-2), fids stores 0.
        if (!fids.empty() && !(fids.size() == 1 && fids[0].getNumber() == 0)) {
          // make the fid list to be deleted
          std::vector<TFrameId> deleteList;
          TLevel::Iterator it;
          for (it = level->begin(); it != level->end(); it++) {
            // If the fid is not included in the reference list, then delete it
            int i;
            for (i = 0; i < (int)fids.size(); i++) {
              if (fids[i].getNumber() == it->first.getNumber()) break;
            }
            if (i == fids.size()) {
              deleteList.push_back(it->first);
            }
          }
          // delete list items here
          if (!deleteList.empty())
            for (int i = 0; i < (int)deleteList.size(); i++)
              level->getTable()->erase(deleteList[i]);
        }
      }

      int fromIndex, toIndex;

      // in order to avoid that the current frame unexpectedly moves to 1 on the
      // Color Model once editing the style
      int current = -1;

      if (from == -1 && to == -1) {
        fromIndex = level->begin()->first.getNumber();
        toIndex   = (--level->end())->first.getNumber();
        if (m_imageViewer->isColorModel())
          current           = m_flipConsole->getCurrentFrame();
        incrementalIndexing = true;
      } else {
        TLevel::Iterator it = level->begin();

        // Adjust the frame interval to read. There is one special case:
        //  If the level read did not support random access, *AND* the level to
        //  show was just rendered,
        //  we have to assume that no level update happened, and the
        //  from-to-step infos are lost.
        //  So, shift the requested interval from 1 and place step to 1.
        fromIndex = from;
        toIndex   = to;
        if (isToonzOutput && !supportsRandomAccess) {
          fromIndex = 1;
          toIndex   = level->getFrameCount();
          step      = 1;
        }

        if (level->begin()->first.getNumber() != TFrameId::NO_FRAME) {
          fromIndex = std::max(fromIndex, level->begin()->first.getNumber());
          toIndex   = std::min(toIndex, (--level->end())->first.getNumber());
        } else {
          fromIndex           = level->begin()->first.getNumber();
          toIndex             = (--level->end())->first.getNumber();
          incrementalIndexing = true;
        }

        // Workaround to display simple background images when loading from
        // the right-click menu context
        fromIndex = std::min(fromIndex, toIndex);
      }

      Level levelToPush(level, fp, fromIndex, toIndex, step);
      levelToPush.m_randomAccessRead    = randomAccessRead;
      levelToPush.m_incrementalIndexing = incrementalIndexing;

      int formatIdx = Preferences::instance()->matchLevelFormat(fp);
      if (formatIdx >= 0 &&
          Preferences::instance()
              ->levelFormat(formatIdx)
              .m_options.m_premultiply) {
        levelToPush.m_premultiply = true;
      }

      m_levels.push_back(levelToPush);

      // Get the frames count to be shown in this flipbook level
      m_framesCount = levelToPush.getIndexesCount();

      assert(m_framesCount <= level->getFrameCount());

      // this value will be used in loadAndCacheAllTlvImages later
      int addingFrameAmount = m_framesCount;

      if (append && !m_levels.empty()) {
        int oldFrom, oldTo, oldStep;
        m_flipConsole->getFrameRange(oldFrom, oldTo, oldStep);
        assert(oldFrom == 1);
        assert(oldStep == 1);
        m_framesCount += oldTo;
      }

      m_flipConsole->setFrameRange(1, m_framesCount, 1, current);

      if (palette && level->getPalette() != palette) level->setPalette(palette);

      m_palette = level->getPalette();

      const TImageInfo *ii = m_lr->getImageInfo();

      if (ii) m_dim = TDimension(ii->m_lx / m_shrink, ii->m_ly / m_shrink);

      int levelFrameCount = 0;
      for (int lev = 0; lev < m_levels.size(); lev++)
        levelFrameCount += m_levels[lev].m_level->getFrameCount();

      if (levelFrameCount == 1)
        m_title = "  ::  1 Frame";
      else
        m_title = "  ::  " + QString::number(levelFrameCount) + " Frames";

      // color model does not concern about the pixel size
      if (ii && !m_imageViewer->isColorModel())
        m_title = m_title + "  ::  " + QString::number(ii->m_lx) + "x" +
                  QString::number(ii->m_ly) + " Pixels";

      if (shrink > 1)
        m_title = m_title + "  ::  " + "Shrink: " + QString::number(shrink);

      // when using the flip module, this signal is to show the loaded level
      // names in application's title bar
      QString arg = QString("Flip : %1").arg(m_levelNames[0]);
      emit imageLoaded(arg);

      // When viewing the tlv, try to cache all frames at the beginning.
      if (!m_imageViewer->isColorModel() && fp.getType() == "tlv" &&
          !(m_flags & eDontKeepFilesOpened) && !m_isPreviewFx) {
        loadAndCacheAllTlvImages(levelToPush,
                                 m_framesCount - addingFrameAmount + 1,  // from
                                 m_framesCount);                         // to
      }

      // An old archived bug says that simulatenous open for read of the same
      // tlv are not allowed...
      // if(m_lr && m_lr->getFilePath().getType()=="tlv")
      //  m_lr = TLevelReaderP();
    } else  // is a render
    {
      m_flipConsole->enableButton(FlipConsole::eCheckBg, true);
      m_previewedFx = 0;
      m_previewXsh  = 0;
      m_levels.clear();
      m_flipConsole->setFrameRange(from, to, step);

      m_framesCount = (to - from) / step + 1;
      m_title       = tr("Rendered Frames  ::  From %1 To %2  ::  Step %3")
                    .arg(QString::number(from))
                    .arg(QString::number(to))
                    .arg(QString::number(step));
      if (shrink > 1)
        m_title = m_title + tr("  ::  Shrink ") + QString::number(shrink);
    }

    // parentWidget()->setWindowTitle(m_title);
    m_imageViewer->setHistogramEnable(true);
    m_imageViewer->setHistogramTitle(m_levelNames[0]);
    m_flipConsole->enableButton(FlipConsole::eSave, isSavable());
    m_flipConsole->showCurrentFrame();
    if (m_flags & eDontKeepFilesOpened) m_lr = TLevelReaderP();
  } catch (...) {
    return;
  }
}

//-----------------------------------------------------------------------------

void FlipBook::setTitle(const QString &title) {
  m_viewerTitle = title;
  if (!m_previewedFx && !m_levelNames.empty())
    m_title = m_viewerTitle + "  ::  " + m_levelNames[0];
  else
    m_title = m_viewerTitle;
}

//-----------------------------------------------------------------------------

void FlipBook::setLevel(TXshSimpleLevel *xl) {
  try {
    clearCache();

    m_xl = xl;

    m_levelNames.push_back(QString::fromStdWString(xl->getName()));
    m_snd         = 0;
    m_previewedFx = 0;
    m_previewXsh  = 0;
    m_levels.clear();

    m_flipConsole->enableButton(FlipConsole::eSound, false);

    m_shrink = 1;
    int step = 1;

    m_framesCount = (m_xl->getFrameCount() - 1) / step + 1;
    m_flipConsole->setFrameRange(1, m_framesCount, step);
    m_flipConsole->enableProgressBar(false);
    m_flipConsole->setProgressBarStatus(0);
    m_palette = m_xl->getPalette();

    const LevelProperties *p = m_xl->getProperties();

    m_title = m_viewerTitle + "  ::  " + m_levelNames[0];

    if (m_framesCount == 1)
      m_title = m_title + "  ::  1 Frame";
    else
      m_title = m_title + "  ::  " + QString::number(m_framesCount) + " Frames";

    if (p) m_dim = p->getImageRes();

    if (p)
      m_title = m_title + "  ::  " + QString::number(p->getImageRes().lx) +
                "x" + QString::number(p->getImageRes().ly) + " Pixels";

    if (m_shrink > 1)
      m_title = m_title + "  ::  " + "Shrink: " + QString::number(m_shrink);

    m_imageViewer->setHistogramEnable(true);
    m_imageViewer->setHistogramTitle(m_levelNames[0]);

    m_flipConsole->showCurrentFrame();

  } catch (...) {
    return;
  }
}

//-----------------------------------------------------------------------------

void FlipBook::setLevel(TFx *previewedFx, TXsheet *xsh, TLevel *level,
                        TPalette *palette, int from, int to, int step,
                        int currentFrame, TSoundTrack *snd) {
  m_xl          = 0;
  m_previewedFx = previewedFx;
  m_previewXsh  = xsh;
  m_isPreviewFx = true;
  m_levels.clear();
  m_levels.push_back(Level(level, TFilePath(), from - 1, to - 1, step));
  m_levelNames.clear();
  m_levelNames.push_back(QString::fromStdString(level->getName()));
  m_title = m_viewerTitle;
  m_flipConsole->setFrameRange(from, to, step, currentFrame);
  m_flipConsole->enableProgressBar(true);

  m_flipConsole->enableButton(FlipConsole::eDefineLoadBox, false);
  m_flipConsole->enableButton(FlipConsole::eUseLoadBox, false);
  m_flipConsole->enableButton(FlipConsole::eSound, snd != 0);
  m_snd         = snd;
  m_framesCount = (to - from) / step + 1;

  m_imageViewer->setHistogramEnable(true);
  m_imageViewer->setHistogramTitle(m_levelNames[0]);
  m_flipConsole->showCurrentFrame();
}

//-----------------------------------------------------------------------------

TFx *FlipBook::getPreviewedFx() const {
  return m_isPreviewFx ? m_previewedFx.getPointer() : 0;
}

//-----------------------------------------------------------------------------

TXsheet *FlipBook::getPreviewXsheet() const {
  return m_isPreviewFx ? m_previewXsh.getPointer() : 0;
}

//-----------------------------------------------------------------------------

TRectD FlipBook::getPreviewedImageGeometry() const {
  if (!m_isPreviewFx) return TRectD();

  // Build viewer's geometry
  QRect viewerGeom(m_imageViewer->geometry());
  viewerGeom.adjust(-1, -1, 1, 1);
  TRectD viewerGeomD(viewerGeom.left(), viewerGeom.top(),
                     viewerGeom.right() + 1, viewerGeom.bottom() + 1);
  TPointD viewerCenter((viewerGeomD.x0 + viewerGeomD.x1) * 0.5,
                       (viewerGeomD.y0 + viewerGeomD.y1) * 0.5);

  // NOTE: The above adjust() is imposed to counter the geometry removal
  // specified in function
  // FlipBook::onDoubleClick.

  // Build viewer-to-camera affine
  TAffine viewToCam(m_imageViewer->getViewAff().inv() *
                    TTranslation(-viewerCenter));

  return viewToCam * viewerGeomD;
}

//-----------------------------------------------------------------------------

void FlipBook::schedulePreviewedFxUpdate() {
  if (m_previewedFx)
    m_previewUpdateTimer.start(
        1000);  // The effective fx update will happen in 1 msec.
}

//-----------------------------------------------------------------------------

void FlipBook::performFxUpdate() {
  // refresh only when the subcamera is active
  if (PreviewFxManager::instance()->isSubCameraActive(m_previewedFx))
    PreviewFxManager::instance()->refreshView(m_previewedFx);
}

//-----------------------------------------------------------------------------

void FlipBook::regenerate() {
  PreviewFxManager::instance()->reset(TFxP(m_previewedFx));
}

//-----------------------------------------------------------------------------

void FlipBook::regenerateFrame() {
  PreviewFxManager::instance()->reset(m_previewedFx, getCurrentFrame() - 1);
}

//-----------------------------------------------------------------------------

void FlipBook::clonePreview() {
  if (!m_previewedFx) return;

  FlipBook *newFlip =
      PreviewFxManager::instance()->showNewPreview(m_previewedFx, true);
  newFlip->m_imageViewer->setViewAff(m_imageViewer->getViewAff());
  PreviewFxManager::instance()->refreshView(m_previewedFx);
}

//-----------------------------------------------------------------------------

void FlipBook::freezePreview() {
  if (!m_previewedFx) return;

  PreviewFxManager::instance()->freeze(this);

  m_freezed = true;

  // sync the button state when triggered by shotcut
  if (m_freezeButton) m_freezeButton->setPressed(true);
}

//-----------------------------------------------------------------------------

void FlipBook::unfreezePreview() {
  if (!m_previewedFx) return;

  PreviewFxManager::instance()->unfreeze(this);

  m_freezed = false;

  // sync the button state when triggered by shotcut
  if (m_freezeButton) m_freezeButton->setPressed(false);
}

//-----------------------------------------------------------------------------

void FlipBook::setProgressBarStatus(const std::vector<UCHAR> *pbStatus) {
  m_flipConsole->setProgressBarStatus(pbStatus);
}

//-----------------------------------------------------------------------------

const std::vector<UCHAR> *FlipBook::getProgressBarStatus() const {
  return m_flipConsole->getProgressBarStatus();
}

//-----------------------------------------------------------------------------

void FlipBook::showFrame(int frame) {
  if (frame < 0) return;
  m_flipConsole->setCurrentFrame(frame);
  m_flipConsole->showCurrentFrame();
}

//-----------------------------------------------------------------------------

void FlipBook::playAudioFrame(int frame) {
  static bool first = true;
  static bool audioCardInstalled;
  if (!m_snd || !m_playSound) return;

  if (first) {
    audioCardInstalled = TSoundOutputDevice::installed();
    first              = false;
  }

  if (!audioCardInstalled) return;

  if (!m_player) {
    m_player = new TSoundOutputDevice();
    m_player->attach(this);
  }
  if (m_player) {
    // Flipbook does not currently support double fps - thus, casting to int in
    // soundtrack playback, too
    int fps = TApp::instance()
                  ->getCurrentScene()
                  ->getScene()
                  ->getProperties()
                  ->getOutputProperties()
                  ->getFrameRate();

    int samplePerFrame = int(m_snd->getSampleRate()) / fps;
    TINT32 firstSample = (frame - 1) * samplePerFrame;
    TINT32 lastSample  = firstSample + samplePerFrame;

    try {
      m_player->play(m_snd, firstSample, lastSample, false, false);
    } catch (TSoundDeviceException &e) {
      std::string msg;
      if (e.getType() == TSoundDeviceException::UnsupportedFormat) {
        try {
          TSoundTrackFormat fmt =
              m_player->getPreferredFormat(m_snd->getFormat());
          m_player->play(TSop::convert(m_snd, fmt), firstSample, lastSample,
                         false, false);
        } catch (TSoundDeviceException &ex) {
          throw TException(ex.getMessage());
          return;
        }
      }
    }
  }
}

//-----------------------------------------------------------------------------

TImageP FlipBook::getCurrentImage(int frame) {
  std::string id = "";
  TFrameId fid;
  TFilePath fp;

  bool randomAccessRead    = false;
  bool incrementalIndexing = false;
  bool premultiply         = false;
  if (m_xl)  // is an xsheet level
  {
    if (m_xl->getFrameCount() <= 0) return 0;
    return m_xl->getFrame(m_xl->index2fid(frame - 1), false);
  } else if (!m_levels.empty())  // is a viewfile or a previewFx
  {
    TLevelP level;
    QString levelName;
    int from, to, step;
    m_flipConsole->getFrameRange(from, to, step);

    int frameIndex = m_previewedFx ? ((frame - from) / step) + 1 : frame;

    int i = 0;
    // Search all subsequent levels on the flipbook and retrieve the one
    // containing the required frame
    for (i = 0; i < m_levels.size(); i++) {
      int frameIndexesCount = m_levels[i].getIndexesCount();
      if (frameIndex > 0 && frameIndex <= frameIndexesCount) break;
      frameIndex -= frameIndexesCount;
    }

    if (i == m_levels.size() || frame < 0) return 0;

    frame--;

    // Now, get the right frame from the level

    level               = m_levels[i].m_level;
    fp                  = m_levels[i].m_fp;  // fp=empty when previewing fx
    randomAccessRead    = m_levels[i].m_randomAccessRead;
    incrementalIndexing = m_levels[i].m_incrementalIndexing;
    levelName           = m_levelNames[i];
    fid                 = m_levels[i].flipbookIndexToLevelFrame(frameIndex);
    premultiply         = m_levels[i].m_premultiply;
    if (fid == TFrameId()) return 0;
    id = levelName.toStdString() + fid.expand(TFrameId::NO_PAD) +
         ((m_isPreviewFx) ? "" : ::to_string(this));

    if (!m_isPreviewFx)
      m_title1 = m_viewerTitle + " :: " + fp.withoutParentDir().withFrame(fid);
    else
      m_title1 = "";
  } else if (m_levelNames.empty())
    return 0;
  else  // is a render
    id = m_levelNames[0].toStdString() + std::to_string(frame);

  bool showSub = m_flipConsole->isChecked(FlipConsole::eUseLoadBox);

  if (TImageCache::instance()->isCached(id)) {
    TRect loadbox;
    std::map<std::string, TRect>::const_iterator it = m_loadboxes.find(id);
    if (it != m_loadboxes.end()) loadbox = it->second;

    // Resubmit the image to the cache as the 'last one' seen by the flipbook.
    // TImageCache::instance()->add(toString(m_poolIndex) + "lastFlipFrame",
    // img);
    // m_lastViewedFrame = frame+1;
    if ((showSub && m_loadbox == loadbox) || (!showSub && loadbox == TRect()))
      return TImageCache::instance()->get(id, false);
    else
      TImageCache::instance()->remove(id);
  }
  if (fp != TFilePath() && !m_isPreviewFx) {
    int lx = 0, oriLx = 0;
    // TLevelReaderP lr(fp);
    if (!m_lr || (fp != m_lr->getFilePath())) {
      m_lr = TLevelReaderP(fp);
      m_lr->enableRandomAccessRead(randomAccessRead);
    }
    if (!m_lr) return 0;
    // try to get image info only when loading tlv or pli as it is quite time
    // consuming
    if (fp.getType() == "tlv" || fp.getType() == "pli") {
      if (m_lr->getImageInfo()) lx = oriLx = m_lr->getImageInfo()->m_lx;
    }
    TImageReaderP ir = m_lr->getFrameReader(fid);
    ir->setShrink(m_shrink);
    if (m_loadbox != TRect() && showSub) {
      ir->setRegion(m_loadbox);
      lx = m_loadbox.getLx();
    }

    TImageP img = ir->load();

    if (img) {
      TRasterImageP ri = ((TRasterImageP)img);
      TToonzImageP ti  = ((TToonzImageP)img);
      if (premultiply) {
        if (ri)
          TRop::premultiply(ri->getRaster());
        else if (ti)
          TRop::premultiply(ti->getRaster());
      }

      // se e' stata caricata una sottoimmagine alcuni formati in realta'
      // caricano tutto il raster e fanno extract, non si ha quindi alcun
      // risparmio di occupazione di memoria; alloco un raster grande
      // giusto copio la region e butto quello originale.
      if (ri && showSub && m_loadbox != TRect() &&
          ri->getRaster()->getLx() == oriLx)  // questo serve perche' per avi e
                                              // mov la setRegion e'
                                              // completamente ignorata...
        ri->setRaster(ri->getRaster()->extract(m_loadbox)->clone());
      else if (ri && ri->getRaster()->getWrap() > ri->getRaster()->getLx())
        ri->setRaster(ri->getRaster()->clone());
      else if (ti && ti->getCMapped()->getWrap() > ti->getCMapped()->getLx())
        ti->setCMapped(ti->getCMapped()->clone());

      if ((fp.getType() == "tlv" || fp.getType() == "pli") && m_shrink > 1 &&
          (lx == 0 || (ri && ri->getRaster()->getLx() == lx) ||
           (ti && ti->getRaster()->getLx() == lx))) {
        if (ri)
          ri->setRaster(TRop::shrink(ri->getRaster(), m_shrink));
        else if (ti)
          ti->setCMapped(TRop::shrink(ti->getRaster(), m_shrink));
      }

      TPalette *palette = img->getPalette();
      if (m_palette && (!palette || palette != m_palette))
        img->setPalette(m_palette);
      TImageCache::instance()->add(id, img);
      m_loadboxes[id] = showSub ? m_loadbox : TRect();
    }

    // An old archived bug says that simulatenous open for read of the same tlv
    // are not allowed...
    // if(fp.getType()=="tlv")
    //  m_lr = TLevelReaderP();
    if (m_flags & eDontKeepFilesOpened) m_lr = TLevelReaderP();
    return img;
  } else if (fp == TFilePath() && m_isPreviewFx) {
    if (!TImageCache::instance()->isCached(id)) {
      /*string lastFrameCacheId(toString(m_poolIndex) + "lastFlipFrame");
if(TImageCache::instance()->isCached(lastFrameCacheId))
return TImageCache::instance()->get(lastFrameCacheId, false);
else*/
      return 0;
      // showFrame(m_lastViewedFrame);
    }
  }

  return 0;
}

//-----------------------------------------------------------------------------

/*! Set current level frame to image viewer. Add the view image in cache.
*/
void FlipBook::onDrawFrame(int frame, const ImagePainter::VisualSettings &vs) {
  try {
    m_imageViewer->setVisual(vs);

    TImageP img = getCurrentImage(frame);

    if (!img) return;

    m_imageViewer->setImage(img);
  } catch (...) {
    m_imageViewer->setImage(TImageP());
  }

  if (m_playSound && !vs.m_drawBlankFrame) playAudioFrame(frame);
}

//-----------------------------------------------------------------------------

void FlipBook::swapBuffers() { m_imageViewer->doSwapBuffers(); }

//-----------------------------------------------------------------------------

void FlipBook::changeSwapBehavior(bool enable) {
  m_imageViewer->changeSwapBehavior(enable);
}

//-----------------------------------------------------------------------------

void FlipBook::setLoadbox(const TRect &box) {
  m_loadbox =
      (m_dim.lx > 0) ? box * TRect(0, 0, m_dim.lx - 1, m_dim.ly - 1) : box;
}

//----------------------------------------------------------------

void FlipBook::clearCache() {
  TLevel::Iterator it;

  if (m_levelNames.empty()) return;
  int i;

  if (!m_levels.empty())  // is a viewfile
    for (i = 0; i < m_levels.size(); i++)
      for (it = m_levels[i].m_level->begin(); it != m_levels[i].m_level->end();
           ++it)
        TImageCache::instance()->remove(
            m_levelNames[i].toStdString() +
            std::to_string(it->first.getNumber()) +
            ((m_isPreviewFx) ? "" : ::to_string(this)));
  else {
    int from, to, step;
    m_flipConsole->getFrameRange(from, to, step);
    for (int i = from; i <= to; i += step)  // is a render
      // color model may loading a part of frames in the level
      if (m_imageViewer->isColorModel() && m_palette) {
        // get the actually-loaded frame list
        std::vector<TFrameId> fids(m_palette->getRefLevelFids());
        if (!fids.empty() && (int)fids.size() >= i) {
          int frame = fids[i - 1].getNumber();
          TImageCache::instance()->remove(m_levelNames[0].toStdString() +
                                          std::to_string(frame));
        } else {
          TImageCache::instance()->remove(m_levelNames[0].toStdString() +
                                          std::to_string(i));
        }
      } else
        TImageCache::instance()->remove(m_levelNames[0].toStdString() +
                                        std::to_string(i));
  }
}

//-----------------------------------------------------------------------------

void FlipBook::onCloseButtonPressed() {
  m_flipConsole->setActive(false);
  closeFlipBook(this);

  reset();

  // hide freeze button in preview fx window
  if (m_freezeButton) {
    m_freezeButton->hide();
    m_imageViewer->setIsRemakingPreviewFx(false);
  }

  // Return the flipbook to the pool in case it was popped from it.
  if (m_poolIndex >= 0) FlipBookPool::instance()->push(this);
}

//-----------------------------------------------------------------------------

void ImageViewer::showHistogram() {
  if (!m_isHistogramEnable) return;
  if (m_histogramPopup->isVisible())
    m_histogramPopup->raise();
  else {
    m_histogramPopup->setImage(getImage());
    m_histogramPopup->show();
  }
}

//-----------------------------------------------------------------------------

void FlipBook::dragEnterEvent(QDragEnterEvent *e) {
  const QMimeData *mimeData = e->mimeData();
  bool isResourceDrop       = acceptResourceDrop(mimeData->urls());
  if (!isResourceDrop &&
      !mimeData->hasFormat("application/vnd.toonz.drawings") &&
      !mimeData->hasFormat(CastItems::getMimeFormat()))
    return;

  for (const QUrl &url : mimeData->urls()) {
    TFilePath fp(url.toLocalFile().toStdWString());
    std::string type = fp.getType();
    if (type == "tzp" || type == "tzu" || type == "tnz" || type == "scr" ||
        type == "mesh")
      return;
  }
  if (mimeData->hasFormat(CastItems::getMimeFormat())) {
    const CastItems *items = dynamic_cast<const CastItems *>(mimeData);
    if (!items) return;

    int i;
    for (i = 0; i < items->getItemCount(); i++) {
      CastItem *item      = items->getItem(i);
      TXshSimpleLevel *sl = item->getSimpleLevel();
      if (!sl) return;
    }
  }

  if (isResourceDrop) {
    // Force CopyAction
    e->setDropAction(Qt::CopyAction);
    // For files, don't accept original proposed action in case it's a move
    e->accept();
  } else
    e->acceptProposedAction();
}

//-----------------------------------------------------------------------------

void FlipBook::dropEvent(QDropEvent *e) {
  const QMimeData *mimeData = e->mimeData();
  bool isResourceDrop       = acceptResourceDrop(mimeData->urls());
  if (mimeData->hasUrls()) {
    for (const QUrl &url : mimeData->urls()) {
      TFilePath fp(url.toLocalFile().toStdWString());
      if (TFileType::getInfo(fp) != TFileType::UNKNOW_FILE) setLevel(fp);
      if (isResourceDrop) {
        // Force CopyAction
        e->setDropAction(Qt::CopyAction);
        // For files, don't accept original proposed action in case it's a move
        e->accept();
      } else
        e->acceptProposedAction();
      return;
    }
  } else if (mimeData->hasFormat(
                 "application/vnd.toonz.drawings"))  // drag-drop from film
                                                     // strip
  {
    TFilmstripSelection *s =
        dynamic_cast<TFilmstripSelection *>(TSelection::getCurrent());
    TXshSimpleLevel *sl = TApp::instance()->getCurrentLevel()->getSimpleLevel();
    if (!s || !sl) return;
    TXshSimpleLevel *newSl = new TXshSimpleLevel();
    newSl->setScene(sl->getScene());
    newSl->setType(sl->getType());
    newSl->setPalette(sl->getPalette());
    newSl->clonePropertiesFrom(sl);
    const std::set<TFrameId> &fids = s->getSelectedFids();
    std::set<TFrameId>::const_iterator it;
    for (it = fids.begin(); it != fids.end(); ++it)
      newSl->setFrame(*it, sl->getFrame(*it, false)->cloneImage());
    setLevel(newSl);
  } else if (mimeData->hasFormat(
                 CastItems::getMimeFormat()))  // Drag-Drop from castviewer
  {
    const CastItems *items = dynamic_cast<const CastItems *>(mimeData);
    if (!items) return;

    int i;
    for (i = 0; i < items->getItemCount(); i++) {
      CastItem *item = items->getItem(i);
      if (TXshSimpleLevel *sl = item->getSimpleLevel()) setLevel(sl);
    }
  }
  m_flipConsole->makeCurrent();
}

//-------------------------------------------------------------------

void FlipBook::reset() {
  if (!m_isPreviewFx)  // The cache is owned by the PreviewFxManager otherwise
    clearCache();
  else
    PreviewFxManager::instance()->detach(this);

  m_levelNames.clear();
  m_levels.clear();
  m_framesCount = 0;
  m_palette     = 0;
  m_imageViewer->setImage(TImageP());
  m_imageViewer->hideHistogram();
  m_isPreviewFx = false;
  m_previewedFx = 0;
  m_previewXsh  = 0;
  m_freezed     = false;
  // sync the freeze button
  if (m_freezeButton) m_freezeButton->setPressed(false);
  m_flipConsole->pressButton(FlipConsole::ePause);
  if (m_playSound) m_flipConsole->pressButton(FlipConsole::eSound);
  if (m_player) m_player->stop();
  if (m_flipConsole->isChecked(FlipConsole::eDefineLoadBox))
    m_flipConsole->pressButton(FlipConsole::eDefineLoadBox);
  if (m_flipConsole->isChecked(FlipConsole::eUseLoadBox))
    m_flipConsole->pressButton(FlipConsole::eUseLoadBox);

  m_flipConsole->enableButton(FlipConsole::eDefineLoadBox, true);
  m_flipConsole->enableButton(FlipConsole::eUseLoadBox, true);

  m_lr = TLevelReaderP();

  m_dim     = TDimension();
  m_loadbox = TRect();
  m_loadboxes.clear();
  // m_lastViewedFrame = -1;
  // TImageCache::instance()->remove(toString(m_poolIndex) + "lastFlipFrame");

  m_flipConsole->enableProgressBar(false);
  m_flipConsole->setProgressBarStatus(0);
  m_flipConsole->setFrameRange(1, 1, 1);

  setTitle(tr("Flipbook"));
  parentWidget()->setWindowTitle(m_title);

  update();
}

//-------------------------------------------------------------------

void FlipBook::showEvent(QShowEvent *e) {
  TSceneHandle *sceneHandle = TApp::instance()->getCurrentScene();
  connect(sceneHandle, SIGNAL(sceneChanged()), m_imageViewer, SLOT(update()));
  // for updating the blank frame button
  if (!m_imageViewer->isColorModel()) {
    connect(sceneHandle, SIGNAL(preferenceChanged(const QString &)),
            m_flipConsole, SLOT(onPreferenceChanged(const QString &)));
    m_flipConsole->onPreferenceChanged("");
  }
  m_flipConsole->setActive(true);
  m_imageViewer->update();
}

//-------------------------------------------------------------------

void FlipBook::hideEvent(QHideEvent *e) {
  TSceneHandle *sceneHandle = TApp::instance()->getCurrentScene();
  disconnect(sceneHandle, SIGNAL(sceneChanged()), m_imageViewer,
             SLOT(update()));
  if (!m_imageViewer->isColorModel()) {
    disconnect(sceneHandle, SIGNAL(preferenceChanged(const QString &)),
               m_flipConsole, SLOT(onPreferenceChanged(const QString &)));
  }
  m_flipConsole->setActive(false);
}

//-----------------------------------------------------------------------------

void FlipBook::resizeEvent(QResizeEvent *e) { schedulePreviewedFxUpdate(); }

//-----------------------------------------------------------------------------

void FlipBook::adaptGeometry(const TRect &interestingImgRect,
                             const TRect &imgRect) {
  TRectD imgRectD(imgRect.x0, imgRect.y0, imgRect.x1 + 1, imgRect.y1 + 1);
  TRectD interestingImgRectD(interestingImgRect.x0, interestingImgRect.y0,
                             interestingImgRect.x1 + 1,
                             interestingImgRect.y1 + 1);

  TAffine toWidgetRef(m_imageViewer->getImgToWidgetAffine(imgRectD));
  TRectD interestGeomD(toWidgetRef * interestingImgRectD);
  TRectD imageGeomD(toWidgetRef * imgRectD);
  adaptWidGeometry(
      TRect(tceil(interestGeomD.x0), tceil(interestGeomD.y0),
            tfloor(interestGeomD.x1) - 1, tfloor(interestGeomD.y1) - 1),
      TRect(tceil(imageGeomD.x0), tceil(imageGeomD.y0),
            tfloor(imageGeomD.x1) - 1, tfloor(imageGeomD.y1) - 1),
      true);
}

//-----------------------------------------------------------------------------
/*! When Fx preview is called without the subcamera, render the full region
    of camera by resize flipbook and zoom-out the rendered image.
*/
void FlipBook::adaptGeometryForFullPreview(const TRect &imgRect) {
  TRectD imgRectD(imgRect.x0, imgRect.y0, imgRect.x1 + 1, imgRect.y1 + 1);

  // Get screen geometry
  TPanel *panel = static_cast<TPanel *>(parentWidget());
  if (!panel->isFloating()) return;
  QDesktopWidget *desk =
      static_cast<QApplication *>(QApplication::instance())->desktop();
  QRect screenGeom = desk->availableGeometry(panel);

  while (1) {
    TAffine toWidgetRef(m_imageViewer->getImgToWidgetAffine(imgRectD));
    TRectD imageGeomD(toWidgetRef * imgRectD);
    TRect imageGeom(tceil(imageGeomD.x0) - 1, tceil(imageGeomD.y0) - 1,
                    tfloor(imageGeomD.x1) + 1, tfloor(imageGeomD.y1) + 1);

    if (imageGeom.getLx() <= screenGeom.width() &&
        imageGeom.getLy() <= screenGeom.height()) {
      adaptWidGeometry(imageGeom, imageGeom, false);
      break;
    } else
      m_imageViewer->zoomQt(false, false);
  }
}

//-----------------------------------------------------------------------------

//! Adapts panel geometry to that of passed rect.
void FlipBook::adaptWidGeometry(const TRect &interestWidGeom,
                                const TRect &imgWidGeom, bool keepPosition) {
  TPanel *panel = static_cast<TPanel *>(parentWidget());
  if (!panel->isFloating()) return;

  // Extract image position in screen coordinates
  QRect qgeom(interestWidGeom.x0, interestWidGeom.y0, interestWidGeom.getLx(),
              interestWidGeom.getLy());
  QRect interestGeom(m_imageViewer->mapToGlobal(qgeom.topLeft()),
                     m_imageViewer->mapToGlobal(qgeom.bottomRight()));
  qgeom = QRect(imgWidGeom.x0, imgWidGeom.y0, imgWidGeom.getLx(),
                imgWidGeom.getLy());
  QRect imageGeom(m_imageViewer->mapToGlobal(qgeom.topLeft()),
                  m_imageViewer->mapToGlobal(qgeom.bottomRight()));

  // qDebug("tgeom= [%d, %d] x [%d, %d]", tgeom.x0, tgeom.x1, tgeom.y0,
  // tgeom.y1);
  // qDebug("imagegeom= [%d, %d] x [%d, %d]", imageGeom.left(),
  // imageGeom.right(),
  //  imageGeom.top(), imageGeom.bottom());

  // Get screen geometry
  QDesktopWidget *desk =
      static_cast<QApplication *>(QApplication::instance())->desktop();
  QRect screenGeom = desk->availableGeometry(panel);

  // Get panel margin measures
  QRect margins;
  QRect currView(m_imageViewer->geometry());
  currView.moveTo(m_imageViewer->mapToGlobal(currView.topLeft()));
  QRect panelGeom(panel->geometry());

  margins.setLeft(panelGeom.left() - currView.left());
  margins.setRight(panelGeom.right() - currView.right());
  margins.setTop(panelGeom.top() - currView.top());
  margins.setBottom(panelGeom.bottom() - currView.bottom());

  // Build the minimum flipbook geometry. Adjust the interesting geometry
  // according to it.
  QSize flipMinimumSize(panel->minimumSize());
  flipMinimumSize -=
      QSize(margins.right() - margins.left(), margins.bottom() - margins.top());
  QSize minAddition(
      tceil(std::max(0, flipMinimumSize.width() - interestGeom.width()) * 0.5),
      tceil(std::max(0, flipMinimumSize.height() - interestGeom.height()) *
            0.5));
  interestGeom.adjust(-minAddition.width(), -minAddition.height(),
                      minAddition.width(), minAddition.height());

  // Translate to keep the current view top-left corner, if required
  if (keepPosition) {
    QPoint shift(currView.topLeft() - interestGeom.topLeft());
    interestGeom.translate(shift);
    imageGeom.translate(shift);
  }

  // Intersect with the screen geometry
  QRect newViewerGeom(screenGeom);
  newViewerGeom.adjust(-margins.left(), -margins.top(), -margins.right(),
                       -margins.bottom());

  // when fx previewing in full size (i.e. keepPosition is false ),
  // try to translate geometry and keep the image inside the viewer as much as
  // posiible
  if (keepPosition)
    newViewerGeom &= interestGeom;
  else if (newViewerGeom.intersects(interestGeom)) {
    int d_ns = 0;
    int d_ew = 0;
    if (interestGeom.top() < newViewerGeom.top())
      d_ns = newViewerGeom.top() - interestGeom.top();
    else if (interestGeom.bottom() > newViewerGeom.bottom())
      d_ns = newViewerGeom.bottom() - interestGeom.bottom();
    if (interestGeom.left() < newViewerGeom.left())
      d_ew = newViewerGeom.left() - interestGeom.left();
    else if (interestGeom.right() > newViewerGeom.right())
      d_ew = newViewerGeom.right() - interestGeom.right();
    if (d_ns || d_ew) {
      interestGeom.translate(d_ew, d_ns);
      imageGeom.translate(d_ew, d_ns);
    }
    newViewerGeom &= interestGeom;
  }

  // qDebug("new Viewer= [%d, %d] x [%d, %d]", newViewerGeom.left(),
  // newViewerGeom.right(),
  //  newViewerGeom.top(), newViewerGeom.bottom());

  // Calculate the pan of content image in order to compensate for our geometry
  // change
  QPointF imageGeomCenter((imageGeom.left() + imageGeom.right() + 1) * 0.5,
                          (imageGeom.top() + imageGeom.bottom() + 1) * 0.5);
  QPointF newViewerGeomCenter(
      (newViewerGeom.left() + newViewerGeom.right() + 1) * 0.5,
      (newViewerGeom.top() + newViewerGeom.bottom() + 1) * 0.5);

  /*QPointF imageGeomCenter(
(imageGeom.width()) * 0.5,
(imageGeom.height()) * 0.5
);
QPointF newViewerGeomCenter(
(newViewerGeom.width()) * 0.5,
(newViewerGeom.height()) * 0.5
);*/

  // NOTE: If delta == (0,0) the image is at center. Typically happens when
  // imageGeom doesn't intersect
  // the screen geometry.
  QPointF delta(imageGeomCenter - newViewerGeomCenter);
  TAffine aff(m_imageViewer->getViewAff());
  aff.a13 = delta.x();
  aff.a23 = -delta.y();

  // Calculate new panel geometry
  newViewerGeom.adjust(margins.left(), margins.top(), margins.right(),
                       margins.bottom());

  // Apply changes
  m_imageViewer->setViewAff(aff);
  panel->setGeometry(newViewerGeom);
}

//-----------------------------------------------------------------------------

void FlipBook::onDoubleClick(QMouseEvent *me) {
  TImageP img(m_imageViewer->getImage());
  if (!img) return;

  TAffine toWidgetRef(m_imageViewer->getImgToWidgetAffine());
  TRectD pixGeomD(TScale(1.0 / (double)getDevPixRatio()) * toWidgetRef *
                  getImageBoundsD(img));
  // TRectD pixGeomD(toWidgetRef  * getImageBoundsD(img));
  TRect pixGeom(tceil(pixGeomD.x0), tceil(pixGeomD.y0), tfloor(pixGeomD.x1) - 1,
                tfloor(pixGeomD.y1) - 1);

  // NOTE: The previous line has ceils and floor inverted on purpose. The reason
  // is the following:
  // As the viewer's zoom level is arbitrary, the image is likely to have a not
  // integer geometry
  // with respect to the widget - the problem is, we cannot take the closest
  // integer rect ENCLOSING ours,
  // or the ImageViewer class adds blank lines on image rendering.
  // So, we do the converse - take the closest ENCLOSED one - eventually to be
  // compensated when
  // performing the inverse.

  adaptWidGeometry(pixGeom, pixGeom, false);
}

//-----------------------------------------------------------------------------

void FlipBook::minimize(bool doMinimize) {
  m_imageViewer->setVisible(!doMinimize);
  m_flipConsole->showHideAllParts(!doMinimize);
}

//-----------------------------------------------------------------------------
/*! When viewing the tlv, try to cache all frames at the beginning.
        NOTE : fromFrame and toFrame are frame numbers displayed on the flipbook
*/
void FlipBook::loadAndCacheAllTlvImages(Level level, int fromFrame,
                                        int toFrame) {
  TFilePath fp                                   = level.m_fp;
  if (!m_lr || (fp != m_lr->getFilePath())) m_lr = TLevelReaderP(fp);
  if (!m_lr) return;

  // show the wait cursor when loading a level with more than 50 frames
  if (toFrame - fromFrame > 50) QApplication::setOverrideCursor(Qt::WaitCursor);

  int lx = 0, oriLx = 0;
  if (m_lr->getImageInfo()) lx = oriLx = m_lr->getImageInfo()->m_lx;

  std::string fileName = toQString(fp.withoutParentDir()).toStdString();

  for (int f = fromFrame; f <= toFrame; f++) {
    TFrameId fid = level.flipbookIndexToLevelFrame(f);
    if (fid == TFrameId()) continue;

    std::string id =
        fileName + fid.expand(TFrameId::NO_PAD) + ::to_string(this);

    TImageReaderP ir = m_lr->getFrameReader(fid);
    ir->setShrink(m_shrink);

    TImageP img = ir->load();

    if (!img) continue;

    TToonzImageP ti = ((TToonzImageP)img);
    if (!ti) continue;

    if (ti->getCMapped()->getWrap() > ti->getCMapped()->getLx())
      ti->setCMapped(ti->getCMapped()->clone());
    if (m_shrink > 1 && (lx == 0 || ti->getRaster()->getLx() == lx))
      ti->setCMapped(TRop::shrink(ti->getRaster(), m_shrink));

    TPalette *palette = img->getPalette();

    if (m_palette && (!palette || palette != m_palette))
      img->setPalette(m_palette);

    TImageCache::instance()->add(id, img);
    m_loadboxes[id] = TRect();
  }

  m_lr = TLevelReaderP();

  // revert the cursor
  if (toFrame - fromFrame > 50) QApplication::restoreOverrideCursor();
}

//=============================================================================
// Utility

//-----------------------------------------------------------------------------

//! Displays the passed file on a Flipbook, supporting a wide range of options.
//! Possible options include:
//! \li The range, step and shrink parameters for the loaded level
//! \li A soundtrack to accompany the level's images
//! \li The flipbook where the file is to be opened. If none, a new one is
//! created.
//! \li Whether the level must replace an existing one on the flipbook, or it
//! must
//! rather be appended at its end
//! \li In case the file has a movie format and it is known to be a toonz
//! output,
//! some additional random access information may be retrieved (i.e. images may
//! map
//! to specific frames).
// returns pointer to the opened flipbook to control modality.
FlipBook *viewFile(const TFilePath &path, int from, int to, int step,
                   int shrink, TSoundTrack *snd, FlipBook *flipbook,
                   bool append, bool isToonzOutput) {
  // In case the step and shrink informations are invalid, load them from
  // preferences
  if (step == -1 || shrink == -1) {
    int _step = 1, _shrink = 1;
    Preferences::instance()->getViewValues(_shrink, _step);
    if (step == -1) step     = _step;
    if (shrink == -1) shrink = _shrink;
  }

  // Movie files must not have the ".." extension
  if ((path.getType() == "mov" || path.getType() == "avi" ||
       path.getType() == "3gp") &&
      path.isLevelName()) {
    DVGui::warning(QObject::tr("%1  has an invalid extension format.")
                       .arg(QString::fromStdString(path.getLevelName())));
    return NULL;
  }

  // Windows Screen Saver - avoid
  if (path.getType() == "scr") return NULL;

  // Avi and movs may be viewed by an external viewer, depending on preferences
  if (path.getType() == "mov" || path.getType() == "avi" && !flipbook) {
    QString str;
    QSettings().value("generatedMovieViewEnabled", str);
    if (str.toInt() != 0) {
      TSystem::showDocument(path);
      return NULL;
    }
  }

  // Retrieve a blank flipbook
  if (!flipbook)
    flipbook = FlipBookPool::instance()->pop();
  else if (!append)
    flipbook->reset();

  // Assign the passed level with associated infos
  flipbook->setLevel(path, 0, from, to, step, shrink, snd, append,
                     isToonzOutput);
  return flipbook;
}

//-----------------------------------------------------------------------------