"""
Pythonic pointfree programming.

* Full documentation: http://pointfree.readthedocs.org/en/latest/
* Project page: https://github.com/markshroyer/pointfree

Copyright notice
----------------

Copyright 2013 Mark Shroyer

Licensed under the Apache License, Version 2.0 (the "License"); you may not
use this file except in compliance with the License.  You may obtain a copy
of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
License for the specific language governing permissions and limitations
under the License.

Usage
-----

The general use case is to wrap functions in the
:py:class:`~pointfree.pointfree` wrapper / decorator class, granting them
both automatic partial application support and a pair of function
composition operators::

    >>> from pointfree import *

    >>> @pointfree
    ... def pfadd(a, b):
    ...     return a + b

    >>> @pointfree
    ... def pfexp(n, exp):
    ...     return n ** exp

    >>> fn = pfexp(exp=2) * pfadd(1)
    >>> fn(3)
    16

:py:class:`pointfree.pointfree` inherits from the
:py:class:`pointfree.partial` class (not to be confused with
:py:func:`functools.partial`), which provides automatic partial application
but not the function composition operators.  See :py:class:`partial's
<pointfree.partial>` documentation for details of the partial application
semantics, and :py:class:`pointfree's <pointfree.pointfree>` documentation
for information about the function composition operators.

The module also includes a number of pre-defined helper functions which can
be combined for various purposes::

    >>> fn = pfmap(lambda x: x**3) >> pfprint_all

    >>> fn(range(4))
    0
    1
    8
    27

Refer to the section :ref:`helper_functions` for information about the
helpers provided by this module.

"""

from __future__ import print_function

__author__  = "Mark Shroyer"
__email__   = "code@markshroyer.com"
__version__ = "1.1.1"

__all__ = [
    'partial',
    'pointfree',
    'pf',
    'pfmap',
    'pfreduce',
    'pffilter',
    'pfcollect',
    'pfprint',
    'pfprint_all',
    'pfignore_all',
    ]

import sys, inspect, types, itertools, functools

# No getfullargspec in Python 2, since there are no keyword-only arguments.
if hasattr(inspect, 'getfullargspec'):
    from inspect import getfullargspec
else:
    def getfullargspec(f):
        return inspect.getargspec(f) + ([], None, {})

class partial(object):
    """Wraps a regular Python function or method into a callable object
    supporting automatic partial application.

    :param func: Function or method to wrap
    :param pargs: Optional, positional arguments for the wrapped function
    :param kargs: Optional, keyword arguments for the wrapped function

    Example::

        >>> @partial
        ... def foo(a,b,c):
        ...     return a + b + c
        >>> foo(1,2,3)
        6
        >>> foo(1)(2)(3)
        6

    Generally speaking, the evaluation strategy with regard to automatic
    partial application is to apply all given arguments to the underlying
    function as soon as possible.

    When a :py:class:`~pointfree.partial` instance is called, the
    positional and keyword arguments supplied are combined with the
    instance's own cache of arguments for the wrapped function (which is
    empty to begin with, for instances directly wrapping -- or applied as
    decorators to -- pure Python functions or methods).  If the combined
    set of arguments is sufficient to invoke the wrapped function, then the
    function is called and its result returned.  If the combined arguments
    are *not* sufficient, then a new copy of the wrapper is returned
    instead, with the new combined argument set in its cache.

    Calling a :py:class:`~pointfree.partial` object never changes its
    state; instances are immutable for practical purposes, so they can be
    called and reused indefinitely::

        >>> p = q = foo(1,2)
        >>> p(3)
        6
        >>> q(4) # Using the same instance twice
        7

    Arguments with default values do not need to be explicitly specified in
    order for evaluation to occur.  In the following example, ``foo2`` can
    be evaluated as soon as we have specified the arguments ``a`` and
    ``b``::

        >>> @partial
        ... def foo2(a, b, c=3):
        ...     return a + b + c

        >>> foo2(1,2)
        6
        >>> foo2(1)(2)
        6

    However, if extra arguments are supplied prior to evaluation, and if
    the underlying function is capable of accepting those arguments, then
    those will be passed to the function as well.  If we call ``foo2`` as
    follows, the third argument will be passed to the wrapped function as
    ``c``, overriding its default value::

        >>> foo2(1,2,5)
        8
        >>> foo2(3)(4,5)
        12

    This works similarly with functions that accept variable positional
    argument lists::

        >>> @partial
        ... def foo3(a, *args):
        ...     return a + sum(args)

        >>> foo3(1)
        1
        >>> foo3(1,2)
        3
        >>> foo3(1,2,3)
        6

    Or variable keyword argument lists::

        >>> @partial
        ... def foo4(a, **kargs):
        ...     kargs.update({'a': a})
        ...     return kargs

        >>> result = foo4(3, b=4, c=5)
        >>> for key in sorted(result.keys()):
        ...     print("%s: %s" % (key, result[key]))
        a: 3
        b: 4
        c: 5

    But if you try to supply an argument that the function cannot accept, a
    :py:exc:`~exceptions.TypeError` will be raised as soon as you attempt
    to do so -- the wrapper doesn't wait until the underlying function is
    called before raising the exception (unlike with
    :py:func:`functools.partial`)::

        >>> @partial
        ... def foo5(a, b, c):
        ...     return a + b + c

        >>> foo5(d=7)
        Traceback (most recent call last):
            ...
        TypeError: foo5() got an unexpected keyword argument 'd'

    There are some sutble differences between how automatic partial
    application works in this module and the semantics of regular Python
    function application (or, again, of :py:func:`functools.partial`).
    First, keyword arguments to partially applied functions can override an
    argument specified in a previous call::

        >>> @partial
        ... def foo6(a, b, c):
        ...     return (a, b, c)

        >>> foo6(1)(b=2)(b=3)(4) # overriding b given as keyword
        (1, 3, 4)
        >>> foo6(1,2)(b=3)(4) # overriding b given positionally
        (1, 3, 4)

    Also, the wrapper somewhat blurs the line between positional and
    keyword arguments for the sake of flexibilty.  If an argument is
    specified with a keyword and then "reached" by a positional argument in
    a subsequent call, the remaining positional argument values "wrap
    around" the argument previously specified as a keyword.

    This second difference is best illustrated by example.  Again using the
    function ``foo6`` from above, if we specify ``b`` as a keyword
    argument::

        >>> p = foo6(b=2)

    and then apply two positional arguments to the resulting
    :py:class:`~pointfree.partial` instance, those arguments will be used
    to specify ``a`` and ``c``, skipping over ``b`` because it has already
    been specified:

        >>> p(1,3)
        (1, 2, 3)

    This approach was chosen because it allows us to compose partial
    applications of functions where a previous argument has been specified
    as a keyword argument.

    As well as functions, :py:class:`~pointfree.partial` can be applied to
    methods, including class and static methods::

        >>> class Foo7(object):
        ...     m = 2
        ...
        ...     def __init__(self, n):
        ...         self.n = n
        ...
        ...     @partial
        ...     def bar_inst(self, a, b, c):
        ...         return self.m + self.n + a + b + c
        ...
        ...     @partial
        ...     @classmethod
        ...     def bar_class(klass, a, b, c):
        ...         return klass.m + a + b + c
        ...
        ...     @partial
        ...     @staticmethod
        ...     def bar_static(a, b, c):
        ...         return a + b + c

        >>> f = Foo7(3)
        >>> f.bar_inst(4)(5)(6)
        20
        >>> f.bar_class(3)(4)(5)
        14
        >>> f.bar_static(2)(3)(4)
        9

    The wrapper can also be instantiated from another
    :py:class:`~pointfree.partial` instance::

        >>> def foo8(a, b, c, *args):
        ...     return a + b + c + sum(args)

        >>> p = partial(foo8, 1)
        >>> q = partial(p, 2)
        >>> q(3)
        6

    Or even from a :py:func:`functools.partial` instance:

        >>> p = functools.partial(foo8, 1)
        >>> q = partial(p)
        >>> q(2)(3)
        6

    However, it cannot currently wrap a Python builtin function (or a
    :py:func:`functools.partial` instance which wraps a builtin function),
    as Python does not currently provide sufficient reflection for its
    builtins.

    While you will probably apply :py:class:`~pointfree.partial` as a
    decorator when defining your own functions, you can also wrap existing
    functions by instantiating the class directly::

        >>> partial(foo8)(1)(2)(3)
        6

    Or like with :py:func:`functools.partial`, you can specify arguments
    for the wrapped function when you instantiate a wrapper:

        >>> p = partial(foo8, 1)
        >>> p(2)(3)
        6

    But unlike calling an existing wrapper instance, the wrapped function
    will not be invoked during instantiation even if enough arguments are
    supplied in order to do so; invocation does not occur until the
    :py:class:`~pointfree.partial` instance is called at least once, even
    with an empty argument list:

        >>> p = partial(foo8, 1, 2, 3)
        >>> type(p)
        <class 'pointfree.partial'>
        >>> p()
        6
        >>> p(4)
        10

    """

    def __init__(self, func, *pargs, **kargs):
        self.func = func
        self.argv = {}
        self.extra_argv = []
        self.__call_error = None

        if isinstance(func, partial):
            self.func = func.func
            functools.update_wrapper(self, self.func)
            inst = func
            self.argv = inst.argv
            self.extra_argv = inst.extra_argv
            self.__sig_from_partial(inst)

        elif isinstance(func, functools.partial):
            self.func = func.func
            functools.update_wrapper(self, self.func)
            self.__sig_from_func(self.func)
            partial_args = func.args or ()
            partial_keywords = func.keywords or {}
            self.__update_argv(*partial_args, **partial_keywords)

        elif isinstance(func, classmethod) or isinstance(func, staticmethod):
            self.__call_error = "'%s' object is not callable" % type(func).__name__

        else:
            functools.update_wrapper(self, func)
            self.__sig_from_func(func)

        self.__update_argv(*pargs, **kargs)

    def __sig_from_func(self, func):
        """Extract function signature, default arguments, keyword-only
        arguments, and whether or not variable positional or keyword
        arguments are allowed.  This also supports calling unbound instance
        methods by passing an object instance as the first argument;
        however, unbound classmethod and staticmethod objects are not
        callable, so we do not attempt to support them here."""

        if isinstance(func, types.MethodType):
            # A bound instance or class method.
            argspec = getfullargspec(func.__func__)
            self.pargl = argspec[0][1:]
        else:
            # A regular function, an unbound instance method, or a
            # bound static method.
            argspec = getfullargspec(func)
            self.pargl = argspec[0][:]

        if argspec[3] is not None:
            def_offset = len(self.pargl) - len(argspec[3])
            self.def_argv = dict((self.pargl[def_offset+i],argspec[3][i]) \
                                     for i in range(len(argspec[3])))
        else:
            self.def_argv = {}

        self.var_pargs = argspec[1] is not None
        self.var_kargs = argspec[2] is not None
        self.kargl     = argspec[4]

        # We need keyword-only arguments' default values too.
        if argspec[5] is not None:
            self.def_argv.update(argspec[5])

    def __sig_from_partial(self, inst):
        """Extract function signature from an existing partial instance."""

        self.pargl     = list(inst.pargl)
        self.kargl     = list(inst.kargl)
        self.def_argv  = inst.def_argv.copy()
        self.var_pargs = inst.var_pargs
        self.var_kargs = inst.var_kargs

    @classmethod
    def make_copy(klass, inst, func=None, argv=None, extra_argv=None, copy_sig=True):
        """Makes a new instance of the partial application wrapper based on
        an existing instance, optionally overriding the original's wrapped
        function and/or saved arguments.

        :param inst: The partial instance we're copying
        :param func: Override the original's wrapped function
        :param argv: Override saved argument values
        :param extra_argv: Override saved extra positional arguments
        :param copy_sig: Copy original's signature?
        :rtype: New partial wrapper instance

        """

        dest            = klass(func or inst.func)
        dest.argv       = (argv or inst.argv).copy()
        dest.extra_argv = list(extra_argv if extra_argv else inst.extra_argv)

        if copy_sig:
            dest.__sig_from_partial(inst)

        return dest

    def __get__(self, inst, owner=None):
        return self.make_copy(self, func=self.func.__get__(inst, owner), copy_sig=False)

    def __new_argv(self, *new_pargs, **new_kargs):
        """Calculate new argv and extra_argv values resulting from adding
        the specified positional and keyword arguments."""

        new_argv = self.argv.copy()
        new_extra_argv = list(self.extra_argv)

        for v in new_pargs:
            arg_name = None
            for name in self.pargl:
                if not name in new_argv:
                    arg_name = name
                    break

            if arg_name:
                new_argv[arg_name] = v
            elif self.var_pargs:
                new_extra_argv.append(v)
            else:
                num_prev_pargs = len([name for name in self.pargl if name in self.argv])
                raise TypeError("%s() takes exactly %d positional arguments (%d given)" \
                                    % (self.__name__,
                                       len(self.pargl),
                                       num_prev_pargs + len(new_pargs)))

        for k,v in new_kargs.items():
            if not (self.var_kargs or (k in self.pargl) or (k in self.kargl)):
                raise TypeError("%s() got an unexpected keyword argument '%s'" \
                                    % (self.__name__, k))
            new_argv[k] = v

        return (new_argv, new_extra_argv)

    def __update_argv(self, *pargs, **kargs):
        self.argv, self.extra_argv = self.__new_argv(*pargs, **kargs)

    def __call__(self, *new_pargs, **new_kargs):
        if self.__call_error:
            raise TypeError(self.__call_error)

        new_argv, extra_argv = self.__new_argv(*new_pargs, **new_kargs)

        applic_argv = self.def_argv.copy()
        applic_argv.update(new_argv)

        applic_ready = True
        for name in self.pargl:
            if not name in applic_argv:
                applic_ready = False
                break

        if applic_ready:
            for name in self.kargl:
                if not name in applic_argv:
                    applic_ready = False
                    break

        if applic_ready:
            fpargs = [new_argv[n] for n in self.pargl if n in new_argv] + extra_argv
            fkargs = dict((n,v) for n,v in new_argv.items() if not n in self.pargl)
            return self.func(*fpargs, **fkargs)
        else:
            return self.make_copy(self, argv=new_argv)

