summaryrefslogtreecommitdiffstats
path: root/plug-ins/pygimp/gimpfu.py
diff options
context:
space:
mode:
Diffstat (limited to 'plug-ins/pygimp/gimpfu.py')
-rw-r--r--plug-ins/pygimp/gimpfu.py876
1 files changed, 876 insertions, 0 deletions
diff --git a/plug-ins/pygimp/gimpfu.py b/plug-ins/pygimp/gimpfu.py
new file mode 100644
index 0000000..266d5ae
--- /dev/null
+++ b/plug-ins/pygimp/gimpfu.py
@@ -0,0 +1,876 @@
+# Gimp-Python - allows the writing of GIMP plug-ins in Python.
+# Copyright (C) 1997 James Henstridge <james@daa.com.au>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+"""Simple interface for writing GIMP plug-ins in Python.
+
+Instead of worrying about all the user interaction, saving last used
+values and everything, the gimpfu module can take care of it for you.
+It provides a simple register() function that will register your
+plug-in if needed, and cause your plug-in function to be called when
+needed.
+
+Gimpfu will also handle showing a user interface for editing plug-in
+parameters if the plug-in is called interactively, and will also save
+the last used parameters, so the RUN_WITH_LAST_VALUES run_type will
+work correctly. It will also make sure that the displays are flushed
+on completion if the plug-in was run interactively.
+
+When registering the plug-in, you do not need to worry about
+specifying the run_type parameter.
+
+A typical gimpfu plug-in would look like this:
+ from gimpfu import *
+
+ def plugin_func(image, drawable, args):
+ # do what plugins do best
+ register(
+ "plugin_func",
+ "blurb",
+ "help message",
+ "author",
+ "copyright",
+ "year",
+ "My plug-in",
+ "*",
+ [
+ (PF_IMAGE, "image", "Input image", None),
+ (PF_DRAWABLE, "drawable", "Input drawable", None),
+ (PF_STRING, "arg", "The argument", "default-value")
+ ],
+ [],
+ plugin_func, menu="<Image>/Somewhere")
+ main()
+
+The call to "from gimpfu import *" will import all the gimp constants
+into the plug-in namespace, and also import the symbols gimp, pdb,
+register and main. This should be just about all any plug-in needs.
+
+You can use any of the PF_* constants below as parameter types, and an
+appropriate user interface element will be displayed when the plug-in
+is run in interactive mode. Note that the the PF_SPINNER and
+PF_SLIDER types expect a fifth element in their description tuple -- a
+3-tuple of the form (lower,upper,step), which defines the limits for
+the slider or spinner.
+
+If want to localize your plug-in, add an optional domain parameter to
+the register call. It can be the name of the translation domain or a
+tuple that consists of the translation domain and the directory where
+the translations are installed.
+"""
+
+import string as _string
+import math
+import gimp
+import gimpcolor
+from gimpenums import *
+pdb = gimp.pdb
+
+import gettext
+t = gettext.translation("gimp20-python", gimp.locale_directory, fallback=True)
+_ = t.ugettext
+
+class error(RuntimeError): pass
+class CancelError(RuntimeError): pass
+
+PF_INT8 = PDB_INT8
+PF_INT16 = PDB_INT16
+PF_INT32 = PDB_INT32
+PF_INT = PF_INT32
+PF_FLOAT = PDB_FLOAT
+PF_STRING = PDB_STRING
+PF_VALUE = PF_STRING
+#PF_INT8ARRAY = PDB_INT8ARRAY
+#PF_INT16ARRAY = PDB_INT16ARRAY
+#PF_INT32ARRAY = PDB_INT32ARRAY
+#PF_INTARRAY = PF_INT32ARRAY
+#PF_FLOATARRAY = PDB_FLOATARRAY
+#PF_STRINGARRAY = PDB_STRINGARRAY
+PF_COLOR = PDB_COLOR
+PF_COLOUR = PF_COLOR
+PF_ITEM = PDB_ITEM
+PF_DISPLAY = PDB_DISPLAY
+PF_IMAGE = PDB_IMAGE
+PF_LAYER = PDB_LAYER
+PF_CHANNEL = PDB_CHANNEL
+PF_DRAWABLE = PDB_DRAWABLE
+PF_VECTORS = PDB_VECTORS
+#PF_SELECTION = PDB_SELECTION
+#PF_BOUNDARY = PDB_BOUNDARY
+#PF_PATH = PDB_PATH
+#PF_STATUS = PDB_STATUS
+
+PF_TOGGLE = 1000
+PF_BOOL = PF_TOGGLE
+PF_SLIDER = 1001
+PF_SPINNER = 1002
+PF_ADJUSTMENT = PF_SPINNER
+
+PF_FONT = 1003
+PF_FILE = 1004
+PF_BRUSH = 1005
+PF_PATTERN = 1006
+PF_GRADIENT = 1007
+PF_RADIO = 1008
+PF_TEXT = 1009
+PF_PALETTE = 1010
+PF_FILENAME = 1011
+PF_DIRNAME = 1012
+PF_OPTION = 1013
+
+_type_mapping = {
+ PF_INT8 : PDB_INT8,
+ PF_INT16 : PDB_INT16,
+ PF_INT32 : PDB_INT32,
+ PF_FLOAT : PDB_FLOAT,
+ PF_STRING : PDB_STRING,
+ #PF_INT8ARRAY : PDB_INT8ARRAY,
+ #PF_INT16ARRAY : PDB_INT16ARRAY,
+ #PF_INT32ARRAY : PDB_INT32ARRAY,
+ #PF_FLOATARRAY : PDB_FLOATARRAY,
+ #PF_STRINGARRAY : PDB_STRINGARRAY,
+ PF_COLOR : PDB_COLOR,
+ PF_ITEM : PDB_ITEM,
+ PF_DISPLAY : PDB_DISPLAY,
+ PF_IMAGE : PDB_IMAGE,
+ PF_LAYER : PDB_LAYER,
+ PF_CHANNEL : PDB_CHANNEL,
+ PF_DRAWABLE : PDB_DRAWABLE,
+ PF_VECTORS : PDB_VECTORS,
+
+ PF_TOGGLE : PDB_INT32,
+ PF_SLIDER : PDB_FLOAT,
+ PF_SPINNER : PDB_INT32,
+
+ PF_FONT : PDB_STRING,
+ PF_FILE : PDB_STRING,
+ PF_BRUSH : PDB_STRING,
+ PF_PATTERN : PDB_STRING,
+ PF_GRADIENT : PDB_STRING,
+ PF_RADIO : PDB_STRING,
+ PF_TEXT : PDB_STRING,
+ PF_PALETTE : PDB_STRING,
+ PF_FILENAME : PDB_STRING,
+ PF_DIRNAME : PDB_STRING,
+ PF_OPTION : PDB_INT32,
+}
+
+_obj_mapping = {
+ PF_INT8 : int,
+ PF_INT16 : int,
+ PF_INT32 : int,
+ PF_FLOAT : float,
+ PF_STRING : str,
+ #PF_INT8ARRAY : list,
+ #PF_INT16ARRAY : list,
+ #PF_INT32ARRAY : list,
+ #PF_FLOATARRAY : list,
+ #PF_STRINGARRAY : list,
+ PF_COLOR : gimpcolor.RGB,
+ PF_ITEM : int,
+ PF_DISPLAY : gimp.Display,
+ PF_IMAGE : gimp.Image,
+ PF_LAYER : gimp.Layer,
+ PF_CHANNEL : gimp.Channel,
+ PF_DRAWABLE : gimp.Drawable,
+ PF_VECTORS : gimp.Vectors,
+
+ PF_TOGGLE : bool,
+ PF_SLIDER : float,
+ PF_SPINNER : int,
+
+ PF_FONT : str,
+ PF_FILE : str,
+ PF_BRUSH : str,
+ PF_PATTERN : str,
+ PF_GRADIENT : str,
+ PF_RADIO : str,
+ PF_TEXT : str,
+ PF_PALETTE : str,
+ PF_FILENAME : str,
+ PF_DIRNAME : str,
+ PF_OPTION : int,
+}
+
+_registered_plugins_ = {}
+
+def register(proc_name, blurb, help, author, copyright, date, label,
+ imagetypes, params, results, function,
+ menu=None, domain=None, on_query=None, on_run=None, run_mode_param=True):
+ """This is called to register a new plug-in."""
+
+ # First perform some sanity checks on the data
+ def letterCheck(str):
+ allowed = _string.letters + _string.digits + "_" + "-"
+ for ch in str:
+ if not ch in allowed:
+ return 0
+ else:
+ return 1
+
+ if not letterCheck(proc_name):
+ raise error, "procedure name contains illegal characters"
+
+ for ent in params:
+ if len(ent) < 4:
+ raise error, ("parameter definition must contain at least 4 "
+ "elements (%s given: %s)" % (len(ent), ent))
+
+ if type(ent[0]) != int:
+ raise error, "parameter types must be integers"
+
+ if not letterCheck(ent[1]):
+ raise error, "parameter name contains illegal characters"
+
+ for ent in results:
+ if len(ent) < 3:
+ raise error, ("result definition must contain at least 3 elements "
+ "(%s given: %s)" % (len(ent), ent))
+
+ if type(ent[0]) != type(42):
+ raise error, "result types must be integers"
+
+ if not letterCheck(ent[1]):
+ raise error, "result name contains illegal characters"
+
+ plugin_type = PLUGIN
+
+ if (not proc_name.startswith("python-") and
+ not proc_name.startswith("python_") and
+ not proc_name.startswith("extension-") and
+ not proc_name.startswith("extension_") and
+ not proc_name.startswith("plug-in-") and
+ not proc_name.startswith("plug_in_") and
+ not proc_name.startswith("file-") and
+ not proc_name.startswith("file_")):
+ proc_name = "python-fu-" + proc_name
+
+ # if menu is not given, derive it from label
+ need_compat_params = False
+ if menu is None and label:
+ fields = label.split("/")
+ if fields:
+ label = fields.pop()
+ menu = "/".join(fields)
+ need_compat_params = True
+
+ import warnings
+ message = ("%s: passing the full menu path for the menu label is "
+ "deprecated, use the 'menu' parameter instead"
+ % (proc_name))
+ warnings.warn(message, DeprecationWarning, 3)
+
+ if need_compat_params and plugin_type == PLUGIN:
+ file_params = [(PDB_STRING, "filename", "The name of the file", ""),
+ (PDB_STRING, "raw-filename", "The name of the file", "")]
+
+ if menu is None:
+ pass
+ elif menu.startswith("<Load>"):
+ params[0:0] = file_params
+ elif menu.startswith("<Image>") or menu.startswith("<Save>"):
+ params.insert(0, (PDB_IMAGE, "image", "Input image", None))
+ params.insert(1, (PDB_DRAWABLE, "drawable", "Input drawable", None))
+ if menu.startswith("<Save>"):
+ params[2:2] = file_params
+
+ _registered_plugins_[proc_name] = (blurb, help, author, copyright,
+ date, label, imagetypes,
+ plugin_type, params, results,
+ function, menu, domain,
+ on_query, on_run, run_mode_param)
+
+def _query():
+ for plugin in _registered_plugins_.keys():
+ (blurb, help, author, copyright, date,
+ label, imagetypes, plugin_type,
+ params, results, function, menu, domain,
+ on_query, on_run, has_param_run_mode) = _registered_plugins_[plugin]
+
+ def make_params(params):
+ return [(_type_mapping[x[0]],
+ x[1],
+ _string.replace(x[2], "_", "")) for x in params]
+
+ params = make_params(params)
+ # add the run mode argument ...
+ if has_param_run_mode:
+ params.insert(0, (PDB_INT32, "run-mode",
+ "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }"))
+
+ results = make_params(results)
+
+ if domain:
+ try:
+ (domain, locale_dir) = domain
+ gimp.domain_register(domain, locale_dir)
+ except ValueError:
+ gimp.domain_register(domain)
+
+ gimp.install_procedure(plugin, blurb, help, author, copyright,
+ date, label, imagetypes, plugin_type,
+ params, results)
+
+ if menu:
+ gimp.menu_register(plugin, menu)
+ if on_query:
+ on_query()
+
+def _get_defaults(proc_name):
+ import gimpshelf
+
+ (blurb, help, author, copyright, date,
+ label, imagetypes, plugin_type,
+ params, results, function, menu, domain,
+ on_query, on_run, has_run_mode) = _registered_plugins_[proc_name]
+
+ key = "python-fu-save--" + proc_name
+
+ if gimpshelf.shelf.has_key(key):
+ return gimpshelf.shelf[key]
+ else:
+ # return the default values
+ return [x[3] for x in params]
+
+def _set_defaults(proc_name, defaults):
+ import gimpshelf
+
+ key = "python-fu-save--" + proc_name
+ gimpshelf.shelf[key] = defaults
+
+def _interact(proc_name, start_params):
+ (blurb, help, author, copyright, date,
+ label, imagetypes, plugin_type,
+ params, results, function, menu, domain,
+ on_query, on_run, has_run_mode) = _registered_plugins_[proc_name]
+
+ def run_script(run_params):
+ params = start_params + tuple(run_params)
+ _set_defaults(proc_name, params)
+ return apply(function, params)
+
+ params = params[len(start_params):]
+
+ # short circuit for no parameters ...
+ if len(params) == 0:
+ return run_script([])
+
+ import pygtk
+ pygtk.require('2.0')
+
+ import gimpui
+ import gtk
+# import pango
+ gimpui.gimp_ui_init ()
+
+ defaults = _get_defaults(proc_name)
+ defaults = defaults[len(start_params):]
+
+ class EntryValueError(Exception):
+ pass
+
+ def warning_dialog(parent, primary, secondary=None):
+ dlg = gtk.MessageDialog(parent, gtk.DIALOG_DESTROY_WITH_PARENT,
+ gtk.MESSAGE_WARNING, gtk.BUTTONS_CLOSE,
+ primary)
+ if secondary:
+ dlg.format_secondary_text(secondary)
+ dlg.run()
+ dlg.destroy()
+
+ def error_dialog(parent, proc_name):
+ import sys, traceback
+
+ exc_str = exc_only_str = _("Missing exception information")
+
+ try:
+ etype, value, tb = sys.exc_info()
+ exc_str = "".join(traceback.format_exception(etype, value, tb))
+ exc_only_str = "".join(traceback.format_exception_only(etype, value))
+ finally:
+ etype = value = tb = None
+
+ title = _("An error occurred running %s") % proc_name
+ dlg = gtk.MessageDialog(parent, gtk.DIALOG_DESTROY_WITH_PARENT,
+ gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE,
+ title)
+ dlg.format_secondary_text(exc_only_str)
+
+ alignment = gtk.Alignment(0.0, 0.0, 1.0, 1.0)
+ alignment.set_padding(0, 0, 12, 12)
+ dlg.vbox.pack_start(alignment)
+ alignment.show()
+
+ expander = gtk.Expander(_("_More Information"));
+ expander.set_use_underline(True)
+ expander.set_spacing(6)
+ alignment.add(expander)
+ expander.show()
+
+ scrolled = gtk.ScrolledWindow()
+ scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ scrolled.set_size_request(-1, 200)
+ expander.add(scrolled)
+ scrolled.show()
+
+
+ label = gtk.Label(exc_str)
+ label.set_alignment(0.0, 0.0)
+ label.set_padding(6, 6)
+ label.set_selectable(True)
+ scrolled.add_with_viewport(label)
+ label.show()
+
+ def response(widget, id):
+ widget.destroy()
+
+ dlg.connect("response", response)
+ dlg.set_resizable(True)
+ dlg.show()
+
+ # define a mapping of param types to edit objects ...
+ class StringEntry(gtk.Entry):
+ def __init__(self, default=""):
+ gtk.Entry.__init__(self)
+ self.set_text(str(default))
+ self.set_activates_default(True)
+
+ def get_value(self):
+ return self.get_text()
+
+ class TextEntry(gtk.ScrolledWindow):
+ def __init__ (self, default=""):
+ gtk.ScrolledWindow.__init__(self)
+ self.set_shadow_type(gtk.SHADOW_IN)
+
+ self.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ self.set_size_request(100, -1)
+
+ self.view = gtk.TextView()
+ self.add(self.view)
+ self.view.show()
+
+ self.buffer = self.view.get_buffer()
+
+ self.set_value(str(default))
+
+ def set_value(self, text):
+ self.buffer.set_text(text)
+
+ def get_value(self):
+ return self.buffer.get_text(self.buffer.get_start_iter(),
+ self.buffer.get_end_iter())
+
+ class IntEntry(StringEntry):
+ def get_value(self):
+ try:
+ return int(self.get_text())
+ except ValueError, e:
+ raise EntryValueError, e.args
+
+ class FloatEntry(StringEntry):
+ def get_value(self):
+ try:
+ return float(self.get_text())
+ except ValueError, e:
+ raise EntryValueError, e.args
+
+# class ArrayEntry(StringEntry):
+# def get_value(self):
+# return eval(self.get_text(), {}, {})
+
+
+ def precision(step):
+ # calculate a reasonable precision from a given step size
+ if math.fabs(step) >= 1.0 or step == 0.0:
+ digits = 0
+ else:
+ digits = abs(math.floor(math.log10(math.fabs(step))));
+ if digits > 20:
+ digits = 20
+ return int(digits)
+
+ class SliderEntry(gtk.HScale):
+ # bounds is (upper, lower, step)
+ def __init__(self, default=0, bounds=(0, 100, 5)):
+ step = bounds[2]
+ self.adj = gtk.Adjustment(default, bounds[0], bounds[1],
+ step, 10 * step, 0)
+ gtk.HScale.__init__(self, self.adj)
+ self.set_digits(precision(step))
+
+ def get_value(self):
+ return self.adj.value
+
+ class SpinnerEntry(gtk.SpinButton):
+ # bounds is (upper, lower, step)
+ def __init__(self, default=0, bounds=(0, 100, 5)):
+ step = bounds[2]
+ self.adj = gtk.Adjustment(default, bounds[0], bounds[1],
+ step, 10 * step, 0)
+ gtk.SpinButton.__init__(self, self.adj, step, precision(step))
+
+ class ToggleEntry(gtk.ToggleButton):
+ def __init__(self, default=0):
+ gtk.ToggleButton.__init__(self)
+
+ self.label = gtk.Label(_("No"))
+ self.add(self.label)
+ self.label.show()
+
+ self.connect("toggled", self.changed)
+
+ self.set_active(default)
+
+ def changed(self, tog):
+ if tog.get_active():
+ self.label.set_text(_("Yes"))
+ else:
+ self.label.set_text(_("No"))
+
+ def get_value(self):
+ return self.get_active()
+
+ class RadioEntry(gtk.VBox):
+ def __init__(self, default=0, items=((_("Yes"), 1), (_("No"), 0))):
+ gtk.VBox.__init__(self, homogeneous=False, spacing=2)
+
+ button = None
+
+ for (label, value) in items:
+ button = gtk.RadioButton(button, label)
+ self.pack_start(button)
+ button.show()
+
+ button.connect("toggled", self.changed, value)
+
+ if value == default:
+ button.set_active(True)
+ self.active_value = value
+
+ def changed(self, radio, value):
+ if radio.get_active():
+ self.active_value = value
+
+ def get_value(self):
+ return self.active_value
+
+ class ComboEntry(gtk.ComboBox):
+ def __init__(self, default=0, items=()):
+ store = gtk.ListStore(str)
+ for item in items:
+ store.append([item])
+
+ gtk.ComboBox.__init__(self, model=store)
+
+ cell = gtk.CellRendererText()
+ self.pack_start(cell)
+ self.set_attributes(cell, text=0)
+
+ self.set_active(default)
+
+ def get_value(self):
+ return self.get_active()
+
+ def FileSelector(default="", title=None):
+ # FIXME: should this be os.path.separator? If not, perhaps explain why?
+ if default and default.endswith("/"):
+ if default == "/": default = ""
+ return DirnameSelector(default)
+ else:
+ return FilenameSelector(default, title=title, save_mode=False)
+
+ class FilenameSelector(gtk.HBox):
+ #gimpfu.FileChooserButton
+ def __init__(self, default, save_mode=True, title=None):
+ super(FilenameSelector, self).__init__()
+ if not title:
+ self.title = _("Python-Fu File Selection")
+ else:
+ self.title = title
+ self.save_mode = save_mode
+ box = self
+ self.entry = gtk.Entry()
+ image = gtk.Image()
+ image.set_from_stock(gtk.STOCK_FILE, gtk.ICON_SIZE_BUTTON)
+ self.button = gtk.Button()
+ self.button.set_image(image)
+ box.pack_start(self.entry)
+ box.pack_start(self.button, expand=False)
+ self.button.connect("clicked", self.pick_file)
+ if default:
+ self.entry.set_text(default)
+
+ def show(self):
+ super(FilenameSelector, self).show()
+ self.button.show()
+ self.entry.show()
+
+ def pick_file(self, widget):
+ entry = self.entry
+ dialog = gtk.FileChooserDialog(
+ title=self.title,
+ action=(gtk.FILE_CHOOSER_ACTION_SAVE
+ if self.save_mode else
+ gtk.FILE_CHOOSER_ACTION_OPEN),
+ buttons=(gtk.STOCK_CANCEL,
+ gtk.RESPONSE_CANCEL,
+ gtk.STOCK_SAVE
+ if self.save_mode else
+ gtk.STOCK_OPEN,
+ gtk.RESPONSE_OK)
+ )
+ dialog.set_alternative_button_order ((gtk.RESPONSE_OK, gtk.RESPONSE_CANCEL))
+ dialog.show_all()
+ response = dialog.run()
+ if response == gtk.RESPONSE_OK:
+ entry.set_text(dialog.get_filename())
+ dialog.destroy()
+
+ def get_value(self):
+ return self.entry.get_text()
+
+
+ class DirnameSelector(gtk.FileChooserButton):
+ def __init__(self, default=""):
+ gtk.FileChooserButton.__init__(self,
+ _("Python-Fu Folder Selection"))
+ self.set_action(gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER)
+ if default:
+ self.set_filename(default)
+
+ def get_value(self):
+ return self.get_filename()
+
+ _edit_mapping = {
+ PF_INT8 : IntEntry,
+ PF_INT16 : IntEntry,
+ PF_INT32 : IntEntry,
+ PF_FLOAT : FloatEntry,
+ PF_STRING : StringEntry,
+ #PF_INT8ARRAY : ArrayEntry,
+ #PF_INT16ARRAY : ArrayEntry,
+ #PF_INT32ARRAY : ArrayEntry,
+ #PF_FLOATARRAY : ArrayEntry,
+ #PF_STRINGARRAY : ArrayEntry,
+ PF_COLOR : gimpui.ColorSelector,
+ PF_ITEM : IntEntry, # should handle differently ...
+ PF_IMAGE : gimpui.ImageSelector,
+ PF_LAYER : gimpui.LayerSelector,
+ PF_CHANNEL : gimpui.ChannelSelector,
+ PF_DRAWABLE : gimpui.DrawableSelector,
+ PF_VECTORS : gimpui.VectorsSelector,
+
+ PF_TOGGLE : ToggleEntry,
+ PF_SLIDER : SliderEntry,
+ PF_SPINNER : SpinnerEntry,
+ PF_RADIO : RadioEntry,
+ PF_OPTION : ComboEntry,
+
+ PF_FONT : gimpui.FontSelector,
+ PF_FILE : FileSelector,
+ PF_FILENAME : FilenameSelector,
+ PF_DIRNAME : DirnameSelector,
+ PF_BRUSH : gimpui.BrushSelector,
+ PF_PATTERN : gimpui.PatternSelector,
+ PF_GRADIENT : gimpui.GradientSelector,
+ PF_PALETTE : gimpui.PaletteSelector,
+ PF_TEXT : TextEntry
+ }
+
+ if on_run:
+ on_run()
+
+ dialog = gimpui.Dialog(proc_name, "python-fu", None, 0, None, proc_name,
+ (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
+ gtk.STOCK_OK, gtk.RESPONSE_OK))
+
+ dialog.set_alternative_button_order((gtk.RESPONSE_OK, gtk.RESPONSE_CANCEL))
+
+ dialog.set_transient()
+
+ vbox = gtk.VBox(False, 12)
+ vbox.set_border_width(12)
+ dialog.vbox.pack_start(vbox)
+ vbox.show()
+
+ if blurb:
+ if domain:
+ try:
+ (domain, locale_dir) = domain
+ trans = gettext.translation(domain, locale_dir, fallback=True)
+ except ValueError:
+ trans = gettext.translation(domain, fallback=True)
+ blurb = trans.ugettext(blurb)
+ box = gimpui.HintBox(blurb)
+ vbox.pack_start(box, expand=False)
+ box.show()
+
+ table = gtk.Table(len(params), 2, False)
+ table.set_row_spacings(6)
+ table.set_col_spacings(6)
+ vbox.pack_start(table, expand=False)
+ table.show()
+
+ def response(dlg, id):
+ if id == gtk.RESPONSE_OK:
+ dlg.set_response_sensitive(gtk.RESPONSE_OK, False)
+ dlg.set_response_sensitive(gtk.RESPONSE_CANCEL, False)
+
+ params = []
+
+ try:
+ for wid in edit_wids:
+ params.append(wid.get_value())
+ except EntryValueError:
+ warning_dialog(dialog, _("Invalid input for '%s'") % wid.desc)
+ else:
+ try:
+ dialog.res = run_script(params)
+ except CancelError:
+ pass
+ except Exception:
+ dlg.set_response_sensitive(gtk.RESPONSE_CANCEL, True)
+ error_dialog(dialog, proc_name)
+ raise
+
+ gtk.main_quit()
+
+ dialog.connect("response", response)
+
+ edit_wids = []
+ for i in range(len(params)):
+ pf_type = params[i][0]
+ name = params[i][1]
+ desc = params[i][2]
+ def_val = defaults[i]
+
+ label = gtk.Label(desc)
+ label.set_use_underline(True)
+ label.set_alignment(0.0, 0.5)
+ table.attach(label, 1, 2, i, i+1, xoptions=gtk.FILL)
+ label.show()
+
+ # Remove accelerator markers from tooltips
+ tooltip_text = desc.replace("_", "")
+
+ if pf_type in (PF_SPINNER, PF_SLIDER, PF_RADIO, PF_OPTION):
+ wid = _edit_mapping[pf_type](def_val, params[i][4])
+ elif pf_type in (PF_FILE, PF_FILENAME):
+ wid = _edit_mapping[pf_type](def_val, title= "%s - %s" %
+ (proc_name, tooltip_text))
+ else:
+ wid = _edit_mapping[pf_type](def_val)
+
+
+ label.set_mnemonic_widget(wid)
+
+ table.attach(wid, 2,3, i,i+1, yoptions=0)
+
+ if pf_type != PF_TEXT:
+ wid.set_tooltip_text(tooltip_text)
+ else:
+ # Attach tip to TextView, not to ScrolledWindow
+ wid.view.set_tooltip_text(tooltip_text)
+ wid.show()
+
+ wid.desc = desc
+ edit_wids.append(wid)
+
+ progress_vbox = gtk.VBox(False, 6)
+ vbox.pack_end(progress_vbox, expand=False)
+ progress_vbox.show()
+
+ progress = gimpui.ProgressBar()
+ progress_vbox.pack_start(progress)
+ progress.show()
+
+# progress_label = gtk.Label()
+# progress_label.set_alignment(0.0, 0.5)
+# progress_label.set_ellipsize(pango.ELLIPSIZE_MIDDLE)
+
+# attrs = pango.AttrList()
+# attrs.insert(pango.AttrStyle(pango.STYLE_ITALIC, 0, -1))
+# progress_label.set_attributes(attrs)
+
+# progress_vbox.pack_start(progress_label)
+# progress_label.show()
+
+ dialog.show()
+
+ gtk.main()
+
+ if hasattr(dialog, "res"):
+ res = dialog.res
+ dialog.destroy()
+ return res
+ else:
+ dialog.destroy()
+ raise CancelError
+
+def _run(proc_name, params):
+ run_mode = params[0]
+ func = _registered_plugins_[proc_name][10]
+
+ if run_mode == RUN_NONINTERACTIVE:
+ return apply(func, params[1:])
+
+ script_params = _registered_plugins_[proc_name][8]
+ has_param_run_mode = _registered_plugins_[proc_name][15]
+
+ min_args = 0
+ start_param_idx = 1 if has_param_run_mode else 0
+ if len(params) > start_param_idx:
+ for i in range(start_param_idx, len(params)):
+ param_type = _obj_mapping[script_params[i - start_param_idx][0]]
+ if not isinstance(params[i], param_type):
+ break
+
+ min_args = i
+
+ if len(script_params) > min_args:
+ start_params = params[:min_args + 1]
+
+ if run_mode == RUN_WITH_LAST_VALS:
+ default_params = _get_defaults(proc_name)
+ params = start_params + default_params[min_args:]
+ else:
+ params = start_params
+ else:
+ run_mode = RUN_NONINTERACTIVE
+
+ if run_mode == RUN_INTERACTIVE:
+ try:
+ res = _interact(proc_name, params[start_param_idx:])
+ except CancelError:
+ return
+ else:
+ res = apply(func, params[start_param_idx:])
+
+ gimp.displays_flush()
+
+ return res
+
+def main():
+ """This should be called after registering the plug-in."""
+ gimp.main(None, None, _query, _run)
+
+def fail(msg):
+ """Display an error message and quit"""
+ gimp.message(msg)
+ raise error, msg
+
+def N_(message):
+ return message