Beispiel #1
0
    def _draw_cb(self, da, cr):
        """Paint a preview of the current brush to the view."""
        if not self._brush_preview:
            cr.set_source_rgb(1, 0, 1)
            cr.paint()
            return
        aw = da.get_allocated_width()
        ah = da.get_allocated_height()

        # Work in a temporary group so that
        # the result can be masked with a gradient later.
        cr.push_group()

        # Paint a shadow line around the edge of
        # where the the brush preview will go.
        # There's an additional top border of one pixel
        # for alignment with the color preview widget
        # in the other corner.
        cr.rectangle(1.5, 2.5, aw - 3, ah)
        cr.set_line_join(cairo.LINE_JOIN_ROUND)
        cr.set_source_rgba(*self._OUTLINE_RGBA)
        cr.set_line_width(3)
        cr.stroke()

        # Scale and align the brush preview in its own saved context
        cr.save()
        # Clip rectangle for the bit in the middle of the shadow.
        # Note that the bottom edge isn't shadowed.
        cr.rectangle(1, 2, aw - 2, ah)
        cr.clip()
        # Scale and align the preview to the top of that clip rect.
        preview = self._brush_preview
        pw = preview.get_width()
        ph = preview.get_height()
        area_size = float(max(aw, ah)) - 2
        preview_size = float(max(pw, ph))
        x = math.floor(-pw / 2.0)
        y = 0
        cr.translate(aw / 2.0, 2)
        scale = area_size / preview_size
        cr.scale(scale, scale)
        Gdk.cairo_set_source_pixbuf(cr, preview, x, y)
        cr.paint()
        cr.restore()

        # Finally a highlight around the edge in the house style
        # Note that the bottom edge isn't highlighted.
        cr.rectangle(1.5, 2.5, aw - 3, ah)
        cr.set_line_width(1)
        cr.set_source_rgba(*self._EDGE_HIGHLIGHT_RGBA)
        cr.stroke()

        # Paint the group within a gradient mask
        cr.pop_group_to_source()
        mask = cairo.LinearGradient(0, 0, 0, ah)
        mask.add_color_stop_rgba(0.0, 1, 1, 1, 1.0)
        mask.add_color_stop_rgba(0.8, 1, 1, 1, 1.0)
        mask.add_color_stop_rgba(0.95, 1, 1, 1, 0.5)
        mask.add_color_stop_rgba(1.0, 1, 1, 1, 0.1)
        cr.mask(mask)
Beispiel #2
0
def render_round_floating_button(cr,
                                 x,
                                 y,
                                 color,
                                 pixbuf,
                                 z=2,
                                 radius=gui.style.FLOATING_BUTTON_RADIUS):
    """Draw a round floating button with a standard size.

    :param cairo.Context cr: Context in which to draw.
    :param float x: X coordinate of the center pixel.
    :param float y: Y coordinate of the center pixel.
    :param lib.color.UIColor color: Color for the button base.
    :param GdkPixbuf.Pixbuf pixbuf: Icon to render.
    :param int z: Simulated height of the button above the canvas.
    :param float radius: Button radius, in pixels.

    These are used within certain overlays tightly associated with
    particular interaction modes for manipulating things on the canvas.

    """
    x = round(float(x))
    y = round(float(y))
    render_round_floating_color_chip(cr, x, y, color, radius=radius, z=z)
    cr.save()
    w = pixbuf.get_width()
    h = pixbuf.get_height()
    x -= w / 2
    y -= h / 2
    Gdk.cairo_set_source_pixbuf(cr, pixbuf, x, y)
    cr.rectangle(x, y, w, h)
    cr.clip()
    cr.paint()
    cr.restore()
Beispiel #3
0
    def _key_release_cb(self, widget, event):
        """Application-wide key release handler."""

        consumed = widget.propagate_key_event(event)
        if consumed:
            return True

        if not self.enabled:
            return

        def released(hardware_keycode):
            action = self.pressed[hardware_keycode]
            del self.pressed[hardware_keycode]
            if action.keyup_callback:
                action.keyup_callback(widget, event)
                action.keyup_callback = None

        if event.keyval == Gdk.KEY_Escape:
            # emergency exit in case of bugs
            for hardware_keycode in list(self.pressed.keys()):
                released(hardware_keycode)
            # Pop all stacked modes; they should release grabs
            self.app.doc.modes.reset()
            # Just in case...
            Gdk.pointer_ungrab(event.time)
        else:
            # note: event.keyval would not be suited for this because
            # it can be different from the one we have seen in
            # key_press_cb if the user has released a modifier first
            if event.hardware_keycode in self.pressed:
                released(event.hardware_keycode)
                return True

        # Fallthru handler: dispatch doc-specific stuff.
        return self._dispatch_fallthru_key_release_event(widget, event)
Beispiel #4
0
def translate(hardware_keycode, state, group):
    # We may need to retry several times to deal with garbled text.

    keymap = Gdk.Keymap.get_default()

    # distinct
    it = list(set([group, 0, 1, 2]))

    ok_to_return = False
    keyval = None
    keyval_lower = None

    for g in it:
        res = keymap.translate_keyboard_state(hardware_keycode,
                                              Gdk.ModifierType(0), g)

        if not res:
            # PyGTK returns None when gdk_keymap_translate_keyboard_state()
            # returns false.  Not sure if this is a bug or a feature - the only
            # time I have seen this happen is when I put my laptop into sleep
            # mode.

            continue

        keyval = res[1]

        # consumed_modifiers = res[4]

        lbl = Gtk.accelerator_get_label(keyval, state)

        if is_ascii(lbl):
            ok_to_return = True
            break

    if not ok_to_return:
        logger.warning(
            'translate_keyboard_state() returned no valid response. '
            'Strange key pressed?')

        return None, None, None, None

    # We want to ignore irrelevant modifiers like ScrollLock.  The stored
    # key binding does not include modifiers that affected its keyval.
    mods = Gdk.ModifierType(state & Gtk.accelerator_get_default_mod_mask())

    keyval_lower = Gdk.keyval_to_lower(keyval)

    # If lowercasing affects the keysym, then we need to include
    # SHIFT in the modifiers. We re-upper case when we match against
    # the keyval, but display and save in caseless form.
    if keyval != keyval_lower:
        mods |= Gdk.ModifierType.SHIFT_MASK

    # So we get (<Shift>j, Shift+J) but just (plus, +). As I
    # understand it.

    accel_label = Gtk.accelerator_get_label(keyval_lower, mods)

    return keyval, keyval_lower, accel_label, mods
