]> git.lyx.org Git - features.git/commitdiff
The graphics inset now has:
authorAngus Leeming <leeming@lyx.org>
Wed, 27 Feb 2002 09:59:52 +0000 (09:59 +0000)
committerAngus Leeming <leeming@lyx.org>
Wed, 27 Feb 2002 09:59:52 +0000 (09:59 +0000)
* lazy loading (don't try and load the image until a request to draw it
  is received).
* asynchronous conversion to a loadable format.
* asynchronous loading if the image loader supports it (it doesn't).
* "simple" cropping, rotating and scaling (in that order) of the image
  on the LyX screen.
* display in color, grayscale or monochrome.

We also have a forked calls dialog, although it isn't very exciting
yet because only the graphics cache makes use of the forked call controller.

git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@3591 a592a061-630c-0410-9148-cb99ea01b6c8

51 files changed:
lib/ChangeLog
lib/ui/default.ui
src/ChangeLog
src/Painter.C
src/Painter.h
src/PainterBase.h
src/frontends/controllers/ChangeLog
src/frontends/controllers/ControlForks.C [new file with mode: 0644]
src/frontends/controllers/ControlForks.h [new file with mode: 0644]
src/frontends/controllers/ControlGraphics.C
src/frontends/controllers/GUI.h
src/frontends/controllers/Makefile.am
src/frontends/xforms/ChangeLog
src/frontends/xforms/Dialogs.C
src/frontends/xforms/FormForks.C [new file with mode: 0644]
src/frontends/xforms/FormForks.h [new file with mode: 0644]
src/frontends/xforms/FormPreferences.C
src/frontends/xforms/Makefile.am
src/frontends/xforms/form_forks.C [new file with mode: 0644]
src/frontends/xforms/form_forks.h [new file with mode: 0644]
src/frontends/xforms/forms/form_forks.fd [new file with mode: 0644]
src/frontends/xforms/forms/makefile
src/graphics/ChangeLog
src/graphics/GraphicsCache.C
src/graphics/GraphicsCache.h
src/graphics/GraphicsCacheItem.C
src/graphics/GraphicsCacheItem.h
src/graphics/GraphicsConverter.C [new file with mode: 0644]
src/graphics/GraphicsConverter.h [new file with mode: 0644]
src/graphics/GraphicsImage.C [new file with mode: 0644]
src/graphics/GraphicsImage.h [new file with mode: 0644]
src/graphics/GraphicsImageXPM.C [new file with mode: 0644]
src/graphics/GraphicsImageXPM.h [new file with mode: 0644]
src/graphics/GraphicsParams.C [new file with mode: 0644]
src/graphics/GraphicsParams.h [new file with mode: 0644]
src/graphics/GraphicsTypes.h [new file with mode: 0644]
src/graphics/ImageLoader.C [deleted file]
src/graphics/ImageLoader.h [deleted file]
src/graphics/ImageLoaderXPM.C [deleted file]
src/graphics/ImageLoaderXPM.h [deleted file]
src/graphics/Makefile.am
src/insets/insetgraphics.C
src/insets/insetgraphics.h
src/lyxfunc.C
src/lyxrc.C
src/support/ChangeLog
src/support/Makefile.am
src/support/forkedcall.C [new file with mode: 0644]
src/support/forkedcall.h [new file with mode: 0644]
src/support/forkedcontr.C [new file with mode: 0644]
src/support/forkedcontr.h [new file with mode: 0644]

index cace336a4844df18a77e61df84088595e974a9fc..4909b31794384fd6962060dc377fcd7352549778 100644 (file)
@@ -1,3 +1,7 @@
+2002-02-19  Angus Leeming  <a.leeming@ic.ac.uk>
+
+       * ui/default.ui: add a show-forks item to the two File menus.
+
 2002-02-21  Jean-Marc Lasgouttes  <lasgouttes@freesurf.fr>
 
        * ui/default.ui: change Layout>LaTeX Preamble to Layout>Preamble.
index 7c5aa5bdebd4bdec1e2ef5617a177e518a6650a9..c3522a45f04eff754ba63a13fb011812108a2294 100644 (file)
@@ -31,6 +31,7 @@ Menuset
        Item "Open...|O" "file-open"
        Separator
        Submenu "Import|I" "file_import"
+       Item "Child processes|h" "show-forks"
        Separator
        Item "Exit|x" "lyx-quit"
        Separator
@@ -52,6 +53,7 @@ Menuset
        Submenu "Export|E" "file_export"
        Item "Print...|P" "buffer-print"
        OptItem "Fax...|F" "buffer-export fax"
+       Item "Child processes|h" "show-forks"
        Separator
        Item "Exit|x" "lyx-quit"
        Separator
index a85eaec9cff62d6717320b40b7df4e5e1441187c..cc9a184b92e65e6435a7d8c9c9c34b37baa1eddd 100644 (file)
@@ -1,3 +1,12 @@
+2002-02-20  Angus Leeming  <a.leeming@ic.ac.uk>
+
+       * lyxfunc.C (dispatch): act on LFUN_FORKS_SHOW and LFUN_FORKS_KILL.
+       also call grfx::GCache::changeDisplay if the graphicsbg color changes.
+
+       * PainterBase.h (image):
+       * Painter.[Ch] (image): now accepts a grfx::GImage const & rather than
+       a LyXImage const *.
+
 2002-02-26  John Levon  <moz@compsoc.man.ac.uk>
 
        * Makefile.am:
index a44065ad108181c5c36c598d29c81777851af2b7..db10023d7364aca30ec944696bbdf030dbd78dbb 100644 (file)
@@ -25,7 +25,7 @@
 #include "language.h"
 
 #include "frontends/GUIRunTime.h"
-#include "frontends/support/LyXImage.h"
+#include "graphics/GraphicsImage.h"
 
 #include "support/LAssert.h"
 #include "support/lstrings.h"
@@ -171,11 +171,10 @@ PainterBase & Painter::pixmap(int x, int y, int w, int h, Pixmap bitmap)
 }
 
 
-PainterBase & Painter::image(int x, int y, int w, int h, LyXImage const * image)
+PainterBase & Painter::image(int x, int y, int w, int h,
+                           grfx::GImage const & image)
 {
-       Pixmap bitmap = image->getPixmap();
-
-       return pixmap(x, y, w, h, bitmap);
+       return pixmap(x, y, w, h, image.getPixmap());
 }
 
 
index 8d39c2ba02c72e75d74464b0fe9ac95fc41cb6bd..f020d5331365eb49359b7bc6b0a51f320711dbed 100644 (file)
@@ -76,7 +76,8 @@ public:
                                    LColor::color);
        
        /// For the graphics inset.
-       PainterBase & image(int x, int y, int w, int h, LyXImage const * image);
+       PainterBase & image(int x, int y, int w, int h,
+                           grfx::GImage const & image);
        
        /// For the figinset
        PainterBase & pixmap(int x, int y, int w, int h, Pixmap bitmap);
index e3b7d0747287ebb4956c4ef6cf34500234060ef7..303bb157fb18e98c7dd2bcc312e8921b7513169f 100644 (file)
@@ -20,7 +20,9 @@
 
 class WorkArea;
 class LyXFont;
-class LyXImage;
+namespace grfx {
+       class GImage;
+}
 
 /** A painter class to encapsulate all graphics parameters and operations
    
@@ -147,8 +149,8 @@ public:
        
        
        // For the figure inset
-       virtual PainterBase & image(int x, int y, int w, int h, LyXImage const * image) = 0;
-
+       virtual PainterBase & image(int x, int y, int w, int h,
+                                   grfx::GImage const & image) = 0;
        
        /// Draw a string at position x, y (y is the baseline)
        virtual PainterBase & text(int x, int y,
index ac150815c43638a6f40ede07e0a32dd4c0558b97..e6978f11d4fa28a4ee9154b2402cd7f4e8331700 100644 (file)
@@ -1,3 +1,16 @@
+2002-02-20  Angus Leeming  <a.leeming@ic.ac.uk>
+
+       * ControlForks.[Ch]: new files. A controller for the Forked Child
+       processes dialog, enabling the user to see what forked processes
+       are running, and, if he so desires, to kill them.
+
+       * GUI.h: add class GUIForks.
+
+       * Makefile.am: add ControlForks.[Ch].
+
+       * ControlGraphics.C (getParams): small change due to change in
+       insetgraphics.
+
 2002-02-21  Herbert Voss  <voss@lyx.org>
 
        * biblio.C: fix bug with commentlines in a bibentry
@@ -8,7 +21,7 @@
 
 2002-02-18  Herbert Voss  <voss@lyx.org>
 
-       * ControlGraphics.[C]: remove help-file call
+       * ControlGraphics.[Ch]: remove help-file call
 
 2002-02-18  Angus Leeming  <a.leeming@ic.ac.uk>
 
diff --git a/src/frontends/controllers/ControlForks.C b/src/frontends/controllers/ControlForks.C
new file mode 100644 (file)
index 0000000..d90b7a1
--- /dev/null
@@ -0,0 +1,96 @@
+/** 
+ * \file ControlForks.C
+ * Copyright 2001 The LyX Team
+ * Read COPYING
+ *
+ * \author Angus Leeming
+ */
+
+#include <config.h>
+
+#ifdef __GNUG__
+#pragma implementation
+#endif
+
+#include "ControlForks.h"
+#include "ButtonControllerBase.h"
+#include "ViewBase.h"
+
+#include "BufferView.h"
+#include "LyXView.h"
+#include "lyxfunc.h"
+
+#include "frontends/Dialogs.h"
+
+#include "support/forkedcontr.h"
+#include "support/lstrings.h"
+
+using std::vector;
+using SigC::slot;
+
+ControlForks::ControlForks(LyXView & lv, Dialogs & d)
+       : ControlDialogBI(lv, d)
+{
+       d_.showForks.connect(slot(this, &ControlForks::show));
+}
+
+
+vector<pid_t> const ControlForks::getPIDs() const
+{
+       ForkedcallsController const & fcc = ForkedcallsController::get();
+       return fcc.getPIDs();
+}
+
+
+string const ControlForks::getCommand(pid_t pid) const
+{
+       ForkedcallsController const & fcc = ForkedcallsController::get();
+       return fcc.getCommand(pid);
+}
+
+
+void ControlForks::kill(pid_t pid)
+{
+       pids_.push_back(tostr(pid));
+}
+
+
+void ControlForks::apply()
+{
+       if (!lv_.view()->available())
+               return;
+
+       view().apply();
+
+       // Nothing to apply?
+       if (pids_.empty())
+               return;
+
+       for (vector<string>::const_iterator it = pids_.begin();
+            it != pids_.end(); ++it) {
+               lv_.getLyXFunc()->dispatch(LFUN_FORKS_KILL, *it);
+       }
+
+       pids_.clear();
+}
+
+
+void ControlForks::setParams()
+{
+       if (childrenChanged_.connected())
+               return;
+
+       pids_.clear();
+
+       ForkedcallsController & fcc = ForkedcallsController::get();
+       childrenChanged_ =
+               fcc.childrenChanged.connect(slot(this, &ControlForks::update));
+}
+
+
+void ControlForks::clearParams()
+{
+       pids_.clear();
+       childrenChanged_.disconnect();
+}
+
diff --git a/src/frontends/controllers/ControlForks.h b/src/frontends/controllers/ControlForks.h
new file mode 100644 (file)
index 0000000..46de185
--- /dev/null
@@ -0,0 +1,49 @@
+// -*- C++ -*-
+/** 
+ * \file ControlForks.h
+ * Copyright 2001 The LyX Team
+ * Read COPYING
+ *
+ * \author Angus Leeming
+ */
+
+#ifndef CONTROLFORKS_H
+#define CONTROLFORKS_H
+
+#ifdef __GNUG__
+#pragma interface
+#endif
+
+#include "ControlDialog_impl.h"
+#include "LString.h"
+#include <sys/types.h>
+#include <vector>
+
+/** A controller for dialogs that display the child processes forked by LyX.
+    Also provides an interface enabling them to be killed prematurely.
+*/
+class ControlForks : public ControlDialogBI {
+public:
+       ///
+       ControlForks(LyXView &, Dialogs &);
+       ///
+       std::vector<pid_t> const getPIDs() const;
+       ///
+       string const getCommand(pid_t) const;
+       ///
+       void kill(pid_t);
+
+private:
+       ///
+       virtual void apply();
+       /// disconnect from the ForkedcallsController
+       virtual void clearParams();
+       /// connect to the ForkedcallsController
+       virtual void setParams();
+       /// Connection to the ForkedcallsController signal
+       SigC::Connection childrenChanged_;
+       /// The list of PIDs to kill
+       std::vector<string> pids_;
+};
+
+#endif // CONTROLFORKS_H
index 9bf1949d3fd87d0502cb13146cd16aadc38cb494..8231bdb0fc2d5bb5514ca3458a0352974438b716 100644 (file)
@@ -58,7 +58,7 @@ InsetGraphicsParams const ControlGraphics::getParams(string const &)
 InsetGraphicsParams const
 ControlGraphics::getParams(InsetGraphics const & inset)
 {
-       return inset.getParams();
+       return inset.params();
 }
 
 
index 08430e15cec3781b19bfade6c8ad7f3b9118c9a9..b3dd8b2fb3a05c1b19e9c702b38e8413e8710f67 100644 (file)
@@ -22,6 +22,7 @@
 #include "ControlERT.h"
 #include "ControlExternal.h"
 #include "ControlFloat.h"
+#include "ControlForks.h"
 #include "ControlGraphics.h"
 #include "insets/insetgraphicsParams.h"
 #include "ControlInclude.h"
@@ -161,6 +162,17 @@ public:
 };
 
 
+/** Specialization for Forks dialog
+ */
+template <class GUIview, class GUIbc>
+class GUIForks :
+       public GUI<ControlForks, GUIview, OkApplyCancelPolicy, GUIbc> {
+public:
+       ///
+       GUIForks(LyXView & lv, Dialogs & d)
+               : GUI<ControlForks, GUIview, OkApplyCancelPolicy, GUIbc>(lv, d) {}
+};
+
 /** Specialization for Graphics dialog
  */
 template <class GUIview, class GUIbc>
index 751261dd8924002558bea08284ade96c7cb4f04a..0c31357fb5081e51f5ac65750fc34408147e211d 100644 (file)
@@ -40,16 +40,18 @@ libcontrollers_la_SOURCES= \
        ControlDialog.h \
        ControlDialog_impl.C \
        ControlDialog_impl.h \
-       ControlError.h \
        ControlError.C \
-       ControlERT.h \
+       ControlError.h \
        ControlERT.C \
-       ControlExternal.h \
+       ControlERT.h \
        ControlExternal.C \
-       ControlFloat.h \
+       ControlExternal.h \
        ControlFloat.C \
-       ControlGraphics.h \
+       ControlFloat.h \
+       ControlForks.C \
+       ControlForks.h \
        ControlGraphics.C \
+       ControlGraphics.h \
        ControlInclude.C \
        ControlInclude.h \
        ControlIndex.C \
index 78fe413384a73f1d7c33357f9bc94c156205a13f..00d070ea890dbbdeeb28a8167d543c0007d4495f 100644 (file)
@@ -1,3 +1,19 @@
+2002-02-20  Angus Leeming  <a.leeming@ic.ac.uk>
+
+       * FormForks.[Ch]:
+       * forms/form_forks.fd: new files. A view for the Forked Child
+       processes dialog, enabling the user to see what forked processes
+       are running, and, if he so desires, to kill them.
+
+       * Dialogs.C: add the class Forked Child dialog.
+
+       * Makefile.am: add FormForks.[Ch], form_forks.[Ch].
+
+       * forms/makefile: add form_forks.fd.
+
+       * FormPreferences.C (LnFmisc::apply): rather ugly: call
+       grfx::GCache::changeDisplay if the lyxrc.display_graphics changes.
+
 2002-02-24  Juergen Spitzmueller  <j.spitzmueller@gmx.de>
 
        * forms/form_graphics.fd: Enlarge Restore button.
index 5bc247bcde7a1a8da3e259764c2962779ed7f279..7333af5b830921b4e0b64d8f0e81b44d8c993ef0 100644 (file)
@@ -31,6 +31,7 @@
 #include "form_ert.h"
 #include "form_external.h"
 #include "form_float.h"
+#include "form_forks.h"
 #include "form_graphics.h"
 #include "form_include.h"
 #include "form_index.h"
@@ -55,6 +56,7 @@
 #include "FormERT.h"
 #include "FormExternal.h"
 #include "FormFloat.h"
+#include "FormForks.h"
 #include "FormGraphics.h"
 #include "FormInclude.h"
 #include "FormIndex.h"
@@ -93,6 +95,7 @@ Dialogs::Dialogs(LyXView * lv)
        add(new GUIError<FormError, xformsBC>(*lv, *this));
        add(new GUIERT<FormERT, xformsBC>(*lv, *this));
        add(new GUIExternal<FormExternal, xformsBC>(*lv, *this));
+       add(new GUIForks<FormForks, xformsBC>(*lv, *this));
        add(new GUIGraphics<FormGraphics, xformsBC>(*lv, *this));
        add(new GUIInclude<FormInclude, xformsBC>(*lv, *this));
        add(new GUIIndex<FormIndex, xformsBC>(*lv, *this));
diff --git a/src/frontends/xforms/FormForks.C b/src/frontends/xforms/FormForks.C
new file mode 100644 (file)
index 0000000..54d2727
--- /dev/null
@@ -0,0 +1,419 @@
+/**
+ * \file FormForks.C
+ * Copyright 2001 the LyX Team
+ * Read the file COPYING
+ *
+ * \author Angus Leeming
+ * \date 2001-10-22
+ */
+
+#include <config.h>
+
+#ifdef __GNUG__
+#pragma implementation
+#endif
+
+#include "xformsBC.h"
+#include "FormForks.h"
+#include "ControlForks.h"
+#include "form_forks.h"
+#include "gettext.h"
+#include "helper_funcs.h"
+#include "xforms_helpers.h"
+#include "support/lstrings.h"
+
+using std::vector;
+using std::find;
+using std::find_if;
+
+typedef FormCB<ControlForks, FormDB<FD_form_forks> > base_class;
+
+FormForks::FormForks(ControlForks & c)
+       : base_class(c, _("Child processes"))
+{}
+
+
+void FormForks::build() {
+       dialog_.reset(build_forks());
+
+       // It appears that the browsers aren't initialised properly.
+       // This fudge fixes tings.
+       fl_add_browser_line(dialog_->browser_children, " ");
+       fl_add_browser_line(dialog_->browser_kill, " ");
+       fl_clear_browser(dialog_->browser_children);
+       fl_clear_browser(dialog_->browser_kill);
+
+        // Manage the ok, apply, restore and cancel/close buttons
+       bc().setOK(dialog_->button_ok);
+       bc().setApply(dialog_->button_apply);
+       bc().setCancel(dialog_->button_close);
+       bc().invalid();
+
+       // Set up the tooltip mechanism
+       setTooltipHandler(dialog_->browser_children);
+       setTooltipHandler(dialog_->browser_kill);
+       setTooltipHandler(dialog_->button_all);
+       setTooltipHandler(dialog_->button_add);
+       setTooltipHandler(dialog_->button_remove);
+}
+
+
+void FormForks::update()
+{
+       if (!form())
+               return;
+
+       string const current_pid_str =
+               getSelectedStringFromBrowser(dialog_->browser_kill);
+       pid_t const current_pid = strToInt(current_pid_str);
+
+       vector<pid_t> pids = controller().getPIDs();
+
+       // No child processes.
+       if (pids.empty()) {
+               if (fl_get_browser_maxline(dialog_->browser_kill) > 0)
+                       fl_clear_browser(dialog_->browser_kill);
+               if (fl_get_browser_maxline(dialog_->browser_children) > 0)
+                       fl_clear_browser(dialog_->browser_children);
+
+               setEnabled(dialog_->browser_children, false);
+               setEnabled(dialog_->browser_kill,     false);
+               setEnabled(dialog_->button_all,       false);
+               setEnabled(dialog_->button_add,       false);
+               setEnabled(dialog_->button_remove,    false);
+
+               return;
+       }
+
+       // Remove any processes from the kill browser that aren't in the
+       // vector of existing PIDs.
+       for (int i = 1; i <= fl_get_browser_maxline(dialog_->browser_kill);
+            ++i) {
+               string const pid_str =
+                       getStringFromBrowser(dialog_->browser_kill, i);
+               pid_t const pid = strToInt(pid_str);
+               vector<pid_t>::const_iterator it =
+                       find(pids.begin(), pids.end(), pid);
+               if (it == pids.end())
+                       fl_delete_browser_line(dialog_->browser_kill, i);
+       }
+
+       // Build the children browser from scratch.
+       if (fl_get_browser_maxline(dialog_->browser_children) > 0)
+               fl_clear_browser(dialog_->browser_children);
+       int i = 1;
+       for (vector<pid_t>::const_iterator it = pids.begin();
+            it != pids.end(); ++it) {
+               string const pid_str = tostr(*it);
+               string const command = controller().getCommand(*it);
+               string const line = pid_str + '\t' + command;
+
+               fl_add_browser_line(dialog_->browser_children, line.c_str());
+
+               if (*it == current_pid)
+                       fl_select_browser_line(dialog_->browser_children, i);
+               ++i;
+       }
+
+       setEnabled(dialog_->browser_children, true);
+       setEnabled(dialog_->button_all,       true);
+       setEnabled(dialog_->button_add,       true);
+}
+
+
+void FormForks::apply()
+{
+       // Get the list of all processes to kill.
+       vector<string> const kill_vec =
+               getVectorFromBrowser(dialog_->browser_kill);
+
+       if (kill_vec.empty())
+               return;
+
+       // Remove these items from the vector of child processes.
+       for (int i = 1; i <= fl_get_browser_maxline(dialog_->browser_children);
+            ++i) {
+               string const selection =
+                       getStringFromBrowser(dialog_->browser_children, i);
+               string pid_str;
+               split(selection, pid_str, '\t');
+
+               vector<string>::const_iterator it =
+                       find(kill_vec.begin(), kill_vec.end(), pid_str);
+
+               if (it != kill_vec.end())
+                       fl_delete_browser_line(dialog_->browser_children, i);
+       }
+
+       // Clear the kill browser and deactivate appropriately.
+       fl_clear_browser(dialog_->browser_kill);
+       setEnabled(dialog_->browser_kill,  false);
+       setEnabled(dialog_->button_remove, false);
+
+       // Pass these pids to the controller for destruction.
+       for (vector<string>::const_iterator it = kill_vec.begin();
+            it != kill_vec.end(); ++it) {
+               pid_t const pid = strToInt(*it);
+               controller().kill(pid);
+       }
+
+}
+
+
+ButtonPolicy::SMInput FormForks::input(FL_OBJECT * ob, long)
+{
+       ButtonPolicy::SMInput activate = ButtonPolicy::SMI_NOOP;
+
+       if (ob == dialog_->browser_children) {
+               activate = input_browser_children();
+               
+       } else if (ob == dialog_->browser_kill) {
+               activate = input_browser_kill();
+
+       } else if (ob == dialog_->button_all) {
+               activate = input_button_all();
+
+       } else if (ob == dialog_->button_add) {
+               activate = input_button_add();
+
+       } else if (ob == dialog_->button_remove) {
+               activate = input_button_remove();
+       }
+
+       return activate;
+}
+
+ButtonPolicy::SMInput FormForks::input_browser_children()
+{
+       // Selected an item in the browser containing a list of all child
+       // processes.
+
+       // 1. Highlight this item in the browser of processes to kill
+       //    if it is already there.
+
+       // 2. If it is there, enable the remove button so that it can
+       //    be removed from this list, if so desired.
+
+       // 3. If it isn't there, activate the add button so that it can
+       //    be added to this list if so desired.
+
+       string const selection =
+               getSelectedStringFromBrowser(dialog_->browser_children);
+       string pid_str;
+       split(selection, pid_str, '\t');
+
+       vector<string> const kill_vec = 
+               getVectorFromBrowser(dialog_->browser_kill);
+
+       vector<string>::const_iterator it =
+               find(kill_vec.begin(), kill_vec.end(), pid_str);
+
+       fl_deselect_browser(dialog_->browser_kill);
+       if (it != kill_vec.end()) {
+               int const n = int(it - kill_vec.begin());
+               fl_select_browser_line(dialog_->browser_kill, n+1);
+               fl_set_browser_topline(dialog_->browser_kill, n+1);
+       }
+
+       setEnabled(dialog_->button_remove, it != kill_vec.end());
+       setEnabled(dialog_->button_add,    it == kill_vec.end());
+
+       return ButtonPolicy::SMI_NOOP;
+}
+
+
+namespace {
+
+class FindPID {
+public:
+       FindPID(string const & pid) : pid_(pid) {}
+       bool operator()(string const & line)
+       {
+               if (line.empty())
+                       return false;
+
+               string pid_str;
+               split(line, pid_str, '\t');
+               return pid_str == pid_;
+       }
+
+private:
+       string pid_;
+};
+} // namespace anon
+
+
+ButtonPolicy::SMInput FormForks::input_browser_kill()
+{
+       // Selected an item in the browser containing a list of processes
+       // to kill.
+
+       // 1. Highlight this item in the browser of all child processes.
+
+       // 2. Enable the remove button so that it can removed from this list,
+       //    if so desired.
+
+       // 3. Disable the add button.
+
+       string const pid_str =
+               getSelectedStringFromBrowser(dialog_->browser_kill);
+
+       // Find this string in the list of all child processes
+       vector<string> const child_vec =
+               getVectorFromBrowser(dialog_->browser_children);
+
+       vector<string>::const_iterator it =
+               find_if(child_vec.begin(), child_vec.end(), FindPID(pid_str));
+
+       fl_deselect_browser(dialog_->browser_children);
+       if (it != child_vec.end()) {
+               int const n = int(it - child_vec.begin());
+               fl_select_browser_line(dialog_->browser_children, n+1);
+               fl_set_browser_topline(dialog_->browser_children, n+1);
+       }
+               
+       setEnabled(dialog_->button_remove, true);
+       setEnabled(dialog_->button_add,    false);
+
+       return ButtonPolicy::SMI_NOOP;
+}
+
+
+namespace {
+
+vector<string> const getPIDvector(FL_OBJECT * ob)
+{
+       vector<string> vec = getVectorFromBrowser(ob);
+       if (vec.empty())
+               return vec;
+
+       for (vector<string>::iterator it = vec.begin(); it != vec.end(); ++it) {
+               string pid_str;
+               split(*it, pid_str, '\t');
+               *it = pid_str;
+       }
+
+       return vec;
+}
+} // namespace anon
+
+
+ButtonPolicy::SMInput FormForks::input_button_all()
+{
+       // Pressed the "All" button.
+
+       // 1. Check that the browser of processes to kill doesn't already
+       //    contain the entire list.
+
+       // 2. If it doesn't, copy the PIDs of all child processes into the
+       //    browser of processes to kill.
+
+       // 3. Deactivate the "children" browser and the "add" and "all" buttons
+
+       // 4. Activate the "kill" browser and the "remove" button"
+
+       ButtonPolicy::SMInput activate = ButtonPolicy::SMI_NOOP;
+
+       vector<string> const pid_vec = getPIDvector(dialog_->browser_children);
+       if (fl_get_browser_maxline(dialog_->browser_kill) != pid_vec.size()) {
+               activate = ButtonPolicy::SMI_VALID;
+
+               fl_clear_browser(dialog_->browser_kill);
+               for (vector<string>::const_iterator it = pid_vec.begin();
+                    it != pid_vec.end(); ++it) {
+                       fl_add_browser_line(dialog_->browser_kill, it->c_str());
+               }
+
+               if (fl_get_browser_maxline(dialog_->browser_kill) >= 1)
+                       fl_set_browser_topline(dialog_->browser_kill, 1);
+       }
+
+       setEnabled(dialog_->browser_children, false);
+       setEnabled(dialog_->button_add,       false);
+       setEnabled(dialog_->button_all,       false);
+       setEnabled(dialog_->browser_kill,     true);
+       setEnabled(dialog_->button_remove,    true);
+
+       return activate;
+}
+
+
+ButtonPolicy::SMInput FormForks::input_button_add()
+{
+       // Pressed the "Add" button.
+
+       // 1. Copy the PID of the selected item in the browser of all child
+       //    processes over into the browser of processes to kill.
+
+       // 2. Activate the "kill" browser and the "remove" button.
+
+       // 3. Deactivate the "add" button.
+
+       string const selection =
+               getSelectedStringFromBrowser(dialog_->browser_children);
+       string pid_str;
+       split(selection, pid_str, '\t');
+
+       vector<string> const kill_vec = 
+               getVectorFromBrowser(dialog_->browser_kill);
+
+       vector<string>::const_iterator it =
+               find(kill_vec.begin(), kill_vec.end(), pid_str);
+
+       if (it == kill_vec.end()) {
+               fl_add_browser_line(dialog_->browser_kill, pid_str.c_str());
+               int const n = fl_get_browser_maxline(dialog_->browser_kill);
+               fl_select_browser_line(dialog_->browser_kill, n);
+       }
+
+       setEnabled(dialog_->browser_kill,  true);
+       setEnabled(dialog_->button_remove, true);
+       setEnabled(dialog_->button_add,    false);
+
+       return ButtonPolicy::SMI_VALID;
+}
+
+
+ButtonPolicy::SMInput FormForks::input_button_remove()
+{
+       // Pressed the "Remove" button.
+
+       // 1. Remove the selected item in the browser of processes to kill.
+
+       // 2. Activate the "add" button and "all" buttons.
+
+       // 3. Deactivate the "remove" button.
+
+       int const sel = fl_get_browser(dialog_->browser_kill);
+       fl_delete_browser_line(dialog_->browser_kill, sel);
+
+       setEnabled(dialog_->button_add,    true);
+       setEnabled(dialog_->button_all,    true);
+       setEnabled(dialog_->button_remove, false);
+
+       return ButtonPolicy::SMI_VALID;
+}
+
+
+string const FormForks::getVerboseTooltip(FL_OBJECT const * ob) const
+{
+       string str;
+       
+       if (ob == dialog_->browser_children) {
+               str = _("All currently running child processes forked by LyX.");
+       } else if (ob == dialog_->browser_kill) {
+               str = _("A list of all child processes to kill.");
+       } else if (ob == dialog_->button_all) {
+               str = _("Add all processes to the list of processes to kill.");
+       } else if (ob == dialog_->button_add) {
+               str = _("Add the currently selected child process to the list of processes to kill.");
+       } else if (ob == dialog_->button_remove) {
+               str = _("Remove the currently selected item from the list of processes to kill.");
+       }
+       
+       return str;
+}
+
+
diff --git a/src/frontends/xforms/FormForks.h b/src/frontends/xforms/FormForks.h
new file mode 100644 (file)
index 0000000..ebfb25b
--- /dev/null
@@ -0,0 +1,51 @@
+// -*- C++ -*-
+/**
+ * \file FormForks.h
+ * Copyright 2001 the LyX Team
+ * Read the file COPYING
+ *
+ * \author Angus Leeming
+ */
+
+#ifndef FORMFORKS_H
+#define FORMFORKS_H
+
+#ifdef __GNUG__
+#pragma interface
+#endif
+
+#include "FormBase.h"
+
+struct FD_form_forks;
+class ControlForks;
+
+class FormForks : public FormCB<ControlForks, FormDB<FD_form_forks> > {
+public:
+       ///
+       FormForks(ControlForks &);
+
+       /// preemptive handler for feedback messages
+       void feedbackCB(FL_OBJECT *, int);
+
+private:
+       /// Return the list of PIDs to kill to the controller.
+       virtual void apply();
+       /// Build the dialog.
+       virtual void build();
+       /// Update the dialog.
+       virtual void update();
+       /// Filter the inputs on callback from xforms
+       virtual ButtonPolicy::SMInput input(FL_OBJECT *, long);
+       /// tooltips
+       string const getVerboseTooltip(FL_OBJECT const * ob) const;
+       /// Fdesign generated method
+       FD_form_forks * build_forks();
+
+       ButtonPolicy::SMInput input_browser_children();
+       ButtonPolicy::SMInput input_browser_kill();
+       ButtonPolicy::SMInput input_button_all();
+       ButtonPolicy::SMInput input_button_add();
+       ButtonPolicy::SMInput input_button_remove();
+};
+
+#endif // FORMFORKS_H
index aba6c9ee75f893d87dea9516814638804854802a..6f0cbb43c4ea2c438e93a5f1866ae42109dd8dc7 100644 (file)
@@ -48,6 +48,8 @@
 #include "support/filetools.h"
 #include "support/LAssert.h"
 
