C:\GIMPPortable\App\gimp\lib\gimp\2.0\python\gimpfu.py__ C:\GIMPPortable\App\gimp\lib\gimp\2.0\python\gimpfu.py
1#   Gimp-Python - allows the writing of GIMP plug-ins in Python. 1#   Gimp-Python - allows the writing of GIMP plug-ins in Python.
2#   Copyright (C) 1997  James Henstridge <james@daa.com.au> 2#   Copyright (C) 1997  James Henstridge <james@daa.com.au>
3# 3#
4#   This program is free software: you can redistribute it and/or modify 4#   This program is free software: you can redistribute it and/or modify
5#   it under the terms of the GNU General Public License as published by 5#   it under the terms of the GNU General Public License as published by
6#   the Free Software Foundation; either version 3 of the License, or 6#   the Free Software Foundation; either version 3 of the License, or
7#   (at your option) any later version. 7#   (at your option) any later version.
8# 8#
9#   This program is distributed in the hope that it will be useful, 9#   This program is distributed in the hope that it will be useful,
10#   but WITHOUT ANY WARRANTY; without even the implied warranty of 10#   but WITHOUT ANY WARRANTY; without even the implied warranty of
11#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 11#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12#   GNU General Public License for more details. 12#   GNU General Public License for more details.
13# 13#
14#   You should have received a copy of the GNU General Public License 14#   You should have received a copy of the GNU General Public License
15#   along with this program.  If not, see <http://www.gnu.org/licenses/>. 15#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
16  16 
17"""Simple interface for writing GIMP plug-ins in Python. 17"""Simple interface for writing GIMP plug-ins in Python.
18  18 
19Instead of worrying about all the user interaction, saving last used 19Instead of worrying about all the user interaction, saving last used
20values and everything, the gimpfu module can take care of it for you. 20values and everything, the gimpfu module can take care of it for you.
21It provides a simple register() function that will register your 21It provides a simple register() function that will register your
22plug-in if needed, and cause your plug-in function to be called when 22plug-in if needed, and cause your plug-in function to be called when
23needed. 23needed.
24  24 
25Gimpfu will also handle showing a user interface for editing plug-in 25Gimpfu will also handle showing a user interface for editing plug-in
26parameters if the plug-in is called interactively, and will also save 26parameters if the plug-in is called interactively, and will also save
27the last used parameters, so the RUN_WITH_LAST_VALUES run_type will 27the last used parameters, so the RUN_WITH_LAST_VALUES run_type will
28work correctly.  It will also make sure that the displays are flushed 28work correctly.  It will also make sure that the displays are flushed
29on completion if the plug-in was run interactively. 29on completion if the plug-in was run interactively.
30  30 
31When registering the plug-in, you do not need to worry about 31When registering the plug-in, you do not need to worry about
32specifying the run_type parameter. 32specifying the run_type parameter.
33  33 
34A typical gimpfu plug-in would look like this: 34A typical gimpfu plug-in would look like this:
35  from gimpfu import * 35  from gimpfu import *
36  36 
37  def plugin_func(image, drawable, args): 37  def plugin_func(image, drawable, args):
38              # do what plugins do best 38              # do what plugins do best
39  register( 39  register(
40              "plugin_func", 40              "plugin_func",
41              "blurb", 41              "blurb",
42              "help message", 42              "help message",
43              "author", 43              "author",
44              "copyright", 44              "copyright",
45              "year", 45              "year",
46              "My plug-in", 46              "My plug-in",
47              "*", 47              "*",
48              [ 48              [
49                  (PF_IMAGE, "image", "Input image"), 49                  (PF_IMAGE, "image", "Input image"),
50                  (PF_DRAWABLE, "drawable", "Input drawable"), 50                  (PF_DRAWABLE, "drawable", "Input drawable"),
51                  (PF_STRING, "arg", "The argument", "default-value") 51                  (PF_STRING, "arg", "The argument", "default-value")
52              ], 52              ],
53              [], 53              [],
54              plugin_func, menu="<Image>/Somewhere") 54              plugin_func, menu="<Image>/Somewhere")
55  main() 55  main()
56  56 
57The call to "from gimpfu import *" will import all the gimp constants 57The call to "from gimpfu import *" will import all the gimp constants
58into the plug-in namespace, and also import the symbols gimp, pdb, 58into the plug-in namespace, and also import the symbols gimp, pdb,
59register and main.  This should be just about all any plug-in needs. 59register and main.  This should be just about all any plug-in needs.
60  60 
61You can use any of the PF_* constants below as parameter types, and an 61You can use any of the PF_* constants below as parameter types, and an
62appropriate user interface element will be displayed when the plug-in 62appropriate user interface element will be displayed when the plug-in
63is run in interactive mode.  Note that the the PF_SPINNER and 63is run in interactive mode.  Note that the the PF_SPINNER and
64PF_SLIDER types expect a fifth element in their description tuple -- a 64PF_SLIDER types expect a fifth element in their description tuple -- a
653-tuple of the form (lower,upper,step), which defines the limits for 653-tuple of the form (lower,upper,step), which defines the limits for
66the slider or spinner. 66the slider or spinner.
67  67 
68If want to localize your plug-in, add an optional domain parameter to 68If want to localize your plug-in, add an optional domain parameter to
69the register call. It can be the name of the translation domain or a 69the register call. It can be the name of the translation domain or a
70tuple that consists of the translation domain and the directory where 70tuple that consists of the translation domain and the directory where
71the translations are installed. 71the translations are installed.
72""" 72"""
73  73 
74import string as _string 74import string as _string
75import math 75import math
76import gimp 76import gimp
77import gimpcolor 77import gimpcolor
78from gimpenums import * 78from gimpenums import *
79pdb = gimp.pdb 79pdb = gimp.pdb
80  80 
81import gettext 81import gettext
82t = gettext.translation("gimp20-python", gimp.locale_directory, fallback=True) 82t = gettext.translation("gimp20-python", gimp.locale_directory, fallback=True)
83_ = t.ugettext 83_ = t.ugettext
84  84 
85class error(RuntimeError): pass 85class error(RuntimeError): pass
86class CancelError(RuntimeError): pass 86class CancelError(RuntimeError): pass
87  87 
88PF_INT8        = PDB_INT8 88PF_INT8        = PDB_INT8
89PF_INT16       = PDB_INT16 89PF_INT16       = PDB_INT16
90PF_INT32       = PDB_INT32 90PF_INT32       = PDB_INT32
91PF_INT         = PF_INT32 91PF_INT         = PF_INT32
92PF_FLOAT       = PDB_FLOAT 92PF_FLOAT       = PDB_FLOAT
93PF_STRING      = PDB_STRING 93PF_STRING      = PDB_STRING
94PF_VALUE       = PF_STRING 94PF_VALUE       = PF_STRING
95#PF_INT8ARRAY   = PDB_INT8ARRAY 95#PF_INT8ARRAY   = PDB_INT8ARRAY
96#PF_INT16ARRAY  = PDB_INT16ARRAY 96#PF_INT16ARRAY  = PDB_INT16ARRAY
97#PF_INT32ARRAY  = PDB_INT32ARRAY 97#PF_INT32ARRAY  = PDB_INT32ARRAY
98#PF_INTARRAY    = PF_INT32ARRAY 98#PF_INTARRAY    = PF_INT32ARRAY
99#PF_FLOATARRAY  = PDB_FLOATARRAY 99#PF_FLOATARRAY  = PDB_FLOATARRAY
100#PF_STRINGARRAY = PDB_STRINGARRAY 100#PF_STRINGARRAY = PDB_STRINGARRAY
101PF_COLOR       = PDB_COLOR 101PF_COLOR       = PDB_COLOR
102PF_COLOUR      = PF_COLOR 102PF_COLOUR      = PF_COLOR
103PF_ITEM        = PDB_ITEM 103PF_ITEM        = PDB_ITEM
104PF_DISPLAY     = PDB_DISPLAY 104PF_DISPLAY     = PDB_DISPLAY
105PF_IMAGE       = PDB_IMAGE 105PF_IMAGE       = PDB_IMAGE
106PF_LAYER       = PDB_LAYER 106PF_LAYER       = PDB_LAYER
107PF_CHANNEL     = PDB_CHANNEL 107PF_CHANNEL     = PDB_CHANNEL
108PF_DRAWABLE    = PDB_DRAWABLE 108PF_DRAWABLE    = PDB_DRAWABLE
109PF_VECTORS     = PDB_VECTORS 109PF_VECTORS     = PDB_VECTORS
110#PF_SELECTION   = PDB_SELECTION 110#PF_SELECTION   = PDB_SELECTION
111#PF_BOUNDARY    = PDB_BOUNDARY 111#PF_BOUNDARY    = PDB_BOUNDARY
112#PF_PATH        = PDB_PATH 112#PF_PATH        = PDB_PATH
113#PF_STATUS      = PDB_STATUS 113#PF_STATUS      = PDB_STATUS
114  114 
115PF_TOGGLE      = 1000 115PF_TOGGLE      = 1000
116PF_BOOL        = PF_TOGGLE 116PF_BOOL        = PF_TOGGLE
117PF_SLIDER      = 1001 117PF_SLIDER      = 1001
118PF_SPINNER     = 1002 118PF_SPINNER     = 1002
119PF_ADJUSTMENT  = PF_SPINNER 119PF_ADJUSTMENT  = PF_SPINNER
120  120 
121PF_FONT        = 1003 121PF_FONT        = 1003
122PF_FILE        = 1004 122PF_FILE        = 1004
123PF_BRUSH       = 1005 123PF_BRUSH       = 1005
124PF_PATTERN     = 1006 124PF_PATTERN     = 1006
125PF_GRADIENT    = 1007 125PF_GRADIENT    = 1007
126PF_RADIO       = 1008 126PF_RADIO       = 1008
127PF_TEXT        = 1009 127PF_TEXT        = 1009
128PF_PALETTE     = 1010 128PF_PALETTE     = 1010
129PF_FILENAME    = 1011 129PF_FILENAME    = 1011
130PF_DIRNAME     = 1012 130PF_DIRNAME     = 1012
131PF_OPTION      = 1013 131PF_OPTION      = 1013
132  132 
133_type_mapping = { 133_type_mapping = {
134    PF_INT8        : PDB_INT8, 134    PF_INT8        : PDB_INT8,
135    PF_INT16       : PDB_INT16, 135    PF_INT16       : PDB_INT16,
136    PF_INT32       : PDB_INT32, 136    PF_INT32       : PDB_INT32,
137    PF_FLOAT       : PDB_FLOAT, 137    PF_FLOAT       : PDB_FLOAT,
138    PF_STRING      : PDB_STRING, 138    PF_STRING      : PDB_STRING,
139    #PF_INT8ARRAY   : PDB_INT8ARRAY, 139    #PF_INT8ARRAY   : PDB_INT8ARRAY,
140    #PF_INT16ARRAY  : PDB_INT16ARRAY, 140    #PF_INT16ARRAY  : PDB_INT16ARRAY,
141    #PF_INT32ARRAY  : PDB_INT32ARRAY, 141    #PF_INT32ARRAY  : PDB_INT32ARRAY,
142    #PF_FLOATARRAY  : PDB_FLOATARRAY, 142    #PF_FLOATARRAY  : PDB_FLOATARRAY,
143    #PF_STRINGARRAY : PDB_STRINGARRAY, 143    #PF_STRINGARRAY : PDB_STRINGARRAY,
144    PF_COLOR       : PDB_COLOR, 144    PF_COLOR       : PDB_COLOR,
145    PF_ITEM        : PDB_ITEM, 145    PF_ITEM        : PDB_ITEM,
146    PF_DISPLAY     : PDB_DISPLAY, 146    PF_DISPLAY     : PDB_DISPLAY,
147    PF_IMAGE       : PDB_IMAGE, 147    PF_IMAGE       : PDB_IMAGE,
148    PF_LAYER       : PDB_LAYER, 148    PF_LAYER       : PDB_LAYER,
149    PF_CHANNEL     : PDB_CHANNEL, 149    PF_CHANNEL     : PDB_CHANNEL,
150    PF_DRAWABLE    : PDB_DRAWABLE, 150    PF_DRAWABLE    : PDB_DRAWABLE,
151    PF_VECTORS     : PDB_VECTORS, 151    PF_VECTORS     : PDB_VECTORS,
152  152 
153    PF_TOGGLE      : PDB_INT32, 153    PF_TOGGLE      : PDB_INT32,
154    PF_SLIDER      : PDB_FLOAT, 154    PF_SLIDER      : PDB_FLOAT,
155    PF_SPINNER     : PDB_INT32, 155    PF_SPINNER     : PDB_INT32,
156  156 
157    PF_FONT        : PDB_STRING, 157    PF_FONT        : PDB_STRING,
158    PF_FILE        : PDB_STRING, 158    PF_FILE        : PDB_STRING,
159    PF_BRUSH       : PDB_STRING, 159    PF_BRUSH       : PDB_STRING,
160    PF_PATTERN     : PDB_STRING, 160    PF_PATTERN     : PDB_STRING,
161    PF_GRADIENT    : PDB_STRING, 161    PF_GRADIENT    : PDB_STRING,
162    PF_RADIO       : PDB_STRING, 162    PF_RADIO       : PDB_STRING,
163    PF_TEXT        : PDB_STRING, 163    PF_TEXT        : PDB_STRING,
164    PF_PALETTE     : PDB_STRING, 164    PF_PALETTE     : PDB_STRING,
165    PF_FILENAME    : PDB_STRING, 165    PF_FILENAME    : PDB_STRING,
166    PF_DIRNAME     : PDB_STRING, 166    PF_DIRNAME     : PDB_STRING,
167    PF_OPTION      : PDB_INT32, 167    PF_OPTION      : PDB_INT32,
168} 168}
169  169 
170_obj_mapping = { 170_obj_mapping = {
171    PF_INT8        : int, 171    PF_INT8        : int,
172    PF_INT16       : int, 172    PF_INT16       : int,
173    PF_INT32       : int, 173    PF_INT32       : int,
174    PF_FLOAT       : float, 174    PF_FLOAT       : float,
175    PF_STRING      : str, 175    PF_STRING      : str,
176    #PF_INT8ARRAY   : list, 176    #PF_INT8ARRAY   : list,
177    #PF_INT16ARRAY  : list, 177    #PF_INT16ARRAY  : list,
178    #PF_INT32ARRAY  : list, 178    #PF_INT32ARRAY  : list,
179    #PF_FLOATARRAY  : list, 179    #PF_FLOATARRAY  : list,
180    #PF_STRINGARRAY : list, 180    #PF_STRINGARRAY : list,
181    PF_COLOR       : gimpcolor.RGB, 181    PF_COLOR       : gimpcolor.RGB,
182    PF_ITEM        : int, 182    PF_ITEM        : int,
183    PF_DISPLAY     : gimp.Display, 183    PF_DISPLAY     : gimp.Display,
184    PF_IMAGE       : gimp.Image, 184    PF_IMAGE       : gimp.Image,
185    PF_LAYER       : gimp.Layer, 185    PF_LAYER       : gimp.Layer,
186    PF_CHANNEL     : gimp.Channel, 186    PF_CHANNEL     : gimp.Channel,
187    PF_DRAWABLE    : gimp.Drawable, 187    PF_DRAWABLE    : gimp.Drawable,
188    PF_VECTORS     : gimp.Vectors, 188    PF_VECTORS     : gimp.Vectors,
189  189 
190    PF_TOGGLE      : bool, 190    PF_TOGGLE      : bool,
191    PF_SLIDER      : float, 191    PF_SLIDER      : float,
192    PF_SPINNER     : int, 192    PF_SPINNER     : int,
193  193 
194    PF_FONT        : str, 194    PF_FONT        : str,
195    PF_FILE        : str, 195    PF_FILE        : str,
196    PF_BRUSH       : str, 196    PF_BRUSH       : str,
197    PF_PATTERN     : str, 197    PF_PATTERN     : str,
198    PF_GRADIENT    : str, 198    PF_GRADIENT    : str,
199    PF_RADIO       : str, 199    PF_RADIO       : str,
200    PF_TEXT        : str, 200    PF_TEXT        : str,
201    PF_PALETTE     : str, 201    PF_PALETTE     : str,
202    PF_FILENAME    : str, 202    PF_FILENAME    : str,
203    PF_DIRNAME     : str, 203    PF_DIRNAME     : str,
204    PF_OPTION      : int, 204    PF_OPTION      : int,
205} 205}
206  206 
207_registered_plugins_ = {} 207_registered_plugins_ = {}
208  208 
209def register(proc_name, blurb, help, author, copyright, date, label, 209def register(proc_name, blurb, help, author, copyright, date, label,
210             imagetypes, params, results, function, 210             imagetypes, params, results, function,
211             menu=None, domain=None, on_query=None, on_run=None): 211             menu=None, domain=None, on_query=None, on_run=None):
212    """This is called to register a new plug-in."""  212    """This is called to register a new plug-in.""" 
213  213 
214    # First perform some sanity checks on the data 214    # First perform some sanity checks on the data
215    def letterCheck(str): 215    def letterCheck(str):
216        allowed = _string.letters + _string.digits + "_" + "-"  216        allowed = _string.letters + _string.digits + "_" + "-" 
217        for ch in str: 217        for ch in str:
218            if not ch in allowed: 218            if not ch in allowed:
219        return 0 219        return 0
220        else: 220        else:
221            return 1 221            return 1
222  222 
223    if not letterCheck(proc_name): 223    if not letterCheck(proc_name):
224        raise error, "procedure name contains illegal characters"  224        raise error, "procedure name contains illegal characters" 
225  225 
226    for ent in params: 226    for ent in params:
227        if len(ent) < 4: 227        if len(ent) < 4:
228            raise error, ("parameter definition must contain at least 4 "  228            raise error, ("parameter definition must contain at least 4 " 
229                          "elements (%s given: %s)" % (len(ent), ent)) 229                          "elements (%s given: %s)" % (len(ent), ent))
230  230 
231        if type(ent[0]) != int: 231        if type(ent[0]) != int:
232            raise error, "parameter types must be integers"  232            raise error, "parameter types must be integers" 
233  233 
234        if not letterCheck(ent[1]): 234        if not letterCheck(ent[1]):
235            raise error, "parameter name contains illegal characters"  235            raise error, "parameter name contains illegal characters" 
236  236 
237    for ent in results: 237    for ent in results:
238        if len(ent) < 3: 238        if len(ent) < 3:
239            raise error, ("result definition must contain at least 3 elements "  239            raise error, ("result definition must contain at least 3 elements " 
240                          "(%s given: %s)" % (len(ent), ent)) 240                          "(%s given: %s)" % (len(ent), ent))
241  241 
242        if type(ent[0]) != type(42): 242        if type(ent[0]) != type(42):
243            raise error, "result types must be integers"  243            raise error, "result types must be integers" 
244  244 
245        if not letterCheck(ent[1]): 245        if not letterCheck(ent[1]):
246            raise error, "result name contains illegal characters"  246            raise error, "result name contains illegal characters" 
247  247 
248    plugin_type = PLUGIN 248    plugin_type = PLUGIN
249  249 
250    if (not proc_name.startswith("python-") and 250    if (not proc_name.startswith("python-") and
251        not proc_name.startswith("python_") and 251        not proc_name.startswith("python_") and
252        not proc_name.startswith("extension-") and 252        not proc_name.startswith("extension-") and
253        not proc_name.startswith("extension_") and 253        not proc_name.startswith("extension_") and
254        not proc_name.startswith("plug-in-") and 254        not proc_name.startswith("plug-in-") and
255        not proc_name.startswith("plug_in_") and 255        not proc_name.startswith("plug_in_") and
256        not proc_name.startswith("file-") and 256        not proc_name.startswith("file-") and
257        not proc_name.startswith("file_")): 257        not proc_name.startswith("file_")):
258           proc_name = "python-fu-" + proc_name 258           proc_name = "python-fu-" + proc_name
259  259 
260    # if menu is not given, derive it from label 260    # if menu is not given, derive it from label
261    need_compat_params = False 261    need_compat_params = False
262    if menu is None and label: 262    if menu is None and label:
263        fields = label.split("/") 263        fields = label.split("/")
264        if fields: 264        if fields:
265            label = fields.pop() 265            label = fields.pop()
266            menu = "/".join(fields) 266            menu = "/".join(fields)
267            need_compat_params = True 267            need_compat_params = True
268  268 
269            import warnings 269            import warnings
270            message = ("%s: passing the full menu path for the menu label is "  270            message = ("%s: passing the full menu path for the menu label is " 
271                       "deprecated, use the 'menu' parameter instead"  271                       "deprecated, use the 'menu' parameter instead" 
272                       % (proc_name)) 272                       % (proc_name))
273            warnings.warn(message, DeprecationWarning, 3) 273            warnings.warn(message, DeprecationWarning, 3)
274  274 
275        if need_compat_params and plugin_type == PLUGIN: 275        if need_compat_params and plugin_type == PLUGIN:
276            file_params = [(PDB_STRING, "filename", "The name of the file", ""), 276            file_params = [(PDB_STRING, "filename", "The name of the file", ""),
277                           (PDB_STRING, "raw-filename", "The name of the file", "")] 277                           (PDB_STRING, "raw-filename", "The name of the file", "")]
278  278 
279            if menu is None: 279            if menu is None:
280                pass 280                pass
281            elif menu.startswith("<Load>"): 281            elif menu.startswith("<Load>"):
282                params[0:0] = file_params 282                params[0:0] = file_params
283            elif menu.startswith("<Image>") or menu.startswith("<Save>"): 283            elif menu.startswith("<Image>") or menu.startswith("<Save>"):
284                params.insert(0, (PDB_IMAGE, "image", "Input image", None)) 284                params.insert(0, (PDB_IMAGE, "image", "Input image", None))
285                params.insert(1, (PDB_DRAWABLE, "drawable", "Input drawable", None)) 285                params.insert(1, (PDB_DRAWABLE, "drawable", "Input drawable", None))
286                if menu.startswith("<Save>"): 286                if menu.startswith("<Save>"):
287                    params[2:2] = file_params 287                    params[2:2] = file_params
288  288 
289    _registered_plugins_[proc_name] = (blurb, help, author, copyright, 289    _registered_plugins_[proc_name] = (blurb, help, author, copyright,
290                                       date, label, imagetypes, 290                                       date, label, imagetypes,
291                                       plugin_type, params, results, 291                                       plugin_type, params, results,
292                                       function, menu, domain, 292                                       function, menu, domain,
293                                       on_query, on_run) 293                                       on_query, on_run)
294  294 
295def _query(): 295def _query():
296    for plugin in _registered_plugins_.keys(): 296    for plugin in _registered_plugins_.keys():
297        (blurb, help, author, copyright, date, 297        (blurb, help, author, copyright, date,
298         label, imagetypes, plugin_type, 298         label, imagetypes, plugin_type,
299         params, results, function, menu, domain, 299         params, results, function, menu, domain,
300         on_query, on_run) = _registered_plugins_[plugin] 300         on_query, on_run) = _registered_plugins_[plugin]
301  301 
302        def make_params(params): 302        def make_params(params):
303            return [(_type_mapping[x[0]], 303            return [(_type_mapping[x[0]],
304                     x[1], 304                     x[1],
305                     _string.replace(x[2], "_", "")) for x in params] 305                     _string.replace(x[2], "_", "")) for x in params]
306  306 
307        params = make_params(params) 307        params = make_params(params)
308        # add the run mode argument ... 308        # add the run mode argument ...
309        params.insert(0, (PDB_INT32, "run-mode", 309        params.insert(0, (PDB_INT32, "run-mode",
310                                     "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }")) 310                                     "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }"))
311  311 
312        results = make_params(results) 312        results = make_params(results)
313  313 
314        if domain: 314        if domain:
315            try: 315            try:
316                (domain, locale_dir) = domain 316                (domain, locale_dir) = domain
317                gimp.domain_register(domain, locale_dir) 317                gimp.domain_register(domain, locale_dir)
318            except ValueError: 318            except ValueError:
319                gimp.domain_register(domain) 319                gimp.domain_register(domain)
320  320 
321        gimp.install_procedure(plugin, blurb, help, author, copyright, 321        gimp.install_procedure(plugin, blurb, help, author, copyright,
322                               date, label, imagetypes, plugin_type, 322                               date, label, imagetypes, plugin_type,
323                               params, results) 323                               params, results)
324  324 
325        if menu: 325        if menu:
326            gimp.menu_register(plugin, menu) 326            gimp.menu_register(plugin, menu)
327        if on_query: 327        if on_query:
328            on_query() 328            on_query()
329  329 
330def _get_defaults(proc_name): 330def _get_defaults(proc_name):
331    import gimpshelf 331    import gimpshelf
332  332 
333    (blurb, help, author, copyright, date, 333    (blurb, help, author, copyright, date,
334     label, imagetypes, plugin_type, 334     label, imagetypes, plugin_type,
335     params, results, function, menu, domain, 335     params, results, function, menu, domain,
336     on_query, on_run) = _registered_plugins_[proc_name] 336     on_query, on_run) = _registered_plugins_[proc_name]
337  337 
338    key = "python-fu-save--" + proc_name 338    key = "python-fu-save--" + proc_name
339  339 
340    if gimpshelf.shelf.has_key(key): 340    if gimpshelf.shelf.has_key(key):
341        return gimpshelf.shelf[key] 341        return gimpshelf.shelf[key]
342    else: 342    else:
343        # return the default values 343        # return the default values
344        return [x[3] for x in params] 344        return [x[3] for x in params]
345  345 
346def _set_defaults(proc_name, defaults): 346def _set_defaults(proc_name, defaults):
347    import gimpshelf 347    import gimpshelf
348  348 
349    key = "python-fu-save--" + proc_name 349    key = "python-fu-save--" + proc_name
350    gimpshelf.shelf[key] = defaults 350    gimpshelf.shelf[key] = defaults
351  351 
352def _interact(proc_name, start_params): 352def _interact(proc_name, start_params):
353    (blurb, help, author, copyright, date, 353    (blurb, help, author, copyright, date,
354     label, imagetypes, plugin_type, 354     label, imagetypes, plugin_type,
355     params, results, function, menu, domain, 355     params, results, function, menu, domain,
356     on_query, on_run) = _registered_plugins_[proc_name] 356     on_query, on_run) = _registered_plugins_[proc_name]
357  357 
358    def run_script(run_params): 358    def run_script(run_params):
359        params = start_params + tuple(run_params) 359        params = start_params + tuple(run_params)
360        _set_defaults(proc_name, params) 360        _set_defaults(proc_name, params)
361        return apply(function, params) 361        return apply(function, params)
362  362 
363    params = params[len(start_params):] 363    params = params[len(start_params):]
364  364 
365    # short circuit for no parameters ... 365    # short circuit for no parameters ...
366    if len(params) == 0: 366    if len(params) == 0:
367         return run_script([]) 367         return run_script([])
368  368 
369    import pygtk 369    import pygtk
370    pygtk.require('2.0') 370    pygtk.require('2.0')
371  371 
372    import gimpui 372    import gimpui
373    import gtk 373    import gtk
374#    import pango 374#    import pango
375  375 
376    defaults = _get_defaults(proc_name) 376    defaults = _get_defaults(proc_name)
377    defaults = defaults[len(start_params):] 377    defaults = defaults[len(start_params):]
378  378 
379    class EntryValueError(Exception): 379    class EntryValueError(Exception):
380        pass 380        pass
381  381 
382    def warning_dialog(parent, primary, secondary=None): 382    def warning_dialog(parent, primary, secondary=None):
383        dlg = gtk.MessageDialog(parent, gtk.DIALOG_DESTROY_WITH_PARENT, 383        dlg = gtk.MessageDialog(parent, gtk.DIALOG_DESTROY_WITH_PARENT,
384                                        gtk.MESSAGE_WARNING, gtk.BUTTONS_CLOSE, 384                                        gtk.MESSAGE_WARNING, gtk.BUTTONS_CLOSE,
385                                        primary) 385                                        primary)
386        if secondary: 386        if secondary:
387            dlg.format_secondary_text(secondary) 387            dlg.format_secondary_text(secondary)
388        dlg.run() 388        dlg.run()
389        dlg.destroy() 389        dlg.destroy()
390  390 
391    def error_dialog(parent, proc_name): 391    def error_dialog(parent, proc_name):
392        import sys, traceback 392        import sys, traceback
393  393 
394        exc_str = exc_only_str = _("Missing exception information") 394        exc_str = exc_only_str = _("Missing exception information")
395  395 
396        try: 396        try:
397            etype, value, tb = sys.exc_info() 397            etype, value, tb = sys.exc_info()
398            exc_str = "".join(traceback.format_exception(etype, value, tb)) 398            exc_str = "".join(traceback.format_exception(etype, value, tb))
399            exc_only_str = "".join(traceback.format_exception_only(etype, value)) 399            exc_only_str = "".join(traceback.format_exception_only(etype, value))
400        finally: 400        finally:
401            etype = value = tb = None 401            etype = value = tb = None
402  402 
403        title = _("An error occurred running %s") % proc_name 403        title = _("An error occurred running %s") % proc_name
404        dlg = gtk.MessageDialog(parent, gtk.DIALOG_DESTROY_WITH_PARENT, 404        dlg = gtk.MessageDialog(parent, gtk.DIALOG_DESTROY_WITH_PARENT,
405                                        gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE, 405                                        gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE,
406                                        title) 406                                        title)
407        dlg.format_secondary_text(exc_only_str) 407        dlg.format_secondary_text(exc_only_str)
408  408 
409        alignment = gtk.Alignment(0.0, 0.0, 1.0, 1.0) 409        alignment = gtk.Alignment(0.0, 0.0, 1.0, 1.0)
410        alignment.set_padding(0, 0, 12, 12) 410        alignment.set_padding(0, 0, 12, 12)
411        dlg.vbox.pack_start(alignment) 411        dlg.vbox.pack_start(alignment)
412        alignment.show() 412        alignment.show()
413  413 
414        expander = gtk.Expander(_("_More Information")); 414        expander = gtk.Expander(_("_More Information"));
415        expander.set_use_underline(True) 415        expander.set_use_underline(True)
416        expander.set_spacing(6) 416        expander.set_spacing(6)
417        alignment.add(expander) 417        alignment.add(expander)
418        expander.show() 418        expander.show()
419  419 
420        scrolled = gtk.ScrolledWindow() 420        scrolled = gtk.ScrolledWindow()
421        scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) 421        scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
422        scrolled.set_size_request(-1, 200) 422        scrolled.set_size_request(-1, 200)
423        expander.add(scrolled) 423        expander.add(scrolled)
424        scrolled.show() 424        scrolled.show()
425  425 
426  426 
427        label = gtk.Label(exc_str) 427        label = gtk.Label(exc_str)
428        label.set_alignment(0.0, 0.0) 428        label.set_alignment(0.0, 0.0)
429        label.set_padding(6, 6) 429        label.set_padding(6, 6)
430        label.set_selectable(True) 430        label.set_selectable(True)
431        scrolled.add_with_viewport(label) 431        scrolled.add_with_viewport(label)
432        label.show() 432        label.show()
433  433 
434        def response(widget, id): 434        def response(widget, id):
435            widget.destroy() 435            widget.destroy()
436  436 
437        dlg.connect("response", response) 437        dlg.connect("response", response)
438        dlg.set_resizable(True) 438        dlg.set_resizable(True)
439        dlg.show() 439        dlg.show()
440  440 
441    # define a mapping of param types to edit objects ... 441    # define a mapping of param types to edit objects ...
442    class StringEntry(gtk.Entry): 442    class StringEntry(gtk.Entry):
443        def __init__(self, default=""): 443        def __init__(self, default=""):
444            gtk.Entry.__init__(self) 444            gtk.Entry.__init__(self)
445            self.set_text(str(default)) 445            self.set_text(str(default))
446  446 
447        def get_value(self): 447        def get_value(self):
448            return self.get_text() 448            return self.get_text()
449  449 
450    class TextEntry(gtk.ScrolledWindow): 450    class TextEntry(gtk.ScrolledWindow):
451        def __init__ (self, default=""): 451        def __init__ (self, default=""):
452            gtk.ScrolledWindow.__init__(self) 452            gtk.ScrolledWindow.__init__(self)
453            self.set_shadow_type(gtk.SHADOW_IN) 453            self.set_shadow_type(gtk.SHADOW_IN)
454  454 
455            self.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) 455            self.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
456            self.set_size_request(100, -1) 456            self.set_size_request(100, -1)
457  457 
458            self.view = gtk.TextView() 458            self.view = gtk.TextView()
459            self.add(self.view) 459            self.add(self.view)
460            self.view.show() 460            self.view.show()
461  461 
462            self.buffer = self.view.get_buffer() 462            self.buffer = self.view.get_buffer()
463  463 
464            self.set_value(str(default)) 464            self.set_value(str(default))
465  465 
466        def set_value(self, text): 466        def set_value(self, text):
467            self.buffer.set_text(text) 467            self.buffer.set_text(text)
468  468 
469        def get_value(self): 469        def get_value(self):
470            return self.buffer.get_text(self.buffer.get_start_iter(), 470            return self.buffer.get_text(self.buffer.get_start_iter(),
471                                        self.buffer.get_end_iter()) 471                                        self.buffer.get_end_iter())
472  472 
473    class IntEntry(StringEntry): 473    class IntEntry(StringEntry):
474        def get_value(self): 474        def get_value(self):
475            try: 475            try:
476                return int(self.get_text()) 476                return int(self.get_text())
477            except ValueError, e: 477            except ValueError, e:
478                raise EntryValueError, e.args 478                raise EntryValueError, e.args
479  479 
480    class FloatEntry(StringEntry): 480    class FloatEntry(StringEntry):
481            def get_value(self): 481            def get_value(self):
482                try: 482                try:
483                    return float(self.get_text()) 483                    return float(self.get_text())
484                except ValueError, e: 484                except ValueError, e:
485                    raise EntryValueError, e.args 485                    raise EntryValueError, e.args
486  486 
487#    class ArrayEntry(StringEntry): 487#    class ArrayEntry(StringEntry):
488#            def get_value(self): 488#            def get_value(self):
489#                return eval(self.get_text(), {}, {}) 489#                return eval(self.get_text(), {}, {})
490  490 
491  491 
492    def precision(step): 492    def precision(step):
493        # calculate a reasonable precision from a given step size 493        # calculate a reasonable precision from a given step size
494        if math.fabs(step) >= 1.0 or step == 0.0: 494        if math.fabs(step) >= 1.0 or step == 0.0:
495            digits = 0 495            digits = 0
496        else: 496        else:
497            digits = abs(math.floor(math.log10(math.fabs(step)))); 497            digits = abs(math.floor(math.log10(math.fabs(step))));
498        if digits > 20: 498        if digits > 20:
499            digits = 20 499            digits = 20
500        return int(digits) 500        return int(digits)
501  501 
502    class SliderEntry(gtk.HScale): 502    class SliderEntry(gtk.HScale):
503        # bounds is (upper, lower, step) 503        # bounds is (upper, lower, step)
504        def __init__(self, default=0, bounds=(0, 100, 5)): 504        def __init__(self, default=0, bounds=(0, 100, 5)):
505            step = bounds[2] 505            step = bounds[2]
506            self.adj = gtk.Adjustment(default, bounds[0], bounds[1], 506            self.adj = gtk.Adjustment(default, bounds[0], bounds[1],
507                                      step, 10 * step, 0) 507                                      step, 10 * step, 0)
508            gtk.HScale.__init__(self, self.adj) 508            gtk.HScale.__init__(self, self.adj)
509            self.set_digits(precision(step)) 509            self.set_digits(precision(step))
510  510 
511        def get_value(self): 511        def get_value(self):
512            return self.adj.value 512            return self.adj.value
513  513 
514    class SpinnerEntry(gtk.SpinButton): 514    class SpinnerEntry(gtk.SpinButton):
515        # bounds is (upper, lower, step) 515        # bounds is (upper, lower, step)
516        def __init__(self, default=0, bounds=(0, 100, 5)): 516        def __init__(self, default=0, bounds=(0, 100, 5)):
517            step = bounds[2] 517            step = bounds[2]
518            self.adj = gtk.Adjustment(default, bounds[0], bounds[1], 518            self.adj = gtk.Adjustment(default, bounds[0], bounds[1],
519                                      step, 10 * step, 0) 519                                      step, 10 * step, 0)
520            gtk.SpinButton.__init__(self, self.adj, step, precision(step)) 520            gtk.SpinButton.__init__(self, self.adj, step, precision(step))
521  521 
522    class ToggleEntry(gtk.ToggleButton): 522    class ToggleEntry(gtk.ToggleButton):
523        def __init__(self, default=0): 523        def __init__(self, default=0):
524            gtk.ToggleButton.__init__(self) 524            gtk.ToggleButton.__init__(self)
525  525 
526            self.label = gtk.Label(_("No")) 526            self.label = gtk.Label(_("No"))
527            self.add(self.label) 527            self.add(self.label)
528            self.label.show() 528            self.label.show()
529  529 
530            self.connect("toggled", self.changed) 530            self.connect("toggled", self.changed)
531  531 
532            self.set_active(default) 532            self.set_active(default)
533  533 
534        def changed(self, tog): 534        def changed(self, tog):
535            if tog.get_active(): 535            if tog.get_active():
536                self.label.set_text(_("Yes")) 536                self.label.set_text(_("Yes"))
537            else: 537            else:
538                self.label.set_text(_("No")) 538                self.label.set_text(_("No"))
539  539 
540        def get_value(self): 540        def get_value(self):
541            return self.get_active() 541            return self.get_active()
542  542 
543    class RadioEntry(gtk.VBox): 543    class RadioEntry(gtk.VBox):
544        def __init__(self, default=0, items=((_("Yes"), 1), (_("No"), 0))): 544        def __init__(self, default=0, items=((_("Yes"), 1), (_("No"), 0))):
545            gtk.VBox.__init__(self, homogeneous=False, spacing=2) 545            gtk.VBox.__init__(self, homogeneous=False, spacing=2)
546  546 
547            button = None 547            button = None
548  548 
549            for (label, value) in items: 549            for (label, value) in items:
550                button = gtk.RadioButton(button, label) 550                button = gtk.RadioButton(button, label)
551                self.pack_start(button) 551                self.pack_start(button)
552                button.show() 552                button.show()
553  553 
554                button.connect("toggled", self.changed, value) 554                button.connect("toggled", self.changed, value)
555  555 
556                if value == default: 556                if value == default:
557                    button.set_active(True) 557                    button.set_active(True)
558                    self.active_value = value 558                    self.active_value = value
559  559 
560        def changed(self, radio, value): 560        def changed(self, radio, value):
561            if radio.get_active(): 561            if radio.get_active():
562                self.active_value = value 562                self.active_value = value
563  563 
564        def get_value(self): 564        def get_value(self):
565            return self.active_value 565            return self.active_value
566  566 
567    class ComboEntry(gtk.ComboBox): 567    class ComboEntry(gtk.ComboBox):
568        def __init__(self, default=0, items=()): 568        def __init__(self, default=0, items=()):
569            store = gtk.ListStore(str) 569            store = gtk.ListStore(str)
570            for item in items: 570            for item in items:
571                store.append([item]) 571                store.append([item])
572  572 
573            gtk.ComboBox.__init__(self, model=store) 573            gtk.ComboBox.__init__(self, model=store)
574  574 
575            cell = gtk.CellRendererText() 575            cell = gtk.CellRendererText()
576            self.pack_start(cell) 576            self.pack_start(cell)
577            self.set_attributes(cell, text=0) 577            self.set_attributes(cell, text=0)
578  578 
579            self.set_active(default) 579            self.set_active(default)
580  580 
581        def get_value(self): 581        def get_value(self):
582            return self.get_active() 582            return self.get_active()
583  583 
584    def FileSelector(default=""): 584    def FileSelector(default=""):
585        # FIXME: should this be os.path.separator?  If not, perhaps explain why? 585        # FIXME: should this be os.path.separator?  If not, perhaps explain why?
586        if default and default.endswith("/"): 586        if default and default.endswith("/"):
587            selector = DirnameSelector 587            selector = DirnameSelector
588            if default == "/": default = ""  588            if default == "/": default = "" 
589        else: 589        else:
590            selector = FilenameSelector 590            selector = FilenameSelector
591        return selector(default) 591        return selector(default)
592  592 
593    class FilenameSelector(gtk.FileChooserButton): 593    class FilenameSelector(gtk.FileChooserButton):
594        def __init__(self, default="", save_mode=False): 594        def __init__(self, default="", save_mode=False):
595            gtk.FileChooserButton.__init__(self, 595            gtk.FileChooserButton.__init__(self,
596                                           _("Python-Fu File Selection")) 596                                           _("Python-Fu File Selection"))
597            self.set_action(gtk.FILE_CHOOSER_ACTION_OPEN) 597            self.set_action(gtk.FILE_CHOOSER_ACTION_OPEN)
598            if default: 598            if default:
599                self.set_filename(default) 599                self.set_filename(default)
600  600 
601        def get_value(self): 601        def get_value(self):
602            return self.get_filename() 602            return self.get_filename()
603  603 
604    class DirnameSelector(gtk.FileChooserButton): 604    class DirnameSelector(gtk.FileChooserButton):
605        def __init__(self, default=""): 605        def __init__(self, default=""):
606            gtk.FileChooserButton.__init__(self, 606            gtk.FileChooserButton.__init__(self,
607                                           _("Python-Fu Folder Selection")) 607                                           _("Python-Fu Folder Selection"))
608            self.set_action(gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER) 608            self.set_action(gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER)
609            if default: 609            if default:
610                self.set_filename(default) 610                self.set_filename(default)
611  611 
612        def get_value(self): 612        def get_value(self):
613            return self.get_filename() 613            return self.get_filename()
614  614 
615    _edit_mapping = { 615    _edit_mapping = {
616            PF_INT8        : IntEntry, 616            PF_INT8        : IntEntry,
617            PF_INT16       : IntEntry, 617            PF_INT16       : IntEntry,
618            PF_INT32       : IntEntry, 618            PF_INT32       : IntEntry,
619            PF_FLOAT       : FloatEntry, 619            PF_FLOAT       : FloatEntry,
620            PF_STRING      : StringEntry, 620            PF_STRING      : StringEntry,
621            #PF_INT8ARRAY   : ArrayEntry, 621            #PF_INT8ARRAY   : ArrayEntry,
622            #PF_INT16ARRAY  : ArrayEntry, 622            #PF_INT16ARRAY  : ArrayEntry,
623            #PF_INT32ARRAY  : ArrayEntry, 623            #PF_INT32ARRAY  : ArrayEntry,
624            #PF_FLOATARRAY  : ArrayEntry, 624            #PF_FLOATARRAY  : ArrayEntry,
625            #PF_STRINGARRAY : ArrayEntry, 625            #PF_STRINGARRAY : ArrayEntry,
626            PF_COLOR       : gimpui.ColorSelector, 626            PF_COLOR       : gimpui.ColorSelector,
627            PF_ITEM        : IntEntry,  # should handle differently ... 627            PF_ITEM        : IntEntry,  # should handle differently ...
628            PF_IMAGE       : gimpui.ImageSelector, 628            PF_IMAGE       : gimpui.ImageSelector,
629            PF_LAYER       : gimpui.LayerSelector, 629            PF_LAYER       : gimpui.LayerSelector,
630            PF_CHANNEL     : gimpui.ChannelSelector, 630            PF_CHANNEL     : gimpui.ChannelSelector,
631            PF_DRAWABLE    : gimpui.DrawableSelector, 631            PF_DRAWABLE    : gimpui.DrawableSelector,
632            PF_VECTORS     : gimpui.VectorsSelector, 632            PF_VECTORS     : gimpui.VectorsSelector,
633  633 
634            PF_TOGGLE      : ToggleEntry, 634            PF_TOGGLE      : ToggleEntry,
635            PF_SLIDER      : SliderEntry, 635            PF_SLIDER      : SliderEntry,
636            PF_SPINNER     : SpinnerEntry, 636            PF_SPINNER     : SpinnerEntry,
637            PF_RADIO       : RadioEntry, 637            PF_RADIO       : RadioEntry,
638            PF_OPTION      : ComboEntry, 638            PF_OPTION      : ComboEntry,
639  639 
640            PF_FONT        : gimpui.FontSelector, 640            PF_FONT        : gimpui.FontSelector,
641            PF_FILE        : FileSelector, 641            PF_FILE        : FileSelector,
642            PF_FILENAME    : FilenameSelector, 642            PF_FILENAME    : FilenameSelector,
643            PF_DIRNAME     : DirnameSelector, 643            PF_DIRNAME     : DirnameSelector,
644            PF_BRUSH       : gimpui.BrushSelector, 644            PF_BRUSH       : gimpui.BrushSelector,
645            PF_PATTERN     : gimpui.PatternSelector, 645            PF_PATTERN     : gimpui.PatternSelector,
646            PF_GRADIENT    : gimpui.GradientSelector, 646            PF_GRADIENT    : gimpui.GradientSelector,
647            PF_PALETTE     : gimpui.PaletteSelector, 647            PF_PALETTE     : gimpui.PaletteSelector,
648            PF_TEXT        : TextEntry 648            PF_TEXT        : TextEntry
649    } 649    }
650  650 
651    if on_run: 651    if on_run:
652        on_run() 652        on_run()
653  653 
654    dialog = gimpui.Dialog(proc_name, "python-fu", None, 0, None, proc_name, 654    dialog = gimpui.Dialog(proc_name, "python-fu", None, 0, None, proc_name,
655                           (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, 655                           (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
656                            gtk.STOCK_OK, gtk.RESPONSE_OK)) 656                            gtk.STOCK_OK, gtk.RESPONSE_OK))
657  657 
658    dialog.set_alternative_button_order((gtk.RESPONSE_OK, gtk.RESPONSE_CANCEL)) 658    dialog.set_alternative_button_order((gtk.RESPONSE_OK, gtk.RESPONSE_CANCEL))
659  659 
660    dialog.set_transient() 660    dialog.set_transient()
661  661 
662    vbox = gtk.VBox(False, 12) 662    vbox = gtk.VBox(False, 12)
663    vbox.set_border_width(12) 663    vbox.set_border_width(12)
664    dialog.vbox.pack_start(vbox) 664    dialog.vbox.pack_start(vbox)
665    vbox.show() 665    vbox.show()
666  666 
667    if blurb: 667    if blurb:
668        if domain: 668        if domain:
669            try: 669            try:
670                (domain, locale_dir) = domain 670                (domain, locale_dir) = domain
671                trans = gettext.translation(domain, locale_dir, fallback=True) 671                trans = gettext.translation(domain, locale_dir, fallback=True)
672            except ValueError: 672            except ValueError:
673                trans = gettext.translation(domain, fallback=True) 673                trans = gettext.translation(domain, fallback=True)
674            blurb = trans.ugettext(blurb) 674            blurb = trans.ugettext(blurb)
675        box = gimpui.HintBox(blurb) 675        box = gimpui.HintBox(blurb)
676        vbox.pack_start(box, expand=False) 676        vbox.pack_start(box, expand=False)
677        box.show() 677        box.show()
678  678 
679    table = gtk.Table(len(params), 2, False) 679    table = gtk.Table(len(params), 5, False)
680    table.set_row_spacings(6) 680    table.set_row_spacings(6)
681    table.set_col_spacings(6) 681    table.set_col_spacings(6)
682    vbox.pack_start(table, expand=False) 682    vbox.pack_start(table, expand=False)
683    table.show() 683    table.show()
684  684 
685    def response(dlg, id): 685    def response(dlg, id):
686        if id == gtk.RESPONSE_OK: 686        if id == gtk.RESPONSE_OK:
687            dlg.set_response_sensitive(gtk.RESPONSE_OK, False) 687            dlg.set_response_sensitive(gtk.RESPONSE_OK, False)
688            dlg.set_response_sensitive(gtk.RESPONSE_CANCEL, False) 688            dlg.set_response_sensitive(gtk.RESPONSE_CANCEL, False)
689  689 
690            params = [] 690            params = []
691  691 
692            try: 692            try:
693                for wid in edit_wids: 693                for wid in edit_wids:
694                    params.append(wid.get_value()) 694                    params.append(wid.get_value())
695            except EntryValueError: 695            except EntryValueError:
696                warning_dialog(dialog, _("Invalid input for '%s'") % wid.desc) 696                warning_dialog(dialog, _("Invalid input for '%s'") % wid.desc)
697            else: 697            else:
698                try: 698                try:
699                    dialog.res = run_script(params) 699                    dialog.res = run_script(params)
700                except Exception: 700                except Exception:
701                    dlg.set_response_sensitive(gtk.RESPONSE_CANCEL, True) 701                    dlg.set_response_sensitive(gtk.RESPONSE_CANCEL, True)
702                    error_dialog(dialog, proc_name) 702                    error_dialog(dialog, proc_name)
703                    raise 703                    raise
704  704 
705        gtk.main_quit() 705        gtk.main_quit()
706  706 
707    dialog.connect("response", response) 707    dialog.connect("response", response)
708  708 
709    edit_wids = [] 709    edit_wids = []
  710    left=False