Beispiel #5
0
    def _key_press_cb(self, widget, event):
        """App-wide keypress handler for toplevel windows."""

        # If an input widget has focus - their key handling is prioritized.
        consumed = widget.propagate_key_event(event)
        if consumed:
            return True
        if not self.enabled:
            return
        # See gtk sourcecode in gtkmenu.c function gtk_menu_key_press,
        # which uses the same code as below when changing an accelerator.
        keymap = Gdk.Keymap.get_default()

        # Instead of using event.keyval, we do it the lowlevel way.
        # Reason: ignoring CAPSLOCK and checking if SHIFT was pressed
        state = Gdk.ModifierType(event.state & ~Gdk.ModifierType.LOCK_MASK)
        res = keymap.translate_keyboard_state(event.hardware_keycode, state,
                                              event.group)
        if not res:
            # PyGTK returns None when gdk_keymap_translate_keyboard_state()
            # returns false.  Not sure if this is a bug or a feature - the only
            # time I have seen this happen is when I put my laptop into sleep
            # mode.
            logger.warning('translate_keyboard_state() returned None. '
                           'Strange key pressed?')
            return

        keyval = res[1]
        consumed_modifiers = res[4]

        # We want to ignore irrelevant modifiers like ScrollLock.  The stored
        # key binding does not include modifiers that affected its keyval.
        modifiers = (event.state
                     & Gtk.accelerator_get_default_mod_mask()
                     & ~consumed_modifiers)

        # Except that key bindings are always stored in lowercase.
        keyval_lower = Gdk.keyval_to_lower(keyval)
        if keyval_lower != keyval:
            modifiers |= Gdk.ModifierType.SHIFT_MASK
        action = self.keymap.get((keyval_lower, modifiers))
        if not action and not self.fallbacks_disabled():
            # try hardcoded keys
            action = self.keymap2.get((keyval_lower, modifiers))

        # Don't dispatch if the window is only sensitive to a subset of
        # actions, and the action is not in that set.
        if action is not None and isinstance(action, Gtk.Action):
            win_actions = self.window_actions.get(widget, None)
            if win_actions is not None:
                if action.get_name() not in win_actions:
                    return False

        # If the lookup succeeded, activate the corresponding action.
        if action:
            return self.activate_keydown_event(action, event)

        # Otherwise, dispatch the event to the active doc.
        return self._dispatch_fallthru_key_press_event(widget, event)
