1#!/usr/local/bin/python
2
3#   Gimp-Python - allows the writing of Gimp plugins in Python.
4#   Copyright (C) 1997  James Henstridge <james@daa.com.au>
5#
6#   This program is free software: you can redistribute it and/or modify
7#   it under the terms of the GNU General Public License as published by
8#   the Free Software Foundation; either version 3 of the License, or
9#   (at your option) any later version.
10#
11#   This program is distributed in the hope that it will be useful,
12#   but WITHOUT ANY WARRANTY; without even the implied warranty of
13#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14#   GNU General Public License for more details.
15#
16#   You should have received a copy of the GNU General Public License
17#   along with this program.  If not, see <https://www.gnu.org/licenses/>.
18
19from gimpfu import *
20
21t = gettext.translation('gimp20-python', gimp.locale_directory, fallback=True)
22_ = t.ugettext
23
24PROC_NAME = 'python-fu-console'
25
26RESPONSE_BROWSE, RESPONSE_CLEAR, RESPONSE_SAVE = range(3)
27
28def do_console():
29    import pygtk
30    pygtk.require('2.0')
31
32    import sys, gobject, gtk, gimpenums, gimpshelf, gimpui, pyconsole
33    gimpui.gimp_ui_init ()
34
35    namespace = {'__builtins__': __builtins__,
36                 '__name__': '__main__', '__doc__': None,
37                 'gimp': gimp, 'pdb': gimp.pdb,
38                 'shelf': gimpshelf.shelf}
39
40    for s in gimpenums.__dict__.keys():
41        if s[0] != '_':
42            namespace[s] = getattr(gimpenums, s)
43
44    class GimpConsole(pyconsole.Console):
45        def __init__(self, quit_func=None):
46            banner = ('GIMP %s Python Console\nPython %s\n' %
47                      (gimp.pdb.gimp_version(), sys.version))
48            pyconsole.Console.__init__(self,
49                                       locals=namespace, banner=banner,
50                                       quit_func=quit_func)
51        def _commit(self):
52            pyconsole.Console._commit(self)
53            gimp.displays_flush()
54
55    class ConsoleDialog(gimpui.Dialog):
56        def __init__(self):
57            gimpui.Dialog.__init__(self, title=_("Python Console"),
58                                   role=PROC_NAME, help_id=PROC_NAME,
59                                   buttons=(gtk.STOCK_SAVE,  RESPONSE_SAVE,
60                                            gtk.STOCK_CLEAR, RESPONSE_CLEAR,
61                                            _("_Browse..."), RESPONSE_BROWSE,
62                                            gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE))
63
64            self.set_name (PROC_NAME)
65            self.set_alternative_button_order((gtk.RESPONSE_CLOSE,
66                                               RESPONSE_BROWSE,
67                                               RESPONSE_CLEAR,
68                                               RESPONSE_SAVE))
69
70            self.cons = GimpConsole(quit_func=lambda: gtk.main_quit())
71
72            self.style_set (None, None)
73
74            self.connect('response', self.response)
75            self.connect('style-set', self.style_set)
76
77            self.browse_dlg = None
78            self.save_dlg = None
79
80            vbox = gtk.VBox(False, 12)
81            vbox.set_border_width(12)
82            self.vbox.pack_start(vbox)
83
84            scrl_win = gtk.ScrolledWindow()
85            scrl_win.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS)
86            vbox.pack_start(scrl_win)
87
88            scrl_win.add(self.cons)
89
90            width, height = self.cons.get_default_size()
91            sb_width, sb_height = scrl_win.get_vscrollbar().size_request()
92
93            # Account for scrollbar width and border width to ensure
94            # the text view gets a width of 80 characters. We don't care
95            # so much whether the height will be exactly 40 characters.
96            self.set_default_size(width + sb_width + 2 * 12, height)
97
98        def style_set(self, old_style, user_data):
99            style = self.get_style ()
100            self.cons.stdout_tag.set_property ("foreground", style.text[gtk.STATE_NORMAL])
101            self.cons.stderr_tag.set_property ("foreground", style.text[gtk.STATE_INSENSITIVE])
102
103        def response(self, dialog, response_id):
104            if response_id == RESPONSE_BROWSE:
105                self.browse()
106            elif response_id == RESPONSE_CLEAR:
107                self.cons.banner = None
108                self.cons.clear()
109            elif response_id == RESPONSE_SAVE:
110                self.save_dialog()
111            else:
112                gtk.main_quit()
113
114            self.cons.grab_focus()
115
116        def browse_response(self, dlg, response_id):
117            if response_id != gtk.RESPONSE_APPLY:
118                dlg.hide()
119                return
120
121            proc_name = dlg.get_selected()
122
123            if not proc_name:
124                return
125
126            proc = pdb[proc_name]
127
128            cmd = ''
129
130            if len(proc.return_vals) > 0:
131                cmd = ', '.join([x[1].replace('-', '_')
132                                for x in proc.return_vals]) + ' = '
133
134            cmd = cmd + 'pdb.%s' % proc.proc_name.replace('-', '_')
135
136            if len(proc.params) > 0 and proc.params[0][1] == 'run-mode':
137                params = proc.params[1:]
138            else:
139                params = proc.params
140
141            cmd = cmd + '(%s)' % ', '.join([x[1].replace('-', '_')
142                                           for x in params])
143
144            buffer = self.cons.buffer
145
146            lines = buffer.get_line_count()
147            iter = buffer.get_iter_at_line_offset(lines - 1, 4)
148            buffer.delete(iter, buffer.get_end_iter())
149            buffer.place_cursor(buffer.get_end_iter())
150            buffer.insert_at_cursor(cmd)
151
152        def browse(self):
153            if not self.browse_dlg:
154                dlg = gimpui.ProcBrowserDialog(_("Python Procedure Browser"),
155                                               role=PROC_NAME,
156                                               buttons=(gtk.STOCK_APPLY,
157                                                        gtk.RESPONSE_APPLY,
158                                                        gtk.STOCK_CLOSE,
159                                                        gtk.RESPONSE_CLOSE))
160
161                dlg.set_default_response(gtk.RESPONSE_APPLY)
162                dlg.set_alternative_button_order((gtk.RESPONSE_CLOSE,
163                                                  gtk.RESPONSE_APPLY))
164
165                dlg.connect('response', self.browse_response)
166                dlg.connect('row-activated',
167                            lambda dlg: dlg.response(gtk.RESPONSE_APPLY))
168
169                self.browse_dlg = dlg
170
171            self.browse_dlg.present()
172
173        def save_response(self, dlg, response_id):
174            if response_id == gtk.RESPONSE_DELETE_EVENT:
175                self.save_dlg = None
176                return
177            elif response_id == gtk.RESPONSE_OK:
178                filename = dlg.get_filename()
179
180                try:
181                    logfile = open(filename, 'w')
182                except IOError, e:
183                    gimp.message(_("Could not open '%s' for writing: %s") %
184                                 (filename, e.strerror))
185                    return
186
187                buffer = self.cons.buffer
188
189                start = buffer.get_start_iter()
190                end = buffer.get_end_iter()
191
192                log = buffer.get_text(start, end, False)
193
194                try:
195                    logfile.write(log)
196                    logfile.close()
197                except IOError, e:
198                    gimp.message(_("Could not write to '%s': %s") %
199                                 (filename, e.strerror))
200                    return
201
202            dlg.hide()
203
204        def save_dialog(self):
205            if not self.save_dlg:
206                dlg = gtk.FileChooserDialog(_("Save Python-Fu Console Output"),
207                                            parent=self,
208                                            action=gtk.FILE_CHOOSER_ACTION_SAVE,
209                                            buttons=(gtk.STOCK_CANCEL,
210                                                     gtk.RESPONSE_CANCEL,
211                                                     gtk.STOCK_SAVE,
212                                                     gtk.RESPONSE_OK))
213
214                dlg.set_default_response(gtk.RESPONSE_OK)
215                dlg.set_alternative_button_order((gtk.RESPONSE_OK,
216                                                  gtk.RESPONSE_CANCEL))
217
218                dlg.connect('response', self.save_response)
219
220                self.save_dlg = dlg
221
222            self.save_dlg.present()
223
224        def run(self):
225            self.show_all()
226            gtk.main()
227
228    ConsoleDialog().run()
229
230register(
231    PROC_NAME,
232    N_("Interactive GIMP Python interpreter"),
233    "Type in commands and see results",
234    "James Henstridge",
235    "James Henstridge",
236    "1997-1999",
237    N_("_Console"),
238    "",
239    [],
240    [],
241    do_console,
242    menu="<Image>/Filters/Languages/Python-Fu",
243    domain=("gimp20-python", gimp.locale_directory))
244
245main()
246