/* Copyright (C) 2007, 2011 and 2013 Chris Vine

The library comprised in this file or of which this file is part is
distributed by Chris Vine under the GNU Lesser General Public
License as follows:

   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, version 2.1, for more details.

   You should have received a copy of the GNU Lesser General Public
   License, version 2.1, along with this library (see the file LGPL.TXT
   which came with this source code package in the c++-gtk-utils
   sub-directory); if not, write to the Free Software Foundation, Inc.,
   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

*/

#ifndef CGU_TEXT_PRINTMANAGER_H
#define CGU_TEXT_PRINTMANAGER_H

#include <vector>
#include <string>
#include <memory>

#include <c++-gtk-utils/cgu_config.h>

#ifdef CGU_USE_GTK
#include <gtk/gtk.h>
#include <pango/pango-layout.h>
#include <glib-object.h>
#endif

#include <c++-gtk-utils/intrusive_ptr.h>
#include <c++-gtk-utils/shared_handle.h>
#include <c++-gtk-utils/gobj_handle.h>
#include <c++-gtk-utils/notifier.h>
#include <c++-gtk-utils/mutex.h>


namespace Cgu {

#if defined(DOXYGEN_PARSING) || defined(CGU_USE_GTK)

#ifndef DOXYGEN_PARSING

// deleter functor for SharedHandle
class TPMPangoLayoutIterFree {
public:
  void operator()(PangoLayoutIter* obj_p) {
    if (obj_p) {
      pango_layout_iter_free(obj_p);
    }
  }
};

typedef SharedHandle<PangoLayoutIter*, TPMPangoLayoutIterFree> TPMPangoLayoutIterSharedHandle;

#endif // DOXYGEN_PARSING

/**
 * @class TextPrintManager text_print_manager.h c++-gtk-utils/text_print_manager.h
 * @brief A class to print plain text using the GTK+ print system.
 *
 * The TextPrintManager class prints text (say, from a GtkTextBuffer
 * object or from a plain text file) using the GTK+ printer interface.
 * To obtain a TextPrintManager object, call
 * TextPrintManager::create_manager().  TextPrintManager::set_text()
 * passes the text to be printed.  The text passed with
 * TextPrintManager::set_text() must form valid UTF-8 (all ASCII
 * characters form valid UTF-8).  To print the text entered, call
 * TextPrintManager::print().  The TextPrintManager::view() and
 * TextPrintManager::print_to_file() methods are also available.
 *
 * The TextPrintManager::page_setup() method can be used as a callback
 * to display a document page setup dialog prior to printing, and this
 * method should only be called in the thread in which the main GTK+
 * event loop runs.  It is a static method (a TextPrintManager object
 * does not need to have been created before it is called).
 *
 * Once TextPrintManager::print(), TextPrintManager::view() or
 * TextPrintManager::print_to_file() has been called, the
 * TextPrintManager class owns a reference to itself and so manages
 * its own lifetime - so once one of the methods has been called it
 * doesn't matter if the IntrusivePtr object returned by
 * TextPrintManager::create_manager() goes out of scope (however, once
 * TextPrintManager::print(), TextPrintManager::view() or
 * TextPrintManager::print_to_file() has been called, the object will
 * not be deleted until both printing has completed or failed AND the
 * IntrusivePtr object returned by TextPrintManager::create_manager()
 * has gone out of scope or has been reset()).
 *
 * Normally, a user would probably only want to use any one
 * TextPrintManager object to print a single print job (it has been
 * designed with that in mind).  Nevertheless, if a reference to the
 * object is kept alive via an active IntrusivePtr object, it can be
 * used to print more than one print job.  However, if that is done it
 * is not possible to called TextPrintManager::print()
 * TextPrintManager::view(), TextPrintManager::print_to_file() or
 * TextPrintManager::set_text() until the previous print job (if any)
 * has been dispatched to the GTK+ print system (or cancelled).  If
 * the TextPrintManager object is not ready because it is in the
 * middle of handling an earlier print job, then the call to
 * TextPrintManager::print(), TextPrintManager::view(),
 * TextPrintManager::print_to_file() or TextPrintManager::set_text()
 * will return false; if however the new print job was successfully
 * started or text successfully set, they will return true.  It is not
 * necessary to check the return value of these methods for the first
 * print job despatched by any one TextPrintManager object.
 *
 * TextPrintManager::print_to_file() will also return false if no file
 * name to which to print was specified.
 *
 * This class is not compiled into the library if the library is built
 * with the \--without-gtk configuration option.
 */

class TextPrintManager: public IntrusiveLockCounter {
  enum Mode {print_mode, view_mode, file_mode} mode;

  Thread::Mutex mutex;
  GtkWindow* parent_p;
  GobjHandle<PangoLayout> text_layout_h;
  int current_line;
  TPMPangoLayoutIterSharedHandle current_line_iter_h;
  std::unique_ptr<std::string> text_u;
  std::string file_name;
  std::vector<int> pages;
  Notifier print_notifier;
  std::string font_family;
  int font_size;
  bool ready;
  bool cancelled_when_drawing;

