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