| 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 | |
| 19 | Instead of worrying about all the user interaction, saving last used |
19 | Instead of worrying about all the user interaction, saving last used |
| 20 | values and everything, the gimpfu module can take care of it for you. |
20 | values and everything, the gimpfu module can take care of it for you. |
| 21 | It provides a simple register() function that will register your |
21 | It provides a simple register() function that will register your |
| 22 | plug-in if needed, and cause your plug-in function to be called when |
22 | plug-in if needed, and cause your plug-in function to be called when |
| 23 | needed. |
23 | needed. |
| 24 | |
24 | |
| 25 | Gimpfu will also handle showing a user interface for editing plug-in |
25 | Gimpfu will also handle showing a user interface for editing plug-in |
| 26 | parameters if the plug-in is called interactively, and will also save |
26 | parameters if the plug-in is called interactively, and will also save |
| 27 | the last used parameters, so the RUN_WITH_LAST_VALUES run_type will |
27 | the last used parameters, so the RUN_WITH_LAST_VALUES run_type will |
| 28 | work correctly. It will also make sure that the displays are flushed |
28 | work correctly. It will also make sure that the displays are flushed |
| 29 | on completion if the plug-in was run interactively. |
29 | on completion if the plug-in was run interactively. |
| 30 | |
30 | |
| 31 | When registering the plug-in, you do not need to worry about |
31 | When registering the plug-in, you do not need to worry about |
| 32 | specifying the run_type parameter. |
32 | specifying the run_type parameter. |
| 33 | |
33 | |
| 34 | A typical gimpfu plug-in would look like this: |
34 | A 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 | |
| 57 | The call to "from gimpfu import *" will import all the gimp constants |
57 | The call to "from gimpfu import *" will import all the gimp constants |
| 58 | into the plug-in namespace, and also import the symbols gimp, pdb, |
58 | into the plug-in namespace, and also import the symbols gimp, pdb, |
| 59 | register and main. This should be just about all any plug-in needs. |
59 | register and main. This should be just about all any plug-in needs. |
| 60 | |
60 | |
| 61 | You can use any of the PF_* constants below as parameter types, and an |
61 | You can use any of the PF_* constants below as parameter types, and an |
| 62 | appropriate user interface element will be displayed when the plug-in |
62 | appropriate user interface element will be displayed when the plug-in |
| 63 | is run in interactive mode. Note that the the PF_SPINNER and |
63 | is run in interactive mode. Note that the the PF_SPINNER and |
| 64 | PF_SLIDER types expect a fifth element in their description tuple -- a |
64 | PF_SLIDER types expect a fifth element in their description tuple -- a |
| 65 | 3-tuple of the form (lower,upper,step), which defines the limits for |
65 | 3-tuple of the form (lower,upper,step), which defines the limits for |
| 66 | the slider or spinner. |
66 | the slider or spinner. |
| 67 | |
67 | |
| 68 | If want to localize your plug-in, add an optional domain parameter to |
68 | If want to localize your plug-in, add an optional domain parameter to |
| 69 | the register call. It can be the name of the translation domain or a |
69 | the register call. It can be the name of the translation domain or a |
| 70 | tuple that consists of the translation domain and the directory where |
70 | tuple that consists of the translation domain and the directory where |
| 71 | the translations are installed. |
71 | the translations are installed. |
| 72 | """ |
72 | """ |
| 73 | |
73 | |
| 74 | import string as _string |
74 | import string as _string |
| 75 | import math |
75 | import math |
| 76 | import gimp |
76 | import gimp |
| 77 | import gimpcolor |
77 | import gimpcolor |
| 78 | from gimpenums import * |
78 | from gimpenums import * |
| 79 | pdb = gimp.pdb |
79 | pdb = gimp.pdb |
| 80 | |
80 | |
| 81 | import gettext |
81 | import gettext |
| 82 | t = gettext.translation("gimp20-python", gimp.locale_directory, fallback=True) |
82 | t = gettext.translation("gimp20-python", gimp.locale_directory, fallback=True) |
| 83 | _ = t.ugettext |
83 | _ = t.ugettext |
| 84 | |
84 | |
| 85 | class error(RuntimeError): pass |
85 | class error(RuntimeError): pass |
| 86 | class CancelError(RuntimeError): pass |
86 | class CancelError(RuntimeError): pass |
| 87 | |
87 | |
| 88 | PF_INT8 = PDB_INT8 |
88 | PF_INT8 = PDB_INT8 |
| 89 | PF_INT16 = PDB_INT16 |
89 | PF_INT16 = PDB_INT16 |
| 90 | PF_INT32 = PDB_INT32 |
90 | PF_INT32 = PDB_INT32 |
| 91 | PF_INT = PF_INT32 |
91 | PF_INT = PF_INT32 |
| 92 | PF_FLOAT = PDB_FLOAT |
92 | PF_FLOAT = PDB_FLOAT |
| 93 | PF_STRING = PDB_STRING |
93 | PF_STRING = PDB_STRING |
| 94 | PF_VALUE = PF_STRING |
94 | PF_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 |
| 101 | PF_COLOR = PDB_COLOR |
101 | PF_COLOR = PDB_COLOR |
| 102 | PF_COLOUR = PF_COLOR |
102 | PF_COLOUR = PF_COLOR |
| 103 | PF_ITEM = PDB_ITEM |
103 | PF_ITEM = PDB_ITEM |
| 104 | PF_DISPLAY = PDB_DISPLAY |
104 | PF_DISPLAY = PDB_DISPLAY |
| 105 | PF_IMAGE = PDB_IMAGE |
105 | PF_IMAGE = PDB_IMAGE |
| 106 | PF_LAYER = PDB_LAYER |
106 | PF_LAYER = PDB_LAYER |
| 107 | PF_CHANNEL = PDB_CHANNEL |
107 | PF_CHANNEL = PDB_CHANNEL |
| 108 | PF_DRAWABLE = PDB_DRAWABLE |
108 | PF_DRAWABLE = PDB_DRAWABLE |
| 109 | PF_VECTORS = PDB_VECTORS |
109 | PF_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 | |
| 115 | PF_TOGGLE = 1000 |
115 | PF_TOGGLE = 1000 |
| 116 | PF_BOOL = PF_TOGGLE |
116 | PF_BOOL = PF_TOGGLE |
| 117 | PF_SLIDER = 1001 |
117 | PF_SLIDER = 1001 |
| 118 | PF_SPINNER = 1002 |
118 | PF_SPINNER = 1002 |
| 119 | PF_ADJUSTMENT = PF_SPINNER |
119 | PF_ADJUSTMENT = PF_SPINNER |
| 120 | |
120 | |
| 121 | PF_FONT = 1003 |
121 | PF_FONT = 1003 |
| 122 | PF_FILE = 1004 |
122 | PF_FILE = 1004 |
| 123 | PF_BRUSH = 1005 |
123 | PF_BRUSH = 1005 |
| 124 | PF_PATTERN = 1006 |
124 | PF_PATTERN = 1006 |
| 125 | PF_GRADIENT = 1007 |
125 | PF_GRADIENT = 1007 |
| 126 | PF_RADIO = 1008 |
126 | PF_RADIO = 1008 |
| 127 | PF_TEXT = 1009 |
127 | PF_TEXT = 1009 |
| 128 | PF_PALETTE = 1010 |
128 | PF_PALETTE = 1010 |
| 129 | PF_FILENAME = 1011 |
129 | PF_FILENAME = 1011 |
| 130 | PF_DIRNAME = 1012 |
130 | PF_DIRNAME = 1012 |
| 131 | PF_OPTION = 1013 |
131 | PF_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 | |
| 209 | def register(proc_name, blurb, help, author, copyright, date, label, |
209 | def 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 | |
| 295 | def _query(): |
295 | def _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 | |
| 330 | def _get_defaults(proc_name): |
330 | def _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 | |
| 346 | def _set_defaults(proc_name, defaults): |
346 | def _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 | |
| 352 | def _interact(proc_name, start_params): |
352 | def _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 | |
| 772 | def _run(proc_name, params): |
784 | def _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 | |
| 813 | def main(): |
825 | def 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 | |
| 817 | def fail(msg): |
829 | def 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 | |
| 822 | def N_(message): |
834 | def N_(message): |
| 823 | return message |
835 | return message |
| 824 | |
836 | |