710    for i in range(len(params)): 711    for i in range(len(params)):
711        pf_type = params[i][0] 712        pf_type = params[i][0]
712        name = params[i][1] 713        name = params[i][1]
713        desc = params[i][2] 714        desc = params[i][2]
714        def_val = defaults[i] 715        def_val = defaults[i]
715  716 
716        label = gtk.Label(desc) 717        label = gtk.Label(desc)
717        label.set_use_underline(True) 718        label.set_use_underline(True)
718        label.set_alignment(0.0, 0.5) 719        label.set_alignment(0.0, 0.5)
719        table.attach(label, 1, 2, i, i+1, xoptions=gtk.FILL)  720        if not left: 
  721            table.attach(label, 1, 2, i, i+1, xoptions=gtk.FILL)
  722        else:
  723            table.attach(label, 3, 4, i-1, i, xoptions=gtk.FILL)
  724             
  725             
720        label.show() 726        label.show()
721  727 
722        if pf_type in (PF_SPINNER, PF_SLIDER, PF_RADIO, PF_OPTION): 728        if pf_type in (PF_SPINNER, PF_SLIDER, PF_RADIO, PF_OPTION):
723            wid = _edit_mapping[pf_type](def_val, params[i][4]) 729            wid = _edit_mapping[pf_type](def_val, params[i][4])
724        else: 730        else:
725            wid = _edit_mapping[pf_type](def_val) 731            wid = _edit_mapping[pf_type](def_val)
726  732 
727        label.set_mnemonic_widget(wid) 733        label.set_mnemonic_widget(wid)
728  734 
729        table.attach(wid, 2,3, i,i+1, yoptions=0)  735        if not left: 
  736            table.attach(wid, 2,3, i,i+1, yoptions=0)
  737            left=True
  738        else:
  739            table.attach(wid, 4,5, i-1,i, yoptions=0)
  740            left=False
  741             
