1 # This file is part of MyPaint.
2 # Copyright (C) 2009 by Martin Renold <martinxyz@gmx.ch>
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 2 of the License, or
7 # (at your option) any later version.
12 from gettext import gettext as _
13 from fnmatch import fnmatch
15 from pixbuflist import PixbufList
21 DONT_OVERWRITE_THIS = 3
22 DONT_OVERWRITE_ANYTHING = 4
25 def confirm(widget, question):
26 window = widget.get_toplevel()
27 d = gtk.MessageDialog(
33 d.add_button(gtk.STOCK_NO, gtk.RESPONSE_REJECT)
34 d.add_button(gtk.STOCK_YES, gtk.RESPONSE_ACCEPT)
35 d.set_default_response(gtk.RESPONSE_ACCEPT)
38 return response == gtk.RESPONSE_ACCEPT
40 def ask_for_name(widget, title, default):
41 window = widget.get_toplevel()
45 (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
46 gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
47 d.set_position(gtk.WIN_POS_MOUSE)
50 hbox.set_property("spacing", widgets.SPACING)
51 hbox.set_border_width(widgets.SPACING)
53 d.vbox.pack_start(hbox)
54 hbox.pack_start(gtk.Label(_('Name')), False, False)
57 e.set_size_request(250, -1)
59 e.select_region(0, len(default))
60 def responseToDialog(entry, dialog, response):
61 dialog.response(response)
62 e.connect("activate", responseToDialog, d, gtk.RESPONSE_ACCEPT)
64 hbox.pack_start(e, True, True)
66 if d.run() == gtk.RESPONSE_ACCEPT:
67 result = d.e.get_text().decode('utf-8')
73 def error(widget, message):
74 window = widget.get_toplevel()
75 d = gtk.MessageDialog(window, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, message)
79 def image_new_from_png_data(data):
80 loader = gtk.gdk.PixbufLoader("png")
83 pixbuf = loader.get_pixbuf()
85 image.set_from_pixbuf(pixbuf)
88 def confirm_rewrite_brush(window, brushname, existing_preview_pixbuf, imported_preview_data):
89 dialog = gtk.Dialog(_("Overwrite brush?"),
90 window, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT)
92 cancel = gtk.Button(stock=gtk.STOCK_CANCEL)
95 img_yes.set_from_stock(gtk.STOCK_YES, gtk.ICON_SIZE_BUTTON)
97 img_no.set_from_stock(gtk.STOCK_NO, gtk.ICON_SIZE_BUTTON)
98 overwrite_this = gtk.Button(_("Replace"))
99 overwrite_this.set_image(img_yes)
100 overwrite_this.show_all()
101 skip_this = gtk.Button(_("Rename"))
102 skip_this.set_image(img_no)
104 overwrite_all = gtk.Button(_("Replace all"))
105 overwrite_all.show_all()
106 skip_all = gtk.Button(_("Rename all"))
109 buttons = [(cancel, CANCEL),
110 (skip_all, DONT_OVERWRITE_ANYTHING),
111 (overwrite_all, OVERWRITE_ALL),
112 (skip_this, DONT_OVERWRITE_THIS),
113 (overwrite_this, OVERWRITE_THIS)]
114 for button, code in buttons:
115 dialog.add_action_widget(button, code)
120 preview_r = gtk.image_new_from_pixbuf(existing_preview_pixbuf)
121 label_l = gtk.Label(_("Imported brush"))
122 label_r = gtk.Label(_("Existing brush"))
124 question = gtk.Label(_("<b>A brush named `%s' already exists.</b>\nDo you want to replace it, or should the new brush be renamed?") % brushname)
125 question.set_use_markup(True)
127 preview_l = image_new_from_png_data(imported_preview_data)
129 vbox_l.pack_start(preview_l, expand=True)
130 vbox_l.pack_start(label_l, expand=False)
132 vbox_r.pack_start(preview_r, expand=True)
133 vbox_r.pack_start(label_r, expand=False)
135 hbox.pack_start(vbox_l, expand=False)
136 hbox.pack_start(question, expand=True)
137 hbox.pack_start(vbox_r, expand=False)
140 dialog.vbox.pack_start(hbox)
142 answer = dialog.run()
146 def confirm_rewrite_group(window, groupname, deleted_groupname):
147 dialog = gtk.Dialog(_("Overwrite brush group?"),
148 window, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT)
150 cancel = gtk.Button(stock=gtk.STOCK_CANCEL)
152 img_yes = gtk.Image()
153 img_yes.set_from_stock(gtk.STOCK_YES, gtk.ICON_SIZE_BUTTON)
155 img_no.set_from_stock(gtk.STOCK_NO, gtk.ICON_SIZE_BUTTON)
156 overwrite_this = gtk.Button(_("Replace"))
157 overwrite_this.set_image(img_yes)
158 overwrite_this.show_all()
159 skip_this = gtk.Button(_("Rename"))
160 skip_this.set_image(img_no)
163 buttons = [(cancel, CANCEL),
164 (skip_this, DONT_OVERWRITE_THIS),
165 (overwrite_this, OVERWRITE_THIS)]
166 for button, code in buttons:
167 dialog.add_action_widget(button, code)
169 question = gtk.Label(_("<b>A group named `%s' already exists.</b>\nDo you want to replace it, or should the new group be renamed?\nIf you replace it, the brushes may be moved to a group called `%s'.") % (groupname, deleted_groupname))
170 question.set_use_markup(True)
172 dialog.vbox.pack_start(question)
173 dialog.vbox.show_all()
175 answer = dialog.run()
179 def open_dialog(title, window, filters):
181 filters should be a list of tuples: (filter title, glob pattern).
182 Returns a tuple: (file format, filename).
183 Here "file format" is index of filter that matches filename (None if no matches).
184 filename is None if no file was selected.
186 dialog = gtk.FileChooserDialog(title, window,
187 gtk.FILE_CHOOSER_ACTION_OPEN,
188 (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
189 gtk.STOCK_OPEN, gtk.RESPONSE_OK))
190 dialog.set_default_response(gtk.RESPONSE_OK)
191 for filter_title, pattern in filters:
193 f.set_name(filter_title)
194 f.add_pattern(pattern)
197 result = (None, None)
198 if dialog.run() == gtk.RESPONSE_OK:
199 filename = dialog.get_filename().decode('utf-8')
201 for i, (_, pattern) in enumerate(filters):
202 if fnmatch(filename, pattern):
205 result = (file_format, filename)
209 def save_dialog(title, window, filters, default_format=None):
211 filters should be a list of tuples: (filter title, glob pattern).
212 default_format may be a pair (format id, suffix). That suffix will be added to filename if
213 it does not match any of filters.
214 Returns a tuple: (file format, filename).
215 Here "file format" is index of filter that matches filename (None if no matches).
216 filename is None if no file was selected.
218 dialog = gtk.FileChooserDialog(title, window,
219 gtk.FILE_CHOOSER_ACTION_SAVE,
220 (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
221 gtk.STOCK_SAVE, gtk.RESPONSE_OK))
222 dialog.set_default_response(gtk.RESPONSE_OK)
223 dialog.set_do_overwrite_confirmation(True)
225 for filter_title, pattern in filters:
227 f.set_name(filter_title)
228 f.add_pattern(pattern)
231 result = (None, None)
232 while dialog.run() == gtk.RESPONSE_OK:
233 filename = dialog.get_filename().decode('utf-8')
235 for i, (_, pattern) in enumerate(filters):
236 if fnmatch(filename, pattern):
239 if file_format is None and default_format is not None:
240 file_format, suffix = default_format
242 dialog.set_current_name(filename)
243 dialog.response(gtk.RESPONSE_OK)
245 result = (file_format, filename)
250 def confirm_brushpack_import(packname, window=None, readme=None):
253 tv.set_wrap_mode(gtk.WRAP_WORD)
254 tv.get_buffer().set_text(text)
257 dialog = gtk.Dialog(_("Import brush package?"),
259 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
260 (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
261 gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
264 #readme_label = gtk.Label(_("readme.txt") % packname)
265 #dialog.vbox.pack_start(readme_label)
266 readme_tv = show_text(readme)
267 dialog.vbox.pack_start(readme_tv)
269 question = gtk.Label(_("<b>Do you really want to import package `%s'?</b>") % packname)
270 question.set_use_markup(True)
271 dialog.vbox.pack_start(question)
272 dialog.vbox.show_all()
273 answer = dialog.run()
277 def change_current_color_detailed(app):
278 """Presents a `gtk.ColorSelectionDialog` for updating the current colour.
280 The dialog isn't particularly simple, but allows colours to be entered as
281 hex strings or using spinners.
283 previous_hsv = app.ch.colors[-1]
284 current_hsv = app.brush.get_color_hsv()
285 dialog = gtk.ColorSelectionDialog(_("Color details"))
286 dialog.set_position(gtk.WIN_POS_MOUSE)
287 dialog.colorsel.set_previous_color(gdk.color_from_hsv(*previous_hsv))
288 dialog.colorsel.set_current_color(gdk.color_from_hsv(*current_hsv))
289 dialog.ok_button.grab_focus()
290 dialog.set_transient_for(app.drawWindow)
291 dialog.set_resizable(False)
292 dialog.set_modal(True)
293 if dialog.run() == gtk.RESPONSE_OK:
294 col = dialog.colorsel.get_current_color()
295 hsv = (col.hue, col.saturation, col.value)
296 app.brush.set_color_hsv(hsv)
298 return app.brush.get_color_hsv()
301 class QuickBrushChooser (gtk.VBox):
302 PREFS_KEY = 'widgets.brush_chooser.selected_group'
305 class _BrushList (PixbufList):
306 def __init__(self, chooser, brushes):
307 s = QuickBrushChooser.ICON_SIZE
308 PixbufList.__init__(self, brushes, s, s,
309 namefunc = lambda x: x.name,
310 pixbuffunc = lambda x: x.preview)
311 self.chooser = chooser
313 def on_select(self, brush):
314 self.chooser.on_select(brush)
316 def __init__(self, app, on_select):
317 gtk.VBox.__init__(self)
319 self.on_select = on_select
320 self.bm = app.brushmanager
322 active_group_name = app.preferences.get(self.PREFS_KEY, None)
324 model = self._make_groups_sb_model()
325 self.groups_sb = spinbox.ItemSpinBox(model, self.on_groups_sb_changed,
327 active_group_name = self.groups_sb.get_value()
329 brushes = self.bm.groups[active_group_name][:]
330 self.brushlist = self._BrushList(self, brushes)
331 self.brushlist.dragging_allowed = False
333 scrolledwin = gtk.ScrolledWindow()
334 scrolledwin.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS)
335 scrolledwin.add_with_viewport(self.brushlist)
336 self.connect("style-set", self._on_style_set, scrolledwin)
337 icon_size = self.ICON_SIZE
340 scrolledwin.set_size_request(-1, h)
341 self.brushlist.set_size_request(w, -1)
342 scrolledwin.get_child().set_size_request(w, -1)
346 self.pack_start(self.groups_sb, False, False)
347 self.pack_start(scrolledwin, True, True)
348 self.set_spacing(widgets.SPACING_TIGHT)
350 def _on_style_set(self, widget, event, sw):
352 bl_style = self.brushlist.get_style()
353 #vp.modify_bg(gtk.STATE_NORMAL, bl_style.base[gtk.STATE_NORMAL])
354 #sw.modify_bg(gtk.STATE_NORMAL, bl_style.base[gtk.STATE_NORMAL])
355 self.brushlist.modify_bg(gtk.STATE_NORMAL, bl_style.base[gtk.STATE_NORMAL])
356 icon_size = self.ICON_SIZE
358 def _make_groups_sb_model(self):
359 group_names = self.bm.groups.keys()
362 for name in group_names:
363 label_text = brushmanager.translate_group_name(name)
364 model.append((name, label_text))
367 def update_groups_sb(self):
368 model = self._make_groups_sb_model()
369 self.groups_sb.set_model(model)
371 def on_groups_sb_changed(self, group_name):
372 self.app.preferences[self.PREFS_KEY] = group_name
373 self.brushlist.itemlist[:] = self.bm.groups[group_name][:]
374 self.brushlist.update()
377 class BrushChooserDialog (gtk.Dialog):
378 """Speedy brush chooser dialog.
381 def __init__(self, app):
382 title = _("Change Brush")
383 parent = app.drawWindow
384 flags = gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT
385 buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT)
386 gtk.Dialog.__init__(self, title, parent, flags, buttons)
387 self.set_position(gtk.WIN_POS_MOUSE)
389 self.response_brush = None
390 def on_select(brush):
391 self.response_brush = brush
392 self.response(gtk.RESPONSE_ACCEPT)
393 self.chooser = QuickBrushChooser(app, on_select)
394 vbox = self.get_content_area()
395 vbox.pack_start(self.chooser, True, True)
399 def change_current_brush_quick(app):
400 dialog = BrushChooserDialog(app)
401 result = dialog.run()
402 if result == gtk.RESPONSE_ACCEPT:
403 app.brushmanager.select_brush(dialog.response_brush)