1# This file is part of MyPaint.
2# Copyright (C) 2011-2018 by the MyPaint Development Team.
3# Copyright (C) 2007-2012 by Martin Renold <martinxyz@gmx.ch>
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation; either version 2 of the License, or
8# (at your option) any later version.
9
10"""Core layer classes etc."""
11
12
13## Imports
14
15from __future__ import division, print_function
16
17import logging
18import os
19import xml.etree.ElementTree as ET
20import weakref
21from warnings import warn
22import abc
23
24from lib.gettext import C_
25import lib.mypaintlib
26import lib.strokemap
27import lib.helpers as helpers
28import lib.fileutils
29import lib.pixbuf
30from lib.modes import PASS_THROUGH_MODE
31from lib.modes import STANDARD_MODES
32from lib.modes import ORA_MODES_BY_OPNAME
33from lib.modes import MODES_EFFECTIVE_AT_ZERO_ALPHA
34from lib.modes import MODES_DECREASING_BACKDROP_ALPHA
35import lib.modes
36import lib.xml
37import lib.tiledsurface
38from .rendering import Renderable
39from lib.pycompat import unicode
40
41logger = logging.getLogger(__name__)
42
43
44## Base class defs
45
46
47class LayerBase (Renderable):
48    """Base class defining the layer API
49
50    Layers support the Renderable interface, and are rendered with the
51    "render_*()" methods of their root layer stack.
52
53    Layers are minimally aware of the tree structure they reside in, in
54    that they contain a reference to the root of their tree for
55    signalling purposes.  Updates to the tree structure and to layers'
56    graphical contents are announced via the RootLayerStack object
57    representing the base of the tree.
58
59    """
60
61    ## Class constants
62
63    #: Forms the default name, may be suffixed per lib.naming consts.
64    DEFAULT_NAME = C_(
65        "layer default names",
66        u"Layer",
67    )
68
69    #: A string for the layer type.
70    TYPE_DESCRIPTION = None
71
72    PERMITTED_MODES = set(STANDARD_MODES)
73
74    ## Construction, loading, other lifecycle stuff
75
76    def __init__(self, name=None, **kwargs):
77        """Construct a new layer
78
79        :param name: The name for the new layer.
80        :param **kwargs: Ignored.
81
82        All layer subclasses must permit construction without
83        parameters.
84        """
85        super(LayerBase, self).__init__()
86        # Defaults for the notifiable properties
87        self._opacity = 1.0
88        self._name = name
89        self._visible = True
90        self._locked = False
91        self._mode = lib.modes.default_mode()
92        self._group_ref = None
93        self._root_ref = None
94        self._thumbnail = None
95        #: True if the layer was marked as selected when loaded.
96        self.initially_selected = False
97
98    @classmethod
99    def new_from_openraster(cls, orazip, elem, cache_dir, progress,
100                            root, x=0, y=0, **kwargs):
101        """Reads and returns a layer from an OpenRaster zipfile
102
103        This implementation just creates a new instance of its class and
104        calls `load_from_openraster()` on it. This should suffice for
105        all subclasses which support parameterless construction.
106        """
107
108        layer = cls()
109        layer.load_from_openraster(
110            orazip,
111            elem,
112            cache_dir,
113            progress,
114            x=x, y=y,
115            **kwargs
116        )
117        return layer
118
119    @classmethod
120    def new_from_openraster_dir(cls, oradir, elem, cache_dir, progress,
121                                root, x=0, y=0, **kwargs):
122        """Reads and returns a layer from an OpenRaster-like folder
123
124        This implementation just creates a new instance of its class and
125        calls `load_from_openraster_dir()` on it. This should suffice
126        for all subclasses which support parameterless construction.
127
128        """
129        layer = cls()
130        layer.load_from_openraster_dir(
131            oradir,
132            elem,
133            cache_dir,
134            progress,
135            x=x, y=y,
136            **kwargs
137        )
138        return layer
139
140    def load_from_openraster(self, orazip, elem, cache_dir, progress,
141                             x=0, y=0, **kwargs):
142        """Loads layer data from an open OpenRaster zipfile
143
144        :param orazip: An OpenRaster zipfile, opened for extracting
145        :type orazip: zipfile.ZipFile
146        :param elem: <layer/> or <stack/> element to load (stack.xml)
147        :type elem: xml.etree.ElementTree.Element
148        :param cache_dir: Cache root dir for this document
149        :param progress: Provides feedback to the user.
150        :type progress: lib.feedback.Progress or None
151        :param x: X offset of the top-left point for image data
152        :param y: Y offset of the top-left point for image data
153        :param **kwargs: Extensibility
154
155        The base implementation loads the common layer flags from a `<layer/>`
156        or `<stack/>` element, but does nothing more than that. Loading layer
157        data from the zipfile or recursing into stack contents is deferred to
158        subclasses.
159        """
160        self._load_common_flags_from_ora_elem(elem)
161
162    def load_from_openraster_dir(self, oradir, elem, cache_dir, progress,
163                                 x=0, y=0, **kwargs):
164        """Loads layer data from an OpenRaster-style folder.
165
166        Parameters are the same as for load_from_openraster, with the
167        following exception (replacing ``orazip``):
168
169        :param unicode/str oradir: Folder with a .ORA-like tree structure.
170
171        """
172        self._load_common_flags_from_ora_elem(elem)
173
174    def _load_common_flags_from_ora_elem(self, elem):
175        attrs = elem.attrib
176        self.name = unicode(attrs.get('name', ''))
177        compop = str(attrs.get('composite-op', ''))
178        self.mode = ORA_MODES_BY_OPNAME.get(compop, lib.modes.default_mode())
179        self.opacity = helpers.clamp(float(attrs.get('opacity', '1.0')),
180                                     0.0, 1.0)
181        visible = attrs.get('visibility', 'visible').lower()
182        self.visible = (visible != "hidden")
183        locked = attrs.get("edit-locked", 'false').lower()
184        self.locked = lib.xml.xsd2bool(locked)
185        selected = attrs.get("selected", 'false').lower()
186        self.initially_selected = lib.xml.xsd2bool(selected)
187
188    def __deepcopy__(self, memo):
189        """Returns an independent copy of the layer, for Duplicate Layer
190
191        >>> from copy import deepcopy
192        >>> orig = _StubLayerBase()
193        >>> dup = deepcopy(orig)
194
195        Everything about the returned layer must be a completely
196        independent copy of the original layer.  If the copy can be
197        worked on, working on it must leave the original unaffected.
198        This base implementation can be reused/extended by subclasses if
199        they support zero-argument construction. It will use the derived
200        class's snapshotting implementation (see `save_snapshot()` and
201        `load_snapshot()`) to populate the copy.
202        """
203        layer = self.__class__()
204        layer.load_snapshot(self.save_snapshot())
205        return layer
206
207    def clear(self):
208        """Clears the layer"""
209        pass
210
211    ## Properties
212
213    @property
214    def group(self):
215        """The group of the current layer.
216
217        Returns None if the layer is not in a group.
218
219        >>> from . import group
220        >>> outer = group.LayerStack()
221        >>> inner = group.LayerStack()
222        >>> scribble = _StubLayerBase()
223        >>> outer.append(inner)
224        >>> inner.append(scribble)
225        >>> outer.group is None
226        True
227        >>> inner.group == outer
228        True
229        >>> scribble.group == inner
230        True
231        """
232        if self._group_ref is not None:
233            return self._group_ref()
234        return None
235
236    @group.setter
237    def group(self, group):
238        if group is None:
239            self._group_ref = None
240        else:
241            self._group_ref = weakref.ref(group)
242
243    @property
244    def root(self):
245        """The root of the layer tree structure
246
247        Only RootLayerStack instances or None are permitted.
248        You won't normally need to adjust this unless you're doing
249        something fancy: it's automatically maintained by intermediate
250        and root `LayerStack` elements in the tree whenever layers are
251        added or removed from a rooted tree structure.
252
253        >>> from . import tree
254        >>> root = tree.RootLayerStack(doc=None)
255        >>> layer = _StubLayerBase()
256        >>> root.append(layer)
257        >>> layer.root                 #doctest: +ELLIPSIS
258        <RootLayerStack...>
259        >>> layer.root is root
260        True
261
262        """
263        if self._root_ref is not None:
264            return self._root_ref()
265        return None
266
267    @root.setter
268    def root(self, newroot):
269        if newroot is None:
270            self._root_ref = None
271        else:
272            self._root_ref = weakref.ref(newroot)
273
274    @property
275    def opacity(self):
276        """Opacity multiplier for the layer.
277
278        Values must permit conversion to a `float` in [0, 1].
279        Changing this property issues ``layer_properties_changed`` and
280        appropriate ``layer_content_changed`` notifications via the root
281        layer stack if the layer is within a tree structure.
282
283        Layers with a `mode` of `PASS_THROUGH_MODE` have immutable
284        opacities: the value is always 100%. This restriction only
285        applies to `LayerStack`s - i.e. layer groups - because those are
286        the only kinds of layer which can be put into pass-through mode.
287        """
288        return self._opacity
289
290    @opacity.setter
291    def opacity(self, opacity):
292        opacity = helpers.clamp(float(opacity), 0.0, 1.0)
293        if opacity == self._opacity:
294            return
295        if self.mode == PASS_THROUGH_MODE:
296            warn("Cannot change the change the opacity multiplier "
297                 "of a layer group in PASS_THROUGH_MODE",
298                 RuntimeWarning, stacklevel=2)
299            return
300        self._opacity = opacity
301        self._properties_changed(["opacity"])
302        # Note: not the full_redraw_bbox here.
303        # Changing a layer's opacity multiplier alone cannot change the
304        # calculated alpha of an outlying empty tile in the layer.
305        # Those are always zero. Even if the layer has a fancy masking
306        # mode, that won't affect redraws arising from mere opacity
307        # multiplier updates.
308        bbox = tuple(self.get_bbox())
309        self._content_changed(*bbox)
310
311    @property
312    def name(self):
313        """The layer's name, for display purposes
314
315        Values must permit conversion to a unicode string.  If the
316        layer is part of a tree structure, ``layer_properties_changed``
317        notifications will be issued via the root layer stack. In
318        addition, assigned names may be corrected to be unique within
319        the tree.
320        """
321        return self._name
322
323    @name.setter
324    def name(self, name):
325        if name is not None:
326            name = unicode(name)
327        else:
328            name = self.DEFAULT_NAME
329        oldname = self._name
330        self._name = name
331        root = self.root
332        if root is not None:
333            self._name = root.get_unique_name(self)
334        if self._name != oldname:
335            self._properties_changed(["name"])
336
337    @property
338    def visible(self):
339        """Whether the layer has a visible effect on its backdrop.
340
341        Some layer modes normally have an effect even if the calculated
342        alpha of a pixel is zero. This switch turns that off too.
343
344        Values must permit conversion to a `bool`.
345        Changing this property issues ``layer_properties_changed`` and
346        appropriate ``layer_content_changed`` notifications via the root
347        layer stack if the layer is within a tree structure.
348        """
349        return self._visible
350
351    @visible.setter
352    def visible(self, visible):
353        visible = bool(visible)
354        if visible == self._visible:
355            return
356        self._visible = visible
357        self._properties_changed(["visible"])
358        # Toggling the visibility flag always causes the mode to stop
359        # or start having its normal effect. Need the full redraw bbox
360        # so that outlying empty tiles will be updated properly.
361        bbox = tuple(self.get_full_redraw_bbox())
362        self._content_changed(*bbox)
363
364    @property
365    def branch_visible(self):
366        """Check whether the layer's branch is visible.
367
368        Returns True if the layer's group and all of its parents are visible,
369        False otherwise.
370
371        Returns True if the layer is not in a group.
372
373        >>> from . import group
374        >>> outer = group.LayerStack()
375        >>> inner = group.LayerStack()
376        >>> scribble = _StubLayerBase()
377        >>> outer.append(inner)
378        >>> inner.append(scribble)
379        >>> outer.branch_visible
380        True
381        >>> inner.branch_visible
382        True
383        >>> scribble.branch_visible
384        True
385        >>> outer.visible = False
386        >>> outer.branch_visible
387        True
388        >>> inner.branch_visible
389        False
390        >>> scribble.branch_visible
391        False
392        """
393        group = self.group
394        if group is None:
395            return True
396
397        return group.visible and group.branch_visible
398
399    @property
400    def locked(self):
401        """Whether the layer is locked (immutable).
402
403        Values must permit conversion to a `bool`.
404        Changing this property issues `layer_properties_changed` via the
405        root layer stack if the layer is within a tree structure.
406
407        """
408        return self._locked
409
410    @locked.setter
411    def locked(self, locked):
412        locked = bool(locked)
413        if locked != self._locked:
414            self._locked = locked
415            self._properties_changed(["locked"])
416
417    @property
418    def branch_locked(self):
419        """Check whether the layer's branch is locked.
420
421        Returns True if the layer's group or at least one of its parents
422        is locked, False otherwise.
423
424        Returns False if the layer is not in a group.
425
426        >>> from . import group
427        >>> outer = group.LayerStack()
428        >>> inner = group.LayerStack()
429        >>> scribble = _StubLayerBase()
430        >>> outer.append(inner)
431        >>> inner.append(scribble)
432        >>> outer.branch_locked
433        False
434        >>> inner.branch_locked
435        False
436        >>> scribble.branch_locked
437        False
438        >>> outer.locked = True
439        >>> outer.branch_locked
440        False
441        >>> inner.branch_locked
442        True
443        >>> scribble.branch_locked
444        True
445        """
446        group = self.group
447        if group is None:
448            return False
449
450        return group.locked or group.branch_locked
451
452    @property
453    def mode(self):
454        """How this layer combines with its backdrop.
455
456        Values must permit conversion to an int, and must be permitted
457        for the mode's class.
458
459        Changing this property issues ``layer_properties_changed`` and
460        appropriate ``layer_content_changed`` notifications via the root
461        layer stack if the layer is within a tree structure.
462
463        In addition to the modes supported by the base implementation,
464        layer groups permit `lib.modes.PASS_THROUGH_MODE`, an
465        additional mode where group contents are rendered as if their
466        group were not present. Setting the mode to this value also
467        sets the opacity to 100%.
468
469        For layer groups, "Normal" mode implies group isolation
470        internally. These semantics differ from those of OpenRaster and
471        the W3C, but saving and loading applies the appropriate
472        transformation.
473
474        See also: PERMITTED_MODES.
475
476        """
477        return self._mode
478
479    @mode.setter
480    def mode(self, mode):
481        mode = int(mode)
482        if mode not in self.PERMITTED_MODES:
483            mode = lib.modes.default_mode()
484        if mode == self._mode:
485            return
486        # Forcing the opacity for layer groups here allows a redraw to
487        # be subsumed. Only layer groups permit PASS_THROUGH_MODE.
488        propchanges = []
489        if mode == PASS_THROUGH_MODE:
490            self._opacity = 1.0
491            propchanges.append("opacity")
492        # When changing the mode, the before and after states may have
493        # different treatments of outlying empty tiles. Need the full
494        # redraw bboxes of both states to ensure correct redraws.
495        redraws = [self.get_full_redraw_bbox()]
496        self._mode = mode
497        redraws.append(self.get_full_redraw_bbox())
498        self._content_changed(*tuple(combine_redraws(redraws)))
499        propchanges.append("mode")
500        self._properties_changed(propchanges)
501
502    ## Notifications
503
504    def _content_changed(self, *args):
505        """Notifies the root's content observers
506
507        If this layer's root stack is defined, i.e. if it is part of a
508        tree structure, the root's `layer_content_changed()` event
509        method will be invoked with this layer and the supplied
510        arguments. This reflects a region of pixels in the document
511        changing.
512        """
513        root = self.root
514        if root is not None:
515            root.layer_content_changed(self, *args)
516
517    def _properties_changed(self, properties):
518        """Notifies the root's layer properties observers
519
520        If this layer's root stack is defined, i.e. if it is part of a
521        tree structure, the root's `layer_properties_changed()` event
522        method will be invoked with the layer and the supplied
523        arguments. This reflects details about the layer like its name
524        or its locked status changing.
525        """
526        root = self.root
527        if root is not None:
528            root._notify_layer_properties_changed(self, set(properties))
529
530    ## Info methods
531
532    def get_icon_name(self):
533        """The name of the icon to display for the layer
534
535        Ideally symbolic. A value of `None` means that no icon should be
536        displayed.
537        """
538        return None
539
540    @property
541    def effective_opacity(self):
542        """The opacity used when rendering a layer: zero if invisible
543
544        This must match the appearance produced by the layer's
545        Renderable.get_render_ops() implementation when it is called
546        with no explicit "layers" specification. The base class's
547        effective opacity is zero because the base get_render_ops() is
548        unimplemented.
549
550        """
551        return 0.0
552
553    def get_alpha(self, x, y, radius):
554        """Gets the average alpha within a certain radius at a point
555
556        :param x: model X coordinate
557        :param y: model Y coordinate
558        :param radius: radius over which to average
559        :rtype: float
560
561        The return value is not affected by the layer opacity, effective or
562        otherwise. This is used by `Document.pick_layer()` and friends to test
563        whether there's anything significant present at a particular point.
564        The default alpha at a point is zero.
565        """
566        return 0.0
567
568    def get_bbox(self):
569        """Returns the inherent (data) bounding box of the layer
570
571        :rtype: lib.helpers.Rect
572
573        The returned rectangle is generally tile-aligned, but isn't
574        required to be. In this base implementation, the returned bbox
575        is a zero-size default Rect, which is also how a full redraw is
576        signalled. Subclasses should override this with a better
577        implementation.
578
579        The data bounding box is used for certain classes of redraws.
580        See also get_full_redraw_bbox().
581
582        """
583        return helpers.Rect()
584
585    def get_full_redraw_bbox(self):
586        """Gets the full update notification bounding box of the layer
587
588        :rtype: lib.helpers.Rect
589
590        This is the appropriate bounding box for redraws if a layer-wide
591        property like visibility or combining mode changes.
592
593        Normally this is the layer's inherent data bounding box, which
594        allows the GUI to skip outlying empty tiles when redrawing the
595        layer stack.  If instead the layer's compositing mode dictates
596        that a calculated pixel alpha of zero would affect the backdrop
597        regardless - something that's true of certain masking modes -
598        then the returned bbox is a zero-size rectangle, which is the
599        signal for a full redraw.
600
601        See also get_bbox().
602
603        """
604        if self.mode in MODES_EFFECTIVE_AT_ZERO_ALPHA:
605            return helpers.Rect()
606        else:
607            return self.get_bbox()
608
609    def is_empty(self):
610        """Tests whether the surface is empty
611
612        Always true in the base implementation.
613        """
614        return True
615
616    def get_paintable(self):
617        """True if this layer currently accepts painting brushstrokes
618
619        Always false in the base implementation.
620        """
621        return False
622
623    def get_fillable(self):
624        """True if this layer currently accepts flood fill
625
626        Always false in the base implementation.
627        """
628        return False
629
630    def get_stroke_info_at(self, x, y):
631        """Return the brushstroke at a given point
632
633        :param x: X coordinate to pick from, in model space.
634        :param y: Y coordinate to pick from, in model space.
635        :rtype: lib.strokemap.StrokeShape or None
636
637        Returns None for the base class.
638        """
639        return None
640
641    def get_last_stroke_info(self):
642        """Return the most recently painted stroke
643
644        :rtype lib.strokemap.StrokeShape or None
645
646        Returns None for the base class.
647        """
648        return None
649
650    def get_mode_normalizable(self):
651        """True if this layer can be normalized"""
652        unsupported = set(MODES_EFFECTIVE_AT_ZERO_ALPHA)
653        # Normalizing would have to make an infinite number of tiles
654        unsupported.update(MODES_DECREASING_BACKDROP_ALPHA)
655        # Normal mode cannot decrease the bg's alpha
656        return self.mode not in unsupported
657
658    def get_trimmable(self):
659        """True if this layer currently accepts trim()"""
660        return False
661
662    def has_interesting_name(self):
663        """True if the layer looks as if it has a user-assigned name
664
665        Interesting means non-blank, and not the default name or a
666        numbered version of it. This is used when merging layers: Merge
667        Down is used on temporary layers a lot, and those probably have
668        boring names.
669        """
670        name = self._name
671        if name is None or name.strip() == '':
672            return False
673        if name == self.DEFAULT_NAME:
674            return False
675        match = lib.naming.UNIQUE_NAME_REGEX.match(name)
676        if match is not None:
677            base = unicode(match.group("name"))
678            if base == self.DEFAULT_NAME:
679                return False
680        return True
681
682    ## Flood fill
683
684    def flood_fill(self, fill_args, dst_layer=None):
685        """Fills a point on the surface with a color
686
687        See PaintingLayer.flood_fill() for parameters and semantics.
688        The base implementation does nothing.
689
690        """
691        pass
692
693    ## Rendering
694
695    def get_tile_coords(self):
696        """Returns all data tiles in this layer
697
698        :returns: All tiles with data
699        :rtype: sequence
700
701        This method should return a sequence listing the coordinates for
702        all tiles with data in this layer.
703
704        It is used when computing layer merges.  Tile coordinates must
705        be returned as ``(tx, ty)`` pairs.
706
707        The base implementation returns an empty sequence.
708        """
709        return []
710
711    ## Translation
712
713    def get_move(self, x, y):
714        """Get a translation/move object for this layer
715
716        :param x: Model X position of the start of the move
717        :param y: Model X position of the start of the move
718        :returns: A move object
719        """
720        raise NotImplementedError
721
722    def translate(self, dx, dy):
723        """Translate a layer non-interactively
724
725        :param dx: Horizontal offset in model coordinates
726        :param dy: Vertical offset in model coordinates
727        :returns: full redraw bboxes for the move: ``[before, after]``
728        :rtype: list
729
730        The base implementation uses `get_move()` and the object it returns.
731        """
732        update_bboxes = [self.get_full_redraw_bbox()]
733        move = self.get_move(0, 0)
734        move.update(dx, dy)
735        move.process(n=-1)
736        move.cleanup()
737        update_bboxes.append(self.get_full_redraw_bbox())
738        return update_bboxes
739
740    ## Standard stuff
741
742    def __repr__(self):
743        """Simplified repr() of a layer"""
744        if self.name:
745            return "<%s %r>" % (self.__class__.__name__, self.name)
746        else:
747            return "<%s>" % (self.__class__.__name__)
748
749    def __nonzero__(self):
750        """Layers are never false in Py2."""
751        return self.__bool__()
752
753    def __bool__(self):
754        """Layers are never false in Py3.
755
756        >>> sample = _StubLayerBase()
757        >>> bool(sample)
758        True
759
760        """
761        return True
762
763    def __eq__(self, layer):
764        """Two layers are only equal if they are the same object
765
766        This is meaningful during layer repositions in the GUI, where
767        shallow copies are used.
768        """
769        return self is layer
770
771    def __hash__(self):
772        """Return a hash for the layer (identity only)"""
773        return id(self)
774
775    ## Saving
776
777    def save_as_png(self, filename, *rect, **kwargs):
778        """Save to a named PNG file
779
780        :param filename: filename to save to
781        :param *rect: rectangle to save, as a 4-tuple
782        :param **kwargs: passthrough opts for underlying implementations
783        :rtype: Gdk.Pixbuf
784
785        The base implementation does nothing.
786        """
787        pass
788
789    def save_to_openraster(self, orazip, tmpdir, path,
790                           canvas_bbox, frame_bbox, **kwargs):
791        """Saves the layer's data into an open OpenRaster ZipFile
792
793        :param orazip: a `zipfile.ZipFile` open for write
794        :param tmpdir: path to a temp dir, removed after the save
795        :param path: Unique path of the layer, for encoding in filenames
796        :type path: tuple of ints
797        :param canvas_bbox: Bounding box of all layers, absolute coords
798        :type canvas_bbox: tuple
799        :param frame_bbox: Bounding box of the image being saved
800        :type frame_bbox: tuple
801        :param **kwargs: Keyword args used by the save implementation
802        :returns: element describing data written
803        :rtype: xml.etree.ElementTree.Element
804
805        There are three bounding boxes which need to considered. The
806        inherent bbox of the layer as returned by `get_bbox()` is always
807        tile aligned and refers to absolute model coordinates, as is
808        `canvas_bbox`.
809
810        All of the above bbox's coordinates are defined relative to the
811        canvas origin. However, when saving, the data written must be
812        translated so that `frame_bbox`'s top left corner defines the
813        origin (0, 0), of the saved OpenRaster file. The width and
814        height of `frame_bbox` determine the saved image's dimensions.
815
816        More than one file may be written to the zipfile. The etree
817        element returned should describe everything that was written.
818
819        Paths must be unique sequences of ints, but are not necessarily
820        valid RootLayerStack paths. It's faked for the normally
821        unaddressable background layer right now, for example.
822        """
823        raise NotImplementedError
824
825    def _get_stackxml_element(self, tag, x=None, y=None):
826        """Internal: get a basic etree Element for .ora saving"""
827
828        elem = ET.Element(tag)
829        attrs = elem.attrib
830        if self.name:
831            attrs["name"] = str(self.name)
832        if x is not None:
833            attrs["x"] = str(x)
834        if y is not None:
835            attrs["y"] = str(y)
836        attrs["opacity"] = str(self.opacity)
837        if self.initially_selected:
838            attrs["selected"] = "true"
839        if self.locked:
840            attrs["edit-locked"] = "true"
841        if self.visible:
842            attrs["visibility"] = "visible"
843        else:
844            attrs["visibility"] = "hidden"
845        # NOTE: This *will* be wrong for the PASS_THROUGH_MODE case.
846        # NOTE: LayerStack will need to override this attr.
847        mode_info = lib.mypaintlib.combine_mode_get_info(self.mode)
848        if mode_info is not None:
849            compop = mode_info.get("name")
850            if compop is not None:
851                attrs["composite-op"] = str(compop)
852        return elem
853
854    ## Painting symmetry axis
855
856    def set_symmetry_state(self, active, center_x, center_y,
857                           symmetry_type, rot_symmetry_lines):
858        """Set the surface's painting symmetry axis and active flag.
859
860        :param bool active: Whether painting should be symmetrical.
861        :param int center_x: X coord of the axis of symmetry.
862        :param int center_y: Y coord of the axis of symmetry.
863        :param int symmetry_type: symmetry type that will be applied if active
864        :param int rot_symmetry_lines: number of rotational
865            symmetry lines for angle dependent symmetry modes.
866
867        The symmetry axis is only meaningful to paintable layers.
868        Received strokes are reflected along the line ``x=center_x``
869        when symmetrical painting is active.
870
871        This method is used by RootLayerStack only,
872        propagating a central shared flag and value to all layers.
873
874        The base implementation does nothing.
875        """
876        pass
877
878    ## Snapshot
879
880    def save_snapshot(self):
881        """Snapshots the state of the layer, for undo purposes
882
883        The returned data should be considered opaque, useful only as a
884        memento to be restored with load_snapshot().
885        """
886        return LayerBaseSnapshot(self)
887
888    def load_snapshot(self, sshot):
889        """Restores the layer from snapshot data"""
890        sshot.restore_to_layer(self)
891
892    ## Thumbnails
893
894    @property
895    def thumbnail(self):
896        """The layer's cached preview thumbnail.
897
898        :rtype: GdkPixbuf.Pixbuf or None
899
900        Thumbnail pixbufs are always 256x256 pixels, and correspond to
901        the data bounding box of the layer only.
902
903        See also: render_thumbnail().
904
905        """
906        return self._thumbnail
907
908    def update_thumbnail(self):
909        """Safely updates the cached preview thumbnail.
910
911        This method updates self.thumbnail using render_thumbnail() and
912        the data bounding box, and eats any NotImplementedErrors.
913
914        This is used by the layer stack to keep the preview thumbnail up
915        to date. It is called automatically after layer data is changed
916        and stable for a bit, so there is normally no need to call it in
917        client code.
918
919        """
920        try:
921            self._thumbnail = self.render_thumbnail(
922                self.get_bbox(),
923                alpha=True,
924            )
925        except NotImplementedError:
926            self._thumbnail = None
927
928    def render_thumbnail(self, bbox, **options):
929        """Renders a 256x256 thumb of the layer in an arbitrary bbox.
930
931        :param tuple bbox: Bounding box to make a thumbnail of.
932        :param **options: Passed to RootLayerStack.render_layer_preview().
933        :rtype: GtkPixbuf or None
934
935        Use the thumbnail property if you just want a reasonably
936        up-to-date preview thumbnail for a single layer.
937
938        See also: RootLayerStack.render_layer_preview().
939
940        """
941        root = self.root
942        if root is None:
943            return None
944        return root.render_layer_preview(self, bbox=bbox, **options)
945
946    ## Trimming
947
948    def trim(self, rect):
949        """Trim the layer to a rectangle, discarding data outside it
950
951        :param rect: A trimming rectangle in model coordinates
952        :type rect: tuple (x, y, w, h)
953
954        The base implementation does nothing.
955        """
956        pass
957
958
959class _StubLayerBase (LayerBase):
960    """An instantiable (but broken) LayerBase, for testing."""
961
962    def get_render_ops(self, *argv, **kwargs):
963        pass
964
965
966class LayerBaseSnapshot (object):
967    """Base snapshot implementation
968
969    Snapshots are stored in commands, and used to implement undo and redo.
970    They must be independent copies of the data, although copy-on-write
971    semantics are fine. Snapshot objects must be complete enough clones of the
972    layer's data for duplication to work.
973    """
974
975    def __init__(self, layer):
976        super(LayerBaseSnapshot, self).__init__()
977        self.name = layer.name
978        self.mode = layer.mode
979        self.opacity = layer.opacity
980        self.visible = layer.visible
981        self.locked = layer.locked
982
983    def restore_to_layer(self, layer):
984        layer.name = self.name
985        layer.mode = self.mode
986        layer.opacity = self.opacity
987        layer.visible = self.visible
988        layer.locked = self.locked
989
990
991class ExternallyEditable:
992    """Interface for layers which can be edited in an external app"""
993
994    __metaclass__ = abc.ABCMeta
995    _EDITS_SUBDIR = u"edits"
996
997    @abc.abstractmethod
998    def new_external_edit_tempfile(self):
999        """Get a tempfile for editing in an external app
1000
1001        :rtype: unicode/str
1002        :returns: Absolute path to a newly-created tempfile for editing
1003
1004        The returned tempfiles are only expected to persist on disk
1005        until a subsequent call to this method is made.
1006
1007        """
1008
1009    @abc.abstractmethod
1010    def load_from_external_edit_tempfile(self, tempfile_path):
1011        """Load content from an external-edit tempfile
1012
1013        :param unicode/str tempfile_path: Tempfile to load.
1014
1015        """
1016
1017    @property
1018    def external_edits_dir(self):
1019        """Directory to use for external edit files"""
1020        cache_dir = self.root.doc.cache_dir
1021        edits_dir = os.path.join(cache_dir, self._EDITS_SUBDIR)
1022        if not os.path.isdir(edits_dir):
1023            os.makedirs(edits_dir)
1024        return edits_dir
1025
1026
1027## Helper functions
1028
1029def combine_redraws(bboxes):
1030    """Combine multiple rectangles representing redraw areas into one
1031
1032    :param iterable bboxes: Sequence of redraw bboxes (lib.helpers.Rect)
1033    :returns: A single redraw bbox.
1034    :rtype: lib.helpers.Rect
1035
1036    This is best used for small, related redraws, since the GUI may have
1037    better ways of combining rectangles into update regions.  Pairs of
1038    before and after states are good candidates for using this.
1039
1040    If any of the input bboxes have zero size, the first such bbox is
1041    returned. Zero-size update bboxes are the conventional way of
1042    requesting a full-screen update.
1043
1044    """
1045    redraw_bbox = helpers.Rect()
1046    for bbox in bboxes:
1047        if bbox.w == 0 and bbox.h == 0:
1048            return bbox
1049        redraw_bbox.expandToIncludeRect(bbox)
1050    return redraw_bbox
1051
1052
1053## Module testing
1054
1055
1056def _test():
1057    """Run doctest strings"""
1058    import doctest
1059    doctest.testmod(optionflags=doctest.ELLIPSIS)
1060
1061
1062if __name__ == '__main__':
1063    logging.basicConfig(level=logging.DEBUG)
1064    _test()
1065