class pointfree(partial):
    """Wraps a regular Python function or method into a callable object
    supporting the ``>>`` and ``*`` function composition operators, as well
    as automatic partial application inherited from
    :py:class:`~pointfree.partial`.

    :param func: Function or method to wrap
    :param pargs: Optional, positional arguments for the wrapped function
    :param kargs: Optional, keyword arguments for the wrapped function

    This class inherits its partial application behavior from
    :py:class:`~pointfree.partial`; refer to its documentation for details.

    On top of automatic partial application, the
    :py:class:`~pointfree.pointfree` wrapper adds two function composition
    operators, ``>>`` and ``*``, for "forward" and "reverse" function
    composition respectively.  For example, given the following wrapped
    functions::

        >>> @pointfree
        ... def pfadd(a, b):
        ...     return a + b

        >>> @pointfree
        ... def pfmul(a, b):
        ...     return a * b

    The following forward composition defines the function ``f()`` as one
    which takes a given number, adds one to it, and then multiplies the
    result of the addition by two::

        >>> f = pfadd(1) >> pfmul(2)
        >>> f(1)
        4

    Reverse composition simply works in the opposite direction.  In this
    example, ``g()`` takes a number, multiplies it by three, and then adds
    four::

        >>> g = pfadd(4) * pfmul(3)
        >>> g(5)
        19

    The alias ``pf`` is provided for :py:class:`~pointfree.pointfree` to
    conserve electrons when wrapping functions inline::

        >>> def add(a, b):
        ...     return a + b

        >>> def mul(a, b):
        ...     return a * b

        >>> f = pf(add, 1) >> pf(mul, 2)
        >>> f(2)
        6

    When using :py:class:`~pointfree.pointfree` as a decorator on class or
    static methods, you must ensure that it is the "topmost" decorator, so
    that the resulting object is a :py:class:`~pointfree.pointfree`
    instance in order for the composition operators to work.

    """

    def __mul__(self, g):
        return self.make_copy(g, func=lambda *p,**k: self(g.func(*p,**k)))

    def __rshift__(self, g):
        return self.make_copy(self, func=lambda *p,**k: g(self.func(*p,**k)))

# Shorthand pointfree notation
pf = pointfree

@pointfree
def pfmap(func, iterable):
    """A pointfree map function: Returns an iterator over the results of
    applying a function of one argument to the items of a given iterable.
    The function is provided "lazily" to the given iterable; each function
    application is performed on the fly as it is requested.

    :param func: A function of one argument to apply to each item
    :param iterable: An iterator yielding input for the function
    :rtype: Iterator of function application results

    Example::

        >>> f = pfmap(lambda x: x+1) \\
        ...     >> pfmap(lambda x: x*2) \\
        ...     >> pfcollect

        >>> f(range(5))
        [2, 4, 6, 8, 10]

    """

    for item in iterable:
        yield func(item)

@pointfree
def pfreduce(func, iterable, initial=None):
    """A pointfree reduce / left fold function: Applies a function of two
    arguments cumulatively to the items supplied by the given iterable, so
    as to reduce the iterable to a single value.  If an initial value is
    supplied, it is placed before the items from the iterable in the
    calculation, and serves as the default when the iterable is empty.

    :param func: A function of two arguments
    :param iterable: An iterable yielding input for the function
    :param initial: An optional initial input for the function
    :rtype: Single value

    Example::

        >>> from operator import add

        >>> sum_of_squares = pfreduce(add, initial=0) * pfmap(lambda n: n**2)
        >>> sum_of_squares([3, 4, 5, 6])
        86

    """

    iterator = iter(iterable)
    try:
        first_item = next(iterator)
        if initial:
            value = func(initial, first_item)
        else:
            value = first_item
    except StopIteration:
        return initial

    for item in iterator:
        value = func(value, item)
    return value

@pointfree
def pffilter(pred, iterable):
    """Pointfree filter function.

    Example::

        >>> f = pffilter(lambda n: n % 2 == 0) \\
        ...     >> pfcollect

        >>> f(range(5))
        [0, 2, 4]

    """

    for item in iterable:
        if pred(item): yield item

@pointfree
def pfcollect(iterable, n=None):
    """Collects and returns a list of values from the given iterable.  If
    the n parameter is not specified, collects all values from the
    iterable.

    :param iterable: An iterable yielding values for the list
    :param n: An optional maximum number of items to collect
    :rtype: List of values from the iterable

    Example::

        >>> @pointfree
        ... def fibonaccis():
        ...     a, b = 0, 1
        ...     while True:
        ...         a, b = b, a+b
        ...         yield a

        >>> (pfcollect(n=10) * fibonaccis)()
        [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

    """

    if n:
        return list(itertools.islice(iterable, n))
    else:
        return list(iterable)

