/* Images
 *
 * GtkImage is used to display an image; the image can be in a number of formats.
 * Typically, you load an image into a GdkPixbuf, then display the pixbuf.
 *
 * This demo code shows some of the more obscure cases, in the simple
 * case a call to gtk_image_new_from_file() is all you need.
 *
 * If you want to put image data in your program as a C variable,
 * use the make-inline-pixbuf program that comes with GTK+.
 * This way you won't need to depend on loading external files, your
 * application binary can be self-contained.
 */

#include <gtkmm.h>

class Example_Images : public Gtk::Window
{
public:
  Example_Images();
  ~Example_Images() override;

protected:
  virtual void start_progressive_loading();

  //signal handler:
  virtual bool on_timeout();
  virtual void on_loader_area_prepared();
  virtual void on_loader_area_updated(int x, int y, int width, int height);

  //Member widgets:
  Gtk::Box m_VBox;
  Gtk::Label m_Label_Image, m_Label_Animation, m_Label_Progressive;
  Gtk::Frame m_Frame_Image, m_Frame_Animation, m_Frame_Progressive;
  Gtk::Image m_Image_Progressive;
  Glib::RefPtr<Gdk::PixbufLoader> m_refPixbufLoader;

  Glib::RefPtr<Gio::InputStream> m_image_stream;
};

//Called by DemoWindow;
Gtk::Window* do_images()
{
  return new Example_Images();
}

Example_Images::Example_Images()
:
  m_VBox                (Gtk::ORIENTATION_VERTICAL, 8),
  m_image_stream        ()
{
  set_title("Images");
  set_border_width(8);

  m_VBox.set_border_width(8);
  add(m_VBox);

  /* Image */

  m_Label_Image.set_markup("<u>Image loaded from a file</u>");
  m_VBox.pack_start(m_Label_Image, Gtk::PACK_SHRINK);

  m_Frame_Image.set_shadow_type(Gtk::SHADOW_IN);

  m_Frame_Image.set_halign(Gtk::ALIGN_CENTER);
  m_Frame_Image.set_valign(Gtk::ALIGN_CENTER);
  m_VBox.pack_start(m_Frame_Image, Gtk::PACK_SHRINK);

  Gtk::Image* pImage = Gtk::manage(new Gtk::Image());
  pImage->set_from_resource("/images/gtk-logo-rgb.gif");
  m_Frame_Image.add(*pImage);

  /* Animation */

  m_Label_Animation.set_markup("<u>Animation loaded from a file</u>");
  m_VBox.pack_start(m_Label_Animation, Gtk::PACK_SHRINK);

  m_Frame_Animation.set_shadow_type(Gtk::SHADOW_IN);

  m_Frame_Animation.set_halign(Gtk::ALIGN_CENTER);
  m_Frame_Animation.set_valign(Gtk::ALIGN_CENTER);
  m_VBox.pack_start(m_Frame_Animation, Gtk::PACK_SHRINK);

  pImage = Gtk::manage(new Gtk::Image());
  pImage->set_from_resource("/images/floppybuddy.gif");
  m_Frame_Animation.add(*pImage);

  /* Progressive */

  m_Label_Progressive.set_markup("<u>Progressive image loading</u>");
  m_VBox.pack_start(m_Label_Progressive, Gtk::PACK_SHRINK);

  m_Frame_Progressive.set_shadow_type(Gtk::SHADOW_IN);

  m_VBox.pack_start(m_Frame_Progressive, Gtk::PACK_SHRINK);

  /* Create an empty image for now; the progressive loader
   * will create the pixbuf and fill it in.
   */
  m_Frame_Progressive.add(m_Image_Progressive);

  start_progressive_loading();

  show_all();
}

Example_Images::~Example_Images()
{
  try
  {
    if(m_refPixbufLoader)
      m_refPixbufLoader->close();
  }
  catch(const Gdk::PixbufError&)
  {
    // ignore errors
  }
}