  GobjHandle<GtkWidget> font_entry_h;
  GobjHandle<GtkWidget> font_size_spin_button_h;

  static GobjHandle<GtkPrintSettings> print_settings_h;
  static GobjHandle<GtkPageSetup> page_setup_h;
  static std::string default_font_family;
  static int default_font_size;

  void paginate(GtkPrintContext*);
  void print_text();
  void begin_print_impl(GtkPrintOperation*, GtkPrintContext*);
  void draw_page_impl(GtkPrintOperation*, GtkPrintContext*, int);
  GObject* create_custom_widget_impl(GtkPrintOperation*);
  static void strip(std::string&);

  // private constructor
  TextPrintManager() {}
public:
#ifndef DOXYGEN_PARSING
  // this helper class avoids exposing GObject callbacks with C
  // linkage to the global namespace
  class CB;
  friend class CB;
#endif
/**
 * This class cannot be copied: it is intended to be held by
 * IntrusivePtr.  The copy constructor is deleted.
 */
  TextPrintManager(const TextPrintManager&) = delete;

/**
 * This class cannot be copied: it is intended to be held by
 * IntrusivePtr.  The assignment operator is deleted.
 */
  TextPrintManager& operator=(const TextPrintManager&) = delete;

/**
 * Creates a new TextPrintManager object.  No GTK+/GDK functions are
 * called by this method, and it is thread safe provided that, if it
 * is called in a thread other than the one in which the GTK+ event
 * loop runs, Cgu::Notifier::init() has previously been called in the
 * GTK+ event loop thread.
 * @param parent The parent of the print dialog which will be created
 * by the TextPrintManager object (optional, NULL may be passed).
 * @param font_family If a particular font is wanted, then this can be
 * entered as the font_family and font_size arguments to this method..
 * If the default of an empty string for font_family is used, then
 * printing will use the previous font family setting (if any), or if
 * not a font of "Mono". The font passed in this method can in any
 * event be overridden from the "Print font" page in the print dialog.
 * @param font_size If a particular font is wanted, then this can be
 * entered as the font_family and font_size arguments to this method..
 * If the default of 0 for font_size is used, then printing will use
 * the previous font size setting (if any), or if not a size of
 * 10. The font passed in this method can in any event be overridden
 * from the "Print font" page in the print dialog.  If a font size is
 * passed as an argument, then the value must be 0 (default) or
 * between 8 and 24 (actual).
 * @exception std::bad_alloc This method might throw std::bad_alloc if
 * memory is exhausted and the system throws in that case.
 * @exception Cgu::Thread::MutexError This method might throw
 * Cgu::Thread::MutexError if initialisation of the contained mutex
 * fails.  (It is often not worth checking for this, as it means
 * either memory is exhausted or pthread has run out of other
 * resources to create new mutexes.)
 * @exception Cgu::PipeError This method might throw Cgu::PipeError if
 * the contained Notifier object is the first Notifier object in the
 * program to be constructed and Cgu::Notifier::init() has not
 * previously been called.
 */
  static Cgu::IntrusivePtr<Cgu::TextPrintManager> create_manager(GtkWindow* parent = 0,
								 const std::string& font_family = "",
								 int font_size = 0);

/**
 * Runs a page set-up dialog.  (If no settings are set using this
 * dialog, then page defaults will be used with a page margin of 15
 * millimetres.)  In a multi-threaded program, this must be called in
 * the thread in which the main GTK+ event loop runs.  If the program
 * by which it is called calls GTK+ directly in more than one thread
 * and thus employs gdk_threads_enter()/gdk_threads_leave() (rather
 * than, say, Cgu::Notifier or Cgu::Callback::post()), it must also be
 * surrounded by gdk_threads_enter()/gdk_threads_leave() if called
 * otherwise than in a GTK+ signal handler.  (The best approach
 * however is for a program only to address GTK+/GDK in the main
 * program thread, for which purpose this library provides various
 * functions and classes for inter-thread communication, such as
 * Cgu::Notifier and Cgu::Callback::post().)  It will not throw.
 * @param parent The parent of the page set-up dialog which will be
 * created by the TextPrintManager object (optional, NULL may be
 * passed).
 */
  static void page_setup(GtkWindow* parent = 0);

/**
 * Sets the text to be printed.  This method is thread-safe and may be
 * called in any thread.  No GTK+/GDK functions are called.  It will
 * not throw.
 * @param text The text to be printed.  It must be in valid UTF-8
 * format.  Ownership is taken of the text string (a move operation is
 * carried out) unless false is returned.
 * @return Returns true, unless the same TextPrintManager object is
 * being used to print more than one print job and when this method is
 * called it is already printing another print job (in which case it
 * returns false).  If it returns false, ownership is not taken of the
 * text string (it can be reused).
 * @note If the library is compiled using the \--with-auto-ptr
 * configuration option, then this function's signature is bool
 * set_text(std::auto_ptr<std::string>& text) in order to retain
 * compatibility with the 1.2 series of the library.
 */
  // for efficiency reasons (the string could hold a lot of text and we do not
  // want more copies than necessary hanging around) the text is passed by
  // reference (by std::unique_ptr<>).  We pass by std::unique_ptr so that the
  // string cannot be modified after it has been passed to the TextPrintManager
  // object - we take ownership of it
#ifdef CGU_USE_AUTO_PTR
  bool set_text(std::auto_ptr<std::string>& text);
#else
  bool set_text(std::unique_ptr<std::string>& text);
#endif

/**
 * Prints the text set with set_text().  This method is thread-safe
 * and may be called in any thread (it hands off its work to the main
 * program thread via a Cgu::Notifier object), unless the program by
 * which it is called calls GTK+ directly in more than one thread and
 * thus employs gdk_threads_enter()/gdk_threads_leave() (rather than,
 * say, Cgu::Notifier or Cgu::Callback::post()), in which case it must
 * be called in the main GUI thread only and surrounded by
 * gdk_threads_enter()/gdk_threads_leave() if called otherwise than in
 * a GTK+ signal handler.  (The best approach however is for a program
 * only to address GTK+/GDK in the main program thread, for which
 * purpose this library provides various functions and classes for
 * inter-thread communication, such as Cgu::Notifier and
 * Cgu::Callback::post().)
 * @return Returns true, unless the same TextPrintManager object is
 * being used to print more than one print job and when this method is
 * called it is already printing another print job (in which case it
 * returns false).
 * @exception std::bad_alloc This method might throw std::bad_alloc if
 * memory is exhausted and the system throws in that case, but only if
 * it is called in the main program thread.  Otherwise it will not
 * throw.
 */
  bool print();

/**
 * Provides a print pre-view of the text set with set_text().  This
 * method is thread-safe and may be called in any thread (it hands off
 * its work to the main program thread via a Cgu::Notifier object),
 * unless the program by which it is called calls GTK+ directly in
 * more than one thread and thus employs
 * gdk_threads_enter()/gdk_threads_leave() (rather than, say,
 * Cgu::Notifier or Cgu::Callback::post()), in which case it must be
 * called in the main GUI thread only and surrounded by
 * gdk_threads_enter()/gdk_threads_leave() if called otherwise than in
 * a GTK+ signal handler.  (The best approach however is for a program
 * only to address GTK+/GDK in the main program thread, for which
 * purpose this library provides various functions and classes for
 * inter-thread communication, such as Cgu::Notifier and
 * Cgu::Callback::post().)
 * @return Returns true, unless the same TextPrintManager object is
 * being used to print more than one print job and when this method is
 * called it is already printing another print job (in which case it
 * returns false).
 * @exception std::bad_alloc This method might throw std::bad_alloc if
 * memory is exhausted and the system throws in that case, but only if
 * it is called in the main program thread.  Otherwise it will not
 * throw.
 */
  bool view();

/**
 * Prints the text set with set_text() to file.  This method is
 * thread-safe and may be called in any thread (it hands off its work
 * to the main program thread via a Cgu::Notifier object), unless the
 * program by which it is called calls GTK+ directly in more than one
 * thread and thus employs gdk_threads_enter()/gdk_threads_leave()
 * (rather than, say, Cgu::Notifier or Cgu::Callback::post()), in
 * which case it must be called in the main GUI thread only and
 * surrounded by gdk_threads_enter()/gdk_threads_leave() if called
 * otherwise than in a GTK+ signal handler.  (The best approach
 * however is for a program only to address GTK+/GDK in the main
 * program thread, for which purpose this library provides various
 * functions and classes for inter-thread communication, such as
 * Cgu::Notifier and Cgu::Callback::post().)
 * @param filename The filename of the file to which the text is to be
 * printed.
 * @return Returns true, unless the same TextPrintManager object is
 * being used to print more than one print job and when this method is
 * called it is already printing another print job, or no filename to
 * print was specified (in either of which case it returns false).
 * @exception std::bad_alloc This method might throw std::bad_alloc if
 * memory is exhausted and the system throws in that case.
 */
  bool print_to_file(const char* filename);

/**  
 * The destructor will not throw.  It is thread safe (the
 * TextPrintManager object may be destroyed in any thread), and does
 * not call any GTK+/GDK functions.
 */
  ~TextPrintManager();

#ifdef CGU_USE_GLIB_MEMORY_SLICES_NO_COMPAT
  CGU_GLIB_MEMORY_SLICES_FUNCS
#endif
};

#endif // CGU_USE_GTK

} // namespace Cgu

#endif // TEXT_PRINTMANAGER_H