@pointfree
def pfprint(item, end='\n', file=None):
    """Prints an item.

    :param item: The item to print
    :param end: String to append to the end of printed output
    :param file: File to which output is printed
    :rtype: None

    Example::

        >>> from operator import add

        >>> fn = pfreduce(add, initial=0) >> pfprint
        >>> fn([1, 2, 3, 4])
        10

    """

    # Can't just make sys.stdout the file argument's default value, because
    # then we would be capturing the stdout file descriptor, and then
    # doctest -- which works by redefining sys.stdout -- would fail:
    if file is None:
        file = sys.stdout

    print(item, end=end, file=file)

@pointfree
def pfprint_all(iterable, end='\n', file=None):
    """Prints each item from an iterable.

    :param iterable: An iterable yielding values to print
    :param end: String to append to the end of printed output
    :param file: File to which output is printed
    :rtype: None

    Example::

        >>> @pointfree
        ... def prefix_all(prefix, iterable):
        ...     for item in iterable:
        ...         yield "%s%s" % (prefix, item)

        >>> fn = prefix_all("An item: ") >> pfprint_all

        >>> fn(["foo", "bar", "baz"])
        An item: foo
        An item: bar
        An item: baz

    """

    for item in iterable:
        pfprint(item, end=end, file=file)

@pointfree
def pfignore_all(iterator):
    """Consumes all the items from an iterable, discarding their output.
    This may be useful if evaluating the iterable produces some desirable
    side-effect, but you have no need to collect its output.

    :param iterable: An iterable
    :rtype: None

    Example::

        >>> result = []

        >>> @pointfree
        ... def append_all(collector, iterable):
        ...     for item in iterable:
        ...         collector.append(item)
        ...         yield item

        >>> @pointfree
        ... def square_all(iterable):
        ...     for item in iterable:
        ...         yield item**2

        >>> fn = square_all \\
        ...      >> append_all(result) \\
        ...      >> pfignore_all
        >>> fn([1, 2, 3, 4])
        >>> result
        [1, 4, 9, 16]

    """

    for item in iterator:
        pass


# -*- coding: utf-8 -*-
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

"""Main module."""

import fnmatch
import functools
import hashlib
import os.path
import re
import sys
from zipfile import ZipFile, ZIP_DEFLATED
from base64 import b64encode

directory_re = re.compile(r"[\\/]$")
ZIPFILE_WRITE_EXCLUSIVE_MODE = 'x'
ZIPFILE_WRITE_MODE = 'w'
if sys.version_info >= (3, 5):
    ZIPFILE_WRITE_MODE = ZIPFILE_WRITE_EXCLUSIVE_MODE

def ignore_certain_metainf_files(filename):
    """
    We do not support multiple signatures in XPI signing because the client
    side code makes some pretty reasonable assumptions about a single signature
    on any given JAR.  This function returns True if the file name given is one
    that we dispose of to prevent multiple signatures.
    """
    ignore = ("META-INF/manifest.mf",
              "META-INF/*.sf",
              "META-INF/*.rsa",
              "META-INF/*.dsa",
              "META-INF/ids.json")

    for glob in ignore:
        # Explicitly match against all upper case to prevent the kind of
        # runtime errors that lead to https://bugzil.la/1169574
        if fnmatch.fnmatchcase(filename.upper(), glob.upper()):
            return True
    return False

def file_key(filename):
    '''Sort keys for xpi files

    The filenames in a manifest are ordered so that files not in a
    directory come before files in any directory, ordered
    alphabetically but ignoring case, with a few exceptions
    (install.rdf, chrome.manifest, icon.png and icon64.png come at the
    beginning; licenses come at the end).

    This order does not appear to affect anything in any way, but it
    looks nicer.
    '''
    prio = 4
    if filename == 'install.rdf':
        prio = 1
    elif filename in ["chrome.manifest", "icon.png", "icon64.png"]:
        prio = 2
    elif filename in ["MPL", "GPL", "LGPL", "COPYING",
                      "LICENSE", "license.txt"]:
        prio = 5
    return (prio, os.path.split(filename.lower()))

def zinfo_key(zinfo):
    """
    Same as file_key, but for a ZipInfo entry.
    """
    return file_key(zinfo.filename)

def _digest(data):
    md5 = hashlib.md5()
    md5.update(data)
    sha1 = hashlib.sha1()
    sha1.update(data)
    sha256 = hashlib.sha256()
    sha256.update(data)
    return {
        'md5': md5.digest(),
        'sha1': sha1.digest(),
        'sha256': sha256.digest(),
    }

class Section(object):
    __slots__ = ('name', 'digests')

    def __init__(self, name, digests={}):
        self.name = name
        self.digests = digests

    def __str__(self):
        # Important thing to note: placement of newlines in these strings is
        # sensitive and should not be changed without reading through
        # http://docs.oracle.com/javase/7/docs/technotes/guides/jar/jar.html#JAR%20Manifest
        # thoroughly.
        entry = ''
        # The spec for zip files only supports extended ASCII and UTF-8
        # See http://www.pkware.com/documents/casestudies/APPNOTE.TXT
        # and search for "language encoding" for details
        #
        # See https://bugzilla.mozilla.org/show_bug.cgi?id=1013347
        name = 'Name: {}'.format(self.name)

        # See https://bugzilla.mozilla.org/show_bug.cgi?id=841569#c35
        while name:
            entry += name[:72]
            name = name[72:]
            if name:
                entry += '\n '
        entry += '\n'
        order = list(self.digests.keys())
        order.sort()
        entry += 'Digest-Algorithms: {}\n'.format(
            ' '.join([algo.upper() for algo in order]))
        for algo in order:
            entry += '{}-Digest: {}\n'.format(
                algo.upper(), b64encode(self.digests[algo]).decode('utf-8'))
        return entry

def manifest_header(type_name, version='1.0'):
    """Returns a header, suitable for use in a manifest.

    >>> manifest_header("signature")
    "Signature-Version: 1.0"

    :param type_name: The kind of manifest which needs a header:
        "manifest", "signature".
    :param version: The manifest version to encode in the header
        (default: '1.0')
    """
    return "{}-Version: {}".format(
        type_name.title(),
        version,
    )

class Manifest(object):
    # Older versions of Firefox crash if a JAR manifest style file doesn't
    # end in a blank line("\n\n").  For more details see:
    # https://bugzilla.mozilla.org/show_bug.cgi?id=1158467

    def __init__(self, sections, **kwargs):
        super(Manifest, self).__init__()
        self.sections = sections
        for k, v in kwargs.items():
            setattr(self, k, v)

    @property
    def body(self):
        return "\n".join([str(i) for i in self.sections])

    def __str__(self):
        segments = [manifest_header('Manifest'),
                    "",
                    self.body,
                    ""]
        return "\n".join(segments)

class Signature(object):
    def __init__(self, digest_manifests):
        self.digest_manifests = digest_manifests

    @property
    def digest_manifest(self):
        return [
            "{}-Digest-Manifest: {}".format(
                item[0].upper(),
                b64encode(item[1]).decode('utf-8')
            )
            for item in sorted(self.digest_manifests.items())
        ]

    @property
    def header(self):
        segments = [manifest_header('Signature')]
        segments.extend(self.digest_manifest)
        segments.append('')
        return "\n".join(segments)

    def __str__(self):
        return self.header + "\n"

class XPIFile(object):
    """An XPI file read from disk.

    This class represents an XPI file and its contents. It can be used
    to generate manifests such as would be found in a META-INF
    directory. These manifests can be signed, and this signature can
    be used to produce a signed XPI file.
    """

    def __init__(self, path, ids=None):
        """
        """
        self.inpath = path
        self._digests = []
        self.ids = ids

        def mksection(data, fname):
            digests = _digest(data)
            item = Section(fname, digests=digests)
            self._digests.append(item)

        with ZipFile(self.inpath, 'r') as zin:
            for f in sorted(zin.filelist, key=zinfo_key):
                # Skip directories and specific files found in META-INF/ that
                # are not permitted in the manifest
                if (directory_re.search(f.filename)
                        or ignore_certain_metainf_files(f.filename)):
                    continue
                mksection(zin.read(f.filename), f.filename)
            if ids:
                mksection(ids, 'META-INF/ids.json')

    @property
    @functools.lru_cache(maxsize=None)
    def manifest(self):
        return Manifest(self._digests)

    @property
    @functools.lru_cache(maxsize=None)
    def signatures(self):
        # The META-INF/*.sf files should contain hashes of the individual
        # sections of the the META-INF/manifest.mf file.  So we generate those
        # signatures here
        digest_manifest = _digest(str(self.manifest).encode('utf-8'))
        return Signature(digest_manifests=digest_manifest)

    @property
    def signature(self):
        # Returns only the x-Digest-Manifest signature and omits the individual
        # section signatures
        return self.signatures.header + "\n"

    def make_signed(self, outpath, sigpath, signed_manifest, signature):
        if not outpath:
            raise IOError("No output file specified")

        # FIXME: Take this out once we stop supporting 3.4 and less
        if os.path.exists(outpath):
            raise FileExistsError("File already exists: {}".format(outpath))

        # Normalize to a simple filename with no extension or prefixed
        # directory
        sigpath = os.path.splitext(os.path.basename(sigpath))[0]
        sigpath = os.path.join('META-INF', sigpath)

        with ZipFile(self.inpath, 'r') as zin:
            with ZipFile(outpath, ZIPFILE_WRITE_MODE, ZIP_DEFLATED) as zout:
                # The PKCS7 file("foo.rsa") *MUST* be the first file in the
                # archive to take advantage of Firefox's optimized downloading
                # of XPIs
                zout.writestr("{}.rsa".format(sigpath), signature)
                for f in zin.infolist():
                    # Make sure we exclude any of our signature and manifest
                    # files
                    if ignore_certain_metainf_files(f.filename):
                        continue
                    zout.writestr(f, zin.read(f.filename))
                zout.writestr("META-INF/manifest.mf", str(self.manifest))
                zout.writestr("{}.sf".format(sigpath), signed_manifest)
                if self.ids is not None:
                    zout.writestr('META-INF/ids.json', self.ids)