+#include "graphics/GraphicsCache.h"
+
 using std::endl;
 using std::pair;
 using std::make_pair;
@@ -1836,6 +1838,7 @@ void FormPreferences::LnFmisc::apply() const
        lyxrc.wheel_jump = static_cast<unsigned int>
                (fl_get_counter_value(dialog_->counter_wm_jump));
 
+       string const old_value = lyxrc.display_graphics;
        if (fl_get_button(dialog_->radio_display_monochrome)) {
                lyxrc.display_graphics = "mono";
        } else if (fl_get_button(dialog_->radio_display_grayscale)) {
@@ -1845,6 +1848,10 @@ void FormPreferences::LnFmisc::apply() const
        } else {
                lyxrc.display_graphics = "no";
        }
+       if (old_value != lyxrc.display_graphics) {
+               grfx::GCache & gc = grfx::GCache::get();
+               gc.changeDisplay();
+       }
 }
 
 
index fe125725478013d6e676603847fd350ed5c86e9f..8c5d13f7e328f5fc2f3ee6fd0f29e065bad3f276 100644 (file)
@@ -76,6 +76,10 @@ libxforms_la_SOURCES = \
        FormFloat.h \
        form_float.C \
        form_float.h \
+       FormForks.C \
+       FormForks.h \
+       form_forks.C \
+       form_forks.h \
        FormGraphics.C \
        FormGraphics.h \
        form_graphics.C \
@@ -178,10 +182,10 @@ libxforms_la_SOURCES = \
        form_url.h \
        FormVCLog.C \
        FormVCLog.h \
-       input_validators.h \
        input_validators.C \
-       MathsSymbols.h \
+       input_validators.h \
        MathsSymbols.C \
+       MathsSymbols.h \
        Menubar_pimpl.C \
        Menubar_pimpl.h \
        RadioButtonGroup.C \