730  742 
731        if pf_type != PF_TEXT: 743        if pf_type != PF_TEXT:
732            wid.set_tooltip_text(desc) 744            wid.set_tooltip_text(desc)
733        else: 745        else:
734            # Attach tip to TextView, not to ScrolledWindow 746            # Attach tip to TextView, not to ScrolledWindow
735            wid.view.set_tooltip_text(desc) 747            wid.view.set_tooltip_text(desc)
736        wid.show() 748        wid.show()
737  749 
738        wid.desc = desc 750        wid.desc = desc
739        edit_wids.append(wid) 751        edit_wids.append(wid)
740  752 
741    progress_vbox = gtk.VBox(False, 6) 753    progress_vbox = gtk.VBox(False, 6)
742    vbox.pack_end(progress_vbox, expand=False) 754    vbox.pack_end(progress_vbox, expand=False)
743    progress_vbox.show() 755    progress_vbox.show()
744  756 
745    progress = gimpui.ProgressBar() 757    progress = gimpui.ProgressBar()
746    progress_vbox.pack_start(progress) 758    progress_vbox.pack_start(progress)
747    progress.show() 759    progress.show()
748  760 
749#    progress_label = gtk.Label() 761#    progress_label = gtk.Label()
750#    progress_label.set_alignment(0.0, 0.5) 762#    progress_label.set_alignment(0.0, 0.5)
751#    progress_label.set_ellipsize(pango.ELLIPSIZE_MIDDLE) 763#    progress_label.set_ellipsize(pango.ELLIPSIZE_MIDDLE)
752  764 
753#    attrs = pango.AttrList() 765#    attrs = pango.AttrList()
754#    attrs.insert(pango.AttrStyle(pango.STYLE_ITALIC, 0, -1)) 766#    attrs.insert(pango.AttrStyle(pango.STYLE_ITALIC, 0, -1))
755#    progress_label.set_attributes(attrs) 767#    progress_label.set_attributes(attrs)
756  768 
757#    progress_vbox.pack_start(progress_label) 769#    progress_vbox.pack_start(progress_label)
758#    progress_label.show() 770#    progress_label.show()
759  771 
760    dialog.show() 772    dialog.show()
761  773 
762    gtk.main() 774    gtk.main()
763  775 
764    if hasattr(dialog, "res"): 776    if hasattr(dialog, "res"):
765        res = dialog.res 777        res = dialog.res
766        dialog.destroy() 778        dialog.destroy()
767        return res 779        return res
768    else: 780    else:
769        dialog.destroy() 781        dialog.destroy()
770        raise CancelError 782        raise CancelError
771  783 
772def _run(proc_name, params): 784def _run(proc_name, params):
773    run_mode = params[0] 785    run_mode = params[0]
774    func = _registered_plugins_[proc_name][10] 786    func = _registered_plugins_[proc_name][10]
775  787 
776    if run_mode == RUN_NONINTERACTIVE: 788    if run_mode == RUN_NONINTERACTIVE:
777        return apply(func, params[1:]) 789        return apply(func, params[1:])
778  790 
779    script_params = _registered_plugins_[proc_name][8] 791    script_params = _registered_plugins_[proc_name][8]
780  792 
781    min_args = 0 793    min_args = 0
782    if len(params) > 1: 794    if len(params) > 1:
783        for i in range(1, len(params)): 795        for i in range(1, len(params)):
784            param_type = _obj_mapping[script_params[i - 1][0]] 796            param_type = _obj_mapping[script_params[i - 1][0]]
785            if not isinstance(params[i], param_type): 797            if not isinstance(params[i], param_type):
786                break 798                break
787  799 
788        min_args = i 800        min_args = i
789  801 
790    if len(script_params) > min_args: 802    if len(script_params) > min_args:
791        start_params = params[:min_args + 1] 803        start_params = params[:min_args + 1]
792  804 
793        if run_mode == RUN_WITH_LAST_VALS: 805        if run_mode == RUN_WITH_LAST_VALS:
794            default_params = _get_defaults(proc_name) 806            default_params = _get_defaults(proc_name)
795            params = start_params + default_params[min_args:] 807            params = start_params + default_params[min_args:]
796        else: 808        else:
797            params = start_params 809            params = start_params
798    else: 810    else:
799       run_mode = RUN_NONINTERACTIVE 811       run_mode = RUN_NONINTERACTIVE
800  812 
801    if run_mode == RUN_INTERACTIVE: 813    if run_mode == RUN_INTERACTIVE:
802        try: 814        try:
803            res = _interact(proc_name, params[1:]) 815            res = _interact(proc_name, params[1:])
804        except CancelError: 816        except CancelError:
805            return 817            return
806    else: 818    else:
807        res = apply(func, params[1:]) 819        res = apply(func, params[1:])
808  820 
809    gimp.displays_flush() 821    gimp.displays_flush()
810  822 
811    return res 823    return res
812  824 
813def main(): 825def main():
814    """This should be called after registering the plug-in."""  826    """This should be called after registering the plug-in.""" 
815    gimp.main(None, None, _query, _run) 827    gimp.main(None, None, _query, _run)
816  828 
817def fail(msg): 829def fail(msg):
818    """Display an error message and quit"""  830    """Display an error message and quit""" 
819    gimp.message(msg) 831    gimp.message(msg)
820    raise error, msg 832    raise error, msg
821  833 
822def N_(message): 834def N_(message):
823    return message 835    return message
824  836