diff --git a/lib/ChangeLog b/lib/ChangeLog index cace336a48..4909b31794 100644 --- a/lib/ChangeLog +++ b/lib/ChangeLog @@ -1,3 +1,7 @@ +2002-02-19 Angus Leeming + + * ui/default.ui: add a show-forks item to the two File menus. + 2002-02-21 Jean-Marc Lasgouttes * ui/default.ui: change Layout>LaTeX Preamble to Layout>Preamble. diff --git a/lib/ui/default.ui b/lib/ui/default.ui index 7c5aa5bdeb..c3522a45f0 100644 --- a/lib/ui/default.ui +++ b/lib/ui/default.ui @@ -31,6 +31,7 @@ Menuset Item "Open...|O" "file-open" Separator Submenu "Import|I" "file_import" + Item "Child processes|h" "show-forks" Separator Item "Exit|x" "lyx-quit" Separator @@ -52,6 +53,7 @@ Menuset Submenu "Export|E" "file_export" Item "Print...|P" "buffer-print" OptItem "Fax...|F" "buffer-export fax" + Item "Child processes|h" "show-forks" Separator Item "Exit|x" "lyx-quit" Separator diff --git a/src/ChangeLog b/src/ChangeLog index a85eaec9cf..cc9a184b92 100644 --- a/src/ChangeLog +++ b/src/ChangeLog @@ -1,3 +1,12 @@ +2002-02-20 Angus Leeming + + * 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 * Makefile.am: diff --git a/src/Painter.C b/src/Painter.C index a44065ad10..db10023d73 100644 --- a/src/Painter.C +++ b/src/Painter.C @@ -25,7 +25,7 @@ #include "language.h" #include "frontends/GUIRunTime.h" -#include "frontends/support/LyXImage.h" +#include "graphics/GraphicsImage.h" #include "support/LAssert.h" #include "support/lstrings.h" @@ -171,11 +171,10 @@ PainterBase & Painter::pixmap(int x, int y, int w, int h, Pixmap bitmap) } -PainterBase & Painter::image(int x, int y, int w, int h, LyXImage const * image) +PainterBase & Painter::image(int x, int y, int w, int h, + grfx::GImage const & image) { - Pixmap bitmap = image->getPixmap(); - - return pixmap(x, y, w, h, bitmap); + return pixmap(x, y, w, h, image.getPixmap()); } diff --git a/src/Painter.h b/src/Painter.h index 8d39c2ba02..f020d53313 100644 --- a/src/Painter.h +++ b/src/Painter.h @@ -76,7 +76,8 @@ public: LColor::color); /// For the graphics inset. - PainterBase & image(int x, int y, int w, int h, LyXImage const * image); + PainterBase & image(int x, int y, int w, int h, + grfx::GImage const & image); /// For the figinset PainterBase & pixmap(int x, int y, int w, int h, Pixmap bitmap); diff --git a/src/PainterBase.h b/src/PainterBase.h index e3b7d07472..303bb157fb 100644 --- a/src/PainterBase.h +++ b/src/PainterBase.h @@ -20,7 +20,9 @@ class WorkArea; class LyXFont; -class LyXImage; +namespace grfx { + class GImage; +} /** A painter class to encapsulate all graphics parameters and operations @@ -147,8 +149,8 @@ public: // For the figure inset - virtual PainterBase & image(int x, int y, int w, int h, LyXImage const * image) = 0; - + virtual PainterBase & image(int x, int y, int w, int h, + grfx::GImage const & image) = 0; /// Draw a string at position x, y (y is the baseline) virtual PainterBase & text(int x, int y, diff --git a/src/frontends/controllers/ChangeLog b/src/frontends/controllers/ChangeLog index ac150815c4..e6978f11d4 100644 --- a/src/frontends/controllers/ChangeLog +++ b/src/frontends/controllers/ChangeLog @@ -1,3 +1,16 @@ +2002-02-20 Angus Leeming + + * 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 * biblio.C: fix bug with commentlines in a bibentry @@ -8,7 +21,7 @@ 2002-02-18 Herbert Voss - * ControlGraphics.[C]: remove help-file call + * ControlGraphics.[Ch]: remove help-file call 2002-02-18 Angus Leeming diff --git a/src/frontends/controllers/ControlForks.C b/src/frontends/controllers/ControlForks.C new file mode 100644 index 0000000000..d90b7a1d28 --- /dev/null +++ b/src/frontends/controllers/ControlForks.C @@ -0,0 +1,96 @@ +/** + * \file ControlForks.C + * Copyright 2001 The LyX Team + * Read COPYING + * + * \author Angus Leeming + */ + +#include + +#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 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::const_iterator it = pids_.begin(); + it != pids_.end(); ++it) { + lv_.getLyXFunc()->dispatch(LFUN_FORKS_KILL, *it); + } + + pids_.clear(); +} + + +void ControlForks::setParams() +{ + if (childrenChanged_.connected()) + return; + + pids_.clear(); + + ForkedcallsController & fcc = ForkedcallsController::get(); + childrenChanged_ = + fcc.childrenChanged.connect(slot(this, &ControlForks::update)); +} + + +void ControlForks::clearParams() +{ + pids_.clear(); + childrenChanged_.disconnect(); +} + diff --git a/src/frontends/controllers/ControlForks.h b/src/frontends/controllers/ControlForks.h new file mode 100644 index 0000000000..46de1853d3 --- /dev/null +++ b/src/frontends/controllers/ControlForks.h @@ -0,0 +1,49 @@ +// -*- C++ -*- +/** + * \file ControlForks.h + * Copyright 2001 The LyX Team + * Read COPYING + * + * \author Angus Leeming + */ + +#ifndef CONTROLFORKS_H +#define CONTROLFORKS_H + +#ifdef __GNUG__ +#pragma interface +#endif + +#include "ControlDialog_impl.h" +#include "LString.h" +#include +#include + +/** 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 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 pids_; +}; + +#endif // CONTROLFORKS_H diff --git a/src/frontends/controllers/ControlGraphics.C b/src/frontends/controllers/ControlGraphics.C index 9bf1949d3f..8231bdb0fc 100644 --- a/src/frontends/controllers/ControlGraphics.C +++ b/src/frontends/controllers/ControlGraphics.C @@ -58,7 +58,7 @@ InsetGraphicsParams const ControlGraphics::getParams(string const &) InsetGraphicsParams const ControlGraphics::getParams(InsetGraphics const & inset) { - return inset.getParams(); + return inset.params(); } diff --git a/src/frontends/controllers/GUI.h b/src/frontends/controllers/GUI.h index 08430e15ce..b3dd8b2fb3 100644 --- a/src/frontends/controllers/GUI.h +++ b/src/frontends/controllers/GUI.h @@ -22,6 +22,7 @@ #include "ControlERT.h" #include "ControlExternal.h" #include "ControlFloat.h" +#include "ControlForks.h" #include "ControlGraphics.h" #include "insets/insetgraphicsParams.h" #include "ControlInclude.h" @@ -161,6 +162,17 @@ public: }; +/** Specialization for Forks dialog + */ +template +class GUIForks : + public GUI { +public: + /// + GUIForks(LyXView & lv, Dialogs & d) + : GUI(lv, d) {} +}; + /** Specialization for Graphics dialog */ template diff --git a/src/frontends/controllers/Makefile.am b/src/frontends/controllers/Makefile.am index 751261dd89..0c31357fb5 100644 --- a/src/frontends/controllers/Makefile.am +++ b/src/frontends/controllers/Makefile.am @@ -40,16 +40,18 @@ libcontrollers_la_SOURCES= \ ControlDialog.h \ ControlDialog_impl.C \ ControlDialog_impl.h \ - ControlError.h \ ControlError.C \ - ControlERT.h \ + ControlError.h \ ControlERT.C \ - ControlExternal.h \ + ControlERT.h \ ControlExternal.C \ - ControlFloat.h \ + ControlExternal.h \ ControlFloat.C \ - ControlGraphics.h \ + ControlFloat.h \ + ControlForks.C \ + ControlForks.h \ ControlGraphics.C \ + ControlGraphics.h \ ControlInclude.C \ ControlInclude.h \ ControlIndex.C \ diff --git a/src/frontends/xforms/ChangeLog b/src/frontends/xforms/ChangeLog index 78fe413384..00d070ea89 100644 --- a/src/frontends/xforms/ChangeLog +++ b/src/frontends/xforms/ChangeLog @@ -1,3 +1,19 @@ +2002-02-20 Angus Leeming + + * 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 * forms/form_graphics.fd: Enlarge Restore button. diff --git a/src/frontends/xforms/Dialogs.C b/src/frontends/xforms/Dialogs.C index 5bc247bcde..7333af5b83 100644 --- a/src/frontends/xforms/Dialogs.C +++ b/src/frontends/xforms/Dialogs.C @@ -31,6 +31,7 @@ #include "form_ert.h" #include "form_external.h" #include "form_float.h" +#include "form_forks.h" #include "form_graphics.h" #include "form_include.h" #include "form_index.h" @@ -55,6 +56,7 @@ #include "FormERT.h" #include "FormExternal.h" #include "FormFloat.h" +#include "FormForks.h" #include "FormGraphics.h" #include "FormInclude.h" #include "FormIndex.h" @@ -93,6 +95,7 @@ Dialogs::Dialogs(LyXView * lv) add(new GUIError(*lv, *this)); add(new GUIERT(*lv, *this)); add(new GUIExternal(*lv, *this)); + add(new GUIForks(*lv, *this)); add(new GUIGraphics(*lv, *this)); add(new GUIInclude(*lv, *this)); add(new GUIIndex(*lv, *this)); diff --git a/src/frontends/xforms/FormForks.C b/src/frontends/xforms/FormForks.C new file mode 100644 index 0000000000..54d2727cf5 --- /dev/null +++ b/src/frontends/xforms/FormForks.C @@ -0,0 +1,419 @@ +/** + * \file FormForks.C + * Copyright 2001 the LyX Team + * Read the file COPYING + * + * \author Angus Leeming + * \date 2001-10-22 + */ + +#include + +#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 > 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 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::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::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 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::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::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 const kill_vec = + getVectorFromBrowser(dialog_->browser_kill); + + vector::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 const child_vec = + getVectorFromBrowser(dialog_->browser_children); + + vector::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 const getPIDvector(FL_OBJECT * ob) +{ + vector vec = getVectorFromBrowser(ob); + if (vec.empty()) + return vec; + + for (vector::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 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::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 const kill_vec = + getVectorFromBrowser(dialog_->browser_kill); + + vector::const_iterator it = + find(kill_vec.begin(), kill_vec.end(), pid_str); + + if (it == kill_vec.end()) { + fl_add_browser_line(dialog_->browser_kill, pid_str.c_str()); + int const n = fl_get_browser_maxline(dialog_->browser_kill); + fl_select_browser_line(dialog_->browser_kill, n); + } + + setEnabled(dialog_->browser_kill, true); + setEnabled(dialog_->button_remove, true); + setEnabled(dialog_->button_add, false); + + return ButtonPolicy::SMI_VALID; +} + + +ButtonPolicy::SMInput FormForks::input_button_remove() +{ + // Pressed the "Remove" button. + + // 1. Remove the selected item in the browser of processes to kill. + + // 2. Activate the "add" button and "all" buttons. + + // 3. Deactivate the "remove" button. + + int const sel = fl_get_browser(dialog_->browser_kill); + fl_delete_browser_line(dialog_->browser_kill, sel); + + setEnabled(dialog_->button_add, true); + setEnabled(dialog_->button_all, true); + setEnabled(dialog_->button_remove, false); + + return ButtonPolicy::SMI_VALID; +} + + +string const FormForks::getVerboseTooltip(FL_OBJECT const * ob) const +{ + string str; + + if (ob == dialog_->browser_children) { + str = _("All currently running child processes forked by LyX."); + } else if (ob == dialog_->browser_kill) { + str = _("A list of all child processes to kill."); + } else if (ob == dialog_->button_all) { + str = _("Add all processes to the list of processes to kill."); + } else if (ob == dialog_->button_add) { + str = _("Add the currently selected child process to the list of processes to kill."); + } else if (ob == dialog_->button_remove) { + str = _("Remove the currently selected item from the list of processes to kill."); + } + + return str; +} + + diff --git a/src/frontends/xforms/FormForks.h b/src/frontends/xforms/FormForks.h new file mode 100644 index 0000000000..ebfb25b7b2 --- /dev/null +++ b/src/frontends/xforms/FormForks.h @@ -0,0 +1,51 @@ +// -*- C++ -*- +/** + * \file FormForks.h + * Copyright 2001 the LyX Team + * Read the file COPYING + * + * \author Angus Leeming + */ + +#ifndef FORMFORKS_H +#define FORMFORKS_H + +#ifdef __GNUG__ +#pragma interface +#endif + +#include "FormBase.h" + +struct FD_form_forks; +class ControlForks; + +class FormForks : public FormCB > { +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 diff --git a/src/frontends/xforms/FormPreferences.C b/src/frontends/xforms/FormPreferences.C index aba6c9ee75..6f0cbb43c4 100644 --- a/src/frontends/xforms/FormPreferences.C +++ b/src/frontends/xforms/FormPreferences.C @@ -48,6 +48,8 @@ #include "support/filetools.h" #include "support/LAssert.h" +#include "graphics/GraphicsCache.h" + using std::endl; using std::pair; using std::make_pair; @@ -1836,6 +1838,7 @@ void FormPreferences::LnFmisc::apply() const lyxrc.wheel_jump = static_cast (fl_get_counter_value(dialog_->counter_wm_jump)); + string const old_value = lyxrc.display_graphics; if (fl_get_button(dialog_->radio_display_monochrome)) { lyxrc.display_graphics = "mono"; } else if (fl_get_button(dialog_->radio_display_grayscale)) { @@ -1845,6 +1848,10 @@ void FormPreferences::LnFmisc::apply() const } else { lyxrc.display_graphics = "no"; } + if (old_value != lyxrc.display_graphics) { + grfx::GCache & gc = grfx::GCache::get(); + gc.changeDisplay(); + } } diff --git a/src/frontends/xforms/Makefile.am b/src/frontends/xforms/Makefile.am index fe12572547..8c5d13f7e3 100644 --- a/src/frontends/xforms/Makefile.am +++ b/src/frontends/xforms/Makefile.am @@ -76,6 +76,10 @@ libxforms_la_SOURCES = \ FormFloat.h \ form_float.C \ form_float.h \ + FormForks.C \ + FormForks.h \ + form_forks.C \ + form_forks.h \ FormGraphics.C \ FormGraphics.h \ form_graphics.C \ @@ -178,10 +182,10 @@ libxforms_la_SOURCES = \ form_url.h \ FormVCLog.C \ FormVCLog.h \ - input_validators.h \ input_validators.C \ - MathsSymbols.h \ + input_validators.h \ MathsSymbols.C \ + MathsSymbols.h \ Menubar_pimpl.C \ Menubar_pimpl.h \ RadioButtonGroup.C \ diff --git a/src/frontends/xforms/form_forks.C b/src/frontends/xforms/form_forks.C new file mode 100644 index 0000000000..d7768c9b34 --- /dev/null +++ b/src/frontends/xforms/form_forks.C @@ -0,0 +1,80 @@ +// File modified by fdfix.sh for use by lyx (with xforms >= 0.88) and gettext +#include +#include "xforms_helpers.h" +#include "gettext.h" + +/* Form definition file generated with fdesign. */ + +#include FORMS_H_LOCATION +#include +#include "form_forks.h" +#include "FormForks.h" + +FD_form_forks::~FD_form_forks() +{ + if ( form->visible ) fl_hide_form( form ); + fl_free_form( form ); +} + + +FD_form_forks * FormForks::build_forks() +{ + FL_OBJECT *obj; + FD_form_forks *fdui = new FD_form_forks; + + fdui->form = fl_bgn_form(FL_NO_BOX, 650, 390); + fdui->form->u_vdata = this; + obj = fl_add_box(FL_UP_BOX, 0, 0, 650, 390, ""); + { + char const * const dummy = N_("Forked child processes|#F"); + fdui->browser_children = obj = fl_add_browser(FL_HOLD_BROWSER, 20, 30, 400, 290, idex(_(dummy))); + fl_set_button_shortcut(obj, scex(_(dummy)), 1); + } + fl_set_object_lsize(obj, FL_NORMAL_SIZE); + fl_set_object_lalign(obj, FL_ALIGN_TOP); + fl_set_object_callback(obj, C_FormBaseInputCB, 0); + { + char const * const dummy = N_("Kill processes|#K"); + fdui->browser_kill = obj = fl_add_browser(FL_HOLD_BROWSER, 510, 30, 125, 290, idex(_(dummy))); + fl_set_button_shortcut(obj, scex(_(dummy)), 1); + } + fl_set_object_lsize(obj, FL_NORMAL_SIZE); + fl_set_object_lalign(obj, FL_ALIGN_TOP); + fl_set_object_callback(obj, C_FormBaseInputCB, 0); + fdui->button_all = obj = fl_add_button(FL_NORMAL_BUTTON, 432, 30, 65, 30, _("All ->")); + fl_set_object_lsize(obj, FL_NORMAL_SIZE); + fl_set_object_callback(obj, C_FormBaseInputCB, 0); + fdui->button_add = obj = fl_add_button(FL_NORMAL_BUTTON, 450, 70, 30, 30, _("@->")); + fl_set_object_lsize(obj, FL_NORMAL_SIZE); + fl_set_object_callback(obj, C_FormBaseInputCB, 0); + fdui->button_remove = obj = fl_add_button(FL_NORMAL_BUTTON, 450, 110, 30, 30, _("@4->")); + fl_set_object_lsize(obj, FL_NORMAL_SIZE); + fl_set_object_callback(obj, C_FormBaseInputCB, 0); + fdui->button_ok = obj = fl_add_button(FL_RETURN_BUTTON, 355, 350, 90, 30, _("OK")); + fl_set_object_lsize(obj, FL_NORMAL_SIZE); + fl_set_object_gravity(obj, FL_SouthEast, FL_SouthEast); + fl_set_object_callback(obj, C_FormBaseOKCB, 0); + { + char const * const dummy = N_("Apply|#A"); + fdui->button_apply = obj = fl_add_button(FL_NORMAL_BUTTON, 450, 350, 90, 30, idex(_(dummy))); + fl_set_button_shortcut(obj, scex(_(dummy)), 1); + } + fl_set_object_lsize(obj, FL_NORMAL_SIZE); + fl_set_object_gravity(obj, FL_SouthEast, FL_SouthEast); + fl_set_object_callback(obj, C_FormBaseApplyCB, 0); + { + char const * const dummy = N_("Close|^["); + fdui->button_close = obj = fl_add_button(FL_NORMAL_BUTTON, 545, 350, 90, 30, idex(_(dummy))); + fl_set_button_shortcut(obj, scex(_(dummy)), 1); + } + fl_set_object_lsize(obj, FL_NORMAL_SIZE); + fl_set_object_gravity(obj, FL_SouthEast, FL_SouthEast); + fl_set_object_callback(obj, C_FormBaseCancelCB, 0); + fl_end_form(); + + fdui->form->fdui = fdui; + + return fdui; +} +/*---------------------------------------*/ + diff --git a/src/frontends/xforms/form_forks.h b/src/frontends/xforms/form_forks.h new file mode 100644 index 0000000000..7ca4800073 --- /dev/null +++ b/src/frontends/xforms/form_forks.h @@ -0,0 +1,29 @@ +// File modified by fdfix.sh for use by lyx (with xforms >= 0.88) and gettext +/** Header file generated with fdesign **/ + +#ifndef FD_form_forks_h_ +#define FD_form_forks_h_ + +/** Callbacks, globals and object handlers **/ +extern "C" void C_FormBaseInputCB(FL_OBJECT *, long); +extern "C" void C_FormBaseOKCB(FL_OBJECT *, long); +extern "C" void C_FormBaseApplyCB(FL_OBJECT *, long); +extern "C" void C_FormBaseCancelCB(FL_OBJECT *, long); + + +/**** Forms and Objects ****/ +struct FD_form_forks { + ~FD_form_forks(); + + FL_FORM *form; + FL_OBJECT *browser_children; + FL_OBJECT *browser_kill; + FL_OBJECT *button_all; + FL_OBJECT *button_add; + FL_OBJECT *button_remove; + FL_OBJECT *button_ok; + FL_OBJECT *button_apply; + FL_OBJECT *button_close; +}; + +#endif /* FD_form_forks_h_ */ diff --git a/src/frontends/xforms/forms/form_forks.fd b/src/frontends/xforms/forms/form_forks.fd new file mode 100644 index 0000000000..3e8db0d796 --- /dev/null +++ b/src/frontends/xforms/forms/form_forks.fd @@ -0,0 +1,178 @@ +Magic: 13000 + +Internal Form Definition File + (do not change) + +Number of forms: 1 +Unit of measure: FL_COORD_PIXEL + +=============== FORM =============== +Name: form_forks +Width: 650 +Height: 390 +Number of Objects: 9 + +-------------------- +class: FL_BOX +type: UP_BOX +box: 0 0 650 390 +boxtype: FL_UP_BOX +colors: FL_COL1 FL_COL1 +alignment: FL_ALIGN_CENTER +style: FL_NORMAL_STYLE +size: FL_DEFAULT_SIZE +lcol: FL_BLACK +label: +shortcut: +resize: FL_RESIZE_ALL +gravity: FL_NoGravity FL_NoGravity +name: +callback: +argument: + +-------------------- +class: FL_BROWSER +type: HOLD_BROWSER +box: 20 30 400 290 +boxtype: FL_DOWN_BOX +colors: FL_COL1 FL_YELLOW +alignment: FL_ALIGN_TOP +style: FL_NORMAL_STYLE +size: FL_NORMAL_SIZE +lcol: FL_BLACK +label: Forked child processes|#F +shortcut: +resize: FL_RESIZE_ALL +gravity: FL_NoGravity FL_NoGravity +name: browser_children +callback: C_FormBaseInputCB +argument: 0 + +-------------------- +class: FL_BROWSER +type: HOLD_BROWSER +box: 510 30 125 290 +boxtype: FL_DOWN_BOX +colors: FL_COL1 FL_YELLOW +alignment: FL_ALIGN_TOP +style: FL_NORMAL_STYLE +size: FL_NORMAL_SIZE +lcol: FL_BLACK +label: Kill processes|#K +shortcut: +resize: FL_RESIZE_ALL +gravity: FL_NoGravity FL_NoGravity +name: browser_kill +callback: C_FormBaseInputCB +argument: 0 + +-------------------- +class: FL_BUTTON +type: NORMAL_BUTTON +box: 432 30 65 30 +boxtype: FL_UP_BOX +colors: FL_COL1 FL_COL1 +alignment: FL_ALIGN_CENTER +style: FL_NORMAL_STYLE +size: FL_NORMAL_SIZE +lcol: FL_BLACK +label: All -> +shortcut: +resize: FL_RESIZE_ALL +gravity: FL_NoGravity FL_NoGravity +name: button_all +callback: C_FormBaseInputCB +argument: 0 + +-------------------- +class: FL_BUTTON +type: NORMAL_BUTTON +box: 450 70 30 30 +boxtype: FL_UP_BOX +colors: FL_COL1 FL_COL1 +alignment: FL_ALIGN_CENTER +style: FL_NORMAL_STYLE +size: FL_NORMAL_SIZE +lcol: FL_BLACK +label: @-> +shortcut: +resize: FL_RESIZE_ALL +gravity: FL_NoGravity FL_NoGravity +name: button_add +callback: C_FormBaseInputCB +argument: 0 + +-------------------- +class: FL_BUTTON +type: NORMAL_BUTTON +box: 450 110 30 30 +boxtype: FL_UP_BOX +colors: FL_COL1 FL_COL1 +alignment: FL_ALIGN_CENTER +style: FL_NORMAL_STYLE +size: FL_NORMAL_SIZE +lcol: FL_BLACK +label: @4-> +shortcut: +resize: FL_RESIZE_ALL +gravity: FL_NoGravity FL_NoGravity +name: button_remove +callback: C_FormBaseInputCB +argument: 0 + +-------------------- +class: FL_BUTTON +type: RETURN_BUTTON +box: 355 350 90 30 +boxtype: FL_UP_BOX +colors: FL_COL1 FL_COL1 +alignment: FL_ALIGN_CENTER +style: FL_NORMAL_STYLE +size: FL_NORMAL_SIZE +lcol: FL_BLACK +label: OK +shortcut: ^M +resize: FL_RESIZE_ALL +gravity: FL_SouthEast FL_SouthEast +name: button_ok +callback: C_FormBaseOKCB +argument: 0 + +-------------------- +class: FL_BUTTON +type: NORMAL_BUTTON +box: 450 350 90 30 +boxtype: FL_UP_BOX +colors: FL_COL1 FL_COL1 +alignment: FL_ALIGN_CENTER +style: FL_NORMAL_STYLE +size: FL_NORMAL_SIZE +lcol: FL_BLACK +label: Apply|#A +shortcut: +resize: FL_RESIZE_ALL +gravity: FL_SouthEast FL_SouthEast +name: button_apply +callback: C_FormBaseApplyCB +argument: 0 + +-------------------- +class: FL_BUTTON +type: NORMAL_BUTTON +box: 545 350 90 30 +boxtype: FL_UP_BOX +colors: FL_COL1 FL_COL1 +alignment: FL_ALIGN_CENTER +style: FL_NORMAL_STYLE +size: FL_NORMAL_SIZE +lcol: FL_BLACK +label: Close|^[ +shortcut: +resize: FL_RESIZE_ALL +gravity: FL_SouthEast FL_SouthEast +name: button_close +callback: C_FormBaseCancelCB +argument: 0 + +============================== +create_the_forms diff --git a/src/frontends/xforms/forms/makefile b/src/frontends/xforms/forms/makefile index 8651dc5bb9..953ddcec2a 100644 --- a/src/frontends/xforms/forms/makefile +++ b/src/frontends/xforms/forms/makefile @@ -30,6 +30,7 @@ SRCS = form_aboutlyx.fd \ form_external.fd \ form_filedialog.fd \ form_float.fd \ + form_forks.fd \ form_graphics.fd \ form_include.fd \ form_index.fd \ diff --git a/src/graphics/ChangeLog b/src/graphics/ChangeLog index ab5dfdf2e4..6fbf637fab 100644 --- a/src/graphics/ChangeLog +++ b/src/graphics/ChangeLog @@ -1,3 +1,21 @@ +2002-02-15 Angus Leeming + + * 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 * GraphicsCacheItem.C: use unzipFile() from support/filetools diff --git a/src/graphics/GraphicsCache.C b/src/graphics/GraphicsCache.C index fe2de807c9..d6b9b20dc0 100644 --- a/src/graphics/GraphicsCache.C +++ b/src/graphics/GraphicsCache.C @@ -1,12 +1,11 @@ -/* This file is part of - * ================================================= - * - * LyX, The Document Processor - * Copyright 1995 Matthias Ettrich. - * Copyright 1995-2001 The LyX Team. +/* + * \file GraphicsCache.C + * Copyright 2002 the LyX Team + * Read the file COPYING * - * This file Copyright 2000 Baruch Even - * ================================================= */ + * \author Baruch Even + * \author Angus Leeming + */ #include @@ -16,51 +15,174 @@ #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); + } + } + + // 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 GCache::changeDisplay(bool changed_background) +{ + CacheType::iterator it = cache->begin(); + CacheType::iterator end = cache->end(); + for(; it != end; ++it) + it->second->changeDisplay(changed_background); +} + + +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; } - 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; + return cache->end(); } -void -GraphicsCache::removeFile(string const & filename) +GCache::CacheType::const_iterator +GCache::find(InsetGraphics const & inset) const { - // We do not destroy the GraphicsCacheItem since we are here because - // the last copy of it is being erased. - - CacheType::iterator it = cache.find(filename); - if (it != cache.end()) - cache.erase(it); + CacheType::const_iterator it = cache->begin(); + for (; it != cache->end(); ++it) { + if (it->second->referencedBy(inset)) + return it; + } + + return cache->end(); } + +} // namespace grfx diff --git a/src/graphics/GraphicsCache.h b/src/graphics/GraphicsCache.h index 9272e58e49..e6297572fe 100644 --- a/src/graphics/GraphicsCache.h +++ b/src/graphics/GraphicsCache.h @@ -1,13 +1,18 @@ // -*- C++ -*- -/* This file is part of - * ================================================= - * - * LyX, The Document Processor - * Copyright 1995 Matthias Ettrich. - * Copyright 1995-2001 The LyX Team. +/** + * \file GraphicsCache.h + * Copyright 2002 the LyX Team + * Read the file COPYING * - * This file Copyright 2000 Baruch Even - * ================================================= */ + * \author Baruch Even + * \author Angus Leeming + * + * 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 @@ -16,48 +21,80 @@ #pragma interface #endif +#include "GraphicsTypes.h" #include - #include "LString.h" -#include "GraphicsCacheItem.h" #include -#include -class GraphicsCacheItem; +class InsetGraphics; -/** 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 { +namespace grfx { + +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 shared_ptr_item; + /// This is a singleton class. Get the instance. + static GCache & get(); - /// Add a file to the cache. - shared_ptr_item addFile(string const & filename); + /// + ~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); + + /// 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() {} - + /** 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 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 CacheType; + + /// Search the cache by inset. + CacheType::const_iterator find(InsetGraphics const &) const; /// - typedef std::map CacheType; - /// - 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 diff --git a/src/graphics/GraphicsCacheItem.C b/src/graphics/GraphicsCacheItem.C index a6ebdcc1ca..ed69a468e8 100644 --- a/src/graphics/GraphicsCacheItem.C +++ b/src/graphics/GraphicsCacheItem.C @@ -1,13 +1,12 @@ -/* This file is part of - * ================================================= - * - * LyX, The Document Processor - * Copyright 1995 Matthias Ettrich. - * Copyright 1995-2001 The LyX Team. +/* + * \file GraphicsCacheItem.C + * Copyright 2002 the LyX Team + * Read the file COPYING * - * \author Baruch Even + * \author Baruch Even * \author Herbert Voss - * ================================================= */ + * \author Angus Leeming + */ #include @@ -15,90 +14,380 @@ #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 { -/* - * 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) +GCacheItem::GCacheItem(InsetGraphics const & inset, GParams const & params) + : filename_(params.filename), zipped_(false), + remove_loaded_file_(false), status_(WaitingToLoad) { - bool success = convertImage(filename); - if (!success) // Conversion failed miserably (couldn't even start). + 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 + + +void GCacheItem::modify(InsetGraphics const & inset, GParams const & params) +{ + // 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; +} + + +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(); + } +} + + +void GCacheItem::startLoading(InsetGraphics const & inset) +{ + 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(); +} + + +bool GCacheItem::referencedBy(InsetGraphics const & inset) const +{ + // 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; +} + + +string const & GCacheItem::filename() const +{ + return filename_; +} + + +ImagePtr const GCacheItem::image(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 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(); +} + + +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(*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_); -GraphicsCacheItem::~GraphicsCacheItem() -{} - - -GraphicsCacheItem::ImageStatus -GraphicsCacheItem::getImageStatus() const -{ - return imageStatus_; -} - - -void GraphicsCacheItem::setStatus(ImageStatus new_status) -{ - imageStatus_ = new_status; -} - - -LyXImage * -GraphicsCacheItem::getImage() const -{ - return image_.get(); -} - - -void GraphicsCacheItem::imageConverted(bool success) -{ - // Debug output - string text = "succeeded"; - if (!success) - text = "failed"; - lyxerr << "imageConverted, conversion " << text << "." << endl; - - if (! success) { - lyxerr << "(GraphicsCacheItem::imageConverter) " - "Error converting image." << endl; - setStatus(GraphicsCacheItem::ErrorConverting); return; } + cc_.disconnect(); + // Do the actual image loading from file to memory. - loadImage(); + file_to_load_ = file_to_load; + + loadImage(); +} + + +// This function gets called from the callback after the image has been +// converted successfully. +void GCacheItem::loadImage() +{ + setStatus(Loading); + lyxerr[Debug::GRAPHICS] << "Loading image." << endl; + + // Connect a signal to this->imageLoaded and pass this signal to + // GImage::loadImage. + SignalLoadTypePtr on_finish; + on_finish.reset(new SignalLoadType); + cl_ = on_finish->connect(SigC::slot(this, &GCacheItem::imageLoaded)); + + image_ = GImage::newImage(); + image_->load(file_to_load_, on_finish); +} + + +void GCacheItem::imageLoaded(bool success) +{ + string const text = success ? "succeeded" : "failed"; + lyxerr[Debug::GRAPHICS] << "Image loading " << text << "." << endl; + + // Clean up after loading. + if (zipped_) + lyx::unlink(unzipped_filename_); + + if (remove_loaded_file_ && unzipped_filename_ != file_to_load_) + lyx::unlink(file_to_load_); + + cl_.disconnect(); + + if (!success) { + setStatus(ErrorLoading); + return; + } + + setStatus(Loaded); + + // Loop over the list of modified images and create them. + ListType::iterator it = modified_images.begin(); + ListType::iterator end = modified_images.end(); + for (; it != end; ++it) { + (*it)->modify(image_); + } } @@ -106,35 +395,44 @@ namespace { string const findTargetFormat(string const & from) { - typedef ImageLoader::FormatList FormatList; - FormatList formats = ImageLoaderXPM().loadableFormats(); - lyx::Assert(formats.size() > 0); // There must be a format to load from. + typedef GImage::FormatList FormatList; + FormatList const & formats = GImage::loadableFormats(); - FormatList::const_iterator iter = formats.begin(); - FormatList::const_iterator end = formats.end(); + // There must be a format to load from. + lyx::Assert(!formats.empty()); - for (; iter != end; ++iter) { - if (converters.isReachable(from, *iter)) + grfx::GConverter const & graphics_converter = grfx::GConverter::get(); + + FormatList::const_iterator it = formats.begin(); + FormatList::const_iterator end = formats.end(); + for (; it != end; ++it) { + if (graphics_converter.isReachable(from, *it)) break; } - if (iter == end) { - // We do not know how to convert the image to something loadable. - lyxerr << "ERROR: Do not know how to convert image." << endl; + + if (it == end) return string(); - } - return (*iter); + + return *it; } } // anon namespace -bool GraphicsCacheItem::convertImage(string const & filename) +void GCacheItem::convertToDisplayFormat() { - setStatus(GraphicsCacheItem::Converting); -#warning shadowing class variable (Lgb) - // Is this needed at all? - string filename_ = string(filename); - lyxerr << "try to convert image file: " << filename_ << endl; + setStatus(Converting); + string filename = filename_; // Make a local copy in case we unzip it + string const displayed_filename = MakeDisplayPath(filename_); + + // First, check that the file exists! + if (!IsFileReadable(filename)) { + Alert::alert(_("File ") + displayed_filename, + _("\nisn't readable or doesn't exist!")); + setStatus(ErrorNoFile); + return; + } + // maybe that other zip extensions also be useful, especially the // ones that may be declared in texmf/tex/latex/config/graphics.cfg. // for example: @@ -146,61 +444,213 @@ bool GraphicsCacheItem::convertImage(string const & filename) \DeclareGraphicsRule{.eps.gz}{eps}{.eps.bb}{}}}% -----------snip-------------*/ - lyxerr << "GetExtension: " << GetExtension(filename_) << endl; - bool const zipped = zippedFile(filename_); - if (zipped) - filename_ = unzipFile(filename_); - string const from = getExtFromContents(filename_); // get the type - lyxerr << "GetExtFromContents: " << from << endl; - string const to = findTargetFormat(from); - lyxerr << "from: " << from << " -> " << to << endl; - if (to.empty()) - return false; - // manage zipped files. unzip them first into the tempdir + lyxerr[Debug::GRAPHICS] + << "Attempting to convert image file: " << displayed_filename + << "\nwith recognised extension: " << GetExtension(filename) + << "." << endl; + + zipped_ = zippedFile(filename); + if (zipped_) { + filename = unzipFile(filename); + unzipped_filename_ = filename; + } + + string const from = getExtFromContents(filename); + string const to = grfx::findTargetFormat(from); + + lyxerr[Debug::GRAPHICS] + << "The file contains " << from << " format data." << endl; + + if (to.empty()) { + Alert::alert(_("Unable to convert file ") + + displayed_filename + + _(" to a loadable format.")); + setStatus(ErrorConverting); + return; + } + if (from == to) { // No conversion needed! - // Saves more than just time: prevents the deletion of - // the "to" file after loading when it's the same as the "from"! - tempfile = filename_; - loadImage(); - return true; + lyxerr[Debug::GRAPHICS] << "No conversion needed!" << endl; + file_to_load_ = filename; + loadImage(); + return; } + + lyxerr[Debug::GRAPHICS] << "Converting it to " << to << " format." << endl; + // Take only the filename part of the file, without path or extension. - string temp = OnlyFilename(filename_); - temp = ChangeExtension(filename_, string()); + string const temp = ChangeExtension(OnlyFilename(filename), string()); - // Add some stuff to have it a unique temp file. - // This tempfile is deleted in loadImage after it is loaded to memory. - tempfile = lyx::tempName(string(), temp); + // Add some stuff to create a uniquely named temporary file. + // This file is deleted in loadImage after it is loaded into memory. + string const to_file_base = lyx::tempName(string(), temp); + remove_loaded_file_ = true; + // Remove the temp file, we only want the name... - lyx::unlink(tempfile); - bool result = converters.convert(0, filename_, tempfile, from, to); - tempfile.append(".xpm"); - // For now we are synchronous - imageConverted(result); - // Cleanup after the conversion. - lyx::unlink(tempfile); - if (zipped) - lyx::unlink(filename_); - tempfile.erase(); - return true; + lyx::unlink(to_file_base); + + // Connect a signal to this->imageConverted and pass this signal to + // the graphics converter so that we can load the modified file + // on completion of the conversion process. + SignalConvertTypePtr on_finish; + on_finish.reset(new SignalConvertType); + cc_ = on_finish->connect(SigC::slot(this, &GCacheItem::imageConverted)); + + GConverter & graphics_converter = GConverter::get(); + graphics_converter.convert(filename, to_file_base, from, to, on_finish); } -// This function gets called from the callback after the image has been -// converted successfully. -void -GraphicsCacheItem::loadImage() +ModifiedItem::ModifiedItem(InsetGraphics const & new_inset, + GParams const & new_params, + ImagePtr const & new_image) + : status_(ScalingEtc) { - lyxerr << "Loading XPM Image... "; + p_.reset(new GParams(new_params)); + insets.push_back(&new_inset); + modify(new_image); +} - ImageLoaderXPM imageLoader; - if (imageLoader.loadImage(tempfile) == ImageLoader::OK) { - lyxerr << "Success." << endl; - image_.reset(imageLoader.getImage()); - setStatus(GraphicsCacheItem::Loaded); + +void ModifiedItem::add(InsetGraphics const & inset) +{ + insets.push_back(&inset); + insets.sort(); +} + + +void ModifiedItem::remove(InsetGraphics const & inset) +{ + 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; + } + + 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(*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 diff --git a/src/graphics/GraphicsCacheItem.h b/src/graphics/GraphicsCacheItem.h index 6264f3b784..9d1e0e12d5 100644 --- a/src/graphics/GraphicsCacheItem.h +++ b/src/graphics/GraphicsCacheItem.h @@ -1,13 +1,30 @@ // -*- C++ -*- -/* This file is part of - * ================================================= - * - * LyX, The Document Processor - * Copyright 1995 Matthias Ettrich. - * Copyright 1995-2001 The LyX Team. +/* + * \file GraphicsCacheItem.h + * Copyright 2002 the LyX Team + * Read the file COPYING * - * This file Copyright 2000 Baruch Even - * ================================================= */ + * \author Baruch Even + * \author Angus Leeming + * + * 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 @@ -16,78 +33,220 @@ #pragma interface #endif -#include XPM_H_LOCATION +#include "GraphicsTypes.h" +#include #include "LString.h" - #include #include - #include -class LyXImage; +class InsetGraphics; -/// A GraphicsCache item holder. -class GraphicsCacheItem : boost::noncopyable { +namespace grfx { + +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; - /// - enum ImageStatus { - /// - Loading = 1, - /// - Converting, - /// - ErrorConverting, - /// - ErrorReading, - /// - UnknownError, - /// - Loaded - }; - - /// Is the pixmap ready for display? - ImageStatus getImageStatus() const; + /// the GCacheItem contains data of this type. + typedef boost::shared_ptr ModifiedItemPtr; - /** Get a notification when the image conversion is done. - used by an internal callback mechanism. - */ - void imageConverted(bool success); + /// + 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 this image file referenced by this inset? + bool referencedBy(InsetGraphics const &) const; + + /** 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 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 SignalLoadType; + /// + typedef boost::shared_ptr 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 SignalConvertType; + /// + typedef boost::shared_ptr SignalConvertTypePtr; + + /// The connection of the signal passed to GConverter::convert. + SigC::Connection cc_; + + /// The list of all modified images. + typedef std::list 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 changeDisplay(); + + /// + typedef std::list 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 p_; +}; + +} // namespace grfx + +#endif // GRAPHICSCACHEITEM_H diff --git a/src/graphics/GraphicsConverter.C b/src/graphics/GraphicsConverter.C new file mode 100644 index 0000000000..ea028a7c54 --- /dev/null +++ b/src/graphics/GraphicsConverter.C @@ -0,0 +1,293 @@ +/* + * \file GraphicsConverter.C + * Copyright 2002 the LyX Team + * Read the file COPYING + * + * \author Angus Leeming + */ + +#include + +#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 + +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 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::iterator begin = all_processes_.begin(); + std::list::iterator end = all_processes_.end(); + std::list::iterator it = + std::find_if(begin, end, Find_Ptr(process)); + + if (it == end) + return; + + all_processes_.erase(it); +} + + +bool GConverter::build_script(string const & from_file, + string const & to_file_base, + string const & from_format, + string const & to_format, + ostringstream & script) const +{ + typedef Converters::EdgePath EdgePath; + + string const to_file = ChangeExtension(to_file_base, + formats.extension(to_format)); + + if (from_format == to_format) { + script << move_file(QuoteName(from_file), QuoteName(to_file)); + return true; + } + + EdgePath edgepath = converters.getPath(from_format, to_format); + + if (edgepath.empty()) { + Alert::alert(_("Cannot convert file"), + _("No information for converting from ") + + formats.prettyName(from_format) + _(" to ") + + formats.prettyName(to_format)); + return false; + } + + // Create a temporary base file-name for all intermediate steps. + // Remember to remove the temp file because we only want the name... + static int counter = 0; + string const tmp = "gconvert" + tostr(counter++); + string const to_base = lyx::tempName(string(), tmp); + lyx::unlink(to_base); + + string outfile = from_file; + + // The conversion commands may contain these tokens that need to be + // changed to infile, infile_base, outfile respectively. + string const token_from("$$i"); + string const token_base("$$b"); + string const token_to("$$o"); + + EdgePath::const_iterator it = edgepath.begin(); + EdgePath::const_iterator end = edgepath.end(); + for (; it != end; ++it) { + Converter const & conv = converters.get(*it); + + // Build the conversion command + string const infile = outfile; + string const infile_base = ChangeExtension(infile, string()); + outfile = ChangeExtension(to_base, conv.To->extension()); + + // Store these names in the shell script + script << "infile=" << QuoteName(infile) << '\n' + << "infile_base=" << QuoteName(infile_base) << '\n' + << "outfile=" << QuoteName(outfile) << '\n'; + + string command = conv.command; + command = subst(command, token_from, "${infile}"); + command = subst(command, token_base, "${infile_base}"); + command = subst(command, token_to, "${outfile}"); + + // Store in the shell script + script << "\n" << command << "\n\n"; + + // Test that this was successful. If not, remove + // ${outfile} and exit the shell script + script << "if [ $? -ne 0 ]; then\n" + << "\t'rm' -f ${outfile}\n" + << "\texit 1\n" + << "fi\n\n"; + + // Test that the outfile exists. + // ImageMagick's convert will often create ${outfile}.0, + // ${outfile}.1. + // If this occurs, move ${outfile}.0 to ${outfile} + // and delete ${outfile}.? + script << "if [ ! -f ${outfile} ]; then\n" + << "\tif [ -f ${outfile}.0 ]; then\n" + << "\t\t'mv' -f ${outfile}.0 ${outfile}\n" + << "\t\t'rm' -f ${outfile}.?\n" + << "\telse\n" + << "\t\texit 1\n" + << "\tfi\n" + << "fi\n\n"; + + // Delete the infile, if it isn't the original, from_file. + if (infile != from_file) { + script << "'rm' -f ${infile}\n\n"; + } + } + + // Move the final outfile to to_file + script << move_file("${outfile}", QuoteName(to_file)); + + return true; +} + + +ConvProcess::ConvProcess(string const & script_file, + string const & script_command, + string const & to_file, SignalTypePtr on_finish) + : script_file_(script_file), to_file_(to_file), on_finish_(on_finish) +{ + Forkedcall::SignalTypePtr convert_ptr; + convert_ptr.reset(new Forkedcall::SignalType); + + convert_ptr->connect(SigC::slot(this, &ConvProcess::converted)); + + Forkedcall call; + int retval = call.startscript(script_command, convert_ptr); + if (retval > 0) { + // Unable to even start the script, so clean-up the mess! + converted(string(), 0, 1); + } +} + + +void ConvProcess::converted(string /* cmd */, pid_t /* pid */, int retval) +{ + // Clean-up behind ourselves + lyx::unlink(script_file_); + + if (retval > 0) { + lyx::unlink(to_file_); + to_file_.erase(); + } + + if (on_finish_.get()) { + on_finish_->emit(to_file_); + } + + grfx::GConverter::get().erase(this); +} + + +} // namespace grfx diff --git a/src/graphics/GraphicsConverter.h b/src/graphics/GraphicsConverter.h new file mode 100644 index 0000000000..536a84013d --- /dev/null +++ b/src/graphics/GraphicsConverter.h @@ -0,0 +1,121 @@ +// -*- C++ -*- +/* + * \file GraphicsConverter.h + * Copyright 2002 the LyX Team + * Read the file COPYING + * + * \author Angus Leeming + * + * 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 +#include +#include +#include + +#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 SignalType; + /// + typedef boost::shared_ptr 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 ConvProcessPtr; + /// + std::list all_processes_; +}; + + +/// Each ConvProcess represents a single conversion process. +struct ConvProcess : public SigC::Object +{ + /// + typedef GConverter::SignalTypePtr SignalTypePtr; + + /** Each ConvProcess represents a single conversion process. + * It is passed : + * 1. The name of the script_file, which it deletes once the + * conversion is comlpeted; + * 2. The script command itself, which it passes on to the forked + * call process; + * 3. The name of the output file, which it returns to the calling + * process on successfull completion, by emitting + * 4. The signal on_finish. + */ + ConvProcess(string const & script_file, string const & script_command, + string const & to_file, SignalTypePtr on_finish); + + /** This method is connected to a signal passed to the forked call + * class, passing control back here when the conversion is completed. + * Cleans-up the temporary files, emits the on_finish signal and + * removes the ConvProcess from the list of all processes. + */ + void converted(string cmd, pid_t pid, int retval); + + /// + string script_file_; + /// + string to_file_; + /// + SignalTypePtr on_finish_; +}; + +} // namespace grfx + +#endif // GRAPHICSCONVERTER_H diff --git a/src/graphics/GraphicsImage.C b/src/graphics/GraphicsImage.C new file mode 100644 index 0000000000..6e7c8300aa --- /dev/null +++ b/src/graphics/GraphicsImage.C @@ -0,0 +1,59 @@ +/* + * \file GraphicsImage.C + * Copyright 2002 the LyX Team + * Read the file COPYING + * + * \author Baruch Even + * \author Angus Leeming + */ + +#include + +#ifdef __GNUG__ +#pragma implementation +#endif + +#include "GraphicsImage.h" +#include "GraphicsParams.h" +#include + +namespace grfx { + +// This will be connected to a function that will return whichever +// whichever derived class we desire. +SigC::Signal0 GImage::newImage; + +/// Return the list of loadable formats. +SigC::Signal0 GImage::loadableFormats; + +std::pair +GImage::getScaledDimensions(GParams const & params) const +{ + if (params.scale == 0 && params.width == 0 && params.height == 0) + // No scaling + return std::make_pair(getWidth(), getHeight()); + + unsigned int width = 0; + unsigned int height = 0; + if (params.scale != 0) { + width = getWidth() * params.scale / 100.0; + height = getHeight() * params.scale / 100.0; + } else { + width = params.width; + height = params.height; + + if (width == 0) { + width = height * getWidth() / getHeight(); + } else if (height == 0) { + height = width * getHeight() / getWidth(); + } + } + + if (width == 0 || height == 0) + // Something is wrong! + return std::make_pair(getWidth(), getHeight()); + + return std::make_pair(width, height); +} +} // namespace grfx + diff --git a/src/graphics/GraphicsImage.h b/src/graphics/GraphicsImage.h new file mode 100644 index 0000000000..50eabd25e2 --- /dev/null +++ b/src/graphics/GraphicsImage.h @@ -0,0 +1,104 @@ +// -*- C++ -*- +/** + * \file GraphicsImage.h + * Copyright 2002 the LyX Team + * Read the file COPYING + * + * \author Baruch Even + * \author Angus Leeming + * + * 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 +#include +#include +#include +#include + +#ifdef __GNUG__ +#pragma interface +#endif + +namespace grfx { + +class GParams; + +class GImage +{ +public: + /// A list of supported formats. + typedef std::vector FormatList; + /** This will be connected to a function that will return whichever + * derived class we desire. + */ + static SigC::Signal0 newImage; + + /// Return the list of loadable formats. + static SigC::Signal0 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 SignalType; + /// + typedef boost::shared_ptr 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 + getScaledDimensions(GParams const & params) const; +}; + +} // namespace grfx + +#endif // GRAPHICSIMAGE_H diff --git a/src/graphics/GraphicsImageXPM.C b/src/graphics/GraphicsImageXPM.C new file mode 100644 index 0000000000..731f1e2507 --- /dev/null +++ b/src/graphics/GraphicsImageXPM.C @@ -0,0 +1,660 @@ +/* + * \file GraphicsImageXPM.C + * Copyright 2002 the LyX Team + * Read the file COPYING + * + * \author Baruch Even + * \author Angus Leeming + */ + +#include + +#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 // std::setfill, etc +#include // cos, sin +#include // 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(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 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(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(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(malloc(strlen(in) + 1)); + return strcpy(out, in); +} + + +bool contains_color_none(XpmImage const & image) +{ + for (int i = 0; i < image.ncolors; ++i) { + char const * const color = image.colorTable[i].c_color; + if (color && lowercase(color) == "none") + return true; + } + return false; +} + + +string const unique_color_string(XpmImage const & image) +{ + string id; + for (int i = 0; i < image.cpp; ++i) { + id.push_back('A'); + } + + for(;;) { + bool found_it = false; + for (int i = 0; i < image.ncolors; ++i) { + string const c_id = image.colorTable[i].string; + if (c_id == id) { + found_it = true; + break; + } + } + + if (!found_it) + return id; + + // A base 57 counter! + // eg AAAz+1 = AABA, AABz+1 = AACA, AAzz+1 = ABAA + int current_index = int(id.size() - 1); + bool continue_loop = true; + while(continue_loop && current_index >= 0) { + continue_loop = false; + + if (id[current_index] == 'z') { + id[current_index] = 'A'; + current_index -= 1; + continue_loop = true; + } else { + id[current_index] += 1; + } + } + if (current_index < 0) + // Unable to find a unique string + return string(); + } +} + +} // namespace anon diff --git a/src/graphics/GraphicsImageXPM.h b/src/graphics/GraphicsImageXPM.h new file mode 100644 index 0000000000..c58560f4cb --- /dev/null +++ b/src/graphics/GraphicsImageXPM.h @@ -0,0 +1,156 @@ +// -*- C++ -*- +/** + * \file GraphicsImageXPM.h + * Copyright 2002 the LyX Team + * Read the file COPYING + * + * \author Baruch Even + * \author Angus Leeming + * + * 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 data_; + lyx::shared_c_ptr colorTable_; + + unsigned int color_none_id() const; + }; + + Data image_; + + /// The pixmap itself. + Pixmap pixmap_; + + /// Is the pixmap initialized? + enum PixmapStatus { + /// + PIXMAP_UNINITIALISED, + /// + PIXMAP_FAILED, + /// + PIXMAP_SUCCESS + }; + + PixmapStatus pixmap_status_; +}; + +} // namespace grfx + +#endif // GRAPHICSIMAGEXPM_H diff --git a/src/graphics/GraphicsParams.C b/src/graphics/GraphicsParams.C new file mode 100644 index 0000000000..f289b305a1 --- /dev/null +++ b/src/graphics/GraphicsParams.C @@ -0,0 +1,171 @@ +/* + * \file GraphicsParams.C + * Copyright 2002 the LyX Team + * Read the file COPYING + * + * \author Angus Leeming + */ + +#include + +#ifdef __GNUG__ +#pragma implementation +#endif + +#include "GraphicsParams.h" +#include "insets/insetgraphicsParams.h" +#include "lyxrc.h" +#include "support/lstrings.h" + +namespace grfx { + +GParams::GParams(InsetGraphicsParams const & iparams) + : filename(iparams.filename), + width(0), + height(0), + scale(0), + angle(0) +{ + if (iparams.clip) + bb = iparams.bb; + + if (iparams.rotate) + angle = int(iparams.rotateAngle); + + if (iparams.display == InsetGraphicsParams::DEFAULT) { + + if (lyxrc.display_graphics == "mono") + display = MONOCHROME; + else if (lyxrc.display_graphics == "gray") + display = GRAYSCALE; + else if (lyxrc.display_graphics == "color") + display = COLOR; + else + display = NONE; + + } else if (iparams.display == InsetGraphicsParams::NONE) { + display = NONE; + + } else if (iparams.display == InsetGraphicsParams::MONOCHROME) { + display = MONOCHROME; + + } else if (iparams.display == InsetGraphicsParams::GRAYSCALE) { + display = GRAYSCALE; + + } else if (iparams.display == InsetGraphicsParams::COLOR) { + display = COLOR; + } + + // Override the above if we're not using a gui + if (!lyxrc.use_gui) { + display = NONE; + } + + if (iparams.lyxsize_type == InsetGraphicsParams::SCALE) { + scale = iparams.lyxscale; + + } else if (iparams.lyxsize_type == InsetGraphicsParams::WH) { + if (!iparams.lyxwidth.zero()) + width = iparams.lyxwidth.inPixels(1, 1); + if (!iparams.lyxheight.zero()) + height = iparams.lyxheight.inPixels(1, 1); + + // inPixels returns a value scaled by lyxrc.zoom. + // We want, therefore, to undo this. + double const scaling_factor = 100.0 / double(lyxrc.zoom); + width = int(scaling_factor * width); + height = int(scaling_factor * height); + } +} + + +bool operator==(GParams const & a, GParams const & b) +{ + return (a.filename == b.filename && + a.display == b.display && + a.bb == b.bb && + a.width == b.width && + a.height == b.height && + a.scale == b.scale && + a.angle == b.angle); +} + + +bool operator!=(GParams const & a, GParams const & b) +{ + return !(a == b); +} + + +BoundingBox::BoundingBox() + : xl(0), yb(0), xr(0), yt(0) +{} + + +BoundingBox::BoundingBox(string const & bb) +{ + if (bb.empty()) + return; + + string tmp1; + string tmp2 = split(bb, tmp1, ' '); + if (!isValidLength(tmp1)) + return; + + LyXLength const length_xl(tmp1); + + tmp2 = split(tmp2, tmp1, ' '); + if (!isValidLength(tmp1)) + return; + + LyXLength const length_yb(tmp1); + + tmp2 = split(tmp2, tmp1, ' '); + if (!isValidLength(tmp1) || !isValidLength(tmp2)) + return; + + LyXLength const length_xr(tmp1); + LyXLength const length_yt(tmp2); + + // inPixels returns the length in inches, scaled by + // lyxrc.dpi and lyxrc.zoom. + // We want, therefore, to undo all this lyxrc nonsense because we + // want the bounding box in Postscript pixels. + // Note further that there are 72 Postscript pixels per inch. + double const scaling_factor = 7200.0 / (lyxrc.dpi * lyxrc.zoom); + xl = int(scaling_factor * length_xl.inPixels(1, 1)); + yb = int(scaling_factor * length_yb.inPixels(1, 1)); + xr = int(scaling_factor * length_xr.inPixels(1, 1)); + yt = int(scaling_factor * length_yt.inPixels(1, 1)); + + if (xr <= xl || yt <= yb) { + xl = 0; + yb = 0; + xr = 0; + yt = 0; + return; + } +} + + +bool BoundingBox::empty() const +{ + return (!xl && !yb && !xr && !yt); +} + + +bool operator==(BoundingBox const & a, BoundingBox const & b) +{ + return (a.xl == b.xl && + a.yb == b.yb && + a.xr == b.xr && + a.yt == b.yt); +} + + +bool operator!=(BoundingBox const & a, BoundingBox const & b) +{ + return !(a == b); +} + +} // namespace grfx diff --git a/src/graphics/GraphicsParams.h b/src/graphics/GraphicsParams.h new file mode 100644 index 0000000000..d8a02610d6 --- /dev/null +++ b/src/graphics/GraphicsParams.h @@ -0,0 +1,98 @@ +// -*- C++ -*- +/** + * \file GraphicsParams.h + * Copyright 2002 the LyX Team + * Read the file COPYING + * + * \author Angus Leeming + * + * Used internally by the GraphicsCache. + * Only a subset of InsetGraphicsParams is needed for display purposes. + * The GraphicsParams c-tor also interrogates lyxrc to ascertain whether + * to display or not. + */ + +#ifndef GRAPHICSPARAMS_H +#define GRAPHICSPARAMS_H + +#ifdef __GNUG__ +#pragma interface +#endif + +#include "LString.h" +#include "lyxlength.h" + +class InsetGraphicsParams; + +namespace grfx { + +/** Parse a string of the form "200pt 500pt 300mm 5in" into a + * usable bounding box. + */ +struct BoundingBox { + /// + BoundingBox(); + /// + BoundingBox(string const &); + + /// 0 0 0 0 is empty! + bool empty() const; + /// + int xl; + int yb; + int xr; + int yt; +}; + +/// +bool operator==(BoundingBox const &, BoundingBox const &); +/// +bool operator!=(BoundingBox const &, BoundingBox const &); + +struct GParams +{ + /// + GParams(InsetGraphicsParams const &); + + /// How is the image to be displayed on the LyX screen? + enum DisplayType { + /// + COLOR, + /// + GRAYSCALE, + /// + MONOCHROME, + /// We aren't going to display it at all! + NONE + }; + + /// + DisplayType display; + + /// The image filename. + string filename; + + /// + BoundingBox bb; + + /** The size of the view inside lyx in pixels or the scaling of the + * image. + */ + unsigned int width; + /// + unsigned int height; + /// + unsigned int scale; + + /// Rotation angle. + int angle; +}; + +/// +bool operator==(GParams const &, GParams const &); +/// +bool operator!=(GParams const &, GParams const &); + +} // namespace grfx + +#endif // GRAPHICSPARAMS_H diff --git a/src/graphics/GraphicsTypes.h b/src/graphics/GraphicsTypes.h new file mode 100644 index 0000000000..81c2503593 --- /dev/null +++ b/src/graphics/GraphicsTypes.h @@ -0,0 +1,57 @@ +// -*- C++ -*- +/** + * \file GraphicsTypes.h + * Copyright 2002 the LyX Team + * Read the file COPYING + * + * \author Angus Leeming + * + * 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 + +#ifdef __GNUG__ +#pragma interface +#endif + +namespace grfx { + + /// + class GImage; + /// + typedef boost::shared_ptr ImagePtr; + + /// The status of the loading process + enum ImageStatus { + /** The data is in the cache, but no request to display it + * has been received. + */ + WaitingToLoad, + /// The image is in a loadable format and is being loaded. + Loading, + /// The image is being converted to a loadable format. + Converting, + /// The image is in memory and is being scaled, rotated, etc. + ScalingEtc, + /// All finished. Can display the image. + Loaded, + /// + ErrorNoFile, + /// + ErrorConverting, + /// + ErrorLoading, + /// Fall back on the unmodified image? + ErrorScalingEtc, + /// The data is not in the cache at all! + ErrorUnknown + }; +} + +#endif // GRAPHICSTYPES_H diff --git a/src/graphics/ImageLoader.C b/src/graphics/ImageLoader.C deleted file mode 100644 index ab7450dd83..0000000000 --- a/src/graphics/ImageLoader.C +++ /dev/null @@ -1,82 +0,0 @@ -/* This file is part of - * ================================================= - * - * LyX, The Document Processor - * Copyright 1995 Matthias Ettrich. - * Copyright 1995-2001 The LyX Team. - * - * ================================================= */ - -#ifdef __GNUG__ -#pragma implementation -#endif - -#include -#include "debug.h" -#include "ImageLoader.h" -#include "frontends/support/LyXImage.h" - -#include "support/filetools.h" - -using std::endl; - -ImageLoader::ImageLoader() - : image_(0) -{ -} - -ImageLoader::~ImageLoader() -{ - freeImage(); -} - -void -ImageLoader::freeImage() -{ - delete image_; - image_ = 0; -} - -bool ImageLoader::isImageFormatOK(string const & /*filename*/) const -{ - return false; -} - -void ImageLoader::setImage(LyXImage * image) -{ - image_ = image; -} - -LyXImage * ImageLoader::getImage() -{ - LyXImage * tmp = image_; - image_ = 0; - return tmp; -} - -ImageLoader::FormatList const -ImageLoader::loadableFormats() const -{ - return FormatList(); -} - -ImageLoader::Result -ImageLoader::loadImage(string const & filename) -{ - // Make sure file exists and is readable. - if (! IsFileReadable(filename)) { - lyxerr << "No XPM file found." << endl; - return NoFile; - } - - // Verify that the file format is correct. - if (! isImageFormatOK(filename)) { - lyxerr << "File format incorrect." << endl; - return ImageFormatUnknown; - } - - freeImage(); - - return runImageLoader(filename); -} - diff --git a/src/graphics/ImageLoader.h b/src/graphics/ImageLoader.h deleted file mode 100644 index cf126b7b72..0000000000 --- a/src/graphics/ImageLoader.h +++ /dev/null @@ -1,87 +0,0 @@ -// -*- C++ -*- -/* This file is part of - * ================================================= - * - * LyX, The Document Processor - * Copyright 1995 Matthias Ettrich. - * Copyright 1995-2001 The LyX Team. - * - * ================================================= */ - -#ifndef IMAGELOADER_H -#define IMAGELOADER_H - -#ifdef __GNUG__ -#pragma interface -#endif - -#include "LString.h" -#include "boost/utility.hpp" -#include - -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, - */ -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 FormatList; - - /// c-tor. - ImageLoader(); - /// d-tor. - virtual ~ImageLoader(); - - /// Start loading the image file. - ImageLoader::Result loadImage(string const & filename); - - /** Get the last rendered pixmap. Returns 0 if no image is ready. - * - * It is a one time operation, that is, after you get the image - * you are completely responsible to destroy it and the ImageLoader - * will not know about the image. - * - * This way we avoid deleting the image if you still use it and the - * ImageLoader is destructed, and if you don't use it we get to - * destruct the image to avoid memory leaks. - */ - LyXImage * getImage(); - - /// Return the list of loadable formats. - virtual FormatList const loadableFormats() const; - -protected: - /// Verify that the file is one that we can handle. - virtual bool isImageFormatOK(string const & filename) const = 0; - - /// Do the actual image loading. - virtual Result runImageLoader(string const & filename) = 0; - - /// Set the image that was loaded. - void setImage(LyXImage * image); - -private: - /// Free the loaded image. - void freeImage(); - - /// The loaded image. An auto_ptr would be great here, but it's not - /// available everywhere (gcc 2.95.2 doesnt have it). - LyXImage * image_; -}; - -#endif diff --git a/src/graphics/ImageLoaderXPM.C b/src/graphics/ImageLoaderXPM.C deleted file mode 100644 index 94db3f9473..0000000000 --- a/src/graphics/ImageLoaderXPM.C +++ /dev/null @@ -1,142 +0,0 @@ -/* This file is part of - * ================================================= - * - * LyX, The Document Processor - * Copyright 1995 Matthias Ettrich. - * Copyright 1995-2001 The LyX Team. - * - * ================================================= */ - -#include - -#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 -#include - - -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(filename.c_str()), - &pixmap, &mask, &attrib); - - if (status != XpmSuccess) { - lyxerr << "Error reading XPM file '" - << XpmGetErrorString(status) << "'" - << endl; - return ErrorWhileLoading; - } - - // This should have been set by the XpmReadFileToPixmap call! - lyx::Assert(attrib.valuemask & XpmSize); - - setImage(new LyXImage(pixmap, attrib.width, attrib.height)); - - XpmFreeAttributes(&attrib); - - return OK; -} diff --git a/src/graphics/ImageLoaderXPM.h b/src/graphics/ImageLoaderXPM.h deleted file mode 100644 index 0d15aa5b91..0000000000 --- a/src/graphics/ImageLoaderXPM.h +++ /dev/null @@ -1,43 +0,0 @@ -// -*- C++ -*- -/* This file is part of - * ================================================= - * - * LyX, The Document Processor - * Copyright 1995 Matthias Ettrich. - * Copyright 1995-2001 The LyX Team. - * - * ================================================= */ - -#ifndef IMAGELOADER_XPM_H -#define IMAGELOADER_XPM_H - -#ifdef __GNUG__ -#pragma interface -#endif - -#include "graphics/ImageLoader.h" - -/** ImageLoaderXPM is an implementation of ImageLoader that can load XPM images by - * using libXPM. - * - * @Author Baruch Even, - */ -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 diff --git a/src/graphics/Makefile.am b/src/graphics/Makefile.am index ecdace9771..1012f7503b 100644 --- a/src/graphics/Makefile.am +++ b/src/graphics/Makefile.am @@ -12,10 +12,14 @@ libgraphics_la_SOURCES = \ GraphicsCache.C \ GraphicsCacheItem.h \ GraphicsCacheItem.C \ - ImageLoaderXPM.h \ - ImageLoaderXPM.C \ - ImageLoader.h \ - ImageLoader.C + GraphicsConverter.h \ + GraphicsConverter.C \ + GraphicsImage.h \ + GraphicsImage.C \ + GraphicsImageXPM.h \ + GraphicsImageXPM.C \ + GraphicsParams.C \ + GraphicsParams.h libgraphics.la: libgraphics.o diff --git a/src/insets/insetgraphics.C b/src/insets/insetgraphics.C index 59f463c436..a9d040a559 100644 --- a/src/insets/insetgraphics.C +++ b/src/insets/insetgraphics.C @@ -93,47 +93,40 @@ TODO Before initial production release: #include "insets/insetgraphicsParams.h" #include "graphics/GraphicsCache.h" -#include "graphics/GraphicsCacheItem.h" +#include "graphics/GraphicsImage.h" #include "LyXView.h" #include "buffer.h" #include "BufferView.h" #include "converter.h" #include "Painter.h" -#include "lyx_gui_misc.h" -#include "lyxtext.h" #include "lyxrc.h" -#include "font.h" +#include "font.h" // For the lyxfont class. #include "debug.h" #include "gettext.h" +#include "LaTeXFeatures.h" #include "frontends/Dialogs.h" #include "frontends/Alert.h" -#include "frontends/controllers/helper_funcs.h" -#include "frontends/support/LyXImage.h" +#include "frontends/controllers/helper_funcs.h" // getVectorFromString -#include "support/FileInfo.h" +#include "support/LAssert.h" #include "support/filetools.h" -#include "support/lyxlib.h" -#include "support/lyxmanip.h" -#include "support/lyxalgo.h" +#include "support/lyxalgo.h" // lyx::count -#include -#include +#include // For the std::max extern string system_tempdir; -using std::ifstream; using std::ostream; using std::endl; -using std::max; -using std::vector; - /////////////////////////////////////////////////////////////////////////// int const VersionNumber = 1; /////////////////////////////////////////////////////////////////////////// +namespace { + // This function is a utility function // ... that should be with ChangeExtension ... inline @@ -141,6 +134,8 @@ string const RemoveExtension(string const & filename) { return ChangeExtension(filename, string()); } + +} // namespace anon namespace { @@ -159,19 +154,17 @@ string const unique_id() } // namespace anon -// Initialize only those variables that do not have a constructor. InsetGraphics::InsetGraphics() - : cacheHandle(0), imageLoaded(false), graphic_label(unique_id()) + : cached_status_(grfx::ErrorUnknown), cache_filled_(false), + graphic_label(unique_id()) {} InsetGraphics::InsetGraphics(InsetGraphics const & ig, bool same_id) - : Inset(), SigC::Object() - , cacheHandle(ig.cacheHandle) - , imageLoaded(ig.imageLoaded) - , graphic_label(unique_id()) + : cached_status_(grfx::ErrorUnknown), cache_filled_(false), + graphic_label(unique_id()) { - setParams(ig.getParams()); + setParams(ig.params()); if (same_id) id_ = ig.id_; } @@ -179,6 +172,10 @@ InsetGraphics::InsetGraphics(InsetGraphics const & ig, bool same_id) InsetGraphics::~InsetGraphics() { + cached_image_.reset(0); + grfx::GCache & gc = grfx::GCache::get(); + gc.remove(*this); + // Emits the hide signal to the dialog connected (if any) hideDialog(); } @@ -187,37 +184,70 @@ InsetGraphics::~InsetGraphics() string const InsetGraphics::statusMessage() const { string msg; - if (cacheHandle.get()) { - switch (cacheHandle->getImageStatus()) { - case GraphicsCacheItem::UnknownError: - msg = _("Unknown Error"); - break; - case GraphicsCacheItem::Loading: - msg = _("Loading..."); - break; - case GraphicsCacheItem::ErrorReading: - msg = _("Error reading"); - break; - case GraphicsCacheItem::Converting: - msg = _("Converting Image"); - break; - case GraphicsCacheItem::ErrorConverting: - msg = _("Error converting"); - break; - case GraphicsCacheItem::Loaded: - // No message to write. - break; - } + + switch (cached_status_) { + case grfx::WaitingToLoad: + msg = _("Waiting for draw request to start loading..."); + break; + case grfx::Loading: + msg = _("Loading..."); + break; + case grfx::Converting: + msg = _("Converting to loadable format..."); + break; + case grfx::ScalingEtc: + msg = _("Loaded. Scaling etc..."); + break; + case grfx::ErrorNoFile: + msg = _("No file found!"); + break; + case grfx::ErrorLoading: + msg = _("Error loading file into memory"); + break; + case grfx::ErrorConverting: + msg = _("Error converting to loadable format"); + break; + case grfx::ErrorScalingEtc: + msg = _("Error scaling etc"); + break; + case grfx::ErrorUnknown: + msg = _("No image associated with this inset is in the cache!"); + break; + case grfx::Loaded: + msg = _("Loaded but not displaying"); + break; } + return msg; } +void InsetGraphics::setCache() const +{ + if (cache_filled_) + return; + + grfx::GCache & gc = grfx::GCache::get(); + cached_status_ = gc.status(*this); + cached_image_ = gc.image(*this); +} + + +bool InsetGraphics::drawImage() const +{ + setCache(); + Pixmap const pixmap = + (cached_status_ == grfx::Loaded && cached_image_.get() != 0) ? + cached_image_->getPixmap() : 0; + + return pixmap != 0; +} + + int InsetGraphics::ascent(BufferView *, LyXFont const &) const { - LyXImage * pixmap = 0; - if (cacheHandle.get() && (pixmap = cacheHandle->getImage())) - return pixmap->getHeight(); + if (drawImage()) + return cached_image_->getHeight(); else return 50; } @@ -225,24 +255,21 @@ int InsetGraphics::ascent(BufferView *, LyXFont const &) const int InsetGraphics::descent(BufferView *, LyXFont const &) const { - // this is not true if viewport is used and clip is not. return 0; } int InsetGraphics::width(BufferView *, LyXFont const & font) const { - LyXImage * pixmap = 0; - - if (cacheHandle.get() && (pixmap = cacheHandle->getImage())) - return pixmap->getWidth(); + if (drawImage()) + return cached_image_->getWidth(); else { int font_width = 0; LyXFont msgFont(font); msgFont.setFamily(LyXFont::SANS_FAMILY); - string const justname = OnlyFilename (params.filename); + string const justname = OnlyFilename (params().filename); if (!justname.empty()) { msgFont.setSize(LyXFont::SIZE_FOOTNOTE); font_width = lyxfont::width(justname, msgFont); @@ -252,10 +279,10 @@ int InsetGraphics::width(BufferView *, LyXFont const & font) const if (!msg.empty()) { msgFont.setSize(LyXFont::SIZE_TINY); int const msg_width = lyxfont::width(msg, msgFont); - font_width = max(font_width, msg_width); + font_width = std::max(font_width, msg_width); } - return max(50, font_width + 15); + return std::max(50, font_width + 15); } } @@ -263,49 +290,47 @@ int InsetGraphics::width(BufferView *, LyXFont const & font) const void InsetGraphics::draw(BufferView * bv, LyXFont const & font, int baseline, float & x, bool) const { - Painter & paint = bv->painter(); - int ldescent = descent(bv, font); - int lascent = ascent(bv, font); - int lwidth = width(bv, font); + int lascent = ascent(bv, font); + int lwidth = width(bv, font); - // Make sure x is updated upon exit from this routine + // Make sure now that x is updated upon exit from this routine int old_x = int(x); x += lwidth; + // Initiate the loading of the graphics file + if (cached_status_ == grfx::WaitingToLoad) { + grfx::GCache & gc = grfx::GCache::get(); + gc.startLoading(*this); + } + // This will draw the graphics. If the graphics has not been loaded yet, // we draw just a rectangle. - if (imageLoaded) { + Painter & paint = bv->painter(); + + if (drawImage()) { paint.image(old_x + 2, baseline - lascent, lwidth - 4, lascent + ldescent, - cacheHandle->getImage()); + *cached_image_.get()); + } else { - // Get the image status, default to unknown error. - GraphicsCacheItem::ImageStatus status = GraphicsCacheItem::UnknownError; - if (lyxrc.use_gui && params.display != InsetGraphicsParams::NONE && - cacheHandle.get()) - status = cacheHandle->getImageStatus(); - // Check if the image is now ready. - if (status == GraphicsCacheItem::Loaded) { - imageLoaded = true; - // Tell BufferView we need to be updated! - bv->text->status(bv, LyXText::CHANGED_IN_DRAW); - return; - } + paint.rectangle(old_x + 2, baseline - lascent, lwidth - 4, lascent + ldescent); + // Print the file name. LyXFont msgFont(font); msgFont.setFamily(LyXFont::SANS_FAMILY); - string const justname = OnlyFilename (params.filename); + string const justname = OnlyFilename (params().filename); if (!justname.empty()) { msgFont.setSize(LyXFont::SIZE_FOOTNOTE); paint.text(old_x + 8, baseline - lyxfont::maxAscent(msgFont) - 4, justname, msgFont); } + // Print the message. string const msg = statusMessage(); if (!msg.empty()) { @@ -313,6 +338,21 @@ void InsetGraphics::draw(BufferView * bv, LyXFont const & font, paint.text(old_x + 8, baseline - 4, msg, msgFont); } } + + // Reset the cache, ready for the next draw request + cached_status_ = grfx::ErrorUnknown; + cached_image_.reset(0); + cache_filled_ = false; +} + + +// Update the inset after parameters changed (read from file or changed in +// dialog. The grfx::GCache makes the decisions about whether or not to draw +// (interogates lyxrc, ascertains whether file exists etc) +void InsetGraphics::updateInset() const +{ + grfx::GCache & gc = grfx::GCache::get(); + gc.update(*this); } @@ -337,7 +377,7 @@ Inset::EDITABLE InsetGraphics::editable() const void InsetGraphics::write(Buffer const * buf, ostream & os) const { os << "Graphics FormatVersion " << VersionNumber << '\n'; - params.Write(buf, os); + params().Write(buf, os); } @@ -364,7 +404,7 @@ void InsetGraphics::readInsetGraphics(Buffer const * buf, LyXLex & lex) string const token = lex.getString(); lyxerr[Debug::GRAPHICS] << "Token: '" << token << '\'' - << endl; + << std::endl; if (token.empty()) { continue; @@ -378,13 +418,13 @@ void InsetGraphics::readInsetGraphics(Buffer const * buf, LyXLex & lex) << "This document was created with a newer Graphics widget" ", You should use a newer version of LyX to read this" " file." - << endl; + << std::endl; // TODO: Possibly open up a dialog? } else { - if (! params.Read(buf, lex, token)) + if (! params_.Read(buf, lex, token)) lyxerr << "Unknown token, " << token << ", skipping." - << endl; + << std::endl; } } } @@ -392,18 +432,18 @@ void InsetGraphics::readInsetGraphics(Buffer const * buf, LyXLex & lex) // FormatVersion < 1.0 (LyX < 1.2) void InsetGraphics::readFigInset(Buffer const * buf, LyXLex & lex) { - vector const oldUnits = + std::vector const oldUnits = getVectorFromString("pt,cm,in,p%,c%"); bool finished = false; // set the display default if (lyxrc.display_graphics == "mono") - params.display = InsetGraphicsParams::MONOCHROME; + params_.display = InsetGraphicsParams::MONOCHROME; else if (lyxrc.display_graphics == "gray") - params.display = InsetGraphicsParams::GRAYSCALE; + params_.display = InsetGraphicsParams::GRAYSCALE; else if (lyxrc.display_graphics == "color") - params.display = InsetGraphicsParams::COLOR; + params_.display = InsetGraphicsParams::COLOR; else - params.display = InsetGraphicsParams::NONE; + params_.display = InsetGraphicsParams::NONE; while (lex.isOK() && !finished) { lex.next(); @@ -418,49 +458,49 @@ void InsetGraphics::readFigInset(Buffer const * buf, LyXLex & lex) if (lex.next()) { string const name = lex.getString(); string const path = buf->filePath(); - params.filename = MakeAbsPath(name, path); + params_.filename = MakeAbsPath(name, path); } } else if (token == "extra") { if (lex.next()); // kept for backwards compability. Delete in 0.13.x } else if (token == "subcaption") { if (lex.eatLine()) - params.subcaptionText = lex.getString(); - params.subcaption = true; + params_.subcaptionText = lex.getString(); + params_.subcaption = true; } else if (token == "label") { if (lex.next()); // kept for backwards compability. Delete in 0.13.x } else if (token == "angle") { if (lex.next()) - params.rotate = true; - params.rotateAngle = lex.getFloat(); + params_.rotate = true; + params_.rotateAngle = lex.getFloat(); } else if (token == "size") { if (lex.next()) - params.lyxwidth = LyXLength(lex.getString()+"pt"); + params_.lyxwidth = LyXLength(lex.getString()+"pt"); if (lex.next()) - params.lyxheight = LyXLength(lex.getString()+"pt"); + params_.lyxheight = LyXLength(lex.getString()+"pt"); } else if (token == "flags") { if (lex.next()) switch (lex.getInteger()) { - case 1: params.display = InsetGraphicsParams::MONOCHROME; + case 1: params_.display = InsetGraphicsParams::MONOCHROME; break; - case 2: params.display = InsetGraphicsParams::GRAYSCALE; + case 2: params_.display = InsetGraphicsParams::GRAYSCALE; break; - case 3: params.display = InsetGraphicsParams::COLOR; + case 3: params_.display = InsetGraphicsParams::COLOR; break; } } else if (token == "subfigure") { - params.subcaption = true; + params_.subcaption = true; } else if (token == "width") { if (lex.next()) { int i = lex.getInteger(); if (lex.next()) { if (i == 5) { - params.scale = lex.getInteger(); - params.size_type = InsetGraphicsParams::SCALE; + params_.scale = lex.getInteger(); + params_.size_type = InsetGraphicsParams::SCALE; } else { - params.width = LyXLength(lex.getString()+oldUnits[i]); - params.size_type = InsetGraphicsParams::WH; + params_.width = LyXLength(lex.getString()+oldUnits[i]); + params_.size_type = InsetGraphicsParams::WH; } } } @@ -468,8 +508,8 @@ void InsetGraphics::readFigInset(Buffer const * buf, LyXLex & lex) if (lex.next()) { int i = lex.getInteger(); if (lex.next()) { - params.height = LyXLength(lex.getString()+oldUnits[i]); - params.size_type = InsetGraphicsParams::WH; + params_.height = LyXLength(lex.getString()+oldUnits[i]); + params_.size_type = InsetGraphicsParams::WH; } } } @@ -482,51 +522,52 @@ string const InsetGraphics::createLatexOptions() const // stream since we might have a trailing comma that we would like to remove // before writing it to the output stream. ostringstream options; - if (!params.bb.empty()) - options << " bb=" << strip(params.bb) << ",\n"; - if (params.draft) + if (!params().bb.empty()) + options << " bb=" << strip(params().bb) << ",\n"; + if (params().draft) options << " draft,\n"; - if (params.clip) + if (params().clip) options << " clip,\n"; - if (params.size_type == InsetGraphicsParams::WH) { - if (!params.width.zero()) - options << " width=" << params.width.asLatexString() << ",\n"; - if (!params.height.zero()) - options << " height=" << params.height.asLatexString() << ",\n"; - } else if (params.size_type == InsetGraphicsParams::SCALE) { - if (params.scale > 0) - options << " scale=" << double(params.scale)/100.0 << ",\n"; + if (params().size_type == InsetGraphicsParams::WH) { + if (!params().width.zero()) + options << " width=" << params().width.asLatexString() << ",\n"; + if (!params().height.zero()) + options << " height=" << params().height.asLatexString() << ",\n"; + } else if (params().size_type == InsetGraphicsParams::SCALE) { + if (params().scale > 0) + options << " scale=" << double(params().scale)/100.0 << ",\n"; } - if (params.keepAspectRatio) + if (params().keepAspectRatio) options << " keepaspectratio,\n"; // Make sure it's not very close to zero, a float can be effectively // zero but not exactly zero. - if (!lyx::float_equal(params.rotateAngle, 0, 0.001) && params.rotate) { - options << " angle=" << params.rotateAngle << ",\n"; - if (!params.rotateOrigin.empty()) { - options << " origin=" << params.rotateOrigin[0]; - if (contains(params.rotateOrigin,"Top")) + if (!lyx::float_equal(params().rotateAngle, 0, 0.001) && params().rotate) { + options << " angle=" << params().rotateAngle << ",\n"; + if (!params().rotateOrigin.empty()) { + options << " origin=" << params().rotateOrigin[0]; + if (contains(params().rotateOrigin,"Top")) options << 't'; - else if (contains(params.rotateOrigin,"Bottom")) + else if (contains(params().rotateOrigin,"Bottom")) options << 'b'; - else if (contains(params.rotateOrigin,"Baseline")) + else if (contains(params().rotateOrigin,"Baseline")) options << 'B'; options << ",\n"; } } - if (!params.special.empty()) - options << params.special << ",\n"; + if (!params().special.empty()) + options << params().special << ",\n"; string opts = options.str().c_str(); return opts.substr(0,opts.size()-2); // delete last ",\n" } namespace { -string decideOutputImageFormat(string const & suffix) +string findTargetFormat(string const & suffix) { // lyxrc.pdf_mode means: // Are we creating a PDF or a PS file? // (Should actually mean, are we using latex or pdflatex). - lyxerr[Debug::GRAPHICS] << "decideOutput::lyxrc.pdf_mode = " << lyxrc.pdf_mode << "\n"; + lyxerr[Debug::GRAPHICS] << "decideOutput: lyxrc.pdf_mode = " + << lyxrc.pdf_mode << std::endl; if (lyxrc.pdf_mode) { if (contains(suffix,"ps") || suffix == "pdf") return "pdf"; @@ -545,6 +586,7 @@ string decideOutputImageFormat(string const & suffix) } // Anon. namespace + string const InsetGraphics::prepareFile(Buffer const *buf) const { // do_convert = Do we need to convert the file? @@ -560,38 +602,36 @@ string const InsetGraphics::prepareFile(Buffer const *buf) const // return original filename without the extension // // if it's a zipped one, than let LaTeX do the rest!!! - if ((zippedFile(params.filename) && params.noUnzip) || buf->niceFile) { - lyxerr[Debug::GRAPHICS] << "don't unzip file or export latex" - << params.filename << endl; - return params.filename; - } - string filename_ = params.filename; - 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) // :-) + 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_; -// 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; + } + + if (zipped) + filename_ = unzipFile(filename_); + + string const from = getExtFromContents(filename_); + string const to = findTargetFormat(from); + + if (from == to) { + // No conversion needed! + return filename_; + } + string const temp = AddName(buf->tmppath, filename_); - outfile = RemoveExtension(temp); + string const outfile_base = RemoveExtension(temp); + lyxerr[Debug::GRAPHICS] << "tempname = " << temp << "\n"; lyxerr[Debug::GRAPHICS] << "buf::tmppath = " << buf->tmppath << "\n"; lyxerr[Debug::GRAPHICS] << "filename_ = " << filename_ << "\n"; - lyxerr[Debug::GRAPHICS] << "outfile = " << outfile << endl; - converters.convert(buf, filename_, outfile, extension, image_target); - return outfile; + lyxerr[Debug::GRAPHICS] << "outfile_base = " << outfile_base << endl; + + converters.convert(buf, filename_, outfile_base, from, to); + return outfile_base; } @@ -600,7 +640,7 @@ int InsetGraphics::latex(Buffer const *buf, ostream & os, { // If there is no file specified, just output a message about it in // the latex output. - if (params.filename.empty()) { + if (params().filename.empty()) { os << "\\fbox{\\rule[-0.5in]{0pt}{1in}" << _("empty figure path") << "}\n"; return 1; // One end of line marker added to the stream. @@ -610,8 +650,8 @@ int InsetGraphics::latex(Buffer const *buf, ostream & os, string before; string after; // Do we want subcaptions? - if (params.subcaption) { - before += "\\subfigure[" + params.subcaptionText + "]{"; + if (params().subcaption) { + before += "\\subfigure[" + params().subcaptionText + "]{"; after = '}'; } // We never use the starred form, we use the "clip" option instead. @@ -626,9 +666,11 @@ int InsetGraphics::latex(Buffer const *buf, ostream & os, // appropriate (when there are several versions in different formats) string const latex_str = before + '{' + prepareFile(buf) + '}' + after; os << latex_str; + // Return how many newlines we issued. int const newlines = int(lyx::count(latex_str.begin(), latex_str.end(),'\n') + 1); + // lyxerr << "includegraphics: " << newlines << " lines of text" // << endl; return newlines; @@ -642,7 +684,7 @@ int InsetGraphics::ascii(Buffer const *, ostream & os, int) const // 1. Convert file to ascii using gifscii // 2. Read ascii output file and add it to the output stream. // at least we send the filename - os << '<' << _("Graphicfile:") << params.filename << ">\n"; + os << '<' << _("Graphicfile:") << params().filename << ">\n"; return 0; } @@ -670,59 +712,27 @@ int InsetGraphics::docbook(Buffer const *, ostream & os) const void InsetGraphics::validate(LaTeXFeatures & features) const { // If we have no image, we should not require anything. - if (params.filename.empty()) + if (params().filename.empty()) return ; - features.includeFile(graphic_label, RemoveExtension(params.filename)); + features.includeFile(graphic_label, RemoveExtension(params_.filename)); features.require("graphicx"); - if (params.subcaption) + if (params().subcaption) features.require("subfigure"); } -// Update the inset after parameters changed (read from file or changed in -// dialog. -void InsetGraphics::updateInset() const -{ - GraphicsCache & gc = GraphicsCache::getInstance(); - boost::shared_ptr temp(0); - - // We do it this way so that in the face of some error, we will still - // be in a valid state. - InsetGraphicsParams::DisplayType local_display = params.display; - if (local_display == InsetGraphicsParams::DEFAULT) { - if (lyxrc.display_graphics == "mono") - local_display = InsetGraphicsParams::MONOCHROME; - else if (lyxrc.display_graphics == "gray") - local_display = InsetGraphicsParams::GRAYSCALE; - else if (lyxrc.display_graphics == "color") - local_display = InsetGraphicsParams::COLOR; - else - local_display = InsetGraphicsParams::NONE; - } - - if (!params.filename.empty() && lyxrc.use_gui && - local_display != InsetGraphicsParams::NONE) { - temp = gc.addFile(params.filename); - } - - // Mark the image as unloaded so that it gets updated. - imageLoaded = false; - - cacheHandle = temp; -} - - bool InsetGraphics::setParams(InsetGraphicsParams const & p) { // If nothing is changed, just return and say so. - if (params == p) + if (params() == p && !p.filename.empty()) { return false; + } // Copy the new parameters. - params = p; + params_ = p; // Update the inset with the new parameters. updateInset(); @@ -732,9 +742,9 @@ bool InsetGraphics::setParams(InsetGraphicsParams const & p) } -InsetGraphicsParams InsetGraphics::getParams() const +InsetGraphicsParams const & InsetGraphics::params() const { - return params; + return params_; } @@ -742,4 +752,3 @@ Inset * InsetGraphics::clone(Buffer const &, bool same_id) const { return new InsetGraphics(*this, same_id); } - diff --git a/src/insets/insetgraphics.h b/src/insets/insetgraphics.h index ac380bffc4..7f5143981d 100644 --- a/src/insets/insetgraphics.h +++ b/src/insets/insetgraphics.h @@ -19,18 +19,14 @@ #pragma interface #endif +#include "graphics/GraphicsTypes.h" #include "insets/inset.h" #include "insets/insetgraphicsParams.h" -#include "graphics/GraphicsCacheItem.h" -#include - -#include "LaTeXFeatures.h" - // We need a signal here to hide an active dialog when we are deleted. #include "sigc++/signal_system.h" class Dialogs; -class LyXImage; +class LaTeXFeatures; /// class InsetGraphics : public Inset, public SigC::Object { @@ -92,7 +88,7 @@ public: bool setParams(InsetGraphicsParams const & params); /// Get the inset parameters, used by the GUIndependent dialog. - InsetGraphicsParams getParams() const; + InsetGraphicsParams const & params() const; /** This signal is connected by our dialog and called when the inset is deleted. @@ -100,6 +96,11 @@ public: SigC::Signal0 hideDialog; private: + /// Set the cached variables + void setCache() const; + /// Is the image ready to draw, or should we display a message instead? + bool drawImage() const; + /// Read the inset native format void readInsetGraphics(Buffer const * buf, LyXLex & lex); /// Read the FigInset file format @@ -113,14 +114,19 @@ private: string const createLatexOptions() const; /// Convert the file if needed, and return the location of the file. string const prepareFile(Buffer const * buf) const; - /// The graphics cache handle. - mutable boost::shared_ptr 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 diff --git a/src/lyxfunc.C b/src/lyxfunc.C index 21b21daf2c..3e3691f1b7 100644 --- a/src/lyxfunc.C +++ b/src/lyxfunc.C @@ -84,10 +84,13 @@ #include "frontends/Menubar.h" #include "frontends/Alert.h" +#include "graphics/GraphicsCache.h" + #include "support/lyxalgo.h" #include "support/LAssert.h" #include "support/filetools.h" #include "support/FileInfo.h" +#include "support/forkedcontr.h" #include "support/lstrings.h" #include "support/path.h" #include "support/lyxfunctional.h" @@ -968,7 +971,7 @@ string const LyXFunc::dispatch(kb_action action, string argument) } switch (action) { - + case LFUN_ESCAPE: { if (!owner->view()->available()) break; @@ -1603,6 +1606,10 @@ string const LyXFunc::dispatch(kb_action action, string argument) break; } + bool const graphicsbg_changed = + (lyx_name == lcolor.getLyXName(LColor::graphicsbg) && + x11_name != lcolor.getX11Name(LColor::graphicsbg)); + if (!lcolor.setColor(lyx_name, x11_name)) { static string const err1 (N_("Set-color \"")); static string const err2 ( @@ -1611,7 +1618,14 @@ string const LyXFunc::dispatch(kb_action action, string argument) setErrorMessage(_(err1) + lyx_name + _(err2)); break; } + lyxColorHandler->updateColor(lcolor.getFromLyXName(lyx_name)); + + if (graphicsbg_changed) { + grfx::GCache & gc = grfx::GCache::get(); + gc.changeDisplay(true); + } + owner->view()->redraw(); break; } @@ -1628,6 +1642,21 @@ string const LyXFunc::dispatch(kb_action action, string argument) owner->messagePop(); break; + case LFUN_FORKS_SHOW: + owner->getDialogs()->showForks(); + break; + + case LFUN_FORKS_KILL: + { + if (!isStrInt(argument)) + break; + + pid_t const pid = strToInt(argument); + ForkedcallsController & fcc = ForkedcallsController::get(); + fcc.kill(pid); + break; + } + default: // Then if it was none of the above // Trying the BufferView::pimpl dispatch: diff --git a/src/lyxrc.C b/src/lyxrc.C index 79f7f72eb8..95f463fe8f 100644 --- a/src/lyxrc.C +++ b/src/lyxrc.C @@ -225,7 +225,7 @@ void LyXRC::setDefaults() { make_backup = true; backupdir_path.erase(); exit_confirmation = true; - display_graphics = "mono"; + display_graphics = "color"; display_shortcuts = true; // Spellchecker settings: #ifdef USE_PSPELL diff --git a/src/support/ChangeLog b/src/support/ChangeLog index 1e72a15a0d..4045531aed 100644 --- a/src/support/ChangeLog +++ b/src/support/ChangeLog @@ -1,3 +1,14 @@ +2002-02-19 Angus Leeming + + * 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 * Makefile.am: diff --git a/src/support/Makefile.am b/src/support/Makefile.am index 4670add509..3462a57536 100644 --- a/src/support/Makefile.am +++ b/src/support/Makefile.am @@ -5,7 +5,8 @@ noinst_LTLIBRARIES = libsupport.la LIBS = ETAGS_ARGS = --lang=c++ BOOST_INCLUDES = -I$(top_srcdir)/boost -INCLUDES = -I${srcdir}/../ $(BOOST_INCLUDES) +SIGC_INCLUDES = -I$(top_srcdir) +INCLUDES = -I${srcdir}/../ $(SIGC_INCLUDES) $(BOOST_INCLUDES) EXTRA_DIST = lyxstring.C lyxstring.h regex.c lyxregex.h \ os_unix.C os_win32.C os_os2.C @@ -42,6 +43,10 @@ libsupport_la_SOURCES = \ filetools.C \ filetools.h \ fmt.C \ + forkedcall.C \ + forkedcall.h \ + forkedcontr.C \ + forkedcontr.h \ getUserName.C \ getcwd.C \ kill.C \ diff --git a/src/support/forkedcall.C b/src/support/forkedcall.C new file mode 100644 index 0000000000..a69cae3c62 --- /dev/null +++ b/src/support/forkedcall.C @@ -0,0 +1,270 @@ +/** + * \file syscall.C + * Copyright 2002 the LyX Team + * Read the file COPYING + * + * \author Asger Alstrup + * + * Interface cleaned up by + * \author Angus Leeming + * + * 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 + +#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 +#include +#include +#include +#include +#include + +using std::endl; + +#ifndef CXX_GLOBAL_CSTD +using std::strerror; +#endif + + +Forkedcall::Forkedcall() + : pid_(0), retval_(0) +{} + + +int Forkedcall::startscript(Starttype wait, string const & what) +{ + if (wait == Wait) { + command_ = what; + retval_ = 0; + + pid_ = generateChild(); + if (pid_ <= 0) { // child or fork failed. + retval_ = 1; + } else { + retval_ = waitForChild(); + } + + return retval_; + } + + // DontWait + retval_ = startscript(what, SignalTypePtr()); + return retval_; +} + + +int Forkedcall::startscript(string const & what, SignalTypePtr signal) +{ + command_ = what; + signal_ = signal; + retval_ = 0; + + pid_ = generateChild(); + if (pid_ <= 0) { // child or fork failed. + retval_ = 1; + return retval_; + } + + // Non-blocking execution. + // Integrate into the Controller + ForkedcallsController & contr = ForkedcallsController::get(); + contr.addCall(*this); + + return retval_; +} + + +void Forkedcall::emitSignal() +{ + if (signal_.get()) { + signal_->emit(command_, pid_, retval_); + } +} + + +namespace { + +class Murder : public SigC::Object { +public: + // + static void killItDead(int secs, pid_t pid) + { + if (secs > 0) { + new Murder(secs, pid); + } else if (pid != 0) { + lyx::kill(pid, SIGKILL); + } + } + + // + void kill() + { + if (pid_ != 0) { + lyx::kill(pid_, SIGKILL); + } + lyxerr << "Killed " << pid_ << std::endl; + delete this; + } + +private: + // + Murder(int secs, pid_t pid) + : timeout_(0), pid_(pid) + { + timeout_ = new Timeout(1000*secs, Timeout::ONETIME); + timeout_->timeout.connect(SigC::slot(this, &Murder::kill)); + timeout_->start(); + } + + // + ~Murder() + { + delete timeout_; + } + // + Timeout * timeout_; + // + pid_t pid_; +}; + +} // namespace anon + + +void Forkedcall::kill(int tol) +{ + lyxerr << "Forkedcall::kill(" << tol << ")" << std::endl; + if (pid() == 0) { + lyxerr << "Can't kill non-existent process!" << endl; + return; + } + + int const tolerance = std::max(0, tol); + if (tolerance == 0) { + // Kill it dead NOW! + Murder::killItDead(0, pid()); + + } else { + int ret = lyx::kill(pid(), SIGHUP); + + // The process is already dead if wait_for_death is false + bool const wait_for_death = (ret == 0 && errno != ESRCH); + + if (wait_for_death) { + Murder::killItDead(tolerance, pid()); + } + } +} + + +// Wait for child process to finish. Returns returncode from child. +int Forkedcall::waitForChild() { + // We'll pretend that the child returns 1 on all error conditions. + retval_ = 1; + int status; + bool wait = true; + while (wait) { + pid_t waitrpid = waitpid(pid_, &status, WUNTRACED); + if (waitrpid == -1) { + lyxerr << "LyX: Error waiting for child:" + << strerror(errno) << endl; + wait = false; + } else if (WIFEXITED(status)) { + // Child exited normally. Update return value. + retval_ = WEXITSTATUS(status); + wait = false; + } else if (WIFSIGNALED(status)) { + lyxerr << "LyX: Child didn't catch signal " + << WTERMSIG(status) + << "and died. Too bad." << endl; + wait = false; + } else if (WIFSTOPPED(status)) { + lyxerr << "LyX: Child (pid: " << pid_ + << ") stopped on signal " + << WSTOPSIG(status) + << ". Waiting for child to finish." << endl; + } else { + lyxerr << "LyX: Something rotten happened while " + "waiting for child " << pid_ << endl; + wait = false; + } + } + return retval_; +} + + +// generate child in background +pid_t Forkedcall::generateChild() +{ + const int MAX_ARGV = 255; + char *syscmd = 0; + char *argv[MAX_ARGV]; + + string childcommand(command_); // copy + bool more = true; + string rest = split(command_, childcommand, ' '); + + int index = 0; + while (more) { + childcommand = frontStrip(childcommand); + if (syscmd == 0) { + syscmd = new char[childcommand.length() + 1]; + childcommand.copy(syscmd, childcommand.length()); + syscmd[childcommand.length()] = '\0'; + } + if (!childcommand.empty()) { + char * tmp = new char[childcommand.length() + 1]; + childcommand.copy(tmp, childcommand.length()); + tmp[childcommand.length()] = '\0'; + argv[index++] = tmp; + } + + // reinit + more = !rest.empty(); + if (more) + rest = split(rest, childcommand, ' '); + } + argv[index] = 0; + +#ifndef __EMX__ + pid_t cpid = ::fork(); + if (cpid == 0) { // child + execvp(syscmd, argv); + // If something goes wrong, we end up here: + lyxerr << "execvp failed: " + << strerror(errno) << endl; + } +#else + pid_t cpid = spawnvp(P_SESSION|P_DEFAULT|P_MINIMIZE|P_BACKGROUND, + syscmd, argv); +#endif + + if (cpid < 0) { // error + lyxerr << "Could not fork: " + << strerror(errno) << endl; + } + + return cpid; +} diff --git a/src/support/forkedcall.h b/src/support/forkedcall.h new file mode 100644 index 0000000000..8e15029278 --- /dev/null +++ b/src/support/forkedcall.h @@ -0,0 +1,138 @@ +// -*- C++ -*- +/** + * \file syscall.h + * Copyright 2002 the LyX Team + * Read the file COPYING + * + * \author Asger Alstrup + * + * Interface cleaned up by + * \author Angus Leeming + * + * 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 +#include +#include + + +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 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 SignalTypePtr; + + /// + int startscript(string const & what, SignalTypePtr); + + /** Invoking the following methods makes sense only if the command + * is running asynchronously! + */ + + /** gets the PID of the child process. + * Used by the timer. + */ + pid_t pid() const { return pid_; } + + /** Emit the signal. + * Used by the timer. + */ + void emitSignal(); + + /** Set the return value of the child process. + * Used by the timer. + */ + void setRetValue(int r) { retval_ = r; } + + /** Kill child prematurely. + * First, a SIGHUP is sent to the child. + * If that does not end the child process within "tolerance" + * seconds, the SIGKILL signal is sent to the child. + * When the child is dead, the callback is called. + */ + void kill(int tolerance = 5); + /// + string const & command() const { return command_; } + +private: + /// Callback function + SignalTypePtr signal_; + + /// Commmand line + string command_; + + /// Process ID of child + pid_t pid_; + + /// Return value from child + int retval_; + + /// + pid_t generateChild(); + + /// Wait for child process to finish. Updates returncode from child. + int waitForChild(); +}; + +#endif // FORKEDCALL_H diff --git a/src/support/forkedcontr.C b/src/support/forkedcontr.C new file mode 100644 index 0000000000..13b397eb88 --- /dev/null +++ b/src/support/forkedcontr.C @@ -0,0 +1,207 @@ +/** + * \file forkedcontr.C + * Copyright 2001 The LyX Team + * Read COPYING + * + * \author Asger Alstrup Nielsen + * \author Angus Leeming + * + * A class for the control of child processes launched using + * fork() and execvp(). + */ + +#include + +#ifdef __GNUG__ +#pragma implementation +#endif + +#include "forkedcontr.h" +#include "forkedcall.h" +#include "lyxfunctional.h" +#include "frontends/Timeout.h" +#include "debug.h" + +#include +#include +#include +#include + +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 const ForkedcallsController::getPIDs() const +{ + vector pids; + + if (forkedCalls.empty()) + return pids; + + pids.resize(forkedCalls.size()); + + vector::iterator vit = pids.begin(); + for (ListType::const_iterator lit = forkedCalls.begin(); + lit != forkedCalls.end(); ++lit, ++vit) { + *vit = (*lit)->pid(); + } + + std::sort(pids.begin(), pids.end()); + return pids; +} + + +// Get the command string of the process. +string const ForkedcallsController::getCommand(pid_t pid) const +{ + ListType::const_iterator it = + find_if(forkedCalls.begin(), forkedCalls.end(), + lyx::compare_memfun(&Forkedcall::pid, pid)); + + if (it == forkedCalls.end()) + return string(); + + return (*it)->command(); +} + + +// Kill the process prematurely and remove it from the list +// within tolerance secs +void ForkedcallsController::kill(pid_t pid, int tolerance) +{ + ListType::iterator it = + find_if(forkedCalls.begin(), forkedCalls.end(), + lyx::compare_memfun(&Forkedcall::pid, pid)); + + if (it == forkedCalls.end()) + return; + + (*it)->kill(tolerance); + forkedCalls.erase(it); + + if (forkedCalls.empty()) { + timeout_->stop(); + } +} diff --git a/src/support/forkedcontr.h b/src/support/forkedcontr.h new file mode 100644 index 0000000000..967ecc9c24 --- /dev/null +++ b/src/support/forkedcontr.h @@ -0,0 +1,78 @@ +// -*- C++ -*- +/** + * \file forkedcontr.h + * Copyright 2001 The LyX Team + * Read COPYING + * + * \author Asger Alstrup Nielsen + * \author Angus Leeming + * + * A class for the control of child processes launched using + * fork() and execvp(). + */ + +#ifndef FORKEDCONTR_H +#define FORKEDCONTR_H + +#include +#include +#include "LString.h" +#include + +#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 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 childrenChanged; + +private: + /// Can't create multiple instances of ForkedcallsController. + ForkedcallsController(); + /// + ForkedcallsController(ForkedcallsController const &); + /// + ~ForkedcallsController(); + + /// The child processes + typedef std::list 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