ó
dòôdc           @` sQ  d  d l  m Z m Z m Z m Z d  d l Z d  d l Z d  d l Z d  d l Z e j	 j
 e j	 j e j e j ƒ  ƒ ƒ ƒ Z y d  d l Z Wn e k
 r¨ e Z n Xe Z d d l m Z e re j d d d e j	 j e ƒ e g d d d	 d
 d d ƒ n  e r1d d l m Z e j d d d d ƒ n  d „  Z e e j	 j e d ƒ ƒ d  d l m Z m Z m Z m  Z  m! Z! m" Z" m# Z# m$ Z$ m% Z% m& Z& m' Z' m( Z( m) Z) m* Z* m+ Z+ m, Z, m- Z- m. Z. m/ Z/ m0 Z0 d  d l1 Z1 d  d l2 Z2 d  d l3 Z3 d d l4 Td d l m5 Z5 d d l m6 Z6 e r$d  d l7 Z7 d  d l8 Z8 d d l m9 Z9 d d l m: Z: d d l m; Z; d d l m< Z< d d l m= Z= d d l m> Z> d d l m? Z? d d l m	 Z	 d d l m@ Z@ d d l mA ZA d d  l mB ZB d  d! l mC ZC d d" lB mD ZD d d# lB mE ZE n  d$ d% d& d' d( g ZF e reF jG d) d* d+ d, d- d. d/ d0 d1 d2 d3 d4 d5 d6 d7 d8 g ƒ n  d aH d9 e( f d: „  ƒ  YZI d; „  ZJ d< „  ZK d= „  ZL d> „  ZM d? „  ZN eJ ƒ  e rMe2 jO ƒ  ZP e2 jO ƒ  ZQ d@ „  ZR dA „  ZS dB dB dB dB dB dB d dC d d dD „
 ZT dE „  ZU dF „  ZV dG „  ZW dH „  ZX n  d S(I   i    (   t   absolute_importt   divisiont   print_functiont   unicode_literalsNi   (   t   loggingt   log_modeu
   exceptionst   log_dirpathst   log_stdout_filenamet   log_stderr_filenameu	   error.logt   log_header_titleu	   pygimplib(   t   _gui_messagest   titlet   app_namec         C` sg   x` t  j |  ƒ D]O } t  j j |  | ƒ } t  j j | ƒ r | t j k r t j j | ƒ q q Wd S(   ud  
  Add directory paths containing external libraries for pygimplib to `sys.path`
  so that modules from these external libraries can be imported as system
  modules (i.e. without using absolute or explicit relative imports).
  
  Modules with the same name that are already installed system-wide override the
  external library modules from `pygimplib`.
  N(   t   ost   listdirt   patht   joint   isdirt   syst   append(   t   dirpatht   filenamet   external_libs_dirpath(    (    sJ   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/__init__.pyt%   _setup_import_of_external_lib_modules&   s    	!u   _lib(   t   asciit   bytest   chrt   dictt   filtert   hext   inputt   intt   listt   mapt   nextt   objectt   octt   opent   powt   ranget   roundt   strt   supert   zip(   t   *(   t   utils(   t   version(   t   invoker(   t   fileformats(   t
   invocation(   t   gui(   t   itemtree(   t   objectfilter(   t	   overwrite(   R   (   t   pdbutils(   t   progress(   t   setting(   t   pdb(   t   SettingGuiTypes(   t   SettingTypesu   loggingu   utilsu   versionu   configu   initu   invokeru   fileformatsu
   invocationu   guiu   itemtreeu   objectfilteru	   overwriteu   pathu   pdbutilsu   progressu   settingu   pdbu	   procedureu   mainu   SettingGuiTypesu   SettingTypest   _Configc           B` s,   e  Z d  „  Z d „  Z d „  Z d „  Z RS(   c         C` s   t  ƒ  j d i  ƒ d  S(   Nu   _config(   R*   t   __setattr__(   t   self(    (    sJ   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/__init__.pyt   __init__   s    c         C` s   | |  j  | <d  S(   N(   t   _config(   R>   t   namet   value(    (    sJ   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/__init__.pyR=   ‚   s    c         C` sO   | |  j  k r' t d j | ƒ ƒ ‚ n  |  j  | } t | ƒ rG | ƒ  S| Sd  S(   Nu"   configuration entry "{}" not found(   R@   t   AttributeErrort   formatt   callable(   R>   RA   t   attr(    (    sJ   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/__init__.pyt   __getattr__…   s    c         C` s   | |  j  k S(   N(   R@   (   R>   RA   (    (    sJ   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/__init__.pyt   __hasattr__   s    (   t   __name__t
   __module__R?   R=   RG   RH   (    (    (    sJ   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/__init__.pyR<   }   s   			c           ` sR  t  d  k	 r d  Sd „  ‰  t ƒ  a  t t  _ t ƒ  ‰ ˆ d  k	 r‚ t j j ˆ ƒ t  _ ˆ t  _	 t j j
 ˆ ƒ t  _ d „  t  _ nK d t  _ t j j
 t ƒ t  _	 t j j
 t  j	 ƒ t  _ t j j
 t ƒ t  _ t  j t  _ d „  t  _ d t  _ d „  t  _ ‡  ‡ f d †  t  _ g  t  _ t r*d t  _ n	 d	 t  _ t t  ƒ t ƒ  t t  ƒ d  S(
   Nc         S` s+   |  d  k r d Sd t j j d d ƒ Sd  S(   Nu   gimp20-pythonu   gimp-plugin-u   _u   -(   t   Nonet   configt   PLUGIN_NAMEt   replace(   t   root_plugin_dirpath(    (    sJ   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/__init__.pyt   _get_domain_nameš   s    c           S` s   t  j S(   N(   RL   t   PLUGIN_DIRPATH(    (    (    sJ   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/__init__.pyt   <lambda>ª   t    u   gimp_pluginc           S` s   t  j S(   N(   RL   RM   (    (    (    sJ   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/__init__.pyRR   ³   RS   u   1.0c           S` s   t  j j t j t j d ƒ S(   Nu   locale(   R   R   R   RL   t   PLUGINS_DIRPATHRM   (    (    (    sJ   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/__init__.pyRR   ·   RS   c           ` s
   ˆ  ˆ ƒ S(   N(    (    (   RP   RO   (    sJ   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/__init__.pyRR   ¸   RS   u
   exceptionsu   none(   RL   RK   R<   t   PYGIMPLIB_DIRPATHt   _get_root_plugin_dirpathR   R   t   basenamet   _DEFAULT_PLUGIN_NAMERQ   t   dirnameRT   t   DEFAULT_LOGS_DIRPATHRM   t   PLUGIN_TITLEt   PLUGIN_VERSIONt   LOCALE_DIRPATHt   DOMAIN_NAMEt   BUG_REPORT_URL_LISTt    _gimp_dependent_modules_importedt   LOG_MODEt   _init_config_builtint   _init_config_from_filet   _init_config_builtin_delayed(    (    (   RP   RO   sJ   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/__init__.pyt   _init_config”   s6    									
c          C` s2   t  j ƒ  }  |  r* t j j |  d d ƒ Sd  Sd  S(   Niÿÿÿÿi   (   t   inspectt   stackR   R   RY   RK   (   t   frame_stack(    (    sJ   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/__init__.pyRV   È   s    c         C` s–   g  |  _  |  j  j |  j ƒ t r_ t j j t j d ƒ } | |  j k r_ |  j  j | ƒ q_ n  |  j |  _	 |  j |  _
 d |  _ d |  _ d |  _ d  S(   Nu   plug-insu
   output.logu	   error.logi2   (   t   PLUGINS_LOG_DIRPATHSR   RZ   R`   R   R   R   t   gimpt	   directoryt   PLUGINS_LOG_STDOUT_DIRPATHt   PLUGINS_LOG_STDERR_DIRPATHt   PLUGINS_LOG_STDOUT_FILENAMEt   PLUGINS_LOG_STDERR_FILENAMEt'   GIMP_CONSOLE_MESSAGE_DELAY_MILLISECONDS(   RL   t   plugins_dirpath_alternate(    (    sJ   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/__init__.pyRb   Ñ   s    			c          C` sœ   d  }  t t d ƒ r! t j }  n  t t _ y d d l m } Wn9 t k
 ry y d d l m } Wqz t k
 ru qz Xn X|  d  k r t ` n	 |  t _ d  S(   Nu   ci   (   t
   config_dev(   RL   (   RK   t   hasattrt   __builtin__t   cRL   RS   Rr   t   ImportError(   t   orig_builtin_ct   plugin_config(    (    sJ   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/__init__.pyRc   æ   s    		c         ` sè   ‡  f d †  } t  r‚ | ƒ  ˆ  _ t j ˆ  j ƒ ˆ  _ t j ˆ  j ƒ ˆ  _ t j j j	 t
 j d ˆ  j f d ˆ  j f g ƒ ƒ n  t j ˆ  j ˆ  j d t ƒt  s³ ˆ  j d k rä t j ˆ  j ˆ  j ˆ  j ˆ  j ˆ  j ˆ  j ƒ n  d  S(   Nc           ` s(   ˆ  j  j d ƒ r ˆ  j  Sd ˆ  j  Sd  S(   Nu   plug_inu   plug_in_(   RM   t
   startswith(    (   RL   (    sJ   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/__init__.pyt   _get_setting_source_name   s    u   sessionu
   persistentt   unicodeu   gimp_console(   R`   t   SOURCE_NAMER8   t   GimpShelfSourcet   SESSION_SOURCEt   GimpParasiteSourcet   PERSISTENT_SOURCEt	   persistort	   Persistort   set_default_setting_sourcest   collectionst   OrderedDictt   gettextt   installR^   R]   t   TrueRa   R   t
   log_outputRi   Rn   Ro   R[   Rp   (   RL   Rz   (    (   RL   sJ   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/__init__.pyRd   þ   s    c          ` s   ‡  f d †  } | S(   uì  Installs a function as a GIMP procedure.
    
    Use this function as a decorator over a function to be exposed to the GIMP
    procedural database (PDB).
    
    The installed procedure can then be accessed via the GIMP (PDB) and,
    optionally, from the GIMP user interface.
    
    The function name is used as the procedure name as found in the GIMP PDB.
    
    The following keyword arguments are accepted:
    
    * `blurb` - Short description of the procedure.
    
    * `description` - More detailed information about the procedure.
    
    * `author` - Author of the plug-in.
    
    * `copyright_holder` - Copyright holder of the plug-in.
    
    * `date` - Dates (usually years) at which the plug-in development was
      active.
    
    * `menu_name` - Name of the menu entry in the GIMP user interface.
    
    * `menu_path` - Path of the menu entry in the GIMP user interface.
    
    * `image_types` - Image types to which the procedure applies (e.g. RGB or
      indexed). Defaults to `'*'` (any image type).
    
    * `parameters` - Procedure parameters. This is a list of tuples of three
      elements: `(PDB type, name, description)`. Alternatively, you may pass a
      `setting.Group` instance or a list of `setting.Group` instances containing
      plug-in settings.
    
    * `return_values` - Return values of the procedure, usable when calling the
      procedure programmatically. The format of `return_values` is the same as
      `parameters`.
    
    Example:
      
      import pygimplib as pg
      
      \@pg.procedure(
        blurb='Export layers as separate images',
        author='John Doe',
        menu_name=_('E_xport Layers...'),
        menu_path='<Image>/File/Export',
        parameters=[
          (gimpenums.PDB_INT32, 'run-mode', 'The run mode'),
          (gimpenums.PDB_IMAGE, 'image', 'The current image'),
          (gimpenums.PDB_STRING, 'dirpath', 'Output directory path')]
      )
      def plug_in_export_layers(run_mode, image, *args):
        ...
    c         ` s   ˆ  t  |  <|  t |  j <|  S(   N(   t   _procedurest   _procedures_namesRI   (   t	   procedure(   t   kwargs(    sJ   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/__init__.pyt   procedure_wrapperZ  s    
(    (   R   RŽ   (    (   R   sJ   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/__init__.pyRŒ      s    :c           C` s   t  j d d t t ƒ d S(   u{   Enables installation and running of GIMP procedures.
    
    Call this function at the end of your main plug-in file.
    N(   Rj   t   mainRK   t   _queryt   _run(    (    (    sJ   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/__init__.pyR   a  s    u    u   *c         C` sf   d „  } t  j |  j | | | | | | | t j | |	 ƒ | |
 ƒ ƒ | rb t  j |  j | ƒ n  d  S(   Nc         S` sP   g  } |  rL t  |  d t j t j f ƒ } | rC t j |  Œ  } qL |  } n  | S(   Ni    (   t
   isinstanceR8   t   Settingt   Groupt   create_params(   t   paramst
   pdb_paramst   has_settings(    (    sJ   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/__init__.pyt   _get_pdb_paramsu  s    	(   Rj   t   install_procedureRI   t	   gimpenumst   PLUGINt   menu_register(   RŒ   t   blurbt   descriptiont   authort   copyright_noticet   datet	   menu_namet	   menu_patht   image_typest
   parameterst   return_valuesR™   (    (    sJ   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/__init__.pyt   _install_procedureh  s    		c          C` sD   t  j t j t j ƒ x' t j ƒ  D] \ }  } t |  |  q# Wd  S(   N(   Rj   t   domain_registerRL   R^   R]   RŠ   t   itemsR¨   (   RŒ   R   (    (    sJ   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/__init__.pyR   ’  s    c         C` sA   t  t |  | d ƒ } t t d ƒ r3 t j ƒ  n  | | Œ  d  S(   Ni    u   gimp_ui_init(   t   _add_gui_excepthookR‹   Rs   t   gimpuit   gimp_ui_init(   t   procedure_namet   procedure_paramsRŒ   (    (    sJ   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/__init__.pyR‘   ˜  s
    c         C` sU   | t  j k rM t j t ƒ t j d t j d t j d t j ƒ } | |  ƒ S|  Sd  S(   NR   R   t   report_uri_list(	   R›   t   RUN_INTERACTIVER2   t&   set_gui_excepthook_additional_callbackt'   _display_message_on_setting_value_errort   add_gui_excepthookRL   R[   R_   (   RŒ   t   run_modet   add_gui_excepthook_func(    (    sJ   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/__init__.pyR«   ¡  s    			
c         C` s:   t  |  t j ƒ r2 t j t j t | ƒ ƒ ƒ t St	 Sd  S(   N(
   t
   issubclassR8   t   SettingValueErrorRj   t   messageR-   t   safe_encode_gimpR)   Rˆ   t   False(   t   exc_typet	   exc_valuet   exc_traceback(    (    sJ   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/__init__.pyR³   ¯  s    (Y   t
   __future__R    R   R   R   Rf   R   R   t	   tracebackR   t   realpathRY   t   getfilet   currentframeRU   Rj   Rv   R»   R`   Rˆ   RS   R   R‰   RK   R
   t   set_gui_excepthookR   R   t   future.builtinsR   R   R   R   R   R   R   R   R    R!   R"   R#   R$   R%   R&   R'   R(   R)   R*   R+   Rt   R„   R†   t	   constantsR-   R.   R›   R¬   R/   R0   R1   R2   R3   R4   R5   R6   R7   R8   R9   R:   R;   t   __all__t   extendRL   R<   Re   RV   Rb   Rc   Rd   R…   RŠ   R‹   RŒ   R   R¨   R   R‘   R«   R³   (    (    (    sJ   /home/josie/.config/GIMP/2.10/plug-ins/export_layers/pygimplib/__init__.pyt   <module>   sÂ   "-
	
	‚
		4						A						                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         # -*- coding: utf-8 -*-

"""Widgets and functions to display GUI messages (particularly error messages),
imported before the rest of pygimplib is initialized.

This module contains:
* GTK exception dialog
* GTK generic message dialog
* wrapper for `sys.excepthook` that displays the GTK exception dialog when an
  unhandled exception is raised

This module should not be used directly. Use the `gui` package as the contents
of this module are included in the package.
"""

# NOTE: In order to allow logging errors as early as possible (before plug-in
# initialization), the `future` library is not imported in case some modules in
# the library are not available in the installed Python distribution and would
# thus cause an `ImportError` to be raised.

from __future__ import absolute_import, division, print_function, unicode_literals

str = unicode

import __builtin__
import functools
import sys
import traceback

try:
  import webbrowser
except ImportError:
  _webbrowser_module_found = False
else:
  _webbrowser_module_found = True

import pygtk
pygtk.require('2.0')
import gtk
import gobject
import pango

__all__ = [
  'display_alert_message',
  'display_message',
  'add_gui_excepthook',
  'set_gui_excepthook',
  'set_gui_excepthook_parent',
  'set_gui_excepthook_additional_callback',
]


ERROR_EXIT_STATUS = 1


def display_alert_message(
      title=None,
      app_name=None,
      parent=None,
      message_type=gtk.MESSAGE_ERROR,
      flags=gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
      message_markup=None,
      message_secondary_markup=None,
      details=None,
      display_details_initially=True,
      report_uri_list=None,
      report_description=None,
      button_stock_id=gtk.STOCK_CLOSE,
      button_response_id=gtk.RESPONSE_CLOSE,
      focus_on_button=False):
  """
  Display a message to alert the user about an error or an exception that
  occurred in the application.
  
  Parameters:
  
  * `title` - Message title.
  
  * `app_name` - Name of the application to use in the default contents of
    `message_secondary_markup`.
  
  * `parent` - Parent widget.
  
  * `message_type` - GTK message type (`gtk.MESSAGE_ERROR`, etc.).
  
  * `flags` - GTK dialog flags.
  
  * `message_markup` - Primary message text to display as markup.
  
  * `message_secondary_markup` - Secondary message text to display as markup.
  
  * `details` - Text to display in a box with details. If `None`, do not display
    any box.
  
  * `display_details_initially` - If `True`, display the details by default,
    otherwise hide them in an expander.
  
  * `report_uri_list` - List of (name, URL) pairs where the user can report
    the error. If no report list is desired, pass `None` or an empty sequence.
  
  * `report_description` - Text accompanying `report_uri_list`. If `None`, use
    default text. To suppress displaying the report description, pass an empty
    string.
  
  * `button_stock_id` - Stock ID of the button to close the dialog with.
  
  * `button_response_id` - Response ID of the button to close the dialog with.
  
  * `focus_on_button` - If `True`, focus on the button to close the dialog with.
    If `False`, focus on the box with details if `details` is not `None`,
    otherwise let the message dialog determine the focus widget.
  """
  if not ('_' in __builtin__.__dict__ or '_' in globals()):
    # This is a placeholder function until `gettext` is properly initialized.
    def _(str_):
      return str_
  else:
    # This is necessary since defining a local variable/function, even inside a
    # condition, obscures a global variable/function of the same name.
    _ = __builtin__.__dict__.get('_', None) or globals()['_']
  
  if app_name is None:
    app_name = _('Plug-in')
  
  if message_markup is None:
    message_markup = (
      '<span font_size="large"><b>{0}</b></span>'.format(
        _('Oops. Something went wrong.')))
  
  if message_secondary_markup is None:
    message_secondary_markup = _(
      '{0} encountered an unexpected error and has to close. Sorry about that!').format(
        gobject.markup_escape_text(app_name))
  
  if report_description is None:
    report_description = _(
      'You can help fix this error by sending a report with the text'
      ' in the details above to one of the following sites')
  
  dialog = gtk.MessageDialog(parent, type=message_type, flags=flags)
  dialog.set_transient_for(parent)
  if title is not None:
    dialog.set_title(title)
  
  if message_markup:
    dialog.set_markup(message_markup)
  if message_secondary_markup:
    dialog.format_secondary_markup(message_secondary_markup)
  
  if details is not None:
    expander = gtk.Expander()
    expander.set_use_markup(True)
    expander.set_label('<b>' + _('Details') + '</b>')
    
    vbox_details = gtk.VBox(homogeneous=False)
    vbox_details.set_spacing(3)
    
    details_window = _get_details_window(details)
    vbox_details.pack_start(details_window, expand=False, fill=False)
    
    if report_uri_list:
      vbox_labels_report = _get_report_link_buttons_and_copy_icon(
        report_uri_list, report_description, _('(right-click to copy link)'), details)
      vbox_details.pack_start(vbox_labels_report, expand=False, fill=False)
    
    if display_details_initially:
      expander.set_expanded(True)
    
    expander.add(vbox_details)
    dialog.vbox.pack_start(expander, expand=False, fill=False)
  else:
    details_window = None
  
  dialog.add_button(button_stock_id, button_response_id)
  
  if focus_on_button:
    button = dialog.get_widget_for_response(button_response_id)
    if button is not None:
      dialog.set_focus(button)
  else:
    if details_window is not None and display_details_initially:
      dialog.set_focus(details_window)
  
  dialog.show_all()
  response_id = dialog.run()
  dialog.destroy()
  
  return response_id


def _get_details_window(details_text):
  scrolled_window = gtk.ScrolledWindow()
  scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
  scrolled_window.set_size_request(400, 200)
  scrolled_window.set_shadow_type(gtk.SHADOW_IN)
  
  exception_text_view = gtk.TextView()
  exception_text_view.set_editable(False)
  exception_text_view.set_wrap_mode(gtk.WRAP_WORD)
  exception_text_view.set_cursor_visible(False)
  exception_text_view.set_pixels_above_lines(1)
  exception_text_view.set_pixels_below_lines(1)
  exception_text_view.set_pixels_inside_wrap(0)
  exception_text_view.set_left_margin(5)
  exception_text_view.set_right_margin(5)
  exception_text_view.get_buffer().set_text(details_text)
  
  scrolled_window.add(exception_text_view)
  
  return scrolled_window


def _get_report_link_buttons_and_copy_icon(
      report_uri_list, report_description, label_report_text_instructions, details):
  if not report_uri_list:
    return None
  
  vbox = gtk.VBox(homogeneous=False)
  
  if report_description:
    label_report_text = report_description
    if not _webbrowser_module_found:
      label_report_text += ' ' + label_report_text_instructions
    label_report_text += ':'
    
    label_report = gtk.Label(label_report_text)
    label_report.set_alignment(0, 0.5)
    label_report.set_padding(3, 6)
    label_report.set_line_wrap(True)
    label_report.set_line_wrap_mode(pango.WRAP_WORD)
    
    button_copy_to_clipboard = gtk.Button()
    button_copy_to_clipboard.set_relief(gtk.RELIEF_NONE)
    button_copy_to_clipboard.add(
      gtk.image_new_from_pixbuf(
        button_copy_to_clipboard.render_icon(gtk.STOCK_COPY, gtk.ICON_SIZE_MENU)))
    button_copy_to_clipboard.set_tooltip_text(_('Copy details to clipboard'))
    button_copy_to_clipboard.connect(
      'clicked', lambda *args, **kwargs: gtk.clipboard_get().set_text(details))
    
    hbox_label_report_and_copy_icon = gtk.HBox(homogeneous=False)
    hbox_label_report_and_copy_icon.set_spacing(3)
    hbox_label_report_and_copy_icon.pack_start(label_report, expand=True, fill=True)
    hbox_label_report_and_copy_icon.pack_start(
      button_copy_to_clipboard, expand=False, fill=False)
    
    vbox.pack_start(hbox_label_report_and_copy_icon, expand=False, fill=False)
  
  report_linkbuttons = []
  for name, uri in report_uri_list:
    linkbutton = gtk.LinkButton(uri, label=name)
    linkbutton.set_alignment(0, 0.5)
    report_linkbuttons.append(linkbutton)
  
  for linkbutton in report_linkbuttons:
    vbox.pack_start(linkbutton, expand=False, fill=False)
  
  if _webbrowser_module_found:
    # Apparently, GTK doesn't know how to open URLs on Windows, hence the custom
    # solution.
    for linkbutton in report_linkbuttons:
      linkbutton.connect(
        'clicked', lambda linkbutton: webbrowser.open_new_tab(linkbutton.get_uri()))
  
  return vbox


def display_message(
      message,
      message_type,
      title=None,
      parent=None,
      buttons=gtk.BUTTONS_OK,
      message_in_text_view=False,
      button_response_id_to_focus=None,
      message_markup=False):
  """
  Display a generic message.
  
  Parameters:
  
  * `message` - The message to display.
  
  * `message_type` - GTK message type (`gtk.MESSAGE_INFO`, etc.).
  
  * `title` - Message title.
  
  * `parent` - Parent GUI element.
  
  * `buttons` - Buttons to display in the dialog.
  
  * `message_in_text_view` - If `True`, display text the after the first newline
    character in a text view.
  
  * `button_response_id_to_focus` - Response ID of the button to set as the
    focus. If `None`, the dialog determines which widget gets the focus.
  
  * `message_markup` - If `True`, treat `message` as markup text.
  """
  dialog = gtk.MessageDialog(
    parent=parent,
    type=message_type,
    flags=gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
    buttons=buttons)
  dialog.set_transient_for(parent)
  if title is not None:
    dialog.set_title(title)
  
  if not message_markup:
    processed_message = gobject.markup_escape_text(message)
  else:
    processed_message = message
  
  messages = processed_message.split('\n', 1)
  if len(messages) > 1:
    dialog.set_markup(messages[0])
    
    if message_in_text_view:
      text_view = gtk.TextView()
      text_view.set_editable(False)
      text_view.set_wrap_mode(gtk.WRAP_WORD)
      text_view.set_cursor_visible(False)
      text_view.set_pixels_above_lines(1)
      text_view.set_pixels_below_lines(1)
      text_view.set_pixels_inside_wrap(0)
      text_view.get_buffer().set_text(messages[1])
      
      scrolled_window = gtk.ScrolledWindow()
      scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
      scrolled_window.set_property('height-request', 100)
      scrolled_window.set_shadow_type(gtk.SHADOW_IN)
      
      scrolled_window.add(text_view)
      
      vbox = dialog.get_message_area()
      vbox.pack_end(scrolled_window, expand=True, fill=True)
    else:
      dialog.format_secondary_markup(messages[1])
  else:
    dialog.set_markup(processed_message)
  
  if button_response_id_to_focus is not None:
    button = dialog.get_widget_for_response(button_response_id_to_focus)
    if button is not None:
      dialog.set_focus(button)
  
  dialog.show_all()
  response_id = dialog.run()
  dialog.destroy()
  
  return response_id


#===============================================================================

_gui_excepthook_parent = None
_gui_excepthook_additional_callback = lambda *args, **kwargs: False


def add_gui_excepthook(title, app_name, report_uri_list=None, parent=None):
  """
  Return a decorator that modifies `sys.excepthook` to display an error dialog
  for unhandled exceptions and terminates the application. `sys.excepthook` is
  restored after the decorated function is finished.
  
  The dialog will not be displayed for exceptions which are not subclasses of
  `Exception` (such as `SystemExit` or `KeyboardInterrupt`).
  
  Parameters:
  
  * `title` - Dialog title.
  
  * `report_uri_list` - List of (name, URL) tuples where the user can report
    the error. If no report list is desired, pass `None` or an empty sequence.
  
  * `parent` - Parent GUI element.
  """
  global _gui_excepthook_parent
  
  _gui_excepthook_parent = parent
  
  def gui_excepthook(func):
    
    @functools.wraps(func)
    def func_wrapper(self, *args, **kwargs):
      
      def _gui_excepthook(exc_type, exc_value, exc_traceback):
        _gui_excepthook_generic(
          exc_type,
          exc_value,
          exc_traceback,
          orig_sys_excepthook,
          title,
          app_name,
          _gui_excepthook_parent,
          report_uri_list)
      
      orig_sys_excepthook = sys.excepthook
      sys.excepthook = _gui_excepthook
      
      func(self, *args, **kwargs)
      
      sys.excepthook = orig_sys_excepthook
    
    return func_wrapper
  
  return gui_excepthook


def set_gui_excepthook(title, app_name, report_uri_list=None, parent=None):
  """
  Modify `sys.excepthook` to display an error dialog for unhandled exceptions.
  
  The dialog will not be displayed for exceptions which are not subclasses of
  `Exception` (such as `SystemExit` or `KeyboardInterrupt`).
  
  For information about parameters, see `add_gui_excepthook()`.
  """
  global _gui_excepthook_parent
  
  _gui_excepthook_parent = parent
  
  def gui_excepthook(exc_type, exc_value, exc_traceback):
    _gui_excepthook_generic(
      exc_type,
      exc_value,
      exc_traceback,
      orig_sys_excepthook,
      title,
      app_name,
      _gui_excepthook_parent,
      report_uri_list)
  
  orig_sys_excepthook = sys.excepthook
  sys.excepthook = gui_excepthook


def set_gui_excepthook_parent(parent):
  """
  Set the parent GUI element to attach the exception dialog to when using
  `add_gui_excepthook()`. This function allows to modify the parent dynamically
  even after decorating a function with `add_gui_excepthook()`.
  """
  global _gui_excepthook_parent
  
  _gui_excepthook_parent = parent


def set_gui_excepthook_additional_callback(callback):
  """
  Set a callback to be invoked at the beginning of exception handling. If the
  callback returns `True`, terminate exception handling at this point. Returning
  `True` consequently prevents the error dialog from being displayed and the
  application from being terminated.
  
  The callback takes the same parameters as `sys.excepthook`.
  """
  global _gui_excepthook_additional_callback
  
  _gui_excepthook_additional_callback = callback


def _gui_excepthook_generic(
      exc_type,
      exc_value,
      exc_traceback,
      orig_sys_excepthook,
      title,
      app_name,
      parent,
      report_uri_list):
  callback_result = _gui_excepthook_additional_callback(
    exc_type, exc_value, exc_traceback)
  
  if callback_result:
    return
  
  orig_sys_excepthook(exc_type, exc_value, exc_traceback)
  
  if issubclass(exc_type, Exception):
    exception_message = ''.join(
      traceback.format_exception(exc_type, exc_value, exc_traceback))
    
    display_alert_message(
      title=title,
      app_name=app_name,
      parent=parent,
      details=exception_message,
      report_uri_list=report_uri_list)
    
    # Make sure to quit the application since unhandled exceptions can
    # mess up the application state.
    if gtk.main_level() > 0:
      gtk.main_quit()
    
    sys.exit(ERROR_EXIT_STATUS)
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           