Beispiel #6
0
    def draw_cb(self, widget, cr):
        # Paint the base color, and the list's pixbuf.
        state_flags = widget.get_state_flags()
        style_context = widget.get_style_context()
        style_context.save()
        bg_color_gdk = style_context.get_background_color(state_flags)
        bg_color = uicolor.from_gdk_rgba(bg_color_gdk)
        cr.set_source_rgb(*bg_color.get_rgb())
        cr.paint()
        Gdk.cairo_set_source_pixbuf(cr, self.pixbuf, 0, 0)
        cr.paint()
        # border colors
        gdkrgba = style_context.get_background_color(state_flags
                                                     | Gtk.StateFlags.SELECTED)
        selected_color = uicolor.from_gdk_rgba(gdkrgba)
        gdkrgba = style_context.get_background_color(state_flags
                                                     | Gtk.StateFlags.NORMAL)
        insertion_color = uicolor.from_gdk_rgba(gdkrgba)
        style_context.restore()
        # Draw borders
        last_i = len(self.itemlist) - 1
        for i, b in enumerate(self.itemlist):
            rect_color = None
            if b is self.selected:
                rect_color = selected_color
            elif self.drag_insertion_index is not None:
                if i == self.drag_insertion_index \
                        or (i == last_i and self.drag_insertion_index > i):
                    rect_color = insertion_color
            if rect_color is None:
                continue
            x = (i % self.tiles_w) * self.total_w
            y = (i // self.tiles_w) * self.total_h
            w = self.total_w
            h = self.total_h

            def shrink(pixels, x, y, w, h):
                x += pixels
                y += pixels
                w -= 2 * pixels
                h -= 2 * pixels
                return (x, y, w, h)

            x, y, w, h = shrink(self.spacing_outside, x, y, w, h)
            for j in xrange(self.border_visible_outside_cell):
                x, y, w, h = shrink(-1, x, y, w, h)
            max_j = self.border_visible + self.border_visible_outside_cell
            for j in xrange(max_j):
                cr.set_source_rgb(*rect_color.get_rgb())
                cr.rectangle(x, y, w - 1,
                             h - 1)  # FIXME: check pixel alignment
                cr.stroke()
                x, y, w, h = shrink(1, x, y, w, h)
        return True
Beispiel #7
0
def button_press_displayname(button, mods, shorten=False):
    """Converts a button number & modifier mask to a localized unicode string.
    """
    button = int(button)
    mods = int(mods)
    if button <= 0:
        return None
    mods = Gdk.ModifierType(mods)
    modif_label = Gtk.accelerator_get_label(0, mods)
    modif_label = unicode(modif_label)
    separator = ""
    if modif_label:
        separator = u"+"
    # TRANSLATORS: "Button" refers to a mouse button
    # TRANSLATORS: It is part of a button map label.
    mouse_button_label = _("Button")
    if shorten:
        # TRANSLATORS: abbreviated "Button <number>" for forms like "Alt+Btn1"
        mouse_button_label = _("Btn")
    return "{modifiers}{plus}{btn}{button_number}".format(
        modifiers=modif_label,
        plus=separator,
        btn=mouse_button_label,
        button_number=button,
    )
Beispiel #8
0
 def _grab_pointer_outside(self, device, time):
     if self._outside_grab_active:
         logger.warning("grab: outside-popup grab already active: "
                        "regrabbing")
         self._ungrab_pointer_outside(device, time)
     event_mask = (Gdk.EventMask.POINTER_MOTION_MASK
                   | Gdk.EventMask.ENTER_NOTIFY_MASK
                   | Gdk.EventMask.LEAVE_NOTIFY_MASK
                   | Gdk.EventMask.BUTTON_PRESS_MASK
                   | Gdk.EventMask.BUTTON_RELEASE_MASK)
     cursor = self._outside_cursor
     grab_status = device.grab(
         window=self.get_window(),
         grab_ownership=Gdk.GrabOwnership.APPLICATION,
         owner_events=False,
         event_mask=Gdk.EventMask(event_mask),
         cursor=cursor,
         time_=time,
     )
     if grab_status == Gdk.GrabStatus.SUCCESS:
         logger.debug("grab: acquired grab on %r successfully", device)
         self._outside_grab_active = True
     else:
         logger.warning("grab: failed to acquire grab on %r (status=%s)",
                        device, grab_status.value_nick)
Beispiel #9
0
 def render_background_cb(self, cr, wd, ht, icon_border=None):
     self._backend.set_brush_color(*self._hsv)
     size = self._backend.get_size()
     pixbuf = GdkPixbuf.Pixbuf.new(
         GdkPixbuf.Colorspace.RGB,
         True,
         8,
         size,
         size,
     )
     arr = gdkpixbuf2numpy(pixbuf)
     self._backend.render(arr)
     self._dx = (wd - size) // 2
     self._dy = (ht - size) // 2
     cr.translate(self._dx, self._dy)
     Gdk.cairo_set_source_pixbuf(cr, pixbuf, 0, 0)
     cr.paint()
Beispiel #10
0
def to_gdk_color(color):
    """Convert a UIColor to a Gdk.Color.

    >>> gcol = to_gdk_color(RGBColor(1,1,1))
    >>> gcol.to_string()
    '#ffffffffffff'

    """
    return Gdk.Color(*[int(c*65535) for c in color.get_rgb()])
Beispiel #11
0
 def drag_motion_cb(self, widget, context, x, y, time):
     if not self.dragging_allowed:
         return False
     action = None
     source_widget = Gtk.drag_get_source_widget(context)
     if self is source_widget:
         # Only moves are possible
         action = Gdk.DragAction.MOVE
     else:
         # Dragging from another widget, is always a copy
         action = Gdk.DragAction.COPY
     Gdk.drag_status(context, action, time)
     if not self.drag_highlighted:
         self.drag_highlighted = True
         self.queue_draw()
     if self.drag_highlighted:
         i = self.index(x, y)
         if i != self.drag_insertion_index:
             self.queue_draw()
             self.drag_insertion_index = i
Beispiel #12
0
def to_gdk_rgba(color):
    """Convert to a `GdkRGBA` (with alpha=1.0).

    >>> col = RGBColor(1,1,1)
    >>> rgba = to_gdk_rgba(col)
    >>> rgba.to_string()
    'rgb(255,255,255)'

    """
    rgba = list(color.get_rgb())
    rgba.append(1.0)
    return Gdk.RGBA(*rgba)
Beispiel #13
0
 def local_mouse_state(self, last_update=False):
     tdw_win = self.tdw.get_window()
     display = self.tdw.get_display()
     devmgr = display and display.get_device_manager() or None
     coredev = devmgr and devmgr.get_client_pointer() or None
     if coredev and tdw_win:
         win_, x, y, kbmods = tdw_win.get_device_position_double(coredev)
     else:
         x, y, kbmods = (0., 0., Gdk.ModifierType(0))
     if last_update:
         return self.lx, self.ly, kbmods
     x, y = self.tdw.display_to_model(x, y)
     return x, y, kbmods
Beispiel #14
0
    def cairo_request(self):
        """Access via a temporary Cairo context.

        Modifications are copied back into the backing pixbuf when the
        context manager finishes.

        """
        # Make a Cairo surface copy of the subpixbuf
        surf = cairo.ImageSurface(
            cairo.FORMAT_ARGB32,
            self.pixbuf.get_width(), self.pixbuf.get_height(),
        )
        cr = cairo.Context(surf)
        Gdk.cairo_set_source_pixbuf(cr, self.pixbuf, 0, 0)
        cr.paint()

        # User can modify its content with Cairo operations
        yield cr

        # Put the modified data back into the tile-aligned pixbuf.
        dx = self.x - self.ex
        dy = self.y - self.ey
        pixbuf = Gdk.pixbuf_get_from_surface(surf, 0, 0, self.w, self.h)
        pixbuf.copy_area(0, 0, self.w, self.h, self.epixbuf, dx, dy)
Beispiel #15
0
 def drag_data_get_cb(self, widget, context, selection, target_type, time):
     """Gets the current color when a drop happens somewhere"""
     if "application/x-color" not in map(str, context.list_targets()):
         return False
     color = self.get_managed_color()
     data = gui.uicolor.to_drag_data(color)
     selection.set(Gdk.atom_intern("application/x-color", False), 16, data)
     logger.debug(
         "drag-data-get: sending type=%r",
         selection.get_data_type(),
     )
     logger.debug("drag-data-get: sending fmt=%r", selection.get_format())
     logger.debug("drag-data-get: sending data=%r len=%r",
                  selection.get_data(), len(selection.get_data()))
     return True
Beispiel #16
0
def button_press_name(button, mods):
    """Converts button number & modifier mask to a prefs-storable string.

    Analogous to `Gtk.accelerator_name()`.  Buttonpress names look similar to
    GDK accelerator names, for example ``<Control><Shift>Button2`` or
    ``<Primary><Alt>Button4`` for newer versions of GTK.  If the button is
    equal to zero (see `button_press_parse()`), `None` is returned.

    """
    button = int(button)
    mods = int(mods)
    if button <= 0:
        return None
    mods = Gdk.ModifierType(mods)
    modif_name = Gtk.accelerator_name(0, mods)
    return modif_name + "Button%d" % (button, )
Beispiel #17
0
    def _key_press_cb(self, widget, event):
        """App-wide keypress handler for toplevel windows."""

        # If an input widget has focus - their key handling is prioritized.
        consumed = widget.propagate_key_event(event)
        if consumed:
            return True
        if not self.enabled:
            return

        # Instead of using event.keyval, we do it the lowlevel way.
        # Reason: ignoring CAPSLOCK and checking if SHIFT was pressed
        keyval, keyval_lower, accel_label, modifiers = kb.translate(
            event.hardware_keycode, event.state, event.group)

        if not keyval:
            return

        # Except that key bindings are always stored in lowercase.
        keyval_lower = Gdk.keyval_to_lower(keyval)
        if keyval_lower != keyval:
            modifiers |= Gdk.ModifierType.SHIFT_MASK
        action = self.keymap.get((keyval_lower, modifiers))
        if not action and not self.fallbacks_disabled():
            # try hardcoded keys
            action = self.keymap2.get((keyval_lower, modifiers))

        # Don't dispatch if the window is only sensitive to a subset of
        # actions, and the action is not in that set.
        if action is not None and isinstance(action, Gtk.Action):
            win_actions = self.window_actions.get(widget, None)
            if win_actions is not None:
                if action.get_name() not in win_actions:
                    return False

        # If the lookup succeeded, activate the corresponding action.
        if action:
            return self.activate_keydown_event(action, event)

        # Otherwise, dispatch the event to the active doc.
        return self._dispatch_fallthru_key_press_event(widget, event)
Beispiel #18
0
def button_press_parse(name):
    """Converts button press names to a button number & modifier mask.

    Analogous to `Gtk.accelerator_parse()`. This function parses the strings
    created by `button_press_name()`, and returns a 2-tuple containing the
    button number and modifier mask corresponding to `name`. If the parse
    fails, both values will be 0 (zero).

    """
    if name is None:
        return (0, 0)
    name = str(name)
    try:
        mods_s, button_s = name.split("Button", 1)
        if button_s == '':
            button = 0
        else:
            button = int(button_s)
    except ValueError:
        button = 0
        mods = Gdk.ModifierType(0)
    else:
        keyval_ignored, mods = Gtk.accelerator_parse(mods_s)
    return button, mods
Beispiel #19
0
    def __init__(self, filenames, state_dirs, version, fullscreen=False):
        """Construct, but do not run.

        :param list filenames: The list of files to load (unicode required)
        :param StateDirs state_dirs: static special paths.
        :param unicode version: Version string for the about dialog.
        :param bool fullscreen: Go fullscreen after starting.

        Only the first filename listed will be loaded. If no files are
        listed, the autosave recovery dialog may be shown when the
        application starts up.

        """
        assert Application._INSTANCE is None
        super(Application, self).__init__()
        Application._INSTANCE = self

        self.state_dirs = state_dirs  #: Static special paths: see StateDirs

        self.version = version  #: version string for the app.

        # Create the user's config directory and any needed R/W data
        # storage areas.
        for basedir in [state_dirs.user_config, state_dirs.user_data]:
            if not os.path.isdir(basedir):
                os.makedirs(basedir)
                logger.info('Created basedir %r', basedir)
        for datasubdir in [u'backgrounds', u'brushes', u'scratchpads']:
            datadir = os.path.join(state_dirs.user_data, datasubdir)
            if not os.path.isdir(datadir):
                os.mkdir(datadir)
                logger.info('Created data subdir %r', datadir)

        _init_icons(state_dirs.app_icons)

        # Core actions and menu structure
        ui_dir = os.path.dirname(os.path.abspath(__file__))
        resources_xml = join(ui_dir, "resources.xml")
        self.builder = Gtk.Builder()
        self.builder.set_translation_domain("mypaint")
        self.builder.add_from_file(resources_xml)

        self.ui_manager = self.builder.get_object("app_ui_manager")
        signal_callback_objs = [self]

        Gdk.set_program_class('MyPaint')

        self.pixmaps = PixbufDirectory(join(state_dirs.app_data, u'pixmaps'))
        self.cursor_color_picker = Gdk.Cursor.new_from_pixbuf(
            Gdk.Display.get_default(),
            self.pixmaps.cursor_color_picker,
            3,
            15,
        )
        self.cursor_color_picker_h = Gdk.Cursor.new_from_pixbuf(
            Gdk.Display.get_default(),
            self.pixmaps.cursor_color_picker_h,
            3,
            15,
        )
        self.cursor_color_picker_c = Gdk.Cursor.new_from_pixbuf(
            Gdk.Display.get_default(),
            self.pixmaps.cursor_color_picker_c,
            3,
            15,
        )
        self.cursor_color_picker_y = Gdk.Cursor.new_from_pixbuf(
            Gdk.Display.get_default(),
            self.pixmaps.cursor_color_picker_y,
            3,
            15,
        )
        self.cursors = gui.cursor.CustomCursorMaker(self)

        # App-level settings
        self._preferences = lib.observable.ObservableDict()
        self.load_settings()

        # Set up compatibility mode (most of it)
        self.compat_mode = None
        self.reset_compat_mode(update=False)
        compat.update_default_layer_type(self)

        # Unmanaged main brush.
        # Always the same instance (we can attach settings_observers).
        # This brush is where temporary changes (color, size...) happen.
        self.brush = brush.BrushInfo()
        self.brush.load_defaults()

        # Global pressure mapping function, ignored unless set
        self.pressure_mapping = None

        # Fake inputs to send when using a mouse.  Adjustable
        # via slider and/or hotkeys
        self.fakepressure = 0.5
        self.fakerotation = 0.5

        # Keyboard manager
        self.kbm = keyboard.KeyboardManager(self)

        # File I/O
        self.filehandler = filehandling.FileHandler(self)

        # Picking grabs
        self.context_grab = gui.picker.ContextPickingGrabPresenter()
        self.context_grab.app = self
        self.color_grab = gui.picker.ColorPickingGrabPresenter()
        self.color_grab.app = self

        # Load the main interface
        mypaint_main_xml = join(ui_dir, "mypaint.glade")
        self.builder.add_from_file(mypaint_main_xml)

        # Main drawing window
        self.drawWindow = self.builder.get_object("drawwindow")
        signal_callback_objs.append(self.drawWindow)

        # Workspace widget. Manages layout of toolwindows, and autohide in
        # fullscreen.
        wkspace = self.builder.get_object("app_workspace")
        wkspace.build_from_layout(self.preferences["workspace.layout"])
        wkspace.floating_window_created += self._floating_window_created_cb
        fs_autohide_action = self.builder.get_object("FullscreenAutohide")
        fs_autohide_action.set_active(wkspace.autohide_enabled)
        self.workspace = wkspace

        # Working document: viewer widget
        app_canvas = self.builder.get_object("app_canvas")

        # Working document: model and controller
        cache_size = self.preferences.get('ui.rendered_tile_cache_size',
                                          lib.cache.DEFAULT_CACHE_SIZE)
        default_stack_size = lib.document.DEFAULT_UNDO_STACK_SIZE
        undo_stack_size = self.preferences.setdefault(
            'command.max_undo_stack_size', default_stack_size)
        undo_stack_size = validation.validate(
            undo_stack_size, default_stack_size, int, lambda a: a > 0,
            "The undo stack size ({value}) must be a positive integer!")
        model = lib.document.Document(self.brush,
                                      cache_size=cache_size,
                                      max_undo_stack_size=undo_stack_size)
        self.doc = document.Document(self, app_canvas, model)
        app_canvas.set_model(model)

        signal_callback_objs.append(self.doc)
        signal_callback_objs.append(self.doc.modes)

        self.scratchpad_filename = ""
        scratchpad_model = lib.document.Document(
            self.brush,
            painting_only=True,
            cache_size=lib.cache.DEFAULT_CACHE_SIZE / 4)
        scratchpad_tdw = tileddrawwidget.TiledDrawWidget()
        scratchpad_tdw.scroll_on_allocate = False
        scratchpad_tdw.set_model(scratchpad_model)
        self.scratchpad_doc = document.Document(self, scratchpad_tdw,
                                                scratchpad_model)

        self.brushmanager = brushmanager.BrushManager(
            lib.config.mypaint_brushdir,
            join(self.state_dirs.user_data, 'brushes'),
            self,
        )

        signal_callback_objs.append(self.filehandler)
        self.brushmodifier = brushmodifier.BrushModifier(self)
        signal_callback_objs.append(self.brushmodifier)
        self.blendmodemanager = blendmodehandler.BlendModeManager(self)
        signal_callback_objs.append(self.blendmodemanager)
        self.blendmodemanager.register(self.brushmodifier.bm)

        self.line_mode_settings = linemode.LineModeSettings(self)

        # Button press mapping
        self.button_mapping = ButtonMapping()

        # Monitors pluggings and uses of input device, configures them,
        # and switches between device-specific brushes.
        self.device_monitor = gui.device.Monitor(self)

        if not self.preferences.get("scratchpad.last_opened_scratchpad", None):
            self.preferences["scratchpad.last_opened_scratchpad"] \
                = self.filehandler.get_scratchpad_autosave()
        self.scratchpad_filename \
            = self.preferences["scratchpad.last_opened_scratchpad"]

        self.brush_color_manager = BrushColorManager(self)
        self.brush_color_manager.set_picker_cursor(self.cursor_color_picker)
        self.brush_color_manager.set_data_path(self.datapath)

        #: Mapping of setting cname to a GtkAdjustment which controls the base
        #: value of that setting for the app's current brush.
        self.brush_adjustment = {}
        self.init_brush_adjustments()
        # Extend with some fake inputs that act kind of like brush settings
        self.fake_adjustment = {}

        # Connect signals defined in resources.xml
        callback_finder = CallbackFinder(signal_callback_objs)
        self.builder.connect_signals(callback_finder)

        self.kbm.start_listening()
        self.filehandler.doc = self.doc
        self.filehandler.filename = None
        Gtk.AccelMap.load(join(self.user_confpath, 'accelmap.conf'))

        # Load the default background image
        self.doc.reset_background()

        # Non-dockable subwindows
        # Loading is deferred as late as possible
        self._subwindow_classes = {
            # action-name: action-class
            "BackgroundWindow": backgroundwindow.BackgroundWindow,
            "BrushEditorWindow": brusheditor.BrushEditorWindow,
            "PreferencesWindow": preferenceswindow.PreferencesWindow,
            "InputTestWindow": inputtestwindow.InputTestWindow,
            "BrushIconEditorWindow": brushiconeditor.BrushIconEditorWindow,
        }
        self._subwindows = {}

        # Statusbar init
        statusbar = self.builder.get_object("app_statusbar")
        self.statusbar = statusbar
        context_id = statusbar.get_context_id("transient-message")
        self._transient_msg_context_id = context_id
        self._transient_msg_remove_timeout_id = None

        # Profiling & debug stuff
        self.profiler = gui.profiling.Profiler()

        # Show main UI.
        self.drawWindow.show_all()
        GLib.idle_add(self._at_application_start, filenames, fullscreen)
Beispiel #20
0
def _info(exctyp, value, tb):
    global exception_dialog_active
    if exctyp is KeyboardInterrupt:
        return original_excepthook(exctyp, value, tb)
    sys.stderr.write(analyse_simple(exctyp, value, tb).getvalue())
    if exception_dialog_active:
        return

    Gdk.pointer_ungrab(Gdk.CURRENT_TIME)
    Gdk.keyboard_ungrab(Gdk.CURRENT_TIME)

    exception_dialog_active = True
    # Create the dialog
    dialog = Gtk.MessageDialog(message_type=Gtk.MessageType.WARNING)
    dialog.set_title(_("Bug Detected"))

    primary = _(
        "<big><b>A programming error has been detected.</b></big>"
    )
    secondary = _(
        "You may be able to ignore this error and carry on working, "
        "but you should probably save your work soon.\n\n"
        "Please tell the developers about this using the issue tracker "
        "if no-one else has reported it yet."
    )
    dialog.set_markup(primary)
    dialog.format_secondary_text(secondary)

    dialog.add_button(_(u"Search Tracker…"), RESPONSE_SEARCH)
    if "-" in lib.meta.MYPAINT_VERSION:  # only development and prereleases
        dialog.add_button(_("Report…"), RESPONSE_REPORT)
        dialog.set_response_sensitive(RESPONSE_REPORT, False)
    dialog.add_button(_("Ignore Error"), Gtk.ResponseType.CLOSE)
    dialog.add_button(_("Quit MyPaint"), RESPONSE_QUIT)

    # Add an expander with details of the problem to the dialog
    def expander_cb(expander, *ignore):
        # Ensures that on deactivating the expander, the dialog is resized down
        if expander.get_expanded():
            dialog.set_resizable(True)
        else:
            dialog.set_resizable(False)
    details_expander = Gtk.Expander()
    details_expander.set_label(_(u"Details…"))
    details_expander.connect("notify::expanded", expander_cb)

    textview = Gtk.TextView()
    textview.show()
    textview.set_editable(False)
    textview.modify_font(Pango.FontDescription("Monospace normal"))

    sw = Gtk.ScrolledWindow()
    sw.show()
    sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
    sw.add(textview)

    # Set window sizing so that it's always at least 600 pixels wide, and
    # increases by 300 pixels in height once the details panel is open
    sw.set_size_request(0, 300)
    dialog.set_size_request(600, 0)

    details_expander.add(sw)
    details_expander.show_all()
    dialog.get_content_area().pack_start(details_expander, True, True, 0)

    # Get the traceback and set contents of the details
    try:
        trace = analyse(exctyp, value, tb).getvalue()
    except:
        try:
            trace = _("Exception while analyzing the exception.") + "\n"
            trace += analyse_simple(exctyp, value, tb).getvalue()
        except:
            trace = _("Exception while analyzing the exception.")
    buf = textview.get_buffer()
    trace = "\n".join(["```python", trace, "```"])
    buf.set_text(trace)
    ## Would be nice to scroll to the bottom automatically, but @#&%*@
    #first, last = buf.get_bounds()
    #buf.place_cursor(last)
    #mark = buf.get_insert()
    ##buf.scroll_mark_onscreen()
    ##textview.scroll_mark_onscreen(buf.get_insert(), 0)
    #textview.scroll_to_mark(mark, 0.0)

    # Connect callback and present the dialog
    dialog.connect('response', _dialog_response_cb, trace, exctyp, value)
    #dialog.set_modal(True) # this might actually be contra-productive...
    dialog.show()
def make_preview(thumb, preview_size):
    """Convert a layer's thumbnail into a nice preview image."""

    # Check size
    check_size = 2
    while check_size < (preview_size / 6) and check_size < 16:
        check_size *= 2

    blank = GdkPixbuf.Pixbuf.new(
        GdkPixbuf.Colorspace.RGB,
        True,
        8,
        preview_size,
        preview_size,
    )
    blank.fill(0x00000000)

    if thumb is None:
        thumb = blank

    # Make a square of chex
    preview = blank.composite_color_simple(
        dest_width=preview_size,
        dest_height=preview_size,
        interp_type=GdkPixbuf.InterpType.NEAREST,
        overall_alpha=255,
        check_size=check_size,
        color1=0xff707070,
        color2=0xff808080,
    )

    w = thumb.get_width()
    h = thumb.get_height()
    scale = preview_size / max(w, h)
    w *= scale
    h *= scale
    x = (preview_size - w) // 2
    y = (preview_size - h) // 2

    thumb.composite(
        dest=preview,
        dest_x=x,
        dest_y=y,
        dest_width=w,
        dest_height=h,
        offset_x=x,
        offset_y=y,
        scale_x=scale,
        scale_y=scale,
        interp_type=GdkPixbuf.InterpType.BILINEAR,
        overall_alpha=255,
    )

    # Add some very minor decorations..
    surf = cairo.ImageSurface(cairo.FORMAT_ARGB32, preview_size, preview_size)
    cr = cairo.Context(surf)
    Gdk.cairo_set_source_pixbuf(cr, preview, 0, 0)
    cr.paint()

    cr.set_source_rgba(1, 1, 1, 0.1)
    cr.rectangle(0.5, 0.5, preview_size - 1, preview_size - 1)
    cr.set_line_width(1.0)
    cr.stroke()

    surf.flush()

    preview = Gdk.pixbuf_get_from_surface(
        surf,
        0,
        0,
        preview_size,
        preview_size,
    )

    return preview
Beispiel #22
0
    def __init__(self, app, actions, config_name):
        """Initialize.

        :param app: the main Application object.
        :param iterable actions: keyboard action names to pass through.
        :param str config_name: config prefix for saving window size.

        Use a simple "lowercase_with_underscores" name for the
        configuration key prefix.

        See also: `gui.keyboard.KeyboardManager.add_window()`.
        """
        # Superclass
        Gtk.Window.__init__(self, type=Gtk.WindowType.POPUP)
        self.set_modal(True)

        # Internal state
        self.app = app
        self._size = None  # last recorded size from any show()
        self._motion_handler_id = None
        self._prefs_size_key = "%s.window_size" % (config_name, )
        self._resize_info = None  # state during an edge resize
        self._outside_grab_active = False
        self._outside_cursor = Gdk.Cursor(Gdk.CursorType.LEFT_PTR)
        self._popup_info = None

        # Initial positioning
        self._initial_move_pos = None  # used when forcing a specific position
        self._corrected_pos = None  # used when keeping the widget on-screen

        # Resize cursors
        self._edge_cursors = {}
        for edge, cursor in self.EDGE_CURSORS.items():
            if cursor is not None:
                cursor = Gdk.Cursor(cursor)
            self._edge_cursors[edge] = cursor

        # Default size
        self.set_gravity(Gdk.Gravity.NORTH_WEST)
        default_size = (self.MIN_WIDTH, self.MIN_HEIGHT)
        w, h = app.preferences.get(self._prefs_size_key, default_size)
        w = clamp(int(w), self.MIN_WIDTH, self.MAX_WIDTH)
        h = clamp(int(h), self.MIN_HEIGHT, self.MAX_HEIGHT)
        default_size = (w, h)
        self.set_transient_for(app.drawWindow)
        self.set_default_size(*default_size)
        self.set_position(Gtk.WindowPosition.MOUSE)

        # Register with the keyboard manager, but only let certain actions be
        # driven from the keyboard.
        app.kbm.add_window(self, actions)

        # Event handlers
        self.connect("realize", self._realize_cb)
        self.connect("configure-event", self._configure_cb)
        self.connect("enter-notify-event", self._crossing_cb)
        self.connect("leave-notify-event", self._crossing_cb)
        self.connect("show", self._show_cb)
        self.connect("hide", self._hide_cb)
        self.connect("button-press-event", self._button_press_cb)
        self.connect("button-release-event", self._button_release_cb)
        self.add_events(Gdk.EventMask.BUTTON_PRESS_MASK
                        | Gdk.EventMask.BUTTON_RELEASE_MASK)

        # Appearance
        self._frame = Gtk.Frame()
        self._frame.set_shadow_type(Gtk.ShadowType.OUT)
        self._align = Gtk.Alignment.new(0.5, 0.5, 1.0, 1.0)
        self._align.set_padding(self.EDGE_SIZE, self.EDGE_SIZE, self.EDGE_SIZE,
                                self.EDGE_SIZE)
        self._frame.add(self._align)
        Gtk.Window.add(self, self._frame)
    def _edit_dialog_key_press_cb(self, dialog, event, editable):
        if event.type != Gdk.EventType.KEY_PRESS:
            return False
        if event.is_modifier:
            return False
        if self._USE_NORMAL_DIALOG_KEYS:
            if event.keyval == Gdk.KEY_Return:
                dialog.response(Gtk.ResponseType.OK)
                return True
            elif event.keyval == Gdk.KEY_Escape:
                dialog.response(Gtk.ResponseType.CANCEL)
                return True
            elif event.keyval == Gdk.KEY_BackSpace:
                dialog.response(Gtk.ResponseType.REJECT)
                return True

        # Stolen from GTK 2.24's gtk/gtkmenu.c (gtk_menu_key_press())
        # Figure out what modifiers went into determining the key symbol
        keymap = Gdk.Keymap.get_default()
        bound, keyval, effective_group, level, consumed_modifiers = (
            keymap.translate_keyboard_state(
                event.hardware_keycode,
                event.state,
                # https://github.com/mypaint/mypaint/issues/974
                # event.group
                1))
        keyval = Gdk.keyval_to_lower(keyval)
        mods = Gdk.ModifierType(event.state
                                & Gtk.accelerator_get_default_mod_mask()
                                & ~consumed_modifiers)

        # If lowercasing affects the keysym, then we need to include
        # SHIFT in the modifiers. We re-upper case when we match against
        # the keyval, but display and save in caseless form.
        if keyval != event.keyval:
            mods |= Gdk.ModifierType.SHIFT_MASK
        accel_label = Gtk.accelerator_get_label(keyval, mods)
        # So we get (<Shift>j, Shift+J) but just (plus, +). As I
        # understand it.

        # This is rejecting some legit key combinations such as the
        # arrowkeys, so I had to remove it...
        #   if not Gtk.accelerator_valid(keyval, mods)
        #       return True

        clash_accel_path = None
        clash_action_label = None
        for path, kv, m, changed in self._get_accel_map_entries():
            if (kv, m) == (keyval, mods):
                clash_accel_path = path
                clash_action_label = _udecode(
                    self._action_labels.get(
                        clash_accel_path,
                        # TRANSLATORS: Part of the keybinding dialog, refers
                        # TRANSLATORS: to an action bound to a key combination.
                        _(u"Unknown Action"),
                    ))
                break
        if clash_accel_path == dialog.accel_path:  # no change
            self._edit_dialog_set_standard_hint(dialog)
            label = str(accel_label)
            dialog.accel_label_widget.set_text(label)
        elif clash_accel_path:
            markup_tmpl = _(
                # TRANSLATORS: Warning message when attempting to assign a
                # TRANSLATORS: keyboard combination that is already used.
                u"<b>{accel} is already in use for '{action}'. "
                u"The existing assignment will be replaced.</b>")
            markup = markup_tmpl.format(
                accel=lib.xml.escape(accel_label),
                action=lib.xml.escape(clash_action_label),
            )
            self._edit_dialog_set_hint(dialog, markup)
            label = u"%s (replace)" % (accel_label, )
            dialog.accel_label_widget.set_text(str(label))
        else:
            self._edit_dialog_set_standard_hint(dialog)
            label = u"%s (changed)" % (accel_label, )
            dialog.accel_label_widget.set_text(label)
        dialog.result_mods = mods
        dialog.result_keyval = keyval
        return True
Beispiel #24
0
 def rgba_or_none(tup):
     return (tup is not None) and Gdk.RGBA(*tup) or None
Beispiel #25
0
def load_symbolic_icon(icon_name,
                       size,
                       fg=None,
                       success=None,
                       warning=None,
                       error=None,
                       outline=None):
    """More Pythonic wrapper for gtk_icon_info_load_symbolic() etc.

    :param str icon_name: Name of the symbolic icon to render
    :param int size: Pixel size to render at
    :param tuple fg: foreground color (rgba tuple, values in [0..1])
    :param tuple success: success color (rgba tuple, values in [0..1])
    :param tuple warning: warning color (rgba tuple, values in [0..1])
    :param tuple error: error color (rgba tuple, values in [0..1])
    :param tuple outline: outline color (rgba tuple, values in [0..1])
    :returns: The rendered symbolic icon
    :rtype: GdkPixbuf.Pixbuf

    If the outline color is specified, a single-pixel outline is faked
    for the icon. Outlined renderings require a size 2 pixels larger
    than non-outlined if the central icon is to be of the same size.

    The returned value should be cached somewhere.

    """
    theme = Gtk.IconTheme.get_default()
    if outline is not None:
        size -= 2
    info = theme.lookup_icon(icon_name, size, Gtk.IconLookupFlags(0))

    def rgba_or_none(tup):
        return (tup is not None) and Gdk.RGBA(*tup) or None

    icon_pixbuf, was_symbolic = info.load_symbolic(
        fg=rgba_or_none(fg),
        success_color=rgba_or_none(success),
        warning_color=rgba_or_none(warning),
        error_color=rgba_or_none(error),
    )
    assert was_symbolic
    if outline is None:
        return icon_pixbuf

    result = GdkPixbuf.Pixbuf.new(
        GdkPixbuf.Colorspace.RGB,
        True,
        8,
        size + 2,
        size + 2,
    )
    result.fill(0x00000000)
    outline_rgba = list(outline)
    outline_rgba[3] /= 3.0
    outline_rgba = Gdk.RGBA(*outline_rgba)
    outline_stamp, was_symbolic = info.load_symbolic(
        fg=outline_rgba,
        success_color=outline_rgba,
        warning_color=outline_rgba,
        error_color=outline_rgba,
    )
    w = outline_stamp.get_width()
    h = outline_stamp.get_height()
    assert was_symbolic
    offsets = [
        (-1, -1),
        (0, -1),
        (1, -1),
        (-1, 0),
        (1, 0),  # noqa: E241 (it's clearer)
        (-1, 1),
        (0, 1),
        (1, 1),
    ]
    for dx, dy in offsets:
        outline_stamp.composite(
            result,
            dx + 1,
            dy + 1,
            w,
            h,
            dx + 1,
            dy + 1,
            1,
            1,
            GdkPixbuf.InterpType.NEAREST,
            255,
        )
    icon_pixbuf.composite(
        result,
        1,
        1,
        w,
        h,
        1,
        1,
        1,
        1,
        GdkPixbuf.InterpType.NEAREST,
        255,
    )
    return result
Beispiel #26
0
def render_brush_preview_pixbuf(brushinfo, max_edge_tiles=4):
    """Renders brush preview images

    :param BrushInfo brushinfo: settings to render
    :param int max_edge_tiles: Use at most this many tiles along an edge
    :returns: Preview image, at 128x128 pixels
    :rtype: GdkPixbuf

    This generates the preview image (128px icon) used for brushes which
    don't have saved ones. These include brushes picked from .ORA files
    where the parent_brush_name doesn't correspond to a brush in the
    user's MyPaint brushes - they're used as the default, and for the
    Auto button in the Brush Icon editor.

    Brushstrokes are inherently unpredictable in size, so the allowable
    area is grown until the brush fits or until the rendering becomes
    too big. `max_edge_tiles` limits this growth.
    """
    assert max_edge_tiles >= 1
    brushinfo = brushinfo.clone()  # avoid capturing a ref
    brush = Brush(brushinfo)
    surface = lib.tiledsurface.Surface()
    n = lib.tiledsurface.N
    for size_in_tiles in range(1, max_edge_tiles):
        width = n * size_in_tiles
        height = n * size_in_tiles
        surface.clear()
        fg, spiral = _brush_preview_bg_fg(surface, size_in_tiles, brushinfo)
        brushinfo.set_color_rgb(fg)
        brush.reset()
        # Curve
        shape = _variable_pressure_scribble(width, height, size_in_tiles)
        surface.begin_atomic()
        for dt, x, y, p, xt, yt in shape:
            brush.stroke_to(surface.backend, x, y, p, xt, yt, dt, 1.0, 0.0,
                            0.0)
        surface.end_atomic()
        # Check rendered size
        tposs = surface.tiledict.keys()

        outside = min({tx for tx, ty in tposs}) < 0
        outside = outside or (min({ty for tx, ty in tposs}) < 0)
        outside = outside or (max({tx for tx, ty in tposs}) >= size_in_tiles)
        outside = outside or (max({ty for tx, ty in tposs}) >= size_in_tiles)

        if not outside:
            break
    # Convert to pixbuf at the right scale
    rect = (0, 0, width, height)
    pixbuf = render_as_pixbuf(surface, *rect, alpha=True)
    if max(width, height) != 128:
        interp = (GdkPixbuf.InterpType.NEAREST if max(width, height) < 128 else
                  GdkPixbuf.InterpType.BILINEAR)
        pixbuf = pixbuf.scale_simple(128, 128, interp)
    # Composite over a checquered bg via Cairo: shows erases
    size = gui.style.ALPHA_CHECK_SIZE
    nchecks = int(128 // size)
    cairo_surf = cairo.ImageSurface(cairo.FORMAT_ARGB32, 128, 128)
    cr = cairo.Context(cairo_surf)
    render_checks(cr, size, nchecks)
    Gdk.cairo_set_source_pixbuf(cr, pixbuf, 0, 0)
    cr.paint()
    cairo_surf.flush()
    return Gdk.pixbuf_get_from_surface(cairo_surf, 0, 0, 128, 128)
Beispiel #27
0
def _image_surface_to_pixbuf(surf):
    """Convert a Cairo surface to a GdkPixbuf"""
    w = surf.get_width()
    h = surf.get_height()
    return Gdk.pixbuf_get_from_surface(surf, 0, 0, w, h)