#!/usr/bin/env python
# coding: utf-8

"""
adjutant is a Python library for parsing StarCraft II replays.
"""

import argparse
import datetime
import os
import re
import StringIO
import struct
from collections import namedtuple, OrderedDict
from itertools import groupby

import mpyq

__author__ = "Aku Kotkavuo"
__version__ = "0.1.2"

COLORS = {
    'B4141E': 'Red',
    '0042FF': 'Blue',
    '1CA7EA': 'Teal',
    'EBE129': 'Yellow',
    '540081': 'Purple',
    'FE8A0E': 'Orange',
    '168000': 'Green',
    'CCA6FC': 'Light pink',
    '1F01C9': 'Violet',
    '525494': 'Light grey',
    '106246': 'Dark green',
    '4E2A04': 'Brown',
    '96FF91': 'Light green',
    '232323': 'Dark grey',
    'E55BB0': 'Pink'
}

KOREAN_MAP_NAMES = {
    u'아그리아 골짜기': "Agria Valley",
    u'폭염 사막': "Blistering Sands",
    # u'': "Burial Grounds",
    u'십자포화': "Crossfire",
    u'사막 오아시스': "Desert Oasis",
    u'소각 지대': "Incineration Zone",
    u'밀림 분지': "Jungle Basin",
    u'고물 처리장': "Junk Yard",
    u'폭염 사막 초보자용': "Novice Blistering Sands",
    u'사막 오아시스 초보자용': "Novice Desert Oasis",
    u'전쟁 초원 초보자용': "Novice Steppes of War",
    u'고철 처리장': "Scrap Station",
    u'전쟁 초원': "Steppes of War",
    # u'': "Worldship",
    u'젤나가 동굴': "Xel'Naga Caverns",
    u'엘리시움': "Elysium",
    u'메마른 황무지': "Arid Wastes",
    # u'': "Debris Field",
    u'델타 사분면': "Delta Quadrant",
    u'불협화음 IV': "Discord IV",
    u'고궤도': "High Orbit",
    u'쿨라스 협곡': "Kulas Ravine",
    u'잃어버린 사원': "Lost Temple",
    u'금속도시': "Metalopolis",
    u'몬리스 마루': "Monlyth Ridge",
    # u'': "New Antioch",
    # u'': "Nightmare",
    u'불협화음 IV 초보자용': "Novice Discord IV",
    u'쿨라스 협곡 초보자용': "Novice Kulas Ravine",
    u'금속도시 초보자용': "Novice Metalopolis",
    u'몬리스 마루 초보자용': "Novice Monlyth Ridge",
    # u'': "Novice Terminus",
    u'황혼 요새 초보자용': "Novice Twilight Fortress",
    # u'': "Red Stone Gulch",
    u'잿더미 안식처': "Scorched Haven",
    u'샤쿠라스 고원': "Shakuras Plateau",
    u'타소니스 공습': "Tarsonis Assault",
    # u'': "Terminus",
    u'황혼 요새': "Twilight Fortress",
    u'전쟁 지역': "War Zone",
    u'아라칸 요새 ': "Arakan Citadel",
    # u'': "Burning Tide",
    u'426 거주지': "Colony 426",
    u'발굴지': "Dig Site",
    # u'': "Dirt Side",
    u'변경 지대': "Frontier",
    u'계절풍 지대': "Monsoon",
    u'모래늪': "Quicksand",
    # u'': "Tectonic Rift",
    u'생체 실험실': "The Bio Lab",
    u'티폰': "Typhon",
    u'울란의 심연': "Ulaan Deeps",
    u'심연': "Abyss",
    u'절멸': "Extinction",
    # u'': "Forbidden Planet",
    u'고지대': "High Ground",
    u'용암 흐름': "Lava Flow",
    u'메가톤': "Megaton",
    u'전초 기지': "Outpost",
    u'모래 협곡': "Sand Canyon",
    u'폭풍우 전장': "Tempest",
    u'독지대': "Toxic Slums",
    # u'': "Zenith"
}

def vlq2int(data):
    """Read one VLQ-encoded integer value from an input data stream."""
    # The VLQ is little-endian.
    byte = ord(data.read(1))
    value = byte & 0x7F
    shift = 1
    while byte & 0x80 != 0:
        byte = ord(data.read(1))
        value = ((byte & 0x7F) << shift * 7) | value
        shift += 1
    return value

def read_table(data, fields):
    """Read a table structure.

    These are used by Blizzard to collect pieces of data together. Each
    value is prefixed by two bytes, first denoting (doubled) index and the
    second denoting some sort of key -- so far it has always been '09'. The
    actual value follows as a Variable-Length Quantity, also known as uintvar.
    The actual value is also doubled.

    In some tables the keys might jump from 0A 09 to 04 09 for example.
    I have no idea why this happens, as the next logical key is 0C. Perhaps
    it's a table in a table? Some sort of headers might exist for these
    tables, I'd imagine at least denoting length. Further research required.
    """
    def read_field(field_name):
        data.read(2)
        table[field_name] = vlq2int(data) / 2
        # Discard unknown fields.
        if field_name == 'unknown':
            del table[field_name]

    table = {}
    for field in fields:
        read_field(field)
    return table

class SC2Replay(object):

    def __init__(self, archive):
        if isinstance(archive, mpyq.MPQArchive):
            self.archive = archive
        else:
            self.archive = mpyq.MPQArchive(archive)
        self._header = self._parse_header()
        self.version = self._header['version']
        self.duration = self.get_duration(self._header['duration'])
        self.players, self.map = self._parse_details()
        self.teams = dict((team, [p for p in players]) for (team, players) in
                          groupby(self.players, lambda x: x['team']))

    def _parse_header(self):
        """Parse the user data header portion of the replay."""
        header = OrderedDict()
        user_data_header = self.archive.header['user_data_header']['content']
        if re.search(r'StarCraft II replay', user_data_header):
            user_data_header = StringIO.StringIO(user_data_header)
            user_data_header.seek(30) # Just skip the beginning.
            header.update(read_table(user_data_header, ['release_flag',
                                                        'major_version',
                                                        'minor_version',
                                                        'maintenance_version',
                                                        'build_number',
                                                        'unknown',
                                                        'unknown',
                                                        'duration']))

            # Some post processing is required.
            header['version'] = '%s.%s.%s.%s' % (header['major_version'],
                                                 header['minor_version'],
                                                 header['maintenance_version'],
                                                 header['build_number'])
            if not header['release_flag']:
                header['version'] += ' (dev)'

            # Duration is actually stored as 1/16th of a seconds. Go figure.
            header['duration'] /= 16
        else:
            raise ValueError("The given file is not a StarCraft II replay.")
        return header

    def _parse_details(self):

        def read_player_struct():
            player = {}
            details.read(4)
            name_len = ord(details.read(1)) // 2
            player['name'] = details.read(name_len)
            details.read(5) # 02 05 08 00 09
            details.read(4) # 00/04 02 07 00
            details.read(3) # 00 00 00 // 00 53 32 (S2)
            read_table(details, ['unknown', 'unknown'])
            details.read(2) # 04 02
            race_len = ord(details.read(1)) // 2
            player['race'] = details.read(race_len)
            details.read(3) # 06 05 08
            values = {}
            values.update(read_table(details, ['alpha',
                                               'r',
                                               'g',
                                               'b',
                                               'unknown',
                                               'unknown',
                                               'unknown',
                                               'unknown',
                                               'team']))
            player['team'] = values['team']
            try:
                player['color'] = COLORS["%(r)02X%(g)02X%(b)02X" % values]
            except KeyError:
                player['color'] = "%(r)02X%(g)02X%(b)02X" % values
            return player

        details = StringIO.StringIO(self.archive.read_file('replay.details'))
        result = {}
        details.read(6) # 05 1C 00 04 01 00
        number_of_players = ord(details.read(1)) // 2
        result['players'] = []
        for player in range(number_of_players):
            result['players'].append(read_player_struct())
        details.read(2) # 02 02
        # Sort players by team by default.
        result['players'] = sorted(result['players'], key=lambda x: x['team'])
        map_name_len = ord(details.read(1)) // 2
        result['map'] = details.read(map_name_len).decode('utf-8')
        if result['map'] in KOREAN_MAP_NAMES:
            result['map'] += ' (%s)' % KOREAN_MAP_NAMES[result['map']]
        return result['players'], result['map']

    def get_duration(self, seconds):
        """Transform duration into a human-readable form."""
        duration = ""
        minutes, seconds = divmod(seconds, 60)
        if minutes >= 60:
            hours, minutes = divmod(minutes, 60)
            duration = "%sh " % hours
        duration += "%sm %ss" % (minutes, seconds)
        return duration

    def print_details(self):
        """Print a summary of the game details."""
        print 'Map      ', self.map
        print 'Duration ', self.duration
        print 'Version  ', self.version
        print 'Team  Player       Race       Color'
        print '-----------------------------------'
        for player in self.players:
            print '{team:<5} {name:12} {race:10} {color}'.format(**player)