void Example_Images::start_progressive_loading()
{
  Glib::signal_timeout().connect(sigc::mem_fun(*this, &Example_Images::on_timeout), 150);
}

bool Example_Images::on_timeout()
{
  /* This shows off fully-paranoid error handling, so looks scary.
   * You could factor out the error handling code into a nice separate
   * function to make things nicer.
   */

  if(m_image_stream)
  {
    guint8 buf[256];
    gsize bytes_read = 0;

    try
    {
      bytes_read = m_image_stream->read(buf, sizeof(buf));
    }
    catch(const Glib::Error& error)
    {

      Glib::ustring strMsg = "Failure reading image 'alphatest.png': ";
      strMsg += error.what();

      Gtk::MessageDialog dialog(strMsg, false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_CLOSE);
      dialog.run();

      m_image_stream.reset();

      return false; // uninstall the timeout
    }

    try
    {
      m_refPixbufLoader->write(buf, bytes_read);
    }
    catch(const Glib::Error& error)
    {
      Glib::ustring strMsg = "Failed to load image: ";
      strMsg += error.what();

      Gtk::MessageDialog dialog(strMsg, false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_CLOSE);
      dialog.run();

      m_image_stream.reset();

      return false; // uninstall the timeout
    }

    if(bytes_read == 0)
    {
      m_image_stream.reset();

      /* Errors can happen on close, e.g. if the image
       * file was truncated we'll know on close that
       * it was incomplete.
       */

      try
      {
        m_refPixbufLoader->close();
      }
      catch(const Glib::Error& error)
      {
        Glib::ustring strMsg = "Failed to close image: ";
        strMsg += error.what();

        Gtk::MessageDialog dialog(strMsg, false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_CLOSE);
        dialog.run();

        m_refPixbufLoader.reset();

        return false; // uninstall the timeout
      }

      m_refPixbufLoader.reset();
    }
  }
  else
  {
    try
    {
      m_image_stream = Gio::Resource::open_stream_global("/images/alphatest.png");
    }
    catch(const Glib::Error& error)
    {
      Glib::ustring strMsg = "Unable to open image 'alphatest.png': ";
      strMsg += error.what();

      Gtk::MessageDialog dialog(strMsg, false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_CLOSE);
      dialog.run();

      return false; // uninstall the timeout
    }

    if(m_refPixbufLoader)
    {
      m_refPixbufLoader->close();

      m_refPixbufLoader.reset();
    }

    m_refPixbufLoader = Gdk::PixbufLoader::create();

    m_refPixbufLoader->signal_area_prepared().connect(
        sigc::mem_fun(*this, &Example_Images::on_loader_area_prepared));

    m_refPixbufLoader->signal_area_updated().connect(
        sigc::mem_fun(*this, &Example_Images::on_loader_area_updated));
  }

  return true; // leave timeout installed
}

void Example_Images::on_loader_area_prepared()
{
  const Glib::RefPtr<Gdk::Pixbuf> refPixbuf = m_refPixbufLoader->get_pixbuf();

  /* Avoid displaying random memory contents, since the pixbuf
   * isn't filled in yet.
   */
  refPixbuf->fill(0xaaaaaaff);
  m_Image_Progressive.set(refPixbuf);
}

void Example_Images::on_loader_area_updated(int/*x*/, int/*y*/, int/*width*/, int/*height*/)
{
  /* We know the pixbuf inside the Gtk::Image has changed, but the image
   * itself doesn't know this; so give it a hint by setting the pixbuf
   * again. Queuing a redraw used to be sufficient, but nowadays Gtk::Image
   * uses GtkIconHelper which caches the pixbuf state and will just redraw
   * from the cache.
   * If we wanted to be really efficient, we could use a drawing area or
   * something instead of a Gtk::Image, so we could control the exact
   * position of the pixbuf on the display, then we could queue a draw
   * for only the updated area of the image.
   */
  Glib::RefPtr<Gdk::Pixbuf> refPixbuf = m_Image_Progressive.get_pixbuf();
  m_Image_Progressive.set(refPixbuf);
}

