ó
dòôdc           @` s†   d  Z  d d l m Z m Z m Z m Z d d l Td d l Z d d l Z d d l	 Z	 d e
 f d „  ƒ  YZ d e
 f d „  ƒ  YZ d S(	   u7   Managing and invoking a list of functions sequentially.i    (   t   absolute_importt   divisiont   print_functiont   unicode_literals(   t   *Nt   Invokerc           B` s†  e  Z d  Z d" Z \ Z Z Z e j d d ƒ Z	 d „  Z
 d# d# d# e e d# e d „ Z d# d# d# d# d „ Z d# d# d „ Z d# e d	 „ Z d# e d
 „ Z d# d „ Z d „  Z d# d „ Z d# e d „ Z e d „ Z d# d „ Z d# e d „ Z d „  Z d „  Z d „  Z d „  Z d „  Z d „  Z d „  Z  d „  Z! d „  Z" d „  Z# d „  Z$ d „  Z% d „  Z& d „  Z' d# d  „ Z( d! „  Z) RS($   uÉ  Class to invoke (call) a sequence of functions or nested instances,
  hereinafter "actions".
  
  Features include:
  * adding and removing actions,
  * reordering actions,
  * grouping actions and invoking only actions in specified groups,
  * adding actions to be invoked before or after each action, hereinafter
    "for-each actions",
  * adding another `Invoker` instance as an action (i.e. nesting the current
    instance inside another instance).
  i    i   i   t   startc         C` sj   t  j ƒ  |  _ t  j ƒ  |  _ t  j d „  ƒ |  _ t  j d „  ƒ |  _ t  j d „  ƒ |  _ i  |  _ d  S(   Nc           S` s   t  j t ƒ S(   N(   t   collectionst   defaultdictt   int(    (    (    sI   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/invoker.pyt   <lambda>'   t    c           S` s   t  j t ƒ S(   N(   R   R   R	   (    (    (    sI   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/invoker.pyR
   *   R   c           S` s   t  j t ƒ S(   N(   R   R   R	   (    (    (    sI   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/invoker.pyR
   -   R   (	   R   t   OrderedDictt   _actionst   _foreach_actionsR   t   _action_functionst   _foreach_action_functionst	   _invokerst   _action_items(   t   self(    (    sI   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/invoker.pyt   __init__   s    c	      	   C` sã   | r |  j  | | | ƒ r d S|  j ƒ  }	 t | ƒ r¯ | sI |  j }
 n	 |  j }
 xŠ |  j | ƒ D]F } |
 |	 | | | d k	 r† | n d | d k	 r› | n i  | | ƒ qb Wn0 x- |  j | ƒ D] } |  j |	 | | | ƒ q¿ W|	 S(   u‚  Adds an action to be invoked by `invoke()`.
    
    The ID of the newly added action is returned.
    
    An action can be:
    * a function, in which case optional arguments (`args`, a list or tuple) and
      keyword arguments (`kwargs`, a dict) can be specified,
    * another `Invoker` instance.
    
    To control which actions are invoked, you may want to group them.
    
    If `groups` is `None` or `'default'`, the action is added to a default
    group appropriately named `'default'`.
    
    If `groups` is a list of group names (strings), the action is added to
    the specified groups. Groups are created automatically if they previously
    did not exist.
    
    If `groups` is `'all'`, the action is added to all existing groups. The
    action will not be added to the default group if it does not exist.
    
    By default, the action is added at the end of the list of actions in the
    specified group(s). Pass an integer to the `position` parameter to customize
    the insertion position. A negative value represents an n-th to last
    position.
    
    Action as a function can also be a generator function or return a generator.
    This allows customizing which parts of the code of the function are called
    on each invocation. For example:
    
      def foo():
        print('bar')
        while True:
          args, kwargs = yield
          print('baz')
    
    prints `'bar'` the first time the function is called and `'baz'` all
    subsequent times. This allows to e.g. initialize objects to an initial state
    in the first part and then use that state in subsequent invocations of this
    function, effectively eliminating the need for global variables for the same
    purpose.
    
    The generator must contain at least one `yield` statement. If you pass
    arguments and want to use the arguments in the function, the yield statement
    must be in the form `args, kwargs = yield`.
    
    To make sure the generator can be called an arbitrary number of times, place
    a `yield` statement in an infinite loop. To limit the number of calls,
    simply do not use an infinite loop. In such a case, the action is
    permanently removed for the group(s) `invoke()` was called for once no more
    yield statements are encountered.
    
    To prevent activating generators and to treat generator functions as regular
    functions, set `run_generator` to `False`.
    
    If `foreach` is `True` and the action is a function, the action is
    treated as a "for-each" action. By default, a for-each action is
    invoked after each regular action (function or `Invoker` instance). To
    customize this behavior, use the `yield` statement in the for-each action
    to specify where it is desired to invoke each action.
    For example:
    
      def foo():
        print('bar')
        yield
        print('baz')
    
    first prints `'bar'`, then invokes the action and finally prints
    `'baz'`. Multiple `yield` statements can be specified to invoke the wrapped
    action multiple times.
    
    If multiple for-each actions are added, they are invoked in the order
    they were added by this method. For example:
      
      def foo1():
        print('bar1')
        yield
        print('baz1')
      
      def foo2():
        print('bar2')
        yield
        print('baz2')
    
    will print `'bar1'`, `'bar2'`, then invoke the action (only once), and
    then print `'baz1'` and `'baz2'`.
    
    To make an `Invoker` instance behave as a for-each action, wrap
    the instance in a function as shown above. For example:
      
      def invoke_before_each_action():
        invoker.invoke()
        yield
    
    If `ignore_if_exists` is `True`, do not add the action if the same
    function or `Invoker` instance is already added in at least one of
    the specified groups and return `None`. Note that the same function with
    different arguments is still treated as one function.
    N(    (   t   containst   Nonet   _get_action_idt   callablet   _add_actiont   _add_foreach_actiont   _process_groups_argt   _add_invoker(   R   t   actiont   groupst   argst   kwargst   foreacht   ignore_if_existst   positiont   run_generatort	   action_idt   add_action_funct   group(    (    sI   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/invoker.pyt   add2   s&    m	c   
      ` s“  ‡  ‡ f d †  ‰ ‡  ‡ f d †  ‰ ‡ ‡ f d †  ‰  ‡ ‡ ‡ ‡ f d †  } d d „ ‰ ‡ ‡ ‡ f d †  } ˆ d k	 r ˆ n d ‰ ˆ d k	 r™ ˆ n i  ‰ xí ˆ j | ƒ D]Ü } | ˆ j k rÔ ˆ j | ƒ n  t ˆ j | ƒ } x¡ | D]™ }	 |	 ˆ j | k rqî n  |	 j ˆ j k rwˆ j | r<| |	 | ƒ q‡ˆ |	 | ƒ |	 j r‡ˆ j	 |	 j
 | g ƒ t |	 _ q‡qî | |	 j | ƒ qî Wq¯ Wd S(	   u  Invokes actions.
    
    If `groups` is `None` or `'default'`, invoke actions in the default
    group.
    
    If `groups` is a list of group names (strings), invoke actions in the
    specified groups.
    
    If `groups` is `'all'`, invoke actions in all existing groups.
    
    If any of the `groups` do not exist, raise `ValueError`.
    
    If `action` is an `Invoker` instance, the instance will invoke
    actions in the specified groups.
    
    Additional arguments and keyword arguments to all actions in the group
    are given by `additional_args` and `additional_kwargs`, respectively.
    If some keyword arguments appear in both the `kwargs` parameter in `add()`
    and in `additional_kwargs`, values from the latter override the values in
    the former.
    
    `additional_args` are appended to the argument list by default. Specify
    `additional_args_position` as an integer to change the insertion position of
    `additional_args`. `additional_args_position` also applies to nested
    `Invoker` instances.
    c         ` sÙ   |  j  \ } } } ˆ  | ƒ } t | ˆ  } | | | Ž  } t j | ƒ rW t |  _ n  |  j of |  j sm | S| |  j k rš | |  j | <t |  j | ƒ Sy |  j | j	 | | g ƒ SWn t
 k
 rÔ t |  _ n Xd  S(   N(   R   t   dictt   inspectt   isgeneratort   Truet   is_generatorR$   t   generators_per_groupt   nextt   sendt   StopIterationt   should_be_removed_from_group(   t   itemR'   R   t   action_argst   action_kwargsR   R    t   result(   t	   _get_argst   additional_kwargs(    sI   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/invoker.pyt   _invoke_actionÚ   s    c         ` s(   ˆ  | ƒ } t  | ˆ  } |  | | Ž  S(   N(   R)   (   R   R4   R5   R   R    (   R7   R8   (    sI   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/invoker.pyt   _prepare_foreach_actionð   s    c         ` sG   ˆ d  k r  t |  ƒ t ˆ  ƒ St |  ƒ } ˆ  | ˆ ˆ +t | ƒ Sd  S(   N(   R   t   tuplet   list(   R4   R   (   t   additional_argst   additional_args_position(    sI   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/invoker.pyR7   õ   s
    c         ` sŒ   g  ˆ j  | D] } ˆ | j Œ  ^ q } ˆ | ƒ xR | r‡ ˆ  |  | ƒ } ˆ | | ƒ |  j r6 ˆ j |  j | g ƒ t |  _ d  Sq6 Wd  S(   N(   R   R   R2   t   removeR%   t   False(   R3   R'   t   foreach_itemt   action_generatorst   result_from_action(   R9   t   _invoke_foreach_actions_onceR:   R   (    sI   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/invoker.pyt#   _invoke_action_with_foreach_actionsý   s    &
			c         S` sk   g  } x@ |  D]8 } y | j  | ƒ Wq t k
 rD | j | ƒ q Xq Wx | D] } |  j | ƒ qP Wd  S(   N(   R0   R1   t   appendR?   (   RB   RC   t   action_generators_to_removet   action_generatort   action_generator_to_remove(    (    sI   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/invoker.pyRD     s    c         ` s   |  j  | g ˆ  ˆ ˆ ƒ d  S(   N(   t   invoke(   t   invokerR'   (   R=   R>   R8   (    sI   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/invoker.pyt   _invoke_invoker  s    N(    (   R   R   R   t   _init_groupR<   t   action_typet   _TYPE_INVOKERR   R2   R?   R%   R@   R   (
   R   R   R=   R8   R>   RE   RL   R'   t   itemsR3   (    (   R7   R9   RD   R:   R=   R>   R8   R   sI   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/invoker.pyRJ   ¹   s.    !	c         C` s^   |  j  | ƒ xJ |  j | ƒ D]9 } | |  j | j k r |  j |  j | | | ƒ q q Wd S(   uO  
    Add an existing action specified by its ID to the specified groups. For
    more information about the `groups` parameter, see `add()`.
    
    If the action was already added to one of the specified groups, it will
    not be added again (call `add()` for that purpose).
    
    By default, the action is added at the end of the list of actions in the
    specified group(s). Pass an integer to the `position` parameter to customize
    the insertion position. A negative value represents an n-th-to-last
    position.
    
    If the action ID is not valid, raise `ValueError`.
    N(   t   _check_action_id_is_validR   R   R   t   _add_action_to_group(   R   R%   R   R#   R'   (    (    sI   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/invoker.pyt   add_to_groups7  s    c         C` sQ   |  j  |  j | | ƒ ƒ d } x+ |  j | ƒ D] } | | | k r/ t Sq/ Wt S(   u  
    Return `True` if the specified action exists, `False` otherwise.
    `action` can be a function or `Invoker` instance.
    
    For information about the `groups` parameter, see `has_action()`.
    
    If `foreach` is `True`, treat the action as a for-each action.
    i   (   t   _get_action_lists_and_functionst   _get_action_typeR   R,   R@   (   R   R   R   R!   t   action_functionsR'   (    (    sI   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/invoker.pyR   L  s    	c   
      C` s¹   |  j  | | ƒ } |  j | ƒ d } g  |  j | ƒ D] } | |  j ƒ  k r5 | ^ q5 } g  } xS | D]K } | j g  | | D]- }	 |	 j | k r} |	 j | k r} |	 j ^ q} ƒ qf W| S(   u  
    Return action IDs matching the specified action. `action` can be a
    function or `Invoker` instance.
    
    For information about the `groups` parameter, see `has_action()`.
    
    If `foreach` is `True`, treat the action as a for-each action.
    i    (   RU   RT   R   t   list_groupst   extendt   action_functionRN   R%   (
   R   R   R   R!   RN   t   action_listsR'   t   processed_groupst   found_action_idst   action_item(    (    sI   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/invoker.pyt   find^  s    	2c         ` s5   ˆ  ˆ j  k o4 t ‡  ‡ f d †  ˆ j | ƒ Dƒ ƒ S(   uX  
    Return `True` if the specified ID (returned from `add()`) belongs to an
    existing action in at least one of the specified groups.
    
    `group` can have one of the following values:
      * `None` or `'default'` - the default group,
      * list of group names (strings) - specific groups,
      * `'all'` - all existing groups.
    c         3` s%   |  ] } | ˆ j  ˆ  j k Vq d  S(   N(   R   R   (   t   .0R'   (   R%   R   (    sI   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/invoker.pys	   <genexpr>…  s   (   R   t   anyR   (   R   R%   R   (    (   R%   R   sI   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/invoker.pyt
   has_actiony  s    c         C` s%   | |  j  k r |  j  | j Sd Sd S(   uS   
    Return action specified by its ID. If the ID is not valid, return `None`.
    N(   R   R   R   (   R   R%   (    (    sI   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/invoker.pyt
   get_actionˆ  s    c         C` sh   | d k r d } n  |  j | ƒ |  j | | ƒ |  j | } |  j | j ƒ \ } } | | j | ƒ S(   uò   
    Return the position of the action specified by its ID in the specified
    group. If `group` is `None` or `'default'`, use the default group.
    
    If the ID is not valid or the action is not in the group, raise
    `ValueError`.
    u   defaultN(   R   RQ   t   _check_action_in_groupR   RT   RN   t   index(   R   R%   R'   R]   RZ   t   unused_(    (    sI   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/invoker.pyt   get_position‘  s    	c         C` se   | d k r d } n  | s' |  j } n	 |  j } | |  j k r] g  | | D] } | j ^ qJ Sd Sd S(   u  
    Return all actions, along with their arguments and keyword arguments, for
    the specified group in the order they would be invoked. If the group does
    not exist, return `None`.
    
    If `foreach` is `True`, return for-each actions instead.
    u   defaultN(   R   R   R   R   (   R   R'   R!   t   action_itemsR3   (    (    sI   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/invoker.pyt   list_actions£  s    		c         ` sL   | r t  ˆ  j ƒ S‡  f d †  } g  ˆ  j D] } | | ƒ r, | ^ q, Sd S(   u   
    Return a list of all groups in the invoker.
    
    If `include_empty_groups` is `False`, do not include groups with no
    actions.
    c         ` s&   t  ‡  f d †  ˆ j ˆ j g Dƒ ƒ S(   Nc         3` s%   |  ] } ˆ  | k o | ˆ  Vq d  S(   N(    (   R_   RZ   (   R'   (    sI   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/invoker.pys	   <genexpr>Ä  s   (   R`   R   R   (   R'   (   R   (   R'   sI   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/invoker.pyt   _is_group_non_emptyÂ  s    N(   R<   R   (   R   t   include_empty_groupsRi   R'   (    (   R   sI   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/invoker.pyRW   ¸  s    c         C` sÊ   | d k r d } n  |  j | ƒ |  j | ƒ |  j | | ƒ |  j | } |  j | j ƒ \ } } | | j | | j | ƒ ƒ | d k  r² t	 t
 | | ƒ | d d ƒ } n  | | j | | ƒ d S(   u  Change the order in which an action is invoked.
    
    The action is specified by its ID (as returned by `add()`).
    
    If `group` is `None` or `'default'`, use the default group.
    
    A position of 0 moves the action to the beginning.
    Negative numbers move the action to the n-th to last position, i.e. -1
    for the last position, -2 for the second to last position, etc.
    
    Raises `ValueError` if:
      * action ID is invalid
      * group does not exist
      * action is not in the group
    u   defaulti    i   N(   R   RQ   t   _check_group_existsRc   R   RT   RN   t   popRd   t   maxt   lent   insert(   R   R%   R#   R'   R]   RZ   Re   (    (    sI   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/invoker.pyt   reorderÉ  s    	$c         C` sµ   | r | |  j  k r) d Sn |  j | ƒ |  j |  j  | j ƒ \ } } xf |  j | ƒ D]U } |  j | ƒ | |  j  | j k rX |  j | | | | ƒ | |  j  k r­ Pq­ qX qX Wd S(   uù  
    Remove the action specified by its ID from the specified groups.
    
    For information about the `groups` parameter, see `has_action()`.
    
    For existing groups where the action is not added, do nothing.
    
    If `ignore_if_not_exists` is `True`, do not raise `ValueError` if
    `action_id` does not match any added action.
    
    Raises `ValueError` if:
      * action ID is invalid and `ignore_if_not_exists` is `False`
      * at least one of the specified groups does not exist
    N(   R   RQ   RT   RN   R   Rk   R   t   _remove_action(   R   R%   R   t   ignore_if_not_existst   action_listRV   R'   (    (    sI   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/invoker.pyR?   ê  s    c         C` sÿ   g  |  j  | ƒ D] } | |  j ƒ  k r | ^ q } xÄ | D]¼ } xh |  j | D]Y } | j |  j k r‰ |  j | j | |  j |  j ƒ qO |  j | j | |  j |  j ƒ qO Wx4 |  j	 | D]% } |  j | j | |  j	 |  j
 ƒ qº W|  j | =|  j	 | =q; Wd S(   uÚ   
    Remove the specified groups and their actions (including for-each
    actions).
    
    For information about the `groups` parameter, see `has_action()`.
    
    Non-existent groups in `groups` are ignored.
    N(   R   RW   R   RN   t   _TYPE_ACTIONRq   R%   R   R   R   R   (   R   R   R'   R[   R]   (    (    sI   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/invoker.pyt   remove_groups
  s    
"#
c         C` s0   | |  j  k r, g  |  j  | <g  |  j | <n  d  S(   N(   R   R   (   R   R'   (    (    sI   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/invoker.pyRM   %  s    c         C` sÓ   | j  |  j k rO |  j | j | j d | | j d | j d | | j ƒ n€ | j  |  j k rž |  j | j | j d | | j d | j d | | j ƒ n1 | j  |  j k rÏ |  j	 | j | j | | ƒ n  d  S(   Ni    i   i   (
   RN   Rt   R   R%   R   R$   t   _TYPE_FOREACH_ACTIONR   RO   R   (   R   R]   R'   R#   (    (    sI   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/invoker.pyRR   *  s(    





c   	      C` sŒ   |  j  | ƒ |  j | | | | | f |  j | | ƒ } | d  k rZ |  j | j | ƒ n |  j | j | | ƒ |  j | | c d 7<d  S(   Ni   (   RM   t   _set_action_itemRt   R   R   RF   Ro   R   (	   R   R%   R   R'   R4   R5   R#   R$   R]   (    (    sI   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/invoker.pyR   @  s    	c         ` s¹   |  j  | ƒ t j ˆ  ƒ s4 ‡  f d †  } | }	 n ˆ  }	 |  j | | |	 | | f |  j ˆ  | ƒ }
 | d  k r‡ |  j | j |
 ƒ n |  j | j | |
 ƒ |  j	 | ˆ  c d 7<d  S(   Nc          ?` s   d  Vˆ  |  | Ž  d  S(   N(    (   R   R    (   t   foreach_action(    sI   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/invoker.pyt"   invoke_foreach_action_after_action_  s    i   (
   RM   R*   t   isgeneratorfunctionRw   Rv   R   R   RF   Ro   R   (   R   R%   Rx   R'   t   foreach_action_argst   foreach_action_kwargsR#   R$   Ry   t!   foreach_action_generator_functionR]   (    (   Rx   sI   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/invoker.pyR   S  s$    			c         C` sƒ   |  j  | ƒ |  j | | | |  j | t ƒ } | d  k rQ |  j | j | ƒ n |  j | j | | ƒ |  j | | c d 7<d  S(   Ni   (	   RM   Rw   RO   R@   R   R   RF   Ro   R   (   R   R%   RK   R'   R#   R]   (    (    sI   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/invoker.pyR   x  s    c         C` s   |  j  j ƒ  S(   N(   t   _action_id_counterR/   (   R   (    (    sI   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/invoker.pyR   …  s    c         C` sV   | |  j  k r4 t | | d  | | | ƒ |  j  | <n  |  j  | j j | ƒ |  j  | S(   N(   R   t   _ActionItemR   R   R(   (   R   R%   R'   R   RN   RY   R$   (    (    sI   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/invoker.pyRw   ˆ  s
    "c         C` sq   |  j  | } | | j | ƒ | | | j c d 8<| | | j d k r] | | | j =n  |  j | | ƒ d  S(   Ni   i    (   R   R?   RY   t   _remove_action_item(   R   R%   R'   RZ   RV   R]   (    (    sI   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/invoker.pyRq   ˜  s    c         C` s8   |  j  | j j | ƒ |  j  | j s4 |  j  | =n  d  S(   N(   R   R   R?   (   R   R%   R'   (    (    sI   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/invoker.pyR€   ¢  s    c         C` s=   | d  k s | d k r d g S| d k r5 |  j ƒ  S| Sd  S(   Nu   defaultu   all(   R   RW   (   R   R   (    (    sI   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/invoker.pyR   ¨  s
    
c         C` s+   | r |  j  St | ƒ r  |  j S|  j Sd  S(   N(   Rv   R   Rt   RO   (   R   R   t
   is_foreach(    (    sI   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/invoker.pyRU   °  s
    c         C` s|   | |  j  k r |  j |  j f S| |  j k r> |  j |  j f S| |  j k r] |  j |  j f St d j	 | |  j
 ƒ ƒ ‚ d  S(   Nu)   invalid action type {}; must be one of {}(   Rt   R   R   Rv   R   R   RO   R   t
   ValueErrort   formatt   _ACTION_TYPES(   R   RN   (    (    sI   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/invoker.pyRT   ¹  s    c         C` s+   | |  j  k r' t d j | ƒ ƒ ‚ n  d  S(   Nu    action with ID {} does not exist(   R   R‚   Rƒ   (   R   R%   (    (    sI   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/invoker.pyRQ   Ä  s    c         C` sC   | d  k r |  j ƒ  } n  | | k r? t d j | ƒ ƒ ‚ n  d  S(   Nu   group "{}" does not exist(   R   RW   R‚   Rƒ   (   R   R'   R   (    (    sI   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/invoker.pyRk   È  s    c         C` s5   | |  j  | j k r1 t d j | | ƒ ƒ ‚ n  d  S(   Nu&   action with ID {} is not in group "{}"(   R   R   R‚   Rƒ   (   R   R%   R'   (    (    sI   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/invoker.pyRc   Ï  s    (   i    i   i   N(*   t   __name__t
   __module__t   __doc__R„   Rt   Rv   RO   t	   itertoolst   countR~   R   R   R@   R,   R(   RJ   RS   R   R^   Ra   Rb   Rf   Rh   RW   Rp   R?   Ru   RM   RR   R   R   R   R   Rw   Rq   R€   R   RU   RT   RQ   Rk   Rc   (    (    (    sI   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/invoker.pyR      sR   	€y		! 					%				
						R   c           B` s   e  Z d  „  Z RS(   c         C` s   | |  _  | |  _ | d  k	 r$ | n t ƒ  |  _ | d  k	 rB | n t j |  _ | |  _ | |  _	 t
 |  _ i  |  _ t
 |  _ d  S(   N(   R   R%   R   t   setR   R   Rt   RN   RY   R$   R@   R-   R.   R2   (   R   R   R%   R   RN   RY   R$   (    (    sI   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/invoker.pyR   Ö  s    						(   R…   R†   R   (    (    (    sI   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/invoker.pyR   Ô  s   (   R‡   t
   __future__R    R   R   R   t   future.builtinsR   R*   Rˆ   t   objectR   R   (    (    (    sI   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/invoker.pyt   <module>   s   "
ÿ ÿ É                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            # -*- coding: utf-8 -*-

"""Managing items of a GIMP image (e.g. layers) in a tree-like structure."""

from __future__ import absolute_import, division, print_function, unicode_literals
from future.builtins import *
import future.utils

import abc
import collections

try:
  import cPickle as pickle
except ImportError:
  import pickle

import gimp
from gimp import pdb
import gimpenums

from . import objectfilter as pgobjectfilter
from . import utils as pgutils


TYPE_ITEM, TYPE_GROUP, TYPE_FOLDER = (0, 1, 2)

FOLDER_KEY = 'folder'
"""Key used to access items as folders in `ItemTree` via `__getitem__()`.
See `ItemTree.__getitem__()` for more information.
"""


@future.utils.python_2_unicode_compatible
class ItemTree(future.utils.with_metaclass(abc.ABCMeta, object)):
  """Interface to store `gimp.Item` objects in a tree-like structure.
  
  Use one of the subclasses for items of a certain type:
  
    * `LayerTree` for layers,
    
    * `ChannelTree` for channels,
    
    * `VectorTree` for vectors (paths).
  
  Each item in the tree is an `Item` instance. Each item contains `gimp.Item`
  attributes and additional derived attributes.
  
  Items can be directly accessed via their ID or name. Both ID and name are
  unique in the entire tree (GIMP readily ensures that item names are unique).
  
  Item groups (e.g. layer groups) are inserted twice in the tree - as folders
  and as items. Parents of items are always folders.
  
  `ItemTree` is a static data structure, i.e. it does not account for
  modifications, additions or removal of GIMP items by GIMP procedures outside
  this class. To refresh the contents of the tree, create a new `ItemTree`
  instance instead.
  
  Attributes:
  
  * `image` (read-only) - GIMP image to generate item tree from.
  
  * `name` (read-only) - Optional name of the item tree. The name is currently
    used as an identifier of the persistent source for tags in items. See
    `Item.tags` for more information.
  
  * `is_filtered` - If `True`, ignore items that do not match the filter
    (`ObjectFilter`) in this object when iterating.
  
  * `filter` - `ObjectFilter` instance that allows filtering items based on
    rules.
  """
  
  def __init__(
        self,
        image,
        name=None,
        is_filtered=True,
        filter_match_type=pgobjectfilter.ObjectFilter.MATCH_ALL):
    self._image = image
    self._name = name
    self.is_filtered = is_filtered
    self._filter_match_type = filter_match_type
    
    # Filters applied to all items in `self._itemtree`
    self.filter = pgobjectfilter.ObjectFilter(self._filter_match_type)
    
    # Contains all items in the item tree (including item groups).
    # key: `Item.raw.ID` or (`Item.raw.ID`, `FOLDER_KEY`) in case of folders
    # value: `Item` instance
    self._itemtree = collections.OrderedDict()
    
    # key:
    #  `Item.orig_name` (derived from `Item.raw.name`)
    #   or (`Item.raw.ID`, `FOLDER_KEY`) in case of folders
    # value: `Item` instance
    self._itemtree_names = {}
    
    self._build_tree()
  
  @property
  def image(self):
    return self._image
  
  @property
  def name(self):
    return self._name
  
  def __getitem__(self, id_or_name):
    """Returns an `Item` object by its ID or original name.
    
    An item's ID is the `Item.raw.ID` attribute. An item's original name is the
    `Item.orig_name` attribute.
    
    To access an item group as a folder, pass a tuple `(ID or name, 'folder')`.
    For example:
        
        item_tree['Frames', 'folder']
    """
    try:
      return self._itemtree[id_or_name]
    except KeyError:
      return self._itemtree_names[id_or_name]
  
  def __contains__(self, id_or_name):
    """Returns `True` if an `Item` object is in the item tree, regardless of
    filters. Return `False` otherwise. The `Item` object is specified by its
    `Item.raw.ID` attribute or its `orig_name` attribute.
    """
    return id_or_name in self._itemtree or id_or_name in self._itemtree_names
  
  def __len__(self):
    """Returns the number of items in the tree.
    
    This includes immediate children of the image and nested children. Empty
    item groups (i.e. groups with no children) are excluded.
    
    The returned number of items depends on whether `is_filtered` is `True` or
    `False`.
    """
    return len([item for item in self])
  
  def __iter__(self):
    """Iterates over items, excluding folders and empty item groups.
    
    If the `is_filtered` attribute is `False`, iterate over all items. If
    `is_filtered` is `True`, iterate only over items that match the filter.
    
    Yields:
    
    * `item` - The current `Item` object.
    """
    return self.iter(with_folders=False, with_empty_groups=False)
  
  def iter(self, with_folders=True, with_empty_groups=False, filtered=True):
    """Iterates over items, optionally including folders and empty item groups.
    
    Parameters:
    
    * `with_folders` - If `True`, include folders. Topmost folders are yielded
      first. Items are always yielded after all of its parent folders.
    
    * `with_empty_groups` - If `True`, include empty item groups. Empty item
      groups as folders are still yielded if `with_folders` is `True`.
    
    * `filtered` - If `True` and `is_filtered` attribute is also `True`, iterate
      only over items matching the filter. Set this to `False` if you need to
      iterate over all items.
    
    Yields:
    
    * `item` - The current `Item` object.
    """
    for item in self._itemtree.values():
      should_yield_item = True
      
      if not with_folders and item.type == TYPE_FOLDER:
        should_yield_item = False
      
      if (not with_empty_groups
          and (item.type == TYPE_GROUP and not pdb.gimp_item_get_children(item.raw)[1])):
        should_yield_item = False
      
      if should_yield_item:
        if (filtered and self.is_filtered) and not self.filter.is_match(item):
          should_yield_item = False
      
      if should_yield_item:
        yield item
  
  def iter_all(self):
    """Iterates over all items.
    
    This is equivalent to `iter(with_folders=True, with_empty_groups=True,
    filtered=False)`.
    
    Yields:
    
    * `item` - The current `Item` object.
    """
    for item in self._itemtree.values():
      yield item
  
  def prev(self, item, with_folders=True, with_empty_groups=False, filtered=True):
    """Returns the previous item in the tree.
    
    Depending on the values of parameters, some items may be skipped. For the
    description of the parameters, see `iter()`.
    """
    return self._prev_next(item, with_folders, with_empty_groups, filtered, 'prev')
  
  def next(self, item, with_folders=True, with_empty_groups=False, filtered=True):
    """Returns the next item in the tree.
    
    Depending on the values of parameters, some items may be skipped. For the
    description of the parameters, see `iter()`.
    """
    return self._prev_next(item, with_folders, with_empty_groups, filtered, 'next')
  
  def _prev_next(self, item, with_folders, with_empty_groups, filtered, adjacent_attr_name):
    adjacent_item = item
    
    while True:
      adjacent_item = getattr(adjacent_item, adjacent_attr_name)
      
      if adjacent_item is None:
        break
      
      if with_folders:
        if adjacent_item.type == TYPE_FOLDER:
          break
      else:
        if adjacent_item.type == TYPE_FOLDER:
          continue
      
      if with_empty_groups:
        if (adjacent_item.type == TYPE_GROUP
            and not pdb.gimp_item_get_children(adjacent_item.raw)[1]):
          break
      else:
        if (adjacent_item.type == TYPE_GROUP
            and not pdb.gimp_item_get_children(adjacent_item.raw)[1]):
          continue
      
      if filtered and self.is_filtered:
        if self.filter.is_match(adjacent_item):
          break
      else:
        break
    
    return adjacent_item
  
  def reset_filter(self):
    """Resets the filter, creating a new empty `ObjectFilter`."""
    self.filter = pgobjectfilter.ObjectFilter(self._filter_match_type)
  
  def _build_tree(self):
    child_items = []
    for raw_item in self._get_children_from_image(self._image):
      if self._is_group(raw_item):
        child_items.append(Item(raw_item, TYPE_FOLDER, [], [], None, None, self._name))
        child_items.append(Item(raw_item, TYPE_GROUP, [], [], None, None, self._name))
      else:
        child_items.append(Item(raw_item, TYPE_ITEM, [], [], None, None, self._name))
    
    item_tree = child_items
    item_list = []
    
    while item_tree:
      item = item_tree.pop(0)
      item_list.append(item)
      
      if item.type == TYPE_FOLDER:
        self._itemtree[(item.raw.ID, FOLDER_KEY)] = item
        self._itemtree_names[(item.orig_name, FOLDER_KEY)] = item
        
        parents_for_child = list(item.parents)
        parents_for_child.append(item)
        
        child_items = []
        for raw_item in self._get_children_from_raw_item(item.raw):
          if self._is_group(raw_item):
            child_items.append(
              Item(raw_item, TYPE_FOLDER, parents_for_child, [], None, None, self._name))
            child_items.append(
              Item(raw_item, TYPE_GROUP, parents_for_child, [], None, None, self._name))
          else:
            child_items.append(
              Item(raw_item, TYPE_ITEM, parents_for_child, [], None, None, self._name))
        
        # We break the convention here and access a private attribute from `Item`.
        item._orig_children = child_items
        item.children = child_items
        
        for child_item in reversed(child_items):
          item_tree.insert(0, child_item)
      else:
        self._itemtree[item.raw.ID] = item
        self._itemtree_names[item.orig_name] = item
    
    for i in range(1, len(item_list) - 1):
      # We break the convention here and access private attributes from `Item`.
      item_list[i]._prev_item = item_list[i - 1]
      item_list[i]._next_item = item_list[i + 1]
    
    if len(item_list) > 1:
      item_list[0]._next_item = item_list[1]
      item_list[-1]._prev_item = item_list[-2]
  
  @abc.abstractmethod
  def _get_children_from_image(self, image):
    """Returns a list of immediate child items from the specified image.
    
    If no child items exist, an empty list is returned.
    """
    pass
  
  def _get_children_from_raw_item(self, raw_item):
    """Returns a list of immediate child items.
    
    If no child items exist, an empty list is returned.
    """
    return raw_item.children
  
  def _is_group(self, raw_item):
    return pdb.gimp_item_is_group(raw_item)


class LayerTree(ItemTree):
  
  def _get_children_from_image(self, image):
    return image.layers
  
  def _get_children_from_raw_item(self, raw_item):
    return raw_item.layers
  
  def _is_group(self, raw_item):
    return isinstance(raw_item, gimp.GroupLayer)


class ChannelTree(ItemTree):
  
  def _get_children_from_image(self, image):
    return image.channels


class VectorTree(ItemTree):
  
  def _get_children_from_image(self, image):
    return image.vectors


@future.utils.python_2_unicode_compatible
class Item(object):
  """Wrapper for a `gimp.Item` object containing additional attributes.
  
  Note that the attributes will not be up to date if changes were made to the
  original `gimp.Item` object.
  
  Attributes:
  
  * `raw` (read-only) - Underlying `gimp.Item` object wrapped by this instance.
  
  * `type` (read-only) - Item type - one of the following:
      * `TYPE_ITEM` - regular item
      * `TYPE_GROUP` - item group (item whose raw `gimp.Item` is a group with
        children; this `Item` has no children and acts as a regular item)
      * `TYPE_FOLDER` - item containing children (raw item is a group with
        children)
  
  * `parents` - List of `Item` parents for this item, sorted from the topmost
    parent to the bottommost (immediate) parent.
  
  * `children` - List of `Item` children for this item.
  
  * `depth` (read-only) - Integer indicating the depth of the item in the item
    tree. 0 means the item is at the top level. The greater the depth, the lower
    the item is in the item tree.
  
  * `parent` (read-only) - Immediate `Item` parent of this object.
    If this object has no parent, return `None`.
  
  * `name` - Item name as a string, initially equal to `orig_name`. Modify this
     attribute instead of `gimp.Item.name` to avoid modifying the original item.
  
  * `prev` - Previous `Item` in the `ItemTree`, or `None` if there is no
    previous item.
  
  * `next` - Next `Item` in the `ItemTree`, or `None` if there is no next item.
  
  * `tags` - Set of arbitrary strings attached to the item. Tags can be used for
    a variety of purposes, such as special handling of items with specific tags.
    Tags are stored persistently in the `gimp.Item` object (`item` attribute) as
    parasites. The name of the parasite source is given by the
    `tags_source_name` attribute.
  
  * `orig_name` (read-only) - Original `gimp.Item.name` as a string. This
    attribute may be used to access `Item`s in `ItemTree`.
  
  * `orig_parents` (read-only) - Initial `parents` of this item.
  
  * `orig_children` (read-only) - Initial `children` of this item.
  
  * `orig_tags` (read-only) - Initial `tags` of this item.
  
  * `tags_source_name` - Name of the persistent source for the `tags` attribute.
    Defaults to `'tags'` if `None`. If `type` is `FOLDER`, `'_folder'` is
    appended to `tags_source_name`.
  """
  
  def __init__(
        self, raw_item, item_type, parents=None, children=None, prev_item=None, next_item=None,
        tags_source_name=None):
    if raw_item is None:
      raise TypeError('item cannot be None')
    
    self._raw_item = raw_item
    self._type = item_type
    self._parents = parents if parents is not None else []
    self._children = children if children is not None else []
    self._prev_item = prev_item
    self._next_item = next_item
    
    self.name = pgutils.safe_decode_gimp(raw_item.name)
    
    self._tags_source_name = _get_effective_tags_source_name(
      tags_source_name if tags_source_name else 'tags', self._type)
    
    self._tags = self._load_tags()
    
    self._orig_name = self.name
    self._orig_parents = self._parents
    self._orig_children = self._children
    self._orig_tags = set(self._tags)
    
    self._item_attributes = ['name', '_parents', '_children', '_tags']
    
    self._saved_states = []
  
  @property
  def raw(self):
    return self._raw_item
  
  @property
  def type(self):
    return self._type
  
  @property
  def parents(self):
    return self._parents
  
  @parents.setter
  def parents(self, parents):
    self._parents = parents
  
  @property
  def children(self):
    return self._children
  
  @children.setter
  def children(self, children):
    self._children = children
  
  @property
  def depth(self):
    return len(self._parents)
  
  @property
  def parent(self):
    return self._parents[-1] if self._parents else None
  
  @property
  def prev(self):
    return self._prev_item
  
  @property
  def next(self):
    return self._next_item
  
  @property
  def tags(self):
    return self._tags
  
  @property
  def tags_source_name(self):
    return self._tags_source_name
  
  @property
  def orig_name(self):
    return self._orig_name
  
  @property
  def orig_parents(self):
    return iter(self._orig_parents)
  
  @property
  def orig_children(self):
    return iter(self._orig_children)
  
  @property
  def orig_tags(self):
    return iter(self._orig_tags)
  
  def __str__(self):
    return pgutils.stringify_object(self, self.orig_name)
  
  def __repr__(self):
    return pgutils.reprify_object(
      self, ' '.join([self.orig_name, str(type(self.raw))]))
  
  def push_state(self):
    """Saves the current values of item's attributes that can be modified.
    
    To restore the last saved values, call `pop_state()`.
    """
    self._saved_states.append({
      attr_name: getattr(self, attr_name) for attr_name in self._item_attributes})
  
  def pop_state(self):
    """Sets the values of item's attributes to the values from the last call to
    `push_state()`.
    
    Calling `pop_state()` without any saved state (e.g. when `push_state()` has
    never been called before) does nothing.
    """
    try:
      saved_states = self._saved_states.pop()
    except IndexError:
      return
    
    for attr_name, attr_value in saved_states.items():
      setattr(self, attr_name, attr_value)
  
  def reset(self, tags=False):
    """Resets the item's attributes to the values upon its instantiation.
    
    Is `tags` is `True`, also reset tags.
    """
    self.name = self._orig_name
    self._parents = list(self._orig_parents)
    self._children = list(self._orig_children)
    if tags:
      self._tags = set(self._orig_tags)
  
  def add_tag(self, tag):
    """Adds the specified tag to the item.
    
    If the tag already exists, do nothing. The tag is saved to the item
    persistently.
    """
    if tag in self._tags:
      return
    
    self._tags.add(tag)
    
    self._save_tags()
  
  def remove_tag(self, tag):
    """Removes the specified tag from the item.
    
    If the tag does not exist, raise `ValueError`.
    """
    if tag not in self._tags:
      raise ValueError('tag "{}" not found in {}'.format(tag, self))
    
    self._tags.remove(tag)
    
    self._save_tags()
  
  def _save_tags(self):
    """Saves tags persistently to the item."""
    set_tags_for_raw_item(self._raw_item, self._tags, self._tags_source_name)
  
  def _load_tags(self):
    return get_tags_from_raw_item(self._raw_item, self._tags_source_name)


def get_tags_from_raw_item(raw_item, source_name, item_type=None):
  """Obtains a set of tags from a `gimp.Item` instance, i.e. a raw item.
  
  `tags_source_name` is the name of the persistent source (parasite) to obtain
  tags from.
  """
  parasite = raw_item.parasite_find(_get_effective_tags_source_name(source_name, item_type))
  if parasite:
    try:
      tags = pickle.loads(parasite.data)
    except Exception:
      tags = set()
    
    return tags
  else:
    return set()


def set_tags_for_raw_item(raw_item, tags, source_name, item_type=None):
  remove_tags_from_raw_item(raw_item, source_name, item_type)
  
  if tags:
    raw_item.parasite_attach(
      gimp.Parasite(
        _get_effective_tags_source_name(source_name, item_type),
        gimpenums.PARASITE_PERSISTENT | gimpenums.PARASITE_UNDOABLE,
        pickle.dumps(tags)))


def remove_tags_from_raw_item(raw_item, source_name, item_type=None):
  raw_item.parasite_detach(_get_effective_tags_source_name(source_name, item_type))


def _get_effective_tags_source_name(source_name, item_type=None):
  if item_type == TYPE_FOLDER:
    return source_name + '_' + FOLDER_KEY
  else:
    return source_name
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               