def main():
    description = "adjutant parses and analyzes StarCraft II replays."
    parser = argparse.ArgumentParser(description=description)
    parser.add_argument("file", action="store", help="path to the archive")
    parser.add_argument("-r", "--rename", action="store_true", dest="rename",
                        help="rename replay")
    args = parser.parse_args()
    if args.file:
        replay = SC2Replay(args.file)
        if args.rename:
            races = 'v'.join(''.join(player['race'][0] for player in players)
                             for players in replay.teams.itervalues())
            names = '.vs.'.join('-'.join(player['name'] for player in players)
                             for players in replay.teams.itervalues())
            map_name = replay.map.replace(' ', '_')
            time = datetime.datetime.fromtimestamp(os.path.getctime(args.file))
            time = time.strftime('%Y-%m-%d-%H%M')
            replay_name = "%s.%s.on.%s.%s.SC2Replay" % (races,
                                                        names,
                                                        map_name,
                                                        time)
            print replay_name
            os.rename(args.file, replay_name)
        else:
            replay.print_details()

if __name__ == '__main__':
    main()


#!/usr/bin/env python

import PyQt4
from PyQt4 import QtGui
import subprocess
from time import sleep
from timeit import Timer
from gui_About import *
import PyQt4.QtCore
import sys
import os
import multiprocessing
import re
import webbrowser
from PyQt4.QtGui import QContextMenuEvent, QMessageBox, QApplication, QWidget, QVBoxLayout
import subprocess
import signal
from threading import Thread

from PyQt4 import QtCore
import time

from gui_batch import BatchFile
from gui_Layout import *
import gui_matplotlibwidgetFile
from gui_log import *
from PyQt4.QtWebKit import *

