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