Parse a human readable key sequence.

        If no error is raised, then ``keyString`` could be
        successfully converted into a valid key sequence and is
        henceforth represented by this object.

        |Args|

        * ``keyString`` (**QtmacsKeysequence**): eg. "<ctrl>+f"

        |Returns|

        **None**

        |Raises|

        * **QtmacsKeysequenceError** if ``keyString`` could not be parsed.

def str2key(self, keyString):
        """
        Parse a human readable key sequence.

        If no error is raised, then ``keyString`` could be
        successfully converted into a valid key sequence and is
        henceforth represented by this object.

        |Args|

        * ``keyString`` (**QtmacsKeysequence**): eg. "<ctrl>+f"

        |Returns|

        **None**

        |Raises|

        * **QtmacsKeysequenceError** if ``keyString`` could not be parsed.
        """

        # Ensure the string is non-empty.
        if keyString == '':
            raise QtmacsKeysequenceError('Cannot parse empty string')

        tmp = str(keyString)
        tmp = tmp.replace('<', '&lt;')
        tmp = tmp.replace('>', '&gt;')
        keyStringHtml = '<b>{}</b>.'.format(tmp)
        del tmp

        # Remove leading and trailing white spaces, and reduce
        # sequences of white spaces to a single white space. If this
        # results in an emtpy string (typically the case when the user
        # tries to register a white space with ' ' instead of with
        # '<space>') then raise an error.
        rawKeyStr = keyString.strip()
        if len(rawKeyStr) == 0:
            msg = 'Cannot parse the key combination {}.'.format(keyStringHtml)
            raise QtmacsKeysequenceError(msg)

        # Split the string at these white spaces and convert eg.
        # " <ctrl>+x <ctrl>+f " first into
        # "<ctrl>+x <ctrl>+f" and from there into the list of
        # individual key combinations ["<ctrl>+x", "<ctrl>+f"].
        rawKeyStr = re.sub(' +', ' ', rawKeyStr)
        rawKeyStr = rawKeyStr.split(' ')

        # Now process the key combinations one by one. By definition.
        for key in rawKeyStr:
            # Find all bracketed keys in the key combination
            # (eg. <ctrl>, <space>).
            desc_keys = re.findall('<.*?>', key)

            # There are four possibilities:
            #   * no bracketed key (eg. "x" or "X")
            #   * one bracketed key (eg. "<ctrl>+x", or just "<space>")
            #   * two bracketed keys (eg. "<ctrl>+<space>" or "<ctrl>+<alt>+f")
            #   * three bracketed keys (eg. <ctrl>+<alt>+<space>).
            if len(desc_keys) == 0:
                # No bracketed key means no modifier, so the key must
                # stand by itself.
                modStr = ['<NONE>']
                keyStr = key
            elif len(desc_keys) == 1:
                if '+' not in key:
                    # If no '+' sign is present then it must be
                    # bracketed key without any modifier
                    # (eg. "<space>").
                    modStr = ['<NONE>']
                    keyStr = key
                else:
                    # Since a '+' sign and exactly one bracketed key
                    # is available, it must be a modifier plus a
                    # normal key (eg. "<ctrl>+f", "<alt>++").
                    idx = key.find('+')
                    modStr = [key[:idx]]
                    keyStr = key[idx + 1:]
            elif len(desc_keys) == 2:
                # There are either two modifiers and a normal key
                # (eg. "<ctrl>+<alt>+x") or one modifier and one
                # bracketed key (eg. "<ctrl>+<space>").
                if (key.count('+') == 0) or (key.count('+') > 3):
                    # A valid key combination must feature at least
                    # one- and at most three "+" symbols.
                    msg = 'Cannot parse the key combination {}.'
                    msg = msg.format(keyStringHtml)
                    raise QtmacsKeysequenceError(msg)
                elif key.count('+') == 1:
                    # One modifier and one bracketed key
                    # (eg. "<ctrl>+<space>").
                    idx = key.find('+')
                    modStr = [key[:idx]]
                    keyStr = key[idx + 1:]
                elif (key.count('+') == 2) or (key.count('+') == 3):
                    # Two modifiers and one normal key
                    # (eg. "<ctrl>+<alt>+f", "<ctrl>+<alt>++").
                    idx1 = key.find('+')
                    idx2 = key.find('+', idx1 + 1)
                    modStr = [key[:idx1], key[idx1 + 1:idx2]]
                    keyStr = key[idx2 + 1:]
            elif len(desc_keys) == 3:
                if key.count('+') == 2:
                    # There are two modifiers and one bracketed key
                    # (eg. "<ctrl>+<alt>+<space>").
                    idx1 = key.find('+')
                    idx2 = key.find('+', idx1 + 1)
                    modStr = [key[:idx1], key[idx1 + 1:idx2]]
                    keyStr = key[idx2 + 1:]
                else:
                    # A key combination with three bracketed entries
                    # must have exactly two '+' symbols. It cannot be
                    # valid otherwise.
                    msg = 'Cannot parse the key combination {}.'
                    msg = msg.format(keyStringHtml)
                    raise QtmacsKeysequenceError(msg)
            else:
                msg = 'Cannot parse the key combination {}.'
                msg = msg.format(keyStringHtml)
                raise QtmacsKeysequenceError(msg)

            # The dictionary keys that map the modifiers and bracketed
            # keys to Qt constants are all upper case by
            # convention. Therefore, convert all modifier keys and
            # bracketed normal keys.
            modStr = [_.upper() for _ in modStr]
            if (keyStr[0] == '<') and (keyStr[-1] == '>'):
                keyStr = keyStr.upper()

            # Convert the text version of the modifier key into the
            # QFlags structure used by Qt by "or"ing them
            # together. The loop is necessary because more than one
            # modifier may be active (eg. <ctrl>+<alt>).
            modQt = QtCore.Qt.NoModifier
            for mod in modStr:
                # Ensure that the modifier actually exists (eg. the
                # user might have made type like "<ctlr>" instead of
                # "<ctrl>"). Also, the keys in the dictionary consist
                # of only upper case letter for the modifier keys.
                if mod not in self.modDict:
                    msg = 'Cannot parse the key combination {}.'
                    msg = msg.format(keyStringHtml)
                    raise QtmacsKeysequenceError(msg)

                # Since the modifier exists in the dictionary, "or"
                # them with the other flags.
                modQt = modQt | self.modDict[mod]

            # Repeat the modifier procedure for the key. However,
            # unlike for the modifiers, no loop is necessary here
            # because only one key can be pressed at the same time.
            if keyStr in self.keyDict:
                modQt_shift, keyQt = self.keyDict[keyStr]
            else:
                msg = 'Cannot parse the key combination {}.'
                msg = msg.format(keyStringHtml)
                raise QtmacsKeysequenceError(msg)

            # Construct a new QKeyEvent. Note that the general
            # modifier (ie. <ctrl> and <alt>) still need to be
            # combined with shift modifier if the key demands it. This
            # combination is a simple "or" on the QFlags structure.
            # Also note that the "text" argument is omitted because Qt
            # is smart enough to determine it internally.
            key_event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, keyQt,
                                        modQt | modQt_shift)

            # Finally, append this key to the key sequence represented
            # by this object.
            self.appendQKeyEvent(key_event)