σ
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 m	 Z
 d d l m Z i e d  d 6e d	  d
 6Z d Z d Z d Z d g Z i  Z d d  Z d   Z d   Z d   Z d   Z d   Z d   Z d   Z d   Z d d d e d d d d e e e d d  Z d e f d  Z  d e f e d  Z! d   Z" d   Z# d   Z$ d    Z% d!   Z& d"   Z' d#   Z( d$   Z) d%   Z* e d&  Z+ d d d'  Z, i e  d 6e! d( 6Z- d) e. f d*     YZ/ d S(+   u  Creation and management of plug-in actions - procedures and constraints.

Most functions take a setting group containing actions as its first argument.

Many functions define events invoked on the setting group containing actions.
These events include:

* `'before-add-action'` - invoked when:
  * calling `add()` before adding an action,
  * calling `clear()` before resetting actions (due to initial actions
    being added back).
  
  Arguments: action dictionary to be added

* `'after-add-action'` - invoked when:
  * calling `add()` after adding an action,
  * calling `setting.Group.load()` or `setting.Persistor.load()` after loading
    an action (loading an action counts as adding).
  * calling `clear()` after resetting actions (due to initial actions
    being added back).
  
  Arguments:
  
  * created action,
  
  * original action dictionary (same as in `'before-add-action'`). When this
    event is triggered in `setting.Group.load()` or `setting.Persistor.load()`,
    this argument is `None` as there is no way to obtain the original
    dictionary.

* `'before-reorder-action'` - invoked when calling `reorder()` before
  reordering an action.
  
  Arguments: action, position before reordering

* `'after-reorder-action'` - invoked when calling `reorder()` after reordering
  an action.
  
  Arguments: action, position before reordering, new position

* `'before-remove-action'` - invoked when calling `remove()` before removing an
  action.
  
  Arguments: action to be removed

* `'after-remove-action'` - invoked when calling `remove()` after removing an
  action.
  
  Arguments: name of the removed action

* `'before-clear-actions'` - invoked when calling `clear()` before clearing
  actions.

* `'after-clear-actions'` - invoked when calling `clear()` after clearing
  actions.
i    (   t   absolute_importt   divisiont   print_functiont   unicode_literals(   t   *N(   t	   pygimplib(   t   placeholdersu
   Backgroundu
   backgroundu
   Foregroundu
   foregroundu   default_proceduresu   default_constraintsu	   procedureu   namec         C` sg   t  j j d |  d i d d 6d d 6 } | t | <t | |  | j d d    | j d t  | S(	   u~  Creates a `setting.Group` instance containing actions.
  
  Parameters:
  * `name` - name of the `setting.Group` instance.
  * `initial_actions` - list of dictionaries describing actions to be
    added by default. Calling `clear()` will reset the actions returned by
    this function to the initial actions. By default, no initial actions
    are added.
  
  Each created action in the returned group is a `setting.Group` instance. Each
  action contains the following settings or child groups:
  * `'function'` - Name of the function to call. If `'origin'` is `'builtin'`,
    then the function is an empty string and the function must be replaced
    during processing with a function object. This allows the function to be
    saved to a persistent setting source.
  * `'origin'` - Type of the function. If `'builtin'`, the function is defined
    directly in the plug-in. If `'gimp_pdb'`, the function is taken from the
    GIMP PDB. The origin affects how the function is modified (wrapped) during
    processing in the `batcher` module.
  * `'arguments'` - Arguments to `'function'` as a `setting.Group` instance
    containing arguments as separate `Setting` instances.
  * `'enabled'` - Whether the action should be applied or not.
  * `'display_name'` - The display name (human-readable name) of the action.
  * `'action_group'` - List of groups the action belongs to, used in
    `pygimplib.invoker.Invoker` and `batcher.Batcher`.
  * `'orig_name'` - The original name of the action. If an action with the
    same `'name'` field (see below) was previously added, the name of the new
    action is made unique to allow lookup of both actions. Otherwise,
    `'orig_name'` is equal to `'name'`.
  * `'tags'` - Additional tags added to each action (the `setting.Group`
    instance).
  * `'more_options_expanded'` - If `True`, display additional options for an
    action when editing the action interactively.
  * `'enabled_for_previews'` - If `True`, this indicates that the action can be
    applied in the preview.
  * `'display_options_on_create'` - If `True`, display action edit dialog upon
    adding an action interactively.
  
  Each dictionary in the `initial_actions` list may contain the following
  fields:
  * `'name'` - This field is required. This is the `name` attribute of the
    created action.
  * `'type'` - Action type. See below for details.
  * `'function'`
  * `'origin'`
  * `'arguments'` - Specified as list of dictionaries defining settings. Each
    dictionary must contain required attributes and can contain optional
    attributes as stated in `setting.Group.add()`.
  * `'enabled'`
  * `'display_name'`
  * `'action_group'`
  * `'tags'`
  * `'more_options_expanded'`
  * `'enabled_for_previews'`
  * `'display_options_on_create'`
  
  Depending on the specified `'type'`, the dictionary may contain additional
  fields and `create` may generate additional settings.
  
  Allowed values for `'type'`:
  * `'procedure'` (default) - Represents a procedure. `'action_group'`
    defaults to `DEFAULT_PROCEDURES_GROUP` if not defined.
  * `'constraint'` - Represents a constraint. `'action_group'` defaults to
    `DEFAULT_CONSTRAINTS_GROUP` if not defined.
  
  Additional allowed fields for type `'constraint'` include:
  * `'also_apply_to_parent_folders'` - If `True`, apply the constraint to parent
    groups (folders) as well. The constraint is then satisfied only if the item
    and all of its parents satisfy the constraint.
  
  Custom fields are accepted as well. For each field, a separate setting is
  created, using the field name as the setting name.
  
  Raises:
  * `ValueError` - invalid `'type'` or missing required fields in
    `initial_actions`.
  t   namet   setting_attributesu   pdb_typeu   setting_sourcesu   before-loadc         S` s   t  |  d t S(   Nt   add_initial_actions(   t   cleart   False(   t   group(    (    s?   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/actions.pyt   <lambda>­   t    u
   after-loadN(   t   pgt   settingt   Groupt   Nonet!   _ACTIONS_AND_INITIAL_ACTION_DICTSt   _create_initial_actionst   connect_eventt   _set_up_action_after_loading(   R   t   initial_actionst   actions(    (    s?   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/actions.pyt   createT   s    N
c         C` s1   | d  k	 r- x | D] } t |  |  q Wn  d  S(   N(   R   t   add(   R   R   t   action_dict(    (    s?   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/actions.pyR   ³   s    c         C` s2   x+ |  D]# } t  |  |  j d | d   q Wd  S(   Nu   after-add-action(   t   _set_up_action_post_creationt   invoke_eventR   (   R   t   action(    (    s?   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/actions.pyR   Ή   s    
c         C` sΊ   t  | t  r t |  } n6 t j j |  r? t |  } n t d j |    t |  t |  } |  j	 d |  t
 |  |  t |   } |  j | g  |  j	 d | |  | S(   u
  
  Add an action to the `actions` setting group.
  
  `action_dict_or_function` can be one of the following:
  * a dictionary - see `create()` for required and accepted fields.
  * a PDB procedure.
  
  Objects of other types passed to `action_dict_or_function` raise
  `TypeError`.
  
  The same action can be added multiple times. Each action will be
  assigned a unique name and display name (e.g. `'rename'` and `'Rename'`
  for the first action, `'rename_2'` and `'Rename (2)'` for the second
  action, and so on).
  u;   "{}" is not a valid object - pass a dict or a PDB procedureu   before-add-actionu   after-add-action(   t
   isinstancet   dictR   t   pdbutilst   is_pdb_proceduret!   get_action_dict_for_pdb_proceduret	   TypeErrort   formatt   _check_required_fieldsR   t   _uniquify_name_and_display_namet   _create_action_by_typeR   (   R   t   action_dict_or_functionR   t   orig_action_dictR   (    (    s?   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/actions.pyR   Ώ   s    
c         C` s9   x2 t  D]* } | |  k r t d j |    q q Wd  S(   Nu   missing required field: "{}"(   t   _REQUIRED_ACTION_FIELDSt
   ValueErrorR%   (   t   action_kwargst   required_field(    (    s?   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/actions.pyR&   κ   s    c         C` sO   | d | d <t  |  | d  | d <d | k rK t |  | d  | d <n  d  S(   Nu   nameu	   orig_nameu   display_name(   t   _uniquify_action_namet   _uniquify_action_display_name(   R   R   (    (    s?   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/actions.pyR'   π   s
    c         C` sA   d   } t  j j | g  t |   D] } | j ^ q" d |   S(   uY   
  Return `name` modified to not match the name of any existing action in
  `actions`.
  c          s` s/   d }  x" t  r* d j |   V|  d 7}  q	 Wd  S(   Ni   u   _{}i   (   t   TrueR%   (   t   i(    (    s?   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/actions.pyt   _generate_unique_action_nameώ   s    	t	   generator(   R   t   patht   uniquify_stringt   walkR   (   R   R   R3   R   (    (    s?   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/actions.pyR/   ψ   s
    		"c         C` sE   d   } t  j j | g  t |   D] } | d j ^ q" d |   S(   ui   
  Return `display_name` modified to not match the display name of any existing
  action in `actions`.
  c          s` s/   d }  x" t  r* d j |   V|  d 7}  q	 Wd  S(   Ni   u    ({})i   (   R1   R%   (   R2   (    (    s?   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/actions.pyt   _generate_unique_display_name  s    	u   display_nameR4   (   R   R5   R6   R7   t   value(   R   t   display_nameR8   R   (    (    s?   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/actions.pyR0     s
    		&c          K` sM   |  j  d t  } | t k r? t d j | t t     n  t | |    S(   Nu   typeu#   invalid type "{}"; valid values: {}(   t   popt   _DEFAULT_ACTION_TYPEt   _ACTION_TYPES_AND_FUNCTIONSR,   R%   t   list(   t   kwargst   type_(    (    s?   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/actions.pyR(     s    u    u   builtinc         C` s$  t  j j |  d | d i d  d 6d  d 6} t  j j d d i d  d 6d  d 6} | rh | j |  n  | j i d d 6d d	 6| d
 6d  d 6d  d 6i d d 6d d	 6| d
 6d t d  f d t d  f g d 6d  d 6| i d d 6d d	 6| d
 6i d d 6d d	 6| d
 6d  d 6d g d 6i d d 6d d	 6| d
 6d  d 6i d d 6d d	 6| d
 6t d 6d  d 6i d d 6d d	 6|	 d
 6t d  d 6d d 6i d d 6d d	 6|
 d
 6t d   d 6i d d 6d! d	 6| d
 6d  d 6g
  | j i d d 6d" d	 6| d  k	 r| n |  d
 6d  d 6g  t |  | S(#   Nt   tagsR   u   pdb_typeu   setting_sourcesu	   argumentsu   stringu   typeu   functionu   nameu   default_valueu   gui_typeu   optionsu   originu   builtinu   Built-inu   gimp_pdbu   GIMP PDB procedureu   itemsu   booleanu   enabledu   display_nameu   ignore_initialize_guiu   tagsu   descriptionu   listu   action_groupsu   nullableu   more_options_expandedu   _More optionsu   expanderu   enabled_for_previewsu   Enable for previewsu   display_options_on_createu	   orig_name(   R   R   R   R   R   t   _R1   R   (   R   t   functiont   origint	   argumentst   enabledR:   t   descriptiont   action_groupsRA   t   more_options_expandedt   enabled_for_previewst   display_options_on_createt	   orig_nameR   t   arguments_group(    (    s?   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/actions.pyt   _create_action)  s    		

c         K` sY   d d g } | d  k	 r% | | 7} n  | d  k	 r@ t |  } n  t |  d | d | | S(   Nu   actionu	   procedureRH   RA   (   R   R>   RN   (   R   t   additional_tagsRH   R?   RA   (    (    s?   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/actions.pyt   _create_procedure  s    c         K` s   d d g } | d  k	 r% | | 7} n  | d  k	 r@ t |  } n  t |  d | d | | } | j i d d 6d d 6| d	 6t d
  d 6g  | S(   Nu   actionu
   constraintRH   RA   u   booleanu   typeu   also_apply_to_parent_foldersu   nameu   default_valueu   Also apply to parent foldersu   display_name(   R   R>   RN   R   RB   (   R   RO   RH   t   also_apply_to_parent_foldersR?   RA   t
   constraint(    (    s?   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/actions.pyt   _create_constraintͺ  s"    	c         C` sI   |  d j  d t |  d  |  d j d  rE t |   t |   n  d  S(   Nu   enabledu   after-set-guiu   display_nameu   originu   gimp_pdb(   R   t!   _set_display_name_for_enabled_guit   is_itemt8   _connect_events_to_sync_array_and_array_length_argumentst1   _hide_gui_for_run_mode_and_array_length_arguments(   R   (    (    s?   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/actions.pyR   Ι  s    

c         C` s    | j  d d d |  j j  d  S(   Nt   gui_typeu   check_button_labelt   gui_element(   t   set_guit   guit   element(   t   setting_enabledt   setting_display_name(    (    s?   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/actions.pyRT   Τ  s    	c         C` sY   d   } d   } x@ t  |   D]2 \ } } | j d | |  | j d | |  q Wd  S(   Nc         S` s   | j  | j d  d  S(   Ni   (   t	   set_valueR9   (   t   array_settingt   insertion_indexR9   t   array_length_setting(    (    s?   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/actions.pyt   _increment_array_lengthά  s    c         S` s   | j  | j d  d  S(   Ni   (   R_   R9   (   R`   Ra   Rb   (    (    s?   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/actions.pyt   _decrement_array_lengthΰ  s    u   after-add-elementu   before-delete-element(   t$   _get_array_length_and_array_settingsR   (   R   Rc   Rd   t   length_settingR`   (    (    s?   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/actions.pyRV   Ϊ  s    		c         C` sx   t  t |  d  d   } | d  k	 rG | j d k rG | j j t  n  x* t |   D] \ } } | j j t  qT Wd  S(   Nu	   argumentsu   run-mode(   t   nextt   iterR   R:   R[   t   set_visibleR   Re   (   R   t   first_argumentRf   t   unused_(    (    s?   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/actions.pyRW   λ  s
    c         C` sb   g  } d  } xO |  d D]C } t | t j j  rT | d  k	 rT | j | | f  n  | } q W| S(   Nu	   arguments(   R   R   R   R   t   ArraySettingt   append(   R   t   array_length_and_array_settingst   previous_settingR   (    (    s?   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/actions.pyRe   τ  s    !
c         C` sΊ  d   } i t  j j |  j  d 6t  j j |  j  d 6d d 6g  d 6t  j j |  j  d 6t d 6} g  } xGt |  j  D]6\ } \ } } } t  j j |  } y t  j j | }	 Wn$ t	 k
 rέ t
 | d |   n Xt  j j | | d	 |   }
 | j |
  t |	 t  r;t |	  } |
 | d <| | d <n i |	 d
 6|
 d 6| d 6} | t j k ryt j | | d
 <n  | d k r‘| d k r‘t j | d <n  | d j |  q| W| S(   u½  Returns a dictionary representing the specified GIMP PDB procedure that can
  be added to a setting group for actions via `add()`.
  
  The `'function'` field contains the PDB procedure name.
  
  If the procedure contains arguments with the same name, each subsequent
  identical name is made unique (since arguments are internally represented as
  `pygimplib.setting.Setting` instances, whose names must be unique within a
  setting group).
  c          s` s/   d }  x" t  r* d j |   V|  d 7}  q	 Wd  S(   Ni   u   -{}i   (   R1   R%   (   R2   (    (    s?   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/actions.pyt,   _generate_unique_pdb_procedure_argument_name  s    	u   nameu   functionu   gimp_pdbu   originu	   argumentsu   display_nameu   display_options_on_createR4   u   typei    u   run-modeu   default_value(   R   t   utilst   safe_decode_gimpt	   proc_nameR1   t	   enumeratet   paramsR   t   PDB_TYPES_TO_SETTING_TYPES_MAPt   KeyErrort   UnsupportedPdbProcedureErrorR5   R6   Rm   R   R    R   t*   PDB_TYPES_TO_PLACEHOLDER_SETTING_TYPES_MAPt	   gimpenumst   RUN_NONINTERACTIVE(   t   pdb_procedureRp   R   t   pdb_procedure_argument_namest   indext   pdb_param_typet   pdb_param_nameRk   t   processed_pdb_param_namet   setting_typet   unique_pdb_param_namet   arguments_dict(    (    s?   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/actions.pyR#     sD    	
%	

c         C` s   t  |  |  } | d k r9 t d j | |  j    n  |  | } |  j d | |  |  j | |  |  j d | | |  d S(   u!  
  Modify the position of the added action given by its name to the new
  position specified as an integer.
  
  A negative position functions as an n-th to last position (-1 for last, -2
  for second to last, etc.).
  
  Raises:
  * `ValueError` - `action_name` not found in `actions`.
  u+   action "{}" not found in actions named "{}"u   before-reorder-actionu   after-reorder-actionN(   t	   get_indexR   R,   R%   R   R   t   reorder(   R   t   action_namet   new_positiont   current_positionR   (    (    s?   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/actions.pyR   D  s    	
c         C` sh   | |  k r* t  d j | |  j    n  |  | } |  j d |  |  j | g  |  j d |  d S(   u   
  Remove the action specified by its name from `actions`.
  
  Raises:
  * `ValueError` - `action_name` not found in `actions`.
  u+   action "{}" not found in actions named "{}"u   before-remove-actionu   after-remove-actionN(   R,   R%   R   R   t   remove(   R   R   R   (    (    s?   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/actions.pyR   ^  s    	
c         ` s#   t    f d   t |   D d  S(   uj   Returns the index of the action matching `action_name`.
  
  If there is no such action, return `None`.
  c         3` s*   |  ]  \ } } | j    k r | Vq d  S(   N(   R   (   t   .0R~   R   (   R   (    s?   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/actions.pys	   <genexpr>x  s    	N(   Rg   Rt   R   (   R   R   (    (   R   s?   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/actions.pyR   r  s    c         C` sj   |  j  d  |  j g  |  D] } | j ^ q  | rY |  t k rY t |  t |   qY n  |  j  d  d S(   u’   Removes all added actions.
  
  If `add_initial_actions` is `True`, add back actions specified as
  `initial_actions` in `create()` after removing all actions.
  u   before-clear-actionsu   after-clear-actionsN(   R   R   R   R   R   (   R   R	   R   (    (    s?   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/actions.pyR
   }  s    #c         #` s‘   t  t     d k	 r<    k r< t d j      n     f d   } xL |  D]D } | |  sm qU n  | d k r | VqU | | k rU | | VqU qU Wd S(   u  
  Walk (iterate over) a setting group containing actions.
  
  The value of `action_type` determines what types of actions to iterate
  over. If `action_type` is `None`, iterate over all actions. For allowed
  action types, see `create()`. Invalid values for `action_type` raise
  `ValueError`.
  
  If `setting_name` is `None`, iterate over each setting group representing the
  entire action.
  
  If `setting_name` is not `None`, iterate over each setting or subgroup inside
  each action. For example, `'enabled'` yields the `'enabled'` setting for
  each action. For the list of possible names of settings and subgroups, see
  `create()`.
  u   invalid action type "{}"c         ` s7    d  k r& t   f d    D  S   j k Sd  S(   Nc         3` s   |  ] } |   j  k Vq d  S(   N(   RA   (   R   R@   (   R   (    s?   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/actions.pys	   <genexpr>¦  s    (   R   t   anyRA   (   R   (   t   action_typet   action_types(   R   s?   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/actions.pyt   has_matching_type€  s    N(   R>   R=   R   R,   R%   (   R   R   t   setting_nameR   R   (    (   R   R   s?   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/actions.pyR7     s    u
   constraintRx   c           B` s   e  Z d    Z RS(   c         C` s   | |  _  | |  _ d  S(   N(   t   procedure_namet   unsupported_param_type(   t   selfR   R   (    (    s?   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/actions.pyt   __init__½  s    	(   t   __name__t
   __module__R   (    (    (    s?   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/actions.pyRx   »  s   (0   t   __doc__t
   __future__R    R   R   R   t   future.builtinsRz   t   export_layersR   R   R   RB   t   BUILTIN_TAGSt   DEFAULT_PROCEDURES_GROUPt   DEFAULT_CONSTRAINTS_GROUPR<   R+   R   R   R   R   R   R   R&   R'   R/   R0   R(   R1   R   RN   RP   RS   R   RT   RV   RW   Re   R#   R   R   R   R
   R7   R=   t	   ExceptionRx   (    (    (    s?   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/actions.pyt   <module>:   sl   "
	_			+					c							C			'
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         # -*- coding: utf-8 -*-

"""Background and foreground layer insertion and manipulation."""

from __future__ import absolute_import, division, print_function, unicode_literals
from future.builtins import *

import gimp
from gimp import pdb
import gimpenums

from export_layers import exceptions

from export_layers import pygimplib as pg


def insert_background_layer(batcher, tag):
  return _insert_tagged_layer(batcher, tag, 'after')


def insert_foreground_layer(batcher, tag):
  return _insert_tagged_layer(batcher, tag, 'before')


def _insert_tagged_layer(batcher, tag, insert_mode):
  tagged_items = [
    item for item in batcher.item_tree.iter(with_folders=True, filtered=False)
    if tag in item.tags]
  merged_tagged_layer = None
  orig_merged_tagged_layer = None
  
  def _cleanup_tagged_layers(batcher):
    if orig_merged_tagged_layer is not None and pdb.gimp_item_is_valid(orig_merged_tagged_layer):
      pdb.gimp_item_delete(orig_merged_tagged_layer)
    
    batcher.invoker.remove(cleanup_tagged_layers_action_id, ['cleanup_contents'])
  
  # We use`Invoker.add` instead of `batcher.add_procedure` since the latter
  # would add the function only at the start of processing and we already are in
  # the middle of processing here.
  cleanup_tagged_layers_action_id = batcher.invoker.add(
    _cleanup_tagged_layers, ['cleanup_contents'])
  
  while True:
    image = batcher.current_image
    current_parent = batcher.current_raw_item.parent
    
    position = pdb.gimp_image_get_item_position(image, batcher.current_raw_item)
    if insert_mode == 'after':
      position += 1
    
    if not tagged_items:
      yield
      continue
    
    if orig_merged_tagged_layer is None:
      merged_tagged_layer = _insert_merged_tagged_layer(
        batcher, image, tagged_items, tag, current_parent, position)
      
      orig_merged_tagged_layer = pdb.gimp_layer_copy(merged_tagged_layer, True)
      _remove_locks_from_layer(orig_merged_tagged_layer)
    else:
      merged_tagged_layer = pdb.gimp_layer_copy(orig_merged_tagged_layer, True)
      _remove_locks_from_layer(merged_tagged_layer)
      pdb.gimp_image_insert_layer(image, merged_tagged_layer, current_parent, position)
    
    yield


def _insert_merged_tagged_layer(batcher, image, tagged_items, tag, parent, position):
  first_tagged_layer_position = position
  
  for i, item in enumerate(tagged_items):
    layer_copy = pg.pdbutils.copy_and_paste_layer(
      item.raw, image, parent, first_tagged_layer_position + i, True, True, True)
    layer_copy.visible = True
    
    batcher.invoker.invoke(
      ['before_process_item_contents'], [batcher, batcher.current_item, layer_copy])

  if parent is None:
    children = image.layers
  else:
    children = parent.children
  
  if len(tagged_items) == 1:
    merged_tagged_layer = children[first_tagged_layer_position]
  else:
    second_to_last_tagged_layer_position = first_tagged_layer_position + len(tagged_items) - 2
    
    for i in range(second_to_last_tagged_layer_position, first_tagged_layer_position - 1, -1):
      merged_tagged_layer = pdb.gimp_image_merge_down(
        image, children[i], gimpenums.EXPAND_AS_NECESSARY)
  
  return merged_tagged_layer


def _remove_locks_from_layer(layer):
  pdb.gimp_item_set_lock_content(layer, False)
  if not isinstance(layer, gimp.GroupLayer):
    if gimp.version >= (2, 10):
      pdb.gimp_item_set_lock_position(layer, False)
    pdb.gimp_layer_set_lock_alpha(layer, False)


def merge_background(batcher, merge_type=gimpenums.EXPAND_AS_NECESSARY):
  _merge_tagged_layer(
    batcher,
    merge_type,
    get_background_layer,
    'current_item')


def merge_foreground(batcher, merge_type=gimpenums.EXPAND_AS_NECESSARY):
  _merge_tagged_layer(
    batcher,
    merge_type,
    get_foreground_layer,
    'tagged_layer')


def _merge_tagged_layer(batcher, merge_type, get_tagged_layer_func, layer_to_merge_down_str):
  tagged_layer = get_tagged_layer_func(batcher)
  
  if tagged_layer is not None:
    name = batcher.current_raw_item.name
    visible = pdb.gimp_item_get_visible(batcher.current_raw_item)
    orig_tags = _get_tags(batcher.current_raw_item)
    
    if layer_to_merge_down_str == 'current_item':
      layer_to_merge_down = batcher.current_raw_item
    elif layer_to_merge_down_str == 'tagged_layer':
      layer_to_merge_down = tagged_layer
    else:
      raise ValueError('invalid value for "layer_to_merge_down_str"')
    
    pdb.gimp_item_set_visible(batcher.current_raw_item, True)
    
    merged_layer = pdb.gimp_image_merge_down(
      batcher.current_image, layer_to_merge_down, merge_type)
    merged_layer.name = name
    
    batcher.current_raw_item = merged_layer
    
    pdb.gimp_item_set_visible(batcher.current_raw_item, visible)
    _set_tags(batcher.current_raw_item, orig_tags)
    # We do not expect layer groups as folders to be merged since the plug-in
    # manipulates regular layers only (a layer group is merged into a single
    # layer during processing). Therefore, folder tags are ignored.
    _set_tags(batcher.current_raw_item, set(), pg.itemtree.TYPE_FOLDER)


def get_background_layer(batcher):
  return _get_adjacent_layer(
    batcher,
    lambda position, num_layers: position < num_layers - 1,
    1,
    'insert_background_layers',
    _('There are no background layers.'))


def get_foreground_layer(batcher):
  return _get_adjacent_layer(
    batcher,
    lambda position, num_layers: position > 0,
    -1,
    'insert_foreground_layers',
    _('There are no foreground layers.'))


def _get_adjacent_layer(
      batcher, position_cond_func, adjacent_position_increment,
      insert_tagged_layers_procedure_name, skip_message):
  raw_item = batcher.current_raw_item
  if raw_item.parent is None:
    children = batcher.current_image.layers
  else:
    children = raw_item.parent.children
  
  adjacent_layer = None
  
  num_layers = len(children)
  
  if num_layers > 1:
    position = pdb.gimp_image_get_item_position(batcher.current_image, batcher.current_raw_item)
    if position_cond_func(position, num_layers):
      next_layer = children[position + adjacent_position_increment]
      tags = [
        procedure['arguments/tag'].value
        for procedure in _get_previous_enabled_procedures(
          batcher, batcher.current_procedure, insert_tagged_layers_procedure_name)]
      
      if _has_tag(next_layer, tags, None) or _has_tag(next_layer, tags, pg.itemtree.TYPE_FOLDER):
        adjacent_layer = next_layer
  
  if adjacent_layer is not None:
    # This is necessary for some procedures relying on the active layer, e.g.
    # `plug-in-autocrop-layer`.
    batcher.current_image.active_layer = adjacent_layer
    return adjacent_layer
  else:
    raise exceptions.SkipAction(skip_message)


def _get_previous_enabled_procedures(batcher, current_action, action_orig_name_to_match):
  # HACK: This avoids a circular import. To resolve this, one possible way is to
  # refactor `actions` to turn actions into classes.
  from export_layers import actions
  
  previous_enabled_procedures = []
  
  for procedure in actions.walk(batcher.procedures):
    if procedure == current_action:
      return previous_enabled_procedures
    
    if procedure['enabled'].value and procedure['orig_name'].value == action_orig_name_to_match:
      previous_enabled_procedures.append(procedure)
  
  return previous_enabled_procedures


def _has_tag(layer, tags, item_type=None):
  return any(tag in _get_tags(layer, item_type) for tag in tags)


def _get_tags(layer, item_type=None):
  return pg.itemtree.get_tags_from_raw_item(layer, pg.config.SOURCE_NAME, item_type)


def _set_tags(layer, tags, item_type=None):
  return pg.itemtree.set_tags_for_raw_item(layer, tags, pg.config.SOURCE_NAME, item_type)
                                                                                                                                                                                                       