def __init__(self, name=None, source=None, elements=None, use_color=True):
        """
        Params:
            name (str): A unique string that describes the theme
            source (str): A string that describes the source of the theme:
                built-in - Should only be used when Theme() is called directly
                preset - Themes packaged with rtv
                installed - Themes in ~/.config/rtv/themes/
                custom - When a filepath is explicitly provided, e.g.
                    ``rtv --theme=/path/to/theme_file.cfg``
            elements (dict): The theme's element map, should be in the same
                format as Theme.DEFAULT_THEME.
        """

        if source not in (None, 'built-in', 'preset', 'installed', 'custom'):
            raise ValueError('Invalid source')

        if name is None and source is None:
            name = 'default' if use_color else 'monochrome'
            source = 'built-in'
        elif name is None or source is None:
            raise ValueError('Must specify both `name` and `source`, or neither one')

        self.name = name
        self.source = source
        self.use_color = use_color

        self._color_pair_map = None
        self._attribute_map = None
        self._selected = None

        self.required_color_pairs = 0
        self.required_colors = 0

        if elements is None:
            elements = self.DEFAULT_ELEMENTS.copy()

        # Set any elements that weren't defined by the config to fallback to
        # the default color and attributes
        for key in self.DEFAULT_ELEMENTS.keys():
            if key not in elements:
                elements[key] = (None, None, None)

        self._set_fallback(elements, 'Normal', (-1, -1, curses.A_NORMAL))
        self._set_fallback(elements, 'Selected', 'Normal')
        self._set_fallback(elements, 'SelectedCursor', 'Normal')

        # Create the "Selected" versions of elements, which are prefixed with
        # the @ symbol. For example, "@CommentText" represents how comment
        # text is formatted when it is highlighted by the cursor.
        for key in self.DEFAULT_THEME['normal']:
            dest = '@{0}'.format(key)
            self._set_fallback(elements, key, 'Selected', dest)
        for key in self.DEFAULT_THEME['cursor']:
            dest = '@{0}'.format(key)
            self._set_fallback(elements, key, 'SelectedCursor', dest)

        # Fill in the ``None`` values for all of the elements with normal text
        for key in self.DEFAULT_THEME['normal']:
            self._set_fallback(elements, key, 'Normal')
        for key in self.DEFAULT_THEME['cursor']:
            self._set_fallback(elements, key, 'Normal')
        for key in self.DEFAULT_THEME['page']:
            self._set_fallback(elements, key, 'Normal')

        self.elements = elements

        if self.use_color:
            # Pre-calculate how many colors / color pairs the theme will need
            colors, color_pairs = set(), set()
            for fg, bg, _ in self.elements.values():
                colors.add(fg)
                colors.add(bg)
                color_pairs.add((fg, bg))

            # Don't count the default (-1, -1) as a color pair because it
            # doesn't need to be initialized by curses.init_pair().
            color_pairs.discard((-1, -1))
            self.required_color_pairs = len(color_pairs)

            # Determine how many colors the terminal needs to support in order
            # to be able to use the theme. This uses the common breakpoints
            # that 99% of terminals follow and doesn't take into account
            # 88 color themes.
            self.required_colors = None
            for marker in [0, 8, 16, 256]:
                if max(colors) < marker:
                    self.required_colors = marker
                    break

        Params:
            name (str): A unique string that describes the theme
            source (str): A string that describes the source of the theme:
                built-in - Should only be used when Theme() is called directly
                preset - Themes packaged with rtv
                installed - Themes in ~/.config/rtv/themes/
                custom - When a filepath is explicitly provided, e.g.
                    ``rtv --theme=/path/to/theme_file.cfg``
            elements (dict): The theme's element map, should be in the same
                format as Theme.DEFAULT_THEME.
        
rtv/theme.py