class FormEvents():
    """
    This class creates answers of buttons of the user interface and checks values that the user typed.
    """

    def __init__(self):

        app = QtGui.QApplication(sys.argv)
        self.main_window = QtGui.QMainWindow()
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self.main_window)

        self.file_dialog = QtGui.QFileDialog()
        self.main_window.setFixedSize(1372, 890)
        self.graphic_widget = gui_matplotlibwidgetFile.matplotlibWidget()
        self.mpl_canvas = gui_matplotlibwidgetFile.MplCanvas()
        # self.ui.actionSave.setIcon(QtGui.QIcon('/home/marrabld/Projects/planarradpy/gui/icons/i_document-save.png'))
        # self.ui.toolBar.setToolButtonStyle(QtCore.Qt.ToolButtonIconOnly)

        # ==================================================#
        # The about window
        # ==================================================#
        self.aboutWindow = QtGui.QDialog()
        self.uiAbout = Ui_win_about()
        self.uiAbout.setupUi(self.aboutWindow)

        # ==================================================#
        # The log window
        # ==================================================#
        self.log_window = QtGui.QDialog()
        self.uiLog = Ui_win_log_reader()
        self.uiLog.setupUi(self.log_window)

        self.without_error = True
        self.slider_value = 0

        self.num_line = 0
        self.data_wanted = []
        self.wavelength = []
        self.information = []

        self.authorized_display = False
        self.ui.show_all_curves.setCheckState(2)
        self.ui.show_grid.setCheckState(2)
        self.p = None  # Variable for subprocess to run PlanarRad.
        # self.result_path = '../outputs'
        # self.result_file = os.path.join(self.result_path, "batch_report.txt")
        self.result_file = ''
        self.is_running = False
        self.table_widget = QtGui.QTableWidget()

        self.t = GuiThread(self, 100)  # Thread for the progress bar.
        self.n = 0

        # ----------------------------------------------------------------------------------------------#
        # The following permits to know how many CPU there is in the computer to list them in a comboBox.
        # ----------------------------------------------------------------------------------------------#
        self.cpu = multiprocessing.cpu_count()
        self.nb_cpu = self.ui.nb_cpu.addItem("-1")
        count_cpu = 1  # Iterator on the number of cpu.
        while count_cpu <= self.cpu:
            self.nb_cpu = self.ui.nb_cpu.addItem(str(count_cpu))
            count_cpu += 1

        self.prerequisite_actions()  # Initialise the GUI.

        # -------------------------------------------#
        # The following connects buttons to an action.
        # -------------------------------------------#

        self.ui.phyto_button.connect(self.ui.phyto_button, PyQt4.QtCore.SIGNAL('clicked()'),
                                     self.search_file_phytoplankton)
        self.ui.bottom_button.connect(self.ui.bottom_button, PyQt4.QtCore.SIGNAL('clicked()'), self.search_file_bottom)
        self.ui.exec_path_button.connect(self.ui.exec_path_button, PyQt4.QtCore.SIGNAL('clicked()'),
                                         self.search_directory_executive_path)

        self.ui.open_result_file_button.connect(self.ui.open_result_file_button, PyQt4.QtCore.SIGNAL('clicked()'),
                                                self.search_file_result)
        self.ui.sens.connect(self.ui.sens, QtCore.SIGNAL('valueChanged(int)'), self.display_the_graphic_connection)
        self.ui.show_all_curves.connect(self.ui.show_all_curves, QtCore.SIGNAL('stateChanged(int)'),
                                        self.display_the_graphic_connection)
        self.ui.show_grid.connect(self.ui.show_grid, QtCore.SIGNAL('stateChanged(int)'),
                                  self.display_the_graphic_connection)

        self.ui.actionSave.connect(self.ui.actionSave, QtCore.SIGNAL('triggered()'), self.save_figure)
        self.ui.actionSave_as.connect(self.ui.actionSave_as, QtCore.SIGNAL('triggered()'), self.save_figure_as)
        self.ui.actionThis_GUI.connect(self.ui.actionThis_GUI, QtCore.SIGNAL('triggered()'), self.open_about)
        self.ui.actionLog_file.connect(self.ui.actionLog_file, QtCore.SIGNAL('triggered()'), self.open_log_file)
        self.ui.actionDocumentation.connect(self.ui.actionDocumentation, QtCore.SIGNAL('triggered()'),
                                            self.open_documentation)

        self.ui.actionRun.connect(self.ui.actionRun, PyQt4.QtCore.SIGNAL('triggered()'), self.run)
        self.ui.actionOpen.connect(self.ui.actionOpen, PyQt4.QtCore.SIGNAL('triggered()'), self.search_file_result)
        self.ui.actionQuit.connect(self.ui.actionQuit, PyQt4.QtCore.SIGNAL('triggered()'), self.quit)

        self.ui.run.connect(self.ui.run, PyQt4.QtCore.SIGNAL('clicked()'), self.run)
        self.ui.cancel.connect(self.ui.cancel, PyQt4.QtCore.SIGNAL('clicked()'), self.cancel_planarrad)
        self.ui.quit.connect(self.ui.quit, PyQt4.QtCore.SIGNAL('clicked()'), self.quit)

        self.ui.graphic_widget.canvas.mpl_connect('button_release_event', self.click)
        self.ui.graphic_widget.canvas.mpl_connect('motion_notify_event', self.mouse_move)

        QtCore.QObject.connect(self.t, QtCore.SIGNAL("total(PyQt_PyObject)"), self.total)
        QtCore.QObject.connect(self.t, QtCore.SIGNAL("update()"), self.update)

        self.main_window.show()
        sys.exit(app.exec_())

    def data(self):
        """
        This function gets back data that the user typed.
        """
        self.batch_name_value = self.ui.batch_name_value.text()
        self.saa_values = self.ui.saa_values.text()
        self.sza_values = self.ui.sza_values.text()
        self.p_values = self.ui.p_values.text()
        self.x_value = self.ui.x_value.text()
        self.y_value = self.ui.y_value.text()
        self.g_value = self.ui.g_value.text()
        self.s_value = self.ui.s_value.text()
        self.z_value = self.ui.z_value.text()
        self.wavelength_values = self.ui.wavelength_values.text()
        self.verbose_value = self.ui.verbose_value.text()
        self.phytoplankton_path = self.ui.phyto_path.text()
        self.bottom_path = self.ui.bottom_path.text()
        self.executive_path = self.ui.exec_path.text()
        self.nb_cpu = self.ui.nb_cpu.currentText()
        self.report_parameter_value = str(self.ui.report_parameter_value.text())

    # -------------------------------------------------------------------------------#
    # These following functions display a fileDialog to search a file or a directory.
    # -------------------------------------------------------------------------------#

    def search_directory_executive_path(self):
        self.executive_file = self.file_dialog.getExistingDirectory(caption=str("Planarrad Directory"),
                                                                    directory="..")
        if not self.executive_file == '':
            self.ui.exec_path.setText(self.executive_file)

    def search_file_phytoplankton(self):
        self.phytoplankton_file = self.file_dialog.getOpenFileName(caption=str("Phytoplankton Absorption File"),
                                                                   directory="./inputs/iop_files")
        if not self.phytoplankton_file == '':
            self.ui.phyto_path.setText(self.phytoplankton_file)

    def search_file_bottom(self):
        self.bottom_file = self.file_dialog.getOpenFileName(caption=str("Bottom Reflectance File"),
                                                            directory="./inputs/bottom_files")
        if not self.bottom_file == '':
            self.ui.bottom_path.setText(self.bottom_file)

    def search_file_result(self):
        """
        This function once the file found, display data's file and the graphic associated.
        """
        if self.ui.tabWidget.currentIndex() == TabWidget.NORMAL_MODE:
            self.result_file = self.file_dialog.getOpenFileName(caption=str("Open Report File"), directory="./outputs")
            if not self.result_file == '':
                self.ui.show_all_curves.setDisabled(False)
                self.ui.show_grid.setDisabled(False)
                self.data_processing()
                self.display_the_graphic(self.num_line, self.wavelength, self.data_wanted, self.information)
                self.authorized_display = True

    # ------------------------------------------------------------------------------#

    def check_values(self):
        """
        This function checks if there is no problem about values given.
        If there is a problem with a or some values, their label's color is changed to red,
        and call a function to display an error message.
        If there is no problem, their label, if it is necessary, is changed to grey (default color).
        """

        error_color = 'color: red'
        no_error_color = 'color: 0.75'  # light gray
        self.error_batch_name = False
        self.error_report_parameter = False
        self.error_saa_result = False
        self.error_sza_result = False
        self.error_p_result = False
        self.error_wavelength_result = False
        self.error_x_result = False
        self.error_y_result = False
        self.error_g_result = False
        self.error_s_result = False
        self.error_z_result = False
        self.error_verbose_result = False
        self.error_phytoplankton_path_result = False
        self.error_bottom_path_result = False
        self.error_executive_path_result = False

        """
        !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
        No particular checking for paths!
        !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
        """

        if self.ui.tabWidget.currentIndex() == TabWidget.NORMAL_MODE:

            check_num = '(.*)'  # Regular expression to use
            self.prog = re.compile(check_num)  # Analysis object creation
            batch_name_result = self.prog.search(
                self.batch_name_value)  # String retrieval thanks to the regular expression
            report_parameter_result = self.prog.search(self.report_parameter_value)
            try:
                if (self.ui.batch_name_value.text().isEmpty()) | (
                            batch_name_result.group() != self.ui.batch_name_value.text()):
                    self.ui.batch_name_label.setStyleSheet(error_color)
                    self.error_batch_name = True
                else:
                    self.ui.batch_name_label.setStyleSheet(no_error_color)
                    self.error_batch_name = False
            except AttributeError:
                self.ui.batch_name_label.setStyleSheet(error_color)
                self.error_batch_name = True
            try:
                if (self.ui.report_parameter_value.text().isEmpty()) | (
                            report_parameter_result.group() != self.ui.report_parameter_value.text()):
                    self.ui.report_parameter_label.setStyleSheet(error_color)
                    self.error_report_parameter = True
                else:
                    self.ui.report_parameter_label.setStyleSheet(no_error_color)
                    self.error_report_parameter = False
            except AttributeError:
                self.ui.report_parameter_label.setStyleSheet(error_color)
                self.error_report_parameter = True

            # -----------------------------------------------------------#
            # The following checks values separate by comas without space.
            # -----------------------------------------------------------#

            """
            Problem : The user can write just one letter or starts with a dot or finishes the list with a dot.
            """

            # check_num_1 = '(^([0-9]+[.]?[0-9]*[,]?){0,}[^,])|(^([0-9]*[,]){1,}[^,])'  # Regular expression to use
            # self.prog_1 = re.compile(check_num_1)  # Analysis object creation
            # self.wavelength_values = str(self.wavelength_values).translate(None, ' ').strip(' ')
            # # print(self.wavelength_values)
            # p_result = self.prog_1.search(self.p_values)  # String retrieval thanks to the regular expression
            # saa_result = self.prog_1.search(self.saa_values)
            # sza_result = self.prog_1.search(self.sza_values)
            # wavelength_result = self.prog_1.search(self.wavelength_values)
            # # print(wavelength_result.group())
            #
            # try:
            #
            #     if saa_result.group() != self.ui.saa_values.text():
            #         self.ui.saa_label.setStyleSheet(error_color)
            #         self.error_sza_result = True
            #     else:
            #         self.ui.saa_label.setStyleSheet(no_error_color)
            #         self.error_sza_result = False
            # except AttributeError:
            #     self.ui.saa_label.setStyleSheet(error_color)
            #     self.error_sza_result = True
            # try:
            #     if sza_result.group() != self.ui.sza_values.text():
            #         self.ui.sza_label.setStyleSheet(error_color)
            #         self.error_saa_result = True
            #     else:
            #         self.ui.sza_label.setStyleSheet(no_error_color)
            #         self.error_saa_result = False
            # except AttributeError:
            #     self.ui.sza_label.setStyleSheet(error_color)
            #     self.error_saa_result = True
            # try:
            #     if p_result.group() != self.ui.p_values.text():
            #         self.ui.p_label.setStyleSheet(error_color)
            #         self.error_p_result = True
            #     else:
            #         self.ui.p_label.setStyleSheet(no_error_color)
            #         self.error_p_result = False
            # except AttributeError:
            #     self.ui.p_label.setStyleSheet(error_color)
            #     self.error_p_result = True
            # try:
            #     if wavelength_result.group() != str(self.ui.wavelength_values.text()).translate(None, ' ').strip(' '):
            #         self.ui.waveL_label.setStyleSheet(error_color)
            #         self.error_wavelength_result = True
            #     else:
            #         self.ui.waveL_label.setStyleSheet(no_error_color)
            #         self.error_wavelength_result = False
            # except AttributeError:
            #     self.ui.waveL_label.setStyleSheet(error_color)
            #     self.error_wavelength_result = True

            # ---------------------------------------------------#
            # The following checks values containing only numbers.
            # ---------------------------------------------------#
            check_num_2 = '(^([0-9]+[.]?[0-9]*[,]?){0,}[^,])|(^([0-9]+[,]){1,}[^,])'
            self.prog_2 = re.compile(check_num_2)
            x_result = self.prog_2.search(self.x_value)
            y_result = self.prog_2.search(self.y_value)
            g_result = self.prog_2.search(self.g_value)
            s_result = self.prog_2.search(self.s_value)
            z_result = self.prog_2.search(self.z_value)

            try:
                if x_result.group() != self.ui.x_value.text():
                    self.ui.particles_label.setStyleSheet(error_color)
                    self.ui.x_label.setStyleSheet(error_color)
                    self.error_x_result = True
                else:
                    self.ui.particles_label.setStyleSheet(no_error_color)
                    self.ui.x_label.setStyleSheet(no_error_color)
                    self.error_x_result = False
            except AttributeError:
                self.ui.particles_label.setStyleSheet(no_error_color)
                self.ui.x_label.setStyleSheet(error_color)
                self.error_x_result = True
            try:
                if y_result.group() != self.ui.y_value.text():
                    self.ui.particles_label.setStyleSheet(error_color)
                    self.ui.y_label.setStyleSheet(error_color)
                    self.error_y_result = True
                else:
                    self.ui.particles_label.setStyleSheet(no_error_color)
                    self.ui.y_label.setStyleSheet(no_error_color)
                    self.error_y_result = False
            except AttributeError:
                self.ui.particles_label.setStyleSheet(error_color)
                self.ui.y_label.setStyleSheet(error_color)
                self.error_y_result = True
            try:
                if g_result.group() != self.ui.g_value.text():
                    self.ui.organic_label.setStyleSheet(error_color)
                    self.ui.g_label.setStyleSheet(error_color)
                    self.error_g_result = True
                else:
                    self.ui.organic_label.setStyleSheet(no_error_color)
                    self.ui.g_label.setStyleSheet(no_error_color)
                    self.error_g_result = False
            except AttributeError:
                self.ui.organic_label.setStyleSheet(error_color)
                self.ui.g_label.setStyleSheet(error_color)
                self.error_g_result = True
            try:
                if s_result.group() != self.ui.s_value.text():
                    self.ui.organic_label.setStyleSheet(error_color)
                    self.ui.s_label.setStyleSheet(error_color)
                    self.error_s_result = True
                else:
                    self.ui.organic_label.setStyleSheet(no_error_color)
                    self.ui.s_label.setStyleSheet(no_error_color)
                    self.error_x_result = False
            except AttributeError:
                self.ui.organic_label.setStyleSheet(error_color)
                self.ui.s_label.setStyleSheet(error_color)
                self.error_x_result = True
            try:
                if z_result.group() != self.ui.z_value.text():
                    self.ui.z_label.setStyleSheet(error_color)
                    self.error_z_result = True
                else:
                    self.ui.z_label.setStyleSheet(no_error_color)
                    self.error_z_result = False
            except AttributeError:
                self.ui.z_label.setStyleSheet(error_color)
                self.error_z_result = True

            check_num_3 = '[1-6]+'
            self.prog_3 = re.compile(check_num_3)
            verbose_result = self.prog_3.search(self.verbose_value)

            try:
                if verbose_result.group() != self.ui.verbose_value.text():
                    self.ui.verbose_label.setStyleSheet(error_color)
                    self.error_verbose_result = True
                else:
                    self.ui.verbose_label.setStyleSheet(no_error_color)
                    self.error_verbose_result = False
            except AttributeError:
                self.ui.verbose_label.setStyleSheet(error_color)
                self.error_verbose_result = True

            # ------------------------------------------------#
            # The following checks values containing only path.
            # ------------------------------------------------#

            """
            #!!!!!!!!!!!!!!!!!!!!!!!!!!
            #Syntax test doesn't work ! ->  #check_num4 = '[/]([A-Za-z]+[/]?)+[A-Za-z]$'
            #!!!!!!!!!!!!!!!!!!!!!!!!!!
            """

            check_num_4 = '(.*)'  # Take all strings possible.
            self.prog_4 = re.compile(check_num_4)
            phytoplankton_path_result = self.prog_4.search(self.phytoplankton_path)
            bottom_path_result = self.prog_4.search(self.bottom_path)
            executive_path_result = self.prog_4.search(self.executive_path)

            try:
                if phytoplankton_path_result.group() != self.ui.phyto_path.text():
                    self.ui.phyto_label.setStyleSheet(error_color)
                    self.error_phytoplankton_path_result = True
                else:
                    self.ui.phyto_label.setStyleSheet(no_error_color)
                    self.error_phytoplankton_path_result = False
            except AttributeError:
                self.ui.phyto_label.setStyleSheet(error_color)
                self.error_phytoplankton_path_result = True
            try:
                if bottom_path_result.group() != self.ui.bottom_path.text():
                    self.ui.bottom_label.setStyleSheet(error_color)
                    self.error_bottom_path_result = True
                else:
                    self.ui.bottom_label.setStyleSheet(no_error_color)
                    self.error_bottom_path_result = False
            except AttributeError:
                self.ui.bottom_label.setStyleSheet(error_color)
                self.error_bottom_path_result = True
            try:
                if executive_path_result.group() != self.ui.exec_path.text():
                    self.ui.execPath_label.setStyleSheet(error_color)
                    self.error_executive_path_result = True
                else:
                    self.ui.execPath_label.setStyleSheet(no_error_color)
                    self.error_executive_path_result = False
            except AttributeError:
                self.ui.execPath_label.setStyleSheet(error_color)
                self.error_executive_path_result = True

        if (self.error_batch_name == True) | (self.error_report_parameter == True) | (
                    self.error_saa_result == True) | (self.error_sza_result == True) | (
                    self.error_p_result == True) | (self.error_wavelength_result == True) | (
                    self.error_x_result == True) | (self.error_y_result == True) | (
                    self.error_g_result == True) | (self.error_s_result == True) | (
                    self.error_z_result == True) | (self.error_verbose_result == True) | (
                    self.error_phytoplankton_path_result == True) | (self.error_bottom_path_result == True) | (
                    self.error_executive_path_result == True):
            self.without_error = False
        else:
            self.without_error = True

    def write_to_file(self):
        """
        This function calls "gui_batch.py" with inputs values to write the batch file.
        """
        bt = BatchFile(self.batch_name_value, self.p_values, self.x_value, self.y_value, self.g_value, self.s_value,
                       self.z_value, self.wavelength_values, self.verbose_value, self.phytoplankton_path,
                       self.bottom_path, self.nb_cpu, self.executive_path, self.saa_values,
                       self.sza_values, self.report_parameter_value)
        # bt.write_batch_to_file(str(self.batch_name_value + "_batch.txt"))
        bt.write_batch_to_file(str(self.batch_name_value + "_batch.txt"))

    def data_processing(self):
        """
        This function separates data, from the file to display curves, and will put them in the good arrays.
        """
        the_file_name = str(self.result_file)
        the_file = open(the_file_name, 'r')

        lines = the_file.readlines()

        # We put all lines in an array and we put each cell of the line in a column.
        lines_array = []
        for line in lines:
            line = line.split(',')  # Each time there is a tabulation, there is a new cell
            lines_array.append(line)

        labels_line = lines_array[0]
        cell_labels_line = 0  # Iterator on each cell of the line labels_line.
        flag = True  # Become FALSE when we find the word which separate data from wavelength values.

        try:
            while flag:  # While it is TRUE, so if the word doesn't match, it's an infinite loop,
                if "wave length (nm)" in labels_line[cell_labels_line]:
                    index = labels_line.index(labels_line[cell_labels_line])  # Find the index of the string searched.
                    flag = False
                else:
                    cell_labels_line += 1
        except IndexError:  # In case of an infinite loop.
            raise sys.exit("Warning : There is no value named 'wavelength' in the file used to plot curves. "
                           "So, I can't separate data to plot curves and data about tests linking with these curves.")

        self.information = []  # This array will contain the data displayed under the curves.
        data_wavelength = []  # This array will contain the data to plot curves.
        self.num_line = 0  # Iterator on each line of lines_array,
        # The array containing data about information and wavelength.
        for line in lines_array:
            cell_line = 0  # Iterator on each cell of the line.
            self.information.append([])
            data_wavelength.append([])
            while cell_line < len(line):
                if cell_line < index:
                    self.information[self.num_line].append(line[cell_line])
                elif cell_line > index:
                    data_wavelength[self.num_line].append(line[cell_line])
                cell_line += 1
            self.num_line += 1

        # We transform wavelengths from strings to floats.
        line_wavelength = 0  # Iterator on each line of data_wavelength
        for row_data_wavelength in data_wavelength:
            row_data_wavelength = [float(item.strip('\n').strip('\"')) for item in row_data_wavelength]
            data_wavelength[line_wavelength] = row_data_wavelength
            line_wavelength += 1

        self.wavelength = data_wavelength[0]  # The first line contains wavelength
        self.data_wanted = data_wavelength[1:]  # The others contain data useful to plot curves.

        the_file.close()

    def display_the_graphic(self, num_line, wavelength, data_wanted, information):
        """
        This function calls the class "MplCanvas" of "gui_matplotlibwidgetFile.py" to plot results.
        Inputs : num_line : The number of cases.
                 wavelength : The wavelengths.
                 data_wanted : The data for wavelengths.
                 information : The array which contains the information, of all curves to display.
        """
        self.nb_case = num_line - 1  # This is the number of line, the number of test.

        self.graphic_slider(self.nb_case)

        self.mpl_canvas.update_fields(wavelength, data_wanted, self.slider_value)

        # Following if the checkbox is checked "All curves" or not.
        if self.ui.show_grid.checkState() == 2:
            grid = True
        else:
            grid = False

        if self.ui.show_all_curves.checkState() == 2:
            self.flag_curves = True
            self.mpl_canvas.display_graphic(self.flag_curves, self.ui, grid)
            self.print_graphic_information(self.slider_value, information)
        else:
            self.flag_curves = False
            self.mpl_canvas.display_graphic(self.flag_curves, self.ui, grid)
            self.print_graphic_information(self.slider_value, information)

    def display_the_graphic_connection(self):
        """
        The following permits to attribute the function "display_the_graphic" to the slider.
        Because, to make a connection, we can not have parameters for the function, but "display_the_graphic" has some.
        """
        self.display_the_graphic(self.num_line, self.wavelength, self.data_wanted, self.information)

    def print_graphic_information(self, num_curve, information):
        """
        This function displays information about curves.
        Inputs ; num_curve ; The index of the curve's line that we have to display.
                 information ; The array which contains the information, of all curves to display.
        """
        """In this function, the best would to create labels each time we need to create one,
        following the number of labels in label_information.
        #self.essai = QtGui.QLabel(self.ui.tab)
        #self.essai.setGeometry(PyQt4.QtCore.QRect(870,650,111,16))
        #self.essai.setText("ESSAI")
        """

        label_information = information[0]
        data_information = information[1:]

        count_nb_label = 0  # Iterator on all labels of label_information
        nb_label = len(label_information)
        while count_nb_label <= nb_label:
            self.ui.column1_label.setText(label_information[0].strip('\"'))
            self.ui.column2_label.setText(label_information[1].strip('\"'))
            self.ui.column3_label.setText(label_information[2].strip('\"'))
            self.ui.column4_label.setText(label_information[3].strip('\"'))
            self.ui.column5_label.setText(label_information[4].strip('\"'))
            self.ui.column6_label.setText(label_information[5].strip('\"'))
            self.ui.column7_label.setText(label_information[6].strip('\"'))
            self.ui.column8_label.setText(label_information[7].strip('\"'))
            count_nb_label += 1

        line_of_data = 0  # Iterator on each line of data_information.
        while line_of_data < len(data_information):
            if line_of_data == num_curve:
                self.ui.column1_result.setText(data_information[line_of_data][0])
                self.ui.column2_result.setText(data_information[line_of_data][1])
                self.ui.column3_result.setText(data_information[line_of_data][2])
                self.ui.column4_result.setText(data_information[line_of_data][3])
                self.ui.column5_result.setText(data_information[line_of_data][4])
                self.ui.column6_result.setText(data_information[line_of_data][5])
                self.ui.column7_result.setText(data_information[line_of_data][6])
                self.ui.column8_result.setText(data_information[line_of_data][7])
            line_of_data += 1

    def graphic_slider(self, nb_case):
        """
        This function scales the slider for curves displayed.
        Input : The number of cases (curves).
        Return ; The slider value.
        """

        """
        The slider range is created each time we call this function. Search to set its range just when it is necessary.
        """

        self.ui.sens.setDisabled(False)
        self.ui.sens.setRange(0, int(nb_case - 1))
        self.slider_value = self.ui.sens.value()

        return self.slider_value

    def display_error_message(self):
        """
        This function displays an error message when a wrong value is typed.
        """
        self.ui.error_label.setScaledContents(True)  # Warning image shown.
        self.ui.error_text_label.show()  # Warning message shown.
        self.ui.error_text_label.setStyleSheet('color: red')

    def hide_error_message(self):
        """
        This function hides the error message when all values are correct.
        """
        self.ui.error_label.setScaledContents(False)  # Warning image hiden.
        self.ui.error_text_label.hide()  # Warning message hiden.

    def run(self):
        """
        This function executes planarRad using the batch file.
        """

        """
        Error when planarRad start : /bin/sh: 1: ../planarrad.py: not found
        """
        print('Executing planarrad')
        # If we are not in the reverse_mode :
        if self.ui.tabWidget.currentIndex() == TabWidget.NORMAL_MODE:
            self.data()
            self.check_values()

            if self.without_error == False:
                self.display_error_message()
            elif self.without_error == True:
                self.is_running = True
                self.hide_error_message()
                self.write_to_file()
                os.chdir('./')
                self.progress_bar()
                this_dir = os.path.dirname(os.path.realpath(__file__)).rstrip('gui/')
                batch_file = os.path.join(this_dir, "inputs/batch_files/" + str(self.batch_name_value) + "_batch.txt")
                print(batch_file)
                self.p = subprocess.Popen(
                    ["./planarrad.py -i " + batch_file],
                    shell=True)
                if self.ui.progressBar.value() == 100:
                    self.display_the_graphic(self.num_line, self.wavelength, self.data_wanted, self.information)

    def update(self):
        """
        This function updates the progress bar.
        """

        """
        Because PlanarRad doesn't lunch, no file created and no possible update of the progress bar.
        """
        # print('update')
        # no_empty = 0
        # number_of_percent = 0
        # result_folders = (os.listdir('outputs/' + self.batch_name_value))
        # nb_folders = len(result_folders)
        # nb_checked_folder = 1
        # for folder in result_folders:
        #     if self.is_running == True:
        #         if os.path.isfile("outputs/" + self.batch_name_value + "/" + folder + "/report.txt") & (
        #                     self.is_running == True):
        #             nb_checked_folder += 1
        #             if (os.path.getsize("outputs/" + self.batch_name_value + "/" + folder + "/report.txt") > 0) & (
        #                         self.is_running == True):
        #                 no_empty += 1
        #     else:
        #         break
        #     number_of_percent = (no_empty * 100 ) / nb_checked_folder
        #     self.ui.progressBar.setValue(number_of_percent)

    def total(self, total):
        """
        This function sets the progress bar with its maximum.
        """
        self.ui.progressBar.setMaximum(total)

    def progress_bar(self):
        """
        This function starts the thread created for the progress bar. Without it, the GUI will freeze..
        """
        self.t.start()

    def cancel_planarrad(self):
        """
        This function cancels PlanarRad.
        """

        """
        This function needs to be tested. We don't know if she works.
        """

        if (self.is_running == True) & (self.ui.tabWidget.currentIndex() == TabWidget.NORMAL_MODE):
            cancel = QtGui.QMessageBox.question(self.ui.cancel, 'Cancel PlanarRad', "Are you sure to cancel ?",
                                                QtGui.QMessageBox.Yes,
                                                QtGui.QMessageBox.No)

            if cancel == QtGui.QMessageBox.Yes:
                self.is_running = False
                os.kill(self.p.pid, signal.SIGTERM)

                print("Necessary to check if cancel_planarrad works well !")
                self.ui.progressBar.reset()
            else:
                pass

    def quit(self):
        """
        This function quits PlanarRad, checking if PlanarRad is running before.
        """

        """
        Nothing programmed for displaying a message box when the user clicks on the window cross in order to quit.
        """

        if self.is_running == True:
            warning_planarrad_running = QtGui.QMessageBox.warning(self.ui.quit, 'Warning !',
                                                                  "PlanarRad is running. Stop it before quit !",
                                                                  QtGui.QMessageBox.Ok)

        else:
            quit = QtGui.QMessageBox.question(self.ui.quit, 'Quit PlanarRad', "Are you sure to quit ?",
                                              QtGui.QMessageBox.Yes,
                                              QtGui.QMessageBox.No)
            if quit == QtGui.QMessageBox.Yes:
                QtGui.qApp.quit()

    def save_figure(self):
        """
        This function programs the button to save the figure displayed
        and save it in a png file in the current repository.
        """

        """
        Increment the name of the figure in order to not erase the previous figure if the user use always this method.
        The png file is put in the "Artists_saved" file localized in the "planarradpy" folder.
        """

        default_name = 'Default_figure.png'
        self.ui.graphic_widget.canvas.print_figure(default_name)

        src = './' + default_name
        dst = './Artists_saved'
        os.system("mv" + " " + src + " " + dst)

    def save_figure_as(self):
        """
        This function programs the button to save the figure displayed
        and save it in a png file where you want / with the name you want thanks to a file dialog.
        """
        self.file_name = QtGui.QFileDialog.getSaveFileName()
        self.file_name = self.file_name + ".png"
        self.ui.graphic_widget.canvas.print_figure(str(self.file_name))

    def open_about(self):
        """
        This function opens the default browser and go on the web page about planarRad and its GUI.
        """
        self.aboutWindow.show()

    def open_log_file(self):
        """
        The following opens the log file of PlanarRad.
        """
        """
        TO DO.
        """
        # webbrowser.open('https://marrabld.github.io/planarradpy/')
        f = open(os.path.expanduser('~/.planarradpy/log/libplanarradpy.log'))
        # self.uiLog.textEdit.setText(str(f.readlines()))
        self.uiLog.textEdit.setPlainText(str(f.read()))
        self.log_window.show()

    def open_documentation(self):
        """
        The following opens the documentation file.
        """
        """
        TO DO.
        """
        # webbrowser.open('https://marrabld.github.io/planarradpy/')

        window = Window()
        html = QtCore.QUrl.fromLocalFile(os.path.join(os.getcwd(), './docs/_build/html/index.html')) #open('./docs/_build/html/index.html').read()
        #window.show()
        window.view.load(html)
        window.show()
        window.exec_()

    def prerequisite_actions(self):
        """
        This function does all required actions at the beginning when we run the GUI.
        """
        self.hide_error_message()

        self.ui.show_all_curves.setDisabled(True)
        self.ui.sens.setDisabled(True)
        self.ui.show_grid.setDisabled(True)

        pathname = os.path.dirname(sys.argv[0])
        path = os.path.abspath(pathname)
        # self.phytoplankton_path = self.ui.phyto_path.setText(path.replace('gui', 'inputs/iop_files'))
        # self.bottom_path = self.ui.bottom_path.setText(path.replace('gui', 'inputs/bottom_files'))
        # self.executive_path = self.ui.exec_path.setText("Decide where will be 'jude2_install/bin'")
        self.verbose_value = self.ui.verbose_value.setText("6")
        self.report_parameter_value = self.ui.report_parameter_value.setText("Rrs")

        self.ui.progressBar.reset()

    def closeEvent(self, event):
        """
        The following asks to the user if he is sure to quit the GUI.
        """
        reply = QtGui.QMessageBox.question(self, 'Quit PlanarRad',
                                           "Are you sure to quit?", QtGui.QMessageBox.Yes, QtGui.QMessageBox.No)

        if reply == QtGui.QMessageBox.Yes:
            event.accept()
        else:
            event.ignore()

    def click(self, event):
        """
        This function intercepts the mouse's right click and its position.
        """
        if event.button == 3:
            if self.ui.tabWidget.currentIndex() == TabWidget.NORMAL_MODE:
                self.pos = QtGui.QCursor().pos()
                self.graphic_context_menu(self.pos)

    def mouse_move(self, event):
        """
        The following gets back coordinates of the mouse on the canvas.
        """
        if (self.ui.tabWidget.currentIndex() == TabWidget.NORMAL_MODE):
            self.posX = event.xdata
            self.posY = event.ydata

            self.graphic_target(self.posX, self.posY)

    def graphic_context_menu(self, pos):
        """
        This function will open a context menu on the graphic to save it.
        Inputs : pos : The position of the mouse cursor.
        """
        menu = QtGui.QMenu()
        self.actionSave_bis = menu.addAction("Save Figure")
        self.actionSave_as_bis = menu.addAction("Save Figure As ...")
        action = menu.exec_(self.table_widget.mapFromGlobal(pos))

        if action == self.actionSave_bis:
            self.save_figure()
        elif action == self.actionSave_as_bis:
            self.save_figure_as()

    def graphic_target(self, x, y):
        """
        The following update labels about mouse coordinates.
        """

        if self.authorized_display == True:
            try:
                self.display_the_graphic(self.num_line, self.wavelength, self.data_wanted, self.information)
                self.ui.mouse_coordinate.setText("(%0.3f, %0.3f)" % (x, y))
            except:
                pass

# fileName = self.dalecFileName=QtGui.QFileDialog.getSaveFileName(parent=None, caption='Save Figure',filter ='*.jpg *.bmp *.png *.pdf *.ps *.eps')

class GuiThread(QtCore.QThread):
    def __init__(self, parent, n):
        QtCore.QThread.__init__(self)
        self.n = n

    def run(self):
        self.emit(QtCore.SIGNAL("total(PyQt_PyObject)"), self.n)
        i = 0
        while i < self.n:
            if (time.time() % 1 == 0):
                i += 1
                # print str(i)
                self.emit(QtCore.SIGNAL("update()"))

class TabWidget():
    """
    This class is for the state machine about if we are in the first tab or in the second tab.
    """
    NORMAL_MODE = 0
    REVERSE_MODE = 1

class Window(QWidget):
    def __init__(self):
        super(Window, self).__init__()
        self.view = QWebView(self)

        layout = QVBoxLayout(self)
        layout.setMargin(0)
        layout.addWidget(self.view)

if __name__ == "__main__":
    FE = FormEvents()