diff --git a/src/frontends/xforms/form_forks.C b/src/frontends/xforms/form_forks.C
new file mode 100644 (file)
index 0000000..d7768c9
--- /dev/null
@@ -0,0 +1,80 @@
+// File modified by fdfix.sh for use by lyx (with xforms >= 0.88) and gettext
+#include <config.h>
+#include "xforms_helpers.h"
+#include "gettext.h"
+
+/* Form definition file generated with fdesign. */
+
+#include FORMS_H_LOCATION
+#include <stdlib.h>
+#include "form_forks.h"
+#include "FormForks.h"
+
+FD_form_forks::~FD_form_forks()
+{
+  if ( form->visible ) fl_hide_form( form );
+  fl_free_form( form );
+}
+
+
+FD_form_forks * FormForks::build_forks()
+{
+  FL_OBJECT *obj;
+  FD_form_forks *fdui = new FD_form_forks;
+
+  fdui->form = fl_bgn_form(FL_NO_BOX, 650, 390);
+  fdui->form->u_vdata = this;
+  obj = fl_add_box(FL_UP_BOX, 0, 0, 650, 390, "");
+  {
+    char const * const dummy = N_("Forked child processes|#F");
+    fdui->browser_children = obj = fl_add_browser(FL_HOLD_BROWSER, 20, 30, 400, 290, idex(_(dummy)));
+    fl_set_button_shortcut(obj, scex(_(dummy)), 1);
+  }
+    fl_set_object_lsize(obj, FL_NORMAL_SIZE);
+    fl_set_object_lalign(obj, FL_ALIGN_TOP);
+    fl_set_object_callback(obj, C_FormBaseInputCB, 0);
+  {
+    char const * const dummy = N_("Kill processes|#K");
+    fdui->browser_kill = obj = fl_add_browser(FL_HOLD_BROWSER, 510, 30, 125, 290, idex(_(dummy)));
+    fl_set_button_shortcut(obj, scex(_(dummy)), 1);
+  }
+    fl_set_object_lsize(obj, FL_NORMAL_SIZE);
+    fl_set_object_lalign(obj, FL_ALIGN_TOP);
+    fl_set_object_callback(obj, C_FormBaseInputCB, 0);
+  fdui->button_all = obj = fl_add_button(FL_NORMAL_BUTTON, 432, 30, 65, 30, _("All ->"));
+    fl_set_object_lsize(obj, FL_NORMAL_SIZE);
+    fl_set_object_callback(obj, C_FormBaseInputCB, 0);
+  fdui->button_add = obj = fl_add_button(FL_NORMAL_BUTTON, 450, 70, 30, 30, _("@->"));
+    fl_set_object_lsize(obj, FL_NORMAL_SIZE);
+    fl_set_object_callback(obj, C_FormBaseInputCB, 0);
+  fdui->button_remove = obj = fl_add_button(FL_NORMAL_BUTTON, 450, 110, 30, 30, _("@4->"));
+    fl_set_object_lsize(obj, FL_NORMAL_SIZE);
+    fl_set_object_callback(obj, C_FormBaseInputCB, 0);
+  fdui->button_ok = obj = fl_add_button(FL_RETURN_BUTTON, 355, 350, 90, 30, _("OK"));
+    fl_set_object_lsize(obj, FL_NORMAL_SIZE);
+    fl_set_object_gravity(obj, FL_SouthEast, FL_SouthEast);
+    fl_set_object_callback(obj, C_FormBaseOKCB, 0);
+  {
+    char const * const dummy = N_("Apply|#A");
+    fdui->button_apply = obj = fl_add_button(FL_NORMAL_BUTTON, 450, 350, 90, 30, idex(_(dummy)));
+    fl_set_button_shortcut(obj, scex(_(dummy)), 1);
+  }
+    fl_set_object_lsize(obj, FL_NORMAL_SIZE);
+    fl_set_object_gravity(obj, FL_SouthEast, FL_SouthEast);
+    fl_set_object_callback(obj, C_FormBaseApplyCB, 0);
+  {
+    char const * const dummy = N_("Close|^[");
+    fdui->button_close = obj = fl_add_button(FL_NORMAL_BUTTON, 545, 350, 90, 30, idex(_(dummy)));
+    fl_set_button_shortcut(obj, scex(_(dummy)), 1);
+  }
+    fl_set_object_lsize(obj, FL_NORMAL_SIZE);
+    fl_set_object_gravity(obj, FL_SouthEast, FL_SouthEast);
+    fl_set_object_callback(obj, C_FormBaseCancelCB, 0);
+  fl_end_form();
+
+  fdui->form->fdui = fdui;
+
+  return fdui;
+}
+/*---------------------------------------*/
+
diff --git a/src/frontends/xforms/form_forks.h b/src/frontends/xforms/form_forks.h
new file mode 100644 (file)
index 0000000..7ca4800
--- /dev/null
@@ -0,0 +1,29 @@
+// File modified by fdfix.sh for use by lyx (with xforms >= 0.88) and gettext
+/** Header file generated with fdesign **/
+
+#ifndef FD_form_forks_h_
+#define FD_form_forks_h_
+
+/** Callbacks, globals and object handlers **/
+extern  "C" void C_FormBaseInputCB(FL_OBJECT *, long);
+extern  "C" void C_FormBaseOKCB(FL_OBJECT *, long);
+extern  "C" void C_FormBaseApplyCB(FL_OBJECT *, long);
+extern  "C" void C_FormBaseCancelCB(FL_OBJECT *, long);
+
+
+/**** Forms and Objects ****/
+struct FD_form_forks {
+       ~FD_form_forks();
+
+       FL_FORM *form;
+       FL_OBJECT *browser_children;
+       FL_OBJECT *browser_kill;
+       FL_OBJECT *button_all;
+       FL_OBJECT *button_add;
+       FL_OBJECT *button_remove;
+       FL_OBJECT *button_ok;
+       FL_OBJECT *button_apply;
+       FL_OBJECT *button_close;
+};
+
+#endif /* FD_form_forks_h_ */
diff --git a/src/frontends/xforms/forms/form_forks.fd b/src/frontends/xforms/forms/form_forks.fd
new file mode 100644 (file)
index 0000000..3e8db0d
--- /dev/null
@@ -0,0 +1,178 @@
+Magic: 13000
+
+Internal Form Definition File
+    (do not change)
+
+Number of forms: 1
+Unit of measure: FL_COORD_PIXEL
+
+=============== FORM ===============
+Name: form_forks
+Width: 650
+Height: 390
+Number of Objects: 9
+
+--------------------
+class: FL_BOX
+type: UP_BOX
+box: 0 0 650 390
+boxtype: FL_UP_BOX
+colors: FL_COL1 FL_COL1
+alignment: FL_ALIGN_CENTER
+style: FL_NORMAL_STYLE
+size: FL_DEFAULT_SIZE
+lcol: FL_BLACK
+label: 
+shortcut: 
+resize: FL_RESIZE_ALL
+gravity: FL_NoGravity FL_NoGravity
+name: 
+callback: 
+argument: 
+
+--------------------
+class: FL_BROWSER
+type: HOLD_BROWSER
+box: 20 30 400 290
+boxtype: FL_DOWN_BOX
+colors: FL_COL1 FL_YELLOW
+alignment: FL_ALIGN_TOP
+style: FL_NORMAL_STYLE
+size: FL_NORMAL_SIZE
+lcol: FL_BLACK
+label: Forked child processes|#F
+shortcut: 
+resize: FL_RESIZE_ALL
+gravity: FL_NoGravity FL_NoGravity
+name: browser_children
+callback: C_FormBaseInputCB
+argument: 0
+
+--------------------
+class: FL_BROWSER
+type: HOLD_BROWSER
+box: 510 30 125 290
+boxtype: FL_DOWN_BOX
+colors: FL_COL1 FL_YELLOW
+alignment: FL_ALIGN_TOP
+style: FL_NORMAL_STYLE
+size: FL_NORMAL_SIZE
+lcol: FL_BLACK
+label: Kill processes|#K
+shortcut: 
+resize: FL_RESIZE_ALL
+gravity: FL_NoGravity FL_NoGravity
+name: browser_kill
+callback: C_FormBaseInputCB
+argument: 0
+
+--------------------
+class: FL_BUTTON
+type: NORMAL_BUTTON
+box: 432 30 65 30
+boxtype: FL_UP_BOX
+colors: FL_COL1 FL_COL1
+alignment: FL_ALIGN_CENTER
+style: FL_NORMAL_STYLE
+size: FL_NORMAL_SIZE
+lcol: FL_BLACK
+label: All ->
+shortcut: 
+resize: FL_RESIZE_ALL
+gravity: FL_NoGravity FL_NoGravity
+name: button_all
+callback: C_FormBaseInputCB
+argument: 0
+
+--------------------
+class: FL_BUTTON
+type: NORMAL_BUTTON
+box: 450 70 30 30
+boxtype: FL_UP_BOX
+colors: FL_COL1 FL_COL1
+alignment: FL_ALIGN_CENTER
+style: FL_NORMAL_STYLE
+size: FL_NORMAL_SIZE
+lcol: FL_BLACK
+label: @->
+shortcut: 
+resize: FL_RESIZE_ALL
+gravity: FL_NoGravity FL_NoGravity
+name: button_add
+callback: C_FormBaseInputCB
+argument: 0
+
+--------------------
+class: FL_BUTTON
+type: NORMAL_BUTTON
+box: 450 110 30 30
+boxtype: FL_UP_BOX
+colors: FL_COL1 FL_COL1
+alignment: FL_ALIGN_CENTER
+style: FL_NORMAL_STYLE
+size: FL_NORMAL_SIZE
+lcol: FL_BLACK
+label: @4->
+shortcut: 
+resize: FL_RESIZE_ALL
+gravity: FL_NoGravity FL_NoGravity
+name: button_remove
+callback: C_FormBaseInputCB
+argument: 0
+
+--------------------
+class: FL_BUTTON
+type: RETURN_BUTTON
+box: 355 350 90 30
+boxtype: FL_UP_BOX
+colors: FL_COL1 FL_COL1
+alignment: FL_ALIGN_CENTER
+style: FL_NORMAL_STYLE
+size: FL_NORMAL_SIZE
+lcol: FL_BLACK
+label: OK
+shortcut: ^M
+resize: FL_RESIZE_ALL
+gravity: FL_SouthEast FL_SouthEast
+name: button_ok
+callback: C_FormBaseOKCB
+argument: 0
+
+--------------------
+class: FL_BUTTON
+type: NORMAL_BUTTON
+box: 450 350 90 30
+boxtype: FL_UP_BOX
+colors: FL_COL1 FL_COL1
+alignment: FL_ALIGN_CENTER
+style: FL_NORMAL_STYLE
+size: FL_NORMAL_SIZE
+lcol: FL_BLACK
+label: Apply|#A
+shortcut: 
+resize: FL_RESIZE_ALL
+gravity: FL_SouthEast FL_SouthEast
+name: button_apply
+callback: C_FormBaseApplyCB
+argument: 0
+
+--------------------
+class: FL_BUTTON
+type: NORMAL_BUTTON
+box: 545 350 90 30
+boxtype: FL_UP_BOX
+colors: FL_COL1 FL_COL1
+alignment: FL_ALIGN_CENTER
+style: FL_NORMAL_STYLE
+size: FL_NORMAL_SIZE
+lcol: FL_BLACK
+label: Close|^[
+shortcut: 
+resize: FL_RESIZE_ALL
+gravity: FL_SouthEast FL_SouthEast
+name: button_close
+callback: C_FormBaseCancelCB
+argument: 0
+
+==============================
+create_the_forms
index 8651dc5bb9b9624ae569762034bbc33dbcf8f7df..953ddcec2ac0a0eac4ca5aaa0a9e013cf57a27f9 100644 (file)
@@ -30,6 +30,7 @@ SRCS =  form_aboutlyx.fd \
        form_external.fd \
        form_filedialog.fd \
        form_float.fd \
+       form_forks.fd \
        form_graphics.fd \
        form_include.fd \
        form_index.fd \
index ab5dfdf2e477a2c4cda3b5f1daa0c7a0ea7f0910..6fbf637fabfcc4ce80e9edc05589d69c32d21b9f 100644 (file)
@@ -1,3 +1,21 @@
+2002-02-15  Angus Leeming  <a.leeming@ic.ac.uk>
+
+       * ImageLoader.[Ch]:
+       * ImageLoaderXPM.[Ch]: removed.
+
+       * GraphicsConverter.[Ch]:
+       * GraphicsImage.[Ch]:
+       * GraphicsImageXPM.[Ch]:
+       * GraphicsParams.[Ch]:
+       * GraphicsTypes.h: new files.
+
+       * All files. A total re-write of the graphics cache. The cache now
+       supports asynchronous file conversion and file loading. Images
+       can be cropped, rotated and scaled for display on the LyX screen.
+       The old LyXImage and ImageLoader have been combined in a new class
+       GImage. Ditto, ImageLoaderXPM's functionality has been moved into
+       GImageXPM.
+
 2002-02-07  Herbert Voss  <voss@lyx.org>
 
        * GraphicsCacheItem.C: use unzipFile() from support/filetools
index fe2de807c940174056b3a7564b71faff8be744c2..d6b9b20dc0a641dd9a439f2f8c15ad6af162a20e 100644 (file)
@@ -1,12 +1,11 @@
-/* This file is part of
- * =================================================
- * 
- *          LyX, The Document Processor
- *          Copyright 1995 Matthias Ettrich.
- *          Copyright 1995-2001 The LyX Team.
+/*
+ * \file GraphicsCache.C
+ * Copyright 2002 the LyX Team
+ * Read the file COPYING
  *
- *          This file Copyright 2000 Baruch Even
- * ================================================= */
+ * \author Baruch Even <baruch.even@writeme.com>
+ * \author Angus Leeming <a.leeming@ic.ac.uk>
+ */
 
 #include <config.h>
 
 
 #include "GraphicsCache.h"
 #include "GraphicsCacheItem.h"
+#include "GraphicsImage.h"
+#include "GraphicsParams.h"
+#include "insets/insetgraphics.h"
 
-#include "support/LAssert.h"
+// I think that graphicsInit should become Dialogs::graphicsInit.
+// These #includes would then be moved there.
+// Angus 25 Feb 2002
+#include "GraphicsImageXPM.h"
+//#include "xformsGraphicsImage.h"
 
-GraphicsCache &
-GraphicsCache::getInstance()
+namespace {
+
+void graphicsInit() 
 {
-       static GraphicsCache singleton;
+       using namespace grfx;
+       using SigC::slot;
+    
+       // connect the image loader based on the XPM library
+       GImage::newImage.connect(slot(&GImageXPM::newImage));
+       GImage::loadableFormats.connect(slot(&GImageXPM::loadableFormats));
+       // connect the image loader based on the xforms library
+//     GImage::newImage.connect(slot(&xformsGImage::newImage));
+//     GImage::loadableFormats.connect(slot(&xformsGImage::loadableFormats));
+}
+    
+} // namespace anon
+
+namespace grfx {
+
+GCache & GCache::get()
+{
+       static bool start = true;
+       if (start) {
+               start = false;
+               graphicsInit();
+       }
+
+       // Now return the cache
+       static GCache singleton;
        return singleton;
 }
 
 
-GraphicsCache::~GraphicsCache()
+GCache::GCache()
 {
-       // All elements are destroyed by the shared_ptr's in the map.
+       cache = new CacheType;
 }
 
 
-GraphicsCache::shared_ptr_item
-GraphicsCache::addFile(string const & filename)
+// all elements are destroyed by the shared_ptr's in the map.
+GCache::~GCache()
 {
-       CacheType::iterator it = cache.find(filename);
-       
-       if (it != cache.end()) {
-               return it->second;
+       delete cache;
+}
+
+
+void GCache::update(InsetGraphics const & inset)
+{
+       // A subset only of InsetGraphicsParams is needed for display purposes.
+       // The GraphicsParams c-tor also interrogates lyxrc to ascertain whether
+       // to display or not.
+       GParams params(inset.params());
+
+       // Each inset can reference only one file, so check the cache for any
+       // graphics files referenced by inset. If the name of this file is
+       // different from that in params, then remove the reference.
+       CacheType::iterator it = find(inset);
+
+       if (it != cache->end()) {
+               CacheItemType item = it->second;
+               if (item->filename() != params.filename) {
+                       item->remove(inset);
+                       if (item->empty())
+                               cache->erase(it);
+               }
        }
-       
-       shared_ptr_item cacheItem(new GraphicsCacheItem(filename));
-       if (cacheItem.get() == 0)
-               return cacheItem;
-       
-       cache[filename] = cacheItem;
 
-       // GraphicsCacheItem_ptr is a shared_ptr and thus reference counted,
-       // it is safe to return it directly.
-       return cacheItem;
+       // Are we adding a new file or modifying the display of an existing one?
+       it = cache->find(params.filename);
+
+       if (it != cache->end()) {
+               it->second->modify(inset, params);
+               return;
+       }
+
+       CacheItemType item(new GCacheItem(inset, params));
+       if (item.get() != 0)
+               (*cache)[params.filename] = item;
+}
+
+
+void GCache::remove(InsetGraphics const & inset)
+{
+       CacheType::iterator it = find(inset);
+       if (it == cache->end())
+               return;
+
+       CacheItemType item = it->second;
+       item->remove(inset);
+       if (item->empty()) {
+               cache->erase(it);
+       }
+}
+
+
+void GCache::startLoading(InsetGraphics const & inset)
+{
+       CacheType::iterator it = find(inset);
+       if (it == cache->end())
+               return;
+
+       it->second->startLoading(inset);
+}
+
+
+ImagePtr const GCache::image(InsetGraphics const & inset) const
+{
+       CacheType::const_iterator it = find(inset);
+       if (it == cache->end())
+               return ImagePtr();
+
+       return it->second->image(inset);
+}
+
+
+ImageStatus GCache::status(InsetGraphics const & inset) const
+{
+       CacheType::const_iterator it = find(inset);
+       if (it == cache->end())
+               return ErrorUnknown;
+
+       return it->second->status(inset);
 }
 
 
-void
-GraphicsCache::removeFile(string const & filename)
+void GCache::changeDisplay(bool changed_background)
 {
-       // We do not destroy the GraphicsCacheItem since we are here because
-       // the last copy of it is being erased.
+       CacheType::iterator it = cache->begin();
+       CacheType::iterator end = cache->end();
+       for(; it != end; ++it)
+               it->second->changeDisplay(changed_background);
+}
 
-       CacheType::iterator it = cache.find(filename);
-       if (it != cache.end())
-               cache.erase(it);
+
+GCache::CacheType::iterator
+GCache::find(InsetGraphics const & inset)
+{
+       CacheType::iterator it = cache->begin();
+       for (; it != cache->end(); ++it) {
+               if (it->second->referencedBy(inset))
+                       return it;
+       }
+       
+       return cache->end();
 }
+
+
+GCache::CacheType::const_iterator
+GCache::find(InsetGraphics const & inset) const
+{
+       CacheType::const_iterator it = cache->begin();
+       for (; it != cache->end(); ++it) {
+               if (it->second->referencedBy(inset))
+                       return it;
+       }
+       
+       return cache->end();
+}
+
+} // namespace grfx
index 9272e58e490cd09b2033d359e8132afdb4da4244..e6297572fe15a3f1ab7e569ee0a7c33d67ede439 100644 (file)
@@ -1,13 +1,18 @@
 // -*- C++ -*-
-/* This file is part of
- * =================================================
- * 
- *          LyX, The Document Processor
- *          Copyright 1995 Matthias Ettrich.
- *          Copyright 1995-2001 The LyX Team.
+/**
+ *  \file GraphicsCache.h
+ *  Copyright 2002 the LyX Team
+ *  Read the file COPYING
  *
- *          This file Copyright 2000 Baruch Even
- * ================================================= */
+ * \author Baruch Even <baruch.even@writeme.com>
+ * \author Angus Leeming <a.leeming@ic.ac.uk>
+ *
+ *  grfx::GCache is the manager of the image cache.
+ *  It is responsible for creating the grfx::GCacheItem's and maintaining them.
+ *  
+ *  grfx::GCache is a singleton class. It is possible to have only one
+ *  instance of it at any moment.
+ */
 
 #ifndef GRAPHICSCACHE_H
 #define GRAPHICSCACHE_H
 #pragma interface
 #endif
 
+#include "GraphicsTypes.h"
 #include <map>
-
 #include "LString.h"
-#include "GraphicsCacheItem.h"
 #include <boost/utility.hpp>
-#include <boost/smart_ptr.hpp>
 
-class GraphicsCacheItem;
+class InsetGraphics;
+
+namespace grfx {
 
-/** GraphicsCache is the manager of the image cache.
-    It is responsible of create the GraphicsCacheItem's and maintain them.
-    
-    GraphicsCache is a singleton class, there should be only one instance of
-    it at any moment.
-*/
-class GraphicsCache : boost::noncopyable {
+class GCacheItem;
+
+class GCache : boost::noncopyable {
 public:
-       /// Get the instance of the class.
-       static GraphicsCache & getInstance();
-       /// Public destructor due to compiler warnings.
-       ~GraphicsCache();
 
-       typedef boost::shared_ptr<GraphicsCacheItem> shared_ptr_item;
+       /// This is a singleton class. Get the instance.
+       static GCache & get();
+
+       ///
+       ~GCache();
+
+       /// Add a file to the cache (or modify an existing image).
+       void update(InsetGraphics const &);
+
+       /** Remove the data associated with this inset.
+        *  Called from the InsetGraphics d-tor.
+        */
+       void remove(InsetGraphics const &);
+
+       /** No processing of the image will take place until this call is
+        *  received.
+        */
+       void startLoading(InsetGraphics const &);
+
+       /** If (changed_background == true), then the background color of the
+        *  graphics inset has changed. Update all images.
+        *  Else, the preferred display type has changed.
+        *  Update the view of all insets whose display type is DEFAULT.
+        */
+       void changeDisplay(bool changed_background = false);
 
-       /// Add a file to the cache.
-       shared_ptr_item addFile(string const & filename);
+       /// Get the image referenced by a particular inset.
+       ImagePtr const image(InsetGraphics const &) const;
+
+       /// How far have we got in loading the image?
+       ImageStatus status(InsetGraphics const &) const;
 
 private:
-       /// Remove a cache item if it's count has gone to zero.
-       void removeFile(string const & filename);
-       
-       /// Private c-tor so we can control how many objects are instantiated.
-       GraphicsCache() {}
-       
-       ///
-       typedef std::map<string, shared_ptr_item> CacheType;
+       /** Make the c-tor private so we can control how many objects
+        *  are instantiated.
+        */
+       GCache();
+
+       /// The cache contains data of this type.
+       typedef boost::shared_ptr<GCacheItem> CacheItemType;
+
+       /** The cache contains one item per file, so use a map to find the
+        *  cache item quickly by filename.
+        *  Note that each cache item can have multiple views, potentially one
+        *  per inset that references the original file.
+        */
+       typedef std::map<string, CacheItemType> CacheType;
+
+       /// Search the cache by inset.
+       CacheType::const_iterator find(InsetGraphics const &) const;
        ///
-       CacheType cache;
-       
-       /** We need this so that an Item can tell the cache that it should be
-           deleted. (to call removeFile).
-           It also helps removing a warning gcc emits. */
-       friend class GraphicsCacheItem;
+       CacheType::iterator find(InsetGraphics const &);
+
+       /** Store a pointer to the cache so that we can forward declare
+        *  GCacheItem.
+        */
+       CacheType * cache;
 };
-#endif
+} // namespace grfx
+
+
+#endif // GRAPHICSCACHE_H
index a6ebdcc1cad1bbf750976541dcf7cf60313d2384..ed69a468e8973507625e3ccb9812629d211cab69 100644 (file)
@@ -1,13 +1,12 @@
-/* This file is part of
- * =================================================
- * 
- *          LyX, The Document Processor
- *          Copyright 1995 Matthias Ettrich.
- *          Copyright 1995-2001 The LyX Team.
+/*
+ * \file GraphicsCacheItem.C
+ * Copyright 2002 the LyX Team
+ * Read the file COPYING
  *
- * \author Baruch Even
+ * \author Baruch Even <baruch.even@writeme.com>
  * \author Herbert Voss <voss@lyx.org>
- * ================================================= */
+ * \author Angus Leeming <a.leeming@ic.ac.uk>
+ */
 
 #include <config.h>
 
 #pragma implementation
 #endif
 
-#include "graphics/GraphicsCacheItem.h"
 #include "graphics/GraphicsCache.h"
-#include "graphics/ImageLoaderXPM.h"
-#include "converter.h"
-#include "lyx_gui_misc.h"
+#include "graphics/GraphicsCacheItem.h"
+#include "graphics/GraphicsImage.h"
+#include "graphics/GraphicsParams.h"
+#include "graphics/GraphicsConverter.h"
+#include "insets/insetgraphics.h"
+#include "BufferView.h"
 #include "debug.h"
-#include "support/LAssert.h"
 #include "gettext.h"
-#include "lyxfunc.h"
-
-#include "frontends/support/LyXImage.h"
-
+#include "lyx_main.h" // for global dispatch method
+#include "support/LAssert.h"
 #include "support/filetools.h"
-#include "support/lyxlib.h"
+#include "frontends/Alert.h"
+
+// Very, Very UGLY!
+extern BufferView * current_view;
 
 using std::endl;
 
+namespace grfx {
+
+GCacheItem::GCacheItem(InsetGraphics const & inset, GParams const & params)
+       : filename_(params.filename), zipped_(false),
+         remove_loaded_file_(false), status_(WaitingToLoad)
+{
+       ModifiedItemPtr item(new ModifiedItem(inset, params, image_));
+       modified_images.push_back(item);
+}
+
+
+namespace {
+
+typedef GCacheItem::ModifiedItemPtr ModifiedItemPtr;
+class Compare_Params {
+public:
+       Compare_Params(GParams const & p) : p_(p) {}
+
+       bool operator()(ModifiedItemPtr const & ptr)
+       {
+               if (!ptr.get())
+                       return false;
+               return ptr->params() == p_;
+       }
+
+private:
+       GParams const & p_;
+};
+
+class Find_Inset {
+public:
+       Find_Inset(InsetGraphics const & i) : i_(i) {}
+
+       bool operator()(ModifiedItemPtr const & ptr)
+       {
+               if (!ptr.get())
+                       return false;
+               return ptr->referencedBy(i_);
+       }
+
+private:
+       InsetGraphics const & i_;
+};
+
+} // namespace anon
 
-/*
- * The order of conversion:
- *
- * The c-tor calls convertImage()
- * 
- * convertImage() verifies that we need to do conversion, if not it will just
- * call the loadImage()
- * if conversion is needed, it will initiate the conversion.
- *
- * When the conversion is completed imageConverted() is called, which in turn
- * calls loadImage().
- *
- * Since we currently do everything synchronously, convertImage() calls
- * imageConverted() right after it does the call to the conversion process.
-*/
 
-GraphicsCacheItem::GraphicsCacheItem(string const & filename)
-       : filename_(filename), imageStatus_(GraphicsCacheItem::Loading)
+void GCacheItem::modify(InsetGraphics const & inset, GParams const & params)
 {
-       bool success = convertImage(filename);
-       if (!success) // Conversion failed miserably (couldn't even start).
-               setStatus(ErrorConverting);
+       // Does this inset currently reference an existing ModifiedItem with
+       // different params?
+       // If so, remove the inset from the ModifiedItem's internal list
+       // of insets
+       ListType::iterator begin = modified_images.begin();
+       ListType::iterator end   = modified_images.end();
+       ListType::iterator it    = begin;
+       while (it != end) {
+               it = std::find_if(it, end, Find_Inset(inset));
+               if (it == end)
+                       break;
+               if ((*it)->params() != params) {
+                       (*it)->remove(inset);
+                       if ((*it)->empty())
+                               it = modified_images.erase(it);
+               }
+               ++it;
+       }
+
+       // Is there an existing ModifiedItem with these params?
+       // If so, add inset to the list of insets referencing this ModifiedItem
+       begin = modified_images.begin();
+       end   = modified_images.end();
+       it = std::find_if(begin, end, Compare_Params(params));
+       if (it != end) {
+               (*it)->add(inset);
+               return;
+       }
+       
+       // If no ModifiedItem exists with these params, then create one.
+       ModifiedItemPtr item(new ModifiedItem(inset, params, image_));
+       modified_images.push_back(item);
+
+       return;
 }
 
 
-GraphicsCacheItem::~GraphicsCacheItem()
-{}
+void GCacheItem::remove(InsetGraphics const & inset)
+{
+       // search the list of ModifiedItems for one referenced by this inset.
+       // If it is found, remove the reference.
+       // If the ModifiedItem is now referenced by no insets, remove it.
+       ListType::iterator begin = modified_images.begin();
+       ListType::iterator end   = modified_images.end();
+       ListType::iterator it = std::find_if(begin, end, Find_Inset(inset));
+
+       if (it == end)
+               return;
+
+       (*it)->remove(inset);
+       if ((*it)->empty()) {
+               modified_images.clear();
+       }
+}
 
 
-GraphicsCacheItem::ImageStatus 
-GraphicsCacheItem::getImageStatus() const
+void GCacheItem::startLoading(InsetGraphics const & inset)
 {
-       return imageStatus_;
+       if (status() != WaitingToLoad)
+               return;
+
+       // Check that the image is referenced by this inset
+       ListType::const_iterator begin = modified_images.begin();
+       ListType::const_iterator end   = modified_images.end();
+       ListType::const_iterator it =
+               std::find_if(begin, end, Find_Inset(inset));
+
+       if (it == end)
+               return;
+
+       if ((*it)->params().display == GParams::NONE)
+               return;
+
+       convertToDisplayFormat();
+}
+
+
+bool GCacheItem::empty() const
+{
+       return modified_images.empty();
 }
 
 
-void GraphicsCacheItem::setStatus(ImageStatus new_status)
+bool GCacheItem::referencedBy(InsetGraphics const & inset) const
 {
-       imageStatus_ = new_status;
+       // Is one of the list of ModifiedItems referenced by this inset?
+       ListType::const_iterator begin = modified_images.begin();
+       ListType::const_iterator end   = modified_images.end();
+       return std::find_if(begin, end, Find_Inset(inset)) != end;
 }
 
 
-LyXImage * 
-GraphicsCacheItem::getImage() const
+string const & GCacheItem::filename() const
 {
-       return image_.get();
+       return filename_;
 }
 
 
-void GraphicsCacheItem::imageConverted(bool success)
+ImagePtr const GCacheItem::image(InsetGraphics const & inset) const
 {
-       // Debug output
-       string text = "succeeded";
-       if (!success)
-               text = "failed";
-       lyxerr << "imageConverted, conversion " << text << "." << endl;
+       // find a ModifiedItem that is referenced by this inset.
+       ListType::const_iterator begin = modified_images.begin();
+       ListType::const_iterator end   = modified_images.end();
+       ListType::const_iterator it = 
+               std::find_if(begin, end, Find_Inset(inset));
+
+       // Someone's being daft.
+       if (it == end)
+               return ImagePtr();
+
+       // We are expressly requested to not render the image
+       if ((*it)->params().display == GParams::NONE)
+               return ImagePtr();
+
+       // If the original image has been loaded, return what's going on
+       // in the ModifiedItem
+       if (status() == Loaded)
+               return (*it)->image();
+
+       return ImagePtr();
+}
 
-       if (! success) {
-               lyxerr << "(GraphicsCacheItem::imageConverter) "
-                       "Error converting image." << endl;
-               setStatus(GraphicsCacheItem::ErrorConverting);
+
+ImageStatus GCacheItem::status(InsetGraphics const & inset) const
+{
+       // find a ModifiedItem that is referenced by this inset.
+       ListType::const_iterator begin = modified_images.begin();
+       ListType::const_iterator end   = modified_images.end();
+       ListType::const_iterator it = 
+               std::find_if(begin, end, Find_Inset(inset));
+
+       // Someone's being daft.
+       if (it == end)
+               return ErrorUnknown;
+
+       if (status() == Loaded)
+               return (*it)->status();
+
+       return status();
+}
+
+
+// Called internally only. Use to ascertain the status of the loading of the
+// original image. No scaling etc.
+ImageStatus GCacheItem::status() const
+{
+       return status_;
+}
+
+
+void GCacheItem::setStatus(ImageStatus new_status)
+{
+       status_ = new_status;
+
+       // Loop over all insets and tell the BufferView that it has changed.
+       typedef ModifiedItem::ListType::const_iterator inset_iterator;
+
+       ListType::const_iterator it  = modified_images.begin();
+       ListType::const_iterator end = modified_images.end();
+       for (; it != end; ++it) {
+               inset_iterator it2  = (*it)->insets.begin();
+               inset_iterator end2 = (*it)->insets.end();
+
+               for (; it2 != end2; ++it2) {
+                       InsetGraphics * inset =
+                               const_cast<InsetGraphics *>(*it2);
+
+                       // Use of current_view is very, very Evil!!
+                       current_view->updateInset(inset, false);
+               }
+       }
+}
+
+
+void GCacheItem::changeDisplay(bool changed_background)
+{
+       ListType::iterator begin = modified_images.begin();
+       ListType::iterator end   = modified_images.end();
+
+       // The background has changed. Change all modified images.
+       if (changed_background) {
+               for (ListType::iterator it = begin; it != end; ++it) {
+                       (*it)->setPixmap();
+               }
+               return;
+       }
+       
+       ListType temp_list;
+
+       for (ListType::iterator it = begin; it != end; ++it) {
+               // ModifiedItem::changeDisplay returns a full
+               // ModifiedItemPtr if any of the insets have display=DEFAULT
+               // and if that DEFAULT value has changed
+               ModifiedItemPtr new_item = (*it)->changeDisplay();
+               if (!new_item.get())
+                       continue;
+
+               temp_list.push_back(new_item);
+
+               // The original store may now be empty
+               if ((*it)->insets.empty()) {
+                       it = modified_images.erase(it);
+               }
+       }
+
+       if (temp_list.empty())
                return;
+
+       // Recombine new_list and modified_images.
+       begin = modified_images.begin();
+       end   = modified_images.end();
+
+       ListType::const_iterator tbegin = temp_list.begin();
+       ListType::const_iterator tend   = temp_list.end();
+
+       ListType append_list;
+
+       for (ListType::const_iterator tit = tbegin; tit != tend; ++tit) {
+               GParams const & params = (*tit)->params();
+               ListType::iterator it =
+                       std::find_if(begin, end, Compare_Params(params));
+               if (it == end)
+                       append_list.push_back(*tit);
+               else
+                       (*it)->insets.merge((*tit)->insets);
        }
 
+       if (append_list.empty())
+               return;
+
+       modified_images.splice(modified_images.end(), append_list);
+}
+
+
+void GCacheItem::imageConverted(string const & file_to_load)
+{
+       bool const success =
+               (!file_to_load.empty() && IsFileReadable(file_to_load));
+
+       string const text = success ? "succeeded" : "failed";
+       lyxerr[Debug::GRAPHICS] << "Image conversion " << text << "." << endl;
+
+       if (!success) {
+               setStatus(ErrorConverting);
+
+               if (zipped_)
+                       lyx::unlink(unzipped_filename_);
+
+               return;
+       }
+
+       cc_.disconnect();
+
        // Do the actual image loading from file to memory.
-       loadImage();    
+       file_to_load_ = file_to_load;
+
+       loadImage();
+}
+
+
+// This function gets called from the callback after the image has been
+// converted successfully.
+void GCacheItem::loadImage()
+{
+       setStatus(Loading);
+       lyxerr[Debug::GRAPHICS] << "Loading image." << endl;
+
+       // Connect a signal to this->imageLoaded and pass this signal to
+       // GImage::loadImage.
+       SignalLoadTypePtr on_finish;
+       on_finish.reset(new SignalLoadType);
+       cl_ = on_finish->connect(SigC::slot(this, &GCacheItem::imageLoaded));
+
+       image_ = GImage::newImage();
+       image_->load(file_to_load_, on_finish);
+}
+
+
+void GCacheItem::imageLoaded(bool success)
+{
+       string const text = success ? "succeeded" : "failed";
+       lyxerr[Debug::GRAPHICS] << "Image loading " << text << "." << endl;
+
+       // Clean up after loading.
+       if (zipped_)
+               lyx::unlink(unzipped_filename_);
+
+       if (remove_loaded_file_ && unzipped_filename_ != file_to_load_)
+               lyx::unlink(file_to_load_);
+
+       cl_.disconnect();
+
+       if (!success) {
+               setStatus(ErrorLoading);
+               return;
+       }
+
+       setStatus(Loaded);
+
+       // Loop over the list of modified images and create them.
+       ListType::iterator it  = modified_images.begin();
+       ListType::iterator end = modified_images.end();
+       for (; it != end; ++it) {
+               (*it)->modify(image_);
+       }
 }
 
 
@@ -106,35 +395,44 @@ namespace {
 
 string const findTargetFormat(string const & from)
 {
-       typedef ImageLoader::FormatList FormatList;
-       FormatList formats = ImageLoaderXPM().loadableFormats();
-       lyx::Assert(formats.size() > 0); // There must be a format to load from.
+       typedef GImage::FormatList FormatList;
+       FormatList const & formats = GImage::loadableFormats();
+
+       // There must be a format to load from. 
+       lyx::Assert(!formats.empty());
 
-       FormatList::const_iterator iter = formats.begin();
-       FormatList::const_iterator end  = formats.end();
+       grfx::GConverter const & graphics_converter = grfx::GConverter::get();
 
-       for (; iter != end; ++iter) {
-               if (converters.isReachable(from, *iter))
+       FormatList::const_iterator it  = formats.begin();
+       FormatList::const_iterator end = formats.end();
+       for (; it != end; ++it) {
+               if (graphics_converter.isReachable(from, *it))
                        break;
        }
-       if (iter == end) {
-               // We do not know how to convert the image to something loadable.
-               lyxerr << "ERROR: Do not know how to convert image." << endl;
+
+       if (it == end)
                return string();
-       }
-       return (*iter);
+
+       return *it;
 }
 
 } // anon namespace
 
        
-bool GraphicsCacheItem::convertImage(string const & filename)
+void GCacheItem::convertToDisplayFormat()
 {
-       setStatus(GraphicsCacheItem::Converting);
-#warning shadowing class variable (Lgb)
-       // Is this needed at all?
-       string filename_ = string(filename);
-       lyxerr << "try to convert image file: " << filename_ << endl;
+       setStatus(Converting);
+       string filename = filename_; // Make a local copy in case we unzip it
+       string const displayed_filename = MakeDisplayPath(filename_);
+
+       // First, check that the file exists!
+       if (!IsFileReadable(filename)) {
+               Alert::alert(_("File ") + displayed_filename,
+                          _("\nisn't readable or doesn't exist!"));
+               setStatus(ErrorNoFile);
+               return;
+       }
+       
 // maybe that other zip extensions also be useful, especially the
 // ones that may be declared in texmf/tex/latex/config/graphics.cfg.
 // for example:
@@ -146,61 +444,213 @@ bool GraphicsCacheItem::convertImage(string const & filename)
            \DeclareGraphicsRule{.eps.gz}{eps}{.eps.bb}{}}}%
    -----------snip-------------*/
 
-       lyxerr << "GetExtension: " << GetExtension(filename_) << endl;
-       bool const zipped = zippedFile(filename_);
-       if (zipped)
-           filename_ = unzipFile(filename_);
-       string const from = getExtFromContents(filename_);      // get the type
-       lyxerr << "GetExtFromContents: " << from << endl;
-       string const to = findTargetFormat(from);
-       lyxerr << "from: " << from << " -> " << to << endl;
-       if (to.empty()) 
-               return false;
-       // manage zipped files. unzip them first into the tempdir
+       lyxerr[Debug::GRAPHICS]
+               << "Attempting to convert image file: " << displayed_filename
+               << "\nwith recognised extension: " << GetExtension(filename)
+               << "." << endl;
+
+       zipped_ = zippedFile(filename);
+       if (zipped_) {
+               filename = unzipFile(filename);
+               unzipped_filename_ = filename;
+       }
+
+       string const from = getExtFromContents(filename);
+       string const to   = grfx::findTargetFormat(from);
+
+       lyxerr[Debug::GRAPHICS]
+               << "The file contains " << from << " format data." << endl;
+
+       if (to.empty()) {
+               Alert::alert(_("Unable to convert file ") +
+                            displayed_filename +
+                            _(" to a loadable format."));
+               setStatus(ErrorConverting);
+               return;
+       }
+
        if (from == to) {
                // No conversion needed!
-               // Saves more than just time: prevents the deletion of
-               // the "to" file after loading when it's the same as the "from"!
-               tempfile = filename_;
-               loadImage();    
-               return true;
+               lyxerr[Debug::GRAPHICS] << "No conversion needed!" << endl;
+               file_to_load_ = filename;
+               loadImage();
+               return;
        }
+
+       lyxerr[Debug::GRAPHICS] << "Converting it to " << to << " format." << endl;
+
        // Take only the filename part of the file, without path or extension.
-       string temp = OnlyFilename(filename_);
-       temp = ChangeExtension(filename_, string());
+       string const temp = ChangeExtension(OnlyFilename(filename), string());
        
-       // Add some stuff to have it a unique temp file.
-       // This tempfile is deleted in loadImage after it is loaded to memory.
-       tempfile = lyx::tempName(string(), temp);
+       // Add some stuff to create a uniquely named temporary file.
+       // This file is deleted in loadImage after it is loaded into memory.
+       string const to_file_base = lyx::tempName(string(), temp);
+       remove_loaded_file_ = true;
+
        // Remove the temp file, we only want the name...
-       lyx::unlink(tempfile);
-       bool result = converters.convert(0, filename_, tempfile, from, to);
-       tempfile.append(".xpm");
-       // For now we are synchronous
-       imageConverted(result);
-       // Cleanup after the conversion.
-       lyx::unlink(tempfile);
-       if (zipped)
-           lyx::unlink(filename_);
-       tempfile.erase();
-       return true;
+       lyx::unlink(to_file_base);
+
+       // Connect a signal to this->imageConverted and pass this signal to
+       // the graphics converter so that we can load the modified file
+       // on completion of the conversion process.
+       SignalConvertTypePtr on_finish;
+       on_finish.reset(new SignalConvertType);
+       cc_ = on_finish->connect(SigC::slot(this, &GCacheItem::imageConverted));
+
+       GConverter & graphics_converter = GConverter::get();
+       graphics_converter.convert(filename, to_file_base, from, to, on_finish);
 }
 
 
-// This function gets called from the callback after the image has been
-// converted successfully.
-void
-GraphicsCacheItem::loadImage()
+ModifiedItem::ModifiedItem(InsetGraphics const & new_inset,
+                          GParams const &  new_params,
+                          ImagePtr const & new_image)
+       : status_(ScalingEtc)
+{
+       p_.reset(new GParams(new_params));
+       insets.push_back(&new_inset);
+       modify(new_image);
+}
+
+
+void ModifiedItem::add(InsetGraphics const & inset)
+{
+       insets.push_back(&inset);
+       insets.sort();
+}
+
+
+void ModifiedItem::remove(InsetGraphics const & inset)
 {
-       lyxerr << "Loading XPM Image... ";
+       ListType::iterator begin = insets.begin();
+       ListType::iterator end   = insets.end();
+       ListType::iterator it    = std::remove(begin, end, &inset);
+       insets.erase(it, end);
+}
+
+
+bool ModifiedItem::referencedBy(InsetGraphics const & inset) const
+{
+       ListType::const_iterator begin = insets.begin();
+       ListType::const_iterator end   = insets.end();
+       return std::find(begin, end, &inset) != end;
+}
+
+
+ImagePtr const ModifiedItem::image() const
+{
+       if (modified_image_.get())
+               return modified_image_;
+
+       return original_image_;
+}
+
+
+void ModifiedItem::modify(ImagePtr const & new_image)
+{
+       if (!new_image.get()) 
+               return;
+
+       original_image_ = new_image;
+       modified_image_.reset(original_image_->clone());
+
+       if (params().display == GParams::NONE) {
+               setStatus(Loaded);
+               return;
+       }
+
+       setStatus(ScalingEtc);
+       modified_image_->clip(params());
+       modified_image_->rotate(params());
+       modified_image_->scale(params());
+       setPixmap();
+}
+
+
+void ModifiedItem::setPixmap()
+{
+       if (!modified_image_.get()) 
+               return;
+
+       if (params().display == GParams::NONE) {
+               setStatus(Loaded);
+               return;
+       }
 
-       ImageLoaderXPM imageLoader;
-       if (imageLoader.loadImage(tempfile) == ImageLoader::OK) {
-               lyxerr << "Success." << endl;
-               image_.reset(imageLoader.getImage());
-               setStatus(GraphicsCacheItem::Loaded);
+       bool const success = modified_image_->setPixmap(params());
+
+       if (success) {
+               setStatus(Loaded);
        } else {
-               lyxerr << "Loading " << tempfile << "Failed" << endl;
-               setStatus(GraphicsCacheItem::ErrorReading);
+               modified_image_.reset();
+               setStatus(ErrorScalingEtc);
        }
 }
+
+
+void ModifiedItem::setStatus(ImageStatus new_status)
+{
+       status_ = new_status;
+
+       // Tell the BufferView that the inset has changed.
+       // Very, Very Ugly!!
+       ListType::const_iterator it  = insets.begin();
+       ListType::const_iterator end = insets.end();
+       for (; it != end; ++it) {
+               InsetGraphics * inset = const_cast<InsetGraphics *>(*it);
+               current_view->updateInset(inset, false);
+       }
+}
+
+
+namespace {
+
+struct Params_Changed {
+
+       Params_Changed(GParams const & p) : p_(p) {}
+
+       bool operator()(InsetGraphics const * inset)
+       {
+               return GParams(inset->params()) != p_;
+       }
+
+private:
+       GParams p_;
+};
+
+} // namespace anon
+
+// changeDisplay returns an initialised ModifiedItem if any of the insets
+// have display == DEFAULT and if that DEFAULT value has changed.
+// If this occurs, then (this) has these insets removed.
+ModifiedItemPtr ModifiedItem::changeDisplay()
+{
+       // Loop over the list of insets. Compare the updated params for each
+       // with params(). If different, move into a new list.
+       ListType::iterator begin = insets.begin();
+       ListType::iterator end   = insets.end();
+       ListType::iterator it =
+               std::remove_if(begin, end, Params_Changed(params()));
+
+       if (it == end) {
+               // No insets have changed params
+               return ModifiedItemPtr();
+       }
+
+       // it -> end have params that are changed. Move to the new list.
+       ListType new_insets;
+       new_insets.insert(new_insets.begin(), it, end);
+       insets.erase(it, end);
+
+       // Create a new ModifiedItem with these new params. Note that
+       // the only params that have changed are the display ones,
+       // so we don't need to crop, rotate, scale.
+       ModifiedItemPtr new_item(new ModifiedItem(*this));
+       new_item->insets = new_insets;
+       *(new_item->p_)  = GParams((*new_insets.begin())->params());
+
+       new_item->setPixmap();
+       return new_item;
+}
+
+} // namespace grfx
index 6264f3b784792fe303e6e0f0ed5696df3602e192..9d1e0e12d5fb1045ae371c833612aab1deaee66d 100644 (file)
@@ -1,13 +1,30 @@
 // -*- C++ -*-
-/* This file is part of
- * =================================================
- * 
- *          LyX, The Document Processor
- *          Copyright 1995 Matthias Ettrich.
- *          Copyright 1995-2001 The LyX Team.
+/*
+ * \file GraphicsCacheItem.h
+ * Copyright 2002 the LyX Team
+ * Read the file COPYING
  *
- *          This file Copyright 2000 Baruch Even
- * ================================================= */
+ * \author Baruch Even <baruch.even@writeme.com>
+ * \author Angus Leeming <a.leeming@ic.ac.uk>
+ *
+ * The graphics cache is a container of GCacheItems. Each GCacheItem, defined
+ * here represents a separate image file. However, each file can be viewed in
+ * different ways (different sizes, rotations etc), so each GCacheItem itself
+ * contains a list of ModifiedItems, also defined here. Each ModifiedItem
+ * has a GParams variable that defines the way it will be viewed. It also
+ * contains a list of the graphics insets that refer to it, so calls through
+ * the GCache to GCacheItem ultimately return the loading status and image
+ * for that particular graphics inset.
+ *
+ * The graphics cache supports fully asynchronous:
+ * file conversion to a loadable format;
+ * file loading.
+ *
+ * Whether you get that, of course, depends on grfx::GConverter and on the
+ * grfx::GImage-derived image class.
+ *
+ * Image modification (scaling, rotation etc) is blocking.
+ */
 
 #ifndef GRAPHICSCACHEITEM_H
 #define GRAPHICSCACHEITEM_H
 #pragma interface
 #endif
 
-#include XPM_H_LOCATION
+#include "GraphicsTypes.h" 
+#include <list>
 #include "LString.h"
-
 #include <boost/utility.hpp>
 #include <boost/smart_ptr.hpp>
-
 #include <sigc++/signal_system.h>
 
-class LyXImage;
+class InsetGraphics;
+
+namespace grfx {
 
-/// A GraphicsCache item holder.
-class GraphicsCacheItem : boost::noncopyable {
+class GParams;
+class ModifiedItem;
+/// A grfx::GCache item holder.
+class GCacheItem : boost::noncopyable, public SigC::Object {
 public:
-       /// c-tor
-       GraphicsCacheItem(string const & filename);
-       /// d-tor, frees the image structures.
-       ~GraphicsCacheItem();
-       
-       /// Return a pixmap that can be displayed on X server.
-       LyXImage * getImage() const; 
+       /// the GCacheItem contains data of this type.
+       typedef boost::shared_ptr<ModifiedItem> ModifiedItemPtr;
+
        ///
-       enum ImageStatus {
-               ///
-               Loading = 1,
-               ///
-               Converting,
-               ///
-               ErrorConverting,
-               ///
-               ErrorReading,
-               ///
-               UnknownError,
-               ///
-               Loaded
-       };
+       GCacheItem(InsetGraphics const &, GParams const &);
+
+       /// The params have changed (but still refer to this file).
+       void modify(InsetGraphics const &, GParams const &);
+
+       /// Remove the reference to this inset.
+       void remove(InsetGraphics const &);
+
+       /// It's in the cache. Now start the loading process.
+       void startLoading(InsetGraphics const &);
+
+       /// Is the cache item referenced by any insets at all?
+       bool empty() const;
+
+       /// The name of the original image file.
+       string const & filename() const;
        
-       /// Is the pixmap ready for display?
-       ImageStatus getImageStatus() const; 
+       /// Is this image file referenced by this inset?
+       bool referencedBy(InsetGraphics const &) const;
 
-       /** Get a notification when the image conversion is done.
-           used by an internal callback mechanism.
-       */
-       void imageConverted(bool success);
+       /** Returns the image referenced by this inset (or an empty container
+        *  if it's not yet loaded.
+        */
+       ImagePtr const image(InsetGraphics const &) const;
+
+       /// The loading status of the image referenced by this inset.
+       ImageStatus status(InsetGraphics const &) const;
+
+       /** If (changed_background == true), then the background color of the
+        *  graphics inset has changed. Update all images.
+        *  Else, the preferred display type has changed.
+        *  Update the view of all insets whose display type is DEFAULT.
+        */
+       void changeDisplay(bool changed_background);
 
 private:
-       /** Start image conversion process, checks first that it is necessary
-        *  if necessary will start an (a)synchronous task and notify upon
-        *  completion by calling imageConverted(bool) where true is for success
-        *  and false is for a failure.
+       /** Start the image conversion process, checking first that it is
+        *  necessary. If it is necessary, then a conversion task is started.
+        *  GCacheItem asumes that the conversion is asynchronous and so
+        *  passes a Signal to the converting routine. When the conversion
+        *  is finished, this Signal is emitted, returning the converted
+        *  file to this->imageConverted.
         *
-        *  Returns a bool to denote success or failure of starting the conversion
-        *  task.
+        *  If no file conversion is needed, then convertToDisplayFormat() calls
+        *  loadImage() directly.
+        *
+        *  convertToDisplayFormat() will set the loading status flag as
+        *  approriate through calls to setStatus().
         */
-       bool convertImage(string const & filename);
+       void convertToDisplayFormat();
 
-       /// Load the image into memory, this gets called from imageConverted(bool).
+       /** Load the image into memory. This is called either from
+        *  convertToDisplayFormat() direct or from imageConverted().
+        */
        void loadImage();
 
-       /// Sets the status of the image, in the future will also notify listeners
-       /// that the status is updated.
+       /** Get a notification when the image conversion is done.
+        *  Connected to a signal on_finish_ which is passed to
+        *  GConverter::convert.
+        */
+       void imageConverted(string const & file_to_load);
+
+       /** Get a notification when the image loading is done.
+        *  Connected to a signal on_finish_ which is passed to
+        *  GImage::loadImage.
+        */
+       void imageLoaded(bool);
+
+       /// How far have we got in loading the original, unmodified image?
+       ImageStatus status() const; 
+
+       /** Sets the status of the loading process. Also notifies
+        *  listeners that the status has chacnged.
+        */
        void setStatus(ImageStatus new_status);
 
-       /** The filename we refer too.
-           This is used when removing ourselves from the cache.
-       */
+       /// The filename we refer too.
        string filename_;
-       /// The temporary file that we use
-       string tempfile;
-       /// The image status
-       ImageStatus imageStatus_;
-       /// The image (if it got loaded)
-       boost::scoped_ptr<LyXImage> image_;
+       /// Is the file compressed?
+       bool zipped_;
+       /// If so, store the uncompressed file in this temporary file.
+       string unzipped_filename_;
+       /// What file are we trying to load?
+       string file_to_load_;
+       /** Should we delete the file after loading? True if the file is
+        *  the result of a conversion process.
+        */
+       bool remove_loaded_file_;
+
+       /// The original, unmodified image and its loading status.
+       ImagePtr image_;
+       ///
+       ImageStatus status_;
+
+       /** A SignalLoadTypePtr is connected to this->imageLoaded and
+        *  then passed to ImagePtr::load.
+        *  When the image has been loaded, the signal is emitted.
+        *
+        *  We pass a shared_ptr because it is eminently possible for the
+        *  ModifiedItem to be destructed before the loading is complete and
+        *  the signal must remain in scope. It doesn't matter if the slot
+        *  disappears, SigC takes care of that.
+        */
+       typedef SigC::Signal1<void, bool> SignalLoadType;
+       ///
+       typedef boost::shared_ptr<SignalLoadType> SignalLoadTypePtr;
+
+       /// The connection of the signal passed to ImagePtr::loadImage.
+       SigC::Connection cl_;
+
+       /** A SignalConvertTypePtr is connected to this->imageConverted and
+        *  then passed to GConverter::convert.
+        *  When the image has been converted to a loadable format, the signal
+        *  is emitted, returning the name of the loadable file to
+        *  imageConverted.
+        */
+       typedef SigC::Signal1<void, string const &> SignalConvertType;
+       ///
+       typedef boost::shared_ptr<SignalConvertType> SignalConvertTypePtr;
+
+       /// The connection of the signal passed to GConverter::convert.
+       SigC::Connection cc_;
+
+       /// The list of all modified images.
+       typedef std::list<ModifiedItemPtr> ListType;
+       ///
+       ListType modified_images;
 };
 
-#endif
+
+class ModifiedItem {
+public:
+       ///
+       ModifiedItem(InsetGraphics const &, GParams const &, ImagePtr const &);
+
+       ///
+       GParams const & params() { return *p_.get(); }
+
+       /// Add inset to the list of insets.
+       void add(InsetGraphics const & inset);
+
+       /// Remove inset from the list of insets.
+       void remove(InsetGraphics const & inset);
+
+       ///
+       bool empty() const { return insets.empty(); }
+
+       /// Is this ModifiedItem referenced by inset?
+       bool referencedBy(InsetGraphics const & inset) const;
+
+       ///
+       ImagePtr const image() const;
+
+       /// How far have we got in loading the modified image?
+       ImageStatus status() const { return status_; }
+
+       /** Called from GCacheItem once the raw image is loaded.
+        *  Modifies the image in accord with p_.
+        */
+       void modify(ImagePtr const &);
+
+       /// Updates the pixmap.
+       void setPixmap();
+
+       /** changeDisplay returns a full ModifiedItemPtr if any of the
+        *  insets have display=DEFAULT and if that DEFAULT value has
+        *  changed.
+        *  If this occurs, then this has these insets removed.
+        */
+       boost::shared_ptr<ModifiedItem> changeDisplay();
+
+       ///
+       typedef std::list<InsetGraphics const *> ListType;
+
+       /// Make these accessible for changeDisplay.
+       ListType insets;
+
+private:
+       /** Sets the status of the loading process. Also notifies
+        *  listeners that the status has changed.
+        */
+       void setStatus(ImageStatus new_status);
+
+       /// The original and modified images and its loading status.
+       ImagePtr original_image_;
+       ///
+       ImagePtr modified_image_;
+       ///
+       ImageStatus status_;
+       /// 
+       boost::shared_ptr<GParams> p_;
+};
+
+} // namespace grfx
+
+#endif // GRAPHICSCACHEITEM_H
diff --git a/src/graphics/GraphicsConverter.C b/src/graphics/GraphicsConverter.C
new file mode 100644 (file)
index 0000000..ea028a7
--- /dev/null
@@ -0,0 +1,293 @@
+/*
+ * \file GraphicsConverter.C
+ * Copyright 2002 the LyX Team
+ * Read the file COPYING
+ *
+ * \author Angus Leeming <a.leeming@ic.ac.uk>
+ */
+
+#include <config.h>
+
+#ifdef __GNUG__
+#pragma implementation
+#endif
+
+#include "GraphicsConverter.h"
+
+#include "converter.h"
+#include "debug.h"
+#include "gettext.h"
+
+#include "frontends/Alert.h"
+
+#include "support/filetools.h"
+#include "support/forkedcall.h"
+#include "support/path.h"
+
+#include <fstream>
+
+namespace {
+
+string const move_file(string const & from_file, string const & to_file)
+{
+       if (from_file == to_file)
+               return string();
+
+       ostringstream command;
+       command << "fromfile=" << from_file << "\n"
+               << "tofile="   << to_file << "\n\n"
+               << "'mv' -f ${fromfile} ${tofile}\n"
+               << "if [ $? -ne 0 ]; then\n"
+               << "\t'cp' -f ${fromfile} ${tofile}\n"
+               << "\tif [ $? -ne 0 ]; then\n"
+               << "\t\texit 1\n"
+               << "\tfi\n"
+               << "\t'rm' -f ${fromfile}\n"
+               << "fi\n";
+
+       return command.str().c_str();
+}
+
+} // namespace anon
+
+namespace grfx {
+
+GConverter & GConverter::get()
+{
+       static GConverter singleton;
+       return singleton;
+}
+       
+
+bool GConverter::isReachable(string const & from_format_name,
+                            string const & to_format_name) const
+{
+       return converters.isReachable(from_format_name, to_format_name);
+}
+
+
+void GConverter::convert(string const & from_file, string const & to_file_base,
+                        string const & from_format, string const & to_format,
+                        SignalTypePtr on_finish)
+{
+       // The conversion commands are stored in a stringstream
+       ostringstream script;
+       script << "#!/bin/sh\n";
+
+       bool const success = build_script(from_file, to_file_base,
+                                         from_format, to_format, script);
+
+       if (!success) {
+               lyxerr[Debug::GRAPHICS]
+                       << "Unable to build the conversion script" << std::endl;
+               on_finish->emit(string());
+               return;
+       }
+
+       lyxerr[Debug::GRAPHICS] << "Conversion script:\n\n"
+                               << script.str().c_str() << "\n" << std::endl;
+
+       // Output the script to file.
+       static int counter = 0;
+       string const script_file = OnlyPath(to_file_base) + "lyxconvert" +
+               tostr(counter++) + ".sh";
+
+       std::ofstream fs(script_file.c_str());
+       if (!fs.good()) {
+               // Unable to output the conversion script to file.
+               on_finish->emit(string());
+               return;
+       }
+               
+       fs << script.str().c_str();
+       fs.close();
+
+       // Create a dummy command for ease of understanding of the
+       // list of forked processes.
+       // Note that 'sh ' is absolutely essential, or execvp will fail.
+       string const script_command =
+               "sh " + script_file + " " +
+               OnlyFilename(from_file) + " " + to_format;
+
+       string const to_file =
+               ChangeExtension(to_file_base, formats.extension(to_format));
+
+       // Launch the conversion process.
+       ConvProcessPtr shared_ptr;
+       shared_ptr.reset(new ConvProcess(script_file, script_command, 
+                                        to_file, on_finish));
+       all_processes_.push_back(shared_ptr);
+}
+
+
+namespace {
+
+typedef boost::shared_ptr<ConvProcess> ConvProcessPtr;
+class Find_Ptr {
+public:
+       Find_Ptr(ConvProcess * ptr) : ptr_(ptr) {}
+
+       bool operator()(ConvProcessPtr const & ptr)
+       {
+               return ptr.get() == ptr_;
+       }
+
+private:
+       ConvProcess * ptr_;
+};
+
+} // namespace anon
+
+void GConverter::erase(ConvProcess * process)
+{
+       std::list<ConvProcessPtr>::iterator begin = all_processes_.begin();
+       std::list<ConvProcessPtr>::iterator end   = all_processes_.end();
+       std::list<ConvProcessPtr>::iterator it =
+               std::find_if(begin, end, Find_Ptr(process));
+
+       if (it == end)
+               return;
+
+       all_processes_.erase(it);
+}
+
+
+bool GConverter::build_script(string const & from_file, 
+                             string const & to_file_base,
+                             string const & from_format,
+                             string const & to_format,
+                             ostringstream & script) const
+{
+       typedef Converters::EdgePath EdgePath;
+
+       string const to_file = ChangeExtension(to_file_base,
+                                              formats.extension(to_format));
+
+       if (from_format == to_format) {
+               script << move_file(QuoteName(from_file), QuoteName(to_file));
+               return true;
+       }
+
+       EdgePath edgepath = converters.getPath(from_format, to_format);
+
+       if (edgepath.empty()) {
+               Alert::alert(_("Cannot convert file"),
+                          _("No information for converting from ")
+                          + formats.prettyName(from_format) + _(" to ")
+                          + formats.prettyName(to_format));
+               return false;
+       }
+
+       // Create a temporary base file-name for all intermediate steps.
+       // Remember to remove the temp file because we only want the name...
+       static int counter = 0;
+       string const tmp = "gconvert" + tostr(counter++);
+       string const to_base = lyx::tempName(string(), tmp);
+       lyx::unlink(to_base);
+
+       string outfile = from_file;
+
+       // The conversion commands may contain these tokens that need to be
+       // changed to infile, infile_base, outfile respectively.
+       string const token_from("$$i");
+       string const token_base("$$b");
+       string const token_to("$$o");
+
+       EdgePath::const_iterator it  = edgepath.begin();
+       EdgePath::const_iterator end = edgepath.end();
+       for (; it != end; ++it) {
+               Converter const & conv = converters.get(*it);
+
+               // Build the conversion command
+               string const infile      = outfile;
+               string const infile_base = ChangeExtension(infile, string());
+               outfile = ChangeExtension(to_base, conv.To->extension());
+
+               // Store these names in the shell script
+               script << "infile="      << QuoteName(infile) << '\n'
+                      << "infile_base=" << QuoteName(infile_base) << '\n'
+                      << "outfile="     << QuoteName(outfile) << '\n';
+
+               string command = conv.command;
+               command = subst(command, token_from, "${infile}");
+               command = subst(command, token_base, "${infile_base}");
+               command = subst(command, token_to,   "${outfile}");
+
+               // Store in the shell script
+               script << "\n" << command << "\n\n";
+
+               // Test that this was successful. If not, remove 
+               // ${outfile} and exit the shell script
+               script << "if [ $? -ne 0 ]; then\n"
+                      << "\t'rm' -f ${outfile}\n"
+                      << "\texit 1\n"
+                      << "fi\n\n";
+
+               // Test that the outfile exists.
+               // ImageMagick's convert will often create ${outfile}.0,
+               // ${outfile}.1.
+               // If this occurs, move ${outfile}.0 to ${outfile}
+               // and delete ${outfile}.?
+               script << "if [ ! -f ${outfile} ]; then\n"
+                      << "\tif [ -f ${outfile}.0 ]; then\n"
+                      << "\t\t'mv' -f ${outfile}.0 ${outfile}\n"
+                      << "\t\t'rm' -f ${outfile}.?\n"
+                      << "\telse\n"
+                      << "\t\texit 1\n"
+                      << "\tfi\n"
+                      << "fi\n\n";
+
+               // Delete the infile, if it isn't the original, from_file.
+               if (infile != from_file) {
+                       script << "'rm' -f ${infile}\n\n";
+               }
+       }
+
+       // Move the final outfile to to_file
+       script << move_file("${outfile}", QuoteName(to_file));
+
+       return true;
+}
+
+
+ConvProcess::ConvProcess(string const & script_file, 
+                        string const & script_command,
+                        string const & to_file, SignalTypePtr on_finish)
+       : script_file_(script_file), to_file_(to_file), on_finish_(on_finish)
+{
+       Forkedcall::SignalTypePtr convert_ptr;
+       convert_ptr.reset(new Forkedcall::SignalType);
+
+       convert_ptr->connect(SigC::slot(this, &ConvProcess::converted));
+
+       Forkedcall call;
+       int retval = call.startscript(script_command, convert_ptr);
+       if (retval > 0) {
+               // Unable to even start the script, so clean-up the mess!
+               converted(string(), 0, 1);
+       }
+}
+
+
+void ConvProcess::converted(string /* cmd */, pid_t /* pid */, int retval)
+{
+       // Clean-up behind ourselves
+       lyx::unlink(script_file_);
+
+       if (retval > 0) {
+               lyx::unlink(to_file_);
+               to_file_.erase();
+       }
+
+       if (on_finish_.get()) {
+               on_finish_->emit(to_file_);
+       }
+
+       grfx::GConverter::get().erase(this);
+}
+
+
+} // namespace grfx
diff --git a/src/graphics/GraphicsConverter.h b/src/graphics/GraphicsConverter.h
new file mode 100644 (file)
index 0000000..536a840
--- /dev/null
@@ -0,0 +1,121 @@
+// -*- C++ -*-
+/*
+ * \file GraphicsConverter.h
+ * Copyright 2002 the LyX Team
+ * Read the file COPYING
+ *
+ * \author Angus Leeming <a.leeming@ic.ac.uk>
+ *
+ * class grfx::GConverter enables graphics files to be converted asynchronously
+ * to a loadable format. It does this by building a shell script of all
+ * the conversion commands needed for the transformation. This script is then
+ * sent to the forked calls controller for non-blocking execution. When it
+ * is finished a signal is emitted, thus informing us to proceed with the
+ * loading of the image.
+ *
+ * Ultimately, this class should be wrapped back into Dekel's converter class.
+ */
+
+#ifndef GRAPHICSCONVERTER_H
+#define GRAPHICSCONVERTER_H
+
+#include "LString.h"
+#include "Lsstream.h"
+#include <boost/smart_ptr.hpp>
+#include <boost/utility.hpp>
+#include <sigc++/signal_system.h>
+#include <list>
+
+#ifdef __GNUG__
+#pragma interface
+#endif
+
+namespace grfx {
+
+class GConverter : boost::noncopyable {
+public:
+
+       /// This is a singleton class. Get the instance.
+       static GConverter & get();
+
+       /// Can the conversion be performed?
+       bool isReachable(string const & from_format_name,
+                        string const & to_format_name) const;
+
+       /** Convert the file and at the end return it by emitting this signal
+        *  If successful, the returned string will be the name of the
+        *  converted file (to_file_base + extension(to_format_name)).
+        *  If unsuccessful, the string will be empty.
+        */
+       typedef SigC::Signal1<void, string const &> SignalType;
+       ///
+       typedef boost::shared_ptr<SignalType> SignalTypePtr;
+       ///
+       void convert(string const & from_file,   string const & to_file_base,
+                    string const & from_format, string const & to_format,
+                    SignalTypePtr on_finish);
+
+private:
+       /** Make the c-tor private so we can control how many objects
+        *  are instantiated.
+        */
+       GConverter() {}
+
+       /** Build the conversion script, returning true if able to build it.
+        *  The script is output to the ostringstream 'script'.
+        */
+       bool build_script(string const & from_file, string const & to_file_base,
+                         string const & from_format, string const & to_format,
+                         ostringstream & script) const;
+
+       /** Remove the ConvProcess from the list of all processes.
+        *  Called by ConvProcess::converted.
+        */
+       friend class ConvProcess;
+       ///
+       void erase(ConvProcess *);
+
+       /// The list of all conversion processs
+       typedef boost::shared_ptr<ConvProcess> ConvProcessPtr;
+       ///
+       std::list<ConvProcessPtr> all_processes_;
+};
+
+
+/// Each ConvProcess represents a single conversion process.
+struct ConvProcess : public SigC::Object
+{
+       ///
+       typedef GConverter::SignalTypePtr SignalTypePtr;
+
+       /** Each ConvProcess represents a single conversion process.
+        *  It is passed :
+        *  1. The name of the script_file, which it deletes once the
+        *     conversion is comlpeted;
+        *  2. The script command itself, which it passes on to the forked
+        *     call process;
+        *  3. The name of the output file, which it returns to the calling
+        *     process on successfull completion, by emitting
+        *  4. The signal on_finish.
+        */
+       ConvProcess(string const & script_file, string const & script_command,
+                   string const & to_file, SignalTypePtr on_finish);
+
+       /** This method is connected to a signal passed to the forked call
+        *  class, passing control back here when the conversion is completed.
+        *  Cleans-up the temporary files, emits the on_finish signal and
+        *  removes the ConvProcess from the list of all processes.
+        */
+       void converted(string cmd, pid_t pid, int retval);
+
+       ///
+       string script_file_;
+       ///
+       string to_file_;
+       ///
+       SignalTypePtr on_finish_;
+};
+
+} // namespace grfx
+
+#endif // GRAPHICSCONVERTER_H
diff --git a/src/graphics/GraphicsImage.C b/src/graphics/GraphicsImage.C
new file mode 100644 (file)
index 0000000..6e7c830
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * \file GraphicsImage.C
+ * Copyright 2002 the LyX Team
+ * Read the file COPYING
+ *
+ * \author Baruch Even <baruch.even@writeme.com>
+ * \author Angus Leeming <a.leeming@ic.ac.uk>
+ */
+
+#include <config.h>
+
+#ifdef __GNUG__
+#pragma implementation
+#endif
+
+#include "GraphicsImage.h"
+#include "GraphicsParams.h"
+#include <iostream>
+
+namespace grfx {
+
+// This will be connected to a function that will return whichever
+// whichever derived class we desire.
+SigC::Signal0<ImagePtr> GImage::newImage;
+
+/// Return the list of loadable formats.
+SigC::Signal0<GImage::FormatList> GImage::loadableFormats;
+
+std::pair<unsigned int, unsigned int>
+GImage::getScaledDimensions(GParams const & params) const
+{
+       if (params.scale == 0 && params.width == 0 && params.height == 0)
+               // No scaling
+               return std::make_pair(getWidth(), getHeight());
+
+       unsigned int width  = 0;
+       unsigned int height = 0;
+       if (params.scale != 0) {
+               width  = getWidth()  * params.scale / 100.0;
+               height = getHeight() * params.scale / 100.0;
+       } else {
+               width  = params.width;
+               height = params.height;
+
+               if (width == 0) {
+                       width = height * getWidth() / getHeight();
+               } else if (height == 0) {
+                       height = width * getHeight() / getWidth();
+               }
+       }
+       
+       if (width == 0 || height == 0)
+               // Something is wrong!
+               return std::make_pair(getWidth(), getHeight());
+
+       return std::make_pair(width, height);
+}
+} // namespace grfx
+
diff --git a/src/graphics/GraphicsImage.h b/src/graphics/GraphicsImage.h
new file mode 100644 (file)
index 0000000..50eabd2
--- /dev/null
@@ -0,0 +1,104 @@
+// -*- C++ -*-
+/**
+ *  \file GraphicsImage.h
+ *  Copyright 2002 the LyX Team
+ *  Read the file COPYING
+ *
+ *  \author Baruch Even <baruch.even@writeme.com>
+ *  \author Angus Leeming <a.leeming@ic.ac.uk>
+ *
+ *  An abstract base class for the images themselves.
+ *  Allows the user to retrieve the pixmap, once loaded and to issue commands
+ *  to modify it.
+ *
+ *  The signals newImage and loadableFormats are connected to the approriate
+ *  derived classes elsewhere, allowing the graphics cache to access them
+ *  without knowing anything about their instantiation.
+ *
+ *  The loading process can be asynchronous, but cropping, rotating and
+ *  scaling block execution.
+ */
+
+#ifndef GRAPHICSIMAGE_H
+#define GRAPHICSIMAGE_H
+
+#include "GraphicsTypes.h"
+#include "LString.h"
+#include <boost/smart_ptr.hpp>
+#include <sigc++/signal_system.h>
+#include <vector>
+#include <utility>
+#include <X11/X.h>
+
+#ifdef __GNUG__
+#pragma interface
+#endif
+
+namespace grfx {
+
+class GParams;
+
+class GImage
+{
+public:
+       /// A list of supported formats.
+       typedef std::vector<string> FormatList;
+       /** This will be connected to a function that will return whichever
+        *  derived class we desire.
+        */
+       static SigC::Signal0<ImagePtr> newImage;
+
+       /// Return the list of loadable formats.
+       static SigC::Signal0<FormatList> loadableFormats;
+
+       ///
+       virtual ~GImage() {}
+
+       /// Create a copy
+       virtual GImage * clone() const = 0;
+
+       ///
+       virtual Pixmap getPixmap() const = 0;
+
+       /// Get the image width
+       virtual unsigned int getWidth() const = 0;
+
+       /// Get the image height
+       virtual unsigned int getHeight() const = 0;
+
+       /** At the end of the loading or modification process, return the new
+        *  image by emitting this signal */
+       typedef SigC::Signal1<void, bool> SignalType;
+       ///
+       typedef boost::shared_ptr<SignalType> SignalTypePtr;
+
+       /// Start loading the image file.
+       virtual void load(string const & filename, SignalTypePtr) = 0;
+
+       /** Generate the pixmap.
+        *  Uses the params to decide on color, grayscale etc.
+        *  Returns true if the pixmap is created.
+        */
+       virtual bool setPixmap(GParams const & params) = 0;
+
+       /// Clip the image using params.
+       virtual void clip(GParams const & params) = 0;
+       
+       /// Rotate the image using params.
+       virtual void rotate(GParams const & params) = 0;
+       
+       /// Scale the image using params.
+       virtual void scale(GParams const & params) = 0;
+
+protected:
+       /** Uses the params to ascertain the dimensions of the scaled image.
+        *  Returned as make_pair(width, height).
+        *  If something geso wrong, returns make_pair(getWidth(), getHeight())
+        */
+       std::pair<unsigned int, unsigned int>
+       getScaledDimensions(GParams const & params) const;
+};
+
+} // namespace grfx
+
+#endif // GRAPHICSIMAGE_H
diff --git a/src/graphics/GraphicsImageXPM.C b/src/graphics/GraphicsImageXPM.C
new file mode 100644 (file)
index 0000000..731f1e2
--- /dev/null
@@ -0,0 +1,660 @@
+/*
+ * \file GraphicsImageXPM.C
+ * Copyright 2002 the LyX Team
+ * Read the file COPYING
+ *
+ * \author Baruch Even <baruch.even@writeme.com>
+ * \author Angus Leeming <a.leeming@ic.ac.uk>
+ */
+
+#include <config.h>
+
+#ifdef __GNUG__
+#pragma implementation
+#endif
+
+#include "GraphicsImageXPM.h"
+#include "GraphicsParams.h"
+#include "ColorHandler.h"
+#include "debug.h"
+#include "frontends/GUIRunTime.h" // x11Display
+#include "support/filetools.h"    // IsFileReadable
+#include "support/lstrings.h"
+#include "Lsstream.h"
+#include <iomanip>                // std::setfill, etc
+#include <cmath>                  // cos, sin
+#include <cstdlib>                // malloc, free
+
+namespace grfx {
+
+/// Access to this class is through this static method.
+ImagePtr GImageXPM::newImage()
+{
+       ImagePtr ptr;
+       ptr.reset(new GImageXPM());
+       return ptr;
+}
+       
+
+/// Return the list of loadable formats.
+GImage::FormatList GImageXPM::loadableFormats()
+{
+       FormatList formats(1);
+       formats[0] = "xpm";
+       return formats;
+}
+
+GImageXPM::GImageXPM()
+       : pixmap_(0),
+         pixmap_status_(PIXMAP_UNINITIALISED)
+{}
+       
+
+GImageXPM::GImageXPM(GImageXPM const & other)
+       : GImage(other),
+         image_(other.image_),
+         pixmap_(other.pixmap_),
+         pixmap_status_(other.pixmap_status_)
+{}
+       
+
+GImageXPM::~GImageXPM()
+{
+       if (pixmap_status_ == PIXMAP_SUCCESS)
+               XFreePixmap(GUIRunTime::x11Display(), pixmap_);
+}
+       
+
+GImage * GImageXPM::clone() const
+{
+       return new GImageXPM(*this);
+}
+
+
+unsigned int GImageXPM::getWidth() const
+{
+       return image_.width();
+}
+
+
+unsigned int GImageXPM::getHeight() const
+{
+       return image_.height();
+}
+
+
+Pixmap GImageXPM::getPixmap() const
+{
+       if (!pixmap_status_ == PIXMAP_SUCCESS)
+               return 0;
+       return pixmap_;
+}
+
+
+void GImageXPM::load(string const & filename, GImage::SignalTypePtr on_finish)
+{
+       if (filename.empty()) {
+               on_finish->emit(false);
+               return;
+       }
+
+       if (!image_.empty()) {
+               lyxerr[Debug::GRAPHICS]
+                       << "Image is loaded already!" << std::endl;
+               on_finish->emit(false);
+               return;
+       }
+
+       XpmImage * xpm_image = new XpmImage;
+
+       int const success =
+               XpmReadFileToXpmImage(const_cast<char *>(filename.c_str()),
+                                     xpm_image, 0);
+
+       switch (success) {
+       case XpmOpenFailed:
+               lyxerr[Debug::GRAPHICS]
+                       << "No XPM image file found." << std::endl;
+               break;
+
+       case XpmFileInvalid:
+               lyxerr[Debug::GRAPHICS]
+                       << "File format is invalid" << std::endl;
+               break;
+
+       case XpmNoMemory:
+               lyxerr[Debug::GRAPHICS] 
+                       << "Insufficient memory to read in XPM file"
+                       << std::endl;
+               break;
+       }
+
+       if (success != XpmSuccess) {
+               XpmFreeXpmImage(xpm_image);
+               delete xpm_image;
+
+               lyxerr[Debug::GRAPHICS]
+                       << "Error reading XPM file '" 
+                       << XpmGetErrorString(success) << "'"
+                       << std::endl;
+       } else {
+               image_.reset(*xpm_image);
+       }
+
+       on_finish->emit(success == XpmSuccess);
+}
+
+
+bool GImageXPM::setPixmap(GParams const & params)
+{
+       if (image_.empty() || params.display == GParams::NONE) {
+               return false;
+       }
+
+       if (pixmap_status_ == PIXMAP_FAILED) {
+               return false;
+       }
+
+       if (pixmap_status_ == PIXMAP_SUCCESS) {
+               return true;
+       }
+
+       using namespace grfx;
+       Display * display = GUIRunTime::x11Display();
+
+       //(BE 2000-08-05)
+       // This might be a dirty thing, but I dont know any other solution.
+       Screen * screen = ScreenOfDisplay(display, GUIRunTime::x11Screen());
+
+       Pixmap pixmap;
+       Pixmap mask;
+
+       XpmAttributes attrib;
+
+       // Allow libXPM lots of leeway when trying to allocate colors.
+       attrib.closeness = 10000;
+       attrib.valuemask = XpmCloseness;
+
+       // The XPM file format allows multiple pixel colours to be defined
+       // as c_color, g_color or m_color.
+       switch (params.display) {
+       case GParams::MONOCHROME:
+               attrib.color_key = XPM_MONO;
+               break;
+       case GParams::GRAYSCALE:
+               attrib.color_key = XPM_GRAY;
+               break;
+       case GParams::COLOR:
+       default: // NONE cannot happen!
+               attrib.color_key = XPM_COLOR;
+               break;
+       }
+
+       attrib.valuemask |= XpmColorKey;
+
+       // Set the color "none" entry to the color of the background.
+       XpmColorSymbol xpm_col;
+       xpm_col.name = 0;
+       xpm_col.value = "none";
+       xpm_col.pixel = lyxColorHandler->colorPixel(LColor::graphicsbg);
+
+       attrib.numsymbols = 1;
+       attrib.colorsymbols = &xpm_col;
+       attrib.valuemask |= XpmColorSymbols;
+
+       // Load up the pixmap
+       XpmImage xpm_image = image_.get();
+       int const status =
+               XpmCreatePixmapFromXpmImage(display, 
+                                           XRootWindowOfScreen(screen), 
+                                           &xpm_image, 
+                                           &pixmap, &mask, &attrib);
+
+       XpmFreeAttributes(&attrib);
+
+       if (status != XpmSuccess) {
+               lyxerr << "Error creating pixmap from xpm_image '" 
+                      << XpmGetErrorString(status) << "'"
+                      << std::endl;
+               pixmap_status_ = PIXMAP_FAILED;
+               return false;
+       }
+
+       pixmap_ = pixmap;
+       pixmap_status_ = PIXMAP_SUCCESS;
+       return true;
+}
+
+
+void GImageXPM::clip(GParams const & params)
+{
+       if (image_.empty())
+               return;
+
+       if (params.bb.empty())
+               // No clipping is necessary.
+               return;
+
+       int const new_width  = params.bb.xr - params.bb.xl;
+       int const new_height = params.bb.yt - params.bb.yb;
+
+       if (new_width > image_.width() || new_height > image_.height())
+               // Bounds are invalid.
+               return;
+
+       if (new_width  == image_.width() && new_height == image_.height())
+               // Bounds are unchanged.
+               return;
+
+       unsigned int * new_data = image_.initialisedData(new_width, new_height);
+       unsigned int const * old_data = image_.data();
+
+       unsigned int * it = new_data;
+       unsigned int const * start_row = old_data;
+       for (int row = params.bb.yb; row < params.bb.yt; ++row) {
+               unsigned int const * begin = start_row + params.bb.xl;
+               unsigned int const * end   = start_row + params.bb.xr;
+               it = std::copy(begin, end, it);
+               start_row += image_.width();
+       }
+
+       image_.resetData(new_width, new_height, new_data);
+}
+
+
+void GImageXPM::rotate(GParams const & params)
+{
+       if (image_.empty())
+               return ;
+
+       if (!params.angle)
+               // No rotation is necessary.
+               return;
+
+       // Ascertain the bounding box of the rotated image
+       // Rotate about the bottom-left corner
+       static double const pi = 3.14159265358979323846;
+       double const angle = double(params.angle) * pi / 180.0;
+       double const cos_a = cos(angle);
+       double const sin_a = sin(angle);
+
+       // (0, 0)
+       double max_x = 0; double min_x = 0;
+       double max_y = 0; double min_y = 0;
+
+       // (old_xpm->width, 0)
+       double x_rot = cos_a * image_.width();
+       double y_rot = sin_a * image_.width();
+       max_x = std::max(max_x, x_rot); min_x = std::min(min_x, x_rot);
+       max_y = std::max(max_y, y_rot); min_y = std::min(min_y, y_rot);
+
+       // (image_.width, image_.height)
+       x_rot = cos_a * image_.width() - sin_a * image_.height();
+       y_rot = sin_a * image_.width() + cos_a * image_.height();
+       max_x = std::max(max_x, x_rot); min_x = std::min(min_x, x_rot);
+       max_y = std::max(max_y, y_rot); min_y = std::min(min_y, y_rot);
+
+       // (0, image_.height)
+       x_rot = - sin_a * image_.height();
+       y_rot =   cos_a * image_.height();
+       max_x = std::max(max_x, x_rot); min_x = std::min(min_x, x_rot);
+       max_y = std::max(max_y, y_rot); min_y = std::min(min_y, y_rot);
+
+       int const new_width  = 1 + int(max_x - min_x); // round up!
+       int const new_height = 1 + int(max_y - min_y);
+
+       unsigned int * new_data = image_.initialisedData(new_width, new_height);
+       unsigned int const * old_data = image_.data();
+       
+       // rotate the data
+       for (int y_old = 0; y_old < image_.height(); ++y_old) {
+               for (int x_old = 0; x_old < image_.width(); ++x_old) {
+                       int x_new = int(cos_a * x_old - sin_a * y_old - min_x);
+                       int y_new = int(sin_a * x_old + cos_a * y_old - min_y);
+
+                       // ensure that there are no rounding errors
+                       y_new = std::min(int(new_height - 1), y_new);
+                       y_new = std::max(0, y_new);
+                       x_new = std::min(int(new_width  - 1), x_new);
+                       x_new = std::max(0, x_new);
+
+                       int const old_id = x_old + image_.width() * y_old;
+                       int const new_id = x_new + new_width * y_new;
+
+                       new_data[new_id] = old_data[old_id];
+               }
+       }
+
+       image_.resetData(new_width, new_height, new_data);
+}
+
+
+void GImageXPM::scale(GParams const & params)
+{
+       if (image_.empty())
+               return;
+
+       // boost::tie produces horrible compilation errors on my machine
+       // Angus 25 Feb 2002
+       std::pair<unsigned int, unsigned int> d = getScaledDimensions(params);
+       int const new_width  = d.first;
+       int const new_height = d.second;
+       if (new_width == getWidth() && new_height == getHeight())
+               // No scaling needed
+               return;
+
+       unsigned int * new_data = image_.initialisedData(new_width, new_height);
+       unsigned int const * old_data = image_.data();
+       
+       double const x_scale = double(image_.width())  / double(new_width);
+       double const y_scale = double(image_.height()) / double(new_height);
+
+       // A very simple scaling routine.
+       // Ascertain the old pixel corresponding to the new one.
+       // There is no dithering at all here.
+       for (int x_new = 0; x_new < new_width; ++x_new) {
+               int x_old = int(x_new * x_scale);
+               for (int y_new = 0; y_new < new_height; ++y_new) {
+                       int y_old = int(y_new * y_scale);
+
+                       int const old_id = x_old + image_.width() * y_old;
+                       int const new_id = x_new + new_width * y_new;
+
+                       new_data[new_id] = old_data[old_id];
+               }
+       }
+       
+       image_.resetData(new_width, new_height, new_data);
+}
+
+} // namespace grfx
+
+
+namespace {
+
+void free_color_table(XpmColor * colorTable, int ncolors);
+
+void copy_color_table(XpmColor const * in, int size, XpmColor * out);
+       
+bool contains_color_none(XpmImage const & image);
+
+string const unique_color_string(XpmImage const & image);
+// create a copy (using malloc and strcpy). If (!in) return 0; 
+char * clone_c_string(char const * in);
+// Given a string of the form #ff0571 create a string for the appropriate
+// grayscale or monochrome color.
+char * mapcolor(char * color, bool toGray);
+
+} // namespace anon
+
+
+namespace grfx {
+
+
+GImageXPM::Data::Data()
+       : width_(0), height_(0), cpp_(0), ncolors_(0)
+{}
+
+
+GImageXPM::Data::~Data()
+{
+       if (colorTable_.unique())
+               free_color_table(colorTable_.get(), ncolors_);
+}
+
+
+void GImageXPM::Data::reset(XpmImage & image)
+{
+       width_ = image.width;
+       height_ = image.height;
+       cpp_ = image.cpp;
+
+       // Move the data ptr into this store and free up image.data
+       data_.reset(image.data);
+       image.data = 0;
+
+       // Don't just store the color table, but check first that it contains
+       // all that we require of it.
+       // The idea is to store the color table in a shared_ptr and for all
+       // modified images to use the same table.
+       // It must, therefore, have a c_color "none" entry and g_color and
+       // m_color entries corresponding to each and every c_color entry
+       // (except "none"!)
+
+       // 1. Create a copy of the color table.
+       // Add a c_color "none" entry to the table if it isn't already there.
+       bool const add_color = !contains_color_none(image);
+       
+       if (add_color) {
+
+               ncolors_ = 1 + image.ncolors;
+               size_t const mem_size = sizeof(XpmColor) * ncolors_;
+               XpmColor * table = static_cast<XpmColor *>(malloc(mem_size));
+
+               copy_color_table(image.colorTable, image.ncolors, table);
+
+               XpmColor & color = table[ncolors_ - 1];
+               color.symbolic = 0;
+               color.m_color  = 0;
+               color.g_color  = 0;
+               color.g4_color = 0;
+               color.string =
+                       clone_c_string(unique_color_string(image).c_str());
+               color.c_color = clone_c_string("none");
+
+               free_color_table(image.colorTable, image.ncolors);
+               colorTable_.reset(table);
+
+       } else {
+
+               // Just move the pointer across
+               ncolors_ = image.ncolors;
+               colorTable_.reset(image.colorTable);
+               image.colorTable = 0;
+       }
+
+       // Clean-up the remaining entries of image.
+       image.width = 0;
+       image.height = 0;
+       image.cpp = 0;
+       image.ncolors = 0;
+
+       // 2. Ensure that the color table has g_color and m_color entries
+       XpmColor * table = colorTable_.get();
+
+       for (int i = 0; i < ncolors_; ++i) {
+               // If the c_color is defined and the equivalent
+               // grayscale one is not, then define it.
+               if (table[i].c_color && !table[i].g_color)
+                       table[i].g_color = mapcolor(table[i].c_color, true);
+
+               // If the c_color is defined and the equivalent
+               // monochrome one is not, then define it.
+               if (table[i].c_color && !table[i].m_color)
+                       table[i].m_color = mapcolor(table[i].c_color, false);
+       }
+}
+
+
+XpmImage GImageXPM::Data::get() const
+{
+       XpmImage image;
+       image.width = width_;
+       image.height = height_;
+       image.cpp = cpp_;
+       image.ncolors = ncolors_;
+       image.data = data_.get();
+       image.colorTable = colorTable_.get();
+       return image;
+}
+
+
+void GImageXPM::Data::resetData(int w, int h, unsigned int * d)
+{
+       width_  = w;
+       height_ = h;
+       data_.reset(d);
+}
+
+unsigned int * GImageXPM::Data::initialisedData(int w, int h) const
+{
+       size_t const data_size = w * h;
+
+       size_t const mem_size  = sizeof(unsigned int) * data_size;
+       unsigned int * ptr = static_cast<unsigned int *>(malloc(mem_size));
+
+       unsigned int none_id = color_none_id();
+       std::fill(ptr, ptr + data_size, none_id);
+
+       return ptr;
+}
+
+
+unsigned int GImageXPM::Data::color_none_id() const
+{
+       XpmColor * table = colorTable_.get();
+       for (int i = 0; i < ncolors_; ++i) {
+               char const * const color = table[i].c_color;
+               if (color && lowercase(color) == "none")
+                       return i;
+       }
+       return 0;
+}
+
+} // namespace grfx
+
+namespace {
+
+// Given a string of the form #ff0571 create a string for the appropriate
+// grayscale or monochrome color.
+char * mapcolor(char * color, bool toGray)
+{
+       if (!color)
+               return 0;
+       
+       Display * display = GUIRunTime::x11Display();
+       Colormap cmap     = GUIRunTime::x11Colormap();
+       XColor xcol;
+       XColor ccol;
+       if (XLookupColor(display, cmap, color, &xcol, &ccol) == 0)
+               return 0;
+
+       // Note that X stores the RGB values in the range 0 - 65535
+       // whilst we require them in the range 0 - 255.
+       int const r = xcol.red   / 256;
+       int const g = xcol.green / 256;
+       int const b = xcol.blue  / 256;
+
+       // This gives a good match to a human's RGB to luminance conversion.
+       // (From xv's Postscript code --- Mike Ressler.)
+       int mapped_color = int((0.32 * r) + (0.5 * g) + (0.18 * b));
+       if (!toGray) // monochrome
+               mapped_color = (mapped_color < 128) ? 0 : 255;
+
+       ostringstream ostr;
+
+       ostr << "#" << std::setbase(16) << std::setfill('0')
+            << std::setw(2) << mapped_color
+            << std::setw(2) << mapped_color
+            << std::setw(2) << mapped_color;
+
+       // This string is going into an XpmImage struct, so create a copy that
+       // libXPM can free successfully.
+       return clone_c_string(ostr.str().c_str());
+}
+
+
+void copy_color_table(XpmColor const * in, int size, XpmColor * out)
+{
+       for (int i = 0; i < size; ++i) {
+               out[i].string   = clone_c_string(in[i].string);
+               out[i].symbolic = clone_c_string(in[i].symbolic);
+               out[i].m_color  = clone_c_string(in[i].m_color);
+               out[i].g_color  = clone_c_string(in[i].g_color);
+               out[i].g4_color = clone_c_string(in[i].g4_color);
+               out[i].c_color  = clone_c_string(in[i].c_color);
+       }
+}
+
+
+void free_color_table(XpmColor * table, int size)
+{
+       for (int i = 0; i < size; ++i) {
+               free(table[i].string);
+               free(table[i].symbolic);
+               free(table[i].m_color);
+               free(table[i].g_color);
+               free(table[i].g4_color);
+               free(table[i].c_color);
+       }
+       free(table);
+}
+
+
+char * clone_c_string(char const * in)
+{
+       if (!in)
+               return 0;
+
+        // Don't forget the '\0'
+       char * out = static_cast<char *>(malloc(strlen(in) + 1));
+       return strcpy(out, in);
+}
+
+
+bool contains_color_none(XpmImage const & image)
+{
+       for (int i = 0; i < image.ncolors; ++i) {
+               char const * const color = image.colorTable[i].c_color;
+               if (color && lowercase(color) == "none")
+                       return true;
+       }
+       return false;
+}
+
+
+string const unique_color_string(XpmImage const & image)
+{
+       string id;
+       for (int i = 0; i < image.cpp; ++i) {
+               id.push_back('A');
+       }
+
+       for(;;) {
+               bool found_it = false;
+               for (int i = 0; i < image.ncolors; ++i) {
+                       string const c_id = image.colorTable[i].string;
+                       if (c_id == id) {
+                               found_it = true;
+                               break;
+                       }
+               }
+
+               if (!found_it)
+                       return id;
+
+               // A base 57 counter!
+               // eg AAAz+1 = AABA, AABz+1 = AACA, AAzz+1 = ABAA
+               int current_index = int(id.size() - 1);
+               bool continue_loop = true;
+               while(continue_loop && current_index >= 0) {
+                       continue_loop = false;
+                       
+                       if (id[current_index] == 'z') {
+                               id[current_index] = 'A';
+                               current_index -= 1;
+                               continue_loop = true;
+                       } else {
+                               id[current_index] += 1;
+                       }
+               }
+               if (current_index < 0)
+                       // Unable to find a unique string
+                       return string();
+       }
+}
+
+} // namespace anon
diff --git a/src/graphics/GraphicsImageXPM.h b/src/graphics/GraphicsImageXPM.h
new file mode 100644 (file)
index 0000000..c58560f
--- /dev/null
@@ -0,0 +1,156 @@
+// -*- C++ -*-
+/**
+ *  \file GraphicsImageXPM.h
+ *  Copyright 2002 the LyX Team
+ *  Read the file COPYING
+ *
+ *  \author Baruch Even <baruch.even@writeme.com>
+ *  \author Angus Leeming <a.leeming@ic.ac.uk>
+ *
+ *  An instantiation of GImage that makes use of libXPM to load and store
+ *  the image in memory.
+ */
+
+#ifndef GRAPHICSIMAGEXPM_H
+#define GRAPHICSIMAGEXPM_H
+
+#include "GraphicsImage.h"
+#include XPM_H_LOCATION
+#include "support/smart_ptr.h"
+
+#ifdef __GNUG__
+#pragma interface
+#endif
+
+namespace grfx {
+
+class GImageXPM : public GImage
+{
+public:
+       /// Access to this class is through this static method.
+       static ImagePtr newImage();
+
+       /// Return the list of loadable formats.
+       static FormatList loadableFormats();
+
+       ///
+       ~GImageXPM();
+
+       /// Create a copy
+       GImage * clone() const;
+
+       ///
+       Pixmap getPixmap() const;
+
+       /// Get the image width
+       unsigned int getWidth() const;
+       
+       /// Get the image height
+       unsigned int getHeight() const;
+
+       /** Load the image file into memory.
+        *  In this case (GImageXPM), the process is blocking.
+        */
+       void load(string const & filename, SignalTypePtr);
+
+       /** Generate the pixmap, based on the current state of the
+        *  xpm_image_ (clipped, rotated, scaled etc).
+        *  Uses the params to decide on color, grayscale etc.
+        *  Returns true if the pixmap is created.
+        */
+       bool setPixmap(GParams const & params);
+
+       /// Clip the image using params.
+       void clip(GParams const & params);
+       
+       /// Rotate the image using params.
+       void rotate(GParams const & params);
+       
+       /// Scale the image using params.
+       void scale(GParams const & params);
+
+private:
+       /// Access to the class is through newImage() and clone.
+       GImageXPM();
+       ///
+       GImageXPM(GImageXPM const &);
+
+       /** Contains the data read from file.
+        *  This class is a wrapper for a XpmImage struct, but all views
+        *  of a single file's data will share the same color table.
+        *  This is done by ensuring that the color table contains a "none"
+        *  c_color together with g_color and m_color entries for each c_color
+        *  entry when it is first stored.
+        */
+       class Data 
+       {
+       public:
+               /// Default c-tor. Initialise everything to zero.
+               Data();
+               ~Data();
+
+               bool empty() const { return width_ == 0; }
+
+               /** Wrap an XpmImage in a nice, clean C++ interface.
+                *  Empty the original XpmImage.
+                *  Does some analysis of the color table to ensure that
+                *  it is suitable for all future eventualities. (See above
+                *  description.)
+                */
+               void reset(XpmImage & image);
+
+               /// Reset the data struct with this data.
+               void resetData(int width, int height, unsigned int * data);
+
+               /** Returns a ptr to an initialised block of memory.
+                *  the data is initialised to the color "none" entry.
+                */
+               unsigned int * initialisedData(int width, int height) const;
+
+               /** Construct an XpmImage from the stored contents.
+                *  To pass to XpmCreatePixmapFromXpmImage.
+                *  Efficient, because we only copy the ptrs to the structs.
+                */
+               XpmImage get() const;
+
+               int width()   const { return width_; }
+               int height()  const { return height_; }
+               int cpp()     const { return cpp_; }
+               int ncolors() const { return ncolors_; }
+               unsigned int const * data() const
+                       { return data_.get(); }
+               XpmColor const * colorTable() const
+                       { return colorTable_.get(); }
+
+       private:
+               int width_;
+               int height_;
+               int cpp_;
+               int ncolors_;
+               lyx::shared_c_ptr<unsigned int> data_;
+               lyx::shared_c_ptr<XpmColor> colorTable_;
+
+               unsigned int color_none_id() const;
+       };
+       
+       Data image_;
+
+       /// The pixmap itself.
+       Pixmap pixmap_;
+
+       /// Is the pixmap initialized?
+       enum PixmapStatus {
+               ///
+               PIXMAP_UNINITIALISED,
+               ///
+               PIXMAP_FAILED,
+               ///
+               PIXMAP_SUCCESS
+       };
+       
+       PixmapStatus pixmap_status_;
+};
+
+} // namespace grfx
+
+#endif // GRAPHICSIMAGEXPM_H
diff --git a/src/graphics/GraphicsParams.C b/src/graphics/GraphicsParams.C
new file mode 100644 (file)
index 0000000..f289b30
--- /dev/null
@@ -0,0 +1,171 @@
+/*
+ * \file GraphicsParams.C
+ * Copyright 2002 the LyX Team
+ * Read the file COPYING
+ *
+ * \author Angus Leeming <a.leeming@ic.ac.uk>
+ */
+
+#include <config.h>
+
+#ifdef __GNUG__
+#pragma implementation
+#endif
+
+#include "GraphicsParams.h"
+#include "insets/insetgraphicsParams.h"
+#include "lyxrc.h"
+#include "support/lstrings.h"
+
+namespace grfx {
+
+GParams::GParams(InsetGraphicsParams const & iparams)
+       : filename(iparams.filename),
+         width(0),
+         height(0),
+         scale(0),
+         angle(0)
+{
+       if (iparams.clip)
+               bb = iparams.bb;
+                 
+       if (iparams.rotate)
+               angle = int(iparams.rotateAngle);
+
+       if (iparams.display == InsetGraphicsParams::DEFAULT) {
+
+               if (lyxrc.display_graphics == "mono")
+                       display = MONOCHROME;
+               else if (lyxrc.display_graphics == "gray")
+                       display = GRAYSCALE;
+               else if (lyxrc.display_graphics == "color")
+                       display = COLOR;
+               else
+                       display = NONE;
+
+       } else if (iparams.display == InsetGraphicsParams::NONE) {
+               display = NONE;
+
+       } else if (iparams.display == InsetGraphicsParams::MONOCHROME) {
+               display = MONOCHROME;
+
+       } else if (iparams.display == InsetGraphicsParams::GRAYSCALE) {
+               display = GRAYSCALE;
+
+       } else if (iparams.display == InsetGraphicsParams::COLOR) {
+               display = COLOR;
+       }
+
+       // Override the above if we're not using a gui
+       if (!lyxrc.use_gui) {
+               display = NONE;
+       }
+
+       if (iparams.lyxsize_type == InsetGraphicsParams::SCALE) {
+               scale = iparams.lyxscale;
+
+       } else if (iparams.lyxsize_type == InsetGraphicsParams::WH) {
+               if (!iparams.lyxwidth.zero())
+                       width  = iparams.lyxwidth.inPixels(1, 1);
+               if (!iparams.lyxheight.zero())
+                       height = iparams.lyxheight.inPixels(1, 1);
+
+               // inPixels returns a value scaled by lyxrc.zoom.
+               // We want, therefore, to undo this.
+               double const scaling_factor = 100.0 / double(lyxrc.zoom);
+               width  = int(scaling_factor * width);
+               height = int(scaling_factor * height);
+       }
+}
+
+
+bool operator==(GParams const & a, GParams const & b)
+{
+       return (a.filename        == b.filename &&
+               a.display         == b.display &&
+               a.bb              == b.bb &&
+               a.width           == b.width &&
+               a.height          == b.height &&
+               a.scale           == b.scale &&
+               a.angle           == b.angle);
+}
+
+
+bool operator!=(GParams const & a, GParams const & b)
+{
+       return !(a == b);
+}
+
+
+BoundingBox::BoundingBox()
+       : xl(0), yb(0), xr(0), yt(0)
+{}
+
+
+BoundingBox::BoundingBox(string const & bb)
+{
+       if (bb.empty())
+               return;
+
+       string tmp1;
+       string tmp2 = split(bb, tmp1, ' ');
+       if (!isValidLength(tmp1))
+               return;
+
+       LyXLength const length_xl(tmp1);
+
+       tmp2 = split(tmp2, tmp1, ' ');
+       if (!isValidLength(tmp1))
+               return;
+
+       LyXLength const length_yb(tmp1);
+       
+       tmp2 = split(tmp2, tmp1, ' ');
+       if (!isValidLength(tmp1) || !isValidLength(tmp2))
+               return;
+
+       LyXLength const length_xr(tmp1);
+       LyXLength const length_yt(tmp2);
+
+       // inPixels returns the length in inches, scaled by
+       // lyxrc.dpi and lyxrc.zoom.
+       // We want, therefore, to undo all this lyxrc nonsense because we
+       // want the bounding box in Postscript pixels.
+       // Note further that there are 72 Postscript pixels per inch.
+       double const scaling_factor = 7200.0 / (lyxrc.dpi * lyxrc.zoom);
+       xl = int(scaling_factor * length_xl.inPixels(1, 1));
+       yb = int(scaling_factor * length_yb.inPixels(1, 1));
+       xr = int(scaling_factor * length_xr.inPixels(1, 1));
+       yt = int(scaling_factor * length_yt.inPixels(1, 1));
+
+       if (xr <= xl || yt <= yb) {
+               xl = 0;
+               yb = 0;
+               xr = 0;
+               yt = 0;
+               return;
+       }
+}
+
+
+bool BoundingBox::empty() const 
+{
+       return (!xl && !yb && !xr && !yt);
+}
+
+
+bool operator==(BoundingBox const & a, BoundingBox const & b)
+{
+       return (a.xl == b.xl &&
+               a.yb == b.yb &&
+               a.xr == b.xr &&
+               a.yt == b.yt);
+}
+
+
+bool operator!=(BoundingBox const & a, BoundingBox const & b)
+{
+       return !(a == b);
+}
+
+} // namespace grfx
diff --git a/src/graphics/GraphicsParams.h b/src/graphics/GraphicsParams.h
new file mode 100644 (file)
index 0000000..d8a0261
--- /dev/null
@@ -0,0 +1,98 @@
+// -*- C++ -*-
+/**
+ *  \file GraphicsParams.h
+ *  Copyright 2002 the LyX Team
+ *  Read the file COPYING
+ *
+ *  \author Angus Leeming <a.leeming@ic.ac.uk>
+ *
+ *  Used internally by the GraphicsCache. 
+ *  Only a subset of InsetGraphicsParams is needed for display purposes.
+ *  The GraphicsParams c-tor also interrogates lyxrc to ascertain whether
+ *  to display or not.
+ */
+
+#ifndef GRAPHICSPARAMS_H
+#define GRAPHICSPARAMS_H
+
+#ifdef __GNUG__
+#pragma interface
+#endif
+
+#include "LString.h"
+#include "lyxlength.h"
+
+class InsetGraphicsParams;
+
+namespace grfx {
+
+/** Parse a string of the form "200pt 500pt 300mm 5in" into a
+ *  usable bounding box.
+ */
+struct BoundingBox {
+       ///
+       BoundingBox();
+       ///
+       BoundingBox(string const &);
+
+       /// 0 0 0 0 is empty!
+       bool empty() const;
+       ///
+       int xl;
+       int yb;
+       int xr;
+       int yt;
+};
+
+///
+bool operator==(BoundingBox const &, BoundingBox const &);
+///
+bool operator!=(BoundingBox const &, BoundingBox const &);
+
+struct GParams 
+{
+       /// 
+       GParams(InsetGraphicsParams const &);
+
+       /// How is the image to be displayed on the LyX screen?
+       enum DisplayType {
+               ///
+               COLOR,
+               ///
+               GRAYSCALE,
+               ///
+               MONOCHROME,
+               /// We aren't going to display it at all!
+               NONE
+       };
+
+       ///
+       DisplayType display;
+
+       /// The image filename.
+       string filename;
+
+       ///
+       BoundingBox bb;
+
+       /** The size of the view inside lyx in pixels or the scaling of the
+        *  image.
+        */
+       unsigned int width;
+       ///
+       unsigned int height;
+       /// 
+       unsigned int scale;
+
+       /// Rotation angle.
+       int angle;
+};
+
+///
+bool operator==(GParams const &, GParams const &);
+///
+bool operator!=(GParams const &, GParams const &);
+
+} // namespace grfx
+
+#endif // GRAPHICSPARAMS_H
diff --git a/src/graphics/GraphicsTypes.h b/src/graphics/GraphicsTypes.h
new file mode 100644 (file)
index 0000000..81c2503
--- /dev/null
@@ -0,0 +1,57 @@
+// -*- C++ -*-
+/**
+ *  \file GraphicsTypes.h
+ *  Copyright 2002 the LyX Team
+ *  Read the file COPYING
+ *
+ *  \author Angus Leeming <a.leeming@ic.ac.uk>
+ *
+ *  All that header files outside the graphics subdirectory should need to
+ *  access. That just leaves insetgraphics.C to access GraphicsCache.h.
+ *  It also makes life easier for files inside the graphics subdirectory!
+ */
+
+#ifndef GRAPHICSTYPES_H
+#define GRAPHICSTYPES_H
+
+#include <boost/smart_ptr.hpp>
+
+#ifdef __GNUG__
+#pragma interface
+#endif
+
+namespace grfx {
+
+       ///
+       class GImage;
+       ///
+       typedef boost::shared_ptr<GImage> ImagePtr;
+
+       /// The status of the loading process
+       enum ImageStatus {
+               /** The data is in the cache, but no request to display it
+                *  has been received.
+                */
+               WaitingToLoad,
+               /// The image is in a loadable format and is being loaded.
+               Loading,
+               /// The image is being converted to a loadable format.
+               Converting,
+               /// The image is in memory and is being scaled, rotated, etc.
+               ScalingEtc,
+               /// All finished. Can display the image.
+               Loaded,
+               ///
+               ErrorNoFile,
+               ///
+               ErrorConverting,
+               ///
+               ErrorLoading,
+               /// Fall back on the unmodified image?
+               ErrorScalingEtc,
+               /// The data is not in the cache at all!
+               ErrorUnknown
+       };
+}
+
+#endif // GRAPHICSTYPES_H
diff --git a/src/graphics/ImageLoader.C b/src/graphics/ImageLoader.C
deleted file mode 100644 (file)
index ab7450d..0000000
+++ /dev/null
@@ -1,82 +0,0 @@
-/* This file is part of
- * =================================================
- * 
- *          LyX, The Document Processor
- *          Copyright 1995 Matthias Ettrich.
- *          Copyright 1995-2001 The LyX Team.
- *
- * ================================================= */
-
-#ifdef __GNUG__
-#pragma implementation
-#endif
-
-#include <config.h>
-#include "debug.h"
-#include "ImageLoader.h"
-#include "frontends/support/LyXImage.h"
-
-#include "support/filetools.h"
-
-using std::endl;
-
-ImageLoader::ImageLoader()
-               : image_(0)
-{
-}
-
-ImageLoader::~ImageLoader()
-{
-       freeImage();
-}
-
-void
-ImageLoader::freeImage()
-{
-       delete image_;
-       image_ = 0;
-}
-
-bool ImageLoader::isImageFormatOK(string const & /*filename*/) const
-{
-       return false;
-}
-
-void ImageLoader::setImage(LyXImage * image)
-{
-       image_ = image;
-}
-
-LyXImage * ImageLoader::getImage()
-{
-       LyXImage * tmp = image_;
-       image_ = 0;
-       return tmp;
-}
-
-ImageLoader::FormatList const
-ImageLoader::loadableFormats() const
-{
-       return FormatList();
-}
-
-ImageLoader::Result
-ImageLoader::loadImage(string const & filename)
-{
-       // Make sure file exists and is readable.
-       if (! IsFileReadable(filename)) {
-               lyxerr << "No XPM file found." << endl;
-               return NoFile;
-       }
-
-       // Verify that the file format is correct.
-       if (! isImageFormatOK(filename)) {
-               lyxerr << "File format incorrect." << endl;
-               return ImageFormatUnknown;
-       }
-
-       freeImage();
-
-       return runImageLoader(filename);
-}
-
diff --git a/src/graphics/ImageLoader.h b/src/graphics/ImageLoader.h
deleted file mode 100644 (file)
index cf126b7..0000000
+++ /dev/null
@@ -1,87 +0,0 @@
-// -*- C++ -*-
-/* This file is part of
- * =================================================
- * 
- *          LyX, The Document Processor
- *          Copyright 1995 Matthias Ettrich.
- *          Copyright 1995-2001 The LyX Team.
- *
- * ================================================= */
-
-#ifndef IMAGELOADER_H
-#define IMAGELOADER_H
-
-#ifdef __GNUG__
-#pragma interface
-#endif
-
-#include "LString.h"
-#include "boost/utility.hpp"
-#include <vector>
-
-class LyXImage;
-
-/** ImageLoader is a base class for all image loaders. An ImageLoader instance is
- *  platform dependent, and knows how to load some image formats into a memory
- *  representation (LyXImage).
- *
- *  It may do the image loading asynchronously.
- *  
- *  @Author Baruch Even, <baruch.even@writeme.com>
- */
-class ImageLoader : boost::noncopyable {
-public:
-       /// Errors that can be returned from this class.
-       enum Result {
-               OK = 0,                 
-               ImageFormatUnknown, // This loader doesn't know how to load this file.
-               NoFile,                         // File doesn't exists.
-               ErrorWhileLoading  // Unknown error when loading.
-       };
-       
-       /// A list of supported formats.
-       typedef std::vector<string> FormatList;
-       
-       /// c-tor.
-       ImageLoader();
-       /// d-tor.
-       virtual ~ImageLoader();
-
-       /// Start loading the image file.
-       ImageLoader::Result loadImage(string const & filename);
-
-       /** Get the last rendered pixmap. Returns 0 if no image is ready.
-        *  
-        *  It is a one time operation, that is, after you get the image
-        *  you are completely responsible to destroy it and the ImageLoader
-        *  will not know about the image.
-        *
-        *  This way we avoid deleting the image if you still use it and the 
-        *  ImageLoader is destructed, and if you don't use it we get to 
-        *  destruct the image to avoid memory leaks.
-        */
-       LyXImage * getImage();
-
-       /// Return the list of loadable formats.
-       virtual FormatList const loadableFormats() const;
-       
-protected:
-       /// Verify that the file is one that we can handle.
-       virtual bool isImageFormatOK(string const & filename) const = 0;
-
-       /// Do the actual image loading.
-       virtual Result runImageLoader(string const & filename) = 0;
-
-       /// Set the image that was loaded.
-       void setImage(LyXImage * image);
-       
-private:
-       /// Free the loaded image.
-       void freeImage();
-       
-       /// The loaded image. An auto_ptr would be great here, but it's not
-       /// available everywhere (gcc 2.95.2 doesnt have it).
-       LyXImage * image_;
-};
-
-#endif
diff --git a/src/graphics/ImageLoaderXPM.C b/src/graphics/ImageLoaderXPM.C
deleted file mode 100644 (file)
index 94db3f9..0000000
+++ /dev/null
@@ -1,142 +0,0 @@
-/* This file is part of
- * =================================================
- * 
- *          LyX, The Document Processor
- *          Copyright 1995 Matthias Ettrich.
- *          Copyright 1995-2001 The LyX Team.
- *
- * ================================================= */
-
-#include <config.h>
-
-#ifdef __GNUG__
-#pragma implementation
-#endif
-
-#include "ImageLoaderXPM.h"
-#include "ColorHandler.h"
-#include "lyxrc.h"
-#include "debug.h"
-
-#include "frontends/support/LyXImage.h"
-#include "frontends/GUIRunTime.h"
-
-#include "support/filetools.h"
-#include "support/LAssert.h"
-
-#include XPM_H_LOCATION
-#include <iostream>
-#include <fstream>
-
-
-using std::ifstream;
-using std::endl;
-using std::ios;
-
-
-bool ImageLoaderXPM::isImageFormatOK(string const & filename) const
-{
-       ifstream is(filename.c_str(), ios::in);
-
-       // The signature of the file without the spaces.
-       static char const str[] = "/*XPM*/";
-       char const * ptr = str;
-
-       for (; *ptr != '\0'; ++ptr) {
-               char c;
-               is >> c;
-
-               if (c != *ptr)
-                       return false;
-       }
-
-       return true;
-}
-
-
-ImageLoaderXPM::FormatList const 
-ImageLoaderXPM::loadableFormats() const 
-{
-       FormatList formats;
-       formats.push_back("xpm");
-
-       return formats;
-}
-
-
-ImageLoader::Result 
-ImageLoaderXPM::runImageLoader(string const & filename)
-{
-       Display * display = GUIRunTime::x11Display();
-
-       //(BE 2000-08-05)
-       // This might be a dirty thing, but I dont know any other solution.
-       Screen * screen = ScreenOfDisplay(display, GUIRunTime::x11Screen());
-
-       Pixmap pixmap;
-       Pixmap mask;
-
-       // If the pixmap contains a transparent colour, then set it to the
-       // colour of the background (Angus 21 Sep 2001)
-       XpmColorSymbol xpm_col;
-       xpm_col.name = 0;
-       xpm_col.value = "none";
-       xpm_col.pixel = lyxColorHandler->colorPixel(LColor::graphicsbg);
-
-       XpmAttributes attrib;
-       attrib.valuemask = XpmCloseness | XpmColorSymbols;
-
-       attrib.closeness = 10000;
-
-       attrib.numsymbols = 1;
-       attrib.colorsymbols = &xpm_col;
-
-       // Set color_key to monochrome, grayscale or color
-       // (Angus 21 Sep 2001)
-       int color_key = 0;
-       if (lyxrc.display_graphics == "color") {
-               color_key = XPM_COLOR;
-
-       } else if (lyxrc.display_graphics == "gray") {
-               color_key = XPM_GRAY;
-
-       } else if (lyxrc.display_graphics == "mono") {
-               color_key = XPM_MONO;
-       }
-
-       // If setting color_key failed, then fail gracefully!
-       if (color_key != 0) {
-               attrib.valuemask |= XpmColorKey;
-               attrib.color_key = color_key;
-
-       } else {
-               lyxerr << "Warning in ImageLoaderXPM::runImageLoader"
-                      << "lyxrc.display_graphics == \""
-                      << lyxrc.display_graphics
-                      << "\""
-                      << endl;
-       }               
-
-       // Load up the pixmap
-       int status = XpmReadFileToPixmap(
-                       display, 
-                       XRootWindowOfScreen(screen), 
-                       const_cast<char *>(filename.c_str()), 
-                       &pixmap, &mask, &attrib);
-
-       if (status != XpmSuccess) {
-               lyxerr << "Error reading XPM file '" 
-                      << XpmGetErrorString(status) << "'"
-                      << endl;
-               return ErrorWhileLoading;
-       }
-
-       // This should have been set by the XpmReadFileToPixmap call!
-       lyx::Assert(attrib.valuemask & XpmSize);
-
-       setImage(new LyXImage(pixmap, attrib.width, attrib.height));
-
-       XpmFreeAttributes(&attrib);
-
-       return OK;
-}
diff --git a/src/graphics/ImageLoaderXPM.h b/src/graphics/ImageLoaderXPM.h
deleted file mode 100644 (file)
index 0d15aa5..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-// -*- C++ -*-
-/* This file is part of
- * =================================================
- * 
- *          LyX, The Document Processor
- *          Copyright 1995 Matthias Ettrich.
- *          Copyright 1995-2001 The LyX Team.
- *
- * ================================================= */
-
-#ifndef IMAGELOADER_XPM_H
-#define IMAGELOADER_XPM_H
-
-#ifdef __GNUG__
-#pragma interface
-#endif
-
-#include "graphics/ImageLoader.h"
-
-/** ImageLoaderXPM is an implementation of ImageLoader that can load XPM images by
- * using libXPM.
- *  
- *  @Author Baruch Even, <baruch.even@writeme.com>
- */
-class ImageLoaderXPM : public ImageLoader {
-public:
-       /// c-tor.
-       ImageLoaderXPM() {};
-       /// d-tor.
-       virtual ~ImageLoaderXPM() {};
-
-       /// Return the list of loadable formats.
-       virtual FormatList const loadableFormats() const;
-       
-protected:
-       /// Verify that the file is one that we can handle.
-       virtual bool isImageFormatOK(string const & filename) const;
-
-       /// Do the actual image loading.
-       virtual ImageLoader::Result runImageLoader(string const & filename);
-};
-
-#endif
index ecdace9771debcd10702e6a5c411a574a71c7489..1012f7503bcf8164a1513d0d579f4806cc88453a 100644 (file)
@@ -12,10 +12,14 @@ libgraphics_la_SOURCES = \
        GraphicsCache.C \
        GraphicsCacheItem.h \
        GraphicsCacheItem.C \
-       ImageLoaderXPM.h \
-       ImageLoaderXPM.C \
-       ImageLoader.h \
-       ImageLoader.C 
+       GraphicsConverter.h \
+       GraphicsConverter.C \
+       GraphicsImage.h \
+       GraphicsImage.C \
+       GraphicsImageXPM.h \
+       GraphicsImageXPM.C \
+       GraphicsParams.C \
+       GraphicsParams.h
 
 libgraphics.la: libgraphics.o
 
index 59f463c436adbaf85eb2208ed4fa2e40ab3f95fb..a9d040a5595bec005589787fd5f86233800715b6 100644 (file)
@@ -93,47 +93,40 @@ TODO Before initial production release:
 #include "insets/insetgraphicsParams.h"
 
 #include "graphics/GraphicsCache.h"
-#include "graphics/GraphicsCacheItem.h"
+#include "graphics/GraphicsImage.h"
 
 #include "LyXView.h"
 #include "buffer.h"
 #include "BufferView.h"
 #include "converter.h"
 #include "Painter.h"
-#include "lyx_gui_misc.h"
-#include "lyxtext.h"
 #include "lyxrc.h"
-#include "font.h"
+#include "font.h"    // For the lyxfont class.
 #include "debug.h"
 #include "gettext.h"
+#include "LaTeXFeatures.h"
 
 #include "frontends/Dialogs.h"
 #include "frontends/Alert.h"
-#include "frontends/controllers/helper_funcs.h"
-#include "frontends/support/LyXImage.h"
+#include "frontends/controllers/helper_funcs.h" // getVectorFromString
 
-#include "support/FileInfo.h"
+#include "support/LAssert.h"
 #include "support/filetools.h"
-#include "support/lyxlib.h"
-#include "support/lyxmanip.h"
-#include "support/lyxalgo.h"
+#include "support/lyxalgo.h" // lyx::count
 
-#include <fstream>
-#include <algorithm>
+#include <algorithm> // For the std::max
 
 extern string system_tempdir;
 
-using std::ifstream;
 using std::ostream;
 using std::endl;
-using std::max;
-using std::vector;
-
 
 ///////////////////////////////////////////////////////////////////////////
 int const VersionNumber = 1;
 ///////////////////////////////////////////////////////////////////////////
 
+namespace {
+
 // This function is a utility function
 // ... that should be with ChangeExtension ...
 inline
@@ -141,6 +134,8 @@ string const RemoveExtension(string const & filename)
 {
        return ChangeExtension(filename, string());
 }
+} // namespace anon
 
 
 namespace {
@@ -159,19 +154,17 @@ string const unique_id()
 } // namespace anon
 
 
-// Initialize only those variables that do not have a constructor.
 InsetGraphics::InsetGraphics()
-       : cacheHandle(0), imageLoaded(false), graphic_label(unique_id())
+       : cached_status_(grfx::ErrorUnknown), cache_filled_(false),
+         graphic_label(unique_id())
 {}
 
 
 InsetGraphics::InsetGraphics(InsetGraphics const & ig, bool same_id)
-       : Inset(), SigC::Object()
-       , cacheHandle(ig.cacheHandle)
-       , imageLoaded(ig.imageLoaded)
-       , graphic_label(unique_id())
+       : cached_status_(grfx::ErrorUnknown), cache_filled_(false),
+         graphic_label(unique_id())
 {
-       setParams(ig.getParams());
+       setParams(ig.params());
        if (same_id)
                id_ = ig.id_;
 }
@@ -179,6 +172,10 @@ InsetGraphics::InsetGraphics(InsetGraphics const & ig, bool same_id)
 
 InsetGraphics::~InsetGraphics()
 {
+       cached_image_.reset(0);
+       grfx::GCache & gc = grfx::GCache::get();
+       gc.remove(*this);
+
        // Emits the hide signal to the dialog connected (if any)
        hideDialog();
 }
@@ -187,37 +184,70 @@ InsetGraphics::~InsetGraphics()
 string const InsetGraphics::statusMessage() const
 {
        string msg;
-       if (cacheHandle.get()) {
-               switch (cacheHandle->getImageStatus()) {
-               case GraphicsCacheItem::UnknownError:
-                       msg = _("Unknown Error");
-                       break;
-               case GraphicsCacheItem::Loading:
-                       msg = _("Loading...");
-                       break;
-               case GraphicsCacheItem::ErrorReading:
-                       msg = _("Error reading");
-                       break;
-               case GraphicsCacheItem::Converting:
-                       msg = _("Converting Image");
-                       break;
-               case GraphicsCacheItem::ErrorConverting:
-                       msg = _("Error converting");
-                       break;
-               case GraphicsCacheItem::Loaded:
-                       // No message to write.
-                       break;
-               }
+
+       switch (cached_status_) {
+       case grfx::WaitingToLoad:
+               msg = _("Waiting for draw request to start loading...");
+               break;
+       case grfx::Loading:
+               msg = _("Loading...");
+               break;
+       case grfx::Converting:
+               msg = _("Converting to loadable format...");
+               break;
+       case grfx::ScalingEtc:
+               msg = _("Loaded. Scaling etc...");
+               break;
+       case grfx::ErrorNoFile:
+               msg = _("No file found!");
+               break;
+       case grfx::ErrorLoading:
+               msg = _("Error loading file into memory");
+               break;
+       case grfx::ErrorConverting:
+               msg = _("Error converting to loadable format");
+               break;
+       case grfx::ErrorScalingEtc:
+               msg = _("Error scaling etc");
+               break;
+       case grfx::ErrorUnknown:
+               msg = _("No image associated with this inset is in the cache!");
+               break;
+       case grfx::Loaded:
+               msg = _("Loaded but not displaying");
+               break;
        }
+
        return msg;
 }
 
 
+void InsetGraphics::setCache() const
+{
+       if (cache_filled_)
+               return;
+
+       grfx::GCache & gc = grfx::GCache::get();
+       cached_status_ = gc.status(*this);
+       cached_image_  = gc.image(*this);
+}
+
+
+bool InsetGraphics::drawImage() const
+{
+       setCache();
+       Pixmap const pixmap =
+               (cached_status_ == grfx::Loaded && cached_image_.get() != 0) ?
+               cached_image_->getPixmap() : 0;
+
+       return pixmap != 0;
+}
+
+       
 int InsetGraphics::ascent(BufferView *, LyXFont const &) const
 {
-       LyXImage * pixmap = 0;
-       if (cacheHandle.get() && (pixmap = cacheHandle->getImage()))
-               return pixmap->getHeight();
+       if (drawImage())
+               return cached_image_->getHeight();
        else
                return 50;
 }
@@ -225,24 +255,21 @@ int InsetGraphics::ascent(BufferView *, LyXFont const &) const
 
 int InsetGraphics::descent(BufferView *, LyXFont const &) const
 {
-       // this is not true if viewport is used and clip is not.
        return 0;
 }
 
 
 int InsetGraphics::width(BufferView *, LyXFont const & font) const
 {
-       LyXImage * pixmap = 0;
-       
-       if (cacheHandle.get() && (pixmap = cacheHandle->getImage()))
-               return pixmap->getWidth();
+       if (drawImage())
+               return cached_image_->getWidth();
        else {
                int font_width = 0;
 
                LyXFont msgFont(font);
                msgFont.setFamily(LyXFont::SANS_FAMILY);
 
-               string const justname = OnlyFilename (params.filename);
+               string const justname = OnlyFilename (params().filename);
                if (!justname.empty()) {
                        msgFont.setSize(LyXFont::SIZE_FOOTNOTE);
                        font_width = lyxfont::width(justname, msgFont);
@@ -252,10 +279,10 @@ int InsetGraphics::width(BufferView *, LyXFont const & font) const
                if (!msg.empty()) {
                        msgFont.setSize(LyXFont::SIZE_TINY);
                        int const msg_width = lyxfont::width(msg, msgFont);
-                       font_width = max(font_width, msg_width);
+                       font_width = std::max(font_width, msg_width);
                }
                
-               return max(50, font_width + 15);
+               return std::max(50, font_width + 15);
        }
 }
 
@@ -263,49 +290,47 @@ int InsetGraphics::width(BufferView *, LyXFont const & font) const
 void InsetGraphics::draw(BufferView * bv, LyXFont const & font,
                          int baseline, float & x, bool) const
 {
-       Painter & paint = bv->painter();
-
        int ldescent = descent(bv, font);
-       int lascent = ascent(bv, font);
-       int lwidth = width(bv, font);
+       int lascent  = ascent(bv, font);
+       int lwidth   = width(bv, font);
 
-       // Make sure x is updated upon exit from this routine
+       // Make sure now that x is updated upon exit from this routine
        int old_x = int(x);
        x += lwidth;
 
+       // Initiate the loading of the graphics file
+       if (cached_status_ == grfx::WaitingToLoad) {
+               grfx::GCache & gc = grfx::GCache::get();
+               gc.startLoading(*this);
+       }
+
        // This will draw the graphics. If the graphics has not been loaded yet,
        // we draw just a rectangle.
-       if (imageLoaded) {
+       Painter & paint = bv->painter();
+
+       if (drawImage()) {
 
                paint.image(old_x + 2, baseline - lascent,
                            lwidth - 4, lascent + ldescent,
-                           cacheHandle->getImage());
+                           *cached_image_.get());
+
        } else {        
-               // Get the image status, default to unknown error.
-               GraphicsCacheItem::ImageStatus status = GraphicsCacheItem::UnknownError;
-               if (lyxrc.use_gui && params.display != InsetGraphicsParams::NONE &&
-                   cacheHandle.get())
-                       status = cacheHandle->getImageStatus();
-               // Check if the image is now ready.
-               if (status == GraphicsCacheItem::Loaded) {
-                       imageLoaded = true;
-                       // Tell BufferView we need to be updated!
-                       bv->text->status(bv, LyXText::CHANGED_IN_DRAW);
-                       return;
-               }
+
                paint.rectangle(old_x + 2, baseline - lascent,
                                lwidth - 4,
                                lascent + ldescent);
+
                // Print the file name.
                LyXFont msgFont(font);
                msgFont.setFamily(LyXFont::SANS_FAMILY);
-               string const justname = OnlyFilename (params.filename);
+               string const justname = OnlyFilename (params().filename);
                if (!justname.empty()) {
                        msgFont.setSize(LyXFont::SIZE_FOOTNOTE);
                        paint.text(old_x + 8, 
                                   baseline - lyxfont::maxAscent(msgFont) - 4,
                                   justname, msgFont);
                }
+
                // Print the message.
                string const msg = statusMessage();
                if (!msg.empty()) {
@@ -313,6 +338,21 @@ void InsetGraphics::draw(BufferView * bv, LyXFont const & font,
                        paint.text(old_x + 8, baseline - 4, msg, msgFont);
                }
        }
+
+       // Reset the cache, ready for the next draw request
+       cached_status_ = grfx::ErrorUnknown;
+       cached_image_.reset(0);
+       cache_filled_ = false;
+}
+
+
+// Update the inset after parameters changed (read from file or changed in
+// dialog. The grfx::GCache makes the decisions about whether or not to draw
+// (interogates lyxrc, ascertains whether file exists etc)
+void InsetGraphics::updateInset() const
+{
+       grfx::GCache & gc = grfx::GCache::get();
+       gc.update(*this);
 }
 
 
@@ -337,7 +377,7 @@ Inset::EDITABLE InsetGraphics::editable() const
 void InsetGraphics::write(Buffer const * buf, ostream & os) const
 {
        os << "Graphics FormatVersion " << VersionNumber << '\n';
-       params.Write(buf, os);
+       params().Write(buf, os);
 }
 
 
@@ -364,7 +404,7 @@ void InsetGraphics::readInsetGraphics(Buffer const * buf, LyXLex & lex)
 
                string const token = lex.getString();
                lyxerr[Debug::GRAPHICS] << "Token: '" << token << '\'' 
-                                   << endl;
+                                   << std::endl;
 
                if (token.empty()) {
                        continue;
@@ -378,13 +418,13 @@ void InsetGraphics::readInsetGraphics(Buffer const * buf, LyXLex & lex)
                                << "This document was created with a newer Graphics widget"
                                ", You should use a newer version of LyX to read this"
                                " file."
-                               << endl;
+                               << std::endl;
                        // TODO: Possibly open up a dialog?
                }
                else {
-                       if (! params.Read(buf, lex, token))
+                       if (! params_.Read(buf, lex, token))
                                lyxerr << "Unknown token, " << token << ", skipping." 
-                                       << endl;
+                                       << std::endl;
                }
        }
 }
@@ -392,18 +432,18 @@ void InsetGraphics::readInsetGraphics(Buffer const * buf, LyXLex & lex)
 // FormatVersion < 1.0  (LyX < 1.2)
 void InsetGraphics::readFigInset(Buffer const * buf, LyXLex & lex)
 {
-       vector<string> const oldUnits =
+       std::vector<string> const oldUnits =
                getVectorFromString("pt,cm,in,p%,c%");
        bool finished = false;
        // set the display default      
        if (lyxrc.display_graphics == "mono") 
-           params.display = InsetGraphicsParams::MONOCHROME;
+           params_.display = InsetGraphicsParams::MONOCHROME;
        else if (lyxrc.display_graphics == "gray") 
-           params.display = InsetGraphicsParams::GRAYSCALE;
+           params_.display = InsetGraphicsParams::GRAYSCALE;
        else if (lyxrc.display_graphics == "color") 
-           params.display = InsetGraphicsParams::COLOR;
+           params_.display = InsetGraphicsParams::COLOR;
        else
-           params.display = InsetGraphicsParams::NONE;
+           params_.display = InsetGraphicsParams::NONE;
        while (lex.isOK() && !finished) {
                lex.next();
 
@@ -418,49 +458,49 @@ void InsetGraphics::readFigInset(Buffer const * buf, LyXLex & lex)
                        if (lex.next()) {
                                string const name = lex.getString();
                                string const path = buf->filePath();
-                               params.filename = MakeAbsPath(name, path);
+                               params_.filename = MakeAbsPath(name, path);
                        }
                } else if (token == "extra") {
                        if (lex.next());
                        // kept for backwards compability. Delete in 0.13.x
                } else if (token == "subcaption") {
                        if (lex.eatLine())
-                               params.subcaptionText = lex.getString();
-                       params.subcaption = true;
+                               params_.subcaptionText = lex.getString();
+                       params_.subcaption = true;
                } else if (token == "label") {
                        if (lex.next());
                        // kept for backwards compability. Delete in 0.13.x
                } else if (token == "angle") {
                        if (lex.next())
-                               params.rotate = true;
-                               params.rotateAngle = lex.getFloat();
+                               params_.rotate = true;
+                               params_.rotateAngle = lex.getFloat();
                } else if (token == "size") {
                        if (lex.next())
-                               params.lyxwidth = LyXLength(lex.getString()+"pt");
+                               params_.lyxwidth = LyXLength(lex.getString()+"pt");
                        if (lex.next())
-                               params.lyxheight = LyXLength(lex.getString()+"pt");
+                               params_.lyxheight = LyXLength(lex.getString()+"pt");
                } else if (token == "flags") {
                        if (lex.next())
                                switch (lex.getInteger()) {
-                               case 1: params.display = InsetGraphicsParams::MONOCHROME; 
+                               case 1: params_.display = InsetGraphicsParams::MONOCHROME; 
                                    break;
-                               case 2: params.display = InsetGraphicsParams::GRAYSCALE; 
+                               case 2: params_.display = InsetGraphicsParams::GRAYSCALE; 
                                    break;
-                               case 3: params.display = InsetGraphicsParams::COLOR; 
+                               case 3: params_.display = InsetGraphicsParams::COLOR; 
                                    break;
                                }
                } else if (token == "subfigure") {
-                       params.subcaption = true;
+                       params_.subcaption = true;
                } else if (token == "width") {
                    if (lex.next()) {
                        int i = lex.getInteger();
                        if (lex.next()) {
                            if (i == 5) {
-                               params.scale = lex.getInteger();
-                               params.size_type = InsetGraphicsParams::SCALE;
+                               params_.scale = lex.getInteger();
+                               params_.size_type = InsetGraphicsParams::SCALE;
                            } else {
-                               params.width = LyXLength(lex.getString()+oldUnits[i]);
-                               params.size_type = InsetGraphicsParams::WH;
+                               params_.width = LyXLength(lex.getString()+oldUnits[i]);
+                               params_.size_type = InsetGraphicsParams::WH;
                            }
                        }
                    }
@@ -468,8 +508,8 @@ void InsetGraphics::readFigInset(Buffer const * buf, LyXLex & lex)
                    if (lex.next()) {
                        int i = lex.getInteger();
                        if (lex.next()) {
-                           params.height = LyXLength(lex.getString()+oldUnits[i]);
-                           params.size_type = InsetGraphicsParams::WH;
+                           params_.height = LyXLength(lex.getString()+oldUnits[i]);
+                           params_.size_type = InsetGraphicsParams::WH;
                        }
                    }
                }
@@ -482,51 +522,52 @@ string const InsetGraphics::createLatexOptions() const
        // stream since we might have a trailing comma that we would like to remove
        // before writing it to the output stream.
        ostringstream options;
-       if (!params.bb.empty())
-           options << "  bb=" << strip(params.bb) << ",\n";
-       if (params.draft)
+       if (!params().bb.empty())
+           options << "  bb=" << strip(params().bb) << ",\n";
+       if (params().draft)
            options << "  draft,\n";
-       if (params.clip)
+       if (params().clip)
            options << "  clip,\n";
-       if (params.size_type == InsetGraphicsParams::WH) {
-           if (!params.width.zero())
-               options << "  width=" << params.width.asLatexString() << ",\n";
-           if (!params.height.zero())
-               options << "  height=" << params.height.asLatexString() << ",\n";
-       } else if (params.size_type == InsetGraphicsParams::SCALE) {
-           if (params.scale > 0)
-               options << "  scale=" << double(params.scale)/100.0 << ",\n";
+       if (params().size_type == InsetGraphicsParams::WH) {
+           if (!params().width.zero())
+               options << "  width=" << params().width.asLatexString() << ",\n";
+           if (!params().height.zero())
+               options << "  height=" << params().height.asLatexString() << ",\n";
+       } else if (params().size_type == InsetGraphicsParams::SCALE) {
+           if (params().scale > 0)
+               options << "  scale=" << double(params().scale)/100.0 << ",\n";
        }
-       if (params.keepAspectRatio)
+       if (params().keepAspectRatio)
            options << "  keepaspectratio,\n";
        // Make sure it's not very close to zero, a float can be effectively
        // zero but not exactly zero.
-       if (!lyx::float_equal(params.rotateAngle, 0, 0.001) && params.rotate) {
-           options << "  angle=" << params.rotateAngle << ",\n";
-           if (!params.rotateOrigin.empty()) {
-               options << "  origin=" << params.rotateOrigin[0];
-               if (contains(params.rotateOrigin,"Top"))
+       if (!lyx::float_equal(params().rotateAngle, 0, 0.001) && params().rotate) {
+           options << "  angle=" << params().rotateAngle << ",\n";
+           if (!params().rotateOrigin.empty()) {
+               options << "  origin=" << params().rotateOrigin[0];
+               if (contains(params().rotateOrigin,"Top"))
                    options << 't';
-               else if (contains(params.rotateOrigin,"Bottom"))
+               else if (contains(params().rotateOrigin,"Bottom"))
                    options << 'b';
-               else if (contains(params.rotateOrigin,"Baseline"))
+               else if (contains(params().rotateOrigin,"Baseline"))
                    options << 'B';
                options << ",\n";
            }
        }
-       if (!params.special.empty())
-           options << params.special << ",\n";
+       if (!params().special.empty())
+           options << params().special << ",\n";
        string opts = options.str().c_str();
        return opts.substr(0,opts.size()-2);    // delete last ",\n"
 }
 
 namespace {
-string decideOutputImageFormat(string const & suffix)
+string findTargetFormat(string const & suffix)
 {
        // lyxrc.pdf_mode means:
        // Are we creating a PDF or a PS file?
        // (Should actually mean, are we using latex or pdflatex).      
-       lyxerr[Debug::GRAPHICS] << "decideOutput::lyxrc.pdf_mode = " << lyxrc.pdf_mode << "\n";
+       lyxerr[Debug::GRAPHICS] << "decideOutput: lyxrc.pdf_mode = "
+                           << lyxrc.pdf_mode << std::endl;
        if (lyxrc.pdf_mode) {
                if (contains(suffix,"ps") || suffix == "pdf")
                        return "pdf";
@@ -545,6 +586,7 @@ string decideOutputImageFormat(string const & suffix)
 
 } // Anon. namespace
 
+
 string const InsetGraphics::prepareFile(Buffer const *buf) const
 {
        // do_convert = Do we need to convert the file?
@@ -560,38 +602,36 @@ string const InsetGraphics::prepareFile(Buffer const *buf) const
        //   return original filename without the extension
        //
        // if it's a zipped one, than let LaTeX do the rest!!!
-       if ((zippedFile(params.filename) && params.noUnzip) || buf->niceFile) {
-           lyxerr[Debug::GRAPHICS] << "don't unzip file or export latex" 
-                   << params.filename << endl;
-           return params.filename;
+       string filename_  = params().filename;
+       bool const zipped = zippedFile(filename_);
+
+       if ((zipped && params().noUnzip) || buf->niceFile) {
+               lyxerr[Debug::GRAPHICS] << "don't unzip file or export latex" 
+                                   << filename_ << endl;
+               return filename_;
        }
-       string filename_ = params.filename;
-       if (zippedFile(filename_))
-           filename_ = unzipFile(filename_);
-       // now we have unzipped files
-       // Get the extension (format) of the original file.
-       // we handle it like a virtual one, so we can have
-       // different extensions with the same type.
-       string const extension = getExtFromContents(filename_);
-       // are we usind latex ((e)ps) or pdflatex (pdf,jpg,png)
-       string const image_target = decideOutputImageFormat(extension);
-       if (extension == image_target)          // :-)
+
+       if (zipped)
+               filename_ = unzipFile(filename_);
+
+       string const from = getExtFromContents(filename_);
+       string const to   = findTargetFormat(from);
+
+       if (from == to) {
+               // No conversion needed!
                return filename_;
-//     commented out to check if the "not exist"bug is fixed.
-//     if (!IsFileReadable(filename_)) {       // :-(
-//             Alert::alert(_("File") + params.filename,
-//                        _("isn't readable or doesn't exists!"));
-//             return filename_;
-//     }
-       string outfile;
+       }
+
        string const temp = AddName(buf->tmppath, filename_);
-       outfile = RemoveExtension(temp);
+       string const outfile_base = RemoveExtension(temp);
+
        lyxerr[Debug::GRAPHICS] << "tempname = " << temp << "\n";
        lyxerr[Debug::GRAPHICS] << "buf::tmppath = " << buf->tmppath << "\n";
        lyxerr[Debug::GRAPHICS] << "filename_ = " << filename_ << "\n";
-       lyxerr[Debug::GRAPHICS] << "outfile = " << outfile << endl;
-       converters.convert(buf, filename_, outfile, extension, image_target);
-       return outfile;
+       lyxerr[Debug::GRAPHICS] << "outfile_base = " << outfile_base << endl;
+
+       converters.convert(buf, filename_, outfile_base, from, to);
+       return outfile_base;
 }
 
 
@@ -600,7 +640,7 @@ int InsetGraphics::latex(Buffer const *buf, ostream & os,
 {
        // If there is no file specified, just output a message about it in
        // the latex output.
-       if (params.filename.empty()) {
+       if (params().filename.empty()) {
                os  << "\\fbox{\\rule[-0.5in]{0pt}{1in}"
                        << _("empty figure path") << "}\n";
                return 1; // One end of line marker added to the stream.
@@ -610,8 +650,8 @@ int InsetGraphics::latex(Buffer const *buf, ostream & os,
        string before;
        string after;
        // Do we want subcaptions?
-       if (params.subcaption) {
-               before += "\\subfigure[" + params.subcaptionText + "]{";
+       if (params().subcaption) {
+               before += "\\subfigure[" + params().subcaptionText + "]{";
                after = '}';
        }
        // We never use the starred form, we use the "clip" option instead.
@@ -626,9 +666,11 @@ int InsetGraphics::latex(Buffer const *buf, ostream & os,
        // appropriate (when there are several versions in different formats)
        string const latex_str = before + '{' + prepareFile(buf) + '}' + after;
        os << latex_str;
+
        // Return how many newlines we issued.
        int const newlines =
                int(lyx::count(latex_str.begin(), latex_str.end(),'\n') + 1);
+               
        // lyxerr << "includegraphics: " << newlines << " lines of text"
        //        << endl; 
        return newlines;
@@ -642,7 +684,7 @@ int InsetGraphics::ascii(Buffer const *, ostream & os, int) const
        // 1. Convert file to ascii using gifscii
        // 2. Read ascii output file and add it to the output stream.
        // at least we send the filename
-       os << '<' << _("Graphicfile:") << params.filename << ">\n";
+       os << '<' << _("Graphicfile:") << params().filename << ">\n";
        return 0;
 }
 
@@ -670,59 +712,27 @@ int InsetGraphics::docbook(Buffer const *, ostream & os) const
 void InsetGraphics::validate(LaTeXFeatures & features) const
 {
        // If we have no image, we should not require anything.
-       if (params.filename.empty())
+       if (params().filename.empty())
                return ;
 
-       features.includeFile(graphic_label, RemoveExtension(params.filename));
+       features.includeFile(graphic_label, RemoveExtension(params_.filename));
 
        features.require("graphicx");
 
-       if (params.subcaption)
+       if (params().subcaption)
                features.require("subfigure");
 }
 
 
-// Update the inset after parameters changed (read from file or changed in
-// dialog.
-void InsetGraphics::updateInset() const
-{
-       GraphicsCache & gc = GraphicsCache::getInstance();
-       boost::shared_ptr<GraphicsCacheItem> temp(0);
-
-       // We do it this way so that in the face of some error, we will still
-       // be in a valid state.
-       InsetGraphicsParams::DisplayType local_display = params.display;
-       if (local_display == InsetGraphicsParams::DEFAULT) {
-               if (lyxrc.display_graphics == "mono")
-                       local_display = InsetGraphicsParams::MONOCHROME;
-               else if (lyxrc.display_graphics == "gray")
-                       local_display = InsetGraphicsParams::GRAYSCALE;
-               else if (lyxrc.display_graphics == "color")
-                       local_display = InsetGraphicsParams::COLOR;
-               else
-                       local_display = InsetGraphicsParams::NONE;
-       }
-
-       if (!params.filename.empty() && lyxrc.use_gui &&
-           local_display != InsetGraphicsParams::NONE) {
-               temp = gc.addFile(params.filename);
-       }
-
-       // Mark the image as unloaded so that it gets updated.
-       imageLoaded = false;
-
-       cacheHandle = temp;
-}
-
-
 bool InsetGraphics::setParams(InsetGraphicsParams const & p)
 {
        // If nothing is changed, just return and say so.
-       if (params == p)
+       if (params() == p && !p.filename.empty()) {
                return false;
+       }
 
        // Copy the new parameters.
-       params = p;
+       params_ = p;
 
        // Update the inset with the new parameters.
        updateInset();
@@ -732,9 +742,9 @@ bool InsetGraphics::setParams(InsetGraphicsParams const & p)
 }
 
 
-InsetGraphicsParams InsetGraphics::getParams() const
+InsetGraphicsParams const & InsetGraphics::params() const
 {
-       return params;
+       return params_;
 }
 
 
@@ -742,4 +752,3 @@ Inset * InsetGraphics::clone(Buffer const &, bool same_id) const
 {
        return new InsetGraphics(*this, same_id);
 }
-
index ac380bffc46564d9f8b3fb5dd26c4139161a7f67..7f5143981d991b217f718630d5aece8d8fe24121 100644 (file)
 #pragma interface
 #endif 
 
+#include "graphics/GraphicsTypes.h"
 #include "insets/inset.h"
 #include "insets/insetgraphicsParams.h"
-#include "graphics/GraphicsCacheItem.h"
-#include <boost/smart_ptr.hpp>
-
-#include "LaTeXFeatures.h"
-
 // We need a signal here to hide an active dialog when we are deleted.
 #include "sigc++/signal_system.h"
 
 class Dialogs;
-class LyXImage;
+class LaTeXFeatures;
 
 ///
 class InsetGraphics : public Inset, public SigC::Object {
@@ -92,7 +88,7 @@ public:
        bool setParams(InsetGraphicsParams const & params);
 
        /// Get the inset parameters, used by the GUIndependent dialog.
-       InsetGraphicsParams getParams() const;
+       InsetGraphicsParams const & params() const;
 
        /** This signal is connected by our dialog and called when the inset
            is deleted.
@@ -100,6 +96,11 @@ public:
        SigC::Signal0<void> hideDialog;
 
 private:
+       /// Set the cached variables
+       void setCache() const;
+       /// Is the image ready to draw, or should we display a message instead?
+       bool drawImage() const;
+
        /// Read the inset native format
        void readInsetGraphics(Buffer const * buf, LyXLex & lex);
        /// Read the FigInset file format
@@ -113,14 +114,19 @@ private:
        string const createLatexOptions() const;
        /// Convert the file if needed, and return the location of the file.
        string const prepareFile(Buffer const * buf) const;
-       /// The graphics cache handle.
-       mutable boost::shared_ptr<GraphicsCacheItem> cacheHandle;
-       /// is the pixmap initialized?
-       mutable bool imageLoaded;
-       /// the parameters
-       InsetGraphicsParams params;
+
+       ///
+       InsetGraphicsParams params_;
+
        /// holds the entity name that defines the graphics location (SGML).
        string const graphic_label;
+
+       /// The cached variables
+       mutable grfx::ImageStatus cached_status_;
+       ///
+       mutable grfx::ImagePtr cached_image_;
+       ///
+       mutable bool cache_filled_;
 };
 
 #endif 
index 21b21daf2caec4ab9dc1713f38d4eb00d6a5cca7..3e3691f1b787eaa1ebe478a6dff1f335ba9a9e01 100644 (file)
 #include "frontends/Menubar.h"
 #include "frontends/Alert.h"
 
+#include "graphics/GraphicsCache.h"
+
 #include "support/lyxalgo.h"
 #include "support/LAssert.h"
 #include "support/filetools.h"
 #include "support/FileInfo.h"
+#include "support/forkedcontr.h"
 #include "support/lstrings.h"
 #include "support/path.h"
 #include "support/lyxfunctional.h"
@@ -968,7 +971,7 @@ string const LyXFunc::dispatch(kb_action action, string argument)
        }
 
        switch (action) {
-               
+
        case LFUN_ESCAPE:
        {
                if (!owner->view()->available()) break;
@@ -1603,6 +1606,10 @@ string const LyXFunc::dispatch(kb_action action, string argument)
                        break;
                        }
 
+               bool const graphicsbg_changed =
+                       (lyx_name == lcolor.getLyXName(LColor::graphicsbg) &&
+                        x11_name != lcolor.getX11Name(LColor::graphicsbg));
+
                if (!lcolor.setColor(lyx_name, x11_name)) {
                        static string const err1 (N_("Set-color \""));
                        static string const err2 (
@@ -1611,7 +1618,14 @@ string const LyXFunc::dispatch(kb_action action, string argument)
                        setErrorMessage(_(err1) + lyx_name + _(err2));
                        break;
                }
+
                lyxColorHandler->updateColor(lcolor.getFromLyXName(lyx_name));
+
+               if (graphicsbg_changed) {
+                       grfx::GCache & gc = grfx::GCache::get();
+                       gc.changeDisplay(true);
+               }
+               
                owner->view()->redraw();
                break;
        }
@@ -1628,6 +1642,21 @@ string const LyXFunc::dispatch(kb_action action, string argument)
                owner->messagePop();
                break;
 
+       case LFUN_FORKS_SHOW:
+               owner->getDialogs()->showForks();
+               break;
+
+       case LFUN_FORKS_KILL:
+       {
+               if (!isStrInt(argument))
+                       break;
+
+               pid_t const pid = strToInt(argument);
+               ForkedcallsController & fcc = ForkedcallsController::get();
+               fcc.kill(pid);
+               break;
+       }
+
        default:
                // Then if it was none of the above
                // Trying the BufferView::pimpl dispatch:
index 79f7f72eb82253a0f477f3f231119c32d148283a..95f463fe8fe3bb07b8364fed4280d48625e900be 100644 (file)
@@ -225,7 +225,7 @@ void LyXRC::setDefaults() {
        make_backup = true;
        backupdir_path.erase();
        exit_confirmation = true;
-       display_graphics = "mono";
+       display_graphics = "color";
        display_shortcuts = true;
        // Spellchecker settings:
 #ifdef USE_PSPELL      
index 1e72a15a0d85851f89eaa8ae78d13a014e55d0b9..4045531aed0c0f10ca414082323e2f029d34a408 100644 (file)
@@ -1,3 +1,14 @@
+2002-02-19  Angus Leeming  <a.leeming@ic.ac.uk>
+
+       * forkedcall.[Ch]:
+       * forkedcontr.[Ch]: new files. Asger's forked call controller is
+       re-born, with a working timer and a modified interface. The
+       startscript method is now passed a Signal rather than a pointer
+       to a callback function. This enables us to connect to the method of
+       a C++ class, if we so desire.
+
+       * Makefile.am: add forkedcall.[Ch], forkedcontr.[Ch].
+
 2002-02-26  John Levon  <moz@compsoc.man.ac.uk>
 
        * Makefile.am:
index 4670add509981d2a6a48ea3b8a0dc477ec51803e..3462a57536966fb0ff9d26d616f60da68c65dd7c 100644 (file)
@@ -5,7 +5,8 @@ noinst_LTLIBRARIES = libsupport.la
 LIBS =
 ETAGS_ARGS = --lang=c++
 BOOST_INCLUDES = -I$(top_srcdir)/boost
-INCLUDES = -I${srcdir}/../ $(BOOST_INCLUDES)
+SIGC_INCLUDES = -I$(top_srcdir)
+INCLUDES = -I${srcdir}/../ $(SIGC_INCLUDES) $(BOOST_INCLUDES)
 
 EXTRA_DIST = lyxstring.C lyxstring.h regex.c lyxregex.h \
              os_unix.C os_win32.C os_os2.C
@@ -42,6 +43,10 @@ libsupport_la_SOURCES = \
        filetools.C \
        filetools.h \
        fmt.C \
+       forkedcall.C \
+       forkedcall.h \
+       forkedcontr.C \
+       forkedcontr.h \
        getUserName.C \
        getcwd.C \
        kill.C \
diff --git a/src/support/forkedcall.C b/src/support/forkedcall.C
new file mode 100644 (file)
index 0000000..a69cae3
--- /dev/null
@@ -0,0 +1,270 @@
+/**
+ *  \file syscall.C
+ *  Copyright 2002 the LyX Team
+ *  Read the file COPYING
+ *
+ * \author Asger Alstrup
+ *
+ * Interface cleaned up by
+ * \author Angus Leeming <a.leeming@ic.ac.uk>
+ *
+ * An instance of Class Forkedcall represents a single child process.
+ *
+ * Class Forkedcall uses fork() and execvp() to lauch the child process.
+ *
+ * Once launched, control is returned immediately to the parent process
+ * but a Signal can be emitted upon completion of the child.
+ *
+ * The child process is not killed when the Forkedcall instance goes out of
+ * scope, but it can be killed by an explicit invocation of the kill() member
+ * function.
+ */
+
+#include <config.h>
+
+#ifdef __GNUG__
+#pragma implementation
+#endif
+
+#include "forkedcall.h"
+#include "forkedcontr.h"
+#include "lstrings.h"
+#include "lyxlib.h"
+#include "filetools.h"
+#include "os.h"
+#include "debug.h"
+#include "frontends/Timeout.h"
+
+#include <cerrno>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <csignal>
+#include <cstdlib>
+#include <unistd.h>
+
+using std::endl;
+
+#ifndef CXX_GLOBAL_CSTD
+using std::strerror;
+#endif
+
+
+Forkedcall::Forkedcall()
+       : pid_(0), retval_(0)
+{}
+
+
+int Forkedcall::startscript(Starttype wait, string const & what)
+{
+       if (wait == Wait) {
+               command_ = what;
+               retval_  = 0;
+
+               pid_ = generateChild();
+               if (pid_ <= 0) { // child or fork failed.
+                       retval_ = 1;
+               } else {
+                       retval_ = waitForChild();
+               }
+               
+               return retval_;
+       }
+
+       // DontWait
+       retval_ = startscript(what, SignalTypePtr());
+       return retval_;
+}
+
+
+int Forkedcall::startscript(string const & what, SignalTypePtr signal)
+{
+       command_ = what;
+       signal_  = signal;
+       retval_  = 0;
+
+       pid_ = generateChild();
+       if (pid_ <= 0) { // child or fork failed.
+               retval_ = 1;
+               return retval_;
+       }
+
+       // Non-blocking execution.
+       // Integrate into the Controller
+       ForkedcallsController & contr = ForkedcallsController::get();
+       contr.addCall(*this);
+
+       return retval_;
+}
+
+
+void Forkedcall::emitSignal()
+{
+       if (signal_.get()) {
+               signal_->emit(command_, pid_, retval_);
+       }
+}
+
+
+namespace {
+
+class Murder : public SigC::Object {
+public:
+       //
+       static void killItDead(int secs, pid_t pid)
+       {
+               if (secs > 0) {
+                       new Murder(secs, pid);
+               } else if (pid != 0) {
+                       lyx::kill(pid, SIGKILL);
+               }
+       }
+
+       //
+       void kill()
+       {
+               if (pid_ != 0) {
+                       lyx::kill(pid_, SIGKILL);
+               }
+               lyxerr << "Killed " << pid_ << std::endl;
+               delete this;
+       }
+
+private:
+       //
+       Murder(int secs, pid_t pid)
+               : timeout_(0), pid_(pid)
+       {
+               timeout_ = new Timeout(1000*secs, Timeout::ONETIME);
+               timeout_->timeout.connect(SigC::slot(this, &Murder::kill));
+               timeout_->start();
+       }
+
+       //
+       ~Murder()
+       {
+               delete timeout_;
+       }
+       //
+       Timeout * timeout_;
+       //
+       pid_t pid_;
+};
+
+} // namespace anon
+
+
+void Forkedcall::kill(int tol)
+{
+       lyxerr << "Forkedcall::kill(" << tol << ")" << std::endl;
+       if (pid() == 0) {
+               lyxerr << "Can't kill non-existent process!" << endl;
+               return;
+       }
+
+       int const tolerance = std::max(0, tol);
+       if (tolerance == 0) {
+               // Kill it dead NOW!
+               Murder::killItDead(0, pid());
+
+       } else {
+               int ret = lyx::kill(pid(), SIGHUP);
+
+               // The process is already dead if wait_for_death is false
+               bool const wait_for_death = (ret == 0 && errno != ESRCH);
+
+               if (wait_for_death) {
+                       Murder::killItDead(tolerance, pid());
+               }
+       }
+}
+
+
+// Wait for child process to finish. Returns returncode from child.
+int Forkedcall::waitForChild() {
+       // We'll pretend that the child returns 1 on all error conditions.
+       retval_ = 1;
+       int status;
+       bool wait = true;
+       while (wait) {
+               pid_t waitrpid = waitpid(pid_, &status, WUNTRACED);
+               if (waitrpid == -1) {
+                       lyxerr << "LyX: Error waiting for child:"
+                              << strerror(errno) << endl;
+                       wait = false;
+               } else if (WIFEXITED(status)) {
+                       // Child exited normally. Update return value.
+                       retval_ = WEXITSTATUS(status);
+                       wait = false;
+               } else if (WIFSIGNALED(status)) {
+                       lyxerr << "LyX: Child didn't catch signal "
+                              << WTERMSIG(status) 
+                              << "and died. Too bad." << endl;
+                       wait = false;
+               } else if (WIFSTOPPED(status)) {
+                       lyxerr << "LyX: Child (pid: " << pid_
+                              << ") stopped on signal "
+                              << WSTOPSIG(status) 
+                              << ". Waiting for child to finish." << endl;
+               } else {
+                       lyxerr << "LyX: Something rotten happened while "
+                               "waiting for child " << pid_ << endl;
+                       wait = false;
+               }
+       }
+       return retval_;
+}
+
+
+// generate child in background
+pid_t Forkedcall::generateChild()
+{
+       const int MAX_ARGV = 255;
+       char *syscmd = 0; 
+       char *argv[MAX_ARGV];
+
+       string childcommand(command_); // copy
+       bool more = true;
+       string rest = split(command_, childcommand, ' ');
+
+       int  index = 0;
+       while (more) {
+               childcommand = frontStrip(childcommand);
+               if (syscmd == 0) {
+                       syscmd = new char[childcommand.length() + 1];
+                       childcommand.copy(syscmd, childcommand.length());
+                       syscmd[childcommand.length()] = '\0';
+               }
+               if (!childcommand.empty()) {
+                       char * tmp = new char[childcommand.length() + 1];
+                       childcommand.copy(tmp, childcommand.length());
+                       tmp[childcommand.length()] = '\0';
+                       argv[index++] = tmp;
+               }
+
+               // reinit
+               more = !rest.empty();
+               if (more) 
+                       rest = split(rest, childcommand, ' ');
+       }
+       argv[index] = 0;
+
+#ifndef __EMX__
+       pid_t cpid = ::fork();
+       if (cpid == 0) { // child
+               execvp(syscmd, argv);
+               // If something goes wrong, we end up here:
+               lyxerr << "execvp failed: "
+                      << strerror(errno) << endl;
+       }
+#else
+       pid_t cpid = spawnvp(P_SESSION|P_DEFAULT|P_MINIMIZE|P_BACKGROUND,
+                            syscmd, argv);
+#endif
+
+       if (cpid < 0) { // error
+               lyxerr << "Could not fork: "
+                      << strerror(errno) << endl;
+       }
+
+       return cpid;
+}
diff --git a/src/support/forkedcall.h b/src/support/forkedcall.h
new file mode 100644 (file)
index 0000000..8e15029
--- /dev/null
@@ -0,0 +1,138 @@
+// -*- C++ -*-
+/**
+ *  \file syscall.h
+ *  Copyright 2002 the LyX Team
+ *  Read the file COPYING
+ *
+ * \author Asger Alstrup
+ *
+ * Interface cleaned up by
+ * \author Angus Leeming <a.leeming@ic.ac.uk>
+ *
+ * An instance of Class Forkedcall represents a single child process.
+ *
+ * Class Forkedcall uses fork() and execvp() to lauch the child process.
+ *
+ * Once launched, control is returned immediately to the parent process
+ * but a Signal can be emitted upon completion of the child.
+ *
+ * The child process is not killed when the Forkedcall instance goes out of
+ * scope, but it can be killed by an explicit invocation of the kill() member
+ * function.
+ */
+
+#ifndef FORKEDCALL_H
+#define FORKEDCALL_H
+
+#ifdef __GNUG__
+#pragma interface
+#endif
+
+#include "LString.h"
+#include <sys/types.h>
+#include <boost/smart_ptr.hpp>
+#include <sigc++/signal_system.h>
+
+
+class Forkedcall {
+public:
+       ///
+       enum Starttype {
+               ///
+               Wait,
+               ///
+               DontWait
+       };
+       
+       ///
+       Forkedcall();
+       
+       /** Start the child process.
+        *
+        *  The command "what" is passed to fork() for execution.
+        *
+        *  There are two startscript commands available. They differ in that
+        *  the second receives a signal that is executed on completion of
+        *  the command. This makes sense only for a command executed
+        *  in the background, ie DontWait.
+        *
+        *  The other startscript command can be executed either blocking
+        *  or non-blocking, but no signal will be emitted on finishing.
+        */
+       int startscript(Starttype, string const & what);
+
+       /** A SignalType signal is can be emitted once the forked process
+        *  has finished. It passes:
+        *  the commandline string;
+        *  the PID of the child and;
+        *  the return value from the child.
+        *
+        *  We use a signal rather than simply a callback function so that
+        *  we can return easily to C++ methods, rather than just globally
+        *  accessible functions.
+        */
+       typedef SigC::Signal3<void, string, pid_t, int> SignalType;
+
+       /** The signal is connected in the calling routine to the desired
+        *  slot. We pass a shared_ptr rather than a reference to the signal
+        *  because it is eminently possible for the instance of the calling
+        *  class (and hence the signal) to be destructed before the forked
+        *  call is complete.
+        *
+        *  It doesn't matter if the slot disappears, SigC takes care of that.
+        */
+       typedef boost::shared_ptr<SignalType> SignalTypePtr;
+
+       ///
+       int startscript(string const & what, SignalTypePtr);
+
+       /** Invoking the following methods makes sense only if the command
+        *  is running asynchronously!
+        */
+
+       /** gets the PID of the child process.
+        *  Used by the timer.
+        */
+       pid_t pid() const { return pid_; }
+       
+       /** Emit the signal.
+        *  Used by the timer.
+        */
+       void emitSignal();
+
+       /** Set the return value of the child process.
+        *  Used by the timer.
+        */
+       void setRetValue(int r) { retval_ = r; }
+
+       /** Kill child prematurely.
+        *  First, a SIGHUP is sent to the child.
+        *  If that does not end the child process within "tolerance"
+        *  seconds, the SIGKILL signal is sent to the child.
+        *  When the child is dead, the callback is called.
+        */
+       void kill(int tolerance = 5);
+       ///
+       string const & command() const { return command_; }
+
+private:
+       /// Callback function
+       SignalTypePtr signal_;
+
+       /// Commmand line
+       string command_;
+
+       /// Process ID of child
+       pid_t pid_;
+
+       /// Return value from child
+       int retval_;
+
+       ///
+       pid_t generateChild();
+
+       /// Wait for child process to finish. Updates returncode from child.
+       int waitForChild();
+};
+
+#endif // FORKEDCALL_H
diff --git a/src/support/forkedcontr.C b/src/support/forkedcontr.C
new file mode 100644 (file)
index 0000000..13b397e
--- /dev/null
@@ -0,0 +1,207 @@
+/**
+ * \file forkedcontr.C
+ * Copyright 2001 The LyX Team
+ * Read COPYING
+ *
+ * \author Asger Alstrup Nielsen
+ * \author Angus Leeming
+ *
+ * A class for the control of child processes launched using 
+ * fork() and execvp().
+ */
+
+#include <config.h>
+
+#ifdef __GNUG__
+#pragma implementation
+#endif
+
+#include "forkedcontr.h"
+#include "forkedcall.h"
+#include "lyxfunctional.h"
+#include "frontends/Timeout.h"
+#include "debug.h"
+
+#include <cerrno>
+#include <cstdlib>
+#include <unistd.h>
+#include <sys/wait.h>
+
+using std::vector;
+using std::endl;
+using std::find_if;
+
+#ifndef CXX_GLOBAL_CSTD
+using std::strerror;
+#endif
+
+// Ensure, that only one controller exists inside process
+ForkedcallsController & ForkedcallsController::get()
+{
+       static ForkedcallsController singleton;
+       return singleton;
+}
+
+
+ForkedcallsController::ForkedcallsController()
+{
+       timeout_ = new Timeout(100, Timeout::CONTINUOUS);
+       
+       timeout_->timeout
+               .connect(SigC::slot(this, &ForkedcallsController::timer));
+}
+
+
+// open question: should we stop childs here?
+// Asger says no: I like to have my xdvi open after closing LyX. Maybe
+// I want to print or something.
+ForkedcallsController::~ForkedcallsController()
+{
+       for (ListType::iterator it = forkedCalls.begin();
+            it != forkedCalls.end(); ++it) {
+               delete *it;
+       }
+
+       delete timeout_;
+}
+
+
+// Add child process information to the list of controlled processes
+void ForkedcallsController::addCall(Forkedcall const &newcall)
+{
+       if (!timeout_->running())
+               timeout_->start();
+
+       Forkedcall * call = new Forkedcall(newcall);
+       forkedCalls.push_back(call);
+       childrenChanged.emit();
+}
+
+
+// Timer-call
+// Check the list and, if there is a stopped child, emit the signal.
+void ForkedcallsController::timer()
+{
+       ListType::size_type start_size = forkedCalls.size();
+
+       for (ListType::iterator it = forkedCalls.begin();
+            it != forkedCalls.end(); ++it) {
+               Forkedcall * actCall = *it;
+
+               pid_t pid = actCall->pid();
+               int stat_loc;
+               pid_t const waitrpid = waitpid(pid, &stat_loc, WNOHANG);
+               bool remove_it = false;
+
+               if (waitrpid == -1) {
+                       lyxerr << "LyX: Error waiting for child: " 
+                              << strerror(errno) << endl;
+
+                       // Child died, so pretend it returned 1
+                       actCall->setRetValue(1);
+                       remove_it = true;
+
+               } else if (waitrpid == 0) {
+                       // Still running. Move on to the next child.
+                       continue;
+
+               } else if (WIFEXITED(stat_loc)) {
+                       // Ok, the return value goes into retval.
+                       actCall->setRetValue(WEXITSTATUS(stat_loc));
+                       remove_it = true;
+
+               } else if (WIFSIGNALED(stat_loc)) {
+                       // Child died, so pretend it returned 1
+                       actCall->setRetValue(1);
+                       remove_it = true;
+
+               } else if (WIFSTOPPED(stat_loc)) {
+                       lyxerr << "LyX: Child (pid: " << pid 
+                              << ") stopped on signal "
+                              << WSTOPSIG(stat_loc) 
+                              << ". Waiting for child to finish." << endl;
+
+               } else {
+                       lyxerr << "LyX: Something rotten happened while "
+                               "waiting for child " << pid << endl;
+
+                       // Child died, so pretend it returned 1
+                       actCall->setRetValue(1);
+                       remove_it = true;
+               }
+
+               if (remove_it) {
+                       // Emit signal and remove the item from the list
+                       actCall->emitSignal();
+                       delete actCall;
+                       // erase returns the next iterator, so decrement it
+                       // to continue the loop.
+                       ListType::iterator prev = it;
+                       --prev;
+                       forkedCalls.erase(it);
+                       it = prev;
+               }
+       }
+
+       if (forkedCalls.empty()) {
+               timeout_->stop();
+       }
+
+       if (start_size != forkedCalls.size())
+               childrenChanged.emit();
+}
+
+
+// Return a vector of the pids of all the controlled processes.
+vector<pid_t> const ForkedcallsController::getPIDs() const
+{
+       vector<pid_t> pids;
+
+       if (forkedCalls.empty())
+               return pids;
+
+       pids.resize(forkedCalls.size());
+
+       vector<pid_t>::iterator vit = pids.begin();
+       for (ListType::const_iterator lit = forkedCalls.begin();
+            lit != forkedCalls.end(); ++lit, ++vit) {
+               *vit = (*lit)->pid();
+       }
+
+       std::sort(pids.begin(), pids.end());
+       return pids;
+}
+
+
+// Get the command string of the process.
+string const ForkedcallsController::getCommand(pid_t pid) const
+{
+       ListType::const_iterator it =
+               find_if(forkedCalls.begin(), forkedCalls.end(),
+                       lyx::compare_memfun(&Forkedcall::pid, pid));
+
+       if (it == forkedCalls.end())
+               return string();
+
+       return (*it)->command();
+}
+
+
+// Kill the process prematurely and remove it from the list
+// within tolerance secs
+void ForkedcallsController::kill(pid_t pid, int tolerance)
+{
+       ListType::iterator it =
+               find_if(forkedCalls.begin(), forkedCalls.end(),
+                       lyx::compare_memfun(&Forkedcall::pid, pid));
+
+       if (it == forkedCalls.end())
+               return;
+
+       (*it)->kill(tolerance);
+       forkedCalls.erase(it);
+
+       if (forkedCalls.empty()) {
+               timeout_->stop();
+       }
+}
diff --git a/src/support/forkedcontr.h b/src/support/forkedcontr.h
new file mode 100644 (file)
index 0000000..967ecc9
--- /dev/null
@@ -0,0 +1,78 @@
+// -*- C++ -*-
+/**
+ * \file forkedcontr.h
+ * Copyright 2001 The LyX Team
+ * Read COPYING
+ *
+ * \author Asger Alstrup Nielsen
+ * \author Angus Leeming
+ *
+ * A class for the control of child processes launched using
+ * fork() and execvp().
+ */
+
+#ifndef FORKEDCONTR_H
+#define FORKEDCONTR_H
+
+#include <list>
+#include <vector>
+#include "LString.h"
+#include <sigc++/signal_system.h>
+
+#ifdef __GNUG__
+#pragma interface
+#endif
+
+class Forkedcall;
+class Timeout;
+
+class ForkedcallsController : public SigC::Object {
+public:
+       /// Get hold of the only controller that can exist inside the process.
+        static ForkedcallsController & get();
+
+       /// Add a new child process to the list of controlled processes.
+       void addCall(Forkedcall const & newcall);
+
+       /** This method is connected to the timer. Every XX ms it is called
+        *  so that we can check on the status of the children. Those that
+        *  are found to have finished are removed from the list and their
+        *  callback function is passed the final return state.
+        */
+       void timer();
+
+       /// Return a vector of the pids of all the controlled processes.
+       std::vector<pid_t> const getPIDs() const;
+
+       /// Get the command string of the process.
+       string const getCommand(pid_t) const;
+
+       /** Kill this process prematurely and remove it from the list.
+        *  The process is killed within tolerance secs.
+        *  See forkedcall.[Ch] for details.
+        */
+       void kill(pid_t, int tolerance = 5);
+
+       /// Signal emitted when the list of current child processes changes.
+       SigC::Signal0<void> childrenChanged;
+       
+private:
+       /// Can't create multiple instances of ForkedcallsController.
+       ForkedcallsController();
+       ///
+       ForkedcallsController(ForkedcallsController const &);
+       ///
+       ~ForkedcallsController();
+
+       /// The child processes
+       typedef std::list<Forkedcall *> ListType;
+       ///
+       ListType forkedCalls;
+
+       /** The timer. Enables us to check the status of the children
+        *  every XX ms and to invoke a callback on completion.
+        */
+        Timeout * timeout_;
+};
+
+#endif // FORKEDCONTR_H