+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.
Item "Open...|O" "file-open"
Separator
Submenu "Import|I" "file_import"
+ Item "Child processes|h" "show-forks"
Separator
Item "Exit|x" "lyx-quit"
Separator
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
+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:
#include "language.h"
#include "frontends/GUIRunTime.h"
-#include "frontends/support/LyXImage.h"
+#include "graphics/GraphicsImage.h"
#include "support/LAssert.h"
#include "support/lstrings.h"
}
-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());
}
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);
class WorkArea;
class LyXFont;
-class LyXImage;
+namespace grfx {
+ class GImage;
+}
/** A painter class to encapsulate all graphics parameters and operations
// 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,
+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
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>
--- /dev/null
+/**
+ * \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();
+}
+
--- /dev/null
+// -*- 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
InsetGraphicsParams const
ControlGraphics::getParams(InsetGraphics const & inset)
{
- return inset.getParams();
+ return inset.params();
}
#include "ControlERT.h"
#include "ControlExternal.h"
#include "ControlFloat.h"
+#include "ControlForks.h"
#include "ControlGraphics.h"
#include "insets/insetgraphicsParams.h"
#include "ControlInclude.h"
};
+/** 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>
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 \
+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.
#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"
#include "FormERT.h"
#include "FormExternal.h"
#include "FormFloat.h"
+#include "FormForks.h"
#include "FormGraphics.h"
#include "FormInclude.h"
#include "FormIndex.h"
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));
--- /dev/null
+/**
+ * \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;
+}
+
+
--- /dev/null
+// -*- 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
#include "support/filetools.h"
#include "support/LAssert.h"
+#include "graphics/GraphicsCache.h"
+
using std::endl;
using std::pair;
using std::make_pair;
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)) {
} else {
lyxrc.display_graphics = "no";
}
+ if (old_value != lyxrc.display_graphics) {
+ grfx::GCache & gc = grfx::GCache::get();
+ gc.changeDisplay();
+ }
}
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 \
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 \
--- /dev/null
+// 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;
+}
+/*---------------------------------------*/
+
--- /dev/null
+// 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_ */
--- /dev/null
+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
form_external.fd \
form_filedialog.fd \
form_float.fd \
+ form_forks.fd \
form_graphics.fd \
form_include.fd \
form_index.fd \
+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
-/* 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
// -*- 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
-/* 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_);
+ }
}
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:
\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
// -*- 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
--- /dev/null
+/*
+ * \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
--- /dev/null
+// -*- 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
--- /dev/null
+/*
+ * \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
+
--- /dev/null
+// -*- 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
--- /dev/null
+/*
+ * \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
--- /dev/null
+// -*- 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
--- /dev/null
+/*
+ * \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
--- /dev/null
+// -*- 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
--- /dev/null
+// -*- 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
+++ /dev/null
-/* 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);
-}
-
+++ /dev/null
-// -*- 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
+++ /dev/null
-/* 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;
-}
+++ /dev/null
-// -*- 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
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
#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
{
return ChangeExtension(filename, string());
}
+
+} // namespace anon
namespace {
} // 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_;
}
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();
}
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;
}
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);
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);
}
}
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()) {
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);
}
void InsetGraphics::write(Buffer const * buf, ostream & os) const
{
os << "Graphics FormatVersion " << VersionNumber << '\n';
- params.Write(buf, os);
+ params().Write(buf, os);
}
string const token = lex.getString();
lyxerr[Debug::GRAPHICS] << "Token: '" << token << '\''
- << endl;
+ << std::endl;
if (token.empty()) {
continue;
<< "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;
}
}
}
// 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();
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;
}
}
}
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;
}
}
}
// 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";
} // Anon. namespace
+
string const InsetGraphics::prepareFile(Buffer const *buf) const
{
// do_convert = Do we need to convert the file?
// 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;
}
{
// 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.
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.
// 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;
// 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;
}
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();
}
-InsetGraphicsParams InsetGraphics::getParams() const
+InsetGraphicsParams const & InsetGraphics::params() const
{
- return params;
+ return params_;
}
{
return new InsetGraphics(*this, same_id);
}
-
#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 {
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.
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
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
#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"
}
switch (action) {
-
+
case LFUN_ESCAPE:
{
if (!owner->view()->available()) break;
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 (
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;
}
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:
make_backup = true;
backupdir_path.erase();
exit_confirmation = true;
- display_graphics = "mono";
+ display_graphics = "color";
display_shortcuts = true;
// Spellchecker settings:
#ifdef USE_PSPELL
+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:
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
filetools.C \
filetools.h \
fmt.C \
+ forkedcall.C \
+ forkedcall.h \
+ forkedcontr.C \
+ forkedcontr.h \
getUserName.C \
getcwd.C \
kill.C \
--- /dev/null
+/**
+ * \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;
+}
--- /dev/null
+// -*- 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
--- /dev/null
+/**
+ * \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();
+ }
+}
--- /dev/null
